[gthumb/ext: 1/79] Imported my local branch
- From: Paolo Bacchilega <paobac src gnome org>
- To: svn-commits-list gnome org
- Cc:
- Subject: [gthumb/ext: 1/79] Imported my local branch
- Date: Sun, 2 Aug 2009 20:22:47 +0000 (UTC)
commit 681533813c73a9d31aa8c0bb7f0246c502889cfa
Author: Paolo Bacchilega <paobac src gnome org>
Date: Tue Jun 9 12:54:38 2009 +0200
Imported my local branch
.cvsignore | 46 -
AUTHORS | 3 -
MAINTAINERS | 2 +-
Makefile.am | 86 +-
NEWS | 15 -
README | 6 +-
add-include-prefix | 1 -
autogen.sh | 6 +-
configure.ac | 221 +
configure.in | 369 -
copy-n-paste/Makefile.am | 16 +
copy-n-paste/eggdesktopfile.c | 1477 ++++
copy-n-paste/eggdesktopfile.h | 159 +
copy-n-paste/eggsmclient-private.h | 53 +
copy-n-paste/eggsmclient-xsmp.c | 1370 +++
copy-n-paste/eggsmclient.c | 589 ++
copy-n-paste/eggsmclient.h | 117 +
data/.cvsignore | 6 -
data/GNOME_GThumb.server | 16 -
data/Makefile.am | 25 +-
data/albumthemes/.cvsignore | 2 -
data/albumthemes/BestFit/.cvsignore | 1 -
data/albumthemes/BestFit/BestFit.css | 369 -
data/albumthemes/BestFit/BestFit.js | 260 -
data/albumthemes/BestFit/Makefile.am | 21 -
data/albumthemes/BestFit/back.png | Bin 714 -> 0 bytes
data/albumthemes/BestFit/image.gthtml | 282 -
data/albumthemes/BestFit/index.gthtml | 92 -
data/albumthemes/BestFit/index.js | 147 -
data/albumthemes/BestFit/lib.js | 246 -
data/albumthemes/BestFit/next.png | Bin 1110 -> 0 bytes
data/albumthemes/BestFit/prev.png | Bin 1152 -> 0 bytes
data/albumthemes/BestFit/preview.png | Bin 19598 -> 0 bytes
data/albumthemes/BestFit/thumbnail.gthtml | 163 -
data/albumthemes/Classic/.cvsignore | 2 -
data/albumthemes/Classic/1.gif | Bin 43 -> 0 bytes
data/albumthemes/Classic/Makefile.am | 25 -
data/albumthemes/Classic/back.png | Bin 448 -> 0 bytes
data/albumthemes/Classic/background.gif | Bin 43 -> 0 bytes
data/albumthemes/Classic/bot.png | Bin 331 -> 0 bytes
data/albumthemes/Classic/image.gthtml | 194 -
data/albumthemes/Classic/index.gthtml | 103 -
data/albumthemes/Classic/layout.css | 142 -
data/albumthemes/Classic/left.png | Bin 119 -> 0 bytes
data/albumthemes/Classic/next.png | Bin 1040 -> 0 bytes
data/albumthemes/Classic/prev.png | Bin 1063 -> 0 bytes
data/albumthemes/Classic/preview.png | Bin 37132 -> 0 bytes
data/albumthemes/Classic/right.png | Bin 182 -> 0 bytes
data/albumthemes/Classic/style.css | 93 -
data/albumthemes/Classic/thumbnail.gthtml | 71 -
data/albumthemes/Classic/top.png | Bin 202 -> 0 bytes
data/albumthemes/ClassicClips/.cvsignore | 2 -
data/albumthemes/ClassicClips/1.gif | Bin 68 -> 0 bytes
data/albumthemes/ClassicClips/Makefile.am | 26 -
data/albumthemes/ClassicClips/back.png | Bin 774 -> 0 bytes
data/albumthemes/ClassicClips/background.gif | Bin 43 -> 0 bytes
data/albumthemes/ClassicClips/bot.png | Bin 738 -> 0 bytes
data/albumthemes/ClassicClips/image.gthtml | 194 -
data/albumthemes/ClassicClips/index.gthtml | 103 -
data/albumthemes/ClassicClips/layout.css | 142 -
data/albumthemes/ClassicClips/left.png | Bin 452 -> 0 bytes
data/albumthemes/ClassicClips/next.png | Bin 1174 -> 0 bytes
data/albumthemes/ClassicClips/prev.png | Bin 1200 -> 0 bytes
data/albumthemes/ClassicClips/preview.png | Bin 39785 -> 0 bytes
data/albumthemes/ClassicClips/right.png | Bin 493 -> 0 bytes
data/albumthemes/ClassicClips/style.css | 93 -
data/albumthemes/ClassicClips/thumbnail.gthtml | 71 -
data/albumthemes/ClassicClips/top.png | Bin 653 -> 0 bytes
data/albumthemes/Flicker/.cvsignore | 2 -
data/albumthemes/Flicker/Makefile.am | 16 -
data/albumthemes/Flicker/image.gthtml | 213 -
data/albumthemes/Flicker/index.gthtml | 85 -
data/albumthemes/Flicker/layout.css | 118 -
data/albumthemes/Flicker/preview.png | Bin 23944 -> 0 bytes
data/albumthemes/Flicker/style.css | 103 -
data/albumthemes/Flicker/thumbnail.gthtml | 54 -
data/albumthemes/Makefile.am | 4 -
data/albumthemes/NeatRound/.cvsignore | 2 -
data/albumthemes/NeatRound/1.gif | Bin 68 -> 0 bytes
data/albumthemes/NeatRound/Makefile.am | 25 -
data/albumthemes/NeatRound/back.png | Bin 860 -> 0 bytes
data/albumthemes/NeatRound/background.gif | Bin 43 -> 0 bytes
data/albumthemes/NeatRound/bot.png | Bin 721 -> 0 bytes
data/albumthemes/NeatRound/image.gthtml | 194 -
data/albumthemes/NeatRound/index.gthtml | 103 -
data/albumthemes/NeatRound/layout.css | 141 -
data/albumthemes/NeatRound/left.png | Bin 144 -> 0 bytes
data/albumthemes/NeatRound/next.png | Bin 1417 -> 0 bytes
data/albumthemes/NeatRound/prev.png | Bin 1443 -> 0 bytes
data/albumthemes/NeatRound/preview.png | Bin 26197 -> 0 bytes
data/albumthemes/NeatRound/right.png | Bin 210 -> 0 bytes
data/albumthemes/NeatRound/style.css | 95 -
data/albumthemes/NeatRound/thumbnail.gthtml | 71 -
data/albumthemes/NeatRound/top.png | Bin 459 -> 0 bytes
data/albumthemes/Wiki/.cvsignore | 2 -
data/albumthemes/Wiki/Makefile.am | 16 -
data/albumthemes/Wiki/image.gthtml | 156 -
data/albumthemes/Wiki/index.gthtml | 70 -
data/albumthemes/Wiki/layout.css | 90 -
data/albumthemes/Wiki/preview.png | Bin 37324 -> 0 bytes
data/albumthemes/Wiki/style.css | 79 -
data/albumthemes/Wiki/thumbnail.gthtml | 64 -
data/albumthemes/text.h | 42 -
data/glade/.cvsignore | 4 -
data/glade/Makefile.am | 25 -
data/glade/ftp-client-48.png | Bin 3252 -> 0 bytes
data/glade/gphoto-48.png | Bin 2781 -> 0 bytes
data/glade/gthumb.glade | 2665 ------
data/glade/gthumb_camera.glade | 669 --
data/glade/gthumb_comments.glade | 695 --
data/glade/gthumb_convert.glade | 1612 ----
data/glade/gthumb_crop.glade | 1308 ---
data/glade/gthumb_edit.glade | 2532 ------
data/glade/gthumb_png_exporter.glade | 3201 -------
data/glade/gthumb_preferences.glade | 2124 -----
data/glade/gthumb_print.glade | 2071 -----
data/glade/gthumb_redeye.glade | 804 --
data/glade/gthumb_search.glade | 2015 -----
data/glade/gthumb_tools.glade | 4526 ----------
data/glade/gthumb_web_exporter.glade | 2323 ------
data/glade/volume-mute.png | Bin 972 -> 0 bytes
data/gthumb-import.desktop.in | 16 -
data/gthumb.schemas.in | 1780 +----
data/ui/Makefile.am | 11 +
data/ui/bookmarks.ui | 130 +
data/ui/filter-editor.ui | 207 +
data/ui/personalize-filters.ui | 202 +
data/ui/preferences.ui | 590 ++
data/ui/sort-order.ui | 94 +
extensions/Makefile.am | 3 +
extensions/catalogs/Makefile.am | 40 +
extensions/catalogs/actions.c | 357 +
extensions/catalogs/actions.h | 37 +
extensions/catalogs/callbacks.c | 255 +
extensions/catalogs/callbacks.h | 39 +
extensions/catalogs/catalogs.extension.in.in | 10 +
extensions/catalogs/data/Makefile.am | 2 +
extensions/catalogs/data/ui/Makefile.am | 5 +
extensions/catalogs/data/ui/add-to-catalog.ui | 192 +
extensions/catalogs/dlg-add-to-catalog.c | 406 +
extensions/catalogs/dlg-add-to-catalog.h | 34 +
extensions/catalogs/gth-catalog.c | 713 ++
extensions/catalogs/gth-catalog.h | 111 +
extensions/catalogs/gth-file-source-catalogs.c | 466 ++
extensions/catalogs/gth-file-source-catalogs.h | 52 +
extensions/catalogs/main.c | 69 +
extensions/comments/Makefile.am | 38 +
extensions/comments/comments.extension.in.in | 10 +
extensions/comments/data/Makefile.am | 2 +
extensions/comments/data/ui/Makefile.am | 5 +
extensions/comments/data/ui/edit-comment-page.ui | 125 +
extensions/comments/gth-comment.c | 501 ++
extensions/comments/gth-comment.h | 79 +
extensions/comments/gth-edit-comment-page.c | 291 +
extensions/comments/gth-edit-comment-page.h | 54 +
.../comments/gth-metadata-provider-comment.c | 247 +
.../comments/gth-metadata-provider-comment.h | 54 +
extensions/comments/gth-test-category.c | 397 +
extensions/comments/gth-test-category.h | 54 +
extensions/comments/main.c | 125 +
extensions/exiv2/Makefile.am | 35 +
extensions/exiv2/exiv2-utils.cpp | 605 ++
extensions/exiv2/exiv2-utils.h | 40 +
extensions/exiv2/exiv2.extension.in.in | 10 +
extensions/exiv2/gth-metadata-provider-exiv2.c | 169 +
extensions/exiv2/gth-metadata-provider-exiv2.h | 54 +
extensions/exiv2/main.c | 173 +
extensions/file_manager/Makefile.am | 34 +
extensions/file_manager/actions.c | 195 +
extensions/file_manager/actions.h | 39 +
extensions/file_manager/callbacks.c | 257 +
extensions/file_manager/callbacks.h | 35 +
.../file_manager/file_manager.extension.in.in | 10 +
extensions/file_manager/gth-duplicate-task.c | 241 +
extensions/file_manager/gth-duplicate-task.h | 56 +
extensions/file_manager/main.c | 55 +
extensions/file_viewer/Makefile.am | 30 +
extensions/file_viewer/file_viewer.extension.in.in | 10 +
extensions/file_viewer/gth-file-viewer-page.c | 200 +
extensions/file_viewer/gth-file-viewer-page.h | 54 +
extensions/file_viewer/main.c | 53 +
extensions/image_tools/Makefile.am | 40 +
extensions/image_tools/data/Makefile.am | 2 +
extensions/image_tools/data/ui/Makefile.am | 5 +
extensions/image_tools/data/ui/crop-options.ui | 413 +
extensions/image_tools/gth-image-tool-crop.c | 528 ++
extensions/image_tools/gth-image-tool-crop.h | 54 +
extensions/image_tools/gth-image-tool-desaturate.c | 161 +
extensions/image_tools/gth-image-tool-desaturate.h | 52 +
extensions/image_tools/gth-image-tool-redo.c | 118 +
extensions/image_tools/gth-image-tool-redo.h | 52 +
extensions/image_tools/gth-image-tool-save.c | 117 +
extensions/image_tools/gth-image-tool-save.h | 52 +
extensions/image_tools/gth-image-tool-undo.c | 117 +
extensions/image_tools/gth-image-tool-undo.h | 52 +
extensions/image_tools/image_tools.extension.in.in | 11 +
extensions/image_tools/main.c | 64 +
extensions/image_viewer/Makefile.am | 36 +
extensions/image_viewer/data/Makefile.am | 19 +
.../data/gthumb-image-viewer.schemas.in | 97 +
extensions/image_viewer/data/ui/Makefile.am | 5 +
.../data/ui/image-viewer-preferences.ui | 291 +
extensions/image_viewer/gth-image-viewer-page.c | 1113 +++
extensions/image_viewer/gth-image-viewer-page.h | 61 +
.../image_viewer/gth-metadata-provider-image.c | 158 +
.../image_viewer/gth-metadata-provider-image.h | 54 +
.../image_viewer/image_viewer.extension.in.in | 10 +
extensions/image_viewer/main.c | 72 +
extensions/image_viewer/preferences.c | 162 +
extensions/image_viewer/preferences.h | 40 +
extensions/search/Makefile.am | 40 +
extensions/search/actions.c | 263 +
extensions/search/actions.h | 34 +
extensions/search/callbacks.c | 204 +
extensions/search/callbacks.h | 36 +
extensions/search/data/Makefile.am | 2 +
extensions/search/data/ui/Makefile.am | 5 +
extensions/search/data/ui/search-editor.ui | 122 +
extensions/search/gth-search-editor-dialog.c | 330 +
extensions/search/gth-search-editor-dialog.h | 58 +
extensions/search/gth-search-task.c | 436 +
extensions/search/gth-search-task.h | 57 +
extensions/search/gth-search.c | 413 +
extensions/search/gth-search.h | 68 +
extensions/search/main.c | 55 +
extensions/search/search.extension.in.in | 11 +
git.mk | 168 +-
gthumb.doap | 6 +-
gthumb/Makefile.am | 296 +
{libgthumb => gthumb}/cursors/Makefile.am | 0
{libgthumb => gthumb}/cursors/hand-closed-data.xbm | 0
{libgthumb => gthumb}/cursors/hand-closed-mask.xbm | 0
{libgthumb => gthumb}/cursors/hand-open-data.xbm | 0
{libgthumb => gthumb}/cursors/hand-open-mask.xbm | 0
{libgthumb => gthumb}/cursors/void-data.xbm | 0
{libgthumb => gthumb}/cursors/void-mask.xbm | 0
gthumb/dlg-bookmarks.c | 238 +
gthumb/dlg-bookmarks.h | 30 +
gthumb/dlg-edit-metadata.c | 124 +
gthumb/dlg-edit-metadata.h | 30 +
gthumb/dlg-personalize-filters.c | 619 ++
gthumb/dlg-personalize-filters.h | 30 +
gthumb/dlg-preferences.c | 303 +
gthumb/dlg-preferences.h | 30 +
gthumb/dlg-sort-order.c | 168 +
gthumb/dlg-sort-order.h | 30 +
gthumb/dom.c | 775 ++
gthumb/dom.h | 181 +
gthumb/egg-macros.h | 154 +
gthumb/eggfileformatchooser.c | 1190 +++
gthumb/eggfileformatchooser.h | 84 +
gthumb/file-cache.c | 340 +
gthumb/file-cache.h | 50 +
gthumb/gconf-utils.c | 958 +++
gthumb/gconf-utils.h | 121 +
gthumb/gedit-message-area.c | 646 ++
gthumb/gedit-message-area.h | 131 +
gthumb/gio-utils.c | 2088 +++++
gthumb/gio-utils.h | 193 +
gthumb/glib-utils.c | 1895 +++++
gthumb/glib-utils.h | 216 +
gthumb/gnome-desktop-thumbnail.c | 1259 +++
gthumb/gnome-desktop-thumbnail.h | 106 +
gthumb/gnome-thumbnail-pixbuf-utils.c | 172 +
gthumb/gth-browser-actions-callbacks.c | 379 +
gthumb/gth-browser-actions-callbacks.h | 65 +
gthumb/gth-browser-actions-entries.h | 215 +
gthumb/gth-browser-ui.h | 204 +
gthumb/gth-browser.c | 3707 +++++++++
gthumb/gth-browser.h | 148 +
gthumb/gth-cell-renderer-thumbnail.c | 606 ++
gthumb/gth-cell-renderer-thumbnail.h | 55 +
gthumb/gth-cursors.c | 118 +
gthumb/gth-cursors.h | 47 +
gthumb/gth-dumb-notebook.c | 270 +
gthumb/gth-dumb-notebook.h | 59 +
gthumb/gth-duplicable.c | 57 +
gthumb/gth-duplicable.h | 50 +
gthumb/gth-edit-metadata-dialog.c | 220 +
gthumb/gth-edit-metadata-dialog.h | 84 +
gthumb/gth-embedded-dialog.c | 213 +
gthumb/gth-embedded-dialog.h | 66 +
gthumb/gth-empty-list.c | 373 +
gthumb/gth-empty-list.h | 61 +
gthumb/gth-extensions.c | 637 ++
gthumb/gth-extensions.h | 155 +
gthumb/gth-file-data.c | 418 +
gthumb/gth-file-data.h | 125 +
gthumb/gth-file-list.c | 1227 +++
gthumb/gth-file-list.h | 101 +
gthumb/gth-file-properties.c | 407 +
gthumb/gth-file-properties.h | 55 +
gthumb/gth-file-selection.c | 116 +
gthumb/gth-file-selection.h | 75 +
gthumb/gth-file-source-vfs.c | 581 ++
gthumb/gth-file-source-vfs.h | 56 +
gthumb/gth-file-source.c | 576 ++
gthumb/gth-file-source.h | 143 +
gthumb/gth-file-store.c | 1279 +++
gthumb/gth-file-store.h | 117 +
gthumb/gth-file-view.c | 159 +
gthumb/gth-file-view.h | 115 +
gthumb/gth-filter-editor-dialog.c | 560 ++
gthumb/gth-filter-editor-dialog.h | 61 +
gthumb/gth-filter-file.c | 278 +
gthumb/gth-filter-file.h | 59 +
gthumb/gth-filter.c | 582 ++
gthumb/gth-filter.h | 78 +
gthumb/gth-filterbar.c | 480 ++
gthumb/gth-filterbar.h | 65 +
gthumb/gth-folder-tree.c | 1524 ++++
gthumb/gth-folder-tree.h | 110 +
gthumb/gth-hook.c | 347 +
gthumb/gth-hook.h | 49 +
gthumb/gth-icon-cache.c | 144 +
gthumb/gth-icon-cache.h | 42 +
gthumb/gth-icon-view.c | 451 +
gthumb/gth-icon-view.h | 60 +
gthumb/gth-image-dragger.c | 303 +
gthumb/gth-image-dragger.h | 60 +
gthumb/gth-image-history.c | 324 +
gthumb/gth-image-history.h | 85 +
gthumb/gth-image-loader.c | 912 ++
gthumb/gth-image-loader.h | 92 +
gthumb/gth-image-preloader.c | 631 ++
gthumb/gth-image-preloader.h | 75 +
gthumb/gth-image-selector.c | 1544 ++++
gthumb/gth-image-selector.h | 98 +
gthumb/gth-image-tool.c | 118 +
gthumb/gth-image-tool.h | 75 +
gthumb/gth-image-viewer.c | 2692 ++++++
gthumb/gth-image-viewer.h | 285 +
gthumb/gth-location-chooser.c | 737 ++
gthumb/gth-location-chooser.h | 67 +
gthumb/gth-main-default-hooks.c | 144 +
gthumb/gth-main-default-metadata.c | 62 +
gthumb/gth-main-default-sort-types.c | 103 +
gthumb/gth-main-default-tests.c | 146 +
gthumb/gth-main-default-types.c | 34 +
gthumb/gth-main.c | 972 +++
gthumb/gth-main.h | 121 +
gthumb/gth-marshal.list | 8 +
gthumb/gth-metadata-provider-file.c | 170 +
gthumb/gth-metadata-provider-file.h | 58 +
gthumb/gth-metadata-provider.c | 501 ++
gthumb/gth-metadata-provider.h | 84 +
gthumb/gth-metadata.c | 233 +
gthumb/gth-metadata.h | 81 +
gthumb/gth-monitor.c | 293 +
gthumb/gth-monitor.h | 97 +
gthumb/gth-multipage.c | 231 +
gthumb/gth-multipage.h | 81 +
gthumb/gth-nav-window.c | 574 ++
gthumb/gth-nav-window.h | 56 +
gthumb/gth-pixbuf-task.c | 318 +
gthumb/gth-pixbuf-task.h | 99 +
gthumb/gth-preferences.c | 210 +
gthumb/gth-preferences.h | 116 +
gthumb/gth-sidebar.c | 243 +
gthumb/gth-sidebar.h | 88 +
gthumb/gth-source-tree.c | 424 +
gthumb/gth-source-tree.h | 61 +
gthumb/gth-statusbar.c | 149 +
gthumb/gth-statusbar.h | 61 +
gthumb/gth-stock.h | 33 +
gthumb/gth-string-list.c | 119 +
gthumb/gth-string-list.h | 58 +
gthumb/gth-task.c | 201 +
gthumb/gth-task.h | 85 +
gthumb/gth-test-chain.c | 329 +
gthumb/gth-test-chain.h | 75 +
gthumb/gth-test-selector.c | 414 +
gthumb/gth-test-selector.h | 66 +
gthumb/gth-test-simple.c | 1021 +++
gthumb/gth-test-simple.h | 69 +
gthumb/gth-test.c | 426 +
gthumb/gth-test.h | 117 +
gthumb/gth-thumb-loader.c | 665 ++
gthumb/gth-thumb-loader.h | 85 +
gthumb/gth-time.c | 275 +
gthumb/gth-time.h | 65 +
gthumb/gth-toolbox.c | 252 +
gthumb/gth-toolbox.h | 75 +
gthumb/gth-uri-list.c | 341 +
gthumb/gth-uri-list.h | 67 +
gthumb/gth-user-dir.c | 109 +
gthumb/gth-user-dir.h | 47 +
gthumb/gth-viewer-page.c | 124 +
gthumb/gth-viewer-page.h | 90 +
gthumb/gth-window-actions-callbacks.c | 31 +
gthumb/gth-window-actions-callbacks.h | 32 +
gthumb/gth-window-actions-entries.h | 38 +
gthumb/gth-window.c | 346 +
gthumb/gth-window.h | 87 +
{libgthumb => gthumb}/gthumb-error.c | 0
gthumb/gthumb-error.h | 35 +
gthumb/gthumb.h.template | 26 +
gthumb/gtk-utils.c | 1011 +++
gthumb/gtk-utils.h | 120 +
gthumb/icons/Makefile.am | 5 +
{libgthumb => gthumb}/icons/nav_button.xpm | 0
gthumb/main.c | 345 +
gthumb/make-header.sh | 11 +
gthumb/pixbuf-io.c | 292 +
gthumb/pixbuf-io.h | 72 +
gthumb/pixbuf-utils.c | 516 ++
gthumb/pixbuf-utils.h | 71 +
gthumb/typedefs.h | 99 +
gthumb/zlib-utils.c | 87 +
gthumb/zlib-utils.h | 37 +
libgthumb/.cvsignore | 11 -
libgthumb/Makefile.am | 176 -
libgthumb/async-pixbuf-ops.c | 1441 ----
libgthumb/async-pixbuf-ops.h | 73 -
libgthumb/bookmarks.c | 440 -
libgthumb/bookmarks.h | 76 -
libgthumb/catalog.c | 522 --
libgthumb/catalog.h | 69 -
libgthumb/comments.c | 905 --
libgthumb/comments.h | 80 -
libgthumb/cursors.c | 110 -
libgthumb/cursors.h | 40 -
libgthumb/cursors/.cvsignore | 2 -
libgthumb/dlg-save-image.c | 649 --
libgthumb/dlg-save-image.h | 50 -
libgthumb/file-data.c | 450 -
libgthumb/file-data.h | 95 -
libgthumb/file-utils.c | 2753 ------
libgthumb/file-utils.h | 239 -
libgthumb/gconf-utils.c | 613 --
libgthumb/gconf-utils.h | 119 -
libgthumb/gfile-utils.c | 902 --
libgthumb/gfile-utils.h | 120 -
libgthumb/glib-utils.c | 520 --
libgthumb/glib-utils.h | 94 -
libgthumb/gstringlist.c | 39 -
libgthumb/gstringlist.h | 31 -
libgthumb/gth-exif-utils.c | 588 --
libgthumb/gth-exif-utils.h | 120 -
libgthumb/gth-exiv2-utils.cpp | 699 --
libgthumb/gth-exiv2-utils.hpp | 41 -
libgthumb/gth-file-list.c | 1903 -----
libgthumb/gth-file-list.h | 133 -
libgthumb/gth-file-view-list.c | 1462 ----
libgthumb/gth-file-view-list.h | 51 -
libgthumb/gth-file-view-thumbs.c | 829 --
libgthumb/gth-file-view-thumbs.h | 51 -
libgthumb/gth-file-view.c | 875 --
libgthumb/gth-file-view.h | 293 -
libgthumb/gth-filter.c | 504 --
libgthumb/gth-filter.h | 108 -
libgthumb/gth-gstreamer-utils.c | 658 --
libgthumb/gth-gstreamer-utils.h | 26 -
libgthumb/gth-image-list.c | 4611 ----------
libgthumb/gth-image-list.h | 250 -
libgthumb/gth-iviewer.c | 201 -
libgthumb/gth-iviewer.h | 85 -
libgthumb/gth-monitor.c | 774 --
libgthumb/gth-monitor.h | 106 -
libgthumb/gth-nav-window.c | 195 -
libgthumb/gth-nav-window.h | 52 -
libgthumb/gth-pixbuf-op.c | 332 -
libgthumb/gth-pixbuf-op.h | 96 -
libgthumb/gth-sort-utils.c | 195 -
libgthumb/gth-sort-utils.h | 52 -
libgthumb/gth-utils.c | 153 -
libgthumb/gth-utils.h | 34 -
libgthumb/gthumb-error.h | 32 -
libgthumb/gthumb-histogram.c | 204 -
libgthumb/gthumb-histogram.h | 64 -
libgthumb/gthumb-info-bar.c | 214 -
libgthumb/gthumb-info-bar.h | 63 -
libgthumb/gthumb-init.c | 183 -
libgthumb/gthumb-init.h | 36 -
libgthumb/gthumb-marshal.list | 20 -
libgthumb/gthumb-slide.c | 834 --
libgthumb/gthumb-slide.h | 86 -
libgthumb/gthumb-stock.c | 147 -
libgthumb/gthumb-stock.h | 72 -
libgthumb/gtk-utils.c | 818 --
libgthumb/gtk-utils.h | 116 -
libgthumb/icons/.cvsignore | 5 -
libgthumb/icons/Makefile.am | 144 -
libgthumb/icons/add-comment-16.png | Bin 551 -> 0 bytes
libgthumb/icons/add-comment-24.png | Bin 998 -> 0 bytes
libgthumb/icons/add-to-catalog-16.png | Bin 733 -> 0 bytes
libgthumb/icons/bookmark-16.png | Bin 480 -> 0 bytes
libgthumb/icons/brightness-contrast-16.png | Bin 583 -> 0 bytes
libgthumb/icons/brightness-contrast-22.png | Bin 791 -> 0 bytes
libgthumb/icons/camera-24.png | Bin 1272 -> 0 bytes
libgthumb/icons/catalog-16.png | Bin 733 -> 0 bytes
libgthumb/icons/catalog-24.png | Bin 1220 -> 0 bytes
libgthumb/icons/catalog-search-16.png | Bin 773 -> 0 bytes
libgthumb/icons/cdrom-24.png | Bin 1498 -> 0 bytes
libgthumb/icons/change-date-16.png | Bin 551 -> 0 bytes
libgthumb/icons/color-balance-16.png | Bin 414 -> 0 bytes
libgthumb/icons/color-balance-22.png | Bin 752 -> 0 bytes
libgthumb/icons/crop-16.png | Bin 367 -> 0 bytes
libgthumb/icons/desaturate-16.png | Bin 147 -> 0 bytes
libgthumb/icons/dir-16.png | Bin 344 -> 0 bytes
libgthumb/icons/dir-24.png | Bin 1076 -> 0 bytes
libgthumb/icons/edit-image-24.png | Bin 1252 -> 0 bytes
libgthumb/icons/enhance-22.png | Bin 737 -> 0 bytes
libgthumb/icons/film-16.png | Bin 644 -> 0 bytes
libgthumb/icons/filter-24.png | Bin 860 -> 0 bytes
libgthumb/icons/flip-16.png | Bin 446 -> 0 bytes
libgthumb/icons/flip-24.png | Bin 600 -> 0 bytes
libgthumb/icons/histogram-16.png | Bin 300 -> 0 bytes
libgthumb/icons/histogram-22.png | Bin 457 -> 0 bytes
libgthumb/icons/hue-saturation-16.png | Bin 311 -> 0 bytes
libgthumb/icons/hue-saturation-22.png | Bin 571 -> 0 bytes
libgthumb/icons/image-24.png | Bin 581 -> 0 bytes
libgthumb/icons/image-info-16.png | Bin 567 -> 0 bytes
libgthumb/icons/image-info-24.png | Bin 1058 -> 0 bytes
libgthumb/icons/index-image-16.png | Bin 401 -> 0 bytes
libgthumb/icons/invert-16.png | Bin 259 -> 0 bytes
libgthumb/icons/levels-16.png | Bin 361 -> 0 bytes
libgthumb/icons/levels-22.png | Bin 506 -> 0 bytes
libgthumb/icons/library-19.png | Bin 279 -> 0 bytes
libgthumb/icons/maintenance-16.png | Bin 668 -> 0 bytes
libgthumb/icons/maintenance-24.png | Bin 989 -> 0 bytes
libgthumb/icons/mirror-16.png | Bin 429 -> 0 bytes
libgthumb/icons/mirror-24.png | Bin 589 -> 0 bytes
libgthumb/icons/posterize-16.png | Bin 466 -> 0 bytes
libgthumb/icons/posterize-22.png | Bin 938 -> 0 bytes
libgthumb/icons/preview-comment-16.png | Bin 533 -> 0 bytes
libgthumb/icons/preview-data-16.png | Bin 343 -> 0 bytes
libgthumb/icons/preview-image-16.png | Bin 507 -> 0 bytes
libgthumb/icons/redeye-removal-16.png | Bin 677 -> 0 bytes
libgthumb/icons/reduce-colors-16.png | Bin 225 -> 0 bytes
libgthumb/icons/reset-16.png | Bin 341 -> 0 bytes
libgthumb/icons/resize-16.png | Bin 492 -> 0 bytes
libgthumb/icons/resize-22.png | Bin 680 -> 0 bytes
libgthumb/icons/rotate-16.png | Bin 569 -> 0 bytes
libgthumb/icons/rotate-270-16.png | Bin 446 -> 0 bytes
libgthumb/icons/rotate-270-24.png | Bin 890 -> 0 bytes
libgthumb/icons/rotate-90-16.png | Bin 457 -> 0 bytes
libgthumb/icons/rotate-90-24.png | Bin 771 -> 0 bytes
libgthumb/icons/search-duplicates-16.png | Bin 478 -> 0 bytes
libgthumb/icons/slideshow-16.png | Bin 381 -> 0 bytes
libgthumb/icons/slideshow-24.png | Bin 710 -> 0 bytes
libgthumb/icons/swap-16.png | Bin 431 -> 0 bytes
libgthumb/icons/swap-24.png | Bin 553 -> 0 bytes
libgthumb/icons/threshold-16.png | Bin 545 -> 0 bytes
libgthumb/icons/transform-16.png | Bin 414 -> 0 bytes
libgthumb/icons/transform-24.png | Bin 637 -> 0 bytes
libgthumb/icons/unknown-48.png | Bin 1064 -> 0 bytes
libgthumb/icons/zoom-width-16.png | Bin 847 -> 0 bytes
libgthumb/icons/zoom-width-24.png | Bin 1431 -> 0 bytes
libgthumb/image-loader.c | 852 --
libgthumb/image-loader.h | 85 -
libgthumb/image-viewer-enums.h | 76 -
libgthumb/image-viewer-image.c | 1112 ---
libgthumb/image-viewer-image.h | 113 -
libgthumb/image-viewer.c | 1992 -----
libgthumb/image-viewer.h | 170 -
libgthumb/jpegutils/.cvsignore | 5 -
libgthumb/jpegutils/Makefile.am | 17 -
libgthumb/jpegutils/README | 385 -
libgthumb/jpegutils/jpegtran.c | 360 -
libgthumb/jpegutils/jpegtran.h | 45 -
libgthumb/jpegutils/transupp.c | 1059 ---
libgthumb/jpegutils/transupp.h | 157 -
libgthumb/md5.c | 366 -
libgthumb/md5.h | 115 -
libgthumb/nav-window.c | 412 -
libgthumb/nav-window.h | 33 -
libgthumb/pixbuf-utils.c | 1404 ----
libgthumb/pixbuf-utils.h | 77 -
libgthumb/preferences.c | 638 --
libgthumb/preferences.h | 334 -
libgthumb/print-callbacks.c | 2777 -------
libgthumb/print-callbacks.h | 33 -
libgthumb/progress-dialog.c | 166 -
libgthumb/progress-dialog.h | 52 -
libgthumb/search.c | 257 -
libgthumb/search.h | 75 -
libgthumb/thumb-cache.c | 105 -
libgthumb/thumb-cache.h | 40 -
libgthumb/thumb-loader.c | 638 --
libgthumb/thumb-loader.h | 83 -
libgthumb/typedefs.h | 276 -
m4/.cvsignore | 2 -
po/.cvsignore | 14 -
po/POTFILES.in | 225 +-
po/POTFILES.skip | 2 -
src/.cvsignore | 16 -
src/GNOME_GThumb.idl | 16 -
src/Makefile.am | 194 -
src/albumtheme-private.c | 576 --
src/albumtheme-private.h | 233 -
src/albumtheme.c | 2536 ------
src/albumtheme.l | 347 -
src/albumtheme.y | 472 --
src/bookmark-list.c | 217 -
src/bookmark-list.h | 51 -
src/catalog-list.c | 638 --
src/catalog-list.h | 68 -
src/catalog-png-exporter.c | 2118 -----
src/catalog-png-exporter.h | 220 -
src/catalog-web-exporter.c | 3104 -------
src/catalog-web-exporter.h | 186 -
src/dlg-bookmarks.c | 376 -
src/dlg-bookmarks.h | 32 -
src/dlg-brightness-contrast.c | 343 -
src/dlg-brightness-contrast.h | 32 -
src/dlg-catalog.c | 507 --
src/dlg-catalog.h | 34 -
src/dlg-change-date.c | 368 -
src/dlg-change-date.h | 32 -
src/dlg-color-balance.c | 379 -
src/dlg-color-balance.h | 32 -
src/dlg-comment.c | 586 --
src/dlg-comment.h | 32 -
src/dlg-convert.c | 643 --
src/dlg-convert.h | 32 -
src/dlg-crop.c | 702 --
src/dlg-crop.h | 32 -
src/dlg-duplicates.c | 1577 ----
src/dlg-duplicates.h | 32 -
src/dlg-file-utils.c | 2607 ------
src/dlg-file-utils.h | 95 -
src/dlg-hue-saturation.c | 360 -
src/dlg-hue-saturation.h | 32 -
src/dlg-image-prop.c | 777 --
src/dlg-image-prop.h | 32 -
src/dlg-jpegtran.c | 734 --
src/dlg-jpegtran.h | 33 -
src/dlg-open-with.c | 454 -
src/dlg-open-with.h | 32 -
src/dlg-photo-importer.c | 1727 ----
src/dlg-photo-importer.h | 32 -
src/dlg-png-exporter.c | 1703 ----
src/dlg-png-exporter.h | 30 -
src/dlg-posterize.c | 328 -
src/dlg-posterize.h | 32 -
src/dlg-preferences.c | 653 --
src/dlg-preferences.h | 30 -
src/dlg-redeye-removal.c | 641 --
src/dlg-redeye-removal.h | 32 -
src/dlg-rename-series.c | 648 --
src/dlg-rename-series.h | 32 -
src/dlg-reset-exif.c | 271 -
src/dlg-reset-exif.h | 31 -
src/dlg-scale-image.c | 398 -
src/dlg-scale-image.h | 32 -
src/dlg-scale-series.c | 351 -
src/dlg-scale-series.h | 32 -
src/dlg-scripts.c | 1269 ---
src/dlg-scripts.h | 46 -
src/dlg-search.c | 1452 ----
src/dlg-search.h | 37 -
src/dlg-tags.c | 838 --
src/dlg-tags.h | 40 -
src/dlg-web-exporter.c | 1245 ---
src/dlg-web-exporter.h | 30 -
src/dlg-write-to-cd.c | 207 -
src/dlg-write-to-cd.h | 32 -
src/gth-application.c | 195 -
src/gth-application.h | 54 -
src/gth-batch-op.c | 739 --
src/gth-batch-op.h | 70 -
src/gth-browser-actions-callbacks.c | 1864 -----
src/gth-browser-actions-callbacks.h | 115 -
src/gth-browser-actions-entries.h | 512 --
src/gth-browser-ui.h | 417 -
src/gth-browser.c | 8778 --------------------
src/gth-browser.h | 127 -
src/gth-dir-list.c | 840 --
src/gth-dir-list.h | 100 -
src/gth-exif-data-viewer.c | 426 -
src/gth-exif-data-viewer.h | 67 -
src/gth-filter-bar.c | 693 --
src/gth-filter-bar.h | 61 -
src/gth-folder-selection-dialog.c | 430 -
src/gth-folder-selection-dialog.h | 64 -
src/gth-fullscreen-actions-callbacks.c | 65 -
src/gth-fullscreen-actions-callbacks.h | 36 -
src/gth-fullscreen-actions-entries.h | 69 -
src/gth-fullscreen-ui.h | 74 -
src/gth-fullscreen.c | 2048 -----
src/gth-fullscreen.h | 67 -
src/gth-image-history.c | 324 -
src/gth-image-history.h | 81 -
src/gth-image-selector.c | 2219 -----
src/gth-image-selector.h | 97 -
src/gth-location.c | 855 --
src/gth-location.h | 70 -
src/gth-tag-selection-dialog.c | 479 --
src/gth-tag-selection-dialog.h | 62 -
src/gth-viewer-actions-callbacks.c | 143 -
src/gth-viewer-actions-callbacks.h | 41 -
src/gth-viewer-actions-entries.h | 100 -
src/gth-viewer-ui.h | 176 -
src/gth-viewer.c | 2224 -----
src/gth-viewer.h | 66 -
src/gth-window-actions-callbacks.c | 975 ---
src/gth-window-actions-callbacks.h | 83 -
src/gth-window-actions-entries.h | 319 -
src/gth-window-utils.c | 71 -
src/gth-window-utils.h | 33 -
src/gth-window.c | 675 --
src/gth-window.h | 146 -
src/gthumb-preloader.c | 585 --
src/gthumb-preloader.h | 111 -
src/gtkcellrendererthreestates.c | 500 --
src/gtkcellrendererthreestates.h | 106 -
src/icons/.cvsignore | 5 -
src/icons/Makefile.am | 10 -
src/icons/layout1.xpm | 58 -
src/icons/layout2.xpm | 58 -
src/icons/layout3.xpm | 58 -
src/icons/layout4.xpm | 58 -
src/icons/nav_button.xpm | 19 -
src/lex.albumtheme.c | 2558 ------
src/main.c | 897 --
src/main.h | 54 -
src/make-albumtheme.sh | 7 -
src/rotation-utils.c | 337 -
src/rotation-utils.h | 49 -
src/totem-scrsaver.c | 209 -
src/totem-scrsaver.h | 52 -
tests/Makefile.am | 12 +
tests/dom-test.c | 180 +
tests/glib-utils-test.c | 53 +
725 files changed, 71978 insertions(+), 144191 deletions(-)
---
diff --git a/AUTHORS b/AUTHORS
index a075447..c5e168d 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -25,6 +25,3 @@ Contributors:
Dor Fire <dorfire gmail com>
Gabriel Falcão <gabriel nacaolivre org>
Marc Pavot <marc pavot gmail com>
- Marlodavampire <brooss teambb gmail com>
- Lincoln de Sousa <lincoln alfaiati net>
- Javier Jardón <javierjc1982 gmail com>
diff --git a/MAINTAINERS b/MAINTAINERS
index 7f62799..c14f939 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1,3 +1,3 @@
Paolo Bacchilega
-E-mail: paobac svn gnome org
+E-mail: paobac src gnome org
Userid: paobac
diff --git a/Makefile.am b/Makefile.am
index 517de23..1f762e3 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1,52 +1,68 @@
-## Process this file with automake to produce Makefile.in.
-AMCFLAGS = -fPIC -DPIC
+SUBDIRS = copy-n-paste data po gthumb extensions tests doc
+ACLOCAL_AMFLAGS = -I m4
-SUBDIRS = \
- po \
- libgthumb \
- src \
- doc \
- data
-
-distcleancheck_listfiles = find . -type f -print | grep -v 'omf\.out'
-
-distuninstallcheck_listfiles = find . -type f -print | grep -v '^\./var/scrollkeeper' | grep -v 'omf' | grep -v 'figures'
-
-
-EXTRA_DIST = \
- AUTHORS \
- MAINTAINERS \
+EXTRA_DIST = \
+ AUTHORS \
+ ChangeLog.pre-git \
+ MAINTAINERS \
NEWS \
README \
- add-include-prefix \
- intltool-merge.in \
- intltool-update.in \
- intltool-extract.in \
- omf.make \
- xmldocs.make \
- gnome-doc-utils.make \
- ChangeLog.pre-git
-
-DISTCLEANFILES = \
- po/.intltool-merge-cache \
- intltool-extract \
- intltool-merge \
- intltool-update \
+ config.rpath \
+ intltool-merge.in \
+ intltool-update.in \
+ intltool-extract.in
+
+DISTCLEANFILES = \
+ po/.intltool-merge-cache \
+ intltool-extract \
+ intltool-merge \
+ intltool-update \
gnome-doc-utils.make
-CLEANFILES = \
- ChangeLog
+MAINTAINERCLEANFILES = \
+ $(srcdir)/INSTALL \
+ $(srcdir)/aclocal.m4 \
+ $(srcdir)/autoscan.log \
+ $(srcdir)/compile \
+ $(srcdir)/config.guess \
+ $(srcdir)/config.h.in \
+ $(srcdir)/config.sub \
+ $(srcdir)/configure.scan \
+ $(srcdir)/depcomp \
+ $(srcdir)/install-sh \
+ $(srcdir)/ltmain.sh \
+ $(srcdir)/m4 \
+ $(srcdir)/missing \
+ $(srcdir)/mkinstalldirs \
+ $(srcdir)/omf.make \
+ $(srcdir)/xmldocs.make \
+ $(srcdir)/gtk-doc.make \
+ $(srcdir)/ChangeLog \
+ `find "$(srcdir)" -type f -name Makefile.in -print`
+
+GITIGNOREFILES = build
DISTCHECK_CONFIGURE_FLAGS = --disable-scrollkeeper
--include $(top_srcdir)/git.mk
+CLEANFILES = ChangeLog
# Build ChangeLog from GIT history
ChangeLog:
+ @echo Creating $@
@if test -d $(top_srcdir)/.git; then \
- GIT_DIR="$(top_srcdir)/.git" git log --stat > $@; \
+ (GIT_DIR=$(top_srcdir)/.git $(top_srcdir)/missing --run git log --stat -M -C --name-status --date=short --no-color) | fmt --split-only > $ tmp \
+ && mv -f $ tmp $@ \
+ || ($(RM) $ tmp; \
+ echo Failed to generate ChangeLog, your ChangeLog may be outdated >&2; \
+ (test -f $@ || echo git-log is required to generate this file >> $@)); \
+ else \
+ test -f $@ || \
+ (echo A git checkout and git-log is required to generate ChangeLog >&2 && \
+ echo A git checkout and git-log is required to generate this file >> $@); \
fi
dist: ChangeLog
.PHONY: ChangeLog
+
+-include $(top_srcdir)/git.mk
diff --git a/NEWS b/NEWS
index eb033d3..53a8b7e 100644
--- a/NEWS
+++ b/NEWS
@@ -1,20 +1,5 @@
trunk, since last 2.10.x
- Major Distro-Related Changes:
-
- * gThumb now imports photos using the libgphoto2 backend for gvfs.
- It is no longer necessary to unmount any gvfs/libgphoto2 mounts
- under ~/.gvfs beforing importing. The import process may be
- invoked using:
-
- gthumb --import-photos
- or
- gthumb --import-photos /path/to/device/mount
-
- If no path is given as an argument, gThumb will scan all folders
- under /media and ~/.gvfs/gphoto2*, looking for "dcim" or "DCIM"
- sub-folders.
-
UI Changes:
* Make the cropping dialog more intuitive. Bug #408342.
diff --git a/README b/README
index a5ad44f..f2c0f00 100644
--- a/README
+++ b/README
@@ -100,10 +100,10 @@ Compiling
If the libtiff library is present you can save images in TIFF
format.
- If the libgphoto2 backend for gvfs you can import photos from your
- camera.
+ If the libgphoto2 library version >= 2.1.3 is present you can import
+ photos from your camera.
- If the libopenraw library version >= 0.0.4 is present you can view
+ If the libopenraw library version >= 0.0.2 is present you can view
RAW photo thumbnails.
If dcraw is present (and is in your executable search path),
diff --git a/autogen.sh b/autogen.sh
index d566b3f..cb30e46 100755
--- a/autogen.sh
+++ b/autogen.sh
@@ -4,11 +4,11 @@
srcdir=`dirname $0`
test -z "$srcdir" && srcdir=.
-PKG_NAME="gThumb"
+PKG_NAME="gthumb"
REQUIRED_AUTOMAKE_VERSION=1.8
-(test -f $srcdir/configure.in \
- && test -d $srcdir/src) || {
+(test -f $srcdir/configure.ac \
+ && test -d $srcdir/gthumb) || {
echo -n "**Error**: Directory "\`$srcdir\'" does not look like the"
echo " top-level $PKG_NAME directory"
exit 1
diff --git a/configure.ac b/configure.ac
new file mode 100644
index 0000000..a8a08fa
--- /dev/null
+++ b/configure.ac
@@ -0,0 +1,221 @@
+AC_INIT([gthumb], [2.11.0], [http://bugzilla.gnome.org/enter_bug.cgi?product=gthumb])
+
+GNOME_COMMON_INIT
+
+AC_CONFIG_MACRO_DIR([m4])
+AC_CONFIG_HEADERS([config.h])
+AC_CONFIG_SRCDIR([configure.ac])
+
+AM_INIT_AUTOMAKE([1.9 foreign])
+
+AC_ISC_POSIX
+AC_PROG_CC
+AM_PROG_CC_STDC
+AC_HEADER_STDC
+AC_C_BIGENDIAN
+AC_PROG_CPP
+AC_PROG_CXX
+
+AC_PATH_PROG(GLIB_GENMARSHAL, glib-genmarshal)
+AC_PATH_PROG(GLIB_MKENUMS, glib-mkenums)
+
+AM_PROG_LIBTOOL
+GNOME_DOC_INIT
+GNOME_COMPILE_WARNINGS([maximum])
+
+GLIB_REQUIRED=2.16.0
+GTK_REQUIRED=2.12.0
+GCONF_REQUIRED=2.6.0
+EXIV2_REQUIRED=0.18
+
+dnl ===========================================================================
+
+AC_ARG_ENABLE(debug,
+ AC_HELP_STRING([--enable-debug], [enable compilation of debugging messages]),
+ [case "${enableval}" in
+ yes) ENABLE_DEBUG=yes ;;
+ no) ENABLE_DEBUG=no ;;
+ *) AC_MSG_ERROR([bad value ${enableval} for --enable-debug]) ;;
+ esac],
+ [ENABLE_DEBUG=no])
+if test x$ENABLE_DEBUG = xyes; then
+ AC_DEFINE(DEBUG, 1, [enable compilation of debugging messages])
+fi
+
+AC_ARG_ENABLE(run_in_place,
+ AC_HELP_STRING([--enable-run-in-place],[load ui data and extensions from the source tree]),
+ [case "${enableval}" in
+ yes) enable_run_in_place=yes ;;
+ no) enable_run_in_place=no ;;
+ *) AC_MSG_ERROR([bad value ${enableval} for --enable-run-in-place]) ;;
+ esac],
+ [enable_run_in_place=no])
+AM_CONDITIONAL(RUN_IN_PLACE, test "x$enable_run_in_place" != xno)
+
+if test x$enable_run_in_place = xyes; then
+ AC_DEFINE(RUN_IN_PLACE, 1, [load ui data and extensions from the source tree])
+fi
+
+AC_ARG_ENABLE(test-suite,
+ AC_HELP_STRING([--enable-test-suite], [enable the compilation of the test suite]),
+ [case "${enableval}" in
+ yes) ENABLE_TEST_SUITE=yes ;;
+ no) ENABLE_TEST_SUITE=no ;;
+ *) AC_MSG_ERROR([bad value ${enableval} for --enable-test-suite]) ;;
+ esac],
+ [ENABLE_TEST_SUITE=no])
+AM_CONDITIONAL(BUILD_TEST_SUITE, test "x$ENABLE_TEST_SUITE" = xyes)
+
+dnl ===========================================================================
+
+PKG_CHECK_MODULES(GTK, [gtk+-2.0 >= $GTK_REQUIRED])
+AC_SUBST([GTK_CFLAGS])
+AC_SUBST([GTK_LIBS])
+
+PKG_CHECK_MODULES(GTHUMB, [
+ glib-2.0 >= $GLIB_REQUIRED
+ gthread-2.0
+ gmodule-2.0
+ gio-unix-2.0
+ gtk+-2.0 >= $GTK_REQUIRED
+ gconf-2.0 >= $GCONF_REQUIRED
+ unique-1.0
+])
+AC_SUBST(GTHUMB_LIBS)
+AC_SUBST(GTHUMB_CFLAGS)
+
+dnl ===========================================================================
+
+AC_PATH_PROG(GLIB_GENMARSHAL, glib-genmarshal)
+AC_PATH_PROG(GLIB_MKENUMS, glib-mkenums)
+
+dnl ===========================================================================
+
+AC_ARG_ENABLE([exiv2],
+ [AC_HELP_STRING([--disable-exiv2],[do not compile code that uses the exiv2 library])],,
+ [enable_exiv2=yes])
+
+if test x$enable_exiv2 = xyes ; then
+ PKG_CHECK_MODULES(EXIV2,
+ exiv2 >= $EXIV2_REQUIRED,
+ [enable_exiv2=yes],
+ [enable_exiv2=no])
+fi
+AC_SUBST(EXIV2_LIBS)
+AC_SUBST(EXIV2_CFLAGS)
+AM_CONDITIONAL(ENABLE_EXIV2, test "x$enable_exiv2" = xyes)
+
+dnl ===========================================================================
+
+IT_PROG_INTLTOOL([0.35.0])
+GETTEXT_PACKAGE=gthumb
+AC_SUBST([GETTEXT_PACKAGE])
+AC_DEFINE_UNQUOTED([GETTEXT_PACKAGE],["$GETTEXT_PACKAGE"],[Gettext package])
+AM_GLIB_GNU_GETTEXT
+
+if test "x${prefix}" = "xNONE"; then
+ AC_DEFINE_UNQUOTED(LOCALEDIR, "${ac_default_prefix}/${DATADIRNAME}/locale", [Locale directory])
+else
+ AC_DEFINE_UNQUOTED(LOCALEDIR, "${prefix}/${DATADIRNAME}/locale", [Locale directory])
+fi
+
+dnl ===========================================================================
+
+AC_PATH_PROG(GCONFTOOL, gconftool-2, no)
+if test x"$GCONFTOOL" = xno; then
+ AC_MSG_ERROR([gconftool-2 executable not found in your path - should be installed with GConf])
+fi
+AM_GCONF_SOURCE_2
+
+dnl ===========================================================================
+
+AC_MSG_CHECKING([for some Win32 platform])
+case "$host" in
+ *-*-cygwin*|*-*-mingw*)
+ platform_win32=yes
+ ;;
+ *)
+ platform_win32=no
+ ;;
+esac
+AC_MSG_RESULT([$platform_win32])
+AM_CONDITIONAL(PLATFORM_WIN32, test "$platform_win32" = "yes")
+
+AC_MSG_CHECKING([for native Win32])
+case "$host" in
+ *-*-mingw*)
+ os_win32=yes
+ ;;
+ *)
+ os_win32=no
+ ;;
+esac
+AC_MSG_RESULT([$os_win32])
+AM_CONDITIONAL(OS_WIN32, test "$os_win32" = "yes")
+
+if test "$platform_win32" = "yes" ; then
+ EXTENSION_LIBTOOL_FLAGS='-module -avoid-version -no-undefined -Wl,$(top_builddir)/src/.libs/gthumb.exe.a'
+else
+ EXTENSION_LIBTOOL_FLAGS='-module -avoid-version'
+fi
+AC_SUBST(EXTENSION_LIBTOOL_FLAGS)
+
+
+dnl ===========================================================================
+
+AC_CONFIG_FILES([
+Makefile
+copy-n-paste/Makefile
+data/Makefile
+data/icons/Makefile
+data/icons/16x16/Makefile
+data/icons/16x16/apps/Makefile
+data/icons/22x22/Makefile
+data/icons/22x22/apps/Makefile
+data/icons/32x32/Makefile
+data/icons/32x32/apps/Makefile
+data/icons/48x48/Makefile
+data/icons/48x48/apps/Makefile
+data/icons/scalable/Makefile
+data/icons/scalable/apps/Makefile
+data/ui/Makefile
+doc/Makefile
+extensions/Makefile
+extensions/catalogs/Makefile
+extensions/catalogs/data/Makefile
+extensions/catalogs/data/ui/Makefile
+extensions/comments/Makefile
+extensions/comments/data/Makefile
+extensions/comments/data/ui/Makefile
+extensions/exiv2/Makefile
+extensions/file_manager/Makefile
+extensions/file_viewer/Makefile
+extensions/image_tools/Makefile
+extensions/image_tools/data/Makefile
+extensions/image_tools/data/ui/Makefile
+extensions/image_viewer/Makefile
+extensions/image_viewer/data/Makefile
+extensions/image_viewer/data/ui/Makefile
+extensions/search/Makefile
+extensions/search/data/Makefile
+extensions/search/data/ui/Makefile
+gthumb/Makefile
+gthumb/cursors/Makefile
+gthumb/icons/Makefile
+po/Makefile.in
+tests/Makefile
+])
+
+AC_OUTPUT
+
+echo "
+Configuration:
+
+ Source code location : $srcdir
+ Compiler : $CC
+ Prefix : $prefix
+ Debug : $ENABLE_DEBUG
+ Run in place : ${enable_run_in_place}
+ Build tests : $ENABLE_TEST_SUITE
+ Exiv2 support : ${enable_exiv2}
+"
diff --git a/copy-n-paste/Makefile.am b/copy-n-paste/Makefile.am
new file mode 100644
index 0000000..3846c85
--- /dev/null
+++ b/copy-n-paste/Makefile.am
@@ -0,0 +1,16 @@
+INCLUDES = -DGTK_DISABLE_DEPRECATED \
+ -DGDK_DISABLE_DEPRECATED \
+ -DG_DISABLE_DEPRECATED
+
+noinst_LTLIBRARIES = libeggsmclient.la
+
+libeggsmclient_la_LIBADD = $(GTK_LIBS)
+libeggsmclient_la_CFLAGS = $(GTK_CFLAGS)
+libeggsmclient_la_SOURCES = eggdesktopfile.h \
+ eggdesktopfile.c \
+ eggsmclient.h \
+ eggsmclient.c \
+ eggsmclient-private.h \
+ eggsmclient-xsmp.c
+
+-include $(top_srcdir)/git.mk
diff --git a/copy-n-paste/eggdesktopfile.c b/copy-n-paste/eggdesktopfile.c
new file mode 100644
index 0000000..357e548
--- /dev/null
+++ b/copy-n-paste/eggdesktopfile.c
@@ -0,0 +1,1477 @@
+/* eggdesktopfile.c - Freedesktop.Org Desktop Files
+ * Copyright (C) 2007 Novell, Inc.
+ *
+ * Based on gnome-desktop-item.c
+ * Copyright (C) 1999, 2000 Red Hat Inc.
+ * Copyright (C) 2001 George Lebl
+ *
+ * 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; see the file COPYING.LIB. 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 "eggdesktopfile.h"
+
+#include <string.h>
+#include <unistd.h>
+
+#include <glib/gi18n.h>
+#include <gdk/gdkx.h>
+#include <gtk/gtk.h>
+
+struct EggDesktopFile {
+ GKeyFile *key_file;
+ char *source;
+
+ char *name, *icon;
+ EggDesktopFileType type;
+ char document_code;
+};
+
+/**
+ * egg_desktop_file_new:
+ * @desktop_file_path: path to a Freedesktop-style Desktop file
+ * @error: error pointer
+ *
+ * Creates a new #EggDesktopFile for @desktop_file.
+ *
+ * Return value: the new #EggDesktopFile, or %NULL on error.
+ **/
+EggDesktopFile *
+egg_desktop_file_new (const char *desktop_file_path, GError **error)
+{
+ GKeyFile *key_file;
+
+ key_file = g_key_file_new ();
+ if (!g_key_file_load_from_file (key_file, desktop_file_path, 0, error))
+ {
+ g_key_file_free (key_file);
+ return NULL;
+ }
+
+ return egg_desktop_file_new_from_key_file (key_file, desktop_file_path,
+ error);
+}
+
+/**
+ * egg_desktop_file_new_from_data_dirs:
+ * @desktop_file_path: relative path to a Freedesktop-style Desktop file
+ * @error: error pointer
+ *
+ * Looks for @desktop_file_path in the paths returned from
+ * g_get_user_data_dir() and g_get_system_data_dirs(), and creates
+ * a new #EggDesktopFile from it.
+ *
+ * Return value: the new #EggDesktopFile, or %NULL on error.
+ **/
+EggDesktopFile *
+egg_desktop_file_new_from_data_dirs (const char *desktop_file_path,
+ GError **error)
+{
+ EggDesktopFile *desktop_file;
+ GKeyFile *key_file;
+ char *full_path;
+
+ key_file = g_key_file_new ();
+ if (!g_key_file_load_from_data_dirs (key_file, desktop_file_path,
+ &full_path, 0, error))
+ {
+ g_key_file_free (key_file);
+ return NULL;
+ }
+
+ desktop_file = egg_desktop_file_new_from_key_file (key_file,
+ full_path,
+ error);
+ g_free (full_path);
+ return desktop_file;
+}
+
+/**
+ * egg_desktop_file_new_from_dirs:
+ * @desktop_file_path: relative path to a Freedesktop-style Desktop file
+ * @search_dirs: NULL-terminated array of directories to search
+ * @error: error pointer
+ *
+ * Looks for @desktop_file_path in the paths returned from
+ * g_get_user_data_dir() and g_get_system_data_dirs(), and creates
+ * a new #EggDesktopFile from it.
+ *
+ * Return value: the new #EggDesktopFile, or %NULL on error.
+ **/
+EggDesktopFile *
+egg_desktop_file_new_from_dirs (const char *desktop_file_path,
+ const char **search_dirs,
+ GError **error)
+{
+ EggDesktopFile *desktop_file;
+ GKeyFile *key_file;
+ char *full_path;
+
+ key_file = g_key_file_new ();
+ if (!g_key_file_load_from_dirs (key_file, desktop_file_path, search_dirs,
+ &full_path, 0, error))
+ {
+ g_key_file_free (key_file);
+ return NULL;
+ }
+
+ desktop_file = egg_desktop_file_new_from_key_file (key_file,
+ full_path,
+ error);
+ g_free (full_path);
+ return desktop_file;
+}
+
+/**
+ * egg_desktop_file_new_from_key_file:
+ * @key_file: a #GKeyFile representing a desktop file
+ * @source: the path or URI that @key_file was loaded from, or %NULL
+ * @error: error pointer
+ *
+ * Creates a new #EggDesktopFile for @key_file. Assumes ownership of
+ * @key_file (on success or failure); you should consider @key_file to
+ * be freed after calling this function.
+ *
+ * Return value: the new #EggDesktopFile, or %NULL on error.
+ **/
+EggDesktopFile *
+egg_desktop_file_new_from_key_file (GKeyFile *key_file,
+ const char *source,
+ GError **error)
+{
+ EggDesktopFile *desktop_file;
+ char *version, *type;
+
+ if (!g_key_file_has_group (key_file, EGG_DESKTOP_FILE_GROUP))
+ {
+ g_set_error (error, EGG_DESKTOP_FILE_ERROR,
+ EGG_DESKTOP_FILE_ERROR_INVALID,
+ _("File is not a valid .desktop file"));
+ g_key_file_free (key_file);
+ return NULL;
+ }
+
+ version = g_key_file_get_value (key_file, EGG_DESKTOP_FILE_GROUP,
+ EGG_DESKTOP_FILE_KEY_VERSION,
+ NULL);
+ if (version)
+ {
+ double version_num;
+ char *end;
+
+ version_num = g_ascii_strtod (version, &end);
+ if (*end)
+ {
+ g_warning ("Invalid Version string '%s' in %s",
+ version, source ? source : "(unknown)");
+ }
+ else if (version_num > 1.0)
+ {
+ g_set_error (error, EGG_DESKTOP_FILE_ERROR,
+ EGG_DESKTOP_FILE_ERROR_INVALID,
+ _("Unrecognized desktop file Version '%s'"), version);
+ g_free (version);
+ g_key_file_free (key_file);
+ return NULL;
+ }
+ g_free (version);
+ }
+
+ desktop_file = g_new0 (EggDesktopFile, 1);
+ desktop_file->key_file = key_file;
+
+ if (g_path_is_absolute (source))
+ desktop_file->source = g_filename_to_uri (source, NULL, NULL);
+ else
+ desktop_file->source = g_strdup (source);
+
+ desktop_file->name = g_key_file_get_string (key_file, EGG_DESKTOP_FILE_GROUP,
+ EGG_DESKTOP_FILE_KEY_NAME, error);
+ if (!desktop_file->name)
+ {
+ egg_desktop_file_free (desktop_file);
+ return NULL;
+ }
+
+ type = g_key_file_get_string (key_file, EGG_DESKTOP_FILE_GROUP,
+ EGG_DESKTOP_FILE_KEY_TYPE, error);
+ if (!type)
+ {
+ egg_desktop_file_free (desktop_file);
+ return NULL;
+ }
+
+ if (!strcmp (type, "Application"))
+ {
+ char *exec, *p;
+
+ desktop_file->type = EGG_DESKTOP_FILE_TYPE_APPLICATION;
+
+ exec = g_key_file_get_string (key_file,
+ EGG_DESKTOP_FILE_GROUP,
+ EGG_DESKTOP_FILE_KEY_EXEC,
+ error);
+ if (!exec)
+ {
+ egg_desktop_file_free (desktop_file);
+ g_free (type);
+ return NULL;
+ }
+
+ /* See if it takes paths or URIs or neither */
+ for (p = exec; *p; p++)
+ {
+ if (*p == '%')
+ {
+ if (p[1] == '\0' || strchr ("FfUu", p[1]))
+ {
+ desktop_file->document_code = p[1];
+ break;
+ }
+ p++;
+ }
+ }
+
+ g_free (exec);
+ }
+ else if (!strcmp (type, "Link"))
+ {
+ char *url;
+
+ desktop_file->type = EGG_DESKTOP_FILE_TYPE_LINK;
+
+ url = g_key_file_get_string (key_file,
+ EGG_DESKTOP_FILE_GROUP,
+ EGG_DESKTOP_FILE_KEY_URL,
+ error);
+ if (!url)
+ {
+ egg_desktop_file_free (desktop_file);
+ g_free (type);
+ return NULL;
+ }
+ g_free (url);
+ }
+ else if (!strcmp (type, "Directory"))
+ desktop_file->type = EGG_DESKTOP_FILE_TYPE_DIRECTORY;
+ else
+ desktop_file->type = EGG_DESKTOP_FILE_TYPE_UNRECOGNIZED;
+
+ g_free (type);
+
+ /* Check the Icon key */
+ desktop_file->icon = g_key_file_get_string (key_file,
+ EGG_DESKTOP_FILE_GROUP,
+ EGG_DESKTOP_FILE_KEY_ICON,
+ NULL);
+ if (desktop_file->icon && !g_path_is_absolute (desktop_file->icon))
+ {
+ char *ext;
+
+ /* Lots of .desktop files still get this wrong */
+ ext = strrchr (desktop_file->icon, '.');
+ if (ext && (!strcmp (ext, ".png") ||
+ !strcmp (ext, ".xpm") ||
+ !strcmp (ext, ".svg")))
+ {
+ g_warning ("Desktop file '%s' has malformed Icon key '%s'"
+ "(should not include extension)",
+ source ? source : "(unknown)",
+ desktop_file->icon);
+ *ext = '\0';
+ }
+ }
+
+ return desktop_file;
+}
+
+/**
+ * egg_desktop_file_free:
+ * @desktop_file: an #EggDesktopFile
+ *
+ * Frees @desktop_file.
+ **/
+void
+egg_desktop_file_free (EggDesktopFile *desktop_file)
+{
+ g_key_file_free (desktop_file->key_file);
+ g_free (desktop_file->source);
+ g_free (desktop_file->name);
+ g_free (desktop_file->icon);
+ g_free (desktop_file);
+}
+
+/**
+ * egg_desktop_file_get_source:
+ * @desktop_file: an #EggDesktopFile
+ *
+ * Gets the URI that @desktop_file was loaded from.
+ *
+ * Return value: @desktop_file's source URI
+ **/
+const char *
+egg_desktop_file_get_source (EggDesktopFile *desktop_file)
+{
+ return desktop_file->source;
+}
+
+/**
+ * egg_desktop_file_get_desktop_file_type:
+ * @desktop_file: an #EggDesktopFile
+ *
+ * Gets the desktop file type of @desktop_file.
+ *
+ * Return value: @desktop_file's type
+ **/
+EggDesktopFileType
+egg_desktop_file_get_desktop_file_type (EggDesktopFile *desktop_file)
+{
+ return desktop_file->type;
+}
+
+/**
+ * egg_desktop_file_get_name:
+ * @desktop_file: an #EggDesktopFile
+ *
+ * Gets the (localized) value of @desktop_file's "Name" key.
+ *
+ * Return value: the application/link name
+ **/
+const char *
+egg_desktop_file_get_name (EggDesktopFile *desktop_file)
+{
+ return desktop_file->name;
+}
+
+/**
+ * egg_desktop_file_get_icon:
+ * @desktop_file: an #EggDesktopFile
+ *
+ * Gets the value of @desktop_file's "Icon" key.
+ *
+ * If the icon string is a full path (that is, if g_path_is_absolute()
+ * returns %TRUE when called on it), it points to a file containing an
+ * unthemed icon. If the icon string is not a full path, it is the
+ * name of a themed icon, which can be looked up with %GtkIconTheme,
+ * or passed directly to a theme-aware widget like %GtkImage or
+ * %GtkCellRendererPixbuf.
+ *
+ * Return value: the icon path or name
+ **/
+const char *
+egg_desktop_file_get_icon (EggDesktopFile *desktop_file)
+{
+ return desktop_file->icon;
+}
+
+gboolean
+egg_desktop_file_has_key (EggDesktopFile *desktop_file,
+ const char *key,
+ GError **error)
+{
+ return g_key_file_has_key (desktop_file->key_file,
+ EGG_DESKTOP_FILE_GROUP, key,
+ error);
+}
+
+char *
+egg_desktop_file_get_string (EggDesktopFile *desktop_file,
+ const char *key,
+ GError **error)
+{
+ return g_key_file_get_string (desktop_file->key_file,
+ EGG_DESKTOP_FILE_GROUP, key,
+ error);
+}
+
+char *
+egg_desktop_file_get_locale_string (EggDesktopFile *desktop_file,
+ const char *key,
+ const char *locale,
+ GError **error)
+{
+ return g_key_file_get_locale_string (desktop_file->key_file,
+ EGG_DESKTOP_FILE_GROUP, key, locale,
+ error);
+}
+
+gboolean
+egg_desktop_file_get_boolean (EggDesktopFile *desktop_file,
+ const char *key,
+ GError **error)
+{
+ return g_key_file_get_boolean (desktop_file->key_file,
+ EGG_DESKTOP_FILE_GROUP, key,
+ error);
+}
+
+double
+egg_desktop_file_get_numeric (EggDesktopFile *desktop_file,
+ const char *key,
+ GError **error)
+{
+ return g_key_file_get_double (desktop_file->key_file,
+ EGG_DESKTOP_FILE_GROUP, key,
+ error);
+}
+
+char **
+egg_desktop_file_get_string_list (EggDesktopFile *desktop_file,
+ const char *key,
+ gsize *length,
+ GError **error)
+{
+ return g_key_file_get_string_list (desktop_file->key_file,
+ EGG_DESKTOP_FILE_GROUP, key, length,
+ error);
+}
+
+char **
+egg_desktop_file_get_locale_string_list (EggDesktopFile *desktop_file,
+ const char *key,
+ const char *locale,
+ gsize *length,
+ GError **error)
+{
+ return g_key_file_get_locale_string_list (desktop_file->key_file,
+ EGG_DESKTOP_FILE_GROUP, key,
+ locale, length,
+ error);
+}
+
+/**
+ * egg_desktop_file_can_launch:
+ * @desktop_file: an #EggDesktopFile
+ * @desktop_environment: the name of the running desktop environment,
+ * or %NULL
+ *
+ * Tests if @desktop_file can/should be launched in the current
+ * environment. If @desktop_environment is non-%NULL, @desktop_file's
+ * "OnlyShowIn" and "NotShowIn" keys are checked to make sure that
+ * this desktop_file is appropriate for the named environment.
+ *
+ * Furthermore, if @desktop_file has type
+ * %EGG_DESKTOP_FILE_TYPE_APPLICATION, its "TryExec" key (if any) is
+ * also checked, to make sure the binary it points to exists.
+ *
+ * egg_desktop_file_can_launch() does NOT check the value of the
+ * "Hidden" key.
+ *
+ * Return value: %TRUE if @desktop_file can be launched
+ **/
+gboolean
+egg_desktop_file_can_launch (EggDesktopFile *desktop_file,
+ const char *desktop_environment)
+{
+ char *try_exec, *found_program;
+ char **only_show_in, **not_show_in;
+ gboolean found;
+ int i;
+
+ if (desktop_file->type != EGG_DESKTOP_FILE_TYPE_APPLICATION &&
+ desktop_file->type != EGG_DESKTOP_FILE_TYPE_LINK)
+ return FALSE;
+
+ if (desktop_environment)
+ {
+ only_show_in = g_key_file_get_string_list (desktop_file->key_file,
+ EGG_DESKTOP_FILE_GROUP,
+ EGG_DESKTOP_FILE_KEY_ONLY_SHOW_IN,
+ NULL, NULL);
+ if (only_show_in)
+ {
+ for (i = 0, found = FALSE; only_show_in[i] && !found; i++)
+ {
+ if (!strcmp (only_show_in[i], desktop_environment))
+ found = TRUE;
+ }
+
+ g_strfreev (only_show_in);
+
+ if (!found)
+ return FALSE;
+ }
+
+ not_show_in = g_key_file_get_string_list (desktop_file->key_file,
+ EGG_DESKTOP_FILE_GROUP,
+ EGG_DESKTOP_FILE_KEY_NOT_SHOW_IN,
+ NULL, NULL);
+ if (not_show_in)
+ {
+ for (i = 0, found = FALSE; not_show_in[i] && !found; i++)
+ {
+ if (!strcmp (not_show_in[i], desktop_environment))
+ found = TRUE;
+ }
+
+ g_strfreev (not_show_in);
+
+ if (found)
+ return FALSE;
+ }
+ }
+
+ if (desktop_file->type == EGG_DESKTOP_FILE_TYPE_APPLICATION)
+ {
+ try_exec = g_key_file_get_string (desktop_file->key_file,
+ EGG_DESKTOP_FILE_GROUP,
+ EGG_DESKTOP_FILE_KEY_TRY_EXEC,
+ NULL);
+ if (try_exec)
+ {
+ found_program = g_find_program_in_path (try_exec);
+ g_free (try_exec);
+
+ if (!found_program)
+ return FALSE;
+ g_free (found_program);
+ }
+ }
+
+ return TRUE;
+}
+
+/**
+ * egg_desktop_file_accepts_documents:
+ * @desktop_file: an #EggDesktopFile
+ *
+ * Tests if @desktop_file represents an application that can accept
+ * documents on the command line.
+ *
+ * Return value: %TRUE or %FALSE
+ **/
+gboolean
+egg_desktop_file_accepts_documents (EggDesktopFile *desktop_file)
+{
+ return desktop_file->document_code != 0;
+}
+
+/**
+ * egg_desktop_file_accepts_multiple:
+ * @desktop_file: an #EggDesktopFile
+ *
+ * Tests if @desktop_file can accept multiple documents at once.
+ *
+ * If this returns %FALSE, you can still pass multiple documents to
+ * egg_desktop_file_launch(), but that will result in multiple copies
+ * of the application being launched. See egg_desktop_file_launch()
+ * for more details.
+ *
+ * Return value: %TRUE or %FALSE
+ **/
+gboolean
+egg_desktop_file_accepts_multiple (EggDesktopFile *desktop_file)
+{
+ return (desktop_file->document_code == 'F' ||
+ desktop_file->document_code == 'U');
+}
+
+/**
+ * egg_desktop_file_accepts_uris:
+ * @desktop_file: an #EggDesktopFile
+ *
+ * Tests if @desktop_file can accept (non-"file:") URIs as documents to
+ * open.
+ *
+ * Return value: %TRUE or %FALSE
+ **/
+gboolean
+egg_desktop_file_accepts_uris (EggDesktopFile *desktop_file)
+{
+ return (desktop_file->document_code == 'U' ||
+ desktop_file->document_code == 'u');
+}
+
+static void
+append_quoted_word (GString *str,
+ const char *s,
+ gboolean in_single_quotes,
+ gboolean in_double_quotes)
+{
+ const char *p;
+
+ if (!in_single_quotes && !in_double_quotes)
+ g_string_append_c (str, '\'');
+ else if (!in_single_quotes && in_double_quotes)
+ g_string_append (str, "\"'");
+
+ if (!strchr (s, '\''))
+ g_string_append (str, s);
+ else
+ {
+ for (p = s; *p != '\0'; p++)
+ {
+ if (*p == '\'')
+ g_string_append (str, "'\\''");
+ else
+ g_string_append_c (str, *p);
+ }
+ }
+
+ if (!in_single_quotes && !in_double_quotes)
+ g_string_append_c (str, '\'');
+ else if (!in_single_quotes && in_double_quotes)
+ g_string_append (str, "'\"");
+}
+
+static void
+do_percent_subst (EggDesktopFile *desktop_file,
+ char code,
+ GString *str,
+ GSList **documents,
+ gboolean in_single_quotes,
+ gboolean in_double_quotes)
+{
+ GSList *d;
+ char *doc;
+
+ switch (code)
+ {
+ case '%':
+ g_string_append_c (str, '%');
+ break;
+
+ case 'F':
+ case 'U':
+ for (d = *documents; d; d = d->next)
+ {
+ doc = d->data;
+ g_string_append (str, " ");
+ append_quoted_word (str, doc, in_single_quotes, in_double_quotes);
+ }
+ *documents = NULL;
+ break;
+
+ case 'f':
+ case 'u':
+ if (*documents)
+ {
+ doc = (*documents)->data;
+ g_string_append (str, " ");
+ append_quoted_word (str, doc, in_single_quotes, in_double_quotes);
+ *documents = (*documents)->next;
+ }
+ break;
+
+ case 'i':
+ if (desktop_file->icon)
+ {
+ g_string_append (str, "--icon ");
+ append_quoted_word (str, desktop_file->icon,
+ in_single_quotes, in_double_quotes);
+ }
+ break;
+
+ case 'c':
+ if (desktop_file->name)
+ {
+ append_quoted_word (str, desktop_file->name,
+ in_single_quotes, in_double_quotes);
+ }
+ break;
+
+ case 'k':
+ if (desktop_file->source)
+ {
+ append_quoted_word (str, desktop_file->source,
+ in_single_quotes, in_double_quotes);
+ }
+ break;
+
+ case 'D':
+ case 'N':
+ case 'd':
+ case 'n':
+ case 'v':
+ case 'm':
+ /* Deprecated; skip */
+ break;
+
+ default:
+ g_warning ("Unrecognized %%-code '%%%c' in Exec", code);
+ break;
+ }
+}
+
+static char *
+parse_exec (EggDesktopFile *desktop_file,
+ GSList **documents,
+ GError **error)
+{
+ char *exec, *p, *command;
+ gboolean escape, single_quot, double_quot;
+ GString *gs;
+
+ exec = g_key_file_get_string (desktop_file->key_file,
+ EGG_DESKTOP_FILE_GROUP,
+ EGG_DESKTOP_FILE_KEY_EXEC,
+ error);
+ if (!exec)
+ return NULL;
+
+ /* Build the command */
+ gs = g_string_new (NULL);
+ escape = single_quot = double_quot = FALSE;
+
+ for (p = exec; *p != '\0'; p++)
+ {
+ if (escape)
+ {
+ escape = FALSE;
+ g_string_append_c (gs, *p);
+ }
+ else if (*p == '\\')
+ {
+ if (!single_quot)
+ escape = TRUE;
+ g_string_append_c (gs, *p);
+ }
+ else if (*p == '\'')
+ {
+ g_string_append_c (gs, *p);
+ if (!single_quot && !double_quot)
+ single_quot = TRUE;
+ else if (single_quot)
+ single_quot = FALSE;
+ }
+ else if (*p == '"')
+ {
+ g_string_append_c (gs, *p);
+ if (!single_quot && !double_quot)
+ double_quot = TRUE;
+ else if (double_quot)
+ double_quot = FALSE;
+ }
+ else if (*p == '%' && p[1])
+ {
+ do_percent_subst (desktop_file, p[1], gs, documents,
+ single_quot, double_quot);
+ p++;
+ }
+ else
+ g_string_append_c (gs, *p);
+ }
+
+ g_free (exec);
+ command = g_string_free (gs, FALSE);
+
+ /* Prepend "xdg-terminal " if needed (FIXME: use gvfs) */
+ if (g_key_file_has_key (desktop_file->key_file,
+ EGG_DESKTOP_FILE_GROUP,
+ EGG_DESKTOP_FILE_KEY_TERMINAL,
+ NULL))
+ {
+ GError *terminal_error = NULL;
+ gboolean use_terminal =
+ g_key_file_get_boolean (desktop_file->key_file,
+ EGG_DESKTOP_FILE_GROUP,
+ EGG_DESKTOP_FILE_KEY_TERMINAL,
+ &terminal_error);
+ if (terminal_error)
+ {
+ g_free (command);
+ g_propagate_error (error, terminal_error);
+ return NULL;
+ }
+
+ if (use_terminal)
+ {
+ gs = g_string_new ("xdg-terminal ");
+ append_quoted_word (gs, command, FALSE, FALSE);
+ g_free (command);
+ command = g_string_free (gs, FALSE);
+ }
+ }
+
+ return command;
+}
+
+static GSList *
+translate_document_list (EggDesktopFile *desktop_file, GSList *documents)
+{
+ gboolean accepts_uris = egg_desktop_file_accepts_uris (desktop_file);
+ GSList *ret, *d;
+
+ for (d = documents, ret = NULL; d; d = d->next)
+ {
+ const char *document = d->data;
+ gboolean is_uri = !g_path_is_absolute (document);
+ char *translated;
+
+ if (accepts_uris)
+ {
+ if (is_uri)
+ translated = g_strdup (document);
+ else
+ translated = g_filename_to_uri (document, NULL, NULL);
+ }
+ else
+ {
+ if (is_uri)
+ translated = g_filename_from_uri (document, NULL, NULL);
+ else
+ translated = g_strdup (document);
+ }
+
+ if (translated)
+ ret = g_slist_prepend (ret, translated);
+ }
+
+ return g_slist_reverse (ret);
+}
+
+static void
+free_document_list (GSList *documents)
+{
+ GSList *d;
+
+ for (d = documents; d; d = d->next)
+ g_free (d->data);
+ g_slist_free (documents);
+}
+
+/**
+ * egg_desktop_file_parse_exec:
+ * @desktop_file: a #EggDesktopFile
+ * @documents: a list of document paths or URIs
+ * @error: error pointer
+ *
+ * Parses @desktop_file's Exec key, inserting @documents into it, and
+ * returns the result.
+ *
+ * If @documents contains non-file: URIs and @desktop_file does not
+ * accept URIs, those URIs will be ignored. Likewise, if @documents
+ * contains more elements than @desktop_file accepts, the extra
+ * documents will be ignored.
+ *
+ * Return value: the parsed Exec string
+ **/
+char *
+egg_desktop_file_parse_exec (EggDesktopFile *desktop_file,
+ GSList *documents,
+ GError **error)
+{
+ GSList *translated, *docs;
+ char *command;
+
+ docs = translated = translate_document_list (desktop_file, documents);
+ command = parse_exec (desktop_file, &docs, error);
+ free_document_list (translated);
+
+ return command;
+}
+
+static gboolean
+parse_link (EggDesktopFile *desktop_file,
+ EggDesktopFile **app_desktop_file,
+ GSList **documents,
+ GError **error)
+{
+ char *url;
+ GKeyFile *key_file;
+
+ url = g_key_file_get_string (desktop_file->key_file,
+ EGG_DESKTOP_FILE_GROUP,
+ EGG_DESKTOP_FILE_KEY_URL,
+ error);
+ if (!url)
+ return FALSE;
+ *documents = g_slist_prepend (NULL, url);
+
+ /* FIXME: use gvfs */
+ key_file = g_key_file_new ();
+ g_key_file_set_string (key_file, EGG_DESKTOP_FILE_GROUP,
+ EGG_DESKTOP_FILE_KEY_NAME,
+ "xdg-open");
+ g_key_file_set_string (key_file, EGG_DESKTOP_FILE_GROUP,
+ EGG_DESKTOP_FILE_KEY_TYPE,
+ "Application");
+ g_key_file_set_string (key_file, EGG_DESKTOP_FILE_GROUP,
+ EGG_DESKTOP_FILE_KEY_EXEC,
+ "xdg-open %u");
+ *app_desktop_file = egg_desktop_file_new_from_key_file (key_file, NULL, NULL);
+ return TRUE;
+}
+
+#if GTK_CHECK_VERSION (2, 12, 0)
+static char *
+start_startup_notification (GdkDisplay *display,
+ EggDesktopFile *desktop_file,
+ const char *argv0,
+ int screen,
+ int workspace,
+ guint32 launch_time)
+{
+ static int sequence = 0;
+ char *startup_id;
+ char *description, *wmclass;
+ char *screen_str, *workspace_str;
+
+ if (g_key_file_has_key (desktop_file->key_file,
+ EGG_DESKTOP_FILE_GROUP,
+ EGG_DESKTOP_FILE_KEY_STARTUP_NOTIFY,
+ NULL))
+ {
+ if (!g_key_file_get_boolean (desktop_file->key_file,
+ EGG_DESKTOP_FILE_GROUP,
+ EGG_DESKTOP_FILE_KEY_STARTUP_NOTIFY,
+ NULL))
+ return NULL;
+ wmclass = NULL;
+ }
+ else
+ {
+ wmclass = g_key_file_get_string (desktop_file->key_file,
+ EGG_DESKTOP_FILE_GROUP,
+ EGG_DESKTOP_FILE_KEY_STARTUP_WM_CLASS,
+ NULL);
+ if (!wmclass)
+ return NULL;
+ }
+
+ if (launch_time == (guint32)-1)
+ launch_time = gdk_x11_display_get_user_time (display);
+ startup_id = g_strdup_printf ("%s-%lu-%s-%s-%d_TIME%lu",
+ g_get_prgname (),
+ (unsigned long)getpid (),
+ g_get_host_name (),
+ argv0,
+ sequence++,
+ (unsigned long)launch_time);
+
+ description = g_strdup_printf (_("Starting %s"), desktop_file->name);
+ screen_str = g_strdup_printf ("%d", screen);
+ workspace_str = workspace == -1 ? NULL : g_strdup_printf ("%d", workspace);
+
+ gdk_x11_display_broadcast_startup_message (display, "new",
+ "ID", startup_id,
+ "NAME", desktop_file->name,
+ "SCREEN", screen_str,
+ "BIN", argv0,
+ "ICON", desktop_file->icon,
+ "DESKTOP", workspace_str,
+ "DESCRIPTION", description,
+ "WMCLASS", wmclass,
+ NULL);
+
+ g_free (description);
+ g_free (wmclass);
+ g_free (screen_str);
+ g_free (workspace_str);
+
+ return startup_id;
+}
+
+static void
+end_startup_notification (GdkDisplay *display,
+ const char *startup_id)
+{
+ gdk_x11_display_broadcast_startup_message (display, "remove",
+ "ID", startup_id,
+ NULL);
+}
+
+#define EGG_DESKTOP_FILE_SN_TIMEOUT_LENGTH (30 /* seconds */)
+
+typedef struct {
+ GdkDisplay *display;
+ char *startup_id;
+} StartupNotificationData;
+
+static gboolean
+startup_notification_timeout (gpointer data)
+{
+ StartupNotificationData *sn_data = data;
+
+ end_startup_notification (sn_data->display, sn_data->startup_id);
+ g_object_unref (sn_data->display);
+ g_free (sn_data->startup_id);
+ g_free (sn_data);
+
+ return FALSE;
+}
+
+static void
+set_startup_notification_timeout (GdkDisplay *display,
+ const char *startup_id)
+{
+ StartupNotificationData *sn_data;
+
+ sn_data = g_new (StartupNotificationData, 1);
+ sn_data->display = g_object_ref (display);
+ sn_data->startup_id = g_strdup (startup_id);
+
+ g_timeout_add_seconds (EGG_DESKTOP_FILE_SN_TIMEOUT_LENGTH,
+ startup_notification_timeout, sn_data);
+}
+#endif /* GTK 2.12 */
+
+static GPtrArray *
+array_putenv (GPtrArray *env, char *variable)
+{
+ guint i, keylen;
+
+ if (!env)
+ {
+ char **envp;
+
+ env = g_ptr_array_new ();
+
+ envp = g_listenv ();
+ for (i = 0; envp[i]; i++)
+ {
+ const char *value;
+
+ value = g_getenv (envp[i]);
+ g_ptr_array_add (env, g_strdup_printf ("%s=%s", envp[i],
+ value ? value : ""));
+ }
+ g_strfreev (envp);
+ }
+
+ keylen = strcspn (variable, "=");
+
+ /* Remove old value of key */
+ for (i = 0; i < env->len; i++)
+ {
+ char *envvar = env->pdata[i];
+
+ if (!strncmp (envvar, variable, keylen) && envvar[keylen] == '=')
+ {
+ g_free (envvar);
+ g_ptr_array_remove_index_fast (env, i);
+ break;
+ }
+ }
+
+ /* Add new value */
+ g_ptr_array_add (env, g_strdup (variable));
+
+ return env;
+}
+
+static gboolean
+egg_desktop_file_launchv (EggDesktopFile *desktop_file,
+ GSList *documents, va_list args,
+ GError **error)
+{
+ EggDesktopFileLaunchOption option;
+ GSList *translated_documents = NULL, *docs = NULL;
+ char *command, **argv;
+ int argc, i, screen_num;
+ gboolean success, current_success;
+ GdkDisplay *display;
+ char *startup_id;
+
+ GPtrArray *env = NULL;
+ char **variables = NULL;
+ GdkScreen *screen = NULL;
+ int workspace = -1;
+ const char *directory = NULL;
+ guint32 launch_time = (guint32)-1;
+ GSpawnFlags flags = G_SPAWN_SEARCH_PATH;
+ GSpawnChildSetupFunc setup_func = NULL;
+ gpointer setup_data = NULL;
+
+ GPid *ret_pid = NULL;
+ int *ret_stdin = NULL, *ret_stdout = NULL, *ret_stderr = NULL;
+ char **ret_startup_id = NULL;
+
+ if (documents && desktop_file->document_code == 0)
+ {
+ g_set_error (error, EGG_DESKTOP_FILE_ERROR,
+ EGG_DESKTOP_FILE_ERROR_NOT_LAUNCHABLE,
+ _("Application does not accept documents on command line"));
+ return FALSE;
+ }
+
+ /* Read the options: technically it's incorrect for the caller to
+ * NULL-terminate the list of options (rather than 0-terminating
+ * it), but NULL-terminating lets us use G_GNUC_NULL_TERMINATED,
+ * it's more consistent with other glib/gtk methods, and it will
+ * work as long as sizeof (int) <= sizeof (NULL), and NULL is
+ * represented as 0. (Which is true everywhere we care about.)
+ */
+ while ((option = va_arg (args, EggDesktopFileLaunchOption)))
+ {
+ switch (option)
+ {
+ case EGG_DESKTOP_FILE_LAUNCH_CLEARENV:
+ if (env)
+ g_ptr_array_free (env, TRUE);
+ env = g_ptr_array_new ();
+ break;
+ case EGG_DESKTOP_FILE_LAUNCH_PUTENV:
+ variables = va_arg (args, char **);
+ for (i = 0; variables[i]; i++)
+ env = array_putenv (env, variables[i]);
+ break;
+
+ case EGG_DESKTOP_FILE_LAUNCH_SCREEN:
+ screen = va_arg (args, GdkScreen *);
+ break;
+ case EGG_DESKTOP_FILE_LAUNCH_WORKSPACE:
+ workspace = va_arg (args, int);
+ break;
+
+ case EGG_DESKTOP_FILE_LAUNCH_DIRECTORY:
+ directory = va_arg (args, const char *);
+ break;
+ case EGG_DESKTOP_FILE_LAUNCH_TIME:
+ launch_time = va_arg (args, guint32);
+ break;
+ case EGG_DESKTOP_FILE_LAUNCH_FLAGS:
+ flags |= va_arg (args, GSpawnFlags);
+ /* Make sure they didn't set any flags that don't make sense. */
+ flags &= ~G_SPAWN_FILE_AND_ARGV_ZERO;
+ break;
+ case EGG_DESKTOP_FILE_LAUNCH_SETUP_FUNC:
+ setup_func = va_arg (args, GSpawnChildSetupFunc);
+ setup_data = va_arg (args, gpointer);
+ break;
+
+ case EGG_DESKTOP_FILE_LAUNCH_RETURN_PID:
+ ret_pid = va_arg (args, GPid *);
+ break;
+ case EGG_DESKTOP_FILE_LAUNCH_RETURN_STDIN_PIPE:
+ ret_stdin = va_arg (args, int *);
+ break;
+ case EGG_DESKTOP_FILE_LAUNCH_RETURN_STDOUT_PIPE:
+ ret_stdout = va_arg (args, int *);
+ break;
+ case EGG_DESKTOP_FILE_LAUNCH_RETURN_STDERR_PIPE:
+ ret_stderr = va_arg (args, int *);
+ break;
+ case EGG_DESKTOP_FILE_LAUNCH_RETURN_STARTUP_ID:
+ ret_startup_id = va_arg (args, char **);
+ break;
+
+ default:
+ g_set_error (error, EGG_DESKTOP_FILE_ERROR,
+ EGG_DESKTOP_FILE_ERROR_UNRECOGNIZED_OPTION,
+ _("Unrecognized launch option: %d"),
+ GPOINTER_TO_INT (option));
+ success = FALSE;
+ goto out;
+ }
+ }
+
+ if (screen)
+ {
+ char *display_name = gdk_screen_make_display_name (screen);
+ char *display_env = g_strdup_printf ("DISPLAY=%s", display_name);
+ env = array_putenv (env, display_env);
+ g_free (display_name);
+ g_free (display_env);
+
+ display = gdk_screen_get_display (screen);
+ }
+ else
+ {
+ display = gdk_display_get_default ();
+ screen = gdk_display_get_default_screen (display);
+ }
+ screen_num = gdk_screen_get_number (screen);
+
+ translated_documents = translate_document_list (desktop_file, documents);
+ docs = translated_documents;
+
+ success = FALSE;
+
+ do
+ {
+ command = parse_exec (desktop_file, &docs, error);
+ if (!command)
+ goto out;
+
+ if (!g_shell_parse_argv (command, &argc, &argv, error))
+ {
+ g_free (command);
+ goto out;
+ }
+ g_free (command);
+
+#if GTK_CHECK_VERSION (2, 12, 0)
+ startup_id = start_startup_notification (display, desktop_file,
+ argv[0], screen_num,
+ workspace, launch_time);
+ if (startup_id)
+ {
+ char *startup_id_env = g_strdup_printf ("DESKTOP_STARTUP_ID=%s",
+ startup_id);
+ env = array_putenv (env, startup_id_env);
+ g_free (startup_id_env);
+ }
+#else
+ startup_id = NULL;
+#endif /* GTK 2.12 */
+
+ if (env != NULL)
+ g_ptr_array_add (env, NULL);
+
+ current_success =
+ g_spawn_async_with_pipes (directory,
+ argv,
+ env ? (char **)(env->pdata) : NULL,
+ flags,
+ setup_func, setup_data,
+ ret_pid,
+ ret_stdin, ret_stdout, ret_stderr,
+ error);
+ g_strfreev (argv);
+
+ if (startup_id)
+ {
+#if GTK_CHECK_VERSION (2, 12, 0)
+ if (current_success)
+ {
+ set_startup_notification_timeout (display, startup_id);
+
+ if (ret_startup_id)
+ *ret_startup_id = startup_id;
+ else
+ g_free (startup_id);
+ }
+ else
+#endif /* GTK 2.12 */
+ g_free (startup_id);
+ }
+ else if (ret_startup_id)
+ *ret_startup_id = NULL;
+
+ if (current_success)
+ {
+ /* If we successfully launch any instances of the app, make
+ * sure we return TRUE and don't set @error.
+ */
+ success = TRUE;
+ error = NULL;
+
+ /* Also, only set the output params on the first one */
+ ret_pid = NULL;
+ ret_stdin = ret_stdout = ret_stderr = NULL;
+ ret_startup_id = NULL;
+ }
+ }
+ while (docs && current_success);
+
+ out:
+ if (env)
+ {
+ g_strfreev ((char **)env->pdata);
+ g_ptr_array_free (env, FALSE);
+ }
+ free_document_list (translated_documents);
+
+ return success;
+}
+
+/**
+ * egg_desktop_file_launch:
+ * @desktop_file: an #EggDesktopFile
+ * @documents: a list of URIs or paths to documents to open
+ * @error: error pointer
+ * @...: additional options
+ *
+ * Launches @desktop_file with the given arguments. Additional options
+ * can be specified as follows:
+ *
+ * %EGG_DESKTOP_FILE_LAUNCH_CLEARENV: (no arguments)
+ * clears the environment in the child process
+ * %EGG_DESKTOP_FILE_LAUNCH_PUTENV: (char **variables)
+ * adds the NAME=VALUE strings in the given %NULL-terminated
+ * array to the child process's environment
+ * %EGG_DESKTOP_FILE_LAUNCH_SCREEN: (GdkScreen *screen)
+ * causes the application to be launched on the given screen
+ * %EGG_DESKTOP_FILE_LAUNCH_WORKSPACE: (int workspace)
+ * causes the application to be launched on the given workspace
+ * %EGG_DESKTOP_FILE_LAUNCH_DIRECTORY: (char *dir)
+ * causes the application to be launched in the given directory
+ * %EGG_DESKTOP_FILE_LAUNCH_TIME: (guint32 launch_time)
+ * sets the "launch time" for the application. If the user
+ * interacts with another window after @launch_time but before
+ * the launched application creates its first window, the window
+ * manager may choose to not give focus to the new application.
+ * Passing 0 for @launch_time will explicitly request that the
+ * application not receive focus.
+ * %EGG_DESKTOP_FILE_LAUNCH_FLAGS (GSpawnFlags flags)
+ * Sets additional #GSpawnFlags to use. See g_spawn_async() for
+ * more details.
+ * %EGG_DESKTOP_FILE_LAUNCH_SETUP_FUNC (GSpawnChildSetupFunc, gpointer)
+ * Sets the child setup callback and the data to pass to it.
+ * (See g_spawn_async() for more details.)
+ *
+ * %EGG_DESKTOP_FILE_LAUNCH_RETURN_PID (GPid **pid)
+ * On a successful launch, sets * pid to the PID of the launched
+ * application.
+ * %EGG_DESKTOP_FILE_LAUNCH_RETURN_STARTUP_ID (char **startup_id)
+ * On a successful launch, sets * startup_id to the Startup
+ * Notification "startup id" of the launched application.
+ * %EGG_DESKTOP_FILE_LAUNCH_RETURN_STDIN_PIPE (int *fd)
+ * On a successful launch, sets * fd to the file descriptor of
+ * a pipe connected to the application's stdin.
+ * %EGG_DESKTOP_FILE_LAUNCH_RETURN_STDOUT_PIPE (int *fd)
+ * On a successful launch, sets * fd to the file descriptor of
+ * a pipe connected to the application's stdout.
+ * %EGG_DESKTOP_FILE_LAUNCH_RETURN_STDERR_PIPE (int *fd)
+ * On a successful launch, sets * fd to the file descriptor of
+ * a pipe connected to the application's stderr.
+ *
+ * The options should be terminated with a single %NULL.
+ *
+ * If @documents contains multiple documents, but
+ * egg_desktop_file_accepts_multiple() returns %FALSE for
+ * @desktop_file, then egg_desktop_file_launch() will actually launch
+ * multiple instances of the application. In that case, the return
+ * value (as well as any values passed via
+ * %EGG_DESKTOP_FILE_LAUNCH_RETURN_PID, etc) will only reflect the
+ * first instance of the application that was launched (but the
+ * %EGG_DESKTOP_FILE_LAUNCH_SETUP_FUNC will be called for each
+ * instance).
+ *
+ * Return value: %TRUE if the application was successfully launched.
+ **/
+gboolean
+egg_desktop_file_launch (EggDesktopFile *desktop_file,
+ GSList *documents, GError **error,
+ ...)
+{
+ va_list args;
+ gboolean success;
+ EggDesktopFile *app_desktop_file;
+
+ switch (desktop_file->type)
+ {
+ case EGG_DESKTOP_FILE_TYPE_APPLICATION:
+ va_start (args, error);
+ success = egg_desktop_file_launchv (desktop_file, documents,
+ args, error);
+ va_end (args);
+ break;
+
+ case EGG_DESKTOP_FILE_TYPE_LINK:
+ if (documents)
+ {
+ g_set_error (error, EGG_DESKTOP_FILE_ERROR,
+ EGG_DESKTOP_FILE_ERROR_NOT_LAUNCHABLE,
+ _("Can't pass document URIs to a 'Type=Link' desktop entry"));
+ return FALSE;
+ }
+
+ if (!parse_link (desktop_file, &app_desktop_file, &documents, error))
+ return FALSE;
+
+ va_start (args, error);
+ success = egg_desktop_file_launchv (app_desktop_file, documents,
+ args, error);
+ va_end (args);
+
+ egg_desktop_file_free (app_desktop_file);
+ free_document_list (documents);
+ break;
+
+ default:
+ g_set_error (error, EGG_DESKTOP_FILE_ERROR,
+ EGG_DESKTOP_FILE_ERROR_NOT_LAUNCHABLE,
+ _("Not a launchable item"));
+ success = FALSE;
+ break;
+ }
+
+ return success;
+}
+
+
+GQuark
+egg_desktop_file_error_quark (void)
+{
+ return g_quark_from_static_string ("egg-desktop_file-error-quark");
+}
+
+
+G_LOCK_DEFINE_STATIC (egg_desktop_file);
+static EggDesktopFile *egg_desktop_file;
+
+/**
+ * egg_set_desktop_file:
+ * @desktop_file_path: path to the application's desktop file
+ *
+ * Creates an #EggDesktopFile for the application from the data at
+ * @desktop_file_path. This will also call g_set_application_name()
+ * with the localized application name from the desktop file, and
+ * gtk_window_set_default_icon_name() or
+ * gtk_window_set_default_icon_from_file() with the application's
+ * icon. Other code may use additional information from the desktop
+ * file.
+ *
+ * Note that for thread safety reasons, this function can only
+ * be called once.
+ **/
+void
+egg_set_desktop_file (const char *desktop_file_path)
+{
+ GError *error = NULL;
+
+ G_LOCK (egg_desktop_file);
+ if (egg_desktop_file)
+ egg_desktop_file_free (egg_desktop_file);
+
+ egg_desktop_file = egg_desktop_file_new (desktop_file_path, &error);
+ if (error)
+ {
+ g_warning ("Could not load desktop file '%s': %s",
+ desktop_file_path, error->message);
+ g_error_free (error);
+ }
+
+ if (egg_desktop_file) {
+ /* Set localized application name and default window icon */
+ if (egg_desktop_file->name)
+ g_set_application_name (egg_desktop_file->name);
+ if (egg_desktop_file->icon)
+ {
+ if (g_path_is_absolute (egg_desktop_file->icon))
+ gtk_window_set_default_icon_from_file (egg_desktop_file->icon, NULL);
+ else
+ gtk_window_set_default_icon_name (egg_desktop_file->icon);
+ }
+ }
+
+ G_UNLOCK (egg_desktop_file);
+}
+
+/**
+ * egg_get_desktop_file:
+ *
+ * Gets the application's #EggDesktopFile, as set by
+ * egg_set_desktop_file().
+ *
+ * Return value: the #EggDesktopFile, or %NULL if it hasn't been set.
+ **/
+EggDesktopFile *
+egg_get_desktop_file (void)
+{
+ EggDesktopFile *retval;
+
+ G_LOCK (egg_desktop_file);
+ retval = egg_desktop_file;
+ G_UNLOCK (egg_desktop_file);
+
+ return retval;
+}
diff --git a/copy-n-paste/eggdesktopfile.h b/copy-n-paste/eggdesktopfile.h
new file mode 100644
index 0000000..f8a3d3e
--- /dev/null
+++ b/copy-n-paste/eggdesktopfile.h
@@ -0,0 +1,159 @@
+/* eggdesktopfile.h - Freedesktop.Org Desktop Files
+ * Copyright (C) 2007 Novell, Inc.
+ *
+ * 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; see the file COPYING.LIB. If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place -
+ * Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __EGG_DESKTOP_FILE_H__
+#define __EGG_DESKTOP_FILE_H__
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+typedef struct EggDesktopFile EggDesktopFile;
+
+typedef enum {
+ EGG_DESKTOP_FILE_TYPE_UNRECOGNIZED,
+
+ EGG_DESKTOP_FILE_TYPE_APPLICATION,
+ EGG_DESKTOP_FILE_TYPE_LINK,
+ EGG_DESKTOP_FILE_TYPE_DIRECTORY
+} EggDesktopFileType;
+
+EggDesktopFile *egg_desktop_file_new (const char *desktop_file_path,
+ GError **error);
+
+EggDesktopFile *egg_desktop_file_new_from_data_dirs (const char *desktop_file_path,
+ GError **error);
+EggDesktopFile *egg_desktop_file_new_from_dirs (const char *desktop_file_path,
+ const char **search_dirs,
+ GError **error);
+EggDesktopFile *egg_desktop_file_new_from_key_file (GKeyFile *key_file,
+ const char *source,
+ GError **error);
+
+void egg_desktop_file_free (EggDesktopFile *desktop_file);
+
+const char *egg_desktop_file_get_source (EggDesktopFile *desktop_file);
+
+EggDesktopFileType egg_desktop_file_get_desktop_file_type (EggDesktopFile *desktop_file);
+
+const char *egg_desktop_file_get_name (EggDesktopFile *desktop_file);
+const char *egg_desktop_file_get_icon (EggDesktopFile *desktop_file);
+
+gboolean egg_desktop_file_can_launch (EggDesktopFile *desktop_file,
+ const char *desktop_environment);
+
+gboolean egg_desktop_file_accepts_documents (EggDesktopFile *desktop_file);
+gboolean egg_desktop_file_accepts_multiple (EggDesktopFile *desktop_file);
+gboolean egg_desktop_file_accepts_uris (EggDesktopFile *desktop_file);
+
+char *egg_desktop_file_parse_exec (EggDesktopFile *desktop_file,
+ GSList *documents,
+ GError **error);
+
+gboolean egg_desktop_file_launch (EggDesktopFile *desktop_file,
+ GSList *documents,
+ GError **error,
+ ...) G_GNUC_NULL_TERMINATED;
+
+typedef enum {
+ EGG_DESKTOP_FILE_LAUNCH_CLEARENV = 1,
+ EGG_DESKTOP_FILE_LAUNCH_PUTENV,
+ EGG_DESKTOP_FILE_LAUNCH_SCREEN,
+ EGG_DESKTOP_FILE_LAUNCH_WORKSPACE,
+ EGG_DESKTOP_FILE_LAUNCH_DIRECTORY,
+ EGG_DESKTOP_FILE_LAUNCH_TIME,
+ EGG_DESKTOP_FILE_LAUNCH_FLAGS,
+ EGG_DESKTOP_FILE_LAUNCH_SETUP_FUNC,
+ EGG_DESKTOP_FILE_LAUNCH_RETURN_PID,
+ EGG_DESKTOP_FILE_LAUNCH_RETURN_STDIN_PIPE,
+ EGG_DESKTOP_FILE_LAUNCH_RETURN_STDOUT_PIPE,
+ EGG_DESKTOP_FILE_LAUNCH_RETURN_STDERR_PIPE,
+ EGG_DESKTOP_FILE_LAUNCH_RETURN_STARTUP_ID
+} EggDesktopFileLaunchOption;
+
+/* Standard Keys */
+#define EGG_DESKTOP_FILE_GROUP "Desktop Entry"
+
+#define EGG_DESKTOP_FILE_KEY_TYPE "Type"
+#define EGG_DESKTOP_FILE_KEY_VERSION "Version"
+#define EGG_DESKTOP_FILE_KEY_NAME "Name"
+#define EGG_DESKTOP_FILE_KEY_GENERIC_NAME "GenericName"
+#define EGG_DESKTOP_FILE_KEY_NO_DISPLAY "NoDisplay"
+#define EGG_DESKTOP_FILE_KEY_COMMENT "Comment"
+#define EGG_DESKTOP_FILE_KEY_ICON "Icon"
+#define EGG_DESKTOP_FILE_KEY_HIDDEN "Hidden"
+#define EGG_DESKTOP_FILE_KEY_ONLY_SHOW_IN "OnlyShowIn"
+#define EGG_DESKTOP_FILE_KEY_NOT_SHOW_IN "NotShowIn"
+#define EGG_DESKTOP_FILE_KEY_TRY_EXEC "TryExec"
+#define EGG_DESKTOP_FILE_KEY_EXEC "Exec"
+#define EGG_DESKTOP_FILE_KEY_PATH "Path"
+#define EGG_DESKTOP_FILE_KEY_TERMINAL "Terminal"
+#define EGG_DESKTOP_FILE_KEY_MIME_TYPE "MimeType"
+#define EGG_DESKTOP_FILE_KEY_CATEGORIES "Categories"
+#define EGG_DESKTOP_FILE_KEY_STARTUP_NOTIFY "StartupNotify"
+#define EGG_DESKTOP_FILE_KEY_STARTUP_WM_CLASS "StartupWMClass"
+#define EGG_DESKTOP_FILE_KEY_URL "URL"
+
+/* Accessors */
+gboolean egg_desktop_file_has_key (EggDesktopFile *desktop_file,
+ const char *key,
+ GError **error);
+char *egg_desktop_file_get_string (EggDesktopFile *desktop_file,
+ const char *key,
+ GError **error) G_GNUC_MALLOC;
+char *egg_desktop_file_get_locale_string (EggDesktopFile *desktop_file,
+ const char *key,
+ const char *locale,
+ GError **error) G_GNUC_MALLOC;
+gboolean egg_desktop_file_get_boolean (EggDesktopFile *desktop_file,
+ const char *key,
+ GError **error);
+double egg_desktop_file_get_numeric (EggDesktopFile *desktop_file,
+ const char *key,
+ GError **error);
+char **egg_desktop_file_get_string_list (EggDesktopFile *desktop_file,
+ const char *key,
+ gsize *length,
+ GError **error) G_GNUC_MALLOC;
+char **egg_desktop_file_get_locale_string_list (EggDesktopFile *desktop_file,
+ const char *key,
+ const char *locale,
+ gsize *length,
+ GError **error) G_GNUC_MALLOC;
+
+
+/* Errors */
+#define EGG_DESKTOP_FILE_ERROR egg_desktop_file_error_quark()
+
+GQuark egg_desktop_file_error_quark (void);
+
+typedef enum {
+ EGG_DESKTOP_FILE_ERROR_INVALID,
+ EGG_DESKTOP_FILE_ERROR_NOT_LAUNCHABLE,
+ EGG_DESKTOP_FILE_ERROR_UNRECOGNIZED_OPTION
+} EggDesktopFileError;
+
+/* Global application desktop file */
+void egg_set_desktop_file (const char *desktop_file_path);
+EggDesktopFile *egg_get_desktop_file (void);
+
+
+G_END_DECLS
+
+#endif /* __EGG_DESKTOP_FILE_H__ */
diff --git a/copy-n-paste/eggsmclient-private.h b/copy-n-paste/eggsmclient-private.h
new file mode 100644
index 0000000..ccb10bf
--- /dev/null
+++ b/copy-n-paste/eggsmclient-private.h
@@ -0,0 +1,53 @@
+/* eggsmclient-private.h
+ * Copyright (C) 2007 Novell, Inc.
+ *
+ * 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.
+ */
+
+#ifndef __EGG_SM_CLIENT_PRIVATE_H__
+#define __EGG_SM_CLIENT_PRIVATE_H__
+
+#include <gdkconfig.h>
+#include "eggsmclient.h"
+
+G_BEGIN_DECLS
+
+GKeyFile *egg_sm_client_save_state (EggSMClient *client);
+void egg_sm_client_quit_requested (EggSMClient *client);
+void egg_sm_client_quit_cancelled (EggSMClient *client);
+void egg_sm_client_quit (EggSMClient *client);
+
+#if defined (GDK_WINDOWING_X11)
+# ifdef EGG_SM_CLIENT_BACKEND_XSMP
+GType egg_sm_client_xsmp_get_type (void);
+EggSMClient *egg_sm_client_xsmp_new (void);
+# endif
+# ifdef EGG_SM_CLIENT_BACKEND_DBUS
+GType egg_sm_client_dbus_get_type (void);
+EggSMClient *egg_sm_client_dbus_new (void);
+# endif
+#elif defined (GDK_WINDOWING_WIN32)
+GType egg_sm_client_win32_get_type (void);
+EggSMClient *egg_sm_client_win32_new (void);
+#elif defined (GDK_WINDOWING_QUARTZ)
+GType egg_sm_client_osx_get_type (void);
+EggSMClient *egg_sm_client_osx_new (void);
+#endif
+
+G_END_DECLS
+
+
+#endif /* __EGG_SM_CLIENT_PRIVATE_H__ */
diff --git a/copy-n-paste/eggsmclient-xsmp.c b/copy-n-paste/eggsmclient-xsmp.c
new file mode 100644
index 0000000..1a56156
--- /dev/null
+++ b/copy-n-paste/eggsmclient-xsmp.c
@@ -0,0 +1,1370 @@
+/*
+ * Copyright (C) 2007 Novell, Inc.
+ *
+ * Inspired by various other pieces of code including GsmClient (C)
+ * 2001 Havoc Pennington, GnomeClient (C) 1998 Carsten Schaar, and twm
+ * session code (C) 1998 The Open Group.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include "config.h"
+
+#include "eggsmclient.h"
+#include "eggsmclient-private.h"
+
+#include "eggdesktopfile.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <X11/SM/SMlib.h>
+
+#include <gdk/gdk.h>
+
+#define EGG_TYPE_SM_CLIENT_XSMP (egg_sm_client_xsmp_get_type ())
+#define EGG_SM_CLIENT_XSMP(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EGG_TYPE_SM_CLIENT_XSMP, EggSMClientXSMP))
+#define EGG_SM_CLIENT_XSMP_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), EGG_TYPE_SM_CLIENT_XSMP, EggSMClientXSMPClass))
+#define EGG_IS_SM_CLIENT_XSMP(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EGG_TYPE_SM_CLIENT_XSMP))
+#define EGG_IS_SM_CLIENT_XSMP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EGG_TYPE_SM_CLIENT_XSMP))
+#define EGG_SM_CLIENT_XSMP_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), EGG_TYPE_SM_CLIENT_XSMP, EggSMClientXSMPClass))
+
+typedef struct _EggSMClientXSMP EggSMClientXSMP;
+typedef struct _EggSMClientXSMPClass EggSMClientXSMPClass;
+
+/* These mostly correspond to the similarly-named states in section
+ * 9.1 of the XSMP spec. Some of the states there aren't represented
+ * here, because we don't need them. SHUTDOWN_CANCELLED is slightly
+ * different from the spec; we use it when the client is IDLE after a
+ * ShutdownCancelled message, but the application is still interacting
+ * and doesn't know the shutdown has been cancelled yet.
+ */
+typedef enum
+{
+ XSMP_STATE_IDLE,
+ XSMP_STATE_SAVE_YOURSELF,
+ XSMP_STATE_INTERACT_REQUEST,
+ XSMP_STATE_INTERACT,
+ XSMP_STATE_SAVE_YOURSELF_DONE,
+ XSMP_STATE_SHUTDOWN_CANCELLED,
+ XSMP_STATE_CONNECTION_CLOSED
+} EggSMClientXSMPState;
+
+static const char *state_names[] = {
+ "idle",
+ "save-yourself",
+ "interact-request",
+ "interact",
+ "save-yourself-done",
+ "shutdown-cancelled",
+ "connection-closed"
+};
+
+#define EGG_SM_CLIENT_XSMP_STATE(xsmp) (state_names[(xsmp)->state])
+
+struct _EggSMClientXSMP
+{
+ EggSMClient parent;
+
+ SmcConn connection;
+ char *client_id;
+
+ EggSMClientXSMPState state;
+ char **restart_command;
+ gboolean set_restart_command;
+ int restart_style;
+
+ guint idle;
+
+ /* Current SaveYourself state */
+ guint expecting_initial_save_yourself : 1;
+ guint need_save_state : 1;
+ guint need_quit_requested : 1;
+ guint interact_errors : 1;
+ guint shutting_down : 1;
+
+ /* Todo list */
+ guint waiting_to_set_initial_properties : 1;
+ guint waiting_to_emit_quit : 1;
+ guint waiting_to_emit_quit_cancelled : 1;
+ guint waiting_to_save_myself : 1;
+
+};
+
+struct _EggSMClientXSMPClass
+{
+ EggSMClientClass parent_class;
+
+};
+
+static void sm_client_xsmp_startup (EggSMClient *client,
+ const char *client_id);
+static void sm_client_xsmp_set_restart_command (EggSMClient *client,
+ int argc,
+ const char **argv);
+static void sm_client_xsmp_will_quit (EggSMClient *client,
+ gboolean will_quit);
+static gboolean sm_client_xsmp_end_session (EggSMClient *client,
+ EggSMClientEndStyle style,
+ gboolean request_confirmation);
+
+static void xsmp_save_yourself (SmcConn smc_conn,
+ SmPointer client_data,
+ int save_style,
+ Bool shutdown,
+ int interact_style,
+ Bool fast);
+static void xsmp_die (SmcConn smc_conn,
+ SmPointer client_data);
+static void xsmp_save_complete (SmcConn smc_conn,
+ SmPointer client_data);
+static void xsmp_shutdown_cancelled (SmcConn smc_conn,
+ SmPointer client_data);
+static void xsmp_interact (SmcConn smc_conn,
+ SmPointer client_data);
+
+static SmProp *array_prop (const char *name,
+ ...);
+static SmProp *ptrarray_prop (const char *name,
+ GPtrArray *values);
+static SmProp *string_prop (const char *name,
+ const char *value);
+static SmProp *card8_prop (const char *name,
+ unsigned char value);
+
+static void set_properties (EggSMClientXSMP *xsmp, ...);
+static void delete_properties (EggSMClientXSMP *xsmp, ...);
+
+static GPtrArray *generate_command (char **restart_command,
+ const char *client_id,
+ const char *state_file);
+
+static void save_state (EggSMClientXSMP *xsmp);
+static void do_save_yourself (EggSMClientXSMP *xsmp);
+static void update_pending_events (EggSMClientXSMP *xsmp);
+
+static void ice_init (void);
+static gboolean process_ice_messages (IceConn ice_conn);
+static void smc_error_handler (SmcConn smc_conn,
+ Bool swap,
+ int offending_minor_opcode,
+ unsigned long offending_sequence,
+ int error_class,
+ int severity,
+ SmPointer values);
+
+G_DEFINE_TYPE (EggSMClientXSMP, egg_sm_client_xsmp, EGG_TYPE_SM_CLIENT)
+
+static void
+egg_sm_client_xsmp_init (EggSMClientXSMP *xsmp)
+{
+ xsmp->state = XSMP_STATE_CONNECTION_CLOSED;
+ xsmp->connection = NULL;
+ xsmp->restart_style = SmRestartIfRunning;
+}
+
+static void
+egg_sm_client_xsmp_class_init (EggSMClientXSMPClass *klass)
+{
+ EggSMClientClass *sm_client_class = EGG_SM_CLIENT_CLASS (klass);
+
+ sm_client_class->startup = sm_client_xsmp_startup;
+ sm_client_class->set_restart_command = sm_client_xsmp_set_restart_command;
+ sm_client_class->will_quit = sm_client_xsmp_will_quit;
+ sm_client_class->end_session = sm_client_xsmp_end_session;
+}
+
+EggSMClient *
+egg_sm_client_xsmp_new (void)
+{
+ if (!g_getenv ("SESSION_MANAGER"))
+ return NULL;
+
+ return g_object_new (EGG_TYPE_SM_CLIENT_XSMP, NULL);
+}
+
+static gboolean
+sm_client_xsmp_set_initial_properties (gpointer user_data)
+{
+ EggSMClientXSMP *xsmp = user_data;
+ EggDesktopFile *desktop_file;
+ GPtrArray *clone, *restart;
+ char pid_str[64];
+
+ if (xsmp->idle)
+ {
+ g_source_remove (xsmp->idle);
+ xsmp->idle = 0;
+ }
+ xsmp->waiting_to_set_initial_properties = FALSE;
+
+ if (egg_sm_client_get_mode () == EGG_SM_CLIENT_MODE_NO_RESTART)
+ xsmp->restart_style = SmRestartNever;
+
+ /* Parse info out of desktop file */
+ desktop_file = egg_get_desktop_file ();
+ if (desktop_file)
+ {
+ GError *err = NULL;
+ char *cmdline, **argv;
+ int argc;
+
+ if (xsmp->restart_style == SmRestartIfRunning)
+ {
+ if (egg_desktop_file_get_boolean (desktop_file,
+ "X-GNOME-AutoRestart", NULL))
+ xsmp->restart_style = SmRestartImmediately;
+ }
+
+ if (!xsmp->set_restart_command)
+ {
+ cmdline = egg_desktop_file_parse_exec (desktop_file, NULL, &err);
+ if (cmdline && g_shell_parse_argv (cmdline, &argc, &argv, &err))
+ {
+ egg_sm_client_set_restart_command (EGG_SM_CLIENT (xsmp),
+ argc, (const char **)argv);
+ g_strfreev (argv);
+ }
+ else
+ {
+ g_warning ("Could not parse Exec line in desktop file: %s",
+ err->message);
+ g_error_free (err);
+ }
+ g_free (cmdline);
+ }
+ }
+
+ if (!xsmp->set_restart_command)
+ xsmp->restart_command = g_strsplit (g_get_prgname (), " ", -1);
+
+ clone = generate_command (xsmp->restart_command, NULL, NULL);
+ restart = generate_command (xsmp->restart_command, xsmp->client_id, NULL);
+
+ g_debug ("Setting initial properties");
+
+ /* Program, CloneCommand, RestartCommand, and UserID are required.
+ * ProcessID isn't required, but the SM may be able to do something
+ * useful with it.
+ */
+ g_snprintf (pid_str, sizeof (pid_str), "%lu", (gulong) getpid ());
+ set_properties (xsmp,
+ string_prop (SmProgram, g_get_prgname ()),
+ ptrarray_prop (SmCloneCommand, clone),
+ ptrarray_prop (SmRestartCommand, restart),
+ string_prop (SmUserID, g_get_user_name ()),
+ string_prop (SmProcessID, pid_str),
+ card8_prop (SmRestartStyleHint, xsmp->restart_style),
+ NULL);
+ g_ptr_array_free (clone, TRUE);
+ g_ptr_array_free (restart, TRUE);
+
+ if (desktop_file)
+ {
+ set_properties (xsmp,
+ string_prop ("_GSM_DesktopFile", egg_desktop_file_get_source (desktop_file)),
+ NULL);
+ }
+
+ update_pending_events (xsmp);
+ return FALSE;
+}
+
+/* This gets called from two different places: xsmp_die() (when the
+ * server asks us to disconnect) and process_ice_messages() (when the
+ * server disconnects unexpectedly).
+ */
+static void
+sm_client_xsmp_disconnect (EggSMClientXSMP *xsmp)
+{
+ SmcConn connection;
+
+ if (!xsmp->connection)
+ return;
+
+ g_debug ("Disconnecting");
+
+ connection = xsmp->connection;
+ xsmp->connection = NULL;
+ SmcCloseConnection (connection, 0, NULL);
+ xsmp->state = XSMP_STATE_CONNECTION_CLOSED;
+
+ xsmp->waiting_to_save_myself = FALSE;
+ update_pending_events (xsmp);
+}
+
+static void
+sm_client_xsmp_startup (EggSMClient *client,
+ const char *client_id)
+{
+ EggSMClientXSMP *xsmp = (EggSMClientXSMP *)client;
+ SmcCallbacks callbacks;
+ char *ret_client_id;
+ char error_string_ret[256];
+
+ xsmp->client_id = g_strdup (client_id);
+
+ ice_init ();
+ SmcSetErrorHandler (smc_error_handler);
+
+ callbacks.save_yourself.callback = xsmp_save_yourself;
+ callbacks.die.callback = xsmp_die;
+ callbacks.save_complete.callback = xsmp_save_complete;
+ callbacks.shutdown_cancelled.callback = xsmp_shutdown_cancelled;
+
+ callbacks.save_yourself.client_data = xsmp;
+ callbacks.die.client_data = xsmp;
+ callbacks.save_complete.client_data = xsmp;
+ callbacks.shutdown_cancelled.client_data = xsmp;
+
+ client_id = NULL;
+ error_string_ret[0] = '\0';
+ xsmp->connection =
+ SmcOpenConnection (NULL, xsmp, SmProtoMajor, SmProtoMinor,
+ SmcSaveYourselfProcMask | SmcDieProcMask |
+ SmcSaveCompleteProcMask |
+ SmcShutdownCancelledProcMask,
+ &callbacks,
+ xsmp->client_id, &ret_client_id,
+ sizeof (error_string_ret), error_string_ret);
+
+ if (!xsmp->connection)
+ {
+ g_warning ("Failed to connect to the session manager: %s\n",
+ error_string_ret[0] ?
+ error_string_ret : "no error message given");
+ xsmp->state = XSMP_STATE_CONNECTION_CLOSED;
+ return;
+ }
+
+ /* We expect a pointless initial SaveYourself if either (a) we
+ * didn't have an initial client ID, or (b) we DID have an initial
+ * client ID, but the server rejected it and gave us a new one.
+ */
+ if (!xsmp->client_id ||
+ (ret_client_id && strcmp (xsmp->client_id, ret_client_id) != 0))
+ xsmp->expecting_initial_save_yourself = TRUE;
+
+ if (ret_client_id)
+ {
+ g_free (xsmp->client_id);
+ xsmp->client_id = g_strdup (ret_client_id);
+ free (ret_client_id);
+
+ gdk_threads_enter ();
+ gdk_set_sm_client_id (xsmp->client_id);
+ gdk_threads_leave ();
+
+ g_debug ("Got client ID \"%s\"", xsmp->client_id);
+ }
+
+ xsmp->state = XSMP_STATE_IDLE;
+
+ /* Do not set the initial properties until we reach the main loop,
+ * so that the application has a chance to call
+ * egg_set_desktop_file(). (This may also help the session manager
+ * have a better idea of when the application is fully up and
+ * running.)
+ */
+ xsmp->waiting_to_set_initial_properties = TRUE;
+ xsmp->idle = g_idle_add (sm_client_xsmp_set_initial_properties, client);
+}
+
+static void
+sm_client_xsmp_set_restart_command (EggSMClient *client,
+ int argc,
+ const char **argv)
+{
+ EggSMClientXSMP *xsmp = (EggSMClientXSMP *)client;
+ int i;
+
+ g_strfreev (xsmp->restart_command);
+
+ xsmp->restart_command = g_new (char *, argc + 1);
+ for (i = 0; i < argc; i++)
+ xsmp->restart_command[i] = g_strdup (argv[i]);
+ xsmp->restart_command[i] = NULL;
+
+ xsmp->set_restart_command = TRUE;
+}
+
+static void
+sm_client_xsmp_will_quit (EggSMClient *client,
+ gboolean will_quit)
+{
+ EggSMClientXSMP *xsmp = (EggSMClientXSMP *)client;
+
+ if (xsmp->state == XSMP_STATE_CONNECTION_CLOSED)
+ {
+ /* The session manager has already exited! Schedule a quit
+ * signal.
+ */
+ xsmp->waiting_to_emit_quit = TRUE;
+ update_pending_events (xsmp);
+ return;
+ }
+ else if (xsmp->state == XSMP_STATE_SHUTDOWN_CANCELLED)
+ {
+ /* We received a ShutdownCancelled message while the application
+ * was interacting; Schedule a quit_cancelled signal.
+ */
+ xsmp->waiting_to_emit_quit_cancelled = TRUE;
+ update_pending_events (xsmp);
+ return;
+ }
+
+ g_return_if_fail (xsmp->state == XSMP_STATE_INTERACT);
+
+ g_debug ("Sending InteractDone(%s)", will_quit ? "False" : "True");
+ SmcInteractDone (xsmp->connection, !will_quit);
+
+ if (will_quit && xsmp->need_save_state)
+ save_state (xsmp);
+
+ g_debug ("Sending SaveYourselfDone(%s)", will_quit ? "True" : "False");
+ SmcSaveYourselfDone (xsmp->connection, will_quit);
+ xsmp->state = XSMP_STATE_SAVE_YOURSELF_DONE;
+}
+
+static gboolean
+sm_client_xsmp_end_session (EggSMClient *client,
+ EggSMClientEndStyle style,
+ gboolean request_confirmation)
+{
+ EggSMClientXSMP *xsmp = (EggSMClientXSMP *)client;
+ int save_type;
+
+ /* To end the session via XSMP, we have to send a
+ * SaveYourselfRequest. We aren't allowed to do that if anything
+ * else is going on, but we don't want to expose this fact to the
+ * application. So we do our best to patch things up here...
+ *
+ * In the worst case, this method might block for some length of
+ * time in process_ice_messages, but the only time that code path is
+ * honestly likely to get hit is if the application tries to end the
+ * session as the very first thing it does, in which case it
+ * probably won't actually block anyway. It's not worth gunking up
+ * the API to try to deal nicely with the other 0.01% of cases where
+ * this happens.
+ */
+
+ while (xsmp->state != XSMP_STATE_IDLE ||
+ xsmp->expecting_initial_save_yourself)
+ {
+ /* If we're already shutting down, we don't need to do anything. */
+ if (xsmp->shutting_down)
+ return TRUE;
+
+ switch (xsmp->state)
+ {
+ case XSMP_STATE_CONNECTION_CLOSED:
+ return FALSE;
+
+ case XSMP_STATE_SAVE_YOURSELF:
+ /* Trying to log out from the save_state callback? Whatever.
+ * Abort the save_state.
+ */
+ SmcSaveYourselfDone (xsmp->connection, FALSE);
+ xsmp->state = XSMP_STATE_SAVE_YOURSELF_DONE;
+ break;
+
+ case XSMP_STATE_INTERACT_REQUEST:
+ case XSMP_STATE_INTERACT:
+ case XSMP_STATE_SHUTDOWN_CANCELLED:
+ /* Already in a shutdown-related state, just ignore
+ * the new shutdown request...
+ */
+ return TRUE;
+
+ case XSMP_STATE_IDLE:
+ if (xsmp->waiting_to_set_initial_properties)
+ sm_client_xsmp_set_initial_properties (xsmp);
+
+ if (!xsmp->expecting_initial_save_yourself)
+ break;
+ /* else fall through */
+
+ case XSMP_STATE_SAVE_YOURSELF_DONE:
+ /* We need to wait for some response from the server.*/
+ process_ice_messages (SmcGetIceConnection (xsmp->connection));
+ break;
+
+ default:
+ /* Hm... shouldn't happen */
+ return FALSE;
+ }
+ }
+
+ /* xfce4-session will do the wrong thing if we pass SmSaveGlobal and
+ * the user chooses to save the session. But gnome-session will do
+ * the wrong thing if we pass SmSaveBoth and the user chooses NOT to
+ * save the session... Sigh.
+ */
+ if (!strcmp (SmcVendor (xsmp->connection), "xfce4-session"))
+ save_type = SmSaveBoth;
+ else
+ save_type = SmSaveGlobal;
+
+ g_debug ("Sending SaveYourselfRequest(SmSaveGlobal, Shutdown, SmInteractStyleAny, %sFast)", request_confirmation ? "!" : "");
+ SmcRequestSaveYourself (xsmp->connection,
+ save_type,
+ True, /* shutdown */
+ SmInteractStyleAny,
+ !request_confirmation, /* fast */
+ True /* global */);
+ return TRUE;
+}
+
+static gboolean
+idle_do_pending_events (gpointer data)
+{
+ EggSMClientXSMP *xsmp = data;
+ EggSMClient *client = data;
+
+ gdk_threads_enter ();
+
+ xsmp->idle = 0;
+
+ if (xsmp->waiting_to_emit_quit)
+ {
+ xsmp->waiting_to_emit_quit = FALSE;
+ egg_sm_client_quit (client);
+ goto out;
+ }
+
+ if (xsmp->waiting_to_emit_quit_cancelled)
+ {
+ xsmp->waiting_to_emit_quit_cancelled = FALSE;
+ egg_sm_client_quit_cancelled (client);
+ xsmp->state = XSMP_STATE_IDLE;
+ }
+
+ if (xsmp->waiting_to_save_myself)
+ {
+ xsmp->waiting_to_save_myself = FALSE;
+ do_save_yourself (xsmp);
+ }
+
+ out:
+ gdk_threads_leave ();
+ return FALSE;
+}
+
+static void
+update_pending_events (EggSMClientXSMP *xsmp)
+{
+ gboolean want_idle =
+ xsmp->waiting_to_emit_quit ||
+ xsmp->waiting_to_emit_quit_cancelled ||
+ xsmp->waiting_to_save_myself;
+
+ if (want_idle)
+ {
+ if (xsmp->idle == 0)
+ xsmp->idle = g_idle_add (idle_do_pending_events, xsmp);
+ }
+ else
+ {
+ if (xsmp->idle != 0)
+ g_source_remove (xsmp->idle);
+ xsmp->idle = 0;
+ }
+}
+
+static void
+fix_broken_state (EggSMClientXSMP *xsmp, const char *message,
+ gboolean send_interact_done,
+ gboolean send_save_yourself_done)
+{
+ g_warning ("Received XSMP %s message in state %s: client or server error",
+ message, EGG_SM_CLIENT_XSMP_STATE (xsmp));
+
+ /* Forget any pending SaveYourself plans we had */
+ xsmp->waiting_to_save_myself = FALSE;
+ update_pending_events (xsmp);
+
+ if (send_interact_done)
+ SmcInteractDone (xsmp->connection, False);
+ if (send_save_yourself_done)
+ SmcSaveYourselfDone (xsmp->connection, True);
+
+ xsmp->state = send_save_yourself_done ? XSMP_STATE_SAVE_YOURSELF_DONE : XSMP_STATE_IDLE;
+}
+
+/* SM callbacks */
+
+static void
+xsmp_save_yourself (SmcConn smc_conn,
+ SmPointer client_data,
+ int save_type,
+ Bool shutdown,
+ int interact_style,
+ Bool fast)
+{
+ EggSMClientXSMP *xsmp = client_data;
+ gboolean wants_quit_requested;
+
+ g_debug ("Received SaveYourself(%s, %s, %s, %s) in state %s",
+ save_type == SmSaveLocal ? "SmSaveLocal" :
+ save_type == SmSaveGlobal ? "SmSaveGlobal" : "SmSaveBoth",
+ shutdown ? "Shutdown" : "!Shutdown",
+ interact_style == SmInteractStyleAny ? "SmInteractStyleAny" :
+ interact_style == SmInteractStyleErrors ? "SmInteractStyleErrors" :
+ "SmInteractStyleNone", fast ? "Fast" : "!Fast",
+ EGG_SM_CLIENT_XSMP_STATE (xsmp));
+
+ if (xsmp->state != XSMP_STATE_IDLE &&
+ xsmp->state != XSMP_STATE_SHUTDOWN_CANCELLED)
+ {
+ fix_broken_state (xsmp, "SaveYourself", FALSE, TRUE);
+ return;
+ }
+
+ if (xsmp->waiting_to_set_initial_properties)
+ sm_client_xsmp_set_initial_properties (xsmp);
+
+ /* If this is the initial SaveYourself, ignore it; we've already set
+ * properties and there's no reason to actually save state too.
+ */
+ if (xsmp->expecting_initial_save_yourself)
+ {
+ xsmp->expecting_initial_save_yourself = FALSE;
+
+ if (save_type == SmSaveLocal &&
+ interact_style == SmInteractStyleNone &&
+ !shutdown && !fast)
+ {
+ g_debug ("Sending SaveYourselfDone(True) for initial SaveYourself");
+ SmcSaveYourselfDone (xsmp->connection, True);
+ /* As explained in the comment at the end of
+ * do_save_yourself(), SAVE_YOURSELF_DONE is the correct
+ * state here, not IDLE.
+ */
+ xsmp->state = XSMP_STATE_SAVE_YOURSELF_DONE;
+ return;
+ }
+ else
+ g_warning ("First SaveYourself was not the expected one!");
+ }
+
+ /* Even ignoring the "fast" flag completely, there are still 18
+ * different combinations of save_type, shutdown and interact_style.
+ * We interpret them as follows:
+ *
+ * Type Shutdown Interact Interpretation
+ * G F A/E/N do nothing (1)
+ * G T N do nothing (1)*
+ * G T A/E quit_requested (2)
+ * L/B F A/E/N save_state (3)
+ * L/B T N save_state (3)*
+ * L/B T A/E quit_requested, then save_state (4)
+ *
+ * 1. Do nothing, because the SM asked us to do something
+ * uninteresting (save open files, but then don't quit
+ * afterward) or rude (save open files without asking the user
+ * for confirmation).
+ *
+ * 2. Request interaction and then emit ::quit_requested. This
+ * perhaps isn't quite correct for the SmInteractStyleErrors
+ * case, but we don't care.
+ *
+ * 3. Emit ::save_state. The SmSaveBoth SaveYourselfs in these
+ * rows essentially get demoted to SmSaveLocal, because their
+ * Global halves correspond to "do nothing".
+ *
+ * 4. Request interaction, emit ::quit_requested, and then emit
+ * ::save_state after interacting. This is the SmSaveBoth
+ * equivalent of #2, but we also promote SmSaveLocal shutdown
+ * SaveYourselfs to SmSaveBoth here, because we want to give
+ * the user a chance to save open files before quitting.
+ *
+ * (* It would be nice if we could do something useful when the
+ * session manager sends a SaveYourself with shutdown True and
+ * SmInteractStyleNone. But we can't, so we just pretend it didn't
+ * even tell us it was shutting down. The docs for ::quit mention
+ * that it might not always be preceded by ::quit_requested.)
+ */
+
+ /* As an optimization, we don't actually request interaction and
+ * emit ::quit_requested if the application isn't listening to the
+ * signal.
+ */
+ wants_quit_requested = g_signal_has_handler_pending (xsmp, g_signal_lookup ("quit_requested", EGG_TYPE_SM_CLIENT), 0, FALSE);
+
+ xsmp->need_save_state = (save_type != SmSaveGlobal);
+ xsmp->need_quit_requested = (shutdown && wants_quit_requested &&
+ interact_style != SmInteractStyleNone);
+ xsmp->interact_errors = (interact_style == SmInteractStyleErrors);
+
+ xsmp->shutting_down = shutdown;
+
+ do_save_yourself (xsmp);
+}
+
+static void
+do_save_yourself (EggSMClientXSMP *xsmp)
+{
+ if (xsmp->state == XSMP_STATE_SHUTDOWN_CANCELLED)
+ {
+ /* The SM cancelled a previous SaveYourself, but we haven't yet
+ * had a chance to tell the application, so we can't start
+ * processing this SaveYourself yet.
+ */
+ xsmp->waiting_to_save_myself = TRUE;
+ update_pending_events (xsmp);
+ return;
+ }
+
+ if (xsmp->need_quit_requested)
+ {
+ xsmp->state = XSMP_STATE_INTERACT_REQUEST;
+
+ g_debug ("Sending InteractRequest(%s)",
+ xsmp->interact_errors ? "Error" : "Normal");
+ SmcInteractRequest (xsmp->connection,
+ xsmp->interact_errors ? SmDialogError : SmDialogNormal,
+ xsmp_interact,
+ xsmp);
+ return;
+ }
+
+ if (xsmp->need_save_state)
+ {
+ save_state (xsmp);
+
+ /* Though unlikely, the client could have been disconnected
+ * while the application was saving its state.
+ */
+ if (!xsmp->connection)
+ return;
+ }
+
+ g_debug ("Sending SaveYourselfDone(True)");
+ SmcSaveYourselfDone (xsmp->connection, True);
+
+ /* The client state diagram in the XSMP spec says that after a
+ * non-shutdown SaveYourself, we go directly back to "idle". But
+ * everything else in both the XSMP spec and the libSM docs
+ * disagrees.
+ */
+ xsmp->state = XSMP_STATE_SAVE_YOURSELF_DONE;
+}
+
+static void
+save_state (EggSMClientXSMP *xsmp)
+{
+ GKeyFile *state_file;
+ char *state_file_path, *data;
+ EggDesktopFile *desktop_file;
+ GPtrArray *restart;
+ int offset, fd;
+
+ /* We set xsmp->state before emitting save_state, but our caller is
+ * responsible for setting it back afterward.
+ */
+ xsmp->state = XSMP_STATE_SAVE_YOURSELF;
+
+ state_file = egg_sm_client_save_state ((EggSMClient *)xsmp);
+ if (!state_file)
+ {
+ restart = generate_command (xsmp->restart_command, xsmp->client_id, NULL);
+ set_properties (xsmp,
+ ptrarray_prop (SmRestartCommand, restart),
+ NULL);
+ g_ptr_array_free (restart, TRUE);
+ delete_properties (xsmp, SmDiscardCommand, NULL);
+ return;
+ }
+
+ desktop_file = egg_get_desktop_file ();
+ if (desktop_file)
+ {
+ GKeyFile *merged_file;
+ char *desktop_file_path;
+
+ merged_file = g_key_file_new ();
+ desktop_file_path =
+ g_filename_from_uri (egg_desktop_file_get_source (desktop_file),
+ NULL, NULL);
+ if (desktop_file_path &&
+ g_key_file_load_from_file (merged_file, desktop_file_path,
+ G_KEY_FILE_KEEP_COMMENTS |
+ G_KEY_FILE_KEEP_TRANSLATIONS, NULL))
+ {
+ guint g, k, i;
+ char **groups, **keys, *value, *exec;
+
+ groups = g_key_file_get_groups (state_file, NULL);
+ for (g = 0; groups[g]; g++)
+ {
+ keys = g_key_file_get_keys (state_file, groups[g], NULL, NULL);
+ for (k = 0; keys[k]; k++)
+ {
+ value = g_key_file_get_value (state_file, groups[g],
+ keys[k], NULL);
+ if (value)
+ {
+ g_key_file_set_value (merged_file, groups[g],
+ keys[k], value);
+ g_free (value);
+ }
+ }
+ g_strfreev (keys);
+ }
+ g_strfreev (groups);
+
+ g_key_file_free (state_file);
+ state_file = merged_file;
+
+ /* Update Exec key using "--sm-client-state-file %k" */
+ restart = generate_command (xsmp->restart_command,
+ NULL, "%k");
+ for (i = 0; i < restart->len; i++)
+ restart->pdata[i] = g_shell_quote (restart->pdata[i]);
+ g_ptr_array_add (restart, NULL);
+ exec = g_strjoinv (" ", (char **)restart->pdata);
+ g_strfreev ((char **)restart->pdata);
+ g_ptr_array_free (restart, FALSE);
+
+ g_key_file_set_string (state_file, EGG_DESKTOP_FILE_GROUP,
+ EGG_DESKTOP_FILE_KEY_EXEC,
+ exec);
+ g_free (exec);
+ }
+ else
+ desktop_file = NULL;
+
+ g_free (desktop_file_path);
+ }
+
+ /* Now write state_file to disk. (We can't use mktemp(), because
+ * that requires the filename to end with "XXXXXX", and we want
+ * it to end with ".desktop".)
+ */
+
+ data = g_key_file_to_data (state_file, NULL, NULL);
+ g_key_file_free (state_file);
+
+ offset = 0;
+ while (1)
+ {
+ state_file_path = g_strdup_printf ("%s%csession-state%c%s-%ld.%s",
+ g_get_user_config_dir (),
+ G_DIR_SEPARATOR, G_DIR_SEPARATOR,
+ g_get_prgname (),
+ (long)time (NULL) + offset,
+ desktop_file ? "desktop" : "state");
+
+ fd = open (state_file_path, O_WRONLY | O_CREAT | O_EXCL, 0644);
+ if (fd == -1)
+ {
+ if (errno == EEXIST)
+ {
+ offset++;
+ g_free (state_file_path);
+ continue;
+ }
+ else if (errno == ENOTDIR || errno == ENOENT)
+ {
+ char *sep = strrchr (state_file_path, G_DIR_SEPARATOR);
+
+ *sep = '\0';
+ if (g_mkdir_with_parents (state_file_path, 0755) != 0)
+ {
+ g_warning ("Could not create directory '%s'",
+ state_file_path);
+ g_free (state_file_path);
+ state_file_path = NULL;
+ break;
+ }
+
+ continue;
+ }
+
+ g_warning ("Could not create file '%s': %s",
+ state_file_path, g_strerror (errno));
+ g_free (state_file_path);
+ state_file_path = NULL;
+ break;
+ }
+
+ close (fd);
+ g_file_set_contents (state_file_path, data, -1, NULL);
+ break;
+ }
+ g_free (data);
+
+ restart = generate_command (xsmp->restart_command, xsmp->client_id,
+ state_file_path);
+ set_properties (xsmp,
+ ptrarray_prop (SmRestartCommand, restart),
+ NULL);
+ g_ptr_array_free (restart, TRUE);
+
+ if (state_file_path)
+ {
+ set_properties (xsmp,
+ array_prop (SmDiscardCommand,
+ "/bin/rm", "-rf", state_file_path,
+ NULL),
+ NULL);
+ g_free (state_file_path);
+ }
+}
+
+static void
+xsmp_interact (SmcConn smc_conn,
+ SmPointer client_data)
+{
+ EggSMClientXSMP *xsmp = client_data;
+ EggSMClient *client = client_data;
+
+ g_debug ("Received Interact message in state %s",
+ EGG_SM_CLIENT_XSMP_STATE (xsmp));
+
+ if (xsmp->state != XSMP_STATE_INTERACT_REQUEST)
+ {
+ fix_broken_state (xsmp, "Interact", TRUE, TRUE);
+ return;
+ }
+
+ xsmp->state = XSMP_STATE_INTERACT;
+ egg_sm_client_quit_requested (client);
+}
+
+static void
+xsmp_die (SmcConn smc_conn,
+ SmPointer client_data)
+{
+ EggSMClientXSMP *xsmp = client_data;
+ EggSMClient *client = client_data;
+
+ g_debug ("Received Die message in state %s",
+ EGG_SM_CLIENT_XSMP_STATE (xsmp));
+
+ sm_client_xsmp_disconnect (xsmp);
+ egg_sm_client_quit (client);
+}
+
+static void
+xsmp_save_complete (SmcConn smc_conn,
+ SmPointer client_data)
+{
+ EggSMClientXSMP *xsmp = client_data;
+
+ g_debug ("Received SaveComplete message in state %s",
+ EGG_SM_CLIENT_XSMP_STATE (xsmp));
+
+ if (xsmp->state == XSMP_STATE_SAVE_YOURSELF_DONE)
+ xsmp->state = XSMP_STATE_IDLE;
+ else
+ fix_broken_state (xsmp, "SaveComplete", FALSE, FALSE);
+}
+
+static void
+xsmp_shutdown_cancelled (SmcConn smc_conn,
+ SmPointer client_data)
+{
+ EggSMClientXSMP *xsmp = client_data;
+ EggSMClient *client = client_data;
+
+ g_debug ("Received ShutdownCancelled message in state %s",
+ EGG_SM_CLIENT_XSMP_STATE (xsmp));
+
+ xsmp->shutting_down = FALSE;
+
+ if (xsmp->state == XSMP_STATE_SAVE_YOURSELF_DONE)
+ {
+ /* We've finished interacting and now the SM has agreed to
+ * cancel the shutdown.
+ */
+ xsmp->state = XSMP_STATE_IDLE;
+ egg_sm_client_quit_cancelled (client);
+ }
+ else if (xsmp->state == XSMP_STATE_SHUTDOWN_CANCELLED)
+ {
+ /* Hm... ok, so we got a shutdown SaveYourself, which got
+ * cancelled, but the application was still interacting, so we
+ * didn't tell it yet, and then *another* SaveYourself arrived,
+ * which we must still be waiting to tell the app about, except
+ * that now that SaveYourself has been cancelled too! Dizzy yet?
+ */
+ xsmp->waiting_to_save_myself = FALSE;
+ update_pending_events (xsmp);
+ }
+ else
+ {
+ g_debug ("Sending SaveYourselfDone(False)");
+ SmcSaveYourselfDone (xsmp->connection, False);
+
+ if (xsmp->state == XSMP_STATE_INTERACT)
+ {
+ /* The application is currently interacting, so we can't
+ * tell it about the cancellation yet; we will wait until
+ * after it calls egg_sm_client_will_quit().
+ */
+ xsmp->state = XSMP_STATE_SHUTDOWN_CANCELLED;
+ }
+ else
+ {
+ /* The shutdown was cancelled before the application got a
+ * chance to interact.
+ */
+ xsmp->state = XSMP_STATE_IDLE;
+ }
+ }
+}
+
+/* Utilities */
+
+/* Create a restart/clone/Exec command based on @restart_command.
+ * If @client_id is non-%NULL, add "--sm-client-id @client_id".
+ * If @state_file is non-%NULL, add "--sm-client-state-file @state_file".
+ *
+ * None of the input strings are g_strdup()ed; the caller must keep
+ * them around until it is done with the returned GPtrArray, and must
+ * then free the array, but not its contents.
+ */
+static GPtrArray *
+generate_command (char **restart_command, const char *client_id,
+ const char *state_file)
+{
+ GPtrArray *cmd;
+ int i;
+
+ cmd = g_ptr_array_new ();
+ g_ptr_array_add (cmd, restart_command[0]);
+
+ if (client_id)
+ {
+ g_ptr_array_add (cmd, "--sm-client-id");
+ g_ptr_array_add (cmd, (char *)client_id);
+ }
+
+ if (state_file)
+ {
+ g_ptr_array_add (cmd, "--sm-client-state-file");
+ g_ptr_array_add (cmd, (char *)state_file);
+ }
+
+ for (i = 1; restart_command[i]; i++)
+ g_ptr_array_add (cmd, restart_command[i]);
+
+ return cmd;
+}
+
+/* Takes a NULL-terminated list of SmProp * values, created by
+ * array_prop, ptrarray_prop, string_prop, card8_prop, sets them, and
+ * frees them.
+ */
+static void
+set_properties (EggSMClientXSMP *xsmp, ...)
+{
+ GPtrArray *props;
+ SmProp *prop;
+ va_list ap;
+ guint i;
+
+ props = g_ptr_array_new ();
+
+ va_start (ap, xsmp);
+ while ((prop = va_arg (ap, SmProp *)))
+ g_ptr_array_add (props, prop);
+ va_end (ap);
+
+ if (xsmp->connection)
+ {
+ SmcSetProperties (xsmp->connection, props->len,
+ (SmProp **)props->pdata);
+ }
+
+ for (i = 0; i < props->len; i++)
+ {
+ prop = props->pdata[i];
+ g_free (prop->vals);
+ g_free (prop);
+ }
+ g_ptr_array_free (props, TRUE);
+}
+
+/* Takes a NULL-terminated list of property names and deletes them. */
+static void
+delete_properties (EggSMClientXSMP *xsmp, ...)
+{
+ GPtrArray *props;
+ char *prop;
+ va_list ap;
+
+ if (!xsmp->connection)
+ return;
+
+ props = g_ptr_array_new ();
+
+ va_start (ap, xsmp);
+ while ((prop = va_arg (ap, char *)))
+ g_ptr_array_add (props, prop);
+ va_end (ap);
+
+ SmcDeleteProperties (xsmp->connection, props->len,
+ (char **)props->pdata);
+
+ g_ptr_array_free (props, TRUE);
+}
+
+/* Takes an array of strings and creates a LISTofARRAY8 property. The
+ * strings are neither dupped nor freed; they need to remain valid
+ * until you're done with the SmProp.
+ */
+static SmProp *
+array_prop (const char *name, ...)
+{
+ SmProp *prop;
+ SmPropValue pv;
+ GArray *vals;
+ char *value;
+ va_list ap;
+
+ prop = g_new (SmProp, 1);
+ prop->name = (char *)name;
+ prop->type = SmLISTofARRAY8;
+
+ vals = g_array_new (FALSE, FALSE, sizeof (SmPropValue));
+
+ va_start (ap, name);
+ while ((value = va_arg (ap, char *)))
+ {
+ pv.length = strlen (value);
+ pv.value = value;
+ g_array_append_val (vals, pv);
+ }
+
+ prop->num_vals = vals->len;
+ prop->vals = (SmPropValue *)vals->data;
+
+ g_array_free (vals, FALSE);
+
+ return prop;
+}
+
+/* Takes a GPtrArray of strings and creates a LISTofARRAY8 property.
+ * The array contents are neither dupped nor freed; they need to
+ * remain valid until you're done with the SmProp.
+ */
+static SmProp *
+ptrarray_prop (const char *name, GPtrArray *values)
+{
+ SmProp *prop;
+ SmPropValue pv;
+ GArray *vals;
+ guint i;
+
+ prop = g_new (SmProp, 1);
+ prop->name = (char *)name;
+ prop->type = SmLISTofARRAY8;
+
+ vals = g_array_new (FALSE, FALSE, sizeof (SmPropValue));
+
+ for (i = 0; i < values->len; i++)
+ {
+ pv.length = strlen (values->pdata[i]);
+ pv.value = values->pdata[i];
+ g_array_append_val (vals, pv);
+ }
+
+ prop->num_vals = vals->len;
+ prop->vals = (SmPropValue *)vals->data;
+
+ g_array_free (vals, FALSE);
+
+ return prop;
+}
+
+/* Takes a string and creates an ARRAY8 property. The string is
+ * neither dupped nor freed; it needs to remain valid until you're
+ * done with the SmProp.
+ */
+static SmProp *
+string_prop (const char *name, const char *value)
+{
+ SmProp *prop;
+
+ prop = g_new (SmProp, 1);
+ prop->name = (char *)name;
+ prop->type = SmARRAY8;
+
+ prop->num_vals = 1;
+ prop->vals = g_new (SmPropValue, 1);
+
+ prop->vals[0].length = strlen (value);
+ prop->vals[0].value = (char *)value;
+
+ return prop;
+}
+
+/* Takes a char and creates a CARD8 property. */
+static SmProp *
+card8_prop (const char *name, unsigned char value)
+{
+ SmProp *prop;
+ char *card8val;
+
+ /* To avoid having to allocate and free prop->vals[0], we cheat and
+ * make vals a 2-element-long array and then use the second element
+ * to store value.
+ */
+
+ prop = g_new (SmProp, 1);
+ prop->name = (char *)name;
+ prop->type = SmCARD8;
+
+ prop->num_vals = 1;
+ prop->vals = g_new (SmPropValue, 2);
+ card8val = (char *)(&prop->vals[1]);
+ card8val[0] = value;
+
+ prop->vals[0].length = 1;
+ prop->vals[0].value = card8val;
+
+ return prop;
+}
+
+/* ICE code. This makes no effort to play nice with anyone else trying
+ * to use libICE. Fortunately, no one uses libICE for anything other
+ * than SM. (DCOP uses ICE, but it has its own private copy of
+ * libICE.)
+ *
+ * When this moves to gtk, it will need to be cleverer, to avoid
+ * tripping over old apps that use GnomeClient or that use libSM
+ * directly.
+ */
+
+#include <X11/ICE/ICElib.h>
+#include <fcntl.h>
+
+static void ice_error_handler (IceConn ice_conn,
+ Bool swap,
+ int offending_minor_opcode,
+ unsigned long offending_sequence,
+ int error_class,
+ int severity,
+ IcePointer values);
+static void ice_io_error_handler (IceConn ice_conn);
+static void ice_connection_watch (IceConn ice_conn,
+ IcePointer client_data,
+ Bool opening,
+ IcePointer *watch_data);
+
+static void
+ice_init (void)
+{
+ IceSetIOErrorHandler (ice_io_error_handler);
+ IceSetErrorHandler (ice_error_handler);
+ IceAddConnectionWatch (ice_connection_watch, NULL);
+}
+
+static gboolean
+process_ice_messages (IceConn ice_conn)
+{
+ IceProcessMessagesStatus status;
+
+ gdk_threads_enter ();
+ status = IceProcessMessages (ice_conn, NULL, NULL);
+ gdk_threads_leave ();
+
+ switch (status)
+ {
+ case IceProcessMessagesSuccess:
+ return TRUE;
+
+ case IceProcessMessagesIOError:
+ sm_client_xsmp_disconnect (IceGetConnectionContext (ice_conn));
+ return FALSE;
+
+ case IceProcessMessagesConnectionClosed:
+ return FALSE;
+
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static gboolean
+ice_iochannel_watch (GIOChannel *channel,
+ GIOCondition condition,
+ gpointer client_data)
+{
+ return process_ice_messages (client_data);
+}
+
+static void
+ice_connection_watch (IceConn ice_conn,
+ IcePointer client_data,
+ Bool opening,
+ IcePointer *watch_data)
+{
+ guint watch_id;
+
+ if (opening)
+ {
+ GIOChannel *channel;
+ int fd = IceConnectionNumber (ice_conn);
+
+ fcntl (fd, F_SETFD, fcntl (fd, F_GETFD, 0) | FD_CLOEXEC);
+ channel = g_io_channel_unix_new (fd);
+ watch_id = g_io_add_watch (channel, G_IO_IN | G_IO_ERR,
+ ice_iochannel_watch, ice_conn);
+ g_io_channel_unref (channel);
+
+ *watch_data = GUINT_TO_POINTER (watch_id);
+ }
+ else
+ {
+ watch_id = GPOINTER_TO_UINT (*watch_data);
+ g_source_remove (watch_id);
+ }
+}
+
+static void
+ice_error_handler (IceConn ice_conn,
+ Bool swap,
+ int offending_minor_opcode,
+ unsigned long offending_sequence,
+ int error_class,
+ int severity,
+ IcePointer values)
+{
+ /* Do nothing */
+}
+
+static void
+ice_io_error_handler (IceConn ice_conn)
+{
+ /* Do nothing */
+}
+
+static void
+smc_error_handler (SmcConn smc_conn,
+ Bool swap,
+ int offending_minor_opcode,
+ unsigned long offending_sequence,
+ int error_class,
+ int severity,
+ SmPointer values)
+{
+ /* Do nothing */
+}
diff --git a/copy-n-paste/eggsmclient.c b/copy-n-paste/eggsmclient.c
new file mode 100644
index 0000000..efa901d
--- /dev/null
+++ b/copy-n-paste/eggsmclient.c
@@ -0,0 +1,589 @@
+/*
+ * Copyright (C) 2007 Novell, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include "config.h"
+
+#include <string.h>
+#include <glib/gi18n.h>
+
+#include "eggsmclient.h"
+#include "eggsmclient-private.h"
+
+static void egg_sm_client_debug_handler (const char *log_domain,
+ GLogLevelFlags log_level,
+ const char *message,
+ gpointer user_data);
+
+enum {
+ SAVE_STATE,
+ QUIT_REQUESTED,
+ QUIT_CANCELLED,
+ QUIT,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+struct _EggSMClientPrivate {
+ GKeyFile *state_file;
+};
+
+#define EGG_SM_CLIENT_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), EGG_TYPE_SM_CLIENT, EggSMClientPrivate))
+
+G_DEFINE_TYPE (EggSMClient, egg_sm_client, G_TYPE_OBJECT)
+
+static EggSMClient *global_client;
+static EggSMClientMode global_client_mode = EGG_SM_CLIENT_MODE_NORMAL;
+
+static void
+egg_sm_client_init (EggSMClient *client)
+{
+ ;
+}
+
+static void
+egg_sm_client_class_init (EggSMClientClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ g_type_class_add_private (klass, sizeof (EggSMClientPrivate));
+
+ /**
+ * EggSMClient::save_state:
+ * @client: the client
+ * @state_file: a #GKeyFile to save state information into
+ *
+ * Emitted when the session manager has requested that the
+ * application save information about its current state. The
+ * application should save its state into @state_file, and then the
+ * session manager may then restart the application in a future
+ * session and tell it to initialize itself from that state.
+ *
+ * You should not save any data into @state_file's "start group"
+ * (ie, the %NULL group). Instead, applications should save their
+ * data into groups with names that start with the application name,
+ * and libraries that connect to this signal should save their data
+ * into groups with names that start with the library name.
+ *
+ * Alternatively, rather than (or in addition to) using @state_file,
+ * the application can save its state by calling
+ * egg_sm_client_set_restart_command() during the processing of this
+ * signal (eg, to include a list of files to open).
+ **/
+ signals[SAVE_STATE] =
+ g_signal_new ("save_state",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (EggSMClientClass, save_state),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__POINTER,
+ G_TYPE_NONE,
+ 1, G_TYPE_POINTER);
+
+ /**
+ * EggSMClient::quit_requested:
+ * @client: the client
+ *
+ * Emitted when the session manager requests that the application
+ * exit (generally because the user is logging out). The application
+ * should decide whether or not it is willing to quit (perhaps after
+ * asking the user what to do with documents that have unsaved
+ * changes) and then call egg_sm_client_will_quit(), passing %TRUE
+ * or %FALSE to give its answer to the session manager. (It does not
+ * need to give an answer before returning from the signal handler;
+ * it can interact with the user asynchronously and then give its
+ * answer later on.) If the application does not connect to this
+ * signal, then #EggSMClient will automatically return %TRUE on its
+ * behalf.
+ *
+ * The application should not save its session state as part of
+ * handling this signal; if the user has requested that the session
+ * be saved when logging out, then ::save_state will be emitted
+ * separately.
+ *
+ * If the application agrees to quit, it should then wait for either
+ * the ::quit_cancelled or ::quit signals to be emitted.
+ **/
+ signals[QUIT_REQUESTED] =
+ g_signal_new ("quit_requested",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (EggSMClientClass, quit_requested),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+
+ /**
+ * EggSMClient::quit_cancelled:
+ * @client: the client
+ *
+ * Emitted when the session manager decides to cancel a logout after
+ * the application has already agreed to quit. After receiving this
+ * signal, the application can go back to what it was doing before
+ * receiving the ::quit_requested signal.
+ **/
+ signals[QUIT_CANCELLED] =
+ g_signal_new ("quit_cancelled",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (EggSMClientClass, quit_cancelled),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+
+ /**
+ * EggSMClient::quit:
+ * @client: the client
+ *
+ * Emitted when the session manager wants the application to quit
+ * (generally because the user is logging out). The application
+ * should exit as soon as possible after receiving this signal; if
+ * it does not, the session manager may choose to forcibly kill it.
+ *
+ * Normally a GUI application would only be sent a ::quit if it
+ * agreed to quit in response to a ::quit_requested signal. However,
+ * this is not guaranteed; in some situations the session manager
+ * may decide to end the session without giving applications a
+ * chance to object.
+ **/
+ signals[QUIT] =
+ g_signal_new ("quit",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (EggSMClientClass, quit),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+}
+
+static gboolean sm_client_disable = FALSE;
+static char *sm_client_state_file = NULL;
+static char *sm_client_id = NULL;
+static char *sm_config_prefix = NULL;
+
+static gboolean
+sm_client_post_parse_func (GOptionContext *context,
+ GOptionGroup *group,
+ gpointer data,
+ GError **error)
+{
+ EggSMClient *client = egg_sm_client_get ();
+
+ if (sm_client_id == NULL)
+ {
+ const gchar *desktop_autostart_id;
+
+ desktop_autostart_id = g_getenv ("DESKTOP_AUTOSTART_ID");
+
+ if (desktop_autostart_id != NULL)
+ sm_client_id = g_strdup (desktop_autostart_id);
+ }
+
+ /* Unset DESKTOP_AUTOSTART_ID in order to avoid child processes to
+ * use the same client id. */
+ g_unsetenv ("DESKTOP_AUTOSTART_ID");
+
+ if (EGG_SM_CLIENT_GET_CLASS (client)->startup)
+ EGG_SM_CLIENT_GET_CLASS (client)->startup (client, sm_client_id);
+ return TRUE;
+}
+
+/**
+ * egg_sm_client_get_option_group:
+ *
+ * Creates a %GOptionGroup containing the session-management-related
+ * options. You should add this group to the application's
+ * %GOptionContext if you want to use #EggSMClient.
+ *
+ * Return value: the %GOptionGroup
+ **/
+GOptionGroup *
+egg_sm_client_get_option_group (void)
+{
+ const GOptionEntry entries[] = {
+ { "sm-client-disable", 0, 0,
+ G_OPTION_ARG_NONE, &sm_client_disable,
+ N_("Disable connection to session manager"), NULL },
+ { "sm-client-state-file", 0, 0,
+ G_OPTION_ARG_FILENAME, &sm_client_state_file,
+ N_("Specify file containing saved configuration"), N_("FILE") },
+ { "sm-client-id", 0, 0,
+ G_OPTION_ARG_STRING, &sm_client_id,
+ N_("Specify session management ID"), N_("ID") },
+ /* GnomeClient compatibility option */
+ { "sm-disable", 0, G_OPTION_FLAG_HIDDEN,
+ G_OPTION_ARG_NONE, &sm_client_disable,
+ NULL, NULL },
+ /* GnomeClient compatibility option. This is a dummy option that only
+ * exists so that sessions saved by apps with GnomeClient can be restored
+ * later when they've switched to EggSMClient. See bug #575308.
+ */
+ { "sm-config-prefix", 0, G_OPTION_FLAG_HIDDEN,
+ G_OPTION_ARG_STRING, &sm_config_prefix,
+ NULL, NULL },
+ { NULL }
+ };
+ GOptionGroup *group;
+
+ /* Use our own debug handler for the "EggSMClient" domain. */
+ g_log_set_handler (G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG,
+ egg_sm_client_debug_handler, NULL);
+
+ group = g_option_group_new ("sm-client",
+ _("Session management options:"),
+ _("Show session management options"),
+ NULL, NULL);
+ g_option_group_add_entries (group, entries);
+ g_option_group_set_parse_hooks (group, NULL, sm_client_post_parse_func);
+
+ return group;
+}
+
+/**
+ * egg_sm_client_set_mode:
+ * @mode: an #EggSMClient mode
+ *
+ * Sets the "mode" of #EggSMClient as follows:
+ *
+ * %EGG_SM_CLIENT_MODE_DISABLED: Session management is completely
+ * disabled. The application will not even connect to the session
+ * manager. (egg_sm_client_get() will still return an #EggSMClient,
+ * but it will just be a dummy object.)
+ *
+ * %EGG_SM_CLIENT_MODE_NO_RESTART: The application will connect to
+ * the session manager (and thus will receive notification when the
+ * user is logging out, etc), but will request to not be
+ * automatically restarted with saved state in future sessions.
+ *
+ * %EGG_SM_CLIENT_MODE_NORMAL: The default. #EggSMCLient will
+ * function normally.
+ *
+ * This must be called before the application's main loop begins.
+ **/
+void
+egg_sm_client_set_mode (EggSMClientMode mode)
+{
+ global_client_mode = mode;
+}
+
+/**
+ * egg_sm_client_get_mode:
+ *
+ * Gets the global #EggSMClientMode. See egg_sm_client_set_mode()
+ * for details.
+ *
+ * Return value: the global #EggSMClientMode
+ **/
+EggSMClientMode
+egg_sm_client_get_mode (void)
+{
+ return global_client_mode;
+}
+
+/**
+ * egg_sm_client_get:
+ *
+ * Returns the master #EggSMClient for the application.
+ *
+ * On platforms that support saved sessions (ie, POSIX/X11), the
+ * application will only request to be restarted by the session
+ * manager if you call egg_set_desktop_file() to set an application
+ * desktop file. In particular, if the desktop file contains the key
+ * "X
+ *
+ * Return value: the master #EggSMClient.
+ **/
+EggSMClient *
+egg_sm_client_get (void)
+{
+ if (!global_client)
+ {
+ if (global_client_mode != EGG_SM_CLIENT_MODE_DISABLED &&
+ !sm_client_disable)
+ {
+#if defined (GDK_WINDOWING_WIN32)
+ global_client = egg_sm_client_win32_new ();
+#elif defined (GDK_WINDOWING_QUARTZ)
+ global_client = egg_sm_client_osx_new ();
+#else
+ /* If both D-Bus and XSMP are compiled in, try XSMP first
+ * (since it supports state saving) and fall back to D-Bus
+ * if XSMP isn't available.
+ */
+# ifdef EGG_SM_CLIENT_BACKEND_XSMP
+ global_client = egg_sm_client_xsmp_new ();
+# endif
+# ifdef EGG_SM_CLIENT_BACKEND_DBUS
+ if (!global_client)
+ global_client = egg_sm_client_dbus_new ();
+# endif
+#endif
+ }
+
+ /* Fallback: create a dummy client, so that callers don't have
+ * to worry about a %NULL return value.
+ */
+ if (!global_client)
+ global_client = g_object_new (EGG_TYPE_SM_CLIENT, NULL);
+ }
+
+ return global_client;
+}
+
+/**
+ * egg_sm_client_is_resumed:
+ * @client: the client
+ *
+ * Checks whether or not the current session has been resumed from
+ * a previous saved session. If so, the application should call
+ * egg_sm_client_get_state_file() and restore its state from the
+ * returned #GKeyFile.
+ *
+ * Return value: %TRUE if the session has been resumed
+ **/
+gboolean
+egg_sm_client_is_resumed (EggSMClient *client)
+{
+ g_return_val_if_fail (client == global_client, FALSE);
+
+ return sm_client_state_file != NULL;
+}
+
+/**
+ * egg_sm_client_get_state_file:
+ * @client: the client
+ *
+ * If the application was resumed by the session manager, this will
+ * return the #GKeyFile containing its state from the previous
+ * session.
+ *
+ * Note that other libraries and #EggSMClient itself may also store
+ * state in the key file, so if you call egg_sm_client_get_groups(),
+ * on it, the return value will likely include groups that you did not
+ * put there yourself. (It is also not guaranteed that the first
+ * group created by the application will still be the "start group"
+ * when it is resumed.)
+ *
+ * Return value: the #GKeyFile containing the application's earlier
+ * state, or %NULL on error. You should not free this key file; it
+ * is owned by @client.
+ **/
+GKeyFile *
+egg_sm_client_get_state_file (EggSMClient *client)
+{
+ EggSMClientPrivate *priv = EGG_SM_CLIENT_GET_PRIVATE (client);
+ char *state_file_path;
+ GError *err = NULL;
+
+ g_return_val_if_fail (client == global_client, NULL);
+
+ if (!sm_client_state_file)
+ return NULL;
+ if (priv->state_file)
+ return priv->state_file;
+
+ if (!strncmp (sm_client_state_file, "file://", 7))
+ state_file_path = g_filename_from_uri (sm_client_state_file, NULL, NULL);
+ else
+ state_file_path = g_strdup (sm_client_state_file);
+
+ priv->state_file = g_key_file_new ();
+ if (!g_key_file_load_from_file (priv->state_file, state_file_path, 0, &err))
+ {
+ g_warning ("Could not load SM state file '%s': %s",
+ sm_client_state_file, err->message);
+ g_clear_error (&err);
+ g_key_file_free (priv->state_file);
+ priv->state_file = NULL;
+ }
+
+ g_free (state_file_path);
+ return priv->state_file;
+}
+
+/**
+ * egg_sm_client_set_restart_command:
+ * @client: the client
+ * @argc: the length of @argv
+ * @argv: argument vector
+ *
+ * Sets the command used to restart @client if it does not have a
+ * .desktop file that can be used to find its restart command.
+ *
+ * This can also be used when handling the ::save_state signal, to
+ * save the current state via an updated command line. (Eg, providing
+ * a list of filenames to open when the application is resumed.)
+ **/
+void
+egg_sm_client_set_restart_command (EggSMClient *client,
+ int argc,
+ const char **argv)
+{
+ g_return_if_fail (EGG_IS_SM_CLIENT (client));
+
+ if (EGG_SM_CLIENT_GET_CLASS (client)->set_restart_command)
+ EGG_SM_CLIENT_GET_CLASS (client)->set_restart_command (client, argc, argv);
+}
+
+/**
+ * egg_sm_client_will_quit:
+ * @client: the client
+ * @will_quit: whether or not the application is willing to quit
+ *
+ * This MUST be called in response to the ::quit_requested signal, to
+ * indicate whether or not the application is willing to quit. The
+ * application may call it either directly from the signal handler, or
+ * at some later point (eg, after asynchronously interacting with the
+ * user).
+ *
+ * If the application does not connect to ::quit_requested,
+ * #EggSMClient will call this method on its behalf (passing %TRUE
+ * for @will_quit).
+ *
+ * After calling this method, the application should wait to receive
+ * either ::quit_cancelled or ::quit.
+ **/
+void
+egg_sm_client_will_quit (EggSMClient *client,
+ gboolean will_quit)
+{
+ g_return_if_fail (EGG_IS_SM_CLIENT (client));
+
+ if (EGG_SM_CLIENT_GET_CLASS (client)->will_quit)
+ EGG_SM_CLIENT_GET_CLASS (client)->will_quit (client, will_quit);
+}
+
+/**
+ * egg_sm_client_end_session:
+ * @style: a hint at how to end the session
+ * @request_confirmation: whether or not the user should get a chance
+ * to confirm the action
+ *
+ * Requests that the session manager end the current session. @style
+ * indicates how the session should be ended, and
+ * @request_confirmation indicates whether or not the user should be
+ * given a chance to confirm the logout/reboot/shutdown. Both of these
+ * flags are merely hints though; the session manager may choose to
+ * ignore them.
+ *
+ * Return value: %TRUE if the request was sent; %FALSE if it could not
+ * be (eg, because it could not connect to the session manager).
+ **/
+gboolean
+egg_sm_client_end_session (EggSMClientEndStyle style,
+ gboolean request_confirmation)
+{
+ EggSMClient *client = egg_sm_client_get ();
+
+ g_return_val_if_fail (EGG_IS_SM_CLIENT (client), FALSE);
+
+ if (EGG_SM_CLIENT_GET_CLASS (client)->end_session)
+ {
+ return EGG_SM_CLIENT_GET_CLASS (client)->end_session (client, style,
+ request_confirmation);
+ }
+ else
+ return FALSE;
+}
+
+/* Signal-emitting callbacks from platform-specific code */
+
+GKeyFile *
+egg_sm_client_save_state (EggSMClient *client)
+{
+ GKeyFile *state_file;
+ char *group;
+
+ g_return_val_if_fail (client == global_client, NULL);
+
+ state_file = g_key_file_new ();
+
+ g_debug ("Emitting save_state");
+ g_signal_emit (client, signals[SAVE_STATE], 0, state_file);
+ g_debug ("Done emitting save_state");
+
+ group = g_key_file_get_start_group (state_file);
+ if (group)
+ {
+ g_free (group);
+ return state_file;
+ }
+ else
+ {
+ g_key_file_free (state_file);
+ return NULL;
+ }
+}
+
+void
+egg_sm_client_quit_requested (EggSMClient *client)
+{
+ g_return_if_fail (client == global_client);
+
+ if (!g_signal_has_handler_pending (client, signals[QUIT_REQUESTED], 0, FALSE))
+ {
+ g_debug ("Not emitting quit_requested because no one is listening");
+ egg_sm_client_will_quit (client, TRUE);
+ return;
+ }
+
+ g_debug ("Emitting quit_requested");
+ g_signal_emit (client, signals[QUIT_REQUESTED], 0);
+ g_debug ("Done emitting quit_requested");
+}
+
+void
+egg_sm_client_quit_cancelled (EggSMClient *client)
+{
+ g_return_if_fail (client == global_client);
+
+ g_debug ("Emitting quit_cancelled");
+ g_signal_emit (client, signals[QUIT_CANCELLED], 0);
+ g_debug ("Done emitting quit_cancelled");
+}
+
+void
+egg_sm_client_quit (EggSMClient *client)
+{
+ g_return_if_fail (client == global_client);
+
+ g_debug ("Emitting quit");
+ g_signal_emit (client, signals[QUIT], 0);
+ g_debug ("Done emitting quit");
+
+ /* FIXME: should we just call gtk_main_quit() here? */
+}
+
+static void
+egg_sm_client_debug_handler (const char *log_domain,
+ GLogLevelFlags log_level,
+ const char *message,
+ gpointer user_data)
+{
+ static int debug = -1;
+
+ if (debug < 0)
+ debug = (g_getenv ("EGG_SM_CLIENT_DEBUG") != NULL);
+
+ if (debug)
+ g_log_default_handler (log_domain, log_level, message, NULL);
+}
diff --git a/copy-n-paste/eggsmclient.h b/copy-n-paste/eggsmclient.h
new file mode 100644
index 0000000..e620b75
--- /dev/null
+++ b/copy-n-paste/eggsmclient.h
@@ -0,0 +1,117 @@
+/* eggsmclient.h
+ * Copyright (C) 2007 Novell, Inc.
+ *
+ * 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.
+ */
+
+#ifndef __EGG_SM_CLIENT_H__
+#define __EGG_SM_CLIENT_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define EGG_TYPE_SM_CLIENT (egg_sm_client_get_type ())
+#define EGG_SM_CLIENT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EGG_TYPE_SM_CLIENT, EggSMClient))
+#define EGG_SM_CLIENT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), EGG_TYPE_SM_CLIENT, EggSMClientClass))
+#define EGG_IS_SM_CLIENT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EGG_TYPE_SM_CLIENT))
+#define EGG_IS_SM_CLIENT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EGG_TYPE_SM_CLIENT))
+#define EGG_SM_CLIENT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), EGG_TYPE_SM_CLIENT, EggSMClientClass))
+
+typedef struct _EggSMClient EggSMClient;
+typedef struct _EggSMClientClass EggSMClientClass;
+typedef struct _EggSMClientPrivate EggSMClientPrivate;
+
+typedef enum {
+ EGG_SM_CLIENT_END_SESSION_DEFAULT,
+ EGG_SM_CLIENT_LOGOUT,
+ EGG_SM_CLIENT_REBOOT,
+ EGG_SM_CLIENT_SHUTDOWN
+} EggSMClientEndStyle;
+
+typedef enum {
+ EGG_SM_CLIENT_MODE_DISABLED,
+ EGG_SM_CLIENT_MODE_NO_RESTART,
+ EGG_SM_CLIENT_MODE_NORMAL
+} EggSMClientMode;
+
+struct _EggSMClient
+{
+ GObject parent;
+
+};
+
+struct _EggSMClientClass
+{
+ GObjectClass parent_class;
+
+ /* signals */
+ void (*save_state) (EggSMClient *client,
+ GKeyFile *state_file);
+
+ void (*quit_requested) (EggSMClient *client);
+ void (*quit_cancelled) (EggSMClient *client);
+ void (*quit) (EggSMClient *client);
+
+ /* virtual methods */
+ void (*startup) (EggSMClient *client,
+ const char *client_id);
+ void (*set_restart_command) (EggSMClient *client,
+ int argc,
+ const char **argv);
+ void (*will_quit) (EggSMClient *client,
+ gboolean will_quit);
+ gboolean (*end_session) (EggSMClient *client,
+ EggSMClientEndStyle style,
+ gboolean request_confirmation);
+
+ /* Padding for future expansion */
+ void (*_egg_reserved1) (void);
+ void (*_egg_reserved2) (void);
+ void (*_egg_reserved3) (void);
+ void (*_egg_reserved4) (void);
+};
+
+GType egg_sm_client_get_type (void) G_GNUC_CONST;
+
+GOptionGroup *egg_sm_client_get_option_group (void);
+
+/* Initialization */
+void egg_sm_client_set_mode (EggSMClientMode mode);
+EggSMClientMode egg_sm_client_get_mode (void);
+EggSMClient *egg_sm_client_get (void);
+
+/* Resuming a saved session */
+gboolean egg_sm_client_is_resumed (EggSMClient *client);
+GKeyFile *egg_sm_client_get_state_file (EggSMClient *client);
+
+/* Alternate means of saving state */
+void egg_sm_client_set_restart_command (EggSMClient *client,
+ int argc,
+ const char **argv);
+
+/* Handling "quit_requested" signal */
+void egg_sm_client_will_quit (EggSMClient *client,
+ gboolean will_quit);
+
+/* Initiate a logout/reboot/shutdown */
+gboolean egg_sm_client_end_session (EggSMClientEndStyle style,
+ gboolean request_confirmation);
+
+G_END_DECLS
+
+
+#endif /* __EGG_SM_CLIENT_H__ */
diff --git a/data/Makefile.am b/data/Makefile.am
index 2c51277..783b469 100644
--- a/data/Makefile.am
+++ b/data/Makefile.am
@@ -1,12 +1,4 @@
-SUBDIRS = albumthemes glade icons
-
- INTLTOOL_DESKTOP_RULE@
-
-desktopdir = $(datadir)/applications
-desktop_in_files=gthumb.desktop.in gthumb-import.desktop.in
-desktop_DATA = $(desktop_in_files:.desktop.in=.desktop)
-
-man_MANS = gthumb.1
+SUBDIRS = icons ui
schemadir = @GCONF_SCHEMA_FILE_DIR@
schema_in_files = gthumb.schemas.in
@@ -14,9 +6,6 @@ schema_DATA = $(schema_in_files:.schemas.in=.schemas)
@INTLTOOL_SCHEMAS_RULE@
-serverdir = $(libdir)/bonobo/servers
-server_DATA = GNOME_GThumb.server
-
if GCONF_SCHEMAS_INSTALL
install-data-local:
GCONF_CONFIG_SOURCE=$(GCONF_SCHEMA_CONFIG_SOURCE) $(GCONFTOOL) --makefile-install-rule $(top_builddir)/data/$(schema_DATA)
@@ -24,14 +13,8 @@ endif
EXTRA_DIST = \
gthumb.schemas \
- gthumb.schemas.in \
- $(desktop_in_files) \
- $(desktop_DATA) \
- $(man_MANS) \
- $(server_DATA)
-
-DISTCLEANFILES = \
- $(schema_DATA) \
- $(desktop_DATA)
+ gthumb.schemas.in
+DISTCLEANFILES = \
+ $(schema_DATA)
-include $(top_srcdir)/git.mk
diff --git a/data/gthumb.schemas.in b/data/gthumb.schemas.in
index eed9293..0acd2bc 100644
--- a/data/gthumb.schemas.in
+++ b/data/gthumb.schemas.in
@@ -98,24 +98,11 @@
<!-- Browser -->
<schema>
- <key>/schemas/apps/gthumb/browser/show_hidden_files</key>
- <applyto>/apps/gthumb/browser/show_hidden_files</applyto>
- <owner>gthumb</owner>
- <type>bool</type>
- <default>false</default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/browser/show_only_images</key>
- <applyto>/apps/gthumb/browser/show_only_images</applyto>
+ <key>/schemas/apps/gthumb/browser/general_filter</key>
+ <applyto>/apps/gthumb/browser/general_filter</applyto>
<owner>gthumb</owner>
- <type>bool</type>
- <default>false</default>
+ <type>string</type>
+ <default>file::type::is_media</default>
<locale name="C">
<short></short>
<long>
@@ -124,8 +111,8 @@
</schema>
<schema>
- <key>/schemas/apps/gthumb/browser/show_filenames</key>
- <applyto>/apps/gthumb/browser/show_filenames</applyto>
+ <key>/schemas/apps/gthumb/browser/show_hidden_files</key>
+ <applyto>/apps/gthumb/browser/show_hidden_files</applyto>
<owner>gthumb</owner>
<type>bool</type>
<default>false</default>
@@ -137,32 +124,6 @@
</schema>
<schema>
- <key>/schemas/apps/gthumb/browser/show_comments</key>
- <applyto>/apps/gthumb/browser/show_comments</applyto>
- <owner>gthumb</owner>
- <type>bool</type>
- <default>true</default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/browser/show_tags</key>
- <applyto>/apps/gthumb/browser/show_tags</applyto>
- <owner>gthumb</owner>
- <type>bool</type>
- <default>true</default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <schema>
<key>/schemas/apps/gthumb/browser/show_thumbnails</key>
<applyto>/apps/gthumb/browser/show_thumbnails</applyto>
<owner>gthumb</owner>
@@ -206,7 +167,7 @@
<applyto>/apps/gthumb/browser/thumbnail_size</applyto>
<owner>gthumb</owner>
<type>int</type>
- <default>95</default>
+ <default>128</default>
<locale name="C">
<short></short>
<long>
@@ -229,19 +190,6 @@
</schema>
<schema>
- <key>/schemas/apps/gthumb/browser/click_policy</key>
- <applyto>/apps/gthumb/browser/click_policy</applyto>
- <owner>gthumb</owner>
- <type>string</type>
- <default>nautilus</default>
- <locale name="C">
- <short></short>
- <long> Possible values are: nautilus, single, double.
- </long>
- </locale>
- </schema>
-
- <schema>
<key>/schemas/apps/gthumb/browser/confirm_deletion</key>
<applyto>/apps/gthumb/browser/confirm_deletion</applyto>
<owner>gthumb</owner>
@@ -255,28 +203,26 @@
</schema>
<schema>
- <key>/schemas/apps/gthumb/browser/arrange_images</key>
- <applyto>/apps/gthumb/browser/arrange_images</applyto>
+ <key>/schemas/apps/gthumb/browser/sort_type</key>
+ <applyto>/apps/gthumb/browser/sort_type</applyto>
<owner>gthumb</owner>
<type>string</type>
<default>name</default>
<locale name="C">
<short></short>
- <long> Possible values are: none, name, path, size, time, exifdate, comment.
- </long>
+ <long></long>
</locale>
</schema>
<schema>
- <key>/schemas/apps/gthumb/browser/sort_images</key>
- <applyto>/apps/gthumb/browser/sort_images</applyto>
+ <key>/schemas/apps/gthumb/browser/sort_inverse</key>
+ <applyto>/apps/gthumb/browser/sort_inverse</applyto>
<owner>gthumb</owner>
- <type>string</type>
- <default>ascending</default>
+ <type>bool</type>
+ <default>false</default>
<locale name="C">
<short></short>
- <long> Possible values are: ascending, descending.
- </long>
+ <long></long>
</locale>
</schema>
@@ -293,45 +239,6 @@
</locale>
</schema>
- <schema>
- <key>/schemas/apps/gthumb/browser/show_preview</key>
- <applyto>/apps/gthumb/browser/show_preview</applyto>
- <owner>gthumb</owner>
- <type>bool</type>
- <default>false</default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/browser/preview_content</key>
- <applyto>/apps/gthumb/browser/preview_content</applyto>
- <owner>gthumb</owner>
- <type>string</type>
- <default>data</default>
- <locale name="C">
- <short></short>
- <long> Possible values are: image, data, comment.
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/browser/show_image_data</key>
- <applyto>/apps/gthumb/browser/show_image_data</applyto>
- <owner>gthumb</owner>
- <type>bool</type>
- <default>false</default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
<!-- Viewer -->
<schema>
@@ -375,16 +282,16 @@
</schema>
<schema>
- <key>/schemas/apps/gthumb/viewer/reset_scrollbars</key>
- <applyto>/apps/gthumb/viewer/reset_scrollbars</applyto>
- <owner>gthumb</owner>
- <type>bool</type>
- <default>true</default>
- <locale name="C">
- <short></short>
- <long> Whether to reset the scrollbar positions after changing image
- </long>
- </locale>
+ <key>/schemas/apps/gthumb/viewer/reset_scrollbars</key>
+ <applyto>/apps/gthumb/viewer/reset_scrollbars</applyto>
+ <owner>gthumb</owner>
+ <type>bool</type>
+ <default>true</default>
+ <locale name="C">
+ <short></short>
+ <long> Whether to reset the scrollbar positions after changing image
+ </long>
+ </locale>
</schema>
<schema>
@@ -426,206 +333,9 @@
</locale>
</schema>
- <schema>
- <key>/schemas/apps/gthumb/viewer/single_window</key>
- <applyto>/apps/gthumb/viewer/single_window</applyto>
- <owner>gthumb</owner>
- <type>bool</type>
- <default>false</default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <!-- Slide Show -->
-
- <schema>
- <key>/schemas/apps/gthumb/slideshow/direction</key>
- <applyto>/apps/gthumb/slideshow/direction</applyto>
- <owner>gthumb</owner>
- <type>string</type>
- <default>forward</default>
- <locale name="C">
- <short></short>
- <long> Possible values are: forward, backward, random.
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/slideshow/change_delay</key>
- <applyto>/apps/gthumb/slideshow/change_delay</applyto>
- <owner>gthumb</owner>
- <type>float</type>
- <default>4.0</default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/slideshow/wrap_around</key>
- <applyto>/apps/gthumb/slideshow/wrap_around</applyto>
- <owner>gthumb</owner>
- <type>bool</type>
- <default>false</default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/hotkeys/hotkey0</key>
- <applyto>/apps/gthumb/hotkeys/hotkey0</applyto>
- <owner>gthumb</owner>
- <type>string</type>
- <default></default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/hotkeys/hotkey1</key>
- <applyto>/apps/gthumb/hotkeys/hotkey1</applyto>
- <owner>gthumb</owner>
- <type>string</type>
- <default></default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/hotkeys/hotkey2</key>
- <applyto>/apps/gthumb/hotkeys/hotkey2</applyto>
- <owner>gthumb</owner>
- <type>string</type>
- <default></default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/hotkeys/hotkey3</key>
- <applyto>/apps/gthumb/hotkeys/hotkey3</applyto>
- <owner>gthumb</owner>
- <type>string</type>
- <default></default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/hotkeys/hotkey4</key>
- <applyto>/apps/gthumb/hotkeys/hotkey4</applyto>
- <owner>gthumb</owner>
- <type>string</type>
- <default></default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/hotkeys/hotkey5</key>
- <applyto>/apps/gthumb/hotkeys/hotkey5</applyto>
- <owner>gthumb</owner>
- <type>string</type>
- <default></default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/hotkeys/hotkey6</key>
- <applyto>/apps/gthumb/hotkeys/hotkey6</applyto>
- <owner>gthumb</owner>
- <type>string</type>
- <default></default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/hotkeys/hotkey7</key>
- <applyto>/apps/gthumb/hotkeys/hotkey7</applyto>
- <owner>gthumb</owner>
- <type>string</type>
- <default></default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/hotkeys/hotkey8</key>
- <applyto>/apps/gthumb/hotkeys/hotkey8</applyto>
- <owner>gthumb</owner>
- <type>string</type>
- <default></default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/hotkeys/hotkey9</key>
- <applyto>/apps/gthumb/hotkeys/hotkey9</applyto>
- <owner>gthumb</owner>
- <type>string</type>
- <default></default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
<!-- UI -->
<schema>
- <key>/schemas/apps/gthumb/ui/layout</key>
- <applyto>/apps/gthumb/ui/layout</applyto>
- <owner>gthumb</owner>
- <type>int</type>
- <default>2</default>
- <locale name="C">
- <short></short>
- <long> Possible values are: 1, 2, 3, 4.
- </long>
- </locale>
- </schema>
-
- <schema>
<key>/schemas/apps/gthumb/ui/toolbar_style</key>
<applyto>/apps/gthumb/ui/toolbar_style</applyto>
<owner>gthumb</owner>
@@ -665,32 +375,6 @@
</schema>
<schema>
- <key>/schemas/apps/gthumb/ui/viewer_width</key>
- <applyto>/apps/gthumb/ui/viewer_width</applyto>
- <owner>gthumb</owner>
- <type>int</type>
- <default>690</default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/ui/viewer_height</key>
- <applyto>/apps/gthumb/ui/viewer_height</applyto>
- <owner>gthumb</owner>
- <type>int</type>
- <default>460</default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <schema>
<key>/schemas/apps/gthumb/ui/image_pane_visible</key>
<applyto>/apps/gthumb/ui/image_pane_visible</applyto>
<owner>gthumb</owner>
@@ -743,238 +427,11 @@
</schema>
<schema>
- <key>/schemas/apps/gthumb/ui/sidebar_size</key>
- <applyto>/apps/gthumb/ui/sidebar_size</applyto>
- <owner>gthumb</owner>
- <type>int</type>
- <default>255</default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/ui/sidebar_content_size</key>
- <applyto>/apps/gthumb/ui/sidebar_content_size</applyto>
- <owner>gthumb</owner>
- <type>int</type>
- <default>190</default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/ui/comment_pane_size</key>
- <applyto>/apps/gthumb/ui/comment_pane_size</applyto>
- <owner>gthumb</owner>
- <type>int</type>
- <default>190</default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <!-- Exporter/Web Album -->
-
- <schema>
- <key>/schemas/apps/gthumb/exporter/web_album/index_file</key>
- <applyto>/apps/gthumb/exporter/web_album/index_file</applyto>
- <owner>gthumb</owner>
- <type>string</type>
- <default>index.html</default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/exporter/web_album/directories/previews</key>
- <applyto>/apps/gthumb/exporter/web_album/directories/previews</applyto>
- <owner>gthumb</owner>
- <type>string</type>
- <default>previews</default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/exporter/web_album/directories/thumbnails</key>
- <applyto>/apps/gthumb/exporter/web_album/directories/thumbnails</applyto>
- <owner>gthumb</owner>
- <type>string</type>
- <default>thumbnails</default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/exporter/web_album/directories/images</key>
- <applyto>/apps/gthumb/exporter/web_album/directories/images</applyto>
- <owner>gthumb</owner>
- <type>string</type>
- <default>images</default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/exporter/web_album/directories/html_images</key>
- <applyto>/apps/gthumb/exporter/web_album/directories/html_images</applyto>
- <owner>gthumb</owner>
- <type>string</type>
- <default>html</default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/exporter/web_album/directories/html_indexes</key>
- <applyto>/apps/gthumb/exporter/web_album/directories/html_indexes</applyto>
- <owner>gthumb</owner>
- <type>string</type>
- <default>html</default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/exporter/web_album/directories/theme_files</key>
- <applyto>/apps/gthumb/exporter/web_album/directories/theme_files</applyto>
- <owner>gthumb</owner>
- <type>string</type>
- <default>theme</default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <!-- Exporter/General -->
-
- <schema>
- <key>/schemas/apps/gthumb/exporter/general/name_template</key>
- <applyto>/apps/gthumb/exporter/general/name_template</applyto>
- <owner>gthumb</owner>
- <type>string</type>
- <default>###</default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/exporter/general/start_from</key>
- <applyto>/apps/gthumb/exporter/general/start_from</applyto>
- <owner>gthumb</owner>
- <type>int</type>
- <default>1</default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/exporter/general/file_type</key>
- <applyto>/apps/gthumb/exporter/general/file_type</applyto>
- <owner>gthumb</owner>
- <type>string</type>
- <default>jpeg</default>
- <locale name="C">
- <short></short>
- <long> Possible values are: png, jpeg.
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/exporter/general/write_image_map</key>
- <applyto>/apps/gthumb/exporter/general/write_image_map</applyto>
- <owner>gthumb</owner>
- <type>bool</type>
- <default>false</default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <!-- Exporter/Page -->
-
- <schema>
- <key>/schemas/apps/gthumb/exporter/page/width</key>
- <applyto>/apps/gthumb/exporter/page/width</applyto>
- <owner>gthumb</owner>
- <type>int</type>
- <default>400</default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/exporter/page/height</key>
- <applyto>/apps/gthumb/exporter/page/height</applyto>
- <owner>gthumb</owner>
- <type>int</type>
- <default>400</default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/exporter/page/size_use_row_col</key>
- <applyto>/apps/gthumb/exporter/page/size_use_row_col</applyto>
- <owner>gthumb</owner>
- <type>bool</type>
- <default>true</default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/exporter/page/rows</key>
- <applyto>/apps/gthumb/exporter/page/rows</applyto>
+ <key>/schemas/apps/gthumb/ui/sidebar_width</key>
+ <applyto>/apps/gthumb/ui/sidebar_width</applyto>
<owner>gthumb</owner>
<type>int</type>
- <default>3</default>
+ <default>250</default>
<locale name="C">
<short></short>
<long>
@@ -983,128 +440,11 @@
</schema>
<schema>
- <key>/schemas/apps/gthumb/exporter/page/cols</key>
- <applyto>/apps/gthumb/exporter/page/cols</applyto>
+ <key>/schemas/apps/gthumb/ui/properties_height</key>
+ <applyto>/apps/gthumb/ui/properties_height</applyto>
<owner>gthumb</owner>
<type>int</type>
- <default>4</default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/exporter/page/all_pages_same_size</key>
- <applyto>/apps/gthumb/exporter/page/all_pages_same_size</applyto>
- <owner>gthumb</owner>
- <type>bool</type>
- <default>true</default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/exporter/page/background_color</key>
- <applyto>/apps/gthumb/exporter/page/background_color</applyto>
- <owner>gthumb</owner>
- <type>string</type>
- <default>#62757b</default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/exporter/page/hgrad_color1</key>
- <applyto>/apps/gthumb/exporter/page/hgrad_color1</applyto>
- <owner>gthumb</owner>
- <type>string</type>
- <default>#e0d3c0</default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/exporter/page/hgrad_color2</key>
- <applyto>/apps/gthumb/exporter/page/hgrad_color2</applyto>
- <owner>gthumb</owner>
- <type>string</type>
- <default>#b1c3ad</default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/exporter/page/vgrad_color1</key>
- <applyto>/apps/gthumb/exporter/page/vgrad_color1</applyto>
- <owner>gthumb</owner>
- <type>string</type>
- <default>#e8e8ea</default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/exporter/page/vgrad_color2</key>
- <applyto>/apps/gthumb/exporter/page/vgrad_color2</applyto>
- <owner>gthumb</owner>
- <type>string</type>
- <default>#bad8d8</default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/exporter/page/use_solid_color</key>
- <applyto>/apps/gthumb/exporter/page/use_solid_color</applyto>
- <owner>gthumb</owner>
- <type>bool</type>
- <default>false</default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/exporter/page/use_hgradient</key>
- <applyto>/apps/gthumb/exporter/page/use_hgradient</applyto>
- <owner>gthumb</owner>
- <type>bool</type>
- <default>true</default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/exporter/page/use_vgradient</key>
- <applyto>/apps/gthumb/exporter/page/use_vgradient</applyto>
- <owner>gthumb</owner>
- <type>bool</type>
- <default>true</default>
+ <default>250</default>
<locale name="C">
<short></short>
<long>
@@ -1113,179 +453,8 @@
</schema>
<schema>
- <key>/schemas/apps/gthumb/exporter/page/header_text</key>
- <applyto>/apps/gthumb/exporter/page/header_text</applyto>
- <owner>gthumb</owner>
- <type>string</type>
- <default></default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/exporter/page/header_color</key>
- <applyto>/apps/gthumb/exporter/page/header_color</applyto>
- <owner>gthumb</owner>
- <type>string</type>
- <default>#d5504a</default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/exporter/page/header_font</key>
- <applyto>/apps/gthumb/exporter/page/header_font</applyto>
- <owner>gthumb</owner>
- <type>string</type>
- <default>Arial 22</default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/exporter/page/footer_text</key>
- <applyto>/apps/gthumb/exporter/page/footer_text</applyto>
- <owner>gthumb</owner>
- <type>string</type>
- <default></default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/exporter/page/footer_color</key>
- <applyto>/apps/gthumb/exporter/page/footer_color</applyto>
- <owner>gthumb</owner>
- <type>string</type>
- <default>#394083</default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/exporter/page/footer_font</key>
- <applyto>/apps/gthumb/exporter/page/footer_font</applyto>
- <owner>gthumb</owner>
- <type>string</type>
- <default>Arial Bold Italic 12</default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <!-- Exporter/Thumbnail -->
-
- <schema>
- <key>/schemas/apps/gthumb/exporter/thumbnail/show_comment</key>
- <applyto>/apps/gthumb/exporter/thumbnail/show_comment</applyto>
- <owner>gthumb</owner>
- <type>bool</type>
- <default>false</default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/exporter/thumbnail/show_path</key>
- <applyto>/apps/gthumb/exporter/thumbnail/show_path</applyto>
- <owner>gthumb</owner>
- <type>bool</type>
- <default>false</default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/exporter/thumbnail/show_name</key>
- <applyto>/apps/gthumb/exporter/thumbnail/show_name</applyto>
- <owner>gthumb</owner>
- <type>bool</type>
- <default>true</default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/exporter/thumbnail/show_size</key>
- <applyto>/apps/gthumb/exporter/thumbnail/show_size</applyto>
- <owner>gthumb</owner>
- <type>bool</type>
- <default>false</default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/exporter/thumbnail/show_image_dim</key>
- <applyto>/apps/gthumb/exporter/thumbnail/show_image_dim</applyto>
- <owner>gthumb</owner>
- <type>bool</type>
- <default>false</default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/exporter/thumbnail/frame_style</key>
- <applyto>/apps/gthumb/exporter/thumbnail/frame_style</applyto>
- <owner>gthumb</owner>
- <type>string</type>
- <default>simple_with_shadow</default>
- <locale name="C">
- <short></short>
- <long>Possible values are: none, simple, simple_with_shadow, shadow, slide, shadow_in, shadow_out.
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/exporter/thumbnail/frame_color</key>
- <applyto>/apps/gthumb/exporter/thumbnail/frame_color</applyto>
- <owner>gthumb</owner>
- <type>string</type>
- <default>#94d6cd</default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/exporter/thumbnail/thumb_size</key>
- <applyto>/apps/gthumb/exporter/thumbnail/thumb_size</applyto>
+ <key>/schemas/apps/gthumb/ui/comment_height</key>
+ <applyto>/apps/gthumb/ui/comment_height</applyto>
<owner>gthumb</owner>
<type>int</type>
<default>128</default>
@@ -1296,32 +465,6 @@
</locale>
</schema>
- <schema>
- <key>/schemas/apps/gthumb/exporter/thumbnail/text_color</key>
- <applyto>/apps/gthumb/exporter/thumbnail/text_color</applyto>
- <owner>gthumb</owner>
- <type>string</type>
- <default>#414141</default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/exporter/thumbnail/text_font</key>
- <applyto>/apps/gthumb/exporter/thumbnail/text_font</applyto>
- <owner>gthumb</owner>
- <type>string</type>
- <default>Arial Bold 12</default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
<!-- JPEG Saver -->
<schema>
@@ -1432,671 +575,6 @@
</locale>
</schema>
- <!-- Convert Format -->
-
- <schema>
- <key>/schemas/apps/gthumb/dialogs/convert_format/image_type</key>
- <applyto>/apps/gthumb/dialogs/convert_format/image_type</applyto>
- <owner>gthumb</owner>
- <type>string</type>
- <default>jpeg</default>
- <locale name="C">
- <short></short>
- <long> Possible values are: jpeg, png, tga, tiff.
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/dialogs/convert_format/overwrite_mode</key>
- <applyto>/apps/gthumb/dialogs/convert_format/overwrite_mode</applyto>
- <owner>gthumb</owner>
- <type>string</type>
- <default>rename</default>
- <locale name="C">
- <short></short>
- <long> Possible values are: skip, rename, ask, overwrite.
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/dialogs/convert_format/remove_original</key>
- <applyto>/apps/gthumb/dialogs/convert_format/remove_original</applyto>
- <owner>gthumb</owner>
- <type>bool</type>
- <default>false</default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <!-- Rename Series -->
-
- <schema>
- <key>/schemas/apps/gthumb/dialogs/rename_series/template</key>
- <applyto>/apps/gthumb/dialogs/rename_series/template</applyto>
- <owner>gthumb</owner>
- <type>string</type>
- <default>###%e</default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/dialogs/rename_series/start_at</key>
- <applyto>/apps/gthumb/dialogs/rename_series/start_at</applyto>
- <owner>gthumb</owner>
- <type>int</type>
- <default>1</default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/dialogs/rename_series/sort_by</key>
- <applyto>/apps/gthumb/dialogs/rename_series/sort_by</applyto>
- <owner>gthumb</owner>
- <type>string</type>
- <default>name</default>
- <locale name="C">
- <short></short>
- <long> Possible values are: none, name, path, size, time, exifdate, comment.
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/dialogs/rename_series/reverse_order</key>
- <applyto>/apps/gthumb/dialogs/rename_series/reverse_order</applyto>
- <owner>gthumb</owner>
- <type>bool</type>
- <default>false</default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/dialogs/rename_series/change_case</key>
- <applyto>/apps/gthumb/dialogs/rename_series/change_case</applyto>
- <owner>gthumb</owner>
- <type>string</type>
- <default>original</default>
- <locale name="C">
- <short></short>
- <long> Possible values are: original, lower, upper.
- </long>
- </locale>
- </schema>
-
-
- <!-- Scale Image -->
-
- <schema>
- <key>/schemas/apps/gthumb/dialogs/scale_image/unit</key>
- <applyto>/apps/gthumb/dialogs/scale_image/unit</applyto>
- <owner>gthumb</owner>
- <type>string</type>
- <default>pixels</default>
- <locale name="C">
- <short></short>
- <long> Possible values are: pixels, percentage.
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/dialogs/scale_image/keep_aspect_ratio</key>
- <applyto>/apps/gthumb/dialogs/scale_image/keep_aspect_ratio</applyto>
- <owner>gthumb</owner>
- <type>bool</type>
- <default>true</default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/dialogs/scale_image/high_quality</key>
- <applyto>/apps/gthumb/dialogs/scale_image/high_quality</applyto>
- <owner>gthumb</owner>
- <type>bool</type>
- <default>true</default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <!-- Scale Series -->
-
- <schema>
- <key>/schemas/apps/gthumb/dialogs/scale_series/width</key>
- <applyto>/apps/gthumb/dialogs/scale_series/width</applyto>
- <owner>gthumb</owner>
- <type>int</type>
- <default>640</default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/dialogs/scale_series/height</key>
- <applyto>/apps/gthumb/dialogs/scale_series/height</applyto>
- <owner>gthumb</owner>
- <type>int</type>
- <default>480</default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/dialogs/scale_series/allow_swap_width_height</key>
- <applyto>/apps/gthumb/dialogs/scale_series/allow_swap_width_height</applyto>
- <owner>gthumb</owner>
- <type>bool</type>
- <default>true</default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <!-- Web Album -->
-
- <schema>
- <key>/schemas/apps/gthumb/dialogs/web_album/destination</key>
- <applyto>/apps/gthumb/dialogs/web_album/destination</applyto>
- <owner>gthumb</owner>
- <type>string</type>
- <default></default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/dialogs/web_album/use_subfolders</key>
- <applyto>/apps/gthumb/dialogs/web_album/use_subfolders</applyto>
- <owner>gthumb</owner>
- <type>bool</type>
- <default>true</default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/dialogs/web_album/copy_images</key>
- <applyto>/apps/gthumb/dialogs/web_album/copy_images</applyto>
- <owner>gthumb</owner>
- <type>bool</type>
- <default>false</default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/dialogs/web_album/resize_images</key>
- <applyto>/apps/gthumb/dialogs/web_album/resize_images</applyto>
- <owner>gthumb</owner>
- <type>bool</type>
- <default>false</default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/dialogs/web_album/resize_width</key>
- <applyto>/apps/gthumb/dialogs/web_album/resize_width</applyto>
- <owner>gthumb</owner>
- <type>int</type>
- <default>640</default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/dialogs/web_album/resize_height</key>
- <applyto>/apps/gthumb/dialogs/web_album/resize_height</applyto>
- <owner>gthumb</owner>
- <type>int</type>
- <default>480</default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/dialogs/web_album/rows</key>
- <applyto>/apps/gthumb/dialogs/web_album/rows</applyto>
- <owner>gthumb</owner>
- <type>int</type>
- <default>4</default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/dialogs/web_album/single_index</key>
- <applyto>/apps/gthumb/dialogs/web_album/single_index</applyto>
- <owner>gthumb</owner>
- <type>bool</type>
- <default>false</default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/dialogs/web_album/columns</key>
- <applyto>/apps/gthumb/dialogs/web_album/columns</applyto>
- <owner>gthumb</owner>
- <type>int</type>
- <default>4</default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/dialogs/web_album/sort_by</key>
- <applyto>/apps/gthumb/dialogs/web_album/sort_by</applyto>
- <owner>gthumb</owner>
- <type>string</type>
- <default>name</default>
- <locale name="C">
- <short></short>
- <long> Possible values are: none, name, path, size, time, exifdate, comment.
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/dialogs/web_album/reverse_order</key>
- <applyto>/apps/gthumb/dialogs/web_album/reverse_order</applyto>
- <owner>gthumb</owner>
- <type>bool</type>
- <default>false</default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/dialogs/web_album/header</key>
- <applyto>/apps/gthumb/dialogs/web_album/header</applyto>
- <owner>gthumb</owner>
- <type>string</type>
- <default></default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/dialogs/web_album/footer</key>
- <applyto>/apps/gthumb/dialogs/web_album/footer</applyto>
- <owner>gthumb</owner>
- <type>string</type>
- <default></default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/dialogs/web_album/theme</key>
- <applyto>/apps/gthumb/dialogs/web_album/theme</applyto>
- <owner>gthumb</owner>
- <type>string</type>
- <default>Wiki</default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/dialogs/web_album/index_caption</key>
- <applyto>/apps/gthumb/dialogs/web_album/index_caption</applyto>
- <owner>gthumb</owner>
- <type>int</type>
- <default>0</default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/dialogs/web_album/image_caption</key>
- <applyto>/apps/gthumb/dialogs/web_album/image_caption</applyto>
- <owner>gthumb</owner>
- <type>int</type>
- <default>0</default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <!-- Search -->
-
- <schema>
- <key>/schemas/apps/gthumb/dialogs/search/recursive</key>
- <applyto>/apps/gthumb/dialogs/search/recursive</applyto>
- <owner>gthumb</owner>
- <type>bool</type>
- <default>true</default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <!-- Print -->
-
- <schema>
- <key>/schemas/apps/gthumb/dialogs/print/paper_size</key>
- <applyto>/apps/gthumb/dialogs/print/paper_size</applyto>
- <owner>gthumb</owner>
- <type>string</type>
- <default>A4</default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/dialogs/print/paper_width</key>
- <applyto>/apps/gthumb/dialogs/print/paper_width</applyto>
- <owner>gthumb</owner>
- <type>float</type>
- <default>0.0</default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/dialogs/print/paper_height</key>
- <applyto>/apps/gthumb/dialogs/print/paper_height</applyto>
- <owner>gthumb</owner>
- <type>float</type>
- <default>0.0</default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/dialogs/print/margin_left</key>
- <applyto>/apps/gthumb/dialogs/print/margin_left</applyto>
- <owner>gthumb</owner>
- <type>float</type>
- <default>20.0</default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/dialogs/print/margin_right</key>
- <applyto>/apps/gthumb/dialogs/print/margin_right</applyto>
- <owner>gthumb</owner>
- <type>float</type>
- <default>20.0</default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/dialogs/print/margin_top</key>
- <applyto>/apps/gthumb/dialogs/print/margin_top</applyto>
- <owner>gthumb</owner>
- <type>float</type>
- <default>30.0</default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/dialogs/print/margin_bottom</key>
- <applyto>/apps/gthumb/dialogs/print/margin_bottom</applyto>
- <owner>gthumb</owner>
- <type>float</type>
- <default>30.0</default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/dialogs/print/paper_unit</key>
- <applyto>/apps/gthumb/dialogs/print/paper_unit</applyto>
- <owner>gthumb</owner>
- <type>string</type>
- <default>mm</default>
- <locale name="C">
- <short></short>
- <long> Possible values are: mm, in.
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/dialogs/print/print_paper_orientation</key>
- <applyto>/apps/gthumb/dialogs/print/print_paper_orientation</applyto>
- <owner>gthumb</owner>
- <type>int</type>
- <default>0</default>
- <locale name="C">
- <short></short>
- <long> Possible values are 0 (portrait), 1 (landscape), 2 (reverse portrait), 3 (reverse landscape)
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/dialogs/print/include_comment</key>
- <applyto>/apps/gthumb/dialogs/print/include_comment</applyto>
- <owner>gthumb</owner>
- <type>bool</type>
- <default>false</default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/dialogs/print/include_filename</key>
- <applyto>/apps/gthumb/dialogs/print/include_filename</applyto>
- <owner>gthumb</owner>
- <type>bool</type>
- <default>false</default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/dialogs/print/include_foldername</key>
- <applyto>/apps/gthumb/dialogs/print/include_foldername</applyto>
- <owner>gthumb</owner>
- <type>bool</type>
- <default>false</default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/dialogs/print/comment_font</key>
- <applyto>/apps/gthumb/dialogs/print/comment_font</applyto>
- <owner>gthumb</owner>
- <type>string</type>
- <default>Sans 12</default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/dialogs/print/images_per_page</key>
- <applyto>/apps/gthumb/dialogs/print/images_per_page</applyto>
- <owner>gthumb</owner>
- <type>int</type>
- <default>4</default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/dialogs/print/image_sizing</key>
- <applyto>/apps/gthumb/dialogs/print/image_sizing</applyto>
- <owner>gthumb</owner>
- <type>string</type>
- <default>manual</default>
- <locale name="C">
- <short></short>
- <long> Possible values are: manual, auto.
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/dialogs/print/image_unit</key>
- <applyto>/apps/gthumb/dialogs/print/image_unit</applyto>
- <owner>gthumb</owner>
- <type>string</type>
- <default>in</default>
- <locale name="C">
- <short></short>
- <long> Possible values are: mm, in.
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/dialogs/print/image_width</key>
- <applyto>/apps/gthumb/dialogs/print/image_width</applyto>
- <owner>gthumb</owner>
- <type>float</type>
- <default>4.0</default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/dialogs/print/image_height</key>
- <applyto>/apps/gthumb/dialogs/print/image_height</applyto>
- <owner>gthumb</owner>
- <type>float</type>
- <default>7.0</default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/dialogs/print/image_resolution</key>
- <applyto>/apps/gthumb/dialogs/print/image_resolution</applyto>
- <owner>gthumb</owner>
- <type>string</type>
- <default>300</default>
- <locale name="C">
- <short></short>
- <long> Possible values are: 72, 150, 300, 600.
- </long>
- </locale>
- </schema>
-
<!-- Messages -->
<schema>
@@ -2126,198 +604,6 @@
</schema>
<schema>
- <key>/schemas/apps/gthumb/dialogs/messages/jpeg_mcu_warning</key>
- <applyto>/apps/gthumb/dialogs/messages/jpeg_mcu_warning</applyto>
- <owner>gthumb</owner>
- <type>bool</type>
- <default>true</default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <!-- Choose destination dialog -->
-
- <schema>
- <key>/schemas/apps/gthumb/dialogs/choose_destination/view</key>
- <applyto>/apps/gthumb/dialogs/choose_destination/view</applyto>
- <owner>gthumb</owner>
- <type>bool</type>
- <default>false</default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <!-- Photo Importer -->
-
- <schema>
- <key>/schemas/apps/gthumb/dialogs/photo_importer/model</key>
- <applyto>/apps/gthumb/dialogs/photo_importer/model</applyto>
- <owner>gthumb</owner>
- <type>string</type>
- <default></default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/dialogs/photo_importer/port</key>
- <applyto>/apps/gthumb/dialogs/photo_importer/port</applyto>
- <owner>gthumb</owner>
- <type>string</type>
- <default></default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/dialogs/photo_importer/destination</key>
- <applyto>/apps/gthumb/dialogs/photo_importer/destination</applyto>
- <owner>gthumb</owner>
- <type>string</type>
- <default></default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/dialogs/photo_importer/subfolder</key>
- <applyto>/apps/gthumb/dialogs/photo_importer/subfolder</applyto>
- <owner>gthumb</owner>
- <type>string</type>
- <default>none</default>
- <locale name="C">
- <short></short>
- <long>Possible values are: none, day, month, now, custom
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/dialogs/photo_importer/custom_format_code</key>
- <applyto>/apps/gthumb/dialogs/photo_importer/custom_format_code</applyto>
- <owner>gthumb</owner>
- <type>string</type>
- <default></default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/dialogs/photo_importer/delete_from_camera</key>
- <applyto>/apps/gthumb/dialogs/photo_importer/delete_from_camera</applyto>
- <owner>gthumb</owner>
- <type>bool</type>
- <default>FALSE</default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/dialogs/photo_importer/reset_exif_orientation_on_import</key>
- <applyto>/apps/gthumb/dialogs/photo_importer/reset_exif_orientation_on_import</applyto>
- <owner>gthumb</owner>
- <type>bool</type>
- <default>TRUE</default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <!-- Rotate Dialog -->
-
- <schema>
- <key>/schemas/apps/gthumb/dialogs/rotate/reset_exif_orientation_on_rotate</key>
- <applyto>/apps/gthumb/dialogs/rotate/reset_exif_orientation_on_rotate</applyto>
- <owner>gthumb</owner>
- <type>bool</type>
- <default>TRUE</default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <!-- Crop Dialog -->
-
- <schema>
- <key>/schemas/apps/gthumb/dialogs/crop/aspect_ratio</key>
- <applyto>/apps/gthumb/dialogs/crop/aspect_ratio</applyto>
- <owner>gthumb</owner>
- <type>string</type>
- <default>none</default>
- <locale name="C">
- <short></short>
- <long> Possible values are: none, square, image, display, 4x3, 4x6, 5x7, 8x10, custom.
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/dialogs/crop/aspect_ratio_width</key>
- <applyto>/apps/gthumb/dialogs/crop/aspect_ratio_width</applyto>
- <owner>gthumb</owner>
- <type>int</type>
- <default>1</default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <schema>
- <key>/schemas/apps/gthumb/dialogs/crop/aspect_ratio_height</key>
- <applyto>/apps/gthumb/dialogs/crop/aspect_ratio_height</applyto>
- <owner>gthumb</owner>
- <type>int</type>
- <default>1</default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <!-- Photo Importer -->
-
- <schema>
- <key>/schemas/apps/gthumb/dialogs/add_to_catalog/last_catalog</key>
- <applyto>/apps/gthumb/general/add_to_catalog/last_catalog</applyto>
- <owner>gthumb</owner>
- <type>string</type>
- <default></default>
- <locale name="C">
- <short></short>
- <long>
- </long>
- </locale>
- </schema>
-
- <schema>
<key>/schemas/apps/gthumb/dialogs/add_to_catalog/view</key>
<applyto>/apps/gthumb/general/add_to_catalog/view</applyto>
<owner>gthumb</owner>
diff --git a/data/ui/Makefile.am b/data/ui/Makefile.am
new file mode 100644
index 0000000..08a6c66
--- /dev/null
+++ b/data/ui/Makefile.am
@@ -0,0 +1,11 @@
+uidir = $(datadir)/gthumb/ui
+ui_DATA = \
+ bookmarks.ui \
+ filter-editor.ui \
+ personalize-filters.ui \
+ preferences.ui \
+ sort-order.ui
+
+EXTRA_DIST = $(ui_DATA)
+
+-include $(top_srcdir)/git.mk
diff --git a/data/ui/bookmarks.ui b/data/ui/bookmarks.ui
new file mode 100644
index 0000000..54c627d
--- /dev/null
+++ b/data/ui/bookmarks.ui
@@ -0,0 +1,130 @@
+<?xml version="1.0"?>
+<interface>
+ <requires lib="gtk+" version="2.14"/>
+ <!-- interface-naming-policy project-wide -->
+ <object class="GtkDialog" id="bookmarks_dialog">
+ <property name="height_request">300</property>
+ <property name="border_width">5</property>
+ <property name="title" translatable="yes">Bookmarks</property>
+ <property name="type_hint">dialog</property>
+ <property name="has_separator">False</property>
+ <child internal-child="vbox">
+ <object class="GtkVBox" id="dialog-vbox7">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkHBox" id="hbox57">
+ <property name="visible">True</property>
+ <property name="border_width">6</property>
+ <child>
+ <object class="GtkVBox" id="vbox47">
+ <property name="visible">True</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="bm_bookmarks_label">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">_Bookmarks:</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox90">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkScrolledWindow" id="bm_list_container">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">automatic</property>
+ <property name="vscrollbar_policy">automatic</property>
+ <property name="window_placement">bottom-right</property>
+ <property name="shadow_type">etched-in</property>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child internal-child="action_area">
+ <object class="GtkHButtonBox" id="dialog-action_area7">
+ <property name="visible">True</property>
+ <property name="layout_style">end</property>
+ <child>
+ <object class="GtkButton" id="bm_go_to_button">
+ <property name="label" translatable="yes">gtk-jump-to</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_stock">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="bm_remove_button">
+ <property name="label">gtk-remove</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_stock">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="bm_close_button">
+ <property name="label" translatable="yes">gtk-close</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_stock">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="pack_type">end</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="0">bm_go_to_button</action-widget>
+ <action-widget response="0">bm_remove_button</action-widget>
+ <action-widget response="0">bm_close_button</action-widget>
+ </action-widgets>
+ </object>
+</interface>
diff --git a/data/ui/filter-editor.ui b/data/ui/filter-editor.ui
new file mode 100644
index 0000000..685c22d
--- /dev/null
+++ b/data/ui/filter-editor.ui
@@ -0,0 +1,207 @@
+<?xml version="1.0"?>
+<interface>
+ <requires lib="gtk+" version="2.14"/>
+ <!-- interface-naming-policy project-wide -->
+ <object class="GtkVBox" id="filter_editor">
+ <property name="visible">True</property>
+ <property name="border_width">6</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkHBox" id="hbox1">
+ <property name="visible">True</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label1">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Filter _Name:</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">name_entry</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="name_entry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame" id="frame1">
+ <property name="visible">True</property>
+ <property name="label_xalign">0</property>
+ <child>
+ <object class="GtkAlignment" id="alignment1">
+ <property name="visible">True</property>
+ <property name="top_padding">6</property>
+ <property name="bottom_padding">6</property>
+ <property name="left_padding">12</property>
+ <property name="right_padding">6</property>
+ <child>
+ <object class="GtkVBox" id="tests_box">
+ <property name="visible">True</property>
+ <property name="spacing">6</property>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkHBox" id="hbox2">
+ <property name="visible">True</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkCheckButton" id="match_checkbutton">
+ <property name="label" translatable="yes">_Match</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="match_type_combobox_box">
+ <property name="visible">True</property>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox1">
+ <property name="visible">True</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkHBox" id="hbox5">
+ <property name="visible">True</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkCheckButton" id="limit_to_checkbutton">
+ <property name="label" translatable="yes">_Limit to</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="limit_options_hbox">
+ <property name="visible">True</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkEntry" id="limit_size_entry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="width_chars">6</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="limit_object_combobox_box">
+ <property name="visible">True</property>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="selection_box">
+ <property name="visible">True</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label3">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">selected by</property>
+ </object>
+ <packing>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="selection_combobox_box">
+ <property name="visible">True</property>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="selection_order_combobox_box">
+ <property name="visible">True</property>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+</interface>
diff --git a/data/ui/personalize-filters.ui b/data/ui/personalize-filters.ui
new file mode 100644
index 0000000..ad1969d
--- /dev/null
+++ b/data/ui/personalize-filters.ui
@@ -0,0 +1,202 @@
+<?xml version="1.0"?>
+<interface>
+ <requires lib="gtk+" version="2.14"/>
+ <!-- interface-naming-policy project-wide -->
+ <object class="GtkDialog" id="personalize_filters_dialog">
+ <property name="border_width">5</property>
+ <property name="title" translatable="yes">Filters</property>
+ <property name="type_hint">normal</property>
+ <property name="has_separator">False</property>
+ <child internal-child="vbox">
+ <object class="GtkVBox" id="dialog-vbox">
+ <property name="visible">True</property>
+ <property name="spacing">2</property>
+ <child>
+ <object class="GtkVBox" id="vbox4">
+ <property name="visible">True</property>
+ <property name="border_width">6</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkVBox" id="vbox5">
+ <property name="visible">True</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="general_filter_label">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">_General filter:</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="general_filter_box">
+ <property name="visible">True</property>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox6">
+ <property name="visible">True</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="filters_label">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">_Other filters:</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox3">
+ <property name="visible">True</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkScrolledWindow" id="filters_scrolledwindow">
+ <property name="width_request">250</property>
+ <property name="height_request">300</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">automatic</property>
+ <property name="vscrollbar_policy">automatic</property>
+ <property name="shadow_type">etched-in</property>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVButtonBox" id="vbuttonbox3">
+ <property name="visible">True</property>
+ <property name="spacing">6</property>
+ <property name="layout_style">start</property>
+ <child>
+ <object class="GtkButton" id="new_button">
+ <property name="label" translatable="yes">gtk-new</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_stock">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="edit_button">
+ <property name="label" translatable="yes">gtk-edit</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_stock">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="delete_button">
+ <property name="label" translatable="yes">gtk-remove</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_stock">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child internal-child="action_area">
+ <object class="GtkHButtonBox" id="dialog-action_area">
+ <property name="visible">True</property>
+ <property name="layout_style">end</property>
+ <child>
+ <object class="GtkButton" id="close_button">
+ <property name="label" translatable="yes">gtk-close</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_stock">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="help_button">
+ <property name="label" translatable="yes">gtk-help</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_stock">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ <property name="secondary">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="pack_type">end</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="0">close_button</action-widget>
+ <action-widget response="0">help_button</action-widget>
+ </action-widgets>
+ </object>
+</interface>
diff --git a/data/ui/preferences.ui b/data/ui/preferences.ui
new file mode 100644
index 0000000..e48e8d7
--- /dev/null
+++ b/data/ui/preferences.ui
@@ -0,0 +1,590 @@
+<?xml version="1.0"?>
+<interface>
+ <requires lib="gtk+" version="2.14"/>
+ <!-- interface-naming-policy toplevel-contextual -->
+ <object class="GtkDialog" id="preferences_dialog">
+ <property name="border_width">6</property>
+ <property name="title" translatable="yes">gThumb Preferences</property>
+ <property name="resizable">False</property>
+ <property name="type_hint">dialog</property>
+ <property name="has_separator">False</property>
+ <child internal-child="vbox">
+ <object class="GtkVBox" id="dialog-vbox4">
+ <property name="visible">True</property>
+ <property name="spacing">8</property>
+ <child>
+ <object class="GtkNotebook" id="notebook">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="border_width">5</property>
+ <child>
+ <object class="GtkVBox" id="vbox11">
+ <property name="visible">True</property>
+ <property name="border_width">12</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkVBox" id="vbox25">
+ <property name="visible">True</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkHBox" id="hbox66">
+ <property name="visible">True</property>
+ <property name="spacing">5</property>
+ <child>
+ <object class="GtkLabel" id="label156">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes"><b>Interface</b></property>
+ <property name="use_markup">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAlignment" id="alignment3">
+ <property name="visible">True</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkHBox" id="hbox7">
+ <property name="visible">True</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkLabel" id="label6">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">_Toolbar style:</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="toolbar_style_combobox_box">
+ <property name="visible">True</property>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox26">
+ <property name="visible">True</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkHBox" id="hbox34">
+ <property name="visible">True</property>
+ <property name="spacing">5</property>
+ <child>
+ <object class="GtkLabel" id="label86">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes"><b>On startup:</b></property>
+ <property name="use_markup">True</property>
+ <property name="justify">center</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAlignment" id="alignment4">
+ <property name="visible">True</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkVBox" id="vbox8">
+ <property name="visible">True</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkRadioButton" id="current_location_radiobutton">
+ <property name="label" translatable="yes">Do _not change folder</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="active">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="go_to_last_location_radiobutton">
+ <property name="label" translatable="yes">Go to last _visited location</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ <property name="group">current_location_radiobutton</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox10">
+ <property name="visible">True</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkRadioButton" id="use_startup_location_radiobutton">
+ <property name="label" translatable="yes">Go to this _folder:</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ <property name="group">current_location_radiobutton</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFileChooserButton" id="startup_dir_filechooserbutton">
+ <property name="visible">True</property>
+ <property name="action">select-folder</property>
+ <property name="local_only">False</property>
+ <property name="title" translatable="yes">Choose startup folder</property>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox11">
+ <property name="visible">True</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkButton" id="set_to_current_button">
+ <property name="label" translatable="yes">Set to C_urrent</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="pack_type">end</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox57">
+ <property name="visible">True</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkHBox" id="hbox110">
+ <property name="visible">True</property>
+ <property name="spacing">5</property>
+ <child>
+ <object class="GtkLabel" id="label183">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes"><b>Other</b></property>
+ <property name="use_markup">True</property>
+ <property name="justify">center</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAlignment" id="alignment5">
+ <property name="visible">True</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkVBox" id="vbox10">
+ <property name="visible">True</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkCheckButton" id="confirm_deletion_checkbutton">
+ <property name="label" translatable="yes">As_k confirmation before deleting files or catalogs</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="ask_to_save_checkbutton">
+ <property name="label" translatable="yes">Ask whether to save _modified files</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="label77">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">General</property>
+ <property name="justify">center</property>
+ </object>
+ <packing>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox12">
+ <property name="visible">True</property>
+ <property name="border_width">12</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkVBox" id="vbox30">
+ <property name="visible">True</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkTable" id="table14">
+ <property name="visible">True</property>
+ <property name="n_columns">2</property>
+ <property name="column_spacing">12</property>
+ <property name="row_spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label113">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Th_umbnail size:</property>
+ <property name="use_underline">True</property>
+ <property name="justify">center</property>
+ </object>
+ <packing>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox113">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkHBox" id="thumbnail_size_combobox_box">
+ <property name="visible">True</property>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options">GTK_FILL</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="slow_mime_type_checkbutton">
+ <property name="label" translatable="yes">D_etermine image type from content (slower)</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="label78">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Browser</property>
+ <property name="justify">center</property>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child internal-child="action_area">
+ <object class="GtkHButtonBox" id="dialog-action_area4">
+ <property name="visible">True</property>
+ <property name="layout_style">end</property>
+ <child>
+ <object class="GtkButton" id="help_button">
+ <property name="label">gtk-help</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_stock">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ <property name="secondary">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="close_button">
+ <property name="label">gtk-close</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="has_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="has_default">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_stock">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="pack_type">end</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="-11">help_button</action-widget>
+ <action-widget response="-7">close_button</action-widget>
+ </action-widgets>
+ </object>
+ <object class="GtkListStore" id="toolbar_style_liststore">
+ <columns>
+ <!-- column-name name -->
+ <column type="gchararray"/>
+ </columns>
+ <data>
+ <row>
+ <col id="0" translatable="yes">System settings</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Text below icons</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Text beside icons</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Icons only</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Text only</col>
+ </row>
+ </data>
+ </object>
+ <object class="GtkListStore" id="thumbnail_size_liststore">
+ <columns>
+ <!-- column-name name -->
+ <column type="gchararray"/>
+ </columns>
+ <data>
+ <row>
+ <col id="0" translatable="yes">48</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">64</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">85</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">95</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">112</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">128</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">164</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">200</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">256</col>
+ </row>
+ </data>
+ </object>
+ <object class="GtkListStore" id="click_policy_liststore">
+ <columns>
+ <!-- column-name name -->
+ <column type="gchararray"/>
+ </columns>
+ <data>
+ <row>
+ <col id="0" translatable="yes">Follow Nautilus behaviour</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Activate items with a single click</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Activate items with a double click</col>
+ </row>
+ </data>
+ </object>
+ <object class="GtkListStore" id="zoom_change_liststore">
+ <columns>
+ <!-- column-name name -->
+ <column type="gchararray"/>
+ </columns>
+ <data>
+ <row>
+ <col id="0" translatable="yes">Keep previous zoom</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Fit to window</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Fit to window if larger</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Set image to actual size</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Fit to width if larger</col>
+ </row>
+ </data>
+ </object>
+ <object class="GtkListStore" id="transparency_type_liststore">
+ <columns>
+ <!-- column-name name -->
+ <column type="gchararray"/>
+ </columns>
+ <data>
+ <row>
+ <col id="0" translatable="yes">White</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">None</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Black</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Checked</col>
+ </row>
+ </data>
+ </object>
+</interface>
diff --git a/data/ui/sort-order.ui b/data/ui/sort-order.ui
new file mode 100644
index 0000000..279de33
--- /dev/null
+++ b/data/ui/sort-order.ui
@@ -0,0 +1,94 @@
+<?xml version="1.0"?>
+<interface>
+ <requires lib="gtk+" version="2.14"/>
+ <!-- interface-naming-policy project-wide -->
+ <object class="GtkDialog" id="sort_order_dialog">
+ <property name="width_request">300</property>
+ <property name="border_width">5</property>
+ <property name="title" translatable="yes">Sort By</property>
+ <property name="type_hint">normal</property>
+ <property name="has_separator">False</property>
+ <child internal-child="vbox">
+ <object class="GtkVBox" id="dialog-vbox1">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">2</property>
+ <child>
+ <object class="GtkVBox" id="vbox3">
+ <property name="visible">True</property>
+ <property name="border_width">6</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkHBox" id="sort_by_hbox">
+ <property name="visible">True</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="sort_by_label">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Sort by</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="inverse_checkbutton">
+ <property name="label" translatable="yes">_Inverse order</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child internal-child="action_area">
+ <object class="GtkHButtonBox" id="dialog-action_area1">
+ <property name="visible">True</property>
+ <property name="layout_style">end</property>
+ <child>
+ <object class="GtkButton" id="close_button">
+ <property name="label" translatable="yes">gtk-close</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_stock">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="pack_type">end</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="0">close_button</action-widget>
+ </action-widgets>
+ </object>
+</interface>
diff --git a/extensions/Makefile.am b/extensions/Makefile.am
new file mode 100644
index 0000000..7615900
--- /dev/null
+++ b/extensions/Makefile.am
@@ -0,0 +1,3 @@
+SUBDIRS = catalogs comments exiv2 file_manager file_viewer image_tools image_viewer search
+
+-include $(top_srcdir)/git.mk
diff --git a/extensions/catalogs/Makefile.am b/extensions/catalogs/Makefile.am
new file mode 100644
index 0000000..963c970
--- /dev/null
+++ b/extensions/catalogs/Makefile.am
@@ -0,0 +1,40 @@
+SUBDIRS = data
+
+extensiondir = $(libdir)/gthumb-2.0/extensions
+extension_LTLIBRARIES = libcatalogs.la
+
+libcatalogs_la_SOURCES = \
+ actions.c \
+ actions.h \
+ callbacks.c \
+ callbacks.h \
+ dlg-add-to-catalog.c \
+ dlg-add-to-catalog.h \
+ gth-catalog.c \
+ gth-catalog.h \
+ gth-file-source-catalogs.c \
+ gth-file-source-catalogs.h \
+ main.c
+
+libcatalogs_la_CFLAGS = $(GTHUMB_CFLAGS) -I$(top_srcdir) -I$(top_builddir)/gthumb
+libcatalogs_la_LDFLAGS = $(EXTENSION_LIBTOOL_FLAGS)
+libcatalogs_la_LIBADD = $(GTHUMB_LIBS)
+libcatalogs_la_DEPENDENCIES = $(top_builddir)/gthumb/gthumb$(EXEEXT)
+
+extensioninidir = $(extensiondir)
+extensionini_in_files = catalogs.extension.in.in
+extensionini_DATA = $(extensionini_in_files:.extension.in.in=.extension)
+
+%.extension.in: %.extension.in.in $(extension_LTLIBRARIES)
+ sed -e "s|%LIBRARY%|`. ./$(extension_LTLIBRARIES) && echo $$dlname`|" \
+ $< > $@
+
+%.extension: %.extension.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*po) ; LC_ALL=C $(INTLTOOL_MERGE) -d -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< $@
+
+EXTRA_DIST = $(extensionini_in_files)
+
+CLEANFILES = $(extensionini_DATA)
+
+DISTCLEANFILES = $(extensionini_DATA)
+
+-include $(top_srcdir)/git.mk
diff --git a/extensions/catalogs/actions.c b/extensions/catalogs/actions.c
new file mode 100644
index 0000000..2842928
--- /dev/null
+++ b/extensions/catalogs/actions.c
@@ -0,0 +1,357 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2008 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+
+#include <config.h>
+#include <gthumb.h>
+#include <gth-catalog.h>
+#include "dlg-add-to-catalog.h"
+
+
+void
+gth_browser_activate_action_edit_add_to_catalog (GtkAction *action,
+ GthBrowser *browser)
+{
+ GList *items;
+ GList *file_list = NULL;
+
+ items = gth_file_selection_get_selected (GTH_FILE_SELECTION (gth_browser_get_file_list_view (browser)));
+ file_list = gth_file_list_get_files (GTH_FILE_LIST (gth_browser_get_file_list (browser)), items);
+
+ dlg_add_to_catalog (browser, file_list);
+
+ _g_object_list_unref (file_list);
+ _gtk_tree_path_list_free (items);
+}
+
+
+typedef struct {
+ GthBrowser *browser;
+ GList *file_data_list;
+ GFile *gio_file;
+ GthCatalog *catalog;
+ char *buffer;
+ gsize length;
+} RemoveFromCatalogData;
+
+
+static void
+remove_from_catalog_end (GError *error,
+ RemoveFromCatalogData *data)
+{
+ if (error != NULL)
+ _gtk_error_dialog_from_gerror_show (GTK_WINDOW (data->browser), _("Could not remove the files from the catalog"), &error);
+
+ g_free (data->buffer);
+ g_object_unref (data->catalog);
+ g_object_unref (data->gio_file);
+ _g_object_list_unref (data->file_data_list);
+ g_free (data);
+}
+
+
+static void
+catalog_save_done_cb (void *buffer,
+ gsize count,
+ GError *error,
+ gpointer user_data)
+{
+ RemoveFromCatalogData *data = user_data;
+
+ if (error == NULL) {
+ GFile *catalog_file;
+ GList *files = NULL;
+ GList *scan;
+
+ catalog_file = gth_catalog_file_from_gio_file (data->gio_file, NULL);
+ for (scan = data->file_data_list; scan; scan = scan->next)
+ files = g_list_prepend (files, ((GthFileData*) scan->data)->file);
+ files = g_list_reverse (files);
+
+ gth_monitor_folder_changed (gth_main_get_default_monitor (),
+ catalog_file,
+ files,
+ GTH_MONITOR_EVENT_DELETED);
+
+ _g_object_list_unref (files);
+ g_object_unref (catalog_file);
+ }
+
+ remove_from_catalog_end (error, data);
+}
+
+
+static void
+catalog_buffer_ready_cb (void *buffer,
+ gsize count,
+ GError *error,
+ gpointer user_data)
+{
+ RemoveFromCatalogData *data = user_data;
+ GList *scan;
+
+ if (error != NULL) {
+ remove_from_catalog_end (error, data);
+ return;
+ }
+
+ data->catalog = gth_hook_invoke_get ("gth-catalog-load-from-data", buffer);
+ if (data->catalog == NULL) {
+ error = g_error_new_literal (G_IO_ERROR, G_IO_ERROR_FAILED, _("Invalid file format"));
+ remove_from_catalog_end (error, data);
+ return;
+ }
+
+ gth_catalog_load_from_data (data->catalog, buffer, count, &error);
+ if (error != NULL) {
+ remove_from_catalog_end (error, data);
+ return;
+ }
+
+ for (scan = data->file_data_list; scan; scan = scan->next) {
+ GthFileData *file_data = scan->data;
+
+ gth_catalog_remove_file (data->catalog, file_data->file);
+ }
+
+ data->buffer = gth_catalog_to_data (data->catalog, &data->length);
+ if (error != NULL) {
+ remove_from_catalog_end (error, data);
+ return;
+ }
+
+ g_write_file_async (data->gio_file,
+ data->buffer,
+ data->length,
+ G_PRIORITY_DEFAULT,
+ NULL,
+ catalog_save_done_cb,
+ data);
+}
+
+
+void
+gth_browser_activate_action_edit_remove_from_catalog (GtkAction *action,
+ GthBrowser *browser)
+{
+ RemoveFromCatalogData *data;
+ GList *items;
+
+ data = g_new0 (RemoveFromCatalogData, 1);
+ data->browser = browser;
+ items = gth_file_selection_get_selected (GTH_FILE_SELECTION (gth_browser_get_file_list_view (browser)));
+ data->file_data_list = gth_file_list_get_files (GTH_FILE_LIST (gth_browser_get_file_list (browser)), items);
+ data->gio_file = gth_main_get_gio_file (gth_browser_get_location (browser));
+ g_load_file_async (data->gio_file,
+ G_PRIORITY_DEFAULT,
+ NULL,
+ catalog_buffer_ready_cb,
+ data);
+
+ _gtk_tree_path_list_free (items);
+}
+
+
+void
+gth_browser_activate_action_catalog_new (GtkAction *action,
+ GthBrowser *browser)
+{
+ GFile *selected_parent;
+ GFile *parent;
+ GthFileSource *file_source;
+ GFile *gio_parent;
+ GError *error;
+ GFile *gio_file;
+
+ selected_parent = gth_folder_tree_get_selected_or_parent (GTH_FOLDER_TREE (gth_browser_get_folder_tree (browser)));
+ if (selected_parent != NULL) {
+ GthFileSource *file_source;
+ GFileInfo *info;
+
+ file_source = gth_main_get_file_source (selected_parent);
+ info = gth_file_source_get_file_info (file_source, selected_parent);
+ if (g_file_info_get_attribute_boolean (info, "gthumb::no-child"))
+ parent = g_file_get_parent (selected_parent);
+ else
+ parent = g_file_dup (selected_parent);
+
+ g_object_unref (info);
+ g_object_unref (file_source);
+ }
+ else
+ parent = g_file_new_for_uri ("catalog:///");
+
+ file_source = gth_main_get_file_source (parent);
+ gio_parent = gth_file_source_to_gio_file (file_source, parent);
+ gio_file = _g_file_create_unique (gio_parent, _("New Catalog"), ".catalog", &error);
+ if (gio_file != NULL) {
+ GFile *file;
+ GList *list;
+ GFileInfo *info;
+ GthFileData *file_data;
+ GList *file_data_list;
+
+ file = gth_catalog_file_from_gio_file (gio_file, NULL);
+ info = gth_file_source_get_file_info (file_source, file);
+ file_data = gth_file_data_new (file, info);
+ file_data_list = g_list_prepend (NULL, file_data);
+ gth_folder_tree_add_children (GTH_FOLDER_TREE (gth_browser_get_folder_tree (browser)), parent, file_data_list);
+ gth_folder_tree_start_editing (GTH_FOLDER_TREE (gth_browser_get_folder_tree (browser)), file);
+
+ list = g_list_prepend (NULL, g_object_ref (file));
+ gth_monitor_folder_changed (gth_main_get_default_monitor (),
+ parent,
+ list,
+ GTH_MONITOR_EVENT_CREATED);
+
+ _g_object_list_unref (list);
+ g_list_free (file_data_list);
+ g_object_unref (file_data);
+ g_object_unref (info);
+ g_object_unref (file);
+ }
+ else
+ _gtk_error_dialog_from_gerror_show (GTK_WINDOW (browser), _("Could not create the catalog"), &error);
+
+ g_object_unref (gio_file);
+ g_object_unref (gio_parent);
+ g_object_unref (file_source);
+}
+
+
+void
+gth_browser_activate_action_catalog_new_library (GtkAction *action,
+ GthBrowser *browser)
+{
+ GFile *selected_parent;
+ GFile *parent;
+ GthFileSource *file_source;
+ GFile *gio_parent;
+ GError *error;
+ GFile *gio_file;
+
+ selected_parent = gth_folder_tree_get_selected_or_parent (GTH_FOLDER_TREE (gth_browser_get_folder_tree (browser)));
+ if (selected_parent != NULL) {
+ GthFileSource *file_source;
+ GFileInfo *info;
+
+ file_source = gth_main_get_file_source (selected_parent);
+ info = gth_file_source_get_file_info (file_source, selected_parent);
+ if (g_file_info_get_attribute_boolean (info, "gthumb::no-child"))
+ parent = g_file_get_parent (selected_parent);
+ else
+ parent = g_file_dup (selected_parent);
+
+ g_object_unref (info);
+ g_object_unref (file_source);
+ }
+ else
+ parent = g_file_new_for_uri ("catalog:///");
+
+ file_source = gth_main_get_file_source (parent);
+ gio_parent = gth_file_source_to_gio_file (file_source, parent);
+ gio_file = _g_directory_create_unique (gio_parent, _("New Library"), "", &error);
+ if (gio_file != NULL) {
+ GFile *file;
+ GList *list;
+ GFileInfo *info;
+ GthFileData *file_data;
+ GList *file_data_list;
+
+ file = gth_catalog_file_from_gio_file (gio_file, NULL);
+ info = gth_file_source_get_file_info (file_source, file);
+ file_data = gth_file_data_new (file, info);
+ file_data_list = g_list_prepend (NULL, file_data);
+ gth_folder_tree_add_children (GTH_FOLDER_TREE (gth_browser_get_folder_tree (browser)), parent, file_data_list);
+ gth_folder_tree_start_editing (GTH_FOLDER_TREE (gth_browser_get_folder_tree (browser)), file);
+
+ list = g_list_prepend (NULL, g_object_ref (file));
+ gth_monitor_folder_changed (gth_main_get_default_monitor (),
+ parent,
+ list,
+ GTH_MONITOR_EVENT_CREATED);
+
+ _g_object_list_unref (list);
+ g_list_free (file_data_list);
+ g_object_unref (file_data);
+ g_object_unref (info);
+ g_object_unref (file);
+ }
+ else
+ _gtk_error_dialog_from_gerror_show (GTK_WINDOW (browser), _("Could not create the library"), &error);
+
+ g_object_unref (gio_file);
+ g_object_unref (gio_parent);
+ g_object_unref (file_source);
+}
+
+
+void
+gth_browser_activate_action_catalog_remove (GtkAction *action,
+ GthBrowser *browser)
+{
+ GthFolderTree *folder_tree;
+ GFile *file;
+ GFile *gio_file;
+ GError *error = NULL;
+
+ folder_tree = GTH_FOLDER_TREE (gth_browser_get_folder_tree (browser));
+ file = gth_folder_tree_get_selected (folder_tree);
+ gio_file = gth_main_get_gio_file (file);
+ if (g_file_delete (gio_file, NULL, &error)) {
+ GFile *parent;
+ GList *files;
+
+ parent = g_file_get_parent (file);
+ files = g_list_prepend (NULL, g_object_ref (file));
+ gth_monitor_folder_changed (gth_main_get_default_monitor (),
+ parent,
+ files,
+ GTH_MONITOR_EVENT_DELETED);
+
+ _g_object_list_unref (files);
+ if (parent != NULL)
+ g_object_unref (parent);
+ }
+ else
+ _gtk_error_dialog_from_gerror_show (GTK_WINDOW (browser),
+ _("Could not remove the catalog"),
+ &error);
+
+ g_object_unref (gio_file);
+ g_object_unref (file);
+}
+
+
+void
+gth_browser_activate_action_catalog_rename (GtkAction *action,
+ GthBrowser *browser)
+{
+ GthFolderTree *folder_tree;
+ GFile *file;
+
+ folder_tree = GTH_FOLDER_TREE (gth_browser_get_folder_tree (browser));
+ file = gth_folder_tree_get_selected (folder_tree);
+ gth_folder_tree_start_editing (folder_tree, file);
+
+ g_object_unref (file);
+}
diff --git a/extensions/catalogs/actions.h b/extensions/catalogs/actions.h
new file mode 100644
index 0000000..22bafdb
--- /dev/null
+++ b/extensions/catalogs/actions.h
@@ -0,0 +1,37 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef ACTIONS_H
+#define ACTIONS_H
+
+#include <gtk/gtkaction.h>
+
+#define DEFINE_ACTION(x) void x (GtkAction *action, gpointer data);
+
+DEFINE_ACTION(gth_browser_activate_action_edit_add_to_catalog)
+DEFINE_ACTION(gth_browser_activate_action_edit_remove_from_catalog)
+DEFINE_ACTION(gth_browser_activate_action_catalog_new)
+DEFINE_ACTION(gth_browser_activate_action_catalog_new_library)
+DEFINE_ACTION(gth_browser_activate_action_catalog_remove)
+DEFINE_ACTION(gth_browser_activate_action_catalog_rename)
+
+#endif /* ACTIONS_H */
diff --git a/extensions/catalogs/callbacks.c b/extensions/catalogs/callbacks.c
new file mode 100644
index 0000000..05b5451
--- /dev/null
+++ b/extensions/catalogs/callbacks.c
@@ -0,0 +1,255 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+
+#include <config.h>
+#include <glib/gi18n.h>
+#include <glib-object.h>
+#include <gthumb.h>
+#include <gth-catalog.h>
+#include "gth-file-source-catalogs.h"
+#include "actions.h"
+
+#define BROWSER_DATA_KEY "catalogs-browser-data"
+
+
+static const char *fixed_ui_info =
+"<ui>"
+" <popup name='FileListPopup'>"
+" <placeholder name='Folder_Actions2'>"
+" <menuitem action='Edit_AddToCatalog'/>"
+" <menuitem action='Edit_RemoveFromCatalog'/>"
+" </placeholder>"
+" </popup>"
+"</ui>";
+
+
+static const char *vfs_ui_info =
+"<ui>"
+" <menubar name='MenuBar'>"
+" <menu name='File' action='FileMenu'>"
+" <placeholder name='Folder_Actions'>"
+" <menuitem action='Catalog_New'/>"
+" <menuitem action='Catalog_New_Library'/>"
+" </placeholder>"
+" </menu>"
+" </menubar>"
+"</ui>";
+
+
+static const gchar *folder_popup_ui_info =
+"<ui>"
+" <popup name='FolderListPopup'>"
+" <placeholder name='SourceCommands'>"
+" <menuitem action='Catalog_New'/>"
+" <menuitem action='Catalog_New_Library'/>"
+" <separator/>"
+" <menuitem action='Catalog_Remove'/>"
+" <menuitem action='Catalog_Rename'/>"
+" </placeholder>"
+" </popup>"
+"</ui>";
+
+
+static GtkActionEntry catalog_action_entries[] = {
+ { "Edit_AddToCatalog", GTK_STOCK_ADD,
+ N_("_Add to Catalog..."), NULL,
+ N_("Add selected images to a catalog"),
+ G_CALLBACK (gth_browser_activate_action_edit_add_to_catalog) },
+
+ { "Edit_RemoveFromCatalog", GTK_STOCK_REMOVE,
+ N_("Remo_ve from Catalog"), NULL,
+ N_("Remove selected images from the catalog"),
+ G_CALLBACK (gth_browser_activate_action_edit_remove_from_catalog) },
+
+ { "Catalog_New", NULL,
+ N_("Create _Catalog"), NULL,
+ NULL,
+ G_CALLBACK (gth_browser_activate_action_catalog_new) },
+
+ { "Catalog_New_Library", NULL,
+ N_("Create _Library"), NULL,
+ NULL,
+ G_CALLBACK (gth_browser_activate_action_catalog_new_library) },
+
+ { "Catalog_Remove", GTK_STOCK_REMOVE,
+ NULL, NULL,
+ NULL,
+ G_CALLBACK (gth_browser_activate_action_catalog_remove) },
+
+ { "Catalog_Rename", NULL,
+ N_("Rena_me"), NULL,
+ NULL,
+ G_CALLBACK (gth_browser_activate_action_catalog_rename) }
+};
+static guint catalog_action_entries_size = G_N_ELEMENTS (catalog_action_entries);
+
+
+typedef struct {
+ GtkActionGroup *actions;
+ guint folder_popup_merge_id;
+ guint vfs_merge_id;
+} BrowserData;
+
+
+static void
+browser_data_free (BrowserData *data)
+{
+ g_free (data);
+}
+
+
+void
+catalogs__gth_browser_construct_cb (GthBrowser *browser)
+{
+ BrowserData *data;
+ GError *error = NULL;
+
+ g_return_if_fail (GTH_IS_BROWSER (browser));
+
+ data = g_new0 (BrowserData, 1);
+
+ data->actions = gtk_action_group_new ("Catalog Actions");
+ gtk_action_group_set_translation_domain (data->actions, NULL);
+ gtk_action_group_add_actions (data->actions,
+ catalog_action_entries,
+ catalog_action_entries_size,
+ browser);
+ gtk_ui_manager_insert_action_group (gth_browser_get_ui_manager (browser), data->actions, 0);
+
+ if (! gtk_ui_manager_add_ui_from_string (gth_browser_get_ui_manager (browser), fixed_ui_info, -1, &error)) {
+ g_message ("building menus failed: %s", error->message);
+ g_error_free (error);
+ }
+
+ g_object_set_data_full (G_OBJECT (browser), BROWSER_DATA_KEY, data, (GDestroyNotify) browser_data_free);
+}
+
+
+void
+catalogs__gth_browser_update_sensitivity_cb (GthBrowser *browser)
+{
+ BrowserData *data;
+ GtkAction *action;
+ int n_selected;
+ gboolean sensitive;
+
+ data = g_object_get_data (G_OBJECT (browser), BROWSER_DATA_KEY);
+ g_return_if_fail (data != NULL);
+
+ n_selected = gth_file_selection_get_n_selected (GTH_FILE_SELECTION (gth_browser_get_file_list_view (browser)));
+
+ action = gtk_action_group_get_action (data->actions, "Edit_AddToCatalog");
+ sensitive = n_selected > 0;
+ g_object_set (action, "sensitive", sensitive, NULL);
+
+ action = gtk_action_group_get_action (data->actions, "Edit_RemoveFromCatalog");
+ sensitive = (n_selected > 0) && GTH_IS_FILE_SOURCE_CATALOGS (gth_browser_get_file_source (browser));
+ g_object_set (action, "sensitive", sensitive, NULL);
+}
+
+
+void
+catalogs__gth_browser_folder_tree_popup_before_cb (GthBrowser *browser,
+ GthFileSource *file_source,
+ GFile *folder)
+{
+ BrowserData *data;
+
+ data = g_object_get_data (G_OBJECT (browser), BROWSER_DATA_KEY);
+ g_return_if_fail (data != NULL);
+
+ if (GTH_IS_FILE_SOURCE_CATALOGS (file_source)) {
+ GtkAction *action;
+ gboolean sensitive;
+
+ if (data->folder_popup_merge_id == 0) {
+ GError *error = NULL;
+
+ data->folder_popup_merge_id = gtk_ui_manager_add_ui_from_string (gth_browser_get_ui_manager (browser), folder_popup_ui_info, -1, &error);
+ if (data->folder_popup_merge_id == 0) {
+ g_message ("building menus failed: %s", error->message);
+ g_error_free (error);
+ }
+ }
+
+ action = gtk_action_group_get_action (data->actions, "Catalog_Remove");
+ sensitive = folder != NULL;
+ g_object_set (action, "sensitive", sensitive, NULL);
+
+ action = gtk_action_group_get_action (data->actions, "Catalog_Rename");
+ sensitive = folder != NULL;
+ g_object_set (action, "sensitive", sensitive, NULL);
+ }
+ else {
+ if (data->folder_popup_merge_id != 0) {
+ gtk_ui_manager_remove_ui (gth_browser_get_ui_manager (browser), data->folder_popup_merge_id);
+ data->folder_popup_merge_id = 0;
+ }
+ }
+}
+
+
+GthCatalog *
+catalogs__gth_catalog_load_from_data_cb (const void *buffer)
+{
+ if ((buffer == NULL)
+ || (strcmp (buffer, "") == 0)
+ || (strncmp (buffer, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<catalog ", 48) == 0))
+ {
+ return gth_catalog_new ();
+ }
+ else
+ return NULL;
+}
+
+
+void
+catalogs__gth_browser_load_location_after_cb (GthBrowser *browser,
+ GFile *location,
+ const GError *error)
+{
+ BrowserData *data;
+
+ if (location == NULL)
+ return;
+
+ data = g_object_get_data (G_OBJECT (browser), BROWSER_DATA_KEY);
+
+ if (GTH_IS_FILE_SOURCE_CATALOGS (gth_browser_get_file_source (browser))) {
+ if (data->vfs_merge_id == 0) {
+ GError *error = NULL;
+
+ data->vfs_merge_id = gtk_ui_manager_add_ui_from_string (gth_browser_get_ui_manager (browser), vfs_ui_info, -1, &error);
+ if (data->vfs_merge_id == 0) {
+ g_message ("building menus failed: %s", error->message);
+ g_error_free (error);
+ }
+ }
+ }
+ else {
+ if (data->vfs_merge_id != 0) {
+ gtk_ui_manager_remove_ui (gth_browser_get_ui_manager (browser), data->vfs_merge_id);
+ data->vfs_merge_id = 0;
+ }
+ }
+}
diff --git a/extensions/catalogs/callbacks.h b/extensions/catalogs/callbacks.h
new file mode 100644
index 0000000..14bacdf
--- /dev/null
+++ b/extensions/catalogs/callbacks.h
@@ -0,0 +1,39 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef CALLBACKS_H
+#define CALLBACKS_H
+
+#include <gthumb.h>
+#include "gth-catalog.h"
+
+void catalogs__gth_browser_construct_cb (GthBrowser *browser);
+void catalogs__gth_browser_update_sensitivity_cb (GthBrowser *browser);
+void catalogs__gth_browser_folder_tree_popup_before_cb (GthBrowser *browser,
+ GthFileSource *file_source,
+ GFile *folder);
+GthCatalog * catalogs__gth_catalog_load_from_data_cb (const void *buffer);
+void catalogs__gth_browser_load_location_after_cb (GthBrowser *browser,
+ GFile *location,
+ const GError *error);
+
+#endif /* CALLBACKS_H */
diff --git a/extensions/catalogs/catalogs.extension.in.in b/extensions/catalogs/catalogs.extension.in.in
new file mode 100644
index 0000000..a6c0868
--- /dev/null
+++ b/extensions/catalogs/catalogs.extension.in.in
@@ -0,0 +1,10 @@
+[Extension]
+_Name=Catalogs
+_Description=Allow to create image collections.
+Authors=Paolo Bacchilega <paobac src gnome org>
+Copyright=Copyright © 2008 Paolo Bacchilega
+Version=1
+
+[Loader]
+Type=module
+File=%LIBRARY%
diff --git a/extensions/catalogs/data/Makefile.am b/extensions/catalogs/data/Makefile.am
new file mode 100644
index 0000000..4d5385d
--- /dev/null
+++ b/extensions/catalogs/data/Makefile.am
@@ -0,0 +1,2 @@
+SUBDIRS = ui
+-include $(top_srcdir)/git.mk
diff --git a/extensions/catalogs/data/ui/Makefile.am b/extensions/catalogs/data/ui/Makefile.am
new file mode 100644
index 0000000..c132892
--- /dev/null
+++ b/extensions/catalogs/data/ui/Makefile.am
@@ -0,0 +1,5 @@
+uidir = $(datadir)/gthumb/ui
+ui_DATA = add-to-catalog.ui
+EXTRA_DIST = $(ui_DATA)
+
+-include $(top_srcdir)/git.mk
diff --git a/extensions/catalogs/data/ui/add-to-catalog.ui b/extensions/catalogs/data/ui/add-to-catalog.ui
new file mode 100644
index 0000000..719d772
--- /dev/null
+++ b/extensions/catalogs/data/ui/add-to-catalog.ui
@@ -0,0 +1,192 @@
+<?xml version="1.0"?>
+<interface>
+ <requires lib="gtk+" version="2.14"/>
+ <!-- interface-naming-policy project-wide -->
+ <object class="GtkDialog" id="add_to_catalog_dialog">
+ <property name="width_request">400</property>
+ <property name="height_request">350</property>
+ <property name="border_width">5</property>
+ <property name="type_hint">normal</property>
+ <property name="has_separator">False</property>
+ <child internal-child="vbox">
+ <object class="GtkVBox" id="dialog-vbox1">
+ <property name="visible">True</property>
+ <property name="spacing">2</property>
+ <child>
+ <object class="GtkVBox" id="vbox1">
+ <property name="visible">True</property>
+ <property name="border_width">5</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkHBox" id="hbox2">
+ <property name="visible">True</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkVBox" id="catalog_list_container0">
+ <property name="visible">True</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="catalogs_label">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">C_atalogs:</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="catalog_list_container">
+ <property name="visible">True</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkScrolledWindow" id="catalog_list_scrolled_window">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">automatic</property>
+ <property name="vscrollbar_policy">automatic</property>
+ <property name="shadow_type">in</property>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox2">
+ <property name="visible">True</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkButton" id="new_catalog_button">
+ <property name="label" translatable="yes">_New Catalog</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="new_library_button">
+ <property name="label" translatable="yes">New _Library</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="pack_type">end</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="view_destination_checkbutton">
+ <property name="label" translatable="yes">_View the destination</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child internal-child="action_area">
+ <object class="GtkHButtonBox" id="dialog-action_area1">
+ <property name="visible">True</property>
+ <property name="layout_style">end</property>
+ <child>
+ <object class="GtkButton" id="cancel_button">
+ <property name="label" translatable="yes">gtk-cancel</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_stock">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="add_button">
+ <property name="label" translatable="yes">gtk-add</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_stock">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button1">
+ <property name="label" translatable="yes">gtk-help</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_stock">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ <property name="secondary">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="pack_type">end</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="0">cancel_button</action-widget>
+ <action-widget response="0">add_button</action-widget>
+ <action-widget response="0">button1</action-widget>
+ </action-widgets>
+ </object>
+</interface>
diff --git a/extensions/catalogs/dlg-add-to-catalog.c b/extensions/catalogs/dlg-add-to-catalog.c
new file mode 100644
index 0000000..05e5952
--- /dev/null
+++ b/extensions/catalogs/dlg-add-to-catalog.c
@@ -0,0 +1,406 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2001-2009 The Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <gthumb.h>
+#include "gth-catalog.h"
+#include "gth-file-source-catalogs.h"
+
+
+#define GET_WIDGET(name) _gtk_builder_get_widget (data->builder, (name))
+
+
+typedef struct {
+ GthBrowser *browser;
+ GtkBuilder *builder;
+ GtkWidget *dialog;
+ GtkWidget *source_tree;
+ GList *files;
+ GthFileData *selected_catalog;
+ GthFileSource *file_source;
+ GFile *gio_file;
+ GthCatalog *catalog;
+ char *buffer;
+ gsize length;
+} DialogData;
+
+
+static void
+destroy_cb (GtkWidget *widget,
+ DialogData *data)
+{
+ g_free (data->buffer);
+ if (data->catalog != NULL)
+ g_object_unref (data->catalog);
+ if (data->gio_file != NULL)
+ g_object_unref (data->gio_file);
+ _g_object_list_unref (data->files);
+ _g_object_unref (data->selected_catalog);
+ g_object_unref (data->builder);
+ g_object_unref (data->file_source);
+ g_free (data);
+}
+
+
+static GthFileData *
+get_selected_catalog (DialogData *data)
+{
+ GthFileData *file_data = NULL;
+ GFile *file;
+
+ file = gth_folder_tree_get_selected_or_parent (GTH_FOLDER_TREE (data->source_tree));
+ if (file != NULL) {
+ GthFileSource *file_source;
+ GFileInfo *info;
+
+ file_source = gth_main_get_file_source (file);
+ info = gth_file_source_get_file_info (file_source, file);
+ if (g_file_info_get_attribute_boolean (info, "gthumb::no-child"))
+ file_data = gth_file_data_new (file, info);
+
+ g_object_unref (info);
+ g_object_unref (file);
+ }
+
+ return file_data;
+}
+
+
+static void
+catalog_save_done_cb (void *buffer,
+ gsize count,
+ GError *error,
+ gpointer user_data)
+{
+ DialogData *data = user_data;
+
+ if (error != NULL) {
+ _gtk_error_dialog_from_gerror_show (GTK_WINDOW (data->dialog), _("Could not add the files to the catalog"), &error);
+ return;
+ }
+
+ if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (GET_WIDGET ("view_destination_checkbutton"))))
+ gth_browser_go_to (data->browser, data->selected_catalog->file);
+
+ gtk_widget_destroy (data->dialog);
+}
+
+
+static void
+catalog_buffer_ready_cb (void *buffer,
+ gsize count,
+ GError *error,
+ gpointer user_data)
+{
+ DialogData *data = user_data;
+ GList *scan;
+
+ if (error != NULL) {
+ _gtk_error_dialog_from_gerror_show (GTK_WINDOW (data->dialog), _("Could not add the files to the catalog"), &error);
+ return;
+ }
+
+ data->catalog = gth_hook_invoke_get ("gth-catalog-load-from-data", buffer);
+ if (data->catalog == NULL) {
+ error = g_error_new_literal (G_IO_ERROR, G_IO_ERROR_FAILED, _("Invalid file format"));
+ _gtk_error_dialog_from_gerror_show (GTK_WINDOW (data->dialog), _("Could not add the files to the catalog"), &error);
+ return;
+ }
+
+ gth_catalog_load_from_data (data->catalog, buffer, count, &error);
+ if (error != NULL) {
+ _gtk_error_dialog_from_gerror_show (GTK_WINDOW (data->dialog), _("Could not add the files to the catalog"), &error);
+ return;
+ }
+
+ for (scan = data->files; scan; scan = scan->next) {
+ GthFileData *file_to_add = scan->data;
+
+ gth_catalog_insert_file (data->catalog, -1, file_to_add->file);
+ }
+
+ data->buffer = gth_catalog_to_data (data->catalog, &data->length);
+ g_write_file_async (data->gio_file,
+ data->buffer,
+ data->length,
+ G_PRIORITY_DEFAULT,
+ NULL,
+ catalog_save_done_cb,
+ data);
+}
+
+
+static void
+add_button_clicked_cb (GtkWidget *widget,
+ DialogData *data)
+{
+ GthFileSource *catalog_source;
+
+ data->selected_catalog = get_selected_catalog (data);
+ if (data->selected_catalog == NULL)
+ return;
+
+ catalog_source = g_object_new (GTH_TYPE_FILE_SOURCE_CATALOGS, NULL);
+ data->gio_file = gth_file_source_to_gio_file (catalog_source, data->selected_catalog->file);
+ g_load_file_async (data->gio_file,
+ G_PRIORITY_DEFAULT,
+ NULL,
+ catalog_buffer_ready_cb,
+ data);
+
+ g_object_unref (catalog_source);
+}
+
+
+static void
+source_tree_open_cb (GthFolderTree *folder_tree,
+ GFile *file,
+ gpointer user_data)
+{
+ add_button_clicked_cb (NULL, (DialogData *)user_data);
+}
+
+
+static void
+source_tree_selection_changed_cb (GtkTreeSelection *treeselection,
+ gpointer user_data)
+{
+ DialogData *data = user_data;
+ GthFileData *file_data;
+
+ file_data = get_selected_catalog (data);
+ gtk_widget_set_sensitive (GTK_WIDGET (GET_WIDGET ("add_button")), file_data != NULL);
+ _g_object_unref (file_data);
+}
+
+
+static GFile *
+get_catalog_parent (GFile *selected_parent)
+{
+ GFile *parent = NULL;
+
+ if (selected_parent != NULL) {
+ GthFileSource *file_source;
+ GFileInfo *info;
+
+ file_source = gth_main_get_file_source (selected_parent);
+ info = gth_file_source_get_file_info (file_source, selected_parent);
+ if ((g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY) &&
+ ! g_file_info_get_attribute_boolean (info, "gthumb::no-child"))
+ {
+ parent = g_file_dup (selected_parent);
+ }
+ else
+ parent = g_file_get_parent (selected_parent);
+
+ g_object_unref (info);
+ g_object_unref (file_source);
+ }
+ else
+ parent = g_file_new_for_uri ("catalog:///");
+
+ return parent;
+}
+
+
+static void
+new_catalog_button_clicked_cb (GtkWidget *widget,
+ DialogData *data)
+{
+ GFile *selected_parent;
+ GFile *parent;
+ GthFileSource *file_source;
+ GFile *gio_parent;
+ GError *error;
+ GFile *gio_file;
+
+ selected_parent = gth_folder_tree_get_selected_or_parent (GTH_FOLDER_TREE (data->source_tree));
+ if (selected_parent != NULL) {
+ GthFileSource *file_source;
+ GFileInfo *info;
+
+ file_source = gth_main_get_file_source (selected_parent);
+ info = gth_file_source_get_file_info (file_source, selected_parent);
+ if (g_file_info_get_attribute_boolean (info, "gthumb::no-child"))
+ parent = g_file_get_parent (selected_parent);
+ else
+ parent = g_file_dup (selected_parent);
+
+ g_object_unref (info);
+ g_object_unref (file_source);
+ }
+ else
+ parent = g_file_new_for_uri ("catalog:///");
+
+ file_source = gth_main_get_file_source (parent);
+ gio_parent = gth_file_source_to_gio_file (file_source, parent);
+ gio_file = _g_file_create_unique (gio_parent, _("New Catalog"), ".catalog", &error);
+ if (gio_file != NULL) {
+ GFile *file;
+ GList *list;
+ GFileInfo *info;
+ GthFileData *file_data;
+ GList *file_data_list;
+
+ file = gth_catalog_file_from_gio_file (gio_file, NULL);
+ info = gth_file_source_get_file_info (file_source, file);
+ file_data = gth_file_data_new (file, info);
+ file_data_list = g_list_prepend (NULL, file_data);
+ gth_folder_tree_add_children (GTH_FOLDER_TREE (data->source_tree), parent, file_data_list);
+ gth_folder_tree_start_editing (GTH_FOLDER_TREE (data->source_tree), file);
+
+ list = g_list_prepend (NULL, g_object_ref (file));
+ gth_monitor_folder_changed (gth_main_get_default_monitor (),
+ parent,
+ list,
+ GTH_MONITOR_EVENT_CREATED);
+
+ _g_object_list_unref (list);
+ g_list_free (file_data_list);
+ g_object_unref (file_data);
+ g_object_unref (info);
+ g_object_unref (file);
+ }
+ else
+ _gtk_error_dialog_from_gerror_show (GTK_WINDOW (data->dialog), _("Could not create the catalog"), &error);
+
+ g_object_unref (gio_file);
+ g_object_unref (gio_parent);
+ g_object_unref (file_source);
+}
+
+
+static void
+new_library_button_clicked_cb (GtkWidget *widget,
+ DialogData *data)
+{
+ char *display_name;
+ GFile *selected_catalog;
+ GFile *parent;
+ GFile *new_library;
+ GError *error = NULL;
+
+ display_name = _gtk_request_dialog_run (GTK_WINDOW (data->dialog),
+ GTK_DIALOG_MODAL,
+ _("Enter the library name: "),
+ "",
+ 1024,
+ GTK_STOCK_CANCEL,
+ _("C_reate"));
+ if (display_name == NULL)
+ return;
+
+ selected_catalog = gth_folder_tree_get_selected (GTH_FOLDER_TREE (data->source_tree));
+ parent = get_catalog_parent (selected_catalog);
+ new_library = g_file_get_child_for_display_name (parent, display_name, &error);
+
+ if ((new_library != NULL) && (strchr (display_name, '/') != NULL)) {
+ error = g_error_new (G_IO_ERROR, G_IO_ERROR_INVALID_FILENAME, _("The name \"%s\" is not valid because it contains the character \"/\". " "Please use a different name."), display_name);
+ g_object_unref (new_library);
+ new_library = NULL;
+ }
+
+ if (error == NULL) {
+ GFile *gio_file;
+
+ gio_file = gth_file_source_to_gio_file (data->file_source, new_library);
+ g_file_make_directory (new_library, NULL, &error);
+
+ g_object_unref (gio_file);
+ }
+
+ if (error != NULL)
+ _gtk_error_dialog_from_gerror_show (GTK_WINDOW (data->dialog), _("Could not create a new library"), &error);
+
+ if (new_library != NULL)
+ g_object_unref (new_library);
+ g_object_unref (parent);
+ g_object_unref (selected_catalog);
+ g_free (display_name);
+}
+
+
+void
+dlg_add_to_catalog (GthBrowser *browser,
+ GList *list)
+{
+ DialogData *data;
+ GFile *base;
+ GtkTreeSelection *selection;
+
+ data = g_new0 (DialogData, 1);
+ data->browser = browser;
+ data->builder = _gtk_builder_new_from_file ("add-to-catalog.ui", "catalogs");
+ data->files = _g_object_list_ref (list);
+ data->file_source = g_object_new (GTH_TYPE_FILE_SOURCE_CATALOGS, NULL);
+
+ data->dialog = _gtk_builder_get_widget (data->builder, "add_to_catalog_dialog");
+
+ base = g_file_new_for_uri ("catalog:///");
+ data->source_tree = gth_source_tree_new (base);
+ g_object_unref (base);
+
+ gtk_widget_show (data->source_tree);
+ gtk_container_add (GTK_CONTAINER (GET_WIDGET ("catalog_list_scrolled_window")), data->source_tree);
+
+ gtk_label_set_mnemonic_widget (GTK_LABEL (GET_WIDGET ("catalogs_label")), data->source_tree);
+ gtk_widget_set_sensitive (GTK_WIDGET (GET_WIDGET ("add_button")), FALSE);
+
+ /* Set the signals handlers. */
+
+ g_signal_connect (G_OBJECT (data->dialog),
+ "destroy",
+ G_CALLBACK (destroy_cb),
+ data);
+ g_signal_connect_swapped (G_OBJECT (GET_WIDGET ("cancel_button")),
+ "clicked",
+ G_CALLBACK (gtk_widget_destroy),
+ G_OBJECT (data->dialog));
+ g_signal_connect (G_OBJECT (data->source_tree),
+ "open",
+ G_CALLBACK (source_tree_open_cb),
+ data);
+ g_signal_connect (G_OBJECT (GET_WIDGET ("add_button")),
+ "clicked",
+ G_CALLBACK (add_button_clicked_cb),
+ data);
+ g_signal_connect (G_OBJECT (GET_WIDGET ("new_catalog_button")),
+ "clicked",
+ G_CALLBACK (new_catalog_button_clicked_cb),
+ data);
+ g_signal_connect (G_OBJECT (GET_WIDGET ("new_library_button")),
+ "clicked",
+ G_CALLBACK (new_library_button_clicked_cb),
+ data);
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (data->source_tree));
+ g_signal_connect (selection,
+ "changed",
+ G_CALLBACK (source_tree_selection_changed_cb),
+ data);
+
+ /* run dialog. */
+
+ gtk_window_set_transient_for (GTK_WINDOW (data->dialog), GTK_WINDOW (browser));
+ gtk_window_set_modal (GTK_WINDOW (data->dialog), TRUE);
+ gtk_widget_show (data->dialog);
+}
diff --git a/extensions/catalogs/dlg-add-to-catalog.h b/extensions/catalogs/dlg-add-to-catalog.h
new file mode 100644
index 0000000..e66a7ee
--- /dev/null
+++ b/extensions/catalogs/dlg-add-to-catalog.h
@@ -0,0 +1,34 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2001-2009 The Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef DLG_CATALOG_H
+#define DLG_CATALOG_H
+
+#include <glib.h>
+#include <gthumb.h>
+
+void dlg_add_to_catalog (GthBrowser *browser,
+ GList *list);
+void dlg_move_to_catalog_directory (GthBrowser *browser,
+ char *catalog_path);
+
+#endif /* DLG_CATALOG_H */
diff --git a/extensions/catalogs/gth-catalog.c b/extensions/catalogs/gth-catalog.c
new file mode 100644
index 0000000..8db743f
--- /dev/null
+++ b/extensions/catalogs/gth-catalog.c
@@ -0,0 +1,713 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+
+#include <config.h>
+#include <glib/gi18n.h>
+#include <glib.h>
+#include <gthumb.h>
+#include "gth-catalog.h"
+
+
+#define CATALOG_FORMAT "1.0"
+
+
+struct _GthCatalogPrivate {
+ GthCatalogType type;
+ GFile *file;
+ GList *file_list;
+ gboolean active;
+ GCancellable *cancellable;
+};
+
+
+static gpointer parent_class = NULL;
+
+
+static void
+gth_catalog_finalize (GObject *object)
+{
+ GthCatalog *catalog = GTH_CATALOG (object);
+
+ if (catalog->priv != NULL) {
+ if (catalog->priv->file != NULL)
+ g_object_unref (catalog->priv->file);
+ _g_object_list_unref (catalog->priv->file_list);
+ g_free (catalog->priv);
+ catalog->priv = NULL;
+ }
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+
+static void
+read_catalog_data_from_xml (GthCatalog *catalog,
+ const char *buffer,
+ gsize count,
+ GError **error)
+{
+ DomDocument *doc;
+
+ gth_catalog_set_file_list (catalog, NULL);
+
+ doc = dom_document_new ();
+ if (dom_document_load (doc, buffer, count, error)) {
+ DomElement *root;
+ DomElement *child;
+
+ root = DOM_ELEMENT (doc)->first_child;
+ for (child = root->first_child; child; child = child->next_sibling) {
+ if (g_strcmp0 (child->tag_name, "files") == 0) {
+ DomElement *file;
+
+ for (file = child->first_child; file; file = file->next_sibling) {
+ const char *uri;
+
+ uri = dom_element_get_attribute (file, "uri");
+ if (uri != NULL)
+ catalog->priv->file_list = g_list_prepend (catalog->priv->file_list, g_file_new_for_uri (uri));
+ }
+ }
+ }
+ catalog->priv->file_list = g_list_reverse (catalog->priv->file_list);
+ }
+
+ g_object_unref (doc);
+}
+
+
+static void
+read_catalog_data_old_format (GthCatalog *catalog,
+ const char *buffer,
+ gsize count)
+{
+ GInputStream *mem_stream;
+ GDataInputStream *data_stream;
+ gboolean is_search;
+ int list_start;
+ int n_line;
+ char *line;
+
+ mem_stream = g_memory_input_stream_new_from_data (buffer, count, NULL);
+ data_stream = g_data_input_stream_new (mem_stream);
+
+ is_search = (strncmp (buffer, "# Search", 8) == 0);
+ if (is_search)
+ list_start = 10;
+ else
+ list_start = 1;
+
+ gth_catalog_set_file_list (catalog, NULL);
+
+ n_line = 0;
+ while ((line = g_data_input_stream_read_line (data_stream, NULL, NULL, NULL)) != NULL) {
+ n_line++;
+
+ if (is_search) {
+ /* FIXME: read the search metadata here. */
+ }
+
+ if (n_line > list_start) {
+ char *uri;
+
+ uri = g_strndup (line + 1, strlen (line) - 2);
+ catalog->priv->file_list = g_list_prepend (catalog->priv->file_list, g_file_new_for_uri (uri));
+
+ g_free (uri);
+ }
+ g_free (line);
+ }
+
+ catalog->priv->file_list = g_list_reverse (catalog->priv->file_list);
+
+ g_object_unref (data_stream);
+ g_object_unref (mem_stream);
+}
+
+
+static void
+base_load_from_data (GthCatalog *catalog,
+ const void *buffer,
+ gsize count,
+ GError **error)
+{
+ char *text_buffer;
+
+ if (buffer == NULL)
+ return;
+
+ text_buffer = (char*) buffer;
+ if (strncmp (text_buffer, "<?xml ", 6) == 0)
+ read_catalog_data_from_xml (catalog, text_buffer, count, error);
+ else
+ read_catalog_data_old_format (catalog, text_buffer, count);
+}
+
+
+static char *
+base_to_data (GthCatalog *catalog,
+ gsize *length)
+{
+ DomDocument *doc;
+ DomElement *root;
+ char *data;
+
+ doc = dom_document_new ();
+ root = dom_document_create_element (doc, "catalog",
+ "version", CATALOG_FORMAT,
+ NULL);
+ dom_element_append_child (DOM_ELEMENT (doc), root);
+ if (catalog->priv->file_list != NULL) {
+ DomElement *uri_list;
+ GList *scan;
+
+ uri_list = dom_document_create_element (doc, "files", NULL);
+ dom_element_append_child (root, uri_list);
+
+ for (scan = catalog->priv->file_list; scan; scan = scan->next) {
+ GFile *file = scan->data;
+ char *uri;
+
+ uri = g_file_get_uri (file);
+ dom_element_append_child (DOM_ELEMENT (uri_list), dom_document_create_element (doc, "file", "uri", uri, NULL));
+
+ g_free (uri);
+ }
+ }
+ data = dom_document_dump (doc, length);
+
+ g_object_unref (doc);
+
+ return data;
+}
+
+
+static void
+gth_catalog_class_init (GthCatalogClass *class)
+{
+ GObjectClass *object_class;
+
+ parent_class = g_type_class_peek_parent (class);
+ object_class = (GObjectClass*) class;
+
+ object_class->finalize = gth_catalog_finalize;
+
+ class->load_from_data = base_load_from_data;
+ class->to_data = base_to_data;
+}
+
+
+static void
+gth_catalog_init (GthCatalog *catalog)
+{
+ catalog->priv = g_new0 (GthCatalogPrivate, 1);
+}
+
+
+GType
+gth_catalog_get_type (void)
+{
+ static GType type = 0;
+
+ if (! type) {
+ GTypeInfo type_info = {
+ sizeof (GthCatalogClass),
+ NULL,
+ NULL,
+ (GClassInitFunc) gth_catalog_class_init,
+ NULL,
+ NULL,
+ sizeof (GthCatalog),
+ 0,
+ (GInstanceInitFunc) gth_catalog_init
+ };
+
+ type = g_type_register_static (G_TYPE_OBJECT,
+ "GthCatalog",
+ &type_info,
+ 0);
+ }
+
+ return type;
+}
+
+
+GthCatalog *
+gth_catalog_new (void)
+{
+ return (GthCatalog *) g_object_new (GTH_TYPE_CATALOG, NULL);
+}
+
+
+void
+gth_catalog_set_file (GthCatalog *catalog,
+ GFile *file)
+{
+ if (catalog->priv->file != NULL) {
+ g_object_unref (catalog->priv->file);
+ catalog->priv->file = NULL;
+ }
+
+ if (file != NULL)
+ catalog->priv->file = g_file_dup (file);
+
+ catalog->priv->type = GTH_CATALOG_TYPE_CATALOG;
+}
+
+
+GFile *
+gth_catalog_get_file (GthCatalog *catalog)
+{
+ return catalog->priv->file;
+}
+
+
+void
+gth_catalog_load_from_data (GthCatalog *catalog,
+ const void *buffer,
+ gsize count,
+ GError **error)
+{
+ GTH_CATALOG_GET_CLASS (catalog)->load_from_data (catalog, buffer, count, error);
+}
+
+
+char *
+gth_catalog_to_data (GthCatalog *catalog,
+ gsize *length)
+{
+ return GTH_CATALOG_GET_CLASS (catalog)->to_data (catalog, length);
+}
+
+
+void
+gth_catalog_set_file_list (GthCatalog *catalog,
+ GList *file_list)
+{
+ _g_object_list_unref (catalog->priv->file_list);
+ catalog->priv->file_list = NULL;
+
+ if (file_list != NULL)
+ catalog->priv->file_list = _g_file_list_dup (file_list);
+}
+
+
+GList *
+gth_catalog_get_file_list (GthCatalog *catalog)
+{
+ return catalog->priv->file_list;
+}
+
+
+void
+gth_catalog_insert_file (GthCatalog *catalog,
+ int pos,
+ GFile *file)
+{
+ GList *link;
+
+ link = g_list_find_custom (catalog->priv->file_list, file, (GCompareFunc) _g_file_cmp_uris);
+ if (link == NULL)
+ catalog->priv->file_list = g_list_insert (catalog->priv->file_list, g_file_dup (file), pos);
+}
+
+
+void
+gth_catalog_remove_file (GthCatalog *catalog,
+ GFile *file)
+{
+ GList *link;
+
+ g_return_if_fail (file != NULL);
+
+ link = g_list_find_custom (catalog->priv->file_list, file, (GCompareFunc) _g_file_cmp_uris);
+ if (link != NULL) {
+ catalog->priv->file_list = g_list_remove_link (catalog->priv->file_list, link);
+ g_object_unref ((GFile *) link->data);
+ g_list_free (link);
+ }
+}
+
+
+/* -- gth_catalog_list_async -- */
+
+
+typedef struct {
+ GthCatalog *catalog;
+ const char *attributes;
+ CatalogReadyCallback list_ready_func;
+ gpointer user_data;
+ GList *current_file;
+ GList *files;
+} ListData;
+
+
+static void
+gth_catalog_list_done (ListData *list_data,
+ GError *error)
+{
+ GthCatalog *catalog = list_data->catalog;
+
+ catalog->priv->active = FALSE;
+ if (list_data->list_ready_func != NULL)
+ list_data->list_ready_func (catalog, list_data->files, error, list_data->user_data);
+
+ _g_object_list_unref (list_data->files);
+ g_free (list_data);
+}
+
+
+static void
+catalog_file_info_ready_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ ListData *list_data = user_data;
+ GthCatalog *catalog = list_data->catalog;
+ GFile *file;
+ GFileInfo *info;
+
+ file = (GFile*) source_object;
+ info = g_file_query_info_finish (file, result, NULL);
+ if (info != NULL) {
+ list_data->files = g_list_prepend (list_data->files, gth_file_data_new (file, info));
+ g_object_unref (info);
+ }
+
+ list_data->current_file = list_data->current_file->next;
+ if (list_data->current_file == NULL) {
+ gth_catalog_list_done (list_data, NULL);
+ return;
+ }
+
+ g_file_query_info_async ((GFile *) list_data->current_file->data,
+ list_data->attributes,
+ 0,
+ G_PRIORITY_DEFAULT,
+ catalog->priv->cancellable,
+ catalog_file_info_ready_cb,
+ list_data);
+}
+
+
+static void
+catalog_buffer_ready_cb (void *buffer,
+ gsize count,
+ GError *error,
+ gpointer user_data)
+{
+ ListData *list_data = user_data;
+ GthCatalog *catalog = list_data->catalog;
+
+ if ((error == NULL) && (buffer != NULL)) {
+ gth_catalog_load_from_data (catalog, buffer, count, &error);
+ if (error != NULL) {
+ gth_catalog_list_done (list_data, error);
+ return;
+ }
+
+ list_data->current_file = catalog->priv->file_list;
+ if (list_data->current_file == NULL) {
+ gth_catalog_list_done (list_data, NULL);
+ return;
+ }
+
+ g_file_query_info_async ((GFile *) list_data->current_file->data,
+ list_data->attributes,
+ 0,
+ G_PRIORITY_DEFAULT,
+ catalog->priv->cancellable,
+ catalog_file_info_ready_cb,
+ list_data);
+ }
+ else
+ gth_catalog_list_done (list_data, error);
+}
+
+
+void
+gth_catalog_list_async (GthCatalog *catalog,
+ const char *attributes,
+ GCancellable *cancellable,
+ CatalogReadyCallback ready_func,
+ gpointer user_data)
+{
+ ListData *list_data;
+
+ g_return_if_fail (catalog->priv->file != NULL);
+
+ if (catalog->priv->active) {
+ /* FIXME: object_ready_with_error (catalog, ready_func, user_data, g_error_new (G_IO_ERROR, G_IO_ERROR_PENDING, "Action pending"));*/
+ return;
+ }
+
+ catalog->priv->active = TRUE;
+ catalog->priv->cancellable = cancellable;
+
+ list_data = g_new0 (ListData, 1);
+ list_data->catalog = catalog;
+ list_data->attributes = attributes;
+ list_data->list_ready_func = ready_func;
+ list_data->user_data = user_data;
+
+ g_load_file_async (catalog->priv->file,
+ G_PRIORITY_DEFAULT,
+ catalog->priv->cancellable,
+ catalog_buffer_ready_cb,
+ list_data);
+}
+
+
+void
+gth_catalog_cancel (GthCatalog *catalog)
+{
+ g_cancellable_cancel (catalog->priv->cancellable);
+}
+
+
+/* utils */
+
+
+GFile *
+gth_catalog_get_base (void)
+{
+ char *catalogs_dir;
+ GFile *base;
+
+ catalogs_dir = gth_user_dir_get_file (GTH_DIR_DATA, GTHUMB_DIR, "catalogs", NULL);
+ /*catalogs_dir = g_strdup ("/home/paolo/.gnome2/gthumb/collections");*/
+ base = g_file_new_for_path (catalogs_dir);
+
+ g_free (catalogs_dir);
+
+ return base;
+}
+
+
+GFile *
+gth_catalog_file_to_gio_file (GFile *file)
+{
+ GFile *gio_file = NULL;
+ char *child_uri;
+
+ child_uri = g_file_get_uri (file);
+ if (strncmp (child_uri, "catalog:///", 11) == 0) {
+ const char *query;
+
+ query = strchr (child_uri, '?');
+ if (query != NULL) {
+ char *uri;
+
+ uri = g_uri_unescape_string (query, "");
+ gio_file = g_file_new_for_uri (uri);
+
+ g_free (uri);
+ }
+ else {
+ GFile *base;
+ char *base_uri;
+ const char *part;
+ char *full_uri;
+
+ base = gth_catalog_get_base ();
+ base_uri = g_file_get_uri (base);
+ part = child_uri + 11;
+ full_uri = g_strconcat (base_uri, part ? "/" : NULL, part, NULL);
+ gio_file = g_file_new_for_uri (full_uri);
+
+ g_free (base_uri);
+ g_object_unref (base);
+ }
+ }
+ else
+ gio_file = g_file_dup (file);
+
+ g_free (child_uri);
+
+ return gio_file;
+}
+
+
+GFile *
+gth_catalog_file_from_gio_file (GFile *gio_file,
+ GFile *catalog)
+{
+ GFile *gio_base;
+ GFile *file = NULL;
+ char *path;
+
+ gio_base = gth_catalog_get_base ();
+ if (g_file_equal (gio_base, gio_file)) {
+ g_object_unref (gio_base);
+ return g_file_new_for_uri ("catalog:///");
+ }
+
+ path = g_file_get_relative_path (gio_base, gio_file);
+ if (path != NULL) {
+ GFile *base;
+
+ base = g_file_new_for_uri ("catalog:///");
+ file = _g_file_append_path (base, path);
+
+ g_object_unref (base);
+ }
+ else if (catalog != NULL) {
+ char *catalog_uri;
+ char *file_uri;
+ char *query;
+ char *uri;
+
+ catalog_uri = g_file_get_uri (catalog);
+ file_uri = g_file_get_uri (gio_file);
+ query = g_uri_escape_string (file_uri, G_URI_RESERVED_CHARS_ALLOWED_IN_PATH, FALSE);
+ uri = g_strconcat (file_uri, "?", query, NULL);
+ file = g_file_new_for_uri (uri);
+
+ g_free (uri);
+ g_free (query);
+ g_free (file_uri);
+ g_free (catalog_uri);
+ }
+
+ g_free (path);
+ g_object_unref (gio_base);
+
+ return file;
+}
+
+
+GFile *
+gth_catalog_file_from_relative_path (const char *name,
+ const char *file_extension)
+{
+
+ char *partial_uri;
+ char *uri;
+ GFile *file;
+
+ partial_uri = g_uri_escape_string ((name[0] == '/' ? name + 1 : name), G_URI_RESERVED_CHARS_ALLOWED_IN_PATH, FALSE);
+ uri = g_strconcat ("catalog:///", partial_uri, file_extension, NULL);
+ file = g_file_new_for_uri (uri);
+
+ g_free (uri);
+ g_free (partial_uri);
+
+ return file;
+}
+
+
+char *
+gth_catalog_get_relative_path (GFile *file)
+{
+ GFile *base;
+ char *path;
+
+ base = gth_catalog_get_base ();
+ path = g_file_get_relative_path (base, file);
+
+ g_object_unref (base);
+
+ return path;
+}
+
+
+GIcon *
+gth_catalog_get_icon (GFile *file)
+{
+ char *uri;
+ GIcon *icon;
+
+ uri = g_file_get_uri (file);
+ if (g_str_has_suffix (uri, ".catalog"))
+ icon = g_themed_icon_new ("image-catalog");
+ else
+ icon = g_themed_icon_new ("image-library");
+
+ g_free (uri);
+
+ return icon;
+}
+
+
+char *
+gth_catalog_get_display_name (GFile *file)
+{
+ char *display_name = NULL;
+ char *basename;
+
+ basename = g_file_get_basename (file);
+ if ((basename != NULL) && (strcmp (basename, "/") != 0)) {
+ char *name;
+
+ name = _g_uri_remove_extension (basename);
+ display_name = g_filename_to_utf8 (name, -1, NULL, NULL, NULL);
+
+ g_free (name);
+ }
+ else
+ display_name = g_strdup (_("Catalogs"));
+
+ return display_name;
+}
+
+
+#if 0
+GthCatalogType
+gth_catalog_data_get_type (const void *buffer,
+ gsize count)
+{
+ GthCatalogType type = GTH_CATALOG_TYPE_INVALID;
+ char *text_buffer;
+
+ text_buffer = (char*) buffer;
+ if (strncmp (text_buffer, "<?xml ", 6) == 0) {
+ DomDocument *doc;
+ GError *error = NULL;
+ DomElement *root;
+
+ doc = dom_document_new ();
+ if (! dom_document_load (doc, text_buffer, count, &error)) {
+ g_warning ("%s", error->message);
+ g_clear_error (&error);
+ }
+
+ root = DOM_ELEMENT (doc)->first_child;
+ if (g_strcmp0 (root->tag_name, "catalog") == 0)
+ type = GTH_CATALOG_TYPE_CATALOG;
+ else if (g_strcmp0 (root->tag_name, "search") == 0)
+ type = GTH_CATALOG_TYPE_SEARCH;
+
+ g_object_unref (doc);
+ }
+ else {
+ /* Old catalog format */
+
+ if (strncmp (text_buffer, "# Search", 8) == 0)
+ type = GTH_CATALOG_TYPE_SEARCH;
+ else
+ type = GTH_CATALOG_TYPE_CATALOG;
+ }
+
+ return type;
+}
+#endif
diff --git a/extensions/catalogs/gth-catalog.h b/extensions/catalogs/gth-catalog.h
new file mode 100644
index 0000000..b8cb2f2
--- /dev/null
+++ b/extensions/catalogs/gth-catalog.h
@@ -0,0 +1,111 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GTH_CATALOG_H
+#define GTH_CATALOG_H
+
+#include <glib.h>
+#include <glib-object.h>
+#include <gio/gio.h>
+#include <gthumb.h>
+
+typedef enum {
+ GTH_CATALOG_TYPE_INVALID,
+ GTH_CATALOG_TYPE_CATALOG,
+ GTH_CATALOG_TYPE_EXTENSION
+} GthCatalogType;
+
+#define GTH_TYPE_CATALOG (gth_catalog_get_type ())
+#define GTH_CATALOG(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GTH_TYPE_CATALOG, GthCatalog))
+#define GTH_CATALOG_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), GTH_TYPE_CATALOG, GthCatalogClass))
+#define GTH_IS_CATALOG(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GTH_TYPE_CATALOG))
+#define GTH_IS_CATALOG_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GTH_TYPE_CATALOG))
+#define GTH_CATALOG_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS((o), GTH_TYPE_CATALOG, GthCatalogClass))
+
+typedef struct _GthCatalog GthCatalog;
+typedef struct _GthCatalogPrivate GthCatalogPrivate;
+typedef struct _GthCatalogClass GthCatalogClass;
+
+struct _GthCatalog
+{
+ GObject __parent;
+ GthCatalogPrivate *priv;
+};
+
+struct _GthCatalogClass
+{
+ GObjectClass __parent_class;
+
+ /*< virtual functions >*/
+
+ void (*load_from_data) (GthCatalog *catalog,
+ const void *buffer,
+ gsize count,
+ GError **error);
+ char * (*to_data) (GthCatalog *catalog,
+ gsize *length);
+};
+
+typedef void (*CatalogReadyCallback) (GthCatalog *catalog,
+ GList *files,
+ GError *error,
+ gpointer user_data);
+
+GType gth_catalog_get_type (void) G_GNUC_CONST;
+GthCatalog * gth_catalog_new (void);
+void gth_catalog_set_file (GthCatalog *catalog,
+ GFile *file);
+GFile * gth_catalog_get_file (GthCatalog *catalog);
+void gth_catalog_load_from_data (GthCatalog *catalog,
+ const void *buffer,
+ gsize count,
+ GError **error);
+char * gth_catalog_to_data (GthCatalog *catalog,
+ gsize *length);
+void gth_catalog_set_file_list (GthCatalog *catalog,
+ GList *file_list);
+GList * gth_catalog_get_file_list (GthCatalog *catalog);
+void gth_catalog_insert_file (GthCatalog *catalog,
+ int pos,
+ GFile *file);
+void gth_catalog_remove_file (GthCatalog *catalog,
+ GFile *file);
+void gth_catalog_list_async (GthCatalog *catalog,
+ const char *attributes,
+ GCancellable *cancellable,
+ CatalogReadyCallback ready_func,
+ gpointer user_data);
+void gth_catalog_cancel (GthCatalog *catalog);
+
+/* utils */
+
+GFile * gth_catalog_get_base (void);
+GFile * gth_catalog_file_to_gio_file (GFile *file);
+GFile * gth_catalog_file_from_gio_file (GFile *file,
+ GFile *catalog);
+GFile * gth_catalog_file_from_relative_path (const char *name,
+ const char *file_extension);
+char * gth_catalog_get_relative_path (GFile *file);
+GIcon * gth_catalog_get_icon (GFile *file);
+char * gth_catalog_get_display_name (GFile *file);
+
+#endif /*GTH_CATALOG_H*/
diff --git a/extensions/catalogs/gth-file-source-catalogs.c b/extensions/catalogs/gth-file-source-catalogs.c
new file mode 100644
index 0000000..6c0be92
--- /dev/null
+++ b/extensions/catalogs/gth-file-source-catalogs.c
@@ -0,0 +1,466 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2008 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <string.h>
+#include <glib/gi18n.h>
+#include <glib.h>
+#include <gthumb.h>
+#include "gth-catalog.h"
+#include "gth-file-source-catalogs.h"
+
+
+struct _GthFileSourceCatalogsPrivate
+{
+ GCancellable *cancellable;
+ GList *files;
+ GthCatalog *catalog;
+ ListReady ready_func;
+ gpointer ready_data;
+};
+
+
+static GthFileSourceClass *parent_class = NULL;
+
+
+static GList *
+get_entry_points (GthFileSource *file_source)
+{
+ GList *list = NULL;
+ GFile *file;
+ GFileInfo *info;
+
+ file = g_file_new_for_uri ("catalog:///");
+ info = gth_file_source_get_file_info (file_source, file);
+ list = g_list_append (list, gth_file_data_new (file, info));
+
+ g_object_unref (info);
+ g_object_unref (file);
+
+ return list;
+}
+
+
+static GFile *
+gth_file_source_catalogs_to_gio_file (GthFileSource *file_source,
+ GFile *file)
+{
+ return gth_catalog_file_to_gio_file (file);
+}
+
+
+static void
+update_file_info (GthFileSource *file_source,
+ GFile *catalog_file,
+ GFileInfo *info)
+{
+ char *uri;
+ char *name;
+
+ uri = g_file_get_uri (catalog_file);
+
+ if (g_str_has_suffix (uri, ".gqv") || g_str_has_suffix (uri, ".catalog")) {
+ g_file_info_set_file_type (info, G_FILE_TYPE_DIRECTORY);
+ g_file_info_set_icon (info, g_themed_icon_new ("image-catalog"));
+ g_file_info_set_sort_order (info, 1);
+ g_file_info_set_attribute_boolean (info, "gthumb::no-child", TRUE);
+
+ name = gth_catalog_get_display_name (catalog_file);
+ g_file_info_set_display_name (info, name);
+ g_free (name);
+ }
+ else if (g_str_has_suffix (uri, ".search")) {
+ g_file_info_set_file_type (info, G_FILE_TYPE_DIRECTORY);
+ g_file_info_set_icon (info, g_themed_icon_new ("image-search"));
+ g_file_info_set_sort_order (info, 1);
+ g_file_info_set_attribute_boolean (info, "gthumb::no-child", TRUE);
+
+ name = gth_catalog_get_display_name (catalog_file);
+ g_file_info_set_display_name (info, name);
+ g_free (name);
+ }
+ else {
+ g_file_info_set_file_type (info, G_FILE_TYPE_DIRECTORY);
+ g_file_info_set_icon (info, g_themed_icon_new ("image-library"));
+ g_file_info_set_sort_order (info, 0);
+ g_file_info_set_attribute_boolean (info, "gthumb::no-child", FALSE);
+
+ name = gth_catalog_get_display_name (catalog_file);
+ g_file_info_set_display_name (info, name);
+ g_free (name);
+ }
+
+ g_free (uri);
+}
+
+
+static GFileInfo *
+gth_file_source_catalogs_get_file_info (GthFileSource *file_source,
+ GFile *file)
+{
+ GFile *gio_file;
+ GFileInfo *file_info;
+
+ gio_file = gth_catalog_file_to_gio_file (file);
+ file_info = g_file_query_info (gio_file,
+ "standard::display-name,standard::icon",
+ G_FILE_QUERY_INFO_NONE,
+ NULL,
+ NULL);
+ if (file_info == NULL)
+ file_info = g_file_info_new ();
+ update_file_info (file_source, file, file_info);
+
+ g_object_unref (gio_file);
+
+ return file_info;
+}
+
+
+static void
+list__done_func (GError *error,
+ gpointer user_data)
+{
+ GthFileSourceCatalogs *catalogs = user_data;
+
+ if (G_IS_OBJECT (catalogs))
+ gth_file_source_set_active (GTH_FILE_SOURCE (catalogs), FALSE);
+
+ if (error != NULL) {
+ _g_object_list_unref (catalogs->priv->files);
+ catalogs->priv->files = NULL;
+ g_clear_error (&error);
+ }
+
+ g_object_ref (catalogs);
+ catalogs->priv->ready_func ((GthFileSource *) catalogs,
+ catalogs->priv->files,
+ error,
+ catalogs->priv->ready_data);
+ g_object_unref (catalogs);
+}
+
+
+static GthFileData *
+create_file_data (GthFileSourceCatalogs *catalogs,
+ GFile *file,
+ GFileInfo *info)
+{
+ GthFileData *file_data = NULL;
+ char *uri;
+ GFile *catalog_file;
+
+ uri = g_file_get_uri (file);
+
+ switch (g_file_info_get_file_type (info)) {
+ case G_FILE_TYPE_REGULAR:
+ if (! g_str_has_suffix (uri, ".gqv")
+ && ! g_str_has_suffix (uri, ".catalog")
+ && ! g_str_has_suffix (uri, ".search"))
+ {
+ file_data = gth_file_data_new (file, info);
+ break;
+ }
+
+ catalog_file = gth_catalog_file_from_gio_file (file, NULL);
+ update_file_info (GTH_FILE_SOURCE (catalogs), catalog_file, info);
+ file_data = gth_file_data_new (catalog_file, info);
+
+ g_object_unref (catalog_file);
+ break;
+
+ case G_FILE_TYPE_DIRECTORY:
+ catalog_file = gth_catalog_file_from_gio_file (file, NULL);
+ update_file_info (GTH_FILE_SOURCE (catalogs), catalog_file, info);
+ file_data = gth_file_data_new (catalog_file, info);
+
+ g_object_unref (catalog_file);
+ break;
+
+ default:
+ break;
+ }
+
+ g_free (uri);
+
+ return file_data;
+}
+
+
+static void
+list__for_each_file_func (GFile *file,
+ GFileInfo *info,
+ gpointer user_data)
+{
+ GthFileSourceCatalogs *catalogs = user_data;
+ GthFileData *file_data;
+
+ file_data = create_file_data (catalogs, file, info);
+ if (file_data != NULL)
+ catalogs->priv->files = g_list_prepend (catalogs->priv->files, file_data);
+}
+
+
+static DirOp
+list__start_dir_func (GFile *directory,
+ GFileInfo *info,
+ GError **error,
+ gpointer user_data)
+{
+ return DIR_OP_CONTINUE;
+}
+
+
+static void
+catalog_list_ready_cb (GthCatalog *catalog,
+ GList *files,
+ GError *error,
+ gpointer user_data)
+{
+ GthFileSourceCatalogs *catalogs = user_data;
+
+ g_object_ref (catalogs);
+
+ catalogs->priv->ready_func ((GthFileSource *) catalogs,
+ files,
+ error,
+ catalogs->priv->ready_data);
+
+ gth_catalog_set_file_list (catalogs->priv->catalog, NULL);
+ g_object_unref (catalogs);
+
+ if (G_IS_OBJECT (catalogs))
+ gth_file_source_set_active (GTH_FILE_SOURCE (catalogs), FALSE);
+}
+
+
+static void
+gth_file_source_catalogs_list (GthFileSource *file_source,
+ GFile *folder,
+ const char *attributes,
+ ListReady func,
+ gpointer user_data)
+{
+ GthFileSourceCatalogs *catalogs = (GthFileSourceCatalogs *) file_source;
+ char *uri;
+ GFile *gio_file;
+
+ gth_file_source_set_active (GTH_FILE_SOURCE (catalogs), TRUE);
+ g_cancellable_reset (catalogs->priv->cancellable);
+
+ _g_object_list_unref (catalogs->priv->files);
+ catalogs->priv->files = NULL;
+
+ catalogs->priv->ready_func = func;
+ catalogs->priv->ready_data = user_data;
+
+ uri = g_file_get_uri (folder);
+ gio_file = gth_file_source_to_gio_file (file_source, folder);
+
+ if (g_str_has_suffix (uri, ".gqv")
+ || g_str_has_suffix (uri, ".catalog")
+ || g_str_has_suffix (uri, ".search"))
+ {
+ gth_catalog_set_file (catalogs->priv->catalog, gio_file);
+ gth_catalog_list_async (catalogs->priv->catalog,
+ attributes,
+ catalogs->priv->cancellable,
+ catalog_list_ready_cb,
+ file_source);
+ }
+ else
+ g_directory_foreach_child (gio_file,
+ FALSE,
+ TRUE,
+ attributes,
+ catalogs->priv->cancellable,
+ list__start_dir_func,
+ list__for_each_file_func,
+ list__done_func,
+ file_source);
+
+ g_object_unref (gio_file);
+ g_free (uri);
+}
+
+
+typedef struct {
+ GthFileSourceCatalogs *catalogs;
+ ListReady ready_func;
+ gpointer ready_data;
+} ReadAttributesData;
+
+
+static void
+read_attributes_data_free (ReadAttributesData *data)
+{
+ g_free (data);
+}
+
+
+static void
+info_ready_cb (GList *files,
+ GError *error,
+ gpointer user_data)
+{
+ ReadAttributesData *data = user_data;
+ GthFileSourceCatalogs *catalogs = data->catalogs;
+ GList *scan;
+ GList *result_files;
+
+ g_object_ref (catalogs);
+
+ result_files = NULL;
+ for (scan = files; scan; scan = scan->next) {
+ GthFileData *file_data = scan->data;
+ result_files = g_list_prepend (result_files, create_file_data (catalogs, file_data->file, file_data->info));
+ }
+ result_files = g_list_reverse (result_files);
+
+ data->ready_func ((GthFileSource *) catalogs,
+ result_files,
+ error,
+ data->ready_data);
+
+ _g_object_list_unref (result_files);
+ read_attributes_data_free (data);
+ g_object_unref (catalogs);
+
+ if (G_IS_OBJECT (catalogs))
+ gth_file_source_set_active (GTH_FILE_SOURCE (catalogs), FALSE);
+}
+
+
+static void
+gth_file_source_catalogs_read_attributes (GthFileSource *file_source,
+ GList *files,
+ const char *attributes,
+ ListReady func,
+ gpointer user_data)
+{
+ GthFileSourceCatalogs *catalogs = (GthFileSourceCatalogs *) file_source;
+ ReadAttributesData *data;
+ GList *gio_files;
+
+ gth_file_source_set_active (GTH_FILE_SOURCE (catalogs), TRUE);
+
+ data = g_new0 (ReadAttributesData, 1);
+ data->catalogs = catalogs;
+ data->ready_func = func;
+ data->ready_data = user_data;
+
+ gio_files = gth_file_source_to_gio_file_list (GTH_FILE_SOURCE (catalogs), files);
+ g_query_info_async (gio_files,
+ GTH_FILE_DATA_ATTRIBUTES_WITH_FAST_CONTENT_TYPE,
+ catalogs->priv->cancellable,
+ info_ready_cb,
+ data);
+
+ _g_object_list_unref (gio_files);
+}
+
+
+static void
+gth_file_source_catalogs_cancel (GthFileSource *file_source)
+{
+ GthFileSourceCatalogs *catalogs = (GthFileSourceCatalogs *) file_source;
+
+ if (gth_file_source_is_active (file_source))
+ g_cancellable_cancel (catalogs->priv->cancellable);
+}
+
+
+static void
+gth_file_source_catalogs_finalize (GObject *object)
+{
+ GthFileSourceCatalogs *catalogs = GTH_FILE_SOURCE_CATALOGS (object);
+
+ if (catalogs->priv != NULL) {
+ g_object_unref (catalogs->priv->catalog);
+ _g_object_list_unref (catalogs->priv->files);
+ catalogs->priv->files = NULL;
+
+ g_free (catalogs->priv);
+ catalogs->priv = NULL;
+ }
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+
+static void
+gth_file_source_catalogs_class_init (GthFileSourceCatalogsClass *class)
+{
+ GObjectClass *object_class;
+ GthFileSourceClass *file_source_class;
+
+ parent_class = g_type_class_peek_parent (class);
+ object_class = (GObjectClass*) class;
+ file_source_class = (GthFileSourceClass*) class;
+
+ object_class->finalize = gth_file_source_catalogs_finalize;
+ file_source_class->get_entry_points = get_entry_points;
+ file_source_class->to_gio_file = gth_file_source_catalogs_to_gio_file;
+ file_source_class->get_file_info = gth_file_source_catalogs_get_file_info;
+ file_source_class->list = gth_file_source_catalogs_list;
+ file_source_class->read_attributes = gth_file_source_catalogs_read_attributes;
+ file_source_class->cancel = gth_file_source_catalogs_cancel;
+}
+
+
+static void
+gth_file_source_catalogs_init (GthFileSourceCatalogs *catalogs)
+{
+ gth_file_source_add_scheme (GTH_FILE_SOURCE (catalogs), "catalog://");
+
+ catalogs->priv = g_new0 (GthFileSourceCatalogsPrivate, 1);
+ catalogs->priv->cancellable = g_cancellable_new ();
+ catalogs->priv->catalog = gth_catalog_new ();
+}
+
+
+GType
+gth_file_source_catalogs_get_type (void)
+{
+ static GType type = 0;
+
+ if (! type) {
+ GTypeInfo type_info = {
+ sizeof (GthFileSourceCatalogsClass),
+ NULL,
+ NULL,
+ (GClassInitFunc) gth_file_source_catalogs_class_init,
+ NULL,
+ NULL,
+ sizeof (GthFileSourceCatalogs),
+ 0,
+ (GInstanceInitFunc) gth_file_source_catalogs_init
+ };
+
+ type = g_type_register_static (GTH_TYPE_FILE_SOURCE,
+ "GthFileSourceCatalogs",
+ &type_info,
+ 0);
+ }
+
+ return type;
+}
diff --git a/extensions/catalogs/gth-file-source-catalogs.h b/extensions/catalogs/gth-file-source-catalogs.h
new file mode 100644
index 0000000..aa49ef0
--- /dev/null
+++ b/extensions/catalogs/gth-file-source-catalogs.h
@@ -0,0 +1,52 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2008 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GTH_FILE_SOURCE_CATALOGS_H
+#define GTH_FILE_SOURCE_CATALOGS_H
+
+#include <gthumb.h>
+
+#define GTH_TYPE_FILE_SOURCE_CATALOGS (gth_file_source_catalogs_get_type ())
+#define GTH_FILE_SOURCE_CATALOGS(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GTH_TYPE_FILE_SOURCE_CATALOGS, GthFileSourceCatalogs))
+#define GTH_FILE_SOURCE_CATALOGS_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), GTH_TYPE_FILE_SOURCE_CATALOGS, GthFileSourceCatalogsClass))
+#define GTH_IS_FILE_SOURCE_CATALOGS(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GTH_TYPE_FILE_SOURCE_CATALOGS))
+#define GTH_IS_FILE_SOURCE_CATALOGS_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GTH_TYPE_FILE_SOURCE_CATALOGS))
+#define GTH_FILE_SOURCE_CATALOGS_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS((o), GTH_TYPE_FILE_SOURCE_CATALOGS, GthFileSourceCatalogsClass))
+
+typedef struct _GthFileSourceCatalogs GthFileSourceCatalogs;
+typedef struct _GthFileSourceCatalogsPrivate GthFileSourceCatalogsPrivate;
+typedef struct _GthFileSourceCatalogsClass GthFileSourceCatalogsClass;
+
+struct _GthFileSourceCatalogs
+{
+ GthFileSource __parent;
+ GthFileSourceCatalogsPrivate *priv;
+};
+
+struct _GthFileSourceCatalogsClass
+{
+ GthFileSourceClass __parent_class;
+};
+
+GType gth_file_source_catalogs_get_type (void) G_GNUC_CONST;
+
+#endif /* GTH_FILE_SOURCE_CATALOGS_H */
diff --git a/extensions/catalogs/main.c b/extensions/catalogs/main.c
new file mode 100644
index 0000000..76ca553
--- /dev/null
+++ b/extensions/catalogs/main.c
@@ -0,0 +1,69 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2008 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+
+#include <config.h>
+#include <gtk/gtk.h>
+#include <gthumb.h>
+#include "callbacks.h"
+#include "gth-file-source-catalogs.h"
+
+
+G_MODULE_EXPORT void
+gthumb_extension_activate (void)
+{
+ /**
+ * Called to create the catalog class from the given file data.
+ *
+ * @buffer (const char *): the file data.
+ * @return (GthCatalog *): return a pointer to the object that can
+ * handle the catalog data, or NULL if the data type doesn't match.
+ **/
+ gth_hook_register ("gth-catalog-load-from-data", 1);
+
+ gth_hook_add_callback ("gth-catalog-load-from-data", 10, G_CALLBACK (catalogs__gth_catalog_load_from_data_cb), NULL);
+
+ gth_main_register_file_source (GTH_TYPE_FILE_SOURCE_CATALOGS);
+ gth_hook_add_callback ("gth-browser-construct", 10, G_CALLBACK (catalogs__gth_browser_construct_cb), NULL);
+ gth_hook_add_callback ("gth-browser-update-sensitivity", 10, G_CALLBACK (catalogs__gth_browser_update_sensitivity_cb), NULL);
+ gth_hook_add_callback ("gth-browser-folder-tree-popup-before", 10, G_CALLBACK (catalogs__gth_browser_folder_tree_popup_before_cb), NULL);
+ gth_hook_add_callback ("gth-browser-load-location-after", 10, G_CALLBACK (catalogs__gth_browser_load_location_after_cb), NULL);
+}
+
+
+G_MODULE_EXPORT void
+gthumb_extension_deactivate (void)
+{
+}
+
+
+G_MODULE_EXPORT gboolean
+gthumb_extension_is_configurable (void)
+{
+ return FALSE;
+}
+
+
+G_MODULE_EXPORT void
+gthumb_extension_configure (GtkWindow *parent)
+{
+}
diff --git a/extensions/comments/Makefile.am b/extensions/comments/Makefile.am
new file mode 100644
index 0000000..afa1153
--- /dev/null
+++ b/extensions/comments/Makefile.am
@@ -0,0 +1,38 @@
+SUBDIRS = data
+
+extensiondir = $(libdir)/gthumb-2.0/extensions
+extension_LTLIBRARIES = libcomments.la
+
+libcomments_la_SOURCES = \
+ gth-comment.c \
+ gth-comment.h \
+ gth-edit-comment-page.c \
+ gth-edit-comment-page.h \
+ gth-metadata-provider-comment.c \
+ gth-metadata-provider-comment.h \
+ gth-test-category.c \
+ gth-test-category.h \
+ main.c
+
+libcomments_la_CFLAGS = $(GTHUMB_CFLAGS) -I$(top_srcdir) -I$(top_builddir)/gthumb
+libcomments_la_LDFLAGS = $(EXTENSION_LIBTOOL_FLAGS)
+libcomments_la_LIBADD = $(GTHUMB_LIBS)
+libcomments_la_DEPENDENCIES = $(top_builddir)/gthumb/gthumb$(EXEEXT)
+
+extensioninidir = $(extensiondir)
+extensionini_in_files = comments.extension.in.in
+extensionini_DATA = $(extensionini_in_files:.extension.in.in=.extension)
+
+%.extension.in: %.extension.in.in $(extension_LTLIBRARIES)
+ sed -e "s|%LIBRARY%|`. ./$(extension_LTLIBRARIES) && echo $$dlname`|" \
+ $< > $@
+
+%.extension: %.extension.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*po) ; LC_ALL=C $(INTLTOOL_MERGE) -d -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< $@
+
+EXTRA_DIST = $(extensionini_in_files)
+
+CLEANFILES = $(extensionini_DATA)
+
+DISTCLEANFILES = $(extensionini_DATA)
+
+-include $(top_srcdir)/git.mk
diff --git a/extensions/comments/comments.extension.in.in b/extensions/comments/comments.extension.in.in
new file mode 100644
index 0000000..6fa4f35
--- /dev/null
+++ b/extensions/comments/comments.extension.in.in
@@ -0,0 +1,10 @@
+[Extension]
+_Name=Comments and tags
+_Description=Allow to add comments and tags to images.
+Authors=Paolo Bacchilega <paobac src gnome org>
+Copyright=Copyright © 2009 Paolo Bacchilega
+Version=1
+
+[Loader]
+Type=module
+File=%LIBRARY%
diff --git a/extensions/comments/data/Makefile.am b/extensions/comments/data/Makefile.am
new file mode 100644
index 0000000..4d5385d
--- /dev/null
+++ b/extensions/comments/data/Makefile.am
@@ -0,0 +1,2 @@
+SUBDIRS = ui
+-include $(top_srcdir)/git.mk
diff --git a/extensions/comments/data/ui/Makefile.am b/extensions/comments/data/ui/Makefile.am
new file mode 100644
index 0000000..6457377
--- /dev/null
+++ b/extensions/comments/data/ui/Makefile.am
@@ -0,0 +1,5 @@
+uidir = $(datadir)/gthumb/ui
+ui_DATA = edit-comment-page.ui
+EXTRA_DIST = $(ui_DATA)
+
+-include $(top_srcdir)/git.mk
diff --git a/extensions/comments/data/ui/edit-comment-page.ui b/extensions/comments/data/ui/edit-comment-page.ui
new file mode 100644
index 0000000..98f15e0
--- /dev/null
+++ b/extensions/comments/data/ui/edit-comment-page.ui
@@ -0,0 +1,125 @@
+<?xml version="1.0"?>
+<interface>
+ <requires lib="gtk+" version="2.16"/>
+ <!-- interface-naming-policy project-wide -->
+ <object class="GtkTable" id="content">
+ <property name="visible">True</property>
+ <property name="n_rows">4</property>
+ <property name="n_columns">2</property>
+ <property name="column_spacing">6</property>
+ <property name="row_spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label9">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0</property>
+ <property name="label" translatable="yes">Co_mment:</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">scrolledwindow3</property>
+ </object>
+ <packing>
+ <property name="x_options">GTK_FILL</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label13">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">_Date:</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="place_entry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">●</property>
+ <property name="activates_default">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow" id="scrolledwindow3">
+ <property name="width_request">250</property>
+ <property name="height_request">200</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">automatic</property>
+ <property name="vscrollbar_policy">automatic</property>
+ <property name="shadow_type">in</property>
+ <child>
+ <object class="GtkTextView" id="note_text">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="wrap_mode">word</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label10">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">_Place:</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">place_entry</property>
+ </object>
+ <packing>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options">GTK_FILL</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="date_combobox_container">
+ <property name="visible">True</property>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="date_datetime_container">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+</interface>
diff --git a/extensions/comments/gth-comment.c b/extensions/comments/gth-comment.c
new file mode 100644
index 0000000..efa1edd
--- /dev/null
+++ b/extensions/comments/gth-comment.c
@@ -0,0 +1,501 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 The Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <string.h>
+#include <gthumb.h>
+#include "gth-comment.h"
+
+
+#define COMMENT_VERSION "3.0"
+#define GTH_COMMENT_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GTH_TYPE_COMMENT, GthCommentPrivate))
+
+
+struct _GthCommentPrivate { /* All strings in utf8 format. */
+ char *note;
+ char *place;
+ GPtrArray *categories;
+ GDate *date;
+ GthTime *time_of_day;
+ gboolean changed;
+ gboolean utf8;
+};
+
+static gpointer gth_comment_parent_class = NULL;
+
+
+static void
+gth_comment_free_data (GthComment *self)
+{
+ if (self->priv->place != NULL) {
+ g_free (self->priv->place);
+ self->priv->place = NULL;
+ }
+
+ if (self->priv->note != NULL) {
+ g_free (self->priv->note);
+ self->priv->note = NULL;
+ }
+}
+
+
+static void
+gth_comment_finalize (GObject *obj)
+{
+ GthComment *self = GTH_COMMENT (obj);
+
+ gth_comment_free_data (self);
+ gth_comment_clear_categories (self);
+ g_ptr_array_free (self->priv->categories, TRUE);
+ g_date_free (self->priv->date);
+ gth_time_free (self->priv->time_of_day);
+
+ G_OBJECT_CLASS (gth_comment_parent_class)->finalize (obj);
+}
+
+
+static void
+gth_comment_class_init (GthCommentClass *klass)
+{
+ gth_comment_parent_class = g_type_class_peek_parent (klass);
+ g_type_class_add_private (klass, sizeof (GthCommentPrivate));
+
+ G_OBJECT_CLASS (klass)->finalize = gth_comment_finalize;
+}
+
+
+static void
+gth_comment_instance_init (GthComment *self)
+{
+ self->priv = GTH_COMMENT_GET_PRIVATE (self);
+ self->priv->categories = g_ptr_array_new ();
+ self->priv->date = g_date_new ();
+ self->priv->time_of_day = gth_time_new ();
+}
+
+
+static GObject *
+gth_comment_real_duplicate (GthDuplicable *base)
+{
+ return (GObject *) gth_comment_dup ((GthComment*) base);
+}
+
+
+static void
+gth_comment_gth_duplicable_interface_init (GthDuplicableIface *iface)
+{
+ iface->duplicate = gth_comment_real_duplicate;
+}
+
+
+static DomElement*
+gth_comment_real_create_element (DomDomizable *base,
+ DomDocument *doc)
+{
+ GthComment *self;
+ DomElement *element;
+ char *value;
+ GPtrArray *categories;
+ DomElement *categories_element;
+ int i;
+
+ g_return_val_if_fail (DOM_IS_DOCUMENT (doc), NULL);
+
+ self = GTH_COMMENT (base);
+ element = dom_document_create_element (doc, "comment",
+ "version", COMMENT_VERSION,
+ NULL);
+
+ dom_element_append_child (element, dom_document_create_element_with_text (doc, self->priv->note, "note", NULL));
+ dom_element_append_child (element, dom_document_create_element_with_text (doc, self->priv->place, "place", NULL));
+
+ value = gth_comment_get_time_as_exif_format (self);
+ if (value != NULL) {
+ dom_element_append_child (element, dom_document_create_element (doc, "time", "value", value, NULL));
+ g_free (value);
+ }
+
+ categories = gth_comment_get_categories (self);
+ categories_element = dom_document_create_element (doc, "categories", NULL);
+ dom_element_append_child (element, categories_element);
+ for (i = 0; i < categories->len; i++)
+ dom_element_append_child (categories_element, dom_document_create_element (doc, "category", "value", g_ptr_array_index (categories, i), NULL));
+
+ return element;
+}
+
+
+static void
+gth_comment_real_load_from_element (DomDomizable *base,
+ DomElement *element)
+{
+ GthComment *self;
+ DomElement *node;
+
+ g_return_if_fail (DOM_IS_ELEMENT (element));
+
+ self = GTH_COMMENT (base);
+ gth_comment_reset (self);
+
+ if (g_strcmp0 (dom_element_get_attribute (element, "format"), "2.0") == 0) {
+ for (node = element->first_child; node; node = node->next_sibling) {
+ if (g_strcmp0 (node->tag_name, "Note") == 0)
+ gth_comment_set_note (self, dom_element_get_inner_text (node));
+ else if (g_strcmp0 (node->tag_name, "Place") == 0)
+ gth_comment_set_place (self, dom_element_get_inner_text (node));
+ else if (g_strcmp0 (node->tag_name, "Time") == 0)
+ gth_comment_set_time_from_time_t (self, atol (dom_element_get_inner_text (node)));
+ else if (g_strcmp0 (node->tag_name, "Keywords") == 0) {
+ char **categories;
+ int i;
+
+ categories = g_strsplit (dom_element_get_inner_text (node), ",", -1);
+ for (i = 0; categories[i] != NULL; i++)
+ gth_comment_add_category (self, categories[i]);
+ g_strfreev (categories);
+ }
+ }
+ }
+ else if (g_strcmp0 (dom_element_get_attribute (element, "version"), "3.0") == 0) {
+ for (node = element->first_child; node; node = node->next_sibling) {
+ if (g_strcmp0 (node->tag_name, "note") == 0)
+ gth_comment_set_note (self, dom_element_get_inner_text (node));
+ else if (g_strcmp0 (node->tag_name, "place") == 0)
+ gth_comment_set_place (self, dom_element_get_inner_text (node));
+ else if (g_strcmp0 (node->tag_name, "time") == 0)
+ gth_comment_set_time_from_exif_format (self, dom_element_get_attribute (node, "value"));
+ else if (g_strcmp0 (node->tag_name, "categories") == 0) {
+ DomElement *child;
+
+ for (child = node->first_child; child != NULL; child = child->next_sibling)
+ if (strcmp (child->tag_name, "category") == 0)
+ gth_comment_add_category (self, dom_element_get_attribute (child, "value"));
+ }
+ }
+ }
+}
+
+
+static void
+gth_comment_dom_domizable_interface_init (DomDomizableIface *iface)
+{
+ iface->create_element = gth_comment_real_create_element;
+ iface->load_from_element = gth_comment_real_load_from_element;
+}
+
+
+GType
+gth_comment_get_type (void)
+{
+ static GType gth_comment_type_id = 0;
+
+ if (gth_comment_type_id == 0) {
+ static const GTypeInfo g_define_type_info = {
+ sizeof (GthCommentClass),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) gth_comment_class_init,
+ (GClassFinalizeFunc) NULL,
+ NULL,
+ sizeof (GthComment),
+ 0,
+ (GInstanceInitFunc) gth_comment_instance_init,
+ NULL
+ };
+ static const GInterfaceInfo gth_duplicable_info = {
+ (GInterfaceInitFunc) gth_comment_gth_duplicable_interface_init,
+ (GInterfaceFinalizeFunc) NULL,
+ NULL
+ };
+ static const GInterfaceInfo dom_domizable_info = {
+ (GInterfaceInitFunc) gth_comment_dom_domizable_interface_init,
+ (GInterfaceFinalizeFunc) NULL,
+ NULL
+ };
+ gth_comment_type_id = g_type_register_static (G_TYPE_OBJECT, "GthComment", &g_define_type_info, 0);
+ g_type_add_interface_static (gth_comment_type_id, GTH_TYPE_DUPLICABLE, >h_duplicable_info);
+ g_type_add_interface_static (gth_comment_type_id, DOM_TYPE_DOMIZABLE, &dom_domizable_info);
+ }
+
+ return gth_comment_type_id;
+}
+
+
+GthComment *
+gth_comment_new (void)
+{
+ return g_object_new (GTH_TYPE_COMMENT, NULL);
+}
+
+
+GFile *
+gth_comment_get_comment_file (GFile *file)
+{
+ GFile *parent;
+ char *basename;
+ char *comment_basename;
+ GFile *comment_file;
+
+ parent = g_file_get_parent (file);
+ if (parent == NULL)
+ return NULL;
+
+ basename = g_file_get_basename (file);
+ comment_basename = g_strconcat (basename, ".xml", NULL);
+ comment_file = _g_file_get_child (parent, ".comments", comment_basename, NULL);
+
+ g_free (comment_basename);
+ g_free (basename);
+ g_object_unref (parent);
+
+ return comment_file;
+}
+
+
+GthComment *
+gth_comment_new_for_file (GFile *file,
+ GError **error)
+{
+ GFile *comment_file;
+ GthComment *comment;
+ void *zipped_buffer;
+ gsize zipped_size;
+ void *buffer;
+ gsize size;
+ DomDocument *doc;
+
+ comment_file = gth_comment_get_comment_file (file);
+ if (! g_load_file_in_buffer (comment_file, &zipped_buffer, &zipped_size, error)) {
+ g_object_unref (comment_file);
+ return NULL;
+ }
+ g_object_unref (comment_file);
+
+ if ((zipped_buffer != NULL) && (((char *) zipped_buffer)[0] != '<')) {
+ if (! zlib_decompress_buffer (zipped_buffer, zipped_size, &buffer, &size))
+ return NULL;
+ }
+ else {
+ buffer = zipped_buffer;
+ size = zipped_size;
+
+ zipped_buffer = NULL;
+ }
+
+ comment = gth_comment_new ();
+ doc = dom_document_new ();
+ if (dom_document_load (doc, buffer, size, error)) {
+ dom_domizable_load_from_element (DOM_DOMIZABLE (comment), DOM_ELEMENT (doc)->first_child);
+ }
+ else {
+ buffer = NULL;
+ g_object_unref (comment);
+ comment = NULL;
+ }
+
+ g_free (buffer);
+ g_free (zipped_buffer);
+
+ return comment;
+}
+
+
+char *
+gth_comment_to_data (GthComment *comment,
+ gsize *length)
+{
+ DomDocument *doc;
+ char *data;
+
+ doc = dom_document_new ();
+ dom_element_append_child (DOM_ELEMENT (doc), dom_domizable_create_element (DOM_DOMIZABLE (comment), doc));
+ data = dom_document_dump (doc, length);
+
+ g_object_unref (doc);
+
+ return data;
+}
+
+
+GthComment *
+gth_comment_dup (GthComment *self)
+{
+ GthComment *comment;
+
+ if (self == NULL)
+ return NULL;
+
+ /* FIXME */
+
+ return comment;
+}
+
+
+void
+gth_comment_reset (GthComment *self)
+{
+ gth_comment_free_data (self);
+ gth_comment_clear_categories (self);
+ gth_comment_reset_time (self);
+}
+
+
+void
+gth_comment_set_note (GthComment *comment,
+ const char *value)
+{
+ g_free (comment->priv->note);
+ comment->priv->note = NULL;
+
+ if (value != NULL)
+ comment->priv->note = g_strdup (value);
+}
+
+
+void
+gth_comment_set_place (GthComment *comment,
+ const char *value)
+{
+ g_free (comment->priv->place);
+ comment->priv->place = NULL;
+
+ if (value != NULL)
+ comment->priv->place = g_strdup (value);
+}
+
+
+void
+gth_comment_clear_categories (GthComment *self)
+{
+ g_ptr_array_foreach (self->priv->categories, (GFunc) g_free, NULL);
+ g_ptr_array_free (self->priv->categories, TRUE);
+ self->priv->categories = g_ptr_array_new ();
+}
+
+
+void
+gth_comment_add_category (GthComment *comment,
+ const char *value)
+{
+ g_return_if_fail (value != NULL);
+
+ g_ptr_array_add (comment->priv->categories, g_strdup (value));
+}
+
+
+void
+gth_comment_reset_time (GthComment *self)
+{
+ g_date_clear (self->priv->date, 1);
+ gth_time_clear (self->priv->time_of_day);
+}
+
+
+void
+gth_comment_set_time_from_exif_format (GthComment *comment,
+ const char *value)
+{
+ unsigned int y, m, d, hh, mm, ss;
+
+ gth_comment_reset_time (comment);
+
+ if ((value == NULL) || (*value == '\0'))
+ return;
+
+ if (sscanf (value, "%u:%u:%u %u:%u:%u", &y, &m, &d, &hh, &mm, &ss) != 6) {
+ g_warning ("invalid time format: %s", value);
+ return;
+ }
+
+ g_date_set_dmy (comment->priv->date, d, m, y);
+ gth_time_set_hms (comment->priv->time_of_day, hh, mm, ss, 0);
+}
+
+
+void
+gth_comment_set_time_from_time_t (GthComment *comment,
+ time_t value)
+{
+ struct tm *tm;
+
+ if (value == 0)
+ return;
+
+ tm = localtime (&value);
+ g_date_set_dmy (comment->priv->date, tm->tm_mday, tm->tm_mon + 1, 1900 + tm->tm_year);
+ gth_time_set_hms (comment->priv->time_of_day, tm->tm_hour, tm->tm_min, tm->tm_sec, 0);
+}
+
+
+const char *
+gth_comment_get_note (GthComment *comment)
+{
+ return comment->priv->note;
+}
+
+
+const char *
+gth_comment_get_place (GthComment *comment)
+{
+ return comment->priv->place;
+}
+
+
+GPtrArray *
+gth_comment_get_categories (GthComment *comment)
+{
+ return comment->priv->categories;
+}
+
+
+GDate *
+gth_comment_get_date (GthComment *comment)
+{
+ return comment->priv->date;
+}
+
+
+GthTime *
+gth_comment_get_time_of_day (GthComment *comment)
+{
+ return comment->priv->time_of_day;
+}
+
+
+char *
+gth_comment_get_time_as_exif_format (GthComment *comment)
+{
+ char *s;
+
+ if (! g_date_valid (comment->priv->date))
+ return NULL;
+
+ s = g_strdup_printf ("%04u:%02u:%02u %02u:%02u:%02u",
+ g_date_get_year (comment->priv->date),
+ g_date_get_month (comment->priv->date),
+ g_date_get_day (comment->priv->date),
+ comment->priv->time_of_day->hour,
+ comment->priv->time_of_day->min,
+ comment->priv->time_of_day->sec);
+
+ return s;
+}
diff --git a/extensions/comments/gth-comment.h b/extensions/comments/gth-comment.h
new file mode 100644
index 0000000..1b88e4c
--- /dev/null
+++ b/extensions/comments/gth-comment.h
@@ -0,0 +1,79 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 The Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GTH_COMMENT_H
+#define GTH_COMMENT_H
+
+#include <glib.h>
+#include <glib-object.h>
+#include <gio/gio.h>
+#include <gthumb.h>
+
+#define GTH_TYPE_COMMENT (gth_comment_get_type ())
+#define GTH_COMMENT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTH_TYPE_COMMENT, GthComment))
+#define GTH_COMMENT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTH_TYPE_COMMENT, GthCommentClass))
+#define GTH_IS_COMMENT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTH_TYPE_COMMENT))
+#define GTH_IS_COMMENT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTH_TYPE_COMMENT))
+#define GTH_COMMENT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTH_TYPE_COMMENT, GthCommentClass))
+
+typedef struct _GthComment GthComment;
+typedef struct _GthCommentClass GthCommentClass;
+typedef struct _GthCommentPrivate GthCommentPrivate;
+
+struct _GthComment {
+ GObject parent_instance;
+ GthCommentPrivate *priv;
+};
+
+struct _GthCommentClass {
+ GObjectClass parent_class;
+};
+
+GFile * gth_comment_get_comment_file (GFile *file);
+GType gth_comment_get_type (void);
+GthComment * gth_comment_new (void);
+GthComment * gth_comment_new_for_file (GFile *file,
+ GError **error);
+char * gth_comment_to_data (GthComment *comment,
+ gsize *length);
+GthComment * gth_comment_dup (GthComment *comment);
+void gth_comment_reset (GthComment *comment);
+void gth_comment_set_note (GthComment *comment,
+ const char *value);
+void gth_comment_set_place (GthComment *comment,
+ const char *value);
+void gth_comment_clear_categories (GthComment *comment);
+void gth_comment_add_category (GthComment *comment,
+ const char *value);
+void gth_comment_reset_time (GthComment *comment);
+void gth_comment_set_time_from_exif_format (GthComment *comment,
+ const char *value);
+void gth_comment_set_time_from_time_t (GthComment *comment,
+ time_t value);
+const char * gth_comment_get_note (GthComment *comment);
+const char * gth_comment_get_place (GthComment *comment);
+GPtrArray * gth_comment_get_categories (GthComment *comment);
+GDate * gth_comment_get_date (GthComment *comment);
+GthTime * gth_comment_get_time_of_day (GthComment *comment);
+char * gth_comment_get_time_as_exif_format (GthComment *comment);
+
+#endif /* GTH_COMMENT_H */
diff --git a/extensions/comments/gth-edit-comment-page.c b/extensions/comments/gth-edit-comment-page.c
new file mode 100644
index 0000000..245ec62
--- /dev/null
+++ b/extensions/comments/gth-edit-comment-page.c
@@ -0,0 +1,291 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <glib/gi18n.h>
+#include <gio/gio.h>
+#include "gth-edit-comment-page.h"
+
+
+#define GTH_EDIT_COMMENT_PAGE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GTH_TYPE_EDIT_COMMENT_PAGE, GthEditCommentPagePrivate))
+#define GET_WIDGET(name) _gtk_builder_get_widget (self->priv->builder, (name))
+
+
+typedef enum {
+ NO_DATE = 0,
+ FOLLOWING_DATE,
+ CURRENT_DATE,
+ EMBEDDED_DATE,
+ LAST_MODIFIED_DATE,
+ CREATION_DATE,
+ NO_CHANGE
+} DateOption;
+
+
+static gpointer gth_edit_comment_page_parent_class = NULL;
+
+
+struct _GthEditCommentPagePrivate {
+ GthFileData *file_data;
+ GtkBuilder *builder;
+ GtkWidget *date_combobox;
+ GtkWidget *date_datetime;
+};
+
+
+void
+gth_edit_comment_page_real_set_file (GthEditMetadataPage *base,
+ GthFileData *file_data)
+{
+ GthEditCommentPage *self;
+ GtkTextBuffer *buffer;
+ GthMetadata *metadata;
+
+ self = GTH_EDIT_COMMENT_PAGE (base);
+
+ _g_object_unref (self->priv->file_data);
+ self->priv->file_data = g_object_ref (file_data);
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (GET_WIDGET ("note_text")));
+ metadata = (GthMetadata *) g_file_info_get_attribute_object (file_data->info, "Embedded::Image::Comment");
+ if (metadata != NULL) {
+ GtkTextIter iter;
+
+ gtk_text_buffer_set_text (buffer, gth_metadata_get_formatted (metadata), -1);
+ gtk_text_buffer_get_iter_at_line (buffer, &iter, 0);
+ gtk_text_buffer_place_cursor (buffer, &iter);
+ }
+ else
+ gtk_text_buffer_set_text (buffer, "", -1);
+
+ metadata = (GthMetadata *) g_file_info_get_attribute_object (file_data->info, "Embedded::Image::Location");
+ if (metadata != NULL)
+ gtk_entry_set_text (GTK_ENTRY (GET_WIDGET ("place_entry")), gth_metadata_get_formatted (metadata));
+ else
+ gtk_entry_set_text (GTK_ENTRY (GET_WIDGET ("place_entry")), "");
+
+ metadata = (GthMetadata *) g_file_info_get_attribute_object (file_data->info, "Embedded::Image::DateTime");
+ if (metadata != NULL) {
+ gtk_combo_box_set_active (GTK_COMBO_BOX (self->priv->date_combobox), FOLLOWING_DATE);
+ gtk_entry_set_text (GTK_ENTRY (self->priv->date_datetime), gth_metadata_get_formatted (metadata));
+ /*gtk_widget_set_sensitive (self->priv->date_datetime, TRUE);*/
+ }
+ else {
+ gtk_combo_box_set_active (GTK_COMBO_BOX (self->priv->date_combobox), NO_DATE);
+ gtk_entry_set_text (GTK_ENTRY (self->priv->date_datetime), "");
+ /*gtk_widget_set_sensitive (self->priv->date_datetime, FALSE);*/
+ }
+
+ gtk_widget_grab_focus (GET_WIDGET ("note_text"));
+}
+
+
+void
+gth_edit_comment_page_real_update_info (GthEditMetadataPage *base,
+ GFileInfo *info)
+{
+ GthEditCommentPage *self;
+ GtkTextBuffer *buffer;
+ GtkTextIter start;
+ GtkTextIter end;
+ char *text;
+
+ self = GTH_EDIT_COMMENT_PAGE (base);
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (GET_WIDGET ("note_text")));
+ gtk_text_buffer_get_bounds (buffer, &start, &end);
+ text = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
+ g_file_info_set_attribute_string (self->priv->file_data->info, "comment::note", text);
+ g_free (text);
+
+ g_file_info_set_attribute_string (self->priv->file_data->info, "comment::place", gtk_entry_get_text (GTK_ENTRY (GET_WIDGET ("place_entry"))));
+ g_file_info_set_attribute_string (self->priv->file_data->info, "comment::time", gtk_entry_get_text (GTK_ENTRY (self->priv->date_datetime)));
+}
+
+
+const char *
+gth_edit_comment_page_real_get_name (GthEditMetadataPage *self)
+{
+ return _("Properties");
+}
+
+
+static void
+gth_edit_comment_page_finalize (GObject *object)
+{
+ GthEditCommentPage *self;
+
+ self = GTH_EDIT_COMMENT_PAGE (object);
+
+ _g_object_unref (self->priv->file_data);
+ g_object_unref (self->priv->builder);
+
+ G_OBJECT_CLASS (gth_edit_comment_page_parent_class)->finalize (object);
+}
+
+
+static void
+gth_edit_comment_page_class_init (GthEditCommentPageClass *klass)
+{
+ gth_edit_comment_page_parent_class = g_type_class_peek_parent (klass);
+ g_type_class_add_private (klass, sizeof (GthEditCommentPagePrivate));
+
+ G_OBJECT_CLASS (klass)->finalize = gth_edit_comment_page_finalize;
+}
+
+
+static char *
+get_date_from_option (GthEditCommentPage *self,
+ DateOption option)
+{
+ GTimeVal timeval;
+ const char *date;
+ GthMetadata *metadata;
+
+ _g_time_val_reset (&timeval);
+
+ switch (option) {
+ case NO_DATE:
+ return g_strdup ("");
+ case FOLLOWING_DATE:
+ _g_time_val_from_exif_date (gtk_entry_get_text (GTK_ENTRY (self->priv->date_datetime)), &timeval);
+ break;
+ case CURRENT_DATE:
+ g_get_current_time (&timeval);
+ break;
+ case EMBEDDED_DATE:
+ metadata = (GthMetadata *) g_file_info_get_attribute_object (self->priv->file_data->info, "Embedded::Image::DateTime");
+ if (metadata != NULL)
+ _g_time_val_from_exif_date (gth_metadata_get_raw (metadata), &timeval);
+ else
+ return g_strdup ("");
+ break;
+ case LAST_MODIFIED_DATE:
+ timeval.tv_sec = g_file_info_get_attribute_uint64 (self->priv->file_data->info, "time::modified");
+ timeval.tv_usec = g_file_info_get_attribute_uint32 (self->priv->file_data->info, "time::modified-usec");
+ break;
+ case CREATION_DATE:
+ timeval.tv_sec = g_file_info_get_attribute_uint64 (self->priv->file_data->info, "time::created");
+ timeval.tv_usec = g_file_info_get_attribute_uint32 (self->priv->file_data->info, "time::created-usec");
+ break;
+ case NO_CHANGE:
+ date = g_file_info_get_attribute_string (self->priv->file_data->info, "comment::time");
+ if (date != NULL)
+ _g_time_val_from_exif_date (date, &timeval);
+ else
+ return g_strdup ("");
+ break;
+ }
+
+ return _g_time_val_to_exif_date (&timeval);
+}
+
+
+static void
+date_combobox_changed_cb (GtkComboBox *widget,
+ gpointer user_data)
+{
+ GthEditCommentPage *self = user_data;
+ char *value;
+
+ value = get_date_from_option (self, gtk_combo_box_get_active (widget));
+ gtk_entry_set_text (GTK_ENTRY (self->priv->date_datetime), value);
+
+ g_free (value);
+}
+
+
+static void
+gth_edit_comment_page_init (GthEditCommentPage *self)
+{
+ self->priv = GTH_EDIT_COMMENT_PAGE_GET_PRIVATE (self);
+
+ gtk_container_set_border_width (GTK_CONTAINER (self), 12);
+
+ self->priv->builder = _gtk_builder_new_from_file ("edit-comment-page.ui", "comments");
+ gtk_box_pack_start (GTK_BOX (self), _gtk_builder_get_widget (self->priv->builder, "content"), TRUE, TRUE, 0);
+
+ self->priv->date_combobox = gtk_combo_box_new_text ();
+ _gtk_combo_box_append_texts (GTK_COMBO_BOX (self->priv->date_combobox),
+ _("No date"),
+ _("The following date"),
+ _("Current date"),
+ _("Date photo taken (from embedded metadata)"),
+ _("Last modified date"),
+ _("File creation date"),
+ _("Do not modify"),
+ NULL);
+ gtk_widget_show (self->priv->date_combobox);
+ gtk_box_pack_start (GTK_BOX (GET_WIDGET ("date_combobox_container")), self->priv->date_combobox, FALSE, FALSE, 0);
+
+ g_signal_connect (self->priv->date_combobox,
+ "changed",
+ G_CALLBACK (date_combobox_changed_cb),
+ self);
+
+ self->priv->date_datetime = gtk_entry_new ();
+ gtk_widget_show (self->priv->date_datetime);
+ gtk_box_pack_start (GTK_BOX (GET_WIDGET ("date_datetime_container")), self->priv->date_datetime, FALSE, FALSE, 0);
+}
+
+
+static void
+gth_edit_comment_page_gth_edit_comment_page_interface_init (GthEditMetadataPageIface *iface)
+{
+ iface->set_file = gth_edit_comment_page_real_set_file;
+ iface->update_info = gth_edit_comment_page_real_update_info;
+ iface->get_name = gth_edit_comment_page_real_get_name;
+}
+
+
+GType
+gth_edit_comment_page_get_type (void)
+{
+ static GType type = 0;
+
+ if (type == 0) {
+ static const GTypeInfo g_define_type_info = {
+ sizeof (GthEditCommentPageClass),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) gth_edit_comment_page_class_init,
+ (GClassFinalizeFunc) NULL,
+ NULL,
+ sizeof (GthEditCommentPage),
+ 0,
+ (GInstanceInitFunc) gth_edit_comment_page_init,
+ NULL
+ };
+ static const GInterfaceInfo gth_edit_comment_page_info = {
+ (GInterfaceInitFunc) gth_edit_comment_page_gth_edit_comment_page_interface_init,
+ (GInterfaceFinalizeFunc) NULL,
+ NULL
+ };
+ type = g_type_register_static (GTK_TYPE_VBOX,
+ "GthEditCommentPage",
+ &g_define_type_info,
+ 0);
+ g_type_add_interface_static (type, GTH_TYPE_EDIT_METADATA_PAGE, >h_edit_comment_page_info);
+ }
+
+ return type;
+}
diff --git a/extensions/comments/gth-edit-comment-page.h b/extensions/comments/gth-edit-comment-page.h
new file mode 100644
index 0000000..5b1ef46
--- /dev/null
+++ b/extensions/comments/gth-edit-comment-page.h
@@ -0,0 +1,54 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GTH_EDIT_COMMENT_PAGE_H
+#define GTH_EDIT_COMMENT_PAGE_H
+
+#include <glib.h>
+#include <glib-object.h>
+#include <gthumb.h>
+
+#define GTH_TYPE_EDIT_COMMENT_PAGE (gth_edit_comment_page_get_type ())
+#define GTH_EDIT_COMMENT_PAGE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GTH_TYPE_EDIT_COMMENT_PAGE, GthEditCommentPage))
+#define GTH_EDIT_COMMENT_PAGE_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), GTH_TYPE_EDIT_COMMENT_PAGE, GthEditCommentPageClass))
+#define GTH_IS_EDIT_COMMENT_PAGE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GTH_TYPE_EDIT_COMMENT_PAGE))
+#define GTH_IS_EDIT_COMMENT_PAGE_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GTH_TYPE_EDIT_COMMENT_PAGE))
+#define GTH_EDIT_COMMENT_PAGE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS((o), GTH_TYPE_EDIT_COMMENT_PAGE, GthEditCommentPageClass))
+
+typedef struct _GthEditCommentPage GthEditCommentPage;
+typedef struct _GthEditCommentPagePrivate GthEditCommentPagePrivate;
+typedef struct _GthEditCommentPageClass GthEditCommentPageClass;
+
+struct _GthEditCommentPage
+{
+ GtkVBox __parent;
+ GthEditCommentPagePrivate *priv;
+};
+
+struct _GthEditCommentPageClass
+{
+ GtkVBoxClass __parent_class;
+};
+
+GType gth_edit_comment_page_get_type (void) G_GNUC_CONST;
+
+#endif /* GTH_EDIT_COMMENT_PAGE_H */
diff --git a/extensions/comments/gth-metadata-provider-comment.c b/extensions/comments/gth-metadata-provider-comment.c
new file mode 100644
index 0000000..28f1069
--- /dev/null
+++ b/extensions/comments/gth-metadata-provider-comment.c
@@ -0,0 +1,247 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <string.h>
+#include <glib/gi18n.h>
+#include <glib.h>
+#include <gthumb.h>
+#include "gth-comment.h"
+#include "gth-metadata-provider-comment.h"
+
+
+#define GTH_METADATA_PROVIDER_COMMENT_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GTH_TYPE_METADATA_PROVIDER_COMMENT, GthMetadataProviderCommentPrivate))
+
+
+struct _GthMetadataProviderCommentPrivate {
+ int dummy;
+};
+
+
+static GthMetadataProviderClass *parent_class = NULL;
+
+
+static void
+set_attribute_from_string (GFileInfo *info,
+ const char *key,
+ const char *value)
+{
+ GthMetadata *metadata;
+
+ metadata = g_object_new (GTH_TYPE_METADATA,
+ "id", key,
+ "raw", value,
+ "formatted", value,
+ NULL);
+ g_file_info_set_attribute_object (info, key, G_OBJECT (metadata));
+}
+
+
+static void
+gth_metadata_provider_comment_read (GthMetadataProvider *self,
+ GthFileData *file_data,
+ const char *attributes)
+{
+ GthComment *comment;
+ GFileAttributeMatcher *matcher;
+
+ comment = gth_comment_new_for_file (file_data->file, NULL);
+ if (comment == NULL)
+ return;
+
+ matcher = g_file_attribute_matcher_new (attributes);
+
+ if (g_file_attribute_matcher_matches (matcher, "comment::note")) {
+ const char *value;
+
+ value = gth_comment_get_note (comment);
+ if (value != NULL) {
+ g_file_info_set_attribute_string (file_data->info, "comment::note", value);
+ set_attribute_from_string (file_data->info, "Embedded::Image::Comment", value);
+ }
+ }
+
+ if (g_file_attribute_matcher_matches (matcher, "comment::place")) {
+ const char *value;
+
+ value = gth_comment_get_place (comment);
+ if (value != NULL) {
+ g_file_info_set_attribute_string (file_data->info, "comment::place", value);
+ set_attribute_from_string (file_data->info, "Embedded::Image::Location", value);
+ }
+ }
+
+ if (g_file_attribute_matcher_matches (matcher, "comment::categories")) {
+ GPtrArray *categories;
+
+ categories = gth_comment_get_categories (comment);
+ if (categories->len > 0) {
+ GObject *value;
+
+ value = (GObject *) gth_string_list_new_from_ptr_array (categories);
+ g_file_info_set_attribute_object (file_data->info, "comment::categories", value);
+ g_object_unref (value);
+ }
+ }
+
+ if (g_file_attribute_matcher_matches (matcher, "comment::time")) {
+ char *comment_time;
+
+ comment_time = gth_comment_get_time_as_exif_format (comment);
+ if (comment_time != NULL) {
+ g_file_info_set_attribute_string (file_data->info, "comment::time", comment_time);
+ set_attribute_from_string (file_data->info, "Embedded::Image::DateTime", comment_time);
+ g_free (comment_time);
+ }
+ }
+
+ g_file_attribute_matcher_unref (matcher);
+ g_object_unref (comment);
+}
+
+
+static void
+gth_metadata_provider_comment_write (GthMetadataProvider *self,
+ GthFileData *file_data,
+ const char *attributes)
+{
+ GFileAttributeMatcher *matcher;
+
+ matcher = g_file_attribute_matcher_new (attributes);
+
+ if (g_file_attribute_matcher_matches (matcher, "comment::*")) {
+ GthComment *comment;
+ char *data;
+ gsize length;
+ GthStringList *categories;
+ GFile *comment_file;
+ GFile *comment_folder;
+
+ comment = gth_comment_new ();
+ gth_comment_set_note (comment, g_file_info_get_attribute_string (file_data->info, "comment::note"));
+ gth_comment_set_place (comment, g_file_info_get_attribute_string (file_data->info, "comment::place"));
+ gth_comment_set_time_from_exif_format (comment, g_file_info_get_attribute_string (file_data->info, "comment::time"));
+
+ categories = (GthStringList *) g_file_info_get_attribute_object (file_data->info, "comment::categories");
+ if (categories != NULL) {
+ GList *list;
+ GList *scan;
+
+ list = gth_string_list_get_list (categories);
+ for (scan = list; scan; scan = scan->next)
+ gth_comment_add_category (comment, (char *) scan->data);
+ }
+
+ data = gth_comment_to_data (comment, &length);
+ comment_file = gth_comment_get_comment_file (file_data->file);
+ comment_folder = g_file_get_parent (comment_file);
+
+ g_file_make_directory (comment_folder, NULL, NULL);
+ g_write_file (comment_file, FALSE, 0, data, length, NULL, NULL);
+
+ g_object_unref (comment_folder);
+ g_object_unref (comment_file);
+ g_free (data);
+ g_object_unref (comment);
+ }
+
+ g_file_attribute_matcher_unref (matcher);
+}
+
+
+static void
+gth_metadata_provider_comment_finalize (GObject *object)
+{
+ /*GthMetadataProviderComment *comment = GTH_METADATA_PROVIDER_COMMENT (object);*/
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+
+static GObject *
+gth_metadata_provider_constructor (GType type,
+ guint n_construct_properties,
+ GObjectConstructParam *construct_properties)
+{
+ GthMetadataProviderClass *klass;
+ GObjectClass *parent_class;
+ GObject *obj;
+ GthMetadataProvider *self;
+
+ klass = GTH_METADATA_PROVIDER_CLASS (g_type_class_peek (GTH_TYPE_METADATA_PROVIDER));
+ parent_class = G_OBJECT_CLASS (g_type_class_peek_parent (klass));
+ obj = parent_class->constructor (type, n_construct_properties, construct_properties);
+ self = GTH_METADATA_PROVIDER (obj);
+
+ g_object_set (self, "readable-attributes", "comment::*", NULL);
+ g_object_set (self, "writable-attributes", "comment::*", NULL);
+
+ return obj;
+}
+
+
+static void
+gth_metadata_provider_comment_class_init (GthMetadataProviderCommentClass *klass)
+{
+ parent_class = g_type_class_peek_parent (klass);
+ g_type_class_add_private (klass, sizeof (GthMetadataProviderCommentPrivate));
+
+ G_OBJECT_CLASS (klass)->finalize = gth_metadata_provider_comment_finalize;
+ G_OBJECT_CLASS (klass)->constructor = gth_metadata_provider_constructor;
+
+ GTH_METADATA_PROVIDER_CLASS (klass)->read = gth_metadata_provider_comment_read;
+ GTH_METADATA_PROVIDER_CLASS (klass)->write = gth_metadata_provider_comment_write;
+}
+
+
+static void
+gth_metadata_provider_comment_init (GthMetadataProviderComment *catalogs)
+{
+}
+
+
+GType
+gth_metadata_provider_comment_get_type (void)
+{
+ static GType type = 0;
+
+ if (! type) {
+ GTypeInfo type_info = {
+ sizeof (GthMetadataProviderCommentClass),
+ NULL,
+ NULL,
+ (GClassInitFunc) gth_metadata_provider_comment_class_init,
+ NULL,
+ NULL,
+ sizeof (GthMetadataProviderComment),
+ 0,
+ (GInstanceInitFunc) gth_metadata_provider_comment_init
+ };
+
+ type = g_type_register_static (GTH_TYPE_METADATA_PROVIDER,
+ "GthMetadataProviderComment",
+ &type_info,
+ 0);
+ }
+
+ return type;
+}
diff --git a/extensions/comments/gth-metadata-provider-comment.h b/extensions/comments/gth-metadata-provider-comment.h
new file mode 100644
index 0000000..5404e7a
--- /dev/null
+++ b/extensions/comments/gth-metadata-provider-comment.h
@@ -0,0 +1,54 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GTH_METADATA_PROVIDER_COMMENT_H
+#define GTH_METADATA_PROVIDER_COMMENT_H
+
+#include <glib.h>
+#include <glib-object.h>
+#include <gthumb.h>
+
+#define GTH_TYPE_METADATA_PROVIDER_COMMENT (gth_metadata_provider_comment_get_type ())
+#define GTH_METADATA_PROVIDER_COMMENT(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GTH_TYPE_METADATA_PROVIDER_COMMENT, GthMetadataProviderComment))
+#define GTH_METADATA_PROVIDER_COMMENT_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), GTH_TYPE_METADATA_PROVIDER_COMMENT, GthMetadataProviderCommentClass))
+#define GTH_IS_METADATA_PROVIDER_COMMENT(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GTH_TYPE_METADATA_PROVIDER_COMMENT))
+#define GTH_IS_METADATA_PROVIDER_COMMENT_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GTH_TYPE_METADATA_PROVIDER_COMMENT))
+#define GTH_METADATA_PROVIDER_COMMENT_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS((o), GTH_TYPE_METADATA_PROVIDER_COMMENT, GthMetadataProviderCommentClass))
+
+typedef struct _GthMetadataProviderComment GthMetadataProviderComment;
+typedef struct _GthMetadataProviderCommentPrivate GthMetadataProviderCommentPrivate;
+typedef struct _GthMetadataProviderCommentClass GthMetadataProviderCommentClass;
+
+struct _GthMetadataProviderComment
+{
+ GthMetadataProvider __parent;
+ GthMetadataProviderCommentPrivate *priv;
+};
+
+struct _GthMetadataProviderCommentClass
+{
+ GthMetadataProviderClass __parent_class;
+};
+
+GType gth_metadata_provider_comment_get_type (void) G_GNUC_CONST;
+
+#endif /* GTH_METADATA_PROVIDER_COMMENT_H */
diff --git a/extensions/comments/gth-test-category.c b/extensions/comments/gth-test-category.c
new file mode 100644
index 0000000..ac3f2e2
--- /dev/null
+++ b/extensions/comments/gth-test-category.c
@@ -0,0 +1,397 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2008 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <stdlib.h>
+#include <string.h>
+#include <glib/gi18n.h>
+#include <glib.h>
+#include <gtk/gtk.h>
+#include "gth-test-category.h"
+
+
+typedef struct {
+ char *name;
+ GthTestOp op;
+ gboolean negative;
+} GthOpData;
+
+
+GthOpData category_op_data[] = {
+ { N_("is"), GTH_TEST_OP_EQUAL, FALSE },
+ { N_("is not"), GTH_TEST_OP_EQUAL, TRUE }
+};
+
+
+struct _GthTestCategoryPrivate
+{
+ char *category;
+ GthTestOp op;
+ gboolean negative;
+ gboolean has_focus;
+ GtkWidget *text_entry;
+ GtkWidget *op_combo_box;
+};
+
+
+static gpointer parent_class = NULL;
+static DomDomizableIface *dom_domizable_parent_iface = NULL;
+static GthDuplicableIface *gth_duplicable_parent_iface = NULL;
+
+
+static void
+gth_test_category_finalize (GObject *object)
+{
+ GthTestCategory *test;
+
+ test = GTH_TEST_CATEGORY (object);
+
+ if (test->priv != NULL) {
+ g_free (test->priv->category);
+ g_free (test->priv);
+ test->priv = NULL;
+ }
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+
+static gboolean
+text_entry_focus_in_event_cb (GtkEntry *entry,
+ GdkEventFocus *event,
+ GthTestCategory *test)
+{
+ test->priv->has_focus = TRUE;
+ return FALSE;
+}
+
+
+static gboolean
+text_entry_focus_out_event_cb (GtkEntry *entry,
+ GdkEventFocus *event,
+ GthTestCategory *test)
+{
+ test->priv->has_focus = FALSE;
+ return FALSE;
+}
+
+
+static void
+text_entry_activate_cb (GtkEntry *entry,
+ GthTestCategory *test)
+{
+ gth_test_update_from_control (GTH_TEST (test), NULL);
+ gth_test_changed (GTH_TEST (test));
+}
+
+
+static void
+op_combo_box_changed_cb (GtkComboBox *combo_box,
+ GthTestCategory *test)
+{
+ gth_test_update_from_control (GTH_TEST (test), NULL);
+ gth_test_changed (GTH_TEST (test));
+}
+
+
+static GtkWidget *
+gth_test_category_real_create_control (GthTest *base)
+{
+ GthTestCategory *test;
+ GtkWidget *control;
+ int i, op_idx;
+
+ test = (GthTestCategory *) base;
+
+ control = gtk_hbox_new (FALSE, 6);
+
+ /* text operation combo box */
+
+ test->priv->op_combo_box = gtk_combo_box_new_text ();
+ gtk_widget_show (test->priv->op_combo_box);
+
+ op_idx = 0;
+ for (i = 0; i < G_N_ELEMENTS (category_op_data); i++) {
+ gtk_combo_box_append_text (GTK_COMBO_BOX (test->priv->op_combo_box), _(category_op_data[i].name));
+ if ((category_op_data[i].op == test->priv->op) && (category_op_data[i].negative == test->priv->negative))
+ op_idx = i;
+ }
+ gtk_combo_box_set_active (GTK_COMBO_BOX (test->priv->op_combo_box), op_idx);
+
+ g_signal_connect (G_OBJECT (test->priv->op_combo_box),
+ "changed",
+ G_CALLBACK (op_combo_box_changed_cb),
+ test);
+
+ /* text entry */
+
+ test->priv->text_entry = gtk_entry_new ();
+ /*gtk_entry_set_width_chars (GTK_ENTRY (test->priv->text_entry), 6);*/
+ if (test->priv->category != NULL)
+ gtk_entry_set_text (GTK_ENTRY (test->priv->text_entry), test->priv->category);
+ gtk_widget_show (test->priv->text_entry);
+
+ g_signal_connect (G_OBJECT (test->priv->text_entry),
+ "activate",
+ G_CALLBACK (text_entry_activate_cb),
+ test);
+ g_signal_connect (G_OBJECT (test->priv->text_entry),
+ "focus-in-event",
+ G_CALLBACK (text_entry_focus_in_event_cb),
+ test);
+ g_signal_connect (G_OBJECT (test->priv->text_entry),
+ "focus-out-event",
+ G_CALLBACK (text_entry_focus_out_event_cb),
+ test);
+
+ /**/
+
+ gtk_box_pack_start (GTK_BOX (control), test->priv->op_combo_box, FALSE, FALSE, 0);
+ gtk_box_pack_start (GTK_BOX (control), test->priv->text_entry, FALSE, FALSE, 0);
+
+ return control;
+}
+
+
+static GthMatch
+gth_test_category_real_match (GthTest *test,
+ GthFileData *file)
+{
+ GthTestCategory *test_category;
+ gboolean result = FALSE;
+
+ test_category = GTH_TEST_CATEGORY (test);
+
+ if (test_category->priv->category != NULL) {
+ GthStringList *string_list;
+ GList *list, *scan;
+
+ string_list = (GthStringList *) g_file_info_get_attribute_object (file->info, "comment::categories");
+ if (string_list != NULL)
+ list = gth_string_list_get_list (string_list);
+ else
+ list = NULL;
+
+ for (scan = list; scan; scan = scan->next) {
+ char *category = scan->data;
+
+ if (g_utf8_collate (category, test_category->priv->category) == 0) {
+ result = TRUE;
+ break;
+ }
+ }
+ }
+
+ if (test_category->priv->negative)
+ result = ! result;
+
+ return result ? GTH_MATCH_YES : GTH_MATCH_NO;
+}
+
+
+static DomElement*
+gth_test_category_real_create_element (DomDomizable *base,
+ DomDocument *doc)
+{
+ GthTestCategory *self;
+ DomElement *element;
+
+ g_return_val_if_fail (DOM_IS_DOCUMENT (doc), NULL);
+
+ self = GTH_TEST_CATEGORY (base);
+
+ element = dom_document_create_element (doc, "test",
+ "id", gth_test_get_id (GTH_TEST (self)),
+ NULL);
+
+ if (! gth_test_is_visible (GTH_TEST (self)))
+ dom_element_set_attribute (element, "display", "none");
+
+ dom_element_set_attribute (element, "op", _g_enum_type_get_value (GTH_TYPE_TEST_OP, self->priv->op)->value_nick);
+ if (self->priv->negative)
+ dom_element_set_attribute (element, "negative", self->priv->negative ? "true" : "false");
+ if (self->priv->category != NULL)
+ dom_element_set_attribute (element, "value", self->priv->category);
+
+ return element;
+}
+
+
+static void
+gth_test_category_set_category (GthTestCategory *self,
+ const char *category)
+{
+ g_free (self->priv->category);
+ self->priv->category = NULL;
+ if (category != NULL)
+ self->priv->category = g_strdup (category);
+}
+
+
+static void
+gth_test_category_real_load_from_element (DomDomizable *base,
+ DomElement *element)
+{
+ GthTestCategory *self;
+ const char *value;
+
+ g_return_if_fail (DOM_IS_ELEMENT (element));
+
+ self = GTH_TEST_CATEGORY (base);
+
+ g_object_set (self, "visible", (g_strcmp0 (dom_element_get_attribute (element, "display"), "none") != 0), NULL);
+
+ value = dom_element_get_attribute (element, "op");
+ if (value != NULL)
+ self->priv->op = _g_enum_type_get_value_by_nick (GTH_TYPE_TEST_OP, value)->value;
+
+ self->priv->negative = g_strcmp0 (dom_element_get_attribute (element, "negative"), "true") == 0;
+
+ gth_test_category_set_category (self, NULL);
+ value = dom_element_get_attribute (element, "value");
+ if (value != NULL)
+ gth_test_category_set_category (self, value);
+}
+
+
+static gboolean
+gth_test_category_real_update_from_control (GthTest *base,
+ GError **error)
+{
+ GthTestCategory *self;
+ GthOpData op_data;
+
+ self = GTH_TEST_CATEGORY (base);
+
+ op_data = category_op_data[gtk_combo_box_get_active (GTK_COMBO_BOX (self->priv->op_combo_box))];
+ self->priv->op = op_data.op;
+ self->priv->negative = op_data.negative;
+
+ gth_test_category_set_category (self, gtk_entry_get_text (GTK_ENTRY (self->priv->text_entry)));
+ if (g_strcmp0 (self->priv->category, "") == 0) {
+ if (error != NULL)
+ *error = g_error_new (GTH_TEST_ERROR, 0, _("The test definition is incomplete"));
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+
+static GObject *
+gth_test_category_real_duplicate (GthDuplicable *duplicable)
+{
+ GthTestCategory *test = GTH_TEST_CATEGORY (duplicable);
+ GthTestCategory *new_test;
+
+ new_test = g_object_new (GTH_TYPE_TEST_CATEGORY,
+ "id", gth_test_get_id (GTH_TEST (test)),
+ "display-name", gth_test_get_display_name (GTH_TEST (test)),
+ "visible", gth_test_is_visible (GTH_TEST (test)),
+ NULL);
+ new_test->priv->op = test->priv->op;
+ new_test->priv->negative = test->priv->negative;
+ gth_test_category_set_category (new_test, test->priv->category);
+
+ return (GObject *) new_test;
+}
+
+
+static void
+gth_test_category_class_init (GthTestCategoryClass *class)
+{
+ GObjectClass *object_class;
+ GthTestClass *test_class;
+
+ parent_class = g_type_class_peek_parent (class);
+ object_class = (GObjectClass*) class;
+ test_class = (GthTestClass *) class;
+
+ object_class->finalize = gth_test_category_finalize;
+ test_class->create_control = gth_test_category_real_create_control;
+ test_class->update_from_control = gth_test_category_real_update_from_control;
+ test_class->match = gth_test_category_real_match;
+}
+
+
+static void
+gth_test_category_dom_domizable_interface_init (DomDomizableIface * iface)
+{
+ dom_domizable_parent_iface = g_type_interface_peek_parent (iface);
+ iface->create_element = gth_test_category_real_create_element;
+ iface->load_from_element = gth_test_category_real_load_from_element;
+}
+
+
+static void
+gth_test_category_gth_duplicable_interface_init (GthDuplicableIface *iface)
+{
+ gth_duplicable_parent_iface = g_type_interface_peek_parent (iface);
+ iface->duplicate = gth_test_category_real_duplicate;
+}
+
+
+static void
+gth_test_category_init (GthTestCategory *test)
+{
+ test->priv = g_new0 (GthTestCategoryPrivate, 1);
+}
+
+
+GType
+gth_test_category_get_type (void)
+{
+ static GType type = 0;
+
+ if (! type) {
+ GTypeInfo type_info = {
+ sizeof (GthTestCategoryClass),
+ NULL,
+ NULL,
+ (GClassInitFunc) gth_test_category_class_init,
+ NULL,
+ NULL,
+ sizeof (GthTestCategory),
+ 0,
+ (GInstanceInitFunc) gth_test_category_init
+ };
+ static const GInterfaceInfo dom_domizable_info = {
+ (GInterfaceInitFunc) gth_test_category_dom_domizable_interface_init,
+ (GInterfaceFinalizeFunc) NULL,
+ NULL
+ };
+ static const GInterfaceInfo gth_duplicable_info = {
+ (GInterfaceInitFunc) gth_test_category_gth_duplicable_interface_init,
+ (GInterfaceFinalizeFunc) NULL,
+ NULL
+ };
+
+ type = g_type_register_static (GTH_TYPE_TEST,
+ "GthTestCategory",
+ &type_info,
+ 0);
+ g_type_add_interface_static (type, DOM_TYPE_DOMIZABLE, &dom_domizable_info);
+ g_type_add_interface_static (type, GTH_TYPE_DUPLICABLE, >h_duplicable_info);
+ }
+
+ return type;
+}
diff --git a/extensions/comments/gth-test-category.h b/extensions/comments/gth-test-category.h
new file mode 100644
index 0000000..c93cb81
--- /dev/null
+++ b/extensions/comments/gth-test-category.h
@@ -0,0 +1,54 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GTH_TEST_CATEGORY_H
+#define GTH_TEST_CATEGORY_H
+
+#include <glib-object.h>
+#include <gtk/gtk.h>
+#include <gthumb.h>
+
+#define GTH_TYPE_TEST_CATEGORY (gth_test_category_get_type ())
+#define GTH_TEST_CATEGORY(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GTH_TYPE_TEST_CATEGORY, GthTestCategory))
+#define GTH_TEST_CATEGORY_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), GTH_TYPE_TEST_CATEGORY, GthTestCategoryClass))
+#define GTH_IS_TEST_CATEGORY(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GTH_TYPE_TEST_CATEGORY))
+#define GTH_IS_TEST_CATEGORY_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GTH_TYPE_TEST_CATEGORY))
+#define GTH_TEST_CATEGORY_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS((o), GTH_TYPE_TEST_CATEGORY, GthTestCategoryClass))
+
+typedef struct _GthTestCategory GthTestCategory;
+typedef struct _GthTestCategoryPrivate GthTestCategoryPrivate;
+typedef struct _GthTestCategoryClass GthTestCategoryClass;
+
+struct _GthTestCategory
+{
+ GthTest __parent;
+ GthTestCategoryPrivate *priv;
+};
+
+struct _GthTestCategoryClass
+{
+ GthTestClass __parent_class;
+};
+
+GType gth_test_category_get_type (void) G_GNUC_CONST;
+
+#endif /* GTH_TEST_CATEGORY_H */
diff --git a/extensions/comments/main.c b/extensions/comments/main.c
new file mode 100644
index 0000000..e910b06
--- /dev/null
+++ b/extensions/comments/main.c
@@ -0,0 +1,125 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+
+#include <config.h>
+#include <gtk/gtk.h>
+#include <gthumb.h>
+#include "gth-comment.h"
+#include "gth-edit-comment-page.h"
+#include "gth-metadata-provider-comment.h"
+#include "gth-test-category.h"
+
+
+GthMetadataCategory comments_metadata_category[] = {
+ { "comment", N_("Comment"), 20 },
+ { NULL, NULL, 0 }
+};
+
+
+GthMetadataInfo comments_metadata_info[] = {
+ { "comment::note", N_("Comment"), "comment", 1, GTH_METADATA_ALLOW_NOWHERE },
+ { "comment::place", N_("Place"), "comment", 2, GTH_METADATA_ALLOW_EVERYWHERE },
+ { "comment::time", N_("Date"), "comment", 3, GTH_METADATA_ALLOW_EVERYWHERE },
+ { "comment::categories", N_("Categories"), "comment", 4, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+ { "comment::rating", N_("Rating"), "comment", 5, GTH_METADATA_ALLOW_EVERYWHERE },
+ { NULL, NULL, NULL, 0, 0 }
+};
+
+
+static gint64
+get_comment_for_test (GthTest *test,
+ GthFileData *file,
+ gconstpointer *data)
+{
+ *data = g_file_info_get_attribute_string (file->info, "comment::note");
+ return 0;
+}
+
+
+static gint64
+get_place_for_test (GthTest *test,
+ GthFileData *file,
+ gconstpointer *data)
+{
+ *data = g_file_info_get_attribute_string (file->info, "comment::place");
+ return 0;
+}
+
+
+void
+comments__add_sidecars_cb (GList *sources,
+ GList **sidecars)
+{
+ GList *scan;
+
+ for (scan = sources; scan; scan = scan->next) {
+ GFile *file = (GFile *) scan->data;
+ *sidecars = g_list_prepend (*sidecars, gth_comment_get_comment_file (file));
+ }
+}
+
+
+G_MODULE_EXPORT void
+gthumb_extension_activate (void)
+{
+ gth_main_register_metadata_category (comments_metadata_category);
+ gth_main_register_metadata_info_v (comments_metadata_info);
+ gth_main_register_metadata_provider (GTH_TYPE_METADATA_PROVIDER_COMMENT);
+ gth_main_register_type ("edit-metadata-dialog-page", GTH_TYPE_EDIT_COMMENT_PAGE);
+ gth_main_register_test ("comment::note",
+ GTH_TYPE_TEST_SIMPLE,
+ "display-name", _("Comment"),
+ "data-type", GTH_TEST_DATA_TYPE_STRING,
+ "get-data-func", get_comment_for_test,
+ NULL);
+ gth_main_register_test ("comment::place",
+ GTH_TYPE_TEST_SIMPLE,
+ "display-name", _("Place"),
+ "data-type", GTH_TEST_DATA_TYPE_STRING,
+ "get-data-func", get_place_for_test,
+ NULL);
+ gth_main_register_test ("comment::category",
+ GTH_TYPE_TEST_CATEGORY,
+ "display-name", _("Category"),
+ NULL);
+ gth_hook_add_callback ("add-sidecars", 10, G_CALLBACK (comments__add_sidecars_cb), NULL);
+}
+
+
+G_MODULE_EXPORT void
+gthumb_extension_deactivate (void)
+{
+}
+
+
+G_MODULE_EXPORT gboolean
+gthumb_extension_is_configurable (void)
+{
+ return FALSE;
+}
+
+
+G_MODULE_EXPORT void
+gthumb_extension_configure (GtkWindow *parent)
+{
+}
diff --git a/extensions/exiv2/Makefile.am b/extensions/exiv2/Makefile.am
new file mode 100644
index 0000000..b3cbae2
--- /dev/null
+++ b/extensions/exiv2/Makefile.am
@@ -0,0 +1,35 @@
+if ENABLE_EXIV2
+
+extensiondir = $(libdir)/gthumb-2.0/extensions
+extension_LTLIBRARIES = libexiv2.la
+
+libexiv2_la_SOURCES = \
+ exiv2-utils.h \
+ exiv2-utils.cpp \
+ gth-metadata-provider-exiv2.c \
+ gth-metadata-provider-exiv2.h \
+ main.c
+
+libexiv2_la_CPPFLAGS = $(GTHUMB_CFLAGS) $(EXIV2_CFLAGS) -I$(top_srcdir) -I$(top_builddir)/gthumb
+libexiv2_la_LDFLAGS = $(EXTENSION_LIBTOOL_FLAGS)
+libexiv2_la_LIBADD = $(GTHUMB_LIBS)
+libexiv2_la_DEPENDENCIES = $(top_builddir)/gthumb/gthumb$(EXEEXT)
+
+extensioninidir = $(extensiondir)
+extensionini_in_files = exiv2.extension.in.in
+extensionini_DATA = $(extensionini_in_files:.extension.in.in=.extension)
+
+%.extension.in: %.extension.in.in $(extension_LTLIBRARIES)
+ sed -e "s|%LIBRARY%|`. ./$(extension_LTLIBRARIES) && echo $$dlname`|" \
+ $< > $@
+
+%.extension: %.extension.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*po) ; LC_ALL=C $(INTLTOOL_MERGE) -d -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< $@
+
+EXTRA_DIST = $(extensionini_in_files)
+
+CLEANFILES = $(extensionini_DATA)
+
+DISTCLEANFILES = $(extensionini_DATA)
+
+endif
+-include $(top_srcdir)/git.mk
diff --git a/extensions/exiv2/exiv2-utils.cpp b/extensions/exiv2/exiv2-utils.cpp
new file mode 100644
index 0000000..1ebb9f3
--- /dev/null
+++ b/extensions/exiv2/exiv2-utils.cpp
@@ -0,0 +1,605 @@
+/* -*- Mode: CPP; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2008-2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <exiv2/basicio.hpp>
+#include <exiv2/error.hpp>
+#include <exiv2/image.hpp>
+#include <exiv2/exif.hpp>
+#include <iostream>
+#include <string>
+#include <sstream>
+#include <vector>
+#include <exiv2/xmp.hpp>
+#include <gthumb.h>
+#include "exiv2-utils.h"
+
+using namespace std;
+
+
+/* Some bits of information may be contained in more than one metadata tag.
+ The arrays below define the valid tags for a particular piece of
+ information, in decreasing order of preference (best one first) */
+
+const char *_DATE_TAG_NAMES[] = {
+ "Exif::Image::DateTime",
+ "Xmp::exif::DateTime",
+ "Exif::Photo::DateTimeOriginal",
+ "Xmp::exif::DateTimeOriginal",
+ "Exif::Photo::DateTimeDigitized",
+ "Xmp::exif::DateTimeDigitized",
+ "Xmp::xmp::CreateDate",
+ "Xmp::photoshop::DateCreated",
+ "Xmp::xmp::ModifyDate",
+ "Xmp::xmp::MetadataDate",
+ NULL
+};
+
+const char *_EXPOSURE_TIME_TAG_NAMES[] = {
+ "Exif::Photo::ExposureTime",
+ "Xmp::exif::ExposureTime",
+ "Exif::Photo::ShutterSpeedValue",
+ "Xmp::exif::ShutterSpeedValue",
+ NULL
+};
+
+const char *_EXPOSURE_MODE_TAG_NAMES[] = {
+ "Exif::Photo::ExposureMode",
+ "Xmp::exif::ExposureMode",
+ NULL
+};
+
+const char *_ISOSPEED_TAG_NAMES[] = {
+ "Exif::Photo::ISOSpeedRatings",
+ "Xmp::exif::ISOSpeedRatings",
+ NULL
+};
+
+const char *_APERTURE_TAG_NAMES[] = {
+ "Exif::Photo::ApertureValue",
+ "Xmp::exif::ApertureValue",
+ "Exif::Photo::FNumber",
+ "Xmp::exif::FNumber",
+ NULL
+};
+
+const char *_FOCAL_LENGTH_TAG_NAMES[] = {
+ "Exif::Photo::FocalLength",
+ "Xmp::exif::FocalLength",
+ NULL
+};
+
+const char *_SHUTTER_SPEED_TAG_NAMES[] = {
+ "Exif::Photo::ShutterSpeedValue",
+ "Xmp::exif::ShutterSpeedValue",
+ NULL
+};
+
+const char *_MAKE_TAG_NAMES[] = {
+ "Exif::Image::Make",
+ "Xmp::tiff::Make",
+ NULL
+};
+
+const char *_MODEL_TAG_NAMES[] = {
+ "Exif::Image::Model",
+ "Xmp::tiff::Model",
+ NULL
+};
+
+const char *_FLASH_TAG_NAMES[] = {
+ "Exif::Photo::Flash",
+ "Xmp::exif::Flash",
+ NULL
+};
+
+const char *_ORIENTATION_TAG_NAMES[] = {
+ "Exif::Image::Orientation",
+ "Xmp::tiff::Orientation",
+ NULL
+};
+
+const char *_COMMENT_TAG_NAMES[] = {
+ "Exif::Photo::UserComment",
+ "Exif::Image::ImageDescription",
+ "Xmp::tiff::ImageDescription",
+ "Xmp::dc::description",
+ "Iptc::Application2::Caption",
+ "Iptc::Application2::Headline",
+ NULL
+};
+
+const char *_LOCATION_TAG_NAMES[] = {
+ "Xmp::iptc::Location",
+ "Iptc::Application2::LocationName",
+ NULL
+};
+
+const char *_KEYWORDS_TAG_NAMES[] = {
+ "Xmp::dc::subject",
+ "Xmp::iptc::Keywords",
+ "Iptc::Application2::Keywords",
+ NULL
+};
+
+
+inline static char *
+exiv2_key_from_attribute (const char *attribute)
+{
+ return _g_replace (attribute, "::", ".");
+}
+
+
+inline static char *
+exiv2_key_to_attribute (const char *key)
+{
+ return _g_replace (key, ".", "::");
+}
+
+
+inline static void
+set_file_info (GFileInfo *info,
+ const char *key,
+ const char *description,
+ const char *formatted_value,
+ const char *raw_value,
+ const char *category)
+{
+ char *attribute;
+ GthMetadataInfo *metadata_info;
+ GthMetadata *metadata;
+ char *description_utf8;
+ char *formatted_value_utf8;
+
+ if (_g_utf8_all_spaces (formatted_value))
+ return;
+
+ attribute = exiv2_key_to_attribute (key);
+ description_utf8 = g_locale_to_utf8 (description, -1, NULL, NULL, NULL);
+ formatted_value_utf8 = g_locale_to_utf8 (formatted_value, -1, NULL, NULL, NULL);
+
+/*
+g_print ("%s (%s): %s (%s)\n", key, description, formatted_value, raw_value);
+*/
+
+ metadata_info = gth_main_get_metadata_info (attribute);
+ if ((metadata_info == NULL) && (category != NULL)) {
+ GthMetadataInfo info;
+
+ info.id = attribute;
+ info.display_name = description_utf8;
+ info.category = category;
+ info.sort_order = 500;
+ info.flags = GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW;
+ metadata_info = gth_main_register_metadata_info (&info);
+ }
+
+ if ((metadata_info->display_name == NULL) && (description_utf8 != NULL))
+ metadata_info->display_name = g_strdup (description_utf8);
+
+ metadata = gth_metadata_new ();
+ g_object_set (metadata, "id", key, "description", description_utf8, "formatted", formatted_value_utf8, "raw", raw_value, NULL);
+ g_file_info_set_attribute_object (info, attribute, G_OBJECT (metadata));
+
+ g_object_unref (metadata);
+ g_free (formatted_value_utf8);
+ g_free (description_utf8);
+ g_free (attribute);
+}
+
+
+static void
+set_attribute_from_tagset (GFileInfo *info,
+ const char *attribute,
+ const char *tagset[])
+{
+ GObject *metadata;
+ int i;
+ char *key;
+ char *description;
+ char *formatted_value;
+ char *raw_value;
+
+ metadata = NULL;
+ for (i = 0; tagset[i] != NULL; i++) {
+ metadata = g_file_info_get_attribute_object (info, tagset[i]);
+ if (metadata != NULL)
+ break;
+ }
+
+ if (metadata == NULL)
+ return;
+
+ g_object_get (metadata,
+ "id", &key,
+ "description", &description,
+ "formatted", &formatted_value,
+ "raw", &raw_value,
+ NULL);
+ set_file_info (info, attribute, description, formatted_value, raw_value, NULL);
+}
+
+
+static void
+set_attributes_from_tagsets (GFileInfo *info)
+{
+ /*set_attribute_from_tagset (info, "Exif::Photo::ExposureTime", _EXPOSURE_TIME_TAG_NAMES);
+ set_attribute_from_tagset (info, "Exif::Photo::ExposureMode", _EXPOSURE_MODE_TAG_NAMES);
+ set_attribute_from_tagset (info, "Exif::Photo::ISOSpeedRatings", _ISOSPEED_TAG_NAMES);
+ set_attribute_from_tagset (info, "Exif::Photo::ApertureValue", _APERTURE_TAG_NAMES);
+ set_attribute_from_tagset (info, "Exif::Photo::FocalLength", _FOCAL_LENGTH_TAG_NAMES);
+ set_attribute_from_tagset (info, "Exif::Photo::ShutterSpeedValue", _SHUTTER_SPEED_TAG_NAMES);
+ set_attribute_from_tagset (info, "Exif::Image::Make", _MAKE_TAG_NAMES);
+ set_attribute_from_tagset (info, "Exif::Image::Model", _MODEL_TAG_NAMES);
+ set_attribute_from_tagset (info, "Exif::Photo::Flash", _FLASH_TAG_NAMES);*/
+
+ set_attribute_from_tagset (info, "Embedded::Image::DateTime", _DATE_TAG_NAMES);
+ set_attribute_from_tagset (info, "Embedded::Image::Comment", _COMMENT_TAG_NAMES);
+ set_attribute_from_tagset (info, "Embedded::Image::Location", _LOCATION_TAG_NAMES);
+ set_attribute_from_tagset (info, "Embedded::Image::Keywords", _KEYWORDS_TAG_NAMES);
+ set_attribute_from_tagset (info, "Embedded::Image::Orientation", _ORIENTATION_TAG_NAMES);
+}
+
+
+static const char *
+get_exif_default_category (const Exiv2::Exifdatum &md)
+{
+ if (Exiv2::ExifTags::isMakerIfd(md.ifdId()))
+ return "Exif::MakerNotes";
+
+ switch (md.ifdId()) {
+ case Exiv2::ifd1Id:
+ return "Exif::Thumbnail";
+ case Exiv2::gpsIfdId:
+ return "Exif::GPS";
+ case Exiv2::iopIfdId:
+ return "Exif::Versions";
+ default:
+ break;
+ }
+
+ return "Exif::Other";
+}
+
+
+/*
+ * exiv2_read_metadata
+ * reads metadata from image files
+ * code relies heavily on example1 from the exiv2 website
+ * http://www.exiv2.org/example1.html
+ */
+extern "C"
+gboolean
+exiv2_read_metadata (GFile *file,
+ GFileInfo *info)
+{
+ try {
+ char *path;
+
+ path = g_file_get_path (file);
+ Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open(path);
+ g_free (path);
+
+ if (image.get() == 0) {
+ //die silently if image cannot be opened
+ return FALSE;
+ }
+ image->readMetadata();
+
+ Exiv2::ExifData &exifData = image->exifData();
+ if (! exifData.empty()) {
+ Exiv2::ExifData::const_iterator end = exifData.end();
+ for (Exiv2::ExifData::const_iterator md = exifData.begin(); md != end; ++md) {
+ stringstream value;
+ value << *md;
+
+ stringstream short_name;
+ if (md->ifdId () > Exiv2::ifd1Id) {
+ // Must be a MakerNote - include group name
+ short_name << md->groupName() << "." << md->tagName();
+ }
+ else {
+ // Normal exif tag - just use tag name
+ short_name << md->tagName();
+ }
+
+ set_file_info (info,
+ md->key().c_str(),
+ short_name.str().c_str(),
+ value.str().c_str(),
+ md->toString().c_str(),
+ get_exif_default_category (*md));
+ }
+ }
+
+ Exiv2::IptcData &iptcData = image->iptcData();
+ if (! iptcData.empty()) {
+ Exiv2::IptcData::iterator end = iptcData.end();
+ for (Exiv2::IptcData::iterator md = iptcData.begin(); md != end; ++md) {
+ stringstream value;
+ value << *md;
+
+ stringstream short_name;
+ short_name << md->tagName();
+
+ set_file_info (info,
+ md->key().c_str(),
+ short_name.str().c_str(),
+ value.str().c_str(),
+ md->toString().c_str(),
+ "Iptc");
+ }
+ }
+
+ Exiv2::XmpData &xmpData = image->xmpData();
+ if (! xmpData.empty()) {
+ Exiv2::XmpData::iterator end = xmpData.end();
+ for (Exiv2::XmpData::iterator md = xmpData.begin(); md != end; ++md) {
+ stringstream value;
+ value << *md;
+
+ stringstream short_name;
+ short_name << md->groupName() << "." << md->tagName();
+
+ set_file_info (info,
+ md->key().c_str(),
+ short_name.str().c_str(),
+ value.str().c_str(),
+ md->toString().c_str(),
+ "Xmp::Embedded");
+ }
+ }
+
+ set_attributes_from_tagsets (info);
+ }
+ catch (Exiv2::AnyError& e) {
+ std::cerr << "Caught Exiv2 exception '" << e << "'\n";
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+
+extern "C"
+gboolean
+exiv2_read_sidecar (GFile *file,
+ GFileInfo *info)
+{
+ try {
+ char *path;
+
+ path = g_file_get_path (file);
+ Exiv2::DataBuf buf = Exiv2::readFile(path);
+ g_free (path);
+
+ std::string xmpPacket;
+ xmpPacket.assign(reinterpret_cast<char*>(buf.pData_), buf.size_);
+ Exiv2::XmpData xmpData;
+
+ if (0 != Exiv2::XmpParser::decode(xmpData, xmpPacket))
+ return FALSE;
+
+ if (! xmpData.empty()) {
+ Exiv2::XmpData::iterator end = xmpData.end();
+ for (Exiv2::XmpData::iterator md = xmpData.begin(); md != end; ++md) {
+ stringstream value;
+ value << *md;
+
+ stringstream short_name;
+ short_name << md->groupName() << "." << md->tagName();
+
+ set_file_info (info,
+ md->key().c_str(),
+ short_name.str().c_str(),
+ value.str().c_str(),
+ md->toString().c_str(),
+ "Xmp::Sidecar");
+ }
+ }
+ Exiv2::XmpParser::terminate();
+
+ set_attributes_from_tagsets (info);
+ }
+ catch (Exiv2::AnyError& e) {
+ std::cout << "Caught Exiv2 exception '" << e << "'\n";
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+
+static void
+mandatory_int (Exiv2::ExifData &checkdata,
+ const char *tag,
+ int value)
+{
+ Exiv2::ExifKey key = Exiv2::ExifKey(tag);
+ if (checkdata.findKey(key) == checkdata.end())
+ checkdata[tag] = value;
+}
+
+
+static void
+mandatory_string (Exiv2::ExifData &checkdata,
+ const char *tag,
+ const char *value)
+{
+ Exiv2::ExifKey key = Exiv2::ExifKey(tag);
+ if (checkdata.findKey(key) == checkdata.end())
+ checkdata[tag] = value;
+}
+
+
+extern "C"
+void
+exiv2_write_metadata (SavePixbufData *data)
+{
+ try {
+ Exiv2::Image::AutoPtr image1 = Exiv2::ImageFactory::open ((Exiv2::byte*) data->buffer, data->buffer_size);
+ g_assert (image1.get() != 0);
+
+ image1->readMetadata();
+
+ char **attributes;
+ int i;
+
+ // EXIF Data
+
+ Exiv2::ExifData &ed = image1->exifData();
+ attributes = g_file_info_list_attributes (data->file_data->info, "Exif");
+ for (i = 0; attributes[i] != NULL; i++) {
+ GthMetadata *metadatum;
+ char *key;
+
+ metadatum = (GthMetadata *) g_file_info_get_attribute_object (data->file_data->info, attributes[i]);
+ key = exiv2_key_from_attribute (attributes[i]);
+
+ try {
+ ed[key] = gth_metadata_get_raw (metadatum);
+ }
+ catch (...) {
+ /* we don't care about invalid key errors */
+ }
+
+ g_free (key);
+ }
+ g_strfreev (attributes);
+
+ // Mandatory tags - add if not already present
+
+ mandatory_int (ed, "Exif.Image.XResolution", 72);
+ mandatory_int (ed, "Exif.Image.YResolution", 72);
+ mandatory_int (ed, "Exif.Image.ResolutionUnit", 2);
+ mandatory_int (ed, "Exif.Image.YCbCrPositioning", 1);
+ mandatory_int (ed, "Exif.Photo.ColorSpace", 1);
+ mandatory_string (ed, "Exif.Photo.ExifVersion", "48 50 50 49");
+ mandatory_string (ed, "Exif.Photo.ComponentsConfiguration", "1 2 3 0");
+ mandatory_string (ed, "Exif.Photo.FlashpixVersion", "48 49 48 48");
+
+ // Overwrite the software tag
+
+ ed["Exif.Image.Software"] = PACKAGE " " VERSION;
+
+ // Update the dimension tags with actual image values
+
+ int width = gdk_pixbuf_get_width (data->pixbuf);
+ if (width > 0)
+ ed["Exif.Photo.PixelXDimension"] = width;
+
+ int height = gdk_pixbuf_get_height (data->pixbuf);
+ if (height > 0)
+ ed["Exif.Photo.PixelYDimension"] = height;
+
+ // Update the thumbnail
+
+ Exiv2::ExifThumb thumb(image1->exifData());
+ if ((width > 0) && (height > 0)) {
+ GdkPixbuf *thumb_pixbuf;
+ char *buffer;
+ gsize buffer_size;
+
+ scale_keeping_ratio (&width, &height, 128, 128, FALSE);
+ thumb_pixbuf = _gdk_pixbuf_scale_simple_safe (data->pixbuf, width, height, GDK_INTERP_BILINEAR);
+ if (gdk_pixbuf_save_to_buffer (thumb_pixbuf, &buffer, &buffer_size, "jpeg", NULL, NULL)) {
+ thumb.setJpegThumbnail ((Exiv2::byte*) buffer, buffer_size);
+ ed["Exif.Thumbnail.XResolution"] = 72;
+ ed["Exif.Thumbnail.YResolution"] = 72;
+ ed["Exif.Thumbnail.ResolutionUnit"] = 2;
+ g_free (buffer);
+ }
+ else
+ thumb.erase();
+
+ g_object_unref (thumb_pixbuf);
+ }
+ else
+ thumb.erase();
+
+ // Update the DateTime tag
+
+ GTimeVal current_time;
+ g_get_current_time (¤t_time);
+ char *date_time = _g_time_val_to_exif_date (¤t_time);
+ ed["Exif.Image.DateTime"] = date_time;
+ g_free (date_time);
+
+ // IPTC Data
+
+ Exiv2::IptcData &id = image1->iptcData();
+ attributes = g_file_info_list_attributes (data->file_data->info, "Iptc");
+ for (i = 0; attributes[i] != NULL; i++) {
+ GthMetadata *metadatum = (GthMetadata *) g_file_info_get_attribute_object (data->file_data->info, attributes[i]);
+ char *key = exiv2_key_from_attribute (attributes[i]);
+
+ try {
+ id[key] = gth_metadata_get_raw (metadatum);
+ }
+ catch (...) {
+ /* we don't care about invalid key errors */
+ }
+
+ g_free (key);
+ }
+ g_strfreev (attributes);
+
+ // XMP Data
+
+ Exiv2::XmpData &xd = image1->xmpData();
+ attributes = g_file_info_list_attributes (data->file_data->info, "Xmp");
+ for (i = 0; attributes[i] != NULL; i++) {
+ GthMetadata *metadatum = (GthMetadata *) g_file_info_get_attribute_object (data->file_data->info, attributes[i]);
+ char *key = exiv2_key_from_attribute (attributes[i]);
+
+ // Remove existing tags of the same type.
+ // Seems to be needed for storing category keywords.
+ // Not exactly sure why!
+ Exiv2::XmpData::iterator iter = xd.findKey (Exiv2::XmpKey (key));
+ if (iter != xd.end ())
+ xd.erase (iter);
+
+ try {
+ ed[key] = gth_metadata_get_raw (metadatum);
+ }
+ catch (...) {
+ /* we don't care about invalid key errors */
+ }
+
+ g_free (key);
+ }
+ g_strfreev (attributes);
+
+ // overwrite existing metadata with new metadata
+ image1->writeMetadata();
+
+ Exiv2::BasicIo &io = image1->io();
+ io.open();
+ Exiv2::DataBuf buf = io.read(io.size());
+ g_free (data->buffer);
+ data->buffer = g_memdup (buf.pData_, buf.size_);
+ data->buffer_size = buf.size_;
+ }
+ catch (char *msg) {
+ *data->error = g_error_new_literal (G_IO_ERROR, G_IO_ERROR_FAILED, msg);
+ }
+}
diff --git a/extensions/exiv2/exiv2-utils.h b/extensions/exiv2/exiv2-utils.h
new file mode 100644
index 0000000..7bc2759
--- /dev/null
+++ b/extensions/exiv2/exiv2-utils.h
@@ -0,0 +1,40 @@
+/* -*- Mode: CPP; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2003-2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef EXIV2_UTILS_H
+#define EXIV2_UTILS_H
+
+#include <glib.h>
+#include <gio/gio.h>
+#include <gthumb.h>
+
+G_BEGIN_DECLS
+
+gboolean exiv2_read_metadata (GFile *file,
+ GFileInfo *info);
+gboolean exiv2_read_sidecar (GFile *file,
+ GFileInfo *info);
+void exiv2_write_metadata (SavePixbufData *data);
+
+G_END_DECLS
+
+#endif /* EXIV2_UTILS_H */
diff --git a/extensions/exiv2/exiv2.extension.in.in b/extensions/exiv2/exiv2.extension.in.in
new file mode 100644
index 0000000..00123f5
--- /dev/null
+++ b/extensions/exiv2/exiv2.extension.in.in
@@ -0,0 +1,10 @@
+[Extension]
+_Name=Image Viewer
+_Description=Allows to view images.
+Authors=Paolo Bacchilega <paobac src gnome org>
+Copyright=Copyright © 2009 Paolo Bacchilega
+Version=1
+
+[Loader]
+Type=module
+File=%LIBRARY%
diff --git a/extensions/exiv2/gth-metadata-provider-exiv2.c b/extensions/exiv2/gth-metadata-provider-exiv2.c
new file mode 100644
index 0000000..8270979
--- /dev/null
+++ b/extensions/exiv2/gth-metadata-provider-exiv2.c
@@ -0,0 +1,169 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <string.h>
+#include <glib/gi18n.h>
+#include <glib.h>
+#include <gthumb.h>
+#include "exiv2-utils.h"
+#include "gth-metadata-provider-exiv2.h"
+
+
+#define GTH_METADATA_PROVIDER_EXIV2_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GTH_TYPE_METADATA_PROVIDER_EXIV2, GthMetadataProviderExiv2Private))
+
+
+struct _GthMetadataProviderExiv2Private {
+ int dummy;
+};
+
+
+static GthMetadataProviderClass *parent_class = NULL;
+
+
+static void
+gth_metadata_provider_exiv2_read (GthMetadataProvider *self,
+ GthFileData *file_data,
+ const char *attributes)
+{
+ GFileAttributeMatcher *matcher;
+
+ matcher = g_file_attribute_matcher_new (attributes);
+
+ if (g_file_attribute_matcher_matches (matcher, "Exif::*,Iptc::*,Xmp::*")) {
+ char *uri;
+ char *uri_wo_ext;
+ char *sidecar_uri;
+ GthFileData *sidecar_file_data;
+
+ /* this function is executed in a secondary thread, so calling
+ * slow sync functions, such as obtain_local_file, is not a
+ * problem. */
+
+ exiv2_read_metadata (file_data->file, file_data->info);
+
+ /* sidecar data */
+
+ uri = g_file_get_uri (file_data->file);
+ uri_wo_ext = _g_uri_remove_extension (uri);
+ sidecar_uri = g_strconcat (uri_wo_ext, ".xmp", NULL);
+ sidecar_file_data = gth_file_data_new_for_uri (sidecar_uri, NULL);
+ if (g_file_query_exists (sidecar_file_data->file, NULL)) {
+ gth_file_data_update_info (sidecar_file_data, "time::*");
+ if (g_file_query_exists (sidecar_file_data->file, NULL))
+ exiv2_read_sidecar (sidecar_file_data->file, file_data->info);
+ }
+
+ g_object_unref (sidecar_file_data);
+ g_free (sidecar_uri);
+ g_free (uri_wo_ext);
+ g_free (uri);
+ }
+
+ g_file_attribute_matcher_unref (matcher);
+}
+
+
+static void
+gth_metadata_provider_exiv2_write (GthMetadataProvider *self,
+ GthFileData *file_data,
+ const char *attributes)
+{
+}
+
+
+static void
+gth_metadata_provider_exiv2_finalize (GObject *object)
+{
+ /*GthMetadataProviderExiv2 *comment = GTH_METADATA_PROVIDER_EXIV2 (object);*/
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+
+static GObject *
+gth_metadata_provider_constructor (GType type,
+ guint n_construct_properties,
+ GObjectConstructParam *construct_properties)
+{
+ GthMetadataProviderClass *klass;
+ GObjectClass *parent_class;
+ GObject *obj;
+ GthMetadataProvider *self;
+
+ klass = GTH_METADATA_PROVIDER_CLASS (g_type_class_peek (GTH_TYPE_METADATA_PROVIDER));
+ parent_class = G_OBJECT_CLASS (g_type_class_peek_parent (klass));
+ obj = parent_class->constructor (type, n_construct_properties, construct_properties);
+ self = GTH_METADATA_PROVIDER (obj);
+
+ g_object_set (self, "readable-attributes", "Exif::*,Xmp::*,Iptc::*", NULL);
+
+ return obj;
+}
+
+
+static void
+gth_metadata_provider_exiv2_class_init (GthMetadataProviderExiv2Class *klass)
+{
+ parent_class = g_type_class_peek_parent (klass);
+ g_type_class_add_private (klass, sizeof (GthMetadataProviderExiv2Private));
+
+ G_OBJECT_CLASS (klass)->finalize = gth_metadata_provider_exiv2_finalize;
+ G_OBJECT_CLASS (klass)->constructor = gth_metadata_provider_constructor;
+
+ GTH_METADATA_PROVIDER_CLASS (klass)->read = gth_metadata_provider_exiv2_read;
+ GTH_METADATA_PROVIDER_CLASS (klass)->write = gth_metadata_provider_exiv2_write;
+}
+
+
+static void
+gth_metadata_provider_exiv2_init (GthMetadataProviderExiv2 *catalogs)
+{
+}
+
+
+GType
+gth_metadata_provider_exiv2_get_type (void)
+{
+ static GType type = 0;
+
+ if (! type) {
+ GTypeInfo type_info = {
+ sizeof (GthMetadataProviderExiv2Class),
+ NULL,
+ NULL,
+ (GClassInitFunc) gth_metadata_provider_exiv2_class_init,
+ NULL,
+ NULL,
+ sizeof (GthMetadataProviderExiv2),
+ 0,
+ (GInstanceInitFunc) gth_metadata_provider_exiv2_init
+ };
+
+ type = g_type_register_static (GTH_TYPE_METADATA_PROVIDER,
+ "GthMetadataProviderExiv2",
+ &type_info,
+ 0);
+ }
+
+ return type;
+}
diff --git a/extensions/exiv2/gth-metadata-provider-exiv2.h b/extensions/exiv2/gth-metadata-provider-exiv2.h
new file mode 100644
index 0000000..a6b4c7d
--- /dev/null
+++ b/extensions/exiv2/gth-metadata-provider-exiv2.h
@@ -0,0 +1,54 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GTH_METADATA_PROVIDER_EXIV2_H
+#define GTH_METADATA_PROVIDER_EXIV2_H
+
+#include <glib.h>
+#include <glib-object.h>
+#include <gthumb.h>
+
+#define GTH_TYPE_METADATA_PROVIDER_EXIV2 (gth_metadata_provider_exiv2_get_type ())
+#define GTH_METADATA_PROVIDER_EXIV2(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GTH_TYPE_METADATA_PROVIDER_EXIV2, GthMetadataProviderExiv2))
+#define GTH_METADATA_PROVIDER_EXIV2_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), GTH_TYPE_METADATA_PROVIDER_EXIV2, GthMetadataProviderExiv2Class))
+#define GTH_IS_METADATA_PROVIDER_EXIV2(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GTH_TYPE_METADATA_PROVIDER_EXIV2))
+#define GTH_IS_METADATA_PROVIDER_EXIV2_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GTH_TYPE_METADATA_PROVIDER_EXIV2))
+#define GTH_METADATA_PROVIDER_EXIV2_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS((o), GTH_TYPE_METADATA_PROVIDER_EXIV2, GthMetadataProviderExiv2Class))
+
+typedef struct _GthMetadataProviderExiv2 GthMetadataProviderExiv2;
+typedef struct _GthMetadataProviderExiv2Private GthMetadataProviderExiv2Private;
+typedef struct _GthMetadataProviderExiv2Class GthMetadataProviderExiv2Class;
+
+struct _GthMetadataProviderExiv2
+{
+ GthMetadataProvider __parent;
+ GthMetadataProviderExiv2Private *priv;
+};
+
+struct _GthMetadataProviderExiv2Class
+{
+ GthMetadataProviderClass __parent_class;
+};
+
+GType gth_metadata_provider_exiv2_get_type (void) G_GNUC_CONST;
+
+#endif /* GTH_METADATA_PROVIDER_EXIV2_H */
diff --git a/extensions/exiv2/main.c b/extensions/exiv2/main.c
new file mode 100644
index 0000000..8198798
--- /dev/null
+++ b/extensions/exiv2/main.c
@@ -0,0 +1,173 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+
+#include <config.h>
+#include <gtk/gtk.h>
+#include <gthumb.h>
+#include "gth-metadata-provider-exiv2.h"
+#include "exiv2-utils.h"
+
+
+GthMetadataCategory exiv2_metadata_category[] = {
+ { "Exif::General", N_("Exif General"), 30 },
+ { "Exif::Conditions", N_("Exif Conditions"), 31 },
+ { "Exif::Structure", N_("Exif Structure"), 32 },
+ { "Exif::Thumbnail", N_("Exif Thumbnail"), 33 },
+ { "Exif::GPS", N_("Exif GPS"), 34 },
+ { "Exif::MakerNotes", N_("Exif Maker Notes"), 35 },
+ { "Exif::Versions", N_("Exif Versions"), 36 },
+ { "Exif::Other", N_("Exif Other"), 37 },
+ { "Iptc", N_("IPTC"), 38 },
+ { "Xmp::Embedded", N_("XMP Embedded"), 39 },
+ { "Xmp::Sidecar", N_("XMP Attached"), 40 },
+ { NULL, NULL, 0 }
+};
+
+
+GthMetadataInfo exiv2_metadata_info[] = {
+ { "Exif::Image::Make", NULL, "Exif::General", 1, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+ { "Exif::Image::Model", NULL, "Exif::General", 2, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+ { "Exif::Image::Software", NULL, "Exif::General", 3, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+ { "Exif::Image::DateTime", NULL, "Exif::General", 4, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+ { "Exif::Photo::SubSecTime", NULL, "Exif::General", 5, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+ { "Exif::Photo::DateTimeOriginal", NULL, "Exif::General", 6, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+ { "Exif::Photo::SubSecTimeOriginal", NULL, "Exif::General", 7, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+ { "Exif::Photo::DateTimeDigitized", NULL, "Exif::General", 8, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+ { "Exif::Photo::SubSecTimeDigitized", NULL, "Exif::General", 9, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+ { "Exif::Image::Artist", NULL, "Exif::General", 11, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+ { "Exif::Image::Copyright", NULL, "Exif::General", 12, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+ { "Exif::Photo::UniqueID", NULL, "Exif::General", 13, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+ { "Exif::Photo::SoundFile", NULL, "Exif::General", 14, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+
+ { "Exif::Photo::ISOSpeedRatings", NULL, "Exif::Conditions", 2, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+ { "Exif::Photo::BrightnessValue", NULL, "Exif::Conditions", 3, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+ { "Exif::Photo::FNumber", NULL, "Exif::Conditions", 4, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+ { "Exif::Photo::ApertureValue", NULL, "Exif::Conditions", 5, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+ { "Exif::Photo::MaxApertureValue", NULL, "Exif::Conditions", 6, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+ { "Exif::Photo::ExposureTime", NULL, "Exif::Conditions", 7, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+ { "Exif::Photo::ExposureProgram", NULL, "Exif::Conditions", 8, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+ { "Exif::Photo::ExposureIndex", NULL, "Exif::Conditions", 9, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+ { "Exif::Photo::ExposureBiasValue", NULL, "Exif::Conditions", 10, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+ { "Exif::Photo::ExposureMode", NULL, "Exif::Conditions", 11, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+ { "Exif::Photo::ShutterSpeedValue", NULL, "Exif::Conditions", 12, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+ { "Exif::Photo::MeteringMode", NULL, "Exif::Conditions", 13, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+ { "Exif::Photo::LightSource", NULL, "Exif::Conditions", 14, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+ { "Exif::Photo::WhiteBalance", NULL, "Exif::Conditions", 15, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+ { "Exif::Photo::Flash", NULL, "Exif::Conditions", 16, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+ { "Exif::Photo::FlashEnergy", NULL, "Exif::Conditions", 17, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+ { "Exif::Photo::SubjectDistance", NULL, "Exif::Conditions", 18, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+ { "Exif::Photo::SubjectDistanceRange", NULL, "Exif::Conditions", 19, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+ { "Exif::Photo::SubjectArea", NULL, "Exif::Conditions", 20, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+ { "Exif::Photo::SubjectLocation", NULL, "Exif::Conditions", 21, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+ { "Exif::Photo::FocalLength", NULL, "Exif::Conditions", 22, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+ { "Exif::Photo::FocalLengthIn35mmFilm", NULL, "Exif::Conditions", 23, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+ { "Exif::Photo::FocalPlaneXResolution", NULL, "Exif::Conditions", 24, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+ { "Exif::Photo::FocalPlaneYResolution", NULL, "Exif::Conditions", 25, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+ { "Exif::Photo::FocalPlaneResolutionUnit", NULL, "Exif::Conditions", 26, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+ { "Exif::Photo::Contrast", NULL, "Exif::Conditions", 27, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+ { "Exif::Photo::Saturation", NULL, "Exif::Conditions", 28, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+ { "Exif::Photo::Sharpness", NULL, "Exif::Conditions", 29, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+ { "Exif::Photo::SceneType", NULL, "Exif::Conditions", 30, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+ { "Exif::Photo::SceneCaptureType", NULL, "Exif::Conditions", 31, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+ { "Exif::Photo::CustomRendered", NULL, "Exif::Conditions", 32, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+ { "Exif::Photo::DigitalZoomRatio", NULL, "Exif::Conditions", 33, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+ { "Exif::Photo::FileSource", NULL, "Exif::Conditions", 34, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+ { "Exif::Photo::SensingMethod", NULL, "Exif::Conditions", 35, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+ { "Exif::Photo::DeviceSettingDescription", NULL, "Exif::Conditions", 36, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+ { "Exif::Photo::OECF", NULL, "Exif::Conditions", 37, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+ { "Exif::Photo::SpatialFrequencyResponse", NULL, "Exif::Conditions", 38, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+ { "Exif::Photo::SpectralSensitivity", NULL, "Exif::Conditions", 39, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+ { "Exif::Photo::GainControl", NULL, "Exif::Conditions", 40, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+
+ { "Exif::Image::ImageWidth", NULL, "Exif::Structure", 1, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+ { "Exif::Image::ImageLength", NULL, "Exif::Structure", 2, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+ { "Exif::Photo::PixelXDimension", NULL, "Exif::Structure", 3, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+ { "Exif::Photo::PixelYDimension", NULL, "Exif::Structure", 4, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+ { "Exif::Image::Orientation", NULL, "Exif::Structure", 5, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+ { "Exif::Image::XResolution", NULL, "Exif::Structure", 6, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+ { "Exif::Image::YResolution", NULL, "Exif::Structure", 7, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+ { "Exif::Image::ResolutionUnit", NULL, "Exif::Structure", 8, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+ { "Exif::Image::Compression", NULL, "Exif::Structure", 9, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+ { "Exif::Image::SamplesPerPixel", NULL, "Exif::Structure", 10, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+ { "Exif::Image::BitsPerSample", NULL, "Exif::Structure", 11, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+ { "Exif::Image::PlanarConfiguration", NULL, "Exif::Structure", 12, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+ { "Exif::Image::YCbCrSubSampling", NULL, "Exif::Structure", 13, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+ { "Exif::Image::YCbCrPositioning", NULL, "Exif::Structure", 14, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+ { "Exif::Image::PhotometricInterpretation", NULL, "Exif::Structure", 15, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+ { "Exif::Photo::ComponentsConfiguration", NULL, "Exif::Structure", 16, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+ { "Exif::Photo::CompressedBitsPerPixel", NULL, "Exif::Structure", 17, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+ { "Exif::Photo::StripOffset", NULL, "Exif::Structure", 18, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+ { "Exif::Photo::RowsPerStrip", NULL, "Exif::Structure", 19, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+ { "Exif::Photo::StripByteCounts", NULL, "Exif::Structure", 20, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+ { "Exif::Photo::JPEGInterchangeFormat", NULL, "Exif::Structure", 21, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+ { "Exif::Photo::JPEGInterchangeFormatLength", NULL, "Exif::Structure", 22, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+ { "Exif::Photo::TransferFunction", NULL, "Exif::Structure", 23, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+ { "Exif::Photo::WhitePoint", NULL, "Exif::Structure", 24, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+ { "Exif::Photo::PrimaryChromaticities", NULL, "Exif::Structure", 25, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+ { "Exif::Photo::YCbCrCoefficients", NULL, "Exif::Structure", 26, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+ { "Exif::Photo::ReferenceBlackWhite", NULL, "Exif::Structure", 27, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+ { "Exif::Photo::ColorSpace", NULL, "Exif::Structure", 28, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+
+ { "Exif::Photo::ExifVersion", NULL, "Exif::Versions", 1, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+ { "Exif::Image::ExifTag", NULL, "Exif::Versions", 2, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+ { "Exif::Photo::FlashpixVersion", NULL, "Exif::Versions", 3, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+ { "Exif::Iop::InteroperabilityIndex", NULL, "Exif::Versions", 4, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+ { "Exif::Iop::InteroperabilityVersion", NULL, "Exif::Versions", 5, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+ { "Exif::Photo::InteroperabilityTag", NULL, "Exif::Versions", 6, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+ { "Exif::Photo::RelatedImageFileFormat", NULL, "Exif::Versions", 7, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+ { "Exif::Photo::RelatedImageWidth", NULL, "Exif::Versions", 8, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+ { "Exif::Photo::RelatedImageLength", NULL, "Exif::Versions", 9, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+
+ { "Exif::Photo::MakerNote", NULL, "Exif::Other", 0, GTH_METADATA_ALLOW_NOWHERE },
+
+ { NULL, NULL, NULL, 0, 0 }
+};
+
+
+G_MODULE_EXPORT void
+gthumb_extension_activate (void)
+{
+ gth_main_register_metadata_category (exiv2_metadata_category);
+ gth_main_register_metadata_info_v (exiv2_metadata_info);
+ gth_main_register_metadata_provider (GTH_TYPE_METADATA_PROVIDER_EXIV2);
+ gth_hook_add_callback ("save-pixbuf", 10, G_CALLBACK (exiv2_write_metadata), NULL);
+}
+
+
+G_MODULE_EXPORT void
+gthumb_extension_deactivate (void)
+{
+}
+
+
+G_MODULE_EXPORT gboolean
+gthumb_extension_is_configurable (void)
+{
+ return FALSE;
+}
+
+
+G_MODULE_EXPORT void
+gthumb_extension_configure (GtkWindow *parent)
+{
+}
diff --git a/extensions/file_manager/Makefile.am b/extensions/file_manager/Makefile.am
new file mode 100644
index 0000000..36dc51d
--- /dev/null
+++ b/extensions/file_manager/Makefile.am
@@ -0,0 +1,34 @@
+extensiondir = $(libdir)/gthumb-2.0/extensions
+extension_LTLIBRARIES = libfile_manager.la
+
+libfile_manager_la_SOURCES = \
+ actions.c \
+ actions.h \
+ callbacks.c \
+ callbacks.h \
+ gth-duplicate-task.c \
+ gth-duplicate-task.h \
+ main.c
+
+libfile_manager_la_CFLAGS = $(GTHUMB_CFLAGS) -I$(top_srcdir) -I$(top_builddir)/gthumb
+libfile_manager_la_LDFLAGS = $(EXTENSION_LIBTOOL_FLAGS)
+libfile_manager_la_LIBADD = $(GTHUMB_LIBS)
+libfile_manager_la_DEPENDENCIES = $(top_builddir)/gthumb/gthumb$(EXEEXT)
+
+extensioninidir = $(extensiondir)
+extensionini_in_files = file_manager.extension.in.in
+extensionini_DATA = $(extensionini_in_files:.extension.in.in=.extension)
+
+%.extension.in: %.extension.in.in $(extension_LTLIBRARIES)
+ sed -e "s|%LIBRARY%|`. ./$(extension_LTLIBRARIES) && echo $$dlname`|" \
+ $< > $@
+
+%.extension: %.extension.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*po) ; LC_ALL=C $(INTLTOOL_MERGE) -d -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< $@
+
+EXTRA_DIST = $(extensionini_in_files)
+
+CLEANFILES = $(extensionini_DATA)
+
+DISTCLEANFILES = $(extensionini_DATA)
+
+-include $(top_srcdir)/git.mk
diff --git a/extensions/file_manager/actions.c b/extensions/file_manager/actions.c
new file mode 100644
index 0000000..b560f15
--- /dev/null
+++ b/extensions/file_manager/actions.c
@@ -0,0 +1,195 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+
+#include <config.h>
+#include <glib/gi18n.h>
+#include <gthumb.h>
+#include "gth-duplicate-task.h"
+
+
+void
+gth_browser_activate_action_file_new_folder (GtkAction *action,
+ GthBrowser *browser)
+{
+}
+
+
+void
+gth_browser_activate_action_edit_cut_files (GtkAction *action,
+ GthBrowser *browser)
+{
+}
+
+
+void
+gth_browser_activate_action_edit_copy_files (GtkAction *action,
+ GthBrowser *browser)
+{
+}
+
+
+void
+gth_browser_activate_action_edit_paste_in_folder (GtkAction *action,
+ GthBrowser *browser)
+{
+}
+
+
+void
+gth_browser_activate_action_edit_duplicate (GtkAction *action,
+ GthBrowser *browser)
+{
+ GList *items;
+ GList *file_list = NULL;
+ GthTask *task;
+
+ items = gth_file_selection_get_selected (GTH_FILE_SELECTION (gth_browser_get_file_list_view (browser)));
+ file_list = gth_file_list_get_files (GTH_FILE_LIST (gth_browser_get_file_list (browser)), items);
+ task = gth_duplicate_task_new (file_list);
+ gth_browser_exec_task (browser, task);
+
+ g_object_unref (task);
+ _g_object_list_unref (file_list);
+ _gtk_tree_path_list_free (items);
+}
+
+
+void
+gth_browser_activate_action_edit_rename (GtkAction *action,
+ GthBrowser *browser)
+{
+}
+
+
+static void
+delete_file_permanently (GtkWindow *window,
+ GList *file_list)
+{
+ GList *files;
+ GError *error = NULL;
+
+ files = gth_file_data_list_to_file_list (file_list);
+ if (! _g_delete_files (files, TRUE, &error))
+ _gtk_error_dialog_from_gerror_show (window, _("Could not delete the files"), &error);
+
+ _g_object_list_unref (files);
+}
+
+
+static void
+delete_permanently_response_cb (GtkDialog *dialog,
+ int response_id,
+ gpointer user_data)
+{
+ GList *file_list = user_data;
+
+ if (response_id == GTK_RESPONSE_YES)
+ delete_file_permanently (gtk_window_get_transient_for (GTK_WINDOW (dialog)), file_list);
+ gtk_widget_destroy (GTK_WIDGET (dialog));
+ _g_object_list_unref (file_list);
+}
+
+
+void
+gth_browser_activate_action_edit_trash (GtkAction *action,
+ GthBrowser *browser)
+{
+ GList *items;
+ GList *file_list = NULL;
+ GList *scan;
+ GError *error = NULL;
+
+ items = gth_file_selection_get_selected (GTH_FILE_SELECTION (gth_browser_get_file_list_view (browser)));
+ file_list = gth_file_list_get_files (GTH_FILE_LIST (gth_browser_get_file_list (browser)), items);
+
+ for (scan = file_list; scan; scan = scan->next) {
+ GthFileData *file_data = scan->data;
+
+ if (! g_file_trash (file_data->file, NULL, &error)) {
+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED)) {
+ GtkWidget *d;
+
+ g_clear_error (&error);
+
+ d = _gtk_yesno_dialog_new (GTK_WINDOW (browser),
+ GTK_DIALOG_MODAL,
+ _("The files cannot be moved to the Trash. Do you want to delete them permanently?"),
+ GTK_STOCK_CANCEL,
+ GTK_STOCK_DELETE);
+ g_signal_connect (d, "response", G_CALLBACK (delete_permanently_response_cb), file_list);
+ gtk_widget_show (d);
+
+ file_list = NULL;
+
+ break;
+ }
+ _gtk_error_dialog_from_gerror_show (GTK_WINDOW (browser), _("Could not move the files to the Trash"), &error);
+ break;
+ }
+ }
+
+ _g_object_list_unref (file_list);
+ _gtk_tree_path_list_free (items);
+}
+
+
+void
+gth_browser_activate_action_edit_delete (GtkAction *action,
+ GthBrowser *browser)
+{
+ GList *items;
+ GList *file_list = NULL;
+ int file_count;
+ char *prompt;
+ GtkWidget *d;
+
+ items = gth_file_selection_get_selected (GTH_FILE_SELECTION (gth_browser_get_file_list_view (browser)));
+ file_list = gth_file_list_get_files (GTH_FILE_LIST (gth_browser_get_file_list (browser)), items);
+
+ file_count = g_list_length (file_list);
+ if (file_count == 1) {
+ GthFileData *file_data = file_list->data;
+
+ prompt = g_strdup_printf (_("Are you sure you want to permanently delete \"%s\"?"), g_file_info_get_display_name (file_data->info));
+ }
+ else
+ prompt = g_strdup_printf (ngettext("Are you sure you want to permanently delete "
+ "the %'d selected file?",
+ "Are you sure you want to permanently delete "
+ "the %'d selected files?", file_count),
+ file_count);
+
+ d = _gtk_message_dialog_new (GTK_WINDOW (browser),
+ GTK_DIALOG_MODAL,
+ GTK_STOCK_DIALOG_QUESTION,
+ prompt,
+ _("If you delete a file, it will be permanently lost."),
+ GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+ GTK_STOCK_DELETE, GTK_RESPONSE_YES,
+ NULL);
+ g_signal_connect (d, "response", G_CALLBACK (delete_permanently_response_cb), file_list);
+ gtk_widget_show (d);
+
+ g_free (prompt);
+ _gtk_tree_path_list_free (items);
+}
diff --git a/extensions/file_manager/actions.h b/extensions/file_manager/actions.h
new file mode 100644
index 0000000..800f3a6
--- /dev/null
+++ b/extensions/file_manager/actions.h
@@ -0,0 +1,39 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef ACTIONS_H
+#define ACTIONS_H
+
+#include <gtk/gtk.h>
+
+#define DEFINE_ACTION(x) void x (GtkAction *action, gpointer data);
+
+DEFINE_ACTION(gth_browser_activate_action_file_new_folder)
+DEFINE_ACTION(gth_browser_activate_action_edit_cut_files)
+DEFINE_ACTION(gth_browser_activate_action_edit_copy_files)
+DEFINE_ACTION(gth_browser_activate_action_edit_paste_in_folder)
+DEFINE_ACTION(gth_browser_activate_action_edit_duplicate)
+DEFINE_ACTION(gth_browser_activate_action_edit_rename)
+DEFINE_ACTION(gth_browser_activate_action_edit_trash)
+DEFINE_ACTION(gth_browser_activate_action_edit_delete)
+
+#endif /* ACTIONS_H */
diff --git a/extensions/file_manager/callbacks.c b/extensions/file_manager/callbacks.c
new file mode 100644
index 0000000..3fb3371
--- /dev/null
+++ b/extensions/file_manager/callbacks.c
@@ -0,0 +1,257 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+
+#include <config.h>
+#include <glib/gi18n.h>
+#include <glib-object.h>
+#include <gthumb.h>
+#include "actions.h"
+
+
+#define BROWSER_DATA_KEY "file-manager-browser-data"
+
+
+static const char *fixed_ui_info =
+"<ui>"
+" <menubar name='MenuBar'>"
+" <menu name='Edit' action='EditMenu'>"
+" </menu>"
+" </menubar>"
+"</ui>";
+
+
+static const char *vfs_ui_info =
+"<ui>"
+" <menubar name='MenuBar'>"
+" <menu name='Edit' action='EditMenu'>"
+" <placeholder name='Folder_Actions'>"
+" <menuitem action='Edit_Duplicate'/>"
+" <menuitem action='Edit_Rename'/>"
+" <separator/>"
+" <menuitem action='Edit_Trash'/>"
+" <menuitem action='Edit_Delete'/>"
+" </placeholder>"
+" </menu>"
+" </menubar>"
+"</ui>";
+
+
+static const char *browser_ui_info =
+"<ui>"
+" <popup name='FileListPopup'>"
+" <placeholder name='File_Actions'>"
+" <menuitem action='Edit_CutFiles'/>"
+" <menuitem action='Edit_CopyFiles'/>"
+" <menuitem action='Edit_PasteInFolder'/>"
+" </placeholder>"
+" </popup>"
+"</ui>";
+
+
+static const char *browser_vfs_ui_info =
+"<ui>"
+" <menubar name='MenuBar'>"
+" <menu name='File' action='FileMenu'>"
+" <placeholder name='Folder_Actions'>"
+" <menuitem action='File_NewFolder'/>"
+" </placeholder>"
+" </menu>"
+" <menu name='Edit' action='EditMenu'>"
+" <placeholder name='File_Actions'>"
+" <menuitem action='Edit_CutFiles'/>"
+" <menuitem action='Edit_CopyFiles'/>"
+" <menuitem action='Edit_PasteInFolder'/>"
+" </placeholder>"
+" </menu>"
+" </menubar>"
+" <popup name='FileListPopup'>"
+" <placeholder name='Folder_Actions'>"
+" <menuitem action='Edit_Duplicate'/>"
+" <menuitem action='Edit_Rename'/>"
+" <separator/>"
+" <menuitem action='Edit_Trash'/>"
+" <menuitem action='Edit_Delete'/>"
+" </placeholder>"
+" </popup>"
+"</ui>";
+
+
+static GtkActionEntry action_entries[] = {
+ { "File_NewFolder", "folder-new",
+ N_("Create _Folder"), "<control><shift>N",
+ N_("Create a new empty folder inside this folder"),
+ G_CALLBACK (gth_browser_activate_action_file_new_folder) },
+ { "Edit_CutFiles", GTK_STOCK_CUT,
+ NULL, NULL,
+ NULL,
+ G_CALLBACK (gth_browser_activate_action_edit_cut_files) },
+ { "Edit_CopyFiles", GTK_STOCK_COPY,
+ NULL, NULL,
+ NULL,
+ G_CALLBACK (gth_browser_activate_action_edit_copy_files) },
+ { "Edit_PasteInFolder", GTK_STOCK_PASTE,
+ NULL, NULL,
+ NULL,
+ G_CALLBACK (gth_browser_activate_action_edit_paste_in_folder) },
+ { "Edit_Duplicate", NULL,
+ N_("D_uplicate"), NULL,
+ N_("Duplicate the selected files"),
+ G_CALLBACK (gth_browser_activate_action_edit_duplicate) },
+ { "Edit_Rename", NULL,
+ N_("_Rename"), "F2",
+ N_("Rename the selected files"),
+ G_CALLBACK (gth_browser_activate_action_edit_rename) },
+ { "Edit_Trash", "user-trash",
+ N_("Mo_ve to Trash"), "Delete",
+ N_("Move the selected files to the Trash"),
+ G_CALLBACK (gth_browser_activate_action_edit_trash) },
+ { "Edit_Delete", "edit-delete",
+ N_("_Delete"), "<shift>Delete",
+ N_("Delete the selected files"),
+ G_CALLBACK (gth_browser_activate_action_edit_delete) },
+};
+
+
+typedef struct {
+ GtkActionGroup *action_group;
+ guint fixed_merge_id;
+ guint vfs_merge_id;
+ guint browser_merge_id;
+ guint browser_vfs_merge_id;
+} BrowserData;
+
+
+static void
+browser_data_free (BrowserData *data)
+{
+ g_free (data);
+}
+
+
+void
+fm__gth_browser_construct_cb (GthBrowser *browser)
+{
+ BrowserData *data;
+ GError *error = NULL;
+
+ g_return_if_fail (GTH_IS_BROWSER (browser));
+
+ data = g_new0 (BrowserData, 1);
+
+ data->action_group = gtk_action_group_new ("File Manager Actions");
+ gtk_action_group_set_translation_domain (data->action_group, NULL);
+ gtk_action_group_add_actions (data->action_group,
+ action_entries,
+ G_N_ELEMENTS (action_entries),
+ browser);
+ gtk_ui_manager_insert_action_group (gth_browser_get_ui_manager (browser), data->action_group, 0);
+
+ data->fixed_merge_id = gtk_ui_manager_add_ui_from_string (gth_browser_get_ui_manager (browser), fixed_ui_info, -1, &error);
+ if (data->fixed_merge_id == 0) {
+ g_warning ("ui building failed: %s", error->message);
+ g_clear_error (&error);
+ }
+
+ g_object_set_data_full (G_OBJECT (browser), BROWSER_DATA_KEY, data, (GDestroyNotify) browser_data_free);
+}
+
+
+static void
+file_manager_update_ui (BrowserData *data,
+ GthBrowser *browser)
+{
+ if (GTH_IS_FILE_SOURCE_VFS (gth_browser_get_file_source (browser))) {
+ if (data->vfs_merge_id == 0) {
+ GError *local_error = NULL;
+
+ data->vfs_merge_id = gtk_ui_manager_add_ui_from_string (gth_browser_get_ui_manager (browser), vfs_ui_info, -1, &local_error);
+ if (data->vfs_merge_id == 0) {
+ g_warning ("building ui failed: %s", local_error->message);
+ g_error_free (local_error);
+ }
+ }
+ }
+ else if (data->vfs_merge_id != 0) {
+ gtk_ui_manager_remove_ui (gth_browser_get_ui_manager (browser), data->vfs_merge_id);
+ data->vfs_merge_id = 0;
+ }
+
+ if (gth_window_get_current_page (GTH_WINDOW (browser)) == GTH_BROWSER_PAGE_BROWSER) {
+ if (data->browser_merge_id == 0) {
+ GError *local_error = NULL;
+
+ data->browser_merge_id = gtk_ui_manager_add_ui_from_string (gth_browser_get_ui_manager (browser), browser_ui_info, -1, &local_error);
+ if (data->browser_merge_id == 0) {
+ g_warning ("building ui failed: %s", local_error->message);
+ g_error_free (local_error);
+ }
+ }
+ }
+ else if (data->browser_merge_id != 0) {
+ gtk_ui_manager_remove_ui (gth_browser_get_ui_manager (browser), data->browser_merge_id);
+ data->browser_merge_id = 0;
+ }
+
+ if (GTH_IS_FILE_SOURCE_VFS (gth_browser_get_file_source (browser))
+ && (gth_window_get_current_page (GTH_WINDOW (browser)) == GTH_BROWSER_PAGE_BROWSER))
+ {
+ if (data->browser_vfs_merge_id == 0) {
+ GError *local_error = NULL;
+
+ data->browser_vfs_merge_id = gtk_ui_manager_add_ui_from_string (gth_browser_get_ui_manager (browser), browser_vfs_ui_info, -1, &local_error);
+ if (data->browser_vfs_merge_id == 0) {
+ g_warning ("building ui failed: %s", local_error->message);
+ g_error_free (local_error);
+ }
+ }
+ }
+ else if (data->browser_vfs_merge_id != 0) {
+ gtk_ui_manager_remove_ui (gth_browser_get_ui_manager (browser), data->browser_vfs_merge_id);
+ data->browser_vfs_merge_id = 0;
+ }
+}
+
+
+void
+fm__gth_browser_set_current_page_cb (GthBrowser *browser)
+{
+ BrowserData *data;
+
+ data = g_object_get_data (G_OBJECT (browser), BROWSER_DATA_KEY);
+ file_manager_update_ui (data, browser);
+}
+
+
+void
+fm__gth_browser_load_location_after_cb (GthBrowser *browser,
+ GFile *location,
+ const GError *error)
+{
+ BrowserData *data;
+
+ if (location == NULL)
+ return;
+
+ data = g_object_get_data (G_OBJECT (browser), BROWSER_DATA_KEY);
+ file_manager_update_ui (data, browser);
+}
diff --git a/extensions/file_manager/callbacks.h b/extensions/file_manager/callbacks.h
new file mode 100644
index 0000000..89fd3dc
--- /dev/null
+++ b/extensions/file_manager/callbacks.h
@@ -0,0 +1,35 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef CALLBACKS_H
+#define CALLBACKS_H
+
+#include <gthumb.h>
+
+void fm__gth_browser_construct_cb (GthBrowser *browser);
+void fm__gth_browser_update_sensitivity_cb (GthBrowser *browser);
+void fm__gth_browser_set_current_page_cb (GthBrowser *browser);
+void fm__gth_browser_load_location_after_cb (GthBrowser *browser,
+ GFile *location,
+ GError *error);
+
+#endif /* CALLBACKS_H */
diff --git a/extensions/file_manager/file_manager.extension.in.in b/extensions/file_manager/file_manager.extension.in.in
new file mode 100644
index 0000000..c06f357
--- /dev/null
+++ b/extensions/file_manager/file_manager.extension.in.in
@@ -0,0 +1,10 @@
+[Extension]
+_Name=File Manager
+_Description=File manager actions.
+Authors=Paolo Bacchilega <paobac src gnome org>
+Copyright=Copyright © 2009 Paolo Bacchilega
+Version=1
+
+[Loader]
+Type=module
+File=%LIBRARY%
diff --git a/extensions/file_manager/gth-duplicate-task.c b/extensions/file_manager/gth-duplicate-task.c
new file mode 100644
index 0000000..2a5546b
--- /dev/null
+++ b/extensions/file_manager/gth-duplicate-task.c
@@ -0,0 +1,241 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2001-2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include "gth-duplicate-task.h"
+
+
+struct _GthDuplicateTaskPrivate {
+ GCancellable *cancellable;
+ GList *file_list;
+ GList *current;
+ int attempt;
+};
+
+
+static gpointer parent_class = NULL;
+
+
+static void
+gth_duplicate_task_finalize (GObject *object)
+{
+ GthDuplicateTask *self;
+
+ self = GTH_DUPLICATE_TASK (object);
+
+ _g_object_list_unref (self->priv->file_list);
+ _g_object_unref (self->priv->cancellable);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+
+static GFile *
+get_destination (GthFileData *file_data,
+ int n)
+{
+ char *uri;
+ char *uri_no_ext;
+ const char *ext;
+ char *new_uri;
+ GFile *new_file;
+
+ uri = g_file_get_uri (file_data->file);
+ uri_no_ext = _g_uri_remove_extension (uri);
+ ext = _g_uri_get_file_extension (uri);
+ new_uri = g_strdup_printf ("%s%%20(%d)%s",
+ uri_no_ext,
+ n + 1,
+ (ext == NULL) ? "" : ext);
+ new_file = g_file_new_for_uri (new_uri);
+
+ g_free (new_uri);
+ g_free (uri_no_ext);
+ g_free (uri);
+
+ return new_file;
+}
+
+
+static void
+copy_progress_cb (goffset current_file,
+ goffset total_files,
+ GFile *source,
+ GFile *destination,
+ goffset current_num_bytes,
+ goffset total_num_bytes,
+ gpointer user_data)
+{
+ GthDuplicateTask *self = user_data;
+
+ gth_task_progress (GTH_TASK (self), (float) current_num_bytes / total_num_bytes);
+}
+
+
+static void duplicate_current_file (GthDuplicateTask *self);
+
+
+static void
+copy_ready_cb (GError *error,
+ gpointer user_data)
+{
+ GthDuplicateTask *self = user_data;
+
+ if (error != NULL) {
+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_EXISTS)) {
+ g_clear_error (&error);
+
+ self->priv->attempt++;
+ duplicate_current_file (self);
+ return;
+ }
+
+ gth_task_completed (GTH_TASK (self), error);
+ return;
+ }
+
+ self->priv->current = self->priv->current->next;
+ self->priv->attempt = 1;
+ duplicate_current_file (self);
+}
+
+
+static void
+duplicate_current_file (GthDuplicateTask *self)
+{
+ GthFileData *file_data;
+ GFile *destination;
+
+ if (self->priv->current == NULL) {
+ gth_task_completed (GTH_TASK (self), NULL);
+ return;
+ }
+
+ file_data = self->priv->current->data;
+ destination = get_destination (file_data, self->priv->attempt);
+
+ _g_copy_file_async (file_data->file,
+ destination,
+ G_FILE_COPY_ALL_METADATA,
+ G_PRIORITY_DEFAULT,
+ self->priv->cancellable,
+ copy_progress_cb,
+ self,
+ copy_ready_cb,
+ self);
+
+ g_object_unref (destination);
+}
+
+
+static void
+gth_duplicate_task_exec (GthTask *task)
+{
+ GthDuplicateTask *self;
+
+ g_return_if_fail (GTH_IS_DUPLICATE_TASK (task));
+
+ self = GTH_DUPLICATE_TASK (task);
+
+ self->priv->current = self->priv->file_list;
+ self->priv->attempt = 1;
+ duplicate_current_file (self);
+}
+
+
+static void
+gth_duplicate_task_cancel (GthTask *task)
+{
+ GthDuplicateTask *self;
+
+ g_return_if_fail (GTH_IS_DUPLICATE_TASK (task));
+
+ self = GTH_DUPLICATE_TASK (task);
+
+ g_cancellable_cancel (self->priv->cancellable);
+}
+
+
+static void
+gth_duplicate_task_class_init (GthDuplicateTaskClass *klass)
+{
+ GObjectClass *object_class;
+ GthTaskClass *task_class;
+
+ parent_class = g_type_class_peek_parent (klass);
+ g_type_class_add_private (klass, sizeof (GthDuplicateTaskPrivate));
+
+ object_class = G_OBJECT_CLASS (klass);
+ object_class->finalize = gth_duplicate_task_finalize;
+
+ task_class = GTH_TASK_CLASS (klass);
+ task_class->exec = gth_duplicate_task_exec;
+ task_class->cancel = gth_duplicate_task_cancel;
+}
+
+
+static void
+gth_duplicate_task_init (GthDuplicateTask *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GTH_TYPE_DUPLICATE_TASK, GthDuplicateTaskPrivate);
+ self->priv->cancellable = g_cancellable_new ();
+}
+
+
+GType
+gth_duplicate_task_get_type (void)
+{
+ static GType type = 0;
+
+ if (! type) {
+ GTypeInfo type_info = {
+ sizeof (GthDuplicateTaskClass),
+ NULL,
+ NULL,
+ (GClassInitFunc) gth_duplicate_task_class_init,
+ NULL,
+ NULL,
+ sizeof (GthDuplicateTask),
+ 0,
+ (GInstanceInitFunc) gth_duplicate_task_init
+ };
+
+ type = g_type_register_static (GTH_TYPE_TASK,
+ "GthDuplicateTask",
+ &type_info,
+ 0);
+ }
+
+ return type;
+}
+
+
+GthTask *
+gth_duplicate_task_new (GList *file_list)
+{
+ GthDuplicateTask *self;
+
+ self = GTH_DUPLICATE_TASK (g_object_new (GTH_TYPE_DUPLICATE_TASK, NULL));
+ self->priv->file_list = _g_object_list_ref (file_list);
+
+ return (GthTask *) self;
+}
diff --git a/extensions/file_manager/gth-duplicate-task.h b/extensions/file_manager/gth-duplicate-task.h
new file mode 100644
index 0000000..6600dc9
--- /dev/null
+++ b/extensions/file_manager/gth-duplicate-task.h
@@ -0,0 +1,56 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2001-2009 The Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GTH_DUPLICATE_TASK_H
+#define GTH_DUPLICATE_TASK_H
+
+#include <glib.h>
+#include <gthumb.h>
+
+G_BEGIN_DECLS
+
+#define GTH_TYPE_DUPLICATE_TASK (gth_duplicate_task_get_type ())
+#define GTH_DUPLICATE_TASK(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTH_TYPE_DUPLICATE_TASK, GthDuplicateTask))
+#define GTH_DUPLICATE_TASK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTH_TYPE_DUPLICATE_TASK, GthDuplicateTaskClass))
+#define GTH_IS_DUPLICATE_TASK(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTH_TYPE_DUPLICATE_TASK))
+#define GTH_IS_DUPLICATE_TASK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTH_TYPE_DUPLICATE_TASK))
+#define GTH_DUPLICATE_TASK_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GTH_TYPE_DUPLICATE_TASK, GthDuplicateTaskClass))
+
+typedef struct _GthDuplicateTask GthDuplicateTask;
+typedef struct _GthDuplicateTaskClass GthDuplicateTaskClass;
+typedef struct _GthDuplicateTaskPrivate GthDuplicateTaskPrivate;
+
+struct _GthDuplicateTask {
+ GthTask __parent;
+ GthDuplicateTaskPrivate *priv;
+};
+
+struct _GthDuplicateTaskClass {
+ GthTaskClass __parent;
+};
+
+GType gth_duplicate_task_get_type (void);
+GthTask * gth_duplicate_task_new (GList *file_list);
+
+G_END_DECLS
+
+#endif /* GTH_DUPLICATE_TASK_H */
diff --git a/extensions/file_manager/main.c b/extensions/file_manager/main.c
new file mode 100644
index 0000000..5076c1b
--- /dev/null
+++ b/extensions/file_manager/main.c
@@ -0,0 +1,55 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2008 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+
+#include <config.h>
+#include <gtk/gtk.h>
+#include <gthumb.h>
+#include "callbacks.h"
+
+
+G_MODULE_EXPORT void
+gthumb_extension_activate (void)
+{
+ gth_hook_add_callback ("gth-browser-construct", 10, G_CALLBACK (fm__gth_browser_construct_cb), NULL);
+ gth_hook_add_callback ("gth-browser-load-location-after", 10, G_CALLBACK (fm__gth_browser_load_location_after_cb), NULL);
+ gth_hook_add_callback ("gth-browser-set-current-page", 10, G_CALLBACK (fm__gth_browser_set_current_page_cb), NULL);
+}
+
+
+G_MODULE_EXPORT void
+gthumb_extension_deactivate (void)
+{
+}
+
+
+G_MODULE_EXPORT gboolean
+gthumb_extension_is_configurable (void)
+{
+ return FALSE;
+}
+
+
+G_MODULE_EXPORT void
+gthumb_extension_configure (GtkWindow *parent)
+{
+}
diff --git a/extensions/file_viewer/Makefile.am b/extensions/file_viewer/Makefile.am
new file mode 100644
index 0000000..3e2d066
--- /dev/null
+++ b/extensions/file_viewer/Makefile.am
@@ -0,0 +1,30 @@
+extensiondir = $(libdir)/gthumb-2.0/extensions
+extension_LTLIBRARIES = libfile_viewer.la
+
+libfile_viewer_la_SOURCES = \
+ gth-file-viewer-page.c \
+ gth-file-viewer-page.h \
+ main.c
+
+libfile_viewer_la_CFLAGS = $(GTHUMB_CFLAGS) -I$(top_srcdir) -I$(top_builddir)/gthumb
+libfile_viewer_la_LDFLAGS = $(EXTENSION_LIBTOOL_FLAGS)
+libfile_viewer_la_LIBADD = $(GTHUMB_LIBS)
+libfile_viewer_la_DEPENDENCIES = $(top_builddir)/gthumb/gthumb$(EXEEXT)
+
+extensioninidir = $(extensiondir)
+extensionini_in_files = file_viewer.extension.in.in
+extensionini_DATA = $(extensionini_in_files:.extension.in.in=.extension)
+
+%.extension.in: %.extension.in.in $(extension_LTLIBRARIES)
+ sed -e "s|%LIBRARY%|`. ./$(extension_LTLIBRARIES) && echo $$dlname`|" \
+ $< > $@
+
+%.extension: %.extension.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*po) ; LC_ALL=C $(INTLTOOL_MERGE) -d -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< $@
+
+EXTRA_DIST = $(extensionini_in_files)
+
+CLEANFILES = $(extensionini_DATA)
+
+DISTCLEANFILES = $(extensionini_DATA)
+
+-include $(top_srcdir)/git.mk
diff --git a/extensions/file_viewer/file_viewer.extension.in.in b/extensions/file_viewer/file_viewer.extension.in.in
new file mode 100644
index 0000000..00123f5
--- /dev/null
+++ b/extensions/file_viewer/file_viewer.extension.in.in
@@ -0,0 +1,10 @@
+[Extension]
+_Name=Image Viewer
+_Description=Allows to view images.
+Authors=Paolo Bacchilega <paobac src gnome org>
+Copyright=Copyright © 2009 Paolo Bacchilega
+Version=1
+
+[Loader]
+Type=module
+File=%LIBRARY%
diff --git a/extensions/file_viewer/gth-file-viewer-page.c b/extensions/file_viewer/gth-file-viewer-page.c
new file mode 100644
index 0000000..4f55b94
--- /dev/null
+++ b/extensions/file_viewer/gth-file-viewer-page.c
@@ -0,0 +1,200 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include "gth-file-viewer-page.h"
+
+
+#define GTH_FILE_VIEWER_PAGE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GTH_TYPE_FILE_VIEWER_PAGE, GthFileViewerPagePrivate))
+
+
+static const char *file_viewer_ui_info =
+"<ui>"
+" <toolbar name='ViewerToolBar'>"
+" <placeholder name='ViewerCommands'>"
+" </placeholder>"
+" </toolbar>"
+"</ui>";
+
+
+struct _GthFileViewerPagePrivate {
+ GthBrowser *browser;
+ GtkWidget *viewer;
+ guint merge_id;
+};
+
+
+static gpointer gth_file_viewer_page_parent_class = NULL;
+
+
+static void
+gth_file_viewer_page_real_activate (GthViewerPage *base,
+ GthBrowser *browser)
+{
+ GthFileViewerPage *self;
+ GError *error = NULL;
+
+ self = (GthFileViewerPage*) base;
+
+ self->priv->browser = browser;
+
+ self->priv->merge_id = gtk_ui_manager_add_ui_from_string (gth_browser_get_ui_manager (browser), file_viewer_ui_info, -1, &error);
+ if (self->priv->merge_id == 0) {
+ g_warning ("ui building failed: %s", error->message);
+ g_error_free (error);
+ }
+
+ self->priv->viewer = gtk_label_new ("...");
+ gtk_widget_show (self->priv->viewer);
+ gth_browser_set_viewer_widget (browser, self->priv->viewer);
+ gtk_widget_grab_focus (self->priv->viewer);
+}
+
+
+static void
+gth_file_viewer_page_real_deactivate (GthViewerPage *base)
+{
+ GthFileViewerPage *self;
+
+ self = (GthFileViewerPage*) base;
+
+ if (self->priv->merge_id != 0) {
+ gtk_ui_manager_remove_ui (gth_browser_get_ui_manager (self->priv->browser), self->priv->merge_id);
+ self->priv->merge_id = 0;
+ }
+ gth_browser_set_viewer_widget (self->priv->browser, NULL);
+}
+
+
+static void
+gth_file_viewer_page_real_show (GthViewerPage *base)
+{
+}
+
+
+static void
+gth_file_viewer_page_real_hide (GthViewerPage *base)
+{
+}
+
+
+static gboolean
+gth_file_viewer_page_real_can_view (GthViewerPage *base,
+ GthFileData *file_data)
+{
+ GthFileViewerPage *self;
+
+ self = (GthFileViewerPage*) base;
+ g_return_val_if_fail (file_data != NULL, FALSE);
+
+ return TRUE;
+}
+
+
+static void
+gth_file_viewer_page_real_view (GthViewerPage *base,
+ GthFileData *file_data)
+{
+ GthFileViewerPage *self;
+
+ self = (GthFileViewerPage*) base;
+ g_return_if_fail (file_data != NULL);
+
+ gtk_label_set_text (GTK_LABEL (self->priv->viewer), g_file_info_get_display_name (file_data->info));
+}
+
+
+static void
+gth_file_viewer_page_real_update_sensitivity (GthViewerPage *base)
+{
+}
+
+
+static gboolean
+gth_file_viewer_page_real_can_save (GthViewerPage *base)
+{
+ return FALSE;
+}
+
+
+static void
+gth_file_viewer_page_finalize (GObject *obj)
+{
+ G_OBJECT_CLASS (gth_file_viewer_page_parent_class)->finalize (obj);
+}
+
+
+static void
+gth_file_viewer_page_class_init (GthFileViewerPageClass *klass)
+{
+ gth_file_viewer_page_parent_class = g_type_class_peek_parent (klass);
+ g_type_class_add_private (klass, sizeof (GthFileViewerPagePrivate));
+
+ G_OBJECT_CLASS (klass)->finalize = gth_file_viewer_page_finalize;
+}
+
+
+static void
+gth_viewer_page_interface_init (GthViewerPageIface *iface)
+{
+ iface->activate = gth_file_viewer_page_real_activate;
+ iface->deactivate = gth_file_viewer_page_real_deactivate;
+ iface->show = gth_file_viewer_page_real_show;
+ iface->hide = gth_file_viewer_page_real_hide;
+ iface->can_view = gth_file_viewer_page_real_can_view;
+ iface->view = gth_file_viewer_page_real_view;
+ iface->update_sensitivity = gth_file_viewer_page_real_update_sensitivity;
+ iface->can_save = gth_file_viewer_page_real_can_save;
+}
+
+
+static void
+gth_file_viewer_page_instance_init (GthFileViewerPage *self)
+{
+ self->priv = GTH_FILE_VIEWER_PAGE_GET_PRIVATE (self);
+}
+
+
+GType gth_file_viewer_page_get_type (void) {
+ static GType gth_file_viewer_page_type_id = 0;
+ if (gth_file_viewer_page_type_id == 0) {
+ static const GTypeInfo g_define_type_info = {
+ sizeof (GthFileViewerPageClass),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) gth_file_viewer_page_class_init,
+ (GClassFinalizeFunc) NULL,
+ NULL,
+ sizeof (GthFileViewerPage),
+ 0,
+ (GInstanceInitFunc) gth_file_viewer_page_instance_init,
+ NULL
+ };
+ static const GInterfaceInfo gth_viewer_page_info = {
+ (GInterfaceInitFunc) gth_viewer_page_interface_init,
+ (GInterfaceFinalizeFunc) NULL,
+ NULL
+ };
+ gth_file_viewer_page_type_id = g_type_register_static (G_TYPE_OBJECT, "GthFileViewerPage", &g_define_type_info, 0);
+ g_type_add_interface_static (gth_file_viewer_page_type_id, GTH_TYPE_VIEWER_PAGE, >h_viewer_page_info);
+ }
+ return gth_file_viewer_page_type_id;
+}
diff --git a/extensions/file_viewer/gth-file-viewer-page.h b/extensions/file_viewer/gth-file-viewer-page.h
new file mode 100644
index 0000000..63599dc
--- /dev/null
+++ b/extensions/file_viewer/gth-file-viewer-page.h
@@ -0,0 +1,54 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GTH_FILE_VIEWER_PAGE_H
+#define GTH_FILE_VIEWER_PAGE_H
+
+#include <gthumb.h>
+
+G_BEGIN_DECLS
+
+#define GTH_TYPE_FILE_VIEWER_PAGE (gth_file_viewer_page_get_type ())
+#define GTH_FILE_VIEWER_PAGE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTH_TYPE_FILE_VIEWER_PAGE, GthFileViewerPage))
+#define GTH_FILE_VIEWER_PAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTH_TYPE_FILE_VIEWER_PAGE, GthFileViewerPageClass))
+#define GTH_IS_FILE_VIEWER_PAGE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTH_TYPE_FILE_VIEWER_PAGE))
+#define GTH_IS_FILE_VIEWER_PAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTH_TYPE_FILE_VIEWER_PAGE))
+#define GTH_FILE_VIEWER_PAGE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTH_TYPE_FILE_VIEWER_PAGE, GthFileViewerPageClass))
+
+typedef struct _GthFileViewerPage GthFileViewerPage;
+typedef struct _GthFileViewerPageClass GthFileViewerPageClass;
+typedef struct _GthFileViewerPagePrivate GthFileViewerPagePrivate;
+
+struct _GthFileViewerPage {
+ GObject parent_instance;
+ GthFileViewerPagePrivate * priv;
+};
+
+struct _GthFileViewerPageClass {
+ GObjectClass parent_class;
+};
+
+GType gth_file_viewer_page_get_type (void);
+
+G_END_DECLS
+
+#endif /* GTH_FILE_VIEWER_PAGE_H */
diff --git a/extensions/file_viewer/main.c b/extensions/file_viewer/main.c
new file mode 100644
index 0000000..cbf52f3
--- /dev/null
+++ b/extensions/file_viewer/main.c
@@ -0,0 +1,53 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+
+#include <config.h>
+#include <gtk/gtk.h>
+#include <gthumb.h>
+#include "gth-file-viewer-page.h"
+
+
+G_MODULE_EXPORT void
+gthumb_extension_activate (void)
+{
+ gth_main_register_viewer_page (GTH_TYPE_FILE_VIEWER_PAGE);
+}
+
+
+G_MODULE_EXPORT void
+gthumb_extension_deactivate (void)
+{
+}
+
+
+G_MODULE_EXPORT gboolean
+gthumb_extension_is_configurable (void)
+{
+ return FALSE;
+}
+
+
+G_MODULE_EXPORT void
+gthumb_extension_configure (GtkWindow *parent)
+{
+}
diff --git a/extensions/image_tools/Makefile.am b/extensions/image_tools/Makefile.am
new file mode 100644
index 0000000..3561a65
--- /dev/null
+++ b/extensions/image_tools/Makefile.am
@@ -0,0 +1,40 @@
+SUBDIRS = data
+
+extensiondir = $(libdir)/gthumb-2.0/extensions
+extension_LTLIBRARIES = libimage_tools.la
+
+libimage_tools_la_SOURCES = \
+ gth-image-tool-crop.c \
+ gth-image-tool-crop.h \
+ gth-image-tool-desaturate.c \
+ gth-image-tool-desaturate.h \
+ gth-image-tool-redo.c \
+ gth-image-tool-redo.h \
+ gth-image-tool-save.c \
+ gth-image-tool-save.h \
+ gth-image-tool-undo.c \
+ gth-image-tool-undo.h \
+ main.c
+
+libimage_tools_la_CFLAGS = $(GTHUMB_CFLAGS) -I$(top_srcdir) -I$(top_builddir)/gthumb
+libimage_tools_la_LDFLAGS = $(EXTENSION_LIBTOOL_FLAGS)
+libimage_tools_la_LIBADD = $(GTHUMB_LIBS)
+libimage_tools_la_DEPENDENCIES = $(top_builddir)/gthumb/gthumb$(EXEEXT)
+
+extensioninidir = $(extensiondir)
+extensionini_in_files = image_tools.extension.in.in
+extensionini_DATA = $(extensionini_in_files:.extension.in.in=.extension)
+
+%.extension.in: %.extension.in.in $(extension_LTLIBRARIES)
+ sed -e "s|%LIBRARY%|`. ./$(extension_LTLIBRARIES) && echo $$dlname`|" \
+ $< > $@
+
+%.extension: %.extension.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*po) ; LC_ALL=C $(INTLTOOL_MERGE) -d -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< $@
+
+EXTRA_DIST = $(extensionini_in_files)
+
+CLEANFILES = $(extensionini_DATA)
+
+DISTCLEANFILES = $(extensionini_DATA)
+
+-include $(top_srcdir)/git.mk
diff --git a/extensions/image_tools/data/Makefile.am b/extensions/image_tools/data/Makefile.am
new file mode 100644
index 0000000..4d5385d
--- /dev/null
+++ b/extensions/image_tools/data/Makefile.am
@@ -0,0 +1,2 @@
+SUBDIRS = ui
+-include $(top_srcdir)/git.mk
diff --git a/extensions/image_tools/data/ui/Makefile.am b/extensions/image_tools/data/ui/Makefile.am
new file mode 100644
index 0000000..eee4413
--- /dev/null
+++ b/extensions/image_tools/data/ui/Makefile.am
@@ -0,0 +1,5 @@
+uidir = $(datadir)/gthumb/ui
+ui_DATA = crop-options.ui
+EXTRA_DIST = $(ui_DATA)
+
+-include $(top_srcdir)/git.mk
diff --git a/extensions/image_tools/data/ui/crop-options.ui b/extensions/image_tools/data/ui/crop-options.ui
new file mode 100644
index 0000000..236d0aa
--- /dev/null
+++ b/extensions/image_tools/data/ui/crop-options.ui
@@ -0,0 +1,413 @@
+<?xml version="1.0"?>
+<interface>
+ <requires lib="gtk+" version="2.16"/>
+ <!-- interface-naming-policy project-wide -->
+ <object class="GtkAlignment" id="options">
+ <property name="visible">True</property>
+ <property name="top_padding">6</property>
+ <child>
+ <object class="GtkVBox" id="vbox2">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkVBox" id="vbox1">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkFrame" id="frame4">
+ <property name="visible">True</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkAlignment" id="alignment12">
+ <property name="visible">True</property>
+ <property name="top_padding">6</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkVBox" id="vbox10">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkTable" id="table2">
+ <property name="visible">True</property>
+ <property name="n_rows">4</property>
+ <property name="n_columns">2</property>
+ <property name="column_spacing">6</property>
+ <property name="row_spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label3">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">_X:</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label5">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">_Y:</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label4">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">_Width:</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label6">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">_Height:</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox5">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkSpinButton" id="crop_x_spinbutton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">●</property>
+ <property name="adjustment">crop_x_adjustment</property>
+ <property name="climb_rate">1</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options">GTK_FILL</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox6">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkSpinButton" id="crop_y_spinbutton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">●</property>
+ <property name="adjustment">crop_y_adjustment</property>
+ <property name="climb_rate">1</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options">GTK_FILL</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox7">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkSpinButton" id="crop_width_spinbutton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">●</property>
+ <property name="adjustment">crop_width_adjustment</property>
+ <property name="climb_rate">1</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options">GTK_FILL</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox8">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkSpinButton" id="crop_height_spinbutton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">●</property>
+ <property name="adjustment">crop_height_adjustment</property>
+ <property name="climb_rate">1</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options">GTK_FILL</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label15">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes"><b>Selection</b></property>
+ <property name="use_markup">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame" id="frame5">
+ <property name="visible">True</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkAlignment" id="alignment6">
+ <property name="visible">True</property>
+ <property name="top_padding">6</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkVBox" id="vbox8">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkHBox" id="ratio_combobox_box">
+ <property name="visible">True</property>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="custom_ratio_box">
+ <property name="visible">True</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkSpinButton" id="ratio_w_spinbutton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">●</property>
+ <property name="adjustment">ratio_w_adjustment</property>
+ <property name="climb_rate">1</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label17">
+ <property name="visible">True</property>
+ <property name="label">:</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="ratio_h_spinbutton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">●</property>
+ <property name="adjustment">ratio_h_adjustment</property>
+ <property name="climb_rate">1</property>
+ <property name="update_policy">if-valid</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="invert_ratio_checkbutton">
+ <property name="label" translatable="yes">I_nvert aspect ratio</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label16">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes"><b>Aspect ratio</b></property>
+ <property name="use_markup">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHSeparator" id="hseparator1">
+ <property name="visible">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="padding">6</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHButtonBox" id="hbuttonbox1">
+ <property name="visible">True</property>
+ <property name="spacing">12</property>
+ <property name="layout_style">center</property>
+ <child>
+ <object class="GtkButton" id="crop_button">
+ <property name="label" translatable="yes">_Crop</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="cancel_button">
+ <property name="label" translatable="yes">gtk-cancel</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_stock">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="padding">6</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <object class="GtkAdjustment" id="ratio_w_adjustment">
+ <property name="upper">9999</property>
+ <property name="step_increment">1</property>
+ </object>
+ <object class="GtkAdjustment" id="crop_x_adjustment">
+ <property name="upper">100</property>
+ <property name="step_increment">1</property>
+ </object>
+ <object class="GtkAdjustment" id="crop_y_adjustment">
+ <property name="upper">100</property>
+ <property name="step_increment">1</property>
+ </object>
+ <object class="GtkAdjustment" id="crop_width_adjustment">
+ <property name="upper">100</property>
+ <property name="step_increment">1</property>
+ </object>
+ <object class="GtkAdjustment" id="crop_height_adjustment">
+ <property name="upper">100</property>
+ <property name="step_increment">1</property>
+ </object>
+ <object class="GtkAdjustment" id="ratio_h_adjustment">
+ <property name="upper">100</property>
+ <property name="step_increment">1</property>
+ </object>
+</interface>
diff --git a/extensions/image_tools/gth-image-tool-crop.c b/extensions/image_tools/gth-image-tool-crop.c
new file mode 100644
index 0000000..0338854
--- /dev/null
+++ b/extensions/image_tools/gth-image-tool-crop.c
@@ -0,0 +1,528 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <gthumb.h>
+#include <extensions/image_viewer/gth-image-viewer-page.h>
+#include "gth-image-tool-crop.h"
+
+
+#define GET_WIDGET(x) (_gtk_builder_get_widget (self->priv->builder, (x)))
+
+
+typedef enum {
+ GTH_CROP_RATIO_NONE = 0,
+ GTH_CROP_RATIO_SQUARE,
+ GTH_CROP_RATIO_IMAGE,
+ GTH_CROP_RATIO_DISPLAY,
+ GTH_CROP_RATIO_4_3,
+ GTH_CROP_RATIO_4_6,
+ GTH_CROP_RATIO_5_7,
+ GTH_CROP_RATIO_8_10,
+ GTH_CROP_RATIO_CUSTOM
+} GthCropRatio;
+
+
+static gpointer parent_class = NULL;
+
+
+struct _GthImageToolCropPrivate {
+ GdkPixbuf *src_pixbuf;
+ GtkBuilder *builder;
+ int pixbuf_width;
+ int pixbuf_height;
+ int screen_width;
+ int screen_height;
+ GthImageSelector *selector;
+ GtkWidget *ratio_combobox;
+ GtkWidget *crop_x_spinbutton;
+ GtkWidget *crop_y_spinbutton;
+ GtkWidget *crop_width_spinbutton;
+ GtkWidget *crop_height_spinbutton;
+};
+
+
+static void
+gth_image_tool_crop_real_update_sensitivity (GthFileTool *base,
+ GtkWidget *window)
+{
+ GthImageToolCrop *self;
+
+ self = (GthImageToolCrop*) base;
+}
+
+
+static void
+gth_file_tool_interface_init (GthFileToolIface *iface)
+{
+ iface->update_sensitivity = gth_image_tool_crop_real_update_sensitivity;
+}
+
+
+static void
+cancel_button_clicked_cb (GtkButton *button,
+ GthImageToolCrop *self)
+{
+ GtkWidget *window;
+ GtkWidget *sidebar;
+ GtkWidget *viewer_page;
+ GtkWidget *viewer;
+
+ window = gtk_widget_get_toplevel (GTK_WIDGET (self));
+ sidebar = gth_browser_get_viewer_sidebar (GTH_BROWSER (window));
+ gth_sidebar_set_options (GTH_SIDEBAR (sidebar), NULL, NULL, NULL);
+ gtk_notebook_set_current_page (GTK_NOTEBOOK (sidebar), GTH_SIDEBAR_PAGE_TOOLS);
+
+ viewer_page = gth_browser_get_viewer_page (GTH_BROWSER (window));
+ viewer = gth_image_viewer_page_get_image_viewer (GTH_IMAGE_VIEWER_PAGE (viewer_page));
+ gth_image_viewer_set_tool (GTH_IMAGE_VIEWER (viewer), NULL);
+
+ _g_object_unref (self->priv->src_pixbuf);
+ _g_object_unref (self->priv->builder);
+ self->priv->src_pixbuf = NULL;
+ self->priv->builder = NULL;
+}
+
+
+static void
+crop_button_clicked_cb (GtkButton *button,
+ GthImageToolCrop *self)
+{
+ GdkRectangle selection;
+ GdkPixbuf *new_pixbuf;
+
+ gth_image_selector_get_selection (self->priv->selector, &selection);
+ if ((selection.width == 0) || (selection.height == 0))
+ return;
+
+ new_pixbuf = gdk_pixbuf_new_subpixbuf (self->priv->src_pixbuf,
+ selection.x,
+ selection.y,
+ selection.width,
+ selection.height);
+ if (new_pixbuf != NULL) {
+ GtkWidget *window;
+ GtkWidget *viewer_page;
+
+ window = gtk_widget_get_toplevel (GTK_WIDGET (self));
+ viewer_page = gth_browser_get_viewer_page (GTH_BROWSER (window));
+ gth_image_viewer_page_set_pixbuf (GTH_IMAGE_VIEWER_PAGE (viewer_page), new_pixbuf);
+ cancel_button_clicked_cb (NULL, self);
+
+ g_object_unref (new_pixbuf);
+ }
+}
+
+
+static void
+selection_x_value_changed_cb (GtkSpinButton *spin,
+ GthImageToolCrop *self)
+{
+ gth_image_selector_set_selection_x (self->priv->selector, gtk_spin_button_get_value_as_int (spin));
+}
+
+
+static void
+selection_y_value_changed_cb (GtkSpinButton *spin,
+ GthImageToolCrop *self)
+{
+ gth_image_selector_set_selection_y (self->priv->selector, gtk_spin_button_get_value_as_int (spin));
+}
+
+
+static void
+selection_width_value_changed_cb (GtkSpinButton *spin,
+ GthImageToolCrop *self)
+{
+ gth_image_selector_set_selection_width (self->priv->selector, gtk_spin_button_get_value_as_int (spin));
+}
+
+
+static void
+selection_height_value_changed_cb (GtkSpinButton *spin,
+ GthImageToolCrop *self)
+{
+ gth_image_selector_set_selection_height (self->priv->selector, gtk_spin_button_get_value_as_int (spin));
+}
+
+
+static void
+set_spin_range_value (GthImageToolCrop *self,
+ GtkWidget *spin,
+ int min,
+ int max,
+ int x)
+{
+ g_signal_handlers_block_by_data (G_OBJECT (spin), self);
+ gtk_spin_button_set_range (GTK_SPIN_BUTTON (spin), min, max);
+ gtk_spin_button_set_value (GTK_SPIN_BUTTON (spin), x);
+ g_signal_handlers_unblock_by_data (G_OBJECT (spin), self);
+}
+
+
+static void
+selector_selection_changed_cb (GthImageSelector *selector,
+ GthImageToolCrop *self)
+{
+ GdkRectangle selection;
+ int min, max;
+
+ gth_image_selector_get_selection (selector, &selection);
+
+ min = 0;
+ max = self->priv->pixbuf_width - selection.width;
+ set_spin_range_value (self, self->priv->crop_x_spinbutton, min, max, selection.x);
+
+ min = 0;
+ max = self->priv->pixbuf_height - selection.height;
+ set_spin_range_value (self, self->priv->crop_y_spinbutton, min, max, selection.y);
+
+ min = 0;
+ max = self->priv->pixbuf_width - selection.x;
+ set_spin_range_value (self, self->priv->crop_width_spinbutton, min, max, selection.width);
+
+ min = 0;
+ max = self->priv->pixbuf_height - selection.y;
+ set_spin_range_value (self, self->priv->crop_height_spinbutton, min, max, selection.height);
+
+ gth_image_selector_set_mask_visible (selector, (selection.width != 0 || selection.height != 0));
+}
+
+
+static void
+set_spin_value (GthImageToolCrop *self,
+ GtkWidget *spin,
+ int x)
+{
+ g_signal_handlers_block_by_data (G_OBJECT (spin), self);
+ gtk_spin_button_set_value (GTK_SPIN_BUTTON (spin), x);
+ g_signal_handlers_unblock_by_data (G_OBJECT (spin), self);
+}
+
+
+static void
+ratio_combobox_changed_cb (GtkComboBox *combobox,
+ GthImageToolCrop *self)
+{
+ GtkWidget *ratio_w_spinbutton;
+ GtkWidget *ratio_h_spinbutton;
+ int idx;
+ int w, h;
+ gboolean use_ratio;
+
+ ratio_w_spinbutton = GET_WIDGET ("ratio_w_spinbutton");
+ ratio_h_spinbutton = GET_WIDGET ("ratio_h_spinbutton");
+ w = h = 1;
+ use_ratio = TRUE;
+ idx = gtk_combo_box_get_active (combobox);
+
+ switch (idx) {
+ case GTH_CROP_RATIO_NONE:
+ use_ratio = FALSE;
+ break;
+ case GTH_CROP_RATIO_SQUARE:
+ w = h = 1;
+ break;
+ case GTH_CROP_RATIO_IMAGE:
+ w = self->priv->pixbuf_width;
+ h = self->priv->pixbuf_height;
+ break;
+ case GTH_CROP_RATIO_DISPLAY:
+ w = self->priv->screen_width;
+ h = self->priv->screen_height;
+ break;
+ case GTH_CROP_RATIO_4_3:
+ w = 4;
+ h = 3;
+ break;
+ case GTH_CROP_RATIO_4_6:
+ w = 4;
+ h = 6;
+ break;
+ case GTH_CROP_RATIO_5_7:
+ w = 5;
+ h = 7;
+ break;
+ case GTH_CROP_RATIO_8_10:
+ w = 8;
+ h = 10;
+ break;
+ case GTH_CROP_RATIO_CUSTOM:
+ default:
+ w = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (ratio_w_spinbutton));
+ h = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (ratio_h_spinbutton));
+ break;
+ }
+
+ gtk_widget_set_sensitive (GET_WIDGET ("custom_ratio_box"), idx == GTH_CROP_RATIO_CUSTOM);
+ gtk_widget_set_sensitive (GET_WIDGET ("invert_ratio_checkbutton"), use_ratio);
+ set_spin_value (self, ratio_w_spinbutton, w);
+ set_spin_value (self, ratio_h_spinbutton, h);
+ gth_image_selector_set_ratio (GTH_IMAGE_SELECTOR (self->priv->selector),
+ use_ratio,
+ (double) w / h,
+ FALSE);
+}
+
+
+static void
+update_ratio (GtkSpinButton *spin,
+ GthImageToolCrop *self,
+ gboolean swap_x_and_y_to_start)
+{
+ gboolean use_ratio;
+ int w, h;
+ double ratio;
+
+ use_ratio = gtk_combo_box_get_active (GTK_COMBO_BOX (self->priv->ratio_combobox)) != GTH_CROP_RATIO_NONE;
+ w = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (GET_WIDGET ("ratio_w_spinbutton")));
+ h = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (GET_WIDGET ("ratio_h_spinbutton")));
+
+ if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (GET_WIDGET ("invert_ratio_checkbutton"))))
+ ratio = (double) h / w;
+ else
+ ratio = (double) w / h;
+ gth_image_selector_set_ratio (self->priv->selector,
+ use_ratio,
+ ratio,
+ swap_x_and_y_to_start);
+}
+
+
+static void
+ratio_value_changed_cb (GtkSpinButton *spin,
+ GthImageToolCrop *self)
+{
+ update_ratio (spin, self, FALSE);
+}
+
+
+static void
+invert_ratio_changed_cb (GtkSpinButton *spin,
+ GthImageToolCrop *self)
+{
+ update_ratio (spin, self, TRUE);
+}
+
+
+static void
+button_clicked_cb (GtkButton *button,
+ gpointer data)
+{
+ GthImageToolCrop *self;
+ GtkWidget *window;
+ GtkWidget *viewer_page;
+ GtkWidget *viewer;
+ GtkWidget *options;
+ GtkWidget *sidebar;
+ char *text;
+
+ self = (GthImageToolCrop *) button;
+
+ window = gtk_widget_get_toplevel (GTK_WIDGET (button));
+ viewer_page = gth_browser_get_viewer_page (GTH_BROWSER (window));
+ if (! GTH_IS_IMAGE_VIEWER_PAGE (viewer_page))
+ return;
+
+ viewer = gth_image_viewer_page_get_image_viewer (GTH_IMAGE_VIEWER_PAGE (viewer_page));
+ self->priv->src_pixbuf = gth_image_viewer_get_current_pixbuf (GTH_IMAGE_VIEWER (viewer));
+ if (self->priv->src_pixbuf == NULL)
+ return;
+
+ g_object_ref (self->priv->src_pixbuf);
+
+ self->priv->pixbuf_width = gdk_pixbuf_get_width (self->priv->src_pixbuf);
+ self->priv->pixbuf_height = gdk_pixbuf_get_height (self->priv->src_pixbuf);
+ _gtk_widget_get_screen_size (window, &self->priv->screen_width, &self->priv->screen_height);
+
+ self->priv->builder = _gtk_builder_new_from_file ("crop-options.ui", "image_tools");
+ options = _gtk_builder_get_widget (self->priv->builder, "options");
+ self->priv->crop_x_spinbutton = _gtk_builder_get_widget (self->priv->builder, "crop_x_spinbutton");
+ self->priv->crop_y_spinbutton = _gtk_builder_get_widget (self->priv->builder, "crop_y_spinbutton");
+ self->priv->crop_width_spinbutton = _gtk_builder_get_widget (self->priv->builder, "crop_width_spinbutton");
+ self->priv->crop_height_spinbutton = _gtk_builder_get_widget (self->priv->builder, "crop_height_spinbutton");
+
+ self->priv->ratio_combobox = _gtk_combo_box_new_with_texts (_("None"), _("Square"), NULL);
+ text = g_strdup_printf (_("%d x %d (Image)"), self->priv->pixbuf_width, self->priv->pixbuf_height);
+ gtk_combo_box_append_text (GTK_COMBO_BOX (self->priv->ratio_combobox), text);
+ g_free (text);
+ text = g_strdup_printf (_("%d x %d (Screen)"), self->priv->screen_width, self->priv->screen_height);
+ gtk_combo_box_append_text (GTK_COMBO_BOX (self->priv->ratio_combobox), text);
+ g_free (text);
+ _gtk_combo_box_append_texts (GTK_COMBO_BOX (self->priv->ratio_combobox),
+ _("4 x 3 (Book, DVD)"),
+ _("4 x 6 (Postcard)"),
+ _("5 x 7"),
+ _("8 x 10"),
+ _("Custom"),
+ NULL);
+ gtk_widget_show (self->priv->ratio_combobox);
+ gtk_box_pack_start (GTK_BOX (GET_WIDGET ("ratio_combobox_box")), self->priv->ratio_combobox, FALSE, FALSE, 0);
+
+ gtk_widget_show (options);
+ sidebar = gth_browser_get_viewer_sidebar (GTH_BROWSER (window));
+ gth_sidebar_set_options (GTH_SIDEBAR (sidebar), "gtk-edit", _("Crop"), options);
+
+ g_signal_connect (GET_WIDGET ("crop_button"),
+ "clicked",
+ G_CALLBACK (crop_button_clicked_cb),
+ self);
+ g_signal_connect (GET_WIDGET ("cancel_button"),
+ "clicked",
+ G_CALLBACK (cancel_button_clicked_cb),
+ self);
+ g_signal_connect (G_OBJECT (self->priv->crop_x_spinbutton),
+ "value-changed",
+ G_CALLBACK (selection_x_value_changed_cb),
+ self);
+ g_signal_connect (G_OBJECT (self->priv->crop_y_spinbutton),
+ "value-changed",
+ G_CALLBACK (selection_y_value_changed_cb),
+ self);
+ g_signal_connect (G_OBJECT (self->priv->crop_width_spinbutton),
+ "value-changed",
+ G_CALLBACK (selection_width_value_changed_cb),
+ self);
+ g_signal_connect (G_OBJECT (self->priv->crop_height_spinbutton),
+ "value-changed",
+ G_CALLBACK (selection_height_value_changed_cb),
+ self);
+ g_signal_connect (self->priv->ratio_combobox,
+ "changed",
+ G_CALLBACK (ratio_combobox_changed_cb),
+ self);
+ g_signal_connect (GET_WIDGET ("ratio_w_spinbutton"),
+ "value_changed",
+ G_CALLBACK (ratio_value_changed_cb),
+ self);
+ g_signal_connect (GET_WIDGET ("ratio_h_spinbutton"),
+ "value_changed",
+ G_CALLBACK (ratio_value_changed_cb),
+ self);
+ g_signal_connect (GET_WIDGET ("invert_ratio_checkbutton"),
+ "toggled",
+ G_CALLBACK (invert_ratio_changed_cb),
+ self);
+
+ self->priv->selector = (GthImageSelector *) gth_image_selector_new (GTH_IMAGE_VIEWER (viewer), GTH_SELECTOR_TYPE_REGION);
+ g_signal_connect (self->priv->selector,
+ "selection-changed",
+ G_CALLBACK (selector_selection_changed_cb),
+ self);
+ /*g_signal_connect (self->priv->selector,
+ "mask_visibility_changed",
+ G_CALLBACK (selector_mask_visibility_changed_cb),
+ self);*/
+
+ gtk_combo_box_set_active (GTK_COMBO_BOX (self->priv->ratio_combobox), 0);
+ gtk_notebook_set_current_page (GTK_NOTEBOOK (sidebar), GTH_SIDEBAR_PAGE_OPTIONS);
+ gth_image_viewer_set_tool (GTH_IMAGE_VIEWER (viewer), (GthImageTool *) self->priv->selector);
+}
+
+
+static void
+gth_image_tool_crop_instance_init (GthImageToolCrop *self)
+{
+ GtkWidget *hbox;
+ GtkWidget *icon;
+ GtkWidget *label;
+
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GTH_TYPE_IMAGE_TOOL_CROP, GthImageToolCropPrivate);
+ self->priv->builder = NULL;
+
+ gtk_button_set_relief (GTK_BUTTON (self), GTK_RELIEF_NONE);
+
+ hbox = gtk_hbox_new (FALSE, 6);
+
+ icon = gtk_image_new_from_stock (GTK_STOCK_EDIT, GTK_ICON_SIZE_MENU);
+ gtk_widget_show (icon);
+ gtk_box_pack_start (GTK_BOX (hbox), icon, FALSE, FALSE, 0);
+
+ label = gtk_label_new (_("Crop"));
+ gtk_widget_show (label);
+ gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
+
+ gtk_widget_show (hbox);
+ gtk_container_add (GTK_CONTAINER (self), hbox);
+
+ g_signal_connect (self, "clicked", G_CALLBACK (button_clicked_cb), self);
+}
+
+
+static void
+gth_image_tool_crop_finalize (GObject *object)
+{
+ GthImageToolCrop *self;
+
+ g_return_if_fail (object != NULL);
+ g_return_if_fail (GTH_IS_IMAGE_TOOL_CROP (object));
+
+ self = (GthImageToolCrop *) object;
+
+ _g_object_unref (self->priv->src_pixbuf);
+ _g_object_unref (self->priv->selector);
+ _g_object_unref (self->priv->builder);
+
+ /* Chain up */
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+
+static void
+gth_image_tool_crop_class_init (GthImageToolCropClass *class)
+{
+ GObjectClass *gobject_class;
+
+ parent_class = g_type_class_peek_parent (class);
+ g_type_class_add_private (class, sizeof (GthImageToolCropPrivate));
+
+ gobject_class = (GObjectClass*) class;
+ gobject_class->finalize = gth_image_tool_crop_finalize;
+}
+
+
+GType
+gth_image_tool_crop_get_type (void) {
+ static GType type_id = 0;
+ if (type_id == 0) {
+ static const GTypeInfo g_define_type_info = {
+ sizeof (GthImageToolCropClass),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) gth_image_tool_crop_class_init,
+ (GClassFinalizeFunc) NULL,
+ NULL,
+ sizeof (GthImageToolCrop),
+ 0,
+ (GInstanceInitFunc) gth_image_tool_crop_instance_init,
+ NULL
+ };
+ static const GInterfaceInfo gth_file_tool_info = {
+ (GInterfaceInitFunc) gth_file_tool_interface_init,
+ (GInterfaceFinalizeFunc) NULL,
+ NULL
+ };
+ type_id = g_type_register_static (GTK_TYPE_BUTTON, "GthImageToolCrop", &g_define_type_info, 0);
+ g_type_add_interface_static (type_id, GTH_TYPE_FILE_TOOL, >h_file_tool_info);
+ }
+ return type_id;
+}
diff --git a/extensions/image_tools/gth-image-tool-crop.h b/extensions/image_tools/gth-image-tool-crop.h
new file mode 100644
index 0000000..037106a
--- /dev/null
+++ b/extensions/image_tools/gth-image-tool-crop.h
@@ -0,0 +1,54 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GTH_IMAGE_TOOL_CROP_H
+#define GTH_IMAGE_TOOL_CROP_H
+
+#include <gthumb.h>
+
+G_BEGIN_DECLS
+
+#define GTH_TYPE_IMAGE_TOOL_CROP (gth_image_tool_crop_get_type ())
+#define GTH_IMAGE_TOOL_CROP(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTH_TYPE_IMAGE_TOOL_CROP, GthImageToolCrop))
+#define GTH_IMAGE_TOOL_CROP_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTH_TYPE_IMAGE_TOOL_CROP, GthImageToolCropClass))
+#define GTH_IS_IMAGE_TOOL_CROP(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTH_TYPE_IMAGE_TOOL_CROP))
+#define GTH_IS_IMAGE_TOOL_CROP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTH_TYPE_IMAGE_TOOL_CROP))
+#define GTH_IMAGE_TOOL_CROP_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTH_TYPE_IMAGE_TOOL_CROP, GthImageToolCropClass))
+
+typedef struct _GthImageToolCrop GthImageToolCrop;
+typedef struct _GthImageToolCropClass GthImageToolCropClass;
+typedef struct _GthImageToolCropPrivate GthImageToolCropPrivate;
+
+struct _GthImageToolCrop {
+ GtkButton parent_instance;
+ GthImageToolCropPrivate *priv;
+};
+
+struct _GthImageToolCropClass {
+ GtkButtonClass parent_class;
+};
+
+GType gth_image_tool_crop_get_type (void);
+
+G_END_DECLS
+
+#endif /* GTH_IMAGE_TOOL_CROP_H */
diff --git a/extensions/image_tools/gth-image-tool-desaturate.c b/extensions/image_tools/gth-image-tool-desaturate.c
new file mode 100644
index 0000000..179e739
--- /dev/null
+++ b/extensions/image_tools/gth-image-tool-desaturate.c
@@ -0,0 +1,161 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <gthumb.h>
+#include <extensions/image_viewer/gth-image-viewer-page.h>
+#include "gth-image-tool-desaturate.h"
+
+
+static void
+gth_image_tool_desaturate_real_update_sensitivity (GthFileTool *base,
+ GtkWidget *window)
+{
+ GthImageToolDesaturate *self;
+
+ self = (GthImageToolDesaturate*) base;
+}
+
+
+static void
+gth_file_tool_interface_init (GthFileToolIface *iface)
+{
+ iface->update_sensitivity = gth_image_tool_desaturate_real_update_sensitivity;
+}
+
+
+static void
+desaturate_step (GthPixbufTask *pixop)
+{
+ guchar min, max, lightness;
+
+ max = MAX (pixop->src_pixel[RED_PIX], pixop->src_pixel[GREEN_PIX]);
+ max = MAX (max, pixop->src_pixel[BLUE_PIX]);
+
+ min = MIN (pixop->src_pixel[RED_PIX], pixop->src_pixel[GREEN_PIX]);
+ min = MIN (min, pixop->src_pixel[BLUE_PIX]);
+
+ lightness = (max + min) / 2;
+
+ pixop->dest_pixel[RED_PIX] = lightness;
+ pixop->dest_pixel[GREEN_PIX] = lightness;
+ pixop->dest_pixel[BLUE_PIX] = lightness;
+
+ if (pixop->has_alpha)
+ pixop->dest_pixel[ALPHA_PIX] = pixop->src_pixel[ALPHA_PIX];
+}
+
+
+static void
+desaturate_release (GthPixbufTask *pixop)
+{
+ gth_image_viewer_page_set_pixbuf (GTH_IMAGE_VIEWER_PAGE (pixop->data), pixop->dest);
+}
+
+
+static void
+button_clicked_cb (GtkButton *button,
+ gpointer data)
+{
+ GtkWidget *window;
+ GtkWidget *viewer_page;
+ GtkWidget *viewer;
+ GdkPixbuf *src_pixbuf;
+ GdkPixbuf *dest_pixbuf;
+ GthTask *task;
+
+ window = gtk_widget_get_toplevel (GTK_WIDGET (button));
+ viewer_page = gth_browser_get_viewer_page (GTH_BROWSER (window));
+ if (! GTH_IS_IMAGE_VIEWER_PAGE (viewer_page))
+ return;
+
+ viewer = gth_image_viewer_page_get_image_viewer (GTH_IMAGE_VIEWER_PAGE (viewer_page));
+ src_pixbuf = gth_image_viewer_get_current_pixbuf (GTH_IMAGE_VIEWER (viewer));
+ if (src_pixbuf == NULL)
+ return;
+
+ dest_pixbuf = gdk_pixbuf_copy (src_pixbuf);
+ task = gth_pixbuf_task_new (src_pixbuf,
+ dest_pixbuf,
+ NULL,
+ desaturate_step,
+ desaturate_release,
+ viewer_page);
+ gth_browser_exec_task (GTH_BROWSER (window), task);
+
+ g_object_unref (task);
+ g_object_unref (dest_pixbuf);
+}
+
+
+static void
+gth_image_tool_desaturate_instance_init (GthImageToolDesaturate *self)
+{
+ GtkWidget *hbox;
+ GtkWidget *icon;
+ GtkWidget *label;
+
+ gtk_button_set_relief (GTK_BUTTON (self), GTK_RELIEF_NONE);
+
+ hbox = gtk_hbox_new (FALSE, 6);
+
+ icon = gtk_image_new_from_stock (GTK_STOCK_EDIT, GTK_ICON_SIZE_MENU);
+ gtk_widget_show (icon);
+ gtk_box_pack_start (GTK_BOX (hbox), icon, FALSE, FALSE, 0);
+
+ label = gtk_label_new (_("Desaturate"));
+ gtk_widget_show (label);
+ gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
+
+ gtk_widget_show (hbox);
+ gtk_container_add (GTK_CONTAINER (self), hbox);
+
+ g_signal_connect (self, "clicked", G_CALLBACK (button_clicked_cb), self);
+}
+
+
+GType
+gth_image_tool_desaturate_get_type (void) {
+ static GType type_id = 0;
+ if (type_id == 0) {
+ static const GTypeInfo g_define_type_info = {
+ sizeof (GthImageToolDesaturateClass),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) NULL,
+ (GClassFinalizeFunc) NULL,
+ NULL,
+ sizeof (GthImageToolDesaturate),
+ 0,
+ (GInstanceInitFunc) gth_image_tool_desaturate_instance_init,
+ NULL
+ };
+ static const GInterfaceInfo gth_file_tool_info = {
+ (GInterfaceInitFunc) gth_file_tool_interface_init,
+ (GInterfaceFinalizeFunc) NULL,
+ NULL
+ };
+ type_id = g_type_register_static (GTK_TYPE_BUTTON, "GthImageToolDesaturate", &g_define_type_info, 0);
+ g_type_add_interface_static (type_id, GTH_TYPE_FILE_TOOL, >h_file_tool_info);
+ }
+ return type_id;
+}
diff --git a/extensions/image_tools/gth-image-tool-desaturate.h b/extensions/image_tools/gth-image-tool-desaturate.h
new file mode 100644
index 0000000..afa6ef4
--- /dev/null
+++ b/extensions/image_tools/gth-image-tool-desaturate.h
@@ -0,0 +1,52 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GTH_IMAGE_TOOL_DESATURATE_H
+#define GTH_IMAGE_TOOL_DESATURATE_H
+
+#include <gthumb.h>
+
+G_BEGIN_DECLS
+
+#define GTH_TYPE_IMAGE_TOOL_DESATURATE (gth_image_tool_desaturate_get_type ())
+#define GTH_IMAGE_TOOL_DESATURATE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTH_TYPE_IMAGE_TOOL_DESATURATE, GthImageToolDesaturate))
+#define GTH_IMAGE_TOOL_DESATURATE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTH_TYPE_IMAGE_TOOL_DESATURATE, GthImageToolDesaturateClass))
+#define GTH_IS_IMAGE_TOOL_DESATURATE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTH_TYPE_IMAGE_TOOL_DESATURATE))
+#define GTH_IS_IMAGE_TOOL_DESATURATE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTH_TYPE_IMAGE_TOOL_DESATURATE))
+#define GTH_IMAGE_TOOL_DESATURATE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTH_TYPE_IMAGE_TOOL_DESATURATE, GthImageToolDesaturateClass))
+
+typedef struct _GthImageToolDesaturate GthImageToolDesaturate;
+typedef struct _GthImageToolDesaturateClass GthImageToolDesaturateClass;
+
+struct _GthImageToolDesaturate {
+ GtkButton parent_instance;
+};
+
+struct _GthImageToolDesaturateClass {
+ GtkButtonClass parent_class;
+};
+
+GType gth_image_tool_desaturate_get_type (void);
+
+G_END_DECLS
+
+#endif /* GTH_IMAGE_TOOL_DESATURATE_H */
diff --git a/extensions/image_tools/gth-image-tool-redo.c b/extensions/image_tools/gth-image-tool-redo.c
new file mode 100644
index 0000000..c09451e
--- /dev/null
+++ b/extensions/image_tools/gth-image-tool-redo.c
@@ -0,0 +1,118 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <gthumb.h>
+#include <extensions/image_viewer/gth-image-viewer-page.h>
+#include "gth-image-tool-redo.h"
+
+
+static void
+gth_file_tool_redo_real_update_sensitivity (GthFileTool *base,
+ GtkWidget *window)
+{
+ GtkWidget *viewer_page;
+
+ viewer_page = gth_browser_get_viewer_page (GTH_BROWSER (window));
+ if (! GTH_IS_IMAGE_VIEWER_PAGE (viewer_page))
+ gtk_widget_set_sensitive (GTK_WIDGET (base), FALSE);
+ else
+ gtk_widget_set_sensitive (GTK_WIDGET (base), gth_image_history_can_redo (gth_image_viewer_page_get_history (GTH_IMAGE_VIEWER_PAGE (viewer_page))));
+
+}
+
+
+static void
+gth_file_tool_interface_init (GthFileToolIface *iface)
+{
+ iface->update_sensitivity = gth_file_tool_redo_real_update_sensitivity;
+}
+
+
+static void
+button_clicked_cb (GtkButton *button,
+ gpointer data)
+{
+ GtkWidget *window;
+ GtkWidget *viewer_page;
+
+ window = gtk_widget_get_toplevel (GTK_WIDGET (button));
+ viewer_page = gth_browser_get_viewer_page (GTH_BROWSER (window));
+ if (! GTH_IS_IMAGE_VIEWER_PAGE (viewer_page))
+ return;
+
+ gth_image_viewer_page_redo (GTH_IMAGE_VIEWER_PAGE (viewer_page));
+}
+
+
+static void
+gth_image_tool_redo_instance_init (GthImageToolRedo *self)
+{
+ GtkWidget *hbox;
+ GtkWidget *icon;
+ GtkWidget *label;
+
+ gtk_button_set_relief (GTK_BUTTON (self), GTK_RELIEF_NONE);
+
+ hbox = gtk_hbox_new (FALSE, 6);
+
+ icon = gtk_image_new_from_stock (GTK_STOCK_REDO, GTK_ICON_SIZE_MENU);
+ gtk_widget_show (icon);
+ gtk_box_pack_start (GTK_BOX (hbox), icon, FALSE, FALSE, 0);
+
+ label = gtk_label_new (_("Redo"));
+ gtk_widget_show (label);
+ gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
+
+ gtk_widget_show (hbox);
+ gtk_container_add (GTK_CONTAINER (self), hbox);
+
+ g_signal_connect (self, "clicked", G_CALLBACK (button_clicked_cb), self);
+}
+
+
+GType
+gth_image_tool_redo_get_type (void) {
+ static GType type_id = 0;
+ if (type_id == 0) {
+ static const GTypeInfo g_define_type_info = {
+ sizeof (GthImageToolRedoClass),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) NULL,
+ (GClassFinalizeFunc) NULL,
+ NULL,
+ sizeof (GthImageToolRedo),
+ 0,
+ (GInstanceInitFunc) gth_image_tool_redo_instance_init,
+ NULL
+ };
+ static const GInterfaceInfo gth_file_tool_info = {
+ (GInterfaceInitFunc) gth_file_tool_interface_init,
+ (GInterfaceFinalizeFunc) NULL,
+ NULL
+ };
+ type_id = g_type_register_static (GTK_TYPE_BUTTON, "GthImageToolRedo", &g_define_type_info, 0);
+ g_type_add_interface_static (type_id, GTH_TYPE_FILE_TOOL, >h_file_tool_info);
+ }
+ return type_id;
+}
diff --git a/extensions/image_tools/gth-image-tool-redo.h b/extensions/image_tools/gth-image-tool-redo.h
new file mode 100644
index 0000000..9f0f77b
--- /dev/null
+++ b/extensions/image_tools/gth-image-tool-redo.h
@@ -0,0 +1,52 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GTH_IMAGE_TOOL_REDO_H
+#define GTH_IMAGE_TOOL_REDO_H
+
+#include <gthumb.h>
+
+G_BEGIN_DECLS
+
+#define GTH_TYPE_IMAGE_TOOL_REDO (gth_image_tool_redo_get_type ())
+#define GTH_IMAGE_TOOL_REDO(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTH_TYPE_IMAGE_TOOL_REDO, GthImageToolRedo))
+#define GTH_IMAGE_TOOL_REDO_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTH_TYPE_IMAGE_TOOL_REDO, GthImageToolRedoClass))
+#define GTH_IS_IMAGE_TOOL_REDO(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTH_TYPE_IMAGE_TOOL_REDO))
+#define GTH_IS_IMAGE_TOOL_REDO_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTH_TYPE_IMAGE_TOOL_REDO))
+#define GTH_IMAGE_TOOL_REDO_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTH_TYPE_IMAGE_TOOL_REDO, GthImageToolRedoClass))
+
+typedef struct _GthImageToolRedo GthImageToolRedo;
+typedef struct _GthImageToolRedoClass GthImageToolRedoClass;
+
+struct _GthImageToolRedo {
+ GtkButton parent_instance;
+};
+
+struct _GthImageToolRedoClass {
+ GtkButtonClass parent_class;
+};
+
+GType gth_image_tool_redo_get_type (void);
+
+G_END_DECLS
+
+#endif /* GTH_IMAGE_TOOL_REDO_H */
diff --git a/extensions/image_tools/gth-image-tool-save.c b/extensions/image_tools/gth-image-tool-save.c
new file mode 100644
index 0000000..7c7583a
--- /dev/null
+++ b/extensions/image_tools/gth-image-tool-save.c
@@ -0,0 +1,117 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <gthumb.h>
+#include <extensions/image_viewer/gth-image-viewer-page.h>
+#include "gth-image-tool-save.h"
+
+
+static void
+gth_file_tool_save_real_update_sensitivity (GthFileTool *base,
+ GtkWidget *window)
+{
+ GtkWidget *viewer_page;
+
+ viewer_page = gth_browser_get_viewer_page (GTH_BROWSER (window));
+ if (! GTH_IS_IMAGE_VIEWER_PAGE (viewer_page))
+ gtk_widget_set_sensitive (GTK_WIDGET (base), FALSE);
+ else
+ gtk_widget_set_sensitive (GTK_WIDGET (base), gth_browser_get_file_modified (GTH_BROWSER (window)));
+}
+
+
+static void
+gth_file_tool_interface_init (GthFileToolIface *iface)
+{
+ iface->update_sensitivity = gth_file_tool_save_real_update_sensitivity;
+}
+
+
+static void
+button_clicked_cb (GtkButton *button,
+ gpointer data)
+{
+ GtkWidget *window;
+ GtkWidget *viewer_page;
+
+ window = gtk_widget_get_toplevel (GTK_WIDGET (button));
+ viewer_page = gth_browser_get_viewer_page (GTH_BROWSER (window));
+ if (! GTH_IS_IMAGE_VIEWER_PAGE (viewer_page))
+ return;
+
+ gth_viewer_page_save (GTH_VIEWER_PAGE (viewer_page), NULL, NULL, NULL);
+}
+
+
+static void
+gth_image_tool_save_instance_init (GthImageToolSave *self)
+{
+ GtkWidget *hbox;
+ GtkWidget *icon;
+ GtkWidget *label;
+
+ gtk_button_set_relief (GTK_BUTTON (self), GTK_RELIEF_NONE);
+
+ hbox = gtk_hbox_new (FALSE, 6);
+
+ icon = gtk_image_new_from_stock (GTK_STOCK_SAVE, GTK_ICON_SIZE_MENU);
+ gtk_widget_show (icon);
+ gtk_box_pack_start (GTK_BOX (hbox), icon, FALSE, FALSE, 0);
+
+ label = gtk_label_new (_("Save"));
+ gtk_widget_show (label);
+ gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
+
+ gtk_widget_show (hbox);
+ gtk_container_add (GTK_CONTAINER (self), hbox);
+
+ g_signal_connect (self, "clicked", G_CALLBACK (button_clicked_cb), self);
+}
+
+
+GType
+gth_image_tool_save_get_type (void) {
+ static GType type_id = 0;
+ if (type_id == 0) {
+ static const GTypeInfo g_define_type_info = {
+ sizeof (GthImageToolSaveClass),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) NULL,
+ (GClassFinalizeFunc) NULL,
+ NULL,
+ sizeof (GthImageToolSave),
+ 0,
+ (GInstanceInitFunc) gth_image_tool_save_instance_init,
+ NULL
+ };
+ static const GInterfaceInfo gth_file_tool_info = {
+ (GInterfaceInitFunc) gth_file_tool_interface_init,
+ (GInterfaceFinalizeFunc) NULL,
+ NULL
+ };
+ type_id = g_type_register_static (GTK_TYPE_BUTTON, "GthImageToolSave", &g_define_type_info, 0);
+ g_type_add_interface_static (type_id, GTH_TYPE_FILE_TOOL, >h_file_tool_info);
+ }
+ return type_id;
+}
diff --git a/extensions/image_tools/gth-image-tool-save.h b/extensions/image_tools/gth-image-tool-save.h
new file mode 100644
index 0000000..7edf30e
--- /dev/null
+++ b/extensions/image_tools/gth-image-tool-save.h
@@ -0,0 +1,52 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GTH_IMAGE_TOOL_SAVE_H
+#define GTH_IMAGE_TOOL_SAVE_H
+
+#include <gthumb.h>
+
+G_BEGIN_DECLS
+
+#define GTH_TYPE_IMAGE_TOOL_SAVE (gth_image_tool_save_get_type ())
+#define GTH_IMAGE_TOOL_SAVE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTH_TYPE_IMAGE_TOOL_SAVE, GthImageToolSave))
+#define GTH_IMAGE_TOOL_SAVE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTH_TYPE_IMAGE_TOOL_SAVE, GthImageToolSaveClass))
+#define GTH_IS_IMAGE_TOOL_SAVE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTH_TYPE_IMAGE_TOOL_SAVE))
+#define GTH_IS_IMAGE_TOOL_SAVE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTH_TYPE_IMAGE_TOOL_SAVE))
+#define GTH_IMAGE_TOOL_SAVE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTH_TYPE_IMAGE_TOOL_SAVE, GthImageToolSaveClass))
+
+typedef struct _GthImageToolSave GthImageToolSave;
+typedef struct _GthImageToolSaveClass GthImageToolSaveClass;
+
+struct _GthImageToolSave {
+ GtkButton parent_instance;
+};
+
+struct _GthImageToolSaveClass {
+ GtkButtonClass parent_class;
+};
+
+GType gth_image_tool_save_get_type (void);
+
+G_END_DECLS
+
+#endif /* GTH_IMAGE_TOOL_SAVE_H */
diff --git a/extensions/image_tools/gth-image-tool-undo.c b/extensions/image_tools/gth-image-tool-undo.c
new file mode 100644
index 0000000..2653187
--- /dev/null
+++ b/extensions/image_tools/gth-image-tool-undo.c
@@ -0,0 +1,117 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <gthumb.h>
+#include <extensions/image_viewer/gth-image-viewer-page.h>
+#include "gth-image-tool-undo.h"
+
+
+static void
+gth_file_tool_undo_real_update_sensitivity (GthFileTool *base,
+ GtkWidget *window)
+{
+ GtkWidget *viewer_page;
+
+ viewer_page = gth_browser_get_viewer_page (GTH_BROWSER (window));
+ if (! GTH_IS_IMAGE_VIEWER_PAGE (viewer_page))
+ gtk_widget_set_sensitive (GTK_WIDGET (base), FALSE);
+ else
+ gtk_widget_set_sensitive (GTK_WIDGET (base), gth_image_history_can_undo (gth_image_viewer_page_get_history (GTH_IMAGE_VIEWER_PAGE (viewer_page))));
+}
+
+
+static void
+gth_file_tool_interface_init (GthFileToolIface *iface)
+{
+ iface->update_sensitivity = gth_file_tool_undo_real_update_sensitivity;
+}
+
+
+static void
+button_clicked_cb (GtkButton *button,
+ gpointer data)
+{
+ GtkWidget *window;
+ GtkWidget *viewer_page;
+
+ window = gtk_widget_get_toplevel (GTK_WIDGET (button));
+ viewer_page = gth_browser_get_viewer_page (GTH_BROWSER (window));
+ if (! GTH_IS_IMAGE_VIEWER_PAGE (viewer_page))
+ return;
+
+ gth_image_viewer_page_undo (GTH_IMAGE_VIEWER_PAGE (viewer_page));
+}
+
+
+static void
+gth_image_tool_undo_instance_init (GthImageToolUndo *self)
+{
+ GtkWidget *hbox;
+ GtkWidget *icon;
+ GtkWidget *label;
+
+ gtk_button_set_relief (GTK_BUTTON (self), GTK_RELIEF_NONE);
+
+ hbox = gtk_hbox_new (FALSE, 6);
+
+ icon = gtk_image_new_from_stock (GTK_STOCK_UNDO, GTK_ICON_SIZE_MENU);
+ gtk_widget_show (icon);
+ gtk_box_pack_start (GTK_BOX (hbox), icon, FALSE, FALSE, 0);
+
+ label = gtk_label_new (_("Undo"));
+ gtk_widget_show (label);
+ gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
+
+ gtk_widget_show (hbox);
+ gtk_container_add (GTK_CONTAINER (self), hbox);
+
+ g_signal_connect (self, "clicked", G_CALLBACK (button_clicked_cb), self);
+}
+
+
+GType
+gth_image_tool_undo_get_type (void) {
+ static GType type_id = 0;
+ if (type_id == 0) {
+ static const GTypeInfo g_define_type_info = {
+ sizeof (GthImageToolUndoClass),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) NULL,
+ (GClassFinalizeFunc) NULL,
+ NULL,
+ sizeof (GthImageToolUndo),
+ 0,
+ (GInstanceInitFunc) gth_image_tool_undo_instance_init,
+ NULL
+ };
+ static const GInterfaceInfo gth_file_tool_info = {
+ (GInterfaceInitFunc) gth_file_tool_interface_init,
+ (GInterfaceFinalizeFunc) NULL,
+ NULL
+ };
+ type_id = g_type_register_static (GTK_TYPE_BUTTON, "GthImageToolUndo", &g_define_type_info, 0);
+ g_type_add_interface_static (type_id, GTH_TYPE_FILE_TOOL, >h_file_tool_info);
+ }
+ return type_id;
+}
diff --git a/extensions/image_tools/gth-image-tool-undo.h b/extensions/image_tools/gth-image-tool-undo.h
new file mode 100644
index 0000000..56b2f90
--- /dev/null
+++ b/extensions/image_tools/gth-image-tool-undo.h
@@ -0,0 +1,52 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GTH_IMAGE_TOOL_UNDO_H
+#define GTH_IMAGE_TOOL_UNDO_H
+
+#include <gthumb.h>
+
+G_BEGIN_DECLS
+
+#define GTH_TYPE_IMAGE_TOOL_UNDO (gth_image_tool_undo_get_type ())
+#define GTH_IMAGE_TOOL_UNDO(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTH_TYPE_IMAGE_TOOL_UNDO, GthImageToolUndo))
+#define GTH_IMAGE_TOOL_UNDO_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTH_TYPE_IMAGE_TOOL_UNDO, GthImageToolUndoClass))
+#define GTH_IS_IMAGE_TOOL_UNDO(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTH_TYPE_IMAGE_TOOL_UNDO))
+#define GTH_IS_IMAGE_TOOL_UNDO_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTH_TYPE_IMAGE_TOOL_UNDO))
+#define GTH_IMAGE_TOOL_UNDO_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTH_TYPE_IMAGE_TOOL_UNDO, GthImageToolUndoClass))
+
+typedef struct _GthImageToolUndo GthImageToolUndo;
+typedef struct _GthImageToolUndoClass GthImageToolUndoClass;
+
+struct _GthImageToolUndo {
+ GtkButton parent_instance;
+};
+
+struct _GthImageToolUndoClass {
+ GtkButtonClass parent_class;
+};
+
+GType gth_image_tool_undo_get_type (void);
+
+G_END_DECLS
+
+#endif /* GTH_IMAGE_TOOL_UNDO_H */
diff --git a/extensions/image_tools/image_tools.extension.in.in b/extensions/image_tools/image_tools.extension.in.in
new file mode 100644
index 0000000..a0e2444
--- /dev/null
+++ b/extensions/image_tools/image_tools.extension.in.in
@@ -0,0 +1,11 @@
+[Extension]
+_Name=Image Tools
+_Description=Basic tools to modify images.
+Authors=Paolo Bacchilega <paobac src gnome org>
+Copyright=Copyright © 2009 Paolo Bacchilega
+Version=1
+Requires=image_viewer
+
+[Loader]
+Type=module
+File=%LIBRARY%
diff --git a/extensions/image_tools/main.c b/extensions/image_tools/main.c
new file mode 100644
index 0000000..920aa08
--- /dev/null
+++ b/extensions/image_tools/main.c
@@ -0,0 +1,64 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+
+#include <config.h>
+#include <gtk/gtk.h>
+#include <gthumb.h>
+#include "gth-image-tool-crop.h"
+#include "gth-image-tool-desaturate.h"
+#include "gth-image-tool-redo.h"
+#include "gth-image-tool-save.h"
+#include "gth-image-tool-undo.h"
+
+
+G_MODULE_EXPORT void
+gthumb_extension_activate (void)
+{
+ gth_main_register_type ("file-tools", GTH_TYPE_IMAGE_TOOL_SAVE);
+ gth_main_register_type ("file-tools", GTH_TYPE_IMAGE_TOOL_UNDO);
+ gth_main_register_type ("file-tools", GTH_TYPE_IMAGE_TOOL_REDO);
+ gth_main_register_type ("file-tools", GTH_TYPE_IMAGE_TOOL_CROP);
+ gth_main_register_type ("file-tools", GTH_TYPE_IMAGE_TOOL_DESATURATE);
+
+ /*gth_main_register_type ("file-tools", GTH_TYPE_IMAGE_TOOL_SAVE);
+ gth_main_register_type ("file-tools", GTH_TYPE_IMAGE_TOOL_SAVE_AS);*/
+}
+
+
+G_MODULE_EXPORT void
+gthumb_extension_deactivate (void)
+{
+}
+
+
+G_MODULE_EXPORT gboolean
+gthumb_extension_is_configurable (void)
+{
+ return FALSE;
+}
+
+
+G_MODULE_EXPORT void
+gthumb_extension_configure (GtkWindow *parent)
+{
+}
diff --git a/extensions/image_viewer/Makefile.am b/extensions/image_viewer/Makefile.am
new file mode 100644
index 0000000..d321db1
--- /dev/null
+++ b/extensions/image_viewer/Makefile.am
@@ -0,0 +1,36 @@
+SUBDIRS = data
+
+extensiondir = $(libdir)/gthumb-2.0/extensions
+extension_LTLIBRARIES = libimage_viewer.la
+
+libimage_viewer_la_SOURCES = \
+ gth-image-viewer-page.c \
+ gth-image-viewer-page.h \
+ gth-metadata-provider-image.c \
+ gth-metadata-provider-image.h \
+ main.c \
+ preferences.c \
+ preferences.h
+
+libimage_viewer_la_CFLAGS = $(GTHUMB_CFLAGS) -I$(top_srcdir) -I$(top_builddir)/gthumb
+libimage_viewer_la_LDFLAGS = $(EXTENSION_LIBTOOL_FLAGS)
+libimage_viewer_la_LIBADD = $(GTHUMB_LIBS)
+libimage_viewer_la_DEPENDENCIES = $(top_builddir)/gthumb/gthumb$(EXEEXT)
+
+extensioninidir = $(extensiondir)
+extensionini_in_files = image_viewer.extension.in.in
+extensionini_DATA = $(extensionini_in_files:.extension.in.in=.extension)
+
+%.extension.in: %.extension.in.in $(extension_LTLIBRARIES)
+ sed -e "s|%LIBRARY%|`. ./$(extension_LTLIBRARIES) && echo $$dlname`|" \
+ $< > $@
+
+%.extension: %.extension.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*po) ; LC_ALL=C $(INTLTOOL_MERGE) -d -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< $@
+
+EXTRA_DIST = $(extensionini_in_files)
+
+CLEANFILES = $(extensionini_DATA)
+
+DISTCLEANFILES = $(extensionini_DATA)
+
+-include $(top_srcdir)/git.mk
diff --git a/extensions/image_viewer/data/Makefile.am b/extensions/image_viewer/data/Makefile.am
new file mode 100644
index 0000000..88da273
--- /dev/null
+++ b/extensions/image_viewer/data/Makefile.am
@@ -0,0 +1,19 @@
+SUBDIRS = ui
+
+schemadir = @GCONF_SCHEMA_FILE_DIR@
+schema_in_files = gthumb-image-viewer.schemas.in
+schema_DATA = $(schema_in_files:.schemas.in=.schemas)
+
+ INTLTOOL_SCHEMAS_RULE@
+
+if GCONF_SCHEMAS_INSTALL
+install-data-local:
+ GCONF_CONFIG_SOURCE=$(GCONF_SCHEMA_CONFIG_SOURCE) $(GCONFTOOL) --makefile-install-rule $(top_builddir)/data/$(schema_DATA)
+endif
+
+EXTRA_DIST = \
+ gthumb-image-viewer.schemas \
+ gthumb-image-viewer.schemas.in
+
+DISTCLEANFILES = $(schema_DATA)
+-include $(top_srcdir)/git.mk
diff --git a/extensions/image_viewer/data/gthumb-image-viewer.schemas.in b/extensions/image_viewer/data/gthumb-image-viewer.schemas.in
new file mode 100644
index 0000000..e4dc62d
--- /dev/null
+++ b/extensions/image_viewer/data/gthumb-image-viewer.schemas.in
@@ -0,0 +1,97 @@
+<gconfschemafile>
+ <schemalist>
+
+ <schema>
+ <key>/schemas/apps/gthumb/viewer/zoom_quality</key>
+ <applyto>/apps/gthumb/viewer/zoom_quality</applyto>
+ <owner>gthumb</owner>
+ <type>string</type>
+ <default>high</default>
+ <locale name="C">
+ <short></short>
+ <long> Possible values are: high, low.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/gthumb/viewer/zoom_change</key>
+ <applyto>/apps/gthumb/viewer/zoom_change</applyto>
+ <owner>gthumb</owner>
+ <type>string</type>
+ <default>fit</default>
+ <locale name="C">
+ <short></short>
+ <long> Possible values are: actual-size, keep-prev, fit-size,
+ fit-size-if-larger, fit-width, fit-width-if-larger.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/gthumb/viewer/transparency_type</key>
+ <applyto>/apps/gthumb/viewer/transparency_type</applyto>
+ <owner>gthumb</owner>
+ <type>string</type>
+ <default>none</default>
+ <locale name="C">
+ <short></short>
+ <long> Possible values are: white, black, checked, none.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/gthumb/viewer/reset_scrollbars</key>
+ <applyto>/apps/gthumb/viewer/reset_scrollbars</applyto>
+ <owner>gthumb</owner>
+ <type>bool</type>
+ <default>true</default>
+ <locale name="C">
+ <short></short>
+ <long> Whether to reset the scrollbar positions after changing image
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/gthumb/viewer/check_type</key>
+ <applyto>/apps/gthumb/viewer/check_type</applyto>
+ <owner>gthumb</owner>
+ <type>string</type>
+ <default>midtone</default>
+ <locale name="C">
+ <short></short>
+ <long> Possible values are: light, midtone, dark.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/gthumb/viewer/check_size</key>
+ <applyto>/apps/gthumb/viewer/check_size</applyto>
+ <owner>gthumb</owner>
+ <type>string</type>
+ <default>medium</default>
+ <locale name="C">
+ <short></short>
+ <long> Possible values are: small, medium, large.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/gthumb/viewer/black_background</key>
+ <applyto>/apps/gthumb/viewer/black_background</applyto>
+ <owner>gthumb</owner>
+ <type>bool</type>
+ <default>false</default>
+ <locale name="C">
+ <short></short>
+ <long> Whether to always use a black background.
+ </long>
+ </locale>
+ </schema>
+
+ </schemalist>
+</gconfschemafile>
diff --git a/extensions/image_viewer/data/ui/Makefile.am b/extensions/image_viewer/data/ui/Makefile.am
new file mode 100644
index 0000000..ab2fd97
--- /dev/null
+++ b/extensions/image_viewer/data/ui/Makefile.am
@@ -0,0 +1,5 @@
+uidir = $(datadir)/gthumb/ui
+ui_DATA = image-viewer-preferences.ui
+EXTRA_DIST = $(ui_DATA)
+
+-include $(top_srcdir)/git.mk
diff --git a/extensions/image_viewer/data/ui/image-viewer-preferences.ui b/extensions/image_viewer/data/ui/image-viewer-preferences.ui
new file mode 100644
index 0000000..ef54b81
--- /dev/null
+++ b/extensions/image_viewer/data/ui/image-viewer-preferences.ui
@@ -0,0 +1,291 @@
+<?xml version="1.0"?>
+<interface>
+ <requires lib="gtk+" version="2.16"/>
+ <!-- interface-naming-policy project-wide -->
+ <object class="GtkVBox" id="preferences_page">
+ <property name="visible">True</property>
+ <property name="border_width">12</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkVBox" id="vbox61">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkHBox" id="hbox118">
+ <property name="visible">True</property>
+ <property name="spacing">5</property>
+ <child>
+ <object class="GtkLabel" id="label187">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes"><b>After loading an image:</b></property>
+ <property name="use_markup">True</property>
+ <property name="justify">center</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox119">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkLabel" id="label188">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes"> </property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox1">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkHBox" id="zoom_change_box">
+ <property name="visible">True</property>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="toggle_reset_scrollbars">
+ <property name="label" translatable="yes">Reset scrollbar positions</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox59">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkHBox" id="hbox116">
+ <property name="visible">True</property>
+ <property name="spacing">5</property>
+ <child>
+ <object class="GtkLabel" id="label185">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes"><b>Zoom quality</b></property>
+ <property name="use_markup">True</property>
+ <property name="justify">center</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox117">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkLabel" id="label186">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes"> </property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox60">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkRadioButton" id="opt_zoom_quality_high">
+ <property name="label" translatable="yes">H_igh</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="active">True</property>
+ <property name="draw_indicator">True</property>
+ <property name="group">opt_zoom_quality_low</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="opt_zoom_quality_low">
+ <property name="label" translatable="yes">Lo_w</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox2">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkHBox" id="hbox2">
+ <property name="visible">True</property>
+ <property name="spacing">5</property>
+ <child>
+ <object class="GtkLabel" id="label1">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes"><b>Other</b></property>
+ <property name="use_markup">True</property>
+ <property name="justify">center</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox3">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkLabel" id="label2">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes"> </property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox1">
+ <property name="visible">True</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkLabel" id="label95">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Transparency _type:</property>
+ <property name="use_underline">True</property>
+ <property name="justify">center</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="transp_type_box">
+ <property name="visible">True</property>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+</interface>
diff --git a/extensions/image_viewer/gth-image-viewer-page.c b/extensions/image_viewer/gth-image-viewer-page.c
new file mode 100644
index 0000000..eb727c7
--- /dev/null
+++ b/extensions/image_viewer/gth-image-viewer-page.c
@@ -0,0 +1,1113 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <math.h>
+#include <gdk/gdkkeysyms.h>
+#include <gthumb.h>
+#include "gth-image-viewer-page.h"
+
+
+#define GTH_IMAGE_VIEWER_PAGE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GTH_TYPE_IMAGE_VIEWER_PAGE, GthImageViewerPagePrivate))
+#define GCONF_NOTIFICATIONS 7
+
+
+struct _GthImageViewerPagePrivate {
+ GthBrowser *browser;
+ GtkWidget *viewer;
+ GthImagePreloader *preloader;
+ GtkActionGroup *actions;
+ guint merge_id;
+ GthImageHistory *history;
+ GthFileData *file_data;
+ gulong preloader_sig_id;
+ guint cnxn_id[GCONF_NOTIFICATIONS];
+};
+
+static gpointer gth_image_viewer_page_parent_class = NULL;
+
+static const char *image_viewer_ui_info =
+"<ui>"
+" <menubar name='MenuBar'>"
+" <menu name='Edit' action='EditMenu'>"
+" <placeholder name='File_Actions_1'>"
+" <menuitem action='ImageViewer_Edit_Undo'/>"
+" <menuitem action='ImageViewer_Edit_Redo'/>"
+" </placeholder>"
+" </menu>"
+" </menubar>"
+" <toolbar name='ViewerToolBar'>"
+" <placeholder name='ViewerCommands'>"
+" <separator/>"
+" <toolitem action='ImageViewer_View_Fullscreen'/>"
+" <separator/>"
+" <toolitem action='ImageViewer_View_ZoomIn'/>"
+" <toolitem action='ImageViewer_View_ZoomOut'/>"
+" <toolitem action='ImageViewer_View_Zoom100'/>"
+" <toolitem action='ImageViewer_View_ZoomFit'/>"
+" <toolitem action='ImageViewer_View_ZoomFitWidth'/>"
+" </placeholder>"
+" <placeholder name='ViewerCommandsSecondary'>"
+" <toolitem action='Viewer_Tools'/>"
+" </placeholder>"
+" </toolbar>"
+"</ui>";
+
+
+static void
+image_viewer_activate_action_view_fullscreen (GtkAction *action,
+ GthImageViewerPage *self)
+{
+}
+
+
+static void
+image_viewer_activate_action_view_zoom_in (GtkAction *action,
+ GthImageViewerPage *self)
+{
+ gth_image_viewer_zoom_in (GTH_IMAGE_VIEWER (self->priv->viewer));
+}
+
+
+static void
+image_viewer_activate_action_view_zoom_out (GtkAction *action,
+ GthImageViewerPage *self)
+{
+ gth_image_viewer_zoom_out (GTH_IMAGE_VIEWER (self->priv->viewer));
+}
+
+
+static void
+image_viewer_activate_action_view_zoom_100 (GtkAction *action,
+ GthImageViewerPage *self)
+{
+ gth_image_viewer_set_zoom (GTH_IMAGE_VIEWER (self->priv->viewer), 1.0);
+}
+
+
+static void
+image_viewer_activate_action_view_zoom_fit (GtkAction *action,
+ GthImageViewerPage *self)
+{
+ gth_image_viewer_set_fit_mode (GTH_IMAGE_VIEWER (self->priv->viewer), GTH_FIT_SIZE);
+}
+
+
+static void
+image_viewer_activate_action_view_zoom_fit_width (GtkAction *action,
+ GthImageViewerPage *self)
+{
+ gth_image_viewer_set_fit_mode (GTH_IMAGE_VIEWER (self->priv->viewer), GTH_FIT_WIDTH);
+}
+
+
+static void
+image_viewer_activate_action_edit_undo (GtkAction *action,
+ GthImageViewerPage *self)
+{
+ gth_image_viewer_page_undo (self);
+}
+
+
+static void
+image_viewer_activate_action_edit_redo (GtkAction *action,
+ GthImageViewerPage *self)
+{
+ gth_image_viewer_page_redo (self);
+}
+
+
+static GtkActionEntry image_viewer_action_entries[] = {
+ { "ImageViewer_Edit_Undo", GTK_STOCK_UNDO,
+ NULL, "<control>z",
+ NULL,
+ G_CALLBACK (image_viewer_activate_action_edit_undo) },
+
+ { "ImageViewer_Edit_Redo", GTK_STOCK_REDO,
+ NULL, "<shift><control>z",
+ NULL,
+ G_CALLBACK (image_viewer_activate_action_edit_redo) },
+
+ { "ImageViewer_View_Fullscreen", GTK_STOCK_FULLSCREEN,
+ NULL, "F11",
+ NULL,
+ G_CALLBACK (image_viewer_activate_action_view_fullscreen) },
+
+ { "ImageViewer_View_ZoomIn", GTK_STOCK_ZOOM_IN,
+ N_("In"), "<control>plus",
+ N_("Zoom in"),
+ G_CALLBACK (image_viewer_activate_action_view_zoom_in) },
+
+ { "ImageViewer_View_ZoomOut", GTK_STOCK_ZOOM_OUT,
+ N_("Out"), "<control>minus",
+ N_("Zoom out"),
+ G_CALLBACK (image_viewer_activate_action_view_zoom_out) },
+
+ { "ImageViewer_View_Zoom100", GTK_STOCK_ZOOM_100,
+ N_("1:1"), "<control>0",
+ N_("Actual size"),
+ G_CALLBACK (image_viewer_activate_action_view_zoom_100) },
+
+ { "ImageViewer_View_ZoomFit", GTK_STOCK_ZOOM_FIT,
+ N_("Fit"), "",
+ N_("Zoom to fit window"),
+ G_CALLBACK (image_viewer_activate_action_view_zoom_fit) },
+
+ { "ImageViewer_View_ZoomFitWidth", GTH_STOCK_ZOOM_FIT_WIDTH,
+ N_("Width"), "",
+ N_("Zoom to fit width"),
+ G_CALLBACK (image_viewer_activate_action_view_zoom_fit_width) },
+};
+
+
+static void
+image_ready_cb (GtkWidget *widget,
+ GthImageViewerPage *self)
+{
+ gth_image_history_clear (self->priv->history);
+
+ g_file_info_set_attribute_boolean (self->priv->file_data->info, "file::is-modified", FALSE);
+ gth_monitor_metadata_changed (gth_main_get_default_monitor (), self->priv->file_data);
+
+}
+
+
+static gboolean
+zoom_changed_cb (GtkWidget *widget,
+ GthImageViewerPage *self)
+{
+ double zoom;
+ char *text;
+
+ gth_viewer_page_update_sensitivity (GTH_VIEWER_PAGE (self));
+
+ zoom = gth_image_viewer_get_zoom (GTH_IMAGE_VIEWER (self->priv->viewer));
+ text = g_strdup_printf (" %d%% ", (int) (zoom * 100));
+ gth_statusbar_set_secondary_text (GTH_STATUSBAR (gth_browser_get_statusbar (self->priv->browser)), text);
+
+ g_free (text);
+
+ return TRUE;
+}
+
+
+static gboolean
+image_button_press_cb (GtkWidget *widget,
+ GdkEventButton *event,
+ GthImageViewerPage *self)
+{
+ if (event->button == 3) {
+ /*gtk_menu_popup (GTK_MENU (self->priv->image_popup_menu),
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ 3,
+ event->time);
+ return TRUE;
+ */
+ }
+
+ return FALSE;
+}
+
+
+static gboolean
+mouse_wheel_scrolled_cb (GtkWidget *widget,
+ GdkScrollDirection direction,
+ GthImageViewerPage *self)
+{
+ if (direction == GDK_SCROLL_UP)
+ gth_browser_show_prev_image (self->priv->browser, TRUE, FALSE);
+ else
+ gth_browser_show_next_image (self->priv->browser, TRUE, FALSE);
+
+ return TRUE;
+}
+
+
+static gboolean
+viewer_key_press_cb (GtkWidget *widget,
+ GdkEventKey *event,
+ GthImageViewerPage *self)
+{
+ switch (gdk_keyval_to_lower (event->keyval)) {
+ case GDK_Page_Up:
+ gth_browser_show_prev_image (self->priv->browser, TRUE, FALSE);
+ return TRUE;
+
+ case GDK_Page_Down:
+ gth_browser_show_next_image (self->priv->browser, TRUE, FALSE);
+ return TRUE;
+
+ case GDK_Home:
+ gth_browser_show_first_image (self->priv->browser, TRUE, FALSE);
+ return TRUE;
+
+ case GDK_End:
+ gth_browser_show_last_image (self->priv->browser, TRUE, FALSE);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+
+static void
+image_preloader_requested_ready_cb (GthImagePreloader *preloader,
+ GError *error,
+ GthImageViewerPage *self)
+{
+ GthImageLoader *image_loader;
+
+ image_loader = gth_image_preloader_get_loader (self->priv->preloader, gth_image_preloader_get_requested (self->priv->preloader));
+ if (image_loader == NULL)
+ return;
+
+ gth_image_viewer_load_from_image_loader (GTH_IMAGE_VIEWER (self->priv->viewer), image_loader);
+}
+
+
+static void
+pref_zoom_quality_changed (GConfClient *client,
+ guint cnxn_id,
+ GConfEntry *entry,
+ gpointer user_data)
+{
+ GthImageViewerPage *self = user_data;
+ GthImageViewer *image_viewer = GTH_IMAGE_VIEWER (self->priv->viewer);
+
+ gth_image_viewer_set_zoom_quality (image_viewer, eel_gconf_get_enum (PREF_ZOOM_QUALITY, GTH_TYPE_ZOOM_QUALITY, GTH_ZOOM_QUALITY_HIGH));
+ gth_image_viewer_update_view (image_viewer);
+}
+
+
+static void
+pref_zoom_change_changed (GConfClient *client,
+ guint cnxn_id,
+ GConfEntry *entry,
+ gpointer user_data)
+{
+ GthImageViewerPage *self = user_data;
+ GthImageViewer *image_viewer = GTH_IMAGE_VIEWER (self->priv->viewer);
+
+ gth_image_viewer_set_zoom_change (image_viewer, eel_gconf_get_enum (PREF_ZOOM_CHANGE, GTH_TYPE_ZOOM_CHANGE, GTH_ZOOM_CHANGE_FIT_SIZE_IF_LARGER));
+ gth_image_viewer_update_view (image_viewer);
+}
+
+
+static void
+pref_transp_type_changed (GConfClient *client,
+ guint cnxn_id,
+ GConfEntry *entry,
+ gpointer user_data)
+{
+ GthImageViewerPage *self = user_data;
+ GthImageViewer *image_viewer = GTH_IMAGE_VIEWER (self->priv->viewer);
+
+ gth_image_viewer_set_transp_type (image_viewer, eel_gconf_get_enum (PREF_TRANSP_TYPE, GTH_TYPE_TRANSP_TYPE, GTH_TRANSP_TYPE_NONE));
+ gth_image_viewer_update_view (image_viewer);
+}
+
+
+static void
+pref_check_type_changed (GConfClient *client,
+ guint cnxn_id,
+ GConfEntry *entry,
+ gpointer user_data)
+{
+ GthImageViewerPage *self = user_data;
+ GthImageViewer *image_viewer = GTH_IMAGE_VIEWER (self->priv->viewer);
+
+ gth_image_viewer_set_check_type (image_viewer, eel_gconf_get_enum (PREF_CHECK_TYPE, GTH_TYPE_CHECK_TYPE, GTH_CHECK_TYPE_MIDTONE));
+ gth_image_viewer_update_view (image_viewer);
+}
+
+
+static void
+pref_check_size_changed (GConfClient *client,
+ guint cnxn_id,
+ GConfEntry *entry,
+ gpointer user_data)
+{
+ GthImageViewerPage *self = user_data;
+ GthImageViewer *image_viewer = GTH_IMAGE_VIEWER (self->priv->viewer);
+
+ gth_image_viewer_set_check_size (image_viewer, eel_gconf_get_enum (PREF_CHECK_SIZE, GTH_TYPE_CHECK_SIZE, GTH_CHECK_SIZE_MEDIUM));
+ gth_image_viewer_update_view (image_viewer);
+}
+
+
+static void
+pref_black_background_changed (GConfClient *client,
+ guint cnxn_id,
+ GConfEntry *entry,
+ gpointer user_data)
+{
+ GthImageViewerPage *self = user_data;
+ GthImageViewer *image_viewer = GTH_IMAGE_VIEWER (self->priv->viewer);
+
+ gth_image_viewer_set_black_background (image_viewer, eel_gconf_get_boolean (PREF_BLACK_BACKGROUND, FALSE));
+}
+
+
+static void
+pref_reset_scrollbars_changed (GConfClient *client,
+ guint cnxn_id,
+ GConfEntry *entry,
+ gpointer user_data)
+{
+ GthImageViewerPage *self = user_data;
+ GthImageViewer *image_viewer = GTH_IMAGE_VIEWER (self->priv->viewer);
+
+ gth_image_viewer_set_reset_scrollbars (image_viewer, eel_gconf_get_boolean (PREF_RESET_SCROLLBARS, TRUE));
+}
+
+
+static void
+gth_image_viewer_page_real_activate (GthViewerPage *base,
+ GthBrowser *browser)
+{
+ GthImageViewerPage *self;
+ GtkWidget *nav_window;
+ int i;
+
+ self = (GthImageViewerPage*) base;
+
+ self->priv->browser = browser;
+
+ self->priv->actions = gtk_action_group_new ("Image Viewer Actions");
+ gtk_action_group_set_translation_domain (self->priv->actions, NULL);
+ gtk_action_group_add_actions (self->priv->actions,
+ image_viewer_action_entries,
+ G_N_ELEMENTS (image_viewer_action_entries),
+ self);
+ gtk_ui_manager_insert_action_group (gth_browser_get_ui_manager (browser), self->priv->actions, 0);
+
+ self->priv->preloader = gth_browser_get_image_preloader (browser);
+ self->priv->preloader_sig_id = g_signal_connect (G_OBJECT (self->priv->preloader),
+ "requested_ready",
+ G_CALLBACK (image_preloader_requested_ready_cb),
+ self);
+
+ self->priv->viewer = gth_image_viewer_new ();
+ gth_image_viewer_set_zoom_quality (GTH_IMAGE_VIEWER (self->priv->viewer), eel_gconf_get_enum (PREF_ZOOM_QUALITY, GTH_TYPE_ZOOM_QUALITY, GTH_ZOOM_QUALITY_HIGH));
+ gth_image_viewer_set_zoom_change (GTH_IMAGE_VIEWER (self->priv->viewer), eel_gconf_get_enum (PREF_ZOOM_CHANGE, GTH_TYPE_ZOOM_CHANGE, GTH_ZOOM_CHANGE_FIT_SIZE_IF_LARGER));
+ gth_image_viewer_set_transp_type (GTH_IMAGE_VIEWER (self->priv->viewer), eel_gconf_get_enum (PREF_TRANSP_TYPE, GTH_TYPE_TRANSP_TYPE, GTH_TRANSP_TYPE_NONE));
+ gth_image_viewer_set_check_type (GTH_IMAGE_VIEWER (self->priv->viewer), eel_gconf_get_enum (PREF_CHECK_TYPE, GTH_TYPE_CHECK_TYPE, GTH_CHECK_TYPE_MIDTONE));
+ gth_image_viewer_set_check_size (GTH_IMAGE_VIEWER (self->priv->viewer), eel_gconf_get_enum (PREF_CHECK_SIZE, GTH_TYPE_CHECK_SIZE, GTH_CHECK_SIZE_MEDIUM));
+ gth_image_viewer_set_black_background (GTH_IMAGE_VIEWER (self->priv->viewer), eel_gconf_get_boolean (PREF_BLACK_BACKGROUND, FALSE));
+ gth_image_viewer_set_reset_scrollbars (GTH_IMAGE_VIEWER (self->priv->viewer), eel_gconf_get_boolean (PREF_RESET_SCROLLBARS, TRUE));
+
+ gtk_widget_show (self->priv->viewer);
+
+ g_signal_connect (G_OBJECT (self->priv->viewer),
+ "image_ready",
+ G_CALLBACK (image_ready_cb),
+ self);
+ g_signal_connect (G_OBJECT (self->priv->viewer),
+ "zoom_changed",
+ G_CALLBACK (zoom_changed_cb),
+ self);
+ g_signal_connect_after (G_OBJECT (self->priv->viewer),
+ "button_press_event",
+ G_CALLBACK (image_button_press_cb),
+ self);
+ g_signal_connect_after (G_OBJECT (self->priv->viewer),
+ "mouse_wheel_scroll",
+ G_CALLBACK (mouse_wheel_scrolled_cb),
+ self);
+ g_signal_connect (G_OBJECT (self->priv->viewer),
+ "key_press_event",
+ G_CALLBACK (viewer_key_press_cb),
+ self);
+
+ nav_window = gth_nav_window_new (GTH_IMAGE_VIEWER (self->priv->viewer));
+ gtk_widget_show (nav_window);
+
+ gth_browser_set_viewer_widget (browser, nav_window);
+
+ /* gconf notifications */
+
+ for (i = 0; i < GCONF_NOTIFICATIONS; i++)
+ self->priv->cnxn_id[i] = 0;
+
+ i = 0;
+ self->priv->cnxn_id[i++] = eel_gconf_notification_add (
+ PREF_ZOOM_QUALITY,
+ pref_zoom_quality_changed,
+ self);
+
+ self->priv->cnxn_id[i++] = eel_gconf_notification_add (
+ PREF_ZOOM_CHANGE,
+ pref_zoom_change_changed,
+ self);
+
+ self->priv->cnxn_id[i++] = eel_gconf_notification_add (
+ PREF_TRANSP_TYPE,
+ pref_transp_type_changed,
+ self);
+
+ self->priv->cnxn_id[i++] = eel_gconf_notification_add (
+ PREF_CHECK_TYPE,
+ pref_check_type_changed,
+ self);
+
+ self->priv->cnxn_id[i++] = eel_gconf_notification_add (
+ PREF_CHECK_SIZE,
+ pref_check_size_changed,
+ self);
+
+ self->priv->cnxn_id[i++] = eel_gconf_notification_add (
+ PREF_BLACK_BACKGROUND,
+ pref_black_background_changed,
+ self);
+
+ self->priv->cnxn_id[i++] = eel_gconf_notification_add (
+ PREF_RESET_SCROLLBARS,
+ pref_reset_scrollbars_changed,
+ self);
+}
+
+
+static void
+gth_image_viewer_page_real_deactivate (GthViewerPage *base)
+{
+ GthImageViewerPage *self;
+ int i;
+
+ self = (GthImageViewerPage*) base;
+
+ /* remove gconf notifications */
+
+ for (i = 0; i < GCONF_NOTIFICATIONS; i++)
+ if (self->priv->cnxn_id[i] != 0)
+ eel_gconf_notification_remove (self->priv->cnxn_id[i]);
+
+ /**/
+
+ g_signal_handler_disconnect (self->priv->preloader, self->priv->preloader_sig_id);
+ self->priv->preloader_sig_id = 0;
+ g_object_unref (self->priv->preloader);
+ self->priv->preloader = NULL;
+
+ gth_browser_set_viewer_widget (self->priv->browser, NULL);
+}
+
+
+static void
+gth_image_viewer_page_real_show (GthViewerPage *base)
+{
+ GthImageViewerPage *self;
+ GError *error = NULL;
+
+ self = (GthImageViewerPage*) base;
+
+ if (self->priv->merge_id != 0)
+ return;
+
+ self->priv->merge_id = gtk_ui_manager_add_ui_from_string (gth_browser_get_ui_manager (self->priv->browser), image_viewer_ui_info, -1, &error);
+ if (self->priv->merge_id == 0) {
+ g_warning ("ui building failed: %s", error->message);
+ g_error_free (error);
+ }
+
+ gtk_widget_grab_focus (self->priv->viewer);
+}
+
+
+static void
+gth_image_viewer_page_real_hide (GthViewerPage *base)
+{
+ GthImageViewerPage *self;
+
+ self = (GthImageViewerPage*) base;
+
+ if (self->priv->merge_id != 0) {
+ gtk_ui_manager_remove_ui (gth_browser_get_ui_manager (self->priv->browser), self->priv->merge_id);
+ self->priv->merge_id = 0;
+ }
+}
+
+
+static gboolean
+gth_image_viewer_page_real_can_view (GthViewerPage *base,
+ GthFileData *file_data)
+{
+ GthImageViewerPage *self;
+
+ self = (GthImageViewerPage*) base;
+ g_return_val_if_fail (file_data != NULL, FALSE);
+
+ return _g_mime_type_is_image (gth_file_data_get_mime_type (file_data));
+}
+
+
+static void
+gth_image_viewer_page_real_view (GthViewerPage *base,
+ GthFileData *file_data)
+{
+ GthImageViewerPage *self;
+ GthFileStore *file_store;
+ int file_pos;
+ GthFileData *next_file_data;
+ GthFileData *prev_file_data;
+
+ self = (GthImageViewerPage*) base;
+ g_return_if_fail (file_data != NULL);
+
+ _g_object_unref (self->priv->file_data);
+ self->priv->file_data = gth_file_data_dup (file_data);
+
+ file_store = gth_browser_get_file_store (self->priv->browser);
+ file_pos = gth_file_store_find_visible (file_store, file_data->file);
+ next_file_data = gth_file_store_get_file_at_pos (file_store, file_pos + 1);
+ prev_file_data = gth_file_store_get_file_at_pos (file_store, file_pos - 1);
+
+ gth_image_preloader_load (self->priv->preloader,
+ file_data,
+ next_file_data,
+ prev_file_data);
+}
+
+static void
+_set_action_sensitive (GthImageViewerPage *self,
+ const char *action_name,
+ gboolean sensitive)
+{
+ GtkAction *action;
+
+ action = gtk_action_group_get_action (self->priv->actions, action_name);
+ g_object_set (action, "sensitive", sensitive, NULL);
+}
+
+
+static void
+gth_image_viewer_page_real_update_sensitivity (GthViewerPage *base)
+{
+ GthImageViewerPage *self;
+ double zoom;
+ GthFit fit_mode;
+
+ self = (GthImageViewerPage*) base;
+
+ _set_action_sensitive (self, "ImageViewer_Edit_Undo", gth_image_history_can_undo (self->priv->history));
+ _set_action_sensitive (self, "ImageViewer_Edit_Redo", gth_image_history_can_redo (self->priv->history));
+
+ zoom = gth_image_viewer_get_zoom (GTH_IMAGE_VIEWER (self->priv->viewer));
+ _set_action_sensitive (self, "ImageViewer_View_Zoom100", ! FLOAT_EQUAL (zoom, 1.0));
+ _set_action_sensitive (self, "ImageViewer_View_ZoomOut", zoom > 0.05);
+ _set_action_sensitive (self, "ImageViewer_View_ZoomIn", zoom < 100.0);
+
+ fit_mode = gth_image_viewer_get_fit_mode (GTH_IMAGE_VIEWER (self->priv->viewer));
+ _set_action_sensitive (self, "ImageViewer_View_ZoomFit", fit_mode != GTH_FIT_SIZE);
+ _set_action_sensitive (self, "ImageViewer_View_ZoomFitWidth", fit_mode != GTH_FIT_WIDTH);
+}
+
+
+typedef struct {
+ GthImageViewerPage *self;
+ GthFileData *original_file;
+ FileSavedFunc func;
+ gpointer user_data;
+} SaveData;
+
+
+static void
+image_saved_cb (GthFileData *file_data,
+ GError *error,
+ gpointer user_data)
+{
+ SaveData *data = user_data;
+ GthImageViewerPage *self = data->self;
+ gboolean error_occurred;
+
+ error_occurred = error != NULL;
+
+ if (error_occurred) {
+ GthFileData *current_file;
+
+ current_file = gth_browser_get_current_file (self->priv->browser);
+ if (current_file != NULL) {
+ gth_file_data_set_file (current_file, data->original_file->file);
+ g_file_info_set_attribute_boolean (current_file->info, "file::is-modified", FALSE);
+ }
+ }
+
+ if (data->func != NULL)
+ (data->func) ((GthViewerPage *) self, self->priv->file_data, error, data->user_data);
+ else if (error != NULL)
+ _gtk_error_dialog_from_gerror_show (GTK_WINDOW (self->priv->browser), _("Could not save the file"), &error);
+
+ if (! error_occurred) {
+ GFile *folder;
+ GList *file_list;
+
+ folder = g_file_get_parent (self->priv->file_data->file);
+ file_list = g_list_prepend (NULL, g_object_ref (self->priv->file_data->file));
+ gth_monitor_folder_changed (gth_main_get_default_monitor (),
+ folder,
+ file_list,
+ GTH_MONITOR_EVENT_CHANGED);
+
+ _g_object_list_unref (file_list);
+ g_object_unref (folder);
+ }
+
+ g_object_unref (data->original_file);
+ g_free (data);
+}
+
+
+static void
+_gth_image_viewer_page_real_save (GthViewerPage *base,
+ GFile *file,
+ const char *mime_type,
+ FileSavedFunc func,
+ gpointer user_data)
+{
+ GthImageViewerPage *self;
+ SaveData *data;
+ char *pixbuf_type;
+ GthFileData *current_file;
+
+ self = (GthImageViewerPage *) base;
+
+ data = g_new0 (SaveData, 1);
+ data->self = self;
+ data->func = func;
+ data->user_data = user_data;
+
+ if (mime_type == NULL)
+ mime_type = gth_file_data_get_mime_type (self->priv->file_data);
+ pixbuf_type = get_pixbuf_type_from_mime_type (mime_type);
+
+ current_file = gth_browser_get_current_file (self->priv->browser);
+ data->original_file = gth_file_data_dup (current_file);
+ if (file != NULL)
+ gth_file_data_set_file (current_file, file);
+ g_file_info_set_attribute_boolean (current_file->info, "file::is-modified", FALSE);
+
+ _gdk_pixbuf_save_async (gth_image_viewer_get_current_pixbuf (GTH_IMAGE_VIEWER (self->priv->viewer)),
+ current_file,
+ pixbuf_type,
+ NULL,
+ NULL,
+ image_saved_cb,
+ data);
+
+ g_free (pixbuf_type);
+}
+
+
+static gboolean
+gth_image_viewer_page_real_can_save (GthViewerPage *base)
+{
+ return TRUE;
+}
+
+
+static void
+gth_image_viewer_page_real_save (GthViewerPage *base,
+ GFile *file,
+ FileSavedFunc func,
+ gpointer user_data)
+{
+ _gth_image_viewer_page_real_save (base, file, NULL, func, user_data);
+}
+
+
+/* -- gth_image_viewer_page_real_save_as -- */
+
+
+typedef struct {
+ GthImageViewerPage *self;
+ FileSavedFunc func;
+ gpointer user_data;
+ GthFileData *file_data;
+ GtkWidget *file_sel;
+ GtkWidget *format_chooser;
+} SaveAsData;
+
+
+static struct {
+ const char *type;
+ const char *extensions;
+ const char *default_ext;
+}
+supported_formats[] = {
+ { "image/jpeg", "jpeg jpg jpe", "jpeg" },
+ { "image/png", "png", "png" },
+ { "image/tiff", "tiff tif", "tiff" },
+ { NULL, NULL }
+};
+
+
+static void
+save_as_destroy_cb (GtkWidget *w,
+ SaveAsData *data)
+{
+ g_object_unref (data->file_data);
+ g_free (data);
+}
+
+
+static void
+save_as_response_cb (GtkDialog *file_sel,
+ int response,
+ SaveAsData *data)
+{
+ char *filename;
+ int format;
+ GFile *file;
+
+ if (response != GTK_RESPONSE_ACCEPT) {
+ if (data->func != NULL) {
+ (*data->func) ((GthViewerPage *) data->self,
+ data->file_data,
+ g_error_new_literal (G_IO_ERROR, G_IO_ERROR_CANCELLED, ""),
+ data->user_data);
+ }
+ gtk_widget_destroy (GTK_WIDGET (file_sel));
+ return;
+ }
+
+ filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (file_sel));
+ format = egg_file_format_chooser_get_format (EGG_FILE_FORMAT_CHOOSER (data->format_chooser), filename);
+ g_free (filename);
+
+ if ((format < 1) || (format > G_N_ELEMENTS (supported_formats)))
+ return;
+
+ file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (file_sel));
+ gth_file_data_set_file (data->file_data, file);
+ _gth_image_viewer_page_real_save ((GthViewerPage *) data->self,
+ file,
+ supported_formats[format - 1].type,
+ data->func,
+ data->user_data);
+ gtk_widget_destroy (GTK_WIDGET (data->file_sel));
+
+ g_object_unref (file);
+}
+
+
+static void
+format_chooser_selection_changed_cb (EggFileFormatChooser *self,
+ SaveAsData *data)
+{
+ char *filename;
+ int format;
+ char *basename;
+ char *basename_noext;
+ char *new_basename;
+
+ filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (data->file_sel));
+ format = egg_file_format_chooser_get_format (EGG_FILE_FORMAT_CHOOSER (data->format_chooser), filename);
+
+ if ((format < 1) || (format > G_N_ELEMENTS (supported_formats))) {
+ g_free (filename);
+ return;
+ }
+
+ basename = g_path_get_basename (filename);
+ basename_noext = _g_uri_remove_extension (basename);
+ new_basename = g_strconcat (basename_noext, ".", supported_formats[format - 1].default_ext, NULL);
+ gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (data->file_sel), new_basename);
+
+ g_free (new_basename);
+ g_free (basename_noext);
+ g_free (basename);
+ g_free (filename);
+}
+
+
+static char *
+get_icon_name_for_type (const char *mime_type)
+{
+ char *name = NULL;
+
+ if (mime_type != NULL) {
+ char *s;
+
+ name = g_strconcat ("gnome-mime-", mime_type, NULL);
+ for (s = name; *s; ++s)
+ if (! g_ascii_isalpha (*s))
+ *s = '-';
+ }
+
+ if ((name == NULL) || ! gtk_icon_theme_has_icon (gtk_icon_theme_get_default (), name)) {
+ g_free (name);
+ name = g_strdup ("image-x-generic");
+ }
+
+ return name;
+}
+
+
+static void
+gth_image_viewer_page_real_save_as (GthViewerPage *base,
+ FileSavedFunc func,
+ gpointer user_data)
+{
+ GthImageViewerPage *self;
+ GtkWidget *file_sel;
+ char *uri;
+ EggFileFormatChooser *format_chooser;
+ SaveAsData *data;
+ int i;
+
+ self = GTH_IMAGE_VIEWER_PAGE (base);
+ file_sel = gtk_file_chooser_dialog_new (_("Save Image"),
+ GTK_WINDOW (self->priv->browser),
+ GTK_FILE_CHOOSER_ACTION_SAVE,
+ GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+ GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
+ NULL);
+ gtk_file_chooser_set_local_only (GTK_FILE_CHOOSER (file_sel), FALSE);
+ gtk_file_chooser_set_do_overwrite_confirmation (GTK_FILE_CHOOSER (file_sel), TRUE);
+ gtk_dialog_set_default_response (GTK_DIALOG (file_sel), GTK_RESPONSE_ACCEPT);
+ uri = g_file_get_uri (self->priv->file_data->file);
+ gtk_file_chooser_set_uri (GTK_FILE_CHOOSER (file_sel), uri);
+
+ /**/
+
+ format_chooser = (EggFileFormatChooser *) egg_file_format_chooser_new ();
+ for (i = 0; supported_formats[i].type != NULL; i++) {
+ char *icon_name;
+ char **extensions;
+
+ icon_name = get_icon_name_for_type (supported_formats[i].type);
+ extensions = g_strsplit (supported_formats[i].extensions, " ", -1);
+ egg_file_format_chooser_add_format (format_chooser,
+ 0,
+ g_content_type_get_description (supported_formats[i].type),
+ icon_name,
+ extensions[0],
+ extensions[1],
+ extensions[2],
+ NULL);
+
+ g_strfreev (extensions);
+ g_free (icon_name);
+ }
+
+ gtk_file_chooser_set_extra_widget (GTK_FILE_CHOOSER (file_sel),
+ GTK_WIDGET (format_chooser));
+
+ /**/
+
+ data = g_new0 (SaveAsData, 1);
+ data->self = self;
+ data->func = func;
+ data->file_data = gth_file_data_dup (self->priv->file_data);
+ data->user_data = user_data;
+ data->file_sel = file_sel;
+ data->format_chooser = (GtkWidget *) format_chooser;
+
+ g_signal_connect (GTK_DIALOG (file_sel),
+ "response",
+ G_CALLBACK (save_as_response_cb),
+ data);
+ g_signal_connect (G_OBJECT (file_sel),
+ "destroy",
+ G_CALLBACK (save_as_destroy_cb),
+ data);
+ g_signal_connect (G_OBJECT (format_chooser),
+ "selection-changed",
+ G_CALLBACK (format_chooser_selection_changed_cb),
+ data);
+
+ gtk_window_set_transient_for (GTK_WINDOW (file_sel), GTK_WINDOW (self->priv->browser));
+ gtk_window_set_modal (GTK_WINDOW (file_sel), TRUE);
+ gtk_widget_show (file_sel);
+
+ g_free (uri);
+}
+
+
+static void
+gth_image_viewer_page_finalize (GObject *obj)
+{
+ GthImageViewerPage *self;
+
+ self = GTH_IMAGE_VIEWER_PAGE (obj);
+
+ g_object_unref (self->priv->history);
+ _g_object_unref (self->priv->file_data);
+
+ G_OBJECT_CLASS (gth_image_viewer_page_parent_class)->finalize (obj);
+}
+
+
+static void
+gth_image_viewer_page_class_init (GthImageViewerPageClass *klass)
+{
+ gth_image_viewer_page_parent_class = g_type_class_peek_parent (klass);
+ g_type_class_add_private (klass, sizeof (GthImageViewerPagePrivate));
+
+ G_OBJECT_CLASS (klass)->finalize = gth_image_viewer_page_finalize;
+}
+
+
+static void
+gth_viewer_page_interface_init (GthViewerPageIface *iface)
+{
+ iface->activate = gth_image_viewer_page_real_activate;
+ iface->deactivate = gth_image_viewer_page_real_deactivate;
+ iface->show = gth_image_viewer_page_real_show;
+ iface->hide = gth_image_viewer_page_real_hide;
+ iface->can_view = gth_image_viewer_page_real_can_view;
+ iface->view = gth_image_viewer_page_real_view;
+ iface->update_sensitivity = gth_image_viewer_page_real_update_sensitivity;
+ iface->can_save = gth_image_viewer_page_real_can_save;
+ iface->save = gth_image_viewer_page_real_save;
+ iface->save_as = gth_image_viewer_page_real_save_as;
+}
+
+
+static void
+gth_image_viewer_page_instance_init (GthImageViewerPage *self)
+{
+ self->priv = GTH_IMAGE_VIEWER_PAGE_GET_PRIVATE (self);
+ self->priv->history = gth_image_history_new ();
+}
+
+
+GType
+gth_image_viewer_page_get_type (void) {
+ static GType gth_image_viewer_page_type_id = 0;
+ if (gth_image_viewer_page_type_id == 0) {
+ static const GTypeInfo g_define_type_info = {
+ sizeof (GthImageViewerPageClass),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) gth_image_viewer_page_class_init,
+ (GClassFinalizeFunc) NULL,
+ NULL,
+ sizeof (GthImageViewerPage),
+ 0,
+ (GInstanceInitFunc) gth_image_viewer_page_instance_init,
+ NULL
+ };
+ static const GInterfaceInfo gth_viewer_page_info = {
+ (GInterfaceInitFunc) gth_viewer_page_interface_init,
+ (GInterfaceFinalizeFunc) NULL,
+ NULL
+ };
+ gth_image_viewer_page_type_id = g_type_register_static (G_TYPE_OBJECT, "GthImageViewerPage", &g_define_type_info, 0);
+ g_type_add_interface_static (gth_image_viewer_page_type_id, GTH_TYPE_VIEWER_PAGE, >h_viewer_page_info);
+ }
+ return gth_image_viewer_page_type_id;
+}
+
+
+GtkWidget *
+gth_image_viewer_page_get_image_viewer (GthImageViewerPage *self)
+{
+ return self->priv->viewer;
+}
+
+
+GdkPixbuf *
+gth_image_viewer_page_get_pixbuf (GthImageViewerPage *self)
+{
+ return gth_image_viewer_get_current_pixbuf (GTH_IMAGE_VIEWER (self->priv->viewer));
+}
+
+
+static void
+_gth_image_viewer_page_set_pixbuf (GthImageViewerPage *self,
+ GdkPixbuf *pixbuf,
+ gboolean modified)
+{
+ GthFileData *file_data;
+ int width;
+ int height;
+ char *size;
+
+ gth_image_viewer_set_pixbuf (GTH_IMAGE_VIEWER (self->priv->viewer), pixbuf);
+
+ file_data = gth_browser_get_current_file (GTH_BROWSER (self->priv->browser));
+
+ g_file_info_set_attribute_boolean (file_data->info, "file::is-modified", modified);
+
+ width = gdk_pixbuf_get_width (pixbuf);
+ height = gdk_pixbuf_get_height (pixbuf);
+ g_file_info_set_attribute_int32 (file_data->info, "image::width", width);
+ g_file_info_set_attribute_int32 (file_data->info, "image::height", height);
+
+ size = g_strdup_printf ("%d x %d", width, height);
+ g_file_info_set_attribute_string (file_data->info, "image::size", size);
+
+ gth_monitor_metadata_changed (gth_main_get_default_monitor (), file_data);
+
+ g_free (size);
+}
+
+
+void
+gth_image_viewer_page_set_pixbuf (GthImageViewerPage *self,
+ GdkPixbuf *pixbuf)
+{
+ gth_image_history_add_image (self->priv->history,
+ gth_image_viewer_page_get_pixbuf (self),
+ gth_browser_get_file_modified (GTH_BROWSER (self->priv->browser)));
+ _gth_image_viewer_page_set_pixbuf (self, pixbuf, TRUE);
+}
+
+
+void
+gth_image_viewer_page_undo (GthImageViewerPage *self)
+{
+ GthImageData *idata;
+
+ idata = gth_image_history_undo (self->priv->history,
+ gth_image_viewer_page_get_pixbuf (self),
+ gth_browser_get_file_modified (GTH_BROWSER (self->priv->browser)));
+ if (idata != NULL) {
+ _gth_image_viewer_page_set_pixbuf (self, idata->image, idata->unsaved);
+ gth_image_data_unref (idata);
+ }
+}
+
+
+void
+gth_image_viewer_page_redo (GthImageViewerPage *self)
+{
+ GthImageData *idata;
+
+ idata = gth_image_history_redo (self->priv->history,
+ gth_image_viewer_page_get_pixbuf (self),
+ gth_browser_get_file_modified (GTH_BROWSER (self->priv->browser)));
+ if (idata != NULL) {
+ _gth_image_viewer_page_set_pixbuf (self, idata->image, idata->unsaved);
+ gth_image_data_unref (idata);
+ }
+}
+
+
+GthImageHistory *
+gth_image_viewer_page_get_history (GthImageViewerPage *self)
+{
+ return self->priv->history;
+}
diff --git a/extensions/image_viewer/gth-image-viewer-page.h b/extensions/image_viewer/gth-image-viewer-page.h
new file mode 100644
index 0000000..2e6cda6
--- /dev/null
+++ b/extensions/image_viewer/gth-image-viewer-page.h
@@ -0,0 +1,61 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GTH_IMAGE_VIEWER_PAGE_H
+#define GTH_IMAGE_VIEWER_PAGE_H
+
+#include <gthumb.h>
+
+G_BEGIN_DECLS
+
+#define GTH_TYPE_IMAGE_VIEWER_PAGE (gth_image_viewer_page_get_type ())
+#define GTH_IMAGE_VIEWER_PAGE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTH_TYPE_IMAGE_VIEWER_PAGE, GthImageViewerPage))
+#define GTH_IMAGE_VIEWER_PAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTH_TYPE_IMAGE_VIEWER_PAGE, GthImageViewerPageClass))
+#define GTH_IS_IMAGE_VIEWER_PAGE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTH_TYPE_IMAGE_VIEWER_PAGE))
+#define GTH_IS_IMAGE_VIEWER_PAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTH_TYPE_IMAGE_VIEWER_PAGE))
+#define GTH_IMAGE_VIEWER_PAGE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTH_TYPE_IMAGE_VIEWER_PAGE, GthImageViewerPageClass))
+
+typedef struct _GthImageViewerPage GthImageViewerPage;
+typedef struct _GthImageViewerPageClass GthImageViewerPageClass;
+typedef struct _GthImageViewerPagePrivate GthImageViewerPagePrivate;
+
+struct _GthImageViewerPage {
+ GObject parent_instance;
+ GthImageViewerPagePrivate * priv;
+};
+
+struct _GthImageViewerPageClass {
+ GObjectClass parent_class;
+};
+
+GType gth_image_viewer_page_get_type (void);
+GtkWidget * gth_image_viewer_page_get_image_viewer (GthImageViewerPage *page);
+GdkPixbuf * gth_image_viewer_page_get_pixbuf (GthImageViewerPage *page);
+void gth_image_viewer_page_set_pixbuf (GthImageViewerPage *page,
+ GdkPixbuf *pixbuf);
+void gth_image_viewer_page_undo (GthImageViewerPage *page);
+void gth_image_viewer_page_redo (GthImageViewerPage *page);
+GthImageHistory * gth_image_viewer_page_get_history (GthImageViewerPage *self);
+
+G_END_DECLS
+
+#endif /* GTH_IMAGE_VIEWER_PAGE_H */
diff --git a/extensions/image_viewer/gth-metadata-provider-image.c b/extensions/image_viewer/gth-metadata-provider-image.c
new file mode 100644
index 0000000..f233729
--- /dev/null
+++ b/extensions/image_viewer/gth-metadata-provider-image.c
@@ -0,0 +1,158 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <string.h>
+#include <glib/gi18n.h>
+#include <glib.h>
+#include <gdk-pixbuf/gdk-pixbuf-io.h>
+#include <gthumb.h>
+#include "gth-metadata-provider-image.h"
+
+
+#define GTH_METADATA_PROVIDER_IMAGE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GTH_TYPE_METADATA_PROVIDER_IMAGE, GthMetadataProviderImagePrivate))
+
+
+struct _GthMetadataProviderImagePrivate {
+ int dummy;
+};
+
+
+static GthMetadataProviderClass *parent_class = NULL;
+
+
+static void
+gth_metadata_provider_image_read (GthMetadataProvider *self,
+ GthFileData *file_data,
+ const char *attributes)
+{
+ GFileAttributeMatcher *matcher;
+
+ matcher = g_file_attribute_matcher_new (attributes);
+
+ if (g_file_attribute_matcher_matches (matcher, "image::*")) {
+ GdkPixbufFormat *format;
+ GFile *cache_file;
+ char *filename;
+ int width, height;
+
+ cache_file = _g_file_get_cache_file (file_data->file);
+ filename = g_file_get_path (cache_file);
+ format = gdk_pixbuf_get_file_info (filename, &width, &height);
+
+ if (format != NULL) {
+ char *size;
+
+ g_file_info_set_attribute_string (file_data->info, "image::format", gdk_pixbuf_format_get_description (format));
+
+ g_file_info_set_attribute_int32 (file_data->info, "image::width", width);
+ g_file_info_set_attribute_int32 (file_data->info, "image::height", height);
+
+ size = g_strdup_printf ("%d x %d", width, height);
+ g_file_info_set_attribute_string (file_data->info, "image::size", size);
+
+ g_free (size);
+ }
+
+ g_free (filename);
+ g_object_unref (cache_file);
+ }
+
+ g_file_attribute_matcher_unref (matcher);
+}
+
+
+static void
+gth_metadata_provider_image_finalize (GObject *object)
+{
+ /*GthMetadataProviderImage *image = GTH_METADATA_PROVIDER_IMAGE (object);*/
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+
+static GObject *
+gth_metadata_provider_constructor (GType type,
+ guint n_construct_properties,
+ GObjectConstructParam *construct_properties)
+{
+ GthMetadataProviderClass *klass;
+ GObjectClass *parent_class;
+ GObject *obj;
+ GthMetadataProvider *self;
+
+ klass = GTH_METADATA_PROVIDER_CLASS (g_type_class_peek (GTH_TYPE_METADATA_PROVIDER));
+ parent_class = G_OBJECT_CLASS (g_type_class_peek_parent (klass));
+ obj = parent_class->constructor (type, n_construct_properties, construct_properties);
+ self = GTH_METADATA_PROVIDER (obj);
+
+ g_object_set (self, "readable-attributes", "image::format,image::size,image::width,image::height", NULL);
+
+ return obj;
+}
+
+
+static void
+gth_metadata_provider_image_class_init (GthMetadataProviderImageClass *klass)
+{
+ parent_class = g_type_class_peek_parent (klass);
+ g_type_class_add_private (klass, sizeof (GthMetadataProviderImagePrivate));
+
+ G_OBJECT_CLASS (klass)->finalize = gth_metadata_provider_image_finalize;
+ G_OBJECT_CLASS (klass)->constructor = gth_metadata_provider_constructor;
+
+ GTH_METADATA_PROVIDER_CLASS (klass)->read = gth_metadata_provider_image_read;
+}
+
+
+static void
+gth_metadata_provider_image_init (GthMetadataProviderImage *catalogs)
+{
+}
+
+
+GType
+gth_metadata_provider_image_get_type (void)
+{
+ static GType type = 0;
+
+ if (! type) {
+ GTypeInfo type_info = {
+ sizeof (GthMetadataProviderImageClass),
+ NULL,
+ NULL,
+ (GClassInitFunc) gth_metadata_provider_image_class_init,
+ NULL,
+ NULL,
+ sizeof (GthMetadataProviderImage),
+ 0,
+ (GInstanceInitFunc) gth_metadata_provider_image_init
+ };
+
+ type = g_type_register_static (GTH_TYPE_METADATA_PROVIDER,
+ "GthMetadataProviderImage",
+ &type_info,
+ 0);
+ }
+
+ return type;
+}
diff --git a/extensions/image_viewer/gth-metadata-provider-image.h b/extensions/image_viewer/gth-metadata-provider-image.h
new file mode 100644
index 0000000..1af3cbc
--- /dev/null
+++ b/extensions/image_viewer/gth-metadata-provider-image.h
@@ -0,0 +1,54 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GTH_METADATA_PROVIDER_IMAGE_H
+#define GTH_METADATA_PROVIDER_IMAGE_H
+
+#include <glib.h>
+#include <glib-object.h>
+#include <gthumb.h>
+
+#define GTH_TYPE_METADATA_PROVIDER_IMAGE (gth_metadata_provider_image_get_type ())
+#define GTH_METADATA_PROVIDER_IMAGE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GTH_TYPE_METADATA_PROVIDER_IMAGE, GthMetadataProviderImage))
+#define GTH_METADATA_PROVIDER_IMAGE_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), GTH_TYPE_METADATA_PROVIDER_IMAGE, GthMetadataProviderImageClass))
+#define GTH_IS_METADATA_PROVIDER_IMAGE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GTH_TYPE_METADATA_PROVIDER_IMAGE))
+#define GTH_IS_METADATA_PROVIDER_IMAGE_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GTH_TYPE_METADATA_PROVIDER_IMAGE))
+#define GTH_METADATA_PROVIDER_IMAGE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS((o), GTH_TYPE_METADATA_PROVIDER_IMAGE, GthMetadataProviderImageClass))
+
+typedef struct _GthMetadataProviderImage GthMetadataProviderImage;
+typedef struct _GthMetadataProviderImagePrivate GthMetadataProviderImagePrivate;
+typedef struct _GthMetadataProviderImageClass GthMetadataProviderImageClass;
+
+struct _GthMetadataProviderImage
+{
+ GthMetadataProvider __parent;
+ GthMetadataProviderImagePrivate *priv;
+};
+
+struct _GthMetadataProviderImageClass
+{
+ GthMetadataProviderClass __parent_class;
+};
+
+GType gth_metadata_provider_image_get_type (void) G_GNUC_CONST;
+
+#endif /* GTH_METADATA_PROVIDER_IMAGE_H */
diff --git a/extensions/image_viewer/image_viewer.extension.in.in b/extensions/image_viewer/image_viewer.extension.in.in
new file mode 100644
index 0000000..00123f5
--- /dev/null
+++ b/extensions/image_viewer/image_viewer.extension.in.in
@@ -0,0 +1,10 @@
+[Extension]
+_Name=Image Viewer
+_Description=Allows to view images.
+Authors=Paolo Bacchilega <paobac src gnome org>
+Copyright=Copyright © 2009 Paolo Bacchilega
+Version=1
+
+[Loader]
+Type=module
+File=%LIBRARY%
diff --git a/extensions/image_viewer/main.c b/extensions/image_viewer/main.c
new file mode 100644
index 0000000..57e1a06
--- /dev/null
+++ b/extensions/image_viewer/main.c
@@ -0,0 +1,72 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+
+#include <config.h>
+#include <gtk/gtk.h>
+#include <gthumb.h>
+#include "gth-image-viewer-page.h"
+#include "gth-metadata-provider-image.h"
+#include "preferences.h"
+
+
+GthMetadataCategory image_metadata_category[] = {
+ { "image", N_("Image"), 5 },
+ { NULL, NULL, 0 }
+};
+
+
+GthMetadataInfo image_metadata_info[] = {
+ { "image::size", N_("Size"), "image", 1, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+ { "image::format", N_("Format"), "image", 2, GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW },
+ { NULL, NULL, NULL, 0, 0 }
+};
+
+
+G_MODULE_EXPORT void
+gthumb_extension_activate (void)
+{
+ gth_main_register_metadata_category (image_metadata_category);
+ gth_main_register_metadata_info_v (image_metadata_info);
+ gth_main_register_viewer_page (GTH_TYPE_IMAGE_VIEWER_PAGE);
+ gth_main_register_metadata_provider (GTH_TYPE_METADATA_PROVIDER_IMAGE);
+ gth_hook_add_callback ("dlg-preferences-construct", 10, G_CALLBACK (image_viewer__dlg_preferences_construct_cb), NULL);
+}
+
+
+G_MODULE_EXPORT void
+gthumb_extension_deactivate (void)
+{
+}
+
+
+G_MODULE_EXPORT gboolean
+gthumb_extension_is_configurable (void)
+{
+ return FALSE;
+}
+
+
+G_MODULE_EXPORT void
+gthumb_extension_configure (GtkWindow *parent)
+{
+}
diff --git a/extensions/image_viewer/preferences.c b/extensions/image_viewer/preferences.c
new file mode 100644
index 0000000..1d0bbac
--- /dev/null
+++ b/extensions/image_viewer/preferences.c
@@ -0,0 +1,162 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <glib/gi18n.h>
+#include "preferences.h"
+
+
+#define BROWSER_DATA_KEY "image-viewer-preference-data"
+
+
+typedef struct {
+ GtkBuilder *builder;
+ GtkWidget *change_zoom_combobox;
+ GtkWidget *transp_type_combobox;
+} BrowserData;
+
+
+static void
+browser_data_free (BrowserData *data)
+{
+ g_object_unref (data->builder);
+ g_free (data);
+}
+
+
+static void
+zoom_quality_high_cb (GtkToggleButton *button,
+ BrowserData *data)
+{
+ if (! gtk_toggle_button_get_active (button))
+ return;
+ eel_gconf_set_enum (PREF_ZOOM_QUALITY, GTH_TYPE_ZOOM_QUALITY, GTH_ZOOM_QUALITY_HIGH);
+}
+
+
+static void
+zoom_quality_low_cb (GtkToggleButton *button,
+ BrowserData *data)
+{
+ if (! gtk_toggle_button_get_active (button))
+ return;
+ eel_gconf_set_enum (PREF_ZOOM_QUALITY, GTH_TYPE_ZOOM_QUALITY, GTH_ZOOM_QUALITY_LOW);
+}
+
+
+static void
+zoom_change_changed_cb (GtkComboBox *combo_box,
+ BrowserData *data)
+{
+ eel_gconf_set_enum (PREF_ZOOM_CHANGE, GTH_TYPE_ZOOM_CHANGE, gtk_combo_box_get_active (combo_box));
+}
+
+
+static void
+reset_scrollbars_toggled_cb (GtkToggleButton *button,
+ BrowserData *data)
+{
+ eel_gconf_set_boolean (PREF_RESET_SCROLLBARS, gtk_toggle_button_get_active (button));
+}
+
+
+static void
+transp_type_changed_cb (GtkComboBox *combo_box,
+ BrowserData *data)
+{
+ eel_gconf_set_enum (PREF_TRANSP_TYPE, GTH_TYPE_TRANSP_TYPE, gtk_combo_box_get_active (combo_box));
+}
+
+
+void
+image_viewer__dlg_preferences_construct_cb (GtkWidget *dialog,
+ GthBrowser *browser,
+ GtkBuilder *dialog_builder)
+{
+ BrowserData *data;
+ GtkWidget *notebook;
+ GtkWidget *page;
+ GtkWidget *label;
+
+ data = g_new0 (BrowserData, 1);
+ data->builder = _gtk_builder_new_from_file ("image-viewer-preferences.ui", "image_viewer");
+
+ notebook = _gtk_builder_get_widget (dialog_builder, "notebook");
+
+ page = _gtk_builder_get_widget (data->builder, "preferences_page");
+ gtk_widget_show (page);
+
+ data->change_zoom_combobox = _gtk_combo_box_new_with_texts (_("Set to actual size"),
+ _("Keep previous zoom"),
+ _("Fit to window"),
+ _("Fit to window if larger"),
+ _("Fit to width"),
+ _("Fit to width if larger"),
+ NULL);
+ gtk_combo_box_set_active (GTK_COMBO_BOX (data->change_zoom_combobox), eel_gconf_get_enum (PREF_ZOOM_CHANGE, GTH_TYPE_ZOOM_CHANGE, GTH_ZOOM_CHANGE_FIT_SIZE_IF_LARGER));
+ gtk_widget_show (data->change_zoom_combobox);
+ gtk_container_add (GTK_CONTAINER (_gtk_builder_get_widget (data->builder, "zoom_change_box")), data->change_zoom_combobox);
+
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (_gtk_builder_get_widget (data->builder, "toggle_reset_scrollbars")), eel_gconf_get_boolean (PREF_RESET_SCROLLBARS, TRUE));
+
+ data->transp_type_combobox = _gtk_combo_box_new_with_texts (_("White"),
+ _("None"),
+ _("Black"),
+ _("Checked"),
+ NULL);
+ gtk_combo_box_set_active (GTK_COMBO_BOX (data->transp_type_combobox), eel_gconf_get_enum (PREF_TRANSP_TYPE, GTH_TYPE_TRANSP_TYPE, GTH_TRANSP_TYPE_NONE));
+ gtk_widget_show (data->transp_type_combobox);
+ gtk_container_add (GTK_CONTAINER (_gtk_builder_get_widget (data->builder, "transp_type_box")), data->transp_type_combobox);
+
+ if (eel_gconf_get_enum (PREF_ZOOM_QUALITY, GTH_TYPE_ZOOM_QUALITY, GTH_ZOOM_QUALITY_HIGH) == GTH_ZOOM_QUALITY_HIGH)
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (_gtk_builder_get_widget (data->builder, "opt_zoom_quality_high")), TRUE);
+ else
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (_gtk_builder_get_widget (data->builder, "opt_zoom_quality_low")), TRUE);
+
+ g_signal_connect (G_OBJECT (_gtk_builder_get_widget (data->builder, "opt_zoom_quality_high")),
+ "toggled",
+ G_CALLBACK (zoom_quality_high_cb),
+ data);
+ g_signal_connect (G_OBJECT (_gtk_builder_get_widget (data->builder, "opt_zoom_quality_low")),
+ "toggled",
+ G_CALLBACK (zoom_quality_low_cb),
+ data);
+ g_signal_connect (G_OBJECT (data->change_zoom_combobox),
+ "changed",
+ G_CALLBACK (zoom_change_changed_cb),
+ data);
+ g_signal_connect (G_OBJECT (data->transp_type_combobox),
+ "changed",
+ G_CALLBACK (transp_type_changed_cb),
+ data);
+ g_signal_connect (G_OBJECT (_gtk_builder_get_widget (data->builder, "toggle_reset_scrollbars")),
+ "toggled",
+ G_CALLBACK (reset_scrollbars_toggled_cb),
+ data);
+
+ label = gtk_label_new (_("Viewer"));
+ gtk_widget_show (label);
+
+ gtk_notebook_append_page (GTK_NOTEBOOK (notebook), page, label);
+
+ g_object_set_data_full (G_OBJECT (dialog), BROWSER_DATA_KEY, data, (GDestroyNotify) browser_data_free);
+}
diff --git a/extensions/image_viewer/preferences.h b/extensions/image_viewer/preferences.h
new file mode 100644
index 0000000..f8fd8d3
--- /dev/null
+++ b/extensions/image_viewer/preferences.h
@@ -0,0 +1,40 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef PREFERENCES_H
+#define PREFERENCES_H
+
+#include <gthumb.h>
+
+#define PREF_ZOOM_QUALITY "/apps/gthumb/viewer/zoom_quality"
+#define PREF_ZOOM_CHANGE "/apps/gthumb/viewer/zoom_change"
+#define PREF_TRANSP_TYPE "/apps/gthumb/viewer/transparency_type"
+#define PREF_RESET_SCROLLBARS "/apps/gthumb/viewer/reset_scrollbars"
+#define PREF_CHECK_TYPE "/apps/gthumb/viewer/check_type"
+#define PREF_CHECK_SIZE "/apps/gthumb/viewer/check_size"
+#define PREF_BLACK_BACKGROUND "/apps/gthumb/viewer/black_background"
+
+void image_viewer__dlg_preferences_construct_cb (GtkWidget *dialog,
+ GthBrowser *browser,
+ GtkBuilder *builder);
+
+#endif /* CALLBACKS_H */
diff --git a/extensions/search/Makefile.am b/extensions/search/Makefile.am
new file mode 100644
index 0000000..0765f2e
--- /dev/null
+++ b/extensions/search/Makefile.am
@@ -0,0 +1,40 @@
+SUBDIRS = data
+
+extensiondir = $(libdir)/gthumb-2.0/extensions
+extension_LTLIBRARIES = libsearch.la
+
+libsearch_la_SOURCES = \
+ actions.c \
+ actions.h \
+ callbacks.c \
+ callbacks.h \
+ gth-search.c \
+ gth-search.h \
+ gth-search-editor-dialog.c \
+ gth-search-editor-dialog.h \
+ gth-search-task.c \
+ gth-search-task.h \
+ main.c
+
+libsearch_la_CFLAGS = $(GTHUMB_CFLAGS) -I$(top_srcdir) -I$(top_builddir)/gthumb
+libsearch_la_LDFLAGS = $(EXTENSION_LIBTOOL_FLAGS)
+libsearch_la_LIBADD = $(GTHUMB_LIBS)
+libsearch_la_DEPENDENCIES = $(top_builddir)/gthumb/gthumb$(EXEEXT)
+
+extensioninidir = $(extensiondir)
+extensionini_in_files = search.extension.in.in
+extensionini_DATA = $(extensionini_in_files:.extension.in.in=.extension)
+
+%.extension.in: %.extension.in.in $(extension_LTLIBRARIES)
+ sed -e "s|%LIBRARY%|`. ./$(extension_LTLIBRARIES) && echo $$dlname`|" \
+ $< > $@
+
+%.extension: %.extension.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*po) ; LC_ALL=C $(INTLTOOL_MERGE) -d -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< $@
+
+EXTRA_DIST = $(extensionini_in_files)
+
+CLEANFILES = $(extensionini_DATA)
+
+DISTCLEANFILES = $(extensionini_DATA)
+
+-include $(top_srcdir)/git.mk
diff --git a/extensions/search/actions.c b/extensions/search/actions.c
new file mode 100644
index 0000000..1bcdf40
--- /dev/null
+++ b/extensions/search/actions.c
@@ -0,0 +1,263 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2008 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+
+#include <config.h>
+#include <glib/gi18n.h>
+#include <gthumb.h>
+#include <extensions/catalogs/gth-catalog.h>
+#include "gth-search-editor-dialog.h"
+#include "gth-search-task.h"
+
+
+static void
+search_editor_dialog__response_cb (GtkDialog *dialog,
+ int response,
+ gpointer user_data)
+{
+ GthBrowser *browser = user_data;
+ GthSearch *search;
+ GError *error = NULL;
+ GFile *search_catalog;
+ GthTask *task;
+
+ if (response != GTK_RESPONSE_OK) {
+ gtk_widget_destroy (GTK_WIDGET (dialog));
+ return;
+ }
+
+ search = gth_search_editor_dialog_get_search (GTH_SEARCH_EDITOR_DIALOG (dialog), &error);
+ if (search == NULL) {
+ _gtk_error_dialog_from_gerror_show (GTK_WINDOW (dialog), _("Could not perform the search"), &error);
+ return;
+ }
+
+ search_catalog = gth_catalog_file_from_relative_path (_("Search Result"), ".search");
+ task = gth_search_task_new (browser, search, search_catalog);
+ gth_browser_exec_task (browser, task);
+
+ g_object_unref (task);
+ g_object_unref (search_catalog);
+ g_object_unref (search);
+ gtk_widget_destroy (GTK_WIDGET (dialog));
+}
+
+
+void
+gth_browser_activate_action_edit_find (GtkAction *action,
+ GthBrowser *browser)
+{
+ GthSearch *search;
+ GthFileSource *file_source;
+ GtkWidget *dialog;
+
+ search = gth_search_new ();
+ file_source = gth_main_get_file_source (gth_browser_get_location (browser));
+ if (GTH_IS_FILE_SOURCE_VFS (file_source)) {
+ GFile *folder;
+
+ folder = gth_file_source_to_gio_file (file_source, gth_browser_get_location (browser));
+ gth_search_set_folder (search, folder);
+ g_object_unref (folder);
+ }
+ gth_search_set_recursive (search, TRUE);
+
+ dialog = gth_search_editor_dialog_new (_("Find"), search, GTK_WINDOW (browser));
+ gtk_dialog_add_button (GTK_DIALOG (dialog), GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL);
+ gtk_dialog_add_button (GTK_DIALOG (dialog), GTK_STOCK_FIND, GTK_RESPONSE_OK);
+
+ g_signal_connect (dialog, "response",
+ G_CALLBACK (search_editor_dialog__response_cb),
+ browser);
+
+ gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
+ gtk_window_present (GTK_WINDOW (dialog));
+
+ g_object_unref (file_source);
+ g_object_unref (search);
+}
+
+
+typedef struct {
+ GthBrowser *browser;
+ GFile *file;
+} SearchData;
+
+
+static void
+search_data_free (SearchData *search_data)
+{
+ g_object_unref (search_data->file);
+ g_free (search_data);
+}
+
+
+static void
+search_edit_response_cb (GtkDialog *dialog,
+ int response,
+ gpointer user_data)
+{
+ SearchData *search_data = user_data;
+ GthSearch *search;
+ GError *error = NULL;
+ GthTask *task;
+
+ if (response != GTK_RESPONSE_OK) {
+ gtk_widget_destroy (GTK_WIDGET (dialog));
+ search_data_free (search_data);
+ return;
+ }
+
+ search = gth_search_editor_dialog_get_search (GTH_SEARCH_EDITOR_DIALOG (dialog), &error);
+ if (search == NULL) {
+ _gtk_error_dialog_from_gerror_show (GTK_WINDOW (dialog), _("Could not perform the search"), &error);
+ search_data_free (search_data);
+ return;
+ }
+
+ task = gth_search_task_new (search_data->browser, search, search_data->file);
+ gth_browser_exec_task (search_data->browser, task);
+
+ gtk_widget_destroy (GTK_WIDGET (dialog));
+
+ g_object_unref (task);
+ g_object_unref (search);
+ search_data_free (search_data);
+}
+
+
+static void
+search_edit_buffer_ready_cb (void *buffer,
+ gsize count,
+ GError *error,
+ gpointer user_data)
+{
+ SearchData *search_data = user_data;
+ GError *local_error = NULL;
+ GthSearch *search;
+ GtkWidget *dialog;
+
+ if (error != NULL) {
+ _gtk_error_dialog_from_gerror_show (GTK_WINDOW (search_data->browser), _("Could not perform the search"), &error);
+ return;
+ }
+
+ search = gth_search_new_from_data (buffer, count, &local_error);
+ if (search == NULL) {
+ _gtk_error_dialog_from_gerror_show (GTK_WINDOW (search_data->browser), _("Could not perform the search"), &local_error);
+ return;
+ }
+
+ dialog = gth_search_editor_dialog_new (_("Find"), search, GTK_WINDOW (search_data->browser));
+ gtk_dialog_add_button (GTK_DIALOG (dialog), GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL);
+ gtk_dialog_add_button (GTK_DIALOG (dialog), GTK_STOCK_FIND, GTK_RESPONSE_OK);
+
+ g_signal_connect (dialog, "response",
+ G_CALLBACK (search_edit_response_cb),
+ search_data);
+
+ gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
+ gtk_window_present (GTK_WINDOW (dialog));
+
+ g_object_unref (search);
+}
+
+
+void
+gth_browser_activate_action_edit_search_edit (GtkAction *action,
+ GthBrowser *browser)
+{
+ GFile *location;
+ SearchData *search_data;
+ GFile *file;
+
+ location = gth_browser_get_location (browser);
+
+ search_data = g_new0 (SearchData, 1);
+ search_data->browser = browser;
+ search_data->file = g_file_dup (location);
+
+ file = gth_main_get_gio_file (location);
+ g_load_file_async (file,
+ G_PRIORITY_DEFAULT,
+ NULL,
+ search_edit_buffer_ready_cb,
+ search_data);
+
+ g_object_unref (file);
+}
+
+
+static void
+search_update_buffer_ready_cb (void *buffer,
+ gsize count,
+ GError *error,
+ gpointer user_data)
+{
+ SearchData *search_data = user_data;
+ GError *local_error = NULL;
+ GthSearch *search;
+ GthTask *task;
+
+ if (error != NULL) {
+ _gtk_error_dialog_from_gerror_show (GTK_WINDOW (search_data->browser), _("Could not perform the search"), &error);
+ return;
+ }
+
+ search = gth_search_new_from_data (buffer, count, &local_error);
+ if (search == NULL) {
+ _gtk_error_dialog_from_gerror_show (GTK_WINDOW (search_data->browser), _("Could not perform the search"), &local_error);
+ return;
+ }
+
+ task = gth_search_task_new (search_data->browser, search, search_data->file);
+ gth_browser_exec_task (search_data->browser, task);
+
+ g_object_unref (task);
+ g_object_unref (search);
+ search_data_free (search_data);
+}
+
+
+void
+gth_browser_activate_action_edit_search_update (GtkAction *action,
+ GthBrowser *browser)
+{
+ GFile *location;
+ SearchData *search_data;
+ GFile *file;
+
+ location = gth_browser_get_location (browser);
+
+ search_data = g_new0 (SearchData, 1);
+ search_data->browser = browser;
+ search_data->file = g_file_dup (location);
+
+ file = gth_main_get_gio_file (location);
+ g_load_file_async (file,
+ G_PRIORITY_DEFAULT,
+ NULL,
+ search_update_buffer_ready_cb,
+ search_data);
+
+ g_object_unref (file);
+}
diff --git a/extensions/search/actions.h b/extensions/search/actions.h
new file mode 100644
index 0000000..0d996dc
--- /dev/null
+++ b/extensions/search/actions.h
@@ -0,0 +1,34 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef ACTIONS_H
+#define ACTIONS_H
+
+#include <gtk/gtk.h>
+
+#define DEFINE_ACTION(x) void x (GtkAction *action, gpointer data);
+
+DEFINE_ACTION(gth_browser_activate_action_edit_find)
+DEFINE_ACTION(gth_browser_activate_action_edit_search_edit)
+DEFINE_ACTION(gth_browser_activate_action_edit_search_update)
+
+#endif /* ACTIONS_H */
diff --git a/extensions/search/callbacks.c b/extensions/search/callbacks.c
new file mode 100644
index 0000000..ee6c29e
--- /dev/null
+++ b/extensions/search/callbacks.c
@@ -0,0 +1,204 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+
+#include <config.h>
+#include <glib/gi18n.h>
+#include <glib-object.h>
+#include <gthumb.h>
+#include <extensions/catalogs/gth-catalog.h>
+#include "actions.h"
+#include "gth-search.h"
+
+
+#define BROWSER_DATA_KEY "search-browser-data"
+
+
+static const char *find_ui_info =
+"<ui>"
+" <menubar name='MenuBar'>"
+" <menu name='Edit' action='EditMenu'>"
+" <placeholder name='Edit_Actions'>"
+" <menuitem action='Edit_Find'/>"
+" </placeholder>"
+" </menu>"
+" </menubar>"
+" <toolbar name='ToolBar'>"
+" <placeholder name='SourceCommands'>"
+" <toolitem action='Edit_Find'/>"
+" </placeholder>"
+" </toolbar>"
+"</ui>";
+
+
+static GtkActionEntry find_action_entries[] = {
+ { "Edit_Find", GTK_STOCK_FIND,
+ NULL, NULL,
+ NULL,
+ G_CALLBACK (gth_browser_activate_action_edit_find) }
+};
+static guint find_action_entries_size = G_N_ELEMENTS (find_action_entries);
+
+
+static const char *search_ui_info =
+"<ui>"
+" <menubar name='MenuBar'>"
+" <menu name='Edit' action='EditMenu'>"
+" <placeholder name='Edit_Actions'>"
+" <menuitem action='Edit_Search_Edit'/>"
+" <menuitem action='Edit_Search_Update'/>"
+" </placeholder>"
+" </menu>"
+" </menubar>"
+" <toolbar name='ToolBar'>"
+" <placeholder name='SourceCommands'>"
+" <toolitem action='Edit_Search_Update'/>"
+" <toolitem action='Edit_Search_Edit'/>"
+" </placeholder>"
+" </toolbar>"
+"</ui>";
+
+
+static GtkActionEntry search_actions_entries[] = {
+ { "Edit_Search_Edit", GTK_STOCK_FIND_AND_REPLACE,
+ N_("Edit Search"), "<ctrl>F",
+ NULL,
+ G_CALLBACK (gth_browser_activate_action_edit_search_edit) },
+ { "Edit_Search_Update", GTK_STOCK_REFRESH,
+ N_("Redo Search"), "<shift><ctrl>R",
+ NULL,
+ G_CALLBACK (gth_browser_activate_action_edit_search_update) }
+};
+static guint search_actions_entries_size = G_N_ELEMENTS (search_actions_entries);
+
+
+typedef struct {
+ GtkActionGroup *find_action;
+ guint find_merge_id;
+ GtkActionGroup *search_actions;
+ guint search_merge_id;
+} BrowserData;
+
+
+static void
+browser_data_free (BrowserData *data)
+{
+ g_free (data);
+}
+
+
+void
+search__gth_browser_construct_cb (GthBrowser *browser)
+{
+ BrowserData *data;
+ GError *error = NULL;
+
+ g_return_if_fail (GTH_IS_BROWSER (browser));
+
+ data = g_new0 (BrowserData, 1);
+
+ data->find_action = gtk_action_group_new ("Find Action");
+ gtk_action_group_set_translation_domain (data->find_action, NULL);
+ gtk_action_group_add_actions (data->find_action,
+ find_action_entries,
+ find_action_entries_size,
+ browser);
+ gtk_ui_manager_insert_action_group (gth_browser_get_ui_manager (browser), data->find_action, 0);
+
+ data->search_actions = gtk_action_group_new ("Search Actions");
+ gtk_action_group_set_translation_domain (data->search_actions, NULL);
+ gtk_action_group_add_actions (data->search_actions,
+ search_actions_entries,
+ search_actions_entries_size,
+ browser);
+ gtk_ui_manager_insert_action_group (gth_browser_get_ui_manager (browser), data->search_actions, 0);
+
+ data->find_merge_id = gtk_ui_manager_add_ui_from_string (gth_browser_get_ui_manager (browser), find_ui_info, -1, &error);
+ if (data->find_merge_id == 0) {
+ g_warning ("building menus failed: %s", error->message);
+ g_error_free (error);
+ }
+
+ g_object_set_data_full (G_OBJECT (browser), BROWSER_DATA_KEY, data, (GDestroyNotify) browser_data_free);
+}
+
+
+void
+search__gth_browser_load_location_after_cb (GthBrowser *browser,
+ GFile *location,
+ const GError *error)
+{
+ BrowserData *data;
+ char *uri;
+
+ if (location == NULL)
+ return;
+
+ data = g_object_get_data (G_OBJECT (browser), BROWSER_DATA_KEY);
+ uri = g_file_get_uri (location);
+
+ if (g_str_has_suffix (uri, ".search") && (error == NULL)) {
+ if (data->find_merge_id != 0) {
+ gtk_ui_manager_remove_ui (gth_browser_get_ui_manager (browser), data->find_merge_id);
+ data->find_merge_id = 0;
+ }
+ if (data->search_merge_id == 0) {
+ GError *local_error = NULL;
+
+ data->search_merge_id = gtk_ui_manager_add_ui_from_string (gth_browser_get_ui_manager (browser), search_ui_info, -1, &local_error);
+ if (data->search_merge_id == 0) {
+ g_warning ("building menus failed: %s", local_error->message);
+ g_error_free (local_error);
+ }
+ }
+ }
+ else {
+ if (data->search_merge_id != 0) {
+ gtk_ui_manager_remove_ui (gth_browser_get_ui_manager (browser), data->search_merge_id);
+ data->search_merge_id = 0;
+ }
+ if (data->find_merge_id == 0) {
+ GError *local_error = NULL;
+
+ data->find_merge_id = gtk_ui_manager_add_ui_from_string (gth_browser_get_ui_manager (browser), find_ui_info, -1, &local_error);
+ if (data->find_merge_id == 0) {
+ g_warning ("building menus failed: %s", local_error->message);
+ g_error_free (local_error);
+ }
+ }
+ }
+
+ g_free (uri);
+}
+
+
+GthCatalog *
+search__gth_catalog_load_from_data_cb (const void *buffer)
+{
+ if ((buffer == NULL)
+ || (strncmp (buffer, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<search ", 47) != 0))
+ {
+ return NULL;
+ }
+ else
+ return (GthCatalog *) gth_search_new ();
+}
diff --git a/extensions/search/callbacks.h b/extensions/search/callbacks.h
new file mode 100644
index 0000000..76588ee
--- /dev/null
+++ b/extensions/search/callbacks.h
@@ -0,0 +1,36 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef CALLBACKS_H
+#define CALLBACKS_H
+
+#include <gthumb.h>
+#include <extensions/catalogs/gth-catalog.h>
+
+void search__gth_browser_construct_cb (GthBrowser *browser);
+void search__gth_browser_update_sensitivity_cb (GthBrowser *browser);
+void search__gth_browser_load_location_after_cb (GthBrowser *browser,
+ GFile *location,
+ GError *error);
+GthCatalog * search__gth_catalog_load_from_data_cb (const void *buffer);
+
+#endif /* CALLBACKS_H */
diff --git a/extensions/search/data/Makefile.am b/extensions/search/data/Makefile.am
new file mode 100644
index 0000000..4d5385d
--- /dev/null
+++ b/extensions/search/data/Makefile.am
@@ -0,0 +1,2 @@
+SUBDIRS = ui
+-include $(top_srcdir)/git.mk
diff --git a/extensions/search/data/ui/Makefile.am b/extensions/search/data/ui/Makefile.am
new file mode 100644
index 0000000..d7f3ab9
--- /dev/null
+++ b/extensions/search/data/ui/Makefile.am
@@ -0,0 +1,5 @@
+uidir = $(datadir)/gthumb/ui
+ui_DATA = search-editor.ui
+EXTRA_DIST = $(ui_DATA)
+
+-include $(top_srcdir)/git.mk
diff --git a/extensions/search/data/ui/search-editor.ui b/extensions/search/data/ui/search-editor.ui
new file mode 100644
index 0000000..762395d
--- /dev/null
+++ b/extensions/search/data/ui/search-editor.ui
@@ -0,0 +1,122 @@
+<?xml version="1.0"?>
+<interface>
+ <requires lib="gtk+" version="2.14"/>
+ <!-- interface-naming-policy project-wide -->
+ <object class="GtkVBox" id="search_editor">
+ <property name="visible">True</property>
+ <property name="border_width">6</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkTable" id="table1">
+ <property name="visible">True</property>
+ <property name="n_rows">2</property>
+ <property name="n_columns">2</property>
+ <property name="column_spacing">6</property>
+ <property name="row_spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label2">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Start _at:</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options">GTK_FILL</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFileChooserButton" id="start_at_filechooserbutton">
+ <property name="visible">True</property>
+ <property name="action">select-folder</property>
+ <property name="title" translatable="yes"></property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="x_options">GTK_FILL</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="include_subfolders_checkbutton">
+ <property name="label" translatable="yes">_Include sub-folders</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame" id="frame1">
+ <property name="visible">True</property>
+ <property name="label_xalign">0</property>
+ <child>
+ <object class="GtkAlignment" id="alignment1">
+ <property name="visible">True</property>
+ <property name="top_padding">6</property>
+ <property name="bottom_padding">6</property>
+ <property name="left_padding">12</property>
+ <property name="right_padding">6</property>
+ <child>
+ <object class="GtkVBox" id="tests_box">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkHBox" id="hbox1">
+ <property name="visible">True</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="match_label">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Match</property>
+ <property name="use_markup">True</property>
+ </object>
+ <packing>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="match_type_combobox_box">
+ <property name="visible">True</property>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+</interface>
diff --git a/extensions/search/gth-search-editor-dialog.c b/extensions/search/gth-search-editor-dialog.c
new file mode 100644
index 0000000..a668ca4
--- /dev/null
+++ b/extensions/search/gth-search-editor-dialog.c
@@ -0,0 +1,330 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 The Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <stdlib.h>
+#include <gtk/gtk.h>
+#include <gthumb.h>
+#include "gth-search-editor-dialog.h"
+
+
+#define GET_WIDGET(name) _gtk_builder_get_widget (self->priv->builder, (name))
+
+
+static gpointer parent_class = NULL;
+
+
+struct _GthSearchEditorDialogPrivate {
+ GtkBuilder *builder;
+ GtkWidget *match_type_combobox;
+};
+
+
+static void
+gth_search_editor_dialog_finalize (GObject *object)
+{
+ GthSearchEditorDialog *dialog;
+
+ dialog = GTH_SEARCH_EDITOR_DIALOG (object);
+
+ if (dialog->priv != NULL) {
+ g_object_unref (dialog->priv->builder);
+ g_free (dialog->priv);
+ dialog->priv = NULL;
+ }
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gth_search_editor_dialog_class_init (GthSearchEditorDialogClass *class)
+{
+ GObjectClass *object_class;
+
+ parent_class = g_type_class_peek_parent (class);
+ object_class = (GObjectClass*) class;
+
+ object_class->finalize = gth_search_editor_dialog_finalize;
+}
+
+
+static void
+gth_search_editor_dialog_init (GthSearchEditorDialog *dialog)
+{
+ dialog->priv = g_new0 (GthSearchEditorDialogPrivate, 1);
+}
+
+
+GType
+gth_search_editor_dialog_get_type (void)
+{
+ static GType type = 0;
+
+ if (! type) {
+ GTypeInfo type_info = {
+ sizeof (GthSearchEditorDialogClass),
+ NULL,
+ NULL,
+ (GClassInitFunc) gth_search_editor_dialog_class_init,
+ NULL,
+ NULL,
+ sizeof (GthSearchEditorDialog),
+ 0,
+ (GInstanceInitFunc) gth_search_editor_dialog_init
+ };
+
+ type = g_type_register_static (GTK_TYPE_DIALOG,
+ "GthSearchEditorDialog",
+ &type_info,
+ 0);
+ }
+
+ return type;
+}
+
+
+static void
+update_sensitivity (GthSearchEditorDialog *self)
+{
+ GList *test_selectors;
+ int more_selectors;
+ GList *scan;
+
+ test_selectors = gtk_container_get_children (GTK_CONTAINER (GET_WIDGET ("tests_box")));
+ more_selectors = (test_selectors != NULL) && (test_selectors->next != NULL);
+ for (scan = test_selectors; scan; scan = scan->next)
+ gth_test_selector_can_remove (GTH_TEST_SELECTOR (scan->data), more_selectors);
+ g_list_free (test_selectors);
+}
+
+
+static void
+gth_search_editor_dialog_construct (GthSearchEditorDialog *self,
+ const char *title,
+ GthSearch *search,
+ GtkWindow *parent)
+{
+ GtkWidget *content;
+
+ if (title != NULL)
+ gtk_window_set_title (GTK_WINDOW (self), title);
+ if (parent != NULL)
+ gtk_window_set_transient_for (GTK_WINDOW (self), parent);
+ gtk_window_set_resizable (GTK_WINDOW (self), FALSE);
+ gtk_dialog_set_has_separator (GTK_DIALOG (self), FALSE);
+ gtk_box_set_spacing (GTK_BOX (GTK_DIALOG (self)->vbox), 5);
+ gtk_container_set_border_width (GTK_CONTAINER (self), 5);
+
+ self->priv->builder = _gtk_builder_new_from_file ("search-editor.ui", "search");
+
+ content = _gtk_builder_get_widget (self->priv->builder, "search_editor");
+ gtk_container_set_border_width (GTK_CONTAINER (content), 5);
+ gtk_box_pack_start (GTK_BOX (GTK_DIALOG (self)->vbox), content, TRUE, TRUE, 0);
+
+ self->priv->match_type_combobox = gtk_combo_box_new_text ();
+ _gtk_combo_box_append_texts (GTK_COMBO_BOX (self->priv->match_type_combobox),
+ _("all the following rules"),
+ _("any of the following rules"),
+ NULL);
+ gtk_combo_box_set_active (GTK_COMBO_BOX (self->priv->match_type_combobox), 0);
+ gtk_widget_show (self->priv->match_type_combobox);
+ gtk_container_add (GTK_CONTAINER (GET_WIDGET ("match_type_combobox_box")),
+ self->priv->match_type_combobox);
+
+ gtk_label_set_use_underline (GTK_LABEL (GET_WIDGET ("match_label")), TRUE);
+ gtk_label_set_mnemonic_widget (GTK_LABEL (GET_WIDGET ("match_label")), self->priv->match_type_combobox);
+
+ gth_search_editor_dialog_set_search (self, search);
+}
+
+
+GtkWidget *
+gth_search_editor_dialog_new (const char *title,
+ GthSearch *search,
+ GtkWindow *parent)
+{
+ GthSearchEditorDialog *self;
+
+ self = g_object_new (GTH_TYPE_SEARCH_EDITOR_DIALOG, NULL);
+ gth_search_editor_dialog_construct (self, title, search, parent);
+
+ return (GtkWidget *) self;
+}
+
+
+static GtkWidget *
+_gth_search_editor_dialog_add_test (GthSearchEditorDialog *self,
+ int pos);
+
+
+static void
+test_selector_add_test_cb (GthTestSelector *selector,
+ GthSearchEditorDialog *self)
+{
+ int pos;
+
+ pos = _gtk_container_get_pos (GTK_CONTAINER (GET_WIDGET ("tests_box")), (GtkWidget*) selector);
+ _gth_search_editor_dialog_add_test (self, pos == -1 ? -1 : pos + 1);
+ update_sensitivity (self);
+}
+
+
+static void
+test_selector_remove_test_cb (GthTestSelector *selector,
+ GthSearchEditorDialog *self)
+{
+ gtk_container_remove (GTK_CONTAINER (GET_WIDGET ("tests_box")), (GtkWidget*) selector);
+ update_sensitivity (self);
+}
+
+
+static GtkWidget *
+_gth_search_editor_dialog_add_test (GthSearchEditorDialog *self,
+ int pos)
+{
+ GtkWidget *test_selector;
+
+ test_selector = gth_test_selector_new ();
+ gtk_widget_show (test_selector);
+
+ g_signal_connect (G_OBJECT (test_selector),
+ "add_test",
+ G_CALLBACK (test_selector_add_test_cb),
+ self);
+ g_signal_connect (G_OBJECT (test_selector),
+ "remove_test",
+ G_CALLBACK (test_selector_remove_test_cb),
+ self);
+
+ gtk_box_pack_start (GTK_BOX (GET_WIDGET ("tests_box")), test_selector, FALSE, FALSE, 0);
+
+ if (pos >= 0)
+ gtk_box_reorder_child (GTK_BOX (GET_WIDGET ("tests_box")),
+ test_selector,
+ pos);
+
+ return test_selector;
+}
+
+
+static void
+_gth_search_editor_dialog_set_new_search (GthSearchEditorDialog *self)
+{
+ gtk_file_chooser_set_uri (GTK_FILE_CHOOSER (GET_WIDGET ("start_at_filechooserbutton")), get_home_uri ());
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (GET_WIDGET ("include_subfolders_checkbutton")), TRUE);
+ _gtk_container_remove_children (GTK_CONTAINER (GET_WIDGET ("tests_box")), NULL, NULL);
+}
+
+
+void
+gth_search_editor_dialog_set_search (GthSearchEditorDialog *self,
+ GthSearch *search)
+{
+ GthTestChain *test;
+ GthMatchType match_type;
+
+ _gth_search_editor_dialog_set_new_search (self);
+
+ if (search == NULL) {
+ _gth_search_editor_dialog_add_test (self, -1);
+ update_sensitivity (self);
+ return;
+ }
+
+ if (gth_search_get_folder (search) != NULL) {
+ char *uri;
+
+ uri = g_file_get_uri (gth_search_get_folder (search));
+ if (uri != NULL) {
+ gtk_file_chooser_set_uri (GTK_FILE_CHOOSER (GET_WIDGET ("start_at_filechooserbutton")), uri);
+ g_free (uri);
+ }
+ }
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (GET_WIDGET ("include_subfolders_checkbutton")), gth_search_is_recursive (search));
+
+ test = gth_search_get_test (search);
+ match_type = (test != NULL) ? gth_test_chain_get_match_type (test) : GTH_MATCH_TYPE_NONE;
+ _gtk_container_remove_children (GTK_CONTAINER (GET_WIDGET ("tests_box")), NULL, NULL);
+ if (match_type != GTH_MATCH_TYPE_NONE) {
+ GList *tests;
+ GList *scan;
+
+ tests = gth_test_chain_get_tests (test);
+ for (scan = tests; scan; scan = scan->next) {
+ GthTest *test = scan->data;
+ GtkWidget *test_selector;
+
+ test_selector = _gth_search_editor_dialog_add_test (self, -1);
+ gth_test_selector_set_test (GTH_TEST_SELECTOR (test_selector), test);
+ }
+ _g_object_list_unref (tests);
+ }
+ else
+ _gth_search_editor_dialog_add_test (self, -1);
+
+ update_sensitivity (self);
+}
+
+
+GthSearch *
+gth_search_editor_dialog_get_search (GthSearchEditorDialog *self,
+ GError **error)
+{
+ GthSearch *search;
+ char *uri;
+ GthTest *test;
+ GList *test_selectors;
+ GList *scan;
+
+ search = gth_search_new ();
+
+ uri = gtk_file_chooser_get_uri (GTK_FILE_CHOOSER (GET_WIDGET ("start_at_filechooserbutton")));
+ if (uri != NULL) {
+ GFile *folder;
+
+ folder = g_file_new_for_uri (uri);
+ gth_search_set_folder (search, folder);
+ g_object_unref (folder);
+ }
+
+ gth_search_set_recursive (search, gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (GET_WIDGET ("include_subfolders_checkbutton"))));
+
+ test = gth_test_chain_new (gtk_combo_box_get_active (GTK_COMBO_BOX (self->priv->match_type_combobox)) + 1, NULL);
+ test_selectors = gtk_container_get_children (GTK_CONTAINER (GET_WIDGET ("tests_box")));
+ for (scan = test_selectors; scan; scan = scan->next) {
+ GthTestSelector *test_selector = GTH_TEST_SELECTOR (scan->data);
+ GthTest *sub_test;
+
+ sub_test = gth_test_selector_get_test (test_selector, error);
+ if (sub_test == NULL) {
+ g_object_unref (search);
+ return NULL;
+ }
+
+ gth_test_chain_add_test (GTH_TEST_CHAIN (test), sub_test);
+ g_object_unref (sub_test);
+ }
+ g_list_free (test_selectors);
+ gth_search_set_test (search, GTH_TEST_CHAIN (test));
+
+ return search;
+}
diff --git a/extensions/search/gth-search-editor-dialog.h b/extensions/search/gth-search-editor-dialog.h
new file mode 100644
index 0000000..6206115
--- /dev/null
+++ b/extensions/search/gth-search-editor-dialog.h
@@ -0,0 +1,58 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 The Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GTH_SEARCH_EDITOR_DIALOG_H
+#define GTH_SEARCH_EDITOR_DIALOG_H
+
+#include <gtk/gtk.h>
+#include "gth-search.h"
+
+#define GTH_TYPE_SEARCH_EDITOR_DIALOG (gth_search_editor_dialog_get_type ())
+#define GTH_SEARCH_EDITOR_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTH_TYPE_SEARCH_EDITOR_DIALOG, GthSearchEditorDialog))
+#define GTH_SEARCH_EDITOR_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTH_TYPE_SEARCH_EDITOR_DIALOG, GthSearchEditorDialogClass))
+#define GTH_IS_SEARCH_EDITOR_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTH_TYPE_SEARCH_EDITOR_DIALOG))
+#define GTH_IS_SEARCH_EDITOR_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTH_TYPE_SEARCH_EDITOR_DIALOG))
+#define GTH_SEARCH_EDITOR_DIALOG_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTH_TYPE_SEARCH_EDITOR_DIALOG, GthSearchEditorDialogClass))
+
+typedef struct _GthSearchEditorDialog GthSearchEditorDialog;
+typedef struct _GthSearchEditorDialogClass GthSearchEditorDialogClass;
+typedef struct _GthSearchEditorDialogPrivate GthSearchEditorDialogPrivate;
+
+struct _GthSearchEditorDialog {
+ GtkDialog parent_instance;
+ GthSearchEditorDialogPrivate *priv;
+};
+
+struct _GthSearchEditorDialogClass {
+ GtkDialogClass parent_class;
+};
+
+GType gth_search_editor_dialog_get_type (void);
+GtkWidget * gth_search_editor_dialog_new (const char *title,
+ GthSearch *search,
+ GtkWindow *parent);
+void gth_search_editor_dialog_set_search (GthSearchEditorDialog *self,
+ GthSearch *search);
+GthSearch * gth_search_editor_dialog_get_search (GthSearchEditorDialog *self,
+ GError **error);
+
+#endif /* GTH_SEARCH_EDITOR_DIALOG_H */
diff --git a/extensions/search/gth-search-task.c b/extensions/search/gth-search-task.c
new file mode 100644
index 0000000..e7544bf
--- /dev/null
+++ b/extensions/search/gth-search-task.c
@@ -0,0 +1,436 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <gthumb.h>
+#include <extensions/catalogs/gth-catalog.h>
+#include "gth-search-task.h"
+
+
+struct _GthSearchTaskPrivate
+{
+ GthBrowser *browser;
+ GthSearch *search;
+ GthTestChain *test;
+ GFile *search_catalog;
+ GCancellable *cancellable;
+ gboolean io_operation;
+ GError *error;
+ gulong location_ready_id;
+ GtkWidget *dialog;
+};
+
+
+static gpointer parent_class = NULL;
+
+
+static void
+browser_unref_cb (gpointer data,
+ GObject *browser)
+{
+ ((GthSearchTask *) data)->priv->browser = NULL;
+}
+
+
+static void
+gth_task_finalize (GObject *object)
+{
+ GthSearchTask *task;
+
+ task = GTH_SEARCH_TASK (object);
+
+ if (task->priv != NULL) {
+ g_object_unref (task->priv->cancellable);
+ g_object_unref (task->priv->search);
+ g_object_unref (task->priv->test);
+ g_object_unref (task->priv->search_catalog);
+ if (task->priv->browser != NULL)
+ g_object_weak_unref (G_OBJECT (task->priv->browser), browser_unref_cb, task);
+ g_free (task->priv);
+ task->priv = NULL;
+ }
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+
+typedef struct {
+ GthBrowser *browser;
+ GthSearchTask *task;
+} EmbeddedDialogData;
+
+
+static void
+embedded_dialog_destroy_cb (GeditMessageArea *message_area,
+ gpointer user_data)
+{
+ g_free ((EmbeddedDialogData *) user_data);
+}
+
+
+static void
+embedded_dialog_response_cb (GeditMessageArea *message_area,
+ int response_id,
+ gpointer user_data)
+{
+ EmbeddedDialogData *data = user_data;
+
+ switch (response_id) {
+ case GTK_RESPONSE_CLOSE:
+ gth_browser_set_list_extra_widget (data->browser, NULL);
+ break;
+
+ case GTK_RESPONSE_CANCEL:
+ gth_task_cancel (GTH_TASK (data->task));
+ break;
+
+ default:
+ break;
+ }
+}
+
+
+static void
+save_search_result_copy_done_cb (void *buffer,
+ gsize count,
+ GError *error,
+ gpointer user_data)
+{
+ GthSearchTask *task = user_data;
+ GList *result;
+ int n;
+ char *text;
+
+ g_free (buffer);
+
+ gth_embedded_dialog_set_primary_text (GTH_EMBEDDED_DIALOG (task->priv->dialog), _("Search completed"));
+
+ result = gth_catalog_get_file_list (GTH_CATALOG (task->priv->search));
+ n = g_list_length (result);
+ text = g_strdup_printf (ngettext("%d file found.", "%d files found.", n), n);
+ gth_embedded_dialog_set_secondary_text (GTH_EMBEDDED_DIALOG (task->priv->dialog), text);
+
+ g_free (text);
+
+ gedit_message_area_clear_action_area (GEDIT_MESSAGE_AREA (task->priv->dialog));
+ gedit_message_area_add_stock_button_with_text (GEDIT_MESSAGE_AREA (task->priv->dialog),
+ NULL,
+ GTK_STOCK_CLOSE,
+ GTK_RESPONSE_CLOSE);
+
+ task->priv->io_operation = FALSE;
+ gth_task_completed (GTH_TASK (task), task->priv->error);
+}
+
+
+static void
+done_func (GError *error,
+ gpointer user_data)
+{
+ GthSearchTask *task = user_data;
+ DomDocument *doc;
+ char *data;
+ gsize size;
+ GFile *search_result_real_file;
+
+ task->priv->error = NULL;
+ if (error != NULL) {
+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+ task->priv->error = g_error_new_literal (GTH_TASK_ERROR, GTH_TASK_ERROR_CANCELLED, "");
+ g_error_free (error);
+ }
+ else
+ task->priv->error = error;
+ }
+
+ /* save the search result */
+
+ doc = dom_document_new ();
+ dom_element_append_child (DOM_ELEMENT (doc), dom_domizable_create_element (DOM_DOMIZABLE (task->priv->search), doc));
+ data = dom_document_dump (doc, &size);
+
+ search_result_real_file = gth_catalog_file_to_gio_file (task->priv->search_catalog);
+ g_write_file_async (search_result_real_file,
+ data,
+ size,
+ G_PRIORITY_DEFAULT,
+ task->priv->cancellable,
+ save_search_result_copy_done_cb,
+ task);
+
+ g_object_unref (search_result_real_file);
+ g_object_unref (doc);
+}
+
+
+static void
+for_each_file_func (GFile *file,
+ GFileInfo *info,
+ gpointer user_data)
+{
+ GthSearchTask *task = user_data;
+ GthFileData *file_data;
+
+ if (g_file_info_get_file_type (info) != G_FILE_TYPE_REGULAR)
+ return;
+
+ file_data = gth_file_data_new (file, info);
+
+ if (gth_test_match (GTH_TEST (task->priv->test), file_data)) {
+ GList *list;
+
+ gth_catalog_insert_file (GTH_CATALOG (task->priv->search), -1, file_data->file);
+
+ list = g_list_prepend (NULL, g_object_ref (file_data->file));
+ gth_monitor_folder_changed (gth_main_get_default_monitor (),
+ task->priv->search_catalog,
+ list,
+ GTH_MONITOR_EVENT_CREATED);
+ _g_object_list_unref (list);
+ }
+
+ g_object_unref (file_data);
+}
+
+
+static DirOp
+start_dir_func (GFile *directory,
+ GFileInfo *info,
+ GError **error,
+ gpointer user_data)
+{
+ GthSearchTask *task = user_data;
+ char *uri;
+ char *text;
+
+ uri = g_file_get_uri (directory);
+ text = g_strdup_printf ("Searching in %s", uri);
+ gth_embedded_dialog_set_secondary_text (GTH_EMBEDDED_DIALOG (task->priv->dialog), text);
+
+ g_free (text);
+ g_free (uri);
+
+ return DIR_OP_CONTINUE;
+}
+
+
+static void
+browser_location_ready_cb (GthBrowser *browser,
+ GFile *folder,
+ gboolean error,
+ GthSearchTask *task)
+{
+ GtkWidget *alignment;
+ EmbeddedDialogData *dialog_data;
+
+ g_signal_handler_disconnect (task->priv->browser, task->priv->location_ready_id);
+
+ if (error) {
+ gth_task_completed (GTH_TASK (task), NULL);
+ return;
+ }
+
+ alignment = gtk_alignment_new (0, 0, 1.0, 1.0);
+ gtk_alignment_set_padding (GTK_ALIGNMENT (alignment), 3, 3, 0, 0);
+ gtk_widget_show (alignment);
+
+ task->priv->dialog = gth_embedded_dialog_new (GTK_STOCK_FIND, _("Searching..."), NULL);
+ gedit_message_area_add_stock_button_with_text (GEDIT_MESSAGE_AREA (task->priv->dialog),
+ NULL,
+ GTK_STOCK_CANCEL,
+ GTK_RESPONSE_CANCEL);
+ gtk_widget_show (task->priv->dialog);
+ gtk_container_add (GTK_CONTAINER (alignment), task->priv->dialog);
+
+ dialog_data = g_new0 (EmbeddedDialogData, 1);
+ dialog_data->browser = task->priv->browser;
+ dialog_data->task = task;
+ g_signal_connect (task->priv->dialog,
+ "destroy",
+ G_CALLBACK (embedded_dialog_destroy_cb),
+ dialog_data);
+ g_signal_connect (task->priv->dialog,
+ "response",
+ G_CALLBACK (embedded_dialog_response_cb),
+ dialog_data);
+
+ gth_browser_set_list_extra_widget (task->priv->browser, alignment);
+
+ /**/
+
+ if (gth_search_get_test (task->priv->search) != NULL)
+ task->priv->test = (GthTestChain*) gth_duplicable_duplicate (GTH_DUPLICABLE (gth_search_get_test (task->priv->search)));
+ else
+ task->priv->test = (GthTestChain*) gth_test_chain_new (GTH_MATCH_TYPE_ALL, NULL);
+
+ if (! gth_test_chain_has_type_test (task->priv->test)) {
+ GthTest *general_filter;
+
+ general_filter = gth_main_get_general_filter ();
+ gth_test_chain_add_test (task->priv->test, general_filter);
+
+ g_object_unref (general_filter);
+ }
+
+ task->priv->io_operation = TRUE;
+ g_directory_foreach_child (gth_search_get_folder (task->priv->search),
+ gth_search_is_recursive (task->priv->search),
+ TRUE,
+ eel_gconf_get_boolean (PREF_FAST_FILE_TYPE, TRUE) ? GTH_FILE_DATA_ATTRIBUTES_WITH_FAST_CONTENT_TYPE : GTH_FILE_DATA_ATTRIBUTES_WITH_CONTENT_TYPE,
+ task->priv->cancellable,
+ start_dir_func,
+ for_each_file_func,
+ done_func,
+ task);
+}
+
+
+static void
+clear_search_result_copy_done_cb (void *buffer,
+ gsize count,
+ GError *error,
+ gpointer user_data)
+{
+ GthSearchTask *task = user_data;
+
+ task->priv->location_ready_id = g_signal_connect (task->priv->browser,
+ "location-ready",
+ G_CALLBACK (browser_location_ready_cb),
+ task);
+ gth_browser_go_to (task->priv->browser, task->priv->search_catalog);
+}
+
+
+static void
+gth_search_task_exec (GthTask *base)
+{
+ GthSearchTask *task = (GthSearchTask *) base;
+ DomDocument *doc;
+ char *data;
+ gsize size;
+ GFile *search_result_real_file;
+
+ gth_catalog_set_file_list (GTH_CATALOG (task->priv->search), NULL);
+ task->priv->io_operation = FALSE;
+
+ /* save the search result */
+
+ doc = dom_document_new ();
+ dom_element_append_child (DOM_ELEMENT (doc), dom_domizable_create_element (DOM_DOMIZABLE (task->priv->search), doc));
+ data = dom_document_dump (doc, &size);
+
+ search_result_real_file = gth_catalog_file_to_gio_file (task->priv->search_catalog);
+ g_write_file_async (search_result_real_file,
+ data,
+ size,
+ G_PRIORITY_DEFAULT,
+ task->priv->cancellable,
+ clear_search_result_copy_done_cb,
+ task);
+
+ g_object_unref (search_result_real_file);
+ g_object_unref (doc);
+}
+
+
+static void
+gth_search_task_cancel (GthTask *task)
+{
+ if (GTH_SEARCH_TASK (task)->priv->io_operation)
+ g_cancellable_cancel (GTH_SEARCH_TASK (task)->priv->cancellable);
+ else
+ gth_task_completed (task, g_error_new_literal (GTH_TASK_ERROR, GTH_TASK_ERROR_CANCELLED, NULL));
+}
+
+
+static void
+gth_search_task_class_init (GthSearchTaskClass *class)
+{
+ GObjectClass *object_class;
+ GthTaskClass *task_class;
+
+ parent_class = g_type_class_peek_parent (class);
+
+ object_class = (GObjectClass*) class;
+ object_class->finalize = gth_task_finalize;
+
+ task_class = (GthTaskClass*) class;
+ task_class->exec = gth_search_task_exec;
+ task_class->cancel = gth_search_task_cancel;
+}
+
+
+static void
+gth_search_task_init (GthSearchTask *task)
+{
+ task->priv = g_new0 (GthSearchTaskPrivate, 1);
+ task->priv->cancellable = g_cancellable_new ();
+}
+
+
+GType
+gth_search_task_get_type (void)
+{
+ static GType type = 0;
+
+ if (! type) {
+ GTypeInfo type_info = {
+ sizeof (GthSearchTaskClass),
+ NULL,
+ NULL,
+ (GClassInitFunc) gth_search_task_class_init,
+ NULL,
+ NULL,
+ sizeof (GthSearchTask),
+ 0,
+ (GInstanceInitFunc) gth_search_task_init
+ };
+
+ type = g_type_register_static (GTH_TYPE_TASK,
+ "GthSearchTask",
+ &type_info,
+ 0);
+ }
+
+ return type;
+}
+
+
+GthTask *
+gth_search_task_new (GthBrowser *browser,
+ GthSearch *search,
+ GFile *search_catalog)
+{
+ GthSearchTask *task;
+
+ task = (GthSearchTask *) g_object_new (GTH_TYPE_SEARCH_TASK, NULL);
+
+ task->priv->browser = browser;
+ g_object_weak_ref (G_OBJECT (task->priv->browser), browser_unref_cb, task);
+
+ task->priv->search = g_object_ref (search);
+ task->priv->search_catalog = g_object_ref (search_catalog);
+
+ return (GthTask*) task;
+}
diff --git a/extensions/search/gth-search-task.h b/extensions/search/gth-search-task.h
new file mode 100644
index 0000000..7b0054a
--- /dev/null
+++ b/extensions/search/gth-search-task.h
@@ -0,0 +1,57 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GTH_SEARCH_TASK_H
+#define GTH_SEARCH_TASK_H
+
+#include <glib-object.h>
+#include <gthumb.h>
+#include "gth-search.h"
+
+#define GTH_TYPE_SEARCH_TASK (gth_search_task_get_type ())
+#define GTH_SEARCH_TASK(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GTH_TYPE_SEARCH_TASK, GthSearchTask))
+#define GTH_SEARCH_TASK_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), GTH_TYPE_SEARCH_TASK, GthSearchTaskClass))
+#define GTH_IS_SEARCH_TASK(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GTH_TYPE_SEARCH_TASK))
+#define GTH_IS_SEARCH_TASK_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GTH_TYPE_SEARCH_TASK))
+#define GTH_SEARCH_TASK_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS((o), GTH_TYPE_SEARCH_TASK, GthSearchTaskClass))
+
+typedef struct _GthSearchTask GthSearchTask;
+typedef struct _GthSearchTaskPrivate GthSearchTaskPrivate;
+typedef struct _GthSearchTaskClass GthSearchTaskClass;
+
+struct _GthSearchTask
+{
+ GthTask __parent;
+ GthSearchTaskPrivate *priv;
+};
+
+struct _GthSearchTaskClass
+{
+ GthTaskClass __parent_class;
+};
+
+GType gth_search_task_get_type (void) G_GNUC_CONST;
+GthTask * gth_search_task_new (GthBrowser *browser,
+ GthSearch *search,
+ GFile *search_catalog);
+
+#endif /* GTH_SEARCH_TASK_H */
diff --git a/extensions/search/gth-search.c b/extensions/search/gth-search.c
new file mode 100644
index 0000000..789ec26
--- /dev/null
+++ b/extensions/search/gth-search.c
@@ -0,0 +1,413 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <glib/gi18n.h>
+#include <gthumb.h>
+#include "gth-search.h"
+
+
+#define SEARCH_FORMAT "1.0"
+
+
+struct _GthSearchPrivate {
+ GFile *folder;
+ gboolean recursive;
+ GthTestChain *test;
+};
+
+
+static gpointer *parent_class = NULL;
+static DomDomizableIface *dom_domizable_parent_iface = NULL;
+static GthDuplicableIface *gth_duplicable_parent_iface = NULL;
+
+
+static DomElement*
+gth_search_real_create_element (DomDomizable *base,
+ DomDocument *doc)
+{
+ GthSearch *self;
+ DomElement *element;
+ char *uri;
+ GList *file_list;
+
+ g_return_val_if_fail (DOM_IS_DOCUMENT (doc), NULL);
+
+ self = GTH_SEARCH (base);
+
+ element = dom_document_create_element (doc, "search",
+ "version", SEARCH_FORMAT,
+ NULL);
+
+ uri = g_file_get_uri (self->priv->folder);
+ dom_element_append_child (element,
+ dom_document_create_element (doc, "folder",
+ "uri", uri,
+ "recursive", (self->priv->recursive ? "true" : "false"),
+ NULL));
+ g_free (uri);
+
+ dom_element_append_child (element, dom_domizable_create_element (DOM_DOMIZABLE (self->priv->test), doc));
+
+ file_list = gth_catalog_get_file_list (GTH_CATALOG (self));
+ if (file_list != NULL) {
+ DomElement *e_file_list;
+ GList *scan;
+
+ e_file_list = dom_document_create_element (doc, "files", NULL);
+ dom_element_append_child (element, e_file_list);
+ for (scan = file_list; scan; scan = scan->next) {
+ GFile *file = scan->data;
+ char *uri;
+
+ uri = g_file_get_uri (file);
+ dom_element_append_child (e_file_list, dom_document_create_element (doc, "file", "uri", uri, NULL));
+
+ g_free (uri);
+ }
+ }
+
+ return element;
+}
+
+
+static void
+gth_search_real_load_from_element (DomDomizable *base,
+ DomElement *element)
+{
+ GthSearch *self;
+ DomElement *node;
+ GFile *folder;
+ GList *files = NULL;
+
+ g_return_if_fail (DOM_IS_ELEMENT (element));
+
+ self = GTH_SEARCH (base);
+
+ gth_search_set_test (self, NULL);
+
+ for (node = element->first_child; node; node = node->next_sibling) {
+ if (g_strcmp0 (node->tag_name, "folder") == 0) {
+ folder = g_file_new_for_uri (dom_element_get_attribute (node, "uri"));
+ gth_search_set_folder (self, folder);
+ g_object_unref (folder);
+
+ gth_search_set_recursive (self, (g_strcmp0 (dom_element_get_attribute (node, "recursive"), "true") == 0));
+ }
+ else if (g_strcmp0 (node->tag_name, "tests") == 0) {
+ GthTest *test;
+
+ test = gth_test_chain_new (GTH_MATCH_TYPE_NONE, NULL);
+ dom_domizable_load_from_element (DOM_DOMIZABLE (test), node);
+ gth_search_set_test (self, GTH_TEST_CHAIN (test));
+ }
+ else if (g_strcmp0 (node->tag_name, "files") == 0) {
+ DomElement *file;
+
+ for (file = node->first_child; file; file = file->next_sibling) {
+ const char *uri;
+
+ uri = dom_element_get_attribute (file, "uri");
+ if (uri != NULL)
+ files = g_list_prepend (files, g_file_new_for_uri (uri));
+ }
+ }
+ }
+ gth_catalog_set_file_list (GTH_CATALOG (self), files);
+
+ _g_object_list_unref (files);
+}
+
+
+GObject *
+gth_search_real_duplicate (GthDuplicable *duplicable)
+{
+ GthSearch *search = GTH_SEARCH (duplicable);
+ GthSearch *new_search;
+ GList *file_list;
+ GList *new_file_list = NULL;
+ GList *scan;
+
+ new_search = gth_search_new ();
+
+ gth_search_set_folder (new_search, gth_search_get_folder (search));
+ gth_search_set_recursive (new_search, gth_search_is_recursive (search));
+
+ if (search->priv->test != NULL)
+ new_search->priv->test = (GthTestChain*) gth_duplicable_duplicate (GTH_DUPLICABLE (search->priv->test));
+
+ file_list = gth_catalog_get_file_list (GTH_CATALOG (search));
+ for (scan = file_list; scan; scan = scan->next) {
+ GFile *file = scan->data;
+ new_file_list = g_list_prepend (new_file_list, g_file_dup (file));
+ }
+ gth_catalog_set_file_list (GTH_CATALOG (new_search), new_file_list);
+
+ _g_object_list_unref (new_file_list);
+
+ return (GObject *) new_search;
+}
+
+
+static void
+gth_search_finalize (GObject *object)
+{
+ GthSearch *search;
+
+ search = GTH_SEARCH (object);
+
+ if (search->priv != NULL) {
+ if (search->priv->folder != NULL)
+ g_object_unref (search->priv->folder);
+ if (search->priv->test != NULL)
+ g_object_unref (search->priv->test);
+ g_free (search->priv);
+ search->priv = NULL;
+ }
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+
+static void
+gth_search_load_from_data (GthCatalog *catalog,
+ const void *buffer,
+ gsize count,
+ GError **error)
+{
+ GthSearch *search = GTH_SEARCH (catalog);
+ DomDocument *doc;
+ DomElement *root;
+
+
+ doc = dom_document_new ();
+ if (! dom_document_load (doc, (const char *) buffer, count, error))
+ return;
+
+ root = DOM_ELEMENT (doc)->first_child;
+ if (g_strcmp0 (root->tag_name, "search") != 0) {
+ *error = g_error_new_literal (DOM_ERROR, DOM_ERROR_INVALID_FORMAT, _("Invalid file format"));
+ return;
+ }
+
+ dom_domizable_load_from_element (DOM_DOMIZABLE (search), root);
+
+ g_object_unref (doc);
+}
+
+
+static char *
+gth_search_to_data (GthCatalog *catalog,
+ gsize *length)
+{
+ GthSearch *search = GTH_SEARCH (catalog);
+ DomDocument *doc;
+ DomElement *root;
+ char *data;
+
+ doc = dom_document_new ();
+ root = dom_domizable_create_element (DOM_DOMIZABLE (search), doc);
+ dom_element_append_child (DOM_ELEMENT (doc), root);
+
+ data = dom_document_dump (doc, length);
+
+ g_object_unref (doc);
+
+ return data;
+}
+
+
+static void
+gth_search_class_init (GthSearchClass *class)
+{
+ GObjectClass *object_class;
+ GthCatalogClass *catalog_class;
+
+ parent_class = g_type_class_peek_parent (class);
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->finalize = gth_search_finalize;
+
+ catalog_class = GTH_CATALOG_CLASS (class);
+ catalog_class->load_from_data = gth_search_load_from_data;
+ catalog_class->to_data = gth_search_to_data;
+}
+
+
+static void
+gth_search_dom_domizable_interface_init (DomDomizableIface *iface)
+{
+ dom_domizable_parent_iface = g_type_interface_peek_parent (iface);
+ iface->create_element = gth_search_real_create_element;
+ iface->load_from_element = gth_search_real_load_from_element;
+}
+
+
+static void
+gth_search_gth_duplicable_interface_init (GthDuplicableIface *iface)
+{
+ gth_duplicable_parent_iface = g_type_interface_peek_parent (iface);
+ iface->duplicate = gth_search_real_duplicate;
+}
+
+
+static void
+gth_search_init (GthSearch *search)
+{
+ search->priv = g_new0 (GthSearchPrivate, 1);
+}
+
+
+GType
+gth_search_get_type (void)
+{
+ static GType type = 0;
+
+ if (! type) {
+ GTypeInfo type_info = {
+ sizeof (GthSearchClass),
+ NULL,
+ NULL,
+ (GClassInitFunc) gth_search_class_init,
+ NULL,
+ NULL,
+ sizeof (GthSearch),
+ 0,
+ (GInstanceInitFunc) gth_search_init
+ };
+ static const GInterfaceInfo dom_domizable_info = {
+ (GInterfaceInitFunc) gth_search_dom_domizable_interface_init,
+ (GInterfaceFinalizeFunc) NULL,
+ NULL
+ };
+ static const GInterfaceInfo gth_duplicable_info = {
+ (GInterfaceInitFunc) gth_search_gth_duplicable_interface_init,
+ (GInterfaceFinalizeFunc) NULL,
+ NULL
+ };
+
+ type = g_type_register_static (GTH_TYPE_CATALOG,
+ "GthSearch",
+ &type_info,
+ 0);
+ g_type_add_interface_static (type, DOM_TYPE_DOMIZABLE, &dom_domizable_info);
+ g_type_add_interface_static (type, GTH_TYPE_DUPLICABLE, >h_duplicable_info);
+ }
+
+ return type;
+}
+
+
+GthSearch *
+gth_search_new (void)
+{
+ return (GthSearch *) g_object_new (GTH_TYPE_SEARCH, NULL);
+}
+
+
+GthSearch*
+gth_search_new_from_data (void *buffer,
+ gsize count,
+ GError **error)
+{
+ DomDocument *doc;
+ DomElement *root;
+ GthSearch *search;
+
+ doc = dom_document_new ();
+ if (! dom_document_load (doc, (const char *) buffer, count, error))
+ return NULL;
+
+ root = DOM_ELEMENT (doc)->first_child;
+ if (g_strcmp0 (root->tag_name, "search") != 0) {
+ *error = g_error_new_literal (DOM_ERROR, DOM_ERROR_INVALID_FORMAT, _("Invalid file format"));
+ return NULL;
+ }
+
+ search = gth_search_new ();
+ dom_domizable_load_from_element (DOM_DOMIZABLE (search), root);
+
+ g_object_unref (doc);
+
+ return search;
+}
+
+
+void
+gth_search_set_folder (GthSearch *search,
+ GFile *folder)
+{
+ if (search->priv->folder != NULL) {
+ g_object_unref (search->priv->folder);
+ search->priv->folder = NULL;
+ }
+
+ if (folder != NULL)
+ search->priv->folder = g_object_ref (folder);
+}
+
+
+GFile *
+gth_search_get_folder (GthSearch *search)
+{
+ return search->priv->folder;
+}
+
+
+void
+gth_search_set_recursive (GthSearch *search,
+ gboolean recursive)
+{
+ search->priv->recursive = recursive;
+}
+
+
+gboolean
+gth_search_is_recursive (GthSearch *search)
+{
+ return search->priv->recursive;
+}
+
+
+void
+gth_search_set_test (GthSearch *search,
+ GthTestChain *test)
+{
+ if (test == search->priv->test)
+ return;
+ if (search->priv->test != NULL) {
+ g_object_unref (search->priv->test);
+ search->priv->test = NULL;
+ }
+ if (test != NULL)
+ search->priv->test = g_object_ref (test);
+}
+
+
+GthTestChain *
+gth_search_get_test (GthSearch *search)
+{
+ if (search->priv->test != NULL)
+ return search->priv->test;
+ else
+ return NULL;
+}
diff --git a/extensions/search/gth-search.h b/extensions/search/gth-search.h
new file mode 100644
index 0000000..5ce37c8
--- /dev/null
+++ b/extensions/search/gth-search.h
@@ -0,0 +1,68 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GTH_SEARCH_H
+#define GTH_SEARCH_H
+
+#include <glib-object.h>
+#include <gio/gio.h>
+#include <gthumb.h>
+#include <extensions/catalogs/gth-catalog.h>
+
+#define GTH_TYPE_SEARCH (gth_search_get_type ())
+#define GTH_SEARCH(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GTH_TYPE_SEARCH, GthSearch))
+#define GTH_SEARCH_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), GTH_TYPE_SEARCH, GthSearchClass))
+#define GTH_IS_SEARCH(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GTH_TYPE_SEARCH))
+#define GTH_IS_SEARCH_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GTH_TYPE_SEARCH))
+#define GTH_SEARCH_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS((o), GTH_TYPE_SEARCH, GthSearchClass))
+
+typedef struct _GthSearch GthSearch;
+typedef struct _GthSearchPrivate GthSearchPrivate;
+typedef struct _GthSearchClass GthSearchClass;
+
+struct _GthSearch
+{
+ GthCatalog __parent;
+ GthSearchPrivate *priv;
+};
+
+struct _GthSearchClass
+{
+ GthCatalogClass __parent_class;
+};
+
+GType gth_search_get_type (void) G_GNUC_CONST;
+GthSearch * gth_search_new (void);
+GthSearch * gth_search_new_from_data (void *buffer,
+ gsize count,
+ GError **error);
+void gth_search_set_folder (GthSearch *search,
+ GFile *folder);
+GFile * gth_search_get_folder (GthSearch *search);
+void gth_search_set_recursive (GthSearch *search,
+ gboolean recursive);
+gboolean gth_search_is_recursive (GthSearch *search);
+void gth_search_set_test (GthSearch *search,
+ GthTestChain *test);
+GthTestChain * gth_search_get_test (GthSearch *search);
+
+#endif /* GTH_SEARCH_H */
diff --git a/extensions/search/main.c b/extensions/search/main.c
new file mode 100644
index 0000000..f315b48
--- /dev/null
+++ b/extensions/search/main.c
@@ -0,0 +1,55 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2008 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+
+#include <config.h>
+#include <gtk/gtk.h>
+#include <gthumb.h>
+#include "callbacks.h"
+
+
+G_MODULE_EXPORT void
+gthumb_extension_activate (void)
+{
+ gth_hook_add_callback ("gth-catalog-load-from-data", 10, G_CALLBACK (search__gth_catalog_load_from_data_cb), NULL);
+ gth_hook_add_callback ("gth-browser-construct", 10, G_CALLBACK (search__gth_browser_construct_cb), NULL);
+ gth_hook_add_callback ("gth-browser-load-location-after", 10, G_CALLBACK (search__gth_browser_load_location_after_cb), NULL);
+}
+
+
+G_MODULE_EXPORT void
+gthumb_extension_deactivate (void)
+{
+}
+
+
+G_MODULE_EXPORT gboolean
+gthumb_extension_is_configurable (void)
+{
+ return FALSE;
+}
+
+
+G_MODULE_EXPORT void
+gthumb_extension_configure (GtkWindow *parent)
+{
+}
diff --git a/extensions/search/search.extension.in.in b/extensions/search/search.extension.in.in
new file mode 100644
index 0000000..adc9afc
--- /dev/null
+++ b/extensions/search/search.extension.in.in
@@ -0,0 +1,11 @@
+[Extension]
+_Name=Search
+_Description=Allows to search for files.
+Authors=Paolo Bacchilega <paobac src gnome org>
+Copyright=Copyright © 2008 Paolo Bacchilega
+Version=1
+Requires=catalogs
+
+[Loader]
+Type=module
+File=%LIBRARY%
diff --git a/git.mk b/git.mk
index 739ba92..01bf288 100644
--- a/git.mk
+++ b/git.mk
@@ -83,96 +83,100 @@ git-mk-install:
$(srcdir)/.gitignore: Makefile.am $(top_srcdir)/git.mk
@echo Generating $@; \
- GTKDOCGITIGNOREFILES=; \
- test "x$(DOC_MODULE)" = x -o "x$(DOC_MAIN_SGML_FILE)" = x || \
- GTKDOCGITIGNOREFILES=" \
- $(DOC_MODULE)-decl-list.txt \
- $(DOC_MODULE)-decl.txt \
- tmpl/$(DOC_MODULE)-unused.sgml \
- tmpl/*.bak \
- xml html \
- "; \
- GNOMEDOCUTILSGITIGNOREFILES=; \
- test "x$(DOC_MODULE)" = x -o "x$(DOC_LINGUAS)" = x || \
- GNOMEDOCUTILSGITIGNOREFILES=" \
- $(_DOC_C_DOCS) \
- $(_DOC_LC_DOCS) \
- $(_DOC_OMF_ALL) \
- $(_DOC_DSK_ALL) \
- $(_DOC_HTML_ALL) \
- $(_DOC_POFILES) \
- */.xml2po.mo \
- */*.omf.out \
- "; \
- INTLTOOLGITIGNOREFILES=; test -f $(srcdir)/po/Makefile.in.in && \
- INTLTOOLGITIGNOREFILES=" \
- po/Makefile.in.in \
- po/Makefile.in \
- po/Makefile \
- po/*.gmo \
- po/*.mo \
- po/POTFILES \
- po/stamp-it \
- po/.intltool-merge-cache \
- intltool-extract.in \
- intltool-merge.in \
- intltool-update.in \
- "; \
- AUTOCONFGITIGNOREFILES=; test -f $(srcdir)/configure && \
- AUTOCONFGITIGNOREFILES=" \
- autom4te.cache \
- configure \
- config.h \
- stamp-h1 \
- libtool \
- config.lt \
- "; \
- for x in \
- .gitignore \
- $$GTKDOCGITIGNOREFILES \
- $$GNOMEDOCUTILSGITIGNOREFILES \
- $$INTLTOOLGITIGNOREFILES \
- $$AUTOCONFGITIGNOREFILES \
- $(GITIGNOREFILES) \
- $(CLEANFILES) \
- $(PROGRAMS) \
- $(EXTRA_PROGRAMS) \
- $(LTLIBRARIES) \
- so_locations \
- .libs _libs \
- $(MOSTLYCLEANFILES) \
- "*.$(OBJEXT)" \
- "*.lo" \
- $(DISTCLEANFILES) \
- $(am__CONFIG_DISTCLEAN_FILES) \
- $(CONFIG_CLEAN_FILES) \
- TAGS ID GTAGS GRTAGS GSYMS GPATH tags \
- "*.tab.c" \
- $(MAINTAINERCLEANFILES) \
- $(BUILT_SOURCES) \
- $(DEPDIR) \
- Makefile \
- Makefile.in \
- "*.orig" \
- "*.rej" \
- "*.bak" \
- "*~" \
- ".*.sw[nop]" \
- ; do echo /$$x; done | \
+ { \
+ if test "x$(DOC_MODULE)" = x -o "x$(DOC_MAIN_SGML_FILE)" = x; then :; else \
+ for x in \
+ $(DOC_MODULE)-decl-list.txt \
+ $(DOC_MODULE)-decl.txt \
+ tmpl/$(DOC_MODULE)-unused.sgml \
+ "tmpl/*.bak" \
+ xml html \
+ ; do echo /$$x; done; \
+ fi; \
+ if test "x$(DOC_MODULE)" = x -o "x$(DOC_LINGUAS)" = x; then :; else \
+ for x in \
+ $(_DOC_C_DOCS) \
+ $(_DOC_LC_DOCS) \
+ $(_DOC_OMF_ALL) \
+ $(_DOC_DSK_ALL) \
+ $(_DOC_HTML_ALL) \
+ $(_DOC_POFILES) \
+ "*/.xml2po.mo" \
+ "*/*.omf.out" \
+ ; do echo /$$x; done; \
+ fi; \
+ if test -f $(srcdir)/po/Makefile.in.in; then \
+ for x in \
+ po/Makefile.in.in \
+ po/Makefile.in \
+ po/Makefile \
+ po/POTFILES \
+ po/stamp-it \
+ po/.intltool-merge-cache \
+ "po/*.gmo" \
+ "po/*.mo" \
+ intltool-extract.in \
+ intltool-merge.in \
+ intltool-update.in \
+ ; do echo /$$x; done; \
+ fi; \
+ if test -f $(srcdir)/configure; then \
+ for x in \
+ autom4te.cache \
+ configure \
+ config.h \
+ stamp-h1 \
+ libtool \
+ config.lt \
+ ; do echo /$$x; done; \
+ fi; \
+ for x in \
+ .gitignore \
+ $(GITIGNOREFILES) \
+ $(CLEANFILES) \
+ $(PROGRAMS) \
+ $(EXTRA_PROGRAMS) \
+ $(LTLIBRARIES) \
+ so_locations \
+ .libs _libs \
+ $(MOSTLYCLEANFILES) \
+ "*.$(OBJEXT)" \
+ "*.lo" \
+ $(DISTCLEANFILES) \
+ $(am__CONFIG_DISTCLEAN_FILES) \
+ $(CONFIG_CLEAN_FILES) \
+ TAGS ID GTAGS GRTAGS GSYMS GPATH tags \
+ "*.tab.c" \
+ $(MAINTAINERCLEANFILES) \
+ $(BUILT_SOURCES) \
+ $(DEPDIR) \
+ Makefile \
+ Makefile.in \
+ "*.orig" \
+ "*.rej" \
+ "*.bak" \
+ "*~" \
+ ".*.sw[nop]" \
+ ; do echo /$$x; done; \
+ } | \
sed "s ^/`echo "$(srcdir)" | sed 's/\(.\)/[\1]/g'`/@/@" | \
sed 's@/[.]/@/@g' | \
LANG=C sort | uniq > $ tmp && \
mv $ tmp $@;
-all: $(srcdir)/.gitignore gitignore-recurse
-gitignore-recurse:
+all: $(srcdir)/.gitignore gitignore-recurse-maybe
+gitignore-recurse-maybe:
@if test "x$(SUBDIRS)" = "x$(DIST_SUBDIRS)"; then :; else \
- list='$(DIST_SUBDIRS)'; for subdir in $$list; do \
- test "$$subdir" = . || (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) .gitignore); \
- done; \
+ $(MAKE) $(AM_MAKEFLAGS) gitignore-recurse; \
fi;
+gitignore-recurse:
+ @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \
+ test "$$subdir" = . || (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) .gitignore gitignore-recurse || echo "Skipping $$subdir"); \
+ done
+gitignore: $(srcdir)/.gitignore gitignore-recurse
+
maintainer-clean: gitignore-clean
gitignore-clean:
-rm -f $(srcdir)/.gitignore
-.PHONY: gitignore-clean gitignore-recurse
+.PHONY: gitignore-clean gitignore gitignore-recurse gitignore-recurse-maybe
diff --git a/gthumb.doap b/gthumb.doap
index f428ae6..a5dd66c 100644
--- a/gthumb.doap
+++ b/gthumb.doap
@@ -4,8 +4,8 @@
xmlns:gnome="http://api.gnome.org/doap-extensions#"
xmlns="http://usefulinc.com/ns/doap#">
- <name xml:lang="en">gThumb</name>
- <shortdesc xml:lang="en">gThumb Image Viewer</shortdesc>
+ <name xml:lang="en">gthumb</name>
+ <shortdesc xml:lang="en">gthumb image viewer</shortdesc>
<homepage rdf:resource="http://gthumb.sourceforge.net/" />
<!--
@@ -15,7 +15,7 @@
<maintainer>
<foaf:Person>
<foaf:name>Paolo Bacchilega</foaf:name>
- <foaf:mbox rdf:resource="paobac svn gnome org" />
+ <foaf:mbox rdf:resource="paobac src gnome org" />
<gnome:userid>paobac</gnome:userid>
</foaf:Person>
</maintainer>
diff --git a/gthumb/Makefile.am b/gthumb/Makefile.am
new file mode 100644
index 0000000..939ccfc
--- /dev/null
+++ b/gthumb/Makefile.am
@@ -0,0 +1,296 @@
+SUBDIRS = cursors icons
+
+bin_PROGRAMS = gthumb
+
+ENUM_TYPES = \
+ gth-enum-types.h \
+ gth-enum-types.c
+
+MARSHALLERS = \
+ gth-marshal.c \
+ gth-marshal.h
+
+EXTERNAL = \
+ egg-macros.h \
+ eggfileformatchooser.c \
+ eggfileformatchooser.h \
+ gedit-message-area.c \
+ gedit-message-area.h \
+ gnome-desktop-thumbnail.c \
+ gnome-desktop-thumbnail.h \
+ gnome-thumbnail-pixbuf-utils.c
+
+PUBLIC_HEADER_FILES = \
+ dom.h \
+ egg-macros.h \
+ eggfileformatchooser.h \
+ file-cache.h \
+ gconf-utils.h \
+ gedit-message-area.h \
+ gio-utils.h \
+ glib-utils.h \
+ gnome-desktop-thumbnail.h \
+ gth-browser.h \
+ gth-cell-renderer-thumbnail.h \
+ gth-cursors.h \
+ gth-dumb-notebook.h \
+ gth-duplicable.h \
+ gth-edit-metadata-dialog.h \
+ gth-embedded-dialog.h \
+ gth-empty-list.h \
+ gth-enum-types.h \
+ gth-extensions.h \
+ gth-file-data.h \
+ gth-file-list.h \
+ gth-file-properties.h \
+ gth-file-selection.h \
+ gth-file-source.h \
+ gth-file-source-vfs.h \
+ gth-file-store.h \
+ gth-file-view.h \
+ gth-filter.h \
+ gth-filterbar.h \
+ gth-filter-editor-dialog.h \
+ gth-filter-file.h \
+ gth-folder-tree.h \
+ gth-hook.h \
+ gth-icon-cache.h \
+ gth-icon-view.h \
+ gth-image-dragger.h \
+ gth-image-history.h \
+ gth-image-loader.h \
+ gth-image-preloader.h \
+ gth-image-selector.h \
+ gth-image-tool.h \
+ gth-image-viewer.h \
+ gth-location-chooser.h \
+ gth-main.h \
+ gth-marshal.h \
+ gth-metadata.h \
+ gth-metadata-provider.h \
+ gth-monitor.h \
+ gth-multipage.h \
+ gth-nav-window.h \
+ gth-pixbuf-task.h \
+ gth-preferences.h \
+ gth-sidebar.h \
+ gth-statusbar.h \
+ gth-source-tree.h \
+ gth-stock.h \
+ gth-string-list.h \
+ gth-task.h \
+ gth-test.h \
+ gth-test-chain.h \
+ gth-test-selector.h \
+ gth-test-simple.h \
+ gth-thumb-loader.h \
+ gth-time.h \
+ gth-toolbox.h \
+ gth-uri-list.h \
+ gth-user-dir.h \
+ gth-viewer-page.h \
+ gth-window.h \
+ gthumb-error.h \
+ gtk-utils.h \
+ pixbuf-io.h \
+ pixbuf-utils.h \
+ typedefs.h \
+ zlib-utils.h \
+ $(NULL)
+
+PRIVATE_HEADER_FILES = \
+ dlg-bookmarks.h \
+ dlg-edit-metadata.h \
+ gth-browser-actions-callbacks.h \
+ gth-browser-actions-entries.h \
+ gth-browser-ui.h \
+ gth-metadata-provider-file.h \
+ dlg-personalize-filters.h \
+ dlg-preferences.h \
+ dlg-sort-order.h \
+ gth-window-actions-callbacks.h \
+ gth-window-actions-entries.h \
+ $(NULL)
+
+gthumb_SOURCES = \
+ $(ENUM_TYPES) \
+ $(EXTERNAL) \
+ $(PUBLIC_HEADER_FILES) \
+ $(PRIVATE_HEADER_FILES) \
+ $(MARSHALLERS) \
+ dlg-bookmarks.c \
+ dlg-edit-metadata.c \
+ dlg-personalize-filters.c \
+ dlg-preferences.c \
+ dlg-sort-order.c \
+ dom.c \
+ file-cache.c \
+ gconf-utils.c \
+ gio-utils.c \
+ glib-utils.c \
+ gth-browser.c \
+ gth-browser-actions-callbacks.c \
+ gth-cell-renderer-thumbnail.c \
+ gth-cursors.c \
+ gth-dumb-notebook.c \
+ gth-duplicable.c \
+ gth-edit-metadata-dialog.c \
+ gth-embedded-dialog.c \
+ gth-empty-list.c \
+ gth-extensions.c \
+ gth-file-data.c \
+ gth-file-list.c \
+ gth-file-properties.c \
+ gth-file-selection.c \
+ gth-file-source.c \
+ gth-file-source-vfs.c \
+ gth-file-store.c \
+ gth-file-view.c \
+ gth-filter.c \
+ gth-filterbar.c \
+ gth-filter-editor-dialog.c \
+ gth-filter-file.c \
+ gth-folder-tree.c \
+ gth-hook.c \
+ gth-icon-cache.c \
+ gth-icon-view.c \
+ gth-image-dragger.c \
+ gth-image-history.c \
+ gth-image-loader.c \
+ gth-image-preloader.c \
+ gth-image-selector.c \
+ gth-image-tool.c \
+ gth-image-viewer.c \
+ gth-location-chooser.c \
+ gth-main.c \
+ gth-main-default-hooks.c \
+ gth-main-default-metadata.c \
+ gth-main-default-sort-types.c \
+ gth-main-default-tests.c \
+ gth-main-default-types.c \
+ gth-metadata.c \
+ gth-metadata-provider.c \
+ gth-metadata-provider-file.c \
+ gth-monitor.c \
+ gth-multipage.c \
+ gth-nav-window.c \
+ gth-pixbuf-task.c \
+ gth-preferences.c \
+ gth-sidebar.c \
+ gth-source-tree.c \
+ gth-statusbar.c \
+ gth-string-list.c \
+ gth-task.c \
+ gth-test.c \
+ gth-test-chain.c \
+ gth-test-selector.c \
+ gth-test-simple.c \
+ gth-thumb-loader.c \
+ gth-time.c \
+ gth-toolbox.c \
+ gth-uri-list.c \
+ gth-user-dir.c \
+ gth-viewer-page.c \
+ gth-window.c \
+ gth-window-actions-callbacks.c \
+ gthumb-error.c \
+ gtk-utils.c \
+ main.c \
+ pixbuf-io.c \
+ pixbuf-utils.c \
+ zlib-utils.c \
+ $(NULL)
+
+if PLATFORM_WIN32
+gthumb_LDFLAGS = -Wl,--export-all-symbols,--out-implib,.libs/gthumb.exe.a
+endif
+
+gthumb_LDADD = \
+ $(top_builddir)/copy-n-paste/libeggsmclient.la \
+ $(GTHUMB_LIBS) \
+ $(EXIV2_LIBS) \
+ $(NULL)
+
+if RUN_IN_PLACE
+ui_dir = $(abs_top_srcdir)/data/ui
+extensions_ui_dir = $(abs_top_srcdir)/extensions
+extensions_dir = $(abs_top_builddir)/extensions
+else
+ui_dir = $(datadir)/gthumb/ui
+extensions_ui_dir = $(datadir)/gthumb/ui
+extensions_dir = $(libdir)/gthumb/extensions
+endif
+
+gthumb_CFLAGS = \
+ $(GTHUMB_CFLAGS) \
+ $(EXIV2_CFLAGS) \
+ -I$(top_srcdir)/copy-n-paste/ \
+ -DGTHUMB_LOCALEDIR=\"$(datadir)/locale\" \
+ -DGTHUMB_PREFIX=\"$(prefix)\" \
+ -DGTHUMB_SYSCONFDIR=\"$(sysconfdir)\" \
+ -DGTHUMB_DATADIR=\"$(datadir)\" \
+ -DGTHUMB_LIBDIR=\"$(libdir)\" \
+ -DGTHUMB_PKGDATADIR=\"$(pkgdatadir)\" \
+ -DGTHUMB_UI_DIR=\"$(ui_dir)\" \
+ -DGTHUMB_EXTENSIONS_UI_DIR=\"$(extensions_ui_dir)\" \
+ -DGTHUMB_EXTENSIONS_DIR=\"$(extensions_dir)\" \
+ $(NULL)
+
+gth-enum-types.h: $(PUBLIC_HEADER_FILES) $(GLIB_MKENUMS)
+ $(GLIB_MKENUMS) \
+ --fhead "#ifndef GTH_ENUM__TYPES_H\n#define GTH_ENUM_TYPES_H\n\n#include <glib-object.h>\n\nG_BEGIN_DECLS\n" \
+ --fprod "/* enumerations from \"@filename \" */\n" \
+ --vhead "GType @enum_name _get_type (void);\n#define GTH_TYPE_ ENUMSHORT@ (@enum_name _get_type())\n" \
+ --ftail "G_END_DECLS\n\n#endif /* GTH_ENUM_TYPES_H */" \
+ $^> xgen-$(@F) \
+ && (cmp -s xgen-$(@F) gth-enum-types.h || cp xgen-$(@F) gth-enum-types.h ) \
+ && rm -f xgen-$(@F)
+
+gth-enum-types.c: $(PUBLIC_HEADER_FILES) gth-enum-types.h
+ $(GLIB_MKENUMS) \
+ --fhead "#include <glib-object.h>\n" \
+ --fprod "\n/* enumerations from \"@filename \" */\n#include \"@filename \"" \
+ --vhead "GType\n enum_name@_get_type (void)\n{\n static GType etype = 0;\n if (etype == 0) {\n static const G Type@Value values[] = {" \
+ --vprod " { @VALUENAME@, \"@VALUENAME \", \"@valuenick \" }," \
+ --vtail " { 0, NULL, NULL }\n };\n etype = g_ type@_register_static (\"@EnumName \", values);\n }\n return etype;\n}\n" \
+ $^> xgen-$(@F) \
+ && (cmp -s xgen-$(@F) gth-enum-types.c || cp xgen-$(@F) gth-enum-types.c ) \
+ && rm -f xgen-$(@F)
+
+gth-marshal.h: gth-marshal.list $(GLIB_GENMARSHAL)
+ $(GLIB_GENMARSHAL) $(srcdir)/gth-marshal.list --header --prefix=gth_marshal > $@
+
+gth-marshal.c: gth-marshal.h gth-marshal.list $(GLIB_GENMARSHAL)
+ echo "#include \"gth-marshal.h\"" > $@ \
+ && $(GLIB_GENMARSHAL) $(srcdir)/gth-marshal.list --body --prefix=gth_marshal >> $@
+
+gthumb.h: make-header.sh gthumb.h.template
+ $^ $(PUBLIC_HEADER_FILES) > xgen-$(@F) \
+ && (cmp -s xgen-$(@F) gthumb.h || cp xgen-$(@F) gthumb.h ) \
+ && rm -f xgen-$(@F)
+
+gthumbincludedir = $(includedir)/gthumb-2.0
+gthumbinclude_HEADERS = gthumb.h
+
+gthumbsubincludedir = $(includedir)/gthumb-2.0/gthumb
+gthumbsubinclude_HEADERS = $(PUBLIC_HEADER_FILES)
+
+AUTHORS.tab : $(top_srcdir)/AUTHORS
+ sed -e 's/^/"/' -e 's/$$/",/' < $(top_srcdir)/AUTHORS > $ tmp
+ mv $ tmp $@
+
+BUILT_SOURCES = AUTHORS.tab gthumb.h
+
+CLEANFILES = \
+ $(BUILT_SOURCES) \
+ $(MARSHALLERS) \
+ $(ENUM_TYPES)
+
+EXTRA_DIST = \
+ gth-marshal.list \
+ gthumb.h.template \
+ make-header.sh
+
+BUILT_EXTRA_DIST = AUTHORS.tab gthumb.h
+
+-include $(top_srcdir)/git.mk
diff --git a/libgthumb/cursors/Makefile.am b/gthumb/cursors/Makefile.am
similarity index 100%
rename from libgthumb/cursors/Makefile.am
rename to gthumb/cursors/Makefile.am
diff --git a/libgthumb/cursors/hand-closed-data.xbm b/gthumb/cursors/hand-closed-data.xbm
similarity index 100%
rename from libgthumb/cursors/hand-closed-data.xbm
rename to gthumb/cursors/hand-closed-data.xbm
diff --git a/libgthumb/cursors/hand-closed-mask.xbm b/gthumb/cursors/hand-closed-mask.xbm
similarity index 100%
rename from libgthumb/cursors/hand-closed-mask.xbm
rename to gthumb/cursors/hand-closed-mask.xbm
diff --git a/libgthumb/cursors/hand-open-data.xbm b/gthumb/cursors/hand-open-data.xbm
similarity index 100%
rename from libgthumb/cursors/hand-open-data.xbm
rename to gthumb/cursors/hand-open-data.xbm
diff --git a/libgthumb/cursors/hand-open-mask.xbm b/gthumb/cursors/hand-open-mask.xbm
similarity index 100%
rename from libgthumb/cursors/hand-open-mask.xbm
rename to gthumb/cursors/hand-open-mask.xbm
diff --git a/libgthumb/cursors/void-data.xbm b/gthumb/cursors/void-data.xbm
similarity index 100%
rename from libgthumb/cursors/void-data.xbm
rename to gthumb/cursors/void-data.xbm
diff --git a/libgthumb/cursors/void-mask.xbm b/gthumb/cursors/void-mask.xbm
similarity index 100%
rename from libgthumb/cursors/void-mask.xbm
rename to gthumb/cursors/void-mask.xbm
diff --git a/gthumb/dlg-bookmarks.c b/gthumb/dlg-bookmarks.c
new file mode 100644
index 0000000..8e86084
--- /dev/null
+++ b/gthumb/dlg-bookmarks.c
@@ -0,0 +1,238 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2001-2008 The Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <gtk/gtk.h>
+#include "glib-utils.h"
+#include "gth-browser.h"
+#include "gth-main.h"
+#include "gth-uri-list.h"
+#include "gtk-utils.h"
+
+
+typedef struct {
+ GthBrowser *browser;
+ GtkBuilder *builder;
+ GtkWidget *dialog;
+ GtkWidget *uri_list;
+ gboolean do_not_update;
+ gulong bookmarks_changed_id;
+} DialogData;
+
+
+/* called when the main dialog is closed. */
+static void
+destroy_cb (GtkWidget *widget,
+ DialogData *data)
+{
+ gth_browser_set_dialog (data->browser, "bookmarks", NULL);
+ g_signal_handler_disconnect (gth_main_get_default_monitor (), data->bookmarks_changed_id);
+
+ g_object_unref (data->builder);
+ g_free (data);
+}
+
+
+static void
+remove_cb (GtkWidget *widget,
+ DialogData *data)
+{
+ char *uri;
+ GBookmarkFile *bookmarks;
+ GError *error = NULL;
+
+ uri = gth_uri_list_get_selected (GTH_URI_LIST (data->uri_list));
+ if (uri == NULL)
+ return;
+
+ bookmarks = gth_main_get_default_bookmarks ();
+ if (! g_bookmark_file_remove_item (bookmarks, uri, &error)) {
+ _gtk_error_dialog_from_gerror_show (GTK_WINDOW (data->dialog), _("Could not remove the bookmark"), &error);
+ }
+ gth_main_bookmarks_changed ();
+
+ g_free (uri);
+}
+
+
+static void
+go_to_cb (GtkWidget *widget,
+ DialogData *data)
+{
+ char *uri;
+
+ uri = gth_uri_list_get_selected (GTH_URI_LIST (data->uri_list));
+ if (uri != NULL) {
+ GFile *location;
+
+ location = g_file_new_for_uri (uri);
+ gth_browser_go_to (data->browser, location);
+
+ g_object_unref (location);
+ g_free (uri);
+ }
+}
+
+
+static void
+bookmarks_changed_cb (GthMonitor *monitor,
+ DialogData *data)
+{
+ GBookmarkFile *bookmarks;
+ char **uris;
+
+ bookmarks = gth_main_get_default_bookmarks ();
+ uris = g_bookmark_file_get_uris (bookmarks, NULL);
+ gth_uri_list_set_uris (GTH_URI_LIST (data->uri_list), uris);
+ g_strfreev (uris);
+}
+
+
+static void
+uri_list_order_changed_cb (GthUriList *uri_list,
+ DialogData *data)
+{
+ GBookmarkFile *bookmarks;
+ GList *uris;
+
+ uris = gth_uri_list_get_uris (GTH_URI_LIST (data->uri_list));
+ bookmarks = gth_main_get_default_bookmarks ();
+ _g_bookmark_file_set_uris (bookmarks, uris);
+ _g_string_list_free (uris);
+
+ gth_main_bookmarks_changed ();
+}
+
+
+static void
+uri_list_row_activated_cb (GtkTreeView *tree_view,
+ GtkTreePath *path,
+ GtkTreeViewColumn *column,
+ gpointer user_data)
+{
+ DialogData *data = user_data;
+ GtkTreeModel *tree_model;
+ GtkTreeIter iter;
+ char *uri;
+ GFile *location;
+
+ tree_model = gtk_tree_view_get_model (tree_view);
+ if (! gtk_tree_model_get_iter (tree_model, &iter, path))
+ return;
+
+ uri = gth_uri_list_get_uri (GTH_URI_LIST (tree_view), &iter);
+ if (uri == NULL)
+ return;
+
+ location = g_file_new_for_uri (uri);
+ gth_browser_go_to (data->browser, location);
+
+ g_object_unref (location);
+ g_free (uri);
+}
+
+
+void
+dlg_bookmarks (GthBrowser *browser)
+{
+ DialogData *data;
+ GtkWidget *bm_list_container;
+ GtkWidget *bm_bookmarks_label;
+ GtkWidget *bm_remove_button;
+ GtkWidget *bm_close_button;
+ GtkWidget *bm_go_to_button;
+ GBookmarkFile *bookmarks;
+ char **uris;
+
+ if (gth_browser_get_dialog (browser, "bookmarks") != NULL) {
+ gtk_window_present (GTK_WINDOW (gth_browser_get_dialog (browser, "bookmarks")));
+ return;
+ }
+
+ data = g_new0 (DialogData, 1);
+ data->browser = browser;
+ data->do_not_update = FALSE;
+ data->builder = _gtk_builder_new_from_file ("bookmarks.ui", NULL);
+
+ /* Get the widgets. */
+
+ data->dialog = _gtk_builder_get_widget (data->builder, "bookmarks_dialog");
+ gth_browser_set_dialog (browser, "bookmarks", data->dialog);
+ g_object_set_data (G_OBJECT (data->dialog), "dialog_data", data);
+
+ bm_list_container = _gtk_builder_get_widget (data->builder, "bm_list_container");
+ bm_bookmarks_label = _gtk_builder_get_widget (data->builder, "bm_bookmarks_label");
+ bm_remove_button = _gtk_builder_get_widget (data->builder, "bm_remove_button");
+ bm_close_button = _gtk_builder_get_widget (data->builder, "bm_close_button");
+ bm_go_to_button = _gtk_builder_get_widget (data->builder, "bm_go_to_button");
+
+ data->uri_list = gth_uri_list_new ();
+ gtk_widget_show (data->uri_list);
+ gtk_container_add (GTK_CONTAINER (bm_list_container), data->uri_list);
+ gtk_label_set_mnemonic_widget (GTK_LABEL (bm_bookmarks_label), data->uri_list);
+
+ /* Set widgets data. */
+
+ bookmarks = gth_main_get_default_bookmarks ();
+ uris = g_bookmark_file_get_uris (bookmarks, NULL);
+ gth_uri_list_set_uris (GTH_URI_LIST (data->uri_list), uris);
+ g_strfreev (uris);
+
+ data->bookmarks_changed_id = g_signal_connect (gth_main_get_default_monitor (),
+ "bookmarks-changed",
+ G_CALLBACK (bookmarks_changed_cb),
+ data);
+
+ /* Set the signals handlers. */
+
+ g_signal_connect (G_OBJECT (data->dialog),
+ "destroy",
+ G_CALLBACK (destroy_cb),
+ data);
+ g_signal_connect_swapped (G_OBJECT (bm_close_button),
+ "clicked",
+ G_CALLBACK (gtk_widget_destroy),
+ G_OBJECT (data->dialog));
+ g_signal_connect (G_OBJECT (bm_remove_button),
+ "clicked",
+ G_CALLBACK (remove_cb),
+ data);
+ g_signal_connect (G_OBJECT (bm_go_to_button),
+ "clicked",
+ G_CALLBACK (go_to_cb),
+ data);
+ g_signal_connect (G_OBJECT (data->uri_list),
+ "order-changed",
+ G_CALLBACK (uri_list_order_changed_cb),
+ data);
+ g_signal_connect (G_OBJECT (data->uri_list),
+ "row-activated",
+ G_CALLBACK (uri_list_row_activated_cb),
+ data);
+
+ /* run dialog. */
+
+ gtk_window_set_transient_for (GTK_WINDOW (data->dialog),
+ GTK_WINDOW (browser));
+ gtk_window_set_modal (GTK_WINDOW (data->dialog), FALSE);
+ gtk_widget_show (data->dialog);
+}
diff --git a/gthumb/dlg-bookmarks.h b/gthumb/dlg-bookmarks.h
new file mode 100644
index 0000000..47499aa
--- /dev/null
+++ b/gthumb/dlg-bookmarks.h
@@ -0,0 +1,30 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2001-2008 The Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef DLG_BOOKMARKS_H
+#define DLG_BOOKMARKS_H
+
+#include "gth-browser.h"
+
+void dlg_bookmarks (GthBrowser *browser);
+
+#endif /* DLG_BOOKMARKS_H */
diff --git a/gthumb/dlg-edit-metadata.c b/gthumb/dlg-edit-metadata.c
new file mode 100644
index 0000000..c317311
--- /dev/null
+++ b/gthumb/dlg-edit-metadata.c
@@ -0,0 +1,124 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 The Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <gtk/gtk.h>
+#include "dlg-edit-metadata.h"
+#include "glib-utils.h"
+#include "gth-edit-metadata-dialog.h"
+#include "gth-main.h"
+#include "gth-metadata-provider.h"
+#include "gtk-utils.h"
+
+
+typedef struct {
+ GthBrowser *browser;
+ GtkWidget *dialog;
+ GthFileData *file_data;
+} DialogData;
+
+
+static void
+destroy_cb (GtkWidget *widget,
+ DialogData *data)
+{
+ g_object_unref (data->file_data);
+ g_free (data);
+}
+
+
+static void
+write_metadata_ready_cb (GError *error,
+ gpointer user_data)
+{
+ DialogData *data = user_data;
+ GthMonitor *monitor;
+ GFile *parent;
+ GList *files;
+
+ if (error != NULL) {
+ _gtk_error_dialog_from_gerror_show (GTK_WINDOW (data->browser), _("Could not save file properties"), &error);
+ return;
+ }
+
+ monitor = gth_main_get_default_monitor ();
+ parent = g_file_get_parent (data->file_data->file);
+ files = g_list_prepend (NULL, g_object_ref (data->file_data->file));
+ gth_monitor_folder_changed (monitor, parent, files, GTH_MONITOR_EVENT_CHANGED);
+
+ gtk_widget_destroy (GTK_WIDGET (data->dialog));
+
+ _g_object_list_unref (files);
+ g_object_unref (parent);
+}
+
+
+static void
+edit_metadata_dialog__response_cb (GtkDialog *dialog,
+ int response,
+ gpointer user_data)
+{
+ DialogData *data = user_data;
+ GList *files;
+
+ if (response != GTK_RESPONSE_OK) {
+ gtk_widget_destroy (GTK_WIDGET (data->dialog));
+ return;
+ }
+
+ gth_edit_metadata_dialog_update_info (GTH_EDIT_METADATA_DIALOG (data->dialog), data->file_data->info);
+
+ files = g_list_prepend (NULL, g_object_ref (data->file_data));
+ _g_write_metadata_async (files, "*", NULL, write_metadata_ready_cb, data);
+
+ _g_object_list_unref (files);
+}
+
+
+void
+dlg_edit_metadata (GthBrowser *browser)
+{
+ DialogData *data;
+
+ if (gth_browser_get_current_file (browser) == NULL)
+ return;
+
+ data = g_new0 (DialogData, 1);
+ data->browser = browser;
+ data->file_data = g_object_ref (gth_browser_get_current_file (browser));
+ data->dialog = gth_edit_metadata_dialog_new ();
+
+ g_signal_connect (G_OBJECT (data->dialog),
+ "destroy",
+ G_CALLBACK (destroy_cb),
+ data);
+ g_signal_connect (data->dialog,
+ "response",
+ G_CALLBACK (edit_metadata_dialog__response_cb),
+ data);
+
+ gtk_window_set_transient_for (GTK_WINDOW (data->dialog), GTK_WINDOW (browser));
+ gtk_window_set_modal (GTK_WINDOW (data->dialog), TRUE);
+ gtk_window_present (GTK_WINDOW (data->dialog));
+
+ gth_edit_metadata_dialog_set_file (GTH_EDIT_METADATA_DIALOG (data->dialog), data->file_data);
+}
diff --git a/gthumb/dlg-edit-metadata.h b/gthumb/dlg-edit-metadata.h
new file mode 100644
index 0000000..9fa3740
--- /dev/null
+++ b/gthumb/dlg-edit-metadata.h
@@ -0,0 +1,30 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 The Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef DLG_EDIT_METADATA_H
+#define DLG_EDIT_METADATA_H
+
+#include "gth-browser.h"
+
+void dlg_edit_metadata (GthBrowser *browser);
+
+#endif /* DLG_EDIT_METADATA_H */
diff --git a/gthumb/dlg-personalize-filters.c b/gthumb/dlg-personalize-filters.c
new file mode 100644
index 0000000..08db11d
--- /dev/null
+++ b/gthumb/dlg-personalize-filters.c
@@ -0,0 +1,619 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2008 The Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <gtk/gtk.h>
+#include "dlg-personalize-filters.h"
+#include "gconf-utils.h"
+#include "glib-utils.h"
+#include "gth-filter-editor-dialog.h"
+#include "gth-main.h"
+#include "gth-preferences.h"
+#include "gtk-utils.h"
+
+
+#define GET_WIDGET(name) _gtk_builder_get_widget (data->builder, (name))
+#define ORDER_CHANGED_DELAY 250
+
+
+enum {
+ COLUMN_FILTER,
+ COLUMN_NAME,
+ COLUMN_VISIBLE,
+ COLUMN_STYLE,
+ NUM_COLUMNS
+};
+
+
+typedef struct {
+ GthBrowser *browser;
+ GtkBuilder *builder;
+ GtkWidget *dialog;
+ GtkWidget *list_view;
+ GtkWidget *general_filter_combobox;
+ GtkListStore *list_store;
+ gulong filters_changed_id;
+ guint list_changed_id;
+ GList *general_tests;
+} DialogData;
+
+
+static void
+destroy_cb (GtkWidget *widget,
+ DialogData *data)
+{
+ if (data->list_changed_id != 0)
+ g_source_remove (data->list_changed_id);
+ data->list_changed_id = 0;
+
+ gth_browser_set_dialog (data->browser, "personalize_filters", NULL);
+ g_signal_handler_disconnect (gth_main_get_default_monitor (), data->filters_changed_id);
+
+ _g_string_list_free (data->general_tests);
+ g_object_unref (data->builder);
+ g_free (data);
+}
+
+
+static gboolean
+list_view_row_order_changed_cb (gpointer user_data)
+{
+ DialogData *data = user_data;
+ GtkTreeModel *model = GTK_TREE_MODEL (data->list_store);
+ GtkTreeIter iter;
+
+ if (data->list_changed_id != 0)
+ g_source_remove (data->list_changed_id);
+ data->list_changed_id = 0;
+
+ if (gtk_tree_model_get_iter_first (model, &iter)) {
+ GthFilterFile *filter_file;
+
+ filter_file = gth_main_get_default_filter_file ();
+ gth_filter_file_clear (filter_file);
+
+ do {
+ GthTest *test;
+
+ gtk_tree_model_get (model, &iter, COLUMN_FILTER, &test, -1);
+ gth_filter_file_add (filter_file, test);
+ g_object_unref (test);
+ }
+ while (gtk_tree_model_iter_next (model, &iter));
+
+ gth_main_filters_changed ();
+ }
+
+ return FALSE;
+}
+
+
+static void
+row_deleted_cb (GtkTreeModel *tree_model,
+ GtkTreePath *path,
+ gpointer user_data)
+{
+ DialogData *data = user_data;
+
+ if (data->list_changed_id != 0)
+ g_source_remove (data->list_changed_id);
+ data->list_changed_id = g_timeout_add (ORDER_CHANGED_DELAY, list_view_row_order_changed_cb, data);
+}
+
+
+static void
+row_inserted_cb (GtkTreeModel *tree_model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ gpointer user_data)
+{
+ DialogData *data = user_data;
+
+ if (data->list_changed_id != 0)
+ g_source_remove (data->list_changed_id);
+ data->list_changed_id = g_timeout_add (ORDER_CHANGED_DELAY, list_view_row_order_changed_cb, data);
+}
+
+
+static void
+set_filter_list (DialogData *data,
+ GList *filter_list)
+{
+ GList *scan;
+
+ g_signal_handlers_block_by_func (data->list_store, row_inserted_cb, data);
+
+ for (scan = filter_list; scan; scan = scan->next) {
+ GthTest *test = scan->data;
+ GtkTreeIter iter;
+
+ gtk_list_store_append (data->list_store, &iter);
+ gtk_list_store_set (data->list_store, &iter,
+ COLUMN_FILTER, test,
+ COLUMN_NAME, gth_test_get_display_name (test),
+ COLUMN_VISIBLE, gth_test_is_visible (test),
+ COLUMN_STYLE, GTH_IS_FILTER (test) ? PANGO_STYLE_NORMAL : PANGO_STYLE_ITALIC,
+ -1);
+ }
+
+ g_signal_handlers_unblock_by_func (data->list_store, row_inserted_cb, data);
+}
+
+
+static void
+update_filter_list (DialogData *data)
+{
+ GList *filter_list;
+
+ g_signal_handlers_block_by_func (data->list_store, row_deleted_cb, data);
+ gtk_list_store_clear (data->list_store);
+ g_signal_handlers_unblock_by_func (data->list_store, row_deleted_cb, data);
+
+ filter_list = gth_main_get_all_filters ();
+ set_filter_list (data, filter_list);
+ _g_object_list_unref (filter_list);
+}
+
+
+static void
+filters_changed_cb (GthMonitor *monitor,
+ DialogData *data)
+{
+ update_filter_list (data);
+}
+
+
+static void
+cell_renderer_toggle_toggled_cb (GtkCellRendererToggle *cell_renderer,
+ char *path,
+ gpointer user_data)
+{
+ DialogData *data = user_data;
+ GtkTreePath *tpath;
+ GtkTreeIter iter;
+ gboolean visible;
+
+ tpath = gtk_tree_path_new_from_string (path);
+ if (tpath == NULL)
+ return;
+
+ if (gtk_tree_model_get_iter (GTK_TREE_MODEL (data->list_store), &iter, tpath)) {
+ GthTest *filter;
+ GthFilterFile *filter_file;
+
+ gtk_tree_model_get (GTK_TREE_MODEL (data->list_store), &iter,
+ COLUMN_FILTER, &filter,
+ COLUMN_VISIBLE, &visible,
+ -1);
+ visible = ! visible;
+ g_object_set (filter, "visible", visible, NULL);
+
+ g_signal_handlers_block_by_func (gth_main_get_default_monitor (), filters_changed_cb, data);
+ filter_file = gth_main_get_default_filter_file ();
+ gth_filter_file_add (filter_file, filter);
+ gth_main_filters_changed ();
+ g_signal_handlers_unblock_by_func (gth_main_get_default_monitor (), filters_changed_cb, data);
+
+ gtk_list_store_set (data->list_store, &iter,
+ COLUMN_VISIBLE, visible,
+ -1);
+
+ g_object_unref (filter);
+ }
+
+ gtk_tree_path_free (tpath);
+}
+
+
+static void
+add_columns (GtkTreeView *treeview,
+ DialogData *data)
+{
+ GtkCellRenderer *renderer;
+ GtkTreeViewColumn *column;
+
+ /* the name column. */
+
+ column = gtk_tree_view_column_new ();
+ gtk_tree_view_column_set_title (column, _("Filter"));
+
+ renderer = gtk_cell_renderer_text_new ();
+ gtk_tree_view_column_pack_start (column, renderer, TRUE);
+ gtk_tree_view_column_set_attributes (column, renderer,
+ "text", COLUMN_NAME,
+ "style", COLUMN_STYLE,
+ NULL);
+
+ gtk_tree_view_column_set_expand (column, TRUE);
+ gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column);
+
+ /* the checkbox column */
+
+ column = gtk_tree_view_column_new ();
+ gtk_tree_view_column_set_title (column, _("Show"));
+
+ renderer = gtk_cell_renderer_toggle_new ();
+ g_signal_connect (renderer,
+ "toggled",
+ G_CALLBACK (cell_renderer_toggle_toggled_cb),
+ data);
+
+ gtk_tree_view_column_pack_start (column, renderer, TRUE);
+ gtk_tree_view_column_set_attributes (column, renderer,
+ "active", COLUMN_VISIBLE,
+ NULL);
+
+ gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column);
+}
+
+
+static gboolean
+get_test_iter (DialogData *data,
+ GthTest *test,
+ GtkTreeIter *iter)
+{
+ GtkTreeModel *model = GTK_TREE_MODEL (data->list_store);
+ gboolean found = FALSE;
+ const char *test_id;
+
+ test_id = gth_test_get_id (test);
+ if (! gtk_tree_model_get_iter_first (model, iter))
+ return FALSE;
+
+ do {
+ GthTest *list_test;
+
+ gtk_tree_model_get (model, iter, COLUMN_FILTER, &list_test, -1);
+ found = g_strcmp0 (test_id, gth_test_get_id (list_test)) == 0;
+ g_object_unref (list_test);
+ }
+ while (! found && gtk_tree_model_iter_next (model, iter));
+
+ return found;
+}
+
+
+static void
+filter_editor_dialog__response_cb (GtkDialog *dialog,
+ int response,
+ gpointer user_data)
+{
+ DialogData *data = user_data;
+ GthFilter *filter;
+ GError *error = NULL;
+ GthFilterFile *filter_file;
+ gboolean new_filter;
+ GtkTreeIter iter;
+ gboolean change_list = TRUE;
+
+ if (response != GTK_RESPONSE_OK) {
+ gtk_widget_destroy (GTK_WIDGET (dialog));
+ return;
+ }
+
+ filter = gth_filter_editor_dialog_get_filter (GTH_FILTER_EDITOR_DIALOG (dialog), &error);
+ if (filter == NULL) {
+ _gtk_error_dialog_from_gerror_show (GTK_WINDOW (dialog), _("Could not save the filter"), &error);
+ return;
+ }
+
+ /* update the filter file */
+
+ filter_file = gth_main_get_default_filter_file ();
+ new_filter = ! gth_filter_file_has_test (filter_file, GTH_TEST (filter));
+
+ g_signal_handlers_block_by_func (gth_main_get_default_monitor (), filters_changed_cb, data);
+ gth_filter_file_add (filter_file, GTH_TEST (filter));
+ gth_main_filters_changed ();
+ g_signal_handlers_unblock_by_func (gth_main_get_default_monitor (), filters_changed_cb, data);
+
+ /* update the filter list */
+
+ if (new_filter) {
+ g_signal_handlers_block_by_func (data->list_store, row_inserted_cb, data);
+ gtk_list_store_append (data->list_store, &iter);
+ g_signal_handlers_unblock_by_func (data->list_store, row_inserted_cb, data);
+ }
+ else
+ change_list = get_test_iter (data, GTH_TEST (filter), &iter);
+
+ if (change_list)
+ gtk_list_store_set (data->list_store, &iter,
+ COLUMN_FILTER, filter,
+ COLUMN_NAME, gth_test_get_display_name (GTH_TEST (filter)),
+ COLUMN_VISIBLE, gth_test_is_visible (GTH_TEST (filter)),
+ -1);
+
+ g_object_unref (filter);
+ gtk_widget_destroy (GTK_WIDGET (dialog));
+}
+
+
+static void
+new_filter_cb (GtkButton *button,
+ DialogData *data)
+{
+ GtkWidget *dialog;
+
+ dialog = gth_filter_editor_dialog_new (_("New Filter"), GTK_WINDOW (data->dialog));
+ g_signal_connect (dialog, "response",
+ G_CALLBACK (filter_editor_dialog__response_cb),
+ data);
+ gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
+ gtk_window_present (GTK_WINDOW (dialog));
+}
+
+
+static void
+edit_filter_cb (GtkButton *button,
+ DialogData *data)
+{
+ GtkTreeSelection *selection;
+ GtkTreeModel *model = GTK_TREE_MODEL (data->list_store);
+ GtkTreeIter iter;
+ GthTest *filter;
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (data->list_view));
+ if (! gtk_tree_selection_get_selected (selection, &model, &iter))
+ return;
+
+ gtk_tree_model_get (model, &iter, COLUMN_FILTER, &filter, -1);
+ if (filter == NULL)
+ return;
+
+ if (GTH_IS_FILTER (filter)) {
+ GtkWidget *dialog;
+
+ dialog = gth_filter_editor_dialog_new (_("Edit Filter"), GTK_WINDOW (data->dialog));
+ gth_filter_editor_dialog_set_filter (GTH_FILTER_EDITOR_DIALOG (dialog), GTH_FILTER (filter));
+ g_signal_connect (dialog, "response",
+ G_CALLBACK (filter_editor_dialog__response_cb),
+ data);
+ gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
+ gtk_window_present (GTK_WINDOW (dialog));
+ }
+
+ g_object_unref (filter);
+}
+
+
+static void
+delete_filter_cb (GtkButton *button,
+ DialogData *data)
+{
+ GtkTreeSelection *selection;
+ GtkTreeModel *model = GTK_TREE_MODEL (data->list_store);
+ GtkTreeIter iter;
+ GthTest *filter;
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (data->list_view));
+ if (! gtk_tree_selection_get_selected (selection, &model, &iter))
+ return;
+
+ gtk_tree_model_get (model, &iter, COLUMN_FILTER, &filter, -1);
+ if (filter == NULL)
+ return;
+
+ if (GTH_IS_FILTER (filter)) {
+ GthFilterFile *filter_file;
+
+ /* update the filter file */
+
+ g_signal_handlers_block_by_func (gth_main_get_default_monitor (), filters_changed_cb, data);
+
+ filter_file = gth_main_get_default_filter_file ();
+ gth_filter_file_remove (filter_file, filter);
+ gth_main_filters_changed ();
+
+ g_signal_handlers_unblock_by_func (gth_main_get_default_monitor (), filters_changed_cb, data);
+
+ /* update the filter list */
+
+ g_signal_handlers_block_by_func (data->list_store, row_deleted_cb, data);
+ gtk_list_store_remove (data->list_store, &iter);
+ g_signal_handlers_unblock_by_func (data->list_store, row_deleted_cb, data);
+ }
+
+ g_object_unref (filter);
+}
+
+
+static void
+update_sensitivity (DialogData *data)
+{
+ GtkTreeModel *model;
+ GtkTreeSelection *selection;
+ GtkTreeIter iter;
+ GthTest *test;
+ gboolean is_filter;
+
+ model = GTK_TREE_MODEL (data->list_store);
+ is_filter = FALSE;
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (data->list_view));
+ if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
+ gtk_tree_model_get (model, &iter, COLUMN_FILTER, &test, -1);
+ if (test != NULL) {
+ is_filter = GTH_IS_FILTER (test);
+ g_object_unref (test);
+ }
+ }
+
+ gtk_widget_set_sensitive (GET_WIDGET ("edit_button"), is_filter);
+ gtk_widget_set_sensitive (GET_WIDGET ("delete_button"), is_filter);
+}
+
+
+static void
+list_view_selection_changed_cb (GtkTreeSelection *treeselection,
+ gpointer user_data)
+{
+ update_sensitivity ((DialogData *) user_data);
+}
+
+
+static void
+general_filter_changed_cb (GtkComboBox *widget,
+ gpointer user_data)
+{
+ DialogData *data = user_data;
+ int idx;
+
+ idx = gtk_combo_box_get_active (widget);
+ eel_gconf_set_string (PREF_GENERAL_FILTER, g_list_nth (data->general_tests, idx)->data);
+}
+
+
+void
+dlg_personalize_filters (GthBrowser *browser)
+{
+ DialogData *data;
+ GList *tests, *scan;
+ char *general_filter;
+ int i, active_filter;
+ int i_general;
+
+ if (gth_browser_get_dialog (browser, "personalize_filters") != NULL) {
+ gtk_window_present (GTK_WINDOW (gth_browser_get_dialog (browser, "personalize_filters")));
+ return;
+ }
+
+ data = g_new0 (DialogData, 1);
+ data->browser = browser;
+ data->builder = _gtk_builder_new_from_file ("personalize-filters.ui", NULL);
+
+ /* Get the widgets. */
+
+ data->dialog = _gtk_builder_get_widget (data->builder, "personalize_filters_dialog");
+ gth_browser_set_dialog (browser, "personalize_filters", data->dialog);
+ g_object_set_data (G_OBJECT (data->dialog), "dialog_data", data);
+
+ /* Set widgets data. */
+
+ tests = gth_main_get_all_tests ();
+ general_filter = eel_gconf_get_string (PREF_GENERAL_FILTER, DEFAULT_GENERAL_FILTER);
+ active_filter = 0;
+
+ data->general_filter_combobox = gtk_combo_box_new_text ();
+ for (i = 0, i_general = -1, scan = tests; scan; scan = scan->next, i++) {
+ const char *registered_test_id = scan->data;
+ GthTest *test;
+
+ if (strncmp (registered_test_id, "file::type::", 12) != 0)
+ continue;
+
+ i_general += 1;
+
+ if (strcmp (registered_test_id, general_filter) == 0)
+ active_filter = i_general;
+
+ test = gth_main_get_test (registered_test_id);
+ data->general_tests = g_list_prepend (data->general_tests, g_strdup (gth_test_get_id (test)));
+ gtk_combo_box_append_text (GTK_COMBO_BOX (data->general_filter_combobox), gth_test_get_display_name (test));
+ g_object_unref (test);
+ }
+ data->general_tests = g_list_reverse (data->general_tests);
+
+ gtk_combo_box_set_active (GTK_COMBO_BOX (data->general_filter_combobox), active_filter);
+ gtk_widget_show (data->general_filter_combobox);
+ gtk_container_add (GTK_CONTAINER (GET_WIDGET ("general_filter_box")), data->general_filter_combobox);
+
+ gtk_label_set_mnemonic_widget (GTK_LABEL (GET_WIDGET ("general_filter_label")), data->general_filter_combobox);
+ gtk_label_set_use_underline (GTK_LABEL (GET_WIDGET ("general_filter_label")), TRUE);
+
+ g_free (general_filter);
+ _g_string_list_free (tests);
+
+ /**/
+
+ data->list_store = gtk_list_store_new (NUM_COLUMNS,
+ G_TYPE_OBJECT,
+ G_TYPE_STRING,
+ G_TYPE_BOOLEAN,
+ PANGO_TYPE_STYLE);
+ data->list_view = gtk_tree_view_new_with_model (GTK_TREE_MODEL (data->list_store));
+ g_object_unref (data->list_store);
+ gtk_tree_view_set_reorderable (GTK_TREE_VIEW (data->list_view), TRUE);
+ gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (data->list_view), TRUE);
+
+ add_columns (GTK_TREE_VIEW (data->list_view), data);
+
+ gtk_widget_show (data->list_view);
+ gtk_container_add (GTK_CONTAINER (GET_WIDGET ("filters_scrolledwindow")), data->list_view);
+
+ gtk_label_set_mnemonic_widget (GTK_LABEL (GET_WIDGET ("filters_label")), data->list_view);
+ gtk_label_set_use_underline (GTK_LABEL (GET_WIDGET ("filters_label")), TRUE);
+
+ update_filter_list (data);
+ update_sensitivity (data);
+
+ /* Set the signals handlers. */
+
+ g_signal_connect (G_OBJECT (data->dialog),
+ "destroy",
+ G_CALLBACK (destroy_cb),
+ data);
+ g_signal_connect_swapped (G_OBJECT (GET_WIDGET ("close_button")),
+ "clicked",
+ G_CALLBACK (gtk_widget_destroy),
+ G_OBJECT (data->dialog));
+ g_signal_connect (G_OBJECT (GET_WIDGET ("new_button")),
+ "clicked",
+ G_CALLBACK (new_filter_cb),
+ data);
+ g_signal_connect (G_OBJECT (GET_WIDGET ("edit_button")),
+ "clicked",
+ G_CALLBACK (edit_filter_cb),
+ data);
+ g_signal_connect (G_OBJECT (GET_WIDGET ("delete_button")),
+ "clicked",
+ G_CALLBACK (delete_filter_cb),
+ data);
+ g_signal_connect (gtk_tree_view_get_selection (GTK_TREE_VIEW (data->list_view)),
+ "changed",
+ G_CALLBACK (list_view_selection_changed_cb),
+ data);
+ g_signal_connect (data->list_store,
+ "row-deleted",
+ G_CALLBACK (row_deleted_cb),
+ data);
+ g_signal_connect (data->list_store,
+ "row-inserted",
+ G_CALLBACK (row_inserted_cb),
+ data);
+ g_signal_connect (G_OBJECT (data->general_filter_combobox),
+ "changed",
+ G_CALLBACK (general_filter_changed_cb),
+ data);
+
+ data->filters_changed_id = g_signal_connect (gth_main_get_default_monitor (),
+ "filters-changed",
+ G_CALLBACK (filters_changed_cb),
+ data);
+
+ /* run dialog. */
+
+ gtk_window_set_transient_for (GTK_WINDOW (data->dialog),
+ GTK_WINDOW (browser));
+ gtk_window_set_modal (GTK_WINDOW (data->dialog), FALSE);
+ gtk_widget_show (data->dialog);
+}
diff --git a/gthumb/dlg-personalize-filters.h b/gthumb/dlg-personalize-filters.h
new file mode 100644
index 0000000..c0b4cf0
--- /dev/null
+++ b/gthumb/dlg-personalize-filters.h
@@ -0,0 +1,30 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2008 The Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef DLG_PERSONALIZE_FILTERS_H
+#define DLG_PERSONALIZE_FILTERS_H
+
+#include "gth-browser.h"
+
+void dlg_personalize_filters (GthBrowser *browser);
+
+#endif /* DLG_PERSONALIZE_FILTERS_H */
diff --git a/gthumb/dlg-preferences.c b/gthumb/dlg-preferences.c
new file mode 100644
index 0000000..fc18184
--- /dev/null
+++ b/gthumb/dlg-preferences.c
@@ -0,0 +1,303 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2001-2008 The Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <gtk/gtk.h>
+#include "dlg-preferences.h"
+#include "gconf-utils.h"
+#include "gth-browser.h"
+#include "gth-enum-types.h"
+#include "gth-file-source-vfs.h"
+#include "gth-main.h"
+#include "gth-preferences.h"
+#include "gtk-utils.h"
+#include "typedefs.h"
+
+
+#define GET_WIDGET(name) _gtk_builder_get_widget (data->builder, (name))
+
+typedef struct {
+ GthBrowser *browser;
+ GtkBuilder *builder;
+ GtkWidget *dialog;
+ GtkWidget *toolbar_style_combobox;
+ GtkWidget *thumbnail_size_combobox;
+} DialogData;
+
+static int thumb_size[] = { 48, 64, 85, 95, 112, 128, 164, 200, 256 };
+static int thumb_sizes = sizeof (thumb_size) / sizeof (int);
+
+
+static int
+get_idx_from_size (gint size)
+{
+ int i;
+
+ for (i = 0; i < thumb_sizes; i++)
+ if (size == thumb_size[i])
+ return i;
+ return -1;
+}
+
+
+static void
+destroy_cb (GtkWidget *widget,
+ DialogData *data)
+{
+ gth_browser_set_dialog (data->browser, "preferences", NULL);
+ g_object_unref (data->builder);
+ g_free (data);
+}
+
+
+/* called when the "apply" button is clicked. */
+static void
+apply_cb (GtkWidget *widget,
+ DialogData *data)
+{
+ /* Startup dir. */
+
+ eel_gconf_set_boolean (PREF_GO_TO_LAST_LOCATION, gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (GET_WIDGET ("go_to_last_location_radiobutton"))));
+ eel_gconf_set_boolean (PREF_USE_STARTUP_LOCATION, gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (GET_WIDGET ("use_startup_location_radiobutton"))));
+
+ if (eel_gconf_get_boolean (PREF_USE_STARTUP_LOCATION, FALSE)) {
+ char *location;
+
+ location = gtk_file_chooser_get_uri (GTK_FILE_CHOOSER (GET_WIDGET ("startup_dir_filechooserbutton")));
+ eel_gconf_set_path (PREF_STARTUP_LOCATION, location);
+ gth_pref_set_startup_location (location);
+ g_free (location);
+ }
+
+ gth_hook_invoke ("dlg-preferences-apply", data->dialog, data->browser, data->builder);
+}
+
+
+/* called when the "close" button is clicked. */
+static void
+close_cb (GtkWidget *widget,
+ DialogData *data)
+{
+ apply_cb (widget, data);
+ gtk_widget_destroy (data->dialog);
+}
+
+
+/* called when the "help" button is clicked. */
+static void
+help_cb (GtkWidget *widget,
+ DialogData *data)
+{
+ show_help_dialog (GTK_WINDOW (data->dialog), "preferences");
+}
+
+
+static void
+use_startup_toggled_cb (GtkWidget *widget,
+ DialogData *data)
+{
+ gtk_widget_set_sensitive (GET_WIDGET ("startup_dir_filechooserbutton"), gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)));
+ gtk_widget_set_sensitive (GET_WIDGET ("set_to_current_button"), gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)));
+}
+
+
+static void
+set_to_current_cb (GtkWidget *widget,
+ DialogData *data)
+{
+ GthFileSource *file_source;
+
+ file_source = gth_main_get_file_source (gth_browser_get_location (data->browser));
+ if (GTH_IS_FILE_SOURCE_VFS (file_source)) {
+ GFile *gio_file;
+ char *uri;
+
+ gio_file = gth_file_source_to_gio_file (file_source, gth_browser_get_location (data->browser));
+ uri = g_file_get_uri (gio_file);
+ gtk_file_chooser_set_current_folder_uri (GTK_FILE_CHOOSER (GET_WIDGET ("startup_dir_filechooserbutton")), uri);
+
+ g_free (uri);
+ g_object_unref (gio_file);
+ }
+ g_object_unref (file_source);
+}
+
+
+static void
+toolbar_style_changed_cb (GtkOptionMenu *option_menu,
+ DialogData *data)
+{
+ eel_gconf_set_enum (PREF_UI_TOOLBAR_STYLE, GTH_TYPE_TOOLBAR_STYLE, gtk_combo_box_get_active (GTK_COMBO_BOX (data->toolbar_style_combobox)));
+}
+
+
+static void
+ask_to_save_toggled_cb (GtkToggleButton *button,
+ DialogData *data)
+{
+ eel_gconf_set_boolean (PREF_MSG_SAVE_MODIFIED_IMAGE, gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (GET_WIDGET ("ask_to_save_checkbutton"))));
+}
+
+
+static void
+thumbs_size_changed_cb (GtkOptionMenu *option_menu,
+ DialogData *data)
+{
+ eel_gconf_set_integer (PREF_THUMBNAIL_SIZE, thumb_size[gtk_combo_box_get_active (GTK_COMBO_BOX (data->thumbnail_size_combobox))]);
+}
+
+
+static void
+fast_file_type_toggled_cb (GtkToggleButton *button,
+ DialogData *data)
+{
+ eel_gconf_set_boolean (PREF_FAST_FILE_TYPE, ! gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (GET_WIDGET ("slow_mime_type_checkbutton"))));
+}
+
+
+void
+dlg_preferences (GthBrowser *browser)
+{
+ DialogData *data;
+ char *startup_location;
+ GthFileSource *file_source;
+
+ if (gth_browser_get_dialog (browser, "preferences") != NULL) {
+ gtk_window_present (GTK_WINDOW (gth_browser_get_dialog (browser, "preferences")));
+ return;
+ }
+
+ data = g_new0 (DialogData, 1);
+ data->browser = browser;
+ data->builder = _gtk_builder_new_from_file ("preferences.ui", NULL);
+ data->dialog = GET_WIDGET ("preferences_dialog");
+
+ gth_browser_set_dialog (browser, "preferences", data->dialog);
+
+ eel_gconf_preload_cache ("/apps/gthumb/browser", GCONF_CLIENT_PRELOAD_ONELEVEL);
+ eel_gconf_preload_cache ("/apps/gthumb/ui", GCONF_CLIENT_PRELOAD_ONELEVEL);
+
+ /* Set widgets data. */
+
+ data->toolbar_style_combobox = _gtk_combo_box_new_with_texts (_("System settings"), _("Text below icons"), _("Text beside icons"), _("Icons only"), _("Text only"), NULL);
+ data->thumbnail_size_combobox = _gtk_combo_box_new_with_texts (_("48"), _("64"), _("85"), _("95"), _("112"), _("128"), _("164"), _("200"), _("256"), NULL);
+
+ gtk_widget_show (data->toolbar_style_combobox);
+ gtk_widget_show (data->thumbnail_size_combobox);
+
+ gtk_box_pack_start (GTK_BOX (GET_WIDGET ("toolbar_style_combobox_box")), data->toolbar_style_combobox, FALSE, FALSE, 0);
+ gtk_box_pack_start (GTK_BOX (GET_WIDGET ("thumbnail_size_combobox_box")), data->thumbnail_size_combobox, FALSE, FALSE, 0);
+
+ /* * general */
+
+ if (eel_gconf_get_boolean (PREF_USE_STARTUP_LOCATION, FALSE))
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (GET_WIDGET ("use_startup_location_radiobutton")), TRUE);
+ else if (eel_gconf_get_boolean (PREF_GO_TO_LAST_LOCATION, TRUE))
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (GET_WIDGET ("go_to_last_location_radiobutton")), TRUE);
+ else
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (GET_WIDGET ("current_location_radiobutton")), TRUE);
+
+ if (! eel_gconf_get_boolean (PREF_USE_STARTUP_LOCATION, FALSE)) {
+ gtk_widget_set_sensitive (GET_WIDGET ("startup_dir_filechooserbutton"), FALSE);
+ gtk_widget_set_sensitive (GET_WIDGET ("set_to_current_button"), FALSE);
+ }
+
+ startup_location = eel_gconf_get_path (PREF_STARTUP_LOCATION, NULL);
+ file_source = gth_main_get_file_source_for_uri (startup_location);
+ if (GTH_IS_FILE_SOURCE_VFS (file_source)) {
+ GFile *location;
+ GFile *folder;
+ char *folder_uri;
+
+ location = g_file_new_for_uri (startup_location);
+ folder = gth_file_source_to_gio_file (file_source, location);
+ folder_uri = g_file_get_uri (folder);
+ gtk_file_chooser_set_current_folder_uri (GTK_FILE_CHOOSER (GET_WIDGET ("startup_dir_filechooserbutton")), folder_uri);
+
+ g_free (folder_uri);
+ g_object_unref (folder);
+ g_object_unref (location);
+ }
+ g_object_unref (file_source);
+ g_free (startup_location);
+
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (GET_WIDGET ("ask_to_save_checkbutton")), eel_gconf_get_boolean (PREF_MSG_SAVE_MODIFIED_IMAGE, DEFAULT_MSG_SAVE_MODIFIED_IMAGE));
+ gtk_combo_box_set_active (GTK_COMBO_BOX (data->toolbar_style_combobox), eel_gconf_get_enum (PREF_UI_TOOLBAR_STYLE, GTH_TYPE_TOOLBAR_STYLE, GTH_TOOLBAR_STYLE_SYSTEM));
+
+ /* * browser */
+
+ gtk_combo_box_set_active (GTK_COMBO_BOX (data->thumbnail_size_combobox), get_idx_from_size (eel_gconf_get_integer (PREF_THUMBNAIL_SIZE, DEFAULT_THUMBNAIL_SIZE)));
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (GET_WIDGET ("slow_mime_type_checkbutton")), ! eel_gconf_get_boolean (PREF_FAST_FILE_TYPE, DEFAULT_FAST_FILE_TYPE));
+
+ gth_hook_invoke ("dlg-preferences-construct", data->dialog, data->browser, data->builder);
+
+ /* Set the signals handlers. */
+
+ g_signal_connect (G_OBJECT (data->dialog),
+ "destroy",
+ G_CALLBACK (destroy_cb),
+ data);
+ g_signal_connect (G_OBJECT (GET_WIDGET ("close_button")),
+ "clicked",
+ G_CALLBACK (close_cb),
+ data);
+ g_signal_connect (G_OBJECT (GET_WIDGET ("help_button")),
+ "clicked",
+ G_CALLBACK (help_cb),
+ data);
+
+ /* general */
+
+ g_signal_connect (G_OBJECT (data->toolbar_style_combobox),
+ "changed",
+ G_CALLBACK (toolbar_style_changed_cb),
+ data);
+ g_signal_connect (G_OBJECT (GET_WIDGET ("use_startup_location_radiobutton")),
+ "toggled",
+ G_CALLBACK (use_startup_toggled_cb),
+ data);
+ g_signal_connect (G_OBJECT (GET_WIDGET ("set_to_current_button")),
+ "clicked",
+ G_CALLBACK (set_to_current_cb),
+ data);
+ g_signal_connect (G_OBJECT (GET_WIDGET ("ask_to_save_checkbutton")),
+ "toggled",
+ G_CALLBACK (ask_to_save_toggled_cb),
+ data);
+
+ /* browser */
+
+ g_signal_connect (G_OBJECT (data->thumbnail_size_combobox),
+ "changed",
+ G_CALLBACK (thumbs_size_changed_cb),
+ data);
+ g_signal_connect (G_OBJECT (GET_WIDGET ("slow_mime_type_checkbutton")),
+ "toggled",
+ G_CALLBACK (fast_file_type_toggled_cb),
+ data);
+
+ /* run dialog. */
+
+ gtk_window_set_transient_for (GTK_WINDOW (data->dialog), GTK_WINDOW (browser));
+ gtk_window_set_modal (GTK_WINDOW (data->dialog), FALSE);
+ gtk_widget_show (data->dialog);
+}
diff --git a/gthumb/dlg-preferences.h b/gthumb/dlg-preferences.h
new file mode 100644
index 0000000..3bb0650
--- /dev/null
+++ b/gthumb/dlg-preferences.h
@@ -0,0 +1,30 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2001-2008 The Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef DLG_PREFERENCES_H
+#define DLG_PREFERENCES_H
+
+#include "gth-browser.h"
+
+void dlg_preferences (GthBrowser *browser);
+
+#endif /* DLG_PREFERENCES_H */
diff --git a/gthumb/dlg-sort-order.c b/gthumb/dlg-sort-order.c
new file mode 100644
index 0000000..92a46d8
--- /dev/null
+++ b/gthumb/dlg-sort-order.c
@@ -0,0 +1,168 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 The Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <gtk/gtk.h>
+#include "gth-browser.h"
+#include "gth-main.h"
+#include "gtk-utils.h"
+
+
+#define GET_WIDGET(name) _gtk_builder_get_widget (data->builder, (name))
+
+
+enum {
+ SELECTION_COLUMN_DATA,
+ SELECTION_COLUMN_NAME
+};
+
+
+typedef struct {
+ GthBrowser *browser;
+ GtkBuilder *builder;
+ GtkWidget *dialog;
+ GtkWidget *sort_by_combobox;
+} DialogData;
+
+
+/* called when the main dialog is closed. */
+static void
+destroy_cb (GtkWidget *widget,
+ DialogData *data)
+{
+ gth_browser_set_dialog (data->browser, "sort-order", NULL);
+ g_object_unref (data->builder);
+ g_free (data);
+}
+
+
+static void
+apply_sort_order (GtkWidget *widget,
+ DialogData *data)
+{
+ GtkTreeIter iter;
+ GtkTreeModel *tree_model;
+ GthFileDataSort *sort_type;
+
+ if (! gtk_combo_box_get_active_iter (GTK_COMBO_BOX (data->sort_by_combobox), &iter))
+ return;
+
+ tree_model = gtk_combo_box_get_model (GTK_COMBO_BOX (data->sort_by_combobox));
+ gtk_tree_model_get (tree_model, &iter, SELECTION_COLUMN_DATA, &sort_type, -1);
+
+ gth_browser_set_sort_order (data->browser,
+ sort_type,
+ gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (GET_WIDGET ("inverse_checkbutton"))));
+}
+
+
+void
+dlg_sort_order (GthBrowser *browser)
+{
+ DialogData *data;
+ GtkListStore *selection_model;
+ GtkCellRenderer *renderer;
+ GList *scan;
+ GthFileDataSort *current_sort_type;
+ gboolean sort_inverse;
+ int i, i_active;
+
+ if (gth_browser_get_dialog (browser, "sort-order") != NULL) {
+ gtk_window_present (GTK_WINDOW (gth_browser_get_dialog (browser, "sort-order")));
+ return;
+ }
+
+ data = g_new0 (DialogData, 1);
+ data->browser = browser;
+ data->builder = _gtk_builder_new_from_file ("sort-order.ui", NULL);
+
+ /* Get the widgets. */
+
+ data->dialog = _gtk_builder_get_widget (data->builder, "sort_order_dialog");
+ gth_browser_set_dialog (browser, "sort-order", data->dialog);
+ g_object_set_data (G_OBJECT (data->dialog), "dialog_data", data);
+
+ /* Set widgets data. */
+
+ selection_model = gtk_list_store_new (2, G_TYPE_POINTER, G_TYPE_STRING);
+ data->sort_by_combobox = gtk_combo_box_new_with_model (GTK_TREE_MODEL (selection_model));
+ g_object_unref (selection_model);
+
+ renderer = gtk_cell_renderer_text_new ();
+ gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (data->sort_by_combobox),
+ renderer,
+ TRUE);
+ gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (data->sort_by_combobox),
+ renderer,
+ "text", SELECTION_COLUMN_NAME,
+ NULL);
+
+ gth_browser_get_sort_order (data->browser, ¤t_sort_type, &sort_inverse);
+
+ for (i = 0, i_active = 0, scan = gth_main_get_all_sort_types (); scan; scan = scan->next, i++) {
+ GthFileDataSort *sort_type = scan->data;
+ GtkTreeIter iter;
+
+ if (strcmp (sort_type->name, current_sort_type->name) == 0)
+ i_active = i;
+
+ gtk_list_store_append (selection_model, &iter);
+ gtk_list_store_set (selection_model, &iter,
+ SELECTION_COLUMN_DATA, sort_type,
+ SELECTION_COLUMN_NAME, sort_type->display_name,
+ -1);
+ }
+ gtk_combo_box_set_active (GTK_COMBO_BOX (data->sort_by_combobox), i_active);
+ gtk_widget_show (data->sort_by_combobox);
+ gtk_container_add (GTK_CONTAINER (GET_WIDGET ("sort_by_hbox")), data->sort_by_combobox);
+
+ gtk_label_set_use_underline (GTK_LABEL (GET_WIDGET ("sort_by_label")), TRUE);
+ gtk_label_set_mnemonic_widget (GTK_LABEL (GET_WIDGET ("sort_by_label")), data->sort_by_combobox);
+
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (GET_WIDGET ("inverse_checkbutton")), sort_inverse);
+
+ /* Set the signals handlers. */
+
+ g_signal_connect (G_OBJECT (data->dialog),
+ "destroy",
+ G_CALLBACK (destroy_cb),
+ data);
+ g_signal_connect_swapped (GET_WIDGET ("close_button"),
+ "clicked",
+ G_CALLBACK (gtk_widget_destroy),
+ G_OBJECT (data->dialog));
+ g_signal_connect (GET_WIDGET ("inverse_checkbutton"),
+ "toggled",
+ G_CALLBACK (apply_sort_order),
+ data);
+ g_signal_connect (data->sort_by_combobox,
+ "changed",
+ G_CALLBACK (apply_sort_order),
+ data);
+
+ /* run dialog. */
+
+ gtk_window_set_transient_for (GTK_WINDOW (data->dialog),
+ GTK_WINDOW (browser));
+ gtk_window_set_modal (GTK_WINDOW (data->dialog), FALSE);
+ gtk_widget_show (data->dialog);
+}
diff --git a/gthumb/dlg-sort-order.h b/gthumb/dlg-sort-order.h
new file mode 100644
index 0000000..fa6076a
--- /dev/null
+++ b/gthumb/dlg-sort-order.h
@@ -0,0 +1,30 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 The Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef DLG_SORT_ORDER_H
+#define DLG_SORT_ORDER_H
+
+#include "gth-browser.h"
+
+void dlg_sort_order (GthBrowser *browser);
+
+#endif /* DLG_SORT_ORDER_H */
diff --git a/gthumb/dom.c b/gthumb/dom.c
new file mode 100644
index 0000000..1138738
--- /dev/null
+++ b/gthumb/dom.c
@@ -0,0 +1,775 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2008 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <string.h>
+#include "dom.h"
+
+#define XML_DOCUMENT_TAG_NAME ("#document")
+#define XML_TEXT_NODE_TAG_NAME ("#text")
+#define XML_TAB_WIDTH (2)
+#define XML_RC ("\n")
+
+
+struct _DomDocumentPrivate {
+ GQueue *open_nodes;
+};
+
+
+static gpointer dom_element_parent_class = NULL;
+static gpointer dom_text_node_parent_class = NULL;
+static gpointer dom_document_parent_class = NULL;
+
+
+GQuark
+dom_error_quark (void)
+{
+ return g_quark_from_static_string ("dom-error-quark");
+}
+
+
+static void
+_g_list_free_g_object_unref (GList *self)
+{
+ g_list_foreach (self, ((GFunc) (g_object_unref)), NULL);
+ g_list_free (self);
+}
+
+
+static void
+_g_string_append_c_n_times (GString *string,
+ char c,
+ int times)
+{
+ int i;
+
+ for (i = 1; i <= times; i++)
+ g_string_append_c (string, c);
+}
+
+
+static char *
+_g_xml_attribute_quote (char *value)
+{
+ char *escaped;
+ GString *dest;
+ const char *p;
+
+ g_return_val_if_fail (value != NULL, NULL);
+
+ escaped = g_markup_escape_text (value, -1);
+
+ dest = g_string_new ("\"");
+ for (p = escaped; (*p); p++) {
+ if (*p == '"')
+ g_string_append (dest, "\\\"");
+ else
+ g_string_append_c (dest, *p);
+ }
+ g_string_append_c (dest, '"');
+
+ g_free (escaped);
+
+ return g_string_free (dest, FALSE);
+}
+
+
+static gboolean
+_g_xml_tag_is_special (const char *tag_name)
+{
+ return (*tag_name == '#');
+}
+
+
+/* -- DomElement -- */
+
+
+static void
+dom_attribute_dump (gpointer key,
+ gpointer value,
+ gpointer user_data)
+{
+ GString *xml = user_data;
+ char *quoted_value;
+
+ g_string_append_c (xml, ' ');
+ g_string_append (xml, key);
+ g_string_append_c (xml, '=');
+ quoted_value = _g_xml_attribute_quote (value);
+ g_string_append (xml, quoted_value);
+ g_free (quoted_value);
+}
+
+
+static char *
+dom_element_dump (DomElement *self,
+ int level)
+{
+ return DOM_ELEMENT_GET_CLASS (self)->dump (self, level);
+}
+
+
+static char *
+dom_element_real_dump (DomElement *self,
+ int level)
+{
+ GString *xml;
+ GList *scan;
+
+ xml = g_string_new ("");
+
+ if ((self->parent_node != NULL) && (self != self->parent_node->first_child))
+ g_string_append (xml, XML_RC);
+
+ /* start tag */
+
+ if (! _g_xml_tag_is_special (self->tag_name)) {
+ _g_string_append_c_n_times (xml, ' ', level * XML_TAB_WIDTH);
+ g_string_append_c (xml, '<');
+ g_string_append (xml, self->tag_name);
+ g_hash_table_foreach (self->attributes, (GHFunc) dom_attribute_dump, xml);
+ if (self->child_nodes != NULL) {
+ g_string_append_c (xml, '>');
+ if (! DOM_IS_TEXT_NODE (self->first_child))
+ g_string_append (xml, XML_RC);
+ }
+ else {
+ g_string_append (xml, "/>");
+ return g_string_free (xml, FALSE);
+ }
+ }
+
+ /* tag children */
+
+ for (scan = self->child_nodes; scan != NULL; scan = scan->next) {
+ DomElement *child = scan->data;
+ char *child_dump;
+
+ child_dump = dom_element_dump (child, level + 1);
+ g_string_append (xml, child_dump);
+ g_free (child_dump);
+ }
+
+ /* end tag */
+
+ if (! _g_xml_tag_is_special (self->tag_name)) {
+ if ((self->child_nodes != NULL) && ! DOM_IS_TEXT_NODE (self->first_child)) {
+ g_string_append (xml, XML_RC);
+ _g_string_append_c_n_times (xml, ' ', level * XML_TAB_WIDTH);
+ }
+ g_string_append (xml, "</");
+ g_string_append (xml, self->tag_name);
+ g_string_append_c (xml, '>');
+ }
+
+ return g_string_free (xml, FALSE);
+}
+
+
+static void
+dom_element_instance_init (DomElement *self)
+{
+ self->tag_name = NULL;
+ self->prefix = NULL;
+ self->attributes = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ (GDestroyNotify) g_free,
+ (GDestroyNotify )g_free);
+ self->next_sibling = NULL;
+ self->previous_sibling = NULL;
+ self->parent_node = NULL;
+ self->child_nodes = NULL;
+ self->first_child = NULL;
+ self->last_child = NULL;
+}
+
+
+static void
+dom_element_finalize (GObject *obj)
+{
+ DomElement *self;
+
+ self = DOM_ELEMENT (obj);
+ g_free (self->tag_name);
+ g_free (self->prefix);
+ g_hash_table_unref (self->attributes);
+ _g_list_free_g_object_unref (self->child_nodes);
+
+ G_OBJECT_CLASS (dom_element_parent_class)->finalize (obj);
+}
+
+
+static void
+dom_element_class_init (DomElementClass *klass)
+{
+ dom_element_parent_class = g_type_class_peek_parent (klass);
+ G_OBJECT_CLASS (klass)->finalize = dom_element_finalize;
+ DOM_ELEMENT_CLASS (klass)->dump = dom_element_real_dump;
+}
+
+
+GType
+dom_element_get_type (void)
+{
+ static GType dom_element_type_id = 0;
+ if (dom_element_type_id == 0) {
+ static const GTypeInfo g_define_type_info = {
+ sizeof (DomElementClass),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) dom_element_class_init,
+ (GClassFinalizeFunc) NULL,
+ NULL,
+ sizeof (DomElement),
+ 0,
+ (GInstanceInitFunc) dom_element_instance_init,
+ NULL
+ };
+ dom_element_type_id = g_type_register_static (G_TYPE_INITIALLY_UNOWNED,
+ "DomElement",
+ &g_define_type_info,
+ 0);
+ }
+ return dom_element_type_id;
+}
+
+
+static DomElement *
+dom_element_new (const char *a_tag_name)
+{
+ DomElement *self;
+
+ g_return_val_if_fail (a_tag_name != NULL, NULL);
+
+ self = g_object_newv (DOM_TYPE_ELEMENT, 0, NULL);
+ self->tag_name = (a_tag_name == NULL) ? NULL : g_strdup (a_tag_name);
+
+ return self;
+}
+
+
+void
+dom_element_append_child (DomElement *self,
+ DomElement *child)
+{
+ GList *child_link;
+
+ g_return_if_fail (DOM_IS_ELEMENT (self));
+ g_return_if_fail (DOM_IS_ELEMENT (child));
+
+ self->child_nodes = g_list_append (self->child_nodes, g_object_ref_sink (child));
+
+ /* update child attributes */
+
+ child_link = g_list_find (self->child_nodes, child);
+ child->parent_node = self;
+ if (child_link->prev != NULL) {
+ DomElement *prev_sibling;
+
+ prev_sibling = child_link->prev->data;
+ child->previous_sibling = prev_sibling;
+ prev_sibling->next_sibling = child;
+ }
+ else
+ child->previous_sibling = NULL;
+ child->next_sibling = NULL;
+
+ /* update self attributes */
+
+ self->first_child = (self->first_child == NULL) ? child_link->data : self->first_child;
+ self->last_child = child;
+}
+
+
+const char *
+dom_element_get_attribute (DomElement *self,
+ const char *name)
+{
+ g_return_val_if_fail (DOM_IS_ELEMENT (self), NULL);
+ g_return_val_if_fail (name != NULL, NULL);
+
+ return (const char*) g_hash_table_lookup (self->attributes, name);
+}
+
+
+gboolean
+dom_element_has_attribute (DomElement *self,
+ const char *name)
+{
+ g_return_val_if_fail (DOM_IS_ELEMENT (self), FALSE);
+ g_return_val_if_fail (name != NULL, FALSE);
+
+ return ((const char*) (g_hash_table_lookup (self->attributes, name))) != NULL;
+}
+
+
+gboolean
+dom_element_has_child_nodes (DomElement *self)
+{
+ g_return_val_if_fail (DOM_IS_ELEMENT (self), FALSE);
+
+ return self->child_nodes != NULL;
+}
+
+
+void
+dom_element_remove_attribute (DomElement *self,
+ const char *name)
+{
+ g_return_if_fail (DOM_IS_ELEMENT (self));
+ g_return_if_fail (name != NULL);
+
+ g_hash_table_remove (self->attributes, name);
+}
+
+
+DomElement *
+dom_element_remove_child (DomElement *self,
+ DomElement *node)
+{
+ g_return_val_if_fail (DOM_IS_ELEMENT (self), NULL);
+ g_return_val_if_fail (DOM_IS_ELEMENT (node), NULL);
+
+ self->child_nodes = g_list_remove (self->child_nodes, node);
+ return node;
+}
+
+
+void
+dom_element_replace_child (DomElement *self,
+ DomElement *new_child,
+ DomElement *old_child)
+{
+ GList *link;
+
+ g_return_if_fail (DOM_IS_ELEMENT (self));
+ g_return_if_fail (DOM_IS_ELEMENT (new_child));
+ g_return_if_fail (DOM_IS_ELEMENT (old_child));
+
+ link = g_list_find (self->child_nodes, old_child);
+ if (link == NULL)
+ return;
+ g_object_unref (link->data);
+ link->data = g_object_ref (new_child);
+}
+
+
+void
+dom_element_set_attribute (DomElement *self,
+ const char *name,
+ const char *value)
+{
+ g_return_if_fail (DOM_IS_ELEMENT (self));
+ g_return_if_fail (name != NULL);
+ g_return_if_fail (value != NULL);
+
+ g_hash_table_insert (self->attributes, g_strdup (name), g_strdup (value));
+}
+
+
+const char *
+dom_element_get_inner_text (DomElement *self)
+{
+ if (! DOM_IS_TEXT_NODE (self->first_child))
+ return NULL;
+ else
+ return DOM_TEXT_NODE (self->first_child)->data;
+}
+
+
+/* -- DomTextNode -- */
+
+
+static char *
+dom_text_node_real_dump (DomElement *base,
+ int level)
+{
+ DomTextNode *self;
+
+ self = DOM_TEXT_NODE (base);
+ return (self->data == NULL ? g_strdup ("") : g_markup_escape_text (self->data, -1));
+}
+
+
+static void
+dom_text_node_finalize (GObject *obj)
+{
+ DomTextNode *self;
+
+ self = DOM_TEXT_NODE (obj);
+ g_free (self->data);
+
+ G_OBJECT_CLASS (dom_text_node_parent_class)->finalize (obj);
+}
+
+
+static void
+dom_text_node_class_init (DomTextNodeClass *klass)
+{
+ dom_text_node_parent_class = g_type_class_peek_parent (klass);
+ G_OBJECT_CLASS (klass)->finalize = dom_text_node_finalize;
+ DOM_ELEMENT_CLASS (klass)->dump = dom_text_node_real_dump;
+}
+
+
+static void
+dom_text_node_instance_init (DomTextNode *self)
+{
+ DOM_ELEMENT (self)->tag_name = g_strdup (XML_TEXT_NODE_TAG_NAME);
+ self->data = NULL;
+}
+
+
+GType
+dom_text_node_get_type (void)
+{
+ static GType dom_text_node_type_id = 0;
+ if (dom_text_node_type_id == 0) {
+ static const GTypeInfo g_define_type_info = {
+ sizeof (DomTextNodeClass),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) dom_text_node_class_init,
+ (GClassFinalizeFunc) NULL,
+ NULL,
+ sizeof (DomTextNode),
+ 0,
+ (GInstanceInitFunc) dom_text_node_instance_init,
+ NULL
+ };
+ dom_text_node_type_id = g_type_register_static (DOM_TYPE_ELEMENT,
+ "DomTextNode",
+ &g_define_type_info,
+ 0);
+ }
+ return dom_text_node_type_id;
+}
+
+
+static DomTextNode *
+dom_text_node_new (const char *a_data)
+{
+ DomTextNode *self;
+
+ self = g_object_newv (DOM_TYPE_TEXT_NODE, 0, NULL);
+ self->data = (a_data == NULL ? g_strdup ("") : g_strdup (a_data));
+
+ return self;
+}
+
+
+/* -- DomDocument -- */
+
+
+static void
+dom_document_finalize (GObject *obj)
+{
+ DomDocument *self;
+
+ self = DOM_DOCUMENT (obj);
+
+ if (self->priv != NULL) {
+ g_queue_free (self->priv->open_nodes);
+ g_free (self->priv);
+ }
+
+ G_OBJECT_CLASS (dom_document_parent_class)->finalize (obj);
+}
+
+
+static void
+dom_document_class_init (DomDocumentClass *klass)
+{
+ dom_document_parent_class = g_type_class_peek_parent (klass);
+ G_OBJECT_CLASS (klass)->finalize = dom_document_finalize;
+}
+
+
+static void
+dom_document_instance_init (DomDocument *self)
+{
+ DOM_ELEMENT (self)->tag_name = g_strdup (XML_DOCUMENT_TAG_NAME);
+
+ self->priv = g_new0 (DomDocumentPrivate, 1);
+ self->priv->open_nodes = g_queue_new ();
+}
+
+
+GType
+dom_document_get_type (void)
+{
+ static GType dom_document_type_id = 0;
+ if (dom_document_type_id == 0) {
+ static const GTypeInfo g_define_type_info = {
+ sizeof (DomDocumentClass),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) dom_document_class_init,
+ (GClassFinalizeFunc) NULL,
+ NULL,
+ sizeof (DomDocument),
+ 0,
+ (GInstanceInitFunc) dom_document_instance_init,
+ NULL
+ };
+ dom_document_type_id = g_type_register_static (DOM_TYPE_ELEMENT,
+ "DomDocument",
+ &g_define_type_info,
+ 0);
+ }
+ return dom_document_type_id;
+}
+
+
+DomDocument *
+dom_document_new (void)
+{
+ DomDocument *self;
+
+ self = g_object_newv (DOM_TYPE_DOCUMENT, 0, NULL);
+ return g_object_ref_sink (self);
+}
+
+
+DomElement *
+dom_document_create_element (DomDocument *self,
+ const char *tag_name,
+ const char *first_attr,
+ ...)
+{
+ DomElement *element;
+ va_list var_args;
+ const char *attr;
+ const char *value;
+
+ g_return_val_if_fail (DOM_IS_DOCUMENT (self), NULL);
+ g_return_val_if_fail (tag_name != NULL, NULL);
+
+ element = dom_element_new (tag_name);
+ va_start (var_args, first_attr);
+ attr = first_attr;
+ while (attr != NULL) {
+ value = va_arg (var_args, const char *);
+ dom_element_set_attribute (element, attr, value);
+ attr = va_arg (var_args, const char *);
+ }
+ va_end (var_args);
+
+ return element;
+}
+
+
+DomElement *
+dom_document_create_text_node (DomDocument *self,
+ const char *data)
+{
+ g_return_val_if_fail (DOM_IS_DOCUMENT (self), NULL);
+ g_return_val_if_fail (data != NULL, NULL);
+
+ return (DomElement *) dom_text_node_new (data);
+}
+
+
+DomElement *
+dom_document_create_element_with_text (DomDocument *self,
+ const char *text_data,
+ const char *tag_name,
+ const char *first_attr,
+ ...)
+{
+ DomElement *element;
+ va_list var_args;
+ const char *attr;
+ const char *value;
+
+ g_return_val_if_fail (DOM_IS_DOCUMENT (self), NULL);
+ g_return_val_if_fail (tag_name != NULL, NULL);
+
+ element = dom_element_new (tag_name);
+ va_start (var_args, first_attr);
+ attr = first_attr;
+ while (attr != NULL) {
+ value = va_arg (var_args, const char *);
+ dom_element_set_attribute (element, attr, value);
+ attr = va_arg (var_args, const char *);
+ }
+ va_end (var_args);
+
+ if (text_data != NULL)
+ dom_element_append_child (element, dom_document_create_text_node (self, text_data));
+
+ return element;
+}
+
+
+char *
+dom_document_dump (DomDocument *self,
+ gsize *len)
+{
+ GString *xml;
+ char *body;
+
+ xml = g_string_sized_new (4096);
+ g_string_append (xml, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
+ g_string_append (xml, XML_RC);
+
+ body = dom_element_dump (DOM_ELEMENT (self), -1);
+ g_string_append (xml, body);
+ g_free (body);
+
+ if (dom_element_has_child_nodes (DOM_ELEMENT (self)))
+ g_string_append (xml, XML_RC);
+
+ if (len != NULL)
+ *len = xml->len;
+
+ return g_string_free (xml, FALSE);
+}
+
+
+static void
+start_element_cb (GMarkupParseContext *context,
+ const char *element_name,
+ const char **attribute_names,
+ const char **attribute_values,
+ gpointer user_data,
+ GError **error)
+{
+ DomDocument *doc = user_data;
+ DomElement *node;
+ int i;
+
+ node = dom_document_create_element (doc, element_name, NULL);
+ for (i = 0; attribute_names[i]; i++)
+ dom_element_set_attribute (node, attribute_names[i], attribute_values[i]);
+ dom_element_append_child (DOM_ELEMENT (g_queue_peek_head (doc->priv->open_nodes)), node);
+
+ g_queue_push_head (doc->priv->open_nodes, node);
+}
+
+
+static void
+end_element_cb (GMarkupParseContext *context,
+ const char *element_name,
+ gpointer user_data,
+ GError **error)
+{
+ DomDocument *doc = user_data;
+
+ g_queue_pop_head (doc->priv->open_nodes);
+}
+
+
+static void
+text_cb (GMarkupParseContext *context,
+ const char *text,
+ gsize text_len,
+ gpointer user_data,
+ GError **error)
+{
+ DomDocument *doc = user_data;
+ char *data;
+
+ data = g_strndup (text, text_len);
+ dom_element_append_child (DOM_ELEMENT (g_queue_peek_head (doc->priv->open_nodes)),
+ dom_document_create_text_node (doc, data));
+ g_free (data);
+}
+
+
+static const GMarkupParser markup_parser = {
+ start_element_cb, /* start_element */
+ end_element_cb, /* end_element */
+ text_cb, /* text */
+ NULL, /* passthrough */
+ NULL /* error */
+};
+
+
+gboolean
+dom_document_load (DomDocument *self,
+ const char *xml,
+ gssize len,
+ GError **error)
+{
+ GMarkupParseContext *context;
+
+ g_return_val_if_fail (DOM_IS_DOCUMENT (self), FALSE);
+ g_return_val_if_fail (xml != NULL, FALSE);
+
+ g_queue_clear (self->priv->open_nodes);
+ g_queue_push_head (self->priv->open_nodes, self);
+
+ context = g_markup_parse_context_new (&markup_parser, 0, self, NULL);
+ if (! g_markup_parse_context_parse (context, xml, (len < 0) ? strlen (xml) : len, error))
+ return FALSE;
+ if (! g_markup_parse_context_end_parse (context, error))
+ return FALSE;
+ g_markup_parse_context_free (context);
+
+ return TRUE;
+}
+
+
+/* -- DomDomizable -- */
+
+
+DomElement *
+dom_domizable_create_element (DomDomizable *self,
+ DomDocument *doc)
+{
+ return DOM_DOMIZABLE_GET_INTERFACE (self)->create_element (self, doc);
+}
+
+
+void
+dom_domizable_load_from_element (DomDomizable *self,
+ DomElement *e)
+{
+ DOM_DOMIZABLE_GET_INTERFACE (self)->load_from_element (self, e);
+}
+
+
+GType
+dom_domizable_get_type (void)
+{
+ static GType dom_domizable_type_id = 0;
+ if (dom_domizable_type_id == 0) {
+ static const GTypeInfo g_define_type_info = {
+ sizeof (DomDomizableIface),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) NULL,
+ (GClassFinalizeFunc) NULL,
+ NULL,
+ 0,
+ 0,
+ (GInstanceInitFunc) NULL,
+ NULL
+ };
+ dom_domizable_type_id = g_type_register_static (G_TYPE_INTERFACE,
+ "DomDomizable",
+ &g_define_type_info,
+ 0);
+ }
+ return dom_domizable_type_id;
+}
diff --git a/gthumb/dom.h b/gthumb/dom.h
new file mode 100644
index 0000000..113a065
--- /dev/null
+++ b/gthumb/dom.h
@@ -0,0 +1,181 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2008 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef DOM_H
+#define DOM_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define DOM_ERROR dom_error_quark ()
+typedef enum {
+ DOM_ERROR_FAILED,
+ DOM_ERROR_INVALID_FORMAT
+} DomErrorEnum;
+
+#define DOM_TYPE_ELEMENT (dom_element_get_type ())
+#define DOM_ELEMENT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), DOM_TYPE_ELEMENT, DomElement))
+#define DOM_ELEMENT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), DOM_TYPE_ELEMENT, DomElementClass))
+#define DOM_IS_ELEMENT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), DOM_TYPE_ELEMENT))
+#define DOM_IS_ELEMENT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), DOM_TYPE_ELEMENT))
+#define DOM_ELEMENT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), DOM_TYPE_ELEMENT, DomElementClass))
+
+typedef struct _DomElement DomElement;
+typedef struct _DomElementClass DomElementClass;
+typedef struct _DomElementPrivate DomElementPrivate;
+
+#define DOM_TYPE_TEXT_NODE (dom_text_node_get_type ())
+#define DOM_TEXT_NODE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), DOM_TYPE_TEXT_NODE, DomTextNode))
+#define DOM_TEXT_NODE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), DOM_TYPE_TEXT_NODE, DomTextNodeClass))
+#define DOM_IS_TEXT_NODE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), DOM_TYPE_TEXT_NODE))
+#define DOM_IS_TEXT_NODE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), DOM_TYPE_TEXT_NODE))
+#define DOM_TEXT_NODE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), DOM_TYPE_TEXT_NODE, DomTextNodeClass))
+
+typedef struct _DomTextNode DomTextNode;
+typedef struct _DomTextNodeClass DomTextNodeClass;
+typedef struct _DomTextNodePrivate DomTextNodePrivate;
+
+#define DOM_TYPE_DOCUMENT (dom_document_get_type ())
+#define DOM_DOCUMENT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), DOM_TYPE_DOCUMENT, DomDocument))
+#define DOM_DOCUMENT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), DOM_TYPE_DOCUMENT, DomDocumentClass))
+#define DOM_IS_DOCUMENT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), DOM_TYPE_DOCUMENT))
+#define DOM_IS_DOCUMENT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), DOM_TYPE_DOCUMENT))
+#define DOM_DOCUMENT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), DOM_TYPE_DOCUMENT, DomDocumentClass))
+
+typedef struct _DomDocument DomDocument;
+typedef struct _DomDocumentClass DomDocumentClass;
+typedef struct _DomDocumentPrivate DomDocumentPrivate;
+
+#define DOM_TYPE_DOMIZABLE (dom_domizable_get_type ())
+#define DOM_DOMIZABLE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), DOM_TYPE_DOMIZABLE, DomDomizable))
+#define DOM_IS_DOMIZABLE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), DOM_TYPE_DOMIZABLE))
+#define DOM_DOMIZABLE_GET_INTERFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), DOM_TYPE_DOMIZABLE, DomDomizableIface))
+
+typedef struct _DomDomizable DomDomizable;
+typedef struct _DomDomizableIface DomDomizableIface;
+
+struct _DomElement {
+ GInitiallyUnowned parent_instance;
+ DomElementPrivate *priv;
+
+ char *tag_name;
+ char *prefix;
+ GHashTable *attributes;
+ DomElement *next_sibling;
+ DomElement *previous_sibling;
+ DomElement *parent_node;
+ GList *child_nodes;
+ DomElement *first_child;
+ DomElement *last_child;
+};
+
+struct _DomElementClass {
+ GInitiallyUnownedClass parent_class;
+
+ char * (*dump) (DomElement *self,
+ int level);
+};
+
+struct _DomTextNode {
+ DomElement parent_instance;
+ DomTextNodePrivate *priv;
+
+ char *data;
+};
+
+struct _DomTextNodeClass {
+ DomElementClass parent_class;
+};
+
+struct _DomDocument {
+ DomElement parent_instance;
+ DomDocumentPrivate *priv;
+};
+
+struct _DomDocumentClass {
+ DomElementClass parent_class;
+};
+
+struct _DomDomizableIface {
+ GTypeInterface parent_iface;
+
+ DomElement * (*create_element) (DomDomizable *self,
+ DomDocument *doc);
+ void (*load_from_element) (DomDomizable *self,
+ DomElement *e);
+};
+
+GQuark dom_error_quark (void);
+
+GType dom_element_get_type (void);
+void dom_element_append_child (DomElement *self,
+ DomElement *child);
+const char * dom_element_get_attribute (DomElement *self,
+ const char *name);
+gboolean dom_element_has_attribute (DomElement *self,
+ const char *name);
+gboolean dom_element_has_child_nodes (DomElement *self);
+void dom_element_remove_attribute (DomElement *self,
+ const char *name);
+DomElement * dom_element_remove_child (DomElement *self,
+ DomElement *node);
+void dom_element_replace_child (DomElement *self,
+ DomElement *new_child,
+ DomElement *old_child);
+void dom_element_set_attribute (DomElement *self,
+ const char *name,
+ const char *value);
+const char * dom_element_get_inner_text (DomElement *self);
+
+GType dom_text_node_get_type (void);
+
+GType dom_document_get_type (void);
+DomDocument * dom_document_new (void);
+DomElement * dom_document_create_element (DomDocument *self,
+ const char *tag_name,
+ const char *first_attr,
+ ...);
+DomElement * dom_document_create_text_node (DomDocument *self,
+ const char *data);
+DomElement * dom_document_create_element_with_text (DomDocument *self,
+ const char *text,
+ const char *tag_name,
+ const char *first_attr,
+ ...);
+char * dom_document_dump (DomDocument *self,
+ gsize *len);
+gboolean dom_document_load (DomDocument *self,
+ const char *xml,
+ gssize len,
+ GError **error);
+
+GType dom_domizable_get_type (void);
+DomElement * dom_domizable_create_element (DomDomizable *self,
+ DomDocument *doc);
+void dom_domizable_load_from_element (DomDomizable *self,
+ DomElement *e);
+
+G_END_DECLS
+
+#endif /* DOM_H */
diff --git a/gthumb/egg-macros.h b/gthumb/egg-macros.h
new file mode 100644
index 0000000..2b5718e
--- /dev/null
+++ b/gthumb/egg-macros.h
@@ -0,0 +1,154 @@
+/**
+ * Useful macros.
+ *
+ * Author:
+ * Darin Adler <darin bentspoon com>
+ *
+ * Copyright 2001 Ben Tea Spoons, Inc.
+ */
+#ifndef _EGG_MACROS_H_
+#define _EGG_MACROS_H_
+
+#include <glib/gmacros.h>
+
+G_BEGIN_DECLS
+
+/* Macros for defining classes. Ideas taken from Nautilus and GOB. */
+
+/* Define the boilerplate type stuff to reduce typos and code size. Defines
+ * the get_type method and the parent_class static variable. */
+
+#define EGG_BOILERPLATE(type, type_as_function, corba_type, \
+ parent_type, parent_type_macro, \
+ register_type_macro) \
+static void type_as_function ## _class_init (type ## Class *klass); \
+static void type_as_function ## _instance_init (type *object); \
+static parent_type ## Class *parent_class = NULL; \
+static void \
+type_as_function ## _class_init_trampoline (gpointer klass, \
+ gpointer data) \
+{ \
+ parent_class = (parent_type ## Class *)g_type_class_ref ( \
+ parent_type_macro); \
+ type_as_function ## _class_init ((type ## Class *)klass); \
+} \
+GType \
+type_as_function ## _get_type (void) \
+{ \
+ static GType object_type = 0; \
+ if (object_type == 0) { \
+ static const GTypeInfo object_info = { \
+ sizeof (type ## Class), \
+ NULL, /* base_init */ \
+ NULL, /* base_finalize */ \
+ type_as_function ## _class_init_trampoline, \
+ NULL, /* class_finalize */ \
+ NULL, /* class_data */ \
+ sizeof (type), \
+ 0, /* n_preallocs */ \
+ (GInstanceInitFunc) type_as_function ## _instance_init \
+ }; \
+ object_type = register_type_macro \
+ (type, type_as_function, corba_type, \
+ parent_type, parent_type_macro); \
+ } \
+ return object_type; \
+}
+
+/* Just call the parent handler. This assumes that there is a variable
+ * named parent_class that points to the (duh!) parent class. Note that
+ * this macro is not to be used with things that return something, use
+ * the _WITH_DEFAULT version for that */
+#define EGG_CALL_PARENT(parent_class_cast, name, args) \
+ ((parent_class_cast(parent_class)->name != NULL) ? \
+ parent_class_cast(parent_class)->name args : (void)0)
+
+/* Same as above, but in case there is no implementation, it evaluates
+ * to def_return */
+#define EGG_CALL_PARENT_WITH_DEFAULT(parent_class_cast, \
+ name, args, def_return) \
+ ((parent_class_cast(parent_class)->name != NULL) ? \
+ parent_class_cast(parent_class)->name args : def_return)
+
+/* Call a virtual method */
+#define EGG_CALL_VIRTUAL(object, get_class_cast, method, args) \
+ (get_class_cast (object)->method ? (* get_class_cast (object)->method) args : (void)0)
+
+/* Call a virtual method with default */
+#define EGG_CALL_VIRTUAL_WITH_DEFAULT(object, get_class_cast, method, args, default) \
+ (get_class_cast (object)->method ? (* get_class_cast (object)->method) args : default)
+
+#define EGG_CLASS_BOILERPLATE(type, type_as_function, \
+ parent_type, parent_type_macro) \
+ EGG_BOILERPLATE(type, type_as_function, type, \
+ parent_type, parent_type_macro, \
+ EGG_REGISTER_TYPE)
+
+#define EGG_REGISTER_TYPE(type, type_as_function, corba_type, \
+ parent_type, parent_type_macro) \
+ g_type_register_static (parent_type_macro, #type, &object_info, 0)
+
+
+#define EGG_DEFINE_BOXED_TYPE(TN, t_n) \
+EGG_DEFINE_BOXED_TYPE_WITH_CODE(TN, t_n, {});
+
+#define EGG_DEFINE_BOXED_TYPE_WITH_CODE(TN, t_n, _C_) \
+\
+static gpointer t_n##_copy (gpointer boxed); \
+static void t_n##_free (gpointer boxed); \
+\
+EGG_DEFINE_BOXED_TYPE_EXTENDED(TN, t_n, t_n##_copy, t_n##_free, _C_);
+
+#define EGG_DEFINE_BOXED_TYPE_EXTENDED(TN, t_n, b_c, b_f, _C_) \
+\
+_EGG_DEFINE_BOXED_TYPE_EXTENDED_BEGIN(TN, t_n, b_c, b_f) {_C_;} \
+_EGG_DEFINE_BOXED_TYPE_EXTENDED_END()
+
+#define _EGG_DEFINE_BOXED_TYPE_EXTENDED_BEGIN(TypeName, type_name, boxed_copy, boxed_free) \
+\
+GType \
+type_name##_get_type (void) \
+{ \
+ static volatile gsize g_define_type_id__volatile = 0; \
+ if (g_once_init_enter (&g_define_type_id__volatile)) \
+ { \
+ GType g_define_type_id = \
+ g_boxed_type_register_static (g_intern_static_string (#TypeName), \
+ boxed_copy, boxed_free); \
+ { /* custom code follows */
+#define _EGG_DEFINE_BOXED_TYPE_EXTENDED_END() \
+ /* following custom code */ \
+ } \
+ g_once_init_leave (&g_define_type_id__volatile, g_define_type_id); \
+ } \
+ return g_define_type_id__volatile; \
+} /* closes type_name##_get_type() */
+
+#define EGG_DEFINE_QUARK(QN, q_n) \
+\
+GQuark \
+q_n##_quark (void) \
+{ \
+ static volatile gsize g_define_quark__volatile = 0; \
+ if (g_once_init_enter (&g_define_quark__volatile)) \
+ { \
+ GQuark g_define_quark = g_quark_from_string (#QN); \
+ g_once_init_leave (&g_define_quark__volatile, g_define_quark); \
+ } \
+ return g_define_quark__volatile; \
+}
+
+#define EGG_IS_POSITIVE_RESPONSE(response_id) \
+ ((response_id) == GTK_RESPONSE_ACCEPT || \
+ (response_id) == GTK_RESPONSE_OK || \
+ (response_id) == GTK_RESPONSE_YES || \
+ (response_id) == GTK_RESPONSE_APPLY)
+
+#define EGG_IS_NEGATIVE_RESPONSE(response_id) \
+ ((response_id) == GTK_RESPONSE_REJECT || \
+ (response_id) == GTK_RESPONSE_CANCEL || \
+ (response_id) == GTK_RESPONSE_NO)
+
+G_END_DECLS
+
+#endif /* _EGG_MACROS_H_ */
diff --git a/gthumb/eggfileformatchooser.c b/gthumb/eggfileformatchooser.c
new file mode 100644
index 0000000..5eeecdb
--- /dev/null
+++ b/gthumb/eggfileformatchooser.c
@@ -0,0 +1,1190 @@
+/* EggFileFormatChooser
+ * Copyright (C) 2007 Mathias Hasselmann
+ *
+ * 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.
+ */
+#include "eggfileformatchooser.h"
+#include "egg-macros.h"
+
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include <string.h>
+#include <ctype.h>
+
+typedef struct _EggFileFormatFilterInfo EggFileFormatFilterInfo;
+typedef struct _EggFileFormatSearch EggFileFormatSearch;
+
+enum
+{
+ MODEL_COLUMN_ID,
+ MODEL_COLUMN_NAME,
+ MODEL_COLUMN_ICON,
+ MODEL_COLUMN_EXTENSIONS,
+ MODEL_COLUMN_FILTER,
+ MODEL_COLUMN_DATA,
+ MODEL_COLUMN_DESTROY
+};
+
+enum
+{
+ SIGNAL_SELECTION_CHANGED,
+ SIGNAL_LAST
+};
+
+struct _EggFileFormatChooserPrivate
+{
+ GtkTreeStore *model;
+ GtkTreeSelection *selection;
+ guint idle_hack;
+ guint last_id;
+
+ GtkFileChooser *chooser;
+ GtkFileFilter *all_files;
+ GtkFileFilter *supported_files;
+};
+
+struct _EggFileFormatFilterInfo
+{
+ GHashTable *extension_set;
+ GSList *extension_list;
+ gboolean show_extensions;
+ gchar *name;
+};
+
+struct _EggFileFormatSearch
+{
+ gboolean success;
+ GtkTreeIter iter;
+
+ guint format;
+ const gchar *extension;
+};
+
+static guint signals[SIGNAL_LAST];
+
+G_DEFINE_TYPE (EggFileFormatChooser,
+ egg_file_format_chooser,
+ GTK_TYPE_EXPANDER);
+EGG_DEFINE_QUARK (EggFileFormatFilterInfo,
+ egg_file_format_filter_info);
+
+static EggFileFormatFilterInfo*
+egg_file_format_filter_info_new (const gchar *name,
+ gboolean show_extensions)
+{
+ EggFileFormatFilterInfo *self;
+
+ self = g_new0 (EggFileFormatFilterInfo, 1);
+ self->extension_set = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+ self->show_extensions = show_extensions;
+ self->name = g_strdup (name);
+
+ return self;
+}
+
+static void
+egg_file_format_filter_info_free (gpointer boxed)
+{
+ EggFileFormatFilterInfo *self;
+
+ if (boxed)
+ {
+ self = boxed;
+
+ g_hash_table_unref (self->extension_set);
+ g_slist_foreach (self->extension_list, (GFunc) g_free, NULL);
+ g_slist_free (self->extension_list);
+ g_free (self->name);
+ g_free (self);
+ }
+}
+
+static gboolean
+egg_file_format_filter_find (gpointer key,
+ gpointer value G_GNUC_UNUSED,
+ gpointer data)
+{
+ const GtkFileFilterInfo *info = data;
+ const gchar *pattern = key;
+
+ return g_str_has_suffix (info->filename, pattern + 1);
+}
+
+static gboolean
+egg_file_format_filter_filter (const GtkFileFilterInfo *info,
+ gpointer data)
+{
+ EggFileFormatFilterInfo *self = data;
+
+ return NULL != g_hash_table_find (self->extension_set,
+ egg_file_format_filter_find,
+ (gpointer) info);
+}
+
+static GtkFileFilter*
+egg_file_format_filter_new (const gchar *name,
+ gboolean show_extensions)
+{
+ GtkFileFilter *filter;
+ EggFileFormatFilterInfo *info;
+
+ filter = gtk_file_filter_new ();
+ gtk_file_filter_set_name (filter, name);
+
+ info = egg_file_format_filter_info_new (name, show_extensions);
+
+ gtk_file_filter_add_custom (filter, GTK_FILE_FILTER_FILENAME,
+ egg_file_format_filter_filter,
+ info, NULL);
+ g_object_set_qdata_full (G_OBJECT (filter),
+ egg_file_format_filter_info_quark (),
+ info, egg_file_format_filter_info_free);
+
+ return filter;
+}
+
+static void
+egg_file_format_filter_add_extensions (GtkFileFilter *filter,
+ const gchar *extensions)
+{
+ EggFileFormatFilterInfo *info;
+ GString *filter_name;
+ const gchar *extptr;
+ gchar *pattern;
+ gsize length;
+
+ g_assert (NULL != extensions);
+
+ info = g_object_get_qdata (G_OBJECT (filter),
+ egg_file_format_filter_info_quark ());
+
+ info->extension_list = g_slist_prepend (info->extension_list,
+ g_strdup (extensions));
+
+ if (info->show_extensions)
+ {
+ filter_name = g_string_new (info->name);
+ g_string_append (filter_name, " (");
+ }
+ else
+ filter_name = NULL;
+
+ extptr = extensions;
+ while (*extptr)
+ {
+ length = strcspn (extptr, ",");
+ pattern = g_new (gchar, length + 3);
+
+ memcpy (pattern, "*.", 2);
+ memcpy (pattern + 2, extptr, length);
+ pattern[length + 2] = '\0';
+
+ if (filter_name)
+ {
+ if (extptr != extensions)
+ g_string_append (filter_name, ", ");
+
+ g_string_append (filter_name, pattern);
+ }
+
+ extptr += length;
+
+ if (*extptr)
+ extptr += 2;
+
+ g_hash_table_replace (info->extension_set, pattern, pattern);
+ }
+
+ if (filter_name)
+ {
+ g_string_append (filter_name, ")");
+ gtk_file_filter_set_name (filter, filter_name->str);
+ g_string_free (filter_name, TRUE);
+ }
+}
+
+static void
+selection_changed_cb (GtkTreeSelection *selection,
+ EggFileFormatChooser *self)
+{
+ gchar *label;
+ gchar *name;
+
+ GtkFileFilter *filter;
+ GtkTreeModel *model;
+ GtkTreeIter parent;
+ GtkTreeIter iter;
+
+ if (gtk_tree_selection_get_selected (selection, &model, &iter))
+ {
+ gtk_tree_model_get (model, &iter, MODEL_COLUMN_NAME, &name, -1);
+
+ label = g_strdup_printf (_("File _Format: %s"), name);
+ gtk_expander_set_use_underline (GTK_EXPANDER (self), TRUE);
+ gtk_expander_set_label (GTK_EXPANDER (self), label);
+
+ g_free (name);
+ g_free (label);
+
+ if (self->priv->chooser)
+ {
+ while (gtk_tree_model_iter_parent (model, &parent, &iter))
+ iter = parent;
+
+ gtk_tree_model_get (model, &iter, MODEL_COLUMN_FILTER, &filter, -1);
+ gtk_file_chooser_set_filter (self->priv->chooser, filter);
+ g_object_unref (filter);
+ }
+
+ g_signal_emit (self, signals[SIGNAL_SELECTION_CHANGED], 0);
+ }
+}
+
+/* XXX This hack is needed, as gtk_expander_set_label seems
+ * not to work from egg_file_format_chooser_init */
+static gboolean
+select_default_file_format (gpointer data)
+{
+ EggFileFormatChooser *self = EGG_FILE_FORMAT_CHOOSER (data);
+ egg_file_format_chooser_set_format (self, 0);
+ self->priv->idle_hack = 0;
+ return FALSE;
+}
+
+static gboolean
+find_by_format (GtkTreeModel *model,
+ GtkTreePath *path G_GNUC_UNUSED,
+ GtkTreeIter *iter,
+ gpointer data)
+{
+ EggFileFormatSearch *search = data;
+ guint id;
+
+ gtk_tree_model_get (model, iter, MODEL_COLUMN_ID, &id, -1);
+
+ if (id == search->format)
+ {
+ search->success = TRUE;
+ search->iter = *iter;
+ }
+
+ return search->success;
+}
+
+static gboolean
+find_in_list (gchar *list,
+ const gchar *needle)
+{
+ gchar *saveptr;
+ gchar *token;
+
+ for (token = strtok_r (list, ",", &saveptr); NULL != token;
+ token = strtok_r (NULL, ",", &saveptr))
+ {
+ token = g_strstrip (token);
+
+ if (g_str_equal (needle, token))
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+accept_filename (gchar *extensions,
+ const gchar *filename)
+{
+ const gchar *extptr;
+ gchar *saveptr;
+ gchar *token;
+ gsize length;
+
+ length = strlen (filename);
+
+ for (token = strtok_r (extensions, ",", &saveptr); NULL != token;
+ token = strtok_r (NULL, ",", &saveptr))
+ {
+ token = g_strstrip (token);
+ extptr = filename + length - strlen (token) - 1;
+
+ if (extptr > filename && '.' == *extptr &&
+ !strcmp (extptr + 1, token))
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+find_by_extension (GtkTreeModel *model,
+ GtkTreePath *path G_GNUC_UNUSED,
+ GtkTreeIter *iter,
+ gpointer data)
+{
+ EggFileFormatSearch *search = data;
+
+ gchar *extensions = NULL;
+ guint format = 0;
+
+ gtk_tree_model_get (model, iter,
+ MODEL_COLUMN_EXTENSIONS, &extensions,
+ MODEL_COLUMN_ID, &format,
+ -1);
+
+ if (extensions && find_in_list (extensions, search->extension))
+ {
+ search->format = format;
+ search->success = TRUE;
+ search->iter = *iter;
+ }
+
+ g_free (extensions);
+ return search->success;
+}
+
+static void
+egg_file_format_chooser_init (EggFileFormatChooser *self)
+{
+ GtkWidget *scroller;
+ GtkWidget *view;
+
+ GtkTreeViewColumn *column;
+ GtkCellRenderer *cell;
+ GtkTreeIter iter;
+
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, EGG_TYPE_FILE_FORMAT_CHOOSER,
+ EggFileFormatChooserPrivate);
+
+/* file filters */
+
+ self->priv->all_files = g_object_ref_sink (gtk_file_filter_new ());
+ gtk_file_filter_set_name (self->priv->all_files, _("All Files"));
+ self->priv->supported_files = egg_file_format_filter_new (_("All Supported Files"), FALSE);
+
+/* tree model */
+
+ self->priv->model = gtk_tree_store_new (7, G_TYPE_UINT, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
+ GTK_TYPE_FILE_FILTER, G_TYPE_POINTER, G_TYPE_POINTER);
+
+ gtk_tree_store_append (self->priv->model, &iter, NULL);
+ gtk_tree_store_set (self->priv->model, &iter,
+ MODEL_COLUMN_NAME, _("By Extension"),
+ MODEL_COLUMN_FILTER, self->priv->supported_files,
+ MODEL_COLUMN_ID, 0,
+ -1);
+
+/* tree view */
+
+ view = gtk_tree_view_new_with_model (GTK_TREE_MODEL (self->priv->model));
+ self->priv->selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
+ gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (view), TRUE);
+
+/* file format column */
+
+ column = gtk_tree_view_column_new ();
+ gtk_tree_view_column_set_expand (column, TRUE);
+ gtk_tree_view_column_set_title (column, _("File Format"));
+ gtk_tree_view_append_column (GTK_TREE_VIEW (view), column);
+
+ cell = gtk_cell_renderer_pixbuf_new ();
+ gtk_tree_view_column_pack_start (column, cell, FALSE);
+ gtk_tree_view_column_set_attributes (column, cell,
+ "icon-name", MODEL_COLUMN_ICON,
+ NULL);
+
+ cell = gtk_cell_renderer_text_new ();
+ gtk_tree_view_column_pack_start (column, cell, TRUE);
+ gtk_tree_view_column_set_attributes (column, cell,
+ "text", MODEL_COLUMN_NAME,
+ NULL);
+
+/* extensions column */
+
+ column = gtk_tree_view_column_new_with_attributes (
+ _("Extension(s)"), gtk_cell_renderer_text_new (),
+ "text", MODEL_COLUMN_EXTENSIONS, NULL);
+ gtk_tree_view_column_set_expand (column, FALSE);
+ gtk_tree_view_append_column (GTK_TREE_VIEW (view), column);
+
+/* selection */
+
+ gtk_tree_selection_set_mode (self->priv->selection, GTK_SELECTION_BROWSE);
+ g_signal_connect (self->priv->selection, "changed",
+ G_CALLBACK (selection_changed_cb), self);
+ self->priv->idle_hack = g_idle_add (select_default_file_format, self);
+
+/* scroller */
+
+ scroller = gtk_scrolled_window_new (NULL, NULL);
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scroller),
+ GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
+ gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scroller),
+ GTK_SHADOW_IN);
+ gtk_widget_set_size_request (scroller, -1, 150);
+ gtk_container_add (GTK_CONTAINER (scroller), view);
+ gtk_widget_show_all (scroller);
+
+ gtk_container_add (GTK_CONTAINER (self), scroller);
+}
+
+static void
+reset_model (EggFileFormatChooser *self)
+{
+ GtkTreeModel *model = GTK_TREE_MODEL (self->priv->model);
+ GtkTreeIter iter;
+
+ if (gtk_tree_model_get_iter_first (model, &iter))
+ {
+ do
+ {
+ GDestroyNotify destroy = NULL;
+ gpointer data = NULL;
+
+ gtk_tree_model_get (model, &iter,
+ MODEL_COLUMN_DESTROY, &destroy,
+ MODEL_COLUMN_DATA, &data,
+ -1);
+
+ if (destroy)
+ destroy (data);
+ }
+ while (gtk_tree_model_iter_next (model, &iter));
+ }
+
+ gtk_tree_store_clear (self->priv->model);
+}
+
+static void
+egg_file_format_chooser_dispose (GObject *obj)
+{
+ EggFileFormatChooser *self = EGG_FILE_FORMAT_CHOOSER (obj);
+
+ if (NULL != self)
+ {
+ if (self->priv->idle_hack)
+ {
+ g_source_remove (self->priv->idle_hack);
+ self->priv->idle_hack = 0;
+ }
+ }
+
+ G_OBJECT_CLASS (egg_file_format_chooser_parent_class)->dispose (obj);
+}
+
+static void
+egg_file_format_chooser_finalize (GObject *obj)
+{
+ EggFileFormatChooser *self = EGG_FILE_FORMAT_CHOOSER (obj);
+
+ if (NULL != self)
+ {
+ if (self->priv->model)
+ {
+ reset_model (self);
+
+ g_object_unref (self->priv->model);
+ self->priv->model = NULL;
+
+ g_object_unref (self->priv->all_files);
+ self->priv->all_files = NULL;
+ }
+ }
+
+ G_OBJECT_CLASS (egg_file_format_chooser_parent_class)->finalize (obj);
+}
+
+static void
+filter_changed_cb (GObject *object,
+ GParamSpec *spec,
+ gpointer data)
+{
+ EggFileFormatChooser *self;
+
+ GtkFileFilter *current_filter;
+ GtkFileFilter *format_filter;
+
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ GtkTreeIter parent;
+
+ self = EGG_FILE_FORMAT_CHOOSER (data);
+
+ format_filter = NULL;
+ current_filter = gtk_file_chooser_get_filter (GTK_FILE_CHOOSER (object));
+ model = GTK_TREE_MODEL (self->priv->model);
+
+ if (gtk_tree_selection_get_selected (self->priv->selection, &model, &iter))
+ {
+ while (gtk_tree_model_iter_parent (model, &parent, &iter))
+ iter = parent;
+
+ gtk_tree_model_get (model, &iter,
+ MODEL_COLUMN_FILTER,
+ &format_filter, -1);
+ g_object_unref (format_filter);
+ }
+
+ if (current_filter && current_filter != format_filter &&
+ gtk_tree_model_get_iter_first (model, &iter))
+ {
+ if (current_filter == self->priv->all_files)
+ format_filter = current_filter;
+ else
+ {
+ format_filter = NULL;
+
+ do
+ {
+ gtk_tree_model_get (model, &iter,
+ MODEL_COLUMN_FILTER,
+ &format_filter, -1);
+ g_object_unref (format_filter);
+
+ if (format_filter == current_filter)
+ break;
+ }
+ while (gtk_tree_model_iter_next (model, &iter));
+ }
+
+ if (format_filter)
+ gtk_tree_selection_select_iter (self->priv->selection, &iter);
+ }
+}
+
+/* Shows an error dialog set as transient for the specified window */
+static void
+error_message_with_parent (GtkWindow *parent,
+ const char *msg,
+ const char *detail)
+{
+ gboolean first_call = TRUE;
+ GtkWidget *dialog;
+
+ if (first_call)
+ {
+ g_warning ("%s: Merge with the code in Gtk{File,Recent}ChooserDefault.", G_STRLOC);
+ first_call = FALSE;
+ }
+
+ dialog = gtk_message_dialog_new (parent,
+ GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_ERROR,
+ GTK_BUTTONS_OK,
+ "%s",
+ msg);
+ gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
+ "%s", detail);
+
+ if (parent->group)
+ gtk_window_group_add_window (parent->group, GTK_WINDOW (dialog));
+
+ gtk_dialog_run (GTK_DIALOG (dialog));
+ gtk_widget_destroy (dialog);
+}
+
+/* Returns a toplevel GtkWindow, or NULL if none */
+static GtkWindow *
+get_toplevel (GtkWidget *widget)
+{
+ GtkWidget *toplevel;
+
+ toplevel = gtk_widget_get_toplevel (widget);
+ if (!GTK_WIDGET_TOPLEVEL (toplevel))
+ return NULL;
+ else
+ return GTK_WINDOW (toplevel);
+}
+
+/* Shows an error dialog for the file chooser */
+static void
+error_message (EggFileFormatChooser *impl,
+ const char *msg,
+ const char *detail)
+{
+ error_message_with_parent (get_toplevel (GTK_WIDGET (impl)), msg, detail);
+}
+
+static void
+chooser_response_cb (GtkDialog *dialog,
+ gint response_id,
+ gpointer data)
+{
+ EggFileFormatChooser *self;
+ gchar *filename, *basename;
+ gchar *message;
+ guint format;
+
+ self = EGG_FILE_FORMAT_CHOOSER (data);
+
+ if (EGG_IS_POSITIVE_RESPONSE (response_id))
+ {
+ filename = gtk_file_chooser_get_filename (self->priv->chooser);
+ basename = g_filename_display_basename (filename);
+ g_free (filename);
+
+ format = egg_file_format_chooser_get_format (self, basename);
+ g_print ("%s: %s - %d\n", G_STRFUNC, basename, format);
+
+ if (0 == format)
+ {
+
+ message = g_strdup_printf (
+ _("The program was not able to find out the file format "
+ "you want to use for `%s'. Please make sure to use a "
+ "known extension for that file or manually choose a "
+ "file format from the list below."),
+ basename);
+
+ error_message (self,
+ _("File format not recognized"),
+ message);
+
+ g_free (message);
+
+ g_signal_stop_emission_by_name (dialog, "response");
+ }
+ else
+ {
+ filename = egg_file_format_chooser_append_extension (self, basename, format);
+
+ if (strcmp (filename, basename))
+ {
+ gtk_file_chooser_set_current_name (self->priv->chooser, filename);
+ g_signal_stop_emission_by_name (dialog, "response");
+ }
+
+ g_free (filename);
+ }
+
+ g_free (basename);
+ }
+
+}
+
+static void
+egg_file_format_chooser_realize (GtkWidget *widget)
+{
+ EggFileFormatChooser *self;
+ GtkWidget *parent;
+
+ GtkFileFilter *filter;
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+
+ GTK_WIDGET_CLASS (egg_file_format_chooser_parent_class)->realize (widget);
+
+ self = EGG_FILE_FORMAT_CHOOSER (widget);
+
+ g_return_if_fail (NULL == self->priv->chooser);
+
+ parent = gtk_widget_get_toplevel (widget);
+
+ if (!GTK_IS_FILE_CHOOSER (parent))
+ parent = gtk_widget_get_parent (widget);
+
+ while (parent && !GTK_IS_FILE_CHOOSER (parent))
+ parent = gtk_widget_get_parent (parent);
+
+ self->priv->chooser = GTK_FILE_CHOOSER (parent);
+
+ g_return_if_fail (GTK_IS_FILE_CHOOSER (self->priv->chooser));
+ g_return_if_fail (gtk_file_chooser_get_action (self->priv->chooser) ==
+ GTK_FILE_CHOOSER_ACTION_SAVE);
+
+ g_object_ref (self->priv->chooser);
+
+ g_signal_connect (self->priv->chooser, "notify::filter",
+ G_CALLBACK (filter_changed_cb), self);
+ gtk_file_chooser_add_filter (self->priv->chooser, self->priv->all_files);
+
+ model = GTK_TREE_MODEL (self->priv->model);
+
+ if (gtk_tree_model_get_iter_first (model, &iter))
+ {
+ do
+ {
+ gtk_tree_model_get (model, &iter, MODEL_COLUMN_FILTER, &filter, -1);
+ gtk_file_chooser_add_filter (self->priv->chooser, filter);
+ g_object_unref (filter);
+ }
+ while (gtk_tree_model_iter_next (model, &iter));
+ }
+
+ gtk_file_chooser_set_filter (self->priv->chooser,
+ self->priv->supported_files);
+
+ if (GTK_IS_DIALOG (self->priv->chooser))
+ g_signal_connect (self->priv->chooser, "response",
+ G_CALLBACK (chooser_response_cb), self);
+}
+
+static void
+egg_file_format_chooser_unrealize (GtkWidget *widget)
+{
+ EggFileFormatChooser *self;
+
+ GtkFileFilter *filter;
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+
+ GTK_WIDGET_CLASS (egg_file_format_chooser_parent_class)->unrealize (widget);
+
+ self = EGG_FILE_FORMAT_CHOOSER (widget);
+ model = GTK_TREE_MODEL (self->priv->model);
+
+ g_signal_handlers_disconnect_by_func (self->priv->chooser,
+ filter_changed_cb, self);
+ g_signal_handlers_disconnect_by_func (self->priv->chooser,
+ chooser_response_cb, self);
+
+ if (gtk_tree_model_get_iter_first (model, &iter))
+ {
+ do
+ {
+ gtk_tree_model_get (model, &iter, MODEL_COLUMN_FILTER, &filter, -1);
+ gtk_file_chooser_remove_filter (self->priv->chooser, filter);
+ g_object_unref (filter);
+ }
+ while (gtk_tree_model_iter_next (model, &iter));
+ }
+
+ gtk_file_chooser_remove_filter (self->priv->chooser, self->priv->all_files);
+ g_object_unref (self->priv->chooser);
+}
+
+static void
+egg_file_format_chooser_class_init (EggFileFormatChooserClass *cls)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (cls);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (cls);
+
+ g_type_class_add_private (cls, sizeof (EggFileFormatChooserPrivate));
+
+ object_class->dispose = egg_file_format_chooser_dispose;
+ object_class->finalize = egg_file_format_chooser_finalize;
+
+ widget_class->realize = egg_file_format_chooser_realize;
+ widget_class->unrealize = egg_file_format_chooser_unrealize;
+
+ signals[SIGNAL_SELECTION_CHANGED] = g_signal_new (
+ "selection-changed", EGG_TYPE_FILE_FORMAT_CHOOSER, G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (EggFileFormatChooserClass, selection_changed),
+ NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
+}
+
+GtkWidget*
+egg_file_format_chooser_new (void)
+{
+ return g_object_new (EGG_TYPE_FILE_FORMAT_CHOOSER, NULL);
+}
+
+static guint
+egg_file_format_chooser_add_format_impl (EggFileFormatChooser *self,
+ guint parent,
+ const gchar *name,
+ const gchar *icon,
+ const gchar *extensions)
+{
+ EggFileFormatSearch search;
+ GtkFileFilter *filter;
+ GtkTreeIter iter;
+
+ search.success = FALSE;
+ search.format = parent;
+ filter = NULL;
+
+ if (parent > 0)
+ {
+ gtk_tree_model_foreach (GTK_TREE_MODEL (self->priv->model),
+ find_by_format, &search);
+ g_return_val_if_fail (search.success, -1);
+ }
+ else
+ filter = egg_file_format_filter_new (name, TRUE);
+
+ gtk_tree_store_append (self->priv->model, &iter,
+ parent > 0 ? &search.iter : NULL);
+
+ gtk_tree_store_set (self->priv->model, &iter,
+ MODEL_COLUMN_ID, ++self->priv->last_id,
+ MODEL_COLUMN_EXTENSIONS, extensions,
+ MODEL_COLUMN_FILTER, filter,
+ MODEL_COLUMN_NAME, name,
+ MODEL_COLUMN_ICON, icon,
+ -1);
+
+ if (extensions)
+ {
+ if (parent > 0)
+ gtk_tree_model_get (GTK_TREE_MODEL (self->priv->model), &search.iter,
+ MODEL_COLUMN_FILTER, &filter, -1);
+
+ egg_file_format_filter_add_extensions (self->priv->supported_files, extensions);
+ egg_file_format_filter_add_extensions (filter, extensions);
+
+ if (parent > 0)
+ g_object_unref (filter);
+ }
+
+ return self->priv->last_id;
+}
+
+guint
+egg_file_format_chooser_add_format (EggFileFormatChooser *self,
+ guint parent,
+ const gchar *name,
+ const gchar *icon,
+ ...)
+{
+ GString *buffer = NULL;
+ const gchar* extptr;
+ va_list extensions;
+ guint id;
+
+ g_return_val_if_fail (EGG_IS_FILE_FORMAT_CHOOSER (self), 0);
+ g_return_val_if_fail (NULL != name, 0);
+
+ va_start (extensions, icon);
+
+ while (NULL != (extptr = va_arg (extensions, const gchar*)))
+ {
+ if (NULL == buffer)
+ buffer = g_string_new (NULL);
+ else
+ g_string_append (buffer, ", ");
+
+ g_string_append (buffer, extptr);
+ }
+
+ va_end (extensions);
+
+ id = egg_file_format_chooser_add_format_impl (self, parent, name, icon,
+ buffer ? buffer->str : NULL);
+
+ if (buffer)
+ g_string_free (buffer, TRUE);
+
+ return id;
+}
+
+static gchar*
+get_icon_name (const gchar *mime_type)
+{
+ static gboolean first_call = TRUE;
+ gchar *name = NULL;
+ gchar *s;
+
+ if (first_call)
+ {
+ g_warning ("%s: Replace by g_content_type_get_icon "
+ "when GVFS is merged into GLib.", G_STRLOC);
+ first_call = FALSE;
+ }
+
+ if (mime_type)
+ {
+ name = g_strconcat ("gnome-mime-", mime_type, NULL);
+
+ for(s = name; *s; ++s)
+ {
+ if (!isalpha (*s) || !isascii (*s))
+ *s = '-';
+ }
+ }
+
+ if (!name ||
+ !gtk_icon_theme_has_icon (gtk_icon_theme_get_default (), name))
+ {
+ g_free (name);
+ name = g_strdup ("gnome-mime-image");
+ }
+
+ return name;
+}
+
+void
+egg_file_format_chooser_add_pixbuf_formats (EggFileFormatChooser *self,
+ guint parent G_GNUC_UNUSED,
+ guint **formats)
+{
+ GSList *pixbuf_formats = NULL;
+ GSList *iter;
+ gint i;
+
+ g_return_if_fail (EGG_IS_FILE_FORMAT_CHOOSER (self));
+
+ pixbuf_formats = gdk_pixbuf_get_formats ();
+
+ if (formats)
+ *formats = g_new0 (guint, g_slist_length (pixbuf_formats) + 1);
+
+ for(iter = pixbuf_formats, i = 0; iter; iter = iter->next, ++i)
+ {
+ GdkPixbufFormat *format = iter->data;
+
+ gchar *description, *name, *extensions, *icon;
+ gchar **mime_types, **extension_list;
+ guint id;
+
+ if (gdk_pixbuf_format_is_disabled (format) ||
+ !gdk_pixbuf_format_is_writable (format))
+ continue;
+
+ mime_types = gdk_pixbuf_format_get_mime_types (format);
+ icon = get_icon_name (mime_types[0]);
+ g_strfreev (mime_types);
+
+ extension_list = gdk_pixbuf_format_get_extensions (format);
+ extensions = g_strjoinv (", ", extension_list);
+ g_strfreev (extension_list);
+
+ description = gdk_pixbuf_format_get_description (format);
+ name = gdk_pixbuf_format_get_name (format);
+
+ id = egg_file_format_chooser_add_format_impl (self, parent, description,
+ icon, extensions);
+
+ g_free (description);
+ g_free (extensions);
+ g_free (icon);
+
+ egg_file_format_chooser_set_format_data (self, id, name, g_free);
+
+ if (formats)
+ *formats[i] = id;
+ }
+
+ g_slist_free (pixbuf_formats);
+}
+
+void
+egg_file_format_chooser_remove_format (EggFileFormatChooser *self,
+ guint format)
+{
+ GDestroyNotify destroy = NULL;
+ gpointer data = NULL;
+
+ EggFileFormatSearch search;
+ GtkFileFilter *filter;
+ GtkTreeModel *model;
+
+ g_return_if_fail (EGG_IS_FILE_FORMAT_CHOOSER (self));
+
+ search.success = FALSE;
+ search.format = format;
+
+ model = GTK_TREE_MODEL (self->priv->model);
+ gtk_tree_model_foreach (model, find_by_format, &search);
+
+ g_return_if_fail (search.success);
+
+ gtk_tree_model_get (model, &search.iter,
+ MODEL_COLUMN_FILTER, &filter,
+ MODEL_COLUMN_DESTROY, &destroy,
+ MODEL_COLUMN_DATA, &data,
+ -1);
+
+ if (destroy)
+ destroy (data);
+
+ if (filter)
+ {
+ if (self->priv->chooser)
+ gtk_file_chooser_remove_filter (self->priv->chooser, filter);
+
+ g_object_unref (filter);
+ }
+ else
+ g_warning ("TODO: Remove extensions from parent filter");
+
+ gtk_tree_store_remove (self->priv->model, &search.iter);
+}
+
+void
+egg_file_format_chooser_set_format (EggFileFormatChooser *self,
+ guint format)
+{
+ EggFileFormatSearch search;
+
+ GtkTreeModel *model;
+ GtkTreePath *path;
+ GtkTreeView *view;
+
+ g_return_if_fail (EGG_IS_FILE_FORMAT_CHOOSER (self));
+
+ search.success = FALSE;
+ search.format = format;
+
+ model = GTK_TREE_MODEL (self->priv->model);
+ gtk_tree_model_foreach (model, find_by_format, &search);
+
+ g_return_if_fail (search.success);
+
+ path = gtk_tree_model_get_path (model, &search.iter);
+ view = gtk_tree_selection_get_tree_view (self->priv->selection);
+
+ gtk_tree_view_expand_to_path (view, path);
+ gtk_tree_selection_unselect_all (self->priv->selection);
+ gtk_tree_selection_select_path (self->priv->selection, path);
+
+ gtk_tree_path_free (path);
+
+ if (self->priv->idle_hack > 0)
+ {
+ g_source_remove (self->priv->idle_hack);
+ self->priv->idle_hack = 0;
+ }
+}
+
+guint
+egg_file_format_chooser_get_format (EggFileFormatChooser *self,
+ const gchar *filename)
+{
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ guint format = 0;
+
+ g_return_val_if_fail (EGG_IS_FILE_FORMAT_CHOOSER (self), -1);
+
+ if (gtk_tree_selection_get_selected (self->priv->selection, &model, &iter))
+ gtk_tree_model_get (model, &iter, MODEL_COLUMN_ID, &format, -1);
+
+ if (0 == format && NULL != filename)
+ {
+ EggFileFormatSearch search;
+
+ search.extension = strrchr(filename, '.');
+ search.success = FALSE;
+
+ if (search.extension++)
+ gtk_tree_model_foreach (model, find_by_extension, &search);
+ if (search.success)
+ format = search.format;
+ }
+
+ return format;
+}
+
+void
+egg_file_format_chooser_set_format_data (EggFileFormatChooser *self,
+ guint format,
+ gpointer data,
+ GDestroyNotify destroy)
+{
+ EggFileFormatSearch search;
+
+ g_return_if_fail (EGG_IS_FILE_FORMAT_CHOOSER (self));
+
+ search.success = FALSE;
+ search.format = format;
+
+ gtk_tree_model_foreach (GTK_TREE_MODEL (self->priv->model),
+ find_by_format, &search);
+
+ g_return_if_fail (search.success);
+
+ gtk_tree_store_set (self->priv->model, &search.iter,
+ MODEL_COLUMN_DESTROY, destroy,
+ MODEL_COLUMN_DATA, data,
+ -1);
+}
+
+gpointer
+egg_file_format_chooser_get_format_data (EggFileFormatChooser *self,
+ guint format)
+{
+ EggFileFormatSearch search;
+ gpointer data = NULL;
+ GtkTreeModel *model;
+
+ g_return_val_if_fail (EGG_IS_FILE_FORMAT_CHOOSER (self), NULL);
+
+ search.success = FALSE;
+ search.format = format;
+
+ model = GTK_TREE_MODEL (self->priv->model);
+ gtk_tree_model_foreach (model, find_by_format, &search);
+
+ g_return_val_if_fail (search.success, NULL);
+
+ gtk_tree_model_get (model, &search.iter,
+ MODEL_COLUMN_DATA, &data,
+ -1);
+ return data;
+}
+
+gchar*
+egg_file_format_chooser_append_extension (EggFileFormatChooser *self,
+ const gchar *filename,
+ guint format)
+{
+ EggFileFormatSearch search;
+ GtkTreeModel *model;
+ GtkTreeIter child;
+
+ gchar *extensions;
+ gchar *result;
+
+ g_return_val_if_fail (EGG_IS_FILE_FORMAT_CHOOSER (self), NULL);
+ g_return_val_if_fail (NULL != filename, NULL);
+
+ if (0 == format)
+ format = egg_file_format_chooser_get_format (self, NULL);
+
+ if (0 == format)
+ {
+ g_warning ("%s: No file format selected. Cannot append extension.", __FUNCTION__);
+ return NULL;
+ }
+
+ search.success = FALSE;
+ search.format = format;
+
+ model = GTK_TREE_MODEL (self->priv->model);
+ gtk_tree_model_foreach (model, find_by_format, &search);
+
+ g_return_val_if_fail (search.success, NULL);
+
+ gtk_tree_model_get (model, &search.iter,
+ MODEL_COLUMN_EXTENSIONS, &extensions,
+ -1);
+
+ if (NULL == extensions &&
+ gtk_tree_model_iter_nth_child (model, &child, &search.iter, 0))
+ {
+ gtk_tree_model_get (model, &child,
+ MODEL_COLUMN_EXTENSIONS, &extensions,
+ -1);
+ }
+
+ if (NULL == extensions)
+ {
+ g_warning ("%s: File format %d doesn't provide file extensions. "
+ "Cannot append extension.", __FUNCTION__, format);
+ return NULL;
+ }
+
+ if (accept_filename (extensions, filename))
+ result = g_strdup (filename);
+ else
+ result = g_strconcat (filename, ".", extensions, NULL);
+
+ g_assert (NULL == strchr(extensions, ','));
+ g_free (extensions);
+ return result;
+}
+
+/* vim: set sw=2 sta et: */
diff --git a/gthumb/eggfileformatchooser.h b/gthumb/eggfileformatchooser.h
new file mode 100644
index 0000000..7ef0aa3
--- /dev/null
+++ b/gthumb/eggfileformatchooser.h
@@ -0,0 +1,84 @@
+/* EggFileFormatChooser
+ * Copyright (C) 2007 Mathias Hasselmann
+ *
+ * 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.
+ */
+#ifndef __EGG_FILE_FORMAT_CHOOSER_H__
+#define __EGG_FILE_FORMAT_CHOOSER_H__
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define EGG_TYPE_FILE_FORMAT_CHOOSER (egg_file_format_chooser_get_type())
+#define EGG_FILE_FORMAT_CHOOSER(obj) (G_TYPE_CHECK_INSTANCE_CAST(obj, EGG_TYPE_FILE_FORMAT_CHOOSER, EggFileFormatChooser))
+#define EGG_FILE_FORMAT_CHOOSER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST(klass, EGG_TYPE_FILE_FORMAT_CHOOSER, EggFileFormatChooserClass))
+#define EGG_IS_FILE_FORMAT_CHOOSER(obj) (G_TYPE_CHECK_INSTANCE_TYPE(obj, EGG_TYPE_FILE_FORMAT_CHOOSER))
+#define EGG_IS_FILE_FORMAT_CHOOSER_CLASS(obj) (G_TYPE_CHECK_CLASS_TYPE(obj, EGG_TYPE_FILE_FORMAT_CHOOSER))
+#define EGG_FILE_FORMAT_CHOOSER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), EGG_TYPE_FILE_FORMAT_CHOOSER, EggFileFormatChooserClass))
+
+typedef struct _EggFileFormatChooser EggFileFormatChooser;
+typedef struct _EggFileFormatChooserClass EggFileFormatChooserClass;
+typedef struct _EggFileFormatChooserPrivate EggFileFormatChooserPrivate;
+
+struct _EggFileFormatChooser
+{
+ GtkExpander parent;
+ EggFileFormatChooserPrivate *priv;
+};
+
+struct _EggFileFormatChooserClass
+{
+ GtkExpanderClass parent;
+
+ void (*selection_changed)(EggFileFormatChooser *self);
+};
+
+GType egg_file_format_chooser_get_type (void) G_GNUC_CONST;
+GtkWidget* egg_file_format_chooser_new (void);
+
+guint egg_file_format_chooser_add_format (EggFileFormatChooser *self,
+ guint parent,
+ const gchar *name,
+ const gchar *icon,
+ ...) G_GNUC_NULL_TERMINATED;
+void egg_file_format_chooser_add_pixbuf_formats (EggFileFormatChooser *self,
+ guint parent,
+ guint **formats);
+void egg_file_format_chooser_remove_format (EggFileFormatChooser *self,
+ guint format);
+
+void egg_file_format_chooser_set_format (EggFileFormatChooser *self,
+ guint format);
+guint egg_file_format_chooser_get_format (EggFileFormatChooser *self,
+ const gchar *filename);
+
+void egg_file_format_chooser_set_format_data (EggFileFormatChooser *self,
+ guint format,
+ gpointer data,
+ GDestroyNotify destroy);
+gpointer egg_file_format_chooser_get_format_data (EggFileFormatChooser *self,
+ guint format);
+
+gchar* egg_file_format_chooser_append_extension (EggFileFormatChooser *self,
+ const gchar *filename,
+ guint format);
+
+G_END_DECLS
+
+#endif /* __EGG_FILE_FORMAT_CHOOSER_H__ */
+
+/* vim: set sw=2 sta et: */
diff --git a/gthumb/file-cache.c b/gthumb/file-cache.c
new file mode 100644
index 0000000..3e1e1a4
--- /dev/null
+++ b/gthumb/file-cache.c
@@ -0,0 +1,340 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2008 The Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include "gio-utils.h"
+#include "glib-utils.h"
+#include "gnome-desktop-thumbnail.h"
+#include "gth-file-data.h"
+#include "gth-user-dir.h"
+#include "typedefs.h"
+
+
+#define MAX_CACHE_SIZE (256 * 1024 * 1024)
+
+static goffset Cache_Used_Space = 0;
+static GList *Cache_Files = NULL; /* GthFileData list */
+static gboolean Cache_Loaded = FALSE;
+
+
+char *
+get_file_cache_full_path (const char *filename,
+ const char *extension)
+{
+ return gth_user_dir_get_file (GTH_DIR_CACHE,
+ GTHUMB_DIR,
+ filename,
+ extension,
+ NULL);
+}
+
+
+GFile *
+_g_file_get_cache_file (GFile *file)
+{
+ GFile *cache_file;
+
+ if (! g_file_is_native (file)) {
+ char *uri;
+ char *name;
+ char *path;
+
+ uri = g_file_get_uri (file);
+ name = gnome_desktop_thumbnail_md5 (uri);
+ if (name == NULL)
+ return NULL;
+ path = get_file_cache_full_path (name, NULL);
+
+ cache_file = g_file_new_for_path (path);
+
+ g_free (path);
+ g_free (name);
+ g_free (uri);
+ }
+ else
+ cache_file = g_file_dup (file);
+
+ return cache_file;
+}
+
+
+void
+free_file_cache (void)
+{
+ char *cache_path;
+ GFile *cache_dir;
+ GFileEnumerator *fenum;
+ GFileInfo *info;
+
+ cache_path = get_file_cache_full_path (NULL, NULL);
+ cache_dir = g_file_new_for_path (cache_path);
+ g_free (cache_path);
+
+ fenum = g_file_enumerate_children (cache_dir,
+ "standard::name",
+ G_FILE_QUERY_INFO_NONE,
+ NULL,
+ NULL);
+ if (fenum == NULL) {
+ g_object_unref (cache_dir);
+ return;
+ }
+
+ while ((info = g_file_enumerator_next_file (fenum, NULL, NULL)) != NULL) {
+ GFile *file;
+
+ file = g_file_get_child (cache_dir, g_file_info_get_name (info));
+ g_file_delete (file, NULL, NULL);
+ g_object_unref (file);
+ }
+
+ g_object_unref (fenum);
+ g_object_unref (cache_dir);
+
+ Cache_Files = NULL;
+ Cache_Used_Space = 0;
+}
+
+
+static gint
+comp_func_time (gconstpointer a,
+ gconstpointer b)
+{
+ GFileInfo *info_a, *info_b;
+ GTimeVal time_a, time_b;
+
+ info_a = (GFileInfo *) a;
+ info_b = (GFileInfo *) b;
+
+ g_file_info_get_modification_time (info_a, &time_a);
+ g_file_info_get_modification_time (info_b, &time_b);
+
+ return _g_time_val_cmp (&time_a, &time_b);
+}
+
+
+void
+check_cache_free_space (void)
+{
+ char *cache_path;
+ GFile *cache_dir;
+
+ cache_path = get_file_cache_full_path (NULL, NULL);
+ cache_dir = g_file_new_for_path (cache_path);
+ g_free (cache_path);
+
+ if (! Cache_Loaded) {
+ GFileEnumerator *fenum;
+ GList *info_list, *scan;
+ GFileInfo *info;
+
+ fenum = g_file_enumerate_children (cache_dir,
+ "standard::name,standard::size",
+ G_FILE_QUERY_INFO_NONE,
+ NULL,
+ NULL);
+ if (fenum == NULL) {
+ g_object_unref (cache_dir);
+ return;
+ }
+
+ info_list = NULL;
+ while ((info = g_file_enumerator_next_file (fenum, NULL, NULL)) != NULL)
+ info_list = g_list_prepend (info_list, g_object_ref (info));
+ info_list = g_list_sort (info_list, comp_func_time);
+
+ g_object_unref (fenum);
+
+ Cache_Used_Space = 0;
+ for (scan = info_list; scan; scan = scan->next) {
+ GFileInfo *info = scan->data;
+ GFile *file;
+
+ file = g_file_get_child (cache_dir, g_file_info_get_name (info));
+
+ Cache_Files = g_list_prepend (Cache_Files, gth_file_data_new (file, info));
+ Cache_Used_Space += g_file_info_get_size (info);
+
+ g_object_unref (file);
+ g_object_unref (info);
+ }
+ g_list_free (info_list);
+
+ Cache_Loaded = TRUE;
+ }
+
+ debug (DEBUG_INFO, "cache size: %ul.\n", Cache_Used_Space);
+
+ if (Cache_Used_Space > MAX_CACHE_SIZE) {
+ GList *scan;
+ int n = 0;
+
+ /* the first file is the last copied, so reverse the list to
+ * delete the older files first. */
+
+ Cache_Files = g_list_reverse (Cache_Files);
+ for (scan = Cache_Files; scan && (Cache_Used_Space > MAX_CACHE_SIZE / 2); ) {
+ GthFileData *file_data = scan->data;
+
+ g_file_delete (file_data->file, NULL, NULL);
+ Cache_Used_Space -= g_file_info_get_size (file_data->info);
+
+ Cache_Files = g_list_remove_link (Cache_Files, scan);
+ _g_object_list_unref (scan);
+ scan = Cache_Files;
+
+ n++;
+ }
+ Cache_Files = g_list_reverse (Cache_Files);
+
+ debug (DEBUG_INFO, "deleted %d files, new cache size: %ul.\n", n, Cache_Used_Space);
+ }
+
+ g_object_unref (cache_dir);
+}
+
+
+typedef struct {
+ CopyDoneCallback done_func;
+ gpointer done_data;
+ GFile *cache_file;
+ gboolean dummy;
+} CopyToCacheData;
+
+
+static void
+copy_remote_file_to_cache_ready (GError *error,
+ gpointer callback_data)
+{
+ CopyToCacheData *copy_data = callback_data;
+
+ if (! copy_data->dummy && (error == NULL)) {
+ Cache_Used_Space += _g_file_get_size (copy_data->cache_file);
+ Cache_Files = g_list_prepend (Cache_Files, g_object_ref (copy_data->cache_file));
+ }
+
+ if (copy_data->done_func != NULL)
+ copy_data->done_func (error, copy_data->done_data);
+
+ g_object_unref (copy_data->cache_file);
+ g_free (copy_data);
+}
+
+
+void
+copy_remote_file_to_cache (GthFileData *file_data,
+ GCancellable *cancellable,
+ CopyDoneCallback done_func,
+ gpointer done_data)
+{
+ CopyToCacheData *copy_data;
+ GFile *cache_file;
+ GTimeVal cache_mtime;
+
+ cache_file = _g_file_get_cache_file (file_data->file);
+ _g_file_get_modification_time (cache_file, &cache_mtime);
+
+ copy_data = g_new0 (CopyToCacheData, 1);
+ copy_data->done_func = done_func;
+ copy_data->done_data = done_data;
+ copy_data->cache_file = cache_file;
+
+ if (! g_file_is_native (file_data->file) &&
+ (_g_time_val_cmp (&cache_mtime, gth_file_data_get_modification_time (file_data)) < 0))
+ {
+ copy_data->dummy = FALSE;
+ _g_copy_file_async (file_data->file,
+ cache_file,
+ G_FILE_COPY_OVERWRITE,
+ G_PRIORITY_DEFAULT,
+ cancellable,
+ NULL,
+ NULL,
+ copy_remote_file_to_cache_ready,
+ copy_data);
+ }
+ else {
+ copy_data->dummy = TRUE;
+ _g_dummy_file_op_async (copy_remote_file_to_cache_ready, copy_data);
+ }
+}
+
+
+GFile *
+obtain_local_file (GthFileData *file_data)
+{
+ GFile *cache_file;
+ GTimeVal cache_mtime;
+
+ cache_file = _g_file_get_cache_file (file_data->file);
+ _g_file_get_modification_time (cache_file, &cache_mtime);
+
+ if (! g_file_is_native (file_data->file) &&
+ (_g_time_val_cmp (&cache_mtime, gth_file_data_get_modification_time (file_data)) < 0))
+ {
+ if (! g_file_copy (file_data->file,
+ cache_file,
+ G_FILE_COPY_OVERWRITE,
+ NULL,
+ NULL,
+ NULL,
+ NULL))
+ {
+ g_object_unref (cache_file);
+ cache_file = NULL;
+ }
+ }
+
+ return cache_file;
+}
+
+
+void
+update_file_from_cache (GthFileData *file_data,
+ GCancellable *cancellable,
+ CopyDoneCallback done_func,
+ gpointer done_data)
+{
+ GFile *cache_file;
+ GTimeVal cache_mtime;
+
+ cache_file = _g_file_get_cache_file (file_data->file);
+ _g_file_get_modification_time (cache_file, &cache_mtime);
+
+ if (! g_file_is_native (file_data->file)
+ && (_g_time_val_cmp (&cache_mtime, gth_file_data_get_modification_time (file_data)) > 0))
+ {
+ _g_copy_file_async (cache_file,
+ file_data->file,
+ G_FILE_COPY_OVERWRITE,
+ G_PRIORITY_DEFAULT,
+ cancellable,
+ NULL,
+ NULL,
+ done_func,
+ done_data);
+ }
+ else
+ _g_dummy_file_op_async (done_func, done_data);
+
+ g_object_unref (cache_file);
+}
diff --git a/gthumb/file-cache.h b/gthumb/file-cache.h
new file mode 100644
index 0000000..5ac8508
--- /dev/null
+++ b/gthumb/file-cache.h
@@ -0,0 +1,50 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2008 The Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef FILE_CACHE_H
+#define FILE_CACHE_H
+
+#include <glib.h>
+#include <gio/gio.h>
+#include "gth-file-data.h"
+#include "gio-utils.h"
+
+G_BEGIN_DECLS
+
+char * get_file_cache_full_path (const char *filename,
+ const char *extension);
+GFile * _g_file_get_cache_file (GFile *file);
+void free_file_cache (void);
+void check_cache_free_space (void);
+void copy_remote_file_to_cache (GthFileData *file_data,
+ GCancellable *cancellable,
+ CopyDoneCallback done_func,
+ gpointer done_data);
+GFile * obtain_local_file (GthFileData *file_data);
+void update_file_from_cache (GthFileData *file_data,
+ GCancellable *cancellable,
+ CopyDoneCallback done_func,
+ gpointer done_data);
+
+G_END_DECLS
+
+#endif /* FILE_CACHE_H */
diff --git a/gthumb/gconf-utils.c b/gthumb/gconf-utils.c
new file mode 100644
index 0000000..65c1e07
--- /dev/null
+++ b/gthumb/gconf-utils.c
@@ -0,0 +1,958 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * gThumb
+ *
+ * Copyright (C) 2001, 2002 The Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+/* eel-gconf-extensions.c - Stuff to make GConf easier to use.
+
+ Copyright (C) 2000, 2001 Eazel, Inc.
+
+ The Gnome Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ The Gnome Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public
+ License along with the Gnome Library; see the file COPYING.LIB. If not,
+ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Authors: Ramiro Estrugo <ramiro eazel com>
+*/
+
+/* Modified by Paolo Bacchilega <paolo bacch tin it> for gThumb. */
+
+#include <config.h>
+#include <string.h>
+#include <errno.h>
+#include <gconf/gconf-client.h>
+#include <gconf/gconf.h>
+#include "gconf-utils.h"
+#include "gtk-utils.h"
+#include "gthumb-error.h"
+#include "glib-utils.h"
+
+#define HOME_DIR "~"
+
+
+static GConfClient *global_gconf_client = NULL;
+
+
+void
+eel_global_client_free (void)
+{
+ if (global_gconf_client == NULL) {
+ return;
+ }
+
+ g_object_unref (global_gconf_client);
+ global_gconf_client = NULL;
+}
+
+
+GConfClient *
+eel_gconf_client_get_global (void)
+{
+ /* Initialize gconf if needed */
+ if (!gconf_is_initialized ()) {
+ char *argv[] = { "eel-preferences", NULL };
+ GError *error = NULL;
+
+ if (!gconf_init (1, argv, &error)) {
+ if (eel_gconf_handle_error (&error)) {
+ return NULL;
+ }
+ }
+ }
+
+ if (global_gconf_client == NULL)
+ global_gconf_client = gconf_client_get_default ();
+
+ return global_gconf_client;
+}
+
+
+gboolean
+eel_gconf_handle_error (GError **error)
+{
+ static gboolean shown_dialog = FALSE;
+
+ g_return_val_if_fail (error != NULL, FALSE);
+
+ if (*error != NULL) {
+ g_warning ("GConf error:\n %s", (*error)->message);
+ if (! shown_dialog) {
+ shown_dialog = TRUE;
+ _gtk_error_dialog_run (NULL,
+ "GConf error:\n %s\n"
+ "All further errors "
+ "shown only on terminal",
+ (*error)->message);
+ }
+ g_error_free (*error);
+ *error = NULL;
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+
+static gboolean
+check_type (const char *key,
+ GConfValue *val,
+ GConfValueType t,
+ GError **err)
+{
+ if (val->type != t) {
+ g_set_error (err,
+ GTHUMB_ERROR,
+ errno,
+ "Type mismatch for key %s",
+ key);
+ return FALSE;
+ } else
+ return TRUE;
+}
+
+
+void
+eel_gconf_set_boolean (const char *key,
+ gboolean boolean_value)
+{
+ GConfClient *client;
+ GError *error = NULL;
+
+ g_return_if_fail (key != NULL);
+
+ client = eel_gconf_client_get_global ();
+ g_return_if_fail (client != NULL);
+
+ gconf_client_set_bool (client, key, boolean_value, &error);
+ eel_gconf_handle_error (&error);
+}
+
+
+gboolean
+eel_gconf_get_boolean (const char *key,
+ gboolean def)
+{
+ GError *error = NULL;
+ gboolean result = def;
+ GConfClient *client;
+ GConfValue *val;
+
+ g_return_val_if_fail (key != NULL, def);
+
+ client = eel_gconf_client_get_global ();
+ g_return_val_if_fail (client != NULL, def);
+
+ val = gconf_client_get (client, key, &error);
+
+ if (val != NULL) {
+ if (check_type (key, val, GCONF_VALUE_BOOL, &error))
+ result = gconf_value_get_bool (val);
+ else
+ eel_gconf_handle_error (&error);
+ gconf_value_free (val);
+
+ } else if (error != NULL)
+ eel_gconf_handle_error (&error);
+
+ return result;
+}
+
+
+void
+eel_gconf_set_integer (const char *key,
+ int int_value)
+{
+ GConfClient *client;
+ GError *error = NULL;
+
+ g_return_if_fail (key != NULL);
+
+ client = eel_gconf_client_get_global ();
+ g_return_if_fail (client != NULL);
+
+ gconf_client_set_int (client, key, int_value, &error);
+ eel_gconf_handle_error (&error);
+}
+
+
+int
+eel_gconf_get_integer (const char *key,
+ int def)
+{
+ GError *error = NULL;
+ int result = def;
+ GConfClient *client;
+ GConfValue *val;
+
+ g_return_val_if_fail (key != NULL, def);
+
+ client = eel_gconf_client_get_global ();
+ g_return_val_if_fail (client != NULL, def);
+
+ val = gconf_client_get (client, key, &error);
+
+ if (val != NULL) {
+ if (check_type (key, val, GCONF_VALUE_INT, &error))
+ result = gconf_value_get_int (val);
+ else
+ eel_gconf_handle_error (&error);
+ gconf_value_free (val);
+
+ } else if (error != NULL)
+ eel_gconf_handle_error (&error);
+
+ return result;
+}
+
+
+int
+eel_gconf_get_enum (const char *key,
+ GType enum_type,
+ int def_val)
+{
+ GEnumValue *def_enum_val;
+ char *value_nick;
+ GEnumValue *value;
+
+ def_enum_val = _g_enum_type_get_value (enum_type, def_val);
+ value_nick = eel_gconf_get_string (key, def_enum_val->value_nick);
+ value = _g_enum_type_get_value_by_nick (enum_type, value_nick);
+ g_free (value_nick);
+
+ return (value != NULL) ? value->value : 0;
+}
+
+
+void
+eel_gconf_set_enum (const char *key,
+ GType enum_type,
+ int value)
+{
+ GEnumValue *enum_value;
+
+ enum_value = _g_enum_type_get_value (enum_type, value);
+ if (enum_value != NULL)
+ eel_gconf_set_string (key, enum_value->value_nick);
+}
+
+
+void
+eel_gconf_set_float (const char *key,
+ float float_value)
+{
+ GConfClient *client;
+ GError *error = NULL;
+
+ g_return_if_fail (key != NULL);
+
+ client = eel_gconf_client_get_global ();
+ g_return_if_fail (client != NULL);
+
+ gconf_client_set_float (client, key, float_value, &error);
+ eel_gconf_handle_error (&error);
+}
+
+
+float
+eel_gconf_get_float (const char *key,
+ float def)
+{
+ GError *error = NULL;
+ float result = def;
+ GConfClient *client;
+ GConfValue *val;
+
+ g_return_val_if_fail (key != NULL, def);
+
+ client = eel_gconf_client_get_global ();
+ g_return_val_if_fail (client != NULL, def);
+
+ val = gconf_client_get (client, key, &error);
+
+ if (val != NULL) {
+ if (check_type (key, val, GCONF_VALUE_FLOAT, &error))
+ result = gconf_value_get_float (val);
+ else
+ eel_gconf_handle_error (&error);
+ gconf_value_free (val);
+
+ } else if (error != NULL)
+ eel_gconf_handle_error (&error);
+
+ return result;
+}
+
+
+void
+eel_gconf_set_string (const char *key,
+ const char *string_value)
+{
+ GConfClient *client;
+ GError *error = NULL;
+
+ g_return_if_fail (key != NULL);
+
+ client = eel_gconf_client_get_global ();
+ g_return_if_fail (client != NULL);
+
+ gconf_client_set_string (client, key, string_value, &error);
+ eel_gconf_handle_error (&error);
+}
+
+
+char *
+eel_gconf_get_string (const char *key,
+ const char *def)
+{
+ GError *error = NULL;
+ char *result;
+ GConfClient *client;
+ char *val;
+
+ if (def != NULL)
+ result = g_strdup (def);
+ else
+ result = NULL;
+
+ g_return_val_if_fail (key != NULL, result);
+
+ client = eel_gconf_client_get_global ();
+ g_return_val_if_fail (client != NULL, result);
+
+ val = gconf_client_get_string (client, key, &error);
+
+ /* Return the default value if the key does not exist,
+ or if it is empty. */
+ if (val != NULL && strcmp (val, "")) {
+ g_return_val_if_fail (error == NULL, result);
+ g_free (result);
+ result = g_strdup (val);
+
+ } else if (error != NULL)
+ eel_gconf_handle_error (&error);
+
+ return result;
+}
+
+
+void
+eel_gconf_set_locale_string (const char *key,
+ const char *string_value)
+{
+ char *utf8;
+
+ utf8 = g_locale_to_utf8 (string_value, -1, 0, 0, 0);
+
+ if (utf8 != NULL) {
+ eel_gconf_set_string (key, utf8);
+ g_free (utf8);
+ }
+}
+
+
+char *
+eel_gconf_get_locale_string (const char *key,
+ const char *def)
+{
+ char *utf8;
+ char *result;
+
+ utf8 = eel_gconf_get_string (key, def);
+
+ if (utf8 == NULL)
+ return NULL;
+
+ result = g_locale_from_utf8 (utf8, -1, 0, 0, 0);
+ g_free (utf8);
+
+ return result;
+}
+
+
+void
+eel_gconf_set_string_list (const char *key,
+ const GSList *slist)
+{
+ GConfClient *client;
+ GError *error;
+
+ g_return_if_fail (key != NULL);
+
+ client = eel_gconf_client_get_global ();
+ g_return_if_fail (client != NULL);
+
+ error = NULL;
+ gconf_client_set_list (client, key, GCONF_VALUE_STRING,
+ /* Need cast cause of GConf api bug */
+ (GSList *) slist,
+ &error);
+ eel_gconf_handle_error (&error);
+}
+
+
+GSList *
+eel_gconf_get_string_list (const char *key)
+{
+ GSList *slist;
+ GConfClient *client;
+ GError *error;
+
+ g_return_val_if_fail (key != NULL, NULL);
+
+ client = eel_gconf_client_get_global ();
+ g_return_val_if_fail (client != NULL, NULL);
+
+ error = NULL;
+ slist = gconf_client_get_list (client, key, GCONF_VALUE_STRING, &error);
+ if (eel_gconf_handle_error (&error)) {
+ slist = NULL;
+ }
+
+ return slist;
+}
+
+
+GSList *
+eel_gconf_get_path_list (const char *key)
+{
+ GSList *str_slist, *slist, *scan;
+
+ str_slist = eel_gconf_get_string_list (key);
+
+ slist = NULL;
+ for (scan = str_slist; scan; scan = scan->next) {
+ char *str = scan->data;
+ char *path = _g_replace (str, HOME_DIR, g_get_home_dir ());
+ slist = g_slist_prepend (slist, path);
+ }
+
+ g_slist_foreach (str_slist, (GFunc) g_free, NULL);
+ g_slist_free (str_slist);
+
+ return g_slist_reverse (slist);
+}
+
+
+void
+eel_gconf_set_path_list (const char *key,
+ const GSList *string_list_value)
+{
+ GSList *path_slist;
+ const GSList *scan;
+
+ path_slist = NULL;
+ for (scan = string_list_value; scan; scan = scan->next) {
+ char *value = scan->data;
+ char *path = _g_replace (value, g_get_home_dir (), HOME_DIR);
+ path_slist = g_slist_prepend (path_slist, path);
+ }
+ path_slist = g_slist_reverse (path_slist);
+
+ eel_gconf_set_string_list (key, path_slist);
+
+ g_slist_foreach (path_slist, (GFunc) g_free, NULL);
+ g_slist_free (path_slist);
+}
+
+
+GSList *
+eel_gconf_get_locale_string_list (const char *key)
+{
+ GSList *utf8_slist, *slist, *scan;
+
+ utf8_slist = eel_gconf_get_string_list (key);
+
+ slist = NULL;
+ for (scan = utf8_slist; scan; scan = scan->next) {
+ char *utf8 = scan->data;
+ char *locale = g_locale_from_utf8 (utf8, -1, 0, 0, 0);
+ slist = g_slist_prepend (slist, locale);
+ }
+
+ g_slist_foreach (utf8_slist, (GFunc) g_free, NULL);
+ g_slist_free (utf8_slist);
+
+ return g_slist_reverse (slist);
+}
+
+
+void
+eel_gconf_set_locale_string_list (const char *key,
+ const GSList *string_list_value)
+{
+ GSList *utf8_slist;
+ const GSList *scan;
+
+ utf8_slist = NULL;
+ for (scan = string_list_value; scan; scan = scan->next) {
+ char *locale = scan->data;
+ char *utf8 = g_locale_to_utf8 (locale, -1, 0, 0, 0);
+ utf8_slist = g_slist_prepend (utf8_slist, utf8);
+ }
+
+ utf8_slist = g_slist_reverse (utf8_slist);
+
+ eel_gconf_set_string_list (key, utf8_slist);
+
+ g_slist_foreach (utf8_slist, (GFunc) g_free, NULL);
+ g_slist_free (utf8_slist);
+}
+
+
+char *
+eel_gconf_get_path (const char *key,
+ const char *def_val)
+{
+ char *value;
+ char *path;
+
+ value = eel_gconf_get_string (key, def_val);
+ path = _g_replace (value, HOME_DIR, g_get_home_dir ());
+ g_free (value);
+
+ return path;
+}
+
+
+void
+eel_gconf_set_path (const char *key,
+ const char *path)
+{
+ char *value;
+
+ value = _g_replace (path, g_get_home_dir (), HOME_DIR);
+ eel_gconf_set_string (key, value);
+ g_free (value);
+}
+
+
+gboolean
+eel_gconf_is_default (const char *key)
+{
+ gboolean result;
+ GConfValue *value;
+ GError *error = NULL;
+
+ g_return_val_if_fail (key != NULL, FALSE);
+
+ value = gconf_client_get_without_default (eel_gconf_client_get_global (), key, &error);
+
+ if (eel_gconf_handle_error (&error)) {
+ if (value != NULL) {
+ gconf_value_free (value);
+ }
+ return FALSE;
+ }
+
+ result = (value == NULL);
+ eel_gconf_value_free (value);
+ return result;
+}
+
+
+gboolean
+eel_gconf_monitor_add (const char *directory)
+{
+ GError *error = NULL;
+ GConfClient *client;
+
+ g_return_val_if_fail (directory != NULL, FALSE);
+
+ client = gconf_client_get_default ();
+ g_return_val_if_fail (client != NULL, FALSE);
+
+ gconf_client_add_dir (client,
+ directory,
+ GCONF_CLIENT_PRELOAD_NONE,
+ &error);
+
+ if (eel_gconf_handle_error (&error)) {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+
+gboolean
+eel_gconf_monitor_remove (const char *directory)
+{
+ GError *error = NULL;
+ GConfClient *client;
+
+ if (directory == NULL) {
+ return FALSE;
+ }
+
+ client = gconf_client_get_default ();
+ g_return_val_if_fail (client != NULL, FALSE);
+
+ gconf_client_remove_dir (client,
+ directory,
+ &error);
+
+ if (eel_gconf_handle_error (&error)) {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+
+void
+eel_gconf_preload_cache (const char *directory,
+ GConfClientPreloadType preload_type)
+{
+ GError *error = NULL;
+ GConfClient *client;
+
+ if (directory == NULL) {
+ return;
+ }
+
+ client = gconf_client_get_default ();
+ g_return_if_fail (client != NULL);
+
+ gconf_client_preload (client,
+ directory,
+ preload_type,
+ &error);
+
+ eel_gconf_handle_error (&error);
+}
+
+
+void
+eel_gconf_suggest_sync (void)
+{
+ GConfClient *client;
+ GError *error = NULL;
+
+ client = eel_gconf_client_get_global ();
+ g_return_if_fail (client != NULL);
+
+ gconf_client_suggest_sync (client, &error);
+ eel_gconf_handle_error (&error);
+}
+
+
+GConfValue*
+eel_gconf_get_value (const char *key)
+{
+ GConfValue *value = NULL;
+ GConfClient *client;
+ GError *error = NULL;
+
+ g_return_val_if_fail (key != NULL, NULL);
+
+ client = eel_gconf_client_get_global ();
+ g_return_val_if_fail (client != NULL, NULL);
+
+ value = gconf_client_get (client, key, &error);
+
+ if (eel_gconf_handle_error (&error)) {
+ if (value != NULL) {
+ gconf_value_free (value);
+ value = NULL;
+ }
+ }
+
+ return value;
+}
+
+
+GConfValue*
+eel_gconf_get_default_value (const char *key)
+{
+ GConfValue *value = NULL;
+ GConfClient *client;
+ GError *error = NULL;
+
+ g_return_val_if_fail (key != NULL, NULL);
+
+ client = eel_gconf_client_get_global ();
+ g_return_val_if_fail (client != NULL, NULL);
+
+ value = gconf_client_get_default_from_schema (client, key, &error);
+
+ if (eel_gconf_handle_error (&error)) {
+ if (value != NULL) {
+ gconf_value_free (value);
+ value = NULL;
+ }
+ }
+
+ return value;
+}
+
+
+static int
+eel_strcmp (const char *string_a, const char *string_b)
+{
+ /* FIXME bugzilla.eazel.com 5450: Maybe we need to make this
+ * treat 'NULL < ""', or have a flavor that does that. If we
+ * didn't have code that already relies on 'NULL == ""', I
+ * would change it right now.
+ */
+ return strcmp (string_a == NULL ? "" : string_a,
+ string_b == NULL ? "" : string_b);
+}
+
+
+static gboolean
+eel_str_is_equal (const char *string_a, const char *string_b)
+{
+ /* FIXME bugzilla.eazel.com 5450: Maybe we need to make this
+ * treat 'NULL != ""', or have a flavor that does that. If we
+ * didn't have code that already relies on 'NULL == ""', I
+ * would change it right now.
+ */
+ return eel_strcmp (string_a, string_b) == 0;
+}
+
+
+static gboolean
+simple_value_is_equal (const GConfValue *a,
+ const GConfValue *b)
+{
+ g_return_val_if_fail (a != NULL, FALSE);
+ g_return_val_if_fail (b != NULL, FALSE);
+
+ switch (a->type) {
+ case GCONF_VALUE_STRING:
+ return eel_str_is_equal (gconf_value_get_string (a),
+ gconf_value_get_string (b));
+ break;
+
+ case GCONF_VALUE_INT:
+ return gconf_value_get_int (a) ==
+ gconf_value_get_int (b);
+ break;
+
+ case GCONF_VALUE_FLOAT:
+ return gconf_value_get_float (a) ==
+ gconf_value_get_float (b);
+ break;
+
+ case GCONF_VALUE_BOOL:
+ return gconf_value_get_bool (a) ==
+ gconf_value_get_bool (b);
+ break;
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+
+ return FALSE;
+}
+
+
+gboolean
+eel_gconf_value_is_equal (const GConfValue *a,
+ const GConfValue *b)
+{
+ GSList *node_a;
+ GSList *node_b;
+
+ if (a == NULL && b == NULL) {
+ return TRUE;
+ }
+
+ if (a == NULL || b == NULL) {
+ return FALSE;
+ }
+
+ if (a->type != b->type) {
+ return FALSE;
+ }
+
+ switch (a->type) {
+ case GCONF_VALUE_STRING:
+ case GCONF_VALUE_INT:
+ case GCONF_VALUE_FLOAT:
+ case GCONF_VALUE_BOOL:
+ return simple_value_is_equal (a, b);
+ break;
+
+ case GCONF_VALUE_LIST:
+ if (gconf_value_get_list_type (a) !=
+ gconf_value_get_list_type (b)) {
+ return FALSE;
+ }
+
+ node_a = gconf_value_get_list (a);
+ node_b = gconf_value_get_list (b);
+
+ if (node_a == NULL && node_b == NULL) {
+ return TRUE;
+ }
+
+ if (g_slist_length (node_a) !=
+ g_slist_length (node_b)) {
+ return FALSE;
+ }
+
+ for (;
+ node_a != NULL && node_b != NULL;
+ node_a = node_a->next, node_b = node_b->next) {
+ g_assert (node_a->data != NULL);
+ g_assert (node_b->data != NULL);
+ if (!simple_value_is_equal (node_a->data, node_b->data)) {
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+ default:
+ /* FIXME: pair ? */
+ g_assert (0);
+ break;
+ }
+
+ g_assert_not_reached ();
+ return FALSE;
+}
+
+
+void
+eel_gconf_value_free (GConfValue *value)
+{
+ if (value == NULL) {
+ return;
+ }
+
+ gconf_value_free (value);
+}
+
+
+guint
+eel_gconf_notification_add (const char *key,
+ GConfClientNotifyFunc notification_callback,
+ gpointer callback_data)
+{
+ guint notification_id;
+ GConfClient *client;
+ GError *error = NULL;
+
+ g_return_val_if_fail (key != NULL, EEL_GCONF_UNDEFINED_CONNECTION);
+ g_return_val_if_fail (notification_callback != NULL, EEL_GCONF_UNDEFINED_CONNECTION);
+
+ client = eel_gconf_client_get_global ();
+ g_return_val_if_fail (client != NULL, EEL_GCONF_UNDEFINED_CONNECTION);
+
+ notification_id = gconf_client_notify_add (client,
+ key,
+ notification_callback,
+ callback_data,
+ NULL,
+ &error);
+
+ if (eel_gconf_handle_error (&error)) {
+ if (notification_id != EEL_GCONF_UNDEFINED_CONNECTION) {
+ gconf_client_notify_remove (client, notification_id);
+ notification_id = EEL_GCONF_UNDEFINED_CONNECTION;
+ }
+ }
+
+ return notification_id;
+}
+
+
+void
+eel_gconf_notification_remove (guint notification_id)
+{
+ GConfClient *client;
+
+ if (notification_id == EEL_GCONF_UNDEFINED_CONNECTION) {
+ return;
+ }
+
+ client = eel_gconf_client_get_global ();
+ g_return_if_fail (client != NULL);
+
+ gconf_client_notify_remove (client, notification_id);
+}
+
+
+GSList *
+eel_gconf_value_get_string_list (const GConfValue *value)
+{
+ GSList *result;
+ const GSList *slist;
+ const GSList *node;
+ const char *string;
+ const GConfValue *next_value;
+
+ if (value == NULL) {
+ return NULL;
+ }
+
+ g_return_val_if_fail (value->type == GCONF_VALUE_LIST, NULL);
+ g_return_val_if_fail (gconf_value_get_list_type (value) == GCONF_VALUE_STRING, NULL);
+
+ slist = gconf_value_get_list (value);
+ result = NULL;
+ for (node = slist; node != NULL; node = node->next) {
+ next_value = node->data;
+ g_return_val_if_fail (next_value != NULL, NULL);
+ g_return_val_if_fail (next_value->type == GCONF_VALUE_STRING, NULL);
+ string = gconf_value_get_string (next_value);
+ result = g_slist_append (result, g_strdup (string));
+ }
+ return result;
+}
+
+
+void
+eel_gconf_value_set_string_list (GConfValue *value,
+ const GSList *string_list)
+{
+ const GSList *node;
+ GConfValue *next_value;
+ GSList *value_list;
+
+ g_return_if_fail (value->type == GCONF_VALUE_LIST);
+ g_return_if_fail (gconf_value_get_list_type (value) == GCONF_VALUE_STRING);
+
+ value_list = NULL;
+ for (node = string_list; node != NULL; node = node->next) {
+ next_value = gconf_value_new (GCONF_VALUE_STRING);
+ gconf_value_set_string (next_value, node->data);
+ value_list = g_slist_append (value_list, next_value);
+ }
+
+ gconf_value_set_list (value, value_list);
+
+ for (node = value_list; node != NULL; node = node->next) {
+ gconf_value_free (node->data);
+ }
+ g_slist_free (value_list);
+}
diff --git a/gthumb/gconf-utils.h b/gthumb/gconf-utils.h
new file mode 100644
index 0000000..810d2ed
--- /dev/null
+++ b/gthumb/gconf-utils.h
@@ -0,0 +1,121 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * gThumb
+ *
+ * Copyright (C) 2001, 2002 The Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+/* eel-gconf-extensions.h - Stuff to make GConf easier to use.
+
+ Copyright (C) 2000, 2001 Eazel, Inc.
+
+ The Gnome Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ The Gnome Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public
+ License along with the Gnome Library; see the file COPYING.LIB. If not,
+ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Authors: Ramiro Estrugo <ramiro eazel com>
+*/
+
+/* Modified by Paolo Bacchilega <paolo bacch tin it> for gThumb. */
+
+#ifndef GCONF_UTILS_H
+#define GCONF_UTILS_H
+
+#include <glib.h>
+#include <gconf/gconf.h>
+#include <gconf/gconf-client.h>
+
+G_BEGIN_DECLS
+
+#define EEL_GCONF_UNDEFINED_CONNECTION 0
+
+GConfClient *eel_gconf_client_get_global (void);
+void eel_global_client_free (void);
+gboolean eel_gconf_handle_error (GError **error);
+gboolean eel_gconf_get_boolean (const char *key,
+ gboolean def_val);
+void eel_gconf_set_boolean (const char *key,
+ gboolean value);
+int eel_gconf_get_integer (const char *key,
+ int def_val);
+void eel_gconf_set_integer (const char *key,
+ int value);
+int eel_gconf_get_enum (const char *key,
+ GType enum_type,
+ int def_val);
+void eel_gconf_set_enum (const char *key,
+ GType enum_type,
+ int value);
+float eel_gconf_get_float (const char *key,
+ float def_val);
+void eel_gconf_set_float (const char *key,
+ float value);
+char * eel_gconf_get_string (const char *key,
+ const char *def_val);
+void eel_gconf_set_string (const char *key,
+ const char *value);
+char * eel_gconf_get_path (const char *key,
+ const char *def_val);
+void eel_gconf_set_path (const char *key,
+ const char *value);
+char * eel_gconf_get_locale_string (const char *key,
+ const char *def_val);
+void eel_gconf_set_locale_string (const char *key,
+ const char *value);
+GSList * eel_gconf_get_string_list (const char *key);
+void eel_gconf_set_string_list (const char *key,
+ const GSList *string_list_value);
+GSList * eel_gconf_get_path_list (const char *key);
+void eel_gconf_set_path_list (const char *key,
+ const GSList *string_list_value);
+GSList * eel_gconf_get_locale_string_list(const char *key);
+void eel_gconf_set_locale_string_list(const char *key,
+ const GSList *string_list_value);
+gboolean eel_gconf_is_default (const char *key);
+gboolean eel_gconf_monitor_add (const char *directory);
+gboolean eel_gconf_monitor_remove (const char *directory);
+void eel_gconf_preload_cache (const char *directory,
+ GConfClientPreloadType preload_type);
+void eel_gconf_suggest_sync (void);
+GConfValue* eel_gconf_get_value (const char *key);
+GConfValue* eel_gconf_get_default_value (const char *key);
+gboolean eel_gconf_value_is_equal (const GConfValue *a,
+ const GConfValue *b);
+void eel_gconf_value_free (GConfValue *value);
+guint eel_gconf_notification_add (const char *key,
+ GConfClientNotifyFunc notification_callback,
+ gpointer callback_data);
+void eel_gconf_notification_remove (guint notification_id);
+GSList * eel_gconf_value_get_string_list (const GConfValue *value);
+void eel_gconf_value_set_string_list (GConfValue *value,
+ const GSList *string_list);
+
+G_END_DECLS
+
+#endif /* GCONF_UTILS_H */
diff --git a/gthumb/gedit-message-area.c b/gthumb/gedit-message-area.c
new file mode 100644
index 0000000..2b60663
--- /dev/null
+++ b/gthumb/gedit-message-area.c
@@ -0,0 +1,646 @@
+/*
+ * gedit-message-area.c
+ * This file is part of gedit
+ *
+ * Copyright (C) 2005 - Paolo Maggi
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+/*
+ * Modified by the gedit Team, 2005. See the AUTHORS file for a
+ * list of people on the gedit Team.
+ * See the ChangeLog files for a list of changes.
+ *
+ * $Id: gedit-message-area.c 6468 2008-08-28 08:23:00Z icq $
+ */
+
+/* TODO: Style properties */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include <gdk/gdk.h>
+#include <gdk/gdkkeysyms.h>
+
+#include "gedit-message-area.h"
+#include "gtk-utils.h"
+
+#define GEDIT_MESSAGE_AREA_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), \
+ GEDIT_TYPE_MESSAGE_AREA, \
+ GeditMessageAreaPrivate))
+
+struct _GeditMessageAreaPrivate
+{
+ GtkWidget *main_hbox;
+
+ GtkWidget *contents;
+ GtkWidget *action_area;
+
+ gboolean changing_style;
+};
+
+typedef struct _ResponseData ResponseData;
+
+struct _ResponseData
+{
+ gint response_id;
+};
+
+enum {
+ RESPONSE,
+ CLOSE,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+G_DEFINE_TYPE(GeditMessageArea, gedit_message_area, GTK_TYPE_HBOX)
+
+
+static void
+gedit_message_area_finalize (GObject *object)
+{
+ /*
+ GeditMessageArea *message_area = GEDIT_MESSAGE_AREA (object);
+ */
+
+ G_OBJECT_CLASS (gedit_message_area_parent_class)->finalize (object);
+}
+
+static ResponseData *
+get_response_data (GtkWidget *widget,
+ gboolean create)
+{
+ ResponseData *ad = g_object_get_data (G_OBJECT (widget),
+ "gedit-message-area-response-data");
+
+ if (ad == NULL && create)
+ {
+ ad = g_new (ResponseData, 1);
+
+ g_object_set_data_full (G_OBJECT (widget),
+ "gedit-message-area-response-data",
+ ad,
+ g_free);
+ }
+
+ return ad;
+}
+
+static GtkWidget *
+find_button (GeditMessageArea *message_area,
+ gint response_id)
+{
+ GList *children, *tmp_list;
+ GtkWidget *child = NULL;
+
+ children = gtk_container_get_children (
+ GTK_CONTAINER (message_area->priv->action_area));
+
+ for (tmp_list = children; tmp_list; tmp_list = tmp_list->next)
+ {
+ ResponseData *rd = get_response_data (tmp_list->data, FALSE);
+
+ if (rd && rd->response_id == response_id)
+ {
+ child = tmp_list->data;
+ break;
+ }
+ }
+
+ g_list_free (children);
+
+ return child;
+}
+
+static void
+gedit_message_area_close (GeditMessageArea *message_area)
+{
+ if (!find_button (message_area, GTK_RESPONSE_CANCEL))
+ return;
+
+ /* emit response signal */
+ gedit_message_area_response (GEDIT_MESSAGE_AREA (message_area),
+ GTK_RESPONSE_CANCEL);
+}
+
+static gboolean
+paint_message_area (GtkWidget *widget,
+ GdkEventExpose *event,
+ gpointer user_data)
+{
+ guint border;
+ GdkRectangle area;
+ GdkRectangle paint_area;
+
+ border = gtk_container_get_border_width (GTK_CONTAINER (widget));
+
+ area.x = widget->allocation.x + border;
+ area.y = widget->allocation.y + border;
+ area.width = widget->allocation.width - (border * 2);
+ area.height = widget->allocation.height - (border * 2);
+
+ if (gdk_rectangle_intersect (&event->area, &area, &paint_area))
+ gtk_paint_flat_box (widget->style,
+ widget->window,
+ GTK_STATE_NORMAL,
+ GTK_SHADOW_NONE,
+ &paint_area,
+ widget,
+ "tooltip",
+ area.x,
+ area.y,
+ area.width,
+ area.height);
+
+ return FALSE;
+}
+
+static void
+gedit_message_area_class_init (GeditMessageAreaClass *klass)
+{
+ GObjectClass *object_class;
+ GtkBindingSet *binding_set;
+
+ object_class = G_OBJECT_CLASS (klass);
+ object_class->finalize = gedit_message_area_finalize;
+
+ klass->close = gedit_message_area_close;
+
+ g_type_class_add_private (object_class, sizeof(GeditMessageAreaPrivate));
+
+ signals[RESPONSE] = g_signal_new ("response",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GeditMessageAreaClass, response),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__INT,
+ G_TYPE_NONE, 1,
+ G_TYPE_INT);
+
+ signals[CLOSE] = g_signal_new ("close",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (GeditMessageAreaClass, close),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ binding_set = gtk_binding_set_by_class (klass);
+
+ gtk_binding_entry_add_signal (binding_set, GDK_Escape, 0, "close", 0);
+}
+
+static void
+style_set (GtkWidget *widget,
+ GtkStyle *prev_style,
+ GeditMessageArea *message_area)
+{
+ GtkWidget *window;
+ GtkStyle *style;
+
+ if (message_area->priv->changing_style)
+ return;
+
+ /* This is a hack needed to use the tooltip background color */
+ window = gtk_window_new (GTK_WINDOW_POPUP);
+ gtk_widget_set_name (window, "gtk-tooltip");
+ gtk_widget_ensure_style (window);
+ style = gtk_widget_get_style (window);
+
+ message_area->priv->changing_style = TRUE;
+ gtk_widget_set_style (GTK_WIDGET (message_area), style);
+ message_area->priv->changing_style = FALSE;
+
+ gtk_widget_destroy (window);
+
+ gtk_widget_queue_draw (GTK_WIDGET (message_area));
+}
+
+static void
+gedit_message_area_init (GeditMessageArea *message_area)
+{
+ message_area->priv = GEDIT_MESSAGE_AREA_GET_PRIVATE (message_area);
+
+ message_area->priv->main_hbox = gtk_hbox_new (FALSE, 16); /* FIXME: use style properties */
+ gtk_widget_show (message_area->priv->main_hbox);
+ gtk_container_set_border_width (GTK_CONTAINER (message_area->priv->main_hbox),
+ 8); /* FIXME: use style properties */
+
+ message_area->priv->action_area = gtk_vbox_new (TRUE, 10); /* FIXME: use style properties */
+ gtk_widget_show (message_area->priv->action_area);
+ gtk_box_pack_end (GTK_BOX (message_area->priv->main_hbox),
+ message_area->priv->action_area,
+ FALSE,
+ TRUE,
+ 0);
+
+ gtk_box_pack_start (GTK_BOX (message_area),
+ message_area->priv->main_hbox,
+ TRUE,
+ TRUE,
+ 0);
+
+ gtk_widget_set_app_paintable (GTK_WIDGET (message_area), TRUE);
+
+ g_signal_connect (message_area,
+ "expose-event",
+ G_CALLBACK (paint_message_area),
+ NULL);
+
+ /* Note that we connect to style-set on one of the internal
+ * widgets, not on the message area itself, since gtk does
+ * not deliver any further style-set signals for a widget on
+ * which the style has been forced with gtk_widget_set_style() */
+ g_signal_connect (message_area->priv->main_hbox,
+ "style-set",
+ G_CALLBACK (style_set),
+ message_area);
+}
+
+static gint
+get_response_for_widget (GeditMessageArea *message_area,
+ GtkWidget *widget)
+{
+ ResponseData *rd;
+
+ rd = get_response_data (widget, FALSE);
+ if (!rd)
+ return GTK_RESPONSE_NONE;
+ else
+ return rd->response_id;
+}
+
+static void
+action_widget_activated (GtkWidget *widget, GeditMessageArea *message_area)
+{
+ gint response_id;
+
+ response_id = get_response_for_widget (message_area, widget);
+
+ gedit_message_area_response (message_area, response_id);
+}
+
+void
+gedit_message_area_add_action_widget (GeditMessageArea *message_area,
+ GtkWidget *child,
+ gint response_id)
+{
+ ResponseData *ad;
+ guint signal_id;
+
+ g_return_if_fail (GEDIT_IS_MESSAGE_AREA (message_area));
+ g_return_if_fail (GTK_IS_WIDGET (child));
+
+ ad = get_response_data (child, TRUE);
+
+ ad->response_id = response_id;
+
+ if (GTK_IS_BUTTON (child))
+ signal_id = g_signal_lookup ("clicked", GTK_TYPE_BUTTON);
+ else
+ signal_id = GTK_WIDGET_GET_CLASS (child)->activate_signal;
+
+ if (signal_id)
+ {
+ GClosure *closure;
+
+ closure = g_cclosure_new_object (G_CALLBACK (action_widget_activated),
+ G_OBJECT (message_area));
+
+ g_signal_connect_closure_by_id (child,
+ signal_id,
+ 0,
+ closure,
+ FALSE);
+ }
+ else
+ g_warning ("Only 'activatable' widgets can be packed into the action area of a GeditMessageArea");
+
+ if (response_id != GTK_RESPONSE_HELP)
+ gtk_box_pack_start (GTK_BOX (message_area->priv->action_area),
+ child,
+ FALSE,
+ FALSE,
+ 0);
+ else
+ gtk_box_pack_end (GTK_BOX (message_area->priv->action_area),
+ child,
+ FALSE,
+ FALSE,
+ 0);
+}
+
+/**
+ * gedit_message_area_set_contents:
+ * @message_area: a #GeditMessageArea
+ * @contents: widget you want to add to the contents area
+ *
+ * Adds the @contents widget to the contents area of #GeditMessageArea.
+ */
+void
+gedit_message_area_set_contents (GeditMessageArea *message_area,
+ GtkWidget *contents)
+{
+ g_return_if_fail (GEDIT_IS_MESSAGE_AREA (message_area));
+ g_return_if_fail (GTK_IS_WIDGET (contents));
+
+ message_area->priv->contents = contents;
+ gtk_box_pack_start (GTK_BOX (message_area->priv->main_hbox),
+ message_area->priv->contents,
+ TRUE,
+ TRUE,
+ 0);
+}
+
+/**
+ * gedit_message_area_add_button:
+ * @message_area: a #GeditMessageArea
+ * @button_text: text of button, or stock ID
+ * @response_id: response ID for the button
+ *
+ * Adds a button with the given text (or a stock button, if button_text is a stock ID)
+ * and sets things up so that clicking the button will emit the "response" signal
+ * with the given response_id. The button is appended to the end of the message area's
+ * action area. The button widget is returned, but usually you don't need it.
+ *
+ * Returns: the button widget that was added
+ */
+GtkWidget*
+gedit_message_area_add_button (GeditMessageArea *message_area,
+ const gchar *button_text,
+ gint response_id)
+{
+ GtkWidget *button;
+
+ g_return_val_if_fail (GEDIT_IS_MESSAGE_AREA (message_area), NULL);
+ g_return_val_if_fail (button_text != NULL, NULL);
+
+ button = gtk_button_new_from_stock (button_text);
+
+ GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
+
+ gtk_widget_show (button);
+
+ gedit_message_area_add_action_widget (message_area,
+ button,
+ response_id);
+
+ return button;
+}
+
+static void
+add_buttons_valist (GeditMessageArea *message_area,
+ const gchar *first_button_text,
+ va_list args)
+{
+ const gchar* text;
+ gint response_id;
+
+ g_return_if_fail (GEDIT_IS_MESSAGE_AREA (message_area));
+
+ if (first_button_text == NULL)
+ return;
+
+ text = first_button_text;
+ response_id = va_arg (args, gint);
+
+ while (text != NULL)
+ {
+ gedit_message_area_add_button (message_area,
+ text,
+ response_id);
+
+ text = va_arg (args, gchar*);
+ if (text == NULL)
+ break;
+
+ response_id = va_arg (args, int);
+ }
+}
+
+/**
+ * gedit_message_area_add_buttons:
+ * @message_area: a #GeditMessageArea
+ * @first_button_text: button text or stock ID
+ * @...: response ID for first button, then more text-response_id pairs
+ *
+ * Adds more buttons, same as calling gedit_message_area_add_button() repeatedly.
+ * The variable argument list should be NULL-terminated as with
+ * gedit_message_area_new_with_buttons(). Each button must have both text and response ID.
+ */
+void
+gedit_message_area_add_buttons (GeditMessageArea *message_area,
+ const gchar *first_button_text,
+ ...)
+{
+ va_list args;
+
+ va_start (args, first_button_text);
+
+ add_buttons_valist (message_area,
+ first_button_text,
+ args);
+
+ va_end (args);
+}
+
+/**
+ * gedit_message_area_new:
+ *
+ * Creates a new #GeditMessageArea object.
+ *
+ * Returns: a new #GeditMessageArea object
+ */
+GtkWidget *
+gedit_message_area_new (void)
+{
+ return g_object_new (GEDIT_TYPE_MESSAGE_AREA, NULL);
+}
+
+/**
+ * gedit_message_area_new_with_buttons:
+ * @first_button_text: stock ID or text to go in first button, or NULL
+ * @...: response ID for first button, then additional buttons, ending with NULL
+ *
+ * Creates a new #GeditMessageArea with buttons. Button text/response ID pairs
+ * should be listed, with a NULL pointer ending the list. Button text can be either
+ * a stock ID such as GTK_STOCK_OK, or some arbitrary text. A response ID can be any
+ * positive number, or one of the values in the GtkResponseType enumeration. If
+ * the user clicks one of these dialog buttons, GeditMessageArea will emit the "response"
+ * signal with the corresponding response ID.
+ *
+ * Returns: a new #GeditMessageArea
+ */
+GtkWidget *
+gedit_message_area_new_with_buttons (const gchar *first_button_text,
+ ...)
+{
+ GeditMessageArea *message_area;
+ va_list args;
+
+ message_area = GEDIT_MESSAGE_AREA (gedit_message_area_new ());
+
+ va_start (args, first_button_text);
+
+ add_buttons_valist (message_area,
+ first_button_text,
+ args);
+
+ va_end (args);
+
+ return GTK_WIDGET (message_area);
+}
+
+/**
+ * gedit_message_area_set_response_sensitive:
+ * @message_area: a #GeditMessageArea
+ * @response_id: a response ID
+ * @setting: TRUE for sensitive
+ *
+ * Calls gtk_widget_set_sensitive (widget, setting) for each widget in the dialog's
+ * action area with the given response_id. A convenient way to sensitize/desensitize
+ * dialog buttons.
+ */
+void
+gedit_message_area_set_response_sensitive (GeditMessageArea *message_area,
+ gint response_id,
+ gboolean setting)
+{
+ GList *children;
+ GList *tmp_list;
+
+ g_return_if_fail (GEDIT_IS_MESSAGE_AREA (message_area));
+
+ children = gtk_container_get_children (GTK_CONTAINER (message_area->priv->action_area));
+
+ tmp_list = children;
+ while (tmp_list != NULL)
+ {
+ GtkWidget *widget = tmp_list->data;
+ ResponseData *rd = get_response_data (widget, FALSE);
+
+ if (rd && rd->response_id == response_id)
+ gtk_widget_set_sensitive (widget, setting);
+
+ tmp_list = g_list_next (tmp_list);
+ }
+
+ g_list_free (children);
+}
+
+/**
+ * gedit_message_area_set_default_response:
+ * @message_area: a #GeditMessageArea
+ * @response_id: a response ID
+ *
+ * Sets the last widget in the message area's action area with the given response_id
+ * as the default widget for the dialog. Pressing "Enter" normally activates the
+ * default widget.
+ */
+void
+gedit_message_area_set_default_response (GeditMessageArea *message_area,
+ gint response_id)
+{
+ GList *children;
+ GList *tmp_list;
+
+ g_return_if_fail (GEDIT_IS_MESSAGE_AREA (message_area));
+
+ children = gtk_container_get_children (GTK_CONTAINER (message_area->priv->action_area));
+
+ tmp_list = children;
+ while (tmp_list != NULL)
+ {
+ GtkWidget *widget = tmp_list->data;
+ ResponseData *rd = get_response_data (widget, FALSE);
+
+ if (rd && rd->response_id == response_id)
+ gtk_widget_grab_default (widget);
+
+ tmp_list = g_list_next (tmp_list);
+ }
+
+ g_list_free (children);
+}
+
+/**
+ * gedit_message_area_set_default_response:
+ * @message_area: a #GeditMessageArea
+ * @response_id: a response ID
+ *
+ * Emits the 'response' signal with the given @response_id.
+ */
+void
+gedit_message_area_response (GeditMessageArea *message_area,
+ gint response_id)
+{
+ g_return_if_fail (GEDIT_IS_MESSAGE_AREA (message_area));
+
+ g_signal_emit (message_area,
+ signals[RESPONSE],
+ 0,
+ response_id);
+}
+
+/**
+ * gedit_message_area_add_stock_button_with_text:
+ * @message_area: a #GeditMessageArea
+ * @text: the text to visualize in the button
+ * @stock_id: the stock ID of the button
+ * @response_id: a response ID
+ *
+ * Same as gedit_message_area_add_button() but with a specific text.
+ */
+GtkWidget *
+gedit_message_area_add_stock_button_with_text (GeditMessageArea *message_area,
+ const gchar *text,
+ const gchar *stock_id,
+ gint response_id)
+{
+ GtkWidget *button;
+
+ g_return_val_if_fail (GEDIT_IS_MESSAGE_AREA (message_area), NULL);
+ g_return_val_if_fail (stock_id != NULL, NULL);
+
+ if (text != NULL) {
+ button = gtk_button_new_with_mnemonic (text);
+ gtk_button_set_image (GTK_BUTTON (button), gtk_image_new_from_stock (stock_id, GTK_ICON_SIZE_BUTTON));
+ }
+ else
+ button = gtk_button_new_from_stock (stock_id);
+
+ GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
+
+ gtk_widget_show (button);
+
+ gedit_message_area_add_action_widget (message_area,
+ button,
+ response_id);
+
+ return button;
+}
+
+
+void
+gedit_message_area_clear_action_area (GeditMessageArea *message_area)
+{
+ _gtk_container_remove_children (GTK_CONTAINER (message_area->priv->action_area), NULL, NULL);
+}
diff --git a/gthumb/gedit-message-area.h b/gthumb/gedit-message-area.h
new file mode 100644
index 0000000..ce912c1
--- /dev/null
+++ b/gthumb/gedit-message-area.h
@@ -0,0 +1,131 @@
+/*
+ * gedit-message-area.h
+ * This file is part of gedit
+ *
+ * Copyright (C) 2005 - Paolo Maggi
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+/*
+ * Modified by the gedit Team, 2005. See the AUTHORS file for a
+ * list of people on the gedit Team.
+ * See the ChangeLog files for a list of changes.
+ *
+ * $Id: gedit-message-area.h 6631 2008-11-29 11:06:49Z pborelli $
+ */
+
+#ifndef __GEDIT_MESSAGE_AREA_H__
+#define __GEDIT_MESSAGE_AREA_H__
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+/*
+ * Type checking and casting macros
+ */
+#define GEDIT_TYPE_MESSAGE_AREA (gedit_message_area_get_type())
+#define GEDIT_MESSAGE_AREA(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GEDIT_TYPE_MESSAGE_AREA, GeditMessageArea))
+#define GEDIT_MESSAGE_AREA_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GEDIT_TYPE_MESSAGE_AREA, GeditMessageAreaClass))
+#define GEDIT_IS_MESSAGE_AREA(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GEDIT_TYPE_MESSAGE_AREA))
+#define GEDIT_IS_MESSAGE_AREA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GEDIT_TYPE_MESSAGE_AREA))
+#define GEDIT_MESSAGE_AREA_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GEDIT_TYPE_MESSAGE_AREA, GeditMessageAreaClass))
+
+/* Private structure type */
+typedef struct _GeditMessageAreaPrivate GeditMessageAreaPrivate;
+
+/*
+ * Main object structure
+ */
+typedef struct _GeditMessageArea GeditMessageArea;
+
+struct _GeditMessageArea
+{
+ GtkHBox parent;
+
+ /*< private > */
+ GeditMessageAreaPrivate *priv;
+};
+
+/*
+ * Class definition
+ */
+typedef struct _GeditMessageAreaClass GeditMessageAreaClass;
+
+struct _GeditMessageAreaClass
+{
+ GtkHBoxClass parent_class;
+
+ /* Signals */
+ void (* response) (GeditMessageArea *message_area, gint response_id);
+
+ /* Keybinding signals */
+ void (* close) (GeditMessageArea *message_area);
+
+ /* Padding for future expansion */
+ void (*_gedit_reserved1) (void);
+ void (*_gedit_reserved2) (void);
+};
+
+/*
+ * Public methods
+ */
+GType gedit_message_area_get_type (void) G_GNUC_CONST;
+
+GtkWidget *gedit_message_area_new (void);
+
+GtkWidget *gedit_message_area_new_with_buttons (const gchar *first_button_text,
+ ...);
+
+void gedit_message_area_set_contents (GeditMessageArea *message_area,
+ GtkWidget *contents);
+
+void gedit_message_area_add_action_widget (GeditMessageArea *message_area,
+ GtkWidget *child,
+ gint response_id);
+
+GtkWidget *gedit_message_area_add_button (GeditMessageArea *message_area,
+ const gchar *button_text,
+ gint response_id);
+
+GtkWidget *gedit_message_area_add_stock_button_with_text
+ (GeditMessageArea *message_area,
+ const gchar *text,
+ const gchar *stock_id,
+ gint response_id);
+
+void gedit_message_area_add_buttons (GeditMessageArea *message_area,
+ const gchar *first_button_text,
+ ...);
+
+void gedit_message_area_set_response_sensitive
+ (GeditMessageArea *message_area,
+ gint response_id,
+ gboolean setting);
+void gedit_message_area_set_default_response
+ (GeditMessageArea *message_area,
+ gint response_id);
+
+void gedit_message_area_clear_action_area (GeditMessageArea *message_area);
+
+/* Emit response signal */
+void gedit_message_area_response (GeditMessageArea *message_area,
+ gint response_id);
+
+G_END_DECLS
+
+#endif /* __GEDIT_MESSAGE_AREA_H__ */
diff --git a/gthumb/gio-utils.c b/gthumb/gio-utils.c
new file mode 100644
index 0000000..7813c07
--- /dev/null
+++ b/gthumb/gio-utils.c
@@ -0,0 +1,2088 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2008 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <string.h>
+#include <glib.h>
+#include <gio/gio.h>
+#include "gth-file-data.h"
+#include "gth-hook.h"
+#include "glib-utils.h"
+#include "gio-utils.h"
+
+
+#define N_FILES_PER_REQUEST 128
+
+
+/* -- filter -- */
+
+
+typedef enum {
+ FILTER_DEFAULT = 0,
+ FILTER_NODOTFILES = 1 << 1,
+ FILTER_IGNORECASE = 1 << 2,
+ FILTER_NOBACKUPFILES = 1 << 3
+} FilterOptions;
+
+
+typedef struct {
+ char *pattern;
+ FilterOptions options;
+ GRegex **regexps;
+} Filter;
+
+
+static Filter *
+filter_new (const char *pattern,
+ FilterOptions options)
+{
+ Filter *filter;
+ GRegexCompileFlags flags;
+
+ filter = g_new0 (Filter, 1);
+
+ if ((pattern != NULL) && (strcmp (pattern, "*") != 0))
+ filter->pattern = g_strdup (pattern);
+
+ filter->options = options;
+ if (filter->options & FILTER_IGNORECASE)
+ flags = G_REGEX_CASELESS;
+ else
+ flags = 0;
+ filter->regexps = get_regexps_from_pattern (pattern, flags);
+
+ return filter;
+}
+
+
+static void
+filter_destroy (Filter *filter)
+{
+ if (filter == NULL)
+ return;
+
+ g_free (filter->pattern);
+ if (filter->regexps != NULL)
+ free_regexps (filter->regexps);
+ g_free (filter);
+}
+
+
+static gboolean
+filter_matches (Filter *filter,
+ const char *name)
+{
+ const char *file_name;
+ char *utf8_name;
+ gboolean matched;
+
+ g_return_val_if_fail (name != NULL, FALSE);
+
+ file_name = _g_uri_get_basename (name);
+
+ if ((filter->options & FILTER_NODOTFILES)
+ && ((file_name[0] == '.') || (strstr (file_name, "/.") != NULL)))
+ return FALSE;
+
+ if ((filter->options & FILTER_NOBACKUPFILES)
+ && (file_name[strlen (file_name) - 1] == '~'))
+ return FALSE;
+
+ if (filter->pattern == NULL)
+ return TRUE;
+
+ utf8_name = g_filename_to_utf8 (file_name, -1, NULL, NULL, NULL);
+ matched = string_matches_regexps (filter->regexps, utf8_name, 0);
+ g_free (utf8_name);
+
+ return matched;
+}
+
+
+static gboolean
+filter_empty (Filter *filter)
+{
+ return ((filter->pattern == NULL) || (strcmp (filter->pattern, "*") == 0));
+}
+
+
+/* -- g_directory_foreach_child -- */
+
+
+typedef struct {
+ GFile *file;
+ GFileInfo *info;
+} ChildData;
+
+
+static ChildData *
+child_data_new (GFile *file,
+ GFileInfo *info)
+{
+ ChildData *data;
+
+ data = g_new0 (ChildData, 1);
+ data->file = g_file_dup (file);
+ data->info = g_file_info_dup (info);
+
+ return data;
+}
+
+
+static void
+child_data_free (ChildData *data)
+{
+ if (data == NULL)
+ return;
+ if (data->file != NULL)
+ g_object_unref (data->file);
+ if (data->info != NULL)
+ g_object_unref (data->info);
+ g_free (data);
+}
+
+
+static void
+clear_child_data (ChildData **data)
+{
+ if (*data != NULL)
+ child_data_free (*data);
+ *data = NULL;
+}
+
+
+typedef struct {
+ GFile *base_directory;
+ gboolean recursive;
+ gboolean follow_links;
+ StartDirCallback start_dir_func;
+ ForEachChildCallback for_each_file_func;
+ ForEachDoneCallback done_func;
+ gpointer user_data;
+
+ /* private */
+
+ ChildData *current;
+ GHashTable *already_visited;
+ GList *to_visit;
+ const char *attributes;
+ GCancellable *cancellable;
+ GFileEnumerator *enumerator;
+ GError *error;
+ guint source_id;
+} ForEachChildData;
+
+
+static void
+for_each_child_data_free (ForEachChildData *fec)
+{
+ if (fec == NULL)
+ return;
+
+ g_object_unref (fec->base_directory);
+ if (fec->already_visited)
+ g_hash_table_destroy (fec->already_visited);
+ clear_child_data (&(fec->current));
+ if (fec->to_visit != NULL) {
+ g_list_foreach (fec->to_visit, (GFunc) child_data_free, NULL);
+ g_list_free (fec->to_visit);
+ }
+ g_free (fec);
+}
+
+
+static gboolean
+for_each_child_done_cb (gpointer user_data)
+{
+ ForEachChildData *fec = user_data;
+
+ g_source_remove (fec->source_id);
+ clear_child_data (&(fec->current));
+ if (fec->done_func)
+ fec->done_func (fec->error, fec->user_data);
+ for_each_child_data_free (fec);
+
+ return FALSE;
+}
+
+
+static void
+for_each_child_done (ForEachChildData *fec)
+{
+ fec->source_id = g_idle_add (for_each_child_done_cb, fec);
+}
+
+
+static void for_each_child_start_current (ForEachChildData *fec);
+
+
+static gboolean
+for_each_child_start_cb (gpointer user_data)
+{
+ ForEachChildData *fec = user_data;
+
+ g_source_remove (fec->source_id);
+ for_each_child_start_current (fec);
+
+ return FALSE;
+}
+
+
+static void
+for_each_child_start (ForEachChildData *fec)
+{
+ fec->source_id = g_idle_add (for_each_child_start_cb, fec);
+}
+
+
+static void
+for_each_child_set_current (ForEachChildData *fec,
+ ChildData *data)
+{
+ clear_child_data (&(fec->current));
+ fec->current = data;
+}
+
+
+static void
+for_each_child_start_next_sub_directory (ForEachChildData *fec)
+{
+ ChildData *child = NULL;
+
+ if (fec->to_visit != NULL) {
+ GList *tmp;
+
+ child = (ChildData *) fec->to_visit->data;
+ tmp = fec->to_visit;
+ fec->to_visit = g_list_remove_link (fec->to_visit, tmp);
+ g_list_free (tmp);
+ }
+
+ if (child != NULL) {
+ for_each_child_set_current (fec, child);
+ for_each_child_start (fec);
+ }
+ else
+ for_each_child_done (fec);
+}
+
+
+static void
+for_each_child_close_enumerator (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ ForEachChildData *fec = user_data;
+ GError *error = NULL;
+
+ if (! g_file_enumerator_close_finish (fec->enumerator,
+ result,
+ &error))
+ {
+ if (fec->error == NULL)
+ fec->error = g_error_copy (error);
+ else
+ g_clear_error (&error);
+ }
+
+ if ((fec->error == NULL) && fec->recursive)
+ for_each_child_start_next_sub_directory (fec);
+ else
+ for_each_child_done (fec);
+}
+
+
+static void
+for_each_child_next_files_ready (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ ForEachChildData *fec = user_data;
+ GList *children, *scan;
+
+ children = g_file_enumerator_next_files_finish (fec->enumerator,
+ result,
+ &(fec->error));
+
+ if (children == NULL) {
+ g_file_enumerator_close_async (fec->enumerator,
+ G_PRIORITY_DEFAULT,
+ fec->cancellable,
+ for_each_child_close_enumerator,
+ fec);
+ return;
+ }
+
+ for (scan = children; scan; scan = scan->next) {
+ GFileInfo *child_info = scan->data;
+ GFile *file;
+
+ file = g_file_get_child (fec->current->file, g_file_info_get_name (child_info));
+
+ if (g_file_info_get_file_type (child_info) == G_FILE_TYPE_DIRECTORY) {
+ /* avoid to visit a directory more than ones */
+
+ if (g_hash_table_lookup (fec->already_visited, file) == NULL) {
+ g_hash_table_insert (fec->already_visited, g_file_dup (file), GINT_TO_POINTER (1));
+ fec->to_visit = g_list_append (fec->to_visit, child_data_new (file, child_info));
+ }
+ }
+
+ fec->for_each_file_func (file, child_info, fec->user_data);
+
+ g_object_unref (file);
+ }
+
+ g_file_enumerator_next_files_async (fec->enumerator,
+ N_FILES_PER_REQUEST,
+ G_PRIORITY_DEFAULT,
+ fec->cancellable,
+ for_each_child_next_files_ready,
+ fec);
+}
+
+static void
+for_each_child_ready (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ ForEachChildData *fec = user_data;
+
+ fec->enumerator = g_file_enumerate_children_finish (G_FILE (source_object), result, &(fec->error));
+ if (fec->enumerator == NULL) {
+ for_each_child_done (fec);
+ return;
+ }
+
+ g_file_enumerator_next_files_async (fec->enumerator,
+ N_FILES_PER_REQUEST,
+ G_PRIORITY_DEFAULT,
+ fec->cancellable,
+ for_each_child_next_files_ready,
+ fec);
+}
+
+
+static void
+for_each_child_start_current (ForEachChildData *fec)
+{
+ if (fec->start_dir_func != NULL) {
+ DirOp op;
+
+ op = fec->start_dir_func (fec->current->file, fec->current->info, &(fec->error), fec->user_data);
+ switch (op) {
+ case DIR_OP_SKIP:
+ for_each_child_start_next_sub_directory (fec);
+ return;
+ case DIR_OP_STOP:
+ for_each_child_done (fec);
+ return;
+ case DIR_OP_CONTINUE:
+ break;
+ }
+ }
+
+ g_file_enumerate_children_async (fec->current->file,
+ fec->attributes,
+ fec->follow_links ? G_FILE_QUERY_INFO_NONE : G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+ G_PRIORITY_DEFAULT,
+ fec->cancellable,
+ for_each_child_ready,
+ fec);
+}
+
+
+static void
+directory_info_ready_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ ForEachChildData *fec = user_data;
+ GFileInfo *info;
+ ChildData *child;
+
+ info = g_file_query_info_finish (G_FILE (source_object), result, &(fec->error));
+ if (info == NULL) {
+ for_each_child_done (fec);
+ return;
+ }
+
+ child = child_data_new (fec->base_directory, info);
+ g_object_unref (info);
+
+ for_each_child_set_current (fec, child);
+ for_each_child_start_current (fec);
+}
+
+
+/**
+ * g_directory_foreach_child:
+ * @directory: The directory to visit.
+ * @recursive: Whether to traverse the @directory recursively.
+ * @follow_links: Whether to dereference the symbolic links.
+ * @attributes: The GFileInfo attributes to read.
+ * @cancellable: An optional @GCancellable object, used to cancel the process.
+ * @start_dir_func: the function called for each sub-directory, or %NULL if
+ * not needed.
+ * @for_each_file_func: the function called for each file. Can't be %NULL.
+ * @done_func: the function called at the end of the traversing process.
+ * Can't be %NULL.
+ * @user_data: data to pass to @done_func
+ *
+ * Traverse the @directory's filesystem structure calling the
+ * @for_each_file_func function for each file in the directory; the
+ * @start_dir_func function on each directory before it's going to be
+ * traversed, this includes @directory too; the @done_func function is
+ * called at the end of the process.
+ * Some traversing options are available: if @recursive is TRUE the
+ * directory is traversed recursively; if @follow_links is TRUE, symbolic
+ * links are dereferenced, otherwise they are returned as links.
+ * Each callback uses the same @user_data additional parameter.
+ */
+void
+g_directory_foreach_child (GFile *directory,
+ gboolean recursive,
+ gboolean follow_links,
+ const char *attributes,
+ GCancellable *cancellable,
+ StartDirCallback start_dir_func,
+ ForEachChildCallback for_each_file_func,
+ ForEachDoneCallback done_func,
+ gpointer user_data)
+{
+ ForEachChildData *fec;
+
+ g_return_if_fail (for_each_file_func != NULL);
+
+ fec = g_new0 (ForEachChildData, 1);
+
+ fec->base_directory = g_file_dup (directory);
+ fec->recursive = recursive;
+ fec->follow_links = follow_links;
+ fec->attributes = attributes;
+ fec->cancellable = cancellable;
+ fec->start_dir_func = start_dir_func;
+ fec->for_each_file_func = for_each_file_func;
+ fec->done_func = done_func;
+ fec->user_data = user_data;
+ fec->already_visited = g_hash_table_new_full (g_file_hash,
+ (GEqualFunc) g_file_equal,
+ g_object_unref,
+ NULL);
+
+ g_file_query_info_async (fec->base_directory,
+ fec->attributes,
+ G_FILE_QUERY_INFO_NONE,
+ G_PRIORITY_DEFAULT,
+ cancellable,
+ directory_info_ready_cb,
+ fec);
+}
+
+
+/* -- get_file_list_data -- */
+
+
+typedef struct {
+ GList *files;
+ GList *dirs;
+ GFile *directory;
+ char *base_dir;
+ GCancellable *cancellable;
+ ListReadyCallback done_func;
+ gpointer done_data;
+ GList *to_visit;
+ GList *current_dir;
+ Filter *include_filter;
+ Filter *exclude_filter;
+ Filter *exclude_folders_filter;
+ guint visit_timeout;
+} GetFileListData;
+
+
+static void
+get_file_list_data_free (GetFileListData *gfl)
+{
+ if (gfl == NULL)
+ return;
+
+ filter_destroy (gfl->include_filter);
+ filter_destroy (gfl->exclude_filter);
+ filter_destroy (gfl->exclude_folders_filter);
+ _g_string_list_free (gfl->files);
+ _g_string_list_free (gfl->dirs);
+ _g_string_list_free (gfl->to_visit);
+ g_object_unref (gfl->directory);
+ g_free (gfl->base_dir);
+ g_free (gfl);
+}
+
+
+/* -- g_directory_list_async -- */
+
+
+static GList*
+get_relative_file_list (GList *rel_list,
+ GList *file_list,
+ const char *base_dir)
+{
+ GList *scan;
+ int base_len;
+
+ if (base_dir == NULL)
+ return NULL;
+
+ base_len = 0;
+ if (strcmp (base_dir, "/") != 0)
+ base_len = strlen (base_dir);
+
+ for (scan = file_list; scan; scan = scan->next) {
+ char *uri = scan->data;
+ if (_g_uri_parent_of_uri (base_dir, uri)) {
+ char *rel_uri = g_strdup (uri + base_len + 1);
+ rel_list = g_list_prepend (rel_list, rel_uri);
+ }
+ }
+
+ return rel_list;
+}
+
+
+static GList*
+get_dir_list_from_file_list (GHashTable *h_dirs,
+ const char *base_dir,
+ GList *files,
+ gboolean is_dir_list)
+{
+ GList *scan;
+ GList *dir_list = NULL;
+ int base_dir_len;
+
+ if (base_dir == NULL)
+ base_dir = "";
+ base_dir_len = strlen (base_dir);
+
+ for (scan = files; scan; scan = scan->next) {
+ char *filename = scan->data;
+ char *dir_name;
+
+ if (strlen (filename) <= base_dir_len)
+ continue;
+
+ if (is_dir_list)
+ dir_name = g_strdup (filename + base_dir_len + 1);
+ else
+ dir_name = _g_uri_get_parent (filename + base_dir_len + 1);
+
+ while ((dir_name != NULL) && (dir_name[0] != '\0') && (strcmp (dir_name, "/") != 0)) {
+ char *tmp;
+ char *dir;
+
+ /* avoid to insert duplicated folders */
+
+ dir = g_strconcat (base_dir, "/", dir_name, NULL);
+ if (g_hash_table_lookup (h_dirs, dir) == NULL) {
+ g_hash_table_insert (h_dirs, dir, GINT_TO_POINTER (1));
+ dir_list = g_list_prepend (dir_list, dir);
+ }
+ else
+ g_free (dir);
+
+ tmp = dir_name;
+ dir_name = _g_uri_get_parent (tmp);
+ g_free (tmp);
+ }
+
+ g_free (dir_name);
+ }
+
+ return dir_list;
+}
+
+
+static void
+get_file_list_done (GError *error,
+ gpointer user_data)
+{
+ GetFileListData *gfl = user_data;
+ GHashTable *h_dirs;
+ GList *scan;
+
+ gfl->files = g_list_reverse (gfl->files);
+ gfl->dirs = g_list_reverse (gfl->dirs);
+
+ if (! filter_empty (gfl->include_filter) || (gfl->exclude_filter->pattern != NULL)) {
+ _g_string_list_free (gfl->dirs);
+ gfl->dirs = NULL;
+ }
+
+ h_dirs = g_hash_table_new (g_str_hash, g_str_equal);
+
+ /* Always include the base directory, this way empty base
+ * directories are added to the archive as well. */
+
+ if (gfl->base_dir != NULL) {
+ char *dir;
+
+ dir = g_strdup (gfl->base_dir);
+ gfl->dirs = g_list_prepend (gfl->dirs, dir);
+ g_hash_table_insert (h_dirs, dir, GINT_TO_POINTER (1));
+ }
+
+ /* Add all the parent directories in gfl->files/gfl->dirs to the
+ * gfl->dirs list, the hash table is used to avoid duplicated
+ * entries. */
+
+ for (scan = gfl->dirs; scan; scan = scan->next)
+ g_hash_table_insert (h_dirs, (char*)scan->data, GINT_TO_POINTER (1));
+
+ gfl->dirs = g_list_concat (gfl->dirs, get_dir_list_from_file_list (h_dirs, gfl->base_dir, gfl->files, FALSE));
+
+ if (filter_empty (gfl->include_filter))
+ gfl->dirs = g_list_concat (gfl->dirs, get_dir_list_from_file_list (h_dirs, gfl->base_dir, gfl->dirs, TRUE));
+
+ /**/
+
+ if (error == NULL) {
+ GList *rel_files, *rel_dirs;
+
+ if (gfl->base_dir != NULL) {
+ rel_files = get_relative_file_list (NULL, gfl->files, gfl->base_dir);
+ rel_dirs = get_relative_file_list (NULL, gfl->dirs, gfl->base_dir);
+ }
+ else {
+ rel_files = gfl->files;
+ rel_dirs = gfl->dirs;
+ gfl->files = NULL;
+ gfl->dirs = NULL;
+ }
+
+ /* rel_files/rel_dirs must be deallocated in done_func */
+ gfl->done_func (rel_files, rel_dirs, NULL, gfl->done_data);
+ }
+ else
+ gfl->done_func (NULL, NULL, error, gfl->done_data);
+
+ g_hash_table_destroy (h_dirs);
+ get_file_list_data_free (gfl);
+}
+
+
+static void
+get_file_list_for_each_file (GFile *file,
+ GFileInfo *info,
+ gpointer user_data)
+{
+ GetFileListData *gfl = user_data;
+ char *uri;
+
+ uri = g_file_get_uri (file);
+
+ switch (g_file_info_get_file_type (info)) {
+ case G_FILE_TYPE_REGULAR:
+ if (filter_matches (gfl->include_filter, uri))
+ if ((gfl->exclude_filter->pattern == NULL) || ! filter_matches (gfl->exclude_filter, uri))
+ gfl->files = g_list_prepend (gfl->files, g_strdup (uri));
+ break;
+ default:
+ break;
+ }
+
+ g_free (uri);
+}
+
+
+static DirOp
+get_file_list_start_dir (GFile *directory,
+ GFileInfo *info,
+ GError **error,
+ gpointer user_data)
+{
+ DirOp dir_op = DIR_OP_CONTINUE;
+ GetFileListData *gfl = user_data;
+ char *uri;
+
+ uri = g_file_get_uri (directory);
+ if ((gfl->exclude_folders_filter->pattern == NULL) || ! filter_matches (gfl->exclude_folders_filter, uri)) {
+ gfl->dirs = g_list_prepend (gfl->dirs, g_strdup (uri));
+ dir_op = DIR_OP_CONTINUE;
+ }
+ else
+ dir_op = DIR_OP_SKIP;
+
+ g_free (uri);
+
+ return dir_op;
+}
+
+
+void
+g_directory_list_async (GFile *directory,
+ const char *base_dir,
+ gboolean recursive,
+ gboolean follow_links,
+ gboolean no_backup_files,
+ gboolean no_dot_files,
+ const char *include_files,
+ const char *exclude_files,
+ const char *exclude_folders,
+ gboolean ignorecase,
+ GCancellable *cancellable,
+ ListReadyCallback done_func,
+ gpointer done_data)
+{
+ GetFileListData *gfl;
+ FilterOptions filter_options;
+
+ gfl = g_new0 (GetFileListData, 1);
+ gfl->directory = g_file_dup (directory);
+ gfl->base_dir = g_strdup (base_dir);
+ gfl->done_func = done_func;
+ gfl->done_data = done_data;
+
+ filter_options = FILTER_DEFAULT;
+ if (no_backup_files)
+ filter_options |= FILTER_NOBACKUPFILES;
+ if (no_dot_files)
+ filter_options |= FILTER_NODOTFILES;
+ if (ignorecase)
+ filter_options |= FILTER_IGNORECASE;
+ gfl->include_filter = filter_new (include_files, filter_options);
+ gfl->exclude_filter = filter_new (exclude_files, ignorecase ? FILTER_IGNORECASE : FILTER_DEFAULT);
+ gfl->exclude_folders_filter = filter_new (exclude_folders, ignorecase ? FILTER_IGNORECASE : FILTER_DEFAULT);
+
+ g_directory_foreach_child (directory,
+ recursive,
+ follow_links,
+ "standard::name,standard::type",
+ cancellable,
+ get_file_list_start_dir,
+ get_file_list_for_each_file,
+ get_file_list_done,
+ gfl);
+}
+
+
+/* -- g_list_items_async -- */
+
+
+static void get_items_for_current_dir (GetFileListData *gfl);
+
+
+static gboolean
+get_items_for_next_dir_idle_cb (gpointer data)
+{
+ GetFileListData *gfl = data;
+
+ g_source_remove (gfl->visit_timeout);
+ gfl->visit_timeout = 0;
+
+ gfl->current_dir = g_list_next (gfl->current_dir);
+ get_items_for_current_dir (gfl);
+
+ return FALSE;
+}
+
+
+static void
+get_items_for_current_dir_done (GList *files,
+ GList *dirs,
+ GError *error,
+ gpointer data)
+{
+ GetFileListData *gfl = data;
+
+ if (error != NULL) {
+ if (gfl->done_func)
+ gfl->done_func (NULL, NULL, error, gfl->done_data);
+ _g_string_list_free (files);
+ _g_string_list_free (dirs);
+ get_file_list_data_free (gfl);
+ return;
+ }
+
+ gfl->files = g_list_concat (gfl->files, files);
+ gfl->dirs = g_list_concat (gfl->dirs, dirs);
+
+ gfl->visit_timeout = g_idle_add (get_items_for_next_dir_idle_cb, gfl);
+}
+
+
+static void
+get_items_for_current_dir (GetFileListData *gfl)
+{
+ const char *directory_name;
+ char *directory_uri;
+ GFile *directory;
+
+ if (gfl->current_dir == NULL) {
+ if (gfl->done_func) {
+ /* gfl->files/gfl->dirs must be deallocated in gfl->done_func */
+ gfl->done_func (gfl->files, gfl->dirs, NULL, gfl->done_data);
+ gfl->files = NULL;
+ gfl->dirs = NULL;
+ }
+ get_file_list_data_free (gfl);
+ return;
+ }
+
+ directory_name = _g_uri_get_basename ((char*) gfl->current_dir->data);
+ if (strcmp (gfl->base_dir, "/") == 0)
+ directory_uri = g_strconcat (gfl->base_dir, directory_name, NULL);
+ else
+ directory_uri = g_strconcat (gfl->base_dir, "/", directory_name, NULL);
+
+ directory = g_file_new_for_uri (directory_uri);
+ g_directory_list_all_async (directory,
+ gfl->base_dir,
+ TRUE,
+ gfl->cancellable,
+ get_items_for_current_dir_done,
+ gfl);
+
+ g_object_unref (directory);
+ g_free (directory_uri);
+}
+
+
+void
+g_list_items_async (GList *items,
+ const char *base_dir,
+ GCancellable *cancellable,
+ ListReadyCallback done_func,
+ gpointer done_data)
+{
+ GetFileListData *gfl;
+ int base_len;
+ GList *scan;
+
+ g_return_if_fail (base_dir != NULL);
+
+ gfl = g_new0 (GetFileListData, 1);
+ gfl->base_dir = g_strdup (base_dir);
+ gfl->cancellable = cancellable;
+ gfl->done_func = done_func;
+ gfl->done_data = done_data;
+
+ base_len = 0;
+ if (strcmp (base_dir, "/") != 0)
+ base_len = strlen (base_dir);
+
+ for (scan = items; scan; scan = scan->next) {
+ char *path = scan->data;
+
+ /* FIXME: this is not async */
+ if (_g_uri_is_dir (path)) {
+ gfl->to_visit = g_list_prepend (gfl->to_visit, g_strdup (path));
+ }
+ else {
+ char *rel_path = g_strdup (path + base_len + 1);
+ gfl->files = g_list_prepend (gfl->files, rel_path);
+ }
+ }
+
+ gfl->current_dir = gfl->to_visit;
+ get_items_for_current_dir (gfl);
+}
+
+
+/* -- g_query_info_async -- */
+
+
+typedef struct {
+ GList *files;
+ GList *current;
+ const char *attributes;
+ GCancellable *cancellable;
+ InfoReadyCallback ready_func;
+ gpointer user_data;
+ GList *file_data;
+} QueryInfoData;
+
+
+static void
+query_data_free (QueryInfoData *query_data)
+{
+ _g_object_list_unref (query_data->files);
+ g_free (query_data);
+}
+
+
+static void
+query_info_ready_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ QueryInfoData *query_data = user_data;
+ GFile *file;
+ GFileInfo *info;
+ GError *error = NULL;
+
+ file = (GFile*) source_object;
+ info = g_file_query_info_finish (file, result, &error);
+ if (info == NULL) {
+ query_data->ready_func (NULL, error, query_data->user_data);
+ query_data_free (query_data);
+ return;
+ }
+
+ query_data->file_data = g_list_prepend (query_data->file_data, gth_file_data_new (file, info));
+ g_object_unref (info);
+
+ query_data->current = query_data->current->next;
+ if (query_data->current == NULL) {
+ query_data->file_data = g_list_reverse (query_data->file_data);
+ query_data->ready_func (query_data->file_data, NULL, query_data->user_data);
+ query_data_free (query_data);
+ return;
+ }
+
+ g_file_query_info_async ((GFile*) query_data->current->data,
+ query_data->attributes,
+ 0,
+ G_PRIORITY_DEFAULT,
+ query_data->cancellable,
+ query_info_ready_cb,
+ query_data);
+}
+
+
+void
+g_query_info_async (GList *files,
+ const char *attributes,
+ GCancellable *cancellable,
+ InfoReadyCallback ready_func,
+ gpointer user_data)
+{
+ QueryInfoData *query_data;
+
+ query_data = g_new0 (QueryInfoData, 1);
+ query_data->files = _g_object_list_ref (files);
+ query_data->attributes = attributes;
+ query_data->cancellable = cancellable;
+ query_data->ready_func = ready_func;
+ query_data->user_data = user_data;
+
+ query_data->current = query_data->files;
+
+ g_file_query_info_async ((GFile*) query_data->current->data,
+ query_data->attributes,
+ 0,
+ G_PRIORITY_DEFAULT,
+ query_data->cancellable,
+ query_info_ready_cb,
+ query_data);
+}
+
+
+/* -- g_copy_files_async -- */
+
+
+typedef struct {
+ GList *sources;
+ GList *destinations;
+ GFileCopyFlags flags;
+ int io_priority;
+ GCancellable *cancellable;
+ CopyProgressCallback progress_callback;
+ gpointer progress_callback_data;
+ CopyDoneCallback callback;
+ gpointer user_data;
+
+ GList *source;
+ GList *destination;
+ int n_file;
+ int tot_files;
+
+ guint dummy_event;
+} CopyFilesData;
+
+
+static CopyFilesData*
+copy_files_data_new (GList *sources,
+ GList *destinations,
+ GFileCopyFlags flags,
+ int io_priority,
+ GCancellable *cancellable,
+ CopyProgressCallback progress_callback,
+ gpointer progress_callback_data,
+ CopyDoneCallback callback,
+ gpointer user_data)
+{
+ CopyFilesData *cfd;
+
+ cfd = g_new0 (CopyFilesData, 1);
+ cfd->sources = _g_file_list_dup (sources);
+ cfd->destinations = _g_file_list_dup (destinations);
+ cfd->flags = flags;
+ cfd->io_priority = io_priority;
+ cfd->cancellable = cancellable;
+ cfd->progress_callback = progress_callback;
+ cfd->progress_callback_data = progress_callback_data;
+ cfd->callback = callback;
+ cfd->user_data = user_data;
+
+ cfd->source = cfd->sources;
+ cfd->destination = cfd->destinations;
+ cfd->n_file = 1;
+ cfd->tot_files = g_list_length (cfd->sources);
+
+ return cfd;
+}
+
+
+static void
+copy_files_data_free (CopyFilesData *cfd)
+{
+ if (cfd == NULL)
+ return;
+ _g_file_list_free (cfd->sources);
+ _g_file_list_free (cfd->destinations);
+ g_free (cfd);
+}
+
+
+gboolean
+_g_dummy_file_op_completed (gpointer data)
+{
+ CopyFilesData *cfd = data;
+
+ if (cfd->dummy_event != 0) {
+ g_source_remove (cfd->dummy_event);
+ cfd->dummy_event = 0;
+ }
+
+ if (cfd->callback)
+ cfd->callback (NULL, cfd->user_data);
+
+ copy_files_data_free (cfd);
+
+ return FALSE;
+}
+
+
+void
+_g_dummy_file_op_async (CopyDoneCallback callback,
+ gpointer user_data)
+{
+ CopyFilesData *cfd;
+
+ cfd = copy_files_data_new (NULL,
+ NULL,
+ 0,
+ 0,
+ NULL,
+ NULL,
+ NULL,
+ callback,
+ user_data);
+ cfd->dummy_event = g_idle_add (_g_dummy_file_op_completed, cfd);
+}
+
+
+static void g_copy_current_file (CopyFilesData *cfd);
+
+
+static void
+g_copy_next_file (CopyFilesData *cfd)
+{
+ cfd->source = g_list_next (cfd->source);
+ cfd->destination = g_list_next (cfd->destination);
+ cfd->n_file++;
+
+ g_copy_current_file (cfd);
+}
+
+
+static void
+g_copy_files_ready_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ CopyFilesData *cfd = user_data;
+ GFile *source = cfd->source->data;
+ GError *error = NULL;
+
+ if (! g_file_copy_finish (source, result, &error)) {
+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) {
+ g_clear_error (&error);
+ g_copy_next_file (cfd);
+ return;
+ }
+
+ if (cfd->callback)
+ cfd->callback (error, cfd->user_data);
+ copy_files_data_free (cfd);
+ return;
+ }
+
+ g_copy_next_file (cfd);
+}
+
+
+static void
+g_copy_files_progess_cb (goffset current_num_bytes,
+ goffset total_num_bytes,
+ gpointer user_data)
+{
+ CopyFilesData *cfd = user_data;
+
+ if (cfd->progress_callback)
+ cfd->progress_callback (cfd->n_file,
+ cfd->tot_files,
+ (GFile*) cfd->source->data,
+ (GFile*) cfd->destination->data,
+ current_num_bytes,
+ total_num_bytes,
+ cfd->progress_callback_data);
+}
+
+
+static void
+g_copy_current_file (CopyFilesData *cfd)
+{
+ if ((cfd->source == NULL) || (cfd->destination == NULL)) {
+ if (cfd->callback)
+ cfd->callback (NULL, cfd->user_data);
+ copy_files_data_free (cfd);
+ return;
+ }
+
+ g_file_copy_async ((GFile*) cfd->source->data,
+ (GFile*) cfd->destination->data,
+ cfd->flags,
+ cfd->io_priority,
+ cfd->cancellable,
+ g_copy_files_progess_cb,
+ cfd,
+ g_copy_files_ready_cb,
+ cfd);
+}
+
+
+void
+g_copy_files_async (GList *sources,
+ GList *destinations,
+ GFileCopyFlags flags,
+ int io_priority,
+ GCancellable *cancellable,
+ CopyProgressCallback progress_callback,
+ gpointer progress_callback_data,
+ CopyDoneCallback callback,
+ gpointer user_data)
+{
+ CopyFilesData *cfd;
+
+ cfd = copy_files_data_new (sources,
+ destinations,
+ flags,
+ io_priority,
+ cancellable,
+ progress_callback,
+ progress_callback_data,
+ callback,
+ user_data);
+
+ /* add the metadata sidecars if requested */
+
+ if (flags && G_FILE_COPY_ALL_METADATA) {
+ GList *source_sidecars = NULL;
+ GList *destination_sidecars = NULL;
+
+
+ gth_hook_invoke ("add-sidecars", sources, &source_sidecars);
+ gth_hook_invoke ("add-sidecars", destinations, &destination_sidecars);
+
+ source_sidecars = g_list_reverse (source_sidecars);
+ destination_sidecars = g_list_reverse (destination_sidecars);
+
+ cfd->sources = g_list_concat (cfd->sources, source_sidecars);
+ cfd->destinations = g_list_concat (cfd->destinations, destination_sidecars);
+ }
+
+ /* create the destination folders */
+
+ {
+ GHashTable *folders;
+ GList *scan;
+ GList *folder_list;
+
+ folders = g_hash_table_new_full (g_file_hash, (GEqualFunc) g_file_equal, (GDestroyNotify) g_object_unref, NULL);
+ for (scan = cfd->destinations; scan; scan = scan->next) {
+ GFile *file = scan->data;
+ GFile *folder;
+
+ folder = g_file_get_parent (file);
+ if (folder == NULL)
+ continue;
+
+ if (! g_hash_table_lookup (folders, folder))
+ g_hash_table_insert (folders, g_object_ref (folder), GINT_TO_POINTER (1));
+
+ g_object_unref (folder);
+ }
+
+ folder_list = g_hash_table_get_keys (folders);
+ for (scan = folder_list; scan; scan = scan->next) {
+ GFile *folder = scan->data;
+
+ g_file_make_directory_with_parents (folder, NULL, NULL);
+ }
+
+ g_list_free (folder_list);
+ g_hash_table_destroy (folders);
+ }
+
+ /* copy a file at a time */
+
+ g_copy_current_file (cfd);
+}
+
+
+void
+_g_copy_file_async (GFile *source,
+ GFile *destination,
+ GFileCopyFlags flags,
+ int io_priority,
+ GCancellable *cancellable,
+ CopyProgressCallback progress_callback,
+ gpointer progress_callback_data,
+ CopyDoneCallback callback,
+ gpointer user_data)
+{
+ GList *source_files;
+ GList *destination_files;
+
+ source_files = g_list_append (NULL, (gpointer) source);
+ destination_files = g_list_append (NULL, (gpointer) destination);
+
+ g_copy_files_async (source_files,
+ destination_files,
+ flags,
+ io_priority,
+ cancellable,
+ progress_callback,
+ progress_callback_data,
+ callback,
+ user_data);
+
+ g_list_free (source_files);
+ g_list_free (destination_files);
+}
+
+
+/* -- g_directory_copy_async -- */
+
+
+typedef struct {
+ GFile *source;
+ GFile *destination;
+ GFileCopyFlags flags;
+ int io_priority;
+ GCancellable *cancellable;
+ CopyProgressCallback progress_callback;
+ gpointer progress_callback_data;
+ CopyDoneCallback callback;
+ gpointer user_data;
+ GError *error;
+
+ GList *to_copy;
+ GList *current;
+ GFile *current_source;
+ GFile *current_destination;
+ int n_file, tot_files;
+ guint source_id;
+} DirectoryCopyData;
+
+
+static void
+directory_copy_data_free (DirectoryCopyData *dcd)
+{
+ if (dcd == NULL)
+ return;
+
+ g_object_unref (dcd->source);
+ g_object_unref (dcd->destination);
+ if (dcd->current_source != NULL) {
+ g_object_unref (dcd->current_source);
+ dcd->current_source = NULL;
+ }
+ if (dcd->current_destination != NULL) {
+ g_object_unref (dcd->current_destination);
+ dcd->current_destination = NULL;
+ }
+ g_list_foreach (dcd->to_copy, (GFunc) child_data_free, NULL);
+ g_list_free (dcd->to_copy);
+ g_object_unref (dcd->cancellable);
+ g_free (dcd);
+}
+
+
+static gboolean
+g_directory_copy_done (gpointer user_data)
+{
+ DirectoryCopyData *dcd = user_data;
+
+ g_source_remove (dcd->source_id);
+
+ if (dcd->callback)
+ dcd->callback (dcd->error, dcd->user_data);
+ if (dcd->error != NULL)
+ g_clear_error (&(dcd->error));
+ directory_copy_data_free (dcd);
+
+ return FALSE;
+}
+
+
+static GFile *
+get_destination_for_file (DirectoryCopyData *dcd,
+ GFile *file)
+{
+ char *uri;
+ char *source_uri;
+ char *partial_uri;
+ char *path;
+ GFile *destination_file;
+
+ if (! g_file_has_prefix (file, dcd->source))
+ return NULL;
+
+ uri = g_file_get_uri (file);
+ source_uri = g_file_get_uri (dcd->source);
+ partial_uri = uri + strlen (source_uri) + 1;
+ path = g_uri_unescape_string (partial_uri, "");
+ destination_file = _g_file_append_path (dcd->destination, path);
+
+ g_free (path);
+ g_free (partial_uri);
+ g_free (source_uri);
+ g_free (uri);
+
+ return destination_file;
+}
+
+
+static void g_directory_copy_current_child (DirectoryCopyData *dcd);
+
+
+static gboolean
+g_directory_copy_next_child (gpointer user_data)
+{
+ DirectoryCopyData *dcd = user_data;
+
+ g_source_remove (dcd->source_id);
+
+ dcd->current = g_list_next (dcd->current);
+ dcd->n_file++;
+ g_directory_copy_current_child (dcd);
+
+ return FALSE;
+}
+
+
+static void
+g_directory_copy_child_done_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ DirectoryCopyData *dcd = user_data;
+
+ if (! g_file_copy_finish ((GFile*)source_object, result, &(dcd->error))) {
+ dcd->source_id = g_idle_add (g_directory_copy_done, dcd);
+ return;
+ }
+
+ dcd->source_id = g_idle_add (g_directory_copy_next_child, dcd);
+}
+
+
+static void
+g_directory_copy_child_progess_cb (goffset current_num_bytes,
+ goffset total_num_bytes,
+ gpointer user_data)
+{
+ DirectoryCopyData *dcd = user_data;
+
+ if (dcd->progress_callback)
+ dcd->progress_callback (dcd->n_file,
+ dcd->tot_files,
+ dcd->current_source,
+ dcd->current_destination,
+ current_num_bytes,
+ total_num_bytes,
+ dcd->progress_callback_data);
+}
+
+
+static void
+g_directory_copy_current_child (DirectoryCopyData *dcd)
+{
+ ChildData *child;
+ gboolean async_op = FALSE;
+
+ if (dcd->current == NULL) {
+ dcd->source_id = g_idle_add (g_directory_copy_done, dcd);
+ return;
+ }
+
+ if (dcd->current_source != NULL) {
+ g_object_unref (dcd->current_source);
+ dcd->current_source = NULL;
+ }
+ if (dcd->current_destination != NULL) {
+ g_object_unref (dcd->current_destination);
+ dcd->current_destination = NULL;
+ }
+
+ child = dcd->current->data;
+ dcd->current_source = g_file_dup (child->file);
+ dcd->current_destination = get_destination_for_file (dcd, child->file);
+ if (dcd->current_destination == NULL) {
+ dcd->source_id = g_idle_add (g_directory_copy_next_child, dcd);
+ return;
+ }
+
+ switch (g_file_info_get_file_type (child->info)) {
+ case G_FILE_TYPE_DIRECTORY:
+ /* FIXME: how to make a directory asynchronously ? */
+
+ /* doesn't check the returned error for now, because when an
+ * error occurs the code is not returned (for example when
+ * a directory already exists the G_IO_ERROR_EXISTS code is
+ * *not* returned), so we cannot discriminate between warnings
+ * and fatal errors. (see bug #525155) */
+
+ g_file_make_directory (dcd->current_destination,
+ NULL,
+ NULL);
+
+ /*if (! g_file_make_directory (dcd->current_destination,
+ dcd->cancellable,
+ &(dcd->error)))
+ {
+ dcd->source_id = g_idle_add (g_directory_copy_done, dcd);
+ return;
+ }*/
+ break;
+ case G_FILE_TYPE_SYMBOLIC_LINK:
+ /* FIXME: how to make a link asynchronously ? */
+
+ g_file_make_symbolic_link (dcd->current_destination,
+ g_file_info_get_symlink_target (child->info),
+ NULL,
+ NULL);
+
+ /*if (! g_file_make_symbolic_link (dcd->current_destination,
+ g_file_info_get_symlink_target (child->info),
+ dcd->cancellable,
+ &(dcd->error)))
+ {
+ dcd->source_id = g_idle_add (g_directory_copy_done, dcd);
+ return;
+ }*/
+ break;
+ case G_FILE_TYPE_REGULAR:
+ g_file_copy_async (dcd->current_source,
+ dcd->current_destination,
+ dcd->flags,
+ dcd->io_priority,
+ dcd->cancellable,
+ g_directory_copy_child_progess_cb,
+ dcd,
+ g_directory_copy_child_done_cb,
+ dcd);
+ async_op = TRUE;
+ break;
+ default:
+ break;
+ }
+
+ if (! async_op)
+ dcd->source_id = g_idle_add (g_directory_copy_next_child, dcd);
+}
+
+
+static gboolean
+g_directory_copy_start_copying (gpointer user_data)
+{
+ DirectoryCopyData *dcd = user_data;
+
+ g_source_remove (dcd->source_id);
+
+ dcd->to_copy = g_list_reverse (dcd->to_copy);
+ dcd->current = dcd->to_copy;
+ dcd->n_file = 1;
+ g_directory_copy_current_child (dcd);
+
+ return FALSE;
+}
+
+
+static void
+g_directory_copy_list_ready (GError *error,
+ gpointer user_data)
+{
+ DirectoryCopyData *dcd = user_data;
+
+ if (error != NULL) {
+ dcd->error = g_error_copy (error);
+ dcd->source_id = g_idle_add (g_directory_copy_done, dcd);
+ return;
+ }
+
+ dcd->source_id = g_idle_add (g_directory_copy_start_copying, dcd);
+}
+
+
+static void
+g_directory_copy_for_each_file (GFile *file,
+ GFileInfo *info,
+ gpointer user_data)
+{
+ DirectoryCopyData *dcd = user_data;
+
+ dcd->to_copy = g_list_prepend (dcd->to_copy, child_data_new (file, info));
+ dcd->tot_files++;
+}
+
+
+static DirOp
+g_directory_copy_start_dir (GFile *directory,
+ GFileInfo *info,
+ GError **error,
+ gpointer user_data)
+{
+ DirectoryCopyData *dcd = user_data;
+
+ dcd->to_copy = g_list_prepend (dcd->to_copy, child_data_new (directory, info));
+ dcd->tot_files++;
+
+ return DIR_OP_CONTINUE;
+}
+
+
+void
+g_directory_copy_async (GFile *source,
+ GFile *destination,
+ GFileCopyFlags flags,
+ int io_priority,
+ GCancellable *cancellable,
+ CopyProgressCallback progress_callback,
+ gpointer progress_callback_data,
+ CopyDoneCallback callback,
+ gpointer user_data)
+{
+ DirectoryCopyData *dcd;
+
+ dcd = g_new0 (DirectoryCopyData, 1);
+ dcd->source = g_file_dup (source);
+ dcd->destination = g_file_dup (destination);
+ dcd->flags = flags;
+ dcd->io_priority = io_priority;
+ dcd->cancellable = cancellable;
+ dcd->progress_callback = progress_callback;
+ dcd->progress_callback_data = progress_callback_data;
+ dcd->callback = callback;
+ dcd->user_data = user_data;
+
+ g_directory_foreach_child (dcd->source,
+ TRUE,
+ TRUE,
+ "standard::name,standard::type",
+ dcd->cancellable,
+ g_directory_copy_start_dir,
+ g_directory_copy_for_each_file,
+ g_directory_copy_list_ready,
+ dcd);
+}
+
+
+gboolean
+_g_delete_files (GList *file_list,
+ gboolean include_metadata,
+ GError **error)
+{
+ GList *scan;
+
+ for (scan = file_list; scan; scan = scan->next) {
+ GFile *file = scan->data;
+
+ if (! g_file_delete (file, NULL, error))
+ return FALSE;
+ }
+
+ if (include_metadata) {
+ GList *sidecars = NULL;
+
+ gth_hook_invoke ("add-sidecars", file_list, &sidecars);
+ sidecars = g_list_reverse (sidecars);
+ for (scan = sidecars; scan; scan = scan->next) {
+ GFile *file = scan->data;
+
+ g_file_delete (file, NULL, NULL);
+ }
+
+ _g_object_list_unref (sidecars);
+ }
+
+ return TRUE;
+}
+
+
+#define BUFFER_SIZE 4096
+
+
+gboolean
+g_load_file_in_buffer (GFile *file,
+ void **buffer,
+ gsize *size,
+ GError **error)
+{
+ GFileInputStream *istream;
+ gboolean retval;
+ void *local_buffer;
+ gsize count;
+ gssize n;
+ char tmp_buffer[BUFFER_SIZE];
+
+ istream = g_file_read (file, NULL, error);
+ if (istream == NULL)
+ return FALSE;
+
+ retval = FALSE;
+ local_buffer = NULL;
+ count = 0;
+ for (;;) {
+ n = g_input_stream_read (G_INPUT_STREAM (istream), tmp_buffer, BUFFER_SIZE, NULL, error);
+ if (n < 0) {
+ g_free (local_buffer);
+ retval = FALSE;
+ break;
+ }
+ else if (n == 0) {
+ *buffer = local_buffer;
+ *size = count;
+ retval = TRUE;
+ break;
+ }
+
+ local_buffer = g_realloc (local_buffer, count + n + 1);
+ memcpy (local_buffer + count, tmp_buffer, n);
+ count += n;
+ }
+
+ g_object_unref (istream);
+
+ return retval;
+}
+
+
+typedef struct {
+ int io_priority;
+ GCancellable *cancellable;
+ BufferReadyCallback callback;
+ gpointer user_data;
+ GInputStream *stream;
+ char tmp_buffer[BUFFER_SIZE];
+ void *buffer;
+ gsize count;
+} LoadData;
+
+
+static void
+load_data_free (LoadData *load_data)
+{
+ if (load_data->stream != NULL)
+ g_object_unref (load_data->stream);
+ g_free (load_data->buffer);
+ g_free (load_data);
+}
+
+
+static void
+load_file__stream_read_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ LoadData *load_data = user_data;
+ GError *error = NULL;
+ gssize count;
+
+ count = g_input_stream_read_finish (load_data->stream, result, &error);
+ if (count < 0) {
+ load_data->callback (NULL, -1, error, load_data->user_data);
+ load_data_free (load_data);
+ return;
+ }
+ else if (count == 0) {
+ if (load_data->buffer != NULL)
+ ((char *)load_data->buffer)[load_data->count] = 0;
+ load_data->callback (load_data->buffer, load_data->count, NULL, load_data->user_data);
+ load_data_free (load_data);
+ return;
+ }
+
+ load_data->buffer = g_realloc (load_data->buffer, load_data->count + count + 1);
+ memcpy (load_data->buffer + load_data->count, load_data->tmp_buffer, count);
+ load_data->count += count;
+
+ g_input_stream_read_async (load_data->stream,
+ load_data->tmp_buffer,
+ BUFFER_SIZE,
+ load_data->io_priority,
+ load_data->cancellable,
+ load_file__stream_read_cb,
+ load_data);
+}
+
+
+static void
+load_file__file_read_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ LoadData *load_data = user_data;
+ GError *error = NULL;
+
+ load_data->stream = (GInputStream *) g_file_read_finish (G_FILE (source_object), result, &error);
+ if (load_data->stream == NULL) {
+ load_data->callback (NULL, -1, error, load_data->user_data);
+ load_data_free (load_data);
+ return;
+ }
+
+ load_data->count = 0;
+ g_input_stream_read_async (load_data->stream,
+ load_data->tmp_buffer,
+ BUFFER_SIZE,
+ load_data->io_priority,
+ load_data->cancellable,
+ load_file__stream_read_cb,
+ load_data);
+}
+
+
+void
+g_load_file_async (GFile *file,
+ int io_priority,
+ GCancellable *cancellable,
+ BufferReadyCallback callback,
+ gpointer user_data)
+{
+ LoadData *load_data;
+
+ load_data = g_new0 (LoadData, 1);
+ load_data->io_priority = io_priority;
+ load_data->cancellable = cancellable;
+ load_data->callback = callback;
+ load_data->user_data = user_data;
+
+ g_file_read_async (file, io_priority, cancellable, load_file__file_read_cb, load_data);
+}
+
+
+/* -- g_write_file_async -- */
+
+
+typedef struct {
+ int io_priority;
+ GCancellable *cancellable;
+ BufferReadyCallback callback;
+ gpointer user_data;
+ void *buffer;
+ gsize count;
+ gsize written;
+ GError *error;
+} WriteData;
+
+
+static void
+write_data_free (WriteData *write_data)
+{
+ g_free (write_data);
+}
+
+
+static void
+write_file__notify (gpointer user_data)
+{
+ WriteData *write_data = user_data;
+
+ write_data->callback (write_data->buffer, write_data->count, write_data->error, write_data->user_data);
+ write_data_free (write_data);
+}
+
+
+static void
+write_file__stream_flush_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GOutputStream *stream = (GOutputStream *) source_object;
+ WriteData *write_data = user_data;
+ GError *error = NULL;
+
+ g_output_stream_flush_finish (stream, result, &error);
+ write_data->error = error;
+ g_object_unref (stream);
+
+ call_when_idle (write_file__notify, write_data);
+}
+
+
+static void
+write_file__stream_write_ready_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GOutputStream *stream = (GOutputStream *) source_object;
+ WriteData *write_data = user_data;
+ GError *error = NULL;
+ gssize count;
+
+ count = g_output_stream_write_finish (stream, result, &error);
+ write_data->written += count;
+
+ if ((count == 0) || (write_data->written == write_data->count)) {
+ g_output_stream_flush_async (stream,
+ write_data->io_priority,
+ write_data->cancellable,
+ write_file__stream_flush_cb,
+ user_data);
+ return;
+ }
+
+ g_output_stream_write_async (stream,
+ write_data->buffer + write_data->written,
+ write_data->count - write_data->written,
+ write_data->io_priority,
+ write_data->cancellable,
+ write_file__stream_write_ready_cb,
+ write_data);
+}
+
+
+static void
+write_file__replace_ready_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ WriteData *write_data = user_data;
+ GOutputStream *stream;
+ GError *error = NULL;
+
+ stream = (GOutputStream*) g_file_replace_finish ((GFile*) source_object, result, &error);
+ if (stream == NULL) {
+ write_data->callback (write_data->buffer, write_data->count, error, write_data->user_data);
+ write_data_free (write_data);
+ return;
+ }
+
+ write_data->written = 0;
+ g_output_stream_write_async (stream,
+ write_data->buffer,
+ write_data->count,
+ write_data->io_priority,
+ write_data->cancellable,
+ write_file__stream_write_ready_cb,
+ write_data);
+}
+
+
+void
+g_write_file_async (GFile *file,
+ void *buffer,
+ gsize count,
+ int io_priority,
+ GCancellable *cancellable,
+ BufferReadyCallback callback,
+ gpointer user_data)
+{
+ WriteData *write_data;
+
+ write_data = g_new0 (WriteData, 1);
+ write_data->buffer = buffer;
+ write_data->count = count;
+ write_data->io_priority = io_priority;
+ write_data->cancellable = cancellable;
+ write_data->callback = callback;
+ write_data->user_data = user_data;
+
+ g_file_replace_async (file, NULL, FALSE, 0, io_priority, cancellable, write_file__replace_ready_cb, write_data);
+}
+
+
+GFile *
+_g_file_create_unique (GFile *parent,
+ const char *display_name,
+ const char *suffix,
+ GError **error)
+{
+ GFile *file = NULL;
+ GError *local_error = NULL;
+ int n;
+ GFileOutputStream *stream;
+
+ file = g_file_get_child_for_display_name (parent, display_name, &local_error);
+ n = 0;
+ do {
+ char *new_display_name;
+
+ if (file != NULL)
+ g_object_unref (file);
+
+ n++;
+ if (n == 1)
+ new_display_name = g_strdup_printf ("%s%s", display_name, suffix);
+ else
+ new_display_name = g_strdup_printf ("%s %d%s", display_name, n, suffix);
+
+ file = g_file_get_child_for_display_name (parent, new_display_name, &local_error);
+ if (local_error == NULL)
+ stream = g_file_create (file, 0, NULL, &local_error);
+
+ if ((stream == NULL) && g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_EXISTS))
+ g_clear_error (&local_error);
+
+ g_free (new_display_name);
+ }
+ while ((stream == NULL) && (local_error == NULL));
+
+ if (stream == NULL) {
+ g_object_unref (file);
+ file = NULL;
+ }
+ else
+ g_object_unref (stream);
+
+ return file;
+}
+
+
+GFile *
+_g_directory_create_unique (GFile *parent,
+ const char *display_name,
+ const char *suffix,
+ GError **error)
+{
+ GFile *file = NULL;
+ gboolean created = FALSE;
+ GError *local_error = NULL;
+ int n;
+
+ file = g_file_get_child_for_display_name (parent, display_name, &local_error);
+ n = 0;
+ do {
+ char *new_display_name;
+
+ if (file != NULL)
+ g_object_unref (file);
+
+ n++;
+ if (n == 1)
+ new_display_name = g_strdup_printf ("%s%s", display_name, suffix);
+ else
+ new_display_name = g_strdup_printf ("%s %d%s", display_name, n, suffix);
+
+ file = g_file_get_child_for_display_name (parent, new_display_name, &local_error);
+ if (local_error == NULL)
+ created = g_file_make_directory (file, NULL, &local_error);
+
+ if (! created && g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_EXISTS))
+ g_clear_error (&local_error);
+
+ g_free (new_display_name);
+ }
+ while (! created && (local_error == NULL));
+
+ return file;
+}
+
+
+GFileType
+_g_file_get_standard_type (GFile *file)
+{
+ GFileType result;
+ GFileInfo *info;
+ GError *error = NULL;
+
+ info = g_file_query_info (file, G_FILE_ATTRIBUTE_STANDARD_TYPE, 0, NULL, &error);
+ if (error == NULL) {
+ result = g_file_info_get_file_type (info);
+ }
+ else {
+ result = G_FILE_ATTRIBUTE_TYPE_INVALID;
+ g_error_free (error);
+ }
+
+ g_object_unref (info);
+
+ return result;
+}
+
+
+
+/* -- g_write_file -- */
+
+
+gboolean
+g_write_file (GFile *file,
+ gboolean make_backup,
+ GFileCreateFlags flags,
+ void *buffer,
+ gsize count,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean success;
+ GOutputStream *stream;
+
+ stream = (GOutputStream *) g_file_replace (file, NULL, make_backup, flags, cancellable, error);
+ if (stream != NULL)
+ success = g_output_stream_write_all (stream, buffer, count, NULL, cancellable, error);
+ else
+ success = FALSE;
+
+ _g_object_unref (stream);
+
+ return success;
+}
+
+
+gboolean
+_g_directory_make (GFile *file,
+ guint32 unix_mode,
+ GError **error)
+{
+ if (! g_file_make_directory (file, NULL, error)) {
+ if (! (*error)->code == G_IO_ERROR_EXISTS)
+ return FALSE;
+ g_clear_error (error);
+ }
+
+ return g_file_set_attribute_uint32 (file,
+ G_FILE_ATTRIBUTE_UNIX_MODE,
+ unix_mode,
+ G_FILE_QUERY_INFO_NONE,
+ NULL,
+ error);
+}
diff --git a/gthumb/gio-utils.h b/gthumb/gio-utils.h
new file mode 100644
index 0000000..cd42ff9
--- /dev/null
+++ b/gthumb/gio-utils.h
@@ -0,0 +1,193 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2008 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef _GIO_UTILS_H
+#define _GIO_UTILS_H
+
+#include <glib.h>
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+/* callback types */
+
+typedef enum { /*< skip >*/
+ DIR_OP_CONTINUE,
+ DIR_OP_SKIP,
+ DIR_OP_STOP
+} DirOp;
+
+typedef DirOp (*StartDirCallback) (GFile *directory,
+ GFileInfo *info,
+ GError **error,
+ gpointer user_data);
+typedef void (*ForEachChildCallback) (GFile *file,
+ GFileInfo *info,
+ gpointer user_data);
+typedef void (*ForEachDoneCallback) (GError *error,
+ gpointer data);
+typedef void (*ListReadyCallback) (GList *files,
+ GList *dirs,
+ GError *error,
+ gpointer user_data);
+typedef void (*CopyProgressCallback) (goffset current_file,
+ goffset total_files,
+ GFile *source,
+ GFile *destination,
+ goffset current_num_bytes,
+ goffset total_num_bytes,
+ gpointer user_data);
+typedef void (*CopyDoneCallback) (GError *error,
+ gpointer user_data);
+typedef void (*BufferReadyCallback) (void *buffer,
+ gsize count,
+ GError *error,
+ gpointer user_data);
+typedef void (*InfoReadyCallback) (GList *files,
+ GError *error,
+ gpointer user_data);
+
+/* asynchronous recursive list functions */
+
+void g_directory_foreach_child (GFile *directory,
+ gboolean recursive,
+ gboolean follow_links,
+ const char *attributes,
+ GCancellable *cancellable,
+ StartDirCallback start_dir_func,
+ ForEachChildCallback for_each_file_func,
+ ForEachDoneCallback done_func,
+ gpointer user_data);
+void g_directory_list_async (GFile *directory,
+ const char *base_dir,
+ gboolean recursive,
+ gboolean follow_links,
+ gboolean no_backup_files,
+ gboolean no_dot_files,
+ const char *include_files,
+ const char *exclude_files,
+ const char *exclude_folders,
+ gboolean ignorecase,
+ GCancellable *cancellable,
+ ListReadyCallback done_func,
+ gpointer done_data);
+void g_list_items_async (GList *items,
+ const char *base_dir,
+ GCancellable *cancellable,
+ ListReadyCallback done_func,
+ gpointer done_data);
+void g_query_info_async (GList *files,
+ const char *attributes,
+ GCancellable *cancellable,
+ InfoReadyCallback ready_func,
+ gpointer user_data);
+
+/* asynchronous copy functions */
+
+void _g_dummy_file_op_async (CopyDoneCallback callback,
+ gpointer user_data);
+void g_copy_files_async (GList *sources,
+ GList *destinations,
+ GFileCopyFlags flags,
+ int io_priority,
+ GCancellable *cancellable,
+ CopyProgressCallback progress_callback,
+ gpointer progress_callback_data,
+ CopyDoneCallback callback,
+ gpointer user_data);
+void _g_copy_file_async (GFile *source,
+ GFile *destination,
+ GFileCopyFlags flags,
+ int io_priority,
+ GCancellable *cancellable,
+ CopyProgressCallback progress_callback,
+ gpointer progress_callback_data,
+ CopyDoneCallback callback,
+ gpointer user_data);
+void g_directory_copy_async (GFile *source,
+ GFile *destination,
+ GFileCopyFlags flags,
+ int io_priority,
+ GCancellable *cancellable,
+ CopyProgressCallback progress_callback,
+ gpointer progress_callback_data,
+ CopyDoneCallback callback,
+ gpointer user_data);
+gboolean _g_delete_files (GList *file_list,
+ gboolean include_metadata,
+ GError **error);
+gboolean g_load_file_in_buffer (GFile *file,
+ void **buffer,
+ gsize *size,
+ GError **error);
+void g_load_file_async (GFile *file,
+ int io_priority,
+ GCancellable *cancellable,
+ BufferReadyCallback callback,
+ gpointer user_data);
+gboolean g_write_file (GFile *file,
+ gboolean make_backup,
+ GFileCreateFlags flags,
+ void *buffer,
+ gsize count,
+ GCancellable *cancellable,
+ GError **error);
+void g_write_file_async (GFile *file,
+ void *buffer,
+ gsize count,
+ int io_priority,
+ GCancellable *cancellable,
+ BufferReadyCallback callback,
+ gpointer user_data);
+GFile * _g_file_create_unique (GFile *parent,
+ const char *display_name,
+ const char *suffix,
+ GError **error);
+GFile * _g_directory_create_unique (GFile *parent,
+ const char *display_name,
+ const char *suffix,
+ GError **error);
+GFileType
+ _g_file_get_standard_type (GFile *file);
+
+/* convenience macros */
+
+/**
+ * g_directory_list_all_async:
+ * @directory:
+ * @base_dir:
+ * @recursive:
+ * @cancellable:
+ * @done_func:
+ * @done_data:
+ *
+ */
+#define g_directory_list_all_async(directory, base_dir, recursive, cancellable, done_func, done_data) \
+ g_directory_list_async ((directory), (base_dir), (recursive), TRUE, FALSE, FALSE, NULL, NULL, NULL, FALSE, (cancellable), (done_func), (done_data))
+
+gboolean _g_directory_make (GFile *file,
+ guint32 unix_mode,
+ GError **error);
+
+G_END_DECLS
+
+#endif /* _GIO_UTILS_H */
diff --git a/gthumb/glib-utils.c b/gthumb/glib-utils.c
new file mode 100644
index 0000000..c56559e
--- /dev/null
+++ b/gthumb/glib-utils.c
@@ -0,0 +1,1895 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2001-2008 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <glib.h>
+#include <glib/gprintf.h>
+#include <gio/gio.h>
+#include "glib-utils.h"
+
+#define MAX_PATTERNS 128
+
+
+/* gobject utils*/
+
+
+void
+_g_object_unref (gpointer object)
+{
+ if (object != NULL)
+ g_object_unref (object);
+}
+
+
+GList *
+_g_object_list_ref (GList *list)
+{
+ GList *new_list;
+
+ if (list == NULL)
+ return NULL;
+
+ new_list = g_list_copy (list);
+ g_list_foreach (new_list, (GFunc) g_object_ref, NULL);
+
+ return new_list;
+}
+
+
+void
+_g_object_list_unref (GList *list)
+{
+ g_list_foreach (list, (GFunc) g_object_unref, NULL);
+ g_list_free (list);
+}
+
+
+GType
+g_object_list_get_type (void)
+{
+ static GType type = 0;
+
+ if (type == 0)
+ type = g_boxed_type_register_static ("GObjectList",
+ (GBoxedCopyFunc) _g_object_list_ref,
+ (GBoxedFreeFunc) _g_object_list_unref);
+
+ return type;
+}
+
+
+GEnumValue *
+_g_enum_type_get_value (GType enum_type,
+ int value)
+{
+ GEnumClass *class;
+ GEnumValue *enum_value;
+
+ class = G_ENUM_CLASS (g_type_class_ref (enum_type));
+ enum_value = g_enum_get_value (class, value);
+ g_type_class_unref (class);
+
+ return enum_value;
+}
+
+
+GEnumValue *
+_g_enum_type_get_value_by_nick (GType enum_type,
+ const char *nick)
+{
+ GEnumClass *class;
+ GEnumValue *enum_value;
+
+ class = G_ENUM_CLASS (g_type_class_ref (enum_type));
+ enum_value = g_enum_get_value_by_nick (class, nick);
+ g_type_class_unref (class);
+
+ return enum_value;
+}
+
+
+/* idle callback */
+
+
+IdleCall*
+idle_call_new (DoneFunc func,
+ gpointer data)
+{
+ IdleCall *call = g_new0 (IdleCall, 1);
+ call->func = func;
+ call->data = data;
+ return call;
+}
+
+
+void
+idle_call_free (IdleCall *call)
+{
+ g_free (call);
+}
+
+
+static gboolean
+idle_call_exec_cb (gpointer data)
+{
+ IdleCall *call = data;
+ (*call->func) (call->data);
+ return FALSE;
+}
+
+
+guint
+idle_call_exec (IdleCall *call,
+ gboolean use_idle_cb)
+{
+ if (use_idle_cb)
+ return g_idle_add_full (G_PRIORITY_DEFAULT_IDLE,
+ idle_call_exec_cb,
+ call,
+ (GDestroyNotify) idle_call_free);
+ else {
+ (*call->func) (call->data);
+ return 0;
+ }
+}
+
+
+guint
+call_when_idle (DoneFunc func,
+ gpointer data)
+{
+ return idle_call_exec (idle_call_new (func, data), TRUE);
+}
+
+
+typedef struct {
+ gpointer object;
+ ReadyCallback ready_func;
+ gpointer user_data;
+ GError *error;
+ guint id;
+} ObjectReadyData;
+
+
+static gboolean
+exec_object_ready_func (gpointer user_data)
+{
+ ObjectReadyData *data = user_data;
+
+ g_source_remove (data->id);
+ data->ready_func (G_OBJECT (data->object), data->error, data->user_data);
+ g_free (data);
+
+ return FALSE;
+}
+
+
+void
+object_ready_with_error (gpointer object,
+ ReadyCallback ready_func,
+ gpointer user_data,
+ GError *error)
+{
+ ObjectReadyData *data;
+
+ data = g_new0 (ObjectReadyData, 1);
+
+ data->object = object;
+ data->ready_func = ready_func;
+ data->user_data = user_data;
+ data->error = error;
+ data->id = g_idle_add (exec_object_ready_func, data);
+}
+
+
+typedef struct {
+ ReadyFunc ready_func;
+ gpointer user_data;
+ GError *error;
+ guint id;
+} ReadyData;
+
+
+static gboolean
+exec_ready_func (gpointer user_data)
+{
+ ReadyData *data = user_data;
+
+ g_source_remove (data->id);
+ data->ready_func (data->error, data->user_data);
+ g_free (data);
+
+ return FALSE;
+}
+
+
+void
+ready_with_error (ReadyFunc ready_func,
+ gpointer user_data,
+ GError *error)
+{
+ ReadyData *data;
+
+ data = g_new0 (ReadyData, 1);
+ data->ready_func = ready_func;
+ data->user_data = user_data;
+ data->error = error;
+ data->id = g_idle_add (exec_ready_func, data);
+}
+
+
+/* debug */
+
+
+void
+debug (const char *file,
+ int line,
+ const char *function,
+ const char *format,
+ ...)
+{
+#ifdef DEBUG
+ static gboolean first_time = 0;
+ static gboolean print_debug_info = 0;
+ va_list args;
+ char *str;
+
+ if (! first_time) {
+ first_time = 1;
+ if (g_getenv ("GTHUMB_DEBUG"))
+ print_debug_info = 1;
+ }
+
+ if (! print_debug_info)
+ return;
+
+ g_return_if_fail (format != NULL);
+
+ va_start (args, format);
+ str = g_strdup_vprintf (format, args);
+ va_end (args);
+
+ g_fprintf (stderr, "[%s] %s:%d (%s):\n\t%s\n", g_get_prgname(), file, line, function, str);
+
+ g_free (str);
+#endif
+}
+
+
+void
+performance (const char *file,
+ int line,
+ const char *function,
+ const char *format,
+ ...)
+{
+#ifdef DEBUG
+ va_list args;
+ char *formatted, *str, *filename;
+
+ filename = g_path_get_basename (file);
+
+ va_start (args, format);
+ formatted = g_strdup_vprintf (format, args);
+ va_end (args);
+
+ str = g_strdup_printf ("MARK: %s: (%s:%d) [%s] : %s", g_get_prgname(), filename, line, function, formatted);
+ g_free (formatted);
+
+ access (str, F_OK);
+ g_free (str);
+ g_free (filename);
+#endif
+}
+
+
+/* GTimeVal utils */
+
+
+int
+_g_time_val_cmp (GTimeVal *a,
+ GTimeVal *b)
+{
+ if (a->tv_sec == b->tv_sec) {
+ if (a->tv_usec == b->tv_usec)
+ return 0;
+ else
+ return a->tv_usec > b->tv_usec ? 1 : -1;
+ }
+ else if (a->tv_sec > b->tv_sec)
+ return 1;
+ else
+ return -1;
+}
+
+
+void
+_g_time_val_reset (GTimeVal *time_)
+{
+ time_->tv_sec = 0;
+ time_->tv_usec = 0;
+}
+
+
+gboolean
+_g_time_val_from_exif_date (const char *exif_date,
+ GTimeVal *time_)
+{
+ struct tm tm;
+ long val;
+
+ g_return_val_if_fail (exif_date != NULL, FALSE);
+ g_return_val_if_fail (time_ != NULL, FALSE);
+
+ while (g_ascii_isspace (*exif_date))
+ exif_date++;
+
+ if (*exif_date == '\0')
+ return FALSE;
+
+ if (! g_ascii_isdigit (*exif_date))
+ return FALSE;
+
+ /* YYYY */
+
+ val = g_ascii_strtoull (exif_date, (char **)&exif_date, 10);
+ tm.tm_year = val - 1900;
+
+ if (*exif_date != ':')
+ return FALSE;
+
+ /* MM */
+
+ exif_date++;
+ tm.tm_mon = g_ascii_strtoull (exif_date, (char **)&exif_date, 10) - 1;
+
+ if (*exif_date != ':')
+ return FALSE;
+
+ /* DD */
+
+ exif_date++;
+ tm.tm_mday = g_ascii_strtoull (exif_date, (char **)&exif_date, 10);
+
+ if (*exif_date != ' ')
+ return FALSE;
+
+ /* hh */
+
+ val = g_ascii_strtoull (exif_date, (char **)&exif_date, 10);
+ tm.tm_hour = val;
+
+ if (*exif_date != ':')
+ return FALSE;
+
+ /* mm */
+
+ exif_date++;
+ tm.tm_min = g_ascii_strtoull (exif_date, (char **)&exif_date, 10);
+
+ if (*exif_date != ':')
+ return FALSE;
+
+ /* ss */
+
+ exif_date++;
+ tm.tm_sec = strtoul (exif_date, (char **)&exif_date, 10);
+
+ time_->tv_sec = mktime (&tm);
+ time_->tv_usec = 0;
+
+ /* usec */
+
+ if ((*exif_date == ',') || (*exif_date == '.')) {
+ glong mul = 100000;
+
+ while (g_ascii_isdigit (*++exif_date)) {
+ time_->tv_usec += (*exif_date - '0') * mul;
+ mul /= 10;
+ }
+ }
+
+ while (g_ascii_isspace (*exif_date))
+ exif_date++;
+
+ return *exif_date == '\0';
+}
+
+
+char *
+_g_time_val_to_exif_date (GTimeVal *time_)
+{
+ char *retval;
+ struct tm *tm;
+ time_t secs;
+
+ g_return_val_if_fail (time_->tv_usec >= 0 && time_->tv_usec < G_USEC_PER_SEC, NULL);
+
+ secs = time_->tv_sec;
+ tm = localtime (&secs);
+
+ retval = g_strdup_printf ("%4d:%02d:%02d %02d:%02d:%02d",
+ tm->tm_year + 1900,
+ tm->tm_mon + 1,
+ tm->tm_mday,
+ tm->tm_hour,
+ tm->tm_min,
+ tm->tm_sec);
+
+ return retval;
+}
+
+
+/* Bookmark file utils */
+
+
+void
+_g_bookmark_file_clear (GBookmarkFile *bookmark)
+{
+ char **uris;
+ int i;
+
+ uris = g_bookmark_file_get_uris (bookmark, NULL);
+ for (i = 0; uris[i] != NULL; i++)
+ g_bookmark_file_remove_item (bookmark, uris[i], NULL);
+ g_strfreev (uris);
+}
+
+
+void
+_g_bookmark_file_add_uri (GBookmarkFile *bookmark,
+ const char *uri)
+{
+ g_bookmark_file_set_is_private (bookmark, uri,TRUE);
+ g_bookmark_file_add_application (bookmark, uri, NULL, NULL);
+}
+
+
+void
+_g_bookmark_file_set_uris (GBookmarkFile *bookmark,
+ GList *uri_list)
+{
+ GList *scan;
+
+ _g_bookmark_file_clear (bookmark);
+ for (scan = uri_list; scan; scan = scan->next)
+ _g_bookmark_file_add_uri (bookmark, scan->data);
+}
+
+
+/* String utils */
+
+
+void
+_g_strset (char **s,
+ const char *value)
+{
+ if (*s == value)
+ return;
+
+ if (*s != NULL) {
+ g_free (*s);
+ *s = NULL;
+ }
+
+ if (value != NULL)
+ *s = g_strdup (value);
+}
+
+
+char *
+_g_strdup_with_max_size (const char *s,
+ int max_size)
+{
+ char *result;
+ int l = strlen (s);
+
+ if (l > max_size) {
+ char *first_half;
+ char *second_half;
+ int offset;
+ int half_max_size = max_size / 2 + 1;
+
+ first_half = g_strndup (s, half_max_size);
+ offset = half_max_size + l - max_size;
+ second_half = g_strndup (s + offset, half_max_size);
+
+ result = g_strconcat (first_half, "...", second_half, NULL);
+
+ g_free (first_half);
+ g_free (second_half);
+ } else
+ result = g_strdup (s);
+
+ return result;
+}
+
+
+/**
+ * example 1 : "xxx##yy#" --> [0] = xxx
+ * [1] = ##
+ * [2] = yy
+ * [3] = #
+ * [4] = NULL
+ *
+ * example 2 : "" --> [0] = NULL
+ **/
+char **
+_g_get_template_from_text (const char *utf8_template)
+{
+ const char *chunk_start = utf8_template;
+ char **str_vect;
+ GList *str_list = NULL, *scan;
+ int n = 0;
+
+ if (utf8_template == NULL)
+ return NULL;
+
+ while (*chunk_start != 0) {
+ gunichar ch;
+ gboolean reading_sharps;
+ char *chunk;
+ const char *chunk_end;
+ int chunk_len = 0;
+
+ reading_sharps = (g_utf8_get_char (chunk_start) == '#');
+ chunk_end = chunk_start;
+
+ ch = g_utf8_get_char (chunk_end);
+ while (reading_sharps
+ && (*chunk_end != 0)
+ && (ch == '#')) {
+ chunk_end = g_utf8_next_char (chunk_end);
+ ch = g_utf8_get_char (chunk_end);
+ chunk_len++;
+ }
+
+ ch = g_utf8_get_char (chunk_end);
+ while (! reading_sharps
+ && (*chunk_end != 0)
+ && (*chunk_end != '#')) {
+ chunk_end = g_utf8_next_char (chunk_end);
+ ch = g_utf8_get_char (chunk_end);
+ chunk_len++;
+ }
+
+ chunk = _g_utf8_strndup (chunk_start, chunk_len);
+ str_list = g_list_prepend (str_list, chunk);
+ n++;
+
+ chunk_start = chunk_end;
+ }
+
+ str_vect = g_new (char*, n + 1);
+
+ str_vect[n--] = NULL;
+ for (scan = str_list; scan; scan = scan->next)
+ str_vect[n--] = scan->data;
+
+ g_list_free (str_list);
+
+ return str_vect;
+}
+
+
+char *
+_g_get_name_from_template (char **utf8_template,
+ int n)
+{
+ GString *s;
+ int i;
+ char *result;
+
+ s = g_string_new (NULL);
+
+ for (i = 0; utf8_template[i] != NULL; i++) {
+ const char *chunk = utf8_template[i];
+ gunichar ch = g_utf8_get_char (chunk);
+
+ if (ch != '#')
+ g_string_append (s, chunk);
+ else {
+ char *s_n;
+ int s_n_len;
+ int sharps_len = g_utf8_strlen (chunk, -1);
+
+ s_n = g_strdup_printf ("%d", n);
+ s_n_len = strlen (s_n);
+
+ while (s_n_len < sharps_len) {
+ g_string_append_c (s, '0');
+ sharps_len--;
+ }
+
+ g_string_append (s, s_n);
+ g_free (s_n);
+ }
+ }
+
+ result = s->str;
+ g_string_free (s, FALSE);
+
+ return result;
+}
+
+
+char *
+_g_replace (const char *str,
+ const char *from_str,
+ const char *to_str)
+{
+ char **tokens;
+ int i;
+ GString *gstr;
+
+ if (str == NULL)
+ return NULL;
+
+ if (from_str == NULL)
+ return g_strdup (str);
+
+ if (strcmp (str, from_str) == 0)
+ return g_strdup (to_str);
+
+ tokens = g_strsplit (str, from_str, -1);
+
+ gstr = g_string_new (NULL);
+ for (i = 0; tokens[i] != NULL; i++) {
+ gstr = g_string_append (gstr, tokens[i]);
+ if ((to_str != NULL) && (tokens[i+1] != NULL))
+ gstr = g_string_append (gstr, to_str);
+ }
+
+ return g_string_free (gstr, FALSE);
+}
+
+
+char *
+_g_replace_pattern (const char *utf8_text,
+ gunichar pattern,
+ const char *value)
+{
+ const char *s;
+ GString *r;
+ char *r_str;
+
+ if (utf8_text == NULL)
+ return NULL;
+
+ if (g_utf8_strchr (utf8_text, -1, '%') == NULL)
+ return g_strdup (utf8_text);
+
+ r = g_string_new (NULL);
+ for (s = utf8_text; *s != 0; s = g_utf8_next_char (s)) {
+ gunichar ch = g_utf8_get_char (s);
+
+ if (ch == '%') {
+ s = g_utf8_next_char (s);
+
+ if (*s == 0) {
+ g_string_append_unichar (r, ch);
+ break;
+ }
+
+ ch = g_utf8_get_char (s);
+ if (ch == pattern) {
+ if (value)
+ g_string_append (r, value);
+ }
+ else {
+ g_string_append (r, "%");
+ g_string_append_unichar (r, ch);
+ }
+
+ } else
+ g_string_append_unichar (r, ch);
+ }
+
+ r_str = r->str;
+ g_string_free (r, FALSE);
+
+ return r_str;
+}
+
+
+char *
+_g_utf8_replace (const char *string,
+ const char *pattern,
+ const char *replacement)
+{
+ GRegex *regex;
+ char *result;
+
+ if (string == NULL)
+ return NULL;
+
+ regex = g_regex_new (pattern, 0, 0, NULL);
+ if (regex == NULL)
+ return NULL;
+
+ result = g_regex_replace_literal (regex, string, -1, 0, replacement, 0, NULL);
+
+ g_regex_unref (regex);
+
+ return result;
+}
+
+
+char *
+_g_utf8_strndup (const char *str,
+ gsize n)
+{
+ const char *s = str;
+ char *result;
+
+ while (n && *s) {
+ s = g_utf8_next_char (s);
+ n--;
+ }
+
+ result = g_strndup (str, s - str);
+
+ return result;
+}
+
+
+static const char *
+_g_utf8_strstr (const char *haystack,
+ const char *needle)
+{
+ const char *s;
+ gsize i;
+ gsize haystack_len = g_utf8_strlen (haystack, -1);
+ gsize needle_len = g_utf8_strlen (needle, -1);
+ int needle_size = strlen (needle);
+
+ s = haystack;
+ for (i = 0; i <= haystack_len - needle_len; i++) {
+ if (strncmp (s, needle, needle_size) == 0)
+ return s;
+ s = g_utf8_next_char(s);
+ }
+
+ return NULL;
+}
+
+
+char **
+_g_utf8_strsplit (const char *string,
+ const char *delimiter,
+ int max_tokens)
+{
+ GSList *string_list = NULL, *slist;
+ char **str_array;
+ const char *s;
+ guint n = 0;
+ const char *remainder;
+
+ g_return_val_if_fail (string != NULL, NULL);
+ g_return_val_if_fail (delimiter != NULL, NULL);
+ g_return_val_if_fail (delimiter[0] != '\0', NULL);
+
+ if (max_tokens < 1)
+ max_tokens = G_MAXINT;
+
+ remainder = string;
+ s = _g_utf8_strstr (remainder, delimiter);
+ if (s != NULL) {
+ gsize delimiter_size = strlen (delimiter);
+
+ while (--max_tokens && (s != NULL)) {
+ gsize size = s - remainder;
+ char *new_string;
+
+ new_string = g_new (char, size + 1);
+ strncpy (new_string, remainder, size);
+ new_string[size] = 0;
+
+ string_list = g_slist_prepend (string_list, new_string);
+ n++;
+ remainder = s + delimiter_size;
+ s = _g_utf8_strstr (remainder, delimiter);
+ }
+ }
+ if (*string) {
+ n++;
+ string_list = g_slist_prepend (string_list, g_strdup (remainder));
+ }
+
+ str_array = g_new (char*, n + 1);
+
+ str_array[n--] = NULL;
+ for (slist = string_list; slist; slist = slist->next)
+ str_array[n--] = slist->data;
+
+ g_slist_free (string_list);
+
+ return str_array;
+}
+
+
+char *
+_g_utf8_strstrip (const char *str)
+{
+ if (str == NULL)
+ return NULL;
+ return g_strstrip (g_strdup (str));
+}
+
+
+gboolean
+_g_utf8_all_spaces (const char *utf8_string)
+{
+ gunichar c;
+
+ if (utf8_string == NULL)
+ return TRUE;
+
+ c = g_utf8_get_char (utf8_string);
+ while (c != 0) {
+ if (! g_unichar_isspace (c))
+ return FALSE;
+ utf8_string = g_utf8_next_char (utf8_string);
+ c = g_utf8_get_char (utf8_string);
+ }
+
+ return TRUE;
+}
+
+
+GList *
+_g_list_insert_list_before (GList *list1,
+ GList *sibling,
+ GList *list2)
+{
+ if (!list2)
+ {
+ return list1;
+ }
+ else if (!list1)
+ {
+ return list2;
+ }
+ else if (sibling)
+ {
+ GList *list2_last = g_list_last (list2);
+ if (sibling->prev)
+ {
+ sibling->prev->next = list2;
+ list2->prev = sibling->prev;
+ sibling->prev = list2_last;
+ list2_last->next = sibling;
+ return list1;
+ }
+ else
+ {
+ sibling->prev = list2_last;
+ list2_last->next = sibling;
+ return list2;
+ }
+ }
+ else
+ {
+ return g_list_concat (list1, list2);
+ }
+}
+
+
+GHashTable *static_strings = NULL;
+
+
+const char *
+get_static_string (const char *s)
+{
+ const char *result;
+
+ if (s == NULL)
+ return NULL;
+
+ if (static_strings == NULL)
+ static_strings = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+
+ if (! g_hash_table_lookup_extended (static_strings, s, (gpointer) &result, NULL)) {
+ result = g_strdup (s);
+ g_hash_table_insert (static_strings,
+ (gpointer) result,
+ GINT_TO_POINTER (1));
+ }
+
+ return result;
+}
+
+
+char *
+_g_rand_string (int len)
+{
+ static char *alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
+ static int letters_only = 52;
+ static int whole_alphabet = 62;
+ char *s;
+ GRand *rand_gen;
+ int i;
+
+ s = g_malloc (sizeof (char) * (len + 1));
+ rand_gen = g_rand_new ();
+ for (i = 0; i < len; i++)
+ s[i] = alphabet[g_rand_int_range (rand_gen, 0, (i == 0) ? letters_only : whole_alphabet)];
+ g_rand_free (rand_gen);
+ s[len] = 0;
+
+ return s;
+}
+
+
+/* Regexp utils */
+
+static char **
+get_patterns_from_pattern (const char *pattern_string)
+{
+ char **patterns;
+ int i;
+
+ if (pattern_string == NULL)
+ return NULL;
+
+ patterns = _g_utf8_strsplit (pattern_string, ";", MAX_PATTERNS);
+ for (i = 0; patterns[i] != NULL; i++) {
+ char *p1, *p2;
+
+ p1 = _g_utf8_strstrip (patterns[i]);
+ p2 = _g_replace (p1, ".", "\\.");
+ patterns[i] = _g_replace (p2, "*", ".*");
+
+ g_free (p2);
+ g_free (p1);
+ }
+
+ return patterns;
+}
+
+
+GRegex **
+get_regexps_from_pattern (const char *pattern_string,
+ GRegexCompileFlags compile_options)
+{
+ char **patterns;
+ GRegex **regexps;
+ int i;
+
+ patterns = get_patterns_from_pattern (pattern_string);
+ if (patterns == NULL)
+ return NULL;
+
+ regexps = g_new0 (GRegex*, g_strv_length (patterns) + 1);
+ for (i = 0; patterns[i] != NULL; i++)
+ regexps[i] = g_regex_new (patterns[i],
+ G_REGEX_OPTIMIZE | compile_options,
+ G_REGEX_MATCH_NOTEMPTY,
+ NULL);
+ g_strfreev (patterns);
+
+ return regexps;
+}
+
+
+gboolean
+string_matches_regexps (GRegex **regexps,
+ const char *string,
+ GRegexMatchFlags match_options)
+{
+ gboolean matched;
+ int i;
+
+ if ((regexps == NULL) || (regexps[0] == NULL))
+ return TRUE;
+
+ if (string == NULL)
+ return FALSE;
+
+ matched = FALSE;
+ for (i = 0; regexps[i] != NULL; i++)
+ if (g_regex_match (regexps[i], string, match_options, NULL)) {
+ matched = TRUE;
+ break;
+ }
+
+ return matched;
+}
+
+
+void
+free_regexps (GRegex **regexps)
+{
+ int i;
+
+ if (regexps == NULL)
+ return;
+
+ for (i = 0; regexps[i] != NULL; i++)
+ g_regex_unref (regexps[i]);
+ g_free (regexps);
+}
+
+
+/* URI utils */
+
+
+const char *
+get_home_uri (void)
+{
+ static char *home_uri = NULL;
+
+ if (home_uri == NULL) {
+ const char *path;
+ char *uri;
+
+ path = g_get_home_dir ();
+ uri = g_uri_escape_string (path, G_URI_RESERVED_CHARS_ALLOWED_IN_PATH, TRUE);
+
+ home_uri = g_strconcat ("file://", uri, NULL);
+
+ g_free (uri);
+ }
+
+ return home_uri;
+}
+
+
+const char *
+get_desktop_uri (void)
+{
+ static char *desktop_uri = NULL;
+
+ if (desktop_uri == NULL) {
+ char *path;
+ char *uri;
+
+ path = xdg_user_dir_lookup ("DESKTOP");
+ uri = g_uri_escape_string (path, G_URI_RESERVED_CHARS_ALLOWED_IN_PATH, TRUE);
+
+ desktop_uri = g_strconcat ("file://", uri, NULL);
+
+ g_free (uri);
+ g_free (path);
+ }
+
+ return desktop_uri;
+}
+
+
+char *
+xdg_user_dir_lookup (const char *type)
+{
+ /* This function is used by gthumb to determine the default "PICTURES"
+ directory for imports. The actually directory name is localized.
+ Bug 425365. */
+
+ FILE *file;
+ char *home_dir, *config_home, *config_file;
+ char buffer[512];
+ char *user_dir;
+ char *p, *d;
+ int len;
+ int relative;
+
+ home_dir = getenv ("HOME");
+ if (home_dir == NULL)
+ return strdup ("/tmp");
+
+ config_home = getenv ("XDG_CONFIG_HOME");
+ if (config_home == NULL || config_home[0] == 0) {
+ config_file = malloc (strlen (home_dir) + strlen ("/.config/user-dirs.dirs") + 1);
+ strcpy (config_file, home_dir);
+ strcat (config_file, "/.config/user-dirs.dirs");
+ }
+ else {
+ config_file = malloc (strlen (config_home) + strlen ("/user-dirs.dirs") + 1);
+ strcpy (config_file, config_home);
+ strcat (config_file, "/user-dirs.dirs");
+ }
+
+ file = fopen (config_file, "r");
+ free (config_file);
+ if (file == NULL)
+ goto error;
+
+ user_dir = NULL;
+
+ while (fgets (buffer, sizeof (buffer), file)) {
+ /* Remove newline at end */
+ len = strlen (buffer);
+ if (len > 0 && buffer[len-1] == '\n')
+ buffer[len-1] = 0;
+
+ p = buffer;
+ while (*p == ' ' || *p == '\t')
+ p++;
+
+ if (strncmp (p, "XDG_", 4) != 0)
+ continue;
+ p += 4;
+
+ if (strncmp (p, type, strlen (type)) != 0)
+ continue;
+ p += strlen (type);
+
+ if (strncmp (p, "_DIR", 4) != 0)
+ continue;
+ p += 4;
+
+ while (*p == ' ' || *p == '\t')
+ p++;
+
+ if (*p != '=')
+ continue;
+ p++;
+
+ while (*p == ' ' || *p == '\t')
+ p++;
+
+ if (*p != '"')
+ continue;
+ p++;
+
+ relative = 0;
+ if (strncmp (p, "$HOME/", 6) == 0) {
+ p += 6;
+ relative = 1;
+ } else if (*p != '/')
+ continue;
+
+ if (relative) {
+ user_dir = malloc (strlen (home_dir) + 1 + strlen (p) + 1);
+ strcpy (user_dir, home_dir);
+ strcat (user_dir, "/");
+ } else {
+ user_dir = malloc (strlen (p) + 1);
+ *user_dir = 0;
+ }
+
+ d = user_dir + strlen (user_dir);
+ while (*p && *p != '"') {
+ if ((*p == '\\') && (*(p+1) != 0))
+ p++;
+ *d++ = *p++;
+ }
+ *d = 0;
+ }
+
+ fclose (file);
+
+ if (user_dir)
+ return user_dir;
+
+error:
+ /* Special case desktop for historical compatibility */
+ if (strcmp (type, "DESKTOP") == 0) {
+ user_dir = malloc (strlen (home_dir) + strlen ("/Desktop") + 1);
+ strcpy (user_dir, home_dir);
+ strcat (user_dir, "/Desktop");
+ return user_dir;
+ }
+ else
+ return strdup (home_dir);
+}
+
+
+int
+uricmp (const char *uri1,
+ const char *uri2)
+{
+ if (uri1 == NULL) {
+ if (uri2 == NULL)
+ return 0;
+ else
+ return -1;
+ }
+
+ if (uri2 == NULL) {
+ if (uri1 == NULL)
+ return 0;
+ else
+ return 1;
+ }
+
+ return g_strcmp0 (uri1, uri2);
+}
+
+
+gboolean
+same_uri (const char *uri1,
+ const char *uri2)
+{
+ return uricmp (uri1, uri2) == 0;
+}
+
+
+void
+_g_string_list_free (GList *string_list)
+{
+ if (string_list == NULL)
+ return;
+ g_list_foreach (string_list, (GFunc) g_free, NULL);
+ g_list_free (string_list);
+
+}
+
+
+GList *
+_g_string_list_dup (GList *string_list)
+{
+ GList *new_list = NULL;
+ GList *scan;
+
+ for (scan = string_list; scan; scan = scan->next)
+ new_list = g_list_prepend (new_list, g_strdup (scan->data));
+
+ return g_list_reverse (new_list);
+}
+
+
+GType
+g_string_list_get_type (void)
+{
+ static GType type = 0;
+
+ if (type == 0)
+ type = g_boxed_type_register_static ("GStringList",
+ (GBoxedCopyFunc) _g_string_list_dup,
+ (GBoxedFreeFunc) _g_string_list_free);
+
+ return type;
+}
+
+
+GList *
+get_file_list_from_url_list (char *url_list)
+{
+ GList *list = NULL;
+ int i;
+ char *url_start, *url_end;
+
+ url_start = url_list;
+ while (url_start[0] != '\0') {
+ char *url;
+
+ if (strncmp (url_start, "file:", 5) == 0) {
+ url_start += 5;
+ if ((url_start[0] == '/')
+ && (url_start[1] == '/')) url_start += 2;
+ }
+
+ i = 0;
+ while ((url_start[i] != '\0')
+ && (url_start[i] != '\r')
+ && (url_start[i] != '\n')) i++;
+ url_end = url_start + i;
+
+ url = g_strndup (url_start, url_end - url_start);
+ list = g_list_prepend (list, url);
+
+ url_start = url_end;
+ i = 0;
+ while ((url_start[i] != '\0')
+ && ((url_start[i] == '\r')
+ || (url_start[i] == '\n'))) i++;
+ url_start += i;
+ }
+
+ return g_list_reverse (list);
+}
+
+
+const char *
+_g_uri_get_basename (const char *uri)
+{
+ register char *base;
+ register gssize last_char;
+
+ if (uri == NULL)
+ return NULL;
+
+ if (uri[0] == '\0')
+ return "";
+
+ last_char = strlen (uri) - 1;
+
+ if (uri[last_char] == G_DIR_SEPARATOR)
+ return "";
+
+ base = g_utf8_strrchr (uri, -1, G_DIR_SEPARATOR);
+ if (! base)
+ return uri;
+
+ return base + 1;
+}
+
+
+const char *
+_g_uri_get_file_extension (const char *uri)
+{
+ int len;
+ int p;
+ const char *ptr = uri;
+
+ if (uri == NULL)
+ return NULL;
+
+ len = strlen (uri);
+ if (len <= 1)
+ return NULL;
+
+ p = len - 1;
+ while ((p >= 0) && (ptr[p] != '.'))
+ p--;
+
+ if (p < 0)
+ return NULL;
+
+ return uri + p;
+}
+
+
+static gboolean
+uri_is_filetype (const char *uri,
+ GFileType file_type)
+{
+ gboolean result = FALSE;
+ GFile *file;
+ GFileInfo *info;
+ GError *error = NULL;
+
+ file = g_file_new_for_uri (uri);
+
+ if (! g_file_query_exists (file, NULL)) {
+ g_object_unref (file);
+ return FALSE;
+ }
+
+ info = g_file_query_info (file, G_FILE_ATTRIBUTE_STANDARD_TYPE, 0, NULL, &error);
+ if (error == NULL) {
+ result = (g_file_info_get_file_type (info) == file_type);
+ }
+ else {
+ g_warning ("Failed to get file type for uri %s: %s", uri, error->message);
+ g_error_free (error);
+ }
+
+ g_object_unref (info);
+ g_object_unref (file);
+
+ return result;
+}
+
+
+gboolean
+_g_uri_is_file (const char *uri)
+{
+ return uri_is_filetype (uri, G_FILE_TYPE_REGULAR);
+}
+
+
+gboolean
+_g_uri_is_dir (const char *uri)
+{
+ return uri_is_filetype (uri, G_FILE_TYPE_DIRECTORY);
+}
+
+
+gboolean
+_g_uri_parent_of_uri (const char *dirname,
+ const char *filename)
+{
+ int dirname_l, filename_l, separator_position;
+
+ if ((dirname == NULL) || (filename == NULL))
+ return FALSE;
+
+ dirname_l = strlen (dirname);
+ filename_l = strlen (filename);
+
+ if ((dirname_l == filename_l + 1)
+ && (dirname[dirname_l - 1] == '/'))
+ return FALSE;
+
+ if ((filename_l == dirname_l + 1)
+ && (filename[filename_l - 1] == '/'))
+ return FALSE;
+
+ if (dirname[dirname_l - 1] == '/')
+ separator_position = dirname_l - 1;
+ else
+ separator_position = dirname_l;
+
+ return ((filename_l > dirname_l)
+ && (strncmp (dirname, filename, dirname_l) == 0)
+ && (filename[separator_position] == '/'));
+}
+
+
+char *
+_g_uri_get_parent (const char *uri)
+{
+ int p;
+ const char *ptr = uri;
+ char *new_uri;
+
+ if (uri == NULL)
+ return NULL;
+
+ p = strlen (uri) - 1;
+ if (p < 0)
+ return NULL;
+
+ while ((p > 0) && (ptr[p] != '/'))
+ p--;
+ if ((p == 0) && (ptr[p] == '/'))
+ p++;
+ new_uri = g_strndup (uri, (guint)p);
+
+ return new_uri;
+}
+
+
+char *
+_g_uri_remove_extension (const char *uri)
+{
+ int len;
+ int p;
+ char *new_path;
+
+ if (uri == NULL)
+ return NULL;
+
+ len = strlen (uri);
+ if (len == 1)
+ return g_strdup (uri);
+
+ p = len - 1;
+ while ((p > 0) && (uri[p] != '.'))
+ p--;
+ if (p == 0)
+ p = len;
+ new_path = g_strndup (uri, (guint) p);
+
+ return new_path;
+}
+
+
+char *
+_g_build_uri (const char *base, ...)
+{
+ va_list args;
+ const char *child;
+ GString *uri;
+
+ uri = g_string_new (base);
+
+ va_start (args, base);
+ while ((child = va_arg (args, const char *)) != NULL) {
+ if (! g_str_has_suffix (uri->str, "/") && ! g_str_has_prefix (child, "/"))
+ g_string_append (uri, "/");
+ g_string_append (uri, child);
+ }
+ va_end (args);
+
+ return g_string_free (uri, FALSE);
+}
+
+
+/* GIO utils */
+
+
+char *
+_g_file_get_display_name (GFile *file)
+{
+ char *name = NULL;
+ GFileInfo *file_info;
+
+ file_info = g_file_query_info (file,
+ G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME,
+ G_FILE_QUERY_INFO_NONE,
+ NULL,
+ NULL);
+ if (file_info != NULL)
+ name = g_strdup (g_file_info_get_attribute_string (file_info, G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME));
+ else
+ name = g_file_get_uri (file);
+
+ return name;
+}
+
+
+GFile *
+_g_file_get_child (GFile *file,
+ ...)
+{
+ va_list args;
+ const char *name;
+ GFile *child;
+
+ child = g_object_ref (file);
+
+ va_start (args, file);
+ while ((name = va_arg (args, const char *)) != NULL) {
+ GFile *tmp;
+
+ tmp = g_file_get_child (child, name);
+ g_object_unref (child);
+ child = tmp;
+ }
+ va_end (args);
+
+ return child;
+}
+
+
+GIcon *
+_g_file_get_icon (GFile *file)
+{
+ GIcon *icon = NULL;
+ GFileInfo *file_info;
+
+ file_info = g_file_query_info (file,
+ G_FILE_ATTRIBUTE_STANDARD_ICON,
+ G_FILE_QUERY_INFO_NONE,
+ NULL,
+ NULL);
+ if (file_info != NULL)
+ icon = (GIcon*) g_file_info_get_attribute_object (file_info, G_FILE_ATTRIBUTE_STANDARD_ICON);
+ else
+ icon = g_themed_icon_new ("file");
+
+ return icon;
+}
+
+
+GList *
+_g_file_list_dup (GList *l)
+{
+ GList *r = NULL, *scan;
+ for (scan = l; scan; scan = scan->next)
+ r = g_list_prepend (r, g_file_dup ((GFile*) scan->data));
+ return g_list_reverse (r);
+}
+
+
+void
+_g_file_list_free (GList *l)
+{
+ GList *scan;
+ for (scan = l; scan; scan = scan->next)
+ g_object_unref (scan->data);
+ g_list_free (l);
+}
+
+
+GList *
+_g_file_list_new_from_uri_list (GList *uris)
+{
+ GList *r = NULL, *scan;
+ for (scan = uris; scan; scan = scan->next)
+ r = g_list_prepend (r, g_file_new_for_uri ((char*)scan->data));
+ return g_list_reverse (r);
+}
+
+
+GList *
+_g_file_list_find_file (GList *l,
+ GFile *file)
+{
+ GList *scan;
+
+ for (scan = l; scan; scan = scan->next)
+ if (g_file_equal (file, (GFile *) scan->data))
+ return scan;
+ return NULL;
+}
+
+
+const char*
+_g_file_get_mime_type (GFile *file,
+ gboolean fast_file_type)
+{
+ GFileInfo *info;
+ GError *err = NULL;
+ const char *result = NULL;
+
+ info = g_file_query_info (file,
+ fast_file_type ?
+ G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE :
+ G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE,
+ 0, NULL, &err);
+ if (info == NULL) {
+ char *uri;
+
+ uri = g_file_get_uri (file);
+ g_warning ("could not get content type for %s: %s", uri, err->message);
+ g_free (uri);
+ g_clear_error (&err);
+ }
+ else {
+ result = get_static_string (g_content_type_get_mime_type (g_file_info_get_content_type (info)));
+ g_object_unref (info);
+ }
+
+ return result;
+}
+
+
+void
+_g_file_get_modification_time (GFile *file,
+ GTimeVal *result)
+{
+ GFileInfo *info;
+ GError *err = NULL;
+
+ result->tv_sec = 0;
+ result->tv_usec = 0;
+
+ info = g_file_query_info (file,
+ G_FILE_ATTRIBUTE_TIME_MODIFIED "," G_FILE_ATTRIBUTE_TIME_MODIFIED_USEC,
+ 0,
+ NULL,
+ &err);
+ if (info != NULL) {
+ g_file_info_get_modification_time (info, result);
+ g_object_unref (info);
+ }
+ else {
+ char *uri;
+
+ uri = g_file_get_uri (file);
+ g_warning ("could not get modification time for %s: %s", uri, err->message);
+ g_free (uri);
+ g_clear_error (&err);
+ }
+}
+
+
+time_t
+_g_file_get_mtime (GFile *file)
+{
+ GTimeVal timeval;
+
+ _g_file_get_modification_time (file, &timeval);
+ return (time_t) timeval.tv_sec;
+}
+
+
+int
+_g_file_cmp_uris (GFile *a,
+ GFile *b)
+{
+ return g_file_equal (a, b) ? 0 : 1;
+}
+
+
+int
+_g_file_cmp_modification_time (GFile *file_a,
+ GFile *file_b)
+{
+ GTimeVal timeval_a;
+ GTimeVal timeval_b;
+
+ _g_file_get_modification_time (file_a, &timeval_a);
+ _g_file_get_modification_time (file_b, &timeval_b);
+
+ return _g_time_val_cmp (&timeval_a, &timeval_b);
+}
+
+
+goffset
+_g_file_get_size (GFile *file)
+{
+ GFileInfo *info;
+ GError *err = NULL;
+ goffset size = 0;
+
+ info = g_file_query_info (file, G_FILE_ATTRIBUTE_FILESYSTEM_SIZE, 0, NULL, &err);
+ if (info != NULL) {
+ size = g_file_info_get_size (info);
+ g_object_unref (info);
+ }
+ else {
+ char *uri;
+
+ uri = g_file_get_uri (file);
+ g_warning ("could not get size for %s: %s", uri, err->message);
+ g_free (uri);
+ g_clear_error (&err);
+ }
+
+ return size;
+}
+
+
+#define MAX_SYMLINKS 32
+
+
+static GFile *
+resolve_symlinks (GFile *file,
+ GError **error,
+ int level)
+{
+ GFile *resolved;
+ char *path;
+ char **names;
+ int i;
+
+ if (level > MAX_SYMLINKS) {
+ char *uri;
+
+ uri = g_file_get_uri (file);
+ *error = g_error_new (G_IO_ERROR, G_IO_ERROR_TOO_MANY_LINKS, "Too many symbolic links for file: %s.", uri);
+ g_free (uri);
+
+ return NULL;
+ }
+
+ path = g_file_get_path (file);
+ if (path == NULL) {
+ char *uri;
+
+ uri = g_file_get_uri (file);
+ *error = g_error_new (G_IO_ERROR, G_IO_ERROR_FAILED, "No local pathname for file: %s.", uri);
+ g_free (uri);
+
+ return NULL;
+ }
+
+ resolved = g_file_new_for_path (G_DIR_SEPARATOR_S);
+
+ names = g_strsplit (path, G_DIR_SEPARATOR_S, -1);
+ for (i = 0; names[i] != NULL; i++) {
+ GFile *child;
+ GFileInfo *info;
+ GFile *new_resolved;
+
+ if (strcmp (names[i], "") == 0)
+ continue;
+
+ child = g_file_get_child (resolved, names[i]);
+ info = g_file_query_info (child, G_FILE_ATTRIBUTE_STANDARD_IS_SYMLINK "," G_FILE_ATTRIBUTE_STANDARD_SYMLINK_TARGET, 0, NULL, error);
+ if (info == NULL) {
+ g_object_unref (child);
+ g_object_unref (resolved);
+ resolved = NULL;
+ break;
+ }
+
+ /* if names[i] isn't a symbolic link add it to the resolved path and continue */
+
+ if (! g_file_info_get_is_symlink (info)) {
+ g_object_unref (info);
+ g_object_unref (resolved);
+ resolved = child;
+ continue;
+ }
+
+ g_object_unref (child);
+
+ /* names[i] is a symbolic link */
+
+ new_resolved = g_file_resolve_relative_path (resolved, g_file_info_get_symlink_target (info));
+
+ g_object_unref (resolved);
+ g_object_unref (info);
+
+ resolved = resolve_symlinks (new_resolved, error, level + 1);
+
+ g_object_unref (new_resolved);
+ }
+
+ g_strfreev (names);
+ g_free (path);
+
+ return resolved;
+}
+
+
+GFile *
+_g_file_resolve_all_symlinks (GFile *file,
+ GError **error)
+{
+ return resolve_symlinks (file, error, 0);
+}
+
+
+GFile *
+_g_file_append_prefix (GFile *file,
+ const char *prefix)
+{
+ char *uri;
+ char *new_uri;
+ GFile *new_file;
+
+ uri = g_file_get_uri (file);
+ new_uri = g_strconcat (prefix, uri, NULL);
+ new_file = g_file_new_for_uri (new_uri);
+
+ g_free (new_uri);
+ g_free (uri);
+
+ return new_file;
+}
+
+
+GFile *
+_g_file_append_path (GFile *file,
+ const char *path)
+{
+ char *uri;
+ char *escaped;
+ char *new_uri;
+ GFile *new_file;
+
+ uri = g_file_get_uri (file);
+ escaped = g_uri_escape_string (path, G_URI_RESERVED_CHARS_ALLOWED_IN_PATH, FALSE);
+ new_uri = _g_build_uri (uri, escaped, NULL);
+ new_file = g_file_new_for_uri (new_uri);
+
+ g_free (new_uri);
+ g_free (escaped);
+ g_free (uri);
+
+ return new_file;
+}
+
+
+gboolean
+_g_mime_type_is_image (const char *mime_type)
+{
+ g_return_val_if_fail (mime_type != NULL, FALSE);
+
+ /* Valid image mime types:
+ 1. All *image* types,
+ 2. application/x-crw
+ This is a RAW photo file, which for some reason
+ uses an "application" prefix instead of "image".
+ */
+
+ return (g_content_type_is_a (mime_type, "image/*")
+ || (strcmp (mime_type, "application/x-crw") == 0));
+}
+
+
+gboolean
+_g_mime_type_is_video (const char *mime_type)
+{
+ g_return_val_if_fail (mime_type != NULL, FALSE);
+
+ return (g_content_type_is_a (mime_type, "video/*")
+ || (strcmp (mime_type, "application/ogg") == 0));
+}
+
+
+gboolean
+_g_mime_type_is_audio (const char *mime_type)
+{
+ g_return_val_if_fail (mime_type != NULL, FALSE);
+
+ return g_content_type_is_a (mime_type, "audio/*");
+}
diff --git a/gthumb/glib-utils.h b/gthumb/glib-utils.h
new file mode 100644
index 0000000..6fe0964
--- /dev/null
+++ b/gthumb/glib-utils.h
@@ -0,0 +1,216 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2001-2008 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef _GLIB_UTILS_H
+#define _GLIB_UTILS_H
+
+#include <glib.h>
+#include <glib-object.h>
+#include <gio/gio.h>
+#include "typedefs.h"
+
+G_BEGIN_DECLS
+
+#define IROUND(x) ((int)floor(((double)x) + 0.5))
+#define FLOAT_EQUAL(a,b) (fabs (a - b) < 1e-6)
+#define ID_LENGTH 8
+#define G_TYPE_OBJECT_LIST (g_object_list_get_type ())
+#define G_TYPE_STRING_LIST (g_string_list_get_type ())
+
+/* signals */
+
+#define g_signal_handlers_disconnect_by_data(instance, data) \
+ g_signal_handlers_disconnect_matched ((instance), G_SIGNAL_MATCH_DATA, \
+ 0, 0, NULL, NULL, (data))
+#define g_signal_handlers_block_by_data(instance, data) \
+ g_signal_handlers_block_matched ((instance), G_SIGNAL_MATCH_DATA, \
+ 0, 0, NULL, NULL, (data))
+#define g_signal_handlers_unblock_by_data(instance, data) \
+ g_signal_handlers_unblock_matched ((instance), G_SIGNAL_MATCH_DATA, \
+ 0, 0, NULL, NULL, (data))
+
+/* gobject utils*/
+
+void _g_object_unref (gpointer object);
+GList * _g_object_list_ref (GList *list);
+void _g_object_list_unref (GList *list);
+GType g_object_list_get_type (void);
+GEnumValue * _g_enum_type_get_value (GType enum_type,
+ int value);
+GEnumValue * _g_enum_type_get_value_by_nick (GType enum_type,
+ const char *nick);
+
+/* idle callback */
+
+typedef struct {
+ DoneFunc func;
+ gpointer data;
+} IdleCall;
+
+
+IdleCall* idle_call_new (DoneFunc func,
+ gpointer data);
+void idle_call_free (IdleCall *call);
+guint idle_call_exec (IdleCall *call,
+ gboolean use_idle_cb);
+guint call_when_idle (DoneFunc func,
+ gpointer data);
+void object_ready_with_error (gpointer object,
+ ReadyCallback ready_func,
+ gpointer user_data,
+ GError *error);
+void ready_with_error (ReadyFunc ready_func,
+ gpointer user_data,
+ GError *error);
+
+/* debug */
+
+void debug (const char *file,
+ int line,
+ const char *function,
+ const char *format,
+ ...);
+void performance (const char *file,
+ int line,
+ const char *function,
+ const char *format,
+ ...);
+
+#define DEBUG_INFO __FILE__, __LINE__, G_STRFUNC
+
+/* GTimeVal utils */
+
+int _g_time_val_cmp (GTimeVal *a,
+ GTimeVal *b);
+void _g_time_val_reset (GTimeVal *time_);
+gboolean _g_time_val_from_exif_date (const char *exif_date,
+ GTimeVal *time_);
+char * _g_time_val_to_exif_date (GTimeVal *time_);
+
+/* Bookmark file utils */
+
+void _g_bookmark_file_clear (GBookmarkFile *bookmark);
+void _g_bookmark_file_add_uri (GBookmarkFile *bookmark,
+ const char *uri);
+void _g_bookmark_file_set_uris (GBookmarkFile *bookmark,
+ GList *uri_list);
+
+/* String utils */
+
+void _g_strset (char **s,
+ const char *value);
+char * _g_strdup_with_max_size (const char *s,
+ int max_size);
+char ** _g_get_template_from_text (const char *s_template);
+char * _g_get_name_from_template (char **s_template,
+ int num);
+char * _g_replace (const char *str,
+ const char *from_str,
+ const char *to_str);
+char * _g_replace_pattern (const char *utf8_text,
+ gunichar pattern,
+ const char *value);
+char * _g_utf8_replace (const char *string,
+ const char *pattern,
+ const char *replacement);
+char * _g_utf8_strndup (const char *str,
+ gsize n);
+char ** _g_utf8_strsplit (const char *string,
+ const char *delimiter,
+ int max_tokens);
+char * _g_utf8_strstrip (const char *str);
+gboolean _g_utf8_all_spaces (const char *utf8_string);
+GList * _g_list_insert_list_before (GList *list1,
+ GList *sibling,
+ GList *list2);
+const char * get_static_string (const char *s);
+char * _g_rand_string (int len);
+
+/* Regexp utils */
+
+GRegex ** get_regexps_from_pattern (const char *pattern_string,
+ GRegexCompileFlags compile_options);
+gboolean string_matches_regexps (GRegex **regexps,
+ const char *string,
+ GRegexMatchFlags match_options);
+void free_regexps (GRegex **regexps);
+
+
+/* URI utils */
+
+const char * get_home_uri (void);
+const char * get_desktop_uri (void);
+char * xdg_user_dir_lookup (const char *type);
+int uricmp (const char *uri1,
+ const char *uri2);
+gboolean same_uri (const char *uri1,
+ const char *uri2);
+void _g_string_list_free (GList *string_list);
+GList * _g_string_list_dup (GList *string_list);
+GType g_string_list_get_type (void);
+GList * get_file_list_from_url_list (char *url_list);
+const char * _g_uri_get_basename (const char *uri);
+const char * _g_uri_get_file_extension (const char *uri);
+gboolean _g_uri_is_file (const char *uri);
+gboolean _g_uri_is_dir (const char *uri);
+gboolean _g_uri_parent_of_uri (const char *dir,
+ const char *file);
+char * _g_uri_get_parent (const char *uri);
+char * _g_uri_remove_extension (const char *uri);
+char * _g_build_uri (const char *base,
+ ...);
+
+/* GIO utils */
+
+char * _g_file_get_display_name (GFile *file);
+GFile * _g_file_get_child (GFile *file,
+ ...);
+GIcon * _g_file_get_icon (GFile *file);
+GList * _g_file_list_dup (GList *l);
+void _g_file_list_free (GList *l);
+GList * _g_file_list_new_from_uri_list (GList *uris);
+GList * _g_file_list_find_file (GList *l,
+ GFile *file);
+const char* _g_file_get_mime_type (GFile *file,
+ gboolean fast_file_type);
+void _g_file_get_modification_time (GFile *file,
+ GTimeVal *result);
+time_t _g_file_get_mtime (GFile *file);
+int _g_file_cmp_uris (GFile *a,
+ GFile *b);
+int _g_file_cmp_modification_time (GFile *a,
+ GFile *b);
+goffset _g_file_get_size (GFile *info);
+GFile * _g_file_resolve_all_symlinks (GFile *file,
+ GError **error);
+GFile * _g_file_append_prefix (GFile *file,
+ const char *prefix);
+GFile * _g_file_append_path (GFile *file,
+ const char *path);
+
+gboolean _g_mime_type_is_image (const char *mime_type);
+gboolean _g_mime_type_is_video (const char *mime_type);
+gboolean _g_mime_type_is_audio (const char *mime_type);
+
+G_END_DECLS
+
+#endif /* _GLIB_UTILS_H */
diff --git a/gthumb/gnome-desktop-thumbnail.c b/gthumb/gnome-desktop-thumbnail.c
new file mode 100644
index 0000000..0927f91
--- /dev/null
+++ b/gthumb/gnome-desktop-thumbnail.c
@@ -0,0 +1,1259 @@
+/*
+ * gnome-thumbnail.c: Utilities for handling thumbnails
+ *
+ * Copyright (C) 2002 Red Hat, Inc.
+ *
+ * This file is part of the Gnome Library.
+ *
+ * The Gnome Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * The Gnome Library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with the Gnome Library; see the file COPYING.LIB. If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Alexander Larsson <alexl redhat com>
+ */
+
+#include <config.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <dirent.h>
+#include <time.h>
+#include <math.h>
+#include <string.h>
+#include <glib.h>
+#include <stdio.h>
+
+#define GDK_PIXBUF_ENABLE_BACKEND
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#define GNOME_DESKTOP_USE_UNSTABLE_API
+#include "gnome-desktop-thumbnail.h"
+#include <gconf/gconf.h>
+#include <gconf/gconf-client.h>
+#include <glib/gstdio.h>
+
+#define SECONDS_BETWEEN_STATS 10
+
+struct _GnomeDesktopThumbnailFactoryPrivate {
+ char *application;
+ GnomeDesktopThumbnailSize size;
+
+ GMutex *lock;
+
+ GHashTable *scripts_hash;
+ guint thumbnailers_notify;
+ guint reread_scheduled;
+};
+
+static void gnome_desktop_thumbnail_factory_init (GnomeDesktopThumbnailFactory *factory);
+static void gnome_desktop_thumbnail_factory_class_init (GnomeDesktopThumbnailFactoryClass *class);
+
+G_DEFINE_TYPE (GnomeDesktopThumbnailFactory,
+ gnome_desktop_thumbnail_factory,
+ G_TYPE_OBJECT)
+#define parent_class gnome_desktop_thumbnail_factory_parent_class
+
+typedef struct {
+ gint width;
+ gint height;
+ gint input_width;
+ gint input_height;
+ gboolean preserve_aspect_ratio;
+} SizePrepareContext;
+
+#define LOAD_BUFFER_SIZE 4096
+
+static void
+size_prepared_cb (GdkPixbufLoader *loader,
+ int width,
+ int height,
+ gpointer data)
+{
+ SizePrepareContext *info = data;
+
+ g_return_if_fail (width > 0 && height > 0);
+
+ info->input_width = width;
+ info->input_height = height;
+
+ if (width < info->width && height < info->height) return;
+
+ if (info->preserve_aspect_ratio &&
+ (info->width > 0 || info->height > 0)) {
+ if (info->width < 0)
+ {
+ width = width * (double)info->height/(double)height;
+ height = info->height;
+ }
+ else if (info->height < 0)
+ {
+ height = height * (double)info->width/(double)width;
+ width = info->width;
+ }
+ else if ((double)height * (double)info->width >
+ (double)width * (double)info->height) {
+ width = 0.5 + (double)width * (double)info->height / (double)height;
+ height = info->height;
+ } else {
+ height = 0.5 + (double)height * (double)info->width / (double)width;
+ width = info->width;
+ }
+ } else {
+ if (info->width > 0)
+ width = info->width;
+ if (info->height > 0)
+ height = info->height;
+ }
+
+ gdk_pixbuf_loader_set_size (loader, width, height);
+}
+
+static GdkPixbuf *
+_gdk_pixbuf_new_from_uri_at_scale (const char *uri,
+ gint width,
+ gint height,
+ gboolean preserve_aspect_ratio)
+{
+ gboolean result;
+ char buffer[LOAD_BUFFER_SIZE];
+ gsize bytes_read;
+ GdkPixbufLoader *loader;
+ GdkPixbuf *pixbuf;
+ GdkPixbufAnimation *animation;
+ GdkPixbufAnimationIter *iter;
+ gboolean has_frame;
+ SizePrepareContext info;
+ GFile *file;
+ GInputStream *input_stream;
+
+ g_return_val_if_fail (uri != NULL, NULL);
+
+ file = g_file_new_for_uri (uri);
+
+ input_stream = G_INPUT_STREAM (g_file_read (file, NULL, NULL));
+ if (input_stream == NULL) {
+ g_object_unref (file);
+ return NULL;
+ }
+
+ loader = gdk_pixbuf_loader_new ();
+ if (1 <= width || 1 <= height) {
+ info.width = width;
+ info.height = height;
+ info.input_width = info.input_height = 0;
+ info.preserve_aspect_ratio = preserve_aspect_ratio;
+ g_signal_connect (loader, "size-prepared", G_CALLBACK (size_prepared_cb), &info);
+ }
+
+ has_frame = FALSE;
+
+ result = FALSE;
+ while (!has_frame) {
+
+ bytes_read = g_input_stream_read (input_stream,
+ buffer,
+ sizeof (buffer),
+ NULL,
+ NULL);
+ if (bytes_read == -1) {
+ break;
+ }
+ result = TRUE;
+ if (bytes_read == 0) {
+ break;
+ }
+
+ if (!gdk_pixbuf_loader_write (loader,
+ (unsigned char *)buffer,
+ bytes_read,
+ NULL)) {
+ result = FALSE;
+ break;
+ }
+
+ animation = gdk_pixbuf_loader_get_animation (loader);
+ if (animation) {
+ iter = gdk_pixbuf_animation_get_iter (animation, NULL);
+ if (!gdk_pixbuf_animation_iter_on_currently_loading_frame (iter)) {
+ has_frame = TRUE;
+ }
+ g_object_unref (iter);
+ }
+ }
+
+ gdk_pixbuf_loader_close (loader, NULL);
+
+ if (!result) {
+ g_object_unref (G_OBJECT (loader));
+ g_input_stream_close (input_stream, NULL, NULL);
+ g_object_unref (input_stream);
+ g_object_unref (file);
+ return NULL;
+ }
+
+ g_input_stream_close (input_stream, NULL, NULL);
+ g_object_unref (input_stream);
+ g_object_unref (file);
+
+ pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
+ if (pixbuf != NULL) {
+ g_object_ref (G_OBJECT (pixbuf));
+ g_object_set_data (G_OBJECT (pixbuf), "gnome-original-width",
+ GINT_TO_POINTER (info.input_width));
+ g_object_set_data (G_OBJECT (pixbuf), "gnome-original-height",
+ GINT_TO_POINTER (info.input_height));
+ }
+ g_object_unref (G_OBJECT (loader));
+
+ return pixbuf;
+}
+
+static void
+gnome_desktop_thumbnail_factory_finalize (GObject *object)
+{
+ GnomeDesktopThumbnailFactory *factory;
+ GnomeDesktopThumbnailFactoryPrivate *priv;
+ GConfClient *client;
+
+ factory = GNOME_DESKTOP_THUMBNAIL_FACTORY (object);
+
+ priv = factory->priv;
+
+ g_free (priv->application);
+ priv->application = NULL;
+
+ if (priv->reread_scheduled != 0) {
+ g_source_remove (priv->reread_scheduled);
+ priv->reread_scheduled = 0;
+ }
+
+ if (priv->thumbnailers_notify != 0) {
+ client = gconf_client_get_default ();
+ gconf_client_notify_remove (client, priv->thumbnailers_notify);
+ priv->thumbnailers_notify = 0;
+ g_object_unref (client);
+ }
+
+ if (priv->scripts_hash)
+ {
+ g_hash_table_destroy (priv->scripts_hash);
+ priv->scripts_hash = NULL;
+ }
+
+ if (priv->lock)
+ {
+ g_mutex_free (priv->lock);
+ priv->lock = NULL;
+ }
+
+ g_free (priv);
+ factory->priv = NULL;
+
+ if (G_OBJECT_CLASS (parent_class)->finalize)
+ (* G_OBJECT_CLASS (parent_class)->finalize) (object);
+}
+
+/* Must be called on main thread */
+static GHashTable *
+read_scripts (void)
+{
+ GHashTable *scripts_hash;
+ GConfClient *client;
+ GSList *subdirs, *l;
+ char *subdir, *enable, *escape, *commandkey, *command, *mimetype;
+
+ client = gconf_client_get_default ();
+
+ if (gconf_client_get_bool (client,
+ "/desktop/gnome/thumbnailers/disable_all",
+ NULL))
+ {
+ g_object_unref (G_OBJECT (client));
+ return NULL;
+ }
+
+ scripts_hash = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ g_free, g_free);
+
+
+ subdirs = gconf_client_all_dirs (client, "/desktop/gnome/thumbnailers", NULL);
+
+ for (l = subdirs; l != NULL; l = l->next)
+ {
+ subdir = l->data;
+
+ enable = g_strdup_printf ("%s/enable", subdir);
+ if (gconf_client_get_bool (client,
+ enable,
+ NULL))
+ {
+ commandkey = g_strdup_printf ("%s/command", subdir);
+ command = gconf_client_get_string (client, commandkey, NULL);
+ g_free (commandkey);
+
+ if (command != NULL) {
+ mimetype = strrchr (subdir, '/');
+ if (mimetype != NULL)
+ {
+ mimetype++; /* skip past slash */
+
+ /* Convert '@' to slash in mimetype */
+ escape = strchr (mimetype, '@');
+ if (escape != NULL)
+ *escape = '/';
+
+ /* Convert any remaining '@' to '+' in mimetype */
+ while ((escape = strchr (mimetype, '@')) != NULL)
+ *escape = '+';
+
+ g_hash_table_insert (scripts_hash,
+ g_strdup (mimetype), command);
+ }
+ else
+ {
+ g_free (command);
+ }
+ }
+ }
+ g_free (enable);
+
+ g_free (subdir);
+ }
+
+ g_slist_free(subdirs);
+
+ g_object_unref (G_OBJECT (client));
+
+ return scripts_hash;
+}
+
+
+/* Must be called on main thread */
+static void
+gnome_desktop_thumbnail_factory_reread_scripts (GnomeDesktopThumbnailFactory *factory)
+{
+ GnomeDesktopThumbnailFactoryPrivate *priv = factory->priv;
+ GHashTable *scripts_hash;
+
+ scripts_hash = read_scripts ();
+
+ g_mutex_lock (priv->lock);
+
+ if (priv->scripts_hash != NULL)
+ g_hash_table_destroy (priv->scripts_hash);
+
+ priv->scripts_hash = scripts_hash;
+
+ g_mutex_unlock (priv->lock);
+}
+
+static gboolean
+reread_idle_callback (gpointer user_data)
+{
+ GnomeDesktopThumbnailFactory *factory = user_data;
+ GnomeDesktopThumbnailFactoryPrivate *priv = factory->priv;
+
+ gnome_desktop_thumbnail_factory_reread_scripts (factory);
+
+ g_mutex_lock (priv->lock);
+ priv->reread_scheduled = 0;
+ g_mutex_unlock (priv->lock);
+
+ return FALSE;
+}
+
+static void
+schedule_reread (GConfClient* client,
+ guint cnxn_id,
+ GConfEntry *entry,
+ gpointer user_data)
+{
+ GnomeDesktopThumbnailFactory *factory = user_data;
+ GnomeDesktopThumbnailFactoryPrivate *priv = factory->priv;
+
+ g_mutex_lock (priv->lock);
+
+ if (priv->reread_scheduled == 0)
+ {
+ priv->reread_scheduled = g_idle_add (reread_idle_callback,
+ factory);
+ }
+
+ g_mutex_unlock (priv->lock);
+}
+
+
+static void
+gnome_desktop_thumbnail_factory_init (GnomeDesktopThumbnailFactory *factory)
+{
+ GConfClient *client;
+ GnomeDesktopThumbnailFactoryPrivate *priv;
+
+ factory->priv = g_new0 (GnomeDesktopThumbnailFactoryPrivate, 1);
+
+ priv = factory->priv;
+
+ priv->size = GNOME_DESKTOP_THUMBNAIL_SIZE_NORMAL;
+ priv->application = g_strdup ("gnome-thumbnail-factory");
+
+ priv->scripts_hash = NULL;
+
+ priv->lock = g_mutex_new ();
+
+ gnome_desktop_thumbnail_factory_reread_scripts (factory);
+
+ client = gconf_client_get_default ();
+ gconf_client_add_dir (client,
+ "/desktop/gnome",
+ GCONF_CLIENT_PRELOAD_NONE, NULL);
+
+ priv->thumbnailers_notify = gconf_client_notify_add (client, "/desktop/gnome/thumbnailers",
+ schedule_reread, factory, NULL,
+ NULL);
+
+ g_object_unref (G_OBJECT (client));
+}
+
+static void
+gnome_desktop_thumbnail_factory_class_init (GnomeDesktopThumbnailFactoryClass *class)
+{
+ GObjectClass *gobject_class;
+
+ gobject_class = G_OBJECT_CLASS (class);
+
+ gobject_class->finalize = gnome_desktop_thumbnail_factory_finalize;
+}
+
+/**
+ * gnome_desktop_thumbnail_factory_new:
+ * @size: The thumbnail size to use
+ *
+ * Creates a new #GnomeDesktopThumbnailFactory.
+ *
+ * This function must be called on the main thread.
+ *
+ * Return value: a new #GnomeDesktopThumbnailFactory
+ *
+ * Since: 2.2
+ **/
+GnomeDesktopThumbnailFactory *
+gnome_desktop_thumbnail_factory_new (GnomeDesktopThumbnailSize size)
+{
+ GnomeDesktopThumbnailFactory *factory;
+
+ factory = g_object_new (GNOME_DESKTOP_TYPE_THUMBNAIL_FACTORY, NULL);
+
+ factory->priv->size = size;
+
+ return factory;
+}
+
+/**
+ * gnome_desktop_thumbnail_factory_lookup:
+ * @factory: a #GnomeDesktopThumbnailFactory
+ * @uri: the uri of a file
+ * @mtime: the mtime of the file
+ *
+ * Tries to locate an existing thumbnail for the file specified.
+ *
+ * Usage of this function is threadsafe.
+ *
+ * Return value: The absolute path of the thumbnail, or %NULL if none exist.
+ *
+ * Since: 2.2
+ **/
+char *
+gnome_desktop_thumbnail_factory_lookup (GnomeDesktopThumbnailFactory *factory,
+ const char *uri,
+ time_t mtime)
+{
+ GnomeDesktopThumbnailFactoryPrivate *priv = factory->priv;
+ char *path, *file;
+ GChecksum *checksum;
+ guint8 digest[16];
+ gsize digest_len = sizeof (digest);
+ GdkPixbuf *pixbuf;
+ gboolean res;
+
+ g_return_val_if_fail (uri != NULL, NULL);
+
+ res = FALSE;
+
+ checksum = g_checksum_new (G_CHECKSUM_MD5);
+ g_checksum_update (checksum, (const guchar *) uri, strlen (uri));
+
+ g_checksum_get_digest (checksum, digest, &digest_len);
+ g_assert (digest_len == 16);
+
+ file = g_strconcat (g_checksum_get_string (checksum), ".png", NULL);
+
+ path = g_build_filename (g_get_home_dir (),
+ ".thumbnails",
+ (priv->size == GNOME_DESKTOP_THUMBNAIL_SIZE_NORMAL)?"normal":"large",
+ file,
+ NULL);
+ g_free (file);
+
+ pixbuf = gdk_pixbuf_new_from_file (path, NULL);
+ if (pixbuf != NULL)
+ {
+ res = gnome_desktop_thumbnail_is_valid (pixbuf, uri, mtime);
+ g_object_unref (pixbuf);
+ }
+
+ g_checksum_free (checksum);
+
+ if (res)
+ return path;
+
+ g_free (path);
+ return FALSE;
+}
+
+/**
+ * gnome_desktop_thumbnail_factory_has_valid_failed_thumbnail:
+ * @factory: a #GnomeDesktopThumbnailFactory
+ * @uri: the uri of a file
+ * @mtime: the mtime of the file
+ *
+ * Tries to locate an failed thumbnail for the file specified. Writing
+ * and looking for failed thumbnails is important to avoid to try to
+ * thumbnail e.g. broken images several times.
+ *
+ * Usage of this function is threadsafe.
+ *
+ * Return value: TRUE if there is a failed thumbnail for the file.
+ *
+ * Since: 2.2
+ **/
+gboolean
+gnome_desktop_thumbnail_factory_has_valid_failed_thumbnail (GnomeDesktopThumbnailFactory *factory,
+ const char *uri,
+ time_t mtime)
+{
+ char *path, *file;
+ GdkPixbuf *pixbuf;
+ gboolean res;
+ GChecksum *checksum;
+ guint8 digest[16];
+ gsize digest_len = sizeof (digest);
+
+ checksum = g_checksum_new (G_CHECKSUM_MD5);
+ g_checksum_update (checksum, (const guchar *) uri, strlen (uri));
+
+ g_checksum_get_digest (checksum, digest, &digest_len);
+ g_assert (digest_len == 16);
+
+ res = FALSE;
+
+ file = g_strconcat (g_checksum_get_string (checksum), ".png", NULL);
+
+ path = g_build_filename (g_get_home_dir (),
+ ".thumbnails/fail",
+ factory->priv->application,
+ file,
+ NULL);
+ g_free (file);
+
+ pixbuf = gdk_pixbuf_new_from_file (path, NULL);
+ g_free (path);
+
+ if (pixbuf)
+ {
+ res = gnome_desktop_thumbnail_is_valid (pixbuf, uri, mtime);
+ g_object_unref (pixbuf);
+ }
+
+ g_checksum_free (checksum);
+
+ return res;
+}
+
+static gboolean
+mimetype_supported_by_gdk_pixbuf (const char *mime_type)
+{
+ guint i;
+ static GHashTable *formats_hash = NULL;
+
+ if (!formats_hash) {
+ GSList *formats, *list;
+
+ formats_hash = g_hash_table_new (g_str_hash, g_str_equal);
+
+ formats = gdk_pixbuf_get_formats ();
+ list = formats;
+
+ while (list) {
+ GdkPixbufFormat *format = list->data;
+ gchar **mime_types;
+
+ mime_types = gdk_pixbuf_format_get_mime_types (format);
+
+ for (i = 0; mime_types[i] != NULL; i++)
+ g_hash_table_insert (formats_hash,
+ (gpointer) g_strdup (mime_types[i]),
+ GUINT_TO_POINTER (1));
+
+ g_strfreev (mime_types);
+ list = list->next;
+ }
+ g_slist_free (formats);
+ }
+
+ if (g_hash_table_lookup (formats_hash, mime_type))
+ return TRUE;
+
+ return FALSE;
+}
+
+/**
+ * gnome_desktop_thumbnail_factory_can_thumbnail:
+ * @factory: a #GnomeDesktopThumbnailFactory
+ * @uri: the uri of a file
+ * @mime_type: the mime type of the file
+ * @mtime: the mtime of the file
+ *
+ * Returns TRUE if this GnomeIconFactory can (at least try) to thumbnail
+ * this file. Thumbnails or files with failed thumbnails won't be thumbnailed.
+ *
+ * Usage of this function is threadsafe.
+ *
+ * Return value: TRUE if the file can be thumbnailed.
+ *
+ * Since: 2.2
+ **/
+gboolean
+gnome_desktop_thumbnail_factory_can_thumbnail (GnomeDesktopThumbnailFactory *factory,
+ const char *uri,
+ const char *mime_type,
+ time_t mtime)
+{
+ /* Don't thumbnail thumbnails */
+ if (uri &&
+ strncmp (uri, "file:/", 6) == 0 &&
+ strstr (uri, "/.thumbnails/") != NULL)
+ return FALSE;
+
+ if (mime_type != NULL &&
+ (mimetype_supported_by_gdk_pixbuf (mime_type) ||
+ (factory->priv->scripts_hash != NULL &&
+ g_hash_table_lookup (factory->priv->scripts_hash, mime_type))))
+ {
+ return !gnome_desktop_thumbnail_factory_has_valid_failed_thumbnail (factory,
+ uri,
+ mtime);
+ }
+
+ return FALSE;
+}
+
+static char *
+expand_thumbnailing_script (const char *script,
+ const int size,
+ const char *inuri,
+ const char *outfile)
+{
+ GString *str;
+ const char *p, *last;
+ char *localfile, *quoted;
+ gboolean got_in;
+
+ str = g_string_new (NULL);
+
+ got_in = FALSE;
+ last = script;
+ while ((p = strchr (last, '%')) != NULL)
+ {
+ g_string_append_len (str, last, p - last);
+ p++;
+
+ switch (*p) {
+ case 'u':
+ quoted = g_shell_quote (inuri);
+ g_string_append (str, quoted);
+ g_free (quoted);
+ got_in = TRUE;
+ p++;
+ break;
+ case 'i':
+ localfile = g_filename_from_uri (inuri, NULL, NULL);
+ if (localfile)
+ {
+ quoted = g_shell_quote (localfile);
+ g_string_append (str, quoted);
+ got_in = TRUE;
+ g_free (quoted);
+ g_free (localfile);
+ }
+ p++;
+ break;
+ case 'o':
+ quoted = g_shell_quote (outfile);
+ g_string_append (str, quoted);
+ g_free (quoted);
+ p++;
+ break;
+ case 's':
+ g_string_append_printf (str, "%d", size);
+ p++;
+ break;
+ case '%':
+ g_string_append_c (str, '%');
+ p++;
+ break;
+ case 0:
+ default:
+ break;
+ }
+ last = p;
+ }
+ g_string_append (str, last);
+
+ if (got_in)
+ return g_string_free (str, FALSE);
+
+ g_string_free (str, TRUE);
+ return NULL;
+}
+
+/**
+ * gnome_desktop_thumbnail_factory_generate_thumbnail:
+ * @factory: a #GnomeDesktopThumbnailFactory
+ * @uri: the uri of a file
+ * @mime_type: the mime type of the file
+ *
+ * Tries to generate a thumbnail for the specified file. If it succeeds
+ * it returns a pixbuf that can be used as a thumbnail.
+ *
+ * Usage of this function is threadsafe.
+ *
+ * Return value: thumbnail pixbuf if thumbnailing succeeded, %NULL otherwise.
+ *
+ * Since: 2.2
+ **/
+GdkPixbuf *
+gnome_desktop_thumbnail_factory_generate_thumbnail (GnomeDesktopThumbnailFactory *factory,
+ const char *uri,
+ const char *mime_type)
+{
+ GdkPixbuf *pixbuf, *scaled, *tmp_pixbuf;
+ char *script, *expanded_script;
+ int width, height, size;
+ int original_width = 0;
+ int original_height = 0;
+ char dimension[12];
+ double scale;
+ int exit_status;
+ char *tmpname;
+
+ g_return_val_if_fail (uri != NULL, NULL);
+ g_return_val_if_fail (mime_type != NULL, NULL);
+
+ /* Doesn't access any volatile fields in factory, so it's threadsafe */
+
+ size = 128;
+ if (factory->priv->size == GNOME_DESKTOP_THUMBNAIL_SIZE_LARGE)
+ size = 256;
+
+ pixbuf = NULL;
+
+ script = NULL;
+ if (factory->priv->scripts_hash != NULL)
+ script = g_hash_table_lookup (factory->priv->scripts_hash, mime_type);
+
+ if (script)
+ {
+ int fd;
+ GError *error = NULL;
+
+ fd = g_file_open_tmp (".gnome_desktop_thumbnail.XXXXXX", &tmpname, &error);
+
+ if (fd != -1)
+ {
+ close (fd);
+
+ expanded_script = expand_thumbnailing_script (script, size, uri, tmpname);
+ if (expanded_script != NULL &&
+ g_spawn_command_line_sync (expanded_script,
+ NULL, NULL, &exit_status, NULL) &&
+ exit_status == 0)
+ {
+ pixbuf = gdk_pixbuf_new_from_file (tmpname, NULL);
+ }
+
+ g_free (expanded_script);
+ g_unlink(tmpname);
+ }
+ g_free (tmpname);
+ }
+
+ /* Fall back to gdk-pixbuf */
+ if (pixbuf == NULL)
+ {
+ pixbuf = _gdk_pixbuf_new_from_uri_at_scale (uri, size, size, TRUE);
+
+ if (pixbuf != NULL)
+ {
+ original_width = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (pixbuf),
+ "gnome-original-width"));
+ original_height = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (pixbuf),
+ "gnome-original-height"));
+ }
+ }
+
+ if (pixbuf == NULL)
+ return NULL;
+
+ /* The pixbuf loader may attach an "orientation" option to the pixbuf,
+ if the tiff or exif jpeg file had an orientation tag. Rotate/flip
+ the pixbuf as specified by this tag, if present. */
+ tmp_pixbuf = gdk_pixbuf_apply_embedded_orientation (pixbuf);
+ g_object_unref (pixbuf);
+ pixbuf = tmp_pixbuf;
+
+ width = gdk_pixbuf_get_width (pixbuf);
+ height = gdk_pixbuf_get_height (pixbuf);
+
+ if (width > size || height > size)
+ {
+ const gchar *orig_width, *orig_height;
+ scale = (double)size / MAX (width, height);
+
+ scaled = gnome_desktop_thumbnail_scale_down_pixbuf (pixbuf,
+ floor (width * scale + 0.5),
+ floor (height * scale + 0.5));
+
+ orig_width = gdk_pixbuf_get_option (pixbuf, "tEXt::Thumb::Image::Width");
+ orig_height = gdk_pixbuf_get_option (pixbuf, "tEXt::Thumb::Image::Height");
+
+ if (orig_width != NULL) {
+ gdk_pixbuf_set_option (scaled, "tEXt::Thumb::Image::Width", orig_width);
+ }
+ if (orig_height != NULL) {
+ gdk_pixbuf_set_option (scaled, "tEXt::Thumb::Image::Height", orig_height);
+ }
+
+ g_object_unref (pixbuf);
+ pixbuf = scaled;
+ }
+
+ if (original_width > 0) {
+ g_snprintf (dimension, sizeof (dimension), "%i", original_width);
+ gdk_pixbuf_set_option (pixbuf, "tEXt::Thumb::Image::Width", dimension);
+ }
+ if (original_height > 0) {
+ g_snprintf (dimension, sizeof (dimension), "%i", original_height);
+ gdk_pixbuf_set_option (pixbuf, "tEXt::Thumb::Image::Height", dimension);
+ }
+
+ return pixbuf;
+}
+
+static gboolean
+make_thumbnail_dirs (GnomeDesktopThumbnailFactory *factory)
+{
+ char *thumbnail_dir;
+ char *image_dir;
+ gboolean res;
+
+ res = FALSE;
+
+ thumbnail_dir = g_build_filename (g_get_home_dir (),
+ ".thumbnails",
+ NULL);
+ if (!g_file_test (thumbnail_dir, G_FILE_TEST_IS_DIR))
+ {
+ g_mkdir (thumbnail_dir, 0700);
+ res = TRUE;
+ }
+
+ image_dir = g_build_filename (thumbnail_dir,
+ (factory->priv->size == GNOME_DESKTOP_THUMBNAIL_SIZE_NORMAL)?"normal":"large",
+ NULL);
+ if (!g_file_test (image_dir, G_FILE_TEST_IS_DIR))
+ {
+ g_mkdir (image_dir, 0700);
+ res = TRUE;
+ }
+
+ g_free (thumbnail_dir);
+ g_free (image_dir);
+
+ return res;
+}
+
+static gboolean
+make_thumbnail_fail_dirs (GnomeDesktopThumbnailFactory *factory)
+{
+ char *thumbnail_dir;
+ char *fail_dir;
+ char *app_dir;
+ gboolean res;
+
+ res = FALSE;
+
+ thumbnail_dir = g_build_filename (g_get_home_dir (),
+ ".thumbnails",
+ NULL);
+ if (!g_file_test (thumbnail_dir, G_FILE_TEST_IS_DIR))
+ {
+ g_mkdir (thumbnail_dir, 0700);
+ res = TRUE;
+ }
+
+ fail_dir = g_build_filename (thumbnail_dir,
+ "fail",
+ NULL);
+ if (!g_file_test (fail_dir, G_FILE_TEST_IS_DIR))
+ {
+ g_mkdir (fail_dir, 0700);
+ res = TRUE;
+ }
+
+ app_dir = g_build_filename (fail_dir,
+ factory->priv->application,
+ NULL);
+ if (!g_file_test (app_dir, G_FILE_TEST_IS_DIR))
+ {
+ g_mkdir (app_dir, 0700);
+ res = TRUE;
+ }
+
+ g_free (thumbnail_dir);
+ g_free (fail_dir);
+ g_free (app_dir);
+
+ return res;
+}
+
+
+/**
+ * gnome_desktop_thumbnail_factory_save_thumbnail:
+ * @factory: a #GnomeDesktopThumbnailFactory
+ * @thumbnail: the thumbnail as a pixbuf
+ * @uri: the uri of a file
+ * @original_mtime: the modification time of the original file
+ *
+ * Saves @thumbnail at the right place. If the save fails a
+ * failed thumbnail is written.
+ *
+ * Usage of this function is threadsafe.
+ *
+ * Since: 2.2
+ **/
+void
+gnome_desktop_thumbnail_factory_save_thumbnail (GnomeDesktopThumbnailFactory *factory,
+ GdkPixbuf *thumbnail,
+ const char *uri,
+ time_t original_mtime)
+{
+ GnomeDesktopThumbnailFactoryPrivate *priv = factory->priv;
+ char *path, *file, *dir;
+ char *tmp_path;
+ const char *width, *height;
+ int tmp_fd;
+ char mtime_str[21];
+ gboolean saved_ok;
+ GChecksum *checksum;
+ guint8 digest[16];
+ gsize digest_len = sizeof (digest);
+
+ checksum = g_checksum_new (G_CHECKSUM_MD5);
+ g_checksum_update (checksum, (const guchar *) uri, strlen (uri));
+
+ g_checksum_get_digest (checksum, digest, &digest_len);
+ g_assert (digest_len == 16);
+
+ file = g_strconcat (g_checksum_get_string (checksum), ".png", NULL);
+
+ dir = g_build_filename (g_get_home_dir (),
+ ".thumbnails",
+ (priv->size == GNOME_DESKTOP_THUMBNAIL_SIZE_NORMAL)?"normal":"large",
+ NULL);
+
+ path = g_build_filename (dir,
+ file,
+ NULL);
+ g_free (file);
+
+ g_checksum_free (checksum);
+
+ tmp_path = g_strconcat (path, ".XXXXXX", NULL);
+
+ tmp_fd = g_mkstemp (tmp_path);
+ if (tmp_fd == -1 &&
+ make_thumbnail_dirs (factory))
+ {
+ g_free (tmp_path);
+ tmp_path = g_strconcat (path, ".XXXXXX", NULL);
+ tmp_fd = g_mkstemp (tmp_path);
+ }
+
+ if (tmp_fd == -1)
+ {
+ gnome_desktop_thumbnail_factory_create_failed_thumbnail (factory, uri, original_mtime);
+ g_free (dir);
+ g_free (tmp_path);
+ g_free (path);
+ return;
+ }
+ close (tmp_fd);
+
+ g_snprintf (mtime_str, 21, "%ld", original_mtime);
+ width = gdk_pixbuf_get_option (thumbnail, "tEXt::Thumb::Image::Width");
+ height = gdk_pixbuf_get_option (thumbnail, "tEXt::Thumb::Image::Height");
+
+ if (width != NULL && height != NULL)
+ saved_ok = gdk_pixbuf_save (thumbnail,
+ tmp_path,
+ "png", NULL,
+ "tEXt::Thumb::Image::Width", width,
+ "tEXt::Thumb::Image::Height", height,
+ "tEXt::Thumb::URI", uri,
+ "tEXt::Thumb::MTime", mtime_str,
+ "tEXt::Software", "GNOME::ThumbnailFactory",
+ NULL);
+ else
+ saved_ok = gdk_pixbuf_save (thumbnail,
+ tmp_path,
+ "png", NULL,
+ "tEXt::Thumb::URI", uri,
+ "tEXt::Thumb::MTime", mtime_str,
+ "tEXt::Software", "GNOME::ThumbnailFactory",
+ NULL);
+
+
+ if (saved_ok)
+ {
+ g_chmod (tmp_path, 0600);
+ g_rename(tmp_path, path);
+ }
+ else
+ {
+ gnome_desktop_thumbnail_factory_create_failed_thumbnail (factory, uri, original_mtime);
+ }
+
+ g_free (dir);
+ g_free (path);
+ g_free (tmp_path);
+}
+
+/**
+ * gnome_desktop_thumbnail_factory_create_failed_thumbnail:
+ * @factory: a #GnomeDesktopThumbnailFactory
+ * @uri: the uri of a file
+ * @mtime: the modification time of the file
+ *
+ * Creates a failed thumbnail for the file so that we don't try
+ * to re-thumbnail the file later.
+ *
+ * Usage of this function is threadsafe.
+ *
+ * Since: 2.2
+ **/
+void
+gnome_desktop_thumbnail_factory_create_failed_thumbnail (GnomeDesktopThumbnailFactory *factory,
+ const char *uri,
+ time_t mtime)
+{
+ char *path, *file, *dir;
+ char *tmp_path;
+ int tmp_fd;
+ char mtime_str[21];
+ gboolean saved_ok;
+ GdkPixbuf *pixbuf;
+ GChecksum *checksum;
+ guint8 digest[16];
+ gsize digest_len = sizeof (digest);
+
+ checksum = g_checksum_new (G_CHECKSUM_MD5);
+ g_checksum_update (checksum, (const guchar *) uri, strlen (uri));
+
+ g_checksum_get_digest (checksum, digest, &digest_len);
+ g_assert (digest_len == 16);
+
+ file = g_strconcat (g_checksum_get_string (checksum), ".png", NULL);
+
+ dir = g_build_filename (g_get_home_dir (),
+ ".thumbnails/fail",
+ factory->priv->application,
+ NULL);
+
+ path = g_build_filename (dir,
+ file,
+ NULL);
+ g_free (file);
+
+ g_checksum_free (checksum);
+
+ tmp_path = g_strconcat (path, ".XXXXXX", NULL);
+
+ tmp_fd = g_mkstemp (tmp_path);
+ if (tmp_fd == -1 &&
+ make_thumbnail_fail_dirs (factory))
+ {
+ g_free (tmp_path);
+ tmp_path = g_strconcat (path, ".XXXXXX", NULL);
+ tmp_fd = g_mkstemp (tmp_path);
+ }
+
+ if (tmp_fd == -1)
+ {
+ g_free (dir);
+ g_free (tmp_path);
+ g_free (path);
+ return;
+ }
+ close (tmp_fd);
+
+ g_snprintf (mtime_str, 21, "%ld", mtime);
+ pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8, 1, 1);
+ saved_ok = gdk_pixbuf_save (pixbuf,
+ tmp_path,
+ "png", NULL,
+ "tEXt::Thumb::URI", uri,
+ "tEXt::Thumb::MTime", mtime_str,
+ "tEXt::Software", "GNOME::ThumbnailFactory",
+ NULL);
+ g_object_unref (pixbuf);
+ if (saved_ok)
+ {
+ g_chmod (tmp_path, 0600);
+ g_rename(tmp_path, path);
+ }
+
+ g_free (dir);
+ g_free (path);
+ g_free (tmp_path);
+}
+
+/**
+ * gnome_desktop_thumbnail_md5:
+ * @uri: an uri
+ *
+ * Calculates the MD5 checksum of the uri. This can be useful
+ * if you want to manually handle thumbnail files.
+ *
+ * Return value: A string with the MD5 digest of the uri string.
+ *
+ * Since: 2.2
+ *
+ * @Deprecated: 2.22: Use #GChecksum instead
+ **/
+char *
+gnome_desktop_thumbnail_md5 (const char *uri)
+{
+ return g_compute_checksum_for_data (G_CHECKSUM_MD5,
+ (const guchar *) uri,
+ strlen (uri));
+}
+
+/**
+ * gnome_desktop_thumbnail_path_for_uri:
+ * @uri: an uri
+ * @size: a thumbnail size
+ *
+ * Returns the filename that a thumbnail of size @size for @uri would have.
+ *
+ * Return value: an absolute filename
+ *
+ * Since: 2.2
+ **/
+char *
+gnome_desktop_thumbnail_path_for_uri (const char *uri,
+ GnomeDesktopThumbnailSize size)
+{
+ char *md5;
+ char *file;
+ char *path;
+
+ md5 = gnome_desktop_thumbnail_md5 (uri);
+ file = g_strconcat (md5, ".png", NULL);
+ g_free (md5);
+
+ path = g_build_filename (g_get_home_dir (),
+ ".thumbnails",
+ (size == GNOME_DESKTOP_THUMBNAIL_SIZE_NORMAL)?"normal":"large",
+ file,
+ NULL);
+
+ g_free (file);
+
+ return path;
+}
+
+/**
+ * gnome_desktop_thumbnail_has_uri:
+ * @pixbuf: an loaded thumbnail pixbuf
+ * @uri: a uri
+ *
+ * Returns whether the thumbnail has the correct uri embedded in the
+ * Thumb::URI option in the png.
+ *
+ * Return value: TRUE if the thumbnail is for @uri
+ *
+ * Since: 2.2
+ **/
+gboolean
+gnome_desktop_thumbnail_has_uri (GdkPixbuf *pixbuf,
+ const char *uri)
+{
+ const char *thumb_uri;
+
+ thumb_uri = gdk_pixbuf_get_option (pixbuf, "tEXt::Thumb::URI");
+ if (!thumb_uri)
+ return FALSE;
+
+ return strcmp (uri, thumb_uri) == 0;
+}
+
+/**
+ * gnome_desktop_thumbnail_is_valid:
+ * @pixbuf: an loaded thumbnail #GdkPixbuf
+ * @uri: a uri
+ * @mtime: the mtime
+ *
+ * Returns whether the thumbnail has the correct uri and mtime embedded in the
+ * png options.
+ *
+ * Return value: TRUE if the thumbnail has the right @uri and @mtime
+ *
+ * Since: 2.2
+ **/
+gboolean
+gnome_desktop_thumbnail_is_valid (GdkPixbuf *pixbuf,
+ const char *uri,
+ time_t mtime)
+{
+ const char *thumb_uri, *thumb_mtime_str;
+ time_t thumb_mtime;
+
+ thumb_uri = gdk_pixbuf_get_option (pixbuf, "tEXt::Thumb::URI");
+ if (!thumb_uri)
+ return FALSE;
+ if (strcmp (uri, thumb_uri) != 0)
+ return FALSE;
+
+ thumb_mtime_str = gdk_pixbuf_get_option (pixbuf, "tEXt::Thumb::MTime");
+ if (!thumb_mtime_str)
+ return FALSE;
+ thumb_mtime = atol (thumb_mtime_str);
+ if (mtime != thumb_mtime)
+ return FALSE;
+
+ return TRUE;
+}
diff --git a/gthumb/gnome-desktop-thumbnail.h b/gthumb/gnome-desktop-thumbnail.h
new file mode 100644
index 0000000..c7537de
--- /dev/null
+++ b/gthumb/gnome-desktop-thumbnail.h
@@ -0,0 +1,106 @@
+/*
+ * gnome-thumbnail.h: Utilities for handling thumbnails
+ *
+ * Copyright (C) 2002 Red Hat, Inc.
+ *
+ * This file is part of the Gnome Library.
+ *
+ * The Gnome Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * The Gnome Library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with the Gnome Library; see the file COPYING.LIB. If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Alexander Larsson <alexl redhat com>
+ */
+
+#ifndef GNOME_DESKTOP_THUMBNAIL_H
+#define GNOME_DESKTOP_THUMBNAIL_H
+
+#include <glib.h>
+#include <glib-object.h>
+#include <time.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+G_BEGIN_DECLS
+
+typedef enum {
+ GNOME_DESKTOP_THUMBNAIL_SIZE_NORMAL,
+ GNOME_DESKTOP_THUMBNAIL_SIZE_LARGE
+} GnomeDesktopThumbnailSize;
+
+#define GNOME_DESKTOP_TYPE_THUMBNAIL_FACTORY (gnome_desktop_thumbnail_factory_get_type ())
+#define GNOME_DESKTOP_THUMBNAIL_FACTORY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GNOME_DESKTOP_TYPE_THUMBNAIL_FACTORY, GnomeDesktopThumbnailFactory))
+#define GNOME_DESKTOP_THUMBNAIL_FACTORY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GNOME_DESKTOP_TYPE_THUMBNAIL_FACTORY, GnomeDesktopThumbnailFactoryClass))
+#define GNOME_DESKTOP_IS_THUMBNAIL_FACTORY(obj) (G_TYPE_INSTANCE_CHECK_TYPE ((obj), GNOME_DESKTOP_TYPE_THUMBNAIL_FACTORY))
+#define GNOME_DESKTOP_IS_THUMBNAIL_FACTORY_CLASS(klass) (G_TYPE_CLASS_CHECK_CLASS_TYPE ((klass), GNOME_DESKTOP_TYPE_THUMBNAIL_FACTORY))
+
+typedef struct _GnomeDesktopThumbnailFactory GnomeDesktopThumbnailFactory;
+typedef struct _GnomeDesktopThumbnailFactoryClass GnomeDesktopThumbnailFactoryClass;
+typedef struct _GnomeDesktopThumbnailFactoryPrivate GnomeDesktopThumbnailFactoryPrivate;
+
+struct _GnomeDesktopThumbnailFactory {
+ GObject parent;
+
+ GnomeDesktopThumbnailFactoryPrivate *priv;
+};
+
+struct _GnomeDesktopThumbnailFactoryClass {
+ GObjectClass parent;
+};
+
+GType gnome_desktop_thumbnail_factory_get_type (void);
+GnomeDesktopThumbnailFactory *gnome_desktop_thumbnail_factory_new (GnomeDesktopThumbnailSize size);
+
+char * gnome_desktop_thumbnail_factory_lookup (GnomeDesktopThumbnailFactory *factory,
+ const char *uri,
+ time_t mtime);
+
+gboolean gnome_desktop_thumbnail_factory_has_valid_failed_thumbnail (GnomeDesktopThumbnailFactory *factory,
+ const char *uri,
+ time_t mtime);
+gboolean gnome_desktop_thumbnail_factory_can_thumbnail (GnomeDesktopThumbnailFactory *factory,
+ const char *uri,
+ const char *mime_type,
+ time_t mtime);
+GdkPixbuf * gnome_desktop_thumbnail_factory_generate_thumbnail (GnomeDesktopThumbnailFactory *factory,
+ const char *uri,
+ const char *mime_type);
+void gnome_desktop_thumbnail_factory_save_thumbnail (GnomeDesktopThumbnailFactory *factory,
+ GdkPixbuf *thumbnail,
+ const char *uri,
+ time_t original_mtime);
+void gnome_desktop_thumbnail_factory_create_failed_thumbnail (GnomeDesktopThumbnailFactory *factory,
+ const char *uri,
+ time_t mtime);
+
+
+/* Thumbnailing utils: */
+gboolean gnome_desktop_thumbnail_has_uri (GdkPixbuf *pixbuf,
+ const char *uri);
+gboolean gnome_desktop_thumbnail_is_valid (GdkPixbuf *pixbuf,
+ const char *uri,
+ time_t mtime);
+char * gnome_desktop_thumbnail_md5 (const char *uri);
+char * gnome_desktop_thumbnail_path_for_uri (const char *uri,
+ GnomeDesktopThumbnailSize size);
+
+
+/* Pixbuf utils */
+
+GdkPixbuf *gnome_desktop_thumbnail_scale_down_pixbuf (GdkPixbuf *pixbuf,
+ int dest_width,
+ int dest_height);
+
+G_END_DECLS
+
+#endif /* GNOME_DESKTOP_THUMBNAIL_H */
diff --git a/gthumb/gnome-thumbnail-pixbuf-utils.c b/gthumb/gnome-thumbnail-pixbuf-utils.c
new file mode 100644
index 0000000..833242a
--- /dev/null
+++ b/gthumb/gnome-thumbnail-pixbuf-utils.c
@@ -0,0 +1,172 @@
+/*
+ * gnome-thumbnail-pixbuf-utils.c: Utilities for handling pixbufs when thumbnailing
+ *
+ * Copyright (C) 2002 Red Hat, Inc.
+ *
+ * This file is part of the Gnome Library.
+ *
+ * The Gnome Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * The Gnome Library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with the Gnome Library; see the file COPYING.LIB. If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Alexander Larsson <alexl redhat com>
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <string.h>
+#include <glib.h>
+
+#define GNOME_DESKTOP_USE_UNSTABLE_API
+#include "gnome-desktop-thumbnail.h"
+
+#define LOAD_BUFFER_SIZE 65536
+
+/**
+ * gnome_thumbnail_scale_down_pixbuf:
+ * @pixbuf: a #GdkPixbuf
+ * @dest_width: the desired new width
+ * @dest_height: the desired new height
+ *
+ * Scales the pixbuf to the desired size. This function
+ * is a lot faster than gdk-pixbuf when scaling down by
+ * large amounts.
+ *
+ * Return value: a scaled pixbuf
+ *
+ * Since: 2.2
+ **/
+GdkPixbuf *
+gnome_desktop_thumbnail_scale_down_pixbuf (GdkPixbuf *pixbuf,
+ int dest_width,
+ int dest_height)
+{
+ int source_width, source_height;
+ int s_x1, s_y1, s_x2, s_y2;
+ int s_xfrac, s_yfrac;
+ int dx, dx_frac, dy, dy_frac;
+ div_t ddx, ddy;
+ int x, y;
+ int r, g, b, a;
+ int n_pixels;
+ gboolean has_alpha;
+ guchar *dest, *src, *xsrc, *src_pixels;
+ GdkPixbuf *dest_pixbuf;
+ int pixel_stride;
+ int source_rowstride, dest_rowstride;
+
+ if (dest_width == 0 || dest_height == 0) {
+ return NULL;
+ }
+
+ source_width = gdk_pixbuf_get_width (pixbuf);
+ source_height = gdk_pixbuf_get_height (pixbuf);
+
+ g_assert (source_width >= dest_width);
+ g_assert (source_height >= dest_height);
+
+ ddx = div (source_width, dest_width);
+ dx = ddx.quot;
+ dx_frac = ddx.rem;
+
+ ddy = div (source_height, dest_height);
+ dy = ddy.quot;
+ dy_frac = ddy.rem;
+
+ has_alpha = gdk_pixbuf_get_has_alpha (pixbuf);
+ source_rowstride = gdk_pixbuf_get_rowstride (pixbuf);
+ src_pixels = gdk_pixbuf_get_pixels (pixbuf);
+
+ dest_pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, has_alpha, 8,
+ dest_width, dest_height);
+ dest = gdk_pixbuf_get_pixels (dest_pixbuf);
+ dest_rowstride = gdk_pixbuf_get_rowstride (dest_pixbuf);
+
+ pixel_stride = (has_alpha)?4:3;
+
+ s_y1 = 0;
+ s_yfrac = -dest_height/2;
+ while (s_y1 < source_height) {
+ s_y2 = s_y1 + dy;
+ s_yfrac += dy_frac;
+ if (s_yfrac > 0) {
+ s_y2++;
+ s_yfrac -= dest_height;
+ }
+
+ s_x1 = 0;
+ s_xfrac = -dest_width/2;
+ while (s_x1 < source_width) {
+ s_x2 = s_x1 + dx;
+ s_xfrac += dx_frac;
+ if (s_xfrac > 0) {
+ s_x2++;
+ s_xfrac -= dest_width;
+ }
+
+ /* Average block of [x1,x2[ x [y1,y2[ and store in dest */
+ r = g = b = a = 0;
+ n_pixels = 0;
+
+ src = src_pixels + s_y1 * source_rowstride + s_x1 * pixel_stride;
+ for (y = s_y1; y < s_y2; y++) {
+ xsrc = src;
+ if (has_alpha) {
+ for (x = 0; x < s_x2-s_x1; x++) {
+ n_pixels++;
+
+ r += xsrc[3] * xsrc[0];
+ g += xsrc[3] * xsrc[1];
+ b += xsrc[3] * xsrc[2];
+ a += xsrc[3];
+ xsrc += 4;
+ }
+ } else {
+ for (x = 0; x < s_x2-s_x1; x++) {
+ n_pixels++;
+ r += *xsrc++;
+ g += *xsrc++;
+ b += *xsrc++;
+ }
+ }
+ src += source_rowstride;
+ }
+
+ if (has_alpha) {
+ if (a != 0) {
+ *dest++ = r / a;
+ *dest++ = g / a;
+ *dest++ = b / a;
+ *dest++ = a / n_pixels;
+ } else {
+ *dest++ = 0;
+ *dest++ = 0;
+ *dest++ = 0;
+ *dest++ = 0;
+ }
+ } else {
+ *dest++ = r / n_pixels;
+ *dest++ = g / n_pixels;
+ *dest++ = b / n_pixels;
+ }
+
+ s_x1 = s_x2;
+ }
+ s_y1 = s_y2;
+ dest += dest_rowstride - dest_width * pixel_stride;
+ }
+
+ return dest_pixbuf;
+}
diff --git a/gthumb/gth-browser-actions-callbacks.c b/gthumb/gth-browser-actions-callbacks.c
new file mode 100644
index 0000000..9ea9842
--- /dev/null
+++ b/gthumb/gth-browser-actions-callbacks.c
@@ -0,0 +1,379 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2008 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <glib/gi18n.h>
+#include "dlg-bookmarks.h"
+#include "dlg-edit-metadata.h"
+#include "dlg-personalize-filters.h"
+#include "dlg-preferences.h"
+#include "dlg-sort-order.h"
+#include "gconf-utils.h"
+#include "glib-utils.h"
+#include "gth-browser.h"
+#include "gth-file-selection.h"
+#include "gth-main.h"
+#include "gth-preferences.h"
+#include "gth-viewer-page.h"
+#include "gtk-utils.h"
+
+
+void
+gth_browser_activate_action_bookmarks_add (GtkAction *action,
+ GthBrowser *browser)
+{
+ GBookmarkFile *bookmarks;
+ GFile *location;
+ char *uri;
+
+ location = gth_browser_get_location (browser);
+ if (location == NULL)
+ return;
+
+ bookmarks = gth_main_get_default_bookmarks ();
+ uri = g_file_get_uri (location);
+ _g_bookmark_file_add_uri (bookmarks, uri);
+ gth_main_bookmarks_changed ();
+
+ g_free (uri);
+}
+
+
+void
+gth_browser_activate_action_bookmarks_edit (GtkAction *action,
+ GthBrowser *browser)
+{
+ dlg_bookmarks (browser);
+}
+
+
+void
+gth_browser_activate_action_file_open_with (GtkAction *action,
+ GthBrowser *browser)
+{
+ /* FIXME */
+}
+
+
+void
+gth_browser_activate_action_file_revert (GtkAction *action,
+ GthBrowser *browser)
+{
+ gth_browser_load_file (browser, gth_browser_get_current_file (browser), TRUE);
+}
+
+
+void
+gth_browser_activate_action_file_save (GtkAction *action,
+ GthBrowser *browser)
+{
+ GtkWidget *viewer_page;
+
+ viewer_page = gth_browser_get_viewer_page (browser);
+ if (viewer_page == NULL)
+ return;
+
+ gth_viewer_page_save (GTH_VIEWER_PAGE (viewer_page), NULL, NULL, browser);
+}
+
+
+void
+gth_browser_activate_action_file_save_as (GtkAction *action,
+ GthBrowser *browser)
+{
+ GtkWidget *viewer_page;
+
+ viewer_page = gth_browser_get_viewer_page (browser);
+ if (viewer_page == NULL)
+ return;
+
+ gth_viewer_page_save_as (GTH_VIEWER_PAGE (viewer_page), NULL, NULL);
+}
+
+
+void
+gth_browser_activate_action_file_new_window (GtkAction *action,
+ GthBrowser *browser)
+{
+ GtkWidget *window;
+
+ window = gth_browser_new (NULL);
+ gth_browser_go_to (GTH_BROWSER (window), gth_browser_get_location (browser));
+ gtk_window_present (GTK_WINDOW (window));
+}
+
+
+void
+gth_browser_activate_action_edit_preferences (GtkAction *action,
+ GthBrowser *browser)
+{
+ dlg_preferences (browser);
+}
+
+
+void
+gth_browser_activate_action_go_back (GtkAction *action,
+ GthBrowser *browser)
+{
+ gth_browser_go_back (browser, 1);
+}
+
+
+void
+gth_browser_activate_action_go_forward (GtkAction *action,
+ GthBrowser *browser)
+{
+ gth_browser_go_forward (browser, 1);
+}
+
+
+void
+gth_browser_activate_action_go_up (GtkAction *action,
+ GthBrowser *browser)
+{
+ gth_browser_go_up (browser, 1);
+}
+
+
+void
+gth_browser_activate_action_go_home (GtkAction *action,
+ GthBrowser *browser)
+{
+ gth_browser_go_home (browser);
+}
+
+
+void
+gth_browser_activate_action_go_clear_history (GtkAction *action,
+ GthBrowser *browser)
+{
+ gth_browser_clear_history (browser);
+}
+
+
+void
+gth_browser_activate_action_view_filter (GtkAction *action,
+ GthBrowser *browser)
+{
+ dlg_personalize_filters (browser);
+}
+
+
+void
+gth_browser_activate_action_view_filterbar (GtkAction *action,
+ GthBrowser *browser)
+{
+ gth_browser_show_filterbar (browser, gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)));
+}
+
+
+void
+gth_browser_activate_action_view_sort_by (GtkAction *action,
+ GthBrowser *browser)
+{
+ dlg_sort_order (browser);
+}
+
+
+void
+gth_browser_activate_action_view_thumbnails (GtkAction *action,
+ GthBrowser *browser)
+{
+ gth_browser_enable_thumbnails (browser, gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)));
+}
+
+
+void
+gth_browser_activate_action_view_toolbar (GtkAction *action,
+ GthBrowser *browser)
+{
+ eel_gconf_set_boolean (PREF_UI_TOOLBAR_VISIBLE, gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)));
+}
+
+
+void
+gth_browser_activate_action_view_show_hidden_files (GtkAction *action,
+ GthBrowser *browser)
+{
+ eel_gconf_set_boolean (PREF_SHOW_HIDDEN_FILES, gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)));
+}
+
+
+void
+gth_browser_activate_action_view_statusbar (GtkAction *action,
+ GthBrowser *browser)
+{
+ eel_gconf_set_boolean (PREF_UI_STATUSBAR_VISIBLE, gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)));
+}
+
+
+void
+gth_browser_activate_action_view_stop (GtkAction *action,
+ GthBrowser *browser)
+{
+ gth_browser_stop (browser);
+}
+
+
+void
+gth_browser_activate_action_view_reload (GtkAction *action,
+ GthBrowser *browser)
+{
+ gth_browser_reload (browser);
+}
+
+
+void
+gth_browser_activate_action_view_prev (GtkAction *action,
+ GthBrowser *browser)
+{
+ gth_browser_show_prev_image (browser, FALSE, FALSE);
+}
+
+
+void
+gth_browser_activate_action_view_next (GtkAction *action,
+ GthBrowser *browser)
+{
+ gth_browser_show_next_image (browser, FALSE, FALSE);
+}
+
+
+void
+gth_browser_activate_action_folder_open (GtkAction *action,
+ GthBrowser *browser)
+{
+}
+
+
+void
+gth_browser_activate_action_folder_open_in_new_window (GtkAction *action,
+ GthBrowser *browser)
+{
+}
+
+
+void
+gth_browser_activate_action_browser_mode (GtkAction *action,
+ GthBrowser *browser)
+{
+ gth_window_set_current_page (GTH_WINDOW (browser), GTH_BROWSER_PAGE_BROWSER);
+}
+
+
+void
+gth_browser_activate_action_viewer_properties (GtkAction *action,
+ GthBrowser *browser)
+{
+ gth_browser_show_viewer_properties (GTH_BROWSER (browser), gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)));
+}
+
+
+void
+gth_browser_activate_action_viewer_tools (GtkAction *action,
+ GthBrowser *browser)
+{
+ gth_browser_show_viewer_tools (GTH_BROWSER (browser), gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)));
+}
+
+
+void
+gth_browser_activate_action_edit_metadata (GtkAction *action,
+ GthBrowser *browser)
+{
+ dlg_edit_metadata (browser);
+}
+
+
+void
+gth_browser_activate_action_edit_select_all (GtkAction *action,
+ GthBrowser *browser)
+{
+ gth_file_selection_select_all (GTH_FILE_SELECTION (gth_browser_get_file_list_view (browser)));
+}
+
+
+void
+gth_browser_activate_action_help_about (GtkAction *action,
+ gpointer data)
+{
+ GthWindow *window = GTH_WINDOW (data);
+ const char *authors[] = {
+#include "AUTHORS.tab"
+ "",
+ NULL
+ };
+ const char *documenters [] = {
+ "Paolo Bacchilega",
+ "Alexander Kirillov",
+ NULL
+ };
+ char *license_text;
+ const char *license[] = {
+ "gthumb is free software; you can redistribute it and/or modify "
+ "it under the terms of the GNU General Public License as published by "
+ "the Free Software Foundation; either version 2 of the License, or "
+ "(at your option) any later version.",
+ "gthumb is distributed in the hope that it will be useful, "
+ "but WITHOUT ANY WARRANTY; without even the implied warranty of "
+ "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the "
+ "GNU General Public License for more details.",
+ "You should have received a copy of the GNU General Public License "
+ "along with gthumb; if not, write to the Free Software Foundation, Inc., "
+ "51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA"
+ };
+
+ license_text = g_strconcat (license[0], "\n\n", license[1], "\n\n",
+ license[2], "\n\n", NULL);
+
+
+ gtk_show_about_dialog (GTK_WINDOW (window),
+ "version", VERSION,
+ "copyright", "Copyright \xc2\xa9 2001-2009 Free Software Foundation, Inc.",
+ "comments", _("An image viewer and browser for GNOME."),
+ "authors", authors,
+ "documenters", documenters,
+ "translator-credits", _("translator_credits"),
+ "logo-icon-name", "gthumb",
+ "license", license_text,
+ "wrap-license", TRUE,
+ "website", "http://gthumb.sourceforge.net",
+ NULL);
+
+ g_free (license_text);
+}
+
+
+void
+gth_browser_activate_action_help_help (GtkAction *action,
+ gpointer data)
+{
+ show_help_dialog (GTK_WINDOW (data), NULL);
+}
+
+
+void
+gth_browser_activate_action_help_shortcuts (GtkAction *action,
+ gpointer data)
+{
+ show_help_dialog (GTK_WINDOW (data), "shortcuts");
+}
diff --git a/gthumb/gth-browser-actions-callbacks.h b/gthumb/gth-browser-actions-callbacks.h
new file mode 100644
index 0000000..f9ca94c
--- /dev/null
+++ b/gthumb/gth-browser-actions-callbacks.h
@@ -0,0 +1,65 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2008 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GTH_BROWSER_ACTIONS_CALLBACKS_H
+#define GTH_BROWSER_ACTIONS_CALLBACKS_H
+
+#include <gtk/gtkaction.h>
+
+#define DEFINE_ACTION(x) void x (GtkAction *action, gpointer data);
+
+DEFINE_ACTION(gth_browser_activate_action_bookmarks_add)
+DEFINE_ACTION(gth_browser_activate_action_bookmarks_edit)
+DEFINE_ACTION(gth_browser_activate_action_browser_mode)
+DEFINE_ACTION(gth_browser_activate_action_edit_metadata)
+DEFINE_ACTION(gth_browser_activate_action_edit_preferences)
+DEFINE_ACTION(gth_browser_activate_action_edit_select_all)
+DEFINE_ACTION(gth_browser_activate_action_file_open_with)
+DEFINE_ACTION(gth_browser_activate_action_file_new_window)
+DEFINE_ACTION(gth_browser_activate_action_file_revert)
+DEFINE_ACTION(gth_browser_activate_action_file_save)
+DEFINE_ACTION(gth_browser_activate_action_file_save_as)
+DEFINE_ACTION(gth_browser_activate_action_folder_open)
+DEFINE_ACTION(gth_browser_activate_action_folder_open_in_new_window)
+DEFINE_ACTION(gth_browser_activate_action_go_back)
+DEFINE_ACTION(gth_browser_activate_action_go_forward)
+DEFINE_ACTION(gth_browser_activate_action_go_up)
+DEFINE_ACTION(gth_browser_activate_action_go_clear_history)
+DEFINE_ACTION(gth_browser_activate_action_go_home)
+DEFINE_ACTION(gth_browser_activate_action_help_help)
+DEFINE_ACTION(gth_browser_activate_action_help_shortcuts)
+DEFINE_ACTION(gth_browser_activate_action_help_about)
+DEFINE_ACTION(gth_browser_activate_action_view_sort_by)
+DEFINE_ACTION(gth_browser_activate_action_view_filter)
+DEFINE_ACTION(gth_browser_activate_action_view_filterbar)
+DEFINE_ACTION(gth_browser_activate_action_view_thumbnails)
+DEFINE_ACTION(gth_browser_activate_action_view_toolbar)
+DEFINE_ACTION(gth_browser_activate_action_view_show_hidden_files)
+DEFINE_ACTION(gth_browser_activate_action_view_statusbar)
+DEFINE_ACTION(gth_browser_activate_action_view_stop)
+DEFINE_ACTION(gth_browser_activate_action_view_reload)
+DEFINE_ACTION(gth_browser_activate_action_view_next)
+DEFINE_ACTION(gth_browser_activate_action_view_prev)
+DEFINE_ACTION(gth_browser_activate_action_viewer_properties)
+DEFINE_ACTION(gth_browser_activate_action_viewer_tools)
+
+#endif /* GTH_BROWSER_ACTIONS_CALLBACK_H */
diff --git a/gthumb/gth-browser-actions-entries.h b/gthumb/gth-browser-actions-entries.h
new file mode 100644
index 0000000..da2869f
--- /dev/null
+++ b/gthumb/gth-browser-actions-entries.h
@@ -0,0 +1,215 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2005-2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GTH_BROWSER_ACTION_ENTRIES_H
+#define GTH_BROWSER_ACTION_ENTRIES_H
+
+#include <config.h>
+#include <glib/gi18n.h>
+#include "gth-stock.h"
+
+static GtkActionEntry gth_browser_action_entries[] = {
+ { "FileMenu", NULL, N_("_File") },
+ { "EditMenu", NULL, N_("_Edit") },
+ { "ViewMenu", NULL, N_("_View") },
+ { "GoMenu", NULL, N_("_Go") },
+ { "BookmarksMenu", NULL, N_("_Bookmarks") },
+ { "HelpMenu", NULL, N_("_Help") },
+
+ { "File_NewWindow", "window-new",
+ N_("New _Window"), "<control>N",
+ N_("Open another window"),
+ G_CALLBACK (gth_browser_activate_action_file_new_window) },
+
+ { "File_OpenWith", GTK_STOCK_OPEN,
+ N_("_Open With..."), "",
+ N_("Open selected images with an application"),
+ G_CALLBACK (gth_browser_activate_action_file_open_with) },
+
+ { "File_Save", GTK_STOCK_SAVE,
+ NULL, "<control>s",
+ NULL,
+ G_CALLBACK (gth_browser_activate_action_file_save) },
+
+ { "File_SaveAs", GTK_STOCK_SAVE_AS,
+ NULL, NULL,
+ NULL,
+ G_CALLBACK (gth_browser_activate_action_file_save_as) },
+
+ { "File_Revert", GTK_STOCK_REVERT_TO_SAVED,
+ NULL, "F4",
+ NULL,
+ G_CALLBACK (gth_browser_activate_action_file_revert) },
+
+ { "Folder_Open", GTK_STOCK_OPEN,
+ N_("Open"), "",
+ NULL,
+ G_CALLBACK (gth_browser_activate_action_folder_open) },
+
+ { "Folder_OpenInNewWindow", NULL,
+ N_("Open in New Window"), "",
+ NULL,
+ G_CALLBACK (gth_browser_activate_action_folder_open_in_new_window) },
+
+ { "Edit_Preferences", GTK_STOCK_PREFERENCES,
+ NULL, NULL,
+ N_("Edit various preferences"),
+ G_CALLBACK (gth_browser_activate_action_edit_preferences) },
+
+ { "Edit_SelectAll", GTK_STOCK_SELECT_ALL,
+ NULL, NULL,
+ NULL,
+ G_CALLBACK (gth_browser_activate_action_edit_select_all) },
+
+ { "Edit_Metadata", GTK_STOCK_EDIT,
+ N_("Metadata"), "C",
+ N_("Edit file metadata"),
+ G_CALLBACK (gth_browser_activate_action_edit_metadata) },
+
+ { "View_Sort_By", NULL,
+ N_("_Sort By..."), NULL,
+ NULL,
+ G_CALLBACK (gth_browser_activate_action_view_sort_by) },
+
+ { "View_Filters", NULL,
+ N_("_Filter..."), NULL,
+ NULL,
+ G_CALLBACK (gth_browser_activate_action_view_filter) },
+
+ { "View_Stop", GTK_STOCK_STOP,
+ NULL, "Escape",
+ NULL,
+ G_CALLBACK (gth_browser_activate_action_view_stop) },
+
+ { "View_Reload", GTK_STOCK_REFRESH,
+ NULL, "<ctrl>R",
+ NULL,
+ G_CALLBACK (gth_browser_activate_action_view_reload) },
+
+ { "View_Prev", GTK_STOCK_GO_UP,
+ N_("Previous"), NULL,
+ N_("View previous image"),
+ G_CALLBACK (gth_browser_activate_action_view_prev) },
+
+ { "View_Next", GTK_STOCK_GO_DOWN,
+ N_("Next"), NULL,
+ N_("View next image"),
+ G_CALLBACK (gth_browser_activate_action_view_next) },
+
+ { "Go_Back", GTK_STOCK_GO_BACK,
+ NULL, "<alt>Left",
+ N_("Go to the previous visited location"),
+ G_CALLBACK (gth_browser_activate_action_go_back) },
+
+ { "Go_Forward", GTK_STOCK_GO_FORWARD,
+ NULL, "<alt>Right",
+ N_("Go to the next visited location"),
+ G_CALLBACK (gth_browser_activate_action_go_forward) },
+
+ { "Go_Up", GTK_STOCK_GO_UP,
+ NULL, "<alt>Up",
+ N_("Go up one level"),
+ G_CALLBACK (gth_browser_activate_action_go_up) },
+
+ { "Go_Home", NULL,
+ NULL, "h",
+ NULL,
+ G_CALLBACK (gth_browser_activate_action_go_home) },
+
+ { "Go_Clear_History", GTK_STOCK_CLEAR,
+ N_("_Delete History"), NULL,
+ N_("Delete the list of visited locations"),
+ G_CALLBACK (gth_browser_activate_action_go_clear_history) },
+
+ { "Bookmarks_Add", GTK_STOCK_ADD,
+ N_("_Add Bookmark"), "<control>D",
+ N_("Add current location to bookmarks"),
+ G_CALLBACK (gth_browser_activate_action_bookmarks_add) },
+
+ { "Bookmarks_Edit", NULL,
+ N_("_Edit Bookmarks..."), "<control>B",
+ N_("Edit bookmarks"),
+ G_CALLBACK (gth_browser_activate_action_bookmarks_edit) },
+
+ { "View_BrowserMode", GTH_STOCK_BROWSER_MODE,
+ N_("Browser"), NULL,
+ N_("View the folders"),
+ G_CALLBACK (gth_browser_activate_action_browser_mode) },
+
+ { "Help_About", GTK_STOCK_ABOUT,
+ NULL, NULL,
+ N_("Show information about gthumb"),
+ G_CALLBACK (gth_browser_activate_action_help_about) },
+
+ { "Help_Help", GTK_STOCK_HELP,
+ N_("Contents"), "F1",
+ N_("Display the gthumb Manual"),
+ G_CALLBACK (gth_browser_activate_action_help_help) },
+
+ { "Help_Shortcuts", NULL,
+ N_("_Keyboard Shortcuts"), NULL,
+ " ",
+ G_CALLBACK (gth_browser_activate_action_help_shortcuts) },
+};
+static guint gth_browser_action_entries_size = G_N_ELEMENTS (gth_browser_action_entries);
+
+
+static GtkToggleActionEntry gth_browser_action_toggle_entries[] = {
+ { "View_Toolbar", NULL,
+ N_("_Toolbar"), NULL,
+ N_("View or hide the toolbar of this window"),
+ G_CALLBACK (gth_browser_activate_action_view_toolbar),
+ TRUE },
+ { "View_Statusbar", NULL,
+ N_("_Statusbar"), NULL,
+ N_("View or hide the statusbar of this window"),
+ G_CALLBACK (gth_browser_activate_action_view_statusbar),
+ TRUE },
+ { "View_Filterbar", NULL,
+ N_("_Filterbar"), NULL,
+ N_("View or hide the filterbar of this window"),
+ G_CALLBACK (gth_browser_activate_action_view_filterbar),
+ TRUE },
+ { "View_Thumbnails", NULL,
+ N_("_Thumbnails"), "<control>T",
+ N_("View thumbnails"),
+ G_CALLBACK (gth_browser_activate_action_view_thumbnails),
+ TRUE },
+ { "View_ShowHiddenFiles", NULL,
+ N_("Show _Hidden Files"), "<control>H",
+ N_("Show hidden files and folders"),
+ G_CALLBACK (gth_browser_activate_action_view_show_hidden_files),
+ FALSE },
+ { "Viewer_Properties", GTK_STOCK_PROPERTIES,
+ NULL, NULL,
+ N_("View file properties"),
+ G_CALLBACK (gth_browser_activate_action_viewer_properties),
+ FALSE },
+ { "Viewer_Tools", GTK_STOCK_EDIT,
+ NULL, NULL,
+ N_("View file tools"),
+ G_CALLBACK (gth_browser_activate_action_viewer_tools),
+ FALSE },
+};
+static guint gth_browser_action_toggle_entries_size = G_N_ELEMENTS (gth_browser_action_toggle_entries);
+
+#endif /* GTH_BROWSER_ACTION_ENTRIES_H */
diff --git a/gthumb/gth-browser-ui.h b/gthumb/gth-browser-ui.h
new file mode 100644
index 0000000..9cadca2
--- /dev/null
+++ b/gthumb/gth-browser-ui.h
@@ -0,0 +1,204 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2004-2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GTH_BROWSER_UI_H
+#define GTH_BROWSER_UI_H
+
+#include <config.h>
+
+static const char *fixed_ui_info =
+"<ui>"
+" <menubar name='MenuBar'>"
+" <menu name='File' action='FileMenu'>"
+" <menuitem action='File_NewWindow'/>"
+" <separator/>"
+" <menuitem action='File_Save'/>"
+" <menuitem action='File_SaveAs'/>"
+" <menuitem action='File_Revert'/>"
+" <placeholder name='File_Actions'/>"
+" <placeholder name='File_Actions_2'/>"
+" <separator/>"
+" <placeholder name='Folder_Actions'/>"
+" <separator/>"
+" <placeholder name='Misc_Actions'/>"
+" <separator/>"
+" <menuitem action='File_CloseWindow'/>"
+" </menu>"
+" <menu name='Edit' action='EditMenu'>"
+" <placeholder name='File_Actions_1'/>"
+" <separator/>"
+" <placeholder name='File_Actions'/>"
+" <separator/>"
+" <placeholder name='List_Actions'/>"
+" <separator/>"
+" <placeholder name='Folder_Actions'/>"
+" <separator/>"
+" <placeholder name='Edit_Actions'/>"
+" <separator/>"
+" <menuitem action='Edit_Preferences'/>"
+" </menu>"
+" <menu name='View' action='ViewMenu'>"
+" <menuitem action='View_Stop'/>"
+" <menuitem action='View_Reload'/>"
+" <separator/>"
+" <menuitem action='View_Toolbar'/>"
+" <menuitem action='View_Statusbar'/>"
+" <placeholder name='View_Bars'/>"
+" <separator/>"
+" <placeholder name='File_Actions'/>"
+" <separator/>"
+" <placeholder name='Folder_Actions'/>"
+" </menu>"
+" <menu name='Go' action='GoMenu'>"
+" <menuitem action='Go_Back'/>"
+" <menuitem action='Go_Forward'/>"
+" <menuitem action='Go_Up'/>"
+" <separator name='BeforeEntryPointList'/>"
+" <placeholder name='EntryPointList'/>"
+" <separator name='EntryPointListSeparator'/>"
+" <menuitem action='Go_Clear_History'/>"
+" <separator name='BeforeHistoryList'/>"
+" <placeholder name='HistoryList'/>"
+" </menu>"
+" <menu name='Bookmarks' action='BookmarksMenu'>"
+" <menuitem action='Bookmarks_Add'/>"
+" <menuitem action='Bookmarks_Edit'/>"
+" <separator name='BookmarkListSeparator'/>"
+" <placeholder name='BookmarkList'/>"
+" </menu>"
+" <menu name='Help' action='HelpMenu'>"
+" <menuitem action='Help_Help'/>"
+" <menuitem action='Help_Shortcuts'/>"
+" <separator/>"
+" <menuitem name='About' action='Help_About'/>"
+" </menu>"
+" </menubar>"
+
+" <toolbar name='ToolBar'>"
+" <toolitem action='View_Stop'/>"
+" <separator/>"
+" <placeholder name='SourceCommands'/>"
+" <separator/>"
+" <placeholder name='BrowserCommands'/>"
+" <separator/>"
+" <toolitem action='File_CloseWindow'/>"
+" </toolbar>"
+
+" <toolbar name='ViewerToolBar'>"
+" <toolitem action='View_BrowserMode'/>"
+" <separator/>"
+" <toolitem action='View_Prev'/>"
+" <toolitem action='View_Next'/>"
+" <separator/>"
+" <toolitem action='Edit_Metadata'/>"
+" <separator/>"
+" <placeholder name='ViewerCommands'/>"
+" <separator expand='true'/>"
+" <placeholder name='ViewerCommandsSecondary'/>"
+" <toolitem action='Viewer_Properties'/>"
+" </toolbar>"
+
+" <popup name='GoBackHistoryPopup'>"
+" </popup>"
+
+" <popup name='GoForwardHistoryPopup'>"
+" </popup>"
+
+" <popup name='GoParentPopup'>"
+" </popup>"
+
+" <popup name='FileListPopup'>"
+" <menuitem action='File_OpenWith'/>"
+" <separator/>"
+" <placeholder name='File_Actions'/>"
+" <separator/>"
+" <placeholder name='Folder_Actions'/>"
+" <separator/>"
+" <placeholder name='Folder_Actions2'/>"
+" <separator/>"
+" <menuitem action='Edit_Metadata'/>"
+" </popup>"
+
+" <popup name='FolderListPopup'>"
+" <menuitem action='Folder_Open'/>"
+" <menuitem action='Folder_OpenInNewWindow'/>"
+" <separator/>"
+" <placeholder name='SourceCommands'/>"
+" </popup>"
+
+" <accelerator action=\"Go_Home\" />"
+
+"</ui>";
+
+
+static const char *browser_ui_info =
+"<ui>"
+" <menubar name='MenuBar'>"
+" <menu name='Edit' action='EditMenu'>"
+" <placeholder name='List_Actions'>"
+" <menuitem action='Edit_SelectAll'/>"
+" </placeholder>"
+" <placeholder name='Edit_Actions'>"
+" <menuitem action='Edit_Metadata'/>"
+" </placeholder>"
+" </menu>"
+" <menu name='View' action='ViewMenu'>"
+" <placeholder name='View_Bars'>"
+" <menuitem action='View_Filterbar'/>"
+" </placeholder>"
+" <placeholder name='Folder_Actions'>"
+" <menuitem action='View_ShowHiddenFiles'/>"
+" <menuitem action='View_Sort_By'/>"
+" <menuitem action='View_Filters'/>"
+" <separator/>"
+" <menuitem action='View_Thumbnails'/>"
+" </placeholder>"
+" </menu>"
+" </menubar>"
+" <toolbar name='ToolBar'>"
+" <placeholder name='BrowserCommands'>"
+" <toolitem action='Edit_Metadata'/>"
+" </placeholder>"
+" </toolbar>"
+"</ui>";
+
+
+static const char *viewer_ui_info =
+"<ui>"
+" <menubar name='MenuBar'>"
+" <menu name='File' action='FileMenu'>"
+" </menu>"
+" <menu name='Edit' action='EditMenu'>"
+" <placeholder name='Edit_Actions'>"
+" <menuitem action='Edit_Metadata'/>"
+" </placeholder>"
+" </menu>"
+" <menu name='View' action='ViewMenu'>"
+" <placeholder name='File_Actions'>"
+" <menuitem action='View_BrowserMode'/>"
+" </placeholder>"
+" </menu>"
+" </menubar>"
+"</ui>";
+
+
+#endif /* GTH_BROWSER_UI_H */
diff --git a/gthumb/gth-browser.c b/gthumb/gth-browser.c
new file mode 100644
index 0000000..04afeb7
--- /dev/null
+++ b/gthumb/gth-browser.c
@@ -0,0 +1,3707 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2005-2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <gtk/gtk.h>
+#include "dlg-personalize-filters.h"
+#include "gconf-utils.h"
+#include "glib-utils.h"
+#include "gtk-utils.h"
+#include "gth-browser.h"
+#include "gth-browser-actions-callbacks.h"
+#include "gth-browser-actions-entries.h"
+#include "gth-browser-ui.h"
+#include "gth-duplicable.h"
+#include "gth-enum-types.h"
+#include "gth-file-list.h"
+#include "gth-file-view.h"
+#include "gth-file-selection.h"
+#include "gth-filter.h"
+#include "gth-filterbar.h"
+#include "gth-folder-tree.h"
+#include "gth-icon-cache.h"
+#include "gth-image-preloader.h"
+#include "gth-location-chooser.h"
+#include "gth-main.h"
+#include "gth-marshal.h"
+#include "gth-metadata-provider.h"
+#include "gth-preferences.h"
+#include "gth-sidebar.h"
+#include "gth-statusbar.h"
+#include "gth-viewer-page.h"
+#include "gth-window.h"
+#include "gth-window-actions-callbacks.h"
+#include "gth-window-actions-entries.h"
+#include "gthumb-error.h"
+
+#define GTH_BROWSER_CALLBACK(f) ((GthBrowserCallback) (f))
+#define GO_BACK_HISTORY_POPUP "/GoBackHistoryPopup"
+#define GO_FORWARD_HISTORY_POPUP "/GoForwardHistoryPopup"
+#define GO_PARENT_POPUP "/GoParentPopup"
+#define MAX_HISTORY_LENGTH 15
+#define GCONF_NOTIFICATIONS 9
+#define DEF_SIDEBAR_WIDTH 250
+#define DEF_PROPERTIES_HEIGHT 128
+#define DEF_THUMBNAIL_SIZE 128
+
+typedef void (*GthBrowserCallback) (GthBrowser *, gboolean cancelled, gpointer user_data);
+
+typedef enum {
+ GTH_ACTION_GO_TO,
+ GTH_ACTION_GO_INTO,
+ GTH_ACTION_GO_BACK,
+ GTH_ACTION_GO_FORWARD,
+ GTH_ACTION_GO_UP,
+ GTH_ACTION_LIST_CHILDREN,
+ GTH_ACTION_VIEW
+} GthAction;
+
+enum {
+ LOCATION_READY,
+ LAST_SIGNAL
+};
+
+struct _GthBrowserPrivateData {
+ /* UI staff */
+
+ GtkUIManager *ui;
+ GtkActionGroup *actions;
+ GtkWidget *statusbar;
+ GtkWidget *browser_toolbar;
+ GtkWidget *browser_container;
+ GtkWidget *browser_sidebar;
+ GtkWidget *location_chooser;
+ GtkWidget *folder_tree;
+ GtkWidget *history_list_popup_menu;
+ GtkWidget *folder_popup;
+ GtkWidget *file_list_popup;
+ GtkWidget *filterbar;
+ GtkWidget *file_list;
+ GtkWidget *list_extra_widget_container;
+ GtkWidget *list_extra_widget;
+ GtkWidget *file_properties;
+
+ GtkWidget *viewer_pane;
+ GtkWidget *viewer_sidebar;
+ GtkWidget *viewer_container;
+ GtkWidget *viewer_toolbar;
+ GthViewerPage *viewer_page;
+ GthImagePreloader *image_preloader;
+
+ GHashTable *named_dialogs;
+ GList *toolbar_menu_buttons[GTH_BROWSER_N_PAGES];
+
+ guint browser_ui_merge_id;
+ guint viewer_ui_merge_id;
+
+ /* Browser data */
+
+ guint help_message_cid;
+ gulong bookmarks_changed_id;
+ gulong folder_changed_id;
+ gulong file_renamed_id;
+ gulong metadata_changed_id;
+ gulong entry_points_changed_id;
+ GFile *location;
+ GthFileData *current_file;
+ GthFileSource *location_source;
+ gboolean activity_ref;
+ GthIconCache *menu_icon_cache;
+ guint cnxn_id[GCONF_NOTIFICATIONS];
+ GthFileDataSort *sort_type;
+ gboolean sort_inverse;
+ gboolean show_hidden_files;
+ gboolean fast_file_type;
+ gboolean closing;
+ GthTask *task;
+ gulong task_completed;
+ GList *load_data_queue;
+
+ /* history */
+
+ GList *history;
+ GList *history_current;
+};
+
+
+static GthWindowClass *parent_class = NULL;
+static guint gth_browser_signals[LAST_SIGNAL] = { 0 };
+static GList *browser_list = NULL;
+
+
+/* -- monitor_event_data -- */
+
+
+typedef struct {
+ int ref;
+ GthFileSource *file_source;
+ GFile *parent;
+ GthMonitorEvent event;
+ GthBrowser *browser;
+ gboolean update_file_list;
+ gboolean update_folder_tree;
+} MonitorEventData;
+
+
+static MonitorEventData *
+monitor_event_data_new (void)
+{
+ MonitorEventData *monitor_data;
+
+ monitor_data = g_new0 (MonitorEventData, 1);
+ monitor_data->ref = 1;
+
+ return monitor_data;
+}
+
+
+G_GNUC_UNUSED
+static MonitorEventData *
+monitor_event_data_ref (MonitorEventData *monitor_data)
+{
+ monitor_data->ref++;
+ return monitor_data;
+}
+
+
+static void
+monitor_event_data_unref (MonitorEventData *monitor_data)
+{
+ monitor_data->ref--;
+
+ if (monitor_data->ref > 0)
+ return;
+
+ g_object_unref (monitor_data->file_source);
+ g_object_unref (monitor_data->parent);
+ g_free (monitor_data);
+}
+
+
+/* -- gth_browser -- */
+
+
+static void
+_gth_browser_set_action_sensitive (GthBrowser *browser,
+ const char *action_name,
+ gboolean sensitive)
+{
+ GtkAction *action;
+
+ action = gtk_action_group_get_action (browser->priv->actions, action_name);
+ g_object_set (action, "sensitive", sensitive, NULL);
+}
+
+
+static void
+_gth_browser_set_action_active (GthBrowser *browser,
+ const char *action_name,
+ gboolean active)
+{
+ GtkAction *action;
+
+ action = gtk_action_group_get_action (browser->priv->actions, action_name);
+ g_object_set (action, "active", active, NULL);
+}
+
+
+static void
+activate_go_back_menu_item (GtkMenuItem *menuitem,
+ gpointer data)
+{
+ GthBrowser *browser = data;
+
+ gth_browser_go_back (browser, GPOINTER_TO_INT (g_object_get_data (G_OBJECT (menuitem), "steps")));
+}
+
+
+static void
+activate_go_forward_menu_item (GtkMenuItem *menuitem,
+ gpointer data)
+{
+ GthBrowser *browser = data;
+
+ gth_browser_go_forward (browser, GPOINTER_TO_INT (g_object_get_data (G_OBJECT (menuitem), "steps")));
+}
+
+
+static void
+activate_go_up_menu_item (GtkMenuItem *menuitem,
+ gpointer data)
+{
+ GthBrowser *browser = data;
+
+ gth_browser_go_up (browser, GPOINTER_TO_INT (g_object_get_data (G_OBJECT (menuitem), "steps")));
+}
+
+
+static void
+activate_go_to_menu_item (GtkMenuItem *menuitem,
+ gpointer data)
+{
+ GthBrowser *browser = data;
+ GFile *location;
+
+ location = g_file_new_for_uri (g_object_get_data (G_OBJECT (menuitem), "uri"));
+ gth_browser_go_to (browser, location);
+
+ g_object_unref (location);
+}
+
+
+static void
+_gth_browser_add_file_menu_item_full (GthBrowser *browser,
+ GtkWidget *menu,
+ GFile *file,
+ GIcon *icon,
+ const char *display_name,
+ GthAction action,
+ int steps,
+ int position)
+{
+ GdkPixbuf *pixbuf;
+ GtkWidget *menu_item;
+
+ pixbuf = gth_icon_cache_get_pixbuf (browser->priv->menu_icon_cache, icon);
+
+ menu_item = gtk_image_menu_item_new_with_label (display_name);
+ if (pixbuf != NULL)
+ gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (menu_item), gtk_image_new_from_pixbuf (pixbuf));
+ else
+ gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (menu_item), gtk_image_new_from_stock (GTK_STOCK_OPEN, GTK_ICON_SIZE_MENU));
+ gtk_widget_show (menu_item);
+ if (position == -1)
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);
+ else
+ gtk_menu_shell_insert (GTK_MENU_SHELL (menu), menu_item, position);
+
+ if (action == GTH_ACTION_GO_TO) {
+ g_object_set_data_full (G_OBJECT (menu_item),
+ "uri",
+ g_file_get_uri (file),
+ (GDestroyNotify) g_free);
+ g_signal_connect (menu_item,
+ "activate",
+ G_CALLBACK (activate_go_to_menu_item),
+ browser);
+ }
+ else {
+ g_object_set_data (G_OBJECT (menu_item),
+ "steps",
+ GINT_TO_POINTER (steps));
+ if (action == GTH_ACTION_GO_BACK)
+ g_signal_connect (menu_item,
+ "activate",
+ G_CALLBACK (activate_go_back_menu_item),
+ browser);
+ else if (action == GTH_ACTION_GO_FORWARD)
+ g_signal_connect (menu_item,
+ "activate",
+ G_CALLBACK (activate_go_forward_menu_item),
+ browser);
+ else if (action == GTH_ACTION_GO_UP)
+ g_signal_connect (menu_item,
+ "activate",
+ G_CALLBACK (activate_go_up_menu_item),
+ browser);
+ }
+
+ if (pixbuf != NULL)
+ g_object_unref (pixbuf);
+}
+
+
+static void
+_gth_browser_add_file_menu_item (GthBrowser *browser,
+ GtkWidget *menu,
+ GFile *file,
+ GthAction action,
+ int steps)
+{
+ GthFileSource *file_source;
+ GFileInfo *info;
+
+ file_source = gth_main_get_file_source (file);
+ info = gth_file_source_get_file_info (file_source, file);
+ if (info != NULL) {
+ _gth_browser_add_file_menu_item_full (browser,
+ menu,
+ file,
+ g_file_info_get_icon (info),
+ g_file_info_get_display_name (info),
+ action,
+ steps,
+ -1);
+ g_object_unref (info);
+ }
+ g_object_unref (file_source);
+}
+
+
+static void
+_gth_browser_update_parent_list (GthBrowser *browser)
+{
+ GtkWidget *menu;
+ int i;
+ GFile *parent;
+
+ menu = gtk_ui_manager_get_widget (browser->priv->ui, GO_PARENT_POPUP);
+ _gtk_container_remove_children (GTK_CONTAINER (menu), NULL, NULL);
+
+ if (browser->priv->location == NULL)
+ return;
+
+ /* Update the parent list menu. */
+
+ i = 0;
+ parent = g_file_get_parent (browser->priv->location);
+ while (parent != NULL) {
+ GFile *parent_parent;
+
+ _gth_browser_add_file_menu_item (browser,
+ menu,
+ parent,
+ GTH_ACTION_GO_UP,
+ ++i);
+
+ parent_parent = g_file_get_parent (parent);
+ g_object_unref (parent);
+ parent = parent_parent;
+ }
+}
+
+
+void
+gth_browser_update_title (GthBrowser *browser)
+{
+ char *uri = NULL;
+ GString *title;
+
+ switch (gth_window_get_current_page (GTH_WINDOW (browser))) {
+ case GTH_BROWSER_PAGE_BROWSER:
+ if (browser->priv->location != NULL)
+ uri = g_file_get_uri (browser->priv->location);
+ break;
+
+ case GTH_BROWSER_PAGE_VIEWER:
+ if (browser->priv->current_file != NULL)
+ uri = g_file_get_uri (browser->priv->current_file->file);
+ break;
+
+ default:
+ break;
+ }
+
+ title = g_string_new (NULL);
+ if (uri != NULL) {
+ g_string_append (title, uri);
+ if (gth_browser_get_file_modified (browser)) {
+ g_string_append (title, " ");
+ g_string_append (title, _("[modified]"));
+ }
+ }
+ else
+ g_string_append (title, _("gthumb"));
+
+ gtk_window_set_title (GTK_WINDOW (browser), title->str);
+
+ g_string_free (title, TRUE);
+}
+
+
+void
+gth_browser_update_sensitivity (GthBrowser *browser)
+{
+ GFile *parent;
+ gboolean parent_available;
+ gboolean viewer_can_save;
+ gboolean modified;
+ int current_file_pos;
+ int n_files;
+ int n_selected;
+
+ if (browser->priv->location != NULL)
+ parent = g_file_get_parent (browser->priv->location);
+ else
+ parent = NULL;
+ parent_available = (parent != NULL);
+ _g_object_unref (parent);
+
+ viewer_can_save = (browser->priv->location != NULL) && (browser->priv->viewer_page != NULL) && gth_viewer_page_can_save (GTH_VIEWER_PAGE (browser->priv->viewer_page));
+ modified = gth_browser_get_file_modified (browser);
+
+ if (browser->priv->current_file != NULL)
+ current_file_pos = gth_file_store_find_visible (gth_browser_get_file_store (browser), browser->priv->current_file->file);
+ else
+ current_file_pos = -1;
+ n_files = gth_file_store_n_visibles (gth_browser_get_file_store (browser));
+ n_selected = gth_file_selection_get_n_selected (GTH_FILE_SELECTION (gth_browser_get_file_list_view (browser)));
+
+ _gth_browser_set_action_sensitive (browser, "File_Save", viewer_can_save && modified);
+ _gth_browser_set_action_sensitive (browser, "File_SaveAs", viewer_can_save);
+ _gth_browser_set_action_sensitive (browser, "File_Revert", viewer_can_save && modified);
+ _gth_browser_set_action_sensitive (browser, "Go_Up", parent_available);
+ _gth_browser_set_action_sensitive (browser, "Toolbar_Go_Up", parent_available);
+ _gth_browser_set_action_sensitive (browser, "View_Stop", (browser->priv->activity_ref > 0));
+ _gth_browser_set_action_sensitive (browser, "View_Prev", current_file_pos > 0);
+ _gth_browser_set_action_sensitive (browser, "View_Next", (current_file_pos != -1) && (current_file_pos < n_files - 1));
+ _gth_browser_set_action_sensitive (browser, "Edit_Metadata", n_selected > 0);
+
+ gth_sidebar_update_sensitivity (GTH_SIDEBAR (browser->priv->viewer_sidebar));
+ if (browser->priv->viewer_page != NULL)
+ gth_viewer_page_update_sensitivity (browser->priv->viewer_page);
+
+ gth_hook_invoke ("gth-browser-update-sensitivity", browser);
+}
+
+
+static void
+_gth_browser_set_location (GthBrowser *browser,
+ GFile *location)
+{
+ if (location == NULL)
+ return;
+
+ if (browser->priv->location != NULL)
+ g_object_unref (browser->priv->location);
+ browser->priv->location = g_file_dup (location);
+
+ gth_browser_update_title (browser);
+ _gth_browser_update_parent_list (browser);
+ gth_browser_update_sensitivity (browser);
+
+ g_signal_handlers_block_by_data (browser->priv->location_chooser, browser);
+ gth_location_chooser_set_current (GTH_LOCATION_CHOOSER (browser->priv->location_chooser), browser->priv->location);
+ g_signal_handlers_unblock_by_data (browser->priv->location_chooser, browser);
+}
+
+
+static void
+_gth_browser_update_go_sensitivity (GthBrowser *browser)
+{
+ gboolean sensitive;
+
+ sensitive = (browser->priv->history_current != NULL) && (browser->priv->history_current->next != NULL);
+ _gth_browser_set_action_sensitive (browser, "Go_Back", sensitive);
+ _gth_browser_set_action_sensitive (browser, "Toolbar_Go_Back", sensitive);
+
+ sensitive = (browser->priv->history_current != NULL) && (browser->priv->history_current->prev != NULL);
+ _gth_browser_set_action_sensitive (browser, "Go_Forward", sensitive);
+ _gth_browser_set_action_sensitive (browser, "Toolbar_Go_Forward", sensitive);
+}
+
+
+static void
+activate_clear_history_menu_item (GtkMenuItem *menuitem,
+ gpointer data)
+{
+ gth_browser_clear_history ((GthBrowser *)data);
+}
+
+
+static void
+_gth_browser_add_clear_history_menu_item (GthBrowser *browser,
+ GtkWidget *menu)
+{
+ GtkWidget *menu_item;
+
+ menu_item = gtk_separator_menu_item_new ();
+ gtk_widget_show (menu_item);
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);
+
+ menu_item = gtk_image_menu_item_new_with_mnemonic (_("_Delete History"));
+ gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (menu_item), gtk_image_new_from_stock (GTK_STOCK_CLEAR, GTK_ICON_SIZE_MENU));
+ gtk_widget_show (menu_item);
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);
+
+ g_signal_connect (menu_item,
+ "activate",
+ G_CALLBACK (activate_clear_history_menu_item),
+ browser);
+}
+
+
+static void
+_gth_browser_update_history_list (GthBrowser *browser)
+{
+ GtkWidget *menu;
+ GList *scan;
+ GtkWidget *separator;
+
+ _gth_browser_update_go_sensitivity (browser);
+
+ /* Update the back history menu. */
+
+ menu = gtk_ui_manager_get_widget (browser->priv->ui, GO_BACK_HISTORY_POPUP);
+ _gtk_container_remove_children (GTK_CONTAINER (menu), NULL, NULL);
+
+ if ((browser->priv->history != NULL)
+ && (browser->priv->history_current->next != NULL))
+ {
+ int i;
+
+ for (i = 0, scan = browser->priv->history_current->next;
+ scan && (i < MAX_HISTORY_LENGTH);
+ scan = scan->next)
+ {
+ _gth_browser_add_file_menu_item (browser,
+ menu,
+ scan->data,
+ GTH_ACTION_GO_BACK,
+ ++i);
+ }
+ if (i > 0)
+ _gth_browser_add_clear_history_menu_item (browser, menu);
+ }
+
+ /* Update the forward history menu. */
+
+ menu = gtk_ui_manager_get_widget (browser->priv->ui, GO_FORWARD_HISTORY_POPUP);
+ _gtk_container_remove_children (GTK_CONTAINER (menu), NULL, NULL);
+
+ if ((browser->priv->history != NULL)
+ && (browser->priv->history_current->prev != NULL))
+ {
+ int i;
+
+ for (i = 0, scan = browser->priv->history_current->prev;
+ scan && (i < MAX_HISTORY_LENGTH);
+ scan = scan->prev)
+ {
+ _gth_browser_add_file_menu_item (browser,
+ menu,
+ scan->data,
+ GTH_ACTION_GO_FORWARD,
+ ++i);
+ }
+ if (i > 0)
+ _gth_browser_add_clear_history_menu_item (browser, menu);
+ }
+
+ /* Update the history list in the go menu */
+
+ separator = gtk_ui_manager_get_widget (browser->priv->ui, "/MenuBar/Go/HistoryList");
+ menu = gtk_widget_get_parent (separator);
+
+ _gtk_container_remove_children (GTK_CONTAINER (menu), separator, NULL);
+
+ if (browser->priv->history != NULL) {
+ int i;
+
+ for (i = 0, scan = browser->priv->history;
+ scan && (i < MAX_HISTORY_LENGTH);
+ scan = scan->next)
+ {
+ _gth_browser_add_file_menu_item (browser,
+ menu,
+ scan->data,
+ GTH_ACTION_GO_TO,
+ ++i);
+ }
+ }
+
+ separator = gtk_ui_manager_get_widget (browser->priv->ui, "/MenuBar/Go/BeforeHistoryList");
+ gtk_widget_show (separator);
+}
+
+
+static void
+_gth_browser_add_to_history (GthBrowser *browser,
+ GFile *file)
+{
+ if (file == NULL)
+ return;
+
+ if ((browser->priv->history_current == NULL) || ! g_file_equal (file, browser->priv->history_current->data)) {
+ browser->priv->history = g_list_prepend (browser->priv->history, g_object_ref (file));
+ browser->priv->history_current = browser->priv->history;
+ }
+}
+
+
+static void
+_gth_browser_update_bookmark_list (GthBrowser *browser)
+{
+ GtkWidget *menu;
+ GtkWidget *bookmark_list;
+ GtkWidget *bookmark_list_separator;
+ GBookmarkFile *bookmarks;
+ char **uris;
+ gsize length;
+ int i;
+
+ bookmark_list = gtk_ui_manager_get_widget (browser->priv->ui, "/MenuBar/Bookmarks/BookmarkList");
+ menu = gtk_widget_get_parent (bookmark_list);
+
+ _gtk_container_remove_children (GTK_CONTAINER (menu), bookmark_list, NULL);
+
+ bookmarks = gth_main_get_default_bookmarks ();
+ uris = g_bookmark_file_get_uris (bookmarks, &length);
+
+ bookmark_list_separator = gtk_ui_manager_get_widget (browser->priv->ui, "/MenuBar/Bookmarks/BookmarkListSeparator");
+ if (length > 0)
+ gtk_widget_show (bookmark_list_separator);
+ else
+ gtk_widget_hide (bookmark_list_separator);
+
+ for (i = 0; uris[i] != NULL; i++) {
+ GFile *file;
+
+ file = g_file_new_for_uri (uris[i]);
+ _gth_browser_add_file_menu_item (browser,
+ menu,
+ file,
+ GTH_ACTION_GO_TO,
+ i);
+
+ g_object_unref (file);
+ }
+
+ g_strfreev (uris);
+}
+
+
+static void
+_gth_browser_monitor_entry_points (GthBrowser *browser)
+{
+ GList *scan;
+
+ for (scan = gth_main_get_all_file_sources (); scan; scan = scan->next) {
+ GthFileSource *file_source = scan->data;
+ gth_file_source_monitor_entry_points (file_source);
+ }
+}
+
+
+static void
+_gth_browser_update_entry_point_list (GthBrowser *browser)
+{
+ GtkWidget *separator1;
+ GtkWidget *separator2;
+ GtkWidget *menu;
+ GList *entry_points;
+ GList *scan;
+ int position;
+ GFile *root;
+
+ separator1 = gtk_ui_manager_get_widget (browser->priv->ui, "/MenuBar/Go/BeforeEntryPointList");
+ separator2 = gtk_ui_manager_get_widget (browser->priv->ui, "/MenuBar/Go/EntryPointList");
+ menu = gtk_widget_get_parent (separator1);
+ _gtk_container_remove_children (GTK_CONTAINER (menu), separator1, separator2);
+
+ separator1 = separator2;
+ separator2 = gtk_ui_manager_get_widget (browser->priv->ui, "/MenuBar/Go/EntryPointListSeparator");
+ _gtk_container_remove_children (GTK_CONTAINER (menu), separator1, separator2);
+
+ position = 5;
+ entry_points = gth_main_get_all_entry_points ();
+ for (scan = entry_points; scan; scan = scan->next) {
+ GthFileData *file_data = scan->data;
+
+ g_file_info_set_attribute_boolean (file_data->info, "gthumb::entry-point", TRUE);
+ g_file_info_set_sort_order (file_data->info, position);
+ _gth_browser_add_file_menu_item_full (browser,
+ menu,
+ file_data->file,
+ g_file_info_get_icon (file_data->info),
+ g_file_info_get_display_name (file_data->info),
+ GTH_ACTION_GO_TO,
+ 0,
+ position++);
+ }
+ root = g_file_new_for_uri ("gthumb-vfs:///");
+ gth_folder_tree_set_children (GTH_FOLDER_TREE (browser->priv->folder_tree), root, entry_points);
+
+ g_object_unref (root);
+ _g_object_list_unref (entry_points);
+}
+
+
+static GthTest *
+_gth_browser_get_file_filter (GthBrowser *browser)
+{
+ GthTest *filterbar_test;
+ GthTest *test;
+
+ filterbar_test = gth_filterbar_get_test (GTH_FILTERBAR (browser->priv->filterbar));
+ test = gth_main_add_general_filter (filterbar_test);
+
+ _g_object_unref (filterbar_test);
+
+ return test;
+}
+
+
+static void
+_gth_browser_update_statusbar_list_info (GthBrowser *browser)
+{
+ GList *file_list;
+ int n_total;
+ gsize size_total;
+ GList *scan;
+ int n_selected;
+ gsize size_selected;
+ GList *selected;
+ char *size_total_formatted;
+ char *size_selected_formatted;
+ char *text_total;
+ char *text_selected;
+ char *text;
+
+ /* total */
+
+ file_list = gth_file_store_get_visibles (gth_browser_get_file_store (browser));
+ n_total = 0;
+ size_total = 0;
+ for (scan = file_list; scan; scan = scan->next) {
+ GthFileData *file_data = scan->data;
+
+ n_total++;
+ size_total += g_file_info_get_size (file_data->info);
+ }
+ _g_object_list_unref (file_list);
+
+ /* selected */
+
+ selected = gth_file_selection_get_selected (GTH_FILE_SELECTION (gth_browser_get_file_list_view (browser)));
+ file_list = gth_file_list_get_files (GTH_FILE_LIST (gth_browser_get_file_list (browser)), selected);
+
+ n_selected = 0;
+ size_selected = 0;
+ for (scan = file_list; scan; scan = scan->next) {
+ GthFileData *file_data = scan->data;
+
+ n_selected++;
+ size_selected += g_file_info_get_size (file_data->info);
+ }
+ _g_object_list_unref (file_list);
+ _gtk_tree_path_list_free (selected);
+
+ /**/
+
+ size_total_formatted = g_format_size_for_display (size_total);
+ size_selected_formatted = g_format_size_for_display (size_selected);
+ text_total = g_strdup_printf (g_dngettext (NULL, "%d file (%s)", "%d files (%s)", n_total), n_total, size_total_formatted);
+ text_selected = g_strdup_printf (g_dngettext (NULL, "%d file (%s)", "%d files (%s)", n_selected), n_selected, size_selected_formatted);
+ text = g_strconcat (text_total,
+ ((n_selected == 0) ? NULL : ", "),
+ text_selected,
+ NULL);
+ gth_statusbar_set_list_info (GTH_STATUSBAR (browser->priv->statusbar), text);
+
+ g_free (text);
+ g_free (text_selected);
+ g_free (text_total);
+ g_free (size_selected_formatted);
+ g_free (size_total_formatted);
+}
+
+
+typedef struct {
+ GthBrowser *browser;
+ GFile *folder;
+ GthAction action;
+ GList *list;
+ GList *current;
+ GFile *entry_point;
+ GthFileSource *file_source;
+ GCancellable *cancellable;
+} LoadData;
+
+
+static LoadData *
+load_data_new (GthBrowser *browser,
+ GFile *location,
+ GthAction action,
+ GFile *entry_point)
+{
+ LoadData *load_data;
+ GFile *file;
+
+ load_data = g_new0 (LoadData, 1);
+ load_data->browser = browser;
+ load_data->folder = g_object_ref (location);
+ load_data->action = action;
+ load_data->cancellable = g_cancellable_new ();
+
+ if (entry_point == NULL)
+ return load_data;
+
+ load_data->entry_point = g_object_ref (entry_point);
+ load_data->file_source = gth_main_get_file_source (load_data->folder);
+
+ file = g_object_ref (load_data->folder);
+ load_data->list = g_list_prepend (NULL, g_object_ref (file));
+ while (! g_file_equal (load_data->entry_point, file)) {
+ GFile *parent;
+
+ parent = g_file_get_parent (file);
+ g_object_unref (file);
+ file = parent;
+
+ load_data->list = g_list_prepend (load_data->list, g_object_ref (file));
+ }
+ g_object_unref (file);
+ load_data->current = NULL;
+
+ browser->priv->load_data_queue = g_list_prepend (browser->priv->load_data_queue, load_data);
+
+ return load_data;
+}
+
+
+static void
+load_data_free (LoadData *data)
+{
+ data->browser->priv->load_data_queue = g_list_remove (data->browser->priv->load_data_queue, data);
+
+ g_object_unref (data->folder);
+ _g_object_unref (data->file_source);
+ _g_object_list_unref (data->list);
+ _g_object_unref (data->entry_point);
+ g_object_unref (data->cancellable);
+ g_free (data);
+}
+
+
+static void
+load_data_cancel (LoadData *data)
+{
+ if (data->file_source != NULL)
+ gth_file_source_cancel (data->file_source);
+ g_cancellable_cancel (data->cancellable);
+}
+
+
+static void
+load_data_done (LoadData *data,
+ GError *error)
+{
+ GthBrowser *browser = data->browser;
+
+ {
+ char *uri;
+
+ uri = g_file_get_uri (data->folder);
+ debug (DEBUG_INFO, "LOAD READY: %s [%s]\n", uri, (error == NULL ? "Ok" : "Error"));
+ performance (DEBUG_INFO, "load done for %s", uri);
+
+ g_free (uri);
+ }
+
+ browser->priv->activity_ref--;
+ g_signal_emit (G_OBJECT (browser),
+ gth_browser_signals[LOCATION_READY],
+ 0,
+ data->folder,
+ (error != NULL));
+
+ if (error == NULL) {
+ _g_object_unref (browser->priv->location_source);
+ browser->priv->location_source = g_object_ref (data->file_source);
+ }
+
+ gth_hook_invoke ("gth-browser-load-location-after", browser, data->folder, error);
+
+ if (error == NULL)
+ return;
+
+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+ g_error_free (error);
+ return;
+ }
+
+#if 0
+ /* update the folder list */
+
+ switch (data->action) {
+ case GTH_ACTION_LIST_CHILDREN:
+ gth_folder_tree_set_children (GTH_FOLDER_TREE (browser->priv->folder_tree), data->folder, NULL);
+ break;
+ default:
+ break;
+ }
+
+ /* update the file list */
+
+ switch (data->action) {
+ case GTH_ACTION_VIEW:
+ case GTH_ACTION_GO_BACK:
+ case GTH_ACTION_GO_FORWARD:
+ case GTH_ACTION_GO_INTO:
+ case GTH_ACTION_GO_TO:
+ gth_file_list_set_files (GTH_FILE_LIST (browser->priv->file_list), NULL, NULL);
+ break;
+ default:
+ break;
+ }
+#endif
+
+ gth_browser_update_sensitivity (browser);
+ _gtk_error_dialog_from_gerror_show (GTK_WINDOW (browser), _("Could not load the position"), &error);
+}
+
+
+static void _gth_browser_load_ready_cb (GthFileSource *file_source, GList *files, GError *error, gpointer user_data);
+
+
+static void
+load_data_load_next_folder (LoadData *data)
+{
+ GthFolderTree *folder_tree;
+ GFile *folder_to_load;
+
+ folder_tree = GTH_FOLDER_TREE (data->browser->priv->folder_tree);
+ do {
+ GtkTreePath *path;
+
+ if (data->current == NULL)
+ data->current = data->list;
+ else
+ data->current = data->current->next;
+
+ folder_to_load = (GFile *) data->current->data;
+
+ if (g_file_equal (folder_to_load, data->folder))
+ break;
+
+ path = gth_folder_tree_get_path (folder_tree, folder_to_load);
+ if (path == NULL)
+ break;
+
+ if (! gth_folder_tree_is_loaded (folder_tree, path)) {
+ gtk_tree_path_free (path);
+ break;
+ }
+
+ if (! g_file_equal (folder_to_load, data->folder))
+ gth_folder_tree_expand_row (folder_tree, path, FALSE);
+
+ gtk_tree_path_free (path);
+ }
+ while (TRUE);
+
+ gth_file_source_list (data->file_source,
+ folder_to_load,
+ eel_gconf_get_boolean (PREF_FAST_FILE_TYPE, TRUE) ? GTH_FILE_DATA_ATTRIBUTES_WITH_FAST_CONTENT_TYPE : GTH_FILE_DATA_ATTRIBUTES_WITH_CONTENT_TYPE,
+ _gth_browser_load_ready_cb,
+ data);
+}
+
+
+static void
+load_data_continue (LoadData *data,
+ GList *loaded_files)
+{
+ GthBrowser *browser = data->browser;
+ GList *files;
+ GFile *loaded_folder;
+ GtkTreePath *path;
+ GthTest *filter;
+
+ if ((data->action != GTH_ACTION_LIST_CHILDREN)
+ && ! g_file_equal (data->folder, data->browser->priv->location))
+ {
+ load_data_done (data, g_error_new_literal (G_IO_ERROR, G_IO_ERROR_CANCELLED, ""));
+ load_data_free (data);
+ return;
+ }
+
+ if (! browser->priv->show_hidden_files) {
+ GList *scan;
+
+ files = NULL;
+ for (scan = loaded_files; scan; scan = scan->next) {
+ GthFileData *file_data = scan->data;
+
+ if (! g_file_info_get_is_hidden (file_data->info))
+ files = g_list_prepend (files, file_data);
+ }
+ files = g_list_reverse (files);
+ }
+ else
+ files = g_list_copy (loaded_files);
+
+ loaded_folder = (GFile *) data->current->data;
+ gth_folder_tree_set_children (GTH_FOLDER_TREE (browser->priv->folder_tree), loaded_folder, files);
+ path = gth_folder_tree_get_path (GTH_FOLDER_TREE (browser->priv->folder_tree), loaded_folder);
+ if ((path != NULL) && ! g_file_equal (loaded_folder, data->folder))
+ gth_folder_tree_expand_row (GTH_FOLDER_TREE (browser->priv->folder_tree), path, FALSE);
+
+ if (! g_file_equal (loaded_folder, data->folder)) {
+ gtk_tree_path_free (path);
+ g_list_free (files);
+
+ load_data_load_next_folder (data);
+ return;
+ }
+
+ load_data_done (data, NULL);
+
+ switch (data->action) {
+ case GTH_ACTION_VIEW:
+ case GTH_ACTION_GO_BACK:
+ case GTH_ACTION_GO_FORWARD:
+ case GTH_ACTION_GO_TO:
+ if (path != NULL) {
+ gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (browser->priv->folder_tree), path, NULL, FALSE, .0, .0);
+ gth_folder_tree_select_path (GTH_FOLDER_TREE (browser->priv->folder_tree), path);
+ }
+ break;
+ default:
+ break;
+ }
+
+ switch (data->action) {
+ case GTH_ACTION_VIEW:
+ case GTH_ACTION_GO_BACK:
+ case GTH_ACTION_GO_FORWARD:
+ case GTH_ACTION_GO_INTO:
+ case GTH_ACTION_GO_TO:
+ filter = _gth_browser_get_file_filter (browser);
+ gth_file_list_set_filter (GTH_FILE_LIST (browser->priv->file_list), filter);
+ gth_file_list_set_files (GTH_FILE_LIST (browser->priv->file_list), data->file_source, files);
+ g_object_unref (filter);
+ break;
+ default:
+ break;
+ }
+
+ gth_browser_update_sensitivity (browser);
+ _gth_browser_update_statusbar_list_info (browser);
+
+ gth_file_source_monitor_directory (browser->priv->location_source,
+ browser->priv->location,
+ TRUE);
+
+ if (path != NULL)
+ gtk_tree_path_free (path);
+ load_data_free (data);
+ g_list_free (files);
+}
+
+
+static void
+metadata_ready_cb (GList *files,
+ GError *error,
+ gpointer user_data)
+{
+ LoadData *load_data = user_data;
+
+ if (error != NULL) {
+ load_data_done (load_data, error);
+ load_data_free (load_data);
+ return;
+ }
+
+ load_data_continue (load_data, files);
+}
+
+
+static void
+load_data_ready (LoadData *data,
+ GList *files,
+ GError *error)
+{
+ if (error != NULL) {
+ load_data_done (data, error);
+ load_data_free (data);
+ }
+ else if (g_file_equal ((GFile *) data->current->data, data->folder))
+ /* FIXME: make the metadata attribute list automatic, based
+ * on the data required to filter and view the file list. */
+ _g_query_metadata_async (files,
+ "file::*,comment::*",
+ data->cancellable,
+ metadata_ready_cb,
+ data);
+ else
+ load_data_continue (data, files);
+}
+
+
+static void
+_gth_browser_load_ready_cb (GthFileSource *file_source,
+ GList *files,
+ GError *error,
+ gpointer user_data)
+{
+ load_data_ready ((LoadData *) user_data, files, error);
+}
+
+
+#if 0
+static void
+_gth_browser_print_history (GthBrowser *browser)
+{
+ GList *scan;
+
+ g_print ("history:\n");
+ for (scan = browser->priv->history; scan; scan = scan->next) {
+ GFile *file = scan->data;
+ char *uri;
+
+ uri = g_file_get_uri (file);
+ g_print (" %s%s\n", (browser->priv->history_current == scan) ? "*" : " ", uri);
+
+ g_free (uri);
+ }
+}
+#endif
+
+
+static void
+_gth_browser_cancel (GthBrowser *browser)
+{
+ g_list_foreach (browser->priv->load_data_queue, (GFunc) load_data_cancel, NULL);
+}
+
+
+static GFile *
+get_nearest_entry_point (GFile *file)
+{
+ GList *list;
+ GList *scan;
+ GList *entries;
+ char *nearest_uri;
+ int max_len;
+ GFile *nearest;
+
+ entries = NULL;
+ list = gth_main_get_all_entry_points ();
+ for (scan = list; scan; scan = scan->next) {
+ GthFileData *entry_point = scan->data;
+
+ if (g_file_equal (file, entry_point->file) || g_file_has_prefix (file, entry_point->file))
+ entries = g_list_prepend (entries, g_file_get_uri (entry_point->file));
+ }
+
+ nearest_uri = NULL;
+ max_len = 0;
+ for (scan = entries; scan; scan = scan->next) {
+ char *entry_uri = scan->data;
+ int entry_len = strlen (entry_uri);
+
+ if (entry_len > max_len) {
+ nearest_uri = entry_uri;
+ max_len = entry_len;
+ }
+ }
+
+ nearest = NULL;
+ if (nearest_uri != NULL)
+ nearest = g_file_new_for_uri (nearest_uri);
+
+ _g_string_list_free (entries);
+ _g_object_list_unref (list);
+
+ return nearest;
+}
+
+
+static void
+_gth_browser_load (GthBrowser *browser,
+ GFile *location,
+ GthAction action)
+{
+ LoadData *load_data;
+ GFile *entry_point;
+
+ _gth_browser_cancel (browser);
+
+ switch (action) {
+ case GTH_ACTION_GO_BACK:
+ case GTH_ACTION_GO_FORWARD:
+ case GTH_ACTION_GO_TO:
+ if (browser->priv->location_source != NULL) {
+ gth_file_source_monitor_directory (browser->priv->location_source,
+ browser->priv->location,
+ FALSE);
+ _g_object_unref (browser->priv->location_source);
+ browser->priv->location_source = NULL;
+ }
+ break;
+ default:
+ break;
+ }
+
+ entry_point = get_nearest_entry_point (location);
+ load_data = load_data_new (browser, location, action, entry_point);
+
+ gth_hook_invoke ("gth-browser-load-location-before", browser, load_data->folder);
+ browser->priv->activity_ref++;
+
+ if (entry_point == NULL) {
+ GError *error;
+ char *uri;
+
+ uri = g_file_get_uri (location);
+ error = g_error_new (GTHUMB_ERROR, 0, _("No suitable module found for %s"), uri);
+ load_data_ready (load_data, NULL, error);
+
+ g_free (uri);
+
+ return;
+ }
+
+ switch (action) {
+ case GTH_ACTION_LIST_CHILDREN:
+ gth_folder_tree_loading_children (GTH_FOLDER_TREE (browser->priv->folder_tree), location);
+ break;
+ case GTH_ACTION_GO_BACK:
+ case GTH_ACTION_GO_FORWARD:
+ case GTH_ACTION_GO_TO:
+ case GTH_ACTION_VIEW:
+ gth_file_list_clear (GTH_FILE_LIST (browser->priv->file_list), _("Getting folder listing..."));
+ break;
+ default:
+ break;
+ }
+
+ if (load_data->file_source == NULL) {
+ GError *error;
+ char *uri;
+
+ uri = g_file_get_uri (load_data->folder);
+ error = g_error_new (GTHUMB_ERROR, 0, _("No suitable module found for %s"), uri);
+ load_data_ready (load_data, NULL, error);
+
+ g_free (uri);
+
+ return;
+ }
+
+ switch (load_data->action) {
+ case GTH_ACTION_GO_INTO:
+ case GTH_ACTION_GO_TO:
+ _gth_browser_set_location (browser, load_data->folder);
+ _gth_browser_add_to_history (browser, browser->priv->location);
+ _gth_browser_update_history_list (browser);
+ break;
+ case GTH_ACTION_GO_BACK:
+ case GTH_ACTION_GO_FORWARD:
+ _gth_browser_set_location (browser, load_data->folder);
+ _gth_browser_update_history_list (browser);
+ break;
+ case GTH_ACTION_VIEW:
+ _gth_browser_set_location (browser, load_data->folder);
+ _gth_browser_add_to_history (browser, browser->priv->location);
+ _gth_browser_update_history_list (browser);
+ break;
+ default:
+ break;
+ }
+
+ {
+ char *uri;
+
+ uri = g_file_get_uri (load_data->folder);
+
+ debug (DEBUG_INFO, "LOAD: %s\n", uri);
+ performance (DEBUG_INFO, "loading %s", uri);
+
+ g_free (uri);
+ }
+
+ gth_browser_update_sensitivity (browser);
+ gth_browser_set_list_extra_widget (browser, NULL);
+ load_data_load_next_folder (load_data);
+
+ g_object_unref (entry_point);
+}
+
+
+/* -- _gth_browser_ask_whether_to_save -- */
+
+
+typedef struct {
+ GthBrowser *browser;
+ GthBrowserCallback callback;
+ gpointer user_data;
+} AskSaveData;
+
+
+static void
+ask_whether_to_save__done (AskSaveData *data,
+ gboolean cancelled)
+{
+ if (cancelled)
+ g_file_info_set_attribute_boolean (data->browser->priv->current_file->info, "file::is-modified", TRUE);
+ if (data->callback != NULL)
+ (*data->callback) (data->browser, cancelled, data->user_data);
+ g_free (data);
+}
+
+
+static void
+ask_whether_to_save__file_saved_cb (GthViewerPage *viewer_page,
+ GthFileData *file_data,
+ GError *error,
+ gpointer user_data)
+{
+ AskSaveData *data = user_data;
+ gboolean error_occurred;
+
+ error_occurred = error != NULL;
+ if (error != NULL)
+ _gtk_error_dialog_from_gerror_show (GTK_WINDOW (data->browser), _("Could not save the file"), &error);
+ ask_whether_to_save__done (data, error_occurred);
+}
+
+
+enum {
+ RESPONSE_SAVE,
+ RESPONSE_NO_SAVE,
+};
+
+
+static void
+ask_whether_to_save__response_cb (GtkWidget *dialog,
+ int response_id,
+ AskSaveData *data)
+{
+ gtk_widget_destroy (dialog);
+
+ if (response_id == RESPONSE_SAVE)
+ gth_viewer_page_save (data->browser->priv->viewer_page,
+ NULL,
+ ask_whether_to_save__file_saved_cb,
+ data);
+ else
+ ask_whether_to_save__done (data, response_id == GTK_RESPONSE_CANCEL);
+}
+
+
+static void
+_gth_browser_ask_whether_to_save (GthBrowser *browser,
+ GthBrowserCallback callback,
+ gpointer user_data)
+{
+ AskSaveData *data;
+ char *title;
+ GtkWidget *d;
+
+ data = g_new0 (AskSaveData, 1);
+ data->browser = browser;
+ data->callback = callback;
+ data->user_data = user_data;
+
+ title = g_strdup_printf (_("Save changes to file '%s'?"), g_file_info_get_display_name (browser->priv->current_file->info));
+ d = _gtk_message_dialog_new (GTK_WINDOW (browser),
+ GTK_DIALOG_MODAL,
+ GTK_STOCK_DIALOG_QUESTION,
+ title,
+ _("If you don't save, changes to the file will be permanently lost."),
+ _("Do _Not Save"), RESPONSE_NO_SAVE,
+ GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+ GTK_STOCK_SAVE, RESPONSE_SAVE,
+ NULL);
+ g_signal_connect (G_OBJECT (d),
+ "response",
+ G_CALLBACK (ask_whether_to_save__response_cb),
+ data);
+ gtk_widget_show (d);
+
+ g_free (title);
+}
+
+
+/* -- _gth_browser_close -- */
+
+
+static void
+_gth_browser_close_final_step (gpointer user_data)
+{
+ GthBrowser *browser = user_data;
+ gboolean last_window;
+ GdkWindowState state;
+ gboolean maximized;
+
+ browser_list = g_list_remove (browser_list, browser);
+ last_window = gth_window_get_n_windows () == 1;
+
+ /* Save visualization options only if the window is not maximized. */
+
+ state = gdk_window_get_state (GTK_WIDGET (browser)->window);
+ maximized = (state & GDK_WINDOW_STATE_MAXIMIZED) != 0;
+ if (! maximized && GTK_WIDGET_VISIBLE (browser)) {
+ int width, height;
+
+ gdk_drawable_get_size (GTK_WIDGET (browser)->window, &width, &height);
+ eel_gconf_set_integer (PREF_UI_WINDOW_WIDTH, width);
+ eel_gconf_set_integer (PREF_UI_WINDOW_HEIGHT, height);
+ }
+
+ eel_gconf_set_integer (PREF_UI_BROWSER_SIDEBAR_WIDTH, gtk_paned_get_position (GTK_PANED (browser->priv->browser_container)));
+ eel_gconf_set_integer (PREF_UI_VIEWER_SIDEBAR_WIDTH, gtk_paned_get_position (GTK_PANED (browser->priv->viewer_pane)));
+ eel_gconf_set_integer (PREF_UI_PROPERTIES_HEIGHT, gtk_paned_get_position (GTK_PANED (browser->priv->browser_sidebar)));
+
+ /**/
+
+ gth_hook_invoke ("gth-browser-close", browser);
+
+ if (last_window) {
+ if (eel_gconf_get_boolean (PREF_GO_TO_LAST_LOCATION, TRUE)
+ && (browser->priv->location != NULL))
+ {
+ char *uri;
+
+ uri = g_file_get_uri (browser->priv->location);
+ eel_gconf_set_path (PREF_STARTUP_LOCATION, uri);
+
+ g_free (uri);
+ }
+
+ eel_gconf_set_string (PREF_SORT_TYPE, browser->priv->sort_type->name);
+ eel_gconf_set_boolean (PREF_SORT_INVERSE, browser->priv->sort_inverse);
+
+ gth_hook_invoke ("gth-browser-close-last-window", browser);
+ }
+
+ if (browser->priv->folder_popup != NULL)
+ gtk_widget_destroy (browser->priv->folder_popup);
+ if (browser->priv->file_list_popup != NULL)
+ gtk_widget_destroy (browser->priv->file_list_popup);
+
+ gtk_widget_destroy (GTK_WIDGET (browser));
+}
+
+
+static void
+_gth_browser_close_step3 (gpointer user_data)
+{
+ GthBrowser *browser = user_data;
+
+ gth_file_list_cancel (GTH_FILE_LIST (browser->priv->file_list), _gth_browser_close_final_step, browser);
+}
+
+
+static void
+_gth_browser_real_close (GthBrowser *browser)
+{
+ int i;
+
+ /* remove gconf notifications */
+
+ for (i = 0; i < GCONF_NOTIFICATIONS; i++)
+ if (browser->priv->cnxn_id[i] != 0)
+ eel_gconf_notification_remove (browser->priv->cnxn_id[i]);
+
+ /* disconnect from the monitor */
+
+ g_signal_handler_disconnect (gth_main_get_default_monitor (),
+ browser->priv->bookmarks_changed_id);
+ g_signal_handler_disconnect (gth_main_get_default_monitor (),
+ browser->priv->folder_changed_id);
+ g_signal_handler_disconnect (gth_main_get_default_monitor (),
+ browser->priv->file_renamed_id);
+ g_signal_handler_disconnect (gth_main_get_default_monitor (),
+ browser->priv->metadata_changed_id);
+ g_signal_handler_disconnect (gth_main_get_default_monitor (),
+ browser->priv->entry_points_changed_id);
+
+ /* cancel async operations */
+
+ browser->priv->closing = TRUE;
+
+ _gth_browser_cancel (browser);
+
+ if ((browser->priv->task != NULL) && gth_task_is_running (browser->priv->task))
+ gth_task_cancel (browser->priv->task);
+ else
+ _gth_browser_close_step3 (browser);
+}
+
+
+static void
+close__file_saved_cb (GthBrowser *browser,
+ gboolean cancelled,
+ gpointer user_data)
+{
+ if (! cancelled)
+ _gth_browser_real_close (browser);
+}
+
+
+static void
+_gth_browser_close (GthWindow *window)
+{
+ GthBrowser *browser = (GthBrowser *) window;
+
+ if (gth_browser_get_file_modified (browser))
+ _gth_browser_ask_whether_to_save (browser,
+ close__file_saved_cb,
+ NULL);
+ else
+ _gth_browser_real_close (browser);
+}
+
+
+static void
+_gth_browser_update_viewer_ui (GthBrowser *browser,
+ int page)
+{
+ if (page == GTH_BROWSER_PAGE_VIEWER) {
+ GError *error = NULL;
+
+ if (browser->priv->viewer_ui_merge_id != 0)
+ return;
+ browser->priv->viewer_ui_merge_id = gtk_ui_manager_add_ui_from_string (browser->priv->ui, viewer_ui_info, -1, &error);
+ if (browser->priv->viewer_ui_merge_id == 0) {
+ g_warning ("ui building failed: %s", error->message);
+ g_clear_error (&error);
+ }
+ }
+ else if (browser->priv->viewer_ui_merge_id != 0) {
+ gtk_ui_manager_remove_ui (browser->priv->ui, browser->priv->viewer_ui_merge_id);
+ browser->priv->viewer_ui_merge_id = 0;
+ }
+
+ if (browser->priv->viewer_page != NULL) {
+ if (page == GTH_BROWSER_PAGE_VIEWER)
+ gth_viewer_page_show (browser->priv->viewer_page);
+ else
+ gth_viewer_page_hide (browser->priv->viewer_page);
+ }
+}
+
+
+static void
+_gth_browser_update_browser_ui (GthBrowser *browser,
+ int page)
+{
+ if (page == GTH_BROWSER_PAGE_BROWSER) {
+ GError *error = NULL;
+
+ if (browser->priv->browser_ui_merge_id != 0)
+ return;
+ browser->priv->browser_ui_merge_id = gtk_ui_manager_add_ui_from_string (browser->priv->ui, browser_ui_info, -1, &error);
+ if (browser->priv->browser_ui_merge_id == 0) {
+ g_warning ("ui building failed: %s", error->message);
+ g_clear_error (&error);
+ }
+ }
+ else if (browser->priv->browser_ui_merge_id != 0) {
+ gtk_ui_manager_remove_ui (browser->priv->ui, browser->priv->browser_ui_merge_id);
+ browser->priv->browser_ui_merge_id = 0;
+ }
+}
+
+
+static void
+_gth_browser_set_current_page (GthWindow *window,
+ int page)
+{
+ GthBrowser *browser = (GthBrowser *) window;
+
+ GTH_WINDOW_CLASS (parent_class)->set_current_page (window, page);
+
+ _gth_browser_update_viewer_ui (browser, page);
+ _gth_browser_update_browser_ui (browser, page);
+
+ gth_hook_invoke ("gth-browser-set-current-page", browser);
+
+ gth_browser_update_title (browser);
+}
+
+
+static void
+gth_browser_init (GthBrowser *browser)
+{
+ int i;
+
+ browser->priv = g_new0 (GthBrowserPrivateData, 1);
+ browser->priv->menu_icon_cache = gth_icon_cache_new_for_widget (GTK_WIDGET (browser), GTK_ICON_SIZE_MENU);
+ browser->priv->named_dialogs = g_hash_table_new (g_str_hash, g_str_equal);
+
+ for (i = 0; i < GCONF_NOTIFICATIONS; i++)
+ browser->priv->cnxn_id[i] = 0;
+}
+
+
+static void
+gth_browser_finalize (GObject *object)
+{
+ GthBrowser *browser = GTH_BROWSER (object);
+
+ if (browser->priv != NULL) {
+ _g_object_unref (browser->priv->location_source);
+ _g_object_unref (browser->priv->location);
+ _g_object_unref (browser->priv->current_file);
+ _g_object_unref (browser->priv->viewer_page);
+ _g_object_unref (browser->priv->image_preloader);
+ _g_object_list_unref (browser->priv->history);
+ gth_icon_cache_free (browser->priv->menu_icon_cache);
+ g_hash_table_unref (browser->priv->named_dialogs);
+ g_free (browser->priv);
+ browser->priv = NULL;
+ }
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+
+static void
+gth_browser_class_init (GthBrowserClass *class)
+{
+ GObjectClass *gobject_class;
+ GthWindowClass *window_class;
+
+ parent_class = g_type_class_peek_parent (class);
+
+ gobject_class = G_OBJECT_CLASS (class);
+ gobject_class->finalize = gth_browser_finalize;
+
+ window_class = GTH_WINDOW_CLASS (class);
+ window_class->close = _gth_browser_close;
+ window_class->set_current_page = _gth_browser_set_current_page;
+
+ /* signals */
+
+ gth_browser_signals[LOCATION_READY] =
+ g_signal_new ("location-ready",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GthBrowserClass, location_ready),
+ NULL, NULL,
+ gth_marshal_VOID__OBJECT_BOOLEAN,
+ G_TYPE_NONE,
+ 2,
+ G_TYPE_OBJECT,
+ G_TYPE_BOOLEAN);
+}
+
+
+GType
+gth_browser_get_type (void)
+{
+ static GType type = 0;
+
+ if (! type) {
+ GTypeInfo type_info = {
+ sizeof (GthBrowserClass),
+ NULL,
+ NULL,
+ (GClassInitFunc) gth_browser_class_init,
+ NULL,
+ NULL,
+ sizeof (GthBrowser),
+ 0,
+ (GInstanceInitFunc) gth_browser_init
+ };
+
+ type = g_type_register_static (GTH_TYPE_WINDOW,
+ "GthBrowser",
+ &type_info,
+ 0);
+ }
+
+ return type;
+}
+
+
+static void
+menu_item_select_cb (GtkMenuItem *proxy,
+ GthBrowser *browser)
+{
+ GtkAction *action;
+ char *message;
+
+ action = g_object_get_data (G_OBJECT (proxy), "gtk-action");
+ g_return_if_fail (action != NULL);
+
+ g_object_get (G_OBJECT (action), "tooltip", &message, NULL);
+ if (message != NULL) {
+ gtk_statusbar_push (GTK_STATUSBAR (browser->priv->statusbar),
+ browser->priv->help_message_cid,
+ message);
+ g_free (message);
+ }
+}
+
+
+static void
+menu_item_deselect_cb (GtkMenuItem *proxy,
+ GthBrowser *browser)
+{
+ gtk_statusbar_pop (GTK_STATUSBAR (browser->priv->statusbar),
+ browser->priv->help_message_cid);
+}
+
+
+static void
+disconnect_proxy_cb (GtkUIManager *manager,
+ GtkAction *action,
+ GtkWidget *proxy,
+ GthBrowser *browser)
+{
+ if (GTK_IS_MENU_ITEM (proxy)) {
+ g_signal_handlers_disconnect_by_func
+ (proxy, G_CALLBACK (menu_item_select_cb), browser);
+ g_signal_handlers_disconnect_by_func
+ (proxy, G_CALLBACK (menu_item_deselect_cb), browser);
+ }
+}
+
+
+static void
+connect_proxy_cb (GtkUIManager *manager,
+ GtkAction *action,
+ GtkWidget *proxy,
+ GthBrowser *browser)
+{
+ if (GTK_IS_MENU_ITEM (proxy)) {
+ g_signal_connect (proxy, "select",
+ G_CALLBACK (menu_item_select_cb), browser);
+ g_signal_connect (proxy, "deselect",
+ G_CALLBACK (menu_item_deselect_cb), browser);
+ }
+}
+
+
+static void
+folder_tree_open_cb (GthFolderTree *folder_tree,
+ GFile *file,
+ GthBrowser *browser)
+{
+ gth_browser_go_to (browser, file);
+}
+
+
+static void
+folder_tree_open_parent_cb (GthFolderTree *folder_tree,
+ GthBrowser *browser)
+{
+ gth_browser_go_up (browser, 1);
+}
+
+
+static void
+folder_tree_list_children_cb (GthFolderTree *folder_tree,
+ GFile *file,
+ GthBrowser *browser)
+{
+ _gth_browser_load (browser, file, GTH_ACTION_LIST_CHILDREN);
+}
+
+
+static void
+folder_tree_load_cb (GthFolderTree *folder_tree,
+ GFile *file,
+ GthBrowser *browser)
+{
+ _gth_browser_load (browser, file, GTH_ACTION_VIEW);
+}
+
+
+static void
+folder_tree_folder_popup_cb (GthFolderTree *folder_tree,
+ GFile *file,
+ guint time,
+ gpointer user_data)
+{
+ GthBrowser *browser = user_data;
+ gboolean sensitive;
+ GthFileSource *file_source;
+
+ sensitive = (file != NULL);
+ _gth_browser_set_action_sensitive (browser, "Folder_Open", sensitive);
+ _gth_browser_set_action_sensitive (browser, "Folder_OpenInNewWindow", sensitive);
+
+ if (file != NULL)
+ file_source = gth_main_get_file_source (file);
+ else
+ file_source = NULL;
+ gth_hook_invoke ("gth-browser-folder-tree-popup-before", browser, file_source, file);
+ gtk_ui_manager_ensure_update (browser->priv->ui);
+
+ gtk_menu_popup (GTK_MENU (browser->priv->folder_popup),
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ 3,
+ (guint32) time);
+
+ _g_object_unref (file_source);
+}
+
+
+static void
+file_source_rename_ready_cb (GObject *object,
+ GError *error,
+ gpointer user_data)
+{
+ GthBrowser *browser = user_data;
+
+ g_object_unref (object);
+
+ if (error != NULL) {
+ _gtk_error_dialog_from_gerror_show (GTK_WINDOW (browser), _("Could not change name"), &error);
+ return;
+ }
+}
+
+
+static void
+folder_tree_rename_cb (GthFolderTree *folder_tree,
+ GFile *file,
+ const char *new_name,
+ GthBrowser *browser)
+{
+ GFile *parent;
+ char *uri;
+ char *new_basename;
+ GFile *new_file;
+ GError *error = NULL;
+
+ parent = g_file_get_parent (file);
+ uri = g_file_get_uri (file);
+ new_basename = g_strconcat (new_name, _g_uri_get_file_extension (uri), NULL);
+ new_file = g_file_get_child_for_display_name (parent, new_basename, &error);
+
+ if (new_file == NULL)
+ _gtk_error_dialog_from_gerror_show (GTK_WINDOW (browser), _("Could not change name"), &error);
+ else {
+ GthFileSource *file_source;
+
+ file_source = gth_main_get_file_source (file);
+ gth_file_source_rename (file_source, file, new_file, file_source_rename_ready_cb, browser);
+ }
+
+ g_object_unref (new_file);
+ g_free (new_basename);
+ g_free (uri);
+ g_object_unref (parent);
+}
+
+
+static void
+location_changed_cb (GthLocationChooser *chooser,
+ GthBrowser *browser)
+{
+ gth_browser_go_to (browser, gth_location_chooser_get_current (chooser));
+}
+
+
+static void
+filterbar_changed_cb (GthFilterbar *filterbar,
+ GthBrowser *browser)
+{
+ GthTest *filter;
+
+ filter = _gth_browser_get_file_filter (browser);
+ gth_file_list_set_filter (GTH_FILE_LIST (browser->priv->file_list), filter);
+ g_object_unref (filter);
+
+ _gth_browser_update_statusbar_list_info (browser);
+ gth_browser_update_sensitivity (browser);
+}
+
+
+static void
+filterbar_personalize_cb (GthFilterbar *filterbar,
+ GthBrowser *browser)
+{
+ dlg_personalize_filters (browser);
+}
+
+
+static void
+bookmarks_changed_cb (GthMonitor *monitor,
+ GthBrowser *browser)
+{
+ _gth_browser_update_bookmark_list (browser);
+}
+
+
+static void
+file_attributes_ready_cb (GthFileSource *file_source,
+ GList *files,
+ GError *error,
+ gpointer user_data)
+{
+ MonitorEventData *monitor_data = user_data;
+ GthBrowser *browser = monitor_data->browser;
+
+ if (error != NULL) {
+ monitor_event_data_unref (monitor_data);
+ g_clear_error (&error);
+ return;
+ }
+
+ if (monitor_data->event == GTH_MONITOR_EVENT_CREATED) {
+ if (monitor_data->update_folder_tree)
+ gth_folder_tree_add_children (GTH_FOLDER_TREE (browser->priv->folder_tree), monitor_data->parent, files);
+ if (monitor_data->update_file_list) {
+ gth_file_list_add_files (GTH_FILE_LIST (browser->priv->file_list), files);
+ gth_file_list_update_files (GTH_FILE_LIST (browser->priv->file_list), files);
+ }
+ }
+ else if (monitor_data->event == GTH_MONITOR_EVENT_CHANGED) {
+ if (monitor_data->update_folder_tree)
+ gth_folder_tree_update_children (GTH_FOLDER_TREE (browser->priv->folder_tree), monitor_data->parent, files);
+ if (monitor_data->update_file_list)
+ gth_file_list_update_files (GTH_FILE_LIST (browser->priv->file_list), files);
+ }
+
+ if (browser->priv->current_file != NULL) {
+ GList *link;
+
+ link = gth_file_data_list_find_file (files, browser->priv->current_file->file);
+ if (link != NULL) {
+ GthFileData *file_data = link->data;
+ gth_browser_load_file (browser, file_data, FALSE);
+ }
+ }
+
+ _gth_browser_update_statusbar_list_info (browser);
+ gth_browser_update_sensitivity (browser);
+
+ monitor_event_data_unref (monitor_data);
+}
+
+
+static void
+folder_changed_cb (GthMonitor *monitor,
+ GFile *parent,
+ GList *list,
+ GthMonitorEvent event,
+ GthBrowser *browser)
+{
+ GtkTreePath *path;
+ gboolean update_folder_tree;
+ gboolean update_file_list;
+
+ if ((event == GTH_MONITOR_EVENT_DELETED) && (_g_file_list_find_file (list, browser->priv->location) != NULL)) {
+ gth_browser_go_to (browser, parent);
+ return;
+ }
+
+#if 0
+{
+ GList *scan;
+ g_print ("folder changed: %s [%s]\n", g_file_get_uri (parent), _g_enum_type_get_value (GTH_TYPE_MONITOR_EVENT, event)->value_nick);
+ for (scan = list; scan; scan = scan->next)
+ g_print (" %s\n", g_file_get_uri (scan->data));
+}
+#endif
+
+ path = gth_folder_tree_get_path (GTH_FOLDER_TREE (browser->priv->folder_tree), parent);
+ update_folder_tree = (g_file_equal (parent, gth_folder_tree_get_root (GTH_FOLDER_TREE (browser->priv->folder_tree)))
+ || ((path != NULL) && gtk_tree_view_row_expanded (GTK_TREE_VIEW (browser->priv->folder_tree), path)));
+
+ update_file_list = g_file_equal (parent, browser->priv->location);
+ if (! update_file_list && (event == GTH_MONITOR_EVENT_CHANGED)) {
+ GthFileStore *file_store;
+ GList *scan;
+
+ file_store = (GthFileStore *) gth_file_view_get_model (GTH_FILE_VIEW (gth_file_list_get_view (GTH_FILE_LIST (browser->priv->file_list))));
+ for (scan = list; scan; scan = scan->next) {
+ if (gth_file_store_find (file_store, (GFile *) scan->data) >= 0) {
+ update_file_list = TRUE;
+ break;
+ }
+ }
+ }
+
+ if (update_folder_tree || update_file_list) {
+ MonitorEventData *monitor_data;
+ gboolean current_file_deleted = FALSE;
+ GthFileData *new_file = NULL;
+
+ switch (event) {
+ case GTH_MONITOR_EVENT_CREATED:
+ case GTH_MONITOR_EVENT_CHANGED:
+ monitor_data = monitor_event_data_new ();
+ monitor_data->file_source = gth_main_get_file_source (parent);
+ monitor_data->parent = g_file_dup (parent);
+ monitor_data->event = event;
+ monitor_data->browser = browser;
+ monitor_data->update_file_list = update_file_list;
+ monitor_data->update_folder_tree = update_folder_tree;
+ gth_file_source_read_attributes (monitor_data->file_source,
+ list,
+ eel_gconf_get_boolean (PREF_FAST_FILE_TYPE, TRUE) ? GTH_FILE_DATA_ATTRIBUTES_WITH_FAST_CONTENT_TYPE : GTH_FILE_DATA_ATTRIBUTES_WITH_CONTENT_TYPE,
+ file_attributes_ready_cb,
+ monitor_data);
+ break;
+
+ case GTH_MONITOR_EVENT_DELETED:
+ if (browser->priv->current_file != NULL) {
+ GList *link;
+
+ link = _g_file_list_find_file (list, browser->priv->current_file->file);
+ if (link != NULL) {
+ GthFileStore *file_store;
+ int pos;
+
+ current_file_deleted = TRUE;
+ file_store = gth_browser_get_file_store (browser);
+ pos = gth_file_store_find_visible (file_store, browser->priv->current_file->file);
+ new_file = gth_file_store_get_file_at_pos (file_store, pos - 1);
+ if (new_file == NULL)
+ new_file = gth_file_store_get_file_at_pos (file_store, pos + 1);
+ if (new_file != NULL)
+ new_file = g_object_ref (new_file);
+ }
+ }
+
+ if (update_folder_tree)
+ gth_folder_tree_delete_children (GTH_FOLDER_TREE (browser->priv->folder_tree), parent, list);
+
+ if (update_file_list) {
+ if (current_file_deleted)
+ g_signal_handlers_block_by_data (gth_browser_get_file_list_view (browser), browser);
+ gth_file_list_delete_files (GTH_FILE_LIST (browser->priv->file_list), list);
+ if (current_file_deleted)
+ g_signal_handlers_unblock_by_data (gth_browser_get_file_list_view (browser), browser);
+ }
+
+ if (current_file_deleted) {
+ gth_browser_load_file (browser, new_file, FALSE);
+ _g_object_unref (new_file);
+ }
+
+ _gth_browser_update_statusbar_list_info (browser);
+ gth_browser_update_sensitivity (browser);
+ break;
+ }
+ }
+
+ gtk_tree_path_free (path);
+}
+
+
+static void
+file_renamed_cb (GthMonitor *monitor,
+ GFile *file,
+ GFile *new_file,
+ GthBrowser *browser)
+{
+ GthFileSource *file_source;
+ GFileInfo *info;
+ GthFileData *file_data;
+ GList *list;
+
+ file_source = gth_main_get_file_source (new_file);
+ info = gth_file_source_get_file_info (file_source, new_file);
+ file_data = gth_file_data_new (new_file, info);
+ gth_folder_tree_update_child (GTH_FOLDER_TREE (browser->priv->folder_tree), file, file_data);
+
+ list = g_list_prepend (NULL, file_data);
+ gth_file_list_update_files (GTH_FILE_LIST (browser->priv->file_list), list);
+
+ if (g_file_equal (file, browser->priv->location))
+ _gth_browser_set_location (browser, new_file);
+
+ g_list_free (list);
+ g_object_unref (file_data);
+ g_object_unref (info);
+ g_object_unref (file_source);
+}
+
+
+static void
+_gth_browser_update_statusbar_file_info (GthBrowser *browser)
+{
+ const char *image_size;
+ const char *file_date;
+ const char *file_size;
+ GthMetadata *metadata;
+ char *text;
+
+ if (browser->priv->current_file == NULL) {
+ gth_statusbar_set_primary_text (GTH_STATUSBAR (browser->priv->statusbar), "");
+ gth_statusbar_set_secondary_text (GTH_STATUSBAR (browser->priv->statusbar), "");
+ return;
+ }
+
+ image_size = g_file_info_get_attribute_string (browser->priv->current_file->info, "image::size");
+ metadata = (GthMetadata *) g_file_info_get_attribute_object (browser->priv->current_file->info, "Embedded::Image::DateTime");
+ if (metadata != NULL)
+ file_date = gth_metadata_get_formatted (metadata);
+ else
+ file_date = g_file_info_get_attribute_string (browser->priv->current_file->info, "file::display-mtime");
+ file_size = g_file_info_get_attribute_string (browser->priv->current_file->info, "file::display-size");
+
+ if (gth_browser_get_file_modified (browser))
+ text = g_strdup_printf ("%s - %s", _("Modified"), image_size);
+ else if (image_size != NULL)
+ text = g_strdup_printf ("%s - %s - %s", image_size, file_size, file_date);
+ else
+ text = g_strdup_printf ("%s - %s", file_size, file_date);
+
+ gth_statusbar_set_primary_text (GTH_STATUSBAR (browser->priv->statusbar), text);
+
+ g_free (text);
+}
+
+
+static void
+metadata_changed_cb (GthMonitor *monitor,
+ GthFileData *file_data,
+ GthBrowser *browser)
+{
+ if (browser->priv->current_file == NULL)
+ return;
+
+ if (! g_file_equal (browser->priv->current_file->file, file_data->file))
+ return;
+
+ if (file_data->info != browser->priv->current_file->info)
+ g_file_info_copy_into (file_data->info, browser->priv->current_file->info);
+
+ gth_sidebar_set_file (GTH_SIDEBAR (browser->priv->file_properties), browser->priv->current_file);
+ gth_sidebar_set_file (GTH_SIDEBAR (browser->priv->viewer_sidebar), browser->priv->current_file);
+
+ _gth_browser_update_statusbar_file_info (browser);
+ gth_browser_update_title (browser);
+ gth_browser_update_sensitivity (browser);
+
+}
+
+
+static void
+entry_points_changed_cb (GthMonitor *monitor,
+ GthBrowser *browser)
+{
+ _gth_browser_update_entry_point_list (browser);
+}
+
+
+static void
+pref_general_filter_changed (GConfClient *client,
+ guint cnxn_id,
+ GConfEntry *entry,
+ gpointer user_data)
+{
+ GthBrowser *browser = user_data;
+ GthTest *filter;
+
+ filter = _gth_browser_get_file_filter (browser);
+ gth_file_list_set_filter (GTH_FILE_LIST (browser->priv->file_list), filter);
+ g_object_unref (filter);
+}
+
+
+static gboolean
+gth_file_list_button_press_cb (GtkWidget *widget,
+ GdkEventButton *event,
+ gpointer user_data)
+{
+ GthBrowser *browser = user_data;
+
+ if ((event->type == GDK_BUTTON_PRESS) && (event->button == 3)) {
+ gtk_menu_popup (GTK_MENU (browser->priv->file_list_popup),
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ event->button,
+ event->time);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+
+static void
+gth_file_view_selection_changed_cb (GtkIconView *iconview,
+ gpointer user_data)
+{
+ GthBrowser *browser = user_data;
+ int n_selected;
+
+ gth_browser_update_sensitivity (browser);
+ _gth_browser_update_statusbar_list_info (browser);
+
+ n_selected = gth_file_selection_get_n_selected (GTH_FILE_SELECTION (gth_browser_get_file_list_view (browser)));
+ if (n_selected == 1)
+ gtk_widget_show (browser->priv->file_properties);
+ else
+ gth_browser_load_file (browser, NULL, FALSE);
+
+ if (gth_window_get_current_page (GTH_WINDOW (browser)) != GTH_BROWSER_PAGE_BROWSER)
+ return;
+
+ if (n_selected == 1) {
+ GList *items;
+ GList *file_list = NULL;
+
+ items = gth_file_selection_get_selected (GTH_FILE_SELECTION (gth_browser_get_file_list_view (browser)));
+ file_list = gth_file_list_get_files (GTH_FILE_LIST (gth_browser_get_file_list (browser)), items);
+ gth_browser_load_file (browser, (GthFileData *) file_list->data, FALSE);
+
+ _g_object_list_unref (file_list);
+ _gtk_tree_path_list_free (items);
+ }
+}
+
+
+static void
+gth_file_view_item_activated_cb (GtkIconView *iconview,
+ GtkTreePath *path,
+ gpointer user_data)
+{
+ GthBrowser *browser = user_data;
+ GList *list;
+ GList *files;
+
+ list = g_list_prepend (NULL, path);
+ files = gth_file_list_get_files (GTH_FILE_LIST (browser->priv->file_list), list);
+
+ if (files != NULL)
+ gth_browser_load_file (browser, (GthFileData *) files->data, TRUE);
+
+ _g_object_list_unref (files);
+ g_list_free (list);
+}
+
+
+static void
+add_browser_toolbar_menu_buttons (GthBrowser *browser)
+{
+ int tool_pos;
+ GtkToolItem *tool_item;
+ GtkAction *action;
+
+ tool_pos = 0;
+
+ /* toolbar back button */
+
+ tool_item = gtk_menu_tool_button_new_from_stock (GTK_STOCK_GO_BACK);
+ gtk_menu_tool_button_set_menu (GTK_MENU_TOOL_BUTTON (tool_item),
+ gtk_ui_manager_get_widget (browser->priv->ui, GO_BACK_HISTORY_POPUP));
+ gtk_tool_item_set_homogeneous (tool_item, TRUE);
+ gtk_widget_set_tooltip_text (GTK_WIDGET (tool_item), _("Go to the previous visited location"));
+ gtk_menu_tool_button_set_arrow_tooltip_text (GTK_MENU_TOOL_BUTTON (tool_item), _("View the list of visited locations"));
+
+ action = gtk_action_new ("Toolbar_Go_Back", NULL, NULL, GTK_STOCK_GO_BACK);
+ g_object_set (action, "is_important", TRUE, NULL);
+ g_signal_connect (action,
+ "activate",
+ G_CALLBACK (gth_browser_activate_action_go_back),
+ browser);
+ gtk_action_connect_proxy (action, GTK_WIDGET (tool_item));
+ gtk_action_group_add_action (browser->priv->actions, action);
+
+ gtk_widget_show (GTK_WIDGET (tool_item));
+ gtk_toolbar_insert (GTK_TOOLBAR (browser->priv->browser_toolbar), tool_item, tool_pos++);
+
+ /* toolbar forward button */
+
+ tool_item = gtk_menu_tool_button_new_from_stock (GTK_STOCK_GO_FORWARD);
+ gtk_menu_tool_button_set_menu (GTK_MENU_TOOL_BUTTON (tool_item),
+ gtk_ui_manager_get_widget (browser->priv->ui, GO_FORWARD_HISTORY_POPUP));
+ gtk_tool_item_set_homogeneous (tool_item, TRUE);
+ gtk_widget_set_tooltip_text (GTK_WIDGET (tool_item), _("Go to the next visited location"));
+ gtk_menu_tool_button_set_arrow_tooltip_text (GTK_MENU_TOOL_BUTTON (tool_item), _("View the list of visited locations"));
+
+ action = gtk_action_new ("Toolbar_Go_Forward", NULL, NULL, GTK_STOCK_GO_FORWARD);
+ g_object_set (action, "is_important", TRUE, NULL);
+ g_signal_connect (action,
+ "activate",
+ G_CALLBACK (gth_browser_activate_action_go_forward),
+ browser);
+ gtk_action_connect_proxy (action, GTK_WIDGET (tool_item));
+ gtk_action_group_add_action (browser->priv->actions, action);
+
+ gtk_widget_show (GTK_WIDGET (tool_item));
+ gtk_toolbar_insert (GTK_TOOLBAR (browser->priv->browser_toolbar), tool_item, tool_pos++);
+
+ /* toolbar up button */
+
+ tool_item = gtk_menu_tool_button_new_from_stock (GTK_STOCK_GO_UP);
+ gtk_menu_tool_button_set_menu (GTK_MENU_TOOL_BUTTON (tool_item),
+ gtk_ui_manager_get_widget (browser->priv->ui, GO_PARENT_POPUP));
+ gtk_tool_item_set_homogeneous (tool_item, TRUE);
+ gtk_widget_set_tooltip_text (GTK_WIDGET (tool_item), _("Go up one level"));
+ gtk_menu_tool_button_set_arrow_tooltip_text (GTK_MENU_TOOL_BUTTON (tool_item), _("View the list of upper locations"));
+
+ action = gtk_action_new ("Toolbar_Go_Up", NULL, NULL, GTK_STOCK_GO_UP);
+ g_object_set (action, "is_important", FALSE, NULL);
+ g_signal_connect (action,
+ "activate",
+ G_CALLBACK (gth_browser_activate_action_go_up),
+ browser);
+ gtk_action_connect_proxy (action, GTK_WIDGET (tool_item));
+ gtk_action_group_add_action (browser->priv->actions, action);
+
+ gtk_widget_show (GTK_WIDGET (tool_item));
+ gtk_toolbar_insert (GTK_TOOLBAR (browser->priv->browser_toolbar), tool_item, tool_pos++);
+}
+
+
+static void
+_gth_browser_construct_step2 (gpointer data)
+{
+ GthBrowser *browser = data;
+
+ _gth_browser_update_bookmark_list (browser);
+ _gth_browser_monitor_entry_points (browser);
+
+ /* force an update to load the correct icons */
+ gth_monitor_file_entry_points_changed (gth_main_get_default_monitor ());
+}
+
+
+static void
+_gth_browser_update_toolbar_style (GthBrowser *browser)
+{
+ GthToolbarStyle toolbar_style;
+ GtkToolbarStyle prop = GTK_TOOLBAR_BOTH;
+
+ toolbar_style = gth_pref_get_real_toolbar_style ();
+
+ switch (toolbar_style) {
+ case GTH_TOOLBAR_STYLE_TEXT_BELOW:
+ prop = GTK_TOOLBAR_BOTH;
+ break;
+ case GTH_TOOLBAR_STYLE_TEXT_BESIDE:
+ prop = GTK_TOOLBAR_BOTH_HORIZ;
+ break;
+ case GTH_TOOLBAR_STYLE_ICONS:
+ prop = GTK_TOOLBAR_ICONS;
+ break;
+ case GTH_TOOLBAR_STYLE_TEXT:
+ prop = GTK_TOOLBAR_TEXT;
+ break;
+ default:
+ break;
+ }
+
+ gtk_toolbar_set_style (GTK_TOOLBAR (browser->priv->browser_toolbar), prop);
+ gtk_toolbar_set_style (GTK_TOOLBAR (browser->priv->viewer_toolbar), prop);
+}
+
+
+static void
+pref_ui_toolbar_style_changed (GConfClient *client,
+ guint cnxn_id,
+ GConfEntry *entry,
+ gpointer user_data)
+{
+ GthBrowser *browser = user_data;
+ _gth_browser_update_toolbar_style (browser);
+}
+
+
+static void
+_gth_browser_set_toolbar_visibility (GthBrowser *browser,
+ gboolean visible)
+{
+ g_return_if_fail (browser != NULL);
+
+ _gth_browser_set_action_active (browser, "View_Toolbar", visible);
+ if (visible) {
+ gtk_widget_show (browser->priv->browser_toolbar);
+ gtk_widget_show (browser->priv->viewer_toolbar);
+ }
+ else {
+ gtk_widget_hide (browser->priv->browser_toolbar);
+ gtk_widget_hide (browser->priv->viewer_toolbar);
+ }
+}
+
+
+static void
+pref_ui_toolbar_visible_changed (GConfClient *client,
+ guint cnxn_id,
+ GConfEntry *entry,
+ gpointer user_data)
+{
+ GthBrowser *browser = user_data;
+ _gth_browser_set_toolbar_visibility (browser, gconf_value_get_bool (gconf_entry_get_value (entry)));
+}
+
+
+static void
+_gth_browser_set_statusbar_visibility (GthBrowser *browser,
+ gboolean visible)
+{
+ g_return_if_fail (browser != NULL);
+
+ _gth_browser_set_action_active (browser, "View_Statusbar", visible);
+ if (visible)
+ gtk_widget_show (browser->priv->statusbar);
+ else
+ gtk_widget_hide (browser->priv->statusbar);
+}
+
+
+static void
+pref_ui_statusbar_visible_changed (GConfClient *client,
+ guint cnxn_id,
+ GConfEntry *entry,
+ gpointer user_data)
+{
+ GthBrowser *browser = user_data;
+ _gth_browser_set_statusbar_visibility (browser, gconf_value_get_bool (gconf_entry_get_value (entry)));
+}
+
+
+static void
+pref_show_hidden_files_changed (GConfClient *client,
+ guint cnxn_id,
+ GConfEntry *entry,
+ gpointer user_data)
+{
+ GthBrowser *browser = user_data;
+ gboolean show_hidden_files;
+
+ show_hidden_files = eel_gconf_get_boolean (PREF_SHOW_HIDDEN_FILES, FALSE);
+ if (show_hidden_files == browser->priv->show_hidden_files)
+ return;
+
+ _gth_browser_set_action_active (browser, "View_ShowHiddenFiles", show_hidden_files);
+ browser->priv->show_hidden_files = show_hidden_files;
+ gth_folder_tree_reset_loaded (GTH_FOLDER_TREE (browser->priv->folder_tree));
+ gth_browser_reload (browser);
+}
+
+
+static void
+pref_fast_file_type_changed (GConfClient *client,
+ guint cnxn_id,
+ GConfEntry *entry,
+ gpointer user_data)
+{
+ GthBrowser *browser = user_data;
+
+ browser->priv->fast_file_type = eel_gconf_get_boolean (PREF_FAST_FILE_TYPE, TRUE);
+ gth_browser_reload (browser);
+}
+
+
+static void
+pref_thumbnail_size_changed (GConfClient *client,
+ guint cnxn_id,
+ GConfEntry *entry,
+ gpointer user_data)
+{
+ GthBrowser *browser = user_data;
+
+ gth_file_list_set_thumb_size (GTH_FILE_LIST (browser->priv->file_list), eel_gconf_get_integer (PREF_THUMBNAIL_SIZE, DEF_THUMBNAIL_SIZE));
+ gth_browser_reload (browser);
+}
+
+
+static void
+_gth_browser_construct (GthBrowser *browser)
+{
+ GError *error = NULL;
+ GtkWidget *vbox;
+ GtkWidget *scrolled_window;
+ GtkWidget *menubar;
+ char *general_filter;
+ int i;
+
+ gtk_window_set_default_size (GTK_WINDOW (browser),
+ eel_gconf_get_integer (PREF_UI_WINDOW_WIDTH, DEFAULT_UI_WINDOW_WIDTH),
+ eel_gconf_get_integer (PREF_UI_WINDOW_HEIGHT, DEFAULT_UI_WINDOW_HEIGHT));
+
+ /* ui actions */
+
+ browser->priv->actions = gtk_action_group_new ("Actions");
+ gtk_action_group_set_translation_domain (browser->priv->actions, NULL);
+ gtk_action_group_add_actions (browser->priv->actions,
+ gth_window_action_entries,
+ gth_window_action_entries_size,
+ browser);
+ gtk_action_group_add_actions (browser->priv->actions,
+ gth_browser_action_entries,
+ gth_browser_action_entries_size,
+ browser);
+ gtk_action_group_add_toggle_actions (browser->priv->actions,
+ gth_browser_action_toggle_entries,
+ gth_browser_action_toggle_entries_size,
+ browser);
+
+ browser->priv->ui = gtk_ui_manager_new ();
+ g_signal_connect (browser->priv->ui,
+ "connect_proxy",
+ G_CALLBACK (connect_proxy_cb),
+ browser);
+ g_signal_connect (browser->priv->ui,
+ "disconnect_proxy",
+ G_CALLBACK (disconnect_proxy_cb),
+ browser);
+
+ gtk_ui_manager_insert_action_group (browser->priv->ui, browser->priv->actions, 0);
+ gtk_window_add_accel_group (GTK_WINDOW (browser), gtk_ui_manager_get_accel_group (browser->priv->ui));
+
+ if (! gtk_ui_manager_add_ui_from_string (browser->priv->ui, fixed_ui_info, -1, &error)) {
+ g_message ("building menus failed: %s", error->message);
+ g_error_free (error);
+ }
+
+ /* -- image page -- */
+
+ /* toolbar */
+
+ browser->priv->viewer_toolbar = gtk_ui_manager_get_widget (browser->priv->ui, "/ViewerToolBar");
+ gtk_toolbar_set_show_arrow (GTK_TOOLBAR (browser->priv->viewer_toolbar), TRUE);
+ gtk_widget_show (browser->priv->viewer_toolbar);
+ gth_window_attach_toolbar (GTH_WINDOW (browser), GTH_BROWSER_PAGE_VIEWER, browser->priv->viewer_toolbar);
+
+ /* content */
+
+ browser->priv->viewer_pane = gtk_hpaned_new ();
+ gtk_paned_set_position (GTK_PANED (browser->priv->viewer_pane), eel_gconf_get_integer (PREF_UI_VIEWER_SIDEBAR_WIDTH, DEFAULT_UI_WINDOW_WIDTH - DEF_SIDEBAR_WIDTH));
+ gtk_widget_show (browser->priv->viewer_pane);
+ gth_window_attach_content (GTH_WINDOW (browser), GTH_BROWSER_PAGE_VIEWER, browser->priv->viewer_pane);
+
+ browser->priv->viewer_container = gtk_alignment_new (0.0, 0.0, 1.0, 1.0);
+ gtk_widget_show (browser->priv->viewer_container);
+ gtk_paned_pack1 (GTK_PANED (browser->priv->viewer_pane), browser->priv->viewer_container, TRUE, TRUE);
+
+ browser->priv->viewer_sidebar = gth_sidebar_new ();
+ gtk_paned_pack2 (GTK_PANED (browser->priv->viewer_pane), browser->priv->viewer_sidebar, FALSE, TRUE);
+
+ /* -- browser page -- */
+
+ /* menus */
+
+ menubar = gtk_ui_manager_get_widget (browser->priv->ui, "/MenuBar");
+#ifdef USE_MACOSMENU
+ {
+ GtkWidget *widget;
+
+ ige_mac_menu_install_key_handler ();
+ ige_mac_menu_set_menu_bar (GTK_MENU_SHELL (menubar));
+ gtk_widget_hide (menubar);
+ widget = gtk_ui_manager_get_widget(ui, "/MenuBar/File/Close");
+ if (widget != NULL) {
+ ige_mac_menu_set_quit_menu_item (GTK_MENU_ITEM (widget));
+ }
+ widget = gtk_ui_manager_get_widget(ui, "/MenuBar/Help/About");
+ if (widget != NULL) {
+ ige_mac_menu_add_app_menu_item (ige_mac_menu_add_app_menu_group (),
+ GTK_MENU_ITEM (widget),
+ NULL);
+ }
+ widget = gtk_ui_manager_get_widget(ui, "/MenuBar/Edit/Preferences");
+ if (widget != NULL) {
+ ige_mac_menu_add_app_menu_item (ige_mac_menu_add_app_menu_group (),
+ GTK_MENU_ITEM (widget),
+ NULL);
+ }
+ }
+#endif
+ gth_window_attach (GTH_WINDOW (browser), menubar, GTH_WINDOW_MENUBAR);
+ browser->priv->folder_popup = gtk_ui_manager_get_widget (browser->priv->ui, "/FolderListPopup");
+
+ /* toolbar */
+
+ browser->priv->browser_toolbar = gtk_ui_manager_get_widget (browser->priv->ui, "/ToolBar");
+ gtk_toolbar_set_show_arrow (GTK_TOOLBAR (browser->priv->browser_toolbar), TRUE);
+ gth_window_attach_toolbar (GTH_WINDOW (browser), GTH_BROWSER_PAGE_BROWSER, browser->priv->browser_toolbar);
+ add_browser_toolbar_menu_buttons (browser);
+
+ /* statusbar */
+
+ browser->priv->statusbar = gth_statusbar_new ();
+ gtk_statusbar_set_has_resize_grip (GTK_STATUSBAR (browser->priv->statusbar), TRUE);
+ browser->priv->help_message_cid = gtk_statusbar_get_context_id (GTK_STATUSBAR (browser->priv->statusbar), "gth_help_message");
+ _gth_browser_set_statusbar_visibility (browser, eel_gconf_get_boolean (PREF_UI_STATUSBAR_VISIBLE, TRUE));
+ gth_window_attach (GTH_WINDOW (browser), browser->priv->statusbar, GTH_WINDOW_STATUSBAR);
+
+ /* main content */
+
+ browser->priv->browser_container = gtk_hpaned_new ();
+ gtk_paned_set_position (GTK_PANED (browser->priv->browser_container), eel_gconf_get_integer (PREF_UI_BROWSER_SIDEBAR_WIDTH, DEF_SIDEBAR_WIDTH));
+ gtk_widget_show (browser->priv->browser_container);
+ gth_window_attach_content (GTH_WINDOW (browser), GTH_BROWSER_PAGE_BROWSER, browser->priv->browser_container);
+
+ /* the browser sidebar */
+
+ browser->priv->browser_sidebar = gtk_vpaned_new ();
+ gtk_paned_set_position (GTK_PANED (browser->priv->browser_sidebar), eel_gconf_get_integer (PREF_UI_PROPERTIES_HEIGHT, DEF_PROPERTIES_HEIGHT));
+ gtk_widget_show (browser->priv->browser_sidebar);
+ gtk_paned_pack1 (GTK_PANED (browser->priv->browser_container), browser->priv->browser_sidebar, FALSE, TRUE);
+
+ /* the box that contains the location and the folder list. */
+
+ vbox = gtk_vbox_new (FALSE, 6);
+ gtk_widget_show (vbox);
+ gtk_paned_pack1 (GTK_PANED (browser->priv->browser_sidebar), vbox, TRUE, TRUE);
+
+ /* the location combobox */
+
+ browser->priv->location_chooser = gth_location_chooser_new ();
+ gtk_widget_show (browser->priv->location_chooser);
+ gtk_box_pack_start (GTK_BOX (vbox), browser->priv->location_chooser, FALSE, FALSE, 0);
+
+ g_signal_connect (browser->priv->location_chooser,
+ "changed",
+ G_CALLBACK (location_changed_cb),
+ browser);
+
+ /* the folder list */
+
+ scrolled_window = gtk_scrolled_window_new (NULL, NULL);
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window),
+ GTK_POLICY_AUTOMATIC,
+ GTK_POLICY_AUTOMATIC);
+ gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled_window),
+ GTK_SHADOW_ETCHED_IN);
+ gtk_widget_show (scrolled_window);
+
+ browser->priv->folder_tree = gth_folder_tree_new ("gthumb-vfs:///");
+ gtk_widget_show (browser->priv->folder_tree);
+
+ gtk_container_add (GTK_CONTAINER (scrolled_window), browser->priv->folder_tree);
+ gtk_box_pack_start (GTK_BOX (vbox), scrolled_window, TRUE, TRUE, 0);
+
+ g_signal_connect (browser->priv->folder_tree,
+ "open",
+ G_CALLBACK (folder_tree_open_cb),
+ browser);
+ g_signal_connect (browser->priv->folder_tree,
+ "open_parent",
+ G_CALLBACK (folder_tree_open_parent_cb),
+ browser);
+ g_signal_connect (browser->priv->folder_tree,
+ "list_children",
+ G_CALLBACK (folder_tree_list_children_cb),
+ browser);
+ g_signal_connect (browser->priv->folder_tree,
+ "load",
+ G_CALLBACK (folder_tree_load_cb),
+ browser);
+ g_signal_connect (browser->priv->folder_tree,
+ "folder_popup",
+ G_CALLBACK (folder_tree_folder_popup_cb),
+ browser);
+ g_signal_connect (browser->priv->folder_tree,
+ "rename",
+ G_CALLBACK (folder_tree_rename_cb),
+ browser);
+
+ /* the file property box */
+
+ browser->priv->file_properties = gth_sidebar_new ();
+ gtk_widget_hide (browser->priv->file_properties);
+ gtk_paned_pack2 (GTK_PANED (browser->priv->browser_sidebar), browser->priv->file_properties, FALSE, TRUE);
+
+ /* the box that contains the file list and the filter bar. */
+
+ vbox = gtk_vbox_new (FALSE, 0);
+ gtk_widget_show (vbox);
+ gtk_paned_pack2 (GTK_PANED (browser->priv->browser_container), vbox, TRUE, TRUE);
+
+ /* the list extra widget container */
+
+ browser->priv->list_extra_widget_container = gtk_vbox_new (FALSE, 0);
+ gtk_widget_show (browser->priv->list_extra_widget_container);
+ gtk_box_pack_start (GTK_BOX (vbox), browser->priv->list_extra_widget_container, FALSE, FALSE, 0);
+
+ /* the file list */
+
+ browser->priv->file_list = gth_file_list_new ();
+ gth_browser_set_sort_order (browser,
+ gth_main_get_sort_type (eel_gconf_get_string (PREF_SORT_TYPE, "file::mtime")),
+ FALSE);
+ gth_browser_enable_thumbnails (browser, eel_gconf_get_boolean (PREF_SHOW_THUMBNAILS, TRUE));
+ gth_file_list_set_thumb_size (GTH_FILE_LIST (browser->priv->file_list), eel_gconf_get_integer (PREF_THUMBNAIL_SIZE, DEF_THUMBNAIL_SIZE));
+ gtk_widget_show (browser->priv->file_list);
+ gtk_box_pack_start (GTK_BOX (vbox), browser->priv->file_list, TRUE, TRUE, 0);
+
+ browser->priv->file_list_popup = gtk_ui_manager_get_widget (browser->priv->ui, "/FileListPopup");
+
+ g_signal_connect (G_OBJECT (browser->priv->file_list),
+ "button_press_event",
+ G_CALLBACK (gth_file_list_button_press_cb),
+ browser);
+ g_signal_connect (G_OBJECT (gth_file_list_get_view (GTH_FILE_LIST (browser->priv->file_list))),
+ "selection_changed",
+ G_CALLBACK (gth_file_view_selection_changed_cb),
+ browser);
+ g_signal_connect (G_OBJECT (gth_file_list_get_view (GTH_FILE_LIST (browser->priv->file_list))),
+ "item_activated",
+ G_CALLBACK (gth_file_view_item_activated_cb),
+ browser);
+
+ /* the filter bar */
+
+ general_filter = eel_gconf_get_string (PREF_GENERAL_FILTER, DEFAULT_GENERAL_FILTER);
+ browser->priv->filterbar = gth_filterbar_new (general_filter);
+ g_free (general_filter);
+
+ gth_browser_show_filterbar (browser, eel_gconf_get_boolean (PREF_UI_FILTERBAR_VISIBLE, TRUE));
+ gtk_box_pack_end (GTK_BOX (vbox), browser->priv->filterbar, FALSE, FALSE, 0);
+
+ g_signal_connect (browser->priv->filterbar,
+ "changed",
+ G_CALLBACK (filterbar_changed_cb),
+ browser);
+ g_signal_connect (browser->priv->filterbar,
+ "personalize",
+ G_CALLBACK (filterbar_personalize_cb),
+ browser);
+
+ /* the image preloader */
+
+ browser->priv->image_preloader = gth_image_preloader_new ();
+
+ /**/
+
+ browser->priv->bookmarks_changed_id =
+ g_signal_connect (gth_main_get_default_monitor (),
+ "bookmarks-changed",
+ G_CALLBACK (bookmarks_changed_cb),
+ browser);
+ browser->priv->folder_changed_id =
+ g_signal_connect (gth_main_get_default_monitor (),
+ "folder-changed",
+ G_CALLBACK (folder_changed_cb),
+ browser);
+ browser->priv->file_renamed_id =
+ g_signal_connect (gth_main_get_default_monitor (),
+ "file-renamed",
+ G_CALLBACK (file_renamed_cb),
+ browser);
+ browser->priv->metadata_changed_id =
+ g_signal_connect (gth_main_get_default_monitor (),
+ "metadata-changed",
+ G_CALLBACK (metadata_changed_cb),
+ browser);
+ browser->priv->entry_points_changed_id =
+ g_signal_connect (gth_main_get_default_monitor (),
+ "entry-points-changed",
+ G_CALLBACK (entry_points_changed_cb),
+ browser);
+
+ /* init browser data */
+
+ _gth_browser_set_toolbar_visibility (browser, eel_gconf_get_boolean (PREF_UI_TOOLBAR_VISIBLE, TRUE));
+ _gth_browser_update_toolbar_style (browser);
+ _gth_browser_update_entry_point_list (browser);
+
+ browser->priv->show_hidden_files = eel_gconf_get_boolean (PREF_SHOW_HIDDEN_FILES, FALSE);
+ _gth_browser_set_action_active (browser, "View_ShowHiddenFiles", browser->priv->show_hidden_files);
+
+ browser->priv->fast_file_type = eel_gconf_get_boolean (PREF_FAST_FILE_TYPE, TRUE);
+
+ gth_hook_invoke ("gth-browser-construct", browser);
+
+ performance (DEBUG_INFO, "window initialized");
+
+ /* gconf notifications */
+
+ i = 0;
+ browser->priv->cnxn_id[i++] = eel_gconf_notification_add (
+ PREF_GENERAL_FILTER,
+ pref_general_filter_changed,
+ browser);
+ browser->priv->cnxn_id[i++] = eel_gconf_notification_add (
+ PREF_UI_TOOLBAR_STYLE,
+ pref_ui_toolbar_style_changed,
+ browser);
+ browser->priv->cnxn_id[i++] = eel_gconf_notification_add (
+ "/desktop/gnome/interface/toolbar_style",
+ pref_ui_toolbar_style_changed,
+ browser);
+ browser->priv->cnxn_id[i++] = eel_gconf_notification_add (
+ PREF_UI_TOOLBAR_VISIBLE,
+ pref_ui_toolbar_visible_changed,
+ browser);
+ browser->priv->cnxn_id[i++] = eel_gconf_notification_add (
+ PREF_UI_STATUSBAR_VISIBLE,
+ pref_ui_statusbar_visible_changed,
+ browser);
+ browser->priv->cnxn_id[i++] = eel_gconf_notification_add (
+ PREF_SHOW_HIDDEN_FILES,
+ pref_show_hidden_files_changed,
+ browser);
+ browser->priv->cnxn_id[i++] = eel_gconf_notification_add (
+ PREF_FAST_FILE_TYPE,
+ pref_fast_file_type_changed,
+ browser);
+ browser->priv->cnxn_id[i++] = eel_gconf_notification_add (
+ PREF_THUMBNAIL_SIZE,
+ pref_thumbnail_size_changed,
+ browser);
+
+ gth_window_set_current_page (GTH_WINDOW (browser), GTH_BROWSER_PAGE_BROWSER);
+
+ call_when_idle (_gth_browser_construct_step2, browser);
+}
+
+
+GtkWidget *
+gth_browser_new (const char *uri)
+{
+ GthBrowser *browser;
+
+ browser = (GthBrowser*) g_object_new (GTH_TYPE_BROWSER, "n-pages", GTH_BROWSER_N_PAGES, NULL);
+ _gth_browser_construct (browser);
+ browser_list = g_list_prepend (browser_list, browser);
+
+ if (uri != NULL) {
+ GFile *location;
+
+ location = g_file_new_for_uri ((uri != NULL) ? uri : gth_pref_get_startup_location ());
+ gth_browser_go_to (browser, location);
+ g_object_unref (location);
+ }
+
+ return (GtkWidget*) browser;
+}
+
+
+GFile *
+gth_browser_get_location (GthBrowser *browser)
+{
+ return browser->priv->location;
+}
+
+
+GthFileData *
+gth_browser_get_current_file (GthBrowser *browser)
+{
+ return browser->priv->current_file;
+}
+
+
+gboolean
+gth_browser_get_file_modified (GthBrowser *browser)
+{
+ if (browser->priv->current_file == NULL)
+ return FALSE;
+ else
+ return g_file_info_get_attribute_boolean (browser->priv->current_file->info, "file::is-modified");
+}
+
+
+void
+gth_browser_go_to (GthBrowser *browser,
+ GFile *location)
+{
+ gth_window_set_current_page (GTH_WINDOW (browser), GTH_BROWSER_PAGE_BROWSER);
+ _gth_browser_load (browser, location, GTH_ACTION_GO_TO);
+}
+
+
+void
+gth_browser_go_back (GthBrowser *browser,
+ int steps)
+{
+ GList *new_current;
+
+ new_current = browser->priv->history_current;
+ while ((new_current != NULL) && (steps-- > 0))
+ new_current = new_current->next;
+
+ if (new_current == NULL)
+ return;
+
+ browser->priv->history_current = new_current;
+ _gth_browser_load (browser, (GFile*) browser->priv->history_current->data, GTH_ACTION_GO_BACK);
+}
+
+
+void
+gth_browser_go_forward (GthBrowser *browser,
+ int steps)
+{
+ GList *new_current;
+
+ new_current = browser->priv->history_current;
+ while ((new_current != NULL) && (steps-- > 0))
+ new_current = new_current->prev;
+
+ if (new_current == NULL)
+ return;
+
+ browser->priv->history_current = new_current;
+ _gth_browser_load (browser, (GFile *) browser->priv->history_current->data, GTH_ACTION_GO_FORWARD);
+}
+
+
+void
+gth_browser_go_up (GthBrowser *browser,
+ int steps)
+{
+ GFile *parent;
+
+ if (browser->priv->location == NULL)
+ return;
+
+ parent = g_object_ref (browser->priv->location);
+ while ((steps-- > 0) && (parent != NULL)) {
+ GFile *parent_parent;
+
+ parent_parent = g_file_get_parent (parent);
+ g_object_unref (parent);
+ parent = parent_parent;
+ }
+
+ if (parent != NULL) {
+ gth_browser_go_to (browser, parent);
+ g_object_unref (parent);
+ }
+}
+
+
+void
+gth_browser_go_home (GthBrowser *browser)
+{
+ GFile *location;
+
+ location = g_file_new_for_uri (gth_pref_get_startup_location ());
+ gth_window_set_current_page (GTH_WINDOW (browser), GTH_BROWSER_PAGE_BROWSER);
+ gth_browser_go_to (browser, location);
+
+ g_object_unref (location);
+}
+
+
+void
+gth_browser_clear_history (GthBrowser *browser)
+{
+ _g_object_list_unref (browser->priv->history);
+ browser->priv->history = NULL;
+ browser->priv->history_current = NULL;
+
+ _gth_browser_add_to_history (browser, browser->priv->location);
+ _gth_browser_update_history_list (browser);
+}
+
+
+void
+gth_browser_set_dialog (GthBrowser *browser,
+ const char *dialog_name,
+ GtkWidget *dialog)
+{
+ g_hash_table_insert (browser->priv->named_dialogs, (gpointer) dialog_name, dialog);
+}
+
+
+GtkWidget *
+gth_browser_get_dialog (GthBrowser *browser,
+ const char *dialog_name)
+{
+ return g_hash_table_lookup (browser->priv->named_dialogs, dialog_name);
+}
+
+
+GtkUIManager *
+gth_browser_get_ui_manager (GthBrowser *browser)
+{
+ return browser->priv->ui;
+}
+
+
+GtkWidget *
+gth_browser_get_statusbar (GthBrowser *browser)
+{
+ return browser->priv->statusbar;
+}
+
+
+GtkWidget *
+gth_browser_get_file_list (GthBrowser *browser)
+{
+ return browser->priv->file_list;
+}
+
+
+GtkWidget *
+gth_browser_get_file_list_view (GthBrowser *browser)
+{
+ return gth_file_list_get_view (GTH_FILE_LIST (browser->priv->file_list));
+}
+
+
+GthFileSource *
+gth_browser_get_file_source (GthBrowser *browser)
+{
+ return browser->priv->location_source;
+}
+
+
+GthFileStore *
+gth_browser_get_file_store (GthBrowser *browser)
+{
+ return GTH_FILE_STORE (gth_file_view_get_model (GTH_FILE_VIEW (gth_browser_get_file_list_view (browser))));
+}
+
+
+GtkWidget *
+gth_browser_get_folder_tree (GthBrowser *browser)
+{
+ return browser->priv->folder_tree;
+}
+
+
+void
+gth_browser_get_sort_order (GthBrowser *browser,
+ GthFileDataSort **sort_type,
+ gboolean *inverse)
+{
+ if (sort_type != NULL)
+ *sort_type = browser->priv->sort_type;
+ if (inverse != NULL)
+ *inverse = browser->priv->sort_inverse;
+}
+
+
+void
+gth_browser_set_sort_order (GthBrowser *browser,
+ GthFileDataSort *sort_type,
+ gboolean inverse)
+{
+ if (sort_type == NULL) {
+ gth_browser_set_sort_order (browser,
+ gth_main_get_sort_type ("file::mtime"),
+ inverse);
+ return;
+ }
+
+ browser->priv->sort_type = sort_type;
+ browser->priv->sort_inverse = inverse;
+
+ gth_file_list_set_sort_func (GTH_FILE_LIST (browser->priv->file_list),
+ browser->priv->sort_type->cmp_func,
+ browser->priv->sort_inverse);
+}
+
+
+void
+gth_browser_stop (GthBrowser *browser)
+{
+ _gth_browser_cancel (browser);
+
+ gth_browser_update_sensitivity (browser);
+
+ if ((browser->priv->task != NULL) && gth_task_is_running (browser->priv->task))
+ gth_task_cancel (browser->priv->task);
+}
+
+
+void
+gth_browser_reload (GthBrowser *browser)
+{
+ gth_browser_go_to (browser, browser->priv->location);
+}
+
+
+static void
+task_completed_cb (GthTask *task,
+ GError *error,
+ GthBrowser *browser)
+{
+ browser->priv->activity_ref--;
+
+ g_signal_handler_disconnect (browser->priv->task, browser->priv->task_completed);
+
+ if (! browser->priv->closing) {
+ gth_browser_update_sensitivity (browser);
+ if (error != NULL) {
+ if (! g_error_matches (error, GTH_TASK_ERROR, GTH_TASK_ERROR_CANCELLED))
+ _gtk_error_dialog_from_gerror_show (GTK_WINDOW (browser), _("Could not perfom the operation"), &error);
+ else
+ g_error_free (error);
+ }
+ }
+
+ g_object_unref (browser->priv->task);
+ browser->priv->task = NULL;
+
+ if (browser->priv->closing)
+ _gth_browser_close_step3 (browser);
+}
+
+
+void
+gth_browser_exec_task (GthBrowser *browser,
+ GthTask *task)
+{
+ g_return_if_fail (task != NULL);
+
+ if (browser->priv->task != NULL)
+ gth_task_cancel (task);
+
+ browser->priv->task = g_object_ref (task);
+ browser->priv->task_completed = g_signal_connect (task,
+ "completed",
+ G_CALLBACK (task_completed_cb),
+ browser);
+ browser->priv->activity_ref++;
+ gth_browser_update_sensitivity (browser);
+ gth_task_exec (browser->priv->task);
+}
+
+
+void
+gth_browser_set_list_extra_widget (GthBrowser *browser,
+ GtkWidget *widget)
+{
+ if (browser->priv->list_extra_widget != NULL) {
+ gtk_container_remove (GTK_CONTAINER (browser->priv->list_extra_widget_container), browser->priv->list_extra_widget);
+ browser->priv->list_extra_widget = NULL;
+ }
+
+ if (widget != NULL) {
+ browser->priv->list_extra_widget = widget;
+ gtk_container_add (GTK_CONTAINER (browser->priv->list_extra_widget_container), browser->priv->list_extra_widget);
+ }
+}
+
+
+GtkWidget *
+gth_browser_get_list_extra_widget (GthBrowser *browser)
+{
+ return browser->priv->list_extra_widget;
+}
+
+
+void
+gth_browser_set_viewer_widget (GthBrowser *browser,
+ GtkWidget *widget)
+{
+ _gtk_container_remove_children (GTK_CONTAINER (browser->priv->viewer_container), NULL, NULL);
+ if (widget != NULL)
+ gtk_container_add (GTK_CONTAINER (browser->priv->viewer_container), widget);
+}
+
+
+GtkWidget *
+gth_browser_get_viewer_widget (GthBrowser *browser)
+{
+ GtkWidget *child = NULL;
+ GList *children;
+
+ children = gtk_container_get_children (GTK_CONTAINER (browser->priv->viewer_container));
+ if (children != NULL)
+ child = children->data;
+ g_list_free (children);
+
+ return child;
+}
+
+
+GtkWidget *
+gth_browser_get_viewer_page (GthBrowser *browser)
+{
+ return (GtkWidget *) browser->priv->viewer_page;
+}
+
+
+GtkWidget *
+gth_browser_get_viewer_sidebar (GthBrowser *browser)
+{
+ return browser->priv->viewer_sidebar;
+}
+
+
+static gboolean
+view_focused_image (GthBrowser *browser)
+{
+ GthFileView *view;
+ int pos;
+ GthFileData *focused_file;
+
+ if (browser->priv->current_file == NULL)
+ return FALSE;
+
+ view = GTH_FILE_VIEW (gth_browser_get_file_list_view (browser));
+ pos = gth_file_view_get_cursor (view);
+ if (pos == -1)
+ return FALSE;
+
+ focused_file = gth_file_store_get_file_at_pos (GTH_FILE_STORE (gth_file_view_get_model (view)), pos);
+ if (focused_file == NULL)
+ return FALSE;
+
+ return ! g_file_equal (browser->priv->current_file->file, focused_file->file);
+}
+
+
+gboolean
+gth_browser_show_next_image (GthBrowser *browser,
+ gboolean skip_broken,
+ gboolean only_selected)
+{
+ GthFileView *view;
+ int pos;
+
+ view = GTH_FILE_VIEW (gth_browser_get_file_list_view (browser));
+
+ if (browser->priv->current_file == NULL) {
+ pos = gth_file_list_next_file (GTH_FILE_LIST (browser->priv->file_list), -1, skip_broken, only_selected, TRUE);
+ }
+ else if (view_focused_image (browser)) {
+ pos = gth_file_view_get_cursor (view);
+ if (pos < 0)
+ pos = gth_file_list_next_file (GTH_FILE_LIST (browser->priv->file_list), -1, skip_broken, only_selected, TRUE);
+ }
+ else {
+ pos = gth_file_store_find_visible (gth_browser_get_file_store (browser), browser->priv->current_file->file);
+ pos = gth_file_list_next_file (GTH_FILE_LIST (browser->priv->file_list), pos, skip_broken, only_selected, FALSE);
+ }
+
+ if (pos >= 0) {
+ GthFileData *file_data;
+
+ file_data = gth_file_store_get_file_at_pos (GTH_FILE_STORE (gth_file_view_get_model (view)), pos);
+ gth_browser_load_file (browser, file_data, TRUE);
+ }
+
+ return (pos >= 0);
+}
+
+
+gboolean
+gth_browser_show_prev_image (GthBrowser *browser,
+ gboolean skip_broken,
+ gboolean only_selected)
+{
+ GthFileView *view;
+ int pos;
+
+ view = GTH_FILE_VIEW (gth_browser_get_file_list_view (browser));
+
+ if (browser->priv->current_file == NULL) {
+ pos = gth_file_list_prev_file (GTH_FILE_LIST (browser->priv->file_list), -1, skip_broken, only_selected, TRUE);
+ }
+ else if (view_focused_image (browser)) {
+ pos = gth_file_view_get_cursor (view);
+ if (pos < 0)
+ pos = gth_file_list_prev_file (GTH_FILE_LIST (browser->priv->file_list), -1, skip_broken, only_selected, TRUE);
+ }
+ else {
+ pos = gth_file_store_find_visible (gth_browser_get_file_store (browser), browser->priv->current_file->file);
+ pos = gth_file_list_prev_file (GTH_FILE_LIST (browser->priv->file_list), pos, skip_broken, only_selected, FALSE);
+ }
+
+ if (pos >= 0) {
+ GthFileData *file_data;
+
+ file_data = gth_file_store_get_file_at_pos (GTH_FILE_STORE (gth_file_view_get_model (view)), pos);
+ gth_browser_load_file (browser, file_data, TRUE);
+ }
+
+ return (pos >= 0);
+}
+
+
+gboolean
+gth_browser_show_first_image (GthBrowser *browser,
+ gboolean skip_broken,
+ gboolean only_selected)
+{
+ int pos;
+ GthFileView *view;
+ GthFileData *file_data;
+
+ pos = gth_file_list_first_file (GTH_FILE_LIST (browser->priv->file_list), skip_broken, only_selected);
+ if (pos < 0)
+ return FALSE;
+
+ view = GTH_FILE_VIEW (gth_browser_get_file_list_view (browser));
+ file_data = gth_file_store_get_file_at_pos (GTH_FILE_STORE (gth_file_view_get_model (view)), pos);
+ gth_browser_load_file (browser, file_data, TRUE);
+
+ return TRUE;
+}
+
+
+gboolean
+gth_browser_show_last_image (GthBrowser *browser,
+ gboolean skip_broken,
+ gboolean only_selected)
+{
+ int pos;
+ GthFileView *view;
+ GthFileData *file_data;
+
+ pos = gth_file_list_last_file (GTH_FILE_LIST (browser->priv->file_list), skip_broken, only_selected);
+ if (pos < 0)
+ return FALSE;
+
+ view = GTH_FILE_VIEW (gth_browser_get_file_list_view (browser));
+ file_data = gth_file_store_get_file_at_pos (GTH_FILE_STORE (gth_file_view_get_model (view)), pos);
+ gth_browser_load_file (browser, file_data, TRUE);
+
+ return TRUE;
+}
+
+
+static void
+file_metadata_ready_cb (GList *files,
+ GError *error,
+ gpointer user_data)
+{
+ GthBrowser *browser = user_data;
+
+ gth_sidebar_set_file (GTH_SIDEBAR (browser->priv->file_properties), browser->priv->current_file);
+ gth_sidebar_set_file (GTH_SIDEBAR (browser->priv->viewer_sidebar), browser->priv->current_file);
+ _gth_browser_update_statusbar_file_info (browser);
+
+ if (browser->priv->viewer_page != NULL)
+ gth_viewer_page_view (browser->priv->viewer_page, browser->priv->current_file);
+
+ gth_browser_update_title (browser);
+ gth_browser_update_sensitivity (browser);
+
+ if (browser->priv->location == NULL) {
+ GFile *parent;
+
+ parent = g_file_get_parent (browser->priv->current_file->file);
+ gth_browser_go_to (browser, parent);
+ g_object_unref (parent);
+ }
+}
+
+
+/* -- gth_browser_load_file -- */
+
+
+static void
+_gth_browser_make_file_visible (GthBrowser *browser,
+ GthFileData *file_data)
+{
+ int file_pos;
+ GtkWidget *view;
+ GthVisibility visibility;
+
+ file_pos = gth_file_store_find_visible (GTH_FILE_STORE (gth_browser_get_file_store (browser)), file_data->file);
+ if (file_pos < 0)
+ return;
+
+ view = gth_browser_get_file_list_view (browser);
+ g_signal_handlers_block_by_func (gth_browser_get_file_list_view (browser), gth_file_view_selection_changed_cb, browser);
+ gth_file_selection_unselect_all (GTH_FILE_SELECTION (view));
+ gth_file_selection_select (GTH_FILE_SELECTION (view), file_pos);
+ gth_file_view_set_cursor (GTH_FILE_VIEW (view), file_pos);
+ g_signal_handlers_unblock_by_func (gth_browser_get_file_list_view (browser), gth_file_view_selection_changed_cb, browser);
+
+ visibility = gth_file_view_get_visibility (GTH_FILE_VIEW (view), file_pos);
+ if (visibility != GTH_VISIBILITY_FULL) {
+ double align;
+
+ switch (visibility) {
+ case GTH_VISIBILITY_NONE:
+ case GTH_VISIBILITY_FULL:
+ case GTH_VISIBILITY_PARTIAL:
+ align = 0.5;
+ break;
+
+ case GTH_VISIBILITY_PARTIAL_TOP:
+ align = 0.0;
+ break;
+
+ case GTH_VISIBILITY_PARTIAL_BOTTOM:
+ align = 1.0;
+ break;
+ }
+ gth_file_view_scroll_to (GTH_FILE_VIEW (view), file_pos, align);
+ }
+}
+
+
+static void
+_gth_browser_deactivate_viewer_page (GthBrowser *browser)
+{
+ if (browser->priv->viewer_page != NULL) {
+ gth_viewer_page_deactivate (browser->priv->viewer_page);
+ gtk_ui_manager_ensure_update (browser->priv->ui);
+ gth_browser_set_viewer_widget (browser, NULL);
+ g_object_unref (browser->priv->viewer_page);
+ browser->priv->viewer_page = NULL;
+ }
+}
+
+
+static void
+_gth_browser_load_file (GthBrowser *browser,
+ GthFileData *file_data,
+ gboolean view)
+{
+ GList *list;
+ GList *scan;
+ GList *files;
+
+ if (file_data == NULL) {
+ _gth_browser_deactivate_viewer_page (browser);
+ _g_object_unref (browser->priv->current_file);
+ browser->priv->current_file = NULL;
+
+ gtk_widget_hide (browser->priv->file_properties);
+
+ _gth_browser_update_statusbar_file_info (browser);
+ gth_browser_update_title (browser);
+ gth_browser_update_sensitivity (browser);
+
+ return;
+ }
+
+ g_object_ref (file_data);
+ _g_object_unref (browser->priv->current_file);
+ browser->priv->current_file = gth_file_data_dup (file_data);
+ g_object_unref (file_data);
+
+ _gth_browser_make_file_visible (browser, browser->priv->current_file);
+
+ list = gth_main_get_all_viewer_pages ();
+ for (scan = list; scan; scan = scan->next) {
+ GthViewerPage *registered_viewer_page = scan->data;
+
+ if (gth_viewer_page_can_view (registered_viewer_page, browser->priv->current_file)) {
+ if ((browser->priv->viewer_page != NULL) && (G_OBJECT_TYPE (registered_viewer_page) != G_OBJECT_TYPE (browser->priv->viewer_page))) {
+ gth_viewer_page_deactivate (browser->priv->viewer_page);
+ gtk_ui_manager_ensure_update (browser->priv->ui);
+ gth_browser_set_viewer_widget (browser, NULL);
+ g_object_unref (browser->priv->viewer_page);
+ browser->priv->viewer_page = NULL;
+ }
+ if (browser->priv->viewer_page == NULL) {
+ browser->priv->viewer_page = g_object_new (G_OBJECT_TYPE (registered_viewer_page), NULL);
+ gth_viewer_page_activate (browser->priv->viewer_page, browser);
+ gtk_ui_manager_ensure_update (browser->priv->ui);
+ }
+ break;
+ }
+ }
+
+ if (view) {
+ gth_viewer_page_show (browser->priv->viewer_page);
+ gth_window_set_current_page (GTH_WINDOW (browser), GTH_BROWSER_PAGE_VIEWER);
+ }
+
+ files = g_list_prepend (NULL, browser->priv->current_file);
+ _g_query_metadata_async (files,
+ "*",
+ NULL,
+ file_metadata_ready_cb,
+ browser);
+
+ g_list_free (files);
+}
+
+
+typedef struct {
+ GthBrowser *browser;
+ GthFileData *file_data;
+ gboolean view;
+} LoadFileData;
+
+
+static void
+load_file__file_saved_cb (GthBrowser *browser,
+ gboolean cancelled,
+ gpointer user_data)
+{
+ LoadFileData *data = user_data;
+
+ if (! cancelled)
+ _gth_browser_load_file (data->browser, data->file_data, data->view);
+
+ _g_object_unref (data->file_data);
+ g_free (data);
+}
+
+
+void
+gth_browser_load_file (GthBrowser *browser,
+ GthFileData *file_data,
+ gboolean view)
+{
+ if (gth_browser_get_file_modified (browser)) {
+ LoadFileData *data;
+
+ data = g_new0 (LoadFileData, 1);
+ data->browser = browser;
+ if (file_data != NULL)
+ data->file_data = g_object_ref (file_data);
+ data->view = view;
+
+ _gth_browser_ask_whether_to_save (browser,
+ load_file__file_saved_cb,
+ data);
+ }
+ else
+ _gth_browser_load_file (browser, file_data, view);
+}
+
+
+void
+gth_browser_show_viewer_properties (GthBrowser *browser,
+ gboolean show)
+{
+ if (show) {
+ _gth_browser_set_action_active (browser, "Viewer_Tools", FALSE);
+ gtk_widget_show (browser->priv->viewer_sidebar);
+ gtk_notebook_set_current_page (GTK_NOTEBOOK (browser->priv->viewer_sidebar), GTH_SIDEBAR_PAGE_PROPERTIES);
+ }
+ else
+ gtk_widget_hide (browser->priv->viewer_sidebar);
+}
+
+
+void
+gth_browser_show_viewer_tools (GthBrowser *browser,
+ gboolean show)
+{
+ if (show) {
+ _gth_browser_set_action_active (browser, "Viewer_Properties", FALSE);
+ gtk_widget_show (browser->priv->viewer_sidebar);
+ gtk_notebook_set_current_page (GTK_NOTEBOOK (browser->priv->viewer_sidebar), GTH_SIDEBAR_PAGE_TOOLS);
+ }
+ else
+ gtk_widget_hide (browser->priv->viewer_sidebar);
+}
+
+
+/* -- gth_browser_load_location -- */
+
+
+typedef struct {
+ GthFileSource *file_source;
+ GFile *location;
+ GthBrowser *browser;
+} LoadLocationData;
+
+
+static void
+load_location_data_free (LoadLocationData *data)
+{
+ g_object_unref (data->location);
+ g_object_unref (data->file_source);
+ g_free (data);
+}
+
+
+static void
+load_file_attributes_ready_cb (GthFileSource *file_source,
+ GList *files,
+ GError *error,
+ gpointer user_data)
+{
+ LoadLocationData *data = user_data;
+ GthBrowser *browser = data->browser;
+
+ if (error == NULL) {
+ GthFileData *file_data;
+
+ file_data = files->data;
+ if (g_file_info_get_file_type (file_data->info) == G_FILE_TYPE_REGULAR) {
+ GFile *parent;
+
+ parent = g_file_get_parent (file_data->file);
+ if ((browser->priv->location != NULL) && ! g_file_equal (parent, browser->priv->location)) {
+ /* set location to NULL to force a folder reload */
+ _g_object_unref (browser->priv->location);
+ browser->priv->location = NULL;
+ }
+
+ gth_browser_load_file (browser, file_data, TRUE);
+
+ g_object_unref (parent);
+ }
+ else if (g_file_info_get_file_type (file_data->info) == G_FILE_TYPE_DIRECTORY) {
+ gth_window_set_current_page (GTH_WINDOW (browser), GTH_BROWSER_PAGE_BROWSER);
+ gth_browser_go_to (browser, file_data->file);
+ }
+ else {
+ GError *error;
+ char *uri;
+
+ uri = g_file_get_uri (data->location);
+ error = g_error_new (GTHUMB_ERROR, 0, _("File type not supported %s"), uri);
+ _gtk_error_dialog_from_gerror_show (GTK_WINDOW (browser), _("Could not load the position"), &error);
+
+ g_free (uri);
+ }
+ }
+ else
+ _gtk_error_dialog_from_gerror_show (GTK_WINDOW (browser), _("Could not load the position"), &error);
+
+ load_location_data_free (data);
+}
+
+
+void
+gth_browser_load_location (GthBrowser *browser,
+ GFile *location)
+{
+ LoadLocationData *data;
+ GList *list;
+
+ data = g_new0 (LoadLocationData, 1);
+ data->browser = browser;
+ data->location = g_object_ref (location);
+ data->file_source = gth_main_get_file_source (data->location);
+ if (data->file_source == NULL) {
+ GError *error;
+ char *uri;
+
+ uri = g_file_get_uri (data->location);
+ error = g_error_new (GTHUMB_ERROR, 0, _("No suitable module found for %s"), uri);
+ _gtk_error_dialog_from_gerror_show (GTK_WINDOW (browser), _("Could not load the position"), &error);
+
+ g_free (uri);
+ }
+
+ list = g_list_prepend (NULL, g_object_ref (data->location));
+ gth_file_source_read_attributes (data->file_source,
+ list,
+ eel_gconf_get_boolean (PREF_FAST_FILE_TYPE, TRUE) ? GTH_FILE_DATA_ATTRIBUTES_WITH_FAST_CONTENT_TYPE : GTH_FILE_DATA_ATTRIBUTES_WITH_CONTENT_TYPE,
+ load_file_attributes_ready_cb,
+ data);
+
+ _g_object_list_unref (list);
+}
+
+
+void
+gth_browser_enable_thumbnails (GthBrowser *browser,
+ gboolean show)
+{
+ gth_file_list_enable_thumbs (GTH_FILE_LIST (browser->priv->file_list), show);
+ _gth_browser_set_action_active (browser, "View_Thumbnails", show);
+ eel_gconf_set_boolean (PREF_SHOW_THUMBNAILS, show);
+}
+
+
+void
+gth_browser_show_filterbar (GthBrowser *browser,
+ gboolean show)
+{
+ if (show)
+ gtk_widget_show (browser->priv->filterbar);
+ else
+ gtk_widget_hide (browser->priv->filterbar);
+ _gth_browser_set_action_active (browser, "View_Filterbar", show);
+ eel_gconf_set_boolean (PREF_UI_FILTERBAR_VISIBLE, show);
+}
+
+
+gpointer
+gth_browser_get_image_preloader (GthBrowser *browser)
+{
+ return g_object_ref (browser->priv->image_preloader);
+}
diff --git a/gthumb/gth-browser.h b/gthumb/gth-browser.h
new file mode 100644
index 0000000..398e3f9
--- /dev/null
+++ b/gthumb/gth-browser.h
@@ -0,0 +1,148 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2005-2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GTH_BROWSER_H
+#define GTH_BROWSER_H
+
+#include "gth-file-source.h"
+#include "gth-file-store.h"
+#include "gth-task.h"
+#include "gth-window.h"
+#include "typedefs.h"
+
+G_BEGIN_DECLS
+
+#define GTH_TYPE_BROWSER (gth_browser_get_type ())
+#define GTH_BROWSER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTH_TYPE_BROWSER, GthBrowser))
+#define GTH_BROWSER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTH_BROWSER_TYPE, GthBrowserClass))
+#define GTH_IS_BROWSER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTH_TYPE_BROWSER))
+#define GTH_IS_BROWSER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTH_TYPE_BROWSER))
+#define GTH_BROWSER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GTH_TYPE_BROWSER, GthBrowserClass))
+
+typedef struct _GthBrowser GthBrowser;
+typedef struct _GthBrowserClass GthBrowserClass;
+typedef struct _GthBrowserPrivateData GthBrowserPrivateData;
+
+typedef enum { /*< skip >*/
+ GTH_BROWSER_PAGE_BROWSER = 0,
+ GTH_BROWSER_PAGE_VIEWER,
+ GTH_BROWSER_PAGE_EDITOR,
+ GTH_BROWSER_N_PAGES
+} GthBrowserPage;
+
+struct _GthBrowser
+{
+ GthWindow __parent;
+ GthBrowserPrivateData *priv;
+};
+
+struct _GthBrowserClass
+{
+ GthWindowClass __parent_class;
+
+ void (*location_ready) (GthBrowser *browser,
+ GFile *location,
+ gboolean error);
+};
+
+GType gth_browser_get_type (void);
+GtkWidget * gth_browser_new (const char *uri);
+GFile * gth_browser_get_location (GthBrowser *browser);
+GthFileData * gth_browser_get_current_file (GthBrowser *browser);
+gboolean gth_browser_get_file_modified (GthBrowser *browser);
+void gth_browser_go_to (GthBrowser *browser,
+ GFile *location);
+void gth_browser_go_back (GthBrowser *browser,
+ int steps);
+void gth_browser_go_forward (GthBrowser *browser,
+ int steps);
+void gth_browser_go_up (GthBrowser *browser,
+ int steps);
+void gth_browser_go_home (GthBrowser *browser);
+void gth_browser_clear_history (GthBrowser *browser);
+void gth_browser_enable_thumbnails (GthBrowser *browser,
+ gboolean enable);
+void gth_browser_set_dialog (GthBrowser *browser,
+ const char *dialog_name,
+ GtkWidget *dialog);
+GtkWidget * gth_browser_get_dialog (GthBrowser *browser,
+ const char *dialog_name);
+GtkUIManager * gth_browser_get_ui_manager (GthBrowser *browser);
+GtkWidget * gth_browser_get_statusbar (GthBrowser *browser);
+GtkWidget * gth_browser_get_file_list (GthBrowser *browser);
+GtkWidget * gth_browser_get_file_list_view (GthBrowser *browser);
+GthFileSource * gth_browser_get_file_source (GthBrowser *browser);
+GthFileStore * gth_browser_get_file_store (GthBrowser *browser);
+GtkWidget * gth_browser_get_folder_tree (GthBrowser *browser);
+void gth_browser_get_sort_order (GthBrowser *browser,
+ GthFileDataSort **sort_type,
+ gboolean *inverse);
+void gth_browser_set_sort_order (GthBrowser *browser,
+ GthFileDataSort *sort_type,
+ gboolean inverse);
+void gth_browser_stop (GthBrowser *browser);
+void gth_browser_reload (GthBrowser *browser);
+void gth_browser_exec_task (GthBrowser *browser,
+ GthTask *task);
+void gth_browser_set_list_extra_widget (GthBrowser *browser,
+ GtkWidget *widget);
+GtkWidget * gth_browser_get_list_extra_widget (GthBrowser *browser);
+void gth_browser_set_current_page (GthBrowser *browser,
+ GthBrowserPage page);
+GthBrowserPage gth_browser_get_current_page (GthBrowser *browser);
+void gth_browser_set_viewer_widget (GthBrowser *browser,
+ GtkWidget *widget);
+GtkWidget * gth_browser_get_viewer_widget (GthBrowser *browser);
+GtkWidget * gth_browser_get_viewer_page (GthBrowser *browser);
+GtkWidget * gth_browser_get_viewer_sidebar (GthBrowser *browser);
+gboolean gth_browser_show_next_image (GthBrowser *browser,
+ gboolean skip_broken,
+ gboolean only_selected);
+gboolean gth_browser_show_prev_image (GthBrowser *browser,
+ gboolean skip_broken,
+ gboolean only_selected);
+gboolean gth_browser_show_first_image (GthBrowser *browser,
+ gboolean skip_broken,
+ gboolean only_selected);
+gboolean gth_browser_show_last_image (GthBrowser *browser,
+ gboolean skip_broken,
+ gboolean only_selected);
+void gth_browser_load_file (GthBrowser *browser,
+ GthFileData *file_data,
+ gboolean view);
+void gth_browser_update_title (GthBrowser *browser);
+void gth_browser_update_sensitivity (GthBrowser *browser);
+void gth_browser_show_viewer_properties (GthBrowser *browser,
+ gboolean show);
+void gth_browser_show_viewer_tools (GthBrowser *browser,
+ gboolean show);
+void gth_browser_load_location (GthBrowser *browser,
+ GFile *location);
+void gth_browser_enable_thumbnails (GthBrowser *browser,
+ gboolean enable);
+void gth_browser_show_filterbar (GthBrowser *browser,
+ gboolean show);
+gpointer gth_browser_get_image_preloader (GthBrowser *browser);
+
+G_END_DECLS
+
+#endif /* GTH_BROWSER_H */
diff --git a/gthumb/gth-cell-renderer-thumbnail.c b/gthumb/gth-cell-renderer-thumbnail.c
new file mode 100644
index 0000000..bf8d3db
--- /dev/null
+++ b/gthumb/gth-cell-renderer-thumbnail.c
@@ -0,0 +1,606 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2008 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <math.h>
+#include "gth-file-data.h"
+#include "glib-utils.h"
+#include "gth-cell-renderer-thumbnail.h"
+
+
+#define DEFAULT_THUMBNAIL_SIZE 112
+#define MAX_THUMBNAIL_SIZE 320
+#define THUMBNAIL_X_BORDER 8
+#define THUMBNAIL_Y_BORDER 8
+
+
+/* properties */
+enum {
+ PROP_0,
+ PROP_SIZE,
+ PROP_IS_ICON,
+ PROP_THUMBNAIL,
+ PROP_FILE
+};
+
+
+struct _GthCellRendererThumbnailPrivate
+{
+ int size;
+ gboolean is_icon;
+ GdkPixbuf *thumbnail;
+ GthFileData *file;
+};
+
+
+static gpointer parent_class = NULL;
+
+
+static void
+gth_cell_renderer_thumbnail_finalize (GObject *object)
+{
+ GthCellRendererThumbnail *cell_renderer;
+
+ cell_renderer = GTH_CELL_RENDERER_THUMBNAIL (object);
+
+ if (cell_renderer->priv != NULL) {
+ _g_object_unref (cell_renderer->priv->thumbnail);
+ _g_object_unref (cell_renderer->priv->file);
+ g_free (cell_renderer->priv);
+ cell_renderer->priv = NULL;
+ }
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+
+static void
+gth_cell_renderer_thumbnail_get_property (GObject *object,
+ guint param_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GthCellRendererThumbnail *self;
+
+ self = GTH_CELL_RENDERER_THUMBNAIL (object);
+
+ switch (param_id) {
+ case PROP_SIZE:
+ g_value_set_int (value, self->priv->size);
+ break;
+ case PROP_IS_ICON:
+ g_value_set_boolean (value, self->priv->is_icon);
+ break;
+ case PROP_THUMBNAIL:
+ g_value_set_object (value, self->priv->thumbnail);
+ break;
+ case PROP_FILE:
+ g_value_set_object (value, self->priv->file);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+ break;
+ }
+}
+
+
+static void
+gth_cell_renderer_thumbnail_set_property (GObject *object,
+ guint param_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GthCellRendererThumbnail *self;
+
+ self = GTH_CELL_RENDERER_THUMBNAIL (object);
+
+ switch (param_id) {
+ case PROP_SIZE:
+ self->priv->size = g_value_get_int (value);
+ break;
+ case PROP_IS_ICON:
+ self->priv->is_icon = g_value_get_boolean (value);
+ break;
+ case PROP_THUMBNAIL:
+ self->priv->thumbnail = g_value_dup_object (value);
+ break;
+ case PROP_FILE:
+ self->priv->file = g_value_dup_object (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+ break;
+ }
+}
+
+
+static void
+gth_cell_renderer_thumbnail_get_size (GtkCellRenderer *cell,
+ GtkWidget *widget,
+ GdkRectangle *cell_area,
+ int *x_offset,
+ int *y_offset,
+ int *width,
+ int *height)
+{
+ GthCellRendererThumbnail *self;
+ int calc_width;
+ int calc_height;
+
+ self = GTH_CELL_RENDERER_THUMBNAIL (cell);
+
+ calc_width = (int) (cell->xpad * 2) + (THUMBNAIL_X_BORDER * 2) + self->priv->size;
+ calc_height = (int) (cell->ypad * 2) + (THUMBNAIL_Y_BORDER * 2) + self->priv->size;
+
+ if (width != NULL)
+ *width = calc_width;
+
+ if (height != NULL)
+ *height = calc_height;
+
+ if (cell_area != NULL) {
+ if (x_offset != NULL) {
+ *x_offset = cell->xalign * (cell_area->width - calc_width);
+ *x_offset = MAX (*x_offset, 0);
+ }
+
+ if (y_offset != NULL) {
+ *y_offset = cell->yalign * (cell_area->height - calc_height);
+ *y_offset = MAX (*y_offset, 0);
+ }
+ }
+}
+
+
+/* From gtkcellrendererpixbuf.c
+ * Copyright (C) 2000 Red Hat, Inc., Jonathan Blandford <jrb redhat com> */
+static GdkPixbuf *
+create_colorized_pixbuf (GdkPixbuf *src,
+ GdkColor *new_color)
+{
+ gint i, j;
+ gint width, height, has_alpha, src_row_stride, dst_row_stride;
+ gint red_value, green_value, blue_value;
+ guchar *target_pixels;
+ guchar *original_pixels;
+ guchar *pixsrc;
+ guchar *pixdest;
+ GdkPixbuf *dest;
+
+ red_value = new_color->red / 255.0;
+ green_value = new_color->green / 255.0;
+ blue_value = new_color->blue / 255.0;
+
+ dest = gdk_pixbuf_new (gdk_pixbuf_get_colorspace (src),
+ gdk_pixbuf_get_has_alpha (src),
+ gdk_pixbuf_get_bits_per_sample (src),
+ gdk_pixbuf_get_width (src),
+ gdk_pixbuf_get_height (src));
+
+ has_alpha = gdk_pixbuf_get_has_alpha (src);
+ width = gdk_pixbuf_get_width (src);
+ height = gdk_pixbuf_get_height (src);
+ src_row_stride = gdk_pixbuf_get_rowstride (src);
+ dst_row_stride = gdk_pixbuf_get_rowstride (dest);
+ target_pixels = gdk_pixbuf_get_pixels (dest);
+ original_pixels = gdk_pixbuf_get_pixels (src);
+
+ for (i = 0; i < height; i++) {
+ pixdest = target_pixels + i*dst_row_stride;
+ pixsrc = original_pixels + i*src_row_stride;
+ for (j = 0; j < width; j++) {
+ *pixdest++ = (*pixsrc++ * red_value) >> 8;
+ *pixdest++ = (*pixsrc++ * green_value) >> 8;
+ *pixdest++ = (*pixsrc++ * blue_value) >> 8;
+ if (has_alpha) {
+ *pixdest++ = *pixsrc++;
+ }
+ }
+ }
+ return dest;
+}
+
+
+/*
+static void
+gth_cell_renderer_thumbnail_render (GtkCellRenderer *cell,
+ GdkWindow *window,
+ GtkWidget *widget,
+ GdkRectangle *background_area,
+ GdkRectangle *cell_area,
+ GdkRectangle *expose_area,
+ GtkCellRendererState flags)
+{
+ GthCellRendererThumbnail *self;
+ GtkStateType state;
+ GdkRectangle thumb_rect;
+ GdkRectangle draw_rect;
+ GdkRectangle image_rect;
+ cairo_t *cr;
+ cairo_path_t *cr_path;
+ GdkPixbuf *pixbuf;
+ GdkPixbuf *colorized = NULL;
+
+ self = GTH_CELL_RENDERER_THUMBNAIL (cell);
+
+ gth_cell_renderer_thumbnail_get_size (cell, widget, cell_area,
+ &thumb_rect.x,
+ &thumb_rect.y,
+ &thumb_rect.width,
+ &thumb_rect.height);
+
+ thumb_rect.x += cell_area->x + cell->xpad;
+ thumb_rect.y += cell_area->y + cell->ypad;
+ thumb_rect.width -= cell->xpad * 2;
+ thumb_rect.height -= cell->ypad * 2;
+
+ if (! gdk_rectangle_intersect (cell_area, &thumb_rect, &draw_rect)
+ || ! gdk_rectangle_intersect (expose_area, &thumb_rect, &draw_rect))
+ {
+ return;
+ }
+
+ if ((flags & GTK_CELL_RENDERER_SELECTED) == GTK_CELL_RENDERER_SELECTED)
+ state = GTK_WIDGET_HAS_FOCUS (widget) ? GTK_STATE_SELECTED : GTK_STATE_ACTIVE;
+ else
+ state = ((flags & GTK_CELL_RENDERER_FOCUSED) == GTK_CELL_RENDERER_FOCUSED) ? GTK_STATE_ACTIVE : GTK_STATE_NORMAL;
+
+ cr = gdk_cairo_create (window);
+
+ if (state == GTK_STATE_NORMAL)
+ gdk_cairo_set_source_color (cr, &widget->style->bg[state]);
+ else
+ gdk_cairo_set_source_color (cr, &widget->style->base[state]);
+
+#define R 7
+#define B 8
+
+ cairo_move_to (cr, thumb_rect.x, thumb_rect.y + R);
+ cairo_arc (cr, thumb_rect.x + R, thumb_rect.y + R, R, 1.0 * M_PI, 1.5 * M_PI);
+ cairo_rel_line_to (cr, thumb_rect.width - (R * 2), 0);
+ cairo_arc (cr, thumb_rect.x + thumb_rect.width - R, thumb_rect.y + R, R, 1.5 * M_PI, 2.0 * M_PI);
+ cairo_rel_line_to (cr, 0, thumb_rect.height - (R * 2));
+ cairo_arc (cr, thumb_rect.x + thumb_rect.width - R, thumb_rect.y + thumb_rect.height - R, R, 0.0 * M_PI, 0.5 * M_PI);
+ cairo_rel_line_to (cr, - (thumb_rect.width - (R * 2)), 0);
+ cairo_arc (cr, thumb_rect.x + R, thumb_rect.y + thumb_rect.height - R, R, 0.5 * M_PI, 1.0 * M_PI);
+ cairo_close_path (cr);
+ cr_path = cairo_copy_path (cr);
+ cairo_fill (cr);
+
+ gdk_cairo_set_source_color (cr, &widget->style->dark[state]);
+ cairo_set_line_width (cr, 0.5);
+ cairo_append_path (cr, cr_path);
+ cairo_stroke (cr);
+
+ cairo_path_destroy (cr_path);
+
+ if (self->priv->is_icon) {
+ gdk_cairo_set_source_color (cr, &widget->style->base[GTK_STATE_NORMAL]);
+ image_rect.width = thumb_rect.width - B;
+ image_rect.height = thumb_rect.height - B;
+ image_rect.x = thumb_rect.x + (thumb_rect.width - image_rect.width) / 2;
+ image_rect.y = thumb_rect.y + (thumb_rect.height - image_rect.height) / 2;
+ gdk_cairo_rectangle (cr, &image_rect);
+ cairo_fill (cr);
+ }
+
+ pixbuf = self->priv->thumbnail;
+
+ if (pixbuf != NULL) {
+ if ((flags & (GTK_CELL_RENDERER_SELECTED|GTK_CELL_RENDERER_PRELIT)) != 0) {
+ colorized = create_colorized_pixbuf (pixbuf, &widget->style->base[state]);
+ pixbuf = colorized;
+ }
+
+ image_rect.width = gdk_pixbuf_get_width (pixbuf);
+ image_rect.height = gdk_pixbuf_get_height (pixbuf);
+ image_rect.x = thumb_rect.x + (thumb_rect.width - image_rect.width) / 2;
+ image_rect.y = thumb_rect.y + (thumb_rect.height - image_rect.height) / 2;
+ gdk_cairo_set_source_pixbuf (cr, pixbuf, image_rect.x, image_rect.y);
+ gdk_cairo_rectangle (cr, &draw_rect);
+ cairo_fill (cr);
+
+ if (! self->priv->is_icon && ! gdk_pixbuf_get_has_alpha (pixbuf)) {
+ gdk_cairo_set_source_color (cr, &widget->style->dark[state]);
+ cairo_set_line_width (cr, 0.25);
+ gdk_cairo_rectangle (cr, &image_rect);
+ cairo_stroke (cr);
+ }
+ }
+
+ cairo_destroy (cr);
+
+ if (GTK_WIDGET_HAS_FOCUS (widget)
+ && ((flags & GTK_CELL_RENDERER_FOCUSED) == GTK_CELL_RENDERER_FOCUSED))
+ {
+ GtkStateType focus_state;
+
+ if ((flags & GTK_CELL_RENDERER_SELECTED) != 0)
+ focus_state = GTK_STATE_NORMAL;
+ else
+ focus_state = state;
+
+ gtk_paint_focus (widget->style,
+ window,
+ focus_state,
+ &draw_rect,
+ widget,
+ "",
+ cell_area->x + (B / 4),
+ cell_area->y + (B / 4),
+ cell_area->width - (B / 2) + 1,
+ cell_area->height - (B / 2) + 1);
+ }
+
+ if (colorized != NULL)
+ g_object_unref (colorized);
+}
+*/
+
+
+static void
+gth_cell_renderer_thumbnail_render (GtkCellRenderer *cell,
+ GdkWindow *window,
+ GtkWidget *widget,
+ GdkRectangle *background_area,
+ GdkRectangle *cell_area,
+ GdkRectangle *expose_area,
+ GtkCellRendererState flags)
+{
+ GthCellRendererThumbnail *self;
+ GtkStateType state;
+ GdkRectangle thumb_rect;
+ GdkRectangle draw_rect;
+ GdkRectangle image_rect;
+ cairo_t *cr;
+ cairo_path_t *cr_path;
+ GdkPixbuf *pixbuf;
+ GdkPixbuf *colorized = NULL;
+ int B;
+
+ self = GTH_CELL_RENDERER_THUMBNAIL (cell);
+
+ gth_cell_renderer_thumbnail_get_size (cell, widget, cell_area,
+ &thumb_rect.x,
+ &thumb_rect.y,
+ &thumb_rect.width,
+ &thumb_rect.height);
+
+ pixbuf = self->priv->thumbnail;
+ if (pixbuf == NULL)
+ return;
+
+ thumb_rect.x += cell_area->x + cell->xpad;
+ thumb_rect.y += cell_area->y + cell->ypad;
+ thumb_rect.width -= cell->xpad * 2;
+ thumb_rect.height -= cell->ypad * 2;
+
+ if (! gdk_rectangle_intersect (cell_area, &thumb_rect, &draw_rect)
+ || ! gdk_rectangle_intersect (expose_area, &thumb_rect, &draw_rect))
+ {
+ return;
+ }
+
+ if ((flags & GTK_CELL_RENDERER_SELECTED) == GTK_CELL_RENDERER_SELECTED)
+ state = GTK_WIDGET_HAS_FOCUS (widget) ? GTK_STATE_SELECTED : GTK_STATE_ACTIVE;
+ else
+ state = ((flags & GTK_CELL_RENDERER_FOCUSED) == GTK_CELL_RENDERER_FOCUSED) ? GTK_STATE_ACTIVE : GTK_STATE_NORMAL;
+
+ if (self->priv->is_icon || state != GTK_STATE_NORMAL) {
+ int R = 7;
+
+ cr = gdk_cairo_create (window);
+
+ if (state == GTK_STATE_NORMAL)
+ gdk_cairo_set_source_color (cr, &widget->style->bg[state]);
+ else
+ gdk_cairo_set_source_color (cr, &widget->style->base[state]);
+
+ cairo_move_to (cr, thumb_rect.x, thumb_rect.y + R);
+ cairo_arc (cr, thumb_rect.x + R, thumb_rect.y + R, R, 1.0 * M_PI, 1.5 * M_PI);
+ cairo_rel_line_to (cr, thumb_rect.width - (R * 2), 0);
+ cairo_arc (cr, thumb_rect.x + thumb_rect.width - R, thumb_rect.y + R, R, 1.5 * M_PI, 2.0 * M_PI);
+ cairo_rel_line_to (cr, 0, thumb_rect.height - (R * 2));
+ cairo_arc (cr, thumb_rect.x + thumb_rect.width - R, thumb_rect.y + thumb_rect.height - R, R, 0.0 * M_PI, 0.5 * M_PI);
+ cairo_rel_line_to (cr, - (thumb_rect.width - (R * 2)), 0);
+ cairo_arc (cr, thumb_rect.x + R, thumb_rect.y + thumb_rect.height - R, R, 0.5 * M_PI, 1.0 * M_PI);
+ cairo_close_path (cr);
+ cr_path = cairo_copy_path (cr);
+ cairo_fill (cr);
+
+ cairo_destroy (cr);
+ }
+
+ image_rect.width = gdk_pixbuf_get_width (pixbuf);
+ image_rect.height = gdk_pixbuf_get_height (pixbuf);
+ image_rect.x = thumb_rect.x + (thumb_rect.width - image_rect.width) * .5;
+ image_rect.y = thumb_rect.y + (thumb_rect.height - image_rect.height) * .5;
+
+ if (! self->priv->is_icon) {
+ GdkRectangle frame_rect;
+
+ if (! _g_mime_type_is_image (gth_file_data_get_mime_type (self->priv->file))) {
+ B = 1;
+ frame_rect = image_rect;
+ }
+ else {
+ B = 4;
+
+ frame_rect = thumb_rect;
+ frame_rect.width = self->priv->size;
+ frame_rect.height = self->priv->size;
+ frame_rect.x = thumb_rect.x + (thumb_rect.width - frame_rect.width) * .5;
+ frame_rect.y = thumb_rect.y + (thumb_rect.height - frame_rect.height) * .5;
+ }
+
+ gdk_draw_rectangle (GDK_DRAWABLE (window), widget->style->dark_gc[state], FALSE,
+ frame_rect.x - B,
+ frame_rect.y - B,
+ frame_rect.width + (B * 2) - 1,
+ frame_rect.height + (B * 2) - 1);
+
+ gdk_draw_line (GDK_DRAWABLE (window), widget->style->dark_gc[state],
+ frame_rect.x - (B / 2),
+ frame_rect.y + frame_rect.height + B,
+ frame_rect.x + frame_rect.width + B,
+ frame_rect.y + frame_rect.height + B);
+ gdk_draw_line (GDK_DRAWABLE (window), widget->style->dark_gc[state],
+ frame_rect.x + frame_rect.width + B,
+ frame_rect.y + frame_rect.height + B,
+ frame_rect.x + frame_rect.width + B,
+ frame_rect.y - (B / 2));
+
+ gdk_draw_line (GDK_DRAWABLE (window), widget->style->mid_gc[state],
+ frame_rect.x - (B / 2) + 1,
+ frame_rect.y + frame_rect.height + B + 1,
+ frame_rect.x + frame_rect.width + B + 1,
+ frame_rect.y + frame_rect.height + B + 1);
+ gdk_draw_line (GDK_DRAWABLE (window), widget->style->mid_gc[state],
+ frame_rect.x + frame_rect.width + B + 1,
+ frame_rect.y + frame_rect.height + B + 1,
+ frame_rect.x + frame_rect.width + B + 1,
+ frame_rect.y - (B / 2) + 1);
+
+ gdk_draw_rectangle (GDK_DRAWABLE (window), widget->style->light_gc[state], TRUE,
+ frame_rect.x - (B - 1),
+ frame_rect.y - (B - 1),
+ frame_rect.width + ((B - 1) * 2),
+ frame_rect.height + ((B - 1) * 2));
+ /*gdk_draw_rectangle (GDK_DRAWABLE (window), widget->style->mid_gc[state], FALSE,
+ image_rect.x - 1,
+ image_rect.y - 1,
+ image_rect.width + 1,
+ image_rect.height + 1);*/
+ }
+
+ if ((flags & (GTK_CELL_RENDERER_SELECTED|GTK_CELL_RENDERER_PRELIT)) != 0) {
+ colorized = create_colorized_pixbuf (pixbuf, &widget->style->base[state]);
+ pixbuf = colorized;
+ }
+
+ gdk_draw_pixbuf (GDK_DRAWABLE (window),
+ NULL,
+ pixbuf,
+ 0, 0,
+ image_rect.x,
+ image_rect.y,
+ image_rect.width,
+ image_rect.height,
+ GDK_RGB_DITHER_NORMAL,
+ 0, 0);
+
+ if (colorized != NULL)
+ g_object_unref (colorized);
+}
+
+
+static void
+gth_cell_renderer_thumbnail_class_init (GthCellRendererThumbnailClass *klass)
+{
+ GObjectClass *object_class;
+ GtkCellRendererClass *cell_renderer;
+
+ parent_class = g_type_class_peek_parent (klass);
+ object_class = G_OBJECT_CLASS (klass);
+ cell_renderer = GTK_CELL_RENDERER_CLASS (klass);
+
+ object_class->finalize = gth_cell_renderer_thumbnail_finalize;
+ object_class->get_property = gth_cell_renderer_thumbnail_get_property;
+ object_class->set_property = gth_cell_renderer_thumbnail_set_property;
+ cell_renderer->get_size = gth_cell_renderer_thumbnail_get_size;
+ cell_renderer->render = gth_cell_renderer_thumbnail_render;
+
+ /* properties */
+
+ g_object_class_install_property (object_class,
+ PROP_SIZE,
+ g_param_spec_int ("size",
+ "Size",
+ "The thumbnail size",
+ 0,
+ MAX_THUMBNAIL_SIZE,
+ DEFAULT_THUMBNAIL_SIZE,
+ G_PARAM_READWRITE));
+ g_object_class_install_property (object_class,
+ PROP_IS_ICON,
+ g_param_spec_boolean ("is_icon",
+ "Is icon",
+ "Whether the image is a file icon",
+ TRUE,
+ G_PARAM_READWRITE));
+ g_object_class_install_property (object_class,
+ PROP_THUMBNAIL,
+ g_param_spec_object ("thumbnail",
+ "Thumbnail",
+ "The image thumbnail",
+ GDK_TYPE_PIXBUF,
+ G_PARAM_READWRITE));
+ g_object_class_install_property (object_class,
+ PROP_FILE,
+ g_param_spec_object ("file",
+ "File",
+ "The file data",
+ GTH_TYPE_FILE_DATA,
+ G_PARAM_READWRITE));
+}
+
+
+static void
+gth_cell_renderer_thumbnail_init (GthCellRendererThumbnail *self)
+{
+ self->priv = g_new0 (GthCellRendererThumbnailPrivate, 1);
+ self->priv->size = DEFAULT_THUMBNAIL_SIZE;
+}
+
+
+GType
+gth_cell_renderer_thumbnail_get_type (void)
+{
+ static GType type = 0;
+
+ if (type == 0) {
+ GTypeInfo type_info = {
+ sizeof (GthCellRendererThumbnailClass),
+ NULL,
+ NULL,
+ (GClassInitFunc) gth_cell_renderer_thumbnail_class_init,
+ NULL,
+ NULL,
+ sizeof (GthCellRendererThumbnail),
+ 0,
+ (GInstanceInitFunc) gth_cell_renderer_thumbnail_init,
+ NULL
+ };
+ type = g_type_register_static (GTK_TYPE_CELL_RENDERER,
+ "GthCellRendererThumbnail",
+ &type_info,
+ 0);
+ }
+ return type;
+}
+
+
+GtkCellRenderer *
+gth_cell_renderer_thumbnail_new (void)
+{
+ return g_object_new (GTH_TYPE_CELL_RENDERER_THUMBNAIL, NULL);
+}
diff --git a/gthumb/gth-cell-renderer-thumbnail.h b/gthumb/gth-cell-renderer-thumbnail.h
new file mode 100644
index 0000000..84eecde
--- /dev/null
+++ b/gthumb/gth-cell-renderer-thumbnail.h
@@ -0,0 +1,55 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2008 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GTH_CELL_RENDERER_THUMBNAIL_H
+#define GTH_CELL_RENDERER_THUMBNAIL_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GTH_TYPE_CELL_RENDERER_THUMBNAIL (gth_cell_renderer_thumbnail_get_type ())
+#define GTH_CELL_RENDERER_THUMBNAIL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTH_TYPE_CELL_RENDERER_THUMBNAIL, GthCellRendererThumbnail))
+#define GTH_CELL_RENDERER_THUMBNAIL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTH_TYPE_CELL_RENDERER_THUMBNAIL, GthCellRendererThumbnailClass))
+#define GTH_IS_CELL_RENDERER_THUMBNAIL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTH_TYPE_CELL_RENDERER_THUMBNAIL))
+#define GTH_IS_CELL_RENDERER_THUMBNAIL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTH_TYPE_CELL_RENDERER_THUMBNAIL))
+#define GTH_CELL_RENDERER_THUMBNAIL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTH_TYPE_CELL_RENDERER_THUMBNAIL, GthCellRendererThumbnailClass))
+
+typedef struct _GthCellRendererThumbnail GthCellRendererThumbnail;
+typedef struct _GthCellRendererThumbnailClass GthCellRendererThumbnailClass;
+typedef struct _GthCellRendererThumbnailPrivate GthCellRendererThumbnailPrivate;
+
+struct _GthCellRendererThumbnail {
+ GtkCellRenderer parent_instance;
+ GthCellRendererThumbnailPrivate * priv;
+};
+
+struct _GthCellRendererThumbnailClass {
+ GtkCellRendererClass parent_class;
+};
+
+GType gth_cell_renderer_thumbnail_get_type (void);
+GtkCellRenderer * gth_cell_renderer_thumbnail_new (void);
+
+G_END_DECLS
+
+#endif /* GTH_CELL_RENDERER_THUMBNAIL_H */
diff --git a/gthumb/gth-cursors.c b/gthumb/gth-cursors.c
new file mode 100644
index 0000000..b6ac897
--- /dev/null
+++ b/gthumb/gth-cursors.c
@@ -0,0 +1,118 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2001-2008 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ *
+ * taken from :
+ *
+ * Eye of Gnome image viewer - mouse cursors
+ * Copyright (C) 2000 The Free Software Foundation
+ */
+
+#include <config.h>
+#include <gdk/gdkcolor.h>
+#include <gdk/gdkdrawable.h>
+#include "gth-cursors.h"
+
+
+/* Cursor definitions. Keep in sync with the CursorType enumeration in
+ * cursors.h.
+ */
+
+#include "cursors/void-data.xbm"
+#include "cursors/void-mask.xbm"
+#include "cursors/hand-open-data.xbm"
+#include "cursors/hand-open-mask.xbm"
+#include "cursors/hand-closed-data.xbm"
+#include "cursors/hand-closed-mask.xbm"
+
+static struct {
+ char *data;
+ char *mask;
+ int data_width;
+ int data_height;
+ int mask_width;
+ int mask_height;
+ int hot_x, hot_y;
+} cursors[] = {
+ { void_data_bits, void_mask_bits,
+ void_data_width, void_data_height,
+ void_mask_width, void_mask_height,
+ void_data_width / 2, void_data_height / 2 },
+ { hand_open_data_bits, hand_open_mask_bits,
+ hand_open_data_width, hand_open_data_height,
+ hand_open_mask_width, hand_open_mask_height,
+ hand_open_data_width / 2, hand_open_data_height / 2 },
+ { hand_closed_data_bits, hand_closed_mask_bits,
+ hand_closed_data_width, hand_closed_data_height,
+ hand_closed_mask_width, hand_closed_mask_height,
+ hand_closed_data_width / 2, hand_closed_data_height / 2 },
+ { NULL, NULL, 0, 0, 0, 0 }
+};
+
+
+/**
+ * gth_cursor_get:
+ * @window: Window whose screen and colormap determine the cursor's.
+ * @type: A cursor type.
+ *
+ * Creates a cursor.
+ *
+ * Return value: The newly-created cursor.
+ **/
+GdkCursor *
+gth_cursor_get (GdkWindow *window,
+ GthCursorType type)
+{
+ GdkBitmap *data;
+ GdkBitmap *mask;
+ GdkColor black, white;
+ GdkCursor *cursor;
+
+ g_return_val_if_fail (window != NULL, NULL);
+ g_return_val_if_fail (type < GTH_CURSOR_NUM_CURSORS, NULL);
+
+ g_assert (cursors[type].data_width == cursors[type].mask_width);
+ g_assert (cursors[type].data_height == cursors[type].mask_height);
+
+ data = gdk_bitmap_create_from_data (window,
+ cursors[type].data,
+ cursors[type].data_width,
+ cursors[type].data_height);
+ mask = gdk_bitmap_create_from_data (window,
+ cursors[type].mask,
+ cursors[type].mask_width,
+ cursors[type].mask_height);
+
+ g_assert (data != NULL && mask != NULL);
+
+ gdk_color_parse ("#000000", &black);
+ gdk_color_parse ("#FFFFFF", &white);
+
+ cursor = gdk_cursor_new_from_pixmap (data, mask,
+ &white, &black,
+ cursors[type].hot_x,
+ cursors[type].hot_y);
+ g_assert (cursor != NULL);
+
+ g_object_unref (data);
+ g_object_unref (mask);
+
+ return cursor;
+}
diff --git a/gthumb/gth-cursors.h b/gthumb/gth-cursors.h
new file mode 100644
index 0000000..f9a8e0e
--- /dev/null
+++ b/gthumb/gth-cursors.h
@@ -0,0 +1,47 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2001-2008 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ *
+ * taken from :
+ *
+ * Eye of Gnome image viewer - mouse cursors
+ * Copyright (C) 2000 The Free Software Foundation
+ */
+
+#ifndef GTH_CURSORS_H
+#define GTH_CURSORS_H
+
+#include <gdk/gdk.h>
+
+G_BEGIN_DECLS
+
+typedef enum { /*< skip >*/
+ GTH_CURSOR_VOID,
+ GTH_CURSOR_HAND_OPEN,
+ GTH_CURSOR_HAND_CLOSED,
+ GTH_CURSOR_NUM_CURSORS
+} GthCursorType;
+
+GdkCursor *gth_cursor_get (GdkWindow *window,
+ GthCursorType type);
+
+G_END_DECLS
+
+#endif /* GTH_CURSORS_H */
diff --git a/gthumb/gth-dumb-notebook.c b/gthumb/gth-dumb-notebook.c
new file mode 100644
index 0000000..b9d9619
--- /dev/null
+++ b/gthumb/gth-dumb-notebook.c
@@ -0,0 +1,270 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <gtk/gtk.h>
+#include "gth-dumb-notebook.h"
+
+
+struct _GthDumbNotebookPrivate {
+ GList *children;
+ int n_children;
+ GtkWidget *current;
+ int current_pos;
+};
+
+
+static gpointer parent_class = NULL;
+
+
+static void
+gth_dumb_notebook_finalize (GObject *object)
+{
+ GthDumbNotebook *dumb_notebook = GTH_DUMB_NOTEBOOK (object);
+
+ if (dumb_notebook->priv != NULL) {
+ g_free (dumb_notebook->priv);
+ dumb_notebook->priv = NULL;
+ }
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+
+static void
+gth_dumb_notebook_size_request (GtkWidget *widget,
+ GtkRequisition *requisition)
+{
+ GthDumbNotebook *dumb_notebook = GTH_DUMB_NOTEBOOK (widget);
+ GList *scan;
+ GtkRequisition child_requisition;
+
+ widget->requisition.width = 0;
+ widget->requisition.height = 0;
+
+ for (scan = dumb_notebook->priv->children; scan; scan = scan->next) {
+ GtkWidget *child = scan->data;
+
+ if (! GTK_WIDGET_VISIBLE (child))
+ continue;
+
+ gtk_widget_size_request (child, &child_requisition);
+
+ widget->requisition.width = MAX (widget->requisition.width,
+ child_requisition.width);
+ widget->requisition.height = MAX (widget->requisition.height,
+ child_requisition.height);
+ }
+
+ widget->requisition.width += GTK_CONTAINER (widget)->border_width * 2;
+ widget->requisition.height += GTK_CONTAINER (widget)->border_width * 2;
+}
+
+
+static void
+gth_dumb_notebook_size_allocate (GtkWidget *widget,
+ GtkAllocation *allocation)
+{
+ GthDumbNotebook *dumb_notebook = GTH_DUMB_NOTEBOOK (widget);
+ GList *scan;
+ int border_width = GTK_CONTAINER (widget)->border_width;
+ GtkAllocation child_allocation;
+
+ widget->allocation = *allocation;
+
+ child_allocation.x = widget->allocation.x + border_width;
+ child_allocation.y = widget->allocation.y + border_width;
+ child_allocation.width = MAX (1, allocation->width - border_width * 2);
+ child_allocation.height = MAX (1, allocation->height - border_width * 2);
+
+ for (scan = dumb_notebook->priv->children; scan; scan = scan->next) {
+ GtkWidget *child = scan->data;
+
+ if (GTK_WIDGET_VISIBLE (child))
+ gtk_widget_size_allocate (child, &child_allocation);
+ }
+}
+
+
+static int
+gth_dumb_notebook_expose (GtkWidget *widget,
+ GdkEventExpose *event)
+{
+ GthDumbNotebook *dumb_notebook = GTH_DUMB_NOTEBOOK (widget);
+
+ if (dumb_notebook->priv->current != NULL)
+ gtk_container_propagate_expose (GTK_CONTAINER (dumb_notebook),
+ dumb_notebook->priv->current,
+ event);
+
+ return FALSE;
+}
+
+
+static void
+gth_dumb_notebook_add (GtkContainer *container,
+ GtkWidget *child)
+{
+ GthDumbNotebook *notebook;
+
+ notebook = GTH_DUMB_NOTEBOOK (container);
+
+ gtk_widget_freeze_child_notify (child);
+
+ notebook->priv->children = g_list_append (notebook->priv->children, child);
+ gtk_widget_set_parent (child, GTK_WIDGET (notebook));
+
+ notebook->priv->n_children++;
+ if (notebook->priv->current_pos == notebook->priv->n_children - 1)
+ gtk_widget_set_child_visible (child, TRUE);
+ else
+ gtk_widget_set_child_visible (child, FALSE);
+
+ gtk_widget_thaw_child_notify (child);
+}
+
+
+static void
+gth_dumb_notebook_remove (GtkContainer *container,
+ GtkWidget *widget)
+{
+ /* FIXME */
+}
+
+
+static void
+gth_dumb_notebook_forall (GtkContainer *container,
+ gboolean include_internals,
+ GtkCallback callback,
+ gpointer callback_data)
+{
+ GthDumbNotebook *notebook;
+ GList *scan;
+
+ notebook = GTH_DUMB_NOTEBOOK (container);
+
+ for (scan = notebook->priv->children; scan; scan = scan->next)
+ (* callback) (scan->data, callback_data);
+}
+
+
+static GType
+gth_dumb_notebook_child_type (GtkContainer *container)
+{
+ return GTK_TYPE_WIDGET;
+}
+
+
+static void
+gth_dumb_notebook_class_init (GthDumbNotebookClass *klass)
+{
+ GObjectClass *gobject_class;
+ GtkWidgetClass *widget_class;
+ GtkContainerClass *container_class;
+
+ parent_class = g_type_class_peek_parent (klass);
+
+ gobject_class = G_OBJECT_CLASS (klass);
+ gobject_class->finalize = gth_dumb_notebook_finalize;
+
+ widget_class = GTK_WIDGET_CLASS (klass);
+ widget_class->size_request = gth_dumb_notebook_size_request;
+ widget_class->size_allocate = gth_dumb_notebook_size_allocate;
+ widget_class->expose_event = gth_dumb_notebook_expose;
+
+ container_class = GTK_CONTAINER_CLASS (klass);
+ container_class->add = gth_dumb_notebook_add;
+ container_class->remove = gth_dumb_notebook_remove;
+ container_class->forall = gth_dumb_notebook_forall;
+ container_class->child_type = gth_dumb_notebook_child_type;
+}
+
+
+static void
+gth_dumb_notebook_init (GthDumbNotebook *notebook)
+{
+ GTK_WIDGET_SET_FLAGS (notebook, GTK_NO_WINDOW);
+
+ notebook->priv = g_new0 (GthDumbNotebookPrivate, 1);
+ notebook->priv->n_children = 0;
+}
+
+
+GType
+gth_dumb_notebook_get_type (void)
+{
+ static GType type = 0;
+
+ if (type == 0) {
+ static const GTypeInfo g_define_type_info = {
+ sizeof (GthDumbNotebookClass),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) gth_dumb_notebook_class_init,
+ (GClassFinalizeFunc) NULL,
+ NULL,
+ sizeof (GthDumbNotebook),
+ 0,
+ (GInstanceInitFunc) gth_dumb_notebook_init,
+ NULL
+ };
+ type = g_type_register_static (GTK_TYPE_CONTAINER,
+ "GthDumbNotebook",
+ &g_define_type_info,
+ 0);
+ }
+
+ return type;
+}
+
+
+GtkWidget *
+gth_dumb_notebook_new (void)
+{
+ return g_object_new (GTH_TYPE_DUMB_NOTEBOOK, NULL);
+}
+
+
+void
+gth_dumb_notebook_show_child (GthDumbNotebook *notebook,
+ int pos)
+{
+ GList *link;
+
+ if (notebook->priv->current_pos == pos)
+ return;
+
+ if (notebook->priv->current)
+ gtk_widget_set_child_visible (notebook->priv->current, FALSE);
+
+ notebook->priv->current = NULL;
+ notebook->priv->current_pos = pos;
+
+ link = g_list_nth (notebook->priv->children, pos);
+ if (link == NULL)
+ return;
+
+ notebook->priv->current = link->data;
+ gtk_widget_set_child_visible (notebook->priv->current, TRUE);
+
+ gtk_widget_queue_resize (GTK_WIDGET (notebook));
+}
diff --git a/gthumb/gth-dumb-notebook.h b/gthumb/gth-dumb-notebook.h
new file mode 100644
index 0000000..801eb4d
--- /dev/null
+++ b/gthumb/gth-dumb-notebook.h
@@ -0,0 +1,59 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GTH_DUMB_NOTEBOOK_H
+#define GTH_DUMB_NOTEBOOK_H
+
+#include <glib.h>
+#include <glib-object.h>
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GTH_TYPE_DUMB_NOTEBOOK (gth_dumb_notebook_get_type ())
+#define GTH_DUMB_NOTEBOOK(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTH_TYPE_DUMB_NOTEBOOK, GthDumbNotebook))
+#define GTH_DUMB_NOTEBOOK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTH_TYPE_DUMB_NOTEBOOK, GthDumbNotebookClass))
+#define GTH_IS_DUMB_NOTEBOOK(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTH_TYPE_DUMB_NOTEBOOK))
+#define GTH_IS_DUMB_NOTEBOOK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTH_TYPE_DUMB_NOTEBOOK))
+#define GTH_DUMB_NOTEBOOK_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTH_TYPE_DUMB_NOTEBOOK, GthDumbNotebookClass))
+
+typedef struct _GthDumbNotebook GthDumbNotebook;
+typedef struct _GthDumbNotebookClass GthDumbNotebookClass;
+typedef struct _GthDumbNotebookPrivate GthDumbNotebookPrivate;
+
+struct _GthDumbNotebook {
+ GtkContainer parent_instance;
+ GthDumbNotebookPrivate *priv;
+};
+
+struct _GthDumbNotebookClass {
+ GtkContainerClass parent_class;
+};
+
+GType gth_dumb_notebook_get_type (void);
+GtkWidget * gth_dumb_notebook_new (void);
+void gth_dumb_notebook_show_child (GthDumbNotebook *notebook,
+ int pos);
+
+G_END_DECLS
+
+#endif /* GTH_DUMB_NOTEBOOK_H */
diff --git a/gthumb/gth-duplicable.c b/gthumb/gth-duplicable.c
new file mode 100644
index 0000000..64a01d6
--- /dev/null
+++ b/gthumb/gth-duplicable.c
@@ -0,0 +1,57 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2008 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include "gth-duplicable.h"
+
+
+GObject *
+gth_duplicable_duplicate (GthDuplicable *self)
+{
+ return GTH_DUPLICABLE_GET_INTERFACE (self)->duplicate (self);
+}
+
+
+GType
+gth_duplicable_get_type (void)
+{
+ static GType type_id = 0;
+ if (type_id == 0) {
+ static const GTypeInfo g_define_type_info = {
+ sizeof (GthDuplicableIface),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) NULL,
+ (GClassFinalizeFunc) NULL,
+ NULL,
+ 0,
+ 0,
+ (GInstanceInitFunc) NULL,
+ NULL
+ };
+ type_id = g_type_register_static (G_TYPE_INTERFACE,
+ "GthDuplicable",
+ &g_define_type_info,
+ 0);
+ }
+ return type_id;
+}
diff --git a/gthumb/gth-duplicable.h b/gthumb/gth-duplicable.h
new file mode 100644
index 0000000..23da4d6
--- /dev/null
+++ b/gthumb/gth-duplicable.h
@@ -0,0 +1,50 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2008-2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GTH_DUPLICABLE_H
+#define GTH_DUPLICABLE_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define GTH_TYPE_DUPLICABLE (gth_duplicable_get_type ())
+#define GTH_DUPLICABLE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTH_TYPE_DUPLICABLE, GthDuplicable))
+#define GTH_IS_DUPLICABLE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTH_TYPE_DUPLICABLE))
+#define GTH_DUPLICABLE_GET_INTERFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), GTH_TYPE_DUPLICABLE, GthDuplicableIface))
+
+typedef struct _GthDuplicable GthDuplicable;
+typedef struct _GthDuplicableIface GthDuplicableIface;
+
+struct _GthDuplicableIface {
+ GTypeInterface parent_iface;
+
+ GObject * (*duplicate) (GthDuplicable *self);
+};
+
+GType gth_duplicable_get_type (void);
+GObject * gth_duplicable_duplicate (GthDuplicable *self);
+
+G_END_DECLS
+
+#endif /* GTH_DUPLICABLE_H */
diff --git a/gthumb/gth-edit-metadata-dialog.c b/gthumb/gth-edit-metadata-dialog.c
new file mode 100644
index 0000000..189e52f
--- /dev/null
+++ b/gthumb/gth-edit-metadata-dialog.c
@@ -0,0 +1,220 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <glib/gi18n.h>
+#include "gth-edit-metadata-dialog.h"
+#include "gth-main.h"
+
+
+#define GTH_EDIT_METADATA_DIALOG_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GTH_TYPE_EDIT_METADATA_DIALOG, GthEditMetadataDialogPrivate))
+
+
+static gpointer gth_edit_metadata_dialog_parent_class = NULL;
+
+
+struct _GthEditMetadataDialogPrivate {
+ GtkWidget *notebook;
+};
+
+
+static void
+gth_edit_metadata_dialog_class_init (GthEditMetadataDialogClass *klass)
+{
+ gth_edit_metadata_dialog_parent_class = g_type_class_peek_parent (klass);
+ g_type_class_add_private (klass, sizeof (GthEditMetadataDialogPrivate));
+}
+
+
+static void
+gth_edit_metadata_dialog_init (GthEditMetadataDialog *edit_metadata_dialog)
+{
+ edit_metadata_dialog->priv = GTH_EDIT_METADATA_DIALOG_GET_PRIVATE (edit_metadata_dialog);
+}
+
+
+GType
+gth_edit_metadata_dialog_get_type (void)
+{
+ static GType type = 0;
+
+ if (type == 0) {
+ static const GTypeInfo g_define_type_info = {
+ sizeof (GthEditMetadataDialogClass),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) gth_edit_metadata_dialog_class_init,
+ (GClassFinalizeFunc) NULL,
+ NULL,
+ sizeof (GthEditMetadataDialog),
+ 0,
+ (GInstanceInitFunc) gth_edit_metadata_dialog_init,
+ NULL
+ };
+ type = g_type_register_static (GTK_TYPE_DIALOG,
+ "GthEditMetadataDialog",
+ &g_define_type_info,
+ 0);
+ }
+
+ return type;
+}
+
+
+static void
+gth_edit_metadata_dialog_construct (GthEditMetadataDialog *self)
+{
+ GArray *pages;
+ int i;
+
+ gtk_window_set_resizable (GTK_WINDOW (self), TRUE);
+ gtk_dialog_set_has_separator (GTK_DIALOG (self), FALSE);
+ gtk_box_set_spacing (GTK_BOX (GTK_DIALOG (self)->vbox), 5);
+ gtk_container_set_border_width (GTK_CONTAINER (self), 5);
+
+ gtk_dialog_add_button (GTK_DIALOG (self), GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL);
+ gtk_dialog_add_button (GTK_DIALOG (self), GTK_STOCK_SAVE, GTK_RESPONSE_OK);
+
+ self->priv->notebook = gtk_notebook_new ();
+ gtk_widget_show (self->priv->notebook);
+ gtk_container_set_border_width (GTK_CONTAINER (self->priv->notebook), 5);
+ gtk_box_pack_start (GTK_BOX (GTK_DIALOG (self)->vbox), self->priv->notebook, TRUE, TRUE, 0);
+
+ pages = gth_main_get_type_set ("edit-metadata-dialog-page");
+ if (pages == NULL)
+ return;
+
+ for (i = 0; i < pages->len; i++) {
+ GType page_type;
+ GtkWidget *page;
+
+ page_type = g_array_index (pages, GType, i);
+ page = g_object_new (page_type, NULL);
+ if (! GTH_IS_EDIT_METADATA_PAGE (page)) {
+ g_object_unref (page);
+ continue;
+ }
+
+ gtk_widget_show (page);
+ gtk_notebook_append_page (GTK_NOTEBOOK (self->priv->notebook),
+ page,
+ gtk_label_new (gth_edit_metadata_page_get_name (GTH_EDIT_METADATA_PAGE (page))));
+ }
+}
+
+
+GtkWidget *
+gth_edit_metadata_dialog_new (void)
+{
+ GthEditMetadataDialog *self;
+
+ self = g_object_new (GTH_TYPE_EDIT_METADATA_DIALOG, NULL);
+ gth_edit_metadata_dialog_construct (self);
+
+ return (GtkWidget *) self;
+}
+
+
+void
+gth_edit_metadata_dialog_set_file (GthEditMetadataDialog *dialog,
+ GthFileData *file)
+{
+ char *title;
+ GList *pages;
+ GList *scan;
+
+ if (file == NULL)
+ return;
+
+ title = g_strdup_printf (_("%s Properties"), g_file_info_get_display_name (file->info));
+ gtk_window_set_title (GTK_WINDOW (dialog), title);
+
+ pages = gtk_container_get_children (GTK_CONTAINER (dialog->priv->notebook));
+ for (scan = pages; scan; scan = scan->next)
+ gth_edit_metadata_page_set_file (GTH_EDIT_METADATA_PAGE (scan->data), file);
+
+ g_list_free (pages);
+ g_free (title);
+}
+
+
+void
+gth_edit_metadata_dialog_update_info (GthEditMetadataDialog *dialog,
+ GFileInfo *info)
+{
+ GList *pages;
+ GList *scan;
+
+ pages = gtk_container_get_children (GTK_CONTAINER (dialog->priv->notebook));
+ for (scan = pages; scan; scan = scan->next)
+ gth_edit_metadata_page_update_info (GTH_EDIT_METADATA_PAGE (scan->data), info);
+
+ g_list_free (pages);
+}
+
+
+/* -- gth_edit_metadata_dialog_page -- */
+
+
+GType
+gth_edit_metadata_page_get_type (void) {
+ static GType gth_edit_metadata_page_type_id = 0;
+ if (gth_edit_metadata_page_type_id == 0) {
+ static const GTypeInfo g_define_type_info = {
+ sizeof (GthEditMetadataPageIface),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) NULL,
+ (GClassFinalizeFunc) NULL,
+ NULL,
+ 0,
+ 0,
+ (GInstanceInitFunc) NULL,
+ NULL
+ };
+ gth_edit_metadata_page_type_id = g_type_register_static (G_TYPE_INTERFACE, "GthEditMetadataPageIface", &g_define_type_info, 0);
+ }
+ return gth_edit_metadata_page_type_id;
+}
+
+
+void
+gth_edit_metadata_page_set_file (GthEditMetadataPage *self,
+ GthFileData *file_data)
+{
+ GTH_EDIT_METADATA_PAGE_GET_INTERFACE (self)->set_file (self, file_data);
+}
+
+
+void
+gth_edit_metadata_page_update_info (GthEditMetadataPage *self,
+ GFileInfo *info)
+{
+ GTH_EDIT_METADATA_PAGE_GET_INTERFACE (self)->update_info (self, info);
+}
+
+
+const char *
+gth_edit_metadata_page_get_name (GthEditMetadataPage *self)
+{
+ return GTH_EDIT_METADATA_PAGE_GET_INTERFACE (self)->get_name (self);
+}
diff --git a/gthumb/gth-edit-metadata-dialog.h b/gthumb/gth-edit-metadata-dialog.h
new file mode 100644
index 0000000..fa272cc
--- /dev/null
+++ b/gthumb/gth-edit-metadata-dialog.h
@@ -0,0 +1,84 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GTH_EDIT_METADATA_DIALOG_H
+#define GTH_EDIT_METADATA_DIALOG_H
+
+#include <gtk/gtk.h>
+#include "gth-file-data.h"
+
+G_BEGIN_DECLS
+
+#define GTH_TYPE_EDIT_METADATA_DIALOG (gth_edit_metadata_dialog_get_type ())
+#define GTH_EDIT_METADATA_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTH_TYPE_EDIT_METADATA_DIALOG, GthEditMetadataDialog))
+#define GTH_EDIT_METADATA_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTH_TYPE_EDIT_METADATA_DIALOG, GthEditMetadataDialogClass))
+#define GTH_IS_EDIT_METADATA_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTH_TYPE_EDIT_METADATA_DIALOG))
+#define GTH_IS_EDIT_METADATA_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTH_TYPE_EDIT_METADATA_DIALOG))
+#define GTH_EDIT_METADATA_DIALOG_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTH_TYPE_EDIT_METADATA_DIALOG, GthEditMetadataDialogClass))
+
+#define GTH_TYPE_EDIT_METADATA_PAGE (gth_edit_metadata_page_get_type ())
+#define GTH_EDIT_METADATA_PAGE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTH_TYPE_EDIT_METADATA_PAGE, GthEditMetadataPage))
+#define GTH_IS_EDIT_METADATA_PAGE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTH_TYPE_EDIT_METADATA_PAGE))
+#define GTH_EDIT_METADATA_PAGE_GET_INTERFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), GTH_TYPE_EDIT_METADATA_PAGE, GthEditMetadataPageIface))
+
+typedef struct _GthEditMetadataDialog GthEditMetadataDialog;
+typedef struct _GthEditMetadataDialogClass GthEditMetadataDialogClass;
+typedef struct _GthEditMetadataDialogPrivate GthEditMetadataDialogPrivate;
+
+struct _GthEditMetadataDialog {
+ GtkDialog parent_instance;
+ GthEditMetadataDialogPrivate *priv;
+};
+
+struct _GthEditMetadataDialogClass {
+ GtkDialogClass parent_class;
+};
+
+typedef struct _GthEditMetadataPage GthEditMetadataPage;
+typedef struct _GthEditMetadataPageIface GthEditMetadataPageIface;
+
+struct _GthEditMetadataPageIface {
+ GTypeInterface parent_iface;
+ void (*set_file) (GthEditMetadataPage *self,
+ GthFileData *file_data);
+ void (*update_info) (GthEditMetadataPage *self,
+ GFileInfo *info);
+ const char * (*get_name) (GthEditMetadataPage *self);
+};
+
+GType gth_edit_metadata_dialog_get_type (void);
+GtkWidget * gth_edit_metadata_dialog_new (void);
+void gth_edit_metadata_dialog_set_file (GthEditMetadataDialog *dialog,
+ GthFileData *file);
+void gth_edit_metadata_dialog_update_info (GthEditMetadataDialog *dialog,
+ GFileInfo *info);
+
+GType gth_edit_metadata_page_get_type (void);
+void gth_edit_metadata_page_set_file (GthEditMetadataPage *self,
+ GthFileData *file_data);
+void gth_edit_metadata_page_update_info (GthEditMetadataPage *self,
+ GFileInfo *info);
+const char * gth_edit_metadata_page_get_name (GthEditMetadataPage *self);
+
+G_END_DECLS
+
+#endif /* GTH_EDIT_METADATA_DIALOG_H */
diff --git a/gthumb/gth-embedded-dialog.c b/gthumb/gth-embedded-dialog.c
new file mode 100644
index 0000000..1a71622
--- /dev/null
+++ b/gthumb/gth-embedded-dialog.c
@@ -0,0 +1,213 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 The Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <gtk/gtk.h>
+#include "gth-embedded-dialog.h"
+
+
+static gpointer parent_class = NULL;
+
+
+struct _GthEmbeddedDialogPrivate {
+ GtkWidget *icon_image;
+ GtkWidget *primary_text_label;
+ GtkWidget *secondary_text_label;
+};
+
+
+static void
+gth_embedded_dialog_finalize (GObject *object)
+{
+ GthEmbeddedDialog *dialog;
+
+ dialog = GTH_EMBEDDED_DIALOG (object);
+
+ if (dialog->priv != NULL) {
+ dialog->priv = NULL;
+ }
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gth_embedded_dialog_class_init (GthEmbeddedDialogClass *class)
+{
+ GObjectClass *object_class;
+
+ parent_class = g_type_class_peek_parent (class);
+ object_class = (GObjectClass*) class;
+
+ object_class->finalize = gth_embedded_dialog_finalize;
+}
+
+
+static void
+gth_embedded_dialog_init (GthEmbeddedDialog *dialog)
+{
+ dialog->priv = g_new0 (GthEmbeddedDialogPrivate, 1);
+}
+
+
+GType
+gth_embedded_dialog_get_type (void)
+{
+ static GType type = 0;
+
+ if (! type) {
+ GTypeInfo type_info = {
+ sizeof (GthEmbeddedDialogClass),
+ NULL,
+ NULL,
+ (GClassInitFunc) gth_embedded_dialog_class_init,
+ NULL,
+ NULL,
+ sizeof (GthEmbeddedDialog),
+ 0,
+ (GInstanceInitFunc) gth_embedded_dialog_init
+ };
+
+ type = g_type_register_static (GEDIT_TYPE_MESSAGE_AREA,
+ "GthEmbeddedEditorDialog",
+ &type_info,
+ 0);
+ }
+
+ return type;
+}
+
+
+static void
+gth_embedded_dialog_construct (GthEmbeddedDialog *self)
+{
+ GtkWidget *hbox_content;
+ GtkWidget *image;
+ GtkWidget *vbox;
+ GtkWidget *primary_label;
+ GtkWidget *secondary_label;
+
+ hbox_content = gtk_hbox_new (FALSE, 8);
+ gtk_widget_show (hbox_content);
+
+ self->priv->icon_image = image = gtk_image_new ();
+ 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);
+
+ self->priv->primary_text_label = primary_label = gtk_label_new (NULL);
+ 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), TRUE);
+ gtk_misc_set_alignment (GTK_MISC (primary_label), 0, 0.5);
+ GTK_WIDGET_SET_FLAGS (primary_label, GTK_CAN_FOCUS);
+ gtk_label_set_selectable (GTK_LABEL (primary_label), TRUE);
+
+
+ self->priv->secondary_text_label = secondary_label = gtk_label_new (NULL);
+ gtk_box_pack_start (GTK_BOX (vbox), secondary_label, TRUE, TRUE, 0);
+ GTK_WIDGET_SET_FLAGS (secondary_label, GTK_CAN_FOCUS);
+ 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_label_set_ellipsize (GTK_LABEL (secondary_label), PANGO_ELLIPSIZE_END);
+ gtk_misc_set_alignment (GTK_MISC (secondary_label), 0, 0.5);
+
+ gedit_message_area_set_contents (GEDIT_MESSAGE_AREA (self), hbox_content);
+}
+
+
+GtkWidget *
+gth_embedded_dialog_new (const char *icon_stock_id,
+ const char *primary_text,
+ const char *secondary_text)
+{
+ GthEmbeddedDialog *self;
+
+ self = g_object_new (GTH_TYPE_EMBEDDED_DIALOG, NULL);
+ gth_embedded_dialog_construct (self);
+ gth_embedded_dialog_set_icon (self, icon_stock_id);
+ gth_embedded_dialog_set_primary_text (self, primary_text);
+ gth_embedded_dialog_set_secondary_text (self, secondary_text);
+
+ return (GtkWidget *) self;
+}
+
+
+void
+gth_embedded_dialog_set_icon (GthEmbeddedDialog *dialog,
+ const char *icon_stock_id)
+{
+ if (icon_stock_id == NULL) {
+ gtk_widget_hide (dialog->priv->icon_image);
+ return;
+ }
+
+ gtk_image_set_from_stock (GTK_IMAGE (dialog->priv->icon_image), icon_stock_id, GTK_ICON_SIZE_DIALOG);
+ gtk_widget_show (dialog->priv->icon_image);
+}
+
+
+void
+gth_embedded_dialog_set_primary_text (GthEmbeddedDialog *dialog,
+ const char *text)
+{
+ char *escaped;
+ char *markup;
+
+ if (text == NULL) {
+ gtk_widget_hide (dialog->priv->primary_text_label);
+ return;
+ }
+
+ escaped = g_markup_escape_text (text, -1);
+ markup = g_strdup_printf ("<b>%s</b>", escaped);
+ gtk_label_set_markup (GTK_LABEL (dialog->priv->primary_text_label), markup);
+ gtk_widget_show (dialog->priv->primary_text_label);
+
+ g_free (markup);
+ g_free (escaped);
+}
+
+
+void
+gth_embedded_dialog_set_secondary_text (GthEmbeddedDialog *dialog,
+ const char *text)
+{
+ char *escaped;
+ char *markup;
+
+ if (text == NULL) {
+ gtk_widget_hide (dialog->priv->secondary_text_label);
+ return;
+ }
+
+ escaped = g_markup_escape_text (text, -1);
+ markup = g_strdup_printf ("<small>%s</small>", escaped);
+ gtk_label_set_markup (GTK_LABEL (dialog->priv->secondary_text_label), markup);
+ gtk_widget_show (dialog->priv->secondary_text_label);
+
+ g_free (markup);
+ g_free (escaped);
+}
diff --git a/gthumb/gth-embedded-dialog.h b/gthumb/gth-embedded-dialog.h
new file mode 100644
index 0000000..05f1230
--- /dev/null
+++ b/gthumb/gth-embedded-dialog.h
@@ -0,0 +1,66 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GTH_EMBEDDED_DIALOG_H
+#define GTH_EMBEDDED_DIALOG_H
+
+#include <gtk/gtk.h>
+#include "gedit-message-area.h"
+
+G_BEGIN_DECLS
+
+#define GTH_TYPE_EMBEDDED_DIALOG (gth_embedded_dialog_get_type ())
+#define GTH_EMBEDDED_DIALOG(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GTH_TYPE_EMBEDDED_DIALOG, GthEmbeddedDialog))
+#define GTH_EMBEDDED_DIALOG_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), GTH_TYPE_EMBEDDED_DIALOG, GthEmbeddedDialogClass))
+#define GTH_IS_EMBEDDED_DIALOG(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GTH_TYPE_EMBEDDED_DIALOG))
+#define GTH_IS_EMBEDDED_DIALOG_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GTH_TYPE_EMBEDDED_DIALOG))
+#define GTH_EMBEDDED_DIALOG_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS((o), GTH_TYPE_EMBEDDED_DIALOG, GthEmbeddedDialogClass))
+
+typedef struct _GthEmbeddedDialog GthEmbeddedDialog;
+typedef struct _GthEmbeddedDialogPrivate GthEmbeddedDialogPrivate;
+typedef struct _GthEmbeddedDialogClass GthEmbeddedDialogClass;
+
+struct _GthEmbeddedDialog
+{
+ GeditMessageArea __parent;
+ GthEmbeddedDialogPrivate *priv;
+};
+
+struct _GthEmbeddedDialogClass
+{
+ GeditMessageAreaClass __parent_class;
+};
+
+GType gth_embedded_dialog_get_type (void) G_GNUC_CONST;
+GtkWidget * gth_embedded_dialog_new (const char *icon_stock_id,
+ const char *primary_text,
+ const char *secondary_text);
+void gth_embedded_dialog_set_icon (GthEmbeddedDialog *dialog,
+ const char *icon_stock_id);
+void gth_embedded_dialog_set_primary_text (GthEmbeddedDialog *dialog,
+ const char *primary_text);
+void gth_embedded_dialog_set_secondary_text (GthEmbeddedDialog *dialog,
+ const char *secondary_text);
+
+G_END_DECLS
+
+#endif /* GTH_EMBEDDED_DIALOG_H */
diff --git a/gthumb/gth-empty-list.c b/gthumb/gth-empty-list.c
new file mode 100644
index 0000000..064a02e
--- /dev/null
+++ b/gthumb/gth-empty-list.c
@@ -0,0 +1,373 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2008 The Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include "gth-empty-list.h"
+
+/* Properties */
+enum {
+ PROP_0,
+ PROP_TEXT
+};
+
+struct _GthEmptyListPrivate {
+ GdkWindow *bin_window;
+ char *text;
+ PangoLayout *layout;
+};
+
+static gpointer parent_class = NULL;
+
+
+static void
+gth_empty_list_finalize (GObject *obj)
+{
+ GthEmptyList *self;
+
+ self = GTH_EMPTY_LIST (obj);
+
+ if (self->priv != NULL) {
+ g_free (self->priv->text);
+ g_free (self->priv);
+ }
+
+ G_OBJECT_CLASS (parent_class)->finalize (obj);
+}
+
+
+static void
+gth_empty_list_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GthEmptyList *self;
+
+ self = GTH_EMPTY_LIST (object);
+
+ switch (property_id) {
+ case PROP_TEXT:
+ g_free (self->priv->text);
+ self->priv->text = g_value_dup_string (value);
+ gtk_widget_queue_resize (GTK_WIDGET (self));
+ break;
+ default:
+ break;
+ }
+}
+
+
+static void
+gth_empty_list_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GthEmptyList *self;
+
+ self = GTH_EMPTY_LIST (object);
+
+ switch (property_id) {
+ case PROP_TEXT:
+ g_value_set_string (value, self->priv->text);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+
+static void
+gth_empty_list_realize (GtkWidget *widget)
+{
+ GthEmptyList *self;
+ GdkWindowAttr attributes;
+ int attributes_mask;
+
+ g_return_if_fail (GTH_IS_EMPTY_LIST (widget));
+
+ GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
+
+ /**/
+
+ attributes.window_type = GDK_WINDOW_CHILD;
+ attributes.x = widget->allocation.x;
+ attributes.y = widget->allocation.y;
+ attributes.width = widget->allocation.width;
+ attributes.height = widget->allocation.height;
+ attributes.wclass = GDK_INPUT_OUTPUT;
+ attributes.visual = gtk_widget_get_visual (widget);
+ attributes.colormap = gtk_widget_get_colormap (widget);
+ attributes.event_mask = GDK_VISIBILITY_NOTIFY_MASK;
+ attributes_mask = (GDK_WA_X
+ | GDK_WA_Y
+ | GDK_WA_VISUAL
+ | GDK_WA_COLORMAP);
+ widget->window = gdk_window_new (gtk_widget_get_parent_window (widget),
+ &attributes,
+ attributes_mask);
+ gdk_window_set_user_data (widget->window, widget);
+
+ /**/
+
+ self = (GthEmptyList*) widget;
+
+ attributes.x = 0;
+ attributes.y = 0;
+ attributes.width = widget->allocation.width;
+ attributes.height = widget->allocation.height;
+ attributes.event_mask = (GDK_EXPOSURE_MASK
+ | GDK_SCROLL_MASK
+ | GDK_POINTER_MOTION_MASK
+ | GDK_ENTER_NOTIFY_MASK
+ | GDK_LEAVE_NOTIFY_MASK
+ | GDK_BUTTON_PRESS_MASK
+ | GDK_BUTTON_RELEASE_MASK
+ | gtk_widget_get_events (widget));
+
+ self->priv->bin_window = gdk_window_new (widget->window,
+ &attributes,
+ attributes_mask);
+ gdk_window_set_user_data (self->priv->bin_window, widget);
+
+ /* Style */
+
+ widget->style = gtk_style_attach (widget->style, widget->window);
+ gdk_window_set_background (widget->window, &widget->style->base[widget->state]);
+ gdk_window_set_background (self->priv->bin_window, &widget->style->base[widget->state]);
+
+ /* 'No Image' message Layout */
+
+ if (self->priv->layout != NULL)
+ g_object_unref (self->priv->layout);
+
+ self->priv->layout = gtk_widget_create_pango_layout (widget, NULL);
+ pango_layout_set_wrap (self->priv->layout, PANGO_WRAP_WORD_CHAR);
+ pango_layout_set_font_description (self->priv->layout, widget->style->font_desc);
+ pango_layout_set_alignment (self->priv->layout, PANGO_ALIGN_CENTER);
+}
+
+
+static void
+gth_empty_list_unrealize (GtkWidget *widget)
+{
+ GthEmptyList *self;
+
+ g_return_if_fail (GTH_IS_EMPTY_LIST (widget));
+
+ self = (GthEmptyList*) widget;
+
+ gdk_window_set_user_data (self->priv->bin_window, NULL);
+ gdk_window_destroy (self->priv->bin_window);
+ self->priv->bin_window = NULL;
+
+ if (self->priv->layout != NULL) {
+ g_object_unref (self->priv->layout);
+ self->priv->layout = NULL;
+ }
+
+ (* GTK_WIDGET_CLASS (parent_class)->unrealize) (widget);
+}
+
+
+static void
+gth_empty_list_map (GtkWidget *widget)
+{
+ GthEmptyList *self = (GthEmptyList*) widget;
+
+ GTK_WIDGET_SET_FLAGS (widget, GTK_MAPPED);
+
+ gdk_window_show (self->priv->bin_window);
+ gdk_window_show (widget->window);
+}
+
+
+static void
+gth_empty_list_unmap (GtkWidget *widget)
+{
+ GthEmptyList *self = (GthEmptyList*) widget;
+
+ GTK_WIDGET_UNSET_FLAGS (widget, GTK_MAPPED);
+
+ gdk_window_hide (self->priv->bin_window);
+ gdk_window_hide (widget->window);
+}
+
+
+static void
+gth_empty_list_size_allocate (GtkWidget *widget,
+ GtkAllocation *allocation)
+{
+ GthEmptyList *self = (GthEmptyList*) widget;
+
+ widget->allocation = *allocation;
+
+ if (GTK_WIDGET_REALIZED (widget)) {
+ gdk_window_move_resize (widget->window,
+ allocation->x,
+ allocation->y,
+ allocation->width,
+ allocation->height);
+ gdk_window_invalidate_rect (self->priv->bin_window,
+ NULL,
+ FALSE);
+ }
+}
+
+
+static gboolean
+gth_empty_list_expose_event (GtkWidget *widget,
+ GdkEventExpose *event)
+{
+ GthEmptyList *self = (GthEmptyList*) widget;
+ PangoRectangle bounds;
+
+ if (event->window != self->priv->bin_window)
+ return FALSE;
+
+ if (self->priv->text == NULL)
+ return TRUE;
+
+ pango_layout_set_width (self->priv->layout, widget->allocation.width * PANGO_SCALE);
+ pango_layout_set_text (self->priv->layout, self->priv->text, strlen (self->priv->text));
+ pango_layout_get_pixel_extents (self->priv->layout, NULL, &bounds);
+
+ gdk_draw_layout (self->priv->bin_window,
+ widget->style->text_gc[GTK_WIDGET_STATE (widget)],
+ 0,
+ (widget->allocation.height - bounds.height) / 2,
+ self->priv->layout);
+
+ if (GTK_WIDGET_HAS_FOCUS (widget)) {
+ gtk_paint_focus (widget->style,
+ self->priv->bin_window,
+ GTK_WIDGET_STATE (widget),
+ &event->area,
+ widget,
+ NULL,
+ 1, 1,
+ widget->allocation.width - 2,
+ widget->allocation.height - 2);
+ }
+
+ return FALSE;
+}
+
+
+static int
+gth_empty_list_button_press (GtkWidget *widget,
+ GdkEventButton *event)
+{
+ GthEmptyList *self = (GthEmptyList*) widget;
+
+ if (event->window == self->priv->bin_window)
+ if (! GTK_WIDGET_HAS_FOCUS (widget))
+ gtk_widget_grab_focus (widget);
+
+ return FALSE;
+}
+
+
+static void
+gth_empty_list_class_init (GthEmptyListClass *klass)
+{
+ GObjectClass *object_class;
+ GtkWidgetClass *widget_class;
+
+ parent_class = g_type_class_peek_parent (klass);
+ object_class = (GObjectClass*) (klass);
+ widget_class = (GtkWidgetClass*) klass;
+
+ object_class->set_property = gth_empty_list_set_property;
+ object_class->get_property = gth_empty_list_get_property;
+ object_class->finalize = gth_empty_list_finalize;
+
+ widget_class->realize = gth_empty_list_realize;
+ widget_class->unrealize = gth_empty_list_unrealize;
+ widget_class->map = gth_empty_list_map;
+ widget_class->unmap = gth_empty_list_unmap;
+ widget_class->size_allocate = gth_empty_list_size_allocate;
+ widget_class->expose_event = gth_empty_list_expose_event;
+ widget_class->button_press_event = gth_empty_list_button_press;
+
+ /* properties */
+
+ g_object_class_install_property (object_class,
+ PROP_TEXT,
+ g_param_spec_string ("text",
+ "Text",
+ "The text to display",
+ NULL,
+ G_PARAM_READWRITE));
+}
+
+
+static void
+gth_empty_list_instance_init (GthEmptyList *self)
+{
+ self->priv = g_new0 (GthEmptyListPrivate, 1);
+
+ GTK_WIDGET_SET_FLAGS (self, GTK_CAN_FOCUS);
+}
+
+
+GType
+gth_empty_list_get_type (void)
+{
+ static GType type = 0;
+
+ if (type == 0) {
+ static const GTypeInfo g_define_type_info = {
+ sizeof (GthEmptyListClass),
+ NULL,
+ NULL,
+ (GClassInitFunc) gth_empty_list_class_init,
+ NULL,
+ NULL,
+ sizeof (GthEmptyList),
+ 0,
+ (GInstanceInitFunc) gth_empty_list_instance_init,
+ NULL
+ };
+ type = g_type_register_static (GTK_TYPE_VBOX,
+ "GthEmptyList",
+ &g_define_type_info,
+ 0);
+ }
+
+ return type;
+}
+
+
+GtkWidget *
+gth_empty_list_new (const char *text)
+{
+ return g_object_new (GTH_TYPE_EMPTY_LIST, "text", text, NULL);
+}
+
+
+void
+gth_empty_list_set_text (GthEmptyList *self,
+ const char *text)
+{
+ g_object_set (self, "text", text, NULL);
+}
diff --git a/gthumb/gth-empty-list.h b/gthumb/gth-empty-list.h
new file mode 100644
index 0000000..9167e6a
--- /dev/null
+++ b/gthumb/gth-empty-list.h
@@ -0,0 +1,61 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2008 The Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GTH_EMPTY_LIST_H
+#define GTH_EMPTY_LIST_H
+
+#include <glib.h>
+#include <glib-object.h>
+#include <gtk/gtk.h>
+#include <stdlib.h>
+#include <string.h>
+
+G_BEGIN_DECLS
+
+#define GTH_TYPE_EMPTY_LIST (gth_empty_list_get_type ())
+#define GTH_EMPTY_LIST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTH_TYPE_EMPTY_LIST, GthEmptyList))
+#define GTH_EMPTY_LIST_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTH_TYPE_EMPTY_LIST, GthEmptyListClass))
+#define GTH_IS_EMPTY_LIST(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTH_TYPE_EMPTY_LIST))
+#define GTH_IS_EMPTY_LIST_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTH_TYPE_EMPTY_LIST))
+#define GTH_EMPTY_LIST_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTH_TYPE_EMPTY_LIST, GthEmptyListClass))
+
+typedef struct _GthEmptyList GthEmptyList;
+typedef struct _GthEmptyListClass GthEmptyListClass;
+typedef struct _GthEmptyListPrivate GthEmptyListPrivate;
+
+struct _GthEmptyList {
+ GtkVBox parent_instance;
+ GthEmptyListPrivate * priv;
+};
+
+struct _GthEmptyListClass {
+ GtkVBoxClass parent_class;
+};
+
+GType gth_empty_list_get_type (void);
+GtkWidget * gth_empty_list_new (const char *text);
+void gth_empty_list_set_text (GthEmptyList *self,
+ const char *text);
+
+G_END_DECLS
+
+#endif /* GTH_EMPTY_LIST_H */
diff --git a/gthumb/gth-extensions.c b/gthumb/gth-extensions.c
new file mode 100644
index 0000000..5fc16b4
--- /dev/null
+++ b/gthumb/gth-extensions.c
@@ -0,0 +1,637 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2008 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include "glib-utils.h"
+#include "gth-extensions.h"
+
+#define EXTENSION_SUFFIX ".extension"
+
+
+/* -- gth_extension -- */
+
+
+static gpointer gth_extension_parent_class = NULL;
+
+
+static gboolean
+gth_extension_base_open (GthExtension *self)
+{
+ g_return_val_if_fail (GTH_IS_EXTENSION (self), FALSE);
+ return FALSE;
+}
+
+
+static void
+gth_extension_base_close (GthExtension *self)
+{
+ g_return_if_fail (GTH_IS_EXTENSION (self));
+}
+
+
+static void
+gth_extension_base_activate (GthExtension *self)
+{
+ g_return_if_fail (GTH_IS_EXTENSION (self));
+ self->active = TRUE;
+}
+
+
+static void
+gth_extension_base_deactivate (GthExtension *self)
+{
+ g_return_if_fail (GTH_IS_EXTENSION (self));
+ self->active = FALSE;
+}
+
+
+static gboolean
+gth_extension_base_is_configurable (GthExtension *self)
+{
+ g_return_val_if_fail (GTH_IS_EXTENSION (self), FALSE);
+ return FALSE;
+}
+
+
+static void
+gth_extension_base_configure (GthExtension *self,
+ GtkWindow *parent)
+{
+ g_return_if_fail (GTH_IS_EXTENSION (self));
+ g_return_if_fail (GTK_IS_WINDOW (parent));
+}
+
+
+static void
+gth_extension_class_init (GthExtensionClass *klass)
+{
+ gth_extension_parent_class = g_type_class_peek_parent (klass);
+
+ klass->open = gth_extension_base_open;
+ klass->close = gth_extension_base_close;
+ klass->activate = gth_extension_base_activate;
+ klass->deactivate = gth_extension_base_deactivate;
+ klass->is_configurable = gth_extension_base_is_configurable;
+ klass->configure = gth_extension_base_configure;
+}
+
+
+static void
+gth_extension_instance_init (GthExtension *self)
+{
+ self->initialized = FALSE;
+ self->active = FALSE;
+}
+
+
+GType
+gth_extension_get_type (void)
+{
+ static GType gth_extension_type_id = 0;
+ if (gth_extension_type_id == 0) {
+ static const GTypeInfo g_define_type_info = {
+ sizeof (GthExtensionClass),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) gth_extension_class_init,
+ (GClassFinalizeFunc) NULL,
+ NULL,
+ sizeof (GthExtension),
+ 0,
+ (GInstanceInitFunc) gth_extension_instance_init,
+ NULL
+ };
+ gth_extension_type_id = g_type_register_static (G_TYPE_OBJECT, "GthExtension", &g_define_type_info, 0);
+ }
+ return gth_extension_type_id;
+}
+
+
+gboolean
+gth_extension_open (GthExtension *self)
+{
+ return GTH_EXTENSION_GET_CLASS (self)->open (self);
+}
+
+
+void
+gth_extension_close (GthExtension *self)
+{
+ GTH_EXTENSION_GET_CLASS (self)->close (self);
+}
+
+
+gboolean
+gth_extension_is_active (GthExtension *self)
+{
+ g_return_val_if_fail (GTH_IS_EXTENSION (self), FALSE);
+ return self->active;
+}
+
+
+void
+gth_extension_activate (GthExtension *self)
+{
+ GTH_EXTENSION_GET_CLASS (self)->activate (self);
+}
+
+
+void
+gth_extension_deactivate (GthExtension *self)
+{
+ GTH_EXTENSION_GET_CLASS (self)->deactivate (self);
+}
+
+
+gboolean
+gth_extension_is_configurable (GthExtension *self)
+{
+ return GTH_EXTENSION_GET_CLASS (self)->is_configurable (self);
+}
+
+
+void
+gth_extension_configure (GthExtension *self,
+ GtkWindow *parent)
+{
+ GTH_EXTENSION_GET_CLASS (self)->configure (self, parent);
+}
+
+
+/* -- gth_extension_module -- */
+
+
+static gpointer gth_extension_module_parent_class = NULL;
+
+
+struct _GthExtensionModulePrivate {
+ char *module_name;
+ GModule *module;
+};
+
+
+static gboolean
+gth_extension_module_real_open (GthExtension *base)
+{
+ GthExtensionModule *self;
+ char *file_name;
+
+ self = GTH_EXTENSION_MODULE (base);
+
+ if (self->priv->module != NULL)
+ g_module_close (self->priv->module);
+
+#ifdef RUN_IN_PLACE
+ {
+ char *extension_dir;
+
+ extension_dir = g_build_filename (GTHUMB_EXTENSIONS_DIR, self->priv->module_name, ".libs", NULL);
+ file_name = g_module_build_path (extension_dir, self->priv->module_name);
+ g_free (extension_dir);
+ }
+#else
+ file_name = g_module_build_path (GTHUMB_EXTENSIONS_DIR, self->priv->module_name);
+#endif
+ self->priv->module = g_module_open (file_name, G_MODULE_BIND_LAZY);
+ g_free (file_name);
+
+ if (self->priv->module == NULL) {
+ /*g_warning ("could not open the module `%s`: %s\n", self->priv->module_name, g_module_error ());*/
+ }
+
+ return self->priv->module != NULL;
+}
+
+
+static void
+gth_extension_module_real_close (GthExtension *base)
+{
+ GthExtensionModule *self;
+
+ self = GTH_EXTENSION_MODULE (base);
+
+ if (self->priv->module != NULL) {
+ g_module_close (self->priv->module);
+ self->priv->module = NULL;
+ }
+}
+
+
+static char *
+get_module_function_name (GthExtensionModule *self,
+ const char *function_name)
+{
+ return g_strconcat ("gthumb_extension_", function_name, NULL);
+}
+
+
+static void
+gth_extension_module_exec_generic_func (GthExtensionModule *self,
+ const char *name)
+{
+ char *function_name;
+ void (*func) (void);
+
+ g_return_if_fail (GTH_IS_EXTENSION_MODULE (self));
+ g_return_if_fail (name != NULL);
+
+ function_name = get_module_function_name (self, name);
+ if (g_module_symbol (self->priv->module, function_name, (gpointer *)&func))
+ func();
+ else
+ g_warning ("could not exec module function: %s\n", g_module_error ());
+
+ g_free (function_name);
+}
+
+
+static void
+gth_extension_module_real_activate (GthExtension *base)
+{
+ GthExtensionModule *self;
+
+ self = GTH_EXTENSION_MODULE (base);
+
+ if (base->active)
+ return;
+
+ gth_extension_module_exec_generic_func (self, "activate");
+ base->active = TRUE;
+}
+
+
+static void
+gth_extension_module_real_deactivate (GthExtension *base)
+{
+ GthExtensionModule *self;
+
+ self = GTH_EXTENSION_MODULE (base);
+
+ gth_extension_module_exec_generic_func (self, "deactivate");
+}
+
+
+static gboolean
+gth_extension_module_real_is_configurable (GthExtension *base)
+{
+ GthExtensionModule *self;
+ char *function_name;
+ gboolean result = FALSE;
+ gboolean (*is_configurable_func) ();
+
+ g_return_val_if_fail (GTH_IS_EXTENSION_MODULE (base), FALSE);
+
+ self = GTH_EXTENSION_MODULE (base);
+
+ function_name = get_module_function_name (self, "is_configurable");
+ if (g_module_symbol (self->priv->module, function_name, (gpointer *)&is_configurable_func))
+ result = is_configurable_func();
+ g_free (function_name);
+
+ return result;
+}
+
+
+static void
+gth_extension_module_real_configure (GthExtension *base,
+ GtkWindow *parent)
+{
+ GthExtensionModule *self;
+ char *function_name;
+ void (*configure_func) (GtkWindow *);
+
+ g_return_if_fail (GTK_IS_WINDOW (parent));
+
+ self = GTH_EXTENSION_MODULE (base);
+
+ function_name = get_module_function_name (self, "configure");
+ if (g_module_symbol (self->priv->module, function_name, (gpointer *)&configure_func))
+ configure_func (parent);
+
+ g_free (function_name);
+}
+
+
+static void
+gth_extension_module_finalize (GObject *obj)
+{
+ GthExtensionModule *self;
+
+ self = GTH_EXTENSION_MODULE (obj);
+
+ if (self->priv != NULL) {
+ if (self->priv->module != NULL)
+ g_module_close (self->priv->module);
+ g_free (self->priv->module_name);
+ g_free (self->priv);
+ }
+
+ G_OBJECT_CLASS (gth_extension_module_parent_class)->finalize (obj);
+}
+
+
+static void
+gth_extension_module_class_init (GthExtensionModuleClass *klass)
+{
+ GthExtensionClass *elc;
+
+ gth_extension_module_parent_class = g_type_class_peek_parent (klass);
+
+ G_OBJECT_CLASS (klass)->finalize = gth_extension_module_finalize;
+
+ elc = GTH_EXTENSION_CLASS (klass);
+ elc->open = gth_extension_module_real_open;
+ elc->close = gth_extension_module_real_close;
+ elc->activate = gth_extension_module_real_activate;
+ elc->deactivate = gth_extension_module_real_deactivate;
+ elc->is_configurable = gth_extension_module_real_is_configurable;
+ elc->configure = gth_extension_module_real_configure;
+}
+
+
+static void
+gth_extension_module_instance_init (GthExtensionModule *self)
+{
+ self->priv = g_new0 (GthExtensionModulePrivate, 1);
+}
+
+
+GType
+gth_extension_module_get_type (void)
+{
+ static GType gth_extension_module_type_id = 0;
+ if (gth_extension_module_type_id == 0) {
+ static const GTypeInfo g_define_type_info = {
+ sizeof (GthExtensionModuleClass),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) gth_extension_module_class_init,
+ (GClassFinalizeFunc) NULL,
+ NULL,
+ sizeof (GthExtensionModule),
+ 0,
+ (GInstanceInitFunc) gth_extension_module_instance_init,
+ NULL
+ };
+ gth_extension_module_type_id = g_type_register_static (GTH_TYPE_EXTENSION, "GthExtensionModule", &g_define_type_info, 0);
+ }
+ return gth_extension_module_type_id;
+}
+
+
+GthExtension *
+gth_extension_module_new (const char *module_name)
+{
+ GthExtension *loader;
+
+ loader = g_object_new (GTH_TYPE_EXTENSION_MODULE, NULL);
+ GTH_EXTENSION_MODULE (loader)->priv->module_name = g_strdup (module_name);
+
+ return loader;
+}
+
+
+/* -- gth_extension_description -- */
+
+
+static gpointer gth_extension_description_parent_class = NULL;
+
+
+static void
+gth_extension_description_finalize (GObject *obj)
+{
+ GthExtensionDescription *self;
+
+ self = GTH_EXTENSION_DESCRIPTION (obj);
+
+ g_free (self->name);
+ g_free (self->description);
+ g_strfreev (self->authors);
+ g_free (self->copyright);
+ g_free (self->version);
+ g_free (self->url);
+ g_free (self->loader_type);
+ g_free (self->loader_file);
+
+ G_OBJECT_CLASS (gth_extension_description_parent_class)->finalize (obj);
+}
+
+
+static void
+gth_extension_description_class_init (GthExtensionDescriptionClass *klass)
+{
+ gth_extension_description_parent_class = g_type_class_peek_parent (klass);
+ G_OBJECT_CLASS (klass)->finalize = gth_extension_description_finalize;
+}
+
+
+static void
+gth_extension_description_instance_init (GthExtensionDescription *self)
+{
+}
+
+
+GType
+gth_extension_description_get_type (void)
+{
+ static GType gth_extension_description_type_id = 0;
+ if (gth_extension_description_type_id == 0) {
+ static const GTypeInfo g_define_type_info = {
+ sizeof (GthExtensionDescriptionClass),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) gth_extension_description_class_init,
+ (GClassFinalizeFunc) NULL,
+ NULL,
+ sizeof (GthExtensionDescription),
+ 0,
+ (GInstanceInitFunc) gth_extension_description_instance_init,
+ NULL
+ };
+ gth_extension_description_type_id = g_type_register_static (G_TYPE_OBJECT, "GthExtensionDescription", &g_define_type_info, 0);
+ }
+ return gth_extension_description_type_id;
+}
+
+
+static void
+gth_extension_description_load_from_file (GthExtensionDescription *desc,
+ const char *file_name)
+{
+ GKeyFile *key_file;
+
+ key_file = g_key_file_new ();
+ g_key_file_load_from_file (key_file, file_name, G_KEY_FILE_NONE, NULL);
+ desc->name = g_key_file_get_string (key_file, "Extension", "Name", NULL);
+ desc->description = g_key_file_get_string (key_file, "Extension", "Description", NULL);
+ desc->version = g_key_file_get_string (key_file, "Extension", "Version", NULL);
+ desc->authors = g_key_file_get_string_list (key_file, "Extension", "Authors", NULL, NULL);
+ desc->copyright = g_key_file_get_string (key_file, "Extension", "Copyright", NULL);
+ desc->url = g_key_file_get_string (key_file, "Extension", "URL", NULL);
+ desc->loader_type = g_key_file_get_string (key_file, "Loader", "Type", NULL);
+ desc->loader_file = g_key_file_get_string (key_file, "Loader", "File", NULL);
+ g_key_file_free (key_file);
+}
+
+
+GthExtensionDescription *
+gth_extension_description_new (GFile *file)
+{
+ GthExtensionDescription *desc;
+
+ desc = g_object_new (GTH_TYPE_EXTENSION_DESCRIPTION, NULL);
+ if (file != NULL) {
+ char *file_name;
+ file_name = g_file_get_path (file);
+ gth_extension_description_load_from_file (desc, file_name);
+ g_free (file_name);
+ }
+
+ return desc;
+}
+
+
+/* -- gth_extension_manager -- */
+
+
+static gpointer gth_extension_manager_parent_class = NULL;
+
+
+struct _GthExtensionManagerPrivate {
+ GList *extensions;
+};
+
+
+static void
+gth_extension_manager_finalize (GObject *obj)
+{
+ GthExtensionManager *self;
+
+ self = GTH_EXTENSION_MANAGER (obj);
+
+ if (self->priv != NULL) {
+ _g_object_list_unref (self->priv->extensions);
+ g_free (self->priv);
+ }
+
+ G_OBJECT_CLASS (gth_extension_manager_parent_class)->finalize (obj);
+}
+
+
+static void
+gth_extension_manager_class_init (GthExtensionManagerClass *klass)
+{
+ gth_extension_manager_parent_class = g_type_class_peek_parent (klass);
+
+ G_OBJECT_CLASS (klass)->finalize = gth_extension_manager_finalize;
+}
+
+
+static void
+gth_extension_manager_instance_init (GthExtensionManager *self)
+{
+ self->priv = g_new0 (GthExtensionManagerPrivate, 1);
+}
+
+
+GType
+gth_extension_manager_get_type (void) {
+ static GType gth_extension_manager_type_id = 0;
+ if (gth_extension_manager_type_id == 0) {
+ static const GTypeInfo g_define_type_info = {
+ sizeof (GthExtensionManagerClass),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) gth_extension_manager_class_init,
+ (GClassFinalizeFunc) NULL,
+ NULL,
+ sizeof (GthExtensionManager),
+ 0,
+ (GInstanceInitFunc) gth_extension_manager_instance_init,
+ NULL
+ };
+ gth_extension_manager_type_id = g_type_register_static (G_TYPE_OBJECT, "GthExtensionManager", &g_define_type_info, 0);
+ }
+ return gth_extension_manager_type_id;
+}
+
+
+static GthExtensionManager *
+gth_extension_manager_construct (GType object_type)
+{
+ GthExtensionManager *self;
+ self = g_object_newv (object_type, 0, NULL);
+ return self;
+}
+
+
+GthExtensionManager *
+gth_extension_manager_new (void)
+{
+ return gth_extension_manager_construct (GTH_TYPE_EXTENSION_MANAGER);
+}
+
+
+void
+gth_extension_manager_load_extensions (GthExtensionManager *self)
+{
+ GFile *extensions_dir;
+ GFileEnumerator *enumerator;
+ GFileInfo *info;
+
+ g_return_if_fail (GTH_IS_EXTENSION_MANAGER (self));
+
+ _g_object_list_unref (self->priv->extensions);
+ self->priv->extensions = NULL;
+
+ extensions_dir = g_file_new_for_path (GTHUMB_EXTENSIONS_DIR);
+ enumerator = g_file_enumerate_children (extensions_dir, G_FILE_ATTRIBUTE_STANDARD_NAME, 0, NULL, NULL);
+ while ((info = g_file_enumerator_next_file (enumerator, NULL, NULL)) != NULL) {
+ const char *name;
+
+ name = g_file_info_get_name (info);
+ if ((name != NULL) && g_str_has_suffix (name, EXTENSION_SUFFIX)) {
+ GFile *ext_file;
+ GthExtensionDescription *ext_desc;
+
+ ext_file = g_file_get_child (extensions_dir, name);
+ ext_desc = gth_extension_description_new (ext_file);
+ if (ext_desc != NULL)
+ self->priv->extensions = g_list_prepend (self->priv->extensions, ext_desc);
+
+ g_object_unref (ext_file);
+ }
+
+ g_object_unref (info);
+ }
+ g_object_unref (enumerator);
+ g_object_unref (extensions_dir);
+}
+
+
+GthExtension *
+gth_extension_manager_get_extension (GthExtensionManager *self,
+ GthExtensionDescription *desc)
+{
+ GthExtension *loader = NULL;
+ return loader;
+}
diff --git a/gthumb/gth-extensions.h b/gthumb/gth-extensions.h
new file mode 100644
index 0000000..67d7e58
--- /dev/null
+++ b/gthumb/gth-extensions.h
@@ -0,0 +1,155 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2008 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GTH_EXTENSIONS_H
+#define GTH_EXTENSIONS_H
+
+#include <glib.h>
+#include <glib-object.h>
+#include <gtk/gtk.h>
+#include <stdlib.h>
+#include <string.h>
+
+G_BEGIN_DECLS
+
+#define GTH_TYPE_EXTENSION (gth_extension_get_type ())
+#define GTH_EXTENSION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTH_TYPE_EXTENSION, GthExtension))
+#define GTH_EXTENSION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTH_TYPE_EXTENSION, GthExtensionClass))
+#define GTH_IS_EXTENSION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTH_TYPE_EXTENSION))
+#define GTH_IS_EXTENSION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTH_TYPE_EXTENSION))
+#define GTH_EXTENSION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTH_TYPE_EXTENSION, GthExtensionClass))
+
+typedef struct _GthExtension GthExtension;
+typedef struct _GthExtensionClass GthExtensionClass;
+typedef struct _GthExtensionPrivate GthExtensionPrivate;
+
+#define GTH_TYPE_EXTENSION_MODULE (gth_extension_module_get_type ())
+#define GTH_EXTENSION_MODULE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTH_TYPE_EXTENSION_MODULE, GthExtensionModule))
+#define GTH_EXTENSION_MODULE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTH_TYPE_EXTENSION_MODULE, GthExtensionModuleClass))
+#define GTH_IS_EXTENSION_MODULE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTH_TYPE_EXTENSION_MODULE))
+#define GTH_IS_EXTENSION_MODULE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTH_TYPE_EXTENSION_MODULE))
+#define GTH_EXTENSION_MODULE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTH_TYPE_EXTENSION_MODULE, GthExtensionModuleClass))
+
+typedef struct _GthExtensionModule GthExtensionModule;
+typedef struct _GthExtensionModuleClass GthExtensionModuleClass;
+typedef struct _GthExtensionModulePrivate GthExtensionModulePrivate;
+
+#define GTH_TYPE_EXTENSION_DESCRIPTION (gth_extension_description_get_type ())
+#define GTH_EXTENSION_DESCRIPTION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTH_TYPE_EXTENSION_DESCRIPTION, GthExtensionDescription))
+#define GTH_EXTENSION_DESCRIPTION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTH_TYPE_EXTENSION_DESCRIPTION, GthExtensionDescriptionClass))
+#define GTH_IS_EXTENSION_DESCRIPTION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTH_TYPE_EXTENSION_DESCRIPTION))
+#define GTH_IS_EXTENSION_DESCRIPTION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTH_TYPE_EXTENSION_DESCRIPTION))
+#define GTH_EXTENSION_DESCRIPTION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTH_TYPE_EXTENSION_DESCRIPTION, GthExtensionDescriptionClass))
+
+typedef struct _GthExtensionDescription GthExtensionDescription;
+typedef struct _GthExtensionDescriptionClass GthExtensionDescriptionClass;
+typedef struct _GthExtensionDescriptionPrivate GthExtensionDescriptionPrivate;
+
+#define GTH_TYPE_EXTENSION_MANAGER (gth_extension_manager_get_type ())
+#define GTH_EXTENSION_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTH_TYPE_EXTENSION_MANAGER, GthExtensionManager))
+#define GTH_EXTENSION_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTH_TYPE_EXTENSION_MANAGER, GthExtensionManagerClass))
+#define GTH_IS_EXTENSION_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTH_TYPE_EXTENSION_MANAGER))
+#define GTH_IS_EXTENSION_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTH_TYPE_EXTENSION_MANAGER))
+#define GTH_EXTENSION_MANAGER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTH_TYPE_EXTENSION_MANAGER, GthExtensionManagerClass))
+
+typedef struct _GthExtensionManager GthExtensionManager;
+typedef struct _GthExtensionManagerClass GthExtensionManagerClass;
+typedef struct _GthExtensionManagerPrivate GthExtensionManagerPrivate;
+
+struct _GthExtension {
+ GObject parent_instance;
+ GthExtensionPrivate *priv;
+ gboolean initialized;
+ gboolean active;
+};
+
+struct _GthExtensionClass {
+ GObjectClass parent_class;
+ gboolean (*open) (GthExtension *self);
+ void (*close) (GthExtension *self);
+ void (*activate) (GthExtension *self);
+ void (*deactivate) (GthExtension *self);
+ gboolean (*is_configurable) (GthExtension *self);
+ void (*configure) (GthExtension *self,
+ GtkWindow *parent);
+};
+
+struct _GthExtensionModule {
+ GthExtension parent_instance;
+ GthExtensionModulePrivate *priv;
+};
+
+struct _GthExtensionModuleClass {
+ GthExtensionClass parent_class;
+};
+
+struct _GthExtensionDescription {
+ GObject parent_instance;
+ GthExtensionDescriptionPrivate *priv;
+ char *name;
+ char *description;
+ char **authors;
+ char *copyright;
+ char *version;
+ char *url;
+ char *loader_type;
+ char *loader_file;
+};
+
+struct _GthExtensionDescriptionClass {
+ GObjectClass parent_class;
+};
+
+struct _GthExtensionManager {
+ GObject parent_instance;
+ GthExtensionManagerPrivate *priv;
+};
+
+struct _GthExtensionManagerClass {
+ GObjectClass parent_class;
+};
+
+GType gth_extension_get_type (void);
+gboolean gth_extension_open (GthExtension *self);
+void gth_extension_close (GthExtension *self);
+gboolean gth_extension_is_active (GthExtension *self);
+void gth_extension_activate (GthExtension *self);
+void gth_extension_deactivate (GthExtension *self);
+gboolean gth_extension_is_configurable (GthExtension *self);
+void gth_extension_configure (GthExtension *self,
+ GtkWindow *parent);
+
+GType gth_extension_module_get_type (void);
+GthExtension * gth_extension_module_new (const char *module_name);
+
+GType gth_extension_description_get_type (void);
+GthExtensionDescription * gth_extension_description_new (GFile *file);
+
+GType gth_extension_manager_get_type (void);
+GthExtensionManager * gth_extension_manager_new (void);
+void gth_extension_manager_load_extensions (GthExtensionManager *self);
+GthExtension * gth_extension_manager_get_extension (GthExtensionManager *self,
+ GthExtensionDescription *desc);
+
+G_END_DECLS
+
+#endif
diff --git a/gthumb/gth-file-data.c b/gthumb/gth-file-data.c
new file mode 100644
index 0000000..03ff5b4
--- /dev/null
+++ b/gthumb/gth-file-data.c
@@ -0,0 +1,418 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 The Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <string.h>
+#include "glib-utils.h"
+#include "gth-duplicable.h"
+#include "gth-file-data.h"
+
+
+#define GTH_FILE_DATA_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GTH_TYPE_FILE_DATA, GthFileDataPrivate))
+
+struct _GthFileDataPrivate {
+ GTimeVal mtime;
+ char *sort_key;
+};
+
+static gpointer gth_file_data_parent_class = NULL;
+
+
+static void
+gth_file_data_finalize (GObject *obj)
+{
+ GthFileData *self = GTH_FILE_DATA (obj);
+
+ if (self->file != NULL)
+ g_object_unref (self->file);
+ if (self->info != NULL)
+ g_object_unref (self->info);
+ g_free (self->priv->sort_key);
+
+ G_OBJECT_CLASS (gth_file_data_parent_class)->finalize (obj);
+}
+
+
+static void
+gth_file_data_class_init (GthFileDataClass *klass)
+{
+ gth_file_data_parent_class = g_type_class_peek_parent (klass);
+ g_type_class_add_private (klass, sizeof (GthFileDataPrivate));
+
+ G_OBJECT_CLASS (klass)->finalize = gth_file_data_finalize;
+}
+
+
+static void
+gth_file_data_instance_init (GthFileData *self)
+{
+ self->priv = GTH_FILE_DATA_GET_PRIVATE (self);
+}
+
+
+static GObject *
+gth_file_data_real_duplicate (GthDuplicable *base)
+{
+ return (GObject *) gth_file_data_dup ((GthFileData*) base);
+}
+
+
+static void
+gth_file_data_gth_duplicable_interface_init (GthDuplicableIface *iface)
+{
+ iface->duplicate = gth_file_data_real_duplicate;
+}
+
+
+GType
+gth_file_data_get_type (void)
+{
+ static GType gth_file_data_type_id = 0;
+
+ if (gth_file_data_type_id == 0) {
+ static const GTypeInfo g_define_type_info = {
+ sizeof (GthFileDataClass),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) gth_file_data_class_init,
+ (GClassFinalizeFunc) NULL,
+ NULL,
+ sizeof (GthFileData),
+ 0,
+ (GInstanceInitFunc) gth_file_data_instance_init,
+ NULL
+ };
+ static const GInterfaceInfo gth_duplicable_info = {
+ (GInterfaceInitFunc) gth_file_data_gth_duplicable_interface_init,
+ (GInterfaceFinalizeFunc) NULL,
+ NULL
+ };
+ gth_file_data_type_id = g_type_register_static (G_TYPE_OBJECT, "GthFileData", &g_define_type_info, 0);
+ g_type_add_interface_static (gth_file_data_type_id, GTH_TYPE_DUPLICABLE, >h_duplicable_info);
+ }
+
+ return gth_file_data_type_id;
+}
+
+
+GthFileData *
+gth_file_data_new (GFile *file,
+ GFileInfo *info)
+{
+ GthFileData *self;
+
+ self = g_object_new (GTH_TYPE_FILE_DATA, NULL);
+ gth_file_data_set_file (self, file);
+ gth_file_data_set_info (self, info);
+
+ return self;
+}
+
+
+GthFileData *
+gth_file_data_new_for_uri (const char *uri,
+ const char *mime_type)
+{
+ GFile *file;
+ GthFileData *file_data;
+
+ file = g_file_new_for_uri (uri);
+ file_data = gth_file_data_new (file, NULL);
+ gth_file_data_set_mime_type (file_data, mime_type);
+
+ g_object_unref (file);
+
+ return file_data;
+}
+
+
+GthFileData *
+gth_file_data_dup (GthFileData *self)
+{
+ GthFileData *file;
+
+ if (self == NULL)
+ return NULL;
+
+ file = g_object_new (GTH_TYPE_FILE_DATA, NULL);
+ file->file = g_file_dup (self->file);
+ file->info = g_file_info_dup (self->info);
+ file->error = self->error;
+ file->thumb_created = self->thumb_created;
+ file->thumb_loaded = self->thumb_loaded;
+
+ return file;
+}
+
+
+void
+gth_file_data_set_file (GthFileData *self,
+ GFile *file)
+{
+ if (self->file != NULL) {
+ g_object_unref (self->file);
+ self->file = NULL;
+ }
+
+ if (file != NULL)
+ self->file = g_object_ref (file);
+}
+
+
+void
+gth_file_data_set_info (GthFileData *self,
+ GFileInfo *info)
+{
+ if (self->info != NULL) {
+ g_object_unref (self->info);
+ self->info = NULL;
+ }
+
+ if (info != NULL)
+ self->info = g_object_ref (info);
+ else
+ self->info = g_file_info_new ();
+}
+
+
+void
+gth_file_data_set_mime_type (GthFileData *self,
+ const char *mime_type)
+{
+ if (mime_type != NULL)
+ g_file_info_set_content_type (self->info, get_static_string (mime_type));
+}
+
+
+const char *
+gth_file_data_get_mime_type (GthFileData *self)
+{
+ const char *content_type;
+
+ if (self->info == NULL)
+ return NULL;
+
+ content_type = g_file_info_get_attribute_string (self->info, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE);
+ if (content_type == NULL)
+ content_type = g_file_info_get_attribute_string (self->info, G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE);
+
+ return get_static_string (content_type);
+}
+
+
+const char *
+gth_file_data_get_filename_sort_key (GthFileData *self)
+{
+ if (self->info == NULL)
+ return NULL;
+
+ if (self->priv->sort_key == NULL)
+ self->priv->sort_key = g_utf8_collate_key_for_filename (g_file_info_get_display_name (self->info), -1);
+
+ return self->priv->sort_key;
+}
+
+
+time_t
+gth_file_data_get_mtime (GthFileData *self)
+{
+ g_file_info_get_modification_time (self->info, &self->priv->mtime);
+ return (time_t) self->priv->mtime.tv_sec;
+}
+
+
+GTimeVal *
+gth_file_data_get_modification_time (GthFileData *self)
+{
+ g_file_info_get_modification_time (self->info, &self->priv->mtime);
+ return &self->priv->mtime;
+}
+
+
+gboolean
+gth_file_data_is_readable (GthFileData *self)
+{
+ return g_file_info_get_attribute_boolean (self->info, G_FILE_ATTRIBUTE_ACCESS_CAN_READ);
+}
+
+
+void
+gth_file_data_update_info (GthFileData *fd,
+ const char *attributes)
+{
+ if (attributes == NULL)
+ attributes = GTH_FILE_DATA_ATTRIBUTES;
+ if (fd->info != NULL)
+ g_object_unref (fd->info);
+
+ fd->info = g_file_query_info (fd->file, attributes, G_FILE_QUERY_INFO_NONE, NULL, NULL);
+
+ if (fd->info == NULL)
+ fd->info = g_file_info_new ();
+}
+
+
+void
+gth_file_data_update_mime_type (GthFileData *fd,
+ gboolean fast)
+{
+ gth_file_data_set_mime_type (fd, _g_file_get_mime_type (fd->file, fast || ! g_file_is_native (fd->file)));
+}
+
+
+void
+gth_file_data_update_all (GthFileData *fd,
+ gboolean fast)
+{
+ gth_file_data_update_info (fd, NULL);
+ gth_file_data_update_mime_type (fd, fast);
+}
+
+
+GList*
+gth_file_data_list_from_uri_list (GList *list)
+{
+ GList *result = NULL;
+ GList *scan;
+
+ for (scan = list; scan; scan = scan->next) {
+ char *uri = scan->data;
+ GFile *file;
+
+ file = g_file_new_for_uri (uri);
+ result = g_list_prepend (result, gth_file_data_new (file, NULL));
+ g_object_unref (file);
+ }
+
+ return g_list_reverse (result);
+}
+
+
+GList*
+gth_file_data_list_to_uri_list (GList *list)
+{
+ GList *result = NULL;
+ GList *scan;
+
+ for (scan = list; scan; scan = scan->next) {
+ GthFileData *file = scan->data;
+ result = g_list_prepend (result, g_file_get_uri (file->file));
+ }
+
+ return g_list_reverse (result);
+}
+
+
+GList *
+gth_file_data_list_to_file_list (GList *list)
+{
+ GList *result = NULL;
+ GList *scan;
+
+ for (scan = list; scan; scan = scan->next) {
+ GthFileData *file = scan->data;
+ result = g_list_prepend (result, g_file_dup (file->file));
+ }
+
+ return g_list_reverse (result);
+}
+
+
+GList *
+gth_file_data_list_find_file (GList *list,
+ GFile *file)
+{
+ GList *scan;
+
+ for (scan = list; scan; scan = scan->next) {
+ GthFileData *file_data = scan->data;
+
+ if (g_file_equal (file_data->file, file))
+ return scan;
+ }
+
+ return NULL;
+}
+
+
+GList *
+gth_file_data_list_find_uri (GList *list,
+ const char *uri)
+{
+ GList *scan;
+
+ for (scan = list; scan; scan = scan->next) {
+ GthFileData *file = scan->data;
+ char *file_uri;
+
+ file_uri = g_file_get_uri (file->file);
+ if (strcmp (file_uri, uri) == 0) {
+ g_free (file_uri);
+ return scan;
+ }
+ g_free (file_uri);
+ }
+
+ return NULL;
+}
+
+
+typedef struct {
+ GthFileData *file_data;
+ GthFileDataFunc ready_func;
+ gpointer user_data;
+ GError *error;
+ guint id;
+} ReadyData;
+
+
+static gboolean
+exec_ready_func (gpointer user_data)
+{
+ ReadyData *data = user_data;
+
+ g_source_remove (data->id);
+ data->ready_func (data->file_data, data->error, data->user_data);
+
+ _g_object_unref (data->file_data);
+ g_free (data);
+
+ return FALSE;
+}
+
+
+void
+gth_file_data_ready_with_error (GthFileData *file_data,
+ GthFileDataFunc ready_func,
+ gpointer user_data,
+ GError *error)
+{
+ ReadyData *data;
+
+ data = g_new0 (ReadyData, 1);
+ if (file_data != NULL)
+ data->file_data = g_object_ref (file_data);
+ data->ready_func = ready_func;
+ data->user_data = user_data;
+ data->error = error;
+ data->id = g_idle_add (exec_ready_func, data);
+}
diff --git a/gthumb/gth-file-data.h b/gthumb/gth-file-data.h
new file mode 100644
index 0000000..8f1da8b
--- /dev/null
+++ b/gthumb/gth-file-data.h
@@ -0,0 +1,125 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 The Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GTH_FILE_DATA_H
+#define GTH_FILE_DATA_H
+
+#include <glib.h>
+#include <glib-object.h>
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define GTH_FILE_DATA_ATTR(a) ("standard::type," \
+ "standard::is-hidden," \
+ "standard::is-backup," \
+ "standard::name," \
+ "standard::display-name," \
+ "standard::edit-name," \
+ "standard::icon," \
+ "standard::size," \
+ "time::created," \
+ "time::created-usec," \
+ "time::modified," \
+ "time::modified-usec," \
+ a)
+#define GTH_FILE_DATA_ATTRIBUTES (GTH_FILE_DATA_ATTR(""))
+#define GTH_FILE_DATA_ATTRIBUTES_WITH_FAST_CONTENT_TYPE (GTH_FILE_DATA_ATTR("standard::fast-content-type"))
+#define GTH_FILE_DATA_ATTRIBUTES_WITH_CONTENT_TYPE (GTH_FILE_DATA_ATTR("standard::content-type"))
+
+#define GTH_TYPE_FILE_DATA (gth_file_data_get_type ())
+#define GTH_FILE_DATA(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTH_TYPE_FILE_DATA, GthFileData))
+#define GTH_FILE_DATA_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTH_TYPE_FILE_DATA, GthFileDataClass))
+#define GTH_IS_FILE_DATA(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTH_TYPE_FILE_DATA))
+#define GTH_IS_FILE_DATA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTH_TYPE_FILE_DATA))
+#define GTH_FILE_DATA_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTH_TYPE_FILE_DATA, GthFileDataClass))
+
+typedef struct _GthFileData GthFileData;
+typedef struct _GthFileDataClass GthFileDataClass;
+typedef struct _GthFileDataPrivate GthFileDataPrivate;
+
+struct _GthFileData {
+ GObject parent_instance;
+ GFile *file;
+ GFileInfo *info;
+ guint error : 1; /* Whether an error occurred loading
+ * this file. */
+ guint thumb_loaded : 1; /* Whether we have a thumb of this
+ * image. */
+ guint thumb_created : 1; /* Whether a thumb has been
+ * created for this image. */
+ GthFileDataPrivate *priv;
+};
+
+struct _GthFileDataClass {
+ GObjectClass parent_class;
+};
+
+typedef int (*GthFileDataCompFunc) (GthFileData *a, GthFileData *b);
+typedef void (*GthFileDataFunc) (GthFileData *a, GError *error, gpointer data);
+
+typedef struct {
+ const char *name;
+ const char *display_name;
+ GthFileDataCompFunc cmp_func;
+} GthFileDataSort;
+
+GType gth_file_data_get_type (void);
+GthFileData * gth_file_data_new (GFile *file,
+ GFileInfo *info);
+GthFileData * gth_file_data_new_for_uri (const char *uri,
+ const char *mime_type);
+GthFileData * gth_file_data_dup (GthFileData *self);
+void gth_file_data_set_file (GthFileData *self,
+ GFile *file);
+void gth_file_data_set_info (GthFileData *self,
+ GFileInfo *info);
+void gth_file_data_set_mime_type (GthFileData *self,
+ const char *mime_type);
+const char * gth_file_data_get_mime_type (GthFileData *self);
+const char * gth_file_data_get_filename_sort_key (GthFileData *self);
+time_t gth_file_data_get_mtime (GthFileData *self);
+GTimeVal * gth_file_data_get_modification_time (GthFileData *self);
+gboolean gth_file_data_is_readable (GthFileData *self);
+void gth_file_data_update_info (GthFileData *self,
+ const char *attributes);
+void gth_file_data_update_mime_type (GthFileData *self,
+ gboolean fast);
+void gth_file_data_update_all (GthFileData *self,
+ gboolean fast);
+
+GList* gth_file_data_list_from_uri_list (GList *list);
+GList* gth_file_data_list_to_uri_list (GList *list);
+GList* gth_file_data_list_to_file_list (GList *list);
+GList* gth_file_data_list_find_file (GList *list,
+ GFile *file);
+GList* gth_file_data_list_find_uri (GList *list,
+ const char *uri);
+
+void gth_file_data_ready_with_error (GthFileData *file_data,
+ GthFileDataFunc ready_func,
+ gpointer ready_data,
+ GError *error);
+
+G_END_DECLS
+
+#endif /* GTH_FILE_DATA_H */
diff --git a/gthumb/gth-file-list.c b/gthumb/gth-file-list.c
new file mode 100644
index 0000000..90306c0
--- /dev/null
+++ b/gthumb/gth-file-list.c
@@ -0,0 +1,1227 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2008 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <string.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include "glib-utils.h"
+#include "gth-cell-renderer-thumbnail.h"
+#include "gth-dumb-notebook.h"
+#include "gth-empty-list.h"
+#include "gth-file-list.h"
+#include "gth-file-store.h"
+#include "gth-icon-cache.h"
+#include "gth-icon-view.h"
+#include "gth-thumb-loader.h"
+#include "gtk-utils.h"
+
+#define DEFAULT_THUMBNAIL_SIZE 112
+#define N_THUMBS_PER_NOTIFICATION 15
+#define N_LOOKAHEAD 50
+#define EMPTY (N_("(Empty)"))
+
+typedef enum {
+ GTH_FILE_LIST_OP_TYPE_SET_FILES,
+ GTH_FILE_LIST_OP_TYPE_CLEAR_FILES,
+ GTH_FILE_LIST_OP_TYPE_ADD_FILES,
+ GTH_FILE_LIST_OP_TYPE_UPDATE_FILES,
+ GTH_FILE_LIST_OP_TYPE_DELETE_FILES,
+ GTH_FILE_LIST_OP_TYPE_SET_FILTER,
+ GTH_FILE_LIST_OP_TYPE_SET_SORT_FUNC,
+ GTH_FILE_LIST_OP_TYPE_ENABLE_THUMBS
+ /*GTH_FILE_LIST_OP_TYPE_RENAME,
+ GTH_FILE_LIST_OP_TYPE_SET_THUMBS_SIZE,*/
+} GthFileListOpType;
+
+
+typedef struct {
+ GthFileListOpType type;
+ GthFileSource *file_source;
+ GtkTreeModel *model;
+ GthTest *filter;
+ GList *file_list; /* GthFileData */
+ GList *files; /* GFile */
+ GthFileDataCompFunc cmp_func;
+ gboolean inverse_sort;
+ char *sval;
+ int ival;
+} GthFileListOp;
+
+
+enum {
+ FILE_POPUP,
+ LAST_SIGNAL
+};
+
+
+enum {
+ GTH_FILE_LIST_PANE_VIEW,
+ GTH_FILE_LIST_PANE_MESSAGE
+};
+
+struct _GthFileListPrivateData
+{
+ GtkWidget *notebook;
+ GtkWidget *view;
+ GtkWidget *message;
+ GthIconCache *icon_cache;
+ GthFileSource *file_source;
+ gboolean load_thumbs;
+ int thumb_size;
+ gboolean ignore_hidden_thumbs;
+ GthThumbLoader *thumb_loader;
+ gboolean update_thumb_in_view;
+ int thumb_pos;
+ int n_thumb;
+ GthFileData *thumb_fd;
+ gboolean loading_thumbs;
+ gboolean cancel;
+ GList *queue; /* list of GthFileListOp */
+ GtkCellRenderer *thumbnail_renderer;
+ GtkCellRenderer *text_renderer;
+
+ DoneFunc done_func;
+ gpointer done_func_data;
+};
+
+
+/* OPs */
+
+
+static void _gth_file_list_exec_next_op (GthFileList *file_list);
+
+
+static GthFileListOp *
+gth_file_list_op_new (GthFileListOpType op_type)
+{
+ GthFileListOp *op;
+
+ op = g_new0 (GthFileListOp, 1);
+ op->type = op_type;
+
+ return op;
+}
+
+
+static void
+gth_file_list_op_free (GthFileListOp *op)
+{
+ switch (op->type) {
+ case GTH_FILE_LIST_OP_TYPE_SET_FILES:
+ g_object_unref (op->file_source);
+ _g_object_list_unref (op->file_list);
+ break;
+ case GTH_FILE_LIST_OP_TYPE_CLEAR_FILES:
+ g_free (op->sval);
+ break;
+ case GTH_FILE_LIST_OP_TYPE_ADD_FILES:
+ case GTH_FILE_LIST_OP_TYPE_UPDATE_FILES:
+ _g_object_list_unref (op->file_list);
+ break;
+ case GTH_FILE_LIST_OP_TYPE_DELETE_FILES:
+ _g_object_list_unref (op->files);
+ break;
+ case GTH_FILE_LIST_OP_TYPE_SET_FILTER:
+ g_object_unref (op->filter);
+ break;
+ default:
+ break;
+ }
+ g_free (op);
+}
+
+
+static void
+_gth_file_list_clear_queue (GthFileList *file_list)
+{
+ g_list_foreach (file_list->priv->queue, (GFunc) gth_file_list_op_free, NULL);
+ g_list_free (file_list->priv->queue);
+ file_list->priv->queue = NULL;
+}
+
+
+static void
+_gth_file_list_remove_op (GthFileList *file_list,
+ GthFileListOpType op_type)
+{
+ GList *scan;
+
+ scan = file_list->priv->queue;
+ while (scan != NULL) {
+ GthFileListOp *op = scan->data;
+
+ if (op->type != op_type) {
+ scan = scan->next;
+ continue;
+ }
+
+ file_list->priv->queue = g_list_remove_link (file_list->priv->queue, scan);
+ gth_file_list_op_free (op);
+ g_list_free (scan);
+
+ scan = file_list->priv->queue;
+ }
+}
+
+
+static void
+_gth_file_list_queue_op (GthFileList *file_list,
+ GthFileListOp *op)
+{
+ if ((op->type == GTH_FILE_LIST_OP_TYPE_SET_FILES) || (op->type == GTH_FILE_LIST_OP_TYPE_CLEAR_FILES))
+ _gth_file_list_clear_queue (file_list);
+ if (op->type == GTH_FILE_LIST_OP_TYPE_SET_FILTER)
+ _gth_file_list_remove_op (file_list, GTH_FILE_LIST_OP_TYPE_SET_FILTER);
+ file_list->priv->queue = g_list_append (file_list->priv->queue, op);
+
+ if (! file_list->priv->loading_thumbs)
+ _gth_file_list_exec_next_op (file_list);
+}
+
+
+/* -- gth_file_list -- */
+
+
+static GtkHBoxClass *parent_class = NULL;
+
+
+static void
+gth_file_list_finalize (GObject *object)
+{
+ GthFileList *file_list;
+
+ file_list = GTH_FILE_LIST (object);
+
+ if (file_list->priv != NULL) {
+ if (file_list->priv->icon_cache != NULL)
+ gth_icon_cache_free (file_list->priv->icon_cache);
+ g_free (file_list->priv);
+ file_list->priv = NULL;
+ }
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+
+static void
+gth_file_list_class_init (GthFileListClass *class)
+{
+ GObjectClass *object_class;
+
+ parent_class = g_type_class_peek_parent (class);
+ object_class = (GObjectClass*) class;
+
+ object_class->finalize = gth_file_list_finalize;
+}
+
+
+static void
+gth_file_list_init (GthFileList *file_list)
+{
+ file_list->priv = g_new0 (GthFileListPrivateData, 1);
+
+ file_list->priv->thumb_size = DEFAULT_THUMBNAIL_SIZE;
+ file_list->priv->ignore_hidden_thumbs = FALSE;
+ file_list->priv->load_thumbs = TRUE; /* FIXME: make this cnfigurable */
+}
+
+
+static void _gth_file_list_update_next_thumb (GthFileList *file_list);
+
+
+static void
+update_thumb_in_file_view (GthFileList *file_list)
+{
+ GthFileStore *file_store;
+ GdkPixbuf *pixbuf;
+
+ file_store = (GthFileStore *) gth_file_view_get_model (GTH_FILE_VIEW (file_list->priv->view));
+
+ pixbuf = gth_thumb_loader_get_pixbuf (file_list->priv->thumb_loader);
+ if (pixbuf != NULL)
+ gth_file_store_queue_set (file_store,
+ gth_file_store_get_abs_pos (file_store, file_list->priv->thumb_pos),
+ NULL,
+ pixbuf,
+ FALSE,
+ NULL);
+
+ if (file_list->priv->n_thumb % N_THUMBS_PER_NOTIFICATION == N_THUMBS_PER_NOTIFICATION - 1)
+ gth_file_store_exec_set (file_store);
+}
+
+
+static void
+thumb_loader_ready_cb (GthThumbLoader *tloader,
+ GError *error,
+ gpointer data)
+{
+ GthFileList *file_list = data;
+
+ if (file_list->priv->thumb_fd != NULL) {
+ if (error == NULL) {
+ file_list->priv->thumb_fd->error = FALSE;
+ file_list->priv->thumb_fd->thumb_created = TRUE;
+ if (file_list->priv->update_thumb_in_view) {
+ file_list->priv->thumb_fd->thumb_loaded = TRUE;
+ update_thumb_in_file_view (file_list);
+ }
+ }
+ else {
+ file_list->priv->thumb_fd->error = TRUE;
+ file_list->priv->thumb_fd->thumb_loaded = FALSE;
+ file_list->priv->thumb_fd->thumb_created = FALSE;
+ }
+ }
+ _gth_file_list_update_next_thumb (file_list);
+}
+
+
+static void
+start_update_next_thumb (GthFileList *file_list)
+{
+ GthFileStore *file_store;
+
+ if (file_list->priv->loading_thumbs)
+ return;
+
+ if (! file_list->priv->load_thumbs) {
+ file_list->priv->loading_thumbs = FALSE;
+ return;
+ }
+
+ file_store = (GthFileStore*) gth_file_view_get_model (GTH_FILE_VIEW (file_list->priv->view));
+ file_list->priv->n_thumb = -1;
+ file_list->priv->loading_thumbs = TRUE;
+ _gth_file_list_update_next_thumb (file_list);
+}
+
+
+static void
+vadj_changed_cb (GtkAdjustment *adjustment,
+ gpointer user_data)
+{
+ GthFileList *file_list = user_data;
+
+ start_update_next_thumb (file_list);
+}
+
+
+static void
+gth_file_list_construct (GthFileList *file_list)
+{
+ GtkWidget *scrolled;
+ GtkAdjustment *vadj;
+ GtkWidget *viewport;
+ GtkCellRenderer *renderer;
+ GthFileStore *model;
+
+ /* thumbnail loader */
+
+ file_list->priv->thumb_loader = gth_thumb_loader_new (file_list->priv->thumb_size, file_list->priv->thumb_size);
+ g_signal_connect (G_OBJECT (file_list->priv->thumb_loader),
+ "ready",
+ G_CALLBACK (thumb_loader_ready_cb),
+ file_list);
+
+ /* other data */
+
+ file_list->priv->icon_cache = gth_icon_cache_new (gtk_icon_theme_get_for_screen (gtk_widget_get_screen (GTK_WIDGET (file_list))), file_list->priv->thumb_size / 2);
+
+ /* the main notebook */
+
+ file_list->priv->notebook = gth_dumb_notebook_new ();
+
+ /* the message pane */
+
+ viewport = gtk_viewport_new (NULL, NULL);
+ gtk_viewport_set_shadow_type (GTK_VIEWPORT (viewport),
+ GTK_SHADOW_ETCHED_IN);
+
+ file_list->priv->message = gth_empty_list_new (_(EMPTY));
+
+ /* the file view */
+
+ scrolled = gtk_scrolled_window_new (NULL, NULL);
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled),
+ GTK_POLICY_NEVER,
+ GTK_POLICY_AUTOMATIC);
+ gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled),
+ GTK_SHADOW_ETCHED_IN);
+
+ vadj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (scrolled));
+ g_signal_connect (G_OBJECT (vadj),
+ "changed",
+ G_CALLBACK (vadj_changed_cb),
+ file_list);
+ g_signal_connect (G_OBJECT (vadj),
+ "value-changed",
+ G_CALLBACK (vadj_changed_cb),
+ file_list);
+
+ model = gth_file_store_new ();
+ file_list->priv->view = gth_icon_view_new_with_model (GTK_TREE_MODEL (model));
+ g_object_unref (model);
+
+ /* thumbnail */
+
+ file_list->priv->thumbnail_renderer = renderer = gth_cell_renderer_thumbnail_new ();
+ g_object_set (renderer,
+ "size", file_list->priv->thumb_size,
+ "yalign", 1.0,
+ NULL);
+ gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (file_list->priv->view), renderer, FALSE);
+ gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (file_list->priv->view),
+ renderer,
+ "thumbnail", GTH_FILE_STORE_THUMBNAIL_COLUMN,
+ "is_icon", GTH_FILE_STORE_IS_ICON_COLUMN,
+ "file", GTH_FILE_STORE_FILE_COLUMN,
+ NULL);
+
+ /* text */
+
+ file_list->priv->text_renderer = renderer = gtk_cell_renderer_text_new ();
+ g_object_set (G_OBJECT (renderer),
+ "ellipsize", PANGO_ELLIPSIZE_END,
+ "alignment", PANGO_ALIGN_CENTER,
+ "width", file_list->priv->thumb_size + (8 * 2) /* FIXME: remove the numbers */,
+ "single-paragraph-mode", TRUE,
+ NULL);
+
+ gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (file_list->priv->view), renderer, FALSE);
+ gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (file_list->priv->view),
+ renderer,
+ "text", GTH_FILE_STORE_METADATA_COLUMN,
+ NULL);
+
+ /* pack the widgets together */
+
+ gtk_widget_show (file_list->priv->view);
+ gtk_container_add (GTK_CONTAINER (scrolled), file_list->priv->view);
+
+ gtk_widget_show (scrolled);
+ gtk_container_add (GTK_CONTAINER (file_list->priv->notebook), scrolled);
+
+ gtk_widget_show (file_list->priv->message);
+ gtk_container_add (GTK_CONTAINER (viewport), file_list->priv->message);
+
+ gtk_widget_show (viewport);
+ gtk_container_add (GTK_CONTAINER (file_list->priv->notebook), viewport);
+
+ gtk_widget_show (file_list->priv->notebook);
+ gtk_box_pack_start (GTK_BOX (file_list), file_list->priv->notebook, TRUE, TRUE, 0);
+}
+
+
+GType
+gth_file_list_get_type (void)
+{
+ static GType type = 0;
+
+ if (! type) {
+ GTypeInfo type_info = {
+ sizeof (GthFileListClass),
+ NULL,
+ NULL,
+ (GClassInitFunc) gth_file_list_class_init,
+ NULL,
+ NULL,
+ sizeof (GthFileList),
+ 0,
+ (GInstanceInitFunc) gth_file_list_init
+ };
+
+ type = g_type_register_static (GTK_TYPE_VBOX,
+ "GthFileList",
+ &type_info,
+ 0);
+ }
+
+ return type;
+}
+
+
+GtkWidget*
+gth_file_list_new (void)
+{
+ GtkWidget *widget;
+
+ widget = GTK_WIDGET (g_object_new (GTH_TYPE_FILE_LIST, NULL));
+ gth_file_list_construct (GTH_FILE_LIST (widget));
+
+ return widget;
+}
+
+
+static void
+_gth_file_list_thumb_cleanup (GthFileList *file_list)
+{
+ _g_object_unref (file_list->priv->thumb_fd);
+ file_list->priv->thumb_fd = NULL;
+}
+
+
+static void
+_gth_file_list_done (GthFileList *file_list)
+{
+ _gth_file_list_thumb_cleanup (file_list);
+ file_list->priv->loading_thumbs = FALSE;
+ file_list->priv->cancel = FALSE;
+}
+
+
+static void
+cancel_step2 (gpointer user_data)
+{
+ GthFileList *file_list = user_data;
+
+ _gth_file_list_done (file_list);
+
+ if (file_list->priv->done_func)
+ (file_list->priv->done_func) (file_list->priv->done_func_data);
+}
+
+
+void
+gth_file_list_cancel (GthFileList *file_list,
+ DoneFunc done_func,
+ gpointer user_data)
+{
+ _gth_file_list_clear_queue (file_list);
+
+ file_list->priv->done_func = done_func;
+ file_list->priv->done_func_data = user_data;
+ gth_thumb_loader_cancel (file_list->priv->thumb_loader, cancel_step2, file_list);
+}
+
+
+static void
+gfl_clear_list (GthFileList *file_list,
+ const char *message)
+{
+ GthFileStore *file_store;
+
+ file_store = (GthFileStore*) gth_file_view_get_model (GTH_FILE_VIEW (file_list->priv->view));
+ gth_file_store_clear (file_store);
+
+ gth_empty_list_set_text (GTH_EMPTY_LIST (file_list->priv->message), message);
+ gth_dumb_notebook_show_child (GTH_DUMB_NOTEBOOK (file_list->priv->notebook), GTH_FILE_LIST_PANE_MESSAGE);
+}
+
+
+void
+gth_file_list_clear (GthFileList *file_list,
+ const char *message)
+{
+ GthFileListOp *op;
+
+ op = gth_file_list_op_new (GTH_FILE_LIST_OP_TYPE_CLEAR_FILES);
+ op->sval = g_strdup (message != NULL ? message : _(EMPTY));
+ _gth_file_list_queue_op (file_list, op);
+}
+
+
+static void
+_gth_file_list_update_pane (GthFileList *file_list)
+{
+ GthFileStore *file_store;
+
+ file_store = (GthFileStore*) gth_file_view_get_model (GTH_FILE_VIEW (file_list->priv->view));
+
+ if (gth_file_store_n_visibles (file_store) > 0) {
+ gth_dumb_notebook_show_child (GTH_DUMB_NOTEBOOK (file_list->priv->notebook), GTH_FILE_LIST_PANE_VIEW);
+ }
+ else {
+ gth_empty_list_set_text (GTH_EMPTY_LIST (file_list->priv->message), _(EMPTY));
+ gth_dumb_notebook_show_child (GTH_DUMB_NOTEBOOK (file_list->priv->notebook), GTH_FILE_LIST_PANE_MESSAGE);
+ }
+}
+
+
+static void
+gfl_add_files (GthFileList *file_list,
+ GList *files)
+{
+ GthFileStore *file_store;
+ GList *scan;
+
+ file_store = (GthFileStore*) gth_file_view_get_model (GTH_FILE_VIEW (file_list->priv->view));
+
+ for (scan = files; scan; scan = scan->next) {
+ GthFileData *fd = scan->data;
+ GIcon *icon;
+ GdkPixbuf *pixbuf = NULL;
+
+ if (g_file_info_get_file_type (fd->info) != G_FILE_TYPE_REGULAR)
+ continue;
+
+ if (gth_file_store_find (file_store, fd->file) >= 0)
+ continue;
+
+ icon = g_file_info_get_icon (fd->info);
+ pixbuf = gth_icon_cache_get_pixbuf (file_list->priv->icon_cache, icon);
+
+ gth_file_store_queue_add (file_store,
+ fd,
+ pixbuf,
+ TRUE,
+ /* FIXME: make this user configurable */
+ g_file_info_get_attribute_string (fd->info, "standard::display-name"));
+
+ if (pixbuf != NULL)
+ g_object_unref (pixbuf);
+ }
+
+ gth_file_store_exec_add (file_store);
+ _gth_file_list_update_pane (file_list);
+}
+
+
+void
+gth_file_list_add_files (GthFileList *file_list,
+ GList *files)
+{
+ GthFileListOp *op;
+
+ op = gth_file_list_op_new (GTH_FILE_LIST_OP_TYPE_ADD_FILES);
+ op->file_list = _g_object_list_ref (files);
+ _gth_file_list_queue_op (file_list, op);
+}
+
+
+static void
+gfl_delete_files (GthFileList *file_list,
+ GList *files)
+{
+ GthFileStore *file_store;
+ GList *scan;
+
+ file_store = (GthFileStore*) gth_file_view_get_model (GTH_FILE_VIEW (file_list->priv->view));
+ for (scan = files; scan; scan = scan->next) {
+ GFile *file = scan->data;
+ int abs_pos;
+
+ abs_pos = gth_file_store_find (file_store, file);
+ if (abs_pos >= 0)
+ gth_file_store_queue_remove (file_store, abs_pos);
+ }
+ gth_file_store_exec_remove (file_store);
+ _gth_file_list_update_pane (file_list);
+}
+
+
+void
+gth_file_list_delete_files (GthFileList *file_list,
+ GList *files)
+{
+ GthFileListOp *op;
+
+ op = gth_file_list_op_new (GTH_FILE_LIST_OP_TYPE_DELETE_FILES);
+ op->files = _g_object_list_ref (files);
+ _gth_file_list_queue_op (file_list, op);
+}
+
+
+static void
+gfl_update_files (GthFileList *file_list,
+ GList *files)
+{
+ GthFileStore *file_store;
+ GList *scan;
+
+ file_store = (GthFileStore*) gth_file_view_get_model (GTH_FILE_VIEW (file_list->priv->view));
+ for (scan = files; scan; scan = scan->next) {
+ GthFileData *fd = scan->data;
+ int abs_pos;
+
+ abs_pos = gth_file_store_find (file_store, fd->file);
+ if (abs_pos >= 0)
+ gth_file_store_queue_set (file_store,
+ abs_pos,
+ fd,
+ NULL,
+ FALSE,
+ NULL);
+ }
+ gth_file_store_exec_set (file_store);
+ _gth_file_list_update_pane (file_list);
+}
+
+
+void
+gth_file_list_update_files (GthFileList *file_list,
+ GList *files)
+{
+ GthFileListOp *op;
+
+ op = gth_file_list_op_new (GTH_FILE_LIST_OP_TYPE_UPDATE_FILES);
+ op->file_list = _g_object_list_ref (files);
+ _gth_file_list_queue_op (file_list, op);
+}
+
+
+static void
+gfl_set_files (GthFileList *file_list,
+ GthFileSource *file_source,
+ GList *files)
+{
+ GthFileStore *file_store;
+
+ if (file_list->priv->file_source != NULL) {
+ g_object_unref (file_list->priv->file_source);
+ file_list->priv->file_source = NULL;
+ }
+ if (file_source != NULL)
+ file_list->priv->file_source = g_object_ref (file_source);
+
+ file_store = (GthFileStore*) gth_file_view_get_model (GTH_FILE_VIEW (file_list->priv->view));
+ gth_file_store_clear (file_store);
+ gfl_add_files (file_list, files);
+}
+
+
+void
+gth_file_list_set_files (GthFileList *file_list,
+ GthFileSource *file_source,
+ GList *files)
+{
+ GthFileListOp *op;
+
+ if (files == NULL) {
+ op = gth_file_list_op_new (GTH_FILE_LIST_OP_TYPE_CLEAR_FILES);
+ op->sval = g_strdup (_(EMPTY));
+ _gth_file_list_queue_op (file_list, op);
+ }
+ else {
+ op = gth_file_list_op_new (GTH_FILE_LIST_OP_TYPE_SET_FILES);
+ op->file_source = g_object_ref (file_source);
+ op->file_list = _g_object_list_ref (files);
+ _gth_file_list_queue_op (file_list, op);
+ }
+}
+
+
+GList *
+gth_file_list_get_files (GthFileList *file_list,
+ GList *items)
+{
+ GList *list = NULL;
+ GthFileStore *file_store;
+ GList *scan;
+
+ file_store = (GthFileStore*) gth_file_view_get_model (GTH_FILE_VIEW (file_list->priv->view));
+ for (scan = items; scan; scan = scan->next) {
+ GtkTreePath *tree_path = scan->data;
+ GtkTreeIter iter;
+ GthFileData *file_data;
+
+ if (! gtk_tree_model_get_iter (GTK_TREE_MODEL (file_store), &iter, tree_path))
+ continue;
+ file_data = gth_file_store_get_file (file_store, &iter);
+ if (file_data != NULL)
+ list = g_list_prepend (list, g_object_ref (file_data));
+ }
+
+ return g_list_reverse (list);
+}
+
+
+static void
+gfl_set_filter (GthFileList *file_list,
+ GthTest *filter)
+{
+ GthFileStore *file_store;
+
+ file_store = (GthFileStore*) gth_file_view_get_model (GTH_FILE_VIEW (file_list->priv->view));
+ if (file_store != NULL)
+ gth_file_store_set_filter (file_store, filter);
+ _gth_file_list_update_pane (file_list);
+}
+
+
+void
+gth_file_list_set_filter (GthFileList *file_list,
+ GthTest *filter)
+{
+ GthFileListOp *op;
+
+ op = gth_file_list_op_new (GTH_FILE_LIST_OP_TYPE_SET_FILTER);
+ if (filter != NULL)
+ op->filter = g_object_ref (filter);
+ else
+ op->filter = gth_test_new ();
+ _gth_file_list_queue_op (file_list, op);
+}
+
+
+static void
+gfl_set_sort_func (GthFileList *file_list,
+ GthFileDataCompFunc cmp_func,
+ gboolean inverse_sort)
+{
+ GthFileStore *file_store;
+
+ file_store = (GthFileStore*) gth_file_view_get_model (GTH_FILE_VIEW (file_list->priv->view));
+ if (file_store != NULL)
+ gth_file_store_set_sort_func (file_store, cmp_func, inverse_sort);
+}
+
+
+void
+gth_file_list_set_sort_func (GthFileList *file_list,
+ GthFileDataCompFunc cmp_func,
+ gboolean inverse_sort)
+{
+ GthFileListOp *op;
+
+ op = gth_file_list_op_new (GTH_FILE_LIST_OP_TYPE_SET_SORT_FUNC);
+ op->cmp_func = cmp_func;
+ op->inverse_sort = inverse_sort;
+ _gth_file_list_queue_op (file_list, op);
+}
+
+
+static void
+gfl_enable_thumbs (GthFileList *file_list,
+ gboolean enable)
+{
+ GthFileStore *file_store;
+ GList *files, *scan;
+ int pos;
+
+ file_list->priv->load_thumbs = enable;
+
+ file_store = (GthFileStore*) gth_file_view_get_model (GTH_FILE_VIEW (file_list->priv->view));
+ files = gth_file_store_get_all (file_store);
+ pos = 0;
+ for (scan = files; scan; scan = scan->next) {
+ GthFileData *fd = scan->data;
+ GIcon *icon;
+ GdkPixbuf *pixbuf = NULL;
+
+ fd->thumb_loaded = FALSE;
+ fd->thumb_created = FALSE;
+ fd->error = FALSE;
+
+ icon = g_file_info_get_icon (fd->info);
+ pixbuf = gth_icon_cache_get_pixbuf (file_list->priv->icon_cache, icon);
+
+ gth_file_store_set (file_store, pos, NULL, pixbuf, TRUE, NULL);
+
+ if (pixbuf != NULL)
+ g_object_unref (pixbuf);
+
+ pos++;
+ }
+ _g_object_list_unref (files);
+
+ start_update_next_thumb (file_list);
+}
+
+
+void
+gth_file_list_enable_thumbs (GthFileList *file_list,
+ gboolean enable)
+{
+ GthFileListOp *op;
+
+ op = gth_file_list_op_new (GTH_FILE_LIST_OP_TYPE_ENABLE_THUMBS);
+ op->ival = enable;
+ _gth_file_list_queue_op (file_list, op);
+}
+
+
+void
+gth_file_list_set_thumb_size (GthFileList *file_list,
+ int size)
+{
+ file_list->priv->thumb_size = size;
+ gth_thumb_loader_set_thumb_size (file_list->priv->thumb_loader, size, size);
+
+ gth_icon_cache_free (file_list->priv->icon_cache);
+ file_list->priv->icon_cache = gth_icon_cache_new (gtk_icon_theme_get_for_screen (gtk_widget_get_screen (GTK_WIDGET (file_list))), size / 2);
+
+ g_object_set (file_list->priv->thumbnail_renderer,
+ "size", size,
+ NULL);
+ g_object_set (file_list->priv->text_renderer,
+ "width", size + (8 * 2),
+ NULL);
+}
+
+
+GtkWidget *
+gth_file_list_get_view (GthFileList *file_list)
+{
+ return file_list->priv->view;
+}
+
+
+/* thumbs */
+
+
+static void
+_gth_file_list_thumbs_completed (GthFileList *file_list)
+{
+ GthFileStore *file_store;
+
+ file_store = (GthFileStore *) gth_file_view_get_model (GTH_FILE_VIEW (file_list->priv->view));
+ if (file_list->priv->n_thumb >= 0)
+ gth_file_store_exec_set (file_store);
+
+ _gth_file_list_done (file_list);
+}
+
+
+static void
+_gth_file_list_update_current_thumb (GthFileList *file_list)
+{
+ gth_thumb_loader_set_file (file_list->priv->thumb_loader, file_list->priv->thumb_fd);
+ gth_thumb_loader_load (file_list->priv->thumb_loader);
+}
+
+
+static gboolean
+update_thumbs_stopped (gpointer callback_data)
+{
+ GthFileList *file_list = callback_data;
+
+ file_list->priv->loading_thumbs = FALSE;
+ _gth_file_list_exec_next_op (file_list);
+
+ return FALSE;
+}
+
+
+static void
+_gth_file_list_update_next_thumb (GthFileList *file_list)
+{
+ GthFileStore *file_store;
+ int pos;
+ int first_pos;
+ int last_pos;
+ int max_pos;
+ GthFileData *fd = NULL;
+ GList *list, *scan;
+ int new_pos = -1;
+
+ if (file_list->priv->cancel || (file_list->priv->queue != NULL)) {
+ g_idle_add (update_thumbs_stopped, file_list);
+ return;
+ }
+
+ file_store = (GthFileStore *) gth_file_view_get_model (GTH_FILE_VIEW (file_list->priv->view));
+
+ /* Find first visible undone. */
+
+ first_pos = gth_file_view_get_first_visible (GTH_FILE_VIEW (file_list->priv->view));
+ if (first_pos < 0)
+ first_pos = 0;
+
+ list = gth_file_store_get_visibles (file_store);
+ max_pos = g_list_length (list) - 1;
+
+ last_pos = gth_file_view_get_last_visible (GTH_FILE_VIEW (file_list->priv->view));
+ if ((last_pos < 0) || (last_pos > max_pos))
+ last_pos = max_pos;
+
+ pos = first_pos;
+ scan = g_list_nth (list, pos);
+ if (scan == NULL) {
+ _g_object_list_unref (list);
+ _gth_file_list_thumbs_completed (file_list);
+ return;
+ }
+
+ /* Find a not loaded thumbnail among the visible images. */
+
+ while (pos <= last_pos) {
+ fd = scan->data;
+ if (! fd->thumb_loaded && ! fd->error) {
+ new_pos = pos;
+ break;
+ }
+ else {
+ pos++;
+ scan = scan->next;
+ }
+ }
+
+ if (! file_list->priv->ignore_hidden_thumbs) {
+
+ /* Find a not created thumbnail among the not-visible images. */
+
+ /* start from the one after the last visible image... */
+
+ if (new_pos == -1) {
+ pos = last_pos + 1;
+ scan = g_list_nth (list, pos);
+ while (scan && ((pos - last_pos) <= N_LOOKAHEAD)) {
+ fd = scan->data;
+ if (! fd->thumb_created && ! fd->error) {
+ new_pos = pos;
+ break;
+ }
+ pos++;
+ scan = scan->next;
+ }
+ }
+
+ /* ...continue from the one before the first visible upward to
+ * the first one */
+
+ if (new_pos == -1) {
+ pos = first_pos - 1;
+ scan = g_list_nth (list, pos);
+ while (scan && ((first_pos - pos) <= N_LOOKAHEAD)) {
+ fd = scan->data;
+ if (! fd->thumb_created && ! fd->error) {
+ new_pos = pos;
+ break;
+ }
+ pos--;
+ scan = scan->prev;
+ }
+ }
+ }
+
+ if (new_pos != -1)
+ fd = g_object_ref (fd);
+
+ _g_object_list_unref (list);
+
+ if (new_pos == -1) {
+ _gth_file_list_thumbs_completed (file_list);
+ return;
+ }
+
+ /* We create thumbnail files for all images in the folder, but we only
+ load the visible ones (and N_LOOKAHEAD before and N_LOOKAHEAD after the visible range),
+ to minimize memory consumption in large folders. */
+ file_list->priv->update_thumb_in_view = (new_pos >= (first_pos - N_LOOKAHEAD)) &&
+ (new_pos <= (last_pos + N_LOOKAHEAD));
+ file_list->priv->thumb_pos = new_pos;
+ _g_object_unref (file_list->priv->thumb_fd);
+ file_list->priv->thumb_fd = fd; /* already ref-ed above */
+ file_list->priv->n_thumb++;
+
+ _gth_file_list_update_current_thumb (file_list);
+}
+
+
+static void
+_gth_file_list_exec_next_op (GthFileList *file_list)
+{
+ GList *first;
+ GthFileListOp *op;
+ gboolean exec_next_op = TRUE;
+
+ if (file_list->priv->queue == NULL) {
+ start_update_next_thumb (file_list);
+ return;
+ }
+
+ first = file_list->priv->queue;
+ file_list->priv->queue = g_list_remove_link (file_list->priv->queue, first);
+
+ op = first->data;
+
+ switch (op->type) {
+ case GTH_FILE_LIST_OP_TYPE_SET_FILES:
+ gfl_set_files (file_list, op->file_source, op->file_list);
+ break;
+ case GTH_FILE_LIST_OP_TYPE_ADD_FILES:
+ gfl_add_files (file_list, op->file_list);
+ break;
+ case GTH_FILE_LIST_OP_TYPE_DELETE_FILES:
+ gfl_delete_files (file_list, op->files);
+ break;
+ case GTH_FILE_LIST_OP_TYPE_UPDATE_FILES:
+ gfl_update_files (file_list, op->file_list);
+ break;
+ case GTH_FILE_LIST_OP_TYPE_ENABLE_THUMBS:
+ gfl_enable_thumbs (file_list, op->ival);
+ exec_next_op = FALSE;
+ break;
+ case GTH_FILE_LIST_OP_TYPE_CLEAR_FILES:
+ gfl_clear_list (file_list, op->sval);
+ break;
+ case GTH_FILE_LIST_OP_TYPE_SET_FILTER:
+ gfl_set_filter (file_list, op->filter);
+ break;
+ case GTH_FILE_LIST_OP_TYPE_SET_SORT_FUNC:
+ gfl_set_sort_func (file_list, op->cmp_func, op->inverse_sort);
+ break;
+ default:
+ exec_next_op = FALSE;
+ break;
+ }
+
+ gth_file_list_op_free (op);
+ g_list_free (first);
+
+ if (exec_next_op)
+ _gth_file_list_exec_next_op (file_list);
+}
+
+
+int
+gth_file_list_first_file (GthFileList *file_list,
+ gboolean skip_broken,
+ gboolean only_selected)
+{
+ GthFileView *view;
+ GList *files;
+ GList *scan;
+ int pos;
+
+ view = GTH_FILE_VIEW (file_list->priv->view);
+ files = gth_file_store_get_visibles (GTH_FILE_STORE (gth_file_view_get_model (view)));
+
+ pos = 0;
+ for (scan = files; scan; scan = scan->next, pos++) {
+ GthFileData *file_data = scan->data;
+
+ if (skip_broken && file_data->error)
+ continue;
+ if (only_selected && ! gth_file_selection_is_selected (GTH_FILE_SELECTION (view), pos))
+ continue;
+
+ return pos;
+ }
+
+ return -1;
+}
+
+
+int
+gth_file_list_last_file (GthFileList *file_list,
+ gboolean skip_broken,
+ gboolean only_selected)
+{
+ GthFileView *view;
+ GList *files;
+ GList *scan;
+ int pos;
+
+ view = GTH_FILE_VIEW (file_list->priv->view);
+ files = gth_file_store_get_visibles (GTH_FILE_STORE (gth_file_view_get_model (view)));
+
+ pos = g_list_length (files) - 1;
+ if (pos < 0)
+ return -1;
+
+ for (scan = g_list_nth (files, pos); scan; scan = scan->prev, pos--) {
+ GthFileData *file_data = scan->data;
+
+ if (skip_broken && file_data->error)
+ continue;
+ if (only_selected && ! gth_file_selection_is_selected (GTH_FILE_SELECTION (view), pos))
+ continue;
+
+ return pos;
+ }
+
+ return -1;
+}
+
+
+int
+gth_file_list_next_file (GthFileList *file_list,
+ int pos,
+ gboolean skip_broken,
+ gboolean only_selected,
+ gboolean wrap)
+{
+ GthFileView *view;
+ GList *files;
+ GList *scan;
+
+ view = GTH_FILE_VIEW (file_list->priv->view);
+ files = gth_file_store_get_visibles (GTH_FILE_STORE (gth_file_view_get_model (view)));
+
+ pos++;
+ if (pos >= 0)
+ scan = g_list_nth (files, pos);
+ else if (wrap)
+ scan = g_list_first (files);
+ else
+ scan = NULL;
+
+ for (/* void */; scan; scan = scan->next, pos++) {
+ GthFileData *file_data = scan->data;
+
+ if (skip_broken && file_data->error)
+ continue;
+ if (only_selected && ! gth_file_selection_is_selected (GTH_FILE_SELECTION (view), pos))
+ continue;
+
+ break;
+ }
+
+ _g_object_list_unref (files);
+
+ return (scan != NULL) ? pos : -1;
+}
+
+
+int
+gth_file_list_prev_file (GthFileList *file_list,
+ int pos,
+ gboolean skip_broken,
+ gboolean only_selected,
+ gboolean wrap)
+{
+ GthFileView *view;
+ GList *files;
+ GList *scan;
+
+ view = GTH_FILE_VIEW (file_list->priv->view);
+ files = gth_file_store_get_visibles (GTH_FILE_STORE (gth_file_view_get_model (view)));
+
+ pos--;
+ if (pos >= 0)
+ scan = g_list_nth (files, pos);
+ else if (wrap) {
+ pos = g_list_length (files) - 1;
+ scan = g_list_nth (files, pos);
+ }
+ else
+ scan = NULL;
+
+ for (/* void */; scan; scan = scan->prev, pos--) {
+ GthFileData *file_data = scan->data;
+
+ if (skip_broken && file_data->error)
+ continue;
+ if (only_selected && ! gth_file_selection_is_selected (GTH_FILE_SELECTION (view), pos))
+ continue;
+
+ break;
+ }
+
+ _g_object_list_unref (files);
+
+ return (scan != NULL) ? pos : -1;
+}
+
diff --git a/gthumb/gth-file-list.h b/gthumb/gth-file-list.h
new file mode 100644
index 0000000..222ccc2
--- /dev/null
+++ b/gthumb/gth-file-list.h
@@ -0,0 +1,101 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2001-2008 The Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GTH_FILE_LIST_H
+#define GTH_FILE_LIST_H
+
+#include <gtk/gtk.h>
+#include "gth-file-data.h"
+#include "gth-file-source.h"
+#include "gth-file-store.h"
+#include "gth-test.h"
+
+G_BEGIN_DECLS
+
+#define GTH_TYPE_FILE_LIST (gth_file_list_get_type ())
+#define GTH_FILE_LIST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTH_TYPE_FILE_LIST, GthFileList))
+#define GTH_FILE_LIST_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTH_TYPE_FILE_LIST, GthFileListClass))
+#define GTH_IS_FILE_LIST(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTH_TYPE_FILE_LIST))
+#define GTH_IS_FILE_LIST_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTH_TYPE_FILE_LIST))
+#define GTH_FILE_LIST_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GTH_TYPE_FILE_LIST, GthFileListClass))
+
+typedef struct _GthFileList GthFileList;
+typedef struct _GthFileListClass GthFileListClass;
+typedef struct _GthFileListPrivateData GthFileListPrivateData;
+
+struct _GthFileList {
+ GtkVBox __parent;
+ GthFileListPrivateData *priv;
+};
+
+struct _GthFileListClass {
+ GtkVBoxClass __parent;
+};
+
+GType gth_file_list_get_type (void);
+GtkWidget * gth_file_list_new (void);
+void gth_file_list_cancel (GthFileList *file_list,
+ DoneFunc done_func,
+ gpointer user_data);
+void gth_file_list_set_files (GthFileList *file_list,
+ GthFileSource *file_source,
+ GList *list);
+GList * gth_file_list_get_files (GthFileList *file_list,
+ GList *tree_path_list);
+void gth_file_list_clear (GthFileList *file_list,
+ const char *message);
+void gth_file_list_add_files (GthFileList *file_list,
+ GList *list /* GthFileData */);
+void gth_file_list_delete_files (GthFileList *file_list,
+ GList *list /* GFile */);
+void gth_file_list_update_files (GthFileList *file_list,
+ GList *list /* GthFileData */);
+void gth_file_list_set_filter (GthFileList *file_list,
+ GthTest *filter);
+void gth_file_list_set_sort_func (GthFileList *file_list,
+ GthFileDataCompFunc cmp_func,
+ gboolean inverse_sort);
+void gth_file_list_enable_thumbs (GthFileList *file_list,
+ gboolean enable);
+void gth_file_list_set_thumb_size (GthFileList *file_list,
+ int size);
+GtkWidget * gth_file_list_get_view (GthFileList *file_list);
+int gth_file_list_first_file (GthFileList *file_list,
+ gboolean skip_broken,
+ gboolean only_selected);
+int gth_file_list_last_file (GthFileList *file_list,
+ gboolean skip_broken,
+ gboolean only_selected);
+int gth_file_list_next_file (GthFileList *file_list,
+ int pos,
+ gboolean skip_broken,
+ gboolean only_selected,
+ gboolean wrap);
+int gth_file_list_prev_file (GthFileList *file_list,
+ int pos,
+ gboolean skip_broken,
+ gboolean only_selected,
+ gboolean wrap);
+
+G_END_DECLS
+
+#endif /* GTH_FILE_LIST_H */
diff --git a/gthumb/gth-file-properties.c b/gthumb/gth-file-properties.c
new file mode 100644
index 0000000..d0d710f
--- /dev/null
+++ b/gthumb/gth-file-properties.c
@@ -0,0 +1,407 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <glib/gi18n.h>
+#include "dlg-edit-metadata.h"
+#include "glib-utils.h"
+#include "gth-file-properties.h"
+#include "gth-main.h"
+#include "gth-multipage.h"
+#include "gth-sidebar.h"
+#include "gth-string-list.h"
+#include "gth-time.h"
+
+
+#define GTH_FILE_PROPERTIES_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GTH_TYPE_FILE_PROPERTIES, GthFilePropertiesPrivate))
+#define FONT_SIZE (8.0)
+#define COMMENT_HEIGHT 150
+#define CATEGORY_SIZE 1000
+
+
+enum {
+ WEIGHT_COLUMN,
+ ID_COLUMN,
+ DISPLAY_NAME_COLUMN,
+ VALUE_COLUMN,
+ TOOLTIP_COLUMN,
+ RAW_COLUMN,
+ POS_COLUMN,
+ WRITEABLE_COLUMN,
+ NUM_COLUMNS
+};
+
+
+static gpointer gth_file_properties_parent_class = NULL;
+
+
+struct _GthFilePropertiesPrivate {
+ GtkWidget *tree_view;
+ GtkWidget *comment_view;
+ GtkWidget *comment_win;
+ GtkListStore *tree_model;
+};
+
+
+static char *
+get_comment (GthFileData *file_data)
+{
+ GString *string;
+ GthMetadata *value;
+ gboolean not_void = FALSE;
+
+ string = g_string_new (NULL);
+
+ value = (GthMetadata *) g_file_info_get_attribute_object (file_data->info, "Embedded::Image::Comment");
+ if (value != NULL) {
+ const char *formatted;
+
+ formatted = gth_metadata_get_formatted (value);
+ if ((formatted != NULL) && (*formatted != '\0')) {
+ g_string_append (string, formatted);
+ not_void = TRUE;
+ }
+ }
+
+ return g_string_free (string, ! not_void);
+}
+
+
+void
+gth_file_properties_real_set_file (GthPropertyView *self,
+ GthFileData *file_data)
+{
+ GthFileProperties *file_properties;
+ GHashTable *category_root;
+ GPtrArray *metadata_info;
+ int i;
+ GtkTextBuffer *text_buffer;
+ char *comment;
+
+ file_properties = GTH_FILE_PROPERTIES (self);
+
+ gtk_list_store_clear (file_properties->priv->tree_model);
+
+ if (file_data == NULL)
+ return;
+
+ category_root = g_hash_table_new_full (g_str_hash, g_str_equal, (GDestroyNotify) g_free, NULL);
+ metadata_info = gth_main_get_all_metadata_info ();
+ for (i = 0; i < metadata_info->len; i++) {
+ GthMetadataInfo *info;
+ GthMetadataCategory *category;
+ GObject *obj;
+ char *value;
+ char *tmp_value;
+ char *tooltip;
+ GtkTreeIter iter;
+
+ info = g_ptr_array_index (metadata_info, i);
+ if ((info->flags & GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW) != GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW)
+ continue;
+
+ if ((info->display_name == NULL) || (strncmp (info->display_name, "0x", 2) == 0))
+ continue;
+
+ category = gth_main_get_metadata_category (info->category);
+
+ switch (g_file_info_get_attribute_type (file_data->info, info->id)) {
+ case G_FILE_ATTRIBUTE_TYPE_OBJECT:
+ obj = g_file_info_get_attribute_object (file_data->info, info->id);
+ if (GTH_IS_METADATA (obj))
+ g_object_get (obj, "formatted", &value, NULL);
+ else if (GTH_IS_STRING_LIST (obj)) {
+ GList *list;
+ GList *scan;
+ GString *str;
+
+ list = gth_string_list_get_list (GTH_STRING_LIST (obj));
+ str = g_string_new ("");
+ for (scan = list; scan; scan = scan->next) {
+ if (scan != list)
+ g_string_append (str, " ");
+ g_string_append (str, (char *) scan->data);
+ }
+ value = g_string_free (str, FALSE);
+ }
+ else
+ value = g_file_info_get_attribute_as_string (file_data->info, info->id);
+ tmp_value = _g_utf8_replace (value, "[\r\n]", " ");
+ g_free (value);
+ value = tmp_value;
+ break;
+ default:
+ value = g_file_info_get_attribute_as_string (file_data->info, info->id);
+ break;
+ }
+
+ if ((value == NULL) || (*value == '\0'))
+ continue;
+
+ tooltip = g_markup_printf_escaped ("%s: %s", info->display_name /*info->id*/, value);
+
+ if (g_hash_table_lookup (category_root, category->id) == NULL) {
+ GtkTreeIter parent;
+
+ gtk_list_store_append (file_properties->priv->tree_model, &parent);
+ gtk_list_store_set (file_properties->priv->tree_model, &parent,
+ WEIGHT_COLUMN, PANGO_WEIGHT_BOLD,
+ ID_COLUMN, category->id,
+ DISPLAY_NAME_COLUMN, category->display_name,
+ POS_COLUMN, category->sort_order * CATEGORY_SIZE,
+ -1);
+ g_hash_table_insert (category_root, g_strdup (category->id), GINT_TO_POINTER (1));
+ }
+
+ gtk_list_store_append (file_properties->priv->tree_model, &iter);
+ gtk_list_store_set (file_properties->priv->tree_model,
+ &iter,
+ ID_COLUMN, info->id,
+ DISPLAY_NAME_COLUMN, info->display_name,
+ VALUE_COLUMN, value,
+ TOOLTIP_COLUMN, tooltip,
+ POS_COLUMN, (category->sort_order * CATEGORY_SIZE) + info->sort_order,
+ -1);
+
+ g_free (tooltip);
+ g_free (value);
+ }
+ gtk_tree_view_expand_all (GTK_TREE_VIEW (file_properties->priv->tree_view));
+
+ g_hash_table_destroy (category_root);
+
+ /* comment */
+
+ text_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (file_properties->priv->comment_view));
+ comment = get_comment (file_data);
+ if (comment != NULL) {
+ GtkTextIter iter;
+ GtkAdjustment *vadj;
+
+ gtk_text_buffer_set_text (text_buffer, comment, strlen (comment));
+ gtk_text_buffer_get_iter_at_line (text_buffer, &iter, 0);
+ gtk_text_buffer_place_cursor (text_buffer, &iter);
+
+ vadj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (file_properties->priv->comment_win));
+ gtk_adjustment_set_value (vadj, 0.0);
+
+ gtk_widget_show (file_properties->priv->comment_win);
+
+ g_free (comment);
+ }
+ else
+ gtk_widget_hide (file_properties->priv->comment_win);
+}
+
+
+const char *
+gth_file_properties_real_get_name (GthMultipageChild *self)
+{
+ return _("Properties");
+}
+
+
+const char *
+gth_file_properties_real_get_icon (GthMultipageChild *self)
+{
+ return GTK_STOCK_PROPERTIES;
+}
+
+
+static void
+gth_file_properties_class_init (GthFilePropertiesClass *klass)
+{
+ gth_file_properties_parent_class = g_type_class_peek_parent (klass);
+ g_type_class_add_private (klass, sizeof (GthFilePropertiesPrivate));
+}
+
+
+static void
+edit_properties_clicked_cb (GtkButton *button,
+ gpointer user_data)
+{
+ GthFileProperties *file_properties = user_data;
+
+ dlg_edit_metadata (GTH_BROWSER (gtk_widget_get_toplevel (GTK_WIDGET (file_properties))));
+}
+
+
+static void
+gth_file_properties_init (GthFileProperties *file_properties)
+{
+ GtkWidget *vpaned;
+ GtkWidget *scrolled_win;
+ GtkCellRenderer *renderer;
+ GtkTreeViewColumn *column;
+ GtkWidget *button;
+
+ file_properties->priv = GTH_FILE_PROPERTIES_GET_PRIVATE (file_properties);
+
+ gtk_box_set_spacing (GTK_BOX (file_properties), 6);
+
+ vpaned = gtk_vpaned_new ();
+ gtk_widget_show (vpaned);
+ gtk_box_pack_start (GTK_BOX (file_properties), vpaned, TRUE, TRUE, 0);
+
+ scrolled_win = gtk_scrolled_window_new (NULL, NULL);
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_win), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
+ gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled_win), GTK_SHADOW_ETCHED_IN);
+ gtk_widget_show (scrolled_win);
+ gtk_paned_pack1 (GTK_PANED (vpaned), scrolled_win, TRUE, TRUE);
+
+ file_properties->priv->tree_view = gtk_tree_view_new ();
+ gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (file_properties->priv->tree_view), FALSE);
+ gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (file_properties->priv->tree_view), TRUE);
+ gtk_tree_view_set_tooltip_column (GTK_TREE_VIEW (file_properties->priv->tree_view), TOOLTIP_COLUMN);
+ file_properties->priv->tree_model = gtk_list_store_new (NUM_COLUMNS,
+ PANGO_TYPE_WEIGHT,
+ G_TYPE_STRING,
+ G_TYPE_STRING,
+ G_TYPE_STRING,
+ G_TYPE_STRING,
+ G_TYPE_STRING,
+ G_TYPE_INT,
+ G_TYPE_BOOLEAN);
+ gtk_tree_view_set_model (GTK_TREE_VIEW (file_properties->priv->tree_view),
+ GTK_TREE_MODEL (file_properties->priv->tree_model));
+ g_object_unref (file_properties->priv->tree_model);
+ gtk_widget_show (file_properties->priv->tree_view);
+ gtk_container_add (GTK_CONTAINER (scrolled_win), file_properties->priv->tree_view);
+
+ /**/
+
+ renderer = gtk_cell_renderer_text_new ();
+ column = gtk_tree_view_column_new_with_attributes ("",
+ renderer,
+ "text", DISPLAY_NAME_COLUMN,
+ "weight", WEIGHT_COLUMN,
+ NULL);
+ g_object_set (renderer,
+ "ellipsize", PANGO_ELLIPSIZE_END,
+ "size-points", FONT_SIZE,
+ NULL);
+
+ gtk_tree_view_column_set_expand (column, TRUE);
+ gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
+ gtk_tree_view_append_column (GTK_TREE_VIEW (file_properties->priv->tree_view),
+ column);
+
+ /**/
+
+ renderer = gtk_cell_renderer_text_new ();
+ column = gtk_tree_view_column_new_with_attributes ("",
+ renderer,
+ "text", VALUE_COLUMN,
+ NULL);
+ g_object_set (renderer,
+ "ellipsize", PANGO_ELLIPSIZE_END,
+ "size-points", FONT_SIZE,
+ NULL);
+
+ gtk_tree_view_column_set_expand (column, TRUE);
+ gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
+ gtk_tree_view_append_column (GTK_TREE_VIEW (file_properties->priv->tree_view),
+ column);
+
+ gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (file_properties->priv->tree_model), POS_COLUMN, GTK_SORT_ASCENDING);
+
+ /* comment */
+
+ file_properties->priv->comment_win = gtk_scrolled_window_new (NULL, NULL);
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (file_properties->priv->comment_win), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
+ gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (file_properties->priv->comment_win), GTK_SHADOW_ETCHED_IN);
+ gtk_paned_pack2 (GTK_PANED (vpaned), file_properties->priv->comment_win, FALSE, TRUE);
+
+ file_properties->priv->comment_view = gtk_text_view_new ();
+ gtk_text_view_set_editable (GTK_TEXT_VIEW (file_properties->priv->comment_view), FALSE);
+ gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (file_properties->priv->comment_view), GTK_WRAP_WORD);
+ gtk_text_view_set_cursor_visible (GTK_TEXT_VIEW (file_properties->priv->comment_view), TRUE);
+ gtk_widget_set_size_request (file_properties->priv->comment_view, -1, COMMENT_HEIGHT);
+ gtk_widget_show (file_properties->priv->comment_view);
+ gtk_container_add (GTK_CONTAINER (file_properties->priv->comment_win), file_properties->priv->comment_view);
+
+ /* edit button */
+
+ button = gtk_button_new_from_stock (GTK_STOCK_EDIT);
+ /* FIXME gtk_widget_show (button); */
+ gtk_box_pack_start (GTK_BOX (file_properties), button, FALSE, FALSE, 0);
+
+ g_signal_connect (button,
+ "clicked",
+ G_CALLBACK (edit_properties_clicked_cb),
+ file_properties);
+}
+
+
+static void
+gth_file_properties_gth_multipage_child_interface_init (GthMultipageChildIface *iface)
+{
+ iface->get_name = gth_file_properties_real_get_name;
+ iface->get_icon = gth_file_properties_real_get_icon;
+}
+
+
+static void
+gth_file_properties_gth_property_view_interface_init (GthPropertyViewIface *iface)
+{
+ iface->set_file = gth_file_properties_real_set_file;
+}
+
+
+GType
+gth_file_properties_get_type (void)
+{
+ static GType type = 0;
+
+ if (type == 0) {
+ static const GTypeInfo g_define_type_info = {
+ sizeof (GthFilePropertiesClass),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) gth_file_properties_class_init,
+ (GClassFinalizeFunc) NULL,
+ NULL,
+ sizeof (GthFileProperties),
+ 0,
+ (GInstanceInitFunc) gth_file_properties_init,
+ NULL
+ };
+ static const GInterfaceInfo gth_multipage_child_info = {
+ (GInterfaceInitFunc) gth_file_properties_gth_multipage_child_interface_init,
+ (GInterfaceFinalizeFunc) NULL,
+ NULL
+ };
+ static const GInterfaceInfo gth_property_view_info = {
+ (GInterfaceInitFunc) gth_file_properties_gth_property_view_interface_init,
+ (GInterfaceFinalizeFunc) NULL,
+ NULL
+ };
+ type = g_type_register_static (GTK_TYPE_VBOX,
+ "GthFileProperties",
+ &g_define_type_info,
+ 0);
+ g_type_add_interface_static (type, GTH_TYPE_MULTIPAGE_CHILD, >h_multipage_child_info);
+ g_type_add_interface_static (type, GTH_TYPE_PROPERTY_VIEW, >h_property_view_info);
+ }
+
+ return type;
+}
diff --git a/gthumb/gth-file-properties.h b/gthumb/gth-file-properties.h
new file mode 100644
index 0000000..8a2d6d2
--- /dev/null
+++ b/gthumb/gth-file-properties.h
@@ -0,0 +1,55 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GTH_FILE_PROPERTIES_H
+#define GTH_FILE_PROPERTIES_H
+
+#include <gtk/gtk.h>
+#include "gth-file-data.h"
+
+G_BEGIN_DECLS
+
+#define GTH_TYPE_FILE_PROPERTIES (gth_file_properties_get_type ())
+#define GTH_FILE_PROPERTIES(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTH_TYPE_FILE_PROPERTIES, GthFileProperties))
+#define GTH_FILE_PROPERTIES_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTH_TYPE_FILE_PROPERTIES, GthFilePropertiesClass))
+#define GTH_IS_FILE_PROPERTIES(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTH_TYPE_FILE_PROPERTIES))
+#define GTH_IS_FILE_PROPERTIES_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTH_TYPE_FILE_PROPERTIES))
+#define GTH_FILE_PROPERTIES_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTH_TYPE_FILE_PROPERTIES, GthFilePropertiesClass))
+
+typedef struct _GthFileProperties GthFileProperties;
+typedef struct _GthFilePropertiesClass GthFilePropertiesClass;
+typedef struct _GthFilePropertiesPrivate GthFilePropertiesPrivate;
+
+struct _GthFileProperties {
+ GtkVBox parent_instance;
+ GthFilePropertiesPrivate *priv;
+};
+
+struct _GthFilePropertiesClass {
+ GtkVBoxClass parent_class;
+};
+
+GType gth_file_properties_get_type (void);
+
+G_END_DECLS
+
+#endif /* GTH_FILE_PROPERTIES_H */
diff --git a/gthumb/gth-file-selection.c b/gthumb/gth-file-selection.c
new file mode 100644
index 0000000..81076cc
--- /dev/null
+++ b/gthumb/gth-file-selection.c
@@ -0,0 +1,116 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2008 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include "gth-file-selection.h"
+
+
+GList *
+gth_file_selection_get_selected (GthFileSelection *self)
+{
+ return GTH_FILE_SELECTION_GET_INTERFACE (self)->get_selected (self);
+}
+
+
+void
+gth_file_selection_select (GthFileSelection *self,
+ int pos)
+{
+ GTH_FILE_SELECTION_GET_INTERFACE (self)->select (self, pos);
+}
+
+
+void
+gth_file_selection_unselect (GthFileSelection *self,
+ int pos)
+{
+ GTH_FILE_SELECTION_GET_INTERFACE (self)->unselect (self, pos);
+}
+
+
+void
+gth_file_selection_select_all (GthFileSelection *self)
+{
+ GTH_FILE_SELECTION_GET_INTERFACE (self)->select_all (self);
+}
+
+
+void
+gth_file_selection_unselect_all (GthFileSelection *self)
+{
+ GTH_FILE_SELECTION_GET_INTERFACE (self)->unselect_all (self);
+}
+
+
+gboolean
+gth_file_selection_is_selected (GthFileSelection *self,
+ int pos)
+{
+ return GTH_FILE_SELECTION_GET_INTERFACE (self)->is_selected (self, pos);
+}
+
+
+GtkTreePath *
+gth_file_selection_get_first_selected (GthFileSelection *self)
+{
+ return GTH_FILE_SELECTION_GET_INTERFACE (self)->get_first_selected (self);
+}
+
+
+GtkTreePath *
+gth_file_selection_get_last_selected (GthFileSelection *self)
+{
+ return GTH_FILE_SELECTION_GET_INTERFACE (self)->get_last_selected (self);
+}
+
+
+guint
+gth_file_selection_get_n_selected (GthFileSelection *self)
+{
+ return GTH_FILE_SELECTION_GET_INTERFACE (self)->get_n_selected (self);
+}
+
+
+GType
+gth_file_selection_get_type (void)
+{
+ static GType type = 0;
+
+ if (type == 0) {
+ static const GTypeInfo g_define_type_info = {
+ sizeof (GthFileSelectionIface),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) NULL,
+ (GClassFinalizeFunc) NULL,
+ NULL,
+ 0,
+ 0,
+ (GInstanceInitFunc) NULL,
+ NULL
+ };
+ type = g_type_register_static (G_TYPE_INTERFACE,
+ "GthFileSelection",
+ &g_define_type_info,
+ 0);
+ }
+ return type;
+}
diff --git a/gthumb/gth-file-selection.h b/gthumb/gth-file-selection.h
new file mode 100644
index 0000000..20fb749
--- /dev/null
+++ b/gthumb/gth-file-selection.h
@@ -0,0 +1,75 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2008-2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GTH_FILE_SELECTION_H
+#define GTH_FILE_SELECTION_H
+
+#include <glib.h>
+#include <glib-object.h>
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GTH_TYPE_FILE_SELECTION (gth_file_selection_get_type ())
+#define GTH_FILE_SELECTION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTH_TYPE_FILE_SELECTION, GthFileSelection))
+#define GTH_IS_FILE_SELECTION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTH_TYPE_FILE_SELECTION))
+#define GTH_FILE_SELECTION_GET_INTERFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), GTH_TYPE_FILE_SELECTION, GthFileSelectionIface))
+
+typedef struct _GthFileSelection GthFileSelection;
+typedef struct _GthFileSelectionIface GthFileSelectionIface;
+
+struct _GthFileSelectionIface {
+ GTypeInterface parent_iface;
+
+ /*< virtual functions >*/
+
+ GList * (*get_selected) (GthFileSelection *self);
+ void (*select) (GthFileSelection *self,
+ int pos);
+ void (*unselect) (GthFileSelection *self,
+ int pos);
+ void (*select_all) (GthFileSelection *self);
+ void (*unselect_all) (GthFileSelection *self);
+ gboolean (*is_selected) (GthFileSelection *self,
+ int pos);
+ GtkTreePath * (*get_first_selected) (GthFileSelection *self);
+ GtkTreePath * (*get_last_selected) (GthFileSelection *self);
+ guint (*get_n_selected) (GthFileSelection *self);
+};
+
+GType gth_file_selection_get_type (void);
+GList * gth_file_selection_get_selected (GthFileSelection *self);
+void gth_file_selection_select (GthFileSelection *self,
+ int pos);
+void gth_file_selection_unselect (GthFileSelection *self,
+ int pos);
+void gth_file_selection_select_all (GthFileSelection *self);
+void gth_file_selection_unselect_all (GthFileSelection *self);
+gboolean gth_file_selection_is_selected (GthFileSelection *self,
+ int pos);
+GtkTreePath * gth_file_selection_get_first_selected (GthFileSelection *self);
+GtkTreePath * gth_file_selection_get_last_selected (GthFileSelection *self);
+guint gth_file_selection_get_n_selected (GthFileSelection *self);
+
+G_END_DECLS
+
+#endif /* GTH_FILE_SELECTION_H */
diff --git a/gthumb/gth-file-source-vfs.c b/gthumb/gth-file-source-vfs.c
new file mode 100644
index 0000000..deaaae1
--- /dev/null
+++ b/gthumb/gth-file-source-vfs.c
@@ -0,0 +1,581 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2008-2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <string.h>
+#include <glib/gi18n.h>
+#include <glib.h>
+#include <gio/gunixmounts.h>
+#include "file-cache.h"
+#include "gth-file-data.h"
+#include "gio-utils.h"
+#include "glib-utils.h"
+#include "gth-file-source-vfs.h"
+#include "gth-main.h"
+
+#define GTH_MONITOR_N_EVENTS 3
+#define MONITOR_UPDATE_DELAY 500
+#define DEBUG_MONITOR 1
+
+struct _GthFileSourceVfsPrivate
+{
+ GCancellable *cancellable;
+ GList *files;
+ ListReady ready_func;
+ gpointer ready_data;
+ GHashTable *monitors;
+ GList *monitor_queue[GTH_MONITOR_N_EVENTS];
+ guint monitor_update_id;
+ GUnixMountMonitor *mount_monitor;
+};
+
+
+static GthFileSourceClass *parent_class = NULL;
+static guint mount_monitor_id = 0;
+
+
+static GList *
+get_entry_points (GthFileSource *file_source)
+{
+ GList *list;
+ GFile *file;
+ GFileInfo *info;
+ GList *mounts;
+ GList *scan;
+
+ list = NULL;
+
+ file = g_file_new_for_uri (get_home_uri ());
+ info = gth_file_source_get_file_info (file_source, file);
+ g_file_info_set_display_name (info, _("Home Folder"));
+ list = g_list_append (list, gth_file_data_new (file, info));
+ g_object_unref (info);
+ g_object_unref (file);
+
+ file = g_file_new_for_uri ("file:///");
+ info = gth_file_source_get_file_info (file_source, file);
+ g_file_info_set_display_name (info, _("File System"));
+ list = g_list_append (list, gth_file_data_new (file, info));
+ g_object_unref (info);
+ g_object_unref (file);
+
+ mounts = g_volume_monitor_get_mounts (g_volume_monitor_get ());
+ for (scan = mounts; scan; scan = scan->next) {
+ GMount *mount = scan->data;
+ GVolume *volume;
+
+ if (g_mount_is_shadowed (mount))
+ continue;
+
+ file = g_mount_get_root (mount);
+ info = gth_file_source_get_file_info (file_source, file);
+
+ volume = g_mount_get_volume (mount);
+ if (volume != NULL) {
+ char *name;
+ GIcon *icon;
+
+ name = g_volume_get_name (volume);
+ g_file_info_set_display_name (info, name);
+
+ icon = g_volume_get_icon (volume);
+ g_file_info_set_icon (info, icon);
+
+ g_object_unref (icon);
+ g_free (name);
+ g_object_unref (volume);
+ }
+
+ list = g_list_append (list, gth_file_data_new (file, info));
+ g_object_unref (info);
+ g_object_unref (file);
+ }
+
+ g_list_foreach (mounts, (GFunc) g_object_unref, NULL);
+ g_list_free (mounts);
+
+ return list;
+}
+
+
+static GFile *
+to_gio_file (GthFileSource *file_source,
+ GFile *file)
+{
+ char *uri;
+ GFile *gio_file;
+
+ uri = g_file_get_uri (file);
+ gio_file = g_file_new_for_uri (g_str_has_prefix (uri, "vfs+") ? uri + 4 : uri);
+ g_free (uri);
+
+ return gio_file;
+}
+
+
+static GFileInfo *
+get_file_info (GthFileSource *file_source,
+ GFile *file)
+{
+ GFile *gio_file;
+ GFileInfo *file_info;
+
+ gio_file = gth_file_source_to_gio_file (file_source, file);
+ file_info = g_file_query_info (gio_file,
+ "standard::display-name,standard::icon,standard::type",
+ G_FILE_QUERY_INFO_NONE,
+ NULL,
+ NULL);
+
+ g_object_unref (gio_file);
+
+ return file_info;
+}
+
+
+static void
+list__done_func (GError *error,
+ gpointer user_data)
+{
+ GthFileSourceVfs *file_source_vfs = user_data;
+
+ if (G_IS_OBJECT (file_source_vfs))
+ gth_file_source_set_active (GTH_FILE_SOURCE (file_source_vfs), FALSE);
+
+ file_source_vfs->priv->ready_func ((GthFileSource *)file_source_vfs,
+ file_source_vfs->priv->files,
+ error,
+ file_source_vfs->priv->ready_data);
+ g_object_unref (file_source_vfs);
+}
+
+
+static void
+list__for_each_file_func (GFile *file,
+ GFileInfo *info,
+ gpointer user_data)
+{
+ GthFileSourceVfs *file_source_vfs = user_data;
+
+ switch (g_file_info_get_file_type (info)) {
+ case G_FILE_TYPE_REGULAR:
+ case G_FILE_TYPE_DIRECTORY:
+ file_source_vfs->priv->files = g_list_prepend (file_source_vfs->priv->files, gth_file_data_new (file, info));
+ break;
+ default:
+ break;
+ }
+}
+
+
+static DirOp
+list__start_dir_func (GFile *directory,
+ GFileInfo *info,
+ GError **error,
+ gpointer user_data)
+{
+ return DIR_OP_CONTINUE;
+}
+
+
+static void
+list (GthFileSource *file_source,
+ GFile *folder,
+ const char *attributes,
+ ListReady func,
+ gpointer user_data)
+{
+ GthFileSourceVfs *file_source_vfs = (GthFileSourceVfs *) file_source;
+ GFile *gio_folder;
+
+ gth_file_source_set_active (file_source, TRUE);
+ g_cancellable_reset (file_source_vfs->priv->cancellable);
+
+ _g_object_list_unref (file_source_vfs->priv->files);
+ file_source_vfs->priv->files = NULL;
+
+ file_source_vfs->priv->ready_func = func;
+ file_source_vfs->priv->ready_data = user_data;
+
+ g_object_ref (file_source);
+ gio_folder = gth_file_source_to_gio_file (file_source, folder);
+ g_directory_foreach_child (gio_folder,
+ FALSE,
+ TRUE,
+ attributes,
+ file_source_vfs->priv->cancellable,
+ list__start_dir_func,
+ list__for_each_file_func,
+ list__done_func,
+ file_source);
+
+ g_object_unref (gio_folder);
+}
+
+
+static void
+info_ready_cb (GList *files,
+ GError *error,
+ gpointer user_data)
+{
+ GthFileSourceVfs *file_source_vfs = user_data;
+ GList *scan;
+ GList *result_files;
+
+ if (G_IS_OBJECT (file_source_vfs))
+ gth_file_source_set_active (GTH_FILE_SOURCE (file_source_vfs), FALSE);
+
+ result_files = NULL;
+ for (scan = files; scan; scan = scan->next)
+ result_files = g_list_prepend (result_files, g_object_ref ((GthFileData *) scan->data));
+ result_files = g_list_reverse (result_files);
+
+ file_source_vfs->priv->ready_func ((GthFileSource *) file_source_vfs,
+ result_files,
+ error,
+ file_source_vfs->priv->ready_data);
+
+ _g_object_list_unref (result_files);
+ g_object_unref (file_source_vfs);
+}
+
+
+static void
+read_attributes (GthFileSource *file_source,
+ GList *files,
+ const char *attributes,
+ ListReady func,
+ gpointer user_data)
+{
+ GthFileSourceVfs *file_source_vfs = (GthFileSourceVfs *) file_source;
+
+ gth_file_source_set_active (file_source, TRUE);
+ g_cancellable_reset (file_source_vfs->priv->cancellable);
+
+ file_source_vfs->priv->ready_func = func;
+ file_source_vfs->priv->ready_data = user_data;
+
+ g_object_ref (file_source_vfs);
+ g_query_info_async (files,
+ attributes,
+ file_source_vfs->priv->cancellable,
+ info_ready_cb,
+ file_source_vfs);
+}
+
+
+static void
+cancel (GthFileSource *file_source)
+{
+ GthFileSourceVfs *file_source_vfs = (GthFileSourceVfs *) file_source;
+
+ g_cancellable_cancel (file_source_vfs->priv->cancellable);
+}
+
+
+static void
+mount_monitor_mountpoints_changed_cb (GUnixMountMonitor *monitor,
+ gpointer user_data)
+{
+ gth_monitor_file_entry_points_changed (gth_main_get_default_monitor ());
+}
+
+
+static void
+monitor_entry_points (GthFileSource *file_source)
+{
+ GthFileSourceVfs *file_source_vfs = (GthFileSourceVfs *) file_source;
+
+ if (mount_monitor_id != 0)
+ return;
+
+ file_source_vfs->priv->mount_monitor = g_unix_mount_monitor_new ();
+ mount_monitor_id = g_signal_connect (file_source_vfs->priv->mount_monitor,
+ "mounts-changed",
+ G_CALLBACK (mount_monitor_mountpoints_changed_cb),
+ file_source_vfs);
+}
+
+
+static gboolean
+process_event_queue (gpointer data)
+{
+ GthFileSourceVfs *file_source_vfs = data;
+ GthMonitor *monitor;
+ GList *monitor_queue[GTH_MONITOR_N_EVENTS];
+ int event_type;
+
+ if (file_source_vfs->priv->monitor_update_id != 0)
+ g_source_remove (file_source_vfs->priv->monitor_update_id);
+ file_source_vfs->priv->monitor_update_id = 0;
+
+ for (event_type = 0; event_type < GTH_MONITOR_N_EVENTS; event_type++) {
+ monitor_queue[event_type] = file_source_vfs->priv->monitor_queue[event_type];
+ file_source_vfs->priv->monitor_queue[event_type] = NULL;
+ }
+
+ monitor = gth_main_get_default_monitor ();
+ for (event_type = 0; event_type < GTH_MONITOR_N_EVENTS; event_type++) {
+ GList *scan;
+
+ for (scan = monitor_queue[event_type]; scan; scan = scan->next) {
+ GFile *file = scan->data;
+ GFile *parent;
+ GList *list;
+
+#ifdef DEBUG_MONITOR
+ switch (event_type) {
+ case GTH_MONITOR_EVENT_CREATED:
+ g_print ("GTH_MONITOR_EVENT_CREATED");
+ break;
+ case GTH_MONITOR_EVENT_DELETED:
+ g_print ("GTH_MONITOR_EVENT_DELETED");
+ break;
+ case GTH_MONITOR_EVENT_CHANGED:
+ g_print ("GTH_MONITOR_EVENT_CHANGED");
+ break;
+ }
+ g_print (" ==> %s\n", g_file_get_uri (file));
+#endif
+
+ parent = g_file_get_parent (file);
+ list = g_list_prepend (NULL, g_object_ref (file));
+ gth_monitor_folder_changed (monitor,
+ parent,
+ list,
+ event_type);
+
+ _g_object_list_unref (list);
+ g_object_unref (parent);
+ }
+ _g_object_list_unref (monitor_queue[event_type]);
+ monitor_queue[event_type] = NULL;
+ }
+
+ return FALSE;
+}
+
+
+static gboolean
+remove_if_present (GthFileSourceVfs *file_source_vfs,
+ GthMonitorEvent event_type,
+ GFile *file)
+{
+ GList *link;
+
+ link = _g_file_list_find_file (file_source_vfs->priv->monitor_queue[event_type], file);
+ if (link != NULL) {
+ file_source_vfs->priv->monitor_queue[event_type] = g_list_remove_link (file_source_vfs->priv->monitor_queue[event_type], link);
+ _g_object_list_unref (link);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+
+static void
+monitor_changed_cb (GFileMonitor *file_monitor,
+ GFile *file,
+ GFile *other_file,
+ GFileMonitorEvent file_event_type,
+ gpointer user_data)
+{
+ GthFileSourceVfs *file_source_vfs = user_data;
+ GthMonitorEvent event_type;
+
+ switch (file_event_type) {
+ case G_FILE_MONITOR_EVENT_CREATED:
+ event_type = GTH_MONITOR_EVENT_CREATED;
+ break;
+
+ case G_FILE_MONITOR_EVENT_DELETED:
+ event_type = GTH_MONITOR_EVENT_DELETED;
+ break;
+
+ case G_FILE_MONITOR_EVENT_CHANGED:
+ case G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED:
+ event_type = GTH_MONITOR_EVENT_CHANGED;
+ break;
+
+ case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT:
+ case G_FILE_MONITOR_EVENT_PRE_UNMOUNT:
+ case G_FILE_MONITOR_EVENT_UNMOUNTED:
+ return;
+ }
+
+#ifdef DEBUG_MONITOR
+ g_print ("[RAW] ");
+ switch (event_type) {
+ case GTH_MONITOR_EVENT_CREATED:
+ g_print ("GTH_MONITOR_EVENT_CREATED");
+ break;
+ case GTH_MONITOR_EVENT_DELETED:
+ g_print ("GTH_MONITOR_EVENT_DELETED");
+ break;
+ case GTH_MONITOR_EVENT_CHANGED:
+ g_print ("GTH_MONITOR_EVENT_CHANGED");
+ break;
+ }
+ g_print (" ==> %s\n", g_file_get_uri (file));
+#endif
+
+ if (event_type == GTH_MONITOR_EVENT_CREATED) {
+ if (remove_if_present (file_source_vfs, GTH_MONITOR_EVENT_DELETED, file))
+ event_type = GTH_MONITOR_EVENT_CHANGED;
+ }
+ else if (event_type == GTH_MONITOR_EVENT_DELETED) {
+ remove_if_present (file_source_vfs, GTH_MONITOR_EVENT_CREATED, file);
+ remove_if_present (file_source_vfs, GTH_MONITOR_EVENT_CHANGED, file);
+ }
+ else if (event_type == GTH_MONITOR_EVENT_CHANGED) {
+ if (_g_file_list_find_file (file_source_vfs->priv->monitor_queue[GTH_MONITOR_EVENT_CREATED], file))
+ return;
+ remove_if_present (file_source_vfs, GTH_MONITOR_EVENT_CHANGED, file);
+ }
+
+ file_source_vfs->priv->monitor_queue[event_type] = g_list_prepend (file_source_vfs->priv->monitor_queue[event_type], g_file_dup (file));
+
+ if (file_source_vfs->priv->monitor_update_id != 0)
+ g_source_remove (file_source_vfs->priv->monitor_update_id);
+ file_source_vfs->priv->monitor_update_id = g_timeout_add (MONITOR_UPDATE_DELAY,
+ process_event_queue,
+ file_source_vfs);
+}
+
+
+static void
+monitor_directory (GthFileSource *file_source,
+ GFile *file,
+ gboolean activate)
+{
+ GthFileSourceVfs *file_source_vfs = (GthFileSourceVfs *) file_source;
+ GFileMonitor *monitor;
+
+ if (! activate) {
+ g_hash_table_remove (file_source_vfs->priv->monitors, file);
+ return;
+ }
+
+ if (g_hash_table_lookup (file_source_vfs->priv->monitors, file) != NULL)
+ return;
+
+ monitor = g_file_monitor_directory (file, 0, NULL, NULL);
+ if (monitor == NULL)
+ return;
+
+ g_hash_table_insert (file_source_vfs->priv->monitors, g_object_ref (file), monitor);
+ g_signal_connect (monitor, "changed", G_CALLBACK (monitor_changed_cb), file_source);
+}
+
+
+static void
+gth_file_source_vfs_finalize (GObject *object)
+{
+ GthFileSourceVfs *file_source_vfs = GTH_FILE_SOURCE_VFS (object);
+
+ if (file_source_vfs->priv != NULL) {
+ int i;
+
+ if (file_source_vfs->priv->monitor_update_id != 0) {
+ g_source_remove (file_source_vfs->priv->monitor_update_id);
+ file_source_vfs->priv->monitor_update_id = 0;
+ }
+
+ g_hash_table_destroy (file_source_vfs->priv->monitors);
+
+ for (i = 0; i < GTH_MONITOR_N_EVENTS; i++) {
+ _g_object_list_unref (file_source_vfs->priv->monitor_queue[i]);
+ file_source_vfs->priv->monitor_queue[i] = NULL;
+ }
+ _g_object_list_unref (file_source_vfs->priv->files);
+
+ g_free (file_source_vfs->priv);
+ file_source_vfs->priv = NULL;
+ }
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+
+static void
+gth_file_source_vfs_class_init (GthFileSourceVfsClass *class)
+{
+ GObjectClass *object_class;
+ GthFileSourceClass *file_source_class;
+
+ parent_class = g_type_class_peek_parent (class);
+ object_class = (GObjectClass*) class;
+ file_source_class = (GthFileSourceClass*) class;
+
+ object_class->finalize = gth_file_source_vfs_finalize;
+ file_source_class->get_entry_points = get_entry_points;
+ file_source_class->to_gio_file = to_gio_file;
+ file_source_class->get_file_info = get_file_info;
+ file_source_class->list = list;
+ file_source_class->read_attributes = read_attributes;
+ file_source_class->cancel = cancel;
+ file_source_class->monitor_entry_points = monitor_entry_points;
+ file_source_class->monitor_directory = monitor_directory;
+}
+
+
+static void
+gth_file_source_vfs_init (GthFileSourceVfs *file_source)
+{
+ int i;
+
+ file_source->priv = g_new0 (GthFileSourceVfsPrivate, 1);
+ gth_file_source_add_scheme (GTH_FILE_SOURCE (file_source), "vfs+");
+ file_source->priv->cancellable = g_cancellable_new ();
+ file_source->priv->monitors = g_hash_table_new_full (g_file_hash, (GEqualFunc) g_file_equal, g_object_unref, g_object_unref);
+ for (i = 0; i < GTH_MONITOR_N_EVENTS; i++)
+ file_source->priv->monitor_queue[i] = NULL;
+}
+
+
+GType
+gth_file_source_vfs_get_type (void)
+{
+ static GType type = 0;
+
+ if (! type) {
+ GTypeInfo type_info = {
+ sizeof (GthFileSourceVfsClass),
+ NULL,
+ NULL,
+ (GClassInitFunc) gth_file_source_vfs_class_init,
+ NULL,
+ NULL,
+ sizeof (GthFileSourceVfs),
+ 0,
+ (GInstanceInitFunc) gth_file_source_vfs_init
+ };
+
+ type = g_type_register_static (GTH_TYPE_FILE_SOURCE,
+ "GthFileSourceVfs",
+ &type_info,
+ 0);
+ }
+
+ return type;
+}
diff --git a/gthumb/gth-file-source-vfs.h b/gthumb/gth-file-source-vfs.h
new file mode 100644
index 0000000..cf693d6
--- /dev/null
+++ b/gthumb/gth-file-source-vfs.h
@@ -0,0 +1,56 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2008 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GTH_FILE_SOURCE_VFS_H
+#define GTH_FILE_SOURCE_VFS_H
+
+#include "gth-file-source.h"
+
+G_BEGIN_DECLS
+
+#define GTH_TYPE_FILE_SOURCE_VFS (gth_file_source_vfs_get_type ())
+#define GTH_FILE_SOURCE_VFS(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GTH_TYPE_FILE_SOURCE_VFS, GthFileSourceVfs))
+#define GTH_FILE_SOURCE_VFS_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), GTH_TYPE_FILE_SOURCE_VFS, GthFileSourceVfsClass))
+#define GTH_IS_FILE_SOURCE_VFS(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GTH_TYPE_FILE_SOURCE_VFS))
+#define GTH_IS_FILE_SOURCE_VFS_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GTH_TYPE_FILE_SOURCE_VFS))
+#define GTH_FILE_SOURCE_VFS_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS((o), GTH_TYPE_FILE_SOURCE_VFS, GthFileSourceVfsClass))
+
+typedef struct _GthFileSourceVfs GthFileSourceVfs;
+typedef struct _GthFileSourceVfsPrivate GthFileSourceVfsPrivate;
+typedef struct _GthFileSourceVfsClass GthFileSourceVfsClass;
+
+struct _GthFileSourceVfs
+{
+ GthFileSource __parent;
+ GthFileSourceVfsPrivate *priv;
+};
+
+struct _GthFileSourceVfsClass
+{
+ GthFileSourceClass __parent_class;
+};
+
+GType gth_file_source_vfs_get_type (void) G_GNUC_CONST;
+
+G_END_DECLS
+
+#endif /* GTH_FILE_SOURCE_VFS_H */
diff --git a/gthumb/gth-file-source.c b/gthumb/gth-file-source.c
new file mode 100644
index 0000000..6e08f2a
--- /dev/null
+++ b/gthumb/gth-file-source.c
@@ -0,0 +1,576 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2008-2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <string.h>
+#include <glib/gi18n.h>
+#include <glib.h>
+#include <gtk/gtk.h>
+#include "gth-file-data.h"
+#include "glib-utils.h"
+#include "gth-file-source.h"
+#include "gth-main.h"
+
+
+struct _GthFileSourcePrivate
+{
+ GList *schemes;
+ gboolean active;
+ GList *queue;
+};
+
+
+static GObjectClass *parent_class = NULL;
+
+
+/* -- queue -- */
+
+
+typedef enum {
+ FILE_SOURCE_OP_LIST,
+ FILE_SOURCE_OP_READ_ATTRIBUTES,
+ FILE_SOURCE_OP_RENAME
+} FileSourceOp;
+
+
+typedef struct {
+ GFile *folder;
+ const char *attributes;
+ ListReady func;
+ gpointer data;
+} ListData;
+
+
+typedef struct {
+ GList *files;
+ const char *attributes;
+ ListReady func;
+ gpointer data;
+} ReadAttributesData;
+
+
+typedef struct {
+ GFile *file;
+ GFile *new_file;
+ ReadyCallback callback;
+ gpointer data;
+} RenameData;
+
+
+typedef struct {
+ GthFileSource *file_source;
+ FileSourceOp op;
+ union {
+ ListData list;
+ ReadAttributesData read_attributes;
+ RenameData rename;
+ } data;
+} FileSourceAsyncOp;
+
+
+static void
+file_source_async_op_free (FileSourceAsyncOp *async_op)
+{
+ switch (async_op->op) {
+ case FILE_SOURCE_OP_LIST:
+ g_object_unref (async_op->data.list.folder);
+ break;
+ case FILE_SOURCE_OP_READ_ATTRIBUTES:
+ _g_object_list_unref (async_op->data.read_attributes.files);
+ break;
+ case FILE_SOURCE_OP_RENAME:
+ g_object_unref (async_op->data.rename.file);
+ g_object_unref (async_op->data.rename.new_file);
+ break;
+ }
+
+ g_free (async_op);
+}
+
+
+static void
+gth_file_source_queue_list (GthFileSource *file_source,
+ GFile *folder,
+ const char *attributes,
+ ListReady func,
+ gpointer data)
+{
+ FileSourceAsyncOp *async_op;
+
+ async_op = g_new0 (FileSourceAsyncOp, 1);
+ async_op->file_source = file_source;
+ async_op->op = FILE_SOURCE_OP_LIST;
+ async_op->data.list.folder = g_file_dup (folder);
+ async_op->data.list.attributes = attributes;
+ async_op->data.list.func = func;
+ async_op->data.list.data = data;
+
+ file_source->priv->queue = g_list_append (file_source->priv->queue, async_op);
+}
+
+
+static void
+gth_file_source_queue_read_attributes (GthFileSource *file_source,
+ GList *files,
+ const char *attributes,
+ ListReady func,
+ gpointer data)
+{
+ FileSourceAsyncOp *async_op;
+
+ async_op = g_new0 (FileSourceAsyncOp, 1);
+ async_op->file_source = file_source;
+ async_op->op = FILE_SOURCE_OP_READ_ATTRIBUTES;
+ async_op->data.read_attributes.files = _g_object_list_ref (files);
+ async_op->data.read_attributes.attributes = attributes;
+ async_op->data.read_attributes.func = func;
+ async_op->data.read_attributes.data = data;
+
+ file_source->priv->queue = g_list_append (file_source->priv->queue, async_op);
+}
+
+
+static void
+gth_file_source_queue_rename (GthFileSource *file_source,
+ GFile *file,
+ GFile *new_file,
+ ReadyCallback callback,
+ gpointer data)
+{
+ FileSourceAsyncOp *async_op;
+
+ async_op = g_new0 (FileSourceAsyncOp, 1);
+ async_op->file_source = file_source;
+ async_op->op = FILE_SOURCE_OP_RENAME;
+ async_op->data.rename.file = g_file_dup (file);
+ async_op->data.rename.new_file = g_file_dup (new_file);
+ async_op->data.rename.callback = callback;
+ async_op->data.rename.data = data;
+
+ file_source->priv->queue = g_list_append (file_source->priv->queue, async_op);
+}
+
+
+static void
+gth_file_source_exec_next_in_queue (GthFileSource *file_source)
+{
+ GList *head;
+ FileSourceAsyncOp *async_op;
+
+ if (file_source->priv->queue == NULL)
+ return;
+
+ head = file_source->priv->queue;
+ file_source->priv->queue = g_list_remove_link (file_source->priv->queue, head);
+
+ async_op = head->data;
+ switch (async_op->op) {
+ case FILE_SOURCE_OP_LIST:
+ gth_file_source_list (file_source,
+ async_op->data.list.folder,
+ async_op->data.list.attributes,
+ async_op->data.list.func,
+ async_op->data.list.data);
+ break;
+ case FILE_SOURCE_OP_READ_ATTRIBUTES:
+ gth_file_source_read_attributes (file_source,
+ async_op->data.read_attributes.files,
+ async_op->data.read_attributes.attributes,
+ async_op->data.read_attributes.func,
+ async_op->data.read_attributes.data);
+ break;
+ case FILE_SOURCE_OP_RENAME:
+ gth_file_source_rename (file_source,
+ async_op->data.rename.file,
+ async_op->data.rename.new_file,
+ async_op->data.rename.callback,
+ async_op->data.rename.data);
+ break;
+ }
+
+ file_source_async_op_free (async_op);
+ g_list_free (head);
+}
+
+
+static void
+gth_file_source_clear_queue (GthFileSource *file_source)
+{
+ g_list_foreach (file_source->priv->queue, (GFunc) file_source_async_op_free, NULL);
+ g_list_free (file_source->priv->queue);
+ file_source->priv->queue = NULL;
+}
+
+
+/* -- */
+
+
+static GList *
+base_get_entry_points (GthFileSource *file_source)
+{
+ return NULL;
+}
+
+
+static GList *
+base_get_current_list (GthFileSource *file_source,
+ GFile *file)
+{
+ GList *list = NULL;
+ GFile *parent;
+
+ if (file == NULL)
+ return NULL;
+
+ parent = g_file_dup (file);
+ while (parent != NULL) {
+ GFile *tmp;
+
+ list = g_list_prepend (list, g_object_ref (parent));
+ tmp = g_file_get_parent (parent);
+ g_object_unref (parent);
+ parent = tmp;
+ }
+
+ return g_list_reverse (list);
+}
+
+
+static GFile *
+base_to_gio_file (GthFileSource *file_source,
+ GFile *file)
+{
+ return g_file_dup (file);
+}
+
+
+static GFileInfo *
+base_get_file_info (GthFileSource *file_source,
+ GFile *file)
+{
+ return NULL;
+}
+
+
+static void
+base_list (GthFileSource *file_source,
+ GFile *folder,
+ const char *attributes,
+ ListReady func,
+ gpointer data)
+{
+ /* void */
+}
+
+
+static void
+base_read_attributes (GthFileSource *file_source,
+ GList *files,
+ const char *attributes,
+ ListReady func,
+ gpointer data)
+{
+ /* void */
+}
+
+
+static void
+base_cancel (GthFileSource *file_source)
+{
+ /* void */
+}
+
+
+static void
+base_rename (GthFileSource *file_source,
+ GFile *file,
+ GFile *new_file,
+ ReadyCallback callback,
+ gpointer data)
+{
+ GFile *source;
+ GFile *destination;
+ GError *error = NULL;
+
+ source = gth_file_source_to_gio_file (file_source, file);
+ destination = gth_file_source_to_gio_file (file_source, new_file);
+
+ if (g_file_move (source, destination, 0, NULL, NULL, NULL, &error)) {
+ GthMonitor *monitor;
+
+ monitor = gth_main_get_default_monitor ();
+ gth_monitor_file_renamed (monitor, file, new_file);
+ }
+ object_ready_with_error (file_source, callback, data, error);
+
+ g_object_unref (destination);
+ g_object_unref (source);
+}
+
+
+static void
+base_monitor_entry_points (GthFileSource *file_source)
+{
+ /* void */
+}
+
+
+static void
+base_monitor_directory (GthFileSource *file_source,
+ GFile *file,
+ gboolean activate)
+{
+ /* void */
+}
+
+
+static void
+gth_file_source_finalize (GObject *object)
+{
+ GthFileSource *file_source = GTH_FILE_SOURCE (object);
+
+ if (file_source->priv != NULL) {
+ gth_file_source_clear_queue (file_source);
+ _g_string_list_free (file_source->priv->schemes);
+
+ g_free (file_source->priv);
+ file_source->priv = NULL;
+ }
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+
+static void
+gth_file_source_class_init (GthFileSourceClass *class)
+{
+ GObjectClass *object_class;
+
+ parent_class = g_type_class_peek_parent (class);
+ object_class = (GObjectClass*) class;
+
+ object_class->finalize = gth_file_source_finalize;
+ class->get_entry_points = base_get_entry_points;
+ class->get_current_list = base_get_current_list;
+ class->to_gio_file = base_to_gio_file;
+ class->get_file_info = base_get_file_info;
+ class->list = base_list;
+ class->read_attributes = base_read_attributes;
+ class->cancel = base_cancel;
+ class->rename = base_rename;
+ class->monitor_entry_points = base_monitor_entry_points;
+ class->monitor_directory = base_monitor_directory;
+}
+
+
+static void
+gth_file_source_init (GthFileSource *file_source)
+{
+ file_source->priv = g_new0 (GthFileSourcePrivate, 1);
+}
+
+
+GType
+gth_file_source_get_type (void)
+{
+ static GType type = 0;
+
+ if (! type) {
+ GTypeInfo type_info = {
+ sizeof (GthFileSourceClass),
+ NULL,
+ NULL,
+ (GClassInitFunc) gth_file_source_class_init,
+ NULL,
+ NULL,
+ sizeof (GthFileSource),
+ 0,
+ (GInstanceInitFunc) gth_file_source_init
+ };
+
+ type = g_type_register_static (G_TYPE_OBJECT,
+ "GthFileSource",
+ &type_info,
+ 0);
+ }
+
+ return type;
+}
+
+
+void
+gth_file_source_add_scheme (GthFileSource *file_source,
+ const char *scheme)
+{
+ file_source->priv->schemes = g_list_prepend (file_source->priv->schemes, g_strdup (scheme));
+}
+
+
+gboolean
+gth_file_source_supports_scheme (GthFileSource *file_source,
+ const char *uri)
+{
+ gboolean result = FALSE;
+ GList *scan;
+
+ for (scan = file_source->priv->schemes; scan; scan = scan->next) {
+ const char *scheme = scan->data;
+
+ if (strncmp (uri, scheme, strlen (scheme)) == 0) {
+ result = TRUE;
+ break;
+ }
+ }
+
+ return result;
+}
+
+
+void
+gth_file_source_set_active (GthFileSource *file_source,
+ gboolean active)
+{
+ file_source->priv->active = active;
+ if (! active)
+ gth_file_source_exec_next_in_queue (file_source);
+}
+
+
+GList *
+gth_file_source_get_entry_points (GthFileSource *file_source)
+{
+ return GTH_FILE_SOURCE_GET_CLASS (G_OBJECT (file_source))->get_entry_points (file_source);
+}
+
+
+GList *
+gth_file_source_get_current_list (GthFileSource *file_source,
+ GFile *file)
+{
+ return GTH_FILE_SOURCE_GET_CLASS (G_OBJECT (file_source))->get_current_list (file_source, file);
+}
+
+
+GFile *
+gth_file_source_to_gio_file (GthFileSource *file_source,
+ GFile *file)
+{
+ return GTH_FILE_SOURCE_GET_CLASS (G_OBJECT (file_source))->to_gio_file (file_source, file);
+}
+
+
+GList *
+gth_file_source_to_gio_file_list (GthFileSource *file_source,
+ GList *files)
+{
+ GList *gio_files = NULL;
+ GList *scan;
+
+ for (scan = files; scan; scan = scan->next)
+ gio_files = g_list_prepend (gio_files, gth_file_source_to_gio_file (file_source, (GFile *) scan->data));
+
+ return g_list_reverse (gio_files);
+}
+
+
+GFileInfo *
+gth_file_source_get_file_info (GthFileSource *file_source,
+ GFile *file)
+{
+ return GTH_FILE_SOURCE_GET_CLASS (G_OBJECT (file_source))->get_file_info (file_source, file);
+}
+
+
+gboolean
+gth_file_source_is_active (GthFileSource *file_source)
+{
+ return file_source->priv->active;
+}
+
+
+void
+gth_file_source_cancel (GthFileSource *file_source)
+{
+ gth_file_source_clear_queue (file_source);
+ GTH_FILE_SOURCE_GET_CLASS (G_OBJECT (file_source))->cancel (file_source);
+}
+
+
+void
+gth_file_source_list (GthFileSource *file_source,
+ GFile *folder,
+ const char *attributes,
+ ListReady func,
+ gpointer data)
+{
+ if (gth_file_source_is_active (file_source)) {
+ gth_file_source_queue_list (file_source, folder, attributes, func, data);
+ return;
+ }
+ GTH_FILE_SOURCE_GET_CLASS (G_OBJECT (file_source))->list (file_source, folder, attributes, func, data);
+}
+
+
+void
+gth_file_source_read_attributes (GthFileSource *file_source,
+ GList *files,
+ const char *attributes,
+ ListReady func,
+ gpointer data)
+{
+ if (gth_file_source_is_active (file_source)) {
+ gth_file_source_queue_read_attributes (file_source, files, attributes, func, data);
+ return;
+ }
+ GTH_FILE_SOURCE_GET_CLASS (G_OBJECT (file_source))->read_attributes (file_source, files, attributes, func, data);
+}
+
+
+void
+gth_file_source_rename (GthFileSource *file_source,
+ GFile *file,
+ GFile *new_file,
+ ReadyCallback callback,
+ gpointer data)
+{
+ if (gth_file_source_is_active (file_source)) {
+ gth_file_source_queue_rename (file_source, file, new_file, callback, data);
+ return;
+ }
+ GTH_FILE_SOURCE_GET_CLASS (G_OBJECT (file_source))->rename (file_source, file, new_file, callback, data);
+}
+
+
+void
+gth_file_source_monitor_entry_points (GthFileSource *file_source)
+{
+ GTH_FILE_SOURCE_GET_CLASS (G_OBJECT (file_source))->monitor_entry_points (file_source);
+}
+
+
+void
+gth_file_source_monitor_directory (GthFileSource *file_source,
+ GFile *file,
+ gboolean activate)
+{
+ GTH_FILE_SOURCE_GET_CLASS (G_OBJECT (file_source))->monitor_directory (file_source, file, activate);
+}
diff --git a/gthumb/gth-file-source.h b/gthumb/gth-file-source.h
new file mode 100644
index 0000000..f3270d1
--- /dev/null
+++ b/gthumb/gth-file-source.h
@@ -0,0 +1,143 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2008-2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GTH_FILE_SOURCE_H
+#define GTH_FILE_SOURCE_H
+
+#include <glib.h>
+#include <glib-object.h>
+#include <gtk/gtk.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include "gth-file-data.h"
+#include "typedefs.h"
+
+G_BEGIN_DECLS
+
+#define GTH_TYPE_FILE_SOURCE (gth_file_source_get_type ())
+#define GTH_FILE_SOURCE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GTH_TYPE_FILE_SOURCE, GthFileSource))
+#define GTH_FILE_SOURCE_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), GTH_TYPE_FILE_SOURCE, GthFileSourceClass))
+#define GTH_IS_FILE_SOURCE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GTH_TYPE_FILE_SOURCE))
+#define GTH_IS_FILE_SOURCE_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GTH_TYPE_FILE_SOURCE))
+#define GTH_FILE_SOURCE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS((o), GTH_TYPE_FILE_SOURCE, GthFileSourceClass))
+
+typedef struct _GthFileSource GthFileSource;
+typedef struct _GthFileSourcePrivate GthFileSourcePrivate;
+typedef struct _GthFileSourceClass GthFileSourceClass;
+
+typedef void (*ListReady) (GthFileSource *file_source,
+ GList *files,
+ GError *error,
+ gpointer data);
+
+struct _GthFileSource
+{
+ GObject __parent;
+ GthFileSourcePrivate *priv;
+
+ /*< protected >*/
+
+ GList *folders; /* list of GthFileData */
+ GList *files; /* list of GthFileData */
+};
+
+struct _GthFileSourceClass
+{
+ GObjectClass __parent_class;
+
+ /*< virtual functions >*/
+
+ GList * (*get_entry_points) (GthFileSource *file_source);
+ GList * (*get_current_list) (GthFileSource *file_source,
+ GFile *file);
+ GFile * (*to_gio_file) (GthFileSource *file_source,
+ GFile *file);
+ GFileInfo * (*get_file_info) (GthFileSource *file_source,
+ GFile *file);
+ void (*list) (GthFileSource *file_source,
+ GFile *folder,
+ const char *attributes,
+ ListReady func,
+ gpointer data);
+ void (*read_attributes) (GthFileSource *file_source,
+ GList *files,
+ const char *attributes,
+ ListReady func,
+ gpointer data);
+ void (*cancel) (GthFileSource *file_source);
+ void (*rename) (GthFileSource *file_source,
+ GFile *file,
+ GFile *new_file,
+ ReadyCallback callback,
+ gpointer data);
+ void (*monitor_entry_points) (GthFileSource *file_source);
+ void (*monitor_directory) (GthFileSource *file_source,
+ GFile *file,
+ gboolean activate);
+};
+
+GType gth_file_source_get_type (void) G_GNUC_CONST;
+
+/*< protected >*/
+
+void gth_file_source_add_scheme (GthFileSource *file_source,
+ const char *scheme);
+gboolean gth_file_source_supports_scheme (GthFileSource *file_source,
+ const char *uri);
+void gth_file_source_set_active (GthFileSource *file_source,
+ gboolean pending);
+
+/*< public >*/
+
+GList * gth_file_source_get_entry_points (GthFileSource *file_source); /* GthFileData list */
+GList * gth_file_source_get_current_list (GthFileSource *file_source, /* GFile list */
+ GFile *file);
+GFile * gth_file_source_to_gio_file (GthFileSource *file_source,
+ GFile *file);
+GList * gth_file_source_to_gio_file_list (GthFileSource *file_source,
+ GList *files);
+GFileInfo * gth_file_source_get_file_info (GthFileSource *file_source,
+ GFile *file);
+gboolean gth_file_source_is_active (GthFileSource *file_source);
+void gth_file_source_cancel (GthFileSource *file_source);
+void gth_file_source_list (GthFileSource *file_source,
+ GFile *folder,
+ const char *attributes,
+ ListReady func,
+ gpointer data);
+void gth_file_source_read_attributes (GthFileSource *file_source,
+ GList *files,
+ const char *attributes,
+ ListReady func,
+ gpointer data);
+void gth_file_source_rename (GthFileSource *file_source,
+ GFile *file,
+ GFile *new_file,
+ ReadyCallback callback,
+ gpointer data);
+void gth_file_source_monitor_entry_points (GthFileSource *file_source);
+void gth_file_source_monitor_directory (GthFileSource *file_source,
+ GFile *file,
+ gboolean activate);
+
+G_END_DECLS
+
+#endif /* GTH_FILE_SOURCE_H */
diff --git a/gthumb/gth-file-store.c b/gthumb/gth-file-store.c
new file mode 100644
index 0000000..b4f75aa
--- /dev/null
+++ b/gthumb/gth-file-store.c
@@ -0,0 +1,1279 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2008-2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <glib/gi18n.h>
+#include "glib-utils.h"
+#include "gth-file-store.h"
+
+
+#undef DEBUG_FILE_STORE
+#define VALID_ITER(iter, store) (((iter) != NULL) && ((iter)->stamp == (store)->priv->stamp) && ((iter)->user_data != NULL))
+#define REALLOC_STEP 32
+
+
+static GType column_type[GTH_FILE_STORE_N_COLUMNS] = { G_TYPE_INVALID, };
+
+
+typedef struct {
+ GthFileData *file;
+ GdkPixbuf *thumbnail;
+ gboolean is_icon;
+ char *metadata;
+
+ /*< private >*/
+
+ guint pos;
+ guint abs_pos;
+ gboolean visible;
+ gboolean changed;
+} GthFileRow;
+
+
+struct _GthFileStorePrivate
+{
+ GthFileRow **all_rows;
+ GthFileRow **rows;
+ guint size;
+ guint tot_rows;
+ guint num_rows;
+ int stamp;
+ gboolean load_thumbs;
+ GthTest *filter;
+ GList *queue;
+ int queue_size;
+ GthFileDataCompFunc cmp_func;
+ gboolean inverse_sort;
+ gboolean update_filter;
+};
+
+
+static GObjectClass *parent_class = NULL;
+
+
+static GthFileRow *
+_gth_file_row_new (void)
+{
+ return g_new0 (GthFileRow, 1);
+}
+
+
+static void
+_gth_file_row_set_file (GthFileRow *row,
+ GthFileData *file)
+{
+ if (file != NULL) {
+ g_object_ref (file);
+ if (row->file != NULL)
+ g_object_unref (row->file);
+ row->file = file;
+ }
+}
+
+
+static void
+_gth_file_row_set_thumbnail (GthFileRow *row,
+ GdkPixbuf *thumbnail)
+{
+ if (thumbnail != NULL) {
+ g_object_ref (thumbnail);
+ if (row->thumbnail != NULL)
+ g_object_unref (row->thumbnail);
+ row->thumbnail = thumbnail;
+ }
+}
+
+
+static void
+_gth_file_row_set_metadata (GthFileRow *row,
+ const char *metadata)
+{
+ if (metadata == NULL)
+ return;
+
+ if (metadata == row->metadata)
+ return;
+
+ g_free (row->metadata);
+ row->metadata = NULL;
+ row->metadata = g_strdup (metadata);
+}
+
+
+GthFileRow *
+_gth_file_row_copy (GthFileRow *row)
+{
+ GthFileRow *row2;
+
+ row2 = _gth_file_row_new ();
+ _gth_file_row_set_file (row2, row->file);
+ _gth_file_row_set_thumbnail (row2, row->thumbnail);
+ _gth_file_row_set_metadata (row2, row->metadata);
+ row2->is_icon = row->is_icon;
+ row2->pos = row->pos;
+ row2->abs_pos = row->abs_pos;
+ row2->visible = row->visible;
+ row2->changed = row->changed;
+
+ return row2;
+}
+
+
+static void
+_gth_file_row_free (GthFileRow *row)
+{
+ if (row->file != NULL)
+ g_object_unref (row->file);
+ if (row->thumbnail != NULL)
+ g_object_unref (row->thumbnail);
+ g_free (row->metadata);
+ g_free (row);
+}
+
+
+static void
+_gth_file_store_clear_queue (GthFileStore *file_store)
+{
+ g_list_free (file_store->priv->queue);
+ file_store->priv->queue = NULL;
+ file_store->priv->queue_size = 0;
+ file_store->priv->update_filter = FALSE;
+}
+
+
+static void
+_gth_file_store_free_rows (GthFileStore *file_store)
+{
+ int i;
+
+ for (i = 0; i < file_store->priv->tot_rows; i++)
+ _gth_file_row_free (file_store->priv->all_rows[i]);
+ g_free (file_store->priv->all_rows);
+ file_store->priv->all_rows = NULL;
+ file_store->priv->tot_rows = 0;
+
+ g_free (file_store->priv->rows);
+ file_store->priv->rows = NULL;
+ file_store->priv->num_rows = 0;
+
+ file_store->priv->size = 0;
+
+ _gth_file_store_clear_queue (file_store);
+}
+
+
+static void
+gth_file_store_finalize (GObject *object)
+{
+ GthFileStore *file_store;
+
+ file_store = GTH_FILE_STORE (object);
+
+ if (file_store->priv != NULL) {
+ _gth_file_store_free_rows (file_store);
+ if (file_store->priv->filter != NULL)
+ g_object_unref (file_store->priv->filter);
+ g_free (file_store->priv);
+ file_store->priv = NULL;
+ }
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+
+static void
+gth_file_store_class_init (GthFileStoreClass *class)
+{
+ GObjectClass *object_class;
+
+ parent_class = g_type_class_peek_parent (class);
+
+ object_class = (GObjectClass*) class;
+ object_class->finalize = gth_file_store_finalize;
+}
+
+
+static void
+gth_file_store_init (GthFileStore *file_store)
+{
+ file_store->priv = g_new0 (GthFileStorePrivate, 1);
+ file_store->priv->rows = NULL;
+ file_store->priv->size = 0;
+ file_store->priv->num_rows = 0;
+ file_store->priv->stamp = g_random_int ();
+ file_store->priv->filter = gth_test_new ();
+
+ if (column_type[0] == G_TYPE_INVALID) {
+ column_type[GTH_FILE_STORE_FILE_COLUMN] = GTH_TYPE_FILE_DATA;
+ column_type[GTH_FILE_STORE_THUMBNAIL_COLUMN] = GDK_TYPE_PIXBUF;
+ column_type[GTH_FILE_STORE_IS_ICON_COLUMN] = G_TYPE_BOOLEAN;
+ column_type[GTH_FILE_STORE_FILENAME_COLUMN] = G_TYPE_STRING;
+ column_type[GTH_FILE_STORE_METADATA_COLUMN] = G_TYPE_STRING;
+ }
+}
+
+
+static GtkTreeModelFlags
+gth_file_store_get_flags (GtkTreeModel *tree_model)
+{
+ return GTK_TREE_MODEL_LIST_ONLY /*| GTK_TREE_MODEL_ITERS_PERSIST*/;
+}
+
+
+static gint
+gth_file_store_get_n_columns (GtkTreeModel *tree_model)
+{
+ return GTH_FILE_STORE_N_COLUMNS;
+}
+
+
+static GType
+gth_file_store_get_column_type (GtkTreeModel *tree_model,
+ int index)
+{
+ g_return_val_if_fail ((index >= 0) && (index < GTH_FILE_STORE_N_COLUMNS), G_TYPE_INVALID);
+
+ return column_type[index];
+}
+
+
+static gboolean
+gth_file_store_get_iter (GtkTreeModel *tree_model,
+ GtkTreeIter *iter,
+ GtkTreePath *path)
+{
+ GthFileStore *file_store;
+ GthFileRow *row;
+ int *indices, n;
+
+ g_return_val_if_fail (path != NULL, FALSE);
+
+ file_store = GTH_FILE_STORE (tree_model);
+
+ indices = gtk_tree_path_get_indices (path);
+ n = indices[0];
+ if ((n < 0) || (n >= file_store->priv->num_rows))
+ return FALSE;
+
+ row = file_store->priv->rows[n];
+ g_return_val_if_fail (row != NULL, FALSE);
+ g_return_val_if_fail (row->pos == n, FALSE);
+
+ iter->stamp = file_store->priv->stamp;
+ iter->user_data = row;
+
+ return TRUE;
+}
+
+
+static GtkTreePath *
+gth_file_store_get_path (GtkTreeModel *tree_model,
+ GtkTreeIter *iter)
+{
+ GthFileStore *file_store;
+ GthFileRow *row;
+ GtkTreePath *path;
+
+ g_return_val_if_fail (iter != NULL, NULL);
+ g_return_val_if_fail (iter->user_data != NULL, NULL);
+
+ file_store = GTH_FILE_STORE (tree_model);
+
+ g_return_val_if_fail (VALID_ITER (iter, file_store), NULL);
+
+ row = (GthFileRow*) iter->user_data;
+
+ path = gtk_tree_path_new ();
+ gtk_tree_path_append_index (path, row->pos);
+
+ return path;
+}
+
+
+static void
+gth_file_store_get_value (GtkTreeModel *tree_model,
+ GtkTreeIter *iter,
+ int column,
+ GValue *value)
+{
+ GthFileStore *file_store;
+ GthFileRow *row;
+
+ g_return_if_fail ((column >= 0) && (column < GTH_FILE_STORE_N_COLUMNS));
+
+ file_store = GTH_FILE_STORE (tree_model);
+
+ g_return_if_fail (VALID_ITER (iter, file_store));
+
+ row = (GthFileRow*) iter->user_data;
+
+ switch (column) {
+ case GTH_FILE_STORE_FILE_COLUMN:
+ g_value_init (value, GTH_TYPE_FILE_DATA);
+ g_value_set_object (value, row->file);
+ break;
+ case GTH_FILE_STORE_THUMBNAIL_COLUMN:
+ g_value_init (value, GDK_TYPE_PIXBUF);
+ g_value_set_object (value, row->thumbnail);
+ break;
+ case GTH_FILE_STORE_IS_ICON_COLUMN:
+ g_value_init (value, G_TYPE_BOOLEAN);
+ g_value_set_boolean (value, row->is_icon);
+ break;
+ case GTH_FILE_STORE_FILENAME_COLUMN:
+ g_value_init (value, G_TYPE_STRING);
+ g_value_set_string (value, g_file_info_get_display_name (row->file->info));
+ break;
+ case GTH_FILE_STORE_METADATA_COLUMN:
+ g_value_init (value, G_TYPE_STRING);
+ g_value_set_string (value, row->metadata);
+ break;
+ }
+}
+
+
+static gboolean
+gth_file_store_iter_next (GtkTreeModel *tree_model,
+ GtkTreeIter *iter)
+{
+ GthFileStore *file_store;
+ GthFileRow *row;
+
+ if ((iter == NULL) || (iter->user_data == NULL))
+ return FALSE;
+
+ file_store = GTH_FILE_STORE (tree_model);
+
+ g_return_val_if_fail (VALID_ITER (iter, file_store), FALSE);
+
+ row = (GthFileRow*) iter->user_data;
+ if ((row->pos + 1) >= file_store->priv->num_rows)
+ return FALSE;
+
+ iter->stamp = file_store->priv->stamp;
+ iter->user_data = file_store->priv->rows[row->pos + 1];
+
+ return TRUE;
+}
+
+
+static gboolean
+gth_file_store_iter_children (GtkTreeModel *tree_model,
+ GtkTreeIter *iter,
+ GtkTreeIter *parent)
+{
+ GthFileStore *file_store;
+
+ if (parent != NULL)
+ return FALSE;
+
+ g_return_val_if_fail (parent->user_data != NULL, FALSE);
+
+ file_store = GTH_FILE_STORE (tree_model);
+
+ if (file_store->priv->num_rows == 0)
+ return FALSE;
+
+ iter->stamp = file_store->priv->stamp;
+ iter->user_data = file_store->priv->rows[0];
+
+ return TRUE;
+}
+
+
+static gboolean
+gth_file_store_iter_has_child (GtkTreeModel *tree_model,
+ GtkTreeIter *iter)
+{
+ return FALSE;
+}
+
+
+static gint
+gth_file_store_iter_n_children (GtkTreeModel *tree_model,
+ GtkTreeIter *iter)
+{
+ GthFileStore *file_store;
+
+ file_store = GTH_FILE_STORE (tree_model);
+
+ if (iter == NULL)
+ return file_store->priv->num_rows;
+
+ g_return_val_if_fail (VALID_ITER (iter, file_store), 0);
+
+ return 0;
+}
+
+
+static gboolean
+gth_file_store_iter_nth_child (GtkTreeModel *tree_model,
+ GtkTreeIter *iter,
+ GtkTreeIter *parent,
+ int n)
+{
+ GthFileStore *file_store;
+
+ file_store = GTH_FILE_STORE (tree_model);
+
+ if (parent != NULL) {
+ g_return_val_if_fail (VALID_ITER (parent, file_store), FALSE);
+ return FALSE;
+ }
+
+ if (n >= file_store->priv->num_rows)
+ return FALSE;
+
+ iter->stamp = file_store->priv->stamp;
+ iter->user_data = file_store->priv->rows[n];
+
+ return TRUE;
+}
+
+
+static gboolean
+gth_file_store_iter_parent (GtkTreeModel *tree_model,
+ GtkTreeIter *iter,
+ GtkTreeIter *child)
+{
+ GthFileStore *file_store;
+
+ g_return_val_if_fail (child == NULL, FALSE);
+
+ file_store = GTH_FILE_STORE (tree_model);
+
+ g_return_val_if_fail (VALID_ITER (child, file_store), FALSE);
+
+ return FALSE;
+}
+
+
+static void
+tree_model_iface_init (GtkTreeModelIface *iface)
+{
+ iface->get_flags = gth_file_store_get_flags;
+ iface->get_n_columns = gth_file_store_get_n_columns;
+ iface->get_column_type = gth_file_store_get_column_type;
+ iface->get_iter = gth_file_store_get_iter;
+ iface->get_path = gth_file_store_get_path;
+ iface->get_value = gth_file_store_get_value;
+ iface->iter_next = gth_file_store_iter_next;
+ iface->iter_children = gth_file_store_iter_children;
+ iface->iter_has_child = gth_file_store_iter_has_child;
+ iface->iter_n_children = gth_file_store_iter_n_children;
+ iface->iter_nth_child = gth_file_store_iter_nth_child;
+ iface->iter_parent = gth_file_store_iter_parent;
+}
+
+
+GType
+gth_file_store_get_type (void)
+{
+ static GType type = 0;
+
+ if (! type) {
+ GTypeInfo type_info = {
+ sizeof (GthFileStoreClass),
+ NULL,
+ NULL,
+ (GClassInitFunc) gth_file_store_class_init,
+ NULL,
+ NULL,
+ sizeof (GthFileStore),
+ 0,
+ (GInstanceInitFunc) gth_file_store_init
+ };
+ static const GInterfaceInfo tree_model_info = {
+ (GInterfaceInitFunc) tree_model_iface_init,
+ NULL,
+ NULL
+ };
+
+ type = g_type_register_static (G_TYPE_OBJECT,
+ "GthFileStore",
+ &type_info,
+ 0);
+
+ g_type_add_interface_static (type, GTK_TYPE_TREE_MODEL, &tree_model_info);
+ }
+
+ return type;
+}
+
+
+GthFileStore *
+gth_file_store_new (void)
+{
+ return (GthFileStore*) g_object_new (GTH_TYPE_FILE_STORE, NULL);
+}
+
+
+int
+gth_file_store_get_abs_pos (GthFileStore *file_store,
+ int pos)
+{
+ g_return_val_if_fail ((pos >= 0) && (pos < file_store->priv->num_rows), -1);
+
+ return file_store->priv->rows[pos]->abs_pos;
+}
+
+
+static void
+_gth_file_store_increment_stamp (GthFileStore *file_store)
+{
+ do {
+ file_store->priv->stamp++;
+ }
+ while (file_store->priv->stamp == 0);
+}
+
+
+G_GNUC_UNUSED
+static GList *
+_gth_file_store_get_files (GthFileStore *file_store)
+{
+ GList *files = NULL;
+ int i;
+
+ for (i = 0; i < file_store->priv->tot_rows; i++) {
+ GthFileRow *row = file_store->priv->all_rows[i];
+ files = g_list_prepend (files, g_object_ref (row->file));
+ }
+
+ return g_list_reverse (files);
+}
+
+
+static void
+_gth_file_store_row_changed (GthFileStore *file_store,
+ GthFileRow *row)
+{
+ GtkTreePath *path;
+ GtkTreeIter iter;
+
+ path = gtk_tree_path_new ();
+ gtk_tree_path_append_index (path, row->pos);
+ gth_file_store_get_iter (GTK_TREE_MODEL (file_store), &iter, path);
+
+ gtk_tree_model_row_changed (GTK_TREE_MODEL (file_store), path, &iter);
+
+ gtk_tree_path_free (path);
+}
+
+
+static int
+campare_row_func (gconstpointer a,
+ gconstpointer b,
+ gpointer user_data)
+{
+ GthFileStore *file_store = user_data;
+ GthFileRow *row_a = *((GthFileRow **) a);
+ GthFileRow *row_b = *((GthFileRow **) b);
+ int result;
+
+ if (file_store->priv->cmp_func == NULL)
+ result = 1 /*strcmp (gth_file_data_get_filename_sort_key (row_a->file),
+ gth_file_data_get_filename_sort_key (row_b->file))*/;
+ else
+ result = file_store->priv->cmp_func (row_a->file, row_b->file);
+
+ if (file_store->priv->inverse_sort)
+ result = result * -1;
+
+ return result;
+}
+
+
+static void
+_gth_file_store_sort (GthFileStore *file_store,
+ gconstpointer pbase,
+ gint total_elems)
+{
+ g_qsort_with_data (pbase,
+ total_elems,
+ (gsize) sizeof (GthFileRow *),
+ campare_row_func,
+ file_store);
+}
+
+
+static int
+campare_by_pos (gconstpointer a,
+ gconstpointer b,
+ gpointer user_data)
+{
+ GthFileRow *row_a = *((GthFileRow **) a);
+ GthFileRow *row_b = *((GthFileRow **) b);
+
+ if (row_a->pos == row_b->pos)
+ return 0;
+ else if (row_a->pos > row_b->pos)
+ return 1;
+ else
+ return -1;
+}
+
+
+static void
+_gth_file_store_compact_rows (GthFileStore *file_store)
+{
+ int i, j;
+
+ for (i = 0, j = 0; i < file_store->priv->tot_rows; i++)
+ if (file_store->priv->all_rows[i] != NULL) {
+ file_store->priv->all_rows[j] = file_store->priv->all_rows[i];
+ file_store->priv->all_rows[j]->abs_pos = j;
+ j++;
+ }
+ file_store->priv->tot_rows = j;
+
+ for (i = 0, j = 0; i < file_store->priv->num_rows; i++)
+ if (file_store->priv->rows[i] != NULL) {
+ file_store->priv->rows[j] = file_store->priv->rows[i];
+ file_store->priv->rows[j]->pos = j;
+ j++;
+ }
+ file_store->priv->num_rows = j;
+}
+
+
+static void
+_gth_file_store_hide_row (GthFileStore *file_store,
+ GthFileRow *row)
+{
+ int k;
+ GtkTreePath *path;
+
+ file_store->priv->rows[row->pos] = NULL;
+ for (k = row->pos; k < file_store->priv->num_rows - 1; k++) {
+ file_store->priv->rows[k] = file_store->priv->rows[k + 1];
+ file_store->priv->rows[k]->pos--;
+ }
+ file_store->priv->num_rows--;
+
+ path = gtk_tree_path_new_from_indices (row->pos, -1);
+ gtk_tree_model_row_deleted (GTK_TREE_MODEL (file_store), path);
+ gtk_tree_path_free (path);
+}
+
+
+static void
+_gth_file_store_update_visibility (GthFileStore *file_store,
+ GList *add_queue)
+{
+ GthFileRow **all_rows = NULL;
+ guint all_rows_n = 0;
+ GthFileRow **old_rows = NULL;
+ guint old_rows_n = 0;
+ GthFileRow **new_rows = NULL;
+ guint new_rows_n = 0;
+ int i;
+ GList *files;
+ GList *scan;
+ GthFileData *file;
+ int j, k;
+ gboolean row_deleted;
+ gboolean row_inserted;
+
+
+#ifdef DEBUG_FILE_STORE
+g_print ("UPDATE VISIBILITY\n");
+#endif
+
+ /* store the current state */
+
+ all_rows_n = file_store->priv->tot_rows + g_list_length (add_queue);
+ all_rows = g_new (GthFileRow *, all_rows_n);
+ for (i = 0; i < file_store->priv->tot_rows; i++)
+ all_rows[i] = _gth_file_row_copy (file_store->priv->all_rows[i]);
+
+ for (scan = add_queue; scan; scan = scan->next)
+ all_rows[i++] = (GthFileRow *) scan->data;
+
+ old_rows_n = file_store->priv->num_rows;
+ old_rows = g_new (GthFileRow *, old_rows_n);
+ for (i = 0, j = 0; i < all_rows_n; i++) {
+ if (all_rows[i]->visible) {
+ old_rows[j] = all_rows[i];
+ old_rows[j]->visible = FALSE;
+ j++;
+ }
+ }
+
+ /* reorder and filter */
+
+ _gth_file_store_sort (file_store, all_rows, all_rows_n);
+
+ files = NULL;
+ for (i = 0; i < all_rows_n; i++) {
+ GthFileRow *row = all_rows[i];
+
+ row->abs_pos = i;
+ files = g_list_prepend (files, g_object_ref (row->file));
+ }
+ files = g_list_reverse (files);
+
+ new_rows_n = 0;
+ gth_test_set_file_list (file_store->priv->filter, files);
+ while ((file = gth_test_get_next (file_store->priv->filter)) != NULL) {
+ GthFileRow *row = NULL;
+
+ for (i = 0; i < all_rows_n; i++) {
+ row = all_rows[i];
+ if (row->file == file)
+ break;
+ }
+
+ g_assert (i < all_rows_n);
+
+ row->visible = TRUE;
+ new_rows_n++;
+ }
+
+ _g_object_list_unref (files);
+
+ /* create the new visible rows array */
+
+ new_rows = g_new (GthFileRow *, new_rows_n);
+ for (i = 0, j = 0; i < all_rows_n; i++) {
+ GthFileRow *row = all_rows[i];
+
+ if (! row->visible)
+ continue;
+
+ row->pos = j++;
+ new_rows[row->pos] = row;
+ }
+
+ /* emit the signals required to go from the old state to the new state */
+
+ /* hide filtered out files */
+
+ row_deleted = FALSE;
+ for (i = old_rows_n - 1; i >= 0; i--) {
+ /* search old_rows[i] in new_rows */
+
+ for (j = 0; j < new_rows_n; j++)
+ if (old_rows[i]->file == new_rows[j]->file)
+ break;
+
+ if (j < new_rows_n)
+ continue;
+
+ /* old_rows[i] is not present in new_rows, emit a deleted
+ * signal. */
+
+#ifdef DEBUG_FILE_STORE
+g_print (" DELETE: %d\n", old_rows[i]->pos);
+#endif
+
+ _gth_file_store_hide_row (file_store, old_rows[i]);
+
+ old_rows[i] = NULL;
+ row_deleted = TRUE;
+ }
+
+ /* compact the old visible rows array if needed */
+
+ if (row_deleted) {
+ for (i = 0, j = 0; i < old_rows_n; i++) {
+ if (old_rows[i] != NULL) {
+ old_rows[j] = old_rows[i];
+ old_rows[j]->abs_pos = j;
+ j++;
+ }
+ }
+ old_rows_n = j;
+ }
+
+ /* Both old_rows and file_store->priv->rows now contain the files
+ * visible before and after the filtering.
+ * Reorder the two arrays according to the new_rows order */
+
+ if (old_rows_n > 0) {
+ gboolean order_changed;
+ int *new_order;
+ GthFileRow *tmp_row;
+
+ order_changed = FALSE;
+ new_order = g_new0 (int, old_rows_n);
+ for (i = 0, k = 0; i < new_rows_n; i++) {
+ /* search new_rows[i] in old_rows */
+
+ for (j = 0; j < old_rows_n; j++)
+ if (new_rows[i]->file == old_rows[j]->file)
+ break;
+
+ if (j >= old_rows_n)
+ continue;
+
+ /* old_rows[j] == new_rows[i] */
+
+ /* k is the new position of old_rows[j] in new_rows
+ * without considering the new elements, that is
+ * the elements in new_rows not present in old_rows */
+
+ new_order[k] = j;
+ old_rows[j]->pos = k;
+
+ /* swap the position of j and k in file_store->priv->rows
+ * old_rows can't be reordered now because we need
+ * to know the old positions, it will be ordered
+ * later with g_qsort_with_data */
+
+ if (k != j) {
+ tmp_row = file_store->priv->rows[j];
+ file_store->priv->rows[j] = file_store->priv->rows[k];
+ file_store->priv->rows[j]->pos = j;
+ file_store->priv->rows[k] = tmp_row;
+ file_store->priv->rows[k]->pos = k;
+
+ order_changed = TRUE;
+ }
+
+ k++;
+ }
+ if (order_changed) {
+
+#ifdef DEBUG_FILE_STORE
+g_print (" REORDER: ");
+for (i = 0; i < old_rows_n; i++)
+ g_print ("%d ", new_order[i]);
+g_print ("\n");
+#endif
+
+ gtk_tree_model_rows_reordered (GTK_TREE_MODEL (file_store), NULL, NULL, new_order);
+ }
+
+ g_free (new_order);
+ }
+
+ /* set the new state */
+
+ _gth_file_store_increment_stamp (file_store);
+
+ for (i = 0; i < file_store->priv->tot_rows; i++)
+ _gth_file_row_free (file_store->priv->all_rows[i]);
+ g_free (file_store->priv->all_rows);
+ file_store->priv->all_rows = all_rows;
+ file_store->priv->tot_rows = all_rows_n;
+
+ g_qsort_with_data (old_rows,
+ old_rows_n,
+ (gsize) sizeof (GthFileRow *),
+ campare_by_pos,
+ NULL);
+
+ g_free (file_store->priv->rows);
+ file_store->priv->rows = g_new (GthFileRow *, new_rows_n);
+ for (i = 0; i < old_rows_n; i++)
+ file_store->priv->rows[i] = old_rows[i];
+
+ g_assert (file_store->priv->num_rows == old_rows_n);
+
+ /* add the new files */
+
+ row_inserted = FALSE;
+ for (i = 0; i < new_rows_n; i++) {
+ GtkTreePath *path;
+ GtkTreeIter iter;
+
+ if ((i < file_store->priv->num_rows) && (new_rows[i]->file == file_store->priv->rows[i]->file))
+ continue;
+
+#ifdef DEBUG_FILE_STORE
+g_print (" INSERT: %d\n", i);
+#endif
+
+ /* add the file to the visible rows */
+
+ for (k = file_store->priv->num_rows; k > i; k--) {
+ file_store->priv->rows[k] = file_store->priv->rows[k - 1];
+ file_store->priv->rows[k]->pos++;
+ }
+ file_store->priv->rows[i] = new_rows[i];
+ file_store->priv->rows[i]->pos = i;
+ file_store->priv->num_rows++;
+
+ path = gtk_tree_path_new ();
+ gtk_tree_path_append_index (path, i);
+ gth_file_store_get_iter (GTK_TREE_MODEL (file_store), &iter, path);
+ gtk_tree_model_row_inserted (GTK_TREE_MODEL (file_store), path, &iter);
+ gtk_tree_path_free (path);
+
+ row_inserted = TRUE;
+ }
+
+ g_free (new_rows);
+ g_free (old_rows);
+}
+
+
+void
+gth_file_store_set_filter (GthFileStore *file_store,
+ GthTest *filter)
+{
+ if (file_store->priv->filter != NULL) {
+ g_object_unref (file_store->priv->filter);
+ file_store->priv->filter = NULL;
+ }
+
+ if (filter != NULL)
+ file_store->priv->filter = g_object_ref (filter);
+ else
+ file_store->priv->filter = gth_test_new ();
+
+ _gth_file_store_update_visibility (file_store, NULL);
+}
+
+
+static void
+_gth_file_store_reorder (GthFileStore *file_store)
+{
+ int *new_order;
+ int i, j;
+
+ _gth_file_store_sort (file_store, file_store->priv->all_rows, file_store->priv->tot_rows);
+
+ /* compute the new position for each row */
+
+ new_order = g_new (int, file_store->priv->num_rows);
+ for (i = 0, j = -1; i < file_store->priv->tot_rows; i++) {
+ GthFileRow *row = file_store->priv->all_rows[i];
+ if (row->visible) {
+ j = j + 1;
+ file_store->priv->rows[j] = row;
+ new_order[j] = row->pos;
+ row->pos = j;
+ }
+ row->abs_pos = i;
+ }
+ if (new_order != NULL)
+ gtk_tree_model_rows_reordered (GTK_TREE_MODEL (file_store), NULL, NULL, new_order);
+ g_free (new_order);
+}
+
+
+void
+gth_file_store_set_sort_func (GthFileStore *file_store,
+ GthFileDataCompFunc cmp_func,
+ gboolean inverse_sort)
+{
+ file_store->priv->cmp_func = cmp_func;
+ file_store->priv->inverse_sort = inverse_sort;
+ _gth_file_store_reorder (file_store);
+}
+
+
+GList *
+gth_file_store_get_all (GthFileStore *file_store)
+{
+ GList *list = NULL;
+ int i;
+
+ for (i = 0; i < file_store->priv->tot_rows; i++)
+ list = g_list_prepend (list, g_object_ref (file_store->priv->all_rows[i]->file));
+
+ return g_list_reverse (list);
+}
+
+
+int
+gth_file_store_n_files (GthFileStore *file_store)
+{
+ return file_store->priv->tot_rows;
+}
+
+
+GList *
+gth_file_store_get_visibles (GthFileStore *file_store)
+{
+ GList *list = NULL;
+ int i;
+
+ for (i = 0; i < file_store->priv->num_rows; i++)
+ list = g_list_prepend (list, g_object_ref (file_store->priv->rows[i]->file));
+
+ return g_list_reverse (list);
+}
+
+
+int
+gth_file_store_n_visibles (GthFileStore *file_store)
+{
+ return file_store->priv->num_rows;
+}
+
+
+GthFileData *
+gth_file_store_get_file (GthFileStore *file_store,
+ GtkTreeIter *iter)
+{
+ g_return_val_if_fail (VALID_ITER (iter, file_store), NULL);
+
+ return ((GthFileRow *) iter->user_data)->file;
+}
+
+
+GthFileData *
+gth_file_store_get_file_at_pos (GthFileStore *file_store,
+ int pos)
+{
+ GthFileData *file_data = NULL;
+ GtkTreePath *path;
+ GtkTreeIter iter;
+
+ if (pos < 0)
+ return NULL;
+
+ path = gtk_tree_path_new ();
+ gtk_tree_path_append_index (path, pos);
+ if (gtk_tree_model_get_iter (GTK_TREE_MODEL (file_store), &iter, path))
+ file_data = gth_file_store_get_file (file_store, &iter);
+
+ gtk_tree_path_free (path);
+
+ return file_data;
+}
+
+
+int
+gth_file_store_find (GthFileStore *file_store,
+ GFile *file)
+{
+ int i;
+
+ for (i = 0; i < file_store->priv->tot_rows; i++) {
+ GthFileRow *row = file_store->priv->all_rows[i];
+
+ if (row == NULL)
+ continue;
+
+ if (g_file_equal (row->file->file, file))
+ return i;
+ }
+
+ return -1;
+}
+
+
+int
+gth_file_store_find_visible (GthFileStore *file_store,
+ GFile *file)
+{
+ int i;
+
+ for (i = 0; i < file_store->priv->num_rows; i++) {
+ GthFileRow *row = file_store->priv->rows[i];
+
+ if (row == NULL)
+ continue;
+
+ if (g_file_equal (row->file->file, file))
+ return i;
+ }
+
+ return -1;
+}
+
+
+void
+gth_file_store_add (GthFileStore *file_store,
+ GthFileData *file,
+ GdkPixbuf *thumbnail,
+ gboolean is_icon,
+ const char *metadata)
+{
+ gth_file_store_queue_add (file_store, file, thumbnail, is_icon, metadata);
+ gth_file_store_exec_add (file_store);
+}
+
+
+void
+gth_file_store_queue_add (GthFileStore *file_store,
+ GthFileData *file,
+ GdkPixbuf *thumbnail,
+ gboolean is_icon,
+ const char *metadata)
+{
+ GthFileRow *row;
+
+ g_return_if_fail (file != NULL);
+
+ /*file_store->priv->tot_rows++;
+ if (file_store->priv->tot_rows > file_store->priv->size) {
+ file_store->priv->size += REALLOC_STEP;
+ file_store->priv->all_rows = g_realloc (file_store->priv->all_rows, sizeof (GthFileRow*) * file_store->priv->size);
+ }
+
+ row = _gth_file_row_new ();
+ _gth_file_row_set_file (row, file);
+ _gth_file_row_set_thumbnail (row, thumbnail);
+ _gth_file_row_set_metadata (row, metadata);
+ row->is_icon = is_icon;
+ row->abs_pos = file_store->priv->tot_rows - 1;
+ file_store->priv->all_rows[row->abs_pos] = row;*/
+
+ row = _gth_file_row_new ();
+ _gth_file_row_set_file (row, file);
+ _gth_file_row_set_thumbnail (row, thumbnail);
+ _gth_file_row_set_metadata (row, metadata);
+ row->is_icon = is_icon;
+ file_store->priv->queue = g_list_prepend (file_store->priv->queue, row);
+}
+
+
+void
+gth_file_store_exec_add (GthFileStore *file_store)
+{
+ _gth_file_store_update_visibility (file_store, file_store->priv->queue);
+ _gth_file_store_clear_queue (file_store);
+}
+
+
+void
+gth_file_store_set (GthFileStore *file_store,
+ int abs_pos,
+ GthFileData *file,
+ GdkPixbuf *thumbnail,
+ gboolean is_icon,
+ const char *metadata)
+{
+ gth_file_store_queue_set (file_store, abs_pos, file, thumbnail, is_icon, metadata);
+ gth_file_store_exec_set (file_store);
+}
+
+
+void
+gth_file_store_queue_set (GthFileStore *file_store,
+ int abs_pos,
+ GthFileData *file,
+ GdkPixbuf *thumbnail,
+ gboolean is_icon,
+ const char *metadata)
+{
+ GthFileRow *row;
+
+ g_return_if_fail ((abs_pos >= 0) && (abs_pos < file_store->priv->tot_rows));
+
+ row = file_store->priv->all_rows[abs_pos];
+ _gth_file_row_set_file (row, file);
+ _gth_file_row_set_thumbnail (row, thumbnail);
+ _gth_file_row_set_metadata (row, metadata);
+ row->is_icon = is_icon;
+ row->changed = TRUE;
+
+ if (file != NULL)
+ file_store->priv->update_filter = TRUE;
+}
+
+
+static void
+_gth_file_store_list_changed (GthFileStore *file_store)
+{
+ int i;
+
+ for (i = 0; i < file_store->priv->num_rows; i++) {
+ GthFileRow *row = file_store->priv->rows[i];
+
+ if (row->visible && row->changed)
+ _gth_file_store_row_changed (file_store, row);
+ row->changed = FALSE;
+ }
+}
+
+
+void
+gth_file_store_exec_set (GthFileStore *file_store)
+{
+ _gth_file_store_list_changed (file_store);
+ _gth_file_store_clear_queue (file_store);
+ if (file_store->priv->update_filter)
+ _gth_file_store_update_visibility (file_store, NULL);
+}
+
+
+void
+gth_file_store_remove (GthFileStore *file_store,
+ int abs_pos)
+{
+ gth_file_store_queue_remove (file_store, abs_pos);
+ gth_file_store_exec_remove (file_store);
+}
+
+
+void
+gth_file_store_queue_remove (GthFileStore *file_store,
+ int abs_pos)
+{
+ g_return_if_fail ((abs_pos >= 0) && (abs_pos < file_store->priv->tot_rows));
+
+ file_store->priv->queue = g_list_prepend (file_store->priv->queue, file_store->priv->all_rows[abs_pos]);
+}
+
+
+void
+gth_file_store_exec_remove (GthFileStore *file_store)
+{
+ GList *scan;
+
+ if (file_store->priv->queue == NULL)
+ return;
+
+ file_store->priv->queue = g_list_reverse (file_store->priv->queue);
+ for (scan = file_store->priv->queue; scan; scan = scan->next) {
+ GthFileRow *row = scan->data;
+
+ if (row->visible)
+ _gth_file_store_hide_row (file_store, row);
+
+ file_store->priv->all_rows[row->abs_pos] = NULL;
+ _gth_file_row_free (row);
+ }
+ _gth_file_store_compact_rows (file_store);
+ _gth_file_store_clear_queue (file_store);
+
+ _gth_file_store_update_visibility (file_store, NULL);
+}
+
+
+void
+gth_file_store_clear (GthFileStore *file_store)
+{
+ int i;
+
+ for (i = file_store->priv->tot_rows - 1; i >= 0; i--) {
+ GthFileRow *row = file_store->priv->all_rows[i];
+
+ if (row->visible) {
+ GtkTreePath *path;
+
+ path = gtk_tree_path_new ();
+ gtk_tree_path_append_index (path, row->pos);
+ gtk_tree_model_row_deleted (GTK_TREE_MODEL (file_store), path);
+ gtk_tree_path_free (path);
+ }
+ }
+
+ _gth_file_store_free_rows (file_store);
+ _gth_file_store_increment_stamp (file_store);
+}
diff --git a/gthumb/gth-file-store.h b/gthumb/gth-file-store.h
new file mode 100644
index 0000000..90c8c29
--- /dev/null
+++ b/gthumb/gth-file-store.h
@@ -0,0 +1,117 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2008 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GTH_FILE_STORE_H
+#define GTH_FILE_STORE_H
+
+#include <gtk/gtk.h>
+#include "gth-file-data.h"
+#include "gth-test.h"
+
+G_BEGIN_DECLS
+
+#define GTH_TYPE_FILE_STORE (gth_file_store_get_type ())
+#define GTH_FILE_STORE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GTH_TYPE_FILE_STORE, GthFileStore))
+#define GTH_FILE_STORE_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), GTH_TYPE_FILE_STORE, GthFileStoreClass))
+#define GTH_IS_FILE_STORE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GTH_TYPE_FILE_STORE))
+#define GTH_IS_FILE_STORE_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GTH_TYPE_FILE_STORE))
+#define GTH_FILE_STORE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS((o), GTH_TYPE_FILE_STORE, GthFileStoreClass))
+
+typedef struct _GthFileStore GthFileStore;
+typedef struct _GthFileStorePrivate GthFileStorePrivate;
+typedef struct _GthFileStoreClass GthFileStoreClass;
+
+enum {
+ GTH_FILE_STORE_FILE_COLUMN,
+ GTH_FILE_STORE_THUMBNAIL_COLUMN,
+ GTH_FILE_STORE_IS_ICON_COLUMN,
+ GTH_FILE_STORE_FILENAME_COLUMN,
+ GTH_FILE_STORE_METADATA_COLUMN,
+ GTH_FILE_STORE_N_COLUMNS
+};
+
+struct _GthFileStore
+{
+ GObject __parent;
+ GthFileStorePrivate *priv;
+};
+
+struct _GthFileStoreClass
+{
+ GObjectClass __parent_class;
+};
+
+GType gth_file_store_get_type (void) G_GNUC_CONST;
+GthFileStore * gth_file_store_new (void);
+int gth_file_store_get_abs_pos (GthFileStore *file_store,
+ int pos);
+void gth_file_store_set_filter (GthFileStore *file_store,
+ GthTest *filter);
+void gth_file_store_set_sort_func (GthFileStore *file_store,
+ GthFileDataCompFunc cmp_func,
+ gboolean inverse_sort);
+GList * gth_file_store_get_all (GthFileStore *file_store);
+int gth_file_store_n_files (GthFileStore *file_store);
+GList * gth_file_store_get_visibles (GthFileStore *file_store);
+int gth_file_store_n_visibles (GthFileStore *file_store);
+GthFileData * gth_file_store_get_file (GthFileStore *file_store,
+ GtkTreeIter *iter);
+GthFileData * gth_file_store_get_file_at_pos (GthFileStore *file_store,
+ int pos);
+int gth_file_store_find (GthFileStore *file_store,
+ GFile *file);
+int gth_file_store_find_visible (GthFileStore *file_store,
+ GFile *file);
+void gth_file_store_add (GthFileStore *file_store,
+ GthFileData *file,
+ GdkPixbuf *thumbnail,
+ gboolean is_icon,
+ const char *metadata);
+void gth_file_store_queue_add (GthFileStore *file_store,
+ GthFileData *file,
+ GdkPixbuf *thumbnail,
+ gboolean is_icon,
+ const char *metadata);
+void gth_file_store_exec_add (GthFileStore *file_store);
+void gth_file_store_set (GthFileStore *file_store,
+ int abs_pos,
+ GthFileData *file,
+ GdkPixbuf *thumbnail,
+ gboolean is_icon,
+ const char *metadata);
+void gth_file_store_queue_set (GthFileStore *file_store,
+ int abs_pos,
+ GthFileData *file,
+ GdkPixbuf *thumbnail,
+ gboolean is_icon,
+ const char *metadata);
+void gth_file_store_exec_set (GthFileStore *file_store);
+void gth_file_store_remove (GthFileStore *file_store,
+ int abs_pos);
+void gth_file_store_queue_remove (GthFileStore *file_store,
+ int abs_pos);
+void gth_file_store_exec_remove (GthFileStore *file_store);
+void gth_file_store_clear (GthFileStore *file_store);
+
+G_END_DECLS
+
+#endif /* GTH_FILE_STORE_H */
diff --git a/gthumb/gth-file-view.c b/gthumb/gth-file-view.c
new file mode 100644
index 0000000..9360b84
--- /dev/null
+++ b/gthumb/gth-file-view.c
@@ -0,0 +1,159 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2008 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include "gth-file-view.h"
+
+
+void
+gth_file_view_set_model (GthFileView *self,
+ GtkTreeModel *model)
+{
+ GTH_FILE_VIEW_GET_INTERFACE (self)->set_model (self, model);
+}
+
+
+GtkTreeModel *
+gth_file_view_get_model (GthFileView *self)
+{
+ return GTH_FILE_VIEW_GET_INTERFACE (self)->get_model (self);
+}
+
+
+void
+gth_file_view_set_view_mode (GthFileView *self,
+ GthViewMode mode)
+{
+ GTH_FILE_VIEW_GET_INTERFACE (self)->set_view_mode (self, mode);
+}
+
+
+GthViewMode
+gth_file_view_get_view_mode (GthFileView *self)
+{
+ return GTH_FILE_VIEW_GET_INTERFACE (self)->get_view_mode (self);
+}
+
+
+void
+gth_file_view_scroll_to (GthFileView *self,
+ int pos,
+ double yalign)
+{
+ GTH_FILE_VIEW_GET_INTERFACE (self)->scroll_to (self, pos, yalign);
+}
+
+
+GthVisibility
+gth_file_view_get_visibility (GthFileView *self,
+ int pos)
+{
+ return GTH_FILE_VIEW_GET_INTERFACE (self)->get_visibility (self, pos);
+}
+
+
+int
+gth_file_view_get_at_position (GthFileView *self,
+ int x,
+ int y)
+{
+ return GTH_FILE_VIEW_GET_INTERFACE (self)->get_at_position (self, x, y);
+}
+
+
+int
+gth_file_view_get_first_visible (GthFileView *self)
+{
+ return GTH_FILE_VIEW_GET_INTERFACE (self)->get_first_visible (self);
+}
+
+
+int
+gth_file_view_get_last_visible (GthFileView *self)
+{
+ return GTH_FILE_VIEW_GET_INTERFACE (self)->get_last_visible (self);
+}
+
+
+void
+gth_file_view_activated (GthFileView *self,
+ int pos)
+{
+ GTH_FILE_VIEW_GET_INTERFACE (self)->activated (self, pos);
+}
+
+
+void
+gth_file_view_set_cursor (GthFileView *self,
+ int pos)
+{
+ GTH_FILE_VIEW_GET_INTERFACE (self)->set_cursor (self, pos);
+}
+
+
+int
+gth_file_view_get_cursor (GthFileView *self)
+{
+ return GTH_FILE_VIEW_GET_INTERFACE (self)->get_cursor (self);
+}
+
+
+void
+gth_file_view_set_reorderable (GthFileView *self,
+ gboolean value)
+{
+ GTH_FILE_VIEW_GET_INTERFACE (self)->set_reorderable (self, value);
+}
+
+
+gboolean
+gth_file_view_get_reorderable (GthFileView *self)
+{
+ return GTH_FILE_VIEW_GET_INTERFACE (self)->get_reorderable (self);
+}
+
+
+GType
+gth_file_view_get_type (void)
+{
+ static GType type = 0;
+
+ if (type == 0) {
+ static const GTypeInfo g_define_type_info = {
+ sizeof (GthFileViewIface),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) NULL,
+ (GClassFinalizeFunc) NULL,
+ NULL,
+ 0,
+ 0,
+ (GInstanceInitFunc) NULL,
+ NULL
+ };
+ type = g_type_register_static (G_TYPE_INTERFACE,
+ "GthFileView",
+ &g_define_type_info,
+ 0);
+ }
+
+ return type;
+}
diff --git a/gthumb/gth-file-view.h b/gthumb/gth-file-view.h
new file mode 100644
index 0000000..52f2148
--- /dev/null
+++ b/gthumb/gth-file-view.h
@@ -0,0 +1,115 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2008 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GTH_FILE_VIEW_H
+#define GTH_FILE_VIEW_H
+
+#include <glib.h>
+#include <glib-object.h>
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GTH_TYPE_FILE_VIEW (gth_file_view_get_type ())
+#define GTH_FILE_VIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTH_TYPE_FILE_VIEW, GthFileView))
+#define GTH_IS_FILE_VIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTH_TYPE_FILE_VIEW))
+#define GTH_FILE_VIEW_GET_INTERFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), GTH_TYPE_FILE_VIEW, GthFileViewIface))
+
+typedef struct _GthFileView GthFileView;
+typedef struct _GthFileViewIface GthFileViewIface;
+
+typedef enum { /*< skip >*/
+ GTH_VIEW_MODE_NONE,
+ GTH_VIEW_MODE_FILENAME,
+ GTH_VIEW_MODE_COMMENT,
+ GTH_VIEW_MODE_COMMENT_OR_FILENAME,
+ GTH_VIEW_MODE_COMMENT_AND_FILENAME
+} GthViewMode;
+
+typedef enum { /*< skip >*/
+ GTH_VISIBILITY_NONE,
+ GTH_VISIBILITY_FULL,
+ GTH_VISIBILITY_PARTIAL,
+ GTH_VISIBILITY_PARTIAL_TOP,
+ GTH_VISIBILITY_PARTIAL_BOTTOM
+} GthVisibility;
+
+struct _GthFileViewIface {
+ GTypeInterface parent_iface;
+
+ /*< virtual functions >*/
+
+ void (*set_model) (GthFileView *self,
+ GtkTreeModel *model);
+ GtkTreeModel * (*get_model) (GthFileView *self);
+ void (*set_view_mode) (GthFileView *self,
+ GthViewMode mode);
+ GthViewMode (*get_view_mode) (GthFileView *self);
+ void (*scroll_to) (GthFileView *self,
+ int pos,
+ double yalign);
+ GthVisibility (*get_visibility) (GthFileView *self,
+ int pos);
+ int (*get_at_position) (GthFileView *self,
+ int x,
+ int y);
+ int (*get_first_visible) (GthFileView *self);
+ int (*get_last_visible) (GthFileView *self);
+ void (*activated) (GthFileView *self,
+ int pos);
+ void (*set_cursor) (GthFileView *self,
+ int pos);
+ int (*get_cursor) (GthFileView *self);
+ void (*set_reorderable) (GthFileView *self,
+ gboolean value);
+ gboolean (*get_reorderable) (GthFileView *self);
+};
+
+GType gth_file_view_get_type (void);
+void gth_file_view_set_model (GthFileView *self,
+ GtkTreeModel *model);
+GtkTreeModel * gth_file_view_get_model (GthFileView *self);
+void gth_file_view_set_view_mode (GthFileView *self,
+ GthViewMode mode);
+GthViewMode gth_file_view_get_view_mode (GthFileView *self);
+void gth_file_view_scroll_to (GthFileView *self,
+ int pos,
+ double yalign);
+GthVisibility gth_file_view_get_visibility (GthFileView *self,
+ int pos);
+int gth_file_view_get_at_position (GthFileView *self,
+ int x,
+ int y);
+int gth_file_view_get_first_visible (GthFileView *self);
+int gth_file_view_get_last_visible (GthFileView *self);
+void gth_file_view_activated (GthFileView *self,
+ int pos);
+void gth_file_view_set_cursor (GthFileView *self,
+ int pos);
+int gth_file_view_get_cursor (GthFileView *self);
+void gth_file_view_set_reorderable (GthFileView *self,
+ gboolean value);
+gboolean gth_file_view_get_reorderable (GthFileView *self);
+
+G_END_DECLS
+
+#endif /* GTH_FILE_VIEW_H */
diff --git a/gthumb/gth-filter-editor-dialog.c b/gthumb/gth-filter-editor-dialog.c
new file mode 100644
index 0000000..55dd194
--- /dev/null
+++ b/gthumb/gth-filter-editor-dialog.c
@@ -0,0 +1,560 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2008-2009 The Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <stdlib.h>
+#include <gtk/gtk.h>
+#include "glib-utils.h"
+#include "gth-filter-editor-dialog.h"
+#include "gth-main.h"
+#include "gth-test-chain.h"
+#include "gth-test-selector.h"
+#include "gtk-utils.h"
+
+
+enum {
+ SELECTION_COLUMN_DATA,
+ SELECTION_COLUMN_NAME
+};
+
+
+#define GET_WIDGET(name) _gtk_builder_get_widget (self->priv->builder, (name))
+
+
+static gpointer parent_class = NULL;
+
+
+struct _GthFilterEditorDialogPrivate {
+ GtkBuilder *builder;
+ GtkWidget *match_type_combobox;
+ GtkWidget *limit_object_combobox;
+ GtkWidget *selection_combobox;
+ GtkWidget *selection_order_combobox;
+ char *filter_id;
+ gboolean filter_visible;
+};
+
+
+static void
+gth_filter_editor_dialog_finalize (GObject *object)
+{
+ GthFilterEditorDialog *dialog;
+
+ dialog = GTH_FILTER_EDITOR_DIALOG (object);
+
+ if (dialog->priv != NULL) {
+ g_object_unref (dialog->priv->builder);
+ g_free (dialog->priv->filter_id);
+ g_free (dialog->priv);
+ dialog->priv = NULL;
+ }
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+
+static void
+gth_filter_editor_dialog_class_init (GthFilterEditorDialogClass *class)
+{
+ GObjectClass *object_class;
+
+ parent_class = g_type_class_peek_parent (class);
+ object_class = (GObjectClass*) class;
+
+ object_class->finalize = gth_filter_editor_dialog_finalize;
+}
+
+
+static void
+gth_filter_editor_dialog_init (GthFilterEditorDialog *dialog)
+{
+ dialog->priv = g_new0 (GthFilterEditorDialogPrivate, 1);
+}
+
+
+GType
+gth_filter_editor_dialog_get_type (void)
+{
+ static GType type = 0;
+
+ if (! type) {
+ GTypeInfo type_info = {
+ sizeof (GthFilterEditorDialogClass),
+ NULL,
+ NULL,
+ (GClassInitFunc) gth_filter_editor_dialog_class_init,
+ NULL,
+ NULL,
+ sizeof (GthFilterEditorDialog),
+ 0,
+ (GInstanceInitFunc) gth_filter_editor_dialog_init
+ };
+
+ type = g_type_register_static (GTK_TYPE_DIALOG,
+ "GthFilterEditorDialog",
+ &type_info,
+ 0);
+ }
+
+ return type;
+}
+
+
+static void
+update_sensitivity (GthFilterEditorDialog *self)
+{
+ gboolean active;
+ GList *test_selectors;
+ int more_selectors;
+ GList *scan;
+
+ active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (GET_WIDGET ("limit_to_checkbutton")));
+ gtk_widget_set_sensitive (GET_WIDGET ("limit_options_hbox"), active);
+ gtk_widget_set_sensitive (GET_WIDGET ("selection_box"), active);
+
+ active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (GET_WIDGET ("match_checkbutton")));
+ gtk_widget_set_sensitive (GET_WIDGET ("match_type_combobox_box"), active);
+ gtk_widget_set_sensitive (GET_WIDGET ("tests_box"), active);
+
+ test_selectors = gtk_container_get_children (GTK_CONTAINER (GET_WIDGET ("tests_box")));
+ more_selectors = (test_selectors != NULL) && (test_selectors->next != NULL);
+ for (scan = test_selectors; scan; scan = scan->next)
+ gth_test_selector_can_remove (GTH_TEST_SELECTOR (scan->data), more_selectors);
+ g_list_free (test_selectors);
+}
+
+
+static void
+match_checkbutton_toggled_cb (GtkToggleButton *button,
+ gpointer user_data)
+{
+ GthFilterEditorDialog *self = user_data;
+ update_sensitivity (self);
+}
+
+
+static void
+limit_to_checkbutton_toggled_cb (GtkToggleButton *button,
+ gpointer user_data)
+{
+ GthFilterEditorDialog *self = user_data;
+ update_sensitivity (self);
+}
+
+
+static void
+gth_filter_editor_dialog_construct (GthFilterEditorDialog *self,
+ const char *title,
+ GtkWindow *parent)
+{
+ GtkWidget *content;
+ GList *scan;
+ GtkListStore *selection_model;
+ GtkCellRenderer *renderer;
+
+ if (title != NULL)
+ gtk_window_set_title (GTK_WINDOW (self), title);
+ if (parent != NULL)
+ gtk_window_set_transient_for (GTK_WINDOW (self), parent);
+ gtk_window_set_resizable (GTK_WINDOW (self), FALSE);
+ gtk_dialog_set_has_separator (GTK_DIALOG (self), FALSE);
+ gtk_box_set_spacing (GTK_BOX (GTK_DIALOG (self)->vbox), 5);
+ gtk_container_set_border_width (GTK_CONTAINER (self), 5);
+
+ gtk_dialog_add_button (GTK_DIALOG (self), GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL);
+ gtk_dialog_add_button (GTK_DIALOG (self), GTK_STOCK_SAVE, GTK_RESPONSE_OK);
+
+ self->priv->builder = _gtk_builder_new_from_file ("filter-editor.ui", NULL);
+
+ content = _gtk_builder_get_widget (self->priv->builder, "filter_editor");
+ gtk_container_set_border_width (GTK_CONTAINER (content), 5);
+ gtk_box_pack_start (GTK_BOX (GTK_DIALOG (self)->vbox), content, TRUE, TRUE, 0);
+
+ /**/
+
+ self->priv->match_type_combobox = gtk_combo_box_new_text ();
+ _gtk_combo_box_append_texts (GTK_COMBO_BOX (self->priv->match_type_combobox),
+ _("all the following rules"),
+ _("any of the following rules"),
+ NULL);
+ gtk_combo_box_set_active (GTK_COMBO_BOX (self->priv->match_type_combobox), 0);
+ gtk_widget_show (self->priv->match_type_combobox);
+ gtk_container_add (GTK_CONTAINER (GET_WIDGET ("match_type_combobox_box")),
+ self->priv->match_type_combobox);
+
+ /**/
+
+ self->priv->limit_object_combobox = gtk_combo_box_new_text ();
+ _gtk_combo_box_append_texts (GTK_COMBO_BOX (self->priv->limit_object_combobox),
+ _("files"),
+ _("kB"),
+ _("MB"),
+ _("GB"),
+ NULL);
+ gtk_combo_box_set_active (GTK_COMBO_BOX (self->priv->limit_object_combobox), 0);
+ gtk_widget_show (self->priv->limit_object_combobox);
+ gtk_container_add (GTK_CONTAINER (GET_WIDGET ("limit_object_combobox_box")),
+ self->priv->limit_object_combobox);
+
+ /**/
+
+ selection_model = gtk_list_store_new (2, G_TYPE_POINTER, G_TYPE_STRING);
+ self->priv->selection_combobox = gtk_combo_box_new_with_model (GTK_TREE_MODEL (selection_model));
+ g_object_unref (selection_model);
+
+ renderer = gtk_cell_renderer_text_new ();
+ gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (self->priv->selection_combobox),
+ renderer,
+ TRUE);
+ gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (self->priv->selection_combobox),
+ renderer,
+ "text", SELECTION_COLUMN_NAME,
+ NULL);
+
+ for (scan = gth_main_get_all_sort_types (); scan; scan = scan->next) {
+ GthFileDataSort *sort_type = scan->data;
+ GtkTreeIter iter;
+
+ gtk_list_store_append (selection_model, &iter);
+ gtk_list_store_set (selection_model, &iter,
+ SELECTION_COLUMN_DATA, sort_type,
+ SELECTION_COLUMN_NAME, sort_type->display_name,
+ -1);
+ }
+ gtk_combo_box_set_active (GTK_COMBO_BOX (self->priv->selection_combobox), 0);
+ gtk_widget_show (self->priv->selection_combobox);
+ gtk_container_add (GTK_CONTAINER (GET_WIDGET ("selection_combobox_box")),
+ self->priv->selection_combobox);
+
+ /**/
+
+ self->priv->selection_order_combobox = gtk_combo_box_new_text ();
+ _gtk_combo_box_append_texts (GTK_COMBO_BOX (self->priv->selection_order_combobox),
+ _("ascending"),
+ _("descending"),
+ NULL);
+ gtk_combo_box_set_active (GTK_COMBO_BOX (self->priv->selection_order_combobox), 0);
+ gtk_widget_show (self->priv->selection_order_combobox);
+ gtk_container_add (GTK_CONTAINER (GET_WIDGET ("selection_order_combobox_box")),
+ self->priv->selection_order_combobox);
+
+ /**/
+
+ g_signal_connect (GET_WIDGET ("match_checkbutton"),
+ "toggled",
+ G_CALLBACK (match_checkbutton_toggled_cb),
+ self);
+ g_signal_connect (GET_WIDGET ("limit_to_checkbutton"),
+ "toggled",
+ G_CALLBACK (limit_to_checkbutton_toggled_cb),
+ self);
+
+ gth_filter_editor_dialog_set_filter (self, NULL);
+}
+
+
+GtkWidget *
+gth_filter_editor_dialog_new (const char *title,
+ GtkWindow *parent)
+{
+ GthFilterEditorDialog *self;
+
+ self = g_object_new (GTH_TYPE_FILTER_EDITOR_DIALOG, NULL);
+ gth_filter_editor_dialog_construct (self, title, parent);
+
+ return (GtkWidget *) self;
+}
+
+
+static GtkWidget *
+_gth_filter_editor_dialog_add_test (GthFilterEditorDialog *self,
+ int pos);
+
+
+static void
+test_selector_add_test_cb (GthTestSelector *selector,
+ GthFilterEditorDialog *self)
+{
+ int pos;
+
+ pos = _gtk_container_get_pos (GTK_CONTAINER (GET_WIDGET ("tests_box")), (GtkWidget*) selector);
+ _gth_filter_editor_dialog_add_test (self, pos == -1 ? -1 : pos + 1);
+ update_sensitivity (self);
+}
+
+
+static void
+test_selector_remove_test_cb (GthTestSelector *selector,
+ GthFilterEditorDialog *self)
+{
+ gtk_container_remove (GTK_CONTAINER (GET_WIDGET ("tests_box")), (GtkWidget*) selector);
+ update_sensitivity (self);
+}
+
+
+static GtkWidget *
+_gth_filter_editor_dialog_add_test (GthFilterEditorDialog *self,
+ int pos)
+{
+ GtkWidget *test_selector;
+
+ test_selector = gth_test_selector_new ();
+ gtk_widget_show (test_selector);
+
+ g_signal_connect (G_OBJECT (test_selector),
+ "add_test",
+ G_CALLBACK (test_selector_add_test_cb),
+ self);
+ g_signal_connect (G_OBJECT (test_selector),
+ "remove_test",
+ G_CALLBACK (test_selector_remove_test_cb),
+ self);
+
+ gtk_box_pack_start (GTK_BOX (GET_WIDGET ("tests_box")), test_selector, FALSE, FALSE, 0);
+
+ if (pos >= 0)
+ gtk_box_reorder_child (GTK_BOX (GET_WIDGET ("tests_box")),
+ test_selector,
+ pos);
+
+ return test_selector;
+}
+
+
+static void
+_gth_filter_editor_dialog_set_new_filter (GthFilterEditorDialog *self)
+{
+ gtk_entry_set_text (GTK_ENTRY (GET_WIDGET ("name_entry")), "");
+
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (GET_WIDGET ("match_checkbutton")), FALSE);
+ gtk_combo_box_set_active (GTK_COMBO_BOX (self->priv->match_type_combobox), 0);
+ _gtk_container_remove_children (GTK_CONTAINER (GET_WIDGET ("tests_box")), NULL, NULL);
+
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (GET_WIDGET ("limit_to_checkbutton")), FALSE);
+ gtk_entry_set_text (GTK_ENTRY (GET_WIDGET ("limit_size_entry")), "");
+ gtk_combo_box_set_active (GTK_COMBO_BOX (self->priv->limit_object_combobox), 0);
+ gtk_combo_box_set_active (GTK_COMBO_BOX (self->priv->selection_combobox), 0);
+}
+
+
+gboolean
+get_iter_for_sort_type (GthFilterEditorDialog *self,
+ const char *sort_type_name,
+ GtkTreeIter *iter)
+{
+ GtkTreeModel *model;
+
+ model = gtk_combo_box_get_model (GTK_COMBO_BOX (self->priv->selection_combobox));
+
+ if (! gtk_tree_model_get_iter_first (model, iter))
+ return FALSE;
+ do {
+ GthFileDataSort *sort_type;
+
+ gtk_tree_model_get (model, iter, SELECTION_COLUMN_DATA, &sort_type, -1);
+ if (g_strcmp0 (sort_type->name, sort_type_name) == 0)
+ return TRUE;
+ }
+ while (gtk_tree_model_iter_next (model, iter));
+
+ return FALSE;
+}
+
+
+void
+gth_filter_editor_dialog_set_filter (GthFilterEditorDialog *self,
+ GthFilter *filter)
+{
+ GthTestChain *chain;
+ GthMatchType match_type;
+ GthLimitType limit_type;
+ goffset limit_value;
+ const char *sort_name;
+ GtkSortType sort_direction;
+
+ g_free (self->priv->filter_id);
+ self->priv->filter_id = NULL;
+ self->priv->filter_visible = TRUE;
+
+ _gth_filter_editor_dialog_set_new_filter (self);
+
+ if (filter == NULL) {
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (GET_WIDGET ("match_checkbutton")), TRUE);
+ _gth_filter_editor_dialog_add_test (self, -1);
+ update_sensitivity (self);
+ return;
+ }
+
+ self->priv->filter_id = g_strdup (gth_test_get_id (GTH_TEST (filter)));
+ self->priv->filter_visible = gth_test_is_visible (GTH_TEST (filter));
+
+ gtk_entry_set_text (GTK_ENTRY (GET_WIDGET ("name_entry")), gth_test_get_display_name (GTH_TEST (filter)));
+
+ chain = gth_filter_get_test (filter);
+ match_type = chain != NULL ? gth_test_chain_get_match_type (chain) : GTH_MATCH_TYPE_NONE;
+
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (GET_WIDGET ("match_checkbutton")), match_type != GTH_MATCH_TYPE_NONE);
+ gtk_combo_box_set_active (GTK_COMBO_BOX (self->priv->match_type_combobox), match_type == GTH_MATCH_TYPE_ANY ? 1 : 0);
+
+ _gtk_container_remove_children (GTK_CONTAINER (GET_WIDGET ("tests_box")), NULL, NULL);
+ if (match_type != GTH_MATCH_TYPE_NONE) {
+ GList *tests;
+ GList *scan;
+
+ tests = gth_test_chain_get_tests (chain);
+ for (scan = tests; scan; scan = scan->next) {
+ GthTest *test = scan->data;
+ GtkWidget *test_selector;
+
+ test_selector = _gth_filter_editor_dialog_add_test (self, -1);
+ gth_test_selector_set_test (GTH_TEST_SELECTOR (test_selector), test);
+ }
+ _g_object_list_unref (tests);
+ }
+ else
+ _gth_filter_editor_dialog_add_test (self, -1);
+
+ gth_filter_get_limit (filter, &limit_type, &limit_value, &sort_name, &sort_direction);
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (GET_WIDGET ("limit_to_checkbutton")), limit_type != GTH_LIMIT_TYPE_NONE);
+ if (limit_type != GTH_LIMIT_TYPE_NONE) {
+ char *value;
+ GtkTreeIter iter;
+
+ gtk_combo_box_set_active (GTK_COMBO_BOX (self->priv->limit_object_combobox), limit_type - 1);
+ if (limit_type == GTH_LIMIT_TYPE_SIZE) {
+ int sizes[] = { 1024, 1024 * 1024, 1024 * 1024 * 1024 };
+ int i;
+
+ for (i = 0; i < G_N_ELEMENTS (sizes); i++) {
+ if ((i == G_N_ELEMENTS (sizes) - 1) || (limit_value < sizes[i + 1])) {
+ value = g_strdup_printf ("%.2f", (double) limit_value / sizes[i]);
+ gtk_entry_set_text (GTK_ENTRY (GET_WIDGET ("limit_size_entry")), value);
+ g_free (value);
+
+ gtk_combo_box_set_active (GTK_COMBO_BOX (self->priv->limit_object_combobox), i + 1);
+ break;
+ }
+ }
+ }
+ else {
+ value = g_strdup_printf ("%lld", limit_value);
+ gtk_entry_set_text (GTK_ENTRY (GET_WIDGET ("limit_size_entry")), value);
+ g_free (value);
+
+ gtk_combo_box_set_active (GTK_COMBO_BOX (self->priv->limit_object_combobox), 0);
+ }
+ if (get_iter_for_sort_type (self, sort_name, &iter))
+ gtk_combo_box_set_active_iter (GTK_COMBO_BOX (self->priv->selection_combobox), &iter);
+ gtk_combo_box_set_active (GTK_COMBO_BOX (self->priv->selection_order_combobox), sort_direction);
+ }
+
+ update_sensitivity (self);
+}
+
+
+GthFilter *
+gth_filter_editor_dialog_get_filter (GthFilterEditorDialog *self,
+ GError **error)
+{
+ GthFilter *filter;
+
+ filter = gth_filter_new ();
+ if (self->priv->filter_id != NULL)
+ g_object_set (filter, "id", self->priv->filter_id, NULL);
+ g_object_set (filter,
+ "display-name", gtk_entry_get_text (GTK_ENTRY (GET_WIDGET ("name_entry"))),
+ "visible", self->priv->filter_visible,
+ NULL);
+
+ if (g_strcmp0 (gth_test_get_display_name (GTH_TEST (filter)), "") == 0) {
+ *error = g_error_new (GTH_TEST_ERROR, 0, _("No name specified"));
+ g_object_unref (filter);
+ return NULL;
+ }
+
+ if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (GET_WIDGET ("match_checkbutton")))) {
+ GthTest *chain;
+ GList *test_selectors;
+ GList *scan;
+
+ chain = gth_test_chain_new (gtk_combo_box_get_active (GTK_COMBO_BOX (self->priv->match_type_combobox)) + 1, NULL);
+
+ test_selectors = gtk_container_get_children (GTK_CONTAINER (GET_WIDGET ("tests_box")));
+ for (scan = test_selectors; scan; scan = scan->next) {
+ GthTestSelector *test_selector = GTH_TEST_SELECTOR (scan->data);
+ GthTest *test;
+
+ test = gth_test_selector_get_test (test_selector, error);
+ if (test == NULL) {
+ g_object_unref (filter);
+ return NULL;
+ }
+
+ gth_test_chain_add_test (GTH_TEST_CHAIN (chain), test);
+ g_object_unref (test);
+ }
+ g_list_free (test_selectors);
+
+ gth_filter_set_test (filter, GTH_TEST_CHAIN (chain));
+ }
+ else
+ gth_filter_set_test (filter, NULL);
+
+ if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (GET_WIDGET ("limit_to_checkbutton")))) {
+ int size;
+ int idx;
+ GthLimitType limit_type;
+ GtkTreeIter iter;
+ GthFileDataSort *sort_type;
+ GtkSortType sort_direction;
+
+ size = atol (gtk_entry_get_text (GTK_ENTRY (GET_WIDGET ("limit_size_entry"))));
+ idx = gtk_combo_box_get_active (GTK_COMBO_BOX (self->priv->limit_object_combobox));
+ if (idx == 0)
+ limit_type = GTH_LIMIT_TYPE_FILES;
+ else {
+ limit_type = GTH_LIMIT_TYPE_SIZE;
+ if (idx == 1)
+ size = size * 1024;
+ else if (idx == 2)
+ size = size * (1024 * 1024);
+ }
+
+ if (size == 0) {
+ *error = g_error_new (GTH_TEST_ERROR, 0, _("No limit specified"));
+ g_object_unref (filter);
+ return NULL;
+ }
+
+ gtk_combo_box_get_active_iter (GTK_COMBO_BOX (self->priv->selection_combobox), &iter);
+ gtk_tree_model_get (gtk_combo_box_get_model (GTK_COMBO_BOX (self->priv->selection_combobox)),
+ &iter,
+ SELECTION_COLUMN_DATA, &sort_type,
+ -1);
+
+ sort_direction = gtk_combo_box_get_active (GTK_COMBO_BOX (self->priv->selection_order_combobox));
+
+ gth_filter_set_limit (filter, limit_type, size, sort_type->name, sort_direction);
+ }
+
+ return filter;
+}
diff --git a/gthumb/gth-filter-editor-dialog.h b/gthumb/gth-filter-editor-dialog.h
new file mode 100644
index 0000000..31d6a7e
--- /dev/null
+++ b/gthumb/gth-filter-editor-dialog.h
@@ -0,0 +1,61 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2008 The Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GTH_FILTER_EDITOR_DIALOG_H
+#define GTH_FILTER_EDITOR_DIALOG_H
+
+#include <gtk/gtk.h>
+#include "gth-filter.h"
+
+G_BEGIN_DECLS
+
+#define GTH_TYPE_FILTER_EDITOR_DIALOG (gth_filter_editor_dialog_get_type ())
+#define GTH_FILTER_EDITOR_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTH_TYPE_FILTER_EDITOR_DIALOG, GthFilterEditorDialog))
+#define GTH_FILTER_EDITOR_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTH_TYPE_FILTER_EDITOR_DIALOG, GthFilterEditorDialogClass))
+#define GTH_IS_FILTER_EDITOR_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTH_TYPE_FILTER_EDITOR_DIALOG))
+#define GTH_IS_FILTER_EDITOR_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTH_TYPE_FILTER_EDITOR_DIALOG))
+#define GTH_FILTER_EDITOR_DIALOG_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTH_TYPE_FILTER_EDITOR_DIALOG, GthFilterEditorDialogClass))
+
+typedef struct _GthFilterEditorDialog GthFilterEditorDialog;
+typedef struct _GthFilterEditorDialogClass GthFilterEditorDialogClass;
+typedef struct _GthFilterEditorDialogPrivate GthFilterEditorDialogPrivate;
+
+struct _GthFilterEditorDialog {
+ GtkDialog parent_instance;
+ GthFilterEditorDialogPrivate *priv;
+};
+
+struct _GthFilterEditorDialogClass {
+ GtkDialogClass parent_class;
+};
+
+GType gth_filter_editor_dialog_get_type (void);
+GtkWidget * gth_filter_editor_dialog_new (const char *title,
+ GtkWindow *parent);
+void gth_filter_editor_dialog_set_filter (GthFilterEditorDialog *self,
+ GthFilter *filter);
+GthFilter * gth_filter_editor_dialog_get_filter (GthFilterEditorDialog *self,
+ GError **error);
+
+G_END_DECLS
+
+#endif /* GTH_FILTER_EDITOR_DIALOG_H */
diff --git a/gthumb/gth-filter-file.c b/gthumb/gth-filter-file.c
new file mode 100644
index 0000000..799dd33
--- /dev/null
+++ b/gthumb/gth-filter-file.c
@@ -0,0 +1,278 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2008 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <string.h>
+#include "dom.h"
+#include "glib-utils.h"
+#include "gth-duplicable.h"
+#include "gth-filter.h"
+#include "gth-filter-file.h"
+#include "gth-main.h"
+
+
+#define FILTER_FORMAT "1.0"
+
+
+struct _GthFilterFile
+{
+ GList *items;
+};
+
+
+GthFilterFile *
+gth_filter_file_new (void)
+{
+ GthFilterFile *filters;
+
+ filters = g_new0 (GthFilterFile, 1);
+ filters->items = NULL;
+
+ return filters;
+}
+
+
+void
+gth_filter_file_free (GthFilterFile *filters)
+{
+ _g_object_list_unref (filters->items);
+ g_free (filters);
+}
+
+
+gboolean
+gth_filter_file_load_from_data (GthFilterFile *filters,
+ const char *data,
+ gsize length,
+ GError **error)
+{
+ DomDocument *doc;
+ gboolean success;
+
+ doc = dom_document_new ();
+ success = dom_document_load (doc, data, length, error);
+ if (success) {
+ DomElement *filters_node;
+ DomElement *child;
+ GList *new_items = NULL;
+
+ filters_node = DOM_ELEMENT (doc)->first_child;
+ if ((filters_node != NULL) && (g_strcmp0 (filters_node->tag_name, "filters") == 0)) {
+ for (child = filters_node->first_child;
+ child != NULL;
+ child = child->next_sibling)
+ {
+ GthTest *test = NULL;
+
+ if (strcmp (child->tag_name, "filter") == 0) {
+ test = (GthTest*) gth_filter_new ();
+ dom_domizable_load_from_element (DOM_DOMIZABLE (test), child);
+ }
+ else if (strcmp (child->tag_name, "test") == 0) {
+ test = gth_main_get_test (dom_element_get_attribute (child, "id"));
+ if (test != NULL)
+ dom_domizable_load_from_element (DOM_DOMIZABLE (test), child);
+ }
+
+ if (test == NULL)
+ continue;
+
+ new_items = g_list_prepend (new_items, test);
+ }
+
+ new_items = g_list_reverse (new_items);
+ filters->items = g_list_concat (filters->items, new_items);
+ }
+ }
+ g_object_unref (doc);
+
+ return success;
+}
+
+
+gboolean
+gth_filter_file_load_from_file (GthFilterFile *filters,
+ const char *filename,
+ GError **error)
+{
+ char *buffer;
+ gsize len;
+ GError *read_error;
+ gboolean retval;
+
+ g_return_val_if_fail (filters != NULL, FALSE);
+ g_return_val_if_fail (filename != NULL, FALSE);
+
+ read_error = NULL;
+ g_file_get_contents (filename, &buffer, &len, &read_error);
+ if (read_error != NULL) {
+ g_propagate_error (error, read_error);
+ return FALSE;
+ }
+
+ read_error = NULL;
+ retval = gth_filter_file_load_from_data (filters,
+ buffer,
+ len,
+ &read_error);
+ if (read_error != NULL) {
+ g_propagate_error (error, read_error);
+ g_free (buffer);
+ return FALSE;
+ }
+
+ g_free (buffer);
+
+ return retval;
+}
+
+
+char *
+gth_filter_file_to_data (GthFilterFile *filters,
+ gsize *len,
+ GError **data_error)
+{
+ DomDocument *doc;
+ DomElement *root;
+ char *data;
+ GList *scan;
+
+ doc = dom_document_new ();
+ root = dom_document_create_element (doc, "filters",
+ "version", FILTER_FORMAT,
+ NULL);
+ dom_element_append_child (DOM_ELEMENT (doc), root);
+ for (scan = filters->items; scan; scan = scan->next)
+ dom_element_append_child (root, dom_domizable_create_element (DOM_DOMIZABLE (scan->data), doc));
+ data = dom_document_dump (doc, len);
+
+ g_object_unref (doc);
+
+ return data;
+}
+
+
+gboolean
+gth_filter_file_to_file (GthFilterFile *filters,
+ const char *filename,
+ GError **error)
+{
+ char *data;
+ GError *data_error, *write_error;
+ gsize len;
+ gboolean retval;
+
+ g_return_val_if_fail (filters != NULL, FALSE);
+ g_return_val_if_fail (filename != NULL, FALSE);
+
+ data_error = NULL;
+ data = gth_filter_file_to_data (filters, &len, &data_error);
+ if (data_error) {
+ g_propagate_error (error, data_error);
+ return FALSE;
+ }
+
+ write_error = NULL;
+ g_file_set_contents (filename, data, len, &write_error);
+ if (write_error) {
+ g_propagate_error (error, write_error);
+ retval = FALSE;
+ }
+ else
+ retval = TRUE;
+
+ g_free (data);
+
+ return retval;
+}
+
+
+GList *
+gth_filter_file_get_tests (GthFilterFile *filters)
+{
+ GList *list = NULL;
+ GList *scan;
+
+ for (scan = filters->items; scan; scan = scan->next)
+ list = g_list_prepend (list, gth_duplicable_duplicate (GTH_DUPLICABLE (scan->data)));
+
+ return g_list_reverse (list);
+}
+
+
+static int
+find_by_id (gconstpointer a,
+ gconstpointer b)
+{
+ GthTest *test = (GthTest *) a;
+ GthTest *test_to_find = (GthTest *) b;
+
+ return g_strcmp0 (gth_test_get_id (test), gth_test_get_id (test_to_find));
+}
+
+
+gboolean
+gth_filter_file_has_test (GthFilterFile *filters,
+ GthTest *test)
+{
+ return g_list_find_custom (filters->items, test, find_by_id) != NULL;
+}
+
+
+void
+gth_filter_file_add (GthFilterFile *filters,
+ GthTest *test)
+{
+ GList *link;
+
+ g_object_ref (test);
+
+ link = g_list_find_custom (filters->items, test, find_by_id);
+ if (link != NULL) {
+ g_object_unref (link->data);
+ link->data = test;
+ }
+ else
+ filters->items = g_list_append (filters->items, test);
+}
+
+
+void
+gth_filter_file_remove (GthFilterFile *filters,
+ GthTest *test)
+{
+ GList *link;
+
+ link = g_list_find_custom (filters->items, test, find_by_id);
+ if (link == NULL)
+ return;
+ filters->items = g_list_remove_link (filters->items, link);
+ _g_object_list_unref (link);
+}
+
+
+void
+gth_filter_file_clear (GthFilterFile *filters)
+{
+ _g_object_list_unref (filters->items);
+ filters->items = NULL;
+}
diff --git a/gthumb/gth-filter-file.h b/gthumb/gth-filter-file.h
new file mode 100644
index 0000000..4620ecc
--- /dev/null
+++ b/gthumb/gth-filter-file.h
@@ -0,0 +1,59 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2008 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GTH_FILTER_FILE_H
+#define GTH_FILTER_FILE_H
+
+#include <glib.h>
+#include "gth-test.h"
+
+G_BEGIN_DECLS
+
+typedef struct _GthFilterFile GthFilterFile;
+
+GthFilterFile * gth_filter_file_new (void);
+void gth_filter_file_free (GthFilterFile *filters);
+gboolean gth_filter_file_load_from_data (GthFilterFile *filters,
+ const char *data,
+ gsize length,
+ GError **error);
+gboolean gth_filter_file_load_from_file (GthFilterFile *bookmark,
+ const char *filename,
+ GError **error);
+char * gth_filter_file_to_data (GthFilterFile *filters,
+ gsize *len,
+ GError **data_error);
+gboolean gth_filter_file_to_file (GthFilterFile *filters,
+ const gchar *filename,
+ GError **error);
+GList * gth_filter_file_get_tests (GthFilterFile *filters);
+gboolean gth_filter_file_has_test (GthFilterFile *filters,
+ GthTest *test);
+void gth_filter_file_add (GthFilterFile *filters,
+ GthTest *test);
+void gth_filter_file_remove (GthFilterFile *filters,
+ GthTest *test);
+void gth_filter_file_clear (GthFilterFile *filters);
+
+G_END_DECLS
+
+#endif /* GTH_FILTER_FILE_H */
diff --git a/gthumb/gth-filter.c b/gthumb/gth-filter.c
new file mode 100644
index 0000000..8982484
--- /dev/null
+++ b/gthumb/gth-filter.c
@@ -0,0 +1,582 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2006-2008 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <stdlib.h>
+#include <string.h>
+#include <glib/gi18n.h>
+#include <glib.h>
+#include "dom.h"
+#include "gth-file-data.h"
+#include "glib-utils.h"
+#include "gth-duplicable.h"
+#include "gth-enum-types.h"
+#include "gth-filter.h"
+#include "gth-main.h"
+
+
+typedef struct {
+ char *name;
+ goffset size;
+} GthSizeData;
+
+
+static GthSizeData size_data[] = {
+ { N_("kB"), 1024 },
+ { N_("MB"), 1024*1024 },
+ { N_("GB"), 1024*1024*1024 }
+};
+
+
+struct _GthFilterPrivate {
+ GthTestChain *test;
+ GthLimitType limit_type;
+ goffset limit;
+ const char *sort_name;
+ GtkSortType sort_direction;
+ int current_images;
+ goffset current_size;
+ GtkWidget *limit_entry;
+ GtkWidget *size_combo_box;
+};
+
+
+static gpointer *parent_class = NULL;
+
+
+static DomElement*
+gth_filter_real_create_element (DomDomizable *base,
+ DomDocument *doc)
+{
+ GthFilter *self;
+ DomElement *element;
+
+ g_return_val_if_fail (DOM_IS_DOCUMENT (doc), NULL);
+
+ self = GTH_FILTER (base);
+ element = dom_document_create_element (doc, "filter",
+ "id", gth_test_get_id (GTH_TEST (self)),
+ "name", gth_test_get_display_name (GTH_TEST (self)),
+ NULL);
+
+ if (! gth_test_is_visible (GTH_TEST (self)))
+ dom_element_set_attribute (element, "display", "none");
+
+ if ((self->priv->test != NULL) && (gth_test_chain_get_match_type (self->priv->test) != GTH_MATCH_TYPE_NONE))
+ dom_element_append_child (element, dom_domizable_create_element (DOM_DOMIZABLE (self->priv->test), doc));
+
+ if (self->priv->limit_type != GTH_LIMIT_TYPE_NONE) {
+ DomElement *limit;
+ char *value;
+
+ value = g_strdup_printf ("%lld", self->priv->limit);
+ limit = dom_document_create_element (doc, "limit",
+ "value", value,
+ "type", _g_enum_type_get_value (GTH_TYPE_LIMIT_TYPE, self->priv->limit_type)->value_nick,
+ "selected_by", self->priv->sort_name,
+ "direction", (self->priv->sort_direction == GTK_SORT_ASCENDING ? "ascending" : "descending"),
+ NULL);
+ g_free (value);
+
+ dom_element_append_child (element, limit);
+ }
+
+ return element;
+}
+
+
+static void
+gth_filter_real_load_from_element (DomDomizable *base,
+ DomElement *element)
+{
+ GthFilter *self;
+ DomElement *node;
+
+ g_return_if_fail (DOM_IS_ELEMENT (element));
+
+ self = GTH_FILTER (base);
+ g_object_set (self,
+ "id", dom_element_get_attribute (element, "id"),
+ "display-name", dom_element_get_attribute (element, "name"),
+ "visible", (g_strcmp0 (dom_element_get_attribute (element, "display"), "none") != 0),
+ NULL);
+
+ gth_filter_set_test (self, NULL);
+ for (node = element->first_child; node; node = node->next_sibling) {
+ if (g_strcmp0 (node->tag_name, "tests") == 0) {
+ GthTest *test;
+
+ test = gth_test_chain_new (GTH_MATCH_TYPE_NONE, NULL);
+ dom_domizable_load_from_element (DOM_DOMIZABLE (test), node);
+ gth_filter_set_test (self, GTH_TEST_CHAIN (test));
+ }
+ else if (g_strcmp0 (node->tag_name, "limit") == 0) {
+ gth_filter_set_limit (self,
+ _g_enum_type_get_value_by_nick (GTH_TYPE_LIMIT_TYPE, dom_element_get_attribute (node, "type"))->value,
+ atol (dom_element_get_attribute (node, "value")),
+ dom_element_get_attribute (node, "selected_by"),
+ (g_strcmp0 (dom_element_get_attribute (node, "direction"), "ascending") == 0) ? GTK_SORT_ASCENDING : GTK_SORT_DESCENDING);
+ }
+ }
+}
+
+
+static int
+qsort_campare_func (gconstpointer a,
+ gconstpointer b,
+ gpointer user_data)
+{
+ GthFileDataSort *sort_type = user_data;
+ GthFileData *file_a = *((GthFileData **) a);
+ GthFileData *file_b = *((GthFileData **) b);
+
+ return sort_type->cmp_func (file_a, file_b);
+}
+
+
+static void
+gth_filter_set_file_list (GthTest *test,
+ GList *files)
+{
+ GthFilter *filter = GTH_FILTER (test);
+
+ GTH_TEST_CLASS (parent_class)->set_file_list (test, files);
+
+ if ((filter->priv->limit_type != GTH_LIMIT_TYPE_NONE)
+ && (filter->priv->sort_name != NULL)
+ && (strcmp (filter->priv->sort_name, "") != 0))
+ {
+ GthFileDataSort *sort_type;
+
+ sort_type = gth_main_get_sort_type (filter->priv->sort_name);
+ if (sort_type != NULL) {
+ g_qsort_with_data (test->files, test->n_files, (gsize) sizeof (GthFileData *), qsort_campare_func, sort_type);
+ if (filter->priv->sort_direction == GTK_SORT_DESCENDING) {
+ int i;
+
+ for (i = 0; i < test->n_files / 2; i++) {
+ GthFileData *tmp;
+
+ tmp = test->files[i];
+ test->files[i] = test->files[test->n_files - 1 - i];
+ test->files[test->n_files - 1 - i] = tmp;
+ }
+ }
+ }
+ }
+
+ filter->priv->current_images = 0;
+ filter->priv->current_size = 0;
+}
+
+
+static GthMatch
+gth_filter_match (GthTest *test,
+ GthFileData *file)
+{
+ GthFilter *filter = GTH_FILTER (test);
+ GthMatch match = GTH_MATCH_NO;
+
+ if (filter->priv->test != NULL)
+ match = gth_test_match (GTH_TEST (filter->priv->test), file);
+ else
+ match = GTH_MATCH_YES;
+
+ if (match == GTH_MATCH_YES) {
+ filter->priv->current_images++;
+ filter->priv->current_size += g_file_info_get_size (file->info);
+ }
+
+ switch (filter->priv->limit_type) {
+ case GTH_LIMIT_TYPE_NONE:
+ break;
+ case GTH_LIMIT_TYPE_FILES:
+ if (filter->priv->current_images > filter->priv->limit)
+ match = GTH_MATCH_LIMIT_REACHED;
+ break;
+ case GTH_LIMIT_TYPE_SIZE:
+ if (filter->priv->current_size > filter->priv->limit)
+ match = GTH_MATCH_LIMIT_REACHED;
+ break;
+ }
+
+ return match;
+}
+
+
+static void
+file_limit_entry_activate_cb (GtkEntry *entry,
+ GthFilter *filter)
+{
+ filter->priv->limit = atol (gtk_entry_get_text (GTK_ENTRY (filter->priv->limit_entry)));
+ gth_test_changed (GTH_TEST (filter));
+}
+
+
+static GtkWidget *
+create_control_for_files (GthFilter *filter)
+{
+ GtkWidget *control;
+ GtkWidget *limit_label;
+ GtkWidget *label;
+ char *value;
+
+ control = gtk_hbox_new (FALSE, 6);
+
+ /* limit label */
+
+ limit_label = gtk_label_new_with_mnemonic (_("_Limit to"));
+ gtk_widget_show (limit_label);
+
+ /* limit entry */
+
+ filter->priv->limit_entry = gtk_entry_new ();
+ gtk_entry_set_width_chars (GTK_ENTRY (filter->priv->limit_entry), 6);
+ value = g_strdup_printf ("%lld", filter->priv->limit);
+ gtk_entry_set_text (GTK_ENTRY (filter->priv->limit_entry), value);
+ g_free (value);
+ gtk_widget_show (filter->priv->limit_entry);
+
+ g_signal_connect (G_OBJECT (filter->priv->limit_entry),
+ "activate",
+ G_CALLBACK (file_limit_entry_activate_cb),
+ filter);
+
+ gtk_label_set_mnemonic_widget (GTK_LABEL (limit_label), filter->priv->limit_entry);
+
+ /* "files" label */
+
+ label = gtk_label_new (_("files"));
+ gtk_widget_show (label);
+
+ /**/
+
+ gtk_box_pack_start (GTK_BOX (control), limit_label, FALSE, FALSE, 0);
+ gtk_box_pack_start (GTK_BOX (control), filter->priv->limit_entry, FALSE, FALSE, 0);
+ gtk_box_pack_start (GTK_BOX (control), label, FALSE, FALSE, 0);
+
+ return control;
+}
+
+
+static void
+size_limit_entry_activate_cb (GtkEntry *entry,
+ GthFilter *filter)
+{
+ GthSizeData size;
+
+ size = size_data[gtk_combo_box_get_active (GTK_COMBO_BOX (filter->priv->size_combo_box))];
+ filter->priv->limit = size.size * atol (gtk_entry_get_text (GTK_ENTRY (filter->priv->limit_entry)));
+
+ gth_test_changed (GTH_TEST (filter));
+}
+
+
+static void
+size_combo_box_changed_cb (GtkComboBox *combo_box,
+ GthFilter *filter)
+{
+ GthSizeData size;
+
+ size = size_data[gtk_combo_box_get_active (GTK_COMBO_BOX (filter->priv->size_combo_box))];
+ filter->priv->limit = size.size * atol (gtk_entry_get_text (GTK_ENTRY (filter->priv->limit_entry)));
+
+ gth_test_changed (GTH_TEST (filter));
+}
+
+
+static GtkWidget *
+create_control_for_size (GthFilter *filter)
+{
+ GtkWidget *control;
+ GtkWidget *limit_label;
+ int i, size_idx;
+ gboolean size_set = FALSE;
+
+ control = gtk_hbox_new (FALSE, 6);
+
+ /* limit label */
+
+ limit_label = gtk_label_new_with_mnemonic (_("_Limit to"));
+ gtk_widget_show (limit_label);
+
+ /* limit entry */
+
+ filter->priv->limit_entry = gtk_entry_new ();
+ gtk_entry_set_width_chars (GTK_ENTRY (filter->priv->limit_entry), 6);
+ gtk_widget_show (filter->priv->limit_entry);
+
+ g_signal_connect (G_OBJECT (filter->priv->limit_entry),
+ "activate",
+ G_CALLBACK (size_limit_entry_activate_cb),
+ filter);
+
+ gtk_label_set_mnemonic_widget (GTK_LABEL (limit_label), filter->priv->limit_entry);
+
+ /* size combo box */
+
+ filter->priv->size_combo_box = gtk_combo_box_new_text ();
+ gtk_widget_show (filter->priv->size_combo_box);
+
+ size_idx = 0;
+ for (i = 0; i < G_N_ELEMENTS (size_data); i++) {
+ gtk_combo_box_append_text (GTK_COMBO_BOX (filter->priv->size_combo_box), _(size_data[i].name));
+ if (! size_set && ((i == G_N_ELEMENTS (size_data) - 1) || (filter->priv->limit < size_data[i + 1].size))) {
+ char *value;
+
+ size_idx = i;
+ value = g_strdup_printf ("%.2f", (double) filter->priv->limit / size_data[i].size);
+ gtk_entry_set_text (GTK_ENTRY (filter->priv->limit_entry), value);
+ g_free (value);
+ size_set = TRUE;
+ }
+ }
+ gtk_combo_box_set_active (GTK_COMBO_BOX (filter->priv->size_combo_box), size_idx);
+
+ g_signal_connect (G_OBJECT (filter->priv->size_combo_box),
+ "changed",
+ G_CALLBACK (size_combo_box_changed_cb),
+ filter);
+
+ /**/
+
+ gtk_box_pack_start (GTK_BOX (control), limit_label, FALSE, FALSE, 0);
+ gtk_box_pack_start (GTK_BOX (control), filter->priv->limit_entry, FALSE, FALSE, 0);
+ gtk_box_pack_start (GTK_BOX (control), filter->priv->size_combo_box, FALSE, FALSE, 0);
+
+ return control;
+}
+
+
+static GtkWidget *
+gth_filter_real_create_control (GthTest *test)
+{
+ GthFilter *filter = (GthFilter *) test;
+ GtkWidget *control = NULL;
+
+ switch (filter->priv->limit_type) {
+ case GTH_LIMIT_TYPE_NONE:
+ break;
+ case GTH_LIMIT_TYPE_FILES:
+ control = create_control_for_files (filter);
+ break;
+ case GTH_LIMIT_TYPE_SIZE:
+ control = create_control_for_size (filter);
+ break;
+ }
+
+ return control;
+}
+
+
+GObject *
+gth_filter_real_duplicate (GthDuplicable *duplicable)
+{
+ GthFilter *filter = GTH_FILTER (duplicable);
+ GthFilter *new_filter;
+
+ new_filter = gth_filter_new ();
+ g_object_set (new_filter,
+ "id", gth_test_get_id (GTH_TEST (filter)),
+ "display-name", gth_test_get_display_name (GTH_TEST (filter)),
+ "visible", gth_test_is_visible (GTH_TEST (filter)),
+ NULL);
+ if (filter->priv->test != NULL)
+ new_filter->priv->test = (GthTestChain*) gth_duplicable_duplicate (GTH_DUPLICABLE (filter->priv->test));
+ new_filter->priv->limit = filter->priv->limit;
+ new_filter->priv->limit_type = filter->priv->limit_type;
+ new_filter->priv->sort_name = filter->priv->sort_name;
+ new_filter->priv->sort_direction = filter->priv->sort_direction;
+
+ return (GObject *) new_filter;
+}
+
+
+static void
+gth_filter_finalize (GObject *object)
+{
+ GthFilter *filter;
+
+ filter = GTH_FILTER (object);
+
+ if (filter->priv != NULL) {
+ if (filter->priv->test != NULL)
+ g_object_unref (filter->priv->test);
+ g_free (filter->priv);
+ filter->priv = NULL;
+ }
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+
+static void
+gth_filter_class_init (GthFilterClass *class)
+{
+ GObjectClass *object_class;
+ GthTestClass *test_class;
+
+ parent_class = g_type_class_peek_parent (class);
+ object_class = G_OBJECT_CLASS (class);
+ test_class = GTH_TEST_CLASS (class);
+
+ object_class->finalize = gth_filter_finalize;
+ test_class->set_file_list = gth_filter_set_file_list;
+ test_class->match = gth_filter_match;
+ test_class->create_control = gth_filter_real_create_control;
+}
+
+
+static void
+gth_filter_dom_domizable_interface_init (DomDomizableIface *iface)
+{
+ iface->create_element = gth_filter_real_create_element;
+ iface->load_from_element = gth_filter_real_load_from_element;
+}
+
+
+static void
+gth_filter_gth_duplicable_interface_init (GthDuplicableIface *iface)
+{
+ iface->duplicate = gth_filter_real_duplicate;
+}
+
+
+static void
+gth_filter_init (GthFilter *filter)
+{
+ filter->priv = g_new0 (GthFilterPrivate, 1);
+ filter->priv->test = NULL;
+ filter->priv->limit_type = GTH_LIMIT_TYPE_NONE;
+ filter->priv->limit = 0;
+ filter->priv->sort_name = NULL;
+ filter->priv->sort_direction = GTK_SORT_ASCENDING;
+}
+
+
+GType
+gth_filter_get_type (void)
+{
+ static GType type = 0;
+
+ if (! type) {
+ GTypeInfo type_info = {
+ sizeof (GthFilterClass),
+ NULL,
+ NULL,
+ (GClassInitFunc) gth_filter_class_init,
+ NULL,
+ NULL,
+ sizeof (GthFilter),
+ 0,
+ (GInstanceInitFunc) gth_filter_init
+ };
+ static const GInterfaceInfo dom_domizable_info = {
+ (GInterfaceInitFunc) gth_filter_dom_domizable_interface_init,
+ (GInterfaceFinalizeFunc) NULL,
+ NULL
+ };
+ static const GInterfaceInfo gth_duplicable_info = {
+ (GInterfaceInitFunc) gth_filter_gth_duplicable_interface_init,
+ (GInterfaceFinalizeFunc) NULL,
+ NULL
+ };
+
+ type = g_type_register_static (GTH_TYPE_TEST,
+ "GthFilter",
+ &type_info,
+ 0);
+ g_type_add_interface_static (type, DOM_TYPE_DOMIZABLE, &dom_domizable_info);
+ g_type_add_interface_static (type, GTH_TYPE_DUPLICABLE, >h_duplicable_info);
+ }
+
+ return type;
+}
+
+
+GthFilter*
+gth_filter_new (void)
+{
+ GthFilter *filter;
+ char *id;
+
+ id = _g_rand_string (ID_LENGTH);
+ filter = (GthFilter *) g_object_new (GTH_TYPE_FILTER, "id", id, NULL);
+ g_free (id);
+
+ return filter;
+}
+
+
+void
+gth_filter_set_limit (GthFilter *filter,
+ GthLimitType type,
+ goffset value,
+ const char *sort_name,
+ GtkSortType sort_direction)
+{
+ filter->priv->limit_type = type;
+ filter->priv->limit = value;
+ filter->priv->sort_name = get_static_string (sort_name);
+ filter->priv->sort_direction = sort_direction;
+}
+
+
+void
+gth_filter_get_limit (GthFilter *filter,
+ GthLimitType *type,
+ goffset *value,
+ const char **sort_name,
+ GtkSortType *sort_direction)
+{
+ if (type != NULL)
+ *type = filter->priv->limit_type;
+ if (value != NULL)
+ *value = filter->priv->limit;
+ if (sort_name != NULL)
+ *sort_name = filter->priv->sort_name;
+ if (sort_direction != NULL)
+ *sort_direction = filter->priv->sort_direction;
+}
+
+
+void
+gth_filter_set_test (GthFilter *filter,
+ GthTestChain *test)
+{
+ if (filter->priv->test != NULL) {
+ g_object_unref (filter->priv->test);
+ filter->priv->test = NULL;
+ }
+ if (test != NULL)
+ filter->priv->test = g_object_ref (test);
+}
+
+
+GthTestChain *
+gth_filter_get_test (GthFilter *filter)
+{
+ if (filter->priv->test != NULL)
+ return g_object_ref (filter->priv->test);
+ else
+ return NULL;
+}
diff --git a/gthumb/gth-filter.h b/gthumb/gth-filter.h
new file mode 100644
index 0000000..7400a24
--- /dev/null
+++ b/gthumb/gth-filter.h
@@ -0,0 +1,78 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2006-2008 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GTH_FILTER_H
+#define GTH_FILTER_H
+
+#include <glib-object.h>
+#include "gth-file-data.h"
+#include "gth-test-chain.h"
+
+G_BEGIN_DECLS
+
+#define GTH_TYPE_FILTER (gth_filter_get_type ())
+#define GTH_FILTER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GTH_TYPE_FILTER, GthFilter))
+#define GTH_FILTER_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), GTH_TYPE_FILTER, GthFilterClass))
+#define GTH_IS_FILTER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GTH_TYPE_FILTER))
+#define GTH_IS_FILTER_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GTH_TYPE_FILTER))
+#define GTH_FILTER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS((o), GTH_TYPE_FILTER, GthFilterClass))
+
+typedef struct _GthFilter GthFilter;
+typedef struct _GthFilterPrivate GthFilterPrivate;
+typedef struct _GthFilterClass GthFilterClass;
+
+typedef enum {
+ GTH_LIMIT_TYPE_NONE = 0,
+ GTH_LIMIT_TYPE_FILES,
+ GTH_LIMIT_TYPE_SIZE
+} GthLimitType;
+
+struct _GthFilter
+{
+ GthTest __parent;
+ GthFilterPrivate *priv;
+};
+
+struct _GthFilterClass
+{
+ GthTestClass __parent_class;
+};
+
+GType gth_filter_get_type (void) G_GNUC_CONST;
+GthFilter * gth_filter_new (void);
+void gth_filter_set_limit (GthFilter *filter,
+ GthLimitType type,
+ goffset value,
+ const char *sort_name,
+ GtkSortType sort_direction);
+void gth_filter_get_limit (GthFilter *filter,
+ GthLimitType *type,
+ goffset *value,
+ const char **sort_name,
+ GtkSortType *sort_direction);
+void gth_filter_set_test (GthFilter *filter,
+ GthTestChain *test);
+GthTestChain * gth_filter_get_test (GthFilter *filter);
+
+G_END_DECLS
+
+#endif /* GTH_FILTER_H */
diff --git a/gthumb/gth-filterbar.c b/gthumb/gth-filterbar.c
new file mode 100644
index 0000000..064decd
--- /dev/null
+++ b/gthumb/gth-filterbar.c
@@ -0,0 +1,480 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2006-2008 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <string.h>
+#include <stdlib.h>
+#include <time.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include "glib-utils.h"
+#include "gth-filterbar.h"
+#include "gth-main.h"
+
+enum {
+ ITEM_TYPE_NONE,
+ ITEM_TYPE_SEPARATOR,
+ ITEM_TYPE_FILTER,
+ ITEM_TYPE_PERSONALIZE
+};
+
+enum {
+ ICON_COLUMN,
+ NAME_COLUMN,
+ TYPE_COLUMN,
+ FILTER_COLUMN,
+ N_COLUMNS
+};
+
+enum {
+ CHANGED,
+ PERSONALIZE,
+ CLOSE_BUTTON_CLICKED,
+ LAST_SIGNAL
+};
+
+struct _GthFilterbarPrivate
+{
+ GtkListStore *model;
+ GtkWidget *test_combo_box;
+ GthTest *test;
+ GtkWidget *control_box;
+ GtkWidget *control;
+ GtkTreeIter current_iter;
+ gulong filters_changed_id;
+};
+
+
+static GtkHBoxClass *parent_class = NULL;
+static guint gth_filterbar_signals[LAST_SIGNAL] = { 0 };
+
+
+static void
+gth_filterbar_finalize (GObject *object)
+{
+ GthFilterbar *filterbar;
+
+ filterbar = GTH_FILTERBAR (object);
+
+ if (filterbar->priv != NULL) {
+ g_signal_handler_disconnect (gth_main_get_default_monitor (), filterbar->priv->filters_changed_id);
+ if (filterbar->priv->test != NULL)
+ g_object_unref (filterbar->priv->test);
+ g_free (filterbar->priv);
+ filterbar->priv = NULL;
+ }
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+
+static void
+gth_filterbar_class_init (GthFilterbarClass *class)
+{
+ GObjectClass *object_class;
+
+ parent_class = g_type_class_peek_parent (class);
+
+ object_class = (GObjectClass*) class;
+ object_class->finalize = gth_filterbar_finalize;
+
+ gth_filterbar_signals[CHANGED] =
+ g_signal_new ("changed",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GthFilterbarClass, changed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+ gth_filterbar_signals[PERSONALIZE] =
+ g_signal_new ("personalize",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GthFilterbarClass, personalize),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+ gth_filterbar_signals[CLOSE_BUTTON_CLICKED] =
+ g_signal_new ("close_button_clicked",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GthFilterbarClass, close_button_clicked),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+}
+
+
+static void
+gth_filterbar_init (GthFilterbar *filterbar)
+{
+ filterbar->priv = g_new0 (GthFilterbarPrivate, 1);
+}
+
+
+static void
+gth_filterbar_changed (GthFilterbar *filterbar)
+{
+ g_signal_emit (filterbar, gth_filterbar_signals[CHANGED], 0);
+}
+
+
+static void
+close_button_clicked_cb (GtkWidget *button,
+ GthFilterbar *filterbar)
+{
+ g_signal_emit (filterbar, gth_filterbar_signals[CLOSE_BUTTON_CLICKED], 0);
+}
+
+
+static void
+_gth_filterbar_set_test_control (GthFilterbar *filterbar,
+ GtkWidget *control)
+{
+ if (filterbar->priv->control != NULL) {
+ gtk_container_remove (GTK_CONTAINER (filterbar->priv->control_box),
+ filterbar->priv->control);
+ filterbar->priv->control = NULL;
+ }
+
+ gth_filterbar_changed (filterbar);
+
+ if (control == NULL)
+ return;
+
+ filterbar->priv->control = control;
+ gtk_widget_show (control);
+ gtk_container_add (GTK_CONTAINER (filterbar->priv->control_box),
+ filterbar->priv->control);
+}
+
+
+static void
+_gth_filterbar_set_test (GthFilterbar *filterbar,
+ GthTest *test)
+{
+ if (filterbar->priv->test != NULL) {
+ g_object_unref (filterbar->priv->test);
+ filterbar->priv->test = NULL;
+ }
+
+ if (test != NULL) {
+ filterbar->priv->test = g_object_ref (test);
+ _gth_filterbar_set_test_control (filterbar, gth_test_create_control (filterbar->priv->test));
+ }
+ else
+ _gth_filterbar_set_test_control (filterbar, NULL);
+}
+
+
+static void
+test_changed_cb (GthTest *test,
+ GthFilterbar *filterbar)
+{
+ gth_filterbar_changed (filterbar);
+}
+
+
+static void
+test_combo_box_changed_cb (GtkComboBox *scope_combo_box,
+ GthFilterbar *filterbar)
+{
+ GtkTreeIter iter;
+ int item_type = ITEM_TYPE_NONE;
+ GthTest *test;
+
+ if (! gtk_combo_box_get_active_iter (scope_combo_box, &iter))
+ return;
+
+ gtk_tree_model_get (GTK_TREE_MODEL (filterbar->priv->model),
+ &iter,
+ TYPE_COLUMN, &item_type,
+ FILTER_COLUMN, &test,
+ -1);
+
+ if (test != NULL)
+ g_signal_connect (test,
+ "changed",
+ G_CALLBACK (test_changed_cb),
+ filterbar);
+
+ switch (item_type) {
+ case ITEM_TYPE_FILTER:
+ _gth_filterbar_set_test (filterbar, test);
+ filterbar->priv->current_iter = iter;
+ break;
+ case ITEM_TYPE_PERSONALIZE:
+ g_signal_emit (filterbar, gth_filterbar_signals[PERSONALIZE], 0);
+ g_signal_handlers_block_by_func (filterbar->priv->test_combo_box, test_combo_box_changed_cb, filterbar);
+ gtk_combo_box_set_active_iter (GTK_COMBO_BOX (filterbar->priv->test_combo_box), &filterbar->priv->current_iter);
+ g_signal_handlers_unblock_by_func (filterbar->priv->test_combo_box, test_combo_box_changed_cb, filterbar);
+ break;
+ default:
+ break;
+ }
+
+ if (test != NULL)
+ g_object_unref (test);
+}
+
+
+static gboolean
+test_combo_box_row_separator_func (GtkTreeModel *model,
+ GtkTreeIter *iter,
+ gpointer data)
+{
+ int item_type = ITEM_TYPE_NONE;
+
+ gtk_tree_model_get (model, iter, TYPE_COLUMN, &item_type, -1);
+
+ return (item_type == ITEM_TYPE_SEPARATOR);
+}
+
+
+static void
+update_filter_list (GthFilterbar *filterbar,
+ const char *current_filter)
+{
+ gboolean no_filter_selected = TRUE;
+ GList *filters, *scan;
+ GtkTreeIter iter;
+
+ gtk_list_store_clear (filterbar->priv->model);
+
+ gtk_list_store_append (filterbar->priv->model, &iter);
+ gtk_list_store_set (filterbar->priv->model, &iter,
+ TYPE_COLUMN, ITEM_TYPE_FILTER,
+ FILTER_COLUMN, NULL,
+ NAME_COLUMN, _("All"),
+ -1);
+
+ filters = gth_main_get_all_filters ();
+ for (scan = filters; scan; scan = scan->next) {
+ GthTest *test = scan->data;
+
+ if (! gth_test_is_visible (test))
+ continue;
+
+ gtk_list_store_append (filterbar->priv->model, &iter);
+ gtk_list_store_set (filterbar->priv->model, &iter,
+ TYPE_COLUMN, ITEM_TYPE_FILTER,
+ FILTER_COLUMN, test,
+ NAME_COLUMN, gth_test_get_display_name (test),
+ -1);
+
+ if (g_strcmp0 (current_filter, gth_test_get_id (test)) == 0) {
+ gtk_combo_box_set_active_iter (GTK_COMBO_BOX (filterbar->priv->test_combo_box), &iter);
+ filterbar->priv->current_iter = iter;
+ _gth_filterbar_set_test (GTH_FILTERBAR (filterbar), test);
+ no_filter_selected = FALSE;
+ }
+ }
+ _g_object_list_unref (filters);
+
+ gtk_list_store_append (filterbar->priv->model, &iter);
+ gtk_list_store_set (filterbar->priv->model, &iter,
+ TYPE_COLUMN, ITEM_TYPE_SEPARATOR,
+ -1);
+
+ gtk_list_store_append (filterbar->priv->model, &iter);
+ gtk_list_store_set (filterbar->priv->model, &iter,
+ TYPE_COLUMN, ITEM_TYPE_PERSONALIZE,
+ NAME_COLUMN, _("Personalize..."),
+ -1);
+
+ if (no_filter_selected) {
+ GtkTreeIter iter;
+
+ gtk_tree_model_get_iter_first (GTK_TREE_MODEL (filterbar->priv->model), &iter);
+ gtk_combo_box_set_active_iter (GTK_COMBO_BOX (filterbar->priv->test_combo_box), &iter);
+ filterbar->priv->current_iter = iter;
+ }
+}
+
+
+static void
+filters_changed_cb (GthMonitor *monitor,
+ GthFilterbar *filterbar)
+{
+ GthTest *current_filter;
+
+ gtk_tree_model_get (GTK_TREE_MODEL (filterbar->priv->model),
+ &filterbar->priv->current_iter,
+ FILTER_COLUMN, ¤t_filter,
+ -1);
+
+ update_filter_list (filterbar, current_filter != NULL ? gth_test_get_id (current_filter) : NULL);
+
+ if (current_filter != NULL)
+ g_object_unref (current_filter);
+}
+
+
+static void
+gth_filterbar_construct (GthFilterbar *filterbar,
+ const char *selected_filter)
+{
+ GtkCellRenderer *renderer;
+ GtkWidget *label;
+ GtkWidget *button;
+ GtkWidget *image;
+
+ GTK_BOX (filterbar)->spacing = 6;
+ gtk_container_set_border_width (GTK_CONTAINER (filterbar), 2);
+
+ /* filter combo box */
+
+ filterbar->priv->model = gtk_list_store_new (N_COLUMNS,
+ GDK_TYPE_PIXBUF,
+ G_TYPE_STRING,
+ G_TYPE_INT,
+ G_TYPE_OBJECT);
+ filterbar->priv->test_combo_box = gtk_combo_box_new_with_model (GTK_TREE_MODEL (filterbar->priv->model));
+ g_object_unref (filterbar->priv->model);
+ gtk_combo_box_set_row_separator_func (GTK_COMBO_BOX (filterbar->priv->test_combo_box),
+ test_combo_box_row_separator_func,
+ filterbar,
+ NULL);
+
+ /* icon renderer */
+
+ renderer = gtk_cell_renderer_pixbuf_new ();
+ gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (filterbar->priv->test_combo_box),
+ renderer,
+ FALSE);
+ gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (filterbar->priv->test_combo_box),
+ renderer,
+ "pixbuf", ICON_COLUMN,
+ NULL);
+
+ /* name renderer */
+
+ renderer = gtk_cell_renderer_text_new ();
+ gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (filterbar->priv->test_combo_box),
+ renderer,
+ TRUE);
+ gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (filterbar->priv->test_combo_box),
+ renderer,
+ "text", NAME_COLUMN,
+ NULL);
+
+ /**/
+
+ update_filter_list (filterbar, selected_filter);
+
+ g_signal_connect (G_OBJECT (filterbar->priv->test_combo_box),
+ "changed",
+ G_CALLBACK (test_combo_box_changed_cb),
+ filterbar);
+
+ gtk_widget_show (filterbar->priv->test_combo_box);
+
+ /* test control box */
+
+ filterbar->priv->control_box = gtk_hbox_new (FALSE, 0);
+ gtk_widget_show (filterbar->priv->control_box);
+
+ /* close button */
+
+ button = gtk_button_new ();
+ image = gtk_image_new_from_stock (GTK_STOCK_CLOSE, GTK_ICON_SIZE_MENU);
+ gtk_container_add (GTK_CONTAINER (button), image);
+ gtk_widget_show_all (button);
+ gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
+ gtk_widget_set_tooltip_text (button, _("Close"));
+ g_signal_connect (G_OBJECT (button),
+ "clicked",
+ G_CALLBACK (close_button_clicked_cb),
+ filterbar);
+
+ /* view label */
+
+ label = gtk_label_new_with_mnemonic (_("S_how:"));
+ gtk_label_set_mnemonic_widget (GTK_LABEL (label), filterbar->priv->test_combo_box);
+ gtk_widget_show (label);
+
+ /**/
+
+ filterbar->priv->filters_changed_id =
+ g_signal_connect (gth_main_get_default_monitor (),
+ "filters-changed",
+ G_CALLBACK (filters_changed_cb),
+ filterbar);
+
+ gtk_box_pack_start (GTK_BOX (filterbar), label, FALSE, FALSE, 0);
+ gtk_box_pack_start (GTK_BOX (filterbar), filterbar->priv->test_combo_box, FALSE, FALSE, 0);
+ gtk_box_pack_start (GTK_BOX (filterbar), filterbar->priv->control_box, FALSE, FALSE, 0);
+ gtk_box_pack_end (GTK_BOX (filterbar), button, FALSE, FALSE, 0);
+}
+
+
+GType
+gth_filterbar_get_type (void)
+{
+ static GType type = 0;
+
+ if (! type) {
+ GTypeInfo type_info = {
+ sizeof (GthFilterbarClass),
+ NULL,
+ NULL,
+ (GClassInitFunc) gth_filterbar_class_init,
+ NULL,
+ NULL,
+ sizeof (GthFilterbar),
+ 0,
+ (GInstanceInitFunc) gth_filterbar_init
+ };
+
+ type = g_type_register_static (GTK_TYPE_HBOX,
+ "GthFilterbar",
+ &type_info,
+ 0);
+ }
+
+ return type;
+}
+
+
+GtkWidget*
+gth_filterbar_new (const char *selected_filter)
+{
+ GtkWidget *widget;
+
+ widget = GTK_WIDGET (g_object_new (GTH_TYPE_FILTERBAR, NULL));
+ gth_filterbar_construct (GTH_FILTERBAR (widget), selected_filter);
+
+ return widget;
+}
+
+
+GthTest *
+gth_filterbar_get_test (GthFilterbar *filterbar)
+{
+ if (filterbar->priv->test != NULL)
+ return g_object_ref (filterbar->priv->test);
+ else
+ return NULL;
+}
diff --git a/gthumb/gth-filterbar.h b/gthumb/gth-filterbar.h
new file mode 100644
index 0000000..410a33e
--- /dev/null
+++ b/gthumb/gth-filterbar.h
@@ -0,0 +1,65 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2006-2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GTH_FILTERBAR_H
+#define GTH_FILTERBAR_H
+
+#include <gtk/gtkhbox.h>
+#include "gth-test.h"
+
+G_BEGIN_DECLS
+
+#define GTH_TYPE_FILTERBAR (gth_filterbar_get_type ())
+#define GTH_FILTERBAR(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GTH_TYPE_FILTERBAR, GthFilterbar))
+#define GTH_FILTERBAR_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), GTH_TYPE_FILTERBAR, GthFilterbarClass))
+#define GTH_IS_FILTERBAR(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GTH_TYPE_FILTERBAR))
+#define GTH_IS_FILTERBAR_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GTH_TYPE_FILTERBAR))
+#define GTH_FILTERBAR_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS((o), GTH_TYPE_FILTERBAR, GthFilterbarClass))
+
+typedef struct _GthFilterbar GthFilterbar;
+typedef struct _GthFilterbarPrivate GthFilterbarPrivate;
+typedef struct _GthFilterbarClass GthFilterbarClass;
+
+struct _GthFilterbar
+{
+ GtkHBox __parent;
+ GthFilterbarPrivate *priv;
+};
+
+struct _GthFilterbarClass
+{
+ GtkHBoxClass __parent_class;
+
+ /* -- Signals -- */
+
+ void (* changed) (GthFilterbar *filterbar);
+ void (* personalize) (GthFilterbar *filterbar);
+ void (* close_button_clicked) (GthFilterbar *filterbar);
+};
+
+GType gth_filterbar_get_type (void) G_GNUC_CONST;
+GtkWidget * gth_filterbar_new (const char *selected_filter);
+GthTest * gth_filterbar_get_test (GthFilterbar *filterbar);
+
+G_END_DECLS
+
+#endif /* GTH_FILTERBAR_H */
diff --git a/gthumb/gth-folder-tree.c b/gthumb/gth-folder-tree.c
new file mode 100644
index 0000000..93393c5
--- /dev/null
+++ b/gthumb/gth-folder-tree.c
@@ -0,0 +1,1524 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2008 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <string.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include "gth-file-data.h"
+#include "glib-utils.h"
+#include "gtk-utils.h"
+#include "gth-file-source.h"
+#include "gth-folder-tree.h"
+#include "gth-icon-cache.h"
+#include "gth-main.h"
+#include "gth-marshal.h"
+
+
+#define EMPTY_URI "..."
+#define LOADING_URI "."
+#define PARENT_URI ".."
+
+
+typedef enum {
+ ENTRY_TYPE_FILE,
+ ENTRY_TYPE_PARENT,
+ ENTRY_TYPE_LOADING,
+ ENTRY_TYPE_EMPTY
+} EntryType;
+
+
+enum {
+ COLUMN_STYLE,
+ COLUMN_WEIGHT,
+ COLUMN_ICON,
+ COLUMN_TYPE,
+ COLUMN_FILE,
+ COLUMN_SORT_KEY,
+ COLUMN_SORT_ORDER,
+ COLUMN_NAME,
+ COLUMN_NO_CHILD,
+ COLUMN_LOADED,
+ NUM_COLUMNS
+};
+
+enum {
+ FOLDER_POPUP,
+ LIST_CHILDREN,
+ LOAD,
+ OPEN,
+ OPEN_PARENT,
+ RENAME,
+ LAST_SIGNAL
+};
+
+
+struct _GthFolderTreePrivate
+{
+ GFile *root;
+ GtkTreeStore *tree_store;
+ GthIconCache *icon_cache;
+ GtkCellRenderer *text_renderer;
+ GtkTreePath *hover_path;
+ GtkTreePath *click_path;
+};
+
+
+static gpointer parent_class = NULL;
+static guint gth_folder_tree_signals[LAST_SIGNAL] = { 0 };
+
+
+static void
+gth_folder_tree_finalize (GObject *object)
+{
+ GthFolderTree *folder_tree;
+
+ folder_tree = GTH_FOLDER_TREE (object);
+
+ if (folder_tree->priv != NULL) {
+ if (folder_tree->priv->root != NULL)
+ g_object_unref (folder_tree->priv->root);
+ gth_icon_cache_free (folder_tree->priv->icon_cache);
+ g_free (folder_tree->priv);
+ folder_tree->priv = NULL;
+ }
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+
+static void
+gth_folder_tree_class_init (GthFolderTreeClass *class)
+{
+ GObjectClass *object_class;
+
+ parent_class = g_type_class_peek_parent (class);
+ object_class = (GObjectClass*) class;
+
+ object_class->finalize = gth_folder_tree_finalize;
+
+ gth_folder_tree_signals[FOLDER_POPUP] =
+ g_signal_new ("folder_popup",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GthFolderTreeClass, folder_popup),
+ NULL, NULL,
+ gth_marshal_VOID__OBJECT_UINT,
+ G_TYPE_NONE,
+ 2,
+ G_TYPE_OBJECT,
+ G_TYPE_UINT);
+ gth_folder_tree_signals[LIST_CHILDREN] =
+ g_signal_new ("list_children",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GthFolderTreeClass, list_children),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE,
+ 1,
+ G_TYPE_OBJECT);
+ gth_folder_tree_signals[LOAD] =
+ g_signal_new ("load",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GthFolderTreeClass, load),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE,
+ 1,
+ G_TYPE_OBJECT);
+ gth_folder_tree_signals[OPEN] =
+ g_signal_new ("open",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GthFolderTreeClass, open),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE,
+ 1,
+ G_TYPE_OBJECT);
+ gth_folder_tree_signals[OPEN_PARENT] =
+ g_signal_new ("open_parent",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GthFolderTreeClass, open_parent),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+ gth_folder_tree_signals[RENAME] =
+ g_signal_new ("rename",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GthFolderTreeClass, rename),
+ NULL, NULL,
+ gth_marshal_VOID__OBJECT_STRING,
+ G_TYPE_NONE,
+ 2,
+ G_TYPE_OBJECT,
+ G_TYPE_STRING);
+}
+
+
+static void
+text_renderer_edited_cb (GtkCellRendererText *renderer,
+ char *path,
+ char *new_text,
+ gpointer user_data)
+{
+ GthFolderTree *folder_tree = user_data;
+ GtkTreePath *tree_path;
+ GtkTreeIter iter;
+ EntryType entry_type;
+ GFile *file;
+ char *name;
+
+ tree_path = gtk_tree_path_new_from_string (path);
+ if (! gtk_tree_model_get_iter (GTK_TREE_MODEL (folder_tree->priv->tree_store),
+ &iter,
+ tree_path))
+ {
+ gtk_tree_path_free (tree_path);
+ return;
+ }
+ gtk_tree_path_free (tree_path);
+
+ gtk_tree_model_get (GTK_TREE_MODEL (folder_tree->priv->tree_store),
+ &iter,
+ COLUMN_TYPE, &entry_type,
+ COLUMN_FILE, &file,
+ COLUMN_NAME, &name,
+ -1);
+
+ if ((entry_type == ENTRY_TYPE_FILE) && (g_utf8_collate (name, new_text) != 0))
+ g_signal_emit (folder_tree, gth_folder_tree_signals[RENAME], 0, file, new_text);
+
+ if (file != NULL)
+ g_object_unref (file);
+ g_free (name);
+}
+
+
+static void
+add_columns (GthFolderTree *folder_tree,
+ GtkTreeView *treeview)
+{
+ GtkCellRenderer *renderer;
+ GtkTreeViewColumn *column;
+
+ column = gtk_tree_view_column_new ();
+
+ renderer = gtk_cell_renderer_pixbuf_new ();
+ gtk_tree_view_column_pack_start (column, renderer, FALSE);
+ gtk_tree_view_column_set_attributes (column, renderer,
+ "pixbuf", COLUMN_ICON,
+ NULL);
+
+ folder_tree->priv->text_renderer = renderer = gtk_cell_renderer_text_new ();
+ g_object_set (G_OBJECT (renderer),
+ "ellipsize", PANGO_ELLIPSIZE_END,
+ "editable", TRUE,
+ NULL);
+ g_signal_connect (folder_tree->priv->text_renderer,
+ "edited",
+ G_CALLBACK (text_renderer_edited_cb),
+ folder_tree);
+
+ gtk_tree_view_column_pack_start (column, renderer, TRUE);
+ gtk_tree_view_column_set_attributes (column, renderer,
+ "text", COLUMN_NAME,
+ "style", COLUMN_STYLE,
+ "weight", COLUMN_WEIGHT,
+ NULL);
+ gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
+ gtk_tree_view_column_set_sort_column_id (column, COLUMN_NAME);
+
+ gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column);
+}
+
+
+static void
+open_uri (GthFolderTree *folder_tree,
+ GFile *file,
+ EntryType entry_type)
+{
+ if (entry_type == ENTRY_TYPE_PARENT)
+ g_signal_emit (folder_tree, gth_folder_tree_signals[OPEN_PARENT], 0);
+ else if (entry_type == ENTRY_TYPE_FILE)
+ g_signal_emit (folder_tree, gth_folder_tree_signals[OPEN], 0, file);
+}
+
+
+static gboolean
+row_activated_cb (GtkTreeView *tree_view,
+ GtkTreePath *path,
+ GtkTreeViewColumn *column,
+ gpointer user_data)
+{
+ GthFolderTree *folder_tree = user_data;
+ GtkTreeIter iter;
+ EntryType entry_type;
+ GFile *file;
+
+ if (! gtk_tree_model_get_iter (GTK_TREE_MODEL (folder_tree->priv->tree_store),
+ &iter,
+ path))
+ {
+ return FALSE;
+ }
+
+ gtk_tree_model_get (GTK_TREE_MODEL (folder_tree->priv->tree_store),
+ &iter,
+ COLUMN_TYPE, &entry_type,
+ COLUMN_FILE, &file,
+ -1);
+ open_uri (folder_tree, file, entry_type);
+
+ if (file != NULL)
+ g_object_unref (file);
+
+ return TRUE;
+}
+
+
+static gboolean
+row_expanded_cb (GtkTreeView *tree_view,
+ GtkTreeIter *expanded_iter,
+ GtkTreePath *expanded_path,
+ gpointer user_data)
+{
+ GthFolderTree *folder_tree = user_data;
+ EntryType entry_type;
+ GFile *file;
+ gboolean loaded;
+
+ if ((folder_tree->priv->click_path == NULL)
+ || gtk_tree_path_compare (folder_tree->priv->click_path, expanded_path) != 0)
+ {
+ return FALSE;
+ }
+
+ gtk_tree_model_get (GTK_TREE_MODEL (folder_tree->priv->tree_store),
+ expanded_iter,
+ COLUMN_TYPE, &entry_type,
+ COLUMN_FILE, &file,
+ COLUMN_LOADED, &loaded,
+ -1);
+
+ if ((entry_type == ENTRY_TYPE_FILE) && ! loaded)
+ g_signal_emit (folder_tree, gth_folder_tree_signals[LIST_CHILDREN], 0, file);
+
+ gtk_tree_path_free (folder_tree->priv->click_path);
+ folder_tree->priv->click_path = NULL;
+
+ if (file != NULL)
+ g_object_unref (file);
+
+ return FALSE;
+}
+
+
+static int
+button_press_cb (GtkWidget *widget,
+ GdkEventButton *event,
+ gpointer data)
+{
+ GthFolderTree *folder_tree = data;
+ GtkTreeStore *tree_store = folder_tree->priv->tree_store;
+ GtkTreePath *path;
+ GtkTreeIter iter;
+ gboolean retval;
+ GtkTreeViewColumn *column;
+ int cell_x;
+ int cell_y;
+
+ retval = FALSE;
+
+ gtk_widget_grab_focus (widget);
+
+ if (folder_tree->priv->click_path != NULL) {
+ gtk_tree_path_free (folder_tree->priv->click_path);
+ folder_tree->priv->click_path = NULL;
+ }
+
+ if ((event->state & GDK_SHIFT_MASK) || (event->state & GDK_CONTROL_MASK))
+ return retval;
+
+ if ((event->button != 1) && (event->button != 3))
+ return retval;
+
+ if (! gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (folder_tree),
+ event->x, event->y,
+ &path,
+ &column,
+ &cell_x,
+ &cell_y))
+ {
+ if (event->button == 3) {
+ GtkTreeSelection *selection;
+
+ /* Update the selection. */
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (folder_tree));
+ gtk_tree_selection_unselect_all (selection);
+
+ g_signal_emit (folder_tree,
+ gth_folder_tree_signals[FOLDER_POPUP],
+ 0,
+ NULL,
+ event->time);
+ retval = TRUE;
+ }
+
+ return retval;
+ }
+
+ if (! gtk_tree_model_get_iter (GTK_TREE_MODEL (tree_store),
+ &iter,
+ path))
+ {
+ gtk_tree_path_free (path);
+ return retval;
+ }
+
+ if (event->button == 3) {
+ EntryType entry_type;
+ GFile *file;
+
+ gtk_tree_model_get (GTK_TREE_MODEL (tree_store),
+ &iter,
+ COLUMN_TYPE, &entry_type,
+ COLUMN_FILE, &file,
+ -1);
+
+ if (entry_type == ENTRY_TYPE_FILE) {
+ GtkTreeSelection *selection;
+
+ /* Update the selection. */
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (folder_tree));
+ if (! gtk_tree_selection_iter_is_selected (selection, &iter)) {
+ gtk_tree_selection_unselect_all (selection);
+ gtk_tree_selection_select_iter (selection, &iter);
+ }
+
+ /* Show the folder popup menu. */
+
+ g_signal_emit (folder_tree,
+ gth_folder_tree_signals[FOLDER_POPUP],
+ 0,
+ file,
+ event->time);
+ retval = TRUE;
+ }
+
+ if (file != NULL)
+ g_object_unref (file);
+ }
+ else if ((event->button == 1) && (event->type == GDK_BUTTON_PRESS)) {
+ GtkTreeSelection *selection;
+ int start_pos;
+ int width;
+ GValue value = { 0, };
+ int expander_size;
+ int horizontal_separator;
+
+ if (! gtk_tree_view_column_cell_get_position (column,
+ folder_tree->priv->text_renderer,
+ &start_pos,
+ &width))
+ {
+ start_pos = 0;
+ width = 0;
+ }
+
+ g_value_init (&value, G_TYPE_INT);
+ gtk_style_get_style_property (gtk_widget_get_style (GTK_WIDGET (folder_tree)),
+ GTK_TYPE_TREE_VIEW,
+ "expander-size",
+ &value);
+ expander_size = g_value_get_int (&value);
+
+ gtk_style_get_style_property (gtk_widget_get_style (GTK_WIDGET (folder_tree)),
+ GTK_TYPE_TREE_VIEW,
+ "horizontal-separator",
+ &value);
+ horizontal_separator = g_value_get_int (&value);
+
+ start_pos += (gtk_tree_path_get_depth (path) - 1) * (expander_size + (horizontal_separator * 2));
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (folder_tree));
+ if ((cell_x > start_pos) && gtk_tree_selection_iter_is_selected (selection, &iter))
+ retval = TRUE;
+
+ folder_tree->priv->click_path = gtk_tree_path_copy (path);
+ }
+ else if ((event->button == 1) && (event->type == GDK_2BUTTON_PRESS)) {
+ gtk_tree_view_row_activated (GTK_TREE_VIEW (folder_tree), path, NULL);
+ retval = TRUE;
+ }
+
+ gtk_tree_path_free (path);
+
+ return retval;
+}
+
+
+static void
+load_uri (GthFolderTree *folder_tree,
+ EntryType entry_type,
+ GFile *file)
+{
+ if (entry_type == ENTRY_TYPE_FILE)
+ g_signal_emit (folder_tree, gth_folder_tree_signals[LOAD], 0, file);
+}
+
+
+static int
+button_release_cb (GtkWidget *widget,
+ GdkEventButton *event,
+ gpointer data)
+{
+ GthFolderTree *folder_tree = data;
+ GtkTreeStore *tree_store = folder_tree->priv->tree_store;
+ GtkTreePath *path;
+ GtkTreeIter iter;
+ GtkTreeSelection *selection;
+
+ if ((event->state & GDK_SHIFT_MASK) || (event->state & GDK_CONTROL_MASK))
+ return FALSE;
+
+return FALSE;
+
+ if (event->button != 1)
+ return FALSE;
+
+ if (! gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (folder_tree),
+ event->x, event->y,
+ &path, NULL, NULL, NULL))
+ {
+ return FALSE;
+ }
+
+ if (! gtk_tree_model_get_iter (GTK_TREE_MODEL (tree_store),
+ &iter,
+ path))
+ {
+ gtk_tree_path_free (path);
+ return FALSE;
+ }
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (folder_tree));
+ if (gtk_tree_selection_iter_is_selected (selection, &iter)) {
+ EntryType entry_type;
+ GFile *file;
+
+ gtk_tree_model_get (GTK_TREE_MODEL (tree_store),
+ &iter,
+ COLUMN_TYPE, &entry_type,
+ COLUMN_FILE, &file,
+ -1);
+
+ load_uri (folder_tree, entry_type, file);
+
+ if (file != NULL)
+ g_object_unref (file);
+ }
+
+ gtk_tree_path_free (path);
+
+ return FALSE;
+}
+
+
+static gboolean
+selection_changed_cb (GtkTreeSelection *selection,
+ gpointer user_data)
+{
+ GthFolderTree *folder_tree = user_data;
+ GtkTreeIter iter;
+ GtkTreePath *selected_path;
+ EntryType entry_type;
+ GFile *file;
+
+ if (! gtk_tree_selection_get_selected (selection, NULL, &iter))
+ return FALSE;
+
+ selected_path = gtk_tree_model_get_path (GTK_TREE_MODEL (folder_tree->priv->tree_store), &iter);
+
+ if ((folder_tree->priv->click_path == NULL)
+ || gtk_tree_path_compare (folder_tree->priv->click_path, selected_path) != 0)
+ {
+ gtk_tree_path_free (selected_path);
+ return FALSE;
+ }
+
+ /* FIXME
+ gtk_tree_path_free (folder_tree->priv->click_path);
+ folder_tree->priv->click_path = NULL;
+ */
+
+ /*if (! gtk_tree_view_row_expanded (GTK_TREE_VIEW (folder_tree), selected_path))
+ gtk_tree_view_expand_row (GTK_TREE_VIEW (folder_tree), selected_path, FALSE);*/
+
+ gtk_tree_model_get (GTK_TREE_MODEL (folder_tree->priv->tree_store),
+ &iter,
+ COLUMN_TYPE, &entry_type,
+ COLUMN_FILE, &file,
+ -1);
+
+ load_uri (folder_tree, entry_type, file);
+
+ if (file != NULL)
+ g_object_unref (file);
+ gtk_tree_path_free (selected_path);
+
+ return FALSE;
+}
+
+
+static gint
+column_name_compare_func (GtkTreeModel *model,
+ GtkTreeIter *a,
+ GtkTreeIter *b,
+ gpointer user_data)
+{
+ char *key_a;
+ char *key_b;
+ int order_a;
+ int order_b;
+ PangoStyle style_a;
+ PangoStyle style_b;
+ gboolean result;
+
+ gtk_tree_model_get (model, a,
+ COLUMN_SORT_KEY, &key_a,
+ COLUMN_SORT_ORDER, &order_a,
+ COLUMN_STYLE, &style_a,
+ -1);
+ gtk_tree_model_get (model, b,
+ COLUMN_SORT_KEY, &key_b,
+ COLUMN_SORT_ORDER, &order_b,
+ COLUMN_STYLE, &style_b,
+ -1);
+
+ if (order_a == order_b) {
+ if (style_a == style_b)
+ result = strcmp (key_a, key_b);
+ else if (style_a == PANGO_STYLE_ITALIC)
+ result = -1;
+ else
+ result = 1;
+ }
+ else if (order_a < order_b)
+ result = -1;
+ else
+ result = 1;
+
+ g_free (key_a);
+ g_free (key_b);
+
+ return result;
+}
+
+
+static gboolean
+gth_folder_tree_get_iter (GthFolderTree *folder_tree,
+ GFile *file,
+ GtkTreeIter *file_iter,
+ GtkTreeIter *root)
+{
+ GtkTreeModel *tree_model = GTK_TREE_MODEL (folder_tree->priv->tree_store);
+ GtkTreeIter iter;
+
+ if (file == NULL)
+ return FALSE;
+
+ if (root != NULL) {
+ GFile *root_file;
+ gboolean found;
+
+ gtk_tree_model_get (tree_model, root, COLUMN_FILE, &root_file, -1);
+
+ found = (root_file != NULL) && g_file_equal (file, root_file);
+
+ if (root_file != NULL)
+ g_object_unref (root_file);
+
+ if (found) {
+ *file_iter = *root;
+ return TRUE;
+ }
+ }
+
+ if (! gtk_tree_model_iter_children (tree_model, &iter, root))
+ return FALSE;
+
+ do {
+ if (gth_folder_tree_get_iter (folder_tree, file, file_iter, &iter))
+ return TRUE;
+ }
+ while (gtk_tree_model_iter_next (tree_model, &iter));
+
+ return FALSE;
+}
+
+
+static gboolean
+_gth_folder_tree_child_type_present (GthFolderTree *folder_tree,
+ GtkTreeIter *parent,
+ EntryType entry_type)
+{
+ GtkTreeIter iter;
+
+ if (! gtk_tree_model_iter_children (GTK_TREE_MODEL (folder_tree->priv->tree_store), &iter, parent))
+ return FALSE;
+
+ do {
+ EntryType file_entry_type;
+
+ gtk_tree_model_get (GTK_TREE_MODEL (folder_tree->priv->tree_store), &iter,
+ COLUMN_TYPE, &file_entry_type,
+ -1);
+
+ if (entry_type == file_entry_type)
+ return TRUE;
+ }
+ while (gtk_tree_model_iter_next (GTK_TREE_MODEL (folder_tree->priv->tree_store), &iter));
+
+ return FALSE;
+}
+
+
+static void
+_gth_folder_tree_add_loading_item (GthFolderTree *folder_tree,
+ GtkTreeIter *parent)
+{
+ char *sort_key;
+ GtkTreeIter iter;
+
+ if (_gth_folder_tree_child_type_present (folder_tree, parent, ENTRY_TYPE_LOADING))
+ return;
+
+ sort_key = g_utf8_collate_key_for_filename (LOADING_URI, -1);
+
+ gtk_tree_store_append (folder_tree->priv->tree_store, &iter, parent);
+ gtk_tree_store_set (folder_tree->priv->tree_store, &iter,
+ COLUMN_STYLE, PANGO_STYLE_ITALIC,
+ COLUMN_TYPE, ENTRY_TYPE_LOADING,
+ COLUMN_NAME, _("Loading..."),
+ COLUMN_SORT_KEY, sort_key,
+ COLUMN_SORT_ORDER, 0,
+ -1);
+
+ g_free (sort_key);
+}
+
+
+static void
+_gth_folder_tree_add_empty_item (GthFolderTree *folder_tree,
+ GtkTreeIter *parent)
+{
+ char *sort_key;
+ GtkTreeIter iter;
+
+ if (_gth_folder_tree_child_type_present (folder_tree, parent, ENTRY_TYPE_EMPTY))
+ return;
+
+ sort_key = g_utf8_collate_key_for_filename (EMPTY_URI, -1);
+
+ gtk_tree_store_append (folder_tree->priv->tree_store, &iter, parent);
+ gtk_tree_store_set (folder_tree->priv->tree_store, &iter,
+ COLUMN_STYLE, PANGO_STYLE_ITALIC,
+ COLUMN_TYPE, ENTRY_TYPE_EMPTY,
+ COLUMN_NAME, _("(Empty)"),
+ COLUMN_SORT_KEY, sort_key,
+ COLUMN_SORT_ORDER, 0,
+ -1);
+
+ g_free (sort_key);
+}
+
+
+static void
+_gth_folder_tree_set_file_data (GthFolderTree *folder_tree,
+ GtkTreeIter *iter,
+ GthFileData *file_data)
+{
+ GIcon *icon;
+ GdkPixbuf *pixbuf;
+ const char *name;
+ char *sort_key;
+
+ icon = g_file_info_get_icon (file_data->info);
+ pixbuf = gth_icon_cache_get_pixbuf (folder_tree->priv->icon_cache, icon);
+ name = g_file_info_get_display_name (file_data->info);
+ sort_key = g_utf8_collate_key_for_filename (name, -1);
+
+ gtk_tree_store_set (folder_tree->priv->tree_store, iter,
+ COLUMN_STYLE, PANGO_STYLE_NORMAL,
+ COLUMN_ICON, pixbuf,
+ COLUMN_TYPE, ENTRY_TYPE_FILE,
+ COLUMN_FILE, file_data->file,
+ COLUMN_NAME, name,
+ COLUMN_SORT_KEY, sort_key,
+ COLUMN_SORT_ORDER, g_file_info_get_sort_order (file_data->info),
+ COLUMN_NO_CHILD, g_file_info_get_attribute_boolean (file_data->info, "gthumb::no-child"),
+ COLUMN_LOADED, FALSE,
+ -1);
+
+ g_free (sort_key);
+ _g_object_unref (pixbuf);
+}
+
+
+static gboolean
+_gth_folder_tree_iter_has_no_child (GthFolderTree *folder_tree,
+ GtkTreeIter *iter)
+{
+ gboolean no_child;
+
+ if (iter == NULL)
+ return FALSE;
+
+ gtk_tree_model_get (GTK_TREE_MODEL (folder_tree->priv->tree_store), iter,
+ COLUMN_NO_CHILD, &no_child,
+ -1);
+
+ return no_child;
+}
+
+
+static gboolean
+_gth_folder_tree_add_file (GthFolderTree *folder_tree,
+ GtkTreeIter *parent,
+ GthFileData *fd)
+{
+ GtkTreeIter iter;
+
+ if (g_file_info_get_file_type (fd->info) != G_FILE_TYPE_DIRECTORY)
+ return FALSE;
+
+ /* add the folder */
+
+ gtk_tree_store_append (folder_tree->priv->tree_store, &iter, parent);
+ _gth_folder_tree_set_file_data (folder_tree, &iter, fd);
+
+ if (g_file_info_get_attribute_boolean (fd->info, "gthumb::entry-point"))
+ gtk_tree_store_set (folder_tree->priv->tree_store, &iter,
+ COLUMN_WEIGHT, PANGO_WEIGHT_BOLD,
+ -1);
+
+ if (! _gth_folder_tree_iter_has_no_child (folder_tree, &iter))
+ _gth_folder_tree_add_loading_item (folder_tree, &iter);
+
+ return TRUE;
+}
+
+
+static void
+gth_folder_tree_construct (GthFolderTree *folder_tree)
+{
+ GtkTreeSelection *selection;
+
+ folder_tree->priv->icon_cache = gth_icon_cache_new (gtk_icon_theme_get_for_screen (gtk_widget_get_screen (GTK_WIDGET (folder_tree))),
+ _gtk_icon_get_pixel_size (GTK_WIDGET (folder_tree), GTK_ICON_SIZE_MENU));
+
+ folder_tree->priv->tree_store = gtk_tree_store_new (NUM_COLUMNS,
+ PANGO_TYPE_STYLE,
+ PANGO_TYPE_WEIGHT,
+ GDK_TYPE_PIXBUF,
+ G_TYPE_INT,
+ G_TYPE_OBJECT,
+ G_TYPE_STRING,
+ G_TYPE_INT,
+ G_TYPE_STRING,
+ G_TYPE_BOOLEAN,
+ G_TYPE_BOOLEAN);
+ gtk_tree_view_set_model (GTK_TREE_VIEW (folder_tree), GTK_TREE_MODEL (folder_tree->priv->tree_store));
+ gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (folder_tree), FALSE);
+
+ add_columns (folder_tree, GTK_TREE_VIEW (folder_tree));
+
+ gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (folder_tree), FALSE);
+ gtk_tree_view_set_enable_search (GTK_TREE_VIEW (folder_tree), TRUE);
+ gtk_tree_view_set_search_column (GTK_TREE_VIEW (folder_tree), COLUMN_NAME);
+ gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (folder_tree->priv->tree_store), COLUMN_NAME, column_name_compare_func, folder_tree, NULL);
+ gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (folder_tree->priv->tree_store), COLUMN_NAME, GTK_SORT_ASCENDING);
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (folder_tree));
+ gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE);
+ g_signal_connect (selection,
+ "changed",
+ G_CALLBACK (selection_changed_cb),
+ folder_tree);
+
+ /**/
+
+ g_signal_connect (G_OBJECT (folder_tree),
+ "button_press_event",
+ G_CALLBACK (button_press_cb),
+ folder_tree);
+ g_signal_connect (G_OBJECT (folder_tree),
+ "button_release_event",
+ G_CALLBACK (button_release_cb),
+ folder_tree);
+ g_signal_connect (G_OBJECT (folder_tree),
+ "row-activated",
+ G_CALLBACK (row_activated_cb),
+ folder_tree);
+ g_signal_connect (G_OBJECT (folder_tree),
+ "row-expanded",
+ G_CALLBACK (row_expanded_cb),
+ folder_tree);
+}
+
+
+static void
+gth_folder_tree_init (GthFolderTree *folder_tree)
+{
+ folder_tree->priv = g_new0 (GthFolderTreePrivate, 1);
+ gth_folder_tree_construct (folder_tree);
+}
+
+
+GType
+gth_folder_tree_get_type (void)
+{
+ static GType type = 0;
+
+ if (! type) {
+ GTypeInfo type_info = {
+ sizeof (GthFolderTreeClass),
+ NULL,
+ NULL,
+ (GClassInitFunc) gth_folder_tree_class_init,
+ NULL,
+ NULL,
+ sizeof (GthFolderTree),
+ 0,
+ (GInstanceInitFunc) gth_folder_tree_init
+ };
+
+ type = g_type_register_static (GTK_TYPE_TREE_VIEW,
+ "GthFolderTree",
+ &type_info,
+ 0);
+ }
+
+ return type;
+}
+
+
+GtkWidget *
+gth_folder_tree_new (const char *uri)
+{
+ GthFolderTree *folder_tree;
+
+ folder_tree = g_object_new (GTH_TYPE_FOLDER_TREE, NULL);
+ if (uri != NULL)
+ folder_tree->priv->root = g_file_new_for_uri (uri);
+
+ return (GtkWidget *) folder_tree;
+}
+
+
+void
+gth_folder_tree_set_list (GthFolderTree *folder_tree,
+ GFile *root,
+ GList *files,
+ gboolean open_parent)
+{
+ gtk_tree_store_clear (folder_tree->priv->tree_store);
+
+ if (folder_tree->priv->root != NULL) {
+ g_object_unref (folder_tree->priv->root);
+ folder_tree->priv->root = NULL;
+ }
+ if (root != NULL)
+ folder_tree->priv->root = g_file_dup (root);
+
+ /* add the parent folder item */
+
+ if (open_parent) {
+ char *sort_key;
+ GdkPixbuf *pixbuf;
+ GtkTreeIter iter;
+
+ sort_key = g_utf8_collate_key_for_filename (PARENT_URI, -1);
+ pixbuf = gtk_widget_render_icon (GTK_WIDGET (folder_tree), GTK_STOCK_GO_UP, GTK_ICON_SIZE_MENU, "folder-list");
+
+ gtk_tree_store_append (folder_tree->priv->tree_store, &iter, NULL);
+ gtk_tree_store_set (folder_tree->priv->tree_store, &iter,
+ COLUMN_STYLE, PANGO_STYLE_ITALIC,
+ COLUMN_ICON, pixbuf,
+ COLUMN_TYPE, ENTRY_TYPE_PARENT,
+ COLUMN_NAME, _("(Open Parent)"),
+ COLUMN_SORT_KEY, sort_key,
+ COLUMN_SORT_ORDER, 0,
+ -1);
+
+ g_object_unref (pixbuf);
+ g_free (sort_key);
+ }
+
+ /* add the folder list */
+
+ gth_folder_tree_set_children (folder_tree, root, files);
+}
+
+
+/* After changing the children list, the node expander is not hilighted
+ * anymore, this prevents the user to close the expander without moving the
+ * mouse pointer. The problem can be fixed emitting a fake motion notify
+ * event, this way the expander gets hilighted again and a click on the
+ * expander will correctly collapse the node. */
+static void
+emit_fake_motion_notify_event (GthFolderTree *folder_tree)
+{
+ GdkEventMotion event;
+ int x, y;
+
+ gtk_widget_get_pointer (GTK_WIDGET (folder_tree), &x, &y);
+
+ event.type = GDK_MOTION_NOTIFY;
+ event.window = gtk_tree_view_get_bin_window (GTK_TREE_VIEW (folder_tree));
+ event.send_event = TRUE;
+ event.time = GDK_CURRENT_TIME;
+ event.x = x;
+ event.y = y;
+ event.axes = NULL;
+ event.state = 0;
+ event.is_hint = FALSE;
+ event.device = NULL;
+
+ GTK_WIDGET_GET_CLASS (folder_tree)->motion_notify_event ((GtkWidget*) folder_tree, &event);
+}
+
+
+static GList *
+_gth_folder_tree_get_children (GthFolderTree *folder_tree,
+ GtkTreeIter *parent)
+{
+ GtkTreeModel *tree_model = GTK_TREE_MODEL (folder_tree->priv->tree_store);
+ GtkTreeIter iter;
+ GList *list;
+
+ if (! gtk_tree_model_iter_children (tree_model, &iter, parent))
+ return NULL;
+
+ list = NULL;
+ do {
+ GFile *file;
+
+ gtk_tree_model_get (tree_model, &iter, COLUMN_FILE, &file, -1);
+ if (file != NULL)
+ list = g_list_prepend (list, file);
+ }
+ while (gtk_tree_model_iter_next (tree_model, &iter));
+
+ return g_list_reverse (list);
+}
+
+
+static void
+_gth_folder_tree_remove_child_type (GthFolderTree *folder_tree,
+ GtkTreeIter *parent,
+ EntryType entry_type)
+{
+ GtkTreeIter iter;
+
+ if (! gtk_tree_model_iter_children (GTK_TREE_MODEL (folder_tree->priv->tree_store), &iter, parent))
+ return;
+
+ do {
+ EntryType file_entry_type;
+
+ gtk_tree_model_get (GTK_TREE_MODEL (folder_tree->priv->tree_store), &iter,
+ COLUMN_TYPE, &file_entry_type,
+ -1);
+
+ if (entry_type == file_entry_type) {
+ gtk_tree_store_remove (folder_tree->priv->tree_store, &iter);
+ break;
+ }
+ }
+ while (gtk_tree_model_iter_next (GTK_TREE_MODEL (folder_tree->priv->tree_store), &iter));
+}
+
+
+void
+gth_folder_tree_set_children (GthFolderTree *folder_tree,
+ GFile *parent,
+ GList *files)
+{
+ GtkTreeIter parent_iter;
+ GtkTreeIter *p_parent_iter;
+ gboolean is_empty;
+ GList *old_files;
+ GList *scan;
+ GtkTreeIter iter;
+
+ if (g_file_equal (parent, folder_tree->priv->root))
+ p_parent_iter = NULL;
+ else if (gth_folder_tree_get_iter (folder_tree, parent, &parent_iter, NULL))
+ p_parent_iter = &parent_iter;
+ else
+ return;
+
+ if (_gth_folder_tree_iter_has_no_child (folder_tree, p_parent_iter))
+ return;
+
+ is_empty = TRUE;
+ _gth_folder_tree_add_empty_item (folder_tree, p_parent_iter);
+ _gth_folder_tree_remove_child_type (folder_tree, p_parent_iter, ENTRY_TYPE_LOADING);
+
+ /* delete the children not present in the new file list */
+
+ old_files = _gth_folder_tree_get_children (folder_tree, p_parent_iter);
+ for (scan = old_files; scan; scan = scan->next) {
+ GFile *file = scan->data;
+
+ if (! gth_file_data_list_find_file (files, file)
+ && gth_folder_tree_get_iter (folder_tree, file, &iter, p_parent_iter))
+ {
+ gtk_tree_store_remove (folder_tree->priv->tree_store, &iter);
+ }
+ }
+ _g_object_list_unref (old_files);
+
+ /* add or update the new files */
+
+ for (scan = files; scan; scan = scan->next) {
+ GthFileData *file_data = scan->data;
+
+ if (gth_folder_tree_get_iter (folder_tree, file_data->file, &iter, p_parent_iter)) {
+ _gth_folder_tree_set_file_data (folder_tree, &iter, file_data);
+ is_empty = FALSE;
+ }
+ else if (_gth_folder_tree_add_file (folder_tree, p_parent_iter, file_data))
+ is_empty = FALSE;
+ }
+
+ if (! is_empty)
+ _gth_folder_tree_remove_child_type (folder_tree, p_parent_iter, ENTRY_TYPE_EMPTY);
+
+ if (p_parent_iter != NULL)
+ gtk_tree_store_set (folder_tree->priv->tree_store, p_parent_iter,
+ COLUMN_LOADED, TRUE,
+ -1);
+
+ emit_fake_motion_notify_event (folder_tree);
+}
+
+
+void
+gth_folder_tree_loading_children (GthFolderTree *folder_tree,
+ GFile *parent)
+{
+ GtkTreeIter parent_iter;
+ GtkTreeIter *p_parent_iter;
+ GtkTreeIter iter;
+ gboolean valid_iter;
+
+ if (g_file_equal (parent, folder_tree->priv->root))
+ p_parent_iter = NULL;
+ else if (gth_folder_tree_get_iter (folder_tree, parent, &parent_iter, NULL))
+ p_parent_iter = &parent_iter;
+ else
+ return;
+
+ _gth_folder_tree_add_loading_item (folder_tree, p_parent_iter);
+
+ /* remove anything but the loading item */
+ gtk_tree_model_iter_children (GTK_TREE_MODEL (folder_tree->priv->tree_store), &iter, p_parent_iter);
+ do {
+ EntryType file_entry_type;
+
+ gtk_tree_model_get (GTK_TREE_MODEL (folder_tree->priv->tree_store), &iter,
+ COLUMN_TYPE, &file_entry_type,
+ -1);
+
+ if (file_entry_type != ENTRY_TYPE_LOADING)
+ valid_iter = gtk_tree_store_remove (folder_tree->priv->tree_store, &iter);
+ else
+ valid_iter = gtk_tree_model_iter_next (GTK_TREE_MODEL (folder_tree->priv->tree_store), &iter);
+ }
+ while (valid_iter);
+
+ emit_fake_motion_notify_event (folder_tree);
+}
+
+
+static gboolean
+_gth_folder_tree_file_is_in_children (GthFolderTree *folder_tree,
+ GtkTreeIter *parent_iter,
+ GFile *file)
+{
+ GtkTreeIter iter;
+ gboolean found = FALSE;
+
+ if (! gtk_tree_model_iter_children (GTK_TREE_MODEL (folder_tree->priv->tree_store), &iter, parent_iter))
+ return FALSE;
+
+ do {
+ GFile *test_file;
+
+ gtk_tree_model_get (GTK_TREE_MODEL (folder_tree->priv->tree_store), &iter,
+ COLUMN_FILE, &test_file,
+ -1);
+ if ((test_file != NULL) && g_file_equal (file, test_file))
+ found = TRUE;
+
+ if (test_file != NULL)
+ g_object_unref (test_file);
+ }
+ while (! found && gtk_tree_model_iter_next (GTK_TREE_MODEL (folder_tree->priv->tree_store), &iter));
+
+ return found;
+}
+
+
+void
+gth_folder_tree_add_children (GthFolderTree *folder_tree,
+ GFile *parent,
+ GList *files)
+{
+ GtkTreeIter parent_iter;
+ GtkTreeIter *p_parent_iter;
+ gboolean is_empty;
+ GList *scan;
+
+ if (g_file_equal (parent, folder_tree->priv->root))
+ p_parent_iter = NULL;
+ else if (gth_folder_tree_get_iter (folder_tree, parent, &parent_iter, NULL))
+ p_parent_iter = &parent_iter;
+ else
+ return;
+
+ is_empty = TRUE;
+ for (scan = files; scan; scan = scan->next) {
+ GthFileData *file_data = scan->data;
+
+ if (_gth_folder_tree_file_is_in_children (folder_tree, p_parent_iter, file_data->file))
+ continue;
+ if (_gth_folder_tree_add_file (folder_tree, p_parent_iter, file_data))
+ is_empty = FALSE;
+ }
+
+ if (! is_empty)
+ _gth_folder_tree_remove_child_type (folder_tree, p_parent_iter, ENTRY_TYPE_EMPTY);
+}
+
+
+static gboolean
+_gth_folder_tree_get_child (GthFolderTree *folder_tree,
+ GFile *file,
+ GtkTreeIter *file_iter,
+ GtkTreeIter *parent)
+{
+ GtkTreeModel *tree_model = GTK_TREE_MODEL (folder_tree->priv->tree_store);
+ GtkTreeIter iter;
+
+ if (! gtk_tree_model_iter_children (tree_model, &iter, parent))
+ return FALSE;
+
+ do {
+ GFile *test_file;
+
+ gtk_tree_model_get (tree_model, &iter, COLUMN_FILE, &test_file, -1);
+ if ((test_file != NULL) && g_file_equal (file, test_file)) {
+ *file_iter = iter;
+ return TRUE;
+ }
+ }
+ while (gtk_tree_model_iter_next (tree_model, &iter));
+
+ return FALSE;
+}
+
+
+void
+gth_folder_tree_update_children (GthFolderTree *folder_tree,
+ GFile *parent,
+ GList *files)
+{
+ GtkTreeIter parent_iter;
+ GtkTreeIter *p_parent_iter;
+ GList *scan;
+ GtkTreeIter iter;
+
+ if (g_file_equal (parent, folder_tree->priv->root))
+ p_parent_iter = NULL;
+ else if (gth_folder_tree_get_iter (folder_tree, parent, &parent_iter, NULL))
+ p_parent_iter = &parent_iter;
+ else
+ return;
+
+ /* update each file if already present */
+
+ for (scan = files; scan; scan = scan->next) {
+ GthFileData *file_data = scan->data;
+
+ if (_gth_folder_tree_get_child (folder_tree, file_data->file, &iter, p_parent_iter))
+ _gth_folder_tree_set_file_data (folder_tree, &iter, file_data);
+ }
+}
+
+
+void
+gth_folder_tree_update_child (GthFolderTree *folder_tree,
+ GFile *old_file,
+ GthFileData *file_data)
+{
+ GtkTreeIter iter;
+
+ if (gth_folder_tree_get_iter (folder_tree, old_file, &iter, NULL))
+ _gth_folder_tree_set_file_data (folder_tree, &iter, file_data);
+}
+
+
+void
+gth_folder_tree_delete_children (GthFolderTree *folder_tree,
+ GFile *parent,
+ GList *files)
+{
+ GtkTreeIter parent_iter;
+ GtkTreeIter *p_parent_iter;
+ GList *scan;
+
+ if (g_file_equal (parent, folder_tree->priv->root))
+ p_parent_iter = NULL;
+ else if (gth_folder_tree_get_iter (folder_tree, parent, &parent_iter, NULL))
+ p_parent_iter = &parent_iter;
+ else
+ return;
+
+ if (_gth_folder_tree_iter_has_no_child (folder_tree, p_parent_iter))
+ return;
+
+ /* add the empty item first to not allow the folder to collapse. */
+ _gth_folder_tree_add_empty_item (folder_tree, p_parent_iter);
+
+ for (scan = files; scan; scan = scan->next) {
+ GFile *file = scan->data;
+ GtkTreeIter iter;
+
+ if (gth_folder_tree_get_iter (folder_tree, file, &iter, p_parent_iter))
+ gtk_tree_store_remove (folder_tree->priv->tree_store, &iter);
+ }
+
+ if (gtk_tree_model_iter_n_children (GTK_TREE_MODEL (folder_tree->priv->tree_store), p_parent_iter) > 1)
+ _gth_folder_tree_remove_child_type (folder_tree, p_parent_iter, ENTRY_TYPE_EMPTY);
+}
+
+
+void
+gth_folder_tree_start_editing (GthFolderTree *folder_tree,
+ GFile *file)
+{
+ GtkTreeIter iter;
+ GtkTreePath *tree_path;
+ char *path;
+ GtkTreeViewColumn *tree_column;
+
+ if (! gth_folder_tree_get_iter (folder_tree, file, &iter, NULL))
+ return;
+
+ tree_path = gtk_tree_model_get_path (GTK_TREE_MODEL (folder_tree->priv->tree_store), &iter);
+ path = gtk_tree_path_to_string (tree_path);
+
+ tree_column = gtk_tree_view_get_column (GTK_TREE_VIEW (folder_tree), 0);
+ gtk_tree_view_expand_to_path (GTK_TREE_VIEW (folder_tree), tree_path);
+ gtk_tree_view_set_cursor (GTK_TREE_VIEW (folder_tree),
+ tree_path,
+ tree_column,
+ TRUE);
+
+ g_free (path);
+ gtk_tree_path_free (tree_path);
+}
+
+
+GtkTreePath *
+gth_folder_tree_get_path (GthFolderTree *folder_tree,
+ GFile *file)
+{
+ GtkTreeIter iter;
+
+ if (! gth_folder_tree_get_iter (folder_tree, file, &iter, NULL))
+ return NULL;
+ else
+ return gtk_tree_model_get_path (GTK_TREE_MODEL (folder_tree->priv->tree_store), &iter);
+}
+
+
+gboolean
+gth_folder_tree_is_loaded (GthFolderTree *folder_tree,
+ GtkTreePath *path)
+{
+ GtkTreeIter iter;
+ gboolean loaded;
+
+ if (! gtk_tree_model_get_iter (GTK_TREE_MODEL (folder_tree->priv->tree_store), &iter, path))
+ return FALSE;
+
+ gtk_tree_model_get (GTK_TREE_MODEL (folder_tree->priv->tree_store), &iter,
+ COLUMN_LOADED, &loaded,
+ -1);
+
+ return loaded;
+}
+
+
+static void
+gth_folder_tree_set_loaded (GthFolderTree *folder_tree,
+ GtkTreeIter *root)
+{
+ GtkTreeModel *tree_model = GTK_TREE_MODEL (folder_tree->priv->tree_store);
+ GtkTreeIter iter;
+
+ if (root != NULL)
+ gtk_tree_store_set (folder_tree->priv->tree_store, root,
+ COLUMN_LOADED, FALSE,
+ -1);
+
+ if (gtk_tree_model_iter_children (tree_model, &iter, root)) {
+ do {
+ gth_folder_tree_set_loaded (folder_tree, &iter);
+ }
+ while (gtk_tree_model_iter_next (tree_model, &iter));
+ }
+}
+
+
+void
+gth_folder_tree_reset_loaded (GthFolderTree *folder_tree)
+{
+ gth_folder_tree_set_loaded (folder_tree, NULL);
+}
+
+
+void
+gth_folder_tree_expand_row (GthFolderTree *folder_tree,
+ GtkTreePath *path,
+ gboolean open_all)
+{
+ g_signal_handlers_block_by_func (folder_tree, row_expanded_cb, folder_tree);
+ gtk_tree_view_expand_row (GTK_TREE_VIEW (folder_tree), path, open_all);
+ g_signal_handlers_unblock_by_func (folder_tree, row_expanded_cb, folder_tree);
+}
+
+
+void
+gth_folder_tree_select_path (GthFolderTree *folder_tree,
+ GtkTreePath *path)
+{
+ GtkTreeSelection *selection;
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (folder_tree));
+ g_signal_handlers_block_by_func (selection, selection_changed_cb, folder_tree);
+ gtk_tree_selection_select_path (selection, path);
+ g_signal_handlers_unblock_by_func (selection, selection_changed_cb, folder_tree);
+}
+
+
+GFile *
+gth_folder_tree_get_root (GthFolderTree *folder_tree)
+{
+ return folder_tree->priv->root;
+}
+
+
+GFile *
+gth_folder_tree_get_selected (GthFolderTree *folder_tree)
+{
+ GtkTreeSelection *selection;
+ GtkTreeModel *tree_model;
+ GtkTreeIter iter;
+ EntryType entry_type;
+ GFile *file;
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (folder_tree));
+ if (selection == NULL)
+ return NULL;
+
+ tree_model = GTK_TREE_MODEL (folder_tree->priv->tree_store);
+ if (! gtk_tree_selection_get_selected (selection, &tree_model, &iter))
+ return NULL;
+
+ gtk_tree_model_get (GTK_TREE_MODEL (folder_tree->priv->tree_store),
+ &iter,
+ COLUMN_TYPE, &entry_type,
+ COLUMN_FILE, &file,
+ -1);
+
+ if (entry_type != ENTRY_TYPE_FILE) {
+ if (file != NULL)
+ g_object_unref (file);
+ file = NULL;
+ }
+
+ return file;
+}
+
+
+GFile *
+gth_folder_tree_get_selected_or_parent (GthFolderTree *folder_tree)
+{
+ GtkTreeSelection *selection;
+ GtkTreeModel *tree_model;
+ GtkTreeIter iter;
+ GtkTreeIter parent;
+ EntryType entry_type;
+ GFile *file;
+
+ file = gth_folder_tree_get_selected (folder_tree);
+ if (file != NULL)
+ return file;
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (folder_tree));
+ if (selection == NULL)
+ return NULL;
+
+ tree_model = GTK_TREE_MODEL (folder_tree->priv->tree_store);
+ if (! gtk_tree_selection_get_selected (selection, &tree_model, &iter))
+ return NULL;
+
+ if (! gtk_tree_model_iter_parent (tree_model, &parent, &iter))
+ return NULL;
+
+ gtk_tree_model_get (GTK_TREE_MODEL (folder_tree->priv->tree_store),
+ &parent,
+ COLUMN_TYPE, &entry_type,
+ COLUMN_FILE, &file,
+ -1);
+
+ if (entry_type != ENTRY_TYPE_FILE) {
+ if (file != NULL)
+ g_object_unref (file);
+ file = NULL;
+ }
+
+ return file;
+}
diff --git a/gthumb/gth-folder-tree.h b/gthumb/gth-folder-tree.h
new file mode 100644
index 0000000..16bfc53
--- /dev/null
+++ b/gthumb/gth-folder-tree.h
@@ -0,0 +1,110 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2008 The Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GTH_FOLDER_TREE_H
+#define GTH_FOLDER_TREE_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GTH_TYPE_FOLDER_TREE (gth_folder_tree_get_type ())
+#define GTH_FOLDER_TREE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTH_TYPE_FOLDER_TREE, GthFolderTree))
+#define GTH_FOLDER_TREE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTH_TYPE_FOLDER_TREE, GthFolderTreeClass))
+#define GTH_IS_FOLDER_TREE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTH_TYPE_FOLDER_TREE))
+#define GTH_IS_FOLDER_TREE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTH_TYPE_FOLDER_TREE))
+#define GTH_FOLDER_TREE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GTH_TYPE_FOLDER_TREE, GthFolderTreeClass))
+
+typedef struct _GthFolderTree GthFolderTree;
+typedef struct _GthFolderTreePrivate GthFolderTreePrivate;
+typedef struct _GthFolderTreeClass GthFolderTreeClass;
+
+struct _GthFolderTree {
+ GtkTreeView __parent;
+ GthFolderTreePrivate *priv;
+};
+
+struct _GthFolderTreeClass {
+ GtkTreeViewClass __parent;
+
+ /* -- signals -- */
+
+ void (*folder_popup) (GthFolderTree *folder_tree,
+ GFile *file,
+ guint32 time);
+ void (*list_children) (GthFolderTree *folder_tree,
+ GFile *file);
+ void (*list_popup) (GthFolderTree *folder_tree,
+ guint32 time);
+ void (*load) (GthFolderTree *folder_tree,
+ GFile *file);
+ void (*open) (GthFolderTree *folder_tree,
+ GFile *file);
+ void (*open_parent) (GthFolderTree *folder_tree);
+ void (*rename) (GthFolderTree *folder_tree,
+ GFile *file,
+ const char *new_name);
+};
+
+GType gth_folder_tree_get_type (void);
+GtkWidget * gth_folder_tree_new (const char *uri);
+void gth_folder_tree_set_list (GthFolderTree *folder_tree,
+ GFile *root,
+ GList *files,
+ gboolean open_parent);
+void gth_folder_tree_set_children (GthFolderTree *folder_tree,
+ GFile *parent,
+ GList *files /* GthFileData */);
+void gth_folder_tree_loading_children (GthFolderTree *folder_tree,
+ GFile *parent);
+void gth_folder_tree_add_children (GthFolderTree *folder_tree,
+ GFile *parent,
+ GList *files /* GthFileData */);
+void gth_folder_tree_update_children (GthFolderTree *folder_tree,
+ GFile *parent,
+ GList *files /* GthFileData */);
+void gth_folder_tree_update_child (GthFolderTree *folder_tree,
+ GFile *file,
+ GthFileData *file_data);
+void gth_folder_tree_delete_children (GthFolderTree *folder_tree,
+ GFile *parent,
+ GList *files /* GFile */);
+void gth_folder_tree_start_editing (GthFolderTree *folder_tree,
+ GFile *file);
+GtkTreePath * gth_folder_tree_get_path (GthFolderTree *folder_tree,
+ GFile *file);
+gboolean gth_folder_tree_is_loaded (GthFolderTree *folder_tree,
+ GtkTreePath *path);
+void gth_folder_tree_reset_loaded (GthFolderTree *folder_tree);
+void gth_folder_tree_expand_row (GthFolderTree *folder_tree,
+ GtkTreePath *path,
+ gboolean open_all);
+void gth_folder_tree_select_path (GthFolderTree *folder_tree,
+ GtkTreePath *path);
+GFile * gth_folder_tree_get_root (GthFolderTree *folder_tree);
+GFile * gth_folder_tree_get_selected (GthFolderTree *folder_tree);
+GFile * gth_folder_tree_get_selected_or_parent
+ (GthFolderTree *folder_tree);
+
+G_END_DECLS
+
+#endif /* GTH_FOLDER_TREE_H */
diff --git a/gthumb/gth-hook.c b/gthumb/gth-hook.c
new file mode 100644
index 0000000..d6d7b74
--- /dev/null
+++ b/gthumb/gth-hook.c
@@ -0,0 +1,347 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include "gth-hook.h"
+
+
+#define GTH_HOOK_CALLBACK(x) ((GthHookCallback *)(x))
+#define GET_HOOK(name) \
+ g_hash_table_lookup (hooks, (name)); \
+ if (hook == NULL) { \
+ g_warning ("hook '%s' not found", (name)); \
+ return; \
+ }
+#define GET_HOOK_OR_NULL(name) \
+ g_hash_table_lookup (hooks, (name)); \
+ if (hook == NULL) { \
+ g_warning ("hook '%s' not found", (name)); \
+ return NULL; \
+ }
+
+static GHashTable *hooks = NULL;
+static gboolean initialized = FALSE;
+
+
+typedef struct {
+ GHook ghook;
+ int sort_order;
+} GthHookCallback;
+
+
+typedef struct {
+ GHookList *list;
+ int n_args;
+} GthHook;
+
+
+static void
+gth_hook_free (GthHook *hook)
+{
+ g_hook_list_clear (hook->list);
+ g_free (hook->list);
+ g_free (hook);
+}
+
+
+void
+gth_hooks_initialize (void)
+{
+ if (initialized)
+ return;
+
+ hooks = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ (GDestroyNotify) g_free,
+ (GDestroyNotify) gth_hook_free);
+
+ initialized = TRUE;
+}
+
+
+void
+gth_hook_register (const char *name,
+ int n_args)
+{
+ GthHook *hook;
+
+ g_return_if_fail (name != NULL);
+ g_return_if_fail ((n_args >= 0) || (n_args <= 3));
+
+ if (g_hash_table_lookup (hooks, name) != NULL) {
+ g_warning ("hook '%s' already registered", name);
+ return;
+ }
+
+ hook = g_new0 (GthHook, 1);
+ hook->list = g_new (GHookList, 1);
+ g_hook_list_init (hook->list, sizeof (GthHookCallback));
+ hook->n_args = n_args;
+
+ g_hash_table_insert (hooks, g_strdup (name), hook);
+}
+
+
+static int
+hook_compare_func (GHook *new_hook,
+ GHook *sibling)
+{
+ GthHookCallback *new_function = GTH_HOOK_CALLBACK (new_hook);
+ GthHookCallback *sibling_function = GTH_HOOK_CALLBACK (sibling);
+
+ if (new_function->sort_order > sibling_function->sort_order)
+ return -1;
+ else if (new_function->sort_order < sibling_function->sort_order)
+ return 1;
+ else
+ return 0;
+}
+
+
+void
+gth_hook_add_callback (const char *name,
+ int sort_order,
+ GCallback callback,
+ gpointer data)
+{
+ GthHook *hook;
+ GHook *function;
+
+ hook = GET_HOOK (name);
+
+ function = g_hook_alloc (hook->list);
+ function->func = callback;
+ function->data = data;
+ function->destroy = NULL;
+ GTH_HOOK_CALLBACK (function)->sort_order = sort_order;
+
+ g_hook_insert_sorted (hook->list, function, hook_compare_func);
+}
+
+
+void
+gth_hook_remove_callback (const char *name,
+ GCallback callback)
+{
+ GthHook *hook;
+ GHook *function;
+
+ hook = GET_HOOK (name);
+ function = g_hook_find_func (hook->list, TRUE, callback);
+ if (function == NULL) {
+ g_warning ("callback not found in hook '%s'", name);
+ return;
+ }
+ g_hook_destroy_link (hook->list, function);
+}
+
+
+typedef void (*GthMarshaller0Args) (gpointer);
+typedef void (*GthMarshaller1Arg) (gpointer, gpointer);
+typedef void (*GthMarshaller2Args) (gpointer, gpointer, gpointer);
+typedef void (*GthMarshaller3Args) (gpointer, gpointer, gpointer, gpointer);
+
+
+static void
+invoke_marshaller_0 (GHook *hook,
+ gpointer data)
+{
+ ((GthMarshaller0Args) hook->func) (hook->data);
+}
+
+
+static void
+invoke_marshaller_1 (GHook *hook,
+ gpointer data)
+{
+ gpointer *marshal_data = data;
+
+ ((GthMarshaller1Arg) hook->func) (marshal_data[0], hook->data);
+}
+
+
+static void
+invoke_marshaller_2 (GHook *hook,
+ gpointer data)
+{
+ gpointer *marshal_data = data;
+
+ ((GthMarshaller2Args) hook->func) (marshal_data[0], marshal_data[1], hook->data);
+}
+
+
+static void
+invoke_marshaller_3 (GHook *hook,
+ gpointer data)
+{
+ gpointer *marshal_data = data;
+
+ ((GthMarshaller3Args) hook->func) (marshal_data[0], marshal_data[1], marshal_data[2], hook->data);
+}
+
+
+void
+gth_hook_invoke (const char *name,
+ gpointer first_data,
+ ...)
+{
+ GthHook *hook;
+ gpointer *marshal_data;
+ int i = 0;
+ va_list args;
+ GHookMarshaller invoke_marshaller;
+
+ hook = GET_HOOK (name);
+ marshal_data = g_new0 (gpointer, hook->n_args);
+
+ if (first_data != NULL)
+ marshal_data[i++] = first_data;
+
+ va_start (args, first_data);
+ while (i < hook->n_args)
+ marshal_data[i++] = va_arg (args, gpointer);
+ va_end (args);
+
+ switch (hook->n_args) {
+ case 0:
+ invoke_marshaller = invoke_marshaller_0;
+ break;
+ case 1:
+ invoke_marshaller = invoke_marshaller_1;
+ break;
+ case 2:
+ invoke_marshaller = invoke_marshaller_2;
+ break;
+ case 3:
+ invoke_marshaller = invoke_marshaller_3;
+ break;
+ default:
+ invoke_marshaller = NULL;
+ break;
+ }
+
+ if (invoke_marshaller != NULL)
+ g_hook_list_marshal (hook->list, FALSE, invoke_marshaller, marshal_data);
+
+ g_free (marshal_data);
+}
+
+
+typedef void * (*GthMarshaller0ArgsGet) (gpointer);
+typedef void * (*GthMarshaller1ArgGet) (gpointer, gpointer);
+typedef void * (*GthMarshaller2ArgsGet) (gpointer, gpointer, gpointer);
+typedef void * (*GthMarshaller3ArgsGet) (gpointer, gpointer, gpointer, gpointer);
+
+
+static void
+invoke_marshaller_0_get (GHook *hook,
+ gpointer data)
+{
+ gpointer *marshal_data = data;
+
+ if (marshal_data[0] == NULL)
+ marshal_data[0] = ((GthMarshaller0ArgsGet) hook->func) (hook->data);
+}
+
+
+static void
+invoke_marshaller_1_get (GHook *hook,
+ gpointer data)
+{
+ gpointer *marshal_data = data;
+
+ if (marshal_data[1] == NULL)
+ marshal_data[1] = ((GthMarshaller1ArgGet) hook->func) (marshal_data[0], hook->data);
+}
+
+
+static void
+invoke_marshaller_2_get (GHook *hook,
+ gpointer data)
+{
+ gpointer *marshal_data = data;
+
+ if (marshal_data[2] == NULL)
+ marshal_data[2] = ((GthMarshaller2ArgsGet) hook->func) (marshal_data[0], marshal_data[1], hook->data);
+}
+
+
+static void
+invoke_marshaller_3_get (GHook *hook,
+ gpointer data)
+{
+ gpointer *marshal_data = data;
+
+ if (marshal_data[3] == NULL)
+ marshal_data[3] = ((GthMarshaller3ArgsGet) hook->func) (marshal_data[0], marshal_data[1], marshal_data[2], hook->data);
+}
+
+
+void *
+gth_hook_invoke_get (const char *name,
+ gpointer first_data,
+ ...)
+{
+ GthHook *hook;
+ gpointer *marshal_data;
+ int i = 0;
+ va_list args;
+ GHookMarshaller invoke_marshaller;
+ void *value;
+
+ hook = GET_HOOK_OR_NULL (name);
+ marshal_data = g_new0 (gpointer, hook->n_args + 1);
+ marshal_data[hook->n_args] = NULL;
+
+ va_start (args, first_data);
+ marshal_data[i++] = first_data;
+ while (i < hook->n_args)
+ marshal_data[i++] = va_arg (args, gpointer);
+ va_end (args);
+
+ switch (hook->n_args) {
+ case 0:
+ invoke_marshaller = invoke_marshaller_0_get;
+ break;
+ case 1:
+ invoke_marshaller = invoke_marshaller_1_get;
+ break;
+ case 2:
+ invoke_marshaller = invoke_marshaller_2_get;
+ break;
+ case 3:
+ invoke_marshaller = invoke_marshaller_3_get;
+ break;
+ default:
+ invoke_marshaller = NULL;
+ break;
+ }
+
+ if (invoke_marshaller != NULL)
+ g_hook_list_marshal (hook->list, FALSE, invoke_marshaller, marshal_data);
+
+ value = marshal_data[hook->n_args];
+
+ g_free (marshal_data);
+
+ return value;
+}
diff --git a/gthumb/gth-hook.h b/gthumb/gth-hook.h
new file mode 100644
index 0000000..b7cb131
--- /dev/null
+++ b/gthumb/gth-hook.h
@@ -0,0 +1,49 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GTH_HOOK_H
+#define GTH_HOOK_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+void gth_hooks_initialize (void);
+void gth_hook_register (const char *name,
+ int n_args);
+void gth_hook_add_callback (const char *name,
+ int sort_order,
+ GCallback callback,
+ gpointer data);
+void gth_hook_remove_callback (const char *name,
+ GCallback callback);
+void gth_hook_invoke (const char *name,
+ gpointer first_data,
+ ...);
+void * gth_hook_invoke_get (const char *name,
+ gpointer first_data,
+ ...);
+
+G_END_DECLS
+
+#endif /* GTH_HOOK_H */
diff --git a/gthumb/gth-icon-cache.c b/gthumb/gth-icon-cache.c
new file mode 100644
index 0000000..de68c92
--- /dev/null
+++ b/gthumb/gth-icon-cache.c
@@ -0,0 +1,144 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2008 The Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include "glib-utils.h"
+#include "gth-icon-cache.h"
+#include "gtk-utils.h"
+#include "pixbuf-utils.h"
+
+
+#define VOID_PIXBUF_KEY "void-pixbuf"
+
+
+struct _GthIconCache {
+ GtkIconTheme *icon_theme;
+ int icon_size;
+ GHashTable *cache;
+};
+
+
+GthIconCache *
+gth_icon_cache_new (GtkIconTheme *icon_theme,
+ int icon_size)
+{
+ GthIconCache *icon_cache;
+
+ g_return_val_if_fail (icon_theme != NULL, NULL);
+
+ icon_cache = g_new0 (GthIconCache, 1);
+ icon_cache->icon_theme = icon_theme;
+ icon_cache->icon_size = icon_size;
+ icon_cache->cache = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_object_unref);
+
+ g_hash_table_insert (icon_cache->cache, VOID_PIXBUF_KEY, create_void_pixbuf (icon_cache->icon_size, icon_cache->icon_size));
+
+ return icon_cache;
+}
+
+
+GthIconCache *
+gth_icon_cache_new_for_widget (GtkWidget *widget,
+ GtkIconSize icon_size)
+{
+ GtkIconTheme *icon_theme;
+ int pixel_size;
+
+ icon_theme = gtk_icon_theme_get_for_screen (gtk_widget_get_screen (widget));
+ pixel_size = _gtk_icon_get_pixel_size (widget, GTK_ICON_SIZE_MENU);
+
+ return gth_icon_cache_new (icon_theme, pixel_size);
+}
+
+
+void
+gth_icon_cache_free (GthIconCache *icon_cache)
+{
+ if (icon_cache == NULL)
+ return;
+ g_hash_table_destroy (icon_cache->cache);
+ g_free (icon_cache);
+}
+
+
+static const char *
+_gth_icon_cache_get_icon_key (GIcon *icon)
+{
+ const char *key = NULL;
+
+ if (G_IS_THEMED_ICON (icon)) {
+ char **icon_names;
+ char *name;
+
+ g_object_get (icon, "names", &icon_names, NULL);
+ name = g_strjoinv (",", icon_names);
+
+ key = get_static_string (name);
+
+ g_free (name);
+ g_strfreev (icon_names);
+ }
+ else if (G_IS_FILE_ICON (icon)) {
+ GFile *file;
+ char *filename;
+
+ file = g_file_icon_get_file (G_FILE_ICON (icon));
+ filename = g_file_get_path (file);
+
+ key = get_static_string (filename);
+
+ g_free (filename);
+ g_object_unref (file);
+ }
+
+ return key;
+}
+
+
+GdkPixbuf *
+gth_icon_cache_get_pixbuf (GthIconCache *icon_cache,
+ GIcon *icon)
+{
+ const char *key;
+ GdkPixbuf *pixbuf;
+
+ if (icon != NULL)
+ key = _gth_icon_cache_get_icon_key (icon);
+
+ if (key == NULL)
+ key = VOID_PIXBUF_KEY;
+
+ performance (DEBUG_INFO, "get pixbuf for %s", key);
+
+ pixbuf = g_hash_table_lookup (icon_cache->cache, key);
+ if (pixbuf != NULL) {
+ g_object_ref (pixbuf);
+ return pixbuf;
+ }
+
+ pixbuf = _g_icon_get_pixbuf (icon, icon_cache->icon_size, icon_cache->icon_theme);
+ g_hash_table_insert (icon_cache->cache, (gpointer) key, g_object_ref (pixbuf));
+
+ performance (DEBUG_INFO, "done (not cached)");
+
+ return pixbuf;
+}
diff --git a/gthumb/gth-icon-cache.h b/gthumb/gth-icon-cache.h
new file mode 100644
index 0000000..0e53c99
--- /dev/null
+++ b/gthumb/gth-icon-cache.h
@@ -0,0 +1,42 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2008 The Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GTH_ICON_CACHE_H
+#define GTH_ICON_CACHE_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+typedef struct _GthIconCache GthIconCache;
+
+GthIconCache * gth_icon_cache_new (GtkIconTheme *icon_theme,
+ int icon_size);
+GthIconCache * gth_icon_cache_new_for_widget (GtkWidget *widget,
+ GtkIconSize icon_size);
+void gth_icon_cache_free (GthIconCache *icon_cache);
+GdkPixbuf * gth_icon_cache_get_pixbuf (GthIconCache *icon_cache,
+ GIcon *icon);
+
+G_END_DECLS
+
+#endif /* GTH_ICON_CACHE_H */
diff --git a/gthumb/gth-icon-view.c b/gthumb/gth-icon-view.c
new file mode 100644
index 0000000..b924162
--- /dev/null
+++ b/gthumb/gth-icon-view.c
@@ -0,0 +1,451 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2008 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <gdk/gdkkeysyms.h>
+#include "gth-icon-view.h"
+
+
+#define IMAGE_TEXT_SPACING 5
+#define ICON_SPACING 12
+
+
+static gpointer gth_icon_view_parent_class = NULL;
+static GthFileViewIface *gth_icon_view_gth_file_view_parent_iface = NULL;
+static GthFileSelectionIface *gth_icon_view_gth_file_selection_parent_iface = NULL;
+
+
+void
+gth_icon_view_real_set_model (GthFileView *self,
+ GtkTreeModel *model)
+{
+ gtk_icon_view_set_model (GTK_ICON_VIEW (self), model);
+}
+
+
+GtkTreeModel *
+gth_icon_view_real_get_model (GthFileView *self)
+{
+ return gtk_icon_view_get_model (GTK_ICON_VIEW (self));
+}
+
+
+static void
+gth_icon_view_real_set_view_mode (GthFileView *base,
+ GthViewMode mode)
+{
+ GthIconView * self;
+ self = GTH_ICON_VIEW (base);
+}
+
+
+static GthViewMode
+gth_icon_view_real_get_view_mode (GthFileView *base)
+{
+ GthIconView * self;
+ self = GTH_ICON_VIEW (base);
+ return GTH_VIEW_MODE_NONE;
+}
+
+
+static void
+gth_icon_view_real_scroll_to (GthFileView *base,
+ int pos,
+ double yalign)
+{
+ GthIconView *self = GTH_ICON_VIEW (base);
+ GtkTreePath *path;
+
+ path = gtk_tree_path_new_from_indices (pos, -1);
+ gtk_icon_view_scroll_to_path (GTK_ICON_VIEW (self),
+ path,
+ TRUE,
+ 0.5,
+ yalign);
+
+ gtk_tree_path_free (path);
+}
+
+
+static GthVisibility
+gth_icon_view_real_get_visibility (GthFileView *base,
+ int pos)
+{
+ GthIconView *self = GTH_ICON_VIEW (base);
+ GtkTreePath *start_path, *end_path;
+ int start_pos, end_pos;
+
+ if (! gtk_icon_view_get_visible_range (GTK_ICON_VIEW (self), &start_path, &end_path))
+ return -1;
+
+ start_pos = gtk_tree_path_get_indices (start_path)[0];
+ end_pos = gtk_tree_path_get_indices (end_path)[0];
+
+ gtk_tree_path_free (start_path);
+ gtk_tree_path_free (end_path);
+
+ return ((pos >= start_pos) && (pos <= end_pos)) ? GTH_VISIBILITY_FULL : GTH_VISIBILITY_NONE;
+}
+
+
+static int
+gth_icon_view_real_get_at_position (GthFileView *base,
+ int x,
+ int y)
+{
+ GthIconView * self;
+ self = GTH_ICON_VIEW (base);
+ return -1;
+}
+
+
+static int
+gth_icon_view_real_get_first_visible (GthFileView *base)
+{
+ GthIconView *self = GTH_ICON_VIEW (base);
+ GtkTreePath *start_path;
+ int pos;
+
+ if (! gtk_icon_view_get_visible_range (GTK_ICON_VIEW (self), &start_path, NULL))
+ return -1;
+
+ pos = gtk_tree_path_get_indices (start_path)[0];
+ gtk_tree_path_free (start_path);
+
+ return pos;
+}
+
+
+static int
+gth_icon_view_real_get_last_visible (GthFileView *base)
+{
+ GthIconView *self = GTH_ICON_VIEW (base);
+ GtkTreePath *end_path;
+ int pos;
+
+ if (! gtk_icon_view_get_visible_range (GTK_ICON_VIEW (self), NULL, &end_path))
+ return -1;
+
+ pos = gtk_tree_path_get_indices (end_path)[0];
+ gtk_tree_path_free (end_path);
+
+ return pos;
+}
+
+
+static void
+gth_icon_view_real_activated (GthFileView *base,
+ int pos)
+{
+ GthIconView * self;
+ self = GTH_ICON_VIEW (base);
+ g_return_if_fail (pos >= 0);
+}
+
+
+static void
+gth_icon_view_real_set_cursor (GthFileView *base,
+ int pos)
+{
+ GthIconView *self;
+ GtkTreePath *path;
+
+ self = GTH_ICON_VIEW (base);
+ g_return_if_fail (pos >= 0);
+
+ path = gtk_tree_path_new_from_indices (pos, -1);
+ gtk_icon_view_set_cursor (GTK_ICON_VIEW (base), path, NULL, FALSE);
+
+ gtk_tree_path_free (path);
+}
+
+
+static int
+gth_icon_view_real_get_cursor (GthFileView *base)
+{
+ GthIconView * self;
+ self = GTH_ICON_VIEW (base);
+ return -1;
+}
+
+
+static void
+gth_icon_view_real_set_reorderable (GthFileView *base,
+ gboolean value)
+{
+ GthIconView * self;
+ self = GTH_ICON_VIEW (base);
+}
+
+
+static gboolean
+gth_icon_view_real_get_reorderable (GthFileView *base)
+{
+ GthIconView * self;
+ self = GTH_ICON_VIEW (base);
+ return FALSE;
+}
+
+
+static GList *
+gth_icon_view_real_get_selected (GthFileSelection *base)
+{
+ return gtk_icon_view_get_selected_items (GTK_ICON_VIEW (base));
+}
+
+
+static void
+gth_icon_view_real_select (GthFileSelection *base,
+ int pos)
+{
+ GtkTreePath *path;
+
+ path = gtk_tree_path_new_from_indices (pos, -1);
+ gtk_icon_view_select_path (GTK_ICON_VIEW (base), path);
+
+ gtk_tree_path_free (path);
+}
+
+
+static void
+gth_icon_view_real_unselect (GthFileSelection *base,
+ int pos)
+{
+ GtkTreePath *path;
+
+ path = gtk_tree_path_new_from_indices (pos, -1);
+ gtk_icon_view_unselect_path (GTK_ICON_VIEW (base), path);
+
+ gtk_tree_path_free (path);
+}
+
+
+static void
+gth_icon_view_real_select_all (GthFileSelection *base)
+{
+ gtk_icon_view_select_all (GTK_ICON_VIEW (base));
+}
+
+
+static void
+gth_icon_view_real_unselect_all (GthFileSelection *base)
+{
+ gtk_icon_view_unselect_all (GTK_ICON_VIEW (base));
+}
+
+
+static gboolean
+gth_icon_view_real_is_selected (GthFileSelection *base,
+ int pos)
+{
+ GthIconView * self;
+ self = GTH_ICON_VIEW (base);
+
+ return FALSE;
+}
+
+
+static GtkTreePath *
+gth_icon_view_real_get_first_selected (GthFileSelection *base)
+{
+ GthIconView * self;
+ self = GTH_ICON_VIEW (base);
+ return NULL;
+}
+
+
+static GtkTreePath *
+gth_icon_view_real_get_last_selected (GthFileSelection *base)
+{
+ GthIconView * self;
+ self = GTH_ICON_VIEW (base);
+ return NULL;
+}
+
+
+static guint
+gth_icon_view_real_get_n_selected (GthFileSelection *base)
+{
+ GthIconView *self;
+ GList *selected;
+ guint n_selected;
+
+ self = GTH_ICON_VIEW (base);
+
+ selected = gtk_icon_view_get_selected_items (GTK_ICON_VIEW (self));
+ n_selected = (guint) g_list_length (selected);
+
+ g_list_foreach (selected, (GFunc) gtk_tree_path_free, NULL);
+ g_list_free (selected);
+
+ return n_selected;
+}
+
+
+GtkWidget *
+gth_icon_view_new (void)
+{
+ return g_object_new (GTH_TYPE_ICON_VIEW, NULL);
+}
+
+
+GtkWidget *
+gth_icon_view_new_with_model (GtkTreeModel *model)
+{
+ return g_object_new (GTH_TYPE_ICON_VIEW, "model", model, NULL);
+}
+
+
+static void
+gtk_icon_view_add_move_binding (GtkBindingSet *binding_set,
+ guint keyval,
+ guint modmask,
+ GtkMovementStep step,
+ gint count)
+{
+ gtk_binding_entry_add_signal (binding_set, keyval, modmask,
+ "move_cursor", 2,
+ G_TYPE_ENUM, step,
+ G_TYPE_INT, count);
+
+ gtk_binding_entry_add_signal (binding_set, keyval, GDK_SHIFT_MASK,
+ "move_cursor", 2,
+ G_TYPE_ENUM, step,
+ G_TYPE_INT, count);
+
+ if ((modmask & GDK_CONTROL_MASK) == GDK_CONTROL_MASK)
+ return;
+
+ gtk_binding_entry_add_signal (binding_set, keyval, GDK_CONTROL_MASK | GDK_SHIFT_MASK,
+ "move_cursor", 2,
+ G_TYPE_ENUM, step,
+ G_TYPE_INT, count);
+
+ gtk_binding_entry_add_signal (binding_set, keyval, GDK_CONTROL_MASK,
+ "move_cursor", 2,
+ G_TYPE_ENUM, step,
+ G_TYPE_INT, count);
+}
+
+
+static void
+gth_icon_view_class_init (GthIconViewClass *klass)
+{
+ GtkBindingSet *binding_set;
+
+ gth_icon_view_parent_class = g_type_class_peek_parent (klass);
+
+ binding_set = gtk_binding_set_by_class (klass);
+
+ gtk_icon_view_add_move_binding (binding_set, GDK_Right, 0,
+ GTK_MOVEMENT_LOGICAL_POSITIONS, 1);
+ gtk_icon_view_add_move_binding (binding_set, GDK_Left, 0,
+ GTK_MOVEMENT_LOGICAL_POSITIONS, -1);
+}
+
+
+static void
+gth_icon_view_init (GthIconView *icon_view)
+{
+ gtk_icon_view_set_spacing (GTK_ICON_VIEW (icon_view), IMAGE_TEXT_SPACING);
+ gtk_icon_view_set_margin (GTK_ICON_VIEW (icon_view), ICON_SPACING);
+ gtk_icon_view_set_column_spacing (GTK_ICON_VIEW (icon_view), ICON_SPACING);
+ gtk_icon_view_set_row_spacing (GTK_ICON_VIEW (icon_view), ICON_SPACING);
+ gtk_icon_view_set_selection_mode (GTK_ICON_VIEW (icon_view), GTK_SELECTION_MULTIPLE);
+}
+
+
+static void
+gth_icon_view_gth_file_view_interface_init (GthFileViewIface *iface)
+{
+ gth_icon_view_gth_file_view_parent_iface = g_type_interface_peek_parent (iface);
+ iface->set_model = gth_icon_view_real_set_model;
+ iface->get_model = gth_icon_view_real_get_model;
+ iface->set_view_mode = gth_icon_view_real_set_view_mode;
+ iface->get_view_mode = gth_icon_view_real_get_view_mode;
+ iface->scroll_to = gth_icon_view_real_scroll_to;
+ iface->get_visibility = gth_icon_view_real_get_visibility;
+ iface->get_at_position = gth_icon_view_real_get_at_position;
+ iface->get_first_visible = gth_icon_view_real_get_first_visible;
+ iface->get_last_visible = gth_icon_view_real_get_last_visible;
+ iface->activated = gth_icon_view_real_activated;
+ iface->set_cursor = gth_icon_view_real_set_cursor;
+ iface->get_cursor = gth_icon_view_real_get_cursor;
+ iface->set_reorderable = gth_icon_view_real_set_reorderable;
+ iface->get_reorderable = gth_icon_view_real_get_reorderable;
+}
+
+
+static void
+gth_icon_view_gth_file_selection_interface_init (GthFileSelectionIface *iface)
+{
+ gth_icon_view_gth_file_selection_parent_iface = g_type_interface_peek_parent (iface);
+ iface->get_selected = gth_icon_view_real_get_selected;
+ iface->select = gth_icon_view_real_select;
+ iface->unselect = gth_icon_view_real_unselect;
+ iface->select_all = gth_icon_view_real_select_all;
+ iface->unselect_all = gth_icon_view_real_unselect_all;
+ iface->is_selected = gth_icon_view_real_is_selected;
+ iface->get_first_selected = gth_icon_view_real_get_first_selected;
+ iface->get_last_selected = gth_icon_view_real_get_last_selected;
+ iface->get_n_selected = gth_icon_view_real_get_n_selected;
+}
+
+
+GType
+gth_icon_view_get_type (void)
+{
+ static GType type = 0;
+
+ if (type == 0) {
+ static const GTypeInfo g_define_type_info = {
+ sizeof (GthIconViewClass),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) gth_icon_view_class_init,
+ (GClassFinalizeFunc) NULL,
+ NULL,
+ sizeof (GthIconView),
+ 0,
+ (GInstanceInitFunc) gth_icon_view_init,
+ NULL
+ };
+ static const GInterfaceInfo gth_file_view_info = {
+ (GInterfaceInitFunc) gth_icon_view_gth_file_view_interface_init,
+ (GInterfaceFinalizeFunc) NULL,
+ NULL
+ };
+ static const GInterfaceInfo gth_file_selection_info = {
+ (GInterfaceInitFunc) gth_icon_view_gth_file_selection_interface_init,
+ (GInterfaceFinalizeFunc) NULL,
+ NULL
+ };
+ type = g_type_register_static (GTK_TYPE_ICON_VIEW,
+ "GthIconView",
+ &g_define_type_info,
+ 0);
+ g_type_add_interface_static (type, GTH_TYPE_FILE_VIEW, >h_file_view_info);
+ g_type_add_interface_static (type, GTH_TYPE_FILE_SELECTION, >h_file_selection_info);
+ }
+
+ return type;
+}
diff --git a/gthumb/gth-icon-view.h b/gthumb/gth-icon-view.h
new file mode 100644
index 0000000..f00bb07
--- /dev/null
+++ b/gthumb/gth-icon-view.h
@@ -0,0 +1,60 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2008 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GTH_ICON_VIEW_H
+#define GTH_ICON_VIEW_H
+
+#include <glib.h>
+#include <glib-object.h>
+#include <gtk/gtk.h>
+#include "gth-file-view.h"
+#include "gth-file-selection.h"
+
+G_BEGIN_DECLS
+
+#define GTH_TYPE_ICON_VIEW (gth_icon_view_get_type ())
+#define GTH_ICON_VIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTH_TYPE_ICON_VIEW, GthIconView))
+#define GTH_ICON_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTH_TYPE_ICON_VIEW, GthIconViewClass))
+#define GTH_IS_ICON_VIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTH_TYPE_ICON_VIEW))
+#define GTH_IS_ICON_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTH_TYPE_ICON_VIEW))
+#define GTH_ICON_VIEW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTH_TYPE_ICON_VIEW, GthIconViewClass))
+
+typedef struct _GthIconView GthIconView;
+typedef struct _GthIconViewClass GthIconViewClass;
+typedef struct _GthIconViewPrivate GthIconViewPrivate;
+
+struct _GthIconView {
+ GtkIconView parent_instance;
+ GthIconViewPrivate *priv;
+};
+
+struct _GthIconViewClass {
+ GtkIconViewClass parent_class;
+};
+
+GType gth_icon_view_get_type (void);
+GtkWidget * gth_icon_view_new (void);
+GtkWidget * gth_icon_view_new_with_model (GtkTreeModel *model);
+
+G_END_DECLS
+
+#endif /* GTH_ICON_VIEW_H */
diff --git a/gthumb/gth-image-dragger.c b/gthumb/gth-image-dragger.c
new file mode 100644
index 0000000..c5c2836
--- /dev/null
+++ b/gthumb/gth-image-dragger.c
@@ -0,0 +1,303 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <math.h>
+#include "glib-utils.h"
+#include "gth-cursors.h"
+#include "gth-image-dragger.h"
+
+
+struct _GthImageDraggerPrivate {
+ GthImageViewer *viewer;
+ gboolean draggable;
+};
+
+
+static gpointer parent_class = NULL;
+
+
+static void
+gth_image_dragger_finalize (GObject *object)
+{
+ g_return_if_fail (object != NULL);
+ g_return_if_fail (GTH_IS_IMAGE_DRAGGER (object));
+
+ /* Chain up */
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+
+static void
+gth_image_dragger_class_init (GthImageDraggerClass *class)
+{
+ GObjectClass *gobject_class;
+
+ parent_class = g_type_class_peek_parent (class);
+ g_type_class_add_private (class, sizeof (GthImageDraggerPrivate));
+
+ gobject_class = (GObjectClass*) class;
+ gobject_class->finalize = gth_image_dragger_finalize;
+}
+
+
+static void
+gth_image_dragger_instance_init (GthImageDragger *dragger)
+{
+ dragger->priv = G_TYPE_INSTANCE_GET_PRIVATE (dragger, GTH_TYPE_IMAGE_DRAGGER, GthImageDraggerPrivate);
+}
+
+
+static void
+gth_image_dragger_realize (GthImageTool *base)
+{
+ /* void */
+}
+
+
+static void
+gth_image_dragger_unrealize (GthImageTool *base)
+{
+ /* void */
+}
+
+
+static void
+gth_image_dragger_size_allocate (GthImageTool *self,
+ GtkAllocation *allocation)
+{
+ GthImageDragger *dragger;
+ GthImageViewer *viewer;
+ GdkCursor *cursor;
+
+ dragger = (GthImageDragger *) self;
+ viewer = (GthImageViewer *) dragger->priv->viewer;
+
+ dragger->priv->draggable = (viewer->hadj->upper > viewer->hadj->page_size) || (viewer->vadj->upper > viewer->vadj->page_size);
+ if (dragger->priv->draggable)
+ cursor = gth_cursor_get (GTK_WIDGET (viewer)->window, GTH_CURSOR_HAND_OPEN);
+ else
+ cursor = gdk_cursor_new (GDK_LEFT_PTR);
+ gth_image_viewer_set_cursor (viewer, cursor);
+
+ gdk_cursor_unref (cursor);
+}
+
+
+static void
+gth_image_dragger_expose (GthImageTool *self,
+ GdkRectangle *event_area)
+{
+ GthImageDragger *dragger;
+ GthImageViewer *viewer;
+ GdkInterpType interp_type;
+ GdkRectangle paint_area;
+
+ dragger = (GthImageDragger *) self;
+ viewer = dragger->priv->viewer;
+
+ if (gth_image_viewer_get_current_pixbuf (viewer) == NULL)
+ return;
+
+ if (gth_image_viewer_get_zoom_quality (viewer) == GTH_ZOOM_QUALITY_LOW)
+ interp_type = GDK_INTERP_NEAREST;
+ else
+ interp_type = GDK_INTERP_BILINEAR;
+
+ if (FLOAT_EQUAL (gth_image_viewer_get_zoom (viewer), 1.0))
+ interp_type = GDK_INTERP_NEAREST;
+
+ if (gdk_rectangle_intersect (&viewer->image_area, event_area, &paint_area))
+ gth_image_viewer_paint (viewer,
+ gth_image_viewer_get_current_pixbuf (viewer),
+ viewer->x_offset + paint_area.x - viewer->image_area.x,
+ viewer->y_offset + paint_area.y - viewer->image_area.y,
+ paint_area.x,
+ paint_area.y,
+ paint_area.width,
+ paint_area.height,
+ interp_type);
+}
+
+
+static gboolean
+gth_image_dragger_button_press (GthImageTool *self,
+ GdkEventButton *event)
+{
+ GthImageDragger *dragger;
+ GthImageViewer *viewer;
+ GtkWidget *widget;
+
+ dragger = (GthImageDragger *) self;
+ viewer = dragger->priv->viewer;
+ widget = GTK_WIDGET (viewer);
+
+ if (! dragger->priv->draggable)
+ return FALSE;
+
+ if ((event->button == 1) && ! viewer->dragging) {
+ GdkCursor *cursor;
+ int retval;
+
+ cursor = gth_cursor_get (widget->window, GTH_CURSOR_HAND_CLOSED);
+ retval = gdk_pointer_grab (widget->window,
+ FALSE,
+ (GDK_POINTER_MOTION_MASK
+ | GDK_POINTER_MOTION_HINT_MASK
+ | GDK_BUTTON_RELEASE_MASK),
+ NULL,
+ cursor,
+ event->time);
+ gdk_cursor_unref (cursor);
+
+ if (retval != 0)
+ return FALSE;
+
+ viewer->pressed = TRUE;
+ viewer->dragging = TRUE;
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+
+static gboolean
+gth_image_dragger_button_release (GthImageTool *self,
+ GdkEventButton *event)
+{
+ GthImageDragger *dragger;
+ GthImageViewer *viewer;
+
+ if (event->button != 1)
+ return FALSE;
+
+ dragger = (GthImageDragger *) self;
+ viewer = dragger->priv->viewer;
+
+ if (viewer->dragging)
+ gdk_pointer_ungrab (event->time);
+
+ return TRUE;
+}
+
+
+static gboolean
+gth_image_dragger_motion_notify (GthImageTool *self,
+ GdkEventMotion *event)
+{
+ GthImageDragger *dragger;
+ GthImageViewer *viewer;
+
+ dragger = (GthImageDragger *) self;
+ viewer = dragger->priv->viewer;
+
+ if (! viewer->pressed)
+ return FALSE;
+
+ viewer->dragging = TRUE;
+
+ if (! event->is_hint)
+ return FALSE;
+
+ gth_image_viewer_scroll_to (viewer,
+ viewer->x_offset + viewer->event_x_prev - event->x,
+ viewer->y_offset + viewer->event_y_prev - event->y);
+
+ return TRUE;
+}
+
+
+static void
+gth_image_dragger_image_changed (GthImageTool *self)
+{
+ /* void */
+}
+
+
+static void
+gth_image_dragger_zoom_changed (GthImageTool *self)
+{
+ /* void */
+}
+
+
+static void
+gth_image_dragger_gth_image_tool_interface_init (GthImageToolIface *iface)
+{
+ iface->realize = gth_image_dragger_realize;
+ iface->unrealize = gth_image_dragger_unrealize;
+ iface->size_allocate = gth_image_dragger_size_allocate;
+ iface->expose = gth_image_dragger_expose;
+ iface->button_press = gth_image_dragger_button_press;
+ iface->button_release = gth_image_dragger_button_release;
+ iface->motion_notify = gth_image_dragger_motion_notify;
+ iface->image_changed = gth_image_dragger_image_changed;
+ iface->zoom_changed = gth_image_dragger_zoom_changed;
+}
+
+
+GType
+gth_image_dragger_get_type (void)
+{
+ static GType type = 0;
+
+ if (! type) {
+ GTypeInfo type_info = {
+ sizeof (GthImageDraggerClass),
+ NULL,
+ NULL,
+ (GClassInitFunc) gth_image_dragger_class_init,
+ NULL,
+ NULL,
+ sizeof (GthImageDragger),
+ 0,
+ (GInstanceInitFunc) gth_image_dragger_instance_init
+ };
+ static const GInterfaceInfo gth_image_tool_info = {
+ (GInterfaceInitFunc) gth_image_dragger_gth_image_tool_interface_init,
+ (GInterfaceFinalizeFunc) NULL,
+ NULL
+ };
+
+ type = g_type_register_static (G_TYPE_OBJECT,
+ "GthImageDragger",
+ &type_info,
+ 0);
+ g_type_add_interface_static (type, GTH_TYPE_IMAGE_TOOL, >h_image_tool_info);
+ }
+
+ return type;
+}
+
+
+GthImageTool *
+gth_image_dragger_new (GthImageViewer *viewer)
+{
+ GthImageDragger *dragger;
+
+ dragger = g_object_new (GTH_TYPE_IMAGE_DRAGGER, NULL);
+ dragger->priv->viewer = viewer;
+
+ return GTH_IMAGE_TOOL (dragger);
+}
diff --git a/gthumb/gth-image-dragger.h b/gthumb/gth-image-dragger.h
new file mode 100644
index 0000000..65e14e0
--- /dev/null
+++ b/gthumb/gth-image-dragger.h
@@ -0,0 +1,60 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GTH_IMAGE_DRAGGER_H
+#define GTH_IMAGE_DRAGGER_H
+
+#include <glib.h>
+#include <glib-object.h>
+#include "gth-image-tool.h"
+#include "gth-image-viewer.h"
+
+G_BEGIN_DECLS
+
+#define GTH_TYPE_IMAGE_DRAGGER (gth_image_dragger_get_type ())
+#define GTH_IMAGE_DRAGGER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTH_TYPE_IMAGE_DRAGGER, GthImageDragger))
+#define GTH_IMAGE_DRAGGER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTH_TYPE_IMAGE_DRAGGER, GthImageDraggerClass))
+#define GTH_IS_IMAGE_DRAGGER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTH_TYPE_IMAGE_DRAGGER))
+#define GTH_IS_IMAGE_DRAGGER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTH_TYPE_IMAGE_DRAGGER))
+#define GTH_IMAGE_DRAGGER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GTH_TYPE_IMAGE_DRAGGER, GthImageDraggerClass))
+
+typedef struct _GthImageDragger GthImageDragger;
+typedef struct _GthImageDraggerClass GthImageDraggerClass;
+typedef struct _GthImageDraggerPrivate GthImageDraggerPrivate;
+
+struct _GthImageDragger
+{
+ GObject __parent;
+ GthImageDraggerPrivate *priv;
+};
+
+struct _GthImageDraggerClass
+{
+ GObjectClass __parent_class;
+};
+
+GType gth_image_dragger_get_type (void);
+GthImageTool * gth_image_dragger_new (GthImageViewer *viewer);
+
+G_END_DECLS
+
+#endif /* GTH_IMAGE_DRAGGER_H */
diff --git a/gthumb/gth-image-history.c b/gthumb/gth-image-history.c
new file mode 100644
index 0000000..50b519e
--- /dev/null
+++ b/gthumb/gth-image-history.c
@@ -0,0 +1,324 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2006-2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include "gth-image-history.h"
+
+
+#define MAX_UNDO_HISTORY_LEN 5
+
+
+/* GthImageData */
+
+
+GthImageData *
+gth_image_data_new (GdkPixbuf *image,
+ gboolean unsaved)
+{
+ GthImageData *idata;
+
+ g_return_val_if_fail (image != NULL, NULL);
+
+ idata = g_new0 (GthImageData, 1);
+
+ idata->ref = 1;
+ g_object_ref (image);
+ idata->image = image;
+ idata->unsaved = unsaved;
+
+ return idata;
+}
+
+
+void
+gth_image_data_ref (GthImageData *idata)
+{
+ g_return_if_fail (idata != NULL);
+ idata->ref++;
+}
+
+
+void
+gth_image_data_unref (GthImageData *idata)
+{
+ g_return_if_fail (idata != NULL);
+
+ idata->ref--;
+ if (idata->ref == 0) {
+ g_object_unref (idata->image);
+ g_free (idata);
+ }
+}
+
+
+void
+gth_image_data_list_free (GList *list)
+{
+ if (list == NULL)
+ return;
+ g_list_foreach (list, (GFunc) gth_image_data_unref, NULL);
+ g_list_free (list);
+}
+
+
+/* GthImageHistory */
+
+
+struct _GthImageHistoryPrivate {
+ GList *undo_history; /* GthImageData items */
+ GList *redo_history; /* GthImageData items */
+};
+
+
+enum {
+ CHANGED,
+ LAST_SIGNAL
+};
+
+
+static gpointer parent_class = NULL;
+static guint gth_image_history_signals[LAST_SIGNAL] = { 0 };
+
+
+static void
+gth_image_history_finalize (GObject *object)
+{
+ GthImageHistory *history;
+
+ g_return_if_fail (GTH_IS_IMAGE_HISTORY (object));
+ history = GTH_IMAGE_HISTORY (object);
+
+ if (history->priv != NULL) {
+ gth_image_history_clear (history);
+ g_free (history->priv);
+ history->priv = NULL;
+ }
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+
+static void
+gth_image_history_class_init (GthImageHistoryClass *class)
+{
+ GObjectClass *object_class;
+
+ parent_class = g_type_class_peek_parent (class);
+
+ gth_image_history_signals[CHANGED] =
+ g_signal_new ("changed",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GthImageHistoryClass, changed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->finalize = gth_image_history_finalize;
+}
+
+
+static void
+gth_image_history_init (GthImageHistory *history)
+{
+ history->priv = g_new0 (GthImageHistoryPrivate, 1);
+}
+
+
+GType
+gth_image_history_get_type (void)
+{
+ static GType type = 0;
+
+ if (! type) {
+ GTypeInfo type_info = {
+ sizeof (GthImageHistoryClass),
+ NULL,
+ NULL,
+ (GClassInitFunc) gth_image_history_class_init,
+ NULL,
+ NULL,
+ sizeof (GthImageHistory),
+ 0,
+ (GInstanceInitFunc) gth_image_history_init
+ };
+
+ type = g_type_register_static (G_TYPE_OBJECT,
+ "GthImageHistory",
+ &type_info,
+ 0);
+ }
+
+ return type;
+}
+
+
+GthImageHistory *
+gth_image_history_new (void)
+{
+ return (GthImageHistory *) g_object_new (GTH_TYPE_IMAGE_HISTORY, NULL);
+}
+
+
+static GthImageData*
+remove_first_image (GList **list)
+{
+ GList *head;
+ GthImageData *idata;
+
+ if (*list == NULL)
+ return NULL;
+
+ head = *list;
+ *list = g_list_remove_link (*list, head);
+ idata = head->data;
+ g_list_free (head);
+
+ return idata;
+}
+
+
+static GList*
+add_image_to_list (GList *list,
+ GdkPixbuf *pixbuf,
+ gboolean unsaved)
+{
+ if (g_list_length (list) > MAX_UNDO_HISTORY_LEN) {
+ GList *last;
+
+ last = g_list_nth (list, MAX_UNDO_HISTORY_LEN - 1);
+ if (last->prev != NULL) {
+ last->prev->next = NULL;
+ gth_image_data_list_free (last);
+ }
+ }
+
+ if (pixbuf == NULL)
+ return list;
+
+ return g_list_prepend (list, gth_image_data_new (pixbuf, unsaved));
+}
+
+
+static void
+add_image_to_undo_history (GthImageHistory *history,
+ GdkPixbuf *pixbuf,
+ gboolean unsaved)
+{
+ history->priv->undo_history = add_image_to_list (history->priv->undo_history,
+ pixbuf,
+ unsaved);
+}
+
+
+static void
+add_image_to_redo_history (GthImageHistory *history,
+ GdkPixbuf *pixbuf,
+ gboolean unsaved)
+{
+ history->priv->redo_history = add_image_to_list (history->priv->redo_history,
+ pixbuf,
+ unsaved);
+}
+
+
+void
+gth_image_history_add_image (GthImageHistory *history,
+ GdkPixbuf *image,
+ gboolean unsaved)
+{
+ add_image_to_undo_history (history, image, unsaved);
+ gth_image_data_list_free (history->priv->redo_history);
+ history->priv->redo_history = NULL;
+
+ g_signal_emit (G_OBJECT (history),
+ gth_image_history_signals[CHANGED],
+ 0);
+}
+
+
+GthImageData *
+gth_image_history_undo (GthImageHistory *history,
+ GdkPixbuf *current_image,
+ gboolean image_is_unsaved)
+{
+ GthImageData *idata;
+
+ if (history->priv->undo_history == NULL)
+ return NULL;
+
+ add_image_to_redo_history (history, current_image, image_is_unsaved);
+ idata = remove_first_image (&(history->priv->undo_history));
+
+ g_signal_emit (G_OBJECT (history),
+ gth_image_history_signals[CHANGED],
+ 0);
+
+ return idata;
+}
+
+
+GthImageData *
+gth_image_history_redo (GthImageHistory *history,
+ GdkPixbuf *current_image,
+ gboolean image_is_unsaved)
+{
+ GthImageData *idata;
+
+ if (history->priv->redo_history == NULL)
+ return NULL;
+
+ add_image_to_undo_history (history, current_image, image_is_unsaved);
+ idata = remove_first_image (&(history->priv->redo_history));
+
+ g_signal_emit (G_OBJECT (history),
+ gth_image_history_signals[CHANGED],
+ 0);
+
+ return idata;
+}
+
+
+void
+gth_image_history_clear (GthImageHistory *history)
+{
+ gth_image_data_list_free (history->priv->undo_history);
+ history->priv->undo_history = NULL;
+
+ gth_image_data_list_free (history->priv->redo_history);
+ history->priv->redo_history = NULL;
+}
+
+
+gboolean
+gth_image_history_can_undo (GthImageHistory *history)
+{
+ return history->priv->undo_history != NULL;
+}
+
+
+gboolean
+gth_image_history_can_redo (GthImageHistory *history)
+{
+ return history->priv->redo_history != NULL;
+}
diff --git a/gthumb/gth-image-history.h b/gthumb/gth-image-history.h
new file mode 100644
index 0000000..8c8dbc3
--- /dev/null
+++ b/gthumb/gth-image-history.h
@@ -0,0 +1,85 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2006-2009 The Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GTH_IMAGE_HISTORY_H
+#define GTH_IMAGE_HISTORY_H
+
+#include <glib.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+G_BEGIN_DECLS
+
+#define GTH_TYPE_IMAGE_HISTORY (gth_image_history_get_type ())
+#define GTH_IMAGE_HISTORY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTH_TYPE_IMAGE_HISTORY, GthImageHistory))
+#define GTH_IMAGE_HISTORY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTH_TYPE_IMAGE_HISTORY, GthImageHistoryClass))
+#define GTH_IS_IMAGE_HISTORY(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTH_TYPE_IMAGE_HISTORY))
+#define GTH_IS_IMAGE_HISTORY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTH_TYPE_IMAGE_HISTORY))
+#define GTH_IMAGE_HISTORY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GTH_TYPE_IMAGE_HISTORY, GthImageHistoryClass))
+
+typedef struct _GthImageHistory GthImageHistory;
+typedef struct _GthImageHistoryPrivate GthImageHistoryPrivate;
+typedef struct _GthImageHistoryClass GthImageHistoryClass;
+
+typedef struct {
+ int ref;
+ GdkPixbuf *image;
+ gboolean unsaved;
+} GthImageData;
+
+struct _GthImageHistory {
+ GObject __parent;
+ GthImageHistoryPrivate *priv;
+};
+
+struct _GthImageHistoryClass {
+ GObjectClass __parent;
+
+ /* -- signals -- */
+
+ void (*changed) (GthImageHistory *image_history);
+};
+
+GthImageData * gth_image_data_new (GdkPixbuf *image,
+ gboolean unsaved);
+void gth_image_data_ref (GthImageData *idata);
+void gth_image_data_unref (GthImageData *idata);
+void gth_image_data_list_free (GList *list);
+
+GType gth_image_history_get_type (void);
+GthImageHistory * gth_image_history_new (void);
+void gth_image_history_add_image (GthImageHistory *history,
+ GdkPixbuf *image,
+ gboolean unsaved);
+GthImageData * gth_image_history_undo (GthImageHistory *history,
+ GdkPixbuf *current_image,
+ gboolean image_is_unsaved);
+GthImageData * gth_image_history_redo (GthImageHistory *history,
+ GdkPixbuf *current_image,
+ gboolean image_is_unsaved);
+void gth_image_history_clear (GthImageHistory *history);
+gboolean gth_image_history_can_undo (GthImageHistory *history);
+gboolean gth_image_history_can_redo (GthImageHistory *history);
+
+G_END_DECLS
+
+#endif /* GTH_IMAGE_HISTORY_H */
+
diff --git a/gthumb/gth-image-loader.c b/gthumb/gth-image-loader.c
new file mode 100644
index 0000000..0f9ffdd
--- /dev/null
+++ b/gthumb/gth-image-loader.c
@@ -0,0 +1,912 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2001-2008 The Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+/*
+#include <errno.h>
+#include <fcntl.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+*/
+#define GDK_PIXBUF_ENABLE_BACKEND
+#include <gdk-pixbuf/gdk-pixbuf-animation.h>
+#include <gtk/gtk.h>
+#include "file-cache.h"
+#include "gth-file-data.h"
+#include "glib-utils.h"
+#include "gth-image-loader.h"
+#include "gth-main.h"
+#include "gthumb-error.h"
+
+/*
+#include "file-utils.h"
+#include "glib-utils.h"
+#include "gconf-utils.h"
+#include "gth-exif-utils.h"
+#include "pixbuf-utils.h"
+#include "preferences.h"
+*/
+
+#define REFRESH_RATE 5
+#define GTH_IMAGE_LOADER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GTH_TYPE_IMAGE_LOADER, GthImageLoaderPrivate))
+G_LOCK_DEFINE_STATIC (pixbuf_loader_lock);
+
+
+struct _GthImageLoaderPrivate {
+ GthFileData *file;
+ GdkPixbuf *pixbuf;
+ GdkPixbufAnimation *animation;
+
+ gboolean as_animation; /* Whether to load the image in a
+ * GdkPixbufAnimation structure. */
+ gboolean ready;
+ GError *error;
+ gboolean loader_ready;
+ gboolean cancelled;
+ gboolean loading;
+ gboolean emit_signal;
+
+ DoneFunc done_func;
+ gpointer done_func_data;
+
+ guint check_id;
+ guint idle_id;
+
+ GThread *thread;
+
+ GMutex *data_mutex;
+
+ gboolean exit_thread;
+ GMutex *exit_thread_mutex;
+
+ gboolean start_loading;
+ GMutex *start_loading_mutex;
+ GCond *start_loading_cond;
+
+ LoaderFunc loader;
+ gpointer loader_data;
+};
+
+
+enum {
+ READY,
+ LAST_SIGNAL
+};
+
+
+static gpointer parent_class = NULL;
+static guint gth_image_loader_signals[LAST_SIGNAL] = { 0 };
+
+
+static void
+gth_image_loader_finalize__step2 (GObject *object)
+{
+ GthImageLoader *iloader;
+
+ iloader = GTH_IMAGE_LOADER (object);
+
+ g_mutex_lock (iloader->priv->data_mutex);
+ if (iloader->priv->file != NULL) {
+ g_object_unref (iloader->priv->file);
+ iloader->priv->file = NULL;
+ }
+ if (iloader->priv->pixbuf != NULL) {
+ g_object_unref (G_OBJECT (iloader->priv->pixbuf));
+ iloader->priv->pixbuf = NULL;
+ }
+
+ if (iloader->priv->animation != NULL) {
+ g_object_unref (G_OBJECT (iloader->priv->animation));
+ iloader->priv->animation = NULL;
+ }
+ g_mutex_unlock (iloader->priv->data_mutex);
+
+ g_mutex_lock (iloader->priv->exit_thread_mutex);
+ iloader->priv->exit_thread = TRUE;
+ g_mutex_unlock (iloader->priv->exit_thread_mutex);
+
+ g_mutex_lock (iloader->priv->start_loading_mutex);
+ iloader->priv->start_loading = TRUE;
+ g_cond_signal (iloader->priv->start_loading_cond);
+ g_mutex_unlock (iloader->priv->start_loading_mutex);
+
+ g_thread_join (iloader->priv->thread);
+
+ g_cond_free (iloader->priv->start_loading_cond);
+ g_mutex_free (iloader->priv->data_mutex);
+ g_mutex_free (iloader->priv->start_loading_mutex);
+ g_mutex_free (iloader->priv->exit_thread_mutex);
+
+ /* Chain up */
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+
+static void _gth_image_loader_stop (GthImageLoader *iloader,
+ DoneFunc done_func,
+ gpointer done_func_data,
+ gboolean emit_sig,
+ gboolean use_idle_cb);
+
+
+static void
+gth_image_loader_finalize (GObject *object)
+{
+ GthImageLoader *iloader;
+
+ g_return_if_fail (object != NULL);
+ g_return_if_fail (GTH_IS_IMAGE_LOADER (object));
+
+ iloader = GTH_IMAGE_LOADER (object);
+
+ if (iloader->priv->idle_id != 0) {
+ g_source_remove (iloader->priv->idle_id);
+ iloader->priv->idle_id = 0;
+ }
+
+ if (iloader->priv->check_id != 0) {
+ g_source_remove (iloader->priv->check_id);
+ iloader->priv->check_id = 0;
+ }
+
+ _gth_image_loader_stop (iloader,
+ (DoneFunc) gth_image_loader_finalize__step2,
+ object,
+ FALSE,
+ FALSE);
+}
+
+
+static void
+gth_image_loader_class_init (GthImageLoaderClass *class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+ parent_class = g_type_class_peek_parent (class);
+ g_type_class_add_private (class, sizeof (GthImageLoaderPrivate));
+
+ object_class->finalize = gth_image_loader_finalize;
+
+ gth_image_loader_signals[READY] =
+ g_signal_new ("ready",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GthImageLoaderClass, ready),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__POINTER,
+ G_TYPE_NONE,
+ 1,
+ G_TYPE_POINTER);
+}
+
+
+static void *
+load_image_thread (void *thread_data)
+{
+ GthImageLoader *iloader = thread_data;
+ GdkPixbufAnimation *animation;
+ GError *error;
+
+ for (;;) {
+ GthFileData *file;
+ gboolean exit_thread;
+
+ g_mutex_lock (iloader->priv->start_loading_mutex);
+ while (! iloader->priv->start_loading)
+ g_cond_wait (iloader->priv->start_loading_cond, iloader->priv->start_loading_mutex);
+ iloader->priv->start_loading = FALSE;
+ g_mutex_unlock (iloader->priv->start_loading_mutex);
+
+ g_mutex_lock (iloader->priv->exit_thread_mutex);
+ exit_thread = iloader->priv->exit_thread;
+ g_mutex_unlock (iloader->priv->exit_thread_mutex);
+
+ if (exit_thread)
+ break;
+
+ file = gth_image_loader_get_file (iloader);
+
+ G_LOCK (pixbuf_loader_lock);
+
+ animation = NULL;
+ if (file != NULL) {
+ error = NULL;
+ if (iloader->priv->loader != NULL) {
+ animation = (*iloader->priv->loader) (file, &error, iloader->priv->loader_data);
+ }
+ else {
+ FileLoader loader;
+
+ loader = gth_main_get_file_loader (gth_file_data_get_mime_type (file));
+ animation = loader (file, &error, -1, -1);
+ }
+ }
+
+ G_UNLOCK (pixbuf_loader_lock);
+
+ g_mutex_lock (iloader->priv->data_mutex);
+
+ iloader->priv->loader_ready = TRUE;
+ if (iloader->priv->animation != NULL)
+ g_object_unref (iloader->priv->animation);
+ iloader->priv->animation = animation;
+ if ((animation == NULL) || (error != NULL)) {
+ iloader->priv->error = error;
+ iloader->priv->ready = FALSE;
+ }
+ else {
+ iloader->priv->error = NULL;
+ iloader->priv->ready = TRUE;
+ }
+
+ g_mutex_unlock (iloader->priv->data_mutex);
+
+ g_object_unref (file);
+ }
+
+ return NULL;
+}
+
+
+static void
+gth_image_loader_init (GthImageLoader *iloader)
+{
+ iloader->priv = GTH_IMAGE_LOADER_GET_PRIVATE (iloader);
+
+ iloader->priv->pixbuf = NULL;
+ iloader->priv->animation = NULL;
+ iloader->priv->file = NULL;
+
+ iloader->priv->ready = FALSE;
+ iloader->priv->error = NULL;
+ iloader->priv->loader_ready = FALSE;
+ iloader->priv->cancelled = FALSE;
+
+ iloader->priv->done_func = NULL;
+ iloader->priv->done_func_data = NULL;
+
+ iloader->priv->check_id = 0;
+ iloader->priv->idle_id = 0;
+
+ iloader->priv->data_mutex = g_mutex_new ();
+
+ iloader->priv->exit_thread = FALSE;
+ iloader->priv->exit_thread_mutex = g_mutex_new ();
+
+ iloader->priv->start_loading = FALSE;
+ iloader->priv->start_loading_mutex = g_mutex_new ();
+ iloader->priv->start_loading_cond = g_cond_new ();
+
+ iloader->priv->loader = NULL;
+ iloader->priv->loader_data = NULL;
+
+ /*iloader->priv->thread = g_thread_create (load_image_thread, iloader, TRUE, NULL);*/
+
+ /* The g_thread_create function assigns a very large default stacksize for each
+ thread (10 MB on FC6), which is probably excessive. 16k seems to be
+ sufficient. To be conversative, we'll try 32k. Use g_thread_create_full to
+ manually specify a small stack size. See Bug 310749 - Memory usage.
+ This reduces the virtual memory requirements, and the "writeable/private"
+ figure reported by "pmap -d". */
+
+ /* Update: 32k caused crashes with svg images. Boosting to 512k. Bug 410827. */
+
+ iloader->priv->thread = g_thread_create_full (load_image_thread,
+ iloader,
+ (512 * 1024),
+ TRUE,
+ TRUE,
+ G_THREAD_PRIORITY_NORMAL,
+ NULL);
+}
+
+
+GType
+gth_image_loader_get_type (void)
+{
+ static GType type = 0;
+
+ if (! type) {
+ GTypeInfo type_info = {
+ sizeof (GthImageLoaderClass),
+ NULL,
+ NULL,
+ (GClassInitFunc) gth_image_loader_class_init,
+ NULL,
+ NULL,
+ sizeof (GthImageLoader),
+ 0,
+ (GInstanceInitFunc) gth_image_loader_init
+ };
+
+ type = g_type_register_static (G_TYPE_OBJECT,
+ "GthImageLoader",
+ &type_info,
+ 0);
+ }
+
+ return type;
+}
+
+
+GthImageLoader *
+gth_image_loader_new (gboolean as_animation)
+{
+ GthImageLoader *iloader;
+
+ iloader = g_object_new (GTH_TYPE_IMAGE_LOADER, NULL);
+ g_mutex_lock (iloader->priv->data_mutex);
+ iloader->priv->as_animation = as_animation;
+ g_mutex_unlock (iloader->priv->data_mutex);
+
+ return iloader;
+}
+
+
+void
+gth_image_loader_set_loader (GthImageLoader *iloader,
+ LoaderFunc loader,
+ gpointer loader_data)
+{
+ g_return_if_fail (iloader != NULL);
+
+ g_mutex_lock (iloader->priv->data_mutex);
+ iloader->priv->loader = loader;
+ iloader->priv->loader_data = loader_data;
+ g_mutex_unlock (iloader->priv->data_mutex);
+}
+
+
+void
+gth_image_loader_set_file_data (GthImageLoader *iloader,
+ GthFileData *file)
+{
+ g_mutex_lock (iloader->priv->data_mutex);
+ _g_object_unref (iloader->priv->file);
+ iloader->priv->file = NULL;
+ if (file != NULL)
+ iloader->priv->file = gth_file_data_dup (file);
+ g_mutex_unlock (iloader->priv->data_mutex);
+}
+
+
+void
+gth_image_loader_set_file (GthImageLoader *iloader,
+ GFile *file,
+ const char *mime_type)
+{
+ GthFileData *file_data;
+
+ file_data = gth_file_data_new (file, NULL);
+ if (mime_type != NULL)
+ gth_file_data_set_mime_type (file_data, mime_type);
+ else
+ gth_file_data_update_mime_type (file_data, TRUE); /* FIXME: always fast mime type is not good */
+
+ gth_image_loader_set_file_data (iloader, file_data);
+
+ g_object_unref (file_data);
+}
+
+
+GthFileData *
+gth_image_loader_get_file (GthImageLoader *iloader)
+{
+ GthFileData *file = NULL;
+
+ g_mutex_lock (iloader->priv->data_mutex);
+ if (iloader->priv->file != NULL)
+ file = gth_file_data_dup (iloader->priv->file);
+ g_mutex_unlock (iloader->priv->data_mutex);
+
+ return file;
+}
+
+
+void
+gth_image_loader_set_pixbuf (GthImageLoader *iloader,
+ GdkPixbuf *pixbuf)
+{
+ g_return_if_fail (iloader != NULL);
+
+ g_mutex_lock (iloader->priv->data_mutex);
+
+ if (iloader->priv->animation != NULL) {
+ g_object_unref (iloader->priv->animation);
+ iloader->priv->animation = NULL;
+ }
+
+ if (iloader->priv->pixbuf != NULL) {
+ g_object_unref (iloader->priv->pixbuf);
+ iloader->priv->pixbuf = NULL;
+ }
+
+ iloader->priv->pixbuf = pixbuf;
+ if (pixbuf != NULL)
+ g_object_ref (pixbuf);
+
+ g_mutex_unlock (iloader->priv->data_mutex);
+}
+
+
+static void
+_gth_image_loader_sync_pixbuf (GthImageLoader *iloader)
+{
+ GdkPixbuf *pixbuf;
+
+ g_mutex_lock (iloader->priv->data_mutex);
+
+ if (iloader->priv->animation == NULL) {
+ if (iloader->priv->pixbuf != NULL)
+ g_object_unref (iloader->priv->pixbuf);
+ iloader->priv->pixbuf = NULL;
+ g_mutex_unlock (iloader->priv->data_mutex);
+ return;
+ }
+
+ pixbuf = gdk_pixbuf_animation_get_static_image (iloader->priv->animation);
+ g_object_ref (pixbuf);
+
+ if (iloader->priv->pixbuf == pixbuf) {
+ g_object_unref (pixbuf);
+ g_mutex_unlock (iloader->priv->data_mutex);
+ return;
+ }
+
+ if (iloader->priv->pixbuf != NULL) {
+ g_object_unref (iloader->priv->pixbuf);
+ iloader->priv->pixbuf = NULL;
+ }
+
+ if (pixbuf != NULL) {
+ g_object_ref (pixbuf);
+ iloader->priv->pixbuf = pixbuf;
+ }
+
+ g_object_unref (pixbuf);
+
+ g_mutex_unlock (iloader->priv->data_mutex);
+}
+
+
+static void
+_gth_image_loader_sync_from_loader (GthImageLoader *iloader,
+ GdkPixbufLoader *pbloader)
+{
+ GdkPixbuf *pixbuf;
+
+ g_return_if_fail (iloader != NULL);
+
+ g_mutex_lock (iloader->priv->data_mutex);
+
+ if (iloader->priv->as_animation) {
+ if (iloader->priv->animation != NULL)
+ g_object_unref (iloader->priv->animation);
+ iloader->priv->animation = gdk_pixbuf_loader_get_animation (pbloader);
+
+ if ((iloader->priv->animation != NULL)
+ && ! gdk_pixbuf_animation_is_static_image (iloader->priv->animation))
+ {
+ g_object_ref (iloader->priv->animation);
+ g_mutex_unlock (iloader->priv->data_mutex);
+ return;
+ }
+ else
+ iloader->priv->animation = NULL;
+ }
+
+ pixbuf = gdk_pixbuf_loader_get_pixbuf (pbloader);
+ g_object_ref (pixbuf);
+
+ if (iloader->priv->pixbuf == pixbuf) {
+ g_object_unref (iloader->priv->pixbuf);
+ g_mutex_unlock (iloader->priv->data_mutex);
+ return;
+ }
+
+ if (iloader->priv->pixbuf != NULL) {
+ g_object_unref (iloader->priv->pixbuf);
+ iloader->priv->pixbuf = NULL;
+ }
+
+ if (pixbuf != NULL) {
+ g_object_ref (pixbuf);
+ iloader->priv->pixbuf = pixbuf;
+ /*priv->pixbuf = gdk_pixbuf_copy (pixbuf);*/
+ }
+
+ g_object_unref (pixbuf);
+
+ g_mutex_unlock (iloader->priv->data_mutex);
+}
+
+
+static void
+_gth_image_loader_cancelled (GthImageLoader *iloader)
+{
+ g_mutex_lock (iloader->priv->data_mutex);
+ iloader->priv->error = g_error_new_literal (G_IO_ERROR, G_IO_ERROR_CANCELLED, "");
+ g_mutex_unlock (iloader->priv->data_mutex);
+
+ _gth_image_loader_stop (iloader,
+ iloader->priv->done_func,
+ iloader->priv->done_func_data,
+ TRUE,
+ TRUE);
+}
+
+
+static void
+_gth_image_loader_ready (GthImageLoader *iloader)
+{
+ g_mutex_lock (iloader->priv->data_mutex);
+ iloader->priv->error = NULL;
+ g_mutex_unlock (iloader->priv->data_mutex);
+
+ _gth_image_loader_stop (iloader, NULL, NULL, TRUE, TRUE);
+}
+
+
+static void
+_gth_image_loader_error (GthImageLoader *iloader,
+ GError *error)
+{
+ g_mutex_lock (iloader->priv->data_mutex);
+
+ if (iloader->priv->error != error) {
+ if (iloader->priv->error != NULL) {
+ g_error_free (iloader->priv->error);
+ iloader->priv->error = NULL;
+ }
+ iloader->priv->error = error;
+ }
+
+ g_mutex_unlock (iloader->priv->data_mutex);
+
+ _gth_image_loader_stop (iloader, NULL, NULL, TRUE, TRUE);
+}
+
+
+static gboolean
+check_thread (gpointer data)
+{
+ GthImageLoader *iloader = data;
+ gboolean ready, loader_done;
+ GError *error = NULL;
+
+ /* Remove check. */
+
+ g_source_remove (iloader->priv->check_id);
+ iloader->priv->check_id = 0;
+
+ /**/
+
+ g_mutex_lock (iloader->priv->data_mutex);
+
+ ready = iloader->priv->ready;
+ if (iloader->priv->error != NULL)
+ error = g_error_copy (iloader->priv->error);
+ loader_done = iloader->priv->loader_ready;
+
+ g_mutex_unlock (iloader->priv->data_mutex);
+
+ /**/
+
+ if (loader_done && iloader->priv->cancelled)
+ _gth_image_loader_cancelled (iloader);
+
+ else if (loader_done && ready)
+ _gth_image_loader_ready (iloader);
+
+ else if (loader_done && (error != NULL))
+ _gth_image_loader_error (iloader, error);
+
+ else /* Add the check again. */
+ iloader->priv->check_id = g_timeout_add (REFRESH_RATE,
+ check_thread,
+ iloader);
+
+ return FALSE;
+}
+
+
+static void
+_gth_image_loader_load__step3 (GError *error,
+ gpointer data)
+{
+ GthImageLoader *iloader = data;
+
+ g_return_if_fail (iloader != NULL);
+
+ if (error != NULL) {
+ _gth_image_loader_error (iloader, error);
+ return;
+ }
+
+ /* */
+
+ g_mutex_lock (iloader->priv->data_mutex);
+
+ iloader->priv->ready = FALSE;
+ iloader->priv->error = NULL;
+ iloader->priv->loader_ready = FALSE;
+ iloader->priv->loading = TRUE;
+ if (iloader->priv->pixbuf != NULL) {
+ g_object_unref (iloader->priv->pixbuf);
+ iloader->priv->pixbuf = NULL;
+ }
+ if (iloader->priv->animation != NULL) {
+ g_object_unref (iloader->priv->animation);
+ iloader->priv->animation = NULL;
+ }
+
+ g_mutex_unlock (iloader->priv->data_mutex);
+
+ /* */
+
+ g_mutex_lock (iloader->priv->start_loading_mutex);
+
+ iloader->priv->start_loading = TRUE;
+ g_cond_signal (iloader->priv->start_loading_cond);
+
+ g_mutex_unlock (iloader->priv->start_loading_mutex);
+
+ /* */
+
+ iloader->priv->check_id = g_timeout_add (REFRESH_RATE,
+ check_thread,
+ iloader);
+}
+
+
+static void
+_gth_image_loader_load__step2 (GthImageLoader *iloader)
+{
+ GthFileData *file;
+
+ g_mutex_lock (iloader->priv->data_mutex);
+ file = gth_file_data_dup (iloader->priv->file);
+ g_mutex_unlock (iloader->priv->data_mutex);
+
+ copy_remote_file_to_cache (file, NULL, _gth_image_loader_load__step3, iloader);
+ g_object_unref (file);
+}
+
+
+void
+gth_image_loader_load (GthImageLoader *iloader)
+{
+ gboolean no_file = FALSE;
+
+ g_return_if_fail (iloader != NULL);
+
+ g_mutex_lock (iloader->priv->data_mutex);
+ if (iloader->priv->file == NULL)
+ no_file = TRUE;
+ g_mutex_unlock (iloader->priv->data_mutex);
+
+ if (no_file)
+ return;
+
+ _gth_image_loader_stop (iloader,
+ (DoneFunc) _gth_image_loader_load__step2,
+ iloader,
+ FALSE,
+ TRUE);
+}
+
+
+/* -- gth_image_loader_stop -- */
+
+
+static void
+_gth_image_loader_stop__step2 (GthImageLoader *iloader,
+ gboolean use_idle_cb)
+{
+ DoneFunc done_func = iloader->priv->done_func;
+ GError *error;
+
+ g_mutex_lock (iloader->priv->data_mutex);
+ error = iloader->priv->error;
+ iloader->priv->error = NULL;
+ iloader->priv->ready = TRUE;
+ g_mutex_unlock (iloader->priv->data_mutex);
+
+ if ((error == NULL) && ! iloader->priv->cancelled && iloader->priv->loading)
+ _gth_image_loader_sync_pixbuf (iloader);
+ iloader->priv->loading = FALSE;
+
+ iloader->priv->done_func = NULL;
+ if (done_func != NULL) {
+ IdleCall *call;
+
+ call = idle_call_new (done_func, iloader->priv->done_func_data);
+ if (iloader->priv->idle_id != 0)
+ g_source_remove (iloader->priv->idle_id);
+ iloader->priv->idle_id = idle_call_exec (call, use_idle_cb);
+ }
+
+ if (! iloader->priv->emit_signal || iloader->priv->cancelled) {
+ iloader->priv->cancelled = FALSE;
+ return;
+ }
+ iloader->priv->cancelled = FALSE;
+
+ g_signal_emit (G_OBJECT (iloader),
+ gth_image_loader_signals[READY],
+ 0,
+ error);
+}
+
+
+static void
+_gth_image_loader_stop (GthImageLoader *iloader,
+ DoneFunc done_func,
+ gpointer done_func_data,
+ gboolean emit_signal,
+ gboolean use_idle_cb)
+{
+ iloader->priv->done_func = done_func;
+ iloader->priv->done_func_data = done_func_data;
+ iloader->priv->emit_signal = emit_signal;
+
+ _gth_image_loader_stop__step2 (iloader, use_idle_cb);
+}
+
+
+void
+gth_image_loader_cancel (GthImageLoader *iloader,
+ DoneFunc done_func,
+ gpointer done_func_data)
+{
+ g_mutex_lock (iloader->priv->data_mutex);
+ iloader->priv->error = FALSE;
+ g_mutex_unlock (iloader->priv->data_mutex);
+
+ if (iloader->priv->loading) {
+ iloader->priv->emit_signal = TRUE;
+ iloader->priv->cancelled = TRUE;
+ iloader->priv->done_func = done_func;
+ iloader->priv->done_func_data = done_func_data;
+ }
+ else
+ _gth_image_loader_stop (iloader,
+ done_func,
+ done_func_data,
+ FALSE,
+ TRUE);
+}
+
+
+void
+gth_image_loader_cancel_with_error (GthImageLoader *iloader,
+ DoneFunc done_func,
+ gpointer done_func_data)
+{
+ g_mutex_lock (iloader->priv->data_mutex);
+ iloader->priv->error = g_error_new_literal (GTHUMB_ERROR, 0, "cancelled");
+ g_mutex_unlock (iloader->priv->data_mutex);
+
+ _gth_image_loader_stop (iloader, done_func, done_func_data, TRUE, TRUE);
+}
+
+
+GdkPixbuf *
+gth_image_loader_get_pixbuf (GthImageLoader *iloader)
+{
+ return iloader->priv->pixbuf;
+}
+
+
+GdkPixbufAnimation *
+gth_image_loader_get_animation (GthImageLoader *iloader)
+{
+ GdkPixbufAnimation *animation;
+
+ g_mutex_lock (iloader->priv->data_mutex);
+ animation = iloader->priv->animation;
+ if (animation != NULL)
+ g_object_ref (animation);
+ g_mutex_unlock (iloader->priv->data_mutex);
+
+ return animation;
+}
+
+
+gboolean
+gth_image_loader_is_ready (GthImageLoader *iloader)
+{
+ gboolean is_ready;
+
+ g_mutex_lock (iloader->priv->data_mutex);
+ is_ready = iloader->priv->ready && iloader->priv->loader_ready;
+ g_mutex_unlock (iloader->priv->data_mutex);
+
+ return is_ready;
+}
+
+
+void
+gth_image_loader_load_from_pixbuf_loader (GthImageLoader *iloader,
+ GdkPixbufLoader *pixbuf_loader)
+{
+ GError *error = NULL;
+
+ _gth_image_loader_sync_from_loader (iloader, pixbuf_loader);
+
+ g_mutex_lock (iloader->priv->data_mutex);
+ if ((iloader->priv->pixbuf == NULL) && (iloader->priv->animation == NULL))
+ error = g_error_new_literal (GTHUMB_ERROR, 0, "No image available");
+ g_mutex_unlock (iloader->priv->data_mutex);
+
+ g_signal_emit (G_OBJECT (iloader), gth_image_loader_signals[READY], 0, error);
+}
+
+
+void
+gth_image_loader_load_from_image_loader (GthImageLoader *to,
+ GthImageLoader *from)
+{
+ GError *error = NULL;
+
+ g_return_if_fail (to != NULL);
+ g_return_if_fail (from != NULL);
+
+ g_mutex_lock (to->priv->data_mutex);
+ g_mutex_lock (from->priv->data_mutex);
+
+ if (to->priv->file != NULL) {
+ g_object_unref (to->priv->file);
+ to->priv->file = NULL;
+ }
+ if (from->priv->file != NULL)
+ to->priv->file = gth_file_data_dup (from->priv->file);
+
+ if (to->priv->pixbuf) {
+ g_object_unref (to->priv->pixbuf);
+ to->priv->pixbuf = NULL;
+ }
+ if (from->priv->pixbuf) {
+ g_object_ref (from->priv->pixbuf);
+ to->priv->pixbuf = from->priv->pixbuf;
+ }
+
+ /**/
+
+ if (to->priv->animation) {
+ g_object_unref (to->priv->animation);
+ to->priv->animation = NULL;
+ }
+ if (from->priv->animation) { /* FIXME: check thread issues. */
+ g_object_ref (from->priv->animation);
+ to->priv->animation = from->priv->animation;
+ }
+
+ if ((to->priv->pixbuf == NULL) && (to->priv->animation == NULL))
+ error = g_error_new_literal (GTHUMB_ERROR, 0, "No image available");
+
+ g_mutex_unlock (to->priv->data_mutex);
+ g_mutex_unlock (from->priv->data_mutex);
+
+ g_signal_emit (G_OBJECT (to), gth_image_loader_signals[READY], 0, error);
+}
diff --git a/gthumb/gth-image-loader.h b/gthumb/gth-image-loader.h
new file mode 100644
index 0000000..25da34f
--- /dev/null
+++ b/gthumb/gth-image-loader.h
@@ -0,0 +1,92 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2001-2008 The Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GTH_IMAGE_LOADER_H
+#define GTH_IMAGE_LOADER_H
+
+#include <glib.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include "typedefs.h"
+#include "gth-file-data.h"
+
+G_BEGIN_DECLS
+
+#define GTH_TYPE_IMAGE_LOADER (gth_image_loader_get_type ())
+#define GTH_IMAGE_LOADER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTH_TYPE_IMAGE_LOADER, GthImageLoader))
+#define GTH_IMAGE_LOADER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTH_TYPE_IMAGE_LOADER, GthImageLoaderClass))
+#define GTH_IS_IMAGE_LOADER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTH_TYPE_IMAGE_LOADER))
+#define GTH_IS_IMAGE_LOADER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTH_TYPE_IMAGE_LOADER))
+#define GTH_IMAGE_LOADER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GTH_TYPE_IMAGE_LOADER, GthImageLoaderClass))
+
+typedef struct _GthImageLoader GthImageLoader;
+typedef struct _GthImageLoaderClass GthImageLoaderClass;
+typedef struct _GthImageLoaderPrivate GthImageLoaderPrivate;
+
+struct _GthImageLoader
+{
+ GObject __parent;
+ GthImageLoaderPrivate *priv;
+};
+
+struct _GthImageLoaderClass
+{
+ GObjectClass __parent_class;
+
+ /* -- Signals -- */
+
+ void (* ready) (GthImageLoader *iloader,
+ GError *error);
+};
+
+typedef GdkPixbufAnimation * (*LoaderFunc) (GthFileData *file, GError **error, gpointer data);
+
+GType gth_image_loader_get_type (void);
+GthImageLoader * gth_image_loader_new (gboolean as_animation);
+void gth_image_loader_set_loader (GthImageLoader *iloader,
+ LoaderFunc loader,
+ gpointer data);
+void gth_image_loader_set_file_data (GthImageLoader *iloader,
+ GthFileData *file);
+void gth_image_loader_set_file (GthImageLoader *iloader,
+ GFile *file,
+ const char *mime_type);
+GthFileData * gth_image_loader_get_file (GthImageLoader *iloader);
+void gth_image_loader_set_pixbuf (GthImageLoader *iloader,
+ GdkPixbuf *pixbuf);
+GdkPixbuf * gth_image_loader_get_pixbuf (GthImageLoader *iloader);
+GdkPixbufAnimation * gth_image_loader_get_animation (GthImageLoader *iloader);
+gboolean gth_image_loader_is_ready (GthImageLoader *iloader);
+void gth_image_loader_load (GthImageLoader *iloader);
+void gth_image_loader_cancel (GthImageLoader *iloader,
+ DoneFunc done_func,
+ gpointer done_func_data);
+void gth_image_loader_cancel_with_error (GthImageLoader *iloader,
+ DoneFunc done_func,
+ gpointer done_func_data);
+void gth_image_loader_load_from_pixbuf_loader (GthImageLoader *iloader,
+ GdkPixbufLoader *pixbuf_loader);
+void gth_image_loader_load_from_image_loader (GthImageLoader *to,
+ GthImageLoader *from);
+
+G_END_DECLS
+
+#endif /* GTH_IMAGE_LOADER_H */
diff --git a/gthumb/gth-image-preloader.c b/gthumb/gth-image-preloader.c
new file mode 100644
index 0000000..35718da
--- /dev/null
+++ b/gthumb/gth-image-preloader.c
@@ -0,0 +1,631 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2001-2009 The Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+
+#include <config.h>
+#include <string.h>
+#include <glib.h>
+#include "glib-utils.h"
+#include "gth-image-preloader.h"
+
+
+#define GTH_IMAGE_PRELOADER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GTH_TYPE_IMAGE_PRELOADER, GthImagePreloaderPrivate))
+#define NEXT_LOAD_SMALL_TIMEOUT 100
+#define NEXT_LOAD_BIG_TIMEOUT 400
+#define N_LOADERS 3
+
+
+enum {
+ REQUESTED_READY,
+ LAST_SIGNAL
+};
+
+
+typedef struct {
+ GthFileData *file_data;
+ GthImageLoader *loader;
+ gboolean loaded;
+ gboolean error;
+ GthImagePreloader *image_preloader;
+} Preloader;
+
+
+struct _GthImagePreloaderPrivate {
+ Preloader *loader[N_LOADERS]; /* Array of loaders, each loader
+ * will load an image. */
+ int requested; /* This is the loader with the
+ * requested image. The
+ * requested image is the image
+ * the user has expressly
+ * requested to view, when this
+ * image is loaded a
+ * requested_ready signal is
+ * emitted.
+ * Other images do not trigger
+ * any signal. */
+ int current; /* This is the loader that has
+ * a loading underway. */
+ gboolean stopped; /* Whether the preloader has
+ * been stopped. */
+ DoneFunc done_func; /* Function to call after
+ * stopping the loader. */
+ gpointer done_func_data;
+ guint load_id;
+};
+
+
+static gpointer parent_class = NULL;
+static guint gth_image_preloader_signals[LAST_SIGNAL] = { 0 };
+
+
+/* -- Preloader -- */
+
+static void start_next_loader (GthImagePreloader *image_preloader);
+static Preloader * current_preloader (GthImagePreloader *image_preloader);
+static Preloader * requested_preloader (GthImagePreloader *image_preloader);
+
+
+static gboolean
+load_next (gpointer data)
+{
+ GthImagePreloader *image_preloader = data;
+
+ if (image_preloader->priv->load_id != 0) {
+ g_source_remove (image_preloader->priv->load_id);
+ image_preloader->priv->load_id = 0;
+ }
+
+ start_next_loader (image_preloader);
+
+ return FALSE;
+}
+
+
+static void
+image_loader_ready_cb (GthImageLoader *il,
+ GError *error,
+ Preloader *preloader)
+{
+ GthImagePreloader *image_preloader = preloader->image_preloader;
+ int timeout = NEXT_LOAD_SMALL_TIMEOUT;
+
+ preloader->loaded = (error == NULL);
+ preloader->error = (error != NULL);
+
+ if (preloader == requested_preloader (image_preloader)) {
+ g_signal_emit (G_OBJECT (image_preloader),
+ gth_image_preloader_signals[REQUESTED_READY],
+ 0,
+ error);
+ debug (DEBUG_INFO, "[requested] error");
+ timeout = NEXT_LOAD_BIG_TIMEOUT;
+ }
+
+ image_preloader->priv->load_id = g_idle_add (load_next, image_preloader);
+}
+
+
+static Preloader*
+preloader_new (GthImagePreloader *image_preloader)
+{
+ Preloader *preloader;
+
+ preloader = g_new0 (Preloader, 1);
+ preloader->file_data = NULL;
+ preloader->loaded = FALSE;
+ preloader->error = FALSE;
+ preloader->loader = GTH_IMAGE_LOADER (gth_image_loader_new (TRUE));
+ preloader->image_preloader = image_preloader;
+
+ g_signal_connect (G_OBJECT (preloader->loader),
+ "ready",
+ G_CALLBACK (image_loader_ready_cb),
+ preloader);
+
+ return preloader;
+}
+
+
+static void
+preloader_free (Preloader *preloader)
+{
+ if (preloader == NULL)
+ return;
+ _g_object_unref (preloader->loader);
+ _g_object_unref (preloader->file_data);
+ g_free (preloader);
+}
+
+
+static void
+preloader_set_file_data (Preloader *preloader,
+ GthFileData *file_data)
+{
+ g_return_if_fail (preloader != NULL);
+
+ if (preloader->file_data != file_data) {
+ _g_object_unref (preloader->file_data);
+ preloader->file_data = NULL;
+ }
+
+ if (file_data != NULL) {
+ preloader->file_data = g_object_ref (file_data);
+ gth_image_loader_set_file_data (preloader->loader, preloader->file_data);
+ }
+
+ preloader->loaded = FALSE;
+ preloader->error = FALSE;
+}
+
+
+/* -- GthImagePreloader -- */
+
+
+static void
+gth_image_preloader_finalize (GObject *object)
+{
+ GthImagePreloader *image_preloader;
+ int i;
+
+ g_return_if_fail (object != NULL);
+ g_return_if_fail (GTH_IS_IMAGE_PRELOADER (object));
+
+ image_preloader = GTH_IMAGE_PRELOADER (object);
+
+ if (image_preloader->priv->load_id != 0) {
+ g_source_remove (image_preloader->priv->load_id);
+ image_preloader->priv->load_id = 0;
+ }
+
+ for (i = 0; i < N_LOADERS; i++) {
+ preloader_free (image_preloader->priv->loader[i]);
+ image_preloader->priv->loader[i] = NULL;
+ }
+
+ /* Chain up */
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+
+static void
+gth_image_preloader_class_init (GthImagePreloaderClass *class)
+{
+ GObjectClass *object_class;
+
+ parent_class = g_type_class_peek_parent (class);
+ g_type_class_add_private (class, sizeof (GthImagePreloaderPrivate));
+
+ gth_image_preloader_signals[REQUESTED_READY] =
+ g_signal_new ("requested_ready",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GthImagePreloaderClass, requested_ready),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__POINTER,
+ G_TYPE_NONE,
+ 1,
+ G_TYPE_POINTER);
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->finalize = gth_image_preloader_finalize;
+}
+
+
+static void
+gth_image_preloader_init (GthImagePreloader *image_preloader)
+{
+ int i;
+
+ image_preloader->priv = GTH_IMAGE_PRELOADER_GET_PRIVATE (image_preloader);
+
+ for (i = 0; i < N_LOADERS; i++)
+ image_preloader->priv->loader[i] = preloader_new (image_preloader);
+ image_preloader->priv->requested = -1;
+ image_preloader->priv->current = -1;
+ image_preloader->priv->stopped = FALSE;
+}
+
+
+GType
+gth_image_preloader_get_type (void)
+{
+ static GType type = 0;
+
+ if (! type) {
+ GTypeInfo type_info = {
+ sizeof (GthImagePreloaderClass),
+ NULL,
+ NULL,
+ (GClassInitFunc) gth_image_preloader_class_init,
+ NULL,
+ NULL,
+ sizeof (GthImagePreloader),
+ 0,
+ (GInstanceInitFunc) gth_image_preloader_init
+ };
+
+ type = g_type_register_static (G_TYPE_OBJECT,
+ "GthImagePreloader",
+ &type_info,
+ 0);
+ }
+
+ return type;
+}
+
+
+GthImagePreloader *
+gth_image_preloader_new (void)
+{
+ return (GthImagePreloader *) g_object_new (GTH_TYPE_IMAGE_PRELOADER, NULL);
+}
+
+
+static Preloader *
+current_preloader (GthImagePreloader *image_preloader)
+{
+ if (image_preloader->priv->current == -1)
+ return NULL;
+ else
+ return image_preloader->priv->loader[image_preloader->priv->current];
+}
+
+
+static Preloader *
+requested_preloader (GthImagePreloader *image_preloader)
+{
+ if (image_preloader->priv->requested == -1)
+ return NULL;
+ else
+ return image_preloader->priv->loader[image_preloader->priv->requested];
+}
+
+
+/* -- gth_image_preloader_load -- */
+
+
+#define N_ARGS 3
+
+
+typedef struct {
+ GthImagePreloader *image_preloader;
+ GthFileData *requested;
+ GthFileData *next1;
+ GthFileData *prev1;
+} LoadData;
+
+
+static GthFileData *
+check_file (GthFileData *file_data)
+{
+ if (file_data == NULL)
+ return NULL;
+
+ if (! g_file_is_native (file_data->file))
+ return NULL;
+
+ if (! _g_mime_type_is_image (gth_file_data_get_mime_type (file_data)))
+ return NULL;
+
+ return gth_file_data_dup (file_data);
+}
+
+
+static LoadData*
+load_data_new (GthImagePreloader *image_preloader,
+ GthFileData *requested,
+ GthFileData *next1,
+ GthFileData *prev1)
+{
+ LoadData *load_data;
+
+ load_data = g_new0 (LoadData, 1);
+
+ load_data->image_preloader = image_preloader;
+ load_data->requested = check_file (requested);
+ load_data->next1 = check_file (next1);
+ load_data->prev1 = check_file (prev1);
+
+ return load_data;
+}
+
+
+static void
+load_data_free (LoadData *load_data)
+{
+ if (load_data == NULL)
+ return;
+
+ _g_object_unref (load_data->requested);
+ _g_object_unref (load_data->next1);
+ _g_object_unref (load_data->prev1);
+ g_free (load_data);
+}
+
+
+void
+gth_image_preloader_load__step2 (LoadData *load_data)
+{
+ GthImagePreloader *image_preloader = load_data->image_preloader;
+ GthFileData *requested = load_data->requested;
+ GthFileData *next1 = load_data->next1;
+ GthFileData *prev1 = load_data->prev1;
+ GthFileData *files[N_ARGS];
+ gboolean file_assigned[N_LOADERS];
+ gboolean loader_assigned[N_LOADERS];
+ int i, j;
+
+ files[0] = requested;
+ files[1] = next1;
+ files[2] = prev1;
+
+ for (i = 0; i < N_LOADERS; i++) {
+ loader_assigned[i] = FALSE;
+ file_assigned[i] = FALSE;
+ }
+
+ image_preloader->priv->requested = -1;
+
+ for (j = 0; j < N_ARGS; j++) {
+ GthFileData *file_data = files[j];
+
+ if (file_data == NULL)
+ continue;
+
+ /* check whether the image has already been loaded. */
+
+ for (i = 0; i < N_LOADERS; i++) {
+ Preloader *preloader = image_preloader->priv->loader[i];
+
+ if ((preloader->file_data != NULL)
+ && g_file_equal (preloader->file_data->file, file_data->file)
+ && (_g_time_val_cmp (gth_file_data_get_modification_time (file_data),
+ gth_file_data_get_modification_time (preloader->file_data)) == 0)
+ && preloader->loaded)
+ {
+ char *uri;
+
+ loader_assigned[i] = TRUE;
+ file_assigned[j] = TRUE;
+ if ((requested != NULL) && g_file_equal (file_data->file, requested->file)) {
+ image_preloader->priv->requested = i;
+ g_signal_emit (G_OBJECT (image_preloader), gth_image_preloader_signals[REQUESTED_READY], 0, NULL);
+ debug (DEBUG_INFO, "[requested] preloaded");
+ }
+
+ uri = g_file_get_uri (file_data->file);
+ debug (DEBUG_INFO, "[=] [%d] <- %s", i, uri);
+ g_free (uri);
+ }
+ }
+ }
+
+ /* assign remaining paths. */
+
+ for (j = 0; j < N_ARGS; j++) {
+ GthFileData *file_data = files[j];
+ Preloader *preloader;
+ int k;
+ char *uri;
+
+ if (file_data == NULL)
+ continue;
+
+ if (file_assigned[j])
+ continue;
+
+ /* find the first non-assigned loader */
+ for (k = 0; (k < N_LOADERS) && loader_assigned[k]; k++)
+ /* void */;
+
+ g_return_if_fail (k < N_LOADERS);
+
+ preloader = image_preloader->priv->loader[k];
+ loader_assigned[k] = TRUE;
+ preloader_set_file_data (preloader, file_data);
+
+ if ((requested != NULL) && g_file_equal (file_data->file, requested->file)) {
+ image_preloader->priv->requested = k;
+
+ uri = g_file_get_uri (file_data->file);
+ debug (DEBUG_INFO, "[requested] %s", uri);
+ g_free (uri);
+ }
+
+ uri = g_file_get_uri (file_data->file);
+ debug (DEBUG_INFO, "[+] [%d] <- %s", k, uri);
+ g_free (uri);
+ }
+
+ load_data_free (load_data);
+
+ image_preloader->priv->stopped = FALSE;
+ start_next_loader (image_preloader);
+}
+
+
+void
+gth_image_preloader_load (GthImagePreloader *image_preloader,
+ GthFileData *requested,
+ GthFileData *next1,
+ GthFileData *prev1)
+{
+ LoadData *load_data;
+
+ load_data = load_data_new (image_preloader, requested, next1, prev1);
+ gth_image_preloader_stop (image_preloader, (DoneFunc) gth_image_preloader_load__step2, load_data);
+}
+
+
+GthImageLoader *
+gth_image_preloader_get_loader (GthImagePreloader *image_preloader,
+ GthFileData *file_data)
+{
+ int i;
+
+ g_return_val_if_fail (image_preloader != NULL, NULL);
+
+ if (file_data == NULL)
+ return NULL;
+
+ for (i = 0; i < N_LOADERS; i++) {
+ Preloader *preloader = image_preloader->priv->loader[i];
+
+ if ((preloader->file_data != NULL)
+ && g_file_equal (preloader->file_data->file, file_data->file)
+ && (_g_time_val_cmp (gth_file_data_get_modification_time (file_data),
+ gth_file_data_get_modification_time (preloader->file_data)) == 0)
+ && preloader->loaded)
+ {
+ return preloader->loader;
+ }
+ }
+
+ return NULL;
+}
+
+
+GthFileData *
+gth_image_preloader_get_requested (GthImagePreloader *image_preloader)
+{
+ Preloader *preloader;
+
+ preloader = requested_preloader (image_preloader);
+ if (preloader == NULL)
+ return NULL;
+ else
+ return preloader->file_data;
+}
+
+
+static void
+start_next_loader (GthImagePreloader *image_preloader)
+{
+ int i;
+ Preloader *preloader;
+ char *uri;
+
+ if (image_preloader->priv->stopped) {
+ image_preloader->priv->current = -1;
+ image_preloader->priv->stopped = FALSE;
+
+ debug (DEBUG_INFO, "stopped");
+
+ if (image_preloader->priv->done_func != NULL)
+ (*image_preloader->priv->done_func) (image_preloader->priv->done_func_data);
+ image_preloader->priv->done_func = NULL;
+
+ return;
+ }
+
+ preloader = requested_preloader (image_preloader);
+ if ((preloader != NULL)
+ && (preloader->file_data != NULL)
+ && ! preloader->error
+ && ! preloader->loaded)
+ {
+ i = image_preloader->priv->requested;
+ }
+ else {
+ int n = 0;
+
+ if (image_preloader->priv->current == -1)
+ i = 0;
+ else
+ i = (image_preloader->priv->current + 1) % N_LOADERS;
+
+ for (i = 0; n < N_LOADERS; i = (i + 1) % N_LOADERS) {
+ preloader = image_preloader->priv->loader[i];
+
+ if ((preloader != NULL)
+ && (preloader->file_data != NULL)
+ && ! preloader->error
+ && ! preloader->loaded)
+ {
+ break;
+ }
+
+ n++;
+ }
+
+ if (n >= N_LOADERS) {
+ image_preloader->priv->current = -1;
+ debug (DEBUG_INFO, "done");
+ return;
+ }
+ }
+
+ image_preloader->priv->current = i;
+ preloader = current_preloader (image_preloader);
+
+ gth_image_loader_load (preloader->loader);
+
+ uri = g_file_get_uri (preloader->file_data->file);
+ debug (DEBUG_INFO, "load %s", uri);
+ g_free (uri);
+}
+
+
+void
+gth_image_preloader_stop (GthImagePreloader *image_preloader,
+ DoneFunc done_func,
+ gpointer done_func_data)
+{
+ if (image_preloader->priv->current == -1) {
+ debug (DEBUG_INFO, "stopped");
+ if (done_func != NULL)
+ (*done_func) (done_func_data);
+ return;
+ }
+
+ image_preloader->priv->stopped = TRUE;
+ image_preloader->priv->done_func = done_func;
+ image_preloader->priv->done_func_data = done_func_data;
+}
+
+
+void
+gth_image_preloader_set (GthImagePreloader *dest,
+ GthImagePreloader *src)
+{
+ int i;
+
+ if (src == NULL)
+ return;
+
+ for (i = 0; i < N_LOADERS; i++) {
+ Preloader *src_loader = src->priv->loader[i];
+ Preloader *dest_loader = dest->priv->loader[i];
+
+ if ((src_loader->file_data != NULL) && src_loader->loaded && ! src_loader->error) {
+ g_object_unref (dest_loader->file_data);
+ dest_loader->file_data = g_object_ref (src_loader->file_data);
+
+ g_signal_handlers_block_by_data (dest_loader->loader, dest_loader);
+ gth_image_loader_load_from_image_loader (dest_loader->loader, src_loader->loader);
+ g_signal_handlers_unblock_by_data (dest_loader->loader, dest_loader);
+
+ dest_loader->loaded = TRUE;
+ dest_loader->error = FALSE;
+ }
+ }
+}
diff --git a/gthumb/gth-image-preloader.h b/gthumb/gth-image-preloader.h
new file mode 100644
index 0000000..6853a5b
--- /dev/null
+++ b/gthumb/gth-image-preloader.h
@@ -0,0 +1,75 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2001-2009 The Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GTH_IMAGE_PRELOADER_H
+#define GTH_IMAGE_PRELOADER_H
+
+#include "gth-image-loader.h"
+#include "gth-file-data.h"
+
+
+#define GTH_TYPE_IMAGE_PRELOADER (gth_image_preloader_get_type ())
+#define GTH_IMAGE_PRELOADER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTH_TYPE_IMAGE_PRELOADER, GthImagePreloader))
+#define GTH_IMAGE_PRELOADER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTH_TYPE_IMAGE_PRELOADER, GthImagePreloaderClass))
+#define GTH_IS_IMAGE_PRELOADER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTH_TYPE_IMAGE_PRELOADER))
+#define GTH_IS_IMAGE_PRELOADER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTH_TYPE_IMAGE_PRELOADER))
+#define GTH_IMAGE_PRELOADER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GTH_TYPE_IMAGE_PRELOADER, GthImagePreloaderClass))
+
+
+typedef struct _GthImagePreloader GthImagePreloader;
+typedef struct _GthImagePreloaderClass GthImagePreloaderClass;
+typedef struct _GthImagePreloaderPrivate GthImagePreloaderPrivate;
+
+
+struct _GthImagePreloader {
+ GObject __parent;
+ GthImagePreloaderPrivate *priv;
+};
+
+
+struct _GthImagePreloaderClass {
+ GObjectClass __parent_class;
+
+ /*< signals >*/
+
+ void (* requested_ready) (GthImagePreloader *preloader,
+ GError *error);
+};
+
+
+GType gth_image_preloader_get_type (void) G_GNUC_CONST;
+GthImagePreloader * gth_image_preloader_new (void);
+/* images get loaded in the following order : requested, next1, prev1. */
+void gth_image_preloader_load (GthImagePreloader *preloader,
+ GthFileData *requested,
+ GthFileData *next1,
+ GthFileData *prev1);
+void gth_image_preloader_stop (GthImagePreloader *preloader,
+ DoneFunc done_func,
+ gpointer done_func_data);
+GthImageLoader * gth_image_preloader_get_loader (GthImagePreloader *preloader,
+ GthFileData *file_data);
+GthFileData * gth_image_preloader_get_requested (GthImagePreloader *preloader);
+void gth_image_preloader_set (GthImagePreloader *dest,
+ GthImagePreloader *src);
+
+#endif /* GTH_IMAGE_PRELOADER_H */
diff --git a/gthumb/gth-image-selector.c b/gthumb/gth-image-selector.c
new file mode 100644
index 0000000..f694955
--- /dev/null
+++ b/gthumb/gth-image-selector.c
@@ -0,0 +1,1544 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <stdlib.h>
+#include <math.h>
+#include "glib-utils.h"
+#include "gth-cursors.h"
+#include "gth-image-selector.h"
+#include "gth-marshal.h"
+
+
+#define BORDER 3
+#define BORDER2 (BORDER * 2)
+#define DRAG_THRESHOLD 1
+#define STEP_INCREMENT 20.0 /* scroll increment. */
+#define SCROLL_TIMEOUT 30 /* autoscroll timeout in milliseconds */
+
+
+typedef struct {
+ int ref_count;
+ int id;
+ GdkRectangle area;
+ GdkCursor *cursor;
+} EventArea;
+
+
+static EventArea *
+event_area_new (int id,
+ GdkCursorType cursor_type)
+{
+ EventArea *event_area;
+
+ event_area = g_new0 (EventArea, 1);
+
+ event_area->ref_count = 1;
+ event_area->id = id;
+ event_area->area.x = 0;
+ event_area->area.y = 0;
+ event_area->area.width = 0;
+ event_area->area.height = 0;
+ event_area->cursor = gdk_cursor_new_for_display (gdk_display_get_default (), cursor_type);
+
+ return event_area;
+}
+
+
+G_GNUC_UNUSED
+static void
+event_area_ref (EventArea *event_area)
+{
+ event_area->ref_count++;
+}
+
+
+static void
+event_area_unref (EventArea *event_area)
+{
+ event_area->ref_count--;
+
+ if (event_area->ref_count > 0)
+ return;
+
+ if (event_area->cursor != NULL)
+ gdk_cursor_unref (event_area->cursor);
+ g_free (event_area);
+}
+
+
+/**/
+
+
+typedef enum {
+ C_SELECTION_AREA,
+ C_TOP_AREA,
+ C_BOTTOM_AREA,
+ C_LEFT_AREA,
+ C_RIGHT_AREA,
+ C_TOP_LEFT_AREA,
+ C_TOP_RIGHT_AREA,
+ C_BOTTOM_LEFT_AREA,
+ C_BOTTOM_RIGHT_AREA
+} GthEventAreaType;
+
+
+static GthEventAreaType
+get_opposite_event_area_on_x (GthEventAreaType type)
+{
+ GthEventAreaType opposite_type = C_SELECTION_AREA;
+ switch (type) {
+ case C_SELECTION_AREA:
+ opposite_type = C_SELECTION_AREA;
+ break;
+ case C_TOP_AREA:
+ opposite_type = C_TOP_AREA;
+ break;
+ case C_BOTTOM_AREA:
+ opposite_type = C_BOTTOM_AREA;
+ break;
+ case C_LEFT_AREA:
+ opposite_type = C_RIGHT_AREA;
+ break;
+ case C_RIGHT_AREA:
+ opposite_type = C_LEFT_AREA;
+ break;
+ case C_TOP_LEFT_AREA:
+ opposite_type = C_TOP_RIGHT_AREA;
+ break;
+ case C_TOP_RIGHT_AREA:
+ opposite_type = C_TOP_LEFT_AREA;
+ break;
+ case C_BOTTOM_LEFT_AREA:
+ opposite_type = C_BOTTOM_RIGHT_AREA;
+ break;
+ case C_BOTTOM_RIGHT_AREA:
+ opposite_type = C_BOTTOM_LEFT_AREA;
+ break;
+ }
+ return opposite_type;
+}
+
+
+static GthEventAreaType
+get_opposite_event_area_on_y (GthEventAreaType type)
+{
+ GthEventAreaType opposite_type = C_SELECTION_AREA;
+ switch (type) {
+ case C_SELECTION_AREA:
+ opposite_type = C_SELECTION_AREA;
+ break;
+ case C_TOP_AREA:
+ opposite_type = C_BOTTOM_AREA;
+ break;
+ case C_BOTTOM_AREA:
+ opposite_type = C_TOP_AREA;
+ break;
+ case C_LEFT_AREA:
+ opposite_type = C_LEFT_AREA;
+ break;
+ case C_RIGHT_AREA:
+ opposite_type = C_RIGHT_AREA;
+ break;
+ case C_TOP_LEFT_AREA:
+ opposite_type = C_BOTTOM_LEFT_AREA;
+ break;
+ case C_TOP_RIGHT_AREA:
+ opposite_type = C_BOTTOM_RIGHT_AREA;
+ break;
+ case C_BOTTOM_LEFT_AREA:
+ opposite_type = C_TOP_LEFT_AREA;
+ break;
+ case C_BOTTOM_RIGHT_AREA:
+ opposite_type = C_TOP_RIGHT_AREA;
+ break;
+ }
+ return opposite_type;
+}
+
+
+enum {
+ SELECTION_CHANGED,
+ SELECTED,
+ MOTION_NOTIFY,
+ MASK_VISIBILITY_CHANGED,
+ LAST_SIGNAL
+};
+
+
+static guint signals[LAST_SIGNAL] = { 0 };
+static gpointer parent_class = NULL;
+
+
+struct _GthImageSelectorPrivate {
+ GthImageViewer *viewer;
+ GthSelectorType type;
+
+ GdkPixbuf *pixbuf;
+ GdkPixbuf *background;
+ GdkRectangle pixbuf_area;
+
+ gboolean use_ratio;
+ double ratio;
+ gboolean mask_visible;
+ gboolean active;
+
+ GdkRectangle drag_start_selection_area;
+ GdkGC *selection_gc;
+ GdkRectangle selection_area;
+ GdkRectangle selection;
+
+ GdkCursor *default_cursor;
+ GdkCursor *current_cursor;
+ GList *event_list;
+ EventArea *current_area;
+
+ guint timer_id; /* Timeout ID for
+ * autoscrolling */
+ double x_value_diff; /* Change the adjustment value
+ * by this
+ * amount when autoscrolling */
+ double y_value_diff;
+};
+
+
+static gboolean
+point_in_rectangle (int x,
+ int y,
+ GdkRectangle rect)
+{
+ return ((x >= rect.x)
+ && (x <= rect.x + rect.width)
+ && (y >= rect.y)
+ && (y <= rect.y + rect.height));
+}
+
+
+static gboolean
+rectangle_in_rectangle (GdkRectangle r1,
+ GdkRectangle r2)
+{
+ return (point_in_rectangle (r1.x, r1.y, r2)
+ && point_in_rectangle (r1.x + r1.width,
+ r1.y + r1.height,
+ r2));
+}
+
+
+static gboolean
+rectangle_equal (GdkRectangle r1,
+ GdkRectangle r2)
+{
+ return ((r1.x == r2.x)
+ && (r1.y == r2.y)
+ && (r1.width == r2.width)
+ && (r1.height == r2.height));
+}
+
+
+static int
+real_to_selector (GthImageSelector *self,
+ int value)
+{
+ return IROUND (gth_image_viewer_get_zoom (self->priv->viewer) * value);
+}
+
+
+static void
+convert_to_selection_area (GthImageSelector *self,
+ GdkRectangle real_area,
+ GdkRectangle *selection_area)
+{
+ selection_area->x = real_to_selector (self, real_area.x);
+ selection_area->y = real_to_selector (self, real_area.y);
+ selection_area->width = real_to_selector (self, real_area.width);
+ selection_area->height = real_to_selector (self, real_area.height);
+}
+
+
+static void
+add_event_area (GthImageSelector *self,
+ int area_id,
+ GdkCursorType cursor_type)
+{
+ EventArea *event_area;
+
+ event_area = event_area_new (area_id, cursor_type);
+ self->priv->event_list = g_list_prepend (self->priv->event_list, event_area);
+}
+
+
+static void
+free_event_area_list (GthImageSelector *self)
+{
+ if (self->priv->event_list != NULL) {
+ g_list_foreach (self->priv->event_list, (GFunc) event_area_unref, NULL);
+ g_list_free (self->priv->event_list);
+ self->priv->event_list = NULL;
+ }
+}
+
+
+static EventArea *
+get_event_area_from_position (GthImageSelector *self,
+ int x,
+ int y)
+{
+ GList *scan;
+
+ for (scan = self->priv->event_list; scan; scan = scan->next) {
+ EventArea *event_area = scan->data;
+ GdkRectangle widget_area;
+
+ widget_area = event_area->area;
+ widget_area.x += self->priv->viewer->image_area.x;
+ widget_area.y += self->priv->viewer->image_area.y;
+
+ if (point_in_rectangle (x, y, widget_area))
+ return event_area;
+ }
+
+ return NULL;
+}
+
+
+static EventArea *
+get_event_area_from_id (GthImageSelector *self,
+ int event_id)
+{
+ GList *scan;
+
+ for (scan = self->priv->event_list; scan; scan = scan->next) {
+ EventArea *event_area = scan->data;
+ if (event_area->id == event_id)
+ return event_area;
+ }
+
+ return NULL;
+}
+
+
+/**/
+
+
+static void
+update_event_areas (GthImageSelector *self)
+{
+ EventArea *event_area;
+ int x, y, width, height;
+
+ if (! GTK_WIDGET_REALIZED (self->priv->viewer))
+ return;
+
+ x = self->priv->selection_area.x - 1;
+ y = self->priv->selection_area.y - 1;
+ width = self->priv->selection_area.width + 1;
+ height = self->priv->selection_area.height + 1;
+
+ event_area = get_event_area_from_id (self, C_SELECTION_AREA);
+ event_area->area.x = x + BORDER;
+ event_area->area.y = y + BORDER;
+ event_area->area.width = width - BORDER2;
+ event_area->area.height = height - BORDER2;
+
+ event_area = get_event_area_from_id (self, C_TOP_AREA);
+ event_area->area.x = x + BORDER;
+ event_area->area.y = y - BORDER;
+ event_area->area.width = width - BORDER2;
+ event_area->area.height = BORDER2;
+
+ event_area = get_event_area_from_id (self, C_BOTTOM_AREA);
+ event_area->area.x = x + BORDER;
+ event_area->area.y = y + height - BORDER;
+ event_area->area.width = width - BORDER2;
+ event_area->area.height = BORDER2;
+
+ event_area = get_event_area_from_id (self, C_LEFT_AREA);
+ event_area->area.x = x - BORDER;
+ event_area->area.y = y + BORDER;
+ event_area->area.width = BORDER2;
+ event_area->area.height = height - BORDER2;
+
+ event_area = get_event_area_from_id (self, C_RIGHT_AREA);
+ event_area->area.x = x + width - BORDER;
+ event_area->area.y = y + BORDER;
+ event_area->area.width = BORDER2;
+ event_area->area.height = height - BORDER2;
+
+ event_area = get_event_area_from_id (self, C_TOP_LEFT_AREA);
+ event_area->area.x = x - BORDER;
+ event_area->area.y = y - BORDER;
+ event_area->area.width = BORDER2;
+ event_area->area.height = BORDER2;
+
+ event_area = get_event_area_from_id (self, C_TOP_RIGHT_AREA);
+ event_area->area.x = x + width - BORDER;
+ event_area->area.y = y - BORDER;
+ event_area->area.width = BORDER2;
+ event_area->area.height = BORDER2;
+
+ event_area = get_event_area_from_id (self, C_BOTTOM_LEFT_AREA);
+ event_area->area.x = x - BORDER;
+ event_area->area.y = y + height - BORDER;
+ event_area->area.width = BORDER2;
+ event_area->area.height = BORDER2;
+
+ event_area = get_event_area_from_id (self, C_BOTTOM_RIGHT_AREA);
+ event_area->area.x = x + width - BORDER;
+ event_area->area.y = y + height - BORDER;
+ event_area->area.width = BORDER2;
+ event_area->area.height = BORDER2;
+}
+
+
+static void
+queue_draw (GthImageSelector *self,
+ GdkRectangle area)
+{
+ if (! GTK_WIDGET_REALIZED (self->priv->viewer))
+ return;
+
+ gtk_widget_queue_draw_area (GTK_WIDGET (self->priv->viewer),
+ self->priv->viewer->image_area.x + area.x - self->priv->viewer->x_offset - BORDER,
+ self->priv->viewer->image_area.y + area.y - self->priv->viewer->y_offset - BORDER,
+ area.width + BORDER2,
+ area.height + BORDER2);
+}
+
+
+static void
+selection_changed (GthImageSelector *self)
+{
+ update_event_areas (self);
+ g_signal_emit (G_OBJECT (self), signals[SELECTION_CHANGED], 0);
+}
+
+
+static void
+set_selection_area (GthImageSelector *self,
+ GdkRectangle new_selection,
+ gboolean force_update)
+{
+ GdkRectangle old_selection_area;
+ GdkRectangle dirty_region;
+
+ if (! force_update && rectangle_equal (self->priv->selection_area, new_selection))
+ return;
+
+ old_selection_area = self->priv->selection_area;
+ self->priv->selection_area = new_selection;
+ gdk_rectangle_union (&old_selection_area, &self->priv->selection_area, &dirty_region);
+ queue_draw (self, dirty_region);
+
+ selection_changed (self);
+}
+
+
+static void
+set_selection (GthImageSelector *self,
+ GdkRectangle new_selection,
+ gboolean force_update)
+{
+ GdkRectangle new_area;
+
+ if (! force_update && rectangle_equal (self->priv->selection, new_selection))
+ return;
+
+ self->priv->selection = new_selection;
+ convert_to_selection_area (self, new_selection, &new_area);
+ set_selection_area (self, new_area, force_update);
+}
+
+
+static void
+init_selection (GthImageSelector *self)
+{
+ GdkRectangle area;
+
+ /*
+ if (! self->priv->use_ratio) {
+ area.width = IROUND (self->priv->pixbuf_area.width * 0.5);
+ area.height = IROUND (self->priv->pixbuf_area.height * 0.5);
+ }
+ else {
+ if (self->priv->ratio > 1.0) {
+ area.width = IROUND (self->priv->pixbuf_area.width * 0.5);
+ area.height = IROUND (area.width / self->priv->ratio);
+ }
+ else {
+ area.height = IROUND (self->priv->pixbuf_area.height * 0.5);
+ area.width = IROUND (area.height * self->priv->ratio);
+ }
+ }
+ area.x = IROUND ((self->priv->pixbuf_area.width - area.width) / 2.0);
+ area.y = IROUND ((self->priv->pixbuf_area.height - area.height) / 2.0);
+ */
+
+ area.x = 0;
+ area.y = 0;
+ area.height = 0;
+ area.width = 0;
+ set_selection (self, area, FALSE);
+}
+
+
+static void
+gth_image_selector_realize (GthImageTool *base)
+{
+ GthImageSelector *self = GTH_IMAGE_SELECTOR (base);
+ GtkWidget *widget;
+
+ widget = (GtkWidget *) self->priv->viewer;
+
+ self->priv->selection_gc = gdk_gc_new (widget->window);
+ gdk_gc_copy (self->priv->selection_gc, widget->style->white_gc);
+ gdk_gc_set_line_attributes (self->priv->selection_gc,
+ 1,
+ GDK_LINE_ON_OFF_DASH /*GDK_LINE_SOLID*/,
+ GDK_CAP_BUTT,
+ GDK_JOIN_MITER);
+ gdk_gc_set_function (self->priv->selection_gc, GDK_INVERT);
+
+ if (self->priv->type == GTH_SELECTOR_TYPE_REGION)
+ self->priv->default_cursor = gdk_cursor_new_for_display (gdk_display_get_default (), GDK_CROSSHAIR /*GDK_LEFT_PTR*/);
+ else if (self->priv->type == GTH_SELECTOR_TYPE_POINT)
+ self->priv->default_cursor = gdk_cursor_new_for_display (gdk_display_get_default (), GDK_CROSSHAIR);
+ gth_image_viewer_set_cursor (self->priv->viewer, self->priv->default_cursor);
+
+ add_event_area (self, C_SELECTION_AREA, GDK_FLEUR);
+ add_event_area (self, C_TOP_AREA, GDK_TOP_SIDE);
+ add_event_area (self, C_BOTTOM_AREA, GDK_BOTTOM_SIDE);
+ add_event_area (self, C_LEFT_AREA, GDK_LEFT_SIDE);
+ add_event_area (self, C_RIGHT_AREA, GDK_RIGHT_SIDE);
+ add_event_area (self, C_TOP_LEFT_AREA, GDK_TOP_LEFT_CORNER);
+ add_event_area (self, C_TOP_RIGHT_AREA, GDK_TOP_RIGHT_CORNER);
+ add_event_area (self, C_BOTTOM_LEFT_AREA, GDK_BOTTOM_LEFT_CORNER);
+ add_event_area (self, C_BOTTOM_RIGHT_AREA, GDK_BOTTOM_RIGHT_CORNER);
+
+ init_selection (self);
+ update_event_areas (self);
+}
+
+
+static void
+gth_image_selector_unrealize (GthImageTool *base)
+{
+ GthImageSelector *self = GTH_IMAGE_SELECTOR (base);
+
+ if (self->priv->default_cursor != NULL) {
+ gdk_cursor_unref (self->priv->default_cursor);
+ self->priv->default_cursor = NULL;
+ }
+
+ if (self->priv->selection_gc != NULL) {
+ g_object_unref (self->priv->selection_gc);
+ self->priv->selection_gc = NULL;
+ }
+
+ free_event_area_list (self);
+}
+
+
+static void
+gth_image_selector_size_allocate (GthImageTool *base,
+ GtkAllocation *allocation)
+{
+ GthImageSelector *self = GTH_IMAGE_SELECTOR (base);
+
+ if (self->priv->pixbuf != NULL)
+ selection_changed (self);
+}
+
+
+G_GNUC_UNUSED
+static void
+print_rectangle (const char *name,
+ GdkRectangle *r)
+{
+ g_print ("%s ==> (%d,%d) [%d,%d]\n", name, r->x, r->y, r->width, r->height);
+}
+
+
+static void
+paint_background (GthImageSelector *self,
+ GdkRectangle *event_area)
+{
+ GdkRectangle paint_area;
+
+ if (! gdk_rectangle_intersect (&self->priv->viewer->image_area, event_area, &paint_area))
+ return;
+
+ gth_image_viewer_paint (self->priv->viewer,
+ self->priv->background,
+ self->priv->viewer->x_offset + paint_area.x - self->priv->viewer->image_area.x,
+ self->priv->viewer->y_offset + paint_area.y - self->priv->viewer->image_area.y,
+ paint_area.x,
+ paint_area.y,
+ paint_area.width,
+ paint_area.height,
+ GDK_INTERP_NEAREST);
+}
+
+
+static void
+paint_selection (GthImageSelector *self,
+ GdkRectangle *event_area)
+{
+ GdkRectangle selection_area, paint_area;
+
+ selection_area = self->priv->selection_area;
+ selection_area.x += self->priv->viewer->image_area.x - self->priv->viewer->x_offset;
+ selection_area.y += self->priv->viewer->image_area.y - self->priv->viewer->y_offset;
+
+ if (! gdk_rectangle_intersect (&selection_area, event_area, &paint_area))
+ return;
+
+ gth_image_viewer_paint (self->priv->viewer,
+ self->priv->pixbuf,
+ self->priv->viewer->x_offset + paint_area.x - self->priv->viewer->image_area.x,
+ self->priv->viewer->y_offset + paint_area.y - self->priv->viewer->image_area.y,
+ paint_area.x,
+ paint_area.y,
+ paint_area.width,
+ paint_area.height,
+ GDK_INTERP_NEAREST);
+}
+
+
+static void
+paint_image (GthImageSelector *self,
+ GdkRectangle *event_area)
+{
+ GdkRectangle paint_area;
+
+ if (! gdk_rectangle_intersect (&self->priv->viewer->image_area, event_area, &paint_area))
+ return;
+
+ gth_image_viewer_paint (self->priv->viewer,
+ self->priv->pixbuf,
+ self->priv->viewer->x_offset + paint_area.x - self->priv->viewer->image_area.x,
+ self->priv->viewer->y_offset + paint_area.y - self->priv->viewer->image_area.y,
+ paint_area.x,
+ paint_area.y,
+ paint_area.width,
+ paint_area.height,
+ GDK_INTERP_NEAREST);
+}
+
+
+static void
+gth_image_selector_expose (GthImageTool *base,
+ GdkRectangle *event_area)
+{
+ GthImageSelector *self = GTH_IMAGE_SELECTOR (base);
+
+ if (self->priv->pixbuf == NULL)
+ return;
+
+ if (! self->priv->mask_visible) {
+ paint_image (self, event_area);
+ return;
+ }
+
+ paint_background (self, event_area);
+ paint_selection (self, event_area);
+
+ if (GTK_WIDGET_HAS_FOCUS (self->priv->viewer)) {
+ GdkRectangle area;
+
+ area = self->priv->selection_area;
+ area.x += self->priv->viewer->image_area.x - self->priv->viewer->x_offset;
+ area.y += self->priv->viewer->image_area.y - self->priv->viewer->y_offset;
+
+ gdk_draw_rectangle (GTK_WIDGET (self->priv->viewer)->window,
+ self->priv->selection_gc,
+ FALSE,
+ area.x,
+ area.y,
+ area.width,
+ area.height);
+ }
+}
+
+
+static int
+selector_to_real (GthImageSelector *self,
+ int value)
+{
+ return IROUND ((double) value / gth_image_viewer_get_zoom (self->priv->viewer));
+}
+
+
+static void
+convert_to_real_selection (GthImageSelector *self,
+ GdkRectangle selection_area,
+ GdkRectangle *real_area)
+{
+ real_area->x = selector_to_real (self, selection_area.x);
+ real_area->y = selector_to_real (self, selection_area.y);
+ real_area->width = selector_to_real (self, selection_area.width);
+ real_area->height = selector_to_real (self, selection_area.height);
+}
+
+
+static void
+set_active_area (GthImageSelector *self,
+ EventArea *event_area)
+{
+ if (self->priv->active != (event_area != NULL)) {
+ self->priv->active = ! self->priv->active;
+ /* queue_draw (self, self->priv->selection_area); FIXME */
+ }
+
+ if (self->priv->current_area != event_area)
+ self->priv->current_area = event_area;
+
+ if (self->priv->current_area == NULL)
+ gth_image_viewer_set_cursor (self->priv->viewer, self->priv->default_cursor);
+ else
+ gth_image_viewer_set_cursor (self->priv->viewer, self->priv->current_area->cursor);
+}
+
+
+static void
+update_cursor (GthImageSelector *self,
+ int x,
+ int y)
+{
+ if (! self->priv->mask_visible)
+ return;
+ set_active_area (self, get_event_area_from_position (self, x, y));
+}
+
+
+static gboolean
+gth_image_selector_button_release (GthImageTool *base,
+ GdkEventButton *event)
+{
+ GthImageSelector *self = GTH_IMAGE_SELECTOR (base);
+
+ if (self->priv->timer_id != 0) {
+ g_source_remove (self->priv->timer_id);
+ self->priv->timer_id = 0;
+ }
+
+ update_cursor (self,
+ event->x + self->priv->viewer->x_offset,
+ event->y + self->priv->viewer->y_offset);
+
+ return FALSE;
+}
+
+
+static void
+grow_upward (GdkRectangle *bound,
+ GdkRectangle *r,
+ int dy,
+ gboolean check)
+{
+ if (check && (r->y + dy < 0))
+ dy = -r->y;
+ r->y += dy;
+ r->height -= dy;
+}
+
+
+static void
+grow_downward (GdkRectangle *bound,
+ GdkRectangle *r,
+ int dy,
+ gboolean check)
+{
+ if (check && (r->y + r->height + dy > bound->height))
+ dy = bound->height - (r->y + r->height);
+ r->height += dy;
+}
+
+
+static void
+grow_leftward (GdkRectangle *bound,
+ GdkRectangle *r,
+ int dx,
+ gboolean check)
+{
+ if (check && (r->x + dx < 0))
+ dx = -r->x;
+ r->x += dx;
+ r->width -= dx;
+}
+
+
+static void
+grow_rightward (GdkRectangle *bound,
+ GdkRectangle *r,
+ int dx,
+ gboolean check)
+{
+ if (check && (r->x + r->width + dx > bound->width))
+ dx = bound->width - (r->x + r->width);
+ r->width += dx;
+}
+
+
+static int
+get_semiplane_no (int x1,
+ int y1,
+ int x2,
+ int y2,
+ int px,
+ int py)
+{
+ double a, b;
+
+ a = atan ((double) (y1 - y2) / (x2 - x1));
+ b = atan ((double) (y1 - py) / (px - x1));
+
+ return (a <= b) && (b <= a + G_PI);
+}
+
+
+static void
+check_and_set_new_selection (GthImageSelector *self,
+ GdkRectangle new_selection)
+{
+ new_selection.width = MAX (0, new_selection.width);
+ new_selection.height = MAX (0, new_selection.height);
+
+ if (((self->priv->current_area == NULL) || (self->priv->current_area->id != C_SELECTION_AREA))
+ && self->priv->use_ratio)
+ {
+ if (rectangle_in_rectangle (new_selection, self->priv->pixbuf_area))
+ set_selection (self, new_selection, FALSE);
+ return;
+ }
+
+ if (new_selection.x < 0)
+ new_selection.x = 0;
+ if (new_selection.y < 0)
+ new_selection.y = 0;
+ if (new_selection.width > self->priv->pixbuf_area.width)
+ new_selection.width = self->priv->pixbuf_area.width;
+ if (new_selection.height > self->priv->pixbuf_area.height)
+ new_selection.height = self->priv->pixbuf_area.height;
+
+ if (new_selection.x + new_selection.width > self->priv->pixbuf_area.width)
+ new_selection.x = self->priv->pixbuf_area.width - new_selection.width;
+ if (new_selection.y + new_selection.height > self->priv->pixbuf_area.height)
+ new_selection.y = self->priv->pixbuf_area.height - new_selection.height;
+
+ set_selection (self, new_selection, FALSE);
+}
+
+
+static gboolean
+gth_image_selector_button_press (GthImageTool *base,
+ GdkEventButton *event)
+{
+ GthImageSelector *self = GTH_IMAGE_SELECTOR (base);
+ gboolean retval = FALSE;
+ int x, y;
+
+ if (event->button != 1)
+ return FALSE;
+
+ if (! point_in_rectangle (event->x, event->y, self->priv->viewer->image_area))
+ return FALSE;
+
+ x = event->x + self->priv->viewer->x_offset;
+ y = event->y + self->priv->viewer->y_offset;
+
+ if (self->priv->current_area == NULL) {
+ GdkRectangle new_selection;
+
+ new_selection.x = selector_to_real (self, x - self->priv->viewer->image_area.x);
+ new_selection.y = selector_to_real (self, y - self->priv->viewer->image_area.y);
+ new_selection.width = selector_to_real (self, 1);
+ new_selection.height = selector_to_real (self, 1);
+
+ if (self->priv->type == GTH_SELECTOR_TYPE_REGION) {
+ check_and_set_new_selection (self, new_selection);
+ set_active_area (self, get_event_area_from_id (self, C_BOTTOM_RIGHT_AREA));
+ }
+ else if (self->priv->type == GTH_SELECTOR_TYPE_POINT) {
+ retval = TRUE;
+ g_signal_emit (G_OBJECT (self),
+ signals[SELECTED],
+ 0,
+ new_selection.x,
+ new_selection.y);
+ }
+ }
+
+ if (self->priv->current_area != NULL) {
+ self->priv->viewer->pressed = TRUE;
+ self->priv->viewer->dragging = TRUE;
+ self->priv->drag_start_selection_area = self->priv->selection_area;
+ retval = TRUE;
+ }
+
+ return retval;
+}
+
+
+static void
+update_mouse_selection (GthImageSelector *self,
+ int new_x,
+ int new_y)
+{
+ gboolean check = ! self->priv->use_ratio;
+ int dx, dy;
+ GdkRectangle new_selection, tmp;
+ int semiplane;
+ GthEventAreaType area_type = self->priv->current_area->id;
+ EventArea *event_area;
+
+ dx = selector_to_real (self, self->priv->viewer->drag_x - self->priv->viewer->drag_x_start);
+ dy = selector_to_real (self, self->priv->viewer->drag_y - self->priv->viewer->drag_y_start);
+
+ convert_to_real_selection (self,
+ self->priv->drag_start_selection_area,
+ &new_selection);
+
+ if (((area_type == C_LEFT_AREA)
+ || (area_type == C_TOP_LEFT_AREA)
+ || (area_type == C_BOTTOM_LEFT_AREA))
+ && (dx > new_selection.width))
+ {
+ new_selection.x += new_selection.width;
+ dx = - (2 * new_selection.width) + dx;
+ area_type = get_opposite_event_area_on_x (area_type);
+ }
+ else if (((area_type == C_RIGHT_AREA)
+ || (area_type == C_TOP_RIGHT_AREA)
+ || (area_type == C_BOTTOM_RIGHT_AREA))
+ && (-dx > new_selection.width))
+ {
+ new_selection.x -= new_selection.width;
+ dx = (2 * new_selection.width) + dx;
+ area_type = get_opposite_event_area_on_x (area_type);
+ }
+
+ if (((area_type == C_TOP_AREA)
+ || (area_type == C_TOP_LEFT_AREA)
+ || (area_type == C_TOP_RIGHT_AREA))
+ && (dy > new_selection.height))
+ {
+ new_selection.y += new_selection.height;
+ dy = - (2 * new_selection.height) + dy;
+ area_type = get_opposite_event_area_on_y (area_type);
+ }
+ else if (((area_type == C_BOTTOM_AREA)
+ || (area_type == C_BOTTOM_LEFT_AREA)
+ || (area_type == C_BOTTOM_RIGHT_AREA))
+ && (-dy > new_selection.height))
+ {
+ new_selection.y -= new_selection.height;
+ dy = (2 * new_selection.height) + dy;
+ area_type = get_opposite_event_area_on_y (area_type);
+ }
+
+ event_area = get_event_area_from_id (self, area_type);
+ if (event_area != NULL)
+ gth_image_viewer_set_cursor (self->priv->viewer, event_area->cursor);
+
+ switch (area_type) {
+ case C_SELECTION_AREA:
+ new_selection.x += dx;
+ new_selection.y += dy;
+ break;
+
+ case C_TOP_AREA:
+ grow_upward (&self->priv->pixbuf_area, &new_selection, dy, check);
+ if (self->priv->use_ratio)
+ grow_rightward (&self->priv->pixbuf_area,
+ &new_selection,
+ IROUND (-dy * self->priv->ratio),
+ check);
+ break;
+
+ case C_BOTTOM_AREA:
+ grow_downward (&self->priv->pixbuf_area, &new_selection, dy, check);
+ if (self->priv->use_ratio)
+ grow_leftward (&self->priv->pixbuf_area,
+ &new_selection,
+ IROUND (-dy * self->priv->ratio),
+ check);
+ break;
+
+ case C_LEFT_AREA:
+ grow_leftward (&self->priv->pixbuf_area, &new_selection, dx, check);
+ if (self->priv->use_ratio)
+ grow_downward (&self->priv->pixbuf_area,
+ &new_selection,
+ IROUND (-dx / self->priv->ratio),
+ check);
+ break;
+
+ case C_RIGHT_AREA:
+ grow_rightward (&self->priv->pixbuf_area, &new_selection, dx, check);
+ if (self->priv->use_ratio)
+ grow_upward (&self->priv->pixbuf_area,
+ &new_selection,
+ IROUND (-dx / self->priv->ratio),
+ check);
+ break;
+
+ case C_TOP_LEFT_AREA:
+ if (self->priv->use_ratio) {
+ tmp = self->priv->selection_area;
+ semiplane = get_semiplane_no (tmp.x + tmp.width,
+ tmp.y + tmp.height,
+ tmp.x,
+ tmp.y,
+ self->priv->viewer->drag_x - self->priv->viewer->image_area.x,
+ self->priv->viewer->drag_y - self->priv->viewer->image_area.y);
+ if (semiplane == 1)
+ dy = IROUND (dx / self->priv->ratio);
+ else
+ dx = IROUND (dy * self->priv->ratio);
+ }
+ grow_upward (&self->priv->pixbuf_area, &new_selection, dy, check);
+ grow_leftward (&self->priv->pixbuf_area, &new_selection, dx, check);
+ break;
+
+ case C_TOP_RIGHT_AREA:
+ if (self->priv->use_ratio) {
+ tmp = self->priv->selection_area;
+ semiplane = get_semiplane_no (tmp.x,
+ tmp.y + tmp.height,
+ tmp.x + tmp.width,
+ tmp.y,
+ self->priv->viewer->drag_x - self->priv->viewer->image_area.x,
+ self->priv->viewer->drag_y - self->priv->viewer->image_area.y);
+ if (semiplane == 1)
+ dx = IROUND (-dy * self->priv->ratio);
+ else
+ dy = IROUND (-dx / self->priv->ratio);
+ }
+ grow_upward (&self->priv->pixbuf_area, &new_selection, dy, check);
+ grow_rightward (&self->priv->pixbuf_area, &new_selection, dx, check);
+ break;
+
+ case C_BOTTOM_LEFT_AREA:
+ if (self->priv->use_ratio) {
+ tmp = self->priv->selection_area;
+ semiplane = get_semiplane_no (tmp.x + tmp.width,
+ tmp.y,
+ tmp.x,
+ tmp.y + tmp.height,
+ self->priv->viewer->drag_x - self->priv->viewer->image_area.x,
+ self->priv->viewer->drag_y - self->priv->viewer->image_area.y);
+ if (semiplane == 1)
+ dx = IROUND (-dy * self->priv->ratio);
+ else
+ dy = IROUND (-dx / self->priv->ratio);
+ }
+ grow_downward (&self->priv->pixbuf_area, &new_selection, dy, check);
+ grow_leftward (&self->priv->pixbuf_area, &new_selection, dx, check);
+ break;
+
+ case C_BOTTOM_RIGHT_AREA:
+ if (self->priv->use_ratio) {
+ tmp = self->priv->selection_area;
+ semiplane = get_semiplane_no (tmp.x,
+ tmp.y,
+ tmp.x + tmp.width,
+ tmp.y + tmp.height,
+ self->priv->viewer->drag_x - self->priv->viewer->image_area.x,
+ self->priv->viewer->drag_y - self->priv->viewer->image_area.y);
+
+ if (semiplane == 1)
+ dy = IROUND (dx / self->priv->ratio);
+ else
+ dx = IROUND (dy * self->priv->ratio);
+ }
+ grow_downward (&self->priv->pixbuf_area, &new_selection, dy, check);
+ grow_rightward (&self->priv->pixbuf_area, &new_selection, dx, check);
+ break;
+
+ default:
+ break;
+ }
+
+ check_and_set_new_selection (self, new_selection);
+}
+
+
+static gboolean
+autoscroll_cb (gpointer data)
+{
+ GthImageSelector *self = GTH_IMAGE_SELECTOR (data);
+ double max_value;
+ double value;
+
+ GDK_THREADS_ENTER ();
+
+ max_value = self->priv->viewer->hadj->upper - self->priv->viewer->hadj->page_size;
+ value = self->priv->viewer->hadj->value + self->priv->x_value_diff;
+ if (value > max_value)
+ value = max_value;
+ gtk_adjustment_set_value (self->priv->viewer->hadj, value);
+ self->priv->viewer->drag_x = self->priv->viewer->drag_x + self->priv->x_value_diff;
+
+ max_value = self->priv->viewer->vadj->upper - self->priv->viewer->vadj->page_size;
+ value = self->priv->viewer->vadj->value + self->priv->y_value_diff;
+ if (value > max_value)
+ value = max_value;
+ gtk_adjustment_set_value (self->priv->viewer->vadj, value);
+ self->priv->viewer->drag_y = self->priv->viewer->drag_y + self->priv->y_value_diff;
+
+ update_mouse_selection (self, self->priv->viewer->drag_x, self->priv->viewer->drag_y);
+ gtk_widget_queue_draw (GTK_WIDGET (self->priv->viewer));
+
+ GDK_THREADS_LEAVE();
+
+ return TRUE;
+}
+
+
+static gboolean
+gth_image_selector_motion_notify (GthImageTool *base,
+ GdkEventMotion *event)
+{
+ GthImageSelector *self = GTH_IMAGE_SELECTOR (base);
+ GtkWidget *widget;
+ int x, y;
+ int absolute_x, absolute_y;
+
+ widget = GTK_WIDGET (self->priv->viewer);
+ x = event->x + self->priv->viewer->x_offset;
+ y = event->y + self->priv->viewer->y_offset;
+
+ if (self->priv->type == GTH_SELECTOR_TYPE_POINT) {
+ x = selector_to_real (self, x - self->priv->viewer->image_area.x);
+ y = selector_to_real (self, y - self->priv->viewer->image_area.y);
+ if (point_in_rectangle (x, y, self->priv->pixbuf_area))
+ g_signal_emit (G_OBJECT (self), signals[MOTION_NOTIFY], 0, x, y);
+ return TRUE;
+ }
+
+ /* type == GTH_SELECTOR_TYPE_REGION */
+
+ if (! self->priv->viewer->dragging
+ && self->priv->viewer->pressed
+ && ((abs (self->priv->viewer->drag_x - self->priv->viewer->drag_x_prev) > DRAG_THRESHOLD)
+ || (abs (self->priv->viewer->drag_y - self->priv->viewer->drag_y_prev) > DRAG_THRESHOLD))
+ && (self->priv->current_area != NULL))
+ {
+ int retval;
+
+ retval = gdk_pointer_grab (widget->window,
+ FALSE,
+ (GDK_POINTER_MOTION_MASK
+ | GDK_POINTER_MOTION_HINT_MASK
+ | GDK_BUTTON_RELEASE_MASK),
+ NULL,
+ self->priv->current_area->cursor,
+ event->time);
+ if (retval == 0)
+ self->priv->viewer->dragging = TRUE;
+
+ return FALSE;
+ }
+
+ if (! self->priv->viewer->dragging) {
+ update_cursor (self, x, y);
+ return FALSE;
+ }
+
+ /* dragging == TRUE */
+
+ update_mouse_selection (self, x, y);
+
+ /* If we are out of bounds, schedule a timeout that will do
+ * the scrolling */
+
+ absolute_x = event->x;
+ absolute_y = event->y;
+
+ if ((absolute_y < 0) || (absolute_y > widget->allocation.height)
+ || (absolute_x < 0) || (absolute_x > widget->allocation.width))
+ {
+
+ /* Make the steppings be relative to the mouse
+ * distance from the canvas.
+ * Also notice the timeout is small to give a smoother
+ * movement.
+ */
+ if (absolute_x < 0)
+ self->priv->x_value_diff = absolute_x;
+ else if (absolute_x > widget->allocation.width)
+ self->priv->x_value_diff = absolute_x - widget->allocation.width;
+ else
+ self->priv->x_value_diff = 0.0;
+ self->priv->x_value_diff /= 2;
+
+ if (absolute_y < 0)
+ self->priv->y_value_diff = absolute_y;
+ else if (absolute_y > widget->allocation.height)
+ self->priv->y_value_diff = absolute_y - widget->allocation.height;
+ else
+ self->priv->y_value_diff = 0.0;
+ self->priv->y_value_diff /= 2;
+
+ if (self->priv->timer_id == 0)
+ self->priv->timer_id = g_timeout_add (SCROLL_TIMEOUT,
+ autoscroll_cb,
+ self);
+ }
+ else if (self->priv->timer_id != 0) {
+ g_source_remove (self->priv->timer_id);
+ self->priv->timer_id = 0;
+ }
+
+ return FALSE;
+}
+
+
+static void
+gth_image_selector_image_changed (GthImageTool *base)
+{
+ GthImageSelector *self = GTH_IMAGE_SELECTOR (base);
+
+ _g_object_unref (self->priv->pixbuf);
+ self->priv->pixbuf = gth_image_viewer_get_current_pixbuf (self->priv->viewer);
+
+ _g_object_unref (self->priv->background);
+ self->priv->background = NULL;
+
+ if (self->priv->pixbuf == NULL) {
+ self->priv->pixbuf_area.width = 0;
+ self->priv->pixbuf_area.height = 0;
+ return;
+ }
+
+ self->priv->pixbuf = g_object_ref (self->priv->pixbuf);
+ self->priv->pixbuf_area.width = gdk_pixbuf_get_width (self->priv->pixbuf);
+ self->priv->pixbuf_area.height = gdk_pixbuf_get_height (self->priv->pixbuf);
+
+ self->priv->background = gdk_pixbuf_composite_color_simple (
+ self->priv->pixbuf,
+ gdk_pixbuf_get_width (self->priv->pixbuf),
+ gdk_pixbuf_get_height (self->priv->pixbuf),
+ GDK_INTERP_NEAREST,
+ 128 /*196 FIXME: find a goob value */,
+ 10,
+ 0x00000000,
+ 0x00000000);
+ init_selection (self);
+}
+
+
+static void
+gth_image_selector_zoom_changed (GthImageTool *base)
+{
+ GthImageSelector *self = GTH_IMAGE_SELECTOR (base);
+ GdkRectangle selection;
+
+ gth_image_selector_get_selection (self, &selection);
+ set_selection (self, selection, TRUE);
+}
+
+
+static void
+gth_image_selector_instance_init (GthImageSelector *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GTH_TYPE_IMAGE_SELECTOR, GthImageSelectorPrivate);
+
+ self->priv->type = GTH_SELECTOR_TYPE_REGION;
+ self->priv->ratio = 1.0;
+ self->priv->mask_visible = TRUE;
+}
+
+
+static void
+gth_image_selector_finalize (GObject *object)
+{
+ GthImageSelector *self;
+
+ g_return_if_fail (object != NULL);
+ g_return_if_fail (GTH_IS_IMAGE_SELECTOR (object));
+
+ self = (GthImageSelector *) object;
+
+ _g_object_unref (self->priv->pixbuf);
+ _g_object_unref (self->priv->background);
+
+ /* Chain up */
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+
+static void
+gth_image_selector_class_init (GthImageSelectorClass *class)
+{
+ GObjectClass *gobject_class;
+
+ parent_class = g_type_class_peek_parent (class);
+ g_type_class_add_private (class, sizeof (GthImageSelectorPrivate));
+
+ gobject_class = (GObjectClass*) class;
+ gobject_class->finalize = gth_image_selector_finalize;
+
+ signals[SELECTION_CHANGED] = g_signal_new ("selection_changed",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GthImageSelectorClass, selection_changed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+ signals[MOTION_NOTIFY] = g_signal_new ("motion_notify",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GthImageSelectorClass, motion_notify),
+ NULL, NULL,
+ gth_marshal_VOID__INT_INT,
+ G_TYPE_NONE,
+ 2,
+ G_TYPE_INT,
+ G_TYPE_INT);
+ signals[SELECTED] = g_signal_new ("selected",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GthImageSelectorClass, selected),
+ NULL, NULL,
+ gth_marshal_VOID__INT_INT,
+ G_TYPE_NONE,
+ 2,
+ G_TYPE_INT,
+ G_TYPE_INT);
+ signals[MASK_VISIBILITY_CHANGED] = g_signal_new ("mask_visibility_changed",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GthImageSelectorClass, mask_visibility_changed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+}
+
+
+static void
+gth_image_selector_gth_image_tool_interface_init (GthImageToolIface *iface)
+{
+ iface->realize = gth_image_selector_realize;
+ iface->unrealize = gth_image_selector_unrealize;
+ iface->size_allocate = gth_image_selector_size_allocate;
+ iface->expose = gth_image_selector_expose;
+ iface->button_press = gth_image_selector_button_press;
+ iface->button_release = gth_image_selector_button_release;
+ iface->motion_notify = gth_image_selector_motion_notify;
+ iface->image_changed = gth_image_selector_image_changed;
+ iface->zoom_changed = gth_image_selector_zoom_changed;
+}
+
+
+GType
+gth_image_selector_get_type (void)
+{
+ static GType type = 0;
+
+ if (! type) {
+ GTypeInfo type_info = {
+ sizeof (GthImageSelectorClass),
+ NULL,
+ NULL,
+ (GClassInitFunc) gth_image_selector_class_init,
+ NULL,
+ NULL,
+ sizeof (GthImageSelector),
+ 0,
+ (GInstanceInitFunc) gth_image_selector_instance_init
+ };
+ static const GInterfaceInfo gth_image_tool_info = {
+ (GInterfaceInitFunc) gth_image_selector_gth_image_tool_interface_init,
+ (GInterfaceFinalizeFunc) NULL,
+ NULL
+ };
+
+ type = g_type_register_static (G_TYPE_OBJECT,
+ "GthImageSelector",
+ &type_info,
+ 0);
+ g_type_add_interface_static (type, GTH_TYPE_IMAGE_TOOL, >h_image_tool_info);
+ }
+
+ return type;
+}
+
+
+GthImageTool *
+gth_image_selector_new (GthImageViewer *viewer,
+ GthSelectorType type)
+{
+ GthImageSelector *selector;
+
+ selector = g_object_new (GTH_TYPE_IMAGE_SELECTOR, NULL);
+ selector->priv->viewer = viewer;
+
+ return GTH_IMAGE_TOOL (selector);
+}
+
+
+void
+gth_image_selector_set_selection_x (GthImageSelector *self,
+ int x)
+{
+ GdkRectangle new_selection;
+
+ new_selection = self->priv->selection;
+ new_selection.x = x;
+ check_and_set_new_selection (self, new_selection);
+}
+
+
+void
+gth_image_selector_set_selection_y (GthImageSelector *self,
+ int y)
+{
+ GdkRectangle new_selection;
+
+ new_selection = self->priv->selection;
+ new_selection.y = y;
+ check_and_set_new_selection (self, new_selection);
+}
+
+
+void
+gth_image_selector_set_selection_width (GthImageSelector *self,
+ int width)
+{
+ GdkRectangle new_selection;
+
+ new_selection = self->priv->selection;
+ new_selection.width = width;
+ if (self->priv->use_ratio)
+ new_selection.height = IROUND (width / self->priv->ratio);
+ check_and_set_new_selection (self, new_selection);
+}
+
+
+void
+gth_image_selector_set_selection_height (GthImageSelector *self,
+ int height)
+{
+ GdkRectangle new_selection;
+
+ new_selection = self->priv->selection;
+ new_selection.height = height;
+ if (self->priv->use_ratio)
+ new_selection.width = IROUND (height * self->priv->ratio);
+ check_and_set_new_selection (self, new_selection);
+}
+
+
+void
+gth_image_selector_set_selection (GthImageSelector *self,
+ GdkRectangle selection)
+{
+ set_selection (self, selection, FALSE);
+}
+
+
+void
+gth_image_selector_get_selection (GthImageSelector *self,
+ GdkRectangle *selection)
+{
+ selection->x = MAX (self->priv->selection.x, 0);
+ selection->y = MAX (self->priv->selection.y, 0);
+ selection->width = MIN (self->priv->selection.width, self->priv->pixbuf_area.width - self->priv->selection.x);
+ selection->height = MIN (self->priv->selection.height, self->priv->pixbuf_area.height - self->priv->selection.y);
+}
+
+
+void
+gth_image_selector_set_ratio (GthImageSelector *self,
+ gboolean use_ratio,
+ double ratio,
+ gboolean swap_x_and_y_to_start)
+{
+ int new_starting_width;
+
+ self->priv->use_ratio = use_ratio;
+ self->priv->ratio = ratio;
+
+ if (self->priv->use_ratio) {
+ /* When changing the cropping aspect ratio, it looks more natural
+ to swap the height and width, rather than (for example) keeping
+ the width constant and shrinking the height. */
+ if (swap_x_and_y_to_start == TRUE)
+ new_starting_width = self->priv->selection.height;
+ else
+ new_starting_width = self->priv->selection.width;
+
+ gth_image_selector_set_selection_width (self, new_starting_width);
+ gth_image_selector_set_selection_height (self, self->priv->selection.height);
+
+ /* However, if swapping the height and width fails because it exceeds
+ the image size, then revert to keeping the width constant and shrinking
+ the height. That is guaranteed to fit inside the old selection. */
+ if ( (swap_x_and_y_to_start == TRUE) &&
+ (self->priv->selection.width != new_starting_width))
+ {
+ gth_image_selector_set_selection_width (self, self->priv->selection.width);
+ gth_image_selector_set_selection_height (self, self->priv->selection.height);
+ }
+ }
+}
+
+
+double
+gth_image_selector_get_ratio (GthImageSelector *self)
+{
+ return self->priv->ratio;
+}
+
+
+gboolean
+gth_image_selector_get_use_ratio (GthImageSelector *self)
+{
+ return self->priv->use_ratio;
+}
+
+
+void
+gth_image_selector_set_mask_visible (GthImageSelector *self,
+ gboolean visible)
+{
+ if (visible == self->priv->mask_visible)
+ return;
+
+ self->priv->mask_visible = visible;
+ gtk_widget_queue_draw (GTK_WIDGET (self->priv->viewer));
+ g_signal_emit (G_OBJECT (self),
+ signals[MASK_VISIBILITY_CHANGED],
+ 0);
+}
+
+
+gboolean
+gth_image_selector_get_mask_visible (GthImageSelector *self)
+{
+ return self->priv->mask_visible;
+}
+
diff --git a/gthumb/gth-image-selector.h b/gthumb/gth-image-selector.h
new file mode 100644
index 0000000..2f60445
--- /dev/null
+++ b/gthumb/gth-image-selector.h
@@ -0,0 +1,98 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GTH_IMAGE_SELECTOR_H
+#define GTH_IMAGE_SELECTOR_H
+
+#include <glib.h>
+#include <glib-object.h>
+#include "gth-image-tool.h"
+#include "gth-image-viewer.h"
+
+G_BEGIN_DECLS
+
+#define GTH_TYPE_IMAGE_SELECTOR (gth_image_selector_get_type ())
+#define GTH_IMAGE_SELECTOR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTH_TYPE_IMAGE_SELECTOR, GthImageSelector))
+#define GTH_IMAGE_SELECTOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTH_TYPE_IMAGE_SELECTOR, GthImageSelectorClass))
+#define GTH_IS_IMAGE_SELECTOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTH_TYPE_IMAGE_SELECTOR))
+#define GTH_IS_IMAGE_SELECTOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTH_TYPE_IMAGE_SELECTOR))
+#define GTH_IMAGE_SELECTOR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GTH_TYPE_IMAGE_SELECTOR, GthImageSelectorClass))
+
+typedef struct _GthImageSelector GthImageSelector;
+typedef struct _GthImageSelectorClass GthImageSelectorClass;
+typedef struct _GthImageSelectorPrivate GthImageSelectorPrivate;
+
+typedef enum {
+ GTH_SELECTOR_TYPE_REGION,
+ GTH_SELECTOR_TYPE_POINT
+} GthSelectorType;
+
+struct _GthImageSelector
+{
+ GObject __parent;
+ GthImageSelectorPrivate *priv;
+};
+
+struct _GthImageSelectorClass
+{
+ GObjectClass __parent_class;
+
+ /*< signals >*/
+
+ void (* selection_changed) (GthImageSelector *selector);
+ void (* selected) (GthImageSelector *selector,
+ int x,
+ int y);
+ void (* motion_notify) (GthImageSelector *selector,
+ int x,
+ int y);
+ void (* mask_visibility_changed) (GthImageSelector *selector);
+};
+
+GType gth_image_selector_get_type (void);
+GthImageTool * gth_image_selector_new (GthImageViewer *viewer,
+ GthSelectorType type);
+void gth_image_selector_set_selection_x (GthImageSelector *selector,
+ int x);
+void gth_image_selector_set_selection_y (GthImageSelector *selector,
+ int y);
+void gth_image_selector_set_selection_width (GthImageSelector *selector,
+ int width);
+void gth_image_selector_set_selection_height (GthImageSelector *selector,
+ int height);
+void gth_image_selector_set_selection (GthImageSelector *selector,
+ GdkRectangle selection);
+void gth_image_selector_get_selection (GthImageSelector *selector,
+ GdkRectangle *selection);
+void gth_image_selector_set_ratio (GthImageSelector *selector,
+ gboolean use_ratio,
+ double ratio,
+ gboolean swap_x_and_y_to_start);
+double gth_image_selector_get_ratio (GthImageSelector *selector);
+gboolean gth_image_selector_get_use_ratio (GthImageSelector *selector);
+void gth_image_selector_set_mask_visible (GthImageSelector *selector,
+ gboolean visible);
+gboolean gth_image_selector_get_mask_visible (GthImageSelector *selector);
+
+G_END_DECLS
+
+#endif /* GTH_IMAGE_SELECTOR_H */
diff --git a/gthumb/gth-image-tool.c b/gthumb/gth-image-tool.c
new file mode 100644
index 0000000..7c78382
--- /dev/null
+++ b/gthumb/gth-image-tool.c
@@ -0,0 +1,118 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include "gth-image-tool.h"
+
+
+GType
+gth_image_tool_get_type (void)
+{
+ static GType type_id = 0;
+ if (type_id == 0) {
+ static const GTypeInfo g_define_type_info = {
+ sizeof (GthImageToolIface),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) NULL,
+ (GClassFinalizeFunc) NULL,
+ NULL,
+ 0,
+ 0,
+ (GInstanceInitFunc) NULL,
+ NULL
+ };
+ type_id = g_type_register_static (G_TYPE_INTERFACE,
+ "GthImageTool",
+ &g_define_type_info,
+ 0);
+ }
+ return type_id;
+}
+
+
+void
+gth_image_tool_realize (GthImageTool *self)
+{
+ GTH_IMAGE_TOOL_GET_INTERFACE (self)->realize (self);
+}
+
+
+void
+gth_image_tool_unrealize (GthImageTool *self)
+{
+ GTH_IMAGE_TOOL_GET_INTERFACE (self)->unrealize (self);
+}
+
+
+void
+gth_image_tool_size_allocate (GthImageTool *self,
+ GtkAllocation *allocation)
+{
+ GTH_IMAGE_TOOL_GET_INTERFACE (self)->size_allocate (self, allocation);
+}
+
+
+void
+gth_image_tool_expose (GthImageTool *self,
+ GdkRectangle *paint_area)
+{
+ GTH_IMAGE_TOOL_GET_INTERFACE (self)->expose (self, paint_area);
+}
+
+
+gboolean
+gth_image_tool_button_press (GthImageTool *self,
+ GdkEventButton *event)
+{
+ return GTH_IMAGE_TOOL_GET_INTERFACE (self)->button_press (self, event);
+}
+
+
+gboolean
+gth_image_tool_button_release (GthImageTool *self,
+ GdkEventButton *event)
+{
+ return GTH_IMAGE_TOOL_GET_INTERFACE (self)->button_release (self, event);
+}
+
+
+gboolean
+gth_image_tool_motion_notify (GthImageTool *self,
+ GdkEventMotion *event)
+{
+ return GTH_IMAGE_TOOL_GET_INTERFACE (self)->motion_notify (self, event);
+}
+
+
+void
+gth_image_tool_image_changed (GthImageTool *self)
+{
+ GTH_IMAGE_TOOL_GET_INTERFACE (self)->image_changed (self);
+}
+
+
+void
+gth_image_tool_zoom_changed (GthImageTool *self)
+{
+ GTH_IMAGE_TOOL_GET_INTERFACE (self)->zoom_changed (self);
+}
diff --git a/gthumb/gth-image-tool.h b/gthumb/gth-image-tool.h
new file mode 100644
index 0000000..2cbb482
--- /dev/null
+++ b/gthumb/gth-image-tool.h
@@ -0,0 +1,75 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GTH_IMAGE_TOOL_H
+#define GTH_IMAGE_TOOL_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GTH_TYPE_IMAGE_TOOL (gth_image_tool_get_type ())
+#define GTH_IMAGE_TOOL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTH_TYPE_IMAGE_TOOL, GthImageTool))
+#define GTH_IS_IMAGE_TOOL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTH_TYPE_IMAGE_TOOL))
+#define GTH_IMAGE_TOOL_GET_INTERFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), GTH_TYPE_IMAGE_TOOL, GthImageToolIface))
+
+typedef struct _GthImageTool GthImageTool;
+typedef struct _GthImageToolIface GthImageToolIface;
+
+struct _GthImageToolIface {
+ GTypeInterface parent_iface;
+
+ void (*realize) (GthImageTool *self);
+ void (*unrealize) (GthImageTool *self);
+ void (*size_allocate) (GthImageTool *self,
+ GtkAllocation *allocation);
+ void (*expose) (GthImageTool *self,
+ GdkRectangle *paint_area);
+ gboolean (*button_press) (GthImageTool *self,
+ GdkEventButton *event);
+ gboolean (*button_release) (GthImageTool *self,
+ GdkEventButton *event);
+ gboolean (*motion_notify) (GthImageTool *self,
+ GdkEventMotion *event);
+ void (*image_changed) (GthImageTool *self);
+ void (*zoom_changed) (GthImageTool *self);
+};
+
+GType gth_image_tool_get_type (void);
+void gth_image_tool_realize (GthImageTool *self);
+void gth_image_tool_unrealize (GthImageTool *self);
+void gth_image_tool_size_allocate (GthImageTool *self,
+ GtkAllocation *allocation);
+void gth_image_tool_expose (GthImageTool *self,
+ GdkRectangle *paint_area);
+gboolean gth_image_tool_button_press (GthImageTool *self,
+ GdkEventButton *event);
+gboolean gth_image_tool_button_release (GthImageTool *self,
+ GdkEventButton *event);
+gboolean gth_image_tool_motion_notify (GthImageTool *self,
+ GdkEventMotion *event);
+void gth_image_tool_image_changed (GthImageTool *self);
+void gth_image_tool_zoom_changed (GthImageTool *self);
+
+G_END_DECLS
+
+#endif /* GTH_IMAGE_TOOL_H */
diff --git a/gthumb/gth-image-viewer.c b/gthumb/gth-image-viewer.c
new file mode 100644
index 0000000..c5ede2d
--- /dev/null
+++ b/gthumb/gth-image-viewer.c
@@ -0,0 +1,2692 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2001-2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <stdlib.h>
+#include <math.h>
+#include <glib/gi18n.h>
+#include <gdk/gdkkeysyms.h>
+#include <gtk/gtk.h>
+#include "gth-cursors.h"
+#include "gth-enum-types.h"
+#include "gth-image-dragger.h"
+#include "gth-image-viewer.h"
+#include "gth-marshal.h"
+#include "glib-utils.h"
+
+#define COLOR_GRAY_00 0x00000000
+#define COLOR_GRAY_33 0x00333333
+#define COLOR_GRAY_66 0x00666666
+#define COLOR_GRAY_99 0x00999999
+#define COLOR_GRAY_CC 0x00cccccc
+#define COLOR_GRAY_FF 0x00ffffff
+
+#define GTH_IMAGE_VIEWER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GTH_TYPE_IMAGE_VIEWER, GthImageViewerPrivate))
+
+#define DRAG_THRESHOLD 1 /* When dragging the image ignores movements
+ * smaller than this value. */
+#define MINIMUM_DELAY 10 /* When an animation frame has a 0 milli seconds
+ * delay use this delay instead. */
+#define STEP_INCREMENT 20.0 /* Scroll increment. */
+
+enum {
+ CLICKED,
+ IMAGE_READY,
+ ZOOM_IN,
+ ZOOM_OUT,
+ SET_ZOOM,
+ SET_FIT_MODE,
+ ZOOM_CHANGED,
+ SIZE_CHANGED,
+ REPAINTED,
+ MOUSE_WHEEL_SCROLL,
+ SCROLL,
+ LAST_SIGNAL
+};
+
+
+struct _GthImageViewerPrivate {
+ gboolean is_animation;
+ gboolean play_animation;
+ gboolean rendering;
+ gboolean cursor_visible;
+
+ gboolean frame_visible;
+ int frame_border;
+ int frame_border2;
+
+ GthTranspType transp_type;
+ GthCheckType check_type;
+ int check_size;
+ guint32 check_color1;
+ guint32 check_color2;
+
+ guint anim_id;
+ GdkPixbuf *frame_pixbuf;
+ int frame_delay;
+
+ GthImageLoader *loader;
+ GdkPixbufAnimation *anim;
+ GdkPixbufAnimationIter *iter;
+ GTimeVal time; /* Timer used to get the right
+ * frame. */
+
+ GthImageTool *tool;
+
+ GdkCursor *cursor;
+ GdkCursor *cursor_void;
+
+ double zoom_level;
+ guint zoom_quality : 1; /* A ZoomQualityType value. */
+ guint zoom_change : 3; /* A ZoomChangeType value. */
+
+ GthFit fit;
+ gboolean doing_zoom_fit; /* Whether we are performing
+ * a zoom to fit the window. */
+ gboolean is_void; /* If TRUE do not show anything.
+ * Itis resetted to FALSE we an
+ * image is loaded. */
+
+ gboolean double_click;
+ gboolean just_focused;
+
+ GdkPixbuf *paint_pixbuf;
+ int paint_max_width;
+ int paint_max_height;
+ int paint_bps;
+ GdkColorspace paint_color_space;
+
+ gboolean black_bg;
+
+ gboolean skip_zoom_change;
+ gboolean skip_size_change;
+
+ gboolean next_scroll_repaint; /* used in fullscreen mode to
+ * delete the comment before
+ * scrolling. */
+ gboolean reset_scrollbars;
+};
+
+
+static gpointer parent_class = NULL;
+static guint gth_image_viewer_signals[LAST_SIGNAL] = { 0 };
+
+
+static void
+gth_image_viewer_finalize (GObject *object)
+{
+ GthImageViewer* viewer;
+
+ g_return_if_fail (object != NULL);
+ g_return_if_fail (GTH_IS_IMAGE_VIEWER (object));
+
+ viewer = GTH_IMAGE_VIEWER (object);
+
+ if (viewer->priv->anim_id != 0) {
+ g_source_remove (viewer->priv->anim_id);
+ viewer->priv->anim_id = 0;
+ }
+
+ if (viewer->priv->loader != NULL) {
+ g_object_unref (viewer->priv->loader);
+ viewer->priv->loader = NULL;
+ }
+
+ if (viewer->priv->anim != NULL) {
+ g_object_unref (viewer->priv->anim);
+ viewer->priv->anim = NULL;
+ }
+
+ if (viewer->priv->iter != NULL) {
+ g_object_unref (viewer->priv->iter);
+ viewer->priv->iter = NULL;
+ }
+
+ if (viewer->priv->cursor != NULL) {
+ gdk_cursor_unref (viewer->priv->cursor);
+ viewer->priv->cursor = NULL;
+ }
+
+ if (viewer->priv->cursor_void != NULL) {
+ gdk_cursor_unref (viewer->priv->cursor_void);
+ viewer->priv->cursor_void = NULL;
+ }
+
+ if (viewer->hadj != NULL) {
+ g_signal_handlers_disconnect_by_data (G_OBJECT (viewer->hadj), viewer);
+ g_object_unref (viewer->hadj);
+ viewer->hadj = NULL;
+ }
+ if (viewer->vadj != NULL) {
+ g_signal_handlers_disconnect_by_data (G_OBJECT (viewer->vadj), viewer);
+ g_object_unref (viewer->vadj);
+ viewer->vadj = NULL;
+ }
+
+ if (viewer->priv->paint_pixbuf != NULL) {
+ g_object_unref (viewer->priv->paint_pixbuf);
+ viewer->priv->paint_pixbuf = NULL;
+ }
+
+ /* Chain up */
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+
+static gdouble zooms[] = { 0.05, 0.07, 0.10,
+ 0.15, 0.20, 0.30, 0.50, 0.75, 1.0,
+ 1.5 , 2.0 , 3.0 , 5.0 , 7.5, 10.0,
+ 15.0, 20.0, 30.0, 50.0, 75.0, 100.0};
+
+static const int nzooms = sizeof (zooms) / sizeof (gdouble);
+
+
+static gdouble
+get_next_zoom (gdouble zoom)
+{
+ gint i;
+
+ i = 0;
+ while ((i < nzooms) && (zooms[i] <= zoom))
+ i++;
+ i = CLAMP (i, 0, nzooms - 1);
+
+ return zooms[i];
+}
+
+
+static gdouble
+get_prev_zoom (gdouble zoom)
+{
+ gint i;
+
+ i = nzooms - 1;
+ while ((i >= 0) && (zooms[i] >= zoom))
+ i--;
+ i = CLAMP (i, 0, nzooms - 1);
+
+ return zooms[i];
+}
+
+
+static void
+get_zoomed_size (GthImageViewer *viewer,
+ int *width,
+ int *height,
+ double zoom_level)
+{
+ if (gth_image_viewer_get_current_pixbuf (viewer) == NULL) {
+ *width = 0;
+ *height = 0;
+ }
+ else {
+ int w, h;
+
+ w = gth_image_viewer_get_image_width (viewer);
+ h = gth_image_viewer_get_image_height (viewer);
+
+ *width = (int) floor ((double) w * zoom_level);
+ *height = (int) floor ((double) h * zoom_level);
+ }
+}
+
+
+static void
+_gth_image_viewer_update_image_area (GthImageViewer *viewer)
+{
+ GtkWidget *widget;
+ int pixbuf_width;
+ int pixbuf_height;
+ int gdk_width;
+ int gdk_height;
+
+ widget = GTK_WIDGET (viewer);
+ get_zoomed_size (viewer, &pixbuf_width, &pixbuf_height, viewer->priv->zoom_level);
+ gdk_width = widget->allocation.width - viewer->priv->frame_border2;
+ gdk_height = widget->allocation.height - viewer->priv->frame_border2;
+
+ viewer->image_area.x = MAX (viewer->priv->frame_border, (gdk_width - pixbuf_width) / 2);
+ viewer->image_area.y = MAX (viewer->priv->frame_border, (gdk_height - pixbuf_height) / 2);
+ viewer->image_area.width = MIN (pixbuf_width, gdk_width);
+ viewer->image_area.height = MIN (pixbuf_height, gdk_height);
+}
+
+
+static void
+set_zoom (GthImageViewer *viewer,
+ gdouble zoom_level,
+ int center_x,
+ int center_y)
+{
+ GtkWidget *widget = (GtkWidget*) viewer;
+ gdouble zoom_ratio;
+ int gdk_width, gdk_height;
+
+ g_return_if_fail (viewer != NULL);
+ g_return_if_fail (viewer->priv->loader != NULL);
+
+ gdk_width = widget->allocation.width - viewer->priv->frame_border2;
+ gdk_height = widget->allocation.height - viewer->priv->frame_border2;
+
+ /* try to keep the center of the view visible. */
+
+ zoom_ratio = zoom_level / viewer->priv->zoom_level;
+ viewer->x_offset = ((viewer->x_offset + center_x) * zoom_ratio - gdk_width / 2);
+ viewer->y_offset = ((viewer->y_offset + center_y) * zoom_ratio - gdk_height / 2);
+
+ /* reset zoom_fit unless we are performing a zoom to fit. */
+
+ if (! viewer->priv->doing_zoom_fit)
+ viewer->priv->fit = GTH_FIT_NONE;
+
+ viewer->priv->zoom_level = zoom_level;
+
+ _gth_image_viewer_update_image_area (viewer);
+ gth_image_tool_zoom_changed (viewer->priv->tool);
+
+ if (! viewer->priv->doing_zoom_fit) {
+ gtk_widget_queue_resize (GTK_WIDGET (viewer));
+ gtk_widget_queue_draw (GTK_WIDGET (viewer));
+ }
+
+ if (! viewer->priv->skip_zoom_change)
+ g_signal_emit (G_OBJECT (viewer),
+ gth_image_viewer_signals[ZOOM_CHANGED],
+ 0);
+ else
+ viewer->priv->skip_zoom_change = FALSE;
+}
+
+
+static int
+to_255 (int v)
+{
+ return v * 255 / 65535;
+}
+
+
+static void
+gth_image_viewer_realize (GtkWidget *widget)
+{
+ GthImageViewer *viewer;
+ GdkWindowAttr attributes;
+ int attributes_mask;
+
+ g_return_if_fail (GTH_IS_IMAGE_VIEWER (widget));
+
+ viewer = GTH_IMAGE_VIEWER (widget);
+ GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
+
+ attributes.window_type = GDK_WINDOW_CHILD;
+ attributes.x = widget->allocation.x;
+ attributes.y = widget->allocation.y;
+ attributes.width = widget->allocation.width;
+ attributes.height = widget->allocation.height;
+ attributes.wclass = GDK_INPUT_OUTPUT;
+ attributes.visual = gtk_widget_get_visual (widget);
+ attributes.colormap = gtk_widget_get_colormap (widget);
+ attributes.event_mask = (gtk_widget_get_events (widget)
+ | GDK_EXPOSURE_MASK
+ | GDK_BUTTON_PRESS_MASK
+ | GDK_BUTTON_RELEASE_MASK
+ | GDK_POINTER_MOTION_MASK
+ | GDK_POINTER_MOTION_HINT_MASK
+ | GDK_BUTTON_MOTION_MASK);
+
+ attributes_mask = (GDK_WA_X
+ | GDK_WA_Y
+ | GDK_WA_VISUAL
+ | GDK_WA_COLORMAP);
+ widget->window = gdk_window_new (gtk_widget_get_parent_window (widget),
+ &attributes,
+ attributes_mask);
+ gdk_window_set_user_data (widget->window, viewer);
+
+ widget->style = gtk_style_attach (widget->style, widget->window);
+ gtk_style_set_background (widget->style, widget->window, GTK_STATE_NORMAL);
+
+ viewer->priv->cursor = gdk_cursor_new (GDK_LEFT_PTR);
+ viewer->priv->cursor_void = gth_cursor_get (widget->window, GTH_CURSOR_VOID);
+ gdk_window_set_cursor (widget->window, viewer->priv->cursor);
+
+ if (viewer->priv->transp_type == GTH_TRANSP_TYPE_NONE) {
+ GdkColor color;
+ guint base_color;
+
+ color = GTK_WIDGET (viewer)->style->bg[GTK_STATE_NORMAL];
+ base_color = (0xFF000000
+ | (to_255 (color.red) << 16)
+ | (to_255 (color.green) << 8)
+ | (to_255 (color.blue) << 0));
+
+ viewer->priv->check_color1 = base_color;
+ viewer->priv->check_color2 = base_color;
+ }
+
+ gth_image_tool_realize (viewer->priv->tool);
+}
+
+
+static void
+gth_image_viewer_unrealize (GtkWidget *widget)
+{
+ GthImageViewer *viewer;
+
+ g_return_if_fail (GTH_IS_IMAGE_VIEWER (widget));
+
+ viewer = GTH_IMAGE_VIEWER (widget);
+
+ if (viewer->priv->cursor) {
+ gdk_cursor_unref (viewer->priv->cursor);
+ viewer->priv->cursor = NULL;
+ }
+ if (viewer->priv->cursor_void) {
+ gdk_cursor_unref (viewer->priv->cursor_void);
+ viewer->priv->cursor_void = NULL;
+ }
+
+ gth_image_tool_unrealize (viewer->priv->tool);
+
+ GTK_WIDGET_CLASS (parent_class)->unrealize (widget);
+}
+
+
+static void
+zoom_to_fit (GthImageViewer *viewer)
+{
+ GdkPixbuf *buf;
+ int gdk_width, gdk_height;
+ double x_level, y_level;
+ double new_zoom_level;
+
+ buf = gth_image_viewer_get_current_pixbuf (viewer);
+
+ gdk_width = GTK_WIDGET (viewer)->allocation.width - viewer->priv->frame_border2;
+ gdk_height = GTK_WIDGET (viewer)->allocation.height - viewer->priv->frame_border2;
+ x_level = (double) gdk_width / gdk_pixbuf_get_width (buf);
+ y_level = (double) gdk_height / gdk_pixbuf_get_height (buf);
+
+ new_zoom_level = (x_level < y_level) ? x_level : y_level;
+ if (new_zoom_level > 0.0) {
+ viewer->priv->doing_zoom_fit = TRUE;
+ gth_image_viewer_set_zoom (viewer, new_zoom_level);
+ viewer->priv->doing_zoom_fit = FALSE;
+ }
+}
+
+
+static void
+zoom_to_fit_width (GthImageViewer *viewer)
+{
+ GdkPixbuf *buf;
+ double new_zoom_level;
+ int gdk_width;
+
+ buf = gth_image_viewer_get_current_pixbuf (viewer);
+
+ gdk_width = GTK_WIDGET (viewer)->allocation.width - viewer->priv->frame_border2;
+ new_zoom_level = (double) gdk_width / gdk_pixbuf_get_width (buf);
+
+ if (new_zoom_level > 0.0) {
+ viewer->priv->doing_zoom_fit = TRUE;
+ gth_image_viewer_set_zoom (viewer, new_zoom_level);
+ viewer->priv->doing_zoom_fit = FALSE;
+ }
+}
+
+
+static void
+gth_image_viewer_size_allocate (GtkWidget *widget,
+ GtkAllocation *allocation)
+{
+ GthImageViewer *viewer;
+ int gdk_width, gdk_height;
+ GdkPixbuf *current_pixbuf;
+
+ viewer = GTH_IMAGE_VIEWER (widget);
+
+ widget->allocation = *allocation;
+ gdk_width = allocation->width - viewer->priv->frame_border2;
+ gdk_height = allocation->height - viewer->priv->frame_border2;
+
+ current_pixbuf = gth_image_viewer_get_current_pixbuf (viewer);
+
+ /* If a fit type is active update the zoom level. */
+
+ if (! viewer->priv->is_void && (current_pixbuf != NULL)) {
+ switch (viewer->priv->fit) {
+ case GTH_FIT_SIZE:
+ zoom_to_fit (viewer);
+ break;
+ case GTH_FIT_SIZE_IF_LARGER:
+ if ((gdk_width < gdk_pixbuf_get_width (current_pixbuf))
+ || (gdk_height < gdk_pixbuf_get_height (current_pixbuf)))
+ {
+ zoom_to_fit (viewer);
+ }
+ else {
+ viewer->priv->doing_zoom_fit = TRUE;
+ gth_image_viewer_set_zoom (viewer, 1.0);
+ viewer->priv->doing_zoom_fit = FALSE;
+ }
+ break;
+ case GTH_FIT_WIDTH:
+ zoom_to_fit_width (viewer);
+ break;
+ case GTH_FIT_WIDTH_IF_LARGER:
+ if (gdk_width < gdk_pixbuf_get_width (current_pixbuf)) {
+ zoom_to_fit_width (viewer);
+ }
+ else {
+ viewer->priv->doing_zoom_fit = TRUE;
+ gth_image_viewer_set_zoom (viewer, 1.0);
+ viewer->priv->doing_zoom_fit = FALSE;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ /* Check whether the offset is still valid. */
+
+ if (current_pixbuf != NULL) {
+ int width, height;
+
+ get_zoomed_size (viewer, &width, &height, viewer->priv->zoom_level);
+
+ if (width > gdk_width)
+ viewer->x_offset = CLAMP (viewer->x_offset,
+ 0,
+ width - gdk_width);
+ else
+ viewer->x_offset = 0;
+
+ if (height > gdk_height)
+ viewer->y_offset = CLAMP (viewer->y_offset,
+ 0,
+ height - gdk_height);
+ else
+ viewer->y_offset = 0;
+
+ if ((width != viewer->hadj->upper) || (height != viewer->vadj->upper))
+ g_signal_emit (G_OBJECT (viewer),
+ gth_image_viewer_signals[SIZE_CHANGED],
+ 0);
+
+ /* Change adjustment values. */
+
+ viewer->hadj->lower = 0.0;
+ viewer->hadj->upper = width;
+ viewer->hadj->value = viewer->x_offset;
+ viewer->hadj->step_increment = STEP_INCREMENT;
+ viewer->hadj->page_increment = gdk_width / 2;
+ viewer->hadj->page_size = gdk_width;
+
+ viewer->vadj->lower = 0.0;
+ viewer->vadj->upper = height;
+ viewer->vadj->value = viewer->y_offset;
+ viewer->vadj->step_increment = STEP_INCREMENT;
+ viewer->vadj->page_increment = gdk_height / 2;
+ viewer->vadj->page_size = gdk_height;
+ }
+ else {
+ viewer->hadj->lower = 0.0;
+ viewer->hadj->upper = 1.0;
+ viewer->hadj->value = 0.0;
+ viewer->hadj->page_size = 1.0;
+
+ viewer->vadj->lower = 0.0;
+ viewer->vadj->upper = 1.0;
+ viewer->vadj->value = 0.0;
+ viewer->vadj->page_size = 1.0;
+ }
+
+ _gth_image_viewer_update_image_area (viewer);
+
+ g_signal_handlers_block_by_data (G_OBJECT (viewer->hadj), viewer);
+ g_signal_handlers_block_by_data (G_OBJECT (viewer->vadj), viewer);
+ gtk_adjustment_changed (viewer->hadj);
+ gtk_adjustment_changed (viewer->vadj);
+ g_signal_handlers_unblock_by_data (G_OBJECT (viewer->hadj), viewer);
+ g_signal_handlers_unblock_by_data (G_OBJECT (viewer->vadj), viewer);
+
+ /**/
+
+ if (GTK_WIDGET_REALIZED (widget))
+ gdk_window_move_resize (widget->window,
+ allocation->x, allocation->y,
+ allocation->width, allocation->height);
+
+ gth_image_tool_size_allocate (viewer->priv->tool, allocation);
+
+ if (! viewer->priv->skip_size_change)
+ g_signal_emit (G_OBJECT (viewer),
+ gth_image_viewer_signals[SIZE_CHANGED],
+ 0);
+ else
+ viewer->priv->skip_size_change = FALSE;
+}
+
+
+static gboolean
+gth_image_viewer_focus_in (GtkWidget *widget,
+ GdkEventFocus *event)
+{
+ GTK_WIDGET_SET_FLAGS (widget, GTK_HAS_FOCUS);
+ gtk_widget_queue_draw (widget);
+
+ return TRUE;
+}
+
+
+static gboolean
+gth_image_viewer_focus_out (GtkWidget *widget,
+ GdkEventFocus *event)
+{
+ GTK_WIDGET_UNSET_FLAGS (widget, GTK_HAS_FOCUS);
+ gtk_widget_queue_draw (widget);
+
+ return TRUE;
+}
+
+
+static int
+gth_image_viewer_key_press (GtkWidget *widget,
+ GdkEventKey *event)
+{
+ gboolean handled;
+
+ handled = gtk_bindings_activate (GTK_OBJECT (widget),
+ event->keyval,
+ event->state);
+ if (handled)
+ return TRUE;
+
+ if (GTK_WIDGET_CLASS (parent_class)->key_press_event &&
+ GTK_WIDGET_CLASS (parent_class)->key_press_event (widget, event))
+ return TRUE;
+
+ return FALSE;
+}
+
+
+static void
+create_pixbuf_from_iter (GthImageViewer *viewer)
+{
+ GdkPixbufAnimationIter *iter;
+
+ iter = viewer->priv->iter;
+ viewer->priv->frame_pixbuf = gdk_pixbuf_animation_iter_get_pixbuf (iter);
+ viewer->priv->frame_delay = gdk_pixbuf_animation_iter_get_delay_time (iter);
+}
+
+
+static gboolean
+change_frame_cb (gpointer data)
+{
+ GthImageViewer *viewer = data;
+
+ if (viewer->priv->anim_id != 0) {
+ g_source_remove (viewer->priv->anim_id);
+ viewer->priv->anim_id = 0;
+ }
+
+ g_time_val_add (&viewer->priv->time, (glong) viewer->priv->frame_delay * 1000);
+ gdk_pixbuf_animation_iter_advance (viewer->priv->iter, &viewer->priv->time);
+
+ create_pixbuf_from_iter (viewer);
+
+ viewer->priv->skip_zoom_change = TRUE;
+ viewer->priv->skip_size_change = TRUE;
+
+ gth_image_viewer_update_view (viewer);
+
+ return FALSE;
+}
+
+
+static void
+queue_frame_change (GthImageViewer *viewer)
+{
+ if (! viewer->priv->is_void
+ && viewer->priv->is_animation
+ && viewer->priv->play_animation
+ && (viewer->priv->anim_id == 0))
+ {
+ viewer->priv->anim_id = g_timeout_add (MAX (MINIMUM_DELAY, viewer->priv->frame_delay),
+ change_frame_cb,
+ viewer);
+ }
+}
+
+
+static int
+gth_image_viewer_expose (GtkWidget *widget,
+ GdkEventExpose *event)
+{
+ GthImageViewer *viewer;
+ int gdk_width;
+ int gdk_height;
+
+ viewer = GTH_IMAGE_VIEWER (widget);
+
+ if (viewer->priv->rendering)
+ return FALSE;
+
+ viewer->priv->rendering = TRUE;
+
+ /* Draw the background. */
+
+ gdk_width = widget->allocation.width - viewer->priv->frame_border2;
+ gdk_height = widget->allocation.height - viewer->priv->frame_border2;
+
+ if ((viewer->image_area.x > viewer->priv->frame_border)
+ || (viewer->image_area.y > viewer->priv->frame_border)
+ || (viewer->image_area.width < gdk_width)
+ || (viewer->image_area.height < gdk_height))
+ {
+ int rx, ry, rw, rh;
+ GdkGC *gc;
+
+ if (viewer->priv->black_bg)
+ gc = widget->style->black_gc;
+ else
+ gc = widget->style->bg_gc[GTK_STATE_NORMAL];
+
+ if (gth_image_viewer_get_current_pixbuf (viewer) == NULL) {
+ gdk_draw_rectangle (widget->window,
+ gc,
+ TRUE,
+ 0, 0,
+ widget->allocation.width,
+ widget->allocation.height);
+ }
+ else {
+ /* If an image is present draw in four phases to avoid
+ * flickering. */
+
+ /* Top rectangle. */
+ rx = 0;
+ ry = 0;
+ rw = widget->allocation.width;
+ rh = viewer->image_area.y;
+ if ((rw > 0) && (rh > 0))
+ gdk_draw_rectangle (widget->window,
+ gc,
+ TRUE,
+ rx, ry,
+ rw, rh);
+
+ /* Bottom rectangle. */
+ rx = 0;
+ ry = viewer->image_area.y + viewer->image_area.height;
+ rw = widget->allocation.width;
+ rh = widget->allocation.height - viewer->image_area.y - viewer->image_area.height;
+ if ((rw > 0) && (rh > 0))
+ gdk_draw_rectangle (widget->window,
+ gc,
+ TRUE,
+ rx, ry,
+ rw, rh);
+
+ /* Left rectangle. */
+ rx = 0;
+ ry = viewer->image_area.y - 1;
+ rw = viewer->image_area.x;
+ rh = viewer->image_area.height + 2;
+ if ((rw > 0) && (rh > 0))
+ gdk_draw_rectangle (widget->window,
+ gc,
+ TRUE,
+ rx, ry,
+ rw, rh);
+
+ /* Right rectangle. */
+ rx = viewer->image_area.x + viewer->image_area.width;
+ ry = viewer->image_area.y - 1;
+ rw = widget->allocation.width - viewer->image_area.x - viewer->image_area.width;
+ rh = viewer->image_area.height + 2;
+ if ((rw > 0) && (rh > 0))
+ gdk_draw_rectangle (widget->window,
+ gc,
+ TRUE,
+ rx, ry,
+ rw, rh);
+ }
+ }
+
+ /* Draw the frame. */
+
+ if ((viewer->priv->frame_border > 0)
+ && (gth_image_viewer_get_current_pixbuf (viewer) != NULL))
+ {
+ int x1, y1, x2, y2;
+ GdkGC *gc;
+
+ if (viewer->priv->black_bg)
+ gc = widget->style->black_gc;
+ else
+ gc = widget->style->light_gc[GTK_STATE_NORMAL];
+ /*gc = widget->style->dark_gc[GTK_STATE_NORMAL];*/
+
+ x1 = viewer->image_area.x + viewer->image_area.width;
+ y1 = viewer->image_area.y - 1;
+ x2 = viewer->image_area.x + viewer->image_area.width;
+ y2 = viewer->image_area.y + viewer->image_area.height;
+ gdk_draw_line (widget->window,
+ gc,
+ x1, y1,
+ x2, y2);
+
+ x1 = viewer->image_area.x - 1;
+ y1 = viewer->image_area.y + viewer->image_area.height;
+ x2 = viewer->image_area.x + viewer->image_area.width;
+ y2 = viewer->image_area.y + viewer->image_area.height;
+ gdk_draw_line (widget->window,
+ gc,
+ x1, y1,
+ x2, y2);
+
+ if (viewer->priv->black_bg)
+ gc = widget->style->black_gc;
+ else
+ gc = widget->style->dark_gc[GTK_STATE_NORMAL];
+
+ x1 = viewer->image_area.x - 1;
+ y1 = viewer->image_area.y - 1;
+ x2 = viewer->image_area.x - 1;
+ y2 = viewer->image_area.y + viewer->image_area.height;
+ gdk_draw_line (widget->window,
+ gc,
+ x1, y1,
+ x2, y2);
+
+ x1 = viewer->image_area.x - 1;
+ y1 = viewer->image_area.y - 1;
+ x2 = viewer->image_area.x + viewer->image_area.width;
+ y2 = viewer->image_area.y - 1;
+ gdk_draw_line (widget->window,
+ gc,
+ x1, y1,
+ x2, y2);
+ }
+
+ gth_image_tool_expose (viewer->priv->tool, &event->area);
+
+ viewer->priv->rendering = FALSE;
+
+ /* Draw the focus. */
+
+#if 0
+ if (GTK_WIDGET_HAS_FOCUS (widget)) {
+ GdkRectangle r;
+
+ r.x = 0;
+ r.y = 0;
+ r.width = gdk_width + 2;
+ r.height = gdk_height + 2;
+
+ gtk_paint_focus (widget->style,
+ widget->window,
+ widget->state,
+ &r,
+ widget, NULL,
+ 0, 0, gdk_width + 2, gdk_height + 2);
+ }
+#endif
+
+ queue_frame_change (viewer);
+
+ return FALSE;
+}
+
+
+static gboolean
+gth_image_viewer_button_press (GtkWidget *widget,
+ GdkEventButton *event)
+{
+ GthImageViewer *viewer = GTH_IMAGE_VIEWER (widget);
+ int retval;
+
+ if (! GTK_WIDGET_HAS_FOCUS (widget)) {
+ gtk_widget_grab_focus (widget);
+ viewer->priv->just_focused = TRUE;
+ }
+
+ if (viewer->dragging)
+ return FALSE;
+
+ if ((event->type == GDK_2BUTTON_PRESS)
+ || (event->type == GDK_3BUTTON_PRESS))
+ {
+ viewer->priv->double_click = TRUE;
+ return FALSE;
+ }
+ else
+ viewer->priv->double_click = FALSE;
+
+ retval = gth_image_tool_button_press (viewer->priv->tool, event);
+
+ if (viewer->pressed) {
+ viewer->event_x_start = viewer->event_x_prev = event->x;
+ viewer->event_y_start = viewer->event_y_prev = event->y;
+ viewer->drag_x = viewer->drag_x_start = viewer->drag_x_prev = event->x + viewer->x_offset;
+ viewer->drag_y = viewer->drag_y_start = viewer->drag_y_prev = event->y + viewer->y_offset;
+ }
+
+ return retval;
+}
+
+
+static gboolean
+gth_image_viewer_button_release (GtkWidget *widget,
+ GdkEventButton *event)
+{
+ GthImageViewer *viewer = GTH_IMAGE_VIEWER (widget);
+
+ if ((event->button == 1)
+ && ! viewer->dragging
+ && ! viewer->priv->double_click
+ && ! viewer->priv->just_focused)
+ {
+ g_signal_emit (G_OBJECT (viewer),
+ gth_image_viewer_signals[CLICKED],
+ 0);
+ }
+
+ gth_image_tool_button_release (viewer->priv->tool, event);
+
+ viewer->priv->just_focused = FALSE;
+ viewer->pressed = FALSE;
+ viewer->dragging = FALSE;
+
+ return FALSE;
+}
+
+
+static void
+expose_area (GthImageViewer *viewer,
+ int x,
+ int y,
+ int width,
+ int height)
+{
+ GdkEventExpose event;
+
+ if (width == 0 || height == 0)
+ return;
+
+ event.area.x = x;
+ event.area.y = y;
+ event.area.width = width;
+ event.area.height = height;
+
+ gth_image_viewer_expose (GTK_WIDGET (viewer), &event);
+}
+
+
+static void
+scroll_to (GthImageViewer *viewer,
+ int *x_offset,
+ int *y_offset)
+{
+ GdkDrawable *drawable;
+ int width, height;
+ int delta_x, delta_y;
+ GdkEvent *event;
+ gboolean replay_animation;
+ int gdk_width, gdk_height;
+
+ g_return_if_fail (viewer != NULL);
+
+ if (gth_image_viewer_get_current_pixbuf (viewer) == NULL)
+ return;
+
+ if (viewer->priv->rendering)
+ return;
+
+ get_zoomed_size (viewer, &width, &height, viewer->priv->zoom_level);
+
+ drawable = GTK_WIDGET (viewer)->window;
+ gdk_width = GTK_WIDGET (viewer)->allocation.width - viewer->priv->frame_border2;
+ gdk_height = GTK_WIDGET (viewer)->allocation.height - viewer->priv->frame_border2;
+
+ if (width > gdk_width)
+ *x_offset = CLAMP (*x_offset, 0, width - gdk_width);
+ else
+ *x_offset = viewer->x_offset;
+
+ if (height > gdk_height)
+ *y_offset = CLAMP (*y_offset, 0, height - gdk_height);
+ else
+ *y_offset = viewer->y_offset;
+
+ if ((*x_offset == viewer->x_offset) && (*y_offset == viewer->y_offset))
+ return;
+
+ delta_x = *x_offset - viewer->x_offset;
+ delta_y = *y_offset - viewer->y_offset;
+
+ if (viewer->priv->next_scroll_repaint) {
+ viewer->priv->next_scroll_repaint = FALSE;
+
+ viewer->x_offset = *x_offset;
+ viewer->y_offset = *y_offset;
+
+ g_signal_emit (G_OBJECT (viewer),
+ gth_image_viewer_signals[REPAINTED],
+ 0);
+
+ expose_area (viewer, 0, 0,
+ GTK_WIDGET (viewer)->allocation.width,
+ GTK_WIDGET (viewer)->allocation.height);
+
+ return;
+ }
+
+ if ((delta_x != 0) || (delta_y != 0)) {
+ GdkGC *gc = GTK_WIDGET (viewer)->style->black_gc;
+ int src_x, dest_x;
+ int src_y, dest_y;
+
+ if (delta_x < 0) {
+ src_x = 0;
+ dest_x = -delta_x;
+ }
+ else {
+ src_x = delta_x;
+ dest_x = 0;
+ }
+
+ if (delta_y < 0) {
+ src_y = 0;
+ dest_y = -delta_y;
+ }
+ else {
+ src_y = delta_y;
+ dest_y = 0;
+ }
+
+ gc = gdk_gc_new (drawable);
+ gdk_gc_set_exposures (gc, TRUE);
+
+ dest_x += viewer->priv->frame_border;
+ dest_y += viewer->priv->frame_border;
+ src_x += viewer->priv->frame_border;
+ src_y += viewer->priv->frame_border;
+
+ gdk_draw_drawable (drawable,
+ gc,
+ drawable,
+ src_x, src_y,
+ dest_x, dest_y,
+ gdk_width - abs (delta_x),
+ gdk_height - abs (delta_y));
+
+ g_object_unref (gc);
+ }
+
+ viewer->x_offset = *x_offset;
+ viewer->y_offset = *y_offset;
+
+ expose_area (viewer,
+ viewer->priv->frame_border,
+ (delta_y < 0) ? viewer->priv->frame_border : viewer->priv->frame_border + gdk_height - abs (delta_y),
+ gdk_width,
+ abs (delta_y));
+
+ expose_area (viewer,
+ (delta_x < 0) ? viewer->priv->frame_border : viewer->priv->frame_border + gdk_width - abs (delta_x),
+ viewer->priv->frame_border,
+ abs (delta_x),
+ gdk_height);
+
+ /* Process graphics exposures */
+
+ replay_animation = viewer->priv->play_animation;
+ viewer->priv->play_animation = FALSE;
+ while ((event = gdk_event_get_graphics_expose (drawable)) != NULL) {
+ GdkEventExpose *expose = (GdkEventExpose*) event;
+
+ expose_area (viewer,
+ expose->area.x,
+ expose->area.y,
+ expose->area.width,
+ expose->area.height);
+
+ if (expose->count == 0) {
+ gdk_event_free (event);
+ break;
+ }
+ gdk_event_free (event);
+ }
+ viewer->priv->play_animation = replay_animation;
+}
+
+
+static gboolean
+gth_image_viewer_motion_notify (GtkWidget *widget,
+ GdkEventMotion *event)
+{
+ GthImageViewer *viewer = GTH_IMAGE_VIEWER (widget);
+
+ if (viewer->priv->rendering)
+ return FALSE;
+
+ if (viewer->pressed) {
+ viewer->drag_x = event->x + viewer->x_offset;
+ viewer->drag_y = event->y + viewer->y_offset;
+ }
+
+ gth_image_tool_motion_notify (viewer->priv->tool, event);
+
+ if (viewer->pressed) {
+ viewer->event_x_prev = event->x;
+ viewer->event_y_prev = event->y;
+ viewer->drag_x_prev = viewer->drag_x;
+ viewer->drag_y_prev = viewer->drag_y;
+ }
+
+ return FALSE;
+}
+
+
+static gboolean
+gth_image_viewer_scroll_event (GtkWidget *widget,
+ GdkEventScroll *event)
+{
+ GthImageViewer *viewer = GTH_IMAGE_VIEWER (widget);
+ GtkAdjustment *adj;
+ gdouble new_value = 0.0;
+
+ g_return_val_if_fail (GTH_IS_IMAGE_VIEWER (widget), FALSE);
+ g_return_val_if_fail (event != NULL, FALSE);
+
+ if (event->state & GDK_CONTROL_MASK) {
+ if (event->direction == GDK_SCROLL_UP) {
+ set_zoom (viewer,
+ get_next_zoom (viewer->priv->zoom_level),
+ (int) event->x,
+ (int) event->y);
+ return TRUE;
+ }
+ if (event->direction == GDK_SCROLL_DOWN) {
+ set_zoom (viewer,
+ get_prev_zoom (viewer->priv->zoom_level),
+ (int) event->x,
+ (int) event->y);
+ return TRUE;
+ }
+ }
+
+ if (event->direction == GDK_SCROLL_UP || event->direction == GDK_SCROLL_DOWN) {
+ g_signal_emit (G_OBJECT (viewer),
+ gth_image_viewer_signals[MOUSE_WHEEL_SCROLL],
+ 0,
+ event->direction);
+ return TRUE;
+ }
+
+ adj = viewer->hadj;
+
+ if (event->direction == GDK_SCROLL_LEFT)
+ new_value = adj->value - adj->page_increment / 2;
+ else if (event->direction == GDK_SCROLL_RIGHT)
+ new_value = adj->value + adj->page_increment / 2;
+
+ new_value = CLAMP (new_value, adj->lower, adj->upper - adj->page_size);
+ gtk_adjustment_set_value (adj, new_value);
+
+ return TRUE;
+}
+
+
+static void
+gth_image_viewer_style_set (GtkWidget *widget,
+ GtkStyle *previous_style)
+{
+ GthImageViewer *viewer = GTH_IMAGE_VIEWER (widget);
+
+ GTK_WIDGET_CLASS (parent_class)->style_set (widget, previous_style);
+
+ if (viewer->priv->transp_type == GTH_TRANSP_TYPE_NONE) {
+ GdkColor color;
+ guint base_color;
+
+ color = GTK_WIDGET (viewer)->style->bg[GTK_STATE_NORMAL];
+ base_color = (0xFF000000
+ | (to_255 (color.red) << 16)
+ | (to_255 (color.green) << 8)
+ | (to_255 (color.blue) << 0));
+
+ viewer->priv->check_color1 = base_color;
+ viewer->priv->check_color2 = base_color;
+ }
+}
+
+
+static void
+scroll_relative (GthImageViewer *viewer,
+ int delta_x,
+ int delta_y)
+{
+ gth_image_viewer_scroll_to (viewer,
+ viewer->x_offset + delta_x,
+ viewer->y_offset + delta_y);
+}
+
+
+static void
+scroll_signal (GtkWidget *widget,
+ GtkScrollType xscroll_type,
+ GtkScrollType yscroll_type)
+{
+ GthImageViewer *viewer = GTH_IMAGE_VIEWER (widget);
+ int xstep, ystep;
+
+ switch (xscroll_type) {
+ case GTK_SCROLL_STEP_LEFT:
+ xstep = -viewer->hadj->step_increment;
+ break;
+ case GTK_SCROLL_STEP_RIGHT:
+ xstep = viewer->hadj->step_increment;
+ break;
+ case GTK_SCROLL_PAGE_LEFT:
+ xstep = -viewer->hadj->page_increment;
+ break;
+ case GTK_SCROLL_PAGE_RIGHT:
+ xstep = viewer->hadj->page_increment;
+ break;
+ default:
+ xstep = 0;
+ break;
+ }
+
+ switch (yscroll_type) {
+ case GTK_SCROLL_STEP_UP:
+ ystep = -viewer->vadj->step_increment;
+ break;
+ case GTK_SCROLL_STEP_DOWN:
+ ystep = viewer->vadj->step_increment;
+ break;
+ case GTK_SCROLL_PAGE_UP:
+ ystep = -viewer->vadj->page_increment;
+ break;
+ case GTK_SCROLL_PAGE_DOWN:
+ ystep = viewer->vadj->page_increment;
+ break;
+ default:
+ ystep = 0;
+ break;
+ }
+
+ scroll_relative (viewer, xstep, ystep);
+}
+
+
+static gboolean
+hadj_value_changed (GtkObject *adj,
+ GthImageViewer *viewer)
+{
+ int x_ofs, y_ofs;
+
+ x_ofs = (int) GTK_ADJUSTMENT (adj)->value;
+ y_ofs = viewer->y_offset;
+ scroll_to (viewer, &x_ofs, &y_ofs);
+
+ return FALSE;
+}
+
+
+static gboolean
+vadj_value_changed (GtkObject *adj,
+ GthImageViewer *viewer)
+{
+ int x_ofs, y_ofs;
+
+ x_ofs = viewer->x_offset;
+ y_ofs = (int) GTK_ADJUSTMENT (adj)->value;
+ scroll_to (viewer, &x_ofs, &y_ofs);
+
+ return FALSE;
+}
+
+
+static void
+set_scroll_adjustments (GtkWidget *widget,
+ GtkAdjustment *hadj,
+ GtkAdjustment *vadj)
+{
+ GthImageViewer *viewer;
+
+ g_return_if_fail (widget != NULL);
+ g_return_if_fail (GTH_IS_IMAGE_VIEWER (widget));
+
+ viewer = GTH_IMAGE_VIEWER (widget);
+
+ if (hadj)
+ g_return_if_fail (GTK_IS_ADJUSTMENT (hadj));
+ else
+ hadj = GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 0.0, 0.0,
+ 0.0, 0.0, 0.0));
+
+ if (vadj)
+ g_return_if_fail (GTK_IS_ADJUSTMENT (vadj));
+ else
+ vadj = GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 0.0, 0.0,
+ 0.0, 0.0, 0.0));
+
+ if (viewer->hadj && viewer->hadj != hadj) {
+ g_signal_handlers_disconnect_by_data (G_OBJECT (viewer->hadj),
+ viewer);
+ g_object_unref (viewer->hadj);
+
+ viewer->hadj = NULL;
+ }
+
+ if (viewer->vadj && viewer->vadj != vadj) {
+ g_signal_handlers_disconnect_by_data (G_OBJECT (viewer->vadj), viewer);
+ g_object_unref (viewer->vadj);
+ viewer->vadj = NULL;
+ }
+
+ if (viewer->hadj != hadj) {
+ viewer->hadj = hadj;
+ g_object_ref (viewer->hadj);
+ gtk_object_sink (GTK_OBJECT (viewer->hadj));
+
+ g_signal_connect (G_OBJECT (viewer->hadj),
+ "value_changed",
+ G_CALLBACK (hadj_value_changed),
+ viewer);
+ }
+
+ if (viewer->vadj != vadj) {
+ viewer->vadj = vadj;
+ g_object_ref (viewer->vadj);
+ gtk_object_sink (GTK_OBJECT (viewer->vadj));
+
+ g_signal_connect (G_OBJECT (viewer->vadj),
+ "value_changed",
+ G_CALLBACK (vadj_value_changed),
+ viewer);
+ }
+}
+
+
+static void
+gth_image_viewer_class_init (GthImageViewerClass *class)
+{
+ GObjectClass *gobject_class;
+ GtkWidgetClass *widget_class;
+ GtkBindingSet *binding_set;
+
+ parent_class = g_type_class_peek_parent (class);
+ g_type_class_add_private (class, sizeof (GthImageViewerPrivate));
+
+ widget_class = (GtkWidgetClass*) class;
+ gobject_class = (GObjectClass*) class;
+
+ gth_image_viewer_signals[CLICKED] =
+ g_signal_new ("clicked",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GthImageViewerClass, clicked),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+ gth_image_viewer_signals[IMAGE_READY] =
+ g_signal_new ("image_ready",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GthImageViewerClass, image_ready),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+ gth_image_viewer_signals[ZOOM_IN] =
+ g_signal_new ("zoom_in",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (GthImageViewerClass, zoom_in),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+ gth_image_viewer_signals[ZOOM_OUT] =
+ g_signal_new ("zoom_out",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (GthImageViewerClass, zoom_out),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+ gth_image_viewer_signals[SET_ZOOM] =
+ g_signal_new ("set_zoom",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (GthImageViewerClass, set_zoom),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__DOUBLE,
+ G_TYPE_NONE,
+ 1,
+ G_TYPE_DOUBLE);
+ gth_image_viewer_signals[SET_FIT_MODE] =
+ g_signal_new ("set_fit_mode",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (GthImageViewerClass, set_fit_mode),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__ENUM,
+ G_TYPE_NONE,
+ 1,
+ GTH_TYPE_FIT);
+ gth_image_viewer_signals[ZOOM_CHANGED] =
+ g_signal_new ("zoom_changed",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GthImageViewerClass, zoom_changed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+ gth_image_viewer_signals[SIZE_CHANGED] =
+ g_signal_new ("size_changed",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GthImageViewerClass, size_changed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+ gth_image_viewer_signals[REPAINTED] =
+ g_signal_new ("repainted",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GthImageViewerClass, repainted),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+ gth_image_viewer_signals[MOUSE_WHEEL_SCROLL] =
+ g_signal_new ("mouse_wheel_scroll",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GthImageViewerClass, mouse_wheel_scroll),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__ENUM,
+ G_TYPE_NONE,
+ 1,
+ GDK_TYPE_SCROLL_DIRECTION);
+
+ class->set_scroll_adjustments = set_scroll_adjustments;
+ widget_class->set_scroll_adjustments_signal =
+ g_signal_new ("set_scroll_adjustments",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GthImageViewerClass, set_scroll_adjustments),
+ NULL, NULL,
+ gth_marshal_VOID__POINTER_POINTER,
+ G_TYPE_NONE,
+ 2,
+ GTK_TYPE_ADJUSTMENT,
+ GTK_TYPE_ADJUSTMENT);
+ gth_image_viewer_signals[SCROLL] =
+ g_signal_new ("scroll",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (GthImageViewerClass, scroll),
+ NULL, NULL,
+ gth_marshal_VOID__ENUM_ENUM,
+ G_TYPE_NONE,
+ 2,
+ GTK_TYPE_SCROLL_TYPE,
+ GTK_TYPE_SCROLL_TYPE);
+
+ gobject_class->finalize = gth_image_viewer_finalize;
+
+ widget_class->realize = gth_image_viewer_realize;
+ widget_class->unrealize = gth_image_viewer_unrealize;
+ widget_class->size_allocate = gth_image_viewer_size_allocate;
+ widget_class->focus_in_event = gth_image_viewer_focus_in;
+ widget_class->focus_out_event = gth_image_viewer_focus_out;
+ widget_class->key_press_event = gth_image_viewer_key_press;
+
+ widget_class->expose_event = gth_image_viewer_expose;
+ widget_class->button_press_event = gth_image_viewer_button_press;
+ widget_class->button_release_event = gth_image_viewer_button_release;
+ widget_class->motion_notify_event = gth_image_viewer_motion_notify;
+
+ widget_class->scroll_event = gth_image_viewer_scroll_event;
+ widget_class->style_set = gth_image_viewer_style_set;
+
+ class->clicked = NULL;
+ class->image_ready = NULL;
+ class->zoom_changed = NULL;
+ class->scroll = scroll_signal;
+ class->zoom_in = gth_image_viewer_zoom_in;
+ class->zoom_out = gth_image_viewer_zoom_out;
+ class->set_zoom = gth_image_viewer_set_zoom;
+ class->set_fit_mode = gth_image_viewer_set_fit_mode;
+
+ /* Add key bindings */
+
+ binding_set = gtk_binding_set_by_class (class);
+
+ /* For scrolling */
+
+ gtk_binding_entry_add_signal (binding_set, GDK_Right, 0,
+ "scroll", 2,
+ GTK_TYPE_SCROLL_TYPE, GTK_SCROLL_STEP_RIGHT,
+ GTK_TYPE_SCROLL_TYPE, GTK_SCROLL_NONE);
+ gtk_binding_entry_add_signal (binding_set, GDK_Left, 0,
+ "scroll", 2,
+ GTK_TYPE_SCROLL_TYPE, GTK_SCROLL_STEP_LEFT,
+ GTK_TYPE_SCROLL_TYPE, GTK_SCROLL_NONE);
+ gtk_binding_entry_add_signal (binding_set, GDK_Down, 0,
+ "scroll", 2,
+ GTK_TYPE_SCROLL_TYPE, GTK_SCROLL_NONE,
+ GTK_TYPE_SCROLL_TYPE, GTK_SCROLL_STEP_DOWN);
+ gtk_binding_entry_add_signal (binding_set, GDK_Up, 0,
+ "scroll", 2,
+ GTK_TYPE_SCROLL_TYPE, GTK_SCROLL_NONE,
+ GTK_TYPE_SCROLL_TYPE, GTK_SCROLL_STEP_UP);
+
+ gtk_binding_entry_add_signal (binding_set, GDK_Right, GDK_SHIFT_MASK,
+ "scroll", 2,
+ GTK_TYPE_SCROLL_TYPE, GTK_SCROLL_PAGE_RIGHT,
+ GTK_TYPE_SCROLL_TYPE, GTK_SCROLL_NONE);
+ gtk_binding_entry_add_signal (binding_set, GDK_Left, GDK_SHIFT_MASK,
+ "scroll", 2,
+ GTK_TYPE_SCROLL_TYPE, GTK_SCROLL_PAGE_LEFT,
+ GTK_TYPE_SCROLL_TYPE, GTK_SCROLL_NONE);
+ gtk_binding_entry_add_signal (binding_set, GDK_Down, GDK_SHIFT_MASK,
+ "scroll", 2,
+ GTK_TYPE_SCROLL_TYPE, GTK_SCROLL_NONE,
+ GTK_TYPE_SCROLL_TYPE, GTK_SCROLL_PAGE_DOWN);
+ gtk_binding_entry_add_signal (binding_set, GDK_Up, GDK_SHIFT_MASK,
+ "scroll", 2,
+ GTK_TYPE_SCROLL_TYPE, GTK_SCROLL_NONE,
+ GTK_TYPE_SCROLL_TYPE, GTK_SCROLL_PAGE_UP);
+
+ gtk_binding_entry_add_signal (binding_set, GDK_Page_Down, 0,
+ "scroll", 2,
+ GTK_TYPE_SCROLL_TYPE, GTK_SCROLL_NONE,
+ GTK_TYPE_SCROLL_TYPE, GTK_SCROLL_PAGE_DOWN);
+ gtk_binding_entry_add_signal (binding_set, GDK_Page_Up, 0,
+ "scroll", 2,
+ GTK_TYPE_SCROLL_TYPE, GTK_SCROLL_NONE,
+ GTK_TYPE_SCROLL_TYPE, GTK_SCROLL_PAGE_UP);
+
+ /* Zoom in */
+
+ gtk_binding_entry_add_signal (binding_set, GDK_plus, 0,
+ "zoom_in", 0);
+ gtk_binding_entry_add_signal (binding_set, GDK_equal, 0,
+ "zoom_in", 0);
+ gtk_binding_entry_add_signal (binding_set, GDK_KP_Add, 0,
+ "zoom_in", 0);
+
+ /* Zoom out */
+
+ gtk_binding_entry_add_signal (binding_set, GDK_minus, 0,
+ "zoom_out", 0);
+ gtk_binding_entry_add_signal (binding_set, GDK_KP_Subtract, 0,
+ "zoom_out", 0);
+
+ /* Set zoom */
+
+ gtk_binding_entry_add_signal (binding_set, GDK_KP_Divide, 0,
+ "set_zoom", 1,
+ G_TYPE_DOUBLE, 1.0);
+ gtk_binding_entry_add_signal (binding_set, GDK_1, 0,
+ "set_zoom", 1,
+ G_TYPE_DOUBLE, 1.0);
+ gtk_binding_entry_add_signal (binding_set, GDK_z, 0,
+ "set_zoom", 1,
+ G_TYPE_DOUBLE, 1.0);
+ gtk_binding_entry_add_signal (binding_set, GDK_2, 0,
+ "set_zoom", 1,
+ G_TYPE_DOUBLE, 2.0);
+ gtk_binding_entry_add_signal (binding_set, GDK_3, 0,
+ "set_zoom", 1,
+ G_TYPE_DOUBLE, 3.0);
+
+ /* Set fit mode */
+
+ gtk_binding_entry_add_signal (binding_set, GDK_x, 0,
+ "set_fit_mode", 1,
+ GTH_TYPE_FIT, GTH_FIT_SIZE_IF_LARGER);
+ gtk_binding_entry_add_signal (binding_set, GDK_KP_Multiply, 0,
+ "set_fit_mode", 1,
+ GTH_TYPE_FIT, GTH_FIT_SIZE_IF_LARGER);
+ gtk_binding_entry_add_signal (binding_set, GDK_x, GDK_SHIFT_MASK,
+ "set_fit_mode", 1,
+ GTH_TYPE_FIT, GTH_FIT_SIZE);
+ gtk_binding_entry_add_signal (binding_set, GDK_w, 0,
+ "set_fit_mode", 1,
+ GTH_TYPE_FIT, GTH_FIT_WIDTH_IF_LARGER);
+ gtk_binding_entry_add_signal (binding_set, GDK_w, GDK_SHIFT_MASK,
+ "set_fit_mode", 1,
+ GTH_TYPE_FIT, GTH_FIT_WIDTH);
+}
+
+
+static void
+create_first_pixbuf (GthImageViewer *viewer)
+{
+ g_return_if_fail (viewer != NULL);
+
+ viewer->priv->frame_pixbuf = NULL;
+ viewer->priv->frame_delay = 0;
+
+ if (viewer->priv->iter != NULL)
+ g_object_unref (viewer->priv->iter);
+
+ g_get_current_time (&viewer->priv->time);
+
+ viewer->priv->iter = gdk_pixbuf_animation_get_iter (viewer->priv->anim, &viewer->priv->time);
+ create_pixbuf_from_iter (viewer);
+}
+
+
+static void
+init_animation (GthImageViewer *viewer)
+{
+ g_return_if_fail (viewer != NULL);
+
+ if (! viewer->priv->is_animation)
+ return;
+
+ if (viewer->priv->anim != NULL)
+ g_object_unref (viewer->priv->anim);
+
+ viewer->priv->anim = gth_image_loader_get_animation (viewer->priv->loader);
+ if (viewer->priv->anim == NULL) {
+ viewer->priv->is_animation = FALSE;
+ return;
+ }
+
+ create_first_pixbuf (viewer);
+}
+
+
+static void
+halt_animation (GthImageViewer *viewer)
+{
+ g_return_if_fail (viewer != NULL);
+
+ if (viewer->priv->anim_id == 0)
+ return;
+
+ g_source_remove (viewer->priv->anim_id);
+ viewer->priv->anim_id = 0;
+}
+
+
+static void
+image_loader_ready_cb (GthImageLoader *il,
+ GError *error,
+ gpointer data)
+{
+ GthImageViewer *viewer = data;
+ GdkPixbufAnimation *anim;
+
+ if (error != NULL) {
+ g_clear_error (&error);
+
+ gth_image_viewer_set_void (viewer);
+ g_signal_emit (G_OBJECT (viewer),
+ gth_image_viewer_signals[IMAGE_READY],
+ 0);
+ return;
+ }
+
+ halt_animation (viewer);
+
+ if (viewer->priv->reset_scrollbars) {
+ viewer->x_offset = 0;
+ viewer->y_offset = 0;
+ }
+
+ if (viewer->priv->anim != NULL) {
+ g_object_unref (viewer->priv->anim);
+ viewer->priv->anim = NULL;
+ }
+
+ anim = gth_image_loader_get_animation (viewer->priv->loader);
+ viewer->priv->is_animation = (anim != NULL) && ! gdk_pixbuf_animation_is_static_image (anim);
+ g_object_unref (anim);
+
+ if (viewer->priv->is_animation)
+ init_animation (viewer);
+
+ gth_image_tool_image_changed (viewer->priv->tool);
+
+ switch (viewer->priv->zoom_change) {
+ case GTH_ZOOM_CHANGE_ACTUAL_SIZE:
+ gth_image_viewer_set_zoom (viewer, 1.0);
+ queue_frame_change (viewer);
+ break;
+
+ case GTH_ZOOM_CHANGE_KEEP_PREV:
+ gth_image_viewer_update_view (viewer);
+ break;
+
+ case GTH_ZOOM_CHANGE_FIT_SIZE:
+ gth_image_viewer_set_fit_mode (viewer, GTH_FIT_SIZE);
+ queue_frame_change (viewer);
+ break;
+
+ case GTH_ZOOM_CHANGE_FIT_SIZE_IF_LARGER:
+ gth_image_viewer_set_fit_mode (viewer, GTH_FIT_SIZE_IF_LARGER);
+ queue_frame_change (viewer);
+ break;
+
+ case GTH_ZOOM_CHANGE_FIT_WIDTH:
+ gth_image_viewer_set_fit_mode (viewer, GTH_FIT_WIDTH);
+ queue_frame_change (viewer);
+ break;
+
+ case GTH_ZOOM_CHANGE_FIT_WIDTH_IF_LARGER:
+ gth_image_viewer_set_fit_mode (viewer, GTH_FIT_WIDTH_IF_LARGER);
+ queue_frame_change (viewer);
+ break;
+ }
+
+ g_signal_emit (G_OBJECT (viewer),
+ gth_image_viewer_signals[IMAGE_READY],
+ 0);
+}
+
+
+static void
+gth_image_viewer_instance_init (GthImageViewer *viewer)
+{
+ GTK_WIDGET_SET_FLAGS (viewer, GTK_CAN_FOCUS);
+ /*GTK_WIDGET_UNSET_FLAGS (viewer, GTK_DOUBLE_BUFFERED); FIXME: check if this is correct */
+
+ viewer->priv = GTH_IMAGE_VIEWER_GET_PRIVATE (viewer);
+
+ /* Initialize data. */
+
+ viewer->priv->check_type = GTH_CHECK_TYPE_MIDTONE;
+ viewer->priv->check_size = GTH_CHECK_SIZE_LARGE;
+ viewer->priv->check_color1 = COLOR_GRAY_66;
+ viewer->priv->check_color2 = COLOR_GRAY_99;
+
+ viewer->priv->is_animation = FALSE;
+ viewer->priv->play_animation = TRUE;
+ viewer->priv->rendering = FALSE;
+ viewer->priv->cursor_visible = TRUE;
+
+ viewer->priv->frame_visible = TRUE;
+ viewer->priv->frame_border = GTH_IMAGE_VIEWER_FRAME_BORDER;
+ viewer->priv->frame_border2 = GTH_IMAGE_VIEWER_FRAME_BORDER2;
+
+ viewer->priv->frame_pixbuf = NULL;
+ viewer->priv->frame_delay = 0;
+ viewer->priv->anim_id = 0;
+
+ viewer->priv->loader = gth_image_loader_new (TRUE);
+ g_signal_connect (G_OBJECT (viewer->priv->loader),
+ "ready",
+ G_CALLBACK (image_loader_ready_cb),
+ viewer);
+
+ viewer->priv->anim = NULL;
+ viewer->priv->iter = NULL;
+
+ viewer->priv->zoom_level = 1.0;
+ viewer->priv->zoom_quality = GTH_ZOOM_QUALITY_HIGH;
+ viewer->priv->zoom_change = GTH_ZOOM_CHANGE_KEEP_PREV;
+ viewer->priv->fit = GTH_FIT_SIZE_IF_LARGER;
+ viewer->priv->doing_zoom_fit = FALSE;
+
+ viewer->priv->skip_zoom_change = FALSE;
+ viewer->priv->skip_size_change = FALSE;
+ viewer->priv->next_scroll_repaint = FALSE;
+
+ viewer->priv->is_void = TRUE;
+ viewer->x_offset = 0;
+ viewer->y_offset = 0;
+ viewer->dragging = FALSE;
+ viewer->priv->double_click = FALSE;
+ viewer->priv->just_focused = FALSE;
+
+ viewer->priv->black_bg = FALSE;
+
+ viewer->priv->paint_pixbuf = NULL;
+ viewer->priv->paint_max_width = 0;
+ viewer->priv->paint_max_height = 0;
+ viewer->priv->paint_bps = 0;
+ viewer->priv->paint_color_space = GDK_COLORSPACE_RGB;
+
+ viewer->priv->cursor = NULL;
+ viewer->priv->cursor_void = NULL;
+
+ viewer->priv->reset_scrollbars = TRUE;
+
+ viewer->priv->tool = gth_image_dragger_new (viewer);
+
+ /* Create the widget. */
+
+ viewer->hadj = GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 1.0, 0.0, 1.0, 1.0, 1.0));
+ viewer->vadj = GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 1.0, 0.0, 1.0, 1.0, 1.0));
+
+ g_object_ref (viewer->hadj);
+ gtk_object_sink (GTK_OBJECT (viewer->hadj));
+ g_object_ref (viewer->vadj);
+ gtk_object_sink (GTK_OBJECT (viewer->vadj));
+
+ g_signal_connect (G_OBJECT (viewer->hadj),
+ "value_changed",
+ G_CALLBACK (hadj_value_changed),
+ viewer);
+ g_signal_connect (G_OBJECT (viewer->vadj),
+ "value_changed",
+ G_CALLBACK (vadj_value_changed),
+ viewer);
+}
+
+
+GType
+gth_image_viewer_get_type (void)
+{
+ static GType type = 0;
+
+ if (! type) {
+ GTypeInfo type_info = {
+ sizeof (GthImageViewerClass),
+ NULL,
+ NULL,
+ (GClassInitFunc) gth_image_viewer_class_init,
+ NULL,
+ NULL,
+ sizeof (GthImageViewer),
+ 0,
+ (GInstanceInitFunc) gth_image_viewer_instance_init
+ };
+
+ type = g_type_register_static (GTK_TYPE_WIDGET,
+ "GthImageViewer",
+ &type_info,
+ 0);
+ }
+
+ return type;
+}
+
+
+GtkWidget*
+gth_image_viewer_new (void)
+{
+ return GTK_WIDGET (g_object_new (GTH_TYPE_IMAGE_VIEWER, NULL));
+}
+
+
+/* -- gth_image_viewer_load -- */
+
+
+typedef struct {
+ GthImageViewer *viewer;
+ GthFileData *file_data;
+} LoadImageData;
+
+
+static void
+load_image_data_free (LoadImageData *lidata)
+{
+ g_object_unref (lidata->file_data);
+ g_free (lidata);
+}
+
+
+static void
+load_image__step2 (LoadImageData *lidata)
+{
+ gth_image_loader_set_file_data (lidata->viewer->priv->loader, lidata->file_data);
+ gth_image_loader_load (lidata->viewer->priv->loader);
+
+ load_image_data_free (lidata);
+}
+
+
+void
+gth_image_viewer_load (GthImageViewer *viewer,
+ GthFileData *file_data)
+{
+ LoadImageData *lidata;
+
+ g_return_if_fail (viewer != NULL);
+ g_return_if_fail (file_data != NULL);
+
+ viewer->priv->is_void = FALSE;
+ halt_animation (viewer);
+
+ lidata = g_new0 (LoadImageData, 1);
+ lidata->viewer = viewer;
+ lidata->file_data = g_object_ref (file_data);
+ gth_image_loader_cancel (viewer->priv->loader, (DoneFunc) load_image__step2, lidata);
+}
+
+
+void
+gth_image_viewer_load_from_file (GthImageViewer *viewer,
+ GFile *file)
+{
+ GthFileData *file_data;
+
+ file_data = gth_file_data_new (file, NULL);
+ gth_image_viewer_load (viewer, file_data);
+
+ g_object_unref (file_data);
+}
+
+
+/* -- gth_image_viewer_load_from_pixbuf_loader -- */
+
+
+typedef struct {
+ GthImageViewer *viewer;
+ gpointer data;
+} GthImageViewerLoadData;
+
+
+static void
+load_from_pixbuf_loader__step2 (GthImageViewerLoadData *ivl_data)
+{
+ GthImageViewer *viewer = ivl_data->viewer;
+ GdkPixbufLoader *pixbuf_loader = ivl_data->data;
+
+ gth_image_loader_load_from_pixbuf_loader (viewer->priv->loader, pixbuf_loader);
+
+ g_object_unref (pixbuf_loader);
+ g_free (ivl_data);
+}
+
+
+void
+gth_image_viewer_load_from_pixbuf_loader (GthImageViewer *viewer,
+ GdkPixbufLoader *pixbuf_loader)
+{
+ GthImageViewerLoadData *ivl_data;
+
+ g_return_if_fail (viewer != NULL);
+ g_return_if_fail (pixbuf_loader != NULL);
+
+ viewer->priv->is_void = FALSE;
+ halt_animation (viewer);
+
+ ivl_data = g_new0 (GthImageViewerLoadData, 1);
+ ivl_data->viewer = viewer;
+ ivl_data->data = g_object_ref (pixbuf_loader);
+
+ gth_image_loader_cancel (viewer->priv->loader,
+ (DoneFunc) load_from_pixbuf_loader__step2,
+ ivl_data);
+}
+
+
+/* -- gth_image_viewer_load_from_image_loader -- */
+
+
+static void
+load_from_image_loader__step2 (GthImageViewerLoadData *ivl_data)
+{
+ GthImageViewer *viewer = ivl_data->viewer;
+ GthImageLoader *image_loader = ivl_data->data;
+
+ gth_image_loader_load_from_image_loader (viewer->priv->loader, image_loader);
+ g_object_unref (image_loader);
+ g_free (ivl_data);
+}
+
+
+void
+gth_image_viewer_load_from_image_loader (GthImageViewer *viewer,
+ GthImageLoader *image_loader)
+{
+ GthImageViewerLoadData *ivl_data;
+
+ g_return_if_fail (viewer != NULL);
+ g_return_if_fail (image_loader != NULL);
+
+ viewer->priv->is_void = FALSE;
+ halt_animation (viewer);
+
+ ivl_data = g_new0 (GthImageViewerLoadData, 1);
+ ivl_data->viewer = viewer;
+ ivl_data->data = g_object_ref (image_loader);
+
+ gth_image_loader_cancel (viewer->priv->loader,
+ (DoneFunc) load_from_image_loader__step2,
+ ivl_data);
+}
+
+
+void
+gth_image_viewer_set_pixbuf (GthImageViewer *viewer,
+ GdkPixbuf *pixbuf)
+{
+ g_return_if_fail (viewer != NULL);
+
+ if (viewer->priv->is_animation || viewer->priv->rendering)
+ return;
+
+ viewer->priv->is_void = (pixbuf == NULL);
+
+ gth_image_loader_set_pixbuf (viewer->priv->loader, pixbuf);
+ gth_image_viewer_update_view (viewer);
+}
+
+
+void
+gth_image_viewer_set_void (GthImageViewer *viewer)
+{
+ viewer->priv->is_void = TRUE;
+ viewer->priv->is_animation = FALSE;
+
+ halt_animation (viewer);
+
+ viewer->priv->frame_pixbuf = NULL;
+
+ if (viewer->priv->reset_scrollbars) {
+ viewer->x_offset = 0;
+ viewer->y_offset = 0;
+ }
+
+ gth_image_tool_image_changed (viewer->priv->tool);
+
+ gtk_widget_queue_resize (GTK_WIDGET (viewer));
+ gtk_widget_queue_draw (GTK_WIDGET (viewer));
+}
+
+
+gboolean
+gth_image_viewer_is_void (GthImageViewer *viewer)
+{
+ return viewer->priv->is_void;
+}
+
+
+void
+gth_image_viewer_update_view (GthImageViewer *viewer)
+{
+ if (viewer->priv->fit == GTH_FIT_NONE)
+ gth_image_viewer_set_zoom (viewer, viewer->priv->zoom_level);
+ else
+ gth_image_viewer_set_fit_mode (viewer, viewer->priv->fit);
+}
+
+
+GthFileData *
+gth_image_viewer_get_file (GthImageViewer *viewer)
+{
+ return gth_image_loader_get_file (viewer->priv->loader);
+}
+
+
+int
+gth_image_viewer_get_image_width (GthImageViewer *viewer)
+{
+ GdkPixbuf *pixbuf;
+
+ g_return_val_if_fail (viewer != NULL, 0);
+
+ if (viewer->priv->anim != NULL)
+ return gdk_pixbuf_animation_get_width (viewer->priv->anim);
+
+ pixbuf = gth_image_loader_get_pixbuf (viewer->priv->loader);
+ if (pixbuf != NULL)
+ return gdk_pixbuf_get_width (pixbuf);
+
+ return 0;
+}
+
+
+int
+gth_image_viewer_get_image_height (GthImageViewer *viewer)
+{
+ GdkPixbuf *pixbuf;
+
+ g_return_val_if_fail (viewer != NULL, 0);
+
+ if (viewer->priv->anim != NULL)
+ return gdk_pixbuf_animation_get_height (viewer->priv->anim);
+
+ pixbuf = gth_image_loader_get_pixbuf (viewer->priv->loader);
+ if (pixbuf != NULL)
+ return gdk_pixbuf_get_height (pixbuf);
+
+ return 0;
+}
+
+
+int
+gth_image_viewer_get_image_bps (GthImageViewer *viewer)
+{
+ GdkPixbuf *pixbuf;
+
+ g_return_val_if_fail (viewer != NULL, 0);
+
+ if (viewer->priv->iter != NULL)
+ pixbuf = gdk_pixbuf_animation_iter_get_pixbuf (viewer->priv->iter);
+ else
+ pixbuf = gth_image_loader_get_pixbuf (viewer->priv->loader);
+
+ if (pixbuf != NULL)
+ return gdk_pixbuf_get_bits_per_sample (pixbuf);
+
+ return 0;
+}
+
+
+gboolean
+gth_image_viewer_get_has_alpha (GthImageViewer *viewer)
+{
+ GdkPixbuf *pixbuf;
+
+ g_return_val_if_fail (viewer != NULL, 0);
+
+ if (viewer->priv->iter != NULL)
+ pixbuf = gdk_pixbuf_animation_iter_get_pixbuf (viewer->priv->iter);
+ else
+ pixbuf = gth_image_loader_get_pixbuf (viewer->priv->loader);
+
+ if (pixbuf != NULL)
+ return gdk_pixbuf_get_has_alpha (pixbuf);
+
+ return FALSE;
+}
+
+
+GdkPixbuf*
+gth_image_viewer_get_current_pixbuf (GthImageViewer *viewer)
+{
+ g_return_val_if_fail (viewer != NULL, NULL);
+
+ if (viewer->priv->is_void)
+ return NULL;
+
+ if (! viewer->priv->is_animation)
+ return gth_image_loader_get_pixbuf (viewer->priv->loader);
+
+ return viewer->priv->frame_pixbuf;
+}
+
+
+void
+gth_image_viewer_start_animation (GthImageViewer *viewer)
+{
+ g_return_if_fail (viewer != NULL);
+
+ viewer->priv->play_animation = TRUE;
+ gth_image_viewer_update_view (viewer);
+}
+
+
+void
+gth_image_viewer_stop_animation (GthImageViewer *viewer)
+{
+ g_return_if_fail (viewer != NULL);
+
+ viewer->priv->play_animation = FALSE;
+ halt_animation (viewer);
+}
+
+
+void
+gth_image_viewer_step_animation (GthImageViewer *viewer)
+{
+ g_return_if_fail (viewer != NULL);
+
+ if (!viewer->priv->is_animation) return;
+ if (viewer->priv->play_animation) return;
+ if (viewer->priv->rendering) return;
+
+ change_frame_cb (viewer);
+}
+
+
+gboolean
+gth_image_viewer_is_animation (GthImageViewer *viewer)
+{
+ g_return_val_if_fail (viewer != NULL, FALSE);
+
+ return viewer->priv->is_animation;
+}
+
+
+gboolean
+gth_image_viewer_is_playing_animation (GthImageViewer *viewer)
+{
+ g_return_val_if_fail (viewer != NULL, FALSE);
+
+ return viewer->priv->is_animation && viewer->priv->play_animation;
+}
+
+
+void
+gth_image_viewer_set_zoom (GthImageViewer *viewer,
+ gdouble zoom_level)
+{
+ GtkWidget *widget = (GtkWidget*) viewer;
+
+ set_zoom (viewer,
+ zoom_level,
+ (widget->allocation.width - viewer->priv->frame_border2) / 2,
+ (widget->allocation.height - viewer->priv->frame_border2) / 2);
+}
+
+
+gdouble
+gth_image_viewer_get_zoom (GthImageViewer *viewer)
+{
+ return viewer->priv->zoom_level;
+}
+
+
+void
+gth_image_viewer_set_zoom_quality (GthImageViewer *viewer,
+ GthZoomQuality quality)
+{
+ viewer->priv->zoom_quality = quality;
+}
+
+
+GthZoomQuality
+gth_image_viewer_get_zoom_quality (GthImageViewer *viewer)
+{
+ return viewer->priv->zoom_quality;
+}
+
+
+void
+gth_image_viewer_set_zoom_change (GthImageViewer *viewer,
+ GthZoomChange zoom_change)
+{
+ viewer->priv->zoom_change = zoom_change;
+}
+
+
+GthZoomChange
+gth_image_viewer_get_zoom_change (GthImageViewer *viewer)
+{
+ return viewer->priv->zoom_change;
+}
+
+
+void
+gth_image_viewer_zoom_in (GthImageViewer *viewer)
+{
+ if (gth_image_viewer_get_current_pixbuf (viewer) == NULL)
+ return;
+ gth_image_viewer_set_zoom (viewer, get_next_zoom (viewer->priv->zoom_level));
+}
+
+
+void
+gth_image_viewer_zoom_out (GthImageViewer *viewer)
+{
+ if (gth_image_viewer_get_current_pixbuf (viewer) == NULL)
+ return;
+ gth_image_viewer_set_zoom (viewer, get_prev_zoom (viewer->priv->zoom_level));
+}
+
+
+void
+gth_image_viewer_set_fit_mode (GthImageViewer *viewer,
+ GthFit fit_mode)
+{
+ viewer->priv->fit = fit_mode;
+ if (viewer->priv->is_void)
+ return;
+ gtk_widget_queue_resize (GTK_WIDGET (viewer));
+}
+
+
+GthFit
+gth_image_viewer_get_fit_mode (GthImageViewer *viewer)
+{
+ return viewer->priv->fit;
+}
+
+
+void
+gth_image_viewer_set_transp_type (GthImageViewer *viewer,
+ GthTranspType transp_type)
+{
+ GdkColor color;
+ guint base_color;
+
+ g_return_if_fail (viewer != NULL);
+
+ viewer->priv->transp_type = transp_type;
+
+ color = GTK_WIDGET (viewer)->style->bg[GTK_STATE_NORMAL];
+ base_color = (0xFF000000
+ | (to_255 (color.red) << 16)
+ | (to_255 (color.green) << 8)
+ | (to_255 (color.blue) << 0));
+
+ switch (transp_type) {
+ case GTH_TRANSP_TYPE_BLACK:
+ viewer->priv->check_color1 = COLOR_GRAY_00;
+ viewer->priv->check_color2 = COLOR_GRAY_00;
+ break;
+
+ case GTH_TRANSP_TYPE_NONE:
+ viewer->priv->check_color1 = base_color;
+ viewer->priv->check_color2 = base_color;
+ break;
+
+ case GTH_TRANSP_TYPE_WHITE:
+ viewer->priv->check_color1 = COLOR_GRAY_FF;
+ viewer->priv->check_color2 = COLOR_GRAY_FF;
+ break;
+
+ case GTH_TRANSP_TYPE_CHECKED:
+ switch (viewer->priv->check_type) {
+ case GTH_CHECK_TYPE_DARK:
+ viewer->priv->check_color1 = COLOR_GRAY_00;
+ viewer->priv->check_color2 = COLOR_GRAY_33;
+ break;
+
+ case GTH_CHECK_TYPE_MIDTONE:
+ viewer->priv->check_color1 = COLOR_GRAY_66;
+ viewer->priv->check_color2 = COLOR_GRAY_99;
+ break;
+
+ case GTH_CHECK_TYPE_LIGHT:
+ viewer->priv->check_color1 = COLOR_GRAY_CC;
+ viewer->priv->check_color2 = COLOR_GRAY_FF;
+ break;
+ }
+ break;
+ }
+}
+
+
+GthTranspType
+gth_image_viewer_get_transp_type (GthImageViewer *viewer)
+{
+ return viewer->priv->transp_type;
+}
+
+
+void
+gth_image_viewer_set_check_type (GthImageViewer *viewer,
+ GthCheckType check_type)
+{
+ viewer->priv->check_type = check_type;
+}
+
+
+GthCheckType
+gth_image_viewer_get_check_type (GthImageViewer *viewer)
+{
+ return viewer->priv->check_type;
+}
+
+
+void
+gth_image_viewer_set_check_size (GthImageViewer *viewer,
+ GthCheckSize check_size)
+{
+ viewer->priv->check_size = check_size;
+}
+
+
+GthCheckSize
+gth_image_viewer_get_check_size (GthImageViewer *viewer)
+{
+ return viewer->priv->check_size;
+}
+
+
+void
+gth_image_viewer_clicked (GthImageViewer *viewer)
+{
+ g_signal_emit (G_OBJECT (viewer), gth_image_viewer_signals[CLICKED], 0);
+}
+
+
+void
+gth_image_viewer_set_size_request (GthImageViewer *viewer,
+ int width,
+ int height)
+{
+ GTK_WIDGET (viewer)->requisition.width = width;
+ GTK_WIDGET (viewer)->requisition.height = height;
+ gtk_widget_queue_resize (GTK_WIDGET (viewer));
+}
+
+
+void
+gth_image_viewer_set_black_background (GthImageViewer *viewer,
+ gboolean set_black)
+{
+ viewer->priv->black_bg = set_black;
+ gtk_widget_queue_draw (GTK_WIDGET (viewer));
+}
+
+
+gboolean
+gth_image_viewer_is_black_background (GthImageViewer *viewer)
+{
+ return viewer->priv->black_bg;
+}
+
+
+void
+gth_image_viewer_get_adjustments (GthImageViewer *self,
+ GtkAdjustment **hadj,
+ GtkAdjustment **vadj)
+{
+ GthImageViewer *viewer;
+
+ viewer = GTH_IMAGE_VIEWER (self);
+ if (hadj != NULL)
+ *hadj = viewer->hadj;
+ if (vadj != NULL)
+ *vadj = viewer->vadj;
+}
+
+
+void
+gth_image_viewer_set_tool (GthImageViewer *viewer,
+ GthImageTool *tool)
+{
+ _g_object_unref (viewer->priv->tool);
+ if (tool == NULL)
+ viewer->priv->tool = gth_image_dragger_new (viewer);
+ else
+ viewer->priv->tool = g_object_ref (tool);
+ if (GTK_WIDGET_REALIZED (viewer))
+ gth_image_tool_realize (viewer->priv->tool);
+ gth_image_tool_image_changed (viewer->priv->tool);
+ gtk_widget_queue_resize (GTK_WIDGET (viewer));
+ gtk_widget_queue_draw (GTK_WIDGET (viewer));
+}
+
+
+void
+gth_image_viewer_scroll_to (GthImageViewer *viewer,
+ int x_offset,
+ int y_offset)
+{
+ g_return_if_fail (viewer != NULL);
+
+ if (gth_image_viewer_get_current_pixbuf (viewer) == NULL)
+ return;
+
+ if (viewer->priv->rendering)
+ return;
+
+ scroll_to (viewer, &x_offset, &y_offset);
+
+ g_signal_handlers_block_by_data (G_OBJECT (viewer->hadj), viewer);
+ g_signal_handlers_block_by_data (G_OBJECT (viewer->vadj), viewer);
+ gtk_adjustment_set_value (viewer->hadj, viewer->x_offset);
+ gtk_adjustment_set_value (viewer->vadj, viewer->y_offset);
+ g_signal_handlers_unblock_by_data (G_OBJECT (viewer->hadj), viewer);
+ g_signal_handlers_unblock_by_data (G_OBJECT (viewer->vadj), viewer);
+}
+
+
+void
+gth_image_viewer_scroll_step_x (GthImageViewer *viewer,
+ gboolean increment)
+{
+ scroll_relative (viewer,
+ (increment ? 1 : -1) * viewer->hadj->step_increment,
+ 0);
+}
+
+
+void
+gth_image_viewer_scroll_step_y (GthImageViewer *viewer,
+ gboolean increment)
+{
+ scroll_relative (viewer,
+ 0,
+ (increment ? 1 : -1) * viewer->vadj->step_increment);
+}
+
+
+void
+gth_image_viewer_scroll_page_x (GthImageViewer *viewer,
+ gboolean increment)
+{
+ scroll_relative (viewer,
+ (increment ? 1 : -1) * viewer->hadj->page_increment,
+ 0);
+}
+
+
+void
+gth_image_viewer_scroll_page_y (GthImageViewer *viewer,
+ gboolean increment)
+{
+ scroll_relative (viewer,
+ 0,
+ (increment ? 1 : -1) * viewer->vadj->page_increment);
+}
+
+
+void
+gth_image_viewer_get_scroll_offset (GthImageViewer *viewer,
+ int *x,
+ int *y)
+{
+ *x = viewer->x_offset;
+ *y = viewer->y_offset;
+}
+
+
+void
+gth_image_viewer_set_reset_scrollbars (GthImageViewer *viewer,
+ gboolean reset)
+{
+ viewer->priv->reset_scrollbars = reset;
+}
+
+
+gboolean
+gth_image_viewer_get_reset_scrollbars (GthImageViewer *viewer)
+{
+ return viewer->priv->reset_scrollbars;
+}
+
+
+void
+gth_image_viewer_show_cursor (GthImageViewer *viewer)
+{
+ viewer->priv->cursor_visible = TRUE;
+ gdk_window_set_cursor (GTK_WIDGET (viewer)->window, viewer->priv->cursor);
+}
+
+
+void
+gth_image_viewer_hide_cursor (GthImageViewer *viewer)
+{
+ viewer->priv->cursor_visible = FALSE;
+ gdk_window_set_cursor (GTK_WIDGET (viewer)->window, viewer->priv->cursor_void);
+}
+
+
+void
+gth_image_viewer_set_cursor (GthImageViewer *viewer,
+ GdkCursor *cursor)
+{
+ if (viewer->priv->cursor != NULL) {
+ gdk_cursor_unref (viewer->priv->cursor);
+ viewer->priv->cursor = NULL;
+ }
+ if (cursor != NULL) {
+ viewer->priv->cursor = gdk_cursor_ref (cursor);
+ }
+ else
+ viewer->priv->cursor = gdk_cursor_ref (viewer->priv->cursor_void);
+
+ if (! GTK_WIDGET_REALIZED (viewer))
+ return;
+
+ gdk_window_set_cursor (GTK_WIDGET (viewer)->window, viewer->priv->cursor);
+}
+
+
+gboolean
+gth_image_viewer_is_cursor_visible (GthImageViewer *viewer)
+{
+ return viewer->priv->cursor_visible;
+}
+
+
+void
+gth_image_viewer_show_frame (GthImageViewer *viewer)
+{
+ viewer->priv->frame_visible = TRUE;
+ viewer->priv->frame_border = GTH_IMAGE_VIEWER_FRAME_BORDER;
+ viewer->priv->frame_border2 = GTH_IMAGE_VIEWER_FRAME_BORDER2;
+
+ gtk_widget_queue_resize (GTK_WIDGET (viewer));
+}
+
+
+void
+gth_image_viewer_hide_frame (GthImageViewer *viewer)
+{
+ viewer->priv->frame_visible = FALSE;
+ viewer->priv->frame_border = 0;
+ viewer->priv->frame_border2 = 0;
+
+ gtk_widget_queue_resize (GTK_WIDGET (viewer));
+}
+
+
+gboolean
+gth_image_viewer_is_frame_visible (GthImageViewer *viewer)
+{
+ return viewer->priv->frame_visible;
+}
+
+
+void
+gth_image_viewer_paint (GthImageViewer *viewer,
+ GdkPixbuf *pixbuf,
+ int src_x,
+ int src_y,
+ int dest_x,
+ int dest_y,
+ int width,
+ int height,
+ int interp_type)
+{
+ double zoom_level;
+ int bits_per_sample;
+ GdkColorspace color_space;
+
+ zoom_level = viewer->priv->zoom_level;
+
+ color_space = gdk_pixbuf_get_colorspace (pixbuf);
+ bits_per_sample = gdk_pixbuf_get_bits_per_sample (pixbuf);
+
+ if ((viewer->priv->paint_pixbuf == NULL)
+ || (viewer->priv->paint_max_width < width)
+ || (viewer->priv->paint_max_height < height)
+ || (viewer->priv->paint_bps != bits_per_sample)
+ || (viewer->priv->paint_color_space != color_space))
+ {
+ if (viewer->priv->paint_pixbuf != NULL)
+ g_object_unref (viewer->priv->paint_pixbuf);
+ viewer->priv->paint_pixbuf = gdk_pixbuf_new (color_space,
+ FALSE,
+ bits_per_sample,
+ width,
+ height);
+ g_return_if_fail (viewer->priv->paint_pixbuf != NULL);
+
+ viewer->priv->paint_max_width = width;
+ viewer->priv->paint_max_height = height;
+ viewer->priv->paint_color_space = color_space;
+ viewer->priv->paint_bps = bits_per_sample;
+ }
+
+ if (gdk_pixbuf_get_has_alpha (pixbuf))
+ gdk_pixbuf_composite_color (pixbuf,
+ viewer->priv->paint_pixbuf,
+ 0, 0,
+ width, height,
+ (double) -src_x,
+ (double) -src_y,
+ zoom_level,
+ zoom_level,
+ interp_type,
+ 255,
+ src_x, src_y,
+ viewer->priv->check_size,
+ viewer->priv->check_color1,
+ viewer->priv->check_color2);
+ else
+ gdk_pixbuf_scale (pixbuf,
+ viewer->priv->paint_pixbuf,
+ 0, 0,
+ width, height,
+ (double) -src_x,
+ (double) -src_y,
+ zoom_level,
+ zoom_level,
+ interp_type);
+
+ gdk_draw_rgb_image_dithalign (GTK_WIDGET (viewer)->window,
+ GTK_WIDGET (viewer)->style->black_gc,
+ dest_x, dest_y,
+ width, height,
+ GDK_RGB_DITHER_MAX,
+ gdk_pixbuf_get_pixels (viewer->priv->paint_pixbuf),
+ gdk_pixbuf_get_rowstride (viewer->priv->paint_pixbuf),
+ dest_x, dest_y);
+
+#if 0
+ gdk_draw_rectangle (GTK_WIDGET (viewer)->window,
+ GTK_WIDGET (viewer)->style->black_gc,
+ FALSE,
+ dest_x, dest_y,
+ width, height);
+#endif
+}
+
+
+void
+gth_image_viewer_crop_area (GthImageViewer *viewer,
+ GdkRectangle *area)
+{
+ GtkWidget *widget = GTK_WIDGET (viewer);
+
+ area->width = MIN (area->width, widget->allocation.width - viewer->priv->frame_border2);
+ area->width = MIN (area->height, widget->allocation.height - viewer->priv->frame_border2);
+}
diff --git a/gthumb/gth-image-viewer.h b/gthumb/gth-image-viewer.h
new file mode 100644
index 0000000..be56e09
--- /dev/null
+++ b/gthumb/gth-image-viewer.h
@@ -0,0 +1,285 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2001-2009 The Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GTH_IMAGE_VIEWER_H
+#define GTH_IMAGE_VIEWER_H
+
+#include <gtk/gtk.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gdk-pixbuf/gdk-pixbuf-loader.h>
+#include "gth-image-loader.h"
+#include "gth-image-tool.h"
+#include "gth-file-data.h"
+
+G_BEGIN_DECLS
+
+#define GTH_TYPE_IMAGE_VIEWER (gth_image_viewer_get_type ())
+#define GTH_IMAGE_VIEWER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTH_TYPE_IMAGE_VIEWER, GthImageViewer))
+#define GTH_IMAGE_VIEWER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTH_TYPE_IMAGE_VIEWER, GthImageViewerClass))
+#define GTH_IS_IMAGE_VIEWER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTH_TYPE_IMAGE_VIEWER))
+#define GTH_IS_IMAGE_VIEWER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTH_TYPE_IMAGE_VIEWER))
+#define GTH_IMAGE_VIEWER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GTH_TYPE_IMAGE_VIEWER, GthImageViewerClass))
+
+typedef struct _GthImageViewer GthImageViewer;
+typedef struct _GthImageViewerClass GthImageViewerClass;
+typedef struct _GthImageViewerPrivate GthImageViewerPrivate;
+
+#define GTH_IMAGE_VIEWER_FRAME_BORDER 1
+#define GTH_IMAGE_VIEWER_FRAME_BORDER2 (GTH_IMAGE_VIEWER_FRAME_BORDER * 2)
+
+
+typedef enum {
+ GTH_ZOOM_QUALITY_HIGH = 0,
+ GTH_ZOOM_QUALITY_LOW
+} GthZoomQuality;
+
+
+typedef enum {
+ GTH_FIT_NONE = 0,
+ GTH_FIT_SIZE,
+ GTH_FIT_SIZE_IF_LARGER,
+ GTH_FIT_WIDTH,
+ GTH_FIT_WIDTH_IF_LARGER
+} GthFit;
+
+
+typedef enum {
+ GTH_ZOOM_CHANGE_ACTUAL_SIZE = 0,
+ GTH_ZOOM_CHANGE_KEEP_PREV,
+ GTH_ZOOM_CHANGE_FIT_SIZE,
+ GTH_ZOOM_CHANGE_FIT_SIZE_IF_LARGER,
+ GTH_ZOOM_CHANGE_FIT_WIDTH,
+ GTH_ZOOM_CHANGE_FIT_WIDTH_IF_LARGER
+} GthZoomChange;
+
+
+typedef enum {
+ GTH_TRANSP_TYPE_WHITE,
+ GTH_TRANSP_TYPE_NONE,
+ GTH_TRANSP_TYPE_BLACK,
+ GTH_TRANSP_TYPE_CHECKED
+} GthTranspType;
+
+
+typedef enum {
+ GTH_CHECK_TYPE_LIGHT,
+ GTH_CHECK_TYPE_MIDTONE,
+ GTH_CHECK_TYPE_DARK
+} GthCheckType;
+
+
+typedef enum {
+ GTH_CHECK_SIZE_SMALL = 4,
+ GTH_CHECK_SIZE_MEDIUM = 8,
+ GTH_CHECK_SIZE_LARGE = 16
+} GthCheckSize;
+
+
+struct _GthImageViewer
+{
+ GtkWidget __parent;
+ GthImageViewerPrivate *priv;
+
+ /*< protected, used by the tools >*/
+
+ GdkRectangle image_area;
+ int x_offset; /* Scroll offsets. */
+ int y_offset;
+ gboolean pressed;
+ int event_x_start;
+ int event_y_start;
+ int event_x_prev;
+ int event_y_prev;
+ gboolean dragging;
+ int drag_x_start;
+ int drag_y_start;
+ int drag_x_prev;
+ int drag_y_prev;
+ int drag_x;
+ int drag_y;
+ GtkAdjustment *vadj;
+ GtkAdjustment *hadj;
+};
+
+
+struct _GthImageViewerClass
+{
+ GtkWidgetClass __parent_class;
+
+ /* -- Signals -- */
+
+ void (* clicked) (GthImageViewer *viewer);
+ void (* image_ready) (GthImageViewer *viewer);
+ void (* zoom_changed) (GthImageViewer *viewer);
+ void (* size_changed) (GthImageViewer *viewer);
+ void (* set_scroll_adjustments) (GtkWidget *widget,
+ GtkAdjustment *hadj,
+ GtkAdjustment *vadj);
+ void (* repainted) (GthImageViewer *viewer);
+ void (* mouse_wheel_scroll) (GthImageViewer *viewer,
+ GdkScrollDirection direction);
+
+ /* -- Key binding signals -- */
+
+ void (*scroll) (GtkWidget *widget,
+ GtkScrollType x_scroll_type,
+ GtkScrollType y_scroll_type);
+ void (* zoom_in) (GthImageViewer *viewer);
+ void (* zoom_out) (GthImageViewer *viewer);
+ void (* set_zoom) (GthImageViewer *viewer,
+ gdouble zoom);
+ void (* set_fit_mode) (GthImageViewer *viewer,
+ GthFit fit_mode);
+};
+
+
+GType gth_image_viewer_get_type (void);
+GtkWidget* gth_image_viewer_new (void);
+
+/* viewer content. */
+
+void gth_image_viewer_load (GthImageViewer *viewer,
+ GthFileData *file);
+void gth_image_viewer_load_from_file (GthImageViewer *viewer,
+ GFile *file);
+void gth_image_viewer_load_from_pixbuf_loader (GthImageViewer *viewer,
+ GdkPixbufLoader *loader);
+void gth_image_viewer_load_from_image_loader (GthImageViewer *viewer,
+ GthImageLoader *loader);
+void gth_image_viewer_set_pixbuf (GthImageViewer *viewer,
+ GdkPixbuf *pixbuf);
+void gth_image_viewer_set_void (GthImageViewer *viewer);
+gboolean gth_image_viewer_is_void (GthImageViewer *viewer);
+void gth_image_viewer_update_view (GthImageViewer *viewer);
+
+/* image info. */
+
+GthFileData * gth_image_viewer_get_file (GthImageViewer *viewer);
+int gth_image_viewer_get_image_width (GthImageViewer *viewer);
+int gth_image_viewer_get_image_height (GthImageViewer *viewer);
+int gth_image_viewer_get_image_bps (GthImageViewer *viewer);
+gboolean gth_image_viewer_get_has_alpha (GthImageViewer *viewer);
+GdkPixbuf * gth_image_viewer_get_current_pixbuf (GthImageViewer *viewer);
+
+/* animation. */
+
+void gth_image_viewer_start_animation (GthImageViewer *viewer);
+void gth_image_viewer_stop_animation (GthImageViewer *viewer);
+void gth_image_viewer_step_animation (GthImageViewer *viewer);
+gboolean gth_image_viewer_is_animation (GthImageViewer *viewer);
+gboolean gth_image_viewer_is_playing_animation (GthImageViewer *viewer);
+
+/* zoom. */
+
+void gth_image_viewer_set_zoom (GthImageViewer *viewer,
+ gdouble zoom);
+gdouble gth_image_viewer_get_zoom (GthImageViewer *viewer);
+void gth_image_viewer_set_zoom_quality (GthImageViewer *viewer,
+ GthZoomQuality quality);
+GthZoomQuality gth_image_viewer_get_zoom_quality (GthImageViewer *viewer);
+void gth_image_viewer_set_zoom_change (GthImageViewer *viewer,
+ GthZoomChange zoom_change);
+GthZoomChange gth_image_viewer_get_zoom_change (GthImageViewer *viewer);
+void gth_image_viewer_zoom_in (GthImageViewer *viewer);
+void gth_image_viewer_zoom_out (GthImageViewer *viewer);
+void gth_image_viewer_set_fit_mode (GthImageViewer *viewer,
+ GthFit fit_mode);
+GthFit gth_image_viewer_get_fit_mode (GthImageViewer *viewer);
+
+/* visualization options. */
+
+void gth_image_viewer_set_transp_type (GthImageViewer *viewer,
+ GthTranspType transp_type);
+GthTranspType gth_image_viewer_get_transp_type (GthImageViewer *viewer);
+void gth_image_viewer_set_check_type (GthImageViewer *viewer,
+ GthCheckType check_type);
+GthCheckType gth_image_viewer_get_check_type (GthImageViewer *viewer);
+void gth_image_viewer_set_check_size (GthImageViewer *view,
+ GthCheckSize check_size);
+GthCheckSize gth_image_viewer_get_check_size (GthImageViewer *viewer);
+
+/* misc. */
+
+void gth_image_viewer_clicked (GthImageViewer *viewer);
+void gth_image_viewer_set_size_request (GthImageViewer *viewer,
+ int width,
+ int height);
+void gth_image_viewer_set_black_background (GthImageViewer *viewer,
+ gboolean set_black);
+gboolean gth_image_viewer_is_black_background (GthImageViewer *viewer);
+void gth_image_viewer_get_adjustments (GthImageViewer *self,
+ GtkAdjustment **hadj,
+ GtkAdjustment **vadj);
+void gth_image_viewer_set_tool (GthImageViewer *viewer,
+ GthImageTool *tool);
+
+/* Scrolling. */
+
+void gth_image_viewer_scroll_to (GthImageViewer *viewer,
+ int x_offset,
+ int y_offset);
+void gth_image_viewer_scroll_step_x (GthImageViewer *viewer,
+ gboolean increment);
+void gth_image_viewer_scroll_step_y (GthImageViewer *viewer,
+ gboolean increment);
+void gth_image_viewer_scroll_page_x (GthImageViewer *viewer,
+ gboolean increment);
+void gth_image_viewer_scroll_page_y (GthImageViewer *viewer,
+ gboolean increment);
+void gth_image_viewer_get_scroll_offset (GthImageViewer *viewer,
+ int *x,
+ int *y);
+void gth_image_viewer_set_reset_scrollbars (GthImageViewer *viewer,
+ gboolean reset);
+gboolean gth_image_viewer_get_reset_scrollbars (GthImageViewer *viewer);
+
+/* Cursor. */
+
+void gth_image_viewer_show_cursor (GthImageViewer *viewer);
+void gth_image_viewer_hide_cursor (GthImageViewer *viewer);
+void gth_image_viewer_set_cursor (GthImageViewer *viewer,
+ GdkCursor *cursor);
+gboolean gth_image_viewer_is_cursor_visible (GthImageViewer *viewer);
+
+/* Frame. */
+
+void gth_image_viewer_show_frame (GthImageViewer *viewer);
+void gth_image_viewer_hide_frame (GthImageViewer *viewer);
+gboolean gth_image_viewer_is_frame_visible (GthImageViewer *viewer);
+
+/*< protected, used by the tools >*/
+
+void gth_image_viewer_paint (GthImageViewer *viewer,
+ GdkPixbuf *pixbuf,
+ int src_x,
+ int src_y,
+ int dest_x,
+ int dest_y,
+ int width,
+ int height,
+ int interp_type);
+void gth_image_viewer_crop_area (GthImageViewer *viewer,
+ GdkRectangle *area);
+
+G_END_DECLS
+
+#endif /* GTH_IMAGE_VIEWER_H */
diff --git a/gthumb/gth-location-chooser.c b/gthumb/gth-location-chooser.c
new file mode 100644
index 0000000..ec05012
--- /dev/null
+++ b/gthumb/gth-location-chooser.c
@@ -0,0 +1,737 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2005-2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <string.h>
+#include <glib/gi18n.h>
+#include <glib.h>
+#include <glib/gprintf.h>
+#include <gtk/gtk.h>
+#include "glib-utils.h"
+#include "gth-file-source.h"
+#include "gth-icon-cache.h"
+#include "gth-location-chooser.h"
+#include "gth-main.h"
+#include "gtk-utils.h"
+#include "pixbuf-utils.h"
+
+
+#define BUFFER_SIZE 4096
+
+
+enum {
+ ITEM_TYPE_NONE,
+ ITEM_TYPE_SEPARATOR,
+ ITEM_TYPE_LOCATION,
+ ITEM_TYPE_BOOKMARK,
+ ITEM_TYPE_ENTRY_POINT
+};
+
+enum {
+ ICON_COLUMN,
+ NAME_COLUMN,
+ URI_COLUMN,
+ TYPE_COLUMN,
+ ELLIPSIZE_COLUMN,
+ N_COLUMNS
+};
+
+enum {
+ CHANGED,
+ LAST_SIGNAL
+};
+
+struct _GthLocationChooserPrivate
+{
+ GtkWidget *combo;
+ GtkTreeStore *model;
+ GFile *location;
+ GthIconCache *icon_cache;
+ GthFileSource *file_source;
+ gulong entry_points_changed_id;
+};
+
+
+static GtkHBoxClass *parent_class = NULL;
+static guint gth_location_chooser_signals[LAST_SIGNAL] = { 0 };
+
+
+static void
+gth_location_chooser_finalize (GObject *object)
+{
+ GthLocationChooser *chooser;
+
+ chooser = GTH_LOCATION_CHOOSER (object);
+
+ if (chooser->priv != NULL) {
+ g_signal_handler_disconnect (gth_main_get_default_monitor (),
+ chooser->priv->entry_points_changed_id);
+ if (chooser->priv->file_source != NULL)
+ g_object_unref (chooser->priv->file_source);
+ gth_icon_cache_free (chooser->priv->icon_cache);
+ if (chooser->priv->location != NULL)
+ g_object_unref (chooser->priv->location);
+ g_free (chooser->priv);
+ }
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+
+static void
+gth_location_chooser_class_init (GthLocationChooserClass *class)
+{
+ GObjectClass *object_class;
+
+ parent_class = g_type_class_peek_parent (class);
+ object_class = (GObjectClass*) class;
+
+ object_class->finalize = gth_location_chooser_finalize;
+
+ gth_location_chooser_signals[CHANGED] =
+ g_signal_new ("changed",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GthLocationChooserClass, changed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+}
+
+
+static void
+gth_location_chooser_init (GthLocationChooser *chooser)
+{
+ chooser->priv = g_new0 (GthLocationChooserPrivate, 1);
+}
+
+
+static void
+combo_changed_cb (GtkComboBox *widget,
+ gpointer user_data)
+{
+ GthLocationChooser *chooser = user_data;
+ GtkTreeIter iter;
+ char *uri = NULL;
+ int item_type = ITEM_TYPE_NONE;
+
+ if (! gtk_combo_box_get_active_iter (GTK_COMBO_BOX (chooser->priv->combo), &iter))
+ return;
+
+ gtk_tree_model_get (GTK_TREE_MODEL (chooser->priv->model),
+ &iter,
+ TYPE_COLUMN, &item_type,
+ URI_COLUMN, &uri,
+ -1);
+
+ if (uri != NULL) {
+ GFile *file;
+
+ file = g_file_new_for_uri (uri);
+ gth_location_chooser_set_current (chooser, file);
+
+ g_object_unref (file);
+ }
+}
+
+
+static gboolean
+row_separator_func (GtkTreeModel *model,
+ GtkTreeIter *iter,
+ gpointer data)
+{
+ int item_type = ITEM_TYPE_NONE;
+
+ gtk_tree_model_get (model,
+ iter,
+ TYPE_COLUMN, &item_type,
+ -1);
+
+ return (item_type == ITEM_TYPE_SEPARATOR);
+}
+
+
+static gboolean
+get_nth_separator_pos (GthLocationChooser *chooser,
+ int pos,
+ int *idx)
+{
+ GtkTreeIter iter;
+ gboolean n_found = 0;
+
+ if (idx != NULL)
+ *idx = 0;
+
+ if (! gtk_tree_model_get_iter_first (GTK_TREE_MODEL (chooser->priv->model), &iter))
+ return FALSE;
+
+ do {
+ int item_type = ITEM_TYPE_NONE;
+
+ gtk_tree_model_get (GTK_TREE_MODEL (chooser->priv->model),
+ &iter,
+ TYPE_COLUMN, &item_type,
+ -1);
+ if (item_type == ITEM_TYPE_SEPARATOR) {
+ n_found++;
+ if (n_found == pos)
+ break;
+ }
+
+ if (idx != NULL)
+ *idx = *idx + 1;
+ }
+ while (gtk_tree_model_iter_next (GTK_TREE_MODEL (chooser->priv->model), &iter));
+
+ return n_found == pos;
+}
+
+
+static void
+add_file_source_entries (GthLocationChooser *chooser,
+ GFile *file,
+ const char *name,
+ GIcon *icon,
+ int position,
+ gboolean update_active_iter,
+ int iter_type)
+{
+ GtkTreeIter iter;
+ GdkPixbuf *pixbuf;
+ char *uri;
+
+ pixbuf = gth_icon_cache_get_pixbuf (chooser->priv->icon_cache, icon);
+ uri = g_file_get_uri (file);
+
+ gtk_tree_store_insert (chooser->priv->model, &iter, NULL, position);
+ gtk_tree_store_set (chooser->priv->model, &iter,
+ TYPE_COLUMN, iter_type,
+ ICON_COLUMN, pixbuf,
+ NAME_COLUMN, name,
+ URI_COLUMN, uri,
+ ELLIPSIZE_COLUMN, PANGO_ELLIPSIZE_END,
+ -1);
+
+ g_free (uri);
+ g_object_unref (pixbuf);
+
+ if (update_active_iter && g_file_equal (chooser->priv->location, file)) {
+ g_signal_handlers_block_by_func (chooser->priv->combo, combo_changed_cb, chooser);
+ gtk_combo_box_set_active_iter (GTK_COMBO_BOX (chooser->priv->combo), &iter);
+ g_signal_handlers_unblock_by_func (chooser->priv->combo, combo_changed_cb, chooser);
+ }
+}
+
+
+static void
+update_entry_point_list (GthLocationChooser *chooser)
+{
+ int first_position;
+ int last_position;
+ int i;
+ int position;
+ GList *scan;
+
+ if (! get_nth_separator_pos (chooser, 1, &first_position))
+ return;
+ if (! get_nth_separator_pos (chooser, 2, &last_position))
+ return;
+
+ for (i = first_position + 1; i < last_position; i++) {
+ GtkTreePath *path;
+ GtkTreeIter iter;
+
+ path = gtk_tree_path_new_from_indices (first_position + 1, -1);
+ if (gtk_tree_model_get_iter (GTK_TREE_MODEL (chooser->priv->model), &iter, path))
+ gtk_tree_store_remove (chooser->priv->model, &iter);
+
+ gtk_tree_path_free (path);
+ }
+
+ position = first_position + 1;
+ for (scan = gth_main_get_all_file_sources (); scan; scan = scan->next) {
+ GthFileSource *file_source = scan->data;
+ GList *entry_points;
+ GList *scan_entry;
+
+ entry_points = gth_file_source_get_entry_points (file_source);
+ for (scan_entry = entry_points; scan_entry; scan_entry = scan_entry->next) {
+ GthFileData *file_data = scan_entry->data;
+
+ add_file_source_entries (chooser,
+ file_data->file,
+ g_file_info_get_display_name (file_data->info),
+ g_file_info_get_icon (file_data->info),
+ position++,
+ FALSE,
+ ITEM_TYPE_ENTRY_POINT);
+ }
+
+ _g_object_list_unref (entry_points);
+ }
+}
+
+
+typedef struct {
+ GthLocationChooser *chooser;
+ GInputStream *stream;
+ char buffer[BUFFER_SIZE];
+ GString *file_content;
+} UpdateBookmarksData;
+
+
+static gboolean
+get_bookmark_iter (GthLocationChooser *chooser,
+ GtkTreeIter *iter)
+{
+ gboolean found = FALSE;
+
+ if (! gtk_tree_model_get_iter_first (GTK_TREE_MODEL (chooser->priv->model), iter))
+ return FALSE;
+
+ do {
+ int item_type = ITEM_TYPE_NONE;
+
+ gtk_tree_model_get (GTK_TREE_MODEL (chooser->priv->model),
+ iter,
+ TYPE_COLUMN, &item_type,
+ -1);
+ if (item_type == ITEM_TYPE_BOOKMARK) {
+ found = TRUE;
+ break;
+ }
+ }
+ while (gtk_tree_model_iter_next (GTK_TREE_MODEL (chooser->priv->model), iter));
+
+ return found;
+}
+
+
+static void
+update_bookmark_list_from_content (GthLocationChooser *chooser,
+ const char *content)
+{
+ GtkTreeIter bookmark_iter;
+ GtkTreeIter iter;
+ char **lines;
+ int i;
+
+ if (! get_bookmark_iter (chooser, &bookmark_iter))
+ return;
+
+ if (gtk_tree_model_iter_children (GTK_TREE_MODEL (chooser->priv->model), &iter, &bookmark_iter))
+ while (gtk_tree_store_remove (chooser->priv->model, &iter)) /* void */;
+
+ lines = g_strsplit (content, "\n", -1);
+ for (i = 0; lines[i] != NULL; i++) {
+ char **line;
+ char *uri;
+ GFile *file;
+ GIcon *icon;
+ char *name;
+ GdkPixbuf *pixbuf;
+
+ line = g_strsplit (lines[i], " ", 2);
+ uri = line[0];
+ if (uri == NULL)
+ continue;
+
+ file = g_file_new_for_uri (uri);
+ icon = _g_file_get_icon (file);
+ if (line[1] != NULL)
+ name = g_strdup (line[1]);
+ else
+ name = _g_file_get_display_name (file);
+ pixbuf = gth_icon_cache_get_pixbuf (chooser->priv->icon_cache, icon);
+
+ gtk_tree_store_append (chooser->priv->model, &iter, &bookmark_iter);
+ gtk_tree_store_set (chooser->priv->model, &iter,
+ TYPE_COLUMN, ITEM_TYPE_LOCATION,
+ ICON_COLUMN, pixbuf,
+ NAME_COLUMN, name,
+ URI_COLUMN, uri,
+ ELLIPSIZE_COLUMN, PANGO_ELLIPSIZE_NONE,
+ -1);
+
+ g_object_unref (pixbuf);
+ g_free (name);
+ g_object_unref (icon);
+ g_object_unref (file);
+ g_strfreev (line);
+ }
+ g_strfreev (lines);
+}
+
+
+static void
+update_bookmark_list_ready (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ UpdateBookmarksData *data = user_data;
+ gssize size;
+
+ size = g_input_stream_read_finish (data->stream, result, NULL);
+ if (size < 0) {
+ g_input_stream_close (data->stream, NULL, NULL);
+ g_object_unref (data->stream);
+ g_free (data);
+ return;
+ }
+
+ if (size > 0) {
+ data->buffer[size + 1] = '\0';
+ g_string_append (data->file_content, data->buffer);
+
+ g_input_stream_read_async (data->stream,
+ data->buffer,
+ BUFFER_SIZE - 1,
+ G_PRIORITY_DEFAULT,
+ NULL,
+ update_bookmark_list_ready,
+ data);
+ return;
+ }
+
+ update_bookmark_list_from_content (data->chooser, data->file_content->str);
+
+ g_input_stream_close (data->stream, NULL, NULL);
+ g_object_unref (data->stream);
+ g_string_free (data->file_content, TRUE);
+ g_free (data);
+}
+
+
+static void
+update_bookmark_list (GthLocationChooser *chooser)
+{
+ char *bookmark_file_path;
+ GFile *bookmark_file;
+ GFileInputStream *input_stream;
+ UpdateBookmarksData *data;
+
+ bookmark_file_path = g_build_filename (g_get_home_dir (), ".gtk-bookmarks", NULL);
+ bookmark_file = g_file_new_for_path (bookmark_file_path);
+ g_free (bookmark_file_path);
+
+ input_stream = g_file_read (bookmark_file, NULL, NULL);
+ g_object_unref (bookmark_file);
+
+ if (input_stream == NULL)
+ return;
+
+ data = g_new0 (UpdateBookmarksData, 1);
+ data->chooser = chooser;
+ data->stream = (GInputStream*) input_stream;
+ data->file_content = g_string_new ("");
+
+ g_input_stream_read_async (data->stream,
+ data->buffer,
+ BUFFER_SIZE - 1,
+ G_PRIORITY_DEFAULT,
+ NULL,
+ update_bookmark_list_ready,
+ data);
+}
+
+
+static void
+entry_points_changed_cb (GthMonitor *monitor,
+ GthLocationChooser *chooser)
+{
+ update_entry_point_list (chooser);
+}
+
+
+static void
+gth_location_chooser_construct (GthLocationChooser *chooser)
+{
+ GtkCellRenderer *renderer;
+ GtkTreeIter iter;
+ GIcon *icon;
+ GdkPixbuf *pixbuf;
+
+ chooser->priv->icon_cache = gth_icon_cache_new (gtk_icon_theme_get_for_screen (gtk_widget_get_screen (GTK_WIDGET (chooser))),
+ _gtk_icon_get_pixel_size (GTK_WIDGET (chooser), GTK_ICON_SIZE_MENU));
+
+ chooser->priv->model = gtk_tree_store_new (N_COLUMNS,
+ GDK_TYPE_PIXBUF,
+ G_TYPE_STRING,
+ G_TYPE_STRING,
+ G_TYPE_INT,
+ PANGO_TYPE_ELLIPSIZE_MODE);
+ chooser->priv->combo = gtk_combo_box_new_with_model (GTK_TREE_MODEL (chooser->priv->model));
+ g_object_unref (chooser->priv->model);
+ g_signal_connect (chooser->priv->combo,
+ "changed",
+ G_CALLBACK (combo_changed_cb),
+ chooser);
+ gtk_combo_box_set_row_separator_func (GTK_COMBO_BOX (chooser->priv->combo),
+ row_separator_func,
+ chooser,
+ NULL);
+
+ /* icon column */
+
+ renderer = gtk_cell_renderer_pixbuf_new ();
+ gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (chooser->priv->combo),
+ renderer,
+ FALSE);
+ gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (chooser->priv->combo),
+ renderer,
+ "pixbuf", ICON_COLUMN,
+ NULL);
+
+ /* path column */
+
+ renderer = gtk_cell_renderer_text_new ();
+ gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (chooser->priv->combo),
+ renderer,
+ TRUE);
+ gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (chooser->priv->combo),
+ renderer,
+ "text", NAME_COLUMN,
+ "ellipsize", ELLIPSIZE_COLUMN,
+ NULL);
+
+ /**/
+
+ gtk_widget_show (chooser->priv->combo);
+ gtk_container_add (GTK_CONTAINER (chooser), chooser->priv->combo);
+
+ /* Add standard items. */
+
+ /* separator #1 */
+
+ gtk_tree_store_append (chooser->priv->model, &iter, NULL);
+ gtk_tree_store_set (chooser->priv->model, &iter,
+ TYPE_COLUMN, ITEM_TYPE_SEPARATOR,
+ -1);
+
+ /* separator #2 */
+
+ gtk_tree_store_append (chooser->priv->model, &iter, NULL);
+ gtk_tree_store_set (chooser->priv->model, &iter,
+ TYPE_COLUMN, ITEM_TYPE_SEPARATOR,
+ -1);
+
+ icon = g_themed_icon_new ("bookmark-view");
+ pixbuf = gth_icon_cache_get_pixbuf (chooser->priv->icon_cache, icon);
+
+ gtk_tree_store_append (chooser->priv->model, &iter, NULL);
+ gtk_tree_store_set (chooser->priv->model, &iter,
+ TYPE_COLUMN, ITEM_TYPE_BOOKMARK,
+ ICON_COLUMN, pixbuf,
+ NAME_COLUMN, _("System Bookmarks"),
+ ELLIPSIZE_COLUMN, PANGO_ELLIPSIZE_END,
+ -1);
+
+ g_object_unref (pixbuf);
+ g_object_unref (icon);
+
+ /**/
+
+ performance (DEBUG_INFO, "update_entry_point_list");
+
+ update_entry_point_list (chooser);
+
+ performance (DEBUG_INFO, "update_bookmark_list");
+
+ update_bookmark_list (chooser);
+
+ performance (DEBUG_INFO, "location constructed");
+
+ /**/
+
+ chooser->priv->entry_points_changed_id =
+ g_signal_connect (gth_main_get_default_monitor (),
+ "entry-points-changed",
+ G_CALLBACK (entry_points_changed_cb),
+ chooser);
+}
+
+
+GType
+gth_location_chooser_get_type (void)
+{
+ static GType type = 0;
+
+ if (! type) {
+ GTypeInfo type_info = {
+ sizeof (GthLocationChooserClass),
+ NULL,
+ NULL,
+ (GClassInitFunc) gth_location_chooser_class_init,
+ NULL,
+ NULL,
+ sizeof (GthLocationChooser),
+ 0,
+ (GInstanceInitFunc) gth_location_chooser_init
+ };
+
+ type = g_type_register_static (GTK_TYPE_HBOX,
+ "GthLocationChooser",
+ &type_info,
+ 0);
+ }
+
+ return type;
+}
+
+
+GtkWidget*
+gth_location_chooser_new (void)
+{
+ GtkWidget *widget;
+
+ widget = GTK_WIDGET (g_object_new (GTH_TYPE_LOCATION_CHOOSER, NULL));
+ gth_location_chooser_construct (GTH_LOCATION_CHOOSER (widget));
+
+ return widget;
+}
+
+
+static gboolean
+delete_current_file_entries (GthLocationChooser *chooser)
+{
+ gboolean found = FALSE;
+ GtkTreeIter iter;
+
+ if (! gtk_tree_model_get_iter_first (GTK_TREE_MODEL (chooser->priv->model), &iter))
+ return FALSE;
+
+ do {
+ int item_type = ITEM_TYPE_NONE;
+
+ gtk_tree_model_get (GTK_TREE_MODEL (chooser->priv->model),
+ &iter,
+ TYPE_COLUMN, &item_type,
+ -1);
+ if (item_type == ITEM_TYPE_SEPARATOR)
+ break;
+ }
+ while (gtk_tree_store_remove (chooser->priv->model, &iter));
+
+ return found;
+}
+
+
+static gboolean
+get_iter_from_current_file_entries (GthLocationChooser *chooser,
+ GFile *file,
+ GtkTreeIter *iter)
+{
+ gboolean found = FALSE;
+ char *uri;
+
+ if (! gtk_tree_model_get_iter_first (GTK_TREE_MODEL (chooser->priv->model), iter))
+ return FALSE;
+
+ uri = g_file_get_uri (file);
+ do {
+ int item_type = ITEM_TYPE_NONE;
+ char *list_uri;
+
+ gtk_tree_model_get (GTK_TREE_MODEL (chooser->priv->model),
+ iter,
+ TYPE_COLUMN, &item_type,
+ URI_COLUMN, &list_uri,
+ -1);
+ if (item_type == ITEM_TYPE_SEPARATOR)
+ break;
+ if (same_uri (uri, list_uri)) {
+ found = TRUE;
+ g_free (list_uri);
+ break;
+ }
+ g_free (list_uri);
+ }
+ while (gtk_tree_model_iter_next (GTK_TREE_MODEL (chooser->priv->model), iter));
+
+ g_free (uri);
+
+ return found;
+}
+
+
+void
+gth_location_chooser_set_current (GthLocationChooser *chooser,
+ GFile *file)
+{
+ GtkTreeIter iter;
+
+ if (chooser->priv->file_source != NULL)
+ g_object_unref (chooser->priv->file_source);
+ chooser->priv->file_source = gth_main_get_file_source (file);
+
+ if (chooser->priv->file_source == NULL)
+ return;
+
+ if ((chooser->priv->location != NULL) && g_file_equal (file, chooser->priv->location))
+ return;
+
+ if (chooser->priv->location != NULL)
+ g_object_unref (chooser->priv->location);
+ chooser->priv->location = g_file_dup (file);
+
+ if (get_iter_from_current_file_entries (chooser, chooser->priv->location, &iter)) {
+ g_signal_handlers_block_by_func (chooser->priv->combo, combo_changed_cb, chooser);
+ gtk_combo_box_set_active_iter (GTK_COMBO_BOX (chooser->priv->combo), &iter);
+ g_signal_handlers_unblock_by_func (chooser->priv->combo, combo_changed_cb, chooser);
+ }
+ else {
+ GList *list;
+ GList *scan;
+ int position = 0;
+
+ delete_current_file_entries (chooser);
+
+ list = gth_file_source_get_current_list (chooser->priv->file_source, chooser->priv->location);
+ for (scan = list; scan; scan = scan->next) {
+ GFile *file = scan->data;
+ GFileInfo *info;
+
+ info = gth_file_source_get_file_info (chooser->priv->file_source, file);
+ if (info == NULL)
+ continue;
+ add_file_source_entries (chooser,
+ file,
+ g_file_info_get_display_name (info),
+ g_file_info_get_icon (info),
+ position++,
+ TRUE,
+ ITEM_TYPE_LOCATION);
+
+ g_object_unref (info);
+ }
+ }
+
+ g_signal_emit (G_OBJECT (chooser), gth_location_chooser_signals[CHANGED], 0);
+}
+
+
+GFile *
+gth_location_chooser_get_current (GthLocationChooser *chooser)
+{
+ return chooser->priv->location;
+}
diff --git a/gthumb/gth-location-chooser.h b/gthumb/gth-location-chooser.h
new file mode 100644
index 0000000..4e21269
--- /dev/null
+++ b/gthumb/gth-location-chooser.h
@@ -0,0 +1,67 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2005-2008 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GTH_LOCATION_CHOOSER_H
+#define GTH_LOCATION_CHOOSER_H
+
+#include <gtk/gtk.h>
+#include "gth-file-source.h"
+
+G_BEGIN_DECLS
+
+#define GTH_TYPE_LOCATION_CHOOSER (gth_location_chooser_get_type ())
+#define GTH_LOCATION_CHOOSER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GTH_TYPE_LOCATION_CHOOSER, GthLocationChooser))
+#define GTH_LOCATION_CHOOSER_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), GTH_TYPE_LOCATION_CHOOSER, GthLocationChooserClass))
+#define GTH_IS_LOCATION_CHOOSER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GTH_TYPE_LOCATION_CHOOSER))
+#define GTH_IS_LOCATION_CHOOSER_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GTH_TYPE_LOCATION_CHOOSER))
+#define GTH_LOCATION_CHOOSER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS((o), GTH_TYPE_LOCATION_CHOOSER, GthLocationChooserClass))
+
+typedef struct _GthLocationChooser GthLocationChooser;
+typedef struct _GthLocationChooserPrivate GthLocationChooserPrivate;
+typedef struct _GthLocationChooserClass GthLocationChooserClass;
+
+struct _GthLocationChooser
+{
+ GtkHBox __parent;
+ GthLocationChooserPrivate *priv;
+};
+
+struct _GthLocationChooserClass
+{
+ GtkHBoxClass __parent_class;
+
+ /* -- Signals -- */
+
+ void (* changed) (GthLocationChooser *loc);
+};
+
+GType gth_location_chooser_get_type (void) G_GNUC_CONST;
+GtkWidget * gth_location_chooser_new (void);
+void gth_location_chooser_set_current (GthLocationChooser *chooser,
+ GFile *location);
+GFile * gth_location_chooser_get_current (GthLocationChooser *chooser);
+GthFileSource * gth_location_chooser_get_file_source (GthLocationChooser *chooser);
+void gth_location_chooser_open_other (GthLocationChooser *chooser);
+
+G_END_DECLS
+
+#endif /* GTH_LOCATION_CHOOSER_H */
diff --git a/gthumb/gth-main-default-hooks.c b/gthumb/gth-main-default-hooks.c
new file mode 100644
index 0000000..f0a4a83
--- /dev/null
+++ b/gthumb/gth-main-default-hooks.c
@@ -0,0 +1,144 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+
+#include <config.h>
+#include <glib/gi18n.h>
+#include "gth-hook.h"
+#include "gth-main.h"
+
+
+void
+gth_main_register_default_hooks (void)
+{
+ gth_hooks_initialize ();
+
+ /**
+ * Called after the window has been initialized. Can be used by
+ * an extension to create and attach specific data to the window.
+ *
+ * @browser (GthBrowser*): the relative window.
+ **/
+ gth_hook_register ("gth-browser-construct", 1);
+
+ /**
+ * Called before closing a window.
+ *
+ * @browser (GthBrowser*): the window to be closed.
+ **/
+ gth_hook_register ("gth-browser-close", 1);
+
+ /**
+ * Called before closing the last window and after the
+ * 'gth-browser-close' hook functions.
+ *
+ * @browser (GthBrowser*): the window to be closed.
+ **/
+ gth_hook_register ("gth-browser-close-last-window", 1);
+
+ /**
+ * Called when a sensitivity update is requested.
+ *
+ * @browser (GthBrowser*): the relative window.
+ **/
+ gth_hook_register ("gth-browser-update-sensitivity", 1);
+
+ /**
+ * Called when the current page changes.
+ *
+ * @browser (GthBrowser*): the relative window.
+ **/
+ gth_hook_register ("gth-browser-set-current-page", 1);
+
+ /**
+ * Called before loading a folder.
+ *
+ * @browser (GthBrowser*): the window
+ * @folder (GFile*): the folder to load
+ **/
+ gth_hook_register ("gth-browser-load-location-before", 2);
+
+ /**
+ * Called after the folder has been loaded.
+ *
+ * @browser (GthBrowser*): the window
+ * @folder (GFile*): the loaded folder
+ * @error (GError*): the error or NULL if the folder was loaded
+ * correctly.
+ **/
+ gth_hook_register ("gth-browser-load-location-after", 3);
+
+ /**
+ * Called before displaying the folder tree popup menu.
+ *
+ * @browser (GthBrowser*): the relative window.
+ * @file_source (GthFileSource*): the file_source relative to the file.
+ * @file (GFile*): the relative folder or NULL if the button was
+ * pressed in an empty area.
+ **/
+ gth_hook_register ("gth-browser-folder-tree-popup-before", 3);
+
+ /**
+ * Called to view file
+ *
+ * @browser (GthBrowser*): the relative window.
+ * @file (GthFileData*): the file
+ **/
+ gth_hook_register ("gth-browser-view-file", 2);
+
+ /**
+ * Called in _gdk_pixbuf_save_async
+ *
+ * @data (SavePixbufData*):
+ **/
+ gth_hook_register ("save-pixbuf", 1);
+
+ /**
+ * Called when copying files in g_copy_files_async with the
+ * G_FILE_COPY_ALL_METADATA flag activated and when deleting file
+ * with _g_delete_files. Used to add sidecar files that contain
+ * file metadata.
+ *
+ * @sources (GList *): the original file list, a GFile * list.
+ * @sidecar_sources (GList **): the sidecars list.
+ */
+ gth_hook_register ("add-sidecars", 2);
+
+ /**
+ * Called when creating the preferences dialog to add other tabs to
+ * the dialog.
+ *
+ * @dialog (GtkWidget *): the preferences dialog.
+ * @browser (GthBrowser*): the relative window.
+ * @builder (GtkBuilder*): the dialog ui builder.
+ **/
+ gth_hook_register ("dlg-preferences-construct", 3);
+
+ /**
+ * Called when to apply the changes from the preferences dialog.
+ *
+ * @dialog (GtkWidget *): the preferences dialog.
+ * @browser (GthBrowser*): the relative window.
+ * @builder (GtkBuilder*): the dialog ui builder.
+ **/
+ gth_hook_register ("dlg-preferences-apply", 3);
+}
diff --git a/gthumb/gth-main-default-metadata.c b/gthumb/gth-main-default-metadata.c
new file mode 100644
index 0000000..4308e3e
--- /dev/null
+++ b/gthumb/gth-main-default-metadata.c
@@ -0,0 +1,62 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2008 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+
+#include <config.h>
+#include <glib/gi18n.h>
+#include "glib-utils.h"
+#include "gth-main.h"
+#include "gth-metadata-provider.h"
+#include "gth-metadata-provider-file.h"
+
+
+GthMetadataCategory file_metadata_category[] = {
+ { "file", N_("File"), 1 },
+ { NULL, NULL, 0 }
+};
+
+
+GthMetadataInfo file_metadata_info[] = {
+ { "standard::display-name", N_("Name"), "file", 1, GTH_METADATA_ALLOW_EVERYWHERE },
+ { "file::display-size", N_("Size"), "file", 2, GTH_METADATA_ALLOW_EVERYWHERE },
+ { "file::display-ctime", N_("Created"), "file", 3, GTH_METADATA_ALLOW_EVERYWHERE },
+ { "file::display-mtime", N_("Modified"), "file", 4, GTH_METADATA_ALLOW_EVERYWHERE },
+ { "standard::fast-content-type", N_("Type"), "file", 5, GTH_METADATA_ALLOW_EVERYWHERE },
+ { "file::is-modified", NULL, "file", 6, GTH_METADATA_ALLOW_NOWHERE },
+
+ { "Embedded::Image::DateTime", "", "", 0, GTH_METADATA_ALLOW_NOWHERE },
+ { "Embedded::Image::Comment", "", "", 0, GTH_METADATA_ALLOW_NOWHERE },
+ { "Embedded::Image::Location", "", "", 0, GTH_METADATA_ALLOW_NOWHERE },
+ { "Embedded::Image::Keywords", "", "", 0, GTH_METADATA_ALLOW_NOWHERE },
+ { "Embedded::Image::Orientation", "", "", 0, GTH_METADATA_ALLOW_NOWHERE },
+
+ { NULL, NULL, NULL, 0, 0 }
+};
+
+
+void
+gth_main_register_default_metadata (void)
+{
+ gth_main_register_metadata_category (file_metadata_category);
+ gth_main_register_metadata_info_v (file_metadata_info);
+ gth_main_register_metadata_provider (GTH_TYPE_METADATA_PROVIDER_FILE);
+}
diff --git a/gthumb/gth-main-default-sort-types.c b/gthumb/gth-main-default-sort-types.c
new file mode 100644
index 0000000..c2083f7
--- /dev/null
+++ b/gthumb/gth-main-default-sort-types.c
@@ -0,0 +1,103 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2008 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+
+#include <config.h>
+#include <glib/gi18n.h>
+#include "gth-file-data.h"
+#include "glib-utils.h"
+#include "gth-main.h"
+
+
+static int
+gth_file_data_cmp_filename (GthFileData *a,
+ GthFileData *b)
+{
+ const char *key_a, *key_b;
+
+ key_a = gth_file_data_get_filename_sort_key (a);
+ key_b = gth_file_data_get_filename_sort_key (b);
+
+ return strcmp (key_a, key_b);
+}
+
+
+static int
+gth_file_data_cmp_filesize (GthFileData *a,
+ GthFileData *b)
+{
+ goffset size_a, size_b;
+
+ size_a = g_file_info_get_size (a->info);
+ size_b = g_file_info_get_size (b->info);
+
+ if (size_a < size_b)
+ return -1;
+ else if (size_a > size_b)
+ return 1;
+ else
+ return 0;
+}
+
+
+static int
+gth_file_data_cmp_modified_time (GthFileData *a,
+ GthFileData *b)
+{
+ GTimeVal *ta, *tb;
+
+ ta = gth_file_data_get_modification_time (a);
+ tb = gth_file_data_get_modification_time (b);
+
+ return _g_time_val_cmp (ta, tb);
+}
+
+
+static int
+gth_file_data_cmp_unsorted (GthFileData *a,
+ GthFileData *b)
+{
+ if (a == b)
+ return 0;
+ else if (a < b)
+ return -1;
+ else
+ return 1;
+}
+
+
+GthFileDataSort default_sort_types[] = {
+ { "file::name", N_("name"), gth_file_data_cmp_filename },
+ { "file::size", N_("size"), gth_file_data_cmp_filesize },
+ { "file::mtime", N_("last modified"), gth_file_data_cmp_modified_time },
+ { "general::unsorted", N_("unsorted"), gth_file_data_cmp_unsorted },
+};
+
+
+void
+gth_main_register_default_sort_types (void)
+{
+ int i;
+
+ for (i = 0; i < G_N_ELEMENTS (default_sort_types); i++)
+ gth_main_register_sort_type (&default_sort_types[i]);
+}
diff --git a/gthumb/gth-main-default-tests.c b/gthumb/gth-main-default-tests.c
new file mode 100644
index 0000000..5afde5f
--- /dev/null
+++ b/gthumb/gth-main-default-tests.c
@@ -0,0 +1,146 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2008 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+
+#include <config.h>
+#include <glib/gi18n.h>
+#include "glib-utils.h"
+#include "gth-main.h"
+#include "gth-test-simple.h"
+
+
+static gint64
+is_file_test (GthTest *test,
+ GthFileData *file,
+ gconstpointer *data)
+{
+ return TRUE;
+}
+
+
+static gint64
+is_image_test (GthTest *test,
+ GthFileData *file,
+ gconstpointer *data)
+{
+ gboolean result = FALSE;
+
+ if (file->info != NULL)
+ result = _g_mime_type_is_image (gth_file_data_get_mime_type (file));
+
+ return result;
+}
+
+
+static gint64
+is_media_test (GthTest *test,
+ GthFileData *file,
+ gconstpointer *data)
+{
+ gboolean result = FALSE;
+
+ if (file->info != NULL) {
+ const char *content_type = gth_file_data_get_mime_type (file);
+ result = (_g_mime_type_is_image (content_type)
+ || _g_mime_type_is_video (content_type)
+ || _g_mime_type_is_audio (content_type));
+ }
+
+ return result;
+}
+
+
+static gint64
+is_text_test (GthTest *test,
+ GthFileData *file,
+ gconstpointer *data)
+{
+ gboolean result = FALSE;
+
+ if (file->info != NULL) {
+ const char *content_type = gth_file_data_get_mime_type (file);
+ result = g_content_type_is_a (content_type, "text/*");
+ }
+
+ return result;
+}
+
+
+static gint64
+get_filename_for_test (GthTest *test,
+ GthFileData *file,
+ gconstpointer *data)
+{
+ *data = g_file_info_get_display_name (file->info);
+ return 0;
+}
+
+
+static gint64
+get_filesize_for_test (GthTest *test,
+ GthFileData *file,
+ gconstpointer *data)
+{
+ return g_file_info_get_size (file->info);
+}
+
+
+void
+gth_main_register_default_tests (void)
+{
+ gth_main_register_test ("file::type::is_file",
+ GTH_TYPE_TEST_SIMPLE,
+ "display-name", _("All Files"),
+ "data-type", GTH_TEST_DATA_TYPE_NONE,
+ "get-data-func", is_file_test,
+ NULL);
+ gth_main_register_test ("file::type::is_image",
+ GTH_TYPE_TEST_SIMPLE,
+ "display-name", _("Images"),
+ "data-type", GTH_TEST_DATA_TYPE_NONE,
+ "get-data-func", is_image_test,
+ NULL);
+ gth_main_register_test ("file::type::is_media",
+ GTH_TYPE_TEST_SIMPLE,
+ "display-name", _("Media"),
+ "data-type", GTH_TEST_DATA_TYPE_NONE,
+ "get-data-func", is_media_test,
+ NULL);
+ gth_main_register_test ("file::type::is_text",
+ GTH_TYPE_TEST_SIMPLE,
+ "display-name", _("Text Files"),
+ "data-type", GTH_TEST_DATA_TYPE_NONE,
+ "get-data-func", is_text_test,
+ NULL);
+ gth_main_register_test ("file::name",
+ GTH_TYPE_TEST_SIMPLE,
+ "display-name", _("Filename"),
+ "data-type", GTH_TEST_DATA_TYPE_STRING,
+ "get-data-func", get_filename_for_test,
+ NULL);
+ gth_main_register_test ("file::size",
+ GTH_TYPE_TEST_SIMPLE,
+ "display-name", _("Size"),
+ "data-type", GTH_TEST_DATA_TYPE_SIZE,
+ "get-data-func", get_filesize_for_test,
+ NULL);
+}
diff --git a/gthumb/gth-main-default-types.c b/gthumb/gth-main-default-types.c
new file mode 100644
index 0000000..64b33ff
--- /dev/null
+++ b/gthumb/gth-main-default-types.c
@@ -0,0 +1,34 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+
+#include <config.h>
+#include <glib/gi18n.h>
+#include "gth-file-properties.h"
+#include "gth-main.h"
+
+
+void
+gth_main_register_default_types (void)
+{
+ gth_main_register_type ("file-properties", GTH_TYPE_FILE_PROPERTIES);
+}
diff --git a/gthumb/gth-main.c b/gthumb/gth-main.c
new file mode 100644
index 0000000..326f590
--- /dev/null
+++ b/gthumb/gth-main.c
@@ -0,0 +1,972 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2008 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <string.h>
+#include <glib/gi18n.h>
+#include <glib.h>
+#include <glib-object.h>
+#include <gobject/gvaluecollector.h>
+#include "gconf-utils.h"
+#include "glib-utils.h"
+#include "gth-duplicable.h"
+#include "gth-filter.h"
+#include "gth-main.h"
+#include "gth-metadata-provider.h"
+#include "gth-user-dir.h"
+#include "gth-preferences.h"
+#include "pixbuf-io.h"
+#include "typedefs.h"
+
+
+typedef struct {
+ GType object_type;
+ guint n_params;
+ GParameter *params;
+} GthTypeSpec;
+
+
+static GthTypeSpec *
+gth_type_spec_new (GType object_type)
+{
+ GthTypeSpec *spec;
+
+ spec = g_new0 (GthTypeSpec, 1);
+ spec->object_type = object_type;
+ spec->n_params = 0;
+ spec->params = NULL;
+
+ return spec;
+}
+
+
+static void
+gth_type_spec_free (GthTypeSpec *spec)
+{
+ while (spec->n_params--)
+ g_value_unset (&spec->params[spec->n_params].value);
+ g_free (spec->params);
+ g_free (spec);
+}
+
+
+static GObject *
+gth_type_spec_create_object (GthTypeSpec *spec,
+ const char *object_id)
+{
+ GObject *object;
+
+ object = g_object_newv (spec->object_type, spec->n_params, spec->params);
+ g_object_set (object, "id", object_id, NULL);
+
+ return object;
+}
+
+
+static GthMain *Main = NULL;
+
+
+struct _GthMainPrivate
+{
+ GList *file_sources;
+ GList *metadata_provider;
+ GPtrArray *metadata_category;
+ GPtrArray *metadata_info;
+ GHashTable *sort_types;
+ GHashTable *tests;
+ GHashTable *loaders;
+ GList *viewer_pages;
+ GHashTable *types;
+ GBookmarkFile *bookmarks;
+ GthFilterFile *filters;
+ GthMonitor *monitor;
+ GthExtensionManager *extension_manager;
+};
+
+
+static GObjectClass *parent_class = NULL;
+
+
+static void
+gth_main_finalize (GObject *object)
+{
+ GthMain *gth_main = GTH_MAIN (object);
+
+ if (gth_main->priv != NULL) {
+ _g_object_list_unref (gth_main->priv->file_sources);
+ _g_object_list_unref (gth_main->priv->viewer_pages);
+
+ g_ptr_array_free (gth_main->priv->metadata_category, TRUE);
+ g_ptr_array_free (gth_main->priv->metadata_info, TRUE);
+ g_list_foreach (gth_main->priv->metadata_provider, (GFunc) g_object_unref, NULL);
+ g_list_free (gth_main->priv->metadata_provider);
+
+ g_hash_table_unref (gth_main->priv->sort_types);
+ g_hash_table_unref (gth_main->priv->tests);
+ g_hash_table_unref (gth_main->priv->loaders);
+
+ g_hash_table_unref (gth_main->priv->types);
+
+ if (gth_main->priv->bookmarks != NULL)
+ g_bookmark_file_free (gth_main->priv->bookmarks);
+ if (gth_main->priv->monitor != NULL)
+ g_object_unref (gth_main->priv->monitor);
+ if (gth_main->priv->extension_manager != NULL)
+ g_object_unref (gth_main->priv->extension_manager);
+
+ g_free (gth_main->priv);
+ gth_main->priv = NULL;
+ }
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+
+static void
+gth_main_class_init (GthMainClass *class)
+{
+ GObjectClass *object_class;
+
+ parent_class = g_type_class_peek_parent (class);
+ object_class = (GObjectClass*) class;
+
+ object_class->finalize = gth_main_finalize;
+}
+
+
+static void
+gth_main_init (GthMain *main)
+{
+ main->priv = g_new0 (GthMainPrivate, 1);
+ main->priv->sort_types = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ NULL,
+ NULL);
+ main->priv->tests = g_hash_table_new_full (g_str_hash,
+ (GEqualFunc) g_content_type_equals,
+ NULL,
+ (GDestroyNotify) gth_type_spec_free);
+ main->priv->loaders = g_hash_table_new (g_str_hash, g_str_equal);
+ main->priv->metadata_category = g_ptr_array_new ();
+ main->priv->metadata_info = g_ptr_array_new ();
+}
+
+
+GType
+gth_main_get_type (void)
+{
+ static GType type = 0;
+
+ if (! type) {
+ GTypeInfo type_info = {
+ sizeof (GthMainClass),
+ NULL,
+ NULL,
+ (GClassInitFunc) gth_main_class_init,
+ NULL,
+ NULL,
+ sizeof (GthMain),
+ 0,
+ (GInstanceInitFunc) gth_main_init
+ };
+
+ type = g_type_register_static (G_TYPE_OBJECT,
+ "GthMain",
+ &type_info,
+ 0);
+ }
+
+ return type;
+}
+
+
+void
+gth_main_initialize (void)
+{
+ if (Main != NULL)
+ return;
+ Main = (GthMain*) g_object_new (GTH_TYPE_MAIN, NULL);
+}
+
+
+void
+gth_main_release (void)
+{
+ if (Main != NULL)
+ g_object_unref (Main);
+}
+
+
+void
+gth_main_register_file_source (GType file_source_type)
+{
+ GObject *file_source;
+
+ file_source = g_object_new (file_source_type, NULL);
+ Main->priv->file_sources = g_list_append (Main->priv->file_sources, file_source);
+}
+
+
+GthFileSource *
+gth_main_get_file_source_for_uri (const char *uri)
+{
+ GthFileSource *file_source = NULL;
+ GList *scan;
+
+ for (scan = Main->priv->file_sources; scan; scan = scan->next) {
+ GthFileSource *registered_file_source = scan->data;
+
+ if (gth_file_source_supports_scheme (registered_file_source, uri)) {
+ file_source = g_object_new (G_OBJECT_TYPE (registered_file_source), NULL);
+ break;
+ }
+ }
+
+ if ((file_source == NULL) && ! g_str_has_prefix (uri, "vfs+")) {
+ char *vfs_uri;
+
+ vfs_uri = g_strconcat ("vfs+", uri, NULL);
+ file_source = gth_main_get_file_source_for_uri (vfs_uri);
+ g_free (vfs_uri);
+ }
+
+ return file_source;
+}
+
+
+GthFileSource *
+gth_main_get_file_source (GFile *file)
+{
+ char *uri;
+ GthFileSource *file_source;
+
+ uri = g_file_get_uri (file);
+ file_source = gth_main_get_file_source_for_uri (uri);
+ g_free (uri);
+
+ return file_source;
+}
+
+GList *
+gth_main_get_all_file_sources (void)
+{
+ return Main->priv->file_sources;
+}
+
+
+GList *
+gth_main_get_all_entry_points (void)
+{
+ GList *list = NULL;
+ GList *scan;
+
+ for (scan = gth_main_get_all_file_sources (); scan; scan = scan->next) {
+ GthFileSource *file_source = scan->data;
+ GList *entry_points;
+ GList *scan_entry;
+
+ entry_points = gth_file_source_get_entry_points (file_source);
+ for (scan_entry = entry_points; scan_entry; scan_entry = scan_entry->next) {
+ GthFileData *file_data = scan_entry->data;
+
+ list = g_list_prepend (list, g_object_ref (file_data));
+ }
+
+ _g_object_list_unref (entry_points);
+ }
+
+ return g_list_reverse (list);
+}
+
+
+char *
+gth_main_get_gio_uri (const char *uri)
+{
+ GthFileSource *file_source;
+ GFile *file;
+ GFile *gio_file;
+ char *gio_uri;
+
+ file_source = gth_main_get_file_source_for_uri (uri);
+ file = g_file_new_for_uri (uri);
+ gio_file = gth_file_source_to_gio_file (file_source, file);
+ gio_uri = g_file_get_uri (gio_file);
+
+ g_object_unref (gio_file);
+ g_object_unref (file);
+ g_object_unref (file_source);
+
+ return gio_uri;
+}
+
+
+GFile *
+gth_main_get_gio_file (GFile *file)
+{
+ GthFileSource *file_source;
+ GFile *gio_file;
+
+ file_source = gth_main_get_file_source (file);
+ gio_file = gth_file_source_to_gio_file (file_source, file);
+ g_object_unref (file_source);
+
+ return gio_file;
+}
+
+
+void
+gth_main_register_metadata_category (GthMetadataCategory *metadata_category)
+{
+ int i;
+
+ for (i = 0; metadata_category[i].id != NULL; i++)
+ if (gth_main_get_metadata_category (metadata_category[i].id) == NULL)
+ g_ptr_array_add (Main->priv->metadata_category, &metadata_category[i]);
+}
+
+
+static GStaticMutex metadata_info_mutex = G_STATIC_MUTEX_INIT;
+
+
+GthMetadataInfo *
+gth_main_register_metadata_info (GthMetadataInfo *metadata_info)
+{
+ GthMetadataInfo *info;
+
+ info = gth_metadata_info_dup (metadata_info);
+
+ g_static_mutex_lock (&metadata_info_mutex);
+ g_ptr_array_add (Main->priv->metadata_info, info);
+ g_static_mutex_unlock (&metadata_info_mutex);
+
+ return info;
+}
+
+
+void
+gth_main_register_metadata_info_v (GthMetadataInfo metadata_info[])
+{
+ int i;
+
+ g_static_mutex_lock (&metadata_info_mutex);
+ for (i = 0; metadata_info[i].id != NULL; i++)
+ g_ptr_array_add (Main->priv->metadata_info, &metadata_info[i]);
+ g_static_mutex_unlock (&metadata_info_mutex);
+}
+
+
+void
+gth_main_register_metadata_provider (GType metadata_provider_type)
+{
+ GObject *metadata;
+
+ metadata = g_object_new (metadata_provider_type, NULL);
+ Main->priv->metadata_provider = g_list_append (Main->priv->metadata_provider, metadata);
+}
+
+
+GList *
+gth_main_get_all_metadata_providers (void)
+{
+ return Main->priv->metadata_provider;
+}
+
+
+char **
+gth_main_get_metadata_attributes (const char *mask)
+{
+ GList *list = NULL;
+ GFileAttributeMatcher *matcher;
+ int i;
+ int n;
+ GList *scan;
+ char **values;
+
+ matcher = g_file_attribute_matcher_new (mask);
+ for (n = 0, i = 0; i < Main->priv->metadata_info->len; i++) {
+ GthMetadataInfo *metadata_info = g_ptr_array_index (Main->priv->metadata_info, i);
+
+ if (g_file_attribute_matcher_matches (matcher, metadata_info->id)) {
+ list = g_list_append (list, (char *) metadata_info->id);
+ n++;
+ }
+ }
+ list = g_list_reverse (list);
+
+ values = g_new (char *, n + 1);
+ for (i = 0, scan = list; scan; i++, scan = scan->next)
+ values[i] = g_strdup (scan->data);
+ values[n] = NULL;
+
+ g_list_free (list);
+ g_file_attribute_matcher_unref (matcher);
+
+ return values;
+}
+
+
+GthMetadataProvider *
+gth_main_get_metadata_reader (const char *id)
+{
+ GthMetadataProvider *metadata = NULL;
+ GList *scan;
+ const char *attribute_v[] = { id, NULL };
+
+ for (scan = Main->priv->metadata_provider; scan; scan = scan->next) {
+ GthMetadataProvider *registered_metadata = scan->data;
+
+ if (gth_metadata_provider_can_read (registered_metadata, (char **)attribute_v)) {
+ metadata = g_object_new (G_OBJECT_TYPE (registered_metadata), NULL);
+ break;
+ }
+ }
+
+ return metadata;
+}
+
+
+GthMetadataCategory *
+gth_main_get_metadata_category (const char *id)
+{
+ int i;
+
+ for (i = 0; i < Main->priv->metadata_category->len; i++) {
+ GthMetadataCategory *category;
+
+ category = g_ptr_array_index (Main->priv->metadata_category, i);
+ if (strcmp (category->id, id) == 0)
+ return category;
+ }
+
+ return NULL;
+}
+
+
+GthMetadataInfo *
+gth_main_get_metadata_info (const char *id)
+{
+ int i;
+
+ for (i = 0; i < Main->priv->metadata_info->len; i++) {
+ GthMetadataInfo *info;
+
+ info = g_ptr_array_index (Main->priv->metadata_info, i);
+ if (strcmp (info->id, id) == 0)
+ return info;
+ }
+
+ return NULL;
+}
+
+
+GPtrArray *
+gth_main_get_all_metadata_info (void)
+{
+ return Main->priv->metadata_info;
+}
+
+
+void
+gth_main_register_sort_type (GthFileDataSort *sort_type)
+{
+ g_hash_table_insert (Main->priv->sort_types, (gpointer) sort_type->name, sort_type);
+}
+
+
+GthFileDataSort *
+gth_main_get_sort_type (const char *name)
+{
+ if (name == NULL)
+ return NULL;
+ else
+ return g_hash_table_lookup (Main->priv->sort_types, name);
+}
+
+
+static void
+collect_sort_types (gpointer key,
+ gpointer value,
+ gpointer user_data)
+{
+ GList **sort_types = user_data;
+ GthFileDataSort *sort_type = value;
+
+ *sort_types = g_list_prepend (*sort_types, sort_type);
+}
+
+
+GList *
+gth_main_get_all_sort_types (void)
+{
+ GList *sort_types = NULL;
+
+ g_hash_table_foreach (Main->priv->sort_types, collect_sort_types, &sort_types);
+ return g_list_reverse (sort_types);
+}
+
+
+static GthTypeSpec *
+_gth_main_create_type_spec (GType object_type,
+ const char *first_property_name,
+ va_list var_args)
+{
+ GObject *object;
+ GthTypeSpec *type_spec;
+ const char *name;
+ guint n_alloced_params = 16;
+
+ type_spec = gth_type_spec_new (object_type);
+
+ if (first_property_name == NULL)
+ return type_spec;
+
+ object = g_object_new (object_type, NULL);
+
+ type_spec->params = g_new (GParameter, n_alloced_params);
+ name = first_property_name;
+ while (name != NULL) {
+ GParamSpec *pspec;
+ char *error = NULL;
+
+ pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (object), name);
+ if (pspec == NULL) {
+ g_warning ("%s: object class `%s' has no property named `%s'",
+ G_STRFUNC,
+ g_type_name (object_type),
+ name);
+ break;
+ }
+ type_spec->params[type_spec->n_params].name = name;
+ type_spec->params[type_spec->n_params].value.g_type = 0;
+ g_value_init (&type_spec->params[type_spec->n_params].value, G_PARAM_SPEC_VALUE_TYPE (pspec));
+ G_VALUE_COLLECT (&type_spec->params[type_spec->n_params].value, var_args, 0, &error);
+ if (error != NULL) {
+ g_warning ("%s: %s", G_STRFUNC, error);
+ g_free (error);
+ g_value_unset (&type_spec->params[type_spec->n_params].value);
+ break;
+ }
+ type_spec->n_params++;
+ name = va_arg (var_args, char*);
+ }
+
+ g_object_unref (object);
+
+ return type_spec;
+}
+
+
+void
+gth_main_register_test (const char *object_id,
+ GType object_type,
+ const char *first_property_name,
+ ...)
+{
+ va_list var_args;
+ GthTypeSpec *spec;
+
+ va_start (var_args, first_property_name);
+ spec = _gth_main_create_type_spec (object_type, first_property_name, var_args);
+ va_end (var_args);
+
+ g_hash_table_insert (Main->priv->tests, (gpointer) object_id, spec);
+}
+
+
+GthTest *
+gth_main_get_test (const char *object_id)
+{
+ GthTypeSpec *spec = NULL;
+
+ if (object_id == NULL)
+ return NULL;
+
+ spec = g_hash_table_lookup (Main->priv->tests, object_id);
+ if (spec == NULL)
+ return NULL;
+
+ return (GthTest *) gth_type_spec_create_object (spec, object_id);
+}
+
+
+static void
+collect_objects (gpointer key,
+ gpointer value,
+ gpointer user_data)
+{
+ GList **objects = user_data;
+ GthTypeSpec *spec = value;
+
+ *objects = g_list_prepend (*objects, gth_type_spec_create_object (spec, key));
+}
+
+
+G_GNUC_UNUSED
+static GList *
+_gth_main_get_all_objects (GHashTable *table)
+{
+ GList *objects = NULL;
+
+ g_hash_table_foreach (table, collect_objects, &objects);
+ return g_list_reverse (objects);
+}
+
+
+static void
+collect_names (gpointer key,
+ gpointer value,
+ gpointer user_data)
+{
+ GList **objects = user_data;
+
+ *objects = g_list_prepend (*objects, g_strdup (key));
+}
+
+
+static GList *
+_gth_main_get_all_object_names (GHashTable *table)
+{
+ GList *objects = NULL;
+
+ g_hash_table_foreach (table, collect_names, &objects);
+ return g_list_reverse (objects);
+}
+
+
+GList *
+gth_main_get_all_tests (void)
+{
+ return _gth_main_get_all_object_names (Main->priv->tests);
+}
+
+
+void
+gth_main_register_file_loader (FileLoader loader,
+ const char *first_mime_type,
+ ...)
+{
+ va_list var_args;
+ const char *mime_type;
+
+ va_start (var_args, first_mime_type);
+ mime_type = first_mime_type;
+ while (mime_type != NULL) {
+ mime_type = va_arg (var_args, const char *);
+ g_hash_table_insert (Main->priv->loaders, (gpointer) get_static_string (mime_type), loader);
+ }
+ va_end (var_args);
+}
+
+
+FileLoader
+gth_main_get_file_loader (const char *mime_type)
+{
+ FileLoader loader;
+
+ loader = g_hash_table_lookup (Main->priv->loaders, mime_type);
+ if (loader == NULL)
+ loader = gth_pixbuf_animation_new_from_file;
+
+ return loader;
+}
+
+
+GthTest *
+gth_main_get_general_filter (void)
+{
+ char *filter_name;
+ GthTest *filter;
+
+ filter_name = eel_gconf_get_string (PREF_GENERAL_FILTER, DEFAULT_GENERAL_FILTER);
+ filter = gth_main_get_test (filter_name);
+ g_free (filter_name);
+
+ return filter;
+}
+
+
+GthTest *
+gth_main_add_general_filter (GthTest *original_filter)
+{
+ GthTest *test;
+
+ if (original_filter == NULL)
+ test = gth_main_get_general_filter ();
+ else
+ test = (GthTest *) gth_duplicable_duplicate (GTH_DUPLICABLE (original_filter));
+
+ if (! GTH_IS_FILTER (test)) {
+ GthTest *new_chain;
+ GthFilter *filter;
+
+ filter = gth_filter_new ();
+
+ new_chain = gth_test_chain_new (GTH_MATCH_TYPE_ALL, NULL);
+ gth_test_chain_add_test (GTH_TEST_CHAIN (new_chain), test);
+ g_object_unref (test);
+
+ if (strncmp (gth_test_get_id (test), "file::type::", 12) != 0) {
+ GthTest *file_type_filter;
+
+ file_type_filter = gth_main_get_general_filter ();
+ gth_test_chain_add_test (GTH_TEST_CHAIN (new_chain), file_type_filter);
+ g_object_unref (file_type_filter);
+ }
+ gth_filter_set_test (filter, GTH_TEST_CHAIN (new_chain));
+ g_object_unref (new_chain);
+
+ test = (GthTest*) filter;
+ }
+ else {
+ GthFilter *filter;
+ GthTestChain *filter_test;
+
+ filter = (GthFilter *) gth_duplicable_duplicate (GTH_DUPLICABLE (test));
+ filter_test = gth_filter_get_test (filter);
+ if ((filter_test == NULL) || ! gth_test_chain_has_type_test (filter_test)) {
+ GthTest *new_filter_test;
+ GthTest *general_filter;
+
+ new_filter_test = gth_test_chain_new (GTH_MATCH_TYPE_ALL, NULL);
+ if (filter_test != NULL)
+ gth_test_chain_add_test (GTH_TEST_CHAIN (new_filter_test), GTH_TEST (filter_test));
+
+ general_filter = gth_main_get_general_filter ();
+ gth_test_chain_add_test (GTH_TEST_CHAIN (new_filter_test), general_filter);
+ g_object_unref (general_filter);
+
+ gth_filter_set_test (filter, GTH_TEST_CHAIN (new_filter_test));
+ g_object_unref (new_filter_test);
+ }
+
+ if (filter_test != NULL)
+ g_object_unref (filter_test);
+ g_object_unref (test);
+
+ test = (GthTest*) filter;
+ }
+
+ return test;
+}
+
+
+void
+gth_main_register_viewer_page (GType viewer_page_type)
+{
+ GObject *viewer_page;
+
+ viewer_page = g_object_new (viewer_page_type, NULL);
+ Main->priv->viewer_pages = g_list_prepend (Main->priv->viewer_pages, viewer_page);
+}
+
+
+GList *
+gth_main_get_all_viewer_pages (void)
+{
+ return Main->priv->viewer_pages;
+}
+
+
+static void
+_g_destroy_array (GArray *array)
+{
+ g_array_free (array, TRUE);
+}
+
+
+void
+gth_main_register_type (const char *set_name,
+ GType object_type)
+{
+ GArray *set;
+
+ if (Main->priv->types == NULL)
+ Main->priv->types = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ (GDestroyNotify) g_free,
+ (GDestroyNotify) _g_destroy_array);
+
+ set = g_hash_table_lookup (Main->priv->types, set_name);
+ if (set == NULL) {
+ set = g_array_new (FALSE, FALSE, sizeof (GType));
+ g_hash_table_insert (Main->priv->types, g_strdup (set_name), set);
+ }
+
+ g_array_append_val (set, object_type);
+}
+
+
+GArray *
+gth_main_get_type_set (const char *set_name)
+{
+ return g_hash_table_lookup (Main->priv->types, set_name);
+}
+
+
+GBookmarkFile *
+gth_main_get_default_bookmarks (void)
+{
+ char *path;
+
+ if (Main->priv->bookmarks != NULL)
+ return Main->priv->bookmarks;
+
+ Main->priv->bookmarks = g_bookmark_file_new ();
+
+ path = gth_user_dir_get_file (GTH_DIR_CONFIG, GTHUMB_DIR, BOOKMARKS_FILE, NULL);
+ g_bookmark_file_load_from_file (Main->priv->bookmarks,
+ path,
+ NULL);
+ g_free (path);
+
+ return Main->priv->bookmarks;
+}
+
+
+void
+gth_main_bookmarks_changed (void)
+{
+ char *filename;
+
+ gth_user_dir_make_dir_for_file (GTH_DIR_CONFIG, GTHUMB_DIR, BOOKMARKS_FILE, NULL);
+
+ filename = gth_user_dir_get_file (GTH_DIR_CONFIG, GTHUMB_DIR, BOOKMARKS_FILE, NULL);
+ g_bookmark_file_to_file (Main->priv->bookmarks, filename, NULL);
+ g_free (filename);
+
+ gth_monitor_bookmarks_changed (gth_main_get_default_monitor ());
+}
+
+
+GthFilterFile *
+gth_main_get_default_filter_file (void)
+{
+ char *path;
+
+ if (Main->priv->filters != NULL)
+ return Main->priv->filters;
+
+ Main->priv->filters = gth_filter_file_new ();
+ path = gth_user_dir_get_file (GTH_DIR_CONFIG, GTHUMB_DIR, FILTERS_FILE, NULL);
+ gth_filter_file_load_from_file (Main->priv->filters, path, NULL);
+ g_free (path);
+
+ return Main->priv->filters;
+}
+
+
+GList *
+gth_main_get_all_filters (void)
+{
+ GthFilterFile *filter_file;
+ GList *filters;
+ GList *registered_tests;
+ GList *scan;
+ gboolean changed = FALSE;
+
+ filter_file = gth_main_get_default_filter_file ();
+ filters = gth_filter_file_get_tests (filter_file);
+
+ registered_tests = gth_main_get_all_tests ();
+ for (scan = registered_tests; scan; scan = scan->next) {
+ const char *registered_test_id = scan->data;
+ gboolean test_present = FALSE;
+ GList *scan2;
+
+ for (scan2 = filters; ! test_present && scan2; scan2 = scan2->next) {
+ GthTest *test = scan2->data;
+
+ if (g_strcmp0 (gth_test_get_id (test), registered_test_id) == 0)
+ test_present = TRUE;
+ }
+
+ if (! test_present) {
+ GthTest *registered_test;
+
+ registered_test = gth_main_get_test (registered_test_id);
+ filters = g_list_append (filters, registered_test);
+ gth_filter_file_add (filter_file, registered_test);
+ changed = TRUE;
+ }
+ }
+ _g_string_list_free (registered_tests);
+
+ if (changed)
+ gth_main_filters_changed ();
+
+ return filters;
+}
+
+
+void
+gth_main_filters_changed (void)
+{
+ char *filename;
+
+ gth_user_dir_make_dir_for_file (GTH_DIR_CONFIG, GTHUMB_DIR, FILTERS_FILE, NULL);
+
+ filename = gth_user_dir_get_file (GTH_DIR_CONFIG, GTHUMB_DIR, FILTERS_FILE, NULL);
+ gth_filter_file_to_file (Main->priv->filters, filename, NULL);
+ g_free (filename);
+
+ gth_monitor_filters_changed (gth_main_get_default_monitor ());
+}
+
+
+GthMonitor *
+gth_main_get_default_monitor (void)
+{
+ if (Main->priv->monitor != NULL)
+ return Main->priv->monitor;
+
+ Main->priv->monitor = gth_monitor_new ();
+
+ return Main->priv->monitor;
+}
+
+
+GthExtensionManager *
+gth_main_get_default_extension_manager (void)
+{
+ return Main->priv->extension_manager;
+}
+
+
+void
+gth_main_activate_extensions (void)
+{
+ const char *default_extensions[] = { "catalogs", "comments", "exiv2", "file_manager", "file_viewer", "image_tools", "image_viewer", "search", NULL };
+ int i;
+
+ if (Main->priv->extension_manager == NULL)
+ Main->priv->extension_manager = gth_extension_manager_new ();
+
+ /* FIXME: read the extensions list from a gconf key */
+
+ for (i = 0; default_extensions[i] != NULL; i++) {
+ GthExtension *extension;
+
+ extension = gth_extension_module_new (default_extensions[i]);
+ if ((extension != NULL) && gth_extension_open (extension))
+ gth_extension_activate (extension);
+ }
+}
diff --git a/gthumb/gth-main.h b/gthumb/gth-main.h
new file mode 100644
index 0000000..1a0ee44
--- /dev/null
+++ b/gthumb/gth-main.h
@@ -0,0 +1,121 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2008 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GTH_MAIN_H
+#define GTH_MAIN_H
+
+#include <glib.h>
+#include <glib-object.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include "gth-file-data.h"
+#include "gth-extensions.h"
+#include "gth-file-source.h"
+#include "gth-filter-file.h"
+#include "gth-hook.h"
+#include "gth-metadata-provider.h"
+#include "gth-monitor.h"
+#include "gth-test.h"
+
+G_BEGIN_DECLS
+
+#define GTH_TYPE_MAIN (gth_main_get_type ())
+#define GTH_MAIN(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GTH_TYPE_MAIN, GthMain))
+#define GTH_MAIN_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), GTH_TYPE_MAIN, GthMainClass))
+#define GTH_IS_MAIN(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GTH_TYPE_MAIN))
+#define GTH_IS_MAIN_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GTH_TYPE_MAIN))
+#define GTH_MAIN_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS((o), GTH_TYPE_MAIN, GthMainClass))
+
+typedef struct _GthMain GthMain;
+typedef struct _GthMainPrivate GthMainPrivate;
+typedef struct _GthMainClass GthMainClass;
+
+typedef GdkPixbufAnimation* (*FileLoader) (GthFileData *file_data,
+ GError **error,
+ int requested_width,
+ int requested_height);
+
+struct _GthMain {
+ GObject __parent;
+ GthMainPrivate *priv;
+};
+
+struct _GthMainClass {
+ GObjectClass __parent_class;
+};
+
+GType gth_main_get_type (void) G_GNUC_CONST;
+void gth_main_initialize (void);
+void gth_main_release (void);
+void gth_main_register_file_source (GType file_source_type);
+GthFileSource * gth_main_get_file_source_for_uri (const char *uri);
+GthFileSource * gth_main_get_file_source (GFile *file);
+GList * gth_main_get_all_file_sources (void);
+GList * gth_main_get_all_entry_points (void);
+char * gth_main_get_gio_uri (const char *uri);
+GFile * gth_main_get_gio_file (GFile *file);
+void gth_main_register_metadata_category (GthMetadataCategory *metadata_category);
+GthMetadataInfo * gth_main_register_metadata_info (GthMetadataInfo *metadata_info);
+void gth_main_register_metadata_info_v (GthMetadataInfo metadata_info[]);
+void gth_main_register_metadata_provider (GType metadata_provider_type);
+GList * gth_main_get_all_metadata_providers (void);
+char ** gth_main_get_metadata_attributes (const char *mask);
+GthMetadataProvider * gth_main_get_metadata_reader (const char *id);
+GthMetadataCategory * gth_main_get_metadata_category (const char *id);
+GthMetadataInfo * gth_main_get_metadata_info (const char *id);
+GPtrArray * gth_main_get_all_metadata_info (void);
+void gth_main_register_sort_type (GthFileDataSort *sort_type);
+GthFileDataSort * gth_main_get_sort_type (const char *name);
+GList * gth_main_get_all_sort_types (void);
+void gth_main_register_test (const char *id,
+ GType type,
+ const char *first_property,
+ ...);
+GthTest * gth_main_get_test (const char *id);
+GList * gth_main_get_all_tests (void);
+void gth_main_register_file_loader (FileLoader loader,
+ const char *first_mime_type,
+ ...);
+FileLoader gth_main_get_file_loader (const char *mime_type);
+GthTest * gth_main_get_general_filter (void);
+GthTest * gth_main_add_general_filter (GthTest *filter);
+void gth_main_register_viewer_page (GType viewer_page_type);
+GList * gth_main_get_all_viewer_pages (void);
+void gth_main_register_type (const char *set_name,
+ GType object_type);
+GArray * gth_main_get_type_set (const char *set_name);
+GBookmarkFile * gth_main_get_default_bookmarks (void);
+void gth_main_bookmarks_changed (void);
+GthFilterFile * gth_main_get_default_filter_file (void);
+GList * gth_main_get_all_filters (void);
+void gth_main_filters_changed (void);
+GthMonitor * gth_main_get_default_monitor (void);
+GthExtensionManager * gth_main_get_default_extension_manager (void);
+void gth_main_register_default_hooks (void);
+void gth_main_register_default_tests (void);
+void gth_main_register_default_types (void);
+void gth_main_register_default_sort_types (void);
+void gth_main_register_default_metadata (void);
+void gth_main_activate_extensions (void);
+
+G_END_DECLS
+
+#endif /* GTH_MAIN_H */
diff --git a/gthumb/gth-marshal.list b/gthumb/gth-marshal.list
new file mode 100644
index 0000000..f05703d
--- /dev/null
+++ b/gthumb/gth-marshal.list
@@ -0,0 +1,8 @@
+VOID:ENUM, ENUM
+VOID:INT, INT
+VOID:OBJECT, BOXED, ENUM
+VOID:OBJECT, OBJECT
+VOID:OBJECT, BOOLEAN
+VOID:OBJECT, UINT
+VOID:OBJECT, STRING
+VOID:POINTER, POINTER
diff --git a/gthumb/gth-metadata-provider-file.c b/gthumb/gth-metadata-provider-file.c
new file mode 100644
index 0000000..263f89d
--- /dev/null
+++ b/gthumb/gth-metadata-provider-file.c
@@ -0,0 +1,170 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <string.h>
+#include <glib/gi18n.h>
+#include <glib.h>
+#include "glib-utils.h"
+#include "gth-metadata-provider-file.h"
+
+
+#define GTH_METADATA_PROVIDER_FILE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GTH_TYPE_METADATA_PROVIDER_COMMENT, GthMetadataProviderFilePrivate))
+
+
+struct _GthMetadataProviderFilePrivate {
+ int dummy;
+};
+
+
+static GthMetadataProviderClass *parent_class = NULL;
+
+
+static void
+gth_metadata_provider_file_read (GthMetadataProvider *self,
+ GthFileData *file_data,
+ const char *attributes)
+{
+ GFileAttributeMatcher *matcher;
+
+ matcher = g_file_attribute_matcher_new (attributes);
+
+ if (g_file_attribute_matcher_matches (matcher, "file::display-size")) {
+ char *value;
+
+ value = g_format_size_for_display (g_file_info_get_size (file_data->info));
+ g_file_info_set_attribute_string (file_data->info, "file::display-size", value);
+
+ g_free (value);
+ }
+
+ if (g_file_attribute_matcher_matches (matcher, "file::display-ctime")) {
+ GTimeVal timeval;
+ char *value;
+
+ timeval.tv_sec = g_file_info_get_attribute_uint64 (file_data->info, "time::created");
+ timeval.tv_usec = g_file_info_get_attribute_uint32 (file_data->info, "time::created-usec");
+
+ value = _g_time_val_to_exif_date (&timeval);
+ g_file_info_set_attribute_string (file_data->info, "file::display-ctime", value);
+
+ g_free (value);
+ }
+
+ if (g_file_attribute_matcher_matches (matcher, "file::display-mtime")) {
+ GTimeVal *timeval;
+ char *value;
+
+ timeval = gth_file_data_get_modification_time (file_data);
+ value = _g_time_val_to_exif_date (timeval);
+ g_file_info_set_attribute_string (file_data->info, "file::display-mtime", value);
+
+ g_free (value);
+ }
+
+ if (g_file_attribute_matcher_matches (matcher, "file::content-type")) {
+ const char *value;
+
+ value = get_static_string (g_file_info_get_content_type (file_data->info));
+ if (value != NULL)
+ g_file_info_set_attribute_string (file_data->info, "file::content-type", value);
+ }
+
+ g_file_attribute_matcher_unref (matcher);
+}
+
+
+static void
+gth_metadata_provider_file_finalize (GObject *object)
+{
+ /*GthMetadataProviderFile *file = GTH_METADATA_PROVIDER_FILE (object);*/
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+
+static GObject *
+gth_metadata_provider_constructor (GType type,
+ guint n_construct_properties,
+ GObjectConstructParam *construct_properties)
+{
+ GthMetadataProviderClass *klass;
+ GObjectClass *parent_class;
+ GObject *obj;
+ GthMetadataProvider *self;
+
+ klass = GTH_METADATA_PROVIDER_CLASS (g_type_class_peek (GTH_TYPE_METADATA_PROVIDER));
+ parent_class = G_OBJECT_CLASS (g_type_class_peek_parent (klass));
+ obj = parent_class->constructor (type, n_construct_properties, construct_properties);
+ self = GTH_METADATA_PROVIDER (obj);
+
+ g_object_set (self, "readable-attributes", "file::display-size,file::display-mtime,file::content-type,file::is-modified", NULL);
+
+ return obj;
+}
+
+
+static void
+gth_metadata_provider_file_class_init (GthMetadataProviderFileClass *klass)
+{
+ parent_class = g_type_class_peek_parent (klass);
+ g_type_class_add_private (klass, sizeof (GthMetadataProviderFilePrivate));
+
+ G_OBJECT_CLASS (klass)->finalize = gth_metadata_provider_file_finalize;
+ G_OBJECT_CLASS (klass)->constructor = gth_metadata_provider_constructor;
+
+ GTH_METADATA_PROVIDER_CLASS (klass)->read = gth_metadata_provider_file_read;
+}
+
+
+static void
+gth_metadata_provider_file_init (GthMetadataProviderFile *catalogs)
+{
+}
+
+
+GType
+gth_metadata_provider_file_get_type (void)
+{
+ static GType type = 0;
+
+ if (! type) {
+ GTypeInfo type_info = {
+ sizeof (GthMetadataProviderFileClass),
+ NULL,
+ NULL,
+ (GClassInitFunc) gth_metadata_provider_file_class_init,
+ NULL,
+ NULL,
+ sizeof (GthMetadataProviderFile),
+ 0,
+ (GInstanceInitFunc) gth_metadata_provider_file_init
+ };
+
+ type = g_type_register_static (GTH_TYPE_METADATA_PROVIDER,
+ "GthMetadataProviderFile",
+ &type_info,
+ 0);
+ }
+
+ return type;
+}
diff --git a/gthumb/gth-metadata-provider-file.h b/gthumb/gth-metadata-provider-file.h
new file mode 100644
index 0000000..42a3bc0
--- /dev/null
+++ b/gthumb/gth-metadata-provider-file.h
@@ -0,0 +1,58 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GTH_METADATA_PROVIDER_FILE_H
+#define GTH_METADATA_PROVIDER_FILE_H
+
+#include <glib.h>
+#include <glib-object.h>
+#include "gth-metadata-provider.h"
+
+G_BEGIN_DECLS
+
+#define GTH_TYPE_METADATA_PROVIDER_FILE (gth_metadata_provider_file_get_type ())
+#define GTH_METADATA_PROVIDER_FILE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GTH_TYPE_METADATA_PROVIDER_FILE, GthMetadataProviderFile))
+#define GTH_METADATA_PROVIDER_FILE_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), GTH_TYPE_METADATA_PROVIDER_FILE, GthMetadataProviderFileClass))
+#define GTH_IS_METADATA_PROVIDER_FILE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GTH_TYPE_METADATA_PROVIDER_FILE))
+#define GTH_IS_METADATA_PROVIDER_FILE_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GTH_TYPE_METADATA_PROVIDER_FILE))
+#define GTH_METADATA_PROVIDER_FILE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS((o), GTH_TYPE_METADATA_PROVIDER_FILE, GthMetadataProviderFileClass))
+
+typedef struct _GthMetadataProviderFile GthMetadataProviderFile;
+typedef struct _GthMetadataProviderFilePrivate GthMetadataProviderFilePrivate;
+typedef struct _GthMetadataProviderFileClass GthMetadataProviderFileClass;
+
+struct _GthMetadataProviderFile
+{
+ GthMetadataProvider __parent;
+ GthMetadataProviderFilePrivate *priv;
+};
+
+struct _GthMetadataProviderFileClass
+{
+ GthMetadataProviderClass __parent_class;
+};
+
+GType gth_metadata_provider_file_get_type (void) G_GNUC_CONST;
+
+G_END_DECLS
+
+#endif /* GTH_METADATA_PROVIDER_FILE_H */
diff --git a/gthumb/gth-metadata-provider.c b/gthumb/gth-metadata-provider.c
new file mode 100644
index 0000000..e17da12
--- /dev/null
+++ b/gthumb/gth-metadata-provider.c
@@ -0,0 +1,501 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include "glib-utils.h"
+#include "gth-file-data.h"
+#include "gth-main.h"
+#include "gth-metadata-provider.h"
+
+
+#define GTH_METADATA_PROVIDER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GTH_TYPE_METADATA_PROVIDER, GthMetadataProviderPrivate))
+#define CHECK_THREAD_RATE 5
+
+
+enum {
+ GTH_METADATA_PROVIDER_DUMMY_PROPERTY,
+ GTH_METADATA_PROVIDER_READABLE_ATTRIBUTES,
+ GTH_METADATA_PROVIDER_WRITABLE_ATTRIBUTES
+};
+
+
+struct _GthMetadataProviderPrivate {
+ char *_readable_attributes;
+ char *_writable_attributes;
+};
+
+static gpointer gth_metadata_provider_parent_class = NULL;
+
+
+static void
+gth_metadata_provider_real_read (GthMetadataProvider *self,
+ GthFileData *file_data,
+ const char *attributes)
+{
+}
+
+
+static void
+gth_metadata_provider_real_write (GthMetadataProvider *self,
+ GthFileData *file_data,
+ const char *attributes)
+{
+}
+
+
+static void
+gth_metadata_provider_set_readable_attributes (GthMetadataProvider *self,
+ const char *value)
+{
+ if (self->priv->_readable_attributes != NULL) {
+ g_free (self->priv->_readable_attributes);
+ self->priv->_readable_attributes = NULL;
+ }
+
+ if (value != NULL)
+ self->priv->_readable_attributes = g_strdup (value);
+}
+
+
+static void
+gth_metadata_provider_set_writable_attributes (GthMetadataProvider *self,
+ const char *value)
+{
+ if (self->priv->_writable_attributes != NULL) {
+ g_free (self->priv->_writable_attributes);
+ self->priv->_writable_attributes = NULL;
+ }
+
+ if (value != NULL)
+ self->priv->_writable_attributes = g_strdup (value);
+}
+
+
+static void
+gth_metadata_provider_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GthMetadataProvider *self;
+
+ self = GTH_METADATA_PROVIDER (object);
+
+ switch (property_id) {
+ case GTH_METADATA_PROVIDER_READABLE_ATTRIBUTES:
+ gth_metadata_provider_set_readable_attributes (self, g_value_get_string (value));
+ break;
+ case GTH_METADATA_PROVIDER_WRITABLE_ATTRIBUTES:
+ gth_metadata_provider_set_writable_attributes (self, g_value_get_string (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+
+static void
+gth_metadata_provider_finalize (GObject *obj)
+{
+ GthMetadataProvider *self;
+
+ self = GTH_METADATA_PROVIDER (obj);
+
+ g_free (self->priv->_readable_attributes);
+ g_free (self->priv->_writable_attributes);
+
+ G_OBJECT_CLASS (gth_metadata_provider_parent_class)->finalize (obj);
+}
+
+
+static void
+gth_metadata_provider_class_init (GthMetadataProviderClass * klass)
+{
+ gth_metadata_provider_parent_class = g_type_class_peek_parent (klass);
+ g_type_class_add_private (klass, sizeof (GthMetadataProviderPrivate));
+
+ G_OBJECT_CLASS (klass)->set_property = gth_metadata_provider_set_property;
+ G_OBJECT_CLASS (klass)->finalize = gth_metadata_provider_finalize;
+
+ GTH_METADATA_PROVIDER_CLASS (klass)->read = gth_metadata_provider_real_read;
+ GTH_METADATA_PROVIDER_CLASS (klass)->write = gth_metadata_provider_real_write;
+
+ g_object_class_install_property (G_OBJECT_CLASS (klass),
+ GTH_METADATA_PROVIDER_READABLE_ATTRIBUTES,
+ g_param_spec_string ("readable-attributes",
+ "readable-attributes",
+ "readable-attributes",
+ NULL,
+ G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
+ g_object_class_install_property (G_OBJECT_CLASS (klass),
+ GTH_METADATA_PROVIDER_WRITABLE_ATTRIBUTES,
+ g_param_spec_string ("writable-attributes",
+ "writable-attributes",
+ "writable-attributes",
+ NULL,
+ G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
+}
+
+
+static void
+gth_metadata_provider_instance_init (GthMetadataProvider *self)
+{
+ self->priv = GTH_METADATA_PROVIDER_GET_PRIVATE (self);
+}
+
+
+GType
+gth_metadata_provider_get_type (void)
+{
+ static GType gth_metadata_provider_type_id = 0;
+ if (gth_metadata_provider_type_id == 0) {
+ static const GTypeInfo g_define_type_info = {
+ sizeof (GthMetadataProviderClass),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) gth_metadata_provider_class_init,
+ (GClassFinalizeFunc) NULL,
+ NULL,
+ sizeof (GthMetadataProvider),
+ 0,
+ (GInstanceInitFunc) gth_metadata_provider_instance_init,
+ NULL
+ };
+ gth_metadata_provider_type_id = g_type_register_static (G_TYPE_OBJECT, "GthMetadataProvider", &g_define_type_info, 0);
+ }
+ return gth_metadata_provider_type_id;
+}
+
+
+GthMetadataProvider *
+gth_metadata_provider_new (void)
+{
+
+ return g_object_new (GTH_TYPE_METADATA_PROVIDER, NULL);
+}
+
+
+static gboolean
+attribute_matches_attributes (const char *attributes,
+ char **attribute_v)
+{
+ GFileAttributeMatcher *matcher;
+ gboolean matches;
+ int i;
+
+ if (attributes == NULL)
+ return FALSE;
+
+ matcher = g_file_attribute_matcher_new (attributes);
+ matches = FALSE;
+ for (i = 0; ! matches && (attribute_v[i] != NULL); i++)
+ matches = g_file_attribute_matcher_matches (matcher, attribute_v[i]);
+
+ g_file_attribute_matcher_unref (matcher);
+
+ return matches;
+}
+
+
+gboolean
+gth_metadata_provider_can_read (GthMetadataProvider *self,
+ char **attribute_v)
+{
+ g_return_val_if_fail (self != NULL, FALSE);
+ g_return_val_if_fail (attribute_v != NULL, FALSE);
+
+ return attribute_matches_attributes (self->priv->_readable_attributes, attribute_v);
+}
+
+
+gboolean
+gth_metadata_provider_can_write (GthMetadataProvider *self,
+ char **attribute_v)
+{
+ g_return_val_if_fail (self != NULL, FALSE);
+ g_return_val_if_fail (attribute_v != NULL, FALSE);
+
+ return attribute_matches_attributes (self->priv->_writable_attributes, attribute_v);
+}
+
+
+void
+gth_metadata_provider_read (GthMetadataProvider *self,
+ GthFileData *file_data,
+ const char *attributes)
+{
+ GTH_METADATA_PROVIDER_GET_CLASS (self)->read (self, file_data, attributes);
+}
+
+
+void
+gth_metadata_provider_write (GthMetadataProvider *self,
+ GthFileData *file_data,
+ const char *attributes)
+{
+ GTH_METADATA_PROVIDER_GET_CLASS (self)->write (self, file_data, attributes);
+}
+
+
+/* -- _g_query_metadata_async -- */
+
+
+typedef struct {
+ GList *files;
+ char *attributes;
+ char **attributes_v;
+ GMutex *mutex;
+ gboolean thread_done;
+ GError *error;
+} QueryMetadataThreadData;
+
+
+typedef struct {
+ GCancellable *cancellable;
+ InfoReadyCallback ready_func;
+ gpointer user_data;
+ guint check_id;
+ GThread *thread;
+ QueryMetadataThreadData *rmtd;
+} QueryMetadataData;
+
+
+static void
+query_metadata_done (QueryMetadataData *rmd)
+{
+ QueryMetadataThreadData *rmtd = rmd->rmtd;
+
+ g_thread_join (rmd->thread);
+
+ if (rmd->ready_func != NULL)
+ (*rmd->ready_func) (rmtd->files, rmtd->error, rmd->user_data);
+
+ g_mutex_free (rmtd->mutex);
+ g_strfreev (rmtd->attributes_v);
+ g_free (rmtd->attributes);
+ _g_object_list_unref (rmtd->files);
+ g_free (rmtd);
+ g_free (rmd);
+}
+
+
+static gpointer
+read_metadata_thread (gpointer data)
+{
+ QueryMetadataThreadData *rmtd = data;
+ GList *scan;
+
+ for (scan = gth_main_get_all_metadata_providers (); scan; scan = scan->next) {
+ GthMetadataProvider *metadata_provider;
+
+ metadata_provider = g_object_new (G_OBJECT_TYPE (scan->data), NULL);
+
+ if (gth_metadata_provider_can_read (metadata_provider, rmtd->attributes_v)) {
+ GList *scan_files;
+
+ for (scan_files = rmtd->files; scan_files; scan_files = scan_files->next) {
+ GthFileData *file_data = scan_files->data;
+ gth_metadata_provider_read (metadata_provider, file_data, rmtd->attributes);
+ }
+ }
+
+ g_object_unref (metadata_provider);
+ }
+
+ g_mutex_lock (rmtd->mutex);
+ rmtd->thread_done = TRUE;
+ g_mutex_unlock (rmtd->mutex);
+
+ return rmtd;
+}
+
+
+static gboolean
+check_read_metadata_thread (gpointer data)
+{
+ QueryMetadataData *rmd = data;
+ QueryMetadataThreadData *rmtd = rmd->rmtd;
+ gboolean thread_done;
+
+ g_source_remove (rmd->check_id);
+ rmd->check_id = 0;
+
+ g_mutex_lock (rmtd->mutex);
+ thread_done = rmtd->thread_done;
+ g_mutex_unlock (rmtd->mutex);
+
+ if (thread_done)
+ query_metadata_done (rmd);
+ else
+ rmd->check_id = g_timeout_add (CHECK_THREAD_RATE, check_read_metadata_thread, rmd);
+
+ return FALSE;
+}
+
+
+void
+_g_query_metadata_async (GList *files, /* GthFileData * list */
+ const char *attributes,
+ GCancellable *cancellable,
+ InfoReadyCallback ready_func,
+ gpointer user_data)
+{
+ QueryMetadataData *rmd;
+ QueryMetadataThreadData *rmtd;
+
+ rmtd = g_new0 (QueryMetadataThreadData, 1);
+ rmtd->files = _g_object_list_ref (files);
+ rmtd->attributes = g_strdup (attributes);
+ rmtd->attributes_v = gth_main_get_metadata_attributes (attributes);
+ rmtd->mutex = g_mutex_new ();
+
+ rmd = g_new0 (QueryMetadataData, 1);
+ rmd->cancellable = cancellable;
+ rmd->ready_func = ready_func;
+ rmd->user_data = user_data;
+ rmd->rmtd = rmtd;
+ rmd->thread = g_thread_create (read_metadata_thread, rmtd, TRUE, NULL);
+ rmd->check_id = g_timeout_add (CHECK_THREAD_RATE, check_read_metadata_thread, rmd);
+}
+
+
+/* -- _g_write_metadata_async -- */
+
+
+typedef struct {
+ GList *files;
+ char *attributes;
+ char **attributes_v;
+ GMutex *mutex;
+ gboolean thread_done;
+ GError *error;
+} WriteMetadataThreadData;
+
+
+typedef struct {
+ GCancellable *cancellable;
+ ReadyFunc ready_func;
+ gpointer user_data;
+ guint check_id;
+ GThread *thread;
+ WriteMetadataThreadData *wmtd;
+} WriteMetadataData;
+
+
+static void
+write_metadata_done (WriteMetadataData *wmd)
+{
+ WriteMetadataThreadData *wmtd = wmd->wmtd;
+
+ g_thread_join (wmd->thread);
+
+ if (wmd->ready_func != NULL)
+ (*wmd->ready_func) (wmtd->error, wmd->user_data);
+
+ g_mutex_free (wmtd->mutex);
+ g_strfreev (wmtd->attributes_v);
+ g_free (wmtd->attributes);
+ _g_object_list_unref (wmtd->files);
+ g_free (wmtd);
+ g_free (wmd);
+}
+
+
+static gpointer
+write_metadata_thread (gpointer data)
+{
+ WriteMetadataThreadData *wmtd = data;
+ GList *scan;
+
+ for (scan = gth_main_get_all_metadata_providers (); scan; scan = scan->next) {
+ GthMetadataProvider *metadata_provider;
+
+ metadata_provider = g_object_new (G_OBJECT_TYPE (scan->data), NULL);
+
+ if (gth_metadata_provider_can_write (metadata_provider, wmtd->attributes_v)) {
+ GList *scan_files;
+
+ for (scan_files = wmtd->files; scan_files; scan_files = scan_files->next) {
+ GthFileData *file_data = scan_files->data;
+ gth_metadata_provider_write (metadata_provider, file_data, wmtd->attributes);
+ }
+ }
+
+ g_object_unref (metadata_provider);
+ }
+
+ g_mutex_lock (wmtd->mutex);
+ wmtd->thread_done = TRUE;
+ g_mutex_unlock (wmtd->mutex);
+
+ return wmtd;
+}
+
+
+static gboolean
+check_write_metadata_thread (gpointer data)
+{
+ WriteMetadataData *wmd = data;
+ WriteMetadataThreadData *wmtd = wmd->wmtd;
+ gboolean thread_done;
+
+ g_source_remove (wmd->check_id);
+ wmd->check_id = 0;
+
+ g_mutex_lock (wmtd->mutex);
+ thread_done = wmtd->thread_done;
+ g_mutex_unlock (wmtd->mutex);
+
+ if (thread_done)
+ write_metadata_done (wmd);
+ else
+ wmd->check_id = g_timeout_add (CHECK_THREAD_RATE, check_write_metadata_thread, wmd);
+
+ return FALSE;
+}
+
+
+void
+_g_write_metadata_async (GList *files, /* GthFileData * list */
+ const char *attributes,
+ GCancellable *cancellable,
+ ReadyFunc ready_func,
+ gpointer user_data)
+{
+ WriteMetadataData *wmd;
+ WriteMetadataThreadData *wmtd;
+
+ wmtd = g_new0 (WriteMetadataThreadData, 1);
+ wmtd->files = _g_object_list_ref (files);
+ wmtd->attributes = g_strdup (attributes);
+ wmtd->attributes_v = gth_main_get_metadata_attributes (attributes);
+ wmtd->mutex = g_mutex_new ();
+
+ wmd = g_new0 (WriteMetadataData, 1);
+ wmd->cancellable = cancellable;
+ wmd->ready_func = ready_func;
+ wmd->user_data = user_data;
+ wmd->wmtd = wmtd;
+ wmd->thread = g_thread_create (write_metadata_thread, wmtd, TRUE, NULL);
+ wmd->check_id = g_timeout_add (CHECK_THREAD_RATE, check_write_metadata_thread, wmd);
+}
diff --git a/gthumb/gth-metadata-provider.h b/gthumb/gth-metadata-provider.h
new file mode 100644
index 0000000..e1d20d6
--- /dev/null
+++ b/gthumb/gth-metadata-provider.h
@@ -0,0 +1,84 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GTH_METADATA_PROVIDER_H
+#define GTH_METADATA_PROVIDER_H
+
+#include "gio-utils.h"
+#include "gth-file-data.h"
+#include "gth-metadata.h"
+#include "typedefs.h"
+
+G_BEGIN_DECLS
+
+#define GTH_TYPE_METADATA_PROVIDER (gth_metadata_provider_get_type ())
+#define GTH_METADATA_PROVIDER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTH_TYPE_METADATA_PROVIDER, GthMetadataProvider))
+#define GTH_METADATA_PROVIDER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTH_TYPE_METADATA_PROVIDER, GthMetadataProviderClass))
+#define GTH_IS_METADATA_PROVIDER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTH_TYPE_METADATA_PROVIDER))
+#define GTH_IS_METADATA_PROVIDER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTH_TYPE_METADATA_PROVIDER))
+#define GTH_METADATA_PROVIDER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTH_TYPE_METADATA_PROVIDER, GthMetadataProviderClass))
+
+typedef struct _GthMetadataProvider GthMetadataProvider;
+typedef struct _GthMetadataProviderClass GthMetadataProviderClass;
+typedef struct _GthMetadataProviderPrivate GthMetadataProviderPrivate;
+
+struct _GthMetadataProvider {
+ GObject parent_instance;
+ GthMetadataProviderPrivate *priv;
+};
+
+struct _GthMetadataProviderClass {
+ GObjectClass parent_class;
+ void (*read) (GthMetadataProvider *self,
+ GthFileData *file_data,
+ const char *attributes);
+ void (*write) (GthMetadataProvider *self,
+ GthFileData *file_data,
+ const char *attributes);
+};
+
+GType gth_metadata_provider_get_type (void);
+GthMetadataProvider * gth_metadata_provider_new (void);
+gboolean gth_metadata_provider_can_read (GthMetadataProvider *self,
+ char **attribute_v);
+gboolean gth_metadata_provider_can_write (GthMetadataProvider *self,
+ char **attribute_v);
+void gth_metadata_provider_read (GthMetadataProvider *self,
+ GthFileData *file_data,
+ const char *attributes);
+void gth_metadata_provider_write (GthMetadataProvider *self,
+ GthFileData *file_data,
+ const char *attributes);
+void _g_query_metadata_async (GList *files, /* GthFileData * list */
+ const char *attributes,
+ GCancellable *cancellable,
+ InfoReadyCallback ready_func,
+ gpointer user_data);
+void _g_write_metadata_async (GList *files, /* GthFileData * list */
+ const char *attributes,
+ GCancellable *cancellable,
+ ReadyFunc ready_func,
+ gpointer user_data);
+
+G_END_DECLS
+
+#endif /* GTH_METADATA_PROVIDER_H */
diff --git a/gthumb/gth-metadata.c b/gthumb/gth-metadata.c
new file mode 100644
index 0000000..e590443
--- /dev/null
+++ b/gthumb/gth-metadata.c
@@ -0,0 +1,233 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include "glib-utils.h"
+#include "gth-metadata.h"
+
+
+enum {
+ GTH_METADATA_DUMMY_PROPERTY,
+ GTH_METADATA_ID,
+ GTH_METADATA_DESCRIPTION,
+ GTH_METADATA_RAW,
+ GTH_METADATA_FORMATTED
+};
+
+struct _GthMetadataPrivate {
+ char *id;
+ char *description;
+ char *raw;
+ char *formatted;
+};
+
+static gpointer gth_metadata_parent_class = NULL;
+
+
+static void
+gth_metadata_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GthMetadata *self;
+
+ self = GTH_METADATA (object);
+ switch (property_id) {
+ case GTH_METADATA_ID:
+ g_value_set_string (value, self->priv->id);
+ break;
+ case GTH_METADATA_DESCRIPTION:
+ g_value_set_string (value, self->priv->description);
+ break;
+ case GTH_METADATA_RAW:
+ g_value_set_string (value, self->priv->raw);
+ break;
+ case GTH_METADATA_FORMATTED:
+ g_value_set_string (value, self->priv->formatted);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+
+static void
+gth_metadata_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GthMetadata *self;
+
+ self = GTH_METADATA (object);
+ switch (property_id) {
+ case GTH_METADATA_ID:
+ _g_strset (&self->priv->id, g_value_get_string (value));
+ break;
+ case GTH_METADATA_DESCRIPTION:
+ _g_strset (&self->priv->description, g_value_get_string (value));
+ break;
+ case GTH_METADATA_RAW:
+ _g_strset (&self->priv->raw, g_value_get_string (value));
+ break;
+ case GTH_METADATA_FORMATTED:
+ _g_strset (&self->priv->formatted, g_value_get_string (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+
+static void
+gth_metadata_finalize (GObject *obj)
+{
+ GthMetadata *self;
+
+ self = GTH_METADATA (obj);
+
+ g_free (self->priv->id);
+ g_free (self->priv->description);
+ g_free (self->priv->raw);
+ g_free (self->priv->formatted);
+
+ G_OBJECT_CLASS (gth_metadata_parent_class)->finalize (obj);
+}
+
+
+static void
+gth_metadata_class_init (GthMetadataClass *klass)
+{
+ gth_metadata_parent_class = g_type_class_peek_parent (klass);
+ g_type_class_add_private (klass, sizeof (GthMetadataPrivate));
+
+ G_OBJECT_CLASS (klass)->get_property = gth_metadata_get_property;
+ G_OBJECT_CLASS (klass)->set_property = gth_metadata_set_property;
+ G_OBJECT_CLASS (klass)->finalize = gth_metadata_finalize;
+
+ g_object_class_install_property (G_OBJECT_CLASS (klass),
+ GTH_METADATA_ID,
+ g_param_spec_string ("id",
+ "ID",
+ "Metadata unique identifier",
+ NULL,
+ G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB | G_PARAM_READABLE | G_PARAM_WRITABLE));
+ g_object_class_install_property (G_OBJECT_CLASS (klass),
+ GTH_METADATA_DESCRIPTION,
+ g_param_spec_string ("description",
+ "Description",
+ "Metadata description",
+ NULL,
+ G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB | G_PARAM_READABLE | G_PARAM_WRITABLE));
+ g_object_class_install_property (G_OBJECT_CLASS (klass),
+ GTH_METADATA_RAW,
+ g_param_spec_string ("raw",
+ "Raw value",
+ "Metadata raw value",
+ NULL,
+ G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB | G_PARAM_READABLE | G_PARAM_WRITABLE));
+ g_object_class_install_property (G_OBJECT_CLASS (klass),
+ GTH_METADATA_FORMATTED,
+ g_param_spec_string ("formatted",
+ "Formatted value",
+ "Metadata formatted value",
+ NULL,
+ G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB | G_PARAM_READABLE | G_PARAM_WRITABLE));
+}
+
+
+static void
+gth_metadata_instance_init (GthMetadata *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GTH_TYPE_METADATA, GthMetadataPrivate);
+ self->priv->id = NULL;
+ self->priv->description = NULL;
+ self->priv->raw = NULL;
+ self->priv->formatted = NULL;
+}
+
+
+GType
+gth_metadata_get_type (void)
+{
+ static GType gth_metadata_type_id = 0;
+
+ if (gth_metadata_type_id == 0) {
+ static const GTypeInfo g_define_type_info = {
+ sizeof (GthMetadataClass),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) gth_metadata_class_init,
+ (GClassFinalizeFunc) NULL,
+ NULL,
+ sizeof (GthMetadata),
+ 0,
+ (GInstanceInitFunc) gth_metadata_instance_init,
+ NULL
+ };
+ gth_metadata_type_id = g_type_register_static (G_TYPE_OBJECT, "GthMetadata", &g_define_type_info, 0);
+ }
+ return gth_metadata_type_id;
+}
+
+
+GthMetadata *
+gth_metadata_new (void)
+{
+ return g_object_new (GTH_TYPE_METADATA, NULL);
+}
+
+
+const char *
+gth_metadata_get_raw (GthMetadata *metadata)
+{
+ return metadata->priv->raw;
+}
+
+
+const char *
+gth_metadata_get_formatted (GthMetadata *metadata)
+{
+ return metadata->priv->formatted;
+}
+
+
+GthMetadataInfo *
+gth_metadata_info_dup (GthMetadataInfo *info)
+{
+ GthMetadataInfo *new_info;
+
+ new_info = g_new0 (GthMetadataInfo, 1);
+ if (info->id != NULL)
+ new_info->id = g_strdup (info->id);
+ if (info->display_name != NULL)
+ new_info->display_name = g_strdup (info->display_name);
+ if (info->category != NULL)
+ new_info->category = g_strdup (info->category);
+ new_info->sort_order = info->sort_order;
+ new_info->flags = info->flags;
+
+ return new_info;
+}
diff --git a/gthumb/gth-metadata.h b/gthumb/gth-metadata.h
new file mode 100644
index 0000000..40454ca
--- /dev/null
+++ b/gthumb/gth-metadata.h
@@ -0,0 +1,81 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GTH_METADATA_H
+#define GTH_METADATA_H
+
+#include <glib.h>
+#include <glib-object.h>
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+typedef struct {
+ const char *id;
+ const char *display_name;
+ int sort_order;
+} GthMetadataCategory;
+
+typedef enum {
+ GTH_METADATA_ALLOW_NOWHERE = 0,
+ GTH_METADATA_ALLOW_IN_FILE_LIST = 1 << 0,
+ GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW = 1 << 1,
+ GTH_METADATA_ALLOW_EVERYWHERE = (GTH_METADATA_ALLOW_IN_FILE_LIST | GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW)
+} GthMetadataFlags;
+
+typedef struct {
+ const char *id;
+ const char *display_name;
+ const char *category;
+ int sort_order;
+ GthMetadataFlags flags;
+} GthMetadataInfo;
+
+#define GTH_TYPE_METADATA (gth_metadata_get_type ())
+#define GTH_METADATA(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTH_TYPE_METADATA, GthMetadata))
+#define GTH_METADATA_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTH_TYPE_METADATA, GthMetadataClass))
+#define GTH_IS_METADATA(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTH_TYPE_METADATA))
+#define GTH_IS_METADATA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTH_TYPE_METADATA))
+#define GTH_METADATA_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTH_TYPE_METADATA, GthMetadataClass))
+
+typedef struct _GthMetadata GthMetadata;
+typedef struct _GthMetadataClass GthMetadataClass;
+typedef struct _GthMetadataPrivate GthMetadataPrivate;
+
+struct _GthMetadata {
+ GObject parent_instance;
+ GthMetadataPrivate *priv;
+};
+
+struct _GthMetadataClass {
+ GObjectClass parent_class;
+};
+
+GType gth_metadata_get_type (void);
+GthMetadata * gth_metadata_new (void);
+const char * gth_metadata_get_raw (GthMetadata *metadata);
+const char * gth_metadata_get_formatted (GthMetadata *metadata);
+GthMetadataInfo * gth_metadata_info_dup (GthMetadataInfo *info);
+
+G_END_DECLS
+
+#endif /* GTH_METADATA_H */
diff --git a/gthumb/gth-monitor.c b/gthumb/gth-monitor.c
new file mode 100644
index 0000000..471760d
--- /dev/null
+++ b/gthumb/gth-monitor.c
@@ -0,0 +1,293 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2005-2008 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <string.h>
+#include "glib-utils.h"
+#include "gth-enum-types.h"
+#include "gth-marshal.h"
+#include "gth-monitor.h"
+
+
+#define UPDATE_DIR_DELAY 500
+
+
+enum {
+ ICON_THEME_CHANGED,
+ BOOKMARKS_CHANGED,
+ FILTERS_CHANGED,
+ FOLDER_CONTENT_CHANGED,
+ FILE_RENAMED,
+ METADATA_CHANGED,
+ ENTRY_POINTS_CHANGED,
+ LAST_SIGNAL
+};
+
+
+struct _GthMonitorPrivateData {
+ gboolean active;
+};
+
+
+static GObjectClass *parent_class = NULL;
+static guint monitor_signals[LAST_SIGNAL] = { 0 };
+
+
+static void
+gth_monitor_finalize (GObject *object)
+{
+ GthMonitor *monitor = GTH_MONITOR (object);
+
+ if (monitor->priv != NULL) {
+ g_free (monitor->priv);
+ monitor->priv = NULL;
+ }
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+
+static void
+gth_monitor_init (GthMonitor *monitor)
+{
+ monitor->priv = g_new0 (GthMonitorPrivateData, 1);
+}
+
+
+static void
+gth_monitor_class_init (GthMonitorClass *class)
+{
+ GObjectClass *gobject_class;
+
+ parent_class = g_type_class_peek_parent (class);
+
+ gobject_class = (GObjectClass*) class;
+ gobject_class->finalize = gth_monitor_finalize;
+
+ monitor_signals[ICON_THEME_CHANGED] =
+ g_signal_new ("icon-theme-changed",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GthMonitorClass, icon_theme_changed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+ monitor_signals[BOOKMARKS_CHANGED] =
+ g_signal_new ("bookmarks-changed",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GthMonitorClass, bookmarks_changed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+ monitor_signals[FILTERS_CHANGED] =
+ g_signal_new ("filters-changed",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GthMonitorClass, filters_changed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+ monitor_signals[FOLDER_CONTENT_CHANGED] =
+ g_signal_new ("folder-changed",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GthMonitorClass, folder_changed),
+ NULL, NULL,
+ gth_marshal_VOID__OBJECT_BOXED_ENUM,
+ G_TYPE_NONE,
+ 3,
+ G_TYPE_OBJECT,
+ G_TYPE_OBJECT_LIST,
+ GTH_TYPE_MONITOR_EVENT);
+ monitor_signals[FILE_RENAMED] =
+ g_signal_new ("file-renamed",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GthMonitorClass, file_renamed),
+ NULL, NULL,
+ gth_marshal_VOID__OBJECT_OBJECT,
+ G_TYPE_NONE,
+ 2,
+ G_TYPE_OBJECT,
+ G_TYPE_OBJECT);
+ monitor_signals[METADATA_CHANGED] =
+ g_signal_new ("metadata-changed",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GthMonitorClass, metadata_changed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE,
+ 1,
+ G_TYPE_OBJECT);
+ monitor_signals[ENTRY_POINTS_CHANGED] =
+ g_signal_new ("entry-points-changed",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GthMonitorClass, entry_points_changed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+}
+
+
+GType
+gth_monitor_get_type (void)
+{
+ static GType type = 0;
+
+ if (! type) {
+ GTypeInfo type_info = {
+ sizeof (GthMonitorClass),
+ NULL,
+ NULL,
+ (GClassInitFunc) gth_monitor_class_init,
+ NULL,
+ NULL,
+ sizeof (GthMonitor),
+ 0,
+ (GInstanceInitFunc) gth_monitor_init
+ };
+
+ type = g_type_register_static (G_TYPE_OBJECT,
+ "GthMonitor",
+ &type_info,
+ 0);
+ }
+
+ return type;
+}
+
+
+GthMonitor *
+gth_monitor_new (void)
+{
+ return (GthMonitor*) g_object_new (GTH_TYPE_MONITOR, NULL);
+}
+
+
+void
+gth_monitor_pause (GthMonitor *monitor)
+{
+ monitor->priv->active = FALSE;
+}
+
+
+void
+gth_monitor_resume (GthMonitor *monitor)
+{
+ monitor->priv->active = TRUE;
+}
+
+
+void
+gth_monitor_icon_theme_changed (GthMonitor *monitor)
+{
+ g_return_if_fail (GTH_IS_MONITOR (monitor));
+
+ g_signal_emit (G_OBJECT (monitor),
+ monitor_signals[ICON_THEME_CHANGED],
+ 0);
+}
+
+
+void
+gth_monitor_bookmarks_changed (GthMonitor *monitor)
+{
+ g_return_if_fail (GTH_IS_MONITOR (monitor));
+
+ g_signal_emit (G_OBJECT (monitor),
+ monitor_signals[BOOKMARKS_CHANGED],
+ 0);
+}
+
+
+void
+gth_monitor_filters_changed (GthMonitor *monitor)
+{
+ g_return_if_fail (GTH_IS_MONITOR (monitor));
+
+ g_signal_emit (G_OBJECT (monitor),
+ monitor_signals[FILTERS_CHANGED],
+ 0);
+}
+
+
+void
+gth_monitor_folder_changed (GthMonitor *monitor,
+ GFile *parent,
+ GList *list,
+ GthMonitorEvent event)
+{
+ g_return_if_fail (GTH_IS_MONITOR (monitor));
+
+ g_signal_emit (G_OBJECT (monitor),
+ monitor_signals[FOLDER_CONTENT_CHANGED],
+ 0,
+ parent,
+ list,
+ event);
+}
+
+
+void
+gth_monitor_file_renamed (GthMonitor *monitor,
+ GFile *file,
+ GFile *new_file)
+{
+ g_return_if_fail (GTH_IS_MONITOR (monitor));
+
+ g_signal_emit (G_OBJECT (monitor),
+ monitor_signals[FILE_RENAMED],
+ 0,
+ file,
+ new_file);
+}
+
+
+void
+gth_monitor_metadata_changed (GthMonitor *monitor,
+ GthFileData *file_data)
+{
+ g_return_if_fail (GTH_IS_MONITOR (monitor));
+
+ g_signal_emit (G_OBJECT (monitor),
+ monitor_signals[METADATA_CHANGED],
+ 0,
+ file_data);
+}
+
+
+void
+gth_monitor_file_entry_points_changed (GthMonitor *monitor)
+{
+ g_return_if_fail (GTH_IS_MONITOR (monitor));
+
+ g_signal_emit (G_OBJECT (monitor),
+ monitor_signals[ENTRY_POINTS_CHANGED],
+ 0);
+}
diff --git a/gthumb/gth-monitor.h b/gthumb/gth-monitor.h
new file mode 100644
index 0000000..8a7e589
--- /dev/null
+++ b/gthumb/gth-monitor.h
@@ -0,0 +1,97 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2005-2008 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GTH_MONITOR_H
+#define GTH_MONITOR_H
+
+#include <gio/gio.h>
+#include <glib-object.h>
+#include "typedefs.h"
+#include "gth-file-data.h"
+
+G_BEGIN_DECLS
+
+typedef enum {
+ GTH_MONITOR_EVENT_CREATED = 0,
+ GTH_MONITOR_EVENT_DELETED,
+ GTH_MONITOR_EVENT_CHANGED
+} GthMonitorEvent;
+
+#define GTH_TYPE_MONITOR (gth_monitor_get_type ())
+#define GTH_MONITOR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTH_TYPE_MONITOR, GthMonitor))
+#define GTH_MONITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTH_TYPE_MONITOR, GthMonitorClass))
+#define GTH_IS_MONITOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTH_TYPE_MONITOR))
+#define GTH_IS_MONITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTH_TYPE_MONITOR))
+#define GTH_MONITOR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GTH_TYPE_MONITOR, GthMonitorClass))
+
+typedef struct _GthMonitor GthMonitor;
+typedef struct _GthMonitorClass GthMonitorClass;
+typedef struct _GthMonitorPrivateData GthMonitorPrivateData;
+
+struct _GthMonitor
+{
+ GObject __parent;
+ GthMonitorPrivateData *priv;
+};
+
+struct _GthMonitorClass
+{
+ GObjectClass __parent_class;
+
+ /*< signals >*/
+
+ void (*icon_theme_changed) (GthMonitor *monitor);
+ void (*bookmarks_changed) (GthMonitor *monitor);
+ void (*filters_changed) (GthMonitor *monitor);
+ void (*folder_changed) (GthMonitor *monitor,
+ GFile *parent,
+ GList *list,
+ GthMonitorEvent event);
+ void (*file_renamed) (GthMonitor *monitor,
+ GFile *file,
+ GFile *new_file);
+ void (*metadata_changed) (GthMonitor *monitor,
+ GthFileData *file_data);
+ void (*entry_points_changed) (GthMonitor *monitor);
+};
+
+GType gth_monitor_get_type (void);
+GthMonitor * gth_monitor_new (void);
+void gth_monitor_pause (GthMonitor *monitor);
+void gth_monitor_resume (GthMonitor *monitor);
+void gth_monitor_icon_theme_changed (GthMonitor *monitor);
+void gth_monitor_bookmarks_changed (GthMonitor *monitor);
+void gth_monitor_filters_changed (GthMonitor *monitor);
+void gth_monitor_folder_changed (GthMonitor *monitor,
+ GFile *parent,
+ GList *list,
+ GthMonitorEvent event);
+void gth_monitor_file_renamed (GthMonitor *monitor,
+ GFile *file,
+ GFile *new_file);
+void gth_monitor_metadata_changed (GthMonitor *monitor,
+ GthFileData *file_data);
+void gth_monitor_file_entry_points_changed (GthMonitor *monitor);
+
+G_END_DECLS
+
+#endif /* GTH_MONITOR_H */
diff --git a/gthumb/gth-multipage.c b/gthumb/gth-multipage.c
new file mode 100644
index 0000000..7510db2
--- /dev/null
+++ b/gthumb/gth-multipage.c
@@ -0,0 +1,231 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <gtk/gtk.h>
+#include "gth-main.h"
+#include "gth-multipage.h"
+
+
+#define GTH_MULTIPAGE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GTH_TYPE_MULTIPAGE, GthMultipagePrivate))
+
+
+enum {
+ ICON_COLUMN,
+ NAME_COLUMN,
+ N_COLUMNS
+};
+
+
+static gpointer parent_class = NULL;
+
+
+struct _GthMultipagePrivate {
+ GtkListStore *model;
+ GtkWidget *combobox;
+ GtkWidget *notebook;
+};
+
+
+static void
+gth_multipage_class_init (GthMultipageClass *klass)
+{
+ parent_class = g_type_class_peek_parent (klass);
+ g_type_class_add_private (klass, sizeof (GthMultipagePrivate));
+}
+
+
+static void
+combobox_changed_cb (GtkComboBox *widget,
+ gpointer user_data)
+{
+ GthMultipage *multipage = user_data;
+
+ gtk_notebook_set_current_page (GTK_NOTEBOOK (multipage->priv->notebook), gtk_combo_box_get_active (GTK_COMBO_BOX (multipage->priv->combobox)));
+}
+
+
+static void
+gth_multipage_init (GthMultipage *multipage)
+{
+ GtkCellRenderer *renderer;
+
+ multipage->priv = GTH_MULTIPAGE_GET_PRIVATE (multipage);
+
+ gtk_box_set_spacing (GTK_BOX (multipage), 6);
+
+ multipage->priv->model = gtk_list_store_new (N_COLUMNS,
+ G_TYPE_STRING,
+ G_TYPE_STRING);
+ multipage->priv->combobox = gtk_combo_box_new_with_model (GTK_TREE_MODEL (multipage->priv->model));
+ gtk_widget_show (multipage->priv->combobox);
+ gtk_box_pack_start (GTK_BOX (multipage), multipage->priv->combobox, FALSE, FALSE, 0);
+ g_object_unref (multipage->priv->model);
+
+ g_signal_connect (multipage->priv->combobox,
+ "changed",
+ G_CALLBACK (combobox_changed_cb),
+ multipage);
+
+ /* icon renderer */
+
+ renderer = gtk_cell_renderer_pixbuf_new ();
+ gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (multipage->priv->combobox),
+ renderer,
+ FALSE);
+ gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (multipage->priv->combobox),
+ renderer,
+ "icon-name", ICON_COLUMN,
+ NULL);
+
+ /* name renderer */
+
+ renderer = gtk_cell_renderer_text_new ();
+ gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (multipage->priv->combobox),
+ renderer,
+ TRUE);
+ gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (multipage->priv->combobox),
+ renderer,
+ "text", NAME_COLUMN,
+ NULL);
+
+ /* notebook */
+
+ multipage->priv->notebook = gtk_notebook_new ();
+ gtk_notebook_set_show_tabs (GTK_NOTEBOOK (multipage->priv->notebook), FALSE);
+ gtk_notebook_set_show_border (GTK_NOTEBOOK (multipage->priv->notebook), FALSE);
+ gtk_widget_show (multipage->priv->notebook);
+ gtk_box_pack_start (GTK_BOX (multipage), multipage->priv->notebook, TRUE, TRUE, 0);
+}
+
+
+GType
+gth_multipage_get_type (void)
+{
+ static GType type = 0;
+
+ if (! type) {
+ GTypeInfo type_info = {
+ sizeof (GthMultipageClass),
+ NULL,
+ NULL,
+ (GClassInitFunc) gth_multipage_class_init,
+ NULL,
+ NULL,
+ sizeof (GthMultipage),
+ 0,
+ (GInstanceInitFunc) gth_multipage_init
+ };
+
+ type = g_type_register_static (GTK_TYPE_VBOX,
+ "GthMultipage",
+ &type_info,
+ 0);
+ }
+
+ return type;
+}
+
+
+GtkWidget *
+gth_multipage_new (void)
+{
+ return (GtkWidget *) g_object_new (GTH_TYPE_MULTIPAGE, NULL);
+}
+
+
+void
+gth_multipage_add_child (GthMultipage *multipage,
+ GthMultipageChild *child)
+{
+ GtkTreeIter iter;
+
+ gtk_widget_show (GTK_WIDGET (child));
+ gtk_notebook_append_page (GTK_NOTEBOOK (multipage->priv->notebook), GTK_WIDGET (child), NULL);
+
+ gtk_list_store_append (GTK_LIST_STORE (multipage->priv->model), &iter);
+ gtk_list_store_set (GTK_LIST_STORE (multipage->priv->model), &iter,
+ NAME_COLUMN, gth_multipage_child_get_name (child),
+ ICON_COLUMN, gth_multipage_child_get_icon (child),
+ -1);
+}
+
+
+GList *
+gth_multipage_get_children (GthMultipage *multipage)
+{
+ return gtk_container_get_children (GTK_CONTAINER (multipage->priv->notebook));
+}
+
+
+void
+gth_multipage_set_current (GthMultipage *multipage,
+ int index_)
+{
+ gtk_combo_box_set_active (GTK_COMBO_BOX (multipage->priv->combobox), index_);
+}
+
+
+int
+gth_multipage_get_current (GthMultipage *multipage)
+{
+ return gtk_combo_box_get_active (GTK_COMBO_BOX (multipage->priv->combobox));
+}
+
+
+/* -- gth_multipage_child -- */
+
+
+GType
+gth_multipage_child_get_type (void) {
+ static GType gth_multipage_child_type_id = 0;
+ if (gth_multipage_child_type_id == 0) {
+ static const GTypeInfo g_define_type_info = {
+ sizeof (GthMultipageChildIface),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) NULL,
+ (GClassFinalizeFunc) NULL,
+ NULL,
+ 0,
+ 0,
+ (GInstanceInitFunc) NULL,
+ NULL
+ };
+ gth_multipage_child_type_id = g_type_register_static (G_TYPE_INTERFACE, "GthMultipageChild", &g_define_type_info, 0);
+ }
+ return gth_multipage_child_type_id;
+}
+
+
+const char *
+gth_multipage_child_get_name (GthMultipageChild *self)
+{
+ return GTH_MULTIPAGE_CHILD_GET_INTERFACE (self)->get_name (self);
+}
+
+
+const char *
+gth_multipage_child_get_icon (GthMultipageChild *self)
+{
+ return GTH_MULTIPAGE_CHILD_GET_INTERFACE (self)->get_icon (self);
+}
diff --git a/gthumb/gth-multipage.h b/gthumb/gth-multipage.h
new file mode 100644
index 0000000..9877cf6
--- /dev/null
+++ b/gthumb/gth-multipage.h
@@ -0,0 +1,81 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GTH_MULTIPAGE_H
+#define GTH_MULTIPAGE_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GTH_TYPE_MULTIPAGE (gth_multipage_get_type ())
+#define GTH_MULTIPAGE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTH_TYPE_MULTIPAGE, GthMultipage))
+#define GTH_MULTIPAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTH_TYPE_MULTIPAGE, GthMultipageClass))
+#define GTH_IS_MULTIPAGE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTH_TYPE_MULTIPAGE))
+#define GTH_IS_MULTIPAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTH_TYPE_MULTIPAGE))
+#define GTH_MULTIPAGE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GTH_TYPE_MULTIPAGE, GthMultipageClass))
+
+#define GTH_TYPE_MULTIPAGE_CHILD (gth_multipage_child_get_type ())
+#define GTH_MULTIPAGE_CHILD(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTH_TYPE_MULTIPAGE_CHILD, GthMultipageChild))
+#define GTH_IS_MULTIPAGE_CHILD(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTH_TYPE_MULTIPAGE_CHILD))
+#define GTH_MULTIPAGE_CHILD_GET_INTERFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), GTH_TYPE_MULTIPAGE_CHILD, GthMultipageChildIface))
+
+typedef struct _GthMultipage GthMultipage;
+typedef struct _GthMultipageClass GthMultipageClass;
+typedef struct _GthMultipagePrivate GthMultipagePrivate;
+
+struct _GthMultipage
+{
+ GtkVBox __parent;
+ GthMultipagePrivate *priv;
+};
+
+struct _GthMultipageClass
+{
+ GtkVBoxClass __parent_class;
+};
+
+typedef struct _GthMultipageChild GthMultipageChild;
+typedef struct _GthMultipageChildIface GthMultipageChildIface;
+
+struct _GthMultipageChildIface {
+ GTypeInterface parent_iface;
+ const char * (*get_name) (GthMultipageChild *self);
+ const char * (*get_icon) (GthMultipageChild *self);
+};
+
+GType gth_multipage_get_type (void);
+GtkWidget * gth_multipage_new (void);
+void gth_multipage_add_child (GthMultipage *multipage,
+ GthMultipageChild *child);
+GList * gth_multipage_get_children (GthMultipage *multipage);
+void gth_multipage_set_current (GthMultipage *multipage,
+ int index_);
+int gth_multipage_get_current (GthMultipage *multipage);
+
+GType gth_multipage_child_get_type (void);
+const char * gth_multipage_child_get_name (GthMultipageChild *self);
+const char * gth_multipage_child_get_icon (GthMultipageChild *self);
+
+G_END_DECLS
+
+#endif /* GTH_MULTIPAGE_H */
diff --git a/gthumb/gth-nav-window.c b/gthumb/gth-nav-window.c
new file mode 100644
index 0000000..5b4aa02
--- /dev/null
+++ b/gthumb/gth-nav-window.c
@@ -0,0 +1,574 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2006-2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <math.h>
+#include <gdk/gdkkeysyms.h>
+#include "gth-nav-window.h"
+#include "gth-image-viewer.h"
+#include "gtk-utils.h"
+#include "icons/nav_button.xpm"
+
+#define GTH_NAV_WINDOW_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GTH_TYPE_NAV_WINDOW, GthNavWindowPrivate))
+
+#define PEN_WIDTH 3 /* Square border width. */
+#define B 4 /* Window border width. */
+#define B2 8 /* Window border width * 2. */
+#define NAV_WIN_MAX_WIDTH 112 /* Max window size. */
+#define NAV_WIN_MAX_HEIGHT 112
+
+
+struct _GthNavWindowPrivate {
+ GthImageViewer *viewer;
+ GtkWidget *viewer_vscr;
+ GtkWidget *viewer_hscr;
+ GtkWidget *viewer_nav_event_box;
+};
+
+
+static GtkHBoxClass *parent_class = NULL;
+
+
+static void
+gth_nav_window_class_init (GthNavWindowClass *class)
+{
+ GObjectClass *object_class;
+
+ parent_class = g_type_class_peek_parent (class);
+ g_type_class_add_private (class, sizeof (GthNavWindowPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+}
+
+
+static void
+gth_nav_window_init (GthNavWindow *nav_window)
+{
+ nav_window->priv = GTH_NAV_WINDOW_GET_PRIVATE (nav_window);
+}
+
+
+GType
+gth_nav_window_get_type ()
+{
+ static GType type = 0;
+
+ if (! type) {
+ GTypeInfo type_info = {
+ sizeof (GthNavWindowClass),
+ NULL,
+ NULL,
+ (GClassInitFunc) gth_nav_window_class_init,
+ NULL,
+ NULL,
+ sizeof (GthNavWindow),
+ 0,
+ (GInstanceInitFunc) gth_nav_window_init
+ };
+
+ type = g_type_register_static (GTK_TYPE_HBOX,
+ "GthNavWindow",
+ &type_info,
+ 0);
+ }
+
+ return type;
+}
+
+
+static gboolean
+size_changed_cb (GtkWidget *widget,
+ GthNavWindow *nav_window)
+{
+ GtkAdjustment *vadj, *hadj;
+ gboolean hide_vscr, hide_hscr;
+
+ gth_image_viewer_get_adjustments (nav_window->priv->viewer, &hadj, &vadj);
+
+ g_return_val_if_fail (hadj != NULL, FALSE);
+ g_return_val_if_fail (vadj != NULL, FALSE);
+
+ hide_vscr = (vadj->upper <= vadj->page_size);
+ hide_hscr = (hadj->upper <= hadj->page_size);
+
+ if (hide_vscr && hide_hscr) {
+ gtk_widget_hide (nav_window->priv->viewer_vscr);
+ gtk_widget_hide (nav_window->priv->viewer_hscr);
+ gtk_widget_hide (nav_window->priv->viewer_nav_event_box);
+ }
+ else {
+ gtk_widget_show (nav_window->priv->viewer_vscr);
+ gtk_widget_show (nav_window->priv->viewer_hscr);
+ gtk_widget_show (nav_window->priv->viewer_nav_event_box);
+ }
+
+ return TRUE;
+}
+
+
+/* -- nav_button_clicked_cb -- */
+
+
+typedef struct {
+ GthImageViewer *viewer;
+ int x_root, y_root;
+ GtkWidget *popup_win;
+ GtkWidget *preview;
+ GdkPixbuf *pixbuf;
+ GdkGC *gc;
+ int image_width, image_height;
+ int window_max_width, window_max_height;
+ int popup_x, popup_y, popup_width, popup_height;
+ int sqr_x, sqr_y, sqr_width, sqr_height;
+ double factor;
+ double sqr_x_d, sqr_y_d;
+} NavWindow;
+
+
+static void
+nav_window_draw_sqr (NavWindow *nav_win,
+ gboolean undraw,
+ int x,
+ int y)
+{
+ if ((nav_win->sqr_x == x) && (nav_win->sqr_y == y) && undraw)
+ return;
+
+ if ((nav_win->sqr_x == 0)
+ && (nav_win->sqr_y == 0)
+ && (nav_win->sqr_width == nav_win->popup_width)
+ && (nav_win->sqr_height == nav_win->popup_height))
+ {
+ return;
+ }
+
+ if (undraw) {
+ gdk_draw_rectangle (nav_win->preview->window,
+ nav_win->gc, FALSE,
+ nav_win->sqr_x + 1,
+ nav_win->sqr_y + 1,
+ nav_win->sqr_width - PEN_WIDTH,
+ nav_win->sqr_height - PEN_WIDTH);
+ }
+
+ gdk_draw_rectangle (nav_win->preview->window,
+ nav_win->gc, FALSE,
+ x + 1,
+ y + 1,
+ nav_win->sqr_width - PEN_WIDTH,
+ nav_win->sqr_height - PEN_WIDTH);
+
+ nav_win->sqr_x = x;
+ nav_win->sqr_y = y;
+}
+
+
+static void
+get_sqr_origin_as_double (NavWindow *nav_win,
+ int mx,
+ int my,
+ double *x,
+ double *y)
+{
+ *x = MIN (mx - B, nav_win->window_max_width);
+ *y = MIN (my - B, nav_win->window_max_height);
+
+ if (*x - nav_win->sqr_width / 2.0 < 0.0)
+ *x = nav_win->sqr_width / 2.0;
+
+ if (*y - nav_win->sqr_height / 2.0 < 0.0)
+ *y = nav_win->sqr_height / 2.0;
+
+ if (*x + nav_win->sqr_width / 2.0 > nav_win->popup_width - 0)
+ *x = nav_win->popup_width - 0 - nav_win->sqr_width / 2.0;
+
+ if (*y + nav_win->sqr_height / 2.0 > nav_win->popup_height - 0)
+ *y = nav_win->popup_height - 0 - nav_win->sqr_height / 2.0;
+
+ *x = *x - nav_win->sqr_width / 2.0;
+ *y = *y - nav_win->sqr_height / 2.0;
+}
+
+
+static void
+update_view (NavWindow *nav_win)
+{
+ GdkPixbuf *image_pixbuf;
+ int popup_x, popup_y;
+ int popup_width, popup_height;
+ int x_offset, y_offset;
+ int w, h;
+ double factor;
+ int gdk_width, gdk_height;
+
+ w = nav_win->image_width * gth_image_viewer_get_zoom (nav_win->viewer);
+ h = nav_win->image_height * gth_image_viewer_get_zoom (nav_win->viewer);
+
+ nav_win->window_max_width = MIN (w, NAV_WIN_MAX_WIDTH);
+ nav_win->window_max_height = MIN (w, NAV_WIN_MAX_HEIGHT);
+
+ factor = MIN ((double) (nav_win->window_max_width) / w,
+ (double) (nav_win->window_max_height) / h);
+ nav_win->factor = factor;
+
+ gdk_width = GTK_WIDGET (nav_win->viewer)->allocation.width - GTH_IMAGE_VIEWER_FRAME_BORDER2;
+ gdk_height = GTK_WIDGET (nav_win->viewer)->allocation.height - GTH_IMAGE_VIEWER_FRAME_BORDER2;
+
+ /* Popup window size. */
+
+ popup_width = MAX ((int) floor (factor * w + 0.5), 1);
+ popup_height = MAX ((int) floor (factor * h + 0.5), 1);
+
+ image_pixbuf = gth_image_viewer_get_current_pixbuf (nav_win->viewer);
+ g_return_if_fail (image_pixbuf != NULL);
+
+ if (nav_win->pixbuf != NULL)
+ g_object_unref (nav_win->pixbuf);
+ nav_win->pixbuf = gdk_pixbuf_scale_simple (image_pixbuf,
+ popup_width,
+ popup_height,
+ GDK_INTERP_BILINEAR);
+
+ /* The square. */
+
+ nav_win->sqr_width = gdk_width * factor;
+ nav_win->sqr_width = MAX (nav_win->sqr_width, B);
+ nav_win->sqr_width = MIN (nav_win->sqr_width, popup_width);
+
+ nav_win->sqr_height = gdk_height * factor;
+ nav_win->sqr_height = MAX (nav_win->sqr_height, B);
+ nav_win->sqr_height = MIN (nav_win->sqr_height, popup_height);
+
+ gth_image_viewer_get_scroll_offset (nav_win->viewer, &x_offset, &y_offset);
+ nav_win->sqr_x = x_offset * factor;
+ nav_win->sqr_y = y_offset * factor;
+
+ /* Popup window position. */
+
+ popup_x = MIN ((int) nav_win->x_root - nav_win->sqr_x
+ - B
+ - nav_win->sqr_width / 2,
+ gdk_screen_width () - popup_width - B2);
+ popup_y = MIN ((int) nav_win->y_root - nav_win->sqr_y
+ - B
+ - nav_win->sqr_height / 2,
+ gdk_screen_height () - popup_height - B2);
+
+ nav_win->popup_x = popup_x;
+ nav_win->popup_y = popup_y;
+ nav_win->popup_width = popup_width;
+ nav_win->popup_height = popup_height;
+}
+
+
+static gboolean
+nav_window_expose (GtkWidget *widget,
+ GdkEventExpose *event,
+ NavWindow *nav_win)
+{
+ if (nav_win->pixbuf == NULL)
+ return FALSE;
+
+ if (! gdk_pixbuf_get_has_alpha (nav_win->pixbuf))
+ gdk_pixbuf_render_to_drawable (
+ nav_win->pixbuf,
+ nav_win->preview->window,
+ nav_win->preview->style->white_gc,
+ 0, 0,
+ 0, 0,
+ nav_win->popup_width,
+ nav_win->popup_height,
+ GDK_RGB_DITHER_MAX,
+ 0, 0);
+ else
+ gdk_pixbuf_render_to_drawable_alpha (
+ nav_win->pixbuf,
+ nav_win->preview->window,
+ 0, 0,
+ 0, 0,
+ nav_win->popup_width,
+ nav_win->popup_height,
+ GDK_PIXBUF_ALPHA_BILEVEL,
+ 112, /* FIXME */
+ GDK_RGB_DITHER_MAX,
+ 0, 0);
+
+ nav_window_draw_sqr (nav_win, FALSE,
+ nav_win->sqr_x,
+ nav_win->sqr_y);
+
+ return TRUE;
+}
+
+
+static int
+nav_window_events (GtkWidget *widget,
+ GdkEvent *event,
+ gpointer data)
+{
+ NavWindow *nav_win = data;
+ GdkModifierType mask;
+ int mx, my;
+ double x, y;
+ GthImageViewer *viewer = nav_win->viewer;
+
+ switch (event->type) {
+ case GDK_BUTTON_RELEASE:
+ /* Release keyboard focus. */
+ gdk_keyboard_ungrab (GDK_CURRENT_TIME);
+ gtk_grab_remove (nav_win->popup_win);
+
+ g_object_unref (nav_win->gc);
+ gtk_widget_destroy (nav_win->popup_win);
+ g_object_unref (nav_win->pixbuf);
+ g_free (nav_win);
+
+ return TRUE;
+
+ case GDK_MOTION_NOTIFY:
+ gdk_window_get_pointer (widget->window, &mx, &my, &mask);
+ get_sqr_origin_as_double (nav_win, mx, my, &x, &y);
+
+ mx = (int) x;
+ my = (int) y;
+ nav_window_draw_sqr (nav_win, TRUE, mx, my);
+
+ mx = (int) (x / nav_win->factor);
+ my = (int) (y / nav_win->factor);
+ gth_image_viewer_scroll_to (viewer, mx, my);
+
+ return TRUE;
+
+ case GDK_KEY_PRESS:
+ switch (event->key.keyval) {
+ case GDK_plus:
+ case GDK_minus:
+ case GDK_1:
+ nav_window_draw_sqr (nav_win, FALSE,
+ nav_win->sqr_x,
+ nav_win->sqr_y);
+ switch (event->key.keyval) {
+ case GDK_plus:
+ gth_image_viewer_zoom_in (viewer);
+ break;
+ case GDK_minus:
+ gth_image_viewer_zoom_out (viewer);
+ break;
+ case GDK_1:
+ gth_image_viewer_set_zoom (viewer, 1.0);
+ break;
+ }
+
+ update_view (nav_win);
+
+ nav_win->sqr_x = MAX (nav_win->sqr_x, 0);
+ nav_win->sqr_x = MIN (nav_win->sqr_x, nav_win->popup_width - nav_win->sqr_width);
+ nav_win->sqr_y = MAX (nav_win->sqr_y, 0);
+ nav_win->sqr_y = MIN (nav_win->sqr_y, nav_win->popup_height - nav_win->sqr_height);
+
+ nav_window_draw_sqr (nav_win, FALSE,
+ nav_win->sqr_x,
+ nav_win->sqr_y);
+ break;
+
+ default:
+ break;
+ }
+ return TRUE;
+
+ default:
+ break;
+ }
+
+ return FALSE;
+}
+
+
+static void
+nav_window_grab_pointer (NavWindow *nav_win)
+{
+ GdkCursor *cursor;
+
+ gtk_grab_add (nav_win->popup_win);
+
+ cursor = gdk_cursor_new (GDK_FLEUR);
+ gdk_pointer_grab (nav_win->popup_win->window,
+ TRUE,
+ (GDK_BUTTON_RELEASE_MASK
+ | GDK_POINTER_MOTION_HINT_MASK
+ | GDK_BUTTON_MOTION_MASK
+ | GDK_EXTENSION_EVENTS_ALL),
+ nav_win->preview->window,
+ cursor,
+ 0);
+ gdk_cursor_unref (cursor);
+
+ /* Capture keyboard events. */
+
+ gdk_keyboard_grab (nav_win->popup_win->window, TRUE, GDK_CURRENT_TIME);
+ gtk_widget_grab_focus (nav_win->popup_win);
+}
+
+
+static NavWindow *
+nav_window_new (GthImageViewer *viewer)
+{
+ NavWindow *nav_window;
+ GtkWidget *out_frame;
+ GtkWidget *in_frame;
+
+ nav_window = g_new0 (NavWindow, 1);
+
+ nav_window->viewer = viewer;
+ nav_window->popup_win = gtk_window_new (GTK_WINDOW_POPUP);
+ gtk_window_set_wmclass (GTK_WINDOW (nav_window->popup_win), "", "gthumb_navigator");
+
+ out_frame = gtk_frame_new (NULL);
+ gtk_frame_set_shadow_type (GTK_FRAME (out_frame), GTK_SHADOW_OUT);
+ gtk_container_add (GTK_CONTAINER (nav_window->popup_win), out_frame);
+
+ in_frame = gtk_frame_new (NULL);
+ gtk_frame_set_shadow_type (GTK_FRAME (in_frame), GTK_SHADOW_IN);
+ gtk_container_add (GTK_CONTAINER (out_frame), in_frame);
+
+ nav_window->preview = gtk_drawing_area_new ();
+ gtk_container_add (GTK_CONTAINER (in_frame), nav_window->preview);
+ g_signal_connect (G_OBJECT (nav_window->preview),
+ "expose_event",
+ G_CALLBACK (nav_window_expose),
+ nav_window);
+
+ /* gc needed to draw the preview square */
+
+ nav_window->gc = gdk_gc_new (GTK_WIDGET (viewer)->window);
+ gdk_gc_set_function (nav_window->gc, GDK_INVERT);
+ gdk_gc_set_line_attributes (nav_window->gc,
+ PEN_WIDTH,
+ GDK_LINE_SOLID,
+ GDK_CAP_BUTT,
+ GDK_JOIN_MITER);
+
+ return nav_window;
+}
+
+
+void
+nav_button_clicked_cb (GtkWidget *widget,
+ GdkEventButton *event,
+ GthImageViewer *viewer)
+{
+ NavWindow *nav_win;
+
+ if (gth_image_viewer_is_void (viewer))
+ return;
+
+ nav_win = nav_window_new (viewer);
+
+ nav_win->x_root = event->x_root;
+ nav_win->y_root = event->y_root;
+
+ nav_win->image_width = gth_image_viewer_get_image_width (viewer);
+ nav_win->image_height = gth_image_viewer_get_image_height (viewer);
+
+ update_view (nav_win);
+
+ g_signal_connect (G_OBJECT (nav_win->popup_win),
+ "event",
+ G_CALLBACK (nav_window_events),
+ nav_win);
+
+ gtk_window_move (GTK_WINDOW (nav_win->popup_win),
+ nav_win->popup_x,
+ nav_win->popup_y);
+
+ gtk_window_set_default_size (GTK_WINDOW (nav_win->popup_win),
+ nav_win->popup_width + B2,
+ nav_win->popup_height + B2);
+
+ gtk_widget_show_all (nav_win->popup_win);
+
+ nav_window_grab_pointer (nav_win);
+}
+
+
+static void
+gth_nav_window_construct (GthNavWindow *nav_window,
+ GthImageViewer *viewer)
+{
+ GtkAdjustment *vadj = NULL, *hadj = NULL;
+ GtkWidget *hbox;
+ GtkWidget *table;
+
+ nav_window->priv->viewer = viewer;
+ g_signal_connect (G_OBJECT (nav_window->priv->viewer),
+ "size_changed",
+ G_CALLBACK (size_changed_cb),
+ nav_window);
+
+ gth_image_viewer_get_adjustments (nav_window->priv->viewer, &hadj, &vadj);
+ nav_window->priv->viewer_hscr = gtk_hscrollbar_new (hadj);
+ nav_window->priv->viewer_vscr = gtk_vscrollbar_new (vadj);
+
+ nav_window->priv->viewer_nav_event_box = gtk_event_box_new ();
+ gtk_container_add (GTK_CONTAINER (nav_window->priv->viewer_nav_event_box), _gtk_image_new_from_xpm_data (nav_button_xpm));
+
+ g_signal_connect (G_OBJECT (nav_window->priv->viewer_nav_event_box),
+ "button_press_event",
+ G_CALLBACK (nav_button_clicked_cb),
+ nav_window->priv->viewer);
+
+ hbox = gtk_hbox_new (FALSE, 0);
+ gtk_container_add (GTK_CONTAINER (hbox), GTK_WIDGET (nav_window->priv->viewer));
+
+ table = gtk_table_new (2, 2, FALSE);
+ gtk_table_attach (GTK_TABLE (table), hbox, 0, 1, 0, 1,
+ (GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
+ (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), 0, 0);
+ gtk_table_attach (GTK_TABLE (table), nav_window->priv->viewer_vscr, 1, 2, 0, 1,
+ (GtkAttachOptions) (GTK_FILL),
+ (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), 0, 0);
+ gtk_table_attach (GTK_TABLE (table), nav_window->priv->viewer_hscr, 0, 1, 1, 2,
+ (GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
+ (GtkAttachOptions) (GTK_FILL), 0, 0);
+ gtk_table_attach (GTK_TABLE (table), nav_window->priv->viewer_nav_event_box, 1, 2, 1, 2,
+ (GtkAttachOptions) (GTK_FILL),
+ (GtkAttachOptions) (GTK_FILL), 0, 0);
+
+ gtk_widget_show_all (hbox);
+ gtk_widget_show (table);
+
+ gtk_container_add (GTK_CONTAINER (nav_window), table);
+}
+
+
+GtkWidget *
+gth_nav_window_new (GthImageViewer *viewer)
+{
+ GthNavWindow *nav_window;
+
+ g_return_val_if_fail (viewer != NULL, NULL);
+
+ nav_window = (GthNavWindow *) g_object_new (GTH_TYPE_NAV_WINDOW, NULL);
+ gth_nav_window_construct (nav_window, viewer);
+
+ return (GtkWidget*) nav_window;
+}
diff --git a/gthumb/gth-nav-window.h b/gthumb/gth-nav-window.h
new file mode 100644
index 0000000..6b39ca9
--- /dev/null
+++ b/gthumb/gth-nav-window.h
@@ -0,0 +1,56 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2006-2009 The Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GTH_NAV_WINDOW_H
+#define GTH_NAV_WINDOW_H
+
+#include <gtk/gtk.h>
+#include "gth-image-viewer.h"
+
+G_BEGIN_DECLS
+
+#define GTH_TYPE_NAV_WINDOW (gth_nav_window_get_type ())
+#define GTH_NAV_WINDOW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTH_TYPE_NAV_WINDOW, GthNavWindow))
+#define GTH_NAV_WINDOW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTH_TYPE_NAV_WINDOW, GthNavWindowClass))
+#define GTH_IS_NAV_WINDOW(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTH_TYPE_NAV_WINDOW))
+#define GTH_IS_NAV_WINDOW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTH_TYPE_NAV_WINDOW))
+#define GTH_NAV_WINDOW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GTH_TYPE_NAV_WINDOW, GthNavWindowClass))
+
+typedef struct _GthNavWindow GthNavWindow;
+typedef struct _GthNavWindowClass GthNavWindowClass;
+typedef struct _GthNavWindowPrivate GthNavWindowPrivate;
+
+struct _GthNavWindow {
+ GtkHBox __parent;
+ GthNavWindowPrivate *priv;
+};
+
+struct _GthNavWindowClass {
+ GtkHBoxClass __parent;
+};
+
+GType gth_nav_window_get_type (void);
+GtkWidget * gth_nav_window_new (GthImageViewer *viewer);
+
+G_END_DECLS
+
+#endif /* GTH_NAV_WINDOW_H */
diff --git a/gthumb/gth-pixbuf-task.c b/gthumb/gth-pixbuf-task.c
new file mode 100644
index 0000000..8c01dea
--- /dev/null
+++ b/gthumb/gth-pixbuf-task.c
@@ -0,0 +1,318 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2001, 2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <glib.h>
+#include "gth-pixbuf-task.h"
+
+
+#define N_STEPS 20 /* number of lines to process in a single
+ * timeout handler. */
+#define PROGRESS_STEP 5 /* notify progress each PROGRESS_STEP lines. */
+
+
+static gpointer parent_class = NULL;
+
+
+static void
+release_pixbufs (GthPixbufTask *pixbuf_task)
+{
+ if (pixbuf_task->src != NULL) {
+ g_object_unref (pixbuf_task->src);
+ pixbuf_task->src = NULL;
+ }
+
+ if (pixbuf_task->dest != NULL) {
+ g_object_unref (pixbuf_task->dest);
+ pixbuf_task->dest = NULL;
+ }
+}
+
+
+static void
+gth_pixbuf_task_finalize (GObject *object)
+{
+ GthPixbufTask *pixbuf_task;
+
+ g_return_if_fail (GTH_IS_PIXBUF_TASK (object));
+ pixbuf_task = GTH_PIXBUF_TASK (object);
+
+ if (pixbuf_task->timeout_id != 0) {
+ g_source_remove (pixbuf_task->timeout_id);
+ pixbuf_task->timeout_id = 0;
+ }
+ release_pixbufs (pixbuf_task);
+ if (pixbuf_task->free_data_func != NULL)
+ (*pixbuf_task->free_data_func) (pixbuf_task);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+
+static gboolean
+one_step (gpointer data)
+{
+ GthPixbufTask *pixbuf_task = data;
+ int dir = 1;
+
+ if (! pixbuf_task->interrupt && pixbuf_task->single_step)
+ (*pixbuf_task->step_func) (pixbuf_task);
+
+ if ((pixbuf_task->line >= pixbuf_task->height)
+ || pixbuf_task->single_step
+ || pixbuf_task->interrupt)
+ {
+ GError *error = NULL;
+
+ if (pixbuf_task->release_func != NULL)
+ (*pixbuf_task->release_func) (pixbuf_task);
+
+ if (pixbuf_task->interrupt)
+ error = g_error_new_literal (GTH_TASK_ERROR, GTH_TASK_ERROR_CANCELLED, NULL);
+ gth_task_completed (GTH_TASK (pixbuf_task), error);
+
+ return FALSE;
+ }
+
+ pixbuf_task->src_pixel = pixbuf_task->src_line;
+ pixbuf_task->src_line += pixbuf_task->rowstride;
+
+ pixbuf_task->dest_pixel = pixbuf_task->dest_line;
+ pixbuf_task->dest_line += pixbuf_task->rowstride;
+
+ if (pixbuf_task->line % PROGRESS_STEP == 0)
+ gth_task_progress (GTH_TASK (pixbuf_task), (float) pixbuf_task->line / pixbuf_task->height);
+
+ if (! pixbuf_task->ltr) { /* right to left */
+ int ofs = (pixbuf_task->width - 1) * pixbuf_task->bytes_per_pixel;
+ pixbuf_task->src_pixel += ofs;
+ pixbuf_task->dest_pixel += ofs;
+ dir = -1;
+ pixbuf_task->column = pixbuf_task->width - 1;
+ }
+ else
+ pixbuf_task->column = 0;
+
+ pixbuf_task->line_step = 0;
+ while (pixbuf_task->line_step < pixbuf_task->width) {
+ (*pixbuf_task->step_func) (pixbuf_task);
+ pixbuf_task->src_pixel += dir * pixbuf_task->bytes_per_pixel;
+ pixbuf_task->dest_pixel += dir * pixbuf_task->bytes_per_pixel;
+ pixbuf_task->column += dir;
+ pixbuf_task->line_step++;
+ }
+
+ pixbuf_task->line++;
+
+ return TRUE;
+}
+
+
+static gboolean
+step (gpointer data)
+{
+ GthPixbufTask *pixbuf_task = data;
+ int i;
+
+ if (pixbuf_task->timeout_id != 0) {
+ g_source_remove (pixbuf_task->timeout_id);
+ pixbuf_task->timeout_id = 0;
+ }
+
+ for (i = 0; i < N_STEPS; i++)
+ if (! one_step (data))
+ return FALSE;
+
+ pixbuf_task->timeout_id = g_idle_add (step, pixbuf_task);
+
+ return FALSE;
+}
+
+
+static void
+gth_pixbuf_task_exec (GthTask *task)
+{
+ GthPixbufTask *pixbuf_task;
+
+ g_return_if_fail (GTH_IS_PIXBUF_TASK (task));
+
+ pixbuf_task = GTH_PIXBUF_TASK (task);
+
+ g_return_if_fail (pixbuf_task->src != NULL);
+
+ pixbuf_task->line = 0;
+ if (pixbuf_task->init_func != NULL)
+ (*pixbuf_task->init_func) (pixbuf_task);
+
+ step (pixbuf_task);
+}
+
+
+static void
+gth_pixbuf_task_cancel (GthTask *task)
+{
+ GthPixbufTask *pixbuf_task;
+
+ g_return_if_fail (GTH_IS_PIXBUF_TASK (task));
+
+ pixbuf_task = GTH_PIXBUF_TASK (task);
+ pixbuf_task->interrupt = TRUE;
+}
+
+
+static void
+gth_pixbuf_task_class_init (GthPixbufTaskClass *class)
+{
+ GObjectClass *object_class;
+ GthTaskClass *task_class;
+
+ parent_class = g_type_class_peek_parent (class);
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->finalize = gth_pixbuf_task_finalize;
+
+ task_class = GTH_TASK_CLASS (class);
+ task_class->exec = gth_pixbuf_task_exec;
+ task_class->cancel = gth_pixbuf_task_cancel;
+}
+
+
+static void
+gth_pixbuf_task_init (GthPixbufTask *pixbuf_task)
+{
+ pixbuf_task->src = NULL;
+ pixbuf_task->dest = NULL;
+ pixbuf_task->data = NULL;
+
+ pixbuf_task->init_func = NULL;
+ pixbuf_task->step_func = NULL;
+ pixbuf_task->release_func = NULL;
+ pixbuf_task->free_data_func = NULL;
+
+ pixbuf_task->src_line = NULL;
+ pixbuf_task->src_pixel = NULL;
+ pixbuf_task->dest_line = NULL;
+ pixbuf_task->dest_pixel = NULL;
+
+ pixbuf_task->ltr = TRUE;
+
+ pixbuf_task->timeout_id = 0;
+ pixbuf_task->line = 0;
+ pixbuf_task->interrupt = FALSE;
+}
+
+
+GType
+gth_pixbuf_task_get_type (void)
+{
+ static GType type = 0;
+
+ if (! type) {
+ GTypeInfo type_info = {
+ sizeof (GthPixbufTaskClass),
+ NULL,
+ NULL,
+ (GClassInitFunc) gth_pixbuf_task_class_init,
+ NULL,
+ NULL,
+ sizeof (GthPixbufTask),
+ 0,
+ (GInstanceInitFunc) gth_pixbuf_task_init
+ };
+
+ type = g_type_register_static (GTH_TYPE_TASK,
+ "GthPixbufTask",
+ &type_info,
+ 0);
+ }
+
+ return type;
+}
+
+
+GthTask *
+gth_pixbuf_task_new (GdkPixbuf *src,
+ GdkPixbuf *dest,
+ PixbufOpFunc init_func,
+ PixbufOpFunc step_func,
+ PixbufOpFunc release_func,
+ gpointer data)
+{
+ GthPixbufTask *pixbuf_task;
+
+ pixbuf_task = GTH_PIXBUF_TASK (g_object_new (GTH_TYPE_PIXBUF_TASK, NULL));
+
+ pixbuf_task->init_func = init_func;
+ pixbuf_task->step_func = step_func;
+ pixbuf_task->release_func = release_func;
+ pixbuf_task->data = data;
+
+ gth_pixbuf_task_set_pixbufs (pixbuf_task, src, dest);
+
+ return (GthTask *) pixbuf_task;
+}
+
+
+void
+gth_pixbuf_task_set_single_step (GthPixbufTask *pixbuf_task,
+ gboolean single_step)
+{
+ pixbuf_task->single_step = single_step;
+}
+
+
+void
+gth_pixbuf_task_set_pixbufs (GthPixbufTask *pixbuf_task,
+ GdkPixbuf *src,
+ GdkPixbuf *dest)
+{
+ if (src == NULL)
+ return;
+
+ /* NOTE that src and dest MAY be the same pixbuf! */
+
+ g_return_if_fail (GDK_IS_PIXBUF (src));
+ if (dest != NULL) {
+ g_return_if_fail (GDK_IS_PIXBUF (dest));
+ g_return_if_fail (gdk_pixbuf_get_has_alpha (src) == gdk_pixbuf_get_has_alpha (dest));
+ g_return_if_fail (gdk_pixbuf_get_width (src) == gdk_pixbuf_get_width (dest));
+ g_return_if_fail (gdk_pixbuf_get_height (src) == gdk_pixbuf_get_height (dest));
+ g_return_if_fail (gdk_pixbuf_get_colorspace (src) == gdk_pixbuf_get_colorspace (dest));
+ }
+
+ release_pixbufs (pixbuf_task);
+
+ g_object_ref (src);
+ pixbuf_task->src = src;
+
+ pixbuf_task->has_alpha = gdk_pixbuf_get_has_alpha (src);
+ pixbuf_task->bytes_per_pixel = pixbuf_task->has_alpha ? 4 : 3;
+ pixbuf_task->width = gdk_pixbuf_get_width (src);
+ pixbuf_task->height = gdk_pixbuf_get_height (src);
+ pixbuf_task->rowstride = gdk_pixbuf_get_rowstride (src);
+ pixbuf_task->src_line = gdk_pixbuf_get_pixels (src);
+
+ if (dest != NULL) {
+ g_object_ref (dest);
+ pixbuf_task->dest = dest;
+ pixbuf_task->dest_line = gdk_pixbuf_get_pixels (dest);
+ }
+}
diff --git a/gthumb/gth-pixbuf-task.h b/gthumb/gth-pixbuf-task.h
new file mode 100644
index 0000000..532cb82
--- /dev/null
+++ b/gthumb/gth-pixbuf-task.h
@@ -0,0 +1,99 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2001-2009 The Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef _GTH_PIXBUF_TASK_H
+#define _GTH_PIXBUF_TASK_H
+
+#include <glib.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include "gth-task.h"
+
+G_BEGIN_DECLS
+
+#define GTH_TYPE_PIXBUF_TASK (gth_pixbuf_task_get_type ())
+#define GTH_PIXBUF_TASK(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTH_TYPE_PIXBUF_TASK, GthPixbufTask))
+#define GTH_PIXBUF_TASK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTH_TYPE_PIXBUF_TASK, GthPixbufTaskClass))
+#define GTH_IS_PIXBUF_TASK(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTH_TYPE_PIXBUF_TASK))
+#define GTH_IS_PIXBUF_TASK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTH_TYPE_PIXBUF_TASK))
+#define GTH_PIXBUF_TASK_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GTH_TYPE_PIXBUF_TASK, GthPixbufTaskClass))
+
+typedef struct _GthPixbufTask GthPixbufTask;
+typedef struct _GthPixbufTaskClass GthPixbufTaskClass;
+
+typedef void (*PixbufOpFunc) (GthPixbufTask *pixbuf_task);
+
+enum {
+ RED_PIX = 0,
+ GREEN_PIX = 1,
+ BLUE_PIX = 2,
+ ALPHA_PIX = 3
+};
+
+struct _GthPixbufTask {
+ GthTask __parent;
+
+ GdkPixbuf *src;
+ GdkPixbuf *dest;
+ gpointer data;
+
+ PixbufOpFunc init_func;
+ PixbufOpFunc step_func;
+ PixbufOpFunc release_func;
+ PixbufOpFunc free_data_func;
+
+ gboolean single_step;
+
+ gboolean has_alpha;
+ int bytes_per_pixel;
+ int width, height;
+ int rowstride;
+ guchar *src_line, *src_pixel;
+ guchar *dest_line, *dest_pixel;
+
+ gboolean ltr, first_step, last_step;
+ guint timeout_id;
+ int line;
+ int line_step;
+ int column;
+ gboolean interrupt;
+};
+
+struct _GthPixbufTaskClass {
+ GthTaskClass __parent;
+};
+
+GType gth_pixbuf_task_get_type (void);
+GthTask * gth_pixbuf_task_new (GdkPixbuf *src,
+ GdkPixbuf *dest,
+ PixbufOpFunc init_func,
+ PixbufOpFunc step_func,
+ PixbufOpFunc release_func,
+ gpointer data);
+void gth_pixbuf_task_set_single_step (GthPixbufTask *pixbuf_task,
+ gboolean single_step);
+void gth_pixbuf_task_set_pixbufs (GthPixbufTask *pixbuf_task,
+ GdkPixbuf *src,
+ GdkPixbuf *dest);
+
+G_END_DECLS
+
+#endif /* _GTH_PIXBUF_TASK_H */
diff --git a/gthumb/gth-preferences.c b/gthumb/gth-preferences.c
new file mode 100644
index 0000000..1ca9a26
--- /dev/null
+++ b/gthumb/gth-preferences.c
@@ -0,0 +1,210 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2003-2008 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <string.h>
+#include <math.h>
+#include <gio/gio.h>
+#include "gconf-utils.h"
+#include "glib-utils.h"
+#include "gth-enum-types.h"
+#include "gth-preferences.h"
+
+#define DIALOG_KEY_PREFIX "/apps/gthumb/dialogs/"
+
+
+typedef struct {
+ char *wallpaper_filename;
+ char *wallpaper_options;
+ char *startup_location;
+} GthPreferences;
+
+
+static GthPreferences *Preferences;
+
+
+void
+gth_pref_initialize (void)
+{
+ Preferences = g_new0 (GthPreferences, 1);
+
+ Preferences->wallpaper_filename = eel_gconf_get_string ("/desktop/gnome/background/picture_filename", NULL);
+ Preferences->wallpaper_options = eel_gconf_get_string ("/desktop/gnome/background/picture_options", NULL);
+
+ Preferences->startup_location = NULL;
+ if (eel_gconf_get_boolean (PREF_USE_STARTUP_LOCATION, FALSE) ||
+ eel_gconf_get_boolean (PREF_GO_TO_LAST_LOCATION, FALSE))
+ {
+ gth_pref_set_startup_location (eel_gconf_get_path (PREF_STARTUP_LOCATION, NULL));
+ }
+ else {
+ char *current_dir;
+ char *current_uri;
+
+ current_dir = g_get_current_dir ();
+ current_uri = g_filename_to_uri (current_dir, NULL, NULL);
+
+ gth_pref_set_startup_location (current_uri);
+
+ g_free (current_uri);
+ g_free (current_dir);
+ }
+
+ eel_gconf_monitor_add ("/apps/gthumb");
+}
+
+
+void
+gth_pref_release (void)
+{
+ g_free (Preferences->wallpaper_filename);
+ g_free (Preferences->wallpaper_options);
+ g_free (Preferences->startup_location);
+ g_free (Preferences);
+}
+
+
+void
+gth_pref_set_startup_location (const char *location)
+{
+ g_free (Preferences->startup_location);
+ Preferences->startup_location = NULL;
+ if (location != NULL)
+ Preferences->startup_location = g_strdup (location);
+}
+
+
+const char *
+gth_pref_get_startup_location (void)
+{
+ if (Preferences->startup_location != NULL)
+ return Preferences->startup_location;
+ else
+ return get_home_uri ();
+}
+
+
+const char *
+gth_pref_get_wallpaper_filename (void)
+{
+ return Preferences->wallpaper_filename;
+}
+
+
+const char *
+gth_pref_get_wallpaper_options (void)
+{
+ return Preferences->wallpaper_options;
+}
+
+
+static void
+_gth_pref_dialog_property_set_int (const char *dialog_name,
+ const char *property,
+ int value)
+{
+ char *key;
+
+ key = g_strconcat (DIALOG_KEY_PREFIX, dialog_name, "/", property, NULL);
+ eel_gconf_set_integer (key, value);
+ g_free (key);
+}
+
+
+GthToolbarStyle
+gth_pref_get_real_toolbar_style (void)
+{
+ GthToolbarStyle toolbar_style;
+
+ toolbar_style = _g_enum_type_get_value_by_nick (GTH_TYPE_TOOLBAR_STYLE, eel_gconf_get_string (PREF_UI_TOOLBAR_STYLE, "system"))->value;
+ if (toolbar_style == GTH_TOOLBAR_STYLE_SYSTEM) {
+ char *system_style;
+
+ system_style = eel_gconf_get_string ("/desktop/gnome/interface/toolbar_style", "system");
+
+ if (system_style == NULL)
+ toolbar_style = GTH_TOOLBAR_STYLE_TEXT_BELOW;
+ else if (strcmp (system_style, "both") == 0)
+ toolbar_style = GTH_TOOLBAR_STYLE_TEXT_BELOW;
+ else if (strcmp (system_style, "both-horiz") == 0)
+ toolbar_style = GTH_TOOLBAR_STYLE_TEXT_BESIDE;
+ else if (strcmp (system_style, "icons") == 0)
+ toolbar_style = GTH_TOOLBAR_STYLE_ICONS;
+ else if (strcmp (system_style, "text") == 0)
+ toolbar_style = GTH_TOOLBAR_STYLE_TEXT;
+ else
+ toolbar_style = GTH_TOOLBAR_STYLE_TEXT_BELOW;
+
+ g_free (system_style);
+ }
+
+ return toolbar_style;
+}
+
+
+void
+gth_pref_save_window_geometry (GtkWindow *window,
+ const char *dialog_name)
+{
+ int x, y, width, height;
+
+ gtk_window_get_position (window, &x, &y);
+ _gth_pref_dialog_property_set_int (dialog_name, "x", x);
+ _gth_pref_dialog_property_set_int (dialog_name, "y", y);
+
+ gtk_window_get_size (window, &width, &height);
+ _gth_pref_dialog_property_set_int (dialog_name, "width", width);
+ _gth_pref_dialog_property_set_int (dialog_name, "height", height);
+}
+
+
+static int
+_gth_pref_dialog_property_get_int (const char *dialog_name,
+ const char *property)
+{
+ char *key;
+ int value;
+
+ key = g_strconcat (DIALOG_KEY_PREFIX, dialog_name, "/", property, NULL);
+ value = eel_gconf_get_integer (key, -1);
+ g_free (key);
+
+ return value;
+}
+
+
+void
+gth_pref_restore_window_geometry (GtkWindow *window,
+ const char *dialog_name)
+{
+ int x, y, width, height;
+
+ x = _gth_pref_dialog_property_get_int (dialog_name, "x");
+ y = _gth_pref_dialog_property_get_int (dialog_name, "y");
+ width = _gth_pref_dialog_property_get_int (dialog_name, "width");
+ height = _gth_pref_dialog_property_get_int (dialog_name, "height");
+
+ if ((width != -1) && (height != 1))
+ gtk_window_set_default_size (window, width, height);
+ if ((x != -1) && (y != 1))
+ gtk_window_move (window, x, y);
+ gtk_window_present (window);
+}
diff --git a/gthumb/gth-preferences.h b/gthumb/gth-preferences.h
new file mode 100644
index 0000000..74dc1d3
--- /dev/null
+++ b/gthumb/gth-preferences.h
@@ -0,0 +1,116 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2001-2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GTH_PREF_H
+#define GTH_PREF_H
+
+#include <glib.h>
+#include <gtk/gtk.h>
+#include "typedefs.h"
+
+G_BEGIN_DECLS
+
+#define PREF_DESKTOP_ICON_THEME "/desktop/gnome/file_views/icon_theme"
+
+#define PREF_GO_TO_LAST_LOCATION "/apps/gthumb/general/go_to_last_location"
+#define PREF_USE_STARTUP_LOCATION "/apps/gthumb/general/use_startup_location"
+#define PREF_STARTUP_LOCATION "/apps/gthumb/general/startup_location"
+#define PREF_MAX_HISTORY_LENGTH "/apps/gthumb/general/max_history_length"
+#define PREF_EDITORS "/apps/gthumb/general/editors"
+#define PREF_MIGRATE_DIRECTORIES "/apps/gthumb/general/migrate_directories"
+#define PREF_MIGRATE_COMMENT_SYSTEM "/apps/gthumb/general/migrate_comment_system"
+
+#define PREF_GENERAL_FILTER "/apps/gthumb/browser/general_filter"
+#define PREF_SHOW_HIDDEN_FILES "/apps/gthumb/browser/show_hidden_files"
+#define PREF_SHOW_THUMBNAILS "/apps/gthumb/browser/show_thumbnails"
+#define PREF_FAST_FILE_TYPE "/apps/gthumb/browser/fast_file_type"
+#define PREF_SAVE_THUMBNAILS "/apps/gthumb/browser/save_thumbnails"
+#define PREF_THUMBNAIL_SIZE "/apps/gthumb/browser/thumbnail_size"
+#define PREF_THUMBNAIL_LIMIT "/apps/gthumb/browser/thumbnail_limit"
+#define PREF_CLICK_POLICY "/apps/gthumb/browser/click_policy"
+#define PREF_SORT_TYPE "/apps/gthumb/browser/sort_type"
+#define PREF_SORT_INVERSE "/apps/gthumb/browser/sort_inverse"
+#define PREF_VIEW_AS "/apps/gthumb/browser/view_as"
+
+#define PREF_ZOOM_QUALITY "/apps/gthumb/viewer/zoom_quality"
+#define PREF_ZOOM_CHANGE "/apps/gthumb/viewer/zoom_change"
+#define PREF_TRANSP_TYPE "/apps/gthumb/viewer/transparency_type"
+#define PREF_RESET_SCROLLBARS "/apps/gthumb/viewer/reset_scrollbars"
+#define PREF_CHECK_TYPE "/apps/gthumb/viewer/check_type"
+#define PREF_CHECK_SIZE "/apps/gthumb/viewer/check_size"
+#define PREF_BLACK_BACKGROUND "/apps/gthumb/viewer/black_background"
+
+#define PREF_UI_TOOLBAR_STYLE "/apps/gthumb/ui/toolbar_style"
+#define PREF_UI_WINDOW_WIDTH "/apps/gthumb/ui/window_width"
+#define PREF_UI_WINDOW_HEIGHT "/apps/gthumb/ui/window_height"
+#define PREF_UI_TOOLBAR_VISIBLE "/apps/gthumb/ui/toolbar_visible"
+#define PREF_UI_STATUSBAR_VISIBLE "/apps/gthumb/ui/statusbar_visible"
+#define PREF_UI_FILTERBAR_VISIBLE "/apps/gthumb/ui/filterbar_visible"
+#define PREF_UI_BROWSER_SIDEBAR_WIDTH "/apps/gthumb/ui/browser_sidebar_width"
+#define PREF_UI_VIEWER_SIDEBAR_WIDTH "/apps/gthumb/ui/viewer_sidebar_width"
+#define PREF_UI_PROPERTIES_HEIGHT "/apps/gthumb/ui/properties_height"
+#define PREF_UI_COMMENT_HEIGHT "/apps/gthumb/ui/comment_height"
+
+#define PREF_PNG_COMPRESSION_LEVEL "/apps/gthumb/dialogs/png_saver/compression_level"
+
+#define PREF_JPEG_QUALITY "/apps/gthumb/dialogs/jpeg_saver/quality"
+#define PREF_JPEG_SMOOTHING "/apps/gthumb/dialogs/jpeg_saver/smoothing"
+#define PREF_JPEG_OPTIMIZE "/apps/gthumb/dialogs/jpeg_saver/optimize"
+#define PREF_JPEG_PROGRESSIVE "/apps/gthumb/dialogs/jpeg_saver/progressive"
+
+#define PREF_TGA_RLE_COMPRESSION "/apps/gthumb/dialogs/tga_saver/rle_compression"
+
+#define PREF_TIFF_COMPRESSION "/apps/gthumb/dialogs/tiff_saver/compression"
+#define PREF_TIFF_HORIZONTAL_RES "/apps/gthumb/dialogs/tiff_saver/horizontal_resolution"
+#define PREF_TIFF_VERTICAL_RES "/apps/gthumb/dialogs/tiff_saver/vertical_resolution"
+
+#define PREF_ADD_TO_CATALOG_LAST_CATALOG "/apps/gthumb/dialogs/add_to_catalog/last_catalog"
+#define PREF_ADD_TO_CATALOG_VIEW "/apps/gthumb/dialogs/add_to_catalog/view"
+
+#define PREF_MSG_CANNOT_MOVE_TO_TRASH "/apps/gthumb/dialogs/messages/cannot_move_to_trash"
+#define PREF_MSG_SAVE_MODIFIED_IMAGE "/apps/gthumb/dialogs/messages/save_modified_image"
+
+
+#define DEFAULT_GENERAL_FILTER "file::type::is_media"
+#define DEFAULT_UI_WINDOW_WIDTH 690
+#define DEFAULT_UI_WINDOW_HEIGHT 460
+#define DEFAULT_FAST_FILE_TYPE TRUE
+#define DEFAULT_THUMBNAIL_SIZE 95
+#define DEFAULT_CONFIRM_DELETION TRUE
+#define DEFAULT_MSG_SAVE_MODIFIED_IMAGE TRUE
+
+
+void gth_pref_initialize (void);
+void gth_pref_release (void);
+void gth_pref_set_startup_location (const char *location);
+const char * gth_pref_get_startup_location (void);
+const char * gth_pref_get_wallpaper_filename (void);
+const char * gth_pref_get_wallpaper_options (void);
+GthToolbarStyle gth_pref_get_real_toolbar_style (void);
+void gth_pref_save_window_geometry (GtkWindow *window,
+ const char *dialog);
+void gth_pref_restore_window_geometry (GtkWindow *window,
+ const char *dialog);
+
+G_END_DECLS
+
+#endif /* GTH_PREF_H */
diff --git a/gthumb/gth-sidebar.c b/gthumb/gth-sidebar.c
new file mode 100644
index 0000000..c5e109c
--- /dev/null
+++ b/gthumb/gth-sidebar.c
@@ -0,0 +1,243 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <gtk/gtk.h>
+#include "gth-main.h"
+#include "gth-multipage.h"
+#include "gth-sidebar.h"
+#include "gth-toolbox.h"
+#include "gtk-utils.h"
+
+
+#define GTH_SIDEBAR_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GTH_TYPE_SIDEBAR, GthSidebarPrivate))
+
+
+static gpointer parent_class = NULL;
+
+
+struct _GthSidebarPrivate {
+ GtkWidget *properties;
+ GtkWidget *tools;
+ GtkWidget *options;
+ GtkWidget *options_icon;
+ GtkWidget *options_title;
+};
+
+
+static void
+gth_sidebar_class_init (GthSidebarClass *klass)
+{
+ parent_class = g_type_class_peek_parent (klass);
+ g_type_class_add_private (klass, sizeof (GthSidebarPrivate));
+}
+
+
+static void
+gth_sidebar_init (GthSidebar *sidebar)
+{
+ GtkWidget *options_box;
+ GtkWidget *options_header;
+ GtkWidget *header_align;
+
+ sidebar->priv = GTH_SIDEBAR_GET_PRIVATE (sidebar);
+
+ gtk_notebook_set_show_tabs (GTK_NOTEBOOK (sidebar), FALSE);
+ gtk_notebook_set_show_border (GTK_NOTEBOOK (sidebar), FALSE);
+
+ sidebar->priv->properties = gth_multipage_new ();
+ gtk_widget_show (sidebar->priv->properties);
+ gtk_notebook_append_page (GTK_NOTEBOOK (sidebar), sidebar->priv->properties, NULL);
+
+ sidebar->priv->tools = gth_toolbox_new ("file-tools");
+ gtk_widget_show (sidebar->priv->tools);
+ gtk_notebook_append_page (GTK_NOTEBOOK (sidebar), sidebar->priv->tools, NULL);
+
+ options_box = gtk_vbox_new (FALSE, 0);
+ gtk_widget_show (options_box);
+ gtk_notebook_append_page (GTK_NOTEBOOK (sidebar), options_box, NULL);
+
+ header_align = gtk_alignment_new (0.0, 0.0, 1.0, 1.0);
+ gtk_alignment_set_padding (GTK_ALIGNMENT (header_align), 5, 5, 0, 0);
+
+ options_header = gtk_hbox_new (FALSE, 6);
+ gtk_widget_show (options_header);
+ gtk_container_add (GTK_CONTAINER (header_align), options_header);
+
+ sidebar->priv->options_icon = gtk_image_new ();
+ gtk_widget_show (sidebar->priv->options_icon);
+ gtk_box_pack_start (GTK_BOX (options_header), sidebar->priv->options_icon, FALSE, FALSE, 0);
+
+ sidebar->priv->options_title = gtk_label_new ("");
+ gtk_label_set_use_markup (GTK_LABEL (sidebar->priv->options_title), TRUE);
+ gtk_widget_show (sidebar->priv->options_title);
+ gtk_box_pack_start (GTK_BOX (options_header), sidebar->priv->options_title, FALSE, FALSE, 0);
+
+ gtk_widget_show (header_align);
+ gtk_box_pack_start (GTK_BOX (options_box), header_align, FALSE, FALSE, 0);
+
+ sidebar->priv->options = gtk_scrolled_window_new (NULL, NULL);
+ gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sidebar->priv->options), GTK_SHADOW_NONE);
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sidebar->priv->options), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
+ gtk_widget_show (sidebar->priv->options);
+ gtk_box_pack_start (GTK_BOX (options_box), sidebar->priv->options, TRUE, TRUE, 0);
+}
+
+
+GType
+gth_sidebar_get_type (void)
+{
+ static GType type = 0;
+
+ if (! type) {
+ GTypeInfo type_info = {
+ sizeof (GthSidebarClass),
+ NULL,
+ NULL,
+ (GClassInitFunc) gth_sidebar_class_init,
+ NULL,
+ NULL,
+ sizeof (GthSidebar),
+ 0,
+ (GInstanceInitFunc) gth_sidebar_init
+ };
+
+ type = g_type_register_static (GTK_TYPE_NOTEBOOK,
+ "GthSidebar",
+ &type_info,
+ 0);
+ }
+
+ return type;
+}
+
+
+static void
+_gth_sidebar_add_property_views (GthSidebar *sidebar)
+{
+ GArray *children;
+ int i;
+
+ children = gth_main_get_type_set ("file-properties");
+ for (i = 0; i < children->len; i++) {
+ GType child_type;
+ GtkWidget *child;
+
+ child_type = g_array_index (children, GType, i);
+ child = g_object_new (child_type, NULL);
+ gth_multipage_add_child (GTH_MULTIPAGE (sidebar->priv->properties), GTH_MULTIPAGE_CHILD (child));
+ }
+ gth_multipage_set_current (GTH_MULTIPAGE (sidebar->priv->properties), 0);
+}
+
+
+GtkWidget *
+gth_sidebar_new (void)
+{
+ GthSidebar *sidebar;
+
+ sidebar = g_object_new (GTH_TYPE_SIDEBAR, NULL);
+ _gth_sidebar_add_property_views (sidebar);
+
+ return (GtkWidget *) sidebar;
+}
+
+
+void
+gth_sidebar_set_file (GthSidebar *sidebar,
+ GthFileData *file_data)
+{
+ GList *children;
+ GList *scan;
+
+ children = gth_multipage_get_children (GTH_MULTIPAGE (sidebar->priv->properties));
+ for (scan = children; scan; scan = scan->next) {
+ GtkWidget *child = scan->data;
+
+ if (! GTH_IS_PROPERTY_VIEW (child))
+ continue;
+
+ gth_property_view_set_file (GTH_PROPERTY_VIEW (child), file_data);
+ }
+
+ g_list_free (children);
+}
+
+
+void
+gth_sidebar_set_options (GthSidebar *sidebar,
+ const char *icon,
+ const char *title,
+ GtkWidget *options)
+{
+ _gtk_container_remove_children (GTK_CONTAINER (sidebar->priv->options), NULL, NULL);
+ if (options != NULL) {
+ char *markup;
+
+ markup = g_markup_printf_escaped ("<span size='large' weight='bold'>%s</span>", title);
+ gtk_label_set_markup (GTK_LABEL (sidebar->priv->options_title), markup);
+ gtk_image_set_from_stock (GTK_IMAGE (sidebar->priv->options_icon), icon, GTK_ICON_SIZE_LARGE_TOOLBAR);
+ gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (sidebar->priv->options), options);
+
+ g_free (markup);
+ }
+}
+
+
+void
+gth_sidebar_update_sensitivity (GthSidebar *sidebar)
+{
+ gth_toolbox_update_sensitivity (GTH_TOOLBOX (sidebar->priv->tools));
+}
+
+
+/* -- gth_property_view -- */
+
+
+GType
+gth_property_view_get_type (void) {
+ static GType gth_property_view_type_id = 0;
+ if (gth_property_view_type_id == 0) {
+ static const GTypeInfo g_define_type_info = {
+ sizeof (GthPropertyViewIface),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) NULL,
+ (GClassFinalizeFunc) NULL,
+ NULL,
+ 0,
+ 0,
+ (GInstanceInitFunc) NULL,
+ NULL
+ };
+ gth_property_view_type_id = g_type_register_static (G_TYPE_INTERFACE, "GthPropertyView", &g_define_type_info, 0);
+ }
+ return gth_property_view_type_id;
+}
+
+
+void
+gth_property_view_set_file (GthPropertyView *self,
+ GthFileData *file_data)
+{
+ GTH_PROPERTY_VIEW_GET_INTERFACE (self)->set_file (self, file_data);
+}
diff --git a/gthumb/gth-sidebar.h b/gthumb/gth-sidebar.h
new file mode 100644
index 0000000..70d99e1
--- /dev/null
+++ b/gthumb/gth-sidebar.h
@@ -0,0 +1,88 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GTH_SIDEBAR_H
+#define GTH_SIDEBAR_H
+
+#include <gtk/gtk.h>
+#include "gth-file-data.h"
+
+G_BEGIN_DECLS
+
+#define GTH_TYPE_SIDEBAR (gth_sidebar_get_type ())
+#define GTH_SIDEBAR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTH_TYPE_SIDEBAR, GthSidebar))
+#define GTH_SIDEBAR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTH_TYPE_SIDEBAR, GthSidebarClass))
+#define GTH_IS_SIDEBAR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTH_TYPE_SIDEBAR))
+#define GTH_IS_SIDEBAR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTH_TYPE_SIDEBAR))
+#define GTH_SIDEBAR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GTH_TYPE_SIDEBAR, GthSidebarClass))
+
+#define GTH_TYPE_PROPERTY_VIEW (gth_property_view_get_type ())
+#define GTH_PROPERTY_VIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTH_TYPE_PROPERTY_VIEW, GthPropertyView))
+#define GTH_IS_PROPERTY_VIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTH_TYPE_PROPERTY_VIEW))
+#define GTH_PROPERTY_VIEW_GET_INTERFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), GTH_TYPE_PROPERTY_VIEW, GthPropertyViewIface))
+
+typedef struct _GthSidebar GthSidebar;
+typedef struct _GthSidebarClass GthSidebarClass;
+typedef struct _GthSidebarPrivate GthSidebarPrivate;
+
+typedef enum {
+ GTH_SIDEBAR_PAGE_PROPERTIES,
+ GTH_SIDEBAR_PAGE_TOOLS,
+ GTH_SIDEBAR_PAGE_OPTIONS
+} GthSidebarPage;
+
+struct _GthSidebar
+{
+ GtkNotebook __parent;
+ GthSidebarPrivate *priv;
+};
+
+struct _GthSidebarClass
+{
+ GtkNotebookClass __parent_class;
+};
+
+typedef struct _GthPropertyView GthPropertyView;
+typedef struct _GthPropertyViewIface GthPropertyViewIface;
+
+struct _GthPropertyViewIface {
+ GTypeInterface parent_iface;
+ void (*set_file) (GthPropertyView *self,
+ GthFileData *file_data);
+};
+
+GType gth_sidebar_get_type (void);
+GtkWidget * gth_sidebar_new (void);
+void gth_sidebar_set_file (GthSidebar *sidebar,
+ GthFileData *file_data);
+void gth_sidebar_set_options (GthSidebar *sidebar,
+ const char *icon,
+ const char *title,
+ GtkWidget *options);
+void gth_sidebar_update_sensitivity (GthSidebar *sidebar);
+GType gth_property_view_get_type (void);
+void gth_property_view_set_file (GthPropertyView *self,
+ GthFileData *file_data);
+
+G_END_DECLS
+
+#endif /* GTH_SIDEBAR_H */
diff --git a/gthumb/gth-source-tree.c b/gthumb/gth-source-tree.c
new file mode 100644
index 0000000..7e08259
--- /dev/null
+++ b/gthumb/gth-source-tree.c
@@ -0,0 +1,424 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include "gconf-utils.h"
+#include "glib-utils.h"
+#include "gtk-utils.h"
+#include "gth-icon-cache.h"
+#include "gth-main.h"
+#include "gth-preferences.h"
+#include "gth-source-tree.h"
+
+
+struct _GthSourceTreePrivate {
+ GthFileSource *file_source;
+ gulong monitor_folder_changed_id;
+ gulong monitor_file_renamed_id;
+};
+
+
+static gpointer parent_class = NULL;
+
+
+/* -- monitor_event_data -- */
+
+
+typedef struct {
+ int ref;
+ GFile *parent;
+ GthMonitorEvent event;
+ GthSourceTree *source_tree;
+} MonitorEventData;
+
+
+static MonitorEventData *
+monitor_event_data_new (void)
+{
+ MonitorEventData *monitor_data;
+
+ monitor_data = g_new0 (MonitorEventData, 1);
+ monitor_data->ref = 1;
+
+ return monitor_data;
+}
+
+
+G_GNUC_UNUSED
+static MonitorEventData *
+monitor_event_data_ref (MonitorEventData *monitor_data)
+{
+ monitor_data->ref++;
+ return monitor_data;
+}
+
+
+static void
+monitor_event_data_unref (MonitorEventData *monitor_data)
+{
+ monitor_data->ref--;
+
+ if (monitor_data->ref > 0)
+ return;
+
+ g_object_unref (monitor_data->parent);
+ g_free (monitor_data);
+}
+
+
+/* -- load_data -- */
+
+
+typedef struct {
+ GthSourceTree *source_tree;
+ GFile *folder;
+ GthFileSource *file_source;
+} LoadData;
+
+
+static LoadData *
+load_data_new (GthSourceTree *source_tree,
+ GFile *file)
+{
+ LoadData *load_data;
+
+ load_data = g_new0 (LoadData, 1);
+ load_data->source_tree = source_tree;
+ load_data->file_source = gth_main_get_file_source (file);
+ load_data->folder = g_file_dup (file);
+
+ return load_data;
+}
+
+
+static void
+load_data_free (LoadData *load_data)
+{
+ g_object_unref (load_data->folder);
+ g_object_unref (load_data->file_source);
+ g_free (load_data);
+}
+
+
+static void
+load_data_run (LoadData *load_data,
+ ListReady func)
+{
+ gth_file_source_list (load_data->file_source,
+ load_data->folder,
+ eel_gconf_get_boolean (PREF_FAST_FILE_TYPE, TRUE) ? GTH_FILE_DATA_ATTRIBUTES_WITH_FAST_CONTENT_TYPE : GTH_FILE_DATA_ATTRIBUTES_WITH_CONTENT_TYPE,
+ func,
+ load_data);
+}
+
+
+/* -- */
+
+
+static void
+source_tree_children_ready (GthFileSource *file_source,
+ GList *files,
+ GError *error,
+ gpointer data)
+{
+ LoadData *load_data = data;
+ GthSourceTree *source_tree = load_data->source_tree;
+
+ if (error != NULL)
+ g_warning ("%s", error->message);
+ else
+ gth_folder_tree_set_children (GTH_FOLDER_TREE (source_tree), load_data->folder, files);
+
+ load_data_free (load_data);
+}
+
+
+static void
+source_tree_list_children_cb (GthFolderTree *folder_tree,
+ GFile *file,
+ GthSourceTree *source_tree)
+{
+ LoadData *load_data;
+
+ gth_folder_tree_loading_children (folder_tree, file);
+
+ load_data = load_data_new (source_tree, file);
+ load_data_run (load_data, source_tree_children_ready);
+}
+
+
+static void
+file_source_rename_ready_cb (GObject *object,
+ GError *error,
+ gpointer user_data)
+{
+ GthSourceTree *source_tree = user_data;
+
+ if (error != NULL)
+ _gtk_error_dialog_from_gerror_show (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (source_tree))), _("Could not change name"), &error);
+}
+
+
+static void
+source_tree_rename_cb (GthFolderTree *folder_tree,
+ GFile *file,
+ const char *new_name,
+ GthSourceTree *source_tree)
+{
+ GFile *parent;
+ char *uri;
+ char *new_basename;
+ GFile *new_file;
+ GError *error = NULL;
+
+ parent = g_file_get_parent (file);
+ uri = g_file_get_uri (file);
+ new_basename = g_strconcat (new_name, _g_uri_get_file_extension (uri), NULL);
+ new_file = g_file_get_child_for_display_name (parent, new_basename, &error);
+
+ if (new_file == NULL)
+ _gtk_error_dialog_from_gerror_show (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (source_tree))), _("Could not change name"), &error);
+ else
+ gth_file_source_rename (source_tree->priv->file_source, file, new_file, file_source_rename_ready_cb, source_tree);
+
+ g_object_unref (new_file);
+ g_free (new_basename);
+ g_free (uri);
+ g_object_unref (parent);
+}
+
+
+static void
+file_attributes_ready_cb (GthFileSource *file_source,
+ GList *files,
+ GError *error,
+ gpointer user_data)
+{
+ MonitorEventData *monitor_data = user_data;
+ GthSourceTree *source_tree = monitor_data->source_tree;
+
+ if (error != NULL) {
+ g_warning ("%s", error->message);
+ g_clear_error (&error);
+ monitor_event_data_unref (monitor_data);
+ return;
+ }
+
+ if (monitor_data->event == GTH_MONITOR_EVENT_CREATED)
+ gth_folder_tree_add_children (GTH_FOLDER_TREE (source_tree), monitor_data->parent, files);
+ else if (monitor_data->event == GTH_MONITOR_EVENT_CHANGED)
+ gth_folder_tree_update_children (GTH_FOLDER_TREE (source_tree), monitor_data->parent, files);
+
+ monitor_event_data_unref (monitor_data);
+}
+
+
+static void
+monitor_folder_changed_cb (GthMonitor *monitor,
+ GFile *parent,
+ GList *list,
+ GthMonitorEvent event,
+ GthSourceTree *source_tree)
+{
+ GtkTreePath *path;
+
+ path = gth_folder_tree_get_path (GTH_FOLDER_TREE (source_tree), parent);
+ if (g_file_equal (parent, gth_folder_tree_get_root (GTH_FOLDER_TREE (source_tree)))
+ || ((path != NULL) && gtk_tree_view_row_expanded (GTK_TREE_VIEW (source_tree), path)))
+ {
+ MonitorEventData *monitor_data;
+
+ switch (event) {
+ case GTH_MONITOR_EVENT_CREATED:
+ case GTH_MONITOR_EVENT_CHANGED:
+ monitor_data = monitor_event_data_new ();
+ monitor_data->parent = g_file_dup (parent);
+ monitor_data->event = event;
+ monitor_data->source_tree = source_tree;
+ gth_file_source_read_attributes (source_tree->priv->file_source,
+ list,
+ eel_gconf_get_boolean (PREF_FAST_FILE_TYPE, TRUE) ? GTH_FILE_DATA_ATTRIBUTES_WITH_FAST_CONTENT_TYPE : GTH_FILE_DATA_ATTRIBUTES_WITH_CONTENT_TYPE,
+ file_attributes_ready_cb,
+ monitor_data);
+ break;
+
+ case GTH_MONITOR_EVENT_DELETED:
+ gth_folder_tree_delete_children (GTH_FOLDER_TREE (source_tree), parent, list);
+ break;
+ }
+ }
+
+ gtk_tree_path_free (path);
+}
+
+
+static void
+monitor_file_renamed_cb (GthMonitor *monitor,
+ GFile *file,
+ GFile *new_file,
+ GthSourceTree *source_tree)
+{
+ GFileInfo *info;
+ GthFileData *file_data;
+
+ info = gth_file_source_get_file_info (source_tree->priv->file_source, new_file);
+ file_data = gth_file_data_new (new_file, info);
+ gth_folder_tree_update_child (GTH_FOLDER_TREE (source_tree), file, file_data);
+
+ g_object_unref (file_data);
+ g_object_unref (info);
+}
+
+
+static void
+gth_source_tree_init (GthSourceTree *source_tree)
+{
+ source_tree->priv = g_new0 (GthSourceTreePrivate, 1);
+
+ g_signal_connect (source_tree,
+ "list_children",
+ G_CALLBACK (source_tree_list_children_cb),
+ source_tree);
+ g_signal_connect (source_tree,
+ "rename",
+ G_CALLBACK (source_tree_rename_cb),
+ source_tree);
+ source_tree->priv->monitor_folder_changed_id =
+ g_signal_connect (gth_main_get_default_monitor (),
+ "folder-changed",
+ G_CALLBACK (monitor_folder_changed_cb),
+ source_tree);
+ source_tree->priv->monitor_file_renamed_id =
+ g_signal_connect (gth_main_get_default_monitor (),
+ "file-renamed",
+ G_CALLBACK (monitor_file_renamed_cb),
+ source_tree);
+}
+
+
+static void
+gth_source_tree_finalize (GObject *object)
+{
+ GthSourceTree *source_tree = GTH_SOURCE_TREE (object);
+
+ if (source_tree->priv != NULL) {
+ g_signal_handler_disconnect (gth_main_get_default_monitor (), source_tree->priv->monitor_folder_changed_id);
+ g_signal_handler_disconnect (gth_main_get_default_monitor (), source_tree->priv->monitor_file_renamed_id);
+
+ if (source_tree->priv->file_source != NULL)
+ g_object_unref (source_tree->priv->file_source);
+ g_free (source_tree->priv);
+ source_tree->priv = NULL;
+ }
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+
+static void
+gth_source_tree_class_init (GthSourceTreeClass *klass)
+{
+ GObjectClass *gobject_class;
+
+ parent_class = g_type_class_peek_parent (klass);
+ gobject_class = G_OBJECT_CLASS (klass);
+
+ gobject_class->finalize = gth_source_tree_finalize;
+}
+
+
+GType
+gth_source_tree_get_type (void)
+{
+ static GType type = 0;
+
+ if (type == 0) {
+ static const GTypeInfo g_define_type_info = {
+ sizeof (GthSourceTreeClass),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) gth_source_tree_class_init,
+ (GClassFinalizeFunc) NULL,
+ NULL,
+ sizeof (GthSourceTree),
+ 0,
+ (GInstanceInitFunc) gth_source_tree_init,
+ NULL
+ };
+ type = g_type_register_static (GTH_TYPE_FOLDER_TREE,
+ "GthSourceTree",
+ &g_define_type_info,
+ 0);
+ }
+
+ return type;
+}
+
+
+GtkWidget *
+gth_source_tree_new (GFile *root)
+{
+ GtkWidget *source_tree;
+
+ source_tree = g_object_new (GTH_TYPE_SOURCE_TREE, NULL);
+ gth_source_tree_set_root (GTH_SOURCE_TREE (source_tree), root);
+
+ return source_tree;
+}
+
+
+static void
+source_tree_file_list_ready (GthFileSource *file_source,
+ GList *files,
+ GError *error,
+ gpointer data)
+{
+ LoadData *load_data = data;
+ GthSourceTree *source_tree = load_data->source_tree;
+
+ if (error != NULL) {
+ g_warning ("%s\n", error->message);
+ load_data_free (load_data);
+ return;
+ }
+
+ gth_folder_tree_set_list (GTH_FOLDER_TREE (source_tree), load_data->folder, files, FALSE);
+
+ load_data_free (load_data);
+}
+
+
+void
+gth_source_tree_set_root (GthSourceTree *source_tree,
+ GFile *root)
+{
+ LoadData *load_data;
+
+ if (source_tree->priv->file_source != NULL)
+ g_object_unref (source_tree->priv->file_source);
+ source_tree->priv->file_source = gth_main_get_file_source (root);
+
+ load_data = load_data_new (source_tree, root);
+ load_data_run (load_data, source_tree_file_list_ready);
+}
diff --git a/gthumb/gth-source-tree.h b/gthumb/gth-source-tree.h
new file mode 100644
index 0000000..b09b524
--- /dev/null
+++ b/gthumb/gth-source-tree.h
@@ -0,0 +1,61 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GTH_SOURCE_TREE_H
+#define GTH_SOURCE_TREE_H
+
+#include <glib.h>
+#include <glib-object.h>
+#include <gtk/gtk.h>
+#include "gth-file-data.h"
+#include "gth-folder-tree.h"
+
+G_BEGIN_DECLS
+
+#define GTH_TYPE_SOURCE_TREE (gth_source_tree_get_type ())
+#define GTH_SOURCE_TREE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTH_TYPE_SOURCE_TREE, GthSourceTree))
+#define GTH_SOURCE_TREE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTH_TYPE_SOURCE_TREE, GthSourceTreeClass))
+#define GTH_IS_SOURCE_TREE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTH_TYPE_SOURCE_TREE))
+#define GTH_IS_SOURCE_TREE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTH_TYPE_SOURCE_TREE))
+#define GTH_SOURCE_TREE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTH_TYPE_SOURCE_TREE, GthSourceTreeClass))
+
+typedef struct _GthSourceTree GthSourceTree;
+typedef struct _GthSourceTreeClass GthSourceTreeClass;
+typedef struct _GthSourceTreePrivate GthSourceTreePrivate;
+
+struct _GthSourceTree {
+ GthFolderTree parent_instance;
+ GthSourceTreePrivate *priv;
+};
+
+struct _GthSourceTreeClass {
+ GthFolderTreeClass parent_class;
+};
+
+GType gth_source_tree_get_type (void);
+GtkWidget * gth_source_tree_new (GFile *root);
+void gth_source_tree_set_root (GthSourceTree *source_tree,
+ GFile *root);
+
+G_END_DECLS
+
+#endif /* GTH_SOURCE_TREE_H */
diff --git a/gthumb/gth-statusbar.c b/gthumb/gth-statusbar.c
new file mode 100644
index 0000000..f6fc1f0
--- /dev/null
+++ b/gthumb/gth-statusbar.c
@@ -0,0 +1,149 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <glib/gi18n.h>
+#include "gth-statusbar.h"
+
+
+#define GTH_STATUSBAR_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GTH_TYPE_STATUSBAR, GthStatusbarPrivate))
+
+
+static gpointer gth_statusbar_parent_class = NULL;
+
+
+struct _GthStatusbarPrivate {
+ guint list_info_cid;
+ GtkWidget *primary_text;
+ GtkWidget *primary_text_frame;
+ GtkWidget *secondary_text;
+ GtkWidget *secondary_text_frame;
+};
+
+
+static void
+gth_statusbar_class_init (GthStatusbarClass *klass)
+{
+ gth_statusbar_parent_class = g_type_class_peek_parent (klass);
+ g_type_class_add_private (klass, sizeof (GthStatusbarPrivate));
+}
+
+
+static void
+gth_statusbar_init (GthStatusbar *statusbar)
+{
+ statusbar->priv = GTH_STATUSBAR_GET_PRIVATE (statusbar);
+ statusbar->priv->list_info_cid = gtk_statusbar_get_context_id (GTK_STATUSBAR (statusbar), "gth_list_info");
+
+ /* Secondary text */
+
+ statusbar->priv->secondary_text = gtk_label_new (NULL);
+ gtk_widget_show (statusbar->priv->secondary_text);
+
+ statusbar->priv->secondary_text_frame = gtk_frame_new (NULL);
+ gtk_widget_show (statusbar->priv->secondary_text_frame);
+ gtk_frame_set_shadow_type (GTK_FRAME (statusbar->priv->secondary_text_frame), GTK_SHADOW_IN);
+ gtk_container_add (GTK_CONTAINER (statusbar->priv->secondary_text_frame), statusbar->priv->secondary_text);
+ gtk_box_pack_start (GTK_BOX (statusbar), statusbar->priv->secondary_text_frame, FALSE, FALSE, 0);
+
+ /* Primary text */
+
+ statusbar->priv->primary_text = gtk_label_new (NULL);
+ gtk_widget_show (statusbar->priv->primary_text);
+
+ statusbar->priv->primary_text_frame = gtk_frame_new (NULL);
+ gtk_widget_show (statusbar->priv->primary_text_frame);
+
+ gtk_frame_set_shadow_type (GTK_FRAME (statusbar->priv->primary_text_frame), GTK_SHADOW_IN);
+ gtk_container_add (GTK_CONTAINER (statusbar->priv->primary_text_frame), statusbar->priv->primary_text);
+ gtk_box_pack_start (GTK_BOX (statusbar), statusbar->priv->primary_text_frame, FALSE, FALSE, 0);
+}
+
+
+GType
+gth_statusbar_get_type (void)
+{
+ static GType type = 0;
+
+ if (type == 0) {
+ static const GTypeInfo g_define_type_info = {
+ sizeof (GthStatusbarClass),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) gth_statusbar_class_init,
+ (GClassFinalizeFunc) NULL,
+ NULL,
+ sizeof (GthStatusbar),
+ 0,
+ (GInstanceInitFunc) gth_statusbar_init,
+ NULL
+ };
+ type = g_type_register_static (GTK_TYPE_STATUSBAR,
+ "GthStatusbar",
+ &g_define_type_info,
+ 0);
+ }
+
+ return type;
+}
+
+
+GtkWidget *
+gth_statusbar_new (void)
+{
+ return g_object_new (GTH_TYPE_STATUSBAR, NULL);
+}
+
+
+void
+gth_statusbar_set_list_info (GthStatusbar *statusbar,
+ const char *text)
+{
+ gtk_statusbar_pop (GTK_STATUSBAR (statusbar), statusbar->priv->list_info_cid);
+ gtk_statusbar_push (GTK_STATUSBAR (statusbar), statusbar->priv->list_info_cid, text);
+}
+
+
+void
+gth_statusbar_set_primary_text (GthStatusbar *statusbar,
+ const char *text)
+{
+ if (text != NULL) {
+ gtk_label_set_text (GTK_LABEL (statusbar->priv->primary_text), text);
+ gtk_widget_show (statusbar->priv->primary_text_frame);
+ }
+ else
+ gtk_widget_hide (statusbar->priv->primary_text_frame);
+}
+
+
+void
+gth_statusbar_set_secondary_text (GthStatusbar *statusbar,
+ const char *text)
+{
+ if (text != NULL) {
+ gtk_label_set_text (GTK_LABEL (statusbar->priv->secondary_text), text);
+ gtk_widget_show (statusbar->priv->secondary_text_frame);
+ }
+ else
+ gtk_widget_hide (statusbar->priv->secondary_text_frame);
+}
diff --git a/gthumb/gth-statusbar.h b/gthumb/gth-statusbar.h
new file mode 100644
index 0000000..1c69fe2
--- /dev/null
+++ b/gthumb/gth-statusbar.h
@@ -0,0 +1,61 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GTH_STATUSBAR_H
+#define GTH_STATUSBAR_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GTH_TYPE_STATUSBAR (gth_statusbar_get_type ())
+#define GTH_STATUSBAR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTH_TYPE_STATUSBAR, GthStatusbar))
+#define GTH_STATUSBAR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTH_TYPE_STATUSBAR, GthStatusbarClass))
+#define GTH_IS_STATUSBAR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTH_TYPE_STATUSBAR))
+#define GTH_IS_STATUSBAR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTH_TYPE_STATUSBAR))
+#define GTH_STATUSBAR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTH_TYPE_STATUSBAR, GthStatusbarClass))
+
+typedef struct _GthStatusbar GthStatusbar;
+typedef struct _GthStatusbarClass GthStatusbarClass;
+typedef struct _GthStatusbarPrivate GthStatusbarPrivate;
+
+struct _GthStatusbar {
+ GtkStatusbar parent_instance;
+ GthStatusbarPrivate *priv;
+};
+
+struct _GthStatusbarClass {
+ GtkStatusbarClass parent_class;
+};
+
+GType gth_statusbar_get_type (void);
+GtkWidget * gth_statusbar_new (void);
+void gth_statusbar_set_list_info (GthStatusbar *statusbar,
+ const char *text);
+void gth_statusbar_set_primary_text (GthStatusbar *statusbar,
+ const char *text);
+void gth_statusbar_set_secondary_text (GthStatusbar *statusbar,
+ const char *text);
+
+G_END_DECLS
+
+#endif /* GTH_STATUSBAR_H */
diff --git a/gthumb/gth-stock.h b/gthumb/gth-stock.h
new file mode 100644
index 0000000..caf7334
--- /dev/null
+++ b/gthumb/gth-stock.h
@@ -0,0 +1,33 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2001-2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GTH_STOCK_H
+#define GTH_STOCK_H
+
+G_BEGIN_DECLS
+
+#define GTH_STOCK_BROWSER_MODE "browser-mode"
+#define GTH_STOCK_ZOOM_FIT_WIDTH "zoom-fit-width"
+
+G_END_DECLS
+
+#endif /* GTH_STOCK_H */
diff --git a/gthumb/gth-string-list.c b/gthumb/gth-string-list.c
new file mode 100644
index 0000000..f846c4f
--- /dev/null
+++ b/gthumb/gth-string-list.c
@@ -0,0 +1,119 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2001-2008 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include "glib-utils.h"
+#include "gth-string-list.h"
+
+
+struct _GthStringListPrivate {
+ GList *list;
+};
+
+#define GTH_STRING_LIST_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GTH_TYPE_STRING_LIST, GthStringListPrivate))
+static gpointer gth_string_list_parent_class = NULL;
+
+
+static void
+gth_string_list_finalize (GObject* obj)
+{
+ GthStringList *self;
+
+ self = GTH_STRING_LIST (obj);
+
+ _g_string_list_free (self->priv->list);
+
+ G_OBJECT_CLASS (gth_string_list_parent_class)->finalize (obj);
+}
+
+
+static void
+gth_string_list_class_init (GthStringListClass * klass)
+{
+ gth_string_list_parent_class = g_type_class_peek_parent (klass);
+ g_type_class_add_private (klass, sizeof (GthStringListPrivate));
+
+ G_OBJECT_CLASS (klass)->finalize = gth_string_list_finalize;
+}
+
+
+static void gth_string_list_instance_init (GthStringList * self) {
+ self->priv = GTH_STRING_LIST_GET_PRIVATE (self);
+}
+
+
+GType gth_string_list_get_type (void) {
+ static GType gth_string_list_type_id = 0;
+ if (gth_string_list_type_id == 0) {
+ static const GTypeInfo g_define_type_info = {
+ sizeof (GthStringListClass),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) gth_string_list_class_init,
+ (GClassFinalizeFunc) NULL,
+ NULL,
+ sizeof (GthStringList),
+ 0,
+ (GInstanceInitFunc) gth_string_list_instance_init,
+ NULL
+ };
+ gth_string_list_type_id = g_type_register_static (G_TYPE_OBJECT, "GthStringList", &g_define_type_info, 0);
+ }
+ return gth_string_list_type_id;
+}
+
+
+GthStringList *
+gth_string_list_new (GList *list)
+{
+ GthStringList *string_list;
+
+ string_list = g_object_new (GTH_TYPE_STRING_LIST, NULL);
+ string_list->priv->list = _g_string_list_dup (list);
+
+ return string_list;
+}
+
+
+GthStringList *
+gth_string_list_new_from_ptr_array (GPtrArray *array)
+{
+ GthStringList *string_list;
+ int i;
+
+ string_list = g_object_new (GTH_TYPE_STRING_LIST, NULL);
+ if (array != NULL) {
+ for (i = 0; i < array->len; i++)
+ string_list->priv->list = g_list_prepend (string_list->priv->list, g_strdup (g_ptr_array_index (array, i)));
+ string_list->priv->list = g_list_reverse (string_list->priv->list);
+ }
+ else
+ string_list->priv->list = NULL;
+
+ return string_list;
+}
+
+
+GList *
+gth_string_list_get_list (GthStringList *list)
+{
+ return list->priv->list;
+}
diff --git a/gthumb/gth-string-list.h b/gthumb/gth-string-list.h
new file mode 100644
index 0000000..cac473b
--- /dev/null
+++ b/gthumb/gth-string-list.h
@@ -0,0 +1,58 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2001-2008 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GTH_STRING_LIST_H
+#define GTH_STRING_LIST_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define GTH_TYPE_STRING_LIST (gth_string_list_get_type ())
+#define GTH_STRING_LIST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTH_TYPE_STRING_LIST, GthStringList))
+#define GTH_STRING_LIST_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTH_TYPE_STRING_LIST, GthStringListClass))
+#define GTH_IS_STRING_LIST(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTH_TYPE_STRING_LIST))
+#define GTH_IS_STRING_LIST_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTH_TYPE_STRING_LIST))
+#define GTH_STRING_LIST_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTH_TYPE_STRING_LIST, GthStringListClass))
+
+typedef struct _GthStringList GthStringList;
+typedef struct _GthStringListClass GthStringListClass;
+typedef struct _GthStringListPrivate GthStringListPrivate;
+
+struct _GthStringList {
+ GObject parent_instance;
+ GthStringListPrivate * priv;
+};
+
+struct _GthStringListClass {
+ GObjectClass parent_class;
+};
+
+GType gth_string_list_get_type (void);
+GthStringList * gth_string_list_new (GList *list);
+GthStringList * gth_string_list_new_from_ptr_array (GPtrArray *array);
+GList * gth_string_list_get_list (GthStringList *list);
+
+G_END_DECLS
+
+#endif /* GTH_STRING_LIST_H */
diff --git a/gthumb/gth-task.c b/gthumb/gth-task.c
new file mode 100644
index 0000000..cfa105c
--- /dev/null
+++ b/gthumb/gth-task.c
@@ -0,0 +1,201 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <glib.h>
+#include "gth-task.h"
+
+
+/* Signals */
+enum {
+ COMPLETED,
+ PROGRESS,
+ LAST_SIGNAL
+};
+
+struct _GthTaskPrivate
+{
+ gboolean running;
+};
+
+
+static GObjectClass *parent_class = NULL;
+static guint gth_task_signals[LAST_SIGNAL] = { 0 };
+
+
+GQuark
+gth_task_error_quark (void)
+{
+ return g_quark_from_static_string ("gth-task-error-quark");
+}
+
+
+static void
+gth_task_finalize (GObject *object)
+{
+ GthTask *task;
+
+ task = GTH_TASK (object);
+
+ if (task->priv != NULL) {
+ g_free (task->priv);
+ task->priv = NULL;
+ }
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+
+static void
+base_exec (GthTask *task)
+{
+ gth_task_completed (task, NULL);
+}
+
+
+static void
+base_cancel (GthTask *task)
+{
+ gth_task_completed (task, g_error_new_literal (GTH_TASK_ERROR, GTH_TASK_ERROR_CANCELLED, NULL));
+}
+
+
+static void
+gth_task_class_init (GthTaskClass *class)
+{
+ GObjectClass *object_class;
+
+ parent_class = g_type_class_peek_parent (class);
+ object_class = (GObjectClass*) class;
+
+ object_class->finalize = gth_task_finalize;
+
+ class->exec = base_exec;
+ class->cancel = base_cancel;
+
+ /* signals */
+
+ gth_task_signals[COMPLETED] =
+ g_signal_new ("completed",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GthTaskClass, completed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__POINTER,
+ G_TYPE_NONE,
+ 1,
+ G_TYPE_POINTER);
+
+ gth_task_signals[PROGRESS] =
+ g_signal_new ("progress",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GthTaskClass, progress),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__FLOAT,
+ G_TYPE_NONE,
+ 1,
+ G_TYPE_FLOAT);
+}
+
+
+static void
+gth_task_init (GthTask *task)
+{
+ task->priv = g_new0 (GthTaskPrivate, 1);
+ task->priv->running = FALSE;
+}
+
+
+GType
+gth_task_get_type (void)
+{
+ static GType type = 0;
+
+ if (! type) {
+ GTypeInfo type_info = {
+ sizeof (GthTaskClass),
+ NULL,
+ NULL,
+ (GClassInitFunc) gth_task_class_init,
+ NULL,
+ NULL,
+ sizeof (GthTask),
+ 0,
+ (GInstanceInitFunc) gth_task_init
+ };
+
+ type = g_type_register_static (G_TYPE_OBJECT,
+ "GthTask",
+ &type_info,
+ 0);
+ }
+
+ return type;
+}
+
+
+GthTask *
+gth_task_new (void)
+{
+ return (GthTask*) g_object_new (GTH_TYPE_TASK, NULL);
+}
+
+
+void
+gth_task_exec (GthTask *task)
+{
+ task->priv->running = TRUE;
+ GTH_TASK_GET_CLASS (task)->exec (task);
+}
+
+
+gboolean
+gth_task_is_running (GthTask *task)
+{
+ return task->priv->running;
+}
+
+
+void
+gth_task_cancel (GthTask *task)
+{
+ if (task->priv->running)
+ GTH_TASK_GET_CLASS (task)->cancel (task);
+}
+
+
+void
+gth_task_completed (GthTask *task,
+ GError *error)
+{
+ task->priv->running = FALSE;
+ g_signal_emit (task, gth_task_signals[COMPLETED], 0, error);
+}
+
+
+void
+gth_task_progress (GthTask *task,
+ float value)
+{
+ g_signal_emit (task, gth_task_signals[PROGRESS], 0, value);
+}
diff --git a/gthumb/gth-task.h b/gthumb/gth-task.h
new file mode 100644
index 0000000..0343018
--- /dev/null
+++ b/gthumb/gth-task.h
@@ -0,0 +1,85 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GTH_TASK_H
+#define GTH_TASK_H
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define GTH_TASK_ERROR gth_task_error_quark ()
+
+typedef enum {
+ GTH_TASK_ERROR_FAILED,
+ GTH_TASK_ERROR_CANCELLED
+} GthTaskErrorEnum;
+
+#define GTH_TYPE_TASK (gth_task_get_type ())
+#define GTH_TASK(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GTH_TYPE_TASK, GthTask))
+#define GTH_TASK_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), GTH_TYPE_TASK, GthTaskClass))
+#define GTH_IS_TASK(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GTH_TYPE_TASK))
+#define GTH_IS_TASK_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GTH_TYPE_TASK))
+#define GTH_TASK_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS((o), GTH_TYPE_TASK, GthTaskClass))
+
+typedef struct _GthTask GthTask;
+typedef struct _GthTaskPrivate GthTaskPrivate;
+typedef struct _GthTaskClass GthTaskClass;
+
+struct _GthTask
+{
+ GObject __parent;
+ GthTaskPrivate *priv;
+};
+
+struct _GthTaskClass
+{
+ GObjectClass __parent_class;
+
+ /*< signals >*/
+
+ void (*completed) (GthTask *task,
+ GError *error);
+ void (*progress) (GthTask *task,
+ float value);
+
+ /*< virtual functions >*/
+
+ void (*exec) (GthTask *task);
+ void (*cancel) (GthTask *task);
+};
+
+GQuark gth_task_error_quark (void);
+
+GType gth_task_get_type (void) G_GNUC_CONST;
+GthTask * gth_task_new (void);
+void gth_task_exec (GthTask *task);
+gboolean gth_task_is_running (GthTask *task);
+void gth_task_cancel (GthTask *task);
+void gth_task_completed (GthTask *task,
+ GError *error);
+void gth_task_progress (GthTask *task,
+ float value);
+
+G_END_DECLS
+
+#endif /* GTH_TASK_H */
diff --git a/gthumb/gth-test-chain.c b/gthumb/gth-test-chain.c
new file mode 100644
index 0000000..f68a65a
--- /dev/null
+++ b/gthumb/gth-test-chain.c
@@ -0,0 +1,329 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2008 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include "dom.h"
+#include "glib-utils.h"
+#include "gth-duplicable.h"
+#include "gth-enum-types.h"
+#include "gth-main.h"
+#include "gth-test-chain.h"
+
+
+struct _GthTestChainPrivate
+{
+ GthMatchType match_type;
+ GList *tests;
+};
+
+
+static GthTestClass *parent_class = NULL;
+static DomDomizableIface *dom_domizable_parent_iface = NULL;
+static GthDuplicableIface *gth_duplicable_parent_iface = NULL;
+
+
+static void
+gth_test_chain_finalize (GObject *object)
+{
+ GthTestChain *test;
+
+ test = GTH_TEST_CHAIN (object);
+
+ if (test->priv != NULL) {
+ _g_object_list_unref (test->priv->tests);
+ test->priv = NULL;
+ }
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+
+static GthMatch
+gth_test_chain_real_match (GthTest *test,
+ GthFileData *file)
+{
+ GthTestChain *chain;
+ GthMatch match = GTH_MATCH_NO;
+ GList *scan;
+
+ chain = GTH_TEST_CHAIN (test);
+
+ if (chain->priv->match_type == GTH_MATCH_TYPE_NONE)
+ return GTH_MATCH_YES;
+
+ match = (chain->priv->match_type == GTH_MATCH_TYPE_ALL) ? GTH_MATCH_YES : GTH_MATCH_NO;
+ for (scan = chain->priv->tests; scan; scan = scan->next) {
+ GthTest *test = scan->data;
+
+ if (gth_test_match (test, file)) {
+ if (chain->priv->match_type == GTH_MATCH_TYPE_ANY) {
+ match = GTH_MATCH_YES;
+ break;
+ }
+ }
+ else if (chain->priv->match_type == GTH_MATCH_TYPE_ALL) {
+ match = GTH_MATCH_NO;
+ break;
+ }
+ }
+
+ return match;
+}
+
+
+static DomElement*
+gth_test_chain_real_create_element (DomDomizable *base,
+ DomDocument *doc)
+{
+ GthTestChain *self;
+ DomElement *element;
+ GList *scan;
+
+ self = GTH_TEST_CHAIN (base);
+
+ element = dom_document_create_element (doc, "tests",
+ "match", _g_enum_type_get_value (GTH_TYPE_MATCH_TYPE, self->priv->match_type)->value_nick,
+ NULL);
+ for (scan = self->priv->tests; scan; scan = scan->next)
+ dom_element_append_child (element, dom_domizable_create_element (DOM_DOMIZABLE (scan->data), doc));
+
+ return element;
+}
+
+
+static void
+gth_test_chain_real_load_from_element (DomDomizable *base,
+ DomElement *element)
+{
+ GthTestChain *chain;
+ GEnumValue *enum_value;
+ DomElement *node;
+
+ chain = GTH_TEST_CHAIN (base);
+
+ enum_value = _g_enum_type_get_value_by_nick (GTH_TYPE_MATCH_TYPE, dom_element_get_attribute (element, "match"));
+ if (enum_value != NULL)
+ chain->priv->match_type = enum_value->value;
+
+ gth_test_chain_clear_tests (chain);
+ for (node = element->first_child; node; node = node->next_sibling) {
+ if (g_strcmp0 (node->tag_name, "test") == 0) {
+ GthTest *test;
+
+ test = gth_main_get_test (dom_element_get_attribute (node, "id"));
+ if (test == NULL)
+ continue;
+
+ dom_domizable_load_from_element (DOM_DOMIZABLE (test), node);
+ gth_test_chain_add_test (chain, test);
+ g_object_unref (test);
+ }
+ else if (g_strcmp0 (node->tag_name, "tests") == 0) {
+ GthTest *sub_chain;
+
+ sub_chain = gth_test_chain_new (GTH_MATCH_TYPE_NONE, NULL);
+ dom_domizable_load_from_element (DOM_DOMIZABLE (sub_chain), node);
+ gth_test_chain_add_test (chain, sub_chain);
+ g_object_unref (sub_chain);
+ }
+ }
+}
+
+
+GObject *
+gth_test_chain_real_duplicate (GthDuplicable *duplicable)
+{
+ GthTestChain *chain;
+ GthTest *new_chain;
+ GList *tests, *scan;
+
+ chain = GTH_TEST_CHAIN (duplicable);
+
+ new_chain = gth_test_chain_new (chain->priv->match_type, NULL);
+ tests = gth_test_chain_get_tests (chain);
+ for (scan = tests; scan; scan = scan->next)
+ gth_test_chain_add_test (GTH_TEST_CHAIN (new_chain), scan->data);
+ _g_object_list_unref (tests);
+
+ return G_OBJECT (new_chain);
+}
+
+
+static void
+gth_test_chain_class_init (GthTestChainClass *class)
+{
+ GObjectClass *object_class;
+ GthTestClass *test_class;
+
+ parent_class = g_type_class_peek_parent (class);
+ object_class = (GObjectClass*) class;
+ test_class = (GthTestClass *) class;
+
+ object_class->finalize = gth_test_chain_finalize;
+ test_class->match = gth_test_chain_real_match;
+}
+
+
+static void
+gth_test_chain_dom_domizable_interface_init (DomDomizableIface * iface)
+{
+ dom_domizable_parent_iface = g_type_interface_peek_parent (iface);
+ iface->create_element = gth_test_chain_real_create_element;
+ iface->load_from_element = gth_test_chain_real_load_from_element;
+}
+
+
+static void
+gth_test_chain_gth_duplicable_interface_init (GthDuplicableIface *iface)
+{
+ gth_duplicable_parent_iface = g_type_interface_peek_parent (iface);
+ iface->duplicate = gth_test_chain_real_duplicate;
+}
+
+
+static void
+gth_test_chain_init (GthTestChain *test)
+{
+ test->priv = g_new0 (GthTestChainPrivate, 1);
+}
+
+
+GType
+gth_test_chain_get_type (void)
+{
+ static GType type = 0;
+
+ if (! type) {
+ GTypeInfo type_info = {
+ sizeof (GthTestChainClass),
+ NULL,
+ NULL,
+ (GClassInitFunc) gth_test_chain_class_init,
+ NULL,
+ NULL,
+ sizeof (GthTestChain),
+ 0,
+ (GInstanceInitFunc) gth_test_chain_init
+ };
+ static const GInterfaceInfo dom_domizable_info = {
+ (GInterfaceInitFunc) gth_test_chain_dom_domizable_interface_init,
+ (GInterfaceFinalizeFunc) NULL,
+ NULL
+ };
+ static const GInterfaceInfo gth_duplicable_info = {
+ (GInterfaceInitFunc) gth_test_chain_gth_duplicable_interface_init,
+ (GInterfaceFinalizeFunc) NULL,
+ NULL
+ };
+
+ type = g_type_register_static (GTH_TYPE_TEST,
+ "GthTestChain",
+ &type_info,
+ 0);
+ g_type_add_interface_static (type, DOM_TYPE_DOMIZABLE, &dom_domizable_info);
+ g_type_add_interface_static (type, GTH_TYPE_DUPLICABLE, >h_duplicable_info);
+ }
+
+ return type;
+}
+
+
+GthTest *
+gth_test_chain_new (GthMatchType match_type,
+ GthTest *test,
+ ...)
+{
+ GthTestChain *chain;
+ va_list args;
+
+ chain = g_object_new (GTH_TYPE_TEST_CHAIN, NULL);
+ chain->priv->match_type = match_type;
+
+ va_start (args, test);
+ while (test != NULL) {
+ gth_test_chain_add_test (chain, g_object_ref (test));
+ test = va_arg (args, GthTest *);
+ }
+ va_end (args);
+
+ return (GthTest *) chain;
+}
+
+
+void
+gth_test_chain_set_match_type (GthTestChain *chain,
+ GthMatchType match_type)
+{
+ chain->priv->match_type = match_type;
+}
+
+
+GthMatchType
+gth_test_chain_get_match_type (GthTestChain *chain)
+{
+ return chain->priv->match_type;
+}
+
+
+void
+gth_test_chain_clear_tests (GthTestChain *chain)
+{
+ if (chain->priv->tests == NULL)
+ return;
+ _g_object_list_unref (chain->priv->tests);
+ chain->priv->tests = NULL;
+}
+
+
+void
+gth_test_chain_add_test (GthTestChain *chain,
+ GthTest *test)
+{
+ g_object_set (test, "visible", TRUE, NULL);
+ chain->priv->tests = g_list_append (chain->priv->tests, g_object_ref (test));
+}
+
+
+GList *
+gth_test_chain_get_tests (GthTestChain *chain)
+{
+ return _g_object_list_ref (chain->priv->tests);
+}
+
+
+gboolean
+gth_test_chain_has_type_test (GthTestChain *chain)
+{
+ gboolean result = FALSE;
+ GList *scan;
+
+ for (scan = chain->priv->tests; scan; scan = scan->next) {
+ GthTest *single_test = scan->data;
+
+ if (strncmp (gth_test_get_id (single_test), "file::type::", 12) == 0) {
+ result = TRUE;
+ break;
+ }
+ }
+
+ return result;
+}
diff --git a/gthumb/gth-test-chain.h b/gthumb/gth-test-chain.h
new file mode 100644
index 0000000..d3dd12f
--- /dev/null
+++ b/gthumb/gth-test-chain.h
@@ -0,0 +1,75 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2008 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GTH_TEST_CHAIN_H
+#define GTH_TEST_CHAIN_H
+
+#include <glib-object.h>
+#include <gtk/gtk.h>
+#include "gth-test.h"
+
+G_BEGIN_DECLS
+
+#define GTH_TYPE_TEST_CHAIN (gth_test_chain_get_type ())
+#define GTH_TEST_CHAIN(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GTH_TYPE_TEST_CHAIN, GthTestChain))
+#define GTH_TEST_CHAIN_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), GTH_TYPE_TEST_CHAIN, GthTestChainClass))
+#define GTH_IS_TEST_CHAIN(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GTH_TYPE_TEST_CHAIN))
+#define GTH_IS_TEST_CHAIN_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GTH_TYPE_TEST_CHAIN))
+#define GTH_TEST_CHAIN_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS((o), GTH_TYPE_TEST_CHAIN, GthTestChainClass))
+
+typedef struct _GthTestChain GthTestChain;
+typedef struct _GthTestChainPrivate GthTestChainPrivate;
+typedef struct _GthTestChainClass GthTestChainClass;
+
+typedef enum {
+ GTH_MATCH_TYPE_NONE = 0,
+ GTH_MATCH_TYPE_ALL,
+ GTH_MATCH_TYPE_ANY
+} GthMatchType;
+
+struct _GthTestChain
+{
+ GthTest __parent;
+ GthTestChainPrivate *priv;
+};
+
+struct _GthTestChainClass
+{
+ GthTestClass __parent_class;
+};
+
+GType gth_test_chain_get_type (void) G_GNUC_CONST;
+GthTest * gth_test_chain_new (GthMatchType match_type,
+ GthTest *first_test,
+ ...);
+void gth_test_chain_set_match_type (GthTestChain *chain,
+ GthMatchType match_type);
+GthMatchType gth_test_chain_get_match_type (GthTestChain *chain);
+void gth_test_chain_clear_tests (GthTestChain *chain);
+void gth_test_chain_add_test (GthTestChain *chain,
+ GthTest *test);
+GList * gth_test_chain_get_tests (GthTestChain *chain);
+gboolean gth_test_chain_has_type_test (GthTestChain *chain);
+
+G_END_DECLS
+
+#endif /* GTH_TEST_CHAIN_H */
diff --git a/gthumb/gth-test-selector.c b/gthumb/gth-test-selector.c
new file mode 100644
index 0000000..f2a94e3
--- /dev/null
+++ b/gthumb/gth-test-selector.c
@@ -0,0 +1,414 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2008 The Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <glib/gi18n.h>
+#include "glib-utils.h"
+#include "gth-main.h"
+#include "gth-test-selector.h"
+
+
+enum {
+ NAME_COLUMN,
+ TEST_COLUMN,
+ N_COLUMNS
+};
+
+
+enum {
+ ADD_TEST,
+ REMOVE_TEST,
+ LAST_SIGNAL
+};
+
+
+struct _GthTestSelectorPrivate {
+ GthTest *test;
+ GtkListStore *model;
+ GtkWidget *test_combo_box;
+ GtkWidget *control_box;
+ GtkWidget *control;
+ GtkWidget *add_button;
+ GtkWidget *remove_button;
+};
+
+
+static gpointer parent_class = NULL;
+static guint gth_test_selector_signals[LAST_SIGNAL] = { 0 };
+
+
+static void
+gth_test_selector_finalize (GObject *object)
+{
+ GthTestSelector *selector;
+
+ selector = GTH_TEST_SELECTOR (object);
+
+ if (selector->priv != NULL) {
+ if (selector->priv->test != NULL)
+ g_object_unref (selector->priv->test);
+ g_free (selector->priv);
+ selector->priv = NULL;
+ }
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+
+static void
+gth_test_selector_class_init (GthTestSelectorClass *klass)
+{
+ parent_class = g_type_class_peek_parent (klass);
+
+ G_OBJECT_CLASS (klass)->finalize = gth_test_selector_finalize;
+
+ /* signals */
+
+ gth_test_selector_signals[ADD_TEST] =
+ g_signal_new ("add-test",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GthTestSelectorClass, add_test),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+ gth_test_selector_signals[REMOVE_TEST] =
+ g_signal_new ("remove-test",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GthTestSelectorClass, remove_test),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+}
+
+
+static void
+gth_test_selector_instance_init (GthTestSelector *self)
+{
+ self->priv = g_new0 (GthTestSelectorPrivate, 1);
+}
+
+
+GType
+gth_test_selector_get_type (void)
+{
+ static GType type_id = 0;
+
+ if (type_id == 0) {
+ static const GTypeInfo type_info = {
+ sizeof (GthTestSelectorClass),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) gth_test_selector_class_init,
+ (GClassFinalizeFunc) NULL,
+ NULL,
+ sizeof (GthTestSelector),
+ 0,
+ (GInstanceInitFunc) gth_test_selector_instance_init,
+ NULL
+ };
+ type_id = g_type_register_static (GTK_TYPE_HBOX,
+ "GthTestSelector",
+ &type_info,
+ 0);
+ }
+ return type_id;
+}
+
+
+static void
+test_combo_box_changed_cb (GtkComboBox *scope_combo_box,
+ GthTestSelector *self)
+{
+ GtkTreeIter iter;
+ const char *test_name;
+
+ if (! gtk_combo_box_get_active_iter (scope_combo_box, &iter))
+ return;
+
+ gtk_tree_model_get (GTK_TREE_MODEL (self->priv->model),
+ &iter,
+ TEST_COLUMN, &test_name,
+ -1);
+
+ if (test_name != NULL) {
+ GthTest *test;
+
+ test = gth_main_get_test (test_name);
+ gth_test_selector_set_test (self, test);
+ g_object_unref (test);
+ }
+}
+
+
+static void
+add_button_clicked_cb (GtkButton *button,
+ GthTestSelector *self)
+{
+ g_signal_emit (self, gth_test_selector_signals[ADD_TEST], 0);
+}
+
+
+static void
+remove_button_clicked_cb (GtkButton *button,
+ GthTestSelector *self)
+{
+ g_signal_emit (self, gth_test_selector_signals[REMOVE_TEST], 0);
+}
+
+
+static void
+gth_test_selector_construct (GthTestSelector *self,
+ GthTest *test)
+{
+ GtkCellRenderer *renderer;
+ GtkTreeIter iter;
+ GtkWidget *vbox;
+ GtkWidget *hbox;
+ GList *tests;
+ GList *scan;
+
+ GTK_BOX (self)->spacing = 6;
+ gtk_container_set_border_width (GTK_CONTAINER (self), 2);
+
+ /* scope combo box */
+
+ self->priv->model = gtk_list_store_new (N_COLUMNS,
+ G_TYPE_STRING,
+ G_TYPE_STRING);
+ self->priv->test_combo_box = gtk_combo_box_new_with_model (GTK_TREE_MODEL (self->priv->model));
+ g_object_unref (self->priv->model);
+
+ /* name renderer */
+
+ renderer = gtk_cell_renderer_text_new ();
+ gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (self->priv->test_combo_box),
+ renderer,
+ TRUE);
+ gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (self->priv->test_combo_box),
+ renderer,
+ "text", NAME_COLUMN,
+ NULL);
+
+ /**/
+
+ tests = gth_main_get_all_tests ();
+ for (scan = tests; scan; scan = scan->next) {
+ const char *test_name = scan->data;
+ GthTest *test;
+
+ test = gth_main_get_test (test_name);
+
+ gtk_list_store_append (self->priv->model, &iter);
+ gtk_list_store_set (self->priv->model, &iter,
+ TEST_COLUMN, test_name,
+ NAME_COLUMN, gth_test_get_display_name (test),
+ -1);
+
+ g_object_unref (test);
+ }
+ _g_string_list_free (tests);
+
+ g_signal_connect (G_OBJECT (self->priv->test_combo_box),
+ "changed",
+ G_CALLBACK (test_combo_box_changed_cb),
+ self);
+
+ gtk_widget_show (self->priv->test_combo_box);
+
+ /* test control box */
+
+ self->priv->control_box = gtk_hbox_new (FALSE, 0);
+ gtk_widget_show (self->priv->control_box);
+
+ /**/
+
+ self->priv->add_button = gtk_button_new ();
+ gtk_container_add (GTK_CONTAINER (self->priv->add_button), gtk_image_new_from_stock (GTK_STOCK_ADD, GTK_ICON_SIZE_BUTTON));
+ gtk_button_set_relief (GTK_BUTTON (self->priv->add_button), GTK_RELIEF_NONE);
+ gtk_widget_set_tooltip_text (self->priv->add_button, _("Add a new rule"));
+ gtk_widget_show_all (self->priv->add_button);
+
+ g_signal_connect (G_OBJECT (self->priv->add_button),
+ "clicked",
+ G_CALLBACK (add_button_clicked_cb),
+ self);
+
+ self->priv->remove_button = gtk_button_new ();
+ gtk_container_add (GTK_CONTAINER (self->priv->remove_button), gtk_image_new_from_stock (GTK_STOCK_REMOVE, GTK_ICON_SIZE_BUTTON));
+ gtk_button_set_relief (GTK_BUTTON (self->priv->remove_button), GTK_RELIEF_NONE);
+ gtk_widget_set_tooltip_text (self->priv->remove_button, _("Remove this rule"));
+ gtk_widget_show_all (self->priv->remove_button);
+
+ g_signal_connect (G_OBJECT (self->priv->remove_button),
+ "clicked",
+ G_CALLBACK (remove_button_clicked_cb),
+ self);
+
+ /**/
+
+ vbox = gtk_vbox_new (FALSE, 0);
+ gtk_widget_show (vbox);
+
+ hbox = gtk_hbox_new (FALSE, 6);
+ gtk_widget_show (hbox);
+
+ gtk_box_pack_start (GTK_BOX (hbox), self->priv->test_combo_box, FALSE, FALSE, 0);
+ gtk_box_pack_start (GTK_BOX (hbox), self->priv->control_box, FALSE, FALSE, 0);
+ gtk_box_pack_start (GTK_BOX (vbox), hbox, TRUE, FALSE, 0);
+ gtk_box_pack_start (GTK_BOX (self), vbox, FALSE, FALSE, 0);
+
+ gtk_box_pack_end (GTK_BOX (self), self->priv->add_button, FALSE, FALSE, 0);
+ gtk_box_pack_end (GTK_BOX (self), self->priv->remove_button, FALSE, FALSE, 0);
+
+ gth_test_selector_set_test (self, test);
+}
+
+
+GtkWidget *
+gth_test_selector_new (void)
+{
+ GthTestSelector *self;
+
+ self = g_object_new (GTH_TYPE_TEST_SELECTOR, NULL);
+ gth_test_selector_construct (self, NULL);
+
+ return (GtkWidget *) self;
+}
+
+
+GtkWidget *
+gth_test_selector_new_with_test (GthTest *test)
+{
+ GthTestSelector *self;
+
+ self = g_object_new (GTH_TYPE_TEST_SELECTOR, NULL);
+ gth_test_selector_construct (self, test);
+
+ return (GtkWidget *) self;
+}
+
+
+static int
+get_test_index (GthTestSelector *self,
+ GthTest *test)
+{
+ GtkTreeModel *model = GTK_TREE_MODEL (self->priv->model);
+ GtkTreeIter iter;
+ const char *test_id;
+ int i;
+ int idx;
+
+ if (! gtk_tree_model_get_iter_first (model, &iter))
+ return 0;
+
+ g_object_get (test, "id", &test_id, NULL);
+ i = idx = -1;
+ do {
+ char *id;
+
+ i++;
+ gtk_tree_model_get (model, &iter, TEST_COLUMN, &id, -1);
+ if (g_strcmp0 (test_id, id) == 0)
+ idx = i;
+ g_free (id);
+ } while ((idx == -1) && gtk_tree_model_iter_next (model, &iter));
+
+ return (idx == -1) ? 0 : idx;
+}
+
+
+void
+gth_test_selector_set_test (GthTestSelector *self,
+ GthTest *test)
+{
+ GtkWidget *control;
+ GthTest *local_test = NULL;
+
+ if (test == NULL) {
+ GtkTreeModel *model = GTK_TREE_MODEL (self->priv->model);
+ GtkTreeIter iter;
+ char *first_test_id;
+
+ if (! gtk_tree_model_get_iter_first (model, &iter))
+ return;
+
+ gtk_tree_model_get (model, &iter, TEST_COLUMN, &first_test_id, -1);
+ test = local_test = gth_main_get_test (first_test_id);
+ g_free (first_test_id);
+ }
+
+ /* update the active test */
+
+ g_signal_handlers_block_by_func (self->priv->test_combo_box, test_combo_box_changed_cb, self);
+ gtk_combo_box_set_active (GTK_COMBO_BOX (self->priv->test_combo_box), get_test_index (self, test));
+ g_signal_handlers_unblock_by_func (self->priv->test_combo_box, test_combo_box_changed_cb, self);
+
+ /* set the test control */
+
+ if (test != NULL)
+ control = gth_test_create_control (test);
+ else
+ control = NULL;
+
+ if (self->priv->control != NULL) {
+ gtk_container_remove (GTK_CONTAINER (self->priv->control_box),
+ self->priv->control);
+ self->priv->control = NULL;
+ }
+
+ if (control != NULL) {
+ self->priv->control = control;
+ gtk_widget_show (control);
+ gtk_container_add (GTK_CONTAINER (self->priv->control_box),
+ self->priv->control);
+ }
+
+ if (self->priv->test != NULL)
+ g_object_unref (self->priv->test);
+ self->priv->test = g_object_ref (test);
+
+ if (local_test != NULL)
+ g_object_unref (local_test);
+}
+
+
+GthTest *
+gth_test_selector_get_test (GthTestSelector *self,
+ GError **error)
+{
+ if (! gth_test_update_from_control (self->priv->test, error))
+ return NULL;
+ else
+ return g_object_ref (self->priv->test);
+}
+
+
+void
+gth_test_selector_can_remove (GthTestSelector *self,
+ gboolean value)
+{
+ gtk_widget_set_sensitive (self->priv->remove_button, value);
+}
diff --git a/gthumb/gth-test-selector.h b/gthumb/gth-test-selector.h
new file mode 100644
index 0000000..2fb4621
--- /dev/null
+++ b/gthumb/gth-test-selector.h
@@ -0,0 +1,66 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2008 The Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GTH_TEST_SELECTOR_H
+#define GTH_TEST_SELECTOR_H
+
+#include <gtk/gtk.h>
+#include "gth-test.h"
+
+G_BEGIN_DECLS
+
+#define GTH_TYPE_TEST_SELECTOR (gth_test_selector_get_type ())
+#define GTH_TEST_SELECTOR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTH_TYPE_TEST_SELECTOR, GthTestSelector))
+#define GTH_TEST_SELECTOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTH_TYPE_TEST_SELECTOR, GthTestSelectorClass))
+#define GTH_IS_TEST_SELECTOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTH_TYPE_TEST_SELECTOR))
+#define GTH_IS_TEST_SELECTOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTH_TYPE_TEST_SELECTOR))
+#define GTH_TEST_SELECTOR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTH_TYPE_TEST_SELECTOR, GthTestSelectorClass))
+
+typedef struct _GthTestSelector GthTestSelector;
+typedef struct _GthTestSelectorClass GthTestSelectorClass;
+typedef struct _GthTestSelectorPrivate GthTestSelectorPrivate;
+
+struct _GthTestSelector {
+ GtkHBox parent_instance;
+ GthTestSelectorPrivate * priv;
+};
+
+struct _GthTestSelectorClass {
+ GtkHBoxClass parent_class;
+
+ void (*add_test) (GthTestSelector *selector);
+ void (*remove_test) (GthTestSelector *selector);
+};
+
+GType gth_test_selector_get_type (void);
+GtkWidget * gth_test_selector_new (void);
+GtkWidget * gth_test_selector_new_with_test (GthTest *test);
+void gth_test_selector_set_test (GthTestSelector *selector,
+ GthTest *test);
+GthTest * gth_test_selector_get_test (GthTestSelector *selector,
+ GError **error);
+void gth_test_selector_can_remove (GthTestSelector *selector,
+ gboolean value);
+
+G_END_DECLS
+
+#endif /* GTH_TEST_SELECTOR_H */
diff --git a/gthumb/gth-test-simple.c b/gthumb/gth-test-simple.c
new file mode 100644
index 0000000..7bca9fe
--- /dev/null
+++ b/gthumb/gth-test-simple.c
@@ -0,0 +1,1021 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2008 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <stdlib.h>
+#include <string.h>
+#include <glib/gi18n.h>
+#include <glib.h>
+#include <gtk/gtk.h>
+#include "dom.h"
+#include "glib-utils.h"
+#include "gth-enum-types.h"
+#include "gth-duplicable.h"
+#include "gth-test.h"
+#include "gth-test-simple.h"
+
+
+typedef struct {
+ char *name;
+ GthTestOp op;
+ gboolean negative;
+} GthOpData;
+
+
+GthOpData text_op_data[] = {
+ { N_("contains"), GTH_TEST_OP_CONTAINS, FALSE },
+ { N_("starts with"), GTH_TEST_OP_STARTS_WITH, FALSE },
+ { N_("ends with"), GTH_TEST_OP_ENDS_WITH, FALSE },
+ { N_("is"), GTH_TEST_OP_EQUAL, FALSE },
+ { N_("is not"), GTH_TEST_OP_EQUAL, TRUE },
+ { N_("does not contain"), GTH_TEST_OP_CONTAINS, TRUE },
+ { N_("matches"), GTH_TEST_OP_MATCHES, FALSE }
+};
+
+GthOpData int_op_data[] = {
+ { N_("is lower than"), GTH_TEST_OP_LOWER, FALSE },
+ { N_("is greater than"), GTH_TEST_OP_GREATER, FALSE },
+ { N_("is equal to"), GTH_TEST_OP_EQUAL, FALSE }
+};
+
+
+typedef struct {
+ char *name;
+ goffset size;
+} GthSizeData;
+
+
+static GthSizeData size_data[] = {
+ { N_("kB"), 1024 },
+ { N_("MB"), 1024*1024 },
+ { N_("GB"), 1024*1024*1024 }
+};
+
+
+enum {
+ PROP_0,
+ PROP_DATA_TYPE,
+ PROP_DATA_AS_STRING,
+ PROP_DATA_AS_INT,
+ PROP_DATA_AS_DATE,
+ PROP_GET_DATA,
+ PROP_OP,
+ PROP_NEGATIVE
+};
+
+
+struct _GthTestSimplePrivate
+{
+ GthTestDataType data_type;
+ union {
+ char *s;
+ gint64 i;
+ GDate *date;
+ } data;
+ GthTestGetData get_data;
+ GthTestOp op;
+ gboolean negative;
+ GPatternSpec *pattern;
+ gboolean has_focus;
+ GtkWidget *text_entry;
+ GtkWidget *text_op_combo_box;
+ GtkWidget *size_op_combo_box;
+ GtkWidget *size_combo_box;
+};
+
+
+static GthTestClass *parent_class = NULL;
+static DomDomizableIface* dom_domizable_parent_iface = NULL;
+static GthDuplicableIface *gth_duplicable_parent_iface = NULL;
+
+
+static void
+_gth_test_simple_free_data (GthTestSimple *test)
+{
+ switch (test->priv->data_type) {
+ case GTH_TEST_DATA_TYPE_STRING:
+ g_free (test->priv->data.s);
+ break;
+ case GTH_TEST_DATA_TYPE_DATE:
+ if (test->priv->data.date != NULL)
+ g_date_free (test->priv->data.date);
+ break;
+ default:
+ break;
+ }
+
+ if (test->priv->pattern != NULL) {
+ g_pattern_spec_free (test->priv->pattern);
+ test->priv->pattern = NULL;
+ }
+}
+
+
+static void
+_gth_test_simple_set_data_as_string (GthTestSimple *test,
+ const char *s)
+{
+ _gth_test_simple_free_data (test);
+ test->priv->data_type = GTH_TEST_DATA_TYPE_STRING;
+ test->priv->data.s = g_strdup (s);
+}
+
+
+static void
+_gth_test_simple_set_data_as_int (GthTestSimple *test,
+ guint64 i)
+{
+ _gth_test_simple_free_data (test);
+ test->priv->data_type = GTH_TEST_DATA_TYPE_INT;
+ test->priv->data.i = i;
+}
+
+
+static void
+_gth_test_simple_set_data_as_size (GthTestSimple *test,
+ guint64 i)
+{
+ _gth_test_simple_free_data (test);
+ test->priv->data_type = GTH_TEST_DATA_TYPE_SIZE;
+ test->priv->data.i = i;
+}
+
+
+static void
+gth_test_simple_finalize (GObject *object)
+{
+ GthTestSimple *test;
+
+ test = GTH_TEST_SIMPLE (object);
+
+ if (test->priv != NULL) {
+ _gth_test_simple_free_data (test);
+ if (test->priv->pattern != NULL)
+ g_pattern_spec_free (test->priv->pattern);
+ g_free (test->priv);
+ test->priv = NULL;
+ }
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+
+static GtkWidget *
+create_control_for_integer (GthTestSimple *test)
+{
+ return NULL;
+}
+
+
+static gboolean
+text_entry_focus_in_event_cb (GtkEntry *entry,
+ GdkEventFocus *event,
+ GthTestSimple *test)
+{
+ test->priv->has_focus = TRUE;
+ return FALSE;
+}
+
+
+static gboolean
+text_entry_focus_out_event_cb (GtkEntry *entry,
+ GdkEventFocus *event,
+ GthTestSimple *test)
+{
+ test->priv->has_focus = FALSE;
+ return FALSE;
+}
+
+
+static void
+size_text_entry_activate_cb (GtkEntry *entry,
+ GthTestSimple *test)
+{
+ gth_test_update_from_control (GTH_TEST (test), NULL);
+ gth_test_changed (GTH_TEST (test));
+}
+
+
+static void
+size_op_combo_box_changed_cb (GtkComboBox *combo_box,
+ GthTestSimple *test)
+{
+ gth_test_update_from_control (GTH_TEST (test), NULL);
+ gth_test_changed (GTH_TEST (test));
+}
+
+
+static void
+size_combo_box_changed_cb (GtkComboBox *combo_box,
+ GthTestSimple *test)
+{
+ gth_test_update_from_control (GTH_TEST (test), NULL);
+ gth_test_changed (GTH_TEST (test));
+}
+
+
+static GtkWidget *
+create_control_for_size (GthTestSimple *test)
+{
+ GtkWidget *control;
+ int i, op_idx, size_idx;
+ gboolean size_set = FALSE;
+
+ control = gtk_hbox_new (FALSE, 6);
+
+ /* text operation combo box */
+
+ test->priv->size_op_combo_box = gtk_combo_box_new_text ();
+ gtk_widget_show (test->priv->size_op_combo_box);
+
+ op_idx = 0;
+ for (i = 0; i < G_N_ELEMENTS (int_op_data); i++) {
+ gtk_combo_box_append_text (GTK_COMBO_BOX (test->priv->size_op_combo_box), _(int_op_data[i].name));
+ if ((int_op_data[i].op == test->priv->op) && (int_op_data[i].negative == test->priv->negative))
+ op_idx = i;
+ }
+ gtk_combo_box_set_active (GTK_COMBO_BOX (test->priv->size_op_combo_box), op_idx);
+
+ g_signal_connect (G_OBJECT (test->priv->size_op_combo_box),
+ "changed",
+ G_CALLBACK (size_op_combo_box_changed_cb),
+ test);
+
+ /* text entry */
+
+ test->priv->text_entry = gtk_entry_new ();
+ gtk_entry_set_width_chars (GTK_ENTRY (test->priv->text_entry), 6);
+ gtk_widget_show (test->priv->text_entry);
+
+ g_signal_connect (G_OBJECT (test->priv->text_entry),
+ "activate",
+ G_CALLBACK (size_text_entry_activate_cb),
+ test);
+ g_signal_connect (G_OBJECT (test->priv->text_entry),
+ "focus-in-event",
+ G_CALLBACK (text_entry_focus_in_event_cb),
+ test);
+ g_signal_connect (G_OBJECT (test->priv->text_entry),
+ "focus-out-event",
+ G_CALLBACK (text_entry_focus_out_event_cb),
+ test);
+
+ /* size combo box */
+
+ test->priv->size_combo_box = gtk_combo_box_new_text ();
+ gtk_widget_show (test->priv->size_combo_box);
+
+ size_idx = 0;
+ for (i = 0; i < G_N_ELEMENTS (size_data); i++) {
+ gtk_combo_box_append_text (GTK_COMBO_BOX (test->priv->size_combo_box), _(size_data[i].name));
+ if (! size_set && ((i == G_N_ELEMENTS (size_data) - 1) || (test->priv->data.i < size_data[i + 1].size))) {
+ char *value;
+
+ size_idx = i;
+ value = g_strdup_printf ("%.2f", (double) test->priv->data.i / size_data[i].size);
+ gtk_entry_set_text (GTK_ENTRY (test->priv->text_entry), value);
+ g_free (value);
+ size_set = TRUE;
+ }
+ }
+ gtk_combo_box_set_active (GTK_COMBO_BOX (test->priv->size_combo_box), size_idx);
+
+ g_signal_connect (G_OBJECT (test->priv->size_combo_box),
+ "changed",
+ G_CALLBACK (size_combo_box_changed_cb),
+ test);
+
+ /**/
+
+ gtk_box_pack_start (GTK_BOX (control), test->priv->size_op_combo_box, FALSE, FALSE, 0);
+ gtk_box_pack_start (GTK_BOX (control), test->priv->text_entry, FALSE, FALSE, 0);
+ gtk_box_pack_start (GTK_BOX (control), test->priv->size_combo_box, FALSE, FALSE, 0);
+
+ return control;
+}
+
+
+static void
+string_text_entry_activate_cb (GtkEntry *entry,
+ GthTestSimple *test)
+{
+ gth_test_update_from_control (GTH_TEST (test), NULL);
+ gth_test_changed (GTH_TEST (test));
+}
+
+
+static void
+text_op_combo_box_changed_cb (GtkComboBox *combo_box,
+ GthTestSimple *test)
+{
+ gth_test_update_from_control (GTH_TEST (test), NULL);
+ gth_test_changed (GTH_TEST (test));
+}
+
+
+static GtkWidget *
+create_control_for_string (GthTestSimple *test)
+{
+ GtkWidget *control;
+ int i, op_idx;
+
+ control = gtk_hbox_new (FALSE, 6);
+
+ /* text operation combo box */
+
+ test->priv->text_op_combo_box = gtk_combo_box_new_text ();
+ gtk_widget_show (test->priv->text_op_combo_box);
+
+ op_idx = 0;
+ for (i = 0; i < G_N_ELEMENTS (text_op_data); i++) {
+ gtk_combo_box_append_text (GTK_COMBO_BOX (test->priv->text_op_combo_box), _(text_op_data[i].name));
+ if ((text_op_data[i].op == test->priv->op) && (text_op_data[i].negative == test->priv->negative))
+ op_idx = i;
+ }
+ gtk_combo_box_set_active (GTK_COMBO_BOX (test->priv->text_op_combo_box), op_idx);
+
+ g_signal_connect (G_OBJECT (test->priv->text_op_combo_box),
+ "changed",
+ G_CALLBACK (text_op_combo_box_changed_cb),
+ test);
+
+ /* text entry */
+
+ test->priv->text_entry = gtk_entry_new ();
+ if (test->priv->data.s != NULL)
+ gtk_entry_set_text (GTK_ENTRY (test->priv->text_entry), test->priv->data.s);
+ gtk_widget_show (test->priv->text_entry);
+
+ g_signal_connect (G_OBJECT (test->priv->text_entry),
+ "activate",
+ G_CALLBACK (string_text_entry_activate_cb),
+ test);
+ g_signal_connect (G_OBJECT (test->priv->text_entry),
+ "focus-in-event",
+ G_CALLBACK (text_entry_focus_in_event_cb),
+ test);
+ g_signal_connect (G_OBJECT (test->priv->text_entry),
+ "focus-out-event",
+ G_CALLBACK (text_entry_focus_out_event_cb),
+ test);
+ gtk_widget_set_size_request (test->priv->text_entry, 150, -1);
+
+ /**/
+
+ gtk_box_pack_start (GTK_BOX (control), test->priv->text_op_combo_box, FALSE, FALSE, 0);
+ gtk_box_pack_start (GTK_BOX (control), test->priv->text_entry, FALSE, FALSE, 0);
+
+ return control;
+}
+
+
+static GtkWidget *
+create_control_for_date (GthTestSimple *test)
+{
+ return NULL;
+}
+
+
+static GtkWidget *
+gth_test_simple_real_create_control (GthTest *test)
+{
+ GthTestSimple *test_simple = (GthTestSimple *) test;
+ GtkWidget *control = NULL;
+
+ switch (test_simple->priv->data_type) {
+ case GTH_TEST_DATA_TYPE_NONE:
+ control = NULL;
+ break;
+ case GTH_TEST_DATA_TYPE_INT:
+ control = create_control_for_integer (GTH_TEST_SIMPLE (test));
+ break;
+ case GTH_TEST_DATA_TYPE_SIZE:
+ control = create_control_for_size (GTH_TEST_SIMPLE (test));
+ break;
+ case GTH_TEST_DATA_TYPE_STRING:
+ control = create_control_for_string (GTH_TEST_SIMPLE (test));
+ break;
+ case GTH_TEST_DATA_TYPE_DATE:
+ control = create_control_for_date (GTH_TEST_SIMPLE (test));
+ break;
+ }
+
+ return control;
+}
+
+
+static gboolean
+test_string (GthTestSimple *test,
+ const char *value)
+{
+ gboolean result = FALSE;
+ char *value2;
+
+ if ((test->priv->data.s == NULL) || (value == NULL))
+ return FALSE;
+
+ value2 = g_utf8_casefold (value, -1);
+
+ switch (test->priv->op) {
+ case GTH_TEST_OP_EQUAL:
+ result = g_utf8_collate (value2, test->priv->data.s) == 0;
+ break;
+ case GTH_TEST_OP_LOWER:
+ result = g_utf8_collate (value2, test->priv->data.s) < 0;
+ break;
+ case GTH_TEST_OP_GREATER:
+ result = g_utf8_collate (value2, test->priv->data.s) > 0;
+ break;
+ case GTH_TEST_OP_CONTAINS:
+ result = g_strstr_len (value2, -1, test->priv->data.s) != NULL;
+ break;
+ case GTH_TEST_OP_STARTS_WITH:
+ result = g_str_has_prefix (value2, test->priv->data.s);
+ break;
+ case GTH_TEST_OP_ENDS_WITH:
+ result = g_str_has_suffix (value2, test->priv->data.s);
+ break;
+ case GTH_TEST_OP_MATCHES:
+ if (test->priv->pattern == NULL)
+ test->priv->pattern = g_pattern_spec_new (test->priv->data.s);
+ result = g_pattern_match_string (test->priv->pattern, value2);
+ break;
+ default:
+ break;
+ }
+
+ g_free (value2);
+
+ return result;
+}
+
+
+static gboolean
+test_integer (GthTestSimple *test,
+ int value)
+{
+ gboolean result = FALSE;
+
+ switch (test->priv->op) {
+ case GTH_TEST_OP_EQUAL:
+ result = (value == test->priv->data.i);
+ break;
+ case GTH_TEST_OP_LOWER:
+ result = (value < test->priv->data.i);
+ break;
+ case GTH_TEST_OP_GREATER:
+ result = (value > test->priv->data.i);
+ break;
+ default:
+ break;
+ }
+
+ return result;
+}
+
+
+static gboolean
+test_date (GthTestSimple *test,
+ const GDate *date)
+{
+ gboolean result = FALSE;
+ int compare;
+
+ compare = g_date_compare (date, test->priv->data.date);
+
+ switch (test->priv->op) {
+ case GTH_TEST_OP_EQUAL:
+ result = (compare == 0);
+ break;
+ case GTH_TEST_OP_BEFORE:
+ result = (compare < 0);
+ break;
+ case GTH_TEST_OP_AFTER:
+ result = (compare > 0);
+ break;
+ default:
+ break;
+ }
+
+ return result;
+}
+
+
+static gconstpointer
+_gth_test_simple_get_pointer (GthTestSimple *test,
+ GthFileData *file)
+{
+ gconstpointer value;
+
+ test->priv->get_data (GTH_TEST (test), file, &value);
+
+ return value;
+}
+
+
+static gint64
+_gth_test_simple_get_int (GthTestSimple *test,
+ GthFileData *file)
+{
+ return test->priv->get_data (GTH_TEST (test), file, NULL);
+}
+
+
+static GthMatch
+gth_test_simple_real_match (GthTest *test,
+ GthFileData *file)
+{
+ GthTestSimple *test_simple;
+ gboolean result = FALSE;
+
+ test_simple = GTH_TEST_SIMPLE (test);
+
+ switch (test_simple->priv->data_type) {
+ case GTH_TEST_DATA_TYPE_NONE:
+ result = _gth_test_simple_get_int (test_simple, file) == TRUE;
+ break;
+ case GTH_TEST_DATA_TYPE_INT:
+ case GTH_TEST_DATA_TYPE_SIZE:
+ result = test_integer (test_simple, _gth_test_simple_get_int (test_simple, file));
+ break;
+ case GTH_TEST_DATA_TYPE_STRING:
+ result = test_string (test_simple, _gth_test_simple_get_pointer (test_simple, file));
+ break;
+ case GTH_TEST_DATA_TYPE_DATE:
+ result = test_date (test_simple, _gth_test_simple_get_pointer (test_simple, file));
+ break;
+ }
+
+ if (test_simple->priv->negative)
+ result = ! result;
+
+ return result ? GTH_MATCH_YES : GTH_MATCH_NO;
+}
+
+
+static DomElement*
+gth_test_simple_real_create_element (DomDomizable *base,
+ DomDocument *doc)
+{
+ GthTestSimple *self;
+ DomElement *element;
+ char *value;
+
+ g_return_val_if_fail (DOM_IS_DOCUMENT (doc), NULL);
+
+ self = GTH_TEST_SIMPLE (base);
+
+ element = dom_document_create_element (doc, "test",
+ "id", gth_test_get_id (GTH_TEST (self)),
+ NULL);
+
+ if (! gth_test_is_visible (GTH_TEST (self)))
+ dom_element_set_attribute (element, "display", "none");
+
+ switch (self->priv->data_type) {
+ case GTH_TEST_DATA_TYPE_NONE:
+ break;
+ case GTH_TEST_DATA_TYPE_INT:
+ case GTH_TEST_DATA_TYPE_SIZE:
+ dom_element_set_attribute (element, "op", _g_enum_type_get_value (GTH_TYPE_TEST_OP, self->priv->op)->value_nick);
+ if (self->priv->op != GTH_TEST_OP_NONE) {
+ if (self->priv->negative)
+ dom_element_set_attribute (element, "negative", self->priv->negative ? "true" : "false");
+ value = g_strdup_printf ("%lld", self->priv->data.i);
+ dom_element_set_attribute (element, "value", value);
+ g_free (value);
+ }
+ break;
+ case GTH_TEST_DATA_TYPE_STRING:
+ dom_element_set_attribute (element, "op", _g_enum_type_get_value (GTH_TYPE_TEST_OP, self->priv->op)->value_nick);
+ if (self->priv->op != GTH_TEST_OP_NONE) {
+ if (self->priv->negative)
+ dom_element_set_attribute (element, "negative", self->priv->negative ? "true" : "false");
+ if (self->priv->data.s != NULL)
+ dom_element_set_attribute (element, "value", self->priv->data.s);
+ }
+ break;
+ case GTH_TEST_DATA_TYPE_DATE:
+ /* TODO */
+ break;
+ }
+
+ return element;
+}
+
+
+static void
+gth_test_simple_real_load_from_element (DomDomizable *base,
+ DomElement *element)
+{
+ GthTestSimple *self;
+ const char *value;
+
+ g_return_if_fail (DOM_IS_ELEMENT (element));
+
+ self = GTH_TEST_SIMPLE (base);
+
+ g_object_set (self, "visible", (g_strcmp0 (dom_element_get_attribute (element, "display"), "none") != 0), NULL);
+
+ value = dom_element_get_attribute (element, "op");
+ if (value != NULL)
+ self->priv->op = _g_enum_type_get_value_by_nick (GTH_TYPE_TEST_OP, value)->value;
+
+ self->priv->negative = g_strcmp0 (dom_element_get_attribute (element, "negative"), "true") == 0;
+
+ value = dom_element_get_attribute (element, "value");
+ if (value == NULL)
+ return;
+
+ switch (self->priv->data_type) {
+ case GTH_TEST_DATA_TYPE_INT:
+ _gth_test_simple_set_data_as_int (self, atol (value));
+ case GTH_TEST_DATA_TYPE_SIZE:
+ _gth_test_simple_set_data_as_size (self, atol (value));
+ break;
+ case GTH_TEST_DATA_TYPE_STRING:
+ _gth_test_simple_set_data_as_string (self, value);
+ break;
+ default:
+ break;
+ }
+}
+
+
+static gboolean
+update_from_control_for_integer (GthTestSimple *self,
+ GError **error)
+{
+ GthOpData op_data;
+
+ op_data = int_op_data[gtk_combo_box_get_active (GTK_COMBO_BOX (self->priv->text_op_combo_box))];
+ self->priv->op = op_data.op;
+ self->priv->negative = op_data.negative;
+ _gth_test_simple_set_data_as_int (self, atol (gtk_entry_get_text (GTK_ENTRY (self->priv->text_entry))));
+
+ if (self->priv->data.i == 0) {
+ if (error != NULL)
+ *error = g_error_new (GTH_TEST_ERROR, 0, _("The test definition is incomplete"));
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+
+static gboolean
+update_from_control_for_size (GthTestSimple *self,
+ GError **error)
+{
+ GthOpData op_data;
+ double value;
+ guint64 size;
+
+ op_data = int_op_data[gtk_combo_box_get_active (GTK_COMBO_BOX (self->priv->size_op_combo_box))];
+ self->priv->op = op_data.op;
+ self->priv->negative = op_data.negative;
+
+ value = atol (gtk_entry_get_text (GTK_ENTRY (self->priv->text_entry)));
+ size = value * size_data[gtk_combo_box_get_active (GTK_COMBO_BOX (self->priv->size_combo_box))].size;
+ _gth_test_simple_set_data_as_size (self, size);
+
+ if ((self->priv->data.i == 0) && (self->priv->op == GTH_TEST_OP_LOWER)) {
+ if (error != NULL)
+ *error = g_error_new (GTH_TEST_ERROR, 0, _("The test definition is incomplete"));
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+
+static gboolean
+update_from_control_for_string (GthTestSimple *self,
+ GError **error)
+{
+ GthOpData op_data;
+
+ op_data = text_op_data[gtk_combo_box_get_active (GTK_COMBO_BOX (self->priv->text_op_combo_box))];
+ self->priv->op = op_data.op;
+ self->priv->negative = op_data.negative;
+ _gth_test_simple_set_data_as_string (self, gtk_entry_get_text (GTK_ENTRY (self->priv->text_entry)));
+
+ if (g_strcmp0 (self->priv->data.s, "") == 0) {
+ if (error != NULL)
+ *error = g_error_new (GTH_TEST_ERROR, 0, _("The test definition is incomplete"));
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+
+static gboolean
+update_from_control_for_date (GthTestSimple *self,
+ GError **error)
+{
+ /* TODO */
+ return TRUE;
+}
+
+
+static gboolean
+gth_test_simple_real_update_from_control (GthTest *base,
+ GError **error)
+{
+ GthTestSimple *self;
+ gboolean retval;
+
+ self = GTH_TEST_SIMPLE (base);
+
+ switch (self->priv->data_type) {
+ case GTH_TEST_DATA_TYPE_NONE:
+ retval = TRUE;
+ break;
+ case GTH_TEST_DATA_TYPE_INT:
+ retval = update_from_control_for_integer (self, error);
+ break;
+ case GTH_TEST_DATA_TYPE_SIZE:
+ retval = update_from_control_for_size (self, error);
+ break;
+ case GTH_TEST_DATA_TYPE_STRING:
+ retval = update_from_control_for_string (self, error);
+ break;
+ case GTH_TEST_DATA_TYPE_DATE:
+ retval = update_from_control_for_date (self, error);
+ break;
+ }
+
+ return retval;
+}
+
+
+static GObject *
+gth_test_simple_real_duplicate (GthDuplicable *duplicable)
+{
+ GthTestSimple *test = GTH_TEST_SIMPLE (duplicable);
+ GthTestSimple *new_test;
+
+ new_test = g_object_new (GTH_TYPE_TEST_SIMPLE,
+ "id", gth_test_get_id (GTH_TEST (test)),
+ "display-name", gth_test_get_display_name (GTH_TEST (test)),
+ "visible", gth_test_is_visible (GTH_TEST (test)),
+ NULL);
+ switch (test->priv->data_type) {
+ case GTH_TEST_DATA_TYPE_NONE:
+ break;
+ case GTH_TEST_DATA_TYPE_INT:
+ _gth_test_simple_set_data_as_int (new_test, test->priv->data.i);
+ break;
+ case GTH_TEST_DATA_TYPE_SIZE:
+ _gth_test_simple_set_data_as_size (new_test, test->priv->data.i);
+ break;
+ case GTH_TEST_DATA_TYPE_STRING:
+ _gth_test_simple_set_data_as_string (new_test, test->priv->data.s);
+ break;
+ case GTH_TEST_DATA_TYPE_DATE:
+ break;
+ }
+ new_test->priv->get_data = test->priv->get_data;
+ new_test->priv->op = test->priv->op;
+ new_test->priv->negative = test->priv->negative;
+
+ return (GObject *) new_test;
+}
+
+
+static void
+gth_test_simple_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GthTestSimple *test;
+
+ test = GTH_TEST_SIMPLE (object);
+
+ switch (property_id) {
+ case PROP_DATA_TYPE:
+ test->priv->data_type = g_value_get_enum (value);
+ break;
+ case PROP_DATA_AS_STRING:
+ _gth_test_simple_free_data (test);
+ test->priv->data.s = g_value_dup_string (value);
+ break;
+ case PROP_DATA_AS_INT:
+ _gth_test_simple_free_data (test);
+ test->priv->data.i = g_value_get_int (value);
+ break;
+ case PROP_DATA_AS_DATE:
+ _gth_test_simple_free_data (test);
+ test->priv->data.date = g_value_dup_boxed (value);
+ break;
+ case PROP_GET_DATA:
+ test->priv->get_data = g_value_get_pointer (value);
+ break;
+ case PROP_OP:
+ test->priv->op = g_value_get_enum (value);
+ break;
+ case PROP_NEGATIVE:
+ test->priv->negative = g_value_get_boolean (value);
+ break;
+ default:
+ break;
+ }
+}
+
+
+static void
+gth_test_simple_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GthTestSimple *test;
+
+ test = GTH_TEST_SIMPLE (object);
+
+ switch (property_id) {
+ case PROP_DATA_TYPE:
+ g_value_set_enum (value, test->priv->data_type);
+ break;
+ case PROP_DATA_AS_STRING:
+ g_value_set_string (value, test->priv->data.s);
+ break;
+ case PROP_DATA_AS_INT:
+ g_value_set_int (value, test->priv->data.i);
+ break;
+ case PROP_DATA_AS_DATE:
+ g_value_set_boxed (value, test->priv->data.date);
+ break;
+ case PROP_GET_DATA:
+ g_value_set_pointer (value, test->priv->get_data);
+ break;
+ case PROP_OP:
+ g_value_set_enum (value, test->priv->op);
+ break;
+ case PROP_NEGATIVE:
+ g_value_set_boolean (value, test->priv->negative);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+
+static void
+gth_test_simple_class_init (GthTestSimpleClass *class)
+{
+ GObjectClass *object_class;
+ GthTestClass *test_class;
+
+ parent_class = g_type_class_peek_parent (class);
+ object_class = (GObjectClass*) class;
+ test_class = (GthTestClass *) class;
+
+ object_class->set_property = gth_test_simple_set_property;
+ object_class->get_property = gth_test_simple_get_property;
+ object_class->finalize = gth_test_simple_finalize;
+ test_class->create_control = gth_test_simple_real_create_control;
+ test_class->update_from_control = gth_test_simple_real_update_from_control;
+ test_class->match = gth_test_simple_real_match;
+
+ /* properties */
+
+ g_object_class_install_property (object_class,
+ PROP_DATA_TYPE,
+ g_param_spec_enum ("data-type",
+ "Data type",
+ "The type of the data to test",
+ GTH_TYPE_TEST_DATA_TYPE,
+ GTH_TEST_DATA_TYPE_NONE,
+ G_PARAM_READWRITE));
+ g_object_class_install_property (object_class,
+ PROP_DATA_AS_STRING,
+ g_param_spec_string ("data-as-string",
+ "Data as string",
+ "The data value as a string",
+ NULL,
+ G_PARAM_READWRITE));
+ g_object_class_install_property (object_class,
+ PROP_DATA_AS_INT,
+ g_param_spec_int ("data-as-int",
+ "Data as integer",
+ "The data value as an integer",
+ G_MININT,
+ G_MAXINT,
+ 0,
+ G_PARAM_READWRITE));
+ g_object_class_install_property (object_class,
+ PROP_DATA_AS_DATE,
+ g_param_spec_boxed ("data-as-date",
+ "Data as date",
+ "The data value as a date",
+ G_TYPE_DATE,
+ G_PARAM_READWRITE));
+ g_object_class_install_property (object_class,
+ PROP_GET_DATA,
+ g_param_spec_pointer ("get-data-func",
+ "Get Data Function",
+ "The function used to get the value to test",
+ G_PARAM_READWRITE));
+ g_object_class_install_property (object_class,
+ PROP_OP,
+ g_param_spec_enum ("op",
+ "Operation",
+ "The operation to use to compare the values",
+ GTH_TYPE_TEST_OP,
+ GTH_TEST_OP_NONE,
+ G_PARAM_READWRITE));
+ g_object_class_install_property (object_class,
+ PROP_NEGATIVE,
+ g_param_spec_boolean ("negative",
+ "Negative",
+ "Whether to negate the test result",
+ FALSE,
+ G_PARAM_READWRITE));
+}
+
+
+static void
+gth_test_simple_dom_domizable_interface_init (DomDomizableIface * iface)
+{
+ dom_domizable_parent_iface = g_type_interface_peek_parent (iface);
+ iface->create_element = gth_test_simple_real_create_element;
+ iface->load_from_element = gth_test_simple_real_load_from_element;
+}
+
+
+static void
+gth_test_simple_gth_duplicable_interface_init (GthDuplicableIface *iface)
+{
+ gth_duplicable_parent_iface = g_type_interface_peek_parent (iface);
+ iface->duplicate = gth_test_simple_real_duplicate;
+}
+
+
+static void
+gth_test_simple_init (GthTestSimple *test)
+{
+ test->priv = g_new0 (GthTestSimplePrivate, 1);
+}
+
+
+GType
+gth_test_simple_get_type (void)
+{
+ static GType type = 0;
+
+ if (! type) {
+ GTypeInfo type_info = {
+ sizeof (GthTestSimpleClass),
+ NULL,
+ NULL,
+ (GClassInitFunc) gth_test_simple_class_init,
+ NULL,
+ NULL,
+ sizeof (GthTestSimple),
+ 0,
+ (GInstanceInitFunc) gth_test_simple_init
+ };
+ static const GInterfaceInfo dom_domizable_info = {
+ (GInterfaceInitFunc) gth_test_simple_dom_domizable_interface_init,
+ (GInterfaceFinalizeFunc) NULL,
+ NULL
+ };
+ static const GInterfaceInfo gth_duplicable_info = {
+ (GInterfaceInitFunc) gth_test_simple_gth_duplicable_interface_init,
+ (GInterfaceFinalizeFunc) NULL,
+ NULL
+ };
+
+ type = g_type_register_static (GTH_TYPE_TEST,
+ "GthTestSimple",
+ &type_info,
+ 0);
+ g_type_add_interface_static (type, DOM_TYPE_DOMIZABLE, &dom_domizable_info);
+ g_type_add_interface_static (type, GTH_TYPE_DUPLICABLE, >h_duplicable_info);
+ }
+
+ return type;
+}
diff --git a/gthumb/gth-test-simple.h b/gthumb/gth-test-simple.h
new file mode 100644
index 0000000..2ee1c9a
--- /dev/null
+++ b/gthumb/gth-test-simple.h
@@ -0,0 +1,69 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2008 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GTH_TEST_SIMPLE_H
+#define GTH_TEST_SIMPLE_H
+
+#include <glib-object.h>
+#include <gtk/gtk.h>
+#include "gth-file-data.h"
+#include "gth-test.h"
+
+G_BEGIN_DECLS
+
+#define GTH_TYPE_TEST_SIMPLE (gth_test_simple_get_type ())
+#define GTH_TEST_SIMPLE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GTH_TYPE_TEST_SIMPLE, GthTestSimple))
+#define GTH_TEST_SIMPLE_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), GTH_TYPE_TEST_SIMPLE, GthTestSimpleClass))
+#define GTH_IS_TEST_SIMPLE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GTH_TYPE_TEST_SIMPLE))
+#define GTH_IS_TEST_SIMPLE_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GTH_TYPE_TEST_SIMPLE))
+#define GTH_TEST_SIMPLE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS((o), GTH_TYPE_TEST_SIMPLE, GthTestSimpleClass))
+
+typedef struct _GthTestSimple GthTestSimple;
+typedef struct _GthTestSimplePrivate GthTestSimplePrivate;
+typedef struct _GthTestSimpleClass GthTestSimpleClass;
+
+typedef gint64 (*GthTestGetData) (GthTest *test, GthFileData *file, gconstpointer *data);
+
+typedef enum {
+ GTH_TEST_DATA_TYPE_NONE,
+ GTH_TEST_DATA_TYPE_INT,
+ GTH_TEST_DATA_TYPE_SIZE,
+ GTH_TEST_DATA_TYPE_STRING,
+ GTH_TEST_DATA_TYPE_DATE
+} GthTestDataType;
+
+struct _GthTestSimple
+{
+ GthTest __parent;
+ GthTestSimplePrivate *priv;
+};
+
+struct _GthTestSimpleClass
+{
+ GthTestClass __parent_class;
+};
+
+GType gth_test_simple_get_type (void) G_GNUC_CONST;
+
+G_END_DECLS
+
+#endif /* GTH_TEST_SIMPLE_H */
diff --git a/gthumb/gth-test.c b/gthumb/gth-test.c
new file mode 100644
index 0000000..8c80080
--- /dev/null
+++ b/gthumb/gth-test.c
@@ -0,0 +1,426 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2008 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <string.h>
+#include <glib/gi18n.h>
+#include <glib.h>
+#include <gtk/gtk.h>
+#include "gth-duplicable.h"
+#include "gth-test.h"
+#include "glib-utils.h"
+
+
+/* Properties */
+enum {
+ PROP_0,
+ PROP_ID,
+ PROP_DISPLAY_NAME,
+ PROP_VISIBLE
+};
+
+/* Signals */
+enum {
+ CHANGED,
+ LAST_SIGNAL
+};
+
+struct _GthTestPrivate
+{
+ char *id;
+ char *display_name;
+ gboolean visible;
+};
+
+
+static GObjectClass *parent_class = NULL;
+static GthDuplicableIface *gth_duplicable_parent_iface = NULL;
+static guint gth_test_signals[LAST_SIGNAL] = { 0 };
+
+
+GQuark
+gth_test_error_quark (void)
+{
+ return g_quark_from_static_string ("gth-test-error-quark");
+}
+
+
+static void
+gth_test_finalize (GObject *object)
+{
+ GthTest *test;
+
+ test = GTH_TEST (object);
+
+ if (test->priv != NULL) {
+ g_free (test->priv->id);
+ g_free (test->priv->display_name);
+ g_free (test->files);
+ g_free (test->priv);
+ test->priv = NULL;
+ }
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+
+static GtkWidget *
+base_create_control (GthTest *test)
+{
+ return NULL;
+}
+
+
+static gboolean
+base_update_from_control (GthTest *test,
+ GError **error)
+{
+ return TRUE;
+}
+
+
+static void
+base_reset (GthTest *test)
+{
+}
+
+
+static GthMatch
+base_match (GthTest *test,
+ GthFileData *fdata)
+{
+ return GTH_MATCH_YES;
+}
+
+
+void
+base_set_file_list (GthTest *test,
+ GList *files)
+{
+ GList *scan;
+ int i;
+
+ test->n_files = g_list_length (files);
+
+ g_free (test->files);
+ test->files = g_malloc (sizeof (GthFileData*) * (test->n_files + 1));
+
+ for (scan = files, i = 0; scan; scan = scan->next)
+ test->files[i++] = scan->data;
+ test->files[i++] = NULL;
+
+ test->iterator = 0;
+}
+
+
+GthFileData *
+base_get_next (GthTest *test)
+{
+ GthFileData *file = NULL;
+ GthMatch match = GTH_MATCH_NO;
+
+ if (test->files == NULL)
+ return NULL;
+
+ while (match == GTH_MATCH_NO) {
+ file = test->files[test->iterator];
+ if (file != NULL) {
+ match = gth_test_match (test, file);
+ test->iterator++;
+ }
+ else
+ match = GTH_MATCH_LIMIT_REACHED;
+ }
+
+ if (match != GTH_MATCH_YES)
+ file = NULL;
+
+ if (file == NULL) {
+ g_free (test->files);
+ test->files = NULL;
+ }
+
+ return file;
+}
+
+
+GObject *
+gth_test_real_duplicate (GthDuplicable *duplicable)
+{
+ GthTest *test = GTH_TEST (duplicable);
+ GthTest *new_test;
+
+ new_test = g_object_new (GTH_TYPE_TEST,
+ "id", gth_test_get_id (test),
+ "display-name", gth_test_get_display_name (test),
+ "visible", gth_test_is_visible (test),
+ NULL);
+
+ return (GObject *) new_test;
+}
+
+
+static void
+gth_test_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GthTest *test;
+
+ test = GTH_TEST (object);
+
+ switch (property_id) {
+ case PROP_ID:
+ g_free (test->priv->id);
+ test->priv->id = g_value_dup_string (value);
+ if (test->priv->id == NULL)
+ test->priv->id = g_strdup ("");
+ break;
+ case PROP_DISPLAY_NAME:
+ g_free (test->priv->display_name);
+ test->priv->display_name = g_value_dup_string (value);
+ if (test->priv->display_name == NULL)
+ test->priv->display_name = g_strdup ("");
+ break;
+ case PROP_VISIBLE:
+ test->priv->visible = g_value_get_boolean (value);
+ break;
+ default:
+ break;
+ }
+}
+
+
+static void
+gth_test_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GthTest *test;
+
+ test = GTH_TEST (object);
+
+ switch (property_id) {
+ case PROP_ID:
+ g_value_set_string (value, test->priv->id);
+ break;
+ case PROP_DISPLAY_NAME:
+ g_value_set_string (value, test->priv->display_name);
+ break;
+ case PROP_VISIBLE:
+ g_value_set_boolean (value, test->priv->visible);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+
+static void
+gth_test_class_init (GthTestClass *class)
+{
+ GObjectClass *object_class;
+
+ parent_class = g_type_class_peek_parent (class);
+ object_class = (GObjectClass*) class;
+
+ object_class->set_property = gth_test_set_property;
+ object_class->get_property = gth_test_get_property;
+ object_class->finalize = gth_test_finalize;
+
+ class->create_control = base_create_control;
+ class->update_from_control = base_update_from_control;
+ class->reset = base_reset;
+ class->match = base_match;
+ class->set_file_list = base_set_file_list;
+ class->get_next = base_get_next;
+
+ /* properties */
+
+ g_object_class_install_property (object_class,
+ PROP_ID,
+ g_param_spec_string ("id",
+ "ID",
+ "The object id",
+ NULL,
+ G_PARAM_READWRITE));
+ g_object_class_install_property (object_class,
+ PROP_DISPLAY_NAME,
+ g_param_spec_string ("display-name",
+ "Display name",
+ "The user visible name",
+ NULL,
+ G_PARAM_READWRITE));
+ g_object_class_install_property (object_class,
+ PROP_VISIBLE,
+ g_param_spec_boolean ("visible",
+ "Visible",
+ "Whether this test should be visible in the filter bar",
+ FALSE,
+ G_PARAM_READWRITE));
+
+ /* signals */
+
+ gth_test_signals[CHANGED] =
+ g_signal_new ("changed",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GthTestClass, changed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+}
+
+
+static void
+gth_test_gth_duplicable_interface_init (GthDuplicableIface *iface)
+{
+ gth_duplicable_parent_iface = g_type_interface_peek_parent (iface);
+ iface->duplicate = gth_test_real_duplicate;
+}
+
+
+static void
+gth_test_init (GthTest *test)
+{
+ test->priv = g_new0 (GthTestPrivate, 1);
+ test->priv->id = g_strdup ("");
+ test->priv->display_name = g_strdup ("");
+ test->priv->visible = FALSE;
+}
+
+
+GType
+gth_test_get_type (void)
+{
+ static GType type = 0;
+
+ if (! type) {
+ GTypeInfo type_info = {
+ sizeof (GthTestClass),
+ NULL,
+ NULL,
+ (GClassInitFunc) gth_test_class_init,
+ NULL,
+ NULL,
+ sizeof (GthTest),
+ 0,
+ (GInstanceInitFunc) gth_test_init
+ };
+ static const GInterfaceInfo gth_duplicable_info = {
+ (GInterfaceInitFunc) gth_test_gth_duplicable_interface_init,
+ (GInterfaceFinalizeFunc) NULL,
+ NULL
+ };
+
+ type = g_type_register_static (G_TYPE_OBJECT,
+ "GthTest",
+ &type_info,
+ 0);
+ g_type_add_interface_static (type, GTH_TYPE_DUPLICABLE, >h_duplicable_info);
+ }
+
+ return type;
+}
+
+
+GthTest *
+gth_test_new (void)
+{
+ return (GthTest*) g_object_new (GTH_TYPE_TEST, NULL);
+}
+
+
+const char *
+gth_test_get_id (GthTest *test)
+{
+ return test->priv->id;
+}
+
+
+const char *
+gth_test_get_display_name (GthTest *test)
+{
+ return test->priv->display_name;
+}
+
+
+gboolean
+gth_test_is_visible (GthTest *test)
+{
+ return test->priv->visible;
+}
+
+
+GtkWidget *
+gth_test_create_control (GthTest *test)
+{
+ return GTH_TEST_GET_CLASS (test)->create_control (test);
+}
+
+
+gboolean
+gth_test_update_from_control (GthTest *test,
+ GError **error)
+{
+ return GTH_TEST_GET_CLASS (test)->update_from_control (test, error);
+}
+
+
+void
+gth_test_changed (GthTest *test)
+{
+ g_signal_emit (test, gth_test_signals[CHANGED], 0);
+}
+
+
+void
+gth_test_reset (GthTest *test)
+{
+ return GTH_TEST_GET_CLASS (test)->reset (test);
+}
+
+
+GthMatch
+gth_test_match (GthTest *test,
+ GthFileData *fdata)
+{
+ return GTH_TEST_GET_CLASS (test)->match (test, fdata);
+}
+
+void
+gth_test_set_file_list (GthTest *test,
+ GList *files)
+{
+ GTH_TEST_GET_CLASS (test)->set_file_list (test, files);
+}
+
+
+GthFileData *
+gth_test_get_next (GthTest *test)
+{
+ return GTH_TEST_GET_CLASS (test)->get_next (test);
+}
diff --git a/gthumb/gth-test.h b/gthumb/gth-test.h
new file mode 100644
index 0000000..6ff35ff
--- /dev/null
+++ b/gthumb/gth-test.h
@@ -0,0 +1,117 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2008-2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GTH_TEST_H
+#define GTH_TEST_H
+
+#include <glib-object.h>
+#include <gtk/gtk.h>
+#include "gth-file-data.h"
+
+G_BEGIN_DECLS
+
+#define GTH_TEST_ERROR gth_test_error_quark ()
+
+#define GTH_TYPE_TEST (gth_test_get_type ())
+#define GTH_TEST(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GTH_TYPE_TEST, GthTest))
+#define GTH_TEST_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), GTH_TYPE_TEST, GthTestClass))
+#define GTH_IS_TEST(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GTH_TYPE_TEST))
+#define GTH_IS_TEST_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GTH_TYPE_TEST))
+#define GTH_TEST_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS((o), GTH_TYPE_TEST, GthTestClass))
+
+typedef struct _GthTest GthTest;
+typedef struct _GthTestPrivate GthTestPrivate;
+typedef struct _GthTestClass GthTestClass;
+
+typedef enum {
+ GTH_MATCH_NO = 0,
+ GTH_MATCH_YES = 1,
+ GTH_MATCH_LIMIT_REACHED = 2
+} GthMatch;
+
+typedef enum {
+ GTH_TEST_OP_NONE,
+ GTH_TEST_OP_EQUAL,
+ GTH_TEST_OP_LOWER,
+ GTH_TEST_OP_GREATER,
+ GTH_TEST_OP_CONTAINS,
+ GTH_TEST_OP_CONTAINS_ALL,
+ GTH_TEST_OP_STARTS_WITH,
+ GTH_TEST_OP_ENDS_WITH,
+ GTH_TEST_OP_MATCHES,
+ GTH_TEST_OP_BEFORE,
+ GTH_TEST_OP_AFTER
+} GthTestOp;
+
+struct _GthTest
+{
+ GObject __parent;
+ GthTestPrivate *priv;
+
+ /*< protected >*/
+
+ GthFileData **files;
+ int n_files;
+ int iterator;
+};
+
+struct _GthTestClass
+{
+ GObjectClass __parent_class;
+
+ /*< signals >*/
+
+ void (*changed) (GthTest *test);
+
+ /*< virtual functions >*/
+
+ GtkWidget * (*create_control) (GthTest *test);
+ gboolean (*update_from_control) (GthTest *test,
+ GError **error);
+ void (*reset) (GthTest *test);
+ GthMatch (*match) (GthTest *test,
+ GthFileData *fdata);
+ void (*set_file_list) (GthTest *test,
+ GList *files);
+ GthFileData * (*get_next) (GthTest *test);
+};
+
+GQuark gth_test_error_quark (void);
+
+GType gth_test_get_type (void) G_GNUC_CONST;
+GthTest * gth_test_new (void);
+const char * gth_test_get_id (GthTest *test);
+const char * gth_test_get_display_name (GthTest *test);
+gboolean gth_test_is_visible (GthTest *test);
+GtkWidget * gth_test_create_control (GthTest *test);
+gboolean gth_test_update_from_control (GthTest *test,
+ GError **error);
+void gth_test_changed (GthTest *test);
+GthMatch gth_test_match (GthTest *test,
+ GthFileData *fdata);
+void gth_test_set_file_list (GthTest *test,
+ GList *files);
+GthFileData * gth_test_get_next (GthTest *test);
+
+G_END_DECLS
+
+#endif /* GTH_TEST_H */
diff --git a/gthumb/gth-thumb-loader.c b/gthumb/gth-thumb-loader.c
new file mode 100644
index 0000000..1c080bf
--- /dev/null
+++ b/gthumb/gth-thumb-loader.c
@@ -0,0 +1,665 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2001-2008 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#define GDK_PIXBUF_ENABLE_BACKEND
+#include <gtk/gtk.h>
+#include <gdk-pixbuf/gdk-pixbuf-animation.h>
+#include "gio-utils.h"
+#include "glib-utils.h"
+#define GNOME_DESKTOP_USE_UNSTABLE_API
+#include "gnome-desktop-thumbnail.h"
+#include "gth-image-loader.h"
+#include "gth-main.h"
+#include "gth-thumb-loader.h"
+#include "gthumb-error.h"
+#include "pixbuf-io.h"
+#include "pixbuf-utils.h"
+#include "typedefs.h"
+
+#define DEFAULT_MAX_FILE_SIZE (4*1024*1024)
+#define THUMBNAIL_LARGE_SIZE 256
+#define THUMBNAIL_NORMAL_SIZE 128
+#define THUMBNAIL_DIR_PERMISSIONS 0700
+
+struct _GthThumbLoaderPrivateData
+{
+ GthFileData *file;
+ GthImageLoader *iloader;
+ GdkPixbuf *pixbuf; /* Contains the final (scaled
+ * if necessary) image when
+ * done. */
+ guint use_cache : 1;
+ guint from_cache : 1;
+ guint save_thumbnails : 1;
+ int max_w;
+ int max_h;
+ int cache_max_w;
+ int cache_max_h;
+ goffset max_file_size; /* If the file size is greater
+ * than this the thumbnail
+ * will not be created, for
+ * functionality reasons. */
+
+ GnomeDesktopThumbnailSize thumb_size;
+ GnomeDesktopThumbnailFactory *thumb_factory;
+};
+
+
+enum {
+ READY,
+ LAST_SIGNAL
+};
+
+
+static GObjectClass *parent_class = NULL;
+static guint gth_thumb_loader_signals[LAST_SIGNAL] = { 0 };
+
+
+static void
+gth_thumb_loader_finalize (GObject *object)
+{
+ GthThumbLoader *tloader;
+
+ g_return_if_fail (object != NULL);
+ g_return_if_fail (GTH_IS_THUMB_LOADER (object));
+
+ tloader = GTH_THUMB_LOADER (object);
+
+ if (tloader->priv != NULL) {
+ if (tloader->priv->pixbuf != NULL)
+ g_object_unref (tloader->priv->pixbuf);
+ g_object_unref (tloader->priv->iloader);
+ g_object_unref (tloader->priv->file);
+ g_free (tloader->priv);
+ tloader->priv = NULL;
+ }
+
+ /* Chain up */
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+
+static void
+gth_thumb_loader_class_init (GthThumbLoaderClass *class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+ parent_class = g_type_class_peek_parent (class);
+
+ object_class->finalize = gth_thumb_loader_finalize;
+
+ gth_thumb_loader_signals[READY] =
+ g_signal_new ("ready",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GthThumbLoaderClass, ready),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__POINTER,
+ G_TYPE_NONE,
+ 1,
+ G_TYPE_POINTER);
+}
+
+
+static void
+gth_thumb_loader_init (GthThumbLoader *tloader)
+{
+ tloader->priv = g_new0 (GthThumbLoaderPrivateData, 1);
+ tloader->priv->file = NULL;
+ tloader->priv->pixbuf = NULL;
+ tloader->priv->use_cache = TRUE;
+ tloader->priv->save_thumbnails = TRUE;
+ tloader->priv->from_cache = FALSE;
+ tloader->priv->max_file_size = 0;
+}
+
+
+GType
+gth_thumb_loader_get_type (void)
+{
+ static GType type = 0;
+
+ if (! type) {
+ GTypeInfo type_info = {
+ sizeof (GthThumbLoaderClass),
+ NULL,
+ NULL,
+ (GClassInitFunc) gth_thumb_loader_class_init,
+ NULL,
+ NULL,
+ sizeof (GthThumbLoader),
+ 0,
+ (GInstanceInitFunc) gth_thumb_loader_init
+ };
+
+ type = g_type_register_static (G_TYPE_OBJECT,
+ "GthThumbLoader",
+ &type_info,
+ 0);
+ }
+
+ return type;
+}
+
+
+static int
+normalize_thumb (int *width,
+ int *height,
+ int max_width,
+ int max_height,
+ int cache_max_w,
+ int cache_max_h)
+{
+ gboolean modified;
+ float max_w = max_width;
+ float max_h = max_height;
+ float w = *width;
+ float h = *height;
+ float factor;
+ int new_width, new_height;
+
+ if ((max_width > cache_max_w) && (max_height > cache_max_h)) {
+ if ((*width < cache_max_w - 1) && (*height < cache_max_h - 1))
+ return FALSE;
+ }
+ else if ((*width < max_width - 1) && (*height < max_height - 1))
+ return FALSE;
+
+ factor = MIN (max_w / w, max_h / h);
+ new_width = MAX ((int) (w * factor), 1);
+ new_height = MAX ((int) (h * factor), 1);
+
+ modified = (new_width != *width) || (new_height != *height);
+
+ *width = new_width;
+ *height = new_height;
+
+ return modified;
+}
+
+
+static gboolean
+_gth_thumb_loader_save_to_cache (GthThumbLoader *tloader)
+{
+ char *uri;
+ char *cache_path;
+ GFile *cache_file;
+ GFile *cache_dir;
+
+ if ((tloader == NULL) || (tloader->priv->pixbuf == NULL))
+ return FALSE;
+
+ uri = g_file_get_uri (tloader->priv->file->file);
+
+ if (g_file_is_native (tloader->priv->file->file)) {
+ char *cache_base_uri;
+
+ /* Do not save thumbnails from the user's thumbnail directory,
+ or an endless loop of thumbnailing may be triggered. */
+
+ cache_base_uri = g_strconcat (get_home_uri (), "/.thumbnails", NULL);
+ if (_g_uri_parent_of_uri (cache_base_uri, uri)) {
+ g_free (cache_base_uri);
+ g_free (uri);
+ return FALSE;
+ }
+ g_free (cache_base_uri);
+ }
+
+ cache_path = gnome_desktop_thumbnail_path_for_uri (uri, tloader->priv->thumb_size);
+ if (cache_path == NULL) {
+ g_free (uri);
+ return FALSE;
+ }
+
+ cache_file = g_file_new_for_path (cache_path);
+ cache_dir = g_file_get_parent (cache_file);
+
+ if (_g_directory_make (cache_dir, THUMBNAIL_DIR_PERMISSIONS, NULL)) {
+ char *uri;
+
+ uri = g_file_get_uri (tloader->priv->file->file);
+ gnome_desktop_thumbnail_factory_save_thumbnail (tloader->priv->thumb_factory,
+ tloader->priv->pixbuf,
+ uri,
+ gth_file_data_get_mtime (tloader->priv->file));
+ g_free (uri);
+ }
+
+ g_object_unref (cache_dir);
+ g_object_unref (cache_file);
+ g_free (cache_path);
+ g_free (uri);
+
+ return TRUE;
+}
+
+
+static void
+image_loader_loaded (GthImageLoader *iloader,
+ gpointer data)
+{
+ GthThumbLoader *tloader = data;
+ GdkPixbuf *pixbuf;
+ int width, height;
+ gboolean modified;
+
+ if (tloader->priv->pixbuf != NULL) {
+ g_object_unref (tloader->priv->pixbuf);
+ tloader->priv->pixbuf = NULL;
+ }
+
+ pixbuf = gth_image_loader_get_pixbuf (tloader->priv->iloader);
+ if (pixbuf == NULL) {
+ char *uri;
+
+ uri = g_file_get_uri (tloader->priv->file->file);
+ gnome_desktop_thumbnail_factory_create_failed_thumbnail (tloader->priv->thumb_factory,
+ uri,
+ gth_file_data_get_mtime (tloader->priv->file));
+ g_signal_emit (G_OBJECT (tloader), gth_thumb_loader_signals[READY], 0, NULL);
+ g_free (uri);
+
+ return;
+ }
+
+ g_object_ref (pixbuf);
+ tloader->priv->pixbuf = pixbuf;
+
+ width = gdk_pixbuf_get_width (pixbuf);
+ height = gdk_pixbuf_get_height (pixbuf);
+
+ if (tloader->priv->use_cache) {
+ /* Thumbnails are always saved with the same size, then
+ * scaled if necessary. */
+
+ /* Check whether to scale. */
+ modified = scale_keeping_ratio (&width,
+ &height,
+ tloader->priv->cache_max_w,
+ tloader->priv->cache_max_h,
+ FALSE);
+ if (modified) {
+ g_object_unref (tloader->priv->pixbuf);
+ tloader->priv->pixbuf = gdk_pixbuf_scale_simple (pixbuf, width, height, GDK_INTERP_BILINEAR);
+ }
+
+ /* Save the thumbnail if necessary. */
+ if (tloader->priv->save_thumbnails && ! tloader->priv->from_cache)
+ _gth_thumb_loader_save_to_cache (tloader);
+
+ /* Scale if the user wants a different size. */
+ modified = normalize_thumb (&width,
+ &height,
+ tloader->priv->max_w,
+ tloader->priv->max_h,
+ tloader->priv->cache_max_w,
+ tloader->priv->cache_max_h);
+ if (modified) {
+ pixbuf = tloader->priv->pixbuf;
+ tloader->priv->pixbuf = gdk_pixbuf_scale_simple (pixbuf, width, height, GDK_INTERP_BILINEAR);
+ g_object_unref (pixbuf);
+ }
+ }
+ else {
+ modified = scale_keeping_ratio (&width, &height, tloader->priv->max_w, tloader->priv->max_h, FALSE);
+ if (modified) {
+ g_object_unref (tloader->priv->pixbuf);
+ tloader->priv->pixbuf = gdk_pixbuf_scale_simple (pixbuf, width, height, GDK_INTERP_BILINEAR);
+ }
+ }
+
+ g_signal_emit (G_OBJECT (tloader), gth_thumb_loader_signals[READY], 0, NULL);
+}
+
+
+static void
+image_loader_error (GthImageLoader *iloader,
+ GError *error,
+ gpointer data)
+{
+ GthThumbLoader *tloader = data;
+
+ g_return_if_fail (error != NULL);
+
+ if (! tloader->priv->from_cache) {
+ char *uri;
+
+ if (tloader->priv->pixbuf != NULL) {
+ g_object_unref (tloader->priv->pixbuf);
+ tloader->priv->pixbuf = NULL;
+ }
+
+ uri = g_file_get_uri (tloader->priv->file->file);
+ gnome_desktop_thumbnail_factory_create_failed_thumbnail (tloader->priv->thumb_factory,
+ uri,
+ gth_file_data_get_mtime (tloader->priv->file));
+ g_free (uri);
+ g_signal_emit (G_OBJECT (tloader), gth_thumb_loader_signals[READY], 0, error);
+ return;
+ }
+
+ g_error_free (error);
+
+ /* Try to load the original image if cache version failed. */
+
+ tloader->priv->from_cache = FALSE;
+ gth_image_loader_set_file_data (tloader->priv->iloader, tloader->priv->file);
+ gth_image_loader_load (tloader->priv->iloader);
+}
+
+
+static void
+image_loader_ready_cb (GthImageLoader *iloader,
+ GError *error,
+ gpointer data)
+{
+ if (error == NULL)
+ image_loader_loaded (iloader, data);
+ else
+ image_loader_error (iloader, error, data);
+}
+
+
+static GdkPixbufAnimation*
+thumb_loader (GthFileData *file,
+ GError **error,
+ gpointer data)
+{
+ GthThumbLoader *tloader = data;
+ GdkPixbuf *pixbuf;
+ GdkPixbufAnimation *animation = NULL;
+
+ /* try with a custom thumbnailer first */
+
+ if (! tloader->priv->from_cache) {
+ FileLoader thumbnailer;
+
+ thumbnailer = gth_main_get_file_loader (gth_file_data_get_mime_type (file));
+ if (thumbnailer != NULL)
+ animation = thumbnailer (file, error, tloader->priv->cache_max_w, tloader->priv->cache_max_h);
+
+ if (animation != NULL)
+ return animation;
+ }
+
+ /* use the default thumbnailer as fallback */
+
+ if (! tloader->priv->from_cache) {
+ char *uri;
+
+ uri = g_file_get_uri (file->file);
+ pixbuf = gnome_desktop_thumbnail_factory_generate_thumbnail (tloader->priv->thumb_factory, uri, gth_file_data_get_mime_type (file));
+ g_free (uri);
+
+ if (pixbuf == NULL)
+ *error = g_error_new_literal (GTHUMB_ERROR, 0, "cannot generate the thumbnail");
+ }
+ else
+ pixbuf = gth_pixbuf_new_from_file (file, error, -1, -1);
+
+ if (pixbuf != NULL) {
+ animation = gdk_pixbuf_non_anim_new (pixbuf);
+ g_object_unref (pixbuf);
+ }
+
+ return animation;
+}
+
+
+static void
+gth_thumb_loader_construct (GthThumbLoader *tloader,
+ int width,
+ int height)
+{
+ gth_thumb_loader_set_thumb_size (tloader, width, height);
+
+ tloader->priv->iloader = gth_image_loader_new (FALSE);
+ gth_image_loader_set_loader (tloader->priv->iloader, thumb_loader, tloader);
+ g_signal_connect (G_OBJECT (tloader->priv->iloader),
+ "ready",
+ G_CALLBACK (image_loader_ready_cb),
+ tloader);
+}
+
+
+GthThumbLoader *
+gth_thumb_loader_new (int width,
+ int height)
+{
+ GthThumbLoader *tloader;
+
+ tloader = g_object_new (GTH_TYPE_THUMB_LOADER, NULL);
+ gth_thumb_loader_construct (tloader, width, height);
+
+ return tloader;
+}
+
+
+void
+gth_thumb_loader_set_thumb_size (GthThumbLoader *tloader,
+ int width,
+ int height)
+{
+ if (tloader->priv->thumb_factory != NULL) {
+ g_object_unref (tloader->priv->thumb_factory);
+ tloader->priv->thumb_factory = NULL;
+ }
+
+ if ((width <= THUMBNAIL_NORMAL_SIZE) && (height <= THUMBNAIL_NORMAL_SIZE)) {
+ tloader->priv->cache_max_w = tloader->priv->cache_max_h = THUMBNAIL_NORMAL_SIZE;
+ tloader->priv->thumb_size = GNOME_DESKTOP_THUMBNAIL_SIZE_NORMAL;
+ }
+ else {
+ tloader->priv->cache_max_w = tloader->priv->cache_max_h = THUMBNAIL_LARGE_SIZE;
+ tloader->priv->thumb_size = GNOME_DESKTOP_THUMBNAIL_SIZE_LARGE;
+ }
+
+ tloader->priv->thumb_factory = gnome_desktop_thumbnail_factory_new (tloader->priv->thumb_size);
+
+ tloader->priv->max_w = width;
+ tloader->priv->max_h = height;
+}
+
+
+void
+gth_thumb_loader_use_cache (GthThumbLoader *tloader,
+ gboolean use)
+{
+ g_return_if_fail (tloader != NULL);
+ tloader->priv->use_cache = use;
+}
+
+
+void
+gth_thumb_loader_save_thumbnails (GthThumbLoader *tloader,
+ gboolean save)
+{
+ g_return_if_fail (tloader != NULL);
+ tloader->priv->save_thumbnails = save;
+}
+
+
+void
+gth_thumb_loader_set_max_file_size (GthThumbLoader *tloader,
+ goffset size)
+{
+ g_return_if_fail (tloader != NULL);
+ tloader->priv->max_file_size = size;
+}
+
+
+void
+gth_thumb_loader_set_file (GthThumbLoader *tloader,
+ GthFileData *fd)
+{
+ g_return_if_fail (tloader != NULL);
+
+ _g_object_unref (tloader->priv->file);
+ tloader->priv->file = NULL;
+
+ if (fd != NULL) {
+ GFile *real_file = NULL;
+
+ tloader->priv->file = gth_file_data_dup (fd);
+
+ real_file = _g_file_resolve_all_symlinks (tloader->priv->file->file, NULL);
+ gth_file_data_set_file (tloader->priv->file, real_file);
+
+ if (real_file != NULL)
+ g_object_unref (real_file);
+ }
+
+ gth_image_loader_set_file_data (tloader->priv->iloader, tloader->priv->file);
+}
+
+
+void
+gth_thumb_loader_set_uri (GthThumbLoader *tloader,
+ const char *uri,
+ const char *mime_type)
+{
+ GFile *file;
+ GthFileData *fd;
+
+ g_return_if_fail (tloader != NULL);
+ g_return_if_fail (uri != NULL);
+
+ file = g_file_new_for_uri (uri);
+ fd = gth_file_data_new (file, NULL);
+ gth_file_data_update_info (fd, NULL);
+ gth_file_data_set_mime_type (fd, mime_type);
+
+ gth_thumb_loader_set_file (tloader, fd);
+
+ g_object_unref (file);
+}
+
+
+GdkPixbuf *
+gth_thumb_loader_get_pixbuf (GthThumbLoader *tloader)
+{
+ g_return_val_if_fail (tloader != NULL, NULL);
+ return tloader->priv->pixbuf;
+}
+
+
+static void
+gth_thumb_loader_load__step2 (GthThumbLoader *tloader)
+{
+ char *cache_path = NULL;
+
+ g_return_if_fail (tloader != NULL);
+
+ /*if ((tloader->priv->file == NULL) || ! gth_file_data_is_readable (tloader->priv->file)) {
+ g_signal_emit (G_OBJECT (tloader),
+ gth_thumb_loader_signals[READY],
+ 0,
+ g_error_new_literal (GTHUMB_ERROR, 0, "cannot read the file"));
+ return;
+ }*/
+
+ if (tloader->priv->use_cache) {
+ char *uri;
+ time_t mtime;
+
+ uri = g_file_get_uri (tloader->priv->file->file);
+ mtime = gth_file_data_get_mtime (tloader->priv->file);
+
+ cache_path = gnome_desktop_thumbnail_factory_lookup (tloader->priv->thumb_factory, uri, mtime);
+
+/*debug (DEBUG_INFO, "thumbnail for %s: %s\n", uri, cache_path); FIXME: delete when done */
+
+ if ((cache_path == NULL)
+ && gnome_desktop_thumbnail_factory_has_valid_failed_thumbnail (tloader->priv->thumb_factory, uri, mtime)
+ && ((time (NULL) - mtime) > (time_t) 5))
+ {
+ /* Use the existing "failed" thumbnail, if it is over
+ 5 seconds old. Otherwise, try to thumbnail it again.
+ The minimum age requirement addresses bug 432759,
+ which occurs when a device like a scanner saves a file
+ slowly in chunks. */
+ g_signal_emit (G_OBJECT (tloader),
+ gth_thumb_loader_signals[READY],
+ 0,
+ g_error_new_literal (GTHUMB_ERROR, 0, "failed thumbnail"));
+ g_free (uri);
+ return;
+ }
+ g_free (uri);
+ }
+
+ if (cache_path != NULL) {
+ GFile *file;
+
+ tloader->priv->from_cache = TRUE;
+ file = g_file_new_for_path (cache_path);
+ gth_image_loader_set_file (tloader->priv->iloader, file, "image/png");
+
+ g_object_unref (file);
+ g_free (cache_path);
+ }
+ else {
+ tloader->priv->from_cache = FALSE;
+ gth_image_loader_set_file_data (tloader->priv->iloader, tloader->priv->file);
+
+ /* Check file dimensions. */
+
+ if ((tloader->priv->max_file_size > 0)
+ && (g_file_info_get_size (tloader->priv->file->info) > tloader->priv->max_file_size))
+ {
+ if (tloader->priv->pixbuf != NULL) {
+ g_object_unref (tloader->priv->pixbuf);
+ tloader->priv->pixbuf = NULL;
+ }
+ g_signal_emit (G_OBJECT (tloader),
+ gth_thumb_loader_signals[READY],
+ 0,
+ NULL);
+ return;
+ }
+ }
+
+ gth_image_loader_load (tloader->priv->iloader);
+}
+
+
+void
+gth_thumb_loader_load (GthThumbLoader *tloader)
+{
+ gth_thumb_loader_cancel (tloader, (DoneFunc) gth_thumb_loader_load__step2, tloader);
+}
+
+
+void
+gth_thumb_loader_cancel (GthThumbLoader *tloader,
+ DoneFunc done_func,
+ gpointer done_func_data)
+{
+ g_return_if_fail (tloader->priv->iloader != NULL);
+
+ gth_image_loader_cancel (tloader->priv->iloader, done_func, done_func_data);
+}
diff --git a/gthumb/gth-thumb-loader.h b/gthumb/gth-thumb-loader.h
new file mode 100644
index 0000000..ff624c5
--- /dev/null
+++ b/gthumb/gth-thumb-loader.h
@@ -0,0 +1,85 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2001, 2003, 2007, 2008 The Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GTH_THUMB_LOADER_H
+#define GTH_THUMB_LOADER_H
+
+#include <glib.h>
+#include <gdk/gdk.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include "gth-image-loader.h"
+#include "gth-file-data.h"
+
+G_BEGIN_DECLS
+
+#define GTH_TYPE_THUMB_LOADER (gth_thumb_loader_get_type ())
+#define GTH_THUMB_LOADER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTH_TYPE_THUMB_LOADER, GthThumbLoader))
+#define GTH_THUMB_LOADER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTH_TYPE_THUMB_LOADER, GthThumbLoaderClass))
+#define GTH_IS_THUMB_LOADER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTH_TYPE_THUMB_LOADER))
+#define GTH_IS_THUMB_LOADER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTH_TYPE_THUMB_LOADER))
+#define GTH_THUMB_LOADER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GTH_TYPE_THUMB_LOADER, GthThumbLoaderClass))
+
+typedef struct _GthThumbLoader GthThumbLoader;
+typedef struct _GthThumbLoaderClass GthThumbLoaderClass;
+typedef struct _GthThumbLoaderPrivateData GthThumbLoaderPrivateData;
+
+struct _GthThumbLoader
+{
+ GObject __parent;
+ GthThumbLoaderPrivateData *priv;
+};
+
+struct _GthThumbLoaderClass
+{
+ GObjectClass __parent_class;
+
+ /* -- Signals -- */
+
+ void (* ready) (GthThumbLoader *il);
+};
+
+GType gth_thumb_loader_get_type (void);
+GthThumbLoader * gth_thumb_loader_new (int width,
+ int height);
+void gth_thumb_loader_set_thumb_size (GthThumbLoader *tl,
+ int width,
+ int height);
+void gth_thumb_loader_use_cache (GthThumbLoader *tl,
+ gboolean use);
+void gth_thumb_loader_save_thumbnails (GthThumbLoader *tl,
+ gboolean save);
+void gth_thumb_loader_set_max_file_size (GthThumbLoader *tl,
+ goffset size);
+void gth_thumb_loader_set_file (GthThumbLoader *tl,
+ GthFileData *fd);
+void gth_thumb_loader_set_uri (GthThumbLoader *tl,
+ const char *uri,
+ const char *mime_type);
+GdkPixbuf * gth_thumb_loader_get_pixbuf (GthThumbLoader *tl);
+void gth_thumb_loader_load (GthThumbLoader *tl);
+void gth_thumb_loader_cancel (GthThumbLoader *tl,
+ DoneFunc func,
+ gpointer data);
+
+G_END_DECLS
+
+#endif /* GTH_THUMB_LOADER_H */
diff --git a/gthumb/gth-time.c b/gthumb/gth-time.c
new file mode 100644
index 0000000..3a00ba2
--- /dev/null
+++ b/gthumb/gth-time.c
@@ -0,0 +1,275 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 The Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <stdlib.h>
+#include "gth-time.h"
+
+
+#define INVALID_HOUR (24)
+#define INVALID_MIN (60)
+#define INVALID_SEC (60)
+#define INVALID_USEC (1000000)
+
+
+GthTime *
+gth_time_new (void)
+{
+ GthTime *time;
+
+ time = g_new (GthTime, 1);
+ gth_time_clear (time);
+
+ return time;
+}
+
+
+void
+gth_time_free (GthTime *time)
+{
+ g_free (time);
+}
+
+
+void
+gth_time_clear (GthTime *time)
+{
+ time->hour = INVALID_HOUR;
+ time->min = INVALID_MIN;
+ time->sec = INVALID_SEC;
+ time->usec = INVALID_USEC;
+}
+
+
+gboolean
+gth_time_valid (GthTime *time)
+{
+ return (time->hour < INVALID_HOUR) && (time->min < INVALID_MIN) && (time->sec < INVALID_SEC) && (time->usec < INVALID_USEC);
+}
+
+
+void
+gth_time_set_hms (GthTime *time,
+ guint8 hour,
+ guint8 min,
+ guint8 sec,
+ guint usec)
+{
+ time->hour = hour;
+ time->min = min;
+ time->sec = sec;
+ time->usec = usec;
+}
+
+
+GthDateTime *
+gth_datetime_new (void)
+{
+ GthDateTime *dt;
+
+ dt = g_new0 (GthDateTime, 1);
+ dt->date = g_date_new ();
+ dt->time = gth_time_new ();
+
+ return dt;
+}
+
+
+void
+gth_datetime_free (GthDateTime *dt)
+{
+ g_date_free (dt->date);
+ gth_time_free (dt->time);
+ g_free (dt);
+}
+
+
+gboolean
+gth_datetime_from_exif_date (GthDateTime *dt,
+ const char *exif_date)
+{
+ GDateYear year;
+ GDateMonth month;
+ GDateDay day;
+ long val;
+
+ g_return_val_if_fail (exif_date != NULL, FALSE);
+ g_return_val_if_fail (dt != NULL, FALSE);
+
+ while (g_ascii_isspace (*exif_date))
+ exif_date++;
+
+ if (*exif_date == '\0')
+ return FALSE;
+
+ if (! g_ascii_isdigit (*exif_date))
+ return FALSE;
+
+ /* YYYY */
+
+ val = g_ascii_strtoull (exif_date, (char **)&exif_date, 10);
+ year = val;
+
+ if (*exif_date != ':')
+ return FALSE;
+
+ /* MM */
+
+ exif_date++;
+ month = g_ascii_strtoull (exif_date, (char **)&exif_date, 10);
+
+ if (*exif_date != ':')
+ return FALSE;
+
+ /* DD */
+
+ exif_date++;
+ day = g_ascii_strtoull (exif_date, (char **)&exif_date, 10);
+
+ if (*exif_date != ' ')
+ return FALSE;
+
+ g_date_set_dmy (dt->date, day, month, year);
+
+ /* hh */
+
+ val = g_ascii_strtoull (exif_date, (char **)&exif_date, 10);
+ dt->time->hour = val;
+
+ if (*exif_date != ':')
+ return FALSE;
+
+ /* mm */
+
+ exif_date++;
+ dt->time->min = g_ascii_strtoull (exif_date, (char **)&exif_date, 10);
+
+ if (*exif_date != ':')
+ return FALSE;
+
+ /* ss */
+
+ exif_date++;
+ dt->time->sec = strtoul (exif_date, (char **)&exif_date, 10);
+
+ /* usec */
+
+ dt->time->usec = 0;
+ if ((*exif_date == ',') || (*exif_date == '.')) {
+ glong mul = 100000;
+
+ while (g_ascii_isdigit (*++exif_date)) {
+ dt->time->usec += (*exif_date - '0') * mul;
+ mul /= 10;
+ }
+ }
+
+ while (g_ascii_isspace (*exif_date))
+ exif_date++;
+
+ return *exif_date == '\0';
+}
+
+
+char *
+gth_datetime_to_exif_date (GthDateTime *dt)
+{
+ return g_strdup_printf ("%4d:%02d:%02d %02d:%02d:%02d",
+ g_date_get_year (dt->date),
+ g_date_get_month (dt->date),
+ g_date_get_day (dt->date),
+ dt->time->hour,
+ dt->time->min,
+ dt->time->sec);
+}
+
+
+void
+gth_datetime_to_struct_tm (GthDateTime *dt,
+ struct tm *tm)
+{
+ g_date_to_struct_tm (dt->date, tm);
+ tm->tm_hour = dt->time->hour;
+ tm->tm_min = dt->time->min;
+ tm->tm_sec = dt->time->sec;
+}
+
+
+/* started from the glib function g_date_strftime */
+char *
+gth_datetime_strftime (GthDateTime *dt,
+ const char *format)
+{
+ gsize locale_format_len = 0;
+ char *locale_format;
+ GError *error = NULL;
+ struct tm tm;
+ gsize tmpbufsize;
+ char *tmpbuf;
+ gsize tmplen;
+ char *retval;
+
+ locale_format = g_locale_from_utf8 (format, -1, NULL, &locale_format_len, &error);
+ if (error != NULL) {
+ g_warning (G_STRLOC "Error converting format to locale encoding: %s\n", error->message);
+ g_error_free (error);
+ return NULL;
+ }
+
+ gth_datetime_to_struct_tm (dt, &tm);
+ tmpbufsize = MAX (128, locale_format_len * 2);
+ while (TRUE) {
+ tmpbuf = g_malloc (tmpbufsize);
+
+ /* Set the first byte to something other than '\0', to be able to
+ * recognize whether strftime actually failed or just returned "".
+ */
+ tmpbuf[0] = '\1';
+ tmplen = strftime (tmpbuf, tmpbufsize, locale_format, &tm);
+
+ if ((tmplen == 0) && (tmpbuf[0] != '\0')) {
+ g_free (tmpbuf);
+ tmpbufsize *= 2;
+
+ if (tmpbufsize > 65536) {
+ g_warning (G_STRLOC "Maximum buffer size for gth_datetime_strftime exceeded: giving up\n");
+ g_free (locale_format);
+ return NULL;
+ }
+ }
+ else
+ break;
+ }
+ g_free (locale_format);
+
+ retval = g_locale_to_utf8 (tmpbuf, tmplen, NULL, NULL, &error);
+ if (error != NULL) {
+ g_warning (G_STRLOC "Error converting results of strftime to UTF-8: %s\n", error->message);
+ g_error_free (error);
+ return NULL;
+ }
+
+ g_free (tmpbuf);
+
+ return retval;
+}
+
diff --git a/gthumb/gth-time.h b/gthumb/gth-time.h
new file mode 100644
index 0000000..92f3f5c
--- /dev/null
+++ b/gthumb/gth-time.h
@@ -0,0 +1,65 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 The Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GTH_TIME_H
+#define GTH_TIME_H
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+typedef struct {
+ guint8 hour;
+ guint8 min;
+ guint8 sec;
+ guint usec;
+} GthTime;
+
+typedef struct {
+ GDate *date;
+ GthTime *time;
+} GthDateTime;
+
+
+GthTime * gth_time_new (void);
+void gth_time_free (GthTime *time);
+void gth_time_clear (GthTime *time);
+gboolean gth_time_valid (GthTime *time);
+void gth_time_set_hms (GthTime *time,
+ guint8 hour,
+ guint8 min,
+ guint8 sec,
+ guint usec);
+GthDateTime * gth_datetime_new (void);
+void gth_datetime_free (GthDateTime *dt);
+gboolean gth_datetime_from_exif_date (GthDateTime *dt,
+ const char *exif_date);
+char * gth_datetime_to_exif_date (GthDateTime *dt);
+void gth_datetime_to_struct_tm (GthDateTime *dt,
+ struct tm *tm);
+char * gth_datetime_strftime (GthDateTime *dt,
+ const char *format);
+
+G_END_DECLS
+
+#endif /* GTH_TIME_H */
+
diff --git a/gthumb/gth-toolbox.c b/gthumb/gth-toolbox.c
new file mode 100644
index 0000000..3ce0fef
--- /dev/null
+++ b/gthumb/gth-toolbox.c
@@ -0,0 +1,252 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <gtk/gtk.h>
+#include "gth-main.h"
+#include "gth-toolbox.h"
+
+
+#define GTH_TOOLBOX_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GTH_TYPE_TOOLBOX, GthToolboxPrivate))
+
+
+enum {
+ GTH_TOOLBOX_DUMMY_PROPERTY,
+ GTH_TOOLBOX_NAME
+};
+
+
+static gpointer parent_class = NULL;
+
+
+struct _GthToolboxPrivate {
+ char *name;
+ GtkWidget *box;
+};
+
+
+static void
+gth_toolbox_set_name (GthToolbox *self,
+ const char *name)
+{
+ g_free (self->priv->name);
+ self->priv->name = NULL;
+
+ if (name != NULL)
+ self->priv->name = g_strdup (name);
+}
+
+
+static void
+gth_toolbox_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GthToolbox *self;
+
+ self = GTH_TOOLBOX (object);
+
+ switch (property_id) {
+ case GTH_TOOLBOX_NAME:
+ gth_toolbox_set_name (self, g_value_get_string (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+
+static void
+gth_toolbox_finalize (GObject *object)
+{
+ GthToolbox *self;
+
+ self = GTH_TOOLBOX (object);
+
+ g_free (self->priv->name);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+
+static void
+gth_toolbox_class_init (GthToolboxClass *klass)
+{
+ GObjectClass *gobject_class;
+
+ parent_class = g_type_class_peek_parent (klass);
+ g_type_class_add_private (klass, sizeof (GthToolboxPrivate));
+
+ gobject_class = (GObjectClass*) klass;
+ gobject_class->finalize = gth_toolbox_finalize;
+ gobject_class->set_property = gth_toolbox_set_property;
+
+ g_object_class_install_property (G_OBJECT_CLASS (klass),
+ GTH_TOOLBOX_NAME,
+ g_param_spec_string ("name",
+ "name",
+ "name",
+ NULL,
+ G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
+}
+
+
+static void
+gth_toolbox_init (GthToolbox *toolbox)
+{
+ GtkWidget *scrolled;
+
+ toolbox->priv = GTH_TOOLBOX_GET_PRIVATE (toolbox);
+ gtk_box_set_spacing (GTK_BOX (toolbox), 0);
+
+ scrolled = gtk_scrolled_window_new (NULL, NULL);
+ gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled), GTK_SHADOW_NONE);
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled),
+ GTK_POLICY_AUTOMATIC,
+ GTK_POLICY_AUTOMATIC);
+ gtk_widget_show (scrolled);
+ gtk_box_pack_start (GTK_BOX (toolbox), scrolled, TRUE, TRUE, 0);
+
+ toolbox->priv->box = gtk_vbox_new (FALSE, 0);
+ gtk_box_set_spacing (GTK_BOX (toolbox->priv->box), 0);
+ gtk_widget_show (toolbox->priv->box);
+ gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (scrolled), toolbox->priv->box);
+}
+
+
+GType
+gth_toolbox_get_type (void)
+{
+ static GType type = 0;
+
+ if (! type) {
+ GTypeInfo type_info = {
+ sizeof (GthToolboxClass),
+ NULL,
+ NULL,
+ (GClassInitFunc) gth_toolbox_class_init,
+ NULL,
+ NULL,
+ sizeof (GthToolbox),
+ 0,
+ (GInstanceInitFunc) gth_toolbox_init
+ };
+
+ type = g_type_register_static (GTK_TYPE_VBOX,
+ "GthToolbox",
+ &type_info,
+ 0);
+ }
+
+ return type;
+}
+
+
+static void
+_gth_toolbox_add_childs (GthToolbox *toolbox)
+{
+ GArray *children;
+ int i;
+
+ children = gth_main_get_type_set (toolbox->priv->name);
+ if (children == NULL)
+ return;
+
+ for (i = 0; i < children->len; i++) {
+ GType child_type;
+ GtkWidget *child;
+
+ child_type = g_array_index (children, GType, i);
+ child = g_object_new (child_type, NULL);
+ gtk_widget_show (child);
+ gtk_box_pack_start (GTK_BOX (toolbox->priv->box), child, FALSE, FALSE, 0);
+ }
+}
+
+
+GtkWidget *
+gth_toolbox_new (const char *name)
+{
+ GtkWidget *toolbox;
+
+ toolbox = g_object_new (GTH_TYPE_TOOLBOX, "name", name, NULL);
+ _gth_toolbox_add_childs (GTH_TOOLBOX (toolbox));
+
+ return toolbox;
+}
+
+
+void
+gth_toolbox_update_sensitivity (GthToolbox *toolbox)
+{
+ GtkWidget *window;
+ GList *children;
+ GList *scan;
+
+ window = gtk_widget_get_toplevel (GTK_WIDGET (toolbox));
+ children = gtk_container_get_children (GTK_CONTAINER (toolbox->priv->box));
+ for (scan = children; scan; scan = scan->next) {
+ GtkWidget *child = scan->data;
+
+ if (! GTH_IS_FILE_TOOL (child))
+ continue;
+
+ gth_file_tool_update_sensitivity (GTH_FILE_TOOL (child), window);
+ }
+
+ g_list_free (children);
+}
+
+
+/* -- gth_file_tool -- */
+
+
+GType
+gth_file_tool_get_type (void) {
+ static GType gth_file_tool_type_id = 0;
+ if (gth_file_tool_type_id == 0) {
+ static const GTypeInfo g_define_type_info = {
+ sizeof (GthFileToolIface),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) NULL,
+ (GClassFinalizeFunc) NULL,
+ NULL,
+ 0,
+ 0,
+ (GInstanceInitFunc) NULL,
+ NULL
+ };
+ gth_file_tool_type_id = g_type_register_static (G_TYPE_INTERFACE, "GthFileTool", &g_define_type_info, 0);
+ }
+ return gth_file_tool_type_id;
+}
+
+
+void
+gth_file_tool_update_sensitivity (GthFileTool *self,
+ GtkWidget *window)
+{
+ GTH_FILE_TOOL_GET_INTERFACE (self)->update_sensitivity (self, window);
+}
diff --git a/gthumb/gth-toolbox.h b/gthumb/gth-toolbox.h
new file mode 100644
index 0000000..6434cc4
--- /dev/null
+++ b/gthumb/gth-toolbox.h
@@ -0,0 +1,75 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GTH_TOOLBOX_H
+#define GTH_TOOLBOX_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GTH_TYPE_TOOLBOX (gth_toolbox_get_type ())
+#define GTH_TOOLBOX(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTH_TYPE_TOOLBOX, GthToolbox))
+#define GTH_TOOLBOX_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTH_TYPE_TOOLBOX, GthToolboxClass))
+#define GTH_IS_TOOLBOX(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTH_TYPE_TOOLBOX))
+#define GTH_IS_TOOLBOX_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTH_TYPE_TOOLBOX))
+#define GTH_TOOLBOX_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GTH_TYPE_TOOLBOX, GthToolboxClass))
+
+#define GTH_TYPE_FILE_TOOL (gth_file_tool_get_type ())
+#define GTH_FILE_TOOL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTH_TYPE_FILE_TOOL, GthFileTool))
+#define GTH_IS_FILE_TOOL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTH_TYPE_FILE_TOOL))
+#define GTH_FILE_TOOL_GET_INTERFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), GTH_TYPE_FILE_TOOL, GthFileToolIface))
+
+typedef struct _GthToolbox GthToolbox;
+typedef struct _GthToolboxClass GthToolboxClass;
+typedef struct _GthToolboxPrivate GthToolboxPrivate;
+
+struct _GthToolbox
+{
+ GtkVBox __parent;
+ GthToolboxPrivate *priv;
+};
+
+struct _GthToolboxClass
+{
+ GtkVBoxClass __parent_class;
+};
+
+typedef struct _GthFileTool GthFileTool;
+typedef struct _GthFileToolIface GthFileToolIface;
+
+struct _GthFileToolIface {
+ GTypeInterface parent_iface;
+ void (*update_sensitivity) (GthFileTool *self,
+ GtkWidget *window);
+};
+
+GType gth_toolbox_get_type (void);
+GtkWidget * gth_toolbox_new (const char *name);
+void gth_toolbox_update_sensitivity (GthToolbox *toolbox);
+GType gth_file_tool_get_type (void);
+void gth_file_tool_update_sensitivity (GthFileTool *self,
+ GtkWidget *window);
+
+G_END_DECLS
+
+#endif /* GTH_TOOLBOX_H */
diff --git a/gthumb/gth-uri-list.c b/gthumb/gth-uri-list.c
new file mode 100644
index 0000000..ad3e991
--- /dev/null
+++ b/gthumb/gth-uri-list.c
@@ -0,0 +1,341 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2008 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <gtk/gtk.h>
+#include "glib-utils.h"
+#include "gth-file-source.h"
+#include "gth-icon-cache.h"
+#include "gth-main.h"
+#include "gth-uri-list.h"
+
+#define ORDER_CHANGED_DELAY 250
+
+
+enum {
+ ORDER_CHANGED,
+ LAST_SIGNAL
+};
+
+
+enum {
+ URI_LIST_COLUMN_ICON,
+ URI_LIST_COLUMN_NAME,
+ URI_LIST_COLUMN_URI,
+ URI_LIST_NUM_COLUMNS
+};
+
+
+struct _GthUriListPrivate {
+ GtkListStore *list_store;
+ GthIconCache *icon_cache;
+ guint changed_id;
+};
+
+
+static gpointer parent_class = NULL;
+static guint uri_list_signals[LAST_SIGNAL] = { 0 };
+
+
+static void
+add_columns (GtkTreeView *treeview)
+{
+ GtkCellRenderer *renderer;
+ GtkTreeViewColumn *column;
+
+ /* The Name column. */
+
+ column = gtk_tree_view_column_new ();
+
+ renderer = gtk_cell_renderer_pixbuf_new ();
+ gtk_tree_view_column_pack_start (column, renderer, FALSE);
+ gtk_tree_view_column_set_attributes (column, renderer,
+ "pixbuf", URI_LIST_COLUMN_ICON,
+ NULL);
+
+ renderer = gtk_cell_renderer_text_new ();
+ gtk_tree_view_column_pack_start (column,
+ renderer,
+ TRUE);
+ gtk_tree_view_column_set_attributes (column, renderer,
+ "text", URI_LIST_COLUMN_NAME,
+ NULL);
+
+ gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
+ gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column);
+}
+
+
+static gboolean
+order_changed (gpointer user_data)
+{
+ GthUriList *uri_list = user_data;
+
+ if (uri_list->priv->changed_id != 0)
+ g_source_remove (uri_list->priv->changed_id);
+ uri_list->priv->changed_id = 0;
+
+ g_signal_emit (G_OBJECT (uri_list),
+ uri_list_signals[ORDER_CHANGED],
+ 0);
+
+ return FALSE;
+}
+
+
+static void
+row_deleted_cb (GtkTreeModel *tree_model,
+ GtkTreePath *path,
+ gpointer user_data)
+{
+ GthUriList *uri_list = user_data;
+
+ if (uri_list->priv->changed_id != 0)
+ g_source_remove (uri_list->priv->changed_id);
+ uri_list->priv->changed_id = g_timeout_add (ORDER_CHANGED_DELAY, order_changed, uri_list);
+}
+
+
+static void
+row_inserted_cb (GtkTreeModel *tree_model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ gpointer user_data)
+{
+ GthUriList *uri_list = user_data;
+
+ if (uri_list->priv->changed_id != 0)
+ g_source_remove (uri_list->priv->changed_id);
+ uri_list->priv->changed_id = g_timeout_add (ORDER_CHANGED_DELAY, order_changed, uri_list);
+}
+
+
+static void
+gth_uri_list_init (GthUriList *uri_list)
+{
+ uri_list->priv = g_new0 (GthUriListPrivate, 1);
+
+ uri_list->priv->list_store = gtk_list_store_new (URI_LIST_NUM_COLUMNS,
+ GDK_TYPE_PIXBUF,
+ G_TYPE_STRING,
+ G_TYPE_STRING);
+ g_signal_connect (uri_list->priv->list_store,
+ "row-deleted",
+ G_CALLBACK (row_deleted_cb),
+ uri_list);
+ g_signal_connect (uri_list->priv->list_store,
+ "row-inserted",
+ G_CALLBACK (row_inserted_cb),
+ uri_list);
+
+ gtk_tree_view_set_model (GTK_TREE_VIEW (uri_list), GTK_TREE_MODEL (uri_list->priv->list_store));
+ g_object_unref (uri_list->priv->list_store);
+
+ gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (uri_list), FALSE);
+ add_columns (GTK_TREE_VIEW (uri_list));
+ gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (uri_list), FALSE);
+ gtk_tree_view_set_enable_search (GTK_TREE_VIEW (uri_list), TRUE);
+ gtk_tree_view_set_search_column (GTK_TREE_VIEW (uri_list), URI_LIST_COLUMN_NAME);
+ gtk_tree_view_set_reorderable (GTK_TREE_VIEW (uri_list), TRUE);
+
+ uri_list->priv->icon_cache = gth_icon_cache_new_for_widget (GTK_WIDGET (uri_list), GTK_ICON_SIZE_MENU);
+ uri_list->priv->changed_id = 0;
+}
+
+
+static void
+gth_uri_list_finalize (GObject *object)
+{
+ GthUriList *uri_list = GTH_URI_LIST (object);
+
+ if (uri_list->priv != NULL) {
+ g_free (uri_list->priv);
+ uri_list->priv = NULL;
+ }
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+
+static void
+gth_uri_list_class_init (GthUriListClass *klass)
+{
+ GObjectClass *gobject_class;
+
+ parent_class = g_type_class_peek_parent (klass);
+ gobject_class = G_OBJECT_CLASS (klass);
+
+ gobject_class->finalize = gth_uri_list_finalize;
+
+ uri_list_signals[ORDER_CHANGED] =
+ g_signal_new ("order-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GthUriListClass, order_changed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+}
+
+
+GType
+gth_uri_list_get_type (void)
+{
+ static GType type = 0;
+
+ if (type == 0) {
+ static const GTypeInfo g_define_type_info = {
+ sizeof (GthUriListClass),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) gth_uri_list_class_init,
+ (GClassFinalizeFunc) NULL,
+ NULL,
+ sizeof (GthUriList),
+ 0,
+ (GInstanceInitFunc) gth_uri_list_init,
+ NULL
+ };
+ type = g_type_register_static (GTK_TYPE_TREE_VIEW,
+ "GthUriList",
+ &g_define_type_info,
+ 0);
+ }
+
+ return type;
+}
+
+
+GtkWidget *
+gth_uri_list_new (void)
+{
+ return g_object_new (GTH_TYPE_URI_LIST, NULL);
+}
+
+
+void
+gth_uri_list_set_uris (GthUriList *uri_list,
+ char **uris)
+{
+ int i;
+
+ g_return_if_fail (uri_list != NULL);
+
+ g_signal_handlers_block_by_func (uri_list->priv->list_store, row_deleted_cb, uri_list);
+ g_signal_handlers_block_by_func (uri_list->priv->list_store, row_inserted_cb, uri_list);
+
+ gtk_list_store_clear (uri_list->priv->list_store);
+
+ for (i = 0; uris[i] != NULL; i++) {
+ char *uri = uris[i];
+ GFile *file;
+ GthFileSource *file_source;
+ GFileInfo *info;
+ const char *display_name;
+ GIcon *icon;
+ GdkPixbuf *pixbuf;
+ GtkTreeIter iter;
+
+ file = g_file_new_for_uri (uri);
+ file_source = gth_main_get_file_source (file);
+ info = gth_file_source_get_file_info (file_source, file);
+
+ display_name = g_file_info_get_display_name (info);
+ icon = g_file_info_get_icon (info);
+ pixbuf = gth_icon_cache_get_pixbuf (uri_list->priv->icon_cache, icon);
+
+ gtk_list_store_append (uri_list->priv->list_store, &iter);
+ gtk_list_store_set (uri_list->priv->list_store, &iter,
+ URI_LIST_COLUMN_ICON, pixbuf,
+ URI_LIST_COLUMN_NAME, display_name,
+ URI_LIST_COLUMN_URI, uri,
+ -1);
+
+ g_object_unref (pixbuf);
+ g_object_unref (file_source);
+ g_object_unref (file);
+ }
+
+ g_signal_handlers_unblock_by_func (uri_list->priv->list_store, row_deleted_cb, uri_list);
+ g_signal_handlers_unblock_by_func (uri_list->priv->list_store, row_inserted_cb, uri_list);
+}
+
+
+char *
+gth_uri_list_get_uri (GthUriList *uri_list,
+ GtkTreeIter *iter)
+{
+ char *uri;
+
+ gtk_tree_model_get (GTK_TREE_MODEL (uri_list->priv->list_store),
+ iter,
+ URI_LIST_COLUMN_URI, &uri,
+ -1);
+
+ return uri;
+}
+
+
+char *
+gth_uri_list_get_selected (GthUriList *uri_list)
+{
+ GtkTreeSelection *selection;
+ GtkTreeIter iter;
+ char *uri;
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (uri_list));
+ if (selection == NULL)
+ return NULL;
+
+ if (! gtk_tree_selection_get_selected (selection, NULL, &iter))
+ return NULL;
+
+ gtk_tree_model_get (GTK_TREE_MODEL (uri_list->priv->list_store),
+ &iter,
+ URI_LIST_COLUMN_URI, &uri,
+ -1);
+
+ return uri;
+}
+
+
+GList *
+gth_uri_list_get_uris (GthUriList *uri_list)
+{
+ GtkTreeModel *model = GTK_TREE_MODEL (uri_list->priv->list_store);
+ GList *uris = NULL;
+ GtkTreeIter iter;
+
+ if (! gtk_tree_model_get_iter_first (model, &iter))
+ return NULL;
+
+ do {
+ char *uri;
+
+ gtk_tree_model_get (model, &iter, URI_LIST_COLUMN_URI, &uri, -1);
+ uris = g_list_prepend (uris, uri);
+ }
+ while (gtk_tree_model_iter_next (model, &iter));
+
+ return g_list_reverse (uris);
+}
diff --git a/gthumb/gth-uri-list.h b/gthumb/gth-uri-list.h
new file mode 100644
index 0000000..216c3a8
--- /dev/null
+++ b/gthumb/gth-uri-list.h
@@ -0,0 +1,67 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2008 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GTH_URI_LIST_H
+#define GTH_URI_LIST_H
+
+#include <glib.h>
+#include <glib-object.h>
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GTH_TYPE_URI_LIST (gth_uri_list_get_type ())
+#define GTH_URI_LIST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTH_TYPE_URI_LIST, GthUriList))
+#define GTH_URI_LIST_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTH_TYPE_URI_LIST, GthUriListClass))
+#define GTH_IS_URI_LIST(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTH_TYPE_URI_LIST))
+#define GTH_IS_URI_LIST_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTH_TYPE_URI_LIST))
+#define GTH_URI_LIST_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTH_TYPE_URI_LIST, GthUriListClass))
+
+typedef struct _GthUriList GthUriList;
+typedef struct _GthUriListClass GthUriListClass;
+typedef struct _GthUriListPrivate GthUriListPrivate;
+
+struct _GthUriList {
+ GtkTreeView parent_instance;
+ GthUriListPrivate *priv;
+};
+
+struct _GthUriListClass {
+ GtkTreeViewClass parent_class;
+
+ /*< signals >*/
+
+ void (*order_changed) (GthUriList *uri_list);
+};
+
+GType gth_uri_list_get_type (void);
+GtkWidget * gth_uri_list_new (void);
+void gth_uri_list_set_uris (GthUriList *uri_list,
+ char **uris);
+char * gth_uri_list_get_uri (GthUriList *uri_list,
+ GtkTreeIter *iter);
+char * gth_uri_list_get_selected (GthUriList *uri_list);
+GList * gth_uri_list_get_uris (GthUriList *uri_list);
+
+G_END_DECLS
+
+#endif /* GTH_URI_LIST_H */
diff --git a/gthumb/gth-user-dir.c b/gthumb/gth-user-dir.c
new file mode 100644
index 0000000..ad1e3d3
--- /dev/null
+++ b/gthumb/gth-user-dir.c
@@ -0,0 +1,109 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2008 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <glib/gi18n.h>
+#include <glib.h>
+#include "gth-user-dir.h"
+
+
+static char *
+gth_user_dir_get_file_va_list (GthDir dir_type,
+ const char *first_element,
+ va_list var_args)
+{
+ const char *user_dir;
+ char *config_dir;
+ GString *path;
+ const char *element;
+ char *filename;
+
+ switch (dir_type) {
+ case GTH_DIR_CONFIG:
+ user_dir = g_get_user_config_dir ();
+ break;
+ case GTH_DIR_CACHE:
+ user_dir = g_get_user_cache_dir ();
+ break;
+ case GTH_DIR_DATA:
+ user_dir = g_get_user_data_dir ();
+ break;
+ }
+
+ config_dir = g_build_path (G_DIR_SEPARATOR_S,
+ user_dir,
+ NULL);
+ path = g_string_new (config_dir);
+
+ element = first_element;
+ while (element != NULL) {
+ if (element[0] != '\0') {
+ g_string_append (path, G_DIR_SEPARATOR_S);
+ g_string_append (path, element);
+ }
+ element = va_arg (var_args, const char *);
+ }
+
+ filename = path->str;
+
+ g_string_free (path, FALSE);
+ g_free (config_dir);
+
+ return filename;
+}
+
+
+char *
+gth_user_dir_get_file (GthDir dir_type,
+ const char *first_element,
+ ...)
+{
+ va_list var_args;
+ char *filename;
+
+ va_start (var_args, first_element);
+ filename = gth_user_dir_get_file_va_list (dir_type, first_element, var_args);
+ va_end (var_args);
+
+ return filename;
+}
+
+
+void
+gth_user_dir_make_dir_for_file (GthDir dir_type,
+ const char *first_element,
+ ...)
+{
+ va_list var_args;
+ char *config_file;
+ char *config_dir;
+
+ va_start (var_args, first_element);
+ config_file = gth_user_dir_get_file_va_list (dir_type, first_element, var_args);
+ va_end (var_args);
+
+ config_dir = g_path_get_dirname (config_file);
+ g_mkdir_with_parents (config_dir, 0700);
+
+ g_free (config_dir);
+ g_free (config_file);
+}
diff --git a/gthumb/gth-user-dir.h b/gthumb/gth-user-dir.h
new file mode 100644
index 0000000..26932f6
--- /dev/null
+++ b/gthumb/gth-user-dir.h
@@ -0,0 +1,47 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2008 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GTH_USER_DIR_H
+#define GTH_USER_DIR_H
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+typedef enum {
+ GTH_DIR_CONFIG,
+ GTH_DIR_CACHE,
+ GTH_DIR_DATA
+} GthDir;
+
+#define GTHUMB_DIR "gthumb"
+
+char * gth_user_dir_get_file (GthDir dir_type,
+ const char *first_element,
+ ...);
+void gth_user_dir_make_dir_for_file (GthDir dir_type,
+ const char *first_element,
+ ...);
+
+G_END_DECLS
+
+#endif /* GTH_USER_DIR_H */
diff --git a/gthumb/gth-viewer-page.c b/gthumb/gth-viewer-page.c
new file mode 100644
index 0000000..fecb51a
--- /dev/null
+++ b/gthumb/gth-viewer-page.c
@@ -0,0 +1,124 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include "gth-viewer-page.h"
+
+
+GType
+gth_viewer_page_get_type (void) {
+ static GType gth_viewer_page_type_id = 0;
+ if (gth_viewer_page_type_id == 0) {
+ static const GTypeInfo g_define_type_info = {
+ sizeof (GthViewerPageIface),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) NULL,
+ (GClassFinalizeFunc) NULL,
+ NULL,
+ 0,
+ 0,
+ (GInstanceInitFunc) NULL,
+ NULL
+ };
+ gth_viewer_page_type_id = g_type_register_static (G_TYPE_INTERFACE, "GthViewerPage", &g_define_type_info, 0);
+ }
+ return gth_viewer_page_type_id;
+}
+
+
+void
+gth_viewer_page_activate (GthViewerPage *self,
+ GthBrowser *browser)
+{
+ GTH_VIEWER_PAGE_GET_INTERFACE (self)->activate (self, browser);
+}
+
+
+void
+gth_viewer_page_deactivate (GthViewerPage *self)
+{
+ gth_viewer_page_hide (self);
+ GTH_VIEWER_PAGE_GET_INTERFACE (self)->deactivate (self);
+}
+
+
+void
+gth_viewer_page_show (GthViewerPage *self)
+{
+ GTH_VIEWER_PAGE_GET_INTERFACE (self)->show (self);
+}
+
+
+void
+gth_viewer_page_hide (GthViewerPage *self)
+{
+ GTH_VIEWER_PAGE_GET_INTERFACE (self)->hide (self);
+}
+
+
+gboolean
+gth_viewer_page_can_view (GthViewerPage *self,
+ GthFileData *file_data)
+{
+ return GTH_VIEWER_PAGE_GET_INTERFACE (self)->can_view (self, file_data);
+}
+
+
+void
+gth_viewer_page_view (GthViewerPage *self,
+ GthFileData *file_data)
+{
+ GTH_VIEWER_PAGE_GET_INTERFACE (self)->view (self, file_data);
+}
+
+
+void
+gth_viewer_page_update_sensitivity (GthViewerPage *self)
+{
+ GTH_VIEWER_PAGE_GET_INTERFACE (self)->update_sensitivity (self);
+}
+
+
+gboolean
+gth_viewer_page_can_save (GthViewerPage *self)
+{
+ return GTH_VIEWER_PAGE_GET_INTERFACE (self)->can_save (self);
+}
+
+
+void
+gth_viewer_page_save (GthViewerPage *self,
+ GFile *file,
+ FileSavedFunc func,
+ gpointer data)
+{
+ GTH_VIEWER_PAGE_GET_INTERFACE (self)->save (self, file, func, data);
+}
+
+void
+gth_viewer_page_save_as (GthViewerPage *self,
+ FileSavedFunc func,
+ gpointer data)
+{
+ GTH_VIEWER_PAGE_GET_INTERFACE (self)->save_as (self, func, data);
+}
diff --git a/gthumb/gth-viewer-page.h b/gthumb/gth-viewer-page.h
new file mode 100644
index 0000000..95aa770
--- /dev/null
+++ b/gthumb/gth-viewer-page.h
@@ -0,0 +1,90 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GTH_VIEWER_PAGE_H
+#define GTH_VIEWER_PAGE_H
+
+#include <gtk/gtk.h>
+#include "gth-browser.h"
+#include "gth-file-data.h"
+
+G_BEGIN_DECLS
+
+#define GTH_TYPE_VIEWER_PAGE (gth_viewer_page_get_type ())
+#define GTH_VIEWER_PAGE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTH_TYPE_VIEWER_PAGE, GthViewerPage))
+#define GTH_IS_VIEWER_PAGE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTH_TYPE_VIEWER_PAGE))
+#define GTH_VIEWER_PAGE_GET_INTERFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), GTH_TYPE_VIEWER_PAGE, GthViewerPageIface))
+
+typedef struct _GthViewerPage GthViewerPage;
+typedef struct _GthViewerPageIface GthViewerPageIface;
+
+typedef void (*FileSavedFunc) (GthViewerPage *viewer_page,
+ GthFileData *file_data,
+ GError *error,
+ gpointer user_data);
+
+struct _GthViewerPageIface {
+ GTypeInterface parent_iface;
+
+ void (*activate) (GthViewerPage *self,
+ GthBrowser *browser);
+ void (*deactivate) (GthViewerPage *self);
+ void (*show) (GthViewerPage *self);
+ void (*hide) (GthViewerPage *self);
+ gboolean (*can_view) (GthViewerPage *self,
+ GthFileData *file_data);
+ void (*view) (GthViewerPage *self,
+ GthFileData *file_data);
+ void (*update_sensitivity) (GthViewerPage *self);
+ gboolean (*can_save) (GthViewerPage *self);
+ void (*save) (GthViewerPage *self,
+ GFile *file,
+ FileSavedFunc func,
+ gpointer data);
+ void (*save_as) (GthViewerPage *self,
+ FileSavedFunc func,
+ gpointer data);
+};
+
+GType gth_viewer_page_get_type (void);
+void gth_viewer_page_activate (GthViewerPage *self,
+ GthBrowser *browser);
+void gth_viewer_page_deactivate (GthViewerPage *self);
+void gth_viewer_page_show (GthViewerPage *self);
+void gth_viewer_page_hide (GthViewerPage *self);
+gboolean gth_viewer_page_can_view (GthViewerPage *self,
+ GthFileData *file_data);
+void gth_viewer_page_view (GthViewerPage *self,
+ GthFileData *file_data);
+void gth_viewer_page_update_sensitivity (GthViewerPage *self);
+gboolean gth_viewer_page_can_save (GthViewerPage *self);
+void gth_viewer_page_save (GthViewerPage *self,
+ GFile *file,
+ FileSavedFunc func,
+ gpointer data);
+void gth_viewer_page_save_as (GthViewerPage *self,
+ FileSavedFunc func,
+ gpointer data);
+
+G_END_DECLS
+
+#endif /* GTH_VIEWER_PAGE_H */
diff --git a/gthumb/gth-window-actions-callbacks.c b/gthumb/gth-window-actions-callbacks.c
new file mode 100644
index 0000000..a022b1f
--- /dev/null
+++ b/gthumb/gth-window-actions-callbacks.c
@@ -0,0 +1,31 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2005-2008 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include "gth-window.h"
+
+void
+gth_window_activate_action_file_close_window (GtkAction *action,
+ gpointer data)
+{
+ gth_window_close ((GthWindow*)data);
+}
diff --git a/gthumb/gth-window-actions-callbacks.h b/gthumb/gth-window-actions-callbacks.h
new file mode 100644
index 0000000..bb5fa8e
--- /dev/null
+++ b/gthumb/gth-window-actions-callbacks.h
@@ -0,0 +1,32 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2005 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GTH_WINDOW_ACTIONS_CALLBACKS_H
+#define GTH_WINDOW_ACTIONS_CALLBACKS_H
+
+#include <gtk/gtkaction.h>
+
+#define DEFINE_ACTION(x) void x (GtkAction *action, gpointer data);
+
+DEFINE_ACTION(gth_window_activate_action_file_close_window)
+
+#endif /* GTH_WINDOW_ACTIONS_CALLBACK_H */
diff --git a/gthumb/gth-window-actions-entries.h b/gthumb/gth-window-actions-entries.h
new file mode 100644
index 0000000..4c0b05a
--- /dev/null
+++ b/gthumb/gth-window-actions-entries.h
@@ -0,0 +1,38 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2005-2008 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GTH_WINDOW_ACTION_ENTRIES_H
+#define GTH_WINDOW_ACTION_ENTRIES_H
+
+#include <config.h>
+#include <glib/gi18n.h>
+#include "gth-window-actions-callbacks.h"
+
+static GtkActionEntry gth_window_action_entries[] = {
+ { "File_CloseWindow", GTK_STOCK_CLOSE,
+ NULL, NULL,
+ N_("Close this window"),
+ G_CALLBACK (gth_window_activate_action_file_close_window) }
+};
+static guint gth_window_action_entries_size = G_N_ELEMENTS (gth_window_action_entries);
+
+#endif /* GTH_WINDOW_ACTION_ENTRIES_H */
diff --git a/gthumb/gth-window.c b/gthumb/gth-window.c
new file mode 100644
index 0000000..95426dc
--- /dev/null
+++ b/gthumb/gth-window.c
@@ -0,0 +1,346 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2005-2008 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <gtk/gtk.h>
+#include "gth-window.h"
+#include "gtk-utils.h"
+
+
+#define GTH_WINDOW_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GTH_TYPE_WINDOW, GthWindowPrivate))
+
+enum {
+ GTH_WINDOW_DUMMY_PROPERTY,
+ GTH_WINDOW_N_PAGES
+};
+
+
+static GtkWindowClass *parent_class = NULL;
+static GList *window_list = NULL;
+
+
+struct _GthWindowPrivate {
+ int n_pages;
+ int current_page;
+ GtkWidget *table;
+ GtkWidget *notebook;
+ GtkWidget **toolbar;
+ GtkWidget **content;
+};
+
+
+static void
+gth_window_set_n_pages (GthWindow *self,
+ int n_pages)
+{
+ int i;
+
+ self->priv->n_pages = n_pages;
+
+ self->priv->table = gtk_table_new (4, 1, FALSE);
+ gtk_widget_show (self->priv->table);
+ gtk_container_add (GTK_CONTAINER (self), self->priv->table);
+
+ self->priv->notebook = gtk_notebook_new ();
+ gtk_notebook_set_show_tabs (GTK_NOTEBOOK (self->priv->notebook), FALSE);
+ gtk_notebook_set_show_border (GTK_NOTEBOOK (self->priv->notebook), FALSE);
+ gtk_widget_show (self->priv->notebook);
+ gtk_table_attach (GTK_TABLE (self->priv->table),
+ self->priv->notebook,
+ 0, 1,
+ 2, 3,
+ GTK_EXPAND | GTK_FILL,
+ GTK_EXPAND | GTK_FILL,
+ 0, 0);
+
+ self->priv->toolbar = g_new0 (GtkWidget *, n_pages);
+ self->priv->content = g_new0 (GtkWidget *, n_pages);
+
+ for (i = 0; i < n_pages; i++) {
+ GtkWidget *page;
+
+ page = gtk_vbox_new (FALSE, 0);
+ gtk_widget_show (page);
+ gtk_notebook_append_page (GTK_NOTEBOOK (self->priv->notebook),
+ page,
+ NULL);
+
+ self->priv->toolbar[i] = gtk_hbox_new (FALSE, 0);
+ gtk_widget_show (self->priv->toolbar[i]);
+ gtk_box_pack_start (GTK_BOX (page), self->priv->toolbar[i], FALSE, FALSE, 0);
+
+ self->priv->content[i] = gtk_hbox_new (FALSE, 0);
+ gtk_widget_show (self->priv->content[i]);
+ gtk_box_pack_start (GTK_BOX (page), self->priv->content[i], TRUE, TRUE, 0);
+ }
+}
+
+
+static void
+gth_window_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GthWindow *self;
+
+ self = GTH_WINDOW (object);
+
+ switch (property_id) {
+ case GTH_WINDOW_N_PAGES:
+ gth_window_set_n_pages (self, g_value_get_int (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+
+static void
+gth_window_finalize (GObject *object)
+{
+ GthWindow *window = GTH_WINDOW (object);
+
+ g_free (window->priv->toolbar);
+ g_free (window->priv->content);
+
+ window_list = g_list_remove (window_list, window);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+
+ if (window_list == NULL)
+ gtk_main_quit ();
+}
+
+
+static gboolean
+gth_window_delete_event (GtkWidget *widget,
+ GdkEventAny *event)
+{
+ gth_window_close ((GthWindow*) widget);
+ return TRUE;
+}
+
+
+static void
+gth_window_real_close (GthWindow *window)
+{
+ /* virtual */
+}
+
+
+static void
+gth_window_real_set_current_page (GthWindow *window,
+ int page)
+{
+ if (window->priv->current_page == page)
+ return;
+ window->priv->current_page = page;
+ gtk_notebook_set_current_page (GTK_NOTEBOOK (window->priv->notebook), page);
+}
+
+
+static void
+gth_window_class_init (GthWindowClass *klass)
+{
+ GObjectClass *gobject_class;
+ GtkWidgetClass *widget_class;
+
+ parent_class = g_type_class_peek_parent (klass);
+ g_type_class_add_private (klass, sizeof (GthWindowPrivate));
+
+ gobject_class = (GObjectClass*) klass;
+ gobject_class->set_property = gth_window_set_property;
+ gobject_class->finalize = gth_window_finalize;
+
+ widget_class = (GtkWidgetClass*) klass;
+ widget_class->delete_event = gth_window_delete_event;
+
+ klass->close = gth_window_real_close;
+ klass->set_current_page = gth_window_real_set_current_page;
+
+ g_object_class_install_property (G_OBJECT_CLASS (klass),
+ GTH_WINDOW_N_PAGES,
+ g_param_spec_int ("n-pages",
+ "n-pages",
+ "n-pages",
+ 0,
+ G_MAXINT,
+ 1,
+ G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
+}
+
+
+static void
+gth_window_init (GthWindow *window)
+{
+ window->priv = GTH_WINDOW_GET_PRIVATE (window);
+ window->priv->table = NULL;
+ window->priv->content = NULL;
+ window->priv->n_pages = 0;
+ window->priv->current_page = -1;
+
+ window_list = g_list_prepend (window_list, window);
+}
+
+
+GType
+gth_window_get_type (void)
+{
+ static GType type = 0;
+
+ if (! type) {
+ GTypeInfo type_info = {
+ sizeof (GthWindowClass),
+ NULL,
+ NULL,
+ (GClassInitFunc) gth_window_class_init,
+ NULL,
+ NULL,
+ sizeof (GthWindow),
+ 0,
+ (GInstanceInitFunc) gth_window_init
+ };
+
+ type = g_type_register_static (GTK_TYPE_WINDOW,
+ "GthWindow",
+ &type_info,
+ 0);
+ }
+
+ return type;
+}
+
+
+void
+gth_window_close (GthWindow *window)
+{
+ GTH_WINDOW_GET_CLASS (window)->close (window);
+}
+
+
+void
+gth_window_attach (GthWindow *window,
+ GtkWidget *child,
+ GthWindowArea area)
+{
+ int position;
+
+ g_return_if_fail (window != NULL);
+ g_return_if_fail (GTH_IS_WINDOW (window));
+ g_return_if_fail (child != NULL);
+ g_return_if_fail (GTK_IS_WIDGET (child));
+
+ switch (area) {
+ case GTH_WINDOW_MENUBAR:
+ position = 0;
+ break;
+ case GTH_WINDOW_TOOLBAR:
+ position = 1;
+ break;
+ case GTH_WINDOW_STATUSBAR:
+ position = 3;
+ break;
+ default:
+ return;
+ }
+
+ gtk_table_attach (GTK_TABLE (window->priv->table),
+ child,
+ 0, 1,
+ position, position + 1,
+ GTK_EXPAND | GTK_FILL,
+ GTK_FILL,
+ 0, 0);
+}
+
+
+void
+gth_window_attach_toolbar (GthWindow *window,
+ int page,
+ GtkWidget *child)
+{
+ g_return_if_fail (window != NULL);
+ g_return_if_fail (GTH_IS_WINDOW (window));
+ g_return_if_fail (page >= 0 && page < window->priv->n_pages);
+ g_return_if_fail (child != NULL);
+ g_return_if_fail (GTK_IS_WIDGET (child));
+
+ _gtk_container_remove_children (GTK_CONTAINER (window->priv->toolbar[page]), NULL, NULL);
+ gtk_container_add (GTK_CONTAINER (window->priv->toolbar[page]), child);
+}
+
+
+void
+gth_window_attach_content (GthWindow *window,
+ int page,
+ GtkWidget *child)
+{
+ g_return_if_fail (window != NULL);
+ g_return_if_fail (GTH_IS_WINDOW (window));
+ g_return_if_fail (page >= 0 && page < window->priv->n_pages);
+ g_return_if_fail (child != NULL);
+ g_return_if_fail (GTK_IS_WIDGET (child));
+
+ _gtk_container_remove_children (GTK_CONTAINER (window->priv->content[page]), NULL, NULL);
+ gtk_container_add (GTK_CONTAINER (window->priv->content[page]), child);
+}
+
+
+void
+gth_window_set_current_page (GthWindow *window,
+ int page)
+{
+ GTH_WINDOW_GET_CLASS (window)->set_current_page (window, page);
+}
+
+
+int
+gth_window_get_current_page (GthWindow *window)
+{
+ return window->priv->current_page;
+}
+
+
+int
+gth_window_get_n_windows (void)
+{
+ return g_list_length (window_list);
+}
+
+
+GList *
+gth_window_get_window_list (void)
+{
+ return window_list;
+}
+
+
+GtkWidget *
+gth_window_get_current_window (void)
+{
+ if ((window_list == NULL) || (g_list_length (window_list) > 1))
+ return NULL;
+ else
+ return (GtkWidget *) window_list->data;
+}
diff --git a/gthumb/gth-window.h b/gthumb/gth-window.h
new file mode 100644
index 0000000..1ffab6e
--- /dev/null
+++ b/gthumb/gth-window.h
@@ -0,0 +1,87 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2005-2008 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GTH_WINDOW_H
+#define GTH_WINDOW_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+typedef enum { /*< skip >*/
+ GTH_WINDOW_MENUBAR,
+ GTH_WINDOW_TOOLBAR,
+ GTH_WINDOW_STATUSBAR,
+} GthWindowArea;
+
+#define GTH_TYPE_WINDOW (gth_window_get_type ())
+#define GTH_WINDOW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTH_TYPE_WINDOW, GthWindow))
+#define GTH_WINDOW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTH_TYPE_WINDOW, GthWindowClass))
+#define GTH_IS_WINDOW(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTH_TYPE_WINDOW))
+#define GTH_IS_WINDOW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTH_TYPE_WINDOW))
+#define GTH_WINDOW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GTH_TYPE_WINDOW, GthWindowClass))
+
+typedef struct _GthWindow GthWindow;
+typedef struct _GthWindowClass GthWindowClass;
+typedef struct _GthWindowPrivate GthWindowPrivate;
+
+struct _GthWindow
+{
+ GtkWindow __parent;
+ GthWindowPrivate *priv;
+};
+
+struct _GthWindowClass
+{
+ GtkWindowClass __parent_class;
+
+ /*< virtual functions >*/
+
+ void (*set_current_page) (GthWindow *window,
+ int page);
+ void (*close) (GthWindow *window);
+};
+
+GType gth_window_get_type (void);
+void gth_window_close (GthWindow *window);
+void gth_window_attach (GthWindow *window,
+ GtkWidget *child,
+ GthWindowArea area);
+void gth_window_attach_toolbar (GthWindow *window,
+ int page,
+ GtkWidget *child);
+void gth_window_attach_content (GthWindow *window,
+ int page,
+ GtkWidget *child);
+void gth_window_set_current_page (GthWindow *window,
+ int page);
+int gth_window_get_current_page (GthWindow *window);
+
+/**/
+
+int gth_window_get_n_windows (void);
+GList * gth_window_get_window_list (void);
+GtkWidget * gth_window_get_current_window (void);
+
+G_END_DECLS
+
+#endif /* GTH_WINDOW_H */
diff --git a/libgthumb/gthumb-error.c b/gthumb/gthumb-error.c
similarity index 100%
rename from libgthumb/gthumb-error.c
rename to gthumb/gthumb-error.c
diff --git a/gthumb/gthumb-error.h b/gthumb/gthumb-error.h
new file mode 100644
index 0000000..ce404cd
--- /dev/null
+++ b/gthumb/gthumb-error.h
@@ -0,0 +1,35 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2001 The Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __GTHUMB_ERROR_H__
+#define __GTHUMB_ERROR_H__
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+#define GTHUMB_ERROR gthumb_error_quark ()
+GQuark gthumb_error_quark (void);
+
+G_END_DECLS
+
+#endif /* __GTHUMB_ERROR_H__ */
diff --git a/gthumb/gthumb.h.template b/gthumb/gthumb.h.template
new file mode 100644
index 0000000..b0f05ce
--- /dev/null
+++ b/gthumb/gthumb.h.template
@@ -0,0 +1,26 @@
+/* Do not edit. Autogenerated from src/gthumb.h.template */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 The Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GTHUMB_H
+#define GTHUMB_H
+@@
+#endif /* GTHUMB_H */
diff --git a/gthumb/gtk-utils.c b/gthumb/gtk-utils.c
new file mode 100644
index 0000000..dd52bfa
--- /dev/null
+++ b/gthumb/gtk-utils.c
@@ -0,0 +1,1011 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * File-Roller
+ *
+ * Copyright (C) 2001-2008 The Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <string.h>
+#include "gconf-utils.h"
+#include "gtk-utils.h"
+#include "pixbuf-utils.h"
+
+#define REQUEST_ENTRY_WIDTH 220
+
+
+static GtkWidget *
+create_button (const char *stock_id,
+ const char *text)
+{
+ GtkWidget *button;
+ GtkWidget *hbox;
+ GtkWidget *image;
+ GtkWidget *label;
+ GtkWidget *align;
+ const char *label_text;
+ gboolean text_is_stock;
+ GtkStockItem stock_item;
+
+ button = gtk_button_new ();
+
+ if (gtk_stock_lookup (text, &stock_item)) {
+ label_text = stock_item.label;
+ text_is_stock = TRUE;
+ } else {
+ label_text = text;
+ text_is_stock = FALSE;
+ }
+
+ if (text_is_stock)
+ image = gtk_image_new_from_stock (text, GTK_ICON_SIZE_BUTTON);
+ else
+ image = gtk_image_new_from_stock (stock_id, GTK_ICON_SIZE_BUTTON);
+ label = gtk_label_new_with_mnemonic (label_text);
+ hbox = gtk_hbox_new (FALSE, 2);
+ align = gtk_alignment_new (0.5, 0.5, 0.0, 0.0);
+
+ GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
+ gtk_label_set_mnemonic_widget (GTK_LABEL (label), GTK_WIDGET (button));
+
+ gtk_box_pack_start (GTK_BOX (hbox), image, FALSE, FALSE, 0);
+ gtk_box_pack_end (GTK_BOX (hbox), label, FALSE, FALSE, 0);
+ gtk_container_add (GTK_CONTAINER (button), align);
+ gtk_container_add (GTK_CONTAINER (align), hbox);
+
+ gtk_widget_show_all (button);
+
+ return button;
+}
+
+
+GtkWidget*
+_gtk_message_dialog_new (GtkWindow *parent,
+ GtkDialogFlags flags,
+ const char *stock_id,
+ const char *message,
+ const char *secondary_message,
+ const char *first_button_text,
+ ...)
+{
+ GtkWidget *d;
+ GtkWidget *label;
+ GtkWidget *image;
+ GtkWidget *hbox;
+ va_list args;
+ const gchar *text;
+ int response_id;
+ char *escaped_message, *markup_text;
+
+ g_return_val_if_fail (message != NULL, NULL);
+
+ if (stock_id == NULL)
+ stock_id = GTK_STOCK_DIALOG_INFO;
+
+ d = gtk_dialog_new_with_buttons ("", parent, flags, NULL);
+ gtk_window_set_resizable (GTK_WINDOW (d), FALSE);
+
+ gtk_dialog_set_has_separator (GTK_DIALOG (d), FALSE);
+ gtk_container_set_border_width (GTK_CONTAINER (d), 6);
+ gtk_container_set_border_width (GTK_CONTAINER (GTK_DIALOG (d)->vbox), 6);
+ gtk_box_set_spacing (GTK_BOX (GTK_DIALOG (d)->vbox), 8);
+
+ /* Add label and image */
+
+ image = gtk_image_new_from_stock (stock_id, GTK_ICON_SIZE_DIALOG);
+ gtk_misc_set_alignment (GTK_MISC (image), 0.5, 0.0);
+
+ label = gtk_label_new ("");
+
+ escaped_message = g_markup_escape_text (message, -1);
+ if (secondary_message != NULL) {
+ char *escaped_secondary_message = g_markup_escape_text (secondary_message, -1);
+ markup_text = g_strdup_printf ("<span weight=\"bold\" size=\"larger\">%s</span>\n\n%s",
+ escaped_message,
+ escaped_secondary_message);
+ g_free (escaped_secondary_message);
+ } else
+ markup_text = g_strdup (escaped_message);
+ gtk_label_set_markup (GTK_LABEL (label), markup_text);
+ g_free (markup_text);
+ g_free (escaped_message);
+
+ gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
+ gtk_label_set_selectable (GTK_LABEL (label), TRUE);
+
+ hbox = gtk_hbox_new (FALSE, 6);
+ gtk_container_set_border_width (GTK_CONTAINER (hbox), 6);
+
+ gtk_box_pack_start (GTK_BOX (hbox), image,
+ FALSE, FALSE, 0);
+
+ gtk_box_pack_start (GTK_BOX (hbox), label,
+ TRUE, TRUE, 0);
+
+ gtk_box_pack_start (GTK_BOX (GTK_DIALOG (d)->vbox),
+ hbox,
+ FALSE, FALSE, 0);
+
+ gtk_widget_show_all (hbox);
+
+ /* Add buttons */
+
+ if (first_button_text == NULL)
+ return d;
+
+ va_start (args, first_button_text);
+
+ text = first_button_text;
+ response_id = va_arg (args, gint);
+
+ while (text != NULL) {
+ gtk_dialog_add_button (GTK_DIALOG (d), text, response_id);
+
+ text = va_arg (args, gchar*);
+ if (text == NULL)
+ break;
+ response_id = va_arg (args, int);
+ }
+
+ va_end (args);
+
+ gtk_dialog_set_default_response (GTK_DIALOG (d), GTK_RESPONSE_YES);
+
+ return d;
+}
+
+
+char *
+_gtk_request_dialog_run (GtkWindow *parent,
+ GtkDialogFlags flags,
+ const char *message,
+ const char *default_value,
+ int max_length,
+ const char *no_button_text,
+ const char *yes_button_text)
+{
+ GtkWidget *d;
+ GtkWidget *label;
+ GtkWidget *image;
+ GtkWidget *hbox;
+ GtkWidget *vbox;
+ GtkWidget *entry;
+ GtkWidget *button;
+ char *result = NULL;
+ char *stock_id = GTK_STOCK_DIALOG_QUESTION;
+
+ d = gtk_dialog_new_with_buttons ("", parent, flags, NULL);
+ gtk_window_set_resizable (GTK_WINDOW (d), FALSE);
+
+ gtk_dialog_set_has_separator (GTK_DIALOG (d), FALSE);
+ gtk_container_set_border_width (GTK_CONTAINER (d), 6);
+ gtk_container_set_border_width (GTK_CONTAINER (GTK_DIALOG (d)->vbox), 6);
+ gtk_box_set_spacing (GTK_BOX (GTK_DIALOG (d)->vbox), 12);
+
+ /* Add label and image */
+
+ image = gtk_image_new_from_stock (stock_id, GTK_ICON_SIZE_DIALOG);
+ gtk_misc_set_alignment (GTK_MISC (image), 0.5, 0.0);
+
+ label = gtk_label_new (message);
+ gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
+ gtk_label_set_selectable (GTK_LABEL (label), FALSE);
+ gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+
+ entry = gtk_entry_new ();
+ gtk_widget_set_size_request (entry, REQUEST_ENTRY_WIDTH, -1);
+ gtk_entry_set_max_length (GTK_ENTRY (entry), max_length);
+ gtk_entry_set_text (GTK_ENTRY (entry), default_value);
+ gtk_entry_set_activates_default (GTK_ENTRY (entry), TRUE);
+
+ hbox = gtk_hbox_new (FALSE, 6);
+ vbox = gtk_vbox_new (FALSE, 6);
+
+ gtk_container_set_border_width (GTK_CONTAINER (hbox), 6);
+ gtk_box_set_spacing (GTK_BOX (hbox), 12);
+ gtk_box_set_spacing (GTK_BOX (vbox), 6);
+
+ gtk_box_pack_start (GTK_BOX (vbox), label,
+ TRUE, TRUE, 0);
+
+ gtk_box_pack_start (GTK_BOX (vbox), entry,
+ FALSE, FALSE, 0);
+
+ gtk_box_pack_start (GTK_BOX (hbox), image,
+ FALSE, FALSE, 0);
+
+ gtk_box_pack_start (GTK_BOX (hbox), vbox,
+ TRUE, TRUE, 0);
+
+ gtk_box_pack_start (GTK_BOX (GTK_DIALOG (d)->vbox),
+ hbox,
+ FALSE, FALSE, 0);
+
+ gtk_widget_show_all (hbox);
+
+ /* Add buttons */
+
+ button = create_button (GTK_STOCK_CANCEL, no_button_text);
+ gtk_dialog_add_action_widget (GTK_DIALOG (d),
+ button,
+ GTK_RESPONSE_CANCEL);
+
+ /**/
+
+ button = create_button (GTK_STOCK_OK, yes_button_text);
+ gtk_dialog_add_action_widget (GTK_DIALOG (d),
+ button,
+ GTK_RESPONSE_YES);
+
+ /**/
+
+ gtk_dialog_set_default_response (GTK_DIALOG (d),
+ GTK_RESPONSE_YES);
+ gtk_widget_grab_focus (entry);
+
+ /* Run dialog */
+
+ if ((gtk_dialog_run (GTK_DIALOG (d)) == GTK_RESPONSE_YES) &&
+ (strlen (gtk_entry_get_text (GTK_ENTRY (entry))) > 0) )
+ /* Normalize unicode text to "NFC" form for consistency. */
+ result = g_utf8_normalize (gtk_entry_get_text (GTK_ENTRY (entry)),
+ -1,
+ G_NORMALIZE_NFC);
+ else
+ result = NULL;
+
+ gtk_widget_destroy (d);
+
+ return result;
+}
+
+
+GtkWidget*
+_gtk_yesno_dialog_new (GtkWindow *parent,
+ GtkDialogFlags flags,
+ const char *message,
+ const char *no_button_text,
+ const char *yes_button_text)
+{
+ GtkWidget *d;
+ GtkWidget *label;
+ GtkWidget *image;
+ GtkWidget *hbox;
+ GtkWidget *button;
+ char *stock_id = GTK_STOCK_DIALOG_QUESTION;
+
+ d = gtk_dialog_new_with_buttons ("", parent, flags, NULL);
+ gtk_window_set_resizable (GTK_WINDOW (d), FALSE);
+
+ gtk_dialog_set_has_separator (GTK_DIALOG (d), FALSE);
+ gtk_container_set_border_width (GTK_CONTAINER (d), 6);
+ gtk_container_set_border_width (GTK_CONTAINER (GTK_DIALOG (d)->vbox), 6);
+ gtk_box_set_spacing (GTK_BOX (GTK_DIALOG (d)->vbox), 8);
+
+ /* Add label and image */
+
+ image = gtk_image_new_from_stock (stock_id, GTK_ICON_SIZE_DIALOG);
+ gtk_misc_set_alignment (GTK_MISC (image), 0.5, 0.0);
+
+ label = gtk_label_new (message);
+ gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
+ gtk_label_set_selectable (GTK_LABEL (label), TRUE);
+
+ hbox = gtk_hbox_new (FALSE, 12);
+ gtk_container_set_border_width (GTK_CONTAINER (hbox), 6);
+
+ gtk_box_pack_start (GTK_BOX (hbox), image,
+ FALSE, FALSE, 0);
+
+ gtk_box_pack_start (GTK_BOX (hbox), label,
+ TRUE, TRUE, 0);
+
+ gtk_box_pack_start (GTK_BOX (GTK_DIALOG (d)->vbox),
+ hbox,
+ FALSE, FALSE, 0);
+
+ gtk_widget_show_all (hbox);
+
+ /* Add buttons */
+
+ button = create_button (GTK_STOCK_CANCEL, no_button_text);
+ gtk_dialog_add_action_widget (GTK_DIALOG (d),
+ button,
+ GTK_RESPONSE_CANCEL);
+
+ /**/
+
+ button = create_button (GTK_STOCK_OK, yes_button_text);
+ gtk_dialog_add_action_widget (GTK_DIALOG (d),
+ button,
+ GTK_RESPONSE_YES);
+
+ /**/
+
+ gtk_dialog_set_default_response (GTK_DIALOG (d), GTK_RESPONSE_YES);
+
+ return d;
+}
+
+
+static void
+yesno__check_button_toggled_cb (GtkToggleButton *button,
+ const char *gconf_key)
+{
+ eel_gconf_set_boolean (gconf_key,
+ ! gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)));
+}
+
+
+GtkWidget*
+_gtk_yesno_dialog_with_checkbutton_new (GtkWindow *parent,
+ GtkDialogFlags flags,
+ const char *message,
+ const char *no_button_text,
+ const char *yes_button_text,
+ const char *check_button_label,
+ const char *gconf_key)
+{
+ GtkWidget *d;
+ GtkWidget *label;
+ GtkWidget *image;
+ GtkWidget *hbox;
+ GtkWidget *button;
+ GtkWidget *check_button;
+ char *stock_id = GTK_STOCK_DIALOG_QUESTION;
+
+ d = gtk_dialog_new_with_buttons ("", parent, flags, NULL);
+ gtk_window_set_resizable (GTK_WINDOW (d), FALSE);
+
+ gtk_dialog_set_has_separator (GTK_DIALOG (d), FALSE);
+ gtk_container_set_border_width (GTK_CONTAINER (d), 6);
+ gtk_container_set_border_width (GTK_CONTAINER (GTK_DIALOG (d)->vbox), 6);
+ gtk_box_set_spacing (GTK_BOX (GTK_DIALOG (d)->vbox), 8);
+
+ /* Add label and image */
+
+ image = gtk_image_new_from_stock (stock_id, GTK_ICON_SIZE_DIALOG);
+ gtk_misc_set_alignment (GTK_MISC (image), 0.5, 0.0);
+
+ label = gtk_label_new (message);
+ gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
+ gtk_label_set_selectable (GTK_LABEL (label), TRUE);
+
+ hbox = gtk_hbox_new (FALSE, 12);
+ gtk_container_set_border_width (GTK_CONTAINER (hbox), 6);
+
+ gtk_box_pack_start (GTK_BOX (hbox), image,
+ FALSE, FALSE, 0);
+
+ gtk_box_pack_start (GTK_BOX (hbox), label,
+ TRUE, TRUE, 0);
+
+ gtk_box_pack_start (GTK_BOX (GTK_DIALOG (d)->vbox),
+ hbox,
+ FALSE, FALSE, 0);
+
+ /* Add checkbutton */
+
+ check_button = gtk_check_button_new_with_mnemonic (check_button_label);
+ gtk_box_pack_start (GTK_BOX (GTK_DIALOG (d)->vbox),
+ check_button,
+ FALSE, FALSE, 0);
+ gtk_widget_show (check_button);
+
+ gtk_widget_show_all (hbox);
+
+ /* Add buttons */
+
+ button = create_button (GTK_STOCK_CANCEL, no_button_text);
+ gtk_dialog_add_action_widget (GTK_DIALOG (d),
+ button,
+ GTK_RESPONSE_CANCEL);
+
+ /**/
+
+ button = create_button (GTK_STOCK_OK, yes_button_text);
+ gtk_dialog_add_action_widget (GTK_DIALOG (d),
+ button,
+ GTK_RESPONSE_YES);
+
+ /**/
+
+ gtk_dialog_set_default_response (GTK_DIALOG (d), GTK_RESPONSE_YES);
+
+ g_signal_connect (G_OBJECT (check_button),
+ "toggled",
+ G_CALLBACK (yesno__check_button_toggled_cb),
+ (gpointer) gconf_key);
+
+ return d;
+}
+
+
+GtkWidget*
+_gtk_message_dialog_with_checkbutton_new (GtkWindow *parent,
+ GtkDialogFlags flags,
+ const char *stock_id,
+ const char *message,
+ const char *secondary_message,
+ const char *gconf_key,
+ const char *check_button_label,
+ const char *first_button_text,
+ ...)
+{
+ GtkWidget *d;
+ GtkWidget *label;
+ GtkWidget *image;
+ GtkWidget *hbox;
+ GtkWidget *check_button;
+ va_list args;
+ const char *text;
+ int response_id;
+ char *escaped_message, *markup_text;
+
+ g_return_val_if_fail (message != NULL, NULL);
+
+ if (stock_id == NULL)
+ stock_id = GTK_STOCK_DIALOG_INFO;
+
+ d = gtk_dialog_new_with_buttons ("", parent, flags, NULL);
+ gtk_window_set_resizable (GTK_WINDOW (d), FALSE);
+
+ gtk_dialog_set_has_separator (GTK_DIALOG (d), FALSE);
+ gtk_container_set_border_width (GTK_CONTAINER (d), 6);
+ gtk_container_set_border_width (GTK_CONTAINER (GTK_DIALOG (d)->vbox), 6);
+ gtk_box_set_spacing (GTK_BOX (GTK_DIALOG (d)->vbox), 8);
+
+ /* Add label and image */
+
+ image = gtk_image_new_from_stock (stock_id, GTK_ICON_SIZE_DIALOG);
+ gtk_misc_set_alignment (GTK_MISC (image), 0.5, 0.0);
+
+ label = gtk_label_new ("");
+
+ escaped_message = g_markup_escape_text (message, -1);
+ if (secondary_message != NULL) {
+ char *escaped_secondary_message = g_markup_escape_text (secondary_message, -1);
+ markup_text = g_strdup_printf ("<span weight=\"bold\" size=\"larger\">%s</span>\n\n%s",
+ escaped_message,
+ escaped_secondary_message);
+ g_free (escaped_secondary_message);
+ } else
+ markup_text = g_strdup (escaped_message);
+ gtk_label_set_markup (GTK_LABEL (label), markup_text);
+ g_free (markup_text);
+ g_free (escaped_message);
+
+ gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
+ gtk_label_set_selectable (GTK_LABEL (label), TRUE);
+
+ hbox = gtk_hbox_new (FALSE, 6);
+ gtk_container_set_border_width (GTK_CONTAINER (hbox), 6);
+
+ gtk_box_pack_start (GTK_BOX (hbox), image,
+ FALSE, FALSE, 0);
+
+ gtk_box_pack_start (GTK_BOX (hbox), label,
+ TRUE, TRUE, 0);
+
+ gtk_box_pack_start (GTK_BOX (GTK_DIALOG (d)->vbox),
+ hbox,
+ FALSE, FALSE, 0);
+
+ gtk_widget_show_all (hbox);
+
+ /* Add checkbutton */
+
+ check_button = gtk_check_button_new_with_mnemonic (check_button_label);
+ gtk_box_pack_start (GTK_BOX (GTK_DIALOG (d)->vbox),
+ check_button,
+ FALSE, FALSE, 0);
+ gtk_widget_show (check_button);
+
+ /* Add buttons */
+
+ if (first_button_text == NULL)
+ return d;
+
+ va_start (args, first_button_text);
+
+ text = first_button_text;
+ response_id = va_arg (args, gint);
+
+ while (text != NULL) {
+ gtk_dialog_add_button (GTK_DIALOG (d), text, response_id);
+
+ text = va_arg (args, gchar*);
+ if (text == NULL)
+ break;
+ response_id = va_arg (args, int);
+ }
+
+ va_end (args);
+
+ gtk_dialog_set_default_response (GTK_DIALOG (d), GTK_RESPONSE_YES);
+
+ g_signal_connect (G_OBJECT (check_button),
+ "toggled",
+ G_CALLBACK (yesno__check_button_toggled_cb),
+ (gpointer) gconf_key);
+
+ return d;
+}
+
+
+void
+_gtk_error_dialog_from_gerror_run (GtkWindow *parent,
+ const char *title,
+ GError **gerror)
+{
+ GtkWidget *d;
+
+ g_return_if_fail (*gerror != NULL);
+
+ d = _gtk_message_dialog_new (parent,
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_STOCK_DIALOG_ERROR,
+ title,
+ (*gerror)->message,
+ GTK_STOCK_OK, GTK_RESPONSE_OK,
+ NULL);
+ gtk_dialog_run (GTK_DIALOG (d));
+
+ gtk_widget_destroy (d);
+ g_clear_error (gerror);
+}
+
+
+static void
+error_dialog_response_cb (GtkDialog *dialog,
+ int response,
+ gpointer user_data)
+{
+ gtk_widget_destroy (GTK_WIDGET (dialog));
+}
+
+
+void
+_gtk_error_dialog_from_gerror_show (GtkWindow *parent,
+ const char *title,
+ GError **gerror)
+{
+ GtkWidget *d;
+
+ g_return_if_fail (*gerror != NULL);
+
+ d = _gtk_message_dialog_new (parent,
+ GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_STOCK_DIALOG_ERROR,
+ title,
+ (*gerror)->message,
+ GTK_STOCK_OK, GTK_RESPONSE_OK,
+ NULL);
+ g_signal_connect (d, "response", G_CALLBACK (error_dialog_response_cb), NULL);
+
+ gtk_window_present (GTK_WINDOW (d));
+
+ g_clear_error (gerror);
+}
+
+
+
+void
+_gtk_error_dialog_run (GtkWindow *parent,
+ const gchar *format,
+ ...)
+{
+ GtkWidget *d;
+ char *message;
+ va_list args;
+
+ va_start (args, format);
+ message = g_strdup_vprintf (format, args);
+ va_end (args);
+
+ d = _gtk_message_dialog_new (parent,
+ GTK_DIALOG_MODAL,
+ GTK_STOCK_DIALOG_ERROR,
+ message,
+ NULL,
+ GTK_STOCK_CLOSE, GTK_RESPONSE_CANCEL,
+ NULL);
+ g_free (message);
+
+ g_signal_connect (G_OBJECT (d), "response",
+ G_CALLBACK (gtk_widget_destroy),
+ NULL);
+
+ gtk_widget_show (d);
+}
+
+
+void
+_gtk_info_dialog_run (GtkWindow *parent,
+ const gchar *format,
+ ...)
+{
+ GtkWidget *d;
+ char *message;
+ va_list args;
+
+ va_start (args, format);
+ message = g_strdup_vprintf (format, args);
+ va_end (args);
+
+ d = _gtk_message_dialog_new (parent,
+ GTK_DIALOG_MODAL,
+ GTK_STOCK_DIALOG_INFO,
+ message,
+ NULL,
+ GTK_STOCK_CLOSE, GTK_RESPONSE_CANCEL,
+ NULL);
+ g_free (message);
+
+ g_signal_connect (G_OBJECT (d), "response",
+ G_CALLBACK (gtk_widget_destroy),
+ NULL);
+
+ gtk_widget_show (d);
+}
+
+
+static GdkPixbuf *
+get_themed_icon_pixbuf (GThemedIcon *icon,
+ int size,
+ GtkIconTheme *icon_theme)
+{
+ char **icon_names;
+ GtkIconInfo *icon_info;
+ GdkPixbuf *pixbuf;
+ GError *error = NULL;
+
+ g_object_get (icon, "names", &icon_names, NULL);
+
+ icon_info = gtk_icon_theme_choose_icon (icon_theme, (const char **)icon_names, size, 0);
+ if (icon_info == NULL)
+ icon_info = gtk_icon_theme_lookup_icon (icon_theme, "text-x-generic", size, GTK_ICON_LOOKUP_USE_BUILTIN);
+
+ pixbuf = gtk_icon_info_load_icon (icon_info, &error);
+ if (pixbuf == NULL) {
+ g_warning ("could not load icon pixbuf: %s\n", error->message);
+ g_clear_error (&error);
+ }
+
+ gtk_icon_info_free (icon_info);
+ g_strfreev (icon_names);
+
+ return pixbuf;
+}
+
+
+static GdkPixbuf *
+get_file_icon_pixbuf (GFileIcon *icon,
+ int size)
+{
+ GFile *file;
+ char *filename;
+ GdkPixbuf *pixbuf;
+
+ file = g_file_icon_get_file (icon);
+ filename = g_file_get_path (file);
+ pixbuf = gdk_pixbuf_new_from_file_at_size (filename, size, -1, NULL);
+ g_free (filename);
+ g_object_unref (file);
+
+ return pixbuf;
+}
+
+
+GdkPixbuf *
+_g_icon_get_pixbuf (GIcon *icon,
+ int size,
+ GtkIconTheme *theme)
+{
+ GdkPixbuf *pixbuf;
+ int w, h;
+
+ if (icon == NULL)
+ return NULL;
+
+ if (G_IS_THEMED_ICON (icon))
+ pixbuf = get_themed_icon_pixbuf (G_THEMED_ICON (icon), size, theme);
+ if (G_IS_FILE_ICON (icon))
+ pixbuf = get_file_icon_pixbuf (G_FILE_ICON (icon), size);
+
+ if (pixbuf == NULL)
+ return NULL;
+
+ w = gdk_pixbuf_get_width (pixbuf);
+ h = gdk_pixbuf_get_height (pixbuf);
+ if (scale_keeping_ratio (&w, &h, size, size, FALSE)) {
+ GdkPixbuf *scaled;
+
+ scaled = gdk_pixbuf_scale_simple (pixbuf, w, h, GDK_INTERP_BILINEAR);
+ g_object_unref (pixbuf);
+ pixbuf = scaled;
+ }
+
+ return pixbuf;
+}
+
+
+GdkPixbuf *
+get_mime_type_pixbuf (const char *mime_type,
+ int icon_size,
+ GtkIconTheme *icon_theme)
+{
+ GdkPixbuf *pixbuf = NULL;
+ GIcon *icon;
+
+ if (icon_theme == NULL)
+ icon_theme = gtk_icon_theme_get_default ();
+
+ icon = g_content_type_get_icon (mime_type);
+ pixbuf = _g_icon_get_pixbuf (icon, icon_size, icon_theme);
+ g_object_unref (icon);
+
+ return pixbuf;
+}
+
+
+int
+_gtk_icon_get_pixel_size (GtkWidget *widget,
+ GtkIconSize size)
+{
+ int icon_width, icon_height;
+
+ gtk_icon_size_lookup_for_settings (gtk_widget_get_settings (widget),
+ size,
+ &icon_width, &icon_height);
+ return MAX (icon_width, icon_height);
+}
+
+
+void
+show_help_dialog (GtkWindow *parent,
+ const char *section)
+{
+ char *uri;
+ GError *error = NULL;
+
+ uri = g_strconcat ("ghelp:file-roller", section ? "?" : NULL, section, NULL);
+ if (! gtk_show_uri (gtk_window_get_screen (parent), uri, GDK_CURRENT_TIME, &error)) {
+ GtkWidget *dialog;
+
+ dialog = _gtk_message_dialog_new (parent,
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_STOCK_DIALOG_ERROR,
+ _("Could not display help"),
+ error->message,
+ GTK_STOCK_OK, GTK_RESPONSE_OK,
+ NULL);
+ gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK);
+
+ g_signal_connect (G_OBJECT (dialog), "response",
+ G_CALLBACK (gtk_widget_destroy),
+ NULL);
+
+ gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE);
+
+ gtk_widget_show (dialog);
+
+ g_clear_error (&error);
+ }
+ g_free (uri);
+}
+
+
+void
+_gtk_container_remove_children (GtkContainer *container,
+ gpointer start_after_this,
+ gpointer stop_before_this)
+{
+ GList *children;
+ GList *scan;
+
+ children = gtk_container_get_children (container);
+
+ if (start_after_this != NULL) {
+ for (scan = children; scan; scan = scan->next)
+ if (scan->data == start_after_this) {
+ scan = scan->next;
+ break;
+ }
+ }
+ else
+ scan = children;
+
+ for (/* void */; scan && (scan->data != stop_before_this); scan = scan->next)
+ gtk_container_remove (container, scan->data);
+ g_list_free (children);
+}
+
+
+int
+_gtk_container_get_pos (GtkContainer *container,
+ GtkWidget *child)
+{
+ GList *children;
+ gboolean found = FALSE;
+ int idx;
+ GList *scan;
+
+ children = gtk_container_get_children (container);
+ for (idx = 0, scan = children; ! found && scan; idx++, scan = scan->next) {
+ if (child == scan->data) {
+ found = TRUE;
+ break;
+ }
+ }
+ g_list_free (children);
+
+ return ! found ? -1 : idx;
+}
+
+
+guint
+_gtk_container_get_n_children (GtkContainer *container)
+{
+ GList *children;
+ guint len;
+
+ children = gtk_container_get_children (container);
+ len = g_list_length (children);
+ g_list_free (children);
+
+ return len;
+}
+
+
+GtkBuilder *
+_gtk_builder_new_from_file (const char *ui_file,
+ const char *extension)
+{
+ char *filename;
+ GtkBuilder *builder;
+ GError *error = NULL;
+
+#ifdef RUN_IN_PLACE
+ if (extension == NULL)
+ filename = g_build_filename (GTHUMB_UI_DIR, ui_file, NULL);
+ else
+ filename = g_build_filename (GTHUMB_EXTENSIONS_UI_DIR, extension, "data", "ui", ui_file, NULL);
+#else
+ filename = g_build_filename (GTHUMB_UI_DIR, ui_file, NULL);
+#endif
+
+ builder = gtk_builder_new ();
+ if (! gtk_builder_add_from_file (builder, filename, &error)) {
+ g_warning ("%s\n", error->message);
+ g_clear_error (&error);
+ }
+ g_free (filename);
+
+ return builder;
+}
+
+
+GtkWidget *
+_gtk_builder_get_widget (GtkBuilder *builder,
+ const char *name)
+{
+ return (GtkWidget *) gtk_builder_get_object (builder, name);
+}
+
+
+GtkWidget *
+_gtk_combo_box_new_with_texts (const char *first_text,
+ ...)
+{
+ GtkWidget *combo_box;
+ va_list args;
+ const char *text;
+
+ combo_box = gtk_combo_box_new_text ();
+
+ va_start (args, first_text);
+
+ text = first_text;
+ while (text != NULL) {
+ gtk_combo_box_append_text (GTK_COMBO_BOX (combo_box), text);
+ text = va_arg (args, const char *);
+ }
+
+ va_end (args);
+
+ return combo_box;
+}
+
+
+void
+_gtk_combo_box_append_texts (GtkComboBox *combo_box,
+ const char *first_text,
+ ...)
+{
+ va_list args;
+ const char *text;
+
+ va_start (args, first_text);
+
+ text = first_text;
+ while (text != NULL) {
+ gtk_combo_box_append_text (combo_box, text);
+ text = va_arg (args, const char *);
+ }
+
+ va_end (args);
+}
+
+
+GtkWidget *
+_gtk_image_new_from_xpm_data (char * xpm_data[])
+{
+ GdkPixbuf *pixbuf;
+ GtkWidget *image;
+
+ pixbuf = gdk_pixbuf_new_from_xpm_data ((const char**) xpm_data);
+ image = gtk_image_new_from_pixbuf (pixbuf);
+ gtk_widget_show (image);
+
+ g_object_unref (G_OBJECT (pixbuf));
+
+ return image;
+}
+
+
+GtkWidget *
+_gtk_image_new_from_inline (const guint8 *data)
+{
+ GdkPixbuf *pixbuf;
+ GtkWidget *image;
+
+ pixbuf = gdk_pixbuf_new_from_inline (-1, data, FALSE, NULL);
+ image = gtk_image_new_from_pixbuf (pixbuf);
+ gtk_widget_show (image);
+
+ g_object_unref (G_OBJECT (pixbuf));
+
+ return image;
+}
+
+
+void
+_gtk_widget_get_screen_size (GtkWidget *widget,
+ int *width,
+ int *height)
+{
+ GdkScreen *screen;
+ GdkRectangle screen_geom;
+
+ screen = gtk_widget_get_screen (widget);
+ gdk_screen_get_monitor_geometry (screen,
+ gdk_screen_get_monitor_at_window (screen, widget->window),
+ &screen_geom);
+
+ *width = screen_geom.width;
+ *height = screen_geom.height;
+}
+
+
+void
+_gtk_tree_path_list_free (GList *list)
+{
+ g_list_foreach (list, (GFunc) gtk_tree_path_free, NULL);
+ g_list_free (list);
+}
diff --git a/gthumb/gtk-utils.h b/gthumb/gtk-utils.h
new file mode 100644
index 0000000..98ce8c3
--- /dev/null
+++ b/gthumb/gtk-utils.h
@@ -0,0 +1,120 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * File-Roller
+ *
+ * Copyright (C) 2001-2008 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GTK_UTILS_H
+#define GTK_UTILS_H
+
+#include <glib/gi18n.h>
+#include <gio/gio.h>
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+GtkWidget* _gtk_message_dialog_new (GtkWindow *parent,
+ GtkDialogFlags flags,
+ const char *stock_id,
+ const char *message,
+ const char *secondary_message,
+ const char *first_button_text,
+ ...);
+GtkWidget*
+_gtk_message_dialog_with_checkbutton_new (GtkWindow *parent,
+ GtkDialogFlags flags,
+ const char *stock_id,
+ const char *message,
+ const char *secondary_message,
+ const char *gconf_key,
+ const char *check_button_label,
+ const char *first_button_text,
+ ...);
+gchar* _gtk_request_dialog_run (GtkWindow *parent,
+ GtkDialogFlags flags,
+ const char *message,
+ const char *default_value,
+ int max_length,
+ const char *no_button_text,
+ const char *yes_button_text);
+GtkWidget* _gtk_yesno_dialog_new (GtkWindow *parent,
+ GtkDialogFlags flags,
+ const char *message,
+ const char *no_button_text,
+ const char *yes_button_text);
+GtkWidget*
+_gtk_yesno_dialog_with_checkbutton_new (GtkWindow *parent,
+ GtkDialogFlags flags,
+ const char *message,
+ const char *no_button_text,
+ const char *yes_button_text,
+ const char *check_button_label,
+ const char *gconf_key);
+void
+_gtk_error_dialog_from_gerror_run (GtkWindow *parent,
+ const char *title,
+ GError **gerror);
+void
+_gtk_error_dialog_from_gerror_show (GtkWindow *parent,
+ const char *title,
+ GError **gerror);
+void _gtk_error_dialog_run (GtkWindow *parent,
+ const gchar *format,
+ ...) G_GNUC_PRINTF (2, 3);
+void _gtk_info_dialog_run (GtkWindow *parent,
+ const gchar *format,
+ ...) G_GNUC_PRINTF (2, 3);
+GdkPixbuf * _g_icon_get_pixbuf (GIcon *icon,
+ int size,
+ GtkIconTheme *icon_theme);
+GdkPixbuf * get_mime_type_pixbuf (const char *mime_type,
+ int icon_size,
+ GtkIconTheme *icon_theme);
+int _gtk_icon_get_pixel_size (GtkWidget *widget,
+ GtkIconSize size);
+void show_help_dialog (GtkWindow *parent,
+ const char *section);
+void _gtk_container_remove_children
+ (GtkContainer *container,
+ gpointer start_after_this,
+ gpointer stop_before_this);
+int _gtk_container_get_pos (GtkContainer *container,
+ GtkWidget *child);
+guint _gtk_container_get_n_children (GtkContainer *container);
+GtkBuilder *
+ _gtk_builder_new_from_file (const char *filename,
+ const char *extension);
+GtkWidget *
+ _gtk_builder_get_widget (GtkBuilder *builder,
+ const char *name);
+GtkWidget * _gtk_combo_box_new_with_texts (const char *first_text,
+ ...);
+void _gtk_combo_box_append_texts (GtkComboBox *combo_box,
+ const char *first_text,
+ ...);
+GtkWidget * _gtk_image_new_from_xpm_data (char *xpm_data[]);
+GtkWidget * _gtk_image_new_from_inline (const guint8 *data);
+void _gtk_widget_get_screen_size (GtkWidget *widget,
+ int *width,
+ int *height);
+void _gtk_tree_path_list_free (GList *list);
+
+G_END_DECLS
+
+#endif
diff --git a/gthumb/icons/Makefile.am b/gthumb/icons/Makefile.am
new file mode 100644
index 0000000..3c5f635
--- /dev/null
+++ b/gthumb/icons/Makefile.am
@@ -0,0 +1,5 @@
+IMAGES = nav_button.xpm
+
+EXTRA_DIST = $(IMAGES)
+
+-include $(top_srcdir)/git.mk
diff --git a/libgthumb/icons/nav_button.xpm b/gthumb/icons/nav_button.xpm
similarity index 100%
rename from libgthumb/icons/nav_button.xpm
rename to gthumb/icons/nav_button.xpm
diff --git a/gthumb/main.c b/gthumb/main.c
new file mode 100644
index 0000000..ee45576
--- /dev/null
+++ b/gthumb/main.c
@@ -0,0 +1,345 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2001-2009 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include <unique/unique.h>
+#include "eggsmclient.h"
+#include "gth-browser.h"
+#include "gth-file-data.h"
+#include "gth-file-source-vfs.h"
+#include "gth-main.h"
+#include "gth-preferences.h"
+
+
+enum {
+ COMMAND_UNUSED,
+ COMMAND_IMPORT_PHOTOS
+};
+
+
+gboolean NewWindow = FALSE;
+gboolean StartInFullscreen = FALSE;
+gboolean StartSlideshow = FALSE;
+gboolean ImportPhotos = FALSE;
+
+
+static UniqueApp *gthumb_app;
+static char **remaining_args;
+static const char *program_argv0; /* argv[0] from main(); used as the command to restart the program */
+
+
+static const GOptionEntry options[] = {
+ { "new-window", 'n', 0, G_OPTION_ARG_NONE, &NewWindow,
+ N_("Open a new window"),
+ 0 },
+
+ { "fullscreen", 'f', 0, G_OPTION_ARG_NONE, &StartInFullscreen,
+ N_("Start in fullscreen mode"),
+ 0 },
+
+ { "slideshow", 's', 0, G_OPTION_ARG_NONE, &StartSlideshow,
+ N_("Automatically start a slideshow"),
+ 0 },
+
+ { "import-photos", 'i', 0, G_OPTION_ARG_NONE, &ImportPhotos,
+ N_("Automatically import digital camera photos"),
+ 0 },
+
+ { G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_STRING_ARRAY, &remaining_args,
+ NULL,
+ NULL },
+
+ { NULL }
+};
+
+
+static void
+gth_save_state (EggSMClient *client,
+ GKeyFile *state,
+ gpointer user_data)
+{
+ const char *argv[2] = { NULL };
+ GList *scan;
+ guint i;
+
+ argv[0] = program_argv0;
+ argv[1] = NULL;
+ egg_sm_client_set_restart_command (client, 1, argv);
+
+ i = 0;
+ for (scan = gth_window_get_window_list (); scan; scan = scan->next) {
+ GtkWidget *window = scan->data;
+ GFile *location;
+ char *key;
+ char *uri;
+
+ location = gth_browser_get_location (GTH_BROWSER (window));
+ if (location == NULL)
+ continue;
+
+ key = g_strdup_printf ("location%d", i++);
+ uri = g_file_get_uri (location);
+ g_key_file_set_string (state, "Session", key, uri);
+
+ g_free (uri);
+ g_free (key);
+ g_object_unref (location);
+ }
+
+ g_key_file_set_integer (state, "Session", "locations", i);
+}
+
+
+static void
+gth_session_manager_init (void)
+{
+ EggSMClient *client = NULL;
+
+ client = egg_sm_client_get ();
+ g_signal_connect (client, "save-state", G_CALLBACK (gth_save_state), NULL);
+}
+
+
+static void
+gth_restore_session (EggSMClient *client)
+{
+ GKeyFile *state = NULL;
+ guint i;
+
+ state = egg_sm_client_get_state_file (client);
+
+ i = g_key_file_get_integer (state, "Session", "locations", NULL);
+ for (; i > 0; i--) {
+ GtkWidget *window;
+ char *key;
+ char *location;
+
+ key = g_strdup_printf ("location%d", i);
+ location = g_key_file_get_string (state, "Session", key, NULL);
+ g_free (key);
+
+ window = gth_browser_new (location);
+ gtk_widget_show (window);
+
+ g_free (location);
+ }
+}
+
+
+static void
+show_window (GtkWindow *window,
+ const char *startup_id,
+ GdkScreen *screen)
+{
+ gtk_window_set_startup_id (window, startup_id);
+ gtk_window_set_screen (window, screen);
+ gtk_window_present (window);
+}
+
+
+static UniqueResponse
+unique_app_message_received_cb (UniqueApp *unique_app,
+ UniqueCommand command,
+ UniqueMessageData *message,
+ guint time_,
+ gpointer user_data)
+{
+ UniqueResponse res;
+ char *uri;
+ GFile *location;
+ GtkWidget *window;
+
+ res = UNIQUE_RESPONSE_OK;
+
+ switch (command) {
+ case UNIQUE_OPEN:
+ case UNIQUE_NEW:
+ if (command == UNIQUE_NEW)
+ window = gth_browser_new (NULL);
+ else
+ window = gth_window_get_current_window ();
+ show_window (GTK_WINDOW (window),
+ unique_message_data_get_startup_id (message),
+ unique_message_data_get_screen (message));
+
+ uri = unique_message_data_get_text (message);
+ location = g_file_new_for_uri (uri);
+ gth_browser_load_location (GTH_BROWSER (window), location);
+
+ g_object_unref (location);
+ g_free (uri);
+ break;
+
+ case COMMAND_IMPORT_PHOTOS:
+ /* gth_browser_activate_action_file_camera_import (NULL, NULL); */
+ break;
+
+ default:
+ res = UNIQUE_RESPONSE_PASSTHROUGH;
+ break;
+ }
+
+ return res;
+}
+
+
+static void
+open_browser_window (GFile *location)
+{
+ if (unique_app_is_running (gthumb_app)) {
+ UniqueMessageData *data;
+ char *uri;
+
+ data = unique_message_data_new ();
+ uri = g_file_get_uri (location);
+ unique_message_data_set_text (data, uri, -1);
+ unique_app_send_message (gthumb_app, NewWindow ? UNIQUE_NEW : UNIQUE_OPEN, data);
+
+ g_free (uri);
+ unique_message_data_free (data);
+ }
+ else {
+ GtkWidget *window;
+
+ window = gth_browser_new (NULL);
+ gtk_widget_show (window);
+ gth_browser_load_location (GTH_BROWSER (window), location);
+ }
+}
+
+
+static void
+prepare_application (void)
+{
+ EggSMClient *client = NULL;
+ const char *arg;
+ int i;
+
+ client = egg_sm_client_get ();
+ if (egg_sm_client_is_resumed (client)) {
+ gth_restore_session (client);
+ return;
+ }
+
+ gthumb_app = unique_app_new_with_commands ("org.gnome.gthumb", NULL,
+ "import-photos", COMMAND_IMPORT_PHOTOS,
+ NULL);
+
+ if (! unique_app_is_running (gthumb_app))
+ g_signal_connect (gthumb_app,
+ "message-received",
+ G_CALLBACK (unique_app_message_received_cb),
+ NULL);
+
+ if (remaining_args == NULL) { /* No location specified. */
+ GFile *location;
+
+ location = g_file_new_for_uri (gth_pref_get_startup_location ());
+ open_browser_window (location);
+
+ g_object_unref (location);
+
+ return;
+ }
+
+ /* open each location in a new window */
+
+ for (i = 0; (arg = remaining_args[i]) != NULL; i++) {
+ GFile *location;
+
+ location = g_file_new_for_commandline_arg (arg);
+ open_browser_window (location);
+
+ g_object_unref (location);
+ }
+}
+
+
+int
+main (int argc, char *argv[])
+{
+ GOptionContext *context = NULL;
+ GError *error = NULL;
+
+ if (! g_thread_supported ()) {
+ g_thread_init (NULL);
+ gdk_threads_init ();
+ }
+
+ program_argv0 = argv[0];
+
+ /* text domain */
+
+ bindtextdomain (GETTEXT_PACKAGE, GTHUMB_LOCALEDIR);
+ bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+ textdomain (GETTEXT_PACKAGE);
+
+ /* command line options */
+
+ context = g_option_context_new (N_("- Image browser and viewer"));
+ g_option_context_set_translation_domain (context, GETTEXT_PACKAGE);
+ g_option_context_add_main_entries (context, options, GETTEXT_PACKAGE);
+ g_option_context_add_group (context, gtk_get_option_group (TRUE));
+ g_option_context_add_group (context, egg_sm_client_get_option_group ());
+ if (! g_option_context_parse (context, &argc, &argv, &error)) {
+ g_critical ("Failed to parse arguments: %s", error->message);
+ g_error_free (error);
+ g_option_context_free (context);
+ return EXIT_FAILURE;
+ }
+ g_option_context_free (context);
+
+ /* application properties */
+
+ g_set_application_name (_("gthumb"));
+ gtk_window_set_default_icon_name ("gthumb");
+ gtk_icon_theme_append_search_path (gtk_icon_theme_get_default (), GTHUMB_PKGDATADIR G_DIR_SEPARATOR_S "icons");
+
+ /* other initializations */
+
+ gth_session_manager_init ();
+ gth_pref_initialize ();
+ gth_main_initialize ();
+ gth_main_register_default_hooks ();
+ gth_main_register_file_source (GTH_TYPE_FILE_SOURCE_VFS);
+ gth_main_register_default_sort_types ();
+ gth_main_register_default_tests ();
+ gth_main_register_default_types ();
+ gth_main_register_default_metadata ();
+ gth_main_activate_extensions ();
+
+ prepare_application ();
+
+ if (! unique_app_is_running (gthumb_app)) {
+ gdk_threads_enter ();
+ gtk_main ();
+ gdk_threads_leave ();
+ }
+
+ g_object_unref (gthumb_app);
+ gth_main_release ();
+ gth_pref_release ();
+
+ return 0;
+}
diff --git a/gthumb/make-header.sh b/gthumb/make-header.sh
new file mode 100755
index 0000000..9a382a2
--- /dev/null
+++ b/gthumb/make-header.sh
@@ -0,0 +1,11 @@
+#!/bin/sh
+
+template="$1"
+
+shift
+includes="\n"
+for i in "$@"; do
+ includes="${includes}#include <gthumb/"$i">\n"
+done
+
+sed -e 's|@@|'"$includes"'|' $template
diff --git a/gthumb/pixbuf-io.c b/gthumb/pixbuf-io.c
new file mode 100644
index 0000000..c76777c
--- /dev/null
+++ b/gthumb/pixbuf-io.c
@@ -0,0 +1,292 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2001-2009 The Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <string.h>
+#define GDK_PIXBUF_ENABLE_BACKEND 1
+#include "gio-utils.h"
+#include "glib-utils.h"
+#include "gth-hook.h"
+#include "pixbuf-io.h"
+
+
+char *
+get_pixbuf_type_from_mime_type (const char *mime_type)
+{
+ if (mime_type == NULL)
+ return NULL;
+
+ if (g_str_has_prefix (mime_type, "image/x-"))
+ return g_strdup (mime_type + strlen ("image/x-"));
+ else if (g_str_has_prefix (mime_type, "image/"))
+ return g_strdup (mime_type + strlen ("image/"));
+ else
+ return g_strdup (mime_type);
+}
+
+
+typedef struct {
+ SavePixbufData *data;
+ GthFileDataFunc ready_func;
+ gpointer ready_data;
+ GList *current;
+} SaveData;
+
+
+static void
+save_pixbuf_file_free (SavePixbufFile *file)
+{
+ g_object_unref (file->file);
+ g_free (file->buffer);
+ g_free (file);
+}
+
+
+static void
+save_pixbuf_data_free (SavePixbufData *data)
+{
+ g_object_unref (data->file_data);
+ g_object_unref (data->pixbuf);
+ g_list_foreach (data->files, (GFunc) save_pixbuf_file_free, NULL);
+ g_list_free (data->files);
+ g_free (data);
+}
+
+
+static void
+save_completed (SaveData *save_data)
+{
+ if (save_data->data->error != NULL)
+ (*save_data->ready_func) (save_data->data->file_data, *save_data->data->error, save_data->ready_data);
+ else
+ (*save_data->ready_func) (save_data->data->file_data, NULL, save_data->ready_data);
+ save_pixbuf_data_free (save_data->data);
+ g_free (save_data);
+}
+
+
+static void save_current_file (SaveData *save_data);
+
+
+static void
+file_saved_cb (void *buffer,
+ gsize count,
+ GError *error,
+ gpointer user_data)
+{
+ SaveData *save_data = user_data;
+
+ if (error != NULL) {
+ save_data->data->error = &error;
+ save_completed (save_data);
+ return;
+ }
+
+ save_data->current = save_data->current->next;
+ save_current_file (save_data);
+}
+
+
+static void
+save_current_file (SaveData *save_data)
+{
+ SavePixbufFile *file;
+
+ if (save_data->current == NULL) {
+ save_completed (save_data);
+ return;
+ }
+
+ file = save_data->current->data;
+ g_write_file_async (file->file,
+ file->buffer,
+ file->buffer_size,
+ G_PRIORITY_DEFAULT,
+ NULL,
+ file_saved_cb,
+ save_data);
+}
+
+
+static void
+save_files (SavePixbufData *data,
+ GthFileDataFunc ready_func,
+ gpointer ready_data)
+{
+ SaveData *save_data;
+
+ save_data = g_new0 (SaveData, 1);
+ save_data->data = data;
+ save_data->ready_func = ready_func;
+ save_data->ready_data = ready_data;
+
+ save_data->current = save_data->data->files;
+ save_current_file (save_data);
+}
+
+
+void
+_gdk_pixbuf_save_async (GdkPixbuf *pixbuf,
+ GthFileData *file_data,
+ const char *type,
+ char **keys,
+ char **values,
+ GthFileDataFunc ready_func,
+ gpointer ready_data)
+{
+ void *buffer;
+ gsize buffer_size;
+ GError *error = NULL;
+ SavePixbufData *data;
+
+ if (! gdk_pixbuf_save_to_bufferv (pixbuf,
+ (char **)&buffer,
+ &buffer_size,
+ type,
+ keys,
+ values,
+ &error))
+ {
+ gth_file_data_ready_with_error (file_data, ready_func, ready_data, error);
+ return;
+ }
+
+ data = g_new0 (SavePixbufData, 1);
+ data->file_data = g_object_ref (file_data);
+ data->pixbuf = g_object_ref (pixbuf);
+ data->type = type;
+ data->buffer = buffer;
+ data->buffer_size = buffer_size;
+ data->files = NULL;
+ data->error = NULL;
+ gth_hook_invoke ("save-pixbuf", data);
+
+ if (data->error == NULL) {
+ SavePixbufFile *file;
+
+ file = g_new0 (SavePixbufFile, 1);
+ file->file = g_object_ref (data->file_data->file);
+ file->buffer = data->buffer;
+ file->buffer_size = data->buffer_size;
+ data->files = g_list_prepend (data->files, file);
+ }
+ else {
+ g_free (buffer);
+ gth_file_data_ready_with_error (file_data, ready_func, ready_data, error);
+ return;
+ }
+
+ save_files (data, ready_func, ready_data);
+}
+
+
+GdkPixbuf*
+gth_pixbuf_new_from_file (GthFileData *file_data,
+ GError **error,
+ int requested_width,
+ int requested_height)
+{
+ GdkPixbuf *pixbuf = NULL;
+ char *path;
+ gboolean scale_pixbuf;
+
+ if (file_data == NULL)
+ return NULL;
+
+ path = g_file_get_path (file_data->file);
+
+ scale_pixbuf = FALSE;
+ if (requested_width > 0) {
+ int w, h;
+
+ if (gdk_pixbuf_get_file_info (path, &w, &h) == NULL) {
+ w = -1;
+ h = -1;
+ }
+ if ((w > requested_width) || (h > requested_height))
+ scale_pixbuf = TRUE;
+ }
+
+ if (scale_pixbuf)
+ pixbuf = gdk_pixbuf_new_from_file_at_scale (path,
+ requested_width,
+ requested_height,
+ TRUE,
+ error);
+ else
+ pixbuf = gdk_pixbuf_new_from_file (path, error);
+
+ if (pixbuf != NULL) {
+ GdkPixbuf *rotated;
+
+ rotated = gdk_pixbuf_apply_embedded_orientation (pixbuf);
+ if (rotated != NULL) {
+ g_object_unref (pixbuf);
+ pixbuf = rotated;
+ }
+ }
+
+ g_free (path);
+
+ return pixbuf;
+}
+
+
+GdkPixbufAnimation*
+gth_pixbuf_animation_new_from_file (GthFileData *file_data,
+ GError **error,
+ int requested_width,
+ int requested_height)
+{
+ GdkPixbufAnimation *animation = NULL;
+ const char *mime_type;
+
+ mime_type = gth_file_data_get_mime_type (file_data);
+ if (mime_type == NULL)
+ return NULL;
+
+ if (g_content_type_equals (mime_type, "image/gif")) {
+ char *path;
+
+ path = g_file_get_path (file_data->file);
+ animation = gdk_pixbuf_animation_new_from_file (path, error);
+
+ g_free (path);
+
+ return animation;
+ }
+ else {
+ GdkPixbuf *pixbuf;
+
+ pixbuf = gth_pixbuf_new_from_file (file_data,
+ error,
+ requested_width,
+ requested_height);
+
+ if (pixbuf != NULL) {
+ animation = gdk_pixbuf_non_anim_new (pixbuf);
+ g_object_unref (pixbuf);
+ }
+ }
+
+ return animation;
+}
diff --git a/gthumb/pixbuf-io.h b/gthumb/pixbuf-io.h
new file mode 100644
index 0000000..63667d6
--- /dev/null
+++ b/gthumb/pixbuf-io.h
@@ -0,0 +1,72 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2001-2009 The Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef PIXBUF_IO_H
+#define PIXBUF_IO_H
+
+#include <glib.h>
+#include <gdk/gdk.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include "gth-file-data.h"
+#include "typedefs.h"
+
+G_BEGIN_DECLS
+
+
+typedef struct {
+ GFile *file;
+ void *buffer;
+ gsize buffer_size;
+} SavePixbufFile;
+
+
+typedef struct {
+ GthFileData *file_data;
+ GdkPixbuf *pixbuf;
+ const char *type;
+ void *buffer;
+ gsize buffer_size;
+ GList *files; /* SavePixbufFile list */
+ GError **error;
+} SavePixbufData;
+
+char * get_pixbuf_type_from_mime_type (const char *mime_type);
+void _gdk_pixbuf_save_async (GdkPixbuf *pixbuf,
+ GthFileData *file_data,
+ const char *type,
+ char **keys,
+ char **values,
+ GthFileDataFunc ready_func,
+ gpointer data);
+GdkPixbuf * gth_pixbuf_new_from_file (GthFileData *file,
+ GError **error,
+ int requested_width,
+ int requested_height);
+GdkPixbufAnimation*
+ gth_pixbuf_animation_new_from_file (GthFileData *file_data,
+ GError **error,
+ int requested_width,
+ int requested_height);
+
+G_END_DECLS
+
+#endif /* PIXBUF_IO_H */
diff --git a/gthumb/pixbuf-utils.c b/gthumb/pixbuf-utils.c
new file mode 100644
index 0000000..ced9194
--- /dev/null
+++ b/gthumb/pixbuf-utils.c
@@ -0,0 +1,516 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2001-2009 The Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <math.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "pixbuf-utils.h"
+
+
+/*
+ * Returns a transformed image.
+ */
+GdkPixbuf *
+_gdk_pixbuf_transform (GdkPixbuf *src,
+ GthTransform transform)
+{
+ GdkPixbuf *temp = NULL, *dest = NULL;
+
+ if (src == NULL)
+ return NULL;
+
+ switch (transform) {
+ case GTH_TRANSFORM_NONE:
+ dest = src;
+ g_object_ref (dest);
+ break;
+ case GTH_TRANSFORM_FLIP_H:
+ dest = gdk_pixbuf_flip (src, TRUE);
+ break;
+ case GTH_TRANSFORM_ROTATE_180:
+ dest = gdk_pixbuf_rotate_simple (src, GDK_PIXBUF_ROTATE_UPSIDEDOWN);
+ break;
+ case GTH_TRANSFORM_FLIP_V:
+ dest = gdk_pixbuf_flip (src, FALSE);
+ break;
+ case GTH_TRANSFORM_TRANSPOSE:
+ temp = gdk_pixbuf_rotate_simple (src, GDK_PIXBUF_ROTATE_CLOCKWISE);
+ dest = gdk_pixbuf_flip (temp, TRUE);
+ g_object_unref (temp);
+ break;
+ case GTH_TRANSFORM_ROTATE_90:
+ dest = gdk_pixbuf_rotate_simple (src, GDK_PIXBUF_ROTATE_CLOCKWISE);
+ break;
+ case GTH_TRANSFORM_TRANSVERSE:
+ temp = gdk_pixbuf_rotate_simple (src, GDK_PIXBUF_ROTATE_CLOCKWISE);
+ dest = gdk_pixbuf_flip (temp, FALSE);
+ g_object_unref (temp);
+ break;
+ case GTH_TRANSFORM_ROTATE_270:
+ dest = gdk_pixbuf_rotate_simple (src, GDK_PIXBUF_ROTATE_COUNTERCLOCKWISE);
+ break;
+ default:
+ dest = src;
+ g_object_ref (dest);
+ break;
+ }
+
+ return dest;
+}
+
+
+void
+pixmap_from_xpm (const char **data,
+ GdkPixmap **pixmap,
+ GdkBitmap **mask)
+{
+ GdkPixbuf *pixbuf;
+
+ pixbuf = gdk_pixbuf_new_from_xpm_data (data);
+ gdk_pixbuf_render_pixmap_and_mask (pixbuf, pixmap, mask, 127);
+ g_object_unref (pixbuf);
+}
+
+
+void
+_gdk_pixbuf_vertical_gradient (GdkPixbuf *pixbuf,
+ guint32 color1,
+ guint32 color2)
+{
+ guchar *pixels;
+ guint32 r1, g1, b1, a1;
+ guint32 r2, g2, b2, a2;
+ double r, g, b, a;
+ double rd, gd, bd, ad;
+ guint32 ri, gi, bi, ai;
+ guchar *p;
+ guint width, height;
+ guint w, h;
+ int n_channels, rowstride;
+
+ g_return_if_fail (GDK_IS_PIXBUF (pixbuf));
+
+ width = gdk_pixbuf_get_width (pixbuf);
+ height = gdk_pixbuf_get_height (pixbuf);
+
+ if (width == 0 || height == 0)
+ return;
+
+ pixels = gdk_pixbuf_get_pixels (pixbuf);
+
+ r1 = (color1 & 0xff000000) >> 24;
+ g1 = (color1 & 0x00ff0000) >> 16;
+ b1 = (color1 & 0x0000ff00) >> 8;
+ a1 = (color1 & 0x000000ff);
+
+ r2 = (color2 & 0xff000000) >> 24;
+ g2 = (color2 & 0x00ff0000) >> 16;
+ b2 = (color2 & 0x0000ff00) >> 8;
+ a2 = (color2 & 0x000000ff);
+
+ rd = ((double) (r2) - r1) / height;
+ gd = ((double) (g2) - g1) / height;
+ bd = ((double) (b2) - b1) / height;
+ ad = ((double) (a2) - a1) / height;
+
+ n_channels = gdk_pixbuf_get_n_channels (pixbuf);
+ rowstride = gdk_pixbuf_get_rowstride (pixbuf);
+
+ r = r1;
+ g = g1;
+ b = b1;
+ a = a1;
+
+ for (h = height; h > 0; h--) {
+ w = width;
+ p = pixels;
+
+ ri = (int) r;
+ gi = (int) g;
+ bi = (int) b;
+ ai = (int) a;
+
+ switch (n_channels) {
+ case 3:
+ while (w--) {
+ p[0] = ri;
+ p[1] = gi;
+ p[2] = bi;
+ p += 3;
+ }
+ break;
+ case 4:
+ while (w--) {
+ p[0] = ri;
+ p[1] = gi;
+ p[2] = bi;
+ p[3] = ai;
+ p += 4;
+ }
+ break;
+ default:
+ break;
+ }
+
+ r += rd;
+ g += gd;
+ b += bd;
+ a += ad;
+
+ pixels += rowstride;
+ }
+}
+
+
+void
+_gdk_pixbuf_horizontal_gradient (GdkPixbuf *pixbuf,
+ guint32 color1,
+ guint32 color2)
+{
+ guchar *pixels;
+ guint32 r1, g1, b1, a1;
+ guint32 r2, g2, b2, a2;
+ double r, g, b, a;
+ double rd, gd, bd, ad;
+ guint32 ri, gi, bi, ai;
+ guchar *p;
+ guint width, height;
+ guint w, h;
+ int n_channels, rowstride;
+
+ g_return_if_fail (GDK_IS_PIXBUF (pixbuf));
+
+ width = gdk_pixbuf_get_width (pixbuf);
+ height = gdk_pixbuf_get_height (pixbuf);
+
+ if (width == 0 || height == 0)
+ return;
+
+ pixels = gdk_pixbuf_get_pixels (pixbuf);
+
+ r1 = (color1 & 0xff000000) >> 24;
+ g1 = (color1 & 0x00ff0000) >> 16;
+ b1 = (color1 & 0x0000ff00) >> 8;
+ a1 = (color1 & 0x000000ff);
+
+ r2 = (color2 & 0xff000000) >> 24;
+ g2 = (color2 & 0x00ff0000) >> 16;
+ b2 = (color2 & 0x0000ff00) >> 8;
+ a2 = (color2 & 0x000000ff);
+
+ rd = ((double) (r2) - r1) / width;
+ gd = ((double) (g2) - g1) / width;
+ bd = ((double) (b2) - b1) / width;
+ ad = ((double) (a2) - a1) / width;
+
+ n_channels = gdk_pixbuf_get_n_channels (pixbuf);
+ rowstride = gdk_pixbuf_get_rowstride (pixbuf);
+
+ r = r1;
+ g = g1;
+ b = b1;
+ a = a1;
+
+ for (w = 0; w < width; w++) {
+ h = height;
+ p = pixels;
+
+ ri = (int) rint (r);
+ gi = (int) rint (g);
+ bi = (int) rint (b);
+ ai = (int) rint (a);
+
+ switch (n_channels) {
+ case 3:
+ while (h--) {
+ p[0] = ri;
+ p[1] = gi;
+ p[2] = bi;
+ p += rowstride;
+ }
+ break;
+ case 4:
+ while (h--) {
+ p[0] = ri;
+ p[1] = gi;
+ p[2] = bi;
+ p[3] = ai;
+ p += rowstride;
+ }
+ break;
+ default:
+ break;
+ }
+
+ r += rd;
+ g += gd;
+ b += bd;
+ a += ad;
+
+ pixels += n_channels;
+ }
+}
+
+
+void
+_gdk_pixbuf_hv_gradient (GdkPixbuf *pixbuf,
+ guint32 hcolor1,
+ guint32 hcolor2,
+ guint32 vcolor1,
+ guint32 vcolor2)
+{
+ guchar *pixels;
+ guint32 hr1, hg1, hb1, ha1;
+ guint32 hr2, hg2, hb2, ha2;
+ guint32 vr1, vg1, vb1, va1;
+ guint32 vr2, vg2, vb2, va2;
+ double r, g, b, a;
+ guint32 ri, gi, bi, ai;
+ guchar *p;
+ guint width, height;
+ guint w, h;
+ int n_channels, rowstride;
+ double x, y;
+
+ g_return_if_fail (GDK_IS_PIXBUF (pixbuf));
+
+ width = gdk_pixbuf_get_width (pixbuf);
+ height = gdk_pixbuf_get_height (pixbuf);
+
+ if (width == 0 || height == 0)
+ return;
+
+ pixels = gdk_pixbuf_get_pixels (pixbuf);
+
+ hr1 = (hcolor1 & 0xff000000) >> 24;
+ hg1 = (hcolor1 & 0x00ff0000) >> 16;
+ hb1 = (hcolor1 & 0x0000ff00) >> 8;
+ ha1 = (hcolor1 & 0x000000ff);
+
+ hr2 = (hcolor2 & 0xff000000) >> 24;
+ hg2 = (hcolor2 & 0x00ff0000) >> 16;
+ hb2 = (hcolor2 & 0x0000ff00) >> 8;
+ ha2 = (hcolor2 & 0x000000ff);
+
+ vr1 = (vcolor1 & 0xff000000) >> 24;
+ vg1 = (vcolor1 & 0x00ff0000) >> 16;
+ vb1 = (vcolor1 & 0x0000ff00) >> 8;
+ va1 = (vcolor1 & 0x000000ff);
+
+ vr2 = (vcolor2 & 0xff000000) >> 24;
+ vg2 = (vcolor2 & 0x00ff0000) >> 16;
+ vb2 = (vcolor2 & 0x0000ff00) >> 8;
+ va2 = (vcolor2 & 0x000000ff);
+
+ n_channels = gdk_pixbuf_get_n_channels (pixbuf);
+ rowstride = gdk_pixbuf_get_rowstride (pixbuf);
+
+ for (h = 0; h < height; h++) {
+ p = pixels;
+
+ x = (((double) height) - h) / height;
+
+ for (w = 0; w < width; w++) {
+ double x_y, x_1_y, y_1_x, _1_x_1_y;
+
+ y = (((double) width) - w) / width;;
+
+ x_y = x * y;
+ x_1_y = x * (1.0 - y);
+ y_1_x = y * (1.0 - x);
+ _1_x_1_y = (1.0 - x) * (1.0 - y);
+
+ r = hr1 * x_y + hr2 * x_1_y + vr1 * y_1_x + vr2 * _1_x_1_y;
+ g = hg1 * x_y + hg2 * x_1_y + vg1 * y_1_x + vg2 * _1_x_1_y;
+ b = hb1 * x_y + hb2 * x_1_y + vb1 * y_1_x + vb2 * _1_x_1_y;
+ a = ha1 * x_y + ha2 * x_1_y + va1 * y_1_x + va2 * _1_x_1_y;
+
+ ri = (int) r;
+ gi = (int) g;
+ bi = (int) b;
+ ai = (int) a;
+
+ switch (n_channels) {
+ case 3:
+ p[0] = ri;
+ p[1] = gi;
+ p[2] = bi;
+ p += 3;
+ break;
+ case 4:
+ p[0] = ri;
+ p[1] = gi;
+ p[2] = bi;
+ p[3] = ai;
+ p += 4;
+ break;
+ default:
+ break;
+ }
+ }
+
+ pixels += rowstride;
+ }
+}
+
+
+GdkPixbuf*
+_gdk_pixbuf_scale_simple_safe (const GdkPixbuf *src,
+ int dest_width,
+ int dest_height,
+ GdkInterpType interp_type)
+{
+ GdkPixbuf* temp_pixbuf1;
+ GdkPixbuf* temp_pixbuf2;
+ int x_ratio, y_ratio;
+ int temp_width = dest_width, temp_height = dest_height;
+
+ g_assert (dest_width > 1);
+ g_assert (dest_height > 1);
+
+ x_ratio = gdk_pixbuf_get_width (src) / dest_width;
+ y_ratio = gdk_pixbuf_get_height (src) / dest_height;
+
+ /* The gdk_pixbuf scaling routines do not handle large-ratio downscaling
+ very well. Memory usage explodes and the application may freeze or crash.
+ For scale-down ratios in excess of 100, do the scale in two steps.
+ It is faster and safer that way. See bug 80925 for background info. */
+
+ if (x_ratio > 100)
+ /* Scale down to 10x the requested size first. */
+ temp_width = 10 * dest_width;
+
+ if (y_ratio > 100)
+ /* Scale down to 10x the requested size first. */
+ temp_height = 10 * dest_height;
+
+ if ( (temp_width != dest_width) || (temp_height != dest_height)) {
+ temp_pixbuf1 = gdk_pixbuf_scale_simple (src, temp_width, temp_height, interp_type);
+ temp_pixbuf2 = gdk_pixbuf_scale_simple (temp_pixbuf1, dest_width, dest_height, interp_type);
+ g_object_unref (temp_pixbuf1);
+ } else
+ temp_pixbuf2 = gdk_pixbuf_scale_simple (src, dest_width, dest_height, interp_type);
+
+ return temp_pixbuf2;
+}
+
+
+gboolean
+scale_keeping_ratio_min (int *width,
+ int *height,
+ int min_width,
+ int min_height,
+ int max_width,
+ int max_height,
+ gboolean allow_upscaling)
+{
+ double w = *width;
+ double h = *height;
+ double min_w = min_width;
+ double min_h = min_height;
+ double max_w = max_width;
+ double max_h = max_height;
+ double factor;
+ int new_width, new_height;
+ gboolean modified;
+
+ if ((*width < max_width) && (*height < max_height)
+ && (!allow_upscaling))
+ return FALSE;
+
+ if (((*width < min_width) || (*height < min_height))
+ && (!allow_upscaling))
+ return FALSE;
+
+ factor = MAX ( MIN (max_w / w, max_h / h), MAX (min_w / w, min_h / h) );
+ new_width = MAX ((int) floor (w * factor + 0.50), 1);
+ new_height = MAX ((int) floor (h * factor + 0.50), 1);
+
+ modified = (new_width != *width) || (new_height != *height);
+
+ *width = new_width;
+ *height = new_height;
+
+ return modified;
+}
+
+
+gboolean
+scale_keeping_ratio (int *width,
+ int *height,
+ int max_width,
+ int max_height,
+ gboolean allow_upscaling)
+{
+ return scale_keeping_ratio_min (width,
+ height,
+ 0,
+ 0,
+ max_width,
+ max_height,
+ allow_upscaling);
+}
+
+
+GdkPixbuf*
+create_void_pixbuf (int width,
+ int height)
+{
+ GdkPixbuf *p;
+
+ p = gdk_pixbuf_new (GDK_COLORSPACE_RGB,
+ TRUE,
+ 8,
+ width,
+ height);
+ gdk_pixbuf_fill (p, 0xFFFFFF00);
+
+ return p;
+}
+
+
+gboolean
+_g_mime_type_is_writable (const char *mime_type)
+{
+ GSList *list;
+ GSList *scan;
+
+ list = gdk_pixbuf_get_formats ();
+ for (scan = list; scan; scan = scan->next) {
+ GdkPixbufFormat *format = scan->data;
+ char **mime_types;
+ int i;
+
+ mime_types = gdk_pixbuf_format_get_mime_types (format);
+ for (i = 0; mime_types[i] != NULL; i++)
+ if (strcmp (mime_type, mime_types[i]) == 0)
+ return ! gdk_pixbuf_format_is_disabled (format) && gdk_pixbuf_format_is_writable (format);
+
+ g_strfreev (mime_types);
+ }
+
+ g_slist_free (list);
+
+ return FALSE;
+}
diff --git a/gthumb/pixbuf-utils.h b/gthumb/pixbuf-utils.h
new file mode 100644
index 0000000..e1d5efb
--- /dev/null
+++ b/gthumb/pixbuf-utils.h
@@ -0,0 +1,71 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2001-2009 The Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef PIXBUF_UTILS_H
+#define PIXBUF_UTILS_H
+
+#include <glib.h>
+#include <gdk/gdk.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include "typedefs.h"
+
+G_BEGIN_DECLS
+
+void pixmap_from_xpm (const char **data,
+ GdkPixmap **pixmap,
+ GdkBitmap **mask);
+void _gdk_pixbuf_vertical_gradient (GdkPixbuf *pixbuf,
+ guint32 color1,
+ guint32 color2);
+void _gdk_pixbuf_horizontal_gradient (GdkPixbuf *pixbuf,
+ guint32 color1,
+ guint32 color2);
+void _gdk_pixbuf_hv_gradient (GdkPixbuf *pixbuf,
+ guint32 hcolor1,
+ guint32 hcolor2,
+ guint32 vcolor1,
+ guint32 vcolor2);
+GdkPixbuf * _gdk_pixbuf_transform (GdkPixbuf *src,
+ GthTransform transform);
+gboolean scale_keeping_ratio_min (int *width,
+ int *height,
+ int min_width,
+ int min_height,
+ int max_width,
+ int max_height,
+ gboolean allow_upscaling);
+gboolean scale_keeping_ratio (int *width,
+ int *height,
+ int max_width,
+ int max_height,
+ gboolean allow_upscaling);
+GdkPixbuf * create_void_pixbuf (int width,
+ int height);
+GdkPixbuf * _gdk_pixbuf_scale_simple_safe (const GdkPixbuf *src,
+ int dest_width,
+ int dest_height,
+ GdkInterpType interp_type);
+gboolean _g_mime_type_is_writable (const char *mime_type);
+
+G_END_DECLS
+
+#endif
diff --git a/gthumb/typedefs.h b/gthumb/typedefs.h
new file mode 100644
index 0000000..3f297e7
--- /dev/null
+++ b/gthumb/typedefs.h
@@ -0,0 +1,99 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2001-2008 The Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef TYPEDEFS_H
+#define TYPEDEFS_H
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+/*
+#define RC_DIR ".gnome2/gthumb"
+#define RC_CATALOG_DIR ".gnome2/gthumb/collections"
+#define RC_COMMENTS_DIR ".gnome2/gthumb/comments"
+#define RC_BOOKMARKS_FILE ".gnome2/gthumb/bookmarks"
+#define RC_HISTORY_FILE ".gnome2/gthumb/history"
+#define RC_CATEGORIES_FILE ".gnome2/gthumb/categories"
+#define RC_REMOTE_CACHE_DIR ".gnome2/gthumb/remote_cache"
+*/
+
+#define BOOKMARKS_FILE "bookmarks.xbel"
+#define FILTERS_FILE "filters.xml"
+#define FILE_CACHE "cache"
+
+
+typedef enum {
+ GTH_CLICK_POLICY_NAUTILUS,
+ GTH_CLICK_POLICY_SINGLE,
+ GTH_CLICK_POLICY_DOUBLE
+} GthClickPolicy;
+
+
+typedef enum {
+ GTH_DIRECTION_FORWARD,
+ GTH_DIRECTION_REVERSE,
+ GTH_DIRECTION_RANDOM
+} GthDirection;
+
+
+typedef enum {
+ GTH_TOOLBAR_STYLE_SYSTEM,
+ GTH_TOOLBAR_STYLE_TEXT_BELOW,
+ GTH_TOOLBAR_STYLE_TEXT_BESIDE,
+ GTH_TOOLBAR_STYLE_ICONS,
+ GTH_TOOLBAR_STYLE_TEXT
+} GthToolbarStyle;
+
+
+/* The GthTransform numeric values range from 1 to 8, corresponding to
+ * the valid range of Exif orientation tags. The name associated with each
+ * numeric valid describes the data transformation required that will allow
+ * the orientation value to be reset to "1" without changing the displayed
+ * image.
+ * GthTransform and ExifShort values are interchangeably in a number of
+ * places. The main difference is that ExifShort can have a value of zero,
+ * corresponding to an error or an absence of an Exif orientation tag.
+ * See bug 361913 for additional details. */
+typedef enum {
+ GTH_TRANSFORM_NONE = 1, /* no transformation */
+ GTH_TRANSFORM_FLIP_H, /* horizontal flip */
+ GTH_TRANSFORM_ROTATE_180, /* 180-degree rotation */
+ GTH_TRANSFORM_FLIP_V, /* vertical flip */
+ GTH_TRANSFORM_TRANSPOSE, /* transpose across UL-to-LR axis (= rotate_90 + flip_h) */
+ GTH_TRANSFORM_ROTATE_90, /* 90-degree clockwise rotation */
+ GTH_TRANSFORM_TRANSVERSE, /* transpose across UR-to-LL axis (= rotate_90 + flip_v) */
+ GTH_TRANSFORM_ROTATE_270 /* 270-degree clockwise */
+} GthTransform;
+
+
+typedef void (*ErrorFunc) (gpointer user_data);
+typedef void (*DoneFunc) (gpointer user_data);
+typedef void (*ReadyFunc) (GError *error,
+ gpointer user_data);
+typedef void (*ReadyCallback) (GObject *object,
+ GError *error,
+ gpointer user_data);
+
+G_END_DECLS
+
+#endif /* TYPEDEFS_H */
diff --git a/gthumb/zlib-utils.c b/gthumb/zlib-utils.c
new file mode 100644
index 0000000..a4d57c8
--- /dev/null
+++ b/gthumb/zlib-utils.c
@@ -0,0 +1,87 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 The Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <string.h>
+#include <zlib.h>
+#include "zlib-utils.h"
+
+
+#define BUFFER_SIZE (16 * 1024)
+
+
+gboolean
+zlib_decompress_buffer (void *zipped_buffer,
+ gsize zipped_size,
+ void **buffer,
+ gsize *size)
+{
+ z_stream strm;
+ int ret;
+ guint n;
+ gsize count;
+ guchar *local_buffer = NULL;
+ guchar tmp_buffer[BUFFER_SIZE];
+
+ strm.zalloc = Z_NULL;
+ strm.zfree = Z_NULL;
+ strm.opaque = Z_NULL;
+ strm.avail_in = 0;
+ strm.next_in = Z_NULL;
+ ret = inflateInit2 (&strm, 16+15);
+ if (ret != Z_OK)
+ return FALSE;
+
+ count = 0;
+ strm.avail_in = zipped_size;
+ strm.next_in = zipped_buffer;
+ do {
+ do {
+ strm.avail_out = BUFFER_SIZE;
+ strm.next_out = tmp_buffer;
+ ret = inflate (&strm, Z_NO_FLUSH);
+
+ switch (ret) {
+ case Z_NEED_DICT:
+ case Z_DATA_ERROR:
+ case Z_MEM_ERROR:
+ g_free (local_buffer);
+ inflateEnd (&strm);
+ return FALSE;
+ }
+
+ n = BUFFER_SIZE - strm.avail_out;
+ local_buffer = g_realloc (local_buffer, count + n + 1);
+ memcpy (local_buffer + count, tmp_buffer, n);
+ count += n;
+ }
+ while (strm.avail_out == 0);
+ }
+ while (ret != Z_STREAM_END);
+
+ inflateEnd (&strm);
+
+ *buffer = local_buffer;
+ *size = count;
+
+ return ret == Z_STREAM_END;
+}
diff --git a/gthumb/zlib-utils.h b/gthumb/zlib-utils.h
new file mode 100644
index 0000000..72860c7
--- /dev/null
+++ b/gthumb/zlib-utils.h
@@ -0,0 +1,37 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2009 The Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef ZLIB_UTILS_H
+#define ZLIB_UTILS_H
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+gboolean zlib_decompress_buffer (void *zipped_buffer,
+ gsize zipped_size,
+ void **buffer,
+ gsize *size);
+
+G_END_DECLS
+
+#endif /* ZLIB_UTILS_H */
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 3959b9f..9d84e67 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -1,208 +1,31 @@
-# List of source files containing translatable strings.
-# Please keep this file sorted alphabetically.
-data/albumthemes/text.h
-data/glade/gthumb.glade
-data/glade/gthumb_camera.glade
-data/glade/gthumb_comments.glade
-data/glade/gthumb_convert.glade
-data/glade/gthumb_crop.glade
-data/glade/gthumb_edit.glade
-data/glade/gthumb_png_exporter.glade
-data/glade/gthumb_preferences.glade
-data/glade/gthumb_print.glade
-data/glade/gthumb_redeye.glade
-data/glade/gthumb_search.glade
-data/glade/gthumb_tools.glade
-data/glade/gthumb_web_exporter.glade
-data/gthumb.desktop.in
-data/gthumb-import.desktop.in
+# List of source files which contain translatable strings.
+[encoding: UTF-8]
data/gthumb.schemas.in
-libgthumb/async-pixbuf-ops.c
-libgthumb/async-pixbuf-ops.h
-libgthumb/bookmarks.c
-libgthumb/bookmarks.h
-libgthumb/catalog.c
-libgthumb/catalog.h
-libgthumb/comments.c
-libgthumb/comments.h
-libgthumb/cursors.c
-libgthumb/cursors.h
-libgthumb/dlg-save-image.c
-libgthumb/dlg-save-image.h
-libgthumb/file-data.c
-libgthumb/file-data.h
-libgthumb/file-utils.c
-libgthumb/file-utils.h
-libgthumb/gconf-utils.c
-libgthumb/gconf-utils.h
-libgthumb/gfile-utils.c
-libgthumb/gfile-utils.h
-libgthumb/glib-utils.c
-libgthumb/glib-utils.h
-libgthumb/gstringlist.c
-libgthumb/gstringlist.h
-libgthumb/gth-file-list.c
-libgthumb/gth-file-list.h
-libgthumb/gth-file-view-list.c
-libgthumb/gth-file-view-list.h
-libgthumb/gth-file-view-thumbs.c
-libgthumb/gth-file-view-thumbs.h
-libgthumb/gth-file-view.c
-libgthumb/gth-file-view.h
-libgthumb/gth-gstreamer-utils.c
-libgthumb/gth-gstreamer-utils.h
-libgthumb/gth-image-list.c
-libgthumb/gth-image-list.h
-libgthumb/gth-monitor.c
-libgthumb/gth-monitor.h
-libgthumb/gth-pixbuf-op.c
-libgthumb/gth-pixbuf-op.h
-libgthumb/gth-utils.c
-libgthumb/gth-utils.h
-libgthumb/gthumb-error.c
-libgthumb/gthumb-error.h
-libgthumb/gthumb-histogram.c
-libgthumb/gthumb-histogram.h
-libgthumb/gthumb-info-bar.c
-libgthumb/gthumb-info-bar.h
-libgthumb/gthumb-init.c
-libgthumb/gthumb-init.h
-libgthumb/gthumb-slide.c
-libgthumb/gthumb-slide.h
-libgthumb/gthumb-stock.c
-libgthumb/gthumb-stock.h
-libgthumb/gtk-utils.c
-libgthumb/gtk-utils.h
-libgthumb/image-loader.c
-libgthumb/image-loader.h
-libgthumb/image-viewer.c
-libgthumb/image-viewer.h
-libgthumb/nav-window.c
-libgthumb/nav-window.h
-libgthumb/pixbuf-utils.c
-libgthumb/pixbuf-utils.h
-libgthumb/preferences.c
-libgthumb/preferences.h
-libgthumb/print-callbacks.c
-libgthumb/print-callbacks.h
-libgthumb/search.c
-libgthumb/search.h
-libgthumb/thumb-cache.c
-libgthumb/thumb-cache.h
-libgthumb/thumb-loader.c
-libgthumb/thumb-loader.h
-libgthumb/typedefs.h
-src/albumtheme.c
-src/albumtheme-private.c
-src/albumtheme-private.h
-src/bookmark-list.c
-src/bookmark-list.h
-src/catalog-list.c
-src/catalog-list.h
-src/catalog-png-exporter.c
-src/catalog-png-exporter.h
-src/catalog-web-exporter.c
-src/catalog-web-exporter.h
-src/dlg-bookmarks.c
-src/dlg-bookmarks.h
-src/dlg-brightness-contrast.c
-src/dlg-brightness-contrast.h
-src/dlg-catalog.c
-src/dlg-catalog.h
-src/dlg-change-date.c
-src/dlg-change-date.h
-src/dlg-color-balance.c
-src/dlg-color-balance.h
-src/dlg-comment.c
-src/dlg-comment.h
-src/dlg-convert.c
-src/dlg-convert.h
-src/dlg-crop.c
-src/dlg-crop.h
-src/dlg-duplicates.c
-src/dlg-duplicates.h
-src/dlg-file-utils.c
-src/dlg-file-utils.h
-src/dlg-hue-saturation.c
-src/dlg-hue-saturation.h
-src/dlg-image-prop.c
-src/dlg-image-prop.h
-src/dlg-jpegtran.c
-src/dlg-jpegtran.h
-src/dlg-open-with.c
-src/dlg-open-with.h
-src/dlg-photo-importer.c
-src/dlg-photo-importer.h
-src/dlg-png-exporter.c
-src/dlg-png-exporter.h
-src/dlg-posterize.c
-src/dlg-posterize.h
+[type: gettext/glade]data/ui/bookmarks.ui
+[type: gettext/glade]data/ui/filter-editor.ui
+[type: gettext/glade]data/ui/personalize-filters.ui
+[type: gettext/glade]data/ui/preferences.ui
+[type: gettext/glade]data/ui/sort-order.ui
+extensions/catalogs/callbacks.c
+[type: gettext/glade]extensions/catalogs/data/ui/add-to-catalog.ui
+extensions/catalogs/dlg-add-to-catalog.c
+extensions/catalogs/gth-catalog.c
+extensions/search/actions.c
+[type: gettext/glade]extensions/search/data/ui/search-editor.ui
+extensions/search/gth-search-editor-dialog.c
+src/dlg-personalize-filters.c
src/dlg-preferences.c
-src/dlg-preferences.h
-src/dlg-rename-series.c
-src/dlg-rename-series.h
-src/dlg-reset-exif.c
-src/dlg-reset-exif.h
-src/dlg-scale-image.c
-src/dlg-scale-image.h
-src/dlg-scale-series.c
-src/dlg-scale-series.h
-src/dlg-search.c
-src/dlg-search.h
-src/dlg-scripts.c
-src/dlg-scripts.h
-src/dlg-tags.c
-src/dlg-tags.h
-src/dlg-web-exporter.c
-src/dlg-web-exporter.h
-src/dlg-write-to-cd.c
-src/dlg-write-to-cd.h
-src/gth-batch-op.c
-src/gth-browser-actions-callbacks.c
-src/gth-browser-actions-callbacks.h
src/gth-browser-actions-entries.h
src/gth-browser.c
-src/gth-browser.h
-src/gth-browser-ui.h
-src/gth-dir-list.c
-src/gth-dir-list.h
-src/gth-exif-data-viewer.c
-src/gth-exif-data-viewer.h
src/gth-filter-bar.c
-src/gth-filter-bar.h
-src/gth-folder-selection-dialog.c
-src/gth-folder-selection-dialog.h
-src/gth-fullscreen-actions-callbacks.c
-src/gth-fullscreen-actions-callbacks.h
-src/gth-fullscreen-actions-entries.h
-src/gth-fullscreen.c
-src/gth-fullscreen.h
-src/gth-fullscreen-ui.h
-src/gth-image-selector.c
-src/gth-image-selector.h
-src/gth-location.c
-src/gth-location.h
-src/gthumb-preloader.c
-src/gthumb-preloader.h
-src/gth-viewer-actions-callbacks.c
-src/gth-viewer-actions-callbacks.h
-src/gth-viewer-actions-entries.h
-src/gth-viewer.c
-src/gth-viewer.h
-src/gth-viewer-ui.h
-src/gth-window-actions-callbacks.c
-src/gth-window-actions-callbacks.h
+src/gth-filter-editor-dialog.c
+src/gth-filter.c
+src/gth-folder-tree.c
+src/gth-location-chooser.c
+src/gth-main-default-sort-types.c
+src/gth-main-default-tests.c
+src/gth-test-selector.c
+src/gth-test-simple.c
src/gth-window-actions-entries.h
-src/gth-window.c
-src/gth-window.h
-src/gth-window-utils.c
-src/gth-window-utils.h
-src/gtkcellrendererthreestates.c
-src/gtkcellrendererthreestates.h
-src/lex.albumtheme.c
+src/gtk-utils.c
src/main.c
-src/main.h
-src/rotation-utils.c
-src/rotation-utils.h
-src/totem-scrsaver.c
-src/totem-scrsaver.h
diff --git a/tests/Makefile.am b/tests/Makefile.am
new file mode 100644
index 0000000..968ec0a
--- /dev/null
+++ b/tests/Makefile.am
@@ -0,0 +1,12 @@
+if BUILD_TEST_SUITE
+bin_PROGRAMS = dom-test glib-utils-test
+endif
+
+dom_test_SOURCES = dom-test.c $(top_srcdir)/gthumb/dom.c
+dom_test_LDADD = $(GTHUMB_LIBS)
+dom_test_CFLAGS = $(GTHUMB_CFLAGS) -I$(top_srcdir)/gthumb
+
+glib_utils_test_SOURCES = glib-utils-test.c $(top_srcdir)/gthumb/glib-utils.c
+glib_utils_test_LDADD = $(GTHUMB_LIBS)
+glib_utils_test_CFLAGS = $(GTHUMB_CFLAGS) -I$(top_srcdir)/gthumb
+-include $(top_srcdir)/git.mk
diff --git a/tests/dom-test.c b/tests/dom-test.c
new file mode 100644
index 0000000..33b56e1
--- /dev/null
+++ b/tests/dom-test.c
@@ -0,0 +1,180 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2008 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include "dom.h"
+
+
+static void
+compare_loaded_and_dumped_xml (DomDocument *doc)
+{
+ char *xml;
+ gsize len;
+ DomDocument *loaded_doc;
+ char *loaded_xml;
+
+ xml = dom_document_dump (doc, &len);
+ /*g_print ("%s", xml);*/
+
+ loaded_doc = dom_document_new ();
+ dom_document_load (loaded_doc, xml, len, NULL);
+ loaded_xml = dom_document_dump (loaded_doc, NULL);
+ /*g_print ("%s", loaded_xml);*/
+
+ g_assert_cmpstr (xml, ==, loaded_xml);
+
+ g_free (loaded_xml);
+ g_object_unref (loaded_doc);
+ g_free (xml);
+}
+
+
+static void
+check_dumped_xml (DomDocument *doc,
+ const char *expected_xml)
+{
+ char *xml;
+
+ xml = dom_document_dump (doc, NULL);
+ /*g_print ("%s", xml);*/
+ g_assert_cmpstr (xml, ==, expected_xml);
+ g_free (xml);
+}
+
+
+static void
+test_dom_1 (void)
+{
+ DomDocument *doc;
+
+ doc = dom_document_new ();
+
+ compare_loaded_and_dumped_xml (doc);
+ check_dumped_xml (doc, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
+
+ g_object_unref (doc);
+}
+
+
+static void
+test_dom_2 (void)
+{
+ DomDocument *doc;
+ DomElement *filters;
+
+ doc = dom_document_new ();
+ filters = dom_document_create_element (doc, "filters", "version", "1.0", NULL);
+ dom_element_append_child (DOM_ELEMENT (doc), filters);
+
+ compare_loaded_and_dumped_xml (doc);
+ check_dumped_xml (doc, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ "<filters version=\"1.0\"/>\n");
+
+ g_object_unref (doc);
+}
+
+
+static void
+test_dom_3 (void)
+{
+ DomDocument *doc;
+ DomElement *filters;
+ DomElement *filter;
+ DomElement *match;
+ DomElement *test;
+ DomElement *limit;
+
+ doc = dom_document_new ();
+ filters = dom_document_create_element (doc, "filters", NULL);
+ dom_element_set_attribute (filters, "version", "1.0");
+ dom_element_append_child (DOM_ELEMENT (doc), filters);
+
+ filter = dom_document_create_element (doc, "filter", NULL);
+ dom_element_set_attribute (filter, "name", "test1");
+ dom_element_append_child (filters, filter);
+
+ match = dom_document_create_element (doc, "match", NULL);
+ dom_element_set_attribute (match, "type", "all");
+ dom_element_append_child (filter, match);
+
+ test = dom_document_create_element (doc, "test", NULL);
+ dom_element_set_attribute (test, "id", "::filesize");
+ dom_element_set_attribute (test, "op", "lt");
+ dom_element_set_attribute (test, "value", "10");
+ dom_element_set_attribute (test, "unit", "kB");
+ dom_element_append_child (match, test);
+
+ test = dom_document_create_element (doc, "test", NULL);
+ dom_element_set_attribute (test, "id", "::filename");
+ dom_element_set_attribute (test, "op", "contains");
+ dom_element_set_attribute (test, "value", "logo");
+ dom_element_append_child (match, test);
+
+ limit = dom_document_create_element (doc, "limit", NULL);
+ dom_element_set_attribute (limit, "value", "25");
+ dom_element_set_attribute (limit, "type", "files");
+ dom_element_set_attribute (limit, "selected_by", "more_recent");
+ dom_element_append_child (filter, limit);
+
+ filter = dom_document_create_element (doc, "filter", NULL);
+ dom_element_set_attribute (filter, "name", "test2");
+ dom_element_append_child (filters, filter);
+
+ limit = dom_document_create_element (doc, "limit", NULL);
+ dom_element_set_attribute (limit, "value", "25");
+ dom_element_set_attribute (limit, "type", "files");
+ dom_element_set_attribute (limit, "selected_by", "more_recent");
+ dom_element_append_child (filter, limit);
+
+ compare_loaded_and_dumped_xml (doc);
+
+ check_dumped_xml (doc, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ "<filters version=\"1.0\">\n"
+ " <filter name=\"test1\">\n"
+ " <match type=\"all\">\n"
+ " <test id=\"::filesize\" op=\"lt\" value=\"10\" unit=\"kB\"/>\n"
+ " <test id=\"::filename\" op=\"contains\" value=\"logo\"/>\n"
+ " </match>\n"
+ " <limit value=\"25\" type=\"files\" selected_by=\"more_recent\"/>\n"
+ " </filter>\n"
+ " <filter name=\"test2\">\n"
+ " <limit value=\"25\" type=\"files\" selected_by=\"more_recent\"/>\n"
+ " </filter>\n"
+ "</filters>\n");
+
+ g_object_unref (doc);
+}
+
+
+int
+main (int argc,
+ char *argv[])
+{
+ g_type_init ();
+ g_test_init (&argc, &argv, NULL);
+
+ g_test_add_func ("/dom/1", test_dom_1);
+ g_test_add_func ("/dom/2", test_dom_2);
+ g_test_add_func ("/dom/3", test_dom_3);
+
+ return g_test_run ();
+}
diff --git a/tests/glib-utils-test.c b/tests/glib-utils-test.c
new file mode 100644
index 0000000..b3c3496
--- /dev/null
+++ b/tests/glib-utils-test.c
@@ -0,0 +1,53 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * GThumb
+ *
+ * Copyright (C) 2008 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <string.h>
+#include "glib-utils.h"
+
+
+static void
+test_g_rand_string (void)
+{
+ char *id;
+
+ id = _g_rand_string (8);
+ g_assert_cmpint (strlen (id), == , 8);
+ g_free (id);
+
+ id = _g_rand_string (16);
+ g_assert_cmpint (strlen (id), == , 16);
+ g_free (id);
+}
+
+
+int
+main (int argc,
+ char *argv[])
+{
+ g_type_init ();
+ g_test_init (&argc, &argv, NULL);
+
+ g_test_add_func ("/glib-utils/_g_rand_string/1", test_g_rand_string);
+
+ return g_test_run ();
+}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]