[gthumb/ext: 1/79] Imported my local branch



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">&lt;b&gt;Interface&lt;/b&gt;</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">&lt;b&gt;On startup:&lt;/b&gt;</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">&lt;b&gt;Other&lt;/b&gt;</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">&#x25CF;</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, &gth_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, &gth_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, &gth_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 (&current_time);
+		char *date_time = _g_time_val_to_exif_date (&current_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, &gth_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">&#x25CF;</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">&#x25CF;</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">&#x25CF;</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">&#x25CF;</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">&lt;b&gt;Selection&lt;/b&gt;</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">&#x25CF;</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">&#x25CF;</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">&lt;b&gt;Aspect ratio&lt;/b&gt;</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, &gth_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, &gth_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, &gth_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, &gth_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, &gth_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">&lt;b&gt;After loading an image:&lt;/b&gt;</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">&lt;b&gt;Zoom quality&lt;/b&gt;</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">&lt;b&gt;Other&lt;/b&gt;</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, &gth_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, &gth_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, &current_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, &gth_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, &gth_multipage_child_info);
+		g_type_add_interface_static (type, GTH_TYPE_PROPERTY_VIEW, &gth_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, &gth_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, &current_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, &gth_file_view_info);
+		g_type_add_interface_static (type, GTH_TYPE_FILE_SELECTION, &gth_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, &gth_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, &gth_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, &gth_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, &gth_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, &gth_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]