[evolution] Consolidate base utility libraries into libeutil.



commit d09d8de870b6697c8a8b262e7e077b871a69b315
Author: Matthew Barnes <mbarnes redhat com>
Date:   Mon Dec 10 08:09:59 2012 -0500

    Consolidate base utility libraries into libeutil.
    
    Evolution consists of entirely too many small utility libraries, which
    increases linking and loading time, places a burden on higher layers of
    the application (e.g. modules) which has to remember to link to all the
    small in-tree utility libraries, and makes it difficult to generate API
    documentation for these utility libraries in one Gtk-Doc module.
    
    Merge the following utility libraries under the umbrella of libeutil,
    and enforce a single-include policy on libeutil so we can reorganize
    the files as desired without disrupting its pseudo-public API.
    
       libemail-utils/libemail-utils.la
       libevolution-utils/libevolution-utils.la
       filter/libfilter.la
       widgets/e-timezone-dialog/libetimezonedialog.la
       widgets/menus/libmenus.la
       widgets/misc/libemiscwidgets.la
       widgets/table/libetable.la
       widgets/text/libetext.la
    
    This also merges libedataserverui from the Evolution-Data-Server module,
    since Evolution is its only consumer nowadays, and I'd like to make some
    improvements to those APIs without concern for backward-compatibility.
    
    And finally, start a Gtk-Doc module for libeutil.  It's going to be a
    project just getting all the symbols _listed_ much less _documented_.
    But the skeletal structure is in place and I'm off to a good start.

 Makefile.am                                        |    7 +-
 a11y/Makefile.am                                   |   23 -
 a11y/ea-factory.h                                  |  114 -
 a11y/gal-a11y-factory.h                            |   85 -
 a11y/gal-a11y-util.h                               |   36 -
 addressbook/gui/contact-editor/Makefile.am         |   12 +-
 addressbook/gui/contact-editor/e-contact-editor.c  |   11 +-
 .../gui/contact-editor/e-contact-quick-add.c       |    3 -
 addressbook/gui/contact-list-editor/Makefile.am    |   12 +-
 .../contact-list-editor/e-contact-list-editor.c    |    7 +-
 .../contact-list-editor/e-contact-list-editor.h    |    2 -
 .../gui/contact-list-editor/e-contact-list-model.c |    1 -
 addressbook/gui/merging/Makefile.am                |    2 +-
 addressbook/gui/merging/eab-contact-compare.c      |    2 +-
 addressbook/gui/widgets/Makefile.am                |    7 -
 .../gui/widgets/e-addressbook-reflow-adapter.h     |    3 +-
 addressbook/gui/widgets/e-addressbook-selector.c   |    2 +-
 addressbook/gui/widgets/e-addressbook-selector.h   |    2 -
 .../gui/widgets/e-addressbook-table-adapter.h      |    2 +-
 addressbook/gui/widgets/e-addressbook-view.c       |   29 +-
 addressbook/gui/widgets/e-addressbook-view.h       |    3 -
 addressbook/gui/widgets/e-minicard-label.c         |    9 +-
 addressbook/gui/widgets/e-minicard-view-widget.c   |    5 +-
 addressbook/gui/widgets/e-minicard-view-widget.h   |    3 +-
 addressbook/gui/widgets/e-minicard-view.c          |   13 +-
 addressbook/gui/widgets/e-minicard-view.h          |    4 +-
 addressbook/gui/widgets/e-minicard.c               |   15 +-
 addressbook/gui/widgets/ea-addressbook.c           |    4 +-
 addressbook/gui/widgets/eab-config.h               |    2 +-
 addressbook/gui/widgets/eab-contact-display.c      |   20 +-
 addressbook/gui/widgets/eab-contact-display.h      |    2 +-
 addressbook/gui/widgets/eab-contact-formatter.c    |   15 +-
 addressbook/gui/widgets/eab-gui-util.c             |    8 +-
 addressbook/gui/widgets/eab-gui-util.h             |    2 +-
 .../gui/widgets/gal-view-factory-minicard.h        |    2 +-
 addressbook/gui/widgets/gal-view-minicard.c        |    1 -
 addressbook/gui/widgets/gal-view-minicard.h        |    2 +-
 addressbook/importers/Makefile.am                  |    8 +-
 addressbook/importers/evolution-csv-importer.c     |    2 -
 addressbook/importers/evolution-ldif-importer.c    |    2 -
 addressbook/importers/evolution-vcard-importer.c   |    4 -
 addressbook/printing/Makefile.am                   |    9 +-
 addressbook/printing/e-contact-print.c             |    1 -
 addressbook/util/Makefile.am                       |    2 -
 calendar/alarm-notify/Makefile.am                  |    9 +-
 calendar/alarm-notify/alarm-notify-dialog.c        |    6 +-
 calendar/alarm-notify/alarm-notify.c               |    2 +
 calendar/alarm-notify/alarm-notify.h               |    1 -
 calendar/gui/Makefile.am                           |   10 +-
 calendar/gui/calendar-config.c                     |    3 +-
 calendar/gui/calendar-config.h                     |    2 +-
 calendar/gui/calendar-view-factory.h               |    1 -
 calendar/gui/calendar-view.h                       |    1 -
 calendar/gui/comp-util.c                           |    2 -
 calendar/gui/comp-util.h                           |    2 +-
 calendar/gui/dialogs/Makefile.am                   |   11 +-
 calendar/gui/dialogs/alarm-dialog.c                |    7 +-
 calendar/gui/dialogs/cancel-comp.c                 |    6 +-
 calendar/gui/dialogs/cancel-comp.h                 |    1 +
 calendar/gui/dialogs/comp-editor-util.c            |    4 +-
 calendar/gui/dialogs/comp-editor-util.h            |    1 -
 calendar/gui/dialogs/comp-editor.c                 |    9 -
 calendar/gui/dialogs/comp-editor.h                 |    4 +-
 calendar/gui/dialogs/copy-source-dialog.c          |    3 +-
 calendar/gui/dialogs/delete-comp.c                 |    6 +-
 calendar/gui/dialogs/e-delegate-dialog.c           |    1 -
 calendar/gui/dialogs/e-send-options-utils.h        |    2 +-
 calendar/gui/dialogs/event-editor.c                |    5 -
 calendar/gui/dialogs/event-page.c                  |   13 -
 calendar/gui/dialogs/memo-editor.c                 |    3 -
 calendar/gui/dialogs/memo-page.c                   |   11 -
 calendar/gui/dialogs/recurrence-page.c             |    5 -
 calendar/gui/dialogs/save-comp.c                   |    1 -
 calendar/gui/dialogs/schedule-page.c               |    3 +-
 calendar/gui/dialogs/schedule-page.h               |    1 -
 calendar/gui/dialogs/select-source-dialog.c        |    3 +-
 calendar/gui/dialogs/send-comp.c                   |    6 +-
 calendar/gui/dialogs/task-details-page.c           |    7 +-
 calendar/gui/dialogs/task-editor.c                 |    3 -
 calendar/gui/dialogs/task-page.c                   |   11 -
 calendar/gui/e-cal-component-preview.c             |    3 -
 calendar/gui/e-cal-component-preview.h             |    3 +-
 calendar/gui/e-cal-config.h                        |    3 +-
 calendar/gui/e-cal-event.h                         |    3 +-
 calendar/gui/e-cal-list-view.c                     |   10 -
 calendar/gui/e-cal-list-view.h                     |    3 +-
 calendar/gui/e-cal-model.h                         |    4 +-
 calendar/gui/e-calendar-selector.c                 |    2 -
 calendar/gui/e-calendar-selector.h                 |    2 +-
 calendar/gui/e-calendar-view.c                     |    8 -
 calendar/gui/e-cell-date-edit-text.c               |    3 -
 calendar/gui/e-cell-date-edit-text.h               |    2 +-
 calendar/gui/e-day-view-layout.c                   |    1 -
 calendar/gui/e-day-view-main-item.c                |   10 +-
 calendar/gui/e-day-view-time-item.c                |    1 -
 calendar/gui/e-day-view-top-item.c                 |    1 -
 calendar/gui/e-day-view.c                          |   24 +-
 calendar/gui/e-meeting-list-view.c                 |    1 -
 calendar/gui/e-meeting-list-view.h                 |    1 -
 calendar/gui/e-meeting-store.c                     |    1 -
 calendar/gui/e-meeting-store.h                     |    3 +-
 calendar/gui/e-meeting-time-sel-item.c             |    2 -
 calendar/gui/e-meeting-time-sel.c                  |    7 -
 calendar/gui/e-meeting-time-sel.h                  |    6 +-
 calendar/gui/e-memo-list-selector.c                |    1 -
 calendar/gui/e-memo-list-selector.h                |    2 +-
 calendar/gui/e-memo-table.c                        |   16 +-
 calendar/gui/e-memo-table.h                        |    3 +-
 calendar/gui/e-select-names-editable.h             |    2 -
 calendar/gui/e-task-list-selector.c                |    1 -
 calendar/gui/e-task-list-selector.h                |    2 +-
 calendar/gui/e-task-table.c                        |   17 +-
 calendar/gui/e-task-table.h                        |    3 +-
 calendar/gui/e-timezone-entry.c                    |    6 +-
 calendar/gui/e-week-view-event-item.c              |    4 +-
 calendar/gui/e-week-view.c                         |   17 +-
 calendar/gui/ea-cal-view-event.c                   |    4 +-
 calendar/gui/ea-calendar-helpers.c                 |    1 -
 calendar/gui/ea-calendar.c                         |    3 +-
 calendar/gui/ea-day-view-cell.c                    |    1 -
 calendar/gui/ea-day-view-main-item.c               |    4 +-
 calendar/gui/ea-week-view-cell.c                   |    1 -
 calendar/gui/ea-week-view-main-item.c              |    5 +-
 calendar/gui/ea-week-view.c                        |    5 +-
 calendar/gui/gnome-cal.c                           |   32 +-
 calendar/gui/gnome-cal.h                           |    2 +-
 calendar/gui/itip-utils.c                          |    2 -
 calendar/gui/print.c                               |    6 +-
 calendar/gui/print.h                               |    3 +-
 calendar/gui/tag-calendar.h                        |    2 +-
 calendar/importers/Makefile.am                     |    4 +-
 calendar/importers/icalendar-importer.c            |    9 +-
 composer/Makefile.am                               |    9 +-
 composer/e-composer-actions.c                      |    1 -
 composer/e-composer-activity.h                     |    1 -
 composer/e-composer-common.h                       |    1 +
 composer/e-composer-from-header.c                  |    2 -
 composer/e-composer-header-table.c                 |    2 -
 composer/e-composer-header-table.h                 |    1 -
 composer/e-composer-name-header.h                  |    1 -
 composer/e-composer-private.h                      |   18 -
 composer/e-composer-spell-header.c                 |    2 -
 composer/e-msg-composer.c                          |    4 -
 composer/e-msg-composer.h                          |    3 -
 configure.ac                                       |   16 +-
 doc/reference/Makefile.am                          |    2 +-
 doc/reference/libeshell/Makefile.am                |   80 +
 doc/reference/libeshell/libeshell-docs.sgml        |   44 +
 .../libeshell-overrides.txt}                       |    0
 doc/reference/libeshell/libeshell-sections.txt     |  423 ++
 doc/reference/libeshell/libeshell.types            |    9 +
 .../tmpl/e-mail-account-manager.sgml               |    0
 .../tmpl/e-mail-account-tree-view.sgml             |    0
 .../tmpl/e-mail-identity-combo-box.sgml            |    0
 doc/reference/libeutil/Makefile.am                 |   67 +
 doc/reference/libeutil/libeutil-docs.sgml          |  263 ++
 .../libeutil-overrides.txt}                        |    0
 doc/reference/libeutil/libeutil-sections.txt       | 1900 +++++++++
 doc/reference/libeutil/libeutil.types              |  170 +
 doc/reference/shell/Makefile.am                    |   81 -
 doc/reference/shell/eshell-docs.sgml               |   59 -
 doc/reference/shell/eshell-sections.txt            | 1096 ------
 doc/reference/shell/eshell.types                   |   10 -
 e-util/Makefile.am                                 |  755 +++-
 {widgets/table => e-util}/arrow-down.xpm           |    0
 {widgets/table => e-util}/arrow-up.xpm             |    0
 {widgets/table => e-util}/check-empty.xpm          |    0
 {widgets/table => e-util}/check-filled.xpm         |    0
 {widgets/misc => e-util}/e-action-combo-box.c      |    0
 e-util/e-action-combo-box.h                        |   89 +
 {widgets/misc => e-util}/e-activity-bar.c          |    0
 e-util/e-activity-bar.h                            |   71 +
 {widgets/misc => e-util}/e-activity-proxy.c        |    0
 e-util/e-activity-proxy.h                          |   74 +
 e-util/e-activity.c                                |    3 +-
 e-util/e-activity.h                                |    7 +-
 {widgets/misc => e-util}/e-alarm-selector.c        |    0
 e-util/e-alarm-selector.h                          |   67 +
 {widgets/misc => e-util}/e-alert-bar.c             |    0
 e-util/e-alert-bar.h                               |   72 +
 {libevolution-utils => e-util}/e-alert-dialog.c    |    0
 e-util/e-alert-dialog.h                            |   81 +
 e-util/e-alert-sink.c                              |   93 +
 e-util/e-alert-sink.h                              |   63 +
 {libevolution-utils => e-util}/e-alert.c           |    0
 {libevolution-utils => e-util}/e-alert.h           |    0
 {widgets/misc => e-util}/e-attachment-bar.c        |    0
 e-util/e-attachment-bar.h                          |   83 +
 {widgets/misc => e-util}/e-attachment-button.c     |    0
 e-util/e-attachment-button.h                       |   91 +
 {widgets/misc => e-util}/e-attachment-dialog.c     |    0
 e-util/e-attachment-dialog.h                       |   77 +
 e-util/e-attachment-handler-image.c                |  246 ++
 e-util/e-attachment-handler-image.h                |   69 +
 .../misc => e-util}/e-attachment-handler-sendto.c  |    0
 e-util/e-attachment-handler-sendto.h               |   66 +
 {widgets/misc => e-util}/e-attachment-handler.c    |    0
 e-util/e-attachment-handler.h                      |   84 +
 {widgets/misc => e-util}/e-attachment-icon-view.c  |    0
 e-util/e-attachment-icon-view.h                    |   71 +
 {widgets/misc => e-util}/e-attachment-paned.c      |    0
 e-util/e-attachment-paned.h                        |   99 +
 e-util/e-attachment-store.c                        | 1280 +++++++
 e-util/e-attachment-store.h                        |  137 +
 {widgets/misc => e-util}/e-attachment-tree-view.c  |    0
 e-util/e-attachment-tree-view.h                    |   70 +
 e-util/e-attachment-view.c                         | 1906 +++++++++
 e-util/e-attachment-view.h                         |  244 ++
 e-util/e-attachment.c                              | 2882 ++++++++++++++
 e-util/e-attachment.h                              |  159 +
 {widgets/misc => e-util}/e-auth-combo-box.c        |    0
 e-util/e-auth-combo-box.h                          |   75 +
 {widgets/misc => e-util}/e-autocomplete-selector.c |    0
 e-util/e-autocomplete-selector.h                   |   68 +
 e-util/e-bit-array.c                               |    1 -
 e-util/e-bit-array.h                               |    4 +
 {widgets/misc => e-util}/e-book-source-config.c    |    0
 e-util/e-book-source-config.h                      |   71 +
 e-util/e-buffer-tagger.c                           |  692 ++++
 e-util/e-buffer-tagger.h                           |   39 +
 e-util/e-cal-source-config.c                       |  431 +++
 e-util/e-cal-source-config.h                       |   76 +
 e-util/e-calendar-item.c                           | 3773 ++++++++++++++++++
 e-util/e-calendar-item.h                           |  392 ++
 {widgets/misc => e-util}/e-calendar.c              |    0
 e-util/e-calendar.h                                |  112 +
 e-util/e-canvas-background.c                       |  279 ++
 e-util/e-canvas-background.h                       |   75 +
 {widgets/misc => e-util}/e-canvas-utils.c          |    0
 e-util/e-canvas-utils.h                            |   59 +
 e-util/e-canvas-vbox.c                             |  410 ++
 e-util/e-canvas-vbox.h                             |   92 +
 e-util/e-canvas.c                                  |  880 +++++
 e-util/e-canvas.h                                  |  141 +
 e-util/e-categories-config.c                       |    5 +-
 e-util/e-categories-config.h                       |    4 +
 e-util/e-categories-dialog.c                       |  155 +
 e-util/e-categories-dialog.h                       |   73 +
 e-util/e-categories-editor.c                       |  435 +++
 e-util/e-categories-editor.h                       |   88 +
 e-util/e-categories-selector.c                     |  587 +++
 e-util/e-categories-selector.h                     |   97 +
 e-util/e-category-completion.c                     |  505 +++
 e-util/e-category-completion.h                     |   72 +
 e-util/e-category-editor.c                         |  343 ++
 e-util/e-category-editor.h                         |   81 +
 e-util/e-cell-checkbox.c                           |  102 +
 e-util/e-cell-checkbox.h                           |   71 +
 e-util/e-cell-combo.c                              |  838 ++++
 e-util/e-cell-combo.h                              |   89 +
 e-util/e-cell-date-edit.c                          | 1039 +++++
 e-util/e-cell-date-edit.h                          |  124 +
 e-util/e-cell-date.c                               |  130 +
 e-util/e-cell-date.h                               |   74 +
 e-util/e-cell-hbox.c                               |  353 ++
 e-util/e-cell-hbox.h                               |   91 +
 e-util/e-cell-number.c                             |   95 +
 e-util/e-cell-number.h                             |   71 +
 {widgets/table => e-util}/e-cell-percent.c         |    0
 e-util/e-cell-percent.h                            |   76 +
 {widgets/table => e-util}/e-cell-pixbuf.c          |    0
 e-util/e-cell-pixbuf.h                             |   75 +
 e-util/e-cell-popup.c                              |  550 +++
 e-util/e-cell-popup.h                              |  118 +
 e-util/e-cell-renderer-color.c                     |  243 ++
 e-util/e-cell-renderer-color.h                     |   79 +
 e-util/e-cell-size.c                               |  112 +
 e-util/e-cell-size.h                               |   72 +
 e-util/e-cell-text.c                               | 2810 ++++++++++++++
 e-util/e-cell-text.h                               |  195 +
 e-util/e-cell-toggle.c                             |  469 +++
 e-util/e-cell-toggle.h                             |   83 +
 e-util/e-cell-tree.c                               |  880 +++++
 e-util/e-cell-tree.h                               |   90 +
 e-util/e-cell-vbox.c                               |  341 ++
 e-util/e-cell-vbox.h                               |   93 +
 e-util/e-cell.c                                    |  679 ++++
 e-util/e-cell.h                                    |  299 ++
 e-util/e-charset-combo-box.c                       |  407 ++
 e-util/e-charset-combo-box.h                       |   73 +
 e-util/e-charset.h                                 |    4 +
 e-util/e-client-utils.c                            |  445 +++
 e-util/e-client-utils.h                            |   64 +
 e-util/e-config.h                                  |    6 +-
 {widgets/misc => e-util}/e-contact-map-window.c    |    0
 e-util/e-contact-map-window.h                      |   85 +
 e-util/e-contact-map.c                             |  407 ++
 e-util/e-contact-map.h                             |  110 +
 {widgets/misc => e-util}/e-contact-marker.c        |    0
 e-util/e-contact-marker.h                          |   88 +
 e-util/e-contact-store.c                           | 1370 +++++++
 e-util/e-contact-store.h                           |   94 +
 e-util/e-dateedit.c                                | 2497 ++++++++++++
 e-util/e-dateedit.h                                |  219 ++
 e-util/e-datetime-format.c                         |    5 +-
 e-util/e-datetime-format.h                         |    4 +
 e-util/e-destination-store.c                       |  751 ++++
 e-util/e-destination-store.h                       |  106 +
 e-util/e-dialog-utils.h                            |    4 +
 e-util/e-dialog-widgets.h                          |    4 +
 e-util/e-event.h                                   |    6 +-
 e-util/e-file-request.c                            |    2 -
 e-util/e-file-request.h                            |    4 +
 e-util/e-file-utils.h                              |    4 +
 {filter => e-util}/e-filter-code.c                 |    0
 e-util/e-filter-code.h                             |   72 +
 {filter => e-util}/e-filter-color.c                |    0
 e-util/e-filter-color.h                            |   74 +
 e-util/e-filter-datespec.c                         |  513 +++
 e-util/e-filter-datespec.h                         |   91 +
 {filter => e-util}/e-filter-element.c              |    0
 e-util/e-filter-element.h                          |  125 +
 e-util/e-filter-file.c                             |  261 ++
 e-util/e-filter-file.h                             |   78 +
 e-util/e-filter-input.c                            |  304 ++
 e-util/e-filter-input.h                            |   78 +
 {filter => e-util}/e-filter-int.c                  |    0
 e-util/e-filter-int.h                              |   81 +
 {filter => e-util}/e-filter-option.c               |    0
 e-util/e-filter-option.h                           |  101 +
 {filter => e-util}/e-filter-part.c                 |    0
 e-util/e-filter-part.h                             |  112 +
 e-util/e-filter-rule.c                             | 1241 ++++++
 e-util/e-filter-rule.h                             |  163 +
 e-util/e-focus-tracker.c                           |  886 +++++
 e-util/e-focus-tracker.h                           |  104 +
 e-util/e-html-utils.h                              |    4 +
 e-util/e-icon-factory.h                            |    4 +
 e-util/e-image-chooser.c                           |  562 +++
 e-util/e-image-chooser.h                           |   80 +
 e-util/e-import-assistant.c                        | 1436 +++++++
 e-util/e-import-assistant.h                        |   72 +
 e-util/e-import.h                                  |    6 +-
 e-util/e-interval-chooser.c                        |  214 ++
 e-util/e-interval-chooser.h                        |   72 +
 .../misc => e-util}/e-mail-identity-combo-box.c    |    0
 e-util/e-mail-identity-combo-box.h                 |   75 +
 .../misc => e-util}/e-mail-signature-combo-box.c   |    0
 e-util/e-mail-signature-combo-box.h                |   95 +
 e-util/e-mail-signature-editor.c                   |  914 +++++
 e-util/e-mail-signature-editor.h                   |   87 +
 .../misc => e-util}/e-mail-signature-manager.c     |    0
 e-util/e-mail-signature-manager.h                  |   93 +
 e-util/e-mail-signature-preview.c                  |  358 ++
 e-util/e-mail-signature-preview.h                  |   84 +
 .../e-mail-signature-script-dialog.c               |    0
 e-util/e-mail-signature-script-dialog.h            |   94 +
 .../misc => e-util}/e-mail-signature-tree-view.c   |    0
 e-util/e-mail-signature-tree-view.h                |   80 +
 e-util/e-map.c                                     | 1429 +++++++
 e-util/e-map.h                                     |  155 +
 {widgets/misc => e-util}/e-menu-tool-action.c      |    0
 e-util/e-menu-tool-action.h                        |   75 +
 {widgets/misc => e-util}/e-menu-tool-button.c      |    0
 e-util/e-menu-tool-button.h                        |   77 +
 e-util/e-misc-utils.c                              | 1807 +++++++++
 e-util/e-misc-utils.h                              |  175 +
 e-util/e-mktemp.c                                  |    3 +-
 e-util/e-mktemp.h                                  |    4 +
 e-util/e-name-selector-dialog.c                    | 1863 +++++++++
 e-util/e-name-selector-dialog.h                    |  100 +
 e-util/e-name-selector-entry.c                     | 3541 +++++++++++++++++
 e-util/e-name-selector-entry.h                     |  124 +
 e-util/e-name-selector-list.c                      |  790 ++++
 e-util/e-name-selector-list.h                      |   82 +
 e-util/e-name-selector-model.c                     |  663 ++++
 e-util/e-name-selector-model.h                     |  108 +
 e-util/e-name-selector.c                           |  658 ++++
 e-util/e-name-selector.h                           |   94 +
 {widgets/misc => e-util}/e-online-button.c         |    0
 e-util/e-online-button.h                           |   69 +
 {widgets/misc => e-util}/e-paned.c                 |    0
 e-util/e-paned.h                                   |   82 +
 e-util/e-passwords-win32.c                         | 1064 +++++
 e-util/e-passwords.c                               |  890 +++++
 e-util/e-passwords.h                               |   81 +
 e-util/e-picture-gallery.c                         |  437 +++
 e-util/e-picture-gallery.h                         |   71 +
 e-util/e-plugin-ui.c                               |    1 -
 e-util/e-plugin-ui.h                               |    4 +
 e-util/e-plugin.h                                  |    4 +
 e-util/e-poolv.h                                   |    4 +
 {widgets/misc => e-util}/e-popup-action.c          |    0
 e-util/e-popup-action.h                            |   96 +
 {widgets/table => e-util}/e-popup-menu.c           |    0
 e-util/e-popup-menu.h                              |   59 +
 {widgets/misc => e-util}/e-port-entry.c            |    0
 e-util/e-port-entry.h                              |   91 +
 e-util/e-preferences-window.c                      |  643 ++++
 e-util/e-preferences-window.h                      |   88 +
 e-util/e-preview-pane.c                            |  322 ++
 e-util/e-preview-pane.h                            |   80 +
 e-util/e-print.c                                   |    2 +-
 e-util/e-print.h                                   |    4 +
 e-util/e-printable.c                               |  226 ++
 e-util/e-printable.h                               |   93 +
 e-util/e-reflow-model.c                            |  411 ++
 e-util/e-reflow-model.h                            |  129 +
 e-util/e-reflow.c                                  | 1732 +++++++++
 e-util/e-reflow.h                                  |  145 +
 e-util/e-rule-context.c                            | 1026 +++++
 e-util/e-rule-context.h                            |  218 ++
 e-util/e-rule-editor.c                             |  920 +++++
 e-util/e-rule-editor.h                             |  125 +
 {widgets/misc => e-util}/e-search-bar.c            |    0
 e-util/e-search-bar.h                              |   89 +
 {widgets/misc => e-util}/e-selectable.c            |    0
 e-util/e-selectable.h                              |   85 +
 e-util/e-selection-model-array.c                   |  646 ++++
 e-util/e-selection-model-array.h                   |   95 +
 e-util/e-selection-model-simple.c                  |  117 +
 e-util/e-selection-model-simple.h                  |   70 +
 e-util/e-selection-model.c                         |  813 ++++
 e-util/e-selection-model.h                         |  209 +
 e-util/e-selection.c                               |    2 +-
 e-util/e-selection.h                               |    4 +
 e-util/e-send-options.c                            |  767 ++++
 e-util/e-send-options.h                            |  131 +
 {widgets/misc => e-util}/e-send-options.ui         |    0
 e-util/e-sorter-array.c                            |    3 +-
 e-util/e-sorter-array.h                            |    4 +
 e-util/e-sorter.c                                  |    1 -
 e-util/e-sorter.h                                  |    4 +
 e-util/e-source-combo-box.c                        |  701 ++++
 e-util/e-source-combo-box.h                        |   90 +
 {widgets/misc => e-util}/e-source-config-backend.c |    0
 e-util/e-source-config-backend.h                   |   98 +
 e-util/e-source-config-dialog.c                    |  394 ++
 e-util/e-source-config-dialog.h                    |   69 +
 e-util/e-source-config.c                           | 1447 +++++++
 e-util/e-source-config.h                           |  120 +
 e-util/e-source-selector-dialog.c                  |  453 +++
 e-util/e-source-selector-dialog.h                  |   85 +
 e-util/e-source-selector.c                         | 2082 ++++++++++
 e-util/e-source-selector.h                         |  141 +
 e-util/e-source-util.h                             |    6 +-
 {widgets/misc => e-util}/e-spell-entry.c           |    0
 e-util/e-spell-entry.h                             |   63 +
 e-util/e-stock-request.c                           |    3 +-
 e-util/e-stock-request.h                           |    4 +
 e-util/e-table-click-to-add.c                      |  666 ++++
 e-util/e-table-click-to-add.h                      |   99 +
 e-util/e-table-col-dnd.h                           |   43 +
 e-util/e-table-col.c                               |  222 ++
 e-util/e-table-col.h                               |  115 +
 e-util/e-table-column-specification.c              |  157 +
 e-util/e-table-column-specification.h              |   93 +
 e-util/e-table-config.c                            | 1481 +++++++
 e-util/e-table-config.h                            |  134 +
 {widgets/table => e-util}/e-table-config.ui        |    0
 e-util/e-table-defines.h                           |   44 +
 e-util/e-table-extras.c                            |  410 ++
 e-util/e-table-extras.h                            |   94 +
 e-util/e-table-field-chooser-dialog.c              |  235 ++
 e-util/e-table-field-chooser-dialog.h              |   77 +
 e-util/e-table-field-chooser-item.c                |  749 ++++
 e-util/e-table-field-chooser-item.h                |   97 +
 e-util/e-table-field-chooser.c                     |  335 ++
 e-util/e-table-field-chooser.h                     |   84 +
 e-util/e-table-group-container.c                   | 1667 ++++++++
 e-util/e-table-group-container.h                   |  138 +
 e-util/e-table-group-leaf.c                        |  816 ++++
 e-util/e-table-group-leaf.h                        |  110 +
 e-util/e-table-group.c                             |  771 ++++
 e-util/e-table-group.h                             |  242 ++
 e-util/e-table-header-item.c                       | 2226 +++++++++++
 e-util/e-table-header-item.h                       |  148 +
 e-util/e-table-header-utils.c                      |  282 ++
 e-util/e-table-header-utils.h                      |   53 +
 e-util/e-table-header.c                            | 1013 +++++
 e-util/e-table-header.h                            |  144 +
 e-util/e-table-item.c                              | 4041 ++++++++++++++++++++
 e-util/e-table-item.h                              |  261 ++
 e-util/e-table-memory-callbacks.c                  |  234 ++
 e-util/e-table-memory-callbacks.h                  |  148 +
 e-util/e-table-memory-store.c                      |  637 +++
 e-util/e-table-memory-store.h                      |  155 +
 e-util/e-table-memory.c                            |  271 ++
 e-util/e-table-memory.h                            |   91 +
 e-util/e-table-model.c                             |  682 ++++
 e-util/e-table-model.h                             |  217 ++
 e-util/e-table-one.c                               |  252 ++
 e-util/e-table-one.h                               |   75 +
 e-util/e-table-search.c                            |  235 ++
 e-util/e-table-search.h                            |   86 +
 e-util/e-table-selection-model.c                   |  384 ++
 e-util/e-table-selection-model.h                   |   91 +
 e-util/e-table-sort-info.c                         |  482 +++
 e-util/e-table-sort-info.h                         |  133 +
 e-util/e-table-sorted-variable.c                   |  235 ++
 e-util/e-table-sorted-variable.h                   |   85 +
 e-util/e-table-sorted.c                            |  328 ++
 e-util/e-table-sorted.h                            |   84 +
 e-util/e-table-sorter.c                            |  519 +++
 e-util/e-table-sorter.h                            |   94 +
 e-util/e-table-sorting-utils.c                     |  492 +++
 e-util/e-table-sorting-utils.h                     |   95 +
 e-util/e-table-specification.c                     |  435 +++
 e-util/e-table-specification.h                     |  116 +
 e-util/e-table-state.c                             |  320 ++
 e-util/e-table-state.h                             |   89 +
 e-util/e-table-subset-variable.c                   |  267 ++
 e-util/e-table-subset-variable.h                   |  105 +
 e-util/e-table-subset.c                            |  567 +++
 e-util/e-table-subset.h                            |  120 +
 e-util/e-table-utils.c                             |  224 ++
 e-util/e-table-utils.h                             |   54 +
 e-util/e-table-without.c                           |  412 ++
 e-util/e-table-without.h                           |  104 +
 e-util/e-table.c                                   | 3626 ++++++++++++++++++
 e-util/e-table.h                                   |  403 ++
 e-util/e-text-event-processor-emacs-like.c         |    1 -
 e-util/e-text-event-processor-emacs-like.h         |    4 +
 e-util/e-text-event-processor-types.h              |    4 +
 e-util/e-text-event-processor.c                    |    1 -
 e-util/e-text-event-processor.h                    |    4 +
 {widgets/text => e-util}/e-text-model-repos.c      |    0
 e-util/e-text-model-repos.h                        |   58 +
 e-util/e-text-model.c                              |  642 ++++
 e-util/e-text-model.h                              |  112 +
 e-util/e-text.c                                    | 3405 +++++++++++++++++
 e-util/e-text.h                                    |  236 ++
 e-util/e-timezone-dialog.c                         |  870 +++++
 e-util/e-timezone-dialog.h                         |   77 +
 .../e-timezone-dialog.ui                           |    0
 e-util/e-tree-memory-callbacks.c                   |  314 ++
 e-util/e-tree-memory-callbacks.h                   |  182 +
 e-util/e-tree-memory.c                             |  743 ++++
 e-util/e-tree-memory.h                             |  124 +
 e-util/e-tree-model-generator.c                    | 1345 +++++++
 e-util/e-tree-model-generator.h                    |  104 +
 e-util/e-tree-model.c                              | 1177 ++++++
 e-util/e-tree-model.h                              |  298 ++
 e-util/e-tree-selection-model.c                    |  939 +++++
 e-util/e-tree-selection-model.h                    |   95 +
 e-util/e-tree-sorted.c                             | 1433 +++++++
 e-util/e-tree-sorted.h                             |  104 +
 e-util/e-tree-table-adapter.c                      | 1414 +++++++
 e-util/e-tree-table-adapter.h                      |  138 +
 e-util/e-tree.c                                    | 3956 +++++++++++++++++++
 e-util/e-tree.h                                    |  376 ++
 e-util/e-ui-manager.c                              |    2 +-
 e-util/e-ui-manager.h                              |    4 +
 e-util/e-unicode.h                                 |    4 +
 e-util/e-url-entry.c                               |  159 +
 e-util/e-url-entry.h                               |   60 +
 e-util/e-util-enums.h                              |    4 +
 e-util/e-util.c                                    | 1519 --------
 e-util/e-util.h                                    |  348 ++-
 e-util/e-web-view-gtkhtml.c                        | 2317 +++++++++++
 e-util/e-web-view-gtkhtml.h                        |  177 +
 {widgets/misc => e-util}/e-web-view-preview.c      |    0
 e-util/e-web-view-preview.h                        |  115 +
 e-util/e-web-view.c                                | 2945 ++++++++++++++
 e-util/e-web-view.h                                |  224 ++
 e-util/e-xml-utils.c                               |  448 +++
 {libevolution-utils => e-util}/e-xml-utils.h       |    0
 e-util/ea-calendar-cell.c                          |  404 ++
 e-util/ea-calendar-cell.h                          |   90 +
 e-util/ea-calendar-item.c                          | 1373 +++++++
 e-util/ea-calendar-item.h                          |   71 +
 {widgets/misc => e-util}/ea-cell-table.c           |    0
 e-util/ea-cell-table.h                             |   63 +
 e-util/ea-factory.h                                |  118 +
 e-util/ea-widgets.c                                |   36 +
 e-util/ea-widgets.h                                |   36 +
 e-util/evolution-source-viewer.c                   | 1176 ++++++
 {filter => e-util}/filter.error.xml                |    0
 {filter => e-util}/filter.ui                       |    0
 e-util/gal-a11y-e-cell-popup.c                     |  153 +
 e-util/gal-a11y-e-cell-popup.h                     |   65 +
 .../table => e-util}/gal-a11y-e-cell-registry.c    |    0
 e-util/gal-a11y-e-cell-registry.h                  |   75 +
 e-util/gal-a11y-e-cell-toggle.c                    |  198 +
 e-util/gal-a11y-e-cell-toggle.h                    |   67 +
 e-util/gal-a11y-e-cell-tree.c                      |  266 ++
 e-util/gal-a11y-e-cell-tree.h                      |   66 +
 e-util/gal-a11y-e-cell-vbox.c                      |  235 ++
 e-util/gal-a11y-e-cell-vbox.h                      |   67 +
 e-util/gal-a11y-e-cell.c                           |  648 ++++
 e-util/gal-a11y-e-cell.h                           |  112 +
 e-util/gal-a11y-e-table-click-to-add-factory.c     |  108 +
 e-util/gal-a11y-e-table-click-to-add-factory.h     |   52 +
 e-util/gal-a11y-e-table-click-to-add.c             |  358 ++
 e-util/gal-a11y-e-table-click-to-add.h             |   58 +
 e-util/gal-a11y-e-table-column-header.c            |  243 ++
 e-util/gal-a11y-e-table-column-header.h            |   59 +
 .../table => e-util}/gal-a11y-e-table-factory.c    |    0
 e-util/gal-a11y-e-table-factory.h                  |   53 +
 e-util/gal-a11y-e-table-item-factory.c             |  107 +
 e-util/gal-a11y-e-table-item-factory.h             |   52 +
 e-util/gal-a11y-e-table-item.c                     | 1437 +++++++
 e-util/gal-a11y-e-table-item.h                     |   62 +
 e-util/gal-a11y-e-table.c                          |  315 ++
 e-util/gal-a11y-e-table.h                          |   62 +
 e-util/gal-a11y-e-text-factory.c                   |  103 +
 e-util/gal-a11y-e-text-factory.h                   |   52 +
 e-util/gal-a11y-e-text.c                           | 1141 ++++++
 e-util/gal-a11y-e-text.h                           |   59 +
 .../table => e-util}/gal-a11y-e-tree-factory.c     |    0
 e-util/gal-a11y-e-tree-factory.h                   |   52 +
 e-util/gal-a11y-e-tree.c                           |  196 +
 e-util/gal-a11y-e-tree.h                           |   61 +
 e-util/gal-a11y-factory.h                          |   89 +
 {a11y => e-util}/gal-a11y-util.c                   |    0
 e-util/gal-a11y-util.h                             |   40 +
 e-util/gal-define-views-dialog.c                   |  451 +++
 e-util/gal-define-views-dialog.h                   |   77 +
 e-util/gal-define-views-model.c                    |  352 ++
 e-util/gal-define-views-model.h                    |   70 +
 {widgets/menus => e-util}/gal-define-views.ui      |    0
 e-util/gal-view-collection.c                       |  829 ++++
 e-util/gal-view-collection.h                       |  150 +
 e-util/gal-view-etable.c                           |  335 ++
 e-util/gal-view-etable.h                           |   96 +
 e-util/gal-view-factory-etable.c                   |  195 +
 e-util/gal-view-factory-etable.h                   |   77 +
 e-util/gal-view-factory.c                          |  101 +
 e-util/gal-view-factory.h                          |   85 +
 e-util/gal-view-instance-save-as-dialog.c          |  359 ++
 e-util/gal-view-instance-save-as-dialog.h          |   92 +
 .../gal-view-instance-save-as-dialog.ui            |    0
 e-util/gal-view-instance.c                         |  502 +++
 e-util/gal-view-instance.h                         |  114 +
 e-util/gal-view-new-dialog.c                       |  291 ++
 e-util/gal-view-new-dialog.h                       |   81 +
 {widgets/menus => e-util}/gal-view-new-dialog.ui   |    0
 e-util/gal-view.c                                  |  280 ++
 e-util/gal-view.h                                  |   98 +
 e-util/test-calendar.c                             |  145 +
 e-util/test-category-completion.c                  |   67 +
 e-util/test-contact-store.c                        |  145 +
 {widgets/misc => e-util}/test-dateedit.c           |    0
 e-util/test-mail-signatures.c                      |  195 +
 e-util/test-name-selector.c                        |  102 +
 {widgets/misc => e-util}/test-preferences-window.c |    0
 e-util/test-source-combo-box.c                     |  107 +
 {widgets/misc => e-util}/test-source-config.c      |    0
 e-util/test-source-selector.c                      |  157 +
 {widgets/table => e-util}/tree-expanded.xpm        |    0
 {widgets/table => e-util}/tree-unexpanded.xpm      |    0
 {widgets/misc => e-util}/widgets.error.xml         |    0
 em-format/Makefile.am                              |   14 +-
 em-format/e-mail-extension-registry.c              |   13 +-
 em-format/e-mail-formatter-attachment-bar.c        |    5 +-
 em-format/e-mail-formatter-attachment.c            |   18 +-
 em-format/e-mail-formatter-error.c                 |    4 +-
 em-format/e-mail-formatter-extension.h             |    4 +-
 em-format/e-mail-formatter-headers.c               |   11 +-
 em-format/e-mail-formatter-image.c                 |   14 +-
 em-format/e-mail-formatter-message-rfc822.c        |   12 +-
 em-format/e-mail-formatter-print-headers.c         |   14 +-
 em-format/e-mail-formatter-print.c                 |    2 -
 em-format/e-mail-formatter-quote-attachment.c      |   11 +-
 em-format/e-mail-formatter-quote-headers.c         |   14 +-
 em-format/e-mail-formatter-quote-message-rfc822.c  |   13 +-
 em-format/e-mail-formatter-quote-text-enriched.c   |   10 +-
 em-format/e-mail-formatter-quote-text-html.c       |   13 +-
 em-format/e-mail-formatter-quote-text-plain.c      |   12 +-
 em-format/e-mail-formatter-secure-button.c         |    4 +-
 em-format/e-mail-formatter-source.c                |    9 +-
 em-format/e-mail-formatter-text-enriched.c         |    9 +-
 em-format/e-mail-formatter-text-html.c             |   16 +-
 em-format/e-mail-formatter-text-plain.c            |   11 +-
 em-format/e-mail-formatter-utils.c                 |   12 +-
 em-format/e-mail-formatter.c                       |    2 -
 em-format/e-mail-formatter.h                       |    5 +-
 em-format/e-mail-parser-application-mbox.c         |   10 +-
 em-format/e-mail-parser-application-smime.c        |   10 +-
 em-format/e-mail-parser-attachment-bar.c           |    9 +-
 em-format/e-mail-parser-extension.c                |    2 -
 em-format/e-mail-parser-extension.h                |    2 +-
 em-format/e-mail-parser-headers.c                  |    9 +-
 em-format/e-mail-parser-image.c                    |    9 +-
 em-format/e-mail-parser-inlinepgp-encrypted.c      |   12 +-
 em-format/e-mail-parser-inlinepgp-signed.c         |   12 +-
 em-format/e-mail-parser-message-deliverystatus.c   |    8 +-
 em-format/e-mail-parser-message-external.c         |   12 +-
 em-format/e-mail-parser-message-rfc822.c           |   12 +-
 em-format/e-mail-parser-message.c                  |   11 +-
 em-format/e-mail-parser-multipart-alternative.c    |   10 +-
 em-format/e-mail-parser-multipart-appledouble.c    |    5 +-
 em-format/e-mail-parser-multipart-digest.c         |    8 +-
 em-format/e-mail-parser-multipart-encrypted.c      |    9 +-
 em-format/e-mail-parser-multipart-mixed.c          |   10 +-
 em-format/e-mail-parser-multipart-related.c        |   10 +-
 em-format/e-mail-parser-multipart-signed.c         |    9 +-
 em-format/e-mail-parser-secure-button.c            |    4 +-
 em-format/e-mail-parser-source.c                   |    7 +-
 em-format/e-mail-parser-text-enriched.c            |    9 +-
 em-format/e-mail-parser-text-html.c                |   12 +-
 em-format/e-mail-parser-text-plain.c               |   13 +-
 em-format/e-mail-parser.c                          |   14 +-
 em-format/e-mail-part-attachment-bar.h             |    2 -
 em-format/e-mail-part-utils.c                      |    1 -
 em-format/e-mail-part.h                            |    3 +-
 evolution-shell.pc.in                              |    4 +-
 evolution-zip.in                                   |    7 -
 filter/Makefile.am                                 |   76 -
 filter/e-filter-code.h                             |   68 -
 filter/e-filter-color.h                            |   70 -
 filter/e-filter-datespec.c                         |  515 ---
 filter/e-filter-datespec.h                         |   87 -
 filter/e-filter-element.h                          |  120 -
 filter/e-filter-file.c                             |  262 --
 filter/e-filter-file.h                             |   74 -
 filter/e-filter-input.c                            |  305 --
 filter/e-filter-input.h                            |   74 -
 filter/e-filter-int.h                              |   77 -
 filter/e-filter-option.h                           |   97 -
 filter/e-filter-part.h                             |  107 -
 filter/e-filter-rule.c                             | 1242 ------
 filter/e-filter-rule.h                             |  159 -
 filter/e-rule-context.c                            | 1027 -----
 filter/e-rule-context.h                            |  214 --
 filter/e-rule-editor.c                             |  920 -----
 filter/e-rule-editor.h                             |  121 -
 libemail-engine/Makefile.am                        |   22 +-
 libemail-engine/e-mail-session.c                   |    5 +-
 libemail-engine/e-mail-session.h                   |    2 +-
 libemail-engine/e-mail-utils.c                     |    2 +-
 libemail-engine/em-filter-folder-element.c         |  226 ++
 libemail-engine/em-filter-folder-element.h         |   74 +
 libemail-engine/em-vfolder-context.c               |  110 +
 libemail-engine/em-vfolder-context.h               |   70 +
 libemail-engine/em-vfolder-rule.c                  |  494 +++
 libemail-engine/em-vfolder-rule.h                  |  102 +
 libemail-engine/libemail-engine.pc.in              |    2 +-
 libemail-engine/mail-folder-cache.c                |    2 +-
 {libemail-utils => libemail-engine}/mail-mt.c      |    0
 libemail-engine/mail-mt.h                          |  125 +
 libemail-engine/mail-ops.c                         |    2 +-
 libemail-engine/mail-ops.h                         |    2 +-
 libemail-engine/mail-vfolder.c                     |   15 +-
 libemail-engine/mail-vfolder.h                     |    5 +-
 libemail-utils/Makefile.am                         |   44 -
 libemail-utils/em-filter-folder-element.c          |  228 --
 libemail-utils/em-filter-folder-element.h          |   74 -
 libemail-utils/em-vfolder-context.c                |  112 -
 libemail-utils/em-vfolder-context.h                |   70 -
 libemail-utils/em-vfolder-rule.c                   |  496 ---
 libemail-utils/em-vfolder-rule.h                   |  102 -
 libemail-utils/libemail-utils.pc.in                |   16 -
 libemail-utils/mail-mt.h                           |  124 -
 libevolution-utils/Makefile.am                     |   45 -
 libevolution-utils/e-alert-dialog.h                |   80 -
 libevolution-utils/e-alert-sink.c                  |   93 -
 libevolution-utils/e-alert-sink.h                  |   62 -
 libevolution-utils/e-xml-utils.c                   |  447 ---
 libevolution-utils/evolution-util.c                |  327 --
 libevolution-utils/evolution-util.h                |   47 -
 libevolution-utils/libevolution-utils.pc.in        |   16 -
 mail/Makefile.am                                   |   29 +-
 mail/e-mail-account-store.c                        |    3 -
 mail/e-mail-backend.c                              |    3 -
 mail/e-mail-browser.c                              |    4 -
 mail/e-mail-browser.h                              |    2 +-
 mail/e-mail-config-activity-page.c                 |    5 -
 mail/e-mail-config-activity-page.h                 |    2 +-
 mail/e-mail-config-assistant.c                     |    2 -
 mail/e-mail-config-auth-check.c                    |    7 +-
 mail/e-mail-config-identity-page.c                 |    4 +-
 mail/e-mail-config-provider-page.h                 |    2 +-
 mail/e-mail-config-window.c                        |    8 +-
 mail/e-mail-display.c                              |   31 +-
 mail/e-mail-display.h                              |    3 +-
 mail/e-mail-folder-pane.c                          |   18 +-
 mail/e-mail-migrate.c                              |   13 +-
 mail/e-mail-notebook-view.h                        |    1 -
 mail/e-mail-paned-view.c                           |   18 +-
 mail/e-mail-printer.c                              |    9 +-
 mail/e-mail-reader-utils.c                         |   35 +-
 mail/e-mail-reader.c                               |   46 +-
 mail/e-mail-reader.h                               |    4 +-
 mail/e-mail-request.c                              |   11 +-
 mail/e-mail-tag-editor.c                           |    1 -
 mail/e-mail-ui-session.c                           |    4 +-
 mail/e-mail-ui-session.h                           |    3 +-
 mail/e-mail-view.h                                 |    1 -
 mail/em-composer-utils.c                           |    7 +-
 mail/em-config.h                                   |    3 +-
 mail/em-event.h                                    |    3 +-
 mail/em-filter-context.c                           |    2 -
 mail/em-filter-context.h                           |    2 +-
 mail/em-filter-editor-folder-element.c             |    5 +-
 mail/em-filter-editor-folder-element.h             |    4 +-
 mail/em-filter-editor.h                            |    3 +-
 mail/em-filter-rule.h                              |    2 +-
 mail/em-filter-source-element.c                    |   12 +-
 mail/em-filter-source-element.h                    |    2 +-
 mail/em-folder-properties.c                        |    2 +-
 mail/em-folder-properties.h                        |    1 -
 mail/em-folder-tree-model.c                        |    5 +-
 mail/em-folder-tree.c                              |    9 +-
 mail/em-folder-tree.h                              |    3 +-
 mail/em-folder-utils.c                             |    9 +-
 mail/em-search-context.c                           |    7 +-
 mail/em-search-context.h                           |    2 +-
 mail/em-subscription-editor.c                      |   10 +-
 mail/em-utils.c                                    |   27 +-
 mail/em-vfolder-editor-context.c                   |   12 +-
 mail/em-vfolder-editor-context.h                   |    4 +-
 mail/em-vfolder-editor-rule.c                      |    1 -
 mail/em-vfolder-editor-rule.h                      |    4 +-
 mail/em-vfolder-editor.h                           |    3 +-
 mail/importers/Makefile.am                         |    6 +-
 mail/importers/elm-importer.c                      |    3 +-
 mail/importers/evolution-mbox-importer.c           |    6 +-
 mail/importers/mail-importer.c                     |    4 +-
 mail/importers/mail-importer.h                     |    2 +-
 mail/importers/pine-importer.c                     |    3 +-
 mail/mail-autofilter.c                             |    3 -
 mail/mail-autofilter.h                             |    3 +-
 mail/mail-send-recv.c                              |    3 +-
 mail/mail-vfolder-ui.c                             |    5 +-
 mail/mail-vfolder-ui.h                             |    7 +-
 mail/message-list.c                                |   30 +-
 mail/message-list.h                                |    2 +-
 maint/Makefile.am                                  |    2 +-
 modules/addressbook/Makefile.am                    |    8 -
 modules/addressbook/autocompletion-config.c        |    4 -
 modules/addressbook/autocompletion-config.h        |    1 -
 modules/addressbook/e-book-config-hook.c           |    1 -
 .../e-book-config-name-selector-entry.c            |    2 +-
 modules/addressbook/e-book-shell-backend.c         |    5 -
 modules/addressbook/e-book-shell-content.c         |    3 -
 modules/addressbook/e-book-shell-content.h         |    2 -
 modules/addressbook/e-book-shell-sidebar.h         |    2 -
 modules/addressbook/e-book-shell-view-actions.c    |    6 -
 modules/addressbook/e-book-shell-view-private.c    |    1 -
 modules/addressbook/e-book-shell-view-private.h    |    7 -
 modules/audio-inline/Makefile.am                   |    5 +-
 .../audio-inline/e-mail-formatter-audio-inline.c   |    8 +-
 modules/backup-restore/Makefile.am                 |   10 +-
 .../backup-restore/e-mail-config-restore-page.c    |    4 +-
 modules/backup-restore/evolution-backup-restore.c  |    6 +-
 modules/bogofilter/Makefile.am                     |    1 -
 modules/book-config-google/Makefile.am             |   10 +-
 .../evolution-book-config-google.c                 |    4 +-
 modules/book-config-ldap/Makefile.am               |    7 +-
 .../book-config-ldap/evolution-book-config-ldap.c  |    4 +-
 modules/book-config-local/Makefile.am              |   10 +-
 .../evolution-book-config-local.c                  |    3 +-
 modules/book-config-webdav/Makefile.am             |   10 +-
 .../evolution-book-config-webdav.c                 |    3 +-
 modules/cal-config-caldav/Makefile.am              |    6 +-
 modules/cal-config-caldav/e-caldav-chooser.c       |    2 +-
 .../evolution-cal-config-caldav.c                  |    4 +-
 modules/cal-config-contacts/Makefile.am            |   10 +-
 modules/cal-config-contacts/e-contacts-selector.h  |    2 +-
 .../evolution-cal-config-contacts.c                |    4 +-
 modules/cal-config-google/Makefile.am              |    6 +-
 modules/cal-config-google/e-google-chooser.c       |    2 +-
 .../evolution-cal-config-google.c                  |    3 +-
 modules/cal-config-local/Makefile.am               |   10 +-
 .../cal-config-local/evolution-cal-config-local.c  |    3 +-
 modules/cal-config-weather/Makefile.am             |    6 +-
 .../evolution-cal-config-weather.c                 |    3 +-
 modules/cal-config-webcal/Makefile.am              |   10 +-
 .../evolution-cal-config-webcal.c                  |    4 +-
 modules/calendar/Makefile.am                       |    9 +-
 modules/calendar/e-cal-attachment-handler.c        |    1 -
 modules/calendar/e-cal-attachment-handler.h        |    2 +-
 modules/calendar/e-cal-config-calendar-item.c      |    1 -
 modules/calendar/e-cal-config-date-edit.c          |    1 -
 modules/calendar/e-cal-config-hook.c               |    1 -
 modules/calendar/e-cal-event-hook.c                |    1 -
 modules/calendar/e-cal-shell-backend.c             |    5 -
 modules/calendar/e-cal-shell-content.c             |    4 -
 modules/calendar/e-cal-shell-content.h             |    1 -
 modules/calendar/e-cal-shell-sidebar.c             |    4 -
 modules/calendar/e-cal-shell-sidebar.h             |    2 -
 modules/calendar/e-cal-shell-view-actions.c        |    1 -
 modules/calendar/e-cal-shell-view-private.c        |    1 -
 modules/calendar/e-cal-shell-view-private.h        |   11 -
 modules/calendar/e-calendar-preferences.c          |    6 -
 modules/calendar/e-calendar-preferences.h          |    3 -
 modules/calendar/e-memo-shell-backend.c            |    3 -
 modules/calendar/e-memo-shell-content.c            |    4 -
 modules/calendar/e-memo-shell-content.h            |    3 -
 modules/calendar/e-memo-shell-sidebar.c            |    2 -
 modules/calendar/e-memo-shell-sidebar.h            |    1 -
 modules/calendar/e-memo-shell-view-actions.c       |    1 -
 modules/calendar/e-memo-shell-view-private.c       |    2 -
 modules/calendar/e-memo-shell-view-private.h       |    7 -
 modules/calendar/e-task-shell-backend.c            |    3 -
 modules/calendar/e-task-shell-content.c            |    4 -
 modules/calendar/e-task-shell-content.h            |    3 -
 modules/calendar/e-task-shell-sidebar.c            |    2 -
 modules/calendar/e-task-shell-sidebar.h            |    1 -
 modules/calendar/e-task-shell-view-actions.c       |    1 -
 modules/calendar/e-task-shell-view-private.c       |    2 -
 modules/calendar/e-task-shell-view-private.h       |    8 -
 modules/composer-autosave/Makefile.am              |    5 +-
 modules/composer-autosave/e-composer-autosave.c    |    1 -
 modules/composer-autosave/e-composer-registry.c    |    1 -
 modules/imap-features/Makefile.am                  |    6 +-
 modules/itip-formatter/Makefile.am                 |   12 +-
 .../itip-formatter/e-conflict-search-selector.h    |    2 +-
 modules/itip-formatter/e-mail-parser-itip.c        |    5 +-
 modules/itip-formatter/e-mail-part-itip.h          |    1 -
 modules/itip-formatter/itip-view.c                 |   13 +-
 modules/itip-formatter/plugin/Makefile.am          |   10 +-
 modules/mail-config/Makefile.am                    |    7 +-
 .../mail-config/e-mail-config-remote-accounts.c    |    2 -
 modules/mail-config/e-mail-config-smtp-backend.c   |    2 -
 modules/mail/Makefile.am                           |   12 +-
 modules/mail/e-mail-attachment-handler.c           |    1 -
 modules/mail/e-mail-attachment-handler.h           |    2 +-
 modules/mail/e-mail-config-hook.c                  |    1 -
 modules/mail/e-mail-config-web-view-gtkhtml.c      |    1 -
 modules/mail/e-mail-config-web-view.c              |    1 -
 modules/mail/e-mail-event-hook.c                   |    1 -
 modules/mail/e-mail-shell-backend.c                |    7 -
 modules/mail/e-mail-shell-backend.h                |    3 +-
 modules/mail/e-mail-shell-content.c                |    6 -
 modules/mail/e-mail-shell-view-private.c           |    3 -
 modules/mail/e-mail-shell-view-private.h           |    9 -
 modules/mail/e-mail-shell-view.c                   |    1 -
 modules/mail/em-account-prefs.c                    |    2 -
 modules/mail/em-account-prefs.h                    |    2 -
 modules/mail/em-composer-prefs.c                   |    6 -
 modules/mail/em-composer-prefs.h                   |    1 -
 modules/mail/em-mailer-prefs.c                     |    6 -
 modules/mail/em-mailer-prefs.h                     |    1 -
 modules/mail/em-network-prefs.c                    |    1 -
 modules/mail/em-network-prefs.h                    |    2 +-
 modules/mailto-handler/Makefile.am                 |    2 +
 modules/mdn/Makefile.am                            |    4 +-
 modules/mdn/evolution-mdn.c                        |    2 -
 modules/offline-alert/Makefile.am                  |   10 +-
 modules/offline-alert/evolution-offline-alert.c    |    1 -
 modules/online-accounts/Makefile.am                |    5 +-
 modules/plugin-lib/Makefile.am                     |   10 +-
 modules/plugin-lib/e-plugin-lib.h                  |    2 +-
 modules/plugin-manager/Makefile.am                 |    9 +-
 modules/plugin-manager/evolution-plugin-manager.c  |    1 -
 modules/prefer-plain/Makefile.am                   |   11 +-
 modules/prefer-plain/plugin/Makefile.am            |    9 +-
 modules/prefer-plain/plugin/config-ui.c            |    2 -
 modules/spamassassin/Makefile.am                   |    9 +-
 modules/spamassassin/evolution-spamassassin.c      |    1 -
 modules/startup-wizard/Makefile.am                 |    8 +-
 modules/startup-wizard/e-mail-config-import-page.c |    2 -
 modules/startup-wizard/e-mail-config-import-page.h |    2 +-
 .../e-mail-config-import-progress-page.h           |    2 -
 modules/startup-wizard/e-startup-assistant.c       |    2 +
 modules/startup-wizard/evolution-startup-wizard.c  |    3 -
 modules/text-highlight/Makefile.am                 |   10 +-
 modules/tnef-attachment/Makefile.am                |    5 +-
 .../e-mail-parser-tnef-attachment.c                |   19 +-
 modules/vcard-inline/Makefile.am                   |    9 +-
 modules/vcard-inline/e-mail-parser-vcard-inline.c  |    1 -
 modules/web-inspector/Makefile.am                  |   10 +-
 modules/web-inspector/evolution-web-inspector.c    |    3 +-
 plugins/attachment-reminder/Makefile.am            |    5 +-
 plugins/attachment-reminder/attachment-reminder.c  |    7 -
 plugins/bbdb/Makefile.am                           |    4 +-
 plugins/bbdb/bbdb.c                                |    3 -
 plugins/bbdb/gaimbuddies.c                         |    5 +-
 plugins/dbx-import/Makefile.am                     |   10 +-
 plugins/dbx-import/dbx-importer.c                  |    7 +-
 plugins/email-custom-header/Makefile.am            |    6 +-
 plugins/email-custom-header/email-custom-header.c  |    3 +-
 plugins/external-editor/Makefile.am                |    4 +-
 plugins/external-editor/external-editor.c          |    1 -
 plugins/face/Makefile.am                           |    6 +-
 plugins/face/face.c                                |    3 -
 plugins/mail-notification/Makefile.am              |    4 +-
 plugins/mail-notification/mail-notification.c      |    1 -
 plugins/mail-to-task/Makefile.am                   |   11 +-
 plugins/mail-to-task/mail-to-task.c                |    6 -
 plugins/mailing-list-actions/Makefile.am           |    5 +-
 .../mailing-list-actions/mailing-list-actions.c    |    3 +-
 plugins/mark-all-read/Makefile.am                  |   10 +-
 plugins/mark-all-read/mark-all-read.c              |    1 -
 plugins/pst-import/Makefile.am                     |    7 +-
 plugins/pst-import/pst-importer.c                  |   13 +-
 plugins/publish-calendar/Makefile.am               |   12 +-
 plugins/publish-calendar/publish-calendar.c        |    3 -
 plugins/publish-calendar/publish-location.c        |    3 +-
 plugins/publish-calendar/url-editor-dialog.h       |    1 -
 plugins/save-calendar/Makefile.am                  |   10 +-
 plugins/save-calendar/format-handler.h             |    4 +-
 plugins/save-calendar/save-calendar.c              |    3 -
 plugins/templates/Makefile.am                      |    4 +-
 plugins/templates/templates.c                      |    5 -
 po/POTFILES.in                                     |    5 +-
 shell/Makefile.am                                  |   25 +-
 shell/e-convert-local-mail.c                       |    1 -
 shell/e-shell-backend.c                            |   36 +-
 shell/e-shell-backend.h                            |    2 +-
 shell/e-shell-content.c                            |    7 -
 shell/e-shell-migrate.c                            |    4 -
 shell/e-shell-searchbar.c                          |    3 -
 shell/e-shell-searchbar.h                          |    1 -
 shell/e-shell-sidebar.c                            |    2 -
 shell/e-shell-taskbar.c                            |    1 -
 shell/e-shell-utils.c                              |    2 -
 shell/e-shell-utils.h                              |    2 -
 shell/e-shell-view.c                               |    8 -
 shell/e-shell-view.h                               |    7 +-
 shell/e-shell-window-actions.c                     |    7 -
 shell/e-shell-window-private.h                     |   11 -
 shell/e-shell-window.c                             |   44 +-
 shell/e-shell-window.h                             |    5 +-
 shell/e-shell.c                                    |    3 -
 shell/e-shell.h                                    |    3 +-
 shell/es-event.h                                   |    2 +-
 shell/main.c                                       |    8 -
 smime/gui/Makefile.am                              |    8 +-
 smime/gui/certificate-manager.c                    |    4 -
 smime/gui/certificate-manager.h                    |    1 -
 smime/gui/component.c                              |    3 +-
 smime/lib/Makefile.am                              |    5 +-
 smime/lib/e-cert-db.c                              |    4 +-
 smime/lib/e-pkcs12.c                               |    2 +-
 widgets/LICENSE                                    |    1 -
 widgets/Makefile.am                                |    9 -
 widgets/e-timezone-dialog/Makefile.am              |   29 -
 widgets/e-timezone-dialog/e-timezone-dialog.c      |  870 -----
 widgets/e-timezone-dialog/e-timezone-dialog.h      |   73 -
 widgets/menus/Makefile.am                          |   58 -
 widgets/menus/gal-define-views-dialog.c            |  451 ---
 widgets/menus/gal-define-views-dialog.h            |   73 -
 widgets/menus/gal-define-views-model.c             |  353 --
 widgets/menus/gal-define-views-model.h             |   66 -
 widgets/menus/gal-view-collection.c                |  829 ----
 widgets/menus/gal-view-collection.h                |  146 -
 widgets/menus/gal-view-etable.c                    |  335 --
 widgets/menus/gal-view-etable.h                    |   92 -
 widgets/menus/gal-view-factory-etable.c            |  196 -
 widgets/menus/gal-view-factory-etable.h            |   73 -
 widgets/menus/gal-view-factory.c                   |  103 -
 widgets/menus/gal-view-factory.h                   |   81 -
 widgets/menus/gal-view-instance-save-as-dialog.c   |  358 --
 widgets/menus/gal-view-instance-save-as-dialog.h   |   88 -
 widgets/menus/gal-view-instance.c                  |  503 ---
 widgets/menus/gal-view-instance.h                  |  110 -
 widgets/menus/gal-view-new-dialog.c                |  290 --
 widgets/menus/gal-view-new-dialog.h                |   77 -
 widgets/menus/gal-view.c                           |  282 --
 widgets/menus/gal-view.h                           |   94 -
 widgets/misc/Makefile.am                           |  289 --
 widgets/misc/e-action-combo-box.h                  |   85 -
 widgets/misc/e-activity-bar.h                      |   67 -
 widgets/misc/e-activity-proxy.h                    |   70 -
 widgets/misc/e-alarm-selector.h                    |   63 -
 widgets/misc/e-alert-bar.h                         |   67 -
 widgets/misc/e-attachment-bar.h                    |   79 -
 widgets/misc/e-attachment-button.h                 |   87 -
 widgets/misc/e-attachment-dialog.h                 |   73 -
 widgets/misc/e-attachment-handler-image.c          |  248 --
 widgets/misc/e-attachment-handler-image.h          |   65 -
 widgets/misc/e-attachment-handler-sendto.h         |   62 -
 widgets/misc/e-attachment-handler.h                |   80 -
 widgets/misc/e-attachment-icon-view.h              |   67 -
 widgets/misc/e-attachment-paned.h                  |   95 -
 widgets/misc/e-attachment-store.c                  | 1281 -------
 widgets/misc/e-attachment-store.h                  |  133 -
 widgets/misc/e-attachment-tree-view.h              |   66 -
 widgets/misc/e-attachment-view.c                   | 1907 ---------
 widgets/misc/e-attachment-view.h                   |  240 --
 widgets/misc/e-attachment.c                        | 2883 --------------
 widgets/misc/e-attachment.h                        |  155 -
 widgets/misc/e-auth-combo-box.h                    |   71 -
 widgets/misc/e-autocomplete-selector.h             |   64 -
 widgets/misc/e-book-source-config.h                |   67 -
 widgets/misc/e-buffer-tagger.c                     |  691 ----
 widgets/misc/e-buffer-tagger.h                     |   35 -
 widgets/misc/e-cal-source-config.c                 |  431 ---
 widgets/misc/e-cal-source-config.h                 |   72 -
 widgets/misc/e-calendar-item.c                     | 3772 ------------------
 widgets/misc/e-calendar-item.h                     |  390 --
 widgets/misc/e-calendar.h                          |  108 -
 widgets/misc/e-canvas-background.c                 |  279 --
 widgets/misc/e-canvas-background.h                 |   70 -
 widgets/misc/e-canvas-utils.h                      |   55 -
 widgets/misc/e-canvas-vbox.c                       |  411 --
 widgets/misc/e-canvas-vbox.h                       |   85 -
 widgets/misc/e-canvas.c                            |  882 -----
 widgets/misc/e-canvas.h                            |  137 -
 widgets/misc/e-charset-combo-box.c                 |  407 --
 widgets/misc/e-charset-combo-box.h                 |   69 -
 widgets/misc/e-contact-map-window.h                |   77 -
 widgets/misc/e-contact-map.c                       |  407 --
 widgets/misc/e-contact-map.h                       |  106 -
 widgets/misc/e-contact-marker.h                    |   84 -
 widgets/misc/e-dateedit.c                          | 2498 ------------
 widgets/misc/e-dateedit.h                          |  215 --
 widgets/misc/e-focus-tracker.c                     |  886 -----
 widgets/misc/e-focus-tracker.h                     |  100 -
 widgets/misc/e-image-chooser.c                     |  562 ---
 widgets/misc/e-image-chooser.h                     |   76 -
 widgets/misc/e-import-assistant.c                  | 1436 -------
 widgets/misc/e-import-assistant.h                  |   68 -
 widgets/misc/e-interval-chooser.c                  |  214 --
 widgets/misc/e-interval-chooser.h                  |   68 -
 widgets/misc/e-mail-identity-combo-box.h           |   71 -
 widgets/misc/e-mail-signature-combo-box.h          |   91 -
 widgets/misc/e-mail-signature-editor.c             |  914 -----
 widgets/misc/e-mail-signature-editor.h             |   82 -
 widgets/misc/e-mail-signature-manager.h            |   88 -
 widgets/misc/e-mail-signature-preview.c            |  358 --
 widgets/misc/e-mail-signature-preview.h            |   79 -
 widgets/misc/e-mail-signature-script-dialog.h      |   90 -
 widgets/misc/e-mail-signature-tree-view.h          |   76 -
 widgets/misc/e-map.c                               | 1430 -------
 widgets/misc/e-map.h                               |  151 -
 widgets/misc/e-menu-tool-action.h                  |   71 -
 widgets/misc/e-menu-tool-button.h                  |   73 -
 widgets/misc/e-online-button.h                     |   65 -
 widgets/misc/e-paned.h                             |   78 -
 widgets/misc/e-picture-gallery.c                   |  437 ---
 widgets/misc/e-picture-gallery.h                   |   67 -
 widgets/misc/e-popup-action.h                      |   92 -
 widgets/misc/e-port-entry.h                        |   87 -
 widgets/misc/e-preferences-window.c                |  642 ----
 widgets/misc/e-preferences-window.h                |   84 -
 widgets/misc/e-preview-pane.c                      |  323 --
 widgets/misc/e-preview-pane.h                      |   75 -
 widgets/misc/e-printable.c                         |  226 --
 widgets/misc/e-printable.h                         |   89 -
 widgets/misc/e-search-bar.h                        |   84 -
 widgets/misc/e-selectable.h                        |   80 -
 widgets/misc/e-selection-model-array.c             |  647 ----
 widgets/misc/e-selection-model-array.h             |   91 -
 widgets/misc/e-selection-model-simple.c            |  119 -
 widgets/misc/e-selection-model-simple.h            |   66 -
 widgets/misc/e-selection-model.c                   |  813 ----
 widgets/misc/e-selection-model.h                   |  205 -
 widgets/misc/e-send-options.c                      |  767 ----
 widgets/misc/e-send-options.h                      |  127 -
 widgets/misc/e-source-config-backend.h             |   94 -
 widgets/misc/e-source-config-dialog.c              |  394 --
 widgets/misc/e-source-config-dialog.h              |   65 -
 widgets/misc/e-source-config.c                     | 1448 -------
 widgets/misc/e-source-config.h                     |  116 -
 widgets/misc/e-spell-entry.h                       |   59 -
 widgets/misc/e-url-entry.c                         |  157 -
 widgets/misc/e-url-entry.h                         |   56 -
 widgets/misc/e-web-view-gtkhtml.c                  | 2318 -----------
 widgets/misc/e-web-view-gtkhtml.h                  |  173 -
 widgets/misc/e-web-view-preview.h                  |  111 -
 widgets/misc/e-web-view.c                          | 2946 --------------
 widgets/misc/e-web-view.h                          |  220 --
 widgets/misc/ea-calendar-cell.c                    |  405 --
 widgets/misc/ea-calendar-cell.h                    |   86 -
 widgets/misc/ea-calendar-item.c                    | 1372 -------
 widgets/misc/ea-calendar-item.h                    |   67 -
 widgets/misc/ea-cell-table.h                       |   59 -
 widgets/misc/ea-widgets.c                          |   36 -
 widgets/misc/ea-widgets.h                          |   32 -
 widgets/misc/test-calendar.c                       |  146 -
 widgets/misc/test-mail-signatures.c                |  199 -
 widgets/table/Makefile.am                          |  193 -
 widgets/table/e-cell-checkbox.c                    |  104 -
 widgets/table/e-cell-checkbox.h                    |   67 -
 widgets/table/e-cell-combo.c                       |  839 ----
 widgets/table/e-cell-combo.h                       |   85 -
 widgets/table/e-cell-date-edit.c                   | 1042 -----
 widgets/table/e-cell-date-edit.h                   |  119 -
 widgets/table/e-cell-date.c                        |  130 -
 widgets/table/e-cell-date.h                        |   70 -
 widgets/table/e-cell-hbox.c                        |  354 --
 widgets/table/e-cell-hbox.h                        |   86 -
 widgets/table/e-cell-number.c                      |   94 -
 widgets/table/e-cell-number.h                      |   67 -
 widgets/table/e-cell-percent.h                     |   72 -
 widgets/table/e-cell-pixbuf.h                      |   71 -
 widgets/table/e-cell-popup.c                       |  551 ---
 widgets/table/e-cell-popup.h                       |  113 -
 widgets/table/e-cell-size.c                        |  114 -
 widgets/table/e-cell-size.h                        |   68 -
 widgets/table/e-cell-text.c                        | 2811 --------------
 widgets/table/e-cell-text.h                        |  190 -
 widgets/table/e-cell-toggle.c                      |  470 ---
 widgets/table/e-cell-toggle.h                      |   78 -
 widgets/table/e-cell-tree.c                        |  881 -----
 widgets/table/e-cell-tree.h                        |   85 -
 widgets/table/e-cell-vbox.c                        |  342 --
 widgets/table/e-cell-vbox.h                        |   88 -
 widgets/table/e-cell.c                             |  680 ----
 widgets/table/e-cell.h                             |  294 --
 widgets/table/e-popup-menu.h                       |   55 -
 widgets/table/e-table-click-to-add.c               |  666 ----
 widgets/table/e-table-click-to-add.h               |   94 -
 widgets/table/e-table-col-dnd.h                    |   39 -
 widgets/table/e-table-col.c                        |  223 --
 widgets/table/e-table-col.h                        |  110 -
 widgets/table/e-table-column-specification.c       |  158 -
 widgets/table/e-table-column-specification.h       |   89 -
 widgets/table/e-table-config.c                     | 1482 -------
 widgets/table/e-table-config.h                     |  129 -
 widgets/table/e-table-defines.h                    |   40 -
 widgets/table/e-table-extras.c                     |  412 --
 widgets/table/e-table-extras.h                     |   90 -
 widgets/table/e-table-field-chooser-dialog.c       |  236 --
 widgets/table/e-table-field-chooser-dialog.h       |   72 -
 widgets/table/e-table-field-chooser-item.c         |  751 ----
 widgets/table/e-table-field-chooser-item.h         |   92 -
 widgets/table/e-table-field-chooser.c              |  337 --
 widgets/table/e-table-field-chooser.h              |   79 -
 widgets/table/e-table-group-container.c            | 1668 --------
 widgets/table/e-table-group-container.h            |  133 -
 widgets/table/e-table-group-leaf.c                 |  816 ----
 widgets/table/e-table-group-leaf.h                 |  105 -
 widgets/table/e-table-group.c                      |  773 ----
 widgets/table/e-table-group.h                      |  237 --
 widgets/table/e-table-header-item.c                | 2227 -----------
 widgets/table/e-table-header-item.h                |  143 -
 widgets/table/e-table-header-utils.c               |  282 --
 widgets/table/e-table-header-utils.h               |   49 -
 widgets/table/e-table-header.c                     | 1014 -----
 widgets/table/e-table-header.h                     |  139 -
 widgets/table/e-table-item.c                       | 4041 --------------------
 widgets/table/e-table-item.h                       |  256 --
 widgets/table/e-table-memory-callbacks.c           |  236 --
 widgets/table/e-table-memory-callbacks.h           |  144 -
 widgets/table/e-table-memory-store.c               |  639 ----
 widgets/table/e-table-memory-store.h               |  151 -
 widgets/table/e-table-memory.c                     |  273 --
 widgets/table/e-table-memory.h                     |   86 -
 widgets/table/e-table-model.c                      |  682 ----
 widgets/table/e-table-model.h                      |  213 -
 widgets/table/e-table-one.c                        |  254 --
 widgets/table/e-table-one.h                        |   71 -
 widgets/table/e-table-search.c                     |  235 --
 widgets/table/e-table-search.h                     |   82 -
 widgets/table/e-table-selection-model.c            |  385 --
 widgets/table/e-table-selection-model.h            |   87 -
 widgets/table/e-table-sort-info.c                  |  483 ---
 widgets/table/e-table-sort-info.h                  |  129 -
 widgets/table/e-table-sorted-variable.c            |  237 --
 widgets/table/e-table-sorted-variable.h            |   81 -
 widgets/table/e-table-sorted.c                     |  330 --
 widgets/table/e-table-sorted.h                     |   80 -
 widgets/table/e-table-sorter.c                     |  520 ---
 widgets/table/e-table-sorter.h                     |   90 -
 widgets/table/e-table-sorting-utils.c              |  492 ---
 widgets/table/e-table-sorting-utils.h              |   91 -
 widgets/table/e-table-specification.c              |  436 ---
 widgets/table/e-table-specification.h              |  111 -
 widgets/table/e-table-state.c                      |  321 --
 widgets/table/e-table-state.h                      |   84 -
 widgets/table/e-table-subset-variable.c            |  269 --
 widgets/table/e-table-subset-variable.h            |  101 -
 widgets/table/e-table-subset.c                     |  569 ---
 widgets/table/e-table-subset.h                     |  116 -
 widgets/table/e-table-utils.c                      |  225 --
 widgets/table/e-table-utils.h                      |   50 -
 widgets/table/e-table-without.c                    |  414 --
 widgets/table/e-table-without.h                    |  100 -
 widgets/table/e-table.c                            | 3626 ------------------
 widgets/table/e-table.dia                          |  Bin 4514 -> 0 bytes
 widgets/table/e-table.h                            |  398 --
 widgets/table/e-tree-memory-callbacks.c            |  316 --
 widgets/table/e-tree-memory-callbacks.h            |  178 -
 widgets/table/e-tree-memory.c                      |  744 ----
 widgets/table/e-tree-memory.h                      |  119 -
 widgets/table/e-tree-model.c                       | 1177 ------
 widgets/table/e-tree-model.h                       |  294 --
 widgets/table/e-tree-selection-model.c             |  939 -----
 widgets/table/e-tree-selection-model.h             |   91 -
 widgets/table/e-tree-sorted.c                      | 1434 -------
 widgets/table/e-tree-sorted.h                      |   99 -
 widgets/table/e-tree-table-adapter.c               | 1413 -------
 widgets/table/e-tree-table-adapter.h               |  133 -
 widgets/table/e-tree.c                             | 3956 -------------------
 widgets/table/e-tree.h                             |  372 --
 widgets/table/gal-a11y-e-cell-popup.c              |  153 -
 widgets/table/gal-a11y-e-cell-popup.h              |   60 -
 widgets/table/gal-a11y-e-cell-registry.h           |   70 -
 widgets/table/gal-a11y-e-cell-toggle.c             |  198 -
 widgets/table/gal-a11y-e-cell-toggle.h             |   63 -
 widgets/table/gal-a11y-e-cell-tree.c               |  266 --
 widgets/table/gal-a11y-e-cell-tree.h               |   62 -
 widgets/table/gal-a11y-e-cell-vbox.c               |  235 --
 widgets/table/gal-a11y-e-cell-vbox.h               |   63 -
 widgets/table/gal-a11y-e-cell.c                    |  648 ----
 widgets/table/gal-a11y-e-cell.h                    |  108 -
 .../table/gal-a11y-e-table-click-to-add-factory.c  |  108 -
 .../table/gal-a11y-e-table-click-to-add-factory.h  |   48 -
 widgets/table/gal-a11y-e-table-click-to-add.c      |  358 --
 widgets/table/gal-a11y-e-table-click-to-add.h      |   54 -
 widgets/table/gal-a11y-e-table-column-header.c     |  241 --
 widgets/table/gal-a11y-e-table-column-header.h     |   52 -
 widgets/table/gal-a11y-e-table-factory.h           |   49 -
 widgets/table/gal-a11y-e-table-item-factory.c      |  107 -
 widgets/table/gal-a11y-e-table-item-factory.h      |   48 -
 widgets/table/gal-a11y-e-table-item.c              | 1437 -------
 widgets/table/gal-a11y-e-table-item.h              |   57 -
 widgets/table/gal-a11y-e-table.c                   |  315 --
 widgets/table/gal-a11y-e-table.h                   |   58 -
 widgets/table/gal-a11y-e-tree-factory.h            |   48 -
 widgets/table/gal-a11y-e-tree.c                    |  196 -
 widgets/table/gal-a11y-e-tree.h                    |   57 -
 widgets/table/sample.table                         |   45 -
 widgets/table/spec.xml                             |   21 -
 widgets/text/Makefile.am                           |   44 -
 widgets/text/e-reflow-model.c                      |  411 --
 widgets/text/e-reflow-model.h                      |  125 -
 widgets/text/e-reflow.c                            | 1732 ---------
 widgets/text/e-reflow.h                            |  140 -
 widgets/text/e-text-model-repos.h                  |   54 -
 widgets/text/e-text-model.c                        |  642 ----
 widgets/text/e-text-model.h                        |  108 -
 widgets/text/e-text.c                              | 3405 -----------------
 widgets/text/e-text.h                              |  232 --
 widgets/text/gal-a11y-e-text-factory.c             |  103 -
 widgets/text/gal-a11y-e-text-factory.h             |   48 -
 widgets/text/gal-a11y-e-text.c                     | 1141 ------
 widgets/text/gal-a11y-e-text.h                     |   55 -
 1313 files changed, 149873 insertions(+), 126000 deletions(-)
---
diff --git a/Makefile.am b/Makefile.am
index d9403ab..888734a 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -51,13 +51,8 @@ SUBDIRS = 			\
 	m4			\
 	data			\
 	libgnomecanvas		\
-	libevolution-utils	\
-	filter			\
-	libemail-utils		\
-	libemail-engine		\
 	e-util			\
-	a11y			\
-	widgets			\
+	libemail-engine		\
 	shell			\
 	$(SMIME_SUBDIR)		\
 	em-format		\
diff --git a/addressbook/gui/contact-editor/Makefile.am b/addressbook/gui/contact-editor/Makefile.am
index d67a21b..ddd4592 100644
--- a/addressbook/gui/contact-editor/Makefile.am
+++ b/addressbook/gui/contact-editor/Makefile.am
@@ -3,16 +3,16 @@ privsolib_LTLIBRARIES =	libecontacteditor.la
 libecontacteditor_la_CPPFLAGS =				\
 	$(AM_CPPFLAGS)					\
 	-I$(top_srcdir)					\
-	-I$(top_srcdir)/widgets				\
 	-I$(top_srcdir)/addressbook/			\
 	-I$(top_srcdir)/addressbook/gui/merging		\
-	-I$(top_srcdir)/widgets/table			\
 	-I$(top_builddir)/shell				\
 	-DEVOLUTION_UIDIR=\""$(uidir)"\"		\
 	-DEVOLUTION_IMAGESDIR=\""$(imagesdir)"\"	\
 	-DG_LOG_DOMAIN=\"contact-editor\"		\
 	$(EVOLUTION_DATA_SERVER_CFLAGS)			\
-	$(GNOME_PLATFORM_CFLAGS)
+	$(GNOME_PLATFORM_CFLAGS)			\
+	$(CHAMPLAIN_CFLAGS)				\
+	$(GTKHTML_CFLAGS)
 
 libecontacteditor_la_SOURCES = 			\
 	eab-editor.c				\
@@ -28,15 +28,15 @@ libecontacteditor_la_LDFLAGS = -avoid-version $(NO_UNDEFINED)
 
 libecontacteditor_la_LIBADD =						\
 	$(top_builddir)/e-util/libeutil.la				\
-	$(top_builddir)/widgets/misc/libemiscwidgets.la			\
 	$(top_builddir)/addressbook/util/libeabutil.la			\
 	$(top_builddir)/addressbook/gui/widgets/libeabwidgets.la	\
 	$(top_builddir)/addressbook/gui/merging/libeabbookmerging.la	\
 	$(top_builddir)/addressbook/printing/libecontactprint.la	\
-	$(top_builddir)/widgets/menus/libmenus.la			\
 	$(EVOLUTION_ADDRESSBOOK_LIBS)					\
 	$(EVOLUTION_DATA_SERVER_LIBS)					\
-	$(GNOME_PLATFORM_LIBS)
+	$(GNOME_PLATFORM_LIBS)						\
+	$(CHAMPLAIN_LIBS)						\
+	$(GTKHTML_LIBS)
 
 ui_DATA = 				\
 	contact-editor.ui		\
diff --git a/addressbook/gui/contact-editor/e-contact-editor.c b/addressbook/gui/contact-editor/e-contact-editor.c
index 8e2e455..ef0700b 100644
--- a/addressbook/gui/contact-editor/e-contact-editor.c
+++ b/addressbook/gui/contact-editor/e-contact-editor.c
@@ -30,23 +30,16 @@
 #include <stdlib.h>
 #include <string.h>
 #include <time.h>
+
 #include <gtk/gtk.h>
 #include <glib/gi18n.h>
 #include <gdk/gdkkeysyms.h>
 #include <gdk-pixbuf/gdk-pixbuf.h>
 
-#include <libedataserverui/libedataserverui.h>
+#include "shell/e-shell.h"
 
 #include "addressbook/printing/e-contact-print.h"
 #include "addressbook/gui/widgets/eab-gui-util.h"
-#include "e-util/e-util.h"
-#include "libevolution-utils/e-alert-dialog.h"
-#include "misc/e-dateedit.h"
-#include "misc/e-image-chooser.h"
-#include "misc/e-url-entry.h"
-#include "e-util/e-icon-factory.h"
-#include "e-util/e-util-private.h"
-#include "shell/e-shell.h"
 
 #include "eab-contact-merging.h"
 
diff --git a/addressbook/gui/contact-editor/e-contact-quick-add.c b/addressbook/gui/contact-editor/e-contact-quick-add.c
index e9076a9..6dcb7dc 100644
--- a/addressbook/gui/contact-editor/e-contact-quick-add.c
+++ b/addressbook/gui/contact-editor/e-contact-quick-add.c
@@ -29,13 +29,10 @@
 #include <gtk/gtk.h>
 #include <glib/gi18n.h>
 
-#include <libedataserverui/libedataserverui.h>
-
 #include <addressbook/util/eab-book-util.h>
 #include "e-contact-editor.h"
 #include "e-contact-quick-add.h"
 #include "eab-contact-merging.h"
-#include "libevolution-utils/e-alert-dialog.h"
 
 typedef struct _QuickAdd QuickAdd;
 struct _QuickAdd {
diff --git a/addressbook/gui/contact-list-editor/Makefile.am b/addressbook/gui/contact-list-editor/Makefile.am
index 01a4c7c..0479683 100644
--- a/addressbook/gui/contact-list-editor/Makefile.am
+++ b/addressbook/gui/contact-list-editor/Makefile.am
@@ -3,7 +3,6 @@ privsolib_LTLIBRARIES =	libecontactlisteditor.la
 libecontactlisteditor_la_CPPFLAGS =			\
 	$(AM_CPPFLAGS)					\
 	-I$(top_srcdir)					\
-	-I$(top_srcdir)/widgets				\
 	-I$(top_srcdir)/addressbook/			\
 	-I$(top_srcdir)/addressbook/gui/merging		\
 	-I$(top_srcdir)/addressbook/gui/contact-editor	\
@@ -11,7 +10,9 @@ libecontactlisteditor_la_CPPFLAGS =			\
 	-DEVOLUTION_UIDIR=\""$(uidir)"\"		\
 	-DG_LOG_DOMAIN=\"contact-list-editor\"		\
 	$(EVOLUTION_DATA_SERVER_CFLAGS)			\
-	$(GNOME_PLATFORM_CFLAGS)
+	$(GNOME_PLATFORM_CFLAGS)			\
+	$(CHAMPLAIN_CFLAGS)				\
+	$(GTKHTML_CFLAGS)
 
 libecontactlisteditor_la_SOURCES = 		\
 	e-contact-list-editor.c			\
@@ -24,13 +25,12 @@ libecontactlisteditor_la_LDFLAGS = -avoid-version $(NO_UNDEFINED)
 libecontactlisteditor_la_LIBADD =				\
 	$(top_builddir)/addressbook/util/libeabutil.la		\
 	$(top_builddir)/addressbook/gui/contact-editor/libecontacteditor.la \
-	$(top_builddir)/widgets/table/libetable.la		\
-	$(top_builddir)/widgets/misc/libemiscwidgets.la		\
 	$(top_builddir)/e-util/libeutil.la			\
 	$(top_builddir)/shell/libeshell.la			\
-	$(top_builddir)/libevolution-utils/libevolution-utils.la \
 	$(EVOLUTION_DATA_SERVER_LIBS)				\
-	$(GNOME_PLATFORM_LIBS)
+	$(GNOME_PLATFORM_LIBS)					\
+	$(CHAMPLAIN_LIBS)					\
+	$(GTKHTML_LIBS)
 
 ui_DATA  = contact-list-editor.ui
 
diff --git a/addressbook/gui/contact-list-editor/e-contact-list-editor.c b/addressbook/gui/contact-list-editor/e-contact-list-editor.c
index a620575..8f0c878 100644
--- a/addressbook/gui/contact-list-editor/e-contact-list-editor.c
+++ b/addressbook/gui/contact-list-editor/e-contact-list-editor.c
@@ -25,10 +25,6 @@
 #endif
 
 #include "e-contact-list-editor.h"
-#include <e-util/e-util-private.h>
-#include <libevolution-utils/e-alert-dialog.h>
-#include <e-util/e-selection.h>
-#include "shell/e-shell.h"
 
 #include <string.h>
 
@@ -38,7 +34,8 @@
 
 #include <camel/camel.h>
 
-#include "e-util/e-util.h"
+#include "shell/e-shell.h"
+
 #include "addressbook/gui/widgets/eab-gui-util.h"
 #include "addressbook/util/eab-book-util.h"
 
diff --git a/addressbook/gui/contact-list-editor/e-contact-list-editor.h b/addressbook/gui/contact-list-editor/e-contact-list-editor.h
index 07cc4db..bafd784 100644
--- a/addressbook/gui/contact-list-editor/e-contact-list-editor.h
+++ b/addressbook/gui/contact-list-editor/e-contact-list-editor.h
@@ -23,8 +23,6 @@
 #ifndef __E_CONTACT_LIST_EDITOR_H__
 #define __E_CONTACT_LIST_EDITOR_H__
 
-#include <libedataserverui/libedataserverui.h>
-
 #include "addressbook/gui/contact-editor/eab-editor.h"
 
 #define E_TYPE_CONTACT_LIST_EDITOR \
diff --git a/addressbook/gui/contact-list-editor/e-contact-list-model.c b/addressbook/gui/contact-list-editor/e-contact-list-model.c
index 337fd35..d2aae53 100644
--- a/addressbook/gui/contact-list-editor/e-contact-list-model.c
+++ b/addressbook/gui/contact-list-editor/e-contact-list-model.c
@@ -26,7 +26,6 @@
 #include <string.h>
 
 #include "e-contact-list-model.h"
-#include "libevolution-utils/e-alert-dialog.h"
 #include "shell/e-shell.h"
 
 #define E_CONTACT_LIST_MODEL_GET_PRIVATE(obj) \
diff --git a/addressbook/gui/merging/Makefile.am b/addressbook/gui/merging/Makefile.am
index b1c2b45..a6a1522 100644
--- a/addressbook/gui/merging/Makefile.am
+++ b/addressbook/gui/merging/Makefile.am
@@ -5,10 +5,10 @@ libeabbookmerging_la_CPPFLAGS =				\
 	-DG_LOG_DOMAIN=\"eab-contact-merging\"		\
 	-DEVOLUTION_UIDIR=\""$(uidir)"\"		\
 	-I$(top_srcdir)					\
-	-I$(top_srcdir)/widgets				\
 	-I$(top_srcdir)/addressbook			\
 	$(EVOLUTION_DATA_SERVER_CFLAGS)			\
 	$(GNOME_PLATFORM_CFLAGS)			\
+	$(CHAMPLAIN_CFLAGS)				\
 	$(GTKHTML_CFLAGS)
 
 libeabbookmerging_la_SOURCES =			\
diff --git a/addressbook/gui/merging/eab-contact-compare.c b/addressbook/gui/merging/eab-contact-compare.c
index 7c30b28..8b24ea3 100644
--- a/addressbook/gui/merging/eab-contact-compare.c
+++ b/addressbook/gui/merging/eab-contact-compare.c
@@ -28,7 +28,7 @@
 #include <ctype.h>
 #include <string.h>
 
-#include <libedataserverui/libedataserverui.h>
+#include "e-util/e-util.h"
 
 #include "addressbook/util/eab-book-util.h"
 #include "eab-contact-compare.h"
diff --git a/addressbook/gui/widgets/Makefile.am b/addressbook/gui/widgets/Makefile.am
index 19c9c4f..e7ca15e 100644
--- a/addressbook/gui/widgets/Makefile.am
+++ b/addressbook/gui/widgets/Makefile.am
@@ -12,12 +12,9 @@ libeabwidgets_la_CPPFLAGS =				\
 	-DEVOLUTION_IMAGESDIR=\"${imagesdir}\"		\
 	-DEVOLUTION_PRIVDATADIR=\"${privdatadir}\"	\
 	-I$(top_srcdir)					\
-	-I$(top_srcdir)/filter				\
-	-I$(top_srcdir)/widgets				\
 	-I$(top_srcdir)/addressbook			\
 	-I$(top_srcdir)/addressbook/gui/merging		\
 	-I$(top_srcdir)/addressbook/util		\
-	-I$(top_srcdir)/widgets/misc			\
 	-I$(top_builddir)/shell				\
 	$(EVOLUTION_DATA_SERVER_CFLAGS)			\
 	$(GNOME_PLATFORM_CFLAGS)			\
@@ -69,11 +66,7 @@ libeabwidgets_la_SOURCES =			\
 	ea-addressbook.h
 
 libeabwidgets_la_LIBADD =					\
-	$(top_builddir)/widgets/misc/libemiscwidgets.la		\
 	$(top_builddir)/shell/libeshell.la			\
-	$(top_builddir)/widgets/table/libetable.la		\
-	$(top_builddir)/widgets/menus/libmenus.la		\
-	$(top_builddir)/a11y/libevolution-a11y.la		\
 	$(top_builddir)/e-util/libeutil.la			\
 	$(EVOLUTION_DATA_SERVER_LIBS)				\
 	$(GNOME_PLATFORM_LIBS)					\
diff --git a/addressbook/gui/widgets/e-addressbook-reflow-adapter.h b/addressbook/gui/widgets/e-addressbook-reflow-adapter.h
index d65f3f3..fee217c 100644
--- a/addressbook/gui/widgets/e-addressbook-reflow-adapter.h
+++ b/addressbook/gui/widgets/e-addressbook-reflow-adapter.h
@@ -22,7 +22,8 @@
 #define _E_ADDRESSBOOK_REFLOW_ADAPTER_H_
 
 #include <libebook/libebook.h>
-#include <text/e-reflow-model.h>
+
+#include <e-util/e-util.h>
 
 #include "e-addressbook-model.h"
 
diff --git a/addressbook/gui/widgets/e-addressbook-selector.c b/addressbook/gui/widgets/e-addressbook-selector.c
index 513da87..2441a0b 100644
--- a/addressbook/gui/widgets/e-addressbook-selector.c
+++ b/addressbook/gui/widgets/e-addressbook-selector.c
@@ -24,7 +24,7 @@
 
 #include "e-addressbook-selector.h"
 
-#include <e-util/e-selection.h>
+#include <e-util/e-util.h>
 
 #include <eab-book-util.h>
 #include <eab-contact-merging.h>
diff --git a/addressbook/gui/widgets/e-addressbook-selector.h b/addressbook/gui/widgets/e-addressbook-selector.h
index adabea7..663f586 100644
--- a/addressbook/gui/widgets/e-addressbook-selector.h
+++ b/addressbook/gui/widgets/e-addressbook-selector.h
@@ -21,8 +21,6 @@
 #ifndef E_ADDRESSBOOK_SELECTOR_H
 #define E_ADDRESSBOOK_SELECTOR_H
 
-#include <libedataserverui/libedataserverui.h>
-
 #include "e-addressbook-view.h"
 
 /* Standard GObject macros */
diff --git a/addressbook/gui/widgets/e-addressbook-table-adapter.h b/addressbook/gui/widgets/e-addressbook-table-adapter.h
index 51c9436..4b089b2 100644
--- a/addressbook/gui/widgets/e-addressbook-table-adapter.h
+++ b/addressbook/gui/widgets/e-addressbook-table-adapter.h
@@ -21,7 +21,7 @@
 #ifndef _EAB_TABLE_ADAPTER_H_
 #define _EAB_TABLE_ADAPTER_H_
 
-#include <table/e-table-model.h>
+#include <e-util/e-util.h>
 
 /* Standard GObject macros */
 #define E_TYPE_ADDRESSBOOK_TABLE_ADAPTER \
diff --git a/addressbook/gui/widgets/e-addressbook-view.c b/addressbook/gui/widgets/e-addressbook-view.c
index 5206041..53402a9 100644
--- a/addressbook/gui/widgets/e-addressbook-view.c
+++ b/addressbook/gui/widgets/e-addressbook-view.c
@@ -25,40 +25,29 @@
 #include <config.h>
 #endif
 
+#include <ctype.h>
+#include <string.h>
+
 #include <glib/gi18n.h>
-#include <table/e-table.h>
-#include <table/e-table-model.h>
-#include <table/e-cell-date.h>
-#include <misc/e-selectable.h>
-#include <widgets/menus/gal-view-factory-etable.h>
-#include <filter/e-rule-editor.h>
-#include <widgets/menus/gal-view-etable.h>
-#include <shell/e-shell-sidebar.h>
+#include <gdk/gdkkeysyms.h>
 
-#include "addressbook/printing/e-contact-print.h"
-#include "ea-addressbook.h"
+#include "e-addressbook-view.h"
 
-#include "e-util/e-print.h"
-#include "e-util/e-selection.h"
 #include "e-util/e-util.h"
+#include "shell/e-shell-sidebar.h"
+
+#include "addressbook/printing/e-contact-print.h"
+#include "ea-addressbook.h"
 
 #include "gal-view-minicard.h"
 #include "gal-view-factory-minicard.h"
 
-#include "e-addressbook-view.h"
 #include "e-addressbook-model.h"
 #include "eab-gui-util.h"
 #include "util/eab-book-util.h"
 #include "e-addressbook-table-adapter.h"
 #include "eab-contact-merging.h"
 
-#include "libevolution-utils/e-alert-dialog.h"
-#include "e-util/e-util-private.h"
-
-#include <gdk/gdkkeysyms.h>
-#include <ctype.h>
-#include <string.h>
-
 #define E_ADDRESSBOOK_VIEW_GET_PRIVATE(obj) \
 	(G_TYPE_INSTANCE_GET_PRIVATE \
 	((obj), E_TYPE_ADDRESSBOOK_VIEW, EAddressbookViewPrivate))
diff --git a/addressbook/gui/widgets/e-addressbook-view.h b/addressbook/gui/widgets/e-addressbook-view.h
index 3c8c5e2..94668d6 100644
--- a/addressbook/gui/widgets/e-addressbook-view.h
+++ b/addressbook/gui/widgets/e-addressbook-view.h
@@ -25,10 +25,7 @@
 
 #include <libebook/libebook.h>
 
-#include <menus/gal-view-instance.h>
-#include <misc/e-selection-model.h>
 #include <shell/e-shell-view.h>
-#include <filter/e-filter-rule.h>
 
 #include "e-addressbook-model.h"
 #include "eab-contact-display.h"
diff --git a/addressbook/gui/widgets/e-minicard-label.c b/addressbook/gui/widgets/e-minicard-label.c
index 618e4ea..0e75917 100644
--- a/addressbook/gui/widgets/e-minicard-label.c
+++ b/addressbook/gui/widgets/e-minicard-label.c
@@ -27,14 +27,13 @@
 #include "e-minicard-label.h"
 
 #include <gtk/gtk.h>
-#include <libgnomecanvas/libgnomecanvas.h>
 #include <glib/gi18n.h>
-#include <e-util/e-util.h>
-#include <text/e-text.h>
-#include <misc/e-canvas.h>
-#include <misc/e-canvas-utils.h>
 #include <gdk/gdkkeysyms.h>
 
+#include <libgnomecanvas/libgnomecanvas.h>
+
+#include "e-util/e-util.h"
+
 static void e_minicard_label_set_property  (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec);
 static void e_minicard_label_get_property  (GObject *object, guint property_id, GValue *value, GParamSpec *pspec);
 static gboolean e_minicard_label_event (GnomeCanvasItem *item, GdkEvent *event);
diff --git a/addressbook/gui/widgets/e-minicard-view-widget.c b/addressbook/gui/widgets/e-minicard-view-widget.c
index ecb2483..7ff63a1 100644
--- a/addressbook/gui/widgets/e-minicard-view-widget.c
+++ b/addressbook/gui/widgets/e-minicard-view-widget.c
@@ -25,11 +25,10 @@
 #endif
 
 #include <gtk/gtk.h>
-#include <misc/e-canvas-background.h>
-#include <misc/e-canvas.h>
 #include <glib/gi18n.h>
 
-#include "e-util/e-util.h"
+#include <e-util/e-util.h>
+
 #include "e-minicard-view-widget.h"
 
 static void	e_minicard_view_widget_set_property
diff --git a/addressbook/gui/widgets/e-minicard-view-widget.h b/addressbook/gui/widgets/e-minicard-view-widget.h
index f540a43..71fe00a 100644
--- a/addressbook/gui/widgets/e-minicard-view-widget.h
+++ b/addressbook/gui/widgets/e-minicard-view-widget.h
@@ -23,9 +23,10 @@
 #ifndef __E_MINICARD_VIEW_WIDGET_H__
 #define __E_MINICARD_VIEW_WIDGET_H__
 
-#include <misc/e-canvas.h>
 #include <libebook/libebook.h>
 
+#include <e-util/e-util.h>
+
 #include "e-minicard-view.h"
 
 G_BEGIN_DECLS
diff --git a/addressbook/gui/widgets/e-minicard-view.c b/addressbook/gui/widgets/e-minicard-view.c
index 1889399..08b0cda 100644
--- a/addressbook/gui/widgets/e-minicard-view.c
+++ b/addressbook/gui/widgets/e-minicard-view.c
@@ -26,16 +26,17 @@
 
 #include "e-minicard-view.h"
 
-#include "eab-gui-util.h"
-#include "util/eab-book-util.h"
-#include "e-util/e-util.h"
+#include <string.h>
 
 #include <gtk/gtk.h>
-#include <gdk/gdkkeysyms.h>
-#include <misc/e-canvas.h>
 #include <glib/gi18n.h>
-#include <string.h>
+#include <gdk/gdkkeysyms.h>
+
 #include "e-util/e-util.h"
+
+#include "eab-gui-util.h"
+#include "util/eab-book-util.h"
+
 #include "ea-addressbook.h"
 
 static void e_minicard_view_drag_data_get (GtkWidget *widget,
diff --git a/addressbook/gui/widgets/e-minicard-view.h b/addressbook/gui/widgets/e-minicard-view.h
index 1024a85..bf116e4 100644
--- a/addressbook/gui/widgets/e-minicard-view.h
+++ b/addressbook/gui/widgets/e-minicard-view.h
@@ -26,8 +26,8 @@
 
 #include "e-minicard.h"
 
-#include <text/e-reflow.h>
-#include <misc/e-selection-model-simple.h>
+#include <e-util/e-util.h>
+
 #include "e-addressbook-reflow-adapter.h"
 
 G_BEGIN_DECLS
diff --git a/addressbook/gui/widgets/e-minicard.c b/addressbook/gui/widgets/e-minicard.c
index b0dae34..6a049ac 100644
--- a/addressbook/gui/widgets/e-minicard.c
+++ b/addressbook/gui/widgets/e-minicard.c
@@ -24,20 +24,19 @@
 #include <config.h>
 #endif
 
+#include "e-minicard.h"
+
 #include <string.h>
-#include <gdk/gdkkeysyms.h>
 #include <glib/gi18n.h>
+#include <gdk/gdkkeysyms.h>
+
 #include <libgnomecanvas/libgnomecanvas.h>
-#include <text/e-text.h>
-#include <e-util/e-util.h>
-#include <misc/e-canvas-utils.h>
-#include <misc/e-canvas.h>
+
+#include "e-util/e-util.h"
+
 #include "eab-gui-util.h"
-#include "e-minicard.h"
 #include "e-minicard-label.h"
 #include "e-minicard-view.h"
-#include <e-util/e-html-utils.h>
-#include <e-util/e-icon-factory.h>
 #include "ea-addressbook.h"
 
 static void e_minicard_set_property  (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec);
diff --git a/addressbook/gui/widgets/ea-addressbook.c b/addressbook/gui/widgets/ea-addressbook.c
index ca218f4..51af22d 100644
--- a/addressbook/gui/widgets/ea-addressbook.c
+++ b/addressbook/gui/widgets/ea-addressbook.c
@@ -24,8 +24,8 @@
 #include <config.h>
 #endif
 
-#include <text/e-text.h>
-#include "a11y/ea-factory.h"
+#include "e-util/e-util.h"
+
 #include "ea-addressbook.h"
 #include "ea-minicard.h"
 #include "ea-minicard-view.h"
diff --git a/addressbook/gui/widgets/eab-config.h b/addressbook/gui/widgets/eab-config.h
index 3907889..83c08b4 100644
--- a/addressbook/gui/widgets/eab-config.h
+++ b/addressbook/gui/widgets/eab-config.h
@@ -26,7 +26,7 @@
 
 #include <libedataserver/libedataserver.h>
 
-#include "e-util/e-config.h"
+#include <e-util/e-util.h>
 
 #define EAB_TYPE_CONFIG (eab_config_get_type ())
 #define EAB_CONFIG(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EAB_TYPE_CONFIG, EABConfig))
diff --git a/addressbook/gui/widgets/eab-contact-display.c b/addressbook/gui/widgets/eab-contact-display.c
index e00e089..549278b 100644
--- a/addressbook/gui/widgets/eab-contact-display.c
+++ b/addressbook/gui/widgets/eab-contact-display.c
@@ -25,25 +25,17 @@
 #endif
 
 #include "eab-contact-display.h"
-#include "eab-contact-formatter.h"
 
-#include "eab-gui-util.h"
-#include "e-util/e-util.h"
-#include "e-util/e-util-private.h"
-#include "e-util/e-html-utils.h"
-#include "e-util/e-icon-factory.h"
-#include "e-util/e-plugin-ui.h"
-#include "e-util/e-file-request.h"
-#include "e-util/e-stock-request.h"
+#include <string.h>
+#include <glib/gi18n.h>
 
 #include <webkit/webkit.h>
 
-#ifdef WITH_CONTACT_MAPS
-#include "widgets/misc/e-contact-map.h"
-#endif
+#include "e-util/e-util.h"
 
-#include <string.h>
-#include <glib/gi18n.h>
+#include "eab-contact-formatter.h"
+
+#include "eab-gui-util.h"
 
 #define EAB_CONTACT_DISPLAY_GET_PRIVATE(obj) \
 	(G_TYPE_INSTANCE_GET_PRIVATE \
diff --git a/addressbook/gui/widgets/eab-contact-display.h b/addressbook/gui/widgets/eab-contact-display.h
index 79243de..484e312 100644
--- a/addressbook/gui/widgets/eab-contact-display.h
+++ b/addressbook/gui/widgets/eab-contact-display.h
@@ -25,7 +25,7 @@
 
 #include <libebook/libebook.h>
 
-#include <misc/e-web-view.h>
+#include <e-util/e-util.h>
 
 /* Standard GObject macros */
 #define EAB_TYPE_CONTACT_DISPLAY \
diff --git a/addressbook/gui/widgets/eab-contact-formatter.c b/addressbook/gui/widgets/eab-contact-formatter.c
index a389318..3684e4b 100644
--- a/addressbook/gui/widgets/eab-contact-formatter.c
+++ b/addressbook/gui/widgets/eab-contact-formatter.c
@@ -20,20 +20,13 @@
 
 #include "eab-contact-formatter.h"
 
-#include "eab-gui-util.h"
-#include "e-util/e-util.h"
-#include "e-util/e-util-private.h"
-#include "e-util/e-html-utils.h"
-#include "e-util/e-icon-factory.h"
-#include "e-util/e-plugin-ui.h"
-
-#ifdef WITH_CONTACT_MAPS
-#include "widgets/misc/e-contact-map.h"
-#endif
-
 #include <string.h>
 #include <glib/gi18n.h>
 
+#include "e-util/e-util.h"
+
+#include "eab-gui-util.h"
+
 G_DEFINE_TYPE (
         EABContactFormatter,
         eab_contact_formatter,
diff --git a/addressbook/gui/widgets/eab-gui-util.c b/addressbook/gui/widgets/eab-gui-util.c
index 736f64d..1fc8644 100644
--- a/addressbook/gui/widgets/eab-gui-util.c
+++ b/addressbook/gui/widgets/eab-gui-util.c
@@ -35,16 +35,10 @@
 #include <gtk/gtk.h>
 #include <glib/gi18n.h>
 
-#include <libedataserverui/libedataserverui.h>
+#include "shell/e-shell.h"
 
-#include <e-util/e-util.h>
 #include "eab-gui-util.h"
 #include "util/eab-book-util.h"
-#include "libevolution-utils/e-alert-dialog.h"
-#include "e-util/e-html-utils.h"
-#include "shell/e-shell.h"
-#include "misc/e-image-chooser.h"
-#include <e-util/e-icon-factory.h>
 #include "eab-contact-merging.h"
 
 /* we link to camel for decoding quoted printable email addresses */
diff --git a/addressbook/gui/widgets/eab-gui-util.h b/addressbook/gui/widgets/eab-gui-util.h
index 7cdd93e..6d3c7bf 100644
--- a/addressbook/gui/widgets/eab-gui-util.h
+++ b/addressbook/gui/widgets/eab-gui-util.h
@@ -27,7 +27,7 @@
 #include <gtk/gtk.h>
 #include <libebook/libebook.h>
 
-#include "libevolution-utils/e-alert-sink.h"
+#include <e-util/e-util.h>
 
 G_BEGIN_DECLS
 
diff --git a/addressbook/gui/widgets/gal-view-factory-minicard.h b/addressbook/gui/widgets/gal-view-factory-minicard.h
index e96c4e4..a01f5e9 100644
--- a/addressbook/gui/widgets/gal-view-factory-minicard.h
+++ b/addressbook/gui/widgets/gal-view-factory-minicard.h
@@ -26,7 +26,7 @@
 #ifndef GAL_VIEW_FACTORY_MINICARD_H
 #define GAL_VIEW_FACTORY_MINICARD_H
 
-#include <widgets/menus/gal-view-factory.h>
+#include <e-util/e-util.h>
 
 /* Standard GObject macros */
 #define GAL_TYPE_VIEW_FACTORY_MINICARD \
diff --git a/addressbook/gui/widgets/gal-view-minicard.c b/addressbook/gui/widgets/gal-view-minicard.c
index a623c57..dffe806 100644
--- a/addressbook/gui/widgets/gal-view-minicard.c
+++ b/addressbook/gui/widgets/gal-view-minicard.c
@@ -28,7 +28,6 @@
 #endif
 
 #include <libxml/parser.h>
-#include <libevolution-utils/e-xml-utils.h>
 
 #include "gal-view-minicard.h"
 
diff --git a/addressbook/gui/widgets/gal-view-minicard.h b/addressbook/gui/widgets/gal-view-minicard.h
index b360301..04e6711 100644
--- a/addressbook/gui/widgets/gal-view-minicard.h
+++ b/addressbook/gui/widgets/gal-view-minicard.h
@@ -25,7 +25,7 @@
 #ifndef GAL_VIEW_MINICARD_H
 #define GAL_VIEW_MINICARD_H
 
-#include <widgets/menus/gal-view.h>
+#include <e-util/e-util.h>
 #include <e-minicard-view-widget.h>
 #include "e-addressbook-view.h"
 
diff --git a/addressbook/importers/Makefile.am b/addressbook/importers/Makefile.am
index 515370d..cd509f9 100644
--- a/addressbook/importers/Makefile.am
+++ b/addressbook/importers/Makefile.am
@@ -7,10 +7,11 @@ libevolution_addressbook_importers_la_CPPFLAGS =	\
 	-DG_LOG_DOMAIN=\"Evolution-Importer\"		\
 	-I$(top_srcdir)					\
 	-I$(top_srcdir)/addressbook			\
-	-I$(top_srcdir)/widgets				\
 	-I$(top_builddir)/addressbook			\
 	$(EVOLUTION_DATA_SERVER_CFLAGS)			\
-	$(GNOME_PLATFORM_CFLAGS)
+	$(GNOME_PLATFORM_CFLAGS)			\
+	$(CHAMPLAIN_CFLAGS)				\
+	$(GTKHTML_CFLAGS)
 
 libevolution_addressbook_importers_la_SOURCES = \
 	evolution-ldif-importer.c		\
@@ -24,9 +25,10 @@ libevolution_addressbook_importers_la_LIBADD = \
 	$(top_builddir)/e-util/libeutil.la 				\
 	$(top_builddir)/shell/libeshell.la				\
 	$(top_builddir)/addressbook/util/libeabutil.la			\
-	$(top_builddir)/widgets/misc/libemiscwidgets.la			\
 	$(EVOLUTION_DATA_SERVER_LIBS)					\
 	$(GNOME_PLATFORM_LIBS)						\
+	$(CHAMPLAIN_LIBS)						\
+	$(GTKHTML_LIBS)							\
 	$(IMPORTERS_LIBS)
 
 -include $(top_srcdir)/git.mk
diff --git a/addressbook/importers/evolution-csv-importer.c b/addressbook/importers/evolution-csv-importer.c
index 7594ab1..b32f8e1 100644
--- a/addressbook/importers/evolution-csv-importer.c
+++ b/addressbook/importers/evolution-csv-importer.c
@@ -35,10 +35,8 @@
 #include <glib/gstdio.h>
 
 #include <libebook/libebook.h>
-#include <libedataserverui/libedataserverui.h>
 
 #include <shell/e-shell.h>
-#include <e-util/e-import.h>
 
 #include "evolution-addressbook-importers.h"
 
diff --git a/addressbook/importers/evolution-ldif-importer.c b/addressbook/importers/evolution-ldif-importer.c
index c95b2f4..db2bfbf 100644
--- a/addressbook/importers/evolution-ldif-importer.c
+++ b/addressbook/importers/evolution-ldif-importer.c
@@ -42,10 +42,8 @@
 #include <glib/gstdio.h>
 
 #include <libebook/libebook.h>
-#include <libedataserverui/libedataserverui.h>
 
 #include <shell/e-shell.h>
-#include <e-util/e-import.h>
 
 #include "evolution-addressbook-importers.h"
 
diff --git a/addressbook/importers/evolution-vcard-importer.c b/addressbook/importers/evolution-vcard-importer.c
index d430bd2..5129754 100644
--- a/addressbook/importers/evolution-vcard-importer.c
+++ b/addressbook/importers/evolution-vcard-importer.c
@@ -36,14 +36,10 @@
 #include <glib/gstdio.h>
 
 #include <libebook/libebook.h>
-#include <libedataserverui/libedataserverui.h>
 
 #include <util/eab-book-util.h>
 
 #include <shell/e-shell.h>
-#include <e-util/e-import.h>
-#include <e-util/e-datetime-format.h>
-#include <misc/e-web-view-preview.h>
 
 #include "evolution-addressbook-importers.h"
 
diff --git a/addressbook/printing/Makefile.am b/addressbook/printing/Makefile.am
index edf3cc9..fa92d19 100644
--- a/addressbook/printing/Makefile.am
+++ b/addressbook/printing/Makefile.am
@@ -12,7 +12,9 @@ libecontactprint_la_CPPFLAGS =			\
 	-DEVOLUTION_UIDIR=\""$(uidir)"\"	\
 	-DEVOLUTION_ECPSDIR=\""$(ecpsdir)"\"	\
 	$(EVOLUTION_DATA_SERVER_CFLAGS)		\
-	$(GNOME_PLATFORM_CFLAGS)
+	$(GNOME_PLATFORM_CFLAGS)		\
+	$(CHAMPLAIN_CFLAGS)			\
+	$(GTKHTML_CFLAGS)
 
 noinst_LTLIBRARIES = libecontactprint.la
 
@@ -22,10 +24,11 @@ libecontactprint_la_SOURCES = 			\
 	e-contact-print.h
 
 libecontactprint_la_LIBADD =					\
-	$(top_builddir)/widgets/misc/libemiscwidgets.la		\
 	$(top_builddir)/e-util/libeutil.la			\
 	$(EVOLUTION_DATA_SERVER_LIBS)				\
-	$(GNOME_PLATFORM_LIBS)
+	$(GNOME_PLATFORM_LIBS)					\
+	$(CHAMPLAIN_LIBS)					\
+	$(GTKHTML_LIBS)
 
 noinst_PROGRAMS = contact-print-test
 
diff --git a/addressbook/printing/e-contact-print.c b/addressbook/printing/e-contact-print.c
index 290863c..248fa97 100644
--- a/addressbook/printing/e-contact-print.c
+++ b/addressbook/printing/e-contact-print.c
@@ -33,7 +33,6 @@
 #include <libxml/xmlmemory.h>
 #include <glib/gi18n.h>
 
-#include "e-util/e-print.h"
 #include "e-util/e-util.h"
 #include "e-util/e-util-private.h"
 
diff --git a/addressbook/util/Makefile.am b/addressbook/util/Makefile.am
index c27b18c..10f3bb0 100644
--- a/addressbook/util/Makefile.am
+++ b/addressbook/util/Makefile.am
@@ -10,7 +10,6 @@ libeabutil_la_CPPFLAGS =				\
 	-I$(top_srcdir) 				\
 	-I$(top_builddir)/shell				\
 	-I$(top_srcdir)/shell				\
-	-I$(top_srcdir)/widgets				\
 	$(EVOLUTION_DATA_SERVER_CFLAGS)			\
 	$(GNOME_PLATFORM_CFLAGS)
 
@@ -21,7 +20,6 @@ libeabutil_la_SOURCES =					\
 libeabutil_la_LDFLAGS = -avoid-version $(NO_UNDEFINED)
 
 libeabutil_la_LIBADD =					\
-	$(top_builddir)/widgets/misc/libemiscwidgets.la \
 	$(top_builddir)/e-util/libeutil.la		\
 	$(top_builddir)/shell/libeshell.la		\
 	$(EVOLUTION_DATA_SERVER_LIBS)			\
diff --git a/calendar/alarm-notify/Makefile.am b/calendar/alarm-notify/Makefile.am
index 775a369..2ff5b3d 100644
--- a/calendar/alarm-notify/Makefile.am
+++ b/calendar/alarm-notify/Makefile.am
@@ -12,7 +12,6 @@ evolution_alarm_notify_CPPFLAGS = 			\
 	$(AM_CPPFLAGS)					\
 	-DG_LOG_DOMAIN=\"evolution-alarm-notify\"	\
 	-I$(top_srcdir)					\
-	-I$(top_srcdir)/widgets				\
 	-I$(top_srcdir)/calendar			\
 	-DEVOLUTION_UIDIR=\""$(uidir)"\"		\
 	-DEVOLUTION_ICONDIR=\""$(icondir)"\"		\
@@ -21,7 +20,9 @@ evolution_alarm_notify_CPPFLAGS = 			\
 	$(EVOLUTION_DATA_SERVER_CFLAGS)			\
 	$(GNOME_PLATFORM_CFLAGS)			\
 	$(LIBNOTIFY_CFLAGS)				\
-	$(CANBERRA_CFLAGS)
+	$(CANBERRA_CFLAGS)				\
+	$(CHAMPLAIN_CFLAGS)				\
+	$(GTKHTML_CFLAGS)
 
 ui_DATA =		\
 	alarm-notify.ui
@@ -44,17 +45,17 @@ evolution_alarm_notify_SOURCES =	\
 evolution_alarm_notify_LDADD =						\
 	$(top_builddir)/e-util/libeutil.la				\
 	$(top_builddir)/composer/libcomposer.la				\
-	$(top_builddir)/widgets/misc/libemiscwidgets.la			\
 	$(top_builddir)/calendar/gui/libevolution-calendar.la		\
 	$(top_builddir)/calendar/importers/libevolution-calendar-importers.la \
 	$(top_builddir)/addressbook/gui/contact-editor/libecontacteditor.la \
 	$(top_builddir)/addressbook/gui/contact-list-editor/libecontactlisteditor.la \
 	$(top_builddir)/addressbook/util/libeabutil.la			\
-	$(top_builddir)/libevolution-utils/libevolution-utils.la	\
 	$(EVOLUTION_DATA_SERVER_LIBS)					\
 	$(GNOME_PLATFORM_LIBS)						\
 	$(LIBNOTIFY_LIBS)						\
 	$(CANBERRA_LIBS)						\
+	$(CHAMPLAIN_LIBS)						\
+	$(GTKHTML_LIBS)
 	$(EVOLUTIONALARMNOTIFYICON)
 
 if OS_WIN32
diff --git a/calendar/alarm-notify/alarm-notify-dialog.c b/calendar/alarm-notify/alarm-notify-dialog.c
index 3bb705a..ac6ce49 100644
--- a/calendar/alarm-notify/alarm-notify-dialog.c
+++ b/calendar/alarm-notify/alarm-notify-dialog.c
@@ -30,14 +30,12 @@
 #include <string.h>
 #include <glib/gi18n.h>
 
+#include "e-util/e-util.h"
+
 #include "alarm-notify-dialog.h"
 #include "config-data.h"
 #include "util.h"
 
-#include "e-util/e-util.h"
-#include "e-util/e-util-private.h"
-#include "misc/e-buffer-tagger.h"
-
 enum {
 	ALARM_DISPLAY_COLUMN,
 	ALARM_SUMMARY_COLUMN,
diff --git a/calendar/alarm-notify/alarm-notify.c b/calendar/alarm-notify/alarm-notify.c
index 54242f2..ce7efee 100644
--- a/calendar/alarm-notify/alarm-notify.c
+++ b/calendar/alarm-notify/alarm-notify.c
@@ -27,6 +27,8 @@
 #include <string.h>
 #include <camel/camel.h>
 
+#include "e-util/e-util.h"
+
 #include "alarm.h"
 #include "alarm-notify.h"
 #include "alarm-queue.h"
diff --git a/calendar/alarm-notify/alarm-notify.h b/calendar/alarm-notify/alarm-notify.h
index 61097f3..45de689 100644
--- a/calendar/alarm-notify/alarm-notify.h
+++ b/calendar/alarm-notify/alarm-notify.h
@@ -28,7 +28,6 @@
 
 #include <gtk/gtk.h>
 #include <libecal/libecal.h>
-#include <libedataserverui/libedataserverui.h>
 
 /* Standard GObject macros */
 #define TYPE_ALARM_NOTIFY \
diff --git a/calendar/gui/Makefile.am b/calendar/gui/Makefile.am
index 7599180..f1c0727 100644
--- a/calendar/gui/Makefile.am
+++ b/calendar/gui/Makefile.am
@@ -58,8 +58,6 @@ libevolution_calendar_la_CPPFLAGS =			\
 	-I$(top_srcdir)/shell				\
 	-I$(top_srcdir)					\
 	-I$(top_srcdir)/calendar			\
-	-I$(top_srcdir)/widgets				\
-	-I$(top_srcdir)/widgets/misc			\
 	-DEVOLUTION_RULEDIR=\"$(ruledir)\"		\
 	-DEVOLUTION_UIDIR=\""$(uidir)"\"		\
 	-DEVOLUTION_ETSPECDIR=\""$(etspecdir)"\"	\
@@ -70,6 +68,7 @@ libevolution_calendar_la_CPPFLAGS =			\
 	-DPREFIX=\""$(prefix)"\"			\
 	$(EVOLUTION_DATA_SERVER_CFLAGS)			\
 	$(GNOME_PLATFORM_CFLAGS)			\
+	$(CHAMPLAIN_CFLAGS)				\
 	$(GTKHTML_CFLAGS)				\
 	$(LIBSOUP_CFLAGS)
 
@@ -205,18 +204,13 @@ libevolution_calendar_la_LIBADD =					\
 	$(top_builddir)/composer/libcomposer.la				\
 	$(top_builddir)/addressbook/gui/contact-editor/libecontacteditor.la \
 	$(top_builddir)/addressbook/gui/contact-list-editor/libecontactlisteditor.la \
-	$(top_builddir)/widgets/menus/libmenus.la			\
 	$(top_builddir)/shell/libeshell.la				\
 	$(top_builddir)/calendar/gui/dialogs/libcal-dialogs.la		\
 	$(top_builddir)/calendar/importers/libevolution-calendar-importers.la \
-	$(top_builddir)/libemail-utils/libemail-utils.la		\
-	$(top_builddir)/widgets/e-timezone-dialog/libetimezonedialog.la	\
-	$(top_builddir)/widgets/misc/libemiscwidgets.la			\
-	$(top_builddir)/widgets/table/libetable.la			\
-	$(top_builddir)/filter/libfilter.la				\
 	$(top_builddir)/e-util/libeutil.la				\
 	$(EVOLUTION_DATA_SERVER_LIBS)					\
 	$(GNOME_PLATFORM_LIBS)						\
+	$(CHAMPLAIN_LIBS)						\
 	$(GTKHTML_LIBS)							\
 	$(LIBSOUP_LIBS)
 
diff --git a/calendar/gui/calendar-config.c b/calendar/gui/calendar-config.c
index 1bc6273..15b6024 100644
--- a/calendar/gui/calendar-config.c
+++ b/calendar/gui/calendar-config.c
@@ -32,8 +32,7 @@
 #include <time.h>
 #include <string.h>
 #include <gio/gio.h>
-#include <e-util/e-util.h>
-#include <widgets/e-timezone-dialog/e-timezone-dialog.h>
+
 #include <shell/e-shell.h>
 
 #include "calendar-config-keys.h"
diff --git a/calendar/gui/calendar-config.h b/calendar/gui/calendar-config.h
index c005ec3..cd492c1 100644
--- a/calendar/gui/calendar-config.h
+++ b/calendar/gui/calendar-config.h
@@ -32,7 +32,7 @@
 #include <gdk/gdk.h>
 #include <libecal/libecal.h>
 
-#include <e-util/e-util-enums.h>
+#include <e-util/e-util.h>
 
 /* These are used to get/set the working days in the week. The bit-flags are
  * combined together. The bits must be from 0 (Sun) to 6 (Sat) to match the
diff --git a/calendar/gui/calendar-view-factory.h b/calendar/gui/calendar-view-factory.h
index bad3dcf..833c4bd 100644
--- a/calendar/gui/calendar-view-factory.h
+++ b/calendar/gui/calendar-view-factory.h
@@ -26,7 +26,6 @@
 #ifndef CALENDAR_VIEW_FACTORY_H
 #define CALENDAR_VIEW_FACTORY_H
 
-#include <menus/gal-view-factory.h>
 #include "gnome-cal.h"
 
 G_BEGIN_DECLS
diff --git a/calendar/gui/calendar-view.h b/calendar/gui/calendar-view.h
index f3a2a31..d76f3f5 100644
--- a/calendar/gui/calendar-view.h
+++ b/calendar/gui/calendar-view.h
@@ -26,7 +26,6 @@
 #ifndef CALENDAR_VIEW_H
 #define CALENDAR_VIEW_H
 
-#include <menus/gal-view.h>
 #include "gnome-cal.h"
 
 G_BEGIN_DECLS
diff --git a/calendar/gui/comp-util.c b/calendar/gui/comp-util.c
index c0935ca..c0e6dbb 100644
--- a/calendar/gui/comp-util.c
+++ b/calendar/gui/comp-util.c
@@ -28,12 +28,10 @@
 
 #include <string.h>
 #include <time.h>
-#include <libedataserverui/libedataserverui.h>
 
 #include "calendar-config.h"
 #include "comp-util.h"
 #include "dialogs/delete-comp.h"
-#include "e-util/e-categories-config.h"
 
 #include "gnome-cal.h"
 #include "shell/e-shell-window.h"
diff --git a/calendar/gui/comp-util.h b/calendar/gui/comp-util.h
index ff7a135..1c91a69 100644
--- a/calendar/gui/comp-util.h
+++ b/calendar/gui/comp-util.h
@@ -28,7 +28,7 @@
 #include <gtk/gtk.h>
 #include <libecal/libecal.h>
 
-#include <e-util/e-util-enums.h>
+#include <e-util/e-util.h>
 
 struct _EShell;
 
diff --git a/calendar/gui/dialogs/Makefile.am b/calendar/gui/dialogs/Makefile.am
index 02af20b..fa6ba86 100644
--- a/calendar/gui/dialogs/Makefile.am
+++ b/calendar/gui/dialogs/Makefile.am
@@ -4,18 +4,18 @@ libcal_dialogs_la_CPPFLAGS = 						\
 	$(AM_CPPFLAGS)							\
 	-DG_LOG_DOMAIN=\"calendar-gui\"					\
 	-I$(top_srcdir)							\
-	-I$(top_srcdir)/widgets						\
 	-I$(top_builddir)						\
 	-I$(top_srcdir)/calendar					\
 	-I$(top_builddir)/shell						\
 	-I$(top_srcdir)/shell						\
-	-I$(top_srcdir)/widgets/misc					\
 	-DEVOLUTION_UIDIR=\""$(uidir)"\"				\
 	-DEVOLUTION_ICONDIR=\""$(icondir)"\"				\
 	-DEVOLUTION_IMAGESDIR=\""$(imagesdir)"\"			\
 	-DPREFIX=\""$(prefix)"\"					\
 	$(EVOLUTION_DATA_SERVER_CFLAGS)					\
-	$(GNOME_PLATFORM_CFLAGS)
+	$(GNOME_PLATFORM_CFLAGS)					\
+	$(CHAMPLAIN_CFLAGS)						\
+	$(GTKHTML_CFLAGS)
 
 ecalendarincludedir = $(privincludedir)/calendar/gui/dialogs
 
@@ -50,10 +50,11 @@ ecalendarinclude_HEADERS =		\
 libcal_dialogs_la_LIBADD =					\
 	$(top_builddir)/addressbook/util/libeabutil.la		\
 	$(EVOLUTION_DATA_SERVER_LIBS)				\
-	$(GNOME_PLATFORM_LIBS)
+	$(GNOME_PLATFORM_LIBS)					\
+	$(CHAMPLAIN_LIBS)					\
+	$(GTKHTML_LIBS)
 
 libcal_dialogs_la_SOURCES =		\
-	$(IDL_GENERATED)		\
 	alarm-dialog.c			\
 	alarm-dialog.h			\
 	alarm-list-dialog.c		\
diff --git a/calendar/gui/dialogs/alarm-dialog.c b/calendar/gui/dialogs/alarm-dialog.c
index dda1d85..d88fd12 100644
--- a/calendar/gui/dialogs/alarm-dialog.c
+++ b/calendar/gui/dialogs/alarm-dialog.c
@@ -32,12 +32,11 @@
 #include <gtk/gtk.h>
 #include <glib/gi18n.h>
 #include <libebook/libebook.h>
-#include <libedataserverui/libedataserverui.h>
 
-#include "e-util/e-util.h"
-#include "e-util/e-dialog-widgets.h"
-#include "e-util/e-util-private.h"
 #include <libical/icalattach.h>
+
+#include "e-util/e-util.h"
+
 #include "../calendar-config.h"
 #include "comp-editor-util.h"
 #include "alarm-dialog.h"
diff --git a/calendar/gui/dialogs/cancel-comp.c b/calendar/gui/dialogs/cancel-comp.c
index 5dbdb0e..29038f4 100644
--- a/calendar/gui/dialogs/cancel-comp.c
+++ b/calendar/gui/dialogs/cancel-comp.c
@@ -26,10 +26,12 @@
 #include <config.h>
 #endif
 
+#include "cancel-comp.h"
+
 #include <gtk/gtk.h>
 #include <glib/gi18n.h>
-#include "libevolution-utils/e-alert-dialog.h"
-#include "cancel-comp.h"
+
+#include "e-util/e-util.h"
 
 /* is_past_event:
  *
diff --git a/calendar/gui/dialogs/cancel-comp.h b/calendar/gui/dialogs/cancel-comp.h
index bfc1c6c..5e6ea88 100644
--- a/calendar/gui/dialogs/cancel-comp.h
+++ b/calendar/gui/dialogs/cancel-comp.h
@@ -26,6 +26,7 @@
 #ifndef CANCEL_COMP_H
 #define CANCEL_COMP_H
 
+#include <gtk/gtk.h>
 #include <libecal/libecal.h>
 
 gboolean cancel_component_dialog (GtkWindow *parent, ECalClient *cal_client, ECalComponent *comp, gboolean deleting);
diff --git a/calendar/gui/dialogs/comp-editor-util.c b/calendar/gui/dialogs/comp-editor-util.c
index 5938f52..5265119 100644
--- a/calendar/gui/dialogs/comp-editor-util.c
+++ b/calendar/gui/dialogs/comp-editor-util.c
@@ -31,9 +31,9 @@
 #include <libical/ical.h>
 #include <glib/gi18n.h>
 
-#include "widgets/misc/e-dateedit.h"
+#include "shell/e-shell.h"
+
 #include "../itip-utils.h"
-#include <shell/e-shell.h>
 #include "comp-editor-util.h"
 
 /**
diff --git a/calendar/gui/dialogs/comp-editor-util.h b/calendar/gui/dialogs/comp-editor-util.h
index a4ecc46..8c80683 100644
--- a/calendar/gui/dialogs/comp-editor-util.h
+++ b/calendar/gui/dialogs/comp-editor-util.h
@@ -28,7 +28,6 @@
 #include <gtk/gtk.h>
 #include "comp-editor.h"
 #include "../e-meeting-attendee.h"
-#include <misc/e-dateedit.h>
 
 void comp_editor_dates (CompEditorPageDates *date, ECalComponent *comp);
 void comp_editor_free_dates (CompEditorPageDates *dates);
diff --git a/calendar/gui/dialogs/comp-editor.c b/calendar/gui/dialogs/comp-editor.c
index 4eaf1bc..5fa87e8 100644
--- a/calendar/gui/dialogs/comp-editor.c
+++ b/calendar/gui/dialogs/comp-editor.c
@@ -38,10 +38,6 @@
 #include <gdk/gdkkeysyms.h>
 #include <libebackend/libebackend.h>
 
-#include <e-util/e-util.h>
-#include <libevolution-utils/e-alert-sink.h>
-#include <e-util/e-dialog-utils.h>
-#include <e-util/e-util-private.h>
 #include <shell/e-shell.h>
 
 #include "../print.h"
@@ -55,11 +51,6 @@
 #include "comp-editor.h"
 #include "comp-editor-util.h"
 #include "../calendar-config-keys.h"
-#include "widgets/misc/e-attachment-view.h"
-#include "widgets/misc/e-attachment-paned.h"
-
-#include "libevolution-utils/e-alert-dialog.h"
-#include "e-util/e-ui-manager.h"
 
 #define COMP_EDITOR_GET_PRIVATE(obj) \
 	(G_TYPE_INSTANCE_GET_PRIVATE \
diff --git a/calendar/gui/dialogs/comp-editor.h b/calendar/gui/dialogs/comp-editor.h
index 14fbc0f..72598ac 100644
--- a/calendar/gui/dialogs/comp-editor.h
+++ b/calendar/gui/dialogs/comp-editor.h
@@ -28,10 +28,10 @@
 #include <gtk/gtk.h>
 #include <libecal/libecal.h>
 
+#include <shell/e-shell.h>
+
 #include "../itip-utils.h"
 #include "comp-editor-page.h"
-#include <shell/e-shell.h>
-#include <misc/e-focus-tracker.h>
 
 /* Standard GObject macros */
 #define TYPE_COMP_EDITOR \
diff --git a/calendar/gui/dialogs/copy-source-dialog.c b/calendar/gui/dialogs/copy-source-dialog.c
index 5a7889e..f8b668b 100644
--- a/calendar/gui/dialogs/copy-source-dialog.c
+++ b/calendar/gui/dialogs/copy-source-dialog.c
@@ -27,7 +27,8 @@
 #endif
 
 #include <glib/gi18n.h>
-#include <libedataserverui/libedataserverui.h>
+
+#include "e-util/e-util.h"
 
 #include "copy-source-dialog.h"
 #include "select-source-dialog.h"
diff --git a/calendar/gui/dialogs/delete-comp.c b/calendar/gui/dialogs/delete-comp.c
index 93e2df0..0ec054c 100644
--- a/calendar/gui/dialogs/delete-comp.c
+++ b/calendar/gui/dialogs/delete-comp.c
@@ -26,10 +26,12 @@
 #include <config.h>
 #endif
 
-#include <glib/gi18n.h>
-#include "libevolution-utils/e-alert-dialog.h"
 #include "delete-comp.h"
 
+#include <glib/gi18n.h>
+
+#include "e-util/e-util.h"
+
 /**
  * delete_component_dialog:
  * @comp: A calendar component if a single component is to be deleted, or NULL
diff --git a/calendar/gui/dialogs/e-delegate-dialog.c b/calendar/gui/dialogs/e-delegate-dialog.c
index 9ae253d..9c5e17f 100644
--- a/calendar/gui/dialogs/e-delegate-dialog.c
+++ b/calendar/gui/dialogs/e-delegate-dialog.c
@@ -29,7 +29,6 @@
 #include <gtk/gtk.h>
 #include <libical/ical.h>
 #include <libebook/libebook.h>
-#include <libedataserverui/libedataserverui.h>
 
 #include "e-util/e-util.h"
 #include "e-util/e-util-private.h"
diff --git a/calendar/gui/dialogs/e-send-options-utils.h b/calendar/gui/dialogs/e-send-options-utils.h
index f5c3f0a..6d365b3 100644
--- a/calendar/gui/dialogs/e-send-options-utils.h
+++ b/calendar/gui/dialogs/e-send-options-utils.h
@@ -27,7 +27,7 @@
 
 #include <libecal/libecal.h>
 
-#include "misc/e-send-options.h"
+#include <e-util/e-util.h>
 
 void		e_send_options_utils_set_default_data
 						(ESendOptionsDialog *sod,
diff --git a/calendar/gui/dialogs/event-editor.c b/calendar/gui/dialogs/event-editor.c
index 0681aa4..2ea6297 100644
--- a/calendar/gui/dialogs/event-editor.c
+++ b/calendar/gui/dialogs/event-editor.c
@@ -32,11 +32,6 @@
 #include <string.h>
 #include <glib/gi18n.h>
 
-#include <misc/e-dateedit.h>
-#include <e-util/e-plugin-ui.h>
-#include <e-util/e-util-private.h>
-#include <e-util/e-ui-manager.h>
-
 #include "event-page.h"
 #include "recurrence-page.h"
 #include "schedule-page.h"
diff --git a/calendar/gui/dialogs/event-page.c b/calendar/gui/dialogs/event-page.c
index 12931ce..793e98e 100644
--- a/calendar/gui/dialogs/event-page.c
+++ b/calendar/gui/dialogs/event-page.c
@@ -35,19 +35,6 @@
 #include <glib/gi18n.h>
 #include <gdk/gdkkeysyms.h>
 
-#include <libedataserverui/libedataserverui.h>
-
-#include <e-util/e-util.h>
-#include <e-util/e-categories-config.h>
-#include <e-util/e-dialog-utils.h>
-#include <e-util/e-dialog-widgets.h>
-#include <e-util/e-util-private.h>
-
-#include <misc/e-dateedit.h>
-#include <misc/e-send-options.h>
-#include <misc/e-spell-entry.h>
-#include <misc/e-buffer-tagger.h>
-
 #include "../e-alarm-list.h"
 #include "../e-meeting-attendee.h"
 #include "../e-meeting-list-view.h"
diff --git a/calendar/gui/dialogs/memo-editor.c b/calendar/gui/dialogs/memo-editor.c
index ae0d351..839bce7 100644
--- a/calendar/gui/dialogs/memo-editor.c
+++ b/calendar/gui/dialogs/memo-editor.c
@@ -31,9 +31,6 @@
 #include <string.h>
 #include <glib/gi18n.h>
 
-#include <e-util/e-plugin-ui.h>
-#include <e-util/e-util-private.h>
-
 #include "memo-page.h"
 #include "cancel-comp.h"
 #include "memo-editor.h"
diff --git a/calendar/gui/dialogs/memo-page.c b/calendar/gui/dialogs/memo-page.c
index ec99587..79b659a 100644
--- a/calendar/gui/dialogs/memo-page.c
+++ b/calendar/gui/dialogs/memo-page.c
@@ -34,17 +34,6 @@
 #include <gtk/gtk.h>
 #include <glib/gi18n.h>
 
-#include <libedataserverui/libedataserverui.h>
-
-#include <e-util/e-util.h>
-#include <e-util/e-categories-config.h>
-#include <e-util/e-dialog-utils.h>
-#include <e-util/e-util-private.h>
-
-#include <misc/e-dateedit.h>
-#include <misc/e-spell-entry.h>
-#include <misc/e-buffer-tagger.h>
-
 #include "../calendar-config.h"
 #include "comp-editor.h"
 #include "comp-editor-util.h"
diff --git a/calendar/gui/dialogs/recurrence-page.c b/calendar/gui/dialogs/recurrence-page.c
index 77862c6..d5a93ea 100644
--- a/calendar/gui/dialogs/recurrence-page.c
+++ b/calendar/gui/dialogs/recurrence-page.c
@@ -33,17 +33,12 @@
 #include <gtk/gtk.h>
 #include <glib/gi18n.h>
 
-#include <misc/e-dateedit.h>
 #include "../tag-calendar.h"
 #include "../weekday-picker.h"
 #include "comp-editor-util.h"
 #include "../e-date-time-list.h"
 #include "recurrence-page.h"
 
-#include "e-util/e-util.h"
-#include "e-util/e-dialog-widgets.h"
-#include "e-util/e-util-private.h"
-
 #define RECURRENCE_PAGE_GET_PRIVATE(obj) \
 	(G_TYPE_INSTANCE_GET_PRIVATE \
 	((obj), TYPE_RECURRENCE_PAGE, RecurrencePagePrivate))
diff --git a/calendar/gui/dialogs/save-comp.c b/calendar/gui/dialogs/save-comp.c
index 2fd53bd..3ae2625 100644
--- a/calendar/gui/dialogs/save-comp.c
+++ b/calendar/gui/dialogs/save-comp.c
@@ -25,7 +25,6 @@
 #include <config.h>
 #endif
 
-#include "libevolution-utils/e-alert-dialog.h"
 #include "save-comp.h"
 #include "comp-editor.h"
 
diff --git a/calendar/gui/dialogs/schedule-page.c b/calendar/gui/dialogs/schedule-page.c
index 516a973..bd3e05e 100644
--- a/calendar/gui/dialogs/schedule-page.c
+++ b/calendar/gui/dialogs/schedule-page.c
@@ -31,8 +31,7 @@
 
 #include <gtk/gtk.h>
 #include <glib/gi18n.h>
-#include <e-util/e-util-private.h>
-#include <misc/e-dateedit.h>
+
 #include "../e-meeting-time-sel.h"
 #include "../itip-utils.h"
 #include "comp-editor-util.h"
diff --git a/calendar/gui/dialogs/schedule-page.h b/calendar/gui/dialogs/schedule-page.h
index 1c55436..9d38de4 100644
--- a/calendar/gui/dialogs/schedule-page.h
+++ b/calendar/gui/dialogs/schedule-page.h
@@ -28,7 +28,6 @@
 #include "../e-meeting-store.h"
 #include "comp-editor.h"
 #include "comp-editor-page.h"
-#include <libedataserverui/libedataserverui.h>
 
 /* Standard GObject macros */
 #define TYPE_SCHEDULE_PAGE \
diff --git a/calendar/gui/dialogs/select-source-dialog.c b/calendar/gui/dialogs/select-source-dialog.c
index 9f80038..5bdf4a7 100644
--- a/calendar/gui/dialogs/select-source-dialog.c
+++ b/calendar/gui/dialogs/select-source-dialog.c
@@ -26,7 +26,8 @@
 #endif
 
 #include <glib/gi18n.h>
-#include <libedataserverui/libedataserverui.h>
+
+#include "e-util/e-util.h"
 
 #include "select-source-dialog.h"
 
diff --git a/calendar/gui/dialogs/send-comp.c b/calendar/gui/dialogs/send-comp.c
index 73938be..b69e6ea 100644
--- a/calendar/gui/dialogs/send-comp.c
+++ b/calendar/gui/dialogs/send-comp.c
@@ -26,10 +26,12 @@
 #include <config.h>
 #endif
 
-#include <glib/gi18n-lib.h>
-#include "libevolution-utils/e-alert-dialog.h"
 #include "send-comp.h"
 
+#include <glib/gi18n-lib.h>
+
+#include "e-util/e-util.h"
+
 static gboolean
 component_has_new_attendees (ECalComponent *comp)
 {
diff --git a/calendar/gui/dialogs/task-details-page.c b/calendar/gui/dialogs/task-details-page.c
index 2ada645..e40db5b 100644
--- a/calendar/gui/dialogs/task-details-page.c
+++ b/calendar/gui/dialogs/task-details-page.c
@@ -31,16 +31,11 @@
 
 #include <gtk/gtk.h>
 #include <glib/gi18n.h>
-#include <misc/e-dateedit.h>
-#include <misc/e-url-entry.h>
+
 #include "../e-timezone-entry.h"
 #include "comp-editor-util.h"
 #include "task-details-page.h"
 
-#include "e-util/e-util.h"
-#include "e-util/e-dialog-widgets.h"
-#include "e-util/e-util-private.h"
-
 #define TASK_DETAILS_PAGE_GET_PRIVATE(obj) \
 	(G_TYPE_INSTANCE_GET_PRIVATE \
 	((obj), TYPE_TASK_DETAILS_PAGE, TaskDetailsPagePrivate))
diff --git a/calendar/gui/dialogs/task-editor.c b/calendar/gui/dialogs/task-editor.c
index 0c81d3e..07ad568 100644
--- a/calendar/gui/dialogs/task-editor.c
+++ b/calendar/gui/dialogs/task-editor.c
@@ -32,9 +32,6 @@
 #include <string.h>
 #include <glib/gi18n.h>
 
-#include "e-util/e-plugin-ui.h"
-#include "e-util/e-util-private.h"
-
 #include "task-page.h"
 #include "task-details-page.h"
 #include "cancel-comp.h"
diff --git a/calendar/gui/dialogs/task-page.c b/calendar/gui/dialogs/task-page.c
index ebf279c..d8d64f3 100644
--- a/calendar/gui/dialogs/task-page.c
+++ b/calendar/gui/dialogs/task-page.c
@@ -34,17 +34,6 @@
 #include <glib/gi18n.h>
 #include <gdk/gdkkeysyms.h>
 
-#include <libedataserverui/libedataserverui.h>
-
-#include <misc/e-dateedit.h>
-#include <misc/e-spell-entry.h>
-#include <misc/e-buffer-tagger.h>
-
-#include <e-util/e-categories-config.h>
-#include <e-util/e-dialog-utils.h>
-#include <e-util/e-util-private.h>
-#include <e-util/e-util.h>
-
 #include "../e-meeting-attendee.h"
 #include "../e-meeting-list-view.h"
 #include "../e-meeting-store.h"
diff --git a/calendar/gui/e-cal-component-preview.c b/calendar/gui/e-cal-component-preview.c
index fe9870e..0647f9b 100644
--- a/calendar/gui/e-cal-component-preview.c
+++ b/calendar/gui/e-cal-component-preview.c
@@ -33,9 +33,6 @@
 #include <glib/gi18n.h>
 #include <camel/camel.h>
 
-#include <e-util/e-util.h>
-#include <e-util/e-categories-config.h>
-
 #define E_CAL_COMPONENT_PREVIEW_GET_PRIVATE(obj) \
 	(G_TYPE_INSTANCE_GET_PRIVATE \
 	((obj), E_TYPE_CAL_COMPONENT_PREVIEW, ECalComponentPreviewPrivate))
diff --git a/calendar/gui/e-cal-component-preview.h b/calendar/gui/e-cal-component-preview.h
index 5048e3a..5490913 100644
--- a/calendar/gui/e-cal-component-preview.h
+++ b/calendar/gui/e-cal-component-preview.h
@@ -26,7 +26,8 @@
 
 #include <gtk/gtk.h>
 #include <libecal/libecal.h>
-#include <misc/e-web-view.h>
+
+#include <e-util/e-util.h>
 
 /* Standard GObject macros */
 #define E_TYPE_CAL_COMPONENT_PREVIEW \
diff --git a/calendar/gui/e-cal-config.h b/calendar/gui/e-cal-config.h
index 5cadd42..e6db09a 100644
--- a/calendar/gui/e-cal-config.h
+++ b/calendar/gui/e-cal-config.h
@@ -25,8 +25,7 @@
 #define E_CAL_CONFIG_H
 
 #include <libecal/libecal.h>
-
-#include "e-util/e-config.h"
+#include <e-util/e-util.h>
 
 /* Standard GObject macros */
 #define E_TYPE_CAL_CONFIG \
diff --git a/calendar/gui/e-cal-event.h b/calendar/gui/e-cal-event.h
index 447f1fd..f44878d 100644
--- a/calendar/gui/e-cal-event.h
+++ b/calendar/gui/e-cal-event.h
@@ -24,8 +24,7 @@
 #ifndef __E_CAL_EVENT_H__
 #define __E_CAL_EVENT_H__
 
-#include "e-util/e-event.h"
-#include "shell/e-shell-backend.h"
+#include <shell/e-shell-backend.h>
 
 G_BEGIN_DECLS
 
diff --git a/calendar/gui/e-cal-list-view.c b/calendar/gui/e-cal-list-view.c
index 55280ce..94722c0 100644
--- a/calendar/gui/e-cal-list-view.c
+++ b/calendar/gui/e-cal-list-view.c
@@ -34,16 +34,6 @@
 #include <glib/gi18n.h>
 #include <glib/gstdio.h>
 #include <gdk/gdkkeysyms.h>
-#include <table/e-table-memory-store.h>
-#include <table/e-cell-checkbox.h>
-#include <table/e-cell-toggle.h>
-#include <table/e-cell-text.h>
-#include <table/e-cell-combo.h>
-#include <table/e-cell-date.h>
-#include <table/e-cell-date-edit.h>
-#include <e-util/e-categories-config.h>
-#include <e-util/e-dialog-utils.h>
-#include <e-util/e-util-private.h>
 
 #include "e-cal-model-calendar.h"
 #include "e-cell-date-edit-text.h"
diff --git a/calendar/gui/e-cal-list-view.h b/calendar/gui/e-cal-list-view.h
index ed4636f..e5eff47 100644
--- a/calendar/gui/e-cal-list-view.h
+++ b/calendar/gui/e-cal-list-view.h
@@ -27,8 +27,7 @@
 #include <time.h>
 #include <gtk/gtk.h>
 
-#include <table/e-table.h>
-#include <table/e-cell-date-edit.h>
+#include <e-util/e-util.h>
 
 #include "e-calendar-view.h"
 #include "gnome-cal.h"
diff --git a/calendar/gui/e-cal-model.h b/calendar/gui/e-cal-model.h
index 7f19e00..9164d9c 100644
--- a/calendar/gui/e-cal-model.h
+++ b/calendar/gui/e-cal-model.h
@@ -28,8 +28,8 @@
 
 #include <libecal/libecal.h>
 
-#include <e-util/e-util-enums.h>
-#include <table/e-table-model.h>
+#include <e-util/e-util.h>
+
 #include "e-cell-date-edit-text.h"
 
 /* Standard GObject macros */
diff --git a/calendar/gui/e-calendar-selector.c b/calendar/gui/e-calendar-selector.c
index 6c403e4..19eb433 100644
--- a/calendar/gui/e-calendar-selector.c
+++ b/calendar/gui/e-calendar-selector.c
@@ -24,8 +24,6 @@
 
 #include <libecal/libecal.h>
 
-#include "e-util/e-selection.h"
-
 #define E_CALENDAR_SELECTOR_GET_PRIVATE(obj) \
 	(G_TYPE_INSTANCE_GET_PRIVATE \
 	((obj), E_TYPE_CALENDAR_SELECTOR, ECalendarSelectorPrivate))
diff --git a/calendar/gui/e-calendar-selector.h b/calendar/gui/e-calendar-selector.h
index 1e7128b..50832ea 100644
--- a/calendar/gui/e-calendar-selector.h
+++ b/calendar/gui/e-calendar-selector.h
@@ -21,7 +21,7 @@
 #ifndef E_CALENDAR_SELECTOR_H
 #define E_CALENDAR_SELECTOR_H
 
-#include <libedataserverui/libedataserverui.h>
+#include <e-util/e-util.h>
 
 /* Standard GObject macros */
 #define E_TYPE_CALENDAR_SELECTOR \
diff --git a/calendar/gui/e-calendar-view.c b/calendar/gui/e-calendar-view.c
index 2801391..d5ce9f3 100644
--- a/calendar/gui/e-calendar-view.c
+++ b/calendar/gui/e-calendar-view.c
@@ -31,15 +31,7 @@
 #include <glib/gstdio.h>
 #include <gdk/gdkkeysyms.h>
 #include <libebackend/libebackend.h>
-#include <libedataserverui/libedataserverui.h>
-
-#include <e-util/e-util.h>
-#include <libevolution-utils/e-alert-dialog.h>
-#include <e-util/e-selection.h>
-#include <e-util/e-datetime-format.h>
-#include <e-util/e-dialog-utils.h>
-#include <e-util/e-icon-factory.h>
-#include <misc/e-selectable.h>
+
 #include <shell/e-shell.h>
 
 #include "comp-util.h"
diff --git a/calendar/gui/e-cell-date-edit-text.c b/calendar/gui/e-cell-date-edit-text.c
index 82d94ef..37e648e 100644
--- a/calendar/gui/e-cell-date-edit-text.c
+++ b/calendar/gui/e-cell-date-edit-text.c
@@ -32,9 +32,6 @@
 #include <glib/gi18n.h>
 #include <libecal/libecal.h>
 
-#include <e-util/e-util.h>
-#include <e-util/e-datetime-format.h>
-
 #include "e-cell-date-edit-text.h"
 
 #define E_CELL_DATE_EDIT_TEXT_GET_PRIVATE(obj) \
diff --git a/calendar/gui/e-cell-date-edit-text.h b/calendar/gui/e-cell-date-edit-text.h
index aab7e27..b620143 100644
--- a/calendar/gui/e-cell-date-edit-text.h
+++ b/calendar/gui/e-cell-date-edit-text.h
@@ -25,7 +25,7 @@
 #define _E_CELL_DATE_EDIT_TEXT_H_
 
 #include <libical/ical.h>
-#include <table/e-cell-text.h>
+#include <e-util/e-util.h>
 
 /* Standard GObject macros */
 #define E_TYPE_CELL_DATE_EDIT_TEXT \
diff --git a/calendar/gui/e-day-view-layout.c b/calendar/gui/e-day-view-layout.c
index 573a3b3..c4bf94a 100644
--- a/calendar/gui/e-day-view-layout.c
+++ b/calendar/gui/e-day-view-layout.c
@@ -31,7 +31,6 @@
 #endif
 
 #include "e-day-view-layout.h"
-#include "e-util/e-bit-array.h"
 
 static void e_day_view_layout_long_event (EDayViewEvent	  *event,
 					  guint8	  *grid,
diff --git a/calendar/gui/e-day-view-main-item.c b/calendar/gui/e-day-view-main-item.c
index 61e238e..1492cde 100644
--- a/calendar/gui/e-day-view-main-item.c
+++ b/calendar/gui/e-day-view-main-item.c
@@ -29,15 +29,13 @@
 #include <config.h>
 #endif
 
-#include <e-calendar-view.h>
+#include "e-day-view-main-item.h"
 
-#include "e-util/e-categories-config.h"
-#include "e-util/e-util.h"
+#include "comp-util.h"
+#include "e-calendar-view.h"
+#include "e-calendar-view.h"
 #include "e-day-view-layout.h"
-#include "e-day-view-main-item.h"
 #include "ea-calendar.h"
-#include "e-calendar-view.h"
-#include "comp-util.h"
 
 #define E_DAY_VIEW_MAIN_ITEM_GET_PRIVATE(obj) \
 	(G_TYPE_INSTANCE_GET_PRIVATE \
diff --git a/calendar/gui/e-day-view-time-item.c b/calendar/gui/e-day-view-time-item.c
index f337aa0..c6d36d3 100644
--- a/calendar/gui/e-day-view-time-item.c
+++ b/calendar/gui/e-day-view-time-item.c
@@ -31,7 +31,6 @@
 
 #include "e-day-view-time-item.h"
 #include "calendar-config.h"
-#include <widgets/e-timezone-dialog/e-timezone-dialog.h>
 
 /* The spacing between items in the time column. GRID_X_PAD is the space down
  * either side of the column, i.e. outside the main horizontal grid lines.
diff --git a/calendar/gui/e-day-view-top-item.c b/calendar/gui/e-day-view-top-item.c
index 8d961ea..ed5d8b0 100644
--- a/calendar/gui/e-day-view-top-item.c
+++ b/calendar/gui/e-day-view-top-item.c
@@ -29,7 +29,6 @@
 #endif
 
 #include <glib/gi18n.h>
-#include "e-util/e-categories-config.h"
 
 #include "e-calendar-view.h"
 #include "e-day-view-top-item.h"
diff --git a/calendar/gui/e-day-view.c b/calendar/gui/e-day-view.c
index db4557c..30eed74 100644
--- a/calendar/gui/e-day-view.c
+++ b/calendar/gui/e-day-view.c
@@ -27,19 +27,14 @@
 #endif
 
 #include "e-day-view.h"
-#include "ea-calendar.h"
 
 #include <math.h>
 #include <time.h>
-#include <gdk/gdkkeysyms.h>
-#include <text/e-text.h>
-#include <misc/e-canvas-utils.h>
-#include <e-util/e-unicode.h>
-#include <libgnomecanvas/libgnomecanvas.h>
+
 #include <glib/gi18n.h>
-#include <e-util/e-categories-config.h>
-#include <e-util/e-dialog-utils.h>
-#include <e-util/e-selection.h>
+#include <gdk/gdkkeysyms.h>
+
+#include "libgnomecanvas/libgnomecanvas.h"
 
 #include "dialogs/delete-comp.h"
 #include "dialogs/delete-error.h"
@@ -47,17 +42,18 @@
 #include "dialogs/cancel-comp.h"
 #include "dialogs/recur-comp.h"
 #include "dialogs/goto-dialog.h"
-#include "print.h"
+
 #include "calendar-config.h"
 #include "comp-util.h"
-#include "itip-utils.h"
 #include "e-cal-model-calendar.h"
-#include "e-day-view-time-item.h"
-#include "e-day-view-top-item.h"
 #include "e-day-view-layout.h"
 #include "e-day-view-main-item.h"
+#include "e-day-view-time-item.h"
+#include "e-day-view-top-item.h"
+#include "ea-calendar.h"
+#include "itip-utils.h"
 #include "misc.h"
-#include <e-util/e-icon-factory.h>
+#include "print.h"
 
 /* The minimum amount of space wanted on each side of the date string. */
 #define E_DAY_VIEW_DATE_X_PAD	4
diff --git a/calendar/gui/e-meeting-list-view.c b/calendar/gui/e-meeting-list-view.c
index 59ffa09..50999c7 100644
--- a/calendar/gui/e-meeting-list-view.c
+++ b/calendar/gui/e-meeting-list-view.c
@@ -30,7 +30,6 @@
 #include <glib/gi18n.h>
 #include <libecal/libecal.h>
 #include <libebook/libebook.h>
-#include <libedataserverui/libedataserverui.h>
 
 #include "calendar-config.h"
 #include "e-meeting-list-view.h"
diff --git a/calendar/gui/e-meeting-list-view.h b/calendar/gui/e-meeting-list-view.h
index d62cb05..aff278c 100644
--- a/calendar/gui/e-meeting-list-view.h
+++ b/calendar/gui/e-meeting-list-view.h
@@ -25,7 +25,6 @@
 #define _E_MEETING_LIST_VIEW_H_
 
 #include <gtk/gtk.h>
-#include <libedataserverui/libedataserverui.h>
 
 #include "e-meeting-store.h"
 
diff --git a/calendar/gui/e-meeting-store.c b/calendar/gui/e-meeting-store.c
index b8e065e..4e8e4e3 100644
--- a/calendar/gui/e-meeting-store.c
+++ b/calendar/gui/e-meeting-store.c
@@ -31,7 +31,6 @@
 
 #include <libecal/libecal.h>
 #include <libebackend/libebackend.h>
-#include <libedataserverui/libedataserverui.h>
 
 #include <shell/e-shell.h>
 #include <e-util/e-util-enumtypes.h>
diff --git a/calendar/gui/e-meeting-store.h b/calendar/gui/e-meeting-store.h
index f762cbc..27c1f9a 100644
--- a/calendar/gui/e-meeting-store.h
+++ b/calendar/gui/e-meeting-store.h
@@ -26,7 +26,8 @@
 #include <gtk/gtk.h>
 #include <libecal/libecal.h>
 
-#include <e-util/e-util-enums.h>
+#include <e-util/e-util.h>
+
 #include "e-meeting-attendee.h"
 
 /* Standard GObject macros */
diff --git a/calendar/gui/e-meeting-time-sel-item.c b/calendar/gui/e-meeting-time-sel-item.c
index a15f25a..aaa8623 100644
--- a/calendar/gui/e-meeting-time-sel-item.c
+++ b/calendar/gui/e-meeting-time-sel-item.c
@@ -30,8 +30,6 @@
 #include <time.h>
 #include <glib/gi18n.h>
 
-#include "e-util/e-datetime-format.h"
-
 #include "calendar-config.h"
 #include "e-meeting-time-sel-item.h"
 #include "e-meeting-time-sel.h"
diff --git a/calendar/gui/e-meeting-time-sel.c b/calendar/gui/e-meeting-time-sel.c
index f3f8b3a..1894406 100644
--- a/calendar/gui/e-meeting-time-sel.c
+++ b/calendar/gui/e-meeting-time-sel.c
@@ -36,13 +36,6 @@
 #include <libebackend/libebackend.h>
 #include <libgnomecanvas/libgnomecanvas.h>
 
-#include "misc/e-canvas.h"
-#include "misc/e-canvas-utils.h"
-#include "misc/e-dateedit.h"
-
-#include "e-util/e-util.h"
-#include "e-util/e-datetime-format.h"
-
 #include "e-meeting-utils.h"
 #include "e-meeting-list-view.h"
 #include "e-meeting-time-sel-item.h"
diff --git a/calendar/gui/e-meeting-time-sel.h b/calendar/gui/e-meeting-time-sel.h
index 7c3f7c3..9f4fb0a 100644
--- a/calendar/gui/e-meeting-time-sel.h
+++ b/calendar/gui/e-meeting-time-sel.h
@@ -25,9 +25,9 @@
 
 #include <gtk/gtk.h>
 #include <libgnomecanvas/libgnomecanvas.h>
-#include <text/e-text.h>
-#include <table/e-table-model.h>
-#include <table/e-table.h>
+
+#include <e-util/e-util.h>
+
 #include "e-meeting-store.h"
 #include "e-meeting-list-view.h"
 
diff --git a/calendar/gui/e-memo-list-selector.c b/calendar/gui/e-memo-list-selector.c
index 8da45b6..366d41e 100644
--- a/calendar/gui/e-memo-list-selector.c
+++ b/calendar/gui/e-memo-list-selector.c
@@ -25,7 +25,6 @@
 #include <string.h>
 #include <libecal/libecal.h>
 
-#include "e-util/e-selection.h"
 #include "calendar/gui/comp-util.h"
 
 #define E_MEMO_LIST_SELECTOR_GET_PRIVATE(obj) \
diff --git a/calendar/gui/e-memo-list-selector.h b/calendar/gui/e-memo-list-selector.h
index f131d66..532fd31 100644
--- a/calendar/gui/e-memo-list-selector.h
+++ b/calendar/gui/e-memo-list-selector.h
@@ -26,7 +26,7 @@
 #ifndef E_MEMO_LIST_SELECTOR_H
 #define E_MEMO_LIST_SELECTOR_H
 
-#include <libedataserverui/libedataserverui.h>
+#include <e-util/e-util.h>
 
 /* Standard GObject macros */
 #define E_TYPE_MEMO_LIST_SELECTOR \
diff --git a/calendar/gui/e-memo-table.c b/calendar/gui/e-memo-table.c
index 45d51b9..7d60f84 100644
--- a/calendar/gui/e-memo-table.c
+++ b/calendar/gui/e-memo-table.c
@@ -30,32 +30,20 @@
 #include <config.h>
 #endif
 
+#include "e-memo-table.h"
+
 #include <sys/stat.h>
 #include <unistd.h>
 #include <glib/gi18n.h>
 #include <glib/gstdio.h>
-#include <misc/e-selectable.h>
-#include <table/e-cell-checkbox.h>
-#include <table/e-cell-toggle.h>
-#include <table/e-cell-text.h>
-#include <table/e-cell-combo.h>
-#include <table/e-cell-date.h>
-#include <e-util/e-selection.h>
-#include <e-util/e-dialog-utils.h>
-#include <e-util/e-util-private.h>
-#include <table/e-cell-date-edit.h>
-#include <table/e-cell-percent.h>
 
 #include "dialogs/delete-comp.h"
 #include "dialogs/delete-error.h"
 #include "dialogs/memo-editor.h"
 #include "e-cal-model-memos.h"
-#include "e-memo-table.h"
 #include "e-calendar-view.h"
 #include "e-cell-date-edit-text.h"
 #include "print.h"
-#include <e-util/e-util-private.h>
-#include <e-util/e-icon-factory.h>
 #include "misc.h"
 
 #define E_MEMO_TABLE_GET_PRIVATE(obj) \
diff --git a/calendar/gui/e-memo-table.h b/calendar/gui/e-memo-table.h
index 4f52ad3..f003aa6 100644
--- a/calendar/gui/e-memo-table.h
+++ b/calendar/gui/e-memo-table.h
@@ -25,9 +25,8 @@
 #ifndef E_MEMO_TABLE_H
 #define E_MEMO_TABLE_H
 
-#include <table/e-table.h>
-#include <table/e-cell-date-edit.h>
 #include <shell/e-shell-view.h>
+
 #include "e-cal-model.h"
 
 /*
diff --git a/calendar/gui/e-select-names-editable.h b/calendar/gui/e-select-names-editable.h
index f168902..84732f7 100644
--- a/calendar/gui/e-select-names-editable.h
+++ b/calendar/gui/e-select-names-editable.h
@@ -24,8 +24,6 @@
 #ifndef __E_SELECT_NAMES_EDITABLE_H__
 #define __E_SELECT_NAMES_EDITABLE_H__
 
-#include <libedataserverui/libedataserverui.h>
-
 G_BEGIN_DECLS
 
 #define E_TYPE_SELECT_NAMES_EDITABLE	     (e_select_names_editable_get_type ())
diff --git a/calendar/gui/e-task-list-selector.c b/calendar/gui/e-task-list-selector.c
index 1b8bbba..0989e74 100644
--- a/calendar/gui/e-task-list-selector.c
+++ b/calendar/gui/e-task-list-selector.c
@@ -25,7 +25,6 @@
 #include <string.h>
 #include <libecal/libecal.h>
 
-#include "e-util/e-selection.h"
 #include "calendar/gui/comp-util.h"
 
 #define E_TASK_LIST_SELECTOR_GET_PRIVATE(obj) \
diff --git a/calendar/gui/e-task-list-selector.h b/calendar/gui/e-task-list-selector.h
index fd133d1..df79d5f 100644
--- a/calendar/gui/e-task-list-selector.h
+++ b/calendar/gui/e-task-list-selector.h
@@ -26,7 +26,7 @@
 #ifndef E_TASK_LIST_SELECTOR_H
 #define E_TASK_LIST_SELECTOR_H
 
-#include <libedataserverui/libedataserverui.h>
+#include <e-util/e-util.h>
 
 /* Standard GObject macros */
 #define E_TYPE_TASK_LIST_SELECTOR \
diff --git a/calendar/gui/e-task-table.c b/calendar/gui/e-task-table.c
index ed73cbe..cda4896 100644
--- a/calendar/gui/e-task-table.c
+++ b/calendar/gui/e-task-table.c
@@ -29,36 +29,23 @@
 #include <config.h>
 #endif
 
+#include "e-task-table.h"
+
 #include <sys/stat.h>
 #include <unistd.h>
 #include <glib/gi18n.h>
 #include <glib/gstdio.h>
 #include <gtk/gtk.h>
 #include <gdk/gdkkeysyms.h>
-#include <misc/e-selectable.h>
-#include <table/e-cell-checkbox.h>
-#include <table/e-cell-toggle.h>
-#include <table/e-cell-text.h>
-#include <table/e-cell-combo.h>
-#include <table/e-cell-date.h>
-#include <e-util/e-selection.h>
-#include <e-util/e-dialog-utils.h>
-#include <e-util/e-util-private.h>
-#include <table/e-cell-date-edit.h>
-#include <table/e-cell-percent.h>
-#include <table/e-table-sorting-utils.h>
 
 #include "calendar-config.h"
 #include "dialogs/delete-comp.h"
 #include "dialogs/delete-error.h"
 #include "dialogs/task-editor.h"
 #include "e-cal-model-tasks.h"
-#include "e-task-table.h"
 #include "e-calendar-view.h"
 #include "e-cell-date-edit-text.h"
 #include "print.h"
-#include <e-util/e-util-private.h>
-#include <e-util/e-icon-factory.h>
 #include "misc.h"
 
 #define E_TASK_TABLE_GET_PRIVATE(obj) \
diff --git a/calendar/gui/e-task-table.h b/calendar/gui/e-task-table.h
index cf215fd..617679c 100644
--- a/calendar/gui/e-task-table.h
+++ b/calendar/gui/e-task-table.h
@@ -23,9 +23,8 @@
 #ifndef E_TASK_TABLE_H
 #define E_TASK_TABLE_H
 
-#include <table/e-table.h>
-#include <table/e-cell-date-edit.h>
 #include <shell/e-shell-view.h>
+
 #include "e-cal-model.h"
 
 /*
diff --git a/calendar/gui/e-timezone-entry.c b/calendar/gui/e-timezone-entry.c
index 41b40dc..9c9aeef 100644
--- a/calendar/gui/e-timezone-entry.c
+++ b/calendar/gui/e-timezone-entry.c
@@ -32,10 +32,12 @@
 #include <config.h>
 #endif
 
-#include <widgets/e-timezone-dialog/e-timezone-dialog.h>
-#include <glib/gi18n.h>
 #include "e-timezone-entry.h"
 
+#include <glib/gi18n.h>
+
+#include "e-util/e-util.h"
+
 #define E_TIMEZONE_ENTRY_GET_PRIVATE(obj) \
 	(G_TYPE_INSTANCE_GET_PRIVATE \
 	((obj), E_TYPE_TIMEZONE_ENTRY, ETimezoneEntryPrivate))
diff --git a/calendar/gui/e-week-view-event-item.c b/calendar/gui/e-week-view-event-item.c
index 1aa507d..298319c 100644
--- a/calendar/gui/e-week-view-event-item.c
+++ b/calendar/gui/e-week-view-event-item.c
@@ -30,15 +30,13 @@
 #include <config.h>
 #endif
 
-#include "e-util/e-categories-config.h"
 #include "e-week-view-event-item.h"
 
 #include <gtk/gtk.h>
+
 #include "e-calendar-view.h"
 #include "comp-util.h"
 
-#include <text/e-text.h>
-
 #define E_WEEK_VIEW_EVENT_ITEM_GET_PRIVATE(obj) \
 	(G_TYPE_INSTANCE_GET_PRIVATE \
 	((obj), E_TYPE_WEEK_VIEW_EVENT_ITEM, EWeekViewEventItemPrivate))
diff --git a/calendar/gui/e-week-view.c b/calendar/gui/e-week-view.c
index 9a3e92d..2e00d3b 100644
--- a/calendar/gui/e-week-view.c
+++ b/calendar/gui/e-week-view.c
@@ -30,36 +30,31 @@
 #endif
 
 #include "e-week-view.h"
-#include "ea-calendar.h"
 
 #include <math.h>
 #include <gdk/gdkkeysyms.h>
 #include <glib/gi18n.h>
 #include <libgnomecanvas/libgnomecanvas.h>
-#include <text/e-text.h>
-#include <misc/e-canvas-utils.h>
-#include <e-util/e-unicode.h>
-#include <e-util/e-categories-config.h>
-#include <e-util/e-dialog-utils.h>
-#include <e-util/e-util.h>
+
 #include "dialogs/delete-comp.h"
 #include "dialogs/delete-error.h"
 #include "dialogs/send-comp.h"
 #include "dialogs/cancel-comp.h"
 #include "dialogs/recur-comp.h"
 #include "dialogs/goto-dialog.h"
+
 #include "calendar-config.h"
-#include "comp-util.h"
-#include "itip-utils.h"
 #include "calendar-config.h"
-#include "print.h"
+#include "comp-util.h"
 #include "e-cal-model-calendar.h"
 #include "e-week-view-event-item.h"
 #include "e-week-view-layout.h"
 #include "e-week-view-main-item.h"
 #include "e-week-view-titles-item.h"
+#include "ea-calendar.h"
+#include "itip-utils.h"
 #include "misc.h"
-#include <e-util/e-icon-factory.h>
+#include "print.h"
 
 /* Images */
 #include "art/jump.xpm"
diff --git a/calendar/gui/ea-cal-view-event.c b/calendar/gui/ea-cal-view-event.c
index 35c3819..fe1f731 100644
--- a/calendar/gui/ea-cal-view-event.c
+++ b/calendar/gui/ea-cal-view-event.c
@@ -24,12 +24,12 @@
 #include <config.h>
 #endif
 
+#include <glib/gi18n.h>
+
 #include "ea-cal-view-event.h"
 #include "ea-calendar-helpers.h"
 #include "ea-day-view.h"
 #include "ea-week-view.h"
-#include <text/e-text.h>
-#include <glib/gi18n.h>
 
 static void	ea_cal_view_event_class_init	(EaCalViewEventClass *klass);
 static void	ea_cal_view_event_init		(EaCalViewEvent *a11y);
diff --git a/calendar/gui/ea-calendar-helpers.c b/calendar/gui/ea-calendar-helpers.c
index e0234d0..8d907f5 100644
--- a/calendar/gui/ea-calendar-helpers.c
+++ b/calendar/gui/ea-calendar-helpers.c
@@ -31,7 +31,6 @@
 #include "e-day-view.h"
 #include "e-week-view.h"
 
-#include <text/e-text.h>
 #include <libgnomecanvas/libgnomecanvas.h>
 
 /**
diff --git a/calendar/gui/ea-calendar.c b/calendar/gui/ea-calendar.c
index 2aa7daa..4fe44ae 100644
--- a/calendar/gui/ea-calendar.c
+++ b/calendar/gui/ea-calendar.c
@@ -24,10 +24,9 @@
 #include <config.h>
 #endif
 
-#include <text/e-text.h>
 #include <libgnomecanvas/libgnomecanvas.h>
+
 #include "ea-calendar-helpers.h"
-#include "a11y/ea-factory.h"
 #include "ea-calendar.h"
 
 #include "calendar/gui/ea-cal-view.h"
diff --git a/calendar/gui/ea-day-view-cell.c b/calendar/gui/ea-day-view-cell.c
index 5e25a2a..c72b21e 100644
--- a/calendar/gui/ea-day-view-cell.c
+++ b/calendar/gui/ea-day-view-cell.c
@@ -28,7 +28,6 @@
 #include "ea-day-view-cell.h"
 #include "ea-day-view-main-item.h"
 #include "ea-day-view.h"
-#include "a11y/ea-factory.h"
 
 /* EDayViewCell */
 
diff --git a/calendar/gui/ea-day-view-main-item.c b/calendar/gui/ea-day-view-main-item.c
index 3367833..e8f81ff 100644
--- a/calendar/gui/ea-day-view-main-item.c
+++ b/calendar/gui/ea-day-view-main-item.c
@@ -25,12 +25,12 @@
 #include <config.h>
 #endif
 
+#include <glib/gi18n.h>
+
 #include "ea-day-view-main-item.h"
 #include "e-day-view-top-item.h"
 #include "ea-day-view.h"
 #include "ea-day-view-cell.h"
-#include "ea-cell-table.h"
-#include <glib/gi18n.h>
 
 /* EaDayViewMainItem */
 static void	ea_day_view_main_item_class_init (EaDayViewMainItemClass *klass);
diff --git a/calendar/gui/ea-week-view-cell.c b/calendar/gui/ea-week-view-cell.c
index 2ceb8e1..d888b47 100644
--- a/calendar/gui/ea-week-view-cell.c
+++ b/calendar/gui/ea-week-view-cell.c
@@ -28,7 +28,6 @@
 
 #include "ea-week-view-cell.h"
 #include "ea-week-view-main-item.h"
-#include "a11y/ea-factory.h"
 
 /* EWeekViewCell */
 
diff --git a/calendar/gui/ea-week-view-main-item.c b/calendar/gui/ea-week-view-main-item.c
index d4597d8..816660c 100644
--- a/calendar/gui/ea-week-view-main-item.c
+++ b/calendar/gui/ea-week-view-main-item.c
@@ -26,10 +26,11 @@
 #endif
 
 #include "ea-week-view-main-item.h"
+
+#include <glib/gi18n.h>
+
 #include "ea-week-view.h"
 #include "ea-week-view-cell.h"
-#include "ea-cell-table.h"
-#include <glib/gi18n.h>
 
 /* EaWeekViewMainItem */
 static void	ea_week_view_main_item_class_init
diff --git a/calendar/gui/ea-week-view.c b/calendar/gui/ea-week-view.c
index 26cdcf2..24d6e8d 100644
--- a/calendar/gui/ea-week-view.c
+++ b/calendar/gui/ea-week-view.c
@@ -25,11 +25,12 @@
 #endif
 
 #include "ea-week-view.h"
+
+#include <glib/gi18n.h>
+
 #include "ea-cal-view-event.h"
 #include "ea-calendar-helpers.h"
 #include "ea-gnome-calendar.h"
-#include <text/e-text.h>
-#include <glib/gi18n.h>
 
 static void ea_week_view_class_init (EaWeekViewClass *klass);
 
diff --git a/calendar/gui/gnome-cal.c b/calendar/gui/gnome-cal.c
index 0ea1008..8b9b2b3 100644
--- a/calendar/gui/gnome-cal.c
+++ b/calendar/gui/gnome-cal.c
@@ -29,38 +29,36 @@
 #include <config.h>
 #endif
 
+#include "gnome-cal.h"
+
 #include <unistd.h>
 #include <math.h>
 #include <signal.h>
 #include <fcntl.h>
-#include <gdk/gdkkeysyms.h>
+
 #include <glib/gi18n.h>
+#include <gdk/gdkkeysyms.h>
 
-#include <widgets/menus/gal-view-factory-etable.h>
-#include <widgets/menus/gal-view-etable.h>
-#include <widgets/menus/gal-define-views-dialog.h>
-#include "e-util/e-util.h"
-#include "libevolution-utils/e-alert-dialog.h"
-#include "e-util/e-util-private.h"
 #include "shell/e-shell.h"
+
 #include "dialogs/delete-error.h"
 #include "dialogs/event-editor.h"
+
+#include "calendar-config.h"
+#include "calendar-view-factory.h"
+#include "calendar-view.h"
 #include "comp-util.h"
+#include "e-cal-list-view.h"
 #include "e-cal-model-calendar.h"
-#include "e-day-view.h"
 #include "e-day-view-time-item.h"
+#include "e-day-view.h"
+#include "e-memo-table.h"
 #include "e-month-view.h"
+#include "e-task-table.h"
 #include "e-week-view.h"
-#include "e-cal-list-view.h"
-#include "gnome-cal.h"
-#include "calendar-config.h"
-#include "calendar-view.h"
-#include "calendar-view-factory.h"
-#include "tag-calendar.h"
-#include "misc.h"
 #include "ea-calendar.h"
-#include "e-memo-table.h"
-#include "e-task-table.h"
+#include "misc.h"
+#include "tag-calendar.h"
 
 #define d(x)
 
diff --git a/calendar/gui/gnome-cal.h b/calendar/gui/gnome-cal.h
index c2f491b..dcb8017 100644
--- a/calendar/gui/gnome-cal.h
+++ b/calendar/gui/gnome-cal.h
@@ -31,7 +31,7 @@
 #include <gtk/gtk.h>
 #include <libecal/libecal.h>
 
-#include <misc/e-calendar.h>
+#include <e-util/e-util.h>
 
 #include "e-cal-model.h"
 
diff --git a/calendar/gui/itip-utils.c b/calendar/gui/itip-utils.c
index 1af60b8..f758574 100644
--- a/calendar/gui/itip-utils.c
+++ b/calendar/gui/itip-utils.c
@@ -29,8 +29,6 @@
 #include <libical/ical.h>
 #include <libsoup/soup.h>
 
-#include <e-util/e-dialog-utils.h>
-
 #include <composer/e-msg-composer.h>
 
 #include "itip-utils.h"
diff --git a/calendar/gui/print.c b/calendar/gui/print.c
index b4e6ff2..c9e48a5 100644
--- a/calendar/gui/print.c
+++ b/calendar/gui/print.c
@@ -28,16 +28,17 @@
 #include <config.h>
 #endif
 
+#include "print.h"
+
 #include <sys/stat.h>
 #include <sys/time.h>
 #include <math.h>
 #include <string.h>
 #include <time.h>
+
 #include <gtk/gtk.h>
 #include <glib/gi18n.h>
 
-#include <e-util/e-util.h>
-#include <e-util/e-print.h>
 #include "e-cal-model.h"
 #include "e-day-view.h"
 #include "e-day-view-layout.h"
@@ -45,7 +46,6 @@
 #include "e-week-view-layout.h"
 #include "e-task-table.h"
 #include "gnome-cal.h"
-#include "print.h"
 
 #include "art/jump.xpm"
 
diff --git a/calendar/gui/print.h b/calendar/gui/print.h
index b81bcfa..1b7d311 100644
--- a/calendar/gui/print.h
+++ b/calendar/gui/print.h
@@ -25,7 +25,8 @@
 #ifndef PRINT_H
 #define PRINT_H
 
-#include <table/e-table.h>
+#include <e-util/e-util.h>
+
 #include "calendar/gui/gnome-cal.h"
 
 typedef enum {
diff --git a/calendar/gui/tag-calendar.h b/calendar/gui/tag-calendar.h
index aa3eb2a..b9f406e 100644
--- a/calendar/gui/tag-calendar.h
+++ b/calendar/gui/tag-calendar.h
@@ -28,7 +28,7 @@
 #define TAG_CALENDAR_H
 
 #include <libecal/libecal.h>
-#include <misc/e-calendar.h>
+#include <e-util/e-util.h>
 
 void tag_calendar_by_client (ECalendar *ecal, ECalClient *client, GCancellable *cancellable);
 void tag_calendar_by_comp (ECalendar *ecal, ECalComponent *comp,
diff --git a/calendar/importers/Makefile.am b/calendar/importers/Makefile.am
index 7480358..f9e9846 100644
--- a/calendar/importers/Makefile.am
+++ b/calendar/importers/Makefile.am
@@ -6,10 +6,10 @@ libevolution_calendar_importers_la_CPPFLAGS = 		\
 	-DG_LOG_DOMAIN=\"Evolution-Importer\"		\
 	-I$(top_srcdir)					\
 	-I$(top_srcdir)/calendar			\
-	-I$(top_srcdir)/widgets				\
 	-I$(top_builddir)/calendar			\
 	$(EVOLUTION_DATA_SERVER_CFLAGS)			\
 	$(GNOME_PLATFORM_CFLAGS)			\
+	$(CHAMPLAIN_CFLAGS)				\
 	$(GTKHTML_CFLAGS)
 
 libevolution_calendar_importers_la_SOURCES = \
@@ -21,9 +21,9 @@ libevolution_calendar_importers_la_LDFLAGS = -avoid-version $(NO_UNDEFINED)
 libevolution_calendar_importers_la_LIBADD =			\
 	$(top_builddir)/e-util/libeutil.la 			\
 	$(top_builddir)/shell/libeshell.la			\
-	$(top_builddir)/widgets/misc/libemiscwidgets.la		\
 	$(EVOLUTION_DATA_SERVER_LIBS)				\
 	$(GNOME_PLATFORM_LIBS)					\
+	$(CHAMPLAIN_LIBS)					\
 	$(GTKHTML_LIBS)
 
 -include $(top_srcdir)/git.mk
diff --git a/calendar/importers/icalendar-importer.c b/calendar/importers/icalendar-importer.c
index 89e5f0c..d54a887 100644
--- a/calendar/importers/icalendar-importer.c
+++ b/calendar/importers/icalendar-importer.c
@@ -36,17 +36,12 @@
 #include <gtk/gtk.h>
 
 #include <libecal/libecal.h>
-#include <libedataserverui/libedataserverui.h>
 #include <libical/icalvcal.h>
 
-#include "evolution-calendar-importer.h"
 #include "shell/e-shell.h"
-#include "gui/calendar-config-keys.h"
 
-#include "e-util/e-import.h"
-#include "e-util/e-util-private.h"
-#include "e-util/e-datetime-format.h"
-#include "misc/e-web-view-preview.h"
+#include "evolution-calendar-importer.h"
+#include "gui/calendar-config-keys.h"
 
 /* We timeout after 2 minutes, when opening the folders. */
 #define IMPORTER_TIMEOUT_SECONDS 120
diff --git a/composer/Makefile.am b/composer/Makefile.am
index 59b3160..d5df640 100644
--- a/composer/Makefile.am
+++ b/composer/Makefile.am
@@ -27,10 +27,6 @@ libcomposer_la_CPPFLAGS =						\
 	-I$(top_srcdir)							\
 	-I$(top_builddir)						\
 	-I$(top_builddir)/composer					\
-	-I$(top_srcdir)/widgets						\
-	-I$(top_builddir)/widgets					\
-	-I$(top_srcdir)/widgets/misc					\
-	-I$(top_builddir)/widgets/misc					\
 	-I$(top_builddir)/shell						\
 	-I$(top_srcdir)/shell						\
 	-DEVOLUTION_DATADIR=\"$(datadir)\"				\
@@ -40,6 +36,7 @@ libcomposer_la_CPPFLAGS =						\
 	-DG_LOG_DOMAIN=\"composer\"					\
 	$(EVOLUTION_DATA_SERVER_CFLAGS)					\
 	$(GNOME_PLATFORM_CFLAGS)					\
+	$(CHAMPLAIN_CFLAGS)						\
 	$(GTKHTML_CFLAGS)
 
 libcomposer_la_SOURCES = 			\
@@ -60,15 +57,13 @@ libcomposer_la_LDFLAGS = -avoid-version $(NO_UNDEFINED)
 
 libcomposer_la_LIBADD =					\
 	$(top_builddir)/e-util/libeutil.la		\
-	$(top_builddir)/widgets/misc/libemiscwidgets.la	\
 	$(top_builddir)/shell/libeshell.la		\
 	$(top_builddir)/em-format/libemformat.la	\
 	$(top_builddir)/addressbook/gui/contact-editor/libecontacteditor.la		\
 	$(top_builddir)/addressbook/gui/contact-list-editor/libecontactlisteditor.la	\
-	$(top_builddir)/libemail-utils/libemail-utils.la \
-	$(top_builddir)/libevolution-utils/libevolution-utils.la \
 	$(EVOLUTION_DATA_SERVER_LIBS)			\
 	$(GNOME_PLATFORM_LIBS)				\
+	$(CHAMPLAIN_CFLAGS)				\
 	$(GTKHTML_LIBS)
 
 ui_DATA = evolution-composer.ui
diff --git a/composer/e-composer-actions.c b/composer/e-composer-actions.c
index 966866b..1317775 100644
--- a/composer/e-composer-actions.c
+++ b/composer/e-composer-actions.c
@@ -25,7 +25,6 @@
 
 #include <errno.h>
 #include <fcntl.h>
-#include <libevolution-utils/e-alert-dialog.h>
 
 static void
 action_attach_cb (GtkAction *action,
diff --git a/composer/e-composer-activity.h b/composer/e-composer-activity.h
index 431e390..a437e06 100644
--- a/composer/e-composer-activity.h
+++ b/composer/e-composer-activity.h
@@ -19,7 +19,6 @@
 #ifndef E_COMPOSER_ACTIVITY_H
 #define E_COMPOSER_ACTIVITY_H
 
-#include <e-util/e-activity.h>
 #include <composer/e-msg-composer.h>
 
 /* Standard GObject macros */
diff --git a/composer/e-composer-common.h b/composer/e-composer-common.h
index 661797e..b58a138 100644
--- a/composer/e-composer-common.h
+++ b/composer/e-composer-common.h
@@ -22,5 +22,6 @@
 #define E_COMPOSER_COMMON
 
 #include <gtk/gtk.h>
+#include <e-util/e-util.h>
 
 #endif /* E_COMPOSER_COMMON */
diff --git a/composer/e-composer-from-header.c b/composer/e-composer-from-header.c
index ceecd68..d9afb59 100644
--- a/composer/e-composer-from-header.c
+++ b/composer/e-composer-from-header.c
@@ -24,8 +24,6 @@
 
 #include "e-composer-from-header.h"
 
-#include <misc/e-mail-identity-combo-box.h>
-
 G_DEFINE_TYPE (
 	EComposerFromHeader,
 	e_composer_from_header,
diff --git a/composer/e-composer-header-table.c b/composer/e-composer-header-table.c
index 72c8dac..2760ba5 100644
--- a/composer/e-composer-header-table.c
+++ b/composer/e-composer-header-table.c
@@ -22,10 +22,8 @@
 #include "e-composer-header-table.h"
 
 #include <glib/gi18n-lib.h>
-#include <libedataserverui/libedataserverui.h>
 
 #include <shell/e-shell.h>
-#include <misc/e-mail-signature-combo-box.h>
 
 #include "e-msg-composer.h"
 #include "e-composer-private.h"
diff --git a/composer/e-composer-header-table.h b/composer/e-composer-header-table.h
index 3e7e16c..f459aae 100644
--- a/composer/e-composer-header-table.h
+++ b/composer/e-composer-header-table.h
@@ -22,7 +22,6 @@
 
 #include <shell/e-shell.h>
 #include <composer/e-composer-header.h>
-#include <misc/e-mail-signature-combo-box.h>
 
 /* Standard GObject macros */
 #define E_TYPE_COMPOSER_HEADER_TABLE \
diff --git a/composer/e-composer-name-header.h b/composer/e-composer-name-header.h
index b745941..5632ddf 100644
--- a/composer/e-composer-name-header.h
+++ b/composer/e-composer-name-header.h
@@ -19,7 +19,6 @@
 #define E_COMPOSER_NAME_HEADER_H
 
 #include <libebook/libebook.h>
-#include <libedataserverui/libedataserverui.h>
 
 #include <composer/e-composer-header.h>
 
diff --git a/composer/e-composer-private.h b/composer/e-composer-private.h
index 4370b1a..fc358b9 100644
--- a/composer/e-composer-private.h
+++ b/composer/e-composer-private.h
@@ -35,24 +35,6 @@
 #include "e-composer-actions.h"
 #include "e-composer-activity.h"
 #include "e-composer-header-table.h"
-#include "libevolution-utils/e-alert-sink.h"
-#include "e-util/e-charset.h"
-#include "e-util/e-marshal.h"
-#include "e-util/e-mktemp.h"
-#include "e-util/e-plugin-ui.h"
-#include "e-util/e-selection.h"
-#include "e-util/e-util.h"
-#include "widgets/misc/e-activity-bar.h"
-#include "widgets/misc/e-alert-bar.h"
-#include "widgets/misc/e-attachment.h"
-#include "widgets/misc/e-attachment-icon-view.h"
-#include "widgets/misc/e-attachment-paned.h"
-#include "widgets/misc/e-attachment-store.h"
-#include "widgets/misc/e-mail-signature-combo-box.h"
-#include "widgets/misc/e-picture-gallery.h"
-#include "widgets/misc/e-preferences-window.h"
-#include "widgets/misc/e-web-view-gtkhtml.h"
-#include "shell/e-shell.h"
 
 #ifdef HAVE_XFREE
 #include <X11/XF86keysym.h>
diff --git a/composer/e-composer-spell-header.c b/composer/e-composer-spell-header.c
index ad4ddca..a3c945d 100644
--- a/composer/e-composer-spell-header.c
+++ b/composer/e-composer-spell-header.c
@@ -19,8 +19,6 @@
 #include <config.h>
 #endif
 
-#include <misc/e-spell-entry.h>
-
 #include "e-composer-spell-header.h"
 
 G_DEFINE_TYPE (
diff --git a/composer/e-msg-composer.c b/composer/e-msg-composer.c
index 5cd9a70..dcfd385 100644
--- a/composer/e-msg-composer.c
+++ b/composer/e-msg-composer.c
@@ -37,10 +37,6 @@
 #include <ctype.h>
 #include <fcntl.h>
 
-#include <libevolution-utils/e-alert-dialog.h>
-#include <e-util/e-dialog-utils.h>
-#include <e-util/e-util-private.h>
-
 #include "e-composer-private.h"
 
 #include <em-format/e-mail-part.h>
diff --git a/composer/e-msg-composer.h b/composer/e-msg-composer.h
index a85993f..941845a 100644
--- a/composer/e-msg-composer.h
+++ b/composer/e-msg-composer.h
@@ -28,9 +28,6 @@
 #include <gtkhtml-editor.h>
 #include <libebook/libebook.h>
 
-#include <misc/e-attachment-view.h>
-#include <misc/e-focus-tracker.h>
-#include <misc/e-web-view-gtkhtml.h>
 #include <shell/e-shell.h>
 
 #include <composer/e-composer-header-table.h>
diff --git a/configure.ac b/configure.ac
index 7f5f844..d2d5d5b 100644
--- a/configure.ac
+++ b/configure.ac
@@ -303,7 +303,6 @@ PKG_CHECK_MODULES([EVOLUTION_DATA_SERVER],
 	 libebook-1.2 >= eds_minimum_version
 	 libecal-1.2 >= eds_minimum_version
 	 libedataserver-1.2 >= eds_minimum_version
-	 libedataserverui-3.0 >= eds_minimum_version
 	 libebackend-1.2 >= eds_minimum_version])
 AC_SUBST(EVOLUTION_DATA_SERVER_CFLAGS)
 AC_SUBST(EVOLUTION_DATA_SERVER_LIBS)
@@ -1565,7 +1564,6 @@ AC_SUBST(EVOLUTION_DIR)
 
 AC_CONFIG_FILES([ po/Makefile.in
 Makefile
-a11y/Makefile
 addressbook/Makefile
 addressbook/gui/Makefile
 addressbook/gui/contact-editor/Makefile
@@ -1585,10 +1583,10 @@ data/evolution-settings.desktop.in
 data/icons/Makefile
 doc/Makefile
 doc/reference/Makefile
-doc/reference/shell/Makefile
+doc/reference/libeshell/Makefile
+doc/reference/libeutil/Makefile
 e-util/Makefile
 em-format/Makefile
-filter/Makefile
 help/Makefile
 help/quickref/Makefile
 help/quickref/C/Makefile
@@ -1604,10 +1602,6 @@ help/quickref/pl/Makefile
 help/quickref/pt/Makefile
 help/quickref/sv/Makefile
 help/quickref/sq/Makefile
-libevolution-utils/Makefile
-libevolution-utils/libevolution-utils.pc
-libemail-utils/Makefile
-libemail-utils/libemail-utils.pc
 libemail-engine/Makefile
 libemail-engine/libemail-engine.pc
 libgnomecanvas/Makefile
@@ -1620,12 +1614,6 @@ views/calendar/Makefile
 views/mail/Makefile
 views/tasks/Makefile
 views/memos/Makefile
-widgets/Makefile
-widgets/e-timezone-dialog/Makefile
-widgets/menus/Makefile
-widgets/misc/Makefile
-widgets/text/Makefile
-widgets/table/Makefile
 calendar/Makefile
 calendar/alarm-notify/Makefile
 calendar/importers/Makefile
diff --git a/doc/reference/Makefile.am b/doc/reference/Makefile.am
index 48d38ad..806e1b0 100644
--- a/doc/reference/Makefile.am
+++ b/doc/reference/Makefile.am
@@ -1,3 +1,3 @@
-SUBDIRS = shell
+SUBDIRS = libeutil libeshell
 
 -include $(top_srcdir)/git.mk
diff --git a/doc/reference/libeshell/Makefile.am b/doc/reference/libeshell/Makefile.am
new file mode 100644
index 0000000..7262075
--- /dev/null
+++ b/doc/reference/libeshell/Makefile.am
@@ -0,0 +1,80 @@
+# The name of the module, e.g. 'glib'.
+DOC_MODULE=libeshell
+
+# The top-level SGML file. You can change this if you want to.
+DOC_MAIN_SGML_FILE=$(DOC_MODULE)-docs.sgml
+
+# The directory containing the source code. Relative to $(srcdir).
+# gtk-doc will search all .c & .h files beneath here for inline comments
+# documenting the functions and macros.
+# e.g. DOC_SOURCE_DIR=../../../gtk
+DOC_SOURCE_DIR=../../../shell
+
+# Extra options to pass to gtkdoc-scangobj. Not normally needed.
+SCANGOBJ_OPTIONS=
+
+# Extra options to supply to gtkdoc-scan.
+# e.g. SCAN_OPTIONS=--deprecated-guards="GTK_DISABLE_DEPRECATED"
+SCAN_OPTIONS=
+
+# Extra options to supply to gtkdoc-mkdb.
+MKDB_OPTIONS=--sgml-mode --output-format=xml --name-space=e
+
+# Extra options to supply to gtkdoc-mktmpl
+# e.g. MKTMPL_OPTIONS=--only-section-tmpl
+MKTMPL_OPTIONS=
+
+# Extra options to supply to gtkdoc-fixref. Not normally needed.
+# e.g. FIXXREF_OPTIONS=--extra-dir=../gdk-pixbuf/html --extra-dir=../gdk/html
+FIXXREF_OPTIONS=
+
+# Used for dependencies. The docs will be rebuilt if any of these change.
+HFILE_GLOB=$(top_srcdir)/shell/*.h
+CFILE_GLOB=$(top_srcdir)/shell/*.c
+
+# Header files to ignore when scanning.
+IGNORE_HFILES= \
+	evo-version.h \
+	e-shell-window-private.h \
+	es-event.h
+
+# Images to copy into HTML directory.
+HTML_IMAGES=
+
+# Extra SGML files that are included by $(DOC_MAIN_SGML_FILE).
+content_files=
+
+# SGML files where gtk-doc abbrevations (#GtkWidget) are expanded
+# These files must be listed here *and* in content_files
+expand_content_files=
+
+# CFLAGS and LDFLAGS for compiling gtkdoc-scangobj with your library.
+# Only needed if you are using gtkdoc-scangobj to dynamically query widget
+# signals and properties.
+GTKDOC_CFLAGS= \
+	-I$(top_builddir)						\
+	-I$(top_srcdir)							\
+	$(EVOLUTION_DATA_SERVER_CFLAGS)					\
+	$(GTKHTML_CFLAGS)
+GTKDOC_LIBS=								\
+	$(top_builddir)/libemail-engine/libemail-engine.la		\
+	$(top_builddir)/shell/libeshell.la				\
+	$(top_builddir)/e-util/libeutil.la				\
+	$(EVOLUTION_DATA_SERVER_LIBS)					\
+	$(GTKHTML_LIBS)
+
+# This includes the standard gtk-doc make rules, copied by gtkdocize.
+include $(top_srcdir)/gtk-doc.make
+
+# Other files to distribute
+# e.g. EXTRA_DIST += version.xml.in
+EXTRA_DIST +=
+
+# Files not to distribute
+# for --rebuild-types in $(SCAN_OPTIONS), e.g. $(DOC_MODULE).types
+# for --rebuild-sections in $(SCAN_OPTIONS) e.g. $(DOC_MODULE)-sections.txt
+#DISTCLEANFILES +=
+
+#TESTS = $(GTKDOC_CHECK)
+
+-include $(top_srcdir)/git.mk
diff --git a/doc/reference/libeshell/libeshell-docs.sgml b/doc/reference/libeshell/libeshell-docs.sgml
new file mode 100644
index 0000000..8a9404f
--- /dev/null
+++ b/doc/reference/libeshell/libeshell-docs.sgml
@@ -0,0 +1,44 @@
+<?xml version="1.0"?>
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN"
+               "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd";>
+<book id="index" xmlns:xi="http://www.w3.org/2003/XInclude";>
+  <bookinfo>
+    <title>Evolution Shell (libeshell)</title>
+    <releaseinfo>
+      The latest version of this documentation can be found on-line at
+      <ulink role="online-location" url="http://library.gnome.org/devel/libeshell/";>http://library.gnome.org/devel/libeshell/</ulink>.
+    </releaseinfo>
+  </bookinfo>
+
+  <chapter>
+    <title>The Shell</title>
+    <xi:include href="xml/e-shell.xml"/>
+    <xi:include href="xml/e-shell-backend.xml"/>
+    <xi:include href="xml/e-shell-window.xml"/>
+    <xi:include href="xml/e-shell-view.xml"/>
+    <xi:include href="xml/e-shell-content.xml"/>
+    <xi:include href="xml/e-shell-sidebar.xml"/>
+    <xi:include href="xml/e-shell-taskbar.xml"/>
+    <xi:include href="xml/e-shell-searchbar.xml"/>
+    <xi:include href="xml/e-shell-settings.xml"/>
+    <xi:include href="xml/e-shell-switcher.xml"/>
+    <xi:include href="xml/e-shell-utils.xml"/>
+  </chapter>
+
+  <chapter>
+    <title>Actions</title>
+    <xi:include href="xml/shell-actions.xml"/>
+    <xi:include href="xml/action-groups.xml"/>
+  </chapter>
+
+  <chapter>
+    <title>Object Hierarchy</title>
+    <xi:include href="xml/tree_index.sgml"/>
+  </chapter>
+
+  <index id="api-index-full">
+    <title>Index</title>
+    <xi:include href="xml/api-index-full.xml"><xi:fallback /></xi:include>
+  </index>
+
+</book>
diff --git a/doc/reference/shell/eshell-overrides.txt b/doc/reference/libeshell/libeshell-overrides.txt
similarity index 100%
copy from doc/reference/shell/eshell-overrides.txt
copy to doc/reference/libeshell/libeshell-overrides.txt
diff --git a/doc/reference/libeshell/libeshell-sections.txt b/doc/reference/libeshell/libeshell-sections.txt
new file mode 100644
index 0000000..f6b997d
--- /dev/null
+++ b/doc/reference/libeshell/libeshell-sections.txt
@@ -0,0 +1,423 @@
+<SECTION>
+<FILE>e-shell</FILE>
+<TITLE>EShell</TITLE>
+EShell
+e_shell_get_default
+e_shell_load_modules
+e_shell_get_shell_backends
+e_shell_get_canonical_name
+e_shell_get_backend_by_name
+e_shell_get_backend_by_scheme
+e_shell_get_shell_settings
+e_shell_get_registry
+e_shell_create_shell_window
+e_shell_handle_uris
+e_shell_submit_alert
+e_shell_get_active_window
+e_shell_get_meego_mode
+e_shell_get_express_mode
+e_shell_get_small_screen_mode
+e_shell_get_module_directory
+e_shell_get_network_available
+e_shell_set_network_available
+e_shell_lock_network_available
+e_shell_get_online
+e_shell_set_online
+e_shell_get_preferences_window
+e_shell_event
+EShellQuitReason
+e_shell_quit
+e_shell_cancel_quit
+e_shell_adapt_window_size
+e_shell_set_startup_view
+e_shell_get_startup_view
+E_SHELL_MIGRATE_ERROR
+EShellMigrateError
+e_shell_migrate_attempt
+e_shell_detect_meego
+<SUBSECTION Standard>
+E_SHELL
+E_IS_SHELL
+E_TYPE_SHELL
+E_SHELL_CLASS
+E_IS_SHELL_CLASS
+E_SHELL_GET_CLASS
+E_TYPE_SHELL_QUIT_REASON
+EShellClass
+e_shell_get_type
+e_shell_quit_reason_get_type
+<SUBSECTION Private>
+EShellPrivate
+e_shell_migrate_error_quark
+</SECTION>
+
+<SECTION>
+<FILE>e-shell-backend</FILE>
+<TITLE>EShellBackend</TITLE>
+EShellBackend
+e_shell_backend_compare
+e_shell_backend_get_config_dir
+e_shell_backend_get_data_dir
+e_shell_backend_get_shell
+e_shell_backend_add_activity
+e_shell_backend_cancel_all
+e_shell_backend_is_busy
+e_shell_backend_get_prefer_new_item
+e_shell_backend_set_prefer_new_item
+e_shell_backend_start
+e_shell_backend_is_started
+e_shell_backend_migrate
+<SUBSECTION Standard>
+E_SHELL_BACKEND
+E_IS_SHELL_BACKEND
+E_TYPE_SHELL_BACKEND
+E_SHELL_BACKEND_CLASS
+E_IS_SHELL_BACKEND_CLASS
+E_SHELL_BACKEND_GET_CLASS
+EShellBackendClass
+e_shell_backend_get_type
+<SUBSECTION Private>
+EShellBackendPrivate
+</SECTION>
+
+<SECTION>
+<FILE>e-shell-content</FILE>
+<TITLE>EShellContent</TITLE>
+EShellContent
+e_shell_content_new
+e_shell_content_set_searchbar
+e_shell_content_check_state
+e_shell_content_focus_search_results
+e_shell_content_get_alert_bar
+e_shell_content_get_shell_view
+e_shell_content_get_view_id
+e_shell_content_set_view_id
+e_shell_content_run_advanced_search_dialog
+e_shell_content_run_edit_searches_dialog
+e_shell_content_run_save_search_dialog
+<SUBSECTION Standard>
+E_SHELL_CONTENT
+E_IS_SHELL_CONTENT
+E_TYPE_SHELL_CONTENT
+E_SHELL_CONTENT_CLASS
+E_IS_SHELL_CONTENT_CLASS
+E_SHELL_CONTENT_GET_CLASS
+EShellContentClass
+e_shell_content_get_type
+<SUBSECTION Private>
+EShellContentPrivate
+</SECTION>
+
+<SECTION>
+<FILE>e-shell-searchbar</FILE>
+<TITLE>EShellSearchbar</TITLE>
+EShellSearchbar
+e_shell_searchbar_new
+e_shell_searchbar_get_shell_view
+e_shell_searchbar_get_express_mode
+e_shell_searchbar_set_express_mode
+e_shell_searchbar_get_filter_combo_box
+e_shell_searchbar_get_filter_visible
+e_shell_searchbar_set_filter_visible
+e_shell_searchbar_get_labels_visible
+e_shell_searchbar_set_labels_visible
+e_shell_searchbar_get_search_hint
+e_shell_searchbar_set_search_hint
+e_shell_searchbar_get_search_option
+e_shell_searchbar_set_search_option
+e_shell_searchbar_get_search_text
+e_shell_searchbar_set_search_text
+e_shell_searchbar_get_search_visible
+e_shell_searchbar_set_search_visible
+e_shell_searchbar_get_search_box
+e_shell_searchbar_get_scope_combo_box
+e_shell_searchbar_get_scope_visible
+e_shell_searchbar_set_scope_visible
+e_shell_searchbar_set_state_dirty
+e_shell_searchbar_get_state_group
+e_shell_searchbar_set_state_group
+e_shell_searchbar_load_state
+e_shell_searchbar_save_state
+<SUBSECTION Standard>
+E_SHELL_SEARCHBAR
+E_IS_SHELL_SEARCHBAR
+E_TYPE_SHELL_SEARCHBAR
+E_SHELL_SEARCHBAR_CLASS
+E_IS_SHELL_SEARCHBAR_CLASS
+E_SHELL_SEARCHBAR_GET_CLASS
+EShellSearchbarClass
+e_shell_searchbar_get_type
+<SUBSECTION Private>
+EShellSearchbarPrivate
+</SECTION>
+
+<SECTION>
+<FILE>e-shell-settings</FILE>
+<TITLE>EShellSettings</TITLE>
+EShellSettings
+e_shell_settings_install_property
+e_shell_settings_install_property_for_key
+e_shell_settings_enable_debug
+e_shell_settings_get_boolean
+e_shell_settings_set_boolean
+e_shell_settings_get_int
+e_shell_settings_set_int
+e_shell_settings_get_string
+e_shell_settings_set_string
+e_shell_settings_get_object
+e_shell_settings_set_object
+e_shell_settings_get_pointer
+e_shell_settings_set_pointer
+<SUBSECTION Standard>
+E_SHELL_SETTINGS
+E_IS_SHELL_SETTINGS
+E_TYPE_SHELL_SETTINGS
+E_SHELL_SETTINGS_CLASS
+E_IS_SHELL_SETTINGS_CLASS
+E_SHELL_SETTINGS_GET_CLASS
+EShellSettingsClass
+e_shell_settings_get_type
+<SUBSECTION Private>
+EShellSettingsPrivate
+</SECTION>
+
+<SECTION>
+<FILE>e-shell-sidebar</FILE>
+<TITLE>EShellSidebar</TITLE>
+EShellSidebar
+e_shell_sidebar_new
+e_shell_sidebar_check_state
+e_shell_sidebar_get_shell_view
+e_shell_sidebar_get_icon_name
+e_shell_sidebar_set_icon_name
+e_shell_sidebar_get_primary_text
+e_shell_sidebar_set_primary_text
+e_shell_sidebar_get_secondary_text
+e_shell_sidebar_set_secondary_text
+<SUBSECTION Standard>
+E_SHELL_SIDEBAR
+E_IS_SHELL_SIDEBAR
+E_TYPE_SHELL_SIDEBAR
+E_SHELL_SIDEBAR_CLASS
+E_IS_SHELL_SIDEBAR_CLASS
+E_SHELL_SIDEBAR_GET_CLASS
+EShellSidebarClass
+e_shell_sidebar_get_type
+<SUBSECTION Private>
+EShellSidebarPrivate
+</SECTION>
+
+<SECTION>
+<FILE>e-shell-switcher</FILE>
+<TITLE>EShellSwitcher</TITLE>
+EShellSwitcher
+e_shell_switcher_new
+e_shell_switcher_add_action
+e_shell_switcher_get_style
+e_shell_switcher_set_style
+e_shell_switcher_unset_style
+e_shell_switcher_get_visible
+e_shell_switcher_set_visible
+<SUBSECTION Standard>
+E_SHELL_SWITCHER
+E_IS_SHELL_SWITCHER
+E_TYPE_SHELL_SWITCHER
+E_SHELL_SWITCHER_CLASS
+E_IS_SHELL_SWITCHER_CLASS
+E_SHELL_SWITCHER_GET_CLASS
+EShellSwitcherClass
+e_shell_switcher_get_type
+<SUBSECTION Private>
+EShellSwitcherPrivate
+E_SHELL_SWITCHER_DEFAULT_TOOLBAR_STYLE
+</SECTION>
+
+<SECTION>
+<FILE>e-shell-taskbar</FILE>
+<TITLE>EShellTaskbar</TITLE>
+EShellTaskbar
+e_shell_taskbar_new
+e_shell_taskbar_get_shell_view
+e_shell_taskbar_get_message
+e_shell_taskbar_set_message
+e_shell_taskbar_unset_message
+e_shell_taskbar_get_activity_count
+<SUBSECTION Standard>
+E_SHELL_TASKBAR
+E_IS_SHELL_TASKBAR
+E_TYPE_SHELL_TASKBAR
+E_SHELL_TASKBAR_CLASS
+E_IS_SHELL_TASKBAR_CLASS
+E_SHELL_TASKBAR_GET_CLASS
+EShellTaskbarClass
+e_shell_taskbar_get_type
+<SUBSECTION Private>
+EShellTaskbarPrivate
+</SECTION>
+
+<SECTION>
+<FILE>e-shell-utils</FILE>
+<TITLE>Shell Utilities</TITLE>
+e_shell_configure_ui_manager
+e_shell_run_open_dialog
+e_shell_run_save_dialog
+e_shell_utils_import_uris
+e_shell_hide_widgets_for_express_mode
+</SECTION>
+
+<SECTION>
+<FILE>e-shell-view</FILE>
+<TITLE>EShellView</TITLE>
+EShellView
+EShellViewClass
+e_shell_view_get_name
+e_shell_view_get_action
+e_shell_view_get_title
+e_shell_view_set_title
+e_shell_view_get_view_id
+e_shell_view_set_view_id
+e_shell_view_is_active
+e_shell_view_get_page_num
+e_shell_view_set_page_num
+e_shell_view_get_searchbar
+e_shell_view_get_search_name
+e_shell_view_get_search_rule
+e_shell_view_set_search_rule
+e_shell_view_get_search_query
+e_shell_view_get_shell_backend
+e_shell_view_get_shell_content
+e_shell_view_get_shell_sidebar
+e_shell_view_get_shell_taskbar
+e_shell_view_get_shell_window
+e_shell_view_get_state_key_file
+e_shell_view_set_state_dirty
+e_shell_view_clear_search
+e_shell_view_custom_search
+e_shell_view_execute_search
+e_shell_view_block_execute_search
+e_shell_view_unblock_execute_search
+e_shell_view_is_execute_search_blocked
+e_shell_view_update_actions
+e_shell_view_block_update_actions
+e_shell_view_unblock_update_actions
+e_shell_view_show_popup_menu
+e_shell_view_new_view_instance
+e_shell_view_write_source
+e_shell_view_remove_source
+e_shell_view_remote_delete_source
+<SUBSECTION Standard>
+E_SHELL_VIEW
+E_IS_SHELL_VIEW
+E_TYPE_SHELL_VIEW
+E_SHELL_VIEW_CLASS
+E_IS_SHELL_VIEW_CLASS
+E_SHELL_VIEW_GET_CLASS
+e_shell_view_get_type
+<SUBSECTION Private>
+EShellViewPrivate
+</SECTION>
+
+<SECTION>
+<FILE>e-shell-window</FILE>
+<TITLE>EShellWindow</TITLE>
+EShellWindow
+e_shell_window_new
+e_shell_window_get_shell
+e_shell_window_get_shell_view
+e_shell_window_peek_shell_view
+e_shell_window_get_shell_view_action
+e_shell_window_get_alert_bar
+e_shell_window_get_focus_tracker
+e_shell_window_get_ui_manager
+e_shell_window_get_action
+e_shell_window_get_action_group
+e_shell_window_get_managed_widget
+e_shell_window_get_active_view
+e_shell_window_set_active_view
+e_shell_window_get_safe_mode
+e_shell_window_set_safe_mode
+e_shell_window_add_action_group
+e_shell_window_get_sidebar_visible
+e_shell_window_set_sidebar_visible
+e_shell_window_get_switcher_visible
+e_shell_window_set_switcher_visible
+e_shell_window_get_taskbar_visible
+e_shell_window_set_taskbar_visible
+e_shell_window_get_toolbar_visible
+e_shell_window_set_toolbar_visible
+e_shell_window_get_toolbar_new_prefer_item
+e_shell_window_set_toolbar_new_prefer_item
+e_shell_window_register_new_item_actions
+e_shell_window_register_new_source_actions
+e_shell_window_get_menu_bar_box
+<SUBSECTION Standard>
+E_SHELL_WINDOW
+E_IS_SHELL_WINDOW
+E_TYPE_SHELL_WINDOW
+E_SHELL_WINDOW_CLASS
+E_IS_SHELL_WINDOW_CLASS
+E_SHELL_WINDOW_GET_CLASS
+EShellWindowClass
+e_shell_window_get_type
+<SUBSECTION Private>
+EShellWindowPrivate
+E_SHELL_WINDOW_ACTION
+E_SHELL_WINDOW_ACTION_GROUP
+</SECTION>
+
+<SECTION>
+<FILE>shell-actions</FILE>
+<TITLE>Shell Actions</TITLE>
+E_SHELL_WINDOW_ACTION_ABOUT
+E_SHELL_WINDOW_ACTION_CLOSE
+E_SHELL_WINDOW_ACTION_CONTENTS
+E_SHELL_WINDOW_ACTION_COPY_CLIPBOARD
+E_SHELL_WINDOW_ACTION_CUT_CLIPBOARD
+E_SHELL_WINDOW_ACTION_DELETE_SELECTION
+E_SHELL_WINDOW_ACTION_GAL_CUSTOM_VIEW
+E_SHELL_WINDOW_ACTION_GAL_DEFINE_VIEWS
+E_SHELL_WINDOW_ACTION_GAL_SAVE_CUSTOM_VIEW
+E_SHELL_WINDOW_ACTION_GROUP_NEW_WINDOW
+E_SHELL_WINDOW_ACTION_IMPORT
+E_SHELL_WINDOW_ACTION_NEW_WINDOW
+E_SHELL_WINDOW_ACTION_PAGE_SETUP
+E_SHELL_WINDOW_ACTION_PASTE_CLIPBOARD
+E_SHELL_WINDOW_ACTION_PREFERENCES
+E_SHELL_WINDOW_ACTION_QUICK_REFERENCE
+E_SHELL_WINDOW_ACTION_QUIT
+E_SHELL_WINDOW_ACTION_SEARCH_ADVANCED
+E_SHELL_WINDOW_ACTION_SEARCH_CLEAR
+E_SHELL_WINDOW_ACTION_SEARCH_EDIT
+E_SHELL_WINDOW_ACTION_SEARCH_OPTIONS
+E_SHELL_WINDOW_ACTION_SEARCH_QUICK
+E_SHELL_WINDOW_ACTION_SEARCH_SAVE
+E_SHELL_WINDOW_ACTION_SELECT_ALL
+E_SHELL_WINDOW_ACTION_SHOW_SIDEBAR
+E_SHELL_WINDOW_ACTION_SHOW_SWITCHER
+E_SHELL_WINDOW_ACTION_SHOW_TASKBAR
+E_SHELL_WINDOW_ACTION_SHOW_TOOLBAR
+E_SHELL_WINDOW_ACTION_SUBMIT_BUG
+E_SHELL_WINDOW_ACTION_SWITCHER_INITIAL
+E_SHELL_WINDOW_ACTION_SWITCHER_MENU
+E_SHELL_WINDOW_ACTION_SWITCHER_STYLE_BOTH
+E_SHELL_WINDOW_ACTION_SWITCHER_STYLE_ICONS
+E_SHELL_WINDOW_ACTION_SWITCHER_STYLE_TEXT
+E_SHELL_WINDOW_ACTION_SWITCHER_STYLE_USER
+E_SHELL_WINDOW_ACTION_WORK_OFFLINE
+E_SHELL_WINDOW_ACTION_WORK_ONLINE
+</SECTION>
+
+<SECTION>
+<FILE>action-groups</FILE>
+<TITLE>Action Groups</TITLE>
+E_SHELL_WINDOW_ACTION_GROUP_SHELL
+E_SHELL_WINDOW_ACTION_GROUP_SWITCHER
+E_SHELL_WINDOW_ACTION_GROUP_NEW_ITEM
+E_SHELL_WINDOW_ACTION_GROUP_NEW_SOURCE
+E_SHELL_WINDOW_ACTION_GROUP_CUSTOM_RULES
+E_SHELL_WINDOW_ACTION_GROUP_GAL_VIEW
+E_SHELL_WINDOW_ACTION_GROUP_LOCKDOWN_APPLICATION_HANDLERS
+E_SHELL_WINDOW_ACTION_GROUP_LOCKDOWN_PRINTING
+E_SHELL_WINDOW_ACTION_GROUP_LOCKDOWN_PRINT_SETUP
+E_SHELL_WINDOW_ACTION_GROUP_LOCKDOWN_SAVE_TO_DISK
+</SECTION>
diff --git a/doc/reference/libeshell/libeshell.types b/doc/reference/libeshell/libeshell.types
new file mode 100644
index 0000000..b0038d5
--- /dev/null
+++ b/doc/reference/libeshell/libeshell.types
@@ -0,0 +1,9 @@
+e_shell_get_type
+e_shell_backend_get_type
+e_shell_content_get_type
+e_shell_searchbar_get_type
+e_shell_sidebar_get_type
+e_shell_switcher_get_type
+e_shell_taskbar_get_type
+e_shell_view_get_type
+e_shell_window_get_type
diff --git a/doc/reference/shell/tmpl/e-mail-account-manager.sgml b/doc/reference/libeshell/tmpl/e-mail-account-manager.sgml
similarity index 100%
rename from doc/reference/shell/tmpl/e-mail-account-manager.sgml
rename to doc/reference/libeshell/tmpl/e-mail-account-manager.sgml
diff --git a/doc/reference/shell/tmpl/e-mail-account-tree-view.sgml b/doc/reference/libeshell/tmpl/e-mail-account-tree-view.sgml
similarity index 100%
rename from doc/reference/shell/tmpl/e-mail-account-tree-view.sgml
rename to doc/reference/libeshell/tmpl/e-mail-account-tree-view.sgml
diff --git a/doc/reference/shell/tmpl/e-mail-identity-combo-box.sgml b/doc/reference/libeshell/tmpl/e-mail-identity-combo-box.sgml
similarity index 100%
rename from doc/reference/shell/tmpl/e-mail-identity-combo-box.sgml
rename to doc/reference/libeshell/tmpl/e-mail-identity-combo-box.sgml
diff --git a/doc/reference/libeutil/Makefile.am b/doc/reference/libeutil/Makefile.am
new file mode 100644
index 0000000..71eabf3
--- /dev/null
+++ b/doc/reference/libeutil/Makefile.am
@@ -0,0 +1,67 @@
+# The name of the module.
+DOC_MODULE = libeutil
+
+# The top-level SGML file.
+DOC_MAIN_SGML_FILE = libeutil-docs.sgml
+
+# Extra options to supply to gtkdoc-scan.
+SCAN_OPTIONS = --deprecated-guards="EDS_DISABLE_DEPRECATED"
+
+# The directory containing the source code. Relative to $(srcdir).
+DOC_SOURCE_DIR = $(top_srcdir)/e-util
+
+# Used for dependencies. The docs will be rebuilt if any of these change.
+HFILE_GLOB = $(top_srcdir)/e-util/*.h
+CFILE_GLOB = $(top_srcdir)/e-util/*.c
+
+# Ignore all accessiblity headers.
+IGNORE_HFILES = \
+	ea-calendar-cell.h \
+	ea-calendar-item.h \
+	ea-cell-table.h \
+	ea-factory.h \
+	ea-widgets.h \
+	gal-a11y-e-cell-registry.h \
+	gal-a11y-e-cell-toggle.h \
+	gal-a11y-e-cell-tree.h \
+	gal-a11y-e-cell-vbox.h \
+	gal-a11y-e-cell.h \
+	gal-a11y-e-popup.h \
+	gal-a11y-e-table-click-to-add-factory.h \
+	gal-a11y-e-table-click-to-add.h \
+	gal-a11y-e-table-column-header.h \
+	gal-a11y-e-table-factory.h \
+	gal-a11y-e-table-item-factory.h \
+	gal-a11y-e-table-item.h \
+	gal-a11y-e-table.h \
+	gal-a11y-e-text-factory.h \
+	gal-a11y-e-text.h \
+	gal-a11y-e-tree-factory.h \
+	gal-a11y-e-tree.h \
+	gal-a11y-factory.h
+	gal-a11y-util.h \
+	$(NULL)
+
+GTKDOC_CFLAGS  =					\
+	-I$(top_srcdir) 				\
+	-I$(top_builddir)				\
+	$(EVOLUTION_DATA_SERVER_CFLAGS)			\
+	$(GNOME_PLATFORM_CFLAGS)			\
+	$(CHAMPLAIN_CFLAGS)				\
+	$(GTKHTML_CFLAGS)				\
+	$(NULL)
+
+GTKDOC_LIBS = 						\
+	$(top_builddir)/e-util/libeutil.la		\
+	$(EVOLUTION_DATA_SERVER_LIBS)			\
+	$(GNOME_PLATFORM_LIBS)				\
+	$(CHAMPLAIN_LIBS)				\
+	$(GTKHTML_LIBS)					\
+	$(NULL)
+
+# This includes the standard gtk-doc make rules, copied by gtkdocize.
+include $(top_srcdir)/gtk-doc.make
+
+#TESTS = $(GTKDOC_CHECK)
+
+-include $(top_srcdir)/git.mk
diff --git a/doc/reference/libeutil/libeutil-docs.sgml b/doc/reference/libeutil/libeutil-docs.sgml
new file mode 100644
index 0000000..329047b
--- /dev/null
+++ b/doc/reference/libeutil/libeutil-docs.sgml
@@ -0,0 +1,263 @@
+<?xml version="1.0"?>
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN" 
+               "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd"; [
+<!ENTITY % local.common.attrib "xmlns:xi  CDATA  #FIXED 'http://www.w3.org/2003/XInclude'">
+]>
+<book id="index" xmlns:xi="http://www.w3.org/2003/XInclude";>
+  <bookinfo>
+    <title>Evolution Utilities (libeutil)</title>
+    <releaseinfo>
+      The latest version of this documentation can be found on-line at
+      <ulink role="online-location" url="http://library.gnome.org/devel/libeutil/";>http://library.gnome.org/devel/libeutil/</ulink>.
+    </releaseinfo>
+  </bookinfo>
+
+  <chapter>
+    <title>Basic Utility Functions</title>
+    <xi:include href="xml/e-alert.xml"/>
+    <xi:include href="xml/e-datetime-format.xml"/>
+    <xi:include href="xml/e-html-utils.xml"/>
+    <xi:include href="xml/e-misc-utils.xml"/>
+    <xi:include href="xml/e-poolv.xml"/>
+    <xi:include href="xml/e-print.xml"/>
+    <xi:include href="xml/e-selection.xml"/>
+    <xi:include href="xml/e-xml-utils.xml"/>
+    <xi:include href="xml/e-bit-array.xml"/>
+    <xi:include href="xml/e-dialog-utils.xml"/>
+    <xi:include href="xml/e-icon-factory.xml"/>
+    <xi:include href="xml/e-passwords.xml"/>
+  </chapter>
+
+  <chapter>
+    <title>Attachment Management</title>
+    <xi:include href="xml/e-attachment.xml"/>
+    <xi:include href="xml/e-attachment-bar.xml"/>
+    <xi:include href="xml/e-attachment-button.xml"/>
+    <xi:include href="xml/e-attachment-dialog.xml"/>
+    <xi:include href="xml/e-attachment-paned.xml"/>
+    <xi:include href="xml/e-attachment-store.xml"/>
+    <xi:include href="xml/e-attachment-view.xml"/>
+    <xi:include href="xml/e-attachment-icon-view.xml"/>
+    <xi:include href="xml/e-attachment-tree-view.xml"/>
+    <xi:include href="xml/e-attachment-handler.xml"/>
+  </chapter>
+
+  <chapter>
+    <title>Category Management</title>
+    <xi:include href="xml/e-categories-config.xml"/>
+    <xi:include href="xml/e-categories-dialog.xml"/>
+    <xi:include href="xml/e-categories-editor.xml"/>
+    <xi:include href="xml/e-categories-selector.xml"/>
+    <xi:include href="xml/e-category-completion.xml"/>
+    <xi:include href="xml/e-category-editor.xml"/>
+  </chapter>
+
+  <chapter>
+    <title>Filtering and Searching</title>
+    <xi:include href="xml/e-rule-context.xml"/>
+    <xi:include href="xml/e-rule-editor.xml"/>
+    <xi:include href="xml/e-filter-rule.xml"/>
+    <xi:include href="xml/e-filter-part.xml"/>
+    <xi:include href="xml/e-filter-element.xml"/>
+    <xi:include href="xml/e-filter-code.xml"/>
+    <xi:include href="xml/e-filter-color.xml"/>
+    <xi:include href="xml/e-filter-datespec.xml"/>
+    <xi:include href="xml/e-filter-file.xml"/>
+    <xi:include href="xml/e-filter-input.xml"/>
+    <xi:include href="xml/e-filter-int.xml"/>
+    <xi:include href="xml/e-filter-option.xml"/>
+  </chapter>
+
+  <chapter>
+    <title>Tables and Trees</title>
+    <xi:include href="xml/e-table.xml"/>
+    <xi:include href="xml/e-table-click-to-add.xml"/>
+    <xi:include href="xml/e-table-col-dnd.xml"/>
+    <xi:include href="xml/e-table-col.xml"/>
+    <xi:include href="xml/e-table-column-specification.xml"/>
+    <xi:include href="xml/e-table-config.xml"/>
+    <xi:include href="xml/e-table-defines.xml"/>
+    <xi:include href="xml/e-table-extras.xml"/>
+    <xi:include href="xml/e-table-field-chooser-dialog.xml"/>
+    <xi:include href="xml/e-table-field-chooser-item.xml"/>
+    <xi:include href="xml/e-table-field-chooser.xml"/>
+    <xi:include href="xml/e-table-group-container.xml"/>
+    <xi:include href="xml/e-table-group-leaf.xml"/>
+    <xi:include href="xml/e-table-group.xml"/>
+    <xi:include href="xml/e-table-header-item.xml"/>
+    <xi:include href="xml/e-table-header-utils.xml"/>
+    <xi:include href="xml/e-table-header.xml"/>
+    <xi:include href="xml/e-table-item.xml"/>
+    <xi:include href="xml/e-table-memory-callbacks.xml"/>
+    <xi:include href="xml/e-table-memory-store.xml"/>
+    <xi:include href="xml/e-table-memory.xml"/>
+    <xi:include href="xml/e-table-model.xml"/>
+    <xi:include href="xml/e-table-one.xml"/>
+    <xi:include href="xml/e-table-search.xml"/>
+    <xi:include href="xml/e-table-selection-model.xml"/>
+    <xi:include href="xml/e-table-sort-info.xml"/>
+    <xi:include href="xml/e-table-sorted-variable.xml"/>
+    <xi:include href="xml/e-table-sorted.xml"/>
+    <xi:include href="xml/e-table-sorter.xml"/>
+    <xi:include href="xml/e-table-sorting-utils.xml"/>
+    <xi:include href="xml/e-table-specification.xml"/>
+    <xi:include href="xml/e-table-state.xml"/>
+    <xi:include href="xml/e-table-subset-variable.xml"/>
+    <xi:include href="xml/e-table-subset.xml"/>
+    <xi:include href="xml/e-table-utils.xml"/>
+    <xi:include href="xml/e-table-without.xml"/>
+    <xi:include href="xml/e-tree.xml"/>
+    <xi:include href="xml/e-tree-memory-callbacks.xml"/>
+    <xi:include href="xml/e-tree-memory.xml"/>
+    <xi:include href="xml/e-tree-model.xml"/>
+    <xi:include href="xml/e-tree-selection-model.xml"/>
+    <xi:include href="xml/e-tree-sorted.xml"/>
+    <xi:include href="xml/e-tree-table-adapter.xml"/>
+    <xi:include href="xml/e-cell.xml"/>
+    <xi:include href="xml/e-cell-checkbox.xml"/>
+    <xi:include href="xml/e-cell-combo.xml"/>
+    <xi:include href="xml/e-cell-date-edit.xml"/>
+    <xi:include href="xml/e-cell-date.xml"/>
+    <xi:include href="xml/e-cell-hbox.xml"/>
+    <xi:include href="xml/e-cell-number.xml"/>
+    <xi:include href="xml/e-cell-percent.xml"/>
+    <xi:include href="xml/e-cell-pixbuf.xml"/>
+    <xi:include href="xml/e-cell-popup.xml"/>
+    <xi:include href="xml/e-cell-size.xml"/>
+    <xi:include href="xml/e-cell-text.xml"/>
+    <xi:include href="xml/e-cell-toggle.xml"/>
+    <xi:include href="xml/e-cell-tree.xml"/>
+    <xi:include href="xml/e-cell-vbox.xml"/>
+    <xi:include href="xml/e-popup-menu.xml"/>
+  </chapter>
+
+  <chapter>
+    <title>Text Processing</title>
+    <xi:include href="xml/e-text.xml"/>
+    <xi:include href="xml/e-text-model.xml"/>
+    <xi:include href="xml/e-text-model-repos.xml"/>
+    <xi:include href="xml/e-text-event-processor.xml"/>
+    <xi:include href="xml/e-text-event-processor-emacs-like.xml"/>
+    <xi:include href="xml/e-text-event-processor-types.xml"/>
+    <xi:include href="xml/e-reflow.xml"/>
+    <xi:include href="xml/e-reflow-model.xml"/>
+  </chapter>
+
+  <chapter>
+    <title>View Management</title>
+    <xi:include href="xml/gal-view.xml"/>
+    <xi:include href="xml/gal-view-factory.xml"/>
+    <xi:include href="xml/gal-view-instance.xml"/>
+    <xi:include href="xml/gal-view-collection.xml"/>
+    <xi:include href="xml/gal-view-etable.xml"/>
+    <xi:include href="xml/gal-view-factory-etable.xml"/>
+    <xi:include href="xml/gal-view-new-dialog.xml"/>
+    <xi:include href="xml/gal-define-views-dialog.xml"/>
+    <xi:include href="xml/gal-define-views-model.xml"/>
+    <xi:include href="xml/gal-view-instance-save-as-dialog.xml"/>
+  </chapter>
+
+  <chapter>
+    <title>(Unsorted Sections)</title>
+    <xi:include href="xml/e-action-combo-box.xml"/>
+    <xi:include href="xml/e-activity-bar.xml"/>
+    <xi:include href="xml/e-activity-proxy.xml"/>
+    <xi:include href="xml/e-activity.xml"/>
+    <xi:include href="xml/e-alarm-selector.xml"/>
+    <xi:include href="xml/e-alert-bar.xml"/>
+    <xi:include href="xml/e-alert-dialog.xml"/>
+    <xi:include href="xml/e-alert-sink.xml"/>
+    <xi:include href="xml/e-auth-combo-box.xml"/>
+    <xi:include href="xml/e-autocomplete-selector.xml"/>
+    <xi:include href="xml/e-book-source-config.xml"/>
+    <xi:include href="xml/e-buffer-tagger.xml"/>
+    <xi:include href="xml/e-cal-source-config.xml"/>
+    <xi:include href="xml/e-calendar-item.xml"/>
+    <xi:include href="xml/e-calendar.xml"/>
+    <xi:include href="xml/e-canvas-background.xml"/>
+    <xi:include href="xml/e-canvas-utils.xml"/>
+    <xi:include href="xml/e-canvas-vbox.xml"/>
+    <xi:include href="xml/e-canvas.xml"/>
+    <xi:include href="xml/e-cell-renderer-color.xml"/>
+    <xi:include href="xml/e-charset-combo-box.xml"/>
+    <xi:include href="xml/e-charset.xml"/>
+    <xi:include href="xml/e-client-utils.xml"/>
+    <xi:include href="xml/e-config.xml"/>
+    <xi:include href="xml/e-contact-store.xml"/>
+    <xi:include href="xml/e-dateedit.xml"/>
+    <xi:include href="xml/e-destination-store.xml"/>
+    <xi:include href="xml/e-dialog-widgets.xml"/>
+    <xi:include href="xml/e-event.xml"/>
+    <xi:include href="xml/e-file-request.xml"/>
+    <xi:include href="xml/e-file-utils.xml"/>
+    <xi:include href="xml/e-focus-tracker.xml"/>
+    <xi:include href="xml/e-image-chooser.xml"/>
+    <xi:include href="xml/e-import-assistant.xml"/>
+    <xi:include href="xml/e-import.xml"/>
+    <xi:include href="xml/e-interval-chooser.xml"/>
+    <xi:include href="xml/e-mail-identity-combo-box.xml"/>
+    <xi:include href="xml/e-mail-signature-combo-box.xml"/>
+    <xi:include href="xml/e-mail-signature-editor.xml"/>
+    <xi:include href="xml/e-mail-signature-manager.xml"/>
+    <xi:include href="xml/e-mail-signature-preview.xml"/>
+    <xi:include href="xml/e-mail-signature-script-dialog.xml"/>
+    <xi:include href="xml/e-mail-signature-tree-view.xml"/>
+    <xi:include href="xml/e-map.xml"/>
+    <xi:include href="xml/e-menu-tool-action.xml"/>
+    <xi:include href="xml/e-menu-tool-button.xml"/>
+    <xi:include href="xml/e-mktemp.xml"/>
+    <xi:include href="xml/e-name-selector-dialog.xml"/>
+    <xi:include href="xml/e-name-selector-entry.xml"/>
+    <xi:include href="xml/e-name-selector-list.xml"/>
+    <xi:include href="xml/e-name-selector-model.xml"/>
+    <xi:include href="xml/e-name-selector.xml"/>
+    <xi:include href="xml/e-online-button.xml"/>
+    <xi:include href="xml/e-paned.xml"/>
+    <xi:include href="xml/e-picture-gallery.xml"/>
+    <xi:include href="xml/e-plugin-ui.xml"/>
+    <xi:include href="xml/e-plugin.xml"/>
+    <xi:include href="xml/e-popup-action.xml"/>
+    <xi:include href="xml/e-port-entry.xml"/>
+    <xi:include href="xml/e-preferences-window.xml"/>
+    <xi:include href="xml/e-preview-pane.xml"/>
+    <xi:include href="xml/e-printable.xml"/>
+    <xi:include href="xml/e-search-bar.xml"/>
+    <xi:include href="xml/e-selectable.xml"/>
+    <xi:include href="xml/e-selection-model-array.xml"/>
+    <xi:include href="xml/e-selection-model-simple.xml"/>
+    <xi:include href="xml/e-selection-model.xml"/>
+    <xi:include href="xml/e-send-options.xml"/>
+    <xi:include href="xml/e-sorter-array.xml"/>
+    <xi:include href="xml/e-sorter.xml"/>
+    <xi:include href="xml/e-source-combo-box.xml"/>
+    <xi:include href="xml/e-source-config-backend.xml"/>
+    <xi:include href="xml/e-source-config-dialog.xml"/>
+    <xi:include href="xml/e-source-config.xml"/>
+    <xi:include href="xml/e-source-selector-dialog.xml"/>
+    <xi:include href="xml/e-source-selector.xml"/>
+    <xi:include href="xml/e-source-util.xml"/>
+    <xi:include href="xml/e-spell-entry.xml"/>
+    <xi:include href="xml/e-stock-request.xml"/>
+    <xi:include href="xml/e-timezone-dialog.xml"/>
+    <xi:include href="xml/e-tree-model-generator.xml"/>
+    <xi:include href="xml/e-ui-manager.xml"/>
+    <xi:include href="xml/e-unicode.xml"/>
+    <xi:include href="xml/e-url-entry.xml"/>
+    <xi:include href="xml/e-web-view-gtkhtml.xml"/>
+    <xi:include href="xml/e-web-view-preview.xml"/>
+    <xi:include href="xml/e-web-view.xml"/>
+  </chapter>
+
+  <chapter>
+    <title>Object Hierarchy</title>
+    <xi:include href="xml/tree_index.sgml"/>
+  </chapter>
+
+  <index id="api-index-full">
+    <title>Index</title>
+    <xi:include href="xml/api-index-full.xml"><xi:fallback /></xi:include>
+  </index>
+
+  <xi:include href="xml/annotation-glossary.xml"><xi:fallback /></xi:include>
+
+</book>
diff --git a/doc/reference/shell/eshell-overrides.txt b/doc/reference/libeutil/libeutil-overrides.txt
similarity index 100%
rename from doc/reference/shell/eshell-overrides.txt
rename to doc/reference/libeutil/libeutil-overrides.txt
diff --git a/doc/reference/libeutil/libeutil-sections.txt b/doc/reference/libeutil/libeutil-sections.txt
new file mode 100644
index 0000000..98b9398
--- /dev/null
+++ b/doc/reference/libeutil/libeutil-sections.txt
@@ -0,0 +1,1900 @@
+<SECTION>
+<FILE>e-action-combo-box</FILE>
+<TITLE>EActionComboBox</TITLE>
+EActionComboBox
+e_action_combo_box_new
+e_action_combo_box_new_with_action
+e_action_combo_box_get_action
+e_action_combo_box_set_action
+e_action_combo_box_get_current_value
+e_action_combo_box_set_current_value
+e_action_combo_box_add_separator_before
+e_action_combo_box_add_separator_after
+<SUBSECTION Standard>
+E_ACTION_COMBO_BOX
+E_IS_ACTION_COMBO_BOX
+E_TYPE_ACTION_COMBO_BOX
+E_ACTION_COMBO_BOX_CLASS
+E_IS_ACTION_COMBO_BOX_CLASS
+E_ACTION_COMBO_BOX_GET_CLASS
+EActionComboBoxClass
+e_action_combo_box_get_type
+<SUBSECTION Private>
+EActionComboBoxPrivate
+</SECTION>
+
+<SECTION>
+<FILE>e-activity</FILE>
+<TITLE>EActivity</TITLE>
+EActivity
+e_activity_new
+e_activity_describe
+e_activity_get_alert_sink
+e_activity_set_alert_sink
+e_activity_get_cancellable
+e_activity_set_cancellable
+e_activity_get_icon_name
+e_activity_set_icon_name
+e_activity_get_percent
+e_activity_set_percent
+EActivityState
+e_activity_get_state
+e_activity_set_state
+e_activity_get_text
+e_activity_set_text
+<SUBSECTION Standard>
+E_ACTIVITY
+E_IS_ACTIVITY
+E_TYPE_ACTIVITY
+E_ACTIVITY_CLASS
+E_IS_ACTIVITY_CLASS
+E_ACTIVITY_GET_CLASS
+EActivityClass
+e_activity_get_type
+<SUBSECTION Private>
+EActivityPrivate
+</SECTION>
+
+<SECTION>
+<FILE>e-activity-bar</FILE>
+<TITLE>EActivityBar</TITLE>
+EActivityBar
+e_activity_bar_new
+e_activity_bar_get_activity
+e_activity_bar_set_activity
+<SUBSECTION Standard>
+E_ACTIVITY_BAR
+E_IS_ACTIVITY_BAR
+E_TYPE_ACTIVITY_BAR
+E_ACTIVITY_BAR_CLASS
+E_IS_ACTIVITY_BAR_CLASS
+E_ACTIVITY_BAR_GET_CLASS
+EActivityBarClass
+e_activity_bar_get_type
+<SUBSECTION Private>
+EActivityBarPrivate
+</SECTION>
+
+<SECTION>
+<FILE>e-activity-proxy</FILE>
+<TITLE>EActivityProxy</TITLE>
+EActivityProxy
+e_activity_proxy_new
+e_activity_proxy_get_activity
+<SUBSECTION Standard>
+E_ACTIVITY_PROXY
+E_IS_ACTIVITY_PROXY
+E_TYPE_ACTIVITY_PROXY
+E_ACTIVITY_PROXY_CLASS
+E_IS_ACTIVITY_PROXY_CLASS
+E_ACTIVITY_PROXY_GET_CLASS
+EActivityProxyClass
+e_activity_proxy_get_type
+<SUBSECTION Private>
+EActivityProxyPrivate
+</SECTION>
+
+<SECTION>
+<FILE>e-alarm-selector</FILE>
+<TITLE>EAlarmSelector</TITLE>
+EAlarmSelector
+e_alarm_selector_new
+<SUBSECTION Standard>
+E_ALARM_SELECTOR
+E_IS_ALARM_SELECTOR
+E_TYPE_ALARM_SELECTOR
+E_ALARM_SELECTOR_CLASS
+E_IS_ALARM_SELECTOR_CLASS
+E_ALARM_SELECTOR_GET_CLASS
+EAlarmSelectorClass
+e_alarm_selector_get_type
+<SUBSECTION Private>
+EAlarmSelectorPrivate
+</SECTION>
+
+<SECTION>
+<FILE>e-alert</FILE>
+<TITLE>EAlert</TITLE>
+E_ALERT_ASK_FILE_EXISTS_OVERWRITE
+E_ALERT_NO_SAVE_FILE
+E_ALERT_NO_LOAD_FILE
+EAlert
+e_alert_new
+e_alert_new_valist
+e_alert_get_default_response
+e_alert_set_default_response
+e_alert_get_message_type
+e_alert_set_message_type
+e_alert_get_primary_text
+e_alert_set_primary_text
+e_alert_get_secondary_text
+e_alert_set_secondary_text
+e_alert_get_stock_id
+e_alert_add_action
+e_alert_peek_actions
+e_alert_create_image
+e_alert_response
+e_alert_start_timer
+e_alert_submit
+e_alert_submit_valist
+<SUBSECTION Standard>
+E_ALERT
+E_IS_ALERT
+E_TYPE_ALERT
+E_ALERT_CLASS
+E_IS_ALERT_CLASS
+E_ALERT_GET_CLASS
+EAlertClass
+e_alert_get_type
+<SUBSECTION Private>
+EAlertPrivate
+</SECTION>
+
+<SECTION>
+<FILE>e-alert-bar</FILE>
+<TITLE>EAlertBar</TITLE>
+EAlertBar
+e_alert_bar_new
+e_alert_bar_clear
+e_alert_bar_add_alert
+<SUBSECTION Standard>
+E_ALERT_BAR
+E_IS_ALERT_BAR
+E_TYPE_ALERT_BAR
+E_ALERT_BAR_CLASS
+E_IS_ALERT_BAR_CLASS
+E_ALERT_BAR_GET_CLASS
+EAlertBarClass
+e_alert_bar_get_type
+<SUBSECTION Private>
+EAlertBarPrivate
+</SECTION>
+
+<SECTION>
+<FILE>e-alert-dialog</FILE>
+<TITLE>EAlertDialog</TITLE>
+EAlertDialog
+e_alert_dialog_new
+e_alert_dialog_new_for_args
+e_alert_run_dialog
+e_alert_run_dialog_for_args
+e_alert_dialog_get_alert
+e_alert_dialog_get_content_area
+<SUBSECTION Standard>
+E_ALERT_DIALOG
+E_IS_ALERT_DIALOG
+E_TYPE_ALERT_DIALOG
+E_ALERT_DIALOG_CLASS
+E_IS_ALERT_DIALOG_CLASS
+E_ALERT_DIALOG_GET_CLASS
+EAlertDialogClass
+e_alert_dialog_get_type
+<SUBSECTION Private>
+EAlertDialogPrivate
+</SECTION>
+
+<SECTION>
+<FILE>e-alert-sink</FILE>
+<TITLE>EAlertSink</TITLE>
+EAlertSink
+EAlertSinkInterface
+e_alert_sink_submit_alert
+<SUBSECTION Standard>
+E_ALERT_SINK
+E_IS_ALERT_SINK
+E_TYPE_ALERT_SINK
+E_ALERT_SINK_INTERFACE
+E_IS_ALERT_SINK_INTERFACE
+E_ALERT_SINK_GET_INTERFACE
+e_alert_sink_get_type
+</SECTION>
+
+<SECTION>
+<FILE>e-attachment</FILE>
+<TITLE>EAttachment</TITLE>
+EAttachment
+e_attachment_new
+e_attachment_new_for_path
+e_attachment_new_for_uri
+e_attachment_new_for_message
+e_attachment_add_to_multipart
+e_attachment_cancel
+e_attachment_get_can_show
+e_attachment_set_can_show
+e_attachment_get_disposition
+e_attachment_set_disposition
+e_attachment_get_file
+e_attachment_set_file
+e_attachment_get_file_info
+e_attachment_set_file_info
+e_attachment_get_icon
+e_attachment_get_loading
+e_attachment_get_mime_part
+e_attachment_set_mime_part
+e_attachment_get_percent
+e_attachment_get_reference
+e_attachment_set_reference
+e_attachment_get_saving
+e_attachment_get_shown
+e_attachment_set_shown
+e_attachment_get_encrypted
+e_attachment_set_encrypted
+e_attachment_get_signed
+e_attachment_set_signed
+e_attachment_get_description
+e_attachment_get_thumbnail_path
+e_attachment_is_rfc822
+e_attachment_list_apps
+e_attachment_load_async
+e_attachment_load_finish
+e_attachment_open_async
+e_attachment_open_finish
+e_attachment_save_async
+e_attachment_save_finish
+e_attachment_load_handle_error
+e_attachment_open_handle_error
+e_attachment_save_handle_error
+<SUBSECTION Standard>
+E_ATTACHMENT
+E_IS_ATTACHMENT
+E_TYPE_ATTACHMENT
+E_ATTACHMENT_CLASS
+E_IS_ATTACHMENT_CLASS
+E_ATTACHMENT_GET_CLASS
+EAttachmentClass
+e_attachment_get_type
+<SUBSECTION Private>
+EAttachmentPrivate
+</SECTION>
+
+<SECTION>
+<FILE>e-attachment-bar</FILE>
+<TITLE>EAttachmentBar</TITLE>
+EAttachmentBar
+e_attachment_bar_new
+e_attachment_bar_get_active_view
+e_attachment_bar_set_active_view
+e_attachment_bar_get_expanded
+e_attachment_bar_set_expanded
+e_attachment_bar_get_store
+<SUBSECTION Standard>
+E_ATTACHMENT_BAR
+E_IS_ATTACHMENT_BAR
+E_TYPE_ATTACHMENT_BAR
+E_ATTACHMENT_BAR_CLASS
+E_IS_ATTACHMENT_BAR_CLASS
+E_ATTACHMENT_BAR_GET_CLASS
+EAttachmentBarClass
+e_attachment_bar_get_type
+<SUBSECTION Private>
+EAttachmentBarPrivate
+</SECTION>
+
+<SECTION>
+<FILE>e-attachment-button</FILE>
+<TITLE>EAttachmentButton</TITLE>
+EAttachmentButton
+e_attachment_button_new
+e_attachment_button_get_view
+e_attachment_button_get_attachment
+e_attachment_button_set_attachment
+e_attachment_button_get_expandable
+e_attachment_button_set_expandable
+e_attachment_button_get_expanded
+e_attachment_button_set_expanded
+<SUBSECTION Standard>
+E_ATTACHMENT_BUTTON
+E_IS_ATTACHMENT_BUTTON
+E_TYPE_ATTACHMENT_BUTTON
+E_ATTACHMENT_BUTTON_CLASS
+E_IS_ATTACHMENT_BUTTON_CLASS
+E_ATTACHMENT_BUTTON_GET_CLASS
+EAttachmentButtonClass
+e_attachment_button_get_type
+<SUBSECTION Private>
+EAttachmentButtonPrivate
+</SECTION>
+
+<SECTION>
+<FILE>e-attachment-dialog</FILE>
+<TITLE>EAttachmentDialog</TITLE>
+EAttachmentDialog
+e_attachment_dialog_new
+e_attachment_dialog_get_attachment
+e_attachment_dialog_set_attachment
+<SUBSECTION Standard>
+E_ATTACHMENT_DIALOG
+E_IS_ATTACHMENT_DIALOG
+E_TYPE_ATTACHMENT_DIALOG
+E_ATTACHMENT_DIALOG_CLASS
+E_IS_ATTACHMENT_DIALOG_CLASS
+E_ATTACHMENT_DIALOG_GET_CLASS
+EAttachmentDialogClass
+e_attachment_dialog_get_type
+<SUBSECTION Private>
+EAttachmentDialogPrivate
+</SECTION>
+
+<SECTION>
+<FILE>e-attachment-handler</FILE>
+<TITLE>EAttachmentHandler</TITLE>
+EAttachmentHandler
+EAttachmentHandlerImage
+EAttachmentHandlerSendto
+e_attachment_handler_get_view
+e_attachment_handler_get_drag_actions
+e_attachment_handler_get_target_table
+<SUBSECTION Standard>
+E_ATTACHMENT_HANDLER
+E_IS_ATTACHMENT_HANDLER
+E_TYPE_ATTACHMENT_HANDLER
+E_ATTACHMENT_HANDLER_CLASS
+E_IS_ATTACHMENT_HANDLER_CLASS
+E_ATTACHMENT_HANDLER_GET_CLASS
+E_ATTACHMENT_HANDLER_IMAGE
+E_IS_ATTACHMENT_HANDLER_IMAGE
+E_TYPE_ATTACHMENT_HANDLER_IMAGE
+E_ATTACHMENT_HANDLER_IMAGE_CLASS
+E_IS_ATTACHMENT_HANDLER_IMAGE_CLASS
+E_ATTACHMENT_HANDLER_IMAGE_GET_CLASS
+E_ATTACHMENT_HANDLER_SENDTO
+E_IS_ATTACHMENT_HANDLER_SENDTO
+E_TYPE_ATTACHMENT_HANDLER_SENDTO
+E_ATTACHMENT_HANDLER_SENDTO_CLASS
+E_IS_ATTACHMENT_HANDLER_SENDTO_CLASS
+E_ATTACHMENT_HANDLER_SENDTO_GET_CLASS
+EAttachmentHandlerClass
+EAttachmentHandlerImageClass
+EAttachmentHandlerSendtoClass
+e_attachment_handler_get_type
+e_attachment_handler_image_get_type
+e_attachment_handler_sendto_get_type
+<SUBSECTION Private>
+EAttachmentHandlerPrivate
+EAttachmentHandlerImagePrivate
+EAttachmentHandlerSendtoPrivate
+</SECTION>
+
+<SECTION>
+<FILE>e-attachment-icon-view</FILE>
+<TITLE>EAttachmentIconView</TITLE>
+EAttachmentIconView
+e_attachment_icon_view_new
+e_attachment_icon_view_set_default_icon_size
+<SUBSECTION Standard>
+E_ATTACHMENT_ICON_VIEW
+E_IS_ATTACHMENT_ICON_VIEW
+E_TYPE_ATTACHMENT_ICON_VIEW
+E_ATTACHMENT_ICON_VIEW_CLASS
+E_IS_ATTACHMENT_ICON_VIEW_CLASS
+E_ATTACHMENT_ICON_VIEW_GET_CLASS
+EAttachmentIconViewClass
+e_attachment_icon_view_get_type
+<SUBSECTION Private>
+EAttachmentIconViewPrivate
+</SECTION>
+
+<SECTION>
+<FILE>e-attachment-paned</FILE>
+<TITLE>EAttachmentPaned</TITLE>
+EAttachmentPaned
+e_attachment_paned_new
+e_attachment_paned_get_content_area
+e_attachment_paned_get_active_view
+e_attachment_paned_set_active_view
+e_attachment_paned_get_expanded
+e_attachment_paned_set_expanded
+e_attachment_paned_drag_data_received
+e_attachment_paned_get_controls_container
+e_attachment_paned_get_view_combo
+e_attachment_paned_set_default_height
+<SUBSECTION Standard>
+E_ATTACHMENT_PANED
+E_IS_ATTACHMENT_PANED
+E_TYPE_ATTACHMENT_PANED
+E_ATTACHMENT_PANED_CLASS
+E_IS_ATTACHMENT_PANED_CLASS
+E_ATTACHMENT_PANED_GET_CLASS
+EAttachmentPanedClass
+e_attachment_paned_get_type
+<SUBSECTION Private>
+EAttachmentPanedPrivate
+</SECTION>
+
+<SECTION>
+<FILE>e-attachment-store</FILE>
+<TITLE>EAttachmentStore</TITLE>
+EAttachmentStore
+e_attachment_store_new
+e_attachment_store_add_attachment
+e_attachment_store_remove_attachment
+e_attachment_store_add_to_multipart
+e_attachment_store_get_attachments
+e_attachment_store_get_num_attachments
+e_attachment_store_get_num_loading
+e_attachment_store_get_total_size
+e_attachment_store_run_load_dialog
+e_attachment_store_run_save_dialog
+e_attachment_store_get_uris_async
+e_attachment_store_get_uris_finish
+e_attachment_store_load_async
+e_attachment_store_load_finish
+e_attachment_store_save_async
+e_attachment_store_save_finish
+<SUBSECTION Standard>
+E_ATTACHMENT_STORE
+E_IS_ATTACHMENT_STORE
+E_TYPE_ATTACHMENT_STORE
+E_ATTACHMENT_STORE_CLASS
+E_IS_ATTACHMENT_STORE_CLASS
+E_ATTACHMENT_STORE_GET_CLASS
+EAttachmentStoreClass
+e_attachment_store_get_type
+<SUBSECTION Private>
+EAttachmentStorePrivate
+</SECTION>
+
+<SECTION>
+<FILE>e-attachment-tree-view</FILE>
+<TITLE>EAttachmentTreeView</TITLE>
+EAttachmentTreeView
+e_attachment_tree_view_new
+<SUBSECTION Standard>
+E_ATTACHMENT_TREE_VIEW
+E_IS_ATTACHMENT_TREE_VIEW
+E_TYPE_ATTACHMENT_TREE_VIEW
+E_ATTACHMENT_TREE_VIEW_CLASS
+E_IS_ATTACHMENT_TREE_VIEW_CLASS
+E_ATTACHMENT_TREE_VIEW_GET_CLASS
+EAttachmentTreeViewClass
+e_attachment_tree_view_get_type
+<SUBSECTION Private>
+EAttachmentTreeViewPrivate
+</SECTION>
+
+<SECTION>
+<FILE>e-attachment-view</FILE>
+<TITLE>EAttachmentView</TITLE>
+EAttachmentView
+e_attachment_view_init
+e_attachment_view_dispose
+e_attachment_view_finalize
+e_attachment_view_get_private
+e_attachment_view_get_store
+e_attachment_view_get_editable
+e_attachment_view_set_editable
+e_attachment_view_get_target_list
+e_attachment_view_get_drag_actions
+e_attachment_view_get_selected_attachments
+e_attachment_view_open_path
+e_attachment_view_remove_selected
+e_attachment_view_button_press_event
+e_attachment_view_button_release_event
+e_attachment_view_motion_notify_event
+e_attachment_view_key_press_event
+e_attachment_view_get_path_at_pos
+e_attachment_view_get_selected_paths
+e_attachment_view_path_is_selected
+e_attachment_view_select_path
+e_attachment_view_unselect_path
+e_attachment_view_select_all
+e_attachment_view_unselect_all
+e_attachment_view_sync_selection
+e_attachment_view_drag_source_set
+e_attachment_view_drag_source_unset
+e_attachment_view_drag_begin
+e_attachment_view_drag_end
+e_attachment_view_drag_data_get
+e_attachment_view_drag_dest_set
+e_attachment_view_drag_dest_unset
+e_attachment_view_drag_motion
+e_attachment_view_drag_drop
+e_attachment_view_drag_data_received
+e_attachment_view_get_action
+e_attachment_view_add_action_group
+e_attachment_view_get_action_group
+e_attachment_view_get_popup_menu
+e_attachment_view_get_ui_manager
+e_attachment_view_show_popup_menu
+e_attachment_view_update_actions
+<SUBSECTION Standard>
+E_ATTACHMENT_VIEW
+E_IS_ATTACHMENT_VIEW
+E_TYPE_ATTACHMENT_VIEW
+E_ATTACHMENT_VIEW_IFACE
+E_IS_ATTACHMENT_VIEW_IFACE
+E_ATTACHMENT_VIEW_GET_IFACE
+EAttachmentViewIface
+e_attachment_view_get_type
+<SUBSECTION Private>
+EAttachmentViewPrivate
+</SECTION>
+
+<SECTION>
+<FILE>e-auth-combo-box</FILE>
+<TITLE>EAuthComboBox</TITLE>
+EAuthComboBox
+e_auth_combo_box_new
+e_auth_combo_box_get_provider
+e_auth_combo_box_set_provider
+e_auth_combo_box_update_available
+<SUBSECTION Standard>
+E_AUTH_COMBO_BOX
+E_IS_AUTH_COMBO_BOX
+E_TYPE_AUTH_COMBO_BOX
+E_AUTH_COMBO_BOX_CLASS
+E_IS_AUTH_COMBO_BOX_CLASS
+E_AUTH_COMBO_BOX_GET_CLASS
+EAuthComboBoxClass
+e_auth_combo_box_get_type
+<SUBSECTION Private>
+EAuthComboBoxPrivate
+</SECTION>
+
+<SECTION>
+<FILE>e-autocomplete-selector</FILE>
+<TITLE>EAutocompleteSelector</TITLE>
+EAutocompleteSelector
+e_autocomplete_selector_new
+<SUBSECTION Standard>
+E_AUTOCOMPLETE_SELECTOR
+E_IS_AUTOCOMPLETE_SELECTOR
+E_TYPE_AUTOCOMPLETE_SELECTOR
+E_AUTOCOMPLETE_SELECTOR_CLASS
+E_IS_AUTOCOMPLETE_SELECTOR_CLASS
+E_AUTOCOMPLETE_SELECTOR_GET_CLASS
+EAutocompleteSelectorClass
+e_autocomplete_selector_get_type
+<SUBSECTION Private>
+EAutocompleteSelectorPrivate
+</SECTION>
+
+<SECTION>
+<FILE>e-bit-array</FILE>
+<TITLE>Bit Arrays (Legacy)</TITLE>
+EBitArray
+e_bit_array_new
+e_bit_array_value_at
+e_bit_array_foreach
+e_bit_array_selected_count
+e_bit_array_select_all
+e_bit_array_invert_selection
+e_bit_array_bit_count
+e_bit_array_change_one_row
+e_bit_array_change_range
+e_bit_array_select_single_row
+e_bit_array_toggle_single_row
+e_bit_array_insert
+e_bit_array_delete
+e_bit_array_delete_single_mode
+e_bit_array_move_row
+<SUBSECTION Standard>
+E_BIT_ARRAY
+E_IS_BIT_ARRAY
+E_BIT_ARRAY_TYPE
+E_BIT_ARRAY_CLASS
+E_IS_BIT_ARRAY_CLASS
+EBitArrayClass
+e_bit_array_get_type
+</SECTION>
+
+<SECTION>
+<FILE>e-book-source-config</FILE>
+<TITLE>EBookSourceConfig</TITLE>
+EBookSourceConfig
+e_book_source_config_new
+e_book_source_config_add_offline_toggle
+<SUBSECTION Standard>
+E_BOOK_SOURCE_CONFIG
+E_IS_BOOK_SOURCE_CONFIG
+E_TYPE_BOOK_SOURCE_CONFIG
+E_BOOK_SOURCE_CONFIG_CLASS
+E_IS_BOOK_SOURCE_CONFIG_CLASS
+E_BOOK_SOURCE_CONFIG_GET_CLASS
+EBookSourceConfigClass
+e_book_source_config_get_type
+<SUBSECTION Private>
+EBookSourceConfigPrivate
+</SECTION>
+
+<SECTION>
+<FILE>e-cal-source-config</FILE>
+<TITLE>ECalSourceConfig</TITLE>
+ECalSourceConfig
+e_cal_source_config_new
+e_cal_source_config_get_source_type
+e_cal_source_config_add_offline_toggle
+<SUBSECTION Standard>
+E_CAL_SOURCE_CONFIG
+E_IS_CAL_SOURCE_CONFIG
+E_TYPE_CAL_SOURCE_CONFIG
+E_CAL_SOURCE_CONFIG_CLASS
+E_IS_CAL_SOURCE_CONFIG_CLASS
+E_CAL_SOURCE_CONFIG_GET_CLASS
+ECalSourceConfigClass
+e_cal_source_config_get_type
+<SUBSECTION Private>
+ECalSourceConfigPrivate
+</SECTION>
+
+<SECTION>
+<FILE>e-calendar</FILE>
+<TITLE>ECalendar</TITLE>
+ECalendar
+e_calendar_new
+e_calendar_set_minimum_size
+e_calendar_set_maximum_size
+e_calendar_get_border_size
+e_calendar_set_focusable
+<SUBSECTION Standard>
+E_CALENDAR
+E_IS_CALENDAR
+E_TYPE_CALENDAR
+E_CALENDAR_CLASS
+E_IS_CALENDAR_CLASS
+E_CALENDAR_GET_CLASS
+ECalendarClass
+e_calendar_get_type
+</SECTION>
+
+<SECTION>
+<FILE>e-calendar-item</FILE>
+<TITLE>ECalendarItem</TITLE>
+E_CALENDAR_ITEM_YPAD_ABOVE_MONTH_NAME
+E_CALENDAR_ITEM_YPAD_BELOW_MONTH_NAME
+E_CALENDAR_ITEM_MARK_BOLD
+E_CALENDAR_ITEM_MARK_ITALIC
+E_CALENDAR_ITEM_MIN_CELL_XPAD
+E_CALENDAR_ITEM_MIN_CELL_YPAD
+E_CALENDAR_ITEM_YPAD_ABOVE_DAY_LETTERS
+E_CALENDAR_ITEM_YPAD_BELOW_DAY_LETTERS
+E_CALENDAR_ITEM_YPAD_ABOVE_CELLS
+E_CALENDAR_ITEM_YPAD_BELOW_CELLS
+E_CALENDAR_ITEM_XPAD_BEFORE_MONTH_NAME_WITH_BUTTON
+E_CALENDAR_ITEM_XPAD_BEFORE_MONTH_NAME
+E_CALENDAR_ITEM_XPAD_AFTER_MONTH_NAME
+E_CALENDAR_ITEM_XPAD_AFTER_MONTH_NAME_WITH_BUTTON
+E_CALENDAR_ITEM_XPAD_BEFORE_WEEK_NUMBERS
+E_CALENDAR_ITEM_XPAD_AFTER_WEEK_NUMBERS
+E_CALENDAR_ITEM_XPAD_BEFORE_CELLS
+E_CALENDAR_ITEM_XPAD_AFTER_CELLS
+ECalendarItemColors
+ECalendarItem
+e_calendar_item_get_first_month
+e_calendar_item_set_first_month
+e_calendar_item_get_max_days_sel
+e_calendar_item_set_max_days_sel
+e_calendar_item_get_days_start_week_sel
+e_calendar_item_set_days_start_week_sel
+e_calendar_item_get_display_popup
+e_calendar_item_set_display_popup
+e_calendar_item_get_date_range
+e_calendar_item_get_selection
+e_calendar_item_set_selection
+e_calendar_item_clear_marks
+e_calendar_item_mark_day
+e_calendar_item_mark_days
+ECalendarItemStyleCallback
+e_calendar_item_set_style_callback
+ECalendarItemGetTimeCallback
+e_calendar_item_set_get_time_callback
+e_calendar_item_normalize_date
+e_calendar_item_get_week_number
+e_calendar_item_style_set
+<SUBSECTION Standard>
+E_CALENDAR_ITEM
+E_IS_CALENDAR_ITEM
+E_TYPE_CALENDAR_ITEM
+E_CALENDAR_ITEM_CLASS
+E_IS_CALENDAR_ITEM_CLASS
+E_CALENDAR_ITEM_GET_CLASS
+ECalendarItemClass
+e_calendar_item_get_type
+</SECTION>
+
+<SECTION>
+<FILE>e-canvas</FILE>
+<TITLE>ECanvas</TITLE>
+ECanvasItemSelectionFunc
+ECanvasItemSelectionCompareFunc
+ECanvasSelectionInfo
+ECanvas
+e_canvas_new
+e_canvas_item_grab_focus
+e_canvas_item_request_reflow
+e_canvas_item_request_parent_reflow
+ECanvasItemReflowFunc
+e_canvas_item_set_reflow_callback
+ECanvasItemGrabCancelled
+e_canvas_item_grab
+e_canvas_item_ungrab
+<SUBSECTION Standard>
+E_CANVAS
+E_IS_CANVAS
+E_TYPE_CANVAS
+E_CANVAS_CLASS
+E_IS_CANVAS_CLASS
+E_TYPE_CANVAS_CLASS
+ECanvasClass
+e_canvas_get_type
+</SECTION>
+
+<SECTION>
+<FILE>e-canvas-background</FILE>
+<TITLE>ECanvasBackground</TITLE>
+ECanvasBackground
+<SUBSECTION Standard>
+E_CANVAS_BACKGROUND
+E_IS_CANVAS_BACKGROUND
+E_TYPE_CANVAS_BACKGROUND
+E_CANVAS_BACKGROUND_CLASS
+E_IS_CANVAS_BACKGROUND_CLASS
+E_CANVAS_BACKGROUND_GET_CLASS
+ECanvasBackgroundClass
+e_canvas_background_get_type
+<SUBSECTION Private>
+ECanvasBackgroundPrivate
+</SECTION>
+
+<SECTION>
+<FILE>e-canvas-vbox</FILE>
+<TITLE>ECanvasVbox</TITLE>
+ECanvasVbox
+e_canvas_vbox_add_item
+e_canvas_vbox_add_item_start
+<SUBSECTION Standard>
+E_CANVAS_VBOX
+E_IS_CANVAS_VBOX
+E_TYPE_CANVAS_VBOX
+E_CANVAS_VBOX_CLASS
+E_IS_CANVAS_VBOX_CLASS
+E_CANVAS_VBOX_GET_CLASS
+ECanvasVboxClass
+e_canvas_vbox_get_type
+</SECTION>
+
+<SECTION>
+<FILE>e-categories-config</FILE>
+<TITLE>Categories</TITLE>
+e_categories_config_get_icon_for
+e_categories_config_open_dialog_for_entry
+</SECTION>
+
+<SECTION>
+<FILE>e-categories-dialog</FILE>
+<TITLE>ECategoriesDialog</TITLE>
+ECategoriesDialog
+e_categories_dialog_new
+e_categories_dialog_get_categories
+e_categories_dialog_set_categories
+<SUBSECTION Standard>
+E_CATEGORIES_DIALOG
+E_IS_CATEGORIES_DIALOG
+E_TYPE_CATEGORIES_DIALOG
+E_CATEGORIES_DIALOG_CLASS
+E_IS_CATEGORIES_DIALOG_CLASS
+E_CATEGORIES_DIALOG_GET_CLASS
+ECategoriesDialogClass
+e_categories_dialog_get_type
+<SUBSECTION Private>
+ECategoriesDialogPrivate
+</SECTION>
+
+<SECTION>
+<FILE>e-categories-editor</FILE>
+<TITLE>ECategoriesEditor</TITLE>
+ECategoriesEditor
+e_categories_editor_new
+e_categories_editor_get_categories
+e_categories_editor_set_categories
+e_categories_editor_get_entry_visible
+e_categories_editor_set_entry_visible
+<SUBSECTION Standard>
+E_CATEGORIES_EDITOR
+E_IS_CATEGORIES_EDITOR
+E_TYPE_CATEGORIES_EDITOR
+E_CATEGORIES_EDITOR_CLASS
+E_IS_CATEGORIES_EDITOR_CLASS
+E_CATEGORIES_EDITOR_GET_CLASS
+ECategoriesEditorClass
+e_categories_editor_get_type
+<SUBSECTION Private>
+ECategoriesEditorPrivate
+</SECTION>
+
+<SECTION>
+<FILE>e-categories-selector</FILE>
+<TITLE>ECategoriesSelector</TITLE>
+ECategoriesSelector
+e_categories_selector_new
+e_categories_selector_get_checked
+e_categories_selector_set_checked
+e_categories_selector_get_items_checkable
+e_categories_selector_set_items_checkable
+e_categories_selector_delete_selection
+e_categories_selector_get_selected
+<SUBSECTION Standard>
+E_CATEGORIES_SELECTOR
+E_IS_CATEGORIES_SELECTOR
+E_TYPE_CATEGORIES_SELECTOR
+E_CATEGORIES_SELECTOR_CLASS
+E_IS_CATEGORIES_SELECTOR_CLASS
+E_CATEGORIES_SELECTOR_GET_CLASS
+ECategoriesSelectorClass
+e_categories_selector_get_type
+<SUBSECTION Private>
+ECategoriesSelectorPrivate
+</SECTION>
+
+<SECTION>
+<FILE>e-category-completion</FILE>
+<TITLE>ECategoryCompletion</TITLE>
+ECategoryCompletion
+e_category_completion_new
+<SUBSECTION Standard>
+E_TYPE_CATEGORY_COMPLETION
+E_CATEGORY_COMPLETION
+E_CATEGORY_COMPLETION_CLASS
+E_IS_CATEGORY_COMPLETION
+E_IS_CATEGORY_COMPLETION_CLASS
+E_CATEGORY_COMPLETION_GET_CLASS
+ECategoryCompletionClass
+e_category_completion_get_type
+<SUBSECTION Private>
+ECategoryCompletionPrivate
+</SECTION>
+
+<SECTION>
+<FILE>e-category-editor</FILE>
+<TITLE>ECategoryEditor</TITLE>
+ECategoryEditor
+e_category_editor_new
+e_category_editor_create_category
+e_category_editor_edit_category
+<SUBSECTION Standard>
+E_CATEGORY_EDITOR
+E_IS_CATEGORY_EDITOR
+E_TYPE_CATEGORY_EDITOR
+E_CATEGORY_EDITOR_CLASS
+E_IS_CATEGORY_EDITOR_CLASS
+E_CATEGORY_EDITOR_GET_CLASS
+ECategoryEditorClass
+e_category_editor_get_type
+<SUBSECTION Private>
+ECategoryEditorPrivate
+</SECTION>
+
+<SECTION>
+<FILE>e-cell-renderer-color</FILE>
+<TITLE>ECellRendererColor</TITLE>
+ECellRendererColor
+e_cell_renderer_color_new
+<SUBSECTION Standard>
+E_TYPE_CELL_RENDERER_COLOR
+E_CELL_RENDERER_COLOR
+E_CELL_RENDERER_COLOR_CLASS
+E_IS_CELL_RENDERER_COLOR
+E_IS_CELL_RENDERER_COLOR_CLASS
+E_CELL_RENDERER_COLOR_GET_CLASS
+ECellRendererColorClass
+e_cell_renderer_color_get_type
+<SUBSECTION Private>
+ECellRendererColorPrivate
+</SECTION>
+
+<SECTION>
+<FILE>e-charset-combo-box</FILE>
+<TITLE>ECharsetComboBox</TITLE>
+ECharsetComboBox
+e_charset_combo_box_new
+e_charset_combo_box_get_charset
+e_charset_combo_box_set_charset
+<SUBSECTION Standard>
+E_CHARSET_COMBO_BOX
+E_IS_CHARSET_COMBO_BOX
+E_TYPE_CHARSET_COMBO_BOX
+E_CHARSET_COMBO_BOX_CLASS
+E_IS_CHARSET_COMBO_BOX_CLASS
+E_CHARSET_COMBO_BOX_GET_CLASS
+ECharsetComboBoxClass
+e_charset_combo_box_get_type
+<SUBSECTION Private>
+ECharsetComboBoxPrivate
+</SECTION>
+
+<SECTION>
+<FILE>e-client-utils</FILE>
+<TITLE>EClient Utilities</TITLE>
+EClientSourceType
+e_client_utils_new
+e_client_utils_open_new
+e_client_utils_open_new_finish
+</SECTION>
+
+<SECTION>
+<FILE>e-contact-store</FILE>
+<TITLE>EContactStore</TITLE>
+EContactStore
+e_contact_store_new
+e_contact_store_get_client
+e_contact_store_get_contact
+e_contact_store_find_contact
+e_contact_store_get_clients
+e_contact_store_add_client
+e_contact_store_remove_client
+e_contact_store_set_query
+e_contact_store_peek_query
+<SUBSECTION Standard>
+E_CONTACT_STORE
+E_IS_CONTACT_STORE
+E_TYPE_CONTACT_STORE
+E_CONTACT_STORE_CLASS
+E_IS_CONTACT_STORE_CLASS
+E_CONTACT_STORE_GET_CLASS
+EContactStoreClass
+e_contact_store_get_type
+<SUBSECTION Private>
+EContactStorePrivate
+</SECTION>
+
+<SECTION>
+<FILE>e-date-edit</FILE>
+<TITLE>EDateEdit</TITLE>
+EDateEditGetTimeCallback
+e_date_edit_new
+e_date_edit_set_editable
+e_date_edit_date_is_valid
+e_date_edit_time_is_valid
+e_date_edit_get_time
+e_date_edit_set_time
+e_date_edit_get_date
+e_date_edit_set_date
+e_date_edit_get_time_of_day
+e_date_edit_set_time_of_day
+e_date_edit_set_date_and_time_of_day
+e_date_edit_get_show_date
+e_date_edit_set_show_date
+e_date_edit_get_show_time
+e_date_edit_set_show_time
+e_date_edit_get_week_start_day
+e_date_edit_set_week_start_day
+e_date_edit_get_show_week_numbers
+e_date_edit_set_show_week_numbers
+e_date_edit_get_use_24_hour_format
+e_date_edit_set_use_24_hour_format
+e_date_edit_get_allow_no_date_set
+e_date_edit_set_allow_no_date_set
+e_date_edit_get_time_popup_range
+e_date_edit_set_time_popup_range
+e_date_edit_get_make_time_insensitive
+e_date_edit_set_make_time_insensitive
+e_date_edit_get_twodigit_year_can_future
+e_date_edit_set_twodigit_year_can_future
+e_date_edit_set_get_time_callback
+e_date_edit_get_entry
+<SUBSECTION Standard>
+E_DATE_EDIT
+E_IS_DATE_EDIT
+E_TYPE_DATE_EDIT
+E_DATE_EDIT_CLASS
+E_IS_DATE_EDIT_CLASS
+E_DATE_EDIT_GET_CLASS
+EDateEditClass
+e_date_edit_get_type
+<SUBSECTION Private>
+EDateEditPrivate
+</SECTION>
+
+<SECTION>
+<FILE>e-datetime-format</FILE>
+<TITLE>Date and Time Formatting</TITLE>
+DTFormatKind
+e_datetime_format_add_setup_widget
+e_datetime_format_format
+e_datetime_format_format_tm
+</SECTION>
+
+<SECTION>
+<FILE>e-destination-store</FILE>
+<TITLE>EDestinationStore</TITLE>
+EDestinationStore
+EDestinationStoreColumnType
+e_destination_store_new
+e_destination_store_get_destination
+e_destination_store_list_destinations
+e_destination_store_insert_destination
+e_destination_store_append_destination
+e_destination_store_remove_destination
+e_destination_store_remove_destination_nth
+e_destination_store_get_destination_count
+e_destination_store_get_path
+e_destination_store_get_stamp
+<SUBSECTION Standard>
+E_DESTINATION_STORE
+E_IS_DESTINATION_STORE
+E_TYPE_DESTINATION_STORE
+E_DESTINATION_STORE_CLASS
+E_IS_DESTINATION_STORE_CLASS
+E_DESTINATION_STORE_GET_CLASS
+EDestinationStoreClass
+e_destination_store_get_type
+<SUBSECTION Private>
+EDestinationStorePrivate
+</SECTION>
+
+<SECTION>
+<FILE>e-dialog-utils</FILE>
+<TITLE>Dialog Utilities (Legacy)</TITLE>
+e_notice
+e_dialog_combo_box_set
+e_dialog_combo_box_get
+</SECTION>
+
+<SECTION>
+<FILE>e-file-request</FILE>
+<TITLE>EFileRequest</TITLE>
+EFileRequest
+<SUBSECTION Standard>
+E_FILE_REQUEST
+E_IS_FILE_REQUEST
+E_TYPE_FILE_REQUEST
+E_FILE_REQUEST_CLASS
+E_IS_FILE_REQUEST_CLASS
+E_FILE_REQUEST_GET_CLASS
+EFileRequestClass
+e_file_request_get_type
+<SUBSECTION Private>
+EFileRequestPrivate
+</SECTION>
+
+<SECTION>
+<FILE>e-filter-code</FILE>
+<TITLE>EFilterCode</TITLE>
+EFilterCode
+e_filter_code_new
+<SUBSECTION Standard>
+E_FILTER_CODE
+E_IS_FILTER_CODE
+E_TYPE_FILTER_CODE
+E_FILTER_CODE_CLASS
+E_IS_FILTER_CODE_CLASS
+E_FILTER_CODE_GET_CLASS
+EFilterCodeClass
+e_filter_code_get_type
+<SUBSECTION Private>
+EFilterCodePrivate
+</SECTION>
+
+<SECTION>
+<FILE>e-filter-color</FILE>
+<TITLE>EFilterColor</TITLE>
+EFilterColor
+e_filter_color_new
+<SUBSECTION Standard>
+E_FILTER_COLOR
+E_IS_FILTER_COLOR
+E_TYPE_FILTER_COLOR
+E_FILTER_COLOR_CLASS
+E_IS_FILTER_COLOR_CLASS
+E_FILTER_COLOR_GET_CLASS
+EFilterColorClass
+e_filter_color_get_type
+<SUBSECTION Private>
+EFilterColorPrivate
+</SUBSECTION>
+
+<SECTION>
+<FILE>e-filter-datespec</FILE>
+<TITLE>EFilterDatespec</TITLE>
+EFilterDatespec
+EFilterDatespecType
+e_filter_datespec_new
+<SUBSECTION Standard>
+E_FILTER_DATESPEC
+E_IS_FILTER_DATESPEC
+E_TYPE_FILTER_DATESPEC
+E_FILTER_DATESPEC_CLASS
+E_IS_FILTER_DATESPEC_CLASS
+E_FILTER_DATESPEC_GET_CLASS
+EFilterDatespecClass
+e_filter_datespec_get_type
+<SUBSECTION Private>
+EFilterDatespecPrivate
+</SUBSECTION>
+
+<SECTION>
+<FILE>e-filter-element</FILE>
+<TITLE>EFilterElement</TITLE>
+EFilterElement
+e_filter_element_new
+e_filter_element_set_data
+e_filter_element_validate
+e_filter_element_eq
+e_filter_element_xml_create
+e_filter_element_xml_encode
+e_filter_element_xml_decode
+e_filter_element_clone
+e_filter_element_copy_value
+e_filter_element_get_widget
+e_filter_element_build_code
+e_filter_element_format_sexp
+<SUBSECTION Standard>
+E_FILTER_ELEMENT
+E_IS_FILTER_ELEMENT
+E_TYPE_FILTER_ELEMENT
+E_FILTER_ELEMENT_CLASS
+E_IS_FILTER_ELEMENT_CLASS
+E_FILTER_ELEMENT_GET_CLASS
+EFilterElementClass
+e_filter_element_get_type
+<SUBSECTION Private>
+EFilterElementPrivate
+</SECTION>
+
+<SECTION>
+<FILE>e-filter-file</FILE>
+<TITLE>EFilterFile</TITLE>
+EFilterFile
+e_filter_file_new
+e_filter_file_new_type_name
+e_filter_file_set_path
+<SUBSECTION Standard>
+E_FILTER_FILE
+E_IS_FILTER_FILE
+E_TYPE_FILTER_FILE
+E_FILTER_FILE_CLASS
+E_IS_FILTER_FILE_CLASS
+E_FILTER_FILE_GET_CLASS
+EFilterFileClass
+e_filter_file_get_type
+<SUBSECTION Private>
+EFilterFilePrivate
+</SECTION>
+
+<SECTION>
+<FILE>e-filter-input</FILE>
+<TITLE>EFilterInput</TITLE>
+EFilterInput
+e_filter_input_new
+e_filter_input_new_type_name
+e_filter_input_set_value
+<SUBSECTION Standard>
+E_FILTER_INPUT
+E_IS_FILTER_INPUT
+E_TYPE_FILTER_INPUT
+E_FILTER_INPUT_CLASS
+E_IS_FILTER_INPUT_CLASS
+E_TYPE_FILTER_INPUT_CLASS
+EFilterInputClass
+e_filter_input_get_type
+<SUBSECTION Private>
+EFilterInputPrivate
+</SECTION>
+
+<SECTION>
+<FILE>e-filter-int</FILE>
+<TITLE>EFilterInt</TITLE>
+EFilterInt
+e_filter_int_new
+e_filter_int_new_type
+e_filter_int_set_value
+<SUBSECTION Standard>
+E_FILTER_INT
+E_IS_FILTER_INT
+E_TYPE_FILTER_INT
+E_FILTER_INT_CLASS
+E_IS_FILTER_INT_CLASS
+E_FILTER_INT_GET_CLASS
+EFilterIntClass
+e_filter_int_get_type
+<SUBSECTION Private>
+EFilterIntPrivate
+</SECTION>
+
+<SECTION>
+<FILE>e-filter-option</FILE>
+<TITLE>EFilterOption</TITLE>
+EFilterOption
+e_filter_option_new
+e_filter_option_set_current
+e_filter_option_get_current
+e_filter_option_add
+e_filter_option_remove_all
+<SUBSECTION Standard>
+E_FILTER_OPTION
+E_IS_FILTER_OPTION
+E_TYPE_FILTER_OPTION
+E_FILTER_OPTION_CLASS
+E_IS_FILTER_OPTION_CLASS
+E_FILTER_OPTION_GET_CLASS
+EFilterOptionClass
+e_filter_option_get_type
+<SUBSECTION Private>
+EFilterOptionPrivate
+</SECTION>
+
+<SECTION>
+<FILE>e-filter-part</FILE>
+<TITLE>EFilterPart</TITLE>
+EFilterPart
+e_filter_part_new
+e_filter_part_validate
+e_filter_part_eq
+e_filter_part_xml_create
+e_filter_part_xml_encode
+e_filter_part_xml_decode
+e_filter_part_clone
+e_filter_part_copy_values
+e_filter_part_find_element
+e_filter_part_get_widget
+e_filter_part_build_code
+e_filter_part_expand_code
+e_filter_part_build_code_list
+e_filter_part_find_list
+e_filter_part_next_list
+<SUBSECTION Standard>
+E_FILTER_PART
+E_IS_FILTER_PART
+E_TYPE_FILTER_PART
+E_FILTER_PART_CLASS
+E_IS_FILTER_PART_CLASS
+E_FILTER_PART_GET_CLASS
+EFilterPartClass
+e_filter_part_get_type
+<SUBSECTION Private>
+EFilterPartPrivate
+</SECTION>
+
+<SECTION>
+<FILE>e-filter-rule</FILE>
+<TITLE>EFilterRule</TITLE>
+EFilterRule
+e_filter_rule_new
+e_filter_rule_clone
+e_filter_rule_set_name
+E_FILTER_SOURCE_INCOMING
+E_FILTER_SOURCE_DEMAND
+E_FILTER_SOURCE_OUTGOING
+E_FILTER_SOURCE_JUNKTEST
+e_filter_rule_set_source
+e_filter_rule_validate
+e_filter_rule_eq
+e_filter_rule_xml_encode
+e_filter_rule_xml_decode
+e_filter_rule_copy
+e_filter_rule_add_part
+e_filter_rule_remove_part
+e_filter_rule_replace_part
+e_filter_rule_get_widget
+e_filter_rule_build_code
+e_filter_rule_emit_changed
+e_filter_rule_next_list
+e_filter_rule_find_list
+<SUBSECTION Standard>
+E_FILTER_RULE
+E_IS_FILTER_RULE
+E_TYPE_FILTER_RULE
+E_FILTER_RULE_CLASS
+E_IS_FILTER_RULE_CLASS
+E_FILTER_RULE_GET_CLASS
+EFilterRuleClass
+e_filter_rule_get_type
+<SUBSECTION Private>
+EFilterRulePrivate
+</SECTION>
+
+<SECTION>
+<FILE>e-focus-tracker</FILE>
+<TITLE>EFocusTracker</TITLE>
+EFocusTracker
+e_focus_tracker_new
+e_focus_tracker_get_focus
+e_focus_tracker_get_window
+e_focus_tracker_get_cut_clipboard_action
+e_focus_tracker_set_cut_clipboard_action
+e_focus_tracker_get_copy_clipboard_action
+e_focus_tracker_set_copy_clipboard_action
+e_focus_tracker_get_paste_clipboard_action
+e_focus_tracker_set_paste_clipboard_action
+e_focus_tracker_get_delete_selection_action
+e_focus_tracker_set_delete_selection_action
+e_focus_tracker_get_select_all_action
+e_focus_tracker_set_select_all_action
+e_focus_tracker_update_actions
+e_focus_tracker_cut_clipboard
+e_focus_tracker_copy_clipboard
+e_focus_tracker_paste_clipboard
+e_focus_tracker_delete_selection
+e_focus_tracker_select_all
+<SUBSECTION Standard>
+E_FOCUS_TRACKER
+E_IS_FOCUS_TRACKER
+E_TYPE_FOCUS_TRACKER
+E_FOCUS_TRACKER_CLASS
+E_IS_FOCUS_TRACKER_CLASS
+E_FOCUS_TRACKER_GET_CLASS
+EFocusTrackerClass
+e_focus_tracker_get_type
+<SUBSECTION Private>
+EFocusTrackerPrivate
+</SECTION>
+
+<SECTION>
+<FILE>e-html-utils</FILE>
+<TITLE>Text to HTML Conversion</TITLE>
+E_TEXT_TO_HTML_PRE
+E_TEXT_TO_HTML_CONVERT_NL
+E_TEXT_TO_HTML_CONVERT_SPACES
+E_TEXT_TO_HTML_CONVERT_URLS
+E_TEXT_TO_HTML_MARK_CITATION
+E_TEXT_TO_HTML_CONVERT_ADDRESSES
+E_TEXT_TO_HTML_ESCAPE_8BIT
+E_TEXT_TO_HTML_CITE
+e_text_to_html_full
+e_text_to_html
+</SECTION>
+
+<SECTION>
+<FILE>e-icon-factory</FILE>
+<TITLE>Icon Utilities (Legacy)</TITLE>
+e_icon_factory_get_icon_filename
+e_icon_factory_get_icon
+e_icon_factory_pixbuf_scale
+e_icon_factory_create_thumbnail
+</SECTION>
+
+<SECTION>
+<FILE>e-mail-identity-combo-box</FILE>
+<TITLE>EMailIdentityComboBox</TITLE>
+EMailIdentityComboBox
+e_mail_identity_combo_box_new
+e_mail_identity_combo_box_refresh
+e_mail_identity_combo_box_get_registry
+<SUBSECTION Standard>
+E_MAIL_IDENTITY_COMBO_BOX
+E_IS_MAIL_IDENTITY_COMBO_BOX
+E_TYPE_MAIL_IDENTITY_COMBO_BOX
+E_MAIL_IDENTITY_COMBO_BOX_CLASS
+E_IS_MAIL_IDENTITY_COMBO_BOX_CLASS
+E_MAIL_IDENTITY_COMBO_BOX_GET_CLASS
+EMailIdentityComboBoxClass
+e_mail_identity_combo_box_get_type
+<SUBSECTION Private>
+EMailIdentityComboBoxPrivate
+</SECTION>
+
+<SECTION>
+<FILE>e-misc-utils</FILE>
+<TITLE>Miscellaneous Utilities</TITLE>
+e_get_accels_filename
+e_show_uri
+e_display_help
+e_lookup_action
+e_lookup_action_group
+e_action_compare_by_label
+e_action_group_remove_all_actions
+e_radio_action_get_current_action
+e_categories_add_change_hook
+e_str_without_underscores
+e_str_compare
+e_str_case_compare
+e_collate_compare
+e_int_compare
+e_color_to_value
+e_format_number
+ESortCompareFunc
+e_bsearch
+e_strftime_fix_am_pm
+e_utf8_strftime_fix_am_pm
+e_get_month_name
+e_get_weekday_name
+e_flexible_strtod
+e_ascii_dtostr
+e_file_lock_create
+e_file_lock_destroy
+e_file_lock_exists
+e_util_guess_mime_type
+e_util_get_category_filter_options
+e_binding_transform_color_to_string
+e_binding_transform_string_to_color
+e_binding_transform_source_to_uid
+e_binding_transform_uid_to_source
+e_charset_add_radio_actions
+e_file_replace_contents_async
+e_file_replace_contents_finish
+e_mktemp
+e_mkstemp
+e_mkdtemp
+</SECTION>
+
+<SECTION>
+<FILE>e-name-selector</FILE>
+<TITLE>ENameSelector</TITLE>
+ENameSelector
+e_name_selector_new
+e_name_selector_get_registry
+e_name_selector_peek_model
+e_name_selector_peek_dialog
+e_name_selector_peek_section_entry
+e_name_selector_peek_section_list
+e_name_selector_show_dialog
+e_name_selector_load_books
+e_name_selector_cancel_loading
+<SUBSECTION Standard>
+E_NAME_SELECTOR
+E_IS_NAME_SELECTOR
+E_TYPE_NAME_SELECTOR
+E_NAME_SELECTOR_CLASS
+E_IS_NAME_SELECTOR_CLASS
+E_NAME_SELECTOR_GET_CLASS
+ENameSelectorClass
+e_name_selector_get_type
+<SUBSECTION Private>
+ENameSelectorPrivate
+</SECTION>
+
+<SECTION>
+<FILE>e-name-selector-dialog</FILE>
+<TITLE>ENameSelectorDialog</TITLE>
+ENameSelectorDialog
+e_name_selector_dialog_new
+e_name_selector_dialog_get_registry
+e_name_selector_dialog_peek_model
+e_name_selector_dialog_set_model
+e_name_selector_dialog_set_destination_index
+e_name_selector_dialog_set_scrolling_policy
+e_name_selector_dialog_get_section_visible
+e_name_selector_dialog_set_section_visible
+<SUBSECTION Standard>
+E_NAME_SELECTOR_DIALOG
+E_IS_NAME_SELECTOR_DIALOG
+E_TYPE_NAME_SELECTOR_DIALOG
+E_NAME_SELECTOR_DIALOG_CLASS
+E_IS_NAME_SELECTOR_DIALOG_CLASS
+E_NAME_SELECTOR_DIALOG_GET_CLASS
+ENameSelectorDialogClass
+e_name_selector_dialog_get_type
+<SUBSECTION Private>
+ENameSelectorDialogPrivate
+</SECTION>
+
+<SECTION>
+<FILE>e-name-selector-entry</FILE>
+<TITLE>ENameSelectorEntry</TITLE>
+ENameSelectorEntry
+e_name_selector_entry_new
+e_name_selector_entry_get_registry
+e_name_selector_entry_set_registry
+e_name_selector_entry_get_minimum_query_length
+e_name_selector_entry_set_minimum_query_length
+e_name_selector_entry_get_show_address
+e_name_selector_entry_set_show_address
+e_name_selector_entry_peek_contact_store
+e_name_selector_entry_set_contact_store
+e_name_selector_entry_peek_destination_store
+e_name_selector_entry_set_destination_store
+e_name_selector_entry_get_popup_destination
+e_name_selector_entry_set_contact_editor_func
+e_name_selector_entry_set_contact_list_editor_func
+ens_util_populate_user_query_fields
+<SUBSECTION Standard>
+E_NAME_SELECTOR_ENTRY
+E_IS_NAME_SELECTOR_ENTRY
+E_TYPE_NAME_SELECTOR_ENTRY
+E_NAME_SELECTOR_ENTRY_CLASS
+E_IS_NAME_SELECTOR_ENTRY_CLASS
+E_NAME_SELECTOR_ENTRY_GET_CLASS
+ENameSelectorEntryClass
+e_name_selector_entry_get_type
+<SUBSECTION Private>
+ENameSelectorEntryPrivate
+</SECTION>
+
+<SECTION>
+<FILE>e-name-selector-list</FILE>
+<TITLE>ENameSelectorList</TITLE>
+ENameSelectorList
+e_name_selector_list_new
+e_name_selector_list_expand_clicked
+<SUBSECTION Standard>
+E_NAME_SELECTOR_LIST
+E_IS_NAME_SELECTOR_LIST
+E_TYPE_NAME_SELECTOR_LIST
+E_NAME_SELECTOR_LIST_CLASS
+E_IS_NAME_SELECTOR_LIST_CLASS
+E_NAME_SELECTOR_LIST_GET_CLASS
+ENameSelectorListClass
+e_name_selector_list_get_type
+<SUBSECTION Private>
+ENameSelectorListPrivate
+</SECTION>
+
+<SECTION>
+<FILE>e-name-selector-model</FILE>
+<TITLE>ENameSelectorModel</TITLE>
+ENameSelectorModel
+e_name_selector_model_new
+e_name_selector_model_peek_contact_store
+e_name_selector_model_peek_contact_filter
+e_name_selector_model_list_sections
+e_name_selector_model_peek_section
+e_name_selector_model_add_section
+e_name_selector_model_remove_section
+e_name_selector_model_get_contact_emails_without_used
+e_name_selector_model_free_emails_list
+<SUBSECTION Standard>
+E_NAME_SELECTOR_MODEL
+E_IS_NAME_SELECTOR_MODEL
+E_TYPE_NAME_SELECTOR_MODEL
+E_NAME_SELECTOR_MODEL_CLASS
+E_IS_NAME_SELECTOR_MODEL_CLASS
+E_NAME_SELECTOR_MODEL_GET_CLASS
+ENameSelectorModelClass
+e_name_selector_model_get_type
+<SUBSECTION Private>
+ENameSelectorModelPrivate
+</SECTION>
+
+<SECTION>
+<FILE>e-passwords</FILE>
+<TITLE>Password Utilities (Legacy)</TITLE>
+e_passwords_init
+e_passwords_shutdown
+e_passwords_cancel
+e_passwords_set_online
+e_passwords_remember_password
+e_passwords_add_password
+e_passwords_get_password
+e_passwords_forget_password
+e_passwords_forget_passwords
+e_passwords_clear_passwords
+EPasswordsRememberType
+e_passwords_ask_password
+</SECTION>
+
+<SECTION>
+<FILE>e-poolv</FILE>
+<TITLE>EPoolv</TITLE>
+EPoolv
+e_poolv_new
+e_poolv_set
+e_poolv_get
+e_poolv_destroy
+</SECTION>
+
+<SECTION>
+<FILE>e-popup-action</FILE>
+<TITLE>EPopupAction</TITLE>
+EPopupAction
+e_popup_action_new
+EPopupActionEntry
+e_action_group_add_popup_actions
+<SUBSECTION Standard>
+E_POPUP_ACTION
+E_IS_POPUP_ACTION
+E_TYPE_POPUP_ACTION
+E_POPUP_ACTION_CLASS
+E_IS_POPUP_ACTION_CLASS
+E_POPUP_ACTION_GET_CLASS
+EPopupActionClass
+e_popup_action_get_type
+<SUBSECTION Private>
+EPopupActionPrivate
+</SECTION>
+
+<SECTION>
+<FILE>e-print</FILE>
+<TITLE>Printing</TITLE>
+e_print_operation_new
+e_print_run_page_setup_dialog
+</SECTION>
+
+<SECTION>
+<FILE>e-rule-context</FILE>
+<TITLE>ERuleContext</TITLE>
+ERuleContext
+ERuleContextRegisterFunc
+ERuleContextPartFunc
+ERuleContextRuleFunc
+ERuleContextNextPartFunc
+ERuleContextNextRuleFunc
+e_rule_context_new
+e_rule_context_load
+e_rule_context_save
+e_rule_context_revert
+e_rule_context_add_part
+e_rule_context_find_part
+e_rule_context_create_part
+e_rule_context_next_part
+e_rule_context_next_rule
+e_rule_context_find_rule
+e_rule_context_find_rank_rule
+e_rule_context_add_rule
+e_rule_context_add_rule_gui
+e_rule_context_remove_rule
+e_rule_context_rank_rule
+e_rule_context_get_rank_rule
+e_rule_context_add_part_set
+e_rule_context_add_rule_set
+e_rule_context_new_element
+e_rule_context_delete_uri
+e_rule_context_rename_uri
+e_rule_context_free_uri_list
+<SUBSECTION Standard>
+E_RULE_CONTEXT
+E_IS_RULE_CONTEXT
+E_TYPE_RULE_CONTEXT
+E_RULE_CONTEXT_CLASS
+E_IS_RULE_CONTEXT_CLASS
+E_RULE_CONTEXT_GET_CLASS
+ERuleContextClass
+e_rule_context_get_type
+<SUBSECTION Private>
+ERuleContextPrivate
+</SECTION>
+
+<SECTION>
+<FILE>e-rule-editor</FILE>
+<TITLE>ERuleEditor</TITLE>
+ERuleEditor
+ERuleEditorUndo
+e_rule_editor_new
+e_rule_editor_construct
+e_rule_editor_set_source
+e_rule_editor_set_sensitive
+e_rule_editor_create_rule
+<SUBSECTION Standard>
+E_RULE_EDITOR
+E_IS_RULE_EDITOR
+E_TYPE_RULE_EDITOR
+E_RULE_EDITOR_CLASS
+E_IS_RULE_EDITOR_CLASS
+E_RULE_EDITOR_GET_CLASS
+ERuleEditorClass
+e_rule_editor_get_type
+<SUBSECTION Private>
+ERuleEditorPrivate
+</SECTION>
+
+<SECTION>
+<FILE>e-selection</FILE>
+<TITLE>Selections</TITLE>
+e_target_list_add_calendar_targets
+e_target_list_add_directory_targets
+e_selection_data_set_calendar
+e_selection_data_set_directory
+e_selection_data_get_calendar
+e_selection_data_get_directory
+e_selection_data_targets_include_calendar
+e_selection_data_targets_include_directory
+e_targets_include_calendar
+e_targets_include_directory
+e_clipboard_set_calendar
+e_clipboard_set_directory
+e_clipboard_request_calendar
+e_clipboard_request_directory
+e_clipboard_wait_for_calendar
+e_clipboard_wait_for_directory
+e_clipboard_wait_is_calendar_available
+e_clipboard_wait_is_directory_available
+</SECTION>
+
+<SECTION>
+<FILE>e-source-combo-box</FILE>
+<TITLE>ESourceComboBox</TITLE>
+ESourceComboBox
+e_source_combo_box_new
+e_source_combo_box_get_registry
+e_source_combo_box_set_registry
+e_source_combo_box_get_extension_name
+e_source_combo_box_set_extension_name
+e_source_combo_box_get_show_colors
+e_source_combo_box_set_show_colors
+e_source_combo_box_ref_active
+e_source_combo_box_set_active
+<SUBSECTION Standard>
+E_SOURCE_COMBO_BOX
+E_IS_SOURCE_COMBO_BOX
+E_TYPE_SOURCE_COMBO_BOX
+E_SOURCE_COMBO_BOX_CLASS
+E_IS_SOURCE_COMBO_BOX_CLASS
+E_SOURCE_COMBO_BOX_GET_CLASS
+ESourceComboBoxClass
+e_source_combo_box_get_type
+<SUBSECTION Private>
+ESourceComboBoxPrivate
+</SECTION>
+
+<SECTION>
+<FILE>e-source-selector</FILE>
+<TITLE>ESourceSelector</TITLE>
+ESourceSelector
+e_source_selector_new
+e_source_selector_get_registry
+e_source_selector_get_extension_name
+e_source_selector_get_show_colors
+e_source_selector_set_show_colors
+e_source_selector_get_show_toggles
+e_source_selector_set_show_toggles
+e_source_selector_select_source
+e_source_selector_unselect_source
+e_source_selector_select_exclusive
+e_source_selector_source_is_selected
+e_source_selector_get_selection
+e_source_selector_free_selection
+e_source_selector_set_select_new
+e_source_selector_edit_primary_selection
+e_source_selector_ref_primary_selection
+e_source_selector_set_primary_selection
+e_source_selector_ref_source_by_path
+e_source_selector_queue_write
+<SUBSECTION Standard>
+E_SOURCE_SELECTOR
+E_IS_SOURCE_SELECTOR
+E_TYPE_SOURCE_SELECTOR
+E_SOURCE_SELECTOR_CLASS
+E_IS_SOURCE_SELECTOR_CLASS
+E_SOURCE_SELECTOR_GET_CLASS
+ESourceSelectorClass
+e_source_selector_get_type
+<SUBSECTION Private>
+ESourceSelectorPrivate
+</SECTION>
+
+<SECTION>
+<FILE>e-source-selector-dialog</FILE>
+<TITLE>ESourceSelectorDialog</TITLE>
+ESourceSelectorDialog
+e_source_selector_dialog_new
+e_source_selector_dialog_get_registry
+e_source_selector_dialog_get_extension_name
+e_source_selector_dialog_get_selector
+e_source_selector_dialog_peek_primary_selection
+<SUBSECTION Standard>
+E_SOURCE_SELECTOR_DIALOG
+E_IS_SOURCE_SELECTOR_DIALOG
+E_TYPE_SOURCE_SELECTOR_DIALOG
+E_SOURCE_SELECTOR_DIALOG_CLASS
+E_IS_SOURCE_SELECTOR_DIALOG_CLASS
+E_SOURCE_SELECTOR_DIALOG_GET_CLASS
+ESourceSelectorDialogClass
+e_source_selector_dialog_get_type
+<SUBSECTION Private>
+ESourceSelectorDialogPrivate
+</SECTION>
+
+<SECTION>
+<FILE>e-tree-model-generator</FILE>
+<TITLE>ETreeModelGenerator</TITLE>
+ETreeModelGeneratorGenerateFunc
+ETreeModelGeneratorModifyFunc
+ETreeModelGenerator
+e_tree_model_generator_new
+e_tree_model_generator_get_model
+e_tree_model_generator_set_generate_func
+e_tree_model_generator_set_modify_func
+e_tree_model_generator_convert_child_path_to_path
+e_tree_model_generator_convert_child_iter_to_iter
+e_tree_model_generator_convert_path_to_child_path
+e_tree_model_generator_convert_iter_to_child_iter
+<SUBSECTION Standard>
+E_TREE_MODEL_GENERATOR
+E_IS_TREE_MODEL_GENERATOR
+E_TYPE_TREE_MODEL_GENERATOR
+E_TREE_MODEL_GENERATOR_CLASS
+E_IS_TREE_MODEL_GENERATOR_CLASS
+E_TREE_MODEL_GENERATOR_GET_CLASS
+ETreeModelGeneratorClass
+e_tree_model_generator_get_type
+<SUBSECTION Private>
+ETreeModelGeneratorPrivate
+</SECTION>
+
+<SECTION>
+<FILE>e-web-view</FILE>
+<TITLE>EWebView</TITLE>
+EWebView
+e_web_view_new
+e_web_view_clear
+e_web_view_load_string
+e_web_view_get_caret_mode
+e_web_view_set_caret_mode
+e_web_view_get_copy_target_list
+e_web_view_get_disable_printing
+e_web_view_set_disable_printing
+e_web_view_get_disable_save_to_disk
+e_web_view_set_disable_save_to_disk
+e_web_view_get_editable
+e_web_view_set_editable
+e_web_view_get_inline_spelling
+e_web_view_set_inline_spelling
+e_web_view_get_magic_links
+e_web_view_set_magic_links
+e_web_view_get_magic_smileys
+e_web_view_set_magic_smileys
+e_web_view_get_selected_uri
+e_web_view_set_selected_uri
+e_web_view_get_open_proxy
+e_web_view_set_open_proxy
+e_web_view_get_paste_target_list
+e_web_view_get_print_proxy
+e_web_view_set_print_proxy
+e_web_view_get_save_as_proxy
+e_web_view_set_save_as_proxy
+e_web_view_get_action
+e_web_view_get_action_group
+e_web_view_extract_uri
+e_web_view_copy_clipboard
+e_web_view_cut_clipboard
+e_web_view_is_selection_active
+e_web_view_paste_clipboard
+e_web_view_scroll_forward
+e_web_view_scroll_backward
+e_web_view_select_all
+e_web_view_unselect_all
+e_web_view_zoom_100
+e_web_view_zoom_in
+e_web_view_zoom_out
+e_web_view_get_ui_manager
+e_web_view_get_popup_menu
+e_web_view_show_popup_menu
+e_web_view_status_message
+e_web_view_stop_loading
+e_web_view_update_actions
+<SUBSECTION Standard>
+E_WEB_VIEW
+E_IS_WEB_VIEW
+E_TYPE_WEB_VIEW
+E_WEB_VIEW_CLASS
+E_IS_WEB_VIEW_CLASS
+E_WEB_VIEW_GET_CLASS
+EWebViewClass
+e_web_view_get_type
+<SUBSECTION Private>
+EWebViewPrivate
+</SECTION>
+
+<SECTION>
+<FILE>e-xml-utils</FILE>
+<TITLE>Reading and Writing XML</TITLE>
+e_xml_get_child_by_name_by_lang
+e_xml_get_child_by_name_by_lang_list
+e_xml_get_child_by_name_no_lang
+e_xml_get_integer_prop_by_name
+e_xml_get_integer_prop_by_name_with_default
+e_xml_set_integer_prop_by_name
+e_xml_get_uint_prop_by_name
+e_xml_get_uint_prop_by_name_with_default
+e_xml_set_uint_prop_by_name
+e_xml_get_bool_prop_by_name
+e_xml_get_bool_prop_by_name_with_default
+e_xml_set_bool_prop_by_name
+e_xml_get_double_prop_by_name
+e_xml_get_double_prop_by_name_with_default
+e_xml_set_double_prop_by_name
+e_xml_get_string_prop_by_name
+e_xml_get_string_prop_by_name_with_default
+e_xml_set_string_prop_by_name
+e_xml_get_translated_string_prop_by_name
+</SECTION>
+
diff --git a/doc/reference/libeutil/libeutil.types b/doc/reference/libeutil/libeutil.types
new file mode 100644
index 0000000..b78b7ad
--- /dev/null
+++ b/doc/reference/libeutil/libeutil.types
@@ -0,0 +1,170 @@
+#include <e-util/e-util.h>
+
+e_action_combo_box_get_type
+e_activity_bar_get_type
+e_activity_get_type
+e_activity_proxy_get_type
+e_alarm_selector_get_type
+e_alert_bar_get_type
+e_alert_dialog_get_type
+e_alert_get_type
+e_alert_sink_get_type
+e_attachment_bar_get_type
+e_attachment_button_get_type
+e_attachment_dialog_get_type
+e_attachment_get_type
+e_attachment_handler_get_type
+e_attachment_handler_image_get_type
+e_attachment_handler_sendto_get_type
+e_attachment_icon_view_get_type
+e_attachment_paned_get_type
+e_attachment_store_get_type
+e_attachment_tree_view_get_type
+e_attachment_view_get_type
+e_auth_combo_box_get_type
+e_bit_array_get_type
+e_book_source_config_get_type
+e_cal_source_config_get_type
+e_calendar_get_type
+e_calendar_item_get_type
+e_canvas_background_get_type
+e_canvas_get_type
+e_canvas_vbox_get_type
+e_categories_dialog_get_type
+e_categories_editor_get_type
+e_categories_selector_get_type
+e_category_completion_get_type
+e_category_editor_get_type
+e_cell_checkbox_get_type
+e_cell_combo_get_type
+e_cell_date_edit_get_type
+e_cell_date_get_type
+e_cell_get_type
+e_cell_hbox_get_type
+e_cell_number_get_type
+e_cell_percent_get_type
+e_cell_pixbuf_get_type
+e_cell_popup_get_type
+e_cell_renderer_color_get_type
+e_cell_size_get_type
+e_cell_text_get_type
+e_cell_toggle_get_type
+e_cell_tree_get_type
+e_cell_vbox_get_type
+e_charset_combo_box_get_type
+e_config_get_type
+e_config_hook_get_type
+e_contact_store_get_type
+e_date_edit_get_type
+e_destination_store_get_type
+e_event_get_type
+e_event_hook_get_type
+e_file_request_get_type
+e_filter_code_get_type
+e_filter_color_get_type
+e_filter_datespec_get_type
+e_filter_element_get_type
+e_filter_file_get_type
+e_filter_input_get_type
+e_filter_int_get_type
+e_filter_option_get_type
+e_filter_part_get_type
+e_filter_rule_get_type
+e_focus_tracker_get_type
+e_image_chooser_get_type
+e_import_assistant_get_type
+e_import_get_type
+e_import_hook_get_type
+e_interval_chooser_get_type
+e_map_get_type
+e_menu_tool_action_get_type
+e_menu_tool_button_get_type
+e_name_selector_dialog_get_type
+e_name_selector_entry_get_type
+e_name_selector_get_type
+e_name_selector_list_get_type
+e_name_selector_model_get_type
+e_online_button_get_type
+e_paned_get_type
+e_picture_gallery_get_type
+e_plugin_get_type
+e_plugin_hook_get_type
+e_plugin_ui_hook_get_type
+e_popup_action_get_type
+e_port_entry_get_type
+e_preferences_window_get_type
+e_preview_pane_get_type
+e_printable_get_type
+e_reflow_get_type
+e_reflow_model_get_type
+e_rule_context_get_type
+e_rule_editor_get_type
+e_search_bar_get_type
+e_selectable_get_type
+e_selection_model_array_get_type
+e_selection_model_get_type
+e_selection_model_simple_get_type
+e_send_options_dialog_get_type
+e_sorter_array_get_type
+e_sorter_get_type
+e_source_combo_box_get_type
+e_source_config_dialog_get_type
+e_source_config_get_type
+e_source_selector_dialog_get_type
+e_source_selector_get_type
+e_spell_entry_get_type
+e_stock_request_get_type
+e_table_click_to_add_get_type
+e_table_col_get_type
+e_table_column_specification_get_type
+e_table_config_get_type
+e_table_extras_get_type
+e_table_field_chooser_dialog_get_type
+e_table_field_chooser_get_type
+e_table_field_chooser_item_get_type
+e_table_get_type
+e_table_group_get_type
+e_table_group_leaf_get_type
+e_table_header_get_type
+e_table_header_item_get_type
+e_table_item_get_type
+e_table_memory_get_type
+e_table_memory_store_get_type
+e_table_model_get_type
+e_table_one_get_type
+e_table_search_get_type
+e_table_selection_model_get_type
+e_table_sort_info_get_type
+e_table_sorted_get_type
+e_table_sorter_get_type
+e_table_specification_get_type
+e_table_state_get_type
+e_table_subset_get_type
+e_table_without_get_type
+e_text_event_processor_emacs_like_get_type
+e_text_event_processor_get_type
+e_text_get_type
+e_text_model_get_type
+e_timezone_dialog_get_type
+e_tree_get_type
+e_tree_memory_get_type
+e_tree_model_generator_get_type
+e_tree_model_get_type
+e_tree_selection_model_get_type
+e_tree_sorted_get_type
+e_tree_table_adapter_get_type
+e_ui_manager_get_type
+e_url_entry_get_type
+e_web_view_get_type
+e_web_view_gtkhtml_get_type
+e_web_view_preview_get_type
+gal_define_views_dialog_get_type
+gal_define_views_model_get_type
+gal_view_collection_get_type
+gal_view_etable_get_type
+gal_view_factory_etable_get_type
+gal_view_factory_get_type
+gal_view_get_type
+gal_view_instance_get_type
+gal_view_instance_save_as_dialog_get_type
+gal_view_new_dialog_get_type
diff --git a/e-util/Makefile.am b/e-util/Makefile.am
index 4464f93..15a9bcd 100644
--- a/e-util/Makefile.am
+++ b/e-util/Makefile.am
@@ -1,3 +1,5 @@
+NULL =
+
 eutilincludedir = $(privincludedir)/e-util
 ecpsdir = $(privdatadir)/ecps
 ruledir = $(privdatadir)
@@ -22,133 +24,650 @@ e-marshal.c: e-marshal.list
 ENUM_GENERATED = e-util-enumtypes.h e-util-enumtypes.c
 MARSHAL_GENERATED = e-marshal.c e-marshal.h
 
-if OS_WIN32
-PLATFORM_SOURCES = e-win32-reloc.c e-win32-defaults.c e-win32-defaults.h
-endif
+error_DATA = \
+	e-system.error \
+	filter.error \
+	widgets.error \
+	$(NULL)
+errordir = $(privdatadir)/errors
+ EVO_PLUGIN_RULE@
+
+ui_DATA = \
+	e-send-options.ui \
+	e-table-config.ui \
+	e-timezone-dialog.ui \
+	filter.ui \
+	gal-define-views.ui \
+	gal-view-instance-save-as-dialog.ui \
+	gal-view-new-dialog.ui \
+	$(NULL)
+
+xpm_icons = \
+	arrow-down.xpm \
+	arrow-up.xpm \
+	check-empty.xpm \
+	check-filled.xpm \
+	tree-expanded.xpm \
+	tree-unexpanded.xpm \
+	$(NULL)
 
 privsolib_LTLIBRARIES = libeutil.la
 
-eutilinclude_HEADERS = 				\
-	e-activity.h				\
-	e-bit-array.h				\
-	e-categories-config.h			\
-	e-charset.h				\
-	e-config.h				\
-	e-datetime-format.h			\
-	e-dialog-utils.h			\
-	e-dialog-widgets.h			\
-	e-event.h				\
-	e-file-request.h			\
-	e-file-utils.h				\
-	e-html-utils.h				\
-	e-icon-factory.h			\
-	e-import.h				\
-	e-marshal.h				\
-	e-mktemp.h				\
-	e-poolv.h				\
-	e-print.h				\
-	e-plugin.h				\
-	e-plugin-ui.h				\
-	e-selection.h				\
-	e-sorter.h				\
-	e-sorter-array.h			\
-	e-source-util.h				\
-	e-stock-request.h			\
-	e-text-event-processor-emacs-like.h	\
-	e-text-event-processor-types.h		\
-	e-text-event-processor.h		\
-	e-ui-manager.h				\
-	e-util.h				\
-	e-util-enums.h				\
-	e-util-enumtypes.h			\
-	e-unicode.h				
-
-libeutil_la_CPPFLAGS =							\
-	$(AM_CPPFLAGS)							\
-	-I$(top_srcdir)							\
-	-I$(top_builddir)						\
-	-I$(top_srcdir)/widgets						\
-	-DEVOLUTION_BINDIR=\""$(bindir)"\"				\
-	-DEVOLUTION_DATADIR=\""$(datadir)"\"				\
-	-DEVOLUTION_ECPSDIR=\""$(ecpsdir)"\"				\
-	-DEVOLUTION_ETSPECDIR=\""$(etspecdir)"\"			\
-	-DEVOLUTION_GALVIEWSDIR=\""$(viewsdir)"\"			\
-	-DEVOLUTION_HELPDIR=\""$(evolutionhelpdir)"\"			\
-	-DEVOLUTION_ICONDIR=\""$(icondir)"\"				\
-	-DEVOLUTION_IMAGESDIR=\""$(imagesdir)"\"			\
-	-DEVOLUTION_LIBDIR=\""$(datadir)"\"				\
-	-DEVOLUTION_LIBEXECDIR=\""$(privlibexecdir)"\"			\
-	-DEVOLUTION_LOCALEDIR=\""$(localedir)"\"			\
-	-DEVOLUTION_MODULEDIR=\""$(moduledir)"\"		\
-	-DEVOLUTION_PLUGINDIR=\""$(plugindir)"\"			\
-	-DEVOLUTION_PREFIX=\""$(prefix)"\"				\
-	-DEVOLUTION_PRIVDATADIR=\""$(privdatadir)"\"			\
-	-DEVOLUTION_SOUNDDIR=\""$(soundsdir)"\"				\
-	-DEVOLUTION_SYSCONFDIR=\""$(sysconfdir)"\"			\
-	-DEVOLUTION_TOOLSDIR=\""$(privlibexecdir)"\"			\
-	-DEVOLUTION_UIDIR=\""$(uidir)"\"				\
-	-DEVOLUTION_RULEDIR=\"$(ruledir)\"				\
-	-DG_LOG_DOMAIN=\"e-utils\"					\
-	$(EVOLUTION_DATA_SERVER_CFLAGS)					\
-	$(GNOME_PLATFORM_CFLAGS)
-
-libeutil_la_SOURCES =				\
-	$(eutilinclude_HEADERS)			\
-	e-activity.c				\
-	e-bit-array.c				\
-	e-categories-config.c			\
-	e-charset.c				\
-	e-config.c				\
-	e-datetime-format.c			\
-	e-dialog-utils.c			\
-	e-dialog-widgets.c			\
-	e-event.c				\
-	e-file-request.c			\
-	e-file-utils.c				\
-	e-html-utils.c				\
-	e-icon-factory.c			\
-	e-import.c				\
-	e-marshal.c				\
-	e-mktemp.c				\
-	e-poolv.c				\
-	e-plugin.c				\
-	e-plugin-ui.c				\
-	e-print.c				\
-	e-selection.c				\
-	e-sorter.c				\
-	e-sorter-array.c			\
-	e-source-util.c				\
-	e-stock-request.c			\
-	e-text-event-processor-emacs-like.c	\
-	e-text-event-processor.c		\
-	e-ui-manager.c				\
-	e-util.c				\
-	e-unicode.c				\
-	e-util-enumtypes.c			\
-	e-util-private.h			\
-	$(PLATFORM_SOURCES)
+noinst_PROGRAMS = \
+	evolution-source-viewer \
+	test-calendar \
+	test-category-completion \
+	test-contact-store \
+	test-dateedit \
+	test-mail-signatures \
+	test-name-selector \
+	test-preferences-window \
+	test-source-combo-box \
+	test-source-config \
+	test-source-selector \
+	$(NULL)
+
+libeutil_la_CPPFLAGS = \
+	$(AM_CPPFLAGS) \
+	-I$(top_srcdir) \
+	-I$(top_builddir) \
+	-DLIBEUTIL_COMPILATION \
+	-DEVOLUTION_BINDIR=\""$(bindir)"\" \
+	-DEVOLUTION_DATADIR=\""$(datadir)"\" \
+	-DEVOLUTION_ECPSDIR=\""$(ecpsdir)"\" \
+	-DEVOLUTION_ETSPECDIR=\""$(etspecdir)"\" \
+	-DEVOLUTION_GALVIEWSDIR=\""$(viewsdir)"\" \
+	-DEVOLUTION_HELPDIR=\""$(evolutionhelpdir)"\" \
+	-DEVOLUTION_ICONDIR=\""$(icondir)"\" \
+	-DEVOLUTION_IMAGESDIR=\""$(imagesdir)"\" \
+	-DEVOLUTION_LIBDIR=\""$(datadir)"\" \
+	-DEVOLUTION_LIBEXECDIR=\""$(privlibexecdir)"\" \
+	-DEVOLUTION_LOCALEDIR=\""$(localedir)"\" \
+	-DEVOLUTION_MODULEDIR=\""$(moduledir)"\" \
+	-DEVOLUTION_PLUGINDIR=\""$(plugindir)"\" \
+	-DEVOLUTION_PREFIX=\""$(prefix)"\" \
+	-DEVOLUTION_PRIVDATADIR=\""$(privdatadir)"\" \
+	-DEVOLUTION_SOUNDDIR=\""$(soundsdir)"\" \
+	-DEVOLUTION_SYSCONFDIR=\""$(sysconfdir)"\" \
+	-DEVOLUTION_TOOLSDIR=\""$(privlibexecdir)"\" \
+	-DEVOLUTION_UIDIR=\""$(uidir)"\" \
+	-DEVOLUTION_RULEDIR=\"$(ruledir)\" \
+	-DG_LOG_DOMAIN=\"libeutil\" \
+	$(EVOLUTION_DATA_SERVER_CFLAGS) \
+	$(GNOME_PLATFORM_CFLAGS) \
+	$(CHAMPLAIN_CFLAGS) \
+	$(GEO_CFLAGS) \
+	$(GTKHTML_CFLAGS) \
+	$(NULL)
+
+eutilinclude_HEADERS =  \
+	e-util.h \
+	e-action-combo-box.h \
+	e-activity-bar.h \
+	e-activity-proxy.h \
+	e-activity.h \
+	e-alarm-selector.h \
+	e-alert-bar.h \
+	e-alert-dialog.h \
+	e-alert-sink.h \
+	e-alert.h \
+	e-attachment-bar.h \
+	e-attachment-button.h \
+	e-attachment-dialog.h \
+	e-attachment-handler-image.h \
+	e-attachment-handler-sendto.h \
+	e-attachment-handler.h \
+	e-attachment-icon-view.h \
+	e-attachment-paned.h \
+	e-attachment-store.h \
+	e-attachment-tree-view.h \
+	e-attachment-view.h \
+	e-attachment.h \
+	e-auth-combo-box.h \
+	e-autocomplete-selector.h \
+	e-bit-array.h \
+	e-book-source-config.h \
+	e-buffer-tagger.h \
+	e-cal-source-config.h \
+	e-calendar-item.h \
+	e-calendar.h \
+	e-canvas-background.h \
+	e-canvas-utils.h \
+	e-canvas-vbox.h \
+	e-canvas.h \
+	e-categories-config.h \
+	e-categories-dialog.h \
+	e-categories-editor.h \
+	e-categories-selector.h \
+	e-category-completion.h \
+	e-category-editor.h \
+	e-cell-checkbox.h \
+	e-cell-combo.h \
+	e-cell-date-edit.h \
+	e-cell-date.h \
+	e-cell-hbox.h \
+	e-cell-number.h \
+	e-cell-percent.h \
+	e-cell-pixbuf.h \
+	e-cell-popup.h \
+	e-cell-renderer-color.h \
+	e-cell-size.h \
+	e-cell-text.h \
+	e-cell-toggle.h \
+	e-cell-tree.h \
+	e-cell-vbox.h \
+	e-cell.h \
+	e-charset-combo-box.h \
+	e-charset.h \
+	e-client-utils.h \
+	e-config.h \
+	e-contact-map-window.h \
+	e-contact-map.h \
+	e-contact-marker.h \
+	e-contact-store.h \
+	e-dateedit.h \
+	e-datetime-format.h \
+	e-destination-store.h \
+	e-dialog-utils.h \
+	e-dialog-widgets.h \
+	e-event.h \
+	e-file-request.h \
+	e-file-utils.h \
+	e-filter-code.h \
+	e-filter-color.h \
+	e-filter-datespec.h \
+	e-filter-element.h \
+	e-filter-file.h \
+	e-filter-input.h \
+	e-filter-int.h \
+	e-filter-option.h \
+	e-filter-part.h \
+	e-filter-rule.h \
+	e-focus-tracker.h \
+	e-html-utils.h \
+	e-icon-factory.h \
+	e-image-chooser.h \
+	e-import-assistant.h \
+	e-import.h \
+	e-interval-chooser.h \
+	e-mail-identity-combo-box.h \
+	e-mail-signature-combo-box.h \
+	e-mail-signature-editor.h \
+	e-mail-signature-manager.h \
+	e-mail-signature-preview.h \
+	e-mail-signature-script-dialog.h \
+	e-mail-signature-tree-view.h \
+	e-map.h \
+	e-marshal.h \
+	e-menu-tool-action.h \
+	e-menu-tool-button.h \
+	e-misc-utils.h \
+	e-mktemp.h \
+	e-name-selector-dialog.h \
+	e-name-selector-entry.h \
+	e-name-selector-list.h \
+	e-name-selector-model.h \
+	e-name-selector.h \
+	e-online-button.h \
+	e-paned.h \
+	e-passwords.h \
+	e-picture-gallery.h \
+	e-plugin-ui.h \
+	e-plugin.h \
+	e-poolv.h \
+	e-popup-action.h \
+	e-popup-menu.h \
+	e-port-entry.h \
+	e-preferences-window.h \
+	e-preview-pane.h \
+	e-print.h \
+	e-printable.h \
+	e-reflow-model.h \
+	e-reflow.h \
+	e-rule-context.h \
+	e-rule-editor.h \
+	e-search-bar.h \
+	e-selectable.h \
+	e-selection-model-array.h \
+	e-selection-model-simple.h \
+	e-selection-model.h \
+	e-selection.h \
+	e-send-options.h \
+	e-sorter-array.h \
+	e-sorter.h \
+	e-source-combo-box.h \
+	e-source-config-backend.h \
+	e-source-config-dialog.h \
+	e-source-config.h \
+	e-source-selector-dialog.h \
+	e-source-selector.h \
+	e-source-util.h \
+	e-spell-entry.h \
+	e-stock-request.h \
+	e-table-click-to-add.h \
+	e-table-col-dnd.h \
+	e-table-col.h \
+	e-table-column-specification.h \
+	e-table-config.h \
+	e-table-defines.h \
+	e-table-extras.h \
+	e-table-field-chooser-dialog.h \
+	e-table-field-chooser-item.h \
+	e-table-field-chooser.h \
+	e-table-group-container.h \
+	e-table-group-leaf.h \
+	e-table-group.h \
+	e-table-header-item.h \
+	e-table-header-utils.h \
+	e-table-header.h \
+	e-table-item.h \
+	e-table-memory-callbacks.h \
+	e-table-memory-store.h \
+	e-table-memory.h \
+	e-table-model.h \
+	e-table-one.h \
+	e-table-search.h \
+	e-table-selection-model.h \
+	e-table-sort-info.h \
+	e-table-sorted-variable.h \
+	e-table-sorted.h \
+	e-table-sorter.h \
+	e-table-sorting-utils.h \
+	e-table-specification.h \
+	e-table-state.h \
+	e-table-subset-variable.h \
+	e-table-subset.h \
+	e-table-utils.h \
+	e-table-without.h \
+	e-table.h \
+	e-text-event-processor-emacs-like.h \
+	e-text-event-processor-types.h \
+	e-text-event-processor.h \
+	e-text-model-repos.h \
+	e-text-model.h \
+	e-text.h \
+	e-timezone-dialog.h \
+	e-tree-memory-callbacks.h \
+	e-tree-memory.h \
+	e-tree-model-generator.h \
+	e-tree-model.h \
+	e-tree-selection-model.h \
+	e-tree-sorted.h \
+	e-tree-table-adapter.h \
+	e-tree.h \
+	e-ui-manager.h \
+	e-unicode.h \
+	e-url-entry.h \
+	e-util-enums.h \
+	e-util-enumtypes.h \
+	e-web-view-gtkhtml.h \
+	e-web-view-preview.h \
+	e-web-view.h \
+	e-xml-utils.h \
+	ea-calendar-cell.h \
+	ea-calendar-item.h \
+	ea-cell-table.h \
+	ea-factory.h \
+	ea-widgets.h \
+	gal-a11y-e-cell-popup.h \
+	gal-a11y-e-cell-registry.h \
+	gal-a11y-e-cell-toggle.h \
+	gal-a11y-e-cell-tree.h \
+	gal-a11y-e-cell-vbox.h \
+	gal-a11y-e-cell.h \
+	gal-a11y-e-table-click-to-add-factory.h \
+	gal-a11y-e-table-click-to-add.h \
+	gal-a11y-e-table-column-header.h \
+	gal-a11y-e-table-factory.h \
+	gal-a11y-e-table-item-factory.h \
+	gal-a11y-e-table-item.h \
+	gal-a11y-e-table.h \
+	gal-a11y-e-text-factory.h \
+	gal-a11y-e-text.h \
+	gal-a11y-e-tree-factory.h \
+	gal-a11y-e-tree.h \
+	gal-a11y-factory.h \
+	gal-a11y-util.h \
+	gal-define-views-dialog.h \
+	gal-define-views-model.h \
+	gal-view-collection.h \
+	gal-view-etable.h \
+	gal-view-factory-etable.h \
+	gal-view-factory.h \
+	gal-view-instance-save-as-dialog.h \
+	gal-view-instance.h \
+	gal-view-new-dialog.h \
+	gal-view.h \
+	$(NULL)
+
+if OS_WIN32
+PLATFORM_SOURCES = \
+	e-win32-reloc.c \
+	e-win32-defaults.c \
+	e-win32-defaults.h \
+	$(NULL)
+endif
+
+libeutil_la_SOURCES = \
+	$(eutilinclude_HEADERS) \
+	e-action-combo-box.c \
+	e-activity-bar.c \
+	e-activity-proxy.c \
+	e-activity.c \
+	e-alarm-selector.c \
+	e-alert-bar.c \
+	e-alert-dialog.c \
+	e-alert-sink.c \
+	e-alert.c \
+	e-attachment-bar.c \
+	e-attachment-button.c \
+	e-attachment-dialog.c \
+	e-attachment-handler-image.c \
+	e-attachment-handler-sendto.c \
+	e-attachment-handler.c \
+	e-attachment-icon-view.c \
+	e-attachment-paned.c \
+	e-attachment-store.c \
+	e-attachment-tree-view.c \
+	e-attachment-view.c \
+	e-attachment.c \
+	e-auth-combo-box.c \
+	e-autocomplete-selector.c \
+	e-bit-array.c \
+	e-book-source-config.c \
+	e-buffer-tagger.c \
+	e-cal-source-config.c \
+	e-calendar-item.c \
+	e-calendar.c \
+	e-canvas-background.c \
+	e-canvas-utils.c \
+	e-canvas-vbox.c \
+	e-canvas.c \
+	e-categories-config.c \
+	e-categories-dialog.c \
+	e-categories-editor.c \
+	e-categories-selector.c \
+	e-category-completion.c \
+	e-category-editor.c \
+	e-cell-checkbox.c \
+	e-cell-combo.c \
+	e-cell-date-edit.c \
+	e-cell-date.c \
+	e-cell-hbox.c \
+	e-cell-number.c \
+	e-cell-percent.c \
+	e-cell-pixbuf.c \
+	e-cell-popup.c \
+	e-cell-renderer-color.c \
+	e-cell-size.c \
+	e-cell-text.c \
+	e-cell-toggle.c \
+	e-cell-tree.c \
+	e-cell-vbox.c \
+	e-cell.c \
+	e-charset-combo-box.c \
+	e-charset.c \
+	e-client-utils.c \
+	e-config.c \
+	e-contact-map-window.c \
+	e-contact-map.c \
+	e-contact-marker.c \
+	e-contact-store.c \
+	e-dateedit.c \
+	e-datetime-format.c \
+	e-destination-store.c \
+	e-dialog-utils.c \
+	e-dialog-widgets.c \
+	e-event.c \
+	e-file-request.c \
+	e-file-utils.c \
+	e-filter-code.c \
+	e-filter-color.c \
+	e-filter-datespec.c \
+	e-filter-element.c \
+	e-filter-file.c \
+	e-filter-input.c \
+	e-filter-int.c \
+	e-filter-option.c \
+	e-filter-part.c \
+	e-filter-rule.c \
+	e-focus-tracker.c \
+	e-html-utils.c \
+	e-icon-factory.c \
+	e-image-chooser.c \
+	e-import-assistant.c \
+	e-import.c \
+	e-interval-chooser.c \
+	e-mail-identity-combo-box.c \
+	e-mail-signature-combo-box.c \
+	e-mail-signature-editor.c \
+	e-mail-signature-manager.c \
+	e-mail-signature-preview.c \
+	e-mail-signature-script-dialog.c \
+	e-mail-signature-tree-view.c \
+	e-map.c \
+	e-marshal.c \
+	e-menu-tool-action.c \
+	e-menu-tool-button.c \
+	e-misc-utils.c \
+	e-mktemp.c \
+	e-name-selector-dialog.c \
+	e-name-selector-entry.c \
+	e-name-selector-list.c \
+	e-name-selector-model.c \
+	e-name-selector.c \
+	e-online-button.c \
+	e-paned.c \
+	e-passwords.c \
+	e-picture-gallery.c \
+	e-plugin-ui.c \
+	e-plugin.c \
+	e-poolv.c \
+	e-popup-action.c \
+	e-popup-menu.c \
+	e-port-entry.c \
+	e-preferences-window.c \
+	e-preview-pane.c \
+	e-print.c \
+	e-printable.c \
+	e-reflow-model.c \
+	e-reflow.c \
+	e-rule-context.c \
+	e-rule-editor.c \
+	e-search-bar.c \
+	e-selectable.c \
+	e-selection-model-array.c \
+	e-selection-model-simple.c \
+	e-selection-model.c \
+	e-selection.c \
+	e-send-options.c \
+	e-sorter-array.c \
+	e-sorter.c \
+	e-source-combo-box.c \
+	e-source-config-backend.c \
+	e-source-config-dialog.c \
+	e-source-config.c \
+	e-source-selector-dialog.c \
+	e-source-selector.c \
+	e-source-util.c \
+	e-spell-entry.c \
+	e-stock-request.c \
+	e-table-click-to-add.c \
+	e-table-col.c \
+	e-table-column-specification.c \
+	e-table-config.c \
+	e-table-extras.c \
+	e-table-field-chooser-dialog.c \
+	e-table-field-chooser-item.c \
+	e-table-field-chooser.c \
+	e-table-group-container.c \
+	e-table-group-leaf.c \
+	e-table-group.c \
+	e-table-header-item.c \
+	e-table-header-utils.c \
+	e-table-header.c \
+	e-table-item.c \
+	e-table-memory-callbacks.c \
+	e-table-memory-store.c \
+	e-table-memory.c \
+	e-table-model.c \
+	e-table-one.c \
+	e-table-search.c \
+	e-table-selection-model.c \
+	e-table-sort-info.c \
+	e-table-sorted-variable.c \
+	e-table-sorted.c \
+	e-table-sorter.c \
+	e-table-sorting-utils.c \
+	e-table-specification.c \
+	e-table-state.c \
+	e-table-subset-variable.c \
+	e-table-subset.c \
+	e-table-utils.c \
+	e-table-without.c \
+	e-table.c \
+	e-text-event-processor-emacs-like.c \
+	e-text-event-processor.c \
+	e-text-model-repos.c \
+	e-text-model.c \
+	e-text.c \
+	e-timezone-dialog.c \
+	e-tree-memory-callbacks.c \
+	e-tree-memory.c \
+	e-tree-model-generator.c \
+	e-tree-model.c \
+	e-tree-selection-model.c \
+	e-tree-sorted.c \
+	e-tree-table-adapter.c \
+	e-tree.c \
+	e-ui-manager.c \
+	e-unicode.c \
+	e-url-entry.c \
+	e-util-enumtypes.c \
+	e-util-private.h \
+	e-web-view-gtkhtml.c \
+	e-web-view-preview.c \
+	e-web-view.c \
+	e-xml-utils.c \
+	ea-calendar-cell.c \
+	ea-calendar-item.c \
+	ea-cell-table.c \
+	ea-widgets.c \
+	gal-a11y-e-cell-popup.c \
+	gal-a11y-e-cell-registry.c \
+	gal-a11y-e-cell-toggle.c \
+	gal-a11y-e-cell-tree.c \
+	gal-a11y-e-cell-vbox.c \
+	gal-a11y-e-cell.c \
+	gal-a11y-e-table-click-to-add-factory.c \
+	gal-a11y-e-table-click-to-add.c \
+	gal-a11y-e-table-column-header.c \
+	gal-a11y-e-table-factory.c \
+	gal-a11y-e-table-item-factory.c \
+	gal-a11y-e-table-item.c \
+	gal-a11y-e-table.c \
+	gal-a11y-e-text-factory.c \
+	gal-a11y-e-text.c \
+	gal-a11y-e-tree-factory.c \
+	gal-a11y-e-tree.c \
+	gal-a11y-util.c \
+	gal-define-views-dialog.c \
+	gal-define-views-model.c \
+	gal-view-collection.c \
+	gal-view-etable.c \
+	gal-view-factory-etable.c \
+	gal-view-factory.c \
+	gal-view-instance-save-as-dialog.c \
+	gal-view-instance.c \
+	gal-view-new-dialog.c \
+	gal-view.c \
+	$(PLATFORM_SOURCES) \
+	$(NULL)
 
 libeutil_la_LDFLAGS = -avoid-version $(NO_UNDEFINED)
 
-libeutil_la_LIBADD = 			\
-	$(top_builddir)/libevolution-utils/libevolution-utils.la	\
-	$(ICONV_LIBS)			\
-	$(EVOLUTION_DATA_SERVER_LIBS)	\
-	$(GNOME_PLATFORM_LIBS)		\
-	$(INTLLIBS)
+libeutil_la_LIBADD =  \
+	$(top_builddir)/libgnomecanvas/libgnomecanvas.la \
+	$(ICONV_LIBS) \
+	$(EVOLUTION_DATA_SERVER_LIBS) \
+	$(GNOME_PLATFORM_LIBS) \
+	$(CHAMPLAIN_LIBS) \
+	$(GEO_LIBS) \
+	$(GTKHTML_LIBS) \
+	$(INTLLIBS) \
+	$(MATH_LIB) \
+	$(NULL)
 
-error_DATA = e-system.error
-errordir = $(privdatadir)/errors
- EVO_PLUGIN_RULE@
+TEST_CPPFLAGS = \
+	$(libeutil_la_CPPFLAGS) \
+	$(NULL)
+
+TEST_LDADD = \
+	$(top_builddir)/e-util/libeutil.la \
+	$(libeutil_la_LIBADD) \
+	$(NULL)
+
+evolution_source_viewer_CPPFLAGS = $(TEST_CPPFLAGS)
+evolution_source_viewer_SOURCES = evolution-source-viewer.c
+evolution_source_viewer_LDADD = $(TEST_LDADD)
+
+test_calendar_CPPFLAGS = $(TEST_CPPFLAGS)
+test_calendar_SOURCES = test-calendar.c
+test_calendar_LDADD = $(TEST_LDADD)
+
+test_category_completion_CPPFLAGS = $(TEST_CPPFLAGS)
+test_category_completion_SOURCES = test-category-completion.c
+test_category_completion_LDADD = $(TEST_LDADD)
+
+test_contact_store_CPPFLAGS = $(TEST_CPPFLAGS)
+test_contact_store_SOURCES = test-contact-store.c
+test_contact_store_LDADD = $(TEST_LDADD)
+
+test_dateedit_CPPFLAGS = $(TEST_CPPFLAGS)
+test_dateedit_SOURCES = test-dateedit.c
+test_dateedit_LDADD = $(TEST_LDADD)
+
+test_mail_signatures_CPPFLAGS = $(TEST_CPPFLAGS)
+test_mail_signatures_SOURCES = test-mail-signatures.c
+test_mail_signatures_LDADD = $(TEST_LDADD)
+
+test_name_selector_CPPFLAGS = $(TEST_CPPFLAGS)
+test_name_selector_SOURCES = test-name-selector.c
+test_name_selector_LDADD = $(TEST_LDADD)
+
+test_preferences_window_CPPFLAGS = $(TEST_CPPFLAGS)
+test_preferences_window_SOURCES = test-preferences-window.c
+test_preferences_window_LDADD = $(TEST_LDADD)
+
+test_source_combo_box_CPPFLAGS = $(TEST_CPPFLAGS)
+test_source_combo_box_SOURCES = test-source-combo-box.c
+test_source_combo_box_LDADD = $(TEST_LDADD)
+
+test_source_config_CPPFLAGS = $(TEST_CPPFLAGS)
+test_source_config_SOURCES = test-source-config.c
+test_source_config_LDADD = $(TEST_LDADD)
+
+test_source_selector_CPPFLAGS = $(TEST_CPPFLAGS)
+test_source_selector_SOURCES = test-source-selector.c
+test_source_selector_LDADD = $(TEST_LDADD)
+
+EXTRA_DIST = \
+	e-util-enumtypes.h.template \
+	e-util-enumtypes.c.template \
+	e-system.error.xml \
+	filter.error.xml \
+	widgets.error.xml \
+	e-marshal.list \
+	$(ui_DATA)
+	$(NULL)
 
-EXTRA_DIST =				\
-	e-util-enumtypes.h.template	\
-	e-util-enumtypes.c.template	\
-	e-system.error.xml		\
-	e-marshal.list
+BUILT_SOURCES = \
+	$(ENUM_GENERATED) \
+	$(MARSHAL_GENERATED) \
+	$(error_DATA) \
+	$(NULL)
 
-BUILT_SOURCES = $(ENUM_GENERATED) $(MARSHAL_GENERATED) $(error_DATA)
-CLEANFILES    = $(BUILT_SOURCES)
+CLEANFILES = $(BUILT_SOURCES)
 
 dist-hook:
 	cd $(distdir); rm -f $(BUILT_SOURCES)
diff --git a/widgets/table/arrow-down.xpm b/e-util/arrow-down.xpm
similarity index 100%
rename from widgets/table/arrow-down.xpm
rename to e-util/arrow-down.xpm
diff --git a/widgets/table/arrow-up.xpm b/e-util/arrow-up.xpm
similarity index 100%
rename from widgets/table/arrow-up.xpm
rename to e-util/arrow-up.xpm
diff --git a/widgets/table/check-empty.xpm b/e-util/check-empty.xpm
similarity index 100%
rename from widgets/table/check-empty.xpm
rename to e-util/check-empty.xpm
diff --git a/widgets/table/check-filled.xpm b/e-util/check-filled.xpm
similarity index 100%
rename from widgets/table/check-filled.xpm
rename to e-util/check-filled.xpm
diff --git a/widgets/misc/e-action-combo-box.c b/e-util/e-action-combo-box.c
similarity index 100%
rename from widgets/misc/e-action-combo-box.c
rename to e-util/e-action-combo-box.c
diff --git a/e-util/e-action-combo-box.h b/e-util/e-action-combo-box.h
new file mode 100644
index 0000000..43adeaf
--- /dev/null
+++ b/e-util/e-action-combo-box.h
@@ -0,0 +1,89 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* e-action-combo-box.h
+ *
+ * Copyright (C) 2008 Novell, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_ACTION_COMBO_BOX_H
+#define E_ACTION_COMBO_BOX_H
+
+/* This is a GtkComboBox that is driven by a group of GtkRadioActions.
+ * Just plug in a GtkRadioAction and the widget will handle the rest.
+ * (Based on GtkhtmlComboBox.) */
+
+#include <gtk/gtk.h>
+
+/* Standard GObject macros */
+#define E_TYPE_ACTION_COMBO_BOX \
+	(e_action_combo_box_get_type ())
+#define E_ACTION_COMBO_BOX(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_ACTION_COMBO_BOX, EActionComboBox))
+#define E_ACTION_COMBO_BOX_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_ACTION_COMBO_BOX, EActionComboBoxClass))
+#define E_ACTION_IS_COMBO_BOX(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_ACTION_COMBO_BOX))
+#define E_ACTION_IS_COMBO_BOX_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_ACTION_COMBO_BOX))
+#define E_ACTION_COMBO_BOX_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_ACTION_COMBO_BOX, EActionComboBoxClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EActionComboBox EActionComboBox;
+typedef struct _EActionComboBoxClass EActionComboBoxClass;
+typedef struct _EActionComboBoxPrivate EActionComboBoxPrivate;
+
+struct _EActionComboBox {
+	GtkComboBox parent;
+	EActionComboBoxPrivate *priv;
+};
+
+struct _EActionComboBoxClass {
+	GtkComboBoxClass parent_class;
+};
+
+GType		e_action_combo_box_get_type	(void);
+GtkWidget *	e_action_combo_box_new		(void);
+GtkWidget *	e_action_combo_box_new_with_action
+						(GtkRadioAction *action);
+GtkRadioAction *e_action_combo_box_get_action	(EActionComboBox *combo_box);
+void		e_action_combo_box_set_action	(EActionComboBox *combo_box,
+						 GtkRadioAction *action);
+gint		e_action_combo_box_get_current_value
+						(EActionComboBox *combo_box);
+void		e_action_combo_box_set_current_value
+						(EActionComboBox *combo_box,
+						 gint current_value);
+void		e_action_combo_box_add_separator_before
+						(EActionComboBox *combo_box,
+						 gint action_value);
+void		e_action_combo_box_add_separator_after
+						(EActionComboBox *combo_box,
+						 gint action_value);
+
+G_END_DECLS
+
+#endif /* E_ACTION_COMBO_BOX_H */
diff --git a/widgets/misc/e-activity-bar.c b/e-util/e-activity-bar.c
similarity index 100%
rename from widgets/misc/e-activity-bar.c
rename to e-util/e-activity-bar.c
diff --git a/e-util/e-activity-bar.h b/e-util/e-activity-bar.h
new file mode 100644
index 0000000..d56378e
--- /dev/null
+++ b/e-util/e-activity-bar.h
@@ -0,0 +1,71 @@
+/*
+ * e-activity-bar.h
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_ACTIVITY_BAR_H
+#define E_ACTIVITY_BAR_H
+
+#include <gtk/gtk.h>
+#include <e-util/e-activity.h>
+
+/* Standard GObject macros */
+#define E_TYPE_ACTIVITY_BAR \
+	(e_activity_bar_get_type ())
+#define E_ACTIVITY_BAR(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_ACTIVITY_BAR, EActivityBar))
+#define E_ACTIVITY_BAR_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_ACTIVITY_BAR, EActivityBarClass))
+#define E_IS_ACTIVITY_BAR(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_ACTIVITY_BAR))
+#define E_IS_ACTIVITY_BAR_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_ACTIVITY_BAR))
+#define E_ACTIVITY_BAR_GET_CLASS(obj) \
+	(G_TYPE_CHECK_INSTANCE_GET_TYPE \
+	((obj), E_TYPE_ACTIVITY_BAR, EActivityBarClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EActivityBar EActivityBar;
+typedef struct _EActivityBarClass EActivityBarClass;
+typedef struct _EActivityBarPrivate EActivityBarPrivate;
+
+struct _EActivityBar {
+	GtkInfoBar parent;
+	EActivityBarPrivate *priv;
+};
+
+struct _EActivityBarClass {
+	GtkInfoBarClass parent_class;
+};
+
+GType		e_activity_bar_get_type		(void);
+GtkWidget *	e_activity_bar_new		(void);
+EActivity *	e_activity_bar_get_activity	(EActivityBar *bar);
+void		e_activity_bar_set_activity	(EActivityBar *bar,
+						 EActivity *activity);
+
+G_END_DECLS
+
+#endif /* E_ACTIVITY_BAR_H */
diff --git a/widgets/misc/e-activity-proxy.c b/e-util/e-activity-proxy.c
similarity index 100%
rename from widgets/misc/e-activity-proxy.c
rename to e-util/e-activity-proxy.c
diff --git a/e-util/e-activity-proxy.h b/e-util/e-activity-proxy.h
new file mode 100644
index 0000000..7512535
--- /dev/null
+++ b/e-util/e-activity-proxy.h
@@ -0,0 +1,74 @@
+/*
+ * e-activity-proxy.h
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_ACTIVITY_PROXY_H
+#define E_ACTIVITY_PROXY_H
+
+#include <gtk/gtk.h>
+#include <e-util/e-activity.h>
+
+/* Standard GObject macros */
+#define E_TYPE_ACTIVITY_PROXY \
+	(e_activity_proxy_get_type ())
+#define E_ACTIVITY_PROXY(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_ACTIVITY_PROXY, EActivityProxy))
+#define E_ACTIVITY_PROXY_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_ACTIVITY_PROXY, EActivityProxyClass))
+#define E_IS_ACTIVITY_PROXY(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_ACTIVITY_PROXY))
+#define E_IS_ACTIVITY_PROXY_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_ACTIVITY_PROXY))
+#define E_ACTIVITY_PROXY_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_ACTIVITY_PROXY, EActivityProxyClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EActivityProxy EActivityProxy;
+typedef struct _EActivityProxyClass EActivityProxyClass;
+typedef struct _EActivityProxyPrivate EActivityProxyPrivate;
+
+struct _EActivityProxy {
+	GtkFrame parent;
+	EActivityProxyPrivate *priv;
+};
+
+struct _EActivityProxyClass {
+	GtkFrameClass parent_class;
+};
+
+GType		e_activity_proxy_get_type	(void);
+GtkWidget *	e_activity_proxy_new		(EActivity *activity);
+EActivity *	e_activity_proxy_get_activity	(EActivityProxy *proxy);
+void		e_activity_proxy_set_activity	(EActivityProxy *proxy,
+						 EActivity *activity);
+
+G_END_DECLS
+
+#endif /* E_ACTIVITY_PROXY_H */
diff --git a/e-util/e-activity.c b/e-util/e-activity.c
index cd1c569..5eefb65 100644
--- a/e-util/e-activity.c
+++ b/e-util/e-activity.c
@@ -29,8 +29,7 @@
 #include <glib/gi18n.h>
 #include <camel/camel.h>
 
-#include "e-util/e-util.h"
-#include "e-util/e-util-enumtypes.h"
+#include "e-util-enumtypes.h"
 
 #define E_ACTIVITY_GET_PRIVATE(obj) \
 	(G_TYPE_INSTANCE_GET_PRIVATE \
diff --git a/e-util/e-activity.h b/e-util/e-activity.h
index 4cc9951..ac380a0 100644
--- a/e-util/e-activity.h
+++ b/e-util/e-activity.h
@@ -19,11 +19,16 @@
  *
  */
 
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
 #ifndef E_ACTIVITY_H
 #define E_ACTIVITY_H
 
 #include <gtk/gtk.h>
-#include <libevolution-utils/e-alert-sink.h>
+
+#include <e-util/e-alert-sink.h>
 #include <e-util/e-util-enums.h>
 
 /* Standard GObject macros */
diff --git a/widgets/misc/e-alarm-selector.c b/e-util/e-alarm-selector.c
similarity index 100%
rename from widgets/misc/e-alarm-selector.c
rename to e-util/e-alarm-selector.c
diff --git a/e-util/e-alarm-selector.h b/e-util/e-alarm-selector.h
new file mode 100644
index 0000000..c545a46
--- /dev/null
+++ b/e-util/e-alarm-selector.h
@@ -0,0 +1,67 @@
+/*
+ * e-alarm-selector.h
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_ALARM_SELECTOR_H
+#define E_ALARM_SELECTOR_H
+
+#include <e-util/e-source-selector.h>
+
+/* Standard GObject macros */
+#define E_TYPE_ALARM_SELECTOR \
+	(e_alarm_selector_get_type ())
+#define E_ALARM_SELECTOR(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_ALARM_SELECTOR, EAlarmSelector))
+#define E_ALARM_SELECTOR_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_ALARM_SELECTOR, EAlarmSelectorClass))
+#define E_IS_ALARM_SELECTOR(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_ALARM_SELECTOR))
+#define E_IS_ALARM_SELECTOR_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_ALARM_SELECTOR))
+#define E_ALARM_SELECTOR_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_ALARM_SELECTOR, EAlarmSelectorClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EAlarmSelector EAlarmSelector;
+typedef struct _EAlarmSelectorClass EAlarmSelectorClass;
+typedef struct _EAlarmSelectorPrivate EAlarmSelectorPrivate;
+
+struct _EAlarmSelector {
+	ESourceSelector parent;
+	EAlarmSelectorPrivate *priv;
+};
+
+struct _EAlarmSelectorClass {
+	ESourceSelectorClass parent_class;
+};
+
+GType		e_alarm_selector_get_type	(void) G_GNUC_CONST;
+GtkWidget *	e_alarm_selector_new		(ESourceRegistry *registry);
+
+G_END_DECLS
+
+#endif /* E_ALARM_SELECTOR_H */
diff --git a/widgets/misc/e-alert-bar.c b/e-util/e-alert-bar.c
similarity index 100%
rename from widgets/misc/e-alert-bar.c
rename to e-util/e-alert-bar.c
diff --git a/e-util/e-alert-bar.h b/e-util/e-alert-bar.h
new file mode 100644
index 0000000..ae5b315
--- /dev/null
+++ b/e-util/e-alert-bar.h
@@ -0,0 +1,72 @@
+/*
+ * e-alert-bar.h
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_ALERT_BAR_H
+#define E_ALERT_BAR_H
+
+#include <gtk/gtk.h>
+
+#include <e-util/e-alert.h>
+
+/* Standard GObject macros */
+#define E_TYPE_ALERT_BAR \
+	(e_alert_bar_get_type ())
+#define E_ALERT_BAR(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_ALERT_BAR, EAlertBar))
+#define E_ALERT_BAR_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_ALERT_BAR, EAlertBarClass))
+#define E_IS_ALERT_BAR(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_ALERT_BAR))
+#define E_IS_ALERT_BAR_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_ALERT_BAR))
+#define E_ALERT_BAR_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_ALERT_BAR, EAlertBarClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EAlertBar EAlertBar;
+typedef struct _EAlertBarClass EAlertBarClass;
+typedef struct _EAlertBarPrivate EAlertBarPrivate;
+
+struct _EAlertBar {
+	GtkInfoBar parent;
+	EAlertBarPrivate *priv;
+};
+
+struct _EAlertBarClass {
+	GtkInfoBarClass parent_class;
+};
+
+GType		e_alert_bar_get_type		(void);
+GtkWidget *	e_alert_bar_new			(void);
+void		e_alert_bar_clear		(EAlertBar *alert_bar);
+void		e_alert_bar_add_alert		(EAlertBar *alert_bar,
+						 EAlert *alert);
+
+G_END_DECLS
+
+#endif /* E_ALERT_BAR_H */
diff --git a/libevolution-utils/e-alert-dialog.c b/e-util/e-alert-dialog.c
similarity index 100%
rename from libevolution-utils/e-alert-dialog.c
rename to e-util/e-alert-dialog.c
diff --git a/e-util/e-alert-dialog.h b/e-util/e-alert-dialog.h
new file mode 100644
index 0000000..3d2662a
--- /dev/null
+++ b/e-util/e-alert-dialog.h
@@ -0,0 +1,81 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *   Michael Zucchi <notzed ximian com>
+ *   Jonathon Jongsma <jonathon jongsma collabora co uk>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ * Copyright (C) 2009 Intel Corporation
+ */
+
+#ifndef E_ALERT_DIALOG_H
+#define E_ALERT_DIALOG_H
+
+#include <gtk/gtk.h>
+
+#include <e-util/e-alert.h>
+
+/* Standard GObject macros */
+#define E_TYPE_ALERT_DIALOG \
+	(e_alert_dialog_get_type ())
+#define E_ALERT_DIALOG(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_ALERT_DIALOG, EAlertDialog))
+#define E_ALERT_DIALOG_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_ALERT_DIALOG, EAlertDialogClass))
+#define E_IS_ALERT_DIALOG(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_ALERT_DIALOG))
+#define E_IS_ALERT_DIALOG_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_ALERT_DIALOG))
+#define E_ALERT_DIALOG_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_ALERT_DIALOG, EAlertDialogClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EAlertDialog EAlertDialog;
+typedef struct _EAlertDialogClass EAlertDialogClass;
+typedef struct _EAlertDialogPrivate EAlertDialogPrivate;
+
+struct _EAlertDialog {
+	GtkDialog parent;
+	EAlertDialogPrivate *priv;
+};
+
+struct _EAlertDialogClass {
+	GtkDialogClass parent_class;
+};
+
+GType		e_alert_dialog_get_type		(void);
+GtkWidget *	e_alert_dialog_new		(GtkWindow *parent,
+						 EAlert *alert);
+GtkWidget *	e_alert_dialog_new_for_args	(GtkWindow *parent,
+						 const gchar *tag,
+						 ...) G_GNUC_NULL_TERMINATED;
+gint		e_alert_run_dialog		(GtkWindow *parent,
+						 EAlert *alert);
+gint		e_alert_run_dialog_for_args	(GtkWindow *parent,
+						 const gchar *tag,
+						 ...) G_GNUC_NULL_TERMINATED;
+EAlert *	e_alert_dialog_get_alert	(EAlertDialog *dialog);
+GtkWidget *	e_alert_dialog_get_content_area	(EAlertDialog *dialog);
+
+G_END_DECLS
+
+#endif /* E_ALERT_DIALOG_H */
diff --git a/e-util/e-alert-sink.c b/e-util/e-alert-sink.c
new file mode 100644
index 0000000..3077261
--- /dev/null
+++ b/e-util/e-alert-sink.c
@@ -0,0 +1,93 @@
+/*
+ * e-alert-sink.c
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+/**
+ * SECTION: e-alert-sink
+ * @short_description: an interface to handle alerts
+ * @include: e-util/e-util.h
+ *
+ * A widget that implements #EAlertSink means it can handle #EAlerts,
+ * usually by displaying them to the user.
+ **/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-alert-sink.h"
+
+#include "e-alert-dialog.h"
+
+G_DEFINE_INTERFACE (
+	EAlertSink,
+	e_alert_sink,
+	GTK_TYPE_WIDGET)
+
+static void
+alert_sink_fallback (GtkWidget *widget,
+                     EAlert *alert)
+{
+	GtkWidget *dialog;
+	gpointer parent;
+
+	parent = gtk_widget_get_toplevel (widget);
+	parent = gtk_widget_is_toplevel (parent) ? parent : NULL;
+
+	dialog = e_alert_dialog_new (parent, alert);
+	gtk_dialog_run (GTK_DIALOG (dialog));
+	gtk_widget_destroy (dialog);
+}
+
+static void
+alert_sink_submit_alert (EAlertSink *alert_sink,
+                         EAlert *alert)
+{
+	/* This is just a lame fallback handler.  Implementors
+	 * are strongly encouraged to override this method. */
+	alert_sink_fallback (GTK_WIDGET (alert_sink), alert);
+}
+
+static void
+e_alert_sink_default_init (EAlertSinkInterface *interface)
+{
+	interface->submit_alert = alert_sink_submit_alert;
+}
+
+/**
+ * e_alert_sink_submit_alert:
+ * @alert_sink: an #EAlertSink
+ * @alert: an #EAlert
+ *
+ * This function is a place to pass #EAlert objects.  Beyond that it has no
+ * well-defined behavior.  It's up to the widget implementing the #EAlertSink
+ * interface to decide what to do with them.
+ **/
+void
+e_alert_sink_submit_alert (EAlertSink *alert_sink,
+                           EAlert *alert)
+{
+	EAlertSinkInterface *interface;
+
+	g_return_if_fail (E_IS_ALERT_SINK (alert_sink));
+	g_return_if_fail (E_IS_ALERT (alert));
+
+	interface = E_ALERT_SINK_GET_INTERFACE (alert_sink);
+	g_return_if_fail (interface->submit_alert != NULL);
+
+	interface->submit_alert (alert_sink, alert);
+}
diff --git a/e-util/e-alert-sink.h b/e-util/e-alert-sink.h
new file mode 100644
index 0000000..c8fd512
--- /dev/null
+++ b/e-util/e-alert-sink.h
@@ -0,0 +1,63 @@
+/*
+ * e-alert-sink.h
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#ifndef E_ALERT_SINK_H
+#define E_ALERT_SINK_H
+
+#include <gtk/gtk.h>
+
+#include <e-util/e-alert.h>
+
+/* Standard GObject macros */
+#define E_TYPE_ALERT_SINK \
+	(e_alert_sink_get_type ())
+#define E_ALERT_SINK(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_ALERT_SINK, EAlertSink))
+#define E_ALERT_SINK_INTERFACE(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_ALERT_SINK, EAlertSinkInterface))
+#define E_IS_ALERT_SINK(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_ALERT_SINK))
+#define E_IS_ALERT_SINK_INTERFACE(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_ALERT_SINK))
+#define E_ALERT_SINK_GET_INTERFACE(obj) \
+	(G_TYPE_INSTANCE_GET_INTERFACE \
+	((obj), E_TYPE_ALERT_SINK, EAlertSinkInterface))
+
+G_BEGIN_DECLS
+
+typedef struct _EAlertSink EAlertSink;
+typedef struct _EAlertSinkInterface EAlertSinkInterface;
+
+struct _EAlertSinkInterface {
+	GTypeInterface parent_interface;
+
+	void		(*submit_alert)		(EAlertSink *alert_sink,
+						 EAlert *alert);
+};
+
+GType		e_alert_sink_get_type		(void);
+void		e_alert_sink_submit_alert	(EAlertSink *alert_sink,
+						 EAlert *alert);
+
+G_END_DECLS
+
+#endif /* E_ALERT_SINK_H */
diff --git a/libevolution-utils/e-alert.c b/e-util/e-alert.c
similarity index 100%
rename from libevolution-utils/e-alert.c
rename to e-util/e-alert.c
diff --git a/libevolution-utils/e-alert.h b/e-util/e-alert.h
similarity index 100%
rename from libevolution-utils/e-alert.h
rename to e-util/e-alert.h
diff --git a/widgets/misc/e-attachment-bar.c b/e-util/e-attachment-bar.c
similarity index 100%
rename from widgets/misc/e-attachment-bar.c
rename to e-util/e-attachment-bar.c
diff --git a/e-util/e-attachment-bar.h b/e-util/e-attachment-bar.h
new file mode 100644
index 0000000..9f35ae2
--- /dev/null
+++ b/e-util/e-attachment-bar.h
@@ -0,0 +1,83 @@
+/*
+ * e-attachment-bar.h
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_ATTACHMENT_BAR_H
+#define E_ATTACHMENT_BAR_H
+
+#include <gtk/gtk.h>
+#include <e-util/e-attachment-view.h>
+
+/* Standard GObject macros */
+#define E_TYPE_ATTACHMENT_BAR \
+	(e_attachment_bar_get_type ())
+#define E_ATTACHMENT_BAR(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_ATTACHMENT_BAR, EAttachmentBar))
+#define E_ATTACHMENT_BAR_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_ATTACHMENT_BAR, EAttachmentBarClass))
+#define E_IS_ATTACHMENT_BAR(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_ATTACHMENT_BAR))
+#define E_IS_ATTACHMENT_BAR_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_ATTACHMENT_BAR))
+#define E_ATTACHMENT_BAR_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_ATTACHMENT_BAR, EAttachmentBarClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EAttachmentBar EAttachmentBar;
+typedef struct _EAttachmentBarClass EAttachmentBarClass;
+typedef struct _EAttachmentBarPrivate EAttachmentBarPrivate;
+
+struct _EAttachmentBar {
+	GtkBox parent;
+	EAttachmentBarPrivate *priv;
+};
+
+struct _EAttachmentBarClass {
+	GtkBoxClass parent_class;
+};
+
+GType		e_attachment_bar_get_type	(void);
+GtkWidget *	e_attachment_bar_new		(EAttachmentStore *store);
+gint		e_attachment_bar_get_active_view
+						(EAttachmentBar *bar);
+void		e_attachment_bar_set_active_view
+						(EAttachmentBar *bar,
+						 gint active_view);
+gboolean	e_attachment_bar_get_expanded
+						(EAttachmentBar *bar);
+void		e_attachment_bar_set_expanded
+						(EAttachmentBar *bar,
+						 gboolean expanded);
+EAttachmentStore *
+		e_attachment_bar_get_store	(EAttachmentBar *bar);
+
+G_END_DECLS
+
+#endif /* E_ATTACHMENT_BAR_H */
diff --git a/widgets/misc/e-attachment-button.c b/e-util/e-attachment-button.c
similarity index 100%
rename from widgets/misc/e-attachment-button.c
rename to e-util/e-attachment-button.c
diff --git a/e-util/e-attachment-button.h b/e-util/e-attachment-button.h
new file mode 100644
index 0000000..abe5fa4
--- /dev/null
+++ b/e-util/e-attachment-button.h
@@ -0,0 +1,91 @@
+/*
+ * e-attachment-button.h
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_ATTACHMENT_BUTTON_H
+#define E_ATTACHMENT_BUTTON_H
+
+#include <gtk/gtk.h>
+#include <e-util/e-attachment.h>
+#include <e-util/e-attachment-view.h>
+
+/* Standard GObject macros */
+#define E_TYPE_ATTACHMENT_BUTTON \
+	(e_attachment_button_get_type ())
+#define E_ATTACHMENT_BUTTON(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_ATTACHMENT_BUTTON, EAttachmentButton))
+#define E_ATTACHMENT_BUTTON_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_ATTACHMENT_BUTTON, EAttachmentButtonClass))
+#define E_IS_ATTACHMENT_BUTTON(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_ATTACHMENT_BUTTON))
+#define E_IS_ATTACHMENT_BUTTON_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_ATTACHMENT_BUTTON))
+#define E_ATTACHMENT_BUTTON_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_ATTACHMENT_BUTTON, EAttachmentButtonClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EAttachmentButton EAttachmentButton;
+typedef struct _EAttachmentButtonClass EAttachmentButtonClass;
+typedef struct _EAttachmentButtonPrivate EAttachmentButtonPrivate;
+
+struct _EAttachmentButton {
+	GtkBox parent;
+	EAttachmentButtonPrivate *priv;
+};
+
+struct _EAttachmentButtonClass {
+	GtkBoxClass parent_class;
+};
+
+GType		e_attachment_button_get_type	(void);
+GtkWidget *	e_attachment_button_new	(void);
+EAttachmentView *
+		e_attachment_button_get_view	(EAttachmentButton *button);
+void		e_attachment_button_set_view	(EAttachmentButton *button,
+						 EAttachmentView *view);
+EAttachment *	e_attachment_button_get_attachment
+						(EAttachmentButton *button);
+void		e_attachment_button_set_attachment
+						(EAttachmentButton *button,
+						 EAttachment *attachment);
+gboolean	e_attachment_button_get_expandable
+						(EAttachmentButton *button);
+void		e_attachment_button_set_expandable
+						(EAttachmentButton *button,
+						 gboolean expandable);
+gboolean	e_attachment_button_get_expanded
+						(EAttachmentButton *button);
+void		e_attachment_button_set_expanded
+						(EAttachmentButton *button,
+						 gboolean expanded);
+
+G_END_DECLS
+
+#endif /* E_ATTACHMENT_BUTTON_H */
diff --git a/widgets/misc/e-attachment-dialog.c b/e-util/e-attachment-dialog.c
similarity index 100%
rename from widgets/misc/e-attachment-dialog.c
rename to e-util/e-attachment-dialog.c
diff --git a/e-util/e-attachment-dialog.h b/e-util/e-attachment-dialog.h
new file mode 100644
index 0000000..af71411
--- /dev/null
+++ b/e-util/e-attachment-dialog.h
@@ -0,0 +1,77 @@
+/*
+ * e-attachment-dialog.h
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_ATTACHMENT_DIALOG_H
+#define E_ATTACHMENT_DIALOG_H
+
+#include <gtk/gtk.h>
+#include <e-util/e-attachment.h>
+
+/* Standard GObject macros */
+#define E_TYPE_ATTACHMENT_DIALOG \
+	(e_attachment_dialog_get_type ())
+#define E_ATTACHMENT_DIALOG(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_ATTACHMENT_DIALOG, EAttachmentDialog))
+#define E_ATTACHMENT_DIALOG_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_ATTACHMENT_DIALOG, EAttachmentDialogClass))
+#define E_IS_ATTACHMENT_DIALOG(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_ATTACHMENT_DIALOG))
+#define E_IS_ATTACHMENT_DIALOG_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_ATTACHMENT_DIALOG))
+#define E_ATTACHMENT_DIALOG_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_ATTACHMENT_DIALOG, EAttachmentDialogClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EAttachmentDialog EAttachmentDialog;
+typedef struct _EAttachmentDialogClass EAttachmentDialogClass;
+typedef struct _EAttachmentDialogPrivate EAttachmentDialogPrivate;
+
+struct _EAttachmentDialog {
+	GtkDialog parent;
+	EAttachmentDialogPrivate *priv;
+};
+
+struct _EAttachmentDialogClass {
+	GtkDialogClass parent_class;
+};
+
+GType		e_attachment_dialog_get_type	(void);
+GtkWidget *	e_attachment_dialog_new		(GtkWindow *parent,
+						 EAttachment *attachment);
+EAttachment *	e_attachment_dialog_get_attachment
+						(EAttachmentDialog *dialog);
+void		e_attachment_dialog_set_attachment
+						(EAttachmentDialog *dialog,
+						 EAttachment *attachment);
+
+G_END_DECLS
+
+#endif /* E_ATTACHMENT_DIALOG_H */
diff --git a/e-util/e-attachment-handler-image.c b/e-util/e-attachment-handler-image.c
new file mode 100644
index 0000000..36c3a83
--- /dev/null
+++ b/e-util/e-attachment-handler-image.c
@@ -0,0 +1,246 @@
+/*
+ * e-attachment-handler-image.c
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-attachment-handler-image.h"
+
+#include <glib/gi18n.h>
+#include <gdesktop-enums.h>
+
+#define E_ATTACHMENT_HANDLER_IMAGE_GET_PRIVATE(obj) \
+	(G_TYPE_INSTANCE_GET_PRIVATE \
+	((obj), E_TYPE_ATTACHMENT_HANDLER_IMAGE, EAttachmentHandlerImagePrivate))
+
+struct _EAttachmentHandlerImagePrivate {
+	gint placeholder;
+};
+
+static const gchar *ui =
+"<ui>"
+"  <popup name='context'>"
+"    <placeholder name='custom-actions'>"
+"      <menuitem action='image-set-as-background'/>"
+"    </placeholder>"
+"  </popup>"
+"</ui>";
+
+G_DEFINE_TYPE (
+	EAttachmentHandlerImage,
+	e_attachment_handler_image,
+	E_TYPE_ATTACHMENT_HANDLER)
+
+static void
+action_image_set_as_background_saved_cb (EAttachment *attachment,
+                                         GAsyncResult *result,
+                                         EAttachmentHandler *handler)
+{
+	GDesktopBackgroundStyle style;
+	EAttachmentView *view;
+	GSettings *settings;
+	GtkWidget *dialog;
+	GFile *file;
+	gpointer parent;
+	gchar *uri;
+	GError *error = NULL;
+
+	view = e_attachment_handler_get_view (handler);
+	settings = g_settings_new ("org.gnome.desktop.background");
+
+	file = e_attachment_save_finish (attachment, result, &error);
+
+	if (error != NULL)
+		goto error;
+
+	uri = g_file_get_uri (file);
+	g_settings_set_string (settings, "picture-uri", uri);
+	g_free (uri);
+
+	style = g_settings_get_enum (settings, "picture-options");
+	if (style == G_DESKTOP_BACKGROUND_STYLE_NONE)
+		g_settings_set_enum (
+			settings, "picture-options",
+			G_DESKTOP_BACKGROUND_STYLE_WALLPAPER);
+
+	g_object_unref (file);
+
+	goto exit;
+
+error:
+	parent = gtk_widget_get_toplevel (GTK_WIDGET (view));
+	parent = gtk_widget_is_toplevel (parent) ? parent : NULL;
+
+	dialog = gtk_message_dialog_new_with_markup (
+		parent, GTK_DIALOG_DESTROY_WITH_PARENT,
+		GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
+		"<big><b>%s</b></big>",
+		_("Could not set as background"));
+
+	gtk_message_dialog_format_secondary_text (
+		GTK_MESSAGE_DIALOG (dialog), "%s", error->message);
+
+	gtk_dialog_run (GTK_DIALOG (dialog));
+
+	gtk_widget_destroy (dialog);
+	g_error_free (error);
+
+exit:
+	g_object_unref (settings);
+	g_object_unref (handler);
+}
+
+static void
+action_image_set_as_background_cb (GtkAction *action,
+                                   EAttachmentHandler *handler)
+{
+	EAttachmentView *view;
+	EAttachment *attachment;
+	GFile *destination;
+	GList *selected;
+	const gchar *path;
+
+	view = e_attachment_handler_get_view (handler);
+	selected = e_attachment_view_get_selected_attachments (view);
+	g_return_if_fail (g_list_length (selected) == 1);
+	attachment = E_ATTACHMENT (selected->data);
+
+	/* Save the image under the user's Pictures directory. */
+	path = g_get_user_special_dir (G_USER_DIRECTORY_PICTURES);
+	destination = g_file_new_for_path (path);
+	g_mkdir_with_parents (path, 0755);
+
+	e_attachment_save_async (
+		attachment, destination, (GAsyncReadyCallback)
+		action_image_set_as_background_saved_cb,
+		g_object_ref (handler));
+
+	g_object_unref (destination);
+
+	g_list_foreach (selected, (GFunc) g_object_unref, NULL);
+	g_list_free (selected);
+}
+
+static GtkActionEntry standard_entries[] = {
+
+	{ "image-set-as-background",
+	  NULL,
+	  N_("Set as _Background"),
+	  NULL,
+	  NULL,  /* XXX Add a tooltip! */
+	  G_CALLBACK (action_image_set_as_background_cb) }
+};
+
+static void
+attachment_handler_image_update_actions_cb (EAttachmentView *view,
+                                            EAttachmentHandler *handler)
+{
+	EAttachment *attachment;
+	GFileInfo *file_info;
+	GtkActionGroup *action_group;
+	const gchar *content_type;
+	gchar *mime_type;
+	GList *selected;
+	gboolean visible = FALSE;
+
+	selected = e_attachment_view_get_selected_attachments (view);
+
+	if (g_list_length (selected) != 1)
+		goto exit;
+
+	attachment = E_ATTACHMENT (selected->data);
+	file_info = e_attachment_get_file_info (attachment);
+
+	if (file_info == NULL)
+		goto exit;
+
+	if (e_attachment_get_loading (attachment))
+		goto exit;
+
+	if (e_attachment_get_saving (attachment))
+		goto exit;
+
+	content_type = g_file_info_get_content_type (file_info);
+
+	mime_type = g_content_type_get_mime_type (content_type);
+	visible = (g_ascii_strncasecmp (mime_type, "image/", 6) == 0);
+	g_free (mime_type);
+
+exit:
+	action_group = e_attachment_view_get_action_group (view, "image");
+	gtk_action_group_set_visible (action_group, visible);
+
+	g_list_foreach (selected, (GFunc) g_object_unref, NULL);
+	g_list_free (selected);
+}
+
+static void
+attachment_handler_image_constructed (GObject *object)
+{
+	EAttachmentHandler *handler;
+	EAttachmentView *view;
+	GtkActionGroup *action_group;
+	GtkUIManager *ui_manager;
+	GError *error = NULL;
+
+	handler = E_ATTACHMENT_HANDLER (object);
+
+	/* Chain up to parent's constructed() method. */
+	G_OBJECT_CLASS (e_attachment_handler_image_parent_class)->constructed (object);
+
+	view = e_attachment_handler_get_view (handler);
+
+	action_group = e_attachment_view_add_action_group (view, "image");
+	gtk_action_group_add_actions (
+		action_group, standard_entries,
+		G_N_ELEMENTS (standard_entries), object);
+
+	ui_manager = e_attachment_view_get_ui_manager (view);
+	gtk_ui_manager_add_ui_from_string (ui_manager, ui, -1, &error);
+
+	if (error != NULL) {
+		g_warning ("%s", error->message);
+		g_error_free (error);
+	}
+
+	g_signal_connect (
+		view, "update-actions",
+		G_CALLBACK (attachment_handler_image_update_actions_cb),
+		object);
+}
+
+static void
+e_attachment_handler_image_class_init (EAttachmentHandlerImageClass *class)
+{
+	GObjectClass *object_class;
+
+	g_type_class_add_private (class, sizeof (EAttachmentHandlerImagePrivate));
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->constructed = attachment_handler_image_constructed;
+}
+
+static void
+e_attachment_handler_image_init (EAttachmentHandlerImage *handler)
+{
+	handler->priv = E_ATTACHMENT_HANDLER_IMAGE_GET_PRIVATE (handler);
+}
diff --git a/e-util/e-attachment-handler-image.h b/e-util/e-attachment-handler-image.h
new file mode 100644
index 0000000..e0e0cb3
--- /dev/null
+++ b/e-util/e-attachment-handler-image.h
@@ -0,0 +1,69 @@
+/*
+ * e-attachment-handler-image.h
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_ATTACHMENT_HANDLER_IMAGE_H
+#define E_ATTACHMENT_HANDLER_IMAGE_H
+
+#include <e-util/e-attachment-handler.h>
+
+/* Standard GObject macros */
+#define E_TYPE_ATTACHMENT_HANDLER_IMAGE \
+	(e_attachment_handler_image_get_type ())
+#define E_ATTACHMENT_HANDLER_IMAGE(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_ATTACHMENT_HANDLER_IMAGE, EAttachmentHandlerImage))
+#define E_ATTACHMENT_HANDLER_IMAGE_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_ATTACHMENT_HANDLER_IMAGE, EAttachmentHandlerImageClass))
+#define E_IS_ATTACHMENT_HANDLER_IMAGE(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_ATTACHMENT_HANDLER_IMAGE))
+#define E_IS_ATTACHMENT_HANDLER_IMAGE_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_ATTACHMENT_HANDLER_IMAGE))
+#define E_ATTACHMENT_HANDLER_IMAGE_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_ATTACHMENT_HANDLER_IMAGE, EAttachmentHandlerImageClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EAttachmentHandlerImage EAttachmentHandlerImage;
+typedef struct _EAttachmentHandlerImageClass EAttachmentHandlerImageClass;
+typedef struct _EAttachmentHandlerImagePrivate EAttachmentHandlerImagePrivate;
+
+struct _EAttachmentHandlerImage {
+	EAttachmentHandler parent;
+	EAttachmentHandlerImagePrivate *priv;
+};
+
+struct _EAttachmentHandlerImageClass {
+	EAttachmentHandlerClass parent_class;
+};
+
+GType		e_attachment_handler_image_get_type	(void);
+
+G_END_DECLS
+
+#endif /* E_ATTACHMENT_HANDLER_IMAGE_H */
diff --git a/widgets/misc/e-attachment-handler-sendto.c b/e-util/e-attachment-handler-sendto.c
similarity index 100%
rename from widgets/misc/e-attachment-handler-sendto.c
rename to e-util/e-attachment-handler-sendto.c
diff --git a/e-util/e-attachment-handler-sendto.h b/e-util/e-attachment-handler-sendto.h
new file mode 100644
index 0000000..17115c4
--- /dev/null
+++ b/e-util/e-attachment-handler-sendto.h
@@ -0,0 +1,66 @@
+/*
+ * e-attachment-handler-sendto.h
+ *
+ * Copyright (C) 2009 Matthew Barnes
+ *
+ * This program 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 program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_ATTACHMENT_HANDLER_SENDTO_H
+#define E_ATTACHMENT_HANDLER_SENDTO_H
+
+#include <e-util/e-attachment-handler.h>
+
+/* Standard GObject macros */
+#define E_TYPE_ATTACHMENT_HANDLER_SENDTO \
+	(e_attachment_handler_sendto_get_type ())
+#define E_ATTACHMENT_HANDLER_SENDTO(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_ATTACHMENT_HANDLER_SENDTO, EAttachmentHandlerSendto))
+#define E_ATTACHMENT_HANDLER_SENDTO_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_ATTACHMENT_HANDLER_SENDTO, EAttachmentHandlerSendtoClass))
+#define E_IS_ATTACHMENT_HANDLER_SENDTO(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_ATTACHMENT_HANDLER_SENDTO))
+#define E_IS_ATTACHMENT_HANDLER_SENDTO_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_ATTACHMENT_HANDLER_SENDTO))
+#define E_ATTACHMENT_HANDLER_SENDTO_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_ATTACHMENT_HANDLER_SENDTO, EAttachmentHandlerSendtoClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EAttachmentHandlerSendto EAttachmentHandlerSendto;
+typedef struct _EAttachmentHandlerSendtoClass EAttachmentHandlerSendtoClass;
+
+struct _EAttachmentHandlerSendto {
+	EAttachmentHandler parent;
+};
+
+struct _EAttachmentHandlerSendtoClass {
+	EAttachmentHandlerClass parent_class;
+};
+
+GType		e_attachment_handler_sendto_get_type	(void);
+
+G_END_DECLS
+
+#endif /* E_ATTACHMENT_HANDLER_SENDTO_H */
diff --git a/widgets/misc/e-attachment-handler.c b/e-util/e-attachment-handler.c
similarity index 100%
rename from widgets/misc/e-attachment-handler.c
rename to e-util/e-attachment-handler.c
diff --git a/e-util/e-attachment-handler.h b/e-util/e-attachment-handler.h
new file mode 100644
index 0000000..086ba8f
--- /dev/null
+++ b/e-util/e-attachment-handler.h
@@ -0,0 +1,84 @@
+/*
+ * e-attachment-handler.h
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_ATTACHMENT_HANDLER_H
+#define E_ATTACHMENT_HANDLER_H
+
+#include <libebackend/libebackend.h>
+
+#include <e-util/e-attachment-view.h>
+
+/* Standard GObject macros */
+#define E_TYPE_ATTACHMENT_HANDLER \
+	(e_attachment_handler_get_type ())
+#define E_ATTACHMENT_HANDLER(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_ATTACHMENT_HANDLER, EAttachmentHandler))
+#define E_ATTACHMENT_HANDLER_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_ATTACHMENT_HANDLER, EAttachmentHandlerClass))
+#define E_IS_ATTACHMENT_HANDLER(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_ATTACHMENT_HANDLER))
+#define E_IS_ATTACHMENT_HANDLER_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_ATTACHMENT_HANDLER))
+#define E_ATTACHMENT_HANDLER_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_ATTACHMENT_HANDLER, EAttachmentHandlerClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EAttachmentHandler EAttachmentHandler;
+typedef struct _EAttachmentHandlerClass EAttachmentHandlerClass;
+typedef struct _EAttachmentHandlerPrivate EAttachmentHandlerPrivate;
+
+struct _EAttachmentHandler {
+	EExtension parent;
+	EAttachmentHandlerPrivate *priv;
+};
+
+struct _EAttachmentHandlerClass {
+	EExtensionClass parent_class;
+
+	GdkDragAction	(*get_drag_actions)	(EAttachmentHandler *handler);
+	const GtkTargetEntry *
+			(*get_target_table)	(EAttachmentHandler *handler,
+						 guint *n_targets);
+};
+
+GType		e_attachment_handler_get_type	(void);
+EAttachmentView *
+		e_attachment_handler_get_view	(EAttachmentHandler *handler);
+GdkDragAction	e_attachment_handler_get_drag_actions
+						(EAttachmentHandler *handler);
+const GtkTargetEntry *
+		e_attachment_handler_get_target_table
+						(EAttachmentHandler *handler,
+						 guint *n_targets);
+
+G_END_DECLS
+
+#endif /* E_ATTACHMENT_HANDLER_H */
diff --git a/widgets/misc/e-attachment-icon-view.c b/e-util/e-attachment-icon-view.c
similarity index 100%
rename from widgets/misc/e-attachment-icon-view.c
rename to e-util/e-attachment-icon-view.c
diff --git a/e-util/e-attachment-icon-view.h b/e-util/e-attachment-icon-view.h
new file mode 100644
index 0000000..bd3d210
--- /dev/null
+++ b/e-util/e-attachment-icon-view.h
@@ -0,0 +1,71 @@
+/*
+ * e-attachment-icon-view.h
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_ATTACHMENT_ICON_VIEW_H
+#define E_ATTACHMENT_ICON_VIEW_H
+
+#include <gtk/gtk.h>
+
+/* Standard GObject macros */
+#define E_TYPE_ATTACHMENT_ICON_VIEW \
+	(e_attachment_icon_view_get_type ())
+#define E_ATTACHMENT_ICON_VIEW(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_ATTACHMENT_ICON_VIEW, EAttachmentIconView))
+#define E_ATTACHMENT_ICON_VIEW_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_ATTACHMENT_ICON_VIEW, EAttachmentIconView))
+#define E_IS_ATTACHMENT_ICON_VIEW(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_ATTACHMENT_ICON_VIEW))
+#define E_IS_ATTACHMENT_ICON_VIEW_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_ATTACHMENT_ICON_VIEW))
+#define E_ATTACHMENT_ICON_VIEW_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_ATTACHMENT_ICON_VIEW))
+
+G_BEGIN_DECLS
+
+typedef struct _EAttachmentIconView EAttachmentIconView;
+typedef struct _EAttachmentIconViewClass EAttachmentIconViewClass;
+typedef struct _EAttachmentIconViewPrivate EAttachmentIconViewPrivate;
+
+struct _EAttachmentIconView {
+	GtkIconView parent;
+	EAttachmentIconViewPrivate *priv;
+};
+
+struct _EAttachmentIconViewClass {
+	GtkIconViewClass parent_class;
+};
+
+GType		e_attachment_icon_view_get_type		(void);
+GtkWidget *	e_attachment_icon_view_new		(void);
+void		e_attachment_icon_view_set_default_icon_size
+							(gint size);
+G_END_DECLS
+
+#endif /* E_ATTACHMENT_ICON_VIEW_H */
diff --git a/widgets/misc/e-attachment-paned.c b/e-util/e-attachment-paned.c
similarity index 100%
rename from widgets/misc/e-attachment-paned.c
rename to e-util/e-attachment-paned.c
diff --git a/e-util/e-attachment-paned.h b/e-util/e-attachment-paned.h
new file mode 100644
index 0000000..af44cd6
--- /dev/null
+++ b/e-util/e-attachment-paned.h
@@ -0,0 +1,99 @@
+/*
+ * e-attachment-paned.h
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_ATTACHMENT_PANED_H
+#define E_ATTACHMENT_PANED_H
+
+#include <gtk/gtk.h>
+
+/* Standard GObject macros */
+#define E_TYPE_ATTACHMENT_PANED \
+	(e_attachment_paned_get_type ())
+#define E_ATTACHMENT_PANED(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_ATTACHMENT_PANED, EAttachmentPaned))
+#define E_ATTACHMENT_PANED_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_ATTACHMENT_PANED, EAttachmentPanedClass))
+#define E_IS_ATTACHMENT_PANED(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_ATTACHMENT_PANED))
+#define E_IS_ATTACHMENT_PANED_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_ATTACHMENT_PANED))
+#define E_ATTACHMENT_PANED_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_ATTACHMENT_PANED, EAttachmentPanedClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EAttachmentPaned EAttachmentPaned;
+typedef struct _EAttachmentPanedClass EAttachmentPanedClass;
+typedef struct _EAttachmentPanedPrivate EAttachmentPanedPrivate;
+
+struct _EAttachmentPaned {
+	GtkVPaned parent;
+	EAttachmentPanedPrivate *priv;
+};
+
+struct _EAttachmentPanedClass {
+	GtkVPanedClass parent_class;
+};
+
+GType		e_attachment_paned_get_type	(void);
+GtkWidget *	e_attachment_paned_new		(void);
+GtkWidget *	e_attachment_paned_get_content_area
+						(EAttachmentPaned *paned);
+gint		e_attachment_paned_get_active_view
+						(EAttachmentPaned *paned);
+void		e_attachment_paned_set_active_view
+						(EAttachmentPaned *paned,
+						 gint active_view);
+gboolean	e_attachment_paned_get_expanded	(EAttachmentPaned *paned);
+void		e_attachment_paned_set_expanded	(EAttachmentPaned *paned,
+						 gboolean expanded);
+gboolean	e_attachment_paned_get_resize_toplevel
+						(EAttachmentPaned *paned);
+void		e_attachment_paned_set_resize_toplevel
+						(EAttachmentPaned *paned,
+						 gboolean resize_toplevel);
+void		e_attachment_paned_drag_data_received
+						(EAttachmentPaned *paned,
+						 GdkDragContext *context,
+						 gint x,
+						 gint y,
+						 GtkSelectionData *selection,
+						 guint info,
+						 guint time);
+GtkWidget *	e_attachment_paned_get_controls_container
+						(EAttachmentPaned *paned);
+GtkWidget *	e_attachment_paned_get_view_combo
+						(EAttachmentPaned *paned);
+void		e_attachment_paned_set_default_height
+						(gint height);
+
+G_END_DECLS
+
+#endif /* E_ATTACHMENT_PANED_H */
diff --git a/e-util/e-attachment-store.c b/e-util/e-attachment-store.c
new file mode 100644
index 0000000..f434f5e
--- /dev/null
+++ b/e-util/e-attachment-store.c
@@ -0,0 +1,1280 @@
+/*
+ * e-attachment-store.c
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-attachment-store.h"
+
+#include <errno.h>
+#include <glib/gi18n.h>
+
+#include "e-mktemp.h"
+
+#define E_ATTACHMENT_STORE_GET_PRIVATE(obj) \
+	(G_TYPE_INSTANCE_GET_PRIVATE \
+	((obj), E_TYPE_ATTACHMENT_STORE, EAttachmentStorePrivate))
+
+struct _EAttachmentStorePrivate {
+	GHashTable *attachment_index;
+
+	guint ignore_row_changed : 1;
+};
+
+enum {
+	PROP_0,
+	PROP_NUM_ATTACHMENTS,
+	PROP_NUM_LOADING,
+	PROP_TOTAL_SIZE
+};
+
+G_DEFINE_TYPE (
+	EAttachmentStore,
+	e_attachment_store,
+	GTK_TYPE_LIST_STORE)
+
+static void
+attachment_store_get_property (GObject *object,
+                               guint property_id,
+                               GValue *value,
+                               GParamSpec *pspec)
+{
+	switch (property_id) {
+		case PROP_NUM_ATTACHMENTS:
+			g_value_set_uint (
+				value,
+				e_attachment_store_get_num_attachments (
+				E_ATTACHMENT_STORE (object)));
+			return;
+
+		case PROP_NUM_LOADING:
+			g_value_set_uint (
+				value,
+				e_attachment_store_get_num_loading (
+				E_ATTACHMENT_STORE (object)));
+			return;
+
+		case PROP_TOTAL_SIZE:
+			g_value_set_uint64 (
+				value,
+				e_attachment_store_get_total_size (
+				E_ATTACHMENT_STORE (object)));
+			return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+attachment_store_dispose (GObject *object)
+{
+	e_attachment_store_remove_all (E_ATTACHMENT_STORE (object));
+
+	/* Chain up to parent's dispose() method. */
+	G_OBJECT_CLASS (e_attachment_store_parent_class)->dispose (object);
+}
+
+static void
+attachment_store_finalize (GObject *object)
+{
+	EAttachmentStorePrivate *priv;
+
+	priv = E_ATTACHMENT_STORE_GET_PRIVATE (object);
+
+	g_hash_table_destroy (priv->attachment_index);
+
+	/* Chain up to parent's finalize() method. */
+	G_OBJECT_CLASS (e_attachment_store_parent_class)->finalize (object);
+}
+
+static void
+e_attachment_store_class_init (EAttachmentStoreClass *class)
+{
+	GObjectClass *object_class;
+
+	g_type_class_add_private (class, sizeof (EAttachmentStorePrivate));
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->get_property = attachment_store_get_property;
+	object_class->dispose = attachment_store_dispose;
+	object_class->finalize = attachment_store_finalize;
+
+	g_object_class_install_property (
+		object_class,
+		PROP_NUM_ATTACHMENTS,
+		g_param_spec_uint (
+			"num-attachments",
+			"Num Attachments",
+			NULL,
+			0,
+			G_MAXUINT,
+			0,
+			G_PARAM_READABLE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_NUM_LOADING,
+		g_param_spec_uint (
+			"num-loading",
+			"Num Loading",
+			NULL,
+			0,
+			G_MAXUINT,
+			0,
+			G_PARAM_READABLE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_TOTAL_SIZE,
+		g_param_spec_uint64 (
+			"total-size",
+			"Total Size",
+			NULL,
+			0,
+			G_MAXUINT64,
+			0,
+			G_PARAM_READABLE));
+}
+
+static void
+e_attachment_store_init (EAttachmentStore *store)
+{
+	GType types[E_ATTACHMENT_STORE_NUM_COLUMNS];
+	GHashTable *attachment_index;
+	gint column = 0;
+
+	attachment_index = g_hash_table_new_full (
+		g_direct_hash, g_direct_equal,
+		(GDestroyNotify) g_object_unref,
+		(GDestroyNotify) gtk_tree_row_reference_free);
+
+	store->priv = E_ATTACHMENT_STORE_GET_PRIVATE (store);
+	store->priv->attachment_index = attachment_index;
+
+	types[column++] = E_TYPE_ATTACHMENT;	/* COLUMN_ATTACHMENT */
+	types[column++] = G_TYPE_STRING;	/* COLUMN_CAPTION */
+	types[column++] = G_TYPE_STRING;	/* COLUMN_CONTENT_TYPE */
+	types[column++] = G_TYPE_STRING;	/* COLUMN_DESCRIPTION */
+	types[column++] = G_TYPE_ICON;		/* COLUMN_ICON */
+	types[column++] = G_TYPE_BOOLEAN;	/* COLUMN_LOADING */
+	types[column++] = G_TYPE_INT;		/* COLUMN_PERCENT */
+	types[column++] = G_TYPE_BOOLEAN;	/* COLUMN_SAVING */
+	types[column++] = G_TYPE_UINT64;	/* COLUMN_SIZE */
+
+	g_assert (column == E_ATTACHMENT_STORE_NUM_COLUMNS);
+
+	gtk_list_store_set_column_types (
+		GTK_LIST_STORE (store), G_N_ELEMENTS (types), types);
+}
+
+GtkTreeModel *
+e_attachment_store_new (void)
+{
+	return g_object_new (E_TYPE_ATTACHMENT_STORE, NULL);
+}
+
+void
+e_attachment_store_add_attachment (EAttachmentStore *store,
+                                   EAttachment *attachment)
+{
+	GtkTreeRowReference *reference;
+	GtkTreeModel *model;
+	GtkTreePath *path;
+	GtkTreeIter iter;
+
+	g_return_if_fail (E_IS_ATTACHMENT_STORE (store));
+	g_return_if_fail (E_IS_ATTACHMENT (attachment));
+
+	gtk_list_store_append (GTK_LIST_STORE (store), &iter);
+
+	gtk_list_store_set (
+		GTK_LIST_STORE (store), &iter,
+		E_ATTACHMENT_STORE_COLUMN_ATTACHMENT, attachment, -1);
+
+	model = GTK_TREE_MODEL (store);
+	path = gtk_tree_model_get_path (model, &iter);
+	reference = gtk_tree_row_reference_new (model, path);
+	gtk_tree_path_free (path);
+
+	g_hash_table_insert (
+		store->priv->attachment_index,
+		g_object_ref (attachment), reference);
+
+	/* This lets the attachment tell us when to update. */
+	e_attachment_set_reference (attachment, reference);
+
+	g_object_freeze_notify (G_OBJECT (store));
+	g_object_notify (G_OBJECT (store), "num-attachments");
+	g_object_notify (G_OBJECT (store), "total-size");
+	g_object_thaw_notify (G_OBJECT (store));
+}
+
+gboolean
+e_attachment_store_remove_attachment (EAttachmentStore *store,
+                                      EAttachment *attachment)
+{
+	GtkTreeRowReference *reference;
+	GHashTable *hash_table;
+	GtkTreeModel *model;
+	GtkTreePath *path;
+	GtkTreeIter iter;
+
+	g_return_val_if_fail (E_IS_ATTACHMENT_STORE (store), FALSE);
+	g_return_val_if_fail (E_IS_ATTACHMENT (attachment), FALSE);
+
+	hash_table = store->priv->attachment_index;
+	reference = g_hash_table_lookup (hash_table, attachment);
+
+	if (reference == NULL)
+		return FALSE;
+
+	if (!gtk_tree_row_reference_valid (reference)) {
+		g_hash_table_remove (hash_table, attachment);
+		return FALSE;
+	}
+
+	e_attachment_cancel (attachment);
+	e_attachment_set_reference (attachment, NULL);
+
+	model = gtk_tree_row_reference_get_model (reference);
+	path = gtk_tree_row_reference_get_path (reference);
+	gtk_tree_model_get_iter (model, &iter, path);
+	gtk_tree_path_free (path);
+
+	gtk_list_store_remove (GTK_LIST_STORE (store), &iter);
+	g_hash_table_remove (hash_table, attachment);
+
+	g_object_freeze_notify (G_OBJECT (store));
+	g_object_notify (G_OBJECT (store), "num-attachments");
+	g_object_notify (G_OBJECT (store), "total-size");
+	g_object_thaw_notify (G_OBJECT (store));
+
+	return TRUE;
+}
+
+void
+e_attachment_store_remove_all (EAttachmentStore *store)
+{
+	GList *list, *iter;
+
+	g_return_if_fail (E_IS_ATTACHMENT_STORE (store));
+
+	if (!g_hash_table_size (store->priv->attachment_index))
+		return;
+
+	g_object_freeze_notify (G_OBJECT (store));
+
+	list = e_attachment_store_get_attachments (store);
+	for (iter = list; iter; iter = iter->next) {
+		EAttachment *attachment = iter->data;
+
+		e_attachment_cancel (attachment);
+		g_hash_table_remove (store->priv->attachment_index, iter->data);
+	}
+
+	g_list_foreach (list, (GFunc) g_object_unref, NULL);
+	g_list_free (list);
+
+	gtk_list_store_clear (GTK_LIST_STORE (store));
+
+	g_object_notify (G_OBJECT (store), "num-attachments");
+	g_object_notify (G_OBJECT (store), "total-size");
+	g_object_thaw_notify (G_OBJECT (store));
+}
+
+void
+e_attachment_store_add_to_multipart (EAttachmentStore *store,
+                                     CamelMultipart *multipart,
+                                     const gchar *default_charset)
+{
+	GList *list, *iter;
+
+	g_return_if_fail (E_IS_ATTACHMENT_STORE (store));
+	g_return_if_fail (CAMEL_MULTIPART (multipart));
+
+	list = e_attachment_store_get_attachments (store);
+
+	for (iter = list; iter != NULL; iter = iter->next) {
+		EAttachment *attachment = iter->data;
+
+		/* Skip the attachment if it's still loading. */
+		if (!e_attachment_get_loading (attachment))
+			e_attachment_add_to_multipart (
+				attachment, multipart, default_charset);
+	}
+
+	g_list_foreach (list, (GFunc) g_object_unref, NULL);
+	g_list_free (list);
+}
+
+GList *
+e_attachment_store_get_attachments (EAttachmentStore *store)
+{
+	GList *list = NULL;
+	GtkTreeModel *model;
+	GtkTreeIter iter;
+	gboolean valid;
+
+	g_return_val_if_fail (E_IS_ATTACHMENT_STORE (store), NULL);
+
+	model = GTK_TREE_MODEL (store);
+	valid = gtk_tree_model_get_iter_first (model, &iter);
+
+	while (valid) {
+		EAttachment *attachment;
+		gint column_id;
+
+		column_id = E_ATTACHMENT_STORE_COLUMN_ATTACHMENT;
+		gtk_tree_model_get (model, &iter, column_id, &attachment, -1);
+
+		list = g_list_prepend (list, attachment);
+
+		valid = gtk_tree_model_iter_next (model, &iter);
+	}
+
+	return g_list_reverse (list);
+}
+
+guint
+e_attachment_store_get_num_attachments (EAttachmentStore *store)
+{
+	g_return_val_if_fail (E_IS_ATTACHMENT_STORE (store), 0);
+
+	return g_hash_table_size (store->priv->attachment_index);
+}
+
+guint
+e_attachment_store_get_num_loading (EAttachmentStore *store)
+{
+	GList *list, *iter;
+	guint num_loading = 0;
+
+	g_return_val_if_fail (E_IS_ATTACHMENT_STORE (store), 0);
+
+	list = e_attachment_store_get_attachments (store);
+
+	for (iter = list; iter != NULL; iter = iter->next) {
+		EAttachment *attachment = iter->data;
+
+		if (e_attachment_get_loading (attachment))
+			num_loading++;
+	}
+
+	g_list_foreach (list, (GFunc) g_object_unref, NULL);
+	g_list_free (list);
+
+	return num_loading;
+}
+
+goffset
+e_attachment_store_get_total_size (EAttachmentStore *store)
+{
+	GList *list, *iter;
+	goffset total_size = 0;
+
+	g_return_val_if_fail (E_IS_ATTACHMENT_STORE (store), 0);
+
+	list = e_attachment_store_get_attachments (store);
+
+	for (iter = list; iter != NULL; iter = iter->next) {
+		EAttachment *attachment = iter->data;
+		GFileInfo *file_info;
+
+		file_info = e_attachment_get_file_info (attachment);
+		if (file_info != NULL)
+			total_size += g_file_info_get_size (file_info);
+	}
+
+	g_list_foreach (list, (GFunc) g_object_unref, NULL);
+	g_list_free (list);
+
+	return total_size;
+}
+
+void
+e_attachment_store_run_load_dialog (EAttachmentStore *store,
+                                    GtkWindow *parent)
+{
+	GtkFileChooser *file_chooser;
+	GtkWidget *dialog;
+	GtkWidget *option;
+	GSList *files, *iter;
+	const gchar *disposition;
+	gboolean active;
+	gint response;
+
+	g_return_if_fail (E_IS_ATTACHMENT_STORE (store));
+	g_return_if_fail (GTK_IS_WINDOW (parent));
+
+	dialog = gtk_file_chooser_dialog_new (
+		_("Add Attachment"), parent,
+		GTK_FILE_CHOOSER_ACTION_OPEN,
+		GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+		_("A_ttach"), GTK_RESPONSE_OK, NULL);
+
+	file_chooser = GTK_FILE_CHOOSER (dialog);
+	gtk_file_chooser_set_local_only (file_chooser, FALSE);
+	gtk_file_chooser_set_select_multiple (file_chooser, TRUE);
+	gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK);
+	gtk_window_set_icon_name (GTK_WINDOW (dialog), "mail-attachment");
+
+	option = gtk_check_button_new_with_mnemonic (
+		_("_Suggest automatic display of attachment"));
+	gtk_file_chooser_set_extra_widget (file_chooser, option);
+	gtk_widget_show (option);
+
+	response = gtk_dialog_run (GTK_DIALOG (dialog));
+
+	if (response != GTK_RESPONSE_OK)
+		goto exit;
+
+	files = gtk_file_chooser_get_files (file_chooser);
+	active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (option));
+	disposition = active ? "inline" : "attachment";
+
+	for (iter = files; iter != NULL; iter = g_slist_next (iter)) {
+		EAttachment *attachment;
+		GFile *file = iter->data;
+
+		attachment = e_attachment_new ();
+		e_attachment_set_file (attachment, file);
+		e_attachment_set_disposition (attachment, disposition);
+		e_attachment_store_add_attachment (store, attachment);
+		e_attachment_load_async (
+			attachment, (GAsyncReadyCallback)
+			e_attachment_load_handle_error, parent);
+		g_object_unref (attachment);
+	}
+
+	g_slist_foreach (files, (GFunc) g_object_unref, NULL);
+	g_slist_free (files);
+
+exit:
+	gtk_widget_destroy (dialog);
+}
+
+GFile *
+e_attachment_store_run_save_dialog (EAttachmentStore *store,
+                                    GList *attachment_list,
+                                    GtkWindow *parent)
+{
+	GtkFileChooser *file_chooser;
+	GtkFileChooserAction action;
+	GtkWidget *dialog;
+	GFile *destination;
+	const gchar *title;
+	gint response;
+	guint length;
+
+	g_return_val_if_fail (E_IS_ATTACHMENT_STORE (store), NULL);
+
+	length = g_list_length (attachment_list);
+
+	if (length == 0)
+		return NULL;
+
+	title = ngettext ("Save Attachment", "Save Attachments", length);
+
+	if (length == 1)
+		action = GTK_FILE_CHOOSER_ACTION_SAVE;
+	else
+		action = GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER;
+
+	dialog = gtk_file_chooser_dialog_new (
+		title, parent, action,
+		GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+		GTK_STOCK_SAVE, GTK_RESPONSE_OK, NULL);
+
+	file_chooser = GTK_FILE_CHOOSER (dialog);
+	gtk_file_chooser_set_local_only (file_chooser, FALSE);
+	gtk_file_chooser_set_do_overwrite_confirmation (file_chooser, TRUE);
+	gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK);
+	gtk_window_set_icon_name (GTK_WINDOW (dialog), "mail-attachment");
+
+	if (action == GTK_FILE_CHOOSER_ACTION_SAVE) {
+		EAttachment *attachment;
+		GFileInfo *file_info;
+		const gchar *name = NULL;
+
+		attachment = attachment_list->data;
+		file_info = e_attachment_get_file_info (attachment);
+		if (file_info != NULL)
+			name = g_file_info_get_display_name (file_info);
+		if (name == NULL)
+			/* Translators: Default attachment filename. */
+			name = _("attachment.dat");
+		gtk_file_chooser_set_current_name (file_chooser, name);
+	}
+
+	response = gtk_dialog_run (GTK_DIALOG (dialog));
+
+	if (response == GTK_RESPONSE_OK)
+		destination = gtk_file_chooser_get_file (file_chooser);
+	else
+		destination = NULL;
+
+	gtk_widget_destroy (dialog);
+
+	return destination;
+}
+
+/******************** e_attachment_store_get_uris_async() ********************/
+
+typedef struct _UriContext UriContext;
+
+struct _UriContext {
+	GSimpleAsyncResult *simple;
+	GList *attachment_list;
+	GError *error;
+	gchar **uris;
+	gint index;
+};
+
+static UriContext *
+attachment_store_uri_context_new (EAttachmentStore *store,
+                                  GList *attachment_list,
+                                  GAsyncReadyCallback callback,
+                                  gpointer user_data)
+{
+	UriContext *uri_context;
+	GSimpleAsyncResult *simple;
+	guint length;
+	gchar **uris;
+
+	simple = g_simple_async_result_new (
+		G_OBJECT (store), callback, user_data,
+		e_attachment_store_get_uris_async);
+
+	/* Add one for NULL terminator. */
+	length = g_list_length (attachment_list) + 1;
+	uris = g_malloc0 (sizeof (gchar *) * length);
+
+	uri_context = g_slice_new0 (UriContext);
+	uri_context->simple = simple;
+	uri_context->attachment_list = g_list_copy (attachment_list);
+	uri_context->uris = uris;
+
+	g_list_foreach (
+		uri_context->attachment_list,
+		(GFunc) g_object_ref, NULL);
+
+	return uri_context;
+}
+
+static void
+attachment_store_uri_context_free (UriContext *uri_context)
+{
+	g_object_unref (uri_context->simple);
+
+	/* The attachment list should be empty now. */
+	g_warn_if_fail (uri_context->attachment_list == NULL);
+
+	/* So should the error. */
+	g_warn_if_fail (uri_context->error == NULL);
+
+	g_strfreev (uri_context->uris);
+
+	g_slice_free (UriContext, uri_context);
+}
+
+static void
+attachment_store_get_uris_save_cb (EAttachment *attachment,
+                                   GAsyncResult *result,
+                                   UriContext *uri_context)
+{
+	GSimpleAsyncResult *simple;
+	GFile *file;
+	gchar **uris;
+	gchar *uri;
+	GError *error = NULL;
+
+	file = e_attachment_save_finish (attachment, result, &error);
+
+	/* Remove the attachment from the list. */
+	uri_context->attachment_list = g_list_remove (
+		uri_context->attachment_list, attachment);
+	g_object_unref (attachment);
+
+	if (file != NULL) {
+		uri = g_file_get_uri (file);
+		uri_context->uris[uri_context->index++] = uri;
+		g_object_unref (file);
+
+	} else if (error != NULL) {
+		/* If this is the first error, cancel the other jobs. */
+		if (uri_context->error == NULL) {
+			g_propagate_error (&uri_context->error, error);
+			g_list_foreach (
+				uri_context->attachment_list,
+				(GFunc) e_attachment_cancel, NULL);
+			error = NULL;
+
+		/* Otherwise, we can only report back one error.  So if
+		 * this is something other than cancellation, dump it to
+		 * the terminal. */
+		} else if (!g_error_matches (
+			error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+			g_warning ("%s", error->message);
+	}
+
+	if (error != NULL)
+		g_error_free (error);
+
+	/* If there's still jobs running, let them finish. */
+	if (uri_context->attachment_list != NULL)
+		return;
+
+	/* Steal the URI list. */
+	uris = uri_context->uris;
+	uri_context->uris = NULL;
+
+	/* And the error. */
+	error = uri_context->error;
+	uri_context->error = NULL;
+
+	simple = uri_context->simple;
+
+	if (error == NULL)
+		g_simple_async_result_set_op_res_gpointer (simple, uris, NULL);
+	else
+		g_simple_async_result_take_error (simple, error);
+
+	g_simple_async_result_complete (simple);
+
+	attachment_store_uri_context_free (uri_context);
+}
+
+void
+e_attachment_store_get_uris_async (EAttachmentStore *store,
+                                   GList *attachment_list,
+                                   GAsyncReadyCallback callback,
+                                   gpointer user_data)
+{
+	GFile *temp_directory;
+	UriContext *uri_context;
+	GList *iter, *trash = NULL;
+	gchar *template;
+	gchar *path;
+
+	g_return_if_fail (E_IS_ATTACHMENT_STORE (store));
+
+	uri_context = attachment_store_uri_context_new (
+		store, attachment_list, callback, user_data);
+
+	/* Grab the copied attachment list. */
+	attachment_list = uri_context->attachment_list;
+
+	/* First scan the list for attachments with a GFile. */
+	for (iter = attachment_list; iter != NULL; iter = iter->next) {
+		EAttachment *attachment = iter->data;
+		GFile *file;
+		gchar *uri;
+
+		file = e_attachment_get_file (attachment);
+		if (file == NULL)
+			continue;
+
+		uri = g_file_get_uri (file);
+		uri_context->uris[uri_context->index++] = uri;
+
+		/* Mark the list node for deletion. */
+		trash = g_list_prepend (trash, iter);
+		g_object_unref (attachment);
+	}
+
+	/* Expunge the list. */
+	for (iter = trash; iter != NULL; iter = iter->next) {
+		GList *link = iter->data;
+		attachment_list = g_list_delete_link (attachment_list, link);
+	}
+	g_list_free (trash);
+
+	uri_context->attachment_list = attachment_list;
+
+	/* If we got them all then we're done. */
+	if (attachment_list == NULL) {
+		GSimpleAsyncResult *simple;
+		gchar **uris;
+
+		/* Steal the URI list. */
+		uris = uri_context->uris;
+		uri_context->uris = NULL;
+
+		simple = uri_context->simple;
+		g_simple_async_result_set_op_res_gpointer (simple, uris, NULL);
+		g_simple_async_result_complete (simple);
+
+		attachment_store_uri_context_free (uri_context);
+		return;
+	}
+
+	/* Any remaining attachments in the list should have MIME parts
+	 * only, so we need to save them all to a temporary directory.
+	 * We use a directory so the files can retain their basenames.
+	 * XXX This could trigger a blocking temp directory cleanup. */
+	template = g_strdup_printf (PACKAGE "-%s-XXXXXX", g_get_user_name ());
+	path = e_mkdtemp (template);
+	g_free (template);
+
+	/* XXX Let's hope errno got set properly. */
+	if (path == NULL) {
+		GSimpleAsyncResult *simple;
+
+		simple = uri_context->simple;
+		g_simple_async_result_set_error (
+			simple, G_FILE_ERROR,
+			g_file_error_from_errno (errno),
+			"%s", g_strerror (errno));
+		g_simple_async_result_complete (simple);
+
+		attachment_store_uri_context_free (uri_context);
+		return;
+	}
+
+	temp_directory = g_file_new_for_path (path);
+
+	for (iter = attachment_list; iter != NULL; iter = iter->next)
+		e_attachment_save_async (
+			E_ATTACHMENT (iter->data),
+			temp_directory, (GAsyncReadyCallback)
+			attachment_store_get_uris_save_cb,
+			uri_context);
+
+	g_object_unref (temp_directory);
+	g_free (path);
+}
+
+gchar **
+e_attachment_store_get_uris_finish (EAttachmentStore *store,
+                                    GAsyncResult *result,
+                                    GError **error)
+{
+	GSimpleAsyncResult *simple;
+	gchar **uris;
+
+	g_return_val_if_fail (E_IS_ATTACHMENT_STORE (store), NULL);
+	g_return_val_if_fail (G_IS_SIMPLE_ASYNC_RESULT (result), NULL);
+
+	simple = G_SIMPLE_ASYNC_RESULT (result);
+	uris = g_simple_async_result_get_op_res_gpointer (simple);
+	g_simple_async_result_propagate_error (simple, error);
+
+	return uris;
+}
+
+/********************** e_attachment_store_load_async() **********************/
+
+typedef struct _LoadContext LoadContext;
+
+struct _LoadContext {
+	GSimpleAsyncResult *simple;
+	GList *attachment_list;
+	GError *error;
+};
+
+static LoadContext *
+attachment_store_load_context_new (EAttachmentStore *store,
+                                   GList *attachment_list,
+                                   GAsyncReadyCallback callback,
+                                   gpointer user_data)
+{
+	LoadContext *load_context;
+	GSimpleAsyncResult *simple;
+
+	simple = g_simple_async_result_new (
+		G_OBJECT (store), callback, user_data,
+		e_attachment_store_load_async);
+
+	load_context = g_slice_new0 (LoadContext);
+	load_context->simple = simple;
+	load_context->attachment_list = g_list_copy (attachment_list);
+
+	g_list_foreach (
+		load_context->attachment_list,
+		(GFunc) g_object_ref, NULL);
+
+	return load_context;
+}
+
+static void
+attachment_store_load_context_free (LoadContext *load_context)
+{
+	g_object_unref (load_context->simple);
+
+	/* The attachment list should be empty now. */
+	g_warn_if_fail (load_context->attachment_list == NULL);
+
+	/* So should the error. */
+	g_warn_if_fail (load_context->error == NULL);
+
+	g_slice_free (LoadContext, load_context);
+}
+
+static void
+attachment_store_load_ready_cb (EAttachment *attachment,
+                                GAsyncResult *result,
+                                LoadContext *load_context)
+{
+	GSimpleAsyncResult *simple;
+	GError *error = NULL;
+
+	e_attachment_load_finish (attachment, result, &error);
+
+	/* Remove the attachment from the list. */
+	load_context->attachment_list = g_list_remove (
+		load_context->attachment_list, attachment);
+	g_object_unref (attachment);
+
+	if (error != NULL) {
+		/* If this is the first error, cancel the other jobs. */
+		if (load_context->error == NULL) {
+			g_propagate_error (&load_context->error, error);
+			g_list_foreach (
+				load_context->attachment_list,
+				(GFunc) e_attachment_cancel, NULL);
+			error = NULL;
+
+		/* Otherwise, we can only report back one error.  So if
+		 * this is something other than cancellation, dump it to
+		 * the terminal. */
+		} else if (!g_error_matches (
+			error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+			g_warning ("%s", error->message);
+	}
+
+	if (error != NULL)
+		g_error_free (error);
+
+	/* If there's still jobs running, let them finish. */
+	if (load_context->attachment_list != NULL)
+		return;
+
+	/* Steal the error. */
+	error = load_context->error;
+	load_context->error = NULL;
+
+	simple = load_context->simple;
+
+	if (error == NULL)
+		g_simple_async_result_set_op_res_gboolean (simple, TRUE);
+	else
+		g_simple_async_result_take_error (simple, error);
+
+	g_simple_async_result_complete (simple);
+
+	attachment_store_load_context_free (load_context);
+}
+
+void
+e_attachment_store_load_async (EAttachmentStore *store,
+                               GList *attachment_list,
+                               GAsyncReadyCallback callback,
+                               gpointer user_data)
+{
+	LoadContext *load_context;
+	GList *iter;
+
+	g_return_if_fail (E_IS_ATTACHMENT_STORE (store));
+
+	load_context = attachment_store_load_context_new (
+		store, attachment_list, callback, user_data);
+
+	if (attachment_list == NULL) {
+		GSimpleAsyncResult *simple;
+
+		simple = load_context->simple;
+		g_simple_async_result_set_op_res_gboolean (simple, TRUE);
+		g_simple_async_result_complete (simple);
+
+		attachment_store_load_context_free (load_context);
+		return;
+	}
+
+	for (iter = attachment_list; iter != NULL; iter = iter->next) {
+		EAttachment *attachment = E_ATTACHMENT (iter->data);
+
+		e_attachment_store_add_attachment (store, attachment);
+
+		e_attachment_load_async (
+			attachment, (GAsyncReadyCallback)
+			attachment_store_load_ready_cb,
+			load_context);
+	}
+}
+
+gboolean
+e_attachment_store_load_finish (EAttachmentStore *store,
+                                GAsyncResult *result,
+                                GError **error)
+{
+	GSimpleAsyncResult *simple;
+	gboolean success;
+
+	g_return_val_if_fail (E_IS_ATTACHMENT_STORE (store), FALSE);
+	g_return_val_if_fail (G_IS_SIMPLE_ASYNC_RESULT (result), FALSE);
+
+	simple = G_SIMPLE_ASYNC_RESULT (result);
+	success = g_simple_async_result_get_op_res_gboolean (simple);
+	g_simple_async_result_propagate_error (simple, error);
+
+	return success;
+}
+
+/********************** e_attachment_store_save_async() **********************/
+
+typedef struct _SaveContext SaveContext;
+
+struct _SaveContext {
+	GSimpleAsyncResult *simple;
+	GFile *destination;
+	gchar *filename_prefix;
+	GFile *fresh_directory;
+	GFile *trash_directory;
+	GList *attachment_list;
+	GError *error;
+	gchar **uris;
+	gint index;
+};
+
+static SaveContext *
+attachment_store_save_context_new (EAttachmentStore *store,
+                                   GFile *destination,
+                                   const gchar *filename_prefix,
+                                   GAsyncReadyCallback callback,
+                                   gpointer user_data)
+{
+	SaveContext *save_context;
+	GSimpleAsyncResult *simple;
+	GList *attachment_list;
+	guint length;
+	gchar **uris;
+
+	simple = g_simple_async_result_new (
+		G_OBJECT (store), callback, user_data,
+		e_attachment_store_save_async);
+
+	attachment_list = e_attachment_store_get_attachments (store);
+
+	/* Add one for NULL terminator. */
+	length = g_list_length (attachment_list) + 1;
+	uris = g_malloc0 (sizeof (gchar *) * length);
+
+	save_context = g_slice_new0 (SaveContext);
+	save_context->simple = simple;
+	save_context->destination = g_object_ref (destination);
+	save_context->filename_prefix = g_strdup (filename_prefix);
+	save_context->attachment_list = attachment_list;
+	save_context->uris = uris;
+
+	return save_context;
+}
+
+static void
+attachment_store_save_context_free (SaveContext *save_context)
+{
+	g_object_unref (save_context->simple);
+
+	/* The attachment list should be empty now. */
+	g_warn_if_fail (save_context->attachment_list == NULL);
+
+	/* So should the error. */
+	g_warn_if_fail (save_context->error == NULL);
+
+	if (save_context->destination) {
+		g_object_unref (save_context->destination);
+		save_context->destination = NULL;
+	}
+
+	g_free (save_context->filename_prefix);
+	save_context->filename_prefix = NULL;
+
+	if (save_context->fresh_directory) {
+		g_object_unref (save_context->fresh_directory);
+		save_context->fresh_directory = NULL;
+	}
+
+	if (save_context->trash_directory) {
+		g_object_unref (save_context->trash_directory);
+		save_context->trash_directory = NULL;
+	}
+
+	g_strfreev (save_context->uris);
+
+	g_slice_free (SaveContext, save_context);
+}
+
+static void
+attachment_store_move_file (SaveContext *save_context,
+                            GFile *source,
+                            GFile *destination,
+                            GError **error)
+{
+	gchar *tmpl;
+	gchar *path;
+
+	g_return_if_fail (save_context != NULL);
+	g_return_if_fail (source != NULL);
+	g_return_if_fail (destination != NULL);
+	g_return_if_fail (error != NULL);
+
+	/* Attachments are all saved to a temporary directory.
+	 * Now we need to move the existing destination directory
+	 * out of the way (if it exists).  Instead of testing for
+	 * existence we'll just attempt the move and ignore any
+	 * G_IO_ERROR_NOT_FOUND errors. */
+
+	/* First, however, we need another temporary directory to
+	 * move the existing destination directory to.  Note we're
+	 * not actually creating the directory yet, just picking a
+	 * name for it.  The usual raciness with this approach
+	 * applies here (read up on mktemp(3)), but worst case is
+	 * we get a spurious G_IO_ERROR_WOULD_MERGE error and the
+	 * user has to try saving attachments again. */
+	tmpl = g_strdup_printf (PACKAGE "-%s-XXXXXX", g_get_user_name ());
+	path = e_mktemp (tmpl);
+	g_free (tmpl);
+
+	save_context->trash_directory = g_file_new_for_path (path);
+	g_free (path);
+
+	/* XXX No asynchronous move operation in GIO? */
+	g_file_move (
+		destination,
+		save_context->trash_directory,
+		G_FILE_COPY_NONE, NULL, NULL, NULL, error);
+
+	if (*error != NULL && !g_error_matches (*error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
+		return;
+
+	g_clear_error (error);
+
+	/* Now we can move the file from the temporary directory
+	 * to the user-specified destination. */
+	g_file_move (
+		source,
+		destination,
+		G_FILE_COPY_NONE, NULL, NULL, NULL, error);
+}
+
+static void
+attachment_store_save_cb (EAttachment *attachment,
+                          GAsyncResult *result,
+                          SaveContext *save_context)
+{
+	GSimpleAsyncResult *simple;
+	GFile *file;
+	gchar **uris;
+	GError *error = NULL;
+
+	file = e_attachment_save_finish (attachment, result, &error);
+
+	/* Remove the attachment from the list. */
+	save_context->attachment_list = g_list_remove (
+		save_context->attachment_list, attachment);
+	g_object_unref (attachment);
+
+	if (file != NULL) {
+		/* Assemble the file's final URI from its basename. */
+		gchar *basename;
+		gchar *uri;
+		GFile *source = NULL, *destination = NULL;
+
+		basename = g_file_get_basename (file);
+		g_object_unref (file);
+
+		source = g_file_get_child (save_context->fresh_directory, basename);
+
+		if (save_context->filename_prefix && *save_context->filename_prefix) {
+			gchar *tmp = basename;
+
+			basename = g_strconcat (save_context->filename_prefix, basename, NULL);
+			g_free (tmp);
+		}
+
+		file = save_context->destination;
+		destination = g_file_get_child (file, basename);
+		uri = g_file_get_uri (destination);
+
+		/* move them file-by-file */
+		attachment_store_move_file (save_context, source, destination, &error);
+
+		if (!error)
+			save_context->uris[save_context->index++] = uri;
+
+		g_object_unref (source);
+		g_object_unref (destination);
+	}
+
+	if (error != NULL) {
+		/* If this is the first error, cancel the other jobs. */
+		if (save_context->error == NULL) {
+			g_propagate_error (&save_context->error, error);
+			g_list_foreach (
+				save_context->attachment_list,
+				(GFunc) e_attachment_cancel, NULL);
+			error = NULL;
+
+		/* Otherwise, we can only report back one error.  So if
+		 * this is something other than cancellation, dump it to
+		 * the terminal. */
+		} else if (!g_error_matches (
+			error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+			g_warning ("%s", error->message);
+	}
+
+	g_clear_error (&error);
+
+	/* If there's still jobs running, let them finish. */
+	if (save_context->attachment_list != NULL)
+		return;
+
+	/* If an error occurred while saving, we're done. */
+	if (save_context->error != NULL) {
+
+		/* Steal the error. */
+		error = save_context->error;
+		save_context->error = NULL;
+
+		simple = save_context->simple;
+		g_simple_async_result_take_error (simple, error);
+		g_simple_async_result_complete (simple);
+
+		attachment_store_save_context_free (save_context);
+		return;
+	}
+
+	if (error != NULL) {
+		simple = save_context->simple;
+		g_simple_async_result_take_error (simple, error);
+		g_simple_async_result_complete (simple);
+
+		attachment_store_save_context_free (save_context);
+		return;
+	}
+
+	/* clean-up left directory */
+	g_file_delete (save_context->fresh_directory, NULL, NULL);
+
+	/* And the URI list. */
+	uris = save_context->uris;
+	save_context->uris = NULL;
+
+	simple = save_context->simple;
+	g_simple_async_result_set_op_res_gpointer (simple, uris, NULL);
+	g_simple_async_result_complete (simple);
+
+	attachment_store_save_context_free (save_context);
+}
+/*
+ * @filename_prefix: prefix to use for a file name; can be %NULL for none
+ **/
+void
+e_attachment_store_save_async (EAttachmentStore *store,
+                               GFile *destination,
+                               const gchar *filename_prefix,
+                               GAsyncReadyCallback callback,
+                               gpointer user_data)
+{
+	SaveContext *save_context;
+	GList *attachment_list, *iter;
+	GFile *temp_directory;
+	gchar *template;
+	gchar *path;
+
+	g_return_if_fail (E_IS_ATTACHMENT_STORE (store));
+	g_return_if_fail (G_IS_FILE (destination));
+
+	save_context = attachment_store_save_context_new (
+		store, destination, filename_prefix, callback, user_data);
+
+	attachment_list = save_context->attachment_list;
+
+	/* Deal with an empty attachment store.  The caller will get
+	 * an empty NULL-terminated list as opposed to NULL, to help
+	 * distinguish it from an error. */
+	if (attachment_list == NULL) {
+		GSimpleAsyncResult *simple;
+		gchar **uris;
+
+		/* Steal the URI list. */
+		uris = save_context->uris;
+		save_context->uris = NULL;
+
+		simple = save_context->simple;
+		g_simple_async_result_set_op_res_gpointer (simple, uris, NULL);
+		g_simple_async_result_complete (simple);
+
+		attachment_store_save_context_free (save_context);
+		return;
+	}
+
+	/* Save all attachments to a temporary directory, which we'll
+	 * then move to its proper location.  We use a directory so
+	 * files can retain their basenames.
+	 * XXX This could trigger a blocking temp directory cleanup. */
+	template = g_strdup_printf (PACKAGE "-%s-XXXXXX", g_get_user_name ());
+	path = e_mkdtemp (template);
+	g_free (template);
+
+	/* XXX Let's hope errno got set properly. */
+	if (path == NULL) {
+		GSimpleAsyncResult *simple;
+
+		simple = save_context->simple;
+		g_simple_async_result_set_error (
+			simple, G_FILE_ERROR,
+			g_file_error_from_errno (errno),
+			"%s", g_strerror (errno));
+		g_simple_async_result_complete (simple);
+
+		attachment_store_save_context_free (save_context);
+		return;
+	}
+
+	temp_directory = g_file_new_for_path (path);
+	save_context->fresh_directory = temp_directory;
+	g_free (path);
+
+	for (iter = attachment_list; iter != NULL; iter = iter->next)
+		e_attachment_save_async (
+			E_ATTACHMENT (iter->data),
+			temp_directory, (GAsyncReadyCallback)
+			attachment_store_save_cb, save_context);
+}
+
+gchar **
+e_attachment_store_save_finish (EAttachmentStore *store,
+                                GAsyncResult *result,
+                                GError **error)
+{
+	GSimpleAsyncResult *simple;
+	gchar **uris;
+
+	g_return_val_if_fail (E_IS_ATTACHMENT_STORE (store), NULL);
+	g_return_val_if_fail (G_IS_SIMPLE_ASYNC_RESULT (result), NULL);
+
+	simple = G_SIMPLE_ASYNC_RESULT (result);
+	uris = g_simple_async_result_get_op_res_gpointer (simple);
+	g_simple_async_result_propagate_error (simple, error);
+
+	return uris;
+}
diff --git a/e-util/e-attachment-store.h b/e-util/e-attachment-store.h
new file mode 100644
index 0000000..a112b0e
--- /dev/null
+++ b/e-util/e-attachment-store.h
@@ -0,0 +1,137 @@
+/*
+ * e-attachment-store.h
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_ATTACHMENT_STORE_H
+#define E_ATTACHMENT_STORE_H
+
+#include <gtk/gtk.h>
+#include <e-util/e-attachment.h>
+
+/* Standard GObject macros */
+#define E_TYPE_ATTACHMENT_STORE \
+	(e_attachment_store_get_type ())
+#define E_ATTACHMENT_STORE(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_ATTACHMENT_STORE, EAttachmentStore))
+#define E_ATTACHMENT_STORE_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_ATTACHMENT_STORE, EAttachmentStoreClass))
+#define E_IS_ATTACHMENT_STORE(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_ATTACHMENT_STORE))
+#define E_IS_ATTACHMENT_STORE_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_ATTACHMENT_STORE))
+#define E_ATTACHMENT_STORE_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_ATTACHMENT_STORE, EAttachmentStoreClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EAttachmentStore EAttachmentStore;
+typedef struct _EAttachmentStoreClass EAttachmentStoreClass;
+typedef struct _EAttachmentStorePrivate EAttachmentStorePrivate;
+
+struct _EAttachmentStore {
+	GtkListStore parent;
+	EAttachmentStorePrivate *priv;
+};
+
+struct _EAttachmentStoreClass {
+	GtkListStoreClass parent_class;
+};
+
+enum {
+	E_ATTACHMENT_STORE_COLUMN_ATTACHMENT,	/* E_TYPE_ATTACHMENT */
+	E_ATTACHMENT_STORE_COLUMN_CAPTION,	/* G_TYPE_STRING */
+	E_ATTACHMENT_STORE_COLUMN_CONTENT_TYPE, /* G_TYPE_STRING */
+	E_ATTACHMENT_STORE_COLUMN_DESCRIPTION,	/* G_TYPE_STRING */
+	E_ATTACHMENT_STORE_COLUMN_ICON,		/* G_TYPE_ICON */
+	E_ATTACHMENT_STORE_COLUMN_LOADING,	/* G_TYPE_BOOLEAN */
+	E_ATTACHMENT_STORE_COLUMN_PERCENT,	/* G_TYPE_INT */
+	E_ATTACHMENT_STORE_COLUMN_SAVING,	/* G_TYPE_BOOLEAN */
+	E_ATTACHMENT_STORE_COLUMN_SIZE,		/* G_TYPE_UINT64 */
+	E_ATTACHMENT_STORE_NUM_COLUMNS
+};
+
+GType		e_attachment_store_get_type	(void);
+GtkTreeModel *	e_attachment_store_new		(void);
+void		e_attachment_store_add_attachment
+						(EAttachmentStore *store,
+						 EAttachment *attachment);
+gboolean	e_attachment_store_remove_attachment
+						(EAttachmentStore *store,
+						 EAttachment *attachment);
+void		e_attachment_store_remove_all	(EAttachmentStore *store);
+void		e_attachment_store_add_to_multipart
+						(EAttachmentStore *store,
+						 CamelMultipart *multipart,
+						 const gchar *default_charset);
+GList *		e_attachment_store_get_attachments
+						(EAttachmentStore *store);
+guint		e_attachment_store_get_num_attachments
+						(EAttachmentStore *store);
+guint		e_attachment_store_get_num_loading
+						(EAttachmentStore *store);
+goffset		e_attachment_store_get_total_size
+						(EAttachmentStore *store);
+void		e_attachment_store_run_load_dialog
+						(EAttachmentStore *store,
+						 GtkWindow *parent);
+GFile *		e_attachment_store_run_save_dialog
+						(EAttachmentStore *store,
+						 GList *attachment_list,
+						 GtkWindow *parent);
+
+/* Asynchronous Operations */
+void		e_attachment_store_get_uris_async
+						(EAttachmentStore *store,
+						 GList *attachment_list,
+						 GAsyncReadyCallback callback,
+						 gpointer user_data);
+gchar **	e_attachment_store_get_uris_finish
+						(EAttachmentStore *store,
+						 GAsyncResult *result,
+						 GError **error);
+void		e_attachment_store_load_async	(EAttachmentStore *store,
+						 GList *attachment_list,
+						 GAsyncReadyCallback callback,
+						 gpointer user_data);
+gboolean	e_attachment_store_load_finish	(EAttachmentStore *store,
+						 GAsyncResult *result,
+						 GError **error);
+void		e_attachment_store_save_async	(EAttachmentStore *store,
+						 GFile *destination,
+						 const gchar *filename_prefix,
+						 GAsyncReadyCallback callback,
+						 gpointer user_data);
+gchar **	e_attachment_store_save_finish	(EAttachmentStore *store,
+						 GAsyncResult *result,
+						 GError **error);
+
+G_END_DECLS
+
+#endif /* E_ATTACHMENT_STORE_H */
+
diff --git a/widgets/misc/e-attachment-tree-view.c b/e-util/e-attachment-tree-view.c
similarity index 100%
rename from widgets/misc/e-attachment-tree-view.c
rename to e-util/e-attachment-tree-view.c
diff --git a/e-util/e-attachment-tree-view.h b/e-util/e-attachment-tree-view.h
new file mode 100644
index 0000000..416a09b
--- /dev/null
+++ b/e-util/e-attachment-tree-view.h
@@ -0,0 +1,70 @@
+/*
+ * e-attachment-tree-view.h
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_ATTACHMENT_TREE_VIEW_H
+#define E_ATTACHMENT_TREE_VIEW_H
+
+#include <gtk/gtk.h>
+
+/* Standard GObject macros */
+#define E_TYPE_ATTACHMENT_TREE_VIEW \
+	(e_attachment_tree_view_get_type ())
+#define E_ATTACHMENT_TREE_VIEW(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_ATTACHMENT_TREE_VIEW, EAttachmentTreeView))
+#define E_ATTACHMENT_TREE_VIEW_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_ATTACHMENT_TREE_VIEW, EAttachmentTreeViewClass))
+#define E_IS_ATTACHMENT_TREE_VIEW(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_ATTACHMENT_TREE_VIEW))
+#define E_IS_ATTACHMENT_TREE_VIEW_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_ATTACHMENT_TREE_VIEW))
+#define E_ATTACHMENT_TREE_VIEW_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_ATTACHMENT_TREE_VIEW, EAttachmentTreeViewClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EAttachmentTreeView EAttachmentTreeView;
+typedef struct _EAttachmentTreeViewClass EAttachmentTreeViewClass;
+typedef struct _EAttachmentTreeViewPrivate EAttachmentTreeViewPrivate;
+
+struct _EAttachmentTreeView {
+	GtkTreeView parent;
+	EAttachmentTreeViewPrivate *priv;
+};
+
+struct _EAttachmentTreeViewClass {
+	GtkTreeViewClass parent_class;
+};
+
+GType		e_attachment_tree_view_get_type		(void);
+GtkWidget *	e_attachment_tree_view_new		(void);
+
+G_END_DECLS
+
+#endif /* E_ATTACHMENT_TREE_VIEW_H */
diff --git a/e-util/e-attachment-view.c b/e-util/e-attachment-view.c
new file mode 100644
index 0000000..e468c14
--- /dev/null
+++ b/e-util/e-attachment-view.c
@@ -0,0 +1,1906 @@
+/*
+ * e-attachment-view.c
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-attachment-view.h"
+
+#include <glib/gi18n.h>
+#include <gdk/gdkkeysyms.h>
+
+#include "e-attachment-dialog.h"
+#include "e-attachment-handler-image.h"
+#include "e-attachment-handler-sendto.h"
+#include "e-misc-utils.h"
+#include "e-selection.h"
+#include "e-ui-manager.h"
+
+enum {
+	UPDATE_ACTIONS,
+	LAST_SIGNAL
+};
+
+/* Note: Do not use the info field. */
+static GtkTargetEntry target_table[] = {
+	{ (gchar *) "_NETSCAPE_URL", 0, 0 }
+};
+
+static const gchar *ui =
+"<ui>"
+"  <popup name='context'>"
+"    <menuitem action='cancel'/>"
+"    <menuitem action='save-as'/>"
+"    <menuitem action='remove'/>"
+"    <menuitem action='properties'/>"
+"    <separator/>"
+"    <placeholder name='inline-actions'>"
+"      <menuitem action='show'/>"
+"      <menuitem action='show-all'/>"
+"      <separator/>"
+"      <menuitem action='hide'/>"
+"      <menuitem action='hide-all'/>"
+"    </placeholder>"
+"    <separator/>"
+"    <placeholder name='custom-actions'/>"
+"    <separator/>"
+"    <menuitem action='add'/>"
+"    <separator/>"
+"    <placeholder name='open-actions'/>"
+"    <menuitem action='open-with'/>"
+"  </popup>"
+"</ui>";
+
+static gulong signals[LAST_SIGNAL];
+
+G_DEFINE_INTERFACE (
+	EAttachmentView,
+	e_attachment_view,
+	GTK_TYPE_WIDGET)
+
+static void
+action_add_cb (GtkAction *action,
+               EAttachmentView *view)
+{
+	EAttachmentStore *store;
+	gpointer parent;
+
+	parent = gtk_widget_get_toplevel (GTK_WIDGET (view));
+	parent = gtk_widget_is_toplevel (parent) ? parent : NULL;
+
+	store = e_attachment_view_get_store (view);
+	e_attachment_store_run_load_dialog (store, parent);
+}
+
+static void
+action_cancel_cb (GtkAction *action,
+                  EAttachmentView *view)
+{
+	EAttachment *attachment;
+	GList *list;
+
+	list = e_attachment_view_get_selected_attachments (view);
+	g_return_if_fail (g_list_length (list) == 1);
+	attachment = list->data;
+
+	e_attachment_cancel (attachment);
+
+	g_list_foreach (list, (GFunc) g_object_unref, NULL);
+	g_list_free (list);
+}
+
+static void
+action_hide_cb (GtkAction *action,
+                EAttachmentView *view)
+{
+	EAttachment *attachment;
+	GList *list;
+
+	list = e_attachment_view_get_selected_attachments (view);
+	g_return_if_fail (g_list_length (list) == 1);
+	attachment = list->data;
+
+	e_attachment_set_shown (attachment, FALSE);
+
+	g_list_foreach (list, (GFunc) g_object_unref, NULL);
+	g_list_free (list);
+}
+
+static void
+action_hide_all_cb (GtkAction *action,
+                    EAttachmentView *view)
+{
+	EAttachmentStore *store;
+	GList *list, *iter;
+
+	store = e_attachment_view_get_store (view);
+	list = e_attachment_store_get_attachments (store);
+
+	for (iter = list; iter != NULL; iter = iter->next) {
+		EAttachment *attachment;
+
+		attachment = E_ATTACHMENT (iter->data);
+		e_attachment_set_shown (attachment, FALSE);
+	}
+
+	g_list_foreach (list, (GFunc) g_object_unref, NULL);
+	g_list_free (list);
+}
+
+static void
+action_open_with_cb (GtkAction *action,
+                     EAttachmentView *view)
+{
+	EAttachment *attachment;
+	EAttachmentStore *store;
+	GtkWidget *dialog;
+	GtkTreePath *path;
+	GtkTreeIter iter;
+	GAppInfo *app_info = NULL;
+	GFileInfo *file_info;
+	GList *list;
+	gpointer parent;
+	const gchar *content_type;
+
+	parent = gtk_widget_get_toplevel (GTK_WIDGET (view));
+	parent = gtk_widget_is_toplevel (parent) ? parent : NULL;
+
+	list = e_attachment_view_get_selected_paths (view);
+	g_return_if_fail (g_list_length (list) == 1);
+	path = list->data;
+
+	store = e_attachment_view_get_store (view);
+	gtk_tree_model_get_iter (GTK_TREE_MODEL (store), &iter, path);
+	gtk_tree_model_get (
+		GTK_TREE_MODEL (store), &iter,
+		E_ATTACHMENT_STORE_COLUMN_ATTACHMENT, &attachment, -1);
+	g_return_if_fail (E_IS_ATTACHMENT (attachment));
+
+	file_info = e_attachment_get_file_info (attachment);
+	g_return_if_fail (file_info != NULL);
+
+	content_type = g_file_info_get_content_type (file_info);
+
+	dialog = gtk_app_chooser_dialog_new_for_content_type (
+		parent, 0, content_type);
+	if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_OK) {
+		GtkAppChooser *app_chooser = GTK_APP_CHOOSER (dialog);
+		app_info = gtk_app_chooser_get_app_info (app_chooser);
+	}
+	gtk_widget_destroy (dialog);
+
+	if (app_info != NULL) {
+		e_attachment_view_open_path (view, path, app_info);
+		g_object_unref (app_info);
+	}
+
+	g_list_foreach (list, (GFunc) gtk_tree_path_free, NULL);
+	g_list_free (list);
+}
+
+static void
+action_open_with_app_info_cb (GtkAction *action,
+                              EAttachmentView *view)
+{
+	GAppInfo *app_info;
+	GtkTreePath *path;
+	GList *list;
+
+	list = e_attachment_view_get_selected_paths (view);
+	g_return_if_fail (g_list_length (list) == 1);
+	path = list->data;
+
+	app_info = g_object_get_data (G_OBJECT (action), "app-info");
+	g_return_if_fail (G_IS_APP_INFO (app_info));
+
+	e_attachment_view_open_path (view, path, app_info);
+
+	g_list_foreach (list, (GFunc) gtk_tree_path_free, NULL);
+	g_list_free (list);
+}
+
+static void
+action_properties_cb (GtkAction *action,
+                      EAttachmentView *view)
+{
+	EAttachment *attachment;
+	GtkWidget *dialog;
+	GList *list;
+	gpointer parent;
+
+	list = e_attachment_view_get_selected_attachments (view);
+	g_return_if_fail (g_list_length (list) == 1);
+	attachment = list->data;
+
+	parent = gtk_widget_get_toplevel (GTK_WIDGET (view));
+	parent = gtk_widget_is_toplevel (parent) ? parent : NULL;
+
+	dialog = e_attachment_dialog_new (parent, attachment);
+	gtk_dialog_run (GTK_DIALOG (dialog));
+	gtk_widget_destroy (dialog);
+
+	g_list_foreach (list, (GFunc) g_object_unref, NULL);
+	g_list_free (list);
+}
+
+static void
+action_remove_cb (GtkAction *action,
+                  EAttachmentView *view)
+{
+	e_attachment_view_remove_selected (view, FALSE);
+}
+
+static void
+action_save_all_cb (GtkAction *action,
+                    EAttachmentView *view)
+{
+	EAttachmentStore *store;
+	GList *list, *iter;
+	GFile *destination;
+	gpointer parent;
+
+	store = e_attachment_view_get_store (view);
+
+	parent = gtk_widget_get_toplevel (GTK_WIDGET (view));
+	parent = gtk_widget_is_toplevel (parent) ? parent : NULL;
+
+	/* XXX We lose the previous selection. */
+	e_attachment_view_select_all (view);
+	list = e_attachment_view_get_selected_attachments (view);
+	e_attachment_view_unselect_all (view);
+
+	destination = e_attachment_store_run_save_dialog (
+		store, list, parent);
+
+	if (destination == NULL)
+		goto exit;
+
+	for (iter = list; iter != NULL; iter = iter->next) {
+		EAttachment *attachment = iter->data;
+
+		e_attachment_save_async (
+			attachment, destination, (GAsyncReadyCallback)
+			e_attachment_save_handle_error, parent);
+	}
+
+	g_object_unref (destination);
+
+exit:
+	g_list_foreach (list, (GFunc) g_object_unref, NULL);
+	g_list_free (list);
+}
+
+static void
+action_save_as_cb (GtkAction *action,
+                   EAttachmentView *view)
+{
+	EAttachmentStore *store;
+	GList *list, *iter;
+	GFile *destination;
+	gpointer parent;
+
+	store = e_attachment_view_get_store (view);
+
+	parent = gtk_widget_get_toplevel (GTK_WIDGET (view));
+	parent = gtk_widget_is_toplevel (parent) ? parent : NULL;
+
+	list = e_attachment_view_get_selected_attachments (view);
+
+	destination = e_attachment_store_run_save_dialog (
+		store, list, parent);
+
+	if (destination == NULL)
+		goto exit;
+
+	for (iter = list; iter != NULL; iter = iter->next) {
+		EAttachment *attachment = iter->data;
+
+		e_attachment_save_async (
+			attachment, destination, (GAsyncReadyCallback)
+			e_attachment_save_handle_error, parent);
+	}
+
+	g_object_unref (destination);
+
+exit:
+	g_list_foreach (list, (GFunc) g_object_unref, NULL);
+	g_list_free (list);
+}
+
+static void
+action_show_cb (GtkAction *action,
+                EAttachmentView *view)
+{
+	EAttachment *attachment;
+	GList *list;
+
+	list = e_attachment_view_get_selected_attachments (view);
+	g_return_if_fail (g_list_length (list) == 1);
+	attachment = list->data;
+
+	e_attachment_set_shown (attachment, TRUE);
+
+	g_list_foreach (list, (GFunc) g_object_unref, NULL);
+	g_list_free (list);
+}
+
+static void
+action_show_all_cb (GtkAction *action,
+                    EAttachmentView *view)
+{
+	EAttachmentStore *store;
+	GList *list, *iter;
+
+	store = e_attachment_view_get_store (view);
+	list = e_attachment_store_get_attachments (store);
+
+	for (iter = list; iter != NULL; iter = iter->next) {
+		EAttachment *attachment;
+
+		attachment = E_ATTACHMENT (iter->data);
+		e_attachment_set_shown (attachment, TRUE);
+	}
+
+	g_list_foreach (list, (GFunc) g_object_unref, NULL);
+	g_list_free (list);
+}
+
+static GtkActionEntry standard_entries[] = {
+
+	{ "cancel",
+	  GTK_STOCK_CANCEL,
+	  NULL,
+	  NULL,
+	  NULL,  /* XXX Add a tooltip! */
+	  G_CALLBACK (action_cancel_cb) },
+
+	{ "open-with",
+	  NULL,
+	  N_("Open With Other Application..."),
+	  NULL,
+	  NULL,  /* XXX Add a tooltip! */
+	  G_CALLBACK (action_open_with_cb) },
+
+	{ "save-all",
+	  GTK_STOCK_SAVE_AS,
+	  N_("S_ave All"),
+	  NULL,
+	  NULL,  /* XXX Add a tooltip! */
+	  G_CALLBACK (action_save_all_cb) },
+
+	{ "save-as",
+	  GTK_STOCK_SAVE_AS,
+	  NULL,
+	  NULL,
+	  NULL,  /* XXX Add a tooltip! */
+	  G_CALLBACK (action_save_as_cb) },
+
+	/* Alternate "save-all" label, for when
+	 * the attachment store has one row. */
+	{ "save-one",
+	  GTK_STOCK_SAVE_AS,
+	  NULL,
+	  NULL,
+	  NULL,  /* XXX Add a tooltip! */
+	  G_CALLBACK (action_save_all_cb) },
+};
+
+static GtkActionEntry editable_entries[] = {
+
+	{ "add",
+	  GTK_STOCK_ADD,
+	  N_("A_dd Attachment..."),
+	  NULL,
+	  N_("Attach a file"),
+	  G_CALLBACK (action_add_cb) },
+
+	{ "properties",
+	  GTK_STOCK_PROPERTIES,
+	  NULL,
+	  NULL,
+	  NULL,  /* XXX Add a tooltip! */
+	  G_CALLBACK (action_properties_cb) },
+
+	{ "remove",
+	  GTK_STOCK_REMOVE,
+	  NULL,
+	  NULL,
+	  NULL,  /* XXX Add a tooltip! */
+	  G_CALLBACK (action_remove_cb) }
+};
+
+static GtkActionEntry inline_entries[] = {
+
+	{ "hide",
+	  NULL,
+	  N_("_Hide"),
+	  NULL,
+	  NULL,  /* XXX Add a tooltip! */
+	  G_CALLBACK (action_hide_cb) },
+
+	{ "hide-all",
+	  NULL,
+	  N_("Hid_e All"),
+	  NULL,
+	  NULL,  /* XXX Add a tooltip! */
+	  G_CALLBACK (action_hide_all_cb) },
+
+	{ "show",
+	  NULL,
+	  N_("_View Inline"),
+	  NULL,
+	  NULL,  /* XXX Add a tooltip! */
+	  G_CALLBACK (action_show_cb) },
+
+	{ "show-all",
+	  NULL,
+	  N_("Vie_w All Inline"),
+	  NULL,
+	  NULL,  /* XXX Add a tooltip! */
+	  G_CALLBACK (action_show_all_cb) }
+};
+
+static void
+attachment_view_netscape_url (EAttachmentView *view,
+                              GdkDragContext *drag_context,
+                              gint x,
+                              gint y,
+                              GtkSelectionData *selection_data,
+                              guint info,
+                              guint time)
+{
+	static GdkAtom atom = GDK_NONE;
+	EAttachmentStore *store;
+	EAttachment *attachment;
+	const gchar *data;
+	gpointer parent;
+	gchar *copied_data;
+	gchar **strv;
+	gint length;
+
+	if (G_UNLIKELY (atom == GDK_NONE))
+		atom = gdk_atom_intern_static_string ("_NETSCAPE_URL");
+
+	if (gtk_selection_data_get_target (selection_data) != atom)
+		return;
+
+	g_signal_stop_emission_by_name (view, "drag-data-received");
+
+	/* _NETSCAPE_URL is represented as "URI\nTITLE" */
+
+	data = (const gchar *) gtk_selection_data_get_data (selection_data);
+	length = gtk_selection_data_get_length (selection_data);
+
+	copied_data = g_strndup (data, length);
+	strv = g_strsplit (copied_data, "\n", 2);
+	g_free (copied_data);
+
+	store = e_attachment_view_get_store (view);
+
+	parent = gtk_widget_get_toplevel (GTK_WIDGET (view));
+	parent = gtk_widget_is_toplevel (parent) ? parent : NULL;
+
+	attachment = e_attachment_new_for_uri (strv[0]);
+	e_attachment_store_add_attachment (store, attachment);
+	e_attachment_load_async (
+		attachment, (GAsyncReadyCallback)
+		e_attachment_load_handle_error, parent);
+	g_object_unref (attachment);
+
+	g_strfreev (strv);
+
+	gtk_drag_finish (drag_context, TRUE, FALSE, time);
+}
+
+static void
+attachment_view_text_calendar (EAttachmentView *view,
+                               GdkDragContext *drag_context,
+                               gint x,
+                               gint y,
+                               GtkSelectionData *selection_data,
+                               guint info,
+                               guint time)
+{
+	EAttachmentStore *store;
+	EAttachment *attachment;
+	CamelMimePart *mime_part;
+	GdkAtom data_type;
+	GdkAtom target;
+	const gchar *data;
+	gpointer parent;
+	gchar *content_type;
+	gint length;
+
+	target = gtk_selection_data_get_target (selection_data);
+	if (!e_targets_include_calendar (&target, 1))
+		return;
+
+	g_signal_stop_emission_by_name (view, "drag-data-received");
+
+	data = (const gchar *) gtk_selection_data_get_data (selection_data);
+	length = gtk_selection_data_get_length (selection_data);
+	data_type = gtk_selection_data_get_data_type (selection_data);
+
+	mime_part = camel_mime_part_new ();
+
+	content_type = gdk_atom_name (data_type);
+	camel_mime_part_set_content (mime_part, data, length, content_type);
+	camel_mime_part_set_disposition (mime_part, "inline");
+	g_free (content_type);
+
+	store = e_attachment_view_get_store (view);
+
+	parent = gtk_widget_get_toplevel (GTK_WIDGET (view));
+	parent = gtk_widget_is_toplevel (parent) ? parent : NULL;
+
+	attachment = e_attachment_new ();
+	e_attachment_set_mime_part (attachment, mime_part);
+	e_attachment_store_add_attachment (store, attachment);
+	e_attachment_load_async (
+		attachment, (GAsyncReadyCallback)
+		e_attachment_load_handle_error, parent);
+	g_object_unref (attachment);
+
+	g_object_unref (mime_part);
+
+	gtk_drag_finish (drag_context, TRUE, FALSE, time);
+}
+
+static void
+attachment_view_text_x_vcard (EAttachmentView *view,
+                              GdkDragContext *drag_context,
+                              gint x,
+                              gint y,
+                              GtkSelectionData *selection_data,
+                              guint info,
+                              guint time)
+{
+	EAttachmentStore *store;
+	EAttachment *attachment;
+	CamelMimePart *mime_part;
+	GdkAtom data_type;
+	GdkAtom target;
+	const gchar *data;
+	gpointer parent;
+	gchar *content_type;
+	gint length;
+
+	target = gtk_selection_data_get_target (selection_data);
+	if (!e_targets_include_directory (&target, 1))
+		return;
+
+	g_signal_stop_emission_by_name (view, "drag-data-received");
+
+	data = (const gchar *) gtk_selection_data_get_data (selection_data);
+	length = gtk_selection_data_get_length (selection_data);
+	data_type = gtk_selection_data_get_data_type (selection_data);
+
+	mime_part = camel_mime_part_new ();
+
+	content_type = gdk_atom_name (data_type);
+	camel_mime_part_set_content (mime_part, data, length, content_type);
+	camel_mime_part_set_disposition (mime_part, "inline");
+	g_free (content_type);
+
+	store = e_attachment_view_get_store (view);
+
+	parent = gtk_widget_get_toplevel (GTK_WIDGET (view));
+	parent = gtk_widget_is_toplevel (parent) ? parent : NULL;
+
+	attachment = e_attachment_new ();
+	e_attachment_set_mime_part (attachment, mime_part);
+	e_attachment_store_add_attachment (store, attachment);
+	e_attachment_load_async (
+		attachment, (GAsyncReadyCallback)
+		e_attachment_load_handle_error, parent);
+	g_object_unref (attachment);
+
+	g_object_unref (mime_part);
+
+	gtk_drag_finish (drag_context, TRUE, FALSE, time);
+}
+
+static void
+attachment_view_uris (EAttachmentView *view,
+                      GdkDragContext *drag_context,
+                      gint x,
+                      gint y,
+                      GtkSelectionData *selection_data,
+                      guint info,
+                      guint time)
+{
+	EAttachmentStore *store;
+	gpointer parent;
+	gchar **uris;
+	gint ii;
+
+	uris = gtk_selection_data_get_uris (selection_data);
+
+	if (uris == NULL)
+		return;
+
+	g_signal_stop_emission_by_name (view, "drag-data-received");
+
+	store = e_attachment_view_get_store (view);
+
+	parent = gtk_widget_get_toplevel (GTK_WIDGET (view));
+	parent = gtk_widget_is_toplevel (parent) ? parent : NULL;
+
+	for (ii = 0; uris[ii] != NULL; ii++) {
+		EAttachment *attachment;
+
+		attachment = e_attachment_new_for_uri (uris[ii]);
+		e_attachment_store_add_attachment (store, attachment);
+		e_attachment_load_async (
+			attachment, (GAsyncReadyCallback)
+			e_attachment_load_handle_error, parent);
+		g_object_unref (attachment);
+	}
+
+	g_strfreev (uris);
+
+	gtk_drag_finish (drag_context, TRUE, FALSE, time);
+}
+
+static void
+attachment_view_update_actions (EAttachmentView *view)
+{
+	EAttachmentViewPrivate *priv;
+	EAttachment *attachment;
+	EAttachmentStore *store;
+	GtkActionGroup *action_group;
+	GtkAction *action;
+	GList *list, *iter;
+	guint n_shown = 0;
+	guint n_hidden = 0;
+	guint n_selected;
+	gboolean busy = FALSE;
+	gboolean can_show = FALSE;
+	gboolean shown = FALSE;
+	gboolean visible;
+
+	g_return_if_fail (E_IS_ATTACHMENT_VIEW (view));
+
+	priv = e_attachment_view_get_private (view);
+
+	store = e_attachment_view_get_store (view);
+	list = e_attachment_store_get_attachments (store);
+
+	for (iter = list; iter != NULL; iter = iter->next) {
+		attachment = iter->data;
+
+		if (!e_attachment_get_can_show (attachment))
+			continue;
+
+		if (e_attachment_get_shown (attachment))
+			n_shown++;
+		else
+			n_hidden++;
+	}
+
+	g_list_foreach (list, (GFunc) g_object_unref, NULL);
+	g_list_free (list);
+
+	list = e_attachment_view_get_selected_attachments (view);
+	n_selected = g_list_length (list);
+
+	if (n_selected == 1) {
+		attachment = g_object_ref (list->data);
+		busy |= e_attachment_get_loading (attachment);
+		busy |= e_attachment_get_saving (attachment);
+		can_show = e_attachment_get_can_show (attachment);
+		shown = e_attachment_get_shown (attachment);
+	} else
+		attachment = NULL;
+
+	g_list_foreach (list, (GFunc) g_object_unref, NULL);
+	g_list_free (list);
+
+	action = e_attachment_view_get_action (view, "cancel");
+	gtk_action_set_visible (action, busy);
+
+	action = e_attachment_view_get_action (view, "hide");
+	gtk_action_set_visible (action, can_show && shown);
+
+	/* Show this action if there are multiple viewable
+	 * attachments, and at least one of them is shown. */
+	visible = (n_shown + n_hidden > 1) && (n_shown > 0);
+	action = e_attachment_view_get_action (view, "hide-all");
+	gtk_action_set_visible (action, visible);
+
+	action = e_attachment_view_get_action (view, "open-with");
+	gtk_action_set_visible (action, !busy && n_selected == 1);
+
+	action = e_attachment_view_get_action (view, "properties");
+	gtk_action_set_visible (action, !busy && n_selected == 1);
+
+	action = e_attachment_view_get_action (view, "remove");
+	gtk_action_set_visible (action, !busy && n_selected > 0);
+
+	action = e_attachment_view_get_action (view, "save-as");
+	gtk_action_set_visible (action, !busy && n_selected > 0);
+
+	action = e_attachment_view_get_action (view, "show");
+	gtk_action_set_visible (action, can_show && !shown);
+
+	/* Show this action if there are multiple viewable
+	 * attachments, and at least one of them is hidden. */
+	visible = (n_shown + n_hidden > 1) && (n_hidden > 0);
+	action = e_attachment_view_get_action (view, "show-all");
+	gtk_action_set_visible (action, visible);
+
+	/* Clear out the "openwith" action group. */
+	gtk_ui_manager_remove_ui (priv->ui_manager, priv->merge_id);
+	action_group = e_attachment_view_get_action_group (view, "openwith");
+	e_action_group_remove_all_actions (action_group);
+	gtk_ui_manager_ensure_update (priv->ui_manager);
+
+	if (attachment == NULL || busy)
+		return;
+
+	list = e_attachment_list_apps (attachment);
+
+	for (iter = list; iter != NULL; iter = iter->next) {
+		GAppInfo *app_info = iter->data;
+		GtkAction *action;
+		GIcon *app_icon;
+		const gchar *app_executable;
+		const gchar *app_name;
+		gchar *action_tooltip;
+		gchar *action_label;
+		gchar *action_name;
+
+		app_executable = g_app_info_get_executable (app_info);
+		app_icon = g_app_info_get_icon (app_info);
+		app_name = g_app_info_get_name (app_info);
+
+		action_name = g_strdup_printf ("open-with-%s", app_executable);
+		action_label = g_strdup_printf (_("Open With \"%s\""), app_name);
+
+		action_tooltip = g_strdup_printf (
+			_("Open this attachment in %s"), app_name);
+
+		action = gtk_action_new (
+			action_name, action_label, action_tooltip, NULL);
+
+		gtk_action_set_gicon (action, app_icon);
+
+		g_object_set_data_full (
+			G_OBJECT (action),
+			"app-info", g_object_ref (app_info),
+			(GDestroyNotify) g_object_unref);
+
+		g_object_set_data_full (
+			G_OBJECT (action),
+			"attachment", g_object_ref (attachment),
+			(GDestroyNotify) g_object_unref);
+
+		g_signal_connect (
+			action, "activate",
+			G_CALLBACK (action_open_with_app_info_cb), view);
+
+		gtk_action_group_add_action (action_group, action);
+
+		gtk_ui_manager_add_ui (
+			priv->ui_manager, priv->merge_id,
+			"/context/open-actions", action_name,
+			action_name, GTK_UI_MANAGER_AUTO, FALSE);
+
+		g_free (action_name);
+		g_free (action_label);
+		g_free (action_tooltip);
+	}
+
+	g_object_unref (attachment);
+	g_list_foreach (list, (GFunc) g_object_unref, NULL);
+	g_list_free (list);
+}
+
+static void
+attachment_view_init_drag_dest (EAttachmentView *view)
+{
+	EAttachmentViewPrivate *priv;
+	GtkTargetList *target_list;
+
+	priv = e_attachment_view_get_private (view);
+
+	target_list = gtk_target_list_new (
+		target_table, G_N_ELEMENTS (target_table));
+
+	gtk_target_list_add_uri_targets (target_list, 0);
+	e_target_list_add_calendar_targets (target_list, 0);
+	e_target_list_add_directory_targets (target_list, 0);
+
+	priv->target_list = target_list;
+	priv->drag_actions = GDK_ACTION_COPY;
+}
+
+static void
+e_attachment_view_default_init (EAttachmentViewInterface *interface)
+{
+	interface->update_actions = attachment_view_update_actions;
+
+	g_object_interface_install_property (
+		interface,
+		g_param_spec_boolean (
+			"dragging",
+			"Dragging",
+			NULL,
+			FALSE,
+			G_PARAM_READWRITE));
+
+	g_object_interface_install_property (
+		interface,
+		g_param_spec_boolean (
+			"editable",
+			"Editable",
+			NULL,
+			TRUE,
+			G_PARAM_READWRITE |
+			G_PARAM_CONSTRUCT));
+
+	signals[UPDATE_ACTIONS] = g_signal_new (
+		"update-actions",
+		G_TYPE_FROM_INTERFACE (interface),
+		G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
+		G_STRUCT_OFFSET (EAttachmentViewInterface, update_actions),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__VOID,
+		G_TYPE_NONE, 0);
+
+	/* Register known handler types. */
+	e_attachment_handler_image_get_type ();
+	e_attachment_handler_sendto_get_type ();
+}
+
+void
+e_attachment_view_init (EAttachmentView *view)
+{
+	EAttachmentViewPrivate *priv;
+	GtkUIManager *ui_manager;
+	GtkActionGroup *action_group;
+	GError *error = NULL;
+
+	priv = e_attachment_view_get_private (view);
+
+	ui_manager = e_ui_manager_new ();
+	priv->merge_id = gtk_ui_manager_new_merge_id (ui_manager);
+	priv->ui_manager = ui_manager;
+
+	action_group = e_attachment_view_add_action_group (view, "standard");
+
+	gtk_action_group_add_actions (
+		action_group, standard_entries,
+		G_N_ELEMENTS (standard_entries), view);
+
+	action_group = e_attachment_view_add_action_group (view, "editable");
+
+	g_object_bind_property (
+		view, "editable",
+		action_group, "visible",
+		G_BINDING_BIDIRECTIONAL |
+		G_BINDING_SYNC_CREATE);
+	gtk_action_group_add_actions (
+		action_group, editable_entries,
+		G_N_ELEMENTS (editable_entries), view);
+
+	action_group = e_attachment_view_add_action_group (view, "inline");
+
+	gtk_action_group_add_actions (
+		action_group, inline_entries,
+		G_N_ELEMENTS (inline_entries), view);
+	gtk_action_group_set_visible (action_group, FALSE);
+
+	e_attachment_view_add_action_group (view, "openwith");
+
+	/* Because we are loading from a hard-coded string, there is
+	 * no chance of I/O errors.  Failure here implies a malformed
+	 * UI definition.  Full stop. */
+	gtk_ui_manager_add_ui_from_string (ui_manager, ui, -1, &error);
+	if (error != NULL)
+		g_error ("%s", error->message);
+
+	attachment_view_init_drag_dest (view);
+
+	e_attachment_view_drag_source_set (view);
+
+	/* Connect built-in drag and drop handlers. */
+
+	g_signal_connect (
+		view, "drag-data-received",
+		G_CALLBACK (attachment_view_netscape_url), NULL);
+
+	g_signal_connect (
+		view, "drag-data-received",
+		G_CALLBACK (attachment_view_text_calendar), NULL);
+
+	g_signal_connect (
+		view, "drag-data-received",
+		G_CALLBACK (attachment_view_text_x_vcard), NULL);
+
+	g_signal_connect (
+		view, "drag-data-received",
+		G_CALLBACK (attachment_view_uris), NULL);
+}
+
+void
+e_attachment_view_dispose (EAttachmentView *view)
+{
+	EAttachmentViewPrivate *priv;
+
+	priv = e_attachment_view_get_private (view);
+
+	if (priv->target_list != NULL) {
+		gtk_target_list_unref (priv->target_list);
+		priv->target_list = NULL;
+	}
+
+	if (priv->ui_manager != NULL) {
+		g_object_unref (priv->ui_manager);
+		priv->ui_manager = NULL;
+	}
+}
+
+void
+e_attachment_view_finalize (EAttachmentView *view)
+{
+	EAttachmentViewPrivate *priv;
+
+	priv = e_attachment_view_get_private (view);
+
+	g_list_foreach (priv->event_list, (GFunc) gdk_event_free, NULL);
+	g_list_free (priv->event_list);
+
+	g_list_foreach (priv->selected, (GFunc) g_object_unref, NULL);
+	g_list_free (priv->selected);
+}
+
+EAttachmentViewPrivate *
+e_attachment_view_get_private (EAttachmentView *view)
+{
+	EAttachmentViewInterface *interface;
+
+	g_return_val_if_fail (E_IS_ATTACHMENT_VIEW (view), NULL);
+
+	interface = E_ATTACHMENT_VIEW_GET_INTERFACE (view);
+	g_return_val_if_fail (interface->get_private != NULL, NULL);
+
+	return interface->get_private (view);
+}
+
+EAttachmentStore *
+e_attachment_view_get_store (EAttachmentView *view)
+{
+	EAttachmentViewInterface *interface;
+
+	g_return_val_if_fail (E_IS_ATTACHMENT_VIEW (view), NULL);
+
+	interface = E_ATTACHMENT_VIEW_GET_INTERFACE (view);
+	g_return_val_if_fail (interface->get_store != NULL, NULL);
+
+	return interface->get_store (view);
+}
+
+gboolean
+e_attachment_view_get_editable (EAttachmentView *view)
+{
+	EAttachmentViewPrivate *priv;
+
+	g_return_val_if_fail (E_IS_ATTACHMENT_VIEW (view), FALSE);
+
+	priv = e_attachment_view_get_private (view);
+
+	return priv->editable;
+}
+
+void
+e_attachment_view_set_editable (EAttachmentView *view,
+                                gboolean editable)
+{
+	EAttachmentViewPrivate *priv;
+
+	g_return_if_fail (E_IS_ATTACHMENT_VIEW (view));
+
+	priv = e_attachment_view_get_private (view);
+
+	priv->editable = editable;
+
+	if (editable)
+		e_attachment_view_drag_dest_set (view);
+	else
+		e_attachment_view_drag_dest_unset (view);
+
+	g_object_notify (G_OBJECT (view), "editable");
+}
+
+gboolean
+e_attachment_view_get_dragging (EAttachmentView *view)
+{
+	EAttachmentViewPrivate *priv;
+
+	g_return_val_if_fail (E_IS_ATTACHMENT_VIEW (view), FALSE);
+
+	priv = e_attachment_view_get_private (view);
+
+	return priv->dragging;
+}
+
+void
+e_attachment_view_set_dragging (EAttachmentView *view,
+                                gboolean dragging)
+{
+	EAttachmentViewPrivate *priv;
+
+	g_return_if_fail (E_IS_ATTACHMENT_VIEW (view));
+
+	priv = e_attachment_view_get_private (view);
+
+	priv->dragging = dragging;
+
+	g_object_notify (G_OBJECT (view), "dragging");
+}
+
+GtkTargetList *
+e_attachment_view_get_target_list (EAttachmentView *view)
+{
+	EAttachmentViewPrivate *priv;
+
+	g_return_val_if_fail (E_IS_ATTACHMENT_VIEW (view), NULL);
+
+	priv = e_attachment_view_get_private (view);
+
+	return priv->target_list;
+}
+
+GdkDragAction
+e_attachment_view_get_drag_actions (EAttachmentView *view)
+{
+	EAttachmentViewPrivate *priv;
+
+	g_return_val_if_fail (E_IS_ATTACHMENT_VIEW (view), 0);
+
+	priv = e_attachment_view_get_private (view);
+
+	return priv->drag_actions;
+}
+
+void
+e_attachment_view_add_drag_actions (EAttachmentView *view,
+                                    GdkDragAction drag_actions)
+{
+	EAttachmentViewPrivate *priv;
+
+	g_return_if_fail (E_IS_ATTACHMENT_VIEW (view));
+
+	priv = e_attachment_view_get_private (view);
+
+	priv->drag_actions |= drag_actions;
+}
+
+GList *
+e_attachment_view_get_selected_attachments (EAttachmentView *view)
+{
+	EAttachmentStore *store;
+	GtkTreeModel *model;
+	GList *list, *item;
+	gint column_id;
+
+	g_return_val_if_fail (E_IS_ATTACHMENT_VIEW (view), NULL);
+
+	column_id = E_ATTACHMENT_STORE_COLUMN_ATTACHMENT;
+	list = e_attachment_view_get_selected_paths (view);
+	store = e_attachment_view_get_store (view);
+	model = GTK_TREE_MODEL (store);
+
+	/* Convert the GtkTreePaths to EAttachments. */
+	for (item = list; item != NULL; item = item->next) {
+		EAttachment *attachment;
+		GtkTreePath *path;
+		GtkTreeIter iter;
+
+		path = item->data;
+
+		gtk_tree_model_get_iter (model, &iter, path);
+		gtk_tree_model_get (model, &iter, column_id, &attachment, -1);
+		gtk_tree_path_free (path);
+
+		item->data = attachment;
+	}
+
+	return list;
+}
+
+void
+e_attachment_view_open_path (EAttachmentView *view,
+                             GtkTreePath *path,
+                             GAppInfo *app_info)
+{
+	EAttachmentStore *store;
+	EAttachment *attachment;
+	GtkTreeModel *model;
+	GtkTreeIter iter;
+	gpointer parent;
+	gint column_id;
+
+	g_return_if_fail (E_IS_ATTACHMENT_VIEW (view));
+	g_return_if_fail (path != NULL);
+
+	column_id = E_ATTACHMENT_STORE_COLUMN_ATTACHMENT;
+	store = e_attachment_view_get_store (view);
+	model = GTK_TREE_MODEL (store);
+
+	gtk_tree_model_get_iter (model, &iter, path);
+	gtk_tree_model_get (model, &iter, column_id, &attachment, -1);
+
+	parent = gtk_widget_get_toplevel (GTK_WIDGET (view));
+	parent = gtk_widget_is_toplevel (parent) ? parent : NULL;
+
+	e_attachment_open_async (
+		attachment, app_info, (GAsyncReadyCallback)
+		e_attachment_open_handle_error, parent);
+
+	g_object_unref (attachment);
+}
+
+void
+e_attachment_view_remove_selected (EAttachmentView *view,
+                                   gboolean select_next)
+{
+	EAttachmentStore *store;
+	GtkTreeModel *model;
+	GList *list, *item;
+	gint column_id;
+
+	g_return_if_fail (E_IS_ATTACHMENT_VIEW (view));
+
+	column_id = E_ATTACHMENT_STORE_COLUMN_ATTACHMENT;
+	list = e_attachment_view_get_selected_paths (view);
+	store = e_attachment_view_get_store (view);
+	model = GTK_TREE_MODEL (store);
+
+	/* Remove attachments in reverse order to avoid invalidating
+	 * tree paths as we iterate over the list.  Note, the list is
+	 * probably already sorted but we sort again just to be safe. */
+	list = g_list_reverse (g_list_sort (
+		list, (GCompareFunc) gtk_tree_path_compare));
+
+	for (item = list; item != NULL; item = item->next) {
+		EAttachment *attachment;
+		GtkTreePath *path = item->data;
+		GtkTreeIter iter;
+
+		gtk_tree_model_get_iter (model, &iter, path);
+		gtk_tree_model_get (model, &iter, column_id, &attachment, -1);
+		e_attachment_store_remove_attachment (store, attachment);
+		g_object_unref (attachment);
+	}
+
+	/* If we only removed one attachment, try to select another. */
+	if (select_next && g_list_length (list) == 1) {
+		GtkTreePath *path = list->data;
+
+		e_attachment_view_select_path (view, path);
+		if (!e_attachment_view_path_is_selected (view, path))
+			if (gtk_tree_path_prev (path))
+				e_attachment_view_select_path (view, path);
+	}
+
+	g_list_foreach (list, (GFunc) gtk_tree_path_free, NULL);
+	g_list_free (list);
+}
+
+gboolean
+e_attachment_view_button_press_event (EAttachmentView *view,
+                                      GdkEventButton *event)
+{
+	EAttachmentViewPrivate *priv;
+	GtkTreePath *path;
+	gboolean editable;
+	gboolean handled = FALSE;
+	gboolean path_is_selected = FALSE;
+
+	g_return_val_if_fail (E_IS_ATTACHMENT_VIEW (view), FALSE);
+	g_return_val_if_fail (event != NULL, FALSE);
+
+	priv = e_attachment_view_get_private (view);
+
+	if (g_list_find (priv->event_list, event) != NULL)
+		return FALSE;
+
+	if (priv->event_list != NULL) {
+		/* Save the event to be propagated in order. */
+		priv->event_list = g_list_append (
+			priv->event_list,
+			gdk_event_copy ((GdkEvent *) event));
+		return TRUE;
+	}
+
+	editable = e_attachment_view_get_editable (view);
+	path = e_attachment_view_get_path_at_pos (view, event->x, event->y);
+	path_is_selected = e_attachment_view_path_is_selected (view, path);
+
+	if (event->button == 1 && event->type == GDK_BUTTON_PRESS) {
+		GList *list, *iter;
+		gboolean busy = FALSE;
+
+		list = e_attachment_view_get_selected_attachments (view);
+
+		for (iter = list; iter != NULL; iter = iter->next) {
+			EAttachment *attachment = iter->data;
+			busy |= e_attachment_get_loading (attachment);
+			busy |= e_attachment_get_saving (attachment);
+		}
+
+		/* Prepare for dragging if the clicked item is selected
+		 * and none of the selected items are loading or saving. */
+		if (path_is_selected && !busy) {
+			priv->start_x = event->x;
+			priv->start_y = event->y;
+			priv->event_list = g_list_append (
+				priv->event_list,
+				gdk_event_copy ((GdkEvent *) event));
+			handled = TRUE;
+		}
+
+		g_list_foreach (list, (GFunc) g_object_unref, NULL);
+		g_list_free (list);
+	}
+
+	if (event->button == 3 && event->type == GDK_BUTTON_PRESS) {
+		/* If the user clicked on a selected item, retain the
+		 * current selection.  If the user clicked on an unselected
+		 * item, select the clicked item only.  If the user did not
+		 * click on an item, clear the current selection. */
+		if (path == NULL)
+			e_attachment_view_unselect_all (view);
+		else if (!path_is_selected) {
+			e_attachment_view_unselect_all (view);
+			e_attachment_view_select_path (view, path);
+		}
+
+		/* Non-editable attachment views should only show a
+		 * popup menu when right-clicking on an attachment,
+		 * but editable views can show the menu any time. */
+		if (path != NULL || editable) {
+			e_attachment_view_show_popup_menu (
+				view, event, NULL, NULL);
+			handled = TRUE;
+		}
+	}
+
+	if (path != NULL)
+		gtk_tree_path_free (path);
+
+	return handled;
+}
+
+gboolean
+e_attachment_view_button_release_event (EAttachmentView *view,
+                                        GdkEventButton *event)
+{
+	EAttachmentViewPrivate *priv;
+	GtkWidget *widget = GTK_WIDGET (view);
+	GList *iter;
+
+	g_return_val_if_fail (E_IS_ATTACHMENT_VIEW (view), FALSE);
+	g_return_val_if_fail (event != NULL, FALSE);
+
+	priv = e_attachment_view_get_private (view);
+
+	for (iter = priv->event_list; iter != NULL; iter = iter->next) {
+		GdkEvent *event = iter->data;
+
+		gtk_propagate_event (widget, event);
+		gdk_event_free (event);
+	}
+
+	g_list_free (priv->event_list);
+	priv->event_list = NULL;
+
+	return FALSE;
+}
+
+gboolean
+e_attachment_view_motion_notify_event (EAttachmentView *view,
+                                       GdkEventMotion *event)
+{
+	EAttachmentViewPrivate *priv;
+	GtkWidget *widget = GTK_WIDGET (view);
+	GtkTargetList *targets;
+
+	g_return_val_if_fail (E_IS_ATTACHMENT_VIEW (view), FALSE);
+	g_return_val_if_fail (event != NULL, FALSE);
+
+	priv = e_attachment_view_get_private (view);
+
+	if (priv->event_list == NULL)
+		return FALSE;
+
+	if (!gtk_drag_check_threshold (
+		widget, priv->start_x, priv->start_y, event->x, event->y))
+		return TRUE;
+
+	g_list_foreach (priv->event_list, (GFunc) gdk_event_free, NULL);
+	g_list_free (priv->event_list);
+	priv->event_list = NULL;
+
+	targets = gtk_drag_source_get_target_list (widget);
+
+	gtk_drag_begin (
+		widget, targets, GDK_ACTION_COPY, 1, (GdkEvent *) event);
+
+	return TRUE;
+}
+
+gboolean
+e_attachment_view_key_press_event (EAttachmentView *view,
+                                   GdkEventKey *event)
+{
+	gboolean editable;
+
+	g_return_val_if_fail (E_IS_ATTACHMENT_VIEW (view), FALSE);
+	g_return_val_if_fail (event != NULL, FALSE);
+
+	editable = e_attachment_view_get_editable (view);
+
+	if (event->keyval == GDK_KEY_Delete && editable) {
+		e_attachment_view_remove_selected (view, TRUE);
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+GtkTreePath *
+e_attachment_view_get_path_at_pos (EAttachmentView *view,
+                                   gint x,
+                                   gint y)
+{
+	EAttachmentViewInterface *interface;
+
+	g_return_val_if_fail (E_IS_ATTACHMENT_VIEW (view), NULL);
+
+	interface = E_ATTACHMENT_VIEW_GET_INTERFACE (view);
+	g_return_val_if_fail (interface->get_path_at_pos != NULL, NULL);
+
+	return interface->get_path_at_pos (view, x, y);
+}
+
+GList *
+e_attachment_view_get_selected_paths (EAttachmentView *view)
+{
+	EAttachmentViewInterface *interface;
+
+	g_return_val_if_fail (E_IS_ATTACHMENT_VIEW (view), NULL);
+
+	interface = E_ATTACHMENT_VIEW_GET_INTERFACE (view);
+	g_return_val_if_fail (interface->get_selected_paths != NULL, NULL);
+
+	return interface->get_selected_paths (view);
+}
+
+gboolean
+e_attachment_view_path_is_selected (EAttachmentView *view,
+                                    GtkTreePath *path)
+{
+	EAttachmentViewInterface *interface;
+
+	g_return_val_if_fail (E_IS_ATTACHMENT_VIEW (view), FALSE);
+
+	/* Handle NULL paths gracefully. */
+	if (path == NULL)
+		return FALSE;
+
+	interface = E_ATTACHMENT_VIEW_GET_INTERFACE (view);
+	g_return_val_if_fail (interface->path_is_selected != NULL, FALSE);
+
+	return interface->path_is_selected (view, path);
+}
+
+void
+e_attachment_view_select_path (EAttachmentView *view,
+                               GtkTreePath *path)
+{
+	EAttachmentViewInterface *interface;
+
+	g_return_if_fail (E_IS_ATTACHMENT_VIEW (view));
+	g_return_if_fail (path != NULL);
+
+	interface = E_ATTACHMENT_VIEW_GET_INTERFACE (view);
+	g_return_if_fail (interface->select_path != NULL);
+
+	interface->select_path (view, path);
+}
+
+void
+e_attachment_view_unselect_path (EAttachmentView *view,
+                                 GtkTreePath *path)
+{
+	EAttachmentViewInterface *interface;
+
+	g_return_if_fail (E_IS_ATTACHMENT_VIEW (view));
+	g_return_if_fail (path != NULL);
+
+	interface = E_ATTACHMENT_VIEW_GET_INTERFACE (view);
+	g_return_if_fail (interface->unselect_path != NULL);
+
+	interface->unselect_path (view, path);
+}
+
+void
+e_attachment_view_select_all (EAttachmentView *view)
+{
+	EAttachmentViewInterface *interface;
+
+	g_return_if_fail (E_IS_ATTACHMENT_VIEW (view));
+
+	interface = E_ATTACHMENT_VIEW_GET_INTERFACE (view);
+	g_return_if_fail (interface->select_all != NULL);
+
+	interface->select_all (view);
+}
+
+void
+e_attachment_view_unselect_all (EAttachmentView *view)
+{
+	EAttachmentViewInterface *interface;
+
+	g_return_if_fail (E_IS_ATTACHMENT_VIEW (view));
+
+	interface = E_ATTACHMENT_VIEW_GET_INTERFACE (view);
+	g_return_if_fail (interface->unselect_all != NULL);
+
+	interface->unselect_all (view);
+}
+
+void
+e_attachment_view_sync_selection (EAttachmentView *view,
+                                  EAttachmentView *target)
+{
+	GList *list, *iter;
+
+	g_return_if_fail (E_IS_ATTACHMENT_VIEW (view));
+	g_return_if_fail (E_IS_ATTACHMENT_VIEW (target));
+
+	list = e_attachment_view_get_selected_paths (view);
+	e_attachment_view_unselect_all (target);
+
+	for (iter = list; iter != NULL; iter = iter->next)
+		e_attachment_view_select_path (target, iter->data);
+
+	g_list_foreach (list, (GFunc) gtk_tree_path_free, NULL);
+	g_list_free (list);
+}
+
+void
+e_attachment_view_drag_source_set (EAttachmentView *view)
+{
+	EAttachmentViewInterface *interface;
+	GtkTargetEntry *targets;
+	GtkTargetList *list;
+	gint n_targets;
+
+	g_return_if_fail (E_IS_ATTACHMENT_VIEW (view));
+
+	interface = E_ATTACHMENT_VIEW_GET_INTERFACE (view);
+	if (interface->drag_source_set == NULL)
+		return;
+
+	list = gtk_target_list_new (NULL, 0);
+	gtk_target_list_add_uri_targets (list, 0);
+	targets = gtk_target_table_new_from_list (list, &n_targets);
+
+	interface->drag_source_set (
+		view, GDK_BUTTON1_MASK,
+		targets, n_targets, GDK_ACTION_COPY);
+
+	gtk_target_table_free (targets, n_targets);
+	gtk_target_list_unref (list);
+}
+
+void
+e_attachment_view_drag_source_unset (EAttachmentView *view)
+{
+	EAttachmentViewInterface *interface;
+
+	g_return_if_fail (E_IS_ATTACHMENT_VIEW (view));
+
+	interface = E_ATTACHMENT_VIEW_GET_INTERFACE (view);
+	if (interface->drag_source_unset == NULL)
+		return;
+
+	interface->drag_source_unset (view);
+}
+
+void
+e_attachment_view_drag_begin (EAttachmentView *view,
+                              GdkDragContext *context)
+{
+	EAttachmentViewPrivate *priv;
+	guint n_selected;
+
+	g_return_if_fail (E_IS_ATTACHMENT_VIEW (view));
+	g_return_if_fail (GDK_IS_DRAG_CONTEXT (context));
+
+	priv = e_attachment_view_get_private (view);
+
+	e_attachment_view_set_dragging (view, TRUE);
+
+	g_warn_if_fail (priv->selected == NULL);
+	priv->selected = e_attachment_view_get_selected_attachments (view);
+	n_selected = g_list_length (priv->selected);
+
+	if (n_selected > 1)
+		gtk_drag_set_icon_stock (
+			context, GTK_STOCK_DND_MULTIPLE, 0, 0);
+
+	else if (n_selected == 1) {
+		EAttachment *attachment;
+		GtkIconTheme *icon_theme;
+		GtkIconInfo *icon_info;
+		GIcon *icon;
+		gint width, height;
+
+		attachment = E_ATTACHMENT (priv->selected->data);
+		icon = e_attachment_get_icon (attachment);
+		g_return_if_fail (icon != NULL);
+
+		icon_theme = gtk_icon_theme_get_default ();
+		gtk_icon_size_lookup (GTK_ICON_SIZE_DND, &width, &height);
+
+		icon_info = gtk_icon_theme_lookup_by_gicon (
+			icon_theme, icon, MIN (width, height),
+			GTK_ICON_LOOKUP_USE_BUILTIN);
+
+		if (icon_info != NULL) {
+			GdkPixbuf *pixbuf;
+			GError *error = NULL;
+
+			pixbuf = gtk_icon_info_load_icon (icon_info, &error);
+
+			if (pixbuf != NULL) {
+				gtk_drag_set_icon_pixbuf (
+					context, pixbuf, 0, 0);
+				g_object_unref (pixbuf);
+			} else if (error != NULL) {
+				g_warning ("%s", error->message);
+				g_error_free (error);
+			}
+
+			gtk_icon_info_free (icon_info);
+		}
+	}
+}
+
+void
+e_attachment_view_drag_end (EAttachmentView *view,
+                            GdkDragContext *context)
+{
+	EAttachmentViewPrivate *priv;
+
+	g_return_if_fail (E_IS_ATTACHMENT_VIEW (view));
+	g_return_if_fail (GDK_IS_DRAG_CONTEXT (context));
+
+	priv = e_attachment_view_get_private (view);
+
+	e_attachment_view_set_dragging (view, FALSE);
+
+	g_list_foreach (priv->selected, (GFunc) g_object_unref, NULL);
+	g_list_free (priv->selected);
+	priv->selected = NULL;
+}
+
+static void
+attachment_view_got_uris_cb (EAttachmentStore *store,
+                             GAsyncResult *result,
+                             gpointer user_data)
+{
+	struct {
+		gchar **uris;
+		gboolean done;
+	} *status = user_data;
+
+	/* XXX Since this is a best-effort function,
+	 *     should we care about errors? */
+	status->uris = e_attachment_store_get_uris_finish (
+		store, result, NULL);
+
+	status->done = TRUE;
+}
+
+void
+e_attachment_view_drag_data_get (EAttachmentView *view,
+                                 GdkDragContext *context,
+                                 GtkSelectionData *selection,
+                                 guint info,
+                                 guint time)
+{
+	EAttachmentViewPrivate *priv;
+	EAttachmentStore *store;
+
+	struct {
+		gchar **uris;
+		gboolean done;
+	} status;
+
+	g_return_if_fail (E_IS_ATTACHMENT_VIEW (view));
+	g_return_if_fail (GDK_IS_DRAG_CONTEXT (context));
+	g_return_if_fail (selection != NULL);
+
+	status.uris = NULL;
+	status.done = FALSE;
+
+	priv = e_attachment_view_get_private (view);
+	store = e_attachment_view_get_store (view);
+
+	if (priv->selected == NULL)
+		return;
+
+	e_attachment_store_get_uris_async (
+		store, priv->selected, (GAsyncReadyCallback)
+		attachment_view_got_uris_cb, &status);
+
+	/* We can't return until we have results, so crank
+	 * the main loop until the callback gets triggered. */
+	while (!status.done)
+		if (gtk_main_iteration ())
+			break;
+
+	if (status.uris != NULL)
+		gtk_selection_data_set_uris (selection, status.uris);
+
+	g_strfreev (status.uris);
+}
+
+void
+e_attachment_view_drag_dest_set (EAttachmentView *view)
+{
+	EAttachmentViewPrivate *priv;
+	EAttachmentViewInterface *interface;
+	GtkTargetEntry *targets;
+	gint n_targets;
+
+	g_return_if_fail (E_IS_ATTACHMENT_VIEW (view));
+
+	interface = E_ATTACHMENT_VIEW_GET_INTERFACE (view);
+	if (interface->drag_dest_set == NULL)
+		return;
+
+	priv = e_attachment_view_get_private (view);
+
+	targets = gtk_target_table_new_from_list (
+		priv->target_list, &n_targets);
+
+	interface->drag_dest_set (
+		view, targets, n_targets, priv->drag_actions);
+
+	gtk_target_table_free (targets, n_targets);
+}
+
+void
+e_attachment_view_drag_dest_unset (EAttachmentView *view)
+{
+	EAttachmentViewInterface *interface;
+
+	g_return_if_fail (E_IS_ATTACHMENT_VIEW (view));
+
+	interface = E_ATTACHMENT_VIEW_GET_INTERFACE (view);
+	if (interface->drag_dest_unset == NULL)
+		return;
+
+	interface->drag_dest_unset (view);
+}
+
+gboolean
+e_attachment_view_drag_motion (EAttachmentView *view,
+                               GdkDragContext *context,
+                               gint x,
+                               gint y,
+                               guint time)
+{
+	EAttachmentViewPrivate *priv;
+	GdkDragAction actions;
+	GdkDragAction chosen_action;
+
+	g_return_val_if_fail (E_IS_ATTACHMENT_VIEW (view), FALSE);
+	g_return_val_if_fail (GDK_IS_DRAG_CONTEXT (context), FALSE);
+
+	priv = e_attachment_view_get_private (view);
+
+	/* Disallow drops if we're not editable. */
+	if (!e_attachment_view_get_editable (view))
+		return FALSE;
+
+	/* Disallow drops if we initiated the drag.
+	 * This helps prevent duplicate attachments. */
+	if (e_attachment_view_get_dragging (view))
+		return FALSE;
+
+	actions = gdk_drag_context_get_actions (context);
+	actions &= priv->drag_actions;
+	chosen_action = gdk_drag_context_get_suggested_action (context);
+
+	if (chosen_action == GDK_ACTION_ASK) {
+		GdkDragAction mask;
+
+		mask = GDK_ACTION_COPY | GDK_ACTION_MOVE;
+		if ((actions & mask) != mask)
+			chosen_action = GDK_ACTION_COPY;
+	}
+
+	gdk_drag_status (context, chosen_action, time);
+
+	return (chosen_action != 0);
+}
+
+gboolean
+e_attachment_view_drag_drop (EAttachmentView *view,
+                             GdkDragContext *context,
+                             gint x,
+                             gint y,
+                             guint time)
+{
+	g_return_val_if_fail (E_IS_ATTACHMENT_VIEW (view), FALSE);
+	g_return_val_if_fail (GDK_IS_DRAG_CONTEXT (context), FALSE);
+
+	/* Disallow drops if we initiated the drag.
+	 * This helps prevent duplicate attachments. */
+	return !e_attachment_view_get_dragging (view);
+}
+
+void
+e_attachment_view_drag_data_received (EAttachmentView *view,
+                                      GdkDragContext *drag_context,
+                                      gint x,
+                                      gint y,
+                                      GtkSelectionData *selection_data,
+                                      guint info,
+                                      guint time)
+{
+	GdkAtom atom;
+	gchar *name;
+
+	g_return_if_fail (E_IS_ATTACHMENT_VIEW (view));
+	g_return_if_fail (GDK_IS_DRAG_CONTEXT (drag_context));
+
+	/* Drop handlers are supposed to stop further emission of the
+	 * "drag-data-received" signal if they can handle the data.  If
+	 * we get this far it means none of the handlers were successful,
+	 * so report the drop as failed. */
+
+	atom = gtk_selection_data_get_target (selection_data);
+
+	name = gdk_atom_name (atom);
+	g_warning ("Unknown selection target: %s", name);
+	g_free (name);
+
+	gtk_drag_finish (drag_context, FALSE, FALSE, time);
+}
+
+GtkAction *
+e_attachment_view_get_action (EAttachmentView *view,
+                              const gchar *action_name)
+{
+	GtkUIManager *ui_manager;
+
+	g_return_val_if_fail (E_IS_ATTACHMENT_VIEW (view), NULL);
+	g_return_val_if_fail (action_name != NULL, NULL);
+
+	ui_manager = e_attachment_view_get_ui_manager (view);
+
+	return e_lookup_action (ui_manager, action_name);
+}
+
+GtkActionGroup *
+e_attachment_view_add_action_group (EAttachmentView *view,
+                                    const gchar *group_name)
+{
+	GtkActionGroup *action_group;
+	GtkUIManager *ui_manager;
+	const gchar *domain;
+
+	g_return_val_if_fail (E_IS_ATTACHMENT_VIEW (view), NULL);
+	g_return_val_if_fail (group_name != NULL, NULL);
+
+	ui_manager = e_attachment_view_get_ui_manager (view);
+	domain = GETTEXT_PACKAGE;
+
+	action_group = gtk_action_group_new (group_name);
+	gtk_action_group_set_translation_domain (action_group, domain);
+	gtk_ui_manager_insert_action_group (ui_manager, action_group, 0);
+	g_object_unref (action_group);
+
+	return action_group;
+}
+
+GtkActionGroup *
+e_attachment_view_get_action_group (EAttachmentView *view,
+                                    const gchar *group_name)
+{
+	GtkUIManager *ui_manager;
+
+	g_return_val_if_fail (E_IS_ATTACHMENT_VIEW (view), NULL);
+	g_return_val_if_fail (group_name != NULL, NULL);
+
+	ui_manager = e_attachment_view_get_ui_manager (view);
+
+	return e_lookup_action_group (ui_manager, group_name);
+}
+
+GtkWidget *
+e_attachment_view_get_popup_menu (EAttachmentView *view)
+{
+	GtkUIManager *ui_manager;
+	GtkWidget *menu;
+
+	g_return_val_if_fail (E_IS_ATTACHMENT_VIEW (view), NULL);
+
+	ui_manager = e_attachment_view_get_ui_manager (view);
+	menu = gtk_ui_manager_get_widget (ui_manager, "/context");
+	g_return_val_if_fail (GTK_IS_MENU (menu), NULL);
+
+	return menu;
+}
+
+GtkUIManager *
+e_attachment_view_get_ui_manager (EAttachmentView *view)
+{
+	EAttachmentViewPrivate *priv;
+
+	g_return_val_if_fail (E_IS_ATTACHMENT_VIEW (view), NULL);
+
+	priv = e_attachment_view_get_private (view);
+
+	return priv->ui_manager;
+}
+
+void
+e_attachment_view_show_popup_menu (EAttachmentView *view,
+                                   GdkEventButton *event,
+                                   GtkMenuPositionFunc func,
+                                   gpointer user_data)
+{
+	GtkWidget *menu;
+
+	g_return_if_fail (E_IS_ATTACHMENT_VIEW (view));
+
+	e_attachment_view_update_actions (view);
+
+	menu = e_attachment_view_get_popup_menu (view);
+
+	if (event != NULL)
+		gtk_menu_popup (
+			GTK_MENU (menu), NULL, NULL, func,
+			user_data, event->button, event->time);
+	else
+		gtk_menu_popup (
+			GTK_MENU (menu), NULL, NULL, func,
+			user_data, 0, gtk_get_current_event_time ());
+}
+
+void
+e_attachment_view_update_actions (EAttachmentView *view)
+{
+	g_return_if_fail (E_IS_ATTACHMENT_VIEW (view));
+
+	g_signal_emit (view, signals[UPDATE_ACTIONS], 0);
+}
diff --git a/e-util/e-attachment-view.h b/e-util/e-attachment-view.h
new file mode 100644
index 0000000..1741815
--- /dev/null
+++ b/e-util/e-attachment-view.h
@@ -0,0 +1,244 @@
+/*
+ * e-attachment-view.h
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_ATTACHMENT_VIEW_H
+#define E_ATTACHMENT_VIEW_H
+
+#include <gtk/gtk.h>
+#include <e-util/e-attachment-store.h>
+
+/* Standard GObject macros */
+#define E_TYPE_ATTACHMENT_VIEW \
+	(e_attachment_view_get_type ())
+#define E_ATTACHMENT_VIEW(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_ATTACHMENT_VIEW, EAttachmentView))
+#define E_ATTACHMENT_VIEW_INTERFACE(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_ATTACHMENT_VIEW, EAttachmentViewInterface))
+#define E_IS_ATTACHMENT_VIEW(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_ATTACHMENT_VIEW))
+#define E_IS_ATTACHMENT_VIEW_INTERFACE(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_ATTACHMENT_VIEW))
+#define E_ATTACHMENT_VIEW_GET_INTERFACE(obj) \
+	(G_TYPE_INSTANCE_GET_INTERFACE \
+	((obj), E_TYPE_ATTACHMENT_VIEW, EAttachmentViewInterface))
+
+G_BEGIN_DECLS
+
+typedef struct _EAttachmentView EAttachmentView;
+typedef struct _EAttachmentViewInterface EAttachmentViewInterface;
+typedef struct _EAttachmentViewPrivate EAttachmentViewPrivate;
+
+struct _EAttachmentViewInterface {
+	GTypeInterface parent_interface;
+
+	/* General Methods */
+	EAttachmentViewPrivate *
+			(*get_private)		(EAttachmentView *view);
+	EAttachmentStore *
+			(*get_store)		(EAttachmentView *view);
+
+	/* Selection Methods */
+	GtkTreePath *	(*get_path_at_pos)	(EAttachmentView *view,
+						 gint x,
+						 gint y);
+	GList *		(*get_selected_paths)	(EAttachmentView *view);
+	gboolean	(*path_is_selected)	(EAttachmentView *view,
+						 GtkTreePath *path);
+	void		(*select_path)		(EAttachmentView *view,
+						 GtkTreePath *path);
+	void		(*unselect_path)	(EAttachmentView *view,
+						 GtkTreePath *path);
+	void		(*select_all)		(EAttachmentView *view);
+	void		(*unselect_all)		(EAttachmentView *view);
+
+	/* Drag and Drop Methods */
+	void		(*drag_source_set)	(EAttachmentView *view,
+						 GdkModifierType start_button_mask,
+						 const GtkTargetEntry *targets,
+						 gint n_targets,
+						 GdkDragAction actions);
+	void		(*drag_dest_set)	(EAttachmentView *view,
+						 const GtkTargetEntry *targets,
+						 gint n_targets,
+						 GdkDragAction actions);
+	void		(*drag_source_unset)	(EAttachmentView *view);
+	void		(*drag_dest_unset)	(EAttachmentView *view);
+
+	/* Signals */
+	void		(*update_actions)	(EAttachmentView *view);
+};
+
+struct _EAttachmentViewPrivate {
+
+	/* Drag Destination */
+	GtkTargetList *target_list;
+	GdkDragAction drag_actions;
+
+	/* Popup Menu Management */
+	GtkUIManager *ui_manager;
+	guint merge_id;
+
+	/* Multi-DnD State */
+	GList *event_list;
+	GList *selected;
+	gint start_x;
+	gint start_y;
+
+	guint dragging : 1;
+	guint editable : 1;
+};
+
+GType		e_attachment_view_get_type	(void);
+
+void		e_attachment_view_init		(EAttachmentView *view);
+void		e_attachment_view_dispose	(EAttachmentView *view);
+void		e_attachment_view_finalize	(EAttachmentView *view);
+
+EAttachmentViewPrivate *
+		e_attachment_view_get_private	(EAttachmentView *view);
+EAttachmentStore *
+		e_attachment_view_get_store	(EAttachmentView *view);
+gboolean	e_attachment_view_get_dragging	(EAttachmentView *view);
+void		e_attachment_view_set_dragging	(EAttachmentView *view,
+						 gboolean dragging);
+gboolean	e_attachment_view_get_editable	(EAttachmentView *view);
+void		e_attachment_view_set_editable	(EAttachmentView *view,
+						 gboolean editable);
+GtkTargetList *	e_attachment_view_get_target_list
+						(EAttachmentView *view);
+GdkDragAction	e_attachment_view_get_drag_actions
+						(EAttachmentView *view);
+void		e_attachment_view_add_drag_actions
+						(EAttachmentView *view,
+						 GdkDragAction drag_actions);
+GList *		e_attachment_view_get_selected_attachments
+						(EAttachmentView *view);
+void		e_attachment_view_open_path	(EAttachmentView *view,
+						 GtkTreePath *path,
+						 GAppInfo *app_info);
+void		e_attachment_view_remove_selected
+						(EAttachmentView *view,
+						 gboolean select_next);
+
+/* Event Support */
+gboolean	e_attachment_view_button_press_event
+						(EAttachmentView *view,
+						 GdkEventButton *event);
+gboolean	e_attachment_view_button_release_event
+						(EAttachmentView *view,
+						 GdkEventButton *event);
+gboolean	e_attachment_view_motion_notify_event
+						(EAttachmentView *view,
+						 GdkEventMotion *event);
+gboolean	e_attachment_view_key_press_event
+						(EAttachmentView *view,
+						 GdkEventKey *event);
+
+/* Selection Management */
+GtkTreePath *	e_attachment_view_get_path_at_pos
+						(EAttachmentView *view,
+						 gint x,
+						 gint y);
+GList *		e_attachment_view_get_selected_paths
+						(EAttachmentView *view);
+gboolean	e_attachment_view_path_is_selected
+						(EAttachmentView *view,
+						 GtkTreePath *path);
+void		e_attachment_view_select_path	(EAttachmentView *view,
+						 GtkTreePath *path);
+void		e_attachment_view_unselect_path	(EAttachmentView *view,
+						 GtkTreePath *path);
+void		e_attachment_view_select_all	(EAttachmentView *view);
+void		e_attachment_view_unselect_all	(EAttachmentView *view);
+void		e_attachment_view_sync_selection
+						(EAttachmentView *view,
+						 EAttachmentView *target);
+
+/* Drag Source Support */
+void		e_attachment_view_drag_source_set
+						(EAttachmentView *view);
+void		e_attachment_view_drag_source_unset
+						(EAttachmentView *view);
+void		e_attachment_view_drag_begin	(EAttachmentView *view,
+						 GdkDragContext *context);
+void		e_attachment_view_drag_end	(EAttachmentView *view,
+						 GdkDragContext *context);
+void		e_attachment_view_drag_data_get	(EAttachmentView *view,
+						 GdkDragContext *context,
+						 GtkSelectionData *selection,
+						 guint info,
+						 guint time);
+
+/* Drag Destination Support */
+void		e_attachment_view_drag_dest_set	(EAttachmentView *view);
+void		e_attachment_view_drag_dest_unset
+						(EAttachmentView *view);
+gboolean	e_attachment_view_drag_motion	(EAttachmentView *view,
+						 GdkDragContext *context,
+						 gint x,
+						 gint y,
+						 guint time);
+gboolean	e_attachment_view_drag_drop	(EAttachmentView *view,
+						 GdkDragContext *context,
+						 gint x,
+						 gint y,
+						 guint time);
+void		e_attachment_view_drag_data_received
+						(EAttachmentView *view,
+						 GdkDragContext *context,
+						 gint x,
+						 gint y,
+						 GtkSelectionData *selection,
+						 guint info,
+						 guint time);
+
+/* Popup Menu Management */
+GtkAction *	e_attachment_view_get_action	(EAttachmentView *view,
+						 const gchar *action_name);
+GtkActionGroup *e_attachment_view_add_action_group
+						(EAttachmentView *view,
+						 const gchar *group_name);
+GtkActionGroup *e_attachment_view_get_action_group
+						(EAttachmentView *view,
+						 const gchar *group_name);
+GtkWidget *	e_attachment_view_get_popup_menu
+						(EAttachmentView *view);
+GtkUIManager *	e_attachment_view_get_ui_manager
+						(EAttachmentView *view);
+void		e_attachment_view_show_popup_menu
+						(EAttachmentView *view,
+						 GdkEventButton *event,
+						 GtkMenuPositionFunc func,
+						 gpointer user_data);
+void		e_attachment_view_update_actions
+						(EAttachmentView *view);
+
+G_END_DECLS
+
+#endif /* E_ATTACHMENT_VIEW_H */
diff --git a/e-util/e-attachment.c b/e-util/e-attachment.c
new file mode 100644
index 0000000..3357775
--- /dev/null
+++ b/e-util/e-attachment.c
@@ -0,0 +1,2882 @@
+/*
+ * e-attachment.c
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-attachment.h"
+
+#include <errno.h>
+#include <glib/gi18n.h>
+#include <glib/gstdio.h>
+
+#include <libedataserver/libedataserver.h>
+
+#include "e-attachment-store.h"
+#include "e-icon-factory.h"
+#include "e-mktemp.h"
+
+#define E_ATTACHMENT_GET_PRIVATE(obj) \
+	(G_TYPE_INSTANCE_GET_PRIVATE \
+	((obj), E_TYPE_ATTACHMENT, EAttachmentPrivate))
+
+/* Fallback Icon */
+#define DEFAULT_ICON_NAME	"mail-attachment"
+
+/* Emblems */
+#define EMBLEM_CANCELLED	"gtk-cancel"
+#define EMBLEM_LOADING		"emblem-downloads"
+#define EMBLEM_SAVING		"document-save"
+#define EMBLEM_ENCRYPT_WEAK	"security-low"
+#define EMBLEM_ENCRYPT_STRONG	"security-high"
+#define EMBLEM_ENCRYPT_UNKNOWN	"security-medium"
+#define EMBLEM_SIGN_BAD		"stock_signature-bad"
+#define EMBLEM_SIGN_GOOD	"stock_signature-ok"
+#define EMBLEM_SIGN_UNKNOWN	"stock_signature"
+
+/* Attributes needed for EAttachmentStore columns. */
+#define ATTACHMENT_QUERY "standard::*,preview::*,thumbnail::*"
+
+struct _EAttachmentPrivate {
+	GFile *file;
+	GIcon *icon;
+	GFileInfo *file_info;
+	GCancellable *cancellable;
+	CamelMimePart *mime_part;
+	guint emblem_timeout_id;
+	gchar *disposition;
+	gint percent;
+	gint64 last_percent_notify; /* to avoid excessive notifications */
+
+	guint can_show : 1;
+	guint loading  : 1;
+	guint saving   : 1;
+	guint shown    : 1;
+
+	camel_cipher_validity_encrypt_t encrypted;
+	camel_cipher_validity_sign_t signed_;
+
+	/* This is a reference to our row in an EAttachmentStore,
+	 * serving as a means of broadcasting "row-changed" signals.
+	 * If we are removed from the store, we lazily free the
+	 * reference when it is found to be to be invalid. */
+	GtkTreeRowReference *reference;
+};
+
+enum {
+	PROP_0,
+	PROP_CAN_SHOW,
+	PROP_DISPOSITION,
+	PROP_ENCRYPTED,
+	PROP_FILE,
+	PROP_FILE_INFO,
+	PROP_ICON,
+	PROP_LOADING,
+	PROP_MIME_PART,
+	PROP_PERCENT,
+	PROP_REFERENCE,
+	PROP_SAVING,
+	PROP_SHOWN,
+	PROP_SIGNED
+};
+
+G_DEFINE_TYPE (
+	EAttachment,
+	e_attachment,
+	G_TYPE_OBJECT)
+
+static gboolean
+create_system_thumbnail (EAttachment *attachment,
+                         GIcon **icon)
+{
+	GFile *file;
+	GFile *icon_file;
+	gchar *thumbnail = NULL;
+
+	g_return_val_if_fail (attachment != NULL, FALSE);
+	g_return_val_if_fail (icon != NULL, FALSE);
+
+	file = e_attachment_get_file (attachment);
+
+	if (file && g_file_has_uri_scheme (file, "file")) {
+		gchar *path = g_file_get_path (file);
+		if (path) {
+			thumbnail = e_icon_factory_create_thumbnail (path);
+			g_free (path);
+		}
+	}
+
+	if (thumbnail == NULL)
+		return FALSE;
+
+	icon_file = g_file_new_for_path (thumbnail);
+
+	if (*icon)
+		g_object_unref (*icon);
+
+	*icon = g_file_icon_new (icon_file);
+
+	g_object_unref (icon_file);
+
+	if (file) {
+		GFileInfo *file_info;
+		const gchar *attribute;
+
+		file_info = e_attachment_get_file_info (attachment);
+		attribute = G_FILE_ATTRIBUTE_THUMBNAIL_PATH;
+
+		if (file_info != NULL)
+			g_file_info_set_attribute_byte_string (
+				file_info, attribute, thumbnail);
+	}
+
+	g_free (thumbnail);
+
+	return TRUE;
+}
+
+static gchar *
+attachment_get_default_charset (void)
+{
+	GSettings *settings;
+	gchar *charset;
+
+	/* XXX This doesn't really belong here. */
+
+	settings = g_settings_new ("org.gnome.evolution.mail");
+	charset = g_settings_get_string (settings, "composer-charset");
+	if (charset == NULL || *charset == '\0') {
+		g_free (charset);
+		/* FIXME This was "/apps/evolution/mail/format/charset",
+		 *       not sure it relates to "charset" */
+		charset = g_settings_get_string (settings, "charset");
+		if (charset == NULL || *charset == '\0') {
+			g_free (charset);
+			charset = NULL;
+		}
+	}
+	g_object_unref (settings);
+
+	if (charset == NULL)
+		charset = g_strdup (camel_iconv_locale_charset ());
+
+	if (charset == NULL)
+		charset = g_strdup ("us-ascii");
+
+	return charset;
+}
+
+static void
+attachment_update_file_info_columns (EAttachment *attachment)
+{
+	GtkTreeRowReference *reference;
+	GtkTreeModel *model;
+	GtkTreePath *path;
+	GtkTreeIter iter;
+	GFileInfo *file_info;
+	const gchar *content_type;
+	const gchar *description;
+	const gchar *display_name;
+	gchar *content_desc;
+	gchar *display_size;
+	gchar *caption;
+	goffset size;
+
+	reference = e_attachment_get_reference (attachment);
+	if (!gtk_tree_row_reference_valid (reference))
+		return;
+
+	file_info = e_attachment_get_file_info (attachment);
+	if (file_info == NULL)
+		return;
+
+	model = gtk_tree_row_reference_get_model (reference);
+	path = gtk_tree_row_reference_get_path (reference);
+	gtk_tree_model_get_iter (model, &iter, path);
+	gtk_tree_path_free (path);
+
+	content_type = g_file_info_get_content_type (file_info);
+	display_name = g_file_info_get_display_name (file_info);
+	size = g_file_info_get_size (file_info);
+
+	content_desc = g_content_type_get_description (content_type);
+	display_size = g_format_size (size);
+
+	description = e_attachment_get_description (attachment);
+	if (description == NULL || *description == '\0')
+		description = display_name;
+
+	if (size > 0)
+		caption = g_strdup_printf (
+			"%s\n(%s)", description, display_size);
+	else
+		caption = g_strdup (description);
+
+	gtk_list_store_set (
+		GTK_LIST_STORE (model), &iter,
+		E_ATTACHMENT_STORE_COLUMN_CAPTION, caption,
+		E_ATTACHMENT_STORE_COLUMN_CONTENT_TYPE, content_desc,
+		E_ATTACHMENT_STORE_COLUMN_DESCRIPTION, description,
+		E_ATTACHMENT_STORE_COLUMN_SIZE, size,
+		-1);
+
+	g_free (content_desc);
+	g_free (display_size);
+	g_free (caption);
+}
+
+static void
+attachment_update_icon_column (EAttachment *attachment)
+{
+	GtkTreeRowReference *reference;
+	GtkTreeModel *model;
+	GtkTreePath *path;
+	GtkTreeIter iter;
+	GFileInfo *file_info;
+	GCancellable *cancellable;
+	GIcon *icon = NULL;
+	const gchar *emblem_name = NULL;
+	const gchar *thumbnail_path = NULL;
+
+	reference = e_attachment_get_reference (attachment);
+	if (!gtk_tree_row_reference_valid (reference))
+		return;
+
+	model = gtk_tree_row_reference_get_model (reference);
+	path = gtk_tree_row_reference_get_path (reference);
+	gtk_tree_model_get_iter (model, &iter, path);
+	gtk_tree_path_free (path);
+
+	cancellable = attachment->priv->cancellable;
+	file_info = e_attachment_get_file_info (attachment);
+
+	if (file_info != NULL) {
+		icon = g_file_info_get_icon (file_info);
+		thumbnail_path = g_file_info_get_attribute_byte_string (
+			file_info, G_FILE_ATTRIBUTE_THUMBNAIL_PATH);
+	}
+
+	/* Prefer the thumbnail if we have one. */
+	if (thumbnail_path != NULL && *thumbnail_path != '\0') {
+		GFile *file;
+
+		file = g_file_new_for_path (thumbnail_path);
+		icon = g_file_icon_new (file);
+		g_object_unref (file);
+
+	/* Try the system thumbnailer. */
+	} else if (create_system_thumbnail (attachment, &icon)) {
+		/* Nothing to do, just use the icon. */
+
+	/* Else use the standard icon for the content type. */
+	} else if (icon != NULL)
+		g_object_ref (icon);
+
+	/* Last ditch fallback.  (GFileInfo not yet loaded?) */
+	else
+		icon = g_themed_icon_new (DEFAULT_ICON_NAME);
+
+	/* Pick an emblem, limit one.  Choices listed by priority. */
+
+	if (g_cancellable_is_cancelled (cancellable))
+		emblem_name = EMBLEM_CANCELLED;
+
+	else if (e_attachment_get_loading (attachment))
+		emblem_name = EMBLEM_LOADING;
+
+	else if (e_attachment_get_saving (attachment))
+		emblem_name = EMBLEM_SAVING;
+
+	else if (e_attachment_get_encrypted (attachment))
+		switch (e_attachment_get_encrypted (attachment)) {
+			case CAMEL_CIPHER_VALIDITY_ENCRYPT_WEAK:
+				emblem_name = EMBLEM_ENCRYPT_WEAK;
+				break;
+
+			case CAMEL_CIPHER_VALIDITY_ENCRYPT_ENCRYPTED:
+				emblem_name = EMBLEM_ENCRYPT_UNKNOWN;
+				break;
+
+			case CAMEL_CIPHER_VALIDITY_ENCRYPT_STRONG:
+				emblem_name = EMBLEM_ENCRYPT_STRONG;
+				break;
+
+			default:
+				g_warn_if_reached ();
+				break;
+		}
+
+	else if (e_attachment_get_signed (attachment))
+		switch (e_attachment_get_signed (attachment)) {
+			case CAMEL_CIPHER_VALIDITY_SIGN_GOOD:
+				emblem_name = EMBLEM_SIGN_GOOD;
+				break;
+
+			case CAMEL_CIPHER_VALIDITY_SIGN_BAD:
+				emblem_name = EMBLEM_SIGN_BAD;
+				break;
+
+			case CAMEL_CIPHER_VALIDITY_SIGN_UNKNOWN:
+			case CAMEL_CIPHER_VALIDITY_SIGN_NEED_PUBLIC_KEY:
+				emblem_name = EMBLEM_SIGN_UNKNOWN;
+				break;
+
+			default:
+				g_warn_if_reached ();
+				break;
+		}
+
+	if (emblem_name != NULL) {
+		GIcon *emblemed_icon;
+		GEmblem *emblem;
+
+		emblemed_icon = g_themed_icon_new (emblem_name);
+		emblem = g_emblem_new (emblemed_icon);
+		g_object_unref (emblemed_icon);
+
+		emblemed_icon = g_emblemed_icon_new (icon, emblem);
+		g_object_unref (emblem);
+		g_object_unref (icon);
+
+		icon = emblemed_icon;
+	}
+
+	gtk_list_store_set (
+		GTK_LIST_STORE (model), &iter,
+		E_ATTACHMENT_STORE_COLUMN_ICON, icon,
+		-1);
+
+	/* Cache the icon to reuse for things like drag-n-drop. */
+	if (attachment->priv->icon != NULL)
+		g_object_unref (attachment->priv->icon);
+	attachment->priv->icon = icon;
+	g_object_notify (G_OBJECT (attachment), "icon");
+}
+
+static void
+attachment_update_progress_columns (EAttachment *attachment)
+{
+	GtkTreeRowReference *reference;
+	GtkTreeModel *model;
+	GtkTreePath *path;
+	GtkTreeIter iter;
+	gboolean loading;
+	gboolean saving;
+	gint percent;
+
+	reference = e_attachment_get_reference (attachment);
+	if (!gtk_tree_row_reference_valid (reference))
+		return;
+
+	model = gtk_tree_row_reference_get_model (reference);
+	path = gtk_tree_row_reference_get_path (reference);
+	gtk_tree_model_get_iter (model, &iter, path);
+	gtk_tree_path_free (path);
+
+	/* Don't show progress bars until we have progress to report. */
+	percent = e_attachment_get_percent (attachment);
+	loading = e_attachment_get_loading (attachment) && (percent > 0);
+	saving = e_attachment_get_saving (attachment) && (percent > 0);
+
+	gtk_list_store_set (
+		GTK_LIST_STORE (model), &iter,
+		E_ATTACHMENT_STORE_COLUMN_LOADING, loading,
+		E_ATTACHMENT_STORE_COLUMN_PERCENT, percent,
+		E_ATTACHMENT_STORE_COLUMN_SAVING, saving,
+		-1);
+}
+
+static void
+attachment_set_loading (EAttachment *attachment,
+                        gboolean loading)
+{
+	GtkTreeRowReference *reference;
+
+	reference = e_attachment_get_reference (attachment);
+
+	attachment->priv->percent = 0;
+	attachment->priv->loading = loading;
+	attachment->priv->last_percent_notify = 0;
+
+	g_object_freeze_notify (G_OBJECT (attachment));
+	g_object_notify (G_OBJECT (attachment), "percent");
+	g_object_notify (G_OBJECT (attachment), "loading");
+	g_object_thaw_notify (G_OBJECT (attachment));
+
+	if (gtk_tree_row_reference_valid (reference)) {
+		GtkTreeModel *model;
+		model = gtk_tree_row_reference_get_model (reference);
+		g_object_notify (G_OBJECT (model), "num-loading");
+	}
+}
+
+static void
+attachment_set_saving (EAttachment *attachment,
+                       gboolean saving)
+{
+	attachment->priv->percent = 0;
+	attachment->priv->saving = saving;
+	attachment->priv->last_percent_notify = 0;
+
+	g_object_freeze_notify (G_OBJECT (attachment));
+	g_object_notify (G_OBJECT (attachment), "percent");
+	g_object_notify (G_OBJECT (attachment), "saving");
+	g_object_thaw_notify (G_OBJECT (attachment));
+}
+
+static void
+attachment_progress_cb (goffset current_num_bytes,
+                        goffset total_num_bytes,
+                        EAttachment *attachment)
+{
+	gint new_percent;
+
+	/* Avoid dividing by zero. */
+	if (total_num_bytes == 0)
+		return;
+
+	/* do not notify too often, 5 times per second is sufficient */
+	if (g_get_monotonic_time () - attachment->priv->last_percent_notify < 200000)
+		return;
+
+	attachment->priv->last_percent_notify = g_get_monotonic_time ();
+
+	new_percent = (current_num_bytes * 100) / total_num_bytes;
+
+	if (new_percent != attachment->priv->percent) {
+		attachment->priv->percent = new_percent;
+		g_object_notify (G_OBJECT (attachment), "percent");
+	}
+}
+
+static gboolean
+attachment_cancelled_timeout_cb (EAttachment *attachment)
+{
+	attachment->priv->emblem_timeout_id = 0;
+	g_cancellable_reset (attachment->priv->cancellable);
+
+	attachment_update_icon_column (attachment);
+
+	return FALSE;
+}
+
+static void
+attachment_cancelled_cb (EAttachment *attachment)
+{
+	/* Reset the GCancellable after one second.  This causes a
+	 * cancel emblem to be briefly shown on the attachment icon
+	 * as visual feedback that an operation was cancelled. */
+
+	if (attachment->priv->emblem_timeout_id > 0)
+		g_source_remove (attachment->priv->emblem_timeout_id);
+
+	attachment->priv->emblem_timeout_id = g_timeout_add_seconds (
+		1, (GSourceFunc) attachment_cancelled_timeout_cb, attachment);
+
+	attachment_update_icon_column (attachment);
+}
+
+static void
+attachment_set_property (GObject *object,
+                         guint property_id,
+                         const GValue *value,
+                         GParamSpec *pspec)
+{
+	switch (property_id) {
+		case PROP_CAN_SHOW:
+			e_attachment_set_can_show (
+				E_ATTACHMENT (object),
+				g_value_get_boolean (value));
+			return;
+
+		case PROP_DISPOSITION:
+			e_attachment_set_disposition (
+				E_ATTACHMENT (object),
+				g_value_get_string (value));
+			return;
+
+		case PROP_ENCRYPTED:
+			e_attachment_set_encrypted (
+				E_ATTACHMENT (object),
+				g_value_get_int (value));
+			return;
+
+		case PROP_FILE:
+			e_attachment_set_file (
+				E_ATTACHMENT (object),
+				g_value_get_object (value));
+			return;
+
+		case PROP_SHOWN:
+			e_attachment_set_shown (
+				E_ATTACHMENT (object),
+				g_value_get_boolean (value));
+			return;
+
+		case PROP_MIME_PART:
+			e_attachment_set_mime_part (
+				E_ATTACHMENT (object),
+				g_value_get_boxed (value));
+			return;
+
+		case PROP_REFERENCE:
+			e_attachment_set_reference (
+				E_ATTACHMENT (object),
+				g_value_get_boxed (value));
+			return;
+
+		case PROP_SIGNED:
+			e_attachment_set_signed (
+				E_ATTACHMENT (object),
+				g_value_get_int (value));
+			return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+attachment_get_property (GObject *object,
+                         guint property_id,
+                         GValue *value,
+                         GParamSpec *pspec)
+{
+	switch (property_id) {
+		case PROP_CAN_SHOW:
+			g_value_set_boolean (
+				value, e_attachment_get_can_show (
+				E_ATTACHMENT (object)));
+			return;
+
+		case PROP_DISPOSITION:
+			g_value_set_string (
+				value, e_attachment_get_disposition (
+				E_ATTACHMENT (object)));
+			return;
+
+		case PROP_ENCRYPTED:
+			g_value_set_int (
+				value, e_attachment_get_encrypted (
+				E_ATTACHMENT (object)));
+			return;
+
+		case PROP_FILE:
+			g_value_set_object (
+				value, e_attachment_get_file (
+				E_ATTACHMENT (object)));
+			return;
+
+		case PROP_FILE_INFO:
+			g_value_set_object (
+				value, e_attachment_get_file_info (
+				E_ATTACHMENT (object)));
+			return;
+
+		case PROP_ICON:
+			g_value_set_object (
+				value, e_attachment_get_icon (
+				E_ATTACHMENT (object)));
+			return;
+
+		case PROP_SHOWN:
+			g_value_set_boolean (
+				value, e_attachment_get_shown (
+				E_ATTACHMENT (object)));
+			return;
+
+		case PROP_LOADING:
+			g_value_set_boolean (
+				value, e_attachment_get_loading (
+				E_ATTACHMENT (object)));
+			return;
+
+		case PROP_MIME_PART:
+			g_value_set_boxed (
+				value, e_attachment_get_mime_part (
+				E_ATTACHMENT (object)));
+			return;
+
+		case PROP_PERCENT:
+			g_value_set_int (
+				value, e_attachment_get_percent (
+				E_ATTACHMENT (object)));
+			return;
+
+		case PROP_REFERENCE:
+			g_value_set_boxed (
+				value, e_attachment_get_reference (
+				E_ATTACHMENT (object)));
+			return;
+
+		case PROP_SAVING:
+			g_value_set_boolean (
+				value, e_attachment_get_saving (
+				E_ATTACHMENT (object)));
+			return;
+
+		case PROP_SIGNED:
+			g_value_set_int (
+				value, e_attachment_get_signed (
+				E_ATTACHMENT (object)));
+			return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+attachment_dispose (GObject *object)
+{
+	EAttachmentPrivate *priv;
+
+	priv = E_ATTACHMENT_GET_PRIVATE (object);
+
+	if (priv->file != NULL) {
+		g_object_unref (priv->file);
+		priv->file = NULL;
+	}
+
+	if (priv->icon != NULL) {
+		g_object_unref (priv->icon);
+		priv->icon = NULL;
+	}
+
+	if (priv->file_info != NULL) {
+		g_object_unref (priv->file_info);
+		priv->file_info = NULL;
+	}
+
+	if (priv->cancellable != NULL) {
+		g_object_unref (priv->cancellable);
+		priv->cancellable = NULL;
+	}
+
+	if (priv->mime_part != NULL) {
+		g_object_unref (priv->mime_part);
+		priv->mime_part = NULL;
+	}
+
+	if (priv->emblem_timeout_id > 0) {
+		g_source_remove (priv->emblem_timeout_id);
+		priv->emblem_timeout_id = 0;
+	}
+
+	/* This accepts NULL arguments. */
+	gtk_tree_row_reference_free (priv->reference);
+	priv->reference = NULL;
+
+	/* Chain up to parent's dispose() method. */
+	G_OBJECT_CLASS (e_attachment_parent_class)->dispose (object);
+}
+
+static void
+attachment_finalize (GObject *object)
+{
+	EAttachmentPrivate *priv;
+
+	priv = E_ATTACHMENT_GET_PRIVATE (object);
+
+	g_free (priv->disposition);
+
+	/* Chain up to parent's finalize() method. */
+	G_OBJECT_CLASS (e_attachment_parent_class)->finalize (object);
+}
+
+static void
+e_attachment_class_init (EAttachmentClass *class)
+{
+	GObjectClass *object_class;
+
+	g_type_class_add_private (class, sizeof (EAttachmentPrivate));
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->set_property = attachment_set_property;
+	object_class->get_property = attachment_get_property;
+	object_class->dispose = attachment_dispose;
+	object_class->finalize = attachment_finalize;
+
+	g_object_class_install_property (
+		object_class,
+		PROP_CAN_SHOW,
+		g_param_spec_boolean (
+			"can-show",
+			"Can Show",
+			NULL,
+			FALSE,
+			G_PARAM_READWRITE |
+			G_PARAM_CONSTRUCT));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_DISPOSITION,
+		g_param_spec_string (
+			"disposition",
+			"Disposition",
+			NULL,
+			"attachment",
+			G_PARAM_READWRITE |
+			G_PARAM_CONSTRUCT));
+
+	/* FIXME Define a GEnumClass for this. */
+	g_object_class_install_property (
+		object_class,
+		PROP_ENCRYPTED,
+		g_param_spec_int (
+			"encrypted",
+			"Encrypted",
+			NULL,
+			CAMEL_CIPHER_VALIDITY_ENCRYPT_NONE,
+			CAMEL_CIPHER_VALIDITY_ENCRYPT_STRONG,
+			CAMEL_CIPHER_VALIDITY_ENCRYPT_NONE,
+			G_PARAM_READWRITE |
+			G_PARAM_CONSTRUCT));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_FILE,
+		g_param_spec_object (
+			"file",
+			"File",
+			NULL,
+			G_TYPE_FILE,
+			G_PARAM_READWRITE |
+			G_PARAM_CONSTRUCT));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_FILE_INFO,
+		g_param_spec_object (
+			"file-info",
+			"File Info",
+			NULL,
+			G_TYPE_FILE_INFO,
+			G_PARAM_READABLE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_ICON,
+		g_param_spec_object (
+			"icon",
+			"Icon",
+			NULL,
+			G_TYPE_ICON,
+			G_PARAM_READABLE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_LOADING,
+		g_param_spec_boolean (
+			"loading",
+			"Loading",
+			NULL,
+			FALSE,
+			G_PARAM_READABLE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_MIME_PART,
+		g_param_spec_object (
+			"mime-part",
+			"MIME Part",
+			NULL,
+			CAMEL_TYPE_MIME_PART,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_PERCENT,
+		g_param_spec_int (
+			"percent",
+			"Percent",
+			NULL,
+			0,
+			100,
+			0,
+			G_PARAM_READABLE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_REFERENCE,
+		g_param_spec_boxed (
+			"reference",
+			"Reference",
+			NULL,
+			GTK_TYPE_TREE_ROW_REFERENCE,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_SAVING,
+		g_param_spec_boolean (
+			"saving",
+			"Saving",
+			NULL,
+			FALSE,
+			G_PARAM_READABLE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_SHOWN,
+		g_param_spec_boolean (
+			"shown",
+			"Shown",
+			NULL,
+			FALSE,
+			G_PARAM_READWRITE |
+			G_PARAM_CONSTRUCT));
+
+	/* FIXME Define a GEnumClass for this. */
+	g_object_class_install_property (
+		object_class,
+		PROP_SIGNED,
+		g_param_spec_int (
+			"signed",
+			"Signed",
+			NULL,
+			CAMEL_CIPHER_VALIDITY_SIGN_NONE,
+			CAMEL_CIPHER_VALIDITY_SIGN_NEED_PUBLIC_KEY,
+			CAMEL_CIPHER_VALIDITY_SIGN_NONE,
+			G_PARAM_READWRITE |
+			G_PARAM_CONSTRUCT));
+}
+
+static void
+e_attachment_init (EAttachment *attachment)
+{
+	attachment->priv = E_ATTACHMENT_GET_PRIVATE (attachment);
+	attachment->priv->cancellable = g_cancellable_new ();
+	attachment->priv->encrypted = CAMEL_CIPHER_VALIDITY_ENCRYPT_NONE;
+	attachment->priv->signed_ = CAMEL_CIPHER_VALIDITY_SIGN_NONE;
+
+	g_signal_connect (
+		attachment, "notify::encrypted",
+		G_CALLBACK (attachment_update_icon_column), NULL);
+
+	g_signal_connect (
+		attachment, "notify::file-info",
+		G_CALLBACK (attachment_update_file_info_columns), NULL);
+
+	g_signal_connect (
+		attachment, "notify::file-info",
+		G_CALLBACK (attachment_update_icon_column), NULL);
+
+	g_signal_connect (
+		attachment, "notify::loading",
+		G_CALLBACK (attachment_update_icon_column), NULL);
+
+	g_signal_connect (
+		attachment, "notify::loading",
+		G_CALLBACK (attachment_update_progress_columns), NULL);
+
+	g_signal_connect (
+		attachment, "notify::percent",
+		G_CALLBACK (attachment_update_progress_columns), NULL);
+
+	g_signal_connect (
+		attachment, "notify::reference",
+		G_CALLBACK (attachment_update_file_info_columns), NULL);
+
+	g_signal_connect (
+		attachment, "notify::reference",
+		G_CALLBACK (attachment_update_icon_column), NULL);
+
+	g_signal_connect (
+		attachment, "notify::reference",
+		G_CALLBACK (attachment_update_progress_columns), NULL);
+
+	g_signal_connect (
+		attachment, "notify::saving",
+		G_CALLBACK (attachment_update_icon_column), NULL);
+
+	g_signal_connect (
+		attachment, "notify::saving",
+		G_CALLBACK (attachment_update_progress_columns), NULL);
+
+	g_signal_connect (
+		attachment, "notify::signed",
+		G_CALLBACK (attachment_update_icon_column), NULL);
+
+	g_signal_connect_swapped (
+		attachment->priv->cancellable, "cancelled",
+		G_CALLBACK (attachment_cancelled_cb), attachment);
+}
+
+EAttachment *
+e_attachment_new (void)
+{
+	return g_object_new (E_TYPE_ATTACHMENT, NULL);
+}
+
+EAttachment *
+e_attachment_new_for_path (const gchar *path)
+{
+	EAttachment *attachment;
+	GFile *file;
+
+	g_return_val_if_fail (path != NULL, NULL);
+
+	file = g_file_new_for_path (path);
+	attachment = g_object_new (E_TYPE_ATTACHMENT, "file", file, NULL);
+	g_object_unref (file);
+
+	return attachment;
+}
+
+EAttachment *
+e_attachment_new_for_uri (const gchar *uri)
+{
+	EAttachment *attachment;
+	GFile *file;
+
+	g_return_val_if_fail (uri != NULL, NULL);
+
+	file = g_file_new_for_uri (uri);
+	attachment = g_object_new (E_TYPE_ATTACHMENT, "file", file, NULL);
+	g_object_unref (file);
+
+	return attachment;
+}
+
+EAttachment *
+e_attachment_new_for_message (CamelMimeMessage *message)
+{
+	CamelDataWrapper *wrapper;
+	CamelMimePart *mime_part;
+	EAttachment *attachment;
+	GString *description;
+	const gchar *subject;
+
+	g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (message), NULL);
+
+	mime_part = camel_mime_part_new ();
+	camel_mime_part_set_disposition (mime_part, "inline");
+	subject = camel_mime_message_get_subject (message);
+
+	/* To Translators: This text is set as a description of an attached
+	 * message when, for example, attaching it to a composer. When the
+	 * message to be attached has also filled Subject, then this text is
+	 * of form "Attached message - Subject", otherwise it's left as is. */
+	description = g_string_new (_("Attached message"));
+	if (subject != NULL)
+		g_string_append_printf (description, " - %s", subject);
+	camel_mime_part_set_description (mime_part, description->str);
+	g_string_free (description, TRUE);
+
+	wrapper = CAMEL_DATA_WRAPPER (message);
+	camel_medium_set_content (CAMEL_MEDIUM (mime_part), wrapper);
+	camel_mime_part_set_content_type (mime_part, "message/rfc822");
+
+	attachment = e_attachment_new ();
+	e_attachment_set_mime_part (attachment, mime_part);
+	g_object_unref (mime_part);
+
+	return attachment;
+}
+
+void
+e_attachment_add_to_multipart (EAttachment *attachment,
+                               CamelMultipart *multipart,
+                               const gchar *default_charset)
+{
+	CamelContentType *content_type;
+	CamelDataWrapper *wrapper;
+	CamelMimePart *mime_part;
+
+	/* XXX EMsgComposer might be a better place for this function. */
+
+	g_return_if_fail (E_IS_ATTACHMENT (attachment));
+	g_return_if_fail (CAMEL_IS_MULTIPART (multipart));
+
+	/* Still loading?  Too bad. */
+	mime_part = e_attachment_get_mime_part (attachment);
+	if (mime_part == NULL)
+		return;
+
+	content_type = camel_mime_part_get_content_type (mime_part);
+	wrapper = camel_medium_get_content (CAMEL_MEDIUM (mime_part));
+
+	if (CAMEL_IS_MULTIPART (wrapper))
+		goto exit;
+
+	/* For text content, determine the best encoding and character set. */
+	if (camel_content_type_is (content_type, "text", "*")) {
+		CamelTransferEncoding encoding;
+		CamelStream *filtered_stream;
+		CamelMimeFilter *filter;
+		CamelStream *stream;
+		const gchar *charset;
+
+		charset = camel_content_type_param (content_type, "charset");
+
+		/* Determine the best encoding by writing the MIME
+		 * part to a NULL stream with a "bestenc" filter. */
+		stream = camel_stream_null_new ();
+		filtered_stream = camel_stream_filter_new (stream);
+		filter = camel_mime_filter_bestenc_new (
+			CAMEL_BESTENC_GET_ENCODING);
+		camel_stream_filter_add (
+			CAMEL_STREAM_FILTER (filtered_stream),
+			CAMEL_MIME_FILTER (filter));
+		camel_data_wrapper_decode_to_stream_sync (
+			wrapper, filtered_stream, NULL, NULL);
+		g_object_unref (filtered_stream);
+		g_object_unref (stream);
+
+		/* Retrieve the best encoding from the filter. */
+		encoding = camel_mime_filter_bestenc_get_best_encoding (
+			CAMEL_MIME_FILTER_BESTENC (filter),
+			CAMEL_BESTENC_8BIT);
+		camel_mime_part_set_encoding (mime_part, encoding);
+		g_object_unref (filter);
+
+		if (encoding == CAMEL_TRANSFER_ENCODING_7BIT) {
+			/* The text fits within us-ascii, so this is safe.
+			 * FIXME Check that this isn't iso-2022-jp? */
+			default_charset = "us-ascii";
+
+		} else if (charset == NULL && default_charset == NULL) {
+			default_charset = attachment_get_default_charset ();
+			/* FIXME Check that this fits within the
+			 *       default_charset and if not, find one
+			 *       that does and/or allow the user to
+			 *       specify. */
+		}
+
+		if (charset == NULL) {
+			gchar *type;
+
+			camel_content_type_set_param (
+				content_type, "charset", default_charset);
+			type = camel_content_type_format (content_type);
+			camel_mime_part_set_content_type (mime_part, type);
+			g_free (type);
+		}
+
+	/* Otherwise, unless it's a message/rfc822, Base64 encode it. */
+	} else if (!CAMEL_IS_MIME_MESSAGE (wrapper))
+		camel_mime_part_set_encoding (
+			mime_part, CAMEL_TRANSFER_ENCODING_BASE64);
+
+exit:
+	camel_multipart_add_part (multipart, mime_part);
+}
+
+void
+e_attachment_cancel (EAttachment *attachment)
+{
+	g_return_if_fail (E_IS_ATTACHMENT (attachment));
+
+	g_cancellable_cancel (attachment->priv->cancellable);
+}
+
+gboolean
+e_attachment_get_can_show (EAttachment *attachment)
+{
+	g_return_val_if_fail (E_IS_ATTACHMENT (attachment), FALSE);
+
+	return attachment->priv->can_show;
+}
+
+void
+e_attachment_set_can_show (EAttachment *attachment,
+                           gboolean can_show)
+{
+	g_return_if_fail (E_IS_ATTACHMENT (attachment));
+
+	attachment->priv->can_show = can_show;
+
+	g_object_notify (G_OBJECT (attachment), "can-show");
+}
+
+const gchar *
+e_attachment_get_disposition (EAttachment *attachment)
+{
+	g_return_val_if_fail (E_IS_ATTACHMENT (attachment), NULL);
+
+	return attachment->priv->disposition;
+}
+
+void
+e_attachment_set_disposition (EAttachment *attachment,
+                              const gchar *disposition)
+{
+	g_return_if_fail (E_IS_ATTACHMENT (attachment));
+
+	g_free (attachment->priv->disposition);
+	attachment->priv->disposition = g_strdup (disposition);
+
+	g_object_notify (G_OBJECT (attachment), "disposition");
+}
+
+GFile *
+e_attachment_get_file (EAttachment *attachment)
+{
+	g_return_val_if_fail (E_IS_ATTACHMENT (attachment), NULL);
+
+	return attachment->priv->file;
+}
+
+void
+e_attachment_set_file (EAttachment *attachment,
+                       GFile *file)
+{
+	g_return_if_fail (E_IS_ATTACHMENT (attachment));
+
+	if (file != NULL) {
+		g_return_if_fail (G_IS_FILE (file));
+		g_object_ref (file);
+	}
+
+	if (attachment->priv->file != NULL)
+		g_object_unref (attachment->priv->file);
+
+	attachment->priv->file = file;
+
+	g_object_notify (G_OBJECT (attachment), "file");
+}
+
+GFileInfo *
+e_attachment_get_file_info (EAttachment *attachment)
+{
+	g_return_val_if_fail (E_IS_ATTACHMENT (attachment), NULL);
+
+	return attachment->priv->file_info;
+}
+
+void
+e_attachment_set_file_info (EAttachment *attachment,
+                            GFileInfo *file_info)
+{
+	GtkTreeRowReference *reference;
+	GIcon *icon;
+
+	reference = e_attachment_get_reference (attachment);
+
+	if (file_info != NULL)
+		g_object_ref (file_info);
+
+	if (attachment->priv->file_info != NULL)
+		g_object_unref (attachment->priv->file_info);
+
+	attachment->priv->file_info = file_info;
+
+	/* If the GFileInfo contains a GThemedIcon, append a
+	 * fallback icon name to ensure we display something. */
+	icon = g_file_info_get_icon (file_info);
+	if (G_IS_THEMED_ICON (icon))
+		g_themed_icon_append_name (
+			G_THEMED_ICON (icon), DEFAULT_ICON_NAME);
+
+	g_object_notify (G_OBJECT (attachment), "file-info");
+
+	/* Tell the EAttachmentStore its total size changed. */
+	if (gtk_tree_row_reference_valid (reference)) {
+		GtkTreeModel *model;
+		model = gtk_tree_row_reference_get_model (reference);
+		g_object_notify (G_OBJECT (model), "total-size");
+	}
+}
+
+/**
+ * e_attachment_get_mime_type:
+ *
+ * Returns mime_type part of the file_info as a newly allocated string,
+ * which should be freed with g_free().
+ * Returns NULL, if mime_type not found or set on the attachment.
+ **/
+gchar *
+e_attachment_get_mime_type (EAttachment *attachment)
+{
+	GFileInfo *file_info;
+	const gchar *content_type;
+	gchar *mime_type;
+
+	g_return_val_if_fail (E_IS_ATTACHMENT (attachment), NULL);
+
+	file_info = e_attachment_get_file_info (attachment);
+	if (file_info == NULL)
+		return NULL;
+
+	content_type = g_file_info_get_content_type (file_info);
+	if (content_type == NULL)
+		return NULL;
+
+	mime_type = g_content_type_get_mime_type (content_type);
+	if (!mime_type)
+		return NULL;
+
+	camel_strdown (mime_type);
+
+	return mime_type;
+}
+
+GIcon *
+e_attachment_get_icon (EAttachment *attachment)
+{
+	g_return_val_if_fail (E_IS_ATTACHMENT (attachment), NULL);
+
+	return attachment->priv->icon;
+}
+
+gboolean
+e_attachment_get_loading (EAttachment *attachment)
+{
+	g_return_val_if_fail (E_IS_ATTACHMENT (attachment), FALSE);
+
+	return attachment->priv->loading;
+}
+
+CamelMimePart *
+e_attachment_get_mime_part (EAttachment *attachment)
+{
+	g_return_val_if_fail (E_IS_ATTACHMENT (attachment), NULL);
+
+	return attachment->priv->mime_part;
+}
+
+void
+e_attachment_set_mime_part (EAttachment *attachment,
+                            CamelMimePart *mime_part)
+{
+	g_return_if_fail (E_IS_ATTACHMENT (attachment));
+
+	if (mime_part != NULL) {
+		g_return_if_fail (CAMEL_IS_MIME_PART (mime_part));
+		g_object_ref (mime_part);
+	}
+
+	if (attachment->priv->mime_part != NULL)
+		g_object_unref (attachment->priv->mime_part);
+
+	attachment->priv->mime_part = mime_part;
+
+	g_object_notify (G_OBJECT (attachment), "mime-part");
+}
+
+gint
+e_attachment_get_percent (EAttachment *attachment)
+{
+	g_return_val_if_fail (E_IS_ATTACHMENT (attachment), 0);
+
+	return attachment->priv->percent;
+}
+
+GtkTreeRowReference *
+e_attachment_get_reference (EAttachment *attachment)
+{
+	g_return_val_if_fail (E_IS_ATTACHMENT (attachment), NULL);
+
+	return attachment->priv->reference;
+}
+
+void
+e_attachment_set_reference (EAttachment *attachment,
+                            GtkTreeRowReference *reference)
+{
+	g_return_if_fail (E_IS_ATTACHMENT (attachment));
+
+	if (reference != NULL)
+		reference = gtk_tree_row_reference_copy (reference);
+
+	gtk_tree_row_reference_free (attachment->priv->reference);
+	attachment->priv->reference = reference;
+
+	g_object_notify (G_OBJECT (attachment), "reference");
+}
+
+gboolean
+e_attachment_get_saving (EAttachment *attachment)
+{
+	g_return_val_if_fail (E_IS_ATTACHMENT (attachment), FALSE);
+
+	return attachment->priv->saving;
+}
+
+gboolean
+e_attachment_get_shown (EAttachment *attachment)
+{
+	g_return_val_if_fail (E_IS_ATTACHMENT (attachment), FALSE);
+
+	return attachment->priv->shown;
+}
+
+void
+e_attachment_set_shown (EAttachment *attachment,
+                        gboolean shown)
+{
+	g_return_if_fail (E_IS_ATTACHMENT (attachment));
+
+	attachment->priv->shown = shown;
+
+	g_object_notify (G_OBJECT (attachment), "shown");
+}
+
+camel_cipher_validity_encrypt_t
+e_attachment_get_encrypted (EAttachment *attachment)
+{
+	g_return_val_if_fail (
+		E_IS_ATTACHMENT (attachment),
+		CAMEL_CIPHER_VALIDITY_ENCRYPT_NONE);
+
+	return attachment->priv->encrypted;
+}
+
+void
+e_attachment_set_encrypted (EAttachment *attachment,
+                            camel_cipher_validity_encrypt_t encrypted)
+{
+	g_return_if_fail (E_IS_ATTACHMENT (attachment));
+
+	attachment->priv->encrypted = encrypted;
+
+	g_object_notify (G_OBJECT (attachment), "encrypted");
+}
+
+camel_cipher_validity_sign_t
+e_attachment_get_signed (EAttachment *attachment)
+{
+	g_return_val_if_fail (
+		E_IS_ATTACHMENT (attachment),
+		CAMEL_CIPHER_VALIDITY_SIGN_NONE);
+
+	return attachment->priv->signed_;
+}
+
+void
+e_attachment_set_signed (EAttachment *attachment,
+                         camel_cipher_validity_sign_t signed_)
+{
+	g_return_if_fail (E_IS_ATTACHMENT (attachment));
+
+	attachment->priv->signed_ = signed_;
+
+	g_object_notify (G_OBJECT (attachment), "signed");
+}
+
+const gchar *
+e_attachment_get_description (EAttachment *attachment)
+{
+	GFileInfo *file_info;
+	const gchar *attribute;
+
+	g_return_val_if_fail (E_IS_ATTACHMENT (attachment), NULL);
+
+	attribute = G_FILE_ATTRIBUTE_STANDARD_DESCRIPTION;
+	file_info = e_attachment_get_file_info (attachment);
+
+	if (file_info == NULL)
+		return NULL;
+
+	return g_file_info_get_attribute_string (file_info, attribute);
+}
+
+const gchar *
+e_attachment_get_thumbnail_path (EAttachment *attachment)
+{
+	GFileInfo *file_info;
+	const gchar *attribute;
+
+	g_return_val_if_fail (E_IS_ATTACHMENT (attachment), NULL);
+
+	attribute = G_FILE_ATTRIBUTE_THUMBNAIL_PATH;
+	file_info = e_attachment_get_file_info (attachment);
+
+	if (file_info == NULL)
+		return NULL;
+
+	return g_file_info_get_attribute_byte_string (file_info, attribute);
+}
+
+gboolean
+e_attachment_is_rfc822 (EAttachment *attachment)
+{
+	gchar *mime_type;
+	gboolean is_rfc822;
+
+	g_return_val_if_fail (E_IS_ATTACHMENT (attachment), FALSE);
+
+	mime_type = e_attachment_get_mime_type (attachment);
+	is_rfc822 = mime_type && g_ascii_strcasecmp (mime_type, "message/rfc822") == 0;
+	g_free (mime_type);
+
+	return is_rfc822;
+}
+
+GList *
+e_attachment_list_apps (EAttachment *attachment)
+{
+	GList *app_info_list;
+	GList *guessed_infos;
+	GFileInfo *file_info;
+	const gchar *content_type;
+	const gchar *display_name;
+	gboolean type_is_unknown;
+	gchar *allocated;
+
+	g_return_val_if_fail (E_IS_ATTACHMENT (attachment), NULL);
+
+	file_info = e_attachment_get_file_info (attachment);
+	if (file_info == NULL)
+		return NULL;
+
+	content_type = g_file_info_get_content_type (file_info);
+	display_name = g_file_info_get_display_name (file_info);
+	g_return_val_if_fail (content_type != NULL, NULL);
+
+	app_info_list = g_app_info_get_all_for_type (content_type);
+	type_is_unknown = g_content_type_is_unknown (content_type);
+
+	if (app_info_list != NULL && !type_is_unknown)
+		goto exit;
+
+	if (display_name == NULL)
+		goto exit;
+
+	allocated = g_content_type_guess (display_name, NULL, 0, NULL);
+	guessed_infos = g_app_info_get_all_for_type (allocated);
+	app_info_list = g_list_concat (guessed_infos, app_info_list);
+	g_free (allocated);
+
+exit:
+	return app_info_list;
+}
+
+/************************* e_attachment_load_async() *************************/
+
+typedef struct _LoadContext LoadContext;
+
+struct _LoadContext {
+	EAttachment *attachment;
+	CamelMimePart *mime_part;
+	GSimpleAsyncResult *simple;
+
+	GInputStream *input_stream;
+	GOutputStream *output_stream;
+	GFileInfo *file_info;
+	goffset total_num_bytes;
+	gssize bytes_read;
+	gchar buffer[4096];
+};
+
+/* Forward Declaration */
+static void
+attachment_load_stream_read_cb (GInputStream *input_stream,
+                                GAsyncResult *result,
+                                LoadContext *load_context);
+
+static LoadContext *
+attachment_load_context_new (EAttachment *attachment,
+                             GAsyncReadyCallback callback,
+                             gpointer user_data)
+{
+	LoadContext *load_context;
+	GSimpleAsyncResult *simple;
+
+	simple = g_simple_async_result_new (
+		G_OBJECT (attachment), callback,
+		user_data, e_attachment_load_async);
+
+	load_context = g_slice_new0 (LoadContext);
+	load_context->attachment = g_object_ref (attachment);
+	load_context->simple = simple;
+
+	attachment_set_loading (load_context->attachment, TRUE);
+
+	return load_context;
+}
+
+static void
+attachment_load_context_free (LoadContext *load_context)
+{
+	g_object_unref (load_context->attachment);
+
+	if (load_context->mime_part != NULL)
+		g_object_unref (load_context->mime_part);
+
+	if (load_context->simple)
+		g_object_unref (load_context->simple);
+
+	if (load_context->input_stream != NULL)
+		g_object_unref (load_context->input_stream);
+
+	if (load_context->output_stream != NULL)
+		g_object_unref (load_context->output_stream);
+
+	if (load_context->file_info != NULL)
+		g_object_unref (load_context->file_info);
+
+	g_slice_free (LoadContext, load_context);
+}
+
+static gboolean
+attachment_load_check_for_error (LoadContext *load_context,
+                                 GError *error)
+{
+	GSimpleAsyncResult *simple;
+
+	if (error == NULL)
+		return FALSE;
+
+	simple = load_context->simple;
+	g_simple_async_result_take_error (simple, error);
+	g_simple_async_result_complete (simple);
+
+	attachment_load_context_free (load_context);
+
+	return TRUE;
+}
+
+static void
+attachment_load_finish (LoadContext *load_context)
+{
+	GFileInfo *file_info;
+	EAttachment *attachment;
+	GMemoryOutputStream *output_stream;
+	GSimpleAsyncResult *simple;
+	CamelDataWrapper *wrapper;
+	CamelMimePart *mime_part;
+	CamelStream *stream;
+	const gchar *attribute;
+	const gchar *content_type;
+	const gchar *display_name;
+	const gchar *description;
+	const gchar *disposition;
+	gchar *mime_type;
+	gpointer data;
+	gsize size;
+
+	simple = load_context->simple;
+
+	file_info = load_context->file_info;
+	attachment = load_context->attachment;
+	output_stream = G_MEMORY_OUTPUT_STREAM (load_context->output_stream);
+
+	if (e_attachment_is_rfc822 (attachment))
+		wrapper = (CamelDataWrapper *) camel_mime_message_new ();
+	else
+		wrapper = camel_data_wrapper_new ();
+
+	content_type = g_file_info_get_content_type (file_info);
+	mime_type = g_content_type_get_mime_type (content_type);
+
+	data = g_memory_output_stream_get_data (output_stream);
+	size = g_memory_output_stream_get_data_size (output_stream);
+
+	stream = camel_stream_mem_new_with_buffer (data, size);
+	camel_data_wrapper_construct_from_stream_sync (
+		wrapper, stream, NULL, NULL);
+	camel_data_wrapper_set_mime_type (wrapper, mime_type);
+	camel_stream_close (stream, NULL, NULL);
+	g_object_unref (stream);
+
+	mime_part = camel_mime_part_new ();
+	camel_medium_set_content (CAMEL_MEDIUM (mime_part), wrapper);
+
+	g_object_unref (wrapper);
+	g_free (mime_type);
+
+	display_name = g_file_info_get_display_name (file_info);
+	if (display_name != NULL)
+		camel_mime_part_set_filename (mime_part, display_name);
+
+	attribute = G_FILE_ATTRIBUTE_STANDARD_DESCRIPTION;
+	description = g_file_info_get_attribute_string (file_info, attribute);
+	if (description != NULL)
+		camel_mime_part_set_description (mime_part, description);
+
+	disposition = e_attachment_get_disposition (attachment);
+	if (disposition != NULL)
+		camel_mime_part_set_disposition (mime_part, disposition);
+
+	/* Correctly report the size of zero length special files. */
+	if (g_file_info_get_size (file_info) == 0)
+		g_file_info_set_size (file_info, size);
+
+	load_context->mime_part = mime_part;
+
+	g_simple_async_result_set_op_res_gpointer (
+		simple, load_context, (GDestroyNotify) attachment_load_context_free);
+
+	g_simple_async_result_complete (simple);
+
+	/* make sure it's freed on operation end */
+	load_context->simple = NULL;
+	g_object_unref (simple);
+}
+
+static void
+attachment_load_write_cb (GOutputStream *output_stream,
+                          GAsyncResult *result,
+                          LoadContext *load_context)
+{
+	EAttachment *attachment;
+	GCancellable *cancellable;
+	GInputStream *input_stream;
+	gssize bytes_written;
+	GError *error = NULL;
+
+	bytes_written = g_output_stream_write_finish (
+		output_stream, result, &error);
+
+	if (attachment_load_check_for_error (load_context, error))
+		return;
+
+	attachment = load_context->attachment;
+	cancellable = attachment->priv->cancellable;
+	input_stream = load_context->input_stream;
+
+	attachment_progress_cb (
+		g_seekable_tell (G_SEEKABLE (output_stream)),
+		load_context->total_num_bytes, attachment);
+
+	if (bytes_written < load_context->bytes_read) {
+		g_memmove (
+			load_context->buffer,
+			load_context->buffer + bytes_written,
+			load_context->bytes_read - bytes_written);
+		load_context->bytes_read -= bytes_written;
+
+		g_output_stream_write_async (
+			output_stream,
+			load_context->buffer,
+			load_context->bytes_read,
+			G_PRIORITY_DEFAULT, cancellable,
+			(GAsyncReadyCallback) attachment_load_write_cb,
+			load_context);
+	} else
+		g_input_stream_read_async (
+			input_stream,
+			load_context->buffer,
+			sizeof (load_context->buffer),
+			G_PRIORITY_DEFAULT, cancellable,
+			(GAsyncReadyCallback) attachment_load_stream_read_cb,
+			load_context);
+}
+
+static void
+attachment_load_stream_read_cb (GInputStream *input_stream,
+                                GAsyncResult *result,
+                                LoadContext *load_context)
+{
+	EAttachment *attachment;
+	GCancellable *cancellable;
+	GOutputStream *output_stream;
+	gssize bytes_read;
+	GError *error = NULL;
+
+	bytes_read = g_input_stream_read_finish (
+		input_stream, result, &error);
+
+	if (attachment_load_check_for_error (load_context, error))
+		return;
+
+	if (bytes_read == 0) {
+		attachment_load_finish (load_context);
+		return;
+	}
+
+	attachment = load_context->attachment;
+	cancellable = attachment->priv->cancellable;
+	output_stream = load_context->output_stream;
+	load_context->bytes_read = bytes_read;
+
+	g_output_stream_write_async (
+		output_stream,
+		load_context->buffer,
+		load_context->bytes_read,
+		G_PRIORITY_DEFAULT, cancellable,
+		(GAsyncReadyCallback) attachment_load_write_cb,
+		load_context);
+}
+
+static void
+attachment_load_file_read_cb (GFile *file,
+                              GAsyncResult *result,
+                              LoadContext *load_context)
+{
+	EAttachment *attachment;
+	GCancellable *cancellable;
+	GFileInputStream *input_stream;
+	GOutputStream *output_stream;
+	GError *error = NULL;
+
+	/* Input stream might be NULL, so don't use cast macro. */
+	input_stream = g_file_read_finish (file, result, &error);
+	load_context->input_stream = (GInputStream *) input_stream;
+
+	if (attachment_load_check_for_error (load_context, error))
+		return;
+
+	/* Load the contents into a GMemoryOutputStream. */
+	output_stream = g_memory_output_stream_new (
+		NULL, 0, g_realloc, g_free);
+
+	attachment = load_context->attachment;
+	cancellable = attachment->priv->cancellable;
+	load_context->output_stream = output_stream;
+
+	g_input_stream_read_async (
+		load_context->input_stream,
+		load_context->buffer,
+		sizeof (load_context->buffer),
+		G_PRIORITY_DEFAULT, cancellable,
+		(GAsyncReadyCallback) attachment_load_stream_read_cb,
+		load_context);
+}
+
+static void
+attachment_load_query_info_cb (GFile *file,
+                               GAsyncResult *result,
+                               LoadContext *load_context)
+{
+	EAttachment *attachment;
+	GCancellable *cancellable;
+	GFileInfo *file_info;
+	GError *error = NULL;
+
+	attachment = load_context->attachment;
+	cancellable = attachment->priv->cancellable;
+
+	file_info = g_file_query_info_finish (file, result, &error);
+	if (attachment_load_check_for_error (load_context, error))
+		return;
+
+	e_attachment_set_file_info (attachment, file_info);
+	load_context->file_info = file_info;
+
+	load_context->total_num_bytes = g_file_info_get_size (file_info);
+
+	g_file_read_async (
+		file, G_PRIORITY_DEFAULT,
+		cancellable, (GAsyncReadyCallback)
+		attachment_load_file_read_cb, load_context);
+}
+
+#define ATTACHMENT_LOAD_CONTEXT "attachment-load-context-data"
+
+static void
+attachment_load_from_mime_part_thread (GSimpleAsyncResult *simple,
+                                       GObject *object,
+                                       GCancellable *cancellable)
+{
+	LoadContext *load_context;
+	GFileInfo *file_info;
+	EAttachment *attachment;
+	CamelContentType *content_type;
+	CamelMimePart *mime_part;
+	const gchar *attribute;
+	const gchar *string;
+	gchar *allocated;
+	CamelStream *null;
+	CamelDataWrapper *dw;
+
+	load_context = g_object_get_data (G_OBJECT (simple), ATTACHMENT_LOAD_CONTEXT);
+	g_return_if_fail (load_context != NULL);
+	g_object_set_data (G_OBJECT (simple), ATTACHMENT_LOAD_CONTEXT, NULL);
+
+	attachment = load_context->attachment;
+	mime_part = e_attachment_get_mime_part (attachment);
+
+	file_info = g_file_info_new ();
+	load_context->file_info = file_info;
+
+	content_type = camel_mime_part_get_content_type (mime_part);
+	allocated = camel_content_type_simple (content_type);
+	if (allocated != NULL) {
+		GIcon *icon;
+		gchar *cp;
+
+		/* GIO expects lowercase MIME types. */
+		for (cp = allocated; *cp != '\0'; cp++)
+			*cp = g_ascii_tolower (*cp);
+
+		/* Swap the MIME type for a content type. */
+		cp = g_content_type_from_mime_type (allocated);
+		g_free (allocated);
+		allocated = cp;
+
+		/* Use the MIME part's filename if we have to. */
+		if (g_content_type_is_unknown (allocated)) {
+			string = camel_mime_part_get_filename (mime_part);
+			if (string != NULL) {
+				g_free (allocated);
+				allocated = g_content_type_guess (
+					string, NULL, 0, NULL);
+			}
+		}
+
+		g_file_info_set_content_type (file_info, allocated);
+
+		icon = g_content_type_get_icon (allocated);
+		if (icon != NULL) {
+			g_file_info_set_icon (file_info, icon);
+			g_object_unref (icon);
+		}
+	}
+	g_free (allocated);
+
+	/* Strip any path components from the filename. */
+	string = camel_mime_part_get_filename (mime_part);
+	if (string == NULL)
+		/* Translators: Default attachment filename. */
+		string = _("attachment.dat");
+	allocated = g_path_get_basename (string);
+	g_file_info_set_display_name (file_info, allocated);
+	g_free (allocated);
+
+	attribute = G_FILE_ATTRIBUTE_STANDARD_DESCRIPTION;
+	string = camel_mime_part_get_description (mime_part);
+	if (string != NULL)
+		g_file_info_set_attribute_string (
+			file_info, attribute, string);
+
+	dw = camel_medium_get_content (CAMEL_MEDIUM (mime_part));
+	null = camel_stream_null_new ();
+	/* this actually downloads the part and makes it available later */
+	camel_data_wrapper_decode_to_stream_sync (dw, null, attachment->priv->cancellable, NULL);
+	g_file_info_set_size (file_info, CAMEL_STREAM_NULL (null)->written);
+	g_object_unref (null);
+
+	load_context->mime_part = g_object_ref (mime_part);
+
+	/* make sure it's freed on operation end */
+	g_object_unref (load_context->simple);
+	load_context->simple = NULL;
+
+	g_simple_async_result_set_op_res_gpointer (
+		simple, load_context,
+		(GDestroyNotify) attachment_load_context_free);
+}
+
+void
+e_attachment_load_async (EAttachment *attachment,
+                         GAsyncReadyCallback callback,
+                         gpointer user_data)
+{
+	LoadContext *load_context;
+	GCancellable *cancellable;
+	CamelMimePart *mime_part;
+	GFile *file;
+
+	g_return_if_fail (E_IS_ATTACHMENT (attachment));
+
+	if (e_attachment_get_loading (attachment)) {
+		g_simple_async_report_error_in_idle (
+			G_OBJECT (attachment), callback, user_data,
+			G_IO_ERROR, G_IO_ERROR_BUSY,
+			_("A load operation is already in progress"));
+		return;
+	}
+
+	if (e_attachment_get_saving (attachment)) {
+		g_simple_async_report_error_in_idle (
+			G_OBJECT (attachment), callback, user_data,
+			G_IO_ERROR, G_IO_ERROR_BUSY,
+			_("A save operation is already in progress"));
+		return;
+	}
+
+	file = e_attachment_get_file (attachment);
+	mime_part = e_attachment_get_mime_part (attachment);
+	g_return_if_fail (file != NULL || mime_part != NULL);
+
+	load_context = attachment_load_context_new (
+		attachment, callback, user_data);
+
+	cancellable = attachment->priv->cancellable;
+	g_cancellable_reset (cancellable);
+
+	if (file != NULL) {
+		g_file_query_info_async (
+			file, ATTACHMENT_QUERY,
+			G_FILE_QUERY_INFO_NONE,G_PRIORITY_DEFAULT,
+			cancellable, (GAsyncReadyCallback)
+			attachment_load_query_info_cb, load_context);
+
+	} else if (mime_part != NULL) {
+		g_object_set_data (G_OBJECT (load_context->simple), ATTACHMENT_LOAD_CONTEXT, load_context);
+
+		g_simple_async_result_run_in_thread (
+			load_context->simple,
+			attachment_load_from_mime_part_thread,
+			G_PRIORITY_DEFAULT,
+			cancellable);
+	}
+}
+
+gboolean
+e_attachment_load_finish (EAttachment *attachment,
+                          GAsyncResult *result,
+                          GError **error)
+{
+	GSimpleAsyncResult *simple;
+	const LoadContext *load_context;
+
+	g_return_val_if_fail (E_IS_ATTACHMENT (attachment), FALSE);
+	g_return_val_if_fail (G_IS_SIMPLE_ASYNC_RESULT (result), FALSE);
+
+	simple = G_SIMPLE_ASYNC_RESULT (result);
+	load_context = g_simple_async_result_get_op_res_gpointer (simple);
+	if (load_context && load_context->mime_part) {
+		const gchar *string;
+
+		string = camel_mime_part_get_disposition (load_context->mime_part);
+		e_attachment_set_disposition (attachment, string);
+
+		e_attachment_set_file_info (attachment, load_context->file_info);
+		e_attachment_set_mime_part (attachment, load_context->mime_part);
+	}
+
+	g_simple_async_result_propagate_error (simple, error);
+
+	attachment_set_loading (attachment, FALSE);
+
+	return (load_context != NULL);
+}
+
+void
+e_attachment_load_handle_error (EAttachment *attachment,
+                                GAsyncResult *result,
+                                GtkWindow *parent)
+{
+	GtkWidget *dialog;
+	GFileInfo *file_info;
+	GtkTreeRowReference *reference;
+	const gchar *display_name;
+	const gchar *primary_text;
+	GError *error = NULL;
+
+	g_return_if_fail (E_IS_ATTACHMENT (attachment));
+	g_return_if_fail (G_IS_ASYNC_RESULT (result));
+	g_return_if_fail (!parent || GTK_IS_WINDOW (parent));
+
+	if (e_attachment_load_finish (attachment, result, &error))
+		return;
+
+	/* XXX Calling EAttachmentStore functions from here violates
+	 *     the abstraction, but for now it's not hurting anything. */
+	reference = e_attachment_get_reference (attachment);
+	if (gtk_tree_row_reference_valid (reference)) {
+		GtkTreeModel *model;
+
+		model = gtk_tree_row_reference_get_model (reference);
+
+		e_attachment_store_remove_attachment (
+			E_ATTACHMENT_STORE (model), attachment);
+	}
+
+	/* Ignore cancellations. */
+	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+		g_error_free (error);
+		return;
+	}
+
+	file_info = e_attachment_get_file_info (attachment);
+
+	if (file_info != NULL)
+		display_name = g_file_info_get_display_name (file_info);
+	else
+		display_name = NULL;
+
+	if (display_name != NULL)
+		primary_text = g_strdup_printf (
+			_("Could not load '%s'"), display_name);
+	else
+		primary_text = g_strdup_printf (
+			_("Could not load the attachment"));
+
+	dialog = gtk_message_dialog_new_with_markup (
+		parent, GTK_DIALOG_DESTROY_WITH_PARENT,
+		GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
+		"<big><b>%s</b></big>", primary_text);
+
+	gtk_message_dialog_format_secondary_text (
+		GTK_MESSAGE_DIALOG (dialog), "%s", error->message);
+
+	gtk_dialog_run (GTK_DIALOG (dialog));
+
+	gtk_widget_destroy (dialog);
+	g_error_free (error);
+}
+
+gboolean
+e_attachment_load (EAttachment *attachment,
+		   GError **error)
+{
+	EAsyncClosure *closure;
+	GAsyncResult *result;
+	gboolean success;
+
+	g_return_val_if_fail (E_IS_ATTACHMENT (attachment), FALSE);
+
+	closure = e_async_closure_new ();
+
+	e_attachment_load_async (attachment, e_async_closure_callback, closure);
+
+	result = e_async_closure_wait (closure);
+
+	success = e_attachment_load_finish (attachment, result, error);
+
+	e_async_closure_free (closure);
+
+	return success;
+}
+
+/************************* e_attachment_open_async() *************************/
+
+typedef struct _OpenContext OpenContext;
+
+struct _OpenContext {
+	EAttachment *attachment;
+	GSimpleAsyncResult *simple;
+
+	GAppInfo *app_info;
+};
+
+static OpenContext *
+attachment_open_context_new (EAttachment *attachment,
+                             GAsyncReadyCallback callback,
+                             gpointer user_data)
+{
+	OpenContext *open_context;
+	GSimpleAsyncResult *simple;
+
+	simple = g_simple_async_result_new (
+		G_OBJECT (attachment), callback,
+		user_data, e_attachment_open_async);
+
+	open_context = g_slice_new0 (OpenContext);
+	open_context->attachment = g_object_ref (attachment);
+	open_context->simple = simple;
+
+	return open_context;
+}
+
+static void
+attachment_open_context_free (OpenContext *open_context)
+{
+	g_object_unref (open_context->attachment);
+	g_object_unref (open_context->simple);
+
+	if (open_context->app_info != NULL)
+		g_object_unref (open_context->app_info);
+
+	g_slice_free (OpenContext, open_context);
+}
+
+static gboolean
+attachment_open_check_for_error (OpenContext *open_context,
+                                 GError *error)
+{
+	GSimpleAsyncResult *simple;
+
+	if (error == NULL)
+		return FALSE;
+
+	simple = open_context->simple;
+	g_simple_async_result_take_error (simple, error);
+	g_simple_async_result_complete (simple);
+
+	attachment_open_context_free (open_context);
+
+	return TRUE;
+}
+
+static void
+attachment_open_file (GFile *file,
+                      OpenContext *open_context)
+{
+	GdkAppLaunchContext *context;
+	GSimpleAsyncResult *simple;
+	GdkDisplay *display;
+	gboolean success;
+	GError *error = NULL;
+
+	simple = open_context->simple;
+
+	display = gdk_display_get_default ();
+	context = gdk_display_get_app_launch_context (display);
+
+	if (open_context->app_info != NULL) {
+		GList *file_list;
+
+		file_list = g_list_prepend (NULL, file);
+		success = g_app_info_launch (
+			open_context->app_info, file_list,
+			G_APP_LAUNCH_CONTEXT (context), &error);
+		g_list_free (file_list);
+	} else {
+		gchar *uri;
+
+		uri = g_file_get_uri (file);
+		success = g_app_info_launch_default_for_uri (
+			uri, G_APP_LAUNCH_CONTEXT (context), &error);
+		g_free (uri);
+	}
+
+	g_object_unref (context);
+
+	g_simple_async_result_set_op_res_gboolean (simple, success);
+
+	if (error != NULL)
+		g_simple_async_result_take_error (simple, error);
+
+	g_simple_async_result_complete (simple);
+	attachment_open_context_free (open_context);
+}
+
+static void
+attachment_open_save_finished_cb (EAttachment *attachment,
+                                  GAsyncResult *result,
+                                  OpenContext *open_context)
+{
+	GFile *file;
+	gchar *path;
+	GError *error = NULL;
+
+	file = e_attachment_save_finish (attachment, result, &error);
+
+	if (attachment_open_check_for_error (open_context, error))
+		return;
+
+	/* Make the temporary file read-only.
+	 *
+	 * This step is non-critical, so if an error occurs just
+	 * emit a warning and move on.
+	 *
+	 * XXX I haven't figured out how to do this through GIO.
+	 *     Attempting to set the "access::can-write" attribute via
+	 *     g_file_set_attribute() returned G_IO_ERROR_NOT_SUPPORTED
+	 *     and the only other possibility I see is "unix::mode",
+	 *     which is obviously not portable.
+	 */
+	path = g_file_get_path (file);
+#ifndef G_OS_WIN32
+	if (g_chmod (path, S_IRUSR | S_IRGRP | S_IROTH) < 0)
+		g_warning ("%s", g_strerror (errno));
+#endif
+	g_free (path);
+
+	attachment_open_file (file, open_context);
+	g_object_unref (file);
+}
+
+static void
+attachment_open_save_temporary (OpenContext *open_context)
+{
+	GFile *temp_directory;
+	gchar *template;
+	gchar *path;
+	GError *error = NULL;
+
+	errno = 0;
+
+	/* Save the file to a temporary directory.
+	 * We use a directory so the files can retain their basenames.
+	 * XXX This could trigger a blocking temp directory cleanup. */
+	template = g_strdup_printf (PACKAGE "-%s-XXXXXX", g_get_user_name ());
+	path = e_mkdtemp (template);
+	g_free (template);
+
+	/* XXX Let's hope errno got set properly. */
+	if (path == NULL)
+		g_set_error (
+			&error, G_FILE_ERROR,
+			g_file_error_from_errno (errno),
+			"%s", g_strerror (errno));
+
+	/* We already know if there's an error, but this does the cleanup. */
+	if (attachment_open_check_for_error (open_context, error))
+		return;
+
+	temp_directory = g_file_new_for_path (path);
+
+	e_attachment_save_async (
+		open_context->attachment,
+		temp_directory, (GAsyncReadyCallback)
+		attachment_open_save_finished_cb, open_context);
+
+	g_object_unref (temp_directory);
+	g_free (path);
+}
+
+void
+e_attachment_open_async (EAttachment *attachment,
+                         GAppInfo *app_info,
+                         GAsyncReadyCallback callback,
+                         gpointer user_data)
+{
+	OpenContext *open_context;
+	CamelMimePart *mime_part;
+	GFile *file;
+
+	g_return_if_fail (E_IS_ATTACHMENT (attachment));
+
+	file = e_attachment_get_file (attachment);
+	mime_part = e_attachment_get_mime_part (attachment);
+	g_return_if_fail (file != NULL || mime_part != NULL);
+
+	open_context = attachment_open_context_new (
+		attachment, callback, user_data);
+
+	if (G_IS_APP_INFO (app_info))
+		open_context->app_info = g_object_ref (app_info);
+
+	/* If the attachment already references a GFile, we can launch
+	 * the application directly.  Otherwise we have to save the MIME
+	 * part to a temporary file and launch the application from that. */
+	if (file != NULL) {
+		attachment_open_file (file, open_context);
+
+	} else if (mime_part != NULL)
+		attachment_open_save_temporary (open_context);
+}
+
+gboolean
+e_attachment_open_finish (EAttachment *attachment,
+                          GAsyncResult *result,
+                          GError **error)
+{
+	GSimpleAsyncResult *simple;
+	gboolean success;
+
+	g_return_val_if_fail (E_IS_ATTACHMENT (attachment), FALSE);
+	g_return_val_if_fail (G_IS_SIMPLE_ASYNC_RESULT (result), FALSE);
+
+	simple = G_SIMPLE_ASYNC_RESULT (result);
+	success = g_simple_async_result_get_op_res_gboolean (simple);
+	g_simple_async_result_propagate_error (simple, error);
+
+	return success;
+}
+
+void
+e_attachment_open_handle_error (EAttachment *attachment,
+                                GAsyncResult *result,
+                                GtkWindow *parent)
+{
+	GtkWidget *dialog;
+	GFileInfo *file_info;
+	const gchar *display_name;
+	const gchar *primary_text;
+	GError *error = NULL;
+
+	g_return_if_fail (E_IS_ATTACHMENT (attachment));
+	g_return_if_fail (G_IS_ASYNC_RESULT (result));
+	g_return_if_fail (GTK_IS_WINDOW (parent));
+
+	if (e_attachment_open_finish (attachment, result, &error))
+		return;
+
+	/* Ignore cancellations. */
+	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+		return;
+
+	file_info = e_attachment_get_file_info (attachment);
+
+	if (file_info != NULL)
+		display_name = g_file_info_get_display_name (file_info);
+	else
+		display_name = NULL;
+
+	if (display_name != NULL)
+		primary_text = g_strdup_printf (
+			_("Could not open '%s'"), display_name);
+	else
+		primary_text = g_strdup_printf (
+			_("Could not open the attachment"));
+
+	dialog = gtk_message_dialog_new_with_markup (
+		parent, GTK_DIALOG_DESTROY_WITH_PARENT,
+		GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
+		"<big><b>%s</b></big>", primary_text);
+
+	gtk_message_dialog_format_secondary_text (
+		GTK_MESSAGE_DIALOG (dialog), "%s", error->message);
+
+	gtk_dialog_run (GTK_DIALOG (dialog));
+
+	gtk_widget_destroy (dialog);
+	g_error_free (error);
+}
+
+gboolean
+e_attachment_open (EAttachment *attachment,
+		   GAppInfo *app_info,
+		   GError **error)
+{
+	EAsyncClosure *closure;
+	GAsyncResult *result;
+	gboolean success;
+
+	g_return_val_if_fail (E_IS_ATTACHMENT (attachment), FALSE);
+
+	closure = e_async_closure_new ();
+
+	e_attachment_open_async (attachment, app_info, e_async_closure_callback, closure);
+
+	result = e_async_closure_wait (closure);
+
+	success = e_attachment_open_finish (attachment, result, error);
+
+	e_async_closure_free (closure);
+
+	return success;
+}
+
+/************************* e_attachment_save_async() *************************/
+
+typedef struct _SaveContext SaveContext;
+
+struct _SaveContext {
+	EAttachment *attachment;
+	GSimpleAsyncResult *simple;
+
+	GFile *directory;
+	GFile *destination;
+	GInputStream *input_stream;
+	GOutputStream *output_stream;
+	goffset total_num_bytes;
+	gssize bytes_read;
+	gchar buffer[4096];
+	gint count;
+};
+
+/* Forward Declaration */
+static void
+attachment_save_read_cb (GInputStream *input_stream,
+                         GAsyncResult *result,
+                         SaveContext *save_context);
+
+static SaveContext *
+attachment_save_context_new (EAttachment *attachment,
+                             GAsyncReadyCallback callback,
+                             gpointer user_data)
+{
+	SaveContext *save_context;
+	GSimpleAsyncResult *simple;
+
+	simple = g_simple_async_result_new (
+		G_OBJECT (attachment), callback,
+		user_data, e_attachment_save_async);
+
+	save_context = g_slice_new0 (SaveContext);
+	save_context->attachment = g_object_ref (attachment);
+	save_context->simple = simple;
+
+	attachment_set_saving (save_context->attachment, TRUE);
+
+	return save_context;
+}
+
+static void
+attachment_save_context_free (SaveContext *save_context)
+{
+	g_object_unref (save_context->attachment);
+	g_object_unref (save_context->simple);
+
+	if (save_context->directory != NULL)
+		g_object_unref (save_context->directory);
+
+	if (save_context->destination != NULL)
+		g_object_unref (save_context->destination);
+
+	if (save_context->input_stream != NULL)
+		g_object_unref (save_context->input_stream);
+
+	if (save_context->output_stream != NULL)
+		g_object_unref (save_context->output_stream);
+
+	g_slice_free (SaveContext, save_context);
+}
+
+static gboolean
+attachment_save_check_for_error (SaveContext *save_context,
+                                 GError *error)
+{
+	GSimpleAsyncResult *simple;
+
+	if (error == NULL)
+		return FALSE;
+
+	simple = save_context->simple;
+	g_simple_async_result_take_error (simple, error);
+	g_simple_async_result_complete (simple);
+
+	attachment_save_context_free (save_context);
+
+	return TRUE;
+}
+
+static GFile *
+attachment_save_new_candidate (SaveContext *save_context)
+{
+	GFile *candidate;
+	GFileInfo *file_info;
+	EAttachment *attachment;
+	const gchar *display_name = NULL;
+	gchar *basename;
+
+	attachment = save_context->attachment;
+	file_info = e_attachment_get_file_info (attachment);
+
+	if (file_info != NULL)
+		display_name = g_file_info_get_display_name (file_info);
+	if (display_name == NULL)
+		/* Translators: Default attachment filename. */
+		display_name = _("attachment.dat");
+
+	if (save_context->count == 0)
+		basename = g_strdup (display_name);
+	else {
+		GString *string;
+		const gchar *ext;
+		gsize length;
+
+		string = g_string_sized_new (strlen (display_name));
+		ext = g_utf8_strchr (display_name, -1, '.');
+
+		if (ext != NULL)
+			length = ext - display_name;
+		else
+			length = strlen (display_name);
+
+		g_string_append_len (string, display_name, length);
+		g_string_append_printf (string, " (%d)", save_context->count);
+		g_string_append (string, (ext != NULL) ? ext : "");
+
+		basename = g_string_free (string, FALSE);
+	}
+
+	save_context->count++;
+
+	candidate = g_file_get_child (save_context->directory, basename);
+
+	g_free (basename);
+
+	return candidate;
+}
+
+static void
+attachment_save_write_cb (GOutputStream *output_stream,
+                          GAsyncResult *result,
+                          SaveContext *save_context)
+{
+	EAttachment *attachment;
+	GCancellable *cancellable;
+	GInputStream *input_stream;
+	gssize bytes_written;
+	GError *error = NULL;
+
+	bytes_written = g_output_stream_write_finish (
+		output_stream, result, &error);
+
+	if (attachment_save_check_for_error (save_context, error))
+		return;
+
+	attachment = save_context->attachment;
+	cancellable = attachment->priv->cancellable;
+	input_stream = save_context->input_stream;
+
+	if (bytes_written < save_context->bytes_read) {
+		g_memmove (
+			save_context->buffer,
+			save_context->buffer + bytes_written,
+			save_context->bytes_read - bytes_written);
+		save_context->bytes_read -= bytes_written;
+
+		g_output_stream_write_async (
+			output_stream,
+			save_context->buffer,
+			save_context->bytes_read,
+			G_PRIORITY_DEFAULT, cancellable,
+			(GAsyncReadyCallback) attachment_save_write_cb,
+			save_context);
+	} else
+		g_input_stream_read_async (
+			input_stream,
+			save_context->buffer,
+			sizeof (save_context->buffer),
+			G_PRIORITY_DEFAULT, cancellable,
+			(GAsyncReadyCallback) attachment_save_read_cb,
+			save_context);
+}
+
+static void
+attachment_save_read_cb (GInputStream *input_stream,
+                         GAsyncResult *result,
+                         SaveContext *save_context)
+{
+	EAttachment *attachment;
+	GCancellable *cancellable;
+	GOutputStream *output_stream;
+	gssize bytes_read;
+	GError *error = NULL;
+
+	bytes_read = g_input_stream_read_finish (
+		input_stream, result, &error);
+
+	if (attachment_save_check_for_error (save_context, error))
+		return;
+
+	if (bytes_read == 0) {
+		GSimpleAsyncResult *simple;
+		GFile *destination;
+
+		/* Steal the destination. */
+		destination = save_context->destination;
+		save_context->destination = NULL;
+
+		simple = save_context->simple;
+		g_simple_async_result_set_op_res_gpointer (
+			simple, destination, (GDestroyNotify) g_object_unref);
+		g_simple_async_result_complete (simple);
+
+		attachment_save_context_free (save_context);
+
+		return;
+	}
+
+	attachment = save_context->attachment;
+	cancellable = attachment->priv->cancellable;
+	output_stream = save_context->output_stream;
+	save_context->bytes_read = bytes_read;
+
+	attachment_progress_cb (
+		g_seekable_tell (G_SEEKABLE (input_stream)),
+		save_context->total_num_bytes, attachment);
+
+	g_output_stream_write_async (
+		output_stream,
+		save_context->buffer,
+		save_context->bytes_read,
+		G_PRIORITY_DEFAULT, cancellable,
+		(GAsyncReadyCallback) attachment_save_write_cb,
+		save_context);
+}
+
+static void
+attachment_save_got_output_stream (SaveContext *save_context)
+{
+	GCancellable *cancellable;
+	GInputStream *input_stream;
+	CamelDataWrapper *wrapper;
+	CamelMimePart *mime_part;
+	CamelStream *stream;
+	EAttachment *attachment;
+	GByteArray *buffer;
+
+	attachment = save_context->attachment;
+	cancellable = attachment->priv->cancellable;
+	mime_part = e_attachment_get_mime_part (attachment);
+
+	/* Decode the MIME part to an in-memory buffer.  We have to do
+	 * this because CamelStream is synchronous-only, and using threads
+	 * is dangerous because CamelDataWrapper is not reentrant. */
+	buffer = g_byte_array_new ();
+	stream = camel_stream_mem_new ();
+	camel_stream_mem_set_byte_array (CAMEL_STREAM_MEM (stream), buffer);
+	wrapper = camel_medium_get_content (CAMEL_MEDIUM (mime_part));
+	camel_data_wrapper_decode_to_stream_sync (wrapper, stream, NULL, NULL);
+	g_object_unref (stream);
+
+	/* Load the buffer into a GMemoryInputStream.
+	 * But watch out for zero length MIME parts. */
+	input_stream = g_memory_input_stream_new ();
+	if (buffer->len > 0)
+		g_memory_input_stream_add_data (
+			G_MEMORY_INPUT_STREAM (input_stream),
+			buffer->data, (gssize) buffer->len,
+			(GDestroyNotify) g_free);
+	save_context->input_stream = input_stream;
+	save_context->total_num_bytes = (goffset) buffer->len;
+	g_byte_array_free (buffer, FALSE);
+
+	g_input_stream_read_async (
+		input_stream,
+		save_context->buffer,
+		sizeof (save_context->buffer),
+		G_PRIORITY_DEFAULT, cancellable,
+		(GAsyncReadyCallback) attachment_save_read_cb,
+		save_context);
+}
+
+static void
+attachment_save_create_cb (GFile *destination,
+                           GAsyncResult *result,
+                           SaveContext *save_context)
+{
+	EAttachment *attachment;
+	GCancellable *cancellable;
+	GFileOutputStream *output_stream;
+	GError *error = NULL;
+
+	/* Output stream might be NULL, so don't use cast macro. */
+	output_stream = g_file_create_finish (destination, result, &error);
+	save_context->output_stream = (GOutputStream *) output_stream;
+
+	attachment = save_context->attachment;
+	cancellable = attachment->priv->cancellable;
+
+	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_EXISTS)) {
+		destination = attachment_save_new_candidate (save_context);
+
+		g_file_create_async (
+			destination, G_FILE_CREATE_NONE,
+			G_PRIORITY_DEFAULT, cancellable,
+			(GAsyncReadyCallback) attachment_save_create_cb,
+			save_context);
+
+		g_object_unref (destination);
+		g_error_free (error);
+		return;
+	}
+
+	if (attachment_save_check_for_error (save_context, error))
+		return;
+
+	save_context->destination = g_object_ref (destination);
+	attachment_save_got_output_stream (save_context);
+}
+
+static void
+attachment_save_replace_cb (GFile *destination,
+                            GAsyncResult *result,
+                            SaveContext *save_context)
+{
+	GFileOutputStream *output_stream;
+	GError *error = NULL;
+
+	/* Output stream might be NULL, so don't use cast macro. */
+	output_stream = g_file_replace_finish (destination, result, &error);
+	save_context->output_stream = (GOutputStream *) output_stream;
+
+	if (attachment_save_check_for_error (save_context, error))
+		return;
+
+	save_context->destination = g_object_ref (destination);
+	attachment_save_got_output_stream (save_context);
+}
+
+static void
+attachment_save_query_info_cb (GFile *destination,
+                               GAsyncResult *result,
+                               SaveContext *save_context)
+{
+	EAttachment *attachment;
+	GCancellable *cancellable;
+	GFileInfo *file_info;
+	GFileType file_type;
+	GError *error = NULL;
+
+	attachment = save_context->attachment;
+	cancellable = attachment->priv->cancellable;
+
+	file_info = g_file_query_info_finish (destination, result, &error);
+
+	/* G_IO_ERROR_NOT_FOUND just means we're creating a new file. */
+	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) {
+		g_error_free (error);
+		goto replace;
+	}
+
+	if (attachment_save_check_for_error (save_context, error))
+		return;
+
+	file_type = g_file_info_get_file_type (file_info);
+	g_object_unref (file_info);
+
+	if (file_type == G_FILE_TYPE_DIRECTORY) {
+		save_context->directory = g_object_ref (destination);
+		destination = attachment_save_new_candidate (save_context);
+
+		g_file_create_async (
+			destination, G_FILE_CREATE_NONE,
+			G_PRIORITY_DEFAULT, cancellable,
+			(GAsyncReadyCallback) attachment_save_create_cb,
+			save_context);
+
+		g_object_unref (destination);
+
+		return;
+	}
+
+replace:
+	g_file_replace_async (
+		destination, NULL, FALSE,
+		G_FILE_CREATE_REPLACE_DESTINATION,
+		G_PRIORITY_DEFAULT, cancellable,
+		(GAsyncReadyCallback) attachment_save_replace_cb,
+		save_context);
+}
+
+void
+e_attachment_save_async (EAttachment *attachment,
+                         GFile *destination,
+                         GAsyncReadyCallback callback,
+                         gpointer user_data)
+{
+	SaveContext *save_context;
+	GCancellable *cancellable;
+
+	g_return_if_fail (E_IS_ATTACHMENT (attachment));
+	g_return_if_fail (G_IS_FILE (destination));
+
+	if (e_attachment_get_loading (attachment)) {
+		g_simple_async_report_error_in_idle (
+			G_OBJECT (attachment), callback, user_data,
+			G_IO_ERROR, G_IO_ERROR_BUSY,
+			_("A load operation is already in progress"));
+		return;
+	}
+
+	if (e_attachment_get_saving (attachment)) {
+		g_simple_async_report_error_in_idle (
+			G_OBJECT (attachment), callback, user_data,
+			G_IO_ERROR, G_IO_ERROR_BUSY,
+			_("A save operation is already in progress"));
+		return;
+	}
+
+	if (e_attachment_get_mime_part (attachment) == NULL) {
+		g_simple_async_report_error_in_idle (
+			G_OBJECT (attachment), callback, user_data,
+			G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
+			_("Attachment contents not loaded"));
+		return;
+	}
+
+	save_context = attachment_save_context_new (
+		attachment, callback, user_data);
+
+	cancellable = attachment->priv->cancellable;
+	g_cancellable_reset (cancellable);
+
+	/* First we need to know if destination is a directory. */
+	g_file_query_info_async (
+		destination, G_FILE_ATTRIBUTE_STANDARD_TYPE,
+		G_FILE_QUERY_INFO_NONE, G_PRIORITY_DEFAULT,
+		cancellable, (GAsyncReadyCallback)
+		attachment_save_query_info_cb, save_context);
+}
+
+GFile *
+e_attachment_save_finish (EAttachment *attachment,
+                          GAsyncResult *result,
+                          GError **error)
+{
+	GSimpleAsyncResult *simple;
+	GFile *destination;
+
+	g_return_val_if_fail (E_IS_ATTACHMENT (attachment), NULL);
+	g_return_val_if_fail (G_IS_SIMPLE_ASYNC_RESULT (result), NULL);
+
+	simple = G_SIMPLE_ASYNC_RESULT (result);
+	destination = g_simple_async_result_get_op_res_gpointer (simple);
+	if (destination != NULL)
+		g_object_ref (destination);
+	g_simple_async_result_propagate_error (simple, error);
+
+	attachment_set_saving (attachment, FALSE);
+
+	return destination;
+}
+
+void
+e_attachment_save_handle_error (EAttachment *attachment,
+                                GAsyncResult *result,
+                                GtkWindow *parent)
+{
+	GFile *file;
+	GFileInfo *file_info;
+	GtkWidget *dialog;
+	const gchar *display_name;
+	const gchar *primary_text;
+	GError *error = NULL;
+
+	g_return_if_fail (E_IS_ATTACHMENT (attachment));
+	g_return_if_fail (G_IS_ASYNC_RESULT (result));
+	g_return_if_fail (GTK_IS_WINDOW (parent));
+
+	file = e_attachment_save_finish (attachment, result, &error);
+
+	if (file != NULL) {
+		g_object_unref (file);
+		return;
+	}
+
+	/* Ignore cancellations. */
+	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+		return;
+
+	file_info = e_attachment_get_file_info (attachment);
+
+	if (file_info != NULL)
+		display_name = g_file_info_get_display_name (file_info);
+	else
+		display_name = NULL;
+
+	if (display_name != NULL)
+		primary_text = g_strdup_printf (
+			_("Could not save '%s'"), display_name);
+	else
+		primary_text = g_strdup_printf (
+			_("Could not save the attachment"));
+
+	dialog = gtk_message_dialog_new_with_markup (
+		parent, GTK_DIALOG_DESTROY_WITH_PARENT,
+		GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
+		"<big><b>%s</b></big>", primary_text);
+
+	gtk_message_dialog_format_secondary_text (
+		GTK_MESSAGE_DIALOG (dialog), "%s", error->message);
+
+	gtk_dialog_run (GTK_DIALOG (dialog));
+
+	gtk_widget_destroy (dialog);
+	g_error_free (error);
+}
+
+gboolean
+e_attachment_save (EAttachment *attachment,
+		   GFile *in_destination,
+		   GFile **out_destination,
+		   GError **error)
+{
+	EAsyncClosure *closure;
+	GAsyncResult *result;
+
+	g_return_val_if_fail (E_IS_ATTACHMENT (attachment), FALSE);
+	g_return_val_if_fail (out_destination != NULL, FALSE);
+
+	closure = e_async_closure_new ();
+
+	e_attachment_save_async (attachment, in_destination, e_async_closure_callback, closure);
+
+	result = e_async_closure_wait (closure);
+
+	*out_destination = e_attachment_save_finish (attachment, result, error);
+
+	e_async_closure_free (closure);
+
+	return *out_destination != NULL;
+}
diff --git a/e-util/e-attachment.h b/e-util/e-attachment.h
new file mode 100644
index 0000000..268bcf6
--- /dev/null
+++ b/e-util/e-attachment.h
@@ -0,0 +1,159 @@
+/*
+ * e-attachment.h
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_ATTACHMENT_H
+#define E_ATTACHMENT_H
+
+#include <gtk/gtk.h>
+#include <camel/camel.h>
+
+/* Standard GObject macros */
+#define E_TYPE_ATTACHMENT \
+	(e_attachment_get_type ())
+#define E_ATTACHMENT(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_ATTACHMENT, EAttachment))
+#define E_ATTACHMENT_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_ATTACHMENT, EAttachmentClass))
+#define E_IS_ATTACHMENT(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_ATTACHMENT))
+#define E_IS_ATTACHMENT_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_ATTACHMENT))
+#define E_ATTACHMENT_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_ATTACHMENT, EAttachmentClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EAttachment EAttachment;
+typedef struct _EAttachmentClass EAttachmentClass;
+typedef struct _EAttachmentPrivate EAttachmentPrivate;
+
+struct _EAttachment {
+	GObject parent;
+	EAttachmentPrivate *priv;
+};
+
+struct _EAttachmentClass {
+	GObjectClass parent_class;
+};
+
+GType		e_attachment_get_type		(void);
+EAttachment *	e_attachment_new		(void);
+EAttachment *	e_attachment_new_for_path	(const gchar *path);
+EAttachment *	e_attachment_new_for_uri	(const gchar *uri);
+EAttachment *	e_attachment_new_for_message	(CamelMimeMessage *message);
+void		e_attachment_add_to_multipart	(EAttachment *attachment,
+						 CamelMultipart *multipart,
+						 const gchar *default_charset);
+void		e_attachment_cancel		(EAttachment *attachment);
+gboolean	e_attachment_get_can_show	(EAttachment *attachment);
+void		e_attachment_set_can_show	(EAttachment *attachment,
+						 gboolean can_show);
+const gchar *	e_attachment_get_disposition	(EAttachment *attachment);
+void		e_attachment_set_disposition	(EAttachment *attachment,
+						 const gchar *disposition);
+GFile *		e_attachment_get_file		(EAttachment *attachment);
+void		e_attachment_set_file		(EAttachment *attachment,
+						 GFile *file);
+GFileInfo *	e_attachment_get_file_info	(EAttachment *attachment);
+void		e_attachment_set_file_info	(EAttachment *attachment,
+						 GFileInfo *file_info);
+gchar *		e_attachment_get_mime_type	(EAttachment *attachment);
+GIcon *		e_attachment_get_icon		(EAttachment *attachment);
+gboolean	e_attachment_get_loading	(EAttachment *attachment);
+CamelMimePart *	e_attachment_get_mime_part	(EAttachment *attachment);
+void		e_attachment_set_mime_part	(EAttachment *attachment,
+						 CamelMimePart *mime_part);
+gint		e_attachment_get_percent	(EAttachment *attachment);
+GtkTreeRowReference *
+		e_attachment_get_reference	(EAttachment *attachment);
+void		e_attachment_set_reference	(EAttachment *attachment,
+						 GtkTreeRowReference *reference);
+gboolean	e_attachment_get_saving		(EAttachment *attachment);
+gboolean	e_attachment_get_shown		(EAttachment *attachment);
+void		e_attachment_set_shown		(EAttachment *attachment,
+						 gboolean shown);
+camel_cipher_validity_encrypt_t
+		e_attachment_get_encrypted	(EAttachment *attachment);
+void		e_attachment_set_encrypted	(EAttachment *attachment,
+						 camel_cipher_validity_encrypt_t encrypted);
+camel_cipher_validity_sign_t
+		e_attachment_get_signed		(EAttachment *attachment);
+void		e_attachment_set_signed		(EAttachment *attachment,
+						 camel_cipher_validity_sign_t signed_);
+const gchar *	e_attachment_get_description	(EAttachment *attachment);
+const gchar *	e_attachment_get_thumbnail_path	(EAttachment *attachment);
+gboolean	e_attachment_is_rfc822		(EAttachment *attachment);
+GList *		e_attachment_list_apps		(EAttachment *attachment);
+
+/* Asynchronous Operations */
+void		e_attachment_load_async		(EAttachment *attachment,
+						 GAsyncReadyCallback callback,
+						 gpointer user_data);
+gboolean	e_attachment_load_finish	(EAttachment *attachment,
+						 GAsyncResult *result,
+						 GError **error);
+gboolean	e_attachment_load		(EAttachment *attachment,
+						 GError **error);
+void		e_attachment_open_async		(EAttachment *attachment,
+						 GAppInfo *app_info,
+						 GAsyncReadyCallback callback,
+						 gpointer user_data);
+gboolean	e_attachment_open_finish	(EAttachment *attachment,
+						 GAsyncResult *result,
+						 GError **error);
+gboolean	e_attachment_open		(EAttachment *attachment,
+						 GAppInfo *app_info,
+						 GError **error);
+void		e_attachment_save_async		(EAttachment *attachment,
+						 GFile *destination,
+						 GAsyncReadyCallback callback,
+						 gpointer user_data);
+GFile *		e_attachment_save_finish	(EAttachment *attachment,
+						 GAsyncResult *result,
+						 GError **error);
+gboolean	e_attachment_save		(EAttachment *attachment,
+						 GFile *in_destination,
+						 GFile **out_destination,
+						 GError **error);
+
+/* Handy GAsyncReadyCallback Functions */
+void		e_attachment_load_handle_error	(EAttachment *attachment,
+						 GAsyncResult *result,
+						 GtkWindow *parent);
+void		e_attachment_open_handle_error	(EAttachment *attachment,
+						 GAsyncResult *result,
+						 GtkWindow *parent);
+void		e_attachment_save_handle_error	(EAttachment *attachment,
+						 GAsyncResult *result,
+						 GtkWindow *parent);
+
+G_END_DECLS
+
+#endif /* E_ATTACHMENT_H */
diff --git a/widgets/misc/e-auth-combo-box.c b/e-util/e-auth-combo-box.c
similarity index 100%
rename from widgets/misc/e-auth-combo-box.c
rename to e-util/e-auth-combo-box.c
diff --git a/e-util/e-auth-combo-box.h b/e-util/e-auth-combo-box.h
new file mode 100644
index 0000000..a8ec3f9
--- /dev/null
+++ b/e-util/e-auth-combo-box.h
@@ -0,0 +1,75 @@
+/*
+ * e-auth-combo-box.h
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_AUTH_COMBO_BOX_H
+#define E_AUTH_COMBO_BOX_H
+
+#include <gtk/gtk.h>
+#include <camel/camel.h>
+
+/* Standard GObject macros */
+#define E_TYPE_AUTH_COMBO_BOX \
+	(e_auth_combo_box_get_type ())
+#define E_AUTH_COMBO_BOX(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_AUTH_COMBO_BOX, EAuthComboBox))
+#define E_AUTH_COMBO_BOX_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_AUTH_COMBO_BOX, EAuthComboBoxClass))
+#define E_IS_AUTH_COMBO_BOX(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_AUTH_COMBO_BOX))
+#define E_IS_AUTH_COMBO_BOX_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_AUTH_COMBO_BOX))
+#define E_AUTH_COMBO_BOX_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_AUTH_COMBO_BOX, EAuthComboBoxClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EAuthComboBox EAuthComboBox;
+typedef struct _EAuthComboBoxClass EAuthComboBoxClass;
+typedef struct _EAuthComboBoxPrivate EAuthComboBoxPrivate;
+
+struct _EAuthComboBox {
+	GtkComboBox parent;
+	EAuthComboBoxPrivate *priv;
+};
+
+struct _EAuthComboBoxClass {
+	GtkComboBoxClass parent_class;
+};
+
+GType		e_auth_combo_box_get_type	(void) G_GNUC_CONST;
+GtkWidget *	e_auth_combo_box_new		(void);
+CamelProvider *	e_auth_combo_box_get_provider	(EAuthComboBox *combo_box);
+void		e_auth_combo_box_set_provider	(EAuthComboBox *combo_box,
+						 CamelProvider *provider);
+void		e_auth_combo_box_update_available
+						(EAuthComboBox *combo_box,
+						 GList *available_authtypes);
+
+G_END_DECLS
+
+#endif /* E_AUTH_COMBO_BOX_H */
+
diff --git a/widgets/misc/e-autocomplete-selector.c b/e-util/e-autocomplete-selector.c
similarity index 100%
rename from widgets/misc/e-autocomplete-selector.c
rename to e-util/e-autocomplete-selector.c
diff --git a/e-util/e-autocomplete-selector.h b/e-util/e-autocomplete-selector.h
new file mode 100644
index 0000000..af17f21
--- /dev/null
+++ b/e-util/e-autocomplete-selector.h
@@ -0,0 +1,68 @@
+/*
+ * e-autocomplete-selector.h
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_AUTOCOMPLETE_SELECTOR_H
+#define E_AUTOCOMPLETE_SELECTOR_H
+
+#include <e-util/e-source-selector.h>
+
+/* Standard GObject macros */
+#define E_TYPE_AUTOCOMPLETE_SELECTOR \
+	(e_autocomplete_selector_get_type ())
+#define E_AUTOCOMPLETE_SELECTOR(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_AUTOCOMPLETE_SELECTOR, EAutocompleteSelector))
+#define E_AUTOCOMPLETE_SELECTOR_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_AUTOCOMPLETE_SELECTOR, EAutocompleteSelectorClass))
+#define E_IS_AUTOCOMPLETE_SELECTOR(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_AUTOCOMPLETE_SELECTOR))
+#define E_IS_AUTOCOMPLETE_SELECTOR_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_AUTOCOMPLETE_SELECTOR))
+#define E_AUTOCOMPLETE_SELECTOR_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_AUTOCOMPLETE_SELECTOR, EAutocompleteSelectorClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EAutocompleteSelector EAutocompleteSelector;
+typedef struct _EAutocompleteSelectorClass EAutocompleteSelectorClass;
+typedef struct _EAutocompleteSelectorPrivate EAutocompleteSelectorPrivate;
+
+struct _EAutocompleteSelector {
+	ESourceSelector parent;
+	EAutocompleteSelectorPrivate *priv;
+};
+
+struct _EAutocompleteSelectorClass {
+	ESourceSelectorClass parent_class;
+};
+
+GType		e_autocomplete_selector_get_type
+						(void) G_GNUC_CONST;
+GtkWidget *	e_autocomplete_selector_new	(ESourceRegistry *registry);
+
+G_END_DECLS
+
+#endif /* E_AUTOCOMPLETE_SELECTOR_H */
diff --git a/e-util/e-bit-array.c b/e-util/e-bit-array.c
index 456a4d4..b17f8a0 100644
--- a/e-util/e-bit-array.c
+++ b/e-util/e-bit-array.c
@@ -28,7 +28,6 @@
 #include <gtk/gtk.h>
 
 #include "e-bit-array.h"
-#include "e-util.h"
 
 #define ONES ((guint32) 0xffffffff)
 
diff --git a/e-util/e-bit-array.h b/e-util/e-bit-array.h
index 717fe58..39b55d9 100644
--- a/e-util/e-bit-array.h
+++ b/e-util/e-bit-array.h
@@ -21,6 +21,10 @@
  *
  */
 
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
 #ifndef _E_BIT_ARRAY_H_
 #define _E_BIT_ARRAY_H_
 
diff --git a/widgets/misc/e-book-source-config.c b/e-util/e-book-source-config.c
similarity index 100%
rename from widgets/misc/e-book-source-config.c
rename to e-util/e-book-source-config.c
diff --git a/e-util/e-book-source-config.h b/e-util/e-book-source-config.h
new file mode 100644
index 0000000..3e00078
--- /dev/null
+++ b/e-util/e-book-source-config.h
@@ -0,0 +1,71 @@
+/*
+ * e-book-source-config.h
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_BOOK_SOURCE_CONFIG_H
+#define E_BOOK_SOURCE_CONFIG_H
+
+#include <e-util/e-source-config.h>
+
+/* Standard GObject macros */
+#define E_TYPE_BOOK_SOURCE_CONFIG \
+	(e_book_source_config_get_type ())
+#define E_BOOK_SOURCE_CONFIG(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_BOOK_SOURCE_CONFIG, EBookSourceConfig))
+#define E_BOOK_SOURCE_CONFIG_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_BOOK_SOURCE_CONFIG, EBookSourceConfigClass))
+#define E_IS_BOOK_SOURCE_CONFIG(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_BOOK_SOURCE_CONFIG))
+#define E_IS_BOOK_SOURCE_CONFIG_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_BOOK_SOURCE_CONFIG))
+#define E_BOOK_SOURCE_CONFIG_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_BOOK_SOURCE_CONFIG, EBookSourceConfigClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EBookSourceConfig EBookSourceConfig;
+typedef struct _EBookSourceConfigClass EBookSourceConfigClass;
+typedef struct _EBookSourceConfigPrivate EBookSourceConfigPrivate;
+
+struct _EBookSourceConfig {
+	ESourceConfig parent;
+	EBookSourceConfigPrivate *priv;
+};
+
+struct _EBookSourceConfigClass {
+	ESourceConfigClass parent_class;
+};
+
+GType		e_book_source_config_get_type	(void) G_GNUC_CONST;
+GtkWidget *	e_book_source_config_new	(ESourceRegistry *registry,
+						 ESource *original_source);
+void		e_book_source_config_add_offline_toggle
+						(EBookSourceConfig *config,
+						 ESource *scratch_source);
+
+G_END_DECLS
+
+#endif /* E_BOOK_SOURCE_CONFIG_H */
diff --git a/e-util/e-buffer-tagger.c b/e-util/e-buffer-tagger.c
new file mode 100644
index 0000000..c05a854
--- /dev/null
+++ b/e-util/e-buffer-tagger.c
@@ -0,0 +1,692 @@
+/*
+ * e-buffer-tagger.c
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+#include <glib/gi18n.h>
+#include <regex.h>
+#include <string.h>
+#include <ctype.h>
+
+#include "e-buffer-tagger.h"
+
+#include "e-misc-utils.h"
+
+enum EBufferTaggerState
+{
+	E_BUFFER_TAGGER_STATE_NONE                = 0,
+	E_BUFFER_TAGGER_STATE_INSDEL              = 1 << 0, /* set when was called insert or delete of a text */
+	E_BUFFER_TAGGER_STATE_CHANGED             = 1 << 1, /* remark of the buffer is scheduled */
+	E_BUFFER_TAGGER_STATE_IS_HOVERING         = 1 << 2, /* mouse is over the link */
+	E_BUFFER_TAGGER_STATE_IS_HOVERING_TOOLTIP = 1 << 3, /* mouse is over the link and the tooltip can be shown */
+	E_BUFFER_TAGGER_STATE_CTRL_DOWN           = 1 << 4  /* Ctrl key is down */
+};
+
+#define E_BUFFER_TAGGER_DATA_STATE "EBufferTagger::state"
+#define E_BUFFER_TAGGER_LINK_TAG   "EBufferTagger::link"
+
+struct _MagicInsertMatch
+{
+	const gchar *regex;
+	regex_t *preg;
+	const gchar *prefix;
+};
+
+typedef struct _MagicInsertMatch MagicInsertMatch;
+
+static MagicInsertMatch mim[] = {
+	/* prefixed expressions */
+	{ "(news|telnet|nntp|file|http|ftp|sftp|https|webcal)://([-a-z0-9]+(:[-a-z0-9]+)?@)?[-a-z0-9.]+[-a-z0-9](:[0-9]*)?(([.])?/[-a-z0-9_$.+!*(),;:@%&=?/~#']*[^]'.}>\\) \n\r\t,?!;:\"]?)?", NULL, NULL },
+	{ "(sip|h323|callto):([-_a-z0-9.'\\+]+(:[0-9]{1,5})?(/[-_a-z0-9.']+)?)(@([-_a-z0-9.%=?]+|([0-9]{1,3}.){3}[0-9]{1,3})?)?(:[0-9]{1,5})?", NULL, NULL },
+	{ "mailto:[-_a-z0-9.'\\+]+ [-_a-z0-9 %=?]+", NULL, NULL },
+	/* not prefixed expression */
+	{ "www\\.[-a-z0-9.]+[-a-z0-9](:[0-9]*)?(([.])?/[-A-Za-z0-9_$.+!*(),;:@%&=?/~#]*[^]'.}>\\) \n\r\t,?!;:\"]?)?", NULL, "http://"; },
+	{ "ftp\\.[-a-z0-9.]+[-a-z0-9](:[0-9]*)?(([.])?/[-A-Za-z0-9_$.+!*(),;:@%&=?/~#]*[^]'.}>\\) \n\r\t,?!;:\"]?)?", NULL, "ftp://"; },
+	{ "[-_a-z0-9.'\\+]+ [-_a-z0-9 %=?]+", NULL, "mailto:"; }
+};
+
+static void
+init_magic_links (void)
+{
+	static gboolean done = FALSE;
+	gint i;
+
+	if (done)
+		return;
+
+	done = TRUE;
+
+	for (i = 0; i < G_N_ELEMENTS (mim); i++) {
+		mim[i].preg = g_new0 (regex_t, 1);
+		if (regcomp (mim[i].preg, mim[i].regex, REG_EXTENDED | REG_ICASE)) {
+			/* error */
+			g_free (mim[i].preg);
+			mim[i].preg = 0;
+		}
+	}
+}
+
+static void
+markup_text (GtkTextBuffer *buffer)
+{
+	GtkTextIter start, end;
+	gchar *text;
+	gint i;
+	regmatch_t pmatch[2];
+	gboolean any;
+	const gchar *str;
+	gint offset = 0;
+
+	g_return_if_fail (buffer != NULL);
+
+	gtk_text_buffer_get_start_iter (buffer, &start);
+	gtk_text_buffer_get_end_iter (buffer, &end);
+	gtk_text_buffer_remove_tag_by_name (buffer, E_BUFFER_TAGGER_LINK_TAG, &start, &end);
+	text = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
+
+	str = text;
+	any = TRUE;
+	while (any) {
+		any = FALSE;
+		for (i = 0; i < G_N_ELEMENTS (mim); i++) {
+			if (mim[i].preg && !regexec (mim[i].preg, str, 2, pmatch, 0)) {
+				gtk_text_buffer_get_iter_at_offset (buffer, &start, offset + pmatch[0].rm_so);
+				gtk_text_buffer_get_iter_at_offset (buffer, &end, offset + pmatch[0].rm_eo);
+				gtk_text_buffer_apply_tag_by_name (buffer, E_BUFFER_TAGGER_LINK_TAG, &start, &end);
+
+				any = TRUE;
+				str += pmatch[0].rm_eo;
+				offset += pmatch[0].rm_eo;
+				break;
+			}
+		}
+	}
+
+	g_free (text);
+}
+
+static void
+get_pointer_position (GtkTextView *text_view,
+                      gint *x,
+                      gint *y)
+{
+	GdkWindow *window;
+	GdkDisplay *display;
+	GdkDeviceManager *device_manager;
+	GdkDevice *device;
+
+	window = gtk_text_view_get_window (text_view, GTK_TEXT_WINDOW_WIDGET);
+	display = gdk_window_get_display (window);
+	device_manager = gdk_display_get_device_manager (display);
+	device = gdk_device_manager_get_client_pointer (device_manager);
+
+	gdk_window_get_device_position (window, device, x, y, NULL);
+}
+
+static guint32
+get_state (GtkTextBuffer *buffer)
+{
+	g_return_val_if_fail (buffer != NULL, E_BUFFER_TAGGER_STATE_NONE);
+	g_return_val_if_fail (GTK_IS_TEXT_BUFFER (buffer), E_BUFFER_TAGGER_STATE_NONE);
+
+	return GPOINTER_TO_INT (g_object_get_data (G_OBJECT (buffer), E_BUFFER_TAGGER_DATA_STATE));
+}
+
+static void
+set_state (GtkTextBuffer *buffer,
+           guint32 state)
+{
+	g_object_set_data (G_OBJECT (buffer), E_BUFFER_TAGGER_DATA_STATE, GINT_TO_POINTER (state));
+}
+
+static void
+update_state (GtkTextBuffer *buffer,
+              guint32 value,
+              gboolean do_set)
+{
+	guint32 state;
+
+	g_return_if_fail (buffer != NULL);
+	g_return_if_fail (GTK_IS_TEXT_BUFFER (buffer));
+
+	state = get_state (buffer);
+
+	if (do_set)
+		state = state | value;
+	else
+		state = state & (~value);
+
+	set_state (buffer, state);
+}
+
+static gboolean
+get_tag_bounds (GtkTextIter *iter,
+                GtkTextTag *tag,
+                GtkTextIter *start,
+                GtkTextIter *end)
+{
+	gboolean res = FALSE;
+
+	g_return_val_if_fail (iter != NULL, FALSE);
+	g_return_val_if_fail (tag != NULL, FALSE);
+	g_return_val_if_fail (start != NULL, FALSE);
+	g_return_val_if_fail (end != NULL, FALSE);
+
+	if (gtk_text_iter_has_tag (iter, tag)) {
+		*start = *iter;
+		*end = *iter;
+
+		if (!gtk_text_iter_begins_tag (start, tag))
+			gtk_text_iter_backward_to_tag_toggle (start, tag);
+
+		if (!gtk_text_iter_ends_tag (end, tag))
+			gtk_text_iter_forward_to_tag_toggle (end, tag);
+
+		res = TRUE;
+	}
+
+	return res;
+}
+
+static gchar *
+get_url_at_iter (GtkTextBuffer *buffer,
+                 GtkTextIter *iter)
+{
+	GtkTextTagTable *tag_table;
+	GtkTextTag *tag;
+	GtkTextIter start, end;
+	gchar *url = NULL;
+
+	g_return_val_if_fail (buffer != NULL, NULL);
+
+	tag_table = gtk_text_buffer_get_tag_table (buffer);
+	tag = gtk_text_tag_table_lookup (tag_table, E_BUFFER_TAGGER_LINK_TAG);
+	g_return_val_if_fail (tag != NULL, FALSE);
+
+	if (get_tag_bounds (iter, tag, &start, &end))
+		url = gtk_text_iter_get_text (&start, &end);
+
+	return url;
+}
+
+static gboolean
+invoke_link_if_present (GtkTextBuffer *buffer,
+                        GtkTextIter *iter)
+{
+	gboolean res;
+	gchar *url;
+
+	g_return_val_if_fail (buffer != NULL, FALSE);
+
+	url = get_url_at_iter (buffer, iter);
+
+	res = url && *url;
+	if (res)
+		e_show_uri (NULL, url);
+
+	g_free (url);
+
+	return res;
+}
+
+static void
+remove_tag_if_present (GtkTextBuffer *buffer,
+                       GtkTextIter *where)
+{
+	GtkTextTagTable *tag_table;
+	GtkTextTag *tag;
+	GtkTextIter start, end;
+
+	g_return_if_fail (buffer != NULL);
+	g_return_if_fail (where != NULL);
+
+	tag_table = gtk_text_buffer_get_tag_table (buffer);
+	tag = gtk_text_tag_table_lookup (tag_table, E_BUFFER_TAGGER_LINK_TAG);
+	g_return_if_fail (tag != NULL);
+
+	if (get_tag_bounds (where, tag, &start, &end))
+		gtk_text_buffer_remove_tag (buffer, tag, &start, &end);
+}
+
+static void
+buffer_insert_text (GtkTextBuffer *buffer,
+                    GtkTextIter *location,
+                    gchar *text,
+                    gint len,
+                    gpointer user_data)
+{
+	update_state (buffer, E_BUFFER_TAGGER_STATE_INSDEL, TRUE);
+	remove_tag_if_present (buffer, location);
+}
+
+static void
+buffer_delete_range (GtkTextBuffer *buffer,
+                     GtkTextIter *start,
+                     GtkTextIter *end,
+                     gpointer user_data)
+{
+	update_state (buffer, E_BUFFER_TAGGER_STATE_INSDEL, TRUE);
+	remove_tag_if_present (buffer, start);
+	remove_tag_if_present (buffer, end);
+}
+
+static void
+buffer_cursor_position (GtkTextBuffer *buffer,
+                        gpointer user_data)
+{
+	guint32 state;
+
+	state = get_state (buffer);
+	if (state & E_BUFFER_TAGGER_STATE_INSDEL) {
+		state = (state & (~E_BUFFER_TAGGER_STATE_INSDEL)) | E_BUFFER_TAGGER_STATE_CHANGED;
+	} else {
+		if (state & E_BUFFER_TAGGER_STATE_CHANGED) {
+			markup_text (buffer);
+		}
+
+		state = state & (~ (E_BUFFER_TAGGER_STATE_CHANGED | E_BUFFER_TAGGER_STATE_INSDEL));
+	}
+
+	set_state (buffer, state);
+}
+
+static void
+update_mouse_cursor (GtkTextView *text_view,
+                     gint x,
+                     gint y)
+{
+	static GdkCursor *hand_cursor = NULL;
+	static GdkCursor *regular_cursor = NULL;
+	gboolean hovering = FALSE, hovering_over_link = FALSE, hovering_real;
+	guint32 state;
+	GtkTextBuffer *buffer = gtk_text_view_get_buffer (text_view);
+	GtkTextTagTable *tag_table;
+	GtkTextTag *tag;
+	GtkTextIter iter;
+
+	if (!hand_cursor) {
+		hand_cursor = gdk_cursor_new (GDK_HAND2);
+		regular_cursor = gdk_cursor_new (GDK_XTERM);
+	}
+
+	g_return_if_fail (buffer != NULL);
+
+	tag_table = gtk_text_buffer_get_tag_table (buffer);
+	tag = gtk_text_tag_table_lookup (tag_table, E_BUFFER_TAGGER_LINK_TAG);
+	g_return_if_fail (tag != NULL);
+
+	state = get_state (buffer);
+
+	gtk_text_view_get_iter_at_location (text_view, &iter, x, y);
+	hovering_real = gtk_text_iter_has_tag (&iter, tag);
+
+	hovering_over_link = (state & E_BUFFER_TAGGER_STATE_IS_HOVERING) != 0;
+	if ((state & E_BUFFER_TAGGER_STATE_CTRL_DOWN) == 0) {
+		hovering = FALSE;
+	} else {
+		hovering = hovering_real;
+	}
+
+	if (hovering != hovering_over_link) {
+		update_state (buffer, E_BUFFER_TAGGER_STATE_IS_HOVERING, hovering);
+
+		if (hovering && gtk_widget_has_focus (GTK_WIDGET (text_view)))
+			gdk_window_set_cursor (gtk_text_view_get_window (text_view, GTK_TEXT_WINDOW_TEXT), hand_cursor);
+		else
+			gdk_window_set_cursor (gtk_text_view_get_window (text_view, GTK_TEXT_WINDOW_TEXT), regular_cursor);
+
+		/* XXX Is this necessary?  Appears to be a no-op. */
+		get_pointer_position (text_view, NULL, NULL);
+	}
+
+	hovering_over_link = (state & E_BUFFER_TAGGER_STATE_IS_HOVERING_TOOLTIP) != 0;
+
+	if (hovering_real != hovering_over_link) {
+		update_state (buffer, E_BUFFER_TAGGER_STATE_IS_HOVERING_TOOLTIP, hovering_real);
+
+		gtk_widget_trigger_tooltip_query (GTK_WIDGET (text_view));
+	}
+}
+
+static gboolean
+textview_query_tooltip (GtkTextView *text_view,
+                        gint x,
+                        gint y,
+                        gboolean keyboard_mode,
+                        GtkTooltip *tooltip,
+                        gpointer user_data)
+{
+	GtkTextBuffer *buffer;
+	guint32 state;
+	gboolean res = FALSE;
+
+	if (keyboard_mode)
+		return FALSE;
+
+	buffer = gtk_text_view_get_buffer (text_view);
+	g_return_val_if_fail (buffer != NULL, FALSE);
+
+	state = get_state (buffer);
+
+	if ((state & E_BUFFER_TAGGER_STATE_IS_HOVERING_TOOLTIP) != 0) {
+		gchar *url;
+		GtkTextIter iter;
+
+		gtk_text_view_window_to_buffer_coords (
+			text_view,
+			GTK_TEXT_WINDOW_WIDGET,
+			x, y, &x, &y);
+		gtk_text_view_get_iter_at_location (text_view, &iter, x, y);
+
+		url = get_url_at_iter (buffer, &iter);
+		res = url && *url;
+
+		if (res) {
+			gchar *str;
+
+			/* To Translators: The text is concatenated to a form: "Ctrl-click to open a link http://www.example.com"; */
+			str = g_strconcat (_("Ctrl-click to open a link"), " ", url, NULL);
+			gtk_tooltip_set_text (tooltip, str);
+			g_free (str);
+		}
+
+		g_free (url);
+	}
+
+	return res;
+}
+
+/* Links can be activated by pressing Enter. */
+static gboolean
+textview_key_press_event (GtkWidget *text_view,
+                          GdkEventKey *event)
+{
+	GtkTextIter iter;
+	GtkTextBuffer *buffer;
+
+	if ((event->state & GDK_CONTROL_MASK) == 0)
+		return FALSE;
+
+	switch (event->keyval) {
+	case GDK_KEY_Return:
+	case GDK_KEY_KP_Enter:
+		buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (text_view));
+		gtk_text_buffer_get_iter_at_mark (buffer, &iter, gtk_text_buffer_get_insert (buffer));
+		if (invoke_link_if_present (buffer, &iter))
+			return TRUE;
+		break;
+
+	default:
+		break;
+	}
+
+	return FALSE;
+}
+
+static void
+update_ctrl_state (GtkTextView *textview,
+                   gboolean ctrl_is_down)
+{
+	GtkTextBuffer *buffer;
+	gint x, y;
+
+	buffer = gtk_text_view_get_buffer (textview);
+	if (buffer) {
+		if (((get_state (buffer) & E_BUFFER_TAGGER_STATE_CTRL_DOWN) != 0) != (ctrl_is_down != FALSE)) {
+			update_state (buffer, E_BUFFER_TAGGER_STATE_CTRL_DOWN, ctrl_is_down != FALSE);
+		}
+
+		get_pointer_position (textview, &x, &y);
+		gtk_text_view_window_to_buffer_coords (textview, GTK_TEXT_WINDOW_WIDGET, x, y, &x, &y);
+		update_mouse_cursor (textview, x, y);
+	}
+}
+
+/* Links can also be activated by clicking. */
+static gboolean
+textview_event_after (GtkTextView *textview,
+                      GdkEvent *event)
+{
+	GtkTextIter start, end, iter;
+	GtkTextBuffer *buffer;
+	gint x, y;
+	GdkModifierType mt = 0;
+	guint event_button = 0;
+	gdouble event_x_win = 0;
+	gdouble event_y_win = 0;
+
+	g_return_val_if_fail (GTK_IS_TEXT_VIEW (textview), FALSE);
+
+	if (event->type == GDK_KEY_PRESS || event->type == GDK_KEY_RELEASE) {
+		guint event_keyval = 0;
+
+		gdk_event_get_keyval (event, &event_keyval);
+
+		switch (event_keyval) {
+			case GDK_KEY_Control_L:
+			case GDK_KEY_Control_R:
+				update_ctrl_state (
+					textview,
+					event->type == GDK_KEY_PRESS);
+				break;
+		}
+
+		return FALSE;
+	}
+
+	if (!gdk_event_get_state (event, &mt)) {
+		GdkWindow *window;
+		GdkDisplay *display;
+		GdkDeviceManager *device_manager;
+		GdkDevice *device;
+
+		window = gtk_widget_get_parent_window (GTK_WIDGET (textview));
+		display = gdk_window_get_display (window);
+		device_manager = gdk_display_get_device_manager (display);
+		device = gdk_device_manager_get_client_pointer (device_manager);
+
+		gdk_window_get_device_position (window, device, NULL, NULL, &mt);
+	}
+
+	update_ctrl_state (textview, (mt & GDK_CONTROL_MASK) != 0);
+
+	if (event->type != GDK_BUTTON_RELEASE)
+		return FALSE;
+
+	gdk_event_get_button (event, &event_button);
+	gdk_event_get_coords (event, &event_x_win, &event_y_win);
+
+	if (event_button != 1 || (mt & GDK_CONTROL_MASK) == 0)
+		return FALSE;
+
+	buffer = gtk_text_view_get_buffer (textview);
+
+	/* we shouldn't follow a link if the user has selected something */
+	gtk_text_buffer_get_selection_bounds (buffer, &start, &end);
+	if (gtk_text_iter_get_offset (&start) != gtk_text_iter_get_offset (&end))
+		return FALSE;
+
+	gtk_text_view_window_to_buffer_coords (
+		textview,
+		GTK_TEXT_WINDOW_WIDGET,
+		event_x_win, event_y_win, &x, &y);
+
+	gtk_text_view_get_iter_at_location (textview, &iter, x, y);
+
+	invoke_link_if_present (buffer, &iter);
+	update_mouse_cursor (textview, x, y);
+
+	return FALSE;
+}
+
+static gboolean
+textview_motion_notify_event (GtkTextView *textview,
+                              GdkEventMotion *event)
+{
+	gint x, y;
+
+	g_return_val_if_fail (GTK_IS_TEXT_VIEW (textview), FALSE);
+
+	gtk_text_view_window_to_buffer_coords (
+		textview,
+		GTK_TEXT_WINDOW_WIDGET,
+		event->x, event->y, &x, &y);
+
+	update_mouse_cursor (textview, x, y);
+
+	return FALSE;
+}
+
+static gboolean
+textview_visibility_notify_event (GtkTextView *textview,
+                                  GdkEventVisibility *event)
+{
+	gint wx, wy, bx, by;
+
+	g_return_val_if_fail (GTK_IS_TEXT_VIEW (textview), FALSE);
+
+	get_pointer_position (textview, &wx, &wy);
+
+	gtk_text_view_window_to_buffer_coords (
+		textview,
+		GTK_TEXT_WINDOW_WIDGET,
+		wx, wy, &bx, &by);
+
+	update_mouse_cursor (textview, bx, by);
+
+	return FALSE;
+}
+
+void
+e_buffer_tagger_connect (GtkTextView *textview)
+{
+	GtkTextBuffer *buffer;
+	GtkTextTagTable *tag_table;
+	GtkTextTag *tag;
+
+	init_magic_links ();
+
+	g_return_if_fail (textview != NULL);
+	g_return_if_fail (GTK_IS_TEXT_VIEW (textview));
+
+	buffer = gtk_text_view_get_buffer (textview);
+	tag_table = gtk_text_buffer_get_tag_table (buffer);
+	tag = gtk_text_tag_table_lookup (tag_table, E_BUFFER_TAGGER_LINK_TAG);
+
+	/* if tag is there already, then it is connected, thus claim */
+	g_return_if_fail (tag == NULL);
+
+	gtk_text_buffer_create_tag (
+		buffer, E_BUFFER_TAGGER_LINK_TAG,
+		"foreground", "blue",
+		"underline", PANGO_UNDERLINE_SINGLE,
+		NULL);
+
+	set_state (buffer, E_BUFFER_TAGGER_STATE_NONE);
+
+	g_signal_connect (
+		buffer, "insert-text",
+		G_CALLBACK (buffer_insert_text), NULL);
+	g_signal_connect (
+		buffer, "delete-range",
+		G_CALLBACK (buffer_delete_range), NULL);
+	g_signal_connect (
+		buffer, "notify::cursor-position",
+		G_CALLBACK (buffer_cursor_position), NULL);
+
+	gtk_widget_set_has_tooltip (GTK_WIDGET (textview), TRUE);
+
+	g_signal_connect (
+		textview, "query-tooltip",
+		G_CALLBACK (textview_query_tooltip), NULL);
+	g_signal_connect (
+		textview, "key-press-event",
+		G_CALLBACK (textview_key_press_event), NULL);
+	g_signal_connect (
+		textview, "event-after",
+		G_CALLBACK (textview_event_after), NULL);
+	g_signal_connect (
+		textview, "motion-notify-event",
+		G_CALLBACK (textview_motion_notify_event), NULL);
+	g_signal_connect (
+		textview, "visibility-notify-event",
+		G_CALLBACK (textview_visibility_notify_event), NULL);
+}
+
+void
+e_buffer_tagger_disconnect (GtkTextView *textview)
+{
+	GtkTextBuffer *buffer;
+	GtkTextTagTable *tag_table;
+	GtkTextTag *tag;
+
+	g_return_if_fail (textview != NULL);
+	g_return_if_fail (GTK_IS_TEXT_VIEW (textview));
+
+	buffer = gtk_text_view_get_buffer (textview);
+	tag_table = gtk_text_buffer_get_tag_table (buffer);
+	tag = gtk_text_tag_table_lookup (tag_table, E_BUFFER_TAGGER_LINK_TAG);
+
+	/* if tag is not there, then it is not connected, thus claim */
+	g_return_if_fail (tag != NULL);
+
+	gtk_text_tag_table_remove (tag_table, tag);
+
+	set_state (buffer, E_BUFFER_TAGGER_STATE_NONE);
+
+	g_signal_handlers_disconnect_by_func (buffer, G_CALLBACK (buffer_insert_text), NULL);
+	g_signal_handlers_disconnect_by_func (buffer, G_CALLBACK (buffer_delete_range), NULL);
+	g_signal_handlers_disconnect_by_func (buffer, G_CALLBACK (buffer_cursor_position), NULL);
+
+	gtk_widget_set_has_tooltip (GTK_WIDGET (textview), FALSE);
+
+	g_signal_handlers_disconnect_by_func (textview, G_CALLBACK (textview_query_tooltip), NULL);
+	g_signal_handlers_disconnect_by_func (textview, G_CALLBACK (textview_key_press_event), NULL);
+	g_signal_handlers_disconnect_by_func (textview, G_CALLBACK (textview_event_after), NULL);
+	g_signal_handlers_disconnect_by_func (textview, G_CALLBACK (textview_motion_notify_event), NULL);
+	g_signal_handlers_disconnect_by_func (textview, G_CALLBACK (textview_visibility_notify_event), NULL);
+}
+
+void
+e_buffer_tagger_update_tags (GtkTextView *textview)
+{
+	GtkTextBuffer *buffer;
+	GtkTextTagTable *tag_table;
+	GtkTextTag *tag;
+
+	g_return_if_fail (textview != NULL);
+	g_return_if_fail (GTK_IS_TEXT_VIEW (textview));
+
+	buffer = gtk_text_view_get_buffer (textview);
+	tag_table = gtk_text_buffer_get_tag_table (buffer);
+	tag = gtk_text_tag_table_lookup (tag_table, E_BUFFER_TAGGER_LINK_TAG);
+
+	/* if tag is not there, then it is not connected, thus claim */
+	g_return_if_fail (tag != NULL);
+
+	update_state (buffer, E_BUFFER_TAGGER_STATE_INSDEL | E_BUFFER_TAGGER_STATE_CHANGED, FALSE);
+
+	markup_text (buffer);
+}
diff --git a/e-util/e-buffer-tagger.h b/e-util/e-buffer-tagger.h
new file mode 100644
index 0000000..f00606e
--- /dev/null
+++ b/e-util/e-buffer-tagger.h
@@ -0,0 +1,39 @@
+/*
+ * e-buffer-tagger.h
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_BUFFER_TAGGER_H
+#define E_BUFFER_TAGGER_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+void e_buffer_tagger_connect     (GtkTextView *textview);
+void e_buffer_tagger_disconnect  (GtkTextView *textview);
+void e_buffer_tagger_update_tags (GtkTextView *textview);
+
+G_END_DECLS
+
+#endif /* E_BUFFER_TAGGER_H */
diff --git a/e-util/e-cal-source-config.c b/e-util/e-cal-source-config.c
new file mode 100644
index 0000000..e009ac6
--- /dev/null
+++ b/e-util/e-cal-source-config.c
@@ -0,0 +1,431 @@
+/*
+ * e-cal-source-config.c
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#include "e-cal-source-config.h"
+
+#include <config.h>
+#include <glib/gi18n-lib.h>
+
+#include "e-misc-utils.h"
+
+#define E_CAL_SOURCE_CONFIG_GET_PRIVATE(obj) \
+	(G_TYPE_INSTANCE_GET_PRIVATE \
+	((obj), E_TYPE_CAL_SOURCE_CONFIG, ECalSourceConfigPrivate))
+
+struct _ECalSourceConfigPrivate {
+	ECalClientSourceType source_type;
+	GtkWidget *color_button;
+	GtkWidget *default_button;
+};
+
+enum {
+	PROP_0,
+	PROP_SOURCE_TYPE
+};
+
+G_DEFINE_TYPE (
+	ECalSourceConfig,
+	e_cal_source_config,
+	E_TYPE_SOURCE_CONFIG)
+
+static ESource *
+cal_source_config_ref_default (ESourceConfig *config)
+{
+	ECalSourceConfigPrivate *priv;
+	ESourceRegistry *registry;
+
+	priv = E_CAL_SOURCE_CONFIG_GET_PRIVATE (config);
+	registry = e_source_config_get_registry (config);
+
+	if (priv->source_type == E_CAL_CLIENT_SOURCE_TYPE_EVENTS)
+		return e_source_registry_ref_default_calendar (registry);
+	else if (priv->source_type == E_CAL_CLIENT_SOURCE_TYPE_MEMOS)
+		return e_source_registry_ref_default_memo_list (registry);
+	else if (priv->source_type == E_CAL_CLIENT_SOURCE_TYPE_TASKS)
+		return e_source_registry_ref_default_task_list (registry);
+
+	g_return_val_if_reached (NULL);
+}
+
+static void
+cal_source_config_set_default (ESourceConfig *config,
+                               ESource *source)
+{
+	ECalSourceConfigPrivate *priv;
+	ESourceRegistry *registry;
+
+	priv = E_CAL_SOURCE_CONFIG_GET_PRIVATE (config);
+	registry = e_source_config_get_registry (config);
+
+	if (priv->source_type == E_CAL_CLIENT_SOURCE_TYPE_EVENTS)
+		e_source_registry_set_default_calendar (registry, source);
+	else if (priv->source_type == E_CAL_CLIENT_SOURCE_TYPE_MEMOS)
+		e_source_registry_set_default_memo_list (registry, source);
+	else if (priv->source_type == E_CAL_CLIENT_SOURCE_TYPE_TASKS)
+		e_source_registry_set_default_task_list (registry, source);
+}
+
+static void
+cal_source_config_set_source_type (ECalSourceConfig *config,
+                                   ECalClientSourceType source_type)
+{
+	config->priv->source_type = source_type;
+}
+
+static void
+cal_source_config_set_property (GObject *object,
+                                guint property_id,
+                                const GValue *value,
+                                GParamSpec *pspec)
+{
+	switch (property_id) {
+		case PROP_SOURCE_TYPE:
+			cal_source_config_set_source_type (
+				E_CAL_SOURCE_CONFIG (object),
+				g_value_get_enum (value));
+			return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+cal_source_config_get_property (GObject *object,
+                                guint property_id,
+                                GValue *value,
+                                GParamSpec *pspec)
+{
+	switch (property_id) {
+		case PROP_SOURCE_TYPE:
+			g_value_set_enum (
+				value,
+				e_cal_source_config_get_source_type (
+				E_CAL_SOURCE_CONFIG (object)));
+			return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+cal_source_config_dispose (GObject *object)
+{
+	ECalSourceConfigPrivate *priv;
+
+	priv = E_CAL_SOURCE_CONFIG_GET_PRIVATE (object);
+
+	if (priv->color_button != NULL) {
+		g_object_unref (priv->color_button);
+		priv->color_button = NULL;
+	}
+
+	if (priv->default_button != NULL) {
+		g_object_unref (priv->default_button);
+		priv->default_button = NULL;
+	}
+
+	/* Chain up to parent's dispose() method. */
+	G_OBJECT_CLASS (e_cal_source_config_parent_class)->dispose (object);
+}
+
+static void
+cal_source_config_constructed (GObject *object)
+{
+	ECalSourceConfigPrivate *priv;
+	ESource *default_source;
+	ESource *original_source;
+	ESourceConfig *config;
+	GObjectClass *class;
+	GtkWidget *widget;
+	const gchar *label;
+
+	/* Chain up to parent's constructed() method. */
+	class = G_OBJECT_CLASS (e_cal_source_config_parent_class);
+	class->constructed (object);
+
+	config = E_SOURCE_CONFIG (object);
+	priv = E_CAL_SOURCE_CONFIG_GET_PRIVATE (object);
+
+	widget = gtk_color_button_new ();
+	priv->color_button = g_object_ref_sink (widget);
+	gtk_widget_show (widget);
+
+	switch (priv->source_type) {
+		case E_CAL_CLIENT_SOURCE_TYPE_EVENTS:
+			label = _("Mark as default calendar");
+			break;
+		case E_CAL_CLIENT_SOURCE_TYPE_TASKS:
+			label = _("Mark as default task list");
+			break;
+		case E_CAL_CLIENT_SOURCE_TYPE_MEMOS:
+			label = _("Mark as default memo list");
+			break;
+		default:
+			/* No need to translate this string. */
+			label = "Invalid ECalSourceType value";
+			g_warn_if_reached ();
+	}
+
+	widget = gtk_check_button_new_with_label (label);
+	priv->default_button = g_object_ref_sink (widget);
+	gtk_widget_show (widget);
+
+	default_source = cal_source_config_ref_default (config);
+	original_source = e_source_config_get_original_source (config);
+
+	if (original_source != NULL) {
+		gboolean active;
+
+		active = e_source_equal (original_source, default_source);
+		g_object_set (priv->default_button, "active", active, NULL);
+	}
+
+	g_object_unref (default_source);
+
+	e_source_config_insert_widget (
+		config, NULL, _("Color:"), priv->color_button);
+
+	e_source_config_insert_widget (
+		config, NULL, NULL, priv->default_button);
+}
+
+static const gchar *
+cal_source_config_get_backend_extension_name (ESourceConfig *config)
+{
+	ECalSourceConfig *cal_config;
+	const gchar *extension_name;
+
+	cal_config = E_CAL_SOURCE_CONFIG (config);
+
+	switch (e_cal_source_config_get_source_type (cal_config)) {
+		case E_CAL_CLIENT_SOURCE_TYPE_EVENTS:
+			extension_name = E_SOURCE_EXTENSION_CALENDAR;
+			break;
+		case E_CAL_CLIENT_SOURCE_TYPE_TASKS:
+			extension_name = E_SOURCE_EXTENSION_TASK_LIST;
+			break;
+		case E_CAL_CLIENT_SOURCE_TYPE_MEMOS:
+			extension_name = E_SOURCE_EXTENSION_MEMO_LIST;
+			break;
+		default:
+			g_return_val_if_reached (NULL);
+	}
+
+	return extension_name;
+}
+
+static GList *
+cal_source_config_list_eligible_collections (ESourceConfig *config)
+{
+	GQueue trash = G_QUEUE_INIT;
+	GList *list, *link;
+
+	/* Chain up to parent's list_eligible_collections() method. */
+	list = E_SOURCE_CONFIG_CLASS (e_cal_source_config_parent_class)->
+		list_eligible_collections (config);
+
+	for (link = list; link != NULL; link = g_list_next (link)) {
+		ESource *source = E_SOURCE (link->data);
+		ESourceCollection *extension;
+		const gchar *extension_name;
+
+		extension_name = E_SOURCE_EXTENSION_COLLECTION;
+		extension = e_source_get_extension (source, extension_name);
+
+		if (!e_source_collection_get_calendar_enabled (extension))
+			g_queue_push_tail (&trash, link);
+	}
+
+	/* Remove ineligible collections from the list. */
+	while ((link = g_queue_pop_head (&trash)) != NULL) {
+		g_object_unref (link->data);
+		list = g_list_delete_link (list, link);
+	}
+
+	return list;
+}
+
+static void
+cal_source_config_init_candidate (ESourceConfig *config,
+                                  ESource *scratch_source)
+{
+	ECalSourceConfigPrivate *priv;
+	ESourceConfigClass *class;
+	ESourceExtension *extension;
+	const gchar *extension_name;
+
+	/* Chain up to parent's init_candidate() method. */
+	class = E_SOURCE_CONFIG_CLASS (e_cal_source_config_parent_class);
+	class->init_candidate (config, scratch_source);
+
+	priv = E_CAL_SOURCE_CONFIG_GET_PRIVATE (config);
+
+	extension_name = e_source_config_get_backend_extension_name (config);
+	extension = e_source_get_extension (scratch_source, extension_name);
+
+	g_object_bind_property_full (
+		extension, "color",
+		priv->color_button, "color",
+		G_BINDING_BIDIRECTIONAL |
+		G_BINDING_SYNC_CREATE,
+		e_binding_transform_string_to_color,
+		e_binding_transform_color_to_string,
+		NULL, (GDestroyNotify) NULL);
+}
+
+static void
+cal_source_config_commit_changes (ESourceConfig *config,
+                                  ESource *scratch_source)
+{
+	ECalSourceConfigPrivate *priv;
+	GtkToggleButton *toggle_button;
+	ESourceConfigClass *class;
+	ESource *default_source;
+
+	priv = E_CAL_SOURCE_CONFIG_GET_PRIVATE (config);
+	toggle_button = GTK_TOGGLE_BUTTON (priv->default_button);
+
+	/* Chain up to parent's commit_changes() method. */
+	class = E_SOURCE_CONFIG_CLASS (e_cal_source_config_parent_class);
+	class->commit_changes (config, scratch_source);
+
+	default_source = cal_source_config_ref_default (config);
+
+	/* The default setting is a little tricky to get right.  If
+	 * the toggle button is active, this ESource is now the default.
+	 * That much is simple.  But if the toggle button is NOT active,
+	 * then we have to inspect the old default.  If this ESource WAS
+	 * the default, reset the default to 'system'.  If this ESource
+	 * WAS NOT the old default, leave it alone. */
+	if (gtk_toggle_button_get_active (toggle_button))
+		cal_source_config_set_default (config, scratch_source);
+	else if (e_source_equal (scratch_source, default_source))
+		cal_source_config_set_default (config, NULL);
+
+	g_object_unref (default_source);
+}
+
+static void
+e_cal_source_config_class_init (ECalSourceConfigClass *class)
+{
+	GObjectClass *object_class;
+	ESourceConfigClass *source_config_class;
+
+	g_type_class_add_private (class, sizeof (ECalSourceConfigPrivate));
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->set_property = cal_source_config_set_property;
+	object_class->get_property = cal_source_config_get_property;
+	object_class->dispose = cal_source_config_dispose;
+	object_class->constructed = cal_source_config_constructed;
+
+	source_config_class = E_SOURCE_CONFIG_CLASS (class);
+	source_config_class->get_backend_extension_name =
+		cal_source_config_get_backend_extension_name;
+	source_config_class->list_eligible_collections =
+		cal_source_config_list_eligible_collections;
+	source_config_class->init_candidate = cal_source_config_init_candidate;
+	source_config_class->commit_changes = cal_source_config_commit_changes;
+
+	g_object_class_install_property (
+		object_class,
+		PROP_SOURCE_TYPE,
+		g_param_spec_enum (
+			"source-type",
+			"Source Type",
+			"The iCalendar object type",
+			E_TYPE_CAL_CLIENT_SOURCE_TYPE,
+			E_CAL_CLIENT_SOURCE_TYPE_EVENTS,
+			G_PARAM_READWRITE |
+			G_PARAM_CONSTRUCT_ONLY |
+			G_PARAM_STATIC_STRINGS));
+}
+
+static void
+e_cal_source_config_init (ECalSourceConfig *config)
+{
+	config->priv = E_CAL_SOURCE_CONFIG_GET_PRIVATE (config);
+}
+
+GtkWidget *
+e_cal_source_config_new (ESourceRegistry *registry,
+                         ESource *original_source,
+                         ECalClientSourceType source_type)
+{
+	g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), NULL);
+
+	if (original_source != NULL)
+		g_return_val_if_fail (E_IS_SOURCE (original_source), NULL);
+
+	return g_object_new (
+		E_TYPE_CAL_SOURCE_CONFIG, "registry", registry,
+		"original-source", original_source, "source-type",
+		source_type, NULL);
+}
+
+ECalClientSourceType
+e_cal_source_config_get_source_type (ECalSourceConfig *config)
+{
+	g_return_val_if_fail (E_IS_CAL_SOURCE_CONFIG (config), 0);
+
+	return config->priv->source_type;
+}
+
+void
+e_cal_source_config_add_offline_toggle (ECalSourceConfig *config,
+                                        ESource *scratch_source)
+{
+	GtkWidget *widget;
+	ESourceExtension *extension;
+	const gchar *extension_name;
+	const gchar *label;
+
+	g_return_if_fail (E_IS_CAL_SOURCE_CONFIG (config));
+	g_return_if_fail (E_IS_SOURCE (scratch_source));
+
+	extension_name = E_SOURCE_EXTENSION_OFFLINE;
+	extension = e_source_get_extension (scratch_source, extension_name);
+
+	switch (e_cal_source_config_get_source_type (config)) {
+		case E_CAL_CLIENT_SOURCE_TYPE_EVENTS:
+			label = _("Copy calendar contents locally "
+				  "for offline operation");
+			break;
+		case E_CAL_CLIENT_SOURCE_TYPE_TASKS:
+			label = _("Copy task list contents locally "
+				  "for offline operation");
+			break;
+		case E_CAL_CLIENT_SOURCE_TYPE_MEMOS:
+			label = _("Copy memo list contents locally "
+				  "for offline operation");
+			break;
+		default:
+			g_return_if_reached ();
+	}
+
+	widget = gtk_check_button_new_with_label (label);
+	e_source_config_insert_widget (
+		E_SOURCE_CONFIG (config), scratch_source, NULL, widget);
+	gtk_widget_show (widget);
+
+	g_object_bind_property (
+		extension, "stay-synchronized",
+		widget, "active",
+		G_BINDING_BIDIRECTIONAL |
+		G_BINDING_SYNC_CREATE);
+}
diff --git a/e-util/e-cal-source-config.h b/e-util/e-cal-source-config.h
new file mode 100644
index 0000000..dc78f70
--- /dev/null
+++ b/e-util/e-cal-source-config.h
@@ -0,0 +1,76 @@
+/*
+ * e-cal-source-config.h
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_CAL_SOURCE_CONFIG_H
+#define E_CAL_SOURCE_CONFIG_H
+
+#include <libecal/libecal.h>
+#include <e-util/e-source-config.h>
+
+/* Standard GObject macros */
+#define E_TYPE_CAL_SOURCE_CONFIG \
+	(e_cal_source_config_get_type ())
+#define E_CAL_SOURCE_CONFIG(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_CAL_SOURCE_CONFIG, ECalSourceConfig))
+#define E_CAL_SOURCE_CONFIG_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_CAL_SOURCE_CONFIG, ECalSourceConfigClass))
+#define E_IS_CAL_SOURCE_CONFIG(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_CAL_SOURCE_CONFIG))
+#define E_IS_CAL_SOURCE_CONFIG_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_CAL_SOURCE_CONFIG))
+#define E_CAL_SOURCE_CONFIG_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_CAL_SOURCE_CONFIG, ECalSourceConfigClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ECalSourceConfig ECalSourceConfig;
+typedef struct _ECalSourceConfigClass ECalSourceConfigClass;
+typedef struct _ECalSourceConfigPrivate ECalSourceConfigPrivate;
+
+struct _ECalSourceConfig {
+	ESourceConfig parent;
+	ECalSourceConfigPrivate *priv;
+};
+
+struct _ECalSourceConfigClass {
+	ESourceConfigClass parent_class;
+};
+
+GType		e_cal_source_config_get_type	(void) G_GNUC_CONST;
+GtkWidget *	e_cal_source_config_new		(ESourceRegistry *registry,
+						 ESource *original_source,
+						 ECalClientSourceType source_type);
+ECalClientSourceType
+		e_cal_source_config_get_source_type
+						(ECalSourceConfig *config);
+void		e_cal_source_config_add_offline_toggle
+						(ECalSourceConfig *config,
+						 ESource *scratch_source);
+
+G_END_DECLS
+
+#endif /* E_CAL_SOURCE_CONFIG_H */
diff --git a/e-util/e-calendar-item.c b/e-util/e-calendar-item.c
new file mode 100644
index 0000000..3e77154
--- /dev/null
+++ b/e-util/e-calendar-item.c
@@ -0,0 +1,3773 @@
+/*
+ * ECalendarItem - canvas item displaying a calendar.
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ * Authors:
+ *		Damon Chaplin <damon ximian com>
+ *		Bolian Yin <bolian yin sun com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <libebackend/libebackend.h>
+
+#include "e-calendar-item.h"
+
+#include <time.h>
+#include <stdlib.h>
+#include <string.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+#include <glib/gi18n.h>
+
+#include "ea-widgets.h"
+#include "e-misc-utils.h"
+
+static const gint e_calendar_item_days_in_month[12] = {
+	31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
+};
+
+#define DAYS_IN_MONTH(year, month) \
+  e_calendar_item_days_in_month[month] + (((month) == 1 \
+  && ((year) % 4 == 0 && ((year) % 100 != 0 || (year) % 400 == 0))) ? 1 : 0)
+
+static void	e_calendar_item_dispose		(GObject *object);
+static void	e_calendar_item_get_property	(GObject *object,
+						 guint property_id,
+						 GValue *value,
+						 GParamSpec *pspec);
+static void	e_calendar_item_set_property	(GObject *object,
+						 guint property_id,
+						 const GValue *value,
+						 GParamSpec *pspec);
+static void	e_calendar_item_realize		(GnomeCanvasItem *item);
+static void	e_calendar_item_unmap		(GnomeCanvasItem *item);
+static void	e_calendar_item_update		(GnomeCanvasItem *item,
+						 const cairo_matrix_t *i2c,
+						 gint flags);
+static void	e_calendar_item_draw		(GnomeCanvasItem *item,
+						 cairo_t *cr,
+						 gint x,
+						 gint y,
+						 gint width,
+						 gint height);
+static void	e_calendar_item_draw_month	(ECalendarItem *calitem,
+						 cairo_t *cr,
+						 gint x,
+						 gint y,
+						 gint width,
+						 gint height,
+						 gint row,
+						 gint col);
+static void	e_calendar_item_draw_day_numbers
+						(ECalendarItem *calitem,
+						 cairo_t *cr,
+						 gint width,
+						 gint height,
+						 gint row,
+						 gint col,
+						 gint year,
+						 gint month,
+						 gint start_weekday,
+						 gint cells_x,
+						 gint cells_y);
+static GnomeCanvasItem *e_calendar_item_point	(GnomeCanvasItem *item,
+						 gdouble x,
+						 gdouble y,
+						 gint cx,
+						 gint cy);
+static void	e_calendar_item_stop_selecting	(ECalendarItem *calitem,
+						 guint32 time);
+static void	e_calendar_item_selection_add_days
+						(ECalendarItem *calitem,
+						 gint n_days,
+						 gboolean multi_selection);
+static gint	e_calendar_item_key_press_event	(ECalendarItem *item,
+						 GdkEvent *event);
+static gint	e_calendar_item_event		(GnomeCanvasItem *item,
+						 GdkEvent *event);
+static void	e_calendar_item_bounds		(GnomeCanvasItem *item,
+						 gdouble *x1,
+						 gdouble *y1,
+						 gdouble *x2,
+						 gdouble *y2);
+
+static gboolean	e_calendar_item_button_press	(ECalendarItem *calitem,
+						 GdkEvent *event);
+static gboolean	e_calendar_item_button_release	(ECalendarItem *calitem,
+						 GdkEvent *event);
+static gboolean	e_calendar_item_motion		(ECalendarItem *calitem,
+						 GdkEvent *event);
+
+static gboolean	e_calendar_item_convert_position_to_day
+						(ECalendarItem *calitem,
+						 gint x,
+						 gint y,
+						 gboolean round_empty_positions,
+						 gint *month_offset,
+						 gint *day,
+						 gboolean *entire_week);
+static void	e_calendar_item_get_month_info	(ECalendarItem *calitem,
+						 gint row,
+						 gint col,
+						 gint *first_day_offset,
+						 gint *days_in_month,
+						 gint *days_in_prev_month);
+static void	e_calendar_item_recalc_sizes	(ECalendarItem *calitem);
+
+static void	e_calendar_item_get_day_style	(ECalendarItem *calitem,
+						 gint year,
+						 gint month,
+						 gint day,
+						 gint day_style,
+						 gboolean today,
+						 gboolean prev_or_next_month,
+						 gboolean selected,
+						 gboolean has_focus,
+						 gboolean drop_target,
+						 GdkColor **bg_color,
+						 GdkColor **fg_color,
+						 GdkColor **box_color,
+						 gboolean *bold,
+						 gboolean *italic);
+static void	e_calendar_item_check_selection_end
+						(ECalendarItem *calitem,
+						 gint start_month,
+						 gint start_day,
+						 gint *end_month,
+						 gint *end_day);
+static void	e_calendar_item_check_selection_start
+						(ECalendarItem *calitem,
+						 gint *start_month,
+						 gint *start_day,
+						 gint end_month,
+						 gint end_day);
+static void	e_calendar_item_add_days_to_selection
+						(ECalendarItem *calitem,
+						 gint days);
+static void	e_calendar_item_round_up_selection
+						(ECalendarItem *calitem,
+						 gint *month_offset,
+						 gint *day);
+static void	e_calendar_item_round_down_selection
+						(ECalendarItem *calitem,
+						 gint *month_offset,
+						 gint *day);
+static gint	e_calendar_item_get_inclusive_days
+						(ECalendarItem *calitem,
+						 gint start_month_offset,
+						 gint start_day,
+						 gint end_month_offset,
+						 gint end_day);
+static void	e_calendar_item_ensure_valid_day
+						(ECalendarItem *calitem,
+						 gint *month_offset,
+						 gint *day);
+static gboolean	e_calendar_item_ensure_days_visible
+						(ECalendarItem *calitem,
+						 gint start_year,
+						 gint start_month,
+						 gint start_day,
+						 gint end_year,
+						 gint end_month,
+						 gint end_day,
+						 gboolean emission);
+static void	e_calendar_item_show_popup_menu	(ECalendarItem *calitem,
+						 GdkEvent *button_event,
+						 gint month_offset);
+static void	e_calendar_item_on_menu_item_activate
+						(GtkWidget *menuitem,
+						 ECalendarItem *calitem);
+static void	e_calendar_item_position_menu	(GtkMenu *menu,
+						 gint *x,
+						 gint *y,
+						 gboolean *push_in,
+						 gpointer user_data);
+static void	e_calendar_item_date_range_changed
+						(ECalendarItem *calitem);
+static void	e_calendar_item_queue_signal_emission
+						(ECalendarItem *calitem);
+static gboolean	e_calendar_item_signal_emission_idle_cb
+						(gpointer data);
+static void	e_calendar_item_set_selection_if_emission
+						(ECalendarItem *calitem,
+						 const GDate *start_date,
+						 const GDate *end_date,
+						 gboolean emission);
+
+/* Our arguments. */
+enum {
+	PROP_0,
+	PROP_YEAR,
+	PROP_MONTH,
+	PROP_X1,
+	PROP_Y1,
+	PROP_X2,
+	PROP_Y2,
+	PROP_FONT_DESC,
+	PROP_WEEK_NUMBER_FONT,
+	PROP_WEEK_NUMBER_FONT_DESC,
+	PROP_ROW_HEIGHT,
+	PROP_COLUMN_WIDTH,
+	PROP_MINIMUM_ROWS,
+	PROP_MINIMUM_COLUMNS,
+	PROP_MAXIMUM_ROWS,
+	PROP_MAXIMUM_COLUMNS,
+	PROP_WEEK_START_DAY,
+	PROP_SHOW_WEEK_NUMBERS,
+	PROP_KEEP_WDAYS_ON_WEEKNUM_CLICK,
+	PROP_MAXIMUM_DAYS_SELECTED,
+	PROP_DAYS_TO_START_WEEK_SELECTION,
+	PROP_MOVE_SELECTION_WHEN_MOVING,
+	PROP_PRESERVE_DAY_WHEN_MOVING,
+	PROP_DISPLAY_POPUP
+};
+
+enum {
+  DATE_RANGE_CHANGED,
+  SELECTION_CHANGED,
+  SELECTION_PREVIEW_CHANGED,
+  LAST_SIGNAL
+};
+
+static guint e_calendar_item_signals[LAST_SIGNAL] = { 0 };
+
+G_DEFINE_TYPE_WITH_CODE (
+	ECalendarItem,
+	e_calendar_item,
+	GNOME_TYPE_CANVAS_ITEM,
+	G_IMPLEMENT_INTERFACE (
+		E_TYPE_EXTENSIBLE, NULL))
+
+static void
+e_calendar_item_class_init (ECalendarItemClass *class)
+{
+	GObjectClass  *object_class;
+	GnomeCanvasItemClass *item_class;
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->dispose = e_calendar_item_dispose;
+	object_class->get_property = e_calendar_item_get_property;
+	object_class->set_property = e_calendar_item_set_property;
+
+	item_class = GNOME_CANVAS_ITEM_CLASS (class);
+	item_class->realize = e_calendar_item_realize;
+	item_class->unmap = e_calendar_item_unmap;
+	item_class->update = e_calendar_item_update;
+	item_class->draw = e_calendar_item_draw;
+	item_class->point = e_calendar_item_point;
+	item_class->event = e_calendar_item_event;
+	item_class->bounds = e_calendar_item_bounds;
+
+	class->date_range_changed = NULL;
+	class->selection_changed = NULL;
+	class->selection_preview_changed = NULL;
+
+	g_object_class_install_property (
+		object_class,
+		PROP_YEAR,
+		g_param_spec_int (
+			"year",
+			NULL,
+			NULL,
+			G_MININT,
+			G_MAXINT,
+			0,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_MONTH,
+		g_param_spec_int (
+			"month",
+			NULL,
+			NULL,
+			G_MININT,
+			G_MAXINT,
+			0,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_X1,
+		g_param_spec_double (
+			"x1",
+			NULL,
+			NULL,
+			-G_MAXDOUBLE,
+			G_MAXDOUBLE,
+			0.,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_Y1,
+		g_param_spec_double (
+			"y1",
+			NULL,
+			NULL,
+			-G_MAXDOUBLE,
+			G_MAXDOUBLE,
+			0.,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_X2,
+		g_param_spec_double (
+			"x2",
+			NULL,
+			NULL,
+			-G_MAXDOUBLE,
+			G_MAXDOUBLE,
+			0.,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_Y2,
+		g_param_spec_double (
+			"y2",
+			NULL,
+			NULL,
+			-G_MAXDOUBLE,
+			G_MAXDOUBLE,
+			0.,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_FONT_DESC,
+		g_param_spec_boxed (
+			"font_desc",
+			NULL,
+			NULL,
+			PANGO_TYPE_FONT_DESCRIPTION,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_WEEK_NUMBER_FONT_DESC,
+		g_param_spec_boxed (
+			"week_number_font_desc",
+			NULL,
+			NULL,
+			PANGO_TYPE_FONT_DESCRIPTION,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_ROW_HEIGHT,
+		g_param_spec_int (
+			"row_height",
+			NULL,
+			NULL,
+			G_MININT,
+			G_MAXINT,
+			0,
+			G_PARAM_READABLE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_COLUMN_WIDTH,
+		g_param_spec_int (
+			"column_width",
+			NULL,
+			NULL,
+			G_MININT,
+			G_MAXINT,
+			0,
+			G_PARAM_READABLE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_MINIMUM_ROWS,
+		g_param_spec_int (
+			"minimum_rows",
+			NULL,
+			NULL,
+			G_MININT,
+			G_MAXINT,
+			0,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_MINIMUM_COLUMNS,
+		g_param_spec_int (
+			"minimum_columns",
+			NULL,
+			NULL,
+			G_MININT,
+			G_MAXINT,
+			0,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_MAXIMUM_ROWS,
+		g_param_spec_int (
+			"maximum_rows",
+			NULL,
+			NULL,
+			G_MININT,
+			G_MAXINT,
+			0,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_MAXIMUM_COLUMNS,
+		g_param_spec_int (
+			"maximum_columns",
+			NULL,
+			NULL,
+			G_MININT,
+			G_MAXINT,
+			0,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_WEEK_START_DAY,
+		g_param_spec_int (
+			"week_start_day",
+			NULL,
+			NULL,
+			G_MININT,
+			G_MAXINT,
+			0,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_SHOW_WEEK_NUMBERS,
+		g_param_spec_boolean (
+			"show_week_numbers",
+			NULL,
+			NULL,
+			TRUE,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_KEEP_WDAYS_ON_WEEKNUM_CLICK,
+		g_param_spec_boolean (
+			"keep_wdays_on_weeknum_click",
+			NULL,
+			NULL,
+			FALSE,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_MAXIMUM_DAYS_SELECTED,
+		g_param_spec_int (
+			"maximum_days_selected",
+			NULL,
+			NULL,
+			G_MININT,
+			G_MAXINT,
+			0,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_DAYS_TO_START_WEEK_SELECTION,
+		g_param_spec_int (
+			"days_to_start_week_selection",
+			NULL,
+			NULL,
+			G_MININT,
+			G_MAXINT,
+			0,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_MOVE_SELECTION_WHEN_MOVING,
+		g_param_spec_boolean (
+			"move_selection_when_moving",
+			NULL,
+			NULL,
+			TRUE,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_PRESERVE_DAY_WHEN_MOVING,
+		g_param_spec_boolean (
+			"preserve_day_when_moving",
+			NULL,
+			NULL,
+			TRUE,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_DISPLAY_POPUP,
+		g_param_spec_boolean (
+			"display_popup",
+			NULL,
+			NULL,
+			TRUE,
+			G_PARAM_READWRITE));
+
+	e_calendar_item_signals[DATE_RANGE_CHANGED] = g_signal_new (
+		"date_range_changed",
+		G_TYPE_FROM_CLASS (object_class),
+		G_SIGNAL_RUN_FIRST,
+		G_STRUCT_OFFSET (ECalendarItemClass, date_range_changed),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__VOID,
+		G_TYPE_NONE, 0);
+
+	e_calendar_item_signals[SELECTION_CHANGED] = g_signal_new (
+		"selection_changed",
+		G_TYPE_FROM_CLASS (object_class),
+		G_SIGNAL_RUN_FIRST,
+		G_STRUCT_OFFSET (ECalendarItemClass, selection_changed),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__VOID,
+		G_TYPE_NONE, 0);
+
+	e_calendar_item_signals[SELECTION_PREVIEW_CHANGED] = g_signal_new (
+		"selection_preview_changed",
+		G_TYPE_FROM_CLASS (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ECalendarItemClass, selection_preview_changed),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__VOID,
+		G_TYPE_NONE, 0);
+
+	e_calendar_item_a11y_init ();
+}
+
+static void
+e_calendar_item_init (ECalendarItem *calitem)
+{
+	struct tm *tmp_tm;
+	time_t t;
+
+	/* Set the default time to the current month. */
+	t = time (NULL);
+	tmp_tm = localtime (&t);
+	calitem->year = tmp_tm->tm_year + 1900;
+	calitem->month = tmp_tm->tm_mon;
+
+	calitem->styles = NULL;
+
+	calitem->min_cols = 1;
+	calitem->min_rows = 1;
+	calitem->max_cols = -1;
+	calitem->max_rows = -1;
+
+	calitem->rows = 0;
+	calitem->cols = 0;
+
+	calitem->show_week_numbers = FALSE;
+	calitem->keep_wdays_on_weeknum_click = FALSE;
+	calitem->week_start_day = 0;
+	calitem->expand = TRUE;
+	calitem->max_days_selected = 1;
+	calitem->days_to_start_week_selection = -1;
+	calitem->move_selection_when_moving = TRUE;
+	calitem->preserve_day_when_moving = FALSE;
+	calitem->display_popup = TRUE;
+
+	calitem->x1 = 0.0;
+	calitem->y1 = 0.0;
+	calitem->x2 = 0.0;
+	calitem->y2 = 0.0;
+
+	calitem->selecting = FALSE;
+	calitem->selecting_axis = NULL;
+
+	calitem->selection_set = FALSE;
+
+	calitem->selection_changed = FALSE;
+	calitem->date_range_changed = FALSE;
+
+	calitem->style_callback = NULL;
+	calitem->style_callback_data = NULL;
+	calitem->style_callback_destroy = NULL;
+
+	calitem->time_callback = NULL;
+	calitem->time_callback_data = NULL;
+	calitem->time_callback_destroy = NULL;
+
+	calitem->signal_emission_idle_id = 0;
+}
+
+static void
+e_calendar_item_dispose (GObject *object)
+{
+	ECalendarItem *calitem;
+
+	calitem = E_CALENDAR_ITEM (object);
+
+	e_calendar_item_set_style_callback (calitem, NULL, NULL, NULL);
+	e_calendar_item_set_get_time_callback (calitem, NULL, NULL, NULL);
+
+	if (calitem->styles) {
+		g_free (calitem->styles);
+		calitem->styles = NULL;
+	}
+
+	if (calitem->signal_emission_idle_id > 0) {
+		g_source_remove (calitem->signal_emission_idle_id);
+		calitem->signal_emission_idle_id = -1;
+	}
+
+	if (calitem->font_desc) {
+		pango_font_description_free (calitem->font_desc);
+		calitem->font_desc = NULL;
+	}
+
+	if (calitem->week_number_font_desc) {
+		pango_font_description_free (calitem->week_number_font_desc);
+		calitem->week_number_font_desc = NULL;
+	}
+
+	if (calitem->selecting_axis)
+		g_free (calitem->selecting_axis);
+
+	G_OBJECT_CLASS (e_calendar_item_parent_class)->dispose (object);
+}
+
+static void
+e_calendar_item_get_property (GObject *object,
+                              guint property_id,
+                              GValue *value,
+                              GParamSpec *pspec)
+{
+	ECalendarItem *calitem;
+
+	calitem = E_CALENDAR_ITEM (object);
+
+	switch (property_id) {
+	case PROP_YEAR:
+		g_value_set_int (value, calitem->year);
+		return;
+	case PROP_MONTH:
+		g_value_set_int (value, calitem->month);
+		return;
+	case PROP_X1:
+		g_value_set_double (value, calitem->x1);
+		return;
+	case PROP_Y1:
+		g_value_set_double (value, calitem->y1);
+		return;
+	case PROP_X2:
+		g_value_set_double (value, calitem->x2);
+		return;
+	case PROP_Y2:
+		g_value_set_double (value, calitem->y2);
+		return;
+	case PROP_FONT_DESC:
+		g_value_set_boxed (value, calitem->font_desc);
+		return;
+	case PROP_WEEK_NUMBER_FONT_DESC:
+		g_value_set_boxed (value, calitem->week_number_font_desc);
+		return;
+	case PROP_ROW_HEIGHT:
+		e_calendar_item_recalc_sizes (calitem);
+		g_value_set_int (value, calitem->min_month_height);
+		return;
+	case PROP_COLUMN_WIDTH:
+		e_calendar_item_recalc_sizes (calitem);
+		g_value_set_int (value, calitem->min_month_width);
+		return;
+	case PROP_MINIMUM_ROWS:
+		g_value_set_int (value, calitem->min_rows);
+		return;
+	case PROP_MINIMUM_COLUMNS:
+		g_value_set_int (value, calitem->min_cols);
+		return;
+	case PROP_MAXIMUM_ROWS:
+		g_value_set_int (value, calitem->max_rows);
+		return;
+	case PROP_MAXIMUM_COLUMNS:
+		g_value_set_int (value, calitem->max_cols);
+		return;
+	case PROP_WEEK_START_DAY:
+		g_value_set_int (value, calitem->week_start_day);
+		return;
+	case PROP_SHOW_WEEK_NUMBERS:
+		g_value_set_boolean (value, calitem->show_week_numbers);
+		return;
+	case PROP_KEEP_WDAYS_ON_WEEKNUM_CLICK:
+		g_value_set_boolean (value, calitem->keep_wdays_on_weeknum_click);
+		return;
+	case PROP_MAXIMUM_DAYS_SELECTED:
+		g_value_set_int (value, e_calendar_item_get_max_days_sel (calitem));
+		return;
+	case PROP_DAYS_TO_START_WEEK_SELECTION:
+		g_value_set_int (value, e_calendar_item_get_days_start_week_sel (calitem));
+		return;
+	case PROP_MOVE_SELECTION_WHEN_MOVING:
+		g_value_set_boolean (value, calitem->move_selection_when_moving);
+		return;
+	case PROP_PRESERVE_DAY_WHEN_MOVING:
+		g_value_set_boolean (value, calitem->preserve_day_when_moving);
+		return;
+	case PROP_DISPLAY_POPUP:
+		g_value_set_boolean (value, e_calendar_item_get_display_popup (calitem));
+		return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+e_calendar_item_set_property (GObject *object,
+                              guint property_id,
+                              const GValue *value,
+                              GParamSpec *pspec)
+{
+	GnomeCanvasItem *item;
+	ECalendarItem *calitem;
+	PangoFontDescription *font_desc;
+	gdouble dvalue;
+	gint ivalue;
+	gboolean bvalue;
+
+	item = GNOME_CANVAS_ITEM (object);
+	calitem = E_CALENDAR_ITEM (object);
+
+	switch (property_id) {
+	case PROP_YEAR:
+		ivalue = g_value_get_int (value);
+		e_calendar_item_set_first_month (
+			calitem, ivalue, calitem->month);
+		return;
+	case PROP_MONTH:
+		ivalue = g_value_get_int (value);
+		e_calendar_item_set_first_month (
+			calitem, calitem->year, ivalue);
+		return;
+	case PROP_X1:
+		dvalue = g_value_get_double (value);
+		if (calitem->x1 != dvalue) {
+			calitem->x1 = dvalue;
+			gnome_canvas_item_request_update (item);
+		}
+		return;
+	case PROP_Y1:
+		dvalue = g_value_get_double (value);
+		if (calitem->y1 != dvalue) {
+			calitem->y1 = dvalue;
+			gnome_canvas_item_request_update (item);
+		}
+		return;
+	case PROP_X2:
+		dvalue = g_value_get_double (value);
+		if (calitem->x2 != dvalue) {
+			calitem->x2 = dvalue;
+			gnome_canvas_item_request_update (item);
+		}
+		return;
+	case PROP_Y2:
+		dvalue = g_value_get_double (value);
+		if (calitem->y2 != dvalue) {
+			calitem->y2 = dvalue;
+			gnome_canvas_item_request_update (item);
+		}
+		return;
+	case PROP_FONT_DESC:
+		font_desc = g_value_get_boxed (value);
+		if (calitem->font_desc)
+			pango_font_description_free (calitem->font_desc);
+		calitem->font_desc = pango_font_description_copy (font_desc);
+		gnome_canvas_item_request_update (item);
+		return;
+	case PROP_WEEK_NUMBER_FONT_DESC:
+		font_desc = g_value_get_boxed (value);
+		if (calitem->week_number_font_desc)
+			pango_font_description_free (calitem->week_number_font_desc);
+		calitem->week_number_font_desc = pango_font_description_copy (font_desc);
+		gnome_canvas_item_request_update (item);
+		return;
+	case PROP_MINIMUM_ROWS:
+		ivalue = g_value_get_int (value);
+		ivalue = MAX (1, ivalue);
+		if (calitem->min_rows != ivalue) {
+			calitem->min_rows = ivalue;
+			gnome_canvas_item_request_update (item);
+		}
+		return;
+	case PROP_MINIMUM_COLUMNS:
+		ivalue = g_value_get_int (value);
+		ivalue = MAX (1, ivalue);
+		if (calitem->min_cols != ivalue) {
+			calitem->min_cols = ivalue;
+			gnome_canvas_item_request_update (item);
+		}
+		return;
+	case PROP_MAXIMUM_ROWS:
+		ivalue = g_value_get_int (value);
+		if (calitem->max_rows != ivalue) {
+			calitem->max_rows = ivalue;
+			gnome_canvas_item_request_update (item);
+		}
+		return;
+	case PROP_MAXIMUM_COLUMNS:
+		ivalue = g_value_get_int (value);
+		if (calitem->max_cols != ivalue) {
+			calitem->max_cols = ivalue;
+			gnome_canvas_item_request_update (item);
+		}
+		return;
+	case PROP_WEEK_START_DAY:
+		ivalue = g_value_get_int (value);
+		if (calitem->week_start_day != ivalue) {
+			calitem->week_start_day = ivalue;
+			gnome_canvas_item_request_update (item);
+		}
+		return;
+	case PROP_SHOW_WEEK_NUMBERS:
+		bvalue = g_value_get_boolean (value);
+		if (calitem->show_week_numbers != bvalue) {
+			calitem->show_week_numbers = bvalue;
+			gnome_canvas_item_request_update (item);
+		}
+		return;
+	case PROP_KEEP_WDAYS_ON_WEEKNUM_CLICK:
+		calitem->keep_wdays_on_weeknum_click = g_value_get_boolean (value);
+		return;
+	case PROP_MAXIMUM_DAYS_SELECTED:
+		ivalue = g_value_get_int (value);
+		e_calendar_item_set_max_days_sel (calitem, ivalue);
+		return;
+	case PROP_DAYS_TO_START_WEEK_SELECTION:
+		ivalue = g_value_get_int (value);
+		e_calendar_item_set_days_start_week_sel (calitem, ivalue);
+		return;
+	case PROP_MOVE_SELECTION_WHEN_MOVING:
+		bvalue = g_value_get_boolean (value);
+		calitem->move_selection_when_moving = bvalue;
+		return;
+	case PROP_PRESERVE_DAY_WHEN_MOVING:
+		bvalue = g_value_get_boolean (value);
+		calitem->preserve_day_when_moving = bvalue;
+		return;
+	case PROP_DISPLAY_POPUP:
+		bvalue = g_value_get_boolean (value);
+		e_calendar_item_set_display_popup (calitem, bvalue);
+		return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+e_calendar_item_realize (GnomeCanvasItem *item)
+{
+	ECalendarItem *calitem;
+
+	if (GNOME_CANVAS_ITEM_CLASS (e_calendar_item_parent_class)->realize)
+		(* GNOME_CANVAS_ITEM_CLASS (e_calendar_item_parent_class)->realize) (item);
+
+	calitem = E_CALENDAR_ITEM (item);
+
+	e_calendar_item_style_set (GTK_WIDGET (item->canvas), calitem);
+
+	e_extensible_load_extensions (E_EXTENSIBLE (calitem));
+}
+
+static void
+e_calendar_item_unmap (GnomeCanvasItem *item)
+{
+	ECalendarItem *calitem;
+
+	calitem = E_CALENDAR_ITEM (item);
+
+	if (calitem->selecting) {
+		gnome_canvas_item_ungrab (item, GDK_CURRENT_TIME);
+		calitem->selecting = FALSE;
+	}
+
+	if (GNOME_CANVAS_ITEM_CLASS (e_calendar_item_parent_class)->unmap)
+		(* GNOME_CANVAS_ITEM_CLASS (e_calendar_item_parent_class)->unmap) (item);
+}
+
+static void
+e_calendar_item_update (GnomeCanvasItem *item,
+                        const cairo_matrix_t *i2c,
+                        gint flags)
+{
+	GnomeCanvasItemClass *item_class;
+	ECalendarItem *calitem;
+	GtkStyle *style;
+	gint char_height, width, height, space, space_per_cal, space_per_cell;
+	gint rows, cols, xthickness, ythickness;
+	PangoFontDescription *font_desc;
+	PangoContext *pango_context;
+	PangoFontMetrics *font_metrics;
+
+	item_class = GNOME_CANVAS_ITEM_CLASS (e_calendar_item_parent_class);
+	if (item_class->update != NULL)
+		item_class->update (item, i2c, flags);
+
+	calitem = E_CALENDAR_ITEM (item);
+	style = gtk_widget_get_style (GTK_WIDGET (item->canvas));
+	xthickness = style->xthickness;
+	ythickness = style->ythickness;
+
+	item->x1 = calitem->x1;
+	item->y1 = calitem->y1;
+	item->x2 = calitem->x2 >= calitem->x1 ? calitem->x2 : calitem->x1;
+	item->y2 = calitem->y2 >= calitem->y1 ? calitem->y2 : calitem->y1;
+
+	/* Set up Pango prerequisites */
+	font_desc = style->font_desc;
+	pango_context = gtk_widget_get_pango_context (GTK_WIDGET (item->canvas));
+	font_metrics = pango_context_get_metrics (
+		pango_context, font_desc,
+		pango_context_get_language (pango_context));
+
+	/*
+	 * Calculate the new layout of the calendar.
+	 */
+
+	/* Make sure the minimum row width & cell height and the widths of
+	 * all the digits and characters are up to date. */
+	e_calendar_item_recalc_sizes (calitem);
+
+	/* Calculate how many rows & cols we can fit in. */
+	width = item->x2 - item->x1;
+	height = item->y2 - item->y1;
+
+	width -= xthickness * 2;
+	height -= ythickness * 2;
+
+	if (calitem->min_month_height == 0)
+		rows = 1;
+	else
+		rows = height / calitem->min_month_height;
+	rows = MAX (rows, calitem->min_rows);
+	if (calitem->max_rows > 0)
+		rows = MIN (rows, calitem->max_rows);
+
+	if (calitem->min_month_width == 0)
+		cols = 1;
+	else
+		cols = width / calitem->min_month_width;
+	cols = MAX (cols, calitem->min_cols);
+	if (calitem->max_cols > 0)
+		cols = MIN (cols, calitem->max_cols);
+
+	if (rows != calitem->rows || cols != calitem->cols)
+		e_calendar_item_date_range_changed (calitem);
+
+	calitem->rows = rows;
+	calitem->cols = cols;
+
+	/* Split up the empty space according to the configuration.
+	 * If the calendar is set to expand, we divide the space between the
+	 * cells and the spaces around the calendar, otherwise we place the
+	 * calendars in the center of the available area. */
+
+	char_height =
+		PANGO_PIXELS (pango_font_metrics_get_ascent (font_metrics)) +
+		PANGO_PIXELS (pango_font_metrics_get_descent (font_metrics));
+
+	calitem->month_width = calitem->min_month_width;
+	calitem->month_height = calitem->min_month_height;
+	calitem->cell_width = MAX (calitem->max_day_width, (calitem->max_digit_width * 2))
+		+ E_CALENDAR_ITEM_MIN_CELL_XPAD;
+	calitem->cell_height = char_height
+		+ E_CALENDAR_ITEM_MIN_CELL_YPAD;
+	calitem->month_tpad = 0;
+	calitem->month_bpad = 0;
+	calitem->month_lpad = 0;
+	calitem->month_rpad = 0;
+
+	space = height - calitem->rows * calitem->month_height;
+	if (space > 0) {
+		space_per_cal = space / calitem->rows;
+		calitem->month_height += space_per_cal;
+
+		if (calitem->expand) {
+			space_per_cell = space_per_cal / E_CALENDAR_ROWS_PER_MONTH;
+			calitem->cell_height += space_per_cell;
+			space_per_cal -= space_per_cell * E_CALENDAR_ROWS_PER_MONTH;
+		}
+
+		calitem->month_tpad = space_per_cal / 2;
+		calitem->month_bpad = space_per_cal - calitem->month_tpad;
+	}
+
+	space = width - calitem->cols * calitem->month_width;
+	if (space > 0) {
+		space_per_cal = space / calitem->cols;
+		calitem->month_width += space_per_cal;
+		space -= space_per_cal * calitem->cols;
+
+		if (calitem->expand) {
+			space_per_cell = space_per_cal / E_CALENDAR_COLS_PER_MONTH;
+			calitem->cell_width += space_per_cell;
+			space_per_cal -= space_per_cell * E_CALENDAR_COLS_PER_MONTH;
+		}
+
+		calitem->month_lpad = space_per_cal / 2;
+		calitem->month_rpad = space_per_cal - calitem->month_lpad;
+	}
+
+	space = MAX (0, space);
+	calitem->x_offset = space / 2;
+
+	gnome_canvas_request_redraw (
+		item->canvas, item->x1, item->y1,
+		item->x2, item->y2);
+
+	pango_font_metrics_unref (font_metrics);
+}
+
+/*
+ * DRAWING ROUTINES - functions to paint the canvas item.
+ */
+static void
+e_calendar_item_draw (GnomeCanvasItem *canvas_item,
+                      cairo_t *cr,
+                      gint x,
+                      gint y,
+                      gint width,
+                      gint height)
+{
+	ECalendarItem *calitem;
+	GtkWidget *widget;
+	GtkStyleContext *style_context;
+	gint char_height, row, col, row_y, bar_height, col_x;
+	const PangoFontDescription *font_desc;
+	PangoContext *pango_context;
+	PangoFontMetrics *font_metrics;
+	GdkRGBA bg_color;
+	GtkBorder border;
+
+#if 0
+	g_print (
+		"In e_calendar_item_draw %i,%i %ix%i\n",
+		x, y, width, height);
+#endif
+	calitem = E_CALENDAR_ITEM (canvas_item);
+
+	widget = GTK_WIDGET (canvas_item->canvas);
+	style_context = gtk_widget_get_style_context (widget);
+
+	/* Set up Pango prerequisites */
+	font_desc = calitem->font_desc;
+	if (!font_desc)
+		font_desc = gtk_style_context_get_font (
+			style_context, GTK_STATE_FLAG_NORMAL);
+	pango_context = gtk_widget_get_pango_context (
+		GTK_WIDGET (canvas_item->canvas));
+	font_metrics = pango_context_get_metrics (
+		pango_context, font_desc,
+		pango_context_get_language (pango_context));
+
+	char_height =
+		PANGO_PIXELS (pango_font_metrics_get_ascent (font_metrics)) +
+		PANGO_PIXELS (pango_font_metrics_get_descent (font_metrics));
+
+	gtk_style_context_get_background_color (
+		style_context, GTK_STATE_NORMAL, &bg_color);
+
+	gtk_style_context_get_border (
+		style_context, GTK_STATE_NORMAL, &border);
+
+	/* Clear the entire background. */
+	cairo_save (cr);
+	gdk_cairo_set_source_rgba (cr, &bg_color);
+	cairo_rectangle (
+		cr, calitem->x1 - x, calitem->y1 - y,
+		calitem->x2 - calitem->x1 + 1,
+		calitem->y2 - calitem->y1 + 1);
+	cairo_fill (cr);
+	cairo_restore (cr);
+
+	/* Draw the shadow around the entire item. */
+	gtk_style_context_save (style_context);
+	gtk_style_context_add_class (
+		style_context, GTK_STYLE_CLASS_ENTRY);
+	cairo_save (cr);
+	gtk_render_frame (
+		style_context, cr,
+		(gdouble) calitem->x1 - x,
+		(gdouble) calitem->y1 - y,
+		(gdouble) calitem->x2 - calitem->x1 + 1,
+		(gdouble) calitem->y2 - calitem->y1 + 1);
+	cairo_restore (cr);
+	gtk_style_context_restore (style_context);
+
+	row_y = canvas_item->y1 + border.top;
+	bar_height =
+		border.top + border.bottom +
+		E_CALENDAR_ITEM_YPAD_ABOVE_MONTH_NAME + char_height +
+		E_CALENDAR_ITEM_YPAD_BELOW_MONTH_NAME;
+
+	for (row = 0; row < calitem->rows; row++) {
+		/* Draw the background for the title bars and the shadow around
+		 * it, and the vertical lines between columns. */
+
+		cairo_save (cr);
+		gdk_cairo_set_source_rgba (cr, &bg_color);
+		cairo_rectangle (
+			cr, calitem->x1 + border.left - x,
+			row_y - y,
+			calitem->x2 - calitem->x1 + 1 -
+			(border.left + border.right),
+			bar_height);
+		cairo_fill (cr);
+		cairo_restore (cr);
+
+		gtk_style_context_save (style_context);
+		gtk_style_context_add_class (
+			style_context, GTK_STYLE_CLASS_HEADER);
+		cairo_save (cr);
+		gtk_render_frame (
+			style_context, cr,
+			(gdouble) calitem->x1 + border.left - x,
+			(gdouble) row_y - y,
+			(gdouble) calitem->x2 - calitem->x1 + 1 -
+				(border.left + border.right),
+			(gdouble) bar_height);
+		cairo_restore (cr);
+		gtk_style_context_restore (style_context);
+
+		for (col = 0; col < calitem->cols; col++) {
+			if (col != 0) {
+				col_x = calitem->x1 + calitem->x_offset
+					+ calitem->month_width * col;
+
+				gtk_style_context_save (style_context);
+				gtk_style_context_add_class (
+					style_context,
+					GTK_STYLE_CLASS_SEPARATOR);
+				cairo_save (cr);
+				gtk_render_line (
+					style_context, cr,
+					(gdouble) col_x - 1 - x,
+					(gdouble) row_y + border.top + 1 - y,
+					(gdouble) row_y + bar_height -
+						border.bottom - 2 - y,
+					(gdouble) col_x - x);
+				cairo_restore (cr);
+				gtk_style_context_restore (style_context);
+			}
+
+			e_calendar_item_draw_month (
+				calitem, cr, x, y,
+				width, height, row, col);
+		}
+
+		row_y += calitem->month_height;
+	}
+
+	pango_font_metrics_unref (font_metrics);
+}
+
+static void
+layout_set_day_text (ECalendarItem *calitem,
+                     PangoLayout *layout,
+                     gint day_index)
+{
+	const gchar *abbr_name;
+
+	/* day_index: 0 = Monday ... 6 = Sunday */
+	abbr_name = e_get_weekday_name (day_index + 1, TRUE);
+	pango_layout_set_text (layout, abbr_name, -1);
+}
+
+static void
+e_calendar_item_draw_month (ECalendarItem *calitem,
+                            cairo_t *cr,
+                            gint x,
+                            gint y,
+                            gint width,
+                            gint height,
+                            gint row,
+                            gint col)
+{
+	GnomeCanvasItem *item;
+	GtkWidget *widget;
+	GtkStyle *style;
+	PangoFontDescription *font_desc;
+	struct tm tmp_tm;
+	GdkRectangle clip_rect;
+	gint char_height, xthickness, ythickness, start_weekday;
+	gint year, month;
+	gint month_x, month_y, month_w, month_h;
+	gint min_x, max_x, text_x, text_y;
+	gint day, day_index, cells_x, cells_y, min_cell_width, text_width, arrow_button_size;
+	gint clip_width, clip_height;
+	gchar buffer[64];
+	PangoContext *pango_context;
+	PangoFontMetrics *font_metrics;
+	PangoLayout *layout;
+
+#if 0
+	g_print (
+		"In e_calendar_item_draw_month: %i,%i %ix%i row:%i col:%i\n",
+		x, y, width, height, row, col);
+#endif
+	item = GNOME_CANVAS_ITEM (calitem);
+	widget = GTK_WIDGET (item->canvas);
+	style = gtk_widget_get_style (widget);
+
+	/* Set up Pango prerequisites */
+	font_desc = calitem->font_desc;
+	if (!font_desc)
+		font_desc = style->font_desc;
+	pango_context = gtk_widget_get_pango_context (widget);
+	font_metrics = pango_context_get_metrics (
+		pango_context, font_desc,
+		pango_context_get_language (pango_context));
+
+	char_height =
+		PANGO_PIXELS (pango_font_metrics_get_ascent (font_metrics)) +
+		PANGO_PIXELS (pango_font_metrics_get_descent (font_metrics));
+	xthickness = style->xthickness;
+	ythickness = style->ythickness;
+	arrow_button_size =
+		PANGO_PIXELS (pango_font_metrics_get_ascent (font_metrics))
+		+ PANGO_PIXELS (pango_font_metrics_get_descent (font_metrics))
+		+ E_CALENDAR_ITEM_YPAD_ABOVE_MONTH_NAME
+		+ E_CALENDAR_ITEM_YPAD_BELOW_MONTH_NAME
+		+ 2 * xthickness;
+
+	pango_font_metrics_unref (font_metrics);
+
+	/* Calculate the top-left position of the entire month display. */
+	month_x = item->x1 + xthickness + calitem->x_offset
+		+ ((gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
+		? (calitem->cols - 1 - col) : col) * calitem->month_width - x;
+	month_w = item->x2 - item->x1 - xthickness * 2;
+	month_w = MIN (month_w, calitem->month_width);
+	month_y = item->y1 + ythickness + row * calitem->month_height - y;
+	month_h = item->y2 - item->y1 - ythickness * 2;
+	month_h = MIN (month_h, calitem->month_height);
+
+	/* Just return if the month is outside the given area. */
+	if (month_x >= width || month_x + calitem->month_width <= 0
+	    || month_y >= height || month_y + calitem->month_height <= 0)
+		return;
+
+	month = calitem->month + row * calitem->cols + col;
+	year = calitem->year + month / 12;
+	month %= 12;
+
+	/* Draw the month name & year, with clipping. Note that the top row
+	 * needs extra space around it for the buttons. */
+
+	layout = gtk_widget_create_pango_layout (widget, NULL);
+
+	if (row == 0 && col == 0)
+		min_x = E_CALENDAR_ITEM_XPAD_BEFORE_MONTH_NAME_WITH_BUTTON;
+	else
+		min_x = E_CALENDAR_ITEM_XPAD_BEFORE_MONTH_NAME;
+
+	max_x = month_w;
+	if (row == 0 && col == 0)
+		max_x -= E_CALENDAR_ITEM_XPAD_AFTER_MONTH_NAME_WITH_BUTTON;
+	else
+		max_x -= E_CALENDAR_ITEM_XPAD_AFTER_MONTH_NAME;
+
+	text_y = month_y + style->ythickness
+		+ E_CALENDAR_ITEM_YPAD_ABOVE_MONTH_NAME;
+	clip_rect.x = month_x + min_x;
+	clip_rect.x = MAX (0, clip_rect.x);
+	clip_rect.y = MAX (0, text_y);
+
+	memset (&tmp_tm, 0, sizeof (tmp_tm));
+	tmp_tm.tm_year = year - 1900;
+	tmp_tm.tm_mon = month;
+	tmp_tm.tm_mday = 1;
+	tmp_tm.tm_isdst = -1;
+	mktime (&tmp_tm);
+	start_weekday = (tmp_tm.tm_wday + 6) % 7;
+
+	if (month_x + max_x - clip_rect.x > 0) {
+		cairo_save (cr);
+
+		clip_rect.width = month_x + max_x - clip_rect.x;
+		clip_rect.height = text_y + char_height - clip_rect.y;
+		gdk_cairo_rectangle (cr, &clip_rect);
+		cairo_clip (cr);
+
+		gdk_cairo_set_source_color (cr, &style->fg[GTK_STATE_NORMAL]);
+
+		if (row == 0 && col == 0) {
+			PangoLayout *layout_yr;
+			gchar buffer_yr[64];
+			gdouble max_width;
+
+			layout_yr = gtk_widget_create_pango_layout (widget, NULL);
+
+			/* This is a strftime() format. %B = Month name. */
+			e_utf8_strftime (buffer, sizeof (buffer), C_("CalItem", "%B"), &tmp_tm);
+			/* This is a strftime() format. %Y = Year. */
+			e_utf8_strftime (buffer_yr, sizeof (buffer_yr), C_("CalItem", "%Y"), &tmp_tm);
+
+			pango_layout_set_font_description (layout, font_desc);
+			pango_layout_set_text (layout, buffer, -1);
+
+			pango_layout_set_font_description (layout_yr, font_desc);
+			pango_layout_set_text (layout_yr, buffer_yr, -1);
+
+			if (gtk_widget_get_direction (widget) != GTK_TEXT_DIR_RTL) {
+				max_width = calitem->max_month_name_width;
+				pango_layout_get_pixel_size (layout, &text_width, NULL);
+
+				cairo_move_to (cr, month_x + min_x + arrow_button_size + (max_width - text_width) / 2, text_y);
+				pango_cairo_show_layout (cr, layout);
+
+				max_width = calitem->max_digit_width * 5;
+				pango_layout_get_pixel_size (layout_yr, &text_width, NULL);
+
+				cairo_move_to (cr, month_x + month_w - arrow_button_size - (max_width - text_width) / 2 - text_width - min_x, text_y);
+				pango_cairo_show_layout (cr, layout_yr);
+			} else {
+				max_width = calitem->max_digit_width * 5;
+				pango_layout_get_pixel_size (layout_yr, &text_width, NULL);
+
+				cairo_move_to (cr, month_x + min_x + arrow_button_size + (max_width - text_width) / 2, text_y);
+				pango_cairo_show_layout (cr, layout_yr);
+
+				max_width = calitem->max_month_name_width;
+				pango_layout_get_pixel_size (layout, &text_width, NULL);
+
+				cairo_move_to (cr, month_x + month_w - arrow_button_size - (max_width - text_width) / 2 - text_width - min_x, text_y);
+				pango_cairo_show_layout (cr, layout);
+			}
+
+			g_object_unref (layout_yr);
+		} else {
+			/* This is a strftime() format. %B = Month name, %Y = Year. */
+			e_utf8_strftime (buffer, sizeof (buffer), C_("CalItem", "%B %Y"), &tmp_tm);
+
+			pango_layout_set_font_description (layout, font_desc);
+			pango_layout_set_text (layout, buffer, -1);
+
+			/* Ideally we place the text centered in the month, but we
+			 * won't go to the left of the minimum x position. */
+			pango_layout_get_pixel_size (layout, &text_width, NULL);
+			text_x = (calitem->month_width - text_width) / 2;
+			text_x = MAX (min_x, text_x);
+
+			cairo_move_to (cr, month_x + text_x, text_y);
+			pango_cairo_show_layout (cr, layout);
+		}
+
+		cairo_restore (cr);
+	}
+
+	/* Set the clip rectangle for the main month display. */
+	clip_rect.x = MAX (0, month_x);
+	clip_rect.y = MAX (0, month_y);
+	clip_width = month_x + month_w - clip_rect.x;
+	clip_height = month_y + month_h - clip_rect.y;
+
+	if (clip_width <= 0 || clip_height <= 0) {
+		g_object_unref (layout);
+		return;
+	}
+
+	clip_rect.width = clip_width;
+	clip_rect.height = clip_height;
+
+	cairo_save (cr);
+
+	gdk_cairo_rectangle (cr, &clip_rect);
+	cairo_clip (cr);
+
+	/* Draw the day initials across the top of the month. */
+	min_cell_width = MAX (calitem->max_day_width, (calitem->max_digit_width * 2))
+		+ E_CALENDAR_ITEM_MIN_CELL_XPAD;
+
+	cells_x = month_x + E_CALENDAR_ITEM_XPAD_BEFORE_WEEK_NUMBERS + calitem->month_lpad
+		+ E_CALENDAR_ITEM_XPAD_BEFORE_CELLS;
+	if (calitem->show_week_numbers)
+		cells_x += calitem->max_week_number_digit_width * 2
+			+ E_CALENDAR_ITEM_XPAD_AFTER_WEEK_NUMBERS + 1;
+	text_x = cells_x + calitem->cell_width
+		- (calitem->cell_width - min_cell_width) / 2;
+	text_x -= E_CALENDAR_ITEM_MIN_CELL_XPAD / 2;
+	text_y = month_y + ythickness * 2
+		+ E_CALENDAR_ITEM_YPAD_ABOVE_MONTH_NAME
+		+ char_height + E_CALENDAR_ITEM_YPAD_BELOW_MONTH_NAME
+		+ E_CALENDAR_ITEM_YPAD_ABOVE_DAY_LETTERS + calitem->month_tpad;
+
+	cells_y = text_y + char_height
+		+ E_CALENDAR_ITEM_YPAD_BELOW_DAY_LETTERS + 1
+		+ E_CALENDAR_ITEM_YPAD_ABOVE_CELLS;
+
+	cairo_save (cr);
+	gdk_cairo_set_source_color (cr, &style->base[GTK_STATE_SELECTED]);
+	cairo_rectangle (
+		cr, cells_x ,
+		text_y - E_CALENDAR_ITEM_YPAD_ABOVE_CELLS - 1,
+			calitem->cell_width * 7  , cells_y - text_y);
+	cairo_fill (cr);
+	cairo_restore (cr);
+
+	day_index = calitem->week_start_day;
+	pango_layout_set_font_description (layout, font_desc);
+	if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
+		text_x += (7 - 1) * calitem->cell_width;
+	gdk_cairo_set_source_color (cr, &style->text[GTK_STATE_ACTIVE]);
+	for (day = 0; day < 7; day++) {
+		cairo_save (cr);
+		layout_set_day_text (calitem, layout, day_index);
+		cairo_move_to (
+			cr,
+			text_x - calitem->day_widths[day_index],
+			text_y);
+		pango_cairo_show_layout (cr, layout);
+		text_x += (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
+				? -calitem->cell_width : calitem->cell_width;
+		day_index++;
+		if (day_index == 7)
+			day_index = 0;
+		cairo_restore (cr);
+	}
+
+	/* Draw the rectangle around the week number. */
+	if (calitem->show_week_numbers) {
+		cairo_save (cr);
+		gdk_cairo_set_source_color (cr, &style->base[GTK_STATE_SELECTED]);
+		cairo_rectangle (
+			cr, cells_x, cells_y - (cells_y - text_y + 2) ,
+				-20, E_CALENDAR_ROWS_PER_MONTH * calitem->cell_height + 18);
+		cairo_fill (cr);
+		cairo_restore (cr);
+	}
+
+	e_calendar_item_draw_day_numbers (
+		calitem, cr, width, height, row, col,
+		year, month, start_weekday, cells_x, cells_y);
+
+	g_object_unref (layout);
+	cairo_restore (cr);
+}
+
+static const gchar *
+get_digit_fomat (void)
+{
+
+#ifdef HAVE_GNU_GET_LIBC_VERSION
+#include <gnu/libc-version.h>
+
+	const gchar *libc_version = gnu_get_libc_version ();
+	gchar **split = g_strsplit (libc_version, ".", -1);
+	gint major = 0;
+	gint minor = 0;
+	gint revision = 0;
+
+	major = atoi (split[0]);
+	minor = atoi (split[1]);
+
+	if (g_strv_length (split) > 2)
+		revision = atoi (split[2]);
+	g_strfreev (split);
+
+	if (major > 2 || minor > 2 || (minor == 2 && revision > 2)) {
+		return "%Id";
+	}
+#endif
+
+	return "%d";
+}
+
+static void
+e_calendar_item_draw_day_numbers (ECalendarItem *calitem,
+                                  cairo_t *cr,
+                                  gint width,
+                                  gint height,
+                                  gint row,
+                                  gint col,
+                                  gint year,
+                                  gint month,
+                                  gint start_weekday,
+                                  gint cells_x,
+                                  gint cells_y)
+{
+	GnomeCanvasItem *item;
+	GtkWidget *widget;
+	GtkStyle *style;
+	PangoFontDescription *font_desc;
+	GdkColor *bg_color, *fg_color, *box_color;
+	struct tm today_tm;
+	time_t t;
+	gint char_height, min_cell_width, min_cell_height;
+	gint day_num, drow, dcol, day_x, day_y;
+	gint text_x, text_y;
+	gint num_chars, digit;
+	gint week_num, mon, days_from_week_start;
+	gint years[3], months[3], days_in_month[3];
+	gboolean today, selected, has_focus, drop_target = FALSE;
+	gboolean bold, italic, draw_day, finished = FALSE;
+	gint today_year, today_month, today_mday, month_offset;
+	gchar buffer[9];
+	gint day_style = 0;
+	PangoContext *pango_context;
+	PangoFontMetrics *font_metrics;
+	PangoLayout *layout;
+
+	item = GNOME_CANVAS_ITEM (calitem);
+	widget = GTK_WIDGET (item->canvas);
+	style = gtk_widget_get_style (widget);
+
+	/* Set up Pango prerequisites */
+	font_desc = calitem->font_desc;
+	if (!font_desc)
+		font_desc = style->font_desc;
+
+	pango_context = gtk_widget_get_pango_context (widget);
+	font_metrics = pango_context_get_metrics (
+		pango_context, font_desc,
+		pango_context_get_language (pango_context));
+
+	char_height =
+		PANGO_PIXELS (pango_font_metrics_get_ascent (font_metrics)) +
+		PANGO_PIXELS (pango_font_metrics_get_descent (font_metrics));
+
+	min_cell_width = MAX (calitem->max_day_width, (calitem->max_digit_width * 2))
+		+ E_CALENDAR_ITEM_MIN_CELL_XPAD;
+	min_cell_height = char_height + E_CALENDAR_ITEM_MIN_CELL_YPAD;
+
+	layout = pango_cairo_create_layout (cr);
+
+	/* Calculate the number of days in the previous, current, and next
+	 * months. */
+	years[0] = years[1] = years[2] = year;
+	months[0] = month - 1;
+	months[1] = month;
+	months[2] = month + 1;
+	if (months[0] == -1) {
+		months[0] = 11;
+		years[0]--;
+	}
+	if (months[2] == 12) {
+		months[2] = 0;
+		years[2]++;
+	}
+
+	days_in_month[0] = DAYS_IN_MONTH (years[0], months[0]);
+	days_in_month[1] = DAYS_IN_MONTH (years[1], months[1]);
+	days_in_month[2] = DAYS_IN_MONTH (years[2], months[2]);
+
+	/* Mon 0 is the previous month, which we may show the end of. Mon 1 is
+	 * the current month, and mon 2 is the next month. */
+	mon = 0;
+
+	month_offset = row * calitem->cols + col - 1;
+	day_num = days_in_month[0];
+	days_from_week_start = (start_weekday + 7 - calitem->week_start_day)
+		% 7;
+	/* For the top-left month we show the end of the previous month, and
+	 * if the new month starts on the first day of the week we show a
+	 * complete week from the previous month. */
+	if (days_from_week_start == 0) {
+		if (row == 0 && col == 0) {
+			day_num -= 6;
+		} else {
+			mon++;
+			month_offset++;
+			day_num = 1;
+		}
+	} else {
+		day_num -= days_from_week_start - 1;
+	}
+
+	/* Get today's date, so we can highlight it. */
+	if (calitem->time_callback) {
+		today_tm = calitem->time_callback (
+			calitem, calitem->time_callback_data);
+	} else {
+		t = time (NULL);
+		today_tm = *localtime (&t);
+	}
+	today_year = today_tm.tm_year + 1900;
+	today_month = today_tm.tm_mon;
+	today_mday = today_tm.tm_mday;
+
+	/* We usually skip the last days of the previous month (mon = 0),
+	 * except for the top-left month displayed. */
+	draw_day = (mon == 1 || (row == 0 && col == 0));
+
+	for (drow = 0; drow < 6; drow++) {
+		/* Draw the week number. */
+		if (calitem->show_week_numbers) {
+			week_num = e_calendar_item_get_week_number (
+				calitem, day_num, months[mon], years[mon]);
+
+			text_x = cells_x - E_CALENDAR_ITEM_XPAD_BEFORE_CELLS - 1
+				- E_CALENDAR_ITEM_XPAD_AFTER_WEEK_NUMBERS;
+			text_y = cells_y + drow * calitem->cell_height +
+				+ (calitem->cell_height - min_cell_height + 1) / 2;
+
+			num_chars = 0;
+			if (week_num >= 10) {
+				digit = week_num / 10;
+				text_x -= calitem->week_number_digit_widths[digit];
+				num_chars += sprintf (
+					&buffer[num_chars],
+					get_digit_fomat (), digit);
+			}
+
+			digit = week_num % 10;
+			text_x -= calitem->week_number_digit_widths[digit] + 6;
+			num_chars += sprintf (
+				&buffer[num_chars],
+				get_digit_fomat (), digit);
+
+			cairo_save (cr);
+			gdk_cairo_set_source_color (
+				cr, &style->text[GTK_STATE_ACTIVE]);
+			pango_layout_set_font_description (layout, font_desc);
+			pango_layout_set_text (layout, buffer, num_chars);
+			cairo_move_to (cr, text_x, text_y);
+			pango_cairo_update_layout (cr, layout);
+			pango_cairo_show_layout (cr, layout);
+			cairo_restore (cr);
+		}
+
+		for (dcol = 0; dcol < 7; dcol++) {
+			if (draw_day) {
+				day_x = cells_x +
+					((gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
+					? 7 - 1 - dcol : dcol) * calitem->cell_width;
+
+				day_y = cells_y + drow * calitem->cell_height;
+
+				today = years[mon] == today_year
+					&& months[mon] == today_month
+					&& day_num == today_mday;
+
+				selected = calitem->selection_set
+					&& (calitem->selection_start_month_offset < month_offset
+					    || (calitem->selection_start_month_offset == month_offset
+						&& calitem->selection_start_day <= day_num))
+					&& (calitem->selection_end_month_offset > month_offset
+					    || (calitem->selection_end_month_offset == month_offset
+						&& calitem->selection_end_day >= day_num));
+
+				if (calitem->styles)
+					day_style = calitem->styles[(month_offset + 1) * 32 + day_num];
+
+				/* Get the colors & style to use for the day.*/
+				if ((gtk_widget_has_focus (GTK_WIDGET (item->canvas))) &&
+				    item->canvas->focused_item == item)
+					has_focus = TRUE;
+				else
+					has_focus = FALSE;
+
+				bold = FALSE;
+				italic = FALSE;
+
+				if (calitem->style_callback)
+					calitem->style_callback (
+						calitem,
+						 years[mon],
+						 months[mon],
+						 day_num,
+						 day_style,
+						 today,
+						 mon != 1,
+						 selected,
+						 has_focus,
+						 drop_target,
+						 &bg_color,
+						 &fg_color,
+						 &box_color,
+						 &bold,
+						 &italic,
+						 calitem->style_callback_data);
+				else
+					e_calendar_item_get_day_style (
+						calitem,
+						years[mon],
+						months[mon],
+						day_num,
+						day_style,
+						today,
+						mon != 1,
+						selected,
+						has_focus,
+						drop_target,
+						&bg_color,
+						&fg_color,
+						&box_color,
+						&bold,
+						&italic);
+
+				/* Draw the background, if set. */
+				if (bg_color) {
+					cairo_save (cr);
+					gdk_cairo_set_source_color (cr, bg_color);
+					cairo_rectangle (
+						cr, day_x , day_y,
+						calitem->cell_width,
+						calitem->cell_height);
+					cairo_fill (cr);
+					cairo_restore (cr);
+				}
+
+				/* Draw the box, if set. */
+				if (box_color) {
+					cairo_save (cr);
+					gdk_cairo_set_source_color (cr, box_color);
+					cairo_rectangle (
+						cr, day_x , day_y,
+						calitem->cell_width - 1,
+						calitem->cell_height - 1);
+					cairo_stroke (cr);
+					cairo_restore (cr);
+				}
+
+				/* Draw the 1- or 2-digit day number. */
+				day_x += calitem->cell_width -
+					(calitem->cell_width -
+					min_cell_width) / 2;
+				day_x -= E_CALENDAR_ITEM_MIN_CELL_XPAD / 2;
+				day_y += (calitem->cell_height - min_cell_height + 1) / 2;
+				day_y += E_CALENDAR_ITEM_MIN_CELL_YPAD / 2;
+
+				num_chars = 0;
+				if (day_num >= 10) {
+					digit = day_num / 10;
+					day_x -= calitem->digit_widths[digit];
+					num_chars += sprintf (
+						&buffer[num_chars],
+						get_digit_fomat (), digit);
+				}
+
+				digit = day_num % 10;
+				day_x -= calitem->digit_widths[digit];
+				num_chars += sprintf (
+					&buffer[num_chars],
+					get_digit_fomat (), digit);
+
+				cairo_save (cr);
+				if (fg_color) {
+					gdk_cairo_set_source_color (
+						cr, fg_color);
+				} else {
+					gdk_cairo_set_source_color (
+						cr, &style->fg[GTK_STATE_NORMAL]);
+				}
+
+				if (bold) {
+					pango_font_description_set_weight (
+						font_desc, PANGO_WEIGHT_BOLD);
+				} else {
+					pango_font_description_set_weight (
+						font_desc, PANGO_WEIGHT_NORMAL);
+				}
+
+				if (italic) {
+					pango_font_description_set_style (
+						font_desc, PANGO_STYLE_ITALIC);
+				} else {
+					pango_font_description_set_style (
+						font_desc, PANGO_STYLE_NORMAL);
+				}
+
+				pango_layout_set_font_description (layout, font_desc);
+				pango_layout_set_text (layout, buffer, num_chars);
+				cairo_move_to (cr, day_x, day_y);
+				pango_cairo_update_layout (cr, layout);
+				pango_cairo_show_layout (cr, layout);
+				cairo_restore (cr);
+			}
+
+			/* See if we've reached the end of a month. */
+			if (day_num == days_in_month[mon]) {
+				month_offset++;
+				mon++;
+				/* We only draw the start of the next month
+				 * for the bottom-right month displayed. */
+				if (mon == 2 && (row != calitem->rows - 1
+						 || col != calitem->cols - 1)) {
+					/* Set a flag so we exit the loop. */
+					finished = TRUE;
+					break;
+				}
+				day_num = 1;
+				draw_day = TRUE;
+			} else {
+				day_num++;
+			}
+		}
+
+		/* Exit the loop if the flag is set. */
+		if (finished)
+			break;
+	}
+
+	/* Reset pango weight and style */
+	pango_font_description_set_weight (font_desc, PANGO_WEIGHT_NORMAL);
+	pango_font_description_set_style (font_desc, PANGO_STYLE_NORMAL);
+
+	g_object_unref (layout);
+
+	pango_font_metrics_unref (font_metrics);
+}
+
+gint
+e_calendar_item_get_week_number (ECalendarItem *calitem,
+                                 gint day,
+                                 gint month,
+                                 gint year)
+{
+	GDate date;
+	guint weekday, yearday;
+	gint week_num;
+
+	g_date_clear (&date, 1);
+	g_date_set_dmy (&date, day, month + 1, year);
+
+	/* This results in a value of 0 (Monday) - 6 (Sunday).
+	 * (or -1 on error - oops!!) */
+	weekday = g_date_get_weekday (&date) - 1;
+
+	if (weekday > 0) {
+		/* we want always point to nearest Monday, as the first day of the week,
+		 * regardless of the calendar's week_start_day */
+		if (weekday >= 3)
+			g_date_add_days (&date, 7 - weekday);
+		else
+			g_date_subtract_days (&date, weekday);
+	}
+
+	/* Calculate the day of the year, from 0 to 365. */
+	yearday = g_date_get_day_of_year (&date) - 1;
+
+	/* If the week starts on or after 29th December, it is week 1 of the
+	 * next year, since there are 4 days in the next year. */
+	if (g_date_get_month (&date) == 12 && g_date_get_day (&date) >= 29)
+		return 1;
+
+	/* Calculate the week number, from 0. */
+	week_num = yearday / 7;
+
+	/* If the first week starts on or after Jan 5th, then we need to add
+	 * 1 since the previous week will really be the first week. */
+	if (yearday % 7 >= 4)
+		week_num++;
+
+	/* Add 1 so week numbers are from 1 to 53. */
+	return week_num + 1;
+}
+
+/* This is supposed to return the nearest item the the point and the distance.
+ * Since we are the only item we just return ourself and 0 for the distance.
+ * This is needed so that we get button/motion events. */
+static GnomeCanvasItem *
+e_calendar_item_point (GnomeCanvasItem *item,
+                       gdouble x,
+                       gdouble y,
+                       gint cx,
+                       gint cy)
+{
+	return item;
+}
+
+static void
+e_calendar_item_stop_selecting (ECalendarItem *calitem,
+                                guint32 time)
+{
+	if (!calitem->selecting)
+		return;
+
+	gnome_canvas_item_ungrab (GNOME_CANVAS_ITEM (calitem), time);
+
+	calitem->selecting = FALSE;
+
+	/* If the user selects the grayed dates before the first month or
+	 * after the last month, we move backwards or forwards one month.
+	 * The set_month () call should take care of updating the selection. */
+	if (calitem->selection_end_month_offset == -1)
+		e_calendar_item_set_first_month (
+			calitem, calitem->year,
+			calitem->month - 1);
+	else if (calitem->selection_start_month_offset == calitem->rows * calitem->cols)
+		e_calendar_item_set_first_month (
+			calitem, calitem->year,
+			calitem->month + 1);
+
+	calitem->selection_changed = TRUE;
+	if (calitem->selecting_axis) {
+		g_free (calitem->selecting_axis);
+		calitem->selecting_axis = NULL;
+	}
+
+	e_calendar_item_queue_signal_emission (calitem);
+	gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (calitem));
+}
+
+static void
+e_calendar_item_selection_add_days (ECalendarItem *calitem,
+                                    gint n_days,
+                                    gboolean multi_selection)
+{
+	GDate gdate_start, gdate_end;
+
+	g_return_if_fail (E_IS_CALENDAR_ITEM (calitem));
+
+	if (!e_calendar_item_get_selection (calitem, &gdate_start, &gdate_end)) {
+		/* We set the date to the first day of the month */
+		g_date_set_dmy (&gdate_start, 1, calitem->month + 1, calitem->year);
+		gdate_end = gdate_start;
+	}
+
+	if (multi_selection && calitem->max_days_selected > 1) {
+		gint days_between;
+
+		days_between = g_date_days_between (&gdate_start, &gdate_end);
+		if (!calitem->selecting_axis) {
+			calitem->selecting_axis = g_new (GDate, 1);
+			*(calitem->selecting_axis) = gdate_start;
+		}
+		if ((days_between != 0 &&
+		     g_date_compare (calitem->selecting_axis, &gdate_end) == 0) ||
+		    (days_between == 0 && n_days < 0)) {
+			if (days_between - n_days > calitem->max_days_selected - 1)
+				n_days =  days_between + 1 - calitem->max_days_selected;
+			g_date_add_days (&gdate_start, n_days);
+		}
+		else {
+			if (days_between + n_days > calitem->max_days_selected - 1)
+				n_days = calitem->max_days_selected - 1 - days_between;
+			g_date_add_days (&gdate_end, n_days);
+		}
+
+		if (g_date_compare (&gdate_end, &gdate_start) < 0) {
+			GDate tmp_date;
+			tmp_date = gdate_start;
+			gdate_start = gdate_end;
+			gdate_end = tmp_date;
+		}
+	}
+	else {
+		/* clear "selecting_axis", it is only for mulit-selecting */
+		if (calitem->selecting_axis) {
+			g_free (calitem->selecting_axis);
+			calitem->selecting_axis = NULL;
+		}
+		g_date_add_days (&gdate_start, n_days);
+		gdate_end = gdate_start;
+	}
+
+	calitem->selecting = TRUE;
+
+	e_calendar_item_set_selection_if_emission (
+		calitem, &gdate_start, &gdate_end, FALSE);
+
+	g_signal_emit_by_name (calitem, "selection_preview_changed");
+}
+
+static gint
+e_calendar_item_key_press_event (ECalendarItem *calitem,
+                                 GdkEvent *event)
+{
+	guint keyval = event->key.keyval;
+	gboolean multi_selection = FALSE;
+
+	if (event->key.state & GDK_CONTROL_MASK ||
+	    event->key.state & GDK_MOD1_MASK)
+		return FALSE;
+
+	multi_selection = event->key.state & GDK_SHIFT_MASK;
+	switch (keyval) {
+	case GDK_KEY_Up:
+		e_calendar_item_selection_add_days (
+			calitem, -7,
+			multi_selection);
+		break;
+	case GDK_KEY_Down:
+		e_calendar_item_selection_add_days (
+			calitem, 7,
+			multi_selection);
+		break;
+	case GDK_KEY_Left:
+		e_calendar_item_selection_add_days (
+			calitem, -1,
+			multi_selection);
+		break;
+	case GDK_KEY_Right:
+		e_calendar_item_selection_add_days (
+			calitem, 1,
+			multi_selection);
+		break;
+	case GDK_KEY_space:
+	case GDK_KEY_Return:
+		e_calendar_item_stop_selecting (calitem, event->key.time);
+		break;
+	default:
+		return FALSE;
+	}
+	return TRUE;
+}
+
+static gint
+e_calendar_item_event (GnomeCanvasItem *item,
+                       GdkEvent *event)
+{
+	ECalendarItem *calitem;
+
+	calitem = E_CALENDAR_ITEM (item);
+
+	switch (event->type) {
+	case GDK_BUTTON_PRESS:
+		return e_calendar_item_button_press (calitem, event);
+	case GDK_BUTTON_RELEASE:
+		return e_calendar_item_button_release (calitem, event);
+	case GDK_MOTION_NOTIFY:
+		return e_calendar_item_motion (calitem, event);
+	case GDK_FOCUS_CHANGE:
+		gnome_canvas_item_request_update (item);
+		return FALSE;
+	case GDK_KEY_PRESS:
+		return e_calendar_item_key_press_event (calitem, event);
+	default:
+		break;
+	}
+
+	return FALSE;
+}
+
+static void
+e_calendar_item_bounds (GnomeCanvasItem *item,
+                        gdouble *x1,
+                        gdouble *y1,
+                        gdouble *x2,
+                        gdouble *y2)
+{
+	ECalendarItem *calitem;
+
+	g_return_if_fail (E_IS_CALENDAR_ITEM (item));
+
+	calitem = E_CALENDAR_ITEM (item);
+	*x1 = calitem->x1;
+	*y1 = calitem->y1;
+	*x2 = calitem->x2;
+	*y2 = calitem->y2;
+}
+
+/* This checks if any fonts have changed, and if so it recalculates the
+ * text sizes and the minimum month size. */
+static void
+e_calendar_item_recalc_sizes (ECalendarItem *calitem)
+{
+	GnomeCanvasItem *canvas_item;
+	GtkStyle *style;
+	gint day, max_day_width, digit, max_digit_width, max_week_number_digit_width;
+	gint char_height, width, min_cell_width, min_cell_height;
+	gchar buffer[64];
+	struct tm tmp_tm;
+	PangoFontDescription *font_desc, *wkfont_desc;
+	PangoContext *pango_context;
+	PangoFontMetrics *font_metrics;
+	PangoLayout *layout;
+
+	canvas_item = GNOME_CANVAS_ITEM (calitem);
+	style = gtk_widget_get_style (GTK_WIDGET (canvas_item->canvas));
+
+	if (!style)
+		return;
+
+	/* Set up Pango prerequisites */
+	font_desc = calitem->font_desc;
+	wkfont_desc = calitem->week_number_font_desc;
+	if (!font_desc)
+		font_desc = style->font_desc;
+
+	pango_context = gtk_widget_create_pango_context (
+		GTK_WIDGET (canvas_item->canvas));
+	font_metrics = pango_context_get_metrics (
+		pango_context, font_desc,
+		pango_context_get_language (pango_context));
+	layout = pango_layout_new (pango_context);
+
+	char_height =
+		PANGO_PIXELS (pango_font_metrics_get_ascent (font_metrics)) +
+		PANGO_PIXELS (pango_font_metrics_get_descent (font_metrics));
+
+	max_day_width = 0;
+	for (day = 0; day < 7; day++) {
+		layout_set_day_text (calitem, layout, day);
+		pango_layout_get_pixel_size (layout, &width, NULL);
+
+		calitem->day_widths[day] = width;
+		max_day_width = MAX (max_day_width, width);
+	}
+	calitem->max_day_width = max_day_width;
+
+	max_digit_width = 0;
+	max_week_number_digit_width = 0;
+	for (digit = 0; digit < 10; digit++) {
+		gchar locale_digit[5];
+		gint locale_digit_len;
+
+		locale_digit_len = sprintf (locale_digit, get_digit_fomat (), digit);
+
+		pango_layout_set_text (layout, locale_digit, locale_digit_len);
+		pango_layout_get_pixel_size (layout, &width, NULL);
+
+		calitem->digit_widths[digit] = width;
+		max_digit_width = MAX (max_digit_width, width);
+
+		if (wkfont_desc) {
+			pango_context_set_font_description (pango_context, wkfont_desc);
+			pango_layout_context_changed (layout);
+
+			pango_layout_set_text (layout, locale_digit, locale_digit_len);
+			pango_layout_get_pixel_size (layout, &width, NULL);
+
+			calitem->week_number_digit_widths[digit] = width;
+			max_week_number_digit_width = MAX (max_week_number_digit_width, width);
+
+			pango_context_set_font_description (pango_context, font_desc);
+			pango_layout_context_changed (layout);
+		} else {
+			calitem->week_number_digit_widths[digit] = width;
+			max_week_number_digit_width = max_digit_width;
+		}
+	}
+	calitem->max_digit_width = max_digit_width;
+	calitem->max_week_number_digit_width = max_week_number_digit_width;
+
+	min_cell_width = MAX (calitem->max_day_width, (calitem->max_digit_width * 2))
+		+ E_CALENDAR_ITEM_MIN_CELL_XPAD;
+	min_cell_height = char_height + E_CALENDAR_ITEM_MIN_CELL_YPAD;
+
+	calitem->min_month_width = E_CALENDAR_ITEM_XPAD_BEFORE_WEEK_NUMBERS
+		+ E_CALENDAR_ITEM_XPAD_BEFORE_CELLS + min_cell_width * 7
+		+ E_CALENDAR_ITEM_XPAD_AFTER_CELLS;
+	if (calitem->show_week_numbers) {
+		calitem->min_month_width += calitem->max_week_number_digit_width * 2
+			+ E_CALENDAR_ITEM_XPAD_AFTER_WEEK_NUMBERS + 1;
+	}
+
+	calitem->min_month_height = style->ythickness * 2
+		+ E_CALENDAR_ITEM_YPAD_ABOVE_MONTH_NAME + char_height
+		+ E_CALENDAR_ITEM_YPAD_BELOW_MONTH_NAME + 1
+		+ E_CALENDAR_ITEM_YPAD_ABOVE_DAY_LETTERS
+		+ char_height + E_CALENDAR_ITEM_YPAD_BELOW_DAY_LETTERS + 1
+		+ E_CALENDAR_ITEM_YPAD_ABOVE_CELLS + min_cell_height * 6
+		+ E_CALENDAR_ITEM_YPAD_BELOW_CELLS;
+
+	calitem->max_month_name_width = 50;
+	memset (&tmp_tm, 0, sizeof (tmp_tm));
+	tmp_tm.tm_year = 2000 - 100;
+	tmp_tm.tm_mday = 1;
+	tmp_tm.tm_isdst = -1;
+	for (tmp_tm.tm_mon = 0; tmp_tm.tm_mon < 12; tmp_tm.tm_mon++) {
+		mktime (&tmp_tm);
+
+		e_utf8_strftime (buffer, sizeof (buffer), C_("CalItem", "%B"), &tmp_tm);
+
+		pango_layout_set_text (layout, buffer, -1);
+		pango_layout_get_pixel_size (layout, &width, NULL);
+
+		if (width > calitem->max_month_name_width)
+			calitem->max_month_name_width = width;
+	}
+
+	g_object_unref (layout);
+	g_object_unref (pango_context);
+	pango_font_metrics_unref (font_metrics);
+}
+
+static void
+e_calendar_item_get_day_style (ECalendarItem *calitem,
+                               gint year,
+                               gint month,
+                               gint day,
+                               gint day_style,
+                               gboolean today,
+                               gboolean prev_or_next_month,
+                               gboolean selected,
+                               gboolean has_focus,
+                               gboolean drop_target,
+                               GdkColor **bg_color,
+                               GdkColor **fg_color,
+                               GdkColor **box_color,
+                               gboolean *bold,
+                               gboolean *italic)
+{
+	GtkWidget *widget;
+	GtkStyle *style;
+
+	widget = GTK_WIDGET (GNOME_CANVAS_ITEM (calitem)->canvas);
+	style = gtk_widget_get_style (widget);
+
+	*bg_color = NULL;
+	*fg_color = NULL;
+	*box_color = NULL;
+
+	*bold = (day_style & E_CALENDAR_ITEM_MARK_BOLD) ==
+		E_CALENDAR_ITEM_MARK_BOLD;
+	*italic = (day_style & E_CALENDAR_ITEM_MARK_ITALIC) ==
+		E_CALENDAR_ITEM_MARK_ITALIC;
+
+	if (today)
+		*box_color = &calitem->colors[E_CALENDAR_ITEM_COLOR_TODAY_BOX];
+
+	if (prev_or_next_month)
+		*fg_color = &style->mid[gtk_widget_get_state (widget)];
+
+	if (selected) {
+		if (has_focus) {
+			*fg_color = &style->text[GTK_STATE_SELECTED];
+			*bg_color = &style->base[GTK_STATE_SELECTED];
+		} else {
+			*fg_color = &style->text[GTK_STATE_ACTIVE];
+			*bg_color = &style->base[GTK_STATE_ACTIVE];
+
+			if ((*bg_color)->red == style->base[GTK_STATE_NORMAL].red &&
+			    (*bg_color)->green == style->base[GTK_STATE_NORMAL].green &&
+			    (*bg_color)->blue == style->base[GTK_STATE_NORMAL].blue) {
+				*fg_color = &style->text[GTK_STATE_SELECTED];
+				*bg_color = &style->base[GTK_STATE_SELECTED];
+			}
+		}
+	}
+}
+
+static gboolean
+e_calendar_item_button_press (ECalendarItem *calitem,
+                              GdkEvent *button_event)
+{
+	GdkGrabStatus grab_status;
+	GdkDevice *event_device;
+	guint event_button = 0;
+	guint32 event_time;
+	gdouble event_x_win = 0;
+	gdouble event_y_win = 0;
+	gint month_offset, day, add_days = 0;
+	gboolean all_week, round_up_end = FALSE, round_down_start = FALSE;
+
+	gdk_event_get_button (button_event, &event_button);
+	gdk_event_get_coords (button_event, &event_x_win, &event_y_win);
+	event_device = gdk_event_get_device (button_event);
+	event_time = gdk_event_get_time (button_event);
+
+	if (event_button == 4)
+		e_calendar_item_set_first_month (
+			calitem, calitem->year,
+			calitem->month - 1);
+	else if (event_button == 5)
+		e_calendar_item_set_first_month (
+			calitem, calitem->year,
+			calitem->month + 1);
+
+	if (!e_calendar_item_convert_position_to_day (calitem,
+						      event_x_win,
+						      event_y_win,
+						      TRUE,
+						      &month_offset, &day,
+						      &all_week))
+		return FALSE;
+
+	if (event_button == 3 && day == -1
+	    && e_calendar_item_get_display_popup (calitem)) {
+		e_calendar_item_show_popup_menu (
+			calitem, button_event, month_offset);
+		return TRUE;
+	}
+
+	if (event_button != 1 || day == -1)
+		return FALSE;
+
+	if (calitem->max_days_selected < 1)
+		return TRUE;
+
+	grab_status = gnome_canvas_item_grab (
+		GNOME_CANVAS_ITEM (calitem),
+		GDK_POINTER_MOTION_MASK |
+		GDK_BUTTON_RELEASE_MASK,
+		NULL,
+		event_device,
+		event_time);
+
+	if (grab_status != GDK_GRAB_SUCCESS)
+		return FALSE;
+
+	if (all_week && calitem->keep_wdays_on_weeknum_click) {
+		gint tmp_start_moff, tmp_start_day;
+
+		tmp_start_moff = calitem->selection_start_month_offset;
+		tmp_start_day = calitem->selection_start_day;
+		e_calendar_item_round_down_selection (
+			calitem, &tmp_start_moff, &tmp_start_day);
+
+		e_calendar_item_round_down_selection (calitem, &month_offset, &day);
+		month_offset += calitem->selection_start_month_offset - tmp_start_moff;
+		day += calitem->selection_start_day - tmp_start_day;
+
+		/* keep same count of days selected */
+		add_days = e_calendar_item_get_inclusive_days (
+			calitem,
+			calitem->selection_start_month_offset,
+			calitem->selection_start_day,
+			calitem->selection_end_month_offset,
+			calitem->selection_end_day) - 1;
+	}
+
+	calitem->selection_set = TRUE;
+	calitem->selection_start_month_offset = month_offset;
+	calitem->selection_start_day = day;
+	calitem->selection_end_month_offset = month_offset;
+	calitem->selection_end_day = day;
+
+	if (add_days > 0)
+		e_calendar_item_add_days_to_selection (calitem, add_days);
+
+	calitem->selection_real_start_month_offset = month_offset;
+	calitem->selection_real_start_day = day;
+
+	calitem->selection_from_full_week = FALSE;
+	calitem->selecting = TRUE;
+	calitem->selection_dragging_end = TRUE;
+
+	if (all_week && !calitem->keep_wdays_on_weeknum_click) {
+		calitem->selection_from_full_week = TRUE;
+		round_up_end = TRUE;
+	}
+
+	if (calitem->days_to_start_week_selection == 1) {
+		round_down_start = TRUE;
+		round_up_end = TRUE;
+	}
+
+	/* Don't round up or down if we can't select a week or more,
+	 * or when keeping week days. */
+	if (calitem->max_days_selected < 7 ||
+		(all_week && calitem->keep_wdays_on_weeknum_click)) {
+		round_down_start = FALSE;
+		round_up_end = FALSE;
+	}
+
+	if (round_up_end)
+		e_calendar_item_round_up_selection (
+			calitem, &calitem->selection_end_month_offset,
+			&calitem->selection_end_day);
+
+	if (round_down_start)
+		e_calendar_item_round_down_selection (
+			calitem, &calitem->selection_start_month_offset,
+			&calitem->selection_start_day);
+
+	gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (calitem));
+
+	return TRUE;
+}
+
+static gboolean
+e_calendar_item_button_release (ECalendarItem *calitem,
+                                GdkEvent *button_event)
+{
+	guint32 event_time;
+
+	event_time = gdk_event_get_time (button_event);
+	e_calendar_item_stop_selecting (calitem, event_time);
+
+	return FALSE;
+}
+
+static gboolean
+e_calendar_item_motion (ECalendarItem *calitem,
+                        GdkEvent *event)
+{
+	gint start_month, start_day, end_month, end_day, month_offset, day;
+	gint tmp_month, tmp_day, days_in_selection;
+	gboolean all_week, round_up_end = FALSE, round_down_start = FALSE;
+
+	if (!calitem->selecting)
+		return FALSE;
+
+	if (!e_calendar_item_convert_position_to_day (calitem,
+						      event->button.x,
+						      event->button.y,
+						      TRUE,
+						      &month_offset, &day,
+						      &all_week))
+		return FALSE;
+
+	if (day == -1)
+		return FALSE;
+
+	if (calitem->selection_dragging_end) {
+		start_month = calitem->selection_real_start_month_offset;
+		start_day = calitem->selection_real_start_day;
+		end_month = month_offset;
+		end_day = day;
+	} else {
+		start_month = month_offset;
+		start_day = day;
+		end_month = calitem->selection_real_start_month_offset;
+		end_day = calitem->selection_real_start_day;
+	}
+
+	if (start_month > end_month || (start_month == end_month
+					&& start_day > end_day)) {
+		tmp_month = start_month;
+		tmp_day = start_day;
+		start_month = end_month;
+		start_day = end_day;
+		end_month = tmp_month;
+		end_day = tmp_day;
+
+		calitem->selection_dragging_end =
+			!calitem->selection_dragging_end;
+	}
+
+	if (calitem->days_to_start_week_selection > 0) {
+		days_in_selection = e_calendar_item_get_inclusive_days (
+			calitem, start_month, start_day, end_month, end_day);
+		if (days_in_selection >= calitem->days_to_start_week_selection) {
+			round_down_start = TRUE;
+			round_up_end = TRUE;
+		}
+	}
+
+	/* If we are over a week number and we are dragging the end of the
+	 * selection, we round up to the end of this week. */
+	if (all_week && calitem->selection_dragging_end)
+		round_up_end = TRUE;
+
+	/* If the selection was started from a week number and we are dragging
+	 * the start of the selection, we need to round up the end to include
+	 * all of the original week selected. */
+	if (calitem->selection_from_full_week
+	    && !calitem->selection_dragging_end)
+			round_up_end = TRUE;
+
+	/* Don't round up or down if we can't select a week or more. */
+	if (calitem->max_days_selected < 7) {
+		round_down_start = FALSE;
+		round_up_end = FALSE;
+	}
+
+	if (round_up_end)
+		e_calendar_item_round_up_selection (
+			calitem, &end_month,
+			&end_day);
+	if (round_down_start)
+		e_calendar_item_round_down_selection (
+			calitem, &start_month,
+			&start_day);
+
+	/* Check we don't go over the maximum number of days to select. */
+	if (calitem->selection_dragging_end) {
+		e_calendar_item_check_selection_end (
+			calitem,
+			start_month,
+			start_day,
+			&end_month,
+			&end_day);
+	} else {
+		e_calendar_item_check_selection_start (
+			calitem,
+			&start_month,
+			&start_day,
+			end_month,
+			end_day);
+	}
+
+	if (start_month == calitem->selection_start_month_offset
+	    && start_day == calitem->selection_start_day
+	    && end_month == calitem->selection_end_month_offset
+	    && end_day == calitem->selection_end_day)
+		return FALSE;
+
+	calitem->selection_start_month_offset = start_month;
+	calitem->selection_start_day = start_day;
+	calitem->selection_end_month_offset = end_month;
+	calitem->selection_end_day = end_day;
+
+	gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (calitem));
+
+	return TRUE;
+}
+
+static void
+e_calendar_item_check_selection_end (ECalendarItem *calitem,
+                                     gint start_month,
+                                     gint start_day,
+                                     gint *end_month,
+                                     gint *end_day)
+{
+	gint year, month, max_month, max_day, days_in_month;
+
+	if (calitem->max_days_selected <= 0)
+		return;
+
+	year = calitem->year;
+	month = calitem->month + start_month;
+	e_calendar_item_normalize_date (calitem, &year, &month);
+
+	max_month = start_month;
+	max_day = start_day + calitem->max_days_selected - 1;
+
+	for (;;) {
+		days_in_month = DAYS_IN_MONTH (year, month);
+		if (max_day <= days_in_month)
+			break;
+		max_month++;
+		month++;
+		if (month == 12) {
+			year++;
+			month = 0;
+		}
+		max_day -= days_in_month;
+	}
+
+	if (*end_month > max_month) {
+		*end_month = max_month;
+		*end_day = max_day;
+	} else if (*end_month == max_month && *end_day > max_day) {
+		*end_day = max_day;
+	}
+}
+
+static void
+e_calendar_item_check_selection_start (ECalendarItem *calitem,
+                                       gint *start_month,
+                                       gint *start_day,
+                                       gint end_month,
+                                       gint end_day)
+{
+	gint year, month, min_month, min_day, days_in_month;
+
+	if (calitem->max_days_selected <= 0)
+		return;
+
+	year = calitem->year;
+	month = calitem->month + end_month;
+	e_calendar_item_normalize_date (calitem, &year, &month);
+
+	min_month = end_month;
+	min_day = end_day - calitem->max_days_selected + 1;
+
+	while (min_day <= 0) {
+		min_month--;
+		month--;
+		if (month == -1) {
+			year--;
+			month = 11;
+		}
+		days_in_month = DAYS_IN_MONTH (year, month);
+		min_day += days_in_month;
+	}
+
+	if (*start_month < min_month) {
+		*start_month = min_month;
+		*start_day = min_day;
+	} else if (*start_month == min_month && *start_day < min_day) {
+		*start_day = min_day;
+	}
+}
+
+/* Converts a position within the item to a month & day.
+ * The month returned is 0 for the top-left month displayed.
+ * If the position is over the month heading -1 is returned for the day.
+ * If the position is over a week number the first day of the week is returned
+ * and entire_week is set to TRUE.
+ * It returns FALSE if the position is completely outside all months. */
+static gboolean
+e_calendar_item_convert_position_to_day (ECalendarItem *calitem,
+                                         gint event_x,
+                                         gint event_y,
+                                         gboolean round_empty_positions,
+                                         gint *month_offset,
+                                         gint *day,
+                                         gboolean *entire_week)
+{
+	GnomeCanvasItem *item;
+	GtkWidget *widget;
+	GtkStyle *style;
+	gint xthickness, ythickness, char_height;
+	gint x, y, row, col, cells_x, cells_y, day_row, day_col;
+	gint first_day_offset, days_in_month, days_in_prev_month;
+	gint week_num_x1, week_num_x2;
+	PangoFontDescription *font_desc;
+	PangoContext *pango_context;
+	PangoFontMetrics *font_metrics;
+
+	item = GNOME_CANVAS_ITEM (calitem);
+	widget = GTK_WIDGET (item->canvas);
+	style = gtk_widget_get_style (widget);
+
+	font_desc = calitem->font_desc;
+	if (!font_desc)
+		font_desc = style->font_desc;
+	pango_context = gtk_widget_create_pango_context (widget);
+	font_metrics = pango_context_get_metrics (
+		pango_context, font_desc,
+		pango_context_get_language (pango_context));
+
+	char_height =
+		PANGO_PIXELS (pango_font_metrics_get_ascent (font_metrics)) +
+		PANGO_PIXELS (pango_font_metrics_get_descent (font_metrics));
+	xthickness = style->xthickness;
+	ythickness = style->ythickness;
+
+	pango_font_metrics_unref (font_metrics);
+
+	*entire_week = FALSE;
+
+	x = event_x - xthickness - calitem->x_offset;
+	y = event_y - ythickness;
+
+	if (x < 0 || y < 0)
+		return FALSE;
+
+	row = y / calitem->month_height;
+	col = x / calitem->month_width;
+
+	if (row >= calitem->rows || col >= calitem->cols)
+		return FALSE;
+	if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
+		col = calitem->cols - 1 - col;
+
+	*month_offset = row * calitem->cols + col;
+
+	x = x % calitem->month_width;
+	y = y % calitem->month_height;
+
+	if (y < ythickness * 2 + E_CALENDAR_ITEM_YPAD_ABOVE_MONTH_NAME
+	    + char_height + E_CALENDAR_ITEM_YPAD_BELOW_MONTH_NAME) {
+		*day = -1;
+		return TRUE;
+	}
+
+	cells_y = ythickness * 2 + E_CALENDAR_ITEM_YPAD_ABOVE_MONTH_NAME
+		+ char_height + E_CALENDAR_ITEM_YPAD_BELOW_MONTH_NAME
+		+ E_CALENDAR_ITEM_YPAD_ABOVE_DAY_LETTERS + calitem->month_tpad
+		+ char_height + E_CALENDAR_ITEM_YPAD_BELOW_DAY_LETTERS + 1
+		+ E_CALENDAR_ITEM_YPAD_ABOVE_CELLS;
+	y -= cells_y;
+	if (y < 0)
+		return FALSE;
+	day_row = y / calitem->cell_height;
+	if (day_row >= E_CALENDAR_ROWS_PER_MONTH)
+		return FALSE;
+
+	week_num_x1 = E_CALENDAR_ITEM_XPAD_BEFORE_WEEK_NUMBERS + calitem->month_lpad;
+
+	if (calitem->show_week_numbers) {
+		week_num_x2 = week_num_x1
+			+ calitem->max_week_number_digit_width * 2;
+		if (x >= week_num_x1 && x < week_num_x2)
+			*entire_week = TRUE;
+		cells_x = week_num_x2 + E_CALENDAR_ITEM_XPAD_AFTER_WEEK_NUMBERS + 1;
+	} else {
+		cells_x = week_num_x1;
+	}
+
+	if (*entire_week) {
+		day_col = 0;
+	} else {
+		cells_x += E_CALENDAR_ITEM_XPAD_BEFORE_CELLS;
+		x -= cells_x;
+		if (x < 0)
+			return FALSE;
+		day_col = x / calitem->cell_width;
+		if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
+			day_col = E_CALENDAR_COLS_PER_MONTH - 1 - day_col;
+		if (day_col >= E_CALENDAR_COLS_PER_MONTH)
+			return FALSE;
+	}
+
+	*day = day_row * E_CALENDAR_COLS_PER_MONTH + day_col;
+
+	e_calendar_item_get_month_info (
+		calitem, row, col, &first_day_offset,
+		&days_in_month, &days_in_prev_month);
+	if (*day < first_day_offset) {
+		if (*entire_week || (row == 0 && col == 0)) {
+			(*month_offset)--;
+			*day = days_in_prev_month + 1 - first_day_offset
+				+ *day;
+			return TRUE;
+		} else if (round_empty_positions) {
+			*day = first_day_offset;
+		} else {
+			return FALSE;
+		}
+	}
+
+	*day -= first_day_offset - 1;
+
+	if (*day > days_in_month) {
+		if (row == calitem->rows - 1 && col == calitem->cols - 1) {
+			(*month_offset)++;
+			*day -= days_in_month;
+			return TRUE;
+		} else if (round_empty_positions) {
+			*day = days_in_month;
+		} else {
+			return FALSE;
+		}
+	}
+
+	return TRUE;
+}
+
+static void
+e_calendar_item_get_month_info (ECalendarItem *calitem,
+                                gint row,
+                                gint col,
+                                gint *first_day_offset,
+                                gint *days_in_month,
+                                gint *days_in_prev_month)
+{
+	gint year, month, start_weekday, first_day_of_month;
+	struct tm tmp_tm = { 0 };
+
+	month = calitem->month + row * calitem->cols + col;
+	year = calitem->year + month / 12;
+	month = month % 12;
+
+	*days_in_month = DAYS_IN_MONTH (year, month);
+	if (month == 0)
+		*days_in_prev_month = DAYS_IN_MONTH (year - 1, 11);
+	else
+		*days_in_prev_month = DAYS_IN_MONTH (year, month - 1);
+
+	tmp_tm.tm_year = year - 1900;
+	tmp_tm.tm_mon = month;
+	tmp_tm.tm_mday = 1;
+	tmp_tm.tm_isdst = -1;
+	mktime (&tmp_tm);
+
+	/* Convert to 0 (Monday) to 6 (Sunday). */
+	start_weekday = (tmp_tm.tm_wday + 6) % 7;
+
+	first_day_of_month = (start_weekday + 7 - calitem->week_start_day) % 7;
+
+	if (row == 0 && col == 0 && first_day_of_month == 0)
+		*first_day_offset = 7;
+	else
+		*first_day_offset = first_day_of_month;
+}
+
+void
+e_calendar_item_get_first_month (ECalendarItem *calitem,
+                                 gint *year,
+                                 gint *month)
+{
+	*year = calitem->year;
+	*month = calitem->month;
+}
+
+static void
+e_calendar_item_preserve_day_selection (ECalendarItem *calitem,
+                                        gint selected_day,
+                                        gint *month_offset,
+                                        gint *day)
+{
+	gint year, month, weekday, days, days_in_month;
+	struct tm tmp_tm = { 0 };
+
+	year = calitem->year;
+	month = calitem->month + *month_offset;
+	e_calendar_item_normalize_date (calitem, &year, &month);
+
+	tmp_tm.tm_year = year - 1900;
+	tmp_tm.tm_mon = month;
+	tmp_tm.tm_mday = *day;
+	tmp_tm.tm_isdst = -1;
+	mktime (&tmp_tm);
+
+	/* Convert to 0 (Monday) to 6 (Sunday). */
+	weekday = (tmp_tm.tm_wday + 6) % 7;
+
+	/* Calculate how many days to the start of the row. */
+	days = (weekday + 7 - selected_day) % 7;
+
+	*day -= days;
+	if (*day <= 0) {
+		month--;
+		if (month == -1) {
+			year--;
+			month = 11;
+		}
+		days_in_month = DAYS_IN_MONTH (year, month);
+		(*month_offset)--;
+		*day += days_in_month;
+	}
+}
+
+/* This also handles values of month < 0 or > 11 by updating the year. */
+void
+e_calendar_item_set_first_month (ECalendarItem *calitem,
+                                 gint year,
+                                 gint month)
+{
+	gint new_year, new_month, months_diff, num_months;
+	gint old_days_in_selection, new_days_in_selection;
+
+	new_year = year;
+	new_month = month;
+	e_calendar_item_normalize_date (calitem, &new_year, &new_month);
+
+	if (calitem->year == new_year && calitem->month == new_month)
+		return;
+
+	/* Update the selection. */
+	num_months = calitem->rows * calitem->cols;
+	months_diff = (new_year - calitem->year) * 12
+		+ new_month - calitem->month;
+
+	if (calitem->selection_set) {
+		if (!calitem->move_selection_when_moving
+		    || (calitem->selection_start_month_offset - months_diff >= 0
+			&& calitem->selection_end_month_offset - months_diff < num_months)) {
+			calitem->selection_start_month_offset -= months_diff;
+			calitem->selection_end_month_offset -= months_diff;
+			calitem->selection_real_start_month_offset -= months_diff;
+
+			calitem->year = new_year;
+			calitem->month = new_month;
+		} else {
+			gint selected_day;
+			struct tm tmp_tm = { 0 };
+
+			old_days_in_selection = e_calendar_item_get_inclusive_days (
+				calitem,
+				calitem->selection_start_month_offset,
+				calitem->selection_start_day,
+				calitem->selection_end_month_offset,
+				calitem->selection_end_day);
+
+			/* Calculate the currently selected day */
+			tmp_tm.tm_year = calitem->year - 1900;
+			tmp_tm.tm_mon = calitem->month + calitem->selection_start_month_offset;
+			tmp_tm.tm_mday = calitem->selection_start_day;
+			tmp_tm.tm_isdst = -1;
+			mktime (&tmp_tm);
+
+			selected_day = (tmp_tm.tm_wday + 6) % 7;
+
+			/* Make sure the selection will be displayed. */
+			if (calitem->selection_start_month_offset < 0
+			    || calitem->selection_start_month_offset >= num_months) {
+				calitem->selection_end_month_offset -=
+					calitem->selection_start_month_offset;
+				calitem->selection_start_month_offset = 0;
+			}
+
+			/* We want to ensure that the same number of days are
+			 * selected after we have moved the selection. */
+			calitem->year = new_year;
+			calitem->month = new_month;
+
+			e_calendar_item_ensure_valid_day (
+				calitem, &calitem->selection_start_month_offset,
+				&calitem->selection_start_day);
+			e_calendar_item_ensure_valid_day (
+				calitem, &calitem->selection_end_month_offset,
+				&calitem->selection_end_day);
+
+			if (calitem->preserve_day_when_moving) {
+				e_calendar_item_preserve_day_selection (
+					calitem, selected_day,
+					&calitem->selection_start_month_offset,
+					&calitem->selection_start_day);
+			}
+
+			new_days_in_selection = e_calendar_item_get_inclusive_days (
+				calitem,
+				calitem->selection_start_month_offset,
+				calitem->selection_start_day,
+				calitem->selection_end_month_offset,
+				calitem->selection_end_day);
+
+			if (old_days_in_selection != new_days_in_selection)
+				e_calendar_item_add_days_to_selection (
+					calitem, old_days_in_selection -
+					new_days_in_selection);
+
+			/* Flag that we need to emit the "selection_changed"
+			 * signal. We don't want to emit it here since setting
+			 * the "year" and "month" args would result in 2
+			 * signals emitted. */
+			calitem->selection_changed = TRUE;
+		}
+	} else {
+		calitem->year = new_year;
+		calitem->month = new_month;
+	}
+
+	e_calendar_item_date_range_changed (calitem);
+	gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (calitem));
+}
+
+/* Get the maximum number of days selectable */
+gint
+e_calendar_item_get_max_days_sel (ECalendarItem *calitem)
+{
+	return calitem->max_days_selected;
+}
+
+/* Set the maximum number of days selectable */
+void
+e_calendar_item_set_max_days_sel (ECalendarItem *calitem,
+                                  gint days)
+{
+	calitem->max_days_selected = MAX (0, days);
+	gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (calitem));
+}
+
+/* Get the maximum number of days before whole weeks are selected */
+gint
+e_calendar_item_get_days_start_week_sel (ECalendarItem *calitem)
+{
+	return calitem->days_to_start_week_selection;
+}
+
+/* Set the maximum number of days before whole weeks are selected */
+void
+e_calendar_item_set_days_start_week_sel (ECalendarItem *calitem,
+                                         gint days)
+{
+	calitem->days_to_start_week_selection = days;
+}
+
+gboolean
+e_calendar_item_get_display_popup (ECalendarItem *calitem)
+{
+	return calitem->display_popup;
+}
+
+void
+e_calendar_item_set_display_popup (ECalendarItem *calitem,
+                                   gboolean display)
+{
+	calitem->display_popup = display;
+}
+
+/* This will make sure that the given year & month are valid, i.e. if month
+ * is < 0 or > 11 the year and month will be updated accordingly. */
+void
+e_calendar_item_normalize_date (ECalendarItem *calitem,
+                                gint *year,
+                                gint *month)
+{
+	if (*month >= 0) {
+		*year += *month / 12;
+		*month = *month % 12;
+	} else {
+		*year += *month / 12 - 1;
+		*month = *month % 12;
+		if (*month != 0)
+			*month += 12;
+	}
+}
+
+/* Adds or subtracts days from the selection. It is used when we switch months
+ * and the selection extends past the end of a month but we want to keep the
+ * number of days selected the same. days should not be more than 30. */
+static void
+e_calendar_item_add_days_to_selection (ECalendarItem *calitem,
+                                       gint days)
+{
+	gint year, month, days_in_month;
+
+	year = calitem->year;
+	month = calitem->month + calitem->selection_end_month_offset;
+	e_calendar_item_normalize_date (calitem, &year,	&month);
+
+	calitem->selection_end_day += days;
+	if (calitem->selection_end_day <= 0) {
+		month--;
+		e_calendar_item_normalize_date (calitem, &year,	&month);
+		calitem->selection_end_month_offset--;
+		calitem->selection_end_day += DAYS_IN_MONTH (year, month);
+	} else {
+		days_in_month = DAYS_IN_MONTH (year, month);
+		if (calitem->selection_end_day > days_in_month) {
+			calitem->selection_end_month_offset++;
+			calitem->selection_end_day -= days_in_month;
+		}
+	}
+}
+
+/* Gets the range of dates actually shown. Months are 0 to 11.
+ * This also includes the last days of the previous month and the first days
+ * of the following month, which are normally shown in gray.
+ * It returns FALSE if no dates are currently shown. */
+gboolean
+e_calendar_item_get_date_range (ECalendarItem *calitem,
+                                gint *start_year,
+                                gint *start_month,
+                                gint *start_day,
+                                gint *end_year,
+                                gint *end_month,
+                                gint *end_day)
+{
+	gint first_day_offset, days_in_month, days_in_prev_month;
+
+	if (calitem->rows == 0 || calitem->cols == 0)
+		return FALSE;
+
+	/* Calculate the first day shown. This will be one of the greyed-out
+	 * days before the first full month begins. */
+	e_calendar_item_get_month_info (
+		calitem, 0, 0, &first_day_offset,
+		&days_in_month, &days_in_prev_month);
+	*start_year = calitem->year;
+	*start_month = calitem->month - 1;
+	if (*start_month == -1) {
+		(*start_year)--;
+		*start_month = 11;
+	}
+	*start_day = days_in_prev_month + 1 - first_day_offset;
+
+	/* Calculate the last day shown. This will be one of the greyed-out
+	 * days after the last full month ends. */
+	e_calendar_item_get_month_info (
+		calitem, calitem->rows - 1,
+		calitem->cols - 1, &first_day_offset,
+		&days_in_month, &days_in_prev_month);
+	*end_month = calitem->month + calitem->rows * calitem->cols;
+	*end_year = calitem->year + *end_month / 12;
+	*end_month %= 12;
+	*end_day = E_CALENDAR_ROWS_PER_MONTH * E_CALENDAR_COLS_PER_MONTH
+		- first_day_offset - days_in_month;
+
+	return TRUE;
+}
+
+/* Simple way to mark days so they appear bold.
+ * A more flexible interface may be added later. */
+void
+e_calendar_item_clear_marks (ECalendarItem *calitem)
+{
+	GnomeCanvasItem *item;
+
+	item = GNOME_CANVAS_ITEM (calitem);
+
+	g_free (calitem->styles);
+	calitem->styles = NULL;
+
+	gnome_canvas_request_redraw (
+		item->canvas, item->x1, item->y1,
+		item->x2, item->y2);
+}
+
+/* add_day_style - whether bit-or with the actual style or change the style fully */
+void
+e_calendar_item_mark_day (ECalendarItem *calitem,
+                          gint year,
+                          gint month,
+                          gint day,
+                          guint8 day_style,
+                          gboolean add_day_style)
+{
+	gint month_offset;
+	gint index;
+
+	month_offset = (year - calitem->year) * 12 + month - calitem->month;
+	if (month_offset < -1 || month_offset > calitem->rows * calitem->cols)
+		return;
+
+	if (!calitem->styles)
+		calitem->styles = g_new0 (guint8, (calitem->rows * calitem->cols + 2) * 32);
+
+	index = (month_offset + 1) * 32 + day;
+	calitem->styles[index] = day_style |
+		(add_day_style ? calitem->styles[index] : 0);
+
+	gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (calitem));
+}
+
+void
+e_calendar_item_mark_days (ECalendarItem *calitem,
+                           gint start_year,
+                           gint start_month,
+                           gint start_day,
+                           gint end_year,
+                           gint end_month,
+                           gint end_day,
+                           guint8 day_style,
+                           gboolean add_day_style)
+{
+	gint month_offset, end_month_offset, day;
+
+	month_offset = (start_year - calitem->year) * 12 + start_month
+		- calitem->month;
+	day = start_day;
+	if (month_offset > calitem->rows * calitem->cols)
+		return;
+	if (month_offset < -1) {
+		month_offset = -1;
+		day = 1;
+	}
+
+	end_month_offset = (end_year - calitem->year) * 12 + end_month
+		- calitem->month;
+	if (end_month_offset < -1)
+		return;
+	if (end_month_offset > calitem->rows * calitem->cols) {
+		end_month_offset = calitem->rows * calitem->cols;
+		end_day = 31;
+	}
+
+	if (month_offset > end_month_offset)
+		return;
+
+	if (!calitem->styles)
+		calitem->styles = g_new0 (guint8, (calitem->rows * calitem->cols + 2) * 32);
+
+	for (;;) {
+		gint index;
+
+		if (month_offset == end_month_offset && day > end_day)
+			break;
+
+		if (month_offset < -1 || month_offset > calitem->rows * calitem->cols)
+			g_warning ("Bad month offset: %i\n", month_offset);
+		if (day < 1 || day > 31)
+			g_warning ("Bad day: %i\n", day);
+
+#if 0
+		g_print ("Marking Month:%i Day:%i\n", month_offset, day);
+#endif
+		index = (month_offset + 1) * 32 + day;
+		calitem->styles[index] = day_style |
+			(add_day_style ? calitem->styles[index] : 0);
+
+		day++;
+		if (day == 32) {
+			month_offset++;
+			day = 1;
+			if (month_offset > end_month_offset)
+				break;
+		}
+	}
+
+	gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (calitem));
+}
+
+/* Rounds up the given day to the end of the week. */
+static void
+e_calendar_item_round_up_selection (ECalendarItem *calitem,
+                                    gint *month_offset,
+                                    gint *day)
+{
+	gint year, month, weekday, days, days_in_month;
+	struct tm tmp_tm = { 0 };
+
+	year = calitem->year;
+	month = calitem->month + *month_offset;
+	e_calendar_item_normalize_date (calitem, &year, &month);
+
+	tmp_tm.tm_year = year - 1900;
+	tmp_tm.tm_mon = month;
+	tmp_tm.tm_mday = *day;
+	tmp_tm.tm_isdst = -1;
+	mktime (&tmp_tm);
+
+	/* Convert to 0 (Monday) to 6 (Sunday). */
+	weekday = (tmp_tm.tm_wday + 6) % 7;
+
+	/* Calculate how many days to the end of the row. */
+	days = (calitem->week_start_day + 6 - weekday) % 7;
+
+	*day += days;
+	days_in_month = DAYS_IN_MONTH (year, month);
+	if (*day > days_in_month) {
+		(*month_offset)++;
+		*day -= days_in_month;
+	}
+}
+
+/* Rounds down the given day to the start of the week. */
+static void
+e_calendar_item_round_down_selection (ECalendarItem *calitem,
+                                      gint *month_offset,
+                                      gint *day)
+{
+	gint year, month, weekday, days, days_in_month;
+	struct tm tmp_tm = { 0 };
+
+	year = calitem->year;
+	month = calitem->month + *month_offset;
+	e_calendar_item_normalize_date (calitem, &year, &month);
+
+	tmp_tm.tm_year = year - 1900;
+	tmp_tm.tm_mon = month;
+	tmp_tm.tm_mday = *day;
+	tmp_tm.tm_isdst = -1;
+	mktime (&tmp_tm);
+
+	/* Convert to 0 (Monday) to 6 (Sunday). */
+	weekday = (tmp_tm.tm_wday + 6) % 7;
+
+	/* Calculate how many days to the start of the row. */
+	days = (weekday + 7 - calitem->week_start_day) % 7;
+
+	*day -= days;
+	if (*day <= 0) {
+		month--;
+		if (month == -1) {
+			year--;
+			month = 11;
+		}
+		days_in_month = DAYS_IN_MONTH (year, month);
+		(*month_offset)--;
+		*day += days_in_month;
+	}
+}
+
+static gint
+e_calendar_item_get_inclusive_days (ECalendarItem *calitem,
+                                    gint start_month_offset,
+                                    gint start_day,
+                                    gint end_month_offset,
+                                    gint end_day)
+{
+	gint start_year, start_month, end_year, end_month, days = 0;
+
+	start_year = calitem->year;
+	start_month = calitem->month + start_month_offset;
+	e_calendar_item_normalize_date (calitem, &start_year, &start_month);
+
+	end_year = calitem->year;
+	end_month = calitem->month + end_month_offset;
+	e_calendar_item_normalize_date (calitem, &end_year, &end_month);
+
+	while (start_year < end_year || start_month < end_month) {
+		days += DAYS_IN_MONTH (start_year, start_month);
+		start_month++;
+		if (start_month == 12) {
+			start_year++;
+			start_month = 0;
+		}
+	}
+
+	days += end_day - start_day + 1;
+
+	return days;
+}
+
+/* If the day is off the end of the month it is set to the last day of the
+ * month. */
+static void
+e_calendar_item_ensure_valid_day (ECalendarItem *calitem,
+                                  gint *month_offset,
+                                  gint *day)
+{
+	gint year, month, days_in_month;
+
+	year = calitem->year;
+	month = calitem->month + *month_offset;
+	e_calendar_item_normalize_date (calitem, &year, &month);
+
+	days_in_month = DAYS_IN_MONTH (year, month);
+	if (*day > days_in_month)
+		*day = days_in_month;
+}
+
+gboolean
+e_calendar_item_get_selection (ECalendarItem *calitem,
+                               GDate *start_date,
+                               GDate *end_date)
+{
+	gint start_year, start_month, start_day;
+	gint end_year, end_month, end_day;
+
+	g_date_clear (start_date, 1);
+	g_date_clear (end_date, 1);
+
+	if (!calitem->selection_set)
+		return FALSE;
+
+	start_year = calitem->year;
+	start_month = calitem->month + calitem->selection_start_month_offset;
+	e_calendar_item_normalize_date (calitem, &start_year, &start_month);
+	start_day = calitem->selection_start_day;
+
+	end_year = calitem->year;
+	end_month = calitem->month + calitem->selection_end_month_offset;
+	e_calendar_item_normalize_date (calitem, &end_year, &end_month);
+	end_day = calitem->selection_end_day;
+
+	g_date_set_dmy (start_date, start_day, start_month + 1, start_year);
+	g_date_set_dmy (end_date, end_day, end_month + 1, end_year);
+
+	return TRUE;
+}
+
+static void
+e_calendar_item_set_selection_if_emission (ECalendarItem *calitem,
+                                           const GDate *start_date,
+                                           const GDate *end_date,
+                                           gboolean emission)
+{
+	gint start_year, start_month, start_day;
+	gint end_year, end_month, end_day;
+	gint new_start_month_offset, new_start_day;
+	gint new_end_month_offset, new_end_day;
+	gboolean need_update;
+
+	g_return_if_fail (E_IS_CALENDAR_ITEM (calitem));
+
+	/* If start_date is NULL, we clear the selection without changing the
+	 * month shown. */
+	if (start_date == NULL) {
+		calitem->selection_set = FALSE;
+		calitem->selection_changed = TRUE;
+		e_calendar_item_queue_signal_emission (calitem);
+		gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (calitem));
+		return;
+	}
+
+	if (end_date == NULL)
+		end_date = start_date;
+
+	g_return_if_fail (g_date_compare (start_date, end_date) <= 0);
+
+	start_year = g_date_get_year (start_date);
+	start_month = g_date_get_month (start_date) - 1;
+	start_day = g_date_get_day (start_date);
+	end_year = g_date_get_year (end_date);
+	end_month = g_date_get_month (end_date) - 1;
+	end_day = g_date_get_day (end_date);
+
+	need_update = e_calendar_item_ensure_days_visible (
+		calitem,
+		start_year,
+		start_month,
+		start_day,
+		end_year,
+		end_month,
+		end_day,
+		emission);
+
+	new_start_month_offset = (start_year - calitem->year) * 12
+		+ start_month - calitem->month;
+	new_start_day = start_day;
+
+	/* This may go outside the visible months, but we don't care. */
+	new_end_month_offset = (end_year - calitem->year) * 12
+		+ end_month - calitem->month;
+	new_end_day = end_day;
+
+	if (!calitem->selection_set
+	    || calitem->selection_start_month_offset != new_start_month_offset
+	    || calitem->selection_start_day != new_start_day
+	    || calitem->selection_end_month_offset != new_end_month_offset
+	    || calitem->selection_end_day != new_end_day) {
+		need_update = TRUE;
+		if (emission) {
+			calitem->selection_changed = TRUE;
+			e_calendar_item_queue_signal_emission (calitem);
+		}
+		calitem->selection_set = TRUE;
+		calitem->selection_start_month_offset = new_start_month_offset;
+		calitem->selection_start_day = new_start_day;
+		calitem->selection_end_month_offset = new_end_month_offset;
+		calitem->selection_end_day = new_end_day;
+
+		calitem->selection_real_start_month_offset = new_start_month_offset;
+		calitem->selection_real_start_day = new_start_day;
+		calitem->selection_from_full_week = FALSE;
+	}
+
+	if (need_update) {
+		g_signal_emit (
+			calitem,
+			e_calendar_item_signals[DATE_RANGE_CHANGED], 0);
+		gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (calitem));
+	}
+}
+
+void
+e_calendar_item_style_set (GtkWidget *widget,
+                           ECalendarItem *calitem)
+{
+	GtkStyle *style;
+	GdkColor *color;
+
+	style = gtk_widget_get_style (widget);
+
+	color = &style->bg[GTK_STATE_SELECTED];
+	calitem->colors[E_CALENDAR_ITEM_COLOR_TODAY_BOX] = *color;
+
+	color = &style->base[GTK_STATE_NORMAL];
+	calitem->colors[E_CALENDAR_ITEM_COLOR_SELECTION_FG] = *color;
+
+	color = &style->bg[GTK_STATE_SELECTED];
+	calitem->colors[E_CALENDAR_ITEM_COLOR_SELECTION_BG_FOCUSED] = *color;
+
+	color = &style->fg[GTK_STATE_INSENSITIVE];
+	calitem->colors[E_CALENDAR_ITEM_COLOR_SELECTION_BG] = *color;
+
+	color = &style->fg[GTK_STATE_INSENSITIVE];
+	calitem->colors[E_CALENDAR_ITEM_COLOR_PREV_OR_NEXT_MONTH_FG] = *color;
+
+	e_calendar_item_recalc_sizes (calitem);
+}
+
+void
+e_calendar_item_set_selection (ECalendarItem *calitem,
+                               const GDate *start_date,
+                               const GDate *end_date)
+{
+	/* If the user is in the middle of a selection, we must abort it. */
+	if (calitem->selecting) {
+		gnome_canvas_item_ungrab (
+			GNOME_CANVAS_ITEM (calitem),
+			GDK_CURRENT_TIME);
+		calitem->selecting = FALSE;
+	}
+
+	e_calendar_item_set_selection_if_emission (calitem,
+						   start_date, end_date,
+						   TRUE);
+}
+
+/* This tries to ensure that the given time range is visible. If the range
+ * given is longer than we can show, only the start of it will be visible.
+ * Note that this will not update the selection. That should be done somewhere
+ * else. It returns TRUE if the visible range has been changed. */
+static gboolean
+e_calendar_item_ensure_days_visible (ECalendarItem *calitem,
+                                     gint start_year,
+                                     gint start_month,
+                                     gint start_day,
+                                     gint end_year,
+                                     gint end_month,
+                                     gint end_day,
+                                     gboolean emission)
+{
+	gint current_end_year, current_end_month;
+	gint months_shown;
+	gint first_day_offset, days_in_month, days_in_prev_month;
+	gboolean need_update = FALSE;
+
+	months_shown = calitem->rows * calitem->cols;
+
+	/* Calculate the range of months currently displayed. */
+	current_end_year = calitem->year;
+	current_end_month = calitem->month + months_shown - 1;
+	e_calendar_item_normalize_date (
+		calitem, &current_end_year,
+		&current_end_month);
+
+	/* Try to ensure that the end month is shown. */
+	if ((end_year == current_end_year + 1 &&
+		current_end_month == 11 && end_month == 0) ||
+	    (end_year == current_end_year && end_month == current_end_month + 1)) {
+		/* See if the end of the selection will fit in the
+		 * leftover days of the month after the last one shown. */
+		calitem->month += (months_shown - 1);
+		e_calendar_item_normalize_date (
+			calitem, &calitem->year,
+			&calitem->month);
+
+		e_calendar_item_get_month_info (
+			calitem, 0, 0,
+			&first_day_offset,
+			&days_in_month,
+			&days_in_prev_month);
+
+		if (end_day >= E_CALENDAR_ROWS_PER_MONTH * E_CALENDAR_COLS_PER_MONTH -
+		    first_day_offset - days_in_month) {
+			need_update = TRUE;
+
+			calitem->year = end_year;
+			calitem->month = end_month - months_shown + 1;
+		} else {
+			calitem->month -= (months_shown - 1);
+		}
+
+		e_calendar_item_normalize_date (
+			calitem, &calitem->year,
+			&calitem->month);
+	}
+	else if (end_year > current_end_year ||
+		 (end_year == current_end_year && end_month > current_end_month)) {
+		/* The selection will definitely not fit in the leftover days
+		 * of the month after the last one shown. */
+		need_update = TRUE;
+
+		calitem->year = end_year;
+		calitem->month = end_month - months_shown + 1;
+
+		e_calendar_item_normalize_date (
+			calitem, &calitem->year,
+			&calitem->month);
+	}
+
+	/* Now try to ensure that the start month is shown. We do this after
+	 * the end month so that the start month will always be shown. */
+	if (start_year < calitem->year
+	    || (start_year == calitem->year
+		&& start_month < calitem->month)) {
+		need_update = TRUE;
+
+		/* First we see if the start of the selection will fit in the
+		 * leftover days of the month before the first one shown. */
+		calitem->year = start_year;
+		calitem->month = start_month + 1;
+		e_calendar_item_normalize_date (
+			calitem, &calitem->year,
+			&calitem->month);
+
+		e_calendar_item_get_month_info (
+			calitem, 0, 0,
+			&first_day_offset,
+			&days_in_month,
+			&days_in_prev_month);
+
+		if (start_day <= days_in_prev_month - first_day_offset) {
+			calitem->year = start_year;
+			calitem->month = start_month;
+		}
+	}
+
+	if (need_update && emission)
+		e_calendar_item_date_range_changed (calitem);
+
+	return need_update;
+}
+
+static gboolean
+destroy_menu_idle_cb (gpointer menu)
+{
+	gtk_widget_destroy (menu);
+
+	return FALSE;
+}
+
+static void
+deactivate_menu_cb (GtkWidget *menu)
+{
+	g_signal_handlers_disconnect_by_func (menu, deactivate_menu_cb, NULL);
+
+	g_idle_add (destroy_menu_idle_cb, menu);
+}
+
+static void
+e_calendar_item_show_popup_menu (ECalendarItem *calitem,
+                                 GdkEvent *button_event,
+                                 gint month_offset)
+{
+	GtkWidget *menu, *submenu, *menuitem, *label;
+	gint year, month;
+	const gchar *name;
+	gchar buffer[64];
+	guint event_button = 0;
+	guint32 event_time;
+
+	menu = gtk_menu_new ();
+
+	for (year = calitem->year - 2; year <= calitem->year + 2; year++) {
+		g_snprintf (buffer, 64, "%i", year);
+		menuitem = gtk_menu_item_new_with_label (buffer);
+		gtk_widget_show (menuitem);
+		gtk_container_add (GTK_CONTAINER (menu), menuitem);
+
+		submenu = gtk_menu_new ();
+		gtk_menu_item_set_submenu (GTK_MENU_ITEM (menuitem), submenu);
+
+		g_object_set_data (
+			G_OBJECT (submenu), "year",
+			GINT_TO_POINTER (year));
+		g_object_set_data (
+			G_OBJECT (submenu), "month_offset",
+			GINT_TO_POINTER (month_offset));
+
+		for (month = 0; month < 12; month++) {
+			name = e_get_month_name (month + 1, FALSE);
+
+			menuitem = gtk_menu_item_new ();
+			gtk_widget_show (menuitem);
+			gtk_container_add (GTK_CONTAINER (submenu), menuitem);
+
+			label = gtk_label_new (name);
+			gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+			gtk_widget_show (label);
+			gtk_container_add (GTK_CONTAINER (menuitem), label);
+
+			g_object_set_data (
+				G_OBJECT (menuitem), "month",
+				GINT_TO_POINTER (month));
+
+			g_signal_connect (
+				menuitem, "activate",
+				G_CALLBACK (e_calendar_item_on_menu_item_activate),
+				calitem);
+		}
+	}
+
+	g_signal_connect (
+		menu, "deactivate",
+		G_CALLBACK (deactivate_menu_cb), NULL);
+
+	gdk_event_get_button (button_event, &event_button);
+	event_time = gdk_event_get_time (button_event);
+
+	gtk_menu_popup (
+		GTK_MENU (menu), NULL, NULL,
+		e_calendar_item_position_menu, calitem,
+		event_button, event_time);
+}
+
+static void
+e_calendar_item_on_menu_item_activate (GtkWidget *menuitem,
+                                       ECalendarItem *calitem)
+{
+	GtkWidget *parent;
+	gint year, month_offset, month;
+	gpointer data;
+
+	parent = gtk_widget_get_parent (menuitem);
+	data = g_object_get_data (G_OBJECT (parent), "year");
+	year = GPOINTER_TO_INT (data);
+
+	parent = gtk_widget_get_parent (menuitem);
+	data = g_object_get_data (G_OBJECT (parent), "month_offset");
+	month_offset = GPOINTER_TO_INT (data);
+
+	data = g_object_get_data (G_OBJECT (menuitem), "month");
+	month = GPOINTER_TO_INT (data);
+
+	month -= month_offset;
+	e_calendar_item_normalize_date (calitem, &year, &month);
+	e_calendar_item_set_first_month (calitem, year, month);
+}
+
+static void
+e_calendar_item_position_menu (GtkMenu *menu,
+                               gint *x,
+                               gint *y,
+                               gboolean *push_in,
+                               gpointer user_data)
+{
+	GtkRequisition requisition;
+	gint screen_width, screen_height;
+
+	gtk_widget_get_preferred_size (GTK_WIDGET (menu), &requisition, NULL);
+
+	*x -= (gtk_widget_get_direction(GTK_WIDGET(menu)) == GTK_TEXT_DIR_RTL)
+		? requisition.width - 2
+		: 2;
+	*y -= requisition.height / 2;
+
+	screen_width = gdk_screen_width ();
+	screen_height = gdk_screen_height ();
+
+	*x = CLAMP (*x, 0, screen_width - requisition.width);
+	*y = CLAMP (*y, 0, screen_height - requisition.height);
+}
+
+/* Sets the function to call to get the colors to use for a particular day. */
+void
+e_calendar_item_set_style_callback (ECalendarItem *calitem,
+                                    ECalendarItemStyleCallback cb,
+                                    gpointer data,
+                                    GDestroyNotify destroy)
+{
+	g_return_if_fail (E_IS_CALENDAR_ITEM (calitem));
+
+	if (calitem->style_callback_data && calitem->style_callback_destroy)
+		(*calitem->style_callback_destroy) (calitem->style_callback_data);
+
+	calitem->style_callback = cb;
+	calitem->style_callback_data = data;
+	calitem->style_callback_destroy = destroy;
+}
+
+static void
+e_calendar_item_date_range_changed (ECalendarItem *calitem)
+{
+	g_free (calitem->styles);
+	calitem->styles = NULL;
+	calitem->date_range_changed = TRUE;
+	e_calendar_item_queue_signal_emission (calitem);
+}
+
+static void
+e_calendar_item_queue_signal_emission (ECalendarItem *calitem)
+{
+	if (calitem->signal_emission_idle_id == 0) {
+		calitem->signal_emission_idle_id = g_idle_add_full (
+			G_PRIORITY_HIGH, (GSourceFunc)
+			e_calendar_item_signal_emission_idle_cb,
+			calitem, NULL);
+	}
+}
+
+static gboolean
+e_calendar_item_signal_emission_idle_cb (gpointer data)
+{
+	ECalendarItem *calitem;
+
+	g_return_val_if_fail (E_IS_CALENDAR_ITEM (data), FALSE);
+
+	calitem = E_CALENDAR_ITEM (data);
+
+	calitem->signal_emission_idle_id = 0;
+
+	/* We ref the calitem & check in case it gets destroyed, since we
+	 * were getting a free memory write here. */
+	g_object_ref ((calitem));
+
+	if (calitem->date_range_changed) {
+		calitem->date_range_changed = FALSE;
+		g_signal_emit (calitem, e_calendar_item_signals[DATE_RANGE_CHANGED], 0);
+	}
+
+	if (calitem->selection_changed) {
+		calitem->selection_changed = FALSE;
+		g_signal_emit (calitem, e_calendar_item_signals[SELECTION_CHANGED], 0);
+	}
+
+	g_object_unref ((calitem));
+
+	return FALSE;
+}
+
+/* Sets a callback to use to get the current time. This is useful if the
+ * application needs to use its own timezone data rather than rely on the
+ * Unix timezone. */
+void
+e_calendar_item_set_get_time_callback (ECalendarItem *calitem,
+                                       ECalendarItemGetTimeCallback cb,
+                                       gpointer data,
+                                       GDestroyNotify destroy)
+{
+	g_return_if_fail (E_IS_CALENDAR_ITEM (calitem));
+
+	if (calitem->time_callback_data && calitem->time_callback_destroy)
+		(*calitem->time_callback_destroy) (calitem->time_callback_data);
+
+	calitem->time_callback = cb;
+	calitem->time_callback_data = data;
+	calitem->time_callback_destroy = destroy;
+}
diff --git a/e-util/e-calendar-item.h b/e-util/e-calendar-item.h
new file mode 100644
index 0000000..a4c0867
--- /dev/null
+++ b/e-util/e-calendar-item.h
@@ -0,0 +1,392 @@
+/*
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Damon Chaplin <damon ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef _E_CALENDAR_ITEM_H_
+#define _E_CALENDAR_ITEM_H_
+
+#include <libgnomecanvas/gnome-canvas.h>
+
+G_BEGIN_DECLS
+
+/*
+ * ECalendarItem - canvas item displaying a calendar.
+ */
+
+#define	E_CALENDAR_ITEM_YPAD_ABOVE_MONTH_NAME	1
+#define	E_CALENDAR_ITEM_YPAD_BELOW_MONTH_NAME	1
+
+/* The number of rows & columns of days in each month. */
+#define E_CALENDAR_ROWS_PER_MONTH	6
+#define E_CALENDAR_COLS_PER_MONTH	7
+
+/* Used to mark days as bold in e_calendar_item_mark_day(). */
+#define E_CALENDAR_ITEM_MARK_BOLD	(1 << 0)
+#define E_CALENDAR_ITEM_MARK_ITALIC     (1 << 1)
+
+/*
+ * These are the padding sizes between various pieces of the calendar.
+ */
+
+/* The minimum padding around the numbers in each cell/day. */
+#define	E_CALENDAR_ITEM_MIN_CELL_XPAD	4
+#define	E_CALENDAR_ITEM_MIN_CELL_YPAD	0
+
+/* Vertical padding. */
+#define	E_CALENDAR_ITEM_YPAD_ABOVE_DAY_LETTERS		1
+#define	E_CALENDAR_ITEM_YPAD_BELOW_DAY_LETTERS		0
+#define	E_CALENDAR_ITEM_YPAD_ABOVE_CELLS		1
+#define	E_CALENDAR_ITEM_YPAD_BELOW_CELLS		2
+
+/* Horizontal padding in the heading bars. */
+#define	E_CALENDAR_ITEM_XPAD_BEFORE_MONTH_NAME_WITH_BUTTON	10
+#define	E_CALENDAR_ITEM_XPAD_BEFORE_MONTH_NAME			3
+#define	E_CALENDAR_ITEM_XPAD_AFTER_MONTH_NAME			3
+#define	E_CALENDAR_ITEM_XPAD_AFTER_MONTH_NAME_WITH_BUTTON	10
+
+/* Horizontal padding in the month displays. */
+#define	E_CALENDAR_ITEM_XPAD_BEFORE_WEEK_NUMBERS	4
+#define	E_CALENDAR_ITEM_XPAD_AFTER_WEEK_NUMBERS		2
+#define	E_CALENDAR_ITEM_XPAD_BEFORE_CELLS		1
+#define	E_CALENDAR_ITEM_XPAD_AFTER_CELLS		4
+
+/* These index our colors array. */
+typedef enum
+{
+	E_CALENDAR_ITEM_COLOR_TODAY_BOX,
+	E_CALENDAR_ITEM_COLOR_SELECTION_FG,
+	E_CALENDAR_ITEM_COLOR_SELECTION_BG_FOCUSED,
+	E_CALENDAR_ITEM_COLOR_SELECTION_BG,
+	E_CALENDAR_ITEM_COLOR_PREV_OR_NEXT_MONTH_FG,
+
+	E_CALENDAR_ITEM_COLOR_LAST
+} ECalendarItemColors;
+
+typedef struct _ECalendarItem       ECalendarItem;
+typedef struct _ECalendarItemClass  ECalendarItemClass;
+
+/* The type of the callback function optionally used to get the colors to
+ * use for each day. */
+typedef void (*ECalendarItemStyleCallback)   (ECalendarItem	*calitem,
+					      gint		 year,
+					      gint		 month,
+					      gint		 day,
+					      gint		 day_style,
+					      gboolean		 today,
+					      gboolean		 prev_or_next_month,
+					      gboolean		 selected,
+					      gboolean		 has_focus,
+					      gboolean		 drop_target,
+					      GdkColor	       **bg_color,
+					      GdkColor	       **fg_color,
+					      GdkColor	       **box_color,
+					      gboolean		*bold,
+					      gboolean		*italic,
+					      gpointer		 data);
+
+/* The type of the callback function optionally used to get the current time.
+ */
+typedef struct tm (*ECalendarItemGetTimeCallback) (ECalendarItem *calitem,
+						   gpointer	  data);
+
+/* Standard GObject macros */
+#define E_TYPE_CALENDAR_ITEM \
+	(e_calendar_item_get_type ())
+#define E_CALENDAR_ITEM(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_CALENDAR_ITEM, ECalendarItem))
+#define E_CALENDAR_ITEM_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_CALENDAR_ITEM, ECalendarItemClass))
+#define E_IS_CALENDAR_ITEM(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_CALENDAR_ITEM))
+#define E_IS_CALENDAR_ITEM_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_CALENDAR_ITEM))
+#define E_CALENDAR_ITEM_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_CALENDAR_ITEM, ECalendarItemClass))
+
+struct _ECalendarItem {
+	GnomeCanvasItem canvas_item;
+
+	/* The year & month of the first calendar being displayed. */
+	gint year;
+	gint month;	/* 0 to 11 */
+
+	/* Points to an array of styles, one gchar for each day. We use 32
+	 * chars for each month, with n + 2 months, where n is the number of
+	 * complete months shown (since we show some days before the first
+	 * month and after the last month grayes out).
+	 * A value of 0 is the default, and 1 is bold. */
+	guint8 *styles;
+
+	/*
+	 * Options.
+	 */
+
+	/* The minimum & maximum number of rows & columns of months.
+	 * If the maximum values are -1 then there is no maximum.
+	 * The minimum valies default to 1. The maximum values to -1. */
+	gint min_rows;
+	gint min_cols;
+	gint max_rows;
+	gint max_cols;
+
+	/* The actual number of rows & columns of months. */
+	gint rows;
+	gint cols;
+
+	/* Whether we show week nubers. */
+	gboolean show_week_numbers;
+	/* whether to keep same week days selected on week number click */
+	gboolean keep_wdays_on_weeknum_click;
+
+	/* The first day of the week, 0 (Monday) to 6 (Sunday). */
+	gint week_start_day;
+
+	/* Whether the cells expand to fill extra space. */
+	gboolean expand;
+
+	/* The maximum number of days that can be selected. Defaults to 1. */
+	gint max_days_selected;
+
+	/* The number of days selected before we switch to selecting whole
+	 * weeks, or -1 if we never switch. Defaults to -1. */
+	gint days_to_start_week_selection;
+
+	/* Whether the selection is moved when we move back/forward one month.
+	 * Used for things like the EDateEdit which only want the selection to
+	 * be changed when the user explicitly selects a day. */
+	gboolean move_selection_when_moving;
+
+	/* Whether the selection day is preserved when we  move back/forward
+	 * one month. Used for the work week and week view. */
+	gboolean preserve_day_when_moving;
+
+	/* Whether to display the pop-up, TRUE by default */
+	gboolean display_popup;
+
+	/*
+	 * Internal stuff.
+	 */
+
+	/* Bounds of item. */
+	gdouble x1, y1, x2, y2;
+
+	/* The minimum size of each month, based on the fonts used. */
+	gint min_month_width;
+	gint min_month_height;
+
+	/* The actual size of each month, after dividing extra space. */
+	gint month_width;
+	gint month_height;
+
+	/* The offset to the left edge of the first calendar. */
+	gint x_offset;
+
+	/* The padding around each calendar month. */
+	gint month_lpad, month_rpad;
+	gint month_tpad, month_bpad;
+
+	/* The size of each cell. */
+	gint cell_width;
+	gint cell_height;
+
+	/* The current selection. The month offsets are from 0, which is the
+	 * top-left calendar month view. Note that -1 is used for the last days
+	 * from the previous month. The days are real month days. */
+	gboolean selecting;
+	GDate *selecting_axis;
+	gboolean selection_dragging_end;
+	gboolean selection_from_full_week;
+	gboolean selection_set;
+	gint selection_start_month_offset;
+	gint selection_start_day;
+	gint selection_end_month_offset;
+	gint selection_end_day;
+	gint selection_real_start_month_offset;
+	gint selection_real_start_day;
+
+	/* Widths of the day characters. */
+	gint day_widths[7];
+	gint max_day_width;
+
+	/* Widths of the digits, '0' .. '9'. */
+	gint digit_widths[10];
+	gint max_digit_width;
+
+	gint week_number_digit_widths[10];
+	gint max_week_number_digit_width;
+
+	gint max_month_name_width;
+
+	/* Fonts for drawing text. If font isn't set it uses the font from the
+	 * canvas widget. If week_number_font isn't set it uses font. */
+	PangoFontDescription *font_desc;
+	PangoFontDescription *week_number_font_desc;
+
+	ECalendarItemStyleCallback style_callback;
+	gpointer style_callback_data;
+	GDestroyNotify style_callback_destroy;
+
+	ECalendarItemGetTimeCallback time_callback;
+	gpointer time_callback_data;
+	GDestroyNotify time_callback_destroy;
+
+	/* Colors for drawing. */
+	GdkColor colors[E_CALENDAR_ITEM_COLOR_LAST];
+
+	/* Our idle handler for emitting signals. */
+	gint signal_emission_idle_id;
+
+	/* A flag to indicate that the selection or date range has changed.
+	 * When set the idle function will emit the signal and reset it to
+	 * FALSE. This is so we don't emit it several times when args are set
+	 * etc. */
+	gboolean selection_changed;
+	gboolean date_range_changed;
+};
+
+struct _ECalendarItemClass {
+	GnomeCanvasItemClass parent_class;
+
+	void (* date_range_changed)	(ECalendarItem *calitem);
+	void (* selection_changed)	(ECalendarItem *calitem);
+	void (* selection_preview_changed)	(ECalendarItem *calitem);
+};
+
+GType	e_calendar_item_get_type		(void);
+
+/* FIXME: months are 0-11 throughout, but 1-12 may be better. */
+
+void	e_calendar_item_get_first_month		(ECalendarItem *calitem,
+						 gint *year,
+						 gint *month);
+void	e_calendar_item_set_first_month		(ECalendarItem *calitem,
+						 gint year,
+						 gint month);
+
+/* Get the maximum number of days selectable */
+gint	e_calendar_item_get_max_days_sel	(ECalendarItem *calitem);
+
+/* Set the maximum number of days selectable */
+void	e_calendar_item_set_max_days_sel	(ECalendarItem *calitem,
+						 gint days);
+
+/* Get the maximum number of days selectable */
+gint	e_calendar_item_get_days_start_week_sel	(ECalendarItem *calitem);
+
+/* Set the maximum number of days selectable */
+void	e_calendar_item_set_days_start_week_sel	(ECalendarItem *calitem,
+						 gint days);
+
+/* Set the maximum number of days before whole weeks are selected */
+gboolean
+	e_calendar_item_get_display_popup	(ECalendarItem *calitem);
+
+/* Get the maximum number of days before whole weeks are selected */
+void	e_calendar_item_set_display_popup	(ECalendarItem *calitem,
+						 gboolean display);
+
+/* Gets the range of dates actually shown. Months are 0 to 11.
+ * This also includes the last days of the previous month and the first days
+ * of the following month, which are normally shown in gray.
+ * It returns FALSE if no dates are currently shown. */
+gboolean
+	e_calendar_item_get_date_range		(ECalendarItem *calitem,
+						 gint *start_year,
+						 gint *start_month,
+						 gint *start_day,
+						 gint *end_year,
+						 gint *end_month,
+						 gint *end_day);
+
+/* Returns the selected date range. It returns FALSE if no days are currently
+ * selected. */
+gboolean
+	e_calendar_item_get_selection		(ECalendarItem *calitem,
+						 GDate *start_date,
+						 GDate *end_date);
+/* Sets the selected date range, and changes the date range shown so at least
+ * the start of the selection is shown. If start_date is NULL it clears the
+ * selection. */
+void	e_calendar_item_set_selection		(ECalendarItem *calitem,
+						 const GDate *start_date,
+						 const GDate *end_date);
+
+/* Marks a particular day. Passing E_CALENDAR_ITEM_MARK_BOLD as the day style
+ * will result in the day being shown as bold by default. The style callback
+ * could support more day_styles, or the style callback could determine the
+ * colors itself, without needing to mark days. */
+void	e_calendar_item_clear_marks		(ECalendarItem *calitem);
+void	e_calendar_item_mark_day		(ECalendarItem *calitem,
+						 gint year,
+						 gint month,
+						 gint day,
+						 guint8 day_style,
+						 gboolean add_day_style);
+
+/* Mark a range of days. Any days outside the currently shown range are
+ * ignored. */
+void	e_calendar_item_mark_days		(ECalendarItem *calitem,
+						 gint start_year,
+						 gint start_month,
+						 gint start_day,
+						 gint end_year,
+						 gint end_month,
+						 gint end_day,
+						 guint8 day_style,
+						 gboolean add_day_style);
+
+/* Sets the function to call to get the colors to use for a particular day. */
+void	e_calendar_item_set_style_callback	(ECalendarItem *calitem,
+						 ECalendarItemStyleCallback cb,
+						 gpointer data,
+						 GDestroyNotify  destroy);
+
+/* Sets a callback to use to get the current time. This is useful if the
+ * application needs to use its own timezone data rather than rely on the
+ * Unix timezone. */
+void	e_calendar_item_set_get_time_callback	(ECalendarItem *calitem,
+						 ECalendarItemGetTimeCallback cb,
+						 gpointer data,
+						 GDestroyNotify  destroy);
+void	e_calendar_item_normalize_date		(ECalendarItem *calitem,
+						 gint *year,
+						 gint *month);
+gint	e_calendar_item_get_week_number		(ECalendarItem *calitem,
+						 gint day,
+						 gint month,
+						 gint year);
+void	e_calendar_item_style_set		(GtkWidget *widget,
+						 ECalendarItem *calitem);
+
+G_END_DECLS
+
+#endif /* _E_CALENDAR_ITEM_H_ */
diff --git a/widgets/misc/e-calendar.c b/e-util/e-calendar.c
similarity index 100%
rename from widgets/misc/e-calendar.c
rename to e-util/e-calendar.c
diff --git a/e-util/e-calendar.h b/e-util/e-calendar.h
new file mode 100644
index 0000000..9a36513
--- /dev/null
+++ b/e-util/e-calendar.h
@@ -0,0 +1,112 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Damon Chaplin <damon ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef _E_CALENDAR_H_
+#define _E_CALENDAR_H_
+
+#include <gtk/gtk.h>
+#include <e-util/e-canvas.h>
+#include <e-util/e-calendar-item.h>
+
+G_BEGIN_DECLS
+
+/*
+ * ECalendar - displays a table of monthly calendars, allowing highlighting
+ * and selection of one or more days. Like GtkCalendar with more features.
+ * Most of the functionality is in the ECalendarItem canvas item, though
+ * we also add GnomeCanvasWidget buttons to go to the previous/next month and
+ * to got to the current day.
+ */
+
+/* Standard GObject macros */
+#define E_TYPE_CALENDAR \
+	(e_calendar_get_type ())
+#define E_CALENDAR(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_CALENDAR, ECalendar))
+#define E_CALENDAR_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_CALENDAR, ECalendarClass))
+#define E_IS_CALENDAR(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_CALENDAR))
+#define E_IS_CALENDAR_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_CALENDAR))
+#define E_CALENDAR_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_CALENDAR, ECalendarClass))
+
+typedef struct _ECalendar ECalendar;
+typedef struct _ECalendarClass  ECalendarClass;
+
+struct _ECalendar {
+	ECanvas parent;
+
+	ECalendarItem *calitem;
+
+	GnomeCanvasItem *prev_item;
+	GnomeCanvasItem *next_item;
+	GnomeCanvasItem *prev_item_year;
+	GnomeCanvasItem *next_item_year;
+
+	gint min_rows;
+	gint min_cols;
+
+	gint max_rows;
+	gint max_cols;
+
+	/* These are all used when the prev/next buttons are held down.
+	 * moving_forward is TRUE if we are moving forward in time, i.e. the
+	 * next button is pressed. */
+	gint timeout_id;
+	gint timeout_delay;
+	gboolean moving_forward;
+};
+
+struct _ECalendarClass {
+	ECanvasClass parent_class;
+};
+
+GType		e_calendar_get_type		(void);
+GtkWidget *	e_calendar_new			(void);
+void		e_calendar_set_minimum_size	(ECalendar *cal,
+						 gint rows,
+						 gint cols);
+void		e_calendar_set_maximum_size	(ECalendar *cal,
+						 gint rows,
+						 gint cols);
+void		e_calendar_get_border_size	(ECalendar *cal,
+						 gint *top,
+						 gint *bottom,
+						 gint *left,
+						 gint *right);
+void		e_calendar_set_focusable	(ECalendar *cal,
+						 gboolean focusable);
+
+G_END_DECLS
+
+#endif /* _E_CALENDAR_H_ */
diff --git a/e-util/e-canvas-background.c b/e-util/e-canvas-background.c
new file mode 100644
index 0000000..6379697
--- /dev/null
+++ b/e-util/e-canvas-background.c
@@ -0,0 +1,279 @@
+/*
+ * e-canvas-background.c - background color for canvas.
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-canvas-background.h"
+
+#include <math.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <gdk/gdkkeysyms.h>
+#include <gtk/gtk.h>
+
+#include <glib/gi18n.h>
+
+#include "e-canvas.h"
+#include "e-canvas-utils.h"
+
+#define E_CANVAS_BACKGROUND_GET_PRIVATE(obj) \
+	(G_TYPE_INSTANCE_GET_PRIVATE \
+	((obj), E_TYPE_CANVAS_BACKGROUND, ECanvasBackgroundPrivate))
+
+/* workaround for avoiding API broken */
+#define ecb_get_type e_canvas_background_get_type
+G_DEFINE_TYPE (
+	ECanvasBackground,
+	ecb,
+	GNOME_TYPE_CANVAS_ITEM)
+
+#define d(x)
+
+struct _ECanvasBackgroundPrivate {
+	guint rgba;		/* Fill color, RGBA */
+};
+
+enum {
+	STYLE_SET,
+	LAST_SIGNAL
+};
+
+static guint ecb_signals[LAST_SIGNAL] = { 0, };
+
+enum {
+	PROP_0,
+	PROP_FILL_COLOR,
+	PROP_FILL_COLOR_GDK,
+	PROP_FILL_COLOR_RGBA,
+};
+
+static void
+ecb_bounds (GnomeCanvasItem *item,
+            gdouble *x1,
+            gdouble *y1,
+            gdouble *x2,
+            gdouble *y2)
+{
+	*x1 = -G_MAXDOUBLE;
+	*y1 = -G_MAXDOUBLE;
+	*x2 = G_MAXDOUBLE;
+	*y2 = G_MAXDOUBLE;
+}
+
+static void
+ecb_update (GnomeCanvasItem *item,
+            const cairo_matrix_t *i2c,
+            gint flags)
+{
+	gdouble x1, y1, x2, y2;
+
+	x1 = item->x1;
+	y1 = item->y1;
+	x2 = item->x2;
+	y2 = item->y2;
+
+	/* The bounds are all constants so we should only have to
+	 * redraw once. */
+	ecb_bounds (item, &item->x1, &item->y1, &item->x2, &item->y2);
+
+	if (item->x1 != x1 || item->y1 != y1 ||
+	    item->x2 != x2 || item->y2 != y2)
+		gnome_canvas_request_redraw (
+			item->canvas, item->x1, item->y1, item->x2, item->y2);
+}
+
+static void
+ecb_set_property (GObject *object,
+                  guint property_id,
+                  const GValue *value,
+                  GParamSpec *pspec)
+{
+	ECanvasBackground *ecb;
+
+	GdkColor color = { 0, 0, 0, 0, };
+	GdkColor *pcolor;
+
+	ecb = E_CANVAS_BACKGROUND (object);
+
+	switch (property_id) {
+	case PROP_FILL_COLOR:
+		if (g_value_get_string (value))
+			gdk_color_parse (g_value_get_string (value), &color);
+
+		ecb->priv->rgba = ((color.red & 0xff00) << 16 |
+				   (color.green & 0xff00) << 8 |
+				   (color.blue & 0xff00) |
+				   0xff);
+		break;
+
+	case PROP_FILL_COLOR_GDK:
+		pcolor = g_value_get_boxed (value);
+		if (pcolor) {
+			color = *pcolor;
+		}
+
+		ecb->priv->rgba = ((color.red & 0xff00) << 16 |
+				   (color.green & 0xff00) << 8 |
+				   (color.blue & 0xff00) |
+				   0xff);
+		break;
+
+	case PROP_FILL_COLOR_RGBA:
+		ecb->priv->rgba = g_value_get_uint (value);
+		break;
+
+	}
+
+	gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (ecb));
+}
+
+static void
+ecb_get_property (GObject *object,
+                  guint property_id,
+                  GValue *value,
+                  GParamSpec *pspec)
+{
+	ECanvasBackground *ecb;
+
+	ecb = E_CANVAS_BACKGROUND (object);
+
+	switch (property_id) {
+	case PROP_FILL_COLOR_RGBA:
+		g_value_set_uint (value, ecb->priv->rgba);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+		break;
+	}
+}
+
+static void
+ecb_init (ECanvasBackground *ecb)
+{
+	ecb->priv = E_CANVAS_BACKGROUND_GET_PRIVATE (ecb);
+}
+
+static void
+ecb_draw (GnomeCanvasItem *item,
+          cairo_t *cr,
+          gint x,
+          gint y,
+          gint width,
+          gint height)
+{
+	ECanvasBackground *ecb = E_CANVAS_BACKGROUND (item);
+
+	cairo_save (cr);
+	cairo_set_source_rgba (
+		cr,
+		((ecb->priv->rgba >> 24) & 0xff) / 255.0,
+		((ecb->priv->rgba >> 16) & 0xff) / 255.0,
+		((ecb->priv->rgba >>  8) & 0xff) / 255.0,
+		( ecb->priv->rgba        & 0xff) / 255.0);
+	cairo_paint (cr);
+	cairo_restore (cr);
+}
+
+static GnomeCanvasItem *
+ecb_point (GnomeCanvasItem *item,
+           gdouble x,
+           gdouble y,
+           gint cx,
+           gint cy)
+{
+	return item;
+}
+
+static void
+ecb_style_set (ECanvasBackground *ecb,
+               GtkStyle *previous_style)
+{
+	GnomeCanvasItem *item = GNOME_CANVAS_ITEM (ecb);
+
+	if (gtk_widget_get_realized (GTK_WIDGET (item->canvas)))
+		gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (ecb));
+}
+
+static void
+ecb_class_init (ECanvasBackgroundClass *ecb_class)
+{
+	GnomeCanvasItemClass *item_class = GNOME_CANVAS_ITEM_CLASS (ecb_class);
+	GObjectClass *object_class = G_OBJECT_CLASS (ecb_class);
+
+	g_type_class_add_private (ecb_class, sizeof (ECanvasBackgroundPrivate));
+
+	object_class->set_property  = ecb_set_property;
+	object_class->get_property  = ecb_get_property;
+
+	item_class->update          = ecb_update;
+	item_class->draw            = ecb_draw;
+	item_class->point           = ecb_point;
+	item_class->bounds          = ecb_bounds;
+
+	ecb_class->style_set	    = ecb_style_set;
+
+	g_object_class_install_property (
+		object_class,
+		PROP_FILL_COLOR,
+		g_param_spec_string (
+			"fill_color",
+			"Fill color",
+			"Fill color",
+			NULL,
+			G_PARAM_WRITABLE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_FILL_COLOR_GDK,
+		g_param_spec_boxed (
+			"fill_color_gdk",
+			"GDK fill color",
+			"GDK fill color",
+			GDK_TYPE_COLOR,
+			G_PARAM_WRITABLE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_FILL_COLOR_RGBA,
+		g_param_spec_uint (
+			"fill_color_rgba",
+			"GDK fill color",
+			"GDK fill color",
+			0, G_MAXUINT, 0,
+			G_PARAM_READWRITE));
+
+	ecb_signals[STYLE_SET] = g_signal_new (
+		"style_set",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ECanvasBackgroundClass, style_set),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__OBJECT,
+		G_TYPE_NONE, 1,
+		GTK_TYPE_STYLE);
+}
+
diff --git a/e-util/e-canvas-background.h b/e-util/e-canvas-background.h
new file mode 100644
index 0000000..c57c64f
--- /dev/null
+++ b/e-util/e-canvas-background.h
@@ -0,0 +1,75 @@
+/*
+ * e-canvas-background.h - background color for canvas.
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_CANVAS_BACKGROUND_H
+#define E_CANVAS_BACKGROUND_H
+
+#include <libgnomecanvas/gnome-canvas.h>
+
+/* Standard GObject macros */
+#define E_TYPE_CANVAS_BACKGROUND \
+	(e_canvas_background_get_type ())
+#define E_CANVAS_BACKGROUND(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_CANVAS_BACKGROUND, ECanvasBackground))
+#define E_CANVAS_BACKGROUND_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_CANVAS_BACKGROUND, ECanvasBackgroundClass))
+#define E_IS_CANVAS_BACKGROUND(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_CANVAS_BACKGROUND))
+#define E_IS_CANVAS_BACKGROUND_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_CANVAS_BACKGROUND))
+#define E_CANVAS_BACKGROUND_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_CANVAS_BACKGROUND, ECanvasBackgroundClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ECanvasBackground ECanvasBackground;
+typedef struct _ECanvasBackgroundClass ECanvasBackgroundClass;
+typedef struct _ECanvasBackgroundPrivate ECanvasBackgroundPrivate;
+
+struct _ECanvasBackground {
+	GnomeCanvasItem item;
+	ECanvasBackgroundPrivate *priv;
+};
+
+struct _ECanvasBackgroundClass {
+	GnomeCanvasItemClass parent_class;
+
+	void		(*style_set)		(ECanvasBackground *eti,
+						 GtkStyle *previous_style);
+};
+
+GType		e_canvas_background_get_type	(void) G_GNUC_CONST;
+
+G_END_DECLS
+
+#endif /* E_CANVAS_BACKGROUND */
diff --git a/widgets/misc/e-canvas-utils.c b/e-util/e-canvas-utils.c
similarity index 100%
rename from widgets/misc/e-canvas-utils.c
rename to e-util/e-canvas-utils.c
diff --git a/e-util/e-canvas-utils.h b/e-util/e-canvas-utils.h
new file mode 100644
index 0000000..c12f156
--- /dev/null
+++ b/e-util/e-canvas-utils.h
@@ -0,0 +1,59 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef __E_CANVAS_UTILS__
+#define __E_CANVAS_UTILS__
+
+#include <libgnomecanvas/gnome-canvas.h>
+
+G_BEGIN_DECLS
+
+void      e_canvas_item_move_absolute      (GnomeCanvasItem *item,
+					    gdouble           dx,
+					    gdouble           dy);
+void      e_canvas_item_show_area          (GnomeCanvasItem *item,
+					    gdouble           x1,
+					    gdouble           y1,
+					    gdouble           x2,
+					    gdouble           y2);
+void      e_canvas_item_show_area_delayed  (GnomeCanvasItem *item,
+					    gdouble           x1,
+					    gdouble           y1,
+					    gdouble           x2,
+					    gdouble           y2,
+					    gint             delay);
+/* Returns TRUE if the area is already shown on the screen (including
+ * spacing.)  This is equivelent to returning FALSE iff show_area
+ * would do anything. */
+gboolean  e_canvas_item_area_shown         (GnomeCanvasItem *item,
+					    gdouble           x1,
+					    gdouble           y1,
+					    gdouble           x2,
+					    gdouble           y2);
+
+G_END_DECLS
+
+#endif /* __E_CANVAS_UTILS__ */
diff --git a/e-util/e-canvas-vbox.c b/e-util/e-canvas-vbox.c
new file mode 100644
index 0000000..f8de013
--- /dev/null
+++ b/e-util/e-canvas-vbox.c
@@ -0,0 +1,410 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <math.h>
+
+#include <gdk/gdkkeysyms.h>
+#include <gtk/gtk.h>
+
+#include <glib/gi18n.h>
+
+#include "e-canvas.h"
+#include "e-canvas-utils.h"
+#include "e-canvas-vbox.h"
+
+static void e_canvas_vbox_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec);
+static void e_canvas_vbox_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec);
+static void e_canvas_vbox_dispose (GObject *object);
+
+static gint e_canvas_vbox_event   (GnomeCanvasItem *item, GdkEvent *event);
+static void e_canvas_vbox_realize (GnomeCanvasItem *item);
+
+static void e_canvas_vbox_reflow (GnomeCanvasItem *item, gint flags);
+
+static void e_canvas_vbox_real_add_item (ECanvasVbox *e_canvas_vbox, GnomeCanvasItem *item);
+static void e_canvas_vbox_real_add_item_start (ECanvasVbox *e_canvas_vbox, GnomeCanvasItem *item);
+static void e_canvas_vbox_resize_children (GnomeCanvasItem *item);
+
+enum {
+	PROP_0,
+	PROP_WIDTH,
+	PROP_MINIMUM_WIDTH,
+	PROP_HEIGHT,
+	PROP_SPACING
+};
+
+G_DEFINE_TYPE (
+	ECanvasVbox,
+	e_canvas_vbox,
+	GNOME_TYPE_CANVAS_GROUP)
+
+static void
+e_canvas_vbox_class_init (ECanvasVboxClass *class)
+{
+	GObjectClass *object_class;
+	GnomeCanvasItemClass *item_class;
+
+	object_class = (GObjectClass *) class;
+	item_class = (GnomeCanvasItemClass *) class;
+
+	class->add_item       = e_canvas_vbox_real_add_item;
+	class->add_item_start = e_canvas_vbox_real_add_item_start;
+
+	object_class->set_property = e_canvas_vbox_set_property;
+	object_class->get_property = e_canvas_vbox_get_property;
+	object_class->dispose      = e_canvas_vbox_dispose;
+
+	/* GnomeCanvasItem method overrides */
+	item_class->event       = e_canvas_vbox_event;
+	item_class->realize     = e_canvas_vbox_realize;
+
+	g_object_class_install_property (
+		object_class,
+		PROP_WIDTH,
+		g_param_spec_double (
+			"width",
+			"Width",
+			"Width",
+			0.0, G_MAXDOUBLE, 0.0,
+			G_PARAM_READWRITE));
+	g_object_class_install_property (
+		object_class,
+		PROP_MINIMUM_WIDTH,
+		g_param_spec_double (
+			"minimum_width",
+			"Minimum width",
+			"Minimum Width",
+			0.0, G_MAXDOUBLE, 0.0,
+			G_PARAM_READWRITE));
+	g_object_class_install_property (
+		object_class,
+		PROP_HEIGHT,
+		g_param_spec_double (
+			"height",
+			"Height",
+			"Height",
+			0.0, G_MAXDOUBLE, 0.0,
+			G_PARAM_READABLE));
+	g_object_class_install_property (
+		object_class,
+		PROP_SPACING,
+		g_param_spec_double (
+			"spacing",
+			"Spacing",
+			"Spacing",
+			0.0, G_MAXDOUBLE, 0.0,
+			G_PARAM_READWRITE));
+}
+
+static void
+e_canvas_vbox_init (ECanvasVbox *vbox)
+{
+	vbox->items = NULL;
+
+	vbox->width = 10;
+	vbox->minimum_width = 10;
+	vbox->height = 10;
+	vbox->spacing = 0;
+
+	e_canvas_item_set_reflow_callback (GNOME_CANVAS_ITEM (vbox), e_canvas_vbox_reflow);
+}
+
+static void
+e_canvas_vbox_set_property (GObject *object,
+                            guint property_id,
+                            const GValue *value,
+                            GParamSpec *pspec)
+{
+	GnomeCanvasItem *item;
+	ECanvasVbox *e_canvas_vbox;
+
+	item = GNOME_CANVAS_ITEM (object);
+	e_canvas_vbox = E_CANVAS_VBOX (object);
+
+	switch (property_id) {
+	case PROP_WIDTH:
+	case PROP_MINIMUM_WIDTH:
+		e_canvas_vbox->minimum_width = g_value_get_double (value);
+		e_canvas_vbox_resize_children (item);
+		e_canvas_item_request_reflow (item);
+		break;
+	case PROP_SPACING:
+		e_canvas_vbox->spacing = g_value_get_double (value);
+		e_canvas_item_request_reflow (item);
+		break;
+	}
+}
+
+static void
+e_canvas_vbox_get_property (GObject *object,
+                            guint property_id,
+                            GValue *value,
+                            GParamSpec *pspec)
+{
+	ECanvasVbox *e_canvas_vbox;
+
+	e_canvas_vbox = E_CANVAS_VBOX (object);
+
+	switch (property_id) {
+	case PROP_WIDTH:
+		g_value_set_double (value, e_canvas_vbox->width);
+		break;
+	case PROP_MINIMUM_WIDTH:
+		g_value_set_double (value, e_canvas_vbox->minimum_width);
+		break;
+	case PROP_HEIGHT:
+		g_value_set_double (value, e_canvas_vbox->height);
+		break;
+	case PROP_SPACING:
+		g_value_set_double (value, e_canvas_vbox->spacing);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+		break;
+	}
+}
+
+/* Used from g_list_foreach(); disconnects from an item's signals */
+static void
+disconnect_item_cb (gpointer data,
+                    gpointer user_data)
+{
+	ECanvasVbox *vbox;
+	GnomeCanvasItem *item;
+
+	vbox = E_CANVAS_VBOX (user_data);
+
+	item = GNOME_CANVAS_ITEM (data);
+	g_signal_handlers_disconnect_matched (
+		item,
+		G_SIGNAL_MATCH_DATA,
+		0, 0, NULL, NULL,
+		vbox);
+}
+
+static void
+e_canvas_vbox_dispose (GObject *object)
+{
+	ECanvasVbox *vbox = E_CANVAS_VBOX (object);
+
+	if (vbox->items) {
+		g_list_foreach (vbox->items, disconnect_item_cb, vbox);
+		g_list_free (vbox->items);
+		vbox->items = NULL;
+	}
+
+	G_OBJECT_CLASS (e_canvas_vbox_parent_class)->dispose (object);
+}
+
+static gint
+e_canvas_vbox_event (GnomeCanvasItem *item,
+                     GdkEvent *event)
+{
+	gint return_val = TRUE;
+
+	switch (event->type) {
+	case GDK_KEY_PRESS:
+		switch (event->key.keyval) {
+		case GDK_KEY_Left:
+		case GDK_KEY_KP_Left:
+		case GDK_KEY_Right:
+		case GDK_KEY_KP_Right:
+		case GDK_KEY_Down:
+		case GDK_KEY_KP_Down:
+		case GDK_KEY_Up:
+		case GDK_KEY_KP_Up:
+		case GDK_KEY_Return:
+		case GDK_KEY_KP_Enter:
+			return_val = TRUE;
+			break;
+		default:
+			return_val = FALSE;
+			break;
+		}
+		break;
+	default:
+		return_val = FALSE;
+		break;
+	}
+	if (!return_val) {
+		if (GNOME_CANVAS_ITEM_CLASS (e_canvas_vbox_parent_class)->event)
+			return GNOME_CANVAS_ITEM_CLASS (e_canvas_vbox_parent_class)->event (item, event);
+	}
+	return return_val;
+
+}
+
+static void
+e_canvas_vbox_realize (GnomeCanvasItem *item)
+{
+	if (GNOME_CANVAS_ITEM_CLASS (e_canvas_vbox_parent_class)->realize)
+		(* GNOME_CANVAS_ITEM_CLASS (e_canvas_vbox_parent_class)->realize) (item);
+
+	e_canvas_vbox_resize_children (item);
+	e_canvas_item_request_reflow (item);
+}
+
+static void
+e_canvas_vbox_remove_item (gpointer data,
+                           GObject *where_object_was)
+{
+	ECanvasVbox *vbox = data;
+	vbox->items = g_list_remove (vbox->items, where_object_was);
+}
+
+static void
+e_canvas_vbox_real_add_item (ECanvasVbox *e_canvas_vbox,
+                             GnomeCanvasItem *item)
+{
+	e_canvas_vbox->items = g_list_append (e_canvas_vbox->items, item);
+	g_object_weak_ref (
+		G_OBJECT (item),
+		e_canvas_vbox_remove_item, e_canvas_vbox);
+	if (GNOME_CANVAS_ITEM (e_canvas_vbox)->flags & GNOME_CANVAS_ITEM_REALIZED) {
+		gnome_canvas_item_set (
+			item,
+			"width", (gdouble) e_canvas_vbox->minimum_width,
+			NULL);
+		e_canvas_item_request_reflow (item);
+	}
+}
+
+static void
+e_canvas_vbox_real_add_item_start (ECanvasVbox *e_canvas_vbox,
+                                   GnomeCanvasItem *item)
+{
+	e_canvas_vbox->items = g_list_prepend (e_canvas_vbox->items, item);
+	g_object_weak_ref (
+		G_OBJECT (item),
+		e_canvas_vbox_remove_item, e_canvas_vbox);
+	if (GNOME_CANVAS_ITEM (e_canvas_vbox)->flags & GNOME_CANVAS_ITEM_REALIZED) {
+		gnome_canvas_item_set (
+			item,
+			"width", (gdouble) e_canvas_vbox->minimum_width,
+			NULL);
+		e_canvas_item_request_reflow (item);
+	}
+}
+
+static void
+e_canvas_vbox_resize_children (GnomeCanvasItem *item)
+{
+	GList *list;
+	ECanvasVbox *e_canvas_vbox;
+
+	e_canvas_vbox = E_CANVAS_VBOX (item);
+	for (list = e_canvas_vbox->items; list; list = list->next) {
+		GnomeCanvasItem *child = GNOME_CANVAS_ITEM (list->data);
+		gnome_canvas_item_set (
+			child,
+			"width", (gdouble) e_canvas_vbox->minimum_width,
+			NULL);
+	}
+}
+
+static void
+e_canvas_vbox_reflow (GnomeCanvasItem *item,
+                      gint flags)
+{
+	ECanvasVbox *e_canvas_vbox = E_CANVAS_VBOX (item);
+	if (item->flags & GNOME_CANVAS_ITEM_REALIZED) {
+
+		gdouble old_height;
+		gdouble running_height;
+		gdouble old_width;
+		gdouble max_width;
+
+		old_width = e_canvas_vbox->width;
+		max_width = e_canvas_vbox->minimum_width;
+
+		old_height = e_canvas_vbox->height;
+		running_height = 0;
+
+		if (e_canvas_vbox->items == NULL) {
+		} else {
+			GList *list;
+			gdouble item_height;
+			gdouble item_width;
+
+			list = e_canvas_vbox->items;
+			g_object_get (
+				list->data,
+				"height", &item_height,
+				"width", &item_width,
+				NULL);
+			e_canvas_item_move_absolute (
+				GNOME_CANVAS_ITEM (list->data),
+				(gdouble) 0,
+				(gdouble) running_height);
+			running_height += item_height;
+			if (max_width < item_width)
+				max_width = item_width;
+			list = g_list_next (list);
+
+			for (; list; list = g_list_next (list)) {
+				running_height += e_canvas_vbox->spacing;
+
+				g_object_get (
+					list->data,
+					"height", &item_height,
+					"width", &item_width,
+					NULL);
+
+				e_canvas_item_move_absolute (
+					GNOME_CANVAS_ITEM (list->data),
+					(gdouble) 0,
+					(gdouble) running_height);
+
+				running_height += item_height;
+				if (max_width < item_width)
+					max_width = item_width;
+			}
+
+		}
+		e_canvas_vbox->height = running_height;
+		e_canvas_vbox->width = max_width;
+		if (old_height != e_canvas_vbox->height ||
+		    old_width !=  e_canvas_vbox->width)
+			e_canvas_item_request_parent_reflow (item);
+	}
+}
+
+void
+e_canvas_vbox_add_item (ECanvasVbox *e_canvas_vbox,
+                        GnomeCanvasItem *item)
+{
+	if (E_CANVAS_VBOX_CLASS (G_OBJECT_GET_CLASS (e_canvas_vbox))->add_item)
+		(E_CANVAS_VBOX_CLASS (G_OBJECT_GET_CLASS (e_canvas_vbox))->add_item) (e_canvas_vbox, item);
+}
+
+void
+e_canvas_vbox_add_item_start (ECanvasVbox *e_canvas_vbox,
+                              GnomeCanvasItem *item)
+{
+	if (E_CANVAS_VBOX_CLASS (G_OBJECT_GET_CLASS (e_canvas_vbox))->add_item_start)
+		(E_CANVAS_VBOX_CLASS (G_OBJECT_GET_CLASS (e_canvas_vbox))->add_item_start) (e_canvas_vbox, item);
+}
+
diff --git a/e-util/e-canvas-vbox.h b/e-util/e-canvas-vbox.h
new file mode 100644
index 0000000..5255e76
--- /dev/null
+++ b/e-util/e-canvas-vbox.h
@@ -0,0 +1,92 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_CANVAS_VBOX_H
+#define E_CANVAS_VBOX_H
+
+#include <gtk/gtk.h>
+#include <libgnomecanvas/gnome-canvas.h>
+
+/* Standard GObject macros */
+#define E_TYPE_CANVAS_VBOX \
+	(e_canvas_vbox_get_type ())
+#define E_CANVAS_VBOX(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_CANVAS_VBOX, ECanvasVbox))
+#define E_CANVAS_VBOX_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_CANVAS_VBOX, ECanvasVboxClass))
+#define E_IS_CANVAS_VBOX(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_CANVAS_VBOX))
+#define E_IS_CANVAS_VBOX_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_CANVAS_VBOX))
+#define E_CANVAS_VBOX_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_CANVAS_VBOX, ECanvasVboxClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ECanvasVbox ECanvasVbox;
+typedef struct _ECanvasVboxClass ECanvasVboxClass;
+
+struct _ECanvasVbox {
+	GnomeCanvasGroup parent;
+
+	/* item specific fields */
+	GList *items; /* Of type GnomeCanvasItem */
+
+	gdouble width;
+	gdouble minimum_width;
+	gdouble height;
+	gdouble spacing;
+};
+
+struct _ECanvasVboxClass {
+	GnomeCanvasGroupClass parent_class;
+
+	void		(*add_item)		(ECanvasVbox *canvas_vbox,
+						 GnomeCanvasItem *item);
+	void		(*add_item_start)	(ECanvasVbox *canvas_vbox,
+						 GnomeCanvasItem *item);
+};
+
+/*
+ * To be added to a CanvasVbox, an item must have the argument "width" as
+ * a Read/Write argument and "height" as a Read Only argument.  It
+ * should also do an ECanvas parent CanvasVbox request if its size
+ * changes.
+ */
+GType		e_canvas_vbox_get_type		(void) G_GNUC_CONST;
+void		e_canvas_vbox_add_item		(ECanvasVbox *canvas_vbox,
+						 GnomeCanvasItem *item);
+void		e_canvas_vbox_add_item_start	(ECanvasVbox *canvas_vbox,
+						 GnomeCanvasItem *item);
+
+G_END_DECLS
+
+#endif /* E_CANVAS_VBOX_H */
diff --git a/e-util/e-canvas.c b/e-util/e-canvas.c
new file mode 100644
index 0000000..d39f9f7
--- /dev/null
+++ b/e-util/e-canvas.c
@@ -0,0 +1,880 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <gtk/gtk.h>
+
+#include "e-canvas.h"
+
+#define d(x)
+
+enum {
+	REFLOW,
+	LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+G_DEFINE_TYPE (
+	ECanvas,
+	e_canvas,
+	GNOME_TYPE_CANVAS)
+
+/* Emits an event for an item in the canvas, be it the current
+ * item, grabbed item, or focused item, as appropriate. */
+static gint
+canvas_emit_event (GnomeCanvas *canvas,
+                   GdkEvent *event)
+{
+	GdkEvent *ev;
+	gint finished;
+	GnomeCanvasItem *item;
+	GnomeCanvasItem *parent;
+	guint mask;
+
+	/* Choose where we send the event */
+
+	item = canvas->current_item;
+
+	if (canvas->focused_item &&
+		((event->type == GDK_KEY_PRESS) ||
+		 (event->type == GDK_KEY_RELEASE) ||
+		 (event->type == GDK_FOCUS_CHANGE)))
+		item = canvas->focused_item;
+
+	if (canvas->grabbed_item)
+		item = canvas->grabbed_item;
+
+	/* Perform checks for grabbed items */
+
+	if (canvas->grabbed_item) {
+		switch (event->type) {
+			case GDK_ENTER_NOTIFY:
+				mask = GDK_ENTER_NOTIFY_MASK;
+				break;
+
+			case GDK_LEAVE_NOTIFY:
+				mask = GDK_LEAVE_NOTIFY_MASK;
+				break;
+
+			case GDK_MOTION_NOTIFY:
+				mask = GDK_POINTER_MOTION_MASK;
+				break;
+
+			case GDK_BUTTON_PRESS:
+			case GDK_2BUTTON_PRESS:
+			case GDK_3BUTTON_PRESS:
+				mask = GDK_BUTTON_PRESS_MASK;
+				break;
+
+			case GDK_BUTTON_RELEASE:
+				mask = GDK_BUTTON_RELEASE_MASK;
+				break;
+
+			case GDK_KEY_PRESS:
+				mask = GDK_KEY_PRESS_MASK;
+				break;
+
+			case GDK_KEY_RELEASE:
+				mask = GDK_KEY_RELEASE_MASK;
+				break;
+
+			default:
+				mask = 0;
+				break;
+		}
+
+		if (!(mask & canvas->grabbed_event_mask))
+			return FALSE;
+	}
+
+	/* Convert to world coordinates -- we have two cases because of
+	 * different offsets of the fields in the event structures. */
+
+	ev = gdk_event_copy (event);
+
+	switch (ev->type) {
+		case GDK_ENTER_NOTIFY:
+		case GDK_LEAVE_NOTIFY:
+			gnome_canvas_window_to_world (
+				canvas,
+				ev->crossing.x, ev->crossing.y,
+				&ev->crossing.x, &ev->crossing.y);
+			break;
+
+		case GDK_MOTION_NOTIFY:
+		case GDK_BUTTON_PRESS:
+		case GDK_2BUTTON_PRESS:
+		case GDK_3BUTTON_PRESS:
+		case GDK_BUTTON_RELEASE:
+			gnome_canvas_window_to_world (
+				canvas,
+				ev->motion.x, ev->motion.y,
+				&ev->motion.x, &ev->motion.y);
+			break;
+
+		default:
+			break;
+	}
+
+	/* The event is propagated up the hierarchy (for if someone connected
+	 * to a group instead of a leaf event), and emission is stopped if a
+	 * handler returns TRUE, just like for GtkWidget events. */
+
+	finished = FALSE;
+
+	while (item && !finished) {
+		g_object_ref (item);
+
+		g_signal_emit_by_name (item, "event", ev, &finished);
+
+		parent = item->parent;
+		g_object_unref (item);
+
+		item = parent;
+	}
+
+	gdk_event_free (ev);
+
+	return finished;
+}
+
+/* This routine invokes the point method of the item.  The argument x, y
+ * should be in the parent's item-relative coordinate system.  This routine
+ * applies the inverse of the item's transform, maintaining the affine
+ * invariant. */
+static GnomeCanvasItem *
+gnome_canvas_item_invoke_point (GnomeCanvasItem *item,
+                                gdouble x,
+                                gdouble y,
+                                gint cx,
+                                gint cy)
+{
+	cairo_matrix_t inverse;
+
+	/* Calculate x & y in item local coordinates */
+	inverse = item->matrix;
+	if (cairo_matrix_invert (&inverse) != CAIRO_STATUS_SUCCESS)
+		return NULL;
+
+	cairo_matrix_transform_point (&inverse, &x, &y);
+
+	if (GNOME_CANVAS_ITEM_GET_CLASS (item)->point)
+		return GNOME_CANVAS_ITEM_GET_CLASS (item)->point (item, x, y, cx, cy);
+
+	return NULL;
+}
+
+/* Re-picks the current item in the canvas, based on the event's coordinates.
+ * Also emits enter/leave events for items as appropriate.
+ */
+#define DISPLAY_X1(canvas) (GNOME_CANVAS (canvas)->layout.xoffset)
+#define DISPLAY_Y1(canvas) (GNOME_CANVAS (canvas)->layout.yoffset)
+static gint
+pick_current_item (GnomeCanvas *canvas,
+                   GdkEvent *event)
+{
+	gint button_down;
+	gdouble x, y;
+	gint cx, cy;
+	gint retval;
+
+	retval = FALSE;
+
+	/* If a button is down, we'll perform enter and leave events on the
+	 * current item, but not enter on any other item.  This is more or less
+	 * like X pointer grabbing for canvas items.
+	 */
+	button_down = canvas->state & (GDK_BUTTON1_MASK
+				       | GDK_BUTTON2_MASK
+				       | GDK_BUTTON3_MASK
+				       | GDK_BUTTON4_MASK
+				       | GDK_BUTTON5_MASK);
+	if (!button_down)
+		canvas->left_grabbed_item = FALSE;
+
+	/* Save the event in the canvas.  This is used to synthesize enter and
+	 * leave events in case the current item changes.  It is also used to
+	 * re-pick the current item if the current one gets deleted.  Also,
+	 * synthesize an enter event.
+	 */
+	if (event != &canvas->pick_event) {
+		if ((event->type == GDK_MOTION_NOTIFY) ||
+		    (event->type == GDK_BUTTON_RELEASE)) {
+			/* these fields have the same offsets in both types of events */
+
+			canvas->pick_event.crossing.type       = GDK_ENTER_NOTIFY;
+			canvas->pick_event.crossing.window     = event->motion.window;
+			canvas->pick_event.crossing.send_event = event->motion.send_event;
+			canvas->pick_event.crossing.subwindow  = NULL;
+			canvas->pick_event.crossing.x          = event->motion.x;
+			canvas->pick_event.crossing.y          = event->motion.y;
+			canvas->pick_event.crossing.mode       = GDK_CROSSING_NORMAL;
+			canvas->pick_event.crossing.detail     = GDK_NOTIFY_NONLINEAR;
+			canvas->pick_event.crossing.focus      = FALSE;
+			canvas->pick_event.crossing.state      = event->motion.state;
+
+			/* these fields don't have the same offsets in both types of events */
+
+			if (event->type == GDK_MOTION_NOTIFY) {
+				canvas->pick_event.crossing.x_root = event->motion.x_root;
+				canvas->pick_event.crossing.y_root = event->motion.y_root;
+			} else {
+				canvas->pick_event.crossing.x_root = event->button.x_root;
+				canvas->pick_event.crossing.y_root = event->button.y_root;
+			}
+		} else
+			canvas->pick_event = *event;
+	}
+
+	/* Don't do anything else if this is a recursive call */
+
+	if (canvas->in_repick)
+		return retval;
+
+	/* LeaveNotify means that there is no current item, so we don't look for one */
+
+	if (canvas->pick_event.type != GDK_LEAVE_NOTIFY) {
+		/* these fields don't have the same offsets in both types of events */
+
+		if (canvas->pick_event.type == GDK_ENTER_NOTIFY) {
+			x = canvas->pick_event.crossing.x +
+				canvas->scroll_x1 - canvas->zoom_xofs;
+			y = canvas->pick_event.crossing.y +
+				canvas->scroll_y1 - canvas->zoom_yofs;
+		} else {
+			x = canvas->pick_event.motion.x +
+				canvas->scroll_x1 - canvas->zoom_xofs;
+			y = canvas->pick_event.motion.y +
+				canvas->scroll_y1 - canvas->zoom_yofs;
+		}
+
+		/* canvas pixel coords */
+
+		cx = (gint) (x + 0.5);
+		cy = (gint) (y + 0.5);
+
+		/* world coords */
+
+		x = canvas->scroll_x1 + x;
+		y = canvas->scroll_y1 + y;
+
+		/* find the closest item */
+
+		if (canvas->root->flags & GNOME_CANVAS_ITEM_VISIBLE)
+			canvas->new_current_item =
+				gnome_canvas_item_invoke_point (
+				canvas->root, x, y, cx, cy);
+		else
+			canvas->new_current_item = NULL;
+	} else
+		canvas->new_current_item = NULL;
+
+	if ((canvas->new_current_item == canvas->current_item) &&
+			!canvas->left_grabbed_item)
+		return retval; /* current item did not change */
+
+	/* Synthesize events for old and new current items */
+
+	if ((canvas->new_current_item != canvas->current_item)
+	    && (canvas->current_item != NULL)
+	    && !canvas->left_grabbed_item) {
+		GdkEvent new_event = { 0 };
+
+		new_event = canvas->pick_event;
+		new_event.type = GDK_LEAVE_NOTIFY;
+
+		new_event.crossing.detail = GDK_NOTIFY_ANCESTOR;
+		new_event.crossing.subwindow = NULL;
+		canvas->in_repick = TRUE;
+		retval = canvas_emit_event (canvas, &new_event);
+		canvas->in_repick = FALSE;
+	}
+
+	/* new_current_item may have been set to NULL during
+	 * the call to canvas_emit_event() above. */
+
+	if ((canvas->new_current_item != canvas->current_item) && button_down) {
+		canvas->left_grabbed_item = TRUE;
+		return retval;
+	}
+
+	/* Handle the rest of cases */
+
+	canvas->left_grabbed_item = FALSE;
+	canvas->current_item = canvas->new_current_item;
+
+	if (canvas->current_item != NULL) {
+		GdkEvent new_event = { 0 };
+
+		new_event = canvas->pick_event;
+		new_event.type = GDK_ENTER_NOTIFY;
+		new_event.crossing.detail = GDK_NOTIFY_ANCESTOR;
+		new_event.crossing.subwindow = NULL;
+		retval = canvas_emit_event (canvas, &new_event);
+	}
+
+	return retval;
+}
+
+static void
+canvas_style_set_recursive (GnomeCanvasItem *item,
+                            GtkStyle *previous_style)
+{
+	guint signal_id = g_signal_lookup ("style_set", G_OBJECT_TYPE (item));
+	if (signal_id >= 1) {
+		GSignalQuery query;
+		g_signal_query (signal_id, &query);
+		if (query.return_type == G_TYPE_NONE &&
+			query.n_params == 1 &&
+			query.param_types[0] == GTK_TYPE_STYLE) {
+			g_signal_emit (item, signal_id, 0, previous_style);
+		}
+	}
+
+	if (GNOME_IS_CANVAS_GROUP (item)) {
+		GList *items = GNOME_CANVAS_GROUP (item)->item_list;
+		for (; items; items = items->next)
+			canvas_style_set_recursive (
+				items->data, previous_style);
+	}
+}
+
+static void
+canvas_dispose (GObject *object)
+{
+	ECanvas *canvas = E_CANVAS (object);
+
+	if (canvas->idle_id)
+		g_source_remove (canvas->idle_id);
+	canvas->idle_id = 0;
+
+	if (canvas->grab_cancelled_check_id)
+		g_source_remove (canvas->grab_cancelled_check_id);
+	canvas->grab_cancelled_check_id = 0;
+
+	if (canvas->toplevel) {
+		if (canvas->visibility_notify_id)
+			g_signal_handler_disconnect (
+				canvas->toplevel,
+				canvas->visibility_notify_id);
+		canvas->visibility_notify_id = 0;
+
+		g_object_unref (canvas->toplevel);
+		canvas->toplevel = NULL;
+	}
+
+	if (canvas->im_context) {
+		g_object_unref (canvas->im_context);
+		canvas->im_context = NULL;
+	}
+
+	/* Chain up to parent's dispose() method. */
+	G_OBJECT_CLASS (e_canvas_parent_class)->dispose (object);
+}
+
+static void
+canvas_realize (GtkWidget *widget)
+{
+	ECanvas *ecanvas = E_CANVAS (widget);
+	GdkWindow *window;
+
+	/* Chain up to parent's realize() method. */
+	GTK_WIDGET_CLASS (e_canvas_parent_class)->realize (widget);
+
+	window = gtk_layout_get_bin_window (GTK_LAYOUT (widget));
+	gdk_window_set_background_pattern (window, NULL);
+
+	window = gtk_widget_get_window (widget);
+	gtk_im_context_set_client_window (ecanvas->im_context, window);
+}
+
+static void
+canvas_unrealize (GtkWidget *widget)
+{
+	ECanvas * ecanvas = E_CANVAS (widget);
+
+	if (ecanvas->idle_id) {
+		g_source_remove (ecanvas->idle_id);
+		ecanvas->idle_id = 0;
+	}
+
+	gtk_im_context_set_client_window (ecanvas->im_context, NULL);
+
+	/* Chain up to parent's unrealize() method. */
+	GTK_WIDGET_CLASS (e_canvas_parent_class)->unrealize (widget);
+}
+
+static void
+canvas_style_set (GtkWidget *widget,
+                  GtkStyle *previous_style)
+{
+	canvas_style_set_recursive (
+		GNOME_CANVAS_ITEM (gnome_canvas_root (
+		GNOME_CANVAS (widget))), previous_style);
+}
+
+static gint
+canvas_button_event (GtkWidget *widget,
+                     GdkEventButton *event)
+{
+	GnomeCanvas *canvas;
+	GdkWindow *bin_window;
+	gint mask;
+	gint retval;
+
+	g_return_val_if_fail (GNOME_IS_CANVAS (widget), FALSE);
+	g_return_val_if_fail (event != NULL, FALSE);
+
+	retval = FALSE;
+
+	canvas = GNOME_CANVAS (widget);
+	bin_window = gtk_layout_get_bin_window (GTK_LAYOUT (canvas));
+
+	d (
+		g_print ("button %d, event type %d, grabbed=%p, current=%p\n",
+		event->button,
+		event->type,
+		canvas->grabbed_item,
+		canvas->current_item));
+
+        /* dispatch normally regardless of the event's window if an item has
+	   has a pointer grab in effect */
+	if (!canvas->grabbed_item && event->window != bin_window)
+		return retval;
+
+	switch (event->button) {
+		case 1:
+			mask = GDK_BUTTON1_MASK;
+			break;
+		case 2:
+			mask = GDK_BUTTON2_MASK;
+			break;
+		case 3:
+			mask = GDK_BUTTON3_MASK;
+			break;
+		case 4:
+			mask = GDK_BUTTON4_MASK;
+			break;
+		case 5:
+			mask = GDK_BUTTON5_MASK;
+			break;
+		default:
+			mask = 0;
+	}
+
+	switch (event->type) {
+		case GDK_BUTTON_PRESS:
+		case GDK_2BUTTON_PRESS:
+		case GDK_3BUTTON_PRESS:
+			/* Pick the current item as if the button were not
+			 * pressed, and then process the event. */
+			canvas->state = event->state;
+			pick_current_item (canvas, (GdkEvent *) event);
+			canvas->state ^= mask;
+			retval = canvas_emit_event (canvas, (GdkEvent *) event);
+			break;
+
+		case GDK_BUTTON_RELEASE:
+			/* Process the event as if the button were pressed,
+			 * then repick after the button has been released. */
+			canvas->state = event->state;
+			retval = canvas_emit_event (canvas, (GdkEvent *) event);
+			event->state ^= mask;
+			canvas->state = event->state;
+			pick_current_item (canvas, (GdkEvent *) event);
+			event->state ^= mask;
+			break;
+
+		default:
+			g_return_val_if_reached (0);
+	}
+
+	return retval;
+}
+
+static gint
+canvas_key_event (GtkWidget *widget,
+                  GdkEventKey *event)
+{
+	GnomeCanvas *canvas;
+	GdkEvent full_event = { 0 };
+
+	g_return_val_if_fail (GNOME_IS_CANVAS (widget), FALSE);
+	g_return_val_if_fail (event != NULL, FALSE);
+
+	canvas = GNOME_CANVAS (widget);
+
+	full_event.type = event->type;
+	full_event.key = *event;
+
+	return canvas_emit_event (canvas, &full_event);
+}
+
+static gint
+canvas_focus_in_event (GtkWidget *widget,
+                       GdkEventFocus *event)
+{
+	GnomeCanvas *canvas;
+	ECanvas *ecanvas;
+	GdkEvent full_event = { 0 };
+
+	canvas = GNOME_CANVAS (widget);
+	ecanvas = E_CANVAS (widget);
+
+	/* XXX Can't access flags directly anymore, but is it really needed?
+	 *     If so, could we call gtk_widget_send_focus_change() instead? */
+#if 0
+	GTK_WIDGET_SET_FLAGS (widget, GTK_HAS_FOCUS);
+#endif
+
+	gtk_im_context_focus_in (ecanvas->im_context);
+
+	if (canvas->focused_item) {
+		full_event.type = event->type;
+		full_event.focus_change = *event;
+		return canvas_emit_event (canvas, &full_event);
+	} else {
+		return FALSE;
+	}
+}
+
+static gint
+canvas_focus_out_event (GtkWidget *widget,
+                        GdkEventFocus *event)
+{
+	GnomeCanvas *canvas;
+	ECanvas *ecanvas;
+	GdkEvent full_event = { 0 };
+
+	canvas = GNOME_CANVAS (widget);
+	ecanvas = E_CANVAS (widget);
+
+	/* XXX Can't access flags directly anymore, but is it really needed?
+	 *     If so, could we call gtk_widget_send_focus_change() instead? */
+#if 0
+	GTK_WIDGET_UNSET_FLAGS (widget, GTK_HAS_FOCUS);
+#endif
+
+	gtk_im_context_focus_out (ecanvas->im_context);
+
+	if (canvas->focused_item) {
+		full_event.type = event->type;
+		full_event.focus_change = *event;
+		return canvas_emit_event (canvas, &full_event);
+	} else {
+		return FALSE;
+	}
+}
+
+static void
+canvas_reflow (ECanvas *canvas)
+{
+	/* Placeholder so subclasses can safely chain up. */
+}
+
+static void
+e_canvas_class_init (ECanvasClass *class)
+{
+	GObjectClass *object_class;
+	GtkWidgetClass *widget_class;
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->dispose = canvas_dispose;
+
+	widget_class = GTK_WIDGET_CLASS (class);
+	widget_class->realize = canvas_realize;
+	widget_class->unrealize = canvas_unrealize;
+	widget_class->style_set = canvas_style_set;
+	widget_class->button_press_event = canvas_button_event;
+	widget_class->button_release_event = canvas_button_event;
+	widget_class->key_press_event = canvas_key_event;
+	widget_class->key_release_event = canvas_key_event;
+	widget_class->focus_in_event = canvas_focus_in_event;
+	widget_class->focus_out_event = canvas_focus_out_event;
+
+	class->reflow = canvas_reflow;
+
+	signals[REFLOW] = g_signal_new (
+		"reflow",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ECanvasClass, reflow),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__VOID,
+		G_TYPE_NONE, 0);
+}
+
+static void
+e_canvas_init (ECanvas *canvas)
+{
+	canvas->im_context = gtk_im_multicontext_new ();
+}
+
+GtkWidget *
+e_canvas_new (void)
+{
+	return g_object_new (E_TYPE_CANVAS, NULL);
+}
+
+/**
+ * e_canvas_item_grab_focus:
+ * @item: A canvas item.
+ * @widget_too: Whether or not to grab the widget-level focus too
+ *
+ * Makes the specified item take the keyboard focus, so all keyboard
+ * events will be sent to it. If the canvas widget itself did not have
+ * the focus and @widget_too is %TRUE, it grabs that focus as well.
+ **/
+void
+e_canvas_item_grab_focus (GnomeCanvasItem *item,
+                          gboolean widget_too)
+{
+	GnomeCanvasItem *focused_item;
+	GdkWindow *bin_window;
+	GdkEvent ev = { 0 };
+
+	g_return_if_fail (GNOME_IS_CANVAS_ITEM (item));
+	g_return_if_fail (gtk_widget_get_can_focus (GTK_WIDGET (item->canvas)));
+
+	bin_window = gtk_layout_get_bin_window (GTK_LAYOUT (item->canvas));
+
+	focused_item = item->canvas->focused_item;
+
+	if (focused_item) {
+		ev.type = GDK_FOCUS_CHANGE;
+		ev.focus_change.type = GDK_FOCUS_CHANGE;
+		ev.focus_change.window = bin_window;
+		ev.focus_change.send_event = FALSE;
+		ev.focus_change.in = FALSE;
+
+		canvas_emit_event (item->canvas, &ev);
+	}
+
+	item->canvas->focused_item = item;
+
+	if (widget_too && !gtk_widget_has_focus (GTK_WIDGET (item->canvas))) {
+		gtk_widget_grab_focus (GTK_WIDGET (item->canvas));
+	}
+
+	if (item) {
+		ev.focus_change.type = GDK_FOCUS_CHANGE;
+		ev.focus_change.window = bin_window;
+		ev.focus_change.send_event = FALSE;
+		ev.focus_change.in = TRUE;
+
+		canvas_emit_event (item->canvas, &ev);
+	}
+}
+
+static void
+e_canvas_item_invoke_reflow (GnomeCanvasItem *item,
+                             gint flags)
+{
+	GnomeCanvasGroup *group;
+	GList *list;
+	GnomeCanvasItem *child;
+
+	if (GNOME_IS_CANVAS_GROUP (item)) {
+		group = GNOME_CANVAS_GROUP (item);
+		for (list = group->item_list; list; list = list->next) {
+			child = GNOME_CANVAS_ITEM (list->data);
+			if (child->flags & E_CANVAS_ITEM_DESCENDENT_NEEDS_REFLOW)
+				e_canvas_item_invoke_reflow (child, flags);
+		}
+	}
+
+	if (item->flags & E_CANVAS_ITEM_NEEDS_REFLOW) {
+		ECanvasItemReflowFunc func;
+		func = (ECanvasItemReflowFunc)
+			g_object_get_data (
+				G_OBJECT (item),
+				"ECanvasItem::reflow_callback");
+		if (func)
+			func (item, flags);
+	}
+
+	item->flags &= ~E_CANVAS_ITEM_NEEDS_REFLOW;
+	item->flags &= ~E_CANVAS_ITEM_DESCENDENT_NEEDS_REFLOW;
+}
+
+static void
+do_reflow (ECanvas *canvas)
+{
+	if (GNOME_CANVAS (canvas)->root->flags & E_CANVAS_ITEM_DESCENDENT_NEEDS_REFLOW)
+		e_canvas_item_invoke_reflow (GNOME_CANVAS (canvas)->root, 0);
+}
+
+/* Idle handler for the e-canvas.  It deals with pending reflows. */
+static gint
+idle_handler (gpointer data)
+{
+	ECanvas *canvas;
+
+	canvas = E_CANVAS (data);
+	do_reflow (canvas);
+
+	/* Reset idle id */
+	canvas->idle_id = 0;
+
+	g_signal_emit (canvas, signals[REFLOW], 0);
+
+	return FALSE;
+}
+
+/* Convenience function to add an idle handler to a canvas */
+static void
+add_idle (ECanvas *canvas)
+{
+	if (canvas->idle_id != 0)
+		return;
+
+	canvas->idle_id = g_idle_add_full (
+		G_PRIORITY_HIGH_IDLE, idle_handler, (gpointer) canvas, NULL);
+}
+
+static void
+e_canvas_item_descendent_needs_reflow (GnomeCanvasItem *item)
+{
+	if (item->flags & E_CANVAS_ITEM_DESCENDENT_NEEDS_REFLOW)
+		return;
+
+	item->flags |= E_CANVAS_ITEM_DESCENDENT_NEEDS_REFLOW;
+	if (item->parent)
+		e_canvas_item_descendent_needs_reflow (item->parent);
+}
+
+void
+e_canvas_item_request_reflow (GnomeCanvasItem *item)
+{
+	g_return_if_fail (GNOME_IS_CANVAS_ITEM (item));
+
+	if (item->flags & GNOME_CANVAS_ITEM_REALIZED) {
+		item->flags |= E_CANVAS_ITEM_NEEDS_REFLOW;
+		e_canvas_item_descendent_needs_reflow (item);
+		add_idle (E_CANVAS (item->canvas));
+	}
+}
+
+void
+e_canvas_item_request_parent_reflow (GnomeCanvasItem *item)
+{
+	g_return_if_fail (GNOME_IS_CANVAS_ITEM (item));
+
+	e_canvas_item_request_reflow (item->parent);
+}
+
+void
+e_canvas_item_set_reflow_callback (GnomeCanvasItem *item,
+                                   ECanvasItemReflowFunc func)
+{
+	g_return_if_fail (GNOME_IS_CANVAS_ITEM (item));
+	g_return_if_fail (func != NULL);
+
+	g_object_set_data (
+		G_OBJECT (item), "ECanvasItem::reflow_callback",
+		(gpointer) func);
+}
+
+static gboolean
+grab_cancelled_check (gpointer data)
+{
+	ECanvas *canvas = data;
+
+	if (GNOME_CANVAS (canvas)->grabbed_item == NULL) {
+		canvas->grab_cancelled_cb = NULL;
+		canvas->grab_cancelled_check_id = 0;
+		canvas->grab_cancelled_time = 0;
+		canvas->grab_cancelled_data = NULL;
+		return FALSE;
+	}
+
+	if (gtk_grab_get_current ()) {
+		gnome_canvas_item_ungrab (
+			GNOME_CANVAS (canvas)->grabbed_item,
+			canvas->grab_cancelled_time);
+		if (canvas->grab_cancelled_cb)
+			canvas->grab_cancelled_cb (
+				canvas, GNOME_CANVAS (canvas)->grabbed_item,
+				canvas->grab_cancelled_data);
+		canvas->grab_cancelled_cb = NULL;
+		canvas->grab_cancelled_check_id = 0;
+		canvas->grab_cancelled_time = 0;
+		canvas->grab_cancelled_data = NULL;
+		return FALSE;
+	}
+	return TRUE;
+}
+
+gint
+e_canvas_item_grab (ECanvas *canvas,
+                    GnomeCanvasItem *item,
+                    guint event_mask,
+                    GdkCursor *cursor,
+                    GdkDevice *device,
+                    guint32 etime,
+                    ECanvasItemGrabCancelled cancelled_cb,
+                    gpointer cancelled_data)
+{
+	GdkGrabStatus grab_status;
+
+	g_return_val_if_fail (E_IS_CANVAS (canvas), -1);
+	g_return_val_if_fail (GNOME_IS_CANVAS_ITEM (item), -1);
+	g_return_val_if_fail (GDK_IS_DEVICE (device), -1);
+
+	if (gtk_grab_get_current ())
+		return GDK_GRAB_ALREADY_GRABBED;
+
+	grab_status = gnome_canvas_item_grab (
+		item, event_mask, cursor, device, etime);
+	if (grab_status == GDK_GRAB_SUCCESS) {
+		canvas->grab_cancelled_cb = cancelled_cb;
+		canvas->grab_cancelled_check_id = g_timeout_add_full (
+			G_PRIORITY_LOW, 100,
+			grab_cancelled_check, canvas, NULL);
+		canvas->grab_cancelled_time = etime;
+		canvas->grab_cancelled_data = cancelled_data;
+	}
+
+	return grab_status;
+}
+
+void
+e_canvas_item_ungrab (ECanvas *canvas,
+                      GnomeCanvasItem *item,
+                      guint32 etime)
+{
+	g_return_if_fail (E_IS_CANVAS (canvas));
+	g_return_if_fail (GNOME_IS_CANVAS_ITEM (item));
+
+	if (canvas->grab_cancelled_check_id) {
+		g_source_remove (canvas->grab_cancelled_check_id);
+		canvas->grab_cancelled_cb = NULL;
+		canvas->grab_cancelled_check_id = 0;
+		canvas->grab_cancelled_time = 0;
+		canvas->grab_cancelled_data = NULL;
+		gnome_canvas_item_ungrab (item, etime);
+	}
+}
diff --git a/e-util/e-canvas.h b/e-util/e-canvas.h
new file mode 100644
index 0000000..8d704b9
--- /dev/null
+++ b/e-util/e-canvas.h
@@ -0,0 +1,141 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_CANVAS_H
+#define E_CANVAS_H
+
+#include <gtk/gtk.h>
+#include <libgnomecanvas/gnome-canvas.h>
+
+/* ECanvas - A class derived from canvas for the purpose of adding
+ * evolution specific canvas hacks. */
+
+/* Standard GObject macros */
+#define E_TYPE_CANVAS \
+	(e_canvas_get_type ())
+#define E_CANVAS(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_CANVAS, ECanvas))
+#define E_CANVAS_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_CANVAS, ECanvasClass))
+#define E_IS_CANVAS(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_CANVAS))
+#define E_IS_CANVAS_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_CANVAS))
+#define E_CANVAS_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_CANVAS, ECanvasClass))
+
+G_BEGIN_DECLS
+
+typedef void	(*ECanvasItemReflowFunc)	(GnomeCanvasItem *item,
+						 gint flags);
+
+typedef void	(*ECanvasItemSelectionFunc)	(GnomeCanvasItem *item,
+						 gint flags,
+						 gpointer user_data);
+/* Returns the same as strcmp does. */
+typedef gint	(*ECanvasItemSelectionCompareFunc)
+						(GnomeCanvasItem *item,
+						 gpointer data1,
+						 gpointer data2,
+						 gint flags);
+
+typedef struct _ECanvas ECanvas;
+typedef struct _ECanvasClass ECanvasClass;
+
+/* Object flags for items */
+enum {
+	E_CANVAS_ITEM_NEEDS_REFLOW            = 1 << 13,
+	E_CANVAS_ITEM_DESCENDENT_NEEDS_REFLOW = 1 << 14
+};
+
+typedef struct {
+	GnomeCanvasItem *item;
+	gpointer         id;
+} ECanvasSelectionInfo;
+
+typedef void	(*ECanvasItemGrabCancelled)	(ECanvas *canvas,
+						 GnomeCanvasItem *item,
+						 gpointer data);
+
+struct _ECanvas {
+	GnomeCanvas parent;
+
+	gint idle_id;
+	GList *selection;
+	ECanvasSelectionInfo *cursor;
+
+	GtkWidget *tooltip_window;
+	gint visibility_notify_id;
+	GtkWidget *toplevel;
+
+	/* Input context for dead key support */
+	GtkIMContext *im_context;
+
+	ECanvasItemGrabCancelled grab_cancelled_cb;
+	guint grab_cancelled_check_id;
+	guint32 grab_cancelled_time;
+	gpointer grab_cancelled_data;
+};
+
+struct _ECanvasClass {
+	GnomeCanvasClass parent_class;
+
+	void		(*reflow)		(ECanvas *canvas);
+};
+
+GType		e_canvas_get_type		(void);
+GtkWidget *	e_canvas_new			(void);
+
+/* Used to send all of the keystroke events to a specific item as well as
+ * GDK_FOCUS_CHANGE events. */
+void		e_canvas_item_grab_focus	(GnomeCanvasItem *item,
+						 gboolean widget_too);
+void		e_canvas_item_request_reflow	(GnomeCanvasItem *item);
+void		e_canvas_item_request_parent_reflow
+						(GnomeCanvasItem *item);
+void		e_canvas_item_set_reflow_callback
+						(GnomeCanvasItem *item,
+						 ECanvasItemReflowFunc func);
+gint		e_canvas_item_grab		(ECanvas *canvas,
+						 GnomeCanvasItem *item,
+						 guint event_mask,
+						 GdkCursor *cursor,
+						 GdkDevice *device,
+						 guint32 etime,
+						 ECanvasItemGrabCancelled cancelled,
+						 gpointer cancelled_data);
+void		e_canvas_item_ungrab		(ECanvas *canvas,
+						 GnomeCanvasItem *item,
+						 guint32 etime);
+
+G_END_DECLS
+
+#endif /* E_CANVAS_H */
diff --git a/e-util/e-categories-config.c b/e-util/e-categories-config.c
index 519fc5d..611c7fa 100644
--- a/e-util/e-categories-config.c
+++ b/e-util/e-categories-config.c
@@ -30,9 +30,8 @@
 #include <gtk/gtk.h>
 #include <glib/gi18n.h>
 
-#include <libedataserverui/libedataserverui.h>
-
-#include "e-util/e-util.h"
+#include "e-categories-dialog.h"
+#include "e-misc-utils.h"
 
 static GHashTable *pixbufs_cache = NULL;
 
diff --git a/e-util/e-categories-config.h b/e-util/e-categories-config.h
index 82294ae..f778e01 100644
--- a/e-util/e-categories-config.h
+++ b/e-util/e-categories-config.h
@@ -23,6 +23,10 @@
  *
  */
 
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
 #ifndef __E_CATEGORIES_CONFIG_H__
 #define __E_CATEGORIES_CONFIG_H__
 
diff --git a/e-util/e-categories-dialog.c b/e-util/e-categories-dialog.c
new file mode 100644
index 0000000..8f46b10
--- /dev/null
+++ b/e-util/e-categories-dialog.c
@@ -0,0 +1,155 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * 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., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+#include <glib/gi18n-lib.h>
+
+#include <libedataserver/libedataserver.h>
+
+#include "e-categories-dialog.h"
+#include "e-categories-editor.h"
+#include "e-categories-selector.h"
+#include "e-category-completion.h"
+#include "e-category-editor.h"
+
+#define E_CATEGORIES_DIALOG_GET_PRIVATE(obj) \
+	(G_TYPE_INSTANCE_GET_PRIVATE \
+	((obj), E_TYPE_CATEGORIES_DIALOG, ECategoriesDialogPrivate))
+
+G_DEFINE_TYPE (ECategoriesDialog, e_categories_dialog, GTK_TYPE_DIALOG)
+
+struct _ECategoriesDialogPrivate {
+	GtkWidget *categories_editor;
+};
+
+static void
+entry_changed_cb (GtkEntry *entry,
+                  ECategoriesDialog *dialog)
+{
+	gtk_dialog_set_response_sensitive (
+		GTK_DIALOG (dialog), GTK_RESPONSE_OK, TRUE);
+}
+
+static void
+e_categories_dialog_class_init (ECategoriesDialogClass *class)
+{
+	g_type_class_add_private (class, sizeof (ECategoriesDialogPrivate));
+}
+
+static void
+e_categories_dialog_init (ECategoriesDialog *dialog)
+{
+	GtkWidget *dialog_content;
+	GtkWidget *categories_editor;
+
+	dialog->priv = E_CATEGORIES_DIALOG_GET_PRIVATE (dialog);
+
+	categories_editor = e_categories_editor_new ();
+	dialog->priv->categories_editor = categories_editor;
+
+	dialog_content = gtk_dialog_get_content_area (GTK_DIALOG (dialog));
+	gtk_container_set_border_width (GTK_CONTAINER (dialog), 12);
+	gtk_box_pack_start (
+		GTK_BOX (dialog_content), categories_editor, TRUE, TRUE, 0);
+	gtk_box_set_spacing (GTK_BOX (dialog_content), 12);
+
+	g_signal_connect (
+		categories_editor, "entry-changed",
+		G_CALLBACK (entry_changed_cb), dialog);
+
+	gtk_dialog_add_buttons (
+		GTK_DIALOG (dialog),
+		GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+		GTK_STOCK_OK, GTK_RESPONSE_OK, NULL);
+	gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK);
+	gtk_dialog_set_response_sensitive (
+		GTK_DIALOG (dialog), GTK_RESPONSE_OK, FALSE);
+	gtk_window_set_title (GTK_WINDOW (dialog), _("Categories"));
+
+	gtk_widget_show_all (categories_editor);
+}
+
+/**
+ * e_categories_dialog_new:
+ * @categories: Comma-separated list of categories
+ *
+ * Creates a new #ECategoriesDialog widget and sets the initial selection
+ * to @categories.
+ *
+ * Returns: a new #ECategoriesDialog
+ **/
+GtkWidget *
+e_categories_dialog_new (const gchar *categories)
+{
+	ECategoriesDialog *dialog;
+
+	dialog = g_object_new (E_TYPE_CATEGORIES_DIALOG, NULL);
+
+	if (categories)
+		e_categories_dialog_set_categories (dialog, categories);
+
+	return GTK_WIDGET (dialog);
+}
+
+/**
+ * e_categories_dialog_get_categories:
+ * @dialog: An #ECategoriesDialog
+ *
+ * Gets a comma-separated list of the categories currently selected
+ * in the dialog.
+ *
+ * Returns: a comma-separated list of categories. Free returned
+ * pointer with g_free().
+ **/
+gchar *
+e_categories_dialog_get_categories (ECategoriesDialog *dialog)
+{
+	gchar *categories;
+
+	g_return_val_if_fail (E_IS_CATEGORIES_DIALOG (dialog), NULL);
+
+	categories = e_categories_editor_get_categories (
+		E_CATEGORIES_EDITOR (dialog->priv->categories_editor));
+
+	return categories;
+}
+
+/**
+ * e_categories_dialog_set_categories:
+ * @dialog: An #ECategoriesDialog
+ * @categories: Comma-separated list of categories
+ *
+ * Sets the list of categories selected on the dialog.
+ **/
+void
+e_categories_dialog_set_categories (ECategoriesDialog *dialog,
+                                    const gchar *categories)
+{
+	g_return_if_fail (E_IS_CATEGORIES_DIALOG (dialog));
+
+	e_categories_editor_set_categories (
+		E_CATEGORIES_EDITOR (dialog->priv->categories_editor),
+		categories);
+}
diff --git a/e-util/e-categories-dialog.h b/e-util/e-categories-dialog.h
new file mode 100644
index 0000000..5ad35e0
--- /dev/null
+++ b/e-util/e-categories-dialog.h
@@ -0,0 +1,73 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * 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., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_CATEGORIES_DIALOG_H
+#define E_CATEGORIES_DIALOG_H
+
+#include <gtk/gtk.h>
+
+/* Standard GObject macros */
+#define E_TYPE_CATEGORIES_DIALOG \
+	(e_categories_dialog_get_type ())
+#define E_CATEGORIES_DIALOG(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_CATEGORIES_DIALOG, ECategoriesDialog))
+#define E_CATEGORIES_DIALOG_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_CATEGORIES_DIALOG, ECategoriesDialogClass))
+#define E_IS_CATEGORIES_DIALOG(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_CATEGORIES_DIALOG))
+#define E_IS_CATEGORIES_DIALOG_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_CATEGORIES_DIALOG))
+#define E_CATEGORIES_DIALOG_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_CATEGORIES_DIALOG, ECategoriesDialogClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ECategoriesDialog ECategoriesDialog;
+typedef struct _ECategoriesDialogClass ECategoriesDialogClass;
+typedef struct _ECategoriesDialogPrivate ECategoriesDialogPrivate;
+
+struct _ECategoriesDialog {
+	GtkDialog parent;
+	ECategoriesDialogPrivate *priv;
+};
+
+struct _ECategoriesDialogClass {
+	GtkDialogClass parent_class;
+};
+
+GType		e_categories_dialog_get_type	(void);
+GtkWidget *	e_categories_dialog_new		(const gchar *categories);
+gchar *		e_categories_dialog_get_categories
+						(ECategoriesDialog *dialog);
+void		e_categories_dialog_set_categories
+						(ECategoriesDialog *dialog,
+						 const gchar *categories);
+
+G_END_DECLS
+
+#endif /* E_CATEGORIES_DIALOG_H */
diff --git a/e-util/e-categories-editor.c b/e-util/e-categories-editor.c
new file mode 100644
index 0000000..ecbebf6
--- /dev/null
+++ b/e-util/e-categories-editor.c
@@ -0,0 +1,435 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * 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., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include <config.h>
+#include <string.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+#include <glib/gi18n-lib.h>
+
+#include <libedataserver/libedataserver.h>
+
+#include "e-categories-editor.h"
+#include "e-categories-selector.h"
+#include "e-category-completion.h"
+#include "e-category-editor.h"
+
+#define E_CATEGORIES_EDITOR_GET_PRIVATE(obj) \
+	(G_TYPE_INSTANCE_GET_PRIVATE \
+	((obj), E_TYPE_CATEGORIES_EDITOR, ECategoriesEditorPrivate))
+
+struct _ECategoriesEditorPrivate {
+	ECategoriesSelector *categories_list;
+	GtkWidget *categories_entry;
+	GtkWidget *categories_entry_label;
+	GtkWidget *new_button;
+	GtkWidget *edit_button;
+	GtkWidget *delete_button;
+
+	guint ignore_category_changes : 1;
+};
+
+enum {
+	COLUMN_ACTIVE,
+	COLUMN_ICON,
+	COLUMN_CATEGORY,
+	N_COLUMNS
+};
+
+enum {
+	PROP_0,
+	PROP_ENTRY_VISIBLE
+};
+
+enum {
+	ENTRY_CHANGED,
+	LAST_SIGNAL
+};
+
+static gint signals[LAST_SIGNAL] = {0};
+
+G_DEFINE_TYPE (ECategoriesEditor, e_categories_editor, GTK_TYPE_GRID)
+
+static void
+entry_changed_cb (GtkEntry *entry,
+                  ECategoriesEditor *editor)
+{
+	g_signal_emit (editor, signals[ENTRY_CHANGED], 0);
+}
+
+static void
+categories_editor_selection_changed_cb (ECategoriesEditor *editor,
+                                        GtkTreeSelection *selection)
+{
+	GtkWidget *widget;
+	gint n_rows;
+
+	n_rows = gtk_tree_selection_count_selected_rows (selection);
+
+	widget = editor->priv->edit_button;
+	gtk_widget_set_sensitive (widget, n_rows == 1);
+
+	widget = editor->priv->delete_button;
+	gtk_widget_set_sensitive (widget, n_rows >= 1);
+}
+
+static void
+category_checked_cb (ECategoriesSelector *selector,
+                     const gchar *category,
+                     const gboolean checked,
+                     ECategoriesEditor *editor)
+{
+	GtkEntry *entry;
+	gchar *categories;
+
+	entry = GTK_ENTRY (editor->priv->categories_entry);
+	categories = e_categories_selector_get_checked (selector);
+
+	gtk_entry_set_text (entry, categories);
+
+	g_free (categories);
+}
+
+static void
+new_button_clicked_cb (GtkButton *button,
+                       ECategoriesEditor *editor)
+{
+	ECategoryEditor *cat_editor = e_category_editor_new ();
+
+	e_category_editor_create_category (cat_editor);
+
+	gtk_widget_destroy (GTK_WIDGET (cat_editor));
+}
+
+static void
+edit_button_clicked_cb (GtkButton *button,
+                        ECategoriesEditor *editor)
+{
+	ECategoryEditor *cat_editor = e_category_editor_new ();
+	gchar *category;
+
+	category = e_categories_selector_get_selected (
+		editor->priv->categories_list);
+
+	e_category_editor_edit_category (cat_editor, category);
+
+	gtk_widget_destroy (GTK_WIDGET (cat_editor));
+	g_free (category);
+}
+
+static void
+categories_editor_set_property (GObject *object,
+                                guint property_id,
+                                const GValue *value,
+                                GParamSpec *pspec)
+{
+	switch (property_id) {
+		case PROP_ENTRY_VISIBLE:
+			e_categories_editor_set_entry_visible (
+				E_CATEGORIES_EDITOR (object),
+				g_value_get_boolean (value));
+			return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+categories_editor_get_property (GObject *object,
+                                guint property_id,
+                                GValue *value,
+                                GParamSpec *pspec)
+{
+	switch (property_id) {
+		case PROP_ENTRY_VISIBLE:
+			g_value_set_boolean (
+				value, e_categories_editor_get_entry_visible (
+				E_CATEGORIES_EDITOR (object)));
+			return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+e_categories_editor_class_init (ECategoriesEditorClass *class)
+{
+	GObjectClass *object_class;
+
+	g_type_class_add_private (class, sizeof (ECategoriesEditorPrivate));
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->set_property = categories_editor_set_property;
+	object_class->get_property = categories_editor_get_property;
+
+	g_object_class_install_property (
+		object_class,
+		PROP_ENTRY_VISIBLE,
+		g_param_spec_boolean (
+			"entry-visible",
+			NULL,
+			NULL,
+			TRUE,
+			G_PARAM_READWRITE));
+
+	signals[ENTRY_CHANGED] = g_signal_new (
+		"entry-changed",
+		G_TYPE_FROM_CLASS (class),
+		G_SIGNAL_RUN_FIRST,
+		G_STRUCT_OFFSET (ECategoriesEditorClass, entry_changed),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__VOID,
+		G_TYPE_NONE, 0);
+}
+
+static void
+e_categories_editor_init (ECategoriesEditor *editor)
+{
+	GtkEntryCompletion *completion;
+	GtkGrid *grid;
+	GtkWidget *entry_categories;
+	GtkWidget *label_header;
+	GtkWidget *label2;
+	GtkWidget *scrolledwindow1;
+	GtkWidget *categories_list;
+	GtkWidget *hbuttonbox1;
+	GtkWidget *button_new;
+	GtkWidget *button_edit;
+	GtkWidget *button_delete;
+
+	gtk_widget_set_size_request (GTK_WIDGET (editor), -1, 400);
+
+	grid = GTK_GRID (editor);
+
+	gtk_grid_set_row_spacing (grid, 6);
+	gtk_grid_set_column_spacing (grid, 6);
+
+	label_header = gtk_label_new_with_mnemonic (
+		_("Currently _used categories:"));
+	gtk_widget_set_halign (label_header, GTK_ALIGN_FILL);
+	gtk_grid_attach (grid, label_header, 0, 0, 1, 1);
+	gtk_label_set_justify (GTK_LABEL (label_header), GTK_JUSTIFY_CENTER);
+	gtk_misc_set_alignment (GTK_MISC (label_header), 0, 0.5);
+
+	entry_categories = gtk_entry_new ();
+	gtk_widget_set_hexpand (entry_categories, TRUE);
+	gtk_widget_set_halign (entry_categories, GTK_ALIGN_FILL);
+	gtk_grid_attach (grid, entry_categories, 0, 1, 1, 1);
+
+	label2 = gtk_label_new_with_mnemonic (_("_Available Categories:"));
+	gtk_widget_set_halign (label2, GTK_ALIGN_FILL);
+	gtk_grid_attach (grid, label2, 0, 2, 1, 1);
+	gtk_label_set_justify (GTK_LABEL (label2), GTK_JUSTIFY_CENTER);
+	gtk_misc_set_alignment (GTK_MISC (label2), 0, 0.5);
+
+	scrolledwindow1 = gtk_scrolled_window_new (NULL, NULL);
+	g_object_set (G_OBJECT (scrolledwindow1),
+		"hexpand", TRUE,
+		"halign", GTK_ALIGN_FILL,
+		"vexpand", TRUE,
+		"valign", GTK_ALIGN_FILL,
+		NULL);
+	gtk_grid_attach (grid, scrolledwindow1, 0, 3, 1, 1);
+	gtk_scrolled_window_set_policy (
+		GTK_SCROLLED_WINDOW (scrolledwindow1),
+		GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
+	gtk_scrolled_window_set_shadow_type (
+		GTK_SCROLLED_WINDOW (scrolledwindow1), GTK_SHADOW_IN);
+
+	categories_list = GTK_WIDGET (e_categories_selector_new ());
+	gtk_container_add (GTK_CONTAINER (scrolledwindow1), categories_list);
+	gtk_widget_set_size_request (categories_list, -1, 350);
+	gtk_tree_view_set_headers_visible (
+		GTK_TREE_VIEW (categories_list), FALSE);
+	gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (categories_list), TRUE);
+	g_signal_connect (
+		G_OBJECT (categories_list), "category-checked",
+		G_CALLBACK (category_checked_cb), editor);
+
+	hbuttonbox1 = gtk_button_box_new (GTK_ORIENTATION_HORIZONTAL);
+	g_object_set (G_OBJECT (hbuttonbox1),
+		"hexpand", TRUE,
+		"halign", GTK_ALIGN_FILL,
+		NULL);
+	gtk_grid_attach (grid, hbuttonbox1, 0, 4, 1, 1);
+	gtk_box_set_spacing (GTK_BOX (hbuttonbox1), 6);
+
+	button_new = gtk_button_new_from_stock (GTK_STOCK_NEW);
+	gtk_container_add (GTK_CONTAINER (hbuttonbox1), button_new);
+	gtk_widget_set_can_default (button_new, TRUE);
+
+	button_edit = gtk_button_new_from_stock (GTK_STOCK_EDIT);
+	gtk_container_add (GTK_CONTAINER (hbuttonbox1), button_edit);
+	gtk_widget_set_can_default (button_edit, TRUE);
+
+	button_delete = gtk_button_new_from_stock (GTK_STOCK_DELETE);
+	gtk_container_add (GTK_CONTAINER (hbuttonbox1), button_delete);
+	gtk_widget_set_can_default (button_delete, TRUE);
+
+	gtk_label_set_mnemonic_widget (
+		GTK_LABEL (label_header), entry_categories);
+	gtk_label_set_mnemonic_widget (
+		GTK_LABEL (label2), categories_list);
+
+	editor->priv = E_CATEGORIES_EDITOR_GET_PRIVATE (editor);
+
+	editor->priv->categories_list = E_CATEGORIES_SELECTOR (categories_list);
+	editor->priv->categories_entry = entry_categories;
+	editor->priv->categories_entry_label = label_header;
+
+	g_signal_connect_swapped (
+		editor->priv->categories_list, "selection-changed",
+		G_CALLBACK (categories_editor_selection_changed_cb), editor);
+
+	completion = e_category_completion_new ();
+	gtk_entry_set_completion (
+		GTK_ENTRY (editor->priv->categories_entry), completion);
+	g_object_unref (completion);
+
+	editor->priv->new_button = button_new;
+	g_signal_connect (
+		editor->priv->new_button, "clicked",
+		G_CALLBACK (new_button_clicked_cb), editor);
+
+	editor->priv->edit_button = button_edit;
+	g_signal_connect (
+		editor->priv->edit_button, "clicked",
+		G_CALLBACK (edit_button_clicked_cb), editor);
+
+	editor->priv->delete_button = button_delete;
+	g_signal_connect_swapped (
+		editor->priv->delete_button, "clicked",
+		G_CALLBACK (e_categories_selector_delete_selection),
+		editor->priv->categories_list);
+
+	g_signal_connect (
+		editor->priv->categories_entry, "changed",
+		G_CALLBACK (entry_changed_cb), editor);
+
+	gtk_widget_show_all (GTK_WIDGET (editor));
+}
+
+/**
+ * e_categories_editor_new:
+ *
+ * Creates a new #ECategoriesEditor widget.
+ *
+ * Returns: a new #ECategoriesEditor
+ *
+ * Since: 3.2
+ **/
+GtkWidget *
+e_categories_editor_new (void)
+{
+	return g_object_new (E_TYPE_CATEGORIES_EDITOR, NULL);
+}
+
+/**
+ * e_categories_editor_get_categories:
+ * @editor: an #ECategoriesEditor
+ *
+ * Gets a comma-separated list of the categories currently selected
+ * in the editor.
+ *
+ * Returns: a comma-separated list of categories. Free returned
+ * pointer with g_free().
+ *
+ * Since: 3.2
+ **/
+gchar *
+e_categories_editor_get_categories (ECategoriesEditor *editor)
+{
+	ECategoriesSelector *categories_list;
+
+	g_return_val_if_fail (E_IS_CATEGORIES_EDITOR (editor), NULL);
+
+	categories_list = editor->priv->categories_list;
+
+	return e_categories_selector_get_checked (categories_list);
+}
+
+/**
+ * e_categories_editor_set_categories:
+ * @editor: an #ECategoriesEditor
+ * @categories: comma-separated list of categories
+ *
+ * Sets the list of categories selected on the editor.
+ *
+ * Since: 3.2
+ **/
+void
+e_categories_editor_set_categories (ECategoriesEditor *editor,
+                                    const gchar *categories)
+{
+	ECategoriesSelector *categories_list;
+
+	g_return_if_fail (E_IS_CATEGORIES_EDITOR (editor));
+
+	categories_list = editor->priv->categories_list;
+
+	e_categories_selector_set_checked (categories_list, categories);
+	category_checked_cb (categories_list, NULL, FALSE, editor);
+}
+
+/**
+ * e_categories_editor_get_entry_visible:
+ * @editor: an #ECategoriesEditor
+ *
+ * Return the visibility of the category input entry.
+ *
+ * Returns: whether the entry is visible
+ *
+ * Since: 3.2
+ **/
+gboolean
+e_categories_editor_get_entry_visible (ECategoriesEditor *editor)
+{
+	g_return_val_if_fail (E_IS_CATEGORIES_EDITOR (editor), TRUE);
+
+	return gtk_widget_get_visible (editor->priv->categories_entry);
+}
+
+/**
+ * e_categories_editor_set_entry_visible:
+ * @editor: an #ECategoriesEditor
+ * @entry_visible: whether to make the entry visible
+ *
+ * Sets the visibility of the category input entry.
+ *
+ * Since: 3.2
+ **/
+void
+e_categories_editor_set_entry_visible (ECategoriesEditor *editor,
+                                       gboolean entry_visible)
+{
+	g_return_if_fail (E_IS_CATEGORIES_EDITOR (editor));
+
+	if ((gtk_widget_get_visible (editor->priv->categories_entry) ? 1 : 0) ==
+	    (entry_visible ? 1 : 0))
+		return;
+
+	gtk_widget_set_visible (
+		editor->priv->categories_entry, entry_visible);
+	gtk_widget_set_visible (
+		editor->priv->categories_entry_label, entry_visible);
+	e_categories_selector_set_items_checkable (
+		editor->priv->categories_list, entry_visible);
+
+	g_object_notify (G_OBJECT (editor), "entry-visible");
+}
diff --git a/e-util/e-categories-editor.h b/e-util/e-categories-editor.h
new file mode 100644
index 0000000..07c9dc6
--- /dev/null
+++ b/e-util/e-categories-editor.h
@@ -0,0 +1,88 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * 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., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_CATEGORIES_EDITOR_H
+#define E_CATEGORIES_EDITOR_H
+
+#include <gtk/gtk.h>
+
+/* Standard GObject macros */
+#define E_TYPE_CATEGORIES_EDITOR \
+	(e_categories_editor_get_type ())
+#define E_CATEGORIES_EDITOR(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_CATEGORIES_EDITOR, ECategoriesEditor))
+#define E_CATEGORIES_EDITOR_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_CATEGORIES_EDITOR, ECategoriesEditorClass))
+#define E_IS_CATEGORIES_EDITOR(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_CATEGORIES_EDITOR))
+#define E_IS_CATEGORIES_EDITOR_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_CATEGORIES_EDITOR))
+#define E_CATEGORIES_EDITOR_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_CATEGORIES_EDITOR, ECategoriesEditorClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ECategoriesEditor ECategoriesEditor;
+typedef struct _ECategoriesEditorClass ECategoriesEditorClass;
+typedef struct _ECategoriesEditorPrivate ECategoriesEditorPrivate;
+
+/**
+ * ECategoriesEditor:
+ *
+ * Contains only private data that should be read and manipulated using the
+ * functions below.
+ *
+ * Since: 3.2
+ **/
+struct _ECategoriesEditor {
+	GtkGrid parent;
+	ECategoriesEditorPrivate *priv;
+};
+
+struct _ECategoriesEditorClass {
+	GtkGridClass parent_class;
+
+	void		(*entry_changed)	(GtkEntry *entry);
+};
+
+GType		e_categories_editor_get_type	(void);
+GtkWidget *	e_categories_editor_new		(void);
+gchar *		e_categories_editor_get_categories
+						(ECategoriesEditor *editor);
+void		e_categories_editor_set_categories
+						(ECategoriesEditor *editor,
+						 const gchar *categories);
+gboolean	e_categories_editor_get_entry_visible
+						(ECategoriesEditor *editor);
+void		e_categories_editor_set_entry_visible
+						(ECategoriesEditor *editor,
+						 gboolean entry_visible);
+
+G_END_DECLS
+
+#endif /* E_CATEGORIES_EDITOR_H */
diff --git a/e-util/e-categories-selector.c b/e-util/e-categories-selector.c
new file mode 100644
index 0000000..5a05238
--- /dev/null
+++ b/e-util/e-categories-selector.c
@@ -0,0 +1,587 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * 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., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include <config.h>
+#include <glib/gi18n-lib.h>
+
+#include <libedataserver/libedataserver.h>
+
+#include "e-categories-selector.h"
+
+#define E_CATEGORIES_SELECTOR_GET_PRIVATE(obj) \
+	(G_TYPE_INSTANCE_GET_PRIVATE \
+	((obj), E_TYPE_CATEGORIES_SELECTOR, ECategoriesSelectorPrivate))
+
+struct _ECategoriesSelectorPrivate {
+	gboolean checkable;
+	GHashTable *selected_categories;
+
+	gboolean ignore_category_changes;
+};
+
+enum {
+	PROP_0,
+	PROP_ITEMS_CHECKABLE
+};
+
+enum {
+	CATEGORY_CHECKED,
+	SELECTION_CHANGED,
+	LAST_SIGNAL
+};
+
+enum {
+	COLUMN_ACTIVE,
+	COLUMN_ICON,
+	COLUMN_CATEGORY,
+	N_COLUMNS
+};
+
+static gint signals[LAST_SIGNAL] = {0};
+
+G_DEFINE_TYPE (
+	ECategoriesSelector,
+	e_categories_selector,
+	GTK_TYPE_TREE_VIEW)
+
+static void
+categories_selector_build_model (ECategoriesSelector *selector)
+{
+	GtkListStore *store;
+	GList *list, *iter;
+
+	store = gtk_list_store_new (
+		N_COLUMNS, G_TYPE_BOOLEAN, GDK_TYPE_PIXBUF, G_TYPE_STRING);
+
+	gtk_tree_sortable_set_sort_column_id (
+		GTK_TREE_SORTABLE (store),
+		COLUMN_CATEGORY, GTK_SORT_ASCENDING);
+
+	list = e_categories_get_list ();
+	for (iter = list; iter != NULL; iter = iter->next) {
+		const gchar *category_name = iter->data;
+		const gchar *filename;
+		GdkPixbuf *pixbuf = NULL;
+		GtkTreeIter iter;
+		gboolean active;
+
+		/* Only add user-visible categories. */
+		if (!e_categories_is_searchable (category_name))
+			continue;
+
+		active = (g_hash_table_lookup (
+				selector->priv->selected_categories,
+				category_name) != NULL);
+
+		filename = e_categories_get_icon_file_for (category_name);
+		if (filename != NULL)
+			pixbuf = gdk_pixbuf_new_from_file (filename, NULL);
+
+		gtk_list_store_append (store, &iter);
+
+		gtk_list_store_set (
+			store, &iter,
+			COLUMN_ACTIVE, active,
+			COLUMN_ICON, pixbuf,
+			COLUMN_CATEGORY, category_name,
+			-1);
+
+		if (pixbuf != NULL)
+			g_object_unref (pixbuf);
+	}
+
+	gtk_tree_view_set_model (
+		GTK_TREE_VIEW (selector), GTK_TREE_MODEL (store));
+
+	/* This has to be reset everytime we install a new model */
+	gtk_tree_view_set_search_column (
+		GTK_TREE_VIEW (selector), COLUMN_CATEGORY);
+
+	g_list_free (list);
+	g_object_unref (store);
+}
+
+static void
+category_toggled_cb (GtkCellRenderer *renderer,
+                     const gchar *path,
+                     ECategoriesSelector *selector)
+{
+	GtkTreeModel *model;
+	GtkTreePath *tree_path;
+	GtkTreeIter iter;
+
+	model = gtk_tree_view_get_model (GTK_TREE_VIEW (selector));
+	g_return_if_fail (model);
+
+	tree_path = gtk_tree_path_new_from_string (path);
+	g_return_if_fail (tree_path);
+
+	if (gtk_tree_model_get_iter (model, &iter, tree_path)) {
+		gchar *category;
+		gboolean active;
+
+		gtk_tree_model_get (
+			model, &iter,
+			COLUMN_ACTIVE, &active,
+			COLUMN_CATEGORY, &category, -1);
+
+		gtk_list_store_set (
+			GTK_LIST_STORE (model), &iter,
+			COLUMN_ACTIVE, !active, -1);
+
+		if (active)
+			g_hash_table_remove (
+				selector->priv->selected_categories, category);
+		else
+			g_hash_table_insert (
+				selector->priv->selected_categories,
+				g_strdup (category), g_strdup (category));
+
+		g_signal_emit (
+			selector, signals[CATEGORY_CHECKED], 0,
+			category, !active);
+
+		g_free (category);
+	}
+
+	gtk_tree_path_free (tree_path);
+}
+
+static void
+categories_selector_listener_cb (gpointer useless_pointer,
+                                 ECategoriesSelector *selector)
+{
+	if (!selector->priv->ignore_category_changes)
+		categories_selector_build_model (selector);
+}
+
+static gboolean
+categories_selector_key_press_event (ECategoriesSelector *selector,
+                                     GdkEventKey *event)
+{
+	if (event->keyval == GDK_KEY_Delete) {
+		e_categories_selector_delete_selection (selector);
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+static void
+categories_selector_selection_changed (GtkTreeSelection *selection,
+                                       ECategoriesSelector *selector)
+{
+	g_signal_emit (selector, signals[SELECTION_CHANGED], 0, selection);
+}
+
+static void
+categories_selector_get_property (GObject *object,
+                                  guint property_id,
+                                  GValue *value,
+                                  GParamSpec *pspec)
+{
+	switch (property_id) {
+		case PROP_ITEMS_CHECKABLE:
+			g_value_set_boolean (
+				value,
+				e_categories_selector_get_items_checkable (
+				E_CATEGORIES_SELECTOR (object)));
+			return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+categories_selector_set_property (GObject *object,
+                                  guint property_id,
+                                  const GValue *value,
+                                  GParamSpec *pspec)
+{
+	switch (property_id) {
+		case PROP_ITEMS_CHECKABLE:
+			e_categories_selector_set_items_checkable (
+				E_CATEGORIES_SELECTOR (object),
+				g_value_get_boolean (value));
+			return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+categories_selector_dispose (GObject *object)
+{
+	ECategoriesSelectorPrivate *priv;
+
+	priv = E_CATEGORIES_SELECTOR_GET_PRIVATE (object);
+
+	if (priv->selected_categories != NULL) {
+		g_hash_table_destroy (priv->selected_categories);
+		priv->selected_categories = NULL;
+	}
+
+	/* Chain up to parent's dispose() method.*/
+	G_OBJECT_CLASS (e_categories_selector_parent_class)->dispose (object);
+}
+
+static void
+categories_selector_finalize (GObject *object)
+{
+	e_categories_unregister_change_listener (
+		G_CALLBACK (categories_selector_listener_cb), object);
+}
+
+static void
+e_categories_selector_class_init (ECategoriesSelectorClass *class)
+{
+	GObjectClass *object_class;
+
+	g_type_class_add_private (class, sizeof (ECategoriesSelectorPrivate));
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->set_property = categories_selector_set_property;
+	object_class->get_property = categories_selector_get_property;
+	object_class->dispose = categories_selector_dispose;
+	object_class->finalize = categories_selector_finalize;
+
+	g_object_class_install_property (
+		object_class,
+		PROP_ITEMS_CHECKABLE,
+		g_param_spec_boolean (
+			"items-checkable",
+			NULL,
+			NULL,
+			TRUE,
+			G_PARAM_READWRITE));
+
+	signals[CATEGORY_CHECKED] = g_signal_new (
+		"category-checked",
+		G_TYPE_FROM_CLASS (class),
+		G_SIGNAL_RUN_FIRST,
+		G_STRUCT_OFFSET (ECategoriesSelectorClass, category_checked),
+		NULL, NULL, NULL,
+		G_TYPE_NONE, 2,
+		G_TYPE_STRING,
+		G_TYPE_BOOLEAN);
+
+	signals[SELECTION_CHANGED] = g_signal_new (
+		"selection-changed",
+		G_TYPE_FROM_CLASS (class),
+		G_SIGNAL_RUN_FIRST,
+		G_STRUCT_OFFSET (ECategoriesSelectorClass, selection_changed),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__OBJECT,
+		G_TYPE_NONE, 1,
+		GTK_TYPE_TREE_SELECTION);
+}
+
+static void
+e_categories_selector_init (ECategoriesSelector *selector)
+{
+	GtkCellRenderer *renderer;
+	GtkTreeViewColumn *column;
+	GtkTreeSelection *selection;
+
+	selector->priv = E_CATEGORIES_SELECTOR_GET_PRIVATE (selector);
+
+	selector->priv->checkable = TRUE;
+	selector->priv->selected_categories = g_hash_table_new_full (
+		(GHashFunc) g_str_hash,
+		(GEqualFunc) g_str_equal,
+		(GDestroyNotify) g_free,
+		(GDestroyNotify) g_free);
+	selector->priv->ignore_category_changes = FALSE;
+
+	renderer = gtk_cell_renderer_toggle_new ();
+	column = gtk_tree_view_column_new_with_attributes (
+		"?", renderer, "active", COLUMN_ACTIVE, NULL);
+	gtk_tree_view_append_column (GTK_TREE_VIEW (selector), column);
+
+	g_signal_connect (
+		renderer, "toggled",
+		G_CALLBACK (category_toggled_cb), selector);
+
+	renderer = gtk_cell_renderer_pixbuf_new ();
+	column = gtk_tree_view_column_new_with_attributes (
+		_("Icon"), renderer, "pixbuf", COLUMN_ICON, NULL);
+	gtk_tree_view_append_column (GTK_TREE_VIEW (selector), column);
+
+	renderer = gtk_cell_renderer_text_new ();
+	column = gtk_tree_view_column_new_with_attributes (
+		_("Category"), renderer, "text", COLUMN_CATEGORY, NULL);
+	gtk_tree_view_append_column (GTK_TREE_VIEW (selector), column);
+
+	selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (selector));
+	g_signal_connect (
+		selection, "changed",
+		G_CALLBACK (categories_selector_selection_changed), selector);
+
+	g_signal_connect (
+		selector, "key-press-event",
+		G_CALLBACK (categories_selector_key_press_event), NULL);
+
+	e_categories_register_change_listener (
+		G_CALLBACK (categories_selector_listener_cb), selector);
+
+	categories_selector_build_model (selector);
+}
+
+/**
+ * e_categories_selector_new:
+ *
+ * Since: 3.2
+ **/
+GtkWidget *
+e_categories_selector_new (void)
+{
+	return g_object_new (
+		E_TYPE_CATEGORIES_SELECTOR,
+		"items-checkable", TRUE, NULL);
+}
+
+/**
+ * e_categories_selector_get_items_checkable:
+ *
+ * Since: 3.2
+ **/
+gboolean
+e_categories_selector_get_items_checkable (ECategoriesSelector *selector)
+{
+	g_return_val_if_fail (E_IS_CATEGORIES_SELECTOR (selector), TRUE);
+
+	return selector->priv->checkable;
+}
+
+/**
+ * e_categories_selector_set_items_checkable:
+ *
+ * Since: 3.2
+ **/
+void
+e_categories_selector_set_items_checkable (ECategoriesSelector *selector,
+                                           gboolean checkable)
+{
+	GtkTreeViewColumn *column;
+
+	g_return_if_fail (E_IS_CATEGORIES_SELECTOR (selector));
+
+	if ((selector->priv->checkable ? 1 : 0) == (checkable ? 1 : 0))
+		return;
+
+	selector->priv->checkable = checkable;
+
+	column = gtk_tree_view_get_column (
+		GTK_TREE_VIEW (selector), COLUMN_ACTIVE);
+	gtk_tree_view_column_set_visible (column, checkable);
+
+	g_object_notify (G_OBJECT (selector), "items-checkable");
+}
+
+/**
+ * e_categories_selector_get_checked:
+ *
+ * Free returned pointer with g_free().
+ *
+ * Since: 3.2
+ **/
+gchar *
+e_categories_selector_get_checked (ECategoriesSelector *selector)
+{
+	GString *str;
+	GList *list, *category;
+
+	g_return_val_if_fail (E_IS_CATEGORIES_SELECTOR (selector), NULL);
+
+	str = g_string_new ("");
+	list = g_hash_table_get_values (selector->priv->selected_categories);
+
+	/* to get them always in the same order */
+	list = g_list_sort (list, (GCompareFunc) g_utf8_collate);
+
+	for (category = list; category != NULL; category = category->next) {
+		if (str->len > 0)
+			g_string_append_printf (
+				str, ",%s", (gchar *) category->data);
+		else
+			g_string_append (str, (gchar *) category->data);
+	}
+
+	g_list_free (list);
+
+	return g_string_free (str, FALSE);
+}
+
+/**
+ * e_categories_selector_set_checked:
+ *
+ * Since: 3.2
+ **/
+void
+e_categories_selector_set_checked (ECategoriesSelector *selector,
+                                   const gchar *categories)
+{
+	GtkTreeModel *model;
+	GtkTreeIter iter;
+	gchar **arr;
+	gint i;
+
+	g_return_if_fail (E_IS_CATEGORIES_SELECTOR (selector));
+
+	/* Clean up table of selected categories. */
+	g_hash_table_remove_all (selector->priv->selected_categories);
+
+	arr = g_strsplit (categories, ",", 0);
+	if (arr) {
+		for (i = 0; arr[i] != NULL; i++) {
+			g_strstrip (arr[i]);
+			g_hash_table_insert (
+				selector->priv->selected_categories,
+				g_strdup (arr[i]), g_strdup (arr[i]));
+		}
+		g_strfreev (arr);
+	}
+
+	model = gtk_tree_view_get_model (GTK_TREE_VIEW (selector));
+	if (gtk_tree_model_get_iter_first (model, &iter)) {
+		do {
+			gchar *category_name;
+			gboolean found;
+
+			gtk_tree_model_get (
+				model, &iter,
+				COLUMN_CATEGORY, &category_name,
+				-1);
+			found = (g_hash_table_lookup (
+				selector->priv->selected_categories,
+				category_name) != NULL);
+			gtk_list_store_set (
+				GTK_LIST_STORE (model), &iter,
+				COLUMN_ACTIVE, found, -1);
+
+			g_free (category_name);
+		} while (gtk_tree_model_iter_next (model, &iter));
+	}
+}
+
+/**
+ * e_categories_selector_delete_selection:
+ *
+ * Since: 3.2
+ **/
+void
+e_categories_selector_delete_selection (ECategoriesSelector *selector)
+{
+	GtkTreeModel *model;
+	GtkTreeSelection *selection;
+	GList *selected, *item;
+
+	g_return_if_fail (E_IS_CATEGORIES_SELECTOR (selector));
+
+	model = gtk_tree_view_get_model (GTK_TREE_VIEW (selector));
+	g_return_if_fail (model != NULL);
+
+	selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (selector));
+	selected = gtk_tree_selection_get_selected_rows (selection, &model);
+
+	/* Remove categories in reverse order to avoid invalidating
+	 * tree paths as we iterate over the list. Note, the list is
+	 * probably already sorted but we sort again just to be safe. */
+	selected = g_list_reverse (g_list_sort (
+		selected, (GCompareFunc) gtk_tree_path_compare));
+
+	/* Prevent the model from being rebuilt every time we
+	 * remove a category, since we're already modifying it. */
+	selector->priv->ignore_category_changes = TRUE;
+
+	for (item = selected; item != NULL; item = item->next) {
+		GtkTreePath *path = item->data;
+		GtkTreeIter iter;
+		gchar *category;
+
+		gtk_tree_model_get_iter (model, &iter, path);
+		gtk_tree_model_get (
+			model, &iter,
+			COLUMN_CATEGORY, &category, -1);
+		gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
+		e_categories_remove (category);
+		g_free (category);
+	}
+
+	selector->priv->ignore_category_changes = FALSE;
+
+	/* If we only remove one category, try to select another */
+	if (g_list_length (selected) == 1) {
+		GtkTreePath *path = selected->data;
+
+		gtk_tree_selection_select_path (selection, path);
+		if (!gtk_tree_selection_path_is_selected (selection, path))
+			if (gtk_tree_path_prev (path))
+				gtk_tree_selection_select_path (selection, path);
+	}
+
+	g_list_foreach (selected, (GFunc) gtk_tree_path_free, NULL);
+	g_list_free (selected);
+}
+
+/**
+ * e_categories_selector_get_selected:
+ *
+ * Free returned pointer with g_free().
+ *
+ * Since: 3.2
+ **/
+gchar *
+e_categories_selector_get_selected (ECategoriesSelector *selector)
+{
+	GtkTreeModel *model;
+	GtkTreeSelection *selection;
+	GList *selected, *item;
+	GString *str = g_string_new ("");
+
+	g_return_val_if_fail (E_IS_CATEGORIES_SELECTOR (selector), NULL);
+
+	model = gtk_tree_view_get_model (GTK_TREE_VIEW (selector));
+	g_return_val_if_fail (model != NULL, NULL);
+
+	selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (selector));
+	selected = gtk_tree_selection_get_selected_rows (selection, &model);
+
+	for (item = selected; item != NULL; item = item->next) {
+		GtkTreePath *path = item->data;
+		GtkTreeIter iter;
+		gchar *category;
+
+		gtk_tree_model_get_iter (model, &iter, path);
+		gtk_tree_model_get (
+			model, &iter,
+			COLUMN_CATEGORY, &category, -1);
+		if (str->len == 0)
+			g_string_assign (str, category);
+		else
+			g_string_append_printf (str, ",%s", category);
+
+		g_free (category);
+	}
+
+	g_list_foreach (selected, (GFunc) gtk_tree_path_free, NULL);
+	g_list_free (selected);
+
+	return g_string_free (str, FALSE);
+}
diff --git a/e-util/e-categories-selector.h b/e-util/e-categories-selector.h
new file mode 100644
index 0000000..6ffc9f8
--- /dev/null
+++ b/e-util/e-categories-selector.h
@@ -0,0 +1,97 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * 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., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_CATEGORIES_SELECTOR_H
+#define E_CATEGORIES_SELECTOR_H
+
+#include <gtk/gtk.h>
+
+/* Standard GObject macros */
+#define E_TYPE_CATEGORIES_SELECTOR \
+	(e_categories_selector_get_type ())
+#define E_CATEGORIES_SELECTOR(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_CATEGORIES_SELECTOR, ECategoriesSelector))
+#define E_CATEGORIES_SELECTOR_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_CATEGORIES_SELECTOR, ECategoriesSelectorClass))
+#define E_IS_CATEGORIES_SELECTOR(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_CATEGORIES_SELECTOR))
+#define E_IS_CATEGORIES_SELECTOR_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_CATEGORIES_SELECTOR))
+#define E_CATEGORIES_SELECTOR_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_CATEGORIES_SELECTOR, ECategoriesSelectorClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ECategoriesSelector ECategoriesSelector;
+typedef struct _ECategoriesSelectorClass ECategoriesSelectorClass;
+typedef struct _ECategoriesSelectorPrivate ECategoriesSelectorPrivate;
+
+/**
+ * ECategoriesSelector:
+ *
+ * Contains only private data that should be read and manipulated using the
+ * functions below.
+ *
+ * Since: 3.2
+ **/
+struct _ECategoriesSelector {
+	GtkTreeView parent;
+	ECategoriesSelectorPrivate *priv;
+};
+
+struct _ECategoriesSelectorClass {
+	GtkTreeViewClass parent_class;
+
+	void		(*category_checked)	(ECategoriesSelector *selector,
+						 const gchar *category,
+						 gboolean checked);
+
+	void		(*selection_changed)	(ECategoriesSelector *selector,
+						 GtkTreeSelection *selection);
+};
+
+GType		e_categories_selector_get_type	(void);
+GtkWidget *	e_categories_selector_new	(void);
+gchar *		e_categories_selector_get_checked
+						(ECategoriesSelector *selector);
+void		e_categories_selector_set_checked
+						(ECategoriesSelector *selector,
+						 const gchar *categories);
+gboolean	e_categories_selector_get_items_checkable
+						(ECategoriesSelector *selector);
+void		e_categories_selector_set_items_checkable
+						(ECategoriesSelector *selectr,
+						 gboolean checkable);
+void		e_categories_selector_delete_selection
+						(ECategoriesSelector *selector);
+gchar *		e_categories_selector_get_selected
+						(ECategoriesSelector *selector);
+
+G_END_DECLS
+
+#endif /* E_CATEGORIES_SELECTOR_H */
diff --git a/e-util/e-category-completion.c b/e-util/e-category-completion.c
new file mode 100644
index 0000000..095df50
--- /dev/null
+++ b/e-util/e-category-completion.c
@@ -0,0 +1,505 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ */
+
+#include "e-category-completion.h"
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <glib/gi18n-lib.h>
+
+#include <libedataserver/libedataserver.h>
+
+#define E_CATEGORY_COMPLETION_GET_PRIVATE(obj) \
+	(G_TYPE_INSTANCE_GET_PRIVATE \
+	((obj), E_TYPE_CATEGORY_COMPLETION, ECategoryCompletionPrivate))
+
+struct _ECategoryCompletionPrivate {
+	GtkWidget *last_known_entry;
+	gchar *create;
+	gchar *prefix;
+};
+
+enum {
+	COLUMN_PIXBUF,
+	COLUMN_CATEGORY,
+	COLUMN_NORMALIZED,
+	NUM_COLUMNS
+};
+
+G_DEFINE_TYPE (
+	ECategoryCompletion,
+	e_category_completion,
+	GTK_TYPE_ENTRY_COMPLETION)
+
+/* Forward Declarations */
+
+static void
+category_completion_track_entry (GtkEntryCompletion *completion);
+
+static void
+category_completion_build_model (GtkEntryCompletion *completion)
+{
+	GtkListStore *store;
+	GList *list;
+
+	store = gtk_list_store_new (
+		NUM_COLUMNS, GDK_TYPE_PIXBUF, G_TYPE_STRING, G_TYPE_STRING);
+
+	list = e_categories_get_list ();
+	while (list != NULL) {
+		const gchar *category = list->data;
+		const gchar *filename;
+		gchar *normalized;
+		gchar *casefolded;
+		GdkPixbuf *pixbuf = NULL;
+		GtkTreeIter iter;
+
+		/* Only add user-visible categories. */
+		if (!e_categories_is_searchable (category)) {
+			list = g_list_delete_link (list, list);
+			continue;
+		}
+
+		filename = e_categories_get_icon_file_for (category);
+		if (filename != NULL && *filename != '\0')
+			pixbuf = gdk_pixbuf_new_from_file (filename, NULL);
+
+		normalized = g_utf8_normalize (
+			category, -1, G_NORMALIZE_DEFAULT);
+		casefolded = g_utf8_casefold (normalized, -1);
+
+		gtk_list_store_append (store, &iter);
+
+		gtk_list_store_set (
+			store, &iter, COLUMN_PIXBUF, pixbuf,
+			COLUMN_CATEGORY, category, COLUMN_NORMALIZED,
+			casefolded, -1);
+
+		g_free (normalized);
+		g_free (casefolded);
+
+		if (pixbuf != NULL)
+			g_object_unref (pixbuf);
+
+		list = g_list_delete_link (list, list);
+	}
+
+	gtk_entry_completion_set_model (completion, GTK_TREE_MODEL (store));
+}
+
+static void
+category_completion_categories_changed_cb (GObject *some_private_object,
+                                           GtkEntryCompletion *completion)
+{
+	category_completion_build_model (completion);
+}
+
+static void
+category_completion_complete (GtkEntryCompletion *completion,
+                              const gchar *category)
+{
+	GtkEditable *editable;
+	GtkWidget *entry;
+	const gchar *text;
+	const gchar *cp;
+	gint start_pos;
+	gint end_pos;
+	glong offset;
+
+	entry = gtk_entry_completion_get_entry (completion);
+
+	editable = GTK_EDITABLE (entry);
+	text = gtk_entry_get_text (GTK_ENTRY (entry));
+
+	/* Get the cursor position as a character offset. */
+	offset = gtk_editable_get_position (editable);
+
+	/* Find the rightmost comma before the cursor. */
+	cp = g_utf8_offset_to_pointer (text, offset);
+	cp = g_utf8_strrchr (text, (gssize) (cp - text), ',');
+
+	/* Calculate the selection start position as a character offset. */
+	if (cp == NULL)
+		offset = 0;
+	else {
+		cp = g_utf8_next_char (cp);
+		if (g_unichar_isspace (g_utf8_get_char (cp)))
+			cp = g_utf8_next_char (cp);
+		offset = g_utf8_pointer_to_offset (text, cp);
+	}
+	start_pos = (gint) offset;
+
+	/* Find the leftmost comma after the cursor. */
+	cp = g_utf8_offset_to_pointer (text, offset);
+	cp = g_utf8_strchr (cp, -1, ',');
+
+	/* Calculate the selection end position as a character offset. */
+	if (cp == NULL)
+		offset = -1;
+	else {
+		cp = g_utf8_next_char (cp);
+		if (g_unichar_isspace (g_utf8_get_char (cp)))
+			cp = g_utf8_next_char (cp);
+		offset = g_utf8_pointer_to_offset (text, cp);
+	}
+	end_pos = (gint) offset;
+
+	/* Complete the partially typed category. */
+	gtk_editable_delete_text (editable, start_pos, end_pos);
+	gtk_editable_insert_text (editable, category, -1, &start_pos);
+	gtk_editable_insert_text (editable, ",", 1, &start_pos);
+	gtk_editable_set_position (editable, start_pos);
+}
+
+static gboolean
+category_completion_is_match (GtkEntryCompletion *completion,
+                              const gchar *key,
+                              GtkTreeIter *iter)
+{
+	ECategoryCompletionPrivate *priv;
+	GtkTreeModel *model;
+	GtkWidget *entry;
+	GValue value = { 0, };
+	gboolean match;
+
+	priv = E_CATEGORY_COMPLETION_GET_PRIVATE (completion);
+	entry = gtk_entry_completion_get_entry (completion);
+	model = gtk_entry_completion_get_model (completion);
+
+	/* XXX This would be easier if GtkEntryCompletion had an 'entry'
+	 *     property that we could listen to for notifications. */
+	if (entry != priv->last_known_entry)
+		category_completion_track_entry (completion);
+
+	if (priv->prefix == NULL)
+		return FALSE;
+
+	gtk_tree_model_get_value (model, iter, COLUMN_NORMALIZED, &value);
+	match = g_str_has_prefix (g_value_get_string (&value), priv->prefix);
+	g_value_unset (&value);
+
+	return match;
+}
+
+static void
+category_completion_update_prefix (GtkEntryCompletion *completion)
+{
+	ECategoryCompletionPrivate *priv;
+	GtkEditable *editable;
+	GtkTreeModel *model;
+	GtkWidget *entry;
+	GtkTreeIter iter;
+	const gchar *text;
+	const gchar *start;
+	const gchar *end;
+	const gchar *cp;
+	gboolean valid;
+	gchar *input;
+	glong offset;
+
+	priv = E_CATEGORY_COMPLETION_GET_PRIVATE (completion);
+	entry = gtk_entry_completion_get_entry (completion);
+	model = gtk_entry_completion_get_model (completion);
+
+	/* XXX This would be easier if GtkEntryCompletion had an 'entry'
+	 *     property that we could listen to for notifications. */
+	if (entry != priv->last_known_entry) {
+		category_completion_track_entry (completion);
+		return;
+	}
+
+	editable = GTK_EDITABLE (entry);
+	text = gtk_entry_get_text (GTK_ENTRY (entry));
+
+	/* Get the cursor position as a character offset. */
+	offset = gtk_editable_get_position (editable);
+
+	/* Find the rightmost comma before the cursor. */
+	cp = g_utf8_offset_to_pointer (text, offset);
+	cp = g_utf8_strrchr (text, (gsize) (cp - text), ',');
+
+	/* Mark the start of the prefix. */
+	if (cp == NULL)
+		start = text;
+	else {
+		cp = g_utf8_next_char (cp);
+		if (g_unichar_isspace (g_utf8_get_char (cp)))
+			cp = g_utf8_next_char (cp);
+		start = cp;
+	}
+
+	/* Find the leftmost comma after the cursor. */
+	cp = g_utf8_offset_to_pointer (text, offset);
+	cp = g_utf8_strchr (cp, -1, ',');
+
+	/* Mark the end of the prefix. */
+	if (cp == NULL)
+		end = text + strlen (text);
+	else
+		end = cp;
+
+	if (priv->create != NULL)
+		gtk_entry_completion_delete_action (completion, 0);
+
+	g_free (priv->create);
+	priv->create = NULL;
+
+	g_free (priv->prefix);
+	priv->prefix = NULL;
+
+	if (start == end)
+		return;
+
+	input = g_strstrip (g_strndup (start, end - start));
+	priv->create = input;
+
+	input = g_utf8_normalize (input, -1, G_NORMALIZE_DEFAULT);
+	priv->prefix = g_utf8_casefold (input, -1);
+	g_free (input);
+
+	if (*priv->create == '\0') {
+		g_free (priv->create);
+		priv->create = NULL;
+		return;
+	}
+
+	valid = gtk_tree_model_get_iter_first (model, &iter);
+	while (valid) {
+		GValue value = { 0, };
+
+		gtk_tree_model_get_value (
+			model, &iter, COLUMN_NORMALIZED, &value);
+		if (strcmp (g_value_get_string (&value), priv->prefix) == 0) {
+			g_value_unset (&value);
+			g_free (priv->create);
+			priv->create = NULL;
+			return;
+		}
+		g_value_unset (&value);
+
+		valid = gtk_tree_model_iter_next (model, &iter);
+	}
+
+	input = g_strdup_printf (_("Create category \"%s\""), priv->create);
+	gtk_entry_completion_insert_action_text (completion, 0, input);
+	g_free (input);
+}
+
+static gboolean
+category_completion_sanitize_suffix (GtkEntry *entry,
+                                     GdkEventFocus *event,
+                                     GtkEntryCompletion *completion)
+{
+	const gchar *text;
+
+	g_return_val_if_fail (entry != NULL, FALSE);
+	g_return_val_if_fail (completion != NULL, FALSE);
+
+	text = gtk_entry_get_text (entry);
+	if (text) {
+		gint len = strlen (text), old_len = len;
+
+		while (len > 0 && (text[len -1] == ' ' || text[len - 1] == ','))
+			len--;
+
+		if (old_len != len) {
+			gchar *tmp = g_strndup (text, len);
+
+			gtk_entry_set_text (entry, tmp);
+
+			g_free (tmp);
+		}
+	}
+
+	return FALSE;
+}
+
+static void
+category_completion_track_entry (GtkEntryCompletion *completion)
+{
+	ECategoryCompletionPrivate *priv;
+
+	priv = E_CATEGORY_COMPLETION_GET_PRIVATE (completion);
+
+	if (priv->last_known_entry != NULL) {
+		g_signal_handlers_disconnect_matched (
+			priv->last_known_entry, G_SIGNAL_MATCH_DATA,
+			0, 0, NULL, NULL, completion);
+		g_object_unref (priv->last_known_entry);
+	}
+
+	g_free (priv->prefix);
+	priv->prefix = NULL;
+
+	priv->last_known_entry = gtk_entry_completion_get_entry (completion);
+	if (priv->last_known_entry == NULL)
+		return;
+
+	g_object_ref (priv->last_known_entry);
+
+	g_signal_connect_swapped (
+		priv->last_known_entry, "notify::cursor-position",
+		G_CALLBACK (category_completion_update_prefix), completion);
+
+	g_signal_connect_swapped (
+		priv->last_known_entry, "notify::text",
+		G_CALLBACK (category_completion_update_prefix), completion);
+
+	g_signal_connect (
+		priv->last_known_entry, "focus-out-event",
+		G_CALLBACK (category_completion_sanitize_suffix), completion);
+
+	category_completion_update_prefix (completion);
+}
+
+static void
+category_completion_constructed (GObject *object)
+{
+	GtkCellRenderer *renderer;
+	GtkEntryCompletion *completion;
+
+	/* Chain up to parent's constructed() method. */
+	G_OBJECT_CLASS (e_category_completion_parent_class)->constructed (object);
+
+	completion = GTK_ENTRY_COMPLETION (object);
+
+	gtk_entry_completion_set_match_func (
+		completion, (GtkEntryCompletionMatchFunc)
+		category_completion_is_match, NULL, NULL);
+
+	gtk_entry_completion_set_text_column (completion, COLUMN_CATEGORY);
+
+	renderer = gtk_cell_renderer_pixbuf_new ();
+	gtk_cell_layout_pack_start (
+		GTK_CELL_LAYOUT (completion), renderer, FALSE);
+	gtk_cell_layout_add_attribute (
+		GTK_CELL_LAYOUT (completion),
+		renderer, "pixbuf", COLUMN_PIXBUF);
+	gtk_cell_layout_reorder (
+		GTK_CELL_LAYOUT (completion), renderer, 0);
+
+	e_categories_register_change_listener (
+		G_CALLBACK (category_completion_categories_changed_cb),
+		completion);
+
+	category_completion_build_model (completion);
+}
+
+static void
+category_completion_dispose (GObject *object)
+{
+	ECategoryCompletionPrivate *priv;
+
+	priv = E_CATEGORY_COMPLETION_GET_PRIVATE (object);
+
+	if (priv->last_known_entry != NULL) {
+		g_signal_handlers_disconnect_matched (
+			priv->last_known_entry, G_SIGNAL_MATCH_DATA,
+			0, 0, NULL, NULL, object);
+		g_object_unref (priv->last_known_entry);
+		priv->last_known_entry = NULL;
+	}
+
+	/* Chain up to parent's dispose() method. */
+	G_OBJECT_CLASS (e_category_completion_parent_class)->dispose (object);
+}
+
+static void
+category_completion_finalize (GObject *object)
+{
+	ECategoryCompletionPrivate *priv;
+
+	priv = E_CATEGORY_COMPLETION_GET_PRIVATE (object);
+
+	g_free (priv->create);
+	g_free (priv->prefix);
+
+	e_categories_unregister_change_listener (
+		G_CALLBACK (category_completion_categories_changed_cb),
+		object);
+
+	/* Chain up to parent's finalize() method. */
+	G_OBJECT_CLASS (e_category_completion_parent_class)->finalize (object);
+}
+
+static gboolean
+category_completion_match_selected (GtkEntryCompletion *completion,
+                                    GtkTreeModel *model,
+                                    GtkTreeIter *iter)
+{
+	GValue value = { 0, };
+
+	gtk_tree_model_get_value (model, iter, COLUMN_CATEGORY, &value);
+	category_completion_complete (completion, g_value_get_string (&value));
+	g_value_unset (&value);
+
+	return TRUE;
+}
+
+static void
+category_completion_action_activated (GtkEntryCompletion *completion,
+                                      gint index)
+{
+	ECategoryCompletionPrivate *priv;
+	gchar *category;
+
+	priv = E_CATEGORY_COMPLETION_GET_PRIVATE (completion);
+
+	category = g_strdup (priv->create);
+	e_categories_add (category, NULL, NULL, TRUE);
+	category_completion_complete (completion, category);
+	g_free (category);
+}
+
+static void
+e_category_completion_class_init (ECategoryCompletionClass *class)
+{
+	GObjectClass *object_class;
+	GtkEntryCompletionClass *entry_completion_class;
+
+	g_type_class_add_private (class, sizeof (ECategoryCompletionPrivate));
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->constructed = category_completion_constructed;
+	object_class->dispose = category_completion_dispose;
+	object_class->finalize = category_completion_finalize;
+
+	entry_completion_class = GTK_ENTRY_COMPLETION_CLASS (class);
+	entry_completion_class->match_selected = category_completion_match_selected;
+	entry_completion_class->action_activated = category_completion_action_activated;
+}
+
+static void
+e_category_completion_init (ECategoryCompletion *category_completion)
+{
+	category_completion->priv =
+		E_CATEGORY_COMPLETION_GET_PRIVATE (category_completion);
+}
+
+/**
+ * e_category_completion_new:
+ *
+ * Since: 2.26
+ **/
+GtkEntryCompletion *
+e_category_completion_new (void)
+{
+	return g_object_new (E_TYPE_CATEGORY_COMPLETION, NULL);
+}
diff --git a/e-util/e-category-completion.h b/e-util/e-category-completion.h
new file mode 100644
index 0000000..477a036
--- /dev/null
+++ b/e-util/e-category-completion.h
@@ -0,0 +1,72 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_CATEGORY_COMPLETION_H
+#define E_CATEGORY_COMPLETION_H
+
+#include <gtk/gtk.h>
+
+/* Standard GObject macros */
+#define E_TYPE_CATEGORY_COMPLETION \
+	(e_category_completion_get_type ())
+#define E_CATEGORY_COMPLETION(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_CATEGORY_COMPLETION, ECategoryCompletion))
+#define E_CATEGORY_COMPLETION_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_CATEGORY_COMPLETION, ECategoryCompletionClass))
+#define E_IS_CATEGORY_COMPLETION(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_CATEGORY_COMPLETION))
+#define E_IS_CATEGORY_COMPLETION_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_CATEGORY_COMPLETION))
+#define E_CATEGORY_COMPLETION_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_CATEGORY_COMPLETION, ECategoryCompletionClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ECategoryCompletion ECategoryCompletion;
+typedef struct _ECategoryCompletionClass ECategoryCompletionClass;
+typedef struct _ECategoryCompletionPrivate ECategoryCompletionPrivate;
+
+/**
+ * ECategoryCompletion:
+ *
+ * Since: 2.26
+ **/
+struct _ECategoryCompletion {
+	GtkEntryCompletion parent;
+	ECategoryCompletionPrivate *priv;
+};
+
+struct _ECategoryCompletionClass {
+	GtkEntryCompletionClass parent_class;
+};
+
+GType		e_category_completion_get_type	(void);
+GtkEntryCompletion *
+		e_category_completion_new	(void);
+
+G_END_DECLS
+
+#endif /* E_CATEGORY_COMPLETION_H */
diff --git a/e-util/e-category-editor.c b/e-util/e-category-editor.c
new file mode 100644
index 0000000..33ad6dd
--- /dev/null
+++ b/e-util/e-category-editor.c
@@ -0,0 +1,343 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * 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., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <gtk/gtk.h>
+#include <glib/gi18n-lib.h>
+
+#include <libedataserver/libedataserver.h>
+
+#include "e-category-editor.h"
+
+#define E_CATEGORY_EDITOR_GET_PRIVATE(obj) \
+	(G_TYPE_INSTANCE_GET_PRIVATE \
+	((obj), E_TYPE_CATEGORY_EDITOR, ECategoryEditorPrivate))
+
+struct _ECategoryEditorPrivate {
+	GtkWidget *category_name;
+	GtkWidget *category_icon;
+};
+
+G_DEFINE_TYPE (ECategoryEditor, e_category_editor, GTK_TYPE_DIALOG)
+
+static void
+update_preview (GtkFileChooser *chooser,
+                gpointer user_data)
+{
+	GtkImage *image;
+	gchar *filename;
+
+	g_return_if_fail (chooser != NULL);
+
+	image = GTK_IMAGE (gtk_file_chooser_get_preview_widget (chooser));
+	g_return_if_fail (image != NULL);
+
+	filename = gtk_file_chooser_get_preview_filename (chooser);
+
+	gtk_image_set_from_file (image, filename);
+	gtk_file_chooser_set_preview_widget_active (chooser, filename != NULL);
+
+	g_free (filename);
+}
+
+static void
+file_chooser_response (GtkDialog *dialog,
+                       gint response_id,
+                       GtkFileChooser *button)
+{
+	g_return_if_fail (button != NULL);
+
+	if (response_id == GTK_RESPONSE_NO)
+		gtk_file_chooser_unselect_all (button);
+}
+
+static void
+category_editor_category_name_changed (GtkEntry *category_name_entry,
+                                       ECategoryEditor *editor)
+{
+	gchar *name;
+
+	g_return_if_fail (editor != NULL);
+	g_return_if_fail (category_name_entry != NULL);
+
+	name = g_strdup (gtk_entry_get_text (category_name_entry));
+	if (name != NULL)
+		name = g_strstrip (name);
+
+	gtk_dialog_set_response_sensitive (
+		GTK_DIALOG (editor), GTK_RESPONSE_OK, name && *name);
+
+	g_free (name);
+}
+
+static gchar *
+check_category_name (const gchar *name)
+{
+	GString *str = NULL;
+	gchar *p = (gchar *) name;
+
+	str = g_string_new ("");
+	while (*p) {
+		switch (*p) {
+			case ',':
+				break;
+			default:
+				str = g_string_append_c (str, *p);
+		}
+		p++;
+	}
+
+	p = g_strstrip (g_string_free (str, FALSE));
+
+	return p;
+}
+
+static void
+e_category_editor_class_init (ECategoryEditorClass *class)
+{
+	g_type_class_add_private (class, sizeof (ECategoryEditorPrivate));
+}
+
+static void
+e_category_editor_init (ECategoryEditor *editor)
+{
+	GtkWidget *dialog_content;
+	GtkWidget *dialog_action_area;
+	GtkGrid *grid_category_properties;
+	GtkWidget *label_name;
+	GtkWidget *label_icon;
+	GtkWidget *category_name;
+	GtkWidget *chooser_button;
+	GtkWidget *no_image_button;
+	GtkWidget *chooser_dialog;
+	GtkWidget *preview;
+
+	editor->priv = E_CATEGORY_EDITOR_GET_PRIVATE (editor);
+
+	chooser_dialog = gtk_file_chooser_dialog_new (
+		_("Category Icon"),
+		NULL, GTK_FILE_CHOOSER_ACTION_OPEN,
+		GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, NULL);
+
+	no_image_button = gtk_button_new_with_mnemonic (_("_No Image"));
+	gtk_button_set_image (
+		GTK_BUTTON (no_image_button),
+		gtk_image_new_from_stock (
+		GTK_STOCK_CLOSE, GTK_ICON_SIZE_BUTTON));
+	gtk_dialog_add_action_widget (
+		GTK_DIALOG (chooser_dialog),
+		no_image_button, GTK_RESPONSE_NO);
+	gtk_dialog_add_button (
+		GTK_DIALOG (chooser_dialog),
+		GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT);
+	gtk_file_chooser_set_local_only (
+		GTK_FILE_CHOOSER (chooser_dialog), TRUE);
+	gtk_widget_show (no_image_button);
+
+	g_signal_connect (
+		chooser_dialog, "update-preview",
+		G_CALLBACK (update_preview), NULL);
+
+	preview = gtk_image_new ();
+	gtk_file_chooser_set_preview_widget (
+		GTK_FILE_CHOOSER (chooser_dialog), preview);
+	gtk_file_chooser_set_preview_widget_active (
+		GTK_FILE_CHOOSER (chooser_dialog), TRUE);
+	gtk_widget_show_all (preview);
+
+	dialog_content = gtk_dialog_get_content_area (GTK_DIALOG (editor));
+
+	grid_category_properties = GTK_GRID (gtk_grid_new ());
+	gtk_box_pack_start (
+		GTK_BOX (dialog_content),
+		GTK_WIDGET (grid_category_properties), TRUE, TRUE, 0);
+	gtk_container_set_border_width (
+		GTK_CONTAINER (grid_category_properties), 12);
+	gtk_grid_set_row_spacing (grid_category_properties, 6);
+	gtk_grid_set_column_spacing (grid_category_properties, 6);
+
+	label_name = gtk_label_new_with_mnemonic (_("Category _Name"));
+	gtk_widget_set_halign (label_name, GTK_ALIGN_FILL);
+	gtk_misc_set_alignment (GTK_MISC (label_name), 0, 0.5);
+	gtk_grid_attach (grid_category_properties, label_name, 0, 0, 1, 1);
+
+	category_name = gtk_entry_new ();
+	gtk_widget_set_hexpand (category_name, TRUE);
+	gtk_widget_set_halign (category_name, GTK_ALIGN_FILL);
+	gtk_label_set_mnemonic_widget (GTK_LABEL (label_name), category_name);
+	gtk_grid_attach (grid_category_properties, category_name, 1, 0, 1, 1);
+	editor->priv->category_name = category_name;
+
+	label_icon = gtk_label_new_with_mnemonic (_("Category _Icon"));
+	gtk_widget_set_halign (label_icon, GTK_ALIGN_FILL);
+	gtk_misc_set_alignment (GTK_MISC (label_icon), 0, 0.5);
+	gtk_grid_attach (grid_category_properties, label_icon, 0, 1, 1, 1);
+
+	chooser_button = GTK_WIDGET (
+		gtk_file_chooser_button_new_with_dialog (chooser_dialog));
+	gtk_widget_set_hexpand (chooser_button, TRUE);
+	gtk_widget_set_halign (chooser_button, GTK_ALIGN_FILL);
+	gtk_label_set_mnemonic_widget (GTK_LABEL (label_icon), chooser_button);
+	gtk_grid_attach (grid_category_properties, chooser_button, 1, 1, 1, 1);
+	editor->priv->category_icon = chooser_button;
+
+	g_signal_connect (
+		chooser_dialog, "response",
+		G_CALLBACK (file_chooser_response), chooser_button);
+
+	dialog_action_area = gtk_dialog_get_action_area (GTK_DIALOG (editor));
+	gtk_button_box_set_layout (
+		GTK_BUTTON_BOX (dialog_action_area), GTK_BUTTONBOX_END);
+
+	gtk_dialog_add_buttons (
+		GTK_DIALOG (editor),
+		GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+		GTK_STOCK_OK, GTK_RESPONSE_OK, NULL);
+	gtk_dialog_set_default_response (GTK_DIALOG (editor), GTK_RESPONSE_OK);
+	gtk_window_set_title (GTK_WINDOW (editor), _("Category Properties"));
+	gtk_window_set_type_hint (
+		GTK_WINDOW (editor), GDK_WINDOW_TYPE_HINT_DIALOG);
+
+	gtk_widget_show_all (dialog_content);
+
+	g_signal_connect (
+		category_name, "changed",
+		G_CALLBACK (category_editor_category_name_changed), editor);
+
+	category_editor_category_name_changed (
+		GTK_ENTRY (category_name), editor);
+}
+
+/**
+ * e_categort_editor_new:
+ *
+ * Creates a new #ECategoryEditor widget.
+ *
+ * Returns: a new #ECategoryEditor
+ *
+ * Since: 3.2
+ **/
+ECategoryEditor *
+e_category_editor_new ()
+{
+	return g_object_new (E_TYPE_CATEGORY_EDITOR, NULL);
+}
+
+/**
+ * e_category_editor_create_category:
+ *
+ * Since: 3.2
+ **/
+const gchar *
+e_category_editor_create_category (ECategoryEditor *editor)
+{
+	GtkEntry *entry;
+	GtkFileChooser *file_chooser;
+
+	g_return_val_if_fail (E_IS_CATEGORY_EDITOR (editor), NULL);
+
+	entry = GTK_ENTRY (editor->priv->category_name);
+	file_chooser = GTK_FILE_CHOOSER (editor->priv->category_icon);
+
+	do {
+		const gchar *category_name;
+		const gchar *correct_category_name;
+
+		if (gtk_dialog_run (GTK_DIALOG (editor)) != GTK_RESPONSE_OK)
+			return NULL;
+
+		category_name = gtk_entry_get_text (entry);
+		correct_category_name = check_category_name (category_name);
+
+		if (e_categories_exist (correct_category_name)) {
+			GtkWidget *error_dialog;
+
+			error_dialog = gtk_message_dialog_new (
+				GTK_WINDOW (editor),
+				0, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
+				_("There is already a category '%s' in the "
+				"configuration. Please use another name"),
+				category_name);
+
+			gtk_dialog_run (GTK_DIALOG (error_dialog));
+			gtk_widget_destroy (error_dialog);
+
+			/* Now we loop and run the dialog again. */
+
+		} else {
+			gchar *category_icon;
+
+			category_icon =
+				gtk_file_chooser_get_filename (file_chooser);
+			e_categories_add (
+				correct_category_name, NULL,
+				category_icon, TRUE);
+			g_free (category_icon);
+
+			return correct_category_name;
+		}
+
+	} while (TRUE);
+}
+
+/**
+ * e_category_editor_edit_category:
+ *
+ * Since: 3.2
+ **/
+gboolean
+e_category_editor_edit_category (ECategoryEditor *editor,
+                                 const gchar *category)
+{
+	GtkFileChooser *file_chooser;
+	const gchar *icon_file;
+
+	g_return_val_if_fail (E_IS_CATEGORY_EDITOR (editor), FALSE);
+	g_return_val_if_fail (category != NULL, FALSE);
+
+	file_chooser = GTK_FILE_CHOOSER (editor->priv->category_icon);
+
+	gtk_entry_set_text (GTK_ENTRY (editor->priv->category_name), category);
+	gtk_widget_set_sensitive (editor->priv->category_name, FALSE);
+
+	icon_file = e_categories_get_icon_file_for (category);
+	if (icon_file) {
+		gtk_file_chooser_set_filename (file_chooser, icon_file);
+		update_preview (file_chooser, NULL);
+	}
+
+	if (gtk_dialog_run (GTK_DIALOG (editor)) == GTK_RESPONSE_OK) {
+		gchar *category_icon;
+
+		category_icon = gtk_file_chooser_get_filename (file_chooser);
+		e_categories_set_icon_file_for (category, category_icon);
+
+		gtk_dialog_set_response_sensitive (
+			GTK_DIALOG (editor), GTK_RESPONSE_OK, TRUE);
+
+		g_free (category_icon);
+
+		return TRUE;
+	}
+
+	return FALSE;
+}
diff --git a/e-util/e-category-editor.h b/e-util/e-category-editor.h
new file mode 100644
index 0000000..bf5ebbe
--- /dev/null
+++ b/e-util/e-category-editor.h
@@ -0,0 +1,81 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * 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., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_CATEGORY_EDITOR_H
+#define E_CATEGORY_EDITOR_H
+
+#include <gtk/gtk.h>
+
+/* Standard GObject macros */
+#define E_TYPE_CATEGORY_EDITOR \
+	(e_category_editor_get_type ())
+#define E_CATEGORY_EDITOR(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_CATEGORY_EDITOR, ECategoryEditor))
+#define E_CATEGORY_EDITOR_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_CATEGORY_EDITOR, ECategoryEditorClass))
+#define E_IS_CATEGORY_EDITOR(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_CATEGORY_EDITOR))
+#define E_IS_CATEGORY_EDITOR_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_CATEGORY_EDITOR))
+#define E_CATEGORY_EDITOR_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_CATEGORY_EDITOR, ECategoryEditorClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ECategoryEditor ECategoryEditor;
+typedef struct _ECategoryEditorClass ECategoryEditorClass;
+typedef struct _ECategoryEditorPrivate ECategoryEditorPrivate;
+
+/**
+ * ECategoryEditor:
+ *
+ * Contains only private data that should be read and manipulated using the
+ * functions below.
+ *
+ * Since: 3.2
+ **/
+struct _ECategoryEditor {
+	GtkDialog parent;
+	ECategoryEditorPrivate *priv;
+};
+
+struct _ECategoryEditorClass {
+	GtkDialogClass parent_class;
+};
+
+GType		e_category_editor_get_type	(void);
+ECategoryEditor *
+		e_category_editor_new		(void);
+const gchar *	e_category_editor_create_category
+						(ECategoryEditor *editor);
+gboolean	e_category_editor_edit_category	(ECategoryEditor *editor,
+						 const gchar *category);
+
+G_END_DECLS
+
+#endif /* E_CATEGORY_EDITOR_H */
diff --git a/e-util/e-cell-checkbox.c b/e-util/e-cell-checkbox.c
new file mode 100644
index 0000000..c434042
--- /dev/null
+++ b/e-util/e-cell-checkbox.c
@@ -0,0 +1,102 @@
+/*
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Miguel de Icaza <miguel ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <gdk/gdkkeysyms.h>
+#include <gtk/gtk.h>
+#include <libgnomecanvas/libgnomecanvas.h>
+
+#include "e-table-item.h"
+#include "e-cell-checkbox.h"
+
+#include "check-empty.xpm"
+#include "check-filled.xpm"
+
+G_DEFINE_TYPE (ECellCheckbox, e_cell_checkbox, E_TYPE_CELL_TOGGLE)
+
+static GdkPixbuf *checks[2];
+
+static void
+ecc_print (ECellView *ecell_view,
+           GtkPrintContext *context,
+           gint model_col,
+           gint view_col,
+           gint row,
+           gdouble width,
+           gdouble height)
+{
+	cairo_t *cr = gtk_print_context_get_cairo_context (context);
+	const gint value = GPOINTER_TO_INT (
+		e_table_model_value_at (
+			ecell_view->e_table_model, model_col, row));
+	cairo_save (cr);
+
+	if (value == 1) {
+		cairo_set_line_width (cr, 2);
+		cairo_move_to (cr, 3, 11);
+		cairo_line_to (cr, 7, 14);
+		cairo_line_to (cr, 11, 5);
+		cairo_stroke (cr);
+	}
+
+	cairo_restore (cr);
+}
+
+static void
+e_cell_checkbox_class_init (ECellCheckboxClass *class)
+{
+	ECellClass *ecc = E_CELL_CLASS (class);
+
+	ecc->print = ecc_print;
+	checks[0] = gdk_pixbuf_new_from_xpm_data (check_empty_xpm);
+	checks[1] = gdk_pixbuf_new_from_xpm_data (check_filled_xpm);
+}
+
+static void
+e_cell_checkbox_init (ECellCheckbox *eccb)
+{
+	GPtrArray *pixbufs;
+
+	pixbufs = e_cell_toggle_get_pixbufs (E_CELL_TOGGLE (eccb));
+
+	g_ptr_array_add (pixbufs, g_object_ref (checks[0]));
+	g_ptr_array_add (pixbufs, g_object_ref (checks[1]));
+}
+
+/**
+ * e_cell_checkbox_new:
+ *
+ * Creates a new ECell renderer that can be used to render check
+ * boxes.  the data provided from the model is cast to an integer.
+ * zero is used for the off display, and non-zero for checked status.
+ *
+ * Returns: an ECell object that can be used to render checkboxes.
+ */
+ECell *
+e_cell_checkbox_new (void)
+{
+	return g_object_new (E_TYPE_CELL_CHECKBOX, NULL);
+}
diff --git a/e-util/e-cell-checkbox.h b/e-util/e-cell-checkbox.h
new file mode 100644
index 0000000..2d1d9db
--- /dev/null
+++ b/e-util/e-cell-checkbox.h
@@ -0,0 +1,71 @@
+/*
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Miguel de Icaza <miguel ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef _E_CELL_CHECKBOX_H_
+#define _E_CELL_CHECKBOX_H_
+
+#include <e-util/e-cell-toggle.h>
+
+/* Standard GObject macros */
+#define E_TYPE_CELL_CHECKBOX \
+	(e_cell_checkbox_get_type ())
+#define E_CELL_CHECKBOX(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_CELL_CHECKBOX, ECellCheckbox))
+#define E_CELL_CHECKBOX_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_CELL_CHECKBOX, ECellCheckboxClass))
+#define E_IS_CELL_CHECKBOX(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_CELL_CHECKBOX))
+#define E_IS_CELL_CHECKBOX_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_CELL_CHECKBOX))
+#define E_CELL_CHECKBOX_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_CELL_CHECKBOX, ECellCheckboxClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ECellCheckbox ECellCheckbox;
+typedef struct _ECellCheckboxClass ECellCheckboxClass;
+
+struct _ECellCheckbox {
+	ECellToggle parent;
+};
+
+struct _ECellCheckboxClass {
+	ECellToggleClass parent_class;
+};
+
+GType		e_cell_checkbox_get_type	(void) G_GNUC_CONST;
+ECell *		e_cell_checkbox_new		(void);
+
+G_END_DECLS
+
+#endif /* _E_CELL_CHECKBOX_H_ */
+
diff --git a/e-util/e-cell-combo.c b/e-util/e-cell-combo.c
new file mode 100644
index 0000000..dba6b53
--- /dev/null
+++ b/e-util/e-cell-combo.c
@@ -0,0 +1,838 @@
+/*
+ * e-cell-combo.c: Combo cell renderer
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Damon Chaplin <damon ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+/*
+ * ECellCombo - a subclass of ECellPopup used to support popup lists like a
+ * GtkCombo widget. It only supports a basic popup list of strings at present,
+ * with no auto-completion.
+ */
+
+/*
+ * Notes: (handling pointer grabs and GTK+ grabs is a nightmare!)
+ *
+ * o We must grab the pointer when we show the popup, so that if any buttons
+ *   are pressed outside the application we hide the popup.
+ *
+ * o We have to be careful when popping up any widgets which also grab the
+ *   pointer at some point, since we will lose our own pointer grab.
+ *   When we pop up a list it will grab the pointer itself when an item is
+ *   selected, and release the grab when the button is released.
+ *   Fortunately we hide the popup at this point, so it isn't a problem.
+ *   But for other types of widgets in the popup it could cause trouble.
+ *   - I think GTK+ should provide help for this (nested pointer grabs?).
+ *
+ * o We must set the 'owner_events' flag of the pointer grab to TRUE so that
+ *   pointer events get reported to all the application windows as normal.
+ *   If we don't do this then the widgets in the popup may not work properly.
+ *
+ * o We must do a gtk_grab_add() so that we only allow events to go to the
+ *   widgets within the popup (though some special events still get reported
+ *   to the widget owning the window). Doing th gtk_grab_add() on the toplevel
+ *   popup window should be fine. We can then check for any events that should
+ *   close the popup, like the Escape key, or a button press outside the popup.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-cell-combo.h"
+
+#include <string.h>
+
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+#include <gdk/gdkkeysyms.h>
+
+#include "e-cell-text.h"
+#include "e-table-item.h"
+#include "e-unicode.h"
+
+#define d(x)
+
+/* The height to make the popup list if there aren't any items in it. */
+#define	E_CELL_COMBO_LIST_EMPTY_HEIGHT	15
+
+static void	e_cell_combo_dispose		(GObject *object);
+static gint	e_cell_combo_do_popup		(ECellPopup *ecp,
+						 GdkEvent *event,
+						 gint row,
+						 gint view_col);
+static void	e_cell_combo_select_matching_item
+						(ECellCombo *ecc);
+static void	e_cell_combo_show_popup		(ECellCombo *ecc,
+						 gint row,
+						 gint view_col);
+static void	e_cell_combo_get_popup_pos	(ECellCombo *ecc,
+						 gint row,
+						 gint view_col,
+						 gint *x,
+						 gint *y,
+						 gint *height,
+						 gint *width);
+static void	e_cell_combo_selection_changed	(GtkTreeSelection *selection,
+						 ECellCombo *ecc);
+static gint	e_cell_combo_button_press	(GtkWidget *popup_window,
+						 GdkEvent *button_event,
+						 ECellCombo *ecc);
+static gint	e_cell_combo_button_release	(GtkWidget *popup_window,
+						 GdkEvent *button_event,
+						 ECellCombo *ecc);
+static gint	e_cell_combo_key_press		(GtkWidget *popup_window,
+						 GdkEvent *key_event,
+						 ECellCombo *ecc);
+static void	e_cell_combo_update_cell	(ECellCombo *ecc);
+static void	e_cell_combo_restart_edit	(ECellCombo *ecc);
+
+G_DEFINE_TYPE (ECellCombo, e_cell_combo, E_TYPE_CELL_POPUP)
+
+static void
+e_cell_combo_class_init (ECellComboClass *class)
+{
+	ECellPopupClass *ecpc = E_CELL_POPUP_CLASS (class);
+	GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+	object_class->dispose = e_cell_combo_dispose;
+
+	ecpc->popup = e_cell_combo_do_popup;
+}
+
+static void
+e_cell_combo_init (ECellCombo *ecc)
+{
+	GtkWidget *frame;
+	AtkObject *a11y;
+	GtkListStore *store;
+	GtkTreeSelection *selection;
+	GtkScrolledWindow *scrolled_window;
+
+	/* We create one popup window for the ECell, since there will only
+	 * ever be one popup in use at a time. */
+	ecc->popup_window = gtk_window_new (GTK_WINDOW_POPUP);
+
+	gtk_window_set_type_hint (
+		GTK_WINDOW (ecc->popup_window), GDK_WINDOW_TYPE_HINT_COMBO);
+	gtk_window_set_resizable (GTK_WINDOW (ecc->popup_window), TRUE);
+
+	frame = gtk_frame_new (NULL);
+	gtk_container_add (GTK_CONTAINER (ecc->popup_window), frame);
+	gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_OUT);
+	gtk_widget_show (frame);
+
+	ecc->popup_scrolled_window = gtk_scrolled_window_new (NULL, NULL);
+	scrolled_window = GTK_SCROLLED_WINDOW (ecc->popup_scrolled_window);
+
+	gtk_scrolled_window_set_policy (
+		scrolled_window, GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
+	gtk_widget_set_can_focus (
+		gtk_scrolled_window_get_hscrollbar (scrolled_window), FALSE);
+	gtk_widget_set_can_focus (
+		gtk_scrolled_window_get_vscrollbar (scrolled_window), FALSE);
+	gtk_container_add (GTK_CONTAINER (frame), ecc->popup_scrolled_window);
+	gtk_widget_show (ecc->popup_scrolled_window);
+
+	store = gtk_list_store_new (1, G_TYPE_STRING);
+	ecc->popup_tree_view =
+		gtk_tree_view_new_with_model (GTK_TREE_MODEL (store));
+	g_object_unref (store);
+
+	gtk_tree_view_append_column (
+		GTK_TREE_VIEW (ecc->popup_tree_view),
+		gtk_tree_view_column_new_with_attributes (
+			"Text", gtk_cell_renderer_text_new (),
+			"text", 0, NULL));
+
+	gtk_tree_view_set_headers_visible (
+		GTK_TREE_VIEW (ecc->popup_tree_view), FALSE);
+
+	selection = gtk_tree_view_get_selection (
+		GTK_TREE_VIEW (ecc->popup_tree_view));
+	gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE);
+	gtk_scrolled_window_add_with_viewport (
+		GTK_SCROLLED_WINDOW (ecc->popup_scrolled_window),
+		ecc->popup_tree_view);
+	gtk_container_set_focus_vadjustment (
+		GTK_CONTAINER (ecc->popup_tree_view),
+		gtk_scrolled_window_get_vadjustment (
+		GTK_SCROLLED_WINDOW (ecc->popup_scrolled_window)));
+	gtk_container_set_focus_hadjustment (
+		GTK_CONTAINER (ecc->popup_tree_view),
+		gtk_scrolled_window_get_hadjustment (
+		GTK_SCROLLED_WINDOW (ecc->popup_scrolled_window)));
+	gtk_widget_show (ecc->popup_tree_view);
+
+	a11y = gtk_widget_get_accessible (ecc->popup_tree_view);
+	atk_object_set_name (a11y, _("popup list"));
+
+	g_signal_connect (
+		selection, "changed",
+		G_CALLBACK (e_cell_combo_selection_changed), ecc);
+	g_signal_connect (
+		ecc->popup_window, "button_press_event",
+		G_CALLBACK (e_cell_combo_button_press), ecc);
+	g_signal_connect (
+		ecc->popup_window, "button_release_event",
+		G_CALLBACK (e_cell_combo_button_release), ecc);
+	g_signal_connect (
+		ecc->popup_window, "key_press_event",
+		G_CALLBACK (e_cell_combo_key_press), ecc);
+}
+
+/**
+ * e_cell_combo_new:
+ *
+ * Creates a new ECellCombo renderer.
+ *
+ * Returns: an ECellCombo object.
+ */
+ECell *
+e_cell_combo_new (void)
+{
+	return g_object_new (E_TYPE_CELL_COMBO, NULL);
+}
+
+/*
+ * GObject::dispose method
+ */
+static void
+e_cell_combo_dispose (GObject *object)
+{
+	ECellCombo *ecc = E_CELL_COMBO (object);
+
+	if (ecc->popup_window != NULL) {
+		gtk_widget_destroy (ecc->popup_window);
+		ecc->popup_window = NULL;
+	}
+
+	if (ecc->grabbed_keyboard != NULL) {
+		gdk_device_ungrab (ecc->grabbed_keyboard, GDK_CURRENT_TIME);
+		g_object_unref (ecc->grabbed_keyboard);
+		ecc->grabbed_keyboard = NULL;
+	}
+
+	if (ecc->grabbed_pointer != NULL) {
+		gdk_device_ungrab (ecc->grabbed_pointer, GDK_CURRENT_TIME);
+		g_object_unref (ecc->grabbed_pointer);
+		ecc->grabbed_pointer = NULL;
+	}
+
+	G_OBJECT_CLASS (e_cell_combo_parent_class)->dispose (object);
+}
+
+void
+e_cell_combo_set_popdown_strings (ECellCombo *ecc,
+                                  GList *strings)
+{
+	GList *elem;
+	GtkListStore *store;
+
+	g_return_if_fail (E_IS_CELL_COMBO (ecc));
+	g_return_if_fail (strings != NULL);
+
+	store = GTK_LIST_STORE (
+		gtk_tree_view_get_model (
+		GTK_TREE_VIEW (ecc->popup_tree_view)));
+	gtk_list_store_clear (store);
+
+	for (elem = strings; elem; elem = elem->next) {
+		GtkTreeIter iter;
+		gchar *utf8_text = elem->data;
+
+		gtk_list_store_append (store, &iter);
+		gtk_list_store_set (store, &iter, 0, utf8_text, -1);
+	}
+}
+
+static gint
+e_cell_combo_do_popup (ECellPopup *ecp,
+                       GdkEvent *event,
+                       gint row,
+                       gint view_col)
+{
+	ECellCombo *ecc = E_CELL_COMBO (ecp);
+	GtkTreeSelection *selection;
+	GdkGrabStatus grab_status;
+	GdkWindow *window;
+	GdkDevice *keyboard;
+	GdkDevice *pointer;
+	GdkDevice *event_device;
+	guint32 event_time;
+
+	g_return_val_if_fail (ecc->grabbed_keyboard == NULL, FALSE);
+	g_return_val_if_fail (ecc->grabbed_pointer == NULL, FALSE);
+
+	selection = gtk_tree_view_get_selection (
+		GTK_TREE_VIEW (ecc->popup_tree_view));
+
+	g_signal_handlers_block_by_func (
+		selection, e_cell_combo_selection_changed, ecc);
+
+	e_cell_combo_show_popup (ecc, row, view_col);
+	e_cell_combo_select_matching_item (ecc);
+
+	g_signal_handlers_unblock_by_func (
+		selection, e_cell_combo_selection_changed, ecc);
+
+	window = gtk_widget_get_window (ecc->popup_tree_view);
+
+	event_device = gdk_event_get_device (event);
+	event_time = gdk_event_get_time (event);
+
+	if (gdk_device_get_source (event_device) == GDK_SOURCE_KEYBOARD) {
+		keyboard = event_device;
+		pointer = gdk_device_get_associated_device (event_device);
+	} else {
+		keyboard = gdk_device_get_associated_device (event_device);
+		pointer = event_device;
+	}
+
+	if (pointer != NULL) {
+		grab_status = gdk_device_grab (
+			pointer,
+			window,
+			GDK_OWNERSHIP_NONE,
+			TRUE,
+			GDK_ENTER_NOTIFY_MASK |
+			GDK_BUTTON_PRESS_MASK |
+			GDK_BUTTON_RELEASE_MASK |
+			GDK_POINTER_MOTION_HINT_MASK |
+			GDK_BUTTON1_MOTION_MASK,
+			NULL,
+			event_time);
+
+		if (grab_status != GDK_GRAB_SUCCESS)
+			return FALSE;
+
+		ecc->grabbed_pointer = g_object_ref (pointer);
+	}
+
+	gtk_grab_add (ecc->popup_window);
+
+	if (keyboard != NULL) {
+		grab_status = gdk_device_grab (
+			keyboard,
+			window,
+			GDK_OWNERSHIP_NONE,
+			TRUE,
+			GDK_KEY_PRESS_MASK |
+			GDK_KEY_RELEASE_MASK,
+			NULL,
+			event_time);
+
+		if (grab_status != GDK_GRAB_SUCCESS) {
+			if (ecc->grabbed_pointer != NULL) {
+				gdk_device_ungrab (
+					ecc->grabbed_pointer,
+					event_time);
+				g_object_unref (ecc->grabbed_pointer);
+				ecc->grabbed_pointer = NULL;
+			}
+			return FALSE;
+		}
+
+		ecc->grabbed_keyboard = g_object_ref (keyboard);
+	}
+
+	return TRUE;
+}
+
+static void
+e_cell_combo_select_matching_item (ECellCombo *ecc)
+{
+	ECellPopup *ecp = E_CELL_POPUP (ecc);
+	ECellView *ecv = (ECellView *) ecp->popup_cell_view;
+	ECellText *ecell_text = E_CELL_TEXT (ecp->child);
+	ETableItem *eti;
+	ETableCol *ecol;
+	gboolean found = FALSE;
+	gchar *cell_text;
+	gboolean valid;
+	GtkTreeSelection *selection;
+	GtkTreeIter iter;
+	GtkTreeModel *model;
+
+	eti = E_TABLE_ITEM (ecp->popup_cell_view->cell_view.e_table_item_view);
+
+	ecol = e_table_header_get_column (eti->header, ecp->popup_view_col);
+	cell_text = e_cell_text_get_text (
+		ecell_text, ecv->e_table_model,
+		ecol->col_idx, ecp->popup_row);
+
+	model = gtk_tree_view_get_model (GTK_TREE_VIEW (ecc->popup_tree_view));
+	selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (ecc->popup_tree_view));
+
+	for (valid = gtk_tree_model_get_iter_first (model, &iter);
+	     valid && !found;
+	     valid = gtk_tree_model_iter_next (model, &iter)) {
+		gchar *str = NULL;
+
+		gtk_tree_model_get (model, &iter, 0, &str, -1);
+
+		if (str && g_str_equal (str, cell_text)) {
+			GtkTreePath *path;
+
+			path = gtk_tree_model_get_path (model, &iter);
+			gtk_tree_view_set_cursor (
+				GTK_TREE_VIEW (ecc->popup_tree_view),
+				path, NULL, FALSE);
+			gtk_tree_path_free (path);
+
+			found = TRUE;
+		}
+
+		g_free (str);
+	}
+
+	if (!found)
+		gtk_tree_selection_unselect_all (selection);
+
+	e_cell_text_free_text (ecell_text, cell_text);
+}
+
+static void
+e_cell_combo_show_popup (ECellCombo *ecc,
+                         gint row,
+                         gint view_col)
+{
+	GdkWindow *window;
+	GtkAllocation allocation;
+	gint x, y, width, height, old_width, old_height;
+
+	gtk_widget_get_allocation (ecc->popup_window, &allocation);
+
+	/* This code is practically copied from GtkCombo. */
+	old_width = allocation.width;
+	old_height = allocation.height;
+
+	e_cell_combo_get_popup_pos (ecc, row, view_col, &x, &y, &height, &width);
+
+	/* workaround for gtk_scrolled_window_size_allocate bug */
+	if (old_width != width || old_height != height) {
+		gtk_widget_hide (
+			gtk_scrolled_window_get_hscrollbar (
+			GTK_SCROLLED_WINDOW (ecc->popup_scrolled_window)));
+		gtk_widget_hide (
+			gtk_scrolled_window_get_vscrollbar (
+			GTK_SCROLLED_WINDOW (ecc->popup_scrolled_window)));
+	}
+
+	gtk_window_move (GTK_WINDOW (ecc->popup_window), x, y);
+	gtk_widget_set_size_request (ecc->popup_window, width, height);
+	gtk_widget_realize (ecc->popup_window);
+	window = gtk_widget_get_window (ecc->popup_window);
+	gdk_window_resize (window, width, height);
+	gtk_widget_show (ecc->popup_window);
+
+	e_cell_popup_set_shown (E_CELL_POPUP (ecc), TRUE);
+	d (g_print ("%s: popup_shown = TRUE\n", __FUNCTION__));
+}
+
+/* Calculates the size and position of the popup window (like GtkCombo). */
+static void
+e_cell_combo_get_popup_pos (ECellCombo *ecc,
+                            gint row,
+                            gint view_col,
+                            gint *x,
+                            gint *y,
+                            gint *height,
+                            gint *width)
+{
+	ECellPopup *ecp = E_CELL_POPUP (ecc);
+	ETableItem *eti;
+	GtkWidget *canvas;
+	GtkWidget *widget;
+	GtkWidget *popwin_child;
+	GtkWidget *popup_child;
+	GtkStyle *popwin_style;
+	GtkStyle *popup_style;
+	GdkWindow *window;
+	GtkBin *popwin;
+	GtkScrolledWindow *popup;
+	GtkRequisition requisition;
+	GtkRequisition list_requisition;
+	gboolean show_vscroll = FALSE, show_hscroll = FALSE;
+	gint avail_height, avail_width, min_height, work_height, screen_width;
+	gint column_width, row_height, scrollbar_width;
+	gdouble x1, y1;
+	gdouble wx, wy;
+
+	eti = E_TABLE_ITEM (ecp->popup_cell_view->cell_view.e_table_item_view);
+	canvas = GTK_WIDGET (GNOME_CANVAS_ITEM (eti)->canvas);
+
+	/* This code is practically copied from GtkCombo. */
+	popup  = GTK_SCROLLED_WINDOW (ecc->popup_scrolled_window);
+	popwin = GTK_BIN (ecc->popup_window);
+
+	window = gtk_widget_get_window (canvas);
+	gdk_window_get_origin (window, x, y);
+
+	x1 = e_table_header_col_diff (eti->header, 0, view_col + 1);
+	y1 = e_table_item_row_diff (eti, 0, row + 1);
+	column_width = e_table_header_col_diff (
+		eti->header, view_col, view_col + 1);
+	row_height = e_table_item_row_diff (eti, row, row + 1);
+	gnome_canvas_item_i2w (GNOME_CANVAS_ITEM (eti), &x1, &y1);
+
+	gnome_canvas_world_to_window (
+		GNOME_CANVAS (canvas), x1, y1, &wx, &wy);
+
+	x1 = wx;
+	y1 = wy;
+
+	*x += x1;
+	/* The ETable positions don't include the grid lines, I think, so we add 1. */
+	*y += y1 + 1 - (gint)
+		 gtk_adjustment_get_value (
+			gtk_scrollable_get_vadjustment (
+			GTK_SCROLLABLE (&((GnomeCanvas *) canvas)->layout)))
+		+ ((GnomeCanvas *) canvas)->zoom_yofs;
+
+	widget = gtk_scrolled_window_get_vscrollbar (popup);
+	gtk_widget_get_preferred_size (widget, &requisition, NULL);
+
+	scrollbar_width =
+		requisition.width
+		+ GTK_SCROLLED_WINDOW_CLASS (G_OBJECT_GET_CLASS (popup))->scrollbar_spacing;
+
+	avail_height = gdk_screen_height () - *y;
+
+	/* We'll use the entire screen width if needed, but we save space for
+	 * the vertical scrollbar in case we need to show that. */
+	screen_width = gdk_screen_width ();
+	avail_width = screen_width - scrollbar_width;
+
+	widget = gtk_scrolled_window_get_vscrollbar (popup);
+	gtk_widget_get_preferred_size (widget, &requisition, NULL);
+
+	gtk_widget_get_preferred_size (ecc->popup_tree_view, &list_requisition, NULL);
+	min_height = MIN (list_requisition.height, requisition.height);
+	if (!gtk_tree_model_iter_n_children (
+			gtk_tree_view_get_model (
+			GTK_TREE_VIEW (ecc->popup_tree_view)), NULL))
+		list_requisition.height += E_CELL_COMBO_LIST_EMPTY_HEIGHT;
+
+	popwin_child = gtk_bin_get_child (popwin);
+	popwin_style = gtk_widget_get_style (popwin_child);
+
+	popup_child = gtk_bin_get_child (GTK_BIN (popup));
+	popup_style = gtk_widget_get_style (popup_child);
+
+	/* Calculate the desired width. */
+	*width = list_requisition.width
+		+ 2 * popwin_style->xthickness
+		+ 2 * gtk_container_get_border_width (GTK_CONTAINER (popwin_child))
+		+ 2 * gtk_container_get_border_width (GTK_CONTAINER (popup))
+		+ 2 * gtk_container_get_border_width (GTK_CONTAINER (popup_child))
+		+ 2 * popup_style->xthickness;
+
+	/* Use at least the same width as the column. */
+	if (*width < column_width)
+		*width = column_width;
+
+	/* If it is larger than the available width, use that instead and show
+	 * the horizontal scrollbar. */
+	if (*width > avail_width) {
+		*width = avail_width;
+		show_hscroll = TRUE;
+	}
+
+	/* Calculate all the borders etc. that we need to add to the height. */
+	work_height = (2 * popwin_style->ythickness
+		       + 2 * gtk_container_get_border_width (GTK_CONTAINER (popwin_child))
+		       + 2 * gtk_container_get_border_width (GTK_CONTAINER (popup))
+		       + 2 * gtk_container_get_border_width (GTK_CONTAINER (popup_child))
+		       + 2 * popup_style->xthickness);
+
+	widget = gtk_scrolled_window_get_hscrollbar (popup);
+	gtk_widget_get_preferred_size (widget, &requisition, NULL);
+
+	/* Add on the height of the horizontal scrollbar if we need it. */
+	if (show_hscroll)
+		work_height +=
+			requisition.height +
+			GTK_SCROLLED_WINDOW_GET_CLASS (popup)->scrollbar_spacing;
+
+	/* Check if it fits in the available height. */
+	if (work_height + list_requisition.height > avail_height) {
+		/* It doesn't fit, so we see if we have the minimum space
+		 * needed. */
+		if (work_height + min_height > avail_height
+		    && *y - row_height > avail_height) {
+			/* We don't, so we show the popup above the cell
+			 * instead of below it. */
+			avail_height = *y - row_height;
+			*y -= (work_height + list_requisition.height
+			       + row_height);
+			if (*y < 0)
+				*y = 0;
+		}
+	}
+
+	/* Check if we still need the vertical scrollbar. */
+	if (work_height + list_requisition.height > avail_height) {
+		*width += scrollbar_width;
+		show_vscroll = TRUE;
+	}
+
+	/* We try to line it up with the right edge of the column, but we don't
+	 * want it to go off the edges of the screen. */
+	if (*x > screen_width)
+		*x = screen_width;
+	*x -= *width;
+	if (*x < 0)
+		*x = 0;
+
+	if (show_vscroll)
+		*height = avail_height;
+	else
+		*height = work_height + list_requisition.height;
+}
+
+static void
+e_cell_combo_selection_changed (GtkTreeSelection *selection,
+                                ECellCombo *ecc)
+{
+	GtkTreeIter iter;
+	GtkTreeModel *model;
+
+	if (!gtk_widget_get_realized (ecc->popup_window) ||
+	    !gtk_tree_selection_get_selected (selection, &model, &iter))
+		return;
+
+	e_cell_combo_update_cell (ecc);
+	e_cell_combo_restart_edit (ecc);
+}
+
+/* This handles button press events in the popup window.
+ * Note that since we have a pointer grab on this window, we also get button
+ * press events for windows outside the application here, so we hide the popup
+ * window if that happens. We also get propagated events from child widgets
+ * which we ignore. */
+static gint
+e_cell_combo_button_press (GtkWidget *popup_window,
+                           GdkEvent *button_event,
+                           ECellCombo *ecc)
+{
+	GtkWidget *event_widget;
+	guint32 event_time;
+
+	event_time = gdk_event_get_time (button_event);
+	event_widget = gtk_get_event_widget (button_event);
+
+	/* If the button press was for a widget inside the popup list, but
+	 * not the popup window itself, then we ignore the event and return
+	 * FALSE. Otherwise we will hide the popup.
+	 * Note that since we have a pointer grab on the popup list, button
+	 * presses outside the application will be reported to this window,
+	 * which is why we hide the popup in this case. */
+	while (event_widget) {
+		event_widget = gtk_widget_get_parent (event_widget);
+		if (event_widget == ecc->popup_tree_view)
+			return FALSE;
+	}
+
+	gtk_grab_remove (ecc->popup_window);
+
+	if (ecc->grabbed_keyboard != NULL) {
+		gdk_device_ungrab (ecc->grabbed_keyboard, event_time);
+		g_object_unref (ecc->grabbed_keyboard);
+		ecc->grabbed_keyboard = NULL;
+	}
+
+	if (ecc->grabbed_pointer != NULL) {
+		gdk_device_ungrab (ecc->grabbed_pointer, event_time);
+		g_object_unref (ecc->grabbed_pointer);
+		ecc->grabbed_pointer = NULL;
+	}
+
+	gtk_widget_hide (ecc->popup_window);
+
+	e_cell_popup_set_shown (E_CELL_POPUP (ecc), FALSE);
+	d (g_print ("%s: popup_shown = FALSE\n", __FUNCTION__));
+
+	/* We don't want to update the cell here. Since the list is in browse
+	 * mode there will always be one item selected, so when we popup the
+	 * list one item is selected even if it doesn't match the current text
+	 * in the cell. So if you click outside the popup (which is what has
+	 * happened here) it is better to not update the cell. */
+	/*e_cell_combo_update_cell (ecc);*/
+	e_cell_combo_restart_edit (ecc);
+
+	return TRUE;
+}
+
+/* This handles button release events in the popup window. If the button is
+ * released inside the list, we want to hide the popup window and update the
+ * cell with the new selection. */
+static gint
+e_cell_combo_button_release (GtkWidget *popup_window,
+                             GdkEvent *button_event,
+                             ECellCombo *ecc)
+{
+	GtkWidget *event_widget;
+	guint32 event_time;
+
+	event_time = gdk_event_get_time (button_event);
+	event_widget = gtk_get_event_widget (button_event);
+
+	/* See if the button was released in the list (or its children). */
+	while (event_widget && event_widget != ecc->popup_tree_view)
+		event_widget = gtk_widget_get_parent (event_widget);
+
+	/* If it wasn't, then we just ignore the event. */
+	if (event_widget != ecc->popup_tree_view)
+		return FALSE;
+
+	/* The button was released inside the list, so we hide the popup and
+	 * update the cell to reflect the new selection. */
+
+	gtk_grab_remove (ecc->popup_window);
+
+	if (ecc->grabbed_keyboard != NULL) {
+		gdk_device_ungrab (ecc->grabbed_keyboard, event_time);
+		g_object_unref (ecc->grabbed_keyboard);
+		ecc->grabbed_keyboard = NULL;
+	}
+
+	if (ecc->grabbed_pointer != NULL) {
+		gdk_device_ungrab (ecc->grabbed_pointer, event_time);
+		g_object_unref (ecc->grabbed_pointer);
+		ecc->grabbed_pointer = NULL;
+	}
+
+	gtk_widget_hide (ecc->popup_window);
+
+	e_cell_popup_set_shown (E_CELL_POPUP (ecc), FALSE);
+	d (g_print ("%s: popup_shown = FALSE\n", __FUNCTION__));
+
+	e_cell_combo_update_cell (ecc);
+	e_cell_combo_restart_edit (ecc);
+
+	return TRUE;
+}
+
+/* This handles key press events in the popup window. If the Escape key is
+ * pressed we hide the popup, and do not change the cell contents. */
+static gint
+e_cell_combo_key_press (GtkWidget *popup_window,
+                        GdkEvent *key_event,
+                        ECellCombo *ecc)
+{
+	guint event_keyval = 0;
+	guint32 event_time;
+
+	gdk_event_get_keyval (key_event, &event_keyval);
+	event_time = gdk_event_get_time (key_event);
+
+	/* If the Escape key is pressed we hide the popup. */
+	if (event_keyval != GDK_KEY_Escape
+	    && event_keyval != GDK_KEY_Return
+	    && event_keyval != GDK_KEY_KP_Enter
+	    && event_keyval != GDK_KEY_ISO_Enter
+	    && event_keyval != GDK_KEY_3270_Enter)
+		return FALSE;
+
+	if (event_keyval == GDK_KEY_Escape &&
+	   (!ecc->popup_window || !gtk_widget_get_visible (ecc->popup_window)))
+		return FALSE;
+
+	gtk_grab_remove (ecc->popup_window);
+
+	if (ecc->grabbed_keyboard != NULL) {
+		gdk_device_ungrab (ecc->grabbed_keyboard, event_time);
+		g_object_unref (ecc->grabbed_keyboard);
+		ecc->grabbed_keyboard = NULL;
+	}
+
+	if (ecc->grabbed_pointer != NULL) {
+		gdk_device_ungrab (ecc->grabbed_pointer, event_time);
+		g_object_unref (ecc->grabbed_pointer);
+		ecc->grabbed_pointer = NULL;
+	}
+
+	gtk_widget_hide (ecc->popup_window);
+
+	e_cell_popup_set_shown (E_CELL_POPUP (ecc), FALSE);
+	d (g_print ("%s: popup_shown = FALSE\n", __FUNCTION__));
+
+	if (event_keyval != GDK_KEY_Escape)
+		e_cell_combo_update_cell (ecc);
+
+	e_cell_combo_restart_edit (ecc);
+
+	return TRUE;
+}
+
+static void
+e_cell_combo_update_cell (ECellCombo *ecc)
+{
+	ECellPopup *ecp = E_CELL_POPUP (ecc);
+	ECellView *ecv = (ECellView *) ecp->popup_cell_view;
+	ECellText *ecell_text = E_CELL_TEXT (ecp->child);
+	ETableItem *eti = E_TABLE_ITEM (ecv->e_table_item_view);
+	ETableCol *ecol;
+	GtkTreeSelection *selection = gtk_tree_view_get_selection (
+		GTK_TREE_VIEW (ecc->popup_tree_view));
+	GtkTreeModel *model;
+	GtkTreeIter iter;
+	gchar *text = NULL, *old_text;
+
+	/* Return if no item is selected. */
+	if (!gtk_tree_selection_get_selected (selection, &model, &iter))
+		return;
+
+	/* Get the text of the selected item. */
+	gtk_tree_model_get (model, &iter, 0, &text, -1);
+	g_return_if_fail (text != NULL);
+
+	/* Compare it with the existing cell contents. */
+	ecol = e_table_header_get_column (eti->header, ecp->popup_view_col);
+
+	old_text = e_cell_text_get_text (
+		ecell_text, ecv->e_table_model,
+		ecol->col_idx, ecp->popup_row);
+
+	/* If they are different, update the cell contents. */
+	if (old_text && strcmp (old_text, text)) {
+		e_cell_text_set_value (
+			ecell_text, ecv->e_table_model,
+			ecol->col_idx, ecp->popup_row, text);
+	}
+
+	e_cell_text_free_text (ecell_text, old_text);
+	g_free (text);
+}
+
+static void
+e_cell_combo_restart_edit (ECellCombo *ecc)
+{
+	/* This doesn't work. ETable stops the edit straight-away again. */
+#if 0
+	ECellView *ecv = (ECellView *) ecc->popup_cell_view;
+	ETableItem *eti = E_TABLE_ITEM (ecv->e_table_item_view);
+
+	e_table_item_enter_edit (eti, ecc->popup_view_col, ecc->popup_row);
+#endif
+}
+
diff --git a/e-util/e-cell-combo.h b/e-util/e-cell-combo.h
new file mode 100644
index 0000000..04f17c6
--- /dev/null
+++ b/e-util/e-cell-combo.h
@@ -0,0 +1,89 @@
+/*
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Damon Chaplin <damon ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+/*
+ * ECellCombo - a subclass of ECellPopup used to support popup lists like a
+ * GtkCombo widget. It only supports a basic popup list of strings at present,
+ * with no auto-completion. The child ECell of the ECellPopup must be an
+ * ECellText or subclass.
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef _E_CELL_COMBO_H_
+#define _E_CELL_COMBO_H_
+
+#include <e-util/e-cell-popup.h>
+
+/* Standard GObject macros */
+#define E_TYPE_CELL_COMBO \
+	(e_cell_combo_get_type ())
+#define E_CELL_COMBO(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_CELL_COMBO, ECellCombo))
+#define E_CELL_COMBO_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_CELL_COMBO, ECellComboClass))
+#define E_IS_CELL_COMBO(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_CELL_COMBO))
+#define E_IS_CELL_COMBO_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_CELL_COMBO))
+#define E_CELL_COMBO_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_CELL_COMBO, ECellComboClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ECellCombo ECellCombo;
+typedef struct _ECellComboClass ECellComboClass;
+
+struct _ECellCombo {
+	ECellPopup parent;
+
+	GtkWidget *popup_window;
+	GtkWidget *popup_scrolled_window;
+	GtkWidget *popup_tree_view;
+
+	GdkDevice *grabbed_keyboard;
+	GdkDevice *grabbed_pointer;
+};
+
+struct _ECellComboClass {
+	ECellPopupClass parent_class;
+};
+
+GType		e_cell_combo_get_type		(void) G_GNUC_CONST;
+ECell *		e_cell_combo_new		(void);
+
+/* These must be UTF-8. */
+void		e_cell_combo_set_popdown_strings
+						(ECellCombo *ecc,
+						 GList *strings);
+
+G_END_DECLS
+
+#endif /* _E_CELL_COMBO_H_ */
diff --git a/e-util/e-cell-date-edit.c b/e-util/e-cell-date-edit.c
new file mode 100644
index 0000000..4f35fbb
--- /dev/null
+++ b/e-util/e-cell-date-edit.c
@@ -0,0 +1,1039 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Damon Chaplin <damon ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+/*
+ * ECellDateEdit - a subclass of ECellPopup used to show a date with a popup
+ * window to edit it.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-cell-date-edit.h"
+
+#include <string.h>
+#include <time.h>
+
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+#include <gdk/gdkkeysyms.h>
+
+#include <libedataserver/libedataserver.h>
+
+#include "e-calendar.h"
+#include "e-cell-text.h"
+#include "e-table-item.h"
+
+static void e_cell_date_edit_get_property	(GObject	*object,
+						 guint		 property_id,
+						 GValue		*value,
+						 GParamSpec	*pspec);
+static void e_cell_date_edit_set_property	(GObject	*object,
+						 guint		 property_id,
+						 const GValue	*value,
+						 GParamSpec	*pspec);
+static void e_cell_date_edit_dispose		(GObject	*object);
+
+static gint e_cell_date_edit_do_popup		(ECellPopup	*ecp,
+						 GdkEvent	*event,
+						 gint             row,
+						 gint             view_col);
+static void e_cell_date_edit_set_popup_values	(ECellDateEdit	*ecde);
+static void e_cell_date_edit_select_matching_time (ECellDateEdit	*ecde,
+						  gchar		*time);
+static void e_cell_date_edit_show_popup		(ECellDateEdit	*ecde,
+						 gint             row,
+						 gint             view_col);
+static void e_cell_date_edit_get_popup_pos	(ECellDateEdit	*ecde,
+						 gint             row,
+						 gint             view_col,
+						 gint		*x,
+						 gint		*y,
+						 gint		*height,
+						 gint		*width);
+
+static void e_cell_date_edit_rebuild_time_list	(ECellDateEdit	*ecde);
+
+static gint e_cell_date_edit_key_press		(GtkWidget	*popup_window,
+						 GdkEventKey	*event,
+						 ECellDateEdit	*ecde);
+static gint  e_cell_date_edit_button_press	(GtkWidget	*popup_window,
+						 GdkEvent	*button_event,
+						 ECellDateEdit	*ecde);
+static void e_cell_date_edit_on_ok_clicked	(GtkWidget	*button,
+						 ECellDateEdit	*ecde);
+static void e_cell_date_edit_show_time_invalid_warning	(ECellDateEdit	*ecde);
+static void e_cell_date_edit_on_now_clicked	(GtkWidget	*button,
+						 ECellDateEdit	*ecde);
+static void e_cell_date_edit_on_none_clicked	(GtkWidget	*button,
+						 ECellDateEdit	*ecde);
+static void e_cell_date_edit_on_today_clicked	(GtkWidget	*button,
+						 ECellDateEdit	*ecde);
+static void e_cell_date_edit_update_cell	(ECellDateEdit	*ecde,
+						 const gchar	*text);
+static void e_cell_date_edit_on_time_selected	(GtkTreeSelection *selection,
+						 ECellDateEdit *ecde);
+static void e_cell_date_edit_hide_popup		(ECellDateEdit	*ecde);
+
+/* Our arguments. */
+enum {
+	PROP_0,
+	PROP_SHOW_TIME,
+	PROP_SHOW_NOW_BUTTON,
+	PROP_SHOW_TODAY_BUTTON,
+	PROP_ALLOW_NO_DATE_SET,
+	PROP_USE_24_HOUR_FORMAT,
+	PROP_LOWER_HOUR,
+	PROP_UPPER_HOUR
+};
+
+G_DEFINE_TYPE (ECellDateEdit, e_cell_date_edit, E_TYPE_CELL_POPUP)
+
+static void
+e_cell_date_edit_class_init (ECellDateEditClass *class)
+{
+	GObjectClass *object_class;
+	ECellPopupClass	*ecpc;
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->get_property = e_cell_date_edit_get_property;
+	object_class->set_property = e_cell_date_edit_set_property;
+	object_class->dispose = e_cell_date_edit_dispose;
+
+	ecpc = E_CELL_POPUP_CLASS (class);
+	ecpc->popup = e_cell_date_edit_do_popup;
+
+	g_object_class_install_property (
+		object_class,
+		PROP_SHOW_TIME,
+		g_param_spec_boolean (
+			"show_time",
+			NULL,
+			NULL,
+			TRUE,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_SHOW_NOW_BUTTON,
+		g_param_spec_boolean (
+			"show_now_button",
+			NULL,
+			NULL,
+			TRUE,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_SHOW_TODAY_BUTTON,
+		g_param_spec_boolean (
+			"show_today_button",
+			NULL,
+			NULL,
+			TRUE,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_ALLOW_NO_DATE_SET,
+		g_param_spec_boolean (
+			"allow_no_date_set",
+			NULL,
+			NULL,
+			TRUE,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_USE_24_HOUR_FORMAT,
+		g_param_spec_boolean (
+			"use_24_hour_format",
+			NULL,
+			NULL,
+			TRUE,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_LOWER_HOUR,
+		g_param_spec_int (
+			"lower_hour",
+			NULL,
+			NULL,
+			G_MININT,
+			G_MAXINT,
+			0,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_UPPER_HOUR,
+		g_param_spec_int (
+			"upper_hour",
+			NULL,
+			NULL,
+			G_MININT,
+			G_MAXINT,
+			24,
+			G_PARAM_READWRITE));
+}
+
+static void
+e_cell_date_edit_init (ECellDateEdit *ecde)
+{
+	GtkWidget *frame, *vbox, *hbox, *vbox2;
+	GtkWidget *scrolled_window, *bbox, *tree_view;
+	GtkWidget *now_button, *today_button, *none_button, *ok_button;
+	GtkListStore *store;
+
+	ecde->lower_hour = 0;
+	ecde->upper_hour = 24;
+	ecde->use_24_hour_format = TRUE;
+	ecde->need_time_list_rebuild = TRUE;
+	ecde->freeze_count = 0;
+	ecde->time_callback = NULL;
+	ecde->time_callback_data = NULL;
+	ecde->time_callback_destroy = NULL;
+
+	/* We create one popup window for the ECell, since there will only
+	 * ever be one popup in use at a time. */
+	ecde->popup_window = gtk_window_new (GTK_WINDOW_POPUP);
+
+	gtk_window_set_type_hint (
+		GTK_WINDOW (ecde->popup_window),
+		GDK_WINDOW_TYPE_HINT_COMBO);
+	gtk_window_set_resizable (GTK_WINDOW (ecde->popup_window), TRUE);
+
+	frame = gtk_frame_new (NULL);
+	gtk_container_add (GTK_CONTAINER (ecde->popup_window), frame);
+	gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_OUT);
+	gtk_widget_show (frame);
+
+	vbox = gtk_vbox_new (FALSE, 0);
+	gtk_container_add (GTK_CONTAINER (frame), vbox);
+	gtk_widget_show (vbox);
+
+	hbox = gtk_hbox_new (FALSE, 4);
+	gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
+	gtk_widget_show (hbox);
+
+	ecde->calendar = e_calendar_new ();
+	gnome_canvas_item_set (
+		GNOME_CANVAS_ITEM (E_CALENDAR (ecde->calendar)->calitem),
+		"move_selection_when_moving", FALSE,
+		NULL);
+	gtk_box_pack_start (GTK_BOX (hbox), ecde->calendar, TRUE, TRUE, 0);
+	gtk_widget_show (ecde->calendar);
+
+	vbox2 = gtk_vbox_new (FALSE, 2);
+	gtk_box_pack_start (GTK_BOX (hbox), vbox2, TRUE, TRUE, 0);
+	gtk_widget_show (vbox2);
+
+	ecde->time_entry = gtk_entry_new ();
+	gtk_widget_set_size_request (ecde->time_entry, 50, -1);
+	gtk_box_pack_start (
+		GTK_BOX (vbox2), ecde->time_entry,
+		FALSE, FALSE, 0);
+	gtk_widget_show (ecde->time_entry);
+
+	scrolled_window = gtk_scrolled_window_new (NULL, NULL);
+	gtk_box_pack_start (GTK_BOX (vbox2), scrolled_window, TRUE, TRUE, 0);
+	gtk_scrolled_window_set_policy (
+		GTK_SCROLLED_WINDOW (scrolled_window),
+		GTK_POLICY_NEVER, GTK_POLICY_ALWAYS);
+	gtk_widget_show (scrolled_window);
+
+	store = gtk_list_store_new (1, G_TYPE_STRING);
+	tree_view = gtk_tree_view_new_with_model (GTK_TREE_MODEL (store));
+	g_object_unref (store);
+
+	gtk_tree_view_append_column (
+		GTK_TREE_VIEW (tree_view),
+		gtk_tree_view_column_new_with_attributes (
+		"Text", gtk_cell_renderer_text_new (), "text", 0, NULL));
+
+	gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (tree_view), FALSE);
+
+	gtk_scrolled_window_add_with_viewport (
+		GTK_SCROLLED_WINDOW (scrolled_window), tree_view);
+	gtk_container_set_focus_vadjustment (
+		GTK_CONTAINER (tree_view),
+		gtk_scrolled_window_get_vadjustment (
+		GTK_SCROLLED_WINDOW (scrolled_window)));
+	gtk_container_set_focus_hadjustment (
+		GTK_CONTAINER (tree_view),
+		gtk_scrolled_window_get_hadjustment (
+		GTK_SCROLLED_WINDOW (scrolled_window)));
+	gtk_widget_show (tree_view);
+	ecde->time_tree_view = tree_view;
+	g_signal_connect (
+		gtk_tree_view_get_selection (GTK_TREE_VIEW (tree_view)), "changed",
+		G_CALLBACK (e_cell_date_edit_on_time_selected), ecde);
+
+	bbox = gtk_hbutton_box_new ();
+	gtk_container_set_border_width (GTK_CONTAINER (bbox), 4);
+	gtk_box_set_spacing (GTK_BOX (bbox), 2);
+	gtk_box_pack_start (GTK_BOX (vbox), bbox, FALSE, FALSE, 0);
+	gtk_widget_show (bbox);
+
+	now_button = gtk_button_new_with_label (_("Now"));
+	gtk_container_add (GTK_CONTAINER (bbox), now_button);
+	gtk_widget_show (now_button);
+	g_signal_connect (
+		now_button, "clicked",
+		G_CALLBACK (e_cell_date_edit_on_now_clicked), ecde);
+	ecde->now_button = now_button;
+
+	today_button = gtk_button_new_with_label (_("Today"));
+	gtk_container_add (GTK_CONTAINER (bbox), today_button);
+	gtk_widget_show (today_button);
+	g_signal_connect (
+		today_button, "clicked",
+		G_CALLBACK (e_cell_date_edit_on_today_clicked), ecde);
+	ecde->today_button = today_button;
+
+	/* Translators: "None" as a label of a button to unset date in a
+	 * date table cell. */
+	none_button = gtk_button_new_with_label (C_("table-date", "None"));
+	gtk_container_add (GTK_CONTAINER (bbox), none_button);
+	gtk_widget_show (none_button);
+	g_signal_connect (
+		none_button, "clicked",
+		G_CALLBACK (e_cell_date_edit_on_none_clicked), ecde);
+	ecde->none_button = none_button;
+
+	ok_button = gtk_button_new_with_label (_("OK"));
+	gtk_container_add (GTK_CONTAINER (bbox), ok_button);
+	gtk_widget_show (ok_button);
+	g_signal_connect (
+		ok_button, "clicked",
+		G_CALLBACK (e_cell_date_edit_on_ok_clicked), ecde);
+
+	g_signal_connect (
+		ecde->popup_window, "key_press_event",
+		G_CALLBACK (e_cell_date_edit_key_press), ecde);
+	g_signal_connect (
+		ecde->popup_window, "button_press_event",
+		G_CALLBACK (e_cell_date_edit_button_press), ecde);
+}
+
+/**
+ * e_cell_date_edit_new:
+ *
+ * Creates a new ECellDateEdit renderer.
+ *
+ * Returns: an ECellDateEdit object.
+ */
+ECell *
+e_cell_date_edit_new (void)
+{
+	return g_object_new (e_cell_date_edit_get_type (), NULL);
+}
+
+static void
+e_cell_date_edit_get_property (GObject *object,
+                               guint property_id,
+                               GValue *value,
+                               GParamSpec *pspec)
+{
+	ECellDateEdit *ecde;
+
+	ecde = E_CELL_DATE_EDIT (object);
+
+	switch (property_id) {
+	case PROP_SHOW_TIME:
+		g_value_set_boolean (value, gtk_widget_get_visible (ecde->time_entry));
+		return;
+	case PROP_SHOW_NOW_BUTTON:
+		g_value_set_boolean (value, gtk_widget_get_visible (ecde->now_button));
+		return;
+	case PROP_SHOW_TODAY_BUTTON:
+		g_value_set_boolean (value, gtk_widget_get_visible (ecde->today_button));
+		return;
+	case PROP_ALLOW_NO_DATE_SET:
+		g_value_set_boolean (value, gtk_widget_get_visible (ecde->none_button));
+		return;
+	case PROP_USE_24_HOUR_FORMAT:
+		g_value_set_boolean (value, ecde->use_24_hour_format);
+		return;
+	case PROP_LOWER_HOUR:
+		g_value_set_int (value, ecde->lower_hour);
+		return;
+	case PROP_UPPER_HOUR:
+		g_value_set_int (value, ecde->upper_hour);
+		return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+e_cell_date_edit_set_property (GObject *object,
+                               guint property_id,
+                               const GValue *value,
+                               GParamSpec *pspec)
+{
+	ECellDateEdit *ecde;
+	gint ivalue;
+	gboolean bvalue;
+
+	ecde = E_CELL_DATE_EDIT (object);
+
+	switch (property_id) {
+	case PROP_SHOW_TIME:
+		if (g_value_get_boolean (value)) {
+			gtk_widget_show (ecde->time_entry);
+			gtk_widget_show (ecde->time_tree_view);
+		} else {
+			gtk_widget_hide (ecde->time_entry);
+			gtk_widget_hide (ecde->time_tree_view);
+		}
+		return;
+	case PROP_SHOW_NOW_BUTTON:
+		if (g_value_get_boolean (value)) {
+			gtk_widget_show (ecde->now_button);
+		} else {
+			gtk_widget_hide (ecde->now_button);
+		}
+		return;
+	case PROP_SHOW_TODAY_BUTTON:
+		if (g_value_get_boolean (value)) {
+			gtk_widget_show (ecde->today_button);
+		} else {
+			gtk_widget_hide (ecde->today_button);
+		}
+		return;
+	case PROP_ALLOW_NO_DATE_SET:
+		if (g_value_get_boolean (value)) {
+			gtk_widget_show (ecde->none_button);
+		} else {
+			/* FIXME: What if we have no date set now. */
+			gtk_widget_hide (ecde->none_button);
+		}
+		return;
+	case PROP_USE_24_HOUR_FORMAT:
+		bvalue = g_value_get_boolean (value);
+		if (ecde->use_24_hour_format != bvalue) {
+			ecde->use_24_hour_format = bvalue;
+			ecde->need_time_list_rebuild = TRUE;
+		}
+		return;
+	case PROP_LOWER_HOUR:
+		ivalue = g_value_get_int (value);
+		ivalue = CLAMP (ivalue, 0, 24);
+		if (ecde->lower_hour != ivalue) {
+			ecde->lower_hour = ivalue;
+			ecde->need_time_list_rebuild = TRUE;
+		}
+		return;
+	case PROP_UPPER_HOUR:
+		ivalue = g_value_get_int (value);
+		ivalue = CLAMP (ivalue, 0, 24);
+		if (ecde->upper_hour != ivalue) {
+			ecde->upper_hour = ivalue;
+			ecde->need_time_list_rebuild = TRUE;
+		}
+		return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+e_cell_date_edit_dispose (GObject *object)
+{
+	ECellDateEdit *ecde = E_CELL_DATE_EDIT (object);
+
+	e_cell_date_edit_set_get_time_callback (ecde, NULL, NULL, NULL);
+
+	if (ecde->popup_window != NULL) {
+		gtk_widget_destroy (ecde->popup_window);
+		ecde->popup_window = NULL;
+	}
+
+	/* Chain up to parent's dispose() method. */
+	G_OBJECT_CLASS (e_cell_date_edit_parent_class)->dispose (object);
+}
+
+static gint
+e_cell_date_edit_do_popup (ECellPopup *ecp,
+                           GdkEvent *event,
+                           gint row,
+                           gint view_col)
+{
+	ECellDateEdit *ecde = E_CELL_DATE_EDIT (ecp);
+	GdkWindow *window;
+
+	e_cell_date_edit_show_popup (ecde, row, view_col);
+	e_cell_date_edit_set_popup_values (ecde);
+
+	gtk_grab_add (ecde->popup_window);
+
+	/* Set the focus to the first widget. */
+	gtk_widget_grab_focus (ecde->time_entry);
+	window = gtk_widget_get_window (ecde->popup_window);
+	gdk_window_focus (window, GDK_CURRENT_TIME);
+
+	return TRUE;
+}
+
+static void
+e_cell_date_edit_set_popup_values (ECellDateEdit *ecde)
+{
+	ECellPopup *ecp = E_CELL_POPUP (ecde);
+	ECellText *ecell_text = E_CELL_TEXT (ecp->child);
+	ECellView *ecv = (ECellView *) ecp->popup_cell_view;
+	ETableItem *eti;
+	ETableCol *ecol;
+	gchar *cell_text;
+	ETimeParseStatus status;
+	struct tm date_tm;
+	GDate date;
+	ECalendarItem *calitem;
+	gchar buffer[64];
+	gboolean is_date = TRUE;
+
+	eti = E_TABLE_ITEM (ecp->popup_cell_view->cell_view.e_table_item_view);
+	ecol = e_table_header_get_column (eti->header, ecp->popup_view_col);
+
+	cell_text = e_cell_text_get_text (
+		ecell_text, ecv->e_table_model,
+		ecol->col_idx, ecp->popup_row);
+
+	/* Try to parse just a date first. If the value is only a date, we
+	 * use a DATE value. */
+	status = e_time_parse_date (cell_text, &date_tm);
+	if (status == E_TIME_PARSE_INVALID) {
+		is_date = FALSE;
+		status = e_time_parse_date_and_time (cell_text, &date_tm);
+	}
+
+	/* If there is no date and time set, or the date is invalid, we clear
+	 * the selections, else we select the appropriate date & time. */
+	calitem = E_CALENDAR_ITEM (E_CALENDAR (ecde->calendar)->calitem);
+	if (status == E_TIME_PARSE_NONE || status == E_TIME_PARSE_INVALID) {
+		gtk_entry_set_text (GTK_ENTRY (ecde->time_entry), "");
+		e_calendar_item_set_selection (calitem, NULL, NULL);
+		gtk_tree_selection_unselect_all (
+			gtk_tree_view_get_selection (
+			GTK_TREE_VIEW (ecde->time_tree_view)));
+	} else {
+		if (is_date) {
+			buffer[0] = '\0';
+		} else {
+			e_time_format_time (
+				&date_tm, ecde->use_24_hour_format,
+				FALSE, buffer, sizeof (buffer));
+		}
+		gtk_entry_set_text (GTK_ENTRY (ecde->time_entry), buffer);
+
+		g_date_clear (&date, 1);
+		g_date_set_dmy (
+			&date,
+			date_tm.tm_mday,
+			date_tm.tm_mon + 1,
+			date_tm.tm_year + 1900);
+		e_calendar_item_set_selection (calitem, &date, &date);
+
+		if (is_date) {
+			gtk_tree_selection_unselect_all (
+				gtk_tree_view_get_selection (
+				GTK_TREE_VIEW (ecde->time_tree_view)));
+		} else {
+			e_cell_date_edit_select_matching_time (ecde, buffer);
+		}
+	}
+
+	e_cell_text_free_text (ecell_text, cell_text);
+}
+
+static void
+e_cell_date_edit_select_matching_time (ECellDateEdit *ecde,
+                                       gchar *time)
+{
+	gboolean found = FALSE;
+	gboolean valid;
+	GtkTreeSelection *selection;
+	GtkTreeIter iter;
+	GtkTreeModel *model;
+
+	model = gtk_tree_view_get_model (GTK_TREE_VIEW (ecde->time_tree_view));
+	selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (ecde->time_tree_view));
+
+	for (valid = gtk_tree_model_get_iter_first (model, &iter);
+	     valid && !found;
+	     valid = gtk_tree_model_iter_next (model, &iter)) {
+		gchar *str = NULL;
+
+		gtk_tree_model_get (model, &iter, 0, &str, -1);
+
+		if (g_str_equal (str, time)) {
+			GtkTreePath *path = gtk_tree_model_get_path (model, &iter);
+
+			gtk_tree_view_set_cursor (
+				GTK_TREE_VIEW (ecde->time_tree_view),
+				path, NULL, FALSE);
+			gtk_tree_view_scroll_to_cell (
+				GTK_TREE_VIEW (ecde->time_tree_view),
+				path, NULL, FALSE, 0.0, 0.0);
+			gtk_tree_path_free (path);
+
+			found = TRUE;
+		}
+
+		g_free (str);
+	}
+
+	if (!found) {
+		gtk_tree_selection_unselect_all (selection);
+		gtk_tree_view_scroll_to_point (GTK_TREE_VIEW (ecde->time_tree_view), 0, 0);
+	}
+}
+
+static void
+e_cell_date_edit_show_popup (ECellDateEdit *ecde,
+                             gint row,
+                             gint view_col)
+{
+	GdkWindow *window;
+	gint x, y, width, height;
+
+	if (ecde->need_time_list_rebuild)
+		e_cell_date_edit_rebuild_time_list (ecde);
+
+	/* This code is practically copied from GtkCombo. */
+
+	e_cell_date_edit_get_popup_pos (ecde, row, view_col, &x, &y, &height, &width);
+
+	window = gtk_widget_get_window (ecde->popup_window);
+	gtk_window_move (GTK_WINDOW (ecde->popup_window), x, y);
+	gtk_widget_set_size_request (ecde->popup_window, width, height);
+	gtk_widget_realize (ecde->popup_window);
+	gdk_window_resize (window, width, height);
+	gtk_widget_show (ecde->popup_window);
+
+	e_cell_popup_set_shown (E_CELL_POPUP (ecde), TRUE);
+}
+
+/* Calculates the size and position of the popup window (like GtkCombo). */
+static void
+e_cell_date_edit_get_popup_pos (ECellDateEdit *ecde,
+                                gint row,
+                                gint view_col,
+                                gint *x,
+                                gint *y,
+                                gint *height,
+                                gint *width)
+{
+	ECellPopup *ecp = E_CELL_POPUP (ecde);
+	ETableItem *eti;
+	GtkWidget *canvas;
+	GtkRequisition popup_requisition;
+	GtkAdjustment *adjustment;
+	GtkScrollable *scrollable;
+	GdkWindow *window;
+	gint avail_height, screen_width, column_width, row_height;
+	gdouble x1, y1, wx, wy;
+	gint value;
+
+	eti = E_TABLE_ITEM (ecp->popup_cell_view->cell_view.e_table_item_view);
+	canvas = GTK_WIDGET (GNOME_CANVAS_ITEM (eti)->canvas);
+
+	window = gtk_widget_get_window (canvas);
+	gdk_window_get_origin (window, x, y);
+
+	x1 = e_table_header_col_diff (eti->header, 0, view_col + 1);
+	y1 = e_table_item_row_diff (eti, 0, row + 1);
+	column_width = e_table_header_col_diff (
+		eti->header, view_col, view_col + 1);
+	row_height = e_table_item_row_diff (eti, row, row + 1);
+	gnome_canvas_item_i2w (GNOME_CANVAS_ITEM (eti), &x1, &y1);
+
+	gnome_canvas_world_to_window (
+		GNOME_CANVAS (canvas), x1, y1, &wx, &wy);
+
+	x1 = wx;
+	y1 = wy;
+
+	*x += x1;
+	/* The ETable positions don't include the grid lines, I think, so we
+	 * add 1. */
+	scrollable = GTK_SCROLLABLE (&GNOME_CANVAS (canvas)->layout);
+	adjustment = gtk_scrollable_get_vadjustment (scrollable);
+	value = (gint) gtk_adjustment_get_value (adjustment);
+	*y += y1 + 1 - value + ((GnomeCanvas *)canvas)->zoom_yofs;
+
+	avail_height = gdk_screen_height () - *y;
+
+	/* We'll use the entire screen width if needed, but we save space for
+	 * the vertical scrollbar in case we need to show that. */
+	screen_width = gdk_screen_width ();
+
+	gtk_widget_get_preferred_size (ecde->popup_window, &popup_requisition, NULL);
+
+	/* Calculate the desired width. */
+	*width = popup_requisition.width;
+
+	/* Use at least the same width as the column. */
+	if (*width < column_width)
+		*width = column_width;
+
+	/* Check if it fits in the available height. */
+	if (popup_requisition.height > avail_height) {
+		/* It doesn't fit, so we see if we have the minimum space
+		 * needed. */
+		if (*y - row_height > avail_height) {
+			/* We don't, so we show the popup above the cell
+			 * instead of below it. */
+			*y -= (popup_requisition.height + row_height);
+			if (*y < 0)
+				*y = 0;
+		}
+	}
+
+	/* We try to line it up with the right edge of the column, but we don't
+	 * want it to go off the edges of the screen. */
+	if (*x > screen_width)
+		*x = screen_width;
+	*x -= *width;
+	if (*x < 0)
+		*x = 0;
+
+	*height = popup_requisition.height;
+}
+
+/* This handles key press events in the popup window. If the Escape key is
+ * pressed we hide the popup, and do not change the cell contents. */
+static gint
+e_cell_date_edit_key_press (GtkWidget *popup_window,
+                            GdkEventKey *event,
+                            ECellDateEdit *ecde)
+{
+	/* If the Escape key is pressed we hide the popup. */
+	if (event->keyval != GDK_KEY_Escape)
+		return FALSE;
+
+	e_cell_date_edit_hide_popup (ecde);
+
+	return TRUE;
+}
+
+/* This handles button press events in the popup window. If the button is
+ * pressed outside the popup, we hide it and do not change the cell contents.
+*/
+static gint
+e_cell_date_edit_button_press (GtkWidget *popup_window,
+                               GdkEvent *button_event,
+                               ECellDateEdit *ecde)
+{
+	GtkWidget *event_widget;
+
+	event_widget = gtk_get_event_widget (button_event);
+
+	if (gtk_widget_get_toplevel (event_widget) != popup_window)
+		e_cell_date_edit_hide_popup (ecde);
+
+	return TRUE;
+}
+
+/* Clears the time list and rebuilds it using the lower_hour, upper_hour
+ * and use_24_hour_format settings. */
+static void
+e_cell_date_edit_rebuild_time_list (ECellDateEdit *ecde)
+{
+	GtkListStore *store;
+	gchar buffer[40];
+	struct tm tmp_tm;
+	gint hour, min;
+
+	store = GTK_LIST_STORE (gtk_tree_view_get_model (
+		GTK_TREE_VIEW (ecde->time_tree_view)));
+	gtk_list_store_clear (store);
+
+	/* Fill the struct tm with some sane values. */
+	tmp_tm.tm_year = 2000;
+	tmp_tm.tm_mon = 0;
+	tmp_tm.tm_mday = 1;
+	tmp_tm.tm_sec  = 0;
+	tmp_tm.tm_isdst = 0;
+
+	for (hour = ecde->lower_hour; hour <= ecde->upper_hour; hour++) {
+		/* We don't want to display midnight at the end, since that is
+		 * really in the next day. */
+		if (hour == 24)
+			break;
+
+		/* We want to finish on upper_hour, with min == 0. */
+		for (min = 0;
+		     min == 0 || (min < 60 && hour != ecde->upper_hour);
+		     min += 30) {
+			GtkTreeIter iter;
+
+			tmp_tm.tm_hour = hour;
+			tmp_tm.tm_min  = min;
+			e_time_format_time (&tmp_tm, ecde->use_24_hour_format,
+					    FALSE, buffer, sizeof (buffer));
+
+			gtk_list_store_append (store, &iter);
+			gtk_list_store_set (store, &iter, 0, buffer, -1);
+		}
+	}
+
+	ecde->need_time_list_rebuild = FALSE;
+}
+
+static void
+e_cell_date_edit_on_ok_clicked (GtkWidget *button,
+                                ECellDateEdit *ecde)
+{
+	ECalendarItem *calitem;
+	GDate start_date, end_date;
+	gboolean day_selected;
+	struct tm date_tm;
+	gchar buffer[64];
+	const gchar *text;
+	ETimeParseStatus status;
+	gboolean is_date = FALSE;
+
+	calitem = E_CALENDAR_ITEM (E_CALENDAR (ecde->calendar)->calitem);
+	day_selected = e_calendar_item_get_selection (
+		calitem, &start_date, &end_date);
+
+	text = gtk_entry_get_text (GTK_ENTRY (ecde->time_entry));
+	status = e_time_parse_time (text, &date_tm);
+	if (status == E_TIME_PARSE_INVALID) {
+		e_cell_date_edit_show_time_invalid_warning (ecde);
+		return;
+	} else if (status == E_TIME_PARSE_NONE) {
+		is_date = TRUE;
+	}
+
+	if (day_selected) {
+		date_tm.tm_year = g_date_get_year (&start_date) - 1900;
+		date_tm.tm_mon = g_date_get_month (&start_date) - 1;
+		date_tm.tm_mday = g_date_get_day (&start_date);
+		/* We need to call this to set the weekday. */
+		mktime (&date_tm);
+		e_time_format_date_and_time (&date_tm,
+					     ecde->use_24_hour_format,
+					     !is_date, FALSE,
+					     buffer, sizeof (buffer));
+	} else {
+		buffer[0] = '\0';
+	}
+
+	e_cell_date_edit_update_cell (ecde, buffer);
+	e_cell_date_edit_hide_popup (ecde);
+}
+
+static void
+e_cell_date_edit_show_time_invalid_warning (ECellDateEdit *ecde)
+{
+	GtkWidget *dialog;
+	struct tm date_tm;
+	gchar buffer[64];
+
+	/* Create a useful error message showing the correct format. */
+	date_tm.tm_year = 100;
+	date_tm.tm_mon = 0;
+	date_tm.tm_mday = 1;
+	date_tm.tm_hour = 1;
+	date_tm.tm_min = 30;
+	date_tm.tm_sec = 0;
+	date_tm.tm_isdst = -1;
+	e_time_format_time (&date_tm, ecde->use_24_hour_format, FALSE,
+			    buffer, sizeof (buffer));
+
+	/* FIXME: Fix transient settings - I'm not sure it works with popup
+	 * windows. Maybe we need to use a normal window without decorations.*/
+	dialog = gtk_message_dialog_new (
+		GTK_WINDOW (ecde->popup_window),
+		GTK_DIALOG_DESTROY_WITH_PARENT,
+		GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
+		_("The time must be in the format: %s"),
+		buffer);
+	gtk_dialog_run (GTK_DIALOG (dialog));
+	gtk_widget_destroy (dialog);
+}
+
+static void
+e_cell_date_edit_on_now_clicked (GtkWidget *button,
+                                 ECellDateEdit *ecde)
+{
+	struct tm tmp_tm;
+	time_t t;
+	gchar buffer[64];
+
+	if (ecde->time_callback) {
+		tmp_tm = ecde->time_callback (
+			ecde, ecde->time_callback_data);
+	} else {
+		t = time (NULL);
+		tmp_tm = *localtime (&t);
+	}
+
+	e_time_format_date_and_time (
+		&tmp_tm, ecde->use_24_hour_format,
+		TRUE, FALSE, buffer, sizeof (buffer));
+
+	e_cell_date_edit_update_cell (ecde, buffer);
+	e_cell_date_edit_hide_popup (ecde);
+}
+
+static void
+e_cell_date_edit_on_none_clicked (GtkWidget *button,
+                                  ECellDateEdit *ecde)
+{
+	e_cell_date_edit_update_cell (ecde, "");
+	e_cell_date_edit_hide_popup (ecde);
+}
+
+static void
+e_cell_date_edit_on_today_clicked (GtkWidget *button,
+                                   ECellDateEdit *ecde)
+{
+	struct tm tmp_tm;
+	time_t t;
+	gchar buffer[64];
+
+	if (ecde->time_callback) {
+		tmp_tm = ecde->time_callback (
+			ecde, ecde->time_callback_data);
+	} else {
+		t = time (NULL);
+		tmp_tm = *localtime (&t);
+	}
+
+	tmp_tm.tm_hour = 0;
+	tmp_tm.tm_min = 0;
+	tmp_tm.tm_sec = 0;
+
+	e_time_format_date_and_time (
+		&tmp_tm, ecde->use_24_hour_format,
+		FALSE, FALSE, buffer, sizeof (buffer));
+
+	e_cell_date_edit_update_cell (ecde, buffer);
+	e_cell_date_edit_hide_popup (ecde);
+}
+
+static void
+e_cell_date_edit_update_cell (ECellDateEdit *ecde,
+                              const gchar *text)
+{
+	ECellPopup *ecp = E_CELL_POPUP (ecde);
+	ECellText *ecell_text = E_CELL_TEXT (ecp->child);
+	ECellView *ecv = (ECellView *) ecp->popup_cell_view;
+	ETableItem *eti = E_TABLE_ITEM (ecv->e_table_item_view);
+	ETableCol *ecol;
+	gchar *old_text;
+
+	/* Compare the new text with the existing cell contents. */
+	ecol = e_table_header_get_column (eti->header, ecp->popup_view_col);
+
+	old_text = e_cell_text_get_text (
+		ecell_text, ecv->e_table_model,
+		ecol->col_idx, ecp->popup_row);
+
+	/* If they are different, update the cell contents. */
+	if (strcmp (old_text, text)) {
+		e_cell_text_set_value (
+			ecell_text, ecv->e_table_model,
+			ecol->col_idx, ecp->popup_row, text);
+		e_cell_leave_edit (
+			ecv, ecp->popup_view_col,
+			ecol->col_idx, ecp->popup_row, NULL);
+	}
+
+	e_cell_text_free_text (ecell_text, old_text);
+}
+
+static void
+e_cell_date_edit_on_time_selected (GtkTreeSelection *selection,
+                                   ECellDateEdit *ecde)
+{
+	gchar *list_item_text = NULL;
+	GtkTreeIter iter;
+	GtkTreeModel *model;
+
+	if (!gtk_tree_selection_get_selected (selection, &model, &iter))
+		return;
+
+	gtk_tree_model_get (model, &iter, 0, &list_item_text, -1);
+
+	g_return_if_fail (list_item_text != NULL);
+
+	gtk_entry_set_text (GTK_ENTRY (ecde->time_entry), list_item_text);
+
+	g_free (list_item_text);
+}
+
+static void
+e_cell_date_edit_hide_popup (ECellDateEdit *ecde)
+{
+	gtk_grab_remove (ecde->popup_window);
+	gtk_widget_hide (ecde->popup_window);
+	e_cell_popup_set_shown (E_CELL_POPUP (ecde), FALSE);
+}
+
+/* These freeze and thaw the rebuilding of the time list. They are useful when
+ * setting several properties which result in rebuilds of the list, e.g. the
+ * lower_hour, upper_hour and use_24_hour_format properties. */
+void
+e_cell_date_edit_freeze (ECellDateEdit *ecde)
+{
+	g_return_if_fail (E_IS_CELL_DATE_EDIT (ecde));
+
+	ecde->freeze_count++;
+}
+
+void
+e_cell_date_edit_thaw (ECellDateEdit *ecde)
+{
+	g_return_if_fail (E_IS_CELL_DATE_EDIT (ecde));
+
+	if (ecde->freeze_count > 0) {
+		ecde->freeze_count--;
+
+		if (ecde->freeze_count == 0)
+			e_cell_date_edit_rebuild_time_list (ecde);
+	}
+}
+
+/* Sets a callback to use to get the current time. This is useful if the
+ * application needs to use its own timezone data rather than rely on the
+ * Unix timezone. */
+void
+e_cell_date_edit_set_get_time_callback (ECellDateEdit *ecde,
+                                        ECellDateEditGetTimeCallback cb,
+                                        gpointer data,
+                                        GDestroyNotify destroy)
+{
+	g_return_if_fail (E_IS_CELL_DATE_EDIT (ecde));
+
+	if (ecde->time_callback_data && ecde->time_callback_destroy)
+		(*ecde->time_callback_destroy) (ecde->time_callback_data);
+
+	ecde->time_callback = cb;
+	ecde->time_callback_data = data;
+	ecde->time_callback_destroy = destroy;
+}
diff --git a/e-util/e-cell-date-edit.h b/e-util/e-cell-date-edit.h
new file mode 100644
index 0000000..ea70731
--- /dev/null
+++ b/e-util/e-cell-date-edit.h
@@ -0,0 +1,124 @@
+/*
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Damon Chaplin <damon ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+/*
+ * ECellDateEdit - a subclass of ECellPopup used to show a date with a popup
+ * window to edit it.
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef _E_CELL_DATE_EDIT_H_
+#define _E_CELL_DATE_EDIT_H_
+
+#include <time.h>
+
+#include <e-util/e-cell-popup.h>
+
+/* Standard GObject macros */
+#define E_TYPE_CELL_DATE_EDIT \
+	(e_cell_date_edit_get_type ())
+#define E_CELL_DATE_EDIT(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_CELL_DATE_EDIT, ECellDateEdit))
+#define E_CELL_DATE_EDIT_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_CELL_DATE_EDIT, ECellDateEditClass))
+#define E_IS_CELL_DATE_EDIT(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_CELL_DATE_EDIT))
+#define E_IS_CELL_DATE_EDIT_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_CELL_DATE_EDIT))
+#define E_CELL_DATE_EDIT_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_CELL_DATE_EDIT, ECellDateEditClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ECellDateEdit ECellDateEdit;
+typedef struct _ECellDateEditClass ECellDateEditClass;
+
+/* The type of the callback function optionally used to get the current time.
+ */
+typedef struct tm (*ECellDateEditGetTimeCallback) (ECellDateEdit *ecde,
+						   gpointer	  data);
+
+struct _ECellDateEdit {
+	ECellPopup parent;
+
+	GtkWidget *popup_window;
+	GtkWidget *calendar;
+	GtkWidget *time_entry;
+	GtkWidget *time_tree_view;
+
+	GtkWidget *now_button;
+	GtkWidget *today_button;
+	GtkWidget *none_button;
+
+	/* This is the range of hours we show in the time list. */
+	gint lower_hour;
+	gint upper_hour;
+
+	/* TRUE if we use 24-hour format for the time list and entry. */
+	gboolean use_24_hour_format;
+
+	/* This is TRUE if we need to rebuild the list of times. */
+	gboolean need_time_list_rebuild;
+
+	/* The freeze count for rebuilding the time list. We only rebuild when
+	 * this is 0. */
+	gint freeze_count;
+
+	ECellDateEditGetTimeCallback time_callback;
+	gpointer time_callback_data;
+	GDestroyNotify time_callback_destroy;
+};
+
+struct _ECellDateEditClass {
+	ECellPopupClass parent_class;
+};
+
+GType		e_cell_date_edit_get_type	(void) G_GNUC_CONST;
+ECell *		e_cell_date_edit_new		(void);
+
+/* These freeze and thaw the rebuilding of the time list. They are useful when
+ * setting several properties which result in rebuilds of the list, e.g. the
+ * lower_hour, upper_hour and use_24_hour_format properties. */
+void		e_cell_date_edit_freeze		(ECellDateEdit *ecde);
+void		e_cell_date_edit_thaw		(ECellDateEdit *ecde);
+
+/* Sets a callback to use to get the current time. This is useful if the
+ * application needs to use its own timezone data rather than rely on the
+ * Unix timezone. */
+void		e_cell_date_edit_set_get_time_callback
+						(ECellDateEdit *ecde,
+						 ECellDateEditGetTimeCallback cb,
+						 gpointer data,
+						 GDestroyNotify destroy);
+
+G_END_DECLS
+
+#endif /* _E_CELL_DATE_EDIT_H_ */
diff --git a/e-util/e-cell-date.c b/e-util/e-cell-date.c
new file mode 100644
index 0000000..b8067f2
--- /dev/null
+++ b/e-util/e-cell-date.c
@@ -0,0 +1,130 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-cell-date.h"
+
+#include <sys/time.h>
+#include <time.h>
+#include <unistd.h>
+#include <string.h>
+
+#include <glib/gi18n.h>
+
+#include "e-datetime-format.h"
+#include "e-unicode.h"
+
+G_DEFINE_TYPE (ECellDate, e_cell_date, E_TYPE_CELL_TEXT)
+
+static gchar *
+ecd_get_text (ECellText *cell,
+              ETableModel *model,
+              gint col,
+              gint row)
+{
+	time_t date = GPOINTER_TO_INT (e_table_model_value_at (model, col, row));
+	const gchar *fmt_component, *fmt_part = NULL;
+
+	if (date == 0) {
+		return g_strdup (_("?"));
+	}
+
+	fmt_component = g_object_get_data ((GObject *) cell, "fmt-component");
+	if (!fmt_component || !*fmt_component)
+		fmt_component = "Default";
+	else
+		fmt_part = "table";
+
+	return e_datetime_format_format (
+		fmt_component, fmt_part, DTFormatKindDateTime, date);
+}
+
+static void
+ecd_free_text (ECellText *cell,
+               gchar *text)
+{
+	g_free (text);
+}
+
+static void
+e_cell_date_class_init (ECellDateClass *class)
+{
+	ECellTextClass *ectc = E_CELL_TEXT_CLASS (class);
+
+	ectc->get_text  = ecd_get_text;
+	ectc->free_text = ecd_free_text;
+}
+
+static void
+e_cell_date_init (ECellDate *ecd)
+{
+}
+
+/**
+ * e_cell_date_new:
+ * @fontname: font to be used to render on the screen
+ * @justify: Justification of the string in the cell.
+ *
+ * Creates a new ECell renderer that can be used to render dates that
+ * that come from the model.  The value returned from the model is
+ * interpreted as being a time_t.
+ *
+ * The ECellDate object support a large set of properties that can be
+ * configured through the Gtk argument system and allows the user to have
+ * a finer control of the way the string is displayed.  The arguments supported
+ * allow the control of strikeout, bold, color and a date filter.
+ *
+ * The arguments "strikeout_column", "underline_column", "bold_column"
+ * and "color_column" set and return an integer that points to a
+ * column in the model that controls these settings.  So controlling
+ * the way things are rendered is achieved by having special columns
+ * in the model that will be used to flag whether the date should be
+ * rendered with strikeout, underline, or bolded.  In the case of the
+ * "color_column" argument, the column in the model is expected to
+ * have a string that can be parsed by gdk_color_parse().
+ *
+ * Returns: an ECell object that can be used to render dates.
+ */
+ECell *
+e_cell_date_new (const gchar *fontname,
+                 GtkJustification justify)
+{
+	ECellDate *ecd = g_object_new (E_TYPE_CELL_DATE, NULL);
+
+	e_cell_text_construct (E_CELL_TEXT (ecd), fontname, justify);
+
+	return (ECell *) ecd;
+}
+
+void
+e_cell_date_set_format_component (ECellDate *ecd,
+                                  const gchar *fmt_component)
+{
+	g_return_if_fail (ecd != NULL);
+
+	g_object_set_data_full (
+		G_OBJECT (ecd), "fmt-component",
+		g_strdup (fmt_component), g_free);
+}
diff --git a/e-util/e-cell-date.h b/e-util/e-cell-date.h
new file mode 100644
index 0000000..a0968b0
--- /dev/null
+++ b/e-util/e-cell-date.h
@@ -0,0 +1,74 @@
+/*
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_CELL_DATE_H
+#define E_CELL_DATE_H
+
+#include <e-util/e-cell-text.h>
+
+/* Standard GObject macros */
+#define E_TYPE_CELL_DATE \
+	(e_cell_date_get_type ())
+#define E_CELL_DATE(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_CELL_DATE, ECellDate))
+#define E_CELL_DATE_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_CELL_DATE, ECellDateClass))
+#define E_IS_CELL_DATE(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_CELL_DATE))
+#define E_IS_CELL_DATE_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_CELL_DATE))
+#define E_CELL_DATE_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_CELL_DATE, ECellDateClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ECellDate ECellDate;
+typedef struct _ECellDateClass ECellDateClass;
+
+struct _ECellDate {
+	ECellText parent;
+};
+
+struct _ECellDateClass {
+	ECellTextClass parent_class;
+};
+
+GType		e_cell_date_get_type		(void) G_GNUC_CONST;
+ECell *		e_cell_date_new			(const gchar *fontname,
+						 GtkJustification justify);
+void		e_cell_date_set_format_component
+						(ECellDate *ecd,
+						 const gchar *fmt_component);
+
+G_END_DECLS
+
+#endif /* E_CELL_DATE_H */
diff --git a/e-util/e-cell-hbox.c b/e-util/e-cell-hbox.c
new file mode 100644
index 0000000..669dd44
--- /dev/null
+++ b/e-util/e-cell-hbox.c
@@ -0,0 +1,353 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Srinivasa Ragavan <sragavan novell com>
+ *
+ * A majority of code taken from:
+ *
+ * the ECellText renderer.
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <ctype.h>
+#include <math.h>
+#include <stdio.h>
+
+#include <gtk/gtk.h>
+
+/* #include "a11y/gal-a11y-e-cell-registry.h" */
+/* #include "a11y/gal-a11y-e-cell-vbox.h" */
+
+#include "e-cell-hbox.h"
+#include "e-table-item.h"
+
+G_DEFINE_TYPE (ECellHbox, e_cell_hbox, E_TYPE_CELL)
+
+#define INDENT_AMOUNT 16
+#define MAX_CELL_SIZE 25
+
+/*
+ * ECell::new_view method
+ */
+static ECellView *
+ecv_new_view (ECell *ecell,
+              ETableModel *table_model,
+              gpointer e_table_item_view)
+{
+	ECellHbox *ecv = E_CELL_HBOX (ecell);
+	ECellHboxView *hbox_view = g_new0 (ECellHboxView, 1);
+	gint i;
+
+	hbox_view->cell_view.ecell = ecell;
+	hbox_view->cell_view.e_table_model = table_model;
+	hbox_view->cell_view.e_table_item_view = e_table_item_view;
+	hbox_view->cell_view.kill_view_cb = NULL;
+	hbox_view->cell_view.kill_view_cb_data = NULL;
+
+	/* create our subcell view */
+	hbox_view->subcell_view_count = ecv->subcell_count;
+	hbox_view->subcell_views = g_new (ECellView *, hbox_view->subcell_view_count);
+	hbox_view->model_cols = g_new (int, hbox_view->subcell_view_count);
+	hbox_view->def_size_cols = g_new (int, hbox_view->subcell_view_count);
+
+	for (i = 0; i < hbox_view->subcell_view_count; i++) {
+		hbox_view->subcell_views[i] = e_cell_new_view (ecv->subcells[i], table_model, e_table_item_view /* XXX */);
+		hbox_view->model_cols[i] = ecv->model_cols[i];
+		hbox_view->def_size_cols[i] = ecv->def_size_cols[i];
+	}
+
+	return (ECellView *) hbox_view;
+}
+
+/*
+ * ECell::kill_view method
+ */
+static void
+ecv_kill_view (ECellView *ecv)
+{
+	ECellHboxView *hbox_view = (ECellHboxView *) ecv;
+	gint i;
+
+	if (hbox_view->cell_view.kill_view_cb)
+	    (hbox_view->cell_view.kill_view_cb)(ecv, hbox_view->cell_view.kill_view_cb_data);
+
+	if (hbox_view->cell_view.kill_view_cb_data)
+	    g_list_free (hbox_view->cell_view.kill_view_cb_data);
+
+	/* kill our subcell view */
+	for (i = 0; i < hbox_view->subcell_view_count; i++)
+		e_cell_kill_view (hbox_view->subcell_views[i]);
+
+	g_free (hbox_view->model_cols);
+	g_free (hbox_view->def_size_cols);
+	g_free (hbox_view->subcell_views);
+	g_free (hbox_view);
+}
+
+/*
+ * ECell::realize method
+ */
+static void
+ecv_realize (ECellView *ecell_view)
+{
+	ECellHboxView *hbox_view = (ECellHboxView *) ecell_view;
+	gint i;
+
+	/* realize our subcell view */
+	for (i = 0; i < hbox_view->subcell_view_count; i++)
+		e_cell_realize (hbox_view->subcell_views[i]);
+
+	if (E_CELL_CLASS (e_cell_hbox_parent_class)->realize)
+		(* E_CELL_CLASS (e_cell_hbox_parent_class)->realize) (ecell_view);
+}
+
+/*
+ * ECell::unrealize method
+ */
+static void
+ecv_unrealize (ECellView *ecv)
+{
+	ECellHboxView *hbox_view = (ECellHboxView *) ecv;
+	gint i;
+
+	/* unrealize our subcell view. */
+	for (i = 0; i < hbox_view->subcell_view_count; i++)
+		e_cell_unrealize (hbox_view->subcell_views[i]);
+
+	if (E_CELL_CLASS (e_cell_hbox_parent_class)->unrealize)
+		(* E_CELL_CLASS (e_cell_hbox_parent_class)->unrealize) (ecv);
+}
+
+/*
+ * ECell::draw method
+ */
+static void
+ecv_draw (ECellView *ecell_view,
+          cairo_t *cr,
+          gint model_col,
+          gint view_col,
+          gint row,
+          ECellFlags flags,
+          gint x1,
+          gint y1,
+          gint x2,
+          gint y2)
+{
+	ECellHboxView *hbox_view = (ECellHboxView *) ecell_view;
+
+	gint subcell_offset = 0;
+	gint i;
+	gint allotted_width = x2 - x1;
+
+	for (i = 0; i < hbox_view->subcell_view_count; i++) {
+		/* Now cause our subcells to draw their contents,
+		 * shifted by subcell_offset pixels */
+		gint width = allotted_width * hbox_view->def_size_cols[i] / 100;
+			/* e_cell_max_width_by_row (hbox_view->subcell_views[i], hbox_view->model_cols[i], view_col, row);
+		if (width < hbox_view->def_size_cols[i])
+			width = hbox_view->def_size_cols[i];
+		printf ("width of %d %d of %d\n", width,hbox_view->def_size_cols[i], allotted_width); */
+
+		e_cell_draw (
+			hbox_view->subcell_views[i], cr,
+			hbox_view->model_cols[i], view_col, row, flags,
+			x1 + subcell_offset , y1,
+			x1 + subcell_offset + width, y2);
+
+		subcell_offset += width; /* e_cell_max_width_by_row (hbox_view->subcell_views[i], hbox_view->model_cols[i], view_col, row); */
+	}
+}
+
+/*
+ * ECell::event method
+ */
+static gint
+ecv_event (ECellView *ecell_view,
+           GdkEvent *event,
+           gint model_col,
+           gint view_col,
+           gint row,
+           ECellFlags flags,
+           ECellActions *actions)
+{
+	ECellHboxView *hbox_view = (ECellHboxView *) ecell_view;
+	gint y = 0;
+	gint i;
+	gint subcell_offset = 0;
+
+	switch (event->type) {
+	case GDK_BUTTON_PRESS:
+	case GDK_BUTTON_RELEASE:
+	case GDK_2BUTTON_PRESS:
+	case GDK_3BUTTON_PRESS:
+		y = event->button.y;
+		break;
+	case GDK_MOTION_NOTIFY:
+		y = event->motion.y;
+		break;
+	default:
+		/* nada */
+		break;
+	}
+
+	for (i = 0; i < hbox_view->subcell_view_count; i++) {
+		gint width = e_cell_max_width_by_row (hbox_view->subcell_views[i], hbox_view->model_cols[i], view_col, row);
+		if (width < hbox_view->def_size_cols[i])
+			width = hbox_view->def_size_cols[i];
+		if (y < subcell_offset + width)
+			return e_cell_event (hbox_view->subcell_views[i], event, hbox_view->model_cols[i], view_col, row, flags, actions);
+		subcell_offset += width;
+	}
+	return 0;
+}
+
+/*
+ * ECell::height method
+ */
+static gint
+ecv_height (ECellView *ecell_view,
+            gint model_col,
+            gint view_col,
+            gint row)
+{
+	ECellHboxView *hbox_view = (ECellHboxView *) ecell_view;
+	gint height = 0, max_height = 0;
+	gint i;
+
+	for (i = 0; i < hbox_view->subcell_view_count; i++) {
+		height = e_cell_height (hbox_view->subcell_views[i], hbox_view->model_cols[i], view_col, row);
+		max_height = MAX (max_height, height);
+	}
+	return max_height;
+}
+
+/*
+ * ECell::max_width method
+ */
+static gint
+ecv_max_width (ECellView *ecell_view,
+               gint model_col,
+               gint view_col)
+{
+	ECellHboxView *hbox_view = (ECellHboxView *) ecell_view;
+	gint width = 0;
+	gint i;
+
+	for (i = 0; i < hbox_view->subcell_view_count; i++) {
+		gint cell_width = e_cell_max_width (hbox_view->subcell_views[i], hbox_view->model_cols[i], view_col);
+
+		if (cell_width < hbox_view->def_size_cols[i])
+			cell_width = hbox_view->def_size_cols[i];
+		width += cell_width;
+	}
+
+	return width;
+}
+
+/*
+ * GObject::dispose method
+ */
+static void
+ecv_dispose (GObject *object)
+{
+	ECellHbox *ecv = E_CELL_HBOX (object);
+	gint i;
+
+	/* destroy our subcell */
+	for (i = 0; i < ecv->subcell_count; i++)
+		if (ecv->subcells[i])
+			g_object_unref (ecv->subcells[i]);
+	g_free (ecv->subcells);
+	ecv->subcells = NULL;
+	ecv->subcell_count = 0;
+
+	g_free (ecv->model_cols);
+	ecv->model_cols = NULL;
+
+	g_free (ecv->def_size_cols);
+	ecv->def_size_cols = NULL;
+
+	G_OBJECT_CLASS (e_cell_hbox_parent_class)->dispose (object);
+}
+
+static void
+e_cell_hbox_class_init (ECellHboxClass *class)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (class);
+	ECellClass *ecc = E_CELL_CLASS (class);
+
+	object_class->dispose = ecv_dispose;
+
+	ecc->new_view         = ecv_new_view;
+	ecc->kill_view        = ecv_kill_view;
+	ecc->realize          = ecv_realize;
+	ecc->unrealize        = ecv_unrealize;
+	ecc->draw             = ecv_draw;
+	ecc->event            = ecv_event;
+	ecc->height           = ecv_height;
+
+	ecc->max_width        = ecv_max_width;
+
+/*	gal_a11y_e_cell_registry_add_cell_type (NULL, E_TYPE_CELL_HBOX, gal_a11y_e_cell_hbox_new); */
+}
+
+static void
+e_cell_hbox_init (ECellHbox *ecv)
+{
+	ecv->subcells = NULL;
+	ecv->subcell_count = 0;
+}
+
+/**
+ * e_cell_hbox_new:
+ *
+ * Creates a new ECell renderer that can be used to render multiple
+ * child cells.
+ *
+ * Return value: an ECell object that can be used to render multiple
+ * child cells.
+ **/
+ECell *
+e_cell_hbox_new (void)
+{
+	return g_object_new (E_TYPE_CELL_HBOX, NULL);
+}
+
+void
+e_cell_hbox_append (ECellHbox *hbox,
+                    ECell *subcell,
+                    gint model_col,
+                    gint size)
+{
+	hbox->subcell_count++;
+
+	hbox->subcells   = g_renew (ECell *, hbox->subcells,   hbox->subcell_count);
+	hbox->model_cols = g_renew (int,     hbox->model_cols, hbox->subcell_count);
+	hbox->def_size_cols = g_renew (int,     hbox->def_size_cols, hbox->subcell_count);
+
+	hbox->subcells[hbox->subcell_count - 1]   = subcell;
+	hbox->model_cols[hbox->subcell_count - 1] = model_col;
+	hbox->def_size_cols[hbox->subcell_count - 1] = size;
+
+	if (subcell)
+		g_object_ref_sink (subcell);
+}
diff --git a/e-util/e-cell-hbox.h b/e-util/e-cell-hbox.h
new file mode 100644
index 0000000..6a0495c
--- /dev/null
+++ b/e-util/e-cell-hbox.h
@@ -0,0 +1,91 @@
+/*
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Srinivasa Ragavan <sragavan novell com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef _E_CELL_HBOX_H_
+#define _E_CELL_HBOX_H_
+
+#include <libgnomecanvas/libgnomecanvas.h>
+
+#include <e-util/e-cell.h>
+
+/* Standard GObject macros */
+#define E_TYPE_CELL_HBOX \
+	(e_cell_hbox_get_type ())
+#define E_CELL_HBOX(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_CELL_HBOX, ECellHbox))
+#define E_CELL_HBOX_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_CELL_HBOX, ECellHboxClass))
+#define E_IS_CELL_HBOX(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_CELL_HBOX))
+#define E_IS_CELL_HBOX_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_CELL_HBOX))
+#define E_CELL_HBOX_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_CELL_HBOX, ECellHboxClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ECellHbox ECellHbox;
+typedef struct _ECellHboxView ECellHboxView;
+typedef struct _ECellHboxClass ECellHboxClass;
+
+struct _ECellHbox {
+	ECell parent;
+
+	gint subcell_count;
+	ECell **subcells;
+	gint *model_cols;
+	gint *def_size_cols;
+};
+
+struct _ECellHboxView {
+	ECellView cell_view;
+
+	gint subcell_view_count;
+	ECellView **subcell_views;
+	gint *model_cols;
+	gint *def_size_cols;
+};
+
+struct _ECellHboxClass {
+	ECellClass parent_class;
+};
+
+GType		e_cell_hbox_get_type		(void) G_GNUC_CONST;
+ECell *		e_cell_hbox_new			(void);
+void		e_cell_hbox_append		(ECellHbox *vbox,
+						 ECell *subcell,
+						 gint model_col,
+						 gint size);
+
+G_END_DECLS
+
+#endif /* _E_CELL_HBOX_H_ */
diff --git a/e-util/e-cell-number.c b/e-util/e-cell-number.c
new file mode 100644
index 0000000..5ecccad
--- /dev/null
+++ b/e-util/e-cell-number.c
@@ -0,0 +1,95 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-cell-number.h"
+
+#include <sys/time.h>
+#include <unistd.h>
+
+#include <glib/gi18n.h>
+
+#include "e-misc-utils.h"
+
+G_DEFINE_TYPE (ECellNumber, e_cell_number, E_TYPE_CELL_TEXT)
+
+static gchar *
+ecn_get_text (ECellText *cell,
+              ETableModel *model,
+              gint col,
+              gint row)
+{
+	gpointer value;
+
+	value = e_table_model_value_at (model, col, row);
+
+	return e_format_number (GPOINTER_TO_INT (value));
+}
+
+static void
+ecn_free_text (ECellText *cell,
+               gchar *text)
+{
+	g_free (text);
+}
+
+static void
+e_cell_number_class_init (ECellNumberClass *class)
+{
+	ECellTextClass *ectc = E_CELL_TEXT_CLASS (class);
+
+	ectc->get_text  = ecn_get_text;
+	ectc->free_text = ecn_free_text;
+}
+
+static void
+e_cell_number_init (ECellNumber *cell_number)
+{
+}
+
+/**
+ * e_cell_number_new:
+ * @fontname: font to be used to render on the screen
+ * @justify: Justification of the string in the cell.
+ *
+ * Creates a new ECell renderer that can be used to render numbers that
+ * that come from the model.  The value returned from the model is
+ * interpreted as being an int.
+ *
+ * See ECellText for other features.
+ *
+ * Returns: an ECell object that can be used to render numbers.
+ */
+ECell *
+e_cell_number_new (const gchar *fontname,
+                   GtkJustification justify)
+{
+	ECellNumber *ecn = g_object_new (E_TYPE_CELL_NUMBER, NULL);
+
+	e_cell_text_construct (E_CELL_TEXT (ecn), fontname, justify);
+
+	return (ECell *) ecn;
+}
+
diff --git a/e-util/e-cell-number.h b/e-util/e-cell-number.h
new file mode 100644
index 0000000..a5ef2ab
--- /dev/null
+++ b/e-util/e-cell-number.h
@@ -0,0 +1,71 @@
+/*
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_CELL_NUMBER_H
+#define E_CELL_NUMBER_H
+
+#include <e-util/e-cell-text.h>
+
+/* Standard GObject macros */
+#define E_TYPE_CELL_NUMBER \
+	(e_cell_number_get_type ())
+#define E_CELL_NUMBER(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_CELL_NUMBER, ECellNumber))
+#define E_CELL_NUMBER_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_CELL_NUMBER, ECellNumberClass))
+#define E_IS_CELL_NUMBER(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_CELL_NUMBER))
+#define E_IS_CELL_NUMBER_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_CELL_NUMBER))
+#define E_CELL_NUMBER_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_CELL_NUMBER, ECellNumberClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ECellNumber ECellNumber;
+typedef struct _ECellNumberClass ECellNumberClass;
+
+struct _ECellNumber {
+	ECellText parent;
+};
+
+struct _ECellNumberClass {
+	ECellTextClass parent_class;
+};
+
+GType		e_cell_number_get_type		(void) G_GNUC_CONST;
+ECell *		e_cell_number_new		(const gchar *fontname,
+						 GtkJustification justify);
+
+G_END_DECLS
+
+#endif /* E_CELL_NUMBER_H */
diff --git a/widgets/table/e-cell-percent.c b/e-util/e-cell-percent.c
similarity index 100%
rename from widgets/table/e-cell-percent.c
rename to e-util/e-cell-percent.c
diff --git a/e-util/e-cell-percent.h b/e-util/e-cell-percent.h
new file mode 100644
index 0000000..57ce41d
--- /dev/null
+++ b/e-util/e-cell-percent.h
@@ -0,0 +1,76 @@
+/*
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Damon Chaplin <damon ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+/*
+ * ECellPercent - a subclass of ECellText used to show an integer percentage
+ * in an ETable.
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_CELL_PERCENT_H
+#define E_CELL_PERCENT_H
+
+#include <e-util/e-cell-text.h>
+
+/* Standard GObject macros */
+#define E_TYPE_CELL_PERCENT \
+	(e_cell_percent_get_type ())
+#define E_CELL_PERCENT(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_CELL_PERCENT, ECellPercent))
+#define E_CELL_PERCENT_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_CELL_PERCENT, ECellPercentClass))
+#define E_IS_CELL_PERCENT(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_CELL_PERCENT))
+#define E_IS_CELL_PERCENT_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_CELL_PERCENT))
+#define E_CELL_PERCENT_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_CELL_PERCENT, ECellPercentClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ECellPercent ECellPercent;
+typedef struct _ECellPercentClass ECellPercentClass;
+
+struct _ECellPercent {
+	ECellText parent;
+};
+
+struct _ECellPercentClass {
+	ECellTextClass parent_class;
+};
+
+GType		e_cell_percent_get_type		(void) G_GNUC_CONST;
+ECell *		e_cell_percent_new		(const gchar *fontname,
+						 GtkJustification justify);
+
+G_END_DECLS
+
+#endif /* E_CELL_PERCENT_H */
diff --git a/widgets/table/e-cell-pixbuf.c b/e-util/e-cell-pixbuf.c
similarity index 100%
rename from widgets/table/e-cell-pixbuf.c
rename to e-util/e-cell-pixbuf.c
diff --git a/e-util/e-cell-pixbuf.h b/e-util/e-cell-pixbuf.h
new file mode 100644
index 0000000..e76fcab
--- /dev/null
+++ b/e-util/e-cell-pixbuf.h
@@ -0,0 +1,75 @@
+/*
+ * e-cell-pixbuf.h - An ECell that displays a GdkPixbuf
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Vladimir Vukicevic <vladimir ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef _E_CELL_PIXBUF_H_
+#define _E_CELL_PIXBUF_H_
+
+#include <e-util/e-table.h>
+
+/* Standard GObject macros */
+#define E_TYPE_CELL_PIXBUF \
+	(e_cell_pixbuf_get_type ())
+#define E_CELL_PIXBUF(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_CELL_PIXBUF, ECellPixbuf))
+#define E_CELL_PIXBUF_CLASS(cls) \
+	(G_TYPE_CHECK_INSTANCE_CAST_CLASS \
+	((cls), E_TYPE_CELL_PIXBUF, ECellPixbufClass))
+#define E_IS_CELL_PIXBUF(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_CELL_PIXBUF))
+#define E_IS_CELL_PIXBUF_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_CELL_PIXBUF))
+#define E_CELL_PIXBUF_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_CELL_PIXBUF, ECellPixbufClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ECellPixbuf ECellPixbuf;
+typedef struct _ECellPixbufClass ECellPixbufClass;
+
+struct _ECellPixbuf {
+	ECell parent;
+
+	gint selected_column;
+	gint focused_column;
+	gint unselected_column;
+};
+
+struct _ECellPixbufClass {
+	ECellClass parent_class;
+};
+
+GType		e_cell_pixbuf_get_type		(void) G_GNUC_CONST;
+ECell *		e_cell_pixbuf_new		(void);
+
+G_END_DECLS
+
+#endif /* _E_CELL_PIXBUF_H */
diff --git a/e-util/e-cell-popup.c b/e-util/e-cell-popup.c
new file mode 100644
index 0000000..19c32a6
--- /dev/null
+++ b/e-util/e-cell-popup.c
@@ -0,0 +1,550 @@
+/*
+ * e-cell-popup.c: Popup cell renderer
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+/*
+ * ECellPopup - an abstract ECell class used to support popup selections like
+ * a GtkCombo widget. It contains a child ECell, e.g. an ECellText, but when
+ * selected it displays an arrow on the right edge which the user can click to
+ * show a popup. Subclasses implement the popup class function to show the
+ * popup.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <gdk/gdkkeysyms.h>
+
+#include "gal-a11y-e-cell-popup.h"
+#include "gal-a11y-e-cell-registry.h"
+
+#include "e-cell-popup.h"
+#include "e-table-item.h"
+#include <gtk/gtk.h>
+
+#define E_CELL_POPUP_ARROW_SIZE		16
+#define E_CELL_POPUP_ARROW_PAD		3
+
+static void	e_cell_popup_dispose	(GObject	*object);
+
+static ECellView * ecp_new_view		(ECell		*ecell,
+					 ETableModel	*table_model,
+					 void		*e_table_item_view);
+static void	ecp_kill_view		(ECellView	*ecv);
+static void	ecp_realize		(ECellView	*ecv);
+static void	ecp_unrealize		(ECellView	*ecv);
+static void	ecp_draw		(ECellView	*ecv,
+					 cairo_t	*cr,
+					 gint		 model_col,
+					 gint		 view_col,
+					 gint		 row,
+					 ECellFlags	 flags,
+					 gint		 x1,
+					 gint		 y1,
+					 gint		 x2,
+					 gint		 y2);
+static gint	ecp_event		(ECellView	*ecv,
+					 GdkEvent	*event,
+					 gint		 model_col,
+					 gint		 view_col,
+					 gint		 row,
+					 ECellFlags	 flags,
+					 ECellActions	*actions);
+static gint	ecp_height		(ECellView	*ecv,
+					 gint		 model_col,
+					 gint		 view_col,
+					 gint		 row);
+static gpointer	ecp_enter_edit		(ECellView	*ecv,
+					 gint		 model_col,
+					 gint		 view_col,
+					 gint		 row);
+static void	ecp_leave_edit		(ECellView	*ecv,
+					 gint		 model_col,
+					 gint		 view_col,
+					 gint		 row,
+					 void		*edit_context);
+static void	ecp_print		(ECellView	*ecv,
+					 GtkPrintContext *context,
+					 gint		 model_col,
+					 gint		 view_col,
+					 gint		 row,
+					 gdouble		 width,
+					 gdouble		 height);
+static gdouble	ecp_print_height	(ECellView	*ecv,
+					 GtkPrintContext *context,
+					 gint		 model_col,
+					 gint		 view_col,
+					 gint		 row,
+					 gdouble		 width);
+static gint	ecp_max_width		(ECellView	*ecv,
+					 gint		 model_col,
+					 gint		 view_col);
+static gchar *ecp_get_bg_color (ECellView *ecell_view, gint row);
+
+static gint e_cell_popup_do_popup	(ECellPopupView	*ecp_view,
+					 GdkEvent	*event,
+					 gint             row,
+					 gint             model_col);
+
+G_DEFINE_TYPE (ECellPopup, e_cell_popup, E_TYPE_CELL)
+
+static void
+e_cell_popup_class_init (ECellPopupClass *class)
+{
+	ECellClass *ecc = E_CELL_CLASS (class);
+
+	G_OBJECT_CLASS (class)->dispose = e_cell_popup_dispose;
+
+	ecc->new_view     = ecp_new_view;
+	ecc->kill_view    = ecp_kill_view;
+	ecc->realize      = ecp_realize;
+	ecc->unrealize    = ecp_unrealize;
+	ecc->draw         = ecp_draw;
+	ecc->event        = ecp_event;
+	ecc->height       = ecp_height;
+	ecc->enter_edit   = ecp_enter_edit;
+	ecc->leave_edit   = ecp_leave_edit;
+	ecc->print        = ecp_print;
+	ecc->print_height = ecp_print_height;
+	ecc->max_width	  = ecp_max_width;
+	ecc->get_bg_color = ecp_get_bg_color;
+
+	gal_a11y_e_cell_registry_add_cell_type (
+		NULL, E_TYPE_CELL_POPUP,
+		gal_a11y_e_cell_popup_new);
+}
+
+static void
+e_cell_popup_init (ECellPopup *ecp)
+{
+	ecp->popup_shown = FALSE;
+	ecp->popup_model = NULL;
+}
+
+/**
+ * e_cell_popup_new:
+ *
+ * Creates a new ECellPopup renderer.
+ *
+ * Returns: an ECellPopup object.
+ */
+ECell *
+e_cell_popup_new (void)
+{
+	return g_object_new (E_TYPE_CELL_POPUP, NULL);
+}
+
+static void
+e_cell_popup_dispose (GObject *object)
+{
+	ECellPopup *ecp = E_CELL_POPUP (object);
+
+	if (ecp->child)
+		g_object_unref (ecp->child);
+	ecp->child = NULL;
+
+	G_OBJECT_CLASS (e_cell_popup_parent_class)->dispose (object);
+}
+
+/*
+ * ECell::new_view method
+ */
+static ECellView *
+ecp_new_view (ECell *ecell,
+              ETableModel *table_model,
+              gpointer e_table_item_view)
+{
+	ECellPopup *ecp = E_CELL_POPUP (ecell);
+	ECellPopupView *ecp_view;
+
+	/* We must have a child ECell before we create any views. */
+	g_return_val_if_fail (ecp->child != NULL, NULL);
+
+	ecp_view = g_new0 (ECellPopupView, 1);
+
+	ecp_view->cell_view.ecell = ecell;
+	ecp_view->cell_view.e_table_model = table_model;
+	ecp_view->cell_view.e_table_item_view = e_table_item_view;
+	ecp_view->cell_view.kill_view_cb = NULL;
+	ecp_view->cell_view.kill_view_cb_data = NULL;
+
+	ecp_view->child_view = e_cell_new_view (
+		ecp->child, table_model,
+		e_table_item_view);
+
+	return (ECellView *) ecp_view;
+}
+
+/*
+ * ECell::kill_view method
+ */
+static void
+ecp_kill_view (ECellView *ecv)
+{
+	ECellPopupView *ecp_view = (ECellPopupView *) ecv;
+
+	if (ecp_view->cell_view.kill_view_cb)
+		ecp_view->cell_view.kill_view_cb (
+			ecv, ecp_view->cell_view.kill_view_cb_data);
+
+	if (ecp_view->cell_view.kill_view_cb_data)
+		g_list_free (ecp_view->cell_view.kill_view_cb_data);
+
+	if (ecp_view->child_view)
+		e_cell_kill_view (ecp_view->child_view);
+
+	g_free (ecp_view);
+}
+
+/*
+ * ECell::realize method
+ */
+static void
+ecp_realize (ECellView *ecv)
+{
+	ECellPopupView *ecp_view = (ECellPopupView *) ecv;
+
+	e_cell_realize (ecp_view->child_view);
+
+	if (E_CELL_CLASS (e_cell_popup_parent_class)->realize)
+		(* E_CELL_CLASS (e_cell_popup_parent_class)->realize) (ecv);
+}
+
+/*
+ * ECell::unrealize method
+ */
+static void
+ecp_unrealize (ECellView *ecv)
+{
+	ECellPopupView *ecp_view = (ECellPopupView *) ecv;
+
+	e_cell_unrealize (ecp_view->child_view);
+
+	if (E_CELL_CLASS (e_cell_popup_parent_class)->unrealize)
+		(* E_CELL_CLASS (e_cell_popup_parent_class)->unrealize) (ecv);
+}
+
+/*
+ * ECell::draw method
+ */
+static void
+ecp_draw (ECellView *ecv,
+          cairo_t *cr,
+          gint model_col,
+          gint view_col,
+          gint row,
+          ECellFlags flags,
+          gint x1,
+          gint y1,
+          gint x2,
+          gint y2)
+{
+	ECellPopup *ecp = E_CELL_POPUP (ecv->ecell);
+	ECellPopupView *ecp_view = (ECellPopupView *) ecv;
+	GtkWidget *canvas;
+	gboolean show_popup_arrow;
+
+	cairo_save (cr);
+
+	canvas = GTK_WIDGET (GNOME_CANVAS_ITEM (ecv->e_table_item_view)->canvas);
+
+	/* Display the popup arrow if we are the cursor cell, or the popup
+	 * is shown for this cell. */
+	show_popup_arrow =
+		e_table_model_is_cell_editable (
+			ecv->e_table_model, model_col, row) &&
+		(flags & E_CELL_CURSOR ||
+			(ecp->popup_shown && ecp->popup_view_col == view_col
+			&& ecp->popup_row == row
+			&& ecp->popup_model == ((ECellView *) ecp_view)->e_table_model));
+
+	if (flags & E_CELL_CURSOR)
+		ecp->popup_arrow_shown = show_popup_arrow;
+
+	if (show_popup_arrow) {
+		GtkStyleContext *style_context;
+		GdkRGBA color;
+		gint arrow_x;
+		gint arrow_y;
+		gint arrow_size;
+		gint midpoint_y;
+
+		e_cell_draw (
+			ecp_view->child_view, cr, model_col,
+			view_col, row, flags,
+			x1, y1, x2 - E_CELL_POPUP_ARROW_SIZE, y2);
+
+		midpoint_y = y1 + ((y2 - y1 + 1) / 2);
+
+		arrow_x = x2 - E_CELL_POPUP_ARROW_SIZE;
+		arrow_y = midpoint_y - E_CELL_POPUP_ARROW_SIZE / 2;
+		arrow_size = E_CELL_POPUP_ARROW_SIZE;
+
+		style_context = gtk_widget_get_style_context (canvas);
+
+		gtk_style_context_save (style_context);
+
+		gtk_style_context_add_class (
+			style_context, GTK_STYLE_CLASS_CELL);
+
+		gtk_style_context_get_background_color (
+			style_context, GTK_STATE_FLAG_NORMAL, &color);
+
+		cairo_save (cr);
+		gdk_cairo_set_source_rgba (cr, &color);
+		gtk_render_background (
+			style_context, cr,
+			(gdouble) arrow_x,
+			(gdouble) arrow_y,
+			(gdouble) arrow_size,
+			(gdouble) arrow_size);
+		cairo_restore (cr);
+
+		arrow_x += E_CELL_POPUP_ARROW_PAD;
+		arrow_y += E_CELL_POPUP_ARROW_PAD;
+		arrow_size -= (E_CELL_POPUP_ARROW_PAD * 2);
+
+		cairo_save (cr);
+		gtk_render_arrow (
+			style_context, cr, G_PI,
+			(gdouble) arrow_x,
+			(gdouble) arrow_y,
+			(gdouble) arrow_size);
+		cairo_restore (cr);
+
+		gtk_style_context_restore (style_context);
+	} else {
+		e_cell_draw (
+			ecp_view->child_view, cr, model_col,
+			view_col, row, flags, x1, y1, x2, y2);
+	}
+
+	cairo_restore (cr);
+}
+
+/*
+ * ECell::event method
+ */
+static gint
+ecp_event (ECellView *ecv,
+           GdkEvent *event,
+           gint model_col,
+           gint view_col,
+           gint row,
+           ECellFlags flags,
+           ECellActions *actions)
+{
+	ECellPopupView *ecp_view = (ECellPopupView *) ecv;
+	ECellPopup *ecp = E_CELL_POPUP (ecp_view->cell_view.ecell);
+	ETableItem *eti = E_TABLE_ITEM (ecv->e_table_item_view);
+	gint width;
+
+	switch (event->type) {
+	case GDK_BUTTON_PRESS:
+		if (e_table_model_is_cell_editable (ecv->e_table_model, model_col, row) &&
+		    flags & E_CELL_CURSOR
+		    && ecp->popup_arrow_shown) {
+			width = e_table_header_col_diff (
+				eti->header, view_col,
+				view_col + 1);
+
+			/* FIXME: The event coords seem to be relative to the
+			 * text within the cell, so we have to add 4. */
+			if (event->button.x + 4 >= width - E_CELL_POPUP_ARROW_SIZE) {
+				return e_cell_popup_do_popup (ecp_view, event, row, view_col);
+			}
+		}
+		break;
+	case GDK_KEY_PRESS:
+		if (e_table_model_is_cell_editable (ecv->e_table_model, model_col, row) &&
+		    event->key.state & GDK_MOD1_MASK
+		    && event->key.keyval == GDK_KEY_Down) {
+			return e_cell_popup_do_popup (ecp_view, event, row, view_col);
+		}
+		break;
+	default:
+		break;
+	}
+
+	return e_cell_event (
+		ecp_view->child_view, event, model_col, view_col,
+		row, flags, actions);
+}
+
+/*
+ * ECell::height method
+ */
+static gint
+ecp_height (ECellView *ecv,
+            gint model_col,
+            gint view_col,
+            gint row)
+{
+	ECellPopupView *ecp_view = (ECellPopupView *) ecv;
+
+	return e_cell_height (ecp_view->child_view, model_col, view_col, row);
+}
+
+/*
+ * ECellView::enter_edit method
+ */
+static gpointer
+ecp_enter_edit (ECellView *ecv,
+                gint model_col,
+                gint view_col,
+                gint row)
+{
+	ECellPopupView *ecp_view = (ECellPopupView *) ecv;
+
+	return e_cell_enter_edit (ecp_view->child_view, model_col, view_col, row);
+}
+
+/*
+ * ECellView::leave_edit method
+ */
+static void
+ecp_leave_edit (ECellView *ecv,
+                gint model_col,
+                gint view_col,
+                gint row,
+                gpointer edit_context)
+{
+	ECellPopupView *ecp_view = (ECellPopupView *) ecv;
+
+	e_cell_leave_edit (
+		ecp_view->child_view, model_col, view_col, row,
+		edit_context);
+}
+
+static void
+ecp_print (ECellView *ecv,
+           GtkPrintContext *context,
+           gint model_col,
+           gint view_col,
+           gint row,
+           gdouble width,
+           gdouble height)
+{
+	ECellPopupView *ecp_view = (ECellPopupView *) ecv;
+
+	e_cell_print (
+		ecp_view->child_view, context, model_col, view_col, row,
+		width, height);
+}
+
+static gdouble
+ecp_print_height (ECellView *ecv,
+                  GtkPrintContext *context,
+                  gint model_col,
+                  gint view_col,
+                  gint row,
+                  gdouble width)
+{
+	ECellPopupView *ecp_view = (ECellPopupView *) ecv;
+
+	return e_cell_print_height (
+		ecp_view->child_view, context, model_col,
+		view_col, row, width);
+}
+
+static gint
+ecp_max_width (ECellView *ecv,
+               gint model_col,
+               gint view_col)
+{
+	ECellPopupView *ecp_view = (ECellPopupView *) ecv;
+
+	return e_cell_max_width (ecp_view->child_view, model_col, view_col);
+}
+
+static gchar *
+ecp_get_bg_color (ECellView *ecell_view,
+                  gint row)
+{
+	ECellPopupView *ecp_view = (ECellPopupView *) ecell_view;
+
+	return e_cell_get_bg_color (ecp_view->child_view, row);
+}
+
+ECell *
+e_cell_popup_get_child (ECellPopup *ecp)
+{
+	g_return_val_if_fail (E_IS_CELL_POPUP (ecp), NULL);
+
+	return ecp->child;
+}
+
+void
+e_cell_popup_set_child (ECellPopup *ecp,
+                        ECell *child)
+{
+	g_return_if_fail (E_IS_CELL_POPUP (ecp));
+
+	if (ecp->child)
+		g_object_unref (ecp->child);
+
+	ecp->child = child;
+	g_object_ref (child);
+}
+
+static gint
+e_cell_popup_do_popup (ECellPopupView *ecp_view,
+                       GdkEvent *event,
+                       gint row,
+                       gint view_col)
+{
+	ECellPopup *ecp = E_CELL_POPUP (ecp_view->cell_view.ecell);
+	gint (*popup_func) (ECellPopup *ecp, GdkEvent *event, gint row, gint view_col);
+
+	ecp->popup_cell_view = ecp_view;
+
+	popup_func = E_CELL_POPUP_CLASS (G_OBJECT_GET_CLASS (ecp))->popup;
+
+	ecp->popup_view_col = view_col;
+	ecp->popup_row = row;
+	ecp->popup_model = ((ECellView *) ecp_view)->e_table_model;
+
+	return popup_func ? popup_func (ecp, event, row, view_col) : FALSE;
+}
+
+/* This redraws the popup cell. Only use this if you know popup_view_col and
+ * popup_row are valid. */
+void
+e_cell_popup_queue_cell_redraw (ECellPopup *ecp)
+{
+	ETableItem *eti;
+
+	eti = E_TABLE_ITEM (ecp->popup_cell_view->cell_view.e_table_item_view);
+
+	e_table_item_redraw_range (
+		eti, ecp->popup_view_col, ecp->popup_row,
+		ecp->popup_view_col, ecp->popup_row);
+}
+
+void
+e_cell_popup_set_shown (ECellPopup *ecp,
+                        gboolean shown)
+{
+	ecp->popup_shown = shown;
+	e_cell_popup_queue_cell_redraw (ecp);
+}
diff --git a/e-util/e-cell-popup.h b/e-util/e-cell-popup.h
new file mode 100644
index 0000000..0b973d8
--- /dev/null
+++ b/e-util/e-cell-popup.h
@@ -0,0 +1,118 @@
+/*
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Damon Chaplin <damon ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+/*
+ * ECellPopup - an ECell used to support popup selections like a GtkCombo
+ * widget. It contains a child ECell, e.g. an ECellText, but when selected it
+ * displays an arrow on the right edge which the user can click to show a
+ * popup. It will support subclassing or signals so that different types of
+ * popup can be provided.
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef _E_CELL_POPUP_H_
+#define _E_CELL_POPUP_H_
+
+#include <libgnomecanvas/libgnomecanvas.h>
+
+#include <e-util/e-cell.h>
+
+/* Standard GObject macros */
+#define E_TYPE_CELL_POPUP \
+	(e_cell_popup_get_type ())
+#define E_CELL_POPUP(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_CELL_POPUP, ECellPopup))
+#define E_CELL_POPUP_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_CELL_POPUP, ECellPopupClass))
+#define E_IS_CELL_POPUP(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_CELL_POPUP))
+#define E_IS_CELL_POPUP_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_CELL_POPUP))
+#define E_CELL_POPUP_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_CELL_POPUP, ECellPopupClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ECellPopup ECellPopup;
+typedef struct _ECellPopupView ECellPopupView;
+typedef struct _ECellPopupClass ECellPopupClass;
+
+struct _ECellPopup {
+	ECell parent;
+
+	ECell *child;
+
+	/* This is TRUE if the popup window is shown for the cell being
+	 * edited. While shown we display the arrow indented. */
+	gboolean popup_shown;
+
+	/* This is TRUE if the popup arrow is shown for the cell being edited.
+	 * This is needed to stop the first click on the cell from popping up
+	 * the popup window. We only popup the window after we have drawn the
+	 * arrow. */
+	gboolean popup_arrow_shown;
+
+	/* The view in which the popup is shown. */
+	ECellPopupView *popup_cell_view;
+
+	gint popup_view_col;
+	gint popup_row;
+	ETableModel *popup_model;
+};
+
+struct _ECellPopupView {
+	ECellView cell_view;
+
+	ECellView *child_view;
+};
+
+struct _ECellPopupClass {
+	ECellClass parent_class;
+
+	/* Virtual function for subclasses to override. */
+	gint		(*popup)		(ECellPopup *ecp,
+						 GdkEvent *event,
+						 gint row,
+						 gint view_col);
+};
+
+GType		e_cell_popup_get_type		(void) G_GNUC_CONST;
+ECell *		e_cell_popup_new		(void);
+ECell *		e_cell_popup_get_child		(ECellPopup *ecp);
+void		e_cell_popup_set_child		(ECellPopup *ecp,
+						 ECell *child);
+void		e_cell_popup_set_shown		(ECellPopup *ecp,
+						 gboolean shown);
+void		e_cell_popup_queue_cell_redraw	(ECellPopup *ecp);
+
+G_END_DECLS
+
+#endif /* _E_CELL_POPUP_H_ */
diff --git a/e-util/e-cell-renderer-color.c b/e-util/e-cell-renderer-color.c
new file mode 100644
index 0000000..4bbb131
--- /dev/null
+++ b/e-util/e-cell-renderer-color.c
@@ -0,0 +1,243 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* e-cell-renderer-color.c
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-cell-renderer-color.h"
+
+#include <string.h>
+#include <glib/gi18n-lib.h>
+
+#define E_CELL_RENDERER_COLOR_GET_PRIVATE(obj) \
+	(G_TYPE_INSTANCE_GET_PRIVATE \
+	((obj), E_TYPE_CELL_RENDERER_COLOR, ECellRendererColorPrivate))
+
+enum {
+	PROP_0,
+	PROP_COLOR
+};
+
+struct _ECellRendererColorPrivate {
+	GdkColor *color;
+};
+
+G_DEFINE_TYPE (
+	ECellRendererColor,
+	e_cell_renderer_color,
+	GTK_TYPE_CELL_RENDERER)
+
+static void
+cell_renderer_color_get_size (GtkCellRenderer *cell,
+                              GtkWidget *widget,
+                              const GdkRectangle *cell_area,
+                              gint *x_offset,
+                              gint *y_offset,
+                              gint *width,
+                              gint *height)
+{
+	gint color_width  = 16;
+	gint color_height = 16;
+	gint calc_width;
+	gint calc_height;
+	gfloat xalign;
+	gfloat yalign;
+	guint xpad;
+	guint ypad;
+
+	g_object_get (
+		cell, "xalign", &xalign, "yalign", &yalign,
+		"xpad", &xpad, "ypad", &ypad, NULL);
+
+	calc_width  = (gint) xpad * 2 + color_width;
+	calc_height = (gint) ypad * 2 + color_height;
+
+	if (cell_area && color_width > 0 && color_height > 0) {
+		if (x_offset) {
+			*x_offset = (((gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL) ?
+					(1.0 - xalign) : xalign) *
+					(cell_area->width - calc_width));
+			*x_offset = MAX (*x_offset, 0);
+		}
+
+		if (y_offset) {
+			*y_offset =(yalign *
+				(cell_area->height - calc_height));
+			*y_offset = MAX (*y_offset, 0);
+		}
+	} else {
+		if (x_offset) *x_offset = 0;
+		if (y_offset) *y_offset = 0;
+	}
+
+	if (width)
+		*width = calc_width;
+
+	if (height)
+		*height = calc_height;
+}
+
+static void
+cell_renderer_color_render (GtkCellRenderer *cell,
+                            cairo_t *cr,
+                            GtkWidget *widget,
+                            const GdkRectangle *background_area,
+                            const GdkRectangle *cell_area,
+                            GtkCellRendererState flags)
+{
+	ECellRendererColorPrivate *priv;
+	GdkRectangle pix_rect;
+	GdkRectangle draw_rect;
+	GdkRGBA rgba;
+	guint xpad;
+	guint ypad;
+
+	priv = E_CELL_RENDERER_COLOR_GET_PRIVATE (cell);
+
+	if (priv->color == NULL)
+		return;
+
+	cell_renderer_color_get_size (
+		cell, widget, cell_area,
+		&pix_rect.x, &pix_rect.y,
+		&pix_rect.width, &pix_rect.height);
+
+	g_object_get (cell, "xpad", &xpad, "ypad", &ypad, NULL);
+
+	pix_rect.x += cell_area->x + xpad;
+	pix_rect.y += cell_area->y + ypad;
+	pix_rect.width  -= xpad * 2;
+	pix_rect.height -= ypad * 2;
+
+	if (!gdk_rectangle_intersect (cell_area, &pix_rect, &draw_rect))
+		return;
+
+	rgba.red = priv->color->red / 65535.0;
+	rgba.green = priv->color->green / 65535.0;
+	rgba.blue = priv->color->blue / 65535.0;
+	rgba.alpha = 1.0;
+
+	gdk_cairo_set_source_rgba (cr, &rgba);
+	cairo_rectangle (cr, pix_rect.x, pix_rect.y, draw_rect.width, draw_rect.height);
+
+	cairo_fill (cr);
+}
+
+static void
+cell_renderer_color_set_property (GObject *object,
+                                  guint property_id,
+                                  const GValue *value,
+                                  GParamSpec *pspec)
+{
+	ECellRendererColorPrivate *priv;
+
+	priv = E_CELL_RENDERER_COLOR_GET_PRIVATE (object);
+
+	switch (property_id) {
+		case PROP_COLOR:
+			if (priv->color != NULL)
+				gdk_color_free (priv->color);
+			priv->color = g_value_dup_boxed (value);
+			return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+cell_renderer_color_get_property (GObject *object,
+                                  guint property_id,
+                                  GValue *value,
+                                  GParamSpec *pspec)
+{
+	ECellRendererColorPrivate *priv;
+
+	priv = E_CELL_RENDERER_COLOR_GET_PRIVATE (object);
+
+	switch (property_id) {
+		case PROP_COLOR:
+			g_value_set_boxed (value, priv->color);
+			return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+cell_renderer_color_finalize (GObject *object)
+{
+	ECellRendererColorPrivate *priv;
+
+	priv = E_CELL_RENDERER_COLOR_GET_PRIVATE (object);
+
+	if (priv->color != NULL)
+		gdk_color_free (priv->color);
+
+	/* Chain up to parent's finalize() method. */
+	G_OBJECT_CLASS (e_cell_renderer_color_parent_class)->finalize (object);
+}
+
+static void
+e_cell_renderer_color_class_init (ECellRendererColorClass *class)
+{
+	GObjectClass *object_class;
+	GtkCellRendererClass *cell_class;
+
+	g_type_class_add_private (class, sizeof (ECellRendererColorPrivate));
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->set_property = cell_renderer_color_set_property;
+	object_class->get_property = cell_renderer_color_get_property;
+	object_class->finalize = cell_renderer_color_finalize;
+
+	cell_class = GTK_CELL_RENDERER_CLASS (class);
+	cell_class->get_size = cell_renderer_color_get_size;
+	cell_class->render = cell_renderer_color_render;
+
+	g_object_class_install_property (
+		object_class,
+		PROP_COLOR,
+		g_param_spec_boxed (
+			"color",
+			"Color Info",
+			"The color to render",
+			GDK_TYPE_COLOR,
+			G_PARAM_READWRITE));
+}
+
+static void
+e_cell_renderer_color_init (ECellRendererColor *cellcolor)
+{
+	cellcolor->priv = E_CELL_RENDERER_COLOR_GET_PRIVATE (cellcolor);
+
+	g_object_set (cellcolor, "xpad", 4, NULL);
+}
+
+/**
+ * e_cell_renderer_color_new:
+ *
+ * Since: 2.22
+ **/
+GtkCellRenderer *
+e_cell_renderer_color_new (void)
+{
+	return g_object_new (E_TYPE_CELL_RENDERER_COLOR, NULL);
+}
diff --git a/e-util/e-cell-renderer-color.h b/e-util/e-cell-renderer-color.h
new file mode 100644
index 0000000..00dd615
--- /dev/null
+++ b/e-util/e-cell-renderer-color.h
@@ -0,0 +1,79 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* e-cell-renderer-color.h
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef _E_CELL_RENDERER_COLOR_H_
+#define _E_CELL_RENDERER_COLOR_H_
+
+#include <gtk/gtk.h>
+
+/* Standard GObject macros */
+#define E_TYPE_CELL_RENDERER_COLOR \
+	(e_cell_renderer_color_get_type ())
+#define E_CELL_RENDERER_COLOR(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_CELL_RENDERER_COLOR, ECellRendererColor))
+#define E_CELL_RENDERER_COLOR_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_CELL_RENDERER_COLOR, ECellRendererColorClass))
+#define E_IS_CELL_RENDERER_COLOR(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_CELL_RENDERER_COLOR))
+#define E_IS_CELL_RENDERER_COLOR_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE ((cls), E_TYPE_CELL_RENDERER_COLOR))
+#define E_CELL_RENDERER_COLOR_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_CELL_RENDERER_COLOR, ECellRendererColorClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ECellRendererColor ECellRendererColor;
+typedef struct _ECellRendererColorClass ECellRendererColorClass;
+typedef struct _ECellRendererColorPrivate ECellRendererColorPrivate;
+
+/**
+ * ECellRendererColor:
+ *
+ * Since: 2.22
+ **/
+struct _ECellRendererColor {
+	GtkCellRenderer parent;
+	ECellRendererColorPrivate *priv;
+};
+
+struct _ECellRendererColorClass {
+	GtkCellRendererClass parent_class;
+
+	/* Padding for future expansion */
+	void (*_gtk_reserved1) (void);
+	void (*_gtk_reserved2) (void);
+	void (*_gtk_reserved3) (void);
+	void (*_gtk_reserved4) (void);
+};
+
+GType            e_cell_renderer_color_get_type	(void);
+GtkCellRenderer *e_cell_renderer_color_new	(void);
+
+G_END_DECLS
+
+#endif /* _E_CELL_RENDERER_COLOR_H_ */
diff --git a/e-util/e-cell-size.c b/e-util/e-cell-size.c
new file mode 100644
index 0000000..02bde88
--- /dev/null
+++ b/e-util/e-cell-size.c
@@ -0,0 +1,112 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <sys/time.h>
+#include <unistd.h>
+
+#include "e-cell-size.h"
+
+G_DEFINE_TYPE (ECellSize, e_cell_size, E_TYPE_CELL_TEXT)
+
+static gchar *
+ecd_get_text (ECellText *cell,
+              ETableModel *model,
+              gint col,
+              gint row)
+{
+	gint size = GPOINTER_TO_INT (e_table_model_value_at (model, col, row));
+	gfloat fsize;
+
+	if (size < 1024) {
+		return g_strdup_printf ("%d bytes", size);
+	} else {
+		fsize = ((gfloat) size) / 1024.0;
+		if (fsize < 1024.0) {
+			return g_strdup_printf ("%d K", (gint) fsize);
+		} else {
+			fsize /= 1024.0;
+			return g_strdup_printf ("%.1f MB", fsize);
+		}
+	}
+}
+
+static void
+ecd_free_text (ECellText *cell,
+               gchar *text)
+{
+	g_free (text);
+}
+
+static void
+e_cell_size_class_init (ECellSizeClass *class)
+{
+	ECellTextClass *ectc = E_CELL_TEXT_CLASS (class);
+
+	ectc->get_text  = ecd_get_text;
+	ectc->free_text = ecd_free_text;
+}
+
+static void
+e_cell_size_init (ECellSize *e_cell_size)
+{
+}
+
+/**
+ * e_cell_size_new:
+ * @fontname: font to be used to render on the screen
+ * @justify: Justification of the string in the cell.
+ *
+ * Creates a new ECell renderer that can be used to render file sizes
+ * that that come from the model.  The value returned from the model
+ * is interpreted as being a time_t.
+ *
+ * The ECellSize object support a large set of properties that can be
+ * configured through the Gtk argument system and allows the user to
+ * have a finer control of the way the string is displayed.  The
+ * arguments supported allow the control of strikeout, underline,
+ * bold, color and a size filter.
+ *
+ * The arguments "strikeout_column", "underline_column", "bold_column"
+ * and "color_column" set and return an integer that points to a
+ * column in the model that controls these settings.  So controlling
+ * the way things are rendered is achieved by having special columns
+ * in the model that will be used to flag whether the size should be
+ * rendered with strikeout, underline, or bolded.  In the case of the
+ * "color_column" argument, the column in the model is expected to
+ * have a string that can be parsed by gdk_color_parse().
+ *
+ * Returns: an ECell object that can be used to render file sizes.  */
+ECell *
+e_cell_size_new (const gchar *fontname,
+                 GtkJustification justify)
+{
+	ECellSize *ecd = g_object_new (E_TYPE_CELL_SIZE, NULL);
+
+	e_cell_text_construct (E_CELL_TEXT (ecd), fontname, justify);
+
+	return (ECell *) ecd;
+}
+
diff --git a/e-util/e-cell-size.h b/e-util/e-cell-size.h
new file mode 100644
index 0000000..b04134c
--- /dev/null
+++ b/e-util/e-cell-size.h
@@ -0,0 +1,72 @@
+/*
+ * e-cell-size.h: Size item for e-table.
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_CELL_SIZE_H
+#define E_CELL_SIZE_H
+
+#include <e-util/e-cell-text.h>
+
+/* Standard GObject macros */
+#define E_TYPE_CELL_SIZE \
+	(e_cell_size_get_type ())
+#define E_CELL_SIZE(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_CELL_SIZE, ECellSize))
+#define E_CELL_SIZE_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_CELL_SIZE, ECellSizeClass))
+#define E_IS_CELL_SIZE(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_CELL_SIZE))
+#define E_IS_CELL_SIZE_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_CELL_SIZE))
+#define E_CELL_SIZE_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_CELL_SIZE, ECellSizeClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ECellSize ECellSize;
+typedef struct _ECellSizeClass ECellSizeClass;
+
+struct _ECellSize {
+	ECellText parent;
+};
+
+struct _ECellSizeClass {
+	ECellTextClass parent_class;
+};
+
+GType		e_cell_size_get_type		(void) G_GNUC_CONST;
+ECell *		e_cell_size_new			(const gchar *fontname,
+						 GtkJustification justify);
+
+G_END_DECLS
+
+#endif /* E_CELL_SIZE_H */
diff --git a/e-util/e-cell-text.c b/e-util/e-cell-text.c
new file mode 100644
index 0000000..577d41c
--- /dev/null
+++ b/e-util/e-cell-text.c
@@ -0,0 +1,2810 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Miguel de Icaza <miguel ximian com>
+ *      Chris Lahey <clahey ximian com>
+ *
+ * A lot of code taken from:
+ *
+ * Text item type for GnomeCanvas widget
+ *
+ * GnomeCanvas is basically a port of the Tk toolkit's most excellent
+ * canvas widget.  Tk is copyrighted by the Regents of the University
+ * of California, Sun Microsystems, and other parties.
+ *
+ * Copyright (C) 1998 The Free Software Foundation
+ *
+ * Author: Federico Mena <federico nuclecu unam mx>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <ctype.h>
+#include <math.h>
+#include <string.h>
+
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+#include <gdk/gdkkeysyms.h>
+
+#include <libgnomecanvas/libgnomecanvas.h>
+
+#include "e-canvas.h"
+#include "e-cell-text.h"
+#include "e-table-item.h"
+#include "e-table.h"
+#include "e-text-event-processor-emacs-like.h"
+#include "e-text-event-processor.h"
+#include "e-text.h"
+#include "e-unicode.h"
+
+#define d(x)
+#define DO_SELECTION 1
+#define VIEW_TO_CELL(view) E_CELL_TEXT (((ECellView *)view)->ecell)
+
+#if d(!)0
+#define e_table_item_leave_edit_(x) (e_table_item_leave_edit((x)), g_print ("%s: e_table_item_leave_edit\n", __FUNCTION__))
+#else
+#define e_table_item_leave_edit_(x) (e_table_item_leave_edit((x)))
+#endif
+
+/* This defines a line of text */
+struct line {
+	gchar *text;	/* Line's text UTF-8, it is a pointer into the text->text string */
+	gint length;	/* Line's length in BYTES */
+	gint width;	/* Line's width in pixels */
+	gint ellipsis_length;  /* Length before adding ellipsis in BYTES */
+};
+
+/* Object argument IDs */
+enum {
+	PROP_0,
+
+	PROP_STRIKEOUT_COLUMN,
+	PROP_UNDERLINE_COLUMN,
+	PROP_BOLD_COLUMN,
+	PROP_COLOR_COLUMN,
+	PROP_EDITABLE,
+	PROP_BG_COLOR_COLUMN
+};
+
+enum {
+	E_SELECTION_PRIMARY,
+	E_SELECTION_CLIPBOARD
+};
+
+/* signals */
+enum {
+	TEXT_INSERTED,
+	TEXT_DELETED,
+	LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+static GdkAtom clipboard_atom = GDK_NONE;
+
+G_DEFINE_TYPE (ECellText, e_cell_text, E_TYPE_CELL)
+
+#define UTF8_ATOM  gdk_atom_intern ("UTF8_STRING", FALSE)
+
+#define TEXT_PAD 4
+
+typedef struct {
+	gpointer lines;			/* Text split into lines (private field) */
+	gint num_lines;			/* Number of lines of text */
+	gint max_width;
+	gint ref_count;
+} ECellTextLineBreaks;
+
+typedef struct _CellEdit CellEdit;
+
+typedef struct {
+	ECellView    cell_view;
+	GdkCursor *i_cursor;
+
+	GnomeCanvas *canvas;
+
+	/*
+	 * During editing.
+	 */
+	CellEdit    *edit;
+
+	gint xofs, yofs;                 /* This gets added to the x
+                                           and y for the cell text. */
+	gdouble ellipsis_width[2];      /* The width of the ellipsis. */
+} ECellTextView;
+
+struct _CellEdit {
+
+	ECellTextView *text_view;
+
+	gint model_col, view_col, row;
+	gint cell_width;
+
+	PangoLayout *layout;
+
+	gchar *text;
+
+	gchar         *old_text;
+
+	/*
+	 * Where the editing is taking place
+	 */
+
+	gint xofs_edit, yofs_edit;       /* Offset because of editing.
+                                           This is negative compared
+                                           to the other offsets. */
+
+	/* This needs to be reworked a bit once we get line wrapping. */
+	gint selection_start;            /* Start of selection - IN BYTES */
+	gint selection_end;              /* End of selection - IN BYTES */
+	gboolean select_by_word;        /* Current selection is by word */
+
+	/* This section is for drag scrolling and blinking cursor. */
+	/* Cursor handling. */
+	gint timeout_id;                /* Current timeout id for scrolling */
+	GTimer *timer;                  /* Timer for blinking cursor and scrolling */
+
+	gint lastx, lasty;              /* Last x and y motion events */
+	gint last_state;                /* Last state */
+	gulong scroll_start;            /* Starting time for scroll (microseconds) */
+
+	gint show_cursor;               /* Is cursor currently shown */
+	gboolean button_down;           /* Is mouse button 1 down */
+
+	ETextEventProcessor *tep;       /* Text Event Processor */
+
+	gboolean has_selection;         /* TRUE if we have the selection */
+
+	guint pointer_in : 1;
+	guint default_cursor_shown : 1;
+	GtkIMContext *im_context;
+	gboolean need_im_reset;
+	gboolean im_context_signals_registered;
+
+	guint16 preedit_length;       /* length of preedit string, in bytes */
+	gint preedit_pos;             /* position of preedit cursor */
+
+	ECellActions actions;
+};
+
+static void e_cell_text_view_command (ETextEventProcessor *tep, ETextEventProcessorCommand *command, gpointer data);
+
+static void e_cell_text_view_get_selection (CellEdit *edit, GdkAtom selection, guint32 time);
+static void e_cell_text_view_supply_selection (CellEdit *edit, guint time, GdkAtom selection, gchar *data, gint length);
+
+static void _get_tep (CellEdit *edit);
+
+static gint get_position_from_xy (CellEdit *edit, gint x, gint y);
+static gboolean _blink_scroll_timeout (gpointer data);
+
+static void e_cell_text_preedit_changed_cb (GtkIMContext *context, ECellTextView *text_view);
+static void e_cell_text_commit_cb (GtkIMContext *context, const gchar  *str, ECellTextView *text_view);
+static gboolean e_cell_text_retrieve_surrounding_cb (GtkIMContext *context, ECellTextView *text_view);
+static gboolean e_cell_text_delete_surrounding_cb   (GtkIMContext *context, gint          offset, gint          n_chars, ECellTextView        *text_view);
+static void _insert (ECellTextView *text_view, const gchar *string, gint value);
+static void _delete_selection (ECellTextView *text_view);
+static PangoAttrList * build_attr_list (ECellTextView *text_view, gint row, gint text_length);
+static void update_im_cursor_location (ECellTextView *tv);
+
+static gchar *
+ect_real_get_text (ECellText *cell,
+                   ETableModel *model,
+                   gint col,
+                   gint row)
+{
+	return e_table_model_value_at (model, col, row);
+}
+
+static void
+ect_real_free_text (ECellText *cell,
+                    gchar *text)
+{
+}
+
+/* This is the default method for setting the ETableModel value based on
+ * the text in the ECellText. This simply uses the text as it is - it assumes
+ * the value in the model is a gchar *. Subclasses may parse the text into
+ * data structures to pass to the model. */
+static void
+ect_real_set_value (ECellText *cell,
+                    ETableModel *model,
+                    gint col,
+                    gint row,
+                    const gchar *text)
+{
+	e_table_model_set_value_at (model, col, row, text);
+}
+
+static void
+ect_queue_redraw (ECellTextView *text_view,
+                  gint view_col,
+                  gint view_row)
+{
+	e_table_item_redraw_range (
+		text_view->cell_view.e_table_item_view,
+		view_col, view_row, view_col, view_row);
+}
+
+/*
+ * Shuts down the editing process
+ */
+static void
+ect_stop_editing (ECellTextView *text_view,
+                  gboolean commit)
+{
+	GdkWindow *window;
+	CellEdit *edit = text_view->edit;
+	gint row, view_col, model_col;
+	gchar *old_text, *text;
+
+	if (!edit)
+		return;
+
+	window = gtk_widget_get_window (GTK_WIDGET (text_view->canvas));
+
+	row = edit->row;
+	view_col = edit->view_col;
+	model_col = edit->model_col;
+
+	old_text = edit->old_text;
+	text = edit->text;
+	if (edit->tep)
+		g_object_unref (edit->tep);
+	if (!edit->default_cursor_shown) {
+		gdk_window_set_cursor (window, NULL);
+		edit->default_cursor_shown = TRUE;
+	}
+	if (edit->timeout_id) {
+		g_source_remove (edit->timeout_id);
+		edit->timeout_id = 0;
+	}
+	if (edit->timer) {
+		g_timer_stop (edit->timer);
+		g_timer_destroy (edit->timer);
+		edit->timer = NULL;
+	}
+
+	g_signal_handlers_disconnect_matched (
+		edit->im_context,
+		G_SIGNAL_MATCH_DATA, 0, 0,
+		NULL, NULL, text_view);
+
+	if (edit->layout)
+		g_object_unref (edit->layout);
+
+	g_free (edit);
+
+	text_view->edit = NULL;
+	if (commit) {
+		/*
+		 * Accept the currently edited text.  if it's the same as what's in the cell, do nothing.
+		 */
+		ECellView *ecell_view = (ECellView *) text_view;
+		ECellText *ect = (ECellText *) ecell_view->ecell;
+
+		if (strcmp (old_text, text)) {
+			e_cell_text_set_value (
+				ect, ecell_view->e_table_model,
+				model_col, row, text);
+		}
+	}
+	g_free (text);
+	g_free (old_text);
+
+	ect_queue_redraw (text_view, view_col, row);
+}
+
+/*
+ * Cancels the edits
+ */
+static void
+ect_cancel_edit (ECellTextView *text_view)
+{
+	ect_stop_editing (text_view, FALSE);
+	e_table_item_leave_edit_ (text_view->cell_view.e_table_item_view);
+}
+
+/*
+ * ECell::new_view method
+ */
+static ECellView *
+ect_new_view (ECell *ecell,
+              ETableModel *table_model,
+              gpointer e_table_item_view)
+{
+	ECellTextView *text_view = g_new0 (ECellTextView, 1);
+	GnomeCanvas *canvas = GNOME_CANVAS_ITEM (e_table_item_view)->canvas;
+
+	text_view->cell_view.ecell = ecell;
+	text_view->cell_view.e_table_model = table_model;
+	text_view->cell_view.e_table_item_view = e_table_item_view;
+	text_view->cell_view.kill_view_cb = NULL;
+	text_view->cell_view.kill_view_cb_data = NULL;
+
+	text_view->canvas = canvas;
+
+	text_view->xofs = 0.0;
+	text_view->yofs = 0.0;
+
+	return (ECellView *) text_view;
+}
+
+/*
+ * ECell::kill_view method
+ */
+static void
+ect_kill_view (ECellView *ecv)
+{
+	ECellTextView *text_view = (ECellTextView *) ecv;
+
+	if (text_view->cell_view.kill_view_cb)
+	    (text_view->cell_view.kill_view_cb)(ecv, text_view->cell_view.kill_view_cb_data);
+
+	if (text_view->cell_view.kill_view_cb_data)
+	    g_list_free (text_view->cell_view.kill_view_cb_data);
+
+	g_free (text_view);
+}
+
+/*
+ * ECell::realize method
+ */
+static void
+ect_realize (ECellView *ecell_view)
+{
+	ECellTextView *text_view = (ECellTextView *) ecell_view;
+
+	text_view->i_cursor = gdk_cursor_new (GDK_XTERM);
+
+	if (E_CELL_CLASS (e_cell_text_parent_class)->realize)
+		(* E_CELL_CLASS (e_cell_text_parent_class)->realize) (ecell_view);
+}
+
+/*
+ * ECell::unrealize method
+ */
+static void
+ect_unrealize (ECellView *ecv)
+{
+	ECellTextView *text_view = (ECellTextView *) ecv;
+
+	if (text_view->edit) {
+		ect_cancel_edit (text_view);
+	}
+
+	g_object_unref (text_view->i_cursor);
+
+	if (E_CELL_CLASS (e_cell_text_parent_class)->unrealize)
+		(* E_CELL_CLASS (e_cell_text_parent_class)->unrealize) (ecv);
+
+}
+
+static PangoAttrList *
+build_attr_list (ECellTextView *text_view,
+                 gint row,
+                 gint text_length)
+{
+
+	ECellView *ecell_view = (ECellView *) text_view;
+	ECellText *ect = E_CELL_TEXT (ecell_view->ecell);
+	PangoAttrList *attrs = pango_attr_list_new ();
+	gboolean bold, strikeout, underline;
+
+	bold = ect->bold_column >= 0 &&
+		row >= 0 &&
+		e_table_model_value_at (ecell_view->e_table_model, ect->bold_column, row);
+	strikeout = ect->strikeout_column >= 0 &&
+		row >= 0 &&
+		e_table_model_value_at (ecell_view->e_table_model, ect->strikeout_column, row);
+	underline = ect->underline_column >= 0 &&
+		row >= 0 &&
+		e_table_model_value_at (ecell_view->e_table_model, ect->underline_column, row);
+
+	if (bold || strikeout || underline) {
+		if (bold) {
+			PangoAttribute *attr = pango_attr_weight_new (PANGO_WEIGHT_BOLD);
+			attr->start_index = 0;
+			attr->end_index = text_length;
+
+			pango_attr_list_insert_before (attrs, attr);
+		}
+		if (strikeout) {
+			PangoAttribute *attr = pango_attr_strikethrough_new (TRUE);
+			attr->start_index = 0;
+			attr->end_index = text_length;
+
+			pango_attr_list_insert_before (attrs, attr);
+		}
+		if (underline) {
+			PangoAttribute *attr = pango_attr_underline_new (TRUE);
+			attr->start_index = 0;
+			attr->end_index = text_length;
+
+			pango_attr_list_insert_before (attrs, attr);
+		}
+	}
+	return attrs;
+}
+
+static PangoLayout *
+layout_with_preedit (ECellTextView *text_view,
+                     gint row,
+                     const gchar *text,
+                     gint width)
+{
+	CellEdit *edit = text_view->edit;
+	PangoAttrList *attrs;
+	PangoLayout *layout;
+	GString *tmp_string = g_string_new (NULL);
+	PangoAttrList *preedit_attrs = NULL;
+	gchar *preedit_string = NULL;
+	gint preedit_length = 0;
+	gint text_length = strlen (text);
+	gint mlen = MIN (edit->selection_start,text_length);
+
+	gtk_im_context_get_preedit_string (
+		edit->im_context,
+		&preedit_string,&preedit_attrs,
+		NULL);
+	preedit_length = edit->preedit_length = strlen (preedit_string);;
+
+	layout = edit->layout;
+
+	g_string_prepend_len (tmp_string, text,text_length);
+
+	if (preedit_length) {
+
+		/* mlen is the text_length in bytes, not chars
+		 * check whether we are not inserting into
+		 * the middle of a utf8 character
+		 */
+
+		if (mlen < text_length) {
+			if (!g_utf8_validate (text + mlen, -1, NULL)) {
+				gchar *tc;
+				tc = g_utf8_find_next_char (text + mlen,NULL);
+				if (tc) {
+					mlen = (gint) (tc - text);
+				}
+			}
+		}
+
+		g_string_insert (tmp_string, mlen, preedit_string);
+	}
+
+	pango_layout_set_text (layout, tmp_string->str, tmp_string->len);
+
+	attrs = (PangoAttrList *) build_attr_list (text_view, row, text_length);
+
+	if (preedit_length)
+		pango_attr_list_splice (attrs, preedit_attrs, mlen, preedit_length);
+	pango_layout_set_attributes (layout, attrs);
+	g_string_free (tmp_string, TRUE);
+	if (preedit_string)
+		g_free (preedit_string);
+	if (preedit_attrs)
+		pango_attr_list_unref (preedit_attrs);
+	pango_attr_list_unref (attrs);
+
+	update_im_cursor_location (text_view);
+
+	return layout;
+}
+
+static PangoLayout *
+build_layout (ECellTextView *text_view,
+              gint row,
+              const gchar *text,
+              gint width)
+{
+	ECellView *ecell_view = (ECellView *) text_view;
+	ECellText *ect = E_CELL_TEXT (ecell_view->ecell);
+	PangoAttrList *attrs;
+	PangoLayout *layout;
+
+	layout = gtk_widget_create_pango_layout (GTK_WIDGET (((GnomeCanvasItem *) ecell_view->e_table_item_view)->canvas), text);
+
+	attrs = (PangoAttrList *) build_attr_list (text_view, row, text ? strlen (text) : 0);
+
+	pango_layout_set_attributes (layout, attrs);
+	pango_attr_list_unref (attrs);
+
+	if (text_view->edit || width <= 0)
+		return layout;
+
+	if (ect->font_name)
+	{
+		PangoFontDescription *desc = NULL, *fixed_desc = NULL;
+		gchar *fixed_family = NULL;
+		gint fixed_size = 0;
+		gboolean fixed_points = TRUE;
+
+		fixed_desc = pango_font_description_from_string (ect->font_name);
+		if (fixed_desc) {
+			fixed_family = (gchar *) pango_font_description_get_family (fixed_desc);
+			fixed_size = pango_font_description_get_size (fixed_desc);
+			fixed_points = !pango_font_description_get_size_is_absolute (fixed_desc);
+		}
+
+		desc = pango_font_description_copy (gtk_widget_get_style (GTK_WIDGET (((GnomeCanvasItem *) ecell_view->e_table_item_view)->canvas))->font_desc);
+		pango_font_description_set_family (desc, fixed_family);
+		if (fixed_points)
+			pango_font_description_set_size (desc, fixed_size);
+		else
+			pango_font_description_set_absolute_size (desc, fixed_size);
+/*		pango_font_description_set_style (desc, PANGO_STYLE_OBLIQUE); */
+		pango_layout_set_font_description (layout, desc);
+		pango_font_description_free (desc);
+		pango_font_description_free (fixed_desc);
+	}
+
+	pango_layout_set_width (layout, width * PANGO_SCALE);
+	pango_layout_set_wrap (layout, PANGO_WRAP_WORD_CHAR);
+
+	pango_layout_set_ellipsize (layout, PANGO_ELLIPSIZE_END);
+	pango_layout_set_height (layout, 0);
+
+	switch (ect->justify) {
+	case GTK_JUSTIFY_RIGHT:
+		pango_layout_set_alignment (layout, PANGO_ALIGN_RIGHT);
+		break;
+	case GTK_JUSTIFY_CENTER:
+		pango_layout_set_alignment (layout, PANGO_ALIGN_CENTER);
+		break;
+	case GTK_JUSTIFY_LEFT:
+	default:
+		break;
+	}
+
+	return layout;
+}
+
+static PangoLayout *
+generate_layout (ECellTextView *text_view,
+                 gint model_col,
+                 gint view_col,
+                 gint row,
+                 gint width)
+{
+	ECellView *ecell_view = (ECellView *) text_view;
+	ECellText *ect = E_CELL_TEXT (ecell_view->ecell);
+	PangoLayout *layout;
+	CellEdit *edit = text_view->edit;
+
+	if (edit && edit->layout && edit->model_col == model_col && edit->row == row) {
+		g_object_ref (edit->layout);
+		return edit->layout;
+	}
+
+	if (row >= 0) {
+		gchar *temp = e_cell_text_get_text (ect, ecell_view->e_table_model, model_col, row);
+		layout = build_layout (text_view, row, temp ? temp : "?", width);
+		e_cell_text_free_text (ect, temp);
+	} else
+		layout = build_layout (text_view, row, "Mumbo Jumbo", width);
+
+	return layout;
+}
+
+static void
+draw_cursor (cairo_t *cr,
+             gint x1,
+             gint y1,
+             PangoRectangle rect)
+{
+	gdouble scaled_x;
+	gdouble scaled_y;
+	gdouble scaled_height;
+
+	/* Pango stores each cursor position as a zero-width rectangle. */
+	scaled_x = x1 + ((gdouble) rect.x) / PANGO_SCALE;
+	scaled_y = y1 + ((gdouble) rect.y) / PANGO_SCALE;
+	scaled_height = ((gdouble) rect.height) / PANGO_SCALE;
+
+	/* Adding 0.5 to scaled_x gives a sharp, one-pixel line. */
+	cairo_move_to (cr, scaled_x + 0.5, scaled_y);
+	cairo_line_to (cr, scaled_x + 0.5, scaled_y + scaled_height);
+	cairo_set_line_width (cr, 1);
+	cairo_stroke (cr);
+}
+
+static gboolean
+show_pango_rectangle (CellEdit *edit,
+                      PangoRectangle rect)
+{
+	gint x1 = rect.x / PANGO_SCALE;
+	gint x2 = (rect.x + rect.width) / PANGO_SCALE;
+#if 0
+	gint y1 = rect.y / PANGO_SCALE;
+	gint y2 = (rect.y + rect.height) / PANGO_SCALE;
+#endif
+
+	gint new_xofs_edit = edit->xofs_edit;
+	gint new_yofs_edit = edit->yofs_edit;
+
+	if (x1 < new_xofs_edit)
+		new_xofs_edit = x1;
+	if (2 + x2 - edit->cell_width > new_xofs_edit)
+		new_xofs_edit = 2 + x2 - edit->cell_width;
+	if (new_xofs_edit < 0)
+		new_xofs_edit = 0;
+
+#if 0
+	if (y1 < new_yofs_edit)
+		new_yofs_edit = y1;
+	if (2 + y2 - edit->cell_height > new_yofs_edit)
+		new_yofs_edit = 2 + y2 - edit->cell_height;
+	if (new_yofs_edit < 0)
+		new_yofs_edit = 0;
+#endif
+
+	if (new_xofs_edit != edit->xofs_edit ||
+	    new_yofs_edit != edit->yofs_edit) {
+		edit->xofs_edit = new_xofs_edit;
+		edit->yofs_edit = new_yofs_edit;
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+static gint
+get_vertical_spacing (GtkWidget *canvas)
+{
+	GtkWidget *widget;
+	gint vspacing = 0;
+
+	g_return_val_if_fail (E_IS_CANVAS (canvas), 3);
+
+	/* The parent should be either an ETable or ETree. */
+	widget = gtk_widget_get_parent (canvas);
+
+	gtk_widget_style_get (widget, "vertical-spacing", &vspacing, NULL);
+
+	return vspacing;
+}
+
+/*
+ * ECell::draw method
+ */
+static void
+ect_draw (ECellView *ecell_view,
+          cairo_t *cr,
+          gint model_col,
+          gint view_col,
+          gint row,
+          ECellFlags flags,
+          gint x1,
+          gint y1,
+          gint x2,
+          gint y2)
+{
+	PangoLayout *layout;
+	ECellTextView *text_view = (ECellTextView *) ecell_view;
+	ECellText *ect = E_CELL_TEXT (ecell_view->ecell);
+	CellEdit *edit = text_view->edit;
+	gboolean selected;
+	GtkWidget *canvas = GTK_WIDGET (text_view->canvas);
+	GtkStyle *style;
+	gint x_origin, y_origin, vspacing;
+
+	cairo_save (cr);
+	style = gtk_widget_get_style (canvas);
+
+	selected = flags & E_CELL_SELECTED;
+
+	if (selected) {
+		if (gtk_widget_has_focus (canvas))
+			gdk_cairo_set_source_color (cr, &style->fg[GTK_STATE_SELECTED]);
+		else
+			gdk_cairo_set_source_color (cr, &style->fg[GTK_STATE_ACTIVE]);
+	} else {
+		gdk_cairo_set_source_color (cr, &style->text[GTK_STATE_NORMAL]);
+
+		if (ect->color_column != -1) {
+			gchar *color_spec;
+			GdkColor color;
+
+			color_spec = e_table_model_value_at (
+				ecell_view->e_table_model,
+				ect->color_column, row);
+			if (color_spec && gdk_color_parse (color_spec, &color))
+				gdk_cairo_set_source_color (cr, &color);
+		}
+	}
+
+	vspacing = get_vertical_spacing (canvas);
+
+	x1 += 4;
+	y1 += vspacing;
+	x2 -= 4;
+	y2 -= vspacing;
+
+	x_origin = x1 + ect->x + text_view->xofs - (edit ? edit->xofs_edit : 0);
+	y_origin = y1 + ect->y + text_view->yofs - (edit ? edit->yofs_edit : 0);
+
+	cairo_rectangle (cr, x1, y1, x2 - x1, y2 - y1);
+	cairo_clip (cr);
+
+	layout = generate_layout (text_view, model_col, view_col, row, x2 - x1);
+
+	if (edit && edit->view_col == view_col && edit->row == row) {
+		layout = layout_with_preedit  (text_view, row, edit->text ? edit->text : "?",  x2 - x1);
+	}
+
+	cairo_move_to (cr, x_origin, y_origin);
+	pango_cairo_show_layout (cr, layout);
+
+	if (edit && edit->view_col == view_col && edit->row == row) {
+		if (edit->selection_start != edit->selection_end) {
+			cairo_region_t *clip_region;
+			gint indices[2];
+			GtkStateType state;
+
+			state = edit->has_selection ? GTK_STATE_SELECTED : GTK_STATE_ACTIVE;
+
+			indices[0] = MIN (edit->selection_start, edit->selection_end);
+			indices[1] = MAX (edit->selection_start, edit->selection_end);
+
+			clip_region = gdk_pango_layout_get_clip_region (
+				layout, x_origin, y_origin, indices, 1);
+			gdk_cairo_region (cr, clip_region);
+			cairo_clip (cr);
+			cairo_region_destroy (clip_region);
+
+			gdk_cairo_set_source_color (cr, &style->base[state]);
+			cairo_paint (cr);
+
+			gdk_cairo_set_source_color (cr, &style->text[state]);
+			cairo_move_to (cr, x_origin, y_origin);
+			pango_cairo_show_layout (cr, layout);
+		} else {
+			if (edit->show_cursor) {
+				PangoRectangle strong_pos, weak_pos;
+				pango_layout_get_cursor_pos (layout, edit->selection_start + edit->preedit_length, &strong_pos, &weak_pos);
+
+				draw_cursor (cr, x_origin, y_origin, strong_pos);
+				if (strong_pos.x != weak_pos.x ||
+				    strong_pos.y != weak_pos.y ||
+				    strong_pos.width != weak_pos.width ||
+				    strong_pos.height != weak_pos.height)
+					draw_cursor (cr, x_origin, y_origin, weak_pos);
+			}
+		}
+	}
+
+	g_object_unref (layout);
+	cairo_restore (cr);
+}
+
+/*
+ * Get the background color
+ */
+static gchar *
+ect_get_bg_color (ECellView *ecell_view,
+                  gint row)
+{
+	ECellText *ect = E_CELL_TEXT (ecell_view->ecell);
+	gchar *color_spec;
+
+	if (ect->bg_color_column == -1)
+		return NULL;
+
+	color_spec = e_table_model_value_at (
+		ecell_view->e_table_model,
+		ect->bg_color_column, row);
+
+	return color_spec;
+}
+
+/*
+ * Selects the entire string
+ */
+
+static void
+ect_edit_select_all (ECellTextView *text_view)
+{
+	g_return_if_fail (text_view->edit);
+
+	text_view->edit->selection_start = 0;
+	text_view->edit->selection_end = strlen (text_view->edit->text);
+}
+
+static gboolean
+key_begins_editing (GdkEventKey *event)
+{
+	if (event->length == 0)
+		return FALSE;
+
+	return TRUE;
+}
+
+/*
+ * ECell::event method
+ */
+static gint
+ect_event (ECellView *ecell_view,
+           GdkEvent *event,
+           gint model_col,
+           gint view_col,
+           gint row,
+           ECellFlags flags,
+           ECellActions *actions)
+{
+	ECellTextView *text_view = (ECellTextView *) ecell_view;
+	ETextEventProcessorEvent e_tep_event;
+	gboolean edit_display = FALSE;
+	gint preedit_len;
+	CellEdit *edit = text_view->edit;
+	GtkWidget *canvas = GTK_WIDGET (text_view->canvas);
+	gint return_val = 0;
+	d (gboolean press = FALSE);
+
+	if (!(flags & E_CELL_EDITING))
+		return 0;
+
+	if (edit && !edit->preedit_length && flags & E_CELL_PREEDIT)
+		return 1;
+
+	if (edit && edit->view_col == view_col && edit->row == row) {
+		edit_display = TRUE;
+	}
+
+	e_tep_event.type = event->type;
+	switch (event->type) {
+	case GDK_FOCUS_CHANGE:
+		break;
+	case GDK_KEY_PRESS: /* Fall Through */
+		if (edit_display) {
+			edit->show_cursor = FALSE;
+		} else {
+			ect_stop_editing (text_view, TRUE);
+		}
+		return_val = TRUE;
+		/* Fallthrough */
+	case GDK_KEY_RELEASE:
+		preedit_len = edit_display ? edit->preedit_length : 0;
+		if (edit_display && edit->im_context &&
+				gtk_im_context_filter_keypress (\
+					edit->im_context,
+					(GdkEventKey *) event)) {
+
+			edit->need_im_reset = TRUE;
+			if (preedit_len && flags & E_CELL_PREEDIT)
+				return 0;
+			else
+				return 1;
+		}
+
+		if (event->key.keyval == GDK_KEY_Escape) {
+			/* if not changed, then pass this even to parent */
+			return_val = text_view->edit != NULL && text_view->edit->text && text_view->edit->old_text && 0 != strcmp (text_view->edit->text, text_view->edit->old_text);
+			ect_cancel_edit (text_view);
+			break;
+		}
+
+		if ((!edit_display) &&
+		    e_table_model_is_cell_editable (ecell_view->e_table_model, model_col, row) &&
+		    key_begins_editing (&event->key)) {
+			  e_table_item_enter_edit (text_view->cell_view.e_table_item_view, view_col, row);
+			  ect_edit_select_all (text_view);
+			  edit = text_view->edit;
+			  edit_display = TRUE;
+		}
+		if (edit_display) {
+			GdkEventKey key = event->key;
+			if (key.type == GDK_KEY_PRESS &&
+			    (key.keyval == GDK_KEY_KP_Enter || key.keyval == GDK_KEY_Return)) {
+				/* stop editing when it's only GDK_KEY_PRESS event */
+				e_table_item_leave_edit_ (text_view->cell_view.e_table_item_view);
+			} else {
+				e_tep_event.key.time = key.time;
+				e_tep_event.key.state = key.state;
+				e_tep_event.key.keyval = key.keyval;
+
+				/* This is probably ugly hack, but we have to handle UTF-8 input somehow */
+#if 0
+				e_tep_event.key.length = key.length;
+				e_tep_event.key.string = key.string;
+#else
+				e_tep_event.key.string = e_utf8_from_gtk_event_key (canvas, key.keyval, key.string);
+				if (e_tep_event.key.string != NULL) {
+					e_tep_event.key.length = strlen (e_tep_event.key.string);
+				} else {
+					e_tep_event.key.length = 0;
+				}
+#endif
+				_get_tep (edit);
+				return_val = e_text_event_processor_handle_event (edit->tep, &e_tep_event);
+				if (e_tep_event.key.string)
+					g_free ((gpointer) e_tep_event.key.string);
+				break;
+			}
+		}
+
+		break;
+	case GDK_BUTTON_PRESS: /* Fall Through */
+		d (press = TRUE);
+	case GDK_BUTTON_RELEASE:
+		d (g_print ("%s: %s\n", __FUNCTION__, press ? "GDK_BUTTON_PRESS" : "GDK_BUTTON_RELEASE"));
+		event->button.x -= 4;
+		event->button.y -= 1;
+		if ((!edit_display)
+		    && e_table_model_is_cell_editable (ecell_view->e_table_model, model_col, row)
+		    && event->type == GDK_BUTTON_RELEASE
+		    && event->button.button == 1) {
+			GdkEventButton button = event->button;
+
+			e_table_item_enter_edit (text_view->cell_view.e_table_item_view, view_col, row);
+			edit = text_view->edit;
+			edit_display = TRUE;
+
+			e_tep_event.button.type = GDK_BUTTON_PRESS;
+			e_tep_event.button.time = button.time;
+			e_tep_event.button.state = button.state;
+			e_tep_event.button.button = button.button;
+			e_tep_event.button.position = get_position_from_xy (edit, event->button.x, event->button.y);
+			e_tep_event.button.device =
+				gdk_event_get_device (event);
+			_get_tep (edit);
+			edit->actions = 0;
+			return_val = e_text_event_processor_handle_event (
+				edit->tep, &e_tep_event);
+			*actions = edit->actions;
+			if (event->button.button == 1) {
+				if (event->type == GDK_BUTTON_PRESS)
+					edit->button_down = TRUE;
+				else
+					edit->button_down = FALSE;
+			}
+			edit->lastx = button.x;
+			edit->lasty = button.y;
+			edit->last_state = button.state;
+
+			e_tep_event.button.type = GDK_BUTTON_RELEASE;
+		}
+		if (edit_display) {
+			GdkEventButton button = event->button;
+			e_tep_event.button.time = button.time;
+			e_tep_event.button.state = button.state;
+			e_tep_event.button.button = button.button;
+			e_tep_event.button.position = get_position_from_xy (edit, event->button.x, event->button.y);
+			e_tep_event.button.device =
+				gdk_event_get_device (event);
+			_get_tep (edit);
+			edit->actions = 0;
+			return_val = e_text_event_processor_handle_event (
+				edit->tep, &e_tep_event);
+			*actions = edit->actions;
+			if (event->button.button == 1) {
+				if (event->type == GDK_BUTTON_PRESS)
+					edit->button_down = TRUE;
+				else
+					edit->button_down = FALSE;
+			}
+			edit->lastx = button.x;
+			edit->lasty = button.y;
+			edit->last_state = button.state;
+		}
+		break;
+	case GDK_MOTION_NOTIFY:
+		event->motion.x -= 4;
+		event->motion.y -= 1;
+		if (edit_display) {
+			GdkEventMotion motion = event->motion;
+			e_tep_event.motion.time = motion.time;
+			e_tep_event.motion.state = motion.state;
+			e_tep_event.motion.position = get_position_from_xy (edit, event->motion.x, event->motion.y);
+			_get_tep (edit);
+			edit->actions = 0;
+			return_val = e_text_event_processor_handle_event (
+				edit->tep, &e_tep_event);
+			*actions = edit->actions;
+			edit->lastx = motion.x;
+			edit->lasty = motion.y;
+			edit->last_state = motion.state;
+		}
+		break;
+	case GDK_ENTER_NOTIFY:
+#if 0
+		edit->pointer_in = TRUE;
+#endif
+		if (edit_display) {
+			if (edit->default_cursor_shown) {
+				GdkWindow *window;
+
+				window = gtk_widget_get_window (canvas);
+				gdk_window_set_cursor (window, text_view->i_cursor);
+				edit->default_cursor_shown = FALSE;
+			}
+		}
+		break;
+	case GDK_LEAVE_NOTIFY:
+#if 0
+		text_view->pointer_in = FALSE;
+#endif
+		if (edit_display) {
+			if (!edit->default_cursor_shown) {
+				GdkWindow *window;
+
+				window = gtk_widget_get_window (canvas);
+				gdk_window_set_cursor (window, NULL);
+				edit->default_cursor_shown = TRUE;
+			}
+		}
+		break;
+	default:
+		break;
+	}
+
+	return return_val;
+}
+
+/*
+ * ECell::height method
+ */
+static gint
+ect_height (ECellView *ecell_view,
+            gint model_col,
+            gint view_col,
+            gint row)
+{
+	ECellTextView *text_view = (ECellTextView *) ecell_view;
+	gint height;
+	PangoLayout *layout;
+
+	layout = generate_layout (text_view, model_col, view_col, row, 0);
+	pango_layout_get_pixel_size (layout, NULL, &height);
+	g_object_unref (layout);
+	return height + (get_vertical_spacing (GTK_WIDGET (text_view->canvas)) * 2);
+}
+
+/*
+ * ECellView::enter_edit method
+ */
+static gpointer
+ect_enter_edit (ECellView *ecell_view,
+                gint model_col,
+                gint view_col,
+                gint row)
+{
+	ECellTextView *text_view = (ECellTextView *) ecell_view;
+	CellEdit *edit;
+	ECellText *ect = E_CELL_TEXT (ecell_view->ecell);
+	gchar *temp;
+
+	edit = g_new0 (CellEdit, 1);
+	text_view->edit = edit;
+
+	edit->im_context =  E_CANVAS (text_view->canvas)->im_context;
+	edit->need_im_reset = FALSE;
+	edit->im_context_signals_registered = FALSE;
+	edit->view_col = -1;
+	edit->model_col = -1;
+	edit->row = -1;
+
+	edit->text_view = text_view;
+	edit->model_col = model_col;
+	edit->view_col = view_col;
+	edit->row = row;
+	edit->cell_width = e_table_header_get_column (
+		((ETableItem *) ecell_view->e_table_item_view)->header,
+		view_col)->width - 8;
+
+	edit->layout = generate_layout (text_view, model_col, view_col, row, edit->cell_width);
+
+	edit->xofs_edit = 0.0;
+	edit->yofs_edit = 0.0;
+
+	edit->selection_start = 0;
+	edit->selection_end = 0;
+	edit->select_by_word = FALSE;
+
+	edit->timeout_id = g_timeout_add (10, _blink_scroll_timeout, text_view);
+	edit->timer = g_timer_new ();
+	g_timer_elapsed (edit->timer, &(edit->scroll_start));
+	g_timer_start (edit->timer);
+
+	edit->lastx = 0;
+	edit->lasty = 0;
+	edit->last_state = 0;
+
+	edit->scroll_start = 0;
+	edit->show_cursor = TRUE;
+	edit->button_down = FALSE;
+
+	edit->tep = NULL;
+
+	edit->has_selection = FALSE;
+
+	edit->pointer_in = FALSE;
+	edit->default_cursor_shown = TRUE;
+
+	temp = e_cell_text_get_text (ect, ecell_view->e_table_model, model_col, row);
+	edit->old_text = g_strdup (temp);
+	e_cell_text_free_text (ect, temp);
+	edit->text = g_strdup (edit->old_text);
+
+	if (edit->im_context) {
+		gtk_im_context_reset (edit->im_context);
+		if (!edit->im_context_signals_registered) {
+			g_signal_connect (
+				edit->im_context, "preedit_changed",
+				G_CALLBACK (e_cell_text_preedit_changed_cb),
+				text_view);
+			g_signal_connect (
+				edit->im_context, "commit",
+				G_CALLBACK (e_cell_text_commit_cb),
+				text_view);
+			g_signal_connect (
+				edit->im_context, "retrieve_surrounding",
+				G_CALLBACK (e_cell_text_retrieve_surrounding_cb),
+				text_view);
+			g_signal_connect (
+				edit->im_context, "delete_surrounding",
+				G_CALLBACK (e_cell_text_delete_surrounding_cb),
+				text_view);
+
+			edit->im_context_signals_registered = TRUE;
+		}
+		gtk_im_context_focus_in (edit->im_context);
+	}
+
+#if 0
+	if (edit->pointer_in) {
+		if (edit->default_cursor_shown) {
+			gdk_window_set_cursor (GTK_WIDGET (item->canvas)->window, text_view->i_cursor);
+			edit->default_cursor_shown = FALSE;
+		}
+	}
+#endif
+	ect_queue_redraw (text_view, view_col, row);
+
+	return NULL;
+}
+
+/*
+ * ECellView::leave_edit method
+ */
+static void
+ect_leave_edit (ECellView *ecell_view,
+                gint model_col,
+                gint view_col,
+                gint row,
+                gpointer edit_context)
+{
+	ECellTextView *text_view = (ECellTextView *) ecell_view;
+	CellEdit *edit = text_view->edit;
+
+	if (edit) {
+		if (edit->im_context) {
+			gtk_im_context_focus_out (edit->im_context);
+
+			if (edit->im_context_signals_registered) {
+				g_signal_handlers_disconnect_matched (edit->im_context, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, edit);
+				edit->im_context_signals_registered = FALSE;
+			}
+		}
+		ect_stop_editing (text_view, TRUE);
+	} else {
+		/*
+		 * We did invoke this leave edit internally
+		 */
+	}
+}
+
+/*
+ * ECellView::save_state method
+ */
+static gpointer
+ect_save_state (ECellView *ecell_view,
+                gint model_col,
+                gint view_col,
+                gint row,
+                gpointer edit_context)
+{
+	ECellTextView *text_view = (ECellTextView *) ecell_view;
+	CellEdit *edit = text_view->edit;
+
+	gint *save_state = g_new (int, 2);
+
+	save_state[0] = edit->selection_start;
+	save_state[1] = edit->selection_end;
+	return save_state;
+}
+
+/*
+ * ECellView::load_state method
+ */
+static void
+ect_load_state (ECellView *ecell_view,
+                gint model_col,
+                gint view_col,
+                gint row,
+                gpointer edit_context,
+                gpointer save_state)
+{
+	ECellTextView *text_view = (ECellTextView *) ecell_view;
+	CellEdit *edit = text_view->edit;
+	gint length;
+	gint *selection = save_state;
+
+	length = strlen (edit->text);
+
+	edit->selection_start = MIN (selection[0], length);
+	edit->selection_end = MIN (selection[1], length);
+
+	ect_queue_redraw (text_view, view_col, row);
+}
+
+/*
+ * ECellView::free_state method
+ */
+static void
+ect_free_state (ECellView *ecell_view,
+                gint model_col,
+                gint view_col,
+                gint row,
+                gpointer save_state)
+{
+	g_free (save_state);
+}
+
+static void
+get_font_size (PangoLayout *layout,
+               PangoFontDescription *font,
+               const gchar *text,
+               gdouble *width,
+               gdouble *height)
+{
+	gint w;
+	gint h;
+
+	g_return_if_fail (layout != NULL);
+	pango_layout_set_font_description (layout, font);
+	pango_layout_set_text (layout, text, -1);
+	pango_layout_set_width (layout, -1);
+	pango_layout_set_indent (layout, 0);
+
+	pango_layout_get_size (layout, &w, &h);
+
+	*width = (gdouble)w/(gdouble)PANGO_SCALE;
+	*height = (gdouble)h/(gdouble)PANGO_SCALE;
+}
+
+static void
+ect_print (ECellView *ecell_view,
+           GtkPrintContext *context,
+           gint model_col,
+           gint view_col,
+           gint row,
+           gdouble width,
+           gdouble height)
+{
+	PangoFontDescription *font_des;
+	PangoLayout *layout;
+	PangoContext *pango_context;
+	PangoFontMetrics *font_metrics;
+	ECellText *ect = E_CELL_TEXT (ecell_view->ecell);
+	ECellTextView *ectView = (ECellTextView *) ecell_view;
+	GtkWidget *canvas = GTK_WIDGET (ectView->canvas);
+	GtkStyle *style;
+	PangoDirection dir;
+	gboolean strikeout, underline;
+	cairo_t *cr;
+	gchar *string;
+	gdouble ty, ly, text_width = 0.0, text_height = 0.0;
+
+	cr = gtk_print_context_get_cairo_context (context);
+	string = e_cell_text_get_text (ect, ecell_view->e_table_model, model_col, row);
+
+	cairo_save (cr);
+	layout = gtk_print_context_create_pango_layout (context);
+	font_des = pango_font_description_from_string ("sans 10"); /* fix me font hardcoded */
+	pango_layout_set_font_description (layout, font_des);
+
+	pango_layout_set_text (layout, string, -1);
+	get_font_size (layout, font_des, string, &text_width, &text_height);
+
+	cairo_move_to (cr, 2, 2);
+	cairo_rectangle (cr, 2, 2, width + 2, height + 2);
+	cairo_clip (cr);
+
+	style = gtk_widget_get_style (canvas);
+	pango_context = gtk_widget_get_pango_context (canvas);
+	font_metrics = pango_context_get_metrics (
+		pango_context, style->font_desc,
+		pango_context_get_language (pango_context));
+	ty =  (gdouble)(text_height -
+		pango_font_metrics_get_ascent (font_metrics) -
+		pango_font_metrics_get_descent (font_metrics)) / 2.0 /(gdouble) PANGO_SCALE;
+
+	strikeout = ect->strikeout_column >= 0 && row >= 0 &&
+		e_table_model_value_at (ecell_view->e_table_model, ect->strikeout_column, row);
+	underline = ect->underline_column >= 0 && row >= 0 &&
+		e_table_model_value_at (ecell_view->e_table_model, ect->underline_column, row);
+
+	dir = pango_find_base_dir (string, strlen (string));
+
+	if (underline) {
+		ly = ty + (gdouble) pango_font_metrics_get_underline_position (font_metrics) / (gdouble) PANGO_SCALE;
+		cairo_new_path (cr);
+		if (dir == PANGO_DIRECTION_RTL) {
+			cairo_move_to (cr, width - 2, ly + text_height + 6);
+			cairo_line_to (cr, MAX (width - 2 - text_width, 2), ly + text_height + 6);
+		}
+		else {
+			cairo_move_to (cr, 2, ly + text_height + 6);
+			cairo_line_to (cr, MIN (2 + text_width, width - 2), ly + text_height + 6);
+		}
+		cairo_set_line_width (cr, (gdouble) pango_font_metrics_get_underline_thickness (font_metrics) / (gdouble) PANGO_SCALE);
+		cairo_stroke (cr);
+	}
+
+	if (strikeout) {
+		ly = ty + (gdouble) pango_font_metrics_get_strikethrough_position (font_metrics) / (gdouble) PANGO_SCALE;
+		cairo_new_path (cr);
+		if (dir == PANGO_DIRECTION_RTL) {
+			cairo_move_to (cr, width - 2, ly + text_height + 6);
+			cairo_line_to (cr, MAX (width - 2 - text_width, 2), ly + text_height + 6);
+		}
+		else {
+			cairo_move_to (cr, 2, ly + text_height + 6);
+			cairo_line_to (cr, MIN (2 + text_width, width - 2), ly + text_height + 6);
+		}
+			cairo_set_line_width (cr,(gdouble) pango_font_metrics_get_strikethrough_thickness (font_metrics) / (gdouble) PANGO_SCALE);
+
+			cairo_stroke (cr);
+	}
+
+	cairo_move_to (cr, 2, text_height- 5);
+	pango_layout_set_width (layout, (width - 4) * PANGO_SCALE);
+	pango_layout_set_wrap (layout, PANGO_WRAP_CHAR);
+	pango_cairo_show_layout (cr, layout);
+	cairo_restore (cr);
+
+	pango_font_description_free (font_des);
+	g_object_unref (layout);
+	e_cell_text_free_text (ect, string);
+}
+
+static gdouble
+ect_print_height (ECellView *ecell_view,
+                  GtkPrintContext *context,
+                  gint model_col,
+                  gint view_col,
+                  gint row,
+                  gdouble width)
+{
+	/*
+	 * Font size is 16 by default. To leave some margin for cell
+	 * text area, 2 for footer, 2 for header, actual print height
+	 * should be 16 + 4.
+	 * Height of some special font is much higher than others,
+	 * such	as Arabic. So leave some more margin for cell.
+	 */
+	PangoFontDescription *font_des;
+	PangoLayout *layout;
+	ECellText *ect = E_CELL_TEXT (ecell_view->ecell);
+	gchar *string;
+	gdouble text_width = 0.0, text_height = 0.0;
+	gint lines = 1;
+
+	string = e_cell_text_get_text (ect, ecell_view->e_table_model, model_col, row);
+
+	layout = gtk_print_context_create_pango_layout (context);
+	font_des = pango_font_description_from_string ("sans 10"); /* fix me font hardcoded */
+	pango_layout_set_font_description (layout, font_des);
+
+	pango_layout_set_text (layout, string, -1);
+	get_font_size (layout, font_des, string, &text_width, &text_height);
+	/* Checking if the text width goes beyond the column width to increase the
+	 * number of lines.
+	 */
+	if (text_width > width - 4)
+		lines = (text_width / (width - 4)) + 1;
+	return 16 *lines + 8;
+}
+
+static gint
+ect_max_width (ECellView *ecell_view,
+               gint model_col,
+               gint view_col)
+{
+	/* New ECellText */
+	ECellTextView *text_view = (ECellTextView *) ecell_view;
+	gint row;
+	gint number_of_rows;
+	gint max_width = 0;
+
+	number_of_rows = e_table_model_row_count (ecell_view->e_table_model);
+
+	for (row = 0; row < number_of_rows; row++) {
+		PangoLayout *layout = generate_layout (text_view, model_col, view_col, row, 0);
+		gint width;
+
+		pango_layout_get_pixel_size (layout, &width, NULL);
+
+		max_width = MAX (max_width, width);
+		g_object_unref (layout);
+	}
+
+	return max_width + 8;
+}
+
+static gint
+ect_max_width_by_row (ECellView *ecell_view,
+                      gint model_col,
+                      gint view_col,
+                      gint row)
+{
+	/* New ECellText */
+	ECellTextView *text_view = (ECellTextView *) ecell_view;
+	gint width;
+	PangoLayout *layout;
+
+	if (row >= e_table_model_row_count (ecell_view->e_table_model))
+		return 0;
+
+	layout = generate_layout (text_view, model_col, view_col, row, 0);
+	pango_layout_get_pixel_size (layout, &width, NULL);
+	g_object_unref (layout);
+
+	return width + 8;
+}
+
+static void
+ect_finalize (GObject *object)
+{
+	ECellText *ect = E_CELL_TEXT (object);
+
+	g_free (ect->font_name);
+
+	G_OBJECT_CLASS (e_cell_text_parent_class)->finalize (object);
+}
+
+/* Set_arg handler for the text item */
+static void
+ect_set_property (GObject *object,
+                  guint property_id,
+                  const GValue *value,
+                  GParamSpec *pspec)
+{
+	ECellText *text;
+
+	text = E_CELL_TEXT (object);
+
+	switch (property_id) {
+	case PROP_STRIKEOUT_COLUMN:
+		text->strikeout_column = g_value_get_int (value);
+		break;
+
+	case PROP_UNDERLINE_COLUMN:
+		text->underline_column = g_value_get_int (value);
+		break;
+
+	case PROP_BOLD_COLUMN:
+		text->bold_column = g_value_get_int (value);
+		break;
+
+	case PROP_COLOR_COLUMN:
+		text->color_column = g_value_get_int (value);
+		break;
+
+	case PROP_EDITABLE:
+		text->editable = g_value_get_boolean (value);
+		break;
+
+	case PROP_BG_COLOR_COLUMN:
+		text->bg_color_column = g_value_get_int (value);
+		break;
+
+	default:
+		return;
+	}
+}
+
+/* Get_arg handler for the text item */
+static void
+ect_get_property (GObject *object,
+                  guint property_id,
+                  GValue *value,
+                  GParamSpec *pspec)
+{
+	ECellText *text;
+
+	text = E_CELL_TEXT (object);
+
+	switch (property_id) {
+	case PROP_STRIKEOUT_COLUMN:
+		g_value_set_int (value, text->strikeout_column);
+		break;
+
+	case PROP_UNDERLINE_COLUMN:
+		g_value_set_int (value, text->underline_column);
+		break;
+
+	case PROP_BOLD_COLUMN:
+		g_value_set_int (value, text->bold_column);
+		break;
+
+	case PROP_COLOR_COLUMN:
+		g_value_set_int (value, text->color_column);
+		break;
+
+	case PROP_EDITABLE:
+		g_value_set_boolean (value, text->editable);
+		break;
+
+	case PROP_BG_COLOR_COLUMN:
+		g_value_set_int (value, text->bg_color_column);
+		break;
+
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+		break;
+	}
+}
+
+static gchar *ellipsis_default = NULL;
+static gboolean use_ellipsis_default = TRUE;
+
+static void
+e_cell_text_class_init (ECellTextClass *class)
+{
+	ECellClass *ecc = E_CELL_CLASS (class);
+	GObjectClass *object_class = G_OBJECT_CLASS (class);
+	const gchar *ellipsis_env;
+
+	object_class->finalize = ect_finalize;
+
+	ecc->new_view   = ect_new_view;
+	ecc->kill_view  = ect_kill_view;
+	ecc->realize    = ect_realize;
+	ecc->unrealize  = ect_unrealize;
+	ecc->draw       = ect_draw;
+	ecc->event      = ect_event;
+	ecc->height     = ect_height;
+	ecc->enter_edit = ect_enter_edit;
+	ecc->leave_edit = ect_leave_edit;
+	ecc->save_state = ect_save_state;
+	ecc->load_state = ect_load_state;
+	ecc->free_state = ect_free_state;
+	ecc->print      = ect_print;
+	ecc->print_height = ect_print_height;
+	ecc->max_width = ect_max_width;
+	ecc->max_width_by_row = ect_max_width_by_row;
+	ecc->get_bg_color = ect_get_bg_color;
+
+	class->get_text = ect_real_get_text;
+	class->free_text = ect_real_free_text;
+	class->set_value = ect_real_set_value;
+
+	object_class->get_property = ect_get_property;
+	object_class->set_property = ect_set_property;
+
+	signals[TEXT_INSERTED] = g_signal_new (
+		"text_inserted",
+		G_TYPE_FROM_CLASS (object_class),
+		G_SIGNAL_RUN_FIRST,
+		G_STRUCT_OFFSET (ECellTextClass, text_inserted),
+		NULL, NULL,
+		e_marshal_VOID__POINTER_INT_INT_INT_INT,
+		G_TYPE_NONE, 5,
+		G_TYPE_POINTER,
+		G_TYPE_INT,
+		G_TYPE_INT,
+		G_TYPE_INT,
+		G_TYPE_INT);
+
+	signals[TEXT_DELETED] = g_signal_new (
+		"text_deleted",
+		G_TYPE_FROM_CLASS (object_class),
+		G_SIGNAL_RUN_FIRST,
+		G_STRUCT_OFFSET (ECellTextClass, text_deleted),
+		NULL, NULL,
+		e_marshal_VOID__POINTER_INT_INT_INT_INT,
+		G_TYPE_NONE, 5,
+		G_TYPE_POINTER,
+		G_TYPE_INT,
+		G_TYPE_INT,
+		G_TYPE_INT,
+		G_TYPE_INT);
+
+	g_object_class_install_property (
+		object_class,
+		PROP_STRIKEOUT_COLUMN,
+		g_param_spec_int (
+			"strikeout_column",
+			"Strikeout Column",
+			NULL,
+			-1, G_MAXINT, -1,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_UNDERLINE_COLUMN,
+		g_param_spec_int (
+			"underline_column",
+			"Underline Column",
+			NULL,
+			-1, G_MAXINT, -1,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_BOLD_COLUMN,
+		g_param_spec_int (
+			"bold_column",
+			"Bold Column",
+			NULL,
+			-1, G_MAXINT, -1,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_COLOR_COLUMN,
+		g_param_spec_int (
+			"color_column",
+			"Color Column",
+			NULL,
+			-1, G_MAXINT, -1,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_EDITABLE,
+		g_param_spec_boolean (
+			"editable",
+			"Editable",
+			NULL,
+			FALSE,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_BG_COLOR_COLUMN,
+		g_param_spec_int (
+			"bg_color_column",
+			"BG Color Column",
+			NULL,
+			-1, G_MAXINT, -1,
+			G_PARAM_READWRITE));
+
+	if (!clipboard_atom)
+		clipboard_atom = gdk_atom_intern ("CLIPBOARD", FALSE);
+
+	ellipsis_env = g_getenv ("GAL_ELLIPSIS");
+	if (ellipsis_env) {
+		if (*ellipsis_env) {
+			ellipsis_default = g_strdup (ellipsis_env);
+		} else {
+			use_ellipsis_default = FALSE;
+		}
+	}
+}
+
+/* IM Context Callbacks */
+
+static void
+e_cell_text_get_cursor_locations (ECellTextView *tv,
+                                  GdkRectangle *strong_pos,
+                                  GdkRectangle *weak_pos)
+{
+	GdkRectangle area;
+	CellEdit *edit = tv->edit;
+	ECellView *cell_view = (ECellView *) tv;
+	ETableItem *item = E_TABLE_ITEM ((cell_view)->e_table_item_view);
+	GnomeCanvasItem *parent_item = GNOME_CANVAS_ITEM (item)->parent;
+	PangoRectangle pango_strong_pos;
+	PangoRectangle pango_weak_pos;
+	gint x, y, col, row;
+	gdouble x1,y1;
+	gint cx, cy;
+	gint index;
+
+	row = edit->row;
+	col = edit->view_col;
+
+	e_table_item_get_cell_geometry (
+		item, &row, &col, &x, &y, NULL, &area.height);
+
+	gnome_canvas_item_get_bounds (GNOME_CANVAS_ITEM (parent_item), &x1, &y1, NULL, NULL);
+
+	gnome_canvas_get_scroll_offsets (GNOME_CANVAS (GNOME_CANVAS_ITEM (parent_item)->canvas), &cx, &cy);
+
+	index = edit->selection_end + edit->preedit_pos;
+
+	pango_layout_get_cursor_pos (
+		edit->layout,
+		index,
+		strong_pos ? &pango_strong_pos : NULL,
+		weak_pos ? &pango_weak_pos : NULL);
+
+	if (strong_pos) {
+		strong_pos->x = x + x1 - cx - edit->xofs_edit + pango_strong_pos.x / PANGO_SCALE;
+		strong_pos->y = y + y1 - cy - edit->yofs_edit + pango_strong_pos.y / PANGO_SCALE;
+		strong_pos->width = 0;
+		strong_pos->height = pango_strong_pos.height / PANGO_SCALE;
+	}
+
+	if (weak_pos) {
+		weak_pos->x = x + x1 - cx - edit->xofs_edit + pango_weak_pos.x / PANGO_SCALE;
+		weak_pos->y = y + y1 - cy - edit->yofs_edit + pango_weak_pos.y / PANGO_SCALE;
+		weak_pos->width = 0;
+		weak_pos->height = pango_weak_pos.height / PANGO_SCALE;
+	}
+}
+
+static void
+update_im_cursor_location (ECellTextView *tv)
+{
+	CellEdit *edit = tv->edit;
+	GdkRectangle area;
+
+	e_cell_text_get_cursor_locations (tv, &area, NULL);
+
+	gtk_im_context_set_cursor_location (edit->im_context, &area);
+}
+
+static void
+e_cell_text_preedit_changed_cb (GtkIMContext *context,
+                                ECellTextView *tv)
+{
+	gchar *preedit_string;
+	gint cursor_pos;
+	CellEdit *edit = tv->edit;
+	gtk_im_context_get_preedit_string (
+		edit->im_context, &preedit_string,
+		NULL, &cursor_pos);
+
+	edit->preedit_length = strlen (preedit_string);
+	cursor_pos = CLAMP (cursor_pos, 0, g_utf8_strlen (preedit_string, -1));
+	edit->preedit_pos = g_utf8_offset_to_pointer (preedit_string, cursor_pos) - preedit_string;
+	g_free (preedit_string);
+
+	ect_queue_redraw (tv, edit->view_col, edit->row);
+}
+
+static void
+e_cell_text_commit_cb (GtkIMContext *context,
+                       const gchar *str,
+                       ECellTextView *tv)
+{
+	CellEdit *edit = tv->edit;
+	ETextEventProcessorCommand command = { 0 };
+
+	if (g_utf8_validate (str, strlen (str), NULL)) {
+		command.action = E_TEP_INSERT;
+		command.position = E_TEP_SELECTION;
+		command.string = (gchar *) str;
+		command.value = strlen (str);
+		e_cell_text_view_command (edit->tep, &command, edit);
+	}
+
+}
+
+static gboolean
+e_cell_text_retrieve_surrounding_cb (GtkIMContext *context,
+                                     ECellTextView *tv)
+{
+	CellEdit *edit = tv->edit;
+
+	gtk_im_context_set_surrounding (
+		context,
+		edit->text,
+		strlen (edit->text),
+		MIN (edit->selection_start, edit->selection_end));
+
+	return TRUE;
+}
+
+static gboolean
+e_cell_text_delete_surrounding_cb (GtkIMContext *context,
+                                   gint offset,
+                                   gint n_chars,
+                                   ECellTextView *tv)
+{
+	gint begin_pos, end_pos;
+	glong text_len;
+	CellEdit *edit = tv->edit;
+
+	text_len = g_utf8_strlen (edit->text, -1);
+	begin_pos = g_utf8_pointer_to_offset (
+		edit->text,
+		edit->text + MIN (edit->selection_start, edit->selection_end));
+	begin_pos += offset;
+	end_pos = begin_pos + n_chars;
+	if (begin_pos < 0 || text_len < begin_pos)
+		return FALSE;
+	if (end_pos > text_len)
+		end_pos = text_len;
+	edit->selection_start = g_utf8_offset_to_pointer (edit->text, begin_pos)
+				- edit->text;
+	edit->selection_end = g_utf8_offset_to_pointer (edit->text, end_pos)
+			      - edit->text;
+
+	_delete_selection (tv);
+
+	return TRUE;
+}
+
+static void
+e_cell_text_init (ECellText *ect)
+{
+	ect->ellipsis = g_strdup (ellipsis_default);
+	ect->use_ellipsis = use_ellipsis_default;
+	ect->strikeout_column = -1;
+	ect->underline_column = -1;
+	ect->bold_column = -1;
+	ect->color_column = -1;
+	ect->bg_color_column = -1;
+	ect->editable = TRUE;
+}
+
+/**
+ * e_cell_text_new:
+ * @fontname: this param is no longer used, but left here for api stability
+ * @justify: Justification of the string in the cell.
+ *
+ * Creates a new ECell renderer that can be used to render strings that
+ * that come from the model.  The value returned from the model is
+ * interpreted as being a gchar *.
+ *
+ * The ECellText object support a large set of properties that can be
+ * configured through the Gtk argument system and allows the user to have
+ * a finer control of the way the string is displayed.  The arguments supported
+ * allow the control of strikeout, underline, bold, and color.
+ *
+ * The arguments "strikeout_column", "underline_column", "bold_column"
+ * and "color_column" set and return an integer that points to a
+ * column in the model that controls these settings.  So controlling
+ * the way things are rendered is achieved by having special columns
+ * in the model that will be used to flag whether the text should be
+ * rendered with strikeout, or bolded.  In the case of the
+ * "color_column" argument, the column in the model is expected to
+ * have a string that can be parsed by gdk_color_parse().
+ *
+ * Returns: an ECell object that can be used to render strings.
+ */
+ECell *
+e_cell_text_new (const gchar *fontname,
+                 GtkJustification justify)
+{
+	ECellText *ect = g_object_new (E_TYPE_CELL_TEXT, NULL);
+
+	e_cell_text_construct (ect, fontname, justify);
+
+	return (ECell *) ect;
+}
+
+/**
+ * e_cell_text_construct:
+ * @cell: The cell to construct
+ * @fontname: this param is no longer used, but left here for api stability
+ * @justify: Justification of the string in the cell
+ *
+ * constructs the ECellText.  To be used by subclasses and language
+ * bindings.
+ *
+ * Returns: The ECellText.
+ */
+ECell *
+e_cell_text_construct (ECellText *cell,
+                       const gchar *fontname,
+                       GtkJustification justify)
+{
+	if (!cell)
+		return E_CELL (NULL);
+	if (fontname)
+		cell->font_name = g_strdup (fontname);
+	cell->justify = justify;
+	return E_CELL (cell);
+}
+
+gchar *
+e_cell_text_get_text (ECellText *cell,
+                      ETableModel *model,
+                      gint col,
+                      gint row)
+{
+	ECellTextClass *class;
+
+	g_return_val_if_fail (E_IS_CELL_TEXT (cell), NULL);
+
+	class = E_CELL_TEXT_GET_CLASS (cell);
+	if (class->get_text == NULL)
+		return NULL;
+
+	return class->get_text (cell, model, col, row);
+}
+
+void
+e_cell_text_free_text (ECellText *cell,
+                       gchar *text)
+{
+	ECellTextClass *class;
+
+	g_return_if_fail (E_IS_CELL_TEXT (cell));
+
+	class = E_CELL_TEXT_GET_CLASS (cell);
+	if (class->free_text == NULL)
+		return;
+
+	class->free_text (cell, text);
+}
+
+void
+e_cell_text_set_value (ECellText *cell,
+                       ETableModel *model,
+                       gint col,
+                       gint row,
+                       const gchar *text)
+{
+	ECellTextClass *class;
+
+	g_return_if_fail (E_IS_CELL_TEXT (cell));
+
+	class = E_CELL_TEXT_GET_CLASS (cell);
+	if (class->set_value == NULL)
+		return;
+
+	class->set_value (cell, model, col, row, text);
+}
+
+/* fixme: Handle Font attributes */
+/* position is in BYTES */
+
+static gint
+get_position_from_xy (CellEdit *edit,
+                      gint x,
+                      gint y)
+{
+	gint index;
+	gint trailing;
+	const gchar *text;
+
+	PangoLayout *layout = generate_layout (edit->text_view, edit->model_col, edit->view_col, edit->row, edit->cell_width);
+	ECellTextView *text_view = edit->text_view;
+	ECellText *ect = (ECellText *) ((ECellView *) text_view)->ecell;
+
+	x -= (ect->x + text_view->xofs - edit->xofs_edit);
+	y -= (ect->y + text_view->yofs - edit->yofs_edit);
+
+	pango_layout_xy_to_index (layout, x * PANGO_SCALE, y * PANGO_SCALE, &index, &trailing);
+
+	text = pango_layout_get_text (layout);
+
+	return g_utf8_offset_to_pointer (text + index, trailing) - text;
+}
+
+#define SCROLL_WAIT_TIME 30000
+
+static gboolean
+_blink_scroll_timeout (gpointer data)
+{
+	ECellTextView *text_view = (ECellTextView *) data;
+	ECellText *ect = E_CELL_TEXT (((ECellView *) text_view)->ecell);
+	CellEdit *edit = text_view->edit;
+
+	gulong current_time;
+	gboolean scroll = FALSE;
+	gboolean redraw = FALSE;
+	gint width, height;
+
+	g_timer_elapsed (edit->timer, &current_time);
+
+	if (edit->scroll_start + SCROLL_WAIT_TIME > 1000000) {
+		if (current_time > edit->scroll_start - (1000000 - SCROLL_WAIT_TIME) &&
+		    current_time < edit->scroll_start)
+			scroll = TRUE;
+	} else {
+		if (current_time > edit->scroll_start + SCROLL_WAIT_TIME ||
+		    current_time < edit->scroll_start)
+			scroll = TRUE;
+	}
+
+	pango_layout_get_pixel_size (edit->layout, &width, &height);
+
+	if (scroll && edit->button_down) {
+		/* FIXME: Copy this for y. */
+		if (edit->lastx - ect->x > edit->cell_width) {
+			if (edit->xofs_edit < width - edit->cell_width) {
+				edit->xofs_edit += 4;
+				if (edit->xofs_edit > width - edit->cell_width + 1)
+					edit->xofs_edit = width - edit->cell_width + 1;
+				redraw = TRUE;
+			}
+		}
+		if (edit->lastx - ect->x < 0 &&
+		    edit->xofs_edit > 0) {
+			edit->xofs_edit -= 4;
+			if (edit->xofs_edit < 0)
+				edit->xofs_edit = 0;
+			redraw = TRUE;
+		}
+		if (redraw) {
+			ETextEventProcessorEvent e_tep_event;
+			e_tep_event.type = GDK_MOTION_NOTIFY;
+			e_tep_event.motion.state = edit->last_state;
+			e_tep_event.motion.time = 0;
+			e_tep_event.motion.position = get_position_from_xy (edit, edit->lastx, edit->lasty);
+			_get_tep (edit);
+			e_text_event_processor_handle_event (
+				edit->tep,
+				&e_tep_event);
+			edit->scroll_start = current_time;
+		}
+	}
+
+	if (!((current_time / 500000) % 2)) {
+		if (!edit->show_cursor)
+			redraw = TRUE;
+		edit->show_cursor = TRUE;
+	} else {
+		if (edit->show_cursor)
+			redraw = TRUE;
+		edit->show_cursor = FALSE;
+	}
+	if (redraw) {
+		ect_queue_redraw (text_view, edit->view_col, edit->row);
+	}
+	return TRUE;
+}
+
+static gint
+next_word (CellEdit *edit,
+           gint start)
+{
+	gchar *p;
+	gint length;
+
+	length = strlen (edit->text);
+	if (start >= length)
+		return length;
+
+	p = g_utf8_next_char (edit->text + start);
+
+	while (*p && g_unichar_validate (g_utf8_get_char (p))) {
+		gunichar unival = g_utf8_get_char (p);
+		if (g_unichar_isspace (unival))
+			return p - edit->text;
+		p = g_utf8_next_char (p);
+	}
+
+	return p - edit->text;
+}
+
+static gint
+_get_position (ECellTextView *text_view,
+               ETextEventProcessorCommand *command)
+{
+	gint length;
+	CellEdit *edit = text_view->edit;
+	gchar *p;
+	gint unival;
+	gint index;
+	gint trailing;
+
+	switch (command->position) {
+
+	case E_TEP_VALUE:
+		return command->value;
+
+	case E_TEP_SELECTION:
+		return edit->selection_end;
+
+	case E_TEP_START_OF_BUFFER:
+		return 0;
+
+		/* fixme: this probably confuses TEP */
+
+	case E_TEP_END_OF_BUFFER:
+		return strlen (edit->text);
+
+	case E_TEP_START_OF_LINE:
+
+		if (edit->selection_end < 1) return 0;
+
+		p = g_utf8_find_prev_char (edit->text, edit->text + edit->selection_end);
+
+		if (p == edit->text) return 0;
+
+		p = g_utf8_find_prev_char (edit->text, p);
+
+		while (p && p > edit->text) {
+			if (*p == '\n') return p - edit->text + 1;
+			p = g_utf8_find_prev_char (edit->text, p);
+		}
+
+		return 0;
+
+	case E_TEP_END_OF_LINE:
+
+		length = strlen (edit->text);
+		if (edit->selection_end >= length) return length;
+
+		p = g_utf8_next_char (edit->text + edit->selection_end);
+
+		while (*p && g_unichar_validate (g_utf8_get_char (p))) {
+			if (*p == '\n') return p - edit->text;
+			p = g_utf8_next_char (p);
+		}
+
+		return p - edit->text;
+
+	case E_TEP_FORWARD_CHARACTER:
+
+		length = strlen (edit->text);
+		if (edit->selection_end >= length) return length;
+
+		p = g_utf8_next_char (edit->text + edit->selection_end);
+
+		return p - edit->text;
+
+	case E_TEP_BACKWARD_CHARACTER:
+
+		if (edit->selection_end < 1) return 0;
+
+		p = g_utf8_find_prev_char (edit->text, edit->text + edit->selection_end);
+
+		if (p == NULL) return 0;
+
+		return p - edit->text;
+
+	case E_TEP_FORWARD_WORD:
+		return next_word (edit, edit->selection_end);
+
+	case E_TEP_BACKWARD_WORD:
+
+		if (edit->selection_end < 1) return 0;
+
+		p = g_utf8_find_prev_char (edit->text, edit->text + edit->selection_end);
+
+		if (p == edit->text) return 0;
+
+		p = g_utf8_find_prev_char (edit->text, p);
+
+		while (p && p > edit->text && g_unichar_validate (g_utf8_get_char (p))) {
+			unival = g_utf8_get_char (p);
+			if (g_unichar_isspace (unival)) {
+				return (g_utf8_next_char (p) - edit->text);
+			}
+			p = g_utf8_find_prev_char (edit->text, p);
+		}
+
+		return 0;
+
+	case E_TEP_FORWARD_LINE:
+		pango_layout_move_cursor_visually (
+			edit->layout,
+			TRUE,
+			edit->selection_end,
+			0,
+			TRUE,
+			&index,
+			&trailing);
+		index = g_utf8_offset_to_pointer (edit->text + index, trailing) - edit->text;
+		if (index < 0)
+			return 0;
+		length = strlen (edit->text);
+		if (index >= length)
+			return length;
+		return index;
+	case E_TEP_BACKWARD_LINE:
+		pango_layout_move_cursor_visually (
+			edit->layout,
+			TRUE,
+			edit->selection_end,
+			0,
+			TRUE,
+			&index,
+			&trailing);
+
+		index = g_utf8_offset_to_pointer (edit->text + index, trailing) - edit->text;
+		if (index < 0)
+			return 0;
+		length = strlen (edit->text);
+		if (index >= length)
+			return length;
+		return index;
+	case E_TEP_FORWARD_PARAGRAPH:
+	case E_TEP_BACKWARD_PARAGRAPH:
+
+	case E_TEP_FORWARD_PAGE:
+	case E_TEP_BACKWARD_PAGE:
+		return edit->selection_end;
+	default:
+		return edit->selection_end;
+	}
+
+	g_return_val_if_reached (0);
+
+	return 0; /* Kill warning */
+}
+
+static void
+_delete_selection (ECellTextView *text_view)
+{
+	CellEdit *edit = text_view->edit;
+	gint length;
+	gchar *sp, *ep;
+
+	if (edit->selection_end == edit->selection_start) return;
+
+	if (edit->selection_end < edit->selection_start) {
+		edit->selection_end ^= edit->selection_start;
+		edit->selection_start ^= edit->selection_end;
+		edit->selection_end ^= edit->selection_start;
+	}
+
+	sp = edit->text + edit->selection_start;
+	ep = edit->text + edit->selection_end;
+	length = strlen (ep) + 1;
+
+	memmove (sp, ep, length);
+
+	edit->selection_end = edit->selection_start;
+
+	g_signal_emit (VIEW_TO_CELL (text_view), signals[TEXT_DELETED], 0, text_view, edit->selection_start, ep - sp, edit->row, edit->model_col);
+}
+
+/* fixme: */
+/* NB! We expect value to be length IN BYTES */
+
+static void
+_insert (ECellTextView *text_view,
+         const gchar *string,
+         gint value)
+{
+	CellEdit *edit = text_view->edit;
+	gchar *temp;
+
+	if (value <= 0) return;
+
+	edit->selection_start = MIN (strlen (edit->text), edit->selection_start);
+
+	temp = g_new (gchar, strlen (edit->text) + value + 1);
+
+	strncpy (temp, edit->text, edit->selection_start);
+	strncpy (temp + edit->selection_start, string, value);
+	strcpy (temp + edit->selection_start + value, edit->text + edit->selection_end);
+
+	g_free (edit->text);
+
+	edit->text = temp;
+
+	edit->selection_start += value;
+	edit->selection_end = edit->selection_start;
+
+	g_signal_emit (VIEW_TO_CELL (text_view), signals[TEXT_INSERTED], 0, text_view, edit->selection_end - value, value, edit->row, edit->model_col);
+}
+
+static void
+capitalize (CellEdit *edit,
+            gint start,
+            gint end,
+            ETextEventProcessorCaps type)
+{
+	ECellTextView *text_view = edit->text_view;
+
+	gboolean first = TRUE;
+	gint character_length = g_utf8_strlen (edit->text + start, start - end);
+	const gchar *p = edit->text + start;
+	const gchar *text_end = edit->text + end;
+	gchar *new_text = g_new0 (char, character_length * 6 + 1);
+	gchar *output = new_text;
+
+	while (p && *p && p < text_end && g_unichar_validate (g_utf8_get_char (p))) {
+		gunichar unival = g_utf8_get_char (p);
+		gunichar newval = unival;
+
+		switch (type) {
+		case E_TEP_CAPS_UPPER:
+			newval = g_unichar_toupper (unival);
+			break;
+		case E_TEP_CAPS_LOWER:
+			newval = g_unichar_tolower (unival);
+			break;
+		case E_TEP_CAPS_TITLE:
+			if (g_unichar_isalpha (unival)) {
+				if (first)
+					newval = g_unichar_totitle (unival);
+				else
+					newval = g_unichar_tolower (unival);
+				first = FALSE;
+			} else {
+				first = TRUE;
+			}
+			break;
+		}
+		g_unichar_to_utf8 (newval, output);
+		output = g_utf8_next_char (output);
+
+		p = g_utf8_next_char (p);
+	}
+	*output = 0;
+
+	edit->selection_end = end;
+	edit->selection_start = start;
+	_delete_selection (text_view);
+
+	_insert (text_view, new_text, output - new_text);
+
+	g_free (new_text);
+}
+
+static void
+e_cell_text_view_command (ETextEventProcessor *tep,
+                          ETextEventProcessorCommand *command,
+                          gpointer data)
+{
+	CellEdit *edit = (CellEdit *) data;
+	ECellTextView *text_view = edit->text_view;
+	ECellText *ect = E_CELL_TEXT (text_view->cell_view.ecell);
+
+	gboolean change = FALSE;
+	gboolean redraw = FALSE;
+
+	gint sel_start, sel_end;
+
+	/* If the EText isn't editable, then ignore any commands that would
+	 * modify the text. */
+	if (!ect->editable && (command->action == E_TEP_DELETE
+			       || command->action == E_TEP_INSERT
+			       || command->action == E_TEP_PASTE
+			       || command->action == E_TEP_GET_SELECTION))
+		return;
+
+	switch (command->action) {
+	case E_TEP_MOVE:
+		edit->selection_start = _get_position (text_view, command);
+		edit->selection_end = edit->selection_start;
+		if (edit->timer) {
+			g_timer_reset (edit->timer);
+		}
+		redraw = TRUE;
+		break;
+	case E_TEP_SELECT:
+		edit->selection_end = _get_position (text_view, command);
+		sel_start = MIN (edit->selection_start, edit->selection_end);
+		sel_end = MAX (edit->selection_start, edit->selection_end);
+		if (sel_start != sel_end) {
+			e_cell_text_view_supply_selection (
+				edit, command->time, GDK_SELECTION_PRIMARY,
+				edit->text + sel_start,
+				sel_end - sel_start);
+		} else if (edit->timer) {
+			g_timer_reset (edit->timer);
+		}
+		redraw = TRUE;
+		break;
+	case E_TEP_DELETE:
+		if (edit->selection_end == edit->selection_start) {
+			edit->selection_end = _get_position (text_view, command);
+		}
+		_delete_selection (text_view);
+		if (edit->timer) {
+			g_timer_reset (edit->timer);
+		}
+		redraw = TRUE;
+		change = TRUE;
+		break;
+
+	case E_TEP_INSERT:
+		if (!edit->preedit_length && edit->selection_end != edit->selection_start) {
+			_delete_selection (text_view);
+		}
+		_insert (text_view, command->string, command->value);
+		if (edit->timer) {
+			g_timer_reset (edit->timer);
+		}
+		redraw = TRUE;
+		change = TRUE;
+		break;
+	case E_TEP_COPY:
+		sel_start = MIN (edit->selection_start, edit->selection_end);
+		sel_end = MAX (edit->selection_start, edit->selection_end);
+		if (sel_start != sel_end) {
+			e_cell_text_view_supply_selection (
+				edit, command->time, clipboard_atom,
+				edit->text + sel_start,
+				sel_end - sel_start);
+		}
+		if (edit->timer) {
+			g_timer_reset (edit->timer);
+		}
+		break;
+	case E_TEP_PASTE:
+		e_cell_text_view_get_selection (edit, clipboard_atom, command->time);
+		if (edit->timer) {
+			g_timer_reset (edit->timer);
+		}
+		redraw = TRUE;
+		change = TRUE;
+		break;
+	case E_TEP_GET_SELECTION:
+		e_cell_text_view_get_selection (edit, GDK_SELECTION_PRIMARY, command->time);
+		break;
+	case E_TEP_ACTIVATE:
+		e_table_item_leave_edit_ (text_view->cell_view.e_table_item_view);
+		break;
+	case E_TEP_SET_SELECT_BY_WORD:
+		edit->select_by_word = command->value;
+		break;
+	case E_TEP_GRAB:
+		edit->actions = E_CELL_GRAB;
+		break;
+	case E_TEP_UNGRAB:
+		edit->actions = E_CELL_UNGRAB;
+		break;
+	case E_TEP_CAPS:
+		if (edit->selection_start == edit->selection_end) {
+			capitalize (edit, edit->selection_start, next_word (edit, edit->selection_start), command->value);
+		} else {
+			gint selection_start = MIN (edit->selection_start, edit->selection_end);
+			gint selection_end = edit->selection_start + edit->selection_end - selection_start; /* Slightly faster than MAX */
+			capitalize (edit, selection_start, selection_end, command->value);
+		}
+		if (edit->timer) {
+			g_timer_reset (edit->timer);
+		}
+		redraw = TRUE;
+		change = TRUE;
+		break;
+	case E_TEP_NOP:
+		break;
+	}
+
+	if (change) {
+		if (edit->layout)
+			g_object_unref (edit->layout);
+		edit->layout = build_layout (text_view, edit->row, edit->text, edit->cell_width);
+	}
+
+	if (!edit->button_down) {
+		PangoRectangle strong_pos, weak_pos;
+		pango_layout_get_cursor_pos (edit->layout, edit->selection_end, &strong_pos, &weak_pos);
+		if (strong_pos.x != weak_pos.x ||
+		    strong_pos.y != weak_pos.y ||
+		    strong_pos.width != weak_pos.width ||
+		    strong_pos.height != weak_pos.height) {
+			if (show_pango_rectangle (edit, weak_pos))
+				redraw = TRUE;
+		}
+		if (show_pango_rectangle (edit, strong_pos)) {
+			redraw = TRUE;
+		}
+	}
+
+	if (redraw) {
+		ect_queue_redraw (text_view, edit->view_col, edit->row);
+	}
+}
+
+static void
+e_cell_text_view_supply_selection (CellEdit *edit,
+                                   guint time,
+                                   GdkAtom selection,
+                                   gchar *data,
+                                   gint length)
+{
+#if DO_SELECTION
+	GtkClipboard *clipboard;
+
+	clipboard = gtk_widget_get_clipboard (GTK_WIDGET (edit->text_view->canvas), selection);
+
+	if (selection == GDK_SELECTION_PRIMARY) {
+		edit->has_selection = TRUE;
+	}
+
+	gtk_clipboard_set_text (clipboard, data, length);
+#endif
+}
+
+#ifdef DO_SELECTION
+static void
+paste_received (GtkClipboard *clipboard,
+                const gchar *text,
+                gpointer data)
+{
+	CellEdit *edit;
+
+	g_return_if_fail (data);
+
+	edit = (CellEdit *) data;
+
+	if (text && g_utf8_validate (text, strlen (text), NULL)) {
+		ETextEventProcessorCommand command = { 0 };
+		command.action = E_TEP_INSERT;
+		command.position = E_TEP_SELECTION;
+		command.string = (gchar *) text;
+		command.value = strlen (text);
+		command.time = GDK_CURRENT_TIME;
+		e_cell_text_view_command (edit->tep, &command, edit);
+	}
+}
+#endif
+
+static void
+e_cell_text_view_get_selection (CellEdit *edit,
+                                GdkAtom selection,
+                                guint32 time)
+{
+#if DO_SELECTION
+	gtk_clipboard_request_text (
+		gtk_widget_get_clipboard (GTK_WIDGET (edit->text_view->canvas),
+		selection),
+		paste_received, edit);
+#endif
+}
+
+static void
+_get_tep (CellEdit *edit)
+{
+	if (!edit->tep) {
+		edit->tep = e_text_event_processor_emacs_like_new ();
+		g_signal_connect (
+			edit->tep, "command",
+			G_CALLBACK (e_cell_text_view_command), edit);
+	}
+}
+
+/**
+ * e_cell_text_set_selection:
+ * @cell_view: the given cell view
+ * @col: column of the given cell in the view
+ * @row: row of the given cell in the view
+ * @start: start offset of the selection
+ * @end: end offset of the selection
+ *
+ * Sets the selection of given text cell.
+ * If the current editing cell is not the given cell, this function
+ * will return FALSE;
+ *
+ * If success, the [start, end) part of the text will be selected.
+ *
+ * This API is most likely to be used by a11y implementations.
+ *
+ * Returns: whether the action is successful.
+ */
+gboolean
+e_cell_text_set_selection (ECellView *cell_view,
+                           gint col,
+                           gint row,
+                           gint start,
+                           gint end)
+{
+	ECellTextView *ectv;
+	CellEdit *edit;
+	ETextEventProcessorCommand command1 = { 0 }, command2 = { 0 };
+
+	g_return_val_if_fail (cell_view != NULL, FALSE);
+
+	ectv = (ECellTextView *) cell_view;
+	edit = ectv->edit;
+	if (!edit)
+		return FALSE;
+
+	if (edit->view_col != col || edit->row != row)
+		return FALSE;
+
+	command1.action = E_TEP_MOVE;
+	command1.position = E_TEP_VALUE;
+	command1.value = start;
+	e_cell_text_view_command (edit->tep, &command1, edit);
+
+	command2.action = E_TEP_SELECT;
+	command2.position = E_TEP_VALUE;
+	command2.value = end;
+	e_cell_text_view_command (edit->tep, &command2, edit);
+
+	return TRUE;
+}
+
+/**
+ * e_cell_text_get_selection:
+ * @cell_view: the given cell view
+ * @col: column of the given cell in the view
+ * @row: row of the given cell in the view
+ * @start: a pointer to an gint value indicates the start offset of the selection
+ * @end: a pointer to an gint value indicates the end offset of the selection
+ *
+ * Gets the selection of given text cell.
+ * If the current editing cell is not the given cell, this function
+ * will return FALSE;
+ *
+ * This API is most likely to be used by a11y implementations.
+ *
+ * Returns: whether the action is successful.
+ */
+gboolean
+e_cell_text_get_selection (ECellView *cell_view,
+                           gint col,
+                           gint row,
+                           gint *start,
+                           gint *end)
+{
+	ECellTextView *ectv;
+	CellEdit *edit;
+
+	g_return_val_if_fail (cell_view != NULL, FALSE);
+
+	ectv = (ECellTextView *) cell_view;
+	edit = ectv->edit;
+	if (!edit)
+		return FALSE;
+
+	if (edit->view_col != col || edit->row != row)
+		return FALSE;
+
+	if (start)
+		*start = edit->selection_start;
+	if (end)
+		*end = edit->selection_end;
+	return TRUE;
+}
+
+/**
+ * e_cell_text_copy_clipboard:
+ * @cell_view: the given cell view
+ * @col: column of the given cell in the view
+ * @row: row of the given cell in the view
+ *
+ * Copys the selected text to clipboard.
+ *
+ * This API is most likely to be used by a11y implementations.
+ */
+void
+e_cell_text_copy_clipboard (ECellView *cell_view,
+                            gint col,
+                            gint row)
+{
+	ECellTextView *ectv;
+	CellEdit *edit;
+	ETextEventProcessorCommand command = { 0 };
+
+	g_return_if_fail (cell_view != NULL);
+
+	ectv = (ECellTextView *) cell_view;
+	edit = ectv->edit;
+	if (!edit)
+		return;
+
+	if (edit->view_col != col || edit->row != row)
+		return;
+
+	command.action = E_TEP_COPY;
+	command.time = GDK_CURRENT_TIME;
+	e_cell_text_view_command (edit->tep, &command, edit);
+}
+
+/**
+ * e_cell_text_paste_clipboard:
+ * @cell_view: the given cell view
+ * @col: column of the given cell in the view
+ * @row: row of the given cell in the view
+ *
+ * Pastes the text from the clipboardt.
+ *
+ * This API is most likely to be used by a11y implementations.
+ */
+void
+e_cell_text_paste_clipboard (ECellView *cell_view,
+                             gint col,
+                             gint row)
+{
+	ECellTextView *ectv;
+	CellEdit *edit;
+	ETextEventProcessorCommand command = { 0 };
+
+	g_return_if_fail (cell_view != NULL);
+
+	ectv = (ECellTextView *) cell_view;
+	edit = ectv->edit;
+	if (!edit)
+		return;
+
+	if (edit->view_col != col || edit->row != row)
+		return;
+
+	command.action = E_TEP_PASTE;
+	command.time = GDK_CURRENT_TIME;
+	e_cell_text_view_command (edit->tep, &command, edit);
+}
+
+/**
+ * e_cell_text_delete_selection:
+ * @cell_view: the given cell view
+ * @col: column of the given cell in the view
+ * @row: row of the given cell in the view
+ *
+ * Deletes the selected text of the cell.
+ *
+ * This API is most likely to be used by a11y implementations.
+ */
+void
+e_cell_text_delete_selection (ECellView *cell_view,
+                              gint col,
+                              gint row)
+{
+	ECellTextView *ectv;
+	CellEdit *edit;
+	ETextEventProcessorCommand command = { 0 };
+
+	g_return_if_fail (cell_view != NULL);
+
+	ectv = (ECellTextView *) cell_view;
+	edit = ectv->edit;
+	if (!edit)
+		return;
+
+	if (edit->view_col != col || edit->row != row)
+		return;
+
+	command.action = E_TEP_DELETE;
+	command.position = E_TEP_SELECTION;
+	e_cell_text_view_command (edit->tep, &command, edit);
+}
+
+/**
+ * e_cell_text_get_text_by_view:
+ * @cell_view: the given cell view
+ * @col: column of the given cell in the model
+ * @row: row of the given cell in the model
+ *
+ * Get the cell's text directly from CellEdit,
+ * during editting this cell, the cell's text value maybe inconsistant
+ * with the text got from table_model.
+ * The caller should free the text after using it.
+ *
+ * This API is most likely to be used by a11y implementations.
+ */
+gchar *
+e_cell_text_get_text_by_view (ECellView *cell_view,
+                              gint col,
+                              gint row)
+{
+	ECellTextView *ectv;
+	CellEdit *edit;
+	gchar	*ret, *model_text;
+
+	g_return_val_if_fail (cell_view != NULL, NULL);
+
+	ectv = (ECellTextView *) cell_view;
+	edit = ectv->edit;
+
+	if (edit && ectv->edit->row == row && ectv->edit->model_col == col) { /* being editted now */
+		ret = g_strdup (edit->text);
+	} else{
+		model_text = e_cell_text_get_text (
+			E_CELL_TEXT (cell_view->ecell),
+			cell_view->e_table_model, col, row);
+		ret = g_strdup (model_text);
+		e_cell_text_free_text (E_CELL_TEXT (cell_view->ecell), model_text);
+	}
+
+	return ret;
+
+}
diff --git a/e-util/e-cell-text.h b/e-util/e-cell-text.h
new file mode 100644
index 0000000..740b87f
--- /dev/null
+++ b/e-util/e-cell-text.h
@@ -0,0 +1,195 @@
+/*
+ * Text cell renderer.
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Miguel de Icaza <miguel ximian com>
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * A lot of code taken from:
+ *
+ * Text item type for GnomeCanvas widget
+ *
+ * GnomeCanvas is basically a port of the Tk toolkit's most excellent
+ * canvas widget.  Tk is copyrighted by the Regents of the University
+ * of California, Sun Microsystems, and other parties.
+ *
+ * Copyright (C) 1998 The Free Software Foundation
+ *
+ * Author: Federico Mena <federico nuclecu unam mx>
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_CELL_TEXT_H
+#define E_CELL_TEXT_H
+
+#include <gtk/gtk.h>
+#include <libgnomecanvas/libgnomecanvas.h>
+
+#include <e-util/e-cell.h>
+
+/* Standard GObject macros */
+#define E_TYPE_CELL_TEXT \
+	(e_cell_text_get_type ())
+#define E_CELL_TEXT(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_CELL_TEXT, ECellText))
+#define E_CELL_TEXT_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_CELL_TEXT, ECellTextClass))
+#define E_IS_CELL_TEXT(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_CELL_TEXT))
+#define E_IS_CELL_TEXT_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_CELL_TEXT))
+#define E_CELL_TEXT_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_CELL_TEXT, ECellTextClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ECellText ECellText;
+typedef struct _ECellTextClass ECellTextClass;
+
+struct _ECellText {
+	ECell parent;
+
+	GtkJustification  justify;
+	gchar             *font_name;
+
+	gdouble x, y;			/* Position at anchor */
+
+	gulong pixel;			/* Fill color */
+
+	/* Clip handling */
+	gchar *ellipsis;                 /* The ellipsis characters.  NULL = "...". */
+
+	guint use_ellipsis : 1;         /* Whether to use the ellipsis. */
+	guint editable : 1;		/* Whether the text can be edited. */
+
+	gint strikeout_column;
+	gint underline_column;
+	gint bold_column;
+
+	/* This column in the ETable should return a string specifying a color,
+	 * either a color name like "red" or a color spec like "rgb:F/0/0".
+	 * See the XParseColor man page for the formats available. */
+	gint color_column;
+	gint bg_color_column;
+};
+
+struct _ECellTextClass {
+	ECellClass parent_class;
+
+	/* Methods */
+	gchar *		(*get_text)		(ECellText *cell,
+						 ETableModel *model,
+						 gint col,
+						 gint row);
+	void		(*free_text)		(ECellText *cell,
+						 gchar *text);
+	void		(*set_value)		(ECellText *cell,
+						 ETableModel *model,
+						 gint col,
+						 gint row,
+						 const gchar *text);
+
+	/* Signals */
+	void		(*text_inserted)	(ECellText *cell,
+						 ECellView *cell_view,
+						 gint pos,
+						 gint len,
+						 gint row,
+						 gint model_col);
+	void		(*text_deleted)		(ECellText *cell,
+						 ECellView *cell_view,
+						 gint pos,
+						 gint len,
+						 gint row,
+						 gint model_col);
+};
+
+GType		e_cell_text_get_type		(void) G_GNUC_CONST;
+ECell *		e_cell_text_new			(const gchar *fontname,
+						 GtkJustification justify);
+ECell *		e_cell_text_construct		(ECellText *cell,
+						 const gchar *fontname,
+						 GtkJustification justify);
+
+/* Gets the value from the model and converts it into a string. In ECellText
+ * itself, the value is assumed to be a gchar * and so needs no conversion.
+ * In subclasses the ETableModel value may be a more complicated datatype. */
+gchar *		e_cell_text_get_text		(ECellText *cell,
+						 ETableModel *model,
+						 gint col,
+						 gint row);
+
+/* Frees the value returned by e_cell_text_get_text(). */
+void		e_cell_text_free_text		(ECellText *cell,
+						 gchar *text);
+
+/* Sets the ETableModel value, based on the given string. */
+void		e_cell_text_set_value		(ECellText *cell,
+						 ETableModel *model,
+						 gint col,
+						 gint row,
+						 const gchar *text);
+
+/* Sets the selection of given text cell */
+gboolean	e_cell_text_set_selection	(ECellView *cell_view,
+						 gint col,
+						 gint row,
+						 gint start,
+						 gint end);
+
+/* Gets the selection of given text cell */
+gboolean	e_cell_text_get_selection	(ECellView *cell_view,
+						 gint col,
+						 gint row,
+						 gint *start,
+						 gint *end);
+
+/* Copys the selected text to the clipboard */
+void		e_cell_text_copy_clipboard	(ECellView *cell_view,
+						 gint col,
+						 gint row);
+
+/* Pastes the text from the clipboard */
+void		e_cell_text_paste_clipboard	(ECellView *cell_view,
+						 gint col,
+						 gint row);
+
+/* Deletes selected text */
+void		e_cell_text_delete_selection	(ECellView *cell_view,
+						 gint col,
+						 gint row);
+
+/* get text directly from view, both col and row are model format */
+gchar *		e_cell_text_get_text_by_view	(ECellView *cell_view,
+						 gint col,
+						 gint row);
+
+G_END_DECLS
+
+#endif /* E_CELL_TEXT_H */
+
diff --git a/e-util/e-cell-toggle.c b/e-util/e-cell-toggle.c
new file mode 100644
index 0000000..2f2bcd3
--- /dev/null
+++ b/e-util/e-cell-toggle.c
@@ -0,0 +1,469 @@
+/*
+ * e-cell-toggle.c - Multi-state image toggle cell object.
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Miguel de Icaza <miguel ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <gdk/gdkkeysyms.h>
+#include <gtk/gtk.h>
+#include <libgnomecanvas/libgnomecanvas.h>
+
+#include "art/empty.xpm"
+
+#include "gal-a11y-e-cell-toggle.h"
+#include "gal-a11y-e-cell-registry.h"
+
+#include "e-cell-toggle.h"
+#include "e-table-item.h"
+
+#define E_CELL_TOGGLE_GET_PRIVATE(obj) \
+	(G_TYPE_INSTANCE_GET_PRIVATE \
+	((obj), E_TYPE_CELL_TOGGLE, ECellTogglePrivate))
+
+struct _ECellTogglePrivate {
+	gchar **icon_names;
+	guint n_icon_names;
+
+	GdkPixbuf *empty;
+	GPtrArray *pixbufs;
+	gint height;
+};
+
+G_DEFINE_TYPE (ECellToggle, e_cell_toggle, E_TYPE_CELL)
+
+typedef struct {
+	ECellView cell_view;
+	GnomeCanvas *canvas;
+} ECellToggleView;
+
+static void
+cell_toggle_load_icons (ECellToggle *cell_toggle)
+{
+	GtkIconTheme *icon_theme;
+	gint width, height;
+	gint max_height = 0;
+	guint ii;
+	GError *error = NULL;
+
+	icon_theme = gtk_icon_theme_get_default ();
+	gtk_icon_size_lookup (GTK_ICON_SIZE_MENU, &width, &height);
+
+	g_ptr_array_set_size (cell_toggle->priv->pixbufs, 0);
+
+	for (ii = 0; ii < cell_toggle->priv->n_icon_names; ii++) {
+		const gchar *icon_name = cell_toggle->priv->icon_names[ii];
+		GdkPixbuf *pixbuf = NULL;
+
+		if (icon_name != NULL)
+			pixbuf = gtk_icon_theme_load_icon (
+				icon_theme, icon_name, height, 0, &error);
+
+		if (error != NULL) {
+			g_warning ("%s", error->message);
+			g_clear_error (&error);
+		}
+
+		if (pixbuf == NULL)
+			pixbuf = g_object_ref (cell_toggle->priv->empty);
+
+		g_ptr_array_add (cell_toggle->priv->pixbufs, pixbuf);
+		max_height = MAX (max_height, gdk_pixbuf_get_height (pixbuf));
+	}
+
+	cell_toggle->priv->height = max_height;
+}
+
+static void
+cell_toggle_dispose (GObject *object)
+{
+	ECellTogglePrivate *priv;
+
+	priv = E_CELL_TOGGLE_GET_PRIVATE (object);
+
+	if (priv->empty != NULL) {
+		g_object_unref (priv->empty);
+		priv->empty = NULL;
+	}
+
+	/* This unrefs all the elements. */
+	g_ptr_array_set_size (priv->pixbufs, 0);
+
+	/* Chain up to parent's dispose() method. */
+	G_OBJECT_CLASS (e_cell_toggle_parent_class)->dispose (object);
+}
+
+static void
+cell_toggle_finalize (GObject *object)
+{
+	ECellTogglePrivate *priv;
+	guint ii;
+
+	priv = E_CELL_TOGGLE_GET_PRIVATE (object);
+
+	/* The array is not NULL-terminated,
+	 * so g_strfreev() will not work. */
+	for (ii = 0; ii < priv->n_icon_names; ii++)
+		g_free (priv->icon_names[ii]);
+	g_free (priv->icon_names);
+
+	g_ptr_array_free (priv->pixbufs, TRUE);
+
+	/* Chain up to parent's finalize() method. */
+	G_OBJECT_CLASS (e_cell_toggle_parent_class)->finalize (object);
+}
+
+static ECellView *
+cell_toggle_new_view (ECell *ecell,
+                      ETableModel *table_model,
+                      gpointer e_table_item_view)
+{
+	ECellToggleView *toggle_view = g_new0 (ECellToggleView, 1);
+	ETableItem *eti = E_TABLE_ITEM (e_table_item_view);
+	GnomeCanvas *canvas = GNOME_CANVAS_ITEM (eti)->canvas;
+
+	toggle_view->cell_view.ecell = ecell;
+	toggle_view->cell_view.e_table_model = table_model;
+	toggle_view->cell_view.e_table_item_view = e_table_item_view;
+	toggle_view->cell_view.kill_view_cb = NULL;
+	toggle_view->cell_view.kill_view_cb_data = NULL;
+	toggle_view->canvas = canvas;
+
+	return (ECellView *) toggle_view;
+}
+
+static void
+cell_toggle_kill_view (ECellView *ecell_view)
+{
+	ECellToggleView *toggle_view = (ECellToggleView *) ecell_view;
+
+	if (toggle_view->cell_view.kill_view_cb)
+		toggle_view->cell_view.kill_view_cb (
+			ecell_view, toggle_view->cell_view.kill_view_cb_data);
+
+	if (toggle_view->cell_view.kill_view_cb_data)
+		g_list_free (toggle_view->cell_view.kill_view_cb_data);
+
+	g_free (ecell_view);
+}
+
+static void
+cell_toggle_draw (ECellView *ecell_view,
+                  cairo_t *cr,
+                  gint model_col,
+                  gint view_col,
+                  gint row,
+                  ECellFlags flags,
+                  gint x1,
+                  gint y1,
+                  gint x2,
+                  gint y2)
+{
+	ECellTogglePrivate *priv;
+	GdkPixbuf *image;
+	gint x, y;
+
+	const gint value = GPOINTER_TO_INT (
+		e_table_model_value_at (ecell_view->e_table_model, model_col, row));
+
+	priv = E_CELL_TOGGLE_GET_PRIVATE (ecell_view->ecell);
+
+	if (value < 0 || value >= priv->pixbufs->len)
+		return;
+
+	image = g_ptr_array_index (priv->pixbufs, value);
+
+	if ((x2 - x1) < gdk_pixbuf_get_width (image))
+		x = x1;
+	else
+		x = x1 + ((x2 - x1) - gdk_pixbuf_get_width (image)) / 2;
+
+	if ((y2 - y1) < gdk_pixbuf_get_height (image))
+		y = y1;
+	else
+		y = y1 + ((y2 - y1) - gdk_pixbuf_get_height (image)) / 2;
+
+	cairo_save (cr);
+	gdk_cairo_set_source_pixbuf (cr, image, x, y);
+	cairo_paint_with_alpha (cr, 1);
+	cairo_restore (cr);
+}
+
+static void
+etog_set_value (ECellToggleView *toggle_view,
+                gint model_col,
+                gint view_col,
+                gint row,
+                gint value)
+{
+	ECellTogglePrivate *priv;
+
+	priv = E_CELL_TOGGLE_GET_PRIVATE (toggle_view->cell_view.ecell);
+
+	if (value >= priv->pixbufs->len)
+		value = 0;
+
+	e_table_model_set_value_at (
+		toggle_view->cell_view.e_table_model,
+		model_col, row, GINT_TO_POINTER (value));
+}
+
+static gint
+cell_toggle_event (ECellView *ecell_view,
+                   GdkEvent *event,
+                   gint model_col,
+                   gint view_col,
+                   gint row,
+                   ECellFlags flags,
+                   ECellActions *actions)
+{
+	ECellToggleView *toggle_view = (ECellToggleView *) ecell_view;
+	gpointer _value = e_table_model_value_at (
+		ecell_view->e_table_model, model_col, row);
+	const gint value = GPOINTER_TO_INT (_value);
+
+	switch (event->type) {
+	case GDK_KEY_PRESS:
+		if (event->key.keyval != GDK_KEY_space)
+			return FALSE;
+		/* Fall through */
+	case GDK_BUTTON_PRESS:
+		if (!e_table_model_is_cell_editable (
+			ecell_view->e_table_model, model_col, row))
+			return FALSE;
+
+		etog_set_value (
+			toggle_view, model_col, view_col, row, value + 1);
+
+		return TRUE;
+
+	default:
+		return FALSE;
+	}
+}
+
+static gint
+cell_toggle_height (ECellView *ecell_view,
+                    gint model_col,
+                    gint view_col,
+                    gint row)
+{
+	ECellTogglePrivate *priv;
+
+	priv = E_CELL_TOGGLE_GET_PRIVATE (ecell_view->ecell);
+
+	return priv->height;
+}
+
+static void
+cell_toggle_print (ECellView *ecell_view,
+                   GtkPrintContext *context,
+                   gint model_col,
+                   gint view_col,
+                   gint row,
+                   gdouble width,
+                   gdouble height)
+{
+	ECellTogglePrivate *priv;
+	GdkPixbuf *image;
+	gdouble image_width, image_height;
+	const gint value = GPOINTER_TO_INT (
+			e_table_model_value_at (ecell_view->e_table_model, model_col, row));
+
+	cairo_t *cr;
+
+	priv = E_CELL_TOGGLE_GET_PRIVATE (ecell_view->ecell);
+
+	if (value >= priv->pixbufs->len)
+		return;
+
+	image = g_ptr_array_index (priv->pixbufs, value);
+	if (image) {
+		cr = gtk_print_context_get_cairo_context (context);
+		cairo_save (cr);
+		cairo_translate (cr, 0 , 0);
+		image = gdk_pixbuf_add_alpha (image, TRUE, 255, 255, 255);
+		image_width = (gdouble) gdk_pixbuf_get_width (image);
+		image_height = (gdouble) gdk_pixbuf_get_height (image);
+		cairo_rectangle (
+			cr,
+			image_width / 7,
+			image_height / 3,
+			image_width - image_width / 4,
+			image_width - image_height / 7);
+		cairo_clip (cr);
+		gdk_cairo_set_source_pixbuf (cr, image, 0, image_height / 4);
+		cairo_paint (cr);
+		cairo_restore (cr);
+	}
+}
+
+static gdouble
+cell_toggle_print_height (ECellView *ecell_view,
+                          GtkPrintContext *context,
+                          gint model_col,
+                          gint view_col,
+                          gint row,
+                          gdouble width)
+{
+	ECellTogglePrivate *priv;
+
+	priv = E_CELL_TOGGLE_GET_PRIVATE (ecell_view->ecell);
+
+	return priv->height;
+}
+
+static gint
+cell_toggle_max_width (ECellView *ecell_view,
+                       gint model_col,
+                       gint view_col)
+{
+	ECellTogglePrivate *priv;
+	gint max_width = 0;
+	gint number_of_rows;
+	gint row;
+
+	priv = E_CELL_TOGGLE_GET_PRIVATE (ecell_view->ecell);
+
+	number_of_rows = e_table_model_row_count (ecell_view->e_table_model);
+	for (row = 0; row < number_of_rows; row++) {
+		GdkPixbuf *pixbuf;
+		gpointer value;
+
+		value = e_table_model_value_at (
+			ecell_view->e_table_model, model_col, row);
+		pixbuf = g_ptr_array_index (
+			priv->pixbufs, GPOINTER_TO_INT (value));
+
+		max_width = MAX (max_width, gdk_pixbuf_get_width (pixbuf));
+	}
+
+	return max_width;
+}
+
+static void
+e_cell_toggle_class_init (ECellToggleClass *class)
+{
+	GObjectClass *object_class;
+	ECellClass *cell_class;
+
+	g_type_class_add_private (class, sizeof (ECellTogglePrivate));
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->dispose = cell_toggle_dispose;
+	object_class->finalize = cell_toggle_finalize;
+
+	cell_class = E_CELL_CLASS (class);
+	cell_class->new_view = cell_toggle_new_view;
+	cell_class->kill_view = cell_toggle_kill_view;
+	cell_class->draw = cell_toggle_draw;
+	cell_class->event = cell_toggle_event;
+	cell_class->height = cell_toggle_height;
+	cell_class->print = cell_toggle_print;
+	cell_class->print_height = cell_toggle_print_height;
+	cell_class->max_width = cell_toggle_max_width;
+
+	gal_a11y_e_cell_registry_add_cell_type (
+		NULL, E_TYPE_CELL_TOGGLE, gal_a11y_e_cell_toggle_new);
+}
+
+static void
+e_cell_toggle_init (ECellToggle *cell_toggle)
+{
+	cell_toggle->priv = E_CELL_TOGGLE_GET_PRIVATE (cell_toggle);
+
+	cell_toggle->priv->empty =
+		gdk_pixbuf_new_from_xpm_data (empty_xpm);
+
+	cell_toggle->priv->pixbufs =
+		g_ptr_array_new_with_free_func (g_object_unref);
+}
+
+/**
+ * e_cell_toggle_construct:
+ * @cell_toggle: a fresh ECellToggle object
+ * @icon_names: array of icon names, some of which may be %NULL
+ * @n_icon_names: length of the @icon_names array
+ *
+ * Constructs the @cell_toggle object with the @icon_names and @n_icon_names
+ * arguments.
+ */
+void
+e_cell_toggle_construct (ECellToggle *cell_toggle,
+                         const gchar **icon_names,
+                         guint n_icon_names)
+{
+	guint ii;
+
+	g_return_if_fail (E_IS_CELL_TOGGLE (cell_toggle));
+	g_return_if_fail (icon_names != NULL);
+	g_return_if_fail (n_icon_names > 0);
+
+	cell_toggle->priv->icon_names = g_new (gchar *, n_icon_names);
+	cell_toggle->priv->n_icon_names = n_icon_names;
+
+	for (ii = 0; ii < n_icon_names; ii++)
+		cell_toggle->priv->icon_names[ii] = g_strdup (icon_names[ii]);
+
+	cell_toggle_load_icons (cell_toggle);
+}
+
+/**
+ * e_cell_toggle_new:
+ * @icon_names: array of icon names, some of which may be %NULL
+ * @n_icon_names: length of the @icon_names array
+ *
+ * Creates a new ECell renderer that can be used to render toggle
+ * buttons with the icons specified in @icon_names.  The value returned
+ * by ETableModel::get_value is typecast into an integer and clamped
+ * to the [0..n_icon_names) range.  That will select the image rendered.
+ *
+ * %NULL elements in @icon_names will show no icon for the corresponding
+ * integer value.
+ *
+ * Returns: an ECell object that can be used to render multi-state
+ * toggle cells.
+ */
+ECell *
+e_cell_toggle_new (const gchar **icon_names,
+                   guint n_icon_names)
+{
+	ECellToggle *cell_toggle;
+
+	g_return_val_if_fail (icon_names != NULL, NULL);
+	g_return_val_if_fail (n_icon_names > 0, NULL);
+
+	cell_toggle = g_object_new (E_TYPE_CELL_TOGGLE, NULL);
+	e_cell_toggle_construct (cell_toggle, icon_names, n_icon_names);
+
+	return (ECell *) cell_toggle;
+}
+
+GPtrArray *
+e_cell_toggle_get_pixbufs (ECellToggle *cell_toggle)
+{
+	g_return_val_if_fail (E_IS_CELL_TOGGLE (cell_toggle), NULL);
+
+	return cell_toggle->priv->pixbufs;
+}
diff --git a/e-util/e-cell-toggle.h b/e-util/e-cell-toggle.h
new file mode 100644
index 0000000..657836f
--- /dev/null
+++ b/e-util/e-cell-toggle.h
@@ -0,0 +1,83 @@
+/*
+ *
+ * Multi-state image toggle cell object.
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Miguel de Icaza <miguel ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_CELL_TOGGLE_H
+#define E_CELL_TOGGLE_H
+
+#include <libgnomecanvas/libgnomecanvas.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#include <e-util/e-cell.h>
+
+/* Standard GObject macros */
+#define E_TYPE_CELL_TOGGLE \
+	(e_cell_toggle_get_type ())
+#define E_CELL_TOGGLE(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_CELL_TOGGLE, ECellToggle))
+#define E_CELL_TOGGLE_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_CELL_TOGGLE, ECellToggleClass))
+#define E_IS_CELL_TOGGLE(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_CELL_TOGGLE))
+#define E_IS_CELL_TOGGLE_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_CELL_TOGGLE))
+#define E_CELL_TOGGLE_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_CELL_TOGGLE, ECellToggleClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ECellToggle ECellToggle;
+typedef struct _ECellToggleClass ECellToggleClass;
+typedef struct _ECellTogglePrivate ECellTogglePrivate;
+
+struct _ECellToggle {
+	ECell parent;
+	ECellTogglePrivate *priv;
+};
+
+struct _ECellToggleClass {
+	ECellClass parent_class;
+};
+
+GType		e_cell_toggle_get_type		(void) G_GNUC_CONST;
+ECell *		e_cell_toggle_new		(const gchar **icon_names,
+						 guint n_icon_names);
+void		e_cell_toggle_construct		(ECellToggle *cell_toggle,
+						 const gchar **icon_names,
+						 guint n_icon_names);
+GPtrArray *	e_cell_toggle_get_pixbufs	(ECellToggle *cell_toggle);
+
+G_END_DECLS
+
+#endif /* E_CELL_TOGGLE_H */
+
diff --git a/e-util/e-cell-tree.c b/e-util/e-cell-tree.c
new file mode 100644
index 0000000..085fb0c
--- /dev/null
+++ b/e-util/e-cell-tree.c
@@ -0,0 +1,880 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * e-cell-tree.c - Tree cell object.
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * Authors:
+ *   Chris Toshok <toshok ximian com>
+ *
+ * A majority of code taken from:
+ *
+ * the ECellText renderer.
+ * Copyright 1998, The Free Software Foundation
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * 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., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <ctype.h>
+#include <math.h>
+#include <stdio.h>
+
+#include <gdk/gdkkeysyms.h>
+#include <gtk/gtk.h>
+#include <libgnomecanvas/libgnomecanvas.h>
+
+#include "gal-a11y-e-cell-registry.h"
+#include "gal-a11y-e-cell-tree.h"
+
+#include "e-cell-tree.h"
+#include "e-table-item.h"
+#include "e-tree.h"
+#include "e-tree-model.h"
+#include "e-tree-table-adapter.h"
+
+G_DEFINE_TYPE (ECellTree, e_cell_tree, E_TYPE_CELL)
+
+typedef struct {
+	ECellView    cell_view;
+	ECellView   *subcell_view;
+
+	GnomeCanvas *canvas;
+	gboolean prelit;
+	gint animate_timeout;
+
+} ECellTreeView;
+
+#define INDENT_AMOUNT 16
+
+ECellView *
+e_cell_tree_view_get_subcell_view (ECellView *ect)
+{
+	return ((ECellTreeView *) ect)->subcell_view;
+}
+
+static ETreePath
+e_cell_tree_get_node (ETableModel *table_model,
+                      gint row)
+{
+	return e_table_model_value_at (table_model, -1, row);
+}
+
+static ETreeModel *
+e_cell_tree_get_tree_model (ETableModel *table_model,
+                            gint row)
+{
+	return e_table_model_value_at (table_model, -2, row);
+}
+
+static ETreeTableAdapter *
+e_cell_tree_get_tree_table_adapter (ETableModel *table_model,
+                                    gint row)
+{
+	return e_table_model_value_at (table_model, -3, row);
+}
+
+static gint
+visible_depth_of_node (ETableModel *model,
+                       gint row)
+{
+	ETreeModel *tree_model = e_cell_tree_get_tree_model (model, row);
+	ETreeTableAdapter *adapter = e_cell_tree_get_tree_table_adapter (model, row);
+	ETreePath path = e_cell_tree_get_node (model, row);
+	return (e_tree_model_node_depth (tree_model, path)
+		- (e_tree_table_adapter_root_node_is_visible (adapter) ? 0 : 1));
+}
+
+/* If this is changed to not include the width of the expansion pixmap
+ * if the path is not expandable, then max_width needs to change as
+ * well. */
+static gint
+offset_of_node (ETableModel *table_model,
+                gint row)
+{
+	ETreeModel *tree_model = e_cell_tree_get_tree_model (table_model, row);
+	ETreePath path = e_cell_tree_get_node (table_model, row);
+
+	if (visible_depth_of_node (table_model, row) >= 0 ||
+	    e_tree_model_node_is_expandable (tree_model, path)) {
+		return (visible_depth_of_node (table_model, row) + 1) * INDENT_AMOUNT;
+	} else {
+		return 0;
+	}
+}
+
+/*
+ * ECell::new_view method
+ */
+static ECellView *
+ect_new_view (ECell *ecell,
+              ETableModel *table_model,
+              gpointer e_table_item_view)
+{
+	ECellTree *ect = E_CELL_TREE (ecell);
+	ECellTreeView *tree_view = g_new0 (ECellTreeView, 1);
+	GnomeCanvas *canvas = GNOME_CANVAS_ITEM (e_table_item_view)->canvas;
+
+	tree_view->cell_view.ecell = ecell;
+	tree_view->cell_view.e_table_model = table_model;
+	tree_view->cell_view.e_table_item_view = e_table_item_view;
+	tree_view->cell_view.kill_view_cb = NULL;
+	tree_view->cell_view.kill_view_cb_data = NULL;
+
+	/* create our subcell view */
+	tree_view->subcell_view = e_cell_new_view (ect->subcell, table_model, e_table_item_view /* XXX */);
+
+	tree_view->canvas = canvas;
+
+	return (ECellView *) tree_view;
+}
+
+/*
+ * ECell::kill_view method
+ */
+static void
+ect_kill_view (ECellView *ecv)
+{
+	ECellTreeView *tree_view = (ECellTreeView *) ecv;
+
+	if (tree_view->cell_view.kill_view_cb)
+	    (tree_view->cell_view.kill_view_cb)(ecv, tree_view->cell_view.kill_view_cb_data);
+
+	if (tree_view->cell_view.kill_view_cb_data)
+	    g_list_free (tree_view->cell_view.kill_view_cb_data);
+
+	/* kill our subcell view */
+	e_cell_kill_view (tree_view->subcell_view);
+
+	g_free (tree_view);
+}
+
+/*
+ * ECell::realize method
+ */
+static void
+ect_realize (ECellView *ecell_view)
+{
+	ECellTreeView *tree_view = (ECellTreeView *) ecell_view;
+
+	/* realize our subcell view */
+	e_cell_realize (tree_view->subcell_view);
+
+	if (E_CELL_CLASS (e_cell_tree_parent_class)->realize)
+		(* E_CELL_CLASS (e_cell_tree_parent_class)->realize) (ecell_view);
+}
+
+/*
+ * ECell::unrealize method
+ */
+static void
+ect_unrealize (ECellView *ecv)
+{
+	ECellTreeView *tree_view = (ECellTreeView *) ecv;
+
+	/* unrealize our subcell view. */
+	e_cell_unrealize (tree_view->subcell_view);
+
+	if (E_CELL_CLASS (e_cell_tree_parent_class)->unrealize)
+		(* E_CELL_CLASS (e_cell_tree_parent_class)->unrealize) (ecv);
+}
+
+static void
+draw_expander (ECellTreeView *ectv,
+               cairo_t *cr,
+               GtkExpanderStyle expander_style,
+               GtkStateType state,
+               GdkRectangle *rect)
+{
+	GtkStyleContext *style_context;
+	GtkWidget *tree;
+	GtkStateFlags flags = 0;
+	gint exp_size;
+
+	tree = gtk_widget_get_parent (GTK_WIDGET (ectv->canvas));
+	style_context = gtk_widget_get_style_context (tree);
+
+	gtk_style_context_save (style_context);
+
+	gtk_style_context_add_class (style_context, GTK_STYLE_CLASS_EXPANDER);
+
+	switch (state) {
+		case GTK_STATE_PRELIGHT:
+			flags |= GTK_STATE_FLAG_PRELIGHT;
+			break;
+		case GTK_STATE_SELECTED:
+			flags |= GTK_STATE_FLAG_SELECTED;
+			break;
+		case GTK_STATE_INSENSITIVE:
+			flags |= GTK_STATE_FLAG_INSENSITIVE;
+			break;
+		default:
+			break;
+	}
+
+	if (expander_style == GTK_EXPANDER_EXPANDED)
+		flags |= GTK_STATE_FLAG_ACTIVE;
+
+	gtk_style_context_set_state (style_context, flags);
+
+	gtk_widget_style_get (tree, "expander_size", &exp_size, NULL);
+
+	cairo_save (cr);
+
+	gtk_render_expander (
+		style_context, cr,
+		(gdouble) rect->x + rect->width - exp_size,
+		(gdouble) (rect->y + rect->height / 2) - (exp_size / 2),
+		(gdouble) exp_size,
+		(gdouble) exp_size);
+
+	cairo_restore (cr);
+
+	gtk_style_context_restore (style_context);
+}
+
+/*
+ * ECell::draw method
+ */
+static void
+ect_draw (ECellView *ecell_view,
+          cairo_t *cr,
+          gint model_col,
+          gint view_col,
+          gint row,
+          ECellFlags flags,
+          gint x1,
+          gint y1,
+          gint x2,
+          gint y2)
+{
+	ECellTreeView *tree_view = (ECellTreeView *) ecell_view;
+	ETreeModel *tree_model = e_cell_tree_get_tree_model (ecell_view->e_table_model, row);
+	ETreeTableAdapter *tree_table_adapter = e_cell_tree_get_tree_table_adapter (ecell_view->e_table_model, row);
+	ETreePath node;
+	GdkRectangle rect;
+	gint offset, subcell_offset;
+
+	cairo_save (cr);
+
+	/* only draw the tree effects if we're the active sort */
+	if (/* XXX */ TRUE) {
+		GdkPixbuf *node_image;
+		gint node_image_width = 0, node_image_height = 0;
+
+		tree_view->prelit = FALSE;
+
+		node = e_cell_tree_get_node (ecell_view->e_table_model, row);
+
+		offset = offset_of_node (ecell_view->e_table_model, row);
+		subcell_offset = offset;
+
+		node_image = e_tree_model_icon_at (tree_model, node);
+
+		if (node_image) {
+			node_image_width = gdk_pixbuf_get_width (node_image);
+			node_image_height = gdk_pixbuf_get_height (node_image);
+		}
+
+		/*
+		 * Be a nice citizen: clip to the region we are supposed to draw on
+		 */
+		rect.x = x1;
+		rect.y = y1;
+		rect.width = subcell_offset + node_image_width;
+		rect.height = y2 - y1;
+
+		/* now draw our icon if we're expandable */
+		if (e_tree_model_node_is_expandable (tree_model, node)) {
+			gboolean expanded = e_tree_table_adapter_node_is_expanded (tree_table_adapter, node);
+			GdkRectangle r;
+
+			r = rect;
+			r.width -= node_image_width + 2;
+			draw_expander (tree_view, cr, expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED, GTK_STATE_NORMAL, &r);
+		}
+
+		if (node_image) {
+			gdk_cairo_set_source_pixbuf (
+				cr, node_image,
+				x1 + subcell_offset,
+				y1 + (y2 - y1) / 2 - node_image_height / 2);
+			cairo_paint (cr);
+
+			subcell_offset += node_image_width;
+		}
+	}
+
+	/* Now cause our subcell to draw its contents, shifted by
+	 * subcell_offset pixels */
+	e_cell_draw (
+		tree_view->subcell_view, cr,
+		model_col, view_col, row, flags,
+		x1 + subcell_offset, y1, x2, y2);
+
+	cairo_restore (cr);
+}
+
+static void
+adjust_event_position (GdkEvent *event,
+                       gint offset)
+{
+	switch (event->type) {
+	case GDK_BUTTON_PRESS:
+	case GDK_BUTTON_RELEASE:
+	case GDK_2BUTTON_PRESS:
+	case GDK_3BUTTON_PRESS:
+		event->button.x += offset;
+		break;
+	case GDK_MOTION_NOTIFY:
+		event->motion.x += offset;
+		break;
+	default:
+		break;
+	}
+}
+
+static gboolean
+event_in_expander (GdkEvent *event,
+                   gint offset,
+                   gint height)
+{
+	switch (event->type) {
+	case GDK_BUTTON_PRESS:
+		return (event->button.x > (offset - INDENT_AMOUNT) && event->button.x < offset);
+	case GDK_MOTION_NOTIFY:
+		return (event->motion.x > (offset - INDENT_AMOUNT) && event->motion.x < offset &&
+			event->motion.y > 2 && event->motion.y < (height - 2));
+	default:
+		break;
+	}
+
+	return FALSE;
+}
+
+/*
+ * ECell::height method
+ */
+static gint
+ect_height (ECellView *ecell_view,
+            gint model_col,
+            gint view_col,
+            gint row)
+{
+	ECellTreeView *tree_view = (ECellTreeView *) ecell_view;
+
+	return (((e_cell_height (tree_view->subcell_view, model_col, view_col, row)) + 1) / 2) * 2;
+}
+
+typedef struct {
+	ECellTreeView *ectv;
+	ETreeTableAdapter *etta;
+	ETreePath node;
+	gboolean expanded;
+	gboolean finish;
+	GdkRectangle area;
+} animate_closure_t;
+
+static gboolean
+animate_expander (gpointer data)
+{
+	GtkLayout *layout;
+	GdkWindow *window;
+	animate_closure_t *closure = (animate_closure_t *) data;
+	cairo_t *cr;
+
+	if (closure->finish) {
+		e_tree_table_adapter_node_set_expanded (closure->etta, closure->node, !closure->expanded);
+		closure->ectv->animate_timeout = 0;
+		g_free (data);
+		return FALSE;
+	}
+
+	layout = GTK_LAYOUT (closure->ectv->canvas);
+	window = gtk_layout_get_bin_window (layout);
+
+	cr = gdk_cairo_create (window);
+
+	draw_expander (
+		closure->ectv, cr, closure->expanded ?
+		GTK_EXPANDER_SEMI_COLLAPSED :
+		GTK_EXPANDER_SEMI_EXPANDED,
+		GTK_STATE_NORMAL, &closure->area);
+	closure->finish = TRUE;
+
+	cairo_destroy (cr);
+
+	return TRUE;
+}
+
+/*
+ * ECell::event method
+ */
+static gint
+ect_event (ECellView *ecell_view,
+           GdkEvent *event,
+           gint model_col,
+           gint view_col,
+           gint row,
+           ECellFlags flags,
+           ECellActions *actions)
+{
+	GtkLayout *layout;
+	GdkWindow *window;
+	ECellTreeView *tree_view = (ECellTreeView *) ecell_view;
+	ETreeModel *tree_model = e_cell_tree_get_tree_model (ecell_view->e_table_model, row);
+	ETreeTableAdapter *etta = e_cell_tree_get_tree_table_adapter (ecell_view->e_table_model, row);
+	ETreePath node = e_cell_tree_get_node (ecell_view->e_table_model, row);
+	gint offset = offset_of_node (ecell_view->e_table_model, row);
+	gint result;
+
+	layout = GTK_LAYOUT (tree_view->canvas);
+	window = gtk_layout_get_bin_window (layout);
+
+	switch (event->type) {
+	case GDK_BUTTON_PRESS:
+
+		if (event_in_expander (event, offset, 0)) {
+			if (e_tree_model_node_is_expandable (tree_model, node)) {
+				gboolean expanded = e_tree_table_adapter_node_is_expanded (etta, node);
+				gint tmp_row = row;
+				GdkRectangle area;
+				animate_closure_t *closure = g_new0 (animate_closure_t, 1);
+				cairo_t *cr;
+				gint hgt;
+
+				e_table_item_get_cell_geometry (
+					tree_view->cell_view.e_table_item_view,
+					&tmp_row, &view_col, &area.x, &area.y, NULL, &area.height);
+				area.width = offset - 2;
+				hgt = e_cell_height (ecell_view, model_col, view_col, row);
+
+				if (hgt != area.height) /* Composite cells */
+					area.height += hgt;
+
+				cr = gdk_cairo_create (window);
+				draw_expander (
+					tree_view, cr, expanded ?
+					GTK_EXPANDER_SEMI_EXPANDED :
+					GTK_EXPANDER_SEMI_COLLAPSED,
+					GTK_STATE_NORMAL, &area);
+				cairo_destroy (cr);
+
+				closure->ectv = tree_view;
+				closure->etta = etta;
+				closure->node = node;
+				closure->expanded = expanded;
+				closure->area = area;
+				tree_view->animate_timeout = g_timeout_add (50, animate_expander, closure);
+				return TRUE;
+			}
+		}
+		else if (event->button.x < (offset - INDENT_AMOUNT))
+			return FALSE;
+		break;
+
+	case GDK_MOTION_NOTIFY:
+
+		if (e_tree_model_node_is_expandable (tree_model, node)) {
+			gint height = ect_height (ecell_view, model_col, view_col, row);
+			GdkRectangle area;
+			gboolean in_expander = event_in_expander (event, offset, height);
+
+			if (tree_view->prelit ^ in_expander) {
+				gint tmp_row = row;
+				cairo_t *cr;
+
+				e_table_item_get_cell_geometry (
+					tree_view->cell_view.e_table_item_view,
+					&tmp_row, &view_col, &area.x, &area.y, NULL, &area.height);
+				area.width = offset - 2;
+
+				cr = gdk_cairo_create (window);
+				draw_expander (
+					tree_view, cr,
+					e_tree_table_adapter_node_is_expanded (etta, node) ?
+					GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
+					in_expander ? GTK_STATE_PRELIGHT : GTK_STATE_NORMAL, &area);
+				cairo_destroy (cr);
+
+				tree_view->prelit = in_expander;
+				return TRUE;
+			}
+
+		}
+		break;
+
+	case GDK_LEAVE_NOTIFY:
+
+		if (tree_view->prelit) {
+			gint tmp_row = row;
+			GdkRectangle area;
+			cairo_t *cr;
+
+			e_table_item_get_cell_geometry (
+				tree_view->cell_view.e_table_item_view,
+				&tmp_row, &view_col, &area.x, &area.y, NULL, &area.height);
+			area.width = offset - 2;
+
+			cr = gdk_cairo_create (window);
+			draw_expander (
+				tree_view, cr,
+				e_tree_table_adapter_node_is_expanded (etta, node) ?
+				GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
+				GTK_STATE_NORMAL, &area);
+			cairo_destroy (cr);
+
+			tree_view->prelit = FALSE;
+		}
+		return TRUE;
+
+	default:
+		break;
+	}
+
+	adjust_event_position (event, -offset);
+	result = e_cell_event (tree_view->subcell_view, event, model_col, view_col, row, flags, actions);
+	adjust_event_position (event, offset);
+
+	return result;
+}
+
+/*
+ * ECell::max_width method
+ */
+static gint
+ect_max_width (ECellView *ecell_view,
+               gint model_col,
+               gint view_col)
+{
+	ECellTreeView *tree_view = (ECellTreeView *) ecell_view;
+	gint row;
+	gint number_of_rows;
+	gint max_width = 0;
+	gint width = 0;
+	gint subcell_max_width = 0;
+	gboolean per_row = e_cell_max_width_by_row_implemented (tree_view->subcell_view);
+
+	number_of_rows = e_table_model_row_count (ecell_view->e_table_model);
+
+	if (!per_row)
+		subcell_max_width = e_cell_max_width (tree_view->subcell_view, model_col, view_col);
+
+	for (row = 0; row < number_of_rows; row++) {
+		ETreeModel *tree_model = e_cell_tree_get_tree_model (ecell_view->e_table_model, row);
+		ETreePath node;
+		GdkPixbuf *node_image;
+		gint node_image_width = 0;
+
+		gint offset, subcell_offset;
+#if 0
+		gboolean expanded, expandable;
+		ETreeTableAdapter *tree_table_adapter = e_cell_tree_get_tree_table_adapter (ecell_view->e_table_model, row);
+#endif
+
+		node = e_cell_tree_get_node (ecell_view->e_table_model, row);
+
+		offset = offset_of_node (ecell_view->e_table_model, row);
+		subcell_offset = offset;
+
+		node_image = e_tree_model_icon_at (tree_model, node);
+
+		if (node_image) {
+			node_image_width = gdk_pixbuf_get_width (node_image);
+		}
+
+		width = subcell_offset + node_image_width;
+
+		if (per_row)
+			width += e_cell_max_width_by_row (tree_view->subcell_view, model_col, view_col, row);
+		else
+			width += subcell_max_width;
+
+#if 0
+		expandable = e_tree_model_node_is_expandable (tree_model, node);
+		expanded = e_tree_table_adapter_node_is_expanded (tree_table_adapter, node);
+
+		/* This is unnecessary since this is already handled
+		 * by the offset_of_node function.  If that changes,
+		 * this will have to change too. */
+
+		if (expandable) {
+			GdkPixbuf *image;
+
+			image = (expanded
+				 ? E_CELL_TREE (tree_view->cell_view.ecell)->open_pixbuf
+				 : E_CELL_TREE (tree_view->cell_view.ecell)->closed_pixbuf);
+
+			width += gdk_pixbuf_get_width (image);
+		}
+#endif
+
+		max_width = MAX (max_width, width);
+	}
+
+	return max_width;
+}
+
+/*
+ * ECellView::get_bg_color method
+ */
+static gchar *
+ect_get_bg_color (ECellView *ecell_view,
+                  gint row)
+{
+	ECellTreeView *tree_view = (ECellTreeView *) ecell_view;
+
+	return e_cell_get_bg_color (tree_view->subcell_view, row);
+}
+
+/*
+ * ECellView::enter_edit method
+ */
+static gpointer
+ect_enter_edit (ECellView *ecell_view,
+                gint model_col,
+                gint view_col,
+                gint row)
+{
+	/* just defer to our subcell's view */
+	ECellTreeView *tree_view = (ECellTreeView *) ecell_view;
+
+	return e_cell_enter_edit (tree_view->subcell_view, model_col, view_col, row);
+}
+
+/*
+ * ECellView::leave_edit method
+ */
+static void
+ect_leave_edit (ECellView *ecell_view,
+                gint model_col,
+                gint view_col,
+                gint row,
+                gpointer edit_context)
+{
+	/* just defer to our subcell's view */
+	ECellTreeView *tree_view = (ECellTreeView *) ecell_view;
+
+	e_cell_leave_edit (tree_view->subcell_view, model_col, view_col, row, edit_context);
+}
+
+static void
+ect_print (ECellView *ecell_view,
+           GtkPrintContext *context,
+           gint model_col,
+           gint view_col,
+           gint row,
+           gdouble width,
+           gdouble height)
+{
+	ECellTreeView *tree_view = (ECellTreeView *) ecell_view;
+	cairo_t *cr = gtk_print_context_get_cairo_context (context);
+
+	cairo_save (cr);
+
+	if (/* XXX only if we're the active sort */ TRUE) {
+		ETreeModel *tree_model = e_cell_tree_get_tree_model (ecell_view->e_table_model, row);
+		ETreeTableAdapter *tree_table_adapter = e_cell_tree_get_tree_table_adapter (ecell_view->e_table_model, row);
+		ETreePath node = e_cell_tree_get_node (ecell_view->e_table_model, row);
+		gint offset = offset_of_node (ecell_view->e_table_model, row);
+		gint subcell_offset = offset;
+		gboolean expandable = e_tree_model_node_is_expandable (tree_model, node);
+
+		/* draw our lines */
+		if (E_CELL_TREE (tree_view->cell_view.ecell)->draw_lines) {
+			gint depth;
+
+			if (!e_tree_model_node_is_root (tree_model, node)
+			    || e_tree_model_node_get_children (tree_model, node, NULL) > 0) {
+				cairo_move_to (
+					cr,
+					offset - INDENT_AMOUNT / 2,
+					height / 2);
+				cairo_line_to (cr, offset, height / 2);
+			}
+
+			if (visible_depth_of_node (ecell_view->e_table_model, row) != 0) {
+				cairo_move_to (
+					cr,
+					offset - INDENT_AMOUNT / 2, height);
+				cairo_line_to (
+					cr,
+					offset - INDENT_AMOUNT / 2,
+					e_tree_table_adapter_node_get_next
+					(tree_table_adapter, node) ? 0 :
+					height / 2);
+			}
+
+			/* now traverse back up to the root of the tree, checking at
+			 * each level if the node has siblings, and drawing the
+			 * correct vertical pipe for it's configuration. */
+			node = e_tree_model_node_get_parent (tree_model, node);
+			depth = visible_depth_of_node (ecell_view->e_table_model, row) - 1;
+			offset -= INDENT_AMOUNT;
+			while (node && depth != 0) {
+				if (e_tree_table_adapter_node_get_next (tree_table_adapter, node)) {
+					cairo_move_to (
+						cr,
+						offset - INDENT_AMOUNT / 2,
+						height);
+					cairo_line_to (
+						cr,
+						offset - INDENT_AMOUNT / 2, 0);
+				}
+				node = e_tree_model_node_get_parent (tree_model, node);
+				depth--;
+				offset -= INDENT_AMOUNT;
+			}
+		}
+
+		/* now draw our icon if we're expandable */
+		if (expandable) {
+			gboolean expanded;
+			GdkRectangle r;
+			gint exp_size = 0;
+
+			gtk_widget_style_get (GTK_WIDGET (gtk_widget_get_parent (GTK_WIDGET (tree_view->canvas))), "expander_size", &exp_size, NULL);
+
+			node = e_cell_tree_get_node (ecell_view->e_table_model, row);
+			expanded = e_tree_table_adapter_node_is_expanded (tree_table_adapter, node);
+
+			r.x = 0;
+			r.y = 0;
+			r.width = MIN (width, exp_size);
+			r.height = height;
+
+			draw_expander (tree_view, cr, expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED, GTK_STATE_NORMAL, &r);
+		}
+
+		cairo_stroke (cr);
+
+		cairo_translate (cr, subcell_offset, 0);
+		width -= subcell_offset;
+	}
+
+	cairo_restore (cr);
+
+	e_cell_print (tree_view->subcell_view, context, model_col, view_col, row, width, height);
+}
+
+static gdouble
+ect_print_height (ECellView *ecell_view,
+                  GtkPrintContext *context,
+                  gint model_col,
+                  gint view_col,
+                  gint row,
+                  gdouble width)
+{
+	return 12; /* XXX */
+}
+
+/*
+ * GObject::dispose method
+ */
+static void
+ect_dispose (GObject *object)
+{
+	ECellTree *ect = E_CELL_TREE (object);
+
+	/* destroy our subcell */
+	if (ect->subcell)
+		g_object_unref (ect->subcell);
+	ect->subcell = NULL;
+
+	G_OBJECT_CLASS (e_cell_tree_parent_class)->dispose (object);
+}
+
+static void
+e_cell_tree_class_init (ECellTreeClass *class)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (class);
+	ECellClass *ecc = E_CELL_CLASS (class);
+
+	object_class->dispose = ect_dispose;
+
+	ecc->new_view         = ect_new_view;
+	ecc->kill_view        = ect_kill_view;
+	ecc->realize          = ect_realize;
+	ecc->unrealize        = ect_unrealize;
+	ecc->draw             = ect_draw;
+	ecc->event            = ect_event;
+	ecc->height           = ect_height;
+	ecc->enter_edit       = ect_enter_edit;
+	ecc->leave_edit       = ect_leave_edit;
+	ecc->print            = ect_print;
+	ecc->print_height     = ect_print_height;
+	ecc->max_width        = ect_max_width;
+	ecc->get_bg_color     = ect_get_bg_color;
+
+	gal_a11y_e_cell_registry_add_cell_type (NULL, E_TYPE_CELL_TREE, gal_a11y_e_cell_tree_new);
+}
+
+static void
+e_cell_tree_init (ECellTree *ect)
+{
+	/* nothing to do */
+}
+
+/**
+ * e_cell_tree_construct:
+ * @ect: the ECellTree we're constructing.
+ * @draw_lines: whether or not to draw the lines between parents/children/siblings.
+ * @subcell: the ECell to render to the right of the tree effects.
+ *
+ * Constructs an ECellTree.  used by subclasses that need to
+ * initialize a nested ECellTree.  See e_cell_tree_new() for more info.
+ *
+ **/
+void
+e_cell_tree_construct (ECellTree *ect,
+                       gboolean draw_lines,
+                       ECell *subcell)
+{
+	ect->subcell = subcell;
+	if (subcell)
+		g_object_ref_sink (subcell);
+
+	ect->draw_lines = draw_lines;
+}
+
+/**
+ * e_cell_tree_new:
+ * @draw_lines: whether or not to draw the lines between parents/children/siblings.
+ * @subcell: the ECell to render to the right of the tree effects.
+ *
+ * Creates a new ECell renderer that can be used to render tree
+ * effects that come from an ETreeModel.  Various assumptions are made
+ * as to the fact that the ETableModel the ETable this cell is
+ * associated with is in fact an ETreeModel.  The cell uses special
+ * columns to get at structural information (needed to draw the
+ * lines/icons.
+ *
+ * Return value: an ECell object that can be used to render trees.
+ **/
+ECell *
+e_cell_tree_new (gboolean draw_lines,
+                 ECell *subcell)
+{
+	ECellTree *ect = g_object_new (E_TYPE_CELL_TREE, NULL);
+
+	e_cell_tree_construct (ect, draw_lines, subcell);
+
+	return (ECell *) ect;
+}
+
diff --git a/e-util/e-cell-tree.h b/e-util/e-cell-tree.h
new file mode 100644
index 0000000..044c14b
--- /dev/null
+++ b/e-util/e-cell-tree.h
@@ -0,0 +1,90 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * e-cell-tree.h - Tree cell object.
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * Authors:
+ *   Chris Toshok <toshok ximian com>
+ *
+ * A majority of code taken from:
+ *
+ * the ECellText renderer.
+ * Copyright 1998, The Free Software Foundation
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * 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., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef _E_CELL_TREE_H_
+#define _E_CELL_TREE_H_
+
+#include <libgnomecanvas/libgnomecanvas.h>
+
+#include <e-util/e-cell.h>
+
+/* Standard GObject macros */
+#define E_TYPE_CELL_TREE \
+	(e_cell_tree_get_type ())
+#define E_CELL_TREE(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_CELL_TREE, ECellTree))
+#define E_CELL_TREE_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_CELL_TREE, ECellTreeClass))
+#define E_IS_CELL_TREE(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_CELL_TREE))
+#define E_IS_CELL_TREE_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_CELL_TREE))
+#define E_CELL_TREE_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_CELL_TREE, ECellTreeClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ECellTree ECellTree;
+typedef struct _ECellTreeClass ECellTreeClass;
+
+struct _ECellTree {
+	ECell parent;
+
+	gboolean draw_lines;
+
+	ECell *subcell;
+};
+
+struct _ECellTreeClass {
+	ECellClass parent_class;
+};
+
+GType		e_cell_tree_get_type		(void) G_GNUC_CONST;
+ECell *		e_cell_tree_new			(gboolean draw_lines,
+						 ECell *subcell);
+void		e_cell_tree_construct		(ECellTree *ect,
+						 gboolean draw_lines,
+						 ECell *subcell);
+ECellView *	e_cell_tree_view_get_subcell_view
+						(ECellView *ect);
+
+G_END_DECLS
+
+#endif /* _E_CELL_TREE_H_ */
+
diff --git a/e-util/e-cell-vbox.c b/e-util/e-cell-vbox.c
new file mode 100644
index 0000000..ef34a0a
--- /dev/null
+++ b/e-util/e-cell-vbox.c
@@ -0,0 +1,341 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Toshok <toshok ximian com>
+ *		Chris Lahey  <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <ctype.h>
+#include <math.h>
+#include <stdio.h>
+
+#include <gtk/gtk.h>
+
+#include "gal-a11y-e-cell-registry.h"
+#include "gal-a11y-e-cell-vbox.h"
+
+#include "e-cell-vbox.h"
+#include "e-table-item.h"
+
+G_DEFINE_TYPE (ECellVbox, e_cell_vbox, E_TYPE_CELL)
+
+#define INDENT_AMOUNT 16
+
+/*
+ * ECell::new_view method
+ */
+static ECellView *
+ecv_new_view (ECell *ecell,
+              ETableModel *table_model,
+              gpointer e_table_item_view)
+{
+	ECellVbox *ecv = E_CELL_VBOX (ecell);
+	ECellVboxView *vbox_view = g_new0 (ECellVboxView, 1);
+	gint i;
+
+	vbox_view->cell_view.ecell = ecell;
+	vbox_view->cell_view.e_table_model = table_model;
+	vbox_view->cell_view.e_table_item_view = e_table_item_view;
+	vbox_view->cell_view.kill_view_cb = NULL;
+	vbox_view->cell_view.kill_view_cb_data = NULL;
+
+	/* create our subcell view */
+	vbox_view->subcell_view_count = ecv->subcell_count;
+	vbox_view->subcell_views = g_new (ECellView *, vbox_view->subcell_view_count);
+	vbox_view->model_cols = g_new (int, vbox_view->subcell_view_count);
+
+	for (i = 0; i < vbox_view->subcell_view_count; i++) {
+		vbox_view->subcell_views[i] = e_cell_new_view (ecv->subcells[i], table_model, e_table_item_view /* XXX */);
+		vbox_view->model_cols[i] = ecv->model_cols[i];
+	}
+
+	return (ECellView *) vbox_view;
+}
+
+/*
+ * ECell::kill_view method
+ */
+static void
+ecv_kill_view (ECellView *ecv)
+{
+	ECellVboxView *vbox_view = (ECellVboxView *) ecv;
+	gint i;
+
+	if (vbox_view->cell_view.kill_view_cb)
+		(vbox_view->cell_view.kill_view_cb)(ecv, vbox_view->cell_view.kill_view_cb_data);
+
+	if (vbox_view->cell_view.kill_view_cb_data)
+	    g_list_free (vbox_view->cell_view.kill_view_cb_data);
+
+	/* kill our subcell view */
+	for (i = 0; i < vbox_view->subcell_view_count; i++)
+		e_cell_kill_view (vbox_view->subcell_views[i]);
+
+	g_free (vbox_view->model_cols);
+	g_free (vbox_view->subcell_views);
+	g_free (vbox_view);
+}
+
+/*
+ * ECell::realize method
+ */
+static void
+ecv_realize (ECellView *ecell_view)
+{
+	ECellVboxView *vbox_view = (ECellVboxView *) ecell_view;
+	gint i;
+
+	/* realize our subcell view */
+	for (i = 0; i < vbox_view->subcell_view_count; i++)
+		e_cell_realize (vbox_view->subcell_views[i]);
+
+	if (E_CELL_CLASS (e_cell_vbox_parent_class)->realize)
+		(* E_CELL_CLASS (e_cell_vbox_parent_class)->realize) (ecell_view);
+}
+
+/*
+ * ECell::unrealize method
+ */
+static void
+ecv_unrealize (ECellView *ecv)
+{
+	ECellVboxView *vbox_view = (ECellVboxView *) ecv;
+	gint i;
+
+	/* unrealize our subcell view. */
+	for (i = 0; i < vbox_view->subcell_view_count; i++)
+		e_cell_unrealize (vbox_view->subcell_views[i]);
+
+	if (E_CELL_CLASS (e_cell_vbox_parent_class)->unrealize)
+		(* E_CELL_CLASS (e_cell_vbox_parent_class)->unrealize) (ecv);
+}
+
+/*
+ * ECell::draw method
+ */
+static void
+ecv_draw (ECellView *ecell_view,
+          cairo_t *cr,
+          gint model_col,
+          gint view_col,
+          gint row,
+          ECellFlags flags,
+          gint x1,
+          gint y1,
+          gint x2,
+          gint y2)
+{
+	ECellVboxView *vbox_view = (ECellVboxView *) ecell_view;
+
+	gint subcell_offset = 0;
+	gint i;
+
+	for (i = 0; i < vbox_view->subcell_view_count; i++) {
+		/* Now cause our subcells to draw their contents,
+		 * shifted by subcell_offset pixels */
+		gint height;
+
+		height = e_cell_height (
+			vbox_view->subcell_views[i],
+			vbox_view->model_cols[i], view_col, row);
+		e_cell_draw (
+			vbox_view->subcell_views[i], cr,
+			vbox_view->model_cols[i], view_col, row, flags,
+			x1, y1 + subcell_offset, x2,
+			y1 + subcell_offset + height);
+
+		subcell_offset += e_cell_height (
+			vbox_view->subcell_views[i],
+			vbox_view->model_cols[i], view_col, row);
+	}
+}
+
+/*
+ * ECell::event method
+ */
+static gint
+ecv_event (ECellView *ecell_view,
+           GdkEvent *event,
+           gint model_col,
+           gint view_col,
+           gint row,
+           ECellFlags flags,
+           ECellActions *actions)
+{
+	ECellVboxView *vbox_view = (ECellVboxView *) ecell_view;
+	gint y = 0;
+	gint i;
+	gint subcell_offset = 0;
+
+	switch (event->type) {
+	case GDK_BUTTON_PRESS:
+	case GDK_BUTTON_RELEASE:
+	case GDK_2BUTTON_PRESS:
+	case GDK_3BUTTON_PRESS:
+		y = event->button.y;
+		break;
+	case GDK_MOTION_NOTIFY:
+		y = event->motion.y;
+		break;
+	default:
+		/* nada */
+		break;
+	}
+
+	for (i = 0; i < vbox_view->subcell_view_count; i++) {
+		gint height = e_cell_height (vbox_view->subcell_views[i], vbox_view->model_cols[i], view_col, row);
+		if (y < subcell_offset + height)
+			return e_cell_event (vbox_view->subcell_views[i], event, vbox_view->model_cols[i], view_col, row, flags, actions);
+		subcell_offset += height;
+	}
+	return 0;
+}
+
+/*
+ * ECell::height method
+ */
+static gint
+ecv_height (ECellView *ecell_view,
+            gint model_col,
+            gint view_col,
+            gint row)
+{
+	ECellVboxView *vbox_view = (ECellVboxView *) ecell_view;
+	gint height = 0;
+	gint i;
+
+	for (i = 0; i < vbox_view->subcell_view_count; i++) {
+		height += e_cell_height (vbox_view->subcell_views[i], vbox_view->model_cols[i], view_col, row);
+	}
+	return height;
+}
+
+/*
+ * ECell::max_width method
+ */
+static gint
+ecv_max_width (ECellView *ecell_view,
+               gint model_col,
+               gint view_col)
+{
+	ECellVboxView *vbox_view = (ECellVboxView *) ecell_view;
+	gint max_width = 0;
+	gint i;
+
+	for (i = 0; i < vbox_view->subcell_view_count; i++) {
+		gint width = e_cell_max_width (vbox_view->subcell_views[i], vbox_view->model_cols[i], view_col);
+		max_width = MAX (width, max_width);
+	}
+
+	return max_width;
+}
+
+/*
+ * GObject::dispose method
+ */
+static void
+ecv_dispose (GObject *object)
+{
+	ECellVbox *ecv = E_CELL_VBOX (object);
+	gint i;
+
+	/* destroy our subcell */
+	for (i = 0; i < ecv->subcell_count; i++)
+		if (ecv->subcells[i])
+			g_object_unref (ecv->subcells[i]);
+	g_free (ecv->subcells);
+	ecv->subcells = NULL;
+	ecv->subcell_count = 0;
+
+	G_OBJECT_CLASS (e_cell_vbox_parent_class)->dispose (object);
+}
+
+static void
+ecv_finalize (GObject *object)
+{
+	ECellVbox *ecv = E_CELL_VBOX (object);
+
+	g_free (ecv->model_cols);
+
+	G_OBJECT_CLASS (e_cell_vbox_parent_class)->finalize (object);
+}
+
+static void
+e_cell_vbox_class_init (ECellVboxClass *class)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (class);
+	ECellClass *ecc = E_CELL_CLASS (class);
+
+	object_class->dispose = ecv_dispose;
+	object_class->finalize = ecv_finalize;
+
+	ecc->new_view         = ecv_new_view;
+	ecc->kill_view        = ecv_kill_view;
+	ecc->realize          = ecv_realize;
+	ecc->unrealize        = ecv_unrealize;
+	ecc->draw             = ecv_draw;
+	ecc->event            = ecv_event;
+	ecc->height           = ecv_height;
+	ecc->max_width        = ecv_max_width;
+
+	gal_a11y_e_cell_registry_add_cell_type (NULL, E_TYPE_CELL_VBOX, gal_a11y_e_cell_vbox_new);
+}
+
+static void
+e_cell_vbox_init (ECellVbox *ecv)
+{
+	ecv->subcells = NULL;
+	ecv->subcell_count = 0;
+}
+
+/**
+ * e_cell_vbox_new:
+ *
+ * Creates a new ECell renderer that can be used to render multiple
+ * child cells.
+ *
+ * Return value: an ECell object that can be used to render multiple
+ * child cells.
+ **/
+ECell *
+e_cell_vbox_new (void)
+{
+	return g_object_new (E_TYPE_CELL_VBOX, NULL);
+}
+
+void
+e_cell_vbox_append (ECellVbox *vbox,
+                    ECell *subcell,
+                    gint model_col)
+{
+	vbox->subcell_count++;
+
+	vbox->subcells   = g_renew (ECell *, vbox->subcells,   vbox->subcell_count);
+	vbox->model_cols = g_renew (int,     vbox->model_cols, vbox->subcell_count);
+
+	vbox->subcells[vbox->subcell_count - 1]   = subcell;
+	vbox->model_cols[vbox->subcell_count - 1] = model_col;
+
+	if (subcell)
+		g_object_ref_sink (subcell);
+}
diff --git a/e-util/e-cell-vbox.h b/e-util/e-cell-vbox.h
new file mode 100644
index 0000000..690d78f
--- /dev/null
+++ b/e-util/e-cell-vbox.h
@@ -0,0 +1,93 @@
+/*
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Toshok <toshok ximian com>
+ *		Chris Lahey  <clahey ximina com
+ *
+ * A majority of code taken from:
+ * the ECellText renderer.
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef _E_CELL_VBOX_H_
+#define _E_CELL_VBOX_H_
+
+#include <libgnomecanvas/libgnomecanvas.h>
+
+#include <e-util/e-cell.h>
+
+/* Standard GObject macros */
+#define E_TYPE_CELL_VBOX \
+	(e_cell_vbox_get_type ())
+#define E_CELL_VBOX(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_CELL_VBOX, ECellVbox))
+#define E_CELL_VBOX_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_CELL_VBOX, ECellVboxClass))
+#define E_IS_CELL_VBOX(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_CELL_VBOX))
+#define E_IS_CELL_VBOX_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_CELL_VBOX))
+#define E_CELL_VBOX_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_CELL_VBOX, ECellVboxClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ECellVbox ECellVbox;
+typedef struct _ECellVboxView ECellVboxView;
+typedef struct _ECellVboxClass ECellVboxClass;
+
+struct _ECellVbox {
+	ECell parent;
+
+	gint subcell_count;
+	ECell **subcells;
+	gint *model_cols;
+};
+
+struct _ECellVboxView  {
+	ECellView cell_view;
+
+	gint subcell_view_count;
+	ECellView **subcell_views;
+	gint *model_cols;
+};
+
+struct _ECellVboxClass {
+	ECellClass parent_class;
+};
+
+GType		e_cell_vbox_get_type		(void) G_GNUC_CONST;
+ECell *		e_cell_vbox_new			(void);
+void		e_cell_vbox_append		(ECellVbox *vbox,
+						 ECell *subcell,
+						 gint model_col);
+
+G_END_DECLS
+
+#endif /* _E_CELL_VBOX_H_ */
diff --git a/e-util/e-cell.c b/e-util/e-cell.c
new file mode 100644
index 0000000..34046e1
--- /dev/null
+++ b/e-util/e-cell.c
@@ -0,0 +1,679 @@
+/*
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Miguel de Icaza <miguel ximian com>
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <gtk/gtk.h>
+
+#include "e-cell.h"
+
+G_DEFINE_TYPE (ECell, e_cell, G_TYPE_OBJECT)
+
+static ECellView *
+ec_new_view (ECell *ecell,
+             ETableModel *table_model,
+             gpointer e_table_item_view)
+{
+	return NULL;
+}
+
+static void
+ec_realize (ECellView *e_cell)
+{
+}
+
+static void
+ec_kill_view (ECellView *ecell_view)
+{
+}
+
+static void
+ec_unrealize (ECellView *e_cell)
+{
+}
+
+static void
+ec_draw (ECellView *ecell_view,
+         cairo_t *cr,
+         gint model_col,
+         gint view_col,
+         gint row,
+         ECellFlags flags,
+         gint x1,
+         gint y1,
+         gint x2,
+         gint y2)
+{
+	g_critical ("e-cell-draw invoked");
+}
+
+static gint
+ec_event (ECellView *ecell_view,
+          GdkEvent *event,
+          gint model_col,
+          gint view_col,
+          gint row,
+          ECellFlags flags,
+          ECellActions *actions)
+{
+	g_critical ("e-cell-event invoked");
+
+	return 0;
+}
+
+static gint
+ec_height (ECellView *ecell_view,
+           gint model_col,
+           gint view_col,
+           gint row)
+{
+	g_critical ("e-cell-height invoked");
+
+	return 0;
+}
+
+static void
+ec_focus (ECellView *ecell_view,
+          gint model_col,
+          gint view_col,
+          gint row,
+          gint x1,
+          gint y1,
+          gint x2,
+          gint y2)
+{
+	ecell_view->focus_col = view_col;
+	ecell_view->focus_row = row;
+	ecell_view->focus_x1 = x1;
+	ecell_view->focus_y1 = y1;
+	ecell_view->focus_x2 = x2;
+	ecell_view->focus_y2 = y2;
+}
+
+static void
+ec_unfocus (ECellView *ecell_view)
+{
+	ecell_view->focus_col = -1;
+	ecell_view->focus_row = -1;
+	ecell_view->focus_x1 = -1;
+	ecell_view->focus_y1 = -1;
+	ecell_view->focus_x2 = -1;
+	ecell_view->focus_y2 = -1;
+}
+
+static gpointer
+ec_enter_edit (ECellView *ecell_view,
+               gint model_col,
+               gint view_col,
+               gint row)
+{
+	return NULL;
+}
+
+static void
+ec_leave_edit (ECellView *ecell_view,
+               gint model_col,
+               gint view_col,
+               gint row,
+               gpointer context)
+{
+}
+
+static gpointer
+ec_save_state (ECellView *ecell_view,
+               gint model_col,
+               gint view_col,
+               gint row,
+               gpointer context)
+{
+	return NULL;
+}
+
+static void
+ec_load_state (ECellView *ecell_view,
+               gint model_col,
+               gint view_col,
+               gint row,
+               gpointer context,
+               gpointer save_state)
+{
+}
+
+static void
+ec_free_state (ECellView *ecell_view,
+               gint model_col,
+               gint view_col,
+               gint row,
+               gpointer save_state)
+{
+}
+
+static void
+e_cell_class_init (ECellClass *class)
+{
+	class->realize = ec_realize;
+	class->unrealize = ec_unrealize;
+	class->new_view = ec_new_view;
+	class->kill_view = ec_kill_view;
+	class->draw = ec_draw;
+	class->event = ec_event;
+	class->focus = ec_focus;
+	class->unfocus = ec_unfocus;
+	class->height = ec_height;
+	class->enter_edit = ec_enter_edit;
+	class->leave_edit = ec_leave_edit;
+	class->save_state = ec_save_state;
+	class->load_state = ec_load_state;
+	class->free_state = ec_free_state;
+	class->print = NULL;
+	class->print_height = NULL;
+	class->max_width = NULL;
+	class->max_width_by_row = NULL;
+}
+
+static void
+e_cell_init (ECell *cell)
+{
+}
+
+/**
+ * e_cell_event:
+ * @ecell_view: The ECellView where the event will be dispatched
+ * @event: The GdkEvent.
+ * @model_col: the column in the model
+ * @view_col: the column in the view
+ * @row: the row
+ * @flags: flags about the current state
+ * @actions: a second return value in case the cell wants to take some action
+ *           (specifically grabbing & ungrabbing)
+ *
+ * Dispatches the event @event to the @ecell_view for.
+ *
+ * Returns: processing state from the GdkEvent handling.
+ */
+gint
+e_cell_event (ECellView *ecell_view,
+              GdkEvent *event,
+              gint model_col,
+              gint view_col,
+              gint row,
+              ECellFlags flags,
+              ECellActions *actions)
+{
+	ECellClass *class;
+
+	class = E_CELL_GET_CLASS (ecell_view->ecell);
+
+	return class->event (
+		ecell_view, event, model_col,
+		view_col, row, flags, actions);
+}
+
+/**
+ * e_cell_new_view:
+ * @ecell: the Ecell that will create the new view
+ * @table_model: the table model the ecell is bound to
+ * @e_table_item_view: an ETableItem object (the CanvasItem that
+ *                     reprensents the view of the table)
+ *
+ * ECell renderers new to be bound to a table_model and to the actual view
+ * during their life time to actually render the data.  This method is invoked
+ * by the ETableItem canvas item to instatiate a new view of the ECell.
+ *
+ * This is invoked when the ETableModel is attached to the ETableItem
+ * (a CanvasItem that can render ETableModels in the screen).
+ *
+ * Returns: a new ECellView for this @ecell on the @table_model displayed
+ * on the @e_table_item_view.
+ */
+ECellView *
+e_cell_new_view (ECell *ecell,
+                 ETableModel *table_model,
+                 gpointer e_table_item_view)
+{
+	return E_CELL_GET_CLASS (ecell)->new_view (
+		ecell, table_model, e_table_item_view);
+}
+
+/**
+ * e_cell_realize:
+ * @ecell_view: The ECellView to be realized.
+ *
+ * This function is invoked to give a chance to the ECellView to allocate
+ * any resources it needs from Gdk, equivalent to the GtkWidget::realize
+ * signal.
+ */
+void
+e_cell_realize (ECellView *ecell_view)
+{
+	ECellClass *class;
+
+	class = E_CELL_GET_CLASS (ecell_view->ecell);
+	g_return_if_fail (class->realize != NULL);
+
+	class->realize (ecell_view);
+}
+
+/**
+ * e_cell_kill_view:
+ * @ecell_view: view to be destroyed.
+ *
+ * This method it used to destroy a view of an ECell renderer
+ */
+void
+e_cell_kill_view (ECellView *ecell_view)
+{
+	ECellClass *class;
+
+	class = E_CELL_GET_CLASS (ecell_view->ecell);
+	g_return_if_fail (class->kill_view != NULL);
+
+	class->kill_view (ecell_view);
+}
+
+/**
+ * e_cell_unrealize:
+ * @ecell_view: The ECellView to be unrealized.
+ *
+ * This function is invoked to give a chance to the ECellView to
+ * release any resources it allocated during the realize method,
+ * equivalent to the GtkWidget::unrealize signal.
+ */
+void
+e_cell_unrealize (ECellView *ecell_view)
+{
+	ECellClass *class;
+
+	class = E_CELL_GET_CLASS (ecell_view->ecell);
+	g_return_if_fail (class->unrealize != NULL);
+
+	class->unrealize (ecell_view);
+}
+
+/**
+ * e_cell_draw:
+ * @ecell_view: the ECellView to redraw
+ * @cr: a Cairo context
+ * @model_col: the column in the model being drawn.
+ * @view_col: the column in the view being drawn (what the model maps to).
+ * @row: the row being drawn
+ * @flags: rendering flags.
+ * @x1: boudary for the rendering
+ * @y1: boudary for the rendering
+ * @x2: boudary for the rendering
+ * @y2: boudary for the rendering
+ *
+ * This instructs the ECellView to render itself into the Cairo context.
+ * The region to be drawn in given by (x1,y1)-(x2,y2).
+ *
+ * The most important flags are %E_CELL_SELECTED and %E_CELL_FOCUSED, other
+ * flags include alignments and justifications.
+ */
+void
+e_cell_draw (ECellView *ecell_view,
+             cairo_t *cr,
+             gint model_col,
+             gint view_col,
+             gint row,
+             ECellFlags flags,
+             gint x1,
+             gint y1,
+             gint x2,
+             gint y2)
+{
+	ECellClass *class;
+
+	g_return_if_fail (ecell_view != NULL);
+	g_return_if_fail (row >= 0);
+	g_return_if_fail (row < e_table_model_row_count (ecell_view->e_table_model));
+
+	class = E_CELL_GET_CLASS (ecell_view->ecell);
+	g_return_if_fail (class->draw != NULL);
+
+	cairo_save (cr);
+
+	class->draw (
+		ecell_view, cr,
+		model_col, view_col,
+		row, flags, x1, y1, x2, y2);
+
+	cairo_restore (cr);
+}
+
+/**
+ * e_cell_print:
+ * @ecell_view: the ECellView to redraw
+ * @context: The GtkPrintContext where we output our printed data.
+ * @model_col: the column in the model being drawn.
+ * @view_col: the column in the view being drawn (what the model maps to).
+ * @row: the row being drawn
+ * @width: width
+ * @height: height
+ *
+ * FIXME:
+ */
+void
+e_cell_print (ECellView *ecell_view,
+              GtkPrintContext *context,
+              gint model_col,
+              gint view_col,
+              gint row,
+              gdouble width,
+              gdouble height)
+{
+	ECellClass *class;
+
+	class = E_CELL_GET_CLASS (ecell_view->ecell);
+
+	if (class->print != NULL)
+		class->print (
+			ecell_view, context,
+			model_col, view_col,
+			row, width, height);
+}
+
+/**
+ * e_cell_print:
+ *
+ * FIXME:
+ */
+gdouble
+e_cell_print_height (ECellView *ecell_view,
+                     GtkPrintContext *context,
+                     gint model_col,
+                     gint view_col,
+                     gint row,
+                     gdouble width)
+{
+	ECellClass *class;
+
+	class = E_CELL_GET_CLASS (ecell_view->ecell);
+
+	if (class->print_height == NULL)
+		return 0.0;
+
+	return class->print_height (
+		ecell_view, context,
+		model_col, view_col,
+		row, width);
+}
+
+/**
+ * e_cell_height:
+ * @ecell_view: the ECellView.
+ * @model_col: the column in the model
+ * @view_col: the column in the view.
+ * @row: the row to me measured
+ *
+ * Returns: the height of the cell at @model_col, @row rendered at
+ * @view_col, @row.
+ */
+gint
+e_cell_height (ECellView *ecell_view,
+               gint model_col,
+               gint view_col,
+               gint row)
+{
+	ECellClass *class;
+
+	class = E_CELL_GET_CLASS (ecell_view->ecell);
+	g_return_val_if_fail (class->height != NULL, 0);
+
+	return class->height (ecell_view, model_col, view_col, row);
+}
+
+/**
+ * e_cell_enter_edit:
+ * @ecell_view: the ECellView that will enter editing
+ * @model_col: the column in the model
+ * @view_col: the column in the view
+ * @row: the row
+ *
+ * Notifies the ECellView that it is about to enter editing mode for
+ * @model_col, @row rendered at @view_col, @row.
+ */
+gpointer
+e_cell_enter_edit (ECellView *ecell_view,
+                   gint model_col,
+                   gint view_col,
+                   gint row)
+{
+	ECellClass *class;
+
+	class = E_CELL_GET_CLASS (ecell_view->ecell);
+	g_return_val_if_fail (class->enter_edit != NULL, NULL);
+
+	return class->enter_edit (ecell_view, model_col, view_col, row);
+}
+
+/**
+ * e_cell_leave_edit:
+ * @ecell_view: the ECellView that will leave editing
+ * @model_col: the column in the model
+ * @view_col: the column in the view
+ * @row: the row
+ * @edit_context: the editing context
+ *
+ * Notifies the ECellView that editing is finished at @model_col, @row
+ * rendered at @view_col, @row.
+ */
+void
+e_cell_leave_edit (ECellView *ecell_view,
+                   gint model_col,
+                   gint view_col,
+                   gint row,
+                   gpointer edit_context)
+{
+	ECellClass *class;
+
+	class = E_CELL_GET_CLASS (ecell_view->ecell);
+	g_return_if_fail (class->leave_edit != NULL);
+
+	class->leave_edit (ecell_view, model_col, view_col, row, edit_context);
+}
+
+/**
+ * e_cell_save_state:
+ * @ecell_view: the ECellView to save
+ * @model_col: the column in the model
+ * @view_col: the column in the view
+ * @row: the row
+ * @edit_context: the editing context
+ *
+ * Returns: The save state.
+ *
+ * Requests that the ECellView return a gpointer  representing the state
+ * of the ECell.  This is primarily intended for things like selection
+ * or scrolling.
+ */
+gpointer
+e_cell_save_state (ECellView *ecell_view,
+                   gint model_col,
+                   gint view_col,
+                   gint row,
+                   gpointer edit_context)
+{
+	ECellClass *class;
+
+	class = E_CELL_GET_CLASS (ecell_view->ecell);
+
+	if (class->save_state == NULL)
+		return NULL;
+
+	return class->save_state (
+		ecell_view, model_col, view_col, row, edit_context);
+}
+
+/**
+ * e_cell_load_state:
+ * @ecell_view: the ECellView to load
+ * @model_col: the column in the model
+ * @view_col: the column in the view
+ * @row: the row
+ * @edit_context: the editing context
+ * @save_state: the save state to load from
+ *
+ * Requests that the ECellView load from the given save state.
+ */
+void
+e_cell_load_state (ECellView *ecell_view,
+                   gint model_col,
+                   gint view_col,
+                   gint row,
+                   gpointer edit_context,
+                   gpointer save_state)
+{
+	ECellClass *class;
+
+	class = E_CELL_GET_CLASS (ecell_view->ecell);
+
+	if (class->load_state != NULL)
+		class->load_state (
+			ecell_view, model_col, view_col,
+			row, edit_context, save_state);
+}
+
+/**
+ * e_cell_free_state:
+ * @ecell_view: the ECellView
+ * @model_col: the column in the model
+ * @view_col: the column in the view
+ * @row: the row
+ * @edit_context: the editing context
+ * @save_state: the save state to free
+ *
+ * Requests that the ECellView free the given save state.
+ */
+void
+e_cell_free_state (ECellView *ecell_view,
+                   gint model_col,
+                   gint view_col,
+                   gint row,
+                   gpointer save_state)
+{
+	ECellClass *class;
+
+	class = E_CELL_GET_CLASS (ecell_view->ecell);
+
+	if (class->free_state != NULL)
+		class->free_state (
+			ecell_view, model_col, view_col, row, save_state);
+}
+
+/**
+ * e_cell_max_width:
+ * @ecell_view: the ECellView that will leave editing
+ * @model_col: the column in the model
+ * @view_col: the column in the view.
+ *
+ * Returns: the maximum width for the ECellview at @model_col which
+ * is being rendered as @view_col
+ */
+gint
+e_cell_max_width (ECellView *ecell_view,
+                  gint model_col,
+                  gint view_col)
+{
+	ECellClass *class;
+
+	class = E_CELL_GET_CLASS (ecell_view->ecell);
+	g_return_val_if_fail (class->max_width != NULL, 0);
+
+	return class->max_width (ecell_view, model_col, view_col);
+}
+
+/**
+ * e_cell_max_width_by_row:
+ * @ecell_view: the ECellView that we are curious about
+ * @model_col: the column in the model
+ * @view_col: the column in the view.
+ * @row: The row in the model.
+ *
+ * Returns: the maximum width for the ECellview at @model_col which
+ * is being rendered as @view_col for the data in @row.
+ */
+gint
+e_cell_max_width_by_row (ECellView *ecell_view,
+                         gint model_col,
+                         gint view_col,
+                         gint row)
+{
+	ECellClass *class;
+
+	class = E_CELL_GET_CLASS (ecell_view->ecell);
+
+	if (class->max_width_by_row == NULL)
+		return e_cell_max_width (ecell_view, model_col, view_col);
+
+	return class->max_width_by_row (ecell_view, model_col, view_col, row);
+}
+
+/**
+ * e_cell_max_width_by_row_implemented:
+ * @ecell_view: the ECellView that we are curious about
+ * @model_col: the column in the model
+ * @view_col: the column in the view.
+ * @row: The row in the model.
+ *
+ * Returns: the maximum width for the ECellview at @model_col which
+ * is being rendered as @view_col for the data in @row.
+ */
+gboolean
+e_cell_max_width_by_row_implemented (ECellView *ecell_view)
+{
+	ECellClass *class;
+
+	class = E_CELL_GET_CLASS (ecell_view->ecell);
+
+	return (class->max_width_by_row != NULL);
+}
+
+gchar *
+e_cell_get_bg_color (ECellView *ecell_view,
+                     gint row)
+{
+	ECellClass *class;
+
+	class = E_CELL_GET_CLASS (ecell_view->ecell);
+
+	if (class->get_bg_color == NULL)
+		return NULL;
+
+	return class->get_bg_color (ecell_view, row);
+}
+
+void
+e_cell_style_set (ECellView *ecell_view,
+                  GtkStyle *previous_style)
+{
+	ECellClass *class;
+
+	class = E_CELL_GET_CLASS (ecell_view->ecell);
+
+	if (class->style_set != NULL)
+		class->style_set (ecell_view, previous_style);
+}
+
diff --git a/e-util/e-cell.h b/e-util/e-cell.h
new file mode 100644
index 0000000..4c13542
--- /dev/null
+++ b/e-util/e-cell.h
@@ -0,0 +1,299 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Miguel de Icaza <miguel ximian com>
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef _E_CELL_H_
+#define _E_CELL_H_
+
+#include <gtk/gtk.h>
+
+#include <e-util/e-table-model.h>
+
+/* Standard GObject macros */
+#define E_TYPE_CELL \
+	(e_cell_get_type ())
+#define E_CELL(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_CELL, ECell))
+#define E_CELL_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_CELL, ECellClass))
+#define E_CELL_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_CELL, ECellClass))
+#define E_IS_CELL(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_CELL))
+#define E_IS_CELL_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_CELL))
+#define E_CELL_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_CELL, ECellClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ECell ECell;
+typedef struct _ECellClass ECellClass;
+typedef struct _ECellView ECellView;
+
+typedef gboolean (*ETableSearchFunc) (gconstpointer haystack,
+				      const gchar *needle);
+
+typedef enum {
+	E_CELL_SELECTED       = 1 << 0,
+
+	E_CELL_JUSTIFICATION  = 3 << 1,
+	E_CELL_JUSTIFY_CENTER = 0 << 1,
+	E_CELL_JUSTIFY_LEFT   = 1 << 1,
+	E_CELL_JUSTIFY_RIGHT  = 2 << 1,
+	E_CELL_JUSTIFY_FILL   = 3 << 1,
+
+	E_CELL_ALIGN_LEFT     = 1 << 1,
+	E_CELL_ALIGN_RIGHT    = 1 << 2,
+
+	E_CELL_FOCUSED        = 1 << 3,
+
+	E_CELL_EDITING        = 1 << 4,
+
+	E_CELL_CURSOR         = 1 << 5,
+
+	E_CELL_PREEDIT        = 1 << 6
+} ECellFlags;
+
+typedef enum {
+	E_CELL_GRAB           = 1 << 0,
+	E_CELL_UNGRAB         = 1 << 1
+} ECellActions;
+
+struct _ECellView {
+	ECell *ecell;
+	ETableModel *e_table_model;
+	void        *e_table_item_view;
+
+	gint   focus_x1, focus_y1, focus_x2, focus_y2;
+	gint   focus_col, focus_row;
+
+	void  (*kill_view_cb) (struct _ECellView *, gpointer);
+	GList *kill_view_cb_data;
+};
+
+#define E_CELL_IS_FOCUSED(ecell_view) (ecell_view->focus_x1 != -1)
+
+struct _ECell {
+	GObject parent;
+};
+
+struct _ECellClass {
+	GObjectClass parent_class;
+
+	ECellView *	(*new_view)		(ECell *ecell,
+						 ETableModel *table_model,
+						 gpointer e_table_item_view);
+	void		(*kill_view)		(ECellView *ecell_view);
+
+	void		(*realize)		(ECellView *ecell_view);
+	void		(*unrealize)		(ECellView *ecell_view);
+
+	void		(*draw)			(ECellView *ecell_view,
+						 cairo_t *cr,
+						 gint model_col,
+						 gint view_col, gint row,
+						 ECellFlags flags,
+						 gint x1,
+						 gint y1,
+						 gint x2,
+						 gint y2);
+	gint		(*event)		(ECellView *ecell_view,
+						 GdkEvent *event,
+						 gint model_col,
+						 gint view_col,
+						 gint row,
+						 ECellFlags flags,
+						 ECellActions *actions);
+	void		(*focus)		(ECellView *ecell_view,
+						 gint model_col,
+						 gint view_col,
+						 gint row,
+						 gint x1,
+						 gint y1,
+						 gint x2,
+						 gint y2);
+	void		(*unfocus)		(ECellView *ecell_view);
+	gint		(*height)		(ECellView *ecell_view,
+						 gint model_col,
+						 gint view_col,
+						 gint row);
+
+	gpointer	(*enter_edit)		(ECellView *ecell_view,
+						 gint model_col,
+						 gint view_col,
+						 gint row);
+	void		(*leave_edit)		(ECellView *ecell_view,
+						 gint model_col,
+						 gint view_col,
+						 gint row,
+						 gpointer context);
+	gpointer	(*save_state)		(ECellView *ecell_view,
+						 gint model_col,
+						 gint view_col,
+						 gint row,
+						 gpointer context);
+	void		(*load_state)		(ECellView *ecell_view,
+						 gint model_col,
+						 gint view_col,
+						 gint row,
+						 gpointer context,
+						 gpointer save_state);
+	void		(*free_state)		(ECellView *ecell_view,
+						 gint model_col,
+						 gint view_col,
+						 gint row,
+						 gpointer save_state);
+	void		(*print)		(ECellView *ecell_view,
+						 GtkPrintContext *context,
+						 gint model_col,
+						 gint view_col,
+						 gint row,
+						 gdouble width,
+						 gdouble height);
+	gdouble		(*print_height)		(ECellView *ecell_view,
+						 GtkPrintContext *context,
+						 gint model_col,
+						 gint view_col,
+						 gint row,
+						 gdouble width);
+	gint		(*max_width)		(ECellView *ecell_view,
+						 gint model_col,
+						 gint view_col);
+	gint		(*max_width_by_row)	(ECellView *ecell_view,
+						 gint model_col,
+						 gint view_col,
+						 gint row);
+	gchar *		(*get_bg_color)		(ECellView *ecell_view,
+						 gint row);
+
+	void		(*style_set)		(ECellView *ecell_view,
+						 GtkStyle *previous_style);
+};
+
+GType		e_cell_get_type			(void) G_GNUC_CONST;
+
+/* View creation methods. */
+ECellView *	e_cell_new_view			(ECell *ecell,
+						 ETableModel *table_model,
+						 gpointer e_table_item_view);
+void		e_cell_kill_view		(ECellView *ecell_view);
+
+/* Cell View methods. */
+gint		e_cell_event			(ECellView *ecell_view,
+						 GdkEvent *event,
+						 gint model_col,
+						 gint view_col,
+						 gint row,
+						 ECellFlags flags,
+						 ECellActions *actions);
+void		e_cell_realize			(ECellView *ecell_view);
+void		e_cell_unrealize		(ECellView *ecell_view);
+void		e_cell_draw			(ECellView *ecell_view,
+						 cairo_t *cr,
+						 gint model_col,
+						 gint view_col,
+						 gint row,
+						 ECellFlags flags,
+						 gint x1,
+						 gint y1,
+						 gint x2,
+						 gint y2);
+void		e_cell_print			(ECellView *ecell_view,
+						 GtkPrintContext *context,
+						 gint model_col,
+						 gint view_col,
+						 gint row,
+						 gdouble width,
+						 gdouble height);
+gdouble		e_cell_print_height		(ECellView *ecell_view,
+						 GtkPrintContext *context,
+						 gint model_col,
+						 gint view_col,
+						 gint row,
+						 gdouble width);
+gint		e_cell_max_width		(ECellView *ecell_view,
+						 gint model_col,
+						 gint view_col);
+gint		e_cell_max_width_by_row		(ECellView *ecell_view,
+						 gint model_col,
+						 gint view_col,
+						 gint row);
+gboolean	e_cell_max_width_by_row_implemented
+						(ECellView *ecell_view);
+gchar *		e_cell_get_bg_color		(ECellView *ecell_view,
+						 gint row);
+void		e_cell_style_set		(ECellView *ecell_view,
+						 GtkStyle *previous_style);
+
+void		e_cell_focus			(ECellView *ecell_view,
+						 gint model_col,
+						 gint view_col,
+						 gint row,
+						 gint x1,
+						 gint y1,
+						 gint x2,
+						 gint y2);
+void		e_cell_unfocus			(ECellView *ecell_view);
+gint		e_cell_height			(ECellView *ecell_view,
+						 gint model_col,
+						 gint view_col,
+						 gint row);
+gpointer	e_cell_enter_edit		(ECellView *ecell_view,
+						 gint model_col,
+						 gint view_col,
+						 gint row);
+void		e_cell_leave_edit		(ECellView *ecell_view,
+						 gint model_col,
+						 gint view_col,
+						 gint row,
+						 gpointer edit_context);
+gpointer	e_cell_save_state		(ECellView *ecell_view,
+						 gint model_col,
+						 gint view_col,
+						 gint row,
+						 gpointer edit_context);
+void		e_cell_load_state		(ECellView *ecell_view,
+						 gint model_col,
+						 gint view_col,
+						 gint row,
+						 gpointer edit_context,
+						 gpointer state);
+void		e_cell_free_state		(ECellView *ecell_view,
+						 gint model_col,
+						 gint view_col,
+						 gint row,
+						 gpointer state);
+
+G_END_DECLS
+
+#endif /* _E_CELL_H_ */
diff --git a/e-util/e-charset-combo-box.c b/e-util/e-charset-combo-box.c
new file mode 100644
index 0000000..1423a59
--- /dev/null
+++ b/e-util/e-charset-combo-box.c
@@ -0,0 +1,407 @@
+/*
+ * e-charset-combo-box.c
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-charset-combo-box.h"
+
+#include <glib/gi18n.h>
+
+#include "e-charset.h"
+#include "e-misc-utils.h"
+
+#define E_CHARSET_COMBO_BOX_GET_PRIVATE(obj) \
+	(G_TYPE_INSTANCE_GET_PRIVATE \
+	((obj), E_TYPE_CHARSET_COMBO_BOX, ECharsetComboBoxPrivate))
+
+#define DEFAULT_CHARSET "UTF-8"
+#define OTHER_VALUE G_MAXINT
+
+struct _ECharsetComboBoxPrivate {
+	GtkActionGroup *action_group;
+	GtkRadioAction *other_action;
+	GHashTable *charset_index;
+
+	/* Used when the user clicks Cancel in the character set
+	 * dialog. Reverts to the previous combo box setting. */
+	gint previous_index;
+
+	/* When setting the character set programmatically, this
+	 * prevents the custom character set dialog from running. */
+	guint block_dialog : 1;
+};
+
+enum {
+	PROP_0,
+	PROP_CHARSET
+};
+
+G_DEFINE_TYPE (
+	ECharsetComboBox,
+	e_charset_combo_box,
+	E_TYPE_ACTION_COMBO_BOX)
+
+static void
+charset_combo_box_entry_changed_cb (GtkEntry *entry,
+                                    GtkDialog *dialog)
+{
+	const gchar *text;
+	gboolean sensitive;
+
+	text = gtk_entry_get_text (entry);
+	sensitive = (text != NULL && *text != '\0');
+	gtk_dialog_set_response_sensitive (dialog, GTK_RESPONSE_OK, sensitive);
+}
+
+static void
+charset_combo_box_run_dialog (ECharsetComboBox *combo_box)
+{
+	GtkDialog *dialog;
+	GtkEntry *entry;
+	GtkWidget *container;
+	GtkWidget *widget;
+	GObject *object;
+	gpointer parent;
+	const gchar *charset;
+
+	/* FIXME Using a dialog for this is lame.  Selecting "Other..."
+	 *       should unlock an entry directly in the Preferences tab.
+	 *       Unfortunately space in Preferences is at a premium right
+	 *       now, but we should revisit this when the space issue is
+	 *       finally resolved. */
+
+	parent = gtk_widget_get_toplevel (GTK_WIDGET (combo_box));
+	parent = gtk_widget_is_toplevel (parent) ? parent : NULL;
+
+	object = G_OBJECT (combo_box->priv->other_action);
+	charset = g_object_get_data (object, "charset");
+
+	widget = gtk_dialog_new_with_buttons (
+		_("Character Encoding"), parent,
+		GTK_DIALOG_DESTROY_WITH_PARENT,
+		GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+		GTK_STOCK_OK, GTK_RESPONSE_OK, NULL);
+
+	/* Load the broken border width defaults so we can override them. */
+	gtk_widget_ensure_style (widget);
+
+	dialog = GTK_DIALOG (widget);
+
+	gtk_dialog_set_default_response (dialog, GTK_RESPONSE_OK);
+
+	gtk_container_set_border_width (GTK_CONTAINER (dialog), 12);
+
+	widget = gtk_dialog_get_action_area (dialog);
+	gtk_container_set_border_width (GTK_CONTAINER (widget), 0);
+
+	widget = gtk_dialog_get_content_area (dialog);
+	gtk_box_set_spacing (GTK_BOX (widget), 12);
+	gtk_container_set_border_width (GTK_CONTAINER (widget), 0);
+
+	container = widget;
+
+	widget = gtk_label_new (_("Enter the character set to use"));
+	gtk_label_set_line_wrap (GTK_LABEL (widget), TRUE);
+	gtk_misc_set_alignment (GTK_MISC (widget), 0.0, 0.5);
+	gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+	gtk_widget_show (widget);
+
+	widget = gtk_alignment_new (0.0, 0.0, 1.0, 1.0);
+	gtk_alignment_set_padding (GTK_ALIGNMENT (widget), 0, 0, 12, 0);
+	gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+	gtk_widget_show (widget);
+
+	container = widget;
+
+	widget = gtk_entry_new ();
+	entry = GTK_ENTRY (widget);
+	gtk_entry_set_activates_default (entry, TRUE);
+	gtk_container_add (GTK_CONTAINER (container), widget);
+	gtk_widget_show (widget);
+
+	g_signal_connect (
+		entry, "changed",
+		G_CALLBACK (charset_combo_box_entry_changed_cb), dialog);
+
+	/* Set the default text -after- connecting the signal handler.
+	 * This will initialize the "OK" button to the proper state. */
+	gtk_entry_set_text (entry, charset);
+
+	if (gtk_dialog_run (dialog) != GTK_RESPONSE_OK) {
+		gint active;
+
+		/* Revert to the previously selected character set. */
+		combo_box->priv->block_dialog = TRUE;
+		active = combo_box->priv->previous_index;
+		gtk_combo_box_set_active (GTK_COMBO_BOX (combo_box), active);
+		combo_box->priv->block_dialog = FALSE;
+
+		goto exit;
+	}
+
+	charset = gtk_entry_get_text (entry);
+	g_return_if_fail (charset != NULL && charset != '\0');
+
+	g_object_set_data_full (
+		object, "charset", g_strdup (charset),
+		(GDestroyNotify) g_free);
+
+exit:
+	gtk_widget_destroy (GTK_WIDGET (dialog));
+}
+
+static void
+charset_combo_box_notify_charset_cb (ECharsetComboBox *combo_box)
+{
+	GtkToggleAction *action;
+
+	action = GTK_TOGGLE_ACTION (combo_box->priv->other_action);
+	if (!gtk_toggle_action_get_active (action))
+		return;
+
+	if (combo_box->priv->block_dialog)
+		return;
+
+	/* "Other" action was selected by user. */
+	charset_combo_box_run_dialog (combo_box);
+}
+
+static void
+charset_combo_box_set_property (GObject *object,
+                                guint property_id,
+                                const GValue *value,
+                                GParamSpec *pspec)
+{
+	switch (property_id) {
+		case PROP_CHARSET:
+			e_charset_combo_box_set_charset (
+				E_CHARSET_COMBO_BOX (object),
+				g_value_get_string (value));
+			return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+charset_combo_box_get_property (GObject *object,
+                                guint property_id,
+                                GValue *value,
+                                GParamSpec *pspec)
+{
+	switch (property_id) {
+		case PROP_CHARSET:
+			g_value_set_string (
+				value, e_charset_combo_box_get_charset (
+				E_CHARSET_COMBO_BOX (object)));
+			return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+charset_combo_box_dispose (GObject *object)
+{
+	ECharsetComboBoxPrivate *priv;
+
+	priv = E_CHARSET_COMBO_BOX_GET_PRIVATE (object);
+
+	if (priv->action_group != NULL) {
+		g_object_unref (priv->action_group);
+		priv->action_group = NULL;
+	}
+
+	if (priv->other_action != NULL) {
+		g_object_unref (priv->other_action);
+		priv->other_action = NULL;
+	}
+
+	g_hash_table_remove_all (priv->charset_index);
+
+	/* Chain up to parent's dispose() method. */
+	G_OBJECT_CLASS (e_charset_combo_box_parent_class)->dispose (object);
+}
+
+static void
+charset_combo_box_finalize (GObject *object)
+{
+	ECharsetComboBoxPrivate *priv;
+
+	priv = E_CHARSET_COMBO_BOX_GET_PRIVATE (object);
+
+	g_hash_table_destroy (priv->charset_index);
+
+	/* Chain up to parent's finalize() method. */
+	G_OBJECT_CLASS (e_charset_combo_box_parent_class)->finalize (object);
+}
+
+static void
+charset_combo_box_changed (GtkComboBox *combo_box)
+{
+	ECharsetComboBoxPrivate *priv;
+
+	priv = E_CHARSET_COMBO_BOX_GET_PRIVATE (combo_box);
+
+	/* Chain up to parent's changed() method. */
+	GTK_COMBO_BOX_CLASS (e_charset_combo_box_parent_class)->
+		changed (combo_box);
+
+	/* Notify -before- updating previous index. */
+	g_object_notify (G_OBJECT (combo_box), "charset");
+	priv->previous_index = gtk_combo_box_get_active (combo_box);
+}
+
+static void
+e_charset_combo_box_class_init (ECharsetComboBoxClass *class)
+{
+	GObjectClass *object_class;
+	GtkComboBoxClass *combo_box_class;
+
+	g_type_class_add_private (class, sizeof (ECharsetComboBoxPrivate));
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->set_property = charset_combo_box_set_property;
+	object_class->get_property = charset_combo_box_get_property;
+	object_class->dispose = charset_combo_box_dispose;
+	object_class->finalize = charset_combo_box_finalize;
+
+	combo_box_class = GTK_COMBO_BOX_CLASS (class);
+	combo_box_class->changed = charset_combo_box_changed;
+
+	g_object_class_install_property (
+		object_class,
+		PROP_CHARSET,
+		g_param_spec_string (
+			"charset",
+			"Charset",
+			"The selected character set",
+			"UTF-8",
+			G_PARAM_READWRITE |
+			G_PARAM_CONSTRUCT));
+}
+
+static void
+e_charset_combo_box_init (ECharsetComboBox *combo_box)
+{
+	GtkActionGroup *action_group;
+	GtkRadioAction *radio_action;
+	GHashTable *charset_index;
+	GSList *group, *iter;
+
+	action_group = gtk_action_group_new ("charset-combo-box-internal");
+
+	charset_index = g_hash_table_new_full (
+		g_str_hash, g_str_equal,
+		(GDestroyNotify) g_free,
+		(GDestroyNotify) g_object_unref);
+
+	combo_box->priv = E_CHARSET_COMBO_BOX_GET_PRIVATE (combo_box);
+	combo_box->priv->action_group = action_group;
+	combo_box->priv->charset_index = charset_index;
+
+	group = e_charset_add_radio_actions (
+		action_group, "charset-", NULL, NULL, NULL);
+
+	/* Populate the character set index. */
+	for (iter = group; iter != NULL; iter = iter->next) {
+		GObject *object = iter->data;
+		const gchar *charset;
+
+		charset = g_object_get_data (object, "charset");
+		g_return_if_fail (charset != NULL);
+
+		g_hash_table_insert (
+			charset_index, g_strdup (charset),
+			g_object_ref (object));
+	}
+
+	/* Note the "other" action is not included in the index. */
+
+	radio_action = gtk_radio_action_new (
+		"charset-other", _("Other..."), NULL, NULL, OTHER_VALUE);
+
+	g_object_set_data (G_OBJECT (radio_action), "charset", (gpointer) "");
+
+	gtk_radio_action_set_group (radio_action, group);
+	group = gtk_radio_action_get_group (radio_action);
+
+	e_action_combo_box_set_action (
+		E_ACTION_COMBO_BOX (combo_box), radio_action);
+
+	e_action_combo_box_add_separator_after (
+		E_ACTION_COMBO_BOX (combo_box), g_slist_length (group));
+
+	g_signal_connect (
+		combo_box, "notify::charset",
+		G_CALLBACK (charset_combo_box_notify_charset_cb), NULL);
+
+	combo_box->priv->other_action = radio_action;
+}
+
+GtkWidget *
+e_charset_combo_box_new (void)
+{
+	return g_object_new (E_TYPE_CHARSET_COMBO_BOX, NULL);
+}
+
+const gchar *
+e_charset_combo_box_get_charset (ECharsetComboBox *combo_box)
+{
+	GtkRadioAction *radio_action;
+
+	g_return_val_if_fail (E_IS_CHARSET_COMBO_BOX (combo_box), NULL);
+
+	radio_action = combo_box->priv->other_action;
+	radio_action = e_radio_action_get_current_action (radio_action);
+
+	return g_object_get_data (G_OBJECT (radio_action), "charset");
+}
+
+void
+e_charset_combo_box_set_charset (ECharsetComboBox *combo_box,
+                                 const gchar *charset)
+{
+	GHashTable *charset_index;
+	GtkRadioAction *radio_action;
+
+	g_return_if_fail (E_IS_CHARSET_COMBO_BOX (combo_box));
+
+	if (charset == NULL || *charset == '\0')
+		charset = "UTF-8";
+
+	charset_index = combo_box->priv->charset_index;
+	radio_action = g_hash_table_lookup (charset_index, charset);
+
+	if (radio_action == NULL) {
+		radio_action = combo_box->priv->other_action;
+		g_object_set_data_full (
+			G_OBJECT (radio_action),
+			"charset", g_strdup (charset),
+			(GDestroyNotify) g_free);
+	}
+
+	combo_box->priv->block_dialog = TRUE;
+	gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (radio_action), TRUE);
+	combo_box->priv->block_dialog = FALSE;
+}
diff --git a/e-util/e-charset-combo-box.h b/e-util/e-charset-combo-box.h
new file mode 100644
index 0000000..54c5527
--- /dev/null
+++ b/e-util/e-charset-combo-box.h
@@ -0,0 +1,73 @@
+/*
+ * e-charset-combo-box.h
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_CHARSET_COMBO_BOX_H
+#define E_CHARSET_COMBO_BOX_H
+
+#include <e-util/e-action-combo-box.h>
+
+/* Standard GObject macros */
+#define E_TYPE_CHARSET_COMBO_BOX \
+	(e_charset_combo_box_get_type ())
+#define E_CHARSET_COMBO_BOX(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_CHARSET_COMBO_BOX, ECharsetComboBox))
+#define E_CHARSET_COMBO_BOX_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_CHARSET_COMBO_BOX, ECharsetComboBoxClass))
+#define E_IS_CHARSET_COMBO_BOX(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_CHARSET_COMBO_BOX))
+#define E_IS_CHARSET_COMBO_BOX_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_CHARSET_COMBO_BOX))
+#define E_CHARSET_COMBO_BOX_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_CHARSET_COMBO_BOX, ECharsetComboBoxClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ECharsetComboBox ECharsetComboBox;
+typedef struct _ECharsetComboBoxClass ECharsetComboBoxClass;
+typedef struct _ECharsetComboBoxPrivate ECharsetComboBoxPrivate;
+
+struct _ECharsetComboBox {
+	EActionComboBox parent;
+	ECharsetComboBoxPrivate *priv;
+};
+
+struct _ECharsetComboBoxClass {
+	EActionComboBoxClass parent_class;
+};
+
+GType		e_charset_combo_box_get_type	(void);
+GtkWidget *	e_charset_combo_box_new		(void);
+const gchar *	e_charset_combo_box_get_charset	(ECharsetComboBox *combo_box);
+void		e_charset_combo_box_set_charset	(ECharsetComboBox *combo_box,
+						 const gchar *charset);
+
+G_END_DECLS
+
+#endif /* E_CHARSET_COMBO_BOX_H */
diff --git a/e-util/e-charset.h b/e-util/e-charset.h
index 57b6976..29bdc50 100644
--- a/e-util/e-charset.h
+++ b/e-util/e-charset.h
@@ -18,6 +18,10 @@
  *
  */
 
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
 #ifndef E_CHARSET_H
 #define E_CHARSET_H
 
diff --git a/e-util/e-client-utils.c b/e-util/e-client-utils.c
new file mode 100644
index 0000000..ed0688b
--- /dev/null
+++ b/e-util/e-client-utils.c
@@ -0,0 +1,445 @@
+/*
+ * e-client-utils.c
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Copyright (C) 2011 Red Hat, Inc. (www.redhat.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <glib/gi18n-lib.h>
+#include <gtk/gtk.h>
+#include <libsoup/soup.h>
+
+#include <libebook/libebook.h>
+#include <libecal/libecal.h>
+
+#include "e-client-utils.h"
+
+/**
+ * e_client_utils_new:
+ *
+ * Proxy function for e_book_client_utils_new() and e_cal_client_utils_new().
+ *
+ * Since: 3.2
+ **/
+EClient	*
+e_client_utils_new (ESource *source,
+                    EClientSourceType source_type,
+                    GError **error)
+{
+	EClient *res = NULL;
+
+	g_return_val_if_fail (source != NULL, NULL);
+	g_return_val_if_fail (E_IS_SOURCE (source), NULL);
+
+	switch (source_type) {
+	case E_CLIENT_SOURCE_TYPE_CONTACTS:
+		res = E_CLIENT (e_book_client_new (source, error));
+		break;
+	case E_CLIENT_SOURCE_TYPE_EVENTS:
+		res = E_CLIENT (e_cal_client_new (source, E_CAL_CLIENT_SOURCE_TYPE_EVENTS, error));
+		break;
+	case E_CLIENT_SOURCE_TYPE_MEMOS:
+		res = E_CLIENT (e_cal_client_new (source, E_CAL_CLIENT_SOURCE_TYPE_MEMOS, error));
+		break;
+	case E_CLIENT_SOURCE_TYPE_TASKS:
+		res = E_CLIENT (e_cal_client_new (source, E_CAL_CLIENT_SOURCE_TYPE_TASKS, error));
+		break;
+	default:
+		g_return_val_if_reached (NULL);
+		break;
+	}
+
+	return res;
+}
+
+typedef struct _EClientUtilsAsyncOpData {
+	GAsyncReadyCallback callback;
+	gpointer user_data;
+	GCancellable *cancellable;
+	ESource *source;
+	EClient *client;
+	gboolean open_finished;
+	GError *opened_cb_error;
+	guint retry_open_id;
+	gboolean only_if_exists;
+	guint pending_properties_count;
+} EClientUtilsAsyncOpData;
+
+static void
+free_client_utils_async_op_data (EClientUtilsAsyncOpData *async_data)
+{
+	g_return_if_fail (async_data != NULL);
+	g_return_if_fail (async_data->cancellable != NULL);
+	g_return_if_fail (async_data->client != NULL);
+
+	if (async_data->retry_open_id)
+		g_source_remove (async_data->retry_open_id);
+
+	g_signal_handlers_disconnect_matched (async_data->cancellable, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, async_data);
+	g_signal_handlers_disconnect_matched (async_data->client, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, async_data);
+
+	if (async_data->opened_cb_error)
+		g_error_free (async_data->opened_cb_error);
+	g_object_unref (async_data->cancellable);
+	g_object_unref (async_data->client);
+	g_object_unref (async_data->source);
+	g_free (async_data);
+}
+
+static gboolean
+complete_async_op_in_idle_cb (gpointer user_data)
+{
+	GSimpleAsyncResult *simple = user_data;
+	gint run_main_depth;
+
+	g_return_val_if_fail (simple != NULL, FALSE);
+
+	run_main_depth = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (simple), "run-main-depth"));
+	if (run_main_depth < 1)
+		run_main_depth = 1;
+
+	/* do not receive in higher level than was initially run */
+	if (g_main_depth () > run_main_depth) {
+		return TRUE;
+	}
+
+	g_simple_async_result_complete (simple);
+	g_object_unref (simple);
+
+	return FALSE;
+}
+
+#define return_async_error_if_fail(expr, callback, user_data, src, source_tag) G_STMT_START {	\
+	if (G_LIKELY ((expr))) { } else {								\
+		GError *error;										\
+													\
+		error = g_error_new (E_CLIENT_ERROR, E_CLIENT_ERROR_INVALID_ARG,			\
+				"%s: assertion '%s' failed", G_STRFUNC, #expr);				\
+													\
+		return_async_error (error, callback, user_data, src, source_tag);		\
+		g_error_free (error);									\
+		return;											\
+	}												\
+	} G_STMT_END
+
+static void
+return_async_error (const GError *error,
+                    GAsyncReadyCallback callback,
+                    gpointer user_data,
+                    ESource *source,
+                    gpointer source_tag)
+{
+	GSimpleAsyncResult *simple;
+
+	g_return_if_fail (error != NULL);
+	g_return_if_fail (source_tag != NULL);
+
+	simple = g_simple_async_result_new (G_OBJECT (source), callback, user_data, source_tag);
+	g_simple_async_result_set_from_error (simple, error);
+
+	g_object_set_data (G_OBJECT (simple), "run-main-depth", GINT_TO_POINTER (g_main_depth ()));
+	g_idle_add (complete_async_op_in_idle_cb, simple);
+}
+
+static void
+client_utils_get_backend_property_cb (GObject *source_object,
+                                      GAsyncResult *result,
+                                      gpointer user_data)
+{
+	EClient *client = E_CLIENT (source_object);
+	EClientUtilsAsyncOpData *async_data = user_data;
+	GSimpleAsyncResult *simple;
+
+	g_return_if_fail (async_data != NULL);
+	g_return_if_fail (async_data->client != NULL);
+	g_return_if_fail (async_data->client == client);
+
+	if (result) {
+		gchar *prop_value = NULL;
+
+		if (e_client_get_backend_property_finish (client, result, &prop_value, NULL))
+			g_free (prop_value);
+
+		async_data->pending_properties_count--;
+		if (async_data->pending_properties_count)
+			return;
+	}
+
+	simple = g_simple_async_result_new (G_OBJECT (async_data->source), async_data->callback, async_data->user_data, e_client_utils_open_new);
+	g_simple_async_result_set_op_res_gpointer (simple, g_object_ref (async_data->client), g_object_unref);
+
+	g_object_set_data (G_OBJECT (simple), "run-main-depth", GINT_TO_POINTER (g_main_depth ()));
+	g_idle_add (complete_async_op_in_idle_cb, simple);
+
+	free_client_utils_async_op_data (async_data);
+}
+
+static void
+client_utils_capabilities_retrieved_cb (GObject *source_object,
+                                        GAsyncResult *result,
+                                        gpointer user_data)
+{
+	EClient *client = E_CLIENT (source_object);
+	EClientUtilsAsyncOpData *async_data = user_data;
+	gchar *capabilities = NULL;
+	gboolean caps_res;
+
+	g_return_if_fail (async_data != NULL);
+	g_return_if_fail (async_data->client != NULL);
+	g_return_if_fail (async_data->client == client);
+
+	caps_res = e_client_retrieve_capabilities_finish (client, result, &capabilities, NULL);
+	g_free (capabilities);
+
+	if (caps_res) {
+		async_data->pending_properties_count = 1;
+
+		/* precache backend properties */
+		if (E_IS_CAL_CLIENT (client)) {
+			async_data->pending_properties_count += 3;
+
+			e_client_get_backend_property (async_data->client, CAL_BACKEND_PROPERTY_CAL_EMAIL_ADDRESS, async_data->cancellable, client_utils_get_backend_property_cb, async_data);
+			e_client_get_backend_property (async_data->client, CAL_BACKEND_PROPERTY_ALARM_EMAIL_ADDRESS, async_data->cancellable, client_utils_get_backend_property_cb, async_data);
+			e_client_get_backend_property (async_data->client, CAL_BACKEND_PROPERTY_DEFAULT_OBJECT, async_data->cancellable, client_utils_get_backend_property_cb, async_data);
+		} else if (E_IS_BOOK_CLIENT (client)) {
+			async_data->pending_properties_count += 2;
+
+			e_client_get_backend_property (async_data->client, BOOK_BACKEND_PROPERTY_REQUIRED_FIELDS, async_data->cancellable, client_utils_get_backend_property_cb, async_data);
+			e_client_get_backend_property (async_data->client, BOOK_BACKEND_PROPERTY_SUPPORTED_FIELDS, async_data->cancellable, client_utils_get_backend_property_cb, async_data);
+		} else {
+			g_warn_if_reached ();
+			client_utils_get_backend_property_cb (source_object, NULL, async_data);
+			return;
+		}
+
+		e_client_get_backend_property (async_data->client, CLIENT_BACKEND_PROPERTY_CACHE_DIR, async_data->cancellable, client_utils_get_backend_property_cb, async_data);
+	} else {
+		client_utils_get_backend_property_cb (source_object, NULL, async_data);
+	}
+}
+
+static void
+client_utils_open_new_done (EClientUtilsAsyncOpData *async_data)
+{
+	g_return_if_fail (async_data != NULL);
+	g_return_if_fail (async_data->client != NULL);
+
+	/* retrieve capabilities just to have them cached on #EClient for later use */
+	e_client_retrieve_capabilities (async_data->client, async_data->cancellable, client_utils_capabilities_retrieved_cb, async_data);
+}
+
+static gboolean client_utils_retry_open_timeout_cb (gpointer user_data);
+static void client_utils_opened_cb (EClient *client, const GError *error, EClientUtilsAsyncOpData *async_data);
+
+static void
+finish_or_retry_open (EClientUtilsAsyncOpData *async_data,
+                      const GError *error)
+{
+	g_return_if_fail (async_data != NULL);
+
+	if (error && g_error_matches (error, E_CLIENT_ERROR, E_CLIENT_ERROR_BUSY)) {
+		/* postpone for 1/2 of a second, backend is busy now */
+		async_data->open_finished = FALSE;
+		async_data->retry_open_id = g_timeout_add (500, client_utils_retry_open_timeout_cb, async_data);
+	} else if (error) {
+		return_async_error (error, async_data->callback, async_data->user_data, async_data->source, e_client_utils_open_new);
+		free_client_utils_async_op_data (async_data);
+	} else {
+		client_utils_open_new_done (async_data);
+	}
+}
+
+static void
+client_utils_opened_cb (EClient *client,
+                        const GError *error,
+                        EClientUtilsAsyncOpData *async_data)
+{
+	g_return_if_fail (client != NULL);
+	g_return_if_fail (async_data != NULL);
+	g_return_if_fail (client == async_data->client);
+
+	g_signal_handlers_disconnect_by_func (client, G_CALLBACK (client_utils_opened_cb), async_data);
+
+	if (!async_data->open_finished) {
+		/* there can happen that the "opened" signal is received
+		 * before the e_client_open () is finished, thus keep detailed
+		 * error for later use, if any */
+		if (error)
+			async_data->opened_cb_error = g_error_copy (error);
+	} else {
+		finish_or_retry_open (async_data, error);
+	}
+}
+
+static void
+client_utils_open_new_async_cb (GObject *source_object,
+                                GAsyncResult *result,
+                                gpointer user_data)
+{
+	EClientUtilsAsyncOpData *async_data = user_data;
+	GError *error = NULL;
+
+	g_return_if_fail (source_object != NULL);
+	g_return_if_fail (result != NULL);
+	g_return_if_fail (async_data != NULL);
+	g_return_if_fail (async_data->callback != NULL);
+	g_return_if_fail (async_data->client == E_CLIENT (source_object));
+
+	async_data->open_finished = TRUE;
+
+	if (!e_client_open_finish (E_CLIENT (source_object), result, &error)
+	    || g_cancellable_set_error_if_cancelled (async_data->cancellable, &error)) {
+		finish_or_retry_open (async_data, error);
+		g_error_free (error);
+		return;
+	}
+
+	if (async_data->opened_cb_error) {
+		finish_or_retry_open (async_data, async_data->opened_cb_error);
+		return;
+	}
+
+	if (e_client_is_opened (async_data->client)) {
+		client_utils_open_new_done (async_data);
+		return;
+	}
+
+	/* wait for 'opened' signal, which is received in client_utils_opened_cb */
+}
+
+static gboolean
+client_utils_retry_open_timeout_cb (gpointer user_data)
+{
+	EClientUtilsAsyncOpData *async_data = user_data;
+
+	g_return_val_if_fail (async_data != NULL, FALSE);
+
+	g_signal_handlers_disconnect_matched (async_data->cancellable, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, async_data);
+
+	/* reconnect to the signal */
+	g_signal_connect (async_data->client, "opened", G_CALLBACK (client_utils_opened_cb), async_data);
+
+	e_client_open (async_data->client, async_data->only_if_exists, async_data->cancellable, client_utils_open_new_async_cb, async_data);
+
+	async_data->retry_open_id = 0;
+
+	return FALSE;
+}
+
+/**
+ * e_client_utils_open_new:
+ * @source: an #ESource to be opened
+ * @source_type: an #EClientSourceType of the @source
+ * @only_if_exists: if %TRUE, fail if this client doesn't already exist, otherwise create it first
+ * @cancellable: a #GCancellable; can be %NULL
+ * @callback: callback to call when a result is ready
+ * @user_data: user data for the @callback
+ *
+ * Begins asynchronous opening of a new #EClient corresponding
+ * to the @source of type @source_type. The resulting #EClient
+ * is fully opened and authenticated client, ready to be used.
+ * The opened client has also fetched capabilities.
+ * This call is finished by e_client_utils_open_new_finish()
+ * from the @callback.
+ *
+ * Since: 3.2
+ **/
+void
+e_client_utils_open_new (ESource *source,
+                         EClientSourceType source_type,
+                         gboolean only_if_exists,
+                         GCancellable *cancellable,
+                         GAsyncReadyCallback callback,
+                         gpointer user_data)
+{
+	EClient *client;
+	GError *error = NULL;
+	EClientUtilsAsyncOpData *async_data;
+
+	g_return_if_fail (callback != NULL);
+	return_async_error_if_fail (source != NULL, callback, user_data, source, e_client_utils_open_new);
+	return_async_error_if_fail (E_IS_SOURCE (source), callback, user_data, source, e_client_utils_open_new);
+
+	client = e_client_utils_new (source, source_type, &error);
+	if (!client) {
+		return_async_error (error, callback, user_data, source, e_client_utils_open_new);
+		g_error_free (error);
+		return;
+	}
+
+	async_data = g_new0 (EClientUtilsAsyncOpData, 1);
+	async_data->callback = callback;
+	async_data->user_data = user_data;
+	async_data->source = g_object_ref (source);
+	async_data->client = client;
+	async_data->open_finished = FALSE;
+	async_data->only_if_exists = only_if_exists;
+	async_data->retry_open_id = 0;
+
+	if (cancellable)
+		async_data->cancellable = g_object_ref (cancellable);
+	else
+		async_data->cancellable = g_cancellable_new ();
+
+	/* wait till backend notifies about its opened state */
+	g_signal_connect (client, "opened", G_CALLBACK (client_utils_opened_cb), async_data);
+
+	e_client_open (async_data->client, async_data->only_if_exists, async_data->cancellable, client_utils_open_new_async_cb, async_data);
+}
+
+/**
+ * e_client_utils_open_new_finish:
+ * @source: an #ESource on which the e_client_utils_open_new() was invoked
+ * @result: a #GAsyncResult
+ * @client: (out): Return value for an #EClient
+ * @error: (out): a #GError to set an error, if any
+ *
+ * Finishes previous call of e_client_utils_open_new() and
+ * sets @client to a fully opened and authenticated #EClient.
+ * This @client, if not NULL, should be freed with g_object_unref().
+ *
+ * Returns: %TRUE if successful, %FALSE otherwise.
+ *
+ * Since: 3.2
+ **/
+gboolean
+e_client_utils_open_new_finish (ESource *source,
+                                GAsyncResult *result,
+                                EClient **client,
+                                GError **error)
+{
+	GSimpleAsyncResult *simple;
+
+	g_return_val_if_fail (source != NULL, FALSE);
+	g_return_val_if_fail (result != NULL, FALSE);
+	g_return_val_if_fail (client != NULL, FALSE);
+	g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (source), e_client_utils_open_new), FALSE);
+
+	*client = NULL;
+	simple = G_SIMPLE_ASYNC_RESULT (result);
+
+	if (g_simple_async_result_propagate_error (simple, error))
+		return FALSE;
+
+	*client = g_object_ref (g_simple_async_result_get_op_res_gpointer (simple));
+
+	return *client != NULL;
+}
diff --git a/e-util/e-client-utils.h b/e-util/e-client-utils.h
new file mode 100644
index 0000000..3f81d1f
--- /dev/null
+++ b/e-util/e-client-utils.h
@@ -0,0 +1,64 @@
+/*
+ * e-client-utils.h
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Copyright (C) 2011 Red Hat, Inc. (www.redhat.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_CLIENT_UTILS_H
+#define E_CLIENT_UTILS_H
+
+#include <gtk/gtk.h>
+#include <libedataserver/libedataserver.h>
+
+G_BEGIN_DECLS
+
+/**
+ * EClientSourceType:
+ *
+ * Since: 3.2
+ **/
+typedef enum {
+	E_CLIENT_SOURCE_TYPE_CONTACTS,
+	E_CLIENT_SOURCE_TYPE_EVENTS,
+	E_CLIENT_SOURCE_TYPE_MEMOS,
+	E_CLIENT_SOURCE_TYPE_TASKS,
+	E_CLIENT_SOURCE_TYPE_LAST
+} EClientSourceType;
+
+EClient	*	e_client_utils_new		(ESource *source,
+						 EClientSourceType source_type,
+						 GError **error);
+
+void		e_client_utils_open_new		(ESource *source,
+						 EClientSourceType source_type,
+						 gboolean only_if_exists,
+						 GCancellable *cancellable,
+						 GAsyncReadyCallback callback,
+						 gpointer user_data);
+gboolean	e_client_utils_open_new_finish	(ESource *source,
+						 GAsyncResult *result,
+						 EClient **client,
+						 GError **error);
+
+G_END_DECLS
+
+#endif /* E_CLIENT_UTILS_H */
diff --git a/e-util/e-config.h b/e-util/e-config.h
index 2922a25..a372601 100644
--- a/e-util/e-config.h
+++ b/e-util/e-config.h
@@ -21,6 +21,10 @@
  *
  */
 
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
 #ifndef E_CONFIG_H
 #define E_CONFIG_H
 
@@ -324,7 +328,7 @@ void		e_config_target_free		(EConfig *config,
 /* To implement a basic config plugin, you just need to subclass
  * this and initialise the class target type tables */
 
-#include "e-util/e-plugin.h"
+#include <e-util/e-plugin.h>
 
 typedef struct _EConfigHookGroup EConfigHookGroup;
 typedef struct _EConfigHook EConfigHook;
diff --git a/widgets/misc/e-contact-map-window.c b/e-util/e-contact-map-window.c
similarity index 100%
rename from widgets/misc/e-contact-map-window.c
rename to e-util/e-contact-map-window.c
diff --git a/e-util/e-contact-map-window.h b/e-util/e-contact-map-window.h
new file mode 100644
index 0000000..f18def5
--- /dev/null
+++ b/e-util/e-contact-map-window.h
@@ -0,0 +1,85 @@
+/*
+ * e-contact-map-window.h
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ * Copyright (C) 2011 Dan Vratil <dvratil redhat com>
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_CONTACT_MAP_WINDOW_H
+#define E_CONTACT_MAP_WINDOW_H
+
+#ifdef WITH_CONTACT_MAPS
+
+#include <gtk/gtk.h>
+
+#include <libebook/libebook.h>
+
+#include <e-util/e-contact-map.h>
+
+/* Standard GObject macros */
+#define E_TYPE_CONTACT_MAP_WINDOW \
+	(e_contact_map_window_get_type ())
+#define E_CONTACT_MAP_WINDOW(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_CONTACT_MAP_WINDOW, EContactMapWindow))
+#define E_CONTACT_MAP_WINDOW_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_CONTACT_MAP_WINDOW, EContactMapWindowClass))
+#define E_IS_CONTACT_MAP_WINDOW(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_CONTACT_MAP_WINDOW))
+#define E_IS_CONTACT_MAP_WINDOW_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_CONTACT_MAP_WINDOW))
+#define E_CONTACT_MAP_WINDOW_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_CONTACT_MAP_WINDOW, EContactMapWindowClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EContactMapWindow EContactMapWindow;
+typedef struct _EContactMapWindowClass EContactMapWindowClass;
+typedef struct _EContactMapWindowPrivate EContactMapWindowPrivate;
+
+struct _EContactMapWindow {
+	GtkWindow parent;
+	EContactMapWindowPrivate *priv;
+};
+
+struct _EContactMapWindowClass {
+	GtkWindowClass parent_class;
+
+	void (*show_contact_editor)	(EContactMapWindow *window,
+					 const gchar *contact_uid);
+};
+
+GType			e_contact_map_window_get_type		(void) G_GNUC_CONST;
+EContactMapWindow *	e_contact_map_window_new		(void);
+
+void			e_contact_map_window_load_addressbook	(EContactMapWindow *window,
+								 EBookClient *book);
+
+EContactMap *		e_contact_map_window_get_map		(EContactMapWindow *window);
+
+G_END_DECLS
+
+#endif /* WITH_CONTACT_MAPS */
+
+#endif /* E_CONTACT_MAP_WINDOW_H */
diff --git a/e-util/e-contact-map.c b/e-util/e-contact-map.c
new file mode 100644
index 0000000..24f5ac1
--- /dev/null
+++ b/e-util/e-contact-map.c
@@ -0,0 +1,407 @@
+/*
+ * e-contact-map.c
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ * Copyright (C) 2011 Dan Vratil <dvratil redhat com>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#ifdef WITH_CONTACT_MAPS
+
+#include "e-contact-map.h"
+
+#include <champlain/champlain.h>
+#include <champlain-gtk/champlain-gtk.h>
+#include <geoclue/geoclue-address.h>
+#include <geoclue/geoclue-position.h>
+#include <geocode-glib.h>
+
+#include <clutter/clutter.h>
+
+#include <string.h>
+#include <glib/gi18n.h>
+#include <math.h>
+
+#include "e-contact-marker.h"
+#include "e-marshal.h"
+
+#define E_CONTACT_MAP_GET_PRIVATE(obj) \
+	(G_TYPE_INSTANCE_GET_PRIVATE \
+	((obj), E_TYPE_CONTACT_MAP, EContactMapPrivate))
+
+typedef struct _AsyncContext AsyncContext;
+
+struct _EContactMapPrivate {
+	GHashTable *markers; /* Hash table contact-name -> marker */
+
+	ChamplainMarkerLayer *marker_layer;
+};
+
+struct _AsyncContext {
+	EContactMap *map;
+	EContactMarker *marker;
+};
+
+enum {
+	CONTACT_ADDED,
+	CONTACT_REMOVED,
+	GEOCODING_STARTED,
+	GEOCODING_FAILED,
+	LAST_SIGNAL
+};
+
+static gint signals[LAST_SIGNAL] = {0};
+
+G_DEFINE_TYPE (EContactMap, e_contact_map, GTK_CHAMPLAIN_TYPE_EMBED)
+
+static void
+async_context_free (AsyncContext *async_context)
+{
+	if (async_context->map != NULL)
+		g_object_unref (async_context->map);
+
+	g_slice_free (AsyncContext, async_context);
+}
+
+static void
+contact_map_address_resolved_cb (GObject *source,
+                                 GAsyncResult *result,
+                                 gpointer user_data)
+{
+	GHashTable *resolved = NULL;
+	gpointer marker_ptr;
+	const gchar *name;
+	gdouble latitude, longitude;
+	AsyncContext *async_context = user_data;
+	ChamplainMarkerLayer *marker_layer;
+	ChamplainMarker *marker;
+	GError *error = NULL;
+
+	g_return_if_fail (async_context != NULL);
+	g_return_if_fail (E_IS_CONTACT_MAP (async_context->map));
+	g_return_if_fail (E_IS_CONTACT_MARKER (async_context->marker));
+
+	marker = CHAMPLAIN_MARKER (async_context->marker);
+	marker_layer = async_context->map->priv->marker_layer;
+
+	/* If the marker_layer does not exist anymore, the map has
+	 * probably been destroyed before this callback was launched.
+	 * It's not a failure, just silently clean up what was left
+	 * behind and pretend nothing happened. */
+
+	if (!CHAMPLAIN_IS_MARKER_LAYER (marker_layer))
+		goto exit;
+
+	resolved = geocode_object_resolve_finish (
+		GEOCODE_OBJECT (source), result, &error);
+
+	if (resolved == NULL ||
+	    !geocode_object_get_coords (resolved, &longitude, &latitude)) {
+		const gchar *name;
+		if (error)
+			g_error_free (error);
+		name = champlain_label_get_text (CHAMPLAIN_LABEL (marker));
+		g_signal_emit (
+			async_context->map,
+			signals[GEOCODING_FAILED], 0, name);
+		goto exit;
+	}
+
+	/* Move the marker to resolved position */
+	champlain_location_set_location (
+		CHAMPLAIN_LOCATION (marker), latitude, longitude);
+	champlain_marker_layer_add_marker (marker_layer, marker);
+	champlain_marker_set_selected (marker, FALSE);
+
+	/* Store the marker in the hash table. Use it's label as key */
+	name = champlain_label_get_text (CHAMPLAIN_LABEL (marker));
+	marker_ptr = g_hash_table_lookup (
+		async_context->map->priv->markers, name);
+	if (marker_ptr != NULL) {
+		g_hash_table_remove (async_context->map->priv->markers, name);
+		champlain_marker_layer_remove_marker (marker_layer, marker_ptr);
+	}
+	g_hash_table_insert (
+		async_context->map->priv->markers,
+		g_strdup (name), marker);
+
+	g_signal_emit (
+		async_context->map,
+		signals[CONTACT_ADDED], 0, marker);
+
+exit:
+	async_context_free (async_context);
+
+	if (resolved != NULL)
+		g_hash_table_unref (resolved);
+}
+
+static void
+resolve_marker_position (EContactMap *map,
+                         EContactMarker *marker,
+                         EContactAddress *address)
+{
+	GeocodeObject *geocoder;
+	AsyncContext *async_context;
+	const gchar *key;
+
+	g_return_if_fail (E_IS_CONTACT_MAP (map));
+	g_return_if_fail (address != NULL);
+
+	geocoder = geocode_object_new ();
+
+	key = GEOCODE_OBJECT_FIELD_POSTAL;
+	geocode_object_add (geocoder, key, address->code);
+
+	key = GEOCODE_OBJECT_FIELD_COUNTRY;
+	geocode_object_add (geocoder, key, address->country);
+
+	key = GEOCODE_OBJECT_FIELD_STATE;
+	geocode_object_add (geocoder, key, address->region);
+
+	key = GEOCODE_OBJECT_FIELD_CITY;
+	geocode_object_add (geocoder, key, address->locality);
+
+	key = GEOCODE_OBJECT_FIELD_STREET;
+	geocode_object_add (geocoder, key, address->street);
+
+	async_context = g_slice_new0 (AsyncContext);
+	async_context->map = g_object_ref (map);
+	async_context->marker = marker;
+
+	geocode_object_resolve_async (
+		geocoder, NULL,
+		contact_map_address_resolved_cb,
+		async_context);
+
+	g_object_unref (geocoder);
+
+	g_signal_emit (map, signals[GEOCODING_STARTED], 0, marker);
+}
+
+static void
+contact_map_finalize (GObject *object)
+{
+	EContactMapPrivate *priv;
+
+	priv = E_CONTACT_MAP (object)->priv;
+
+	if (priv->markers) {
+		g_hash_table_destroy (priv->markers);
+		priv->markers = NULL;
+	}
+
+	/* Chain up to parent's finalize() method. */
+	G_OBJECT_CLASS (e_contact_map_parent_class)->finalize (object);
+}
+
+static void
+e_contact_map_class_init (EContactMapClass *class)
+{
+	GObjectClass *object_class;
+
+	g_type_class_add_private (class, sizeof (EContactMapPrivate));
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->finalize = contact_map_finalize;
+
+	signals[CONTACT_ADDED] = g_signal_new (
+		"contact-added",
+		G_TYPE_FROM_CLASS (class),
+		G_SIGNAL_RUN_FIRST,
+		G_STRUCT_OFFSET (EContactMapClass, contact_added),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__OBJECT,
+		G_TYPE_NONE, 1, G_TYPE_OBJECT);
+
+	signals[CONTACT_REMOVED] = g_signal_new (
+		"contact-removed",
+		G_TYPE_FROM_CLASS (class),
+		G_SIGNAL_RUN_FIRST,
+		G_STRUCT_OFFSET (EContactMapClass, contact_removed),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__STRING,
+		G_TYPE_NONE, 1, G_TYPE_STRING);
+
+	signals[GEOCODING_STARTED] = g_signal_new (
+		"geocoding-started",
+		G_TYPE_FROM_CLASS (class),
+		G_SIGNAL_RUN_FIRST,
+		G_STRUCT_OFFSET (EContactMapClass, geocoding_started),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__OBJECT,
+		G_TYPE_NONE, 1, G_TYPE_OBJECT);
+
+	signals[GEOCODING_FAILED] = g_signal_new (
+		"geocoding-failed",
+		G_TYPE_FROM_CLASS (class),
+		G_SIGNAL_RUN_FIRST,
+		G_STRUCT_OFFSET (EContactMapClass, geocoding_failed),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__STRING,
+		G_TYPE_NONE, 1, G_TYPE_STRING);
+}
+
+static void
+e_contact_map_init (EContactMap *map)
+{
+	GHashTable *hash_table;
+	ChamplainMarkerLayer *layer;
+	ChamplainView *view;
+
+	map->priv = E_CONTACT_MAP_GET_PRIVATE (map);
+
+	hash_table = g_hash_table_new_full (
+		g_str_hash, g_str_equal,
+			(GDestroyNotify) g_free, NULL);
+
+	map->priv->markers = hash_table;
+
+	view = gtk_champlain_embed_get_view (GTK_CHAMPLAIN_EMBED (map));
+	/* This feature is somehow broken sometimes, so disable it for now */
+	champlain_view_set_zoom_on_double_click (view, FALSE);
+	layer = champlain_marker_layer_new_full (CHAMPLAIN_SELECTION_SINGLE);
+	champlain_view_add_layer (view, CHAMPLAIN_LAYER (layer));
+	map->priv->marker_layer = layer;
+}
+
+GtkWidget *
+e_contact_map_new (void)
+{
+	return g_object_new (
+		E_TYPE_CONTACT_MAP,NULL);
+}
+
+void
+e_contact_map_add_contact (EContactMap *map,
+                           EContact *contact)
+{
+	EContactAddress *address;
+	EContactPhoto *photo;
+	const gchar *contact_uid;
+	gchar *name;
+
+	g_return_if_fail (map && E_IS_CONTACT_MAP (map));
+	g_return_if_fail (contact && E_IS_CONTACT (contact));
+
+	photo = e_contact_get (contact, E_CONTACT_PHOTO);
+	contact_uid = e_contact_get_const (contact, E_CONTACT_UID);
+
+	address = e_contact_get (contact, E_CONTACT_ADDRESS_HOME);
+	if (address) {
+		name = g_strconcat (e_contact_get_const (contact, E_CONTACT_FILE_AS), " (", _("Home"), ")", NULL);
+		e_contact_map_add_marker (map, name, contact_uid, address, photo);
+		g_free (name);
+		e_contact_address_free (address);
+	}
+
+	address = e_contact_get (contact, E_CONTACT_ADDRESS_WORK);
+	if (address) {
+		name = g_strconcat (e_contact_get_const (contact, E_CONTACT_FILE_AS), " (", _("Work"), ")", NULL);
+		e_contact_map_add_marker (map, name, contact_uid, address, photo);
+		g_free (name);
+		e_contact_address_free (address);
+	}
+
+	if (photo)
+		e_contact_photo_free (photo);
+}
+
+void
+e_contact_map_add_marker (EContactMap *map,
+                          const gchar *name,
+                          const gchar *contact_uid,
+                          EContactAddress *address,
+                          EContactPhoto *photo)
+{
+	EContactMarker *marker;
+
+	g_return_if_fail (map && E_IS_CONTACT_MAP (map));
+	g_return_if_fail (name && *name);
+	g_return_if_fail (contact_uid && *contact_uid);
+	g_return_if_fail (address);
+
+	marker = E_CONTACT_MARKER (e_contact_marker_new (name, contact_uid, photo));
+
+	resolve_marker_position (map, marker, address);
+}
+
+/**
+ * The \name parameter must match the label of the
+ * marker (for example "John Smith (work)")
+ */
+void
+e_contact_map_remove_contact (EContactMap *map,
+                              const gchar *name)
+{
+	ChamplainMarker *marker;
+
+	g_return_if_fail (map && E_IS_CONTACT_MAP (map));
+	g_return_if_fail (name && *name);
+
+	marker = g_hash_table_lookup (map->priv->markers, name);
+
+	champlain_marker_layer_remove_marker (map->priv->marker_layer, marker);
+
+	g_hash_table_remove (map->priv->markers, name);
+
+	g_signal_emit (map, signals[CONTACT_REMOVED], 0, name);
+}
+
+void
+e_contact_map_remove_marker (EContactMap *map,
+                             ClutterActor *marker)
+{
+	const gchar *name;
+
+	g_return_if_fail (map && E_IS_CONTACT_MAP (map));
+	g_return_if_fail (marker && CLUTTER_IS_ACTOR (marker));
+
+	name = champlain_label_get_text (CHAMPLAIN_LABEL (marker));
+
+	e_contact_map_remove_contact (map, name);
+}
+
+void
+e_contact_map_zoom_on_marker (EContactMap *map,
+                              ClutterActor *marker)
+{
+	ChamplainView *view;
+	gdouble lat, lng;
+
+	g_return_if_fail (map && E_IS_CONTACT_MAP (map));
+	g_return_if_fail (marker && CLUTTER_IS_ACTOR (marker));
+
+	lat = champlain_location_get_latitude (CHAMPLAIN_LOCATION (marker));
+	lng = champlain_location_get_longitude (CHAMPLAIN_LOCATION (marker));
+
+	view = gtk_champlain_embed_get_view (GTK_CHAMPLAIN_EMBED (map));
+
+	champlain_view_center_on (view, lat, lng);
+	champlain_view_set_zoom_level (view, 15);
+}
+
+ChamplainView *
+e_contact_map_get_view (EContactMap *map)
+{
+	g_return_val_if_fail (E_IS_CONTACT_MAP (map), NULL);
+
+	return gtk_champlain_embed_get_view (GTK_CHAMPLAIN_EMBED (map));
+}
+
+#endif /* WITH_CONTACT_MAPS */
diff --git a/e-util/e-contact-map.h b/e-util/e-contact-map.h
new file mode 100644
index 0000000..90b7a6a
--- /dev/null
+++ b/e-util/e-contact-map.h
@@ -0,0 +1,110 @@
+/*
+ * e-contact-map.h
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ * Copyright (C) 2011 Dan Vratil <dvratil redhat com>
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_CONTACT_MAP_H
+#define E_CONTACT_MAP_H
+
+#ifdef WITH_CONTACT_MAPS
+
+#include <gtk/gtk.h>
+
+#include <champlain/champlain.h>
+#include <champlain-gtk/champlain-gtk.h>
+
+#include <libebook/libebook.h>
+
+/* Standard GObject macros */
+#define E_TYPE_CONTACT_MAP \
+	(e_contact_map_get_type ())
+#define E_CONTACT_MAP(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_CONTACT_MAP, EContactMap))
+#define E_CONTACT_MAP_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_CONTACT_MAP, EContactMapClass))
+#define E_IS_CONTACT_MAP(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_CONTACT_MAP))
+#define E_IS_CONTACT_MAP_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_CONTACT_MAP))
+#define E_CONTACT_MAP_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_CONTACT_MAP, EContactMapClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EContactMap EContactMap;
+typedef struct _EContactMapClass EContactMapClass;
+typedef struct _EContactMapPrivate EContactMapPrivate;
+
+struct _EContactMap {
+	GtkChamplainEmbed parent;
+	EContactMapPrivate *priv;
+};
+
+struct _EContactMapClass {
+	GtkWindowClass parent_class;
+
+	void (*contact_added)	   (EContactMap *map,
+				    ClutterActor *marker);
+
+	void (*contact_removed)	  (EContactMap *map,
+				   const gchar *name);
+
+	void (*geocoding_started)	(EContactMap *map,
+					 ClutterActor *marker);
+
+	void (*geocoding_failed)	(EContactMap *map,
+					 const gchar *name);
+};
+
+GType		e_contact_map_get_type		(void) G_GNUC_CONST;
+GtkWidget *	e_contact_map_new		(void);
+
+void		e_contact_map_add_contact	(EContactMap *map,
+						 EContact *contact);
+
+void		e_contact_map_add_marker	(EContactMap *map,
+						 const gchar *name,
+						 const gchar *contact_uid,
+						 EContactAddress *address,
+						 EContactPhoto *photo);
+
+void		e_contact_map_remove_contact	(EContactMap *map,
+						 const gchar *name);
+
+void		e_contact_map_remove_marker	(EContactMap *map,
+						 ClutterActor *marker);
+
+void		e_contact_map_zoom_on_marker	(EContactMap *map,
+						 ClutterActor *marker);
+
+ChamplainView *  e_contact_map_get_view		(EContactMap *map);
+
+G_END_DECLS
+
+#endif /* WITH_CONTACT_MAPS */
+
+#endif
diff --git a/widgets/misc/e-contact-marker.c b/e-util/e-contact-marker.c
similarity index 100%
rename from widgets/misc/e-contact-marker.c
rename to e-util/e-contact-marker.c
diff --git a/e-util/e-contact-marker.h b/e-util/e-contact-marker.h
new file mode 100644
index 0000000..e6e10db
--- /dev/null
+++ b/e-util/e-contact-marker.h
@@ -0,0 +1,88 @@
+/*
+ * e-contact-marker.h
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ * Copyright (C) 2008 Pierre-Luc Beaudoin <pierre-luc pierlux com>
+ * Copyright (C) 2011 Jiri Techet <techet gmail com>
+ * Copyright (C) 2011 Dan Vratil <dvratil redhat com>
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_CONTACT_MARKER_H
+#define E_CONTACT_MARKER_H
+
+#ifdef WITH_CONTACT_MAPS
+
+#include <libebook/libebook.h>
+
+#include <champlain/champlain.h>
+
+#include <glib-object.h>
+#include <clutter/clutter.h>
+
+G_BEGIN_DECLS
+
+#define E_TYPE_CONTACT_MARKER e_contact_marker_get_type ()
+
+#define E_CONTACT_MARKER(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST ((obj), E_TYPE_CONTACT_MARKER, EContactMarker))
+
+#define E_CONTACT_MARKER_CLASS(klass) \
+	(G_TYPE_CHECK_CLASS_CAST ((klass), E_TYPE_CONTACT_MARKER, EContactMarkerClass))
+
+#define E_IS_CONTACT_MARKER(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE ((obj), E_TYPE_CONTACT_MARKER))
+
+#define E_IS_CONTACT_MARKER_CLASS(klass) \
+	(G_TYPE_CHECK_CLASS_TYPE ((klass), E_TYPE_CONTACT_MARKER))
+
+#define E_CONTACT_MARKER_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS ((obj), E_TYPE_CONTACT_MARKER, EContactMarkerClass))
+
+typedef struct _EContactMarkerPrivate EContactMarkerPrivate;
+
+typedef struct _EContactMarker EContactMarker;
+typedef struct _EContactMarkerClass EContactMarkerClass;
+
+struct _EContactMarker
+{
+	ChamplainLabel parent;
+	EContactMarkerPrivate *priv;
+};
+
+struct _EContactMarkerClass
+{
+	ChamplainLabelClass parent_class;
+
+	void (*double_clicked)	(ClutterActor *actor);
+};
+
+GType e_contact_marker_get_type		(void);
+
+ClutterActor * e_contact_marker_new		(const gchar *name,
+						 const gchar *contact_uid,
+						 EContactPhoto *photo);
+
+const gchar * e_contact_marker_get_contact_uid	(EContactMarker *marker);
+
+G_END_DECLS
+
+#endif /* WITH_CONTACT_MAPS */
+
+#endif
diff --git a/e-util/e-contact-store.c b/e-util/e-contact-store.c
new file mode 100644
index 0000000..4e49399
--- /dev/null
+++ b/e-util/e-contact-store.c
@@ -0,0 +1,1370 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/* e-contact-store.c - Contacts store with GtkTreeModel interface.
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that 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 program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * Authors: Hans Petter Jansson <hpj novell com>
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <string.h>
+#include <glib/gi18n-lib.h>
+
+#include "e-contact-store.h"
+
+#define ITER_IS_VALID(contact_store, iter) \
+	((iter)->stamp == (contact_store)->priv->stamp)
+#define ITER_GET(iter) \
+	GPOINTER_TO_INT (iter->user_data)
+#define ITER_SET(contact_store, iter, index) \
+	G_STMT_START { \
+	(iter)->stamp = (contact_store)->priv->stamp; \
+	(iter)->user_data = GINT_TO_POINTER (index); \
+	} G_STMT_END
+
+#define E_CONTACT_STORE_GET_PRIVATE(obj) \
+	(G_TYPE_INSTANCE_GET_PRIVATE \
+	((obj), E_TYPE_CONTACT_STORE, EContactStorePrivate))
+
+struct _EContactStorePrivate {
+	gint stamp;
+	EBookQuery *query;
+	GArray *contact_sources;
+};
+
+/* Signals */
+
+enum {
+	START_CLIENT_VIEW,
+	STOP_CLIENT_VIEW,
+	LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+static void e_contact_store_tree_model_init (GtkTreeModelIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (
+	EContactStore, e_contact_store, G_TYPE_OBJECT,
+	G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_MODEL, e_contact_store_tree_model_init))
+
+static GtkTreeModelFlags e_contact_store_get_flags       (GtkTreeModel       *tree_model);
+static gint         e_contact_store_get_n_columns   (GtkTreeModel       *tree_model);
+static GType        e_contact_store_get_column_type (GtkTreeModel       *tree_model,
+						     gint                index);
+static gboolean     e_contact_store_get_iter        (GtkTreeModel       *tree_model,
+						     GtkTreeIter        *iter,
+						     GtkTreePath        *path);
+static GtkTreePath *e_contact_store_get_path        (GtkTreeModel       *tree_model,
+						     GtkTreeIter        *iter);
+static void         e_contact_store_get_value       (GtkTreeModel       *tree_model,
+						     GtkTreeIter        *iter,
+						     gint                column,
+						     GValue             *value);
+static gboolean     e_contact_store_iter_next       (GtkTreeModel       *tree_model,
+						     GtkTreeIter        *iter);
+static gboolean     e_contact_store_iter_children   (GtkTreeModel       *tree_model,
+						     GtkTreeIter        *iter,
+						     GtkTreeIter        *parent);
+static gboolean     e_contact_store_iter_has_child  (GtkTreeModel       *tree_model,
+						     GtkTreeIter        *iter);
+static gint         e_contact_store_iter_n_children (GtkTreeModel       *tree_model,
+						     GtkTreeIter        *iter);
+static gboolean     e_contact_store_iter_nth_child  (GtkTreeModel       *tree_model,
+						     GtkTreeIter        *iter,
+						     GtkTreeIter        *parent,
+						     gint                n);
+static gboolean     e_contact_store_iter_parent     (GtkTreeModel       *tree_model,
+						     GtkTreeIter        *iter,
+						     GtkTreeIter        *child);
+
+typedef struct
+{
+	EBookClient *book_client;
+
+	EBookClientView *client_view;
+	GPtrArray *contacts;
+
+	EBookClientView *client_view_pending;
+	GPtrArray *contacts_pending;
+}
+ContactSource;
+
+static void free_contact_ptrarray (GPtrArray *contacts);
+static void clear_contact_source  (EContactStore *contact_store, ContactSource *source);
+static void stop_view             (EContactStore *contact_store, EBookClientView *view);
+
+static void
+contact_store_dispose (GObject *object)
+{
+	EContactStorePrivate *priv;
+	gint ii;
+
+	priv = E_CONTACT_STORE_GET_PRIVATE (object);
+
+	/* Free sources and cached contacts */
+	for (ii = 0; ii < priv->contact_sources->len; ii++) {
+		ContactSource *source;
+
+		/* clear from back, because clear_contact_source can later access freed memory */
+		source = &g_array_index (
+			priv->contact_sources, ContactSource, priv->contact_sources->len - ii - 1);
+
+		clear_contact_source (E_CONTACT_STORE (object), source);
+		free_contact_ptrarray (source->contacts);
+		g_object_unref (source->book_client);
+	}
+	g_array_set_size (priv->contact_sources, 0);
+
+	if (priv->query != NULL) {
+		e_book_query_unref (priv->query);
+		priv->query = NULL;
+	}
+
+	/* Chain up to parent's dispose() method. */
+	G_OBJECT_CLASS (e_contact_store_parent_class)->dispose (object);
+}
+
+static void
+contact_store_finalize (GObject *object)
+{
+	EContactStorePrivate *priv;
+
+	priv = E_CONTACT_STORE_GET_PRIVATE (object);
+
+	g_array_free (priv->contact_sources, TRUE);
+
+	/* Chain up to parent's finalize() method. */
+	G_OBJECT_CLASS (e_contact_store_parent_class)->finalize (object);
+}
+
+static void
+e_contact_store_class_init (EContactStoreClass *class)
+{
+	GObjectClass *object_class;
+
+	g_type_class_add_private (class, sizeof (EContactStorePrivate));
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->dispose = contact_store_dispose;
+	object_class->finalize = contact_store_finalize;
+
+	signals[START_CLIENT_VIEW] = g_signal_new (
+		"start-client-view",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (EContactStoreClass, start_client_view),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__OBJECT,
+		G_TYPE_NONE, 1,
+		E_TYPE_BOOK_CLIENT_VIEW);
+
+	signals[STOP_CLIENT_VIEW] = g_signal_new (
+		"stop-client-view",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (EContactStoreClass, stop_client_view),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__OBJECT,
+		G_TYPE_NONE, 1,
+		E_TYPE_BOOK_CLIENT_VIEW);
+}
+
+static void
+e_contact_store_tree_model_init (GtkTreeModelIface *iface)
+{
+	iface->get_flags       = e_contact_store_get_flags;
+	iface->get_n_columns   = e_contact_store_get_n_columns;
+	iface->get_column_type = e_contact_store_get_column_type;
+	iface->get_iter        = e_contact_store_get_iter;
+	iface->get_path        = e_contact_store_get_path;
+	iface->get_value       = e_contact_store_get_value;
+	iface->iter_next       = e_contact_store_iter_next;
+	iface->iter_children   = e_contact_store_iter_children;
+	iface->iter_has_child  = e_contact_store_iter_has_child;
+	iface->iter_n_children = e_contact_store_iter_n_children;
+	iface->iter_nth_child  = e_contact_store_iter_nth_child;
+	iface->iter_parent     = e_contact_store_iter_parent;
+}
+
+static void
+e_contact_store_init (EContactStore *contact_store)
+{
+	GArray *contact_sources;
+
+	contact_sources = g_array_new (FALSE, FALSE, sizeof (ContactSource));
+
+	contact_store->priv = E_CONTACT_STORE_GET_PRIVATE (contact_store);
+	contact_store->priv->stamp = g_random_int ();
+	contact_store->priv->contact_sources = contact_sources;
+}
+
+/**
+ * e_contact_store_new:
+ *
+ * Creates a new #EContactStore.
+ *
+ * Returns: A new #EContactStore.
+ **/
+EContactStore *
+e_contact_store_new (void)
+{
+	return g_object_new (E_TYPE_CONTACT_STORE, NULL);
+}
+
+/* ------------------ *
+ * Row update helpers *
+ * ------------------ */
+
+static void
+row_deleted (EContactStore *contact_store,
+             gint n)
+{
+	GtkTreePath *path;
+
+	path = gtk_tree_path_new ();
+	gtk_tree_path_append_index (path, n);
+	gtk_tree_model_row_deleted (GTK_TREE_MODEL (contact_store), path);
+	gtk_tree_path_free (path);
+}
+
+static void
+row_inserted (EContactStore *contact_store,
+              gint n)
+{
+	GtkTreePath *path;
+	GtkTreeIter  iter;
+
+	path = gtk_tree_path_new ();
+	gtk_tree_path_append_index (path, n);
+
+	if (gtk_tree_model_get_iter (GTK_TREE_MODEL (contact_store), &iter, path))
+		gtk_tree_model_row_inserted (GTK_TREE_MODEL (contact_store), path, &iter);
+
+	gtk_tree_path_free (path);
+}
+
+static void
+row_changed (EContactStore *contact_store,
+             gint n)
+{
+	GtkTreePath *path;
+	GtkTreeIter  iter;
+
+	path = gtk_tree_path_new ();
+	gtk_tree_path_append_index (path, n);
+
+	if (gtk_tree_model_get_iter (GTK_TREE_MODEL (contact_store), &iter, path))
+		gtk_tree_model_row_changed (GTK_TREE_MODEL (contact_store), path, &iter);
+
+	gtk_tree_path_free (path);
+}
+
+/* ---------------------- *
+ * Contact source helpers *
+ * ---------------------- */
+
+static gint
+find_contact_source_by_client (EContactStore *contact_store,
+                               EBookClient *book_client)
+{
+	GArray *array;
+	gint i;
+
+	array = contact_store->priv->contact_sources;
+
+	for (i = 0; i < array->len; i++) {
+		ContactSource *source;
+
+		source = &g_array_index (array, ContactSource, i);
+		if (source->book_client == book_client)
+			return i;
+	}
+
+	return -1;
+}
+
+static gint
+find_contact_source_by_view (EContactStore *contact_store,
+                             EBookClientView *client_view)
+{
+	GArray *array;
+	gint i;
+
+	array = contact_store->priv->contact_sources;
+
+	for (i = 0; i < array->len; i++) {
+		ContactSource *source;
+
+		source = &g_array_index (array, ContactSource, i);
+		if (source->client_view == client_view ||
+		    source->client_view_pending == client_view)
+			return i;
+	}
+
+	return -1;
+}
+
+static gint
+find_contact_source_by_offset (EContactStore *contact_store,
+                               gint offset)
+{
+	GArray *array;
+	gint i;
+
+	array = contact_store->priv->contact_sources;
+
+	for (i = 0; i < array->len; i++) {
+		ContactSource *source;
+
+		source = &g_array_index (array, ContactSource, i);
+		if (source->contacts->len > offset)
+			return i;
+
+		offset -= source->contacts->len;
+	}
+
+	return -1;
+}
+
+static gint
+find_contact_source_by_pointer (EContactStore *contact_store,
+                                ContactSource *source)
+{
+	GArray *array;
+	gint i;
+
+	array = contact_store->priv->contact_sources;
+
+	i = ((gchar *) source - (gchar *) array->data) / sizeof (ContactSource);
+
+	if (i < 0 || i >= array->len)
+		return -1;
+
+	return i;
+}
+
+static gint
+get_contact_source_offset (EContactStore *contact_store,
+                           gint contact_source_index)
+{
+	GArray *array;
+	gint offset = 0;
+	gint i;
+
+	array = contact_store->priv->contact_sources;
+
+	g_assert (contact_source_index < array->len);
+
+	for (i = 0; i < contact_source_index; i++) {
+		ContactSource *source;
+
+		source = &g_array_index (array, ContactSource, i);
+		offset += source->contacts->len;
+	}
+
+	return offset;
+}
+
+static gint
+count_contacts (EContactStore *contact_store)
+{
+	GArray *array;
+	gint count = 0;
+	gint i;
+
+	array = contact_store->priv->contact_sources;
+
+	for (i = 0; i < array->len; i++) {
+		ContactSource *source;
+
+		source = &g_array_index (array, ContactSource, i);
+		count += source->contacts->len;
+	}
+
+	return count;
+}
+
+static gint
+find_contact_by_view_and_uid (EContactStore *contact_store,
+                              EBookClientView *find_view,
+                              const gchar *find_uid)
+{
+	GArray *array;
+	ContactSource *source;
+	GPtrArray *contacts;
+	gint source_index;
+	gint i;
+
+	g_return_val_if_fail (find_uid != NULL, -1);
+
+	source_index = find_contact_source_by_view (contact_store, find_view);
+	if (source_index < 0)
+		return -1;
+
+	array = contact_store->priv->contact_sources;
+	source = &g_array_index (array, ContactSource, source_index);
+
+	if (find_view == source->client_view)
+		contacts = source->contacts;          /* Current view */
+	else
+		contacts = source->contacts_pending;  /* Pending view */
+
+	for (i = 0; i < contacts->len; i++) {
+		EContact    *contact = g_ptr_array_index (contacts, i);
+		const gchar *uid     = e_contact_get_const (contact, E_CONTACT_UID);
+
+		if (uid && !strcmp (find_uid, uid))
+			return i;
+	}
+
+	return -1;
+}
+
+static gint
+find_contact_by_uid (EContactStore *contact_store,
+                     const gchar *find_uid)
+{
+	GArray *array;
+	gint i;
+
+	array = contact_store->priv->contact_sources;
+
+	for (i = 0; i < array->len; i++) {
+		ContactSource *source = &g_array_index (array, ContactSource, i);
+		gint           j;
+
+		for (j = 0; j < source->contacts->len; j++) {
+			EContact    *contact = g_ptr_array_index (source->contacts, j);
+			const gchar *uid     = e_contact_get_const (contact, E_CONTACT_UID);
+
+			if (!strcmp (find_uid, uid))
+				return get_contact_source_offset (contact_store, i) + j;
+		}
+	}
+
+	return -1;
+}
+
+static EBookClient *
+get_book_at_row (EContactStore *contact_store,
+                 gint row)
+{
+	GArray *array;
+	ContactSource *source;
+	gint source_index;
+
+	source_index = find_contact_source_by_offset (contact_store, row);
+	if (source_index < 0)
+		return NULL;
+
+	array = contact_store->priv->contact_sources;
+	source = &g_array_index (array, ContactSource, source_index);
+
+	return source->book_client;
+}
+
+static EContact *
+get_contact_at_row (EContactStore *contact_store,
+                    gint row)
+{
+	GArray *array;
+	ContactSource *source;
+	gint source_index;
+	gint offset;
+
+	source_index = find_contact_source_by_offset (contact_store, row);
+	if (source_index < 0)
+		return NULL;
+
+	array = contact_store->priv->contact_sources;
+	source = &g_array_index (array, ContactSource, source_index);
+	offset = get_contact_source_offset (contact_store, source_index);
+	row -= offset;
+
+	g_assert (row < source->contacts->len);
+
+	return g_ptr_array_index (source->contacts, row);
+}
+
+static gboolean
+find_contact_source_details_by_view (EContactStore *contact_store,
+                                     EBookClientView *client_view,
+                                     ContactSource **contact_source,
+                                     gint *offset)
+{
+	GArray *array;
+	gint source_index;
+
+	source_index = find_contact_source_by_view (contact_store, client_view);
+	if (source_index < 0)
+		return FALSE;
+
+	array = contact_store->priv->contact_sources;
+	*contact_source = &g_array_index (array, ContactSource, source_index);
+	*offset = get_contact_source_offset (contact_store, source_index);
+
+	return TRUE;
+}
+
+/* ------------------------- *
+ * EBookView signal handlers *
+ * ------------------------- */
+
+static void
+view_contacts_added (EContactStore *contact_store,
+                     const GSList *contacts,
+                     EBookClientView *client_view)
+{
+	ContactSource *source;
+	gint           offset;
+	const GSList  *l;
+
+	if (!find_contact_source_details_by_view (contact_store, client_view, &source, &offset)) {
+		g_warning ("EContactStore got 'contacts_added' signal from unknown EBookView!");
+		return;
+	}
+
+	for (l = contacts; l; l = g_slist_next (l)) {
+		EContact *contact = l->data;
+
+		g_object_ref (contact);
+
+		if (client_view == source->client_view) {
+			/* Current view */
+			g_ptr_array_add (source->contacts, contact);
+			row_inserted (contact_store, offset + source->contacts->len - 1);
+		} else {
+			/* Pending view */
+			g_ptr_array_add (source->contacts_pending, contact);
+		}
+	}
+}
+
+static void
+view_contacts_removed (EContactStore *contact_store,
+                       const GSList *uids,
+                       EBookClientView *client_view)
+{
+	ContactSource *source;
+	gint           offset;
+	const GSList  *l;
+
+	if (!find_contact_source_details_by_view (contact_store, client_view, &source, &offset)) {
+		g_warning ("EContactStore got 'contacts_removed' signal from unknown EBookView!");
+		return;
+	}
+
+	for (l = uids; l; l = g_slist_next (l)) {
+		const gchar *uid = l->data;
+		gint         n   = find_contact_by_view_and_uid (contact_store, client_view, uid);
+		EContact    *contact;
+
+		if (n < 0) {
+			g_warning ("EContactStore got 'contacts_removed' on unknown contact!");
+			continue;
+		}
+
+		if (client_view == source->client_view) {
+			/* Current view */
+			contact = g_ptr_array_index (source->contacts, n);
+			g_object_unref (contact);
+			g_ptr_array_remove_index (source->contacts, n);
+			row_deleted (contact_store, offset + n);
+		} else {
+			/* Pending view */
+			contact = g_ptr_array_index (source->contacts_pending, n);
+			g_object_unref (contact);
+			g_ptr_array_remove_index (source->contacts_pending, n);
+		}
+	}
+}
+
+static void
+view_contacts_modified (EContactStore *contact_store,
+                        const GSList *contacts,
+                        EBookClientView *client_view)
+{
+	GPtrArray     *cached_contacts;
+	ContactSource *source;
+	gint           offset;
+	const GSList  *l;
+
+	if (!find_contact_source_details_by_view (contact_store, client_view, &source, &offset)) {
+		g_warning ("EContactStore got 'contacts_changed' signal from unknown EBookView!");
+		return;
+	}
+
+	if (client_view == source->client_view)
+		cached_contacts = source->contacts;
+	else
+		cached_contacts = source->contacts_pending;
+
+	for (l = contacts; l; l = g_slist_next (l)) {
+		EContact    *cached_contact;
+		EContact    *contact = l->data;
+		const gchar *uid     = e_contact_get_const (contact, E_CONTACT_UID);
+		gint         n       = find_contact_by_view_and_uid (contact_store, client_view, uid);
+
+		if (n < 0) {
+			g_warning ("EContactStore got change notification on unknown contact!");
+			continue;
+		}
+
+		cached_contact = g_ptr_array_index (cached_contacts, n);
+
+		/* Update cached contact */
+		if (cached_contact != contact) {
+			g_object_unref (cached_contact);
+			cached_contacts->pdata[n] = g_object_ref (contact);
+		}
+
+		/* Emit changes for current view only */
+		if (client_view == source->client_view)
+			row_changed (contact_store, offset + n);
+	}
+}
+
+static void
+view_complete (EContactStore *contact_store,
+               const GError *error,
+               EBookClientView *client_view)
+{
+	ContactSource *source;
+	gint           offset;
+	gint           i;
+
+	if (!find_contact_source_details_by_view (contact_store, client_view, &source, &offset)) {
+		g_warning ("EContactStore got 'complete' signal from unknown EBookClientView!");
+		return;
+	}
+
+	/* If current view finished, do nothing */
+	if (client_view == source->client_view) {
+		stop_view (contact_store, source->client_view);
+		return;
+	}
+
+	g_assert (client_view == source->client_view_pending);
+
+	/* However, if it was a pending view, calculate and emit the differences between that
+	 * and the current view, and move the pending view up to current.
+	 *
+	 * This is O(m * n), and can be sped up with a temporary hash table if needed. */
+
+	/* Deletions */
+	for (i = 0; i < source->contacts->len; i++) {
+		EContact    *old_contact = g_ptr_array_index (source->contacts, i);
+		const gchar *old_uid     = e_contact_get_const (old_contact, E_CONTACT_UID);
+		gint         result;
+
+		result = find_contact_by_view_and_uid (contact_store, source->client_view_pending, old_uid);
+		if (result < 0) {
+			/* Contact is not in new view; removed */
+			g_object_unref (old_contact);
+			g_ptr_array_remove_index (source->contacts, i);
+			row_deleted (contact_store, offset + i);
+			i--;  /* Stay in place */
+		}
+	}
+
+	/* Insertions */
+	for (i = 0; i < source->contacts_pending->len; i++) {
+		EContact    *new_contact = g_ptr_array_index (source->contacts_pending, i);
+		const gchar *new_uid     = e_contact_get_const (new_contact, E_CONTACT_UID);
+		gint         result;
+
+		result = find_contact_by_view_and_uid (contact_store, source->client_view, new_uid);
+		if (result < 0) {
+			/* Contact is not in old view; inserted */
+			g_ptr_array_add (source->contacts, new_contact);
+			row_inserted (contact_store, offset + source->contacts->len - 1);
+		} else {
+			/* Contact already in old view; drop the new one */
+			g_object_unref (new_contact);
+		}
+	}
+
+	/* Move pending view up to current */
+	stop_view (contact_store, source->client_view);
+	g_object_unref (source->client_view);
+	source->client_view = source->client_view_pending;
+	source->client_view_pending = NULL;
+
+	/* Free array of pending contacts (members have been either moved or unreffed) */
+	g_ptr_array_free (source->contacts_pending, TRUE);
+	source->contacts_pending = NULL;
+}
+
+/* --------------------- *
+ * View/Query management *
+ * --------------------- */
+
+static void
+start_view (EContactStore *contact_store,
+            EBookClientView *view)
+{
+	g_signal_emit (contact_store, signals[START_CLIENT_VIEW], 0, view);
+
+	g_signal_connect_swapped (
+		view, "objects-added",
+		G_CALLBACK (view_contacts_added), contact_store);
+	g_signal_connect_swapped (
+		view, "objects-removed",
+		G_CALLBACK (view_contacts_removed), contact_store);
+	g_signal_connect_swapped (
+		view, "objects-modified",
+		G_CALLBACK (view_contacts_modified), contact_store);
+	g_signal_connect_swapped (
+		view, "complete",
+		G_CALLBACK (view_complete), contact_store);
+
+	e_book_client_view_start (view, NULL);
+}
+
+static void
+stop_view (EContactStore *contact_store,
+           EBookClientView *view)
+{
+	e_book_client_view_stop (view, NULL);
+
+	g_signal_handlers_disconnect_matched (
+		view, G_SIGNAL_MATCH_DATA,
+		0, 0, NULL, NULL, contact_store);
+
+	g_signal_emit (contact_store, signals[STOP_CLIENT_VIEW], 0, view);
+}
+
+static void
+clear_contact_ptrarray (GPtrArray *contacts)
+{
+	gint i;
+
+	for (i = 0; i < contacts->len; i++) {
+		EContact *contact = g_ptr_array_index (contacts, i);
+		g_object_unref (contact);
+	}
+
+	g_ptr_array_set_size (contacts, 0);
+}
+
+static void
+free_contact_ptrarray (GPtrArray *contacts)
+{
+	clear_contact_ptrarray (contacts);
+	g_ptr_array_free (contacts, TRUE);
+}
+
+static void
+clear_contact_source (EContactStore *contact_store,
+                      ContactSource *source)
+{
+	gint source_index;
+	gint offset;
+
+	source_index = find_contact_source_by_pointer (contact_store, source);
+	g_assert (source_index >= 0);
+
+	offset = get_contact_source_offset (contact_store, source_index);
+	g_assert (offset >= 0);
+
+	/* Inform listeners that contacts went away */
+
+	if (source->contacts && source->contacts->len > 0) {
+		GtkTreePath *path = gtk_tree_path_new ();
+		gint         i;
+
+		gtk_tree_path_append_index (path, source->contacts->len);
+
+		for (i = source->contacts->len - 1; i >= 0; i--) {
+			EContact *contact = g_ptr_array_index (source->contacts, i);
+
+			g_object_unref (contact);
+			g_ptr_array_remove_index_fast (source->contacts, i);
+
+			gtk_tree_path_prev (path);
+			gtk_tree_model_row_deleted (GTK_TREE_MODEL (contact_store), path);
+		}
+
+		gtk_tree_path_free (path);
+	}
+
+	/* Free main and pending views, clear cached contacts */
+
+	if (source->client_view) {
+		stop_view (contact_store, source->client_view);
+		g_object_unref (source->client_view);
+
+		source->client_view = NULL;
+	}
+
+	if (source->client_view_pending) {
+		stop_view (contact_store, source->client_view_pending);
+		g_object_unref (source->client_view_pending);
+		free_contact_ptrarray (source->contacts_pending);
+
+		source->client_view_pending = NULL;
+		source->contacts_pending  = NULL;
+	}
+}
+
+static void
+client_view_ready_cb (GObject *source_object,
+                      GAsyncResult *result,
+                      gpointer user_data)
+{
+	EContactStore *contact_store = user_data;
+	gint source_idx;
+	EBookClient *book_client;
+	EBookClientView *client_view = NULL;
+
+	g_return_if_fail (contact_store != NULL);
+	g_return_if_fail (source_object != NULL);
+
+	book_client = E_BOOK_CLIENT (source_object);
+	g_return_if_fail (book_client != NULL);
+
+	if (!e_book_client_get_view_finish (book_client, result, &client_view, NULL))
+		client_view = NULL;
+
+	source_idx = find_contact_source_by_client (contact_store, book_client);
+	if (source_idx >= 0) {
+		ContactSource *source;
+
+		source = &g_array_index (contact_store->priv->contact_sources, ContactSource, source_idx);
+
+		if (source->client_view) {
+			if (source->client_view_pending) {
+				stop_view (contact_store, source->client_view_pending);
+				g_object_unref (source->client_view_pending);
+				free_contact_ptrarray (source->contacts_pending);
+			}
+
+			source->client_view_pending = client_view;
+
+			if (source->client_view_pending) {
+				source->contacts_pending = g_ptr_array_new ();
+				start_view (contact_store, client_view);
+			} else {
+				source->contacts_pending = NULL;
+			}
+		} else {
+			source->client_view = client_view;
+
+			if (source->client_view) {
+				start_view (contact_store, client_view);
+			}
+		}
+	}
+
+	g_object_unref (contact_store);
+}
+
+static void
+query_contact_source (EContactStore *contact_store,
+                      ContactSource *source)
+{
+	gboolean is_opened;
+
+	g_assert (source->book_client != NULL);
+
+	if (!contact_store->priv->query) {
+		clear_contact_source (contact_store, source);
+		return;
+	}
+
+	is_opened = e_client_is_opened (E_CLIENT (source->book_client));
+
+	if (source->client_view) {
+		if (source->client_view_pending) {
+			stop_view (contact_store, source->client_view_pending);
+			g_object_unref (source->client_view_pending);
+			free_contact_ptrarray (source->contacts_pending);
+			source->client_view_pending = NULL;
+			source->contacts_pending = NULL;
+		}
+	}
+
+	if (is_opened) {
+		gchar *query_str;
+
+		query_str = e_book_query_to_string (contact_store->priv->query);
+		e_book_client_get_view (source->book_client, query_str, NULL, client_view_ready_cb, g_object_ref (contact_store));
+		g_free (query_str);
+	}
+}
+
+/* ----------------- *
+ * EContactStore API *
+ * ----------------- */
+
+/**
+ * e_contact_store_get_client:
+ * @contact_store: an #EContactStore
+ * @iter: a #GtkTreeIter from @contact_store
+ *
+ * Gets the #EBookClient that provided the contact at @iter.
+ *
+ * Returns: An #EBookClient.
+ *
+ * Since: 3.2
+ **/
+EBookClient *
+e_contact_store_get_client (EContactStore *contact_store,
+                            GtkTreeIter *iter)
+{
+	gint index;
+
+	g_return_val_if_fail (E_IS_CONTACT_STORE (contact_store), NULL);
+	g_return_val_if_fail (ITER_IS_VALID (contact_store, iter), NULL);
+
+	index = ITER_GET (iter);
+
+	return get_book_at_row (contact_store, index);
+}
+
+/**
+ * e_contact_store_get_contact:
+ * @contact_store: an #EContactStore
+ * @iter: a #GtkTreeIter from @contact_store
+ *
+ * Gets the #EContact at @iter.
+ *
+ * Returns: An #EContact.
+ **/
+EContact *
+e_contact_store_get_contact (EContactStore *contact_store,
+                             GtkTreeIter *iter)
+{
+	gint index;
+
+	g_return_val_if_fail (E_IS_CONTACT_STORE (contact_store), NULL);
+	g_return_val_if_fail (ITER_IS_VALID (contact_store, iter), NULL);
+
+	index = ITER_GET (iter);
+
+	return get_contact_at_row (contact_store, index);
+}
+
+/**
+ * e_contact_store_find_contact:
+ * @contact_store: an #EContactStore
+ * @uid: a unique contact identifier
+ * @iter: a destination #GtkTreeIter to set
+ *
+ * Sets @iter to point to the contact row matching @uid.
+ *
+ * Returns: %TRUE if the contact was found, and @iter was set. %FALSE otherwise.
+ **/
+gboolean
+e_contact_store_find_contact (EContactStore *contact_store,
+                              const gchar *uid,
+                              GtkTreeIter *iter)
+{
+	gint index;
+
+	g_return_val_if_fail (E_IS_CONTACT_STORE (contact_store), FALSE);
+	g_return_val_if_fail (uid != NULL, FALSE);
+
+	index = find_contact_by_uid (contact_store, uid);
+	if (index < 0)
+		return FALSE;
+
+	ITER_SET (contact_store, iter, index);
+	return TRUE;
+}
+
+/**
+ * e_contact_store_get_clients:
+ * @contact_store: an #EContactStore
+ *
+ * Gets the list of book clients that provide contacts for @contact_store.
+ *
+ * Returns: A #GSList of pointers to #EBookClient. The caller owns the list,
+ * but not the book clients.
+ *
+ * Since: 3.2
+ **/
+GSList *
+e_contact_store_get_clients (EContactStore *contact_store)
+{
+	GArray *array;
+	GSList *client_list = NULL;
+	gint i;
+
+	g_return_val_if_fail (E_IS_CONTACT_STORE (contact_store), NULL);
+
+	array = contact_store->priv->contact_sources;
+
+	for (i = 0; i < array->len; i++) {
+		ContactSource *source;
+
+		source = &g_array_index (array, ContactSource, i);
+		client_list = g_slist_prepend (client_list, source->book_client);
+	}
+
+	return client_list;
+}
+
+/**
+ * e_contact_store_add_client:
+ * @contact_store: an #EContactStore
+ * @book_client: an #EBookClient
+ *
+ * Adds @book_client to the list of book clients that provide contacts for @contact_store.
+ * The @contact_store adds a reference to @book_client, if added.
+ *
+ * Since: 3.2
+ **/
+void
+e_contact_store_add_client (EContactStore *contact_store,
+                            EBookClient *book_client)
+{
+	GArray *array;
+	ContactSource source;
+	ContactSource *indexed_source;
+
+	g_return_if_fail (E_IS_CONTACT_STORE (contact_store));
+	g_return_if_fail (E_IS_BOOK_CLIENT (book_client));
+
+	if (find_contact_source_by_client (contact_store, book_client) >= 0) {
+		g_warning ("Same book client added more than once to EContactStore!");
+		return;
+	}
+
+	array = contact_store->priv->contact_sources;
+
+	memset (&source, 0, sizeof (ContactSource));
+	source.book_client = g_object_ref (book_client);
+	source.contacts = g_ptr_array_new ();
+	g_array_append_val (array, source);
+
+	indexed_source = &g_array_index (array, ContactSource, array->len - 1);
+
+	query_contact_source (contact_store, indexed_source);
+}
+
+/**
+ * e_contact_store_remove_client:
+ * @contact_store: an #EContactStore
+ * @book_client: an #EBookClient
+ *
+ * Removes @book from the list of book clients that provide contacts for @contact_store.
+ *
+ * Since: 3.2
+ **/
+void
+e_contact_store_remove_client (EContactStore *contact_store,
+                               EBookClient *book_client)
+{
+	GArray *array;
+	ContactSource *source;
+	gint source_index;
+
+	g_return_if_fail (E_IS_CONTACT_STORE (contact_store));
+	g_return_if_fail (E_IS_BOOK_CLIENT (book_client));
+
+	source_index = find_contact_source_by_client (contact_store, book_client);
+	if (source_index < 0) {
+		g_warning ("Tried to remove unknown book client from EContactStore!");
+		return;
+	}
+
+	array = contact_store->priv->contact_sources;
+
+	source = &g_array_index (array, ContactSource, source_index);
+	clear_contact_source (contact_store, source);
+	free_contact_ptrarray (source->contacts);
+	g_object_unref (book_client);
+
+	g_array_remove_index (array, source_index);  /* Preserve order */
+}
+
+/**
+ * e_contact_store_set_query:
+ * @contact_store: an #EContactStore
+ * @book_query: an #EBookQuery
+ *
+ * Sets @book_query to be the query used to fetch contacts from the books
+ * assigned to @contact_store.
+ **/
+void
+e_contact_store_set_query (EContactStore *contact_store,
+                           EBookQuery *book_query)
+{
+	GArray *array;
+	gint i;
+
+	g_return_if_fail (E_IS_CONTACT_STORE (contact_store));
+
+	if (book_query == contact_store->priv->query)
+		return;
+
+	if (contact_store->priv->query)
+		e_book_query_unref (contact_store->priv->query);
+
+	contact_store->priv->query = book_query;
+	if (book_query)
+		e_book_query_ref (book_query);
+
+	/* Query books */
+	array = contact_store->priv->contact_sources;
+	for (i = 0; i < array->len; i++) {
+		ContactSource *contact_source;
+
+		contact_source = &g_array_index (array, ContactSource, i);
+		query_contact_source (contact_store, contact_source);
+	}
+}
+
+/**
+ * e_contact_store_peek_query:
+ * @contact_store: an #EContactStore
+ *
+ * Gets the query that's being used to fetch contacts from the books
+ * assigned to @contact_store.
+ *
+ * Returns: The #EBookQuery being used.
+ **/
+EBookQuery *
+e_contact_store_peek_query (EContactStore *contact_store)
+{
+	g_return_val_if_fail (E_IS_CONTACT_STORE (contact_store), NULL);
+
+	return contact_store->priv->query;
+}
+
+/* ---------------- *
+ * GtkTreeModel API *
+ * ---------------- */
+
+static GtkTreeModelFlags
+e_contact_store_get_flags (GtkTreeModel *tree_model)
+{
+	g_return_val_if_fail (E_IS_CONTACT_STORE (tree_model), 0);
+
+	return GTK_TREE_MODEL_LIST_ONLY;
+}
+
+static gint
+e_contact_store_get_n_columns (GtkTreeModel *tree_model)
+{
+	g_return_val_if_fail (E_IS_CONTACT_STORE (tree_model), 0);
+
+	return E_CONTACT_FIELD_LAST;
+}
+
+static GType
+get_column_type (EContactStore *contact_store,
+                 gint column)
+{
+	const gchar  *field_name;
+	GObjectClass *contact_class;
+	GParamSpec   *param_spec;
+	GType         value_type;
+
+	/* Silently suppress requests for columns lower than the first EContactField.
+	 * GtkTreeView automatically queries the type of all columns up to the maximum
+	 * provided, and we have to return a valid value type, so let it be a generic
+	 * pointer. */
+	if (column < E_CONTACT_FIELD_FIRST) {
+		return G_TYPE_POINTER;
+	}
+
+	field_name = e_contact_field_name (column);
+	contact_class = g_type_class_ref (E_TYPE_CONTACT);
+	param_spec = g_object_class_find_property (contact_class, field_name);
+	value_type = G_PARAM_SPEC_VALUE_TYPE (param_spec);
+	g_type_class_unref (contact_class);
+
+	return value_type;
+}
+
+static GType
+e_contact_store_get_column_type (GtkTreeModel *tree_model,
+                                 gint index)
+{
+	EContactStore *contact_store = E_CONTACT_STORE (tree_model);
+
+	g_return_val_if_fail (E_IS_CONTACT_STORE (tree_model), G_TYPE_INVALID);
+	g_return_val_if_fail (index >= 0 && index < E_CONTACT_FIELD_LAST, G_TYPE_INVALID);
+
+	return get_column_type (contact_store, index);
+}
+
+static gboolean
+e_contact_store_get_iter (GtkTreeModel *tree_model,
+                          GtkTreeIter *iter,
+                          GtkTreePath *path)
+{
+	EContactStore *contact_store = E_CONTACT_STORE (tree_model);
+	gint           index;
+
+	g_return_val_if_fail (E_IS_CONTACT_STORE (tree_model), FALSE);
+	g_return_val_if_fail (gtk_tree_path_get_depth (path) > 0, FALSE);
+
+	index = gtk_tree_path_get_indices (path)[0];
+	if (index >= count_contacts (contact_store))
+		return FALSE;
+
+	ITER_SET (contact_store, iter, index);
+	return TRUE;
+}
+
+static GtkTreePath *
+e_contact_store_get_path (GtkTreeModel *tree_model,
+                          GtkTreeIter *iter)
+{
+	EContactStore *contact_store = E_CONTACT_STORE (tree_model);
+	GtkTreePath   *path;
+	gint           index;
+
+	g_return_val_if_fail (E_IS_CONTACT_STORE (tree_model), NULL);
+	g_return_val_if_fail (ITER_IS_VALID (contact_store, iter), NULL);
+
+	index = ITER_GET (iter);
+	path = gtk_tree_path_new ();
+	gtk_tree_path_append_index (path, index);
+
+	return path;
+}
+
+static gboolean
+e_contact_store_iter_next (GtkTreeModel *tree_model,
+                           GtkTreeIter *iter)
+{
+	EContactStore *contact_store = E_CONTACT_STORE (tree_model);
+	gint           index;
+
+	g_return_val_if_fail (E_IS_CONTACT_STORE (tree_model), FALSE);
+	g_return_val_if_fail (ITER_IS_VALID (contact_store, iter), FALSE);
+
+	index = ITER_GET (iter);
+
+	if (index + 1 < count_contacts (contact_store)) {
+		ITER_SET (contact_store, iter, index + 1);
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+static gboolean
+e_contact_store_iter_children (GtkTreeModel *tree_model,
+                               GtkTreeIter *iter,
+                               GtkTreeIter *parent)
+{
+	EContactStore *contact_store = E_CONTACT_STORE (tree_model);
+
+	g_return_val_if_fail (E_IS_CONTACT_STORE (tree_model), FALSE);
+
+	/* This is a list, nodes have no children. */
+	if (parent)
+		return FALSE;
+
+	/* But if parent == NULL we return the list itself as children of the root. */
+	if (count_contacts (contact_store) <= 0)
+		return FALSE;
+
+	ITER_SET (contact_store, iter, 0);
+	return TRUE;
+}
+
+static gboolean
+e_contact_store_iter_has_child (GtkTreeModel *tree_model,
+                                GtkTreeIter *iter)
+{
+	g_return_val_if_fail (E_IS_CONTACT_STORE (tree_model), FALSE);
+
+	if (iter == NULL)
+		return TRUE;
+
+	return FALSE;
+}
+
+static gint
+e_contact_store_iter_n_children (GtkTreeModel *tree_model,
+                                 GtkTreeIter *iter)
+{
+	EContactStore *contact_store = E_CONTACT_STORE (tree_model);
+
+	g_return_val_if_fail (E_IS_CONTACT_STORE (tree_model), -1);
+
+	if (iter == NULL)
+		return count_contacts (contact_store);
+
+	g_return_val_if_fail (ITER_IS_VALID (contact_store, iter), -1);
+	return 0;
+}
+
+static gboolean
+e_contact_store_iter_nth_child (GtkTreeModel *tree_model,
+                                GtkTreeIter *iter,
+                                GtkTreeIter *parent,
+                                gint n)
+{
+	EContactStore *contact_store = E_CONTACT_STORE (tree_model);
+
+	g_return_val_if_fail (E_IS_CONTACT_STORE (tree_model), FALSE);
+
+	if (parent)
+		return FALSE;
+
+	if (n < count_contacts (contact_store)) {
+		ITER_SET (contact_store, iter, n);
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+static gboolean
+e_contact_store_iter_parent (GtkTreeModel *tree_model,
+                             GtkTreeIter *iter,
+                             GtkTreeIter *child)
+{
+	return FALSE;
+}
+
+static void
+e_contact_store_get_value (GtkTreeModel *tree_model,
+                           GtkTreeIter *iter,
+                           gint column,
+                           GValue *value)
+{
+	EContactStore *contact_store = E_CONTACT_STORE (tree_model);
+	EContact      *contact;
+	const gchar   *field_name;
+	gint           row;
+
+	g_return_if_fail (E_IS_CONTACT_STORE (tree_model));
+	g_return_if_fail (column < E_CONTACT_FIELD_LAST);
+	g_return_if_fail (ITER_IS_VALID (contact_store, iter));
+
+	g_value_init (value, get_column_type (contact_store, column));
+
+	row = ITER_GET (iter);
+	contact = get_contact_at_row (contact_store, row);
+	if (!contact || column < E_CONTACT_FIELD_FIRST)
+		return;
+
+	field_name = e_contact_field_name (column);
+	g_object_get_property (G_OBJECT (contact), field_name, value);
+}
diff --git a/e-util/e-contact-store.h b/e-util/e-contact-store.h
new file mode 100644
index 0000000..c0754af
--- /dev/null
+++ b/e-util/e-contact-store.h
@@ -0,0 +1,94 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/* e-contact-store.h - Contacts store with GtkTreeModel interface.
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that 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 program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * Authors: Hans Petter Jansson <hpj novell com>
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_CONTACT_STORE_H
+#define E_CONTACT_STORE_H
+
+#include <gtk/gtk.h>
+#include <libebook/libebook.h>
+
+/* Standard GObject macros */
+#define E_TYPE_CONTACT_STORE \
+	(e_contact_store_get_type ())
+#define E_CONTACT_STORE(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_CONTACT_STORE, EContactStore))
+#define E_CONTACT_STORE_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_CONTACT_STORE, EContactStoreClass))
+#define E_IS_CONTACT_STORE(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_CONTACT_STORE))
+#define E_IS_CONTACT_STORE_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_CONTACT_STORE))
+#define E_CONTACT_STORE_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_CONTACT_STORE, EContactStoreClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EContactStore EContactStore;
+typedef struct _EContactStoreClass EContactStoreClass;
+typedef struct _EContactStorePrivate EContactStorePrivate;
+
+struct _EContactStore {
+	GObject parent;
+	EContactStorePrivate *priv;
+};
+
+struct _EContactStoreClass {
+	GObjectClass parent_class;
+
+	/* signals */
+	void (*start_client_view) (EContactStore *contact_store, EBookClientView *client_view);
+	void (*stop_client_view)  (EContactStore *contact_store, EBookClientView *client_view);
+};
+
+GType		e_contact_store_get_type	(void);
+EContactStore *	e_contact_store_new		(void);
+
+EBookClient *	e_contact_store_get_client	(EContactStore *contact_store,
+						 GtkTreeIter *iter);
+EContact *	e_contact_store_get_contact	(EContactStore *contact_store,
+						 GtkTreeIter *iter);
+gboolean	e_contact_store_find_contact	(EContactStore *contact_store,
+						 const gchar *uid,
+						 GtkTreeIter *iter);
+
+/* Returns a shallow copy; free the list when done, but don't unref elements */
+GSList *	e_contact_store_get_clients	(EContactStore *contact_store);
+void		e_contact_store_add_client	(EContactStore *contact_store,
+						 EBookClient *book_client);
+void		e_contact_store_remove_client	(EContactStore *contact_store,
+						 EBookClient *book_client);
+void		e_contact_store_set_query	(EContactStore *contact_store,
+						 EBookQuery *book_query);
+EBookQuery *	e_contact_store_peek_query	(EContactStore *contact_store);
+
+G_END_DECLS
+
+#endif  /* E_CONTACT_STORE_H */
diff --git a/e-util/e-dateedit.c b/e-util/e-dateedit.c
new file mode 100644
index 0000000..ab6085f
--- /dev/null
+++ b/e-util/e-dateedit.c
@@ -0,0 +1,2497 @@
+/*
+ * 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ * Authors:
+ *		Damon Chaplin <damon ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ */
+
+/*
+ * EDateEdit - a widget based on GnomeDateEdit to provide a date & optional
+ * time field with popups for entering a date.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-dateedit.h"
+
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <gdk/gdkkeysyms.h>
+#include <atk/atkrelation.h>
+#include <atk/atkrelationset.h>
+#include <glib/gi18n.h>
+
+#include <libebackend/libebackend.h>
+
+#include "e-calendar.h"
+
+#define E_DATE_EDIT_GET_PRIVATE(obj) \
+	(G_TYPE_INSTANCE_GET_PRIVATE \
+	((obj), E_TYPE_DATE_EDIT, EDateEditPrivate))
+
+struct _EDateEditPrivate {
+	GtkWidget *date_entry;
+	GtkWidget *date_button;
+
+	GtkWidget *space;
+
+	GtkWidget *time_combo;
+
+	GtkWidget *cal_popup;
+	GtkWidget *calendar;
+	GtkWidget *now_button;
+	GtkWidget *today_button;
+	GtkWidget *none_button;		/* This will only be visible if a
+					 * 'None' date/time is permitted. */
+
+	GdkDevice *grabbed_keyboard;
+	GdkDevice *grabbed_pointer;
+
+	gboolean show_date;
+	gboolean show_time;
+	gboolean use_24_hour_format;
+
+	/* This is TRUE if we want to make the time field insensitive rather
+	 * than hide it when set_show_time() is called. */
+	gboolean make_time_insensitive;
+
+	/* This is the range of hours we show in the time popup. */
+	gint lower_hour;
+	gint upper_hour;
+
+	/* This indicates whether the last date committed was invalid.
+	 * (A date is committed by hitting Return, moving the keyboard focus,
+	 * or selecting a date in the popup). Note that this only indicates
+	 * that the date couldn't be parsed. A date set to 'None' is valid
+	 * here, though e_date_edit_date_is_valid() will return FALSE if an
+	 * empty date isn't actually permitted. */
+	gboolean date_is_valid;
+
+	/* This is the last valid date which was set. If the date was set to
+	 * 'None' or empty, date_set_to_none will be TRUE and the other fields
+	 * are undefined, so don't use them. */
+	gboolean date_set_to_none;
+	gint year;
+	gint month;
+	gint day;
+
+	/* This indicates whether the last time committed was invalid.
+	 * (A time is committed by hitting Return, moving the keyboard focus,
+	 * or selecting a time in the popup). Note that this only indicates
+	 * that the time couldn't be parsed. An empty/None time is valid
+	 * here, though e_date_edit_time_is_valid() will return FALSE if an
+	 * empty time isn't actually permitted. */
+	gboolean time_is_valid;
+
+	/* This is the last valid time which was set. If the time was set to
+	 * 'None' or empty, time_set_to_none will be TRUE and the other fields
+	 * are undefined, so don't use them. */
+	gboolean time_set_to_none;
+	gint hour;
+	gint minute;
+
+	EDateEditGetTimeCallback time_callback;
+	gpointer time_callback_data;
+	GDestroyNotify time_callback_destroy;
+
+	gboolean twodigit_year_can_future;
+
+	/* set to TRUE when the date has been changed by typing to the entry */
+	gboolean has_been_changed;
+
+	gboolean allow_no_date_set;
+};
+
+enum {
+	PROP_0,
+	PROP_ALLOW_NO_DATE_SET,
+	PROP_SHOW_DATE,
+	PROP_SHOW_TIME,
+	PROP_SHOW_WEEK_NUMBERS,
+	PROP_USE_24_HOUR_FORMAT,
+	PROP_WEEK_START_DAY,
+	PROP_TWODIGIT_YEAR_CAN_FUTURE,
+	PROP_SET_NONE
+};
+
+enum {
+	CHANGED,
+	LAST_SIGNAL
+};
+
+static void create_children			(EDateEdit	*dedit);
+static gboolean e_date_edit_mnemonic_activate	(GtkWidget	*widget,
+						 gboolean	 group_cycling);
+static void e_date_edit_grab_focus		(GtkWidget	*widget);
+
+static gint on_date_entry_key_press		(GtkWidget	*widget,
+						 GdkEvent	*key_event,
+						 EDateEdit	*dedit);
+static gint on_date_entry_key_release		(GtkWidget	*widget,
+						 GdkEvent	*key_event,
+						 EDateEdit	*dedit);
+static void on_date_button_clicked		(GtkWidget	*widget,
+						 EDateEdit	*dedit);
+static void e_date_edit_show_date_popup		(EDateEdit	*dedit,
+						 GdkEvent	*event);
+static void position_date_popup			(EDateEdit	*dedit);
+static void on_date_popup_none_button_clicked	(GtkWidget	*button,
+						 EDateEdit	*dedit);
+static void on_date_popup_today_button_clicked	(GtkWidget	*button,
+						 EDateEdit	*dedit);
+static void on_date_popup_now_button_clicked	(GtkWidget	*button,
+						 EDateEdit	*dedit);
+static gint on_date_popup_delete_event		(GtkWidget	*widget,
+						 EDateEdit	*dedit);
+static gint on_date_popup_key_press		(GtkWidget	*widget,
+						 GdkEventKey	*event,
+						 EDateEdit	*dedit);
+static gint on_date_popup_button_press		(GtkWidget	*widget,
+						 GdkEvent	*button_event,
+						 gpointer	 data);
+static void on_date_popup_date_selected		(ECalendarItem	*calitem,
+						 EDateEdit	*dedit);
+static void hide_date_popup			(EDateEdit	*dedit);
+static void rebuild_time_popup			(EDateEdit	*dedit);
+static gboolean field_set_to_none		(const gchar	*text);
+static gboolean e_date_edit_parse_date		(EDateEdit	*dedit,
+						 const gchar	*date_text,
+						 struct tm	*date_tm);
+static gboolean e_date_edit_parse_time		(EDateEdit	*dedit,
+						 const gchar	*time_text,
+						 struct tm	*time_tm);
+static void on_date_edit_time_selected		(GtkComboBox	*combo,
+						 EDateEdit	*dedit);
+static gint on_time_entry_key_press		(GtkWidget	*widget,
+						 GdkEvent	*key_event,
+						 EDateEdit	*dedit);
+static gint on_time_entry_key_release		(GtkWidget	*widget,
+						 GdkEvent	*key_event,
+						 EDateEdit	*dedit);
+static gint on_date_entry_focus_out		(GtkEntry	*entry,
+						 GdkEventFocus  *event,
+						 EDateEdit	*dedit);
+static gint on_time_entry_focus_out		(GtkEntry	*entry,
+						 GdkEventFocus  *event,
+						 EDateEdit	*dedit);
+static void e_date_edit_update_date_entry	(EDateEdit	*dedit);
+static void e_date_edit_update_time_entry	(EDateEdit	*dedit);
+static void e_date_edit_update_time_combo_state	(EDateEdit	*dedit);
+static void e_date_edit_check_date_changed	(EDateEdit	*dedit);
+static void e_date_edit_check_time_changed	(EDateEdit	*dedit);
+static gboolean e_date_edit_set_date_internal	(EDateEdit	*dedit,
+						 gboolean	 valid,
+						 gboolean	 none,
+						 gint		 year,
+						 gint		 month,
+						 gint		 day);
+static gboolean e_date_edit_set_time_internal	(EDateEdit	*dedit,
+						 gboolean	 valid,
+						 gboolean	 none,
+						 gint		 hour,
+						 gint		 minute);
+
+static gint signals[LAST_SIGNAL];
+
+G_DEFINE_TYPE_WITH_CODE (
+	EDateEdit,
+	e_date_edit,
+	GTK_TYPE_HBOX,
+	G_IMPLEMENT_INTERFACE (
+		E_TYPE_EXTENSIBLE, NULL))
+
+static void
+date_edit_set_property (GObject *object,
+                        guint property_id,
+                        const GValue *value,
+                        GParamSpec *pspec)
+{
+	switch (property_id) {
+		case PROP_ALLOW_NO_DATE_SET:
+			e_date_edit_set_allow_no_date_set (
+				E_DATE_EDIT (object),
+				g_value_get_boolean (value));
+			return;
+
+		case PROP_SHOW_DATE:
+			e_date_edit_set_show_date (
+				E_DATE_EDIT (object),
+				g_value_get_boolean (value));
+			return;
+
+		case PROP_SHOW_TIME:
+			e_date_edit_set_show_time (
+				E_DATE_EDIT (object),
+				g_value_get_boolean (value));
+			return;
+
+		case PROP_SHOW_WEEK_NUMBERS:
+			e_date_edit_set_show_week_numbers (
+				E_DATE_EDIT (object),
+				g_value_get_boolean (value));
+			return;
+
+		case PROP_USE_24_HOUR_FORMAT:
+			e_date_edit_set_use_24_hour_format (
+				E_DATE_EDIT (object),
+				g_value_get_boolean (value));
+			return;
+
+		case PROP_WEEK_START_DAY:
+			e_date_edit_set_week_start_day (
+				E_DATE_EDIT (object),
+				g_value_get_int (value));
+			return;
+
+		case PROP_TWODIGIT_YEAR_CAN_FUTURE:
+			e_date_edit_set_twodigit_year_can_future (
+				E_DATE_EDIT (object),
+				g_value_get_boolean (value));
+			return;
+
+		case PROP_SET_NONE:
+			if (g_value_get_boolean (value))
+				e_date_edit_set_time (E_DATE_EDIT (object), -1);
+			return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+date_edit_get_property (GObject *object,
+                        guint property_id,
+                        GValue *value,
+                        GParamSpec *pspec)
+{
+	switch (property_id) {
+		case PROP_ALLOW_NO_DATE_SET:
+			g_value_set_boolean (
+				value, e_date_edit_get_allow_no_date_set (
+				E_DATE_EDIT (object)));
+			return;
+
+		case PROP_SHOW_DATE:
+			g_value_set_boolean (
+				value, e_date_edit_get_show_date (
+				E_DATE_EDIT (object)));
+			return;
+
+		case PROP_SHOW_TIME:
+			g_value_set_boolean (
+				value, e_date_edit_get_show_time (
+				E_DATE_EDIT (object)));
+			return;
+
+		case PROP_SHOW_WEEK_NUMBERS:
+			g_value_set_boolean (
+				value, e_date_edit_get_show_week_numbers (
+				E_DATE_EDIT (object)));
+			return;
+
+		case PROP_USE_24_HOUR_FORMAT:
+			g_value_set_boolean (
+				value, e_date_edit_get_use_24_hour_format (
+				E_DATE_EDIT (object)));
+			return;
+
+		case PROP_WEEK_START_DAY:
+			g_value_set_int (
+				value, e_date_edit_get_week_start_day (
+				E_DATE_EDIT (object)));
+			return;
+
+		case PROP_TWODIGIT_YEAR_CAN_FUTURE:
+			g_value_set_boolean (
+				value, e_date_edit_get_twodigit_year_can_future (
+				E_DATE_EDIT (object)));
+			return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+date_edit_dispose (GObject *object)
+{
+	EDateEdit *dedit;
+
+	dedit = E_DATE_EDIT (object);
+
+	e_date_edit_set_get_time_callback (dedit, NULL, NULL, NULL);
+
+	if (dedit->priv->cal_popup != NULL) {
+		gtk_widget_destroy (dedit->priv->cal_popup);
+		dedit->priv->cal_popup = NULL;
+	}
+
+	if (dedit->priv->grabbed_keyboard != NULL) {
+		gdk_device_ungrab (
+			dedit->priv->grabbed_keyboard,
+			GDK_CURRENT_TIME);
+		g_object_unref (dedit->priv->grabbed_keyboard);
+		dedit->priv->grabbed_keyboard = NULL;
+	}
+
+	if (dedit->priv->grabbed_pointer != NULL) {
+		gdk_device_ungrab (
+			dedit->priv->grabbed_pointer,
+			GDK_CURRENT_TIME);
+		g_object_unref (dedit->priv->grabbed_pointer);
+		dedit->priv->grabbed_pointer = NULL;
+	}
+
+	/* Chain up to parent's dispose() method. */
+	G_OBJECT_CLASS (e_date_edit_parent_class)->dispose (object);
+}
+
+static void
+e_date_edit_class_init (EDateEditClass *class)
+{
+	GObjectClass *object_class;
+	GtkWidgetClass *widget_class;
+
+	g_type_class_add_private (class, sizeof (EDateEditPrivate));
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->set_property = date_edit_set_property;
+	object_class->get_property = date_edit_get_property;
+	object_class->dispose = date_edit_dispose;
+
+	widget_class = GTK_WIDGET_CLASS (class);
+	widget_class->mnemonic_activate = e_date_edit_mnemonic_activate;
+	widget_class->grab_focus = e_date_edit_grab_focus;
+
+	g_object_class_install_property (
+		object_class,
+		PROP_ALLOW_NO_DATE_SET,
+		g_param_spec_boolean (
+			"allow-no-date-set",
+			"Allow No Date Set",
+			NULL,
+			FALSE,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_SHOW_DATE,
+		g_param_spec_boolean (
+			"show-date",
+			"Show Date",
+			NULL,
+			TRUE,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_SHOW_TIME,
+		g_param_spec_boolean (
+			"show-time",
+			"Show Time",
+			NULL,
+			TRUE,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_SHOW_WEEK_NUMBERS,
+		g_param_spec_boolean (
+			"show-week-numbers",
+			"Show Week Numbers",
+			NULL,
+			TRUE,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_USE_24_HOUR_FORMAT,
+		g_param_spec_boolean (
+			"use-24-hour-format",
+			"Use 24-Hour Format",
+			NULL,
+			TRUE,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_WEEK_START_DAY,
+		g_param_spec_int (
+			"week-start-day",
+			"Week Start Day",
+			NULL,
+			0,  /* Monday */
+			6,  /* Sunday */
+			0,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_TWODIGIT_YEAR_CAN_FUTURE,
+		g_param_spec_boolean (
+			"twodigit-year-can-future",
+			"Two-digit year can be treated as future",
+			NULL,
+			TRUE,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_SET_NONE,
+		g_param_spec_boolean (
+			"set-none",
+			"Sets None as selected date",
+			NULL,
+			FALSE,
+			G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
+
+	signals[CHANGED] = g_signal_new (
+		"changed",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_FIRST,
+		G_STRUCT_OFFSET (EDateEditClass, changed),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__VOID,
+		G_TYPE_NONE, 0);
+}
+
+static void
+e_date_edit_init (EDateEdit *dedit)
+{
+	dedit->priv = E_DATE_EDIT_GET_PRIVATE (dedit);
+
+	dedit->priv->show_date = TRUE;
+	dedit->priv->show_time = TRUE;
+	dedit->priv->use_24_hour_format = TRUE;
+
+	dedit->priv->make_time_insensitive = FALSE;
+
+	dedit->priv->lower_hour = 0;
+	dedit->priv->upper_hour = 24;
+
+	dedit->priv->date_is_valid = TRUE;
+	dedit->priv->date_set_to_none = TRUE;
+	dedit->priv->time_is_valid = TRUE;
+	dedit->priv->time_set_to_none = TRUE;
+	dedit->priv->time_callback = NULL;
+	dedit->priv->time_callback_data = NULL;
+	dedit->priv->time_callback_destroy = NULL;
+
+	dedit->priv->twodigit_year_can_future = TRUE;
+	dedit->priv->has_been_changed = FALSE;
+
+	create_children (dedit);
+
+	/* Set it to the current time. */
+	e_date_edit_set_time (dedit, 0);
+
+	e_extensible_load_extensions (E_EXTENSIBLE (dedit));
+}
+
+/**
+ * e_date_edit_new:
+ *
+ * Description: Creates a new #EDateEdit widget which can be used
+ * to provide an easy to use way for entering dates and times.
+ *
+ * Returns: a new #EDateEdit widget.
+ */
+GtkWidget *
+e_date_edit_new (void)
+{
+	EDateEdit *dedit;
+	AtkObject *a11y;
+
+	dedit = g_object_new (E_TYPE_DATE_EDIT, NULL);
+	a11y = gtk_widget_get_accessible (GTK_WIDGET (dedit));
+	atk_object_set_name (a11y, _("Date and Time"));
+
+	return GTK_WIDGET (dedit);
+}
+
+static void
+create_children (EDateEdit *dedit)
+{
+	EDateEditPrivate *priv;
+	ECalendar *calendar;
+	GtkWidget *frame, *arrow;
+	GtkWidget *vbox, *bbox;
+	GtkWidget *child;
+	AtkObject *a11y;
+	GtkListStore *time_store;
+	GList *cells;
+	GtkCssProvider *css_provider;
+	GtkStyleContext *style_context;
+	const gchar *css;
+	GError *error = NULL;
+
+	priv = dedit->priv;
+
+	priv->date_entry  = gtk_entry_new ();
+	a11y = gtk_widget_get_accessible (priv->date_entry);
+	atk_object_set_description (a11y, _("Text entry to input date"));
+	atk_object_set_name (a11y, _("Date"));
+	gtk_box_pack_start (GTK_BOX (dedit), priv->date_entry, FALSE, TRUE, 0);
+	gtk_widget_set_size_request (priv->date_entry, 100, -1);
+
+	g_signal_connect (
+		priv->date_entry, "key_press_event",
+		G_CALLBACK (on_date_entry_key_press), dedit);
+	g_signal_connect (
+		priv->date_entry, "key_release_event",
+		G_CALLBACK (on_date_entry_key_release), dedit);
+	g_signal_connect_after (
+		priv->date_entry, "focus_out_event",
+		G_CALLBACK (on_date_entry_focus_out), dedit);
+
+	priv->date_button = gtk_button_new ();
+	g_signal_connect (
+		priv->date_button, "clicked",
+		G_CALLBACK (on_date_button_clicked), dedit);
+	gtk_box_pack_start (
+		GTK_BOX (dedit), priv->date_button,
+		FALSE, FALSE, 0);
+	a11y = gtk_widget_get_accessible (priv->date_button);
+	atk_object_set_description (a11y, _("Click this button to show a calendar"));
+	atk_object_set_name (a11y, _("Date"));
+
+	arrow = gtk_arrow_new (GTK_ARROW_DOWN, GTK_SHADOW_NONE);
+	gtk_container_add (GTK_CONTAINER (priv->date_button), arrow);
+	gtk_widget_show (arrow);
+
+	if (priv->show_date) {
+		gtk_widget_show (priv->date_entry);
+		gtk_widget_show (priv->date_button);
+	}
+
+	/* This is just to create a space between the date & time parts. */
+	priv->space = gtk_drawing_area_new ();
+	gtk_box_pack_start (GTK_BOX (dedit), priv->space, FALSE, FALSE, 2);
+
+	time_store = gtk_list_store_new (1, G_TYPE_STRING);
+	priv->time_combo = gtk_combo_box_new_with_model_and_entry (
+		GTK_TREE_MODEL (time_store));
+	gtk_combo_box_set_entry_text_column (GTK_COMBO_BOX (priv->time_combo), 0);
+	g_object_unref (time_store);
+
+	css_provider = gtk_css_provider_new ();
+	css = "GtkComboBox { -GtkComboBox-appears-as-list: 1; }";
+	gtk_css_provider_load_from_data (css_provider, css, -1, &error);
+	style_context = gtk_widget_get_style_context (priv->time_combo);
+	if (error == NULL) {
+		gtk_style_context_add_provider (
+			style_context,
+			GTK_STYLE_PROVIDER (css_provider),
+			GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+	} else {
+		g_warning ("%s: %s", G_STRFUNC, error->message);
+		g_clear_error (&error);
+	}
+	g_object_unref (css_provider);
+
+	child = gtk_bin_get_child (GTK_BIN (priv->time_combo));
+
+	/* We need to make sure labels are right-aligned, since we want
+	 * digits to line up, and with a nonproportional font, the width
+	 * of a space != width of a digit.  Technically, only 12-hour
+	 * format needs this, but we do it always, for consistency. */
+	g_object_set (child, "xalign", 1.0, NULL);
+	cells = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (priv->time_combo));
+	if (cells) {
+		g_object_set (GTK_CELL_RENDERER (cells->data), "xalign", 1.0, NULL);
+		g_list_free (cells);
+	}
+
+	gtk_box_pack_start (GTK_BOX (dedit), priv->time_combo, FALSE, TRUE, 0);
+	gtk_widget_set_size_request (priv->time_combo, 110, -1);
+	rebuild_time_popup (dedit);
+	a11y = gtk_widget_get_accessible (priv->time_combo);
+	atk_object_set_description (a11y, _("Drop-down combination box to select time"));
+	atk_object_set_name (a11y, _("Time"));
+
+	g_signal_connect (
+		child, "key_press_event",
+		G_CALLBACK (on_time_entry_key_press), dedit);
+	g_signal_connect (
+		child, "key_release_event",
+		G_CALLBACK (on_time_entry_key_release), dedit);
+	g_signal_connect_after (
+		child, "focus_out_event",
+		G_CALLBACK (on_time_entry_focus_out), dedit);
+	g_signal_connect_after (
+		priv->time_combo, "changed",
+		G_CALLBACK (on_date_edit_time_selected), dedit);
+
+	if (priv->show_time || priv->make_time_insensitive)
+		gtk_widget_show (priv->time_combo);
+
+	if (!priv->show_time && priv->make_time_insensitive)
+		gtk_widget_set_sensitive (priv->time_combo, FALSE);
+
+	if (priv->show_date
+	    && (priv->show_time || priv->make_time_insensitive))
+		gtk_widget_show (priv->space);
+
+	priv->cal_popup = gtk_window_new (GTK_WINDOW_POPUP);
+	gtk_window_set_type_hint (
+		GTK_WINDOW (priv->cal_popup),
+		GDK_WINDOW_TYPE_HINT_COMBO);
+	gtk_widget_set_events (
+		priv->cal_popup,
+		gtk_widget_get_events (priv->cal_popup)
+		| GDK_KEY_PRESS_MASK);
+	g_signal_connect (
+		priv->cal_popup, "delete_event",
+		G_CALLBACK (on_date_popup_delete_event), dedit);
+	g_signal_connect (
+		priv->cal_popup, "key_press_event",
+		G_CALLBACK (on_date_popup_key_press), dedit);
+	g_signal_connect (
+		priv->cal_popup, "button_press_event",
+		G_CALLBACK (on_date_popup_button_press), dedit);
+	gtk_window_set_resizable (GTK_WINDOW (priv->cal_popup), TRUE);
+
+	frame = gtk_frame_new (NULL);
+	gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_OUT);
+	gtk_container_add (GTK_CONTAINER (priv->cal_popup), frame);
+	gtk_widget_show (frame);
+
+	vbox = gtk_vbox_new (FALSE, 0);
+	gtk_container_add (GTK_CONTAINER (frame), vbox);
+	gtk_widget_show (vbox);
+
+	priv->calendar = e_calendar_new ();
+	calendar = E_CALENDAR (priv->calendar);
+	gnome_canvas_item_set (
+		GNOME_CANVAS_ITEM (calendar->calitem),
+		"maximum_days_selected", 1,
+		"move_selection_when_moving", FALSE,
+		NULL);
+
+	g_signal_connect (
+		calendar->calitem, "selection_changed",
+		G_CALLBACK (on_date_popup_date_selected), dedit);
+
+	gtk_box_pack_start (GTK_BOX (vbox), priv->calendar, FALSE, FALSE, 0);
+	gtk_widget_show (priv->calendar);
+
+	bbox = gtk_hbutton_box_new ();
+	gtk_container_set_border_width (GTK_CONTAINER (bbox), 4);
+	gtk_box_set_spacing (GTK_BOX (bbox), 2);
+	gtk_box_pack_start (GTK_BOX (vbox), bbox, FALSE, FALSE, 0);
+	gtk_widget_show (bbox);
+
+	priv->now_button = gtk_button_new_with_mnemonic (_("No_w"));
+	gtk_container_add (GTK_CONTAINER (bbox), priv->now_button);
+	gtk_widget_show (priv->now_button);
+	g_signal_connect (
+		priv->now_button, "clicked",
+		G_CALLBACK (on_date_popup_now_button_clicked), dedit);
+
+	priv->today_button = gtk_button_new_with_mnemonic (_("_Today"));
+	gtk_container_add (GTK_CONTAINER (bbox), priv->today_button);
+	gtk_widget_show (priv->today_button);
+	g_signal_connect (
+		priv->today_button, "clicked",
+		G_CALLBACK (on_date_popup_today_button_clicked), dedit);
+
+	/* Note that we don't show this here, since by default a 'None' date
+	 * is not permitted. */
+	priv->none_button = gtk_button_new_with_mnemonic (_("_None"));
+	gtk_container_add (GTK_CONTAINER (bbox), priv->none_button);
+	g_signal_connect (
+		priv->none_button, "clicked",
+		G_CALLBACK (on_date_popup_none_button_clicked), dedit);
+	g_object_bind_property (
+		dedit, "allow-no-date-set",
+		priv->none_button, "visible",
+		G_BINDING_SYNC_CREATE);
+}
+
+/* GtkWidget::mnemonic_activate() handler for the EDateEdit */
+static gboolean
+e_date_edit_mnemonic_activate (GtkWidget *widget,
+                               gboolean group_cycling)
+{
+	e_date_edit_grab_focus (widget);
+	return TRUE;
+}
+
+/* Grab_focus handler for the EDateEdit. If the date field is being shown, we
+ * grab the focus to that, otherwise we grab it to the time field. */
+static void
+e_date_edit_grab_focus (GtkWidget *widget)
+{
+	EDateEdit *dedit;
+	GtkWidget *child;
+
+	g_return_if_fail (E_IS_DATE_EDIT (widget));
+
+	dedit = E_DATE_EDIT (widget);
+	child = gtk_bin_get_child (GTK_BIN (dedit->priv->time_combo));
+
+	if (dedit->priv->show_date)
+		gtk_widget_grab_focus (dedit->priv->date_entry);
+	else
+		gtk_widget_grab_focus (child);
+}
+
+/**
+ * e_date_edit_set_editable:
+ * @dedit: an #EDateEdit widget.
+ * @editable: whether or not the widget should accept edits.
+ *
+ * Allows the programmer to disallow editing (and the popping up of
+ * the calendar widget), while still allowing the user to select the
+ * date from the GtkEntry.
+ */
+void
+e_date_edit_set_editable (EDateEdit *dedit,
+                          gboolean editable)
+{
+	EDateEditPrivate *priv;
+
+	g_return_if_fail (E_IS_DATE_EDIT (dedit));
+
+	priv = dedit->priv;
+
+	gtk_editable_set_editable (GTK_EDITABLE (priv->date_entry), editable);
+	gtk_widget_set_sensitive (priv->date_button, editable);
+}
+
+/**
+ * e_date_edit_get_time:
+ * @dedit: an #EDateEdit widget.
+ * @the_time: returns the last valid time entered.
+ * @Returns: the last valid time entered, or -1 if the time is not set.
+ *
+ * Returns the last valid time entered. If empty times are valid, by calling
+ * e_date_edit_set_allow_no_date_set(), then it may return -1.
+ *
+ * Note that the last time entered may actually have been invalid. You can
+ * check this with e_date_edit_time_is_valid().
+ */
+time_t
+e_date_edit_get_time (EDateEdit *dedit)
+{
+	EDateEditPrivate *priv;
+	struct tm tmp_tm = { 0 };
+
+	g_return_val_if_fail (E_IS_DATE_EDIT (dedit), -1);
+
+	priv = dedit->priv;
+
+	/* Try to parse any new value now. */
+	e_date_edit_check_date_changed (dedit);
+	e_date_edit_check_time_changed (dedit);
+
+	if (priv->date_set_to_none)
+		return -1;
+
+	tmp_tm.tm_year = priv->year;
+	tmp_tm.tm_mon = priv->month;
+	tmp_tm.tm_mday = priv->day;
+
+	if (!priv->show_time || priv->time_set_to_none) {
+		tmp_tm.tm_hour = 0;
+		tmp_tm.tm_min = 0;
+	} else {
+		tmp_tm.tm_hour = priv->hour;
+		tmp_tm.tm_min = priv->minute;
+	}
+	tmp_tm.tm_sec = 0;
+	tmp_tm.tm_isdst = -1;
+
+	return mktime (&tmp_tm);
+}
+
+/**
+ * e_date_edit_set_time:
+ * @dedit: the EDateEdit widget
+ * @the_time: The time and date that should be set on the widget
+ *
+ * Description:  Changes the displayed date and time in the EDateEdit
+ * widget to be the one represented by @the_time.  If @the_time is 0
+ * then current time is used. If it is -1, then the date is set to None.
+ *
+ * Note that the time is converted to local time using the Unix timezone,
+ * so if you are using your own timezones then you should use
+ * e_date_edit_set_date() and e_date_edit_set_time_of_day() instead.
+ */
+void
+e_date_edit_set_time (EDateEdit *dedit,
+                      time_t the_time)
+{
+	EDateEditPrivate *priv;
+	struct tm tmp_tm;
+	gboolean date_changed = FALSE, time_changed = FALSE;
+
+	g_return_if_fail (E_IS_DATE_EDIT (dedit));
+
+	priv = dedit->priv;
+
+	if (the_time == -1) {
+		date_changed = e_date_edit_set_date_internal (
+			dedit, TRUE,
+			TRUE, 0, 0, 0);
+		time_changed = e_date_edit_set_time_internal (
+			dedit, TRUE,
+			TRUE, 0, 0);
+	} else {
+		if (the_time == 0) {
+			if (priv->time_callback) {
+				tmp_tm = (*priv->time_callback) (dedit, priv->time_callback_data);
+			} else {
+				the_time = time (NULL);
+				tmp_tm = *localtime (&the_time);
+			}
+		} else {
+			tmp_tm = *localtime (&the_time);
+		}
+
+		date_changed = e_date_edit_set_date_internal (
+			dedit, TRUE,
+			FALSE,
+			tmp_tm.tm_year,
+			tmp_tm.tm_mon,
+			tmp_tm.tm_mday);
+		time_changed = e_date_edit_set_time_internal (
+			dedit, TRUE,
+			FALSE,
+			tmp_tm.tm_hour,
+			tmp_tm.tm_min);
+	}
+
+	e_date_edit_update_date_entry (dedit);
+	e_date_edit_update_time_entry (dedit);
+	e_date_edit_update_time_combo_state (dedit);
+
+	/* Emit the signals if the date and/or time has actually changed. */
+	if (date_changed || time_changed)
+		g_signal_emit (dedit, signals[CHANGED], 0);
+}
+
+/**
+ * e_date_edit_get_date:
+ * @dedit: an #EDateEdit widget.
+ * @year: returns the year set.
+ * @month: returns the month set (1 - 12).
+ * @day: returns the day set (1 - 31).
+ * @Returns: TRUE if a time was set, or FALSE if the field is empty or 'None'.
+ *
+ * Returns the last valid date entered into the date field.
+ */
+gboolean
+e_date_edit_get_date (EDateEdit *dedit,
+                      gint *year,
+                      gint *month,
+                      gint *day)
+{
+	EDateEditPrivate *priv;
+
+	g_return_val_if_fail (E_IS_DATE_EDIT (dedit), FALSE);
+
+	priv = dedit->priv;
+
+	/* Try to parse any new value now. */
+	e_date_edit_check_date_changed (dedit);
+
+	*year = priv->year + 1900;
+	*month = priv->month + 1;
+	*day = priv->day;
+
+	if (priv->date_set_to_none
+	    && e_date_edit_get_allow_no_date_set (dedit))
+		return FALSE;
+
+	return TRUE;
+}
+
+/**
+ * e_date_edit_set_date:
+ * @dedit: an #EDateEdit widget.
+ * @year: the year to set.
+ * @month: the month to set (1 - 12).
+ * @day: the day to set (1 - 31).
+ *
+ * Sets the date in the date field.
+ */
+void
+e_date_edit_set_date (EDateEdit *dedit,
+                      gint year,
+                      gint month,
+                      gint day)
+{
+	gboolean date_changed = FALSE;
+
+	g_return_if_fail (E_IS_DATE_EDIT (dedit));
+
+	date_changed = e_date_edit_set_date_internal (
+		dedit, TRUE, FALSE,
+		year - 1900, month - 1,
+		day);
+
+	e_date_edit_update_date_entry (dedit);
+	e_date_edit_update_time_combo_state (dedit);
+
+	/* Emit the signals if the date has actually changed. */
+	if (date_changed)
+		g_signal_emit (dedit, signals[CHANGED], 0);
+}
+
+/**
+ * e_date_edit_get_time_of_day:
+ * @dedit: an #EDateEdit widget.
+ * @hour: returns the hour set, or 0 if the time isn't set.
+ * @minute: returns the minute set, or 0 if the time isn't set.
+ * @Returns: TRUE if a time was set, or FALSE if the field is empty or 'None'.
+ *
+ * Returns the last valid time entered into the time field.
+ */
+gboolean
+e_date_edit_get_time_of_day (EDateEdit *dedit,
+                             gint *hour,
+                             gint *minute)
+{
+	EDateEditPrivate *priv;
+
+	g_return_val_if_fail (E_IS_DATE_EDIT (dedit), FALSE);
+
+	priv = dedit->priv;
+
+	/* Try to parse any new value now. */
+	e_date_edit_check_time_changed (dedit);
+
+	if (priv->time_set_to_none) {
+		*hour = 0;
+		*minute = 0;
+		return FALSE;
+	} else {
+		*hour = priv->hour;
+		*minute = priv->minute;
+		return TRUE;
+	}
+}
+
+/**
+ * e_date_edit_set_time_of_day:
+ * @dedit: an #EDateEdit widget.
+ * @hour: the hour to set, or -1 to set the time to None (i.e. empty).
+ * @minute: the minute to set.
+ *
+ * Description: Sets the time in the time field.
+ */
+void
+e_date_edit_set_time_of_day (EDateEdit *dedit,
+                             gint hour,
+                             gint minute)
+{
+	EDateEditPrivate *priv;
+	gboolean time_changed = FALSE;
+
+	g_return_if_fail (E_IS_DATE_EDIT (dedit));
+
+	priv = dedit->priv;
+
+	if (hour == -1) {
+		gboolean allow_no_date_set = e_date_edit_get_allow_no_date_set (dedit);
+		g_return_if_fail (allow_no_date_set);
+		if (!priv->time_set_to_none) {
+			priv->time_set_to_none = TRUE;
+			time_changed = TRUE;
+		}
+	} else if (priv->time_set_to_none
+		   || priv->hour != hour
+		   || priv->minute != minute) {
+		priv->time_set_to_none = FALSE;
+		priv->hour = hour;
+		priv->minute = minute;
+		time_changed = TRUE;
+	}
+
+	e_date_edit_update_time_entry (dedit);
+
+	if (time_changed)
+		g_signal_emit (dedit, signals[CHANGED], 0);
+}
+
+void
+e_date_edit_set_date_and_time_of_day (EDateEdit *dedit,
+                                      gint year,
+                                      gint month,
+                                      gint day,
+                                      gint hour,
+                                      gint minute)
+{
+	gboolean date_changed, time_changed;
+
+	g_return_if_fail (E_IS_DATE_EDIT (dedit));
+
+	date_changed = e_date_edit_set_date_internal (
+		dedit, TRUE, FALSE,
+		year - 1900, month - 1, day);
+	time_changed = e_date_edit_set_time_internal (
+		dedit, TRUE, FALSE,
+		hour, minute);
+
+	e_date_edit_update_date_entry (dedit);
+	e_date_edit_update_time_entry (dedit);
+	e_date_edit_update_time_combo_state (dedit);
+
+	if (date_changed || time_changed)
+		g_signal_emit (dedit, signals[CHANGED], 0);
+}
+
+/**
+ * e_date_edit_get_show_date:
+ * @dedit: an #EDateEdit widget.
+ * @Returns: Whether the date field is shown.
+ *
+ * Description: Returns TRUE if the date field is currently shown.
+ */
+gboolean
+e_date_edit_get_show_date (EDateEdit *dedit)
+{
+	g_return_val_if_fail (E_IS_DATE_EDIT (dedit), TRUE);
+
+	return dedit->priv->show_date;
+}
+
+/**
+ * e_date_edit_set_show_date:
+ * @dedit: an #EDateEdit widget.
+ * @show_date: TRUE if the date field should be shown.
+ *
+ * Description: Specifies whether the date field should be shown. The date
+ * field would be hidden if only a time needed to be entered.
+ */
+void
+e_date_edit_set_show_date (EDateEdit *dedit,
+                           gboolean show_date)
+{
+	EDateEditPrivate *priv;
+
+	g_return_if_fail (E_IS_DATE_EDIT (dedit));
+
+	priv = dedit->priv;
+
+	if (priv->show_date == show_date)
+		return;
+
+	priv->show_date = show_date;
+
+	if (show_date) {
+		gtk_widget_show (priv->date_entry);
+		gtk_widget_show (priv->date_button);
+	} else {
+		gtk_widget_hide (priv->date_entry);
+		gtk_widget_hide (priv->date_button);
+	}
+
+	e_date_edit_update_time_combo_state (dedit);
+
+	if (priv->show_date
+	    && (priv->show_time || priv->make_time_insensitive))
+		gtk_widget_show (priv->space);
+	else
+		gtk_widget_hide (priv->space);
+
+	g_object_notify (G_OBJECT (dedit), "show-date");
+}
+
+/**
+ * e_date_edit_get_show_time:
+ * @dedit: an #EDateEdit widget
+ * @Returns: Whether the time field is shown.
+ *
+ * Description: Returns TRUE if the time field is currently shown.
+ */
+gboolean
+e_date_edit_get_show_time (EDateEdit *dedit)
+{
+	g_return_val_if_fail (E_IS_DATE_EDIT (dedit), TRUE);
+
+	return dedit->priv->show_time;
+}
+
+/**
+ * e_date_edit_set_show_time:
+ * @dedit: an #EDateEdit widget
+ * @show_time: TRUE if the time field should be shown.
+ *
+ * Description: Specifies whether the time field should be shown. The time
+ * field would be hidden if only a date needed to be entered.
+ */
+void
+e_date_edit_set_show_time (EDateEdit *dedit,
+                           gboolean show_time)
+{
+	EDateEditPrivate *priv;
+
+	g_return_if_fail (E_IS_DATE_EDIT (dedit));
+
+	priv = dedit->priv;
+
+	if (priv->show_time == show_time)
+		return;
+
+	priv->show_time = show_time;
+
+	e_date_edit_update_time_combo_state (dedit);
+
+	g_object_notify (G_OBJECT (dedit), "show-time");
+}
+
+/**
+ * e_date_edit_get_make_time_insensitive:
+ * @dedit: an #EDateEdit widget
+ * @Returns: Whether the time field is be made insensitive instead of hiding
+ * it.
+ *
+ * Description: Returns TRUE if the time field is made insensitive instead of
+ * hiding it.
+ */
+gboolean
+e_date_edit_get_make_time_insensitive (EDateEdit *dedit)
+{
+	g_return_val_if_fail (E_IS_DATE_EDIT (dedit), TRUE);
+
+	return dedit->priv->make_time_insensitive;
+}
+
+/**
+ * e_date_edit_set_make_time_insensitive:
+ * @dedit: an #EDateEdit widget
+ * @make_insensitive: TRUE if the time field should be made insensitive instead
+ * of hiding it.
+ *
+ * Description: Specifies whether the time field should be made insensitive
+ * rather than hiding it. Note that this doesn't make it insensitive - you
+ * need to call e_date_edit_set_show_time() with FALSE as show_time to do that.
+ *
+ * This is useful if you want to disable the time field, but don't want it to
+ * disappear as that may affect the layout of the widgets.
+ */
+void
+e_date_edit_set_make_time_insensitive (EDateEdit *dedit,
+                                       gboolean make_insensitive)
+{
+	EDateEditPrivate *priv;
+
+	g_return_if_fail (E_IS_DATE_EDIT (dedit));
+
+	priv = dedit->priv;
+
+	if (priv->make_time_insensitive == make_insensitive)
+		return;
+
+	priv->make_time_insensitive = make_insensitive;
+
+	e_date_edit_update_time_combo_state (dedit);
+}
+
+/**
+ * e_date_edit_get_week_start_day:
+ * @dedit: an #EDateEdit widget
+ * @Returns: the week start day, from 0 (Monday) to 6 (Sunday).
+ *
+ * Description: Returns the week start day currently used in the calendar
+ * popup.
+ */
+gint
+e_date_edit_get_week_start_day (EDateEdit *dedit)
+{
+	gint week_start_day;
+
+	g_return_val_if_fail (E_IS_DATE_EDIT (dedit), 1);
+
+	g_object_get (
+		E_CALENDAR (dedit->priv->calendar)->calitem,
+		"week_start_day", &week_start_day, NULL);
+
+	return week_start_day;
+}
+
+/**
+ * e_date_edit_set_week_start_day:
+ * @dedit: an #EDateEdit widget
+ * @week_start_day: the week start day, from 0 (Monday) to 6 (Sunday).
+ *
+ * Description: Sets the week start day to use in the calendar popup.
+ */
+void
+e_date_edit_set_week_start_day (EDateEdit *dedit,
+                                gint week_start_day)
+{
+	g_return_if_fail (E_IS_DATE_EDIT (dedit));
+
+	gnome_canvas_item_set (
+		GNOME_CANVAS_ITEM (E_CALENDAR (dedit->priv->calendar)->calitem),
+		"week_start_day", week_start_day, NULL);
+
+	g_object_notify (G_OBJECT (dedit), "week-start-day");
+}
+
+/* Whether we show week numbers in the date popup. */
+gboolean
+e_date_edit_get_show_week_numbers (EDateEdit *dedit)
+{
+	gboolean show_week_numbers;
+
+	g_return_val_if_fail (E_IS_DATE_EDIT (dedit), FALSE);
+
+	g_object_get (
+		E_CALENDAR (dedit->priv->calendar)->calitem,
+		"show_week_numbers", &show_week_numbers, NULL);
+
+	return show_week_numbers;
+}
+
+void
+e_date_edit_set_show_week_numbers (EDateEdit *dedit,
+                                   gboolean show_week_numbers)
+{
+	g_return_if_fail (E_IS_DATE_EDIT (dedit));
+
+	gnome_canvas_item_set (
+		GNOME_CANVAS_ITEM (E_CALENDAR (dedit->priv->calendar)->calitem),
+		"show_week_numbers", show_week_numbers, NULL);
+
+	g_object_notify (G_OBJECT (dedit), "show-week-numbers");
+}
+
+/* Whether we use 24 hour format in the time field & popup. */
+gboolean
+e_date_edit_get_use_24_hour_format (EDateEdit *dedit)
+{
+	g_return_val_if_fail (E_IS_DATE_EDIT (dedit), TRUE);
+
+	return dedit->priv->use_24_hour_format;
+}
+
+void
+e_date_edit_set_use_24_hour_format (EDateEdit *dedit,
+                                    gboolean use_24_hour_format)
+{
+	g_return_if_fail (E_IS_DATE_EDIT (dedit));
+
+	if (dedit->priv->use_24_hour_format == use_24_hour_format)
+		return;
+
+	dedit->priv->use_24_hour_format = use_24_hour_format;
+
+	rebuild_time_popup (dedit);
+
+	e_date_edit_update_time_entry (dedit);
+
+	g_object_notify (G_OBJECT (dedit), "use-24-hour-format");
+}
+
+/* Whether we allow the date to be set to 'None'. e_date_edit_get_time() will
+ * return (time_t) -1 in this case. */
+gboolean
+e_date_edit_get_allow_no_date_set (EDateEdit *dedit)
+{
+	g_return_val_if_fail (E_IS_DATE_EDIT (dedit), FALSE);
+
+	return dedit->priv->allow_no_date_set;
+}
+
+void
+e_date_edit_set_allow_no_date_set (EDateEdit *dedit,
+                                   gboolean allow_no_date_set)
+{
+	g_return_if_fail (E_IS_DATE_EDIT (dedit));
+
+	if (dedit->priv->allow_no_date_set == allow_no_date_set)
+		return;
+
+	dedit->priv->allow_no_date_set = allow_no_date_set;
+
+	if (!allow_no_date_set) {
+		/* If the date is showing, we make sure it isn't 'None' (we
+		 * don't really mind if the time is empty), else if just the
+		 * time is showing we make sure it isn't 'None'. */
+		if (dedit->priv->show_date) {
+			if (dedit->priv->date_set_to_none)
+				e_date_edit_set_time (dedit, 0);
+		} else {
+			if (dedit->priv->time_set_to_none)
+				e_date_edit_set_time (dedit, 0);
+		}
+	}
+
+	g_object_notify (G_OBJECT (dedit), "allow-no-date-set");
+}
+
+/* The range of time to show in the time combo popup. */
+void
+e_date_edit_get_time_popup_range (EDateEdit *dedit,
+                                  gint *lower_hour,
+                                  gint *upper_hour)
+{
+	g_return_if_fail (E_IS_DATE_EDIT (dedit));
+
+	*lower_hour = dedit->priv->lower_hour;
+	*upper_hour = dedit->priv->upper_hour;
+}
+
+void
+e_date_edit_set_time_popup_range (EDateEdit *dedit,
+                                  gint lower_hour,
+                                  gint upper_hour)
+{
+	EDateEditPrivate *priv;
+
+	g_return_if_fail (E_IS_DATE_EDIT (dedit));
+
+	priv = dedit->priv;
+
+	if (priv->lower_hour == lower_hour
+	    && priv->upper_hour == upper_hour)
+		return;
+
+	priv->lower_hour = lower_hour;
+	priv->upper_hour = upper_hour;
+
+	rebuild_time_popup (dedit);
+
+	/* Setting the combo list items seems to mess up the time entry, so
+	 * we set it again. We have to reset it to its last valid time. */
+	priv->time_is_valid = TRUE;
+	e_date_edit_update_time_entry (dedit);
+}
+
+/* The arrow button beside the date field has been clicked, so we show the
+ * popup with the ECalendar in. */
+static void
+on_date_button_clicked (GtkWidget *widget,
+                        EDateEdit *dedit)
+{
+	GdkEvent *event;
+
+	/* Obtain the GdkEvent that triggered
+	 * the date button's "clicked" signal. */
+	event = gtk_get_current_event ();
+	e_date_edit_show_date_popup (dedit, event);
+}
+
+static void
+e_date_edit_show_date_popup (EDateEdit *dedit,
+                             GdkEvent *event)
+{
+	EDateEditPrivate *priv;
+	ECalendar *calendar;
+	GdkDevice *event_device;
+	GdkDevice *assoc_device;
+	GdkDevice *keyboard_device;
+	GdkDevice *pointer_device;
+	GdkWindow *window;
+	GdkGrabStatus grab_status;
+	struct tm mtm;
+	const gchar *date_text;
+	GDate selected_day;
+	gboolean clear_selection = FALSE;
+	guint event_time;
+
+	priv = dedit->priv;
+	calendar = E_CALENDAR (priv->calendar);
+
+	date_text = gtk_entry_get_text (GTK_ENTRY (priv->date_entry));
+	if (field_set_to_none (date_text)
+	    || !e_date_edit_parse_date (dedit, date_text, &mtm))
+		clear_selection = TRUE;
+
+	if (clear_selection) {
+		e_calendar_item_set_selection (calendar->calitem, NULL, NULL);
+	} else {
+		g_date_clear (&selected_day, 1);
+		g_date_set_dmy (
+			&selected_day, mtm.tm_mday, mtm.tm_mon + 1,
+			mtm.tm_year + 1900);
+		e_calendar_item_set_selection (
+			calendar->calitem,
+			&selected_day, NULL);
+	}
+
+	/* FIXME: Hack. Change ECalendarItem so it doesn't queue signal
+	 * emissions. */
+	calendar->calitem->selection_changed = FALSE;
+
+	position_date_popup (dedit);
+	gtk_widget_show (priv->cal_popup);
+	gtk_widget_grab_focus (priv->cal_popup);
+	gtk_grab_add (priv->cal_popup);
+
+	window = gtk_widget_get_window (priv->cal_popup);
+
+	g_return_if_fail (priv->grabbed_keyboard == NULL);
+	g_return_if_fail (priv->grabbed_pointer == NULL);
+
+	event_device = gdk_event_get_device (event);
+	assoc_device = gdk_device_get_associated_device (event_device);
+
+	event_time = gdk_event_get_time (event);
+
+	if (gdk_device_get_source (event_device) == GDK_SOURCE_KEYBOARD) {
+		keyboard_device = event_device;
+		pointer_device = assoc_device;
+	} else {
+		keyboard_device = assoc_device;
+		pointer_device = event_device;
+	}
+
+	if (keyboard_device != NULL) {
+		grab_status = gdk_device_grab (
+			keyboard_device,
+			window,
+			GDK_OWNERSHIP_WINDOW,
+			TRUE,
+			GDK_KEY_PRESS_MASK |
+			GDK_KEY_RELEASE_MASK,
+			NULL,
+			event_time);
+		if (grab_status == GDK_GRAB_SUCCESS) {
+			priv->grabbed_keyboard =
+				g_object_ref (keyboard_device);
+		}
+	}
+
+	if (pointer_device != NULL) {
+		grab_status = gdk_device_grab (
+			pointer_device,
+			window,
+			GDK_OWNERSHIP_WINDOW,
+			TRUE,
+			GDK_BUTTON_PRESS_MASK |
+			GDK_BUTTON_RELEASE_MASK |
+			GDK_POINTER_MOTION_MASK,
+			NULL,
+			event_time);
+		if (grab_status == GDK_GRAB_SUCCESS) {
+			priv->grabbed_pointer =
+				g_object_ref (pointer_device);
+		} else if (priv->grabbed_keyboard != NULL) {
+			gdk_device_ungrab (
+				priv->grabbed_keyboard,
+				event_time);
+			g_object_unref (priv->grabbed_keyboard);
+			priv->grabbed_keyboard = NULL;
+		}
+	}
+
+	gdk_window_focus (window, event_time);
+}
+
+/* This positions the date popup below and to the left of the arrow button,
+ * just before it is shown. */
+static void
+position_date_popup (EDateEdit *dedit)
+{
+	gint x, y;
+	gint win_x, win_y;
+	gint bwidth, bheight;
+	GtkWidget *toplevel;
+	GdkWindow *window;
+	GtkRequisition cal_req, button_req;
+	gint screen_width, screen_height;
+
+	gtk_widget_get_preferred_size (dedit->priv->cal_popup, &cal_req, NULL);
+
+	gtk_widget_get_preferred_size (dedit->priv->date_button, &button_req, NULL);
+	bwidth = button_req.width;
+	gtk_widget_get_preferred_size (
+		gtk_widget_get_parent (dedit->priv->date_button), &button_req, NULL);
+	bheight = button_req.height;
+
+	gtk_widget_translate_coordinates (
+		dedit->priv->date_button,
+		gtk_widget_get_toplevel (dedit->priv->date_button),
+		bwidth - cal_req.width, bheight, &x, &y);
+
+	toplevel = gtk_widget_get_toplevel (dedit->priv->date_button);
+	window = gtk_widget_get_window (toplevel);
+	gdk_window_get_origin (window, &win_x, &win_y);
+
+	x += win_x;
+	y += win_y;
+
+	screen_width = gdk_screen_width ();
+	screen_height = gdk_screen_height ();
+
+	x = CLAMP (x, 0, MAX (0, screen_width - cal_req.width));
+	y = CLAMP (y, 0, MAX (0, screen_height - cal_req.height));
+
+	gtk_window_move (GTK_WINDOW (dedit->priv->cal_popup), x, y);
+}
+
+/* A date has been selected in the date popup, so we set the date field
+ * and hide the popup. */
+static void
+on_date_popup_date_selected (ECalendarItem *calitem,
+                             EDateEdit *dedit)
+{
+	GDate start_date, end_date;
+
+	hide_date_popup (dedit);
+
+	if (!e_calendar_item_get_selection (calitem, &start_date, &end_date))
+		return;
+
+	e_date_edit_set_date (
+		dedit, g_date_get_year (&start_date),
+		g_date_get_month (&start_date),
+		g_date_get_day (&start_date));
+}
+
+static void
+on_date_popup_now_button_clicked (GtkWidget *button,
+                                  EDateEdit *dedit)
+{
+	hide_date_popup (dedit);
+	e_date_edit_set_time (dedit, 0);
+}
+
+static void
+on_date_popup_today_button_clicked (GtkWidget *button,
+                                    EDateEdit *dedit)
+{
+	EDateEditPrivate *priv;
+	struct tm tmp_tm;
+	time_t t;
+
+	priv = dedit->priv;
+
+	hide_date_popup (dedit);
+
+	if (priv->time_callback) {
+		tmp_tm = (*priv->time_callback) (dedit, priv->time_callback_data);
+	} else {
+		t = time (NULL);
+		tmp_tm = *localtime (&t);
+	}
+
+	e_date_edit_set_date (
+		dedit, tmp_tm.tm_year + 1900,
+		tmp_tm.tm_mon + 1, tmp_tm.tm_mday);
+}
+
+static void
+on_date_popup_none_button_clicked (GtkWidget *button,
+                                   EDateEdit *dedit)
+{
+	hide_date_popup (dedit);
+	e_date_edit_set_time (dedit, -1);
+}
+
+/* A key has been pressed while the date popup is showing. If it is the Escape
+ * key we hide the popup. */
+static gint
+on_date_popup_key_press (GtkWidget *widget,
+                         GdkEventKey *event,
+                         EDateEdit *dedit)
+{
+	if (event->keyval == GDK_KEY_Escape) {
+		g_signal_stop_emission_by_name (widget, "key_press_event");
+		hide_date_popup (dedit);
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+/* A mouse button has been pressed while the date popup is showing.
+ * Any button press events used to select days etc. in the popup will have
+ * have been handled elsewhere, so here we just hide the popup.
+ * (This function is yanked from gtkcombo.c) */
+static gint
+on_date_popup_button_press (GtkWidget *widget,
+                            GdkEvent *button_event,
+                            gpointer data)
+{
+	EDateEdit *dedit;
+	GtkWidget *child;
+
+	dedit = data;
+
+	child = gtk_get_event_widget (button_event);
+
+	/* We don't ask for button press events on the grab widget, so
+	 *  if an event is reported directly to the grab widget, it must
+	 *  be on a window outside the application (and thus we remove
+	 *  the popup window). Otherwise, we check if the widget is a child
+	 *  of the grab widget, and only remove the popup window if it
+	 *  is not.
+	 */
+	if (child != widget) {
+		while (child) {
+			if (child == widget)
+				return FALSE;
+			child = gtk_widget_get_parent (child);
+		}
+	}
+
+	hide_date_popup (dedit);
+
+	return TRUE;
+}
+
+/* A delete event has been received for the date popup, so we hide it and
+ * return TRUE so it doesn't get destroyed. */
+static gint
+on_date_popup_delete_event (GtkWidget *widget,
+                            EDateEdit *dedit)
+{
+	hide_date_popup (dedit);
+	return TRUE;
+}
+
+/* Hides the date popup, removing any grabs. */
+static void
+hide_date_popup (EDateEdit *dedit)
+{
+	gtk_widget_hide (dedit->priv->cal_popup);
+	gtk_grab_remove (dedit->priv->cal_popup);
+
+	if (dedit->priv->grabbed_keyboard != NULL) {
+		gdk_device_ungrab (
+			dedit->priv->grabbed_keyboard,
+			GDK_CURRENT_TIME);
+		g_object_unref (dedit->priv->grabbed_keyboard);
+		dedit->priv->grabbed_keyboard = NULL;
+	}
+
+	if (dedit->priv->grabbed_pointer != NULL) {
+		gdk_device_ungrab (
+			dedit->priv->grabbed_pointer,
+			GDK_CURRENT_TIME);
+		g_object_unref (dedit->priv->grabbed_pointer);
+		dedit->priv->grabbed_pointer = NULL;
+	}
+}
+
+/* Clears the time popup and rebuilds it using the lower_hour, upper_hour
+ * and use_24_hour_format settings. */
+static void
+rebuild_time_popup (EDateEdit *dedit)
+{
+	EDateEditPrivate *priv;
+	GtkTreeModel *model;
+	GtkListStore *list_store;
+	GtkTreeIter iter;
+	gchar buffer[40];
+	struct tm tmp_tm;
+	gint hour, min;
+
+	priv = dedit->priv;
+
+	model = gtk_combo_box_get_model (GTK_COMBO_BOX (priv->time_combo));
+	list_store = GTK_LIST_STORE (model);
+	gtk_list_store_clear (list_store);
+
+	/* Fill the struct tm with some sane values. */
+	tmp_tm.tm_year = 2000;
+	tmp_tm.tm_mon = 0;
+	tmp_tm.tm_mday = 1;
+	tmp_tm.tm_sec  = 0;
+	tmp_tm.tm_isdst = 0;
+
+	for (hour = priv->lower_hour; hour <= priv->upper_hour; hour++) {
+
+		/* We don't want to display midnight at the end,
+		 * since that is really in the next day. */
+		if (hour == 24)
+			break;
+
+		/* We want to finish on upper_hour, with min == 0. */
+		for (min = 0;
+		     min == 0 || (min < 60 && hour != priv->upper_hour);
+		     min += 30) {
+			tmp_tm.tm_hour = hour;
+			tmp_tm.tm_min  = min;
+
+			if (priv->use_24_hour_format)
+				/* This is a strftime() format.
+				 * %H = hour (0-23), %M = minute. */
+				e_time_format_time (
+					&tmp_tm, 1, 0,
+					buffer, sizeof (buffer));
+			else
+				/* This is a strftime() format.
+				 * %I = hour (1-12), %M = minute,
+				 * %p = am/pm string. */
+				e_time_format_time (
+					&tmp_tm, 0, 0,
+					buffer, sizeof (buffer));
+
+			/* For 12-hour am/pm format, we want space padding,
+			 * not zero padding. This can be done with strftime's
+			 * %l, but it's a potentially unportable extension. */
+			if (!priv->use_24_hour_format && buffer[0] == '0')
+				buffer[0] = ' ';
+
+			gtk_list_store_append (list_store, &iter);
+			gtk_list_store_set (list_store, &iter, 0, buffer, -1);
+		}
+	}
+}
+
+static gboolean
+e_date_edit_parse_date (EDateEdit *dedit,
+                        const gchar *date_text,
+                        struct tm *date_tm)
+{
+	gboolean twodigit_year = FALSE;
+
+	if (e_time_parse_date_ex (date_text, date_tm, &twodigit_year) != E_TIME_PARSE_OK)
+		return FALSE;
+
+	if (twodigit_year && !dedit->priv->twodigit_year_can_future) {
+		time_t t = time (NULL);
+		struct tm *today_tm = localtime (&t);
+
+		/* It was only 2 digit year in dedit and it was interpreted as
+		 * in the future, but we don't want it as this, so decrease by
+		 * 100 years to last century. */
+		if (date_tm->tm_year > today_tm->tm_year)
+			date_tm->tm_year -= 100;
+	}
+
+	return TRUE;
+}
+
+static gboolean
+e_date_edit_parse_time (EDateEdit *dedit,
+                        const gchar *time_text,
+                        struct tm *time_tm)
+{
+	if (field_set_to_none (time_text)) {
+		time_tm->tm_hour = 0;
+		time_tm->tm_min = 0;
+		return TRUE;
+	}
+
+	if (e_time_parse_time (time_text, time_tm) != E_TIME_PARSE_OK)
+		return FALSE;
+
+	return TRUE;
+}
+
+/* Returns TRUE if the string is empty or is "None" in the current locale.
+ * It ignores whitespace. */
+static gboolean
+field_set_to_none (const gchar *text)
+{
+	const gchar *pos;
+	const gchar *none_string;
+	gint n;
+
+	pos = text;
+	while (n = (gint)((guchar) * pos), isspace (n))
+		pos++;
+
+	/* Translators: "None" for date field of a date edit, shown when
+	 * there is no date set. */
+	none_string = C_("date", "None");
+
+	if (*pos == '\0' || !strncmp (pos, none_string, strlen (none_string)))
+		return TRUE;
+	return FALSE;
+}
+
+static void
+on_date_edit_time_selected (GtkComboBox *combo,
+                            EDateEdit *dedit)
+{
+	GtkWidget *child;
+
+	child = gtk_bin_get_child (GTK_BIN (combo));
+
+	/* We only want to emit signals when an item is selected explicitly,
+	 * not when it is selected by the silly combo update thing. */
+	if (gtk_combo_box_get_active (combo) == -1)
+		return;
+
+	if (!gtk_widget_get_mapped (child))
+		return;
+
+	e_date_edit_check_time_changed (dedit);
+}
+
+static gint
+on_date_entry_key_press (GtkWidget *widget,
+                         GdkEvent *key_event,
+                         EDateEdit *dedit)
+{
+	GdkModifierType event_state = 0;
+	guint event_keyval = 0;
+
+	gdk_event_get_keyval (key_event, &event_keyval);
+	gdk_event_get_state (key_event, &event_state);
+
+	if (event_state & GDK_MOD1_MASK
+	    && (event_keyval == GDK_KEY_Up || event_keyval == GDK_KEY_Down
+		|| event_keyval == GDK_KEY_Return)) {
+		g_signal_stop_emission_by_name (widget, "key_press_event");
+		e_date_edit_show_date_popup (dedit, key_event);
+		return TRUE;
+	}
+
+	/* If the user hits the return key emit a "date_changed" signal if
+	 * needed. But let the signal carry on. */
+	if (event_keyval == GDK_KEY_Return) {
+		e_date_edit_check_date_changed (dedit);
+		return FALSE;
+	}
+
+	return FALSE;
+}
+
+static gint
+on_time_entry_key_press (GtkWidget *widget,
+                         GdkEvent *key_event,
+                         EDateEdit *dedit)
+{
+	GtkWidget *child;
+	GdkModifierType event_state = 0;
+	guint event_keyval = 0;
+
+	gdk_event_get_keyval (key_event, &event_keyval);
+	gdk_event_get_state (key_event, &event_state);
+
+	child = gtk_bin_get_child (GTK_BIN (dedit->priv->time_combo));
+
+	/* I'd like to use Alt+Up/Down for popping up the list, like Win32,
+	 * but the combo steals any Up/Down keys, so we use Alt + Return. */
+#if 0
+	if (event_state & GDK_MOD1_MASK
+	    && (event_keyval == GDK_KEY_Up || event_keyval == GDK_KEY_Down)) {
+#else
+	if (event_state & GDK_MOD1_MASK && event_keyval == GDK_KEY_Return) {
+#endif
+		g_signal_stop_emission_by_name (widget, "key_press_event");
+		g_signal_emit_by_name (child, "activate", 0);
+		return TRUE;
+	}
+
+	/* Stop the return key from emitting the activate signal, and check
+	 * if we need to emit a "time_changed" signal. */
+	if (event_keyval == GDK_KEY_Return) {
+		g_signal_stop_emission_by_name (widget, "key_press_event");
+		e_date_edit_check_time_changed (dedit);
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+static gint
+on_date_entry_key_release (GtkWidget *widget,
+                           GdkEvent *key_event,
+                           EDateEdit *dedit)
+{
+	e_date_edit_check_date_changed (dedit);
+	return TRUE;
+}
+
+static gint
+on_time_entry_key_release (GtkWidget *widget,
+                           GdkEvent *key_event,
+                           EDateEdit *dedit)
+{
+	guint event_keyval = 0;
+
+	gdk_event_get_keyval (key_event, &event_keyval);
+
+	if (event_keyval == GDK_KEY_Up || event_keyval == GDK_KEY_Down) {
+		g_signal_stop_emission_by_name (widget, "key_release_event");
+		e_date_edit_check_time_changed (dedit);
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+static gint
+on_date_entry_focus_out (GtkEntry *entry,
+                         GdkEventFocus *event,
+                         EDateEdit *dedit)
+{
+	struct tm tmp_tm;
+	GtkWidget *msg_dialog;
+
+	tmp_tm.tm_year = 0;
+	tmp_tm.tm_mon = 0;
+	tmp_tm.tm_mday = 0;
+
+	e_date_edit_check_date_changed (dedit);
+
+	if (!e_date_edit_date_is_valid (dedit)) {
+		msg_dialog = gtk_message_dialog_new (
+			NULL,
+			GTK_DIALOG_MODAL,
+			GTK_MESSAGE_WARNING,
+			GTK_BUTTONS_OK,
+			"%s", _("Invalid Date Value"));
+		gtk_dialog_run (GTK_DIALOG (msg_dialog));
+		gtk_widget_destroy (msg_dialog);
+		e_date_edit_get_date (
+			dedit, &tmp_tm.tm_year,
+			&tmp_tm.tm_mon, &tmp_tm.tm_mday);
+		e_date_edit_set_date (
+			dedit, tmp_tm.tm_year,
+			tmp_tm.tm_mon, tmp_tm.tm_mday);
+		gtk_widget_grab_focus (GTK_WIDGET (entry));
+		return FALSE;
+	} else if (e_date_edit_get_date (
+		dedit, &tmp_tm.tm_year, &tmp_tm.tm_mon, &tmp_tm.tm_mday)) {
+
+		e_date_edit_set_date (
+			dedit,tmp_tm.tm_year,tmp_tm.tm_mon,tmp_tm.tm_mday);
+
+		if (dedit->priv->has_been_changed) {
+			/* The previous one didn't emit changed signal,
+			 * but we want it even here, thus doing itself. */
+			g_signal_emit (dedit, signals[CHANGED], 0);
+			dedit->priv->has_been_changed = FALSE;
+		}
+	} else {
+		dedit->priv->date_set_to_none = TRUE;
+		e_date_edit_update_date_entry (dedit);
+	}
+	return FALSE;
+}
+
+static gint
+on_time_entry_focus_out (GtkEntry *entry,
+                         GdkEventFocus *event,
+                         EDateEdit *dedit)
+{
+	GtkWidget *msg_dialog;
+
+	e_date_edit_check_time_changed (dedit);
+
+	if (!e_date_edit_time_is_valid (dedit)) {
+		msg_dialog = gtk_message_dialog_new (
+			NULL,
+			GTK_DIALOG_MODAL,
+			GTK_MESSAGE_WARNING,
+			GTK_BUTTONS_OK,
+			"%s", _("Invalid Time Value"));
+		gtk_dialog_run (GTK_DIALOG (msg_dialog));
+		gtk_widget_destroy (msg_dialog);
+		e_date_edit_set_time (dedit,e_date_edit_get_time (dedit));
+		gtk_widget_grab_focus (GTK_WIDGET (entry));
+		return FALSE;
+	}
+	return FALSE;
+}
+
+static void
+add_relation (EDateEdit *dedit,
+              GtkWidget *widget)
+{
+	AtkObject *a11yEdit, *a11yWidget;
+	AtkRelationSet *set;
+	AtkRelation *relation;
+	GPtrArray *target;
+	gpointer target_object;
+
+	/* add a labelled_by relation for widget for accessibility */
+
+	a11yEdit = gtk_widget_get_accessible (GTK_WIDGET (dedit));
+	a11yWidget = gtk_widget_get_accessible (widget);
+
+	set = atk_object_ref_relation_set (a11yWidget);
+	if (set != NULL) {
+		relation = atk_relation_set_get_relation_by_type (
+			set, ATK_RELATION_LABELLED_BY);
+		/* check whether has a labelled_by relation already */
+		if (relation != NULL)
+			return;
+	}
+
+	set = atk_object_ref_relation_set (a11yEdit);
+	if (!set)
+		return;
+
+	relation = atk_relation_set_get_relation_by_type (
+		set, ATK_RELATION_LABELLED_BY);
+	if (relation != NULL) {
+		target = atk_relation_get_target (relation);
+		target_object = g_ptr_array_index (target, 0);
+		if (ATK_IS_OBJECT (target_object)) {
+			atk_object_add_relationship (
+				a11yWidget,
+					ATK_RELATION_LABELLED_BY,
+					ATK_OBJECT (target_object));
+		}
+	}
+}
+
+/* This sets the text in the date entry according to the current settings. */
+static void
+e_date_edit_update_date_entry (EDateEdit *dedit)
+{
+	EDateEditPrivate *priv;
+	gchar buffer[100];
+	struct tm tmp_tm = { 0 };
+
+	priv = dedit->priv;
+
+	if (priv->date_set_to_none || !priv->date_is_valid) {
+		gtk_entry_set_text (GTK_ENTRY (priv->date_entry), C_("date", "None"));
+	} else {
+		/* This is a strftime() format for a short date.
+		 * %x the preferred date representation for the current locale
+		 * without the time, but is forced to use 4 digit year. */
+		gchar *format = e_time_get_d_fmt_with_4digit_year ();
+		time_t tt;
+
+		tmp_tm.tm_year = priv->year;
+		tmp_tm.tm_mon = priv->month;
+		tmp_tm.tm_mday = priv->day;
+		tmp_tm.tm_isdst = -1;
+
+		/* initialize all 'struct tm' members properly */
+		tt = mktime (&tmp_tm);
+		if (tt && localtime (&tt))
+			tmp_tm = *localtime (&tt);
+
+		e_utf8_strftime (buffer, sizeof (buffer), format, &tmp_tm);
+		g_free (format);
+		gtk_entry_set_text (GTK_ENTRY (priv->date_entry), buffer);
+	}
+
+	add_relation (dedit, priv->date_entry);
+	add_relation (dedit, priv->date_button);
+}
+
+/* This sets the text in the time entry according to the current settings. */
+static void
+e_date_edit_update_time_entry (EDateEdit *dedit)
+{
+	EDateEditPrivate *priv;
+	GtkComboBox *combo_box;
+	GtkWidget *child;
+	gchar buffer[40];
+	struct tm tmp_tm = { 0 };
+
+	priv = dedit->priv;
+
+	combo_box = GTK_COMBO_BOX (priv->time_combo);
+	child = gtk_bin_get_child (GTK_BIN (priv->time_combo));
+
+	if (priv->time_set_to_none || !priv->time_is_valid) {
+		gtk_combo_box_set_active (combo_box, -1);
+		gtk_entry_set_text (GTK_ENTRY (child), "");
+	} else {
+		GtkTreeModel *model;
+		GtkTreeIter iter;
+		gboolean valid;
+		gchar *b;
+
+		/* Set these to reasonable values just in case. */
+		tmp_tm.tm_year = 2000;
+		tmp_tm.tm_mon = 0;
+		tmp_tm.tm_mday = 1;
+
+		tmp_tm.tm_hour = priv->hour;
+		tmp_tm.tm_min = priv->minute;
+
+		tmp_tm.tm_sec = 0;
+		tmp_tm.tm_isdst = -1;
+
+		if (priv->use_24_hour_format)
+			/* This is a strftime() format.
+			 * %H = hour (0-23), %M = minute. */
+			e_time_format_time (
+				&tmp_tm, 1, 0, buffer, sizeof (buffer));
+		else
+			/* This is a strftime() format.
+			 * %I = hour (1-12), %M = minute, %p = am/pm. */
+			e_time_format_time (
+				&tmp_tm, 0, 0, buffer, sizeof (buffer));
+
+		/* For 12-hour am/pm format, we want space padding, not
+		 * zero padding.  This can be done with strftime's %l,
+		 * but it's a potentially unportable extension. */
+		if (!priv->use_24_hour_format && buffer[0] == '0')
+			buffer[0] = ' ';
+
+		gtk_entry_set_text (GTK_ENTRY (child), buffer);
+
+		/* truncate left spaces */
+		b = buffer;
+		while (*b == ' ')
+			b++;
+
+		model = gtk_combo_box_get_model (combo_box);
+		valid = gtk_tree_model_get_iter_first (model, &iter);
+
+		while (valid) {
+			gchar *text = NULL;
+
+			gtk_tree_model_get (model, &iter, 0, &text, -1);
+			if (text) {
+				gchar *t = text;
+
+				/* truncate left spaces */
+				while (*t == ' ')
+					t++;
+
+				if (strcmp (b, t) == 0) {
+					gtk_combo_box_set_active_iter (
+						combo_box, &iter);
+					g_free (text);
+					break;
+				}
+			}
+
+			g_free (text);
+
+			valid = gtk_tree_model_iter_next (model, &iter);
+		}
+	}
+
+	add_relation (dedit, priv->time_combo);
+}
+
+static void
+e_date_edit_update_time_combo_state (EDateEdit *dedit)
+{
+	EDateEditPrivate *priv;
+	gboolean show = TRUE, show_now_button = TRUE;
+	gboolean clear_entry = FALSE, sensitive = TRUE;
+	const gchar *text;
+
+	priv = dedit->priv;
+
+	/* If the date entry is currently shown, and it is set to None,
+	 * clear the time entry and disable the time combo. */
+	if (priv->show_date && priv->date_set_to_none) {
+		clear_entry = TRUE;
+		sensitive = FALSE;
+	}
+
+	if (!priv->show_time) {
+		if (priv->make_time_insensitive) {
+			clear_entry = TRUE;
+			sensitive = FALSE;
+		} else {
+			show = FALSE;
+		}
+
+		show_now_button = FALSE;
+	}
+
+	if (clear_entry) {
+		GtkWidget *child;
+
+		/* Only clear it if it isn't empty already. */
+		child = gtk_bin_get_child (GTK_BIN (priv->time_combo));
+		text = gtk_entry_get_text (GTK_ENTRY (child));
+		if (text[0])
+			gtk_entry_set_text (GTK_ENTRY (child), "");
+	}
+
+	gtk_widget_set_sensitive (priv->time_combo, sensitive);
+
+	if (show)
+		gtk_widget_show (priv->time_combo);
+	else
+		gtk_widget_hide (priv->time_combo);
+
+	if (show_now_button)
+		gtk_widget_show (priv->now_button);
+	else
+		gtk_widget_hide (priv->now_button);
+
+	if (priv->show_date
+	    && (priv->show_time || priv->make_time_insensitive))
+		gtk_widget_show (priv->space);
+	else
+		gtk_widget_hide (priv->space);
+}
+
+/* Parses the date, and if it is different from the current settings it
+ * updates the settings and emits a "date_changed" signal. */
+static void
+e_date_edit_check_date_changed (EDateEdit *dedit)
+{
+	EDateEditPrivate *priv;
+	const gchar *date_text;
+	struct tm tmp_tm;
+	gboolean none = FALSE, valid = TRUE, date_changed = FALSE;
+
+	priv = dedit->priv;
+
+	tmp_tm.tm_year = 0;
+	tmp_tm.tm_mon = 0;
+	tmp_tm.tm_mday = 0;
+
+	date_text = gtk_entry_get_text (GTK_ENTRY (priv->date_entry));
+	if (field_set_to_none (date_text)) {
+		none = TRUE;
+	} else if (!e_date_edit_parse_date (dedit, date_text, &tmp_tm)) {
+		valid = FALSE;
+		tmp_tm.tm_year = 0;
+		tmp_tm.tm_mon = 0;
+		tmp_tm.tm_mday = 0;
+	}
+
+	date_changed = e_date_edit_set_date_internal (
+		dedit, valid, none,
+		tmp_tm.tm_year,
+		tmp_tm.tm_mon,
+		tmp_tm.tm_mday);
+
+	if (date_changed) {
+		priv->has_been_changed = TRUE;
+		g_signal_emit (dedit, signals[CHANGED], 0);
+	}
+}
+
+/* Parses the time, and if it is different from the current settings it
+ * updates the settings and emits a "time_changed" signal. */
+static void
+e_date_edit_check_time_changed (EDateEdit *dedit)
+{
+	EDateEditPrivate *priv;
+	GtkWidget *child;
+	const gchar *time_text;
+	struct tm tmp_tm;
+	gboolean none = FALSE, valid = TRUE, time_changed;
+
+	priv = dedit->priv;
+
+	tmp_tm.tm_hour = 0;
+	tmp_tm.tm_min = 0;
+
+	child = gtk_bin_get_child (GTK_BIN (priv->time_combo));
+	time_text = gtk_entry_get_text (GTK_ENTRY (child));
+	if (field_set_to_none (time_text))
+		none = TRUE;
+	else if (!e_date_edit_parse_time (dedit, time_text, &tmp_tm))
+		valid = FALSE;
+
+	time_changed = e_date_edit_set_time_internal (
+		dedit, valid, none,
+		tmp_tm.tm_hour,
+		tmp_tm.tm_min);
+
+	if (time_changed) {
+		e_date_edit_update_time_entry (dedit);
+		g_signal_emit (dedit, signals[CHANGED], 0);
+	}
+}
+
+/**
+ * e_date_edit_date_is_valid:
+ * @dedit: an #EDateEdit widget.
+ * @Returns: TRUE if the last date entered was valid.
+ *
+ * Returns TRUE if the last date entered was valid.
+ *
+ * Note that if this returns FALSE, you can still use e_date_edit_get_time()
+ * or e_date_edit_get_date() to get the last time or date entered which was
+ * valid.
+ */
+gboolean
+e_date_edit_date_is_valid (EDateEdit *dedit)
+{
+	g_return_val_if_fail (E_IS_DATE_EDIT (dedit), FALSE);
+
+	if (!dedit->priv->date_is_valid)
+		return FALSE;
+
+	/* If the date is empty/None and that isn't permitted, return FALSE. */
+	if (dedit->priv->date_set_to_none
+	    && !e_date_edit_get_allow_no_date_set (dedit))
+		return FALSE;
+
+	return TRUE;
+}
+
+/**
+ * e_date_edit_time_is_valid:
+ * @dedit: an #EDateEdit widget.
+ * @Returns: TRUE if the last time entered was valid.
+ *
+ * Returns TRUE if the last time entered was valid.
+ *
+ * Note that if this returns FALSE, you can still use e_date_edit_get_time()
+ * or e_date_edit_get_time_of_day() to get the last time or time of the day
+ * entered which was valid.
+ */
+gboolean
+e_date_edit_time_is_valid (EDateEdit *dedit)
+{
+	g_return_val_if_fail (E_IS_DATE_EDIT (dedit), FALSE);
+
+	if (!dedit->priv->time_is_valid)
+		return FALSE;
+
+	/* If the time is empty and that isn't permitted, return FALSE.
+	 * Note that we don't mind an empty time if the date field is shown
+	 * - in that case we just assume 0:00. */
+	if (dedit->priv->time_set_to_none && !dedit->priv->show_date
+	    && !e_date_edit_get_allow_no_date_set (dedit))
+		return FALSE;
+
+	return TRUE;
+}
+
+static gboolean
+e_date_edit_set_date_internal (EDateEdit *dedit,
+                               gboolean valid,
+                               gboolean none,
+                               gint year,
+                               gint month,
+                               gint day)
+{
+	EDateEditPrivate *priv;
+	gboolean date_changed = FALSE;
+
+	priv = dedit->priv;
+
+	if (!valid) {
+		/* Date is invalid. */
+		if (priv->date_is_valid) {
+			priv->date_is_valid = FALSE;
+			date_changed = TRUE;
+		}
+	} else if (none) {
+		/* Date has been set to 'None'. */
+		if (!priv->date_is_valid
+		    || !priv->date_set_to_none) {
+			priv->date_is_valid = TRUE;
+			priv->date_set_to_none = TRUE;
+			date_changed = TRUE;
+		}
+	} else {
+		/* Date has been set to a specific date. */
+		if (!priv->date_is_valid
+		    || priv->date_set_to_none
+		    || priv->year != year
+		    || priv->month != month
+		    || priv->day != day) {
+			priv->date_is_valid = TRUE;
+			priv->date_set_to_none = FALSE;
+			priv->year = year;
+			priv->month = month;
+			priv->day = day;
+			date_changed = TRUE;
+		}
+	}
+
+	return date_changed;
+}
+
+static gboolean
+e_date_edit_set_time_internal (EDateEdit *dedit,
+                               gboolean valid,
+                               gboolean none,
+                               gint hour,
+                               gint minute)
+{
+	EDateEditPrivate *priv;
+	gboolean time_changed = FALSE;
+
+	priv = dedit->priv;
+
+	if (!valid) {
+		/* Time is invalid. */
+		if (priv->time_is_valid) {
+			priv->time_is_valid = FALSE;
+			time_changed = TRUE;
+		}
+	} else if (none) {
+		/* Time has been set to empty/'None'. */
+		if (!priv->time_is_valid
+		    || !priv->time_set_to_none) {
+			priv->time_is_valid = TRUE;
+			priv->time_set_to_none = TRUE;
+			time_changed = TRUE;
+		}
+	} else {
+		/* Time has been set to a specific time. */
+		if (!priv->time_is_valid
+		    || priv->time_set_to_none
+		    || priv->hour != hour
+		    || priv->minute != minute) {
+			priv->time_is_valid = TRUE;
+			priv->time_set_to_none = FALSE;
+			priv->hour = hour;
+			priv->minute = minute;
+			time_changed = TRUE;
+		}
+	}
+
+	return time_changed;
+}
+
+gboolean
+e_date_edit_get_twodigit_year_can_future (EDateEdit *dedit)
+{
+	g_return_val_if_fail (E_IS_DATE_EDIT (dedit), FALSE);
+
+	return dedit->priv->twodigit_year_can_future;
+}
+
+void
+e_date_edit_set_twodigit_year_can_future (EDateEdit *dedit,
+                                          gboolean value)
+{
+	g_return_if_fail (E_IS_DATE_EDIT (dedit));
+
+	dedit->priv->twodigit_year_can_future = value;
+}
+
+/* Sets a callback to use to get the current time. This is useful if the
+ * application needs to use its own timezone data rather than rely on the
+ * Unix timezone. */
+void
+e_date_edit_set_get_time_callback (EDateEdit *dedit,
+                                   EDateEditGetTimeCallback cb,
+                                   gpointer data,
+                                   GDestroyNotify destroy)
+{
+	EDateEditPrivate *priv;
+
+	g_return_if_fail (E_IS_DATE_EDIT (dedit));
+
+	priv = dedit->priv;
+
+	if (priv->time_callback_data && priv->time_callback_destroy)
+		(*priv->time_callback_destroy) (priv->time_callback_data);
+
+	priv->time_callback = cb;
+	priv->time_callback_data = data;
+	priv->time_callback_destroy = destroy;
+
+}
+
+GtkWidget *
+e_date_edit_get_entry (EDateEdit *dedit)
+{
+	g_return_val_if_fail (E_IS_DATE_EDIT (dedit), NULL);
+
+	return GTK_WIDGET (dedit->priv->date_entry);
+}
diff --git a/e-util/e-dateedit.h b/e-util/e-dateedit.h
new file mode 100644
index 0000000..b415847
--- /dev/null
+++ b/e-util/e-dateedit.h
@@ -0,0 +1,219 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * Author :
+ *  Damon Chaplin <damon ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * Based on the GnomeDateEdit, part of the Gnome Library.
+ * Copyright (C) 1997, 1998, 1999, 2000 Free Software Foundation
+ *
+ * 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
+ * USA
+ */
+
+/*
+ * EDateEdit - a widget based on GnomeDateEdit to provide a date & optional
+ * time field with popups for entering a date.
+ *
+ * It emits a "changed" signal when the date and/or time has changed.
+ * You can check if the last date or time entered was invalid by
+ * calling e_date_edit_date_is_valid() and e_date_edit_time_is_valid().
+ *
+ * Note that when the user types in a date or time, it will only emit the
+ * signals when the user presses the return key or switches the keyboard
+ * focus to another widget, or you call one of the _get_time/date functions.
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_DATE_EDIT_H
+#define E_DATE_EDIT_H
+
+#include <time.h>
+#include <gtk/gtk.h>
+
+/* Standard GObject macros */
+#define E_TYPE_DATE_EDIT \
+	(e_date_edit_get_type ())
+#define E_DATE_EDIT(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_DATE_EDIT, EDateEdit))
+#define E_DATE_EDIT_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_DATE_EDIT, EDateEditClass))
+#define E_IS_DATE_EDIT(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_DATE_EDIT))
+#define E_IS_DATE_EDIT_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_DATE_EDIT))
+#define E_DATE_EDIT_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_DATE_EDIT, EDateEditClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EDateEdit EDateEdit;
+typedef struct _EDateEditClass EDateEditClass;
+typedef struct _EDateEditPrivate EDateEditPrivate;
+
+/* The type of the callback function optionally used to get the current time.
+ */
+typedef struct tm	(*EDateEditGetTimeCallback)
+						(EDateEdit *dedit,
+						 gpointer data);
+
+struct _EDateEdit {
+	GtkBox hbox;
+	EDateEditPrivate *priv;
+};
+
+struct _EDateEditClass {
+	GtkBoxClass parent_class;
+
+	/* Signals */
+	void		(*changed)		(EDateEdit *dedit);
+};
+
+GType		e_date_edit_get_type		(void);
+GtkWidget *	e_date_edit_new			(void);
+
+/* Analogous to gtk_editable_set_editable.  disable editing, while still
+ * allowing selection. */
+void		e_date_edit_set_editable	(EDateEdit *dedit,
+						 gboolean editable);
+
+/* Returns TRUE if the last date and time set were valid. The date and time
+ * are only set when the user hits Return or switches keyboard focus, or
+ * selects a date or time from the popup. */
+gboolean	e_date_edit_date_is_valid	(EDateEdit *dedit);
+gboolean	e_date_edit_time_is_valid	(EDateEdit *dedit);
+
+/* Returns the last valid date & time set, or -1 if the date & time was set to
+ * 'None' and this is permitted via e_date_edit_set_allow_no_date_set. */
+time_t		e_date_edit_get_time		(EDateEdit *dedit);
+void		e_date_edit_set_time		(EDateEdit *dedit,
+						 time_t the_time);
+
+/* This returns the last valid date set, without the time. It returns TRUE
+ * if a date is set, or FALSE if the date is set to 'None' and this is
+ * permitted via e_date_edit_set_allow_no_date_set. (Month is 1 - 12). */
+gboolean	e_date_edit_get_date		(EDateEdit *dedit,
+						 gint *year,
+						 gint *month,
+						 gint *day);
+void		e_date_edit_set_date		(EDateEdit *dedit,
+						 gint year,
+						 gint month,
+						 gint day);
+
+/* This returns the last valid time set, without the date. It returns TRUE
+ * if a time is set, or FALSE if the time is set to 'None' and this is
+ * permitted via e_date_edit_set_allow_no_date_set. */
+gboolean	e_date_edit_get_time_of_day	(EDateEdit *dedit,
+						 gint *hour,
+						 gint *minute);
+/* Set the time. Pass -1 as hour to set to empty. */
+void		e_date_edit_set_time_of_day	(EDateEdit *dedit,
+						 gint		 hour,
+						 gint		 minute);
+
+void		e_date_edit_set_date_and_time_of_day
+						(EDateEdit *dedit,
+						 gint year,
+						 gint month,
+						 gint day,
+						 gint hour,
+						 gint minute);
+
+/* Whether we show the date field. */
+gboolean	e_date_edit_get_show_date	(EDateEdit *dedit);
+void		e_date_edit_set_show_date	(EDateEdit *dedit,
+						 gboolean show_date);
+
+/* Whether we show the time field. */
+gboolean	e_date_edit_get_show_time	(EDateEdit *dedit);
+void		e_date_edit_set_show_time	(EDateEdit *dedit,
+						 gboolean show_time);
+
+/* The week start day, used in the date popup. 0 (Mon) to 6 (Sun). */
+gint		e_date_edit_get_week_start_day	(EDateEdit *dedit);
+void		e_date_edit_set_week_start_day	(EDateEdit *dedit,
+						 gint week_start_day);
+
+/* Whether we show week numbers in the date popup. */
+gboolean	e_date_edit_get_show_week_numbers
+						(EDateEdit *dedit);
+void		e_date_edit_set_show_week_numbers
+						(EDateEdit *dedit,
+						 gboolean show_week_numbers);
+
+/* Whether we use 24 hour format in the time field & popup. */
+gboolean	e_date_edit_get_use_24_hour_format
+						(EDateEdit *dedit);
+void		e_date_edit_set_use_24_hour_format
+						(EDateEdit *dedit,
+						 gboolean use_24_hour_format);
+
+/* Whether we allow the date to be set to 'None'. e_date_edit_get_time() will
+ * return (time_t) -1 in this case. */
+gboolean	e_date_edit_get_allow_no_date_set
+						(EDateEdit *dedit);
+void		e_date_edit_set_allow_no_date_set
+						(EDateEdit *dedit,
+						 gboolean allow_no_date_set);
+
+/* The range of time to show in the time combo popup. */
+void		e_date_edit_get_time_popup_range
+						(EDateEdit *dedit,
+						 gint *lower_hour,
+						 gint *upper_hour);
+void		e_date_edit_set_time_popup_range
+						(EDateEdit *dedit,
+						 gint lower_hour,
+						 gint upper_hour);
+
+/* Whether the time field is made insensitive rather than hiding it. */
+gboolean	e_date_edit_get_make_time_insensitive
+						(EDateEdit *dedit);
+void		e_date_edit_set_make_time_insensitive
+						(EDateEdit *dedit,
+						 gboolean make_insensitive);
+
+/* Whether two-digit years in date could be modified as in future; default is TRUE */
+gboolean	e_date_edit_get_twodigit_year_can_future
+						(EDateEdit *dedit);
+void		e_date_edit_set_twodigit_year_can_future
+						(EDateEdit *dedit,
+						 gboolean value);
+
+/* Sets a callback to use to get the current time. This is useful if the
+ * application needs to use its own timezone data rather than rely on the
+ * Unix timezone. */
+void		e_date_edit_set_get_time_callback
+						(EDateEdit *dedit,
+						 EDateEditGetTimeCallback cb,
+						 gpointer data,
+						 GDestroyNotify destroy);
+
+GtkWidget *	e_date_edit_get_entry		(EDateEdit *dedit);
+
+G_END_DECLS
+
+#endif /* E_DATE_EDIT_H */
diff --git a/e-util/e-datetime-format.c b/e-util/e-datetime-format.c
index fcd93eb..d0066fb 100644
--- a/e-util/e-datetime-format.c
+++ b/e-util/e-datetime-format.c
@@ -26,7 +26,10 @@
 #include <gtk/gtk.h>
 
 #include "e-datetime-format.h"
-#include "e-util.h"
+
+#include <libedataserver/libedataserver.h>
+
+#include "e-misc-utils.h"
 
 #define KEYS_FILENAME "datetime-formats.ini"
 #define KEYS_GROUPNAME "formats"
diff --git a/e-util/e-datetime-format.h b/e-util/e-datetime-format.h
index 28eed15..5974349 100644
--- a/e-util/e-datetime-format.h
+++ b/e-util/e-datetime-format.h
@@ -20,6 +20,10 @@
  *
  */
 
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
 #ifndef __E_DATETIME_FORMAT__
 #define __E_DATETIME_FORMAT__
 
diff --git a/e-util/e-destination-store.c b/e-util/e-destination-store.c
new file mode 100644
index 0000000..82801f2
--- /dev/null
+++ b/e-util/e-destination-store.c
@@ -0,0 +1,751 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/* e-destination-store.c - EDestination store with GtkTreeModel interface.
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that 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 program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * Authors: Hans Petter Jansson <hpj novell com>
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <string.h>
+#include <glib/gi18n-lib.h>
+
+#include "e-destination-store.h"
+
+#define ITER_IS_VALID(destination_store, iter) \
+	((iter)->stamp == (destination_store)->priv->stamp)
+#define ITER_GET(iter) \
+	GPOINTER_TO_INT (iter->user_data)
+#define ITER_SET(destination_store, iter, index) \
+	G_STMT_START { \
+	(iter)->stamp = (destination_store)->priv->stamp; \
+	(iter)->user_data = GINT_TO_POINTER (index); \
+	} G_STMT_END
+
+#define E_DESTINATION_STORE_GET_PRIVATE(obj) \
+	(G_TYPE_INSTANCE_GET_PRIVATE \
+	((obj), E_TYPE_DESTINATION_STORE, EDestinationStorePrivate))
+
+struct _EDestinationStorePrivate {
+	GPtrArray *destinations;
+	gint stamp;
+};
+
+static GType column_types[E_DESTINATION_STORE_NUM_COLUMNS];
+
+static void e_destination_store_tree_model_init (GtkTreeModelIface *iface);
+
+G_DEFINE_TYPE_EXTENDED (
+	EDestinationStore, e_destination_store, G_TYPE_OBJECT, 0,
+	G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_MODEL, e_destination_store_tree_model_init);
+	column_types[E_DESTINATION_STORE_COLUMN_NAME]    = G_TYPE_STRING;
+	column_types[E_DESTINATION_STORE_COLUMN_EMAIL]   = G_TYPE_STRING;
+	column_types[E_DESTINATION_STORE_COLUMN_ADDRESS] = G_TYPE_STRING;
+)
+
+static GtkTreeModelFlags e_destination_store_get_flags       (GtkTreeModel       *tree_model);
+static gint         e_destination_store_get_n_columns   (GtkTreeModel       *tree_model);
+static GType        e_destination_store_get_column_type (GtkTreeModel       *tree_model,
+							 gint                index);
+static gboolean     e_destination_store_get_iter        (GtkTreeModel       *tree_model,
+							 GtkTreeIter        *iter,
+							 GtkTreePath        *path);
+static void         e_destination_store_get_value       (GtkTreeModel       *tree_model,
+							 GtkTreeIter        *iter,
+							 gint                column,
+							 GValue             *value);
+static gboolean     e_destination_store_iter_next       (GtkTreeModel       *tree_model,
+							 GtkTreeIter        *iter);
+static gboolean     e_destination_store_iter_children   (GtkTreeModel       *tree_model,
+							 GtkTreeIter        *iter,
+							 GtkTreeIter        *parent);
+static gboolean     e_destination_store_iter_has_child  (GtkTreeModel       *tree_model,
+							 GtkTreeIter        *iter);
+static gint         e_destination_store_iter_n_children (GtkTreeModel       *tree_model,
+							 GtkTreeIter        *iter);
+static gboolean     e_destination_store_iter_nth_child  (GtkTreeModel       *tree_model,
+							 GtkTreeIter        *iter,
+							 GtkTreeIter        *parent,
+							 gint                n);
+static gboolean     e_destination_store_iter_parent     (GtkTreeModel       *tree_model,
+							 GtkTreeIter        *iter,
+							 GtkTreeIter        *child);
+
+static void destination_changed (EDestinationStore *destination_store, EDestination *destination);
+static void stop_destination    (EDestinationStore *destination_store, EDestination *destination);
+
+static void
+destination_store_dispose (GObject *object)
+{
+	EDestinationStorePrivate *priv;
+	gint ii;
+
+	priv = E_DESTINATION_STORE_GET_PRIVATE (object);
+
+	for (ii = 0; ii < priv->destinations->len; ii++) {
+		EDestination *destination;
+
+		destination = g_ptr_array_index (priv->destinations, ii);
+		stop_destination (E_DESTINATION_STORE (object), destination);
+		g_object_unref (destination);
+	}
+	g_ptr_array_set_size (priv->destinations, 0);
+
+	/* Chain up to parent's dispose() method. */
+	G_OBJECT_CLASS (e_destination_store_parent_class)->dispose (object);
+}
+
+static void
+destination_store_finalize (GObject *object)
+{
+	EDestinationStorePrivate *priv;
+
+	priv = E_DESTINATION_STORE_GET_PRIVATE (object);
+
+	g_ptr_array_free (priv->destinations, TRUE);
+
+	/* Chain up to parent's finalize() method. */
+	G_OBJECT_CLASS (e_destination_store_parent_class)->finalize (object);
+}
+
+static void
+e_destination_store_class_init (EDestinationStoreClass *class)
+{
+	GObjectClass *object_class;
+
+	g_type_class_add_private (class, sizeof (EDestinationStorePrivate));
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->dispose = destination_store_dispose;
+	object_class->finalize = destination_store_finalize;
+}
+
+static void
+e_destination_store_tree_model_init (GtkTreeModelIface *iface)
+{
+	iface->get_flags       = e_destination_store_get_flags;
+	iface->get_n_columns   = e_destination_store_get_n_columns;
+	iface->get_column_type = e_destination_store_get_column_type;
+	iface->get_iter        = e_destination_store_get_iter;
+	iface->get_path        = e_destination_store_get_path;
+	iface->get_value       = e_destination_store_get_value;
+	iface->iter_next       = e_destination_store_iter_next;
+	iface->iter_children   = e_destination_store_iter_children;
+	iface->iter_has_child  = e_destination_store_iter_has_child;
+	iface->iter_n_children = e_destination_store_iter_n_children;
+	iface->iter_nth_child  = e_destination_store_iter_nth_child;
+	iface->iter_parent     = e_destination_store_iter_parent;
+}
+
+static void
+e_destination_store_init (EDestinationStore *destination_store)
+{
+	destination_store->priv =
+		E_DESTINATION_STORE_GET_PRIVATE (destination_store);
+
+	destination_store->priv->destinations = g_ptr_array_new ();
+	destination_store->priv->stamp = g_random_int ();
+}
+
+/**
+ * e_destination_store_new:
+ *
+ * Creates a new #EDestinationStore.
+ *
+ * Returns: A new #EDestinationStore.
+ **/
+EDestinationStore *
+e_destination_store_new (void)
+{
+	return g_object_new (E_TYPE_DESTINATION_STORE, NULL);
+}
+
+/* ------------------ *
+ * Row update helpers *
+ * ------------------ */
+
+static void
+row_deleted (EDestinationStore *destination_store,
+             gint n)
+{
+	GtkTreePath *path;
+
+	path = gtk_tree_path_new ();
+	gtk_tree_path_append_index (path, n);
+	gtk_tree_model_row_deleted (GTK_TREE_MODEL (destination_store), path);
+	gtk_tree_path_free (path);
+}
+
+static void
+row_inserted (EDestinationStore *destination_store,
+              gint n)
+{
+	GtkTreePath *path;
+	GtkTreeIter  iter;
+
+	path = gtk_tree_path_new ();
+	gtk_tree_path_append_index (path, n);
+
+	if (gtk_tree_model_get_iter (GTK_TREE_MODEL (destination_store), &iter, path))
+		gtk_tree_model_row_inserted (GTK_TREE_MODEL (destination_store), path, &iter);
+
+	gtk_tree_path_free (path);
+}
+
+static void
+row_changed (EDestinationStore *destination_store,
+             gint n)
+{
+	GtkTreePath *path;
+	GtkTreeIter  iter;
+
+	path = gtk_tree_path_new ();
+	gtk_tree_path_append_index (path, n);
+
+	if (gtk_tree_model_get_iter (GTK_TREE_MODEL (destination_store), &iter, path))
+		gtk_tree_model_row_changed (GTK_TREE_MODEL (destination_store), path, &iter);
+
+	gtk_tree_path_free (path);
+}
+
+/* ------------------- *
+ * Destination helpers *
+ * ------------------- */
+
+static gint
+find_destination_by_pointer (EDestinationStore *destination_store,
+                             EDestination *destination)
+{
+	GPtrArray *array;
+	gint i;
+
+	array = destination_store->priv->destinations;
+
+	for (i = 0; i < array->len; i++) {
+		EDestination *destination_here;
+
+		destination_here = g_ptr_array_index (array, i);
+
+		if (destination_here == destination)
+			return i;
+	}
+
+	return -1;
+}
+
+static gint
+find_destination_by_email (EDestinationStore *destination_store,
+                           EDestination *destination)
+{
+	GPtrArray *array;
+	gint i;
+	const gchar *e_mail = e_destination_get_email (destination);
+
+	array = destination_store->priv->destinations;
+
+	for (i = 0; i < array->len; i++) {
+		EDestination *destination_here;
+		const gchar *mail;
+
+		destination_here = g_ptr_array_index (array, i);
+		mail = e_destination_get_email (destination_here);
+
+		if (g_str_equal (e_mail, mail))
+			return i;
+	}
+
+	return -1;
+}
+
+static void
+start_destination (EDestinationStore *destination_store,
+                   EDestination *destination)
+{
+	g_signal_connect_swapped (
+		destination, "changed",
+		G_CALLBACK (destination_changed), destination_store);
+}
+
+static void
+stop_destination (EDestinationStore *destination_store,
+                  EDestination *destination)
+{
+	g_signal_handlers_disconnect_matched (
+		destination, G_SIGNAL_MATCH_DATA,
+		0, 0, NULL, NULL, destination_store);
+}
+
+/* --------------- *
+ * Signal handlers *
+ * --------------- */
+
+static void
+destination_changed (EDestinationStore *destination_store,
+                     EDestination *destination)
+{
+	gint n;
+
+	n = find_destination_by_pointer (destination_store, destination);
+	if (n < 0) {
+		g_warning ("EDestinationStore got change from unknown EDestination!");
+		return;
+	}
+
+	row_changed (destination_store, n);
+}
+
+/* --------------------- *
+ * EDestinationStore API *
+ * --------------------- */
+
+/**
+ * e_destination_store_get_destination:
+ * @destination_store: an #EDestinationStore
+ * @iter: a #GtkTreeIter
+ *
+ * Gets the #EDestination from @destination_store at @iter.
+ *
+ * Returns: An #EDestination.
+ **/
+EDestination *
+e_destination_store_get_destination (EDestinationStore *destination_store,
+                                     GtkTreeIter *iter)
+{
+	GPtrArray *array;
+	gint index;
+
+	g_return_val_if_fail (E_IS_DESTINATION_STORE (destination_store), NULL);
+	g_return_val_if_fail (ITER_IS_VALID (destination_store, iter), NULL);
+
+	array = destination_store->priv->destinations;
+	index = ITER_GET (iter);
+
+	return g_ptr_array_index (array, index);
+}
+
+/**
+ * e_destination_store_list_destinations:
+ * @destination_store: an #EDestinationStore
+ *
+ * Gets a list of all the #EDestinations in @destination_store.
+ *
+ * Returns: A #GList of pointers to #EDestination. The list is owned
+ * by the caller, but the #EDestination elements aren't.
+ **/
+GList *
+e_destination_store_list_destinations (EDestinationStore *destination_store)
+{
+	GList *destination_list = NULL;
+	GPtrArray *array;
+	gint   i;
+
+	g_return_val_if_fail (E_IS_DESTINATION_STORE (destination_store), NULL);
+
+	array = destination_store->priv->destinations;
+
+	for (i = 0; i < array->len; i++) {
+		EDestination *destination;
+
+		destination = g_ptr_array_index (array, i);
+		destination_list = g_list_prepend (destination_list, destination);
+	}
+
+	destination_list = g_list_reverse (destination_list);
+
+	return destination_list;
+}
+
+/**
+ * e_destination_store_insert_destination:
+ * @destination_store: an #EDestinationStore
+ * @index: the index at which to insert
+ * @destination: an #EDestination to insert
+ *
+ * Inserts @destination into @destination_store at the position
+ * indicated by @index. @destination_store will ref @destination.
+ **/
+void
+e_destination_store_insert_destination (EDestinationStore *destination_store,
+                                        gint index,
+                                        EDestination *destination)
+{
+	GPtrArray *array;
+
+	g_return_if_fail (E_IS_DESTINATION_STORE (destination_store));
+	g_return_if_fail (index >= 0);
+
+	if (find_destination_by_pointer (destination_store, destination) >= 0) {
+		g_warning ("Same destination added more than once to EDestinationStore!");
+		return;
+	}
+
+	g_object_ref (destination);
+
+	array = destination_store->priv->destinations;
+	index = MIN (index, array->len);
+
+	g_ptr_array_set_size (array, array->len + 1);
+
+	if (array->len - 1 - index > 0) {
+		memmove (
+			array->pdata + index + 1,
+			array->pdata + index,
+			(array->len - 1 - index) * sizeof (gpointer));
+	}
+
+	array->pdata[index] = destination;
+	start_destination (destination_store, destination);
+	row_inserted (destination_store, index);
+}
+
+/**
+ * e_destination_store_append_destination:
+ * @destination_store: an #EDestinationStore
+ * @destination: an #EDestination
+ *
+ * Appends @destination to the list of destinations in @destination_store.
+ * @destination_store will ref @destination.
+ **/
+void
+e_destination_store_append_destination (EDestinationStore *destination_store,
+                                        EDestination *destination)
+{
+	GPtrArray *array;
+
+	g_return_if_fail (E_IS_DESTINATION_STORE (destination_store));
+
+	if (find_destination_by_email (destination_store, destination) >= 0 && !e_destination_is_evolution_list (destination)) {
+		g_warning ("Same destination added more than once to EDestinationStore!");
+		return;
+	}
+
+	array = destination_store->priv->destinations;
+	g_object_ref (destination);
+
+	g_ptr_array_add (array, destination);
+	start_destination (destination_store, destination);
+	row_inserted (destination_store, array->len - 1);
+}
+
+/**
+ * e_destination_store_remove_destination:
+ * @destination_store: an #EDestinationStore
+ * @destination: an #EDestination to remove
+ *
+ * Removes @destination from @destination_store. @destination_store will
+ * unref @destination.
+ **/
+void
+e_destination_store_remove_destination (EDestinationStore *destination_store,
+                                        EDestination *destination)
+{
+	GPtrArray *array;
+	gint n;
+
+	g_return_if_fail (E_IS_DESTINATION_STORE (destination_store));
+
+	n = find_destination_by_pointer (destination_store, destination);
+	if (n < 0) {
+		g_warning ("Tried to remove unknown destination from EDestinationStore!");
+		return;
+	}
+
+	stop_destination (destination_store, destination);
+	g_object_unref (destination);
+
+	array = destination_store->priv->destinations;
+	g_ptr_array_remove_index (array, n);
+	row_deleted (destination_store, n);
+}
+
+void
+e_destination_store_remove_destination_nth (EDestinationStore *destination_store,
+                                            gint n)
+{
+	EDestination *destination;
+	GPtrArray *array;
+
+	g_return_if_fail (n >= 0);
+
+	array = destination_store->priv->destinations;
+	destination = g_ptr_array_index (array, n);
+	stop_destination (destination_store, destination);
+	g_object_unref (destination);
+
+	g_ptr_array_remove_index (array, n);
+	row_deleted (destination_store, n);
+}
+
+guint
+e_destination_store_get_destination_count (EDestinationStore *destination_store)
+{
+	return destination_store->priv->destinations->len;
+}
+
+/* ---------------- *
+ * GtkTreeModel API *
+ * ---------------- */
+
+static GtkTreeModelFlags
+e_destination_store_get_flags (GtkTreeModel *tree_model)
+{
+	g_return_val_if_fail (E_IS_DESTINATION_STORE (tree_model), 0);
+
+	return GTK_TREE_MODEL_LIST_ONLY;
+}
+
+static gint
+e_destination_store_get_n_columns (GtkTreeModel *tree_model)
+{
+	g_return_val_if_fail (E_IS_DESTINATION_STORE (tree_model), 0);
+
+	return E_CONTACT_FIELD_LAST;
+}
+
+static GType
+e_destination_store_get_column_type (GtkTreeModel *tree_model,
+                                     gint index)
+{
+	g_return_val_if_fail (E_IS_DESTINATION_STORE (tree_model), G_TYPE_INVALID);
+	g_return_val_if_fail (index >= 0 && index < E_DESTINATION_STORE_NUM_COLUMNS, G_TYPE_INVALID);
+
+	return column_types[index];
+}
+
+static gboolean
+e_destination_store_get_iter (GtkTreeModel *tree_model,
+                              GtkTreeIter *iter,
+                              GtkTreePath *path)
+{
+	EDestinationStore *destination_store;
+	GPtrArray *array;
+	gint index;
+
+	g_return_val_if_fail (E_IS_DESTINATION_STORE (tree_model), FALSE);
+	g_return_val_if_fail (gtk_tree_path_get_depth (path) > 0, FALSE);
+
+	destination_store = E_DESTINATION_STORE (tree_model);
+
+	index = gtk_tree_path_get_indices (path)[0];
+	array = destination_store->priv->destinations;
+
+	if (index >= array->len)
+		return FALSE;
+
+	ITER_SET (destination_store, iter, index);
+	return TRUE;
+}
+
+GtkTreePath *
+e_destination_store_get_path (GtkTreeModel *tree_model,
+                              GtkTreeIter *iter)
+{
+	EDestinationStore *destination_store = E_DESTINATION_STORE (tree_model);
+	GtkTreePath       *path;
+	gint               index;
+
+	g_return_val_if_fail (E_IS_DESTINATION_STORE (tree_model), NULL);
+	g_return_val_if_fail (ITER_IS_VALID (destination_store, iter), NULL);
+
+	index = ITER_GET (iter);
+	path = gtk_tree_path_new ();
+	gtk_tree_path_append_index (path, index);
+
+	return path;
+}
+
+static gboolean
+e_destination_store_iter_next (GtkTreeModel *tree_model,
+                               GtkTreeIter *iter)
+{
+	EDestinationStore *destination_store = E_DESTINATION_STORE (tree_model);
+	gint           index;
+
+	g_return_val_if_fail (E_IS_DESTINATION_STORE (tree_model), FALSE);
+	g_return_val_if_fail (ITER_IS_VALID (destination_store, iter), FALSE);
+
+	index = ITER_GET (iter);
+
+	if (index + 1 < destination_store->priv->destinations->len) {
+		ITER_SET (destination_store, iter, index + 1);
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+static gboolean
+e_destination_store_iter_children (GtkTreeModel *tree_model,
+                                   GtkTreeIter *iter,
+                                   GtkTreeIter *parent)
+{
+	EDestinationStore *destination_store = E_DESTINATION_STORE (tree_model);
+
+	g_return_val_if_fail (E_IS_DESTINATION_STORE (tree_model), FALSE);
+
+	/* This is a list, nodes have no children. */
+	if (parent)
+		return FALSE;
+
+	/* But if parent == NULL we return the list itself as children of the root. */
+	if (destination_store->priv->destinations->len <= 0)
+		return FALSE;
+
+	ITER_SET (destination_store, iter, 0);
+	return TRUE;
+}
+
+static gboolean
+e_destination_store_iter_has_child (GtkTreeModel *tree_model,
+                                    GtkTreeIter *iter)
+{
+	g_return_val_if_fail (E_IS_DESTINATION_STORE (tree_model), FALSE);
+
+	if (iter == NULL)
+		return TRUE;
+
+	return FALSE;
+}
+
+static gint
+e_destination_store_iter_n_children (GtkTreeModel *tree_model,
+                                     GtkTreeIter *iter)
+{
+	EDestinationStore *destination_store = E_DESTINATION_STORE (tree_model);
+
+	g_return_val_if_fail (E_IS_DESTINATION_STORE (tree_model), -1);
+
+	if (iter == NULL)
+		return destination_store->priv->destinations->len;
+
+	g_return_val_if_fail (ITER_IS_VALID (destination_store, iter), -1);
+	return 0;
+}
+
+static gboolean
+e_destination_store_iter_nth_child (GtkTreeModel *tree_model,
+                                    GtkTreeIter *iter,
+                                    GtkTreeIter *parent,
+                                    gint n)
+{
+	EDestinationStore *destination_store = E_DESTINATION_STORE (tree_model);
+
+	g_return_val_if_fail (E_IS_DESTINATION_STORE (tree_model), FALSE);
+
+	if (parent)
+		return FALSE;
+
+	if (n < destination_store->priv->destinations->len) {
+		ITER_SET (destination_store, iter, n);
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+static gboolean
+e_destination_store_iter_parent (GtkTreeModel *tree_model,
+                                 GtkTreeIter *iter,
+                                 GtkTreeIter *child)
+{
+	return FALSE;
+}
+
+static void
+e_destination_store_get_value (GtkTreeModel *tree_model,
+                               GtkTreeIter *iter,
+                               gint column,
+                               GValue *value)
+{
+	EDestinationStore *destination_store = E_DESTINATION_STORE (tree_model);
+	EDestination *destination;
+	GString *string_new;
+	EContact *contact;
+	GPtrArray *array;
+	const gchar *string;
+	gint row;
+
+	g_return_if_fail (E_IS_DESTINATION_STORE (tree_model));
+	g_return_if_fail (column < E_DESTINATION_STORE_NUM_COLUMNS);
+	g_return_if_fail (ITER_IS_VALID (destination_store, iter));
+
+	g_value_init (value, column_types[column]);
+
+	array = destination_store->priv->destinations;
+
+	row = ITER_GET (iter);
+	if (row >= array->len)
+		return;
+
+	destination = g_ptr_array_index (array, row);
+	g_assert (destination);
+
+	switch (column) {
+		case E_DESTINATION_STORE_COLUMN_NAME:
+			string = e_destination_get_name (destination);
+			g_value_set_string (value, string);
+			break;
+
+		case E_DESTINATION_STORE_COLUMN_EMAIL:
+			string = e_destination_get_email (destination);
+			g_value_set_string (value, string);
+			break;
+
+		case E_DESTINATION_STORE_COLUMN_ADDRESS:
+			contact = e_destination_get_contact (destination);
+			if (contact && E_IS_CONTACT (contact)) {
+				if (e_contact_get (contact, E_CONTACT_IS_LIST)) {
+					string = e_destination_get_name (destination);
+					string_new = g_string_new (string);
+					string_new = g_string_append (string_new, " mailing list");
+					g_value_set_string (value, string_new->str);
+					g_string_free (string_new, TRUE);
+				}
+				else {
+					string = e_destination_get_address (destination);
+					g_value_set_string (value, string);
+				}
+			}
+			else {
+				string = e_destination_get_address (destination);
+				g_value_set_string (value, string);
+
+			}
+			break;
+
+		default:
+			g_assert_not_reached ();
+			break;
+	}
+}
+
+/**
+ * e_destination_store_get_stamp:
+ * @destination_store: an #EDestinationStore
+ *
+ * Since: 2.32
+ **/
+gint
+e_destination_store_get_stamp (EDestinationStore *destination_store)
+{
+	g_return_val_if_fail (E_IS_DESTINATION_STORE (destination_store), 0);
+
+	return destination_store->priv->stamp;
+}
diff --git a/e-util/e-destination-store.h b/e-util/e-destination-store.h
new file mode 100644
index 0000000..630db11
--- /dev/null
+++ b/e-util/e-destination-store.h
@@ -0,0 +1,106 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/* e-destination-store.h - EDestination store with GtkTreeModel interface.
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that 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 program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * Authors: Hans Petter Jansson <hpj novell com>
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_DESTINATION_STORE_H
+#define E_DESTINATION_STORE_H
+
+#include <gtk/gtk.h>
+#include <libebook/libebook.h>
+
+/* Standard GObject macros */
+#define E_TYPE_DESTINATION_STORE \
+	(e_destination_store_get_type ())
+#define E_DESTINATION_STORE(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_DESTINATION_STORE, EDestinationStore))
+#define E_DESTINATION_STORE_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_DESTINATION_STORE, EDestinationStoreClass))
+#define E_IS_DESTINATION_STORE(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_DESTINATION_STORE))
+#define E_IS_DESTINATION_STORE_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_DESTINATION_STORE))
+#define E_DESTINATION_STORE_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_DESTINATION_STORE, EDestinationStoreClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EDestinationStore EDestinationStore;
+typedef struct _EDestinationStoreClass EDestinationStoreClass;
+typedef struct _EDestinationStorePrivate EDestinationStorePrivate;
+
+struct _EDestinationStore {
+	GObject parent;
+	EDestinationStorePrivate *priv;
+};
+
+struct _EDestinationStoreClass {
+	GObjectClass parent_class;
+};
+
+typedef enum {
+	E_DESTINATION_STORE_COLUMN_NAME,
+	E_DESTINATION_STORE_COLUMN_EMAIL,
+	E_DESTINATION_STORE_COLUMN_ADDRESS,
+	E_DESTINATION_STORE_NUM_COLUMNS
+} EDestinationStoreColumnType;
+
+GType		e_destination_store_get_type	(void);
+EDestinationStore *
+		e_destination_store_new		(void);
+EDestination *	e_destination_store_get_destination
+						(EDestinationStore *destination_store,
+						 GtkTreeIter *iter);
+
+/* Returns a shallow copy; free the list when done, but don't unref elements */
+GList *		e_destination_store_list_destinations
+						(EDestinationStore *destination_store);
+
+void		e_destination_store_insert_destination
+						(EDestinationStore *destination_store,
+						 gint index,
+						 EDestination *destination);
+void		e_destination_store_append_destination
+						(EDestinationStore *destination_store,
+						 EDestination *destination);
+void		e_destination_store_remove_destination
+						(EDestinationStore *destination_store,
+						 EDestination *destination);
+void		e_destination_store_remove_destination_nth
+						(EDestinationStore *destination_store,
+						 gint n);
+guint		e_destination_store_get_destination_count
+						(EDestinationStore *destination_store);
+GtkTreePath *	e_destination_store_get_path	(GtkTreeModel *tree_model,
+						 GtkTreeIter *iter);
+gint		e_destination_store_get_stamp	(EDestinationStore *destination_store);
+
+G_END_DECLS
+
+#endif  /* E_DESTINATION_STORE_H */
diff --git a/e-util/e-dialog-utils.h b/e-util/e-dialog-utils.h
index f4f04b0..36c1730 100644
--- a/e-util/e-dialog-utils.h
+++ b/e-util/e-dialog-utils.h
@@ -20,6 +20,10 @@
  *
  */
 
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
 #ifndef E_DIALOG_UTILS_H
 #define E_DIALOG_UTILS_H
 
diff --git a/e-util/e-dialog-widgets.h b/e-util/e-dialog-widgets.h
index 5b3f650..4c8ade4 100644
--- a/e-util/e-dialog-widgets.h
+++ b/e-util/e-dialog-widgets.h
@@ -22,6 +22,10 @@
  *
  */
 
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
 #ifndef E_DIALOG_WIDGETS_H
 #define E_DIALOG_WIDGETS_H
 
diff --git a/e-util/e-event.h b/e-util/e-event.h
index 0b834c8..28caded 100644
--- a/e-util/e-event.h
+++ b/e-util/e-event.h
@@ -21,6 +21,10 @@
  *
  */
 
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
 /* This a bit 'whipped together', so is likely to change mid-term */
 
 #ifndef E_EVENT_H
@@ -197,7 +201,7 @@ void		e_event_target_free		(EEvent *event,
 /* For events, the plugin item talks to a specific instance, rather than
  * a set of instances of the hook handler */
 
-#include "e-util/e-plugin.h"
+#include <e-util/e-plugin.h>
 
 /* Standard GObject macros */
 #define E_TYPE_EVENT_HOOK \
diff --git a/e-util/e-file-request.c b/e-util/e-file-request.c
index 724680a..4ec56d2 100644
--- a/e-util/e-file-request.c
+++ b/e-util/e-file-request.c
@@ -22,8 +22,6 @@
 
 #include <libsoup/soup.h>
 
-#include <e-util/e-util.h>
-
 #include <string.h>
 
 #define d(x)
diff --git a/e-util/e-file-request.h b/e-util/e-file-request.h
index b8dd278..5d1cb3a 100644
--- a/e-util/e-file-request.h
+++ b/e-util/e-file-request.h
@@ -16,6 +16,10 @@
  *
  */
 
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
 #ifndef E_FILE_REQUEST_H
 #define E_FILE_REQUEST_H
 
diff --git a/e-util/e-file-utils.h b/e-util/e-file-utils.h
index e1e8b29..5d5df06 100644
--- a/e-util/e-file-utils.h
+++ b/e-util/e-file-utils.h
@@ -20,6 +20,10 @@
  *
  */
 
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
 #ifndef E_FILE_UTILS_H
 #define E_FILE_UTILS_H
 
diff --git a/filter/e-filter-code.c b/e-util/e-filter-code.c
similarity index 100%
rename from filter/e-filter-code.c
rename to e-util/e-filter-code.c
diff --git a/e-util/e-filter-code.h b/e-util/e-filter-code.h
new file mode 100644
index 0000000..45e1922
--- /dev/null
+++ b/e-util/e-filter-code.h
@@ -0,0 +1,72 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Not Zed <notzed lostzed mmc com au>
+ *      Jeffrey Stedfast <fejj ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_FILTER_CODE_H
+#define E_FILTER_CODE_H
+
+#include <e-util/e-filter-input.h>
+
+/* Standard GObject macros */
+#define E_TYPE_FILTER_CODE \
+	(e_filter_code_get_type ())
+#define E_FILTER_CODE(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_FILTER_CODE, EFilterCode))
+#define E_FILTER_CODE_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_FILTER_CODE, EFilterCodeClass))
+#define E_IS_FILTER_CODE(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_FILTER_CODE))
+#define E_IS_FILTER_CODE_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_FILTER_CODE))
+#define E_FILTER_CODE_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_FILTER_CODE, EFilterCodeClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EFilterCode EFilterCode;
+typedef struct _EFilterCodeClass EFilterCodeClass;
+typedef struct _EFilterCodePrivate EFilterCodePrivate;
+
+struct _EFilterCode {
+	EFilterInput parent;
+	EFilterCodePrivate *priv;
+};
+
+struct _EFilterCodeClass {
+	EFilterInputClass parent_class;
+};
+
+GType		e_filter_code_get_type		(void);
+EFilterCode *	e_filter_code_new		(gboolean raw_code);
+
+G_END_DECLS
+
+#endif /* E_FILTER_CODE_H */
diff --git a/filter/e-filter-color.c b/e-util/e-filter-color.c
similarity index 100%
rename from filter/e-filter-color.c
rename to e-util/e-filter-color.c
diff --git a/e-util/e-filter-color.h b/e-util/e-filter-color.h
new file mode 100644
index 0000000..acecf7d
--- /dev/null
+++ b/e-util/e-filter-color.h
@@ -0,0 +1,74 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Not Zed <notzed lostzed mmc com au>
+ *      Jeffrey Stedfast <fejj ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_FILTER_COLOR_H
+#define E_FILTER_COLOR_H
+
+#include <e-util/e-filter-element.h>
+
+/* Standard GObject macros */
+#define E_TYPE_FILTER_COLOR \
+	(e_filter_color_get_type ())
+#define E_FILTER_COLOR(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_FILTER_COLOR, EFilterColor))
+#define E_FILTER_COLOR_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_FILTER_COLOR, EFilterColorClass))
+#define E_IS_FILTER_COLOR(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_FILTER_COLOR))
+#define E_IS_FILTER_COLOR_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_FILTER_COLOR))
+#define E_FILTER_COLOR_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_FILTER_COLOR, EFilterColorClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EFilterColor EFilterColor;
+typedef struct _EFilterColorClass EFilterColorClass;
+typedef struct _EFilterColorPrivate EFilterColorPrivate;
+
+struct _EFilterColor {
+	EFilterElement parent;
+	EFilterColorPrivate *priv;
+
+	GdkColor color;
+};
+
+struct _EFilterColorClass {
+	EFilterElementClass parent_class;
+};
+
+GType		e_filter_color_get_type		(void);
+EFilterColor *	e_filter_color_new		(void);
+
+G_END_DECLS
+
+#endif /* E_FILTER_COLOR_H */
diff --git a/e-util/e-filter-datespec.c b/e-util/e-filter-datespec.c
new file mode 100644
index 0000000..d135358
--- /dev/null
+++ b/e-util/e-filter-datespec.c
@@ -0,0 +1,513 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Not Zed <notzed lostzed mmc com au>
+ *      Jeffrey Stedfast <fejj ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <stdlib.h>
+#include <time.h>
+#include <math.h>
+
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+
+#include "e-filter-datespec.h"
+#include "e-filter-part.h"
+#include "e-misc-utils.h"
+
+#ifdef G_OS_WIN32
+#ifdef localtime_r
+#undef localtime_r
+#endif
+#define localtime_r(tp,tmp) memcpy(tmp,localtime(tp),sizeof(struct tm))
+#endif
+
+#define E_FILTER_DATESPEC_GET_PRIVATE(obj) \
+	(G_TYPE_INSTANCE_GET_PRIVATE \
+	((obj), E_TYPE_FILTER_DATESPEC, EFilterDatespecPrivate))
+
+#define d(x)
+
+typedef struct {
+	guint32 seconds;
+	const gchar *past_singular;
+	const gchar *past_plural;
+	const gchar *future_singular;
+	const gchar *future_plural;
+	gfloat max;
+} timespan;
+
+#if 0
+
+/* Don't delete this code, since it is needed so that xgettext can extract the translations.
+ * Please, keep these strings in sync with the strings in the timespans array */
+
+	ngettext ("1 second ago", "%d seconds ago", 1);
+	ngettext ("1 second in the future", "%d seconds in the future", 1);
+	ngettext ("1 minute ago", "%d minutes ago", 1);
+	ngettext ("1 minute in the future", "%d minutes in the future", 1);
+	ngettext ("1 hour ago", "%d hours ago", 1);
+	ngettext ("1 hour in the future", "%d hours in the future", 1);
+	ngettext ("1 day ago", "%d days ago", 1);
+	ngettext ("1 day in the future", "%d days in the future", 1);
+	ngettext ("1 week ago", "%d weeks ago", 1);
+	ngettext ("1 week in the future", "%d weeks in the future", 1)
+	ngettext ("1 month ago", "%d months ago", 1);
+	ngettext ("1 month in the future", "%d months in the future", 1);
+	ngettext ("1 year ago", "%d years ago", 1);
+	ngettext ("1 year in the future", "%d years in the future", 1);
+
+#endif
+
+static const timespan timespans[] = {
+	{ 1, "1 second ago", "%d seconds ago", "1 second in the future", "%d seconds in the future", 59.0 },
+	{ 60, "1 minute ago", "%d minutes ago", "1 minute in the future", "%d minutes in the future", 59.0 },
+	{ 3600, "1 hour ago", "%d hours ago", "1 hour in the future", "%d hours in the future", 23.0 },
+	{ 86400, "1 day ago", "%d days ago", "1 day in the future", "%d days in the future", 31.0 },
+	{ 604800, "1 week ago", "%d weeks ago", "1 week in the future", "%d weeks in the future", 52.0 },
+	{ 2419200, "1 month ago", "%d months ago", "1 month in the future", "%d months in the future", 12.0 },
+	{ 31557600, "1 year ago", "%d years ago", "1 year in the future", "%d years in the future", 1000.0 },
+};
+
+#define DAY_INDEX 3
+
+struct _EFilterDatespecPrivate {
+	GtkWidget *label_button;
+	GtkWidget *notebook_type, *combobox_type, *calendar_specify, *spin_relative, *combobox_relative, *combobox_past_future;
+	EFilterDatespecType type;
+	gint span;
+};
+
+G_DEFINE_TYPE (
+	EFilterDatespec,
+	e_filter_datespec,
+	E_TYPE_FILTER_ELEMENT)
+
+static gint
+get_best_span (time_t val)
+{
+	gint i;
+
+	for (i = G_N_ELEMENTS (timespans) - 1; i >= 0; i--) {
+		if (val % timespans[i].seconds == 0)
+			return i;
+	}
+
+	return 0;
+}
+
+/* sets button label */
+static void
+set_button (EFilterDatespec *fds)
+{
+	gchar buf[128];
+	gchar *label = buf;
+
+	switch (fds->type) {
+	case FDST_UNKNOWN:
+		label = _("<click here to select a date>");
+		break;
+	case FDST_NOW:
+		label = _("now");
+		break;
+	case FDST_SPECIFIED: {
+		struct tm tm;
+
+		localtime_r (&fds->value, &tm);
+		/* strftime for date filter display, only needs to show a day date (i.e. no time) */
+		strftime (buf, sizeof (buf), _("%d-%b-%Y"), &tm);
+		break; }
+	case FDST_X_AGO:
+		if (fds->value == 0)
+			label = _("now");
+		else {
+			gint span, count;
+
+			span = get_best_span (fds->value);
+			count = fds->value / timespans[span].seconds;
+			sprintf (buf, ngettext (timespans[span].past_singular, timespans[span].past_plural, count), count);
+		}
+		break;
+	case FDST_X_FUTURE:
+		if (fds->value == 0)
+			label = _("now");
+		else {
+			gint span, count;
+
+			span = get_best_span (fds->value);
+			count = fds->value / timespans[span].seconds;
+			sprintf (buf, ngettext (timespans[span].future_singular, timespans[span].future_plural, count), count);
+		}
+		break;
+	}
+
+	gtk_label_set_text ((GtkLabel *) fds->priv->label_button, label);
+}
+
+static void
+get_values (EFilterDatespec *fds)
+{
+	EFilterDatespecPrivate *p = E_FILTER_DATESPEC_GET_PRIVATE (fds);
+
+	switch (fds->priv->type) {
+	case FDST_SPECIFIED: {
+		guint year, month, day;
+		struct tm tm;
+
+		gtk_calendar_get_date ((GtkCalendar *) p->calendar_specify, &year, &month, &day);
+		memset (&tm, 0, sizeof (tm));
+		tm.tm_mday = day;
+		tm.tm_year = year - 1900;
+		tm.tm_mon = month;
+		fds->value = mktime (&tm);
+		/* what about timezone? */
+		break; }
+	case FDST_X_FUTURE:
+	case FDST_X_AGO: {
+		gint val;
+
+		val = gtk_spin_button_get_value_as_int ((GtkSpinButton *) p->spin_relative);
+		fds->value = timespans[p->span].seconds * val;
+		break; }
+	case FDST_NOW:
+	default:
+		break;
+	}
+
+	fds->type = p->type;
+}
+
+static void
+set_values (EFilterDatespec *fds)
+{
+	gint note_type;
+
+	EFilterDatespecPrivate *p = E_FILTER_DATESPEC_GET_PRIVATE (fds);
+
+	p->type = fds->type == FDST_UNKNOWN ? FDST_NOW : fds->type;
+
+	note_type = p->type==FDST_X_FUTURE ? FDST_X_AGO : p->type; /* FUTURE and AGO use the same notebook pages/etc. */
+
+	switch (p->type) {
+	case FDST_NOW:
+	case FDST_UNKNOWN:
+		/* noop */
+		break;
+	case FDST_SPECIFIED:
+	{
+		struct tm tm;
+
+		localtime_r (&fds->value, &tm);
+		gtk_calendar_select_month ((GtkCalendar *) p->calendar_specify, tm.tm_mon, tm.tm_year + 1900);
+		gtk_calendar_select_day ((GtkCalendar *) p->calendar_specify, tm.tm_mday);
+		break;
+	}
+	case FDST_X_AGO:
+		p->span = get_best_span (fds->value);
+		gtk_spin_button_set_value ((GtkSpinButton *) p->spin_relative, fds->value / timespans[p->span].seconds);
+		gtk_combo_box_set_active (GTK_COMBO_BOX (p->combobox_relative), p->span);
+		gtk_combo_box_set_active (GTK_COMBO_BOX (p->combobox_past_future), 0);
+		break;
+	case FDST_X_FUTURE:
+		p->span = get_best_span (fds->value);
+		gtk_spin_button_set_value ((GtkSpinButton *) p->spin_relative, fds->value / timespans[p->span].seconds);
+		gtk_combo_box_set_active (GTK_COMBO_BOX (p->combobox_relative), p->span);
+		gtk_combo_box_set_active (GTK_COMBO_BOX (p->combobox_past_future), 1);
+		break;
+	}
+
+	gtk_notebook_set_current_page ((GtkNotebook *) p->notebook_type, note_type);
+	gtk_combo_box_set_active (GTK_COMBO_BOX (p->combobox_type), note_type);
+}
+
+static void
+set_combobox_type (GtkComboBox *combobox,
+                   EFilterDatespec *fds)
+{
+	fds->priv->type = gtk_combo_box_get_active (combobox);
+	gtk_notebook_set_current_page ((GtkNotebook *) fds->priv->notebook_type, fds->priv->type);
+}
+
+static void
+set_combobox_relative (GtkComboBox *combobox,
+                       EFilterDatespec *fds)
+{
+	fds->priv->span = gtk_combo_box_get_active (combobox);
+}
+
+static void
+set_combobox_past_future (GtkComboBox *combobox,
+                          EFilterDatespec *fds)
+{
+	if (gtk_combo_box_get_active (combobox) == 0)
+		fds->type = fds->priv->type = FDST_X_AGO;
+	else
+		fds->type = fds->priv->type = FDST_X_FUTURE;
+}
+
+static void
+button_clicked (GtkButton *button,
+                EFilterDatespec *fds)
+{
+	EFilterDatespecPrivate *p = E_FILTER_DATESPEC_GET_PRIVATE (fds);
+	GtkWidget *content_area;
+	GtkWidget *toplevel;
+	GtkDialog *dialog;
+	GtkBuilder *builder;
+
+	/* XXX I think we're leaking the GtkBuilder. */
+	builder = gtk_builder_new ();
+	e_load_ui_builder_definition (builder, "filter.ui");
+
+	toplevel = e_builder_get_widget (builder, "filter_datespec");
+
+	dialog = (GtkDialog *) gtk_dialog_new ();
+	gtk_window_set_title (
+		GTK_WINDOW (dialog),
+		_("Select a time to compare against"));
+	gtk_dialog_add_buttons (
+		dialog,
+		GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+		GTK_STOCK_OK, GTK_RESPONSE_OK,
+		NULL);
+
+	p->notebook_type = e_builder_get_widget (builder, "notebook_type");
+	p->combobox_type = e_builder_get_widget (builder, "combobox_type");
+	p->calendar_specify = e_builder_get_widget (builder, "calendar_specify");
+	p->spin_relative = e_builder_get_widget (builder, "spin_relative");
+	p->combobox_relative = e_builder_get_widget (builder, "combobox_relative");
+	p->combobox_past_future = e_builder_get_widget (builder, "combobox_past_future");
+
+	set_values (fds);
+
+	g_signal_connect (
+		p->combobox_type, "changed",
+		G_CALLBACK (set_combobox_type), fds);
+	g_signal_connect (
+		p->combobox_relative, "changed",
+		G_CALLBACK (set_combobox_relative), fds);
+	g_signal_connect (
+		p->combobox_past_future, "changed",
+		G_CALLBACK (set_combobox_past_future), fds);
+
+	content_area = gtk_dialog_get_content_area (dialog);
+	gtk_box_pack_start (GTK_BOX (content_area), toplevel, TRUE, TRUE, 3);
+
+	if (gtk_dialog_run (dialog) == GTK_RESPONSE_OK) {
+		get_values (fds);
+		set_button (fds);
+	}
+
+	gtk_widget_destroy ((GtkWidget *) dialog);
+}
+
+static gboolean
+filter_datespec_validate (EFilterElement *element,
+                          EAlert **alert)
+{
+	EFilterDatespec *fds = E_FILTER_DATESPEC (element);
+	gboolean valid;
+
+	g_warn_if_fail (alert == NULL || *alert == NULL);
+
+	valid = fds->type != FDST_UNKNOWN;
+	if (!valid) {
+		if (alert)
+			*alert = e_alert_new ("filter:no-date", NULL);
+	}
+
+	return valid;
+}
+
+static gint
+filter_datespec_eq (EFilterElement *element_a,
+                    EFilterElement *element_b)
+{
+	EFilterDatespec *datespec_a = E_FILTER_DATESPEC (element_a);
+	EFilterDatespec *datespec_b = E_FILTER_DATESPEC (element_b);
+
+	/* Chain up to parent's eq() method. */
+	if (!E_FILTER_ELEMENT_CLASS (e_filter_datespec_parent_class)->
+		eq (element_a, element_b))
+		return FALSE;
+
+	return (datespec_a->type == datespec_b->type) &&
+		(datespec_a->value == datespec_b->value);
+}
+
+static xmlNodePtr
+filter_datespec_xml_encode (EFilterElement *element)
+{
+	xmlNodePtr value, work;
+	EFilterDatespec *fds = E_FILTER_DATESPEC (element);
+	gchar str[32];
+
+	d (printf ("Encoding datespec as xml\n"));
+
+	value = xmlNewNode (NULL, (xmlChar *)"value");
+	xmlSetProp (value, (xmlChar *)"name", (xmlChar *) element->name);
+	xmlSetProp (value, (xmlChar *)"type", (xmlChar *)"datespec");
+
+	work = xmlNewChild (value, NULL, (xmlChar *)"datespec", NULL);
+	sprintf (str, "%d", fds->type);
+	xmlSetProp (work, (xmlChar *)"type", (xmlChar *) str);
+	sprintf (str, "%d", (gint) fds->value);
+	xmlSetProp (work, (xmlChar *)"value", (xmlChar *) str);
+
+	return value;
+}
+
+static gint
+filter_datespec_xml_decode (EFilterElement *element,
+                            xmlNodePtr node)
+{
+	EFilterDatespec *fds = E_FILTER_DATESPEC (element);
+	xmlNodePtr n;
+	xmlChar *val;
+
+	d (printf ("Decoding datespec from xml %p\n", element));
+
+	xmlFree (element->name);
+	element->name = (gchar *) xmlGetProp (node, (xmlChar *)"name");
+
+	n = node->children;
+	while (n) {
+		if (!strcmp ((gchar *) n->name, "datespec")) {
+			val = xmlGetProp (n, (xmlChar *)"type");
+			fds->type = atoi ((gchar *) val);
+			xmlFree (val);
+			val = xmlGetProp (n, (xmlChar *)"value");
+			fds->value = atoi ((gchar *) val);
+			xmlFree (val);
+			break;
+		}
+		n = n->next;
+	}
+
+	return 0;
+}
+
+static GtkWidget *
+filter_datespec_get_widget (EFilterElement *element)
+{
+	EFilterDatespec *fds = E_FILTER_DATESPEC (element);
+	GtkWidget *button;
+
+	fds->priv->label_button = gtk_label_new ("");
+	gtk_misc_set_alignment (GTK_MISC (fds->priv->label_button), 0.5, 0.5);
+	set_button (fds);
+
+	button = gtk_button_new ();
+	gtk_container_add (GTK_CONTAINER (button), fds->priv->label_button);
+	g_signal_connect (
+		button, "clicked",
+		G_CALLBACK (button_clicked), fds);
+
+	gtk_widget_show (button);
+	gtk_widget_show (fds->priv->label_button);
+
+	return button;
+}
+
+static void
+filter_datespec_format_sexp (EFilterElement *element,
+                             GString *out)
+{
+	EFilterDatespec *fds = E_FILTER_DATESPEC (element);
+
+	switch (fds->type) {
+	case FDST_UNKNOWN:
+		g_warning ("user hasn't selected a datespec yet!");
+		/* fall through */
+	case FDST_NOW:
+		g_string_append (out, "(get-current-date)");
+		break;
+	case FDST_SPECIFIED:
+		g_string_append_printf (out, "%d", (gint) fds->value);
+		break;
+	case FDST_X_AGO:
+		switch (get_best_span (fds->value)) {
+		case 5: /* months */
+			g_string_append_printf (out, "(get-relative-months (- 0 %d))", (gint) (fds->value / timespans[5].seconds));
+			break;
+		case 6: /* years */
+			g_string_append_printf (out, "(get-relative-months (- 0 %d))", (gint) (12 * fds->value / timespans[6].seconds));
+			break;
+		default:
+			g_string_append_printf (out, "(- (get-current-date) %d)", (gint) fds->value);
+			break;
+		}
+		break;
+	case FDST_X_FUTURE:
+		switch (get_best_span (fds->value)) {
+		case 5: /* months */
+			g_string_append_printf (out, "(get-relative-months %d)", (gint) (fds->value / timespans[5].seconds));
+			break;
+		case 6: /* years */
+			g_string_append_printf (out, "(get-relative-months %d)", (gint) (12 * fds->value / timespans[6].seconds));
+			break;
+		default:
+			g_string_append_printf (out, "(+ (get-current-date) %d)", (gint) fds->value);
+			break;
+		}
+		break;
+	}
+}
+
+static void
+e_filter_datespec_class_init (EFilterDatespecClass *class)
+{
+	EFilterElementClass *filter_element_class;
+
+	g_type_class_add_private (class, sizeof (EFilterDatespecPrivate));
+
+	filter_element_class = E_FILTER_ELEMENT_CLASS (class);
+	filter_element_class->validate = filter_datespec_validate;
+	filter_element_class->eq = filter_datespec_eq;
+	filter_element_class->xml_encode = filter_datespec_xml_encode;
+	filter_element_class->xml_decode = filter_datespec_xml_decode;
+	filter_element_class->get_widget = filter_datespec_get_widget;
+	filter_element_class->format_sexp = filter_datespec_format_sexp;
+}
+
+static void
+e_filter_datespec_init (EFilterDatespec *datespec)
+{
+	datespec->priv = E_FILTER_DATESPEC_GET_PRIVATE (datespec);
+	datespec->type = FDST_UNKNOWN;
+}
+
+/**
+ * filter_datespec_new:
+ *
+ * Create a new EFilterDatespec object.
+ *
+ * Return value: A new #EFilterDatespec object.
+ **/
+EFilterDatespec *
+e_filter_datespec_new (void)
+{
+	return g_object_new (E_TYPE_FILTER_DATESPEC, NULL);
+}
diff --git a/e-util/e-filter-datespec.h b/e-util/e-filter-datespec.h
new file mode 100644
index 0000000..ecc15bf
--- /dev/null
+++ b/e-util/e-filter-datespec.h
@@ -0,0 +1,91 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Not Zed <notzed lostzed mmc com au>
+ *      Jeffrey Stedfast <fejj ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_FILTER_DATESPEC_H
+#define E_FILTER_DATESPEC_H
+
+#include <time.h>
+#include <e-util/e-filter-element.h>
+
+/* Standard GObject types */
+#define E_TYPE_FILTER_DATESPEC \
+	(e_filter_datespec_get_type ())
+#define E_FILTER_DATESPEC(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_FILTER_DATESPEC, EFilterDatespec))
+#define E_FILTER_DATESPEC_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_FILTER_DATESPEC, EFilterDatespecClass))
+#define E_IS_FILTER_DATESPEC(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_FILTER_DATESPEC))
+#define E_IS_FILTER_DATESPEC_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_FILTER_DATESPEC))
+#define E_FILTER_DATESPEC_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_FILTER_DATESPEC, EFilterDatespecClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EFilterDatespec EFilterDatespec;
+typedef struct _EFilterDatespecClass EFilterDatespecClass;
+typedef struct _EFilterDatespecPrivate EFilterDatespecPrivate;
+
+typedef enum {
+	FDST_UNKNOWN = -1,
+	FDST_NOW,
+	FDST_SPECIFIED,
+	FDST_X_AGO,
+	FDST_X_FUTURE
+} EFilterDatespecType;
+
+struct _EFilterDatespec {
+	EFilterElement parent;
+	EFilterDatespecPrivate *priv;
+
+	EFilterDatespecType type;
+
+	/* either a timespan, an absolute time, or 0
+	 * depending on type -- the above mapping to
+	 * (X_FUTURE, X_AGO, SPECIFIED, NOW)
+	 */
+
+	time_t value;
+};
+
+struct _EFilterDatespecClass {
+	EFilterElementClass parent_class;
+};
+
+GType		e_filter_datespec_get_type	(void);
+EFilterDatespec *
+		e_filter_datespec_new		(void);
+
+G_END_DECLS
+
+#endif /* E_FILTER_DATESPEC_H */
diff --git a/filter/e-filter-element.c b/e-util/e-filter-element.c
similarity index 100%
rename from filter/e-filter-element.c
rename to e-util/e-filter-element.c
diff --git a/e-util/e-filter-element.h b/e-util/e-filter-element.h
new file mode 100644
index 0000000..ecec9db
--- /dev/null
+++ b/e-util/e-filter-element.h
@@ -0,0 +1,125 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Not Zed <notzed lostzed mmc com au>
+ *      Jeffrey Stedfast <fejj ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_FILTER_ELEMENT_H
+#define E_FILTER_ELEMENT_H
+
+#include <gtk/gtk.h>
+#include <camel/camel.h>
+#include <libxml/parser.h>
+#include <libxml/xmlmemory.h>
+
+#include <e-util/e-alert.h>
+
+#define E_TYPE_FILTER_ELEMENT \
+	(e_filter_element_get_type ())
+#define E_FILTER_ELEMENT(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_FILTER_ELEMENT, EFilterElement))
+#define E_FILTER_ELEMENT_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_FILTER_ELEMENT, EFilterElementClass))
+#define E_IS_FILTER_ELEMENT(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_FILTER_ELEMENT))
+#define E_IS_FILTER_ELEMENT_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_FILTER_ELEMENT))
+#define E_FILTER_ELEMENT_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_FILTER_ELEMENT, EFilterElementClass))
+
+G_BEGIN_DECLS
+
+struct _EFilterPart;
+
+typedef struct _EFilterElement EFilterElement;
+typedef struct _EFilterElementClass EFilterElementClass;
+typedef struct _EFilterElementPrivate EFilterElementPrivate;
+
+typedef EFilterElement * (*EFilterElementFunc) (gpointer data);
+
+struct _EFilterElement {
+	GObject parent;
+	EFilterElementPrivate *priv;
+
+	gchar *name;
+	gpointer data;
+};
+
+struct _EFilterElementClass {
+	GObjectClass parent_class;
+
+	gboolean	(*validate)		(EFilterElement *element,
+						 EAlert **alert);
+	gint		(*eq)			(EFilterElement *element_a,
+						 EFilterElement *element_b);
+
+	void		(*xml_create)		(EFilterElement *element,
+						 xmlNodePtr node);
+	xmlNodePtr	(*xml_encode)		(EFilterElement *element);
+	gint		(*xml_decode)		(EFilterElement *element,
+						 xmlNodePtr node);
+
+	EFilterElement *(*clone)		(EFilterElement *element);
+	void		(*copy_value)		(EFilterElement *dst_element,
+						 EFilterElement *src_element);
+
+	GtkWidget *	(*get_widget)		(EFilterElement *element);
+	void		(*build_code)		(EFilterElement *element,
+						 GString *out,
+						 struct _EFilterPart *part);
+	void		(*format_sexp)		(EFilterElement *element,
+						 GString *out);
+};
+
+GType		e_filter_element_get_type	(void);
+EFilterElement *e_filter_element_new		(void);
+void		e_filter_element_set_data	(EFilterElement *element,
+						 gpointer data);
+gboolean	e_filter_element_validate	(EFilterElement *element,
+						 EAlert **alert);
+gint		e_filter_element_eq		(EFilterElement *element_a,
+						 EFilterElement *element_b);
+void		e_filter_element_xml_create	(EFilterElement *element,
+						 xmlNodePtr node);
+xmlNodePtr	e_filter_element_xml_encode	(EFilterElement *element);
+gint		e_filter_element_xml_decode	(EFilterElement *element,
+						 xmlNodePtr node);
+EFilterElement *e_filter_element_clone		(EFilterElement *element);
+void		e_filter_element_copy_value	(EFilterElement *dst_element,
+						 EFilterElement *src_element);
+GtkWidget *	e_filter_element_get_widget	(EFilterElement *element);
+void		e_filter_element_build_code	(EFilterElement *element,
+						 GString *out,
+						 struct _EFilterPart *part);
+void		e_filter_element_format_sexp	(EFilterElement *element,
+						 GString *out);
+
+G_END_DECLS
+
+#endif /* E_FILTER_ELEMENT_H */
diff --git a/e-util/e-filter-file.c b/e-util/e-filter-file.c
new file mode 100644
index 0000000..8e46b52
--- /dev/null
+++ b/e-util/e-filter-file.c
@@ -0,0 +1,261 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Jeffrey Stedfast <fejj ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <sys/types.h>
+
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+#include <glib/gstdio.h>
+
+#include "e-alert.h"
+#include "e-filter-file.h"
+#include "e-filter-part.h"
+
+G_DEFINE_TYPE (
+	EFilterFile,
+	e_filter_file,
+	E_TYPE_FILTER_ELEMENT)
+
+static void
+filter_file_filename_changed (GtkFileChooser *file_chooser,
+                              EFilterElement *element)
+{
+	EFilterFile *file = E_FILTER_FILE (element);
+	const gchar *path;
+
+	path = gtk_file_chooser_get_filename (file_chooser);
+
+	g_free (file->path);
+	file->path = g_strdup (path);
+}
+
+static void
+filter_file_finalize (GObject *object)
+{
+	EFilterFile *file = E_FILTER_FILE (object);
+
+	xmlFree (file->type);
+	g_free (file->path);
+
+	/* Chain up to parent's finalize() method. */
+	G_OBJECT_CLASS (e_filter_file_parent_class)->finalize (object);
+}
+
+static gboolean
+filter_file_validate (EFilterElement *element,
+                      EAlert **alert)
+{
+	EFilterFile *file = E_FILTER_FILE (element);
+
+	g_warn_if_fail (alert == NULL || *alert == NULL);
+
+	if (!file->path) {
+		if (alert)
+			*alert = e_alert_new ("filter:no-file", NULL);
+		return FALSE;
+	}
+
+	/* FIXME: do more to validate command-lines? */
+
+	if (g_strcmp0 (file->type, "file") == 0) {
+		if (!g_file_test (file->path, G_FILE_TEST_IS_REGULAR)) {
+			if (alert)
+				*alert = e_alert_new ("filter:bad-file",
+						       file->path, NULL);
+			return FALSE;
+		}
+	} else if (g_strcmp0 (file->type, "command") == 0) {
+		/* Only requirements so far is that the
+		 * command can't be an empty string. */
+		return (file->path[0] != '\0');
+	}
+
+	return TRUE;
+}
+
+static gint
+filter_file_eq (EFilterElement *element_a,
+                EFilterElement *element_b)
+{
+	EFilterFile *file_a = E_FILTER_FILE (element_a);
+	EFilterFile *file_b = E_FILTER_FILE (element_b);
+
+	/* Chain up to parent's eq() method. */
+	if (!E_FILTER_ELEMENT_CLASS (e_filter_file_parent_class)->
+		eq (element_a, element_b))
+		return FALSE;
+
+	if (g_strcmp0 (file_a->path, file_b->path) != 0)
+		return FALSE;
+
+	if (g_strcmp0 (file_a->type, file_b->type) != 0)
+		return FALSE;
+
+	return TRUE;
+}
+
+static xmlNodePtr
+filter_file_xml_encode (EFilterElement *element)
+{
+	EFilterFile *file = E_FILTER_FILE (element);
+	xmlNodePtr cur, value;
+	const gchar *type;
+
+	type = file->type ? file->type : "file";
+
+	value = xmlNewNode (NULL, (xmlChar *)"value");
+	xmlSetProp (value, (xmlChar *) "name", (xmlChar *) element->name);
+	xmlSetProp (value, (xmlChar *) "type", (xmlChar *) type);
+
+	cur = xmlNewChild (value, NULL, (xmlChar *) type, NULL);
+	xmlNodeSetContent (cur, (xmlChar *) file->path);
+
+	return value;
+}
+
+static gint
+filter_file_xml_decode (EFilterElement *element,
+                        xmlNodePtr node)
+{
+	EFilterFile *file = E_FILTER_FILE (element);
+	gchar *name, *str, *type;
+	xmlNodePtr child;
+
+	name = (gchar *) xmlGetProp (node, (xmlChar *) "name");
+	type = (gchar *) xmlGetProp (node, (xmlChar *) "type");
+
+	xmlFree (element->name);
+	element->name = name;
+
+	xmlFree (file->type);
+	file->type = type;
+
+	g_free (file->path);
+	file->path = NULL;
+
+	child = node->children;
+	while (child != NULL) {
+		if (!strcmp ((gchar *) child->name, type)) {
+			str = (gchar *) xmlNodeGetContent (child);
+			file->path = g_strdup (str ? str : "");
+			xmlFree (str);
+
+			break;
+		} else if (child->type == XML_ELEMENT_NODE) {
+			g_warning (
+				"Unknown node type '%s' encountered "
+				"decoding a %s\n", child->name, type);
+		}
+
+		child = child->next;
+	}
+
+	return 0;
+}
+
+static GtkWidget *
+filter_file_get_widget (EFilterElement *element)
+{
+	EFilterFile *file = E_FILTER_FILE (element);
+	GtkWidget *widget;
+
+	widget = gtk_file_chooser_button_new (
+		_("Choose a File"), GTK_FILE_CHOOSER_ACTION_OPEN);
+	gtk_file_chooser_set_filename (
+		GTK_FILE_CHOOSER (widget), file->path);
+	g_signal_connect (
+		widget, "selection-changed",
+		G_CALLBACK (filter_file_filename_changed), element);
+
+	return widget;
+}
+
+static void
+filter_file_format_sexp (EFilterElement *element,
+                         GString *out)
+{
+	EFilterFile *file = E_FILTER_FILE (element);
+
+	camel_sexp_encode_string (out, file->path);
+}
+
+static void
+e_filter_file_class_init (EFilterFileClass *class)
+{
+	GObjectClass *object_class;
+	EFilterElementClass *filter_element_class;
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->finalize = filter_file_finalize;
+
+	filter_element_class = E_FILTER_ELEMENT_CLASS (class);
+	filter_element_class->validate = filter_file_validate;
+	filter_element_class->eq = filter_file_eq;
+	filter_element_class->xml_encode = filter_file_xml_encode;
+	filter_element_class->xml_decode = filter_file_xml_decode;
+	filter_element_class->get_widget = filter_file_get_widget;
+	filter_element_class->format_sexp = filter_file_format_sexp;
+}
+
+static void
+e_filter_file_init (EFilterFile *filter)
+{
+}
+
+/**
+ * filter_file_new:
+ *
+ * Create a new EFilterFile object.
+ *
+ * Return value: A new #EFilterFile object.
+ **/
+EFilterFile *
+e_filter_file_new (void)
+{
+	return g_object_new (E_TYPE_FILTER_FILE, NULL);
+}
+
+EFilterFile *
+e_filter_file_new_type_name (const gchar *type)
+{
+	EFilterFile *file;
+
+	file = e_filter_file_new ();
+	file->type = (gchar *) xmlStrdup ((xmlChar *) type);
+
+	return file;
+}
+
+void
+e_filter_file_set_path (EFilterFile *file,
+                        const gchar *path)
+{
+	g_return_if_fail (E_IS_FILTER_FILE (file));
+
+	g_free (file->path);
+	file->path = g_strdup (path);
+}
diff --git a/e-util/e-filter-file.h b/e-util/e-filter-file.h
new file mode 100644
index 0000000..c78062b
--- /dev/null
+++ b/e-util/e-filter-file.h
@@ -0,0 +1,78 @@
+/*
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Jeffrey Stedfast <fejj ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_FILTER_FILE_H
+#define E_FILTER_FILE_H
+
+#include <e-util/e-filter-element.h>
+
+/* Standard GObject macros */
+#define E_TYPE_FILTER_FILE \
+	(e_filter_file_get_type ())
+#define E_FILTER_FILE(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_FILTER_FILE, EFilterFile))
+#define E_FILTER_FILE_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_FILTER_FILE, EFilterFileClass))
+#define E_IS_FILTER_FILE(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_FILTER_FILE))
+#define E_IS_FILTER_FILE_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_FILTER_FILE))
+#define E_FILTER_FILE_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_FILTER_FILE, EFilterFileClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EFilterFile EFilterFile;
+typedef struct _EFilterFileClass EFilterFileClass;
+typedef struct _EFilterFilePrivate EFilterFilePrivate;
+
+struct _EFilterFile {
+	EFilterElement parent;
+	EFilterFilePrivate *priv;
+
+	gchar *type;
+	gchar *path;
+};
+
+struct _EFilterFileClass {
+	EFilterElementClass parent_class;
+};
+
+GType		e_filter_file_get_type		(void);
+EFilterFile *	e_filter_file_new		(void);
+EFilterFile *	e_filter_file_new_type_name	(const gchar *type);
+void		e_filter_file_set_path		(EFilterFile *file,
+						 const gchar *path);
+
+G_END_DECLS
+
+#endif /* E_FILTER_FILE_H */
diff --git a/e-util/e-filter-input.c b/e-util/e-filter-input.c
new file mode 100644
index 0000000..424363e
--- /dev/null
+++ b/e-util/e-filter-input.c
@@ -0,0 +1,304 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Not Zed <notzed lostzed mmc com au>
+ *      Jeffrey Stedfast <fejj ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <sys/types.h>
+#include <regex.h>
+
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+
+#include "e-alert.h"
+#include "e-filter-input.h"
+
+G_DEFINE_TYPE (
+	EFilterInput,
+	e_filter_input,
+	E_TYPE_FILTER_ELEMENT)
+
+static void
+filter_input_entry_changed (GtkEntry *entry,
+                            EFilterElement *element)
+{
+	EFilterInput *input = E_FILTER_INPUT (element);
+	const gchar *text;
+
+	g_list_foreach (input->values, (GFunc) g_free, NULL);
+	g_list_free (input->values);
+
+	text = gtk_entry_get_text (entry);
+	input->values = g_list_append (NULL, g_strdup (text));
+}
+
+static void
+filter_input_finalize (GObject *object)
+{
+	EFilterInput *input = E_FILTER_INPUT (object);
+
+	xmlFree (input->type);
+
+	g_list_foreach (input->values, (GFunc) g_free, NULL);
+	g_list_free (input->values);
+
+	/* Chain up to parent's finalize() method. */
+	G_OBJECT_CLASS (e_filter_input_parent_class)->finalize (object);
+}
+
+static gboolean
+filter_input_validate (EFilterElement *element,
+                       EAlert **alert)
+{
+	EFilterInput *input = E_FILTER_INPUT (element);
+	gboolean valid = TRUE;
+
+	g_warn_if_fail (alert == NULL || *alert == NULL);
+
+	if (input->values && !strcmp (input->type, "regex")) {
+		const gchar *pattern;
+		regex_t regexpat;
+		gint regerr;
+
+		pattern = input->values->data;
+
+		regerr = regcomp (
+			&regexpat, pattern,
+			REG_EXTENDED | REG_NEWLINE | REG_ICASE);
+		if (regerr != 0) {
+			if (alert) {
+				gsize reglen;
+				gchar *regmsg;
+
+				/* regerror gets called twice to get the full error string
+				 * length to do proper posix error reporting */
+				reglen = regerror (regerr, &regexpat, 0, 0);
+				regmsg = g_malloc0 (reglen + 1);
+				regerror (regerr, &regexpat, regmsg, reglen);
+
+				*alert = e_alert_new ("filter:bad-regexp",
+						      pattern, regmsg, NULL);
+				g_free (regmsg);
+			}
+
+			valid = FALSE;
+		}
+
+		regfree (&regexpat);
+	}
+
+	return valid;
+}
+
+static gint
+filter_input_eq (EFilterElement *element_a,
+                 EFilterElement *element_b)
+{
+	EFilterInput *input_a = E_FILTER_INPUT (element_a);
+	EFilterInput *input_b = E_FILTER_INPUT (element_b);
+	GList *link_a;
+	GList *link_b;
+
+	/* Chain up to parent's eq() method. */
+	if (!E_FILTER_ELEMENT_CLASS (e_filter_input_parent_class)->
+		eq (element_a, element_b))
+		return FALSE;
+
+	if (g_strcmp0 (input_a->type, input_b->type) != 0)
+		return FALSE;
+
+	link_a = input_a->values;
+	link_b = input_b->values;
+
+	while (link_a != NULL && link_b != NULL) {
+		if (g_strcmp0 (link_a->data, link_b->data) != 0)
+			return FALSE;
+
+		link_a = g_list_next (link_a);
+		link_b = g_list_next (link_b);
+	}
+
+	if (link_a != NULL || link_b != NULL)
+		return FALSE;
+
+	return TRUE;
+}
+
+static xmlNodePtr
+filter_input_xml_encode (EFilterElement *element)
+{
+	EFilterInput *input = E_FILTER_INPUT (element);
+	xmlNodePtr value;
+	GList *link;
+	const gchar *type;
+
+	type = input->type ? input->type : "string";
+
+	value = xmlNewNode (NULL, (xmlChar *) "value");
+	xmlSetProp (value, (xmlChar *) "name", (xmlChar *) element->name);
+	xmlSetProp (value, (xmlChar *) "type", (xmlChar *) type);
+
+	for (link = input->values; link != NULL; link = g_list_next (link)) {
+		xmlChar *str = link->data;
+		xmlNodePtr cur;
+
+		cur = xmlNewChild (value, NULL, (xmlChar *) type, NULL);
+
+		str = xmlEncodeEntitiesReentrant (NULL, str);
+		xmlNodeSetContent (cur, str);
+		xmlFree (str);
+	}
+
+	return value;
+}
+
+static gint
+filter_input_xml_decode (EFilterElement *element,
+                         xmlNodePtr node)
+{
+	EFilterInput *input = (EFilterInput *) element;
+	gchar *name, *str, *type;
+	xmlNodePtr child;
+
+	g_list_foreach (input->values, (GFunc) g_free, NULL);
+	g_list_free (input->values);
+	input->values = NULL;
+
+	name = (gchar *) xmlGetProp (node, (xmlChar *) "name");
+	type = (gchar *) xmlGetProp (node, (xmlChar *) "type");
+
+	xmlFree (element->name);
+	element->name = name;
+
+	xmlFree (input->type);
+	input->type = type;
+
+	child = node->children;
+	while (child != NULL) {
+		if (!strcmp ((gchar *) child->name, type)) {
+			if (!(str = (gchar *) xmlNodeGetContent (child)))
+				str = (gchar *) xmlStrdup ((xmlChar *)"");
+
+			input->values = g_list_append (input->values, g_strdup (str));
+			xmlFree (str);
+		} else if (child->type == XML_ELEMENT_NODE) {
+			g_warning (
+				"Unknown node type '%s' encountered "
+				"decoding a %s\n", child->name, type);
+		}
+		child = child->next;
+	}
+
+	return 0;
+}
+
+static GtkWidget *
+filter_input_get_widget (EFilterElement *element)
+{
+	EFilterInput *input = E_FILTER_INPUT (element);
+	GtkWidget *entry;
+
+	entry = gtk_entry_new ();
+	if (input->values && input->values->data)
+		gtk_entry_set_text (
+			GTK_ENTRY (entry), input->values->data);
+
+	g_signal_connect (
+		entry, "changed",
+		G_CALLBACK (filter_input_entry_changed), element);
+
+	return entry;
+}
+
+static void
+filter_input_format_sexp (EFilterElement *element,
+                          GString *out)
+{
+	EFilterInput *input = E_FILTER_INPUT (element);
+	GList *link;
+
+	for (link = input->values; link != NULL; link = g_list_next (link))
+		camel_sexp_encode_string (out, link->data);
+}
+
+static void
+e_filter_input_class_init (EFilterInputClass *class)
+{
+	GObjectClass *object_class;
+	EFilterElementClass *filter_element_class;
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->finalize = filter_input_finalize;
+
+	filter_element_class = E_FILTER_ELEMENT_CLASS (class);
+	filter_element_class->validate = filter_input_validate;
+	filter_element_class->eq = filter_input_eq;
+	filter_element_class->xml_encode = filter_input_xml_encode;
+	filter_element_class->xml_decode = filter_input_xml_decode;
+	filter_element_class->get_widget = filter_input_get_widget;
+	filter_element_class->format_sexp = filter_input_format_sexp;
+}
+
+static void
+e_filter_input_init (EFilterInput *input)
+{
+	input->values = g_list_prepend (NULL, g_strdup (""));
+}
+
+/**
+ * filter_input_new:
+ *
+ * Create a new EFilterInput object.
+ *
+ * Return value: A new #EFilterInput object.
+ **/
+EFilterInput *
+e_filter_input_new (void)
+{
+	return g_object_new (E_TYPE_FILTER_INPUT, NULL);
+}
+
+EFilterInput *
+e_filter_input_new_type_name (const gchar *type)
+{
+	EFilterInput *input;
+
+	input = e_filter_input_new ();
+	input->type = (gchar *) xmlStrdup ((xmlChar *) type);
+
+	return input;
+}
+
+void
+e_filter_input_set_value (EFilterInput *input,
+                          const gchar *value)
+{
+	g_return_if_fail (E_IS_FILTER_INPUT (input));
+
+	g_list_foreach (input->values, (GFunc) g_free, NULL);
+	g_list_free (input->values);
+
+	input->values = g_list_append (NULL, g_strdup (value));
+}
diff --git a/e-util/e-filter-input.h b/e-util/e-filter-input.h
new file mode 100644
index 0000000..782be40
--- /dev/null
+++ b/e-util/e-filter-input.h
@@ -0,0 +1,78 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Not Zed <notzed lostzed mmc com au>
+ *      Jeffrey Stedfast <fejj ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_FILTER_INPUT_H
+#define E_FILTER_INPUT_H
+
+#include <e-util/e-filter-element.h>
+
+/* Standard GObject macros */
+#define E_TYPE_FILTER_INPUT \
+	(e_filter_input_get_type ())
+#define E_FILTER_INPUT(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_FILTER_INPUT, EFilterInput))
+#define E_FILTER_INPUT_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_FILTER_INPUT, EFilterInputClass))
+#define E_IS_FILTER_INPUT(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_FILTER_INPUT))
+#define E_IS_FILTER_INPUT_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_FILTER_INPUT))
+#define E_FILTER_INPUT_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_FILTER_INPUT, EFilterInputClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EFilterInput EFilterInput;
+typedef struct _EFilterInputClass EFilterInputClass;
+typedef struct _EFilterInputPrivate EFilterInputPrivate;
+
+struct _EFilterInput {
+	EFilterElement parent;
+	EFilterInputPrivate *priv;
+
+	gchar *type;		/* name of type */
+	GList *values;		/* strings */
+};
+
+struct _EFilterInputClass {
+	EFilterElementClass parent_class;
+};
+
+GType		e_filter_input_get_type		(void);
+EFilterInput *	e_filter_input_new		(void);
+EFilterInput *	e_filter_input_new_type_name	(const gchar *type);
+void		e_filter_input_set_value	(EFilterInput *input,
+						 const gchar *value);
+
+G_END_DECLS
+
+#endif /* E_FILTER_INPUT_H */
diff --git a/filter/e-filter-int.c b/e-util/e-filter-int.c
similarity index 100%
rename from filter/e-filter-int.c
rename to e-util/e-filter-int.c
diff --git a/e-util/e-filter-int.h b/e-util/e-filter-int.h
new file mode 100644
index 0000000..40b0c9e
--- /dev/null
+++ b/e-util/e-filter-int.h
@@ -0,0 +1,81 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Jeffrey Stedfast <fejj ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_FILTER_INT_H
+#define E_FILTER_INT_H
+
+#include <e-util/e-filter-element.h>
+
+/* Standard GObject macros */
+#define E_TYPE_FILTER_INT \
+	(e_filter_int_get_type ())
+#define E_FILTER_INT(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_FILTER_INT, EFilterInt))
+#define E_FILTER_INT_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_FILTER_INT, EFilterIntClass))
+#define E_IS_FILTER_INT(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_FILTER_INT))
+#define E_IS_FILTER_INT_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_FILTER_INT))
+#define E_FILTER_INT_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_FILTER_INT, EFilterIntClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EFilterInt EFilterInt;
+typedef struct _EFilterIntClass EFilterIntClass;
+typedef struct _EFilterIntPrivate EFilterIntPrivate;
+
+struct _EFilterInt {
+	EFilterElement parent;
+	EFilterIntPrivate *priv;
+
+	gchar *type;
+	gint val;
+	gint min;
+	gint max;
+};
+
+struct _EFilterIntClass {
+	EFilterElementClass parent_class;
+};
+
+GType		e_filter_int_get_type		(void);
+EFilterElement *e_filter_int_new		(void);
+EFilterElement *e_filter_int_new_type		(const gchar *type,
+						 gint min,
+						 gint max);
+void		e_filter_int_set_value		(EFilterInt *f_int,
+						 gint value);
+
+G_END_DECLS
+
+#endif /* E_FILTER_INT_H */
diff --git a/filter/e-filter-option.c b/e-util/e-filter-option.c
similarity index 100%
rename from filter/e-filter-option.c
rename to e-util/e-filter-option.c
diff --git a/e-util/e-filter-option.h b/e-util/e-filter-option.h
new file mode 100644
index 0000000..9bd7543
--- /dev/null
+++ b/e-util/e-filter-option.h
@@ -0,0 +1,101 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Not Zed <notzed lostzed mmc com au>
+ *      Jeffrey Stedfast <fejj ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_FILTER_OPTION_H
+#define E_FILTER_OPTION_H
+
+#include <e-util/e-filter-element.h>
+
+/* Standard GObject macros */
+#define E_TYPE_FILTER_OPTION \
+	(e_filter_option_get_type ())
+#define E_FILTER_OPTION(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_FILTER_OPTION, EFilterOption))
+#define E_FILTER_OPTION_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_FILTER_OPTION, EFilterOptionClass))
+#define E_IS_FILTER_OPTION(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_FILTER_OPTION))
+#define E_IS_FILTER_OPTION_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_FILTER_OPTION))
+#define E_FILTER_OPTION_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_FILTER_OPTION, EFilterOptionClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EFilterOption EFilterOption;
+typedef struct _EFilterOptionClass EFilterOptionClass;
+typedef struct _EFilterOptionPrivate EFilterOptionPrivate;
+
+struct _filter_option {
+	gchar *title;		/* button title */
+	gchar *value;		/* value, if it has one */
+	gchar *code;		/* used to string code segments together */
+	gchar *code_gen_func;	/* function to generate the code;
+				 * either @code or @code_gen_func is non-NULL,
+				 * never both */
+
+	gboolean is_dynamic;	/* whether is the option dynamic, FALSE if static;
+				 * dynamic means "generated by EFilterOption::dynamic_func" */
+};
+
+struct _EFilterOption {
+	EFilterElement parent;
+	EFilterOptionPrivate *priv;
+
+	const gchar *type;	/* static memory, type name written to xml */
+
+	GList *options;
+	struct _filter_option *current;
+	gchar *dynamic_func;	/* name of the dynamic fill func, called in get_widget */
+};
+
+struct _EFilterOptionClass {
+	EFilterElementClass parent_class;
+};
+
+GType		e_filter_option_get_type	(void);
+EFilterElement *e_filter_option_new		(void);
+void		e_filter_option_set_current	(EFilterOption *option,
+						 const gchar *name);
+const gchar *	e_filter_option_get_current	(EFilterOption *option);
+struct _filter_option *
+		e_filter_option_add		(EFilterOption *option,
+						 const gchar *name,
+						 const gchar *title,
+						 const gchar *code,
+						 const gchar *code_gen_func,
+						 gboolean is_dynamic);
+void		e_filter_option_remove_all	(EFilterOption *option);
+
+G_END_DECLS
+
+#endif /* E_FILTER_OPTION_H */
diff --git a/filter/e-filter-part.c b/e-util/e-filter-part.c
similarity index 100%
rename from filter/e-filter-part.c
rename to e-util/e-filter-part.c
diff --git a/e-util/e-filter-part.h b/e-util/e-filter-part.h
new file mode 100644
index 0000000..b5ee2c4
--- /dev/null
+++ b/e-util/e-filter-part.h
@@ -0,0 +1,112 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Not Zed <notzed lostzed mmc com au>
+ *      Jeffrey Stedfast <fejj ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_FILTER_PART_H
+#define E_FILTER_PART_H
+
+#include <gtk/gtk.h>
+#include <libxml/parser.h>
+#include <libxml/xmlmemory.h>
+
+#include <e-util/e-alert.h>
+#include <e-util/e-filter-element.h>
+
+/* Standard GObject macros */
+#define E_TYPE_FILTER_PART \
+	(e_filter_part_get_type ())
+#define E_FILTER_PART(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_FILTER_PART, EFilterPart))
+#define E_FILTER_PART_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_FILTER_PART, EFilterPartClass))
+#define E_IS_FILTER_PART(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_FILTER_PART))
+#define E_IS_FILTER_PART_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_FILTER_PART))
+#define E_FILTER_PART_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_FILTER_PART, EFilterPartClass))
+
+G_BEGIN_DECLS
+
+struct _ERuleContext;
+
+typedef struct _EFilterPart EFilterPart;
+typedef struct _EFilterPartClass EFilterPartClass;
+typedef struct _EFilterPartPrivate EFilterPartPrivate;
+
+struct _EFilterPart {
+	GObject parent;
+	EFilterPartPrivate *priv;
+
+	gchar *name;
+	gchar *title;
+	gchar *code;
+	GList *elements;
+};
+
+struct _EFilterPartClass {
+	GObjectClass parent_class;
+};
+
+GType		e_filter_part_get_type		(void);
+EFilterPart *	e_filter_part_new		(void);
+gboolean	e_filter_part_validate		(EFilterPart *part,
+						 EAlert **alert);
+gint		e_filter_part_eq		(EFilterPart *part_a,
+						 EFilterPart *part_b);
+gint		e_filter_part_xml_create	(EFilterPart *part,
+						 xmlNodePtr node,
+						 struct _ERuleContext *rc);
+xmlNodePtr	e_filter_part_xml_encode	(EFilterPart *fe);
+gint		e_filter_part_xml_decode	(EFilterPart *fe,
+						 xmlNodePtr node);
+EFilterPart *	e_filter_part_clone		(EFilterPart *part);
+void		e_filter_part_copy_values	(EFilterPart *dst_part,
+						 EFilterPart *src_part);
+EFilterElement *e_filter_part_find_element	(EFilterPart *part,
+						 const gchar *name);
+GtkWidget *	e_filter_part_get_widget	(EFilterPart *part);
+void		e_filter_part_build_code	(EFilterPart *part,
+						 GString *out);
+void		e_filter_part_expand_code	(EFilterPart *part,
+						 const gchar *str,
+						 GString *out);
+
+void		e_filter_part_build_code_list	(GList *list,
+						 GString *out);
+EFilterPart *	e_filter_part_find_list		(GList *list,
+						 const gchar *name);
+EFilterPart *	e_filter_part_next_list		(GList *list,
+						 EFilterPart *last);
+
+G_END_DECLS
+
+#endif /* E_FILTER_PART_H */
diff --git a/e-util/e-filter-rule.c b/e-util/e-filter-rule.c
new file mode 100644
index 0000000..111073b
--- /dev/null
+++ b/e-util/e-filter-rule.c
@@ -0,0 +1,1241 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Not Zed <notzed lostzed mmc com au>
+ *		Jeffrey Stedfast <fejj ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+
+#include "e-alert-dialog.h"
+#include "e-filter-rule.h"
+#include "e-rule-context.h"
+
+#define E_FILTER_RULE_GET_PRIVATE(obj) \
+	(G_TYPE_INSTANCE_GET_PRIVATE \
+	((obj), E_TYPE_FILTER_RULE, EFilterRulePrivate))
+
+typedef struct _FilterPartData FilterPartData;
+typedef struct _FilterRuleData FilterRuleData;
+
+struct _EFilterRulePrivate {
+	gint frozen;
+};
+
+struct _FilterPartData {
+	EFilterRule *rule;
+	ERuleContext *context;
+	EFilterPart *part;
+	GtkWidget *partwidget;
+	GtkWidget *container;
+};
+
+struct _FilterRuleData {
+	EFilterRule *rule;
+	ERuleContext *context;
+	GtkWidget *parts;
+};
+
+enum {
+	CHANGED,
+	LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+G_DEFINE_TYPE (
+	EFilterRule,
+	e_filter_rule,
+	G_TYPE_OBJECT)
+
+static void
+filter_rule_grouping_changed_cb (GtkComboBox *combo_box,
+                                 EFilterRule *rule)
+{
+	rule->grouping = gtk_combo_box_get_active (combo_box);
+}
+
+static void
+filter_rule_threading_changed_cb (GtkComboBox *combo_box,
+                                  EFilterRule *rule)
+{
+	rule->threading = gtk_combo_box_get_active (combo_box);
+}
+
+static void
+part_combobox_changed (GtkComboBox *combobox,
+                       FilterPartData *data)
+{
+	EFilterPart *part = NULL;
+	EFilterPart *newpart;
+	gint index, i;
+
+	index = gtk_combo_box_get_active (combobox);
+	for (i = 0, part = e_rule_context_next_part (data->context, part);
+		part && i < index;
+		i++, part = e_rule_context_next_part (data->context, part)) {
+		/* traverse until reached index */
+	}
+
+	g_return_if_fail (part != NULL);
+	g_return_if_fail (i == index);
+
+	/* dont update if we haven't changed */
+	if (!strcmp (part->title, data->part->title))
+		return;
+
+	/* here we do a widget shuffle, throw away the old widget/rulepart,
+	 * and create another */
+	if (data->partwidget)
+		gtk_container_remove (GTK_CONTAINER (data->container), data->partwidget);
+
+	newpart = e_filter_part_clone (part);
+	e_filter_part_copy_values (newpart, data->part);
+	e_filter_rule_replace_part (data->rule, data->part, newpart);
+	g_object_unref (data->part);
+	data->part = newpart;
+	data->partwidget = e_filter_part_get_widget (newpart);
+	if (data->partwidget)
+		gtk_box_pack_start (
+			GTK_BOX (data->container),
+			data->partwidget, TRUE, TRUE, 0);
+}
+
+static GtkWidget *
+get_rule_part_widget (ERuleContext *context,
+                      EFilterPart *newpart,
+                      EFilterRule *rule)
+{
+	EFilterPart *part = NULL;
+	GtkWidget *combobox;
+	GtkWidget *hbox;
+	GtkWidget *p;
+	gint index = 0, current = 0;
+	FilterPartData *data;
+
+	data = g_malloc0 (sizeof (*data));
+	data->rule = rule;
+	data->context = context;
+	data->part = newpart;
+
+	hbox = gtk_hbox_new (FALSE, 0);
+	/* only set to automatically clean up the memory */
+	g_object_set_data_full ((GObject *) hbox, "data", data, g_free);
+
+	p = e_filter_part_get_widget (newpart);
+
+	data->partwidget = p;
+	data->container = hbox;
+
+	combobox = gtk_combo_box_text_new ();
+
+	/* sigh, this is a little ugly */
+	while ((part = e_rule_context_next_part (context, part))) {
+		gtk_combo_box_text_append_text (
+			GTK_COMBO_BOX_TEXT (combobox), _(part->title));
+
+		if (!strcmp (newpart->title, part->title))
+			current = index;
+
+		index++;
+	}
+
+	gtk_combo_box_set_active (GTK_COMBO_BOX (combobox), current);
+	g_signal_connect (
+		combobox, "changed",
+		G_CALLBACK (part_combobox_changed), data);
+	gtk_widget_show (combobox);
+
+	gtk_box_pack_start (GTK_BOX (hbox), combobox, FALSE, FALSE, 0);
+	if (p)
+		gtk_box_pack_start (GTK_BOX (hbox), p, TRUE, TRUE, 0);
+
+	gtk_widget_show_all (hbox);
+
+	return hbox;
+}
+
+static void
+less_parts (GtkWidget *button,
+            FilterRuleData *data)
+{
+	EFilterPart *part;
+	GtkWidget *rule;
+	FilterPartData *part_data;
+
+	if (g_list_length (data->rule->parts) < 1)
+		return;
+
+	rule = g_object_get_data ((GObject *) button, "rule");
+	part_data = g_object_get_data ((GObject *) rule, "data");
+
+	g_return_if_fail (part_data != NULL);
+
+	part = part_data->part;
+
+	/* remove the part from the list */
+	e_filter_rule_remove_part (data->rule, part);
+	g_object_unref (part);
+
+	/* and from the display */
+	gtk_container_remove (GTK_CONTAINER (data->parts), rule);
+	gtk_container_remove (GTK_CONTAINER (data->parts), button);
+}
+
+static void
+attach_rule (GtkWidget *rule,
+             FilterRuleData *data,
+             EFilterPart *part,
+             gint row)
+{
+	GtkWidget *remove;
+
+	gtk_table_attach (
+		GTK_TABLE (data->parts), rule, 0, 1, row, row + 1,
+		GTK_EXPAND | GTK_FILL, 0, 0, 0);
+
+	remove = gtk_button_new_from_stock (GTK_STOCK_REMOVE);
+	g_object_set_data ((GObject *) remove, "rule", rule);
+	g_signal_connect (
+		remove, "clicked",
+		G_CALLBACK (less_parts), data);
+	gtk_table_attach (
+		GTK_TABLE (data->parts), remove, 1, 2, row, row + 1,
+		0, 0, 0, 0);
+
+	gtk_widget_show (remove);
+}
+
+static void
+do_grab_focus_cb (GtkWidget *widget,
+                  gpointer data)
+{
+	gboolean *done = (gboolean *) data;
+
+	if (*done || !widget)
+		return;
+
+	if (gtk_widget_get_can_focus (widget) || GTK_IS_COMBO_BOX (widget)) {
+		*done = TRUE;
+		gtk_widget_grab_focus (widget);
+	} else if (GTK_IS_CONTAINER (widget)) {
+		gtk_container_foreach (GTK_CONTAINER (widget), do_grab_focus_cb, done);
+	}
+}
+
+static void
+more_parts (GtkWidget *button,
+            FilterRuleData *data)
+{
+	EFilterPart *new;
+
+	/* first make sure that the last part is ok */
+	if (data->rule->parts) {
+		EFilterPart *part;
+		GList *l;
+		EAlert *alert = NULL;
+
+		l = g_list_last (data->rule->parts);
+		part = l->data;
+		if (!e_filter_part_validate (part, &alert)) {
+			GtkWidget *toplevel;
+			toplevel = gtk_widget_get_toplevel (button);
+			e_alert_run_dialog (GTK_WINDOW (toplevel), alert);
+			return;
+		}
+	}
+
+	/* create a new rule entry, use the first type of rule */
+	new = e_rule_context_next_part (data->context, NULL);
+	if (new) {
+		GtkWidget *w;
+		guint rows;
+
+		new = e_filter_part_clone (new);
+		e_filter_rule_add_part (data->rule, new);
+		w = get_rule_part_widget (data->context, new, data->rule);
+
+		g_object_get (data->parts, "n-rows", &rows, NULL);
+		gtk_table_resize (GTK_TABLE (data->parts), rows + 1, 2);
+		attach_rule (w, data, new, rows);
+
+		if (GTK_IS_CONTAINER (w)) {
+			gboolean done = FALSE;
+
+			gtk_container_foreach (GTK_CONTAINER (w), do_grab_focus_cb, &done);
+		} else
+			gtk_widget_grab_focus (w);
+
+		/* also scroll down to see new part */
+		w = (GtkWidget *) g_object_get_data (G_OBJECT (button), "scrolled-window");
+		if (w) {
+			GtkAdjustment *adjustment;
+
+			adjustment = gtk_scrolled_window_get_vadjustment (
+				GTK_SCROLLED_WINDOW (w));
+			if (adjustment) {
+				gdouble upper;
+
+				upper = gtk_adjustment_get_upper (adjustment);
+				gtk_adjustment_set_value (adjustment, upper);
+			}
+
+		}
+	}
+}
+
+static void
+name_changed (GtkEntry *entry,
+              EFilterRule *rule)
+{
+	g_free (rule->name);
+	rule->name = g_strdup (gtk_entry_get_text (entry));
+}
+
+GtkWidget *
+e_filter_rule_get_widget (EFilterRule *rule,
+                          ERuleContext *context)
+{
+	EFilterRuleClass *class;
+
+	g_return_val_if_fail (E_IS_FILTER_RULE (rule), NULL);
+	g_return_val_if_fail (E_IS_RULE_CONTEXT (context), NULL);
+
+	class = E_FILTER_RULE_GET_CLASS (rule);
+	g_return_val_if_fail (class->get_widget != NULL, NULL);
+
+	return class->get_widget (rule, context);
+}
+
+static void
+filter_rule_load_set (xmlNodePtr node,
+                      EFilterRule *rule,
+                      ERuleContext *context)
+{
+	xmlNodePtr work;
+	gchar *rulename;
+	EFilterPart *part;
+
+	work = node->children;
+	while (work) {
+		if (!strcmp ((gchar *) work->name, "part")) {
+			rulename = (gchar *) xmlGetProp (work, (xmlChar *)"name");
+			part = e_rule_context_find_part (context, rulename);
+			if (part) {
+				part = e_filter_part_clone (part);
+				e_filter_part_xml_decode (part, work);
+				e_filter_rule_add_part (rule, part);
+			} else {
+				g_warning ("cannot find rule part '%s'\n", rulename);
+			}
+			xmlFree (rulename);
+		} else if (work->type == XML_ELEMENT_NODE) {
+			g_warning ("Unknown xml node in part: %s", work->name);
+		}
+		work = work->next;
+	}
+}
+
+static void
+filter_rule_finalize (GObject *object)
+{
+	EFilterRule *rule = E_FILTER_RULE (object);
+
+	g_free (rule->name);
+	g_free (rule->source);
+
+	g_list_foreach (rule->parts, (GFunc) g_object_unref, NULL);
+	g_list_free (rule->parts);
+
+	/* Chain up to parent's finalize() method. */
+	G_OBJECT_CLASS (e_filter_rule_parent_class)->finalize (object);
+}
+
+static gint
+filter_rule_validate (EFilterRule *rule,
+                      EAlert **alert)
+{
+	gint valid = TRUE;
+	GList *parts;
+
+	g_warn_if_fail (alert == NULL || *alert == NULL);
+	if (!rule->name || !*rule->name) {
+		if (alert)
+			*alert = e_alert_new ("filter:no-name", NULL);
+
+		return FALSE;
+	}
+
+	/* validate rule parts */
+	parts = rule->parts;
+	valid = parts != NULL;
+	while (parts && valid) {
+		valid = e_filter_part_validate ((EFilterPart *) parts->data, alert);
+		parts = parts->next;
+	}
+
+	return valid;
+}
+
+static gint
+filter_rule_eq (EFilterRule *rule_a,
+                EFilterRule *rule_b)
+{
+	GList *link_a;
+	GList *link_b;
+
+	if (rule_a->enabled != rule_b->enabled)
+		return FALSE;
+
+	if (rule_a->grouping != rule_b->grouping)
+		return FALSE;
+
+	if (rule_a->threading != rule_b->threading)
+		return FALSE;
+
+	if (g_strcmp0 (rule_a->name, rule_b->name) != 0)
+		return FALSE;
+
+	if (g_strcmp0 (rule_a->source, rule_b->source) != 0)
+		return FALSE;
+
+	link_a = rule_a->parts;
+	link_b = rule_b->parts;
+
+	while (link_a != NULL && link_b != NULL) {
+		EFilterPart *part_a = link_a->data;
+		EFilterPart *part_b = link_b->data;
+
+		if (!e_filter_part_eq (part_a, part_b))
+			return FALSE;
+
+		link_a = g_list_next (link_a);
+		link_b = g_list_next (link_b);
+	}
+
+	if (link_a != NULL || link_b != NULL)
+		return FALSE;
+
+	return TRUE;
+}
+
+static xmlNodePtr
+filter_rule_xml_encode (EFilterRule *rule)
+{
+	xmlNodePtr node, set, work;
+	GList *l;
+
+	node = xmlNewNode (NULL, (xmlChar *)"rule");
+
+	xmlSetProp (
+		node, (xmlChar *)"enabled",
+		(xmlChar *)(rule->enabled ? "true" : "false"));
+
+	switch (rule->grouping) {
+	case E_FILTER_GROUP_ALL:
+		xmlSetProp (node, (xmlChar *)"grouping", (xmlChar *)"all");
+		break;
+	case E_FILTER_GROUP_ANY:
+		xmlSetProp (node, (xmlChar *)"grouping", (xmlChar *)"any");
+		break;
+	}
+
+	switch (rule->threading) {
+	case E_FILTER_THREAD_NONE:
+		break;
+	case E_FILTER_THREAD_ALL:
+		xmlSetProp (node, (xmlChar *)"threading", (xmlChar *)"all");
+		break;
+	case E_FILTER_THREAD_REPLIES:
+		xmlSetProp (node, (xmlChar *)"threading", (xmlChar *)"replies");
+		break;
+	case E_FILTER_THREAD_REPLIES_PARENTS:
+		xmlSetProp (node, (xmlChar *)"threading", (xmlChar *)"replies_parents");
+		break;
+	case E_FILTER_THREAD_SINGLE:
+		xmlSetProp (node, (xmlChar *)"threading", (xmlChar *)"single");
+		break;
+	}
+
+	if (rule->source) {
+		xmlSetProp (node, (xmlChar *)"source", (xmlChar *) rule->source);
+	} else {
+		/* set to the default filter type */
+		xmlSetProp (node, (xmlChar *)"source", (xmlChar *)"incoming");
+	}
+
+	if (rule->name) {
+		gchar *escaped = g_markup_escape_text (rule->name, -1);
+
+		work = xmlNewNode (NULL, (xmlChar *)"title");
+		xmlNodeSetContent (work, (xmlChar *) escaped);
+		xmlAddChild (node, work);
+
+		g_free (escaped);
+	}
+
+	set = xmlNewNode (NULL, (xmlChar *)"partset");
+	xmlAddChild (node, set);
+	l = rule->parts;
+	while (l) {
+		work = e_filter_part_xml_encode ((EFilterPart *) l->data);
+		xmlAddChild (set, work);
+		l = l->next;
+	}
+
+	return node;
+}
+
+static gint
+filter_rule_xml_decode (EFilterRule *rule,
+                        xmlNodePtr node,
+                        ERuleContext *context)
+{
+	xmlNodePtr work;
+	gchar *grouping;
+	gchar *source;
+
+	g_free (rule->name);
+	rule->name = NULL;
+
+	grouping = (gchar *) xmlGetProp (node, (xmlChar *)"enabled");
+	if (!grouping)
+		rule->enabled = TRUE;
+	else {
+		rule->enabled = strcmp (grouping, "false") != 0;
+		xmlFree (grouping);
+	}
+
+	grouping = (gchar *) xmlGetProp (node, (xmlChar *)"grouping");
+	if (!strcmp (grouping, "any"))
+		rule->grouping = E_FILTER_GROUP_ANY;
+	else
+		rule->grouping = E_FILTER_GROUP_ALL;
+	xmlFree (grouping);
+
+	rule->threading = E_FILTER_THREAD_NONE;
+	if (context->flags & E_RULE_CONTEXT_THREADING
+	    && (grouping = (gchar *) xmlGetProp (node, (xmlChar *)"threading"))) {
+		if (!strcmp (grouping, "all"))
+			rule->threading = E_FILTER_THREAD_ALL;
+		else if (!strcmp (grouping, "replies"))
+			rule->threading = E_FILTER_THREAD_REPLIES;
+		else if (!strcmp (grouping, "replies_parents"))
+			rule->threading = E_FILTER_THREAD_REPLIES_PARENTS;
+		else if (!strcmp (grouping, "single"))
+			rule->threading = E_FILTER_THREAD_SINGLE;
+		xmlFree (grouping);
+	}
+
+	g_free (rule->source);
+	source = (gchar *) xmlGetProp (node, (xmlChar *)"source");
+	if (source) {
+		rule->source = g_strdup (source);
+		xmlFree (source);
+	} else {
+		/* default filter type */
+		rule->source = g_strdup ("incoming");
+	}
+
+	work = node->children;
+	while (work) {
+		if (!strcmp ((gchar *) work->name, "partset")) {
+			filter_rule_load_set (work, rule, context);
+		} else if (!strcmp ((gchar *) work->name, "title") ||
+			!strcmp ((gchar *) work->name, "_title")) {
+
+			if (!rule->name) {
+				gchar *str, *decstr = NULL;
+
+				str = (gchar *) xmlNodeGetContent (work);
+				if (str) {
+					decstr = g_strdup (_(str));
+					xmlFree (str);
+				}
+				rule->name = decstr;
+			}
+		}
+		work = work->next;
+	}
+
+	return 0;
+}
+
+static void
+filter_rule_build_code (EFilterRule *rule,
+                        GString *out)
+{
+	switch (rule->threading) {
+	case E_FILTER_THREAD_NONE:
+		break;
+	case E_FILTER_THREAD_ALL:
+		g_string_append (out, " (match-threads \"all\" ");
+		break;
+	case E_FILTER_THREAD_REPLIES:
+		g_string_append (out, " (match-threads \"replies\" ");
+		break;
+	case E_FILTER_THREAD_REPLIES_PARENTS:
+		g_string_append (out, " (match-threads \"replies_parents\" ");
+		break;
+	case E_FILTER_THREAD_SINGLE:
+		g_string_append (out, " (match-threads \"single\" ");
+		break;
+	}
+
+	switch (rule->grouping) {
+	case E_FILTER_GROUP_ALL:
+		g_string_append (out, " (and\n  ");
+		break;
+	case E_FILTER_GROUP_ANY:
+		g_string_append (out, " (or\n  ");
+		break;
+	default:
+		g_warning ("Invalid grouping");
+	}
+
+	e_filter_part_build_code_list (rule->parts, out);
+	g_string_append (out, ")\n");
+
+	if (rule->threading != E_FILTER_THREAD_NONE)
+		g_string_append (out, ")\n");
+}
+
+static void
+filter_rule_copy (EFilterRule *dest,
+                  EFilterRule *src)
+{
+	GList *node;
+
+	dest->enabled = src->enabled;
+
+	g_free (dest->name);
+	dest->name = g_strdup (src->name);
+
+	g_free (dest->source);
+	dest->source = g_strdup (src->source);
+
+	dest->grouping = src->grouping;
+	dest->threading = src->threading;
+
+	if (dest->parts) {
+		g_list_foreach (dest->parts, (GFunc) g_object_unref, NULL);
+		g_list_free (dest->parts);
+		dest->parts = NULL;
+	}
+
+	node = src->parts;
+	while (node) {
+		EFilterPart *part;
+
+		part = e_filter_part_clone (node->data);
+		dest->parts = g_list_append (dest->parts, part);
+		node = node->next;
+	}
+}
+
+static void
+ensure_scrolled_width_cb (GtkAdjustment *adj,
+                          GParamSpec *param_spec,
+                          GtkScrolledWindow *scrolled_window)
+{
+	gtk_scrolled_window_set_min_content_width (
+		scrolled_window,
+		gtk_adjustment_get_upper (adj));
+}
+
+static void
+ensure_scrolled_height_cb (GtkAdjustment *adj,
+                           GParamSpec *param_spec,
+                           GtkScrolledWindow *scrolled_window)
+{
+	GtkWidget *toplevel;
+	GdkScreen *screen;
+	gint toplevel_height, scw_height, require_scw_height = 0, max_height;
+
+	toplevel = gtk_widget_get_toplevel (GTK_WIDGET (scrolled_window));
+	if (!toplevel || !gtk_widget_is_toplevel (toplevel))
+		return;
+
+	scw_height = gtk_widget_get_allocated_height (GTK_WIDGET (scrolled_window));
+
+	gtk_widget_get_preferred_height_for_width (gtk_bin_get_child (GTK_BIN (scrolled_window)),
+		gtk_widget_get_allocated_width (GTK_WIDGET (scrolled_window)),
+		&require_scw_height, NULL);
+
+	if (scw_height >= require_scw_height) {
+		if (require_scw_height > 0)
+			gtk_scrolled_window_set_min_content_height (scrolled_window, require_scw_height);
+		return;
+	}
+
+	if (!GTK_IS_WINDOW (toplevel) ||
+	    !gtk_widget_get_window (toplevel))
+		return;
+
+	screen = gtk_window_get_screen (GTK_WINDOW (toplevel));
+	if (screen) {
+		gint monitor;
+		GdkRectangle workarea;
+
+		monitor = gdk_screen_get_monitor_at_window (screen, gtk_widget_get_window (toplevel));
+		if (monitor < 0)
+			monitor = 0;
+
+		gdk_screen_get_monitor_workarea (screen, monitor, &workarea);
+
+		/* can enlarge up to 4 / 5 monitor's work area height */
+		max_height = workarea.height * 4 / 5;
+	} else {
+		return;
+	}
+
+	toplevel_height = gtk_widget_get_allocated_height (toplevel);
+	if (toplevel_height + require_scw_height - scw_height > max_height)
+		return;
+
+	gtk_scrolled_window_set_min_content_height (scrolled_window, require_scw_height);
+}
+
+static GtkWidget *
+filter_rule_get_widget (EFilterRule *rule,
+                        ERuleContext *context)
+{
+	GtkGrid *hgrid, *vgrid, *inframe;
+	GtkWidget *parts, *add, *label, *name, *w;
+	GtkWidget *combobox;
+	GtkWidget *scrolledwindow;
+	GtkAdjustment *hadj, *vadj;
+	GList *l;
+	gchar *text;
+	EFilterPart *part;
+	FilterRuleData *data;
+	gint rows, i;
+
+	/* this stuff should probably be a table, but the
+	 * rule parts need to be a vbox */
+	vgrid = GTK_GRID (gtk_grid_new ());
+	gtk_grid_set_row_spacing (vgrid, 6);
+	gtk_orientable_set_orientation (GTK_ORIENTABLE (vgrid), GTK_ORIENTATION_VERTICAL);
+
+	label = gtk_label_new_with_mnemonic (_("R_ule name:"));
+	name = gtk_entry_new ();
+	gtk_widget_set_hexpand (name, TRUE);
+	gtk_widget_set_halign (name, GTK_ALIGN_FILL);
+	gtk_label_set_mnemonic_widget ((GtkLabel *) label, name);
+
+	if (!rule->name) {
+		rule->name = g_strdup (_("Untitled"));
+		gtk_entry_set_text (GTK_ENTRY (name), rule->name);
+		/* FIXME: do we want the following code in the future? */
+		/*gtk_editable_select_region (GTK_EDITABLE (name), 0, -1);*/
+	} else {
+		gtk_entry_set_text (GTK_ENTRY (name), rule->name);
+	}
+
+	g_signal_connect (
+		name, "realize",
+		G_CALLBACK (gtk_widget_grab_focus), name);
+
+	hgrid = GTK_GRID (gtk_grid_new ());
+	gtk_grid_set_column_spacing (hgrid, 12);
+
+	gtk_grid_attach (hgrid, label, 0, 0, 1, 1);
+	gtk_grid_attach_next_to (hgrid, name, label, GTK_POS_RIGHT, 1, 1);
+
+	gtk_container_add (GTK_CONTAINER (vgrid), GTK_WIDGET (hgrid));
+
+	g_signal_connect (
+		name, "changed",
+		G_CALLBACK (name_changed), rule);
+
+	hgrid = GTK_GRID (gtk_grid_new ());
+	gtk_grid_set_column_spacing (hgrid, 12);
+	gtk_container_add (GTK_CONTAINER (vgrid), GTK_WIDGET (hgrid));
+
+	/* this is the parts table, it should probably be inside a scrolling list */
+	rows = g_list_length (rule->parts);
+	parts = gtk_table_new (rows, 2, FALSE);
+
+	/* data for the parts part of the display */
+	data = g_malloc0 (sizeof (*data));
+	data->context = context;
+	data->rule = rule;
+	data->parts = parts;
+
+	/* only set to automatically clean up the memory */
+	g_object_set_data_full ((GObject *) vgrid, "data", data, g_free);
+
+	if (context->flags & E_RULE_CONTEXT_GROUPING) {
+		const gchar *thread_types[] = {
+			N_("all the following conditions"),
+			N_("any of the following conditions")
+		};
+
+		hgrid = GTK_GRID (gtk_grid_new ());
+		gtk_grid_set_column_spacing (hgrid, 12);
+
+		label = gtk_label_new_with_mnemonic (_("_Find items which match:"));
+		combobox = gtk_combo_box_text_new ();
+
+		for (i = 0; i < 2; i++) {
+			gtk_combo_box_text_append_text (
+				GTK_COMBO_BOX_TEXT (combobox),
+				_(thread_types[i]));
+		}
+
+		gtk_label_set_mnemonic_widget ((GtkLabel *) label, combobox);
+		gtk_combo_box_set_active (GTK_COMBO_BOX (combobox), rule->grouping);
+
+		gtk_grid_attach (hgrid, label, 0, 0, 1, 1);
+		gtk_grid_attach_next_to (hgrid, combobox, label, GTK_POS_RIGHT, 1, 1);
+
+		g_signal_connect (
+			combobox, "changed",
+			G_CALLBACK (filter_rule_grouping_changed_cb), rule);
+
+		gtk_container_add (GTK_CONTAINER (vgrid), GTK_WIDGET (hgrid));
+	} else {
+		text = g_strdup_printf (
+			"<b>%s</b>",
+			_("Find items that meet the following conditions"));
+		label = gtk_label_new (text);
+		gtk_label_set_use_markup (GTK_LABEL (label), TRUE);
+		gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5);
+		gtk_container_add (GTK_CONTAINER (vgrid), label);
+		g_free (text);
+	}
+
+	hgrid = GTK_GRID (gtk_grid_new ());
+	gtk_grid_set_column_spacing (hgrid, 12);
+
+	if (context->flags & E_RULE_CONTEXT_THREADING) {
+		const gchar *thread_types[] = {
+			/* Translators: "None" for not including threads;
+			 * part of "Include threads: None" */
+			N_("None"),
+			N_("All related"),
+			N_("Replies"),
+			N_("Replies and parents"),
+			N_("No reply or parent")
+		};
+
+		label = gtk_label_new_with_mnemonic (_("I_nclude threads:"));
+		combobox = gtk_combo_box_text_new ();
+
+		for (i = 0; i < 5; i++) {
+			gtk_combo_box_text_append_text (
+				GTK_COMBO_BOX_TEXT (combobox),
+				_(thread_types[i]));
+		}
+
+		gtk_label_set_mnemonic_widget ((GtkLabel *) label, combobox);
+		gtk_combo_box_set_active (GTK_COMBO_BOX (combobox), rule->threading);
+
+		gtk_grid_attach (hgrid, label, 0, 0, 1, 1);
+		gtk_grid_attach_next_to (hgrid, combobox, label, GTK_POS_RIGHT, 1, 1);
+
+		g_signal_connect (
+			combobox, "changed",
+			G_CALLBACK (filter_rule_threading_changed_cb), rule);
+	}
+
+	gtk_container_add (GTK_CONTAINER (vgrid), GTK_WIDGET (hgrid));
+
+	hgrid = GTK_GRID (gtk_grid_new ());
+	gtk_grid_set_column_spacing (hgrid, 3);
+	gtk_widget_set_vexpand (GTK_WIDGET (hgrid), TRUE);
+	gtk_widget_set_valign (GTK_WIDGET (hgrid), GTK_ALIGN_FILL);
+
+	gtk_container_add (GTK_CONTAINER (vgrid), GTK_WIDGET (hgrid));
+
+	label = gtk_label_new ("");
+	gtk_grid_attach (hgrid, label, 0, 0, 1, 1);
+
+	inframe = GTK_GRID (gtk_grid_new ());
+	gtk_grid_set_row_spacing (inframe, 6);
+	gtk_orientable_set_orientation (GTK_ORIENTABLE (inframe), GTK_ORIENTATION_VERTICAL);
+	gtk_widget_set_hexpand (GTK_WIDGET (inframe), TRUE);
+	gtk_widget_set_halign (GTK_WIDGET (inframe), GTK_ALIGN_FILL);
+	gtk_widget_set_vexpand (GTK_WIDGET (inframe), TRUE);
+	gtk_widget_set_valign (GTK_WIDGET (inframe), GTK_ALIGN_FILL);
+	gtk_grid_attach_next_to (hgrid, GTK_WIDGET (inframe), label, GTK_POS_RIGHT, 1, 1);
+
+	l = rule->parts;
+	i = 0;
+	while (l) {
+		part = l->data;
+		w = get_rule_part_widget (context, part, rule);
+		attach_rule (w, data, part, i++);
+		l = g_list_next (l);
+	}
+
+	hadj = GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 0.0, 1.0, 1.0, 1.0, 1.0));
+	vadj = GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 0.0, 1.0, 1.0, 1.0, 1.0));
+	scrolledwindow = gtk_scrolled_window_new (hadj, vadj);
+
+	g_signal_connect (
+		hadj, "notify::upper",
+		G_CALLBACK (ensure_scrolled_width_cb), scrolledwindow);
+	g_signal_connect (
+		vadj, "notify::upper",
+		G_CALLBACK (ensure_scrolled_height_cb), scrolledwindow);
+
+	gtk_scrolled_window_set_policy (
+		GTK_SCROLLED_WINDOW (scrolledwindow),
+		GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
+
+	gtk_scrolled_window_add_with_viewport (
+		GTK_SCROLLED_WINDOW (scrolledwindow), parts);
+
+	gtk_widget_set_vexpand (scrolledwindow, TRUE);
+	gtk_widget_set_valign (scrolledwindow, GTK_ALIGN_FILL);
+	gtk_widget_set_hexpand (scrolledwindow, TRUE);
+	gtk_widget_set_halign (scrolledwindow, GTK_ALIGN_FILL);
+	gtk_container_add (GTK_CONTAINER (inframe), scrolledwindow);
+
+	hgrid = GTK_GRID (gtk_grid_new ());
+	gtk_grid_set_column_spacing (hgrid, 3);
+
+	add = gtk_button_new_with_mnemonic (_("A_dd Condition"));
+	gtk_button_set_image (
+		GTK_BUTTON (add), gtk_image_new_from_stock (
+		GTK_STOCK_ADD, GTK_ICON_SIZE_BUTTON));
+	g_signal_connect (
+		add, "clicked",
+		G_CALLBACK (more_parts), data);
+	gtk_grid_attach (hgrid, add, 0, 0, 1, 1);
+
+	gtk_container_add (GTK_CONTAINER (inframe), GTK_WIDGET (hgrid));
+
+	gtk_widget_show_all (GTK_WIDGET (vgrid));
+
+	g_object_set_data (G_OBJECT (add), "scrolled-window", scrolledwindow);
+
+	return GTK_WIDGET (vgrid);
+}
+
+static void
+e_filter_rule_class_init (EFilterRuleClass *class)
+{
+	GObjectClass *object_class;
+
+	g_type_class_add_private (class, sizeof (EFilterRulePrivate));
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->finalize = filter_rule_finalize;
+
+	class->validate = filter_rule_validate;
+	class->eq = filter_rule_eq;
+	class->xml_encode = filter_rule_xml_encode;
+	class->xml_decode = filter_rule_xml_decode;
+	class->build_code = filter_rule_build_code;
+	class->copy = filter_rule_copy;
+	class->get_widget = filter_rule_get_widget;
+
+	signals[CHANGED] = g_signal_new (
+		"changed",
+		E_TYPE_FILTER_RULE,
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (EFilterRuleClass, changed),
+		NULL,
+		NULL,
+		g_cclosure_marshal_VOID__VOID,
+		G_TYPE_NONE, 0);
+}
+
+static void
+e_filter_rule_init (EFilterRule *rule)
+{
+	rule->priv = E_FILTER_RULE_GET_PRIVATE (rule);
+	rule->enabled = TRUE;
+}
+
+/**
+ * filter_rule_new:
+ *
+ * Create a new EFilterRule object.
+ *
+ * Return value: A new #EFilterRule object.
+ **/
+EFilterRule *
+e_filter_rule_new (void)
+{
+	return g_object_new (E_TYPE_FILTER_RULE, NULL);
+}
+
+EFilterRule *
+e_filter_rule_clone (EFilterRule *rule)
+{
+	EFilterRule *clone;
+
+	g_return_val_if_fail (E_IS_FILTER_RULE (rule), NULL);
+
+	clone = g_object_new (G_OBJECT_TYPE (rule), NULL);
+	e_filter_rule_copy (clone, rule);
+
+	return clone;
+}
+
+void
+e_filter_rule_set_name (EFilterRule *rule,
+                        const gchar *name)
+{
+	g_return_if_fail (E_IS_FILTER_RULE (rule));
+
+	if (g_strcmp0 (rule->name, name) == 0)
+		return;
+
+	g_free (rule->name);
+	rule->name = g_strdup (name);
+
+	e_filter_rule_emit_changed (rule);
+}
+
+void
+e_filter_rule_set_source (EFilterRule *rule,
+                          const gchar *source)
+{
+	g_return_if_fail (E_IS_FILTER_RULE (rule));
+
+	if (g_strcmp0 (rule->source, source) == 0)
+		return;
+
+	g_free (rule->source);
+	rule->source = g_strdup (source);
+
+	e_filter_rule_emit_changed (rule);
+}
+
+gint
+e_filter_rule_validate (EFilterRule *rule,
+                        EAlert **alert)
+{
+	EFilterRuleClass *class;
+
+	g_return_val_if_fail (E_IS_FILTER_RULE (rule), FALSE);
+
+	class = E_FILTER_RULE_GET_CLASS (rule);
+	g_return_val_if_fail (class->validate != NULL, FALSE);
+
+	return class->validate (rule, alert);
+}
+
+gint
+e_filter_rule_eq (EFilterRule *rule_a,
+                  EFilterRule *rule_b)
+{
+	EFilterRuleClass *class;
+
+	g_return_val_if_fail (E_IS_FILTER_RULE (rule_a), FALSE);
+	g_return_val_if_fail (E_IS_FILTER_RULE (rule_b), FALSE);
+
+	class = E_FILTER_RULE_GET_CLASS (rule_a);
+	g_return_val_if_fail (class->eq != NULL, FALSE);
+
+	if (G_OBJECT_TYPE (rule_a) != G_OBJECT_TYPE (rule_b))
+		return FALSE;
+
+	return class->eq (rule_a, rule_b);
+}
+
+xmlNodePtr
+e_filter_rule_xml_encode (EFilterRule *rule)
+{
+	EFilterRuleClass *class;
+
+	g_return_val_if_fail (E_IS_FILTER_RULE (rule), NULL);
+
+	class = E_FILTER_RULE_GET_CLASS (rule);
+	g_return_val_if_fail (class->xml_encode != NULL, NULL);
+
+	return class->xml_encode (rule);
+}
+
+gint
+e_filter_rule_xml_decode (EFilterRule *rule,
+                          xmlNodePtr node,
+                          ERuleContext *context)
+{
+	EFilterRuleClass *class;
+	gint result;
+
+	g_return_val_if_fail (E_IS_FILTER_RULE (rule), FALSE);
+	g_return_val_if_fail (node != NULL, FALSE);
+	g_return_val_if_fail (E_IS_RULE_CONTEXT (context), FALSE);
+
+	class = E_FILTER_RULE_GET_CLASS (rule);
+	g_return_val_if_fail (class->xml_decode != NULL, FALSE);
+
+	rule->priv->frozen++;
+	result = class->xml_decode (rule, node, context);
+	rule->priv->frozen--;
+
+	e_filter_rule_emit_changed (rule);
+
+	return result;
+}
+
+void
+e_filter_rule_copy (EFilterRule *dst_rule,
+                    EFilterRule *src_rule)
+{
+	EFilterRuleClass *class;
+
+	g_return_if_fail (E_IS_FILTER_RULE (dst_rule));
+	g_return_if_fail (E_IS_FILTER_RULE (src_rule));
+
+	class = E_FILTER_RULE_GET_CLASS (dst_rule);
+	g_return_if_fail (class->copy != NULL);
+
+	class->copy (dst_rule, src_rule);
+
+	e_filter_rule_emit_changed (dst_rule);
+}
+
+void
+e_filter_rule_add_part (EFilterRule *rule,
+                        EFilterPart *part)
+{
+	g_return_if_fail (E_IS_FILTER_RULE (rule));
+	g_return_if_fail (E_IS_FILTER_PART (part));
+
+	rule->parts = g_list_append (rule->parts, part);
+
+	e_filter_rule_emit_changed (rule);
+}
+
+void
+e_filter_rule_remove_part (EFilterRule *rule,
+                           EFilterPart *part)
+{
+	g_return_if_fail (E_IS_FILTER_RULE (rule));
+	g_return_if_fail (E_IS_FILTER_PART (part));
+
+	rule->parts = g_list_remove (rule->parts, part);
+
+	e_filter_rule_emit_changed (rule);
+}
+
+void
+e_filter_rule_replace_part (EFilterRule *rule,
+                            EFilterPart *old_part,
+                            EFilterPart *new_part)
+{
+	GList *link;
+
+	g_return_if_fail (E_IS_FILTER_RULE (rule));
+	g_return_if_fail (E_IS_FILTER_PART (old_part));
+	g_return_if_fail (E_IS_FILTER_PART (new_part));
+
+	link = g_list_find (rule->parts, old_part);
+	if (link != NULL)
+		link->data = new_part;
+	else
+		rule->parts = g_list_append (rule->parts, new_part);
+
+	e_filter_rule_emit_changed (rule);
+}
+
+void
+e_filter_rule_build_code (EFilterRule *rule,
+                          GString *out)
+{
+	EFilterRuleClass *class;
+
+	g_return_if_fail (E_IS_FILTER_RULE (rule));
+	g_return_if_fail (out != NULL);
+
+	class = E_FILTER_RULE_GET_CLASS (rule);
+	g_return_if_fail (class->build_code != NULL);
+
+	class->build_code (rule, out);
+}
+
+void
+e_filter_rule_emit_changed (EFilterRule *rule)
+{
+	g_return_if_fail (E_IS_FILTER_RULE (rule));
+
+	if (rule->priv->frozen == 0)
+		g_signal_emit (rule, signals[CHANGED], 0);
+}
+
+EFilterRule *
+e_filter_rule_next_list (GList *list,
+                         EFilterRule *last,
+                         const gchar *source)
+{
+	GList *link = list;
+
+	if (last != NULL) {
+		link = g_list_find (link, last);
+		if (link == NULL)
+			link = list;
+		else
+			link = g_list_next (link);
+	}
+
+	if (source != NULL) {
+		while (link != NULL) {
+			EFilterRule *rule = link->data;
+
+			if (g_strcmp0 (rule->source, source) == 0)
+				break;
+
+			link = g_list_next (link);
+		}
+	}
+
+	return (link != NULL) ? link->data : NULL;
+}
+
+EFilterRule *
+e_filter_rule_find_list (GList *list,
+                         const gchar *name,
+                         const gchar *source)
+{
+	GList *link;
+
+	g_return_val_if_fail (name != NULL, FALSE);
+
+	for (link = list; link != NULL; link = g_list_next (link)) {
+		EFilterRule *rule = link->data;
+
+		if (strcmp (rule->name, name) == 0)
+			if (source == NULL || (rule->source != NULL &&
+				strcmp (rule->source, source) == 0))
+				return rule;
+	}
+
+	return NULL;
+}
+
+#ifdef FOR_TRANSLATIONS_ONLY
+
+static gchar *list[] = {
+  N_("Incoming"), N_("Outgoing")
+};
+#endif
diff --git a/e-util/e-filter-rule.h b/e-util/e-filter-rule.h
new file mode 100644
index 0000000..8670265
--- /dev/null
+++ b/e-util/e-filter-rule.h
@@ -0,0 +1,163 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Not Zed <notzed lostzed mmc com au>
+ *      Jeffrey Stedfast <fejj ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_FILTER_RULE_H
+#define E_FILTER_RULE_H
+
+#include <e-util/e-filter-part.h>
+
+/* Standard GObject macros */
+#define E_TYPE_FILTER_RULE \
+	(e_filter_rule_get_type ())
+#define E_FILTER_RULE(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_FILTER_RULE, EFilterRule))
+#define E_FILTER_RULE_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_FILTER_RULE, EFilterRuleClass))
+#define E_IS_FILTER_RULE(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_FILTER_RULE))
+#define E_IS_FILTER_RULE_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_FILTER_RULE))
+#define E_FILTER_RULE_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_FILTER_RULE, EFilterRuleClass))
+
+G_BEGIN_DECLS
+
+struct _RuleContext;
+
+typedef struct _EFilterRule EFilterRule;
+typedef struct _EFilterRuleClass EFilterRuleClass;
+typedef struct _EFilterRulePrivate EFilterRulePrivate;
+
+enum _filter_grouping_t {
+	E_FILTER_GROUP_ALL,	/* all rules must match */
+	E_FILTER_GROUP_ANY	/* any rule must match */
+};
+
+/* threading, if the context supports it */
+enum _filter_threading_t {
+	E_FILTER_THREAD_NONE,	/* don't add any thread matching */
+	E_FILTER_THREAD_ALL,	/* add all possible threads */
+	E_FILTER_THREAD_REPLIES,	/* add only replies */
+	E_FILTER_THREAD_REPLIES_PARENTS,	/* replies plus parents */
+	E_FILTER_THREAD_SINGLE	/* messages with no replies or parents */
+};
+
+#define E_FILTER_SOURCE_INCOMING "incoming" /* performed on incoming email */
+#define E_FILTER_SOURCE_DEMAND   "demand"   /* performed on the selected folder
+					     * when the user asks for it */
+#define E_FILTER_SOURCE_OUTGOING  "outgoing"/* performed on outgoing mail */
+#define E_FILTER_SOURCE_JUNKTEST  "junktest"/* check incoming mail for junk */
+
+struct _EFilterRule {
+	GObject parent_object;
+	EFilterRulePrivate *priv;
+
+	gchar *name;
+	gchar *source;
+
+	enum _filter_grouping_t grouping;
+	enum _filter_threading_t threading;
+
+	guint system:1;	/* this is a system rule, cannot be edited/deleted */
+	GList *parts;
+
+	gboolean enabled;
+};
+
+struct _EFilterRuleClass {
+	GObjectClass parent_class;
+
+	/* virtual methods */
+	gint		(*validate)		(EFilterRule *rule,
+						 EAlert **alert);
+	gint		(*eq)			(EFilterRule *rule_a,
+						 EFilterRule *rule_b);
+
+	xmlNodePtr	(*xml_encode)		(EFilterRule *rule);
+	gint		(*xml_decode)		(EFilterRule *rule,
+						 xmlNodePtr node,
+						 struct _ERuleContext *context);
+
+	void		(*build_code)		(EFilterRule *rule,
+						 GString *out);
+
+	void		(*copy)			(EFilterRule *dst_rule,
+						 EFilterRule *src_rule);
+
+	GtkWidget *	(*get_widget)		(EFilterRule *rule,
+						 struct _ERuleContext *context);
+
+	/* signals */
+	void		(*changed)		(EFilterRule *rule);
+};
+
+GType		e_filter_rule_get_type		(void);
+EFilterRule *	e_filter_rule_new		(void);
+EFilterRule *	e_filter_rule_clone		(EFilterRule *rule);
+void		e_filter_rule_set_name		(EFilterRule *rule,
+						 const gchar *name);
+void		e_filter_rule_set_source	(EFilterRule *rule,
+						 const gchar *source);
+gint		e_filter_rule_validate		(EFilterRule *rule,
+						 EAlert **alert);
+gint		e_filter_rule_eq		(EFilterRule *rule_a,
+						 EFilterRule *rule_b);
+xmlNodePtr	e_filter_rule_xml_encode	(EFilterRule *rule);
+gint		e_filter_rule_xml_decode	(EFilterRule *rule,
+						 xmlNodePtr node,
+						 struct _ERuleContext *context);
+void		e_filter_rule_copy		(EFilterRule *dst_rule,
+						 EFilterRule *src_rule);
+void		e_filter_rule_add_part		(EFilterRule *rule,
+						 EFilterPart *part);
+void		e_filter_rule_remove_part	(EFilterRule *rule,
+						 EFilterPart *part);
+void		e_filter_rule_replace_part	(EFilterRule *rule,
+						 EFilterPart *old_part,
+						 EFilterPart *new_part);
+GtkWidget *	e_filter_rule_get_widget	(EFilterRule *rule,
+						 struct _ERuleContext *context);
+void		e_filter_rule_build_code	(EFilterRule *rule,
+						 GString *out);
+void		e_filter_rule_emit_changed	(EFilterRule *rule);
+
+/* static functions */
+EFilterRule *	e_filter_rule_next_list		(GList *list,
+						 EFilterRule *last,
+						 const gchar *source);
+EFilterRule *	e_filter_rule_find_list		(GList *list,
+						 const gchar *name,
+						 const gchar *source);
+
+G_END_DECLS
+
+#endif /* E_FILTER_RULE_H */
diff --git a/e-util/e-focus-tracker.c b/e-util/e-focus-tracker.c
new file mode 100644
index 0000000..a610605
--- /dev/null
+++ b/e-util/e-focus-tracker.c
@@ -0,0 +1,886 @@
+/*
+ * e-focus-tracker.c
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-focus-tracker.h"
+
+#include <glib/gi18n-lib.h>
+
+#include "e-selectable.h"
+
+#define E_FOCUS_TRACKER_GET_PRIVATE(obj) \
+	(G_TYPE_INSTANCE_GET_PRIVATE \
+	((obj), E_TYPE_FOCUS_TRACKER, EFocusTrackerPrivate))
+
+struct _EFocusTrackerPrivate {
+	GtkWidget *focus;  /* not referenced */
+	GtkWindow *window;
+
+	GtkAction *cut_clipboard;
+	GtkAction *copy_clipboard;
+	GtkAction *paste_clipboard;
+	GtkAction *delete_selection;
+	GtkAction *select_all;
+};
+
+enum {
+	PROP_0,
+	PROP_FOCUS,
+	PROP_WINDOW,
+	PROP_CUT_CLIPBOARD_ACTION,
+	PROP_COPY_CLIPBOARD_ACTION,
+	PROP_PASTE_CLIPBOARD_ACTION,
+	PROP_DELETE_SELECTION_ACTION,
+	PROP_SELECT_ALL_ACTION
+};
+
+G_DEFINE_TYPE (
+	EFocusTracker,
+	e_focus_tracker,
+	G_TYPE_OBJECT)
+
+static void
+focus_tracker_disable_actions (EFocusTracker *focus_tracker)
+{
+	GtkAction *action;
+
+	action = e_focus_tracker_get_cut_clipboard_action (focus_tracker);
+	if (action != NULL)
+		gtk_action_set_sensitive (action, FALSE);
+
+	action = e_focus_tracker_get_copy_clipboard_action (focus_tracker);
+	if (action != NULL)
+		gtk_action_set_sensitive (action, FALSE);
+
+	action = e_focus_tracker_get_paste_clipboard_action (focus_tracker);
+	if (action != NULL)
+		gtk_action_set_sensitive (action, FALSE);
+
+	action = e_focus_tracker_get_delete_selection_action (focus_tracker);
+	if (action != NULL)
+		gtk_action_set_sensitive (action, FALSE);
+
+	action = e_focus_tracker_get_select_all_action (focus_tracker);
+	if (action != NULL)
+		gtk_action_set_sensitive (action, FALSE);
+}
+
+static void
+focus_tracker_editable_update_actions (EFocusTracker *focus_tracker,
+                                       GtkEditable *editable,
+                                       GdkAtom *targets,
+                                       gint n_targets)
+{
+	GtkAction *action;
+	gboolean can_edit_text;
+	gboolean clipboard_has_text;
+	gboolean text_is_selected;
+	gboolean sensitive;
+
+	can_edit_text =
+		gtk_editable_get_editable (editable);
+
+	clipboard_has_text = (targets != NULL) &&
+		gtk_targets_include_text (targets, n_targets);
+
+	text_is_selected =
+		gtk_editable_get_selection_bounds (editable, NULL, NULL);
+
+	action = e_focus_tracker_get_cut_clipboard_action (focus_tracker);
+	if (action != NULL) {
+		sensitive = can_edit_text && text_is_selected;
+		gtk_action_set_sensitive (action, sensitive);
+		gtk_action_set_tooltip (action, _("Cut the selection"));
+	}
+
+	action = e_focus_tracker_get_copy_clipboard_action (focus_tracker);
+	if (action != NULL) {
+		sensitive = text_is_selected;
+		gtk_action_set_sensitive (action, sensitive);
+		gtk_action_set_tooltip (action, _("Copy the selection"));
+	}
+
+	action = e_focus_tracker_get_paste_clipboard_action (focus_tracker);
+	if (action != NULL) {
+		sensitive = can_edit_text && clipboard_has_text;
+		gtk_action_set_sensitive (action, sensitive);
+		gtk_action_set_tooltip (action, _("Paste the clipboard"));
+	}
+
+	action = e_focus_tracker_get_delete_selection_action (focus_tracker);
+	if (action != NULL) {
+		sensitive = can_edit_text && text_is_selected;
+		gtk_action_set_sensitive (action, sensitive);
+		gtk_action_set_tooltip (action, _("Delete the selection"));
+	}
+
+	action = e_focus_tracker_get_select_all_action (focus_tracker);
+	if (action != NULL) {
+		sensitive = TRUE;  /* always enabled */
+		gtk_action_set_sensitive (action, sensitive);
+		gtk_action_set_tooltip (action, _("Select all text"));
+	}
+}
+
+static void
+focus_tracker_selectable_update_actions (EFocusTracker *focus_tracker,
+                                         ESelectable *selectable,
+                                         GdkAtom *targets,
+                                         gint n_targets)
+{
+	ESelectableInterface *interface;
+	GtkAction *action;
+
+	interface = E_SELECTABLE_GET_INTERFACE (selectable);
+
+	e_selectable_update_actions (
+		selectable, focus_tracker, targets, n_targets);
+
+	/* Disable actions for which the corresponding method is not
+	 * implemented.  This allows update_actions() implementations
+	 * to simply skip the actions they don't support, which in turn
+	 * allows us to add new actions without disturbing the existing
+	 * ESelectable implementations. */
+
+	action = e_focus_tracker_get_cut_clipboard_action (focus_tracker);
+	if (action != NULL && interface->cut_clipboard == NULL)
+		gtk_action_set_sensitive (action, FALSE);
+
+	action = e_focus_tracker_get_copy_clipboard_action (focus_tracker);
+	if (action != NULL && interface->copy_clipboard == NULL)
+		gtk_action_set_sensitive (action, FALSE);
+
+	action = e_focus_tracker_get_paste_clipboard_action (focus_tracker);
+	if (action != NULL && interface->paste_clipboard == NULL)
+		gtk_action_set_sensitive (action, FALSE);
+
+	action = e_focus_tracker_get_delete_selection_action (focus_tracker);
+	if (action != NULL && interface->delete_selection == NULL)
+		gtk_action_set_sensitive (action, FALSE);
+
+	action = e_focus_tracker_get_select_all_action (focus_tracker);
+	if (action != NULL && interface->select_all == NULL)
+		gtk_action_set_sensitive (action, FALSE);
+}
+
+static void
+focus_tracker_targets_received_cb (GtkClipboard *clipboard,
+                                   GdkAtom *targets,
+                                   gint n_targets,
+                                   EFocusTracker *focus_tracker)
+{
+	GtkWidget *focus;
+
+	focus = e_focus_tracker_get_focus (focus_tracker);
+
+	if (focus == NULL)
+		focus_tracker_disable_actions (focus_tracker);
+
+	else if (GTK_IS_EDITABLE (focus))
+		focus_tracker_editable_update_actions (
+			focus_tracker, GTK_EDITABLE (focus),
+			targets, n_targets);
+
+	else if (E_IS_SELECTABLE (focus))
+		focus_tracker_selectable_update_actions (
+			focus_tracker, E_SELECTABLE (focus),
+			targets, n_targets);
+
+	g_object_unref (focus_tracker);
+}
+
+static void
+focus_tracker_set_focus_cb (GtkWindow *window,
+                            GtkWidget *focus,
+                            EFocusTracker *focus_tracker)
+{
+	while (focus != NULL) {
+		if (GTK_IS_EDITABLE (focus))
+			break;
+
+		if (E_IS_SELECTABLE (focus))
+			break;
+
+		focus = gtk_widget_get_parent (focus);
+	}
+
+	if (focus == focus_tracker->priv->focus)
+		return;
+
+	focus_tracker->priv->focus = focus;
+	g_object_notify (G_OBJECT (focus_tracker), "focus");
+
+	e_focus_tracker_update_actions (focus_tracker);
+}
+
+static void
+focus_tracker_set_window (EFocusTracker *focus_tracker,
+                          GtkWindow *window)
+{
+	g_return_if_fail (GTK_IS_WINDOW (window));
+	g_return_if_fail (focus_tracker->priv->window == NULL);
+
+	focus_tracker->priv->window = g_object_ref (window);
+
+	g_signal_connect (
+		window, "set-focus",
+		G_CALLBACK (focus_tracker_set_focus_cb), focus_tracker);
+}
+
+static void
+focus_tracker_set_property (GObject *object,
+                            guint property_id,
+                            const GValue *value,
+                            GParamSpec *pspec)
+{
+	switch (property_id) {
+		case PROP_WINDOW:
+			focus_tracker_set_window (
+				E_FOCUS_TRACKER (object),
+				g_value_get_object (value));
+			return;
+
+		case PROP_CUT_CLIPBOARD_ACTION:
+			e_focus_tracker_set_cut_clipboard_action (
+				E_FOCUS_TRACKER (object),
+				g_value_get_object (value));
+			return;
+
+		case PROP_COPY_CLIPBOARD_ACTION:
+			e_focus_tracker_set_copy_clipboard_action (
+				E_FOCUS_TRACKER (object),
+				g_value_get_object (value));
+			return;
+
+		case PROP_PASTE_CLIPBOARD_ACTION:
+			e_focus_tracker_set_paste_clipboard_action (
+				E_FOCUS_TRACKER (object),
+				g_value_get_object (value));
+			return;
+
+		case PROP_DELETE_SELECTION_ACTION:
+			e_focus_tracker_set_delete_selection_action (
+				E_FOCUS_TRACKER (object),
+				g_value_get_object (value));
+			return;
+
+		case PROP_SELECT_ALL_ACTION:
+			e_focus_tracker_set_select_all_action (
+				E_FOCUS_TRACKER (object),
+				g_value_get_object (value));
+			return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+focus_tracker_get_property (GObject *object,
+                            guint property_id,
+                            GValue *value,
+                            GParamSpec *pspec)
+{
+	switch (property_id) {
+		case PROP_FOCUS:
+			g_value_set_object (
+				value,
+				e_focus_tracker_get_focus (
+				E_FOCUS_TRACKER (object)));
+			return;
+
+		case PROP_WINDOW:
+			g_value_set_object (
+				value,
+				e_focus_tracker_get_window (
+				E_FOCUS_TRACKER (object)));
+			return;
+
+		case PROP_CUT_CLIPBOARD_ACTION:
+			g_value_set_object (
+				value,
+				e_focus_tracker_get_cut_clipboard_action (
+				E_FOCUS_TRACKER (object)));
+			return;
+
+		case PROP_COPY_CLIPBOARD_ACTION:
+			g_value_set_object (
+				value,
+				e_focus_tracker_get_copy_clipboard_action (
+				E_FOCUS_TRACKER (object)));
+			return;
+
+		case PROP_PASTE_CLIPBOARD_ACTION:
+			g_value_set_object (
+				value,
+				e_focus_tracker_get_paste_clipboard_action (
+				E_FOCUS_TRACKER (object)));
+			return;
+
+		case PROP_DELETE_SELECTION_ACTION:
+			g_value_set_object (
+				value,
+				e_focus_tracker_get_delete_selection_action (
+				E_FOCUS_TRACKER (object)));
+			return;
+
+		case PROP_SELECT_ALL_ACTION:
+			g_value_set_object (
+				value,
+				e_focus_tracker_get_select_all_action (
+				E_FOCUS_TRACKER (object)));
+			return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+focus_tracker_dispose (GObject *object)
+{
+	EFocusTrackerPrivate *priv;
+
+	priv = E_FOCUS_TRACKER_GET_PRIVATE (object);
+
+	g_signal_handlers_disconnect_matched (
+		gtk_clipboard_get (GDK_SELECTION_PRIMARY),
+		G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, object);
+
+	g_signal_handlers_disconnect_matched (
+		gtk_clipboard_get (GDK_SELECTION_CLIPBOARD),
+		G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, object);
+
+	if (priv->window != NULL) {
+		g_signal_handlers_disconnect_matched (
+			priv->window, G_SIGNAL_MATCH_DATA,
+			0, 0, NULL, NULL, object);
+		g_object_unref (priv->window);
+		priv->window = NULL;
+	}
+
+	if (priv->cut_clipboard != NULL) {
+		g_signal_handlers_disconnect_matched (
+			priv->cut_clipboard, G_SIGNAL_MATCH_DATA,
+			0, 0, NULL, NULL, object);
+		g_object_unref (priv->cut_clipboard);
+		priv->cut_clipboard = NULL;
+	}
+
+	if (priv->copy_clipboard != NULL) {
+		g_signal_handlers_disconnect_matched (
+			priv->copy_clipboard, G_SIGNAL_MATCH_DATA,
+			0, 0, NULL, NULL, object);
+		g_object_unref (priv->copy_clipboard);
+		priv->copy_clipboard = NULL;
+	}
+
+	if (priv->paste_clipboard != NULL) {
+		g_signal_handlers_disconnect_matched (
+			priv->paste_clipboard, G_SIGNAL_MATCH_DATA,
+			0, 0, NULL, NULL, object);
+		g_object_unref (priv->paste_clipboard);
+		priv->paste_clipboard = NULL;
+	}
+
+	if (priv->delete_selection != NULL) {
+		g_signal_handlers_disconnect_matched (
+			priv->delete_selection, G_SIGNAL_MATCH_DATA,
+			0, 0, NULL, NULL, object);
+		g_object_unref (priv->delete_selection);
+		priv->delete_selection = NULL;
+	}
+
+	if (priv->select_all != NULL) {
+		g_signal_handlers_disconnect_matched (
+			priv->select_all, G_SIGNAL_MATCH_DATA,
+			0, 0, NULL, NULL, object);
+		g_object_unref (priv->select_all);
+		priv->select_all = NULL;
+	}
+
+	/* Chain up to parent's dispose() method. */
+	G_OBJECT_CLASS (e_focus_tracker_parent_class)->dispose (object);
+}
+
+static void
+focus_tracker_constructed (GObject *object)
+{
+	GtkClipboard *clipboard;
+
+	/* Listen for "owner-change" signals from the primary selection
+	 * clipboard to learn when text selections change in GtkEditable
+	 * widgets.  It's a bit of an overkill, but I don't know of any
+	 * other notification mechanism. */
+
+	clipboard = gtk_clipboard_get (GDK_SELECTION_PRIMARY);
+
+	g_signal_connect_swapped (
+		clipboard, "owner-change",
+		G_CALLBACK (e_focus_tracker_update_actions), object);
+
+	/* Listen for "owner-change" signals from the default clipboard
+	 * so we can update the paste action when the user cuts or copies
+	 * something.  This is how GEdit does it. */
+
+	clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
+
+	g_signal_connect_swapped (
+		clipboard, "owner-change",
+		G_CALLBACK (e_focus_tracker_update_actions), object);
+
+	/* Chain up to parent's constructed() method. */
+	G_OBJECT_CLASS (e_focus_tracker_parent_class)->constructed (object);
+}
+
+static void
+e_focus_tracker_class_init (EFocusTrackerClass *class)
+{
+	GObjectClass *object_class;
+
+	g_type_class_add_private (class, sizeof (EFocusTrackerPrivate));
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->set_property = focus_tracker_set_property;
+	object_class->get_property = focus_tracker_get_property;
+	object_class->dispose = focus_tracker_dispose;
+	object_class->constructed = focus_tracker_constructed;
+
+	g_object_class_install_property (
+		object_class,
+		PROP_FOCUS,
+		g_param_spec_object (
+			"focus",
+			"Focus",
+			NULL,
+			GTK_TYPE_WIDGET,
+			G_PARAM_READABLE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_WINDOW,
+		g_param_spec_object (
+			"window",
+			"Window",
+			NULL,
+			GTK_TYPE_WINDOW,
+			G_PARAM_READWRITE |
+			G_PARAM_CONSTRUCT_ONLY));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_CUT_CLIPBOARD_ACTION,
+		g_param_spec_object (
+			"cut-clipboard-action",
+			"Cut Clipboard Action",
+			NULL,
+			GTK_TYPE_ACTION,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_COPY_CLIPBOARD_ACTION,
+		g_param_spec_object (
+			"copy-clipboard-action",
+			"Copy Clipboard Action",
+			NULL,
+			GTK_TYPE_ACTION,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_PASTE_CLIPBOARD_ACTION,
+		g_param_spec_object (
+			"paste-clipboard-action",
+			"Paste Clipboard Action",
+			NULL,
+			GTK_TYPE_ACTION,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_DELETE_SELECTION_ACTION,
+		g_param_spec_object (
+			"delete-selection-action",
+			"Delete Selection Action",
+			NULL,
+			GTK_TYPE_ACTION,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_SELECT_ALL_ACTION,
+		g_param_spec_object (
+			"select-all-action",
+			"Select All Action",
+			NULL,
+			GTK_TYPE_ACTION,
+			G_PARAM_READWRITE));
+}
+
+static void
+e_focus_tracker_init (EFocusTracker *focus_tracker)
+{
+	GtkAction *action;
+
+	focus_tracker->priv = E_FOCUS_TRACKER_GET_PRIVATE (focus_tracker);
+
+	/* Define dummy actions.  These will most likely be overridden,
+	 * but for cases where they're not it ensures ESelectable objects
+	 * will always get a valid GtkAction when they ask us for one. */
+
+	action = gtk_action_new (
+		"cut-clipboard", NULL,
+		_("Cut the selection"), GTK_STOCK_CUT);
+	focus_tracker->priv->cut_clipboard = action;
+
+	action = gtk_action_new (
+		"copy-clipboard", NULL,
+		_("Copy the selection"), GTK_STOCK_COPY);
+	focus_tracker->priv->copy_clipboard = action;
+
+	action = gtk_action_new (
+		"paste-clipboard", NULL,
+		_("Paste the clipboard"), GTK_STOCK_PASTE);
+	focus_tracker->priv->paste_clipboard = action;
+
+	action = gtk_action_new (
+		"delete-selection", NULL,
+		_("Delete the selection"), GTK_STOCK_DELETE);
+	focus_tracker->priv->delete_selection = action;
+
+	action = gtk_action_new (
+		"select-all", NULL,
+		_("Select all text"), GTK_STOCK_SELECT_ALL);
+	focus_tracker->priv->select_all = action;
+}
+
+EFocusTracker *
+e_focus_tracker_new (GtkWindow *window)
+{
+	g_return_val_if_fail (GTK_IS_WINDOW (window), NULL);
+
+	return g_object_new (E_TYPE_FOCUS_TRACKER, "window", window, NULL);
+}
+
+GtkWidget *
+e_focus_tracker_get_focus (EFocusTracker *focus_tracker)
+{
+	g_return_val_if_fail (E_IS_FOCUS_TRACKER (focus_tracker), NULL);
+
+	return focus_tracker->priv->focus;
+}
+
+GtkWindow *
+e_focus_tracker_get_window (EFocusTracker *focus_tracker)
+{
+	g_return_val_if_fail (E_IS_FOCUS_TRACKER (focus_tracker), NULL);
+
+	return focus_tracker->priv->window;
+}
+
+GtkAction *
+e_focus_tracker_get_cut_clipboard_action (EFocusTracker *focus_tracker)
+{
+	g_return_val_if_fail (E_IS_FOCUS_TRACKER (focus_tracker), NULL);
+
+	return focus_tracker->priv->cut_clipboard;
+}
+
+void
+e_focus_tracker_set_cut_clipboard_action (EFocusTracker *focus_tracker,
+                                          GtkAction *cut_clipboard)
+{
+	g_return_if_fail (E_IS_FOCUS_TRACKER (focus_tracker));
+
+	if (cut_clipboard != NULL) {
+		g_return_if_fail (GTK_IS_ACTION (cut_clipboard));
+		g_object_ref (cut_clipboard);
+	}
+
+	if (focus_tracker->priv->cut_clipboard != NULL) {
+		g_signal_handlers_disconnect_matched (
+			focus_tracker->priv->cut_clipboard,
+			G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL,
+			focus_tracker);
+		g_object_unref (focus_tracker->priv->cut_clipboard);
+	}
+
+	focus_tracker->priv->cut_clipboard = cut_clipboard;
+
+	if (cut_clipboard != NULL)
+		g_signal_connect_swapped (
+			cut_clipboard, "activate",
+			G_CALLBACK (e_focus_tracker_cut_clipboard),
+			focus_tracker);
+
+	g_object_notify (G_OBJECT (focus_tracker), "cut-clipboard-action");
+}
+
+GtkAction *
+e_focus_tracker_get_copy_clipboard_action (EFocusTracker *focus_tracker)
+{
+	g_return_val_if_fail (E_IS_FOCUS_TRACKER (focus_tracker), NULL);
+
+	return focus_tracker->priv->copy_clipboard;
+}
+
+void
+e_focus_tracker_set_copy_clipboard_action (EFocusTracker *focus_tracker,
+                                           GtkAction *copy_clipboard)
+{
+	g_return_if_fail (E_IS_FOCUS_TRACKER (focus_tracker));
+
+	if (copy_clipboard != NULL) {
+		g_return_if_fail (GTK_IS_ACTION (copy_clipboard));
+		g_object_ref (copy_clipboard);
+	}
+
+	if (focus_tracker->priv->copy_clipboard != NULL) {
+		g_signal_handlers_disconnect_matched (
+			focus_tracker->priv->copy_clipboard,
+			G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL,
+			focus_tracker);
+		g_object_unref (focus_tracker->priv->copy_clipboard);
+	}
+
+	focus_tracker->priv->copy_clipboard = copy_clipboard;
+
+	if (copy_clipboard != NULL)
+		g_signal_connect_swapped (
+			copy_clipboard, "activate",
+			G_CALLBACK (e_focus_tracker_copy_clipboard),
+			focus_tracker);
+
+	g_object_notify (G_OBJECT (focus_tracker), "copy-clipboard-action");
+}
+
+GtkAction *
+e_focus_tracker_get_paste_clipboard_action (EFocusTracker *focus_tracker)
+{
+	g_return_val_if_fail (E_IS_FOCUS_TRACKER (focus_tracker), NULL);
+
+	return focus_tracker->priv->paste_clipboard;
+}
+
+void
+e_focus_tracker_set_paste_clipboard_action (EFocusTracker *focus_tracker,
+                                            GtkAction *paste_clipboard)
+{
+	g_return_if_fail (E_IS_FOCUS_TRACKER (focus_tracker));
+
+	if (paste_clipboard != NULL) {
+		g_return_if_fail (GTK_IS_ACTION (paste_clipboard));
+		g_object_ref (paste_clipboard);
+	}
+
+	if (focus_tracker->priv->paste_clipboard != NULL) {
+		g_signal_handlers_disconnect_matched (
+			focus_tracker->priv->paste_clipboard,
+			G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL,
+			focus_tracker);
+		g_object_unref (focus_tracker->priv->paste_clipboard);
+	}
+
+	focus_tracker->priv->paste_clipboard = paste_clipboard;
+
+	if (paste_clipboard != NULL)
+		g_signal_connect_swapped (
+			paste_clipboard, "activate",
+			G_CALLBACK (e_focus_tracker_paste_clipboard),
+			focus_tracker);
+
+	g_object_notify (G_OBJECT (focus_tracker), "paste-clipboard-action");
+}
+
+GtkAction *
+e_focus_tracker_get_delete_selection_action (EFocusTracker *focus_tracker)
+{
+	g_return_val_if_fail (E_IS_FOCUS_TRACKER (focus_tracker), NULL);
+
+	return focus_tracker->priv->delete_selection;
+}
+
+void
+e_focus_tracker_set_delete_selection_action (EFocusTracker *focus_tracker,
+                                             GtkAction *delete_selection)
+{
+	g_return_if_fail (E_IS_FOCUS_TRACKER (focus_tracker));
+
+	if (delete_selection != NULL) {
+		g_return_if_fail (GTK_IS_ACTION (delete_selection));
+		g_object_ref (delete_selection);
+	}
+
+	if (focus_tracker->priv->delete_selection != NULL) {
+		g_signal_handlers_disconnect_matched (
+			focus_tracker->priv->delete_selection,
+			G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL,
+			focus_tracker);
+		g_object_unref (focus_tracker->priv->delete_selection);
+	}
+
+	focus_tracker->priv->delete_selection = delete_selection;
+
+	if (delete_selection != NULL)
+		g_signal_connect_swapped (
+			delete_selection, "activate",
+			G_CALLBACK (e_focus_tracker_delete_selection),
+			focus_tracker);
+
+	g_object_notify (G_OBJECT (focus_tracker), "delete-selection-action");
+}
+
+GtkAction *
+e_focus_tracker_get_select_all_action (EFocusTracker *focus_tracker)
+{
+	g_return_val_if_fail (E_IS_FOCUS_TRACKER (focus_tracker), NULL);
+
+	return focus_tracker->priv->select_all;
+}
+
+void
+e_focus_tracker_set_select_all_action (EFocusTracker *focus_tracker,
+                                       GtkAction *select_all)
+{
+	g_return_if_fail (E_IS_FOCUS_TRACKER (focus_tracker));
+
+	if (select_all != NULL) {
+		g_return_if_fail (GTK_IS_ACTION (select_all));
+		g_object_ref (select_all);
+	}
+
+	if (focus_tracker->priv->select_all != NULL) {
+		g_signal_handlers_disconnect_matched (
+			focus_tracker->priv->select_all,
+			G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL,
+			focus_tracker);
+		g_object_unref (focus_tracker->priv->select_all);
+	}
+
+	focus_tracker->priv->select_all = select_all;
+
+	if (select_all != NULL)
+		g_signal_connect_swapped (
+			select_all, "activate",
+			G_CALLBACK (e_focus_tracker_select_all),
+			focus_tracker);
+
+	g_object_notify (G_OBJECT (focus_tracker), "select-all-action");
+}
+
+void
+e_focus_tracker_update_actions (EFocusTracker *focus_tracker)
+{
+	GtkClipboard *clipboard;
+
+	g_return_if_fail (E_IS_FOCUS_TRACKER (focus_tracker));
+
+	/* Request clipboard targets asynchronously. */
+
+	clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
+
+	gtk_clipboard_request_targets (
+		clipboard, (GtkClipboardTargetsReceivedFunc)
+		focus_tracker_targets_received_cb,
+		g_object_ref (focus_tracker));
+}
+
+void
+e_focus_tracker_cut_clipboard (EFocusTracker *focus_tracker)
+{
+	GtkWidget *focus;
+
+	g_return_if_fail (E_IS_FOCUS_TRACKER (focus_tracker));
+
+	focus = e_focus_tracker_get_focus (focus_tracker);
+
+	if (GTK_IS_EDITABLE (focus))
+		gtk_editable_cut_clipboard (GTK_EDITABLE (focus));
+
+	else if (E_IS_SELECTABLE (focus))
+		e_selectable_cut_clipboard (E_SELECTABLE (focus));
+}
+
+void
+e_focus_tracker_copy_clipboard (EFocusTracker *focus_tracker)
+{
+	GtkWidget *focus;
+
+	g_return_if_fail (E_IS_FOCUS_TRACKER (focus_tracker));
+
+	focus = e_focus_tracker_get_focus (focus_tracker);
+
+	if (GTK_IS_EDITABLE (focus))
+		gtk_editable_copy_clipboard (GTK_EDITABLE (focus));
+
+	else if (E_IS_SELECTABLE (focus))
+		e_selectable_copy_clipboard (E_SELECTABLE (focus));
+}
+
+void
+e_focus_tracker_paste_clipboard (EFocusTracker *focus_tracker)
+{
+	GtkWidget *focus;
+
+	g_return_if_fail (E_IS_FOCUS_TRACKER (focus_tracker));
+
+	focus = e_focus_tracker_get_focus (focus_tracker);
+
+	if (GTK_IS_EDITABLE (focus))
+		gtk_editable_paste_clipboard (GTK_EDITABLE (focus));
+
+	else if (E_IS_SELECTABLE (focus))
+		e_selectable_paste_clipboard (E_SELECTABLE (focus));
+}
+
+void
+e_focus_tracker_delete_selection (EFocusTracker *focus_tracker)
+{
+	GtkWidget *focus;
+
+	g_return_if_fail (E_IS_FOCUS_TRACKER (focus_tracker));
+
+	focus = e_focus_tracker_get_focus (focus_tracker);
+
+	if (GTK_IS_EDITABLE (focus))
+		gtk_editable_delete_selection (GTK_EDITABLE (focus));
+
+	else if (E_IS_SELECTABLE (focus))
+		e_selectable_delete_selection (E_SELECTABLE (focus));
+}
+
+void
+e_focus_tracker_select_all (EFocusTracker *focus_tracker)
+{
+	GtkWidget *focus;
+
+	g_return_if_fail (E_IS_FOCUS_TRACKER (focus_tracker));
+
+	focus = e_focus_tracker_get_focus (focus_tracker);
+
+	if (GTK_IS_EDITABLE (focus))
+		gtk_editable_select_region (GTK_EDITABLE (focus), 0, -1);
+
+	else if (E_IS_SELECTABLE (focus))
+		e_selectable_select_all (E_SELECTABLE (focus));
+}
diff --git a/e-util/e-focus-tracker.h b/e-util/e-focus-tracker.h
new file mode 100644
index 0000000..e633d0f
--- /dev/null
+++ b/e-util/e-focus-tracker.h
@@ -0,0 +1,104 @@
+/*
+ * e-focus-tracker.h
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_FOCUS_TRACKER_H
+#define E_FOCUS_TRACKER_H
+
+#include <gtk/gtk.h>
+
+/* Standard GObject macros */
+#define E_TYPE_FOCUS_TRACKER \
+	(e_focus_tracker_get_type ())
+#define E_FOCUS_TRACKER(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_FOCUS_TRACKER, EFocusTracker))
+#define E_FOCUS_TRACKER_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_FOCUS_TRACKER, EFocusTrackerClass))
+#define E_IS_FOCUS_TRACKER(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_FOCUS_TRACKER))
+#define E_IS_FOCUS_TRACKER_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_FOCUS_TRACKER))
+#define E_FOCUS_TRACKER_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_FOCUS_TRACKER, EFocusTrackerClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EFocusTracker EFocusTracker;
+typedef struct _EFocusTrackerClass EFocusTrackerClass;
+typedef struct _EFocusTrackerPrivate EFocusTrackerPrivate;
+
+struct _EFocusTracker {
+	GObject parent;
+	EFocusTrackerPrivate *priv;
+};
+
+struct _EFocusTrackerClass {
+	GObjectClass parent_class;
+};
+
+GType		e_focus_tracker_get_type	(void);
+EFocusTracker *	e_focus_tracker_new		(GtkWindow *window);
+GtkWidget *	e_focus_tracker_get_focus	(EFocusTracker *focus_tracker);
+GtkWindow *	e_focus_tracker_get_window	(EFocusTracker *focus_tracker);
+GtkAction *	e_focus_tracker_get_cut_clipboard_action
+						(EFocusTracker *focus_tracker);
+void		e_focus_tracker_set_cut_clipboard_action
+						(EFocusTracker *focus_tracker,
+						 GtkAction *cut_clipboard);
+GtkAction *	e_focus_tracker_get_copy_clipboard_action
+						(EFocusTracker *focus_tracker);
+void		e_focus_tracker_set_copy_clipboard_action
+						(EFocusTracker *focus_tracker,
+						 GtkAction *copy_clipboard);
+GtkAction *	e_focus_tracker_get_paste_clipboard_action
+						(EFocusTracker *focus_tracker);
+void		e_focus_tracker_set_paste_clipboard_action
+						(EFocusTracker *focus_tracker,
+						 GtkAction *paste_clipboard);
+GtkAction *	e_focus_tracker_get_delete_selection_action
+						(EFocusTracker *focus_tracker);
+void		e_focus_tracker_set_delete_selection_action
+						(EFocusTracker *focus_tracker,
+						 GtkAction *delete_selection);
+GtkAction *	e_focus_tracker_get_select_all_action
+						(EFocusTracker *focus_tracker);
+void		e_focus_tracker_set_select_all_action
+						(EFocusTracker *focus_tracker,
+						 GtkAction *select_all);
+void		e_focus_tracker_update_actions	(EFocusTracker *focus_tracker);
+void		e_focus_tracker_cut_clipboard	(EFocusTracker *focus_tracker);
+void		e_focus_tracker_copy_clipboard	(EFocusTracker *focus_tracker);
+void		e_focus_tracker_paste_clipboard	(EFocusTracker *focus_tracker);
+void		e_focus_tracker_delete_selection
+						(EFocusTracker *focus_tracker);
+void		e_focus_tracker_select_all	(EFocusTracker *focus_tracker);
+
+G_END_DECLS
+
+#endif /* E_FOCUS_TRACKER_H */
diff --git a/e-util/e-html-utils.h b/e-util/e-html-utils.h
index f87e82f..2fe67ff 100644
--- a/e-util/e-html-utils.h
+++ b/e-util/e-html-utils.h
@@ -20,6 +20,10 @@
  *
  */
 
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
 #ifndef __E_HTML_UTILS__
 #define __E_HTML_UTILS__
 
diff --git a/e-util/e-icon-factory.h b/e-util/e-icon-factory.h
index 89a7d5a..c75c72f 100644
--- a/e-util/e-icon-factory.h
+++ b/e-util/e-icon-factory.h
@@ -21,6 +21,10 @@
  *
  */
 
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
 #ifndef _E_ICON_FACTORY_H_
 #define _E_ICON_FACTORY_H_
 
diff --git a/e-util/e-image-chooser.c b/e-util/e-image-chooser.c
new file mode 100644
index 0000000..20c2f0e
--- /dev/null
+++ b/e-util/e-image-chooser.c
@@ -0,0 +1,562 @@
+/*
+ * e-image-chooser.c
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+
+#include <glib/gi18n.h>
+
+#include "e-image-chooser.h"
+
+#include "e-icon-factory.h"
+
+#define E_IMAGE_CHOOSER_GET_PRIVATE(obj) \
+	(G_TYPE_INSTANCE_GET_PRIVATE \
+	((obj), E_TYPE_IMAGE_CHOOSER, EImageChooserPrivate))
+
+struct _EImageChooserPrivate {
+	GtkWidget *frame;
+	GtkWidget *image;
+
+	gchar *image_buf;
+	gint image_buf_size;
+	gint image_width;
+	gint image_height;
+
+	/* Default Image */
+	gchar *icon_name;
+};
+
+enum {
+	PROP_0,
+	PROP_ICON_NAME
+};
+
+enum {
+	CHANGED,
+	LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+#define URI_LIST_TYPE "text/uri-list"
+
+G_DEFINE_TYPE (
+	EImageChooser,
+	e_image_chooser,
+	GTK_TYPE_VBOX)
+
+static gboolean
+set_image_from_data (EImageChooser *chooser,
+                     gchar *data,
+                     gint length)
+{
+	GdkPixbufLoader *loader;
+	GdkPixbuf *pixbuf;
+	gfloat scale;
+	gint new_height;
+	gint new_width;
+
+	loader = gdk_pixbuf_loader_new ();
+	gdk_pixbuf_loader_write (loader, (guchar *) data, length, NULL);
+	gdk_pixbuf_loader_close (loader, NULL);
+
+	pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
+	if (pixbuf)
+		g_object_ref (pixbuf);
+
+	g_object_unref (loader);
+
+	if (pixbuf == NULL)
+		return FALSE;
+
+	new_height = gdk_pixbuf_get_height (pixbuf);
+	new_width = gdk_pixbuf_get_width (pixbuf);
+
+	if (chooser->priv->image_height == 0
+	    && chooser->priv->image_width == 0) {
+		scale = 1.0;
+	} else if (chooser->priv->image_height < new_height
+		 || chooser->priv->image_width < new_width) {
+		/* we need to scale down */
+		if (new_height > new_width)
+			scale = (gfloat) chooser->priv->image_height / new_height;
+		else
+			scale = (gfloat) chooser->priv->image_width / new_width;
+	} else {
+		/* we need to scale up */
+		if (new_height > new_width)
+			scale = (gfloat) new_height / chooser->priv->image_height;
+		else
+			scale = (gfloat) new_width / chooser->priv->image_width;
+	}
+
+	if (scale == 1.0) {
+		gtk_image_set_from_pixbuf (
+			GTK_IMAGE (chooser->priv->image), pixbuf);
+		chooser->priv->image_width = new_width;
+		chooser->priv->image_height = new_height;
+	} else {
+		GdkPixbuf *scaled;
+		GdkPixbuf *composite;
+
+		new_width *= scale;
+		new_height *= scale;
+		new_width = MIN (new_width, chooser->priv->image_width);
+		new_height = MIN (new_height, chooser->priv->image_height);
+
+		scaled = gdk_pixbuf_scale_simple (
+			pixbuf, new_width, new_height,
+			GDK_INTERP_BILINEAR);
+
+		composite = gdk_pixbuf_new (
+			GDK_COLORSPACE_RGB, TRUE,
+			gdk_pixbuf_get_bits_per_sample (pixbuf),
+			chooser->priv->image_width,
+			chooser->priv->image_height);
+
+		gdk_pixbuf_fill (composite, 0x00000000);
+
+		gdk_pixbuf_copy_area (
+			scaled, 0, 0, new_width, new_height,
+			composite,
+			chooser->priv->image_width / 2 - new_width / 2,
+			chooser->priv->image_height / 2 - new_height / 2);
+
+		gtk_image_set_from_pixbuf (
+			GTK_IMAGE (chooser->priv->image), composite);
+
+		g_object_unref (scaled);
+		g_object_unref (composite);
+	}
+
+	g_object_unref (pixbuf);
+
+	g_free (chooser->priv->image_buf);
+	chooser->priv->image_buf = data;
+	chooser->priv->image_buf_size = length;
+
+	g_signal_emit (chooser, signals[CHANGED], 0);
+
+	return TRUE;
+}
+
+static gboolean
+image_drag_motion_cb (GtkWidget *widget,
+                      GdkDragContext *context,
+                      gint x,
+                      gint y,
+                      guint time,
+                      EImageChooser *chooser)
+{
+	GtkFrame *frame;
+	GList *targets, *p;
+
+	frame = GTK_FRAME (chooser->priv->frame);
+	targets = gdk_drag_context_list_targets (context);
+
+	for (p = targets; p != NULL; p = p->next) {
+		gchar *possible_type;
+
+		possible_type = gdk_atom_name (GDK_POINTER_TO_ATOM (p->data));
+		if (!strcmp (possible_type, URI_LIST_TYPE)) {
+			g_free (possible_type);
+			gdk_drag_status (context, GDK_ACTION_COPY, time);
+			gtk_frame_set_shadow_type (frame, GTK_SHADOW_IN);
+			return TRUE;
+		}
+
+		g_free (possible_type);
+	}
+
+	gtk_frame_set_shadow_type (frame, GTK_SHADOW_NONE);
+
+	return FALSE;
+}
+
+static void
+image_drag_leave_cb (GtkWidget *widget,
+                     GdkDragContext *context,
+                     guint time,
+                     EImageChooser *chooser)
+{
+	GtkFrame *frame;
+
+	frame = GTK_FRAME (chooser->priv->frame);
+	gtk_frame_set_shadow_type (frame, GTK_SHADOW_NONE);
+}
+
+static gboolean
+image_drag_drop_cb (GtkWidget *widget,
+                    GdkDragContext *context,
+                    gint x,
+                    gint y,
+                    guint time,
+                    EImageChooser *chooser)
+{
+	GtkFrame *frame;
+	GList *targets, *p;
+
+	frame = GTK_FRAME (chooser->priv->frame);
+	targets = gdk_drag_context_list_targets (context);
+
+	if (targets == NULL) {
+		gtk_frame_set_shadow_type (frame, GTK_SHADOW_NONE);
+		return FALSE;
+	}
+
+	for (p = targets; p != NULL; p = p->next) {
+		gchar *possible_type;
+
+		possible_type = gdk_atom_name (GDK_POINTER_TO_ATOM (p->data));
+		if (!strcmp (possible_type, URI_LIST_TYPE)) {
+			g_free (possible_type);
+			gtk_drag_get_data (
+				widget, context,
+				GDK_POINTER_TO_ATOM (p->data), time);
+			gtk_frame_set_shadow_type (frame, GTK_SHADOW_NONE);
+			return TRUE;
+		}
+
+		g_free (possible_type);
+	}
+
+	gtk_frame_set_shadow_type (frame, GTK_SHADOW_NONE);
+
+	return FALSE;
+}
+
+static void
+image_chooser_file_loaded_cb (GFile *file,
+                              GAsyncResult *result,
+                              EImageChooser *chooser)
+{
+	gchar *contents;
+	gsize length;
+	GError *error = NULL;
+
+	g_file_load_contents_finish (
+		file, result, &contents, &length, NULL, &error);
+
+	if (error != NULL) {
+		g_warning ("%s", error->message);
+		g_error_free (error);
+		goto exit;
+	}
+
+	set_image_from_data (chooser, contents, length);
+
+	g_free (contents);
+
+exit:
+	g_object_unref (chooser);
+}
+
+static void
+image_drag_data_received_cb (GtkWidget *widget,
+                             GdkDragContext *context,
+                             gint x,
+                             gint y,
+                             GtkSelectionData *selection_data,
+                             guint info,
+                             guint time,
+                             EImageChooser *chooser)
+{
+	GFile *file;
+	gboolean handled = FALSE;
+	gchar **uris;
+
+	uris = gtk_selection_data_get_uris (selection_data);
+
+	if (uris == NULL)
+		goto exit;
+
+	file = g_file_new_for_uri (uris[0]);
+
+	/* XXX Not cancellable. */
+	g_file_load_contents_async (
+		file, NULL, (GAsyncReadyCallback)
+		image_chooser_file_loaded_cb,
+		g_object_ref (chooser));
+
+	g_object_unref (file);
+	g_strfreev (uris);
+
+	/* Assume success.  We won't know til later. */
+	handled = TRUE;
+
+exit:
+	gtk_drag_finish (context, handled, FALSE, time);
+}
+
+static void
+image_chooser_set_icon_name (EImageChooser *chooser,
+                             const gchar *icon_name)
+{
+	GtkIconTheme *icon_theme;
+	GtkIconInfo *icon_info;
+	const gchar *filename;
+	gint width, height;
+
+	g_return_if_fail (chooser->priv->icon_name == NULL);
+
+	chooser->priv->icon_name = g_strdup (icon_name);
+
+	icon_theme = gtk_icon_theme_get_default ();
+	gtk_icon_size_lookup (GTK_ICON_SIZE_DIALOG, &width, &height);
+
+	icon_info = gtk_icon_theme_lookup_icon (
+		icon_theme, icon_name, height, 0);
+	g_return_if_fail (icon_info != NULL);
+
+	filename = gtk_icon_info_get_filename (icon_info);
+	e_image_chooser_set_from_file (chooser, filename);
+	gtk_icon_info_free (icon_info);
+}
+
+static void
+image_chooser_set_property (GObject *object,
+                            guint property_id,
+                            const GValue *value,
+                            GParamSpec *pspec)
+{
+	switch (property_id) {
+		case PROP_ICON_NAME:
+			image_chooser_set_icon_name (
+				E_IMAGE_CHOOSER (object),
+				g_value_get_string (value));
+			return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+image_chooser_get_property (GObject *object,
+                            guint property_id,
+                            GValue *value,
+                            GParamSpec *pspec)
+{
+	switch (property_id) {
+		case PROP_ICON_NAME:
+			g_value_set_string (
+				value,
+				e_image_chooser_get_icon_name (
+				E_IMAGE_CHOOSER (object)));
+			return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+image_chooser_dispose (GObject *object)
+{
+	EImageChooserPrivate *priv;
+
+	priv = E_IMAGE_CHOOSER_GET_PRIVATE (object);
+
+	if (priv->frame != NULL) {
+		g_object_unref (priv->frame);
+		priv->frame = NULL;
+	}
+
+	if (priv->image != NULL) {
+		g_object_unref (priv->image);
+		priv->image = NULL;
+	}
+
+	/* Chain up to parent's dispose() method. */
+	G_OBJECT_CLASS (e_image_chooser_parent_class)->dispose (object);
+}
+
+static void
+image_chooser_finalize (GObject *object)
+{
+	EImageChooserPrivate *priv;
+
+	priv = E_IMAGE_CHOOSER_GET_PRIVATE (object);
+
+	g_free (priv->image_buf);
+	g_free (priv->icon_name);
+
+	/* Chain up to parent's finalize() method. */
+	G_OBJECT_CLASS (e_image_chooser_parent_class)->finalize (object);
+}
+
+static void
+e_image_chooser_class_init (EImageChooserClass *class)
+{
+	GObjectClass *object_class;
+
+	g_type_class_add_private (class, sizeof (EImageChooserPrivate));
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->set_property = image_chooser_set_property;
+	object_class->get_property = image_chooser_get_property;
+	object_class->dispose = image_chooser_dispose;
+	object_class->finalize = image_chooser_finalize;
+
+	g_object_class_install_property (
+		object_class,
+		PROP_ICON_NAME,
+		g_param_spec_string (
+			"icon-name",
+			"Icon Name",
+			NULL,
+			"avatar-default",
+			G_PARAM_READWRITE |
+			G_PARAM_CONSTRUCT_ONLY));
+
+	signals[CHANGED] = g_signal_new (
+		"changed",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_FIRST,
+		G_STRUCT_OFFSET (EImageChooserClass, changed),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__VOID,
+		G_TYPE_NONE, 0);
+}
+
+static void
+e_image_chooser_init (EImageChooser *chooser)
+{
+	GtkWidget *container;
+	GtkWidget *widget;
+
+	chooser->priv = E_IMAGE_CHOOSER_GET_PRIVATE (chooser);
+
+	container = GTK_WIDGET (chooser);
+
+	widget = gtk_frame_new ("");
+	gtk_frame_set_shadow_type (GTK_FRAME (widget), GTK_SHADOW_NONE);
+	gtk_box_pack_start (GTK_BOX (container), widget, TRUE, TRUE, 0);
+	chooser->priv->frame = g_object_ref (widget);
+	gtk_widget_show (widget);
+
+	container = widget;
+
+	widget = gtk_alignment_new (0, 0, 0, 0);
+	gtk_container_add (GTK_CONTAINER (container), widget);
+	gtk_widget_show (widget);
+
+	container = widget;
+
+	widget = gtk_image_new ();
+	gtk_container_add (GTK_CONTAINER (container), widget);
+	chooser->priv->image = g_object_ref (widget);
+	gtk_widget_show (widget);
+
+	gtk_drag_dest_set (widget, 0, NULL, 0, GDK_ACTION_COPY);
+	gtk_drag_dest_add_uri_targets (widget);
+
+	g_signal_connect (
+		widget, "drag-motion",
+		G_CALLBACK (image_drag_motion_cb), chooser);
+	g_signal_connect (
+		widget, "drag-leave",
+		G_CALLBACK (image_drag_leave_cb), chooser);
+	g_signal_connect (
+		widget, "drag-drop",
+		G_CALLBACK (image_drag_drop_cb), chooser);
+	g_signal_connect (
+		widget, "drag-data-received",
+		G_CALLBACK (image_drag_data_received_cb), chooser);
+}
+
+const gchar *
+e_image_chooser_get_icon_name (EImageChooser *chooser)
+{
+	g_return_val_if_fail (E_IS_IMAGE_CHOOSER (chooser), NULL);
+
+	return chooser->priv->icon_name;
+}
+
+GtkWidget *
+e_image_chooser_new (const gchar *icon_name)
+{
+	g_return_val_if_fail (icon_name != NULL, NULL);
+
+	return g_object_new (
+		E_TYPE_IMAGE_CHOOSER,
+		"icon-name", icon_name, NULL);
+}
+
+gboolean
+e_image_chooser_set_from_file (EImageChooser *chooser,
+                               const gchar *filename)
+{
+	gchar *data;
+	gsize data_length;
+
+	g_return_val_if_fail (E_IS_IMAGE_CHOOSER (chooser), FALSE);
+	g_return_val_if_fail (filename != NULL, FALSE);
+
+	if (!g_file_get_contents (filename, &data, &data_length, NULL))
+		return FALSE;
+
+	if (!set_image_from_data (chooser, data, data_length))
+		g_free (data);
+
+	return TRUE;
+}
+
+gboolean
+e_image_chooser_get_image_data (EImageChooser *chooser,
+                                gchar **data,
+                                gsize *data_length)
+{
+	g_return_val_if_fail (E_IS_IMAGE_CHOOSER (chooser), FALSE);
+	g_return_val_if_fail (data != NULL, FALSE);
+	g_return_val_if_fail (data_length != NULL, FALSE);
+
+	*data_length = chooser->priv->image_buf_size;
+	*data = g_malloc (*data_length);
+	memcpy (*data, chooser->priv->image_buf, *data_length);
+
+	return TRUE;
+}
+
+gboolean
+e_image_chooser_set_image_data (EImageChooser *chooser,
+                                gchar *data,
+                                gsize data_length)
+{
+	gchar *buf;
+
+	g_return_val_if_fail (E_IS_IMAGE_CHOOSER (chooser), FALSE);
+	g_return_val_if_fail (data != NULL, FALSE);
+
+	/* yuck, a copy... */
+	buf = g_malloc (data_length);
+	memcpy (buf, data, data_length);
+
+	if (!set_image_from_data (chooser, buf, data_length)) {
+		g_free (buf);
+		return FALSE;
+	}
+
+	return TRUE;
+}
diff --git a/e-util/e-image-chooser.h b/e-util/e-image-chooser.h
new file mode 100644
index 0000000..d9bfb34
--- /dev/null
+++ b/e-util/e-image-chooser.h
@@ -0,0 +1,80 @@
+/*
+ * e-image-chooser.h
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_IMAGE_CHOOSER_H
+#define E_IMAGE_CHOOSER_H
+
+#include <gtk/gtk.h>
+
+/* Standard GObject macros */
+#define E_TYPE_IMAGE_CHOOSER \
+	(e_image_chooser_get_type ())
+#define E_IMAGE_CHOOSER(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_IMAGE_CHOOSER, EImageChooser))
+#define E_IMAGE_CHOOSER_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_IMAGE_CHOOSER, EImageChooserClass))
+#define E_IS_IMAGE_CHOOSER(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_IMAGE_CHOOSER))
+#define E_IS_IMAGE_CHOOSER_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_IMAGE_CHOOSER))
+#define E_IMAGE_CHOOSER_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_IMAGE_CHOOSER, EImageChooserClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EImageChooser EImageChooser;
+typedef struct _EImageChooserClass EImageChooserClass;
+typedef struct _EImageChooserPrivate EImageChooserPrivate;
+
+struct _EImageChooser {
+	GtkBox parent;
+	EImageChooserPrivate *priv;
+};
+
+struct _EImageChooserClass {
+	GtkBoxClass parent_class;
+
+	/* signals */
+	void (*changed) (EImageChooser *chooser);
+};
+
+GType		e_image_chooser_get_type	(void);
+GtkWidget *	e_image_chooser_new		(const gchar *icon_name);
+const gchar *	e_image_chooser_get_icon_name	(EImageChooser *chooser);
+gboolean	e_image_chooser_set_from_file	(EImageChooser *chooser,
+						 const gchar *filename);
+gboolean	e_image_chooser_set_image_data	(EImageChooser *chooser,
+						 gchar *data,
+						 gsize data_length);
+gboolean	e_image_chooser_get_image_data	(EImageChooser *chooser,
+						 gchar **data,
+						 gsize *data_length);
+
+#endif /* E_IMAGE_CHOOSER_H */
diff --git a/e-util/e-import-assistant.c b/e-util/e-import-assistant.c
new file mode 100644
index 0000000..ae48e5c
--- /dev/null
+++ b/e-util/e-import-assistant.c
@@ -0,0 +1,1436 @@
+/*
+ * e-import-assistant.c
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-import-assistant.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <string.h>
+
+#include <glib/gi18n.h>
+#include <gdk/gdkkeysyms.h>
+#include <libebackend/libebackend.h>
+
+#include "e-import.h"
+#include "e-util-private.h"
+
+#define E_IMPORT_ASSISTANT_GET_PRIVATE(obj) \
+	(G_TYPE_INSTANCE_GET_PRIVATE \
+	((obj), E_TYPE_IMPORT_ASSISTANT, EImportAssistantPrivate))
+
+typedef struct _ImportFilePage ImportFilePage;
+typedef struct _ImportDestinationPage ImportDestinationPage;
+typedef struct _ImportTypePage ImportTypePage;
+typedef struct _ImportSelectionPage ImportSelectionPage;
+typedef struct _ImportProgressPage ImportProgressPage;
+typedef struct _ImportSimplePage ImportSimplePage;
+
+struct _ImportFilePage {
+	GtkWidget *filename;
+	GtkWidget *filetype;
+
+	EImportTargetURI *target;
+	EImportImporter *importer;
+};
+
+struct _ImportDestinationPage {
+	GtkWidget *control;
+};
+
+struct _ImportTypePage {
+	GtkWidget *intelligent;
+	GtkWidget *file;
+};
+
+struct _ImportSelectionPage {
+	GSList *importers;
+	GSList *current;
+	EImportTargetHome *target;
+};
+
+struct _ImportProgressPage {
+	GtkWidget *progress_bar;
+};
+
+struct _ImportSimplePage {
+	GtkWidget *actionlabel;
+	GtkWidget *filetypetable;
+	GtkWidget *filetype;
+	GtkWidget *control; /* importer's destination or preview widget in an alignment */
+	gboolean has_preview; /* TRUE when 'control' holds a preview widget,
+				   otherwise holds destination widget */
+
+	EImportTargetURI *target;
+	EImportImporter *importer;
+};
+
+struct _EImportAssistantPrivate {
+	ImportFilePage file_page;
+	ImportDestinationPage destination_page;
+	ImportTypePage type_page;
+	ImportSelectionPage selection_page;
+	ImportProgressPage progress_page;
+	ImportSimplePage simple_page;
+
+	EImport *import;
+
+	gboolean is_simple;
+	GPtrArray *fileuris; /* each element is a file URI, as a newly allocated string */
+
+	/* Used for importing phase of operation */
+	EImportTarget *import_target;
+	EImportImporter *import_importer;
+};
+
+enum {
+	PAGE_START,
+	PAGE_INTELI_OR_DIRECT,
+	PAGE_INTELI_SOURCE,
+	PAGE_FILE_CHOOSE,
+	PAGE_FILE_DEST,
+	PAGE_FINISH,
+	PAGE_PROGRESS
+};
+
+enum {
+	PROP_0,
+	PROP_IS_SIMPLE
+};
+
+enum {
+	FINISHED,
+	LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+G_DEFINE_TYPE_WITH_CODE (
+	EImportAssistant,
+	e_import_assistant,
+	GTK_TYPE_ASSISTANT,
+	G_IMPLEMENT_INTERFACE (
+		E_TYPE_EXTENSIBLE, NULL))
+
+/* Importing functions */
+
+static void
+import_assistant_emit_finished (EImportAssistant *import_assistant)
+{
+	g_signal_emit (import_assistant, signals[FINISHED], 0);
+}
+
+static void
+filename_changed (GtkWidget *widget,
+                  GtkAssistant *assistant)
+{
+	EImportAssistantPrivate *priv;
+	ImportFilePage *page;
+	const gchar *filename;
+	gint fileok;
+
+	priv = E_IMPORT_ASSISTANT_GET_PRIVATE (assistant);
+	page = &priv->file_page;
+
+	filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (widget));
+
+	fileok =
+		filename != NULL && *filename != '\0' &&
+		g_file_test (filename, G_FILE_TEST_IS_REGULAR);
+
+	if (fileok) {
+		GtkTreeIter iter;
+		GtkTreeModel *model;
+		gboolean valid;
+		GSList *l;
+		EImportImporter *first = NULL;
+		gint i = 0, firstitem = 0;
+
+		g_free (page->target->uri_src);
+		page->target->uri_src = g_filename_to_uri (filename, NULL, NULL);
+
+		l = e_import_get_importers (
+			priv->import, (EImportTarget *) page->target);
+		model = gtk_combo_box_get_model (GTK_COMBO_BOX (page->filetype));
+		valid = gtk_tree_model_get_iter_first (model, &iter);
+		while (valid) {
+			gpointer eii = NULL;
+
+			gtk_tree_model_get (model, &iter, 2, &eii, -1);
+
+			if (g_slist_find (l, eii) != NULL) {
+				if (first == NULL) {
+					firstitem = i;
+					first = eii;
+				}
+				gtk_list_store_set (GTK_LIST_STORE (model), &iter, 1, TRUE, -1);
+			} else {
+				if (page->importer == eii)
+					page->importer = NULL;
+				gtk_list_store_set (GTK_LIST_STORE (model), &iter, 1, FALSE, -1);
+			}
+			i++;
+			valid = gtk_tree_model_iter_next (model, &iter);
+		}
+		g_slist_free (l);
+
+		if (page->importer == NULL && first) {
+			page->importer = first;
+			gtk_combo_box_set_active (GTK_COMBO_BOX (page->filetype), firstitem);
+		}
+		fileok = first != NULL;
+	} else {
+		GtkTreeIter iter;
+		GtkTreeModel *model;
+		gboolean valid;
+
+		model = gtk_combo_box_get_model (GTK_COMBO_BOX (page->filetype));
+		for (valid = gtk_tree_model_get_iter_first (model, &iter);
+		     valid;
+		     valid = gtk_tree_model_iter_next (model, &iter)) {
+			gtk_list_store_set (GTK_LIST_STORE (model), &iter, 1, FALSE, -1);
+		}
+	}
+
+	widget = gtk_assistant_get_nth_page (assistant, PAGE_FILE_CHOOSE);
+	gtk_assistant_set_page_complete (assistant, widget, fileok);
+}
+
+static void
+filetype_changed_cb (GtkComboBox *combo_box,
+                     GtkAssistant *assistant)
+{
+	EImportAssistantPrivate *priv;
+	GtkTreeModel *model;
+	GtkTreeIter iter;
+
+	priv = E_IMPORT_ASSISTANT_GET_PRIVATE (assistant);
+
+	g_return_if_fail (gtk_combo_box_get_active_iter (combo_box, &iter));
+
+	model = gtk_combo_box_get_model (combo_box);
+	gtk_tree_model_get (model, &iter, 2, &priv->file_page.importer, -1);
+	filename_changed (priv->file_page.filename, assistant);
+}
+
+static GtkWidget *
+import_assistant_file_page_init (EImportAssistant *import_assistant)
+{
+	GtkWidget *page;
+	GtkWidget *label;
+	GtkWidget *container;
+	GtkWidget *widget;
+	GtkCellRenderer *cell;
+	GtkListStore *store;
+	const gchar *text;
+	gint row = 0;
+
+	page = gtk_vbox_new (FALSE, 6);
+	gtk_container_set_border_width (GTK_CONTAINER (page), 12);
+	gtk_widget_show (page);
+
+	container = page;
+
+	text = _("Choose the file that you want to import into Evolution, "
+		 "and select what type of file it is from the list.");
+
+	widget = gtk_label_new (text);
+	gtk_label_set_line_wrap (GTK_LABEL (widget), TRUE);
+	gtk_box_pack_start (GTK_BOX (container), widget, FALSE, TRUE, 0);
+	gtk_widget_show (widget);
+
+	widget = gtk_table_new (2, 2, FALSE);
+	gtk_table_set_row_spacings (GTK_TABLE (widget), 2);
+	gtk_table_set_col_spacings (GTK_TABLE (widget), 10);
+	gtk_container_set_border_width (GTK_CONTAINER (widget), 8);
+	gtk_box_pack_start (GTK_BOX (container), widget, TRUE, TRUE, 0);
+	gtk_widget_show (widget);
+
+	container = widget;
+
+	widget = gtk_label_new_with_mnemonic (_("F_ilename:"));
+	gtk_misc_set_alignment (GTK_MISC (widget), 1, 0.5);
+	gtk_table_attach (
+		GTK_TABLE (container), widget,
+		0, 1, row, row + 1, GTK_FILL, 0, 0, 0);
+	gtk_widget_show (widget);
+
+	label = widget;
+
+	widget = gtk_file_chooser_button_new (
+		_("Select a file"), GTK_FILE_CHOOSER_ACTION_OPEN);
+	gtk_label_set_mnemonic_widget (GTK_LABEL (label), widget);
+	gtk_table_attach (
+		GTK_TABLE (container), widget, 1, 2,
+		row, row + 1, GTK_EXPAND | GTK_FILL, 0, 0, 0);
+	import_assistant->priv->file_page.filename = widget;
+	gtk_widget_show (widget);
+
+	g_signal_connect (
+		widget, "selection-changed",
+		G_CALLBACK (filename_changed), import_assistant);
+
+	row++;
+
+	widget = gtk_label_new_with_mnemonic (_("File _type:"));
+	gtk_misc_set_alignment (GTK_MISC (widget), 1, 0.5);
+	gtk_table_attach (
+		GTK_TABLE (container), widget,
+		0, 1, row, row + 1, GTK_FILL, 0, 0, 0);
+	gtk_widget_show (widget);
+
+	label = widget;
+
+	store = gtk_list_store_new (
+		3, G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_POINTER);
+	widget = gtk_combo_box_new_with_model (GTK_TREE_MODEL (store));
+	gtk_label_set_mnemonic_widget (GTK_LABEL (label), widget);
+	gtk_table_attach (
+		GTK_TABLE (container), widget,
+		1, 2, row, row + 1, GTK_EXPAND | GTK_FILL, 0, 0, 0);
+	import_assistant->priv->file_page.filetype = widget;
+	gtk_widget_show (widget);
+	g_object_unref (store);
+
+	cell = gtk_cell_renderer_text_new ();
+	gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (widget), cell, TRUE);
+	gtk_cell_layout_set_attributes (
+		GTK_CELL_LAYOUT (widget), cell,
+		"text", 0, "sensitive", 1, NULL);
+
+	return page;
+}
+
+static GtkWidget *
+import_assistant_destination_page_init (EImportAssistant *import_assistant)
+{
+	GtkWidget *page;
+	GtkWidget *container;
+	GtkWidget *widget;
+	const gchar *text;
+
+	page = gtk_vbox_new (FALSE, 6);
+	gtk_container_set_border_width (GTK_CONTAINER (page), 12);
+	gtk_widget_show (page);
+
+	container = page;
+
+	text = _("Choose the destination for this import");
+
+	widget = gtk_label_new (text);
+	gtk_label_set_line_wrap (GTK_LABEL (widget), TRUE);
+	gtk_box_pack_start (GTK_BOX (container), widget, FALSE, TRUE, 0);
+	gtk_widget_show (widget);
+
+	return page;
+}
+
+static GtkWidget *
+import_assistant_type_page_init (EImportAssistant *import_assistant)
+{
+	GtkRadioButton *radio_button;
+	GtkWidget *page;
+	GtkWidget *container;
+	GtkWidget *widget;
+	const gchar *text;
+
+	page = gtk_vbox_new (FALSE, 6);
+	gtk_container_set_border_width (GTK_CONTAINER (page), 12);
+	gtk_widget_show (page);
+
+	container = page;
+
+	text = _("Choose the type of importer to run:");
+
+	widget = gtk_label_new (text);
+	gtk_label_set_line_wrap (GTK_LABEL (widget), TRUE);
+	gtk_box_pack_start (GTK_BOX (container), widget, FALSE, TRUE, 0);
+	gtk_widget_show (widget);
+
+	widget = gtk_radio_button_new_with_mnemonic (
+		NULL, _("Import data and settings from _older programs"));
+	gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+	import_assistant->priv->type_page.intelligent = widget;
+	gtk_widget_show (widget);
+
+	radio_button = GTK_RADIO_BUTTON (widget);
+
+	widget = gtk_radio_button_new_with_mnemonic_from_widget (
+		radio_button, _("Import a _single file"));
+	gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+	import_assistant->priv->type_page.file = widget;
+	gtk_widget_show (widget);
+
+	return page;
+}
+
+static GtkWidget *
+import_assistant_selection_page_init (EImportAssistant *import_assistant)
+{
+	GtkWidget *page;
+	GtkWidget *container;
+	GtkWidget *widget;
+	const gchar *text;
+
+	page = gtk_vbox_new (FALSE, 6);
+	gtk_container_set_border_width (GTK_CONTAINER (page), 12);
+	gtk_widget_show (page);
+
+	container = page;
+
+	text = _("Please select the information "
+		 "that you would like to import:");
+
+	widget = gtk_label_new (text);
+	gtk_label_set_line_wrap (GTK_LABEL (widget), TRUE);
+	gtk_box_pack_start (GTK_BOX (container), widget, FALSE, TRUE, 0);
+	gtk_widget_show (widget);
+
+	widget = gtk_hseparator_new ();
+	gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+	gtk_widget_show (widget);
+
+	return page;
+}
+
+static GtkWidget *
+import_assistant_progress_page_init (EImportAssistant *import_assistant)
+{
+	GtkWidget *page;
+	GtkWidget *container;
+	GtkWidget *widget;
+
+	page = gtk_vbox_new (FALSE, 6);
+	gtk_container_set_border_width (GTK_CONTAINER (page), 12);
+	gtk_widget_show (page);
+
+	container = page;
+
+	widget = gtk_progress_bar_new ();
+	gtk_box_pack_start (GTK_BOX (container), widget, TRUE, FALSE, 0);
+	import_assistant->priv->progress_page.progress_bar = widget;
+	gtk_widget_show (widget);
+
+	return page;
+}
+
+static GtkWidget *
+import_assistant_simple_page_init (EImportAssistant *import_assistant)
+{
+	GtkWidget *page;
+	GtkWidget *label;
+	GtkWidget *container;
+	GtkWidget *widget;
+	GtkCellRenderer *cell;
+	GtkListStore *store;
+	gint row = 0;
+
+	page = gtk_vbox_new (FALSE, 6);
+	gtk_container_set_border_width (GTK_CONTAINER (page), 12);
+	gtk_widget_show (page);
+
+	container = page;
+
+	widget = gtk_label_new ("");
+	gtk_label_set_line_wrap (GTK_LABEL (widget), TRUE);
+	gtk_box_pack_start (GTK_BOX (container), widget, FALSE, TRUE, 0);
+	gtk_widget_show (widget);
+	import_assistant->priv->simple_page.actionlabel = widget;
+
+	widget = gtk_table_new (2, 1, FALSE);
+	gtk_table_set_row_spacings (GTK_TABLE (widget), 2);
+	gtk_table_set_col_spacings (GTK_TABLE (widget), 10);
+	gtk_container_set_border_width (GTK_CONTAINER (widget), 8);
+	gtk_box_pack_start (GTK_BOX (container), widget, FALSE, TRUE, 0);
+	gtk_widget_show (widget);
+	import_assistant->priv->simple_page.filetypetable = widget;
+
+	container = widget;
+
+	widget = gtk_label_new_with_mnemonic (_("File _type:"));
+	gtk_misc_set_alignment (GTK_MISC (widget), 1, 0.5);
+	gtk_table_attach (
+		GTK_TABLE (container), widget,
+		0, 1, row, row + 1, GTK_FILL, 0, 0, 0);
+	gtk_widget_show (widget);
+
+	label = widget;
+
+	store = gtk_list_store_new (
+		3, G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_POINTER);
+	widget = gtk_combo_box_new_with_model (GTK_TREE_MODEL (store));
+	gtk_label_set_mnemonic_widget (GTK_LABEL (label), widget);
+	gtk_table_attach (
+		GTK_TABLE (container), widget,
+		1, 2, row, row + 1, GTK_EXPAND | GTK_FILL, 0, 0, 0);
+	import_assistant->priv->simple_page.filetype = widget;
+	gtk_widget_show (widget);
+	g_object_unref (store);
+
+	cell = gtk_cell_renderer_text_new ();
+	gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (widget), cell, TRUE);
+	gtk_cell_layout_set_attributes (
+		GTK_CELL_LAYOUT (widget), cell,
+		"text", 0, "sensitive", 1, NULL);
+
+	import_assistant->priv->simple_page.control = NULL;
+
+	return page;
+}
+
+static void
+prepare_intelligent_page (GtkAssistant *assistant,
+                          GtkWidget *vbox)
+{
+	EImportAssistantPrivate *priv;
+	GSList *l;
+	GtkWidget *table;
+	gint row;
+	ImportSelectionPage *page;
+
+	priv = E_IMPORT_ASSISTANT_GET_PRIVATE (assistant);
+	page = &priv->selection_page;
+
+	if (page->target != NULL) {
+		gtk_assistant_set_page_complete (assistant, vbox, FALSE);
+		return;
+	}
+
+	page->target = e_import_target_new_home (priv->import);
+
+	if (page->importers)
+		g_slist_free (page->importers);
+	l = page->importers =
+		e_import_get_importers (
+			priv->import, (EImportTarget *) page->target);
+
+	if (l == NULL) {
+		GtkWidget *widget;
+		const gchar *text;
+
+		text = _("Evolution checked for settings to import from "
+			 "the following applications: Pine, Netscape, Elm, "
+			 "iCalendar. No importable settings found. If you "
+			 "would like to try again, please click the "
+			 "\"Back\" button.");
+
+		widget = gtk_label_new (text);
+		gtk_label_set_line_wrap (GTK_LABEL (widget), TRUE);
+		gtk_box_pack_start (GTK_BOX (vbox), widget, FALSE, TRUE, 0);
+		gtk_widget_show (widget);
+
+		gtk_assistant_set_page_complete (assistant, vbox, FALSE);
+
+		return;
+	}
+
+	table = gtk_table_new (g_slist_length (l), 2, FALSE);
+	row = 0;
+	for (; l; l = l->next) {
+		EImportImporter *eii = l->data;
+		gchar *str;
+		GtkWidget *w, *label;
+
+		w = e_import_get_widget (
+			priv->import, (EImportTarget *) page->target, eii);
+
+		str = g_strdup_printf (_("From %s:"), eii->name);
+		label = gtk_label_new (str);
+		gtk_widget_show (label);
+		g_free (str);
+
+		gtk_misc_set_alignment (GTK_MISC (label), 0, .5);
+
+		gtk_table_attach (
+			GTK_TABLE (table), label,
+			0, 1, row, row + 1, GTK_FILL, 0, 0, 0);
+		if (w)
+			gtk_table_attach (
+				GTK_TABLE (table), w,
+				1, 2, row, row + 1, GTK_FILL, 0, 3, 0);
+		row++;
+	}
+
+	gtk_widget_show (table);
+	gtk_box_pack_start (GTK_BOX (vbox), table, FALSE, FALSE, 0);
+
+	gtk_assistant_set_page_complete (assistant, vbox, TRUE);
+}
+
+static void
+import_status (EImport *import,
+               const gchar *what,
+               gint percent,
+               gpointer user_data)
+{
+	EImportAssistant *import_assistant = user_data;
+	GtkProgressBar *progress_bar;
+
+	progress_bar = GTK_PROGRESS_BAR (
+		import_assistant->priv->progress_page.progress_bar);
+	gtk_progress_bar_set_fraction (progress_bar, percent / 100.0);
+	gtk_progress_bar_set_text (progress_bar, what);
+}
+
+static void
+import_done (EImport *ei,
+             gpointer user_data)
+{
+	EImportAssistant *import_assistant = user_data;
+
+	import_assistant_emit_finished (import_assistant);
+}
+
+static void
+import_simple_done (EImport *ei,
+                    gpointer user_data)
+{
+	EImportAssistant *import_assistant = user_data;
+	EImportAssistantPrivate *priv;
+
+	g_return_if_fail (import_assistant != NULL);
+
+	priv = import_assistant->priv;
+	g_return_if_fail (priv != NULL);
+	g_return_if_fail (priv->fileuris != NULL);
+	g_return_if_fail (priv->simple_page.target != NULL);
+
+	if (import_assistant->priv->fileuris->len > 0) {
+		import_status (ei, "", 0, import_assistant);
+
+		/* process next file URI */
+		g_free (priv->simple_page.target->uri_src);
+		priv->simple_page.target->uri_src =
+			g_ptr_array_remove_index (priv->fileuris, 0);
+
+		e_import_import (
+			priv->import, priv->import_target,
+			priv->import_importer, import_status,
+			import_simple_done, import_assistant);
+	} else
+		import_done (ei, import_assistant);
+}
+
+static void
+import_intelligent_done (EImport *ei,
+                         gpointer user_data)
+{
+	EImportAssistant *import_assistant = user_data;
+	ImportSelectionPage *page;
+
+	page = &import_assistant->priv->selection_page;
+
+	if (page->current && (page->current = page->current->next)) {
+		import_status (ei, "", 0, import_assistant);
+		import_assistant->priv->import_importer = page->current->data;
+		e_import_import (
+			import_assistant->priv->import,
+			(EImportTarget *) page->target,
+			import_assistant->priv->import_importer,
+			import_status, import_intelligent_done,
+			import_assistant);
+	} else
+		import_done (ei, import_assistant);
+}
+
+static void
+import_cancelled (EImportAssistant *assistant)
+{
+	e_import_cancel (
+		assistant->priv->import,
+		assistant->priv->import_target,
+		assistant->priv->import_importer);
+}
+
+static void
+prepare_file_page (GtkAssistant *assistant,
+                   GtkWidget *vbox)
+{
+	EImportAssistantPrivate *priv;
+	GSList *importers, *imp;
+	GtkListStore *store;
+	ImportFilePage *page;
+
+	priv = E_IMPORT_ASSISTANT_GET_PRIVATE (assistant);
+	page = &priv->file_page;
+
+	if (page->target != NULL) {
+		filename_changed (priv->file_page.filename, assistant);
+		return;
+	}
+
+	page->target = e_import_target_new_uri (priv->import, NULL, NULL);
+	importers = e_import_get_importers (priv->import, (EImportTarget *) page->target);
+
+	store = GTK_LIST_STORE (gtk_combo_box_get_model (GTK_COMBO_BOX (page->filetype)));
+	gtk_list_store_clear (store);
+
+	for (imp = importers; imp; imp = imp->next) {
+		GtkTreeIter iter;
+		EImportImporter *eii = imp->data;
+
+		gtk_list_store_append (store, &iter);
+		gtk_list_store_set (
+			store, &iter,
+			0, eii->name,
+			1, TRUE,
+			2, eii,
+			-1);
+	}
+
+	g_slist_free (importers);
+
+	gtk_combo_box_set_active (GTK_COMBO_BOX (page->filetype), 0);
+
+	filename_changed (priv->file_page.filename, assistant);
+
+	g_signal_connect (
+		page->filetype, "changed",
+		G_CALLBACK (filetype_changed_cb), assistant);
+}
+
+static GtkWidget *
+create_importer_control (EImport *import,
+                         EImportTarget *target,
+                         EImportImporter *importer)
+{
+	GtkWidget *control;
+
+	control = e_import_get_widget (import, target, importer);
+	if (control == NULL) {
+		/* Coding error, not needed for translators */
+		control = gtk_label_new (
+			"** PLUGIN ERROR ** No settings for importer");
+		gtk_widget_show (control);
+	}
+
+	return control;
+}
+
+static gboolean
+prepare_destination_page (GtkAssistant *assistant,
+                          GtkWidget *vbox)
+{
+	EImportAssistantPrivate *priv;
+	ImportDestinationPage *page;
+
+	priv = E_IMPORT_ASSISTANT_GET_PRIVATE (assistant);
+	page = &priv->destination_page;
+
+	if (page->control)
+		gtk_container_remove (GTK_CONTAINER (vbox), page->control);
+
+	page->control = create_importer_control (
+		priv->import, (EImportTarget *)
+		priv->file_page.target, priv->file_page.importer);
+
+	gtk_box_pack_start (GTK_BOX (vbox), page->control, TRUE, TRUE, 0);
+	gtk_assistant_set_page_complete (assistant, vbox, TRUE);
+
+	return FALSE;
+}
+
+static void
+prepare_progress_page (GtkAssistant *assistant,
+                       GtkWidget *vbox)
+{
+	EImportAssistantPrivate *priv;
+	EImportCompleteFunc done = NULL;
+	ImportSelectionPage *page;
+	GtkWidget *cancel_button;
+	gboolean intelligent_import;
+	gboolean is_simple = FALSE;
+
+	priv = E_IMPORT_ASSISTANT_GET_PRIVATE (assistant);
+	page = &priv->selection_page;
+
+	/* Because we're a GTK_ASSISTANT_PAGE_PROGRESS, this will
+	 * prevent the assistant window from being closed via window
+	 * manager decorations while importing. */
+	gtk_assistant_commit (assistant);
+
+	/* Install a custom "Cancel Import" button. */
+	cancel_button = gtk_button_new_with_mnemonic (_("_Cancel Import"));
+	gtk_button_set_image (
+		GTK_BUTTON (cancel_button),
+		gtk_image_new_from_stock (
+		GTK_STOCK_CANCEL, GTK_ICON_SIZE_BUTTON));
+	g_signal_connect_swapped (
+		cancel_button, "clicked",
+		G_CALLBACK (import_cancelled), assistant);
+	gtk_assistant_add_action_widget (assistant, cancel_button);
+	gtk_widget_show (cancel_button);
+
+	g_object_get (assistant, "is-simple", &is_simple, NULL);
+
+	intelligent_import = is_simple ? FALSE : gtk_toggle_button_get_active (
+		GTK_TOGGLE_BUTTON (priv->type_page.intelligent));
+
+	if (is_simple) {
+		priv->import_importer = priv->simple_page.importer;
+		priv->import_target = (EImportTarget *) priv->simple_page.target;
+		done = import_simple_done;
+	} else if (intelligent_import) {
+		page->current = page->importers;
+		if (page->current) {
+			priv->import_target = (EImportTarget *) page->target;
+			priv->import_importer = page->current->data;
+			done = import_intelligent_done;
+		}
+	} else {
+		if (priv->file_page.importer) {
+			priv->import_importer = priv->file_page.importer;
+			priv->import_target = (EImportTarget *) priv->file_page.target;
+			done = import_done;
+		}
+	}
+
+	if (done)
+		e_import_import (
+			priv->import, priv->import_target,
+			priv->import_importer, import_status,
+			done, assistant);
+	else
+		import_assistant_emit_finished (E_IMPORT_ASSISTANT (assistant));
+}
+
+static void
+simple_filetype_changed_cb (GtkComboBox *combo_box,
+                            GtkAssistant *assistant)
+{
+	EImportAssistantPrivate *priv;
+	ImportSimplePage *page;
+	GtkTreeModel *model;
+	GtkTreeIter iter;
+	GtkWidget *vbox;
+	GtkWidget *control;
+
+	priv = E_IMPORT_ASSISTANT_GET_PRIVATE (assistant);
+	page = &priv->simple_page;
+
+	g_return_if_fail (gtk_combo_box_get_active_iter (combo_box, &iter));
+
+	model = gtk_combo_box_get_model (combo_box);
+	gtk_tree_model_get (model, &iter, 2, &page->importer, -1);
+
+	vbox = g_object_get_data (G_OBJECT (combo_box), "page-vbox");
+	g_return_if_fail (vbox != NULL);
+
+	if (page->control)
+		gtk_widget_destroy (page->control);
+	page->has_preview = FALSE;
+
+	control = e_import_get_preview_widget (
+		priv->import, (EImportTarget *)
+		page->target, page->importer);
+	if (control) {
+		page->has_preview = TRUE;
+		gtk_widget_set_size_request (control, 440, 360);
+	} else
+		control = create_importer_control (
+			priv->import, (EImportTarget *)
+			page->target, page->importer);
+
+	page->control = gtk_alignment_new (0.0, 0.0, 1.0, 1.0);
+	gtk_widget_show (page->control);
+	gtk_container_add (GTK_CONTAINER (page->control), control);
+
+	gtk_box_pack_start (GTK_BOX (vbox), page->control, TRUE, TRUE, 0);
+	gtk_assistant_set_page_complete (assistant, vbox, TRUE);
+}
+
+static void
+prepare_simple_page (GtkAssistant *assistant,
+                     GtkWidget *vbox)
+{
+	EImportAssistantPrivate *priv;
+	GSList *importers, *imp;
+	GtkListStore *store;
+	ImportSimplePage *page;
+	gchar *uri;
+
+	priv = E_IMPORT_ASSISTANT_GET_PRIVATE (assistant);
+	page = &priv->simple_page;
+
+	g_return_if_fail (priv->fileuris != NULL);
+
+	if (page->target != NULL) {
+		return;
+	}
+
+	uri = g_ptr_array_remove_index (priv->fileuris, 0);
+	page->target = e_import_target_new_uri (priv->import, uri, NULL);
+	g_free (uri);
+	importers = e_import_get_importers (priv->import, (EImportTarget *) page->target);
+
+	store = GTK_LIST_STORE (gtk_combo_box_get_model (GTK_COMBO_BOX (page->filetype)));
+	gtk_list_store_clear (store);
+
+	for (imp = importers; imp; imp = imp->next) {
+		GtkTreeIter iter;
+		EImportImporter *eii = imp->data;
+
+		gtk_list_store_append (store, &iter);
+		gtk_list_store_set (
+			store, &iter,
+			0, eii->name,
+			1, TRUE,
+			2, eii,
+			-1);
+	}
+
+	gtk_combo_box_set_active (GTK_COMBO_BOX (page->filetype), 0);
+	g_object_set_data (G_OBJECT (page->filetype), "page-vbox", vbox);
+
+	simple_filetype_changed_cb (GTK_COMBO_BOX (page->filetype), assistant);
+
+	g_signal_connect (
+		page->filetype, "changed",
+		G_CALLBACK (simple_filetype_changed_cb), assistant);
+
+	if (gtk_tree_model_iter_n_children (GTK_TREE_MODEL (store), NULL) == 1) {
+		gchar *title;
+
+		/* only one importer found, make it even simpler */
+		gtk_label_set_text (
+			GTK_LABEL (page->actionlabel),
+			page->has_preview ?
+				_("Preview data to be imported") :
+				_("Choose the destination for this import"));
+
+		gtk_widget_hide (page->filetypetable);
+
+		title = g_strconcat (
+			_("Import Data"), " - ",
+			((EImportImporter *) importers->data)->name, NULL);
+		gtk_assistant_set_page_title (assistant, vbox, title);
+		g_free (title);
+	} else {
+		/* multiple importers found, be able to choose from them */
+		gtk_label_set_text (
+			GTK_LABEL (page->actionlabel),
+			_("Select what type of file you "
+			"want to import from the list."));
+
+		gtk_widget_show (page->filetypetable);
+
+		gtk_assistant_set_page_title (assistant, vbox, _("Import Data"));
+	}
+
+	g_slist_free (importers);
+}
+
+static gboolean
+prepare_simple_destination_page (GtkAssistant *assistant,
+                          GtkWidget *vbox)
+{
+	EImportAssistantPrivate *priv;
+	ImportDestinationPage *page;
+	ImportSimplePage *simple_page;
+
+	priv = E_IMPORT_ASSISTANT_GET_PRIVATE (assistant);
+	page = &priv->destination_page;
+	simple_page = &priv->simple_page;
+
+	if (page->control)
+		gtk_container_remove (GTK_CONTAINER (vbox), page->control);
+
+	page->control = create_importer_control (
+		priv->import, (EImportTarget *)
+		simple_page->target, simple_page->importer);
+
+	gtk_box_pack_start (GTK_BOX (vbox), page->control, TRUE, TRUE, 0);
+	gtk_assistant_set_page_complete (assistant, vbox, TRUE);
+
+	return FALSE;
+}
+
+static gint
+forward_cb (gint current_page,
+            EImportAssistant *import_assistant)
+{
+	GtkToggleButton *toggle_button;
+	gboolean is_simple = FALSE;
+
+	g_object_get (import_assistant, "is-simple", &is_simple, NULL);
+
+	if (is_simple) {
+		if (!import_assistant->priv->simple_page.has_preview)
+			current_page++;
+
+		return current_page + 1;
+	}
+
+	toggle_button = GTK_TOGGLE_BUTTON (
+		import_assistant->priv->type_page.intelligent);
+
+	switch (current_page) {
+		case PAGE_INTELI_OR_DIRECT:
+			if (gtk_toggle_button_get_active (toggle_button))
+				return PAGE_INTELI_SOURCE;
+			else
+				return PAGE_FILE_CHOOSE;
+		case PAGE_INTELI_SOURCE:
+			return PAGE_FINISH;
+	}
+
+	return current_page + 1;
+}
+
+static gboolean
+set_import_uris (EImportAssistant *assistant,
+                 const gchar * const *uris)
+{
+	EImportAssistantPrivate *priv;
+	GPtrArray *fileuris = NULL;
+	gint i;
+
+	g_return_val_if_fail (assistant != NULL, FALSE);
+	g_return_val_if_fail (assistant->priv != NULL, FALSE);
+	g_return_val_if_fail (assistant->priv->import != NULL, FALSE);
+	g_return_val_if_fail (uris != NULL, FALSE);
+
+	priv = E_IMPORT_ASSISTANT_GET_PRIVATE (assistant);
+
+	for (i = 0; uris[i]; i++) {
+		const gchar *uri = uris[i];
+		gchar *filename;
+
+		filename = g_filename_from_uri (uri, NULL, NULL);
+		if (!filename)
+			filename = g_strdup (uri);
+
+		if (filename && *filename &&
+		    g_file_test (filename, G_FILE_TEST_IS_REGULAR)) {
+			gchar *furi;
+
+			if (!g_path_is_absolute (filename)) {
+				gchar *tmp, *curr;
+
+				curr = g_get_current_dir ();
+				tmp = g_build_filename (curr, filename, NULL);
+				g_free (curr);
+
+				g_free (filename);
+				filename = tmp;
+			}
+
+			if (fileuris == NULL) {
+				EImportTargetURI *target;
+				GSList *importers;
+
+				furi = g_filename_to_uri (filename, NULL, NULL);
+				target = e_import_target_new_uri (priv->import, furi, NULL);
+				importers = e_import_get_importers (
+					priv->import, (EImportTarget *) target);
+
+				if (importers != NULL) {
+					/* there is at least one importer which can be used,
+					 * thus there can be done an import */
+					fileuris = g_ptr_array_new ();
+				}
+
+				g_slist_free (importers);
+				e_import_target_free (priv->import, target);
+				g_free (furi);
+
+				if (fileuris == NULL) {
+					g_free (filename);
+					break;
+				}
+			}
+
+			furi = g_filename_to_uri (filename, NULL, NULL);
+			if (furi)
+				g_ptr_array_add (fileuris, furi);
+		}
+
+		g_free (filename);
+	}
+
+	if (fileuris != NULL) {
+		priv->fileuris = fileuris;
+	}
+
+	return fileuris != NULL;
+}
+
+static void
+import_assistant_set_property (GObject *object,
+                               guint property_id,
+                               const GValue *value,
+                               GParamSpec *pspec)
+{
+	EImportAssistantPrivate *priv;
+
+	priv = E_IMPORT_ASSISTANT_GET_PRIVATE (object);
+
+	switch (property_id) {
+		case PROP_IS_SIMPLE:
+			priv->is_simple = g_value_get_boolean (value);
+		return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+import_assistant_get_property (GObject *object,
+                               guint property_id,
+                               GValue *value,
+                               GParamSpec *pspec)
+{
+	EImportAssistantPrivate *priv;
+
+	priv = E_IMPORT_ASSISTANT_GET_PRIVATE (object);
+
+	switch (property_id) {
+		case PROP_IS_SIMPLE:
+			g_value_set_boolean (value, priv->is_simple);
+			return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+import_assistant_dispose (GObject *object)
+{
+	EImportAssistantPrivate *priv;
+
+	priv = E_IMPORT_ASSISTANT_GET_PRIVATE (object);
+
+	if (priv->file_page.target != NULL) {
+		e_import_target_free (
+			priv->import, (EImportTarget *)
+			priv->file_page.target);
+		priv->file_page.target = NULL;
+	}
+
+	if (priv->selection_page.target != NULL) {
+		e_import_target_free (
+			priv->import, (EImportTarget *)
+			priv->selection_page.target);
+		priv->selection_page.target = NULL;
+	}
+
+	if (priv->simple_page.target != NULL) {
+		e_import_target_free (
+			priv->import, (EImportTarget *)
+			priv->simple_page.target);
+		priv->simple_page.target = NULL;
+	}
+
+	if (priv->import != NULL) {
+		g_object_unref (priv->import);
+		priv->import = NULL;
+	}
+
+	if (priv->fileuris != NULL) {
+		g_ptr_array_foreach (priv->fileuris, (GFunc) g_free, NULL);
+		g_ptr_array_free (priv->fileuris, TRUE);
+		priv->fileuris = NULL;
+	}
+
+	/* Chain up to parent's dispose() method. */
+	G_OBJECT_CLASS (e_import_assistant_parent_class)->dispose (object);
+}
+
+static void
+import_assistant_finalize (GObject *object)
+{
+	EImportAssistantPrivate *priv;
+
+	priv = E_IMPORT_ASSISTANT_GET_PRIVATE (object);
+
+	g_slist_free (priv->selection_page.importers);
+
+	/* Chain up to parent's finalize() method. */
+	G_OBJECT_CLASS (e_import_assistant_parent_class)->finalize (object);
+}
+
+static gboolean
+import_assistant_key_press_event (GtkWidget *widget,
+                                  GdkEventKey *event)
+{
+	GtkWidgetClass *parent_class;
+
+	if (event->keyval == GDK_KEY_Escape) {
+		g_signal_emit_by_name (widget, "cancel");
+		return TRUE;
+	}
+
+	/* Chain up to parent's key_press_event () method. */
+	parent_class = GTK_WIDGET_CLASS (e_import_assistant_parent_class);
+	return parent_class->key_press_event (widget, event);
+}
+
+static void
+import_assistant_prepare (GtkAssistant *assistant,
+                          GtkWidget *page)
+{
+	gint page_no = gtk_assistant_get_current_page (assistant);
+	gboolean is_simple = FALSE;
+
+	g_object_get (assistant, "is-simple", &is_simple, NULL);
+
+	if (is_simple) {
+		if (page_no == 0) {
+			prepare_simple_page (assistant, page);
+		} else if (page_no == 1) {
+			prepare_simple_destination_page (assistant, page);
+		} else if (page_no == 2) {
+			prepare_progress_page (assistant, page);
+		}
+
+		return;
+	}
+
+	switch (page_no) {
+		case PAGE_INTELI_SOURCE:
+			prepare_intelligent_page (assistant, page);
+			break;
+		case PAGE_FILE_CHOOSE:
+			prepare_file_page (assistant, page);
+			break;
+		case PAGE_FILE_DEST:
+			prepare_destination_page (assistant, page);
+			break;
+		case PAGE_PROGRESS:
+			prepare_progress_page (assistant, page);
+			break;
+		default:
+			break;
+	}
+}
+
+static void
+e_import_assistant_class_init (EImportAssistantClass *class)
+{
+	GObjectClass *object_class;
+	GtkWidgetClass *widget_class;
+	GtkAssistantClass *assistant_class;
+
+	g_type_class_add_private (class, sizeof (EImportAssistantPrivate));
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->dispose = import_assistant_dispose;
+	object_class->finalize = import_assistant_finalize;
+	object_class->set_property = import_assistant_set_property;
+	object_class->get_property = import_assistant_get_property;
+
+	widget_class = GTK_WIDGET_CLASS (class);
+	widget_class->key_press_event = import_assistant_key_press_event;
+
+	assistant_class = GTK_ASSISTANT_CLASS (class);
+	assistant_class->prepare = import_assistant_prepare;
+
+	g_object_class_install_property (
+		object_class,
+		PROP_IS_SIMPLE,
+		g_param_spec_boolean (
+			"is-simple",
+			NULL,
+			NULL,
+			FALSE,
+			G_PARAM_READWRITE |
+			G_PARAM_CONSTRUCT));
+
+	signals[FINISHED] = g_signal_new (
+		"finished",
+		G_TYPE_FROM_CLASS (class),
+		G_SIGNAL_RUN_LAST,
+		0, NULL, NULL,
+		g_cclosure_marshal_VOID__VOID,
+		G_TYPE_NONE, 0);
+}
+
+static void
+import_assistant_construct (EImportAssistant *import_assistant)
+{
+	GtkAssistant *assistant;
+	GtkWidget *page;
+
+	assistant = GTK_ASSISTANT (import_assistant);
+
+	import_assistant->priv->import =
+		e_import_new ("org.gnome.evolution.shell.importer");
+
+	gtk_window_set_position (GTK_WINDOW (assistant), GTK_WIN_POS_CENTER);
+	gtk_window_set_title (GTK_WINDOW (assistant), _("Evolution Import Assistant"));
+	gtk_window_set_default_size (GTK_WINDOW (assistant), 500, 330);
+
+	e_extensible_load_extensions (E_EXTENSIBLE (import_assistant));
+
+	if (import_assistant->priv->is_simple) {
+		/* simple import assistant page, URIs of files will be known later */
+		page = import_assistant_simple_page_init (import_assistant);
+
+		gtk_assistant_append_page (assistant, page);
+		gtk_assistant_set_page_title (assistant, page, _("Import Data"));
+		gtk_assistant_set_page_type (assistant, page, GTK_ASSISTANT_PAGE_CONTENT);
+
+		/* File destination page - when with preview*/
+		page = import_assistant_destination_page_init (import_assistant);
+
+		gtk_assistant_append_page (assistant, page);
+		gtk_assistant_set_page_title (assistant, page, _("Import Location"));
+		gtk_assistant_set_page_type (assistant, page, GTK_ASSISTANT_PAGE_CONTENT);
+	} else {
+		/* complex import assistant pages */
+
+		/* Start page */
+		page = gtk_label_new ("");
+		gtk_label_set_line_wrap (GTK_LABEL (page), TRUE);
+		gtk_misc_set_alignment (GTK_MISC (page), 0.0, 0.5);
+		gtk_misc_set_padding (GTK_MISC (page), 12, 12);
+		gtk_label_set_text (GTK_LABEL (page), _(
+			"Welcome to the Evolution Import Assistant.\n"
+			"With this assistant you will be guided through the "
+			"process of importing external files into Evolution."));
+		gtk_widget_show (page);
+
+		gtk_assistant_append_page (assistant, page);
+		gtk_assistant_set_page_title (
+			assistant, page, _("Evolution Import Assistant"));
+		gtk_assistant_set_page_type (
+			assistant, page, GTK_ASSISTANT_PAGE_INTRO);
+		gtk_assistant_set_page_complete (assistant, page, TRUE);
+
+		/* Intelligent or direct import page */
+		page = import_assistant_type_page_init (import_assistant);
+
+		gtk_assistant_append_page (assistant, page);
+		gtk_assistant_set_page_title (
+			assistant, page, _("Importer Type"));
+		gtk_assistant_set_page_type (
+			assistant, page, GTK_ASSISTANT_PAGE_CONTENT);
+		gtk_assistant_set_page_complete (assistant, page, TRUE);
+
+		/* Intelligent importer source page */
+		page = import_assistant_selection_page_init (import_assistant);
+
+		gtk_assistant_append_page (assistant, page);
+		gtk_assistant_set_page_title (
+			assistant, page, _("Select Information to Import"));
+		gtk_assistant_set_page_type (
+			assistant, page, GTK_ASSISTANT_PAGE_CONTENT);
+
+		/* File selection and file type page */
+		page = import_assistant_file_page_init (import_assistant);
+
+		gtk_assistant_append_page (assistant, page);
+		gtk_assistant_set_page_title (
+			assistant, page, _("Select a File"));
+		gtk_assistant_set_page_type (
+			assistant, page, GTK_ASSISTANT_PAGE_CONTENT);
+
+		/* File destination page */
+		page = import_assistant_destination_page_init (import_assistant);
+
+		gtk_assistant_append_page (assistant, page);
+		gtk_assistant_set_page_title (
+			assistant, page, _("Import Location"));
+		gtk_assistant_set_page_type (
+			assistant, page, GTK_ASSISTANT_PAGE_CONTENT);
+
+		/* Finish page */
+		page = gtk_label_new ("");
+		gtk_misc_set_alignment (GTK_MISC (page), 0.5, 0.5);
+		gtk_label_set_text (
+			GTK_LABEL (page), _("Click \"Apply\" to "
+			"begin importing the file into Evolution."));
+		gtk_widget_show (page);
+
+		gtk_assistant_append_page (assistant, page);
+		gtk_assistant_set_page_title (assistant, page, _("Import Data"));
+		gtk_assistant_set_page_type (assistant, page, GTK_ASSISTANT_PAGE_CONFIRM);
+		gtk_assistant_set_page_complete (assistant, page, TRUE);
+	}
+
+	/* Progress Page */
+	page = import_assistant_progress_page_init (import_assistant);
+
+	gtk_assistant_append_page (assistant, page);
+	gtk_assistant_set_page_title (assistant, page, _("Import Data"));
+	gtk_assistant_set_page_type (assistant, page, GTK_ASSISTANT_PAGE_PROGRESS);
+	gtk_assistant_set_page_complete (assistant, page, TRUE);
+
+	gtk_assistant_set_forward_page_func (
+		assistant, (GtkAssistantPageFunc)
+		forward_cb, import_assistant, NULL);
+
+	gtk_assistant_update_buttons_state (assistant);
+}
+
+static void
+e_import_assistant_init (EImportAssistant *import_assistant)
+{
+	import_assistant->priv =
+		E_IMPORT_ASSISTANT_GET_PRIVATE (import_assistant);
+}
+
+GtkWidget *
+e_import_assistant_new (GtkWindow *parent)
+{
+	GtkWidget *assistant;
+
+	assistant = g_object_new (
+			E_TYPE_IMPORT_ASSISTANT,
+			"transient-for", parent, NULL);
+
+	import_assistant_construct (E_IMPORT_ASSISTANT (assistant));
+
+	return assistant;
+}
+
+/* Creates a simple assistant with only page to choose an import type
+ * and where to import, and then finishes. It shows import types based
+ * on the first valid URI given.
+ *
+ * Returns: EImportAssistant widget.
+ */
+GtkWidget *
+e_import_assistant_new_simple (GtkWindow *parent,
+                               const gchar * const *uris)
+{
+	GtkWidget *assistant;
+
+	assistant = g_object_new (
+		E_TYPE_IMPORT_ASSISTANT,
+		"transient-for", parent,
+		"is-simple", TRUE,
+		NULL);
+
+	import_assistant_construct (E_IMPORT_ASSISTANT (assistant));
+
+	if (!set_import_uris (E_IMPORT_ASSISTANT (assistant), uris)) {
+		g_object_ref_sink (assistant);
+		g_object_unref (assistant);
+		return NULL;
+	}
+
+	return assistant;
+}
diff --git a/e-util/e-import-assistant.h b/e-util/e-import-assistant.h
new file mode 100644
index 0000000..0ee580e
--- /dev/null
+++ b/e-util/e-import-assistant.h
@@ -0,0 +1,72 @@
+/*
+ * e-import-assistant.h
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_IMPORT_ASSISTANT_H
+#define E_IMPORT_ASSISTANT_H
+
+#include <gtk/gtk.h>
+
+/* Standard GObject macros */
+#define E_TYPE_IMPORT_ASSISTANT \
+	(e_import_assistant_get_type ())
+#define E_IMPORT_ASSISTANT(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_IMPORT_ASSISTANT, EImportAssistant))
+#define E_IMPORT_ASSISTANT_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_IMPORT_ASSISTANT, EImportAssistantClass))
+#define E_IS_IMPORT_ASSISTANT(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_IMPORT_ASSISTANT))
+#define E_IS_IMPORT_ASSISTANT_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_IMPORT_ASSISTANT))
+#define E_IMPORT_ASSISTANT_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_IMPORT_ASSISTANT, EImportAssistantClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EImportAssistant EImportAssistant;
+typedef struct _EImportAssistantClass EImportAssistantClass;
+typedef struct _EImportAssistantPrivate EImportAssistantPrivate;
+
+struct _EImportAssistant {
+	GtkAssistant parent;
+	EImportAssistantPrivate *priv;
+};
+
+struct _EImportAssistantClass {
+	GtkAssistantClass parent_class;
+};
+
+GType		e_import_assistant_get_type	(void);
+GtkWidget *	e_import_assistant_new		(GtkWindow *parent);
+GtkWidget *	e_import_assistant_new_simple	(GtkWindow *parent,
+						 const gchar * const *uris);
+
+G_END_DECLS
+
+#endif /* E_IMPORT_ASSISTANT_H */
diff --git a/e-util/e-import.h b/e-util/e-import.h
index 9d0eb51..69d40cf 100644
--- a/e-util/e-import.h
+++ b/e-util/e-import.h
@@ -21,6 +21,10 @@
  *
  */
 
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
 #ifndef E_IMPORT_H
 #define E_IMPORT_H
 
@@ -244,7 +248,7 @@ EImportTargetHome *
 /* To implement a basic import plugin, you just need to subclass
  * this and initialise the class target type tables */
 
-#include "e-util/e-plugin.h"
+#include <e-util/e-plugin.h>
 
 /* Standard GObject macros */
 #define E_TYPE_IMPORT_HOOK \
diff --git a/e-util/e-interval-chooser.c b/e-util/e-interval-chooser.c
new file mode 100644
index 0000000..70e90bd
--- /dev/null
+++ b/e-util/e-interval-chooser.c
@@ -0,0 +1,214 @@
+/*
+ * e-interval-chooser.c
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#include "e-interval-chooser.h"
+
+#include <config.h>
+#include <glib/gi18n-lib.h>
+
+#include "e-util-enums.h"
+
+#define E_INTERVAL_CHOOSER_GET_PRIVATE(obj) \
+	(G_TYPE_INSTANCE_GET_PRIVATE \
+	((obj), E_TYPE_INTERVAL_CHOOSER, EIntervalChooserPrivate))
+
+#define MINUTES_PER_HOUR	(60)
+#define MINUTES_PER_DAY		(MINUTES_PER_HOUR * 24)
+
+struct _EIntervalChooserPrivate {
+	GtkComboBox *combo_box;		/* not referenced */
+	GtkSpinButton *spin_button;	/* not referenced */
+};
+
+enum {
+	PROP_0,
+	PROP_INTERVAL_MINUTES
+};
+
+G_DEFINE_TYPE (
+	EIntervalChooser,
+	e_interval_chooser,
+	GTK_TYPE_BOX)
+
+static void
+interval_chooser_notify_interval (GObject *object)
+{
+	g_object_notify (object, "interval-minutes");
+}
+
+static void
+interval_chooser_set_property (GObject *object,
+                               guint property_id,
+                               const GValue *value,
+                               GParamSpec *pspec)
+{
+	switch (property_id) {
+		case PROP_INTERVAL_MINUTES:
+			e_interval_chooser_set_interval_minutes (
+				E_INTERVAL_CHOOSER (object),
+				g_value_get_uint (value));
+			return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+interval_chooser_get_property (GObject *object,
+                               guint property_id,
+                               GValue *value,
+                               GParamSpec *pspec)
+{
+	switch (property_id) {
+		case PROP_INTERVAL_MINUTES:
+			g_value_set_uint (
+				value,
+				e_interval_chooser_get_interval_minutes (
+				E_INTERVAL_CHOOSER (object)));
+			return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+e_interval_chooser_class_init (EIntervalChooserClass *class)
+{
+	GObjectClass *object_class;
+
+	g_type_class_add_private (class, sizeof (EIntervalChooserPrivate));
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->set_property = interval_chooser_set_property;
+	object_class->get_property = interval_chooser_get_property;
+
+	g_object_class_install_property (
+		object_class,
+		PROP_INTERVAL_MINUTES,
+		g_param_spec_uint (
+			"interval-minutes",
+			"Interval in Minutes",
+			"Refresh interval in minutes",
+			0, G_MAXUINT, 60,
+			G_PARAM_READWRITE |
+			G_PARAM_CONSTRUCT |
+			G_PARAM_STATIC_STRINGS));
+}
+
+static void
+e_interval_chooser_init (EIntervalChooser *chooser)
+{
+	GtkWidget *widget;
+
+	chooser->priv = E_INTERVAL_CHOOSER_GET_PRIVATE (chooser);
+
+	gtk_orientable_set_orientation (
+		GTK_ORIENTABLE (chooser), GTK_ORIENTATION_HORIZONTAL);
+
+	gtk_box_set_spacing (GTK_BOX (chooser), 6);
+
+	widget = gtk_spin_button_new_with_range (0, G_MAXUINT, 1);
+	gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (widget), TRUE);
+	gtk_spin_button_set_update_policy (
+		GTK_SPIN_BUTTON (widget), GTK_UPDATE_IF_VALID);
+	gtk_box_pack_start (GTK_BOX (chooser), widget, TRUE, TRUE, 0);
+	chooser->priv->spin_button = GTK_SPIN_BUTTON (widget);
+	gtk_widget_show (widget);
+
+	g_signal_connect_swapped (
+		widget, "notify::value",
+		G_CALLBACK (interval_chooser_notify_interval), chooser);
+
+	widget = gtk_combo_box_text_new ();
+	gtk_combo_box_text_append_text (
+		GTK_COMBO_BOX_TEXT (widget), _("minutes"));
+	gtk_combo_box_text_append_text (
+		GTK_COMBO_BOX_TEXT (widget), _("hours"));
+	gtk_combo_box_text_append_text (
+		GTK_COMBO_BOX_TEXT (widget), _("days"));
+	gtk_box_pack_start (GTK_BOX (chooser), widget, FALSE, FALSE, 0);
+	chooser->priv->combo_box = GTK_COMBO_BOX (widget);
+	gtk_widget_show (widget);
+
+	g_signal_connect_swapped (
+		widget, "notify::active",
+		G_CALLBACK (interval_chooser_notify_interval), chooser);
+}
+
+GtkWidget *
+e_interval_chooser_new (void)
+{
+	return g_object_new (E_TYPE_INTERVAL_CHOOSER, NULL);
+}
+
+guint
+e_interval_chooser_get_interval_minutes (EIntervalChooser *chooser)
+{
+	EDurationType units;
+	gdouble interval_minutes;
+
+	g_return_val_if_fail (E_IS_SOURCE_CONFIG_REFRESH (chooser), 0);
+
+	units = gtk_combo_box_get_active (chooser->priv->combo_box);
+
+	interval_minutes = gtk_spin_button_get_value (
+		chooser->priv->spin_button);
+
+	switch (units) {
+		case E_DURATION_HOURS:
+			interval_minutes *= MINUTES_PER_HOUR;
+			break;
+		case E_DURATION_DAYS:
+			interval_minutes *= MINUTES_PER_DAY;
+			break;
+		default:
+			break;
+	}
+
+	return (guint) interval_minutes;
+}
+
+void
+e_interval_chooser_set_interval_minutes (EIntervalChooser *chooser,
+                                         guint interval_minutes)
+{
+	EDurationType units;
+
+	g_return_if_fail (E_IS_SOURCE_CONFIG_REFRESH (chooser));
+
+	if (interval_minutes == 0) {
+		units = E_DURATION_MINUTES;
+	} else if (interval_minutes % MINUTES_PER_DAY == 0) {
+		interval_minutes /= MINUTES_PER_DAY;
+		units = E_DURATION_DAYS;
+	} else if (interval_minutes % MINUTES_PER_HOUR == 0) {
+		interval_minutes /= MINUTES_PER_HOUR;
+		units = E_DURATION_HOURS;
+	} else {
+		units = E_DURATION_MINUTES;
+	}
+
+	g_object_freeze_notify (G_OBJECT (chooser));
+
+	gtk_combo_box_set_active (chooser->priv->combo_box, units);
+
+	gtk_spin_button_set_value (
+		chooser->priv->spin_button, interval_minutes);
+
+	g_object_thaw_notify (G_OBJECT (chooser));
+}
diff --git a/e-util/e-interval-chooser.h b/e-util/e-interval-chooser.h
new file mode 100644
index 0000000..477ae18
--- /dev/null
+++ b/e-util/e-interval-chooser.h
@@ -0,0 +1,72 @@
+/*
+ * e-interval-chooser.h
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_INTERVAL_CHOOSER_H
+#define E_INTERVAL_CHOOSER_H
+
+#include <gtk/gtk.h>
+
+/* Standard GObject macros */
+#define E_TYPE_INTERVAL_CHOOSER \
+	(e_interval_chooser_get_type ())
+#define E_INTERVAL_CHOOSER(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_INTERVAL_CHOOSER, EIntervalChooser))
+#define E_INTERVAL_CHOOSER_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_INTERVAL_CHOOSER, EIntervalChooserClass))
+#define E_IS_SOURCE_CONFIG_REFRESH(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_INTERVAL_CHOOSER))
+#define E_IS_SOURCE_CONFIG_REFRESH_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_INTERVAL_CHOOSER))
+#define E_INTERVAL_CHOOSER_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_INTERVAL_CHOOSER, EIntervalChooserClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EIntervalChooser EIntervalChooser;
+typedef struct _EIntervalChooserClass EIntervalChooserClass;
+typedef struct _EIntervalChooserPrivate EIntervalChooserPrivate;
+
+struct _EIntervalChooser {
+	GtkBox parent;
+	EIntervalChooserPrivate *priv;
+};
+
+struct _EIntervalChooserClass {
+	GtkBoxClass parent_class;
+};
+
+GType		e_interval_chooser_get_type	(void) G_GNUC_CONST;
+GtkWidget *	e_interval_chooser_new		(void);
+guint		e_interval_chooser_get_interval_minutes
+						(EIntervalChooser *refresh);
+void		e_interval_chooser_set_interval_minutes
+						(EIntervalChooser *refresh,
+						 guint interval_minutes);
+
+G_END_DECLS
+
+#endif /* E_INTERVAL_CHOOSER_H */
diff --git a/widgets/misc/e-mail-identity-combo-box.c b/e-util/e-mail-identity-combo-box.c
similarity index 100%
rename from widgets/misc/e-mail-identity-combo-box.c
rename to e-util/e-mail-identity-combo-box.c
diff --git a/e-util/e-mail-identity-combo-box.h b/e-util/e-mail-identity-combo-box.h
new file mode 100644
index 0000000..8c395b3
--- /dev/null
+++ b/e-util/e-mail-identity-combo-box.h
@@ -0,0 +1,75 @@
+/*
+ * e-mail-identity-combo-box.h
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_MAIL_IDENTITY_COMBO_BOX_H
+#define E_MAIL_IDENTITY_COMBO_BOX_H
+
+#include <gtk/gtk.h>
+#include <libedataserver/libedataserver.h>
+
+/* Standard GObject macros */
+#define E_TYPE_MAIL_IDENTITY_COMBO_BOX \
+	(e_mail_identity_combo_box_get_type ())
+#define E_MAIL_IDENTITY_COMBO_BOX(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_MAIL_IDENTITY_COMBO_BOX, EMailIdentityComboBox))
+#define E_MAIL_IDENTITY_COMBO_BOX_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_MAIL_IDENTITY_COMBO_BOX, EMailIdentityComboBoxClass))
+#define E_IS_MAIL_IDENTITY_COMBO_BOX(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_MAIL_IDENTITY_COMBO_BOX))
+#define E_IS_MAIL_IDENTITY_COMBO_BOX_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_MAIL_IDENTITY_COMBO_BOX))
+#define E_MAIL_IDENTITY_COMBO_BOX_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_MAIL_IDENTITY_COMBO_BOX, EMailIdentityComboBoxClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EMailIdentityComboBox EMailIdentityComboBox;
+typedef struct _EMailIdentityComboBoxClass EMailIdentityComboBoxClass;
+typedef struct _EMailIdentityComboBoxPrivate EMailIdentityComboBoxPrivate;
+
+struct _EMailIdentityComboBox {
+	GtkComboBox parent;
+	EMailIdentityComboBoxPrivate *priv;
+};
+
+struct _EMailIdentityComboBoxClass {
+	GtkComboBoxClass parent_class;
+};
+
+GType		e_mail_identity_combo_box_get_type
+					(void) G_GNUC_CONST;
+GtkWidget *	e_mail_identity_combo_box_new
+					(ESourceRegistry *registry);
+void		e_mail_identity_combo_box_refresh
+					(EMailIdentityComboBox *combo_box);
+ESourceRegistry *
+		e_mail_identity_combo_box_get_registry
+					(EMailIdentityComboBox *combo_box);
+
+G_END_DECLS
+
+#endif /* E_MAIL_IDENTITY_COMBO_BOX_H */
diff --git a/widgets/misc/e-mail-signature-combo-box.c b/e-util/e-mail-signature-combo-box.c
similarity index 100%
rename from widgets/misc/e-mail-signature-combo-box.c
rename to e-util/e-mail-signature-combo-box.c
diff --git a/e-util/e-mail-signature-combo-box.h b/e-util/e-mail-signature-combo-box.h
new file mode 100644
index 0000000..d39ba96
--- /dev/null
+++ b/e-util/e-mail-signature-combo-box.h
@@ -0,0 +1,95 @@
+/*
+ * e-mail-signature-combo-box.h
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_MAIL_SIGNATURE_COMBO_BOX_H
+#define E_MAIL_SIGNATURE_COMBO_BOX_H
+
+#include <gtk/gtk.h>
+#include <libedataserver/libedataserver.h>
+
+/* Standard GObject macros */
+#define E_TYPE_MAIL_SIGNATURE_COMBO_BOX \
+	(e_mail_signature_combo_box_get_type ())
+#define E_MAIL_SIGNATURE_COMBO_BOX(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_MAIL_SIGNATURE_COMBO_BOX, EMailSignatureComboBox))
+#define E_MAIL_SIGNATURE_COMBO_BOX_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_MAIL_SIGNATURE_COMBO_BOX, EMailSignatureComboBoxClass))
+#define E_IS_MAIL_SIGNATURE_COMBO_BOX(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_MAIL_SIGNATURE_COMBO_BOX))
+#define E_IS_MAIL_SIGNATURE_COMBO_BOX_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_MAIL_SIGNATURE_COMBO_BOX))
+#define E_MAIL_SIGNATURE_COMBO_BOX_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_MAIL_SIGNATURE_COMBO_BOX, EMailSignatureComboBoxClass))
+
+#define E_MAIL_SIGNATURE_AUTOGENERATED_UID "autogenerated"
+
+G_BEGIN_DECLS
+
+typedef struct _EMailSignatureComboBox EMailSignatureComboBox;
+typedef struct _EMailSignatureComboBoxClass EMailSignatureComboBoxClass;
+typedef struct _EMailSignatureComboBoxPrivate EMailSignatureComboBoxPrivate;
+
+struct _EMailSignatureComboBox {
+	GtkComboBox parent;
+	EMailSignatureComboBoxPrivate *priv;
+};
+
+struct _EMailSignatureComboBoxClass {
+	GtkComboBoxClass parent_class;
+};
+
+GType		e_mail_signature_combo_box_get_type
+					(void) G_GNUC_CONST;
+GtkWidget *	e_mail_signature_combo_box_new
+					(ESourceRegistry *registry);
+void		e_mail_signature_combo_box_refresh
+					(EMailSignatureComboBox *combo_box);
+ESourceRegistry *
+		e_mail_signature_combo_box_get_registry
+					(EMailSignatureComboBox *combo_box);
+const gchar *	e_mail_signature_combo_box_get_identity_uid
+					(EMailSignatureComboBox *combo_box);
+void		e_mail_signature_combo_box_set_identity_uid
+					(EMailSignatureComboBox *combo_box,
+					 const gchar *identity_uid);
+void		e_mail_signature_combo_box_load_selected
+					(EMailSignatureComboBox *combo_box,
+					 gint io_priority,
+					 GCancellable *cancellable,
+					 GAsyncReadyCallback callback,
+					 gpointer user_data);
+gboolean	e_mail_signature_combo_box_load_selected_finish
+					(EMailSignatureComboBox *combo_box,
+					 GAsyncResult *result,
+					 gchar **contents,
+					 gsize *length,
+					 gboolean *is_html,
+					 GError **error);
+
+G_END_DECLS
+
+#endif /* E_MAIL_SIGNATURE_COMBO_BOX_H */
diff --git a/e-util/e-mail-signature-editor.c b/e-util/e-mail-signature-editor.c
new file mode 100644
index 0000000..961edf1
--- /dev/null
+++ b/e-util/e-mail-signature-editor.c
@@ -0,0 +1,914 @@
+/*
+ * e-mail-signature-editor.c
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#include "e-mail-signature-editor.h"
+
+#include <string.h>
+#include <glib/gi18n.h>
+
+#include "e-alert-bar.h"
+#include "e-alert-dialog.h"
+#include "e-alert-sink.h"
+#include "e-web-view-gtkhtml.h"
+
+#define E_MAIL_SIGNATURE_EDITOR_GET_PRIVATE(obj) \
+	(G_TYPE_INSTANCE_GET_PRIVATE \
+	((obj), E_TYPE_MAIL_SIGNATURE_EDITOR, EMailSignatureEditorPrivate))
+
+typedef struct _AsyncContext AsyncContext;
+
+struct _EMailSignatureEditorPrivate {
+	GtkActionGroup *action_group;
+	EFocusTracker *focus_tracker;
+	GCancellable *cancellable;
+	ESourceRegistry *registry;
+	ESource *source;
+	gchar *original_name;
+
+	GtkWidget *entry;		/* not referenced */
+	GtkWidget *alert_bar;		/* not referenced */
+};
+
+struct _AsyncContext {
+	ESource *source;
+	GCancellable *cancellable;
+	gchar *contents;
+	gsize length;
+};
+
+enum {
+	PROP_0,
+	PROP_FOCUS_TRACKER,
+	PROP_REGISTRY,
+	PROP_SOURCE
+};
+
+static const gchar *ui =
+"<ui>\n"
+"  <menubar name='main-menu'>\n"
+"    <placeholder name='pre-edit-menu'>\n"
+"      <menu action='file-menu'>\n"
+"        <menuitem action='save-and-close'/>\n"
+"        <separator/>"
+"        <menuitem action='close'/>\n"
+"      </menu>\n"
+"    </placeholder>\n"
+"  </menubar>\n"
+"  <toolbar name='main-toolbar'>\n"
+"    <placeholder name='pre-main-toolbar'>\n"
+"      <toolitem action='save-and-close'/>\n"
+"    </placeholder>\n"
+"  </toolbar>\n"
+"</ui>";
+
+/* Forward Declarations */
+static void	e_mail_signature_editor_alert_sink_init
+					(EAlertSinkInterface *interface);
+
+G_DEFINE_TYPE_WITH_CODE (
+	EMailSignatureEditor,
+	e_mail_signature_editor,
+	GTKHTML_TYPE_EDITOR,
+	G_IMPLEMENT_INTERFACE (
+		E_TYPE_ALERT_SINK,
+		e_mail_signature_editor_alert_sink_init))
+
+static void
+async_context_free (AsyncContext *async_context)
+{
+	if (async_context->source != NULL)
+		g_object_unref (async_context->source);
+
+	if (async_context->cancellable != NULL)
+		g_object_unref (async_context->cancellable);
+
+	g_free (async_context->contents);
+
+	g_slice_free (AsyncContext, async_context);
+}
+
+static void
+mail_signature_editor_loaded_cb (GObject *object,
+                                 GAsyncResult *result,
+                                 gpointer user_data)
+{
+	ESource *source;
+	EMailSignatureEditor *editor;
+	ESourceMailSignature *extension;
+	const gchar *extension_name;
+	const gchar *mime_type;
+	gchar *contents = NULL;
+	gboolean is_html;
+	GError *error = NULL;
+
+	source = E_SOURCE (object);
+	editor = E_MAIL_SIGNATURE_EDITOR (user_data);
+
+	e_source_mail_signature_load_finish (
+		source, result, &contents, NULL, &error);
+
+	/* Ignore cancellations. */
+	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+		g_warn_if_fail (contents == NULL);
+		g_object_unref (editor);
+		g_error_free (error);
+		return;
+
+	} else if (error != NULL) {
+		g_warn_if_fail (contents == NULL);
+		e_alert_submit (
+			E_ALERT_SINK (editor),
+			"widgets:no-load-signature",
+			error->message, NULL);
+		g_object_unref (editor);
+		g_error_free (error);
+		return;
+	}
+
+	g_return_if_fail (contents != NULL);
+
+	/* The load operation should have set the MIME type. */
+	extension_name = E_SOURCE_EXTENSION_MAIL_SIGNATURE;
+	extension = e_source_get_extension (source, extension_name);
+	mime_type = e_source_mail_signature_get_mime_type (extension);
+	is_html = (g_strcmp0 (mime_type, "text/html") == 0);
+
+	gtkhtml_editor_set_html_mode (GTKHTML_EDITOR (editor), is_html);
+
+	if (is_html) {
+		gtkhtml_editor_insert_html (
+			GTKHTML_EDITOR (editor), contents);
+	} else {
+		gtkhtml_editor_insert_text (
+			GTKHTML_EDITOR (editor), contents);
+
+		gtkhtml_editor_run_command (GTKHTML_EDITOR (editor), "cursor-position-save");
+		gtkhtml_editor_run_command (GTKHTML_EDITOR (editor), "select-all");
+		gtkhtml_editor_run_command (GTKHTML_EDITOR (editor), "style-pre");
+		gtkhtml_editor_run_command (GTKHTML_EDITOR (editor), "unselect-all");
+		gtkhtml_editor_run_command (GTKHTML_EDITOR (editor), "cursor-position-restore");
+	}
+
+	g_free (contents);
+
+	g_object_unref (editor);
+}
+
+static gboolean
+mail_signature_editor_delete_event_cb (EMailSignatureEditor *editor,
+                                       GdkEvent *event)
+{
+	GtkActionGroup *action_group;
+	GtkAction *action;
+
+	action_group = editor->priv->action_group;
+	action = gtk_action_group_get_action (action_group, "close");
+	gtk_action_activate (action);
+
+	return TRUE;
+}
+
+static void
+action_close_cb (GtkAction *action,
+                 EMailSignatureEditor *editor)
+{
+	gboolean something_changed = FALSE;
+	const gchar *original_name;
+	const gchar *signature_name;
+
+	original_name = editor->priv->original_name;
+	signature_name = gtk_entry_get_text (GTK_ENTRY (editor->priv->entry));
+
+	something_changed |= gtkhtml_editor_has_undo (GTKHTML_EDITOR (editor));
+	something_changed |= (strcmp (signature_name, original_name) != 0);
+
+	if (something_changed) {
+		gint response;
+
+		response = e_alert_run_dialog_for_args (
+			GTK_WINDOW (editor),
+			"widgets:ask-signature-changed", NULL);
+		if (response == GTK_RESPONSE_YES) {
+			GtkActionGroup *action_group;
+
+			action_group = editor->priv->action_group;
+			action = gtk_action_group_get_action (
+				action_group, "save-and-close");
+			gtk_action_activate (action);
+			return;
+		} else if (response == GTK_RESPONSE_CANCEL)
+			return;
+	}
+
+	gtk_widget_destroy (GTK_WIDGET (editor));
+}
+
+static void
+action_save_and_close_cb (GtkAction *action,
+                          EMailSignatureEditor *editor)
+{
+	GtkEntry *entry;
+	EAsyncClosure *closure;
+	GAsyncResult *result;
+	ESource *source;
+	gchar *display_name;
+	GError *error = NULL;
+
+	entry = GTK_ENTRY (editor->priv->entry);
+	source = e_mail_signature_editor_get_source (editor);
+
+	display_name = g_strstrip (g_strdup (gtk_entry_get_text (entry)));
+
+	/* Make sure the signature name is not blank. */
+	if (*display_name == '\0') {
+		e_alert_submit (
+			E_ALERT_SINK (editor),
+			"widgets:blank-signature", NULL);
+		gtk_widget_grab_focus (GTK_WIDGET (entry));
+		g_free (display_name);
+		return;
+	}
+
+	e_source_set_display_name (source, display_name);
+
+	g_free (display_name);
+
+	/* Cancel any ongoing load or save operations. */
+	if (editor->priv->cancellable != NULL) {
+		g_cancellable_cancel (editor->priv->cancellable);
+		g_object_unref (editor->priv->cancellable);
+	}
+
+	editor->priv->cancellable = g_cancellable_new ();
+
+	closure = e_async_closure_new ();
+
+	e_mail_signature_editor_commit (
+		editor, editor->priv->cancellable,
+		e_async_closure_callback, closure);
+
+	result = e_async_closure_wait (closure);
+
+	e_mail_signature_editor_commit_finish (editor, result, &error);
+
+	e_async_closure_free (closure);
+
+	/* Ignore cancellations. */
+	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+		g_error_free (error);
+
+	} else if (error != NULL) {
+		e_alert_submit (
+			E_ALERT_SINK (editor),
+			"widgets:no-save-signature",
+			error->message, NULL);
+		g_error_free (error);
+
+	/* Only destroy the editor if the save was successful. */
+	} else {
+		gtk_widget_destroy (GTK_WIDGET (editor));
+	}
+}
+
+static GtkActionEntry entries[] = {
+
+	{ "close",
+	  GTK_STOCK_CLOSE,
+	  N_("_Close"),
+	  "<Control>w",
+	  N_("Close"),
+	  G_CALLBACK (action_close_cb) },
+
+	{ "save-and-close",
+	  GTK_STOCK_SAVE,
+	  N_("_Save and Close"),
+	  "<Control>Return",
+	  N_("Save and Close"),
+	  G_CALLBACK (action_save_and_close_cb) },
+
+	{ "file-menu",
+	  NULL,
+	  N_("_File"),
+	  NULL,
+	  NULL,
+	  NULL }
+};
+
+static void
+mail_signature_editor_set_registry (EMailSignatureEditor *editor,
+                                    ESourceRegistry *registry)
+{
+	g_return_if_fail (E_IS_SOURCE_REGISTRY (registry));
+	g_return_if_fail (editor->priv->registry == NULL);
+
+	editor->priv->registry = g_object_ref (registry);
+}
+
+static void
+mail_signature_editor_set_source (EMailSignatureEditor *editor,
+                                  ESource *source)
+{
+	GDBusObject *dbus_object = NULL;
+	const gchar *extension_name;
+	GError *error = NULL;
+
+	g_return_if_fail (source == NULL || E_IS_SOURCE (source));
+	g_return_if_fail (editor->priv->source == NULL);
+
+	if (source != NULL)
+		dbus_object = e_source_ref_dbus_object (source);
+
+	/* Clone the source so we can make changes to it freely. */
+	editor->priv->source = e_source_new (dbus_object, NULL, &error);
+
+	if (dbus_object != NULL)
+		g_object_unref (dbus_object);
+
+	/* This should rarely fail.  If the file was loaded successfully
+	 * once then it should load successfully here as well, unless an
+	 * I/O error occurs. */
+	if (error != NULL) {
+		g_warning ("%s: %s", G_STRFUNC, error->message);
+		g_error_free (error);
+	}
+
+	/* Make sure the source has a mail signature extension. */
+	extension_name = E_SOURCE_EXTENSION_MAIL_SIGNATURE;
+	e_source_get_extension (editor->priv->source, extension_name);
+}
+
+static void
+mail_signature_editor_set_property (GObject *object,
+                                    guint property_id,
+                                    const GValue *value,
+                                    GParamSpec *pspec)
+{
+	switch (property_id) {
+		case PROP_REGISTRY:
+			mail_signature_editor_set_registry (
+				E_MAIL_SIGNATURE_EDITOR (object),
+				g_value_get_object (value));
+			return;
+
+		case PROP_SOURCE:
+			mail_signature_editor_set_source (
+				E_MAIL_SIGNATURE_EDITOR (object),
+				g_value_get_object (value));
+			return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+mail_signature_editor_get_property (GObject *object,
+                                    guint property_id,
+                                    GValue *value,
+                                    GParamSpec *pspec)
+{
+	switch (property_id) {
+		case PROP_FOCUS_TRACKER:
+			g_value_set_object (
+				value,
+				e_mail_signature_editor_get_focus_tracker (
+				E_MAIL_SIGNATURE_EDITOR (object)));
+			return;
+
+		case PROP_REGISTRY:
+			g_value_set_object (
+				value,
+				e_mail_signature_editor_get_registry (
+				E_MAIL_SIGNATURE_EDITOR (object)));
+			return;
+
+		case PROP_SOURCE:
+			g_value_set_object (
+				value,
+				e_mail_signature_editor_get_source (
+				E_MAIL_SIGNATURE_EDITOR (object)));
+			return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+mail_signature_editor_dispose (GObject *object)
+{
+	EMailSignatureEditorPrivate *priv;
+
+	priv = E_MAIL_SIGNATURE_EDITOR_GET_PRIVATE (object);
+
+	if (priv->action_group != NULL) {
+		g_object_unref (priv->action_group);
+		priv->action_group = NULL;
+	}
+
+	if (priv->focus_tracker != NULL) {
+		g_object_unref (priv->focus_tracker);
+		priv->focus_tracker = NULL;
+	}
+
+	if (priv->cancellable != NULL) {
+		g_cancellable_cancel (priv->cancellable);
+		g_object_unref (priv->cancellable);
+		priv->cancellable = NULL;
+	}
+
+	if (priv->registry != NULL) {
+		g_object_unref (priv->registry);
+		priv->registry = NULL;
+	}
+
+	if (priv->source != NULL) {
+		g_object_unref (priv->source);
+		priv->source = NULL;
+	}
+
+	/* Chain up to parent's dispose() method. */
+	G_OBJECT_CLASS (e_mail_signature_editor_parent_class)->
+		dispose (object);
+}
+
+static void
+mail_signature_editor_finalize (GObject *object)
+{
+	EMailSignatureEditorPrivate *priv;
+
+	priv = E_MAIL_SIGNATURE_EDITOR_GET_PRIVATE (object);
+
+	g_free (priv->original_name);
+
+	/* Chain up to parent's finalize() method. */
+	G_OBJECT_CLASS (e_mail_signature_editor_parent_class)->
+		finalize (object);
+}
+
+static void
+mail_signature_editor_constructed (GObject *object)
+{
+	EMailSignatureEditor *editor;
+	GtkActionGroup *action_group;
+	EFocusTracker *focus_tracker;
+	GtkhtmlEditor *gtkhtml_editor;
+	GtkUIManager *ui_manager;
+	GDBusObject *dbus_object;
+	ESource *source;
+	GtkAction *action;
+	GtkWidget *container;
+	GtkWidget *widget;
+	const gchar *display_name;
+	GError *error = NULL;
+
+	/* Chain up to parent's constructed() method. */
+	G_OBJECT_CLASS (e_mail_signature_editor_parent_class)->
+		constructed (object);
+
+	editor = E_MAIL_SIGNATURE_EDITOR (object);
+
+	gtkhtml_editor = GTKHTML_EDITOR (editor);
+	ui_manager = gtkhtml_editor_get_ui_manager (gtkhtml_editor);
+
+	/* Because we are loading from a hard-coded string, there is
+	 * no chance of I/O errors.  Failure here implies a malformed
+	 * UI definition.  Full stop. */
+	gtk_ui_manager_add_ui_from_string (ui_manager, ui, -1, &error);
+	if (error != NULL)
+		g_error ("%s", error->message);
+
+	action_group = gtk_action_group_new ("signature");
+	gtk_action_group_set_translation_domain (
+		action_group, GETTEXT_PACKAGE);
+	gtk_action_group_add_actions (
+		action_group, entries,
+		G_N_ELEMENTS (entries), editor);
+	gtk_ui_manager_insert_action_group (ui_manager, action_group, 0);
+	editor->priv->action_group = g_object_ref (action_group);
+
+	/* Hide page properties because it is not inherited in the mail. */
+	action = gtkhtml_editor_get_action (gtkhtml_editor, "properties-page");
+	gtk_action_set_visible (action, FALSE);
+
+	action = gtkhtml_editor_get_action (
+		gtkhtml_editor, "context-properties-page");
+	gtk_action_set_visible (action, FALSE);
+
+	gtk_ui_manager_ensure_update (ui_manager);
+
+	gtk_window_set_title (GTK_WINDOW (editor), _("Edit Signature"));
+
+	/* Construct the signature name entry. */
+
+	container = gtkhtml_editor->vbox;
+
+	widget = gtk_hbox_new (FALSE, 6);
+	gtk_container_set_border_width (GTK_CONTAINER (widget), 6);
+	gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+	/* Position 2 should be between the main and style toolbars. */
+	gtk_box_reorder_child (GTK_BOX (container), widget, 2);
+	gtk_widget_show (widget);
+
+	container = widget;
+
+	widget = gtk_entry_new ();
+	gtk_box_pack_end (GTK_BOX (container), widget, TRUE, TRUE, 0);
+	editor->priv->entry = widget;  /* not referenced */
+	gtk_widget_show (widget);
+
+	widget = gtk_label_new_with_mnemonic (_("_Signature Name:"));
+	gtk_label_set_mnemonic_widget (GTK_LABEL (widget), editor->priv->entry);
+	gtk_box_pack_end (GTK_BOX (container), widget, FALSE, FALSE, 0);
+	gtk_widget_show (widget);
+
+	g_signal_connect (
+		editor, "delete-event",
+		G_CALLBACK (mail_signature_editor_delete_event_cb), NULL);
+
+	/* Construct the alert bar for errors. */
+
+	container = gtkhtml_editor->vbox;
+
+	widget = e_alert_bar_new ();
+	gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+	/* Position 5 should be between the style toolbar and editing area. */
+	gtk_box_reorder_child (GTK_BOX (container), widget, 5);
+	editor->priv->alert_bar = widget;  /* not referenced */
+	/* EAlertBar controls its own visibility. */
+
+	/* Configure an EFocusTracker to manage selection actions.
+	 *
+	 * XXX GtkhtmlEditor does not manage its own selection actions,
+	 *     which is technically a bug but works in our favor here
+	 *     because it won't cause any conflicts with EFocusTracker. */
+
+	focus_tracker = e_focus_tracker_new (GTK_WINDOW (editor));
+
+	action = gtkhtml_editor_get_action (gtkhtml_editor, "cut");
+	e_focus_tracker_set_cut_clipboard_action (focus_tracker, action);
+
+	action = gtkhtml_editor_get_action (gtkhtml_editor, "copy");
+	e_focus_tracker_set_copy_clipboard_action (focus_tracker, action);
+
+	action = gtkhtml_editor_get_action (gtkhtml_editor, "paste");
+	e_focus_tracker_set_paste_clipboard_action (focus_tracker, action);
+
+	action = gtkhtml_editor_get_action (gtkhtml_editor, "select-all");
+	e_focus_tracker_set_select_all_action (focus_tracker, action);
+
+	editor->priv->focus_tracker = focus_tracker;
+
+	source = e_mail_signature_editor_get_source (editor);
+
+	display_name = e_source_get_display_name (source);
+	if (display_name == NULL || *display_name == '\0')
+		display_name = _("Unnamed");
+
+	/* Set the entry text before we grab focus. */
+	g_free (editor->priv->original_name);
+	editor->priv->original_name = g_strdup (display_name);
+	gtk_entry_set_text (GTK_ENTRY (editor->priv->entry), display_name);
+
+	/* Set the focus appropriately.  If this is a new signature, draw
+	 * the user's attention to the signature name entry.  Otherwise go
+	 * straight to the editing area. */
+	if (source == NULL)
+		gtk_widget_grab_focus (editor->priv->entry);
+	else {
+		GtkHTML *html;
+
+		html = gtkhtml_editor_get_html (gtkhtml_editor);
+		gtk_widget_grab_focus (GTK_WIDGET (html));
+	}
+
+	/* Load file content only for an existing signature.
+	 * (A new signature will not yet have a GDBusObject.) */
+	dbus_object = e_source_ref_dbus_object (source);
+	if (dbus_object != NULL) {
+		GCancellable *cancellable;
+
+		cancellable = g_cancellable_new ();
+
+		e_source_mail_signature_load (
+			source,
+			G_PRIORITY_DEFAULT,
+			cancellable,
+			mail_signature_editor_loaded_cb,
+			g_object_ref (editor));
+
+		g_warn_if_fail (editor->priv->cancellable == NULL);
+		editor->priv->cancellable = cancellable;
+
+		g_object_unref (dbus_object);
+	}
+}
+
+static void
+mail_signature_editor_cut_clipboard (GtkhtmlEditor *editor)
+{
+	/* Do nothing.  EFocusTracker handles this. */
+}
+
+static void
+mail_signature_editor_copy_clipboard (GtkhtmlEditor *editor)
+{
+	/* Do nothing.  EFocusTracker handles this. */
+}
+
+static void
+mail_signature_editor_paste_clipboard (GtkhtmlEditor *editor)
+{
+	/* Do nothing.  EFocusTracker handles this. */
+}
+
+static void
+mail_signature_editor_select_all (GtkhtmlEditor *editor)
+{
+	/* Do nothing.  EFocusTracker handles this. */
+}
+
+static void
+mail_signature_editor_submit_alert (EAlertSink *alert_sink,
+                                    EAlert *alert)
+{
+	EMailSignatureEditorPrivate *priv;
+	EAlertBar *alert_bar;
+	GtkWidget *dialog;
+	GtkWindow *parent;
+
+	priv = E_MAIL_SIGNATURE_EDITOR_GET_PRIVATE (alert_sink);
+
+	switch (e_alert_get_message_type (alert)) {
+		case GTK_MESSAGE_INFO:
+		case GTK_MESSAGE_WARNING:
+		case GTK_MESSAGE_ERROR:
+			alert_bar = E_ALERT_BAR (priv->alert_bar);
+			e_alert_bar_add_alert (alert_bar, alert);
+			break;
+
+		default:
+			parent = GTK_WINDOW (alert_sink);
+			dialog = e_alert_dialog_new (parent, alert);
+			gtk_dialog_run (GTK_DIALOG (dialog));
+			gtk_widget_destroy (dialog);
+			break;
+	}
+}
+
+static void
+e_mail_signature_editor_class_init (EMailSignatureEditorClass *class)
+{
+	GObjectClass *object_class;
+	GtkhtmlEditorClass *editor_class;
+
+	g_type_class_add_private (class, sizeof (EMailSignatureEditorPrivate));
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->set_property = mail_signature_editor_set_property;
+	object_class->get_property = mail_signature_editor_get_property;
+	object_class->dispose = mail_signature_editor_dispose;
+	object_class->finalize = mail_signature_editor_finalize;
+	object_class->constructed = mail_signature_editor_constructed;
+
+	editor_class = GTKHTML_EDITOR_CLASS (class);
+	editor_class->cut_clipboard = mail_signature_editor_cut_clipboard;
+	editor_class->copy_clipboard = mail_signature_editor_copy_clipboard;
+	editor_class->paste_clipboard = mail_signature_editor_paste_clipboard;
+	editor_class->select_all = mail_signature_editor_select_all;
+
+	g_object_class_install_property (
+		object_class,
+		PROP_FOCUS_TRACKER,
+		g_param_spec_object (
+			"focus-tracker",
+			NULL,
+			NULL,
+			E_TYPE_FOCUS_TRACKER,
+			G_PARAM_READABLE |
+			G_PARAM_STATIC_STRINGS));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_REGISTRY,
+		g_param_spec_object (
+			"registry",
+			"Registry",
+			"Data source registry",
+			E_TYPE_SOURCE_REGISTRY,
+			G_PARAM_READWRITE |
+			G_PARAM_CONSTRUCT_ONLY |
+			G_PARAM_STATIC_STRINGS));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_SOURCE,
+		g_param_spec_object (
+			"source",
+			NULL,
+			NULL,
+			E_TYPE_SOURCE,
+			G_PARAM_READWRITE |
+			G_PARAM_CONSTRUCT_ONLY |
+			G_PARAM_STATIC_STRINGS));
+}
+
+static void
+e_mail_signature_editor_alert_sink_init (EAlertSinkInterface *interface)
+{
+	interface->submit_alert = mail_signature_editor_submit_alert;
+}
+
+static void
+e_mail_signature_editor_init (EMailSignatureEditor *editor)
+{
+	editor->priv = E_MAIL_SIGNATURE_EDITOR_GET_PRIVATE (editor);
+}
+
+GtkWidget *
+e_mail_signature_editor_new (ESourceRegistry *registry,
+                             ESource *source)
+{
+	g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), NULL);
+
+	if (source != NULL)
+		g_return_val_if_fail (E_IS_SOURCE (source), NULL);
+
+	return g_object_new (
+		E_TYPE_MAIL_SIGNATURE_EDITOR,
+		"html", e_web_view_gtkhtml_new (),
+		"registry", registry,
+		"source", source, NULL);
+}
+
+EFocusTracker *
+e_mail_signature_editor_get_focus_tracker (EMailSignatureEditor *editor)
+{
+	g_return_val_if_fail (E_IS_MAIL_SIGNATURE_EDITOR (editor), NULL);
+
+	return editor->priv->focus_tracker;
+}
+
+ESourceRegistry *
+e_mail_signature_editor_get_registry (EMailSignatureEditor *editor)
+{
+	g_return_val_if_fail (E_IS_MAIL_SIGNATURE_EDITOR (editor), NULL);
+
+	return editor->priv->registry;
+}
+
+ESource *
+e_mail_signature_editor_get_source (EMailSignatureEditor *editor)
+{
+	g_return_val_if_fail (E_IS_MAIL_SIGNATURE_EDITOR (editor), NULL);
+
+	return editor->priv->source;
+}
+
+/********************** e_mail_signature_editor_commit() *********************/
+
+static void
+mail_signature_editor_replace_cb (GObject *object,
+                                  GAsyncResult *result,
+                                  gpointer user_data)
+{
+	GSimpleAsyncResult *simple;
+	GError *error = NULL;
+
+	simple = G_SIMPLE_ASYNC_RESULT (user_data);
+
+	e_source_mail_signature_replace_finish (
+		E_SOURCE (object), result, &error);
+
+	if (error != NULL)
+		g_simple_async_result_take_error (simple, error);
+
+	g_simple_async_result_complete (simple);
+
+	g_object_unref (simple);
+}
+
+static void
+mail_signature_editor_commit_cb (GObject *object,
+                                 GAsyncResult *result,
+                                 gpointer user_data)
+{
+	GSimpleAsyncResult *simple;
+	AsyncContext *async_context;
+	GError *error = NULL;
+
+	simple = G_SIMPLE_ASYNC_RESULT (user_data);
+	async_context = g_simple_async_result_get_op_res_gpointer (simple);
+
+	e_source_registry_commit_source_finish (
+		E_SOURCE_REGISTRY (object), result, &error);
+
+	if (error != NULL) {
+		g_simple_async_result_take_error (simple, error);
+		g_simple_async_result_complete (simple);
+		g_object_unref (simple);
+		return;
+	}
+
+	/* We can call this on our scratch source because only its UID is
+	 * really needed, which even a new scratch source already knows. */
+	e_source_mail_signature_replace (
+		async_context->source,
+		async_context->contents,
+		async_context->length,
+		G_PRIORITY_DEFAULT,
+		async_context->cancellable,
+		mail_signature_editor_replace_cb,
+		simple);
+}
+
+void
+e_mail_signature_editor_commit (EMailSignatureEditor *editor,
+                                GCancellable *cancellable,
+                                GAsyncReadyCallback callback,
+                                gpointer user_data)
+{
+	GSimpleAsyncResult *simple;
+	AsyncContext *async_context;
+	ESourceMailSignature *extension;
+	ESourceRegistry *registry;
+	ESource *source;
+	const gchar *extension_name;
+	const gchar *mime_type;
+	gchar *contents;
+	gboolean is_html;
+	gsize length;
+
+	g_return_if_fail (E_IS_MAIL_SIGNATURE_EDITOR (editor));
+
+	registry = e_mail_signature_editor_get_registry (editor);
+	source = e_mail_signature_editor_get_source (editor);
+	is_html = gtkhtml_editor_get_html_mode (GTKHTML_EDITOR (editor));
+
+	if (is_html) {
+		mime_type = "text/html";
+		contents = gtkhtml_editor_get_text_html (
+			GTKHTML_EDITOR (editor), &length);
+	} else {
+		mime_type = "text/plain";
+		contents = gtkhtml_editor_get_text_plain (
+			GTKHTML_EDITOR (editor), &length);
+	}
+
+	extension_name = E_SOURCE_EXTENSION_MAIL_SIGNATURE;
+	extension = e_source_get_extension (source, extension_name);
+	e_source_mail_signature_set_mime_type (extension, mime_type);
+
+	async_context = g_slice_new0 (AsyncContext);
+	async_context->source = g_object_ref (source);
+	async_context->contents = contents;  /* takes ownership */
+	async_context->length = length;
+
+	if (G_IS_CANCELLABLE (cancellable))
+		async_context->cancellable = g_object_ref (cancellable);
+
+	simple = g_simple_async_result_new (
+		G_OBJECT (editor), callback, user_data,
+		e_mail_signature_editor_commit);
+
+	g_simple_async_result_set_op_res_gpointer (
+		simple, async_context, (GDestroyNotify) async_context_free);
+
+	e_source_registry_commit_source (
+		registry, source,
+		async_context->cancellable,
+		mail_signature_editor_commit_cb,
+		simple);
+}
+
+gboolean
+e_mail_signature_editor_commit_finish (EMailSignatureEditor *editor,
+                                       GAsyncResult *result,
+                                       GError **error)
+{
+	GSimpleAsyncResult *simple;
+
+	g_return_val_if_fail (
+		g_simple_async_result_is_valid (
+		result, G_OBJECT (editor),
+		e_mail_signature_editor_commit), FALSE);
+
+	simple = G_SIMPLE_ASYNC_RESULT (result);
+
+	/* Assume success unless a GError is set. */
+	return !g_simple_async_result_propagate_error (simple, error);
+}
+
diff --git a/e-util/e-mail-signature-editor.h b/e-util/e-mail-signature-editor.h
new file mode 100644
index 0000000..c525d5a
--- /dev/null
+++ b/e-util/e-mail-signature-editor.h
@@ -0,0 +1,87 @@
+/*
+ * e-mail-signature-editor.h
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_MAIL_SIGNATURE_EDITOR_H
+#define E_MAIL_SIGNATURE_EDITOR_H
+
+#include <gtkhtml-editor.h>
+#include <libedataserver/libedataserver.h>
+
+#include <e-util/e-focus-tracker.h>
+
+/* Standard GObject macros */
+#define E_TYPE_MAIL_SIGNATURE_EDITOR \
+	(e_mail_signature_editor_get_type ())
+#define E_MAIL_SIGNATURE_EDITOR(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_MAIL_SIGNATURE_EDITOR, EMailSignatureEditor))
+#define E_MAIL_SIGNATURE_EDITOR_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_MAIL_SIGNATURE_EDITOR, EMailSignatureEditorClass))
+#define E_IS_MAIL_SIGNATURE_EDITOR(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_MAIL_SIGNATURE_EDITOR))
+#define E_IS_MAIL_SIGNATURE_EDITOR_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_MAIL_SIGNATURE_EDITOR))
+#define E_MAIL_SIGNATURE_EDITOR_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_MAIL_SIGNATURE_EDITOR, EMailSignatureEditorClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EMailSignatureEditor EMailSignatureEditor;
+typedef struct _EMailSignatureEditorClass EMailSignatureEditorClass;
+typedef struct _EMailSignatureEditorPrivate EMailSignatureEditorPrivate;
+
+struct _EMailSignatureEditor {
+	GtkhtmlEditor parent;
+	EMailSignatureEditorPrivate *priv;
+};
+
+struct _EMailSignatureEditorClass {
+	GtkhtmlEditorClass parent_class;
+};
+
+GType		e_mail_signature_editor_get_type
+						(void) G_GNUC_CONST;
+GtkWidget *	e_mail_signature_editor_new	(ESourceRegistry *registry,
+						 ESource *source);
+EFocusTracker *	e_mail_signature_editor_get_focus_tracker
+						(EMailSignatureEditor *editor);
+ESourceRegistry *
+		e_mail_signature_editor_get_registry
+						(EMailSignatureEditor *editor);
+ESource *	e_mail_signature_editor_get_source
+						(EMailSignatureEditor *editor);
+void		e_mail_signature_editor_commit	(EMailSignatureEditor *editor,
+						 GCancellable *cancellable,
+						 GAsyncReadyCallback callback,
+						 gpointer user_data);
+gboolean	e_mail_signature_editor_commit_finish
+						(EMailSignatureEditor *editor,
+						 GAsyncResult *result,
+						 GError **error);
+
+G_END_DECLS
+
+#endif /* E_MAIL_SIGNATURE_EDITOR_H */
diff --git a/widgets/misc/e-mail-signature-manager.c b/e-util/e-mail-signature-manager.c
similarity index 100%
rename from widgets/misc/e-mail-signature-manager.c
rename to e-util/e-mail-signature-manager.c
diff --git a/e-util/e-mail-signature-manager.h b/e-util/e-mail-signature-manager.h
new file mode 100644
index 0000000..4b749c1
--- /dev/null
+++ b/e-util/e-mail-signature-manager.h
@@ -0,0 +1,93 @@
+/*
+ * e-mail-signature-manager.h
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_MAIL_SIGNATURE_MANAGER_H
+#define E_MAIL_SIGNATURE_MANAGER_H
+
+#include <gtk/gtk.h>
+
+#include <e-util/e-mail-signature-editor.h>
+#include <e-util/e-mail-signature-tree-view.h>
+
+/* Standard GObject macros */
+#define E_TYPE_MAIL_SIGNATURE_MANAGER \
+	(e_mail_signature_manager_get_type ())
+#define E_MAIL_SIGNATURE_MANAGER(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_MAIL_SIGNATURE_MANAGER, EMailSignatureManager))
+#define E_MAIL_SIGNATURE_MANAGER_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_MAIL_SIGNATURE_MANAGER, EMailSignatureManagerClass))
+#define E_IS_MAIL_SIGNATURE_MANAGER(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_MAIL_SIGNATURE_MANAGER))
+#define E_IS_MAIL_SIGNATURE_MANAGER_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_MAIL_SIGNATURE_MANAGER))
+#define E_MAIL_SIGNATURE_MANAGER_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_MAIL_SIGNATURE_MANAGER, EMailSignatureManagerClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EMailSignatureManager EMailSignatureManager;
+typedef struct _EMailSignatureManagerClass EMailSignatureManagerClass;
+typedef struct _EMailSignatureManagerPrivate EMailSignatureManagerPrivate;
+
+struct _EMailSignatureManager {
+	GtkPaned parent;
+	EMailSignatureManagerPrivate *priv;
+};
+
+struct _EMailSignatureManagerClass {
+	GtkPanedClass parent_class;
+
+	void	(*add_signature)	(EMailSignatureManager *manager);
+	void	(*add_signature_script)	(EMailSignatureManager *manager);
+	void	(*editor_created)	(EMailSignatureManager *manager,
+					 EMailSignatureEditor *editor);
+	void	(*edit_signature)	(EMailSignatureManager *manager);
+	void	(*remove_signature)	(EMailSignatureManager *manager);
+};
+
+GType		e_mail_signature_manager_get_type
+					(void) G_GNUC_CONST;
+GtkWidget *	e_mail_signature_manager_new
+					(ESourceRegistry *registry);
+void		e_mail_signature_manager_add_signature
+					(EMailSignatureManager *manager);
+void		e_mail_signature_manager_add_signature_script
+					(EMailSignatureManager *manager);
+void		e_mail_signature_manager_edit_signature
+					(EMailSignatureManager *manager);
+void		e_mail_signature_manager_remove_signature
+					(EMailSignatureManager *manager);
+gboolean	e_mail_signature_manager_get_prefer_html
+					(EMailSignatureManager *manager);
+void		e_mail_signature_manager_set_prefer_html
+					(EMailSignatureManager *manager,
+					 gboolean prefer_html);
+ESourceRegistry *
+		e_mail_signature_manager_get_registry
+					(EMailSignatureManager *manager);
+
+#endif /* E_MAIL_SIGNATURE_MANAGER_H */
diff --git a/e-util/e-mail-signature-preview.c b/e-util/e-mail-signature-preview.c
new file mode 100644
index 0000000..8bf27fd
--- /dev/null
+++ b/e-util/e-mail-signature-preview.c
@@ -0,0 +1,358 @@
+/*
+ * e-mail-signature-preview.c
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#include "e-mail-signature-preview.h"
+
+#include <fcntl.h>
+#include <string.h>
+#include <unistd.h>
+#include <glib/gstdio.h>
+
+#include "e-alert-sink.h"
+
+#define E_MAIL_SIGNATURE_PREVIEW_GET_PRIVATE(obj) \
+	(G_TYPE_INSTANCE_GET_PRIVATE \
+	((obj), E_TYPE_MAIL_SIGNATURE_PREVIEW, EMailSignaturePreviewPrivate))
+
+#define SOURCE_IS_MAIL_SIGNATURE(source) \
+	(e_source_has_extension ((source), E_SOURCE_EXTENSION_MAIL_SIGNATURE))
+
+struct _EMailSignaturePreviewPrivate {
+	ESourceRegistry *registry;
+	GCancellable *cancellable;
+	gchar *source_uid;
+};
+
+enum {
+	PROP_0,
+	PROP_REGISTRY,
+	PROP_SOURCE_UID
+};
+
+enum {
+	REFRESH,
+	LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+G_DEFINE_TYPE (
+	EMailSignaturePreview,
+	e_mail_signature_preview,
+	E_TYPE_WEB_VIEW)
+
+static void
+mail_signature_preview_load_cb (ESource *source,
+                                GAsyncResult *result,
+                                EMailSignaturePreview *preview)
+{
+	ESourceMailSignature *extension;
+	const gchar *extension_name;
+	const gchar *mime_type;
+	gchar *contents = NULL;
+	GError *error = NULL;
+
+	e_source_mail_signature_load_finish (
+		source, result, &contents, NULL, &error);
+
+	/* Ignore cancellations. */
+	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+		g_warn_if_fail (contents == NULL);
+		g_object_unref (preview);
+		g_error_free (error);
+		return;
+
+	} else if (error != NULL) {
+		g_warn_if_fail (contents == NULL);
+		e_alert_submit (
+			E_ALERT_SINK (preview),
+			"widgets:no-load-signature",
+			error->message, NULL);
+		g_object_unref (preview);
+		g_error_free (error);
+		return;
+	}
+
+	g_return_if_fail (contents != NULL);
+
+	extension_name = E_SOURCE_EXTENSION_MAIL_SIGNATURE;
+	extension = e_source_get_extension (source, extension_name);
+	mime_type = e_source_mail_signature_get_mime_type (extension);
+
+	if (g_strcmp0 (mime_type, "text/html") == 0)
+		e_web_view_load_string (E_WEB_VIEW (preview), contents);
+	else {
+		gchar *string;
+
+		string = g_markup_printf_escaped ("<pre>%s</pre>", contents);
+		e_web_view_load_string (E_WEB_VIEW (preview), string);
+		g_free (string);
+	}
+
+	g_free (contents);
+
+	g_object_unref (preview);
+}
+
+static void
+mail_signature_preview_set_registry (EMailSignaturePreview *preview,
+                                     ESourceRegistry *registry)
+{
+	g_return_if_fail (E_IS_SOURCE_REGISTRY (registry));
+	g_return_if_fail (preview->priv->registry == NULL);
+
+	preview->priv->registry = g_object_ref (registry);
+}
+
+static void
+mail_signature_preview_set_property (GObject *object,
+                                guint property_id,
+                                const GValue *value,
+                                GParamSpec *pspec)
+{
+	switch (property_id) {
+		case PROP_REGISTRY:
+			mail_signature_preview_set_registry (
+				E_MAIL_SIGNATURE_PREVIEW (object),
+				g_value_get_object (value));
+			return;
+
+		case PROP_SOURCE_UID:
+			e_mail_signature_preview_set_source_uid (
+				E_MAIL_SIGNATURE_PREVIEW (object),
+				g_value_get_string (value));
+			return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+mail_signature_preview_get_property (GObject *object,
+                                guint property_id,
+                                GValue *value,
+                                GParamSpec *pspec)
+{
+	switch (property_id) {
+		case PROP_REGISTRY:
+			g_value_set_object (
+				value,
+				e_mail_signature_preview_get_registry (
+				E_MAIL_SIGNATURE_PREVIEW (object)));
+			return;
+
+		case PROP_SOURCE_UID:
+			g_value_set_string (
+				value,
+				e_mail_signature_preview_get_source_uid (
+				E_MAIL_SIGNATURE_PREVIEW (object)));
+			return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+mail_signature_preview_dispose (GObject *object)
+{
+	EMailSignaturePreviewPrivate *priv;
+
+	priv = E_MAIL_SIGNATURE_PREVIEW_GET_PRIVATE (object);
+
+	if (priv->registry != NULL) {
+		g_object_unref (priv->registry);
+		priv->registry = NULL;
+	}
+
+	if (priv->cancellable != NULL) {
+		g_cancellable_cancel (priv->cancellable);
+		g_object_unref (priv->cancellable);
+		priv->cancellable = NULL;
+	}
+
+	/* Chain up to parent's dispose() method. */
+	G_OBJECT_CLASS (e_mail_signature_preview_parent_class)->
+		dispose (object);
+}
+
+static void
+mail_signature_preview_finalize (GObject *object)
+{
+	EMailSignaturePreviewPrivate *priv;
+
+	priv = E_MAIL_SIGNATURE_PREVIEW_GET_PRIVATE (object);
+
+	g_free (priv->source_uid);
+
+	/* Chain up to parent's finalize() method. */
+	G_OBJECT_CLASS (e_mail_signature_preview_parent_class)->
+		finalize (object);
+}
+
+static void
+mail_signature_preview_refresh (EMailSignaturePreview *preview)
+{
+	ESourceRegistry *registry;
+	ESource *source;
+	const gchar *extension_name;
+	const gchar *source_uid;
+
+	/* Cancel any unfinished refreshes. */
+	if (preview->priv->cancellable != NULL) {
+		g_cancellable_cancel (preview->priv->cancellable);
+		g_object_unref (preview->priv->cancellable);
+		preview->priv->cancellable = NULL;
+	}
+
+	source_uid = e_mail_signature_preview_get_source_uid (preview);
+
+	if (source_uid == NULL)
+		goto fail;
+
+	registry = e_mail_signature_preview_get_registry (preview);
+	source = e_source_registry_ref_source (registry, source_uid);
+
+	if (source == NULL)
+		goto fail;
+
+	extension_name = E_SOURCE_EXTENSION_MAIL_SIGNATURE;
+	if (!e_source_has_extension (source, extension_name)) {
+		g_object_unref (source);
+		goto fail;
+	}
+
+	preview->priv->cancellable = g_cancellable_new ();
+
+	e_source_mail_signature_load (
+		source, G_PRIORITY_DEFAULT,
+		preview->priv->cancellable, (GAsyncReadyCallback)
+		mail_signature_preview_load_cb, g_object_ref (preview));
+
+	g_object_unref (source);
+
+	return;
+
+fail:
+	e_web_view_clear (E_WEB_VIEW (preview));
+}
+
+static void
+e_mail_signature_preview_class_init (EMailSignaturePreviewClass *class)
+{
+	GObjectClass *object_class;
+
+	g_type_class_add_private (class, sizeof (EMailSignaturePreviewPrivate));
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->set_property = mail_signature_preview_set_property;
+	object_class->get_property = mail_signature_preview_get_property;
+	object_class->dispose = mail_signature_preview_dispose;
+	object_class->finalize = mail_signature_preview_finalize;
+
+	class->refresh = mail_signature_preview_refresh;
+
+	g_object_class_install_property (
+		object_class,
+		PROP_REGISTRY,
+		g_param_spec_object (
+			"registry",
+			"Registry",
+			NULL,
+			E_TYPE_SOURCE_REGISTRY,
+			G_PARAM_READWRITE |
+			G_PARAM_CONSTRUCT_ONLY |
+			G_PARAM_STATIC_STRINGS));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_SOURCE_UID,
+		g_param_spec_string (
+			"source-uid",
+			"Source UID",
+			NULL,
+			NULL,
+			G_PARAM_READWRITE |
+			G_PARAM_STATIC_STRINGS));
+
+	signals[REFRESH] = g_signal_new (
+		"refresh",
+		G_TYPE_FROM_CLASS (class),
+		G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+		G_STRUCT_OFFSET (EMailSignaturePreviewClass, refresh),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__VOID,
+		G_TYPE_NONE, 0);
+}
+
+static void
+e_mail_signature_preview_init (EMailSignaturePreview *preview)
+{
+	preview->priv = E_MAIL_SIGNATURE_PREVIEW_GET_PRIVATE (preview);
+}
+
+GtkWidget *
+e_mail_signature_preview_new (ESourceRegistry *registry)
+{
+	g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), NULL);
+
+	return g_object_new (
+		E_TYPE_MAIL_SIGNATURE_PREVIEW,
+		"registry", registry, NULL);
+}
+
+void
+e_mail_signature_preview_refresh (EMailSignaturePreview *preview)
+{
+	g_return_if_fail (E_IS_MAIL_SIGNATURE_PREVIEW (preview));
+
+	g_signal_emit (preview, signals[REFRESH], 0);
+}
+
+ESourceRegistry *
+e_mail_signature_preview_get_registry (EMailSignaturePreview *preview)
+{
+	g_return_val_if_fail (E_IS_MAIL_SIGNATURE_PREVIEW (preview), NULL);
+
+	return preview->priv->registry;
+}
+
+const gchar *
+e_mail_signature_preview_get_source_uid (EMailSignaturePreview *preview)
+{
+	g_return_val_if_fail (E_IS_MAIL_SIGNATURE_PREVIEW (preview), NULL);
+
+	return preview->priv->source_uid;
+}
+
+void
+e_mail_signature_preview_set_source_uid (EMailSignaturePreview *preview,
+                                         const gchar *source_uid)
+{
+	g_return_if_fail (E_IS_MAIL_SIGNATURE_PREVIEW (preview));
+
+	/* Avoid repeatedly loading the same signature file. */
+	if (g_strcmp0 (source_uid, preview->priv->source_uid) == 0)
+		return;
+
+	g_free (preview->priv->source_uid);
+	preview->priv->source_uid = g_strdup (source_uid);
+
+	g_object_notify (G_OBJECT (preview), "source-uid");
+
+	e_mail_signature_preview_refresh (preview);
+}
diff --git a/e-util/e-mail-signature-preview.h b/e-util/e-mail-signature-preview.h
new file mode 100644
index 0000000..e7b0302
--- /dev/null
+++ b/e-util/e-mail-signature-preview.h
@@ -0,0 +1,84 @@
+/*
+ * e-mail-signature-preview.h
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_MAIL_SIGNATURE_PREVIEW_H
+#define E_MAIL_SIGNATURE_PREVIEW_H
+
+#include <libedataserver/libedataserver.h>
+
+#include <e-util/e-web-view.h>
+
+/* Standard GObject macros */
+#define E_TYPE_MAIL_SIGNATURE_PREVIEW \
+	(e_mail_signature_preview_get_type ())
+#define E_MAIL_SIGNATURE_PREVIEW(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_MAIL_SIGNATURE_PREVIEW, EMailSignaturePreview))
+#define E_MAIL_SIGNATURE_PREVIEW_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_MAIL_SIGNATURE_PREVIEW, EMailSignaturePreviewClass))
+#define E_IS_MAIL_SIGNATURE_PREVIEW(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_MAIL_SIGNATURE_PREVIEW))
+#define E_IS_MAIL_SIGNATURE_PREVIEW_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_MAIL_SIGNATURE_PREVIEW))
+#define E_MAIL_SIGNATURE_PREVIEW_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_MAIL_SIGNATURE_PREVIEW, EMailSignaturePreview))
+
+G_BEGIN_DECLS
+
+typedef struct _EMailSignaturePreview EMailSignaturePreview;
+typedef struct _EMailSignaturePreviewClass EMailSignaturePreviewClass;
+typedef struct _EMailSignaturePreviewPrivate EMailSignaturePreviewPrivate;
+
+struct _EMailSignaturePreview {
+	EWebView parent;
+	EMailSignaturePreviewPrivate *priv;
+};
+
+struct _EMailSignaturePreviewClass {
+	EWebViewClass parent_class;
+
+	/* Signals */
+	void		(*refresh)	(EMailSignaturePreview *preview);
+};
+
+GType		e_mail_signature_preview_get_type
+					(void) G_GNUC_CONST;
+GtkWidget *	e_mail_signature_preview_new
+					(ESourceRegistry *registry);
+void		e_mail_signature_preview_refresh
+					(EMailSignaturePreview *preview);
+ESourceRegistry *
+		e_mail_signature_preview_get_registry
+					(EMailSignaturePreview *preview);
+const gchar *	e_mail_signature_preview_get_source_uid
+					(EMailSignaturePreview *preview);
+void		e_mail_signature_preview_set_source_uid
+					(EMailSignaturePreview *preview,
+					 const gchar *source_uid);
+
+G_END_DECLS
+
+#endif /* E_MAIL_SIGNATURE_PREVIEW_H */
diff --git a/widgets/misc/e-mail-signature-script-dialog.c b/e-util/e-mail-signature-script-dialog.c
similarity index 100%
rename from widgets/misc/e-mail-signature-script-dialog.c
rename to e-util/e-mail-signature-script-dialog.c
diff --git a/e-util/e-mail-signature-script-dialog.h b/e-util/e-mail-signature-script-dialog.h
new file mode 100644
index 0000000..6a266b5
--- /dev/null
+++ b/e-util/e-mail-signature-script-dialog.h
@@ -0,0 +1,94 @@
+/*
+ * e-mail-signature-script-dialog.h
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_MAIL_SIGNATURE_SCRIPT_DIALOG_H
+#define E_MAIL_SIGNATURE_SCRIPT_DIALOG_H
+
+#include <gtk/gtk.h>
+#include <libedataserver/libedataserver.h>
+
+/* Standard GObject macros */
+#define E_TYPE_MAIL_SIGNATURE_SCRIPT_DIALOG \
+	(e_mail_signature_script_dialog_get_type ())
+#define E_MAIL_SIGNATURE_SCRIPT_DIALOG(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_MAIL_SIGNATURE_SCRIPT_DIALOG, \
+	EMailSignatureScriptDialog))
+#define E_MAIL_SIGNATURE_SCRIPT_DIALOG_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_MAIL_SIGNATURE_SCRIPT_DIALOG, \
+	EMailSignatureScriptDialogClass))
+#define E_IS_MAIL_SIGNATURE_SCRIPT_DIALOG(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_MAIL_SIGNATURE_SCRIPT_DIALOG))
+#define E_IS_MAIL_SIGNATURE_SCRIPT_DIALOG_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_MAIL_SIGNATURE_SCRIPT_DIALOG))
+#define E_MAIL_SIGNATURE_SCRIPT_DIALOG_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_MAIL_SIGNATURE_SCRIPT_DIALOG, \
+	EMailSignatureScriptDialogClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EMailSignatureScriptDialog EMailSignatureScriptDialog;
+typedef struct _EMailSignatureScriptDialogClass EMailSignatureScriptDialogClass;
+typedef struct _EMailSignatureScriptDialogPrivate EMailSignatureScriptDialogPrivate;
+
+struct _EMailSignatureScriptDialog {
+	GtkDialog parent;
+	EMailSignatureScriptDialogPrivate *priv;
+};
+
+struct _EMailSignatureScriptDialogClass {
+	GtkDialogClass parent_class;
+};
+
+GType		e_mail_signature_script_dialog_get_type
+					(void) G_GNUC_CONST;
+GtkWidget *	e_mail_signature_script_dialog_new
+					(ESourceRegistry *registry,
+					 GtkWindow *parent,
+					 ESource *source);
+ESourceRegistry *
+		e_mail_signature_script_dialog_get_registry
+					(EMailSignatureScriptDialog *dialog);
+ESource *	e_mail_signature_script_dialog_get_source
+					(EMailSignatureScriptDialog *dialog);
+const gchar *	e_mail_signature_script_dialog_get_symlink_target
+					(EMailSignatureScriptDialog *dialog);
+void		e_mail_signature_script_dialog_set_symlink_target
+					(EMailSignatureScriptDialog *dialog,
+					 const gchar *symlink_target);
+void		e_mail_signature_script_dialog_commit
+					(EMailSignatureScriptDialog *dialog,
+					 GCancellable *cancellable,
+					 GAsyncReadyCallback callback,
+					 gpointer user_data);
+gboolean	e_mail_signature_script_dialog_commit_finish
+					(EMailSignatureScriptDialog *dialog,
+					 GAsyncResult *result,
+					 GError **error);
+
+G_END_DECLS
+
+#endif /* E_MAIL_SIGNATURE_SCRIPT_DIALOG_H */
diff --git a/widgets/misc/e-mail-signature-tree-view.c b/e-util/e-mail-signature-tree-view.c
similarity index 100%
rename from widgets/misc/e-mail-signature-tree-view.c
rename to e-util/e-mail-signature-tree-view.c
diff --git a/e-util/e-mail-signature-tree-view.h b/e-util/e-mail-signature-tree-view.h
new file mode 100644
index 0000000..4da5329
--- /dev/null
+++ b/e-util/e-mail-signature-tree-view.h
@@ -0,0 +1,80 @@
+/*
+ * e-mail-signature-tree-view.h
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_MAIL_SIGNATURE_TREE_VIEW_H
+#define E_MAIL_SIGNATURE_TREE_VIEW_H
+
+#include <gtk/gtk.h>
+#include <libedataserver/libedataserver.h>
+
+/* Standard GObject macros */
+#define E_TYPE_MAIL_SIGNATURE_TREE_VIEW \
+	(e_mail_signature_tree_view_get_type ())
+#define E_MAIL_SIGNATURE_TREE_VIEW(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_MAIL_SIGNATURE_TREE_VIEW, EMailSignatureTreeView))
+#define E_MAIL_SIGNATURE_TREE_VIEW_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_MAIL_SIGNATURE_TREE_VIEW, EMailSignatureTreeViewClass))
+#define E_IS_MAIL_SIGNATURE_TREE_VIEW(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_MAIL_SIGNATURE_TREE_VIEW))
+#define E_IS_MAIL_SIGNATURE_TREE_VIEW_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_MAIL_SIGNATURE_TREE_VIEW))
+#define E_MAIL_SIGNATURE_TREE_VIEW_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_MAIL_SIGNATURE_TREE_VIEW, EMailSignatureTreeViewClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EMailSignatureTreeView EMailSignatureTreeView;
+typedef struct _EMailSignatureTreeViewClass EMailSignatureTreeViewClass;
+typedef struct _EMailSignatureTreeViewPrivate EMailSignatureTreeViewPrivate;
+
+struct _EMailSignatureTreeView {
+	GtkTreeView parent;
+	EMailSignatureTreeViewPrivate *priv;
+};
+
+struct _EMailSignatureTreeViewClass {
+	GtkTreeViewClass parent_class;
+};
+
+GType		e_mail_signature_tree_view_get_type
+					(void) G_GNUC_CONST;
+GtkWidget *	e_mail_signature_tree_view_new
+					(ESourceRegistry *registry);
+void		e_mail_signature_tree_view_refresh
+					(EMailSignatureTreeView *tree_view);
+ESourceRegistry *
+		e_mail_signature_tree_view_get_registry
+					(EMailSignatureTreeView *tree_view);
+ESource *	e_mail_signature_tree_view_ref_selected_source
+					(EMailSignatureTreeView *tree_view);
+void		e_mail_signature_tree_view_set_selected_source
+					(EMailSignatureTreeView *tree_view,
+					 ESource *selected_source);
+
+G_END_DECLS
+
+#endif /* E_MAIL_SIGNATURE_TREE_VIEW_H */
diff --git a/e-util/e-map.c b/e-util/e-map.c
new file mode 100644
index 0000000..a419626
--- /dev/null
+++ b/e-util/e-map.c
@@ -0,0 +1,1429 @@
+/*
+ * Map widget.
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Hans Petter Jansson <hpj ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <math.h>
+#include <stdlib.h>
+#include <gdk/gdkkeysyms.h>
+#include <glib/gi18n.h>
+
+#include "e-map.h"
+
+#include "e-util-private.h"
+
+#define E_MAP_GET_PRIVATE(obj) \
+	(G_TYPE_INSTANCE_GET_PRIVATE \
+	((obj), E_TYPE_MAP, EMapPrivate))
+
+#define E_MAP_TWEEN_TIMEOUT_MSECS 25
+#define E_MAP_TWEEN_DURATION_MSECS 150
+
+/* Scroll step increment */
+
+#define SCROLL_STEP_SIZE 32
+
+/* */
+
+#define E_MAP_GET_WIDTH(map) gtk_adjustment_get_upper((map)->priv->hadjustment)
+#define E_MAP_GET_HEIGHT(map) gtk_adjustment_get_upper((map)->priv->vadjustment)
+
+/* Zoom state - keeps track of animation hacks */
+
+typedef enum
+{
+	E_MAP_ZOOMED_IN,
+	E_MAP_ZOOMED_OUT,
+	E_MAP_ZOOMING_IN,
+	E_MAP_ZOOMING_OUT
+}
+EMapZoomState;
+
+/* The Tween struct used for zooming */
+
+typedef struct _EMapTween EMapTween;
+
+struct _EMapTween {
+	guint start_time;
+	guint end_time;
+	gdouble longitude_offset;
+	gdouble latitude_offset;
+	gdouble zoom_factor;
+};
+
+/* Private part of the EMap structure */
+
+struct _EMapPrivate {
+	/* Pointer to map image */
+	GdkPixbuf *map_pixbuf;
+	cairo_surface_t *map_render_surface;
+
+	/* Settings */
+	gboolean frozen, smooth_zoom;
+
+	/* Adjustments for scrolling */
+	GtkAdjustment *hadjustment;
+	GtkAdjustment *vadjustment;
+
+	/* GtkScrollablePolicy needs to be checked when
+	 * driving the scrollable adjustment values */
+	guint hscroll_policy : 1;
+	guint vscroll_policy : 1;
+
+	/* Current scrolling offsets */
+	gint xofs, yofs;
+
+	/* Realtime zoom data */
+	EMapZoomState zoom_state;
+	gdouble zoom_target_long, zoom_target_lat;
+
+	/* Dots */
+	GPtrArray *points;
+
+	/* Tweens */
+	GSList *tweens;
+	GTimer *timer;
+	guint timer_current_ms;
+	guint tween_id;
+};
+
+/* Properties */
+
+enum {
+	PROP_0,
+
+	/* For scrollable interface */
+	PROP_HADJUSTMENT,
+	PROP_VADJUSTMENT,
+	PROP_HSCROLL_POLICY,
+	PROP_VSCROLL_POLICY
+};
+
+/* Internal prototypes */
+
+static void update_render_surface (EMap *map, gboolean render_overlays);
+static void set_scroll_area (EMap *map, gint width, gint height);
+static void center_at (EMap *map, gdouble longitude, gdouble latitude);
+static void scroll_to (EMap *map, gint x, gint y);
+static gint load_map_background (EMap *map, gchar *name);
+static void update_and_paint (EMap *map);
+static void update_render_point (EMap *map, EMapPoint *point);
+static void repaint_point (EMap *map, EMapPoint *point);
+
+/* ------ *
+ * Tweens *
+ * ------ */
+
+static gboolean
+e_map_is_tweening (EMap *map)
+{
+	return map->priv->timer != NULL;
+}
+
+static void
+e_map_stop_tweening (EMap *map)
+{
+	g_assert (map->priv->tweens == NULL);
+
+	if (!e_map_is_tweening (map))
+		return;
+
+	g_timer_destroy (map->priv->timer);
+	map->priv->timer = NULL;
+	g_source_remove (map->priv->tween_id);
+	map->priv->tween_id = 0;
+}
+
+static void
+e_map_tween_destroy (EMap *map,
+                     EMapTween *tween)
+{
+	map->priv->tweens = g_slist_remove (map->priv->tweens, tween);
+	g_slice_free (EMapTween, tween);
+
+	if (map->priv->tweens == NULL)
+		e_map_stop_tweening (map);
+}
+
+static gboolean
+e_map_do_tween_cb (gpointer data)
+{
+	EMap *map = data;
+	GSList *walk;
+
+	map->priv->timer_current_ms =
+		g_timer_elapsed (map->priv->timer, NULL) * 1000;
+	gtk_widget_queue_draw (GTK_WIDGET (map));
+
+	/* Can't use for loop here, because we need to advance 
+	 * the list before deleting.
+	 */
+	walk = map->priv->tweens;
+	while (walk)
+	{
+		EMapTween *tween = walk->data;
+
+		walk = walk->next;
+
+		if (tween->end_time <= map->priv->timer_current_ms)
+			e_map_tween_destroy (map, tween);
+	}
+
+	return TRUE;
+}
+
+static void
+e_map_start_tweening (EMap *map)
+{
+	if (e_map_is_tweening (map))
+		return;
+
+	map->priv->timer = g_timer_new ();
+	map->priv->timer_current_ms = 0;
+	map->priv->tween_id = g_timeout_add (
+		E_MAP_TWEEN_TIMEOUT_MSECS, e_map_do_tween_cb, map);
+	g_timer_start (map->priv->timer);
+}
+
+static void
+e_map_tween_new (EMap *map,
+                 guint msecs,
+                 gdouble longitude_offset,
+                 gdouble latitude_offset,
+                 gdouble zoom_factor)
+{
+	EMapTween *tween;
+
+	if (!map->priv->smooth_zoom)
+		return;
+
+	e_map_start_tweening (map);
+
+	tween = g_slice_new (EMapTween);
+
+	tween->start_time = map->priv->timer_current_ms;
+	tween->end_time = tween->start_time + msecs;
+	tween->longitude_offset = longitude_offset;
+	tween->latitude_offset = latitude_offset;
+	tween->zoom_factor = zoom_factor;
+
+	map->priv->tweens = g_slist_prepend (map->priv->tweens, tween);
+
+	gtk_widget_queue_draw (GTK_WIDGET (map));
+}
+
+G_DEFINE_TYPE_WITH_CODE (
+	EMap,
+	e_map,
+	GTK_TYPE_WIDGET,
+	G_IMPLEMENT_INTERFACE (GTK_TYPE_SCROLLABLE, NULL))
+
+static void
+e_map_get_current_location (EMap *map,
+                            gdouble *longitude,
+                            gdouble *latitude)
+{
+	GtkAllocation allocation;
+
+	gtk_widget_get_allocation (GTK_WIDGET (map), &allocation);
+
+	e_map_window_to_world (
+		map, allocation.width / 2.0,
+		allocation.height / 2.0,
+		longitude, latitude);
+}
+
+static void
+e_map_world_to_render_surface (EMap *map,
+                               gdouble world_longitude,
+                               gdouble world_latitude,
+                               gdouble *win_x,
+                               gdouble *win_y)
+{
+	gint width, height;
+
+	width = E_MAP_GET_WIDTH (map);
+	height = E_MAP_GET_HEIGHT (map);
+
+	*win_x = (width / 2.0 + (width / 2.0) * world_longitude / 180.0);
+	*win_y = (height / 2.0 - (height / 2.0) * world_latitude / 90.0);
+}
+
+static void
+e_map_tween_new_from (EMap *map,
+                      guint msecs,
+                      gdouble longitude,
+                      gdouble latitude,
+                      gdouble zoom)
+{
+	gdouble current_longitude, current_latitude;
+
+	e_map_get_current_location (
+		map, &current_longitude, &current_latitude);
+
+	e_map_tween_new (
+		map, msecs,
+		longitude - current_longitude,
+		latitude - current_latitude,
+		zoom / e_map_get_magnification (map));
+}
+
+static gdouble
+e_map_get_tween_effect (EMap *map,
+                        EMapTween *tween)
+{
+	gdouble elapsed;
+
+	elapsed = (gdouble)
+		(map->priv->timer_current_ms - tween->start_time) /
+		tween->end_time;
+
+	return MAX (0.0, 1.0 - elapsed);
+}
+
+static void
+e_map_apply_tween (EMapTween *tween,
+                   gdouble effect,
+                   gdouble *longitude,
+                   gdouble *latitude,
+                   gdouble *zoom)
+{
+	*zoom *= pow (tween->zoom_factor, effect);
+	*longitude += tween->longitude_offset * effect;
+	*latitude += tween->latitude_offset * effect;
+}
+
+static void
+e_map_tweens_compute_matrix (EMap *map,
+                             cairo_matrix_t *matrix)
+{
+	GSList *walk;
+	gdouble zoom, x, y, latitude, longitude, effect;
+	GtkAllocation allocation;
+
+	if (!e_map_is_tweening (map)) {
+		cairo_matrix_init_translate (
+			matrix, -map->priv->xofs, -map->priv->yofs);
+		return;
+	}
+
+	e_map_get_current_location (map, &longitude, &latitude);
+	zoom = 1.0;
+
+	for (walk = map->priv->tweens; walk; walk = walk->next) {
+		EMapTween *tween = walk->data;
+
+		effect = e_map_get_tween_effect (map, tween);
+		e_map_apply_tween (tween, effect, &longitude, &latitude, &zoom);
+	}
+
+	gtk_widget_get_allocation (GTK_WIDGET (map), &allocation);
+	cairo_matrix_init_translate (
+		matrix,
+		allocation.width / 2.0,
+		allocation.height / 2.0);
+
+	e_map_world_to_render_surface (map, longitude, latitude, &x, &y);
+	cairo_matrix_scale (matrix, zoom, zoom);
+	cairo_matrix_translate (matrix, -x, -y);
+}
+
+/* GtkScrollable implementation */
+
+static void
+e_map_adjustment_changed (GtkAdjustment *adjustment,
+                          EMap *map)
+{
+	EMapPrivate *priv = map->priv;
+
+	if (gtk_widget_get_realized (GTK_WIDGET (map))) {
+		gint hadj_value;
+		gint vadj_value;
+
+		hadj_value = gtk_adjustment_get_value (priv->hadjustment);
+		vadj_value = gtk_adjustment_get_value (priv->vadjustment);
+
+		scroll_to (map, hadj_value, vadj_value);
+	}
+}
+
+static void
+e_map_set_hadjustment_values (EMap *map)
+{
+	GtkAllocation  allocation;
+	EMapPrivate *priv = map->priv;
+	GtkAdjustment *adj = priv->hadjustment;
+	gdouble old_value;
+	gdouble new_value;
+	gdouble new_upper;
+
+	gtk_widget_get_allocation (GTK_WIDGET (map), &allocation);
+
+	old_value = gtk_adjustment_get_value (adj);
+	new_upper = MAX (allocation.width, gdk_pixbuf_get_width (priv->map_pixbuf));
+
+	g_object_set (
+		adj,
+		"lower", 0.0,
+		"upper", new_upper,
+		"page-size", (gdouble) allocation.height,
+		"step-increment", allocation.height * 0.1,
+		"page-increment", allocation.height * 0.9,
+		NULL);
+
+	new_value = CLAMP (old_value, 0, new_upper - allocation.width);
+	if (new_value != old_value)
+		gtk_adjustment_set_value (adj, new_value);
+}
+
+static void
+e_map_set_vadjustment_values (EMap *map)
+{
+	GtkAllocation  allocation;
+	EMapPrivate *priv = map->priv;
+	GtkAdjustment *adj = priv->vadjustment;
+	gdouble old_value;
+	gdouble new_value;
+	gdouble new_upper;
+
+	gtk_widget_get_allocation (GTK_WIDGET (map), &allocation);
+
+	old_value = gtk_adjustment_get_value (adj);
+	new_upper = MAX (allocation.height, gdk_pixbuf_get_height (priv->map_pixbuf));
+
+	g_object_set (
+		adj,
+		"lower", 0.0,
+		"upper", new_upper,
+		"page-size", (gdouble) allocation.height,
+		"step-increment", allocation.height * 0.1,
+		"page-increment", allocation.height * 0.9,
+		NULL);
+
+	new_value = CLAMP (old_value, 0, new_upper - allocation.height);
+	if (new_value != old_value)
+		gtk_adjustment_set_value (adj, new_value);
+}
+
+static void
+e_map_set_hadjustment (EMap *map,
+                       GtkAdjustment *adjustment)
+{
+	EMapPrivate *priv = map->priv;
+
+	if (adjustment && priv->hadjustment == adjustment)
+		return;
+
+	if (priv->hadjustment != NULL) {
+		g_signal_handlers_disconnect_matched (
+			priv->hadjustment, G_SIGNAL_MATCH_DATA,
+			0, 0, NULL, NULL, map);
+		g_object_unref (priv->hadjustment);
+	}
+
+	if (!adjustment)
+		adjustment = gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
+
+	g_signal_connect (
+		adjustment, "value-changed",
+		G_CALLBACK (e_map_adjustment_changed), map);
+	priv->hadjustment = g_object_ref_sink (adjustment);
+	e_map_set_hadjustment_values (map);
+
+	g_object_notify (G_OBJECT (map), "hadjustment");
+}
+
+static void
+e_map_set_vadjustment (EMap *map,
+                       GtkAdjustment *adjustment)
+{
+	EMapPrivate *priv = map->priv;
+
+	if (adjustment && priv->vadjustment == adjustment)
+		return;
+
+	if (priv->vadjustment != NULL) {
+		g_signal_handlers_disconnect_matched (
+			priv->vadjustment, G_SIGNAL_MATCH_DATA,
+			0, 0, NULL, NULL, map);
+		g_object_unref (priv->vadjustment);
+	}
+
+	if (!adjustment)
+		adjustment = gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
+
+	g_signal_connect (
+		adjustment, "value-changed",
+		G_CALLBACK (e_map_adjustment_changed), map);
+	priv->vadjustment = g_object_ref_sink (adjustment);
+	e_map_set_vadjustment_values (map);
+
+	g_object_notify (G_OBJECT (map), "vadjustment");
+}
+
+/* ----------------- *
+ * Widget management *
+ * ----------------- */
+
+static void
+e_map_set_property (GObject *object,
+                    guint property_id,
+                    const GValue *value,
+                    GParamSpec *pspec)
+{
+	EMap *map;
+
+	map = E_MAP (object);
+
+	switch (property_id) {
+	case PROP_HADJUSTMENT:
+		e_map_set_hadjustment (map, g_value_get_object (value));
+		break;
+	case PROP_VADJUSTMENT:
+		e_map_set_vadjustment (map, g_value_get_object (value));
+		break;
+	case PROP_HSCROLL_POLICY:
+		map->priv->hscroll_policy = g_value_get_enum (value);
+		gtk_widget_queue_resize (GTK_WIDGET (map));
+		break;
+	case PROP_VSCROLL_POLICY:
+		map->priv->vscroll_policy = g_value_get_enum (value);
+		gtk_widget_queue_resize (GTK_WIDGET (map));
+		break;
+
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+		break;
+	}
+}
+
+static void
+e_map_get_property (GObject *object,
+                    guint property_id,
+                    GValue *value,
+                    GParamSpec *pspec)
+{
+	EMap *map;
+
+	map = E_MAP (object);
+
+	switch (property_id) {
+	case PROP_HADJUSTMENT:
+		g_value_set_object (value, map->priv->hadjustment);
+		break;
+	case PROP_VADJUSTMENT:
+		g_value_set_object (value, map->priv->vadjustment);
+		break;
+	case PROP_HSCROLL_POLICY:
+		g_value_set_enum (value, map->priv->hscroll_policy);
+		break;
+	case PROP_VSCROLL_POLICY:
+		g_value_set_enum (value, map->priv->vscroll_policy);
+		break;
+
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+		break;
+	}
+}
+
+static void
+e_map_finalize (GObject *object)
+{
+	EMap *map;
+
+	map = E_MAP (object);
+
+	while (map->priv->tweens)
+		e_map_tween_destroy (map, map->priv->tweens->data);
+	e_map_stop_tweening (map);
+
+	if (map->priv->map_pixbuf) {
+		g_object_unref (map->priv->map_pixbuf);
+		map->priv->map_pixbuf = NULL;
+	}
+
+	/* gone in unrealize */
+	g_assert (map->priv->map_render_surface == NULL);
+
+	G_OBJECT_CLASS (e_map_parent_class)->finalize (object);
+}
+
+static void
+e_map_realize (GtkWidget *widget)
+{
+	GtkAllocation allocation;
+	GdkWindowAttr attr;
+	GdkWindow *window;
+	gint attr_mask;
+
+	g_return_if_fail (widget != NULL);
+	g_return_if_fail (E_IS_MAP (widget));
+
+	gtk_widget_set_realized (widget, TRUE);
+
+	gtk_widget_get_allocation (widget, &allocation);
+
+	attr.window_type = GDK_WINDOW_CHILD;
+	attr.x = allocation.x;
+	attr.y = allocation.y;
+	attr.width = allocation.width;
+	attr.height = allocation.height;
+	attr.wclass = GDK_INPUT_OUTPUT;
+	attr.visual = gtk_widget_get_visual (widget);
+	attr.event_mask = gtk_widget_get_events (widget) |
+	  GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK | GDK_KEY_PRESS_MASK |
+	  GDK_POINTER_MOTION_MASK;
+
+	attr_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL;
+
+	window = gdk_window_new (
+		gtk_widget_get_parent_window (widget), &attr, attr_mask);
+	gtk_widget_set_window (widget, window);
+	gdk_window_set_user_data (window, widget);
+
+	update_render_surface (E_MAP (widget), TRUE);
+}
+
+static void
+e_map_unrealize (GtkWidget *widget)
+{
+	EMap *map = E_MAP (widget);
+
+	cairo_surface_destroy (map->priv->map_render_surface);
+	map->priv->map_render_surface = NULL;
+
+	if (GTK_WIDGET_CLASS (e_map_parent_class)->unrealize)
+		(*GTK_WIDGET_CLASS (e_map_parent_class)->unrealize) (widget);
+}
+
+static void
+e_map_get_preferred_width (GtkWidget *widget,
+                           gint *minimum,
+                           gint *natural)
+{
+	EMap *map;
+
+	g_return_if_fail (widget != NULL);
+	g_return_if_fail (E_IS_MAP (widget));
+
+	map = E_MAP (widget);
+
+	/* TODO: Put real sizes here. */
+
+	*minimum = *natural = gdk_pixbuf_get_width (map->priv->map_pixbuf);
+}
+
+static void
+e_map_get_preferred_height (GtkWidget *widget,
+                            gint *minimum,
+                            gint *natural)
+{
+	EMap *view;
+	EMapPrivate *priv;
+
+	g_return_if_fail (widget != NULL);
+	g_return_if_fail (E_IS_MAP (widget));
+
+	view = E_MAP (widget);
+	priv = view->priv;
+
+	/* TODO: Put real sizes here. */
+
+	*minimum = *natural = gdk_pixbuf_get_height (priv->map_pixbuf);
+}
+
+static void
+e_map_size_allocate (GtkWidget *widget,
+                     GtkAllocation *allocation)
+{
+	EMap *map;
+
+	g_return_if_fail (widget != NULL);
+	g_return_if_fail (E_IS_MAP (widget));
+	g_return_if_fail (allocation != NULL);
+
+	map = E_MAP (widget);
+
+	/* Resize the window */
+
+	gtk_widget_set_allocation (widget, allocation);
+
+	if (gtk_widget_get_realized (widget)) {
+		GdkWindow *window;
+
+		window = gtk_widget_get_window (widget);
+
+		gdk_window_move_resize (
+			window, allocation->x, allocation->y,
+			allocation->width, allocation->height);
+
+		gtk_widget_queue_draw (widget);
+	}
+
+	update_render_surface (map, TRUE);
+}
+
+static gboolean
+e_map_draw (GtkWidget *widget,
+            cairo_t *cr)
+{
+	EMap *map;
+	cairo_matrix_t matrix;
+
+	if (!gtk_widget_is_drawable (widget))
+		return FALSE;
+
+	map = E_MAP (widget);
+
+	cairo_save (cr);
+
+	e_map_tweens_compute_matrix (map, &matrix);
+	cairo_transform (cr, &matrix);
+
+	cairo_set_source_surface (cr, map->priv->map_render_surface, 0, 0);
+	cairo_paint (cr);
+
+	cairo_restore (cr);
+
+	return FALSE;
+}
+
+static gint
+e_map_button_press (GtkWidget *widget,
+                    GdkEventButton *event)
+{
+	if (!gtk_widget_has_focus (widget))
+		gtk_widget_grab_focus (widget);
+
+	return TRUE;
+}
+
+static gint
+e_map_button_release (GtkWidget *widget,
+                      GdkEventButton *event)
+{
+	if (event->button != 1)
+		return FALSE;
+
+	gdk_device_ungrab (event->device, event->time);
+	return TRUE;
+}
+
+static gint
+e_map_motion (GtkWidget *widget,
+              GdkEventMotion *event)
+{
+	return FALSE;
+}
+
+static gint
+e_map_key_press (GtkWidget *widget,
+                 GdkEventKey *event)
+{
+	EMap *map;
+	gboolean do_scroll;
+	gint xofs, yofs;
+
+	map = E_MAP (widget);
+
+	switch (event->keyval)
+	{
+		case GDK_KEY_Up:
+			do_scroll = TRUE;
+			xofs = 0;
+			yofs = -SCROLL_STEP_SIZE;
+			break;
+
+		case GDK_KEY_Down:
+			do_scroll = TRUE;
+			xofs = 0;
+			yofs = SCROLL_STEP_SIZE;
+			break;
+
+		case GDK_KEY_Left:
+			do_scroll = TRUE;
+			xofs = -SCROLL_STEP_SIZE;
+			yofs = 0;
+			break;
+
+		case GDK_KEY_Right:
+			do_scroll = TRUE;
+			xofs = SCROLL_STEP_SIZE;
+			yofs = 0;
+			break;
+
+		default:
+			return FALSE;
+	}
+
+	if (do_scroll) {
+		gint page_size;
+		gint upper;
+		gint x, y;
+
+		page_size = gtk_adjustment_get_page_size (map->priv->hadjustment);
+		upper = gtk_adjustment_get_upper (map->priv->hadjustment);
+		x = CLAMP (map->priv->xofs + xofs, 0, upper - page_size);
+
+		page_size = gtk_adjustment_get_page_size (map->priv->vadjustment);
+		upper = gtk_adjustment_get_upper (map->priv->vadjustment);
+		y = CLAMP (map->priv->yofs + yofs, 0, upper - page_size);
+
+		scroll_to (map, x, y);
+
+		gtk_adjustment_set_value (map->priv->hadjustment, x);
+		gtk_adjustment_set_value (map->priv->vadjustment, y);
+	}
+
+	return TRUE;
+}
+
+static void
+e_map_class_init (EMapClass *class)
+{
+	GObjectClass *object_class;
+	GtkWidgetClass *widget_class;
+
+	g_type_class_add_private (class, sizeof (EMapPrivate));
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->set_property = e_map_set_property;
+	object_class->get_property = e_map_get_property;
+	object_class->finalize = e_map_finalize;
+
+	/* Scrollable interface properties */
+	g_object_class_override_property (
+		object_class, PROP_HADJUSTMENT, "hadjustment");
+	g_object_class_override_property (
+		object_class, PROP_VADJUSTMENT, "vadjustment");
+	g_object_class_override_property (
+		object_class, PROP_HSCROLL_POLICY, "hscroll-policy");
+	g_object_class_override_property (
+		object_class, PROP_VSCROLL_POLICY, "vscroll-policy");
+
+	widget_class = GTK_WIDGET_CLASS (class);
+	widget_class->realize = e_map_realize;
+	widget_class->unrealize = e_map_unrealize;
+	widget_class->get_preferred_height = e_map_get_preferred_height;
+	widget_class->get_preferred_width = e_map_get_preferred_width;
+	widget_class->size_allocate = e_map_size_allocate;
+	widget_class->draw = e_map_draw;
+	widget_class->button_press_event = e_map_button_press;
+	widget_class->button_release_event = e_map_button_release;
+	widget_class->motion_notify_event = e_map_motion;
+	widget_class->key_press_event = e_map_key_press;
+}
+
+static void
+e_map_init (EMap *map)
+{
+	GtkWidget *widget;
+	gchar *map_file_name;
+
+	map_file_name = g_build_filename (
+		EVOLUTION_IMAGESDIR, "world_map-960.png", NULL);
+
+	widget = GTK_WIDGET (map);
+
+	map->priv = E_MAP_GET_PRIVATE (map);
+
+	load_map_background (map, map_file_name);
+	g_free (map_file_name);
+	map->priv->frozen = FALSE;
+	map->priv->smooth_zoom = TRUE;
+	map->priv->zoom_state = E_MAP_ZOOMED_OUT;
+	map->priv->points = g_ptr_array_new ();
+
+	gtk_widget_set_can_focus (widget, TRUE);
+	gtk_widget_set_has_window (widget, TRUE);
+}
+
+/* ---------------- *
+ * Widget interface *
+ * ---------------- */
+
+/**
+ * e_map_new:
+ * @void:
+ *
+ * Creates a new empty map widget.
+ *
+ * Return value: A newly-created map widget.
+ **/
+
+EMap *
+e_map_new (void)
+{
+	GtkWidget *widget;
+	AtkObject *a11y;
+
+	widget = g_object_new (E_TYPE_MAP, NULL);
+	a11y = gtk_widget_get_accessible (widget);
+	atk_object_set_name (a11y, _("World Map"));
+	atk_object_set_role (a11y, ATK_ROLE_IMAGE);
+	atk_object_set_description (
+		a11y, _("Mouse-based interactive map widget for selecting "
+		"timezone. Keyboard users should instead select the timezone "
+		"from the drop-down combination box below."));
+	return (E_MAP (widget));
+}
+
+/* --- Coordinate translation --- */
+
+/* These functions translate coordinates between longitude/latitude and
+ * the image x/y offsets, using the equidistant cylindrical projection.
+ *
+ * Longitude E <-180, 180]
+ * Latitude  E <-90, 90]   */
+
+void
+e_map_window_to_world (EMap *map,
+                       gdouble win_x,
+                       gdouble win_y,
+                       gdouble *world_longitude,
+                       gdouble *world_latitude)
+{
+	gint width, height;
+
+	g_return_if_fail (map);
+
+	g_return_if_fail (gtk_widget_get_realized (GTK_WIDGET (map)));
+
+	width = E_MAP_GET_WIDTH (map);
+	height = E_MAP_GET_HEIGHT (map);
+
+	*world_longitude = (win_x + map->priv->xofs - (gdouble) width / 2.0) /
+		((gdouble) width / 2.0) * 180.0;
+	*world_latitude = ((gdouble) height / 2.0 - win_y - map->priv->yofs) /
+		((gdouble) height / 2.0) * 90.0;
+}
+
+void
+e_map_world_to_window (EMap *map,
+                       gdouble world_longitude,
+                       gdouble world_latitude,
+                       gdouble *win_x,
+                       gdouble *win_y)
+{
+	g_return_if_fail (E_IS_MAP (map));
+	g_return_if_fail (gtk_widget_get_realized (GTK_WIDGET (map)));
+	g_return_if_fail (world_longitude >= -180.0 && world_longitude <= 180.0);
+	g_return_if_fail (world_latitude >= -90.0 && world_latitude <= 90.0);
+
+	e_map_world_to_render_surface (
+		map, world_longitude, world_latitude, win_x, win_y);
+
+	*win_x -= map->priv->xofs;
+	*win_y -= map->priv->yofs;
+}
+
+/* --- Zoom --- */
+
+gdouble
+e_map_get_magnification (EMap *map)
+{
+	if (map->priv->zoom_state == E_MAP_ZOOMED_IN) return 2.0;
+	else return 1.0;
+}
+
+static void
+e_map_set_zoom (EMap *map,
+                EMapZoomState zoom)
+{
+	if (map->priv->zoom_state == zoom)
+		return;
+
+	map->priv->zoom_state = zoom;
+	update_render_surface (map, TRUE);
+	gtk_widget_queue_draw (GTK_WIDGET (map));
+}
+
+void
+e_map_zoom_to_location (EMap *map,
+                        gdouble longitude,
+                        gdouble latitude)
+{
+	gdouble prevlong, prevlat;
+	gdouble prevzoom;
+
+	g_return_if_fail (map);
+	g_return_if_fail (gtk_widget_get_realized (GTK_WIDGET (map)));
+
+	e_map_get_current_location (map, &prevlong, &prevlat);
+	prevzoom = e_map_get_magnification (map);
+
+	e_map_set_zoom (map, E_MAP_ZOOMED_IN);
+	center_at (map, longitude, latitude);
+
+	e_map_tween_new_from (
+		map, E_MAP_TWEEN_DURATION_MSECS,
+		prevlong, prevlat, prevzoom);
+}
+
+void
+e_map_zoom_out (EMap *map)
+{
+	gdouble longitude, latitude;
+	gdouble prevzoom;
+
+	g_return_if_fail (map);
+	g_return_if_fail (gtk_widget_get_realized (GTK_WIDGET (map)));
+
+	e_map_get_current_location (map, &longitude, &latitude);
+	prevzoom = e_map_get_magnification (map);
+	e_map_set_zoom (map, E_MAP_ZOOMED_OUT);
+	center_at (map, longitude, latitude);
+
+	e_map_tween_new_from (
+		map, E_MAP_TWEEN_DURATION_MSECS,
+		longitude, latitude, prevzoom);
+}
+
+void
+e_map_set_smooth_zoom (EMap *map,
+                       gboolean state)
+{
+	((EMapPrivate *) map->priv)->smooth_zoom = state;
+}
+
+gboolean
+e_map_get_smooth_zoom (EMap *map)
+{
+	return (((EMapPrivate *) map->priv)->smooth_zoom);
+}
+
+void
+e_map_freeze (EMap *map)
+{
+	((EMapPrivate *) map->priv)->frozen = TRUE;
+}
+
+void
+e_map_thaw (EMap *map)
+{
+	((EMapPrivate *) map->priv)->frozen = FALSE;
+	update_and_paint (map);
+}
+
+/* --- Point manipulation --- */
+
+EMapPoint *
+e_map_add_point (EMap *map,
+                 gchar *name,
+                 gdouble longitude,
+                 gdouble latitude,
+                 guint32 color_rgba)
+{
+	EMapPoint *point;
+
+	point = g_new0 (EMapPoint, 1);
+
+	point->name = name;  /* Can be NULL */
+	point->longitude = longitude;
+	point->latitude = latitude;
+	point->rgba = color_rgba;
+
+	g_ptr_array_add (map->priv->points, (gpointer) point);
+
+	if (!map->priv->frozen)
+	{
+		update_render_point (map, point);
+		repaint_point (map, point);
+	}
+
+	return point;
+}
+
+void
+e_map_remove_point (EMap *map,
+                    EMapPoint *point)
+{
+	g_ptr_array_remove (map->priv->points, point);
+
+	if (!((EMapPrivate *) map->priv)->frozen)
+	{
+		/* FIXME: Re-scaling the whole pixbuf is more than a little
+		 * overkill when just one point is removed */
+
+		update_render_surface (map, TRUE);
+		repaint_point (map, point);
+	}
+
+	g_free (point);
+}
+
+void
+e_map_point_get_location (EMapPoint *point,
+                          gdouble *longitude,
+                          gdouble *latitude)
+{
+	*longitude = point->longitude;
+	*latitude = point->latitude;
+}
+
+gchar *
+e_map_point_get_name (EMapPoint *point)
+{
+	return point->name;
+}
+
+guint32
+e_map_point_get_color_rgba (EMapPoint *point)
+{
+	return point->rgba;
+}
+
+void
+e_map_point_set_color_rgba (EMap *map,
+                            EMapPoint *point,
+                            guint32 color_rgba)
+{
+	point->rgba = color_rgba;
+
+	if (!((EMapPrivate *) map->priv)->frozen)
+	{
+		/* TODO: Redraw area around point only */
+
+		update_render_point (map, point);
+		repaint_point (map, point);
+	}
+}
+
+void
+e_map_point_set_data (EMapPoint *point,
+                      gpointer data)
+{
+	point->user_data = data;
+}
+
+gpointer
+e_map_point_get_data (EMapPoint *point)
+{
+	return point->user_data;
+}
+
+gboolean
+e_map_point_is_in_view (EMap *map,
+                        EMapPoint *point)
+{
+	GtkAllocation allocation;
+	gdouble x, y;
+
+	if (!map->priv->map_render_surface) return FALSE;
+
+	e_map_world_to_window (map, point->longitude, point->latitude, &x, &y);
+	gtk_widget_get_allocation (GTK_WIDGET (map), &allocation);
+
+	if (x >= 0 && x < allocation.width &&
+	    y >= 0 && y < allocation.height)
+		return TRUE;
+
+	return FALSE;
+}
+
+EMapPoint *
+e_map_get_closest_point (EMap *map,
+                         gdouble longitude,
+                         gdouble latitude,
+                         gboolean in_view)
+{
+	EMapPoint *point_chosen = NULL, *point;
+	gdouble min_dist = 0.0, dist;
+	gdouble dx, dy;
+	gint i;
+
+	for (i = 0; i < map->priv->points->len; i++)
+	{
+		point = g_ptr_array_index (map->priv->points, i);
+		if (in_view && !e_map_point_is_in_view (map, point)) continue;
+
+		dx = point->longitude - longitude;
+		dy = point->latitude - latitude;
+		dist = dx * dx + dy * dy;
+
+		if (!point_chosen || dist < min_dist)
+		{
+			min_dist = dist;
+			point_chosen = point;
+		}
+	}
+
+	return point_chosen;
+}
+
+/* ------------------ *
+ * Internal functions *
+ * ------------------ */
+
+static void
+update_and_paint (EMap *map)
+{
+	update_render_surface (map, TRUE);
+	gtk_widget_queue_draw (GTK_WIDGET (map));
+}
+
+static gint
+load_map_background (EMap *map,
+                     gchar *name)
+{
+	GdkPixbuf *pb0;
+
+	pb0 = gdk_pixbuf_new_from_file (name, NULL);
+	if (!pb0)
+		return FALSE;
+
+	if (map->priv->map_pixbuf) g_object_unref (map->priv->map_pixbuf);
+	map->priv->map_pixbuf = pb0;
+	update_render_surface (map, TRUE);
+
+	return TRUE;
+}
+
+static void
+update_render_surface (EMap *map,
+                       gboolean render_overlays)
+{
+	EMapPoint *point;
+	GtkAllocation allocation;
+	gint width, height, orig_width, orig_height;
+	gdouble zoom;
+	gint i;
+
+	if (!gtk_widget_get_realized (GTK_WIDGET (map)))
+		return;
+
+	gtk_widget_get_allocation (GTK_WIDGET (map), &allocation);
+
+	/* Set up value shortcuts */
+
+	width = allocation.width;
+	height = allocation.height;
+	orig_width = gdk_pixbuf_get_width (map->priv->map_pixbuf);
+	orig_height = gdk_pixbuf_get_height (map->priv->map_pixbuf);
+
+	/* Compute scaled width and height based on the extreme dimension */
+
+	if ((gdouble) width / orig_width > (gdouble) height / orig_height)
+		zoom = (gdouble) width / (gdouble) orig_width;
+	else
+		zoom = (gdouble) height / (gdouble) orig_height;
+
+	if (map->priv->zoom_state == E_MAP_ZOOMED_IN)
+		zoom *= 2.0;
+	height = (orig_height * zoom) + 0.5;
+	width = (orig_width * zoom) + 0.5;
+
+	/* Reallocate the pixbuf */
+
+	if (map->priv->map_render_surface)
+		cairo_surface_destroy (map->priv->map_render_surface);
+	map->priv->map_render_surface = gdk_window_create_similar_surface (
+		gtk_widget_get_window (GTK_WIDGET (map)),
+		CAIRO_CONTENT_COLOR, width, height);
+
+	/* Scale the original map into the rendering pixbuf */
+
+	if (width > 1 && height > 1) {
+		cairo_t *cr = cairo_create (map->priv->map_render_surface);
+		cairo_scale (
+			cr,
+			(gdouble) width / orig_width,
+			(gdouble) height / orig_height);
+		gdk_cairo_set_source_pixbuf (cr, map->priv->map_pixbuf, 0, 0);
+		cairo_paint (cr);
+		cairo_destroy (cr);
+	}
+
+	/* Compute image offsets with respect to window */
+
+	set_scroll_area (map, width, height);
+
+	if (render_overlays) {
+		/* Add points */
+
+		for (i = 0; i < map->priv->points->len; i++) {
+			point = g_ptr_array_index (map->priv->points, i);
+			update_render_point (map, point);
+		}
+	}
+}
+
+/* Redraw point in client surface */
+
+static void
+update_render_point (EMap *map,
+                     EMapPoint *point)
+{
+	cairo_t *cr;
+	gdouble px, py;
+	static guchar mask1[] = { 0x00, 0x00, 0xff, 0x00, 0x00,  0x00, 0x00, 0x00,
+				  0x00, 0xff, 0x00, 0xff, 0x00,  0x00, 0x00, 0x00,
+				  0xff, 0x00, 0x00, 0x00, 0xff,  0x00, 0x00, 0x00,
+				  0x00, 0xff, 0x00, 0xff, 0x00,  0x00, 0x00, 0x00,
+				  0x00, 0x00, 0xff, 0x00, 0x00,  0x00, 0x00, 0x00 };
+	static guchar mask2[] = { 0x00, 0xff, 0x00,  0x00,
+				  0xff, 0xff, 0xff,  0x00,
+				  0x00, 0xff, 0x00,  0x00 };
+	cairo_surface_t *mask;
+
+	if (map->priv->map_render_surface == NULL)
+		return;
+
+	cr = cairo_create (map->priv->map_render_surface);
+	cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
+
+	e_map_world_to_window (map, point->longitude, point->latitude, &px, &py);
+	px = floor (px + map->priv->xofs);
+	py = floor (py + map->priv->yofs);
+
+	cairo_set_source_rgb (cr, 0, 0, 0);
+	mask = cairo_image_surface_create_for_data (mask1, CAIRO_FORMAT_A8, 5, 5, 8);
+	cairo_mask_surface (cr, mask, px - 2, py - 2);
+	cairo_surface_destroy (mask);
+
+	cairo_set_source_rgba (
+		cr,
+		((point->rgba >> 24) & 0xff) / 255.0,
+		((point->rgba >> 16) & 0xff) / 255.0,
+		((point->rgba >>  8) & 0xff) / 255.0,
+		( point->rgba	& 0xff) / 255.0);
+	mask = cairo_image_surface_create_for_data (mask2, CAIRO_FORMAT_A8, 3, 3, 4);
+	cairo_mask_surface (cr, mask, px - 1, py - 1);
+	cairo_surface_destroy (mask);
+
+	cairo_destroy (cr);
+}
+
+/* Repaint point on X server */
+
+static void
+repaint_point (EMap *map,
+               EMapPoint *point)
+{
+	gdouble px, py;
+
+	if (!gtk_widget_is_drawable (GTK_WIDGET (map)))
+		return;
+
+	e_map_world_to_window (map, point->longitude, point->latitude, &px, &py);
+
+	gtk_widget_queue_draw_area (
+		GTK_WIDGET (map),
+		(gint) px - 2, (gint) py - 2,
+		5, 5);
+}
+
+static void
+center_at (EMap *map,
+           gdouble longitude,
+           gdouble latitude)
+{
+	GtkAllocation allocation;
+	gint pb_width, pb_height;
+	gdouble x, y;
+
+	e_map_world_to_render_surface (map, longitude, latitude, &x, &y);
+
+	pb_width = E_MAP_GET_WIDTH (map);
+	pb_height = E_MAP_GET_HEIGHT (map);
+
+	gtk_widget_get_allocation (GTK_WIDGET (map), &allocation);
+
+	x = CLAMP (x - (allocation.width / 2), 0, pb_width - allocation.width);
+	y = CLAMP (y - (allocation.height / 2), 0, pb_height - allocation.height);
+
+	gtk_adjustment_set_value (map->priv->hadjustment, x);
+	gtk_adjustment_set_value (map->priv->vadjustment, y);
+
+	gtk_widget_queue_draw (GTK_WIDGET (map));
+}
+
+/* Scrolls the view to the specified offsets.  Does not perform range checking!  */
+
+static void
+scroll_to (EMap *map,
+           gint x,
+           gint y)
+{
+	gint xofs, yofs;
+
+	/* Compute offsets and check bounds */
+
+	xofs = x - map->priv->xofs;
+	yofs = y - map->priv->yofs;
+
+	if (xofs == 0 && yofs == 0)
+		return;
+
+	map->priv->xofs = x;
+	map->priv->yofs = y;
+
+	gtk_widget_queue_draw (GTK_WIDGET (map));
+}
+
+static void
+set_scroll_area (EMap *view,
+                 gint width,
+                 gint height)
+{
+	EMapPrivate *priv;
+	GtkAllocation allocation;
+
+	priv = view->priv;
+
+	if (!gtk_widget_get_realized (GTK_WIDGET (view)))
+		return;
+
+	if (!priv->hadjustment || !priv->vadjustment)
+		return;
+
+	g_object_freeze_notify (G_OBJECT (priv->hadjustment));
+	g_object_freeze_notify (G_OBJECT (priv->vadjustment));
+
+	gtk_widget_get_allocation (GTK_WIDGET (view), &allocation);
+
+	priv->xofs = CLAMP (priv->xofs, 0, width - allocation.width);
+	priv->yofs = CLAMP (priv->yofs, 0, height - allocation.height);
+
+	gtk_adjustment_configure (
+		priv->hadjustment,
+		priv->xofs,
+		0, width,
+		SCROLL_STEP_SIZE,
+		allocation.width / 2,
+		allocation.width);
+	gtk_adjustment_configure (
+		priv->vadjustment,
+		priv->yofs,
+		0, height,
+		SCROLL_STEP_SIZE,
+		allocation.height / 2,
+		allocation.height);
+
+	g_object_thaw_notify (G_OBJECT (priv->hadjustment));
+	g_object_thaw_notify (G_OBJECT (priv->vadjustment));
+}
diff --git a/e-util/e-map.h b/e-util/e-map.h
new file mode 100644
index 0000000..cb2923d
--- /dev/null
+++ b/e-util/e-map.h
@@ -0,0 +1,155 @@
+/*
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Hans Petter Jansson <hpj ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_MAP_H
+#define E_MAP_H
+
+#include <gtk/gtk.h>
+
+/* Standard GObject macros */
+#define E_TYPE_MAP \
+	(e_map_get_type ())
+#define E_MAP(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_MAP, EMap))
+#define E_MAP_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_MAP, EMapClass))
+#define E_IS_MAP(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_MAP))
+#define E_IS_MAP_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_MAP))
+#define E_MAP_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_MAP, EMapClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EMap EMap;
+typedef struct _EMapClass EMapClass;
+typedef struct _EMapPrivate EMapPrivate;
+typedef struct _EMapPoint EMapPoint;
+
+struct _EMap {
+	GtkWidget widget;
+	EMapPrivate *priv;
+};
+
+struct _EMapClass {
+	GtkWidgetClass parent_class;
+
+	/* Notification signals */
+	void (*zoom_fit) (EMap * view);
+
+	/* GTK+ scrolling interface */
+	void (*set_scroll_adjustments) (GtkWidget * widget,
+					GtkAdjustment * hadj,
+					GtkAdjustment * vadj);
+};
+
+/* The definition of Dot */
+
+struct _EMapPoint {
+	gchar *name;  /* Can be NULL */
+	gdouble longitude, latitude;
+	guint32 rgba;
+	gpointer user_data;
+};
+
+/* --- Widget --- */
+
+GType e_map_get_type (void);
+
+EMap *e_map_new (void);
+
+/* Stop doing redraws when map data changes (e.g. by modifying points) */
+void e_map_freeze (EMap *map);
+
+/* Do an immediate repaint, and start doing realtime repaints again */
+void e_map_thaw (EMap *map);
+
+/* --- Coordinate translation --- */
+
+/* Translates window-relative coords to lat/long */
+void e_map_window_to_world (EMap *map,
+			    gdouble win_x, gdouble win_y,
+			    gdouble *world_longitude, gdouble *world_latitude);
+
+/* Translates lat/long to window-relative coordinates. Note that the
+ * returned coordinates can be negative or greater than the current size
+ * of the allocation area */
+void e_map_world_to_window (EMap *map,
+			    gdouble world_longitude, gdouble world_latitude,
+			    gdouble *win_x, gdouble *win_y);
+
+/* --- Zoom --- */
+
+gdouble e_map_get_magnification (EMap *map);
+
+/* Pass TRUE if we want the smooth zoom hack */
+void e_map_set_smooth_zoom (EMap *map, gboolean state);
+
+/* TRUE if smooth zoom hack will be employed */
+gboolean e_map_get_smooth_zoom (EMap *map);
+
+/* NB: Function definition will change shortly */
+void e_map_zoom_to_location (EMap *map, gdouble longitude, gdouble latitude);
+
+/* Zoom to mag factor 1.0 */
+void e_map_zoom_out (EMap *map);
+
+/* --- Points --- */
+
+EMapPoint *e_map_add_point (EMap *map, gchar *name,
+			    gdouble longitude, gdouble latitude,
+			    guint32 color_rgba);
+
+void e_map_remove_point (EMap *map, EMapPoint *point);
+
+void e_map_point_get_location (EMapPoint *point,
+			       gdouble *longitude, gdouble *latitude);
+
+gchar *e_map_point_get_name (EMapPoint *point);
+
+guint32 e_map_point_get_color_rgba (EMapPoint *point);
+
+void e_map_point_set_color_rgba (EMap *map, EMapPoint *point, guint32 color_rgba);
+
+void e_map_point_set_data (EMapPoint *point, gpointer data);
+
+gpointer e_map_point_get_data (EMapPoint *point);
+
+gboolean e_map_point_is_in_view (EMap *map, EMapPoint *point);
+
+EMapPoint *e_map_get_closest_point (EMap *map, gdouble longitude, gdouble latitude,
+				    gboolean in_view);
+
+G_END_DECLS
+
+#endif
diff --git a/widgets/misc/e-menu-tool-action.c b/e-util/e-menu-tool-action.c
similarity index 100%
rename from widgets/misc/e-menu-tool-action.c
rename to e-util/e-menu-tool-action.c
diff --git a/e-util/e-menu-tool-action.h b/e-util/e-menu-tool-action.h
new file mode 100644
index 0000000..aee4768
--- /dev/null
+++ b/e-util/e-menu-tool-action.h
@@ -0,0 +1,75 @@
+/*
+ * e-menu-tool-action.h
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+/* This is a trivial GtkAction subclass that sets the toolbar
+ * item type to GtkMenuToolButton instead of GtkToolButton. */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_MENU_TOOL_ACTION_H
+#define E_MENU_TOOL_ACTION_H
+
+#include <gtk/gtk.h>
+
+/* Standard GObject macros */
+#define E_TYPE_MENU_TOOL_ACTION \
+	(e_menu_tool_action_get_type ())
+#define E_MENU_TOOL_ACTION(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_MENU_TOOL_ACTION, EMenuToolAction))
+#define E_MENU_TOOL_ACTION_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_MENU_TOOL_ACTION, EMenuToolActionClass))
+#define E_IS_MENU_TOOL_ACTION(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_MENU_TOOL_ACTION))
+#define E_IS_MENU_TOOL_ACTION_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_MENU_TOOL_ACTION))
+#define E_MENU_TOOL_ACTION_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_MENU_TOOL_ACTION, EMenuToolActionClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EMenuToolAction EMenuToolAction;
+typedef struct _EMenuToolActionClass EMenuToolActionClass;
+
+struct _EMenuToolAction {
+	GtkAction parent;
+};
+
+struct _EMenuToolActionClass {
+	GtkActionClass parent_class;
+};
+
+GType		e_menu_tool_action_get_type	(void);
+EMenuToolAction *
+		e_menu_tool_action_new		(const gchar *name,
+						 const gchar *label,
+						 const gchar *tooltip,
+						 const gchar *stock_id);
+
+G_END_DECLS
+
+#endif /* E_MENU_TOOL_ACTION_H */
diff --git a/widgets/misc/e-menu-tool-button.c b/e-util/e-menu-tool-button.c
similarity index 100%
rename from widgets/misc/e-menu-tool-button.c
rename to e-util/e-menu-tool-button.c
diff --git a/e-util/e-menu-tool-button.h b/e-util/e-menu-tool-button.h
new file mode 100644
index 0000000..0451995
--- /dev/null
+++ b/e-util/e-menu-tool-button.h
@@ -0,0 +1,77 @@
+/*
+ * e-menu-tool-button.h
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+/* EMenuToolButton is a variation of GtkMenuToolButton where the
+ * button icon always reflects the first menu item, and clicking
+ * the button activates the first menu item. */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_MENU_TOOL_BUTTON_H
+#define E_MENU_TOOL_BUTTON_H
+
+#include <gtk/gtk.h>
+
+/* Standard GObject macros */
+#define E_TYPE_MENU_TOOL_BUTTON \
+	(e_menu_tool_button_get_type ())
+#define E_MENU_TOOL_BUTTON(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_MENU_TOOL_BUTTON, EMenuToolButton))
+#define E_MENU_TOOL_BUTTON_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_MENU_TOOL_BUTTON, EMenuToolButtonClass))
+#define E_IS_MENU_TOOL_BUTTON(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_MENU_TOOL_BUTTON))
+#define E_IS_MENU_TOOL_BUTTON_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_MENU_TOOL_BUTTON))
+#define E_MENU_TOOL_BUTTON_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_MENU_TOOL_BUTTON, EMenuToolButtonClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EMenuToolButton EMenuToolButton;
+typedef struct _EMenuToolButtonPrivate EMenuToolButtonPrivate;
+typedef struct _EMenuToolButtonClass EMenuToolButtonClass;
+
+struct _EMenuToolButton {
+	GtkMenuToolButton parent;
+	EMenuToolButtonPrivate *priv;
+};
+
+struct _EMenuToolButtonClass {
+	GtkMenuToolButtonClass parent_class;
+};
+
+GType		e_menu_tool_button_get_type		(void);
+GtkToolItem *	e_menu_tool_button_new			(const gchar *label);
+void		e_menu_tool_button_set_prefer_item	(EMenuToolButton *button,
+							 const gchar *prefer_item);
+const gchar *	e_menu_tool_button_get_prefer_item	(EMenuToolButton *button);
+
+G_END_DECLS
+
+#endif /* E_MENU_TOOL_BUTTON_H */
diff --git a/e-util/e-misc-utils.c b/e-util/e-misc-utils.c
new file mode 100644
index 0000000..7d1a0c6
--- /dev/null
+++ b/e-util/e-misc-utils.c
@@ -0,0 +1,1807 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-misc-utils.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <math.h>
+#include <string.h>
+#include <locale.h>
+#include <time.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include <gio/gio.h>
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+#include <glib/gstdio.h>
+
+#ifdef G_OS_WIN32
+#include <windows.h>
+#endif
+
+#include <camel/camel.h>
+#include <libedataserver/libedataserver.h>
+
+#include "e-filter-option.h"
+#include "e-util-private.h"
+
+typedef struct _WindowData WindowData;
+
+struct _WindowData {
+	GtkWindow *window;
+	GSettings *settings;
+	ERestoreWindowFlags flags;
+	gint premax_width;
+	gint premax_height;
+	guint timeout_id;
+};
+
+static void
+window_data_free (WindowData *data)
+{
+	if (data->settings != NULL)
+		g_object_unref (data->settings);
+
+	if (data->timeout_id > 0)
+		g_source_remove (data->timeout_id);
+
+	g_slice_free (WindowData, data);
+}
+
+static gboolean
+window_update_settings (WindowData *data)
+{
+	GSettings *settings = data->settings;
+
+	if (data->flags & E_RESTORE_WINDOW_SIZE) {
+		GdkWindowState state;
+		GdkWindow *window;
+		gboolean maximized;
+
+		window = gtk_widget_get_window (GTK_WIDGET (data->window));
+		state = gdk_window_get_state (window);
+		maximized = ((state & GDK_WINDOW_STATE_MAXIMIZED) != 0);
+
+		g_settings_set_boolean (settings, "maximized", maximized);
+
+		if (!maximized) {
+			gint width, height;
+
+			gtk_window_get_size (data->window, &width, &height);
+
+			g_settings_set_int (settings, "width", width);
+			g_settings_set_int (settings, "height", height);
+		}
+	}
+
+	if (data->flags & E_RESTORE_WINDOW_POSITION) {
+		gint x, y;
+
+		gtk_window_get_position (data->window, &x, &y);
+
+		g_settings_set_int (settings, "x", x);
+		g_settings_set_int (settings, "y", y);
+	}
+
+	data->timeout_id = 0;
+
+	return FALSE;
+}
+
+static void
+window_delayed_update_settings (WindowData *data)
+{
+	if (data->timeout_id > 0)
+		g_source_remove (data->timeout_id);
+
+	data->timeout_id = g_timeout_add_seconds (
+		1, (GSourceFunc) window_update_settings, data);
+}
+
+static gboolean
+window_configure_event_cb (GtkWindow *window,
+                           GdkEventConfigure *event,
+                           WindowData *data)
+{
+	window_delayed_update_settings (data);
+
+	return FALSE;
+}
+
+static gboolean
+window_state_event_cb (GtkWindow *window,
+                       GdkEventWindowState *event,
+                       WindowData *data)
+{
+	gboolean window_was_unmaximized;
+
+	if (data->timeout_id > 0)
+		g_source_remove (data->timeout_id);
+
+	window_was_unmaximized =
+		((event->changed_mask & GDK_WINDOW_STATE_MAXIMIZED) != 0) &&
+		((event->new_window_state & GDK_WINDOW_STATE_MAXIMIZED) == 0);
+
+	if (window_was_unmaximized) {
+		gint width, height;
+
+		width = data->premax_width;
+		data->premax_width = 0;
+
+		height = data->premax_height;
+		data->premax_height = 0;
+
+		/* This only applies when the window is initially restored
+		 * as maximized and is then unmaximized.  GTK+ handles the
+		 * unmaximized window size thereafter. */
+		if (width > 0 && height > 0)
+			gtk_window_resize (window, width, height);
+	}
+
+	window_delayed_update_settings (data);
+
+	return FALSE;
+}
+
+static gboolean
+window_unmap_cb (GtkWindow *window,
+                 WindowData *data)
+{
+	if (data->timeout_id > 0)
+		g_source_remove (data->timeout_id);
+
+	/* It's too late to record the window position.
+	 * gtk_window_get_position() will report (0, 0). */
+	data->flags &= ~E_RESTORE_WINDOW_POSITION;
+
+	window_update_settings (data);
+
+	return FALSE;
+}
+
+/**
+ * e_get_accels_filename:
+ *
+ * Returns the name of the user data file containing custom keyboard
+ * accelerator specifications.
+ *
+ * Returns: filename for accelerator specifications
+ **/
+const gchar *
+e_get_accels_filename (void)
+{
+	static gchar *filename = NULL;
+
+	if (G_UNLIKELY (filename == NULL)) {
+		const gchar *config_dir = e_get_user_config_dir ();
+		filename = g_build_filename (config_dir, "accels", NULL);
+	}
+
+	return filename;
+}
+
+/**
+ * e_show_uri:
+ * @parent: a parent #GtkWindow or %NULL
+ * @uri: the URI to show
+ *
+ * Launches the default application to show the given URI.  The URI must
+ * be of a form understood by GIO.  If the URI cannot be shown, it presents
+ * a dialog describing the error.  The dialog is set as transient to @parent
+ * if @parent is non-%NULL.
+ **/
+void
+e_show_uri (GtkWindow *parent,
+            const gchar *uri)
+{
+	GtkWidget *dialog;
+	GdkScreen *screen = NULL;
+	GError *error = NULL;
+	guint32 timestamp;
+
+	g_return_if_fail (uri != NULL);
+
+	timestamp = gtk_get_current_event_time ();
+
+	if (parent != NULL)
+		screen = gtk_widget_get_screen (GTK_WIDGET (parent));
+
+	if (gtk_show_uri (screen, uri, timestamp, &error))
+		return;
+
+	dialog = gtk_message_dialog_new_with_markup (
+		parent, GTK_DIALOG_DESTROY_WITH_PARENT,
+		GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
+		"<big><b>%s</b></big>",
+		_("Could not open the link."));
+
+	gtk_message_dialog_format_secondary_text (
+		GTK_MESSAGE_DIALOG (dialog), "%s", error->message);
+
+	gtk_dialog_run (GTK_DIALOG (dialog));
+
+	gtk_widget_destroy (dialog);
+	g_error_free (error);
+}
+
+/**
+ * e_display_help:
+ * @parent: a parent #GtkWindow or %NULL
+ * @link_id: help section to present or %NULL
+ *
+ * Opens the user documentation to the section given by @link_id, or to the
+ * table of contents if @link_id is %NULL.  If the user documentation cannot
+ * be opened, it presents a dialog describing the error.  The dialog is set
+ * as transient to @parent if @parent is non-%NULL.
+ **/
+void
+e_display_help (GtkWindow *parent,
+                const gchar *link_id)
+{
+	GString *uri;
+	GtkWidget *dialog;
+	GdkScreen *screen = NULL;
+	GError *error = NULL;
+	guint32 timestamp;
+
+	uri = g_string_new ("help:" PACKAGE);
+	timestamp = gtk_get_current_event_time ();
+
+	if (parent != NULL)
+		screen = gtk_widget_get_screen (GTK_WIDGET (parent));
+
+	if (link_id != NULL)
+		g_string_append_printf (uri, "?%s", link_id);
+
+	if (gtk_show_uri (screen, uri->str, timestamp, &error))
+		goto exit;
+
+	dialog = gtk_message_dialog_new_with_markup (
+		parent, GTK_DIALOG_DESTROY_WITH_PARENT,
+		GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
+		"<big><b>%s</b></big>",
+		_("Could not display help for Evolution."));
+
+	gtk_message_dialog_format_secondary_text (
+		GTK_MESSAGE_DIALOG (dialog), "%s", error->message);
+
+	gtk_dialog_run (GTK_DIALOG (dialog));
+
+	gtk_widget_destroy (dialog);
+	g_error_free (error);
+
+exit:
+	g_string_free (uri, TRUE);
+}
+
+/**
+ * e_restore_window:
+ * @window: a #GtkWindow
+ * @settings_path: a #GSettings path
+ * @flags: flags indicating which window features to restore
+ *
+ * This function can restore one of or both a window's size and position
+ * using #GSettings keys at @settings_path which conform to the relocatable
+ * schema "org.gnome.evolution.window".
+ *
+ * If #E_RESTORE_WINDOW_SIZE is present in @flags, restore @window's
+ * previously recorded size and maximize state.
+ *
+ * If #E_RESTORE_WINDOW_POSITION is present in @flags, move @window to
+ * the previously recorded screen coordinates.
+ *
+ * The respective #GSettings values will be updated when the window is
+ * resized and/or moved.
+ **/
+void
+e_restore_window (GtkWindow *window,
+                  const gchar *settings_path,
+                  ERestoreWindowFlags flags)
+{
+	WindowData *data;
+	GSettings *settings;
+	const gchar *schema;
+
+	g_return_if_fail (GTK_IS_WINDOW (window));
+	g_return_if_fail (settings_path != NULL);
+
+	schema = "org.gnome.evolution.window";
+	settings = g_settings_new_with_path (schema, settings_path);
+
+	data = g_slice_new0 (WindowData);
+	data->window = window;
+	data->settings = g_object_ref (settings);
+	data->flags = flags;
+
+	if (flags & E_RESTORE_WINDOW_SIZE) {
+		gint width, height;
+
+		width = g_settings_get_int (settings, "width");
+		height = g_settings_get_int (settings, "height");
+
+		if (width > 0 && height > 0)
+			gtk_window_resize (window, width, height);
+
+		if (g_settings_get_boolean (settings, "maximized")) {
+			GdkScreen *screen;
+			GdkRectangle monitor_area;
+			gint x, y, monitor;
+
+			x = g_settings_get_int (settings, "x");
+			y = g_settings_get_int (settings, "y");
+
+			screen = gtk_window_get_screen (window);
+			gtk_window_get_size (window, &width, &height);
+
+			data->premax_width = width;
+			data->premax_height = height;
+
+			monitor = gdk_screen_get_monitor_at_point (screen, x, y);
+			if (monitor < 0 || monitor >= gdk_screen_get_n_monitors (screen))
+				monitor = 0;
+
+			gdk_screen_get_monitor_workarea (screen, monitor, &monitor_area);
+
+			gtk_window_resize (window, monitor_area.width, monitor_area.height);
+			gtk_window_maximize (window);
+		}
+	}
+
+	if (flags & E_RESTORE_WINDOW_POSITION) {
+		gint x, y;
+
+		x = g_settings_get_int (settings, "x");
+		y = g_settings_get_int (settings, "y");
+
+		gtk_window_move (window, x, y);
+	}
+
+	g_object_set_data_full (
+		G_OBJECT (window),
+		"e-util-window-data", data,
+		(GDestroyNotify) window_data_free);
+
+	g_signal_connect (
+		window, "configure-event",
+		G_CALLBACK (window_configure_event_cb), data);
+
+	g_signal_connect (
+		window, "window-state-event",
+		G_CALLBACK (window_state_event_cb), data);
+
+	g_signal_connect (
+		window, "unmap",
+		G_CALLBACK (window_unmap_cb), data);
+
+	g_object_unref (settings);
+}
+
+/**
+ * e_lookup_action:
+ * @ui_manager: a #GtkUIManager
+ * @action_name: the name of an action
+ *
+ * Returns the first #GtkAction named @action_name by traversing the
+ * list of action groups in @ui_manager.  If no such action exists, the
+ * function emits a critical warning before returning %NULL, since this
+ * probably indicates a programming error and most code is not prepared
+ * to deal with lookup failures.
+ *
+ * Returns: the first #GtkAction named @action_name
+ **/
+GtkAction *
+e_lookup_action (GtkUIManager *ui_manager,
+                 const gchar *action_name)
+{
+	GtkAction *action = NULL;
+	GList *iter;
+
+	g_return_val_if_fail (GTK_IS_UI_MANAGER (ui_manager), NULL);
+	g_return_val_if_fail (action_name != NULL, NULL);
+
+	iter = gtk_ui_manager_get_action_groups (ui_manager);
+
+	while (iter != NULL) {
+		GtkActionGroup *action_group = iter->data;
+
+		action = gtk_action_group_get_action (
+			action_group, action_name);
+		if (action != NULL)
+			return action;
+
+		iter = g_list_next (iter);
+	}
+
+	g_critical ("%s: action '%s' not found", G_STRFUNC, action_name);
+
+	return NULL;
+}
+
+/**
+ * e_lookup_action_group:
+ * @ui_manager: a #GtkUIManager
+ * @group_name: the name of an action group
+ *
+ * Returns the #GtkActionGroup in @ui_manager named @group_name.  If no
+ * such action group exists, the function emits a critical warnings before
+ * returning %NULL, since this probably indicates a programming error and
+ * most code is not prepared to deal with lookup failures.
+ *
+ * Returns: the #GtkActionGroup named @group_name
+ **/
+GtkActionGroup *
+e_lookup_action_group (GtkUIManager *ui_manager,
+                       const gchar *group_name)
+{
+	GList *iter;
+
+	g_return_val_if_fail (GTK_IS_UI_MANAGER (ui_manager), NULL);
+	g_return_val_if_fail (group_name != NULL, NULL);
+
+	iter = gtk_ui_manager_get_action_groups (ui_manager);
+
+	while (iter != NULL) {
+		GtkActionGroup *action_group = iter->data;
+		const gchar *name;
+
+		name = gtk_action_group_get_name (action_group);
+		if (strcmp (name, group_name) == 0)
+			return action_group;
+
+		iter = g_list_next (iter);
+	}
+
+	g_critical ("%s: action group '%s' not found", G_STRFUNC, group_name);
+
+	return NULL;
+}
+
+/**
+ * e_action_compare_by_label:
+ * @action1: a #GtkAction
+ * @action2: a #GtkAction
+ *
+ * Compares the labels for @action1 and @action2 using g_utf8_collate().
+ *
+ * Returns: &lt; 0 if @action1 compares before @action2, 0 if they
+ *          compare equal, &gt; 0 if @action1 compares after @action2
+ **/
+gint
+e_action_compare_by_label (GtkAction *action1,
+                           GtkAction *action2)
+{
+	gchar *label1;
+	gchar *label2;
+	gint result;
+
+	/* XXX This is horribly inefficient but will generally only be
+	 *     used on short lists of actions during UI construction. */
+
+	if (action1 == action2)
+		return 0;
+
+	g_object_get (action1, "label", &label1, NULL);
+	g_object_get (action2, "label", &label2, NULL);
+
+	result = g_utf8_collate (label1, label2);
+
+	g_free (label1);
+	g_free (label2);
+
+	return result;
+}
+
+/**
+ * e_action_group_remove_all_actions:
+ * @action_group: a #GtkActionGroup
+ *
+ * Removes all actions from the action group.
+ **/
+void
+e_action_group_remove_all_actions (GtkActionGroup *action_group)
+{
+	GList *list, *iter;
+
+	/* XXX I've proposed this function for inclusion in GTK+.
+	 *     GtkActionGroup stores actions in an internal hash
+	 *     table and can do this more efficiently by calling
+	 *     g_hash_table_remove_all().
+	 *
+	 *     http://bugzilla.gnome.org/show_bug.cgi?id=550485 */
+
+	g_return_if_fail (GTK_IS_ACTION_GROUP (action_group));
+
+	list = gtk_action_group_list_actions (action_group);
+	for (iter = list; iter != NULL; iter = iter->next)
+		gtk_action_group_remove_action (action_group, iter->data);
+	g_list_free (list);
+}
+
+/**
+ * e_radio_action_get_current_action:
+ * @radio_action: a #GtkRadioAction
+ *
+ * Returns the currently active member of the group to which @radio_action
+ * belongs.
+ *
+ * Returns: the currently active group member
+ **/
+GtkRadioAction *
+e_radio_action_get_current_action (GtkRadioAction *radio_action)
+{
+	GSList *group;
+	gint current_value;
+
+	g_return_val_if_fail (GTK_IS_RADIO_ACTION (radio_action), NULL);
+
+	group = gtk_radio_action_get_group (radio_action);
+	current_value = gtk_radio_action_get_current_value (radio_action);
+
+	while (group != NULL) {
+		gint value;
+
+		radio_action = GTK_RADIO_ACTION (group->data);
+		g_object_get (radio_action, "value", &value, NULL);
+
+		if (value == current_value)
+			return radio_action;
+
+		group = g_slist_next (group);
+	}
+
+	return NULL;
+}
+
+/**
+ * e_action_group_add_actions_localized:
+ * @action_group: a #GtkActionGroup to add @entries to
+ * @translation_domain: a translation domain to use
+ *    to translate label and tooltip strings in @entries
+ * @entries: (array length=n_entries): an array of action descriptions
+ * @n_entries: the number of entries
+ * @user_data: data to pass to the action callbacks
+ *
+ * Adds #GtkAction-s defined by @entries to @action_group, with action's
+ * label and tooltip localized in the given translation domain, instead
+ * of the domain set on the @action_group.
+ *
+ * Since: 3.4
+ **/
+void
+e_action_group_add_actions_localized (GtkActionGroup *action_group,
+                                      const gchar *translation_domain,
+                                      const GtkActionEntry *entries,
+                                      guint n_entries,
+                                      gpointer user_data)
+{
+	GtkActionGroup *tmp_group;
+	GList *list, *iter;
+	gint ii;
+
+	g_return_if_fail (action_group != NULL);
+	g_return_if_fail (entries != NULL);
+	g_return_if_fail (n_entries > 0);
+	g_return_if_fail (translation_domain != NULL);
+	g_return_if_fail (*translation_domain);
+
+	tmp_group = gtk_action_group_new ("temporary-group");
+	gtk_action_group_set_translation_domain (tmp_group, translation_domain);
+	gtk_action_group_add_actions (tmp_group, entries, n_entries, user_data);
+
+	list = gtk_action_group_list_actions (tmp_group);
+	for (iter = list; iter != NULL; iter = iter->next) {
+		GtkAction *action = GTK_ACTION (iter->data);
+		const gchar *action_name;
+
+		g_object_ref (action);
+
+		action_name = gtk_action_get_name (action);
+
+		for (ii = 0; ii < n_entries; ii++) {
+			if (g_strcmp0 (entries[ii].name, action_name) == 0) {
+				gtk_action_group_remove_action (
+					tmp_group, action);
+				gtk_action_group_add_action_with_accel (
+					action_group, action,
+					entries[ii].accelerator);
+				break;
+			}
+		}
+
+		g_object_unref (action);
+	}
+
+	g_list_free (list);
+	g_object_unref (tmp_group);
+}
+
+/**
+ * e_builder_get_widget:
+ * @builder: a #GtkBuilder
+ * @widget_name: name of a widget in @builder
+ *
+ * Gets the widget named @widget_name.  Note that this function does not
+ * increment the reference count of the returned widget.  If @widget_name
+ * could not be found in the @builder<!-- -->'s object tree, a run-time
+ * warning is emitted since this usually indicates a programming error.
+ *
+ * This is a convenience function to work around the awkwardness of
+ * #GtkBuilder returning #GObject pointers, when the vast majority of
+ * the time you want a #GtkWidget pointer.
+ *
+ * If you need something from @builder other than a #GtkWidget, or you
+ * want to test for the existence of some widget name without incurring
+ * a run-time warning, use gtk_builder_get_object().
+ *
+ * Returns: the widget named @widget_name, or %NULL
+ **/
+GtkWidget *
+e_builder_get_widget (GtkBuilder *builder,
+                      const gchar *widget_name)
+{
+	GObject *object;
+
+	g_return_val_if_fail (GTK_IS_BUILDER (builder), NULL);
+	g_return_val_if_fail (widget_name != NULL, NULL);
+
+	object = gtk_builder_get_object (builder, widget_name);
+	if (object == NULL) {
+		g_warning ("Could not find widget '%s'", widget_name);
+		return NULL;
+	}
+
+	return GTK_WIDGET (object);
+}
+
+/**
+ * e_load_ui_builder_definition:
+ * @builder: a #GtkBuilder
+ * @basename: basename of the UI definition file
+ *
+ * Loads a UI definition into @builder from Evolution's UI directory.
+ * Failure here is fatal, since the application can't function without
+ * its UI definitions.
+ **/
+void
+e_load_ui_builder_definition (GtkBuilder *builder,
+                              const gchar *basename)
+{
+	gchar *filename;
+	GError *error = NULL;
+
+	g_return_if_fail (GTK_IS_BUILDER (builder));
+	g_return_if_fail (basename != NULL);
+
+	filename = g_build_filename (EVOLUTION_UIDIR, basename, NULL);
+	gtk_builder_add_from_file (builder, filename, &error);
+	g_free (filename);
+
+	if (error != NULL) {
+		g_error ("%s: %s", basename, error->message);
+		g_assert_not_reached ();
+	}
+}
+
+/* Helper for e_categories_add_change_hook() */
+static void
+categories_changed_cb (GObject *useless_opaque_object,
+                       GHookList *hook_list)
+{
+	/* e_categories_register_change_listener() is broken because
+	 * it requires callbacks to allow for some opaque GObject as
+	 * the first argument (not does it document this). */
+	g_hook_list_invoke (hook_list, FALSE);
+}
+
+/* Helper for e_categories_add_change_hook() */
+static void
+categories_weak_notify_cb (GHookList *hook_list,
+                           gpointer where_the_object_was)
+{
+	GHook *hook;
+
+	/* This should not happen, but if we fail to find the hook for
+	 * some reason, g_hook_destroy_link() will warn about the NULL
+	 * pointer, which is all we would do anyway so no need to test
+	 * for it ourselves. */
+	hook = g_hook_find_data (hook_list, TRUE, where_the_object_was);
+	g_hook_destroy_link (hook_list, hook);
+}
+
+/**
+ * e_categories_add_change_hook:
+ * @func: a hook function
+ * @object: a #GObject to be passed to @func, or %NULL
+ *
+ * A saner alternative to e_categories_register_change_listener().
+ *
+ * Adds a hook function to be called when a category is added, removed or
+ * modified.  If @object is not %NULL, the hook function is automatically
+ * removed when @object is finalized.
+ **/
+void
+e_categories_add_change_hook (GHookFunc func,
+                              gpointer object)
+{
+	static gboolean initialized = FALSE;
+	static GHookList hook_list;
+	GHook *hook;
+
+	g_return_if_fail (func != NULL);
+
+	if (object != NULL)
+		g_return_if_fail (G_IS_OBJECT (object));
+
+	if (!initialized) {
+		g_hook_list_init (&hook_list, sizeof (GHook));
+		e_categories_register_change_listener (
+			G_CALLBACK (categories_changed_cb), &hook_list);
+		initialized = TRUE;
+	}
+
+	hook = g_hook_alloc (&hook_list);
+
+	hook->func = func;
+	hook->data = object;
+
+	if (object != NULL)
+		g_object_weak_ref (
+			G_OBJECT (object), (GWeakNotify)
+			categories_weak_notify_cb, &hook_list);
+
+	g_hook_append (&hook_list, hook);
+}
+
+/**
+ * e_flexible_strtod:
+ * @nptr:    the string to convert to a numeric value.
+ * @endptr:  if non-NULL, it returns the character after
+ *           the last character used in the conversion.
+ *
+ * Converts a string to a gdouble value.  This function detects
+ * strings either in the standard C locale or in the current locale.
+ *
+ * This function is typically used when reading configuration files or
+ * other non-user input that should not be locale dependent, but may
+ * have been in the past.  To handle input from the user you should
+ * normally use the locale-sensitive system strtod function.
+ *
+ * To convert from a double to a string in a locale-insensitive way, use
+ * @g_ascii_dtostr.
+ *
+ * Returns: the gdouble value
+ **/
+gdouble
+e_flexible_strtod (const gchar *nptr,
+                   gchar **endptr)
+{
+	gchar *fail_pos;
+	gdouble val;
+	struct lconv *locale_data;
+	const gchar *decimal_point;
+	gint decimal_point_len;
+	const gchar *p, *decimal_point_pos;
+	const gchar *end = NULL; /* Silence gcc */
+	gchar *copy, *c;
+
+	g_return_val_if_fail (nptr != NULL, 0);
+
+	fail_pos = NULL;
+
+	locale_data = localeconv ();
+	decimal_point = locale_data->decimal_point;
+	decimal_point_len = strlen (decimal_point);
+
+	g_return_val_if_fail (decimal_point_len != 0, 0);
+
+	decimal_point_pos = NULL;
+	if (!strcmp (decimal_point, "."))
+		return strtod (nptr, endptr);
+
+	p = nptr;
+
+	/* Skip leading space */
+	while (isspace ((guchar) * p))
+		p++;
+
+	/* Skip leading optional sign */
+	if (*p == '+' || *p == '-')
+		p++;
+
+	if (p[0] == '0' &&
+	    (p[1] == 'x' || p[1] == 'X')) {
+		p += 2;
+		/* HEX - find the (optional) decimal point */
+
+		while (isxdigit ((guchar) * p))
+			p++;
+
+		if (*p == '.') {
+			decimal_point_pos = p++;
+
+			while (isxdigit ((guchar) * p))
+				p++;
+
+			if (*p == 'p' || *p == 'P')
+				p++;
+			if (*p == '+' || *p == '-')
+				p++;
+			while (isdigit ((guchar) * p))
+				p++;
+			end = p;
+		} else if (strncmp (p, decimal_point, decimal_point_len) == 0) {
+			return strtod (nptr, endptr);
+		}
+	} else {
+		while (isdigit ((guchar) * p))
+			p++;
+
+		if (*p == '.') {
+			decimal_point_pos = p++;
+
+			while (isdigit ((guchar) * p))
+				p++;
+
+			if (*p == 'e' || *p == 'E')
+				p++;
+			if (*p == '+' || *p == '-')
+				p++;
+			while (isdigit ((guchar) * p))
+				p++;
+			end = p;
+		} else if (strncmp (p, decimal_point, decimal_point_len) == 0) {
+			return strtod (nptr, endptr);
+		}
+	}
+	/* For the other cases, we need not convert the decimal point */
+
+	if (!decimal_point_pos)
+		return strtod (nptr, endptr);
+
+	/* We need to convert the '.' to the locale specific decimal point */
+	copy = g_malloc (end - nptr + 1 + decimal_point_len);
+
+	c = copy;
+	memcpy (c, nptr, decimal_point_pos - nptr);
+	c += decimal_point_pos - nptr;
+	memcpy (c, decimal_point, decimal_point_len);
+	c += decimal_point_len;
+	memcpy (c, decimal_point_pos + 1, end - (decimal_point_pos + 1));
+	c += end - (decimal_point_pos + 1);
+	*c = 0;
+
+	val = strtod (copy, &fail_pos);
+
+	if (fail_pos) {
+		if (fail_pos > decimal_point_pos)
+			fail_pos =
+				(gchar *) nptr + (fail_pos - copy) -
+				(decimal_point_len - 1);
+		else
+			fail_pos = (gchar *) nptr + (fail_pos - copy);
+	}
+
+	g_free (copy);
+
+	if (endptr)
+		*endptr = fail_pos;
+
+	return val;
+}
+
+/**
+ * e_ascii_dtostr:
+ * @buffer: A buffer to place the resulting string in
+ * @buf_len: The length of the buffer.
+ * @format: The printf-style format to use for the
+ *          code to use for converting.
+ * @d: The double to convert
+ *
+ * Converts a double to a string, using the '.' as
+ * decimal_point. To format the number you pass in
+ * a printf-style formating string. Allowed conversion
+ * specifiers are eEfFgG.
+ *
+ * If you want to generates enough precision that converting
+ * the string back using @g_strtod gives the same machine-number
+ * (on machines with IEEE compatible 64bit doubles) use the format
+ * string "%.17g". If you do this it is guaranteed that the size
+ * of the resulting string will never be larger than
+ * @G_ASCII_DTOSTR_BUF_SIZE bytes.
+ *
+ * Returns: the pointer to the buffer with the converted string
+ **/
+gchar *
+e_ascii_dtostr (gchar *buffer,
+                gint buf_len,
+                const gchar *format,
+                gdouble d)
+{
+	struct lconv *locale_data;
+	const gchar *decimal_point;
+	gint decimal_point_len;
+	gchar *p;
+	gint rest_len;
+	gchar format_char;
+
+	g_return_val_if_fail (buffer != NULL, NULL);
+	g_return_val_if_fail (format[0] == '%', NULL);
+	g_return_val_if_fail (strpbrk (format + 1, "'l%") == NULL, NULL);
+
+	format_char = format[strlen (format) - 1];
+
+	g_return_val_if_fail (format_char == 'e' || format_char == 'E' ||
+			      format_char == 'f' || format_char == 'F' ||
+			      format_char == 'g' || format_char == 'G',
+			      NULL);
+
+	if (format[0] != '%')
+		return NULL;
+
+	if (strpbrk (format + 1, "'l%"))
+		return NULL;
+
+	if (!(format_char == 'e' || format_char == 'E' ||
+	      format_char == 'f' || format_char == 'F' ||
+	      format_char == 'g' || format_char == 'G'))
+		return NULL;
+
+	g_snprintf (buffer, buf_len, format, d);
+
+	locale_data = localeconv ();
+	decimal_point = locale_data->decimal_point;
+	decimal_point_len = strlen (decimal_point);
+
+	g_return_val_if_fail (decimal_point_len != 0, NULL);
+
+	if (strcmp (decimal_point, ".")) {
+		p = buffer;
+
+		if (*p == '+' || *p == '-')
+			p++;
+
+		while (isdigit ((guchar) * p))
+			p++;
+
+		if (strncmp (p, decimal_point, decimal_point_len) == 0) {
+			*p = '.';
+			p++;
+			if (decimal_point_len > 1) {
+				rest_len = strlen (p + (decimal_point_len - 1));
+				memmove (
+					p, p + (decimal_point_len - 1),
+					rest_len);
+				p[rest_len] = 0;
+			}
+		}
+	}
+
+	return buffer;
+}
+
+/**
+ * e_str_without_underscores:
+ * @string: the string to strip underscores from
+ *
+ * Strips underscores from a string in the same way
+ * @gtk_label_new_with_mnemonics does.  The returned string should be freed
+ * using g_free().
+ *
+ * Returns: a newly-allocated string without underscores
+ */
+gchar *
+e_str_without_underscores (const gchar *string)
+{
+	gchar *new_string;
+	const gchar *sp;
+	gchar *dp;
+
+	new_string = g_malloc (strlen (string) + 1);
+
+	dp = new_string;
+	for (sp = string; *sp != '\0'; sp++) {
+		if (*sp != '_') {
+			*dp = *sp;
+			dp++;
+		} else if (sp[1] == '_') {
+			/* Translate "__" in "_".  */
+			*dp = '_';
+			dp++;
+			sp++;
+		}
+	}
+	*dp = 0;
+
+	return new_string;
+}
+
+gint
+e_str_compare (gconstpointer x,
+               gconstpointer y)
+{
+	if (x == NULL || y == NULL) {
+		if (x == y)
+			return 0;
+		else
+			return x ? -1 : 1;
+	}
+
+	return strcmp (x, y);
+}
+
+gint
+e_str_case_compare (gconstpointer x,
+                    gconstpointer y)
+{
+	gchar *cx, *cy;
+	gint res;
+
+	if (x == NULL || y == NULL) {
+		if (x == y)
+			return 0;
+		else
+			return x ? -1 : 1;
+	}
+
+	cx = g_utf8_casefold (x, -1);
+	cy = g_utf8_casefold (y, -1);
+
+	res = g_utf8_collate (cx, cy);
+
+	g_free (cx);
+	g_free (cy);
+
+	return res;
+}
+
+gint
+e_collate_compare (gconstpointer x,
+                   gconstpointer y)
+{
+	if (x == NULL || y == NULL) {
+		if (x == y)
+			return 0;
+		else
+			return x ? -1 : 1;
+	}
+
+	return g_utf8_collate (x, y);
+}
+
+gint
+e_int_compare (gconstpointer x,
+               gconstpointer y)
+{
+	gint nx = GPOINTER_TO_INT (x);
+	gint ny = GPOINTER_TO_INT (y);
+
+	return (nx == ny) ? 0 : (nx < ny) ? -1 : 1;
+}
+
+/**
+ * e_color_to_value:
+ * @color: a #GdkColor
+ *
+ * Converts a #GdkColor to a 24-bit RGB color value.
+ *
+ * Returns: a 24-bit color value
+ **/
+guint32
+e_color_to_value (GdkColor *color)
+{
+	GdkRGBA rgba;
+
+	g_return_val_if_fail (color != NULL, 0);
+
+	rgba.red = color->red / 65535.0;
+	rgba.green = color->green / 65535.0;
+	rgba.blue = color->blue / 65535.0;
+	rgba.alpha = 0.0;
+
+	return e_rgba_to_value (&rgba);
+}
+
+/**
+ * e_rgba_to_value:
+ * @rgba: a #GdkRGBA
+ *
+ *
+ * Converts #GdkRGBA to a 24-bit RGB color value
+ *
+ * Returns: a 24-bit color value
+ **/
+guint32
+e_rgba_to_value (GdkRGBA *rgba)
+{
+	guint16 red;
+	guint16 green;
+	guint16 blue;
+
+	g_return_val_if_fail (rgba != NULL, 0);
+
+	red = 255 * rgba->red;
+	green = 255 * rgba->green;
+	blue = 255 * rgba->blue;
+
+	return (guint32)
+		((((red & 0xFF) << 16) |
+		((green & 0xFF) << 8) |
+		(blue & 0xFF)) & 0xffffff);
+}
+
+static gint
+epow10 (gint number)
+{
+	gint value = 1;
+
+	while (number-- > 0)
+		value *= 10;
+
+	return value;
+}
+
+gchar *
+e_format_number (gint number)
+{
+	GList *iterator, *list = NULL;
+	struct lconv *locality;
+	gint char_length = 0;
+	gint group_count = 0;
+	gchar *grouping;
+	gint last_count = 3;
+	gint divider;
+	gchar *value;
+	gchar *value_iterator;
+
+	locality = localeconv ();
+	grouping = locality->grouping;
+	while (number) {
+		gchar *group;
+		switch (*grouping) {
+		default:
+			last_count = *grouping;
+			grouping++;
+		case 0:
+			divider = epow10 (last_count);
+			if (number >= divider) {
+				group = g_strdup_printf (
+					"%0*d", last_count, number % divider);
+			} else {
+				group = g_strdup_printf (
+					"%d", number % divider);
+			}
+			number /= divider;
+			break;
+		case CHAR_MAX:
+			group = g_strdup_printf ("%d", number);
+			number = 0;
+			break;
+		}
+		char_length += strlen (group);
+		list = g_list_prepend (list, group);
+		group_count++;
+	}
+
+	if (list) {
+		value = g_new (
+			gchar, 1 + char_length + (group_count - 1) *
+			strlen (locality->thousands_sep));
+
+		iterator = list;
+		value_iterator = value;
+
+		strcpy (value_iterator, iterator->data);
+		value_iterator += strlen (iterator->data);
+		for (iterator = iterator->next; iterator; iterator = iterator->next) {
+			strcpy (value_iterator, locality->thousands_sep);
+			value_iterator += strlen (locality->thousands_sep);
+
+			strcpy (value_iterator, iterator->data);
+			value_iterator += strlen (iterator->data);
+		}
+		g_list_foreach (list, (GFunc) g_free, NULL);
+		g_list_free (list);
+		return value;
+	} else {
+		return g_strdup ("0");
+	}
+}
+
+/* Perform a binary search for key in base which has nmemb elements
+ * of size bytes each.  The comparisons are done by (*compare)().  */
+void
+e_bsearch (gconstpointer key,
+           gconstpointer base,
+           gsize nmemb,
+           gsize size,
+           ESortCompareFunc compare,
+           gpointer closure,
+           gsize *start,
+           gsize *end)
+{
+	gsize l, u, idx;
+	gconstpointer p;
+	gint comparison;
+	if (!(start || end))
+		return;
+
+	l = 0;
+	u = nmemb;
+	while (l < u) {
+		idx = (l + u) / 2;
+		p = (((const gchar *) base) + (idx * size));
+		comparison = (*compare) (key, p, closure);
+		if (comparison < 0)
+			u = idx;
+		else if (comparison > 0)
+			l = idx + 1;
+		else {
+			gsize lsave, usave;
+			lsave = l;
+			usave = u;
+			if (start) {
+				while (l < u) {
+					idx = (l + u) / 2;
+					p = (((const gchar *) base) + (idx * size));
+					comparison = (*compare) (key, p, closure);
+					if (comparison <= 0)
+						u = idx;
+					else
+						l = idx + 1;
+				}
+				*start = l;
+
+				l = lsave;
+				u = usave;
+			}
+			if (end) {
+				while (l < u) {
+					idx = (l + u) / 2;
+					p = (((const gchar *) base) + (idx * size));
+					comparison = (*compare) (key, p, closure);
+					if (comparison < 0)
+						u = idx;
+					else
+						l = idx + 1;
+				}
+				*end = l;
+			}
+			return;
+		}
+	}
+
+	if (start)
+		*start = l;
+	if (end)
+		*end = l;
+}
+
+/* Function to do a last minute fixup of the AM/PM stuff if the locale
+ * and gettext haven't done it right. Most English speaking countries
+ * except the USA use the 24 hour clock (UK, Australia etc). However
+ * since they are English nobody bothers to write a language
+ * translation (gettext) file. So the locale turns off the AM/PM, but
+ * gettext does not turn on the 24 hour clock. Leaving a mess.
+ *
+ * This routine checks if AM/PM are defined in the locale, if not it
+ * forces the use of the 24 hour clock.
+ *
+ * The function itself is a front end on strftime and takes exactly
+ * the same arguments.
+ *
+ * TODO: Actually remove the '%p' from the fixed up string so that
+ * there isn't a stray space.
+ */
+
+gsize
+e_strftime_fix_am_pm (gchar *str,
+                      gsize max,
+                      const gchar *fmt,
+                      const struct tm *tm)
+{
+	gchar buf[10];
+	gchar *sp;
+	gchar *ffmt;
+	gsize ret;
+
+	if (strstr (fmt, "%p") == NULL && strstr (fmt, "%P") == NULL) {
+		/* No AM/PM involved - can use the fmt string directly */
+		ret = e_strftime (str, max, fmt, tm);
+	} else {
+		/* Get the AM/PM symbol from the locale */
+		e_strftime (buf, 10, "%p", tm);
+
+		if (buf[0]) {
+			/* AM/PM have been defined in the locale
+			 * so we can use the fmt string directly. */
+			ret = e_strftime (str, max, fmt, tm);
+		} else {
+			/* No AM/PM defined by locale
+			 * must change to 24 hour clock. */
+			ffmt = g_strdup (fmt);
+			for (sp = ffmt; (sp = strstr (sp, "%l")); sp++) {
+				/* Maybe this should be 'k', but I have never
+				 * seen a 24 clock actually use that format. */
+				sp[1]='H';
+			}
+			for (sp = ffmt; (sp = strstr (sp, "%I")); sp++) {
+				sp[1]='H';
+			}
+			ret = e_strftime (str, max, ffmt, tm);
+			g_free (ffmt);
+		}
+	}
+
+	return (ret);
+}
+
+gsize
+e_utf8_strftime_fix_am_pm (gchar *str,
+                           gsize max,
+                           const gchar *fmt,
+                           const struct tm *tm)
+{
+	gsize sz, ret;
+	gchar *locale_fmt, *buf;
+
+	locale_fmt = g_locale_from_utf8 (fmt, -1, NULL, &sz, NULL);
+	if (!locale_fmt)
+		return 0;
+
+	ret = e_strftime_fix_am_pm (str, max, locale_fmt, tm);
+	if (!ret) {
+		g_free (locale_fmt);
+		return 0;
+	}
+
+	buf = g_locale_to_utf8 (str, ret, NULL, &sz, NULL);
+	if (!buf) {
+		g_free (locale_fmt);
+		return 0;
+	}
+
+	if (sz >= max) {
+		gchar *tmp = buf + max - 1;
+		tmp = g_utf8_find_prev_char (buf, tmp);
+		if (tmp)
+			sz = tmp - buf;
+		else
+			sz = 0;
+	}
+	memcpy (str, buf, sz);
+	str[sz] = '\0';
+	g_free (locale_fmt);
+	g_free (buf);
+	return sz;
+}
+
+/**
+ * e_get_month_name:
+ * @month: month index
+ * @abbreviated: if %TRUE, abbreviate the month name
+ *
+ * Returns the localized name for @month.  If @abbreviated is %TRUE,
+ * returns the locale's abbreviated month name.
+ *
+ * Returns: localized month name
+ **/
+const gchar *
+e_get_month_name (GDateMonth month,
+                  gboolean abbreviated)
+{
+	/* Make the indices correspond to the enum values. */
+	static const gchar *abbr_names[G_DATE_DECEMBER + 1];
+	static const gchar *full_names[G_DATE_DECEMBER + 1];
+	static gboolean first_time = TRUE;
+
+	g_return_val_if_fail (month >= G_DATE_JANUARY, NULL);
+	g_return_val_if_fail (month <= G_DATE_DECEMBER, NULL);
+
+	if (G_UNLIKELY (first_time)) {
+		gchar buffer[256];
+		GDateMonth ii;
+		GDate date;
+
+		memset (abbr_names, 0, sizeof (abbr_names));
+		memset (full_names, 0, sizeof (full_names));
+
+		/* First Julian day was in January. */
+		g_date_set_julian (&date, 1);
+
+		for (ii = G_DATE_JANUARY; ii <= G_DATE_DECEMBER; ii++) {
+			g_date_strftime (buffer, sizeof (buffer), "%b", &date);
+			abbr_names[ii] = g_intern_string (buffer);
+			g_date_strftime (buffer, sizeof (buffer), "%B", &date);
+			full_names[ii] = g_intern_string (buffer);
+			g_date_add_months (&date, 1);
+		}
+
+		first_time = FALSE;
+	}
+
+	return abbreviated ? abbr_names[month] : full_names[month];
+}
+
+/**
+ * e_get_weekday_name:
+ * @weekday: weekday index
+ * @abbreviated: if %TRUE, abbreviate the weekday name
+ *
+ * Returns the localized name for @weekday.  If @abbreviated is %TRUE,
+ * returns the locale's abbreviated weekday name.
+ *
+ * Returns: localized weekday name
+ **/
+const gchar *
+e_get_weekday_name (GDateWeekday weekday,
+                    gboolean abbreviated)
+{
+	/* Make the indices correspond to the enum values. */
+	static const gchar *abbr_names[G_DATE_SUNDAY + 1];
+	static const gchar *full_names[G_DATE_SUNDAY + 1];
+	static gboolean first_time = TRUE;
+
+	g_return_val_if_fail (weekday >= G_DATE_MONDAY, NULL);
+	g_return_val_if_fail (weekday <= G_DATE_SUNDAY, NULL);
+
+	if (G_UNLIKELY (first_time)) {
+		gchar buffer[256];
+		GDateWeekday ii;
+		GDate date;
+
+		memset (abbr_names, 0, sizeof (abbr_names));
+		memset (full_names, 0, sizeof (full_names));
+
+		/* First Julian day was a Monday. */
+		g_date_set_julian (&date, 1);
+
+		for (ii = G_DATE_MONDAY; ii <= G_DATE_SUNDAY; ii++) {
+			g_date_strftime (buffer, sizeof (buffer), "%a", &date);
+			abbr_names[ii] = g_intern_string (buffer);
+			g_date_strftime (buffer, sizeof (buffer), "%A", &date);
+			full_names[ii] = g_intern_string (buffer);
+			g_date_add_days (&date, 1);
+		}
+
+		first_time = FALSE;
+	}
+
+	return abbreviated ? abbr_names[weekday] : full_names[weekday];
+}
+
+/* Evolution Locks for crash recovery */
+static const gchar *
+get_lock_filename (void)
+{
+	static gchar *filename = NULL;
+
+	if (G_UNLIKELY (filename == NULL))
+		filename = g_build_filename (
+			e_get_user_config_dir (), ".running", NULL);
+
+	return filename;
+}
+
+gboolean
+e_file_lock_create (void)
+{
+	const gchar *filename = get_lock_filename ();
+	gboolean status = FALSE;
+	FILE *file;
+
+	file = g_fopen (filename, "w");
+	if (file != NULL) {
+		/* The lock file also serves as a PID file. */
+		g_fprintf (
+			file, "%" G_GINT64_FORMAT "\n",
+			(gint64) getpid ());
+		fclose (file);
+		status = TRUE;
+	} else {
+		const gchar *errmsg = g_strerror (errno);
+		g_warning ("Lock file creation failed: %s", errmsg);
+	}
+
+	return status;
+}
+
+void
+e_file_lock_destroy (void)
+{
+	const gchar *filename = get_lock_filename ();
+
+	if (g_unlink (filename) == -1) {
+		const gchar *errmsg = g_strerror (errno);
+		g_warning ("Lock file deletion failed: %s", errmsg);
+	}
+}
+
+gboolean
+e_file_lock_exists (void)
+{
+	const gchar *filename = get_lock_filename ();
+
+	return g_file_test (filename, G_FILE_TEST_EXISTS);
+}
+
+/**
+ * e_util_guess_mime_type:
+ * @filename: a local file name, or URI
+ * @localfile: %TRUE to check the file content, FALSE to check only the name
+ *
+ * Tries to determine the MIME type for @filename.  Free the returned
+ * string with g_free().
+ *
+ * Returns: the MIME type of @filename, or %NULL if the the MIME type could
+ *          not be determined
+ **/
+gchar *
+e_util_guess_mime_type (const gchar *filename,
+                        gboolean localfile)
+{
+	gchar *mime_type = NULL;
+
+	g_return_val_if_fail (filename != NULL, NULL);
+
+	if (localfile) {
+		GFile *file;
+		GFileInfo *fi;
+
+		if (strstr (filename, "://"))
+			file = g_file_new_for_uri (filename);
+		else
+			file = g_file_new_for_path (filename);
+
+		fi = g_file_query_info (
+			file, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE,
+			G_FILE_QUERY_INFO_NONE, NULL, NULL);
+		if (fi) {
+			mime_type = g_content_type_get_mime_type (
+				g_file_info_get_content_type (fi));
+			g_object_unref (fi);
+		}
+
+		g_object_unref (file);
+	}
+
+	if (!mime_type) {
+		/* file doesn't exists locally, thus guess based on the filename */
+		gboolean uncertain = FALSE;
+		gchar *content_type;
+
+		content_type = g_content_type_guess (filename, NULL, 0, &uncertain);
+		if (content_type) {
+			mime_type = g_content_type_get_mime_type (content_type);
+			g_free (content_type);
+		}
+	}
+
+	return mime_type;
+}
+
+GSList *
+e_util_get_category_filter_options (void)
+{
+	GSList *res = NULL;
+	GList *clist, *l;
+
+	clist = e_categories_get_list ();
+	for (l = clist; l; l = l->next) {
+		const gchar *cname = l->data;
+		struct _filter_option *fo;
+
+		if (!e_categories_is_searchable (cname))
+			continue;
+
+		fo = g_new0 (struct _filter_option, 1);
+
+		fo->title = g_strdup (cname);
+		fo->value = g_strdup (cname);
+		res = g_slist_prepend (res, fo);
+	}
+
+	g_list_free (clist);
+
+	return g_slist_reverse (res);
+}
+
+/**
+ * e_util_get_searchable_categories:
+ *
+ * Returns list of searchable categories only. The list should
+ * be freed with g_list_free() when done with it, but the items
+ * are internal strings, names of categories, which should not
+ * be touched in other than read-only way, in other words the same
+ * restrictions as for e_categories_get_list() applies here too.
+ **/
+GList *
+e_util_get_searchable_categories (void)
+{
+	GList *res = NULL, *all_categories, *l;
+
+	all_categories = e_categories_get_list ();
+	for (l = all_categories; l; l = l->next) {
+		const gchar *cname = l->data;
+
+		if (e_categories_is_searchable (cname))
+			res = g_list_prepend (res, (gpointer) cname);
+	}
+
+	g_list_free (all_categories);
+
+	return g_list_reverse (res);
+}
+
+/**
+ * e_binding_transform_color_to_string:
+ * @binding: a #GBinding
+ * @source_value: a #GValue of type #GDK_TYPE_COLOR
+ * @target_value: a #GValue of type #G_TYPE_STRING
+ * @not_used: not used
+ *
+ * Transforms a #GdkColor value to a color string specification.
+ *
+ * Returns: %TRUE always
+ **/
+gboolean
+e_binding_transform_color_to_string (GBinding *binding,
+                                     const GValue *source_value,
+                                     GValue *target_value,
+                                     gpointer not_used)
+{
+	const GdkColor *color;
+	gchar *string;
+
+	g_return_val_if_fail (G_IS_BINDING (binding), FALSE);
+
+	color = g_value_get_boxed (source_value);
+	if (!color) {
+		g_value_set_string (target_value, "");
+	} else {
+		/* encode color manually, because css styles expect colors in #rrggbb,
+		 * not in #rrrrggggbbbb, which is a result of gdk_color_to_string()
+		*/
+		string = g_strdup_printf (
+			"#%02x%02x%02x",
+			(gint) color->red * 256 / 65536,
+			(gint) color->green * 256 / 65536,
+			(gint) color->blue * 256 / 65536);
+		g_value_set_string (target_value, string);
+		g_free (string);
+	}
+
+	return TRUE;
+}
+
+/**
+ * e_binding_transform_string_to_color:
+ * @binding: a #GBinding
+ * @source_value: a #GValue of type #G_TYPE_STRING
+ * @target_value: a #GValue of type #GDK_TYPE_COLOR
+ * @not_used: not used
+ *
+ * Transforms a color string specification to a #GdkColor.
+ *
+ * Returns: %TRUE if color string specification was valid
+ **/
+gboolean
+e_binding_transform_string_to_color (GBinding *binding,
+                                     const GValue *source_value,
+                                     GValue *target_value,
+                                     gpointer not_used)
+{
+	GdkColor color;
+	const gchar *string;
+	gboolean success = FALSE;
+
+	g_return_val_if_fail (G_IS_BINDING (binding), FALSE);
+
+	string = g_value_get_string (source_value);
+	if (gdk_color_parse (string, &color)) {
+		g_value_set_boxed (target_value, &color);
+		success = TRUE;
+	}
+
+	return success;
+}
+
+/**
+ * e_binding_transform_source_to_uid:
+ * @binding: a #GBinding
+ * @source_value: a #GValue of type #E_TYPE_SOURCE
+ * @target_value: a #GValue of type #G_TYPE_STRING
+ * @registry: an #ESourceRegistry
+ *
+ * Transforms an #ESource object to its UID string.
+ *
+ * Returns: %TRUE if @source_value was an #ESource object
+ **/
+gboolean
+e_binding_transform_source_to_uid (GBinding *binding,
+                                   const GValue *source_value,
+                                   GValue *target_value,
+                                   ESourceRegistry *registry)
+{
+	ESource *source;
+	const gchar *string;
+	gboolean success = FALSE;
+
+	g_return_val_if_fail (G_IS_BINDING (binding), FALSE);
+	g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), FALSE);
+
+	source = g_value_get_object (source_value);
+	if (E_IS_SOURCE (source)) {
+		string = e_source_get_uid (source);
+		g_value_set_string (target_value, string);
+		success = TRUE;
+	}
+
+	return success;
+}
+
+/**
+ * e_binding_transform_uid_to_source:
+ * @binding: a #GBinding
+ * @source_value: a #GValue of type #G_TYPE_STRING
+ * @target_value: a #GValue of type #E_TYPE_SOURCe
+ * @registry: an #ESourceRegistry
+ *
+ * Transforms an #ESource UID string to the corresponding #ESource object
+ * in @registry.
+ *
+ * Returns: %TRUE if @registry had an #ESource object with a matching
+ *          UID string
+ **/
+gboolean
+e_binding_transform_uid_to_source (GBinding *binding,
+                                   const GValue *source_value,
+                                   GValue *target_value,
+                                   ESourceRegistry *registry)
+{
+	ESource *source;
+	const gchar *string;
+	gboolean success = FALSE;
+
+	g_return_val_if_fail (G_IS_BINDING (binding), FALSE);
+	g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), FALSE);
+
+	string = g_value_get_string (source_value);
+	if (string == NULL || *string == '\0')
+		return FALSE;
+
+	source = e_source_registry_ref_source (registry, string);
+	if (source != NULL) {
+		g_value_take_object (target_value, source);
+		success = TRUE;
+	}
+
+	return success;
+}
diff --git a/e-util/e-misc-utils.h b/e-util/e-misc-utils.h
new file mode 100644
index 0000000..0bd465e
--- /dev/null
+++ b/e-util/e-misc-utils.h
@@ -0,0 +1,175 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_MISC_UTILS_H
+#define E_MISC_UTILS_H
+
+#include <sys/types.h>
+#include <gtk/gtk.h>
+#include <limits.h>
+
+#include <libedataserver/libedataserver.h>
+
+#include "e-marshal.h"
+#include "e-util-enums.h"
+
+G_BEGIN_DECLS
+
+typedef enum {
+	E_FOCUS_NONE,
+	E_FOCUS_CURRENT,
+	E_FOCUS_START,
+	E_FOCUS_END
+} EFocus;
+
+typedef enum {
+	E_RESTORE_WINDOW_SIZE     = 1 << 0,
+	E_RESTORE_WINDOW_POSITION = 1 << 1
+} ERestoreWindowFlags;
+
+const gchar *	e_get_accels_filename		(void);
+void		e_show_uri			(GtkWindow *parent,
+						 const gchar *uri);
+void		e_display_help			(GtkWindow *parent,
+						 const gchar *link_id);
+void		e_restore_window		(GtkWindow *window,
+						 const gchar *settings_path,
+						 ERestoreWindowFlags flags);
+GtkAction *	e_lookup_action			(GtkUIManager *ui_manager,
+						 const gchar *action_name);
+GtkActionGroup *e_lookup_action_group		(GtkUIManager *ui_manager,
+						 const gchar *group_name);
+gint		e_action_compare_by_label	(GtkAction *action1,
+						 GtkAction *action2);
+void		e_action_group_remove_all_actions
+						(GtkActionGroup *action_group);
+GtkRadioAction *e_radio_action_get_current_action
+						(GtkRadioAction *radio_action);
+void		e_action_group_add_actions_localized
+						(GtkActionGroup *action_group,
+						 const gchar *translation_domain,
+						 const GtkActionEntry *entries,
+						 guint n_entries,
+						 gpointer user_data);
+GtkWidget *	e_builder_get_widget		(GtkBuilder *builder,
+						 const gchar *widget_name);
+void		e_load_ui_builder_definition	(GtkBuilder *builder,
+						 const gchar *basename);
+void		e_categories_add_change_hook	(GHookFunc func,
+						 gpointer object);
+
+/* String to/from double conversion functions */
+gdouble		e_flexible_strtod		(const gchar *nptr,
+						 gchar **endptr);
+
+/* 29 bytes should enough for all possible values that
+ * g_ascii_dtostr can produce with the %.17g format.
+ * Then add 10 for good measure */
+#define E_ASCII_DTOSTR_BUF_SIZE (DBL_DIG + 12 + 10)
+gchar *		e_ascii_dtostr			(gchar *buffer,
+						 gint buf_len,
+						 const gchar *format,
+						 gdouble d);
+
+gchar *		e_str_without_underscores	(const gchar *string);
+gint		e_str_compare			(gconstpointer x,
+						 gconstpointer y);
+gint		e_str_case_compare		(gconstpointer x,
+						 gconstpointer y);
+gint		e_collate_compare		(gconstpointer x,
+						 gconstpointer y);
+gint		e_int_compare                   (gconstpointer x,
+						 gconstpointer y);
+guint32		e_color_to_value		(GdkColor *color);
+
+guint32		e_rgba_to_value			(GdkRGBA *rgba);
+
+/* This only makes a filename safe for usage as a filename.
+ * It still may have shell meta-characters in it. */
+gchar *		e_format_number			(gint number);
+
+typedef gint	(*ESortCompareFunc)		(gconstpointer first,
+						 gconstpointer second,
+						 gpointer closure);
+
+void		e_bsearch			(gconstpointer key,
+						 gconstpointer base,
+						 gsize nmemb,
+						 gsize size,
+						 ESortCompareFunc compare,
+						 gpointer closure,
+						 gsize *start,
+						 gsize *end);
+
+gsize		e_strftime_fix_am_pm		(gchar *str,
+						 gsize max,
+						 const gchar *fmt,
+						 const struct tm *tm);
+gsize		e_utf8_strftime_fix_am_pm	(gchar *str,
+						 gsize max,
+						 const gchar *fmt,
+						 const struct tm *tm);
+const gchar *	e_get_month_name		(GDateMonth month,
+						 gboolean abbreviated);
+const gchar *	e_get_weekday_name		(GDateWeekday weekday,
+						 gboolean abbreviated);
+
+gboolean	e_file_lock_create		(void);
+void		e_file_lock_destroy		(void);
+gboolean	e_file_lock_exists		(void);
+
+gchar *		e_util_guess_mime_type		(const gchar *filename,
+                                                 gboolean localfile);
+
+GSList *	e_util_get_category_filter_options
+						(void);
+GList *		e_util_get_searchable_categories (void);
+
+/* Useful GBinding transform functions */
+gboolean	e_binding_transform_color_to_string
+						(GBinding *binding,
+						 const GValue *source_value,
+						 GValue *target_value,
+						 gpointer not_used);
+gboolean	e_binding_transform_string_to_color
+						(GBinding *binding,
+						 const GValue *source_value,
+						 GValue *target_value,
+						 gpointer not_used);
+gboolean	e_binding_transform_source_to_uid
+						(GBinding *binding,
+						 const GValue *source_value,
+						 GValue *target_value,
+						 ESourceRegistry *registry);
+gboolean	e_binding_transform_uid_to_source
+						(GBinding *binding,
+						 const GValue *source_value,
+						 GValue *target_value,
+						 ESourceRegistry *registry);
+
+G_END_DECLS
+
+#endif /* E_MISC_UTILS_H */
diff --git a/e-util/e-mktemp.c b/e-util/e-mktemp.c
index 9b68ccc..f5042fa 100644
--- a/e-util/e-mktemp.c
+++ b/e-util/e-mktemp.c
@@ -35,7 +35,8 @@
 #include <stdio.h>
 #include <time.h>
 
-#include "e-util.h"
+#include <libedataserver/libedataserver.h>
+
 #include "e-mktemp.h"
 
 #define d(x)
diff --git a/e-util/e-mktemp.h b/e-util/e-mktemp.h
index 08f75ea..6c05541 100644
--- a/e-util/e-mktemp.h
+++ b/e-util/e-mktemp.h
@@ -20,6 +20,10 @@
  *
  */
 
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
 #ifndef __E_MKTEMP_H__
 #define __E_MKTEMP_H__
 
diff --git a/e-util/e-name-selector-dialog.c b/e-util/e-name-selector-dialog.c
new file mode 100644
index 0000000..ece556b
--- /dev/null
+++ b/e-util/e-name-selector-dialog.c
@@ -0,0 +1,1863 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/* e-name-selector-dialog.c - Dialog that lets user pick EDestinations.
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * 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., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * Author: Hans Petter Jansson <hpj novell com>
+ */
+
+#ifdef GTK_DISABLE_DEPRECATED
+#undef GTK_DISABLE_DEPRECATED
+#endif
+
+#include <config.h>
+#include <string.h>
+#include <gdk/gdkkeysyms.h>
+#include <glib/gi18n-lib.h>
+
+#include <libebook/libebook.h>
+#include <libebackend/libebackend.h>
+
+#include "e-source-combo-box.h"
+#include "e-destination-store.h"
+#include "e-contact-store.h"
+#include "e-client-utils.h"
+#include "e-name-selector-dialog.h"
+#include "e-name-selector-entry.h"
+
+#define E_NAME_SELECTOR_DIALOG_GET_PRIVATE(obj) \
+	(G_TYPE_INSTANCE_GET_PRIVATE \
+	((obj), E_TYPE_NAME_SELECTOR_DIALOG, ENameSelectorDialogPrivate))
+
+typedef struct {
+	gchar        *name;
+
+	GtkGrid      *section_grid;
+	GtkLabel     *label;
+	GtkButton    *transfer_button;
+	GtkButton    *remove_button;
+	GtkTreeView  *destination_view;
+}
+Section;
+
+typedef struct {
+	GtkTreeView *view;
+	GtkButton   *button;
+	ENameSelectorDialog *dlg_ptr;
+} SelData;
+
+struct _ENameSelectorDialogPrivate {
+	ESourceRegistry *registry;
+	ENameSelectorModel *name_selector_model;
+	GtkTreeModelSort *contact_sort;
+	GCancellable *cancellable;
+
+	GtkTreeView *contact_view;
+	GtkLabel *status_label;
+	GtkGrid *destination_vgrid;
+	GtkEntry *search_entry;
+	GtkSizeGroup *button_size_group;
+	GtkWidget *category_combobox;
+	GtkWidget *contact_window;
+
+	GArray *sections;
+
+	guint destination_index;
+	GSList *user_query_fields;
+	GtkSizeGroup *dest_label_size_group;
+};
+
+enum {
+	PROP_0,
+	PROP_REGISTRY
+};
+
+static void     search_changed                (ENameSelectorDialog *name_selector_dialog);
+static void     source_changed                (ENameSelectorDialog *name_selector_dialog, ESourceComboBox *source_combo_box);
+static void     transfer_button_clicked       (ENameSelectorDialog *name_selector_dialog, GtkButton *transfer_button);
+static void     contact_selection_changed     (ENameSelectorDialog *name_selector_dialog);
+static void     setup_name_selector_model     (ENameSelectorDialog *name_selector_dialog);
+static void     shutdown_name_selector_model  (ENameSelectorDialog *name_selector_dialog);
+static void     contact_activated             (ENameSelectorDialog *name_selector_dialog, GtkTreePath *path);
+static void     destination_activated         (ENameSelectorDialog *name_selector_dialog, GtkTreePath *path,
+					       GtkTreeViewColumn *column, GtkTreeView *tree_view);
+static gboolean destination_key_press         (ENameSelectorDialog *name_selector_dialog, GdkEventKey *event, GtkTreeView *tree_view);
+static void remove_button_clicked (GtkButton *button, SelData *data);
+static void     remove_books                  (ENameSelectorDialog *name_selector_dialog);
+static void     contact_column_formatter      (GtkTreeViewColumn *column, GtkCellRenderer *cell,
+					       GtkTreeModel *model, GtkTreeIter *iter,
+					       ENameSelectorDialog *name_selector_dialog);
+static void     destination_column_formatter  (GtkTreeViewColumn *column, GtkCellRenderer *cell,
+					       GtkTreeModel *model, GtkTreeIter *iter,
+					       ENameSelectorDialog *name_selector_dialog);
+
+/* ------------------ *
+ * Class/object setup *
+ * ------------------ */
+
+G_DEFINE_TYPE_WITH_CODE (
+	ENameSelectorDialog,
+	e_name_selector_dialog,
+	GTK_TYPE_DIALOG,
+	G_IMPLEMENT_INTERFACE (
+		E_TYPE_EXTENSIBLE, NULL))
+
+static void
+name_selector_dialog_populate_categories (ENameSelectorDialog *name_selector_dialog)
+{
+	GtkWidget *combo_box;
+	GList *category_list, *iter;
+
+	/* "Any Category" is preloaded. */
+	combo_box = name_selector_dialog->priv->category_combobox;
+	if (gtk_combo_box_get_active (GTK_COMBO_BOX (combo_box)) == -1)
+		gtk_combo_box_set_active (GTK_COMBO_BOX (combo_box), 0);
+
+	/* Categories are already sorted. */
+	category_list = e_categories_get_list ();
+	for (iter = category_list; iter != NULL; iter = iter->next) {
+		/* Only add user-visible categories. */
+		if (!e_categories_is_searchable (iter->data))
+			continue;
+
+		gtk_combo_box_text_append_text (
+			GTK_COMBO_BOX_TEXT (combo_box), iter->data);
+	}
+
+	g_list_free (category_list);
+
+	g_signal_connect_swapped (
+		combo_box, "changed",
+		G_CALLBACK (search_changed), name_selector_dialog);
+}
+
+static void
+name_selector_dialog_set_registry (ENameSelectorDialog *name_selector_dialog,
+                                   ESourceRegistry *registry)
+{
+	g_return_if_fail (E_IS_SOURCE_REGISTRY (registry));
+	g_return_if_fail (name_selector_dialog->priv->registry == NULL);
+
+	name_selector_dialog->priv->registry = g_object_ref (registry);
+}
+
+static void
+name_selector_dialog_set_property (GObject *object,
+                                   guint property_id,
+                                   const GValue *value,
+                                   GParamSpec *pspec)
+{
+	switch (property_id) {
+		case PROP_REGISTRY:
+			name_selector_dialog_set_registry (
+				E_NAME_SELECTOR_DIALOG (object),
+				g_value_get_object (value));
+			return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+name_selector_dialog_get_property (GObject *object,
+                                   guint property_id,
+                                   GValue *value,
+                                   GParamSpec *pspec)
+{
+	switch (property_id) {
+		case PROP_REGISTRY:
+			g_value_set_object (
+				value,
+				e_name_selector_dialog_get_registry (
+				E_NAME_SELECTOR_DIALOG (object)));
+			return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+name_selector_dialog_dispose (GObject *object)
+{
+	ENameSelectorDialogPrivate *priv;
+
+	priv = E_NAME_SELECTOR_DIALOG_GET_PRIVATE (object);
+
+	remove_books (E_NAME_SELECTOR_DIALOG (object));
+	shutdown_name_selector_model (E_NAME_SELECTOR_DIALOG (object));
+
+	if (priv->registry != NULL) {
+		g_object_unref (priv->registry);
+		priv->registry = NULL;
+	}
+
+	/* Chain up to parent's dispose() method. */
+	G_OBJECT_CLASS (e_name_selector_dialog_parent_class)->dispose (object);
+}
+
+static void
+name_selector_dialog_finalize (GObject *object)
+{
+	ENameSelectorDialogPrivate *priv;
+
+	priv = E_NAME_SELECTOR_DIALOG_GET_PRIVATE (object);
+
+	g_slist_foreach (priv->user_query_fields, (GFunc) g_free, NULL);
+	g_slist_free (priv->user_query_fields);
+
+	g_array_free (priv->sections, TRUE);
+	g_object_unref (priv->button_size_group);
+	g_object_unref (priv->dest_label_size_group);
+
+	/* Chain up to parent's finalize() method. */
+	G_OBJECT_CLASS (e_name_selector_dialog_parent_class)->finalize (object);
+}
+
+static void
+name_selector_dialog_constructed (GObject *object)
+{
+	ENameSelectorDialogPrivate *priv;
+	GtkTreeSelection  *contact_selection;
+	GtkTreeViewColumn *column;
+	GtkCellRenderer   *cell_renderer;
+	GtkTreeSelection  *selection;
+	ESource *source;
+	gchar *tmp_str;
+	GtkWidget *name_selector_grid;
+	GtkWidget *show_contacts_label;
+	GtkWidget *hgrid;
+	GtkWidget *label;
+	GtkWidget *show_contacts_grid;
+	GtkWidget *AddressBookLabel;
+	GtkWidget *label_category;
+	GtkWidget *search;
+	AtkObject *atko;
+	GtkWidget *label_search;
+	GtkWidget *source_menu_hgrid;
+	GtkWidget *combobox_category;
+	GtkWidget *label_contacts;
+	GtkWidget *scrolledwindow0;
+	GtkWidget *scrolledwindow1;
+	AtkRelationSet *tmp_relation_set;
+	AtkRelationType tmp_relationship;
+	AtkRelation *tmp_relation;
+	AtkObject *scrolledwindow1_relation_targets[1];
+	GtkWidget *source_tree_view;
+	GtkWidget *destination_vgrid;
+	GtkWidget *status_message;
+	GtkWidget *source_combo;
+	const gchar *extension_name;
+
+	priv = E_NAME_SELECTOR_DIALOG_GET_PRIVATE (object);
+
+	/* Chain up to parent's constructed() method. */
+	G_OBJECT_CLASS (e_name_selector_dialog_parent_class)->constructed (object);
+
+	name_selector_grid = g_object_new (GTK_TYPE_GRID,
+		"orientation", GTK_ORIENTATION_VERTICAL,
+		"column-homogeneous", FALSE,
+		"row-spacing", 6,
+		NULL);
+	gtk_widget_show (name_selector_grid);
+	gtk_container_set_border_width (GTK_CONTAINER (name_selector_grid), 0);
+
+	tmp_str = g_strconcat ("<b>", _("Show Contacts"), "</b>", NULL);
+	show_contacts_label = gtk_label_new (tmp_str);
+	gtk_widget_show (show_contacts_label);
+	gtk_container_add (GTK_CONTAINER (name_selector_grid), show_contacts_label);
+	gtk_label_set_use_markup (GTK_LABEL (show_contacts_label), TRUE);
+	gtk_misc_set_alignment (GTK_MISC (show_contacts_label), 0, 0.5);
+	g_free (tmp_str);
+
+	hgrid = g_object_new (GTK_TYPE_GRID,
+		"orientation", GTK_ORIENTATION_HORIZONTAL,
+		"row-homogeneous", FALSE,
+		"column-spacing", 12,
+		NULL);
+	gtk_widget_show (hgrid);
+	gtk_container_add (GTK_CONTAINER (name_selector_grid), hgrid);
+
+	label = gtk_label_new ("");
+	gtk_widget_show (label);
+	gtk_container_add (GTK_CONTAINER (hgrid), label);
+
+	show_contacts_grid = gtk_grid_new ();
+	gtk_widget_show (show_contacts_grid);
+	gtk_container_add (GTK_CONTAINER (hgrid), show_contacts_grid);
+	g_object_set (G_OBJECT (show_contacts_grid),
+		"column-spacing", 12,
+		"row-spacing", 6,
+		"hexpand", TRUE,
+		"halign", GTK_ALIGN_FILL,
+		NULL);
+
+	AddressBookLabel = gtk_label_new_with_mnemonic (_("Address B_ook:"));
+	gtk_widget_show (AddressBookLabel);
+	gtk_grid_attach (GTK_GRID (show_contacts_grid), AddressBookLabel, 0, 0, 1, 1);
+	gtk_widget_set_halign (AddressBookLabel, GTK_ALIGN_FILL);
+	gtk_label_set_justify (GTK_LABEL (AddressBookLabel), GTK_JUSTIFY_CENTER);
+	gtk_misc_set_alignment (GTK_MISC (AddressBookLabel), 0, 0.5);
+
+	label_category = gtk_label_new_with_mnemonic (_("Cat_egory:"));
+	gtk_widget_show (label_category);
+	gtk_grid_attach (GTK_GRID (show_contacts_grid), label_category, 0, 1, 1, 1);
+	gtk_widget_set_halign (label_category, GTK_ALIGN_FILL);
+	gtk_label_set_justify (GTK_LABEL (label_category), GTK_JUSTIFY_CENTER);
+	gtk_misc_set_alignment (GTK_MISC (label_category), 0, 0.5);
+
+	hgrid = g_object_new (GTK_TYPE_GRID,
+		"orientation", GTK_ORIENTATION_HORIZONTAL,
+		"row-homogeneous", FALSE,
+		"column-spacing", 12,
+		"hexpand", TRUE,
+		"halign", GTK_ALIGN_FILL,
+		NULL);
+	gtk_widget_show (hgrid);
+	gtk_grid_attach (GTK_GRID (show_contacts_grid), hgrid, 1, 2, 1, 1);
+
+	search = gtk_entry_new ();
+	gtk_widget_show (search);
+	gtk_widget_set_hexpand (search, TRUE);
+	gtk_widget_set_halign (search, GTK_ALIGN_FILL);
+	gtk_container_add (GTK_CONTAINER (hgrid), search);
+
+	label_search = gtk_label_new_with_mnemonic (_("_Search:"));
+	gtk_widget_show (label_search);
+	gtk_grid_attach (GTK_GRID (show_contacts_grid), label_search, 0, 2, 1, 1);
+	gtk_widget_set_halign (label_search, GTK_ALIGN_FILL);
+	gtk_misc_set_alignment (GTK_MISC (label_search), 0, 0.5);
+
+	source_menu_hgrid = g_object_new (GTK_TYPE_GRID,
+		"orientation", GTK_ORIENTATION_HORIZONTAL,
+		"row-homogeneous", FALSE,
+		"column-spacing", 0,
+		"halign", GTK_ALIGN_FILL,
+		"valign", GTK_ALIGN_FILL,
+		NULL);
+	gtk_widget_show (source_menu_hgrid);
+	gtk_grid_attach (GTK_GRID (show_contacts_grid), source_menu_hgrid, 1, 0, 1, 1);
+
+	combobox_category = gtk_combo_box_text_new ();
+	gtk_widget_show (combobox_category);
+	g_object_set (G_OBJECT (combobox_category),
+		"halign", GTK_ALIGN_FILL,
+		"valign", GTK_ALIGN_FILL,
+		NULL);
+	gtk_grid_attach (GTK_GRID (show_contacts_grid), combobox_category, 1, 1, 1, 1);
+	gtk_combo_box_text_append_text (
+		GTK_COMBO_BOX_TEXT (combobox_category), _("Any Category"));
+
+	tmp_str = g_strconcat ("<b>", _("Co_ntacts"), "</b>", NULL);
+	label_contacts = gtk_label_new_with_mnemonic (tmp_str);
+	gtk_widget_show (label_contacts);
+	gtk_container_add (GTK_CONTAINER (name_selector_grid), label_contacts);
+	gtk_label_set_use_markup (GTK_LABEL (label_contacts), TRUE);
+	gtk_misc_set_alignment (GTK_MISC (label_contacts), 0, 0.5);
+	g_free (tmp_str);
+
+	scrolledwindow0 = gtk_scrolled_window_new (NULL, NULL);
+	priv->contact_window = scrolledwindow0;
+	gtk_widget_show (scrolledwindow0);
+	gtk_widget_set_vexpand (scrolledwindow0, TRUE);
+	gtk_widget_set_valign (scrolledwindow0, GTK_ALIGN_FILL);
+	gtk_container_add (GTK_CONTAINER (name_selector_grid), scrolledwindow0);
+	gtk_scrolled_window_set_policy (
+		GTK_SCROLLED_WINDOW (scrolledwindow0),
+		GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
+
+	hgrid = g_object_new (GTK_TYPE_GRID,
+		"orientation", GTK_ORIENTATION_HORIZONTAL,
+		"row-homogeneous", FALSE,
+		"column-spacing", 12,
+		NULL);
+	gtk_widget_show (hgrid);
+	gtk_scrolled_window_add_with_viewport (
+		GTK_SCROLLED_WINDOW (scrolledwindow0), hgrid);
+
+	label = gtk_label_new ("");
+	gtk_widget_show (label);
+	gtk_container_add (GTK_CONTAINER (hgrid), label);
+
+	scrolledwindow1 = gtk_scrolled_window_new (NULL, NULL);
+	gtk_widget_show (scrolledwindow1);
+	gtk_container_add (GTK_CONTAINER (hgrid), scrolledwindow1);
+	gtk_widget_set_hexpand (scrolledwindow1, TRUE);
+	gtk_widget_set_halign (scrolledwindow1, GTK_ALIGN_FILL);
+	gtk_scrolled_window_set_policy (
+		GTK_SCROLLED_WINDOW (scrolledwindow1),
+		GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
+	gtk_scrolled_window_set_shadow_type (
+		GTK_SCROLLED_WINDOW (scrolledwindow1), GTK_SHADOW_IN);
+
+	source_tree_view = gtk_tree_view_new ();
+	gtk_widget_show (source_tree_view);
+	gtk_container_add (GTK_CONTAINER (scrolledwindow1), source_tree_view);
+	gtk_tree_view_set_headers_visible (
+		GTK_TREE_VIEW (source_tree_view), FALSE);
+	gtk_tree_view_set_enable_search (
+		GTK_TREE_VIEW (source_tree_view), FALSE);
+
+	destination_vgrid = g_object_new (GTK_TYPE_GRID,
+		"orientation", GTK_ORIENTATION_VERTICAL,
+		"column-homogeneous", TRUE,
+		"row-spacing", 6,
+		"hexpand", TRUE,
+		"halign", GTK_ALIGN_FILL,
+		"vexpand", TRUE,
+		"valign", GTK_ALIGN_FILL,
+		NULL);
+	gtk_widget_show (destination_vgrid);
+	gtk_container_add (GTK_CONTAINER (hgrid), destination_vgrid);
+
+	status_message = gtk_label_new ("");
+	gtk_widget_show (status_message);
+	gtk_container_add (GTK_CONTAINER (name_selector_grid), status_message);
+	gtk_label_set_use_markup (GTK_LABEL (status_message), TRUE);
+	gtk_misc_set_alignment (GTK_MISC (status_message), 0, 0.5);
+	gtk_misc_set_padding (GTK_MISC (status_message), 0, 3);
+
+	gtk_label_set_mnemonic_widget (GTK_LABEL (AddressBookLabel), source_menu_hgrid);
+	gtk_label_set_mnemonic_widget (GTK_LABEL (label_category), combobox_category);
+	gtk_label_set_mnemonic_widget (GTK_LABEL (label_search), search);
+	gtk_label_set_mnemonic_widget (GTK_LABEL (label_contacts), source_tree_view);
+
+	atko = gtk_widget_get_accessible (search);
+	atk_object_set_name (atko, _("Search"));
+
+	atko = gtk_widget_get_accessible (source_menu_hgrid);
+	atk_object_set_name (atko, _("Address Book"));
+
+	atko = gtk_widget_get_accessible (scrolledwindow1);
+	atk_object_set_name (atko, _("Contacts"));
+	tmp_relation_set = atk_object_ref_relation_set (atko);
+	scrolledwindow1_relation_targets[0] = gtk_widget_get_accessible (label_contacts);
+	tmp_relationship = atk_relation_type_for_name ("labelled-by");
+	tmp_relation = atk_relation_new (scrolledwindow1_relation_targets, 1, tmp_relationship);
+	atk_relation_set_add (tmp_relation_set, tmp_relation);
+	g_object_unref (G_OBJECT (tmp_relation));
+	g_object_unref (G_OBJECT (tmp_relation_set));
+
+	gtk_box_pack_start (
+		GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (object))),
+		name_selector_grid, TRUE, TRUE, 0);
+
+	/* Store pointers to relevant widgets */
+
+	priv->contact_view = GTK_TREE_VIEW (source_tree_view);
+	priv->status_label = GTK_LABEL (status_message);
+	priv->destination_vgrid = GTK_GRID (destination_vgrid);
+	priv->search_entry = GTK_ENTRY (search);
+	priv->category_combobox = combobox_category;
+
+	/* Create size group for transfer buttons */
+
+	priv->button_size_group =
+		gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
+
+	/* Create size group for destination labels */
+
+	priv->dest_label_size_group =
+		gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
+
+	/* Set up contacts view */
+
+	column = gtk_tree_view_column_new ();
+	cell_renderer = GTK_CELL_RENDERER (gtk_cell_renderer_text_new ());
+	gtk_tree_view_column_pack_start (column, cell_renderer, TRUE);
+	gtk_tree_view_column_set_cell_data_func (
+		column, cell_renderer, (GtkTreeCellDataFunc)
+		contact_column_formatter, object, NULL);
+
+	selection = gtk_tree_view_get_selection (priv->contact_view);
+	gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE);
+	gtk_tree_view_append_column (priv->contact_view, column);
+	g_signal_connect_swapped (
+		priv->contact_view, "row-activated",
+		G_CALLBACK (contact_activated), object);
+
+	/* Listen for changes to the contact selection */
+
+	contact_selection = gtk_tree_view_get_selection (priv->contact_view);
+	g_signal_connect_swapped (
+		contact_selection, "changed",
+		G_CALLBACK (contact_selection_changed), object);
+
+	/* Set up our data structures */
+
+	priv->name_selector_model = e_name_selector_model_new ();
+	priv->sections = g_array_new (FALSE, FALSE, sizeof (Section));
+
+	setup_name_selector_model (E_NAME_SELECTOR_DIALOG (object));
+
+	/* Create source menu */
+
+	extension_name = E_SOURCE_EXTENSION_ADDRESS_BOOK;
+	source_combo = e_source_combo_box_new (priv->registry, extension_name);
+	g_signal_connect_swapped (
+		source_combo, "changed",
+		G_CALLBACK (source_changed), object);
+
+	source_changed (E_NAME_SELECTOR_DIALOG (object), E_SOURCE_COMBO_BOX (source_combo));
+
+	gtk_label_set_mnemonic_widget (GTK_LABEL (AddressBookLabel), source_combo);
+	gtk_widget_show (source_combo);
+	gtk_widget_set_hexpand (source_combo, TRUE);
+	gtk_widget_set_halign (source_combo, GTK_ALIGN_FILL);
+	gtk_container_add (GTK_CONTAINER (source_menu_hgrid), source_combo);
+
+	name_selector_dialog_populate_categories (
+		E_NAME_SELECTOR_DIALOG (object));
+
+	/* Set up search-as-you-type signal */
+
+	g_signal_connect_swapped (
+		search, "changed",
+		G_CALLBACK (search_changed), object);
+
+	/* Display initial source */
+
+	source = e_source_registry_ref_default_address_book (priv->registry);
+	e_source_combo_box_set_active (
+		E_SOURCE_COMBO_BOX (source_combo), source);
+	g_object_unref (source);
+
+	/* Set up dialog defaults */
+
+	gtk_dialog_add_buttons (
+		GTK_DIALOG (object),
+		GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
+		NULL);
+
+	/* Try to figure out a sane default size for the dialog. We used to hard
+	 * code this to 512 so keep using 512 if the screen is big enough,
+	 * otherwise use -1 (use as little as possible, use the
+	 * GtkScrolledWindow's scrollbars).
+	 *
+	 * This should allow scrolling on tiny netbook resolutions and let
+	 * others see as much of the dialog as possible.
+	 *
+	 * 600 pixels seems to be a good lower bound resolution to allow room
+	 * above or below for other UI (window manager's?)
+	 */
+	gtk_window_set_default_size (
+		GTK_WINDOW (object), 700,
+		gdk_screen_height () >= 600 ? 512 : -1);
+
+	gtk_dialog_set_default_response (
+		GTK_DIALOG (object), GTK_RESPONSE_CLOSE);
+	gtk_window_set_modal (GTK_WINDOW (object), TRUE);
+	gtk_window_set_resizable (GTK_WINDOW (object), TRUE);
+	gtk_container_set_border_width (GTK_CONTAINER (object), 4);
+	gtk_window_set_title (
+		GTK_WINDOW (object),
+		_("Select Contacts from Address Book"));
+	gtk_widget_grab_focus (search);
+
+	e_extensible_load_extensions (E_EXTENSIBLE (object));
+}
+
+static void
+e_name_selector_dialog_class_init (ENameSelectorDialogClass *class)
+{
+	GObjectClass *object_class;
+
+	g_type_class_add_private (class, sizeof (ENameSelectorDialogPrivate));
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->set_property = name_selector_dialog_set_property;
+	object_class->get_property = name_selector_dialog_get_property;
+	object_class->dispose = name_selector_dialog_dispose;
+	object_class->finalize = name_selector_dialog_finalize;
+	object_class->constructed = name_selector_dialog_constructed;
+
+	g_object_class_install_property (
+		object_class,
+		PROP_REGISTRY,
+		g_param_spec_object (
+			"registry",
+			"Registry",
+			"Data source registry",
+			E_TYPE_SOURCE_REGISTRY,
+			G_PARAM_READWRITE |
+			G_PARAM_CONSTRUCT_ONLY |
+			G_PARAM_STATIC_STRINGS));
+}
+
+static void
+e_name_selector_dialog_init (ENameSelectorDialog *name_selector_dialog)
+{
+	name_selector_dialog->priv =
+		E_NAME_SELECTOR_DIALOG_GET_PRIVATE (name_selector_dialog);
+}
+
+/**
+ * e_name_selector_dialog_new:
+ * @registry: an #ESourceRegistry
+ *
+ * Creates a new #ENameSelectorDialog.
+ *
+ * Returns: A new #ENameSelectorDialog.
+ **/
+ENameSelectorDialog *
+e_name_selector_dialog_new (ESourceRegistry *registry)
+{
+	g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), NULL);
+
+	return g_object_new (
+		E_TYPE_NAME_SELECTOR_DIALOG,
+		"registry", registry, NULL);
+}
+
+/**
+ * e_name_selector_dialog_get_registry:
+ * @name_selector_dialog: an #ENameSelectorDialog
+ *
+ * Returns the #ESourceRegistry that was passed to
+ * e_name_selector_dialog_new().
+ *
+ * Returns: the #ESourceRegistry
+ *
+ * Since: 3.6
+ **/
+ESourceRegistry *
+e_name_selector_dialog_get_registry (ENameSelectorDialog *name_selector_dialog)
+{
+	g_return_val_if_fail (
+		E_IS_NAME_SELECTOR_DIALOG (name_selector_dialog), NULL);
+
+	return name_selector_dialog->priv->registry;
+}
+
+/* --------- *
+ * Utilities *
+ * --------- */
+
+static gchar *
+escape_sexp_string (const gchar *string)
+{
+	GString *gstring;
+	gchar   *encoded_string;
+
+	gstring = g_string_new ("");
+	e_sexp_encode_string (gstring, string);
+
+	encoded_string = gstring->str;
+	g_string_free (gstring, FALSE);
+
+	return encoded_string;
+}
+
+static void
+sort_iter_to_contact_store_iter (ENameSelectorDialog *name_selector_dialog,
+                                 GtkTreeIter *iter,
+                                 gint *email_n)
+{
+	ETreeModelGenerator *contact_filter;
+	GtkTreeIter          child_iter;
+	gint                 email_n_local;
+
+	contact_filter = e_name_selector_model_peek_contact_filter (
+		name_selector_dialog->priv->name_selector_model);
+
+	gtk_tree_model_sort_convert_iter_to_child_iter (
+		name_selector_dialog->priv->contact_sort, &child_iter, iter);
+	e_tree_model_generator_convert_iter_to_child_iter (
+		contact_filter, iter, &email_n_local, &child_iter);
+
+	if (email_n)
+		*email_n = email_n_local;
+}
+
+static void
+add_destination (ENameSelectorModel *name_selector_model,
+                 EDestinationStore *destination_store,
+                 EContact *contact,
+                 gint email_n,
+                 EBookClient *client)
+{
+	EDestination *destination;
+	GList *email_list, *nth;
+
+	/* get the correct index of an email in the contact */
+	email_list = e_name_selector_model_get_contact_emails_without_used (name_selector_model, contact, FALSE);
+	while (nth = g_list_nth (email_list, email_n), nth && nth->data == NULL) {
+		email_n++;
+	}
+	e_name_selector_model_free_emails_list (email_list);
+
+	/* Transfer (actually, copy into a destination and let the model filter out the
+	 * source automatically) */
+
+	destination = e_destination_new ();
+	e_destination_set_contact (destination, contact, email_n);
+	if (client)
+		e_destination_set_client (destination, client);
+	e_destination_store_append_destination (destination_store, destination);
+	g_object_unref (destination);
+}
+
+static void
+remove_books (ENameSelectorDialog *name_selector_dialog)
+{
+	EContactStore *contact_store;
+	GSList        *clients, *l;
+
+	if (!name_selector_dialog->priv->name_selector_model)
+		return;
+
+	contact_store = e_name_selector_model_peek_contact_store (
+		name_selector_dialog->priv->name_selector_model);
+
+	/* Remove books (should be just one) being viewed */
+	clients = e_contact_store_get_clients (contact_store);
+	for (l = clients; l; l = g_slist_next (l)) {
+		EBookClient *client = l->data;
+		e_contact_store_remove_client (contact_store, client);
+	}
+	g_slist_free (clients);
+
+	/* See if we have a book pending; stop loading it if so */
+	if (name_selector_dialog->priv->cancellable != NULL) {
+		g_cancellable_cancel (name_selector_dialog->priv->cancellable);
+		g_object_unref (name_selector_dialog->priv->cancellable);
+		name_selector_dialog->priv->cancellable = NULL;
+	}
+}
+
+/* ------------------ *
+ * Section management *
+ * ------------------ */
+
+static gint
+find_section_by_transfer_button (ENameSelectorDialog *name_selector_dialog,
+                                 GtkButton *transfer_button)
+{
+	gint i;
+
+	for (i = 0; i < name_selector_dialog->priv->sections->len; i++) {
+		Section *section = &g_array_index (
+			name_selector_dialog->priv->sections, Section, i);
+
+		if (section->transfer_button == transfer_button)
+			return i;
+	}
+
+	return -1;
+}
+
+static gint
+find_section_by_tree_view (ENameSelectorDialog *name_selector_dialog,
+                           GtkTreeView *tree_view)
+{
+	gint i;
+
+	for (i = 0; i < name_selector_dialog->priv->sections->len; i++) {
+		Section *section = &g_array_index (
+			name_selector_dialog->priv->sections, Section, i);
+
+		if (section->destination_view == tree_view)
+			return i;
+	}
+
+	return -1;
+}
+
+static gint
+find_section_by_name (ENameSelectorDialog *name_selector_dialog,
+                      const gchar *name)
+{
+	gint i;
+
+	for (i = 0; i < name_selector_dialog->priv->sections->len; i++) {
+		Section *section = &g_array_index (
+			name_selector_dialog->priv->sections, Section, i);
+
+		if (!strcmp (name, section->name))
+			return i;
+	}
+
+	return -1;
+}
+
+static void
+selection_changed (GtkTreeSelection *selection,
+                   SelData *data)
+{
+	GtkTreeSelection *contact_selection;
+	gboolean          have_selection = FALSE;
+
+	contact_selection = gtk_tree_view_get_selection (data->view);
+	if (gtk_tree_selection_count_selected_rows (contact_selection) > 0)
+		have_selection = TRUE;
+	gtk_widget_set_sensitive (GTK_WIDGET (data->button), have_selection);
+}
+
+static GtkTreeView *
+make_tree_view_for_section (ENameSelectorDialog *name_selector_dialog,
+                            EDestinationStore *destination_store)
+{
+	GtkTreeView *tree_view;
+	GtkTreeViewColumn *column;
+	GtkCellRenderer   *cell_renderer;
+
+	tree_view = GTK_TREE_VIEW (gtk_tree_view_new ());
+
+	column = gtk_tree_view_column_new ();
+	cell_renderer = GTK_CELL_RENDERER (gtk_cell_renderer_text_new ());
+	gtk_tree_view_column_pack_start (column, cell_renderer, TRUE);
+	gtk_tree_view_column_set_cell_data_func (
+		column, cell_renderer,
+		(GtkTreeCellDataFunc) destination_column_formatter,
+		name_selector_dialog, NULL);
+	gtk_tree_view_append_column (tree_view, column);
+	gtk_tree_view_set_headers_visible (tree_view, FALSE);
+	gtk_tree_view_set_model (tree_view, GTK_TREE_MODEL (destination_store));
+
+	return tree_view;
+}
+
+static void
+setup_section_button (ENameSelectorDialog *name_selector_dialog,
+                      GtkButton *button,
+                      double halign,
+                      const gchar *label_text,
+                      const gchar *icon_name,
+                      gboolean icon_before_label)
+{
+	GtkWidget *alignment;
+	GtkWidget *hgrid;
+	GtkWidget *label;
+	GtkWidget *image;
+
+	gtk_size_group_add_widget (
+		name_selector_dialog->priv->button_size_group,
+		GTK_WIDGET (button));
+
+	alignment = gtk_alignment_new (halign, 0.5, 0.0, 0.0);
+	gtk_container_add (GTK_CONTAINER (button), GTK_WIDGET (alignment));
+
+	hgrid = g_object_new (GTK_TYPE_GRID,
+		"orientation", GTK_ORIENTATION_HORIZONTAL,
+		"row-homogeneous", FALSE,
+		"column-spacing", 2,
+		NULL);
+	gtk_widget_show (hgrid);
+	gtk_container_add (GTK_CONTAINER (alignment), hgrid);
+
+	label = gtk_label_new_with_mnemonic (label_text);
+	gtk_widget_show (label);
+
+	image = gtk_image_new_from_stock (icon_name, GTK_ICON_SIZE_BUTTON);
+	gtk_widget_show (image);
+
+	if (icon_before_label) {
+		gtk_container_add (GTK_CONTAINER (hgrid), image);
+		gtk_container_add (GTK_CONTAINER (hgrid), label);
+	} else {
+		gtk_container_add (GTK_CONTAINER (hgrid), label);
+		gtk_container_add (GTK_CONTAINER (hgrid), image);
+	}
+}
+
+static gint
+add_section (ENameSelectorDialog *name_selector_dialog,
+             const gchar *name,
+             const gchar *pretty_name,
+             EDestinationStore *destination_store)
+{
+	ENameSelectorDialogPrivate *priv;
+	Section            section;
+	GtkWidget	  *vgrid;
+	GtkWidget	  *alignment;
+	GtkWidget	  *scrollwin;
+	SelData		  *data;
+	GtkTreeSelection  *selection;
+	gchar		  *text;
+	GtkWidget         *hgrid;
+
+	g_assert (name != NULL);
+	g_assert (pretty_name != NULL);
+	g_assert (E_IS_DESTINATION_STORE (destination_store));
+
+	priv = E_NAME_SELECTOR_DIALOG_GET_PRIVATE (name_selector_dialog);
+
+	memset (&section, 0, sizeof (Section));
+
+	section.name = g_strdup (name);
+	section.section_grid = g_object_new (GTK_TYPE_GRID,
+		"orientation", GTK_ORIENTATION_HORIZONTAL,
+		"row-homogeneous", FALSE,
+		"column-spacing", 12,
+		"vexpand", TRUE,
+		"valign", GTK_ALIGN_FILL,
+		NULL);
+	section.label = GTK_LABEL (gtk_label_new_with_mnemonic (pretty_name));
+	section.transfer_button  = GTK_BUTTON (gtk_button_new ());
+	section.remove_button  = GTK_BUTTON (gtk_button_new ());
+	section.destination_view = make_tree_view_for_section (name_selector_dialog, destination_store);
+
+	gtk_label_set_mnemonic_widget (GTK_LABEL (section.label), GTK_WIDGET (section.destination_view));
+
+	if (pango_parse_markup (pretty_name, -1, '_', NULL,
+				&text, NULL, NULL))  {
+		atk_object_set_name (gtk_widget_get_accessible (
+					GTK_WIDGET (section.destination_view)), text);
+		g_free (text);
+	}
+
+	/* Set up transfer button */
+	g_signal_connect_swapped (
+		section.transfer_button, "clicked",
+		G_CALLBACK (transfer_button_clicked), name_selector_dialog);
+
+	/*data for the remove callback*/
+	data = g_malloc0 (sizeof (SelData));
+	data->view = section.destination_view;
+	data->dlg_ptr = name_selector_dialog;
+
+	/*Associate to an object destroy so that it gets freed*/
+	g_object_set_data_full ((GObject *) section.destination_view, "sel-remove-data", data, g_free);
+
+	g_signal_connect (
+		section.remove_button, "clicked",
+		G_CALLBACK (remove_button_clicked), data);
+
+	/* Alignment and vgrid for the add/remove buttons */
+
+	alignment = gtk_alignment_new (0.5, 0.0, 0.0, 0.0);
+	gtk_container_add (GTK_CONTAINER (section.section_grid), alignment);
+
+	vgrid = g_object_new (GTK_TYPE_GRID,
+		"orientation", GTK_ORIENTATION_VERTICAL,
+		"column-homogeneous", TRUE,
+		"row-spacing", 6,
+		NULL);
+
+	gtk_container_add (GTK_CONTAINER (alignment), vgrid);
+
+	/* "Add" button */
+	gtk_container_add (GTK_CONTAINER (vgrid), GTK_WIDGET (section.transfer_button));
+	setup_section_button (name_selector_dialog, section.transfer_button, 0.7, _("_Add"), "gtk-go-forward", FALSE);
+
+	/* "Remove" button */
+	gtk_container_add (GTK_CONTAINER (vgrid), GTK_WIDGET (section.remove_button));
+	setup_section_button (name_selector_dialog, section.remove_button, 0.5, _("_Remove"), "gtk-go-back", TRUE);
+	gtk_widget_set_sensitive (GTK_WIDGET (section.remove_button), FALSE);
+
+	/* hgrid for label and scrolled window. This is a separate hgrid, instead
+	 * of just using the section.section_grid directly, as it has a different
+	 * spacing.
+	 */
+
+	hgrid = g_object_new (GTK_TYPE_GRID,
+		"orientation", GTK_ORIENTATION_HORIZONTAL,
+		"row-homogeneous", FALSE,
+		"column-spacing", 6,
+		"vexpand", TRUE,
+		"valign", GTK_ALIGN_FILL,
+		NULL);
+	gtk_container_add (GTK_CONTAINER (section.section_grid), hgrid);
+
+	/* Title label */
+
+	gtk_size_group_add_widget (priv->dest_label_size_group, GTK_WIDGET (section.label));
+
+	gtk_misc_set_alignment (GTK_MISC (section.label), 0.0, 0.0);
+	gtk_container_add (GTK_CONTAINER (hgrid), GTK_WIDGET (section.label));
+
+	/* Treeview in a scrolled window */
+	scrollwin = gtk_scrolled_window_new (NULL, NULL);
+	gtk_container_add (GTK_CONTAINER (hgrid), scrollwin);
+	gtk_widget_set_hexpand (scrollwin, TRUE);
+	gtk_widget_set_halign (scrollwin, GTK_ALIGN_FILL);
+	gtk_widget_set_vexpand (scrollwin, TRUE);
+	gtk_widget_set_valign (scrollwin, GTK_ALIGN_FILL);
+	gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrollwin), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
+	gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrollwin), GTK_SHADOW_IN);
+	gtk_container_add (GTK_CONTAINER (scrollwin), GTK_WIDGET (section.destination_view));
+
+	/*data for 'changed' callback*/
+	data = g_malloc0 (sizeof (SelData));
+	data->view = section.destination_view;
+	data->button = section.remove_button;
+	g_object_set_data_full ((GObject *) section.destination_view, "sel-change-data", data, g_free);
+	selection = gtk_tree_view_get_selection (section.destination_view);
+	gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE);
+
+	g_signal_connect (
+		selection, "changed",
+		G_CALLBACK (selection_changed), data);
+
+	g_signal_connect_swapped (
+		section.destination_view, "row-activated",
+		G_CALLBACK (destination_activated), name_selector_dialog);
+	g_signal_connect_swapped (
+		section.destination_view, "key-press-event",
+		G_CALLBACK (destination_key_press), name_selector_dialog);
+
+	/* Done! */
+
+	gtk_widget_show_all (GTK_WIDGET (section.section_grid));
+
+	/* Pack this section's box into the dialog */
+	gtk_container_add (GTK_CONTAINER (name_selector_dialog->priv->destination_vgrid), GTK_WIDGET (section.section_grid));
+	g_object_set (G_OBJECT (section.section_grid),
+		"vexpand", TRUE,
+		"valign", GTK_ALIGN_FILL,
+		NULL);
+
+	g_array_append_val (name_selector_dialog->priv->sections, section);
+
+	/* Make sure UI is consistent */
+	contact_selection_changed (name_selector_dialog);
+
+	return name_selector_dialog->priv->sections->len - 1;
+}
+
+static void
+free_section (ENameSelectorDialog *name_selector_dialog,
+              gint n)
+{
+	Section *section;
+
+	g_assert (n >= 0);
+	g_assert (n < name_selector_dialog->priv->sections->len);
+
+	section = &g_array_index (
+		name_selector_dialog->priv->sections, Section, n);
+
+	g_free (section->name);
+	gtk_widget_destroy (GTK_WIDGET (section->section_grid));
+}
+
+static void
+model_section_added (ENameSelectorDialog *name_selector_dialog,
+                     const gchar *name)
+{
+	gchar             *pretty_name;
+	EDestinationStore *destination_store;
+
+	e_name_selector_model_peek_section (
+		name_selector_dialog->priv->name_selector_model,
+		name, &pretty_name, &destination_store);
+	add_section (name_selector_dialog, name, pretty_name, destination_store);
+	g_free (pretty_name);
+}
+
+static void
+model_section_removed (ENameSelectorDialog *name_selector_dialog,
+                       const gchar *name)
+{
+	gint section_index;
+
+	section_index = find_section_by_name (name_selector_dialog, name);
+	g_assert (section_index >= 0);
+
+	free_section (name_selector_dialog, section_index);
+	g_array_remove_index (
+		name_selector_dialog->priv->sections, section_index);
+}
+
+/* -------------------- *
+ * Addressbook selector *
+ * -------------------- */
+
+static void
+view_progress (EBookClientView *view,
+               guint percent,
+               const gchar *message,
+               ENameSelectorDialog *dialog)
+{
+	if (message == NULL)
+		gtk_label_set_text (dialog->priv->status_label, "");
+	else
+		gtk_label_set_text (dialog->priv->status_label, message);
+}
+
+static void
+view_complete (EBookClientView *view,
+               const GError *error,
+               ENameSelectorDialog *dialog)
+{
+	view_progress (view, -1, NULL, dialog);
+}
+
+static void
+start_client_view_cb (EContactStore *store,
+                      EBookClientView *client_view,
+                      ENameSelectorDialog *name_selector_dialog)
+{
+	g_signal_connect (
+		client_view, "progress",
+		G_CALLBACK (view_progress), name_selector_dialog);
+
+	g_signal_connect (
+		client_view, "complete",
+		G_CALLBACK (view_complete), name_selector_dialog);
+}
+
+static void
+stop_client_view_cb (EContactStore *store,
+                     EBookClientView *client_view,
+                     ENameSelectorDialog *name_selector_dialog)
+{
+	g_signal_handlers_disconnect_by_func (client_view, view_progress, name_selector_dialog);
+	g_signal_handlers_disconnect_by_func (client_view, view_complete, name_selector_dialog);
+}
+
+static void
+book_loaded_cb (GObject *source_object,
+                GAsyncResult *result,
+                gpointer user_data)
+{
+	ENameSelectorDialog *name_selector_dialog = user_data;
+	EClient *client = NULL;
+	EBookClient *book_client;
+	EContactStore *store;
+	ENameSelectorModel *model;
+	GError *error = NULL;
+
+	e_client_utils_open_new_finish (E_SOURCE (source_object), result, &client, &error);
+
+	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+		g_warn_if_fail (client == NULL);
+		g_error_free (error);
+		goto exit;
+	}
+
+	if (error != NULL) {
+		gchar *message;
+
+		message = g_strdup_printf (
+			_("Error loading address book: %s"), error->message);
+		gtk_label_set_text (
+			name_selector_dialog->priv->status_label, message);
+		g_free (message);
+
+		g_warn_if_fail (client == NULL);
+		g_error_free (error);
+		goto exit;
+	}
+
+	book_client = E_BOOK_CLIENT (client);
+	if (!book_client) {
+		g_warn_if_fail (book_client != NULL);
+		goto exit;
+	}
+
+	model = name_selector_dialog->priv->name_selector_model;
+	store = e_name_selector_model_peek_contact_store (model);
+	e_contact_store_add_client (store, book_client);
+	g_object_unref (book_client);
+
+ exit:
+	g_object_unref (name_selector_dialog);
+}
+
+static void
+source_changed (ENameSelectorDialog *name_selector_dialog,
+                ESourceComboBox *source_combo_box)
+{
+	GCancellable *cancellable;
+	ESource *source;
+	gpointer parent;
+
+	source = e_source_combo_box_ref_active (source_combo_box);
+
+	parent = gtk_widget_get_toplevel (GTK_WIDGET (name_selector_dialog));
+	parent = gtk_widget_is_toplevel (parent) ? parent : NULL;
+
+	/* Remove any previous books being shown or loaded */
+	remove_books (name_selector_dialog);
+
+	if (source == NULL)
+		return;
+
+	cancellable = g_cancellable_new ();
+	name_selector_dialog->priv->cancellable = cancellable;
+
+	/* Start loading selected book */
+	e_client_utils_open_new (
+		source, E_CLIENT_SOURCE_TYPE_CONTACTS, TRUE, cancellable,
+		book_loaded_cb, g_object_ref (name_selector_dialog));
+
+	g_object_unref (source);
+}
+
+/* --------------- *
+ * Other UI events *
+ * --------------- */
+
+static void
+search_changed (ENameSelectorDialog *name_selector_dialog)
+{
+	ENameSelectorDialogPrivate *priv = E_NAME_SELECTOR_DIALOG_GET_PRIVATE (name_selector_dialog);
+	EContactStore *contact_store;
+	EBookQuery    *book_query;
+	GtkWidget     *combo_box;
+	const gchar   *text;
+	gchar         *text_escaped;
+	gchar         *query_string;
+	gchar         *category;
+	gchar         *category_escaped;
+	gchar         *user_fields_str;
+
+	combo_box = priv->category_combobox;
+	if (gtk_combo_box_get_active (GTK_COMBO_BOX (combo_box)) == -1)
+		gtk_combo_box_set_active (GTK_COMBO_BOX (combo_box), 0);
+
+	category = gtk_combo_box_text_get_active_text (GTK_COMBO_BOX_TEXT (combo_box));
+	category_escaped = escape_sexp_string (category);
+
+	text = gtk_entry_get_text (name_selector_dialog->priv->search_entry);
+	text_escaped = escape_sexp_string (text);
+
+	user_fields_str = ens_util_populate_user_query_fields (priv->user_query_fields, text, text_escaped);
+
+	if (g_strcmp0 (category, _("Any Category")) == 0)
+		query_string = g_strdup_printf (
+			"(or (beginswith \"file_as\" %s) "
+			"    (beginswith \"full_name\" %s) "
+			"    (beginswith \"email\" %s) "
+			"    (beginswith \"nickname\" %s)%s))",
+			text_escaped, text_escaped,
+			text_escaped, text_escaped,
+			user_fields_str ? user_fields_str : "");
+	else
+		query_string = g_strdup_printf (
+			"(and (is \"category_list\" %s) "
+			"(or (beginswith \"file_as\" %s) "
+			"    (beginswith \"full_name\" %s) "
+			"    (beginswith \"email\" %s) "
+			"    (beginswith \"nickname\" %s)%s))",
+			category_escaped, text_escaped, text_escaped,
+			text_escaped, text_escaped,
+			user_fields_str ? user_fields_str : "");
+
+	book_query = e_book_query_from_string (query_string);
+
+	contact_store = e_name_selector_model_peek_contact_store (
+		name_selector_dialog->priv->name_selector_model);
+	e_contact_store_set_query (contact_store, book_query);
+	e_book_query_unref (book_query);
+
+	g_free (query_string);
+	g_free (text_escaped);
+	g_free (category_escaped);
+	g_free (category);
+	g_free (user_fields_str);
+}
+
+static void
+contact_selection_changed (ENameSelectorDialog *name_selector_dialog)
+{
+	GtkTreeSelection *contact_selection;
+	gboolean          have_selection = FALSE;
+	gint              i;
+
+	contact_selection = gtk_tree_view_get_selection (
+		name_selector_dialog->priv->contact_view);
+	if (gtk_tree_selection_count_selected_rows (contact_selection))
+		have_selection = TRUE;
+
+	for (i = 0; i < name_selector_dialog->priv->sections->len; i++) {
+		Section *section = &g_array_index (
+			name_selector_dialog->priv->sections, Section, i);
+		gtk_widget_set_sensitive (GTK_WIDGET (section->transfer_button), have_selection);
+	}
+}
+
+static void
+contact_activated (ENameSelectorDialog *name_selector_dialog,
+                   GtkTreePath *path)
+{
+	EContactStore     *contact_store;
+	EDestinationStore *destination_store;
+	EContact          *contact;
+	GtkTreeIter       iter;
+	Section           *section;
+	gint               email_n;
+
+	/* When a contact is activated, we transfer it to the first destination on our list */
+
+	contact_store = e_name_selector_model_peek_contact_store (
+		name_selector_dialog->priv->name_selector_model);
+
+	/* If we have no sections, we can't transfer */
+	if (name_selector_dialog->priv->sections->len == 0)
+		return;
+
+	/* Get the contact to be transferred */
+
+	if (!gtk_tree_model_get_iter (
+		GTK_TREE_MODEL (name_selector_dialog->priv->contact_sort),
+		&iter, path))
+		g_assert_not_reached ();
+
+	sort_iter_to_contact_store_iter (name_selector_dialog, &iter, &email_n);
+
+	contact = e_contact_store_get_contact (contact_store, &iter);
+	if (!contact) {
+		g_warning ("ENameSelectorDialog could not get selected contact!");
+		return;
+	}
+
+	section = &g_array_index (
+		name_selector_dialog->priv->sections,
+		Section, name_selector_dialog->priv->destination_index);
+	if (!e_name_selector_model_peek_section (
+		name_selector_dialog->priv->name_selector_model,
+		section->name, NULL, &destination_store)) {
+		g_warning ("ENameSelectorDialog has a section unknown to the model!");
+		return;
+	}
+
+	add_destination (
+		name_selector_dialog->priv->name_selector_model,
+		destination_store, contact, email_n,
+		e_contact_store_get_client (contact_store, &iter));
+}
+
+static void
+destination_activated (ENameSelectorDialog *name_selector_dialog,
+                       GtkTreePath *path,
+                       GtkTreeViewColumn *column,
+                       GtkTreeView *tree_view)
+{
+	gint               section_index;
+	EDestinationStore *destination_store;
+	EDestination      *destination;
+	Section           *section;
+	GtkTreeIter        iter;
+
+	/* When a destination is activated, we remove it from the section */
+
+	section_index = find_section_by_tree_view (
+		name_selector_dialog, tree_view);
+	if (section_index < 0) {
+		g_warning ("ENameSelectorDialog got activation from unknown view!");
+		return;
+	}
+
+	section = &g_array_index (
+		name_selector_dialog->priv->sections, Section, section_index);
+	if (!e_name_selector_model_peek_section (
+		name_selector_dialog->priv->name_selector_model,
+		section->name, NULL, &destination_store)) {
+		g_warning ("ENameSelectorDialog has a section unknown to the model!");
+		return;
+	}
+
+	if (!gtk_tree_model_get_iter (
+		GTK_TREE_MODEL (destination_store), &iter, path))
+		g_assert_not_reached ();
+
+	destination = e_destination_store_get_destination (
+		destination_store, &iter);
+	g_assert (destination);
+
+	e_destination_store_remove_destination (
+		destination_store, destination);
+}
+
+static gboolean
+remove_selection (ENameSelectorDialog *name_selector_dialog,
+                  GtkTreeView *tree_view)
+{
+	gint               section_index;
+	EDestinationStore *destination_store;
+	EDestination      *destination;
+	Section           *section;
+	GtkTreeSelection  *selection;
+	GList		  *rows, *l;
+
+	section_index = find_section_by_tree_view (
+		name_selector_dialog, tree_view);
+	if (section_index < 0) {
+		g_warning ("ENameSelectorDialog got key press from unknown view!");
+		return FALSE;
+	}
+
+	section = &g_array_index (
+		name_selector_dialog->priv->sections, Section, section_index);
+	if (!e_name_selector_model_peek_section (
+		name_selector_dialog->priv->name_selector_model,
+		section->name, NULL, &destination_store)) {
+		g_warning ("ENameSelectorDialog has a section unknown to the model!");
+		return FALSE;
+	}
+
+	selection = gtk_tree_view_get_selection (tree_view);
+	if (!gtk_tree_selection_count_selected_rows (selection)) {
+		g_warning ("ENameSelectorDialog remove button clicked, but no selection!");
+		return FALSE;
+	}
+
+	rows = gtk_tree_selection_get_selected_rows (selection, NULL);
+	rows = g_list_reverse (rows);
+
+	for (l = rows; l; l = g_list_next (l)) {
+		GtkTreeIter iter;
+		GtkTreePath *path = l->data;
+
+		if (!gtk_tree_model_get_iter (GTK_TREE_MODEL (destination_store),
+					      &iter, path))
+			g_assert_not_reached ();
+
+		gtk_tree_path_free (path);
+
+		destination = e_destination_store_get_destination (
+			destination_store, &iter);
+		g_assert (destination);
+
+		e_destination_store_remove_destination (
+			destination_store, destination);
+	}
+	g_list_free (rows);
+
+	return TRUE;
+}
+
+static void
+remove_button_clicked (GtkButton *button,
+                       SelData *data)
+{
+	GtkTreeView *view;
+	ENameSelectorDialog *name_selector_dialog;
+
+	view = data->view;
+	name_selector_dialog = data->dlg_ptr;
+	remove_selection (name_selector_dialog, view);
+}
+
+static gboolean
+destination_key_press (ENameSelectorDialog *name_selector_dialog,
+                       GdkEventKey *event,
+                       GtkTreeView *tree_view)
+{
+
+	/* we only care about DEL key */
+	if (event->keyval != GDK_KEY_Delete)
+		return FALSE;
+	return remove_selection (name_selector_dialog, tree_view);
+
+}
+
+static void
+transfer_button_clicked (ENameSelectorDialog *name_selector_dialog,
+                         GtkButton *transfer_button)
+{
+	EContactStore     *contact_store;
+	EDestinationStore *destination_store;
+	GtkTreeSelection  *selection;
+	EContact          *contact;
+	gint               section_index;
+	Section           *section;
+	gint               email_n;
+	GList		  *rows, *l;
+
+	/* Get the contact to be transferred */
+
+	contact_store = e_name_selector_model_peek_contact_store (
+		name_selector_dialog->priv->name_selector_model);
+	selection = gtk_tree_view_get_selection (
+		name_selector_dialog->priv->contact_view);
+
+	if (!gtk_tree_selection_count_selected_rows (selection)) {
+		g_warning ("ENameSelectorDialog transfer button clicked, but no selection!");
+		return;
+	}
+
+	/* Get the target section */
+	section_index = find_section_by_transfer_button (
+		name_selector_dialog, transfer_button);
+	if (section_index < 0) {
+		g_warning ("ENameSelectorDialog got click from unknown button!");
+		return;
+	}
+
+	section = &g_array_index (
+		name_selector_dialog->priv->sections, Section, section_index);
+	if (!e_name_selector_model_peek_section (
+		name_selector_dialog->priv->name_selector_model,
+		section->name, NULL, &destination_store)) {
+		g_warning ("ENameSelectorDialog has a section unknown to the model!");
+		return;
+	}
+
+	rows = gtk_tree_selection_get_selected_rows (selection, NULL);
+	rows = g_list_reverse (rows);
+
+	for (l = rows; l; l = g_list_next (l)) {
+		GtkTreeIter iter;
+		GtkTreePath *path = l->data;
+
+		if (!gtk_tree_model_get_iter (
+			GTK_TREE_MODEL (name_selector_dialog->priv->contact_sort),
+			&iter, path)) {
+			gtk_tree_path_free (path);
+			return;
+		}
+
+		gtk_tree_path_free (path);
+		sort_iter_to_contact_store_iter (name_selector_dialog, &iter, &email_n);
+
+		contact = e_contact_store_get_contact (contact_store, &iter);
+		if (!contact) {
+			g_warning ("ENameSelectorDialog could not get selected contact!");
+			g_list_free (rows);
+			return;
+		}
+
+		add_destination (
+			name_selector_dialog->priv->name_selector_model,
+			destination_store, contact, email_n,
+			e_contact_store_get_client (contact_store, &iter));
+	}
+	g_list_free (rows);
+}
+
+/* --------------------- *
+ * Main model management *
+ * --------------------- */
+
+static void
+setup_name_selector_model (ENameSelectorDialog *name_selector_dialog)
+{
+	ETreeModelGenerator *contact_filter;
+	EContactStore       *contact_store;
+	GList               *new_sections;
+	GList               *l;
+
+	/* Create new destination sections in UI */
+
+	new_sections = e_name_selector_model_list_sections (
+		name_selector_dialog->priv->name_selector_model);
+
+	for (l = new_sections; l; l = g_list_next (l)) {
+		gchar             *name = l->data;
+		gchar             *pretty_name;
+		EDestinationStore *destination_store;
+
+		e_name_selector_model_peek_section (
+			name_selector_dialog->priv->name_selector_model,
+			name, &pretty_name, &destination_store);
+
+		add_section (name_selector_dialog, name, pretty_name, destination_store);
+
+		g_free (pretty_name);
+		g_free (name);
+	}
+
+	g_list_free (new_sections);
+
+	/* Connect to section add/remove signals */
+
+	g_signal_connect_swapped (
+		name_selector_dialog->priv->name_selector_model, "section-added",
+		G_CALLBACK (model_section_added), name_selector_dialog);
+	g_signal_connect_swapped (
+		name_selector_dialog->priv->name_selector_model, "section-removed",
+		G_CALLBACK (model_section_removed), name_selector_dialog);
+
+	/* Get contact store and its filter wrapper */
+
+	contact_filter = e_name_selector_model_peek_contact_filter (
+		name_selector_dialog->priv->name_selector_model);
+
+	/* Create sorting model on top of filter, assign it to view */
+
+	name_selector_dialog->priv->contact_sort = GTK_TREE_MODEL_SORT (
+		gtk_tree_model_sort_new_with_model (GTK_TREE_MODEL (contact_filter)));
+
+	/* sort on full name as we display full name in name selector dialog */
+	gtk_tree_sortable_set_sort_column_id (
+		GTK_TREE_SORTABLE (name_selector_dialog->priv->contact_sort),
+		E_CONTACT_FULL_NAME, GTK_SORT_ASCENDING);
+
+	gtk_tree_view_set_model (
+		name_selector_dialog->priv->contact_view,
+		GTK_TREE_MODEL (name_selector_dialog->priv->contact_sort));
+
+	contact_store = e_name_selector_model_peek_contact_store (name_selector_dialog->priv->name_selector_model);
+	if (contact_store) {
+		g_signal_connect (contact_store, "start-client-view", G_CALLBACK (start_client_view_cb), name_selector_dialog);
+		g_signal_connect (contact_store, "stop-client-view", G_CALLBACK (stop_client_view_cb), name_selector_dialog);
+	}
+
+	/* Make sure UI is consistent */
+
+	search_changed (name_selector_dialog);
+	contact_selection_changed (name_selector_dialog);
+}
+
+static void
+shutdown_name_selector_model (ENameSelectorDialog *name_selector_dialog)
+{
+	gint i;
+
+	/* Rid UI of previous destination sections */
+
+	for (i = 0; i < name_selector_dialog->priv->sections->len; i++)
+		free_section (name_selector_dialog, i);
+
+	g_array_set_size (name_selector_dialog->priv->sections, 0);
+
+	/* Free sorting model */
+
+	if (name_selector_dialog->priv->contact_sort) {
+		g_object_unref (name_selector_dialog->priv->contact_sort);
+		name_selector_dialog->priv->contact_sort = NULL;
+	}
+
+	/* Free backend model */
+
+	if (name_selector_dialog->priv->name_selector_model) {
+		EContactStore *contact_store;
+
+		contact_store = e_name_selector_model_peek_contact_store (name_selector_dialog->priv->name_selector_model);
+		if (contact_store) {
+			g_signal_handlers_disconnect_by_func (contact_store, start_client_view_cb, name_selector_dialog);
+			g_signal_handlers_disconnect_by_func (contact_store, stop_client_view_cb, name_selector_dialog);
+		}
+
+		g_signal_handlers_disconnect_matched (
+			name_selector_dialog->priv->name_selector_model,
+			G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, name_selector_dialog);
+
+		g_object_unref (name_selector_dialog->priv->name_selector_model);
+		name_selector_dialog->priv->name_selector_model = NULL;
+	}
+}
+
+static void
+contact_column_formatter (GtkTreeViewColumn *column,
+                          GtkCellRenderer *cell,
+                          GtkTreeModel *model,
+                          GtkTreeIter *iter,
+                          ENameSelectorDialog *name_selector_dialog)
+{
+	EContactStore *contact_store;
+	EContact      *contact;
+	GtkTreeIter    contact_store_iter;
+	GList         *email_list;
+	gchar         *string;
+	gchar         *full_name_str;
+	gchar         *email_str;
+	gint           email_n;
+
+	contact_store_iter = *iter;
+	sort_iter_to_contact_store_iter (
+		name_selector_dialog, &contact_store_iter, &email_n);
+
+	contact_store = e_name_selector_model_peek_contact_store (
+		name_selector_dialog->priv->name_selector_model);
+	contact = e_contact_store_get_contact (
+		contact_store, &contact_store_iter);
+	email_list = e_name_selector_model_get_contact_emails_without_used (
+		name_selector_dialog->priv->name_selector_model, contact, TRUE);
+	email_str = g_list_nth_data (email_list, email_n);
+	full_name_str = e_contact_get (contact, E_CONTACT_FULL_NAME);
+
+	if (e_contact_get (contact, E_CONTACT_IS_LIST)) {
+		if (!full_name_str)
+			full_name_str = e_contact_get (contact, E_CONTACT_FILE_AS);
+		string = g_strdup_printf ("%s", full_name_str ? full_name_str : "?");
+	} else {
+		string = g_strdup_printf (
+			"%s%s<%s>", full_name_str ? full_name_str : "",
+			full_name_str ? " " : "",
+			email_str ? email_str : "");
+	}
+
+	g_free (full_name_str);
+	e_name_selector_model_free_emails_list (email_list);
+
+	g_object_set (cell, "text", string, NULL);
+	g_free (string);
+}
+
+static void
+destination_column_formatter (GtkTreeViewColumn *column,
+                              GtkCellRenderer *cell,
+                              GtkTreeModel *model,
+                              GtkTreeIter *iter,
+                              ENameSelectorDialog *name_selector_dialog)
+{
+	EDestinationStore *destination_store = E_DESTINATION_STORE (model);
+	EDestination      *destination;
+	GString           *buffer;
+
+	destination = e_destination_store_get_destination (destination_store, iter);
+	g_assert (destination);
+
+	buffer = g_string_new (e_destination_get_name (destination));
+
+	if (!e_destination_is_evolution_list (destination)) {
+		const gchar *email;
+
+		email = e_destination_get_email (destination);
+		if (email == NULL || *email == '\0')
+			email = "?";
+		g_string_append_printf (buffer, " <%s>", email);
+	}
+
+	g_object_set (cell, "text", buffer->str, NULL);
+	g_string_free (buffer, TRUE);
+}
+
+/* ----------------------- *
+ * ENameSelectorDialog API *
+ * ----------------------- */
+
+/**
+ * e_name_selector_dialog_peek_model:
+ * @name_selector_dialog: an #ENameSelectorDialog
+ *
+ * Gets the #ENameSelectorModel used by @name_selector_model.
+ *
+ * Returns: The #ENameSelectorModel being used.
+ **/
+ENameSelectorModel *
+e_name_selector_dialog_peek_model (ENameSelectorDialog *name_selector_dialog)
+{
+	g_return_val_if_fail (E_IS_NAME_SELECTOR_DIALOG (name_selector_dialog), NULL);
+
+	return name_selector_dialog->priv->name_selector_model;
+}
+
+/**
+ * e_name_selector_dialog_set_model:
+ * @name_selector_dialog: an #ENameSelectorDialog
+ * @model: an #ENameSelectorModel
+ *
+ * Sets the model being used by @name_selector_dialog to @model.
+ **/
+void
+e_name_selector_dialog_set_model (ENameSelectorDialog *name_selector_dialog,
+                                  ENameSelectorModel *model)
+{
+	g_return_if_fail (E_IS_NAME_SELECTOR_DIALOG (name_selector_dialog));
+	g_return_if_fail (E_IS_NAME_SELECTOR_MODEL (model));
+
+	if (model == name_selector_dialog->priv->name_selector_model)
+		return;
+
+	shutdown_name_selector_model (name_selector_dialog);
+	name_selector_dialog->priv->name_selector_model = g_object_ref (model);
+
+	setup_name_selector_model (name_selector_dialog);
+}
+
+/**
+ * e_name_selector_dialog_set_destination_index:
+ * @name_selector_dialog: an #ENameSelectorDialog
+ * @index: index of the destination section, starting from 0.
+ *
+ * Sets the index number of the destination section.
+ **/
+void
+e_name_selector_dialog_set_destination_index (ENameSelectorDialog *name_selector_dialog,
+                                              guint index)
+{
+	g_return_if_fail (E_IS_NAME_SELECTOR_DIALOG (name_selector_dialog));
+
+	if (index >= name_selector_dialog->priv->sections->len)
+		return;
+
+	name_selector_dialog->priv->destination_index = index;
+}
+
+/**
+ * e_name_selector_dialog_set_scrolling_policy:
+ * @name_selector_dialog: an #ENameSelectorDialog
+ * @hscrollbar_policy: scrolling policy for horizontal bar of the contacts window.
+ * @vscrollbar_policy: scrolling policy for vertical bar of the contacts window.
+ *
+ * Sets the scrolling policy for the contacts section.
+ *
+ * Since: 3.2
+ **/
+void
+e_name_selector_dialog_set_scrolling_policy (ENameSelectorDialog *name_selector_dialog,
+                                             GtkPolicyType hscrollbar_policy,
+                                             GtkPolicyType vscrollbar_policy)
+{
+	GtkScrolledWindow *win = GTK_SCROLLED_WINDOW (name_selector_dialog->priv->contact_window);
+
+	gtk_scrolled_window_set_policy (win, hscrollbar_policy, vscrollbar_policy);
+}
+
+/**
+ * e_name_selector_dialog_get_section_visible:
+ * @name_selector_dialog: an #ENameSelectorDialog
+ * @name: name of the section
+ *
+ * Returns: whether section named @name is visible in the dialog.
+ *
+ * Since: 3.8
+ **/
+gboolean
+e_name_selector_dialog_get_section_visible (ENameSelectorDialog *name_selector_dialog,
+                                            const gchar *name)
+{
+	Section *section;
+	gint index;
+
+	g_return_val_if_fail (E_IS_NAME_SELECTOR_DIALOG (name_selector_dialog), FALSE);
+	g_return_val_if_fail (name != NULL, FALSE);
+
+	index = find_section_by_name (name_selector_dialog, name);
+	g_return_val_if_fail (index != -1, FALSE);
+
+	section = &g_array_index (name_selector_dialog->priv->sections, Section, index);
+	return gtk_widget_get_visible (GTK_WIDGET (section->section_grid));
+}
+
+/**
+ * e_name_selector_dialog_set_section_visible:
+ * @name_selector_dialog: an #ENameSelectorDialog
+ * @name: name of the section
+ * @visible: whether to show or hide the section
+ *
+ * Shows or hides section named @name in the dialog.
+ *
+ * Since: 3.8
+ **/
+void
+e_name_selector_dialog_set_section_visible (ENameSelectorDialog *name_selector_dialog,
+                                            const gchar *name,
+                                            gboolean visible)
+{
+	Section *section;
+	gint index;
+
+	g_return_if_fail (E_IS_NAME_SELECTOR_DIALOG (name_selector_dialog));
+	g_return_if_fail (name != NULL);
+
+	index = find_section_by_name (name_selector_dialog, name);
+	g_return_if_fail (index != -1);
+
+	section = &g_array_index (name_selector_dialog->priv->sections, Section, index);
+
+	if (visible)
+		gtk_widget_show (GTK_WIDGET (section->section_grid));
+	else
+		gtk_widget_hide (GTK_WIDGET (section->section_grid));
+}
+
diff --git a/e-util/e-name-selector-dialog.h b/e-util/e-name-selector-dialog.h
new file mode 100644
index 0000000..fe10544
--- /dev/null
+++ b/e-util/e-name-selector-dialog.h
@@ -0,0 +1,100 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/* e-name-selector-dialog.c - Dialog that lets user pick EDestinations.
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * 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., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * Authors: Hans Petter Jansson <hpj novell com>
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_NAME_SELECTOR_DIALOG_H
+#define E_NAME_SELECTOR_DIALOG_H
+
+#include <gtk/gtk.h>
+#include <libedataserver/libedataserver.h>
+
+#include <e-util/e-contact-store.h>
+#include <e-util/e-name-selector-model.h>
+
+/* Standard GObject macros */
+#define E_TYPE_NAME_SELECTOR_DIALOG \
+	(e_name_selector_dialog_get_type ())
+#define E_NAME_SELECTOR_DIALOG(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_NAME_SELECTOR_DIALOG, ENameSelectorDialog))
+#define E_NAME_SELECTOR_DIALOG_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_NAME_SELECTOR_DIALOG, ENameSelectorDialogClass))
+#define E_IS_NAME_SELECTOR_DIALOG(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	(obj, E_TYPE_NAME_SELECTOR_DIALOG))
+#define E_IS_NAME_SELECTOR_DIALOG_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_NAME_SELECTOR_DIALOG))
+#define E_NAME_SELECTOR_DIALOG_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_NAME_SELECTOR_DIALOG, ENameSelectorDialogClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ENameSelectorDialog ENameSelectorDialog;
+typedef struct _ENameSelectorDialogClass ENameSelectorDialogClass;
+typedef struct _ENameSelectorDialogPrivate ENameSelectorDialogPrivate;
+
+struct _ENameSelectorDialog {
+	GtkDialog parent;
+	ENameSelectorDialogPrivate *priv;
+};
+
+struct _ENameSelectorDialogClass {
+	GtkDialogClass parent_class;
+};
+
+GType		e_name_selector_dialog_get_type	(void);
+ENameSelectorDialog *
+		e_name_selector_dialog_new	(ESourceRegistry *registry);
+ESourceRegistry *
+		e_name_selector_dialog_get_registry
+						(ENameSelectorDialog *name_selector_dialog);
+ENameSelectorModel *
+		e_name_selector_dialog_peek_model
+						(ENameSelectorDialog *name_selector_dialog);
+void		e_name_selector_dialog_set_model
+						(ENameSelectorDialog *name_selector_dialog,
+						 ENameSelectorModel  *model);
+void		e_name_selector_dialog_set_destination_index
+						(ENameSelectorDialog *name_selector_dialog,
+						 guint index);
+void		e_name_selector_dialog_set_scrolling_policy
+						(ENameSelectorDialog *name_selector_dialog,
+						 GtkPolicyType hscrollbar_policy,
+						 GtkPolicyType vscrollbar_policy);
+gboolean	e_name_selector_dialog_get_section_visible
+						(ENameSelectorDialog *name_selector_dialog,
+						 const gchar *name);
+void		e_name_selector_dialog_set_section_visible
+						(ENameSelectorDialog *name_selector_dialog,
+						 const gchar *name,
+						 gboolean visible);
+
+G_END_DECLS
+
+#endif /* E_NAME_SELECTOR_DIALOG_H */
diff --git a/e-util/e-name-selector-entry.c b/e-util/e-name-selector-entry.c
new file mode 100644
index 0000000..ea7e2ef
--- /dev/null
+++ b/e-util/e-name-selector-entry.c
@@ -0,0 +1,3541 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/* e-name-selector-entry.c - Single-line text entry widget for EDestinations.
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * 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., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * Authors: Hans Petter Jansson <hpj novell com>
+ */
+
+#include <config.h>
+#include <string.h>
+#include <glib/gi18n-lib.h>
+
+#include <camel/camel.h>
+#include <libebackend/libebackend.h>
+
+#include "e-client-utils.h"
+#include "e-name-selector-entry.h"
+
+#define E_NAME_SELECTOR_ENTRY_GET_PRIVATE(obj) \
+	(G_TYPE_INSTANCE_GET_PRIVATE \
+	((obj), E_TYPE_NAME_SELECTOR_ENTRY, ENameSelectorEntryPrivate))
+
+struct _ENameSelectorEntryPrivate {
+
+	ESourceRegistry *registry;
+	gint minimum_query_length;
+	gboolean show_address;
+
+	PangoAttrList *attr_list;
+	EContactStore *contact_store;
+	ETreeModelGenerator *email_generator;
+	EDestinationStore *destination_store;
+	GtkEntryCompletion *entry_completion;
+
+	guint type_ahead_complete_cb_id;
+	guint update_completions_cb_id;
+
+	EDestination *popup_destination;
+
+	gpointer	(*contact_editor_func)	(EBookClient *,
+						 EContact *,
+						 gboolean,
+						 gboolean);
+	gpointer	(*contact_list_editor_func)
+						(EBookClient *,
+						 EContact *,
+						 gboolean,
+						 gboolean);
+
+	gboolean is_completing;
+	GSList *user_query_fields;
+
+	/* For asynchronous operations. */
+	GQueue cancellables;
+};
+
+enum {
+	PROP_0,
+	PROP_REGISTRY,
+	PROP_MINIMUM_QUERY_LENGTH,
+	PROP_SHOW_ADDRESS
+};
+
+enum {
+	UPDATED,
+	LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+#define ENS_DEBUG(x)
+
+G_DEFINE_TYPE_WITH_CODE (
+	ENameSelectorEntry,
+	e_name_selector_entry,
+	GTK_TYPE_ENTRY,
+	G_IMPLEMENT_INTERFACE (
+		E_TYPE_EXTENSIBLE, NULL))
+
+/* 1/3 of the second to wait until invoking autocomplete lookup */
+#define AUTOCOMPLETE_TIMEOUT 333
+
+#define re_set_timeout(id,func,ptr)			\
+	if (id)						\
+		g_source_remove (id);			\
+	id = g_timeout_add (AUTOCOMPLETE_TIMEOUT,	\
+			    (GSourceFunc) func, ptr);
+
+static void destination_row_inserted (ENameSelectorEntry *name_selector_entry, GtkTreePath *path, GtkTreeIter *iter);
+static void destination_row_changed  (ENameSelectorEntry *name_selector_entry, GtkTreePath *path, GtkTreeIter *iter);
+static void destination_row_deleted  (ENameSelectorEntry *name_selector_entry, GtkTreePath *path);
+
+static void user_insert_text (ENameSelectorEntry *name_selector_entry, gchar *new_text, gint new_text_length, gint *position, gpointer user_data);
+static void user_delete_text (ENameSelectorEntry *name_selector_entry, gint start_pos, gint end_pos, gpointer user_data);
+
+static void setup_default_contact_store (ENameSelectorEntry *name_selector_entry);
+static void deep_free_list (GList *list);
+
+static void
+name_selector_entry_set_property (GObject *object,
+                                  guint property_id,
+                                  const GValue *value,
+                                  GParamSpec *pspec)
+{
+	switch (property_id) {
+		case PROP_REGISTRY:
+			e_name_selector_entry_set_registry (
+				E_NAME_SELECTOR_ENTRY (object),
+				g_value_get_object (value));
+			return;
+
+		case PROP_MINIMUM_QUERY_LENGTH:
+			e_name_selector_entry_set_minimum_query_length (
+				E_NAME_SELECTOR_ENTRY (object),
+				g_value_get_int (value));
+			return;
+
+		case PROP_SHOW_ADDRESS:
+			e_name_selector_entry_set_show_address (
+				E_NAME_SELECTOR_ENTRY (object),
+				g_value_get_boolean (value));
+			return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+name_selector_entry_get_property (GObject *object,
+                                  guint property_id,
+                                  GValue *value,
+                                  GParamSpec *pspec)
+{
+	switch (property_id) {
+		case PROP_REGISTRY:
+			g_value_set_object (
+				value,
+				e_name_selector_entry_get_registry (
+				E_NAME_SELECTOR_ENTRY (object)));
+			return;
+
+		case PROP_MINIMUM_QUERY_LENGTH:
+			g_value_set_int (
+				value,
+				e_name_selector_entry_get_minimum_query_length (
+				E_NAME_SELECTOR_ENTRY (object)));
+			return;
+
+		case PROP_SHOW_ADDRESS:
+			g_value_set_boolean (
+				value,
+				e_name_selector_entry_get_show_address (
+				E_NAME_SELECTOR_ENTRY (object)));
+			return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+name_selector_entry_dispose (GObject *object)
+{
+	ENameSelectorEntryPrivate *priv;
+
+	priv = E_NAME_SELECTOR_ENTRY_GET_PRIVATE (object);
+
+	if (priv->registry != NULL) {
+		g_object_unref (priv->registry);
+		priv->registry = NULL;
+	}
+
+	if (priv->attr_list != NULL) {
+		pango_attr_list_unref (priv->attr_list);
+		priv->attr_list = NULL;
+	}
+
+	if (priv->entry_completion) {
+		g_object_unref (priv->entry_completion);
+		priv->entry_completion = NULL;
+	}
+
+	if (priv->destination_store) {
+		g_object_unref (priv->destination_store);
+		priv->destination_store = NULL;
+	}
+
+	if (priv->email_generator) {
+		g_object_unref (priv->email_generator);
+		priv->email_generator = NULL;
+	}
+
+	if (priv->contact_store) {
+		g_object_unref (priv->contact_store);
+		priv->contact_store = NULL;
+	}
+
+	g_slist_foreach (priv->user_query_fields, (GFunc) g_free, NULL);
+	g_slist_free (priv->user_query_fields);
+	priv->user_query_fields = NULL;
+
+	/* Cancel any stuck book loading operations. */
+	while (!g_queue_is_empty (&priv->cancellables)) {
+		GCancellable *cancellable;
+
+		cancellable = g_queue_pop_head (&priv->cancellables);
+		g_cancellable_cancel (cancellable);
+		g_object_unref (cancellable);
+	}
+
+	/* Chain up to parent's dispose() method. */
+	G_OBJECT_CLASS (e_name_selector_entry_parent_class)->dispose (object);
+}
+
+static void
+name_selector_entry_constructed (GObject *object)
+{
+	/* Chain up to parent's constructed() method. */
+	G_OBJECT_CLASS (e_name_selector_entry_parent_class)->
+		constructed (object);
+
+	e_extensible_load_extensions (E_EXTENSIBLE (object));
+}
+
+static void
+name_selector_entry_realize (GtkWidget *widget)
+{
+	ENameSelectorEntryPrivate *priv;
+
+	priv = E_NAME_SELECTOR_ENTRY_GET_PRIVATE (widget);
+
+	/* Chain up to parent's realize() method. */
+	GTK_WIDGET_CLASS (e_name_selector_entry_parent_class)->realize (widget);
+
+	if (priv->contact_store == NULL)
+		setup_default_contact_store (E_NAME_SELECTOR_ENTRY (widget));
+}
+
+static void
+name_selector_entry_drag_data_received (GtkWidget *widget,
+                                        GdkDragContext *context,
+                                        gint x,
+                                        gint y,
+                                        GtkSelectionData *selection_data,
+                                        guint info,
+                                        guint time)
+{
+	CamelInternetAddress *address;
+	gint n_addresses = 0;
+	gchar *text;
+
+	address = camel_internet_address_new ();
+	text = (gchar *) gtk_selection_data_get_text (selection_data);
+
+	/* See if Camel can parse a valid email address from the text. */
+	if (text != NULL && *text != '\0') {
+		camel_url_decode (text);
+		if (g_ascii_strncasecmp (text, "mailto:";, 7) == 0)
+			n_addresses = camel_address_decode (
+				CAMEL_ADDRESS (address), text + 7);
+		else
+			n_addresses = camel_address_decode (
+				CAMEL_ADDRESS (address), text);
+	}
+
+	if (n_addresses > 0) {
+		GtkEditable *editable;
+		GdkDragAction action;
+		gboolean delete;
+		gint position;
+
+		editable = GTK_EDITABLE (widget);
+		gtk_editable_set_position (editable, -1);
+		position = gtk_editable_get_position (editable);
+
+		g_free (text);
+
+		text = camel_address_format (CAMEL_ADDRESS (address));
+		gtk_editable_insert_text (editable, text, -1, &position);
+
+		action = gdk_drag_context_get_selected_action (context);
+		delete = (action == GDK_ACTION_MOVE);
+		gtk_drag_finish (context, TRUE, delete, time);
+	}
+
+	g_object_unref (address);
+	g_free (text);
+
+	if (n_addresses <= 0)
+		/* Chain up to parent's drag_data_received() method. */
+		GTK_WIDGET_CLASS (e_name_selector_entry_parent_class)->
+			drag_data_received (
+				widget, context, x, y,
+				selection_data, info, time);
+}
+
+static void
+e_name_selector_entry_class_init (ENameSelectorEntryClass *class)
+{
+	GObjectClass *object_class;
+	GtkWidgetClass *widget_class;
+
+	g_type_class_add_private (class, sizeof (ENameSelectorEntryPrivate));
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->set_property = name_selector_entry_set_property;
+	object_class->get_property = name_selector_entry_get_property;
+	object_class->dispose = name_selector_entry_dispose;
+	object_class->constructed = name_selector_entry_constructed;
+
+	widget_class = GTK_WIDGET_CLASS (class);
+	widget_class->realize = name_selector_entry_realize;
+	widget_class->drag_data_received = name_selector_entry_drag_data_received;
+
+	g_object_class_install_property (
+		object_class,
+		PROP_REGISTRY,
+		g_param_spec_object (
+			"registry",
+			"Registry",
+			"Data source registry",
+			E_TYPE_SOURCE_REGISTRY,
+			G_PARAM_READWRITE |
+			G_PARAM_CONSTRUCT |
+			G_PARAM_STATIC_STRINGS));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_MINIMUM_QUERY_LENGTH,
+		g_param_spec_int (
+			"minimum-query-length",
+			"Minimum Query Length",
+			NULL,
+			1, G_MAXINT,
+			3,
+			G_PARAM_READWRITE |
+			G_PARAM_STATIC_STRINGS));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_SHOW_ADDRESS,
+		g_param_spec_boolean (
+			"show-address",
+			"Show Address",
+			NULL,
+			FALSE,
+			G_PARAM_READWRITE |
+			G_PARAM_STATIC_STRINGS));
+
+	signals[UPDATED] = g_signal_new (
+		"updated",
+		E_TYPE_NAME_SELECTOR_ENTRY,
+		G_SIGNAL_RUN_FIRST,
+		G_STRUCT_OFFSET (ENameSelectorEntryClass, updated),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__POINTER,
+		G_TYPE_NONE, 1, G_TYPE_POINTER);
+}
+
+/* Remove unquoted commas and control characters from string */
+static gchar *
+sanitize_string (const gchar *string)
+{
+	GString     *gstring;
+	gboolean     quoted = FALSE;
+	const gchar *p;
+
+	gstring = g_string_new ("");
+
+	if (!string)
+		return g_string_free (gstring, FALSE);
+
+	for (p = string; *p; p = g_utf8_next_char (p)) {
+		gunichar c = g_utf8_get_char (p);
+
+		if (c == '"')
+			quoted = ~quoted;
+		else if (c == ',' && !quoted)
+			continue;
+		else if (c == '\t' || c == '\n')
+			continue;
+
+		g_string_append_unichar (gstring, c);
+	}
+
+	return g_string_free (gstring, FALSE);
+}
+
+/* Called for each list store entry whenever the user types (but not on cut/paste) */
+static gboolean
+completion_match_cb (GtkEntryCompletion *completion,
+                     const gchar *key,
+                     GtkTreeIter *iter,
+                     gpointer user_data)
+{
+	ENS_DEBUG (g_print ("completion_match_cb, key=%s\n", key));
+
+	return TRUE;
+}
+
+/* Gets context of n_unichars total (n_unicars / 2, before and after position)
+ * and places them in array. If any positions would be outside the string, the
+ * corresponding unichars are set to zero. */
+static void
+get_utf8_string_context (const gchar *string,
+                         gint position,
+                         gunichar *unichars,
+                         gint n_unichars)
+{
+	gchar *p = NULL;
+	gint   len;
+	gint   gap;
+	gint   i;
+
+	/* n_unichars must be even */
+	g_assert (n_unichars % 2 == 0);
+
+	len = g_utf8_strlen (string, -1);
+	gap = n_unichars / 2;
+
+	for (i = 0; i < n_unichars; i++) {
+		gint char_pos = position - gap + i;
+
+		if (char_pos < 0 || char_pos >= len) {
+			unichars[i] = '\0';
+			continue;
+		}
+
+		if (p)
+			p = g_utf8_next_char (p);
+		else
+			p = g_utf8_offset_to_pointer (string, char_pos);
+
+		unichars[i] = g_utf8_get_char (p);
+	}
+}
+
+static gboolean
+get_range_at_position (const gchar *string,
+                       gint pos,
+                       gint *start_pos,
+                       gint *end_pos)
+{
+	const gchar *p;
+	gboolean     quoted          = FALSE;
+	gint         local_start_pos = 0;
+	gint         local_end_pos   = 0;
+	gint         i;
+
+	if (!string || !*string)
+		return FALSE;
+
+	for (p = string, i = 0; *p; p = g_utf8_next_char (p), i++) {
+		gunichar c = g_utf8_get_char (p);
+
+		if (c == '"') {
+			quoted = ~quoted;
+		} else if (c == ',' && !quoted) {
+			if (i < pos) {
+				/* Start right after comma */
+				local_start_pos = i + 1;
+			} else {
+				/* Stop right before comma */
+				local_end_pos = i;
+				break;
+			}
+		} else if (c == ' ' && local_start_pos == i) {
+			/* Adjust start to skip space after first comma */
+			local_start_pos++;
+		}
+	}
+
+	/* If we didn't hit a comma, we must've hit NULL, and ours was the last element. */
+	if (!local_end_pos)
+		local_end_pos = i;
+
+	if (start_pos)
+		*start_pos = local_start_pos;
+	if (end_pos)
+		*end_pos   = local_end_pos;
+
+	return TRUE;
+}
+
+static gboolean
+is_quoted_at (const gchar *string,
+              gint pos)
+{
+	const gchar *p;
+	gboolean     quoted = FALSE;
+	gint         i;
+
+	for (p = string, i = 0; *p && i < pos; p = g_utf8_next_char (p), i++) {
+		gunichar c = g_utf8_get_char (p);
+
+		if (c == '"')
+			quoted = ~quoted;
+	}
+
+	return quoted ? TRUE : FALSE;
+}
+
+static gint
+get_index_at_position (const gchar *string,
+                       gint pos)
+{
+	const gchar *p;
+	gboolean     quoted = FALSE;
+	gint         n      = 0;
+	gint         i;
+
+	for (p = string, i = 0; *p && i < pos; p = g_utf8_next_char (p), i++) {
+		gunichar c = g_utf8_get_char (p);
+
+		if (c == '"')
+			quoted = ~quoted;
+		else if (c == ',' && !quoted)
+			n++;
+	}
+
+	return n;
+}
+
+static gboolean
+get_range_by_index (const gchar *string,
+                    gint index,
+                    gint *start_pos,
+                    gint *end_pos)
+{
+	const gchar *p;
+	gboolean     quoted = FALSE;
+	gint         i;
+	gint         n = 0;
+
+	for (p = string, i = 0; *p && n < index; p = g_utf8_next_char (p), i++) {
+		gunichar c = g_utf8_get_char (p);
+
+		if (c == '"')
+			quoted = ~quoted;
+		if (c == ',' && !quoted)
+			n++;
+	}
+
+	if (n < index)
+		return FALSE;
+
+	return get_range_at_position (string, i, start_pos, end_pos);
+}
+
+static gchar *
+get_address_at_position (const gchar *string,
+                         gint pos)
+{
+	gint         start_pos;
+	gint         end_pos;
+	const gchar *start_p;
+	const gchar *end_p;
+
+	if (!get_range_at_position (string, pos, &start_pos, &end_pos))
+		return NULL;
+
+	start_p = g_utf8_offset_to_pointer (string, start_pos);
+	end_p   = g_utf8_offset_to_pointer (string, end_pos);
+
+	return g_strndup (start_p, end_p - start_p);
+}
+
+/* Finds the destination in model */
+static EDestination *
+find_destination_by_index (ENameSelectorEntry *name_selector_entry,
+                           gint index)
+{
+	GtkTreePath  *path;
+	GtkTreeIter   iter;
+
+	path = gtk_tree_path_new_from_indices (index, -1);
+	if (!gtk_tree_model_get_iter (GTK_TREE_MODEL (name_selector_entry->priv->destination_store),
+				      &iter, path)) {
+		/* If we have zero destinations, getting a NULL destination at index 0
+		 * is valid. */
+		if (index > 0)
+			g_warning ("ENameSelectorEntry is out of sync with model!");
+		gtk_tree_path_free (path);
+		return NULL;
+	}
+	gtk_tree_path_free (path);
+
+	return e_destination_store_get_destination (name_selector_entry->priv->destination_store, &iter);
+}
+
+/* Finds the destination in model */
+static EDestination *
+find_destination_at_position (ENameSelectorEntry *name_selector_entry,
+                              gint pos)
+{
+	const gchar  *text;
+	gint          index;
+
+	text = gtk_entry_get_text (GTK_ENTRY (name_selector_entry));
+	index = get_index_at_position (text, pos);
+
+	return find_destination_by_index (name_selector_entry, index);
+}
+
+/* Builds destination from our text */
+static EDestination *
+build_destination_at_position (const gchar *string,
+                               gint pos)
+{
+	EDestination *destination;
+	gchar        *address;
+
+	address = get_address_at_position (string, pos);
+	if (!address)
+		return NULL;
+
+	destination = e_destination_new ();
+	e_destination_set_raw (destination, address);
+
+	g_free (address);
+	return destination;
+}
+
+static gchar *
+name_style_query (const gchar *field,
+                  const gchar *value)
+{
+	gchar   *spaced_str;
+	gchar   *comma_str;
+	GString *out = g_string_new ("");
+	gchar  **strv;
+	gchar   *query;
+
+	spaced_str = sanitize_string (value);
+	g_strstrip (spaced_str);
+
+	strv = g_strsplit (spaced_str, " ", 0);
+
+	if (strv[0] && strv[1]) {
+		g_string_append (out, "(or ");
+		comma_str = g_strjoinv (", ", strv);
+	} else {
+		comma_str = NULL;
+	}
+
+	g_string_append (out, " (beginswith ");
+	e_sexp_encode_string (out, field);
+	e_sexp_encode_string (out, spaced_str);
+	g_string_append (out, ")");
+
+	if (comma_str) {
+		g_string_append (out, " (beginswith ");
+
+		e_sexp_encode_string (out, field);
+		g_strstrip (comma_str);
+		e_sexp_encode_string (out, comma_str);
+		g_string_append (out, "))");
+	}
+
+	query = g_string_free (out, FALSE);
+
+	g_free (spaced_str);
+	g_free (comma_str);
+	g_strfreev (strv);
+
+	return query;
+}
+
+static gchar *
+escape_sexp_string (const gchar *string)
+{
+	GString *gstring;
+	gchar   *encoded_string;
+
+	gstring = g_string_new ("");
+	e_sexp_encode_string (gstring, string);
+
+	encoded_string = gstring->str;
+	g_string_free (gstring, FALSE);
+
+	return encoded_string;
+}
+
+/**
+ * ens_util_populate_user_query_fields:
+ *
+ * Populates list of user query fields to string usable in query string.
+ * Returned pointer is either newly allocated string, supposed to be freed with g_free,
+ * or NULL if no fields defined.
+ *
+ * Since: 2.24
+ **/
+gchar *
+ens_util_populate_user_query_fields (GSList *user_query_fields,
+                                     const gchar *cue_str,
+                                     const gchar *encoded_cue_str)
+{
+	GString *user_fields;
+	GSList *s;
+
+	g_return_val_if_fail (cue_str != NULL, NULL);
+	g_return_val_if_fail (encoded_cue_str != NULL, NULL);
+
+	user_fields = g_string_new ("");
+
+	for (s = user_query_fields; s; s = s->next) {
+		const gchar *field = s->data;
+
+		if (!field || !*field)
+			continue;
+
+		if (*field == '$') {
+			g_string_append_printf (user_fields, " (beginswith \"%s\" %s) ", field + 1, encoded_cue_str);
+		} else if (*field == '@') {
+			g_string_append_printf (user_fields, " (is \"%s\" %s) ", field + 1, encoded_cue_str);
+		} else {
+			gchar *tmp = name_style_query (field, cue_str);
+
+			g_string_append (user_fields, " ");
+			g_string_append (user_fields, tmp);
+			g_string_append (user_fields, " ");
+			g_free (tmp);
+		}
+	}
+
+	return g_string_free (user_fields, !user_fields->str || !*user_fields->str);
+}
+
+static void
+set_completion_query (ENameSelectorEntry *name_selector_entry,
+                      const gchar *cue_str)
+{
+	ENameSelectorEntryPrivate *priv;
+	EBookQuery *book_query;
+	gchar      *query_str;
+	gchar      *encoded_cue_str;
+	gchar      *full_name_query_str;
+	gchar      *file_as_query_str;
+	gchar      *user_fields_str;
+
+	priv = E_NAME_SELECTOR_ENTRY_GET_PRIVATE (name_selector_entry);
+
+	if (!name_selector_entry->priv->contact_store)
+		return;
+
+	if (!cue_str) {
+		/* Clear the store */
+		e_contact_store_set_query (name_selector_entry->priv->contact_store, NULL);
+		return;
+	}
+
+	encoded_cue_str     = escape_sexp_string (cue_str);
+	full_name_query_str = name_style_query ("full_name", cue_str);
+	file_as_query_str   = name_style_query ("file_as",   cue_str);
+	user_fields_str     = ens_util_populate_user_query_fields (priv->user_query_fields, cue_str, encoded_cue_str);
+
+	query_str = g_strdup_printf (
+		"(or "
+		" (beginswith \"nickname\"  %s) "
+		" (beginswith \"email\"     %s) "
+		" %s "
+		" %s "
+		" %s "
+		")",
+		encoded_cue_str, encoded_cue_str,
+		full_name_query_str, file_as_query_str,
+		user_fields_str ? user_fields_str : "");
+
+	g_free (user_fields_str);
+	g_free (file_as_query_str);
+	g_free (full_name_query_str);
+	g_free (encoded_cue_str);
+
+	ENS_DEBUG (g_print ("%s\n", query_str));
+
+	book_query = e_book_query_from_string (query_str);
+	e_contact_store_set_query (name_selector_entry->priv->contact_store, book_query);
+	e_book_query_unref (book_query);
+
+	g_free (query_str);
+}
+
+static gchar *
+get_entry_substring (ENameSelectorEntry *name_selector_entry,
+                     gint range_start,
+                     gint range_end)
+{
+	const gchar *entry_text;
+	gchar       *p0, *p1;
+
+	entry_text = gtk_entry_get_text (GTK_ENTRY (name_selector_entry));
+
+	p0 = g_utf8_offset_to_pointer (entry_text, range_start);
+	p1 = g_utf8_offset_to_pointer (entry_text, range_end);
+
+	return g_strndup (p0, p1 - p0);
+}
+
+static gint
+utf8_casefold_collate_len (const gchar *str1,
+                           const gchar *str2,
+                           gint len)
+{
+	gchar *s1 = g_utf8_casefold (str1, len);
+	gchar *s2 = g_utf8_casefold (str2, len);
+	gint rv;
+
+	rv = g_utf8_collate (s1, s2);
+
+	g_free (s1);
+	g_free (s2);
+
+	return rv;
+}
+
+static gchar *
+build_textrep_for_contact (EContact *contact,
+                           EContactField cue_field)
+{
+	gchar *name  = NULL;
+	gchar *email = NULL;
+	gchar *textrep;
+
+	switch (cue_field) {
+		case E_CONTACT_FULL_NAME:
+		case E_CONTACT_NICKNAME:
+		case E_CONTACT_FILE_AS:
+			name  = e_contact_get (contact, cue_field);
+			email = e_contact_get (contact, E_CONTACT_EMAIL_1);
+			break;
+
+		case E_CONTACT_EMAIL_1:
+		case E_CONTACT_EMAIL_2:
+		case E_CONTACT_EMAIL_3:
+		case E_CONTACT_EMAIL_4:
+			name = NULL;
+			email = e_contact_get (contact, cue_field);
+			break;
+
+		default:
+			g_assert_not_reached ();
+			break;
+	}
+
+	g_assert (email);
+	g_assert (strlen (email) > 0);
+
+	if (name)
+		textrep = g_strdup_printf ("%s <%s>", name, email);
+	else
+		textrep = g_strdup_printf ("%s", email);
+
+	g_free (name);
+	g_free (email);
+	return textrep;
+}
+
+static gboolean
+contact_match_cue (ENameSelectorEntry *name_selector_entry,
+                   EContact *contact,
+                   const gchar *cue_str,
+                   EContactField *matched_field,
+                   gint *matched_field_rank)
+{
+	EContactField  fields[] = { E_CONTACT_FULL_NAME, E_CONTACT_NICKNAME, E_CONTACT_FILE_AS,
+				     E_CONTACT_EMAIL_1, E_CONTACT_EMAIL_2, E_CONTACT_EMAIL_3,
+				     E_CONTACT_EMAIL_4 };
+	gchar         *email;
+	gboolean       result = FALSE;
+	gint           cue_len;
+	gint           i;
+
+	g_assert (contact);
+	g_assert (cue_str);
+
+	if (g_utf8_strlen (cue_str, -1) < name_selector_entry->priv->minimum_query_length)
+		return FALSE;
+
+	cue_len = strlen (cue_str);
+
+	/* Make sure contact has an email address */
+	email = e_contact_get (contact, E_CONTACT_EMAIL_1);
+	if (!email || !*email) {
+		g_free (email);
+		return FALSE;
+	}
+	g_free (email);
+
+	for (i = 0; i < G_N_ELEMENTS (fields); i++) {
+		gchar *value;
+		gchar *value_sane;
+
+		/* Don't match e-mail addresses in contact lists */
+		if (e_contact_get (contact, E_CONTACT_IS_LIST) &&
+		    fields[i] >= E_CONTACT_FIRST_EMAIL_ID &&
+		    fields[i] <= E_CONTACT_LAST_EMAIL_ID)
+			continue;
+
+		value = e_contact_get (contact, fields[i]);
+		if (!value)
+			continue;
+
+		value_sane = sanitize_string (value);
+		g_free (value);
+
+		ENS_DEBUG (g_print ("Comparing '%s' to '%s'\n", value, cue_str));
+
+		if (!utf8_casefold_collate_len (value_sane, cue_str, cue_len)) {
+			if (matched_field)
+				*matched_field = fields [i];
+			if (matched_field_rank)
+				*matched_field_rank = i;
+
+			result = TRUE;
+			g_free (value_sane);
+			break;
+		}
+		g_free (value_sane);
+	}
+
+	return result;
+}
+
+static gboolean
+find_existing_completion (ENameSelectorEntry *name_selector_entry,
+                          const gchar *cue_str,
+                          EContact **contact,
+                          gchar **text,
+                          EContactField *matched_field,
+                          EBookClient **book_client)
+{
+	GtkTreeIter    iter;
+	EContact      *best_contact    = NULL;
+	gint           best_field_rank = G_MAXINT;
+	EContactField  best_field = 0;
+	EBookClient   *best_book_client = NULL;
+
+	g_assert (cue_str);
+
+	if (!name_selector_entry->priv->contact_store)
+		return FALSE;
+
+	ENS_DEBUG (g_print ("Completing '%s'\n", cue_str));
+
+	if (!gtk_tree_model_get_iter_first (GTK_TREE_MODEL (name_selector_entry->priv->contact_store), &iter))
+		return FALSE;
+
+	do {
+		EContact      *current_contact;
+		gint           current_field_rank;
+		EContactField  current_field;
+		gboolean       matches;
+
+		current_contact = e_contact_store_get_contact (name_selector_entry->priv->contact_store, &iter);
+		if (!current_contact)
+			continue;
+
+		matches = contact_match_cue (name_selector_entry, current_contact, cue_str, &current_field, &current_field_rank);
+		if (matches && current_field_rank < best_field_rank) {
+			best_contact    = current_contact;
+			best_field_rank = current_field_rank;
+			best_field      = current_field;
+			best_book_client = e_contact_store_get_client (name_selector_entry->priv->contact_store, &iter);
+		}
+	} while (gtk_tree_model_iter_next (GTK_TREE_MODEL (name_selector_entry->priv->contact_store), &iter));
+
+	if (!best_contact)
+		return FALSE;
+
+	if (contact)
+		*contact = best_contact;
+	if (text)
+		*text = build_textrep_for_contact (best_contact, best_field);
+	if (matched_field)
+		*matched_field = best_field;
+	if (book_client)
+		*book_client = best_book_client;
+
+	return TRUE;
+}
+
+static void
+generate_attribute_list (ENameSelectorEntry *name_selector_entry)
+{
+	PangoLayout    *layout;
+	PangoAttrList  *attr_list;
+	const gchar    *text;
+	gint            i;
+
+	text = gtk_entry_get_text (GTK_ENTRY (name_selector_entry));
+	layout = gtk_entry_get_layout (GTK_ENTRY (name_selector_entry));
+
+	/* Set up the attribute list */
+
+	attr_list = pango_attr_list_new ();
+
+	if (name_selector_entry->priv->attr_list)
+		pango_attr_list_unref (name_selector_entry->priv->attr_list);
+
+	name_selector_entry->priv->attr_list = attr_list;
+
+	/* Parse the entry's text and apply attributes to real contacts */
+
+	for (i = 0; ; i++) {
+		EDestination   *destination;
+		PangoAttribute *attr;
+		gint            start_pos;
+		gint            end_pos;
+
+		if (!get_range_by_index (text, i, &start_pos, &end_pos))
+			break;
+
+		destination = find_destination_at_position (name_selector_entry, start_pos);
+
+		/* Destination will be NULL if we have no entries */
+		if (!destination || !e_destination_get_contact (destination))
+			continue;
+
+		attr = pango_attr_underline_new (PANGO_UNDERLINE_SINGLE);
+		attr->start_index = g_utf8_offset_to_pointer (text, start_pos) - text;
+		attr->end_index = g_utf8_offset_to_pointer (text, end_pos) - text;
+		pango_attr_list_insert (attr_list, attr);
+	}
+
+	pango_layout_set_attributes (layout, attr_list);
+}
+
+static gboolean
+draw_event (ENameSelectorEntry *name_selector_entry)
+{
+	PangoLayout *layout;
+
+	layout = gtk_entry_get_layout (GTK_ENTRY (name_selector_entry));
+	pango_layout_set_attributes (layout, name_selector_entry->priv->attr_list);
+
+	return FALSE;
+}
+
+static void
+type_ahead_complete (ENameSelectorEntry *name_selector_entry)
+{
+	EContact      *contact;
+	EBookClient   *book_client = NULL;
+	EContactField  matched_field;
+	EDestination  *destination;
+	gint           cursor_pos;
+	gint           range_start = 0;
+	gint           range_end   = 0;
+	gint           pos         = 0;
+	gchar         *textrep;
+	gint           textrep_len;
+	gint           range_len;
+	const gchar   *text;
+	gchar         *cue_str;
+	gchar         *temp_str;
+	ENameSelectorEntryPrivate *priv;
+
+	priv = E_NAME_SELECTOR_ENTRY_GET_PRIVATE (name_selector_entry);
+
+	cursor_pos = gtk_editable_get_position (GTK_EDITABLE (name_selector_entry));
+	if (cursor_pos < 0)
+		return;
+
+	text = gtk_entry_get_text (GTK_ENTRY (name_selector_entry));
+	get_range_at_position (text, cursor_pos, &range_start, &range_end);
+	range_len = range_end - range_start;
+	if (range_len < priv->minimum_query_length)
+		return;
+
+	destination = find_destination_at_position (name_selector_entry, cursor_pos);
+
+	cue_str = get_entry_substring (name_selector_entry, range_start, range_end);
+	if (!find_existing_completion (name_selector_entry, cue_str, &contact,
+				       &textrep, &matched_field, &book_client)) {
+		g_free (cue_str);
+		return;
+	}
+
+	temp_str = sanitize_string (textrep);
+	g_free (textrep);
+	textrep = temp_str;
+
+	textrep_len = g_utf8_strlen (textrep, -1);
+	pos         = range_start;
+
+	g_signal_handlers_block_by_func (
+		name_selector_entry,
+		user_insert_text, name_selector_entry);
+	g_signal_handlers_block_by_func (
+		name_selector_entry,
+		user_delete_text, name_selector_entry);
+	g_signal_handlers_block_by_func (
+		name_selector_entry->priv->destination_store,
+		destination_row_changed, name_selector_entry);
+
+	if (textrep_len > range_len) {
+		gint i;
+
+		/* keep character's case as user types */
+		for (i = 0; textrep[i] && cue_str[i]; i++)
+			textrep[i] = cue_str[i];
+
+		gtk_editable_delete_text (
+			GTK_EDITABLE (name_selector_entry),
+			range_start, range_end);
+		gtk_editable_insert_text (
+			GTK_EDITABLE (name_selector_entry),
+			textrep, -1, &pos);
+		gtk_editable_select_region (
+			GTK_EDITABLE (name_selector_entry),
+			range_end, range_start + textrep_len);
+		priv->is_completing = TRUE;
+	}
+	g_free (cue_str);
+
+	if (contact && destination) {
+		gint email_n = 0;
+
+		if (matched_field >= E_CONTACT_FIRST_EMAIL_ID && matched_field <= E_CONTACT_LAST_EMAIL_ID)
+			email_n = matched_field - E_CONTACT_FIRST_EMAIL_ID;
+
+		e_destination_set_contact (destination, contact, email_n);
+		if (book_client)
+			e_destination_set_client (destination, book_client);
+		generate_attribute_list (name_selector_entry);
+	}
+
+	g_signal_handlers_unblock_by_func (
+		name_selector_entry->priv->destination_store,
+		destination_row_changed, name_selector_entry);
+	g_signal_handlers_unblock_by_func (name_selector_entry, user_delete_text, name_selector_entry);
+	g_signal_handlers_unblock_by_func (name_selector_entry, user_insert_text, name_selector_entry);
+
+	g_free (textrep);
+}
+
+static void
+clear_completion_model (ENameSelectorEntry *name_selector_entry)
+{
+	ENameSelectorEntryPrivate *priv;
+
+	priv = E_NAME_SELECTOR_ENTRY_GET_PRIVATE (name_selector_entry);
+
+	if (!name_selector_entry->priv->contact_store)
+		return;
+
+	e_contact_store_set_query (name_selector_entry->priv->contact_store, NULL);
+	priv->is_completing = FALSE;
+}
+
+static void
+update_completion_model (ENameSelectorEntry *name_selector_entry)
+{
+	const gchar *text;
+	gint         cursor_pos;
+	gint         range_start = 0;
+	gint         range_end   = 0;
+
+	text       = gtk_entry_get_text (GTK_ENTRY (name_selector_entry));
+	cursor_pos = gtk_editable_get_position (GTK_EDITABLE (name_selector_entry));
+
+	if (cursor_pos >= 0)
+		get_range_at_position (text, cursor_pos, &range_start, &range_end);
+
+	if (range_end - range_start >= name_selector_entry->priv->minimum_query_length && cursor_pos == range_end) {
+		gchar *cue_str;
+
+		cue_str = get_entry_substring (name_selector_entry, range_start, range_end);
+		set_completion_query (name_selector_entry, cue_str);
+		g_free (cue_str);
+	} else {
+		/* N/A; Clear completion model */
+		clear_completion_model (name_selector_entry);
+	}
+}
+
+static gboolean
+type_ahead_complete_on_timeout_cb (ENameSelectorEntry *name_selector_entry)
+{
+	type_ahead_complete (name_selector_entry);
+	name_selector_entry->priv->type_ahead_complete_cb_id = 0;
+	return FALSE;
+}
+
+static gboolean
+update_completions_on_timeout_cb (ENameSelectorEntry *name_selector_entry)
+{
+	update_completion_model (name_selector_entry);
+	name_selector_entry->priv->update_completions_cb_id = 0;
+	return FALSE;
+}
+
+static void
+insert_destination_at_position (ENameSelectorEntry *name_selector_entry,
+                                gint pos)
+{
+	EDestination *destination;
+	const gchar  *text;
+	gint          index;
+
+	text = gtk_entry_get_text (GTK_ENTRY (name_selector_entry));
+	index = get_index_at_position (text, pos);
+
+	destination = build_destination_at_position (text, pos);
+	g_assert (destination);
+
+	g_signal_handlers_block_by_func (
+		name_selector_entry->priv->destination_store,
+		destination_row_inserted, name_selector_entry);
+	e_destination_store_insert_destination (
+		name_selector_entry->priv->destination_store,
+						index, destination);
+	g_signal_handlers_unblock_by_func (
+		name_selector_entry->priv->destination_store,
+		destination_row_inserted, name_selector_entry);
+	g_object_unref (destination);
+}
+
+static void
+modify_destination_at_position (ENameSelectorEntry *name_selector_entry,
+                                gint pos)
+{
+	EDestination *destination;
+	const gchar  *text;
+	gchar        *raw_address;
+	gboolean      rebuild_attributes = FALSE;
+
+	destination = find_destination_at_position (name_selector_entry, pos);
+	if (!destination)
+		return;
+
+	text = gtk_entry_get_text (GTK_ENTRY (name_selector_entry));
+	raw_address = get_address_at_position (text, pos);
+	g_assert (raw_address);
+
+	if (e_destination_get_contact (destination))
+		rebuild_attributes = TRUE;
+
+	g_signal_handlers_block_by_func (
+		name_selector_entry->priv->destination_store,
+		destination_row_changed, name_selector_entry);
+	e_destination_set_raw (destination, raw_address);
+	g_signal_handlers_unblock_by_func (
+		name_selector_entry->priv->destination_store,
+		destination_row_changed, name_selector_entry);
+
+	g_free (raw_address);
+
+	if (rebuild_attributes)
+		generate_attribute_list (name_selector_entry);
+}
+
+static gchar *
+get_destination_textrep (ENameSelectorEntry *name_selector_entry,
+                         EDestination *destination)
+{
+	gboolean show_email = e_name_selector_entry_get_show_address (name_selector_entry);
+	EContact *contact;
+
+	g_return_val_if_fail (destination != NULL, NULL);
+
+	contact = e_destination_get_contact (destination);
+
+	if (!show_email) {
+		if (contact && !e_contact_get (contact, E_CONTACT_IS_LIST)) {
+			GList *email_list;
+
+			email_list = e_contact_get (contact, E_CONTACT_EMAIL);
+			show_email = g_list_length (email_list) > 1;
+			deep_free_list (email_list);
+		}
+	}
+
+	/* do not show emails for contact lists even user forces it */
+	if (show_email && contact && e_contact_get (contact, E_CONTACT_IS_LIST))
+		show_email = FALSE;
+
+	return sanitize_string (e_destination_get_textrep (destination, show_email));
+}
+
+static void
+sync_destination_at_position (ENameSelectorEntry *name_selector_entry,
+                              gint range_pos,
+                              gint *cursor_pos)
+{
+	EDestination *destination;
+	const gchar  *text;
+	gchar        *address;
+	gint          address_len;
+	gint          range_start, range_end;
+
+	/* Get the destination we're looking at. Note that the entry may be empty, and so
+	 * there may not be one. */
+	destination = find_destination_at_position (name_selector_entry, range_pos);
+	if (!destination)
+		return;
+
+	text = gtk_entry_get_text (GTK_ENTRY (name_selector_entry));
+	if (!get_range_at_position (text, range_pos, &range_start, &range_end)) {
+		g_warning ("ENameSelectorEntry is out of sync with model!");
+		return;
+	}
+
+	address = get_destination_textrep (name_selector_entry, destination);
+	address_len = g_utf8_strlen (address, -1);
+
+	if (cursor_pos) {
+		/* Update cursor placement */
+		if (*cursor_pos >= range_end)
+			*cursor_pos += address_len - (range_end - range_start);
+		else if (*cursor_pos > range_start)
+			*cursor_pos = range_start + address_len;
+	}
+
+	g_signal_handlers_block_by_func (name_selector_entry, user_insert_text, name_selector_entry);
+	g_signal_handlers_block_by_func (name_selector_entry, user_delete_text, name_selector_entry);
+
+	gtk_editable_delete_text (GTK_EDITABLE (name_selector_entry), range_start, range_end);
+	gtk_editable_insert_text (GTK_EDITABLE (name_selector_entry), address, -1, &range_start);
+
+	g_signal_handlers_unblock_by_func (name_selector_entry, user_delete_text, name_selector_entry);
+	g_signal_handlers_unblock_by_func (name_selector_entry, user_insert_text, name_selector_entry);
+
+	generate_attribute_list (name_selector_entry);
+	g_free (address);
+}
+
+static void
+remove_destination_by_index (ENameSelectorEntry *name_selector_entry,
+                             gint index)
+{
+	EDestination *destination;
+
+	destination = find_destination_by_index (name_selector_entry, index);
+	if (destination) {
+		g_signal_handlers_block_by_func (
+			name_selector_entry->priv->destination_store,
+			destination_row_deleted, name_selector_entry);
+		e_destination_store_remove_destination (
+			name_selector_entry->priv->destination_store,
+						destination);
+		g_signal_handlers_unblock_by_func (
+			name_selector_entry->priv->destination_store,
+			destination_row_deleted, name_selector_entry);
+	}
+}
+
+static void
+post_insert_update (ENameSelectorEntry *name_selector_entry,
+                    gint position)
+{
+	const gchar *text;
+	glong length;
+
+	text = gtk_entry_get_text (GTK_ENTRY (name_selector_entry));
+	length = g_utf8_strlen (text, -1);
+	text = g_utf8_next_char (text);
+
+	if (*text == '\0') {
+		/* First and only character, create initial destination. */
+		insert_destination_at_position (name_selector_entry, 0);
+	} else {
+		/* Modified an existing destination. */
+		modify_destination_at_position (name_selector_entry, position);
+	}
+
+	/* If editing within the string, regenerate attributes. */
+	if (position < length)
+		generate_attribute_list (name_selector_entry);
+}
+
+/* Returns the number of characters inserted */
+static gint
+insert_unichar (ENameSelectorEntry *name_selector_entry,
+                gint *pos,
+                gunichar c)
+{
+	const gchar *text;
+	gunichar     str_context[4];
+	gchar        buf[7];
+	gint         len;
+
+	text = gtk_entry_get_text (GTK_ENTRY (name_selector_entry));
+	get_utf8_string_context (text, *pos, str_context, 4);
+
+	/* Space is not allowed:
+	 * - Before or after another space.
+	 * - At start of string. */
+
+	if (c == ' ' && (str_context[1] == ' ' || str_context[1] == '\0' || str_context[2] == ' '))
+		return 0;
+
+	/* Comma is not allowed:
+	 * - After another comma.
+	 * - At start of string. */
+
+	if (c == ',' && !is_quoted_at (text, *pos)) {
+		gint         start_pos;
+		gint         end_pos;
+		gboolean     at_start = FALSE;
+		gboolean     at_end   = FALSE;
+
+		if (str_context[1] == ',' || str_context[1] == '\0')
+			return 0;
+
+		/* We do this so we can avoid disturbing destinations with completed contacts
+		 * either before or after the destination being inserted. */
+		get_range_at_position (text, *pos, &start_pos, &end_pos);
+		if (*pos <= start_pos)
+			at_start = TRUE;
+		if (*pos >= end_pos)
+			at_end = TRUE;
+
+		/* Must insert comma first, so modify_destination_at_position can do its job
+		 * correctly, splitting up the contact if necessary. */
+		gtk_editable_insert_text (GTK_EDITABLE (name_selector_entry), ", ", -1, pos);
+
+		/* Update model */
+		g_assert (*pos >= 2);
+
+		/* If we inserted the comma at the end of, or in the middle of, an existing
+		 * address, add a new destination for what appears after comma. Else, we
+		 * have to add a destination for what appears before comma (a blank one). */
+		if (at_end) {
+			/* End: Add last, sync first */
+			insert_destination_at_position (name_selector_entry, *pos);
+			sync_destination_at_position (name_selector_entry, *pos - 2, pos);
+			/* Sync generates the attributes list */
+		} else if (at_start) {
+			/* Start: Add first */
+			insert_destination_at_position (name_selector_entry, *pos - 2);
+			generate_attribute_list (name_selector_entry);
+		} else {
+			/* Middle: */
+			insert_destination_at_position (name_selector_entry, *pos);
+			modify_destination_at_position (name_selector_entry, *pos - 2);
+			generate_attribute_list (name_selector_entry);
+		}
+
+		return 2;
+	}
+
+	/* Generic case. Allowed spaces also end up here. */
+
+	len = g_unichar_to_utf8 (c, buf);
+	buf[len] = '\0';
+
+	gtk_editable_insert_text (GTK_EDITABLE (name_selector_entry), buf, -1, pos);
+
+	post_insert_update (name_selector_entry, *pos);
+
+	return 1;
+}
+
+static void
+user_insert_text (ENameSelectorEntry *name_selector_entry,
+                  gchar *new_text,
+                  gint new_text_length,
+                  gint *position,
+                  gpointer user_data)
+{
+	gint chars_inserted = 0;
+	gboolean fast_insert;
+
+	g_signal_handlers_block_by_func (name_selector_entry, user_insert_text, name_selector_entry);
+	g_signal_handlers_block_by_func (name_selector_entry, user_delete_text, name_selector_entry);
+
+	fast_insert =
+		(g_utf8_strchr (new_text, new_text_length, ' ') == NULL) &&
+		(g_utf8_strchr (new_text, new_text_length, ',') == NULL);
+
+	/* If the text to insert does not contain spaces or commas,
+	 * insert all of it at once.  This avoids confusing on-going
+	 * input method behavior. */
+	if (fast_insert) {
+		gint old_position = *position;
+
+		gtk_editable_insert_text (
+			GTK_EDITABLE (name_selector_entry),
+			new_text, new_text_length, position);
+
+		chars_inserted = *position - old_position;
+		if (chars_inserted > 0)
+			post_insert_update (name_selector_entry, *position);
+
+	/* Otherwise, apply some rules as to where spaces and commas
+	 * can be inserted, and insert a trailing space after comma. */
+	} else {
+		const gchar *cp;
+
+		for (cp = new_text; *cp; cp = g_utf8_next_char (cp)) {
+			gunichar uc = g_utf8_get_char (cp);
+			insert_unichar (name_selector_entry, position, uc);
+			chars_inserted++;
+		}
+	}
+
+	if (chars_inserted >= 1) {
+		/* If the user inserted one character, kick off completion */
+		re_set_timeout (name_selector_entry->priv->update_completions_cb_id,  update_completions_on_timeout_cb,  name_selector_entry);
+		re_set_timeout (name_selector_entry->priv->type_ahead_complete_cb_id, type_ahead_complete_on_timeout_cb, name_selector_entry);
+	}
+
+	g_signal_handlers_unblock_by_func (name_selector_entry, user_delete_text, name_selector_entry);
+	g_signal_handlers_unblock_by_func (name_selector_entry, user_insert_text, name_selector_entry);
+
+	g_signal_stop_emission_by_name (name_selector_entry, "insert_text");
+}
+
+static void
+user_delete_text (ENameSelectorEntry *name_selector_entry,
+                  gint start_pos,
+                  gint end_pos,
+                  gpointer user_data)
+{
+	const gchar *text;
+	gint         index_start, index_end;
+	gint	     selection_start, selection_end;
+	gunichar     str_context[2], str_b_context[2];
+	gint         len;
+	gint         i;
+	gboolean     del_space = FALSE, del_comma = FALSE;
+
+	if (start_pos == end_pos)
+		return;
+
+	text = gtk_entry_get_text (GTK_ENTRY (name_selector_entry));
+	len = g_utf8_strlen (text, -1);
+
+	if (end_pos == -1)
+		end_pos = len;
+
+	gtk_editable_get_selection_bounds (
+		GTK_EDITABLE (name_selector_entry),
+		&selection_start, &selection_end);
+
+	get_utf8_string_context (text, start_pos, str_context, 2);
+	get_utf8_string_context (text, end_pos, str_b_context, 2);
+
+	g_signal_handlers_block_by_func (name_selector_entry, user_delete_text, name_selector_entry);
+
+	if (end_pos - start_pos == 1) {
+		/* Might be backspace; update completion model so dropdown is accurate */
+		re_set_timeout (name_selector_entry->priv->update_completions_cb_id, update_completions_on_timeout_cb, name_selector_entry);
+	}
+
+	index_start = get_index_at_position (text, start_pos);
+	index_end   = get_index_at_position (text, end_pos);
+
+	g_signal_stop_emission_by_name (name_selector_entry, "delete_text");
+
+	/* If the deletion touches more than one destination, the first one is changed
+	 * and the rest are removed. If the last destination wasn't completely deleted,
+	 * it becomes part of the first one, since the separator between them was
+	 * removed.
+	 *
+	 * Here, we let the model know about removals. */
+	for (i = index_end; i > index_start; i--) {
+		EDestination *destination = find_destination_by_index (name_selector_entry, i);
+		gint range_start, range_end;
+		gchar *ttext;
+		const gchar *email = NULL;
+		gboolean sel = FALSE;
+
+		if (destination)
+			email = e_destination_get_textrep (destination, TRUE);
+
+		if (!email || !*email)
+			continue;
+
+		if (!get_range_by_index (text, i, &range_start, &range_end)) {
+			g_warning ("ENameSelectorEntry is out of sync with model!");
+			return;
+		}
+
+		if ((selection_start < range_start && selection_end > range_start) ||
+		    (selection_end > range_start && selection_end < range_end))
+			sel = TRUE;
+
+		if (!sel) {
+			g_signal_handlers_block_by_func (name_selector_entry, user_insert_text, name_selector_entry);
+			g_signal_handlers_block_by_func (name_selector_entry, user_delete_text, name_selector_entry);
+
+			gtk_editable_delete_text (GTK_EDITABLE (name_selector_entry), range_start, range_end);
+
+			ttext = sanitize_string (email);
+			gtk_editable_insert_text (GTK_EDITABLE (name_selector_entry), ttext, -1, &range_start);
+			g_free (ttext);
+
+			g_signal_handlers_unblock_by_func (name_selector_entry, user_delete_text, name_selector_entry);
+			g_signal_handlers_unblock_by_func (name_selector_entry, user_insert_text, name_selector_entry);
+
+		}
+
+		remove_destination_by_index (name_selector_entry, i);
+	}
+
+	/* Do the actual deletion */
+
+	if (end_pos == start_pos +1 &&  index_end == index_start) {
+		/* We could be just deleting the empty text */
+		gchar *c;
+
+		/* Get the actual deleted text */
+		c = gtk_editable_get_chars (GTK_EDITABLE (name_selector_entry), start_pos, start_pos + 1);
+
+		if ( c[0] == ' ') {
+			/* If we are at the beginning or removing junk space, let us ignore it */
+			del_space = TRUE;
+		}
+		g_free (c);
+	} else	if (end_pos == start_pos +1 &&  index_end == index_start + 1) {
+		/* We could be just deleting the empty text */
+		gchar *c;
+
+		/* Get the actual deleted text */
+		c = gtk_editable_get_chars (GTK_EDITABLE (name_selector_entry), start_pos, start_pos + 1);
+
+		if ( c[0] == ',' && !is_quoted_at (text, start_pos)) {
+			/* If we are at the beginning or removing junk space, let us ignore it */
+			del_comma = TRUE;
+		}
+		g_free (c);
+	}
+
+	if (del_comma) {
+		gint range_start=-1, range_end;
+		EDestination *dest = find_destination_by_index (name_selector_entry, index_end);
+		/* If we have deleted the last comma, let us autocomplete normally
+		 */
+
+		if (dest && len - end_pos  != 0) {
+
+			EDestination *destination1  = find_destination_by_index (name_selector_entry, index_start);
+			gchar *ttext;
+			const gchar *email = NULL;
+
+			if (destination1)
+				email = e_destination_get_textrep (destination1, TRUE);
+
+			if (email && *email) {
+
+				if (!get_range_by_index (text, i, &range_start, &range_end)) {
+					g_warning ("ENameSelectorEntry is out of sync with model!");
+					return;
+				}
+
+				g_signal_handlers_block_by_func (name_selector_entry, user_insert_text, name_selector_entry);
+				g_signal_handlers_block_by_func (name_selector_entry, user_delete_text, name_selector_entry);
+
+				gtk_editable_delete_text (GTK_EDITABLE (name_selector_entry), range_start, range_end);
+
+				ttext = sanitize_string (email);
+				gtk_editable_insert_text (GTK_EDITABLE (name_selector_entry), ttext, -1, &range_start);
+				g_free (ttext);
+
+				g_signal_handlers_unblock_by_func (name_selector_entry, user_delete_text, name_selector_entry);
+				g_signal_handlers_unblock_by_func (name_selector_entry, user_insert_text, name_selector_entry);
+			}
+
+			if (range_start != -1) {
+				start_pos = range_start;
+				end_pos = start_pos + 1;
+				gtk_editable_set_position (GTK_EDITABLE (name_selector_entry),start_pos);
+			}
+		}
+	}
+	gtk_editable_delete_text (
+		GTK_EDITABLE (name_selector_entry),
+		start_pos, end_pos);
+
+	/*If the user is deleting a '"' new destinations have to be created for ',' between the quoted text
+	 Like "fd,ty,uy" is a one entity, but if you remove the quotes it has to be broken doan into 3 seperate
+	 addresses.
+	*/
+
+	if (str_b_context[1] == '"') {
+		const gchar *p;
+		gint j;
+		p = text + end_pos;
+		for (p = text + (end_pos - 1), j = end_pos - 1; *p && *p != '"' ; p = g_utf8_next_char (p), j++) {
+			gunichar c = g_utf8_get_char (p);
+			if (c == ',') {
+				insert_destination_at_position (name_selector_entry, j + 1);
+			}
+		}
+
+	}
+
+	/* Let model know about changes */
+	text = gtk_entry_get_text (GTK_ENTRY (name_selector_entry));
+	if (!*text || strlen (text) <= 0) {
+		/* If the entry was completely cleared, remove the initial destination too */
+		remove_destination_by_index (name_selector_entry, 0);
+		generate_attribute_list (name_selector_entry);
+	} else  if (!del_space) {
+		modify_destination_at_position (name_selector_entry, start_pos);
+	}
+
+	/* If editing within the string, we need to regenerate attributes */
+	if (end_pos < len)
+		generate_attribute_list (name_selector_entry);
+
+	/* Prevent type-ahead completion */
+	if (name_selector_entry->priv->type_ahead_complete_cb_id) {
+		g_source_remove (name_selector_entry->priv->type_ahead_complete_cb_id);
+		name_selector_entry->priv->type_ahead_complete_cb_id = 0;
+	}
+
+	g_signal_handlers_unblock_by_func (name_selector_entry, user_delete_text, name_selector_entry);
+}
+
+static gboolean
+completion_match_selected (ENameSelectorEntry *name_selector_entry,
+                           ETreeModelGenerator *email_generator_model,
+                           GtkTreeIter *generator_iter)
+{
+	EContact      *contact;
+	EBookClient   *book_client;
+	EDestination  *destination;
+	gint           cursor_pos;
+	GtkTreeIter    contact_iter;
+	gint           email_n;
+
+	if (!name_selector_entry->priv->contact_store)
+		return FALSE;
+
+	g_return_val_if_fail (name_selector_entry->priv->email_generator == email_generator_model, FALSE);
+
+	e_tree_model_generator_convert_iter_to_child_iter (
+		email_generator_model,
+		&contact_iter, &email_n,
+		generator_iter);
+
+	contact = e_contact_store_get_contact (name_selector_entry->priv->contact_store, &contact_iter);
+	book_client = e_contact_store_get_client (name_selector_entry->priv->contact_store, &contact_iter);
+	cursor_pos = gtk_editable_get_position (GTK_EDITABLE (name_selector_entry));
+
+	/* Set the contact in the model's destination */
+
+	destination = find_destination_at_position (name_selector_entry, cursor_pos);
+	e_destination_set_contact (destination, contact, email_n);
+	if (book_client)
+		e_destination_set_client (destination, book_client);
+	sync_destination_at_position (name_selector_entry, cursor_pos, &cursor_pos);
+
+	g_signal_handlers_block_by_func (name_selector_entry, user_insert_text, name_selector_entry);
+	gtk_editable_insert_text (GTK_EDITABLE (name_selector_entry), ", ", -1, &cursor_pos);
+	g_signal_handlers_unblock_by_func (name_selector_entry, user_insert_text, name_selector_entry);
+
+	/*Add destination at end for next entry*/
+	insert_destination_at_position (name_selector_entry, cursor_pos);
+	/* Place cursor at end of address */
+
+	gtk_editable_set_position (GTK_EDITABLE (name_selector_entry), cursor_pos);
+	g_signal_emit (name_selector_entry, signals[UPDATED], 0, destination, NULL);
+	return TRUE;
+}
+
+static void
+entry_activate (ENameSelectorEntry *name_selector_entry)
+{
+	gint         cursor_pos;
+	gint         range_start, range_end;
+	ENameSelectorEntryPrivate *priv;
+	EDestination  *destination;
+	gint           range_len;
+	const gchar   *text;
+	gchar         *cue_str;
+
+	cursor_pos = gtk_editable_get_position (GTK_EDITABLE (name_selector_entry));
+	if (cursor_pos < 0)
+		return;
+
+	priv = E_NAME_SELECTOR_ENTRY_GET_PRIVATE (name_selector_entry);
+
+	text = gtk_entry_get_text (GTK_ENTRY (name_selector_entry));
+	if (!get_range_at_position (text, cursor_pos, &range_start, &range_end))
+		return;
+
+	range_len = range_end - range_start;
+	if (range_len < priv->minimum_query_length)
+		return;
+
+	destination = find_destination_at_position (name_selector_entry, cursor_pos);
+	if (!destination)
+		return;
+
+	cue_str = get_entry_substring (name_selector_entry, range_start, range_end);
+#if 0
+	if (!find_existing_completion (name_selector_entry, cue_str, &contact,
+				       &textrep, &matched_field)) {
+		g_free (cue_str);
+		return;
+	}
+#endif
+	g_free (cue_str);
+	sync_destination_at_position (name_selector_entry, cursor_pos, &cursor_pos);
+
+	/* Place cursor at end of address */
+	text = gtk_entry_get_text (GTK_ENTRY (name_selector_entry));
+	get_range_at_position (text, cursor_pos, &range_start, &range_end);
+
+	if (priv->is_completing) {
+		gchar *str_context = NULL;
+
+		str_context = gtk_editable_get_chars (GTK_EDITABLE (name_selector_entry), range_end, range_end + 1);
+
+		if (str_context[0] != ',') {
+			/* At the end*/
+			gtk_editable_insert_text (GTK_EDITABLE (name_selector_entry), ", ", -1, &range_end);
+		} else {
+			/* In the middle */
+			gint newpos = strlen (text);
+
+                        /* Doing this we can make sure that It wont ask for completion again. */
+			gtk_editable_insert_text (GTK_EDITABLE (name_selector_entry), ", ", -1, &newpos);
+			g_signal_handlers_block_by_func (name_selector_entry, user_delete_text, name_selector_entry);
+			gtk_editable_delete_text (GTK_EDITABLE (name_selector_entry), newpos - 2, newpos);
+			g_signal_handlers_unblock_by_func (name_selector_entry, user_delete_text, name_selector_entry);
+
+			/* Move it close to next destination*/
+			range_end = range_end + 2;
+
+		}
+		g_free (str_context);
+	}
+
+	gtk_editable_set_position (GTK_EDITABLE (name_selector_entry), range_end);
+	g_signal_emit (name_selector_entry, signals[UPDATED], 0, destination, NULL);
+
+	if (priv->is_completing)
+		clear_completion_model (name_selector_entry);
+}
+
+static void
+update_text (ENameSelectorEntry *name_selector_entry,
+             const gchar *text)
+{
+	gint start = 0, end = 0;
+	gboolean has_selection;
+
+	has_selection = gtk_editable_get_selection_bounds (GTK_EDITABLE (name_selector_entry), &start, &end);
+
+	gtk_entry_set_text (GTK_ENTRY (name_selector_entry), text);
+
+	if (has_selection)
+		gtk_editable_select_region (GTK_EDITABLE (name_selector_entry), start, end);
+}
+
+static void
+sanitize_entry (ENameSelectorEntry *name_selector_entry)
+{
+	gint n;
+	GList *l, *known, *del = NULL;
+	GString *str = g_string_new ("");
+
+	g_signal_handlers_block_matched (name_selector_entry, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, name_selector_entry);
+	g_signal_handlers_block_matched (name_selector_entry->priv->destination_store, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, name_selector_entry);
+
+	known = e_destination_store_list_destinations (name_selector_entry->priv->destination_store);
+	for (l = known, n = 0; l != NULL; l = l->next, n++) {
+		EDestination *dest = l->data;
+
+		if (!dest || !e_destination_get_address (dest))
+			del = g_list_prepend (del, GINT_TO_POINTER (n));
+		else {
+			gchar *text;
+
+			text = get_destination_textrep (name_selector_entry, dest);
+			if (text) {
+				if (str->str && str->str[0])
+					g_string_append (str, ", ");
+
+				g_string_append (str, text);
+			}
+			g_free (text);
+		}
+	}
+	g_list_free (known);
+
+	for (l = del; l != NULL; l = l->next) {
+		e_destination_store_remove_destination_nth (name_selector_entry->priv->destination_store, GPOINTER_TO_INT (l->data));
+	}
+	g_list_free (del);
+
+	update_text (name_selector_entry, str->str);
+
+	g_string_free (str, TRUE);
+
+	g_signal_handlers_unblock_matched (name_selector_entry->priv->destination_store, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, name_selector_entry);
+	g_signal_handlers_unblock_matched (name_selector_entry, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, name_selector_entry);
+
+	generate_attribute_list (name_selector_entry);
+}
+
+static gboolean
+user_focus_in (ENameSelectorEntry *name_selector_entry,
+               GdkEventFocus *event_focus)
+{
+	gint n;
+	GList *l, *known;
+	GString *str = g_string_new ("");
+	EDestination *dest_dummy = e_destination_new ();
+
+	g_signal_handlers_block_matched (name_selector_entry, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, name_selector_entry);
+	g_signal_handlers_block_matched (name_selector_entry->priv->destination_store, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, name_selector_entry);
+
+	known = e_destination_store_list_destinations (name_selector_entry->priv->destination_store);
+	for (l = known, n = 0; l != NULL; l = l->next, n++) {
+		EDestination *dest = l->data;
+
+		if (dest) {
+			gchar *text;
+
+			text = get_destination_textrep (name_selector_entry, dest);
+			if (text) {
+				if (str->str && str->str[0])
+					g_string_append (str, ", ");
+
+				g_string_append (str, text);
+			}
+			g_free (text);
+		}
+	}
+	g_list_free (known);
+
+	/* Add a blank destination */
+	e_destination_store_append_destination (name_selector_entry->priv->destination_store, dest_dummy);
+	if (str->str && str->str[0])
+		g_string_append (str, ", ");
+
+	gtk_entry_set_text (GTK_ENTRY (name_selector_entry), str->str);
+
+	g_string_free (str, TRUE);
+
+	g_signal_handlers_unblock_matched (name_selector_entry->priv->destination_store, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, name_selector_entry);
+	g_signal_handlers_unblock_matched (name_selector_entry, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, name_selector_entry);
+
+	generate_attribute_list (name_selector_entry);
+
+	return FALSE;
+}
+
+static gboolean
+user_focus_out (ENameSelectorEntry *name_selector_entry,
+                GdkEventFocus *event_focus)
+{
+	if (!event_focus->in) {
+		entry_activate (name_selector_entry);
+	}
+
+	if (name_selector_entry->priv->type_ahead_complete_cb_id) {
+		g_source_remove (name_selector_entry->priv->type_ahead_complete_cb_id);
+		name_selector_entry->priv->type_ahead_complete_cb_id = 0;
+	}
+
+	if (name_selector_entry->priv->update_completions_cb_id) {
+		g_source_remove (name_selector_entry->priv->update_completions_cb_id);
+		name_selector_entry->priv->update_completions_cb_id = 0;
+	}
+
+	clear_completion_model (name_selector_entry);
+
+	if (!event_focus->in) {
+		sanitize_entry (name_selector_entry);
+	}
+
+	return FALSE;
+}
+
+static void
+deep_free_list (GList *list)
+{
+	GList *l;
+
+	for (l = list; l; l = g_list_next (l))
+		g_free (l->data);
+
+	g_list_free (list);
+}
+
+/* Given a widget, determines the height that text will normally be drawn. */
+static guint
+entry_height (GtkWidget *widget)
+{
+	PangoLayout *layout;
+	gint bound;
+
+	g_return_val_if_fail (widget != NULL, 0);
+
+	layout = gtk_widget_create_pango_layout (widget, NULL);
+
+	pango_layout_get_pixel_size (layout, NULL, &bound);
+
+	return bound;
+}
+
+static void
+contact_layout_pixbuffer (GtkCellLayout *cell_layout,
+                          GtkCellRenderer *cell,
+                          GtkTreeModel *model,
+                          GtkTreeIter *iter,
+                          ENameSelectorEntry *name_selector_entry)
+{
+	EContact      *contact;
+	GtkTreeIter    generator_iter;
+	GtkTreeIter    contact_store_iter;
+	gint           email_n;
+	EContactPhoto *photo;
+	GdkPixbuf *pixbuf = NULL;
+
+	if (!name_selector_entry->priv->contact_store)
+		return;
+
+	gtk_tree_model_filter_convert_iter_to_child_iter (
+		GTK_TREE_MODEL_FILTER (model),
+		&generator_iter, iter);
+	e_tree_model_generator_convert_iter_to_child_iter (
+		name_selector_entry->priv->email_generator,
+		&contact_store_iter, &email_n,
+		&generator_iter);
+
+	contact = e_contact_store_get_contact (name_selector_entry->priv->contact_store, &contact_store_iter);
+	if (!contact) {
+		g_object_set (cell, "pixbuf", pixbuf, NULL);
+		return;
+	}
+
+	photo =  e_contact_get (contact, E_CONTACT_PHOTO);
+	if (photo && photo->type == E_CONTACT_PHOTO_TYPE_INLINED) {
+		guint max_height = entry_height (GTK_WIDGET (name_selector_entry));
+		GdkPixbufLoader *loader;
+
+		loader = gdk_pixbuf_loader_new ();
+		if (gdk_pixbuf_loader_write (loader, (guchar *) photo->data.inlined.data, photo->data.inlined.length, NULL) &&
+		    gdk_pixbuf_loader_close (loader, NULL)) {
+			pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
+			if (pixbuf)
+				g_object_ref (pixbuf);
+		}
+		g_object_unref (loader);
+
+		if (pixbuf) {
+			gint w, h;
+			gdouble scale = 1.0;
+
+			w = gdk_pixbuf_get_width (pixbuf);
+			h = gdk_pixbuf_get_height (pixbuf);
+
+			if (h > w)
+				scale = max_height / (double) h;
+			else
+				scale = max_height / (double) w;
+
+			if (scale < 1.0) {
+				GdkPixbuf *tmp;
+
+				tmp = gdk_pixbuf_scale_simple (pixbuf, w * scale, h * scale, GDK_INTERP_BILINEAR);
+				g_object_unref (pixbuf);
+				pixbuf = tmp;
+			}
+
+		}
+	}
+
+	e_contact_photo_free (photo);
+
+	g_object_set (cell, "pixbuf", pixbuf, NULL);
+
+	if (pixbuf)
+		g_object_unref (pixbuf);
+}
+
+static void
+contact_layout_formatter (GtkCellLayout *cell_layout,
+                          GtkCellRenderer *cell,
+                          GtkTreeModel *model,
+                          GtkTreeIter *iter,
+                          ENameSelectorEntry *name_selector_entry)
+{
+	EContact      *contact;
+	GtkTreeIter    generator_iter;
+	GtkTreeIter    contact_store_iter;
+	GList         *email_list;
+	gchar         *string;
+	gchar         *file_as_str;
+	gchar         *email_str;
+	gint           email_n;
+
+	if (!name_selector_entry->priv->contact_store)
+		return;
+
+	gtk_tree_model_filter_convert_iter_to_child_iter (
+		GTK_TREE_MODEL_FILTER (model),
+		&generator_iter, iter);
+	e_tree_model_generator_convert_iter_to_child_iter (
+		name_selector_entry->priv->email_generator,
+		&contact_store_iter, &email_n,
+		&generator_iter);
+
+	contact = e_contact_store_get_contact (name_selector_entry->priv->contact_store, &contact_store_iter);
+	email_list = e_contact_get (contact, E_CONTACT_EMAIL);
+	email_str = g_list_nth_data (email_list, email_n);
+	file_as_str = e_contact_get (contact, E_CONTACT_FILE_AS);
+
+	if (e_contact_get (contact, E_CONTACT_IS_LIST)) {
+		string = g_strdup_printf ("%s", file_as_str ? file_as_str : "?");
+	} else {
+		string = g_strdup_printf (
+			"%s%s<%s>", file_as_str ? file_as_str : "",
+			file_as_str ? " " : "",
+			email_str ? email_str : "");
+	}
+
+	g_free (file_as_str);
+	deep_free_list (email_list);
+
+	g_object_set (cell, "text", string, NULL);
+	g_free (string);
+}
+
+static gint
+generate_contact_rows (EContactStore *contact_store,
+                       GtkTreeIter *iter,
+                       ENameSelectorEntry *name_selector_entry)
+{
+	EContact    *contact;
+	const gchar *contact_uid;
+	GList       *email_list;
+	gint         n_rows;
+
+	contact = e_contact_store_get_contact (contact_store, iter);
+	g_assert (contact != NULL);
+
+	contact_uid = e_contact_get_const (contact, E_CONTACT_UID);
+	if (!contact_uid)
+		return 0;  /* Can happen with broken databases */
+
+	if (e_contact_get (contact, E_CONTACT_IS_LIST))
+		return 1;
+
+	email_list = e_contact_get (contact, E_CONTACT_EMAIL);
+	n_rows = g_list_length (email_list);
+	deep_free_list (email_list);
+
+	return n_rows;
+}
+
+static void
+ensure_type_ahead_complete_on_timeout (ENameSelectorEntry *name_selector_entry)
+{
+	re_set_timeout (
+		name_selector_entry->priv->type_ahead_complete_cb_id,
+		type_ahead_complete_on_timeout_cb, name_selector_entry);
+}
+
+static void
+setup_contact_store (ENameSelectorEntry *name_selector_entry)
+{
+	if (name_selector_entry->priv->email_generator) {
+		g_object_unref (name_selector_entry->priv->email_generator);
+		name_selector_entry->priv->email_generator = NULL;
+	}
+
+	if (name_selector_entry->priv->contact_store) {
+		name_selector_entry->priv->email_generator =
+			e_tree_model_generator_new (
+				GTK_TREE_MODEL (
+				name_selector_entry->priv->contact_store));
+
+		e_tree_model_generator_set_generate_func (
+			name_selector_entry->priv->email_generator,
+			(ETreeModelGeneratorGenerateFunc) generate_contact_rows,
+			name_selector_entry, NULL);
+
+		/* Assign the store to the entry completion */
+
+		gtk_entry_completion_set_model (
+			name_selector_entry->priv->entry_completion,
+			GTK_TREE_MODEL (
+			name_selector_entry->priv->email_generator));
+
+		/* Set up callback for incoming matches */
+		g_signal_connect_swapped (
+			name_selector_entry->priv->contact_store, "row-inserted",
+			G_CALLBACK (ensure_type_ahead_complete_on_timeout), name_selector_entry);
+	} else {
+		/* Remove the store from the entry completion */
+
+		gtk_entry_completion_set_model (name_selector_entry->priv->entry_completion, NULL);
+	}
+}
+
+static void
+book_loaded_cb (GObject *source_object,
+                GAsyncResult *result,
+                gpointer user_data)
+{
+	EContactStore *contact_store = user_data;
+	ESource *source = E_SOURCE (source_object);
+	EBookClient *book_client;
+	EClient *client = NULL;
+	GError *error = NULL;
+
+	e_client_utils_open_new_finish (source, result, &client, &error);
+
+	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+		g_warn_if_fail (client == NULL);
+		g_error_free (error);
+		goto exit;
+	}
+
+	if (error != NULL) {
+		g_warning ("%s", error->message);
+		g_warn_if_fail (client == NULL);
+		g_error_free (error);
+		goto exit;
+	}
+
+	book_client = E_BOOK_CLIENT (client);
+
+	g_return_if_fail (E_IS_BOOK_CLIENT (book_client));
+	e_contact_store_add_client (contact_store, book_client);
+	g_object_unref (book_client);
+
+ exit:
+	g_object_unref (contact_store);
+}
+
+static void
+setup_default_contact_store (ENameSelectorEntry *name_selector_entry)
+{
+	ESourceRegistry *registry;
+	EContactStore *contact_store;
+	GList *list, *iter;
+	const gchar *extension_name;
+
+	g_return_if_fail (name_selector_entry->priv->contact_store == NULL);
+
+	/* Create a book for each completion source, and assign them to the contact store */
+
+	contact_store = e_contact_store_new ();
+	name_selector_entry->priv->contact_store = contact_store;
+
+	extension_name = E_SOURCE_EXTENSION_ADDRESS_BOOK;
+	registry = e_name_selector_entry_get_registry (name_selector_entry);
+
+	/* An ESourceRegistry should have been set by now. */
+	g_return_if_fail (registry != NULL);
+
+	list = e_source_registry_list_sources (registry, extension_name);
+
+	for (iter = list; iter != NULL; iter = g_list_next (iter)) {
+		ESource *source = E_SOURCE (iter->data);
+		ESourceAutocomplete *extension;
+		GCancellable *cancellable;
+		const gchar *extension_name;
+
+		extension_name = E_SOURCE_EXTENSION_AUTOCOMPLETE;
+		extension = e_source_get_extension (source, extension_name);
+
+		/* Skip disabled address books. */
+		if (!e_source_registry_check_enabled (registry, source))
+			continue;
+
+		/* Skip non-completion address books. */
+		if (!e_source_autocomplete_get_include_me (extension))
+			continue;
+
+		cancellable = g_cancellable_new ();
+
+		g_queue_push_tail (
+			&name_selector_entry->priv->cancellables,
+			cancellable);
+
+		e_client_utils_open_new (
+			source, E_CLIENT_SOURCE_TYPE_CONTACTS, TRUE, cancellable,
+			book_loaded_cb, g_object_ref (contact_store));
+	}
+
+	g_list_free_full (list, (GDestroyNotify) g_object_unref);
+
+	setup_contact_store (name_selector_entry);
+}
+
+static void
+destination_row_changed (ENameSelectorEntry *name_selector_entry,
+                         GtkTreePath *path,
+                         GtkTreeIter *iter)
+{
+	EDestination *destination;
+	const gchar  *entry_text;
+	gchar        *text;
+	gint          range_start, range_end;
+	gint          n;
+
+	n = gtk_tree_path_get_indices (path)[0];
+	destination = e_destination_store_get_destination (name_selector_entry->priv->destination_store, iter);
+
+	if (!destination)
+		return;
+
+	g_assert (n >= 0);
+
+	entry_text = gtk_entry_get_text (GTK_ENTRY (name_selector_entry));
+	if (!get_range_by_index (entry_text, n, &range_start, &range_end)) {
+		g_warning ("ENameSelectorEntry is out of sync with model!");
+		return;
+	}
+
+	g_signal_handlers_block_by_func (name_selector_entry, user_insert_text, name_selector_entry);
+	g_signal_handlers_block_by_func (name_selector_entry, user_delete_text, name_selector_entry);
+
+	gtk_editable_delete_text (GTK_EDITABLE (name_selector_entry), range_start, range_end);
+
+	text = get_destination_textrep (name_selector_entry, destination);
+	gtk_editable_insert_text (GTK_EDITABLE (name_selector_entry), text, -1, &range_start);
+	g_free (text);
+
+	g_signal_handlers_unblock_by_func (name_selector_entry, user_delete_text, name_selector_entry);
+	g_signal_handlers_unblock_by_func (name_selector_entry, user_insert_text, name_selector_entry);
+
+	clear_completion_model (name_selector_entry);
+	generate_attribute_list (name_selector_entry);
+}
+
+static void
+destination_row_inserted (ENameSelectorEntry *name_selector_entry,
+                          GtkTreePath *path,
+                          GtkTreeIter *iter)
+{
+	EDestination *destination;
+	const gchar  *entry_text;
+	gchar        *text;
+	gboolean      comma_before = FALSE;
+	gboolean      comma_after  = FALSE;
+	gint          range_start, range_end;
+	gint          insert_pos;
+	gint          n;
+
+	n = gtk_tree_path_get_indices (path)[0];
+	destination = e_destination_store_get_destination (name_selector_entry->priv->destination_store, iter);
+
+	g_assert (n >= 0);
+	g_assert (destination != NULL);
+
+	entry_text = gtk_entry_get_text (GTK_ENTRY (name_selector_entry));
+
+	if (get_range_by_index (entry_text, n, &range_start, &range_end) && range_start != range_end) {
+		/* Another destination comes after us */
+		insert_pos = range_start;
+		comma_after = TRUE;
+	} else if (n > 0 && get_range_by_index (entry_text, n - 1, &range_start, &range_end)) {
+		/* Another destination comes before us */
+		insert_pos = range_end;
+		comma_before = TRUE;
+	} else if (n == 0) {
+		/* We're the sole destination */
+		insert_pos = 0;
+	} else {
+		g_warning ("ENameSelectorEntry is out of sync with model!");
+		return;
+	}
+
+	g_signal_handlers_block_by_func (name_selector_entry, user_insert_text, name_selector_entry);
+
+	if (comma_before)
+		gtk_editable_insert_text (GTK_EDITABLE (name_selector_entry), ", ", -1, &insert_pos);
+
+	text = get_destination_textrep (name_selector_entry, destination);
+	gtk_editable_insert_text (GTK_EDITABLE (name_selector_entry), text, -1, &insert_pos);
+	g_free (text);
+
+	if (comma_after)
+		gtk_editable_insert_text (GTK_EDITABLE (name_selector_entry), ", ", -1, &insert_pos);
+
+	g_signal_handlers_unblock_by_func (name_selector_entry, user_insert_text, name_selector_entry);
+
+	clear_completion_model (name_selector_entry);
+	generate_attribute_list (name_selector_entry);
+}
+
+static void
+destination_row_deleted (ENameSelectorEntry *name_selector_entry,
+                         GtkTreePath *path)
+{
+	const gchar *text;
+	gboolean     deleted_comma = FALSE;
+	gint         range_start, range_end;
+	gchar       *p0;
+	gint         n;
+
+	n = gtk_tree_path_get_indices (path)[0];
+	g_assert (n >= 0);
+
+	text = gtk_entry_get_text (GTK_ENTRY (name_selector_entry));
+
+	if (!get_range_by_index (text, n, &range_start, &range_end)) {
+		g_warning ("ENameSelectorEntry is out of sync with model!");
+		return;
+	}
+
+	/* Expand range for deletion forwards */
+	for (p0 = g_utf8_offset_to_pointer (text, range_end); *p0;
+	     p0 = g_utf8_next_char (p0), range_end++) {
+		gunichar c = g_utf8_get_char (p0);
+
+		/* Gobble spaces directly after comma */
+		if (c != ' ' && deleted_comma) {
+			range_end--;
+			break;
+		}
+
+		if (c == ',') {
+			deleted_comma = TRUE;
+			range_end++;
+		}
+	}
+
+	/* Expand range for deletion backwards */
+	for (p0 = g_utf8_offset_to_pointer (text, range_start); range_start > 0;
+	     p0 = g_utf8_prev_char (p0), range_start--) {
+		gunichar c = g_utf8_get_char (p0);
+
+		if (c == ',') {
+			if (!deleted_comma) {
+				deleted_comma = TRUE;
+				break;
+			}
+
+			range_start++;
+
+			/* Leave a space in front; we deleted the comma and spaces before the
+			 * following destination */
+			p0 = g_utf8_next_char (p0);
+			c = g_utf8_get_char (p0);
+			if (c == ' ')
+				range_start++;
+
+			break;
+		}
+	}
+
+	g_signal_handlers_block_by_func (name_selector_entry, user_delete_text, name_selector_entry);
+	gtk_editable_delete_text (GTK_EDITABLE (name_selector_entry), range_start, range_end);
+	g_signal_handlers_unblock_by_func (name_selector_entry, user_delete_text, name_selector_entry);
+
+	clear_completion_model (name_selector_entry);
+	generate_attribute_list (name_selector_entry);
+}
+
+static void
+setup_destination_store (ENameSelectorEntry *name_selector_entry)
+{
+	GtkTreeIter  iter;
+
+	g_signal_connect_swapped (
+		name_selector_entry->priv->destination_store, "row-changed",
+		G_CALLBACK (destination_row_changed), name_selector_entry);
+	g_signal_connect_swapped (
+		name_selector_entry->priv->destination_store, "row-deleted",
+		G_CALLBACK (destination_row_deleted), name_selector_entry);
+	g_signal_connect_swapped (
+		name_selector_entry->priv->destination_store, "row-inserted",
+		G_CALLBACK (destination_row_inserted), name_selector_entry);
+
+	if (!gtk_tree_model_get_iter_first (GTK_TREE_MODEL (name_selector_entry->priv->destination_store), &iter))
+		return;
+
+	do {
+		GtkTreePath *path;
+
+		path = gtk_tree_model_get_path (GTK_TREE_MODEL (name_selector_entry->priv->destination_store), &iter);
+		g_assert (path);
+
+		destination_row_inserted (name_selector_entry, path, &iter);
+	} while (gtk_tree_model_iter_next (GTK_TREE_MODEL (name_selector_entry->priv->destination_store), &iter));
+}
+
+static gboolean
+prepare_popup_destination (ENameSelectorEntry *name_selector_entry,
+                           GdkEventButton *event_button)
+{
+	EDestination *destination;
+	PangoLayout  *layout;
+	gint          layout_offset_x;
+	gint          layout_offset_y;
+	gint          x, y;
+	gint          index;
+
+	if (event_button->type != GDK_BUTTON_PRESS)
+		return FALSE;
+
+	if (event_button->button != 3)
+		return FALSE;
+
+	if (name_selector_entry->priv->popup_destination) {
+		g_object_unref (name_selector_entry->priv->popup_destination);
+		name_selector_entry->priv->popup_destination = NULL;
+	}
+
+	gtk_entry_get_layout_offsets (
+		GTK_ENTRY (name_selector_entry),
+		&layout_offset_x, &layout_offset_y);
+	x = (event_button->x + 0.5) - layout_offset_x;
+	y = (event_button->y + 0.5) - layout_offset_y;
+
+	if (x < 0 || y < 0)
+		return FALSE;
+
+	layout = gtk_entry_get_layout (GTK_ENTRY (name_selector_entry));
+	if (!pango_layout_xy_to_index (layout, x * PANGO_SCALE, y * PANGO_SCALE, &index, NULL))
+		return FALSE;
+
+	index = gtk_entry_layout_index_to_text_index (GTK_ENTRY (name_selector_entry), index);
+	destination = find_destination_at_position (name_selector_entry, index);
+	/* FIXME: Add this to a private variable, in ENameSelectorEntry Class*/
+	g_object_set_data ((GObject *) name_selector_entry, "index", GINT_TO_POINTER (index));
+
+	if (!destination || !e_destination_get_contact (destination))
+		return FALSE;
+
+	/* TODO: Unref destination when we finalize */
+	name_selector_entry->priv->popup_destination = g_object_ref (destination);
+	return FALSE;
+}
+
+static EBookClient *
+find_client_by_contact (GSList *clients,
+                        const gchar *contact_uid,
+                        const gchar *source_uid)
+{
+	GSList *l;
+
+	if (source_uid && *source_uid) {
+		/* this is much quicket than asking each client for an existence */
+		for (l = clients; l; l = g_slist_next (l)) {
+			EBookClient *client = l->data;
+			ESource *source = e_client_get_source (E_CLIENT (client));
+
+			if (!source)
+				continue;
+
+			if (g_strcmp0 (source_uid, e_source_get_uid (source)) == 0)
+				return client;
+		}
+	}
+
+	for (l = clients; l; l = g_slist_next (l)) {
+		EBookClient *client = l->data;
+		EContact *contact = NULL;
+		gboolean  result;
+
+		result = e_book_client_get_contact_sync (client, contact_uid, &contact, NULL, NULL);
+		if (contact)
+			g_object_unref (contact);
+
+		if (result)
+			return client;
+	}
+
+	return NULL;
+}
+
+static void
+editor_closed_cb (GtkWidget *editor,
+                  gpointer data)
+{
+	EContact *contact;
+	gchar *contact_uid;
+	EDestination *destination;
+	GSList *clients;
+	EBookClient *book_client;
+	gint email_num;
+	ENameSelectorEntry *name_selector_entry = E_NAME_SELECTOR_ENTRY (data);
+
+	destination = name_selector_entry->priv->popup_destination;
+	contact = e_destination_get_contact (destination);
+	if (!contact) {
+		g_object_unref (name_selector_entry);
+		return;
+	}
+
+	contact_uid = e_contact_get (contact, E_CONTACT_UID);
+	if (!contact_uid) {
+		g_object_unref (contact);
+		g_object_unref (name_selector_entry);
+		return;
+	}
+
+	if (name_selector_entry->priv->contact_store) {
+		clients = e_contact_store_get_clients (name_selector_entry->priv->contact_store);
+		book_client = find_client_by_contact (clients, contact_uid, e_destination_get_source_uid (destination));
+		g_slist_free (clients);
+	} else {
+		book_client = NULL;
+	}
+
+	if (book_client) {
+		contact = NULL;
+
+		g_warn_if_fail (e_book_client_get_contact_sync (book_client, contact_uid, &contact, NULL, NULL));
+		email_num = e_destination_get_email_num (destination);
+		e_destination_set_contact (destination, contact, email_num);
+		e_destination_set_client (destination, book_client);
+	} else {
+		contact = NULL;
+	}
+
+	g_free (contact_uid);
+	if (contact)
+		g_object_unref (contact);
+	g_object_unref (name_selector_entry);
+}
+
+/* To parse something like...
+ * =?UTF-8?Q?=E0=A4=95=E0=A4=95=E0=A4=AC=E0=A5=82=E0=A5=8B=E0=A5=87?=\t\n=?UTF-8?Q?=E0=A4=B0?=\t\n<aa aa ccom>
+ * and return the decoded representation of name & email parts.
+ * */
+static gboolean
+eab_parse_qp_email (const gchar *string,
+                    gchar **name,
+                    gchar **email)
+{
+	struct _camel_header_address *address;
+	gboolean res = FALSE;
+
+	address = camel_header_address_decode (string, "UTF-8");
+
+	if (!address)
+		return FALSE;
+
+        /* report success only when we have filled both name and email address */
+	if (address->type == CAMEL_HEADER_ADDRESS_NAME  && address->name && *address->name && address->v.addr && *address->v.addr) {
+                *name = g_strdup (address->name);
+                *email = g_strdup (address->v.addr);
+		res = TRUE;
+	}
+
+	camel_header_address_unref (address);
+
+	return res;
+}
+
+static void
+popup_activate_inline_expand (ENameSelectorEntry *name_selector_entry,
+                              GtkWidget *menu_item)
+{
+	const gchar *text;
+	GString *sanitized_text = g_string_new ("");
+	EDestination *destination = name_selector_entry->priv->popup_destination;
+	gint position, start, end;
+	const GList *dests;
+
+	position = GPOINTER_TO_INT (g_object_get_data ((GObject *) name_selector_entry, "index"));
+
+	for (dests = e_destination_list_get_dests (destination); dests; dests = dests->next) {
+		const EDestination *dest = dests->data;
+		gchar *sanitized;
+		gchar *name = NULL, *email = NULL, *tofree = NULL;
+
+		if (!dest)
+			continue;
+
+		text = e_destination_get_textrep (dest, TRUE);
+
+		if (!text || !*text)
+			continue;
+
+		if (eab_parse_qp_email (text, &name, &email)) {
+			tofree = g_strdup_printf ("%s <%s>", name, email);
+			text = tofree;
+			g_free (name);
+			g_free (email);
+		}
+
+		sanitized = sanitize_string (text);
+		g_free (tofree);
+		if (!sanitized)
+			continue;
+
+		if (*sanitized) {
+			if (*sanitized_text->str)
+				g_string_append (sanitized_text, ", ");
+
+			g_string_append (sanitized_text, sanitized);
+		}
+
+		g_free (sanitized);
+	}
+
+	text = gtk_entry_get_text (GTK_ENTRY (name_selector_entry));
+	get_range_at_position (text, position, &start, &end);
+	gtk_editable_delete_text (GTK_EDITABLE (name_selector_entry), start, end);
+	gtk_editable_insert_text (GTK_EDITABLE (name_selector_entry), sanitized_text->str, -1, &start);
+	g_string_free (sanitized_text, TRUE);
+
+	clear_completion_model (name_selector_entry);
+	generate_attribute_list (name_selector_entry);
+}
+
+static void
+popup_activate_contact (ENameSelectorEntry *name_selector_entry,
+                        GtkWidget *menu_item)
+{
+	EBookClient  *book_client;
+	GSList       *clients;
+	EDestination *destination;
+	EContact     *contact;
+	gchar        *contact_uid;
+
+	destination = name_selector_entry->priv->popup_destination;
+	if (!destination)
+		return;
+
+	contact = e_destination_get_contact (destination);
+	if (!contact)
+		return;
+
+	contact_uid = e_contact_get (contact, E_CONTACT_UID);
+	if (!contact_uid)
+		return;
+
+	if (name_selector_entry->priv->contact_store) {
+		clients = e_contact_store_get_clients (name_selector_entry->priv->contact_store);
+		book_client = find_client_by_contact (clients, contact_uid, e_destination_get_source_uid (destination));
+		g_slist_free (clients);
+		g_free (contact_uid);
+	} else {
+		book_client = NULL;
+	}
+
+	if (!book_client)
+		return;
+
+	if (e_destination_is_evolution_list (destination)) {
+		GtkWidget *contact_list_editor;
+
+		if (!name_selector_entry->priv->contact_list_editor_func)
+			return;
+
+		contact_list_editor = (*name_selector_entry->priv->contact_list_editor_func) (book_client, contact, FALSE, TRUE);
+		g_object_ref (name_selector_entry);
+		g_signal_connect (
+			contact_list_editor, "editor_closed",
+			G_CALLBACK (editor_closed_cb), name_selector_entry);
+	} else {
+		GtkWidget *contact_editor;
+
+		if (!name_selector_entry->priv->contact_editor_func)
+			return;
+
+		contact_editor = (*name_selector_entry->priv->contact_editor_func) (book_client, contact, FALSE, TRUE);
+		g_object_ref (name_selector_entry);
+		g_signal_connect (
+			contact_editor, "editor_closed",
+			G_CALLBACK (editor_closed_cb), name_selector_entry);
+	}
+}
+
+static void
+popup_activate_email (ENameSelectorEntry *name_selector_entry,
+                      GtkWidget *menu_item)
+{
+	EDestination *destination;
+	EContact     *contact;
+	gint          email_num;
+
+	destination = name_selector_entry->priv->popup_destination;
+	if (!destination)
+		return;
+
+	contact = e_destination_get_contact (destination);
+	if (!contact)
+		return;
+
+	email_num = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (menu_item), "order"));
+	e_destination_set_contact (destination, contact, email_num);
+}
+
+static void
+popup_activate_list (EDestination *destination,
+                     GtkWidget *item)
+{
+	gboolean status = gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (item));
+
+	e_destination_set_ignored (destination, !status);
+}
+
+static void
+popup_activate_cut (ENameSelectorEntry *name_selector_entry,
+                    GtkWidget *menu_item)
+{
+	EDestination *destination;
+	const gchar *contact_email;
+	gchar *pemail = NULL;
+	GtkClipboard *clipboard;
+
+	destination = name_selector_entry->priv->popup_destination;
+	contact_email =e_destination_get_textrep (destination, TRUE);
+
+	g_signal_handlers_block_by_func (name_selector_entry, user_insert_text, name_selector_entry);
+	g_signal_handlers_block_by_func (name_selector_entry, user_delete_text, name_selector_entry);
+
+	clipboard = gtk_clipboard_get (GDK_SELECTION_PRIMARY);
+	pemail = g_strconcat (contact_email, ",", NULL);
+	gtk_clipboard_set_text (clipboard, pemail, strlen (pemail));
+
+	clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
+	gtk_clipboard_set_text (clipboard, pemail, strlen (pemail));
+
+	gtk_editable_delete_text (GTK_EDITABLE (name_selector_entry), 0, 0);
+	e_destination_store_remove_destination (name_selector_entry->priv->destination_store, destination);
+
+	g_free (pemail);
+	g_signal_handlers_unblock_by_func (name_selector_entry, user_delete_text, name_selector_entry);
+	g_signal_handlers_unblock_by_func (name_selector_entry, user_insert_text, name_selector_entry);
+}
+
+static void
+popup_activate_copy (ENameSelectorEntry *name_selector_entry,
+                     GtkWidget *menu_item)
+{
+	EDestination *destination;
+	const gchar *contact_email;
+	gchar *pemail;
+	GtkClipboard *clipboard;
+
+	destination = name_selector_entry->priv->popup_destination;
+	contact_email = e_destination_get_textrep (destination, TRUE);
+
+	g_signal_handlers_block_by_func (name_selector_entry, user_insert_text, name_selector_entry);
+	g_signal_handlers_block_by_func (name_selector_entry, user_delete_text, name_selector_entry);
+
+	clipboard = gtk_clipboard_get (GDK_SELECTION_PRIMARY);
+	pemail = g_strconcat (contact_email, ",", NULL);
+	gtk_clipboard_set_text (clipboard, pemail, strlen (pemail));
+
+	clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
+	gtk_clipboard_set_text (clipboard, pemail, strlen (pemail));
+	g_free (pemail);
+	g_signal_handlers_unblock_by_func (name_selector_entry, user_delete_text, name_selector_entry);
+	g_signal_handlers_unblock_by_func (name_selector_entry, user_insert_text, name_selector_entry);
+}
+
+static void
+destination_set_list (GtkWidget *item,
+                      EDestination *destination)
+{
+	EContact *contact;
+	gboolean status = gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (item));
+
+	contact = e_destination_get_contact (destination);
+	if (!contact)
+		return;
+
+	e_destination_set_ignored (destination, !status);
+}
+
+static void
+destination_set_email (GtkWidget *item,
+                       EDestination *destination)
+{
+	gint email_num;
+	EContact *contact;
+
+	if (!gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (item)))
+		return;
+	contact = e_destination_get_contact (destination);
+	if (!contact)
+		return;
+
+	email_num = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (item), "order"));
+	e_destination_set_contact (destination, contact, email_num);
+}
+
+static void
+populate_popup (ENameSelectorEntry *name_selector_entry,
+                GtkMenu *menu)
+{
+	EDestination *destination;
+	EContact     *contact;
+	GtkWidget    *menu_item;
+	GList        *email_list = NULL;
+	GList        *l;
+	gint          i;
+	gchar	     *edit_label;
+	gchar	     *cut_label;
+	gchar         *copy_label;
+	gint	      email_num, len;
+	GSList	     *group = NULL;
+	gboolean      is_list;
+	gboolean      show_menu = FALSE;
+
+	destination = name_selector_entry->priv->popup_destination;
+	if (!destination)
+		return;
+
+	contact = e_destination_get_contact (destination);
+	if (!contact)
+		return;
+
+	/* Prepend the menu items, backwards */
+
+	/* Separator */
+
+	menu_item = gtk_separator_menu_item_new ();
+	gtk_widget_show (menu_item);
+	gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), menu_item);
+	email_num = e_destination_get_email_num (destination);
+
+	/* Addresses */
+	is_list = e_contact_get (contact, E_CONTACT_IS_LIST) ? TRUE : FALSE;
+	if (is_list) {
+		const GList *dests = e_destination_list_get_dests (destination);
+		GList *iter;
+		gint length = g_list_length ((GList *) dests);
+
+		for (iter = (GList *) dests; iter; iter = iter->next) {
+			EDestination *dest = (EDestination *) iter->data;
+			const gchar *email = e_destination_get_email (dest);
+
+			if (!email || *email == '\0')
+				continue;
+
+			if (length > 1) {
+				menu_item = gtk_check_menu_item_new_with_label (email);
+				g_signal_connect (
+					menu_item, "toggled",
+					G_CALLBACK (destination_set_list), dest);
+			} else {
+				menu_item = gtk_menu_item_new_with_label (email);
+			}
+
+			gtk_widget_show (menu_item);
+			gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), menu_item);
+			show_menu = TRUE;
+
+			if (length > 1) {
+				gtk_check_menu_item_set_active (
+					GTK_CHECK_MENU_ITEM (menu_item),
+					!e_destination_is_ignored (dest));
+				g_signal_connect_swapped (
+					menu_item, "activate",
+					G_CALLBACK (popup_activate_list), dest);
+			}
+		}
+
+	} else {
+		email_list = e_contact_get (contact, E_CONTACT_EMAIL);
+		len = g_list_length (email_list);
+
+		for (l = email_list, i = 0; l; l = g_list_next (l), i++) {
+			gchar *email = l->data;
+
+			if (!email || *email == '\0')
+				continue;
+
+			if (len > 1) {
+				menu_item = gtk_radio_menu_item_new_with_label (group, email);
+				group = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM (menu_item));
+				g_signal_connect (menu_item, "toggled", G_CALLBACK (destination_set_email), destination);
+			} else {
+				menu_item = gtk_menu_item_new_with_label (email);
+			}
+
+			gtk_widget_show (menu_item);
+			gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), menu_item);
+			show_menu = TRUE;
+			g_object_set_data (G_OBJECT (menu_item), "order", GINT_TO_POINTER (i));
+
+			if (i == email_num && len > 1) {
+				gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (menu_item), TRUE);
+				g_signal_connect_swapped (
+					menu_item, "activate",
+					G_CALLBACK (popup_activate_email),
+					name_selector_entry);
+			}
+		}
+	}
+
+	/* Separator */
+
+	if (show_menu) {
+		menu_item = gtk_separator_menu_item_new ();
+		gtk_widget_show (menu_item);
+		gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), menu_item);
+	}
+
+	/* Expand a list inline */
+	if (is_list) {
+		/* To Translators: This would be similiar to "Expand MyList Inline" where MyList is a Contact List*/
+		edit_label = g_strdup_printf (_("E_xpand %s Inline"), (gchar *) e_contact_get_const (contact, E_CONTACT_FILE_AS));
+		menu_item = gtk_menu_item_new_with_mnemonic (edit_label);
+		g_free (edit_label);
+		gtk_widget_show (menu_item);
+		gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), menu_item);
+		g_signal_connect_swapped (
+			menu_item, "activate", G_CALLBACK (popup_activate_inline_expand),
+			name_selector_entry);
+
+		/* Separator */
+		menu_item = gtk_separator_menu_item_new ();
+		gtk_widget_show (menu_item);
+		gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), menu_item);
+	}
+
+	/* Copy Contact Item */
+	copy_label = g_strdup_printf (_("Cop_y %s"), (gchar *) e_contact_get_const (contact, E_CONTACT_FILE_AS));
+	menu_item = gtk_menu_item_new_with_mnemonic (copy_label);
+	g_free (copy_label);
+	gtk_widget_show (menu_item);
+	gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), menu_item);
+
+	g_signal_connect_swapped (
+		menu_item, "activate", G_CALLBACK (popup_activate_copy),
+		name_selector_entry);
+
+	/* Cut Contact Item */
+	cut_label = g_strdup_printf (_("C_ut %s"), (gchar *) e_contact_get_const (contact, E_CONTACT_FILE_AS));
+	menu_item = gtk_menu_item_new_with_mnemonic (cut_label);
+	g_free (cut_label);
+	gtk_widget_show (menu_item);
+	gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), menu_item);
+
+	g_signal_connect_swapped (
+		menu_item, "activate", G_CALLBACK (popup_activate_cut),
+		name_selector_entry);
+
+	if (show_menu) {
+		menu_item = gtk_separator_menu_item_new ();
+		gtk_widget_show (menu_item);
+		gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), menu_item);
+	}
+
+	/* Edit Contact item */
+
+	edit_label = g_strdup_printf (_("_Edit %s"), (gchar *) e_contact_get_const (contact, E_CONTACT_FILE_AS));
+	menu_item = gtk_menu_item_new_with_mnemonic (edit_label);
+	g_free (edit_label);
+	gtk_widget_show (menu_item);
+	gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), menu_item);
+
+	g_signal_connect_swapped (
+		menu_item, "activate", G_CALLBACK (popup_activate_contact),
+		name_selector_entry);
+
+	deep_free_list (email_list);
+}
+
+static void
+copy_or_cut_clipboard (ENameSelectorEntry *name_selector_entry,
+                       gboolean is_cut)
+{
+	GtkClipboard *clipboard;
+	GtkEditable *editable;
+	const gchar *text, *cp;
+	GHashTable *hash;
+	GHashTableIter iter;
+	gpointer key, value;
+	GString *addresses;
+	gint ii, start, end;
+	gunichar uc;
+
+	editable = GTK_EDITABLE (name_selector_entry);
+	text = gtk_entry_get_text (GTK_ENTRY (editable));
+
+	if (!gtk_editable_get_selection_bounds (editable, &start, &end))
+		return;
+
+	g_return_if_fail (end > start);
+
+	hash = g_hash_table_new (g_direct_hash, g_direct_equal);
+
+	ii = end;
+	cp = g_utf8_offset_to_pointer (text, end);
+	uc = g_utf8_get_char (cp);
+
+	/* Exclude trailing whitespace and commas. */
+	while (ii >= start && (uc == ',' || g_unichar_isspace (uc))) {
+		cp = g_utf8_prev_char (cp);
+		uc = g_utf8_get_char (cp);
+		ii--;
+	}
+
+	/* Determine the index of each remaining character. */
+	while (ii >= start) {
+		gint index = get_index_at_position (text, ii--);
+		g_hash_table_insert (hash, GINT_TO_POINTER (index), NULL);
+	}
+
+	addresses = g_string_new ("");
+
+	g_hash_table_iter_init (&iter, hash);
+	while (g_hash_table_iter_next (&iter, &key, &value)) {
+		gint index = GPOINTER_TO_INT (key);
+		EDestination *dest;
+		gint rstart, rend;
+
+		if (!get_range_by_index (text, index, &rstart, &rend))
+			continue;
+
+		if (rstart < start) {
+			if (addresses->str && *addresses->str)
+				g_string_append (addresses, ", ");
+
+			g_string_append_len (addresses, text + start, rend - start);
+		} else if (rend > end) {
+			if (addresses->str && *addresses->str)
+				g_string_append (addresses, ", ");
+
+			g_string_append_len (addresses, text + rstart, end - rstart);
+		} else {
+			/* the contact is whole selected */
+			dest = find_destination_by_index (name_selector_entry, index);
+			if (dest && e_destination_get_textrep (dest, TRUE)) {
+				if (addresses->str && *addresses->str)
+					g_string_append (addresses, ", ");
+
+				g_string_append (addresses, e_destination_get_textrep (dest, TRUE));
+
+				/* store the 'dest' as a value for the index */
+				g_hash_table_insert (hash, GINT_TO_POINTER (index), dest);
+			} else
+				g_string_append_len (addresses, text + rstart, rend - rstart);
+		}
+	}
+
+	if (is_cut)
+		gtk_editable_delete_text (editable, start, end);
+
+	g_hash_table_unref (hash);
+
+	clipboard = gtk_widget_get_clipboard (
+		GTK_WIDGET (name_selector_entry), GDK_SELECTION_CLIPBOARD);
+	gtk_clipboard_set_text (clipboard, addresses->str, -1);
+
+	g_string_free (addresses, TRUE);
+}
+
+static void
+copy_clipboard (GtkEntry *entry,
+                ENameSelectorEntry *name_selector_entry)
+{
+	copy_or_cut_clipboard (name_selector_entry, FALSE);
+	g_signal_stop_emission_by_name (entry, "copy-clipboard");
+}
+
+static void
+cut_clipboard (GtkEntry *entry,
+               ENameSelectorEntry *name_selector_entry)
+{
+	copy_or_cut_clipboard (name_selector_entry, TRUE);
+	g_signal_stop_emission_by_name (entry, "cut-clipboard");
+}
+
+static void
+e_name_selector_entry_init (ENameSelectorEntry *name_selector_entry)
+{
+	GtkCellRenderer *renderer;
+
+	name_selector_entry->priv =
+		E_NAME_SELECTOR_ENTRY_GET_PRIVATE (name_selector_entry);
+
+	g_queue_init (&name_selector_entry->priv->cancellables);
+
+	name_selector_entry->priv->minimum_query_length = 3;
+	name_selector_entry->priv->show_address = FALSE;
+
+	/* Edit signals */
+
+	g_signal_connect (
+		name_selector_entry, "insert-text",
+		G_CALLBACK (user_insert_text), name_selector_entry);
+	g_signal_connect (
+		name_selector_entry, "delete-text",
+		G_CALLBACK (user_delete_text), name_selector_entry);
+	g_signal_connect (
+		name_selector_entry, "focus-out-event",
+		G_CALLBACK (user_focus_out), name_selector_entry);
+	g_signal_connect_after (
+		name_selector_entry, "focus-in-event",
+		G_CALLBACK (user_focus_in), name_selector_entry);
+
+	/* Drawing */
+
+	g_signal_connect (
+		name_selector_entry, "draw",
+		G_CALLBACK (draw_event), name_selector_entry);
+
+	/* Activation: Complete current entry if possible */
+
+	g_signal_connect (
+		name_selector_entry, "activate",
+		G_CALLBACK (entry_activate), name_selector_entry);
+
+	/* Pop-up menu */
+
+	g_signal_connect (
+		name_selector_entry, "button-press-event",
+		G_CALLBACK (prepare_popup_destination), name_selector_entry);
+	g_signal_connect (
+		name_selector_entry, "populate-popup",
+		G_CALLBACK (populate_popup), name_selector_entry);
+
+	/* Clipboard signals */
+	g_signal_connect (
+		name_selector_entry, "copy-clipboard",
+		G_CALLBACK (copy_clipboard), name_selector_entry);
+	g_signal_connect (
+		name_selector_entry, "cut-clipboard",
+		G_CALLBACK (cut_clipboard), name_selector_entry);
+
+	/* Completion */
+
+	name_selector_entry->priv->email_generator = NULL;
+
+	name_selector_entry->priv->entry_completion = gtk_entry_completion_new ();
+	gtk_entry_completion_set_match_func (
+		name_selector_entry->priv->entry_completion,
+		(GtkEntryCompletionMatchFunc) completion_match_cb, NULL, NULL);
+	g_signal_connect_swapped (
+		name_selector_entry->priv->entry_completion, "match-selected",
+		G_CALLBACK (completion_match_selected), name_selector_entry);
+
+	gtk_entry_set_completion (
+		GTK_ENTRY (name_selector_entry),
+		name_selector_entry->priv->entry_completion);
+
+	renderer = gtk_cell_renderer_pixbuf_new ();
+	gtk_cell_layout_pack_start (
+		GTK_CELL_LAYOUT (name_selector_entry->priv->entry_completion),
+		renderer, FALSE);
+	gtk_cell_layout_set_cell_data_func (
+		GTK_CELL_LAYOUT (name_selector_entry->priv->entry_completion),
+		GTK_CELL_RENDERER (renderer),
+		(GtkCellLayoutDataFunc) contact_layout_pixbuffer,
+		name_selector_entry, NULL);
+
+	/* Completion list name renderer */
+	renderer = gtk_cell_renderer_text_new ();
+	gtk_cell_layout_pack_start (
+		GTK_CELL_LAYOUT (name_selector_entry->priv->entry_completion),
+		renderer, TRUE);
+	gtk_cell_layout_set_cell_data_func (
+		GTK_CELL_LAYOUT (name_selector_entry->priv->entry_completion),
+		GTK_CELL_RENDERER (renderer),
+		(GtkCellLayoutDataFunc) contact_layout_formatter,
+		name_selector_entry, NULL);
+
+	/* Destination store */
+
+	name_selector_entry->priv->destination_store = e_destination_store_new ();
+	setup_destination_store (name_selector_entry);
+	name_selector_entry->priv->is_completing = FALSE;
+}
+
+/**
+ * e_name_selector_entry_new:
+ *
+ * Creates a new #ENameSelectorEntry.
+ *
+ * Returns: A new #ENameSelectorEntry.
+ **/
+ENameSelectorEntry *
+e_name_selector_entry_new (ESourceRegistry *registry)
+{
+	g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), NULL);
+
+	return g_object_new (
+		E_TYPE_NAME_SELECTOR_ENTRY,
+		"registry", registry, NULL);
+}
+
+/**
+ * e_name_selector_entry_get_registry:
+ * @name_selector_entry: an #ENameSelectorEntry
+ *
+ * Returns the #ESourceRegistry used to query address books.
+ *
+ * Returns: the #ESourceRegistry, or %NULL
+ *
+ * Since: 3.6
+ **/
+ESourceRegistry *
+e_name_selector_entry_get_registry (ENameSelectorEntry *name_selector_entry)
+{
+	g_return_val_if_fail (
+		E_IS_NAME_SELECTOR_ENTRY (name_selector_entry), NULL);
+
+	return name_selector_entry->priv->registry;
+}
+
+/**
+ * e_name_selector_entry_set_registry:
+ * @name_selector_entry: an #ENameSelectorEntry
+ * @registry: an #ESourceRegistry
+ *
+ * Sets the #ESourceRegistry used to query address books.
+ *
+ * This function is intended for cases where @name_selector_entry is
+ * instantiated by a #GtkBuilder and has to be given an #EsourceRegistry
+ * after it is fully constructed.
+ *
+ * Since: 3.6
+ **/
+void
+e_name_selector_entry_set_registry (ENameSelectorEntry *name_selector_entry,
+                                    ESourceRegistry *registry)
+{
+	g_return_if_fail (E_IS_NAME_SELECTOR_ENTRY (name_selector_entry));
+
+	if (name_selector_entry->priv->registry == registry)
+		return;
+
+	if (registry != NULL) {
+		g_return_if_fail (E_IS_SOURCE_REGISTRY (registry));
+		g_object_ref (registry);
+	}
+
+	if (name_selector_entry->priv->registry != NULL)
+		g_object_unref (name_selector_entry->priv->registry);
+
+	name_selector_entry->priv->registry = registry;
+
+	g_object_notify (G_OBJECT (name_selector_entry), "registry");
+}
+
+/**
+ * e_name_selector_entry_get_minimum_query_length:
+ * @name_selector_entry: an #ENameSelectorEntry
+ *
+ * Returns: Minimum length of query before completion starts
+ *
+ * Since: 3.6
+ **/
+gint
+e_name_selector_entry_get_minimum_query_length (ENameSelectorEntry *name_selector_entry)
+{
+	g_return_val_if_fail (E_IS_NAME_SELECTOR_ENTRY (name_selector_entry), -1);
+
+	return name_selector_entry->priv->minimum_query_length;
+}
+
+/**
+ * e_name_selector_entry_set_minimum_query_length:
+ * @name_selector_entry: an #ENameSelectorEntry
+ * @length: minimum query length
+ *
+ * Sets minimum length of query before completion starts.
+ *
+ * Since: 3.6
+ **/
+void
+e_name_selector_entry_set_minimum_query_length (ENameSelectorEntry *name_selector_entry,
+                                                gint length)
+{
+	g_return_if_fail (E_IS_NAME_SELECTOR_ENTRY (name_selector_entry));
+	g_return_if_fail (length > 0);
+
+	if (name_selector_entry->priv->minimum_query_length == length)
+		return;
+
+	name_selector_entry->priv->minimum_query_length = length;
+
+	g_object_notify (G_OBJECT (name_selector_entry), "minimum-query-length");
+}
+
+/**
+ * e_name_selector_entry_get_show_address:
+ * @name_selector_entry: an #ENameSelectorEntry
+ *
+ * Returns: Whether always show email address for an auto-completed contact.
+ *
+ * Since: 3.6
+ **/
+gboolean
+e_name_selector_entry_get_show_address (ENameSelectorEntry *name_selector_entry)
+{
+	g_return_val_if_fail (E_IS_NAME_SELECTOR_ENTRY (name_selector_entry), FALSE);
+
+	return name_selector_entry->priv->show_address;
+}
+
+/**
+ * e_name_selector_entry_set_show_address:
+ * @name_selector_entry: an #ENameSelectorEntry
+ * @show: new value to set
+ *
+ * Sets whether always show email address for an auto-completed contact.
+ *
+ * Since: 3.6
+ **/
+void
+e_name_selector_entry_set_show_address (ENameSelectorEntry *name_selector_entry,
+                                        gboolean show)
+{
+	g_return_if_fail (E_IS_NAME_SELECTOR_ENTRY (name_selector_entry));
+
+	if ((name_selector_entry->priv->show_address ? 1 : 0) == (show ? 1 : 0))
+		return;
+
+	name_selector_entry->priv->show_address = show;
+
+	sanitize_entry (name_selector_entry);
+
+	g_object_notify (G_OBJECT (name_selector_entry), "show-address");
+}
+
+/**
+ * e_name_selector_entry_peek_contact_store:
+ * @name_selector_entry: an #ENameSelectorEntry
+ *
+ * Gets the #EContactStore being used by @name_selector_entry.
+ *
+ * Returns: An #EContactStore.
+ **/
+EContactStore *
+e_name_selector_entry_peek_contact_store (ENameSelectorEntry *name_selector_entry)
+{
+	g_return_val_if_fail (E_IS_NAME_SELECTOR_ENTRY (name_selector_entry), NULL);
+
+	return name_selector_entry->priv->contact_store;
+}
+
+/**
+ * e_name_selector_entry_set_contact_store:
+ * @name_selector_entry: an #ENameSelectorEntry
+ * @contact_store: an #EContactStore to use
+ *
+ * Sets the #EContactStore being used by @name_selector_entry to @contact_store.
+ **/
+void
+e_name_selector_entry_set_contact_store (ENameSelectorEntry *name_selector_entry,
+                                         EContactStore *contact_store)
+{
+	g_return_if_fail (E_IS_NAME_SELECTOR_ENTRY (name_selector_entry));
+	g_return_if_fail (contact_store == NULL || E_IS_CONTACT_STORE (contact_store));
+
+	if (contact_store == name_selector_entry->priv->contact_store)
+		return;
+
+	if (name_selector_entry->priv->contact_store)
+		g_object_unref (name_selector_entry->priv->contact_store);
+	name_selector_entry->priv->contact_store = contact_store;
+	if (name_selector_entry->priv->contact_store)
+		g_object_ref (name_selector_entry->priv->contact_store);
+
+	setup_contact_store (name_selector_entry);
+}
+
+/**
+ * e_name_selector_entry_peek_destination_store:
+ * @name_selector_entry: an #ENameSelectorEntry
+ *
+ * Gets the #EDestinationStore being used to store @name_selector_entry's destinations.
+ *
+ * Returns: An #EDestinationStore.
+ **/
+EDestinationStore *
+e_name_selector_entry_peek_destination_store (ENameSelectorEntry *name_selector_entry)
+{
+	g_return_val_if_fail (E_IS_NAME_SELECTOR_ENTRY (name_selector_entry), NULL);
+
+	return name_selector_entry->priv->destination_store;
+}
+
+/**
+ * e_name_selector_entry_set_destination_store:
+ * @name_selector_entry: an #ENameSelectorEntry
+ * @destination_store: an #EDestinationStore to use
+ *
+ * Sets @destination_store as the #EDestinationStore to be used to store
+ * destinations for @name_selector_entry.
+ **/
+void
+e_name_selector_entry_set_destination_store (ENameSelectorEntry *name_selector_entry,
+                                             EDestinationStore *destination_store)
+{
+	g_return_if_fail (E_IS_NAME_SELECTOR_ENTRY (name_selector_entry));
+	g_return_if_fail (E_IS_DESTINATION_STORE (destination_store));
+
+	if (destination_store == name_selector_entry->priv->destination_store)
+		return;
+
+	g_object_unref (name_selector_entry->priv->destination_store);
+	name_selector_entry->priv->destination_store = g_object_ref (destination_store);
+
+	setup_destination_store (name_selector_entry);
+}
+
+/**
+ * e_name_selector_entry_get_popup_destination:
+ *
+ * Since: 2.32
+ **/
+EDestination *
+e_name_selector_entry_get_popup_destination (ENameSelectorEntry *name_selector_entry)
+{
+	g_return_val_if_fail (E_IS_NAME_SELECTOR_ENTRY (name_selector_entry), NULL);
+
+	return name_selector_entry->priv->popup_destination;
+}
+
+/**
+ * e_name_selector_entry_set_contact_editor_func:
+ *
+ * DO NOT USE.
+ **/
+void
+e_name_selector_entry_set_contact_editor_func (ENameSelectorEntry *name_selector_entry,
+                                               gpointer func)
+{
+	name_selector_entry->priv->contact_editor_func = func;
+}
+
+/**
+ * e_name_selector_entry_set_contact_list_editor_func:
+ *
+ * DO NOT USE.
+ **/
+void
+e_name_selector_entry_set_contact_list_editor_func (ENameSelectorEntry *name_selector_entry,
+                                                    gpointer func)
+{
+	name_selector_entry->priv->contact_list_editor_func = func;
+}
diff --git a/e-util/e-name-selector-entry.h b/e-util/e-name-selector-entry.h
new file mode 100644
index 0000000..63ce9aa
--- /dev/null
+++ b/e-util/e-name-selector-entry.h
@@ -0,0 +1,124 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/* e-name-selector-entry.c - Single-line text entry widget for EDestinations.
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * 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., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * Authors: Hans Petter Jansson <hpj novell com>
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_NAME_SELECTOR_ENTRY_H
+#define E_NAME_SELECTOR_ENTRY_H
+
+#include <gtk/gtk.h>
+#include <libebook/libebook.h>
+
+#include <e-util/e-contact-store.h>
+#include <e-util/e-destination-store.h>
+#include <e-util/e-tree-model-generator.h>
+
+/* Standard GObject macros */
+#define E_TYPE_NAME_SELECTOR_ENTRY \
+	(e_name_selector_entry_get_type ())
+#define E_NAME_SELECTOR_ENTRY(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_NAME_SELECTOR_ENTRY, ENameSelectorEntry))
+#define E_NAME_SELECTOR_ENTRY_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_NAME_SELECTOR_ENTRY, ENameSelectorEntryClass))
+#define E_IS_NAME_SELECTOR_ENTRY(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_NAME_SELECTOR_ENTRY))
+#define E_IS_NAME_SELECTOR_ENTRY_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_NAME_SELECTOR_ENTRY))
+#define E_NAME_SELECTOR_ENTRY_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_NAME_SELECTOR_ENTRY, ENameSelectorEntryClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ENameSelectorEntry ENameSelectorEntry;
+typedef struct _ENameSelectorEntryClass ENameSelectorEntryClass;
+typedef struct _ENameSelectorEntryPrivate ENameSelectorEntryPrivate;
+
+struct _ENameSelectorEntry {
+	GtkEntry parent;
+	ENameSelectorEntryPrivate *priv;
+};
+
+struct _ENameSelectorEntryClass {
+	GtkEntryClass parent_class;
+
+	void (*updated) (ENameSelectorEntry *entry, gchar *email);
+
+	gpointer reserved1;
+	gpointer reserved2;
+};
+
+GType		e_name_selector_entry_get_type	(void);
+ENameSelectorEntry *
+		e_name_selector_entry_new	(ESourceRegistry *registry);
+ESourceRegistry *
+		e_name_selector_entry_get_registry
+						(ENameSelectorEntry *name_selector_entry);
+void		e_name_selector_entry_set_registry
+						(ENameSelectorEntry *name_selector_entry,
+						 ESourceRegistry *registry);
+gint		e_name_selector_entry_get_minimum_query_length
+						(ENameSelectorEntry *name_selector_entry);
+void		e_name_selector_entry_set_minimum_query_length
+						(ENameSelectorEntry *name_selector_entry,
+						 gint length);
+gboolean	e_name_selector_entry_get_show_address
+						(ENameSelectorEntry *name_selector_entry);
+void		e_name_selector_entry_set_show_address
+						(ENameSelectorEntry *name_selector_entry,
+						 gboolean show);
+EContactStore *	e_name_selector_entry_peek_contact_store
+						(ENameSelectorEntry *name_selector_entry);
+void		e_name_selector_entry_set_contact_store
+						(ENameSelectorEntry *name_selector_entry,
+						 EContactStore *contact_store);
+EDestinationStore *
+		e_name_selector_entry_peek_destination_store
+						(ENameSelectorEntry *name_selector_entry);
+void		e_name_selector_entry_set_destination_store
+						(ENameSelectorEntry *name_selector_entry,
+						 EDestinationStore *destination_store);
+EDestination *	e_name_selector_entry_get_popup_destination
+						(ENameSelectorEntry *name_selector_entry);
+
+/* TEMPORARY API - DO NOT USE */
+void		e_name_selector_entry_set_contact_editor_func
+						(ENameSelectorEntry *name_selector_entry,
+						 gpointer func);
+void		e_name_selector_entry_set_contact_list_editor_func
+						(ENameSelectorEntry *name_selector_entry,
+						 gpointer func);
+gchar *		ens_util_populate_user_query_fields
+						(GSList *user_query_fields,
+						 const gchar *cue_str,
+						 const gchar *encoded_cue_str);
+
+G_END_DECLS
+
+#endif /* E_NAME_SELECTOR_ENTRY_H */
diff --git a/e-util/e-name-selector-list.c b/e-util/e-name-selector-list.c
new file mode 100644
index 0000000..67afb50
--- /dev/null
+++ b/e-util/e-name-selector-list.c
@@ -0,0 +1,790 @@
+/*
+ * Single-line text entry widget for EDestinations.
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Srinivasa Ragavan <sragavan novell com>
+ *		Devashish Sharma  <sdevashish novell com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#include <config.h>
+#include <string.h>
+#include <gdk/gdkkeysyms.h>
+#include <glib/gi18n-lib.h>
+
+#include "e-name-selector-list.h"
+#include "e-name-selector-entry.h"
+
+#define E_NAME_SELECTOR_LIST_GET_PRIVATE(obj) \
+	(G_TYPE_INSTANCE_GET_PRIVATE \
+	((obj), E_TYPE_NAME_SELECTOR_LIST, ENameSelectorListPrivate))
+
+#define MAX_ROW	10
+
+struct _ENameSelectorListPrivate {
+	GtkWindow *popup;
+	GtkWidget *tree_view;
+	GtkWidget *menu;
+	gint rows;
+	GdkDevice *grab_keyboard;
+	GdkDevice *grab_pointer;
+};
+
+G_DEFINE_TYPE (ENameSelectorList, e_name_selector_list, E_TYPE_NAME_SELECTOR_ENTRY)
+
+/* Signals */
+
+static void
+enl_popup_size (ENameSelectorList *list)
+{
+	gint height = 0, count;
+	GtkAllocation allocation;
+	GtkTreeViewColumn *column = NULL;
+
+	column = gtk_tree_view_get_column ( GTK_TREE_VIEW (list->priv->tree_view), 0);
+	if (column)
+		gtk_tree_view_column_cell_get_size (column, NULL, NULL, NULL, NULL, &height);
+
+	/* Show a maximum of 10 rows in the popup list view */
+	count = list->priv->rows;
+	if (count > MAX_ROW)
+		count = MAX_ROW;
+	if (count <= 0)
+		count = 1;
+
+	gtk_widget_get_allocation (GTK_WIDGET (list), &allocation);
+	gtk_widget_set_size_request (list->priv->tree_view, allocation.width - 3 , height * count);
+}
+
+static void
+enl_popup_position (ENameSelectorList *list)
+{
+	GtkAllocation allocation;
+	GdkWindow *window;
+	gint x,y;
+
+	gtk_widget_get_allocation (GTK_WIDGET (list), &allocation);
+
+	enl_popup_size (list);
+	window = gtk_widget_get_window (GTK_WIDGET (list));
+	gdk_window_get_origin (window, &x, &y);
+	y = y + allocation.height;
+
+	gtk_window_move (list->priv->popup, x, y);
+}
+
+static gboolean
+popup_grab_on_window (GdkWindow *window,
+                      GdkDevice *keyboard,
+                      GdkDevice *pointer,
+                      guint32    activate_time)
+{
+	if (keyboard && gdk_device_grab (keyboard, window,
+			GDK_OWNERSHIP_WINDOW, TRUE,
+			GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK,
+			NULL, activate_time) != GDK_GRAB_SUCCESS)
+		return FALSE;
+
+	if (pointer && gdk_device_grab (pointer, window,
+			GDK_OWNERSHIP_WINDOW, TRUE,
+			GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
+			GDK_POINTER_MOTION_MASK,
+			NULL, activate_time) != GDK_GRAB_SUCCESS) {
+		if (keyboard)
+			gdk_device_ungrab (keyboard, activate_time);
+
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+static void
+enl_popup_grab (ENameSelectorList *list,
+		const GdkEvent *event)
+{
+	EDestinationStore *store;
+	ENameSelectorEntry *entry;
+	GdkWindow *window;
+	GdkDevice *device = NULL;
+	GdkDevice *keyboard, *pointer;
+	gint len;
+
+	if (list->priv->grab_pointer && list->priv->grab_keyboard)
+		return;
+
+	window = gtk_widget_get_window (GTK_WIDGET (list->priv->popup));
+
+	if (event)
+		device = gdk_event_get_device (event);
+	if (!device)
+		device = gtk_get_current_event_device ();
+	if (!device) {
+		GdkDeviceManager *device_manager;
+
+		device_manager = gdk_display_get_device_manager (gtk_widget_get_display (GTK_WIDGET (list)));
+		device = gdk_device_manager_get_client_pointer (device_manager);
+	}
+
+	if (gdk_device_get_source (device) == GDK_SOURCE_KEYBOARD) {
+		keyboard = device;
+		pointer = gdk_device_get_associated_device (device);
+	} else {
+		pointer = device;
+		keyboard = gdk_device_get_associated_device (device);
+	}
+
+	if (!popup_grab_on_window (window, keyboard, pointer, gtk_get_current_event_time ()))
+		return;
+
+	gtk_widget_grab_focus ((GtkWidget *) list);
+
+	/* Build the listview from the model */
+	entry = E_NAME_SELECTOR_ENTRY (list);
+	store = e_name_selector_entry_peek_destination_store (entry);
+	gtk_tree_view_set_model (
+		GTK_TREE_VIEW (list->priv->tree_view),
+		GTK_TREE_MODEL (store));
+
+	/* If any selection of text is present, unselect it */
+	len = strlen (gtk_entry_get_text (GTK_ENTRY (list)));
+	gtk_editable_select_region (GTK_EDITABLE (list), len, -1);
+
+	gtk_device_grab_add (GTK_WIDGET (list->priv->popup), pointer, TRUE);
+	list->priv->grab_keyboard = keyboard;
+	list->priv->grab_pointer = pointer;
+}
+
+static void
+enl_popup_ungrab (ENameSelectorList *list)
+{
+	if (!list->priv->grab_pointer ||
+	    !list->priv->grab_keyboard ||
+	    !gtk_widget_has_grab (GTK_WIDGET (list->priv->popup)))
+		return;
+
+	gtk_device_grab_remove (GTK_WIDGET (list->priv->popup), list->priv->grab_pointer);
+	gtk_device_grab_remove (GTK_WIDGET (list->priv->popup), list->priv->grab_keyboard);
+
+	list->priv->grab_pointer = NULL;
+	list->priv->grab_keyboard = NULL;
+}
+
+static gboolean
+enl_entry_focus_in (ENameSelectorList *list,
+                    GdkEventFocus *event,
+                    gpointer dummy)
+{
+	gint len;
+
+	/* FIXME: Dont select every thing by default- Code is there but still it does */
+	len = strlen (gtk_entry_get_text (GTK_ENTRY (list)));
+	gtk_editable_select_region (GTK_EDITABLE (list), len, -1);
+
+	return TRUE;
+}
+
+static gboolean
+enl_entry_focus_out (ENameSelectorList *list,
+                     GdkEventFocus *event,
+                     gpointer dummy)
+{
+	/* When we lose focus and popup is still present hide it. Dont do it, when we click the popup. Look for grab */
+	if (gtk_widget_get_visible (GTK_WIDGET (list->priv->popup))
+	    && !gtk_widget_has_grab (GTK_WIDGET (list->priv->popup))) {
+		enl_popup_ungrab (list);
+		gtk_widget_hide ((GtkWidget *) list->priv->popup);
+
+		return FALSE;
+	}
+
+	return FALSE;
+}
+
+static gboolean
+enl_popup_button_press (GtkWidget *widget,
+                        GdkEventButton *event,
+                        ENameSelectorList *list)
+{
+	if (!gtk_widget_get_mapped (widget))
+		return FALSE;
+
+	/* if we come here, it's usually time to popdown */
+	gtk_widget_hide ((GtkWidget *) list->priv->popup);
+
+	return TRUE;
+}
+
+static gboolean
+enl_popup_focus_out (GtkWidget *w,
+                     GdkEventFocus *event,
+                     ENameSelectorList *list)
+{
+	/* Just ungrab. We lose focus on button press event */
+	enl_popup_ungrab (list);
+	return TRUE;
+}
+
+static gboolean
+enl_popup_enter_notify (GtkWidget *widget,
+                        GdkEventCrossing *event,
+                        ENameSelectorList *list)
+{
+	if (event->type == GDK_ENTER_NOTIFY && !gtk_widget_has_grab (GTK_WIDGET (list->priv->popup)))
+		enl_popup_grab (list, (GdkEvent *) event);
+
+	return TRUE;
+}
+
+static void
+enl_tree_select_node (ENameSelectorList *list,
+                      gint n)
+{
+	EDestinationStore *store;
+	ENameSelectorEntry *entry;
+	GtkTreeSelection *selection;
+	GtkTreeViewColumn *column;
+	GtkTreeView *tree_view;
+	GtkTreeIter iter;
+	GtkTreePath *path;
+
+	entry = E_NAME_SELECTOR_ENTRY (list);
+	tree_view = GTK_TREE_VIEW (list->priv->tree_view);
+	store = e_name_selector_entry_peek_destination_store (entry);
+	selection = gtk_tree_view_get_selection (tree_view);
+	iter.stamp = e_destination_store_get_stamp (store);
+	iter.user_data = GINT_TO_POINTER (n - 1);
+
+	gtk_tree_selection_unselect_all (selection);
+	gtk_tree_selection_select_iter (selection, &iter);
+
+	column = gtk_tree_view_get_column (tree_view, 0);
+	path = e_destination_store_get_path (GTK_TREE_MODEL (store), &iter);
+	gtk_tree_view_scroll_to_cell (tree_view, path, column, FALSE, 0, 0);
+	gtk_tree_view_set_cursor (tree_view, path, column, FALSE);
+	gtk_widget_grab_focus (GTK_WIDGET (tree_view));
+	/*Fixme: We should grab the focus to the column. How? */
+
+	gtk_tree_path_free (path);
+}
+
+static gboolean
+enl_entry_key_press_event (ENameSelectorList *list,
+                           GdkEventKey *event,
+                           gpointer dummy)
+{
+	ENameSelectorEntry *entry;
+	EDestinationStore *store;
+
+	entry = E_NAME_SELECTOR_ENTRY (list);
+	store = e_name_selector_entry_peek_destination_store (entry);
+
+	if ( (event->state & GDK_CONTROL_MASK)  && (event->keyval == GDK_KEY_Down)) {
+		enl_popup_position (list);
+		gtk_widget_show_all (GTK_WIDGET (list->priv->popup));
+		enl_popup_grab (list, (GdkEvent *) event);
+		list->priv->rows = e_destination_store_get_destination_count (store);
+		enl_popup_size (list);
+		enl_tree_select_node (list, 1);
+		return TRUE;
+	}
+	return FALSE;
+}
+
+static void
+delete_row (GtkTreePath *path,
+            ENameSelectorList *list)
+{
+	ENameSelectorEntry *entry;
+	EDestinationStore *store;
+	GtkTreeIter   iter;
+	gint n, len;
+	GtkTreeSelection *selection;
+
+	entry = E_NAME_SELECTOR_ENTRY (list);
+	store = e_name_selector_entry_peek_destination_store (entry);
+
+	if (!gtk_tree_model_get_iter (GTK_TREE_MODEL (store), &iter, path))
+		return;
+
+	selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (list->priv->tree_view));
+	len = e_destination_store_get_destination_count (store);
+	n = GPOINTER_TO_INT (iter.user_data);
+
+	e_destination_store_remove_destination_nth (store, n);
+
+	/* If the last one is deleted select the last but one or the deleted +1 */
+	if (n == len -1)
+		n -= 1;
+
+	/* We deleted the last entry */
+	if (len == 1) {
+		enl_popup_ungrab (list);
+		if (list->priv->menu)
+			gtk_menu_popdown (GTK_MENU (list->priv->menu));
+		gtk_widget_hide (GTK_WIDGET (list->priv->popup));
+		return;
+	}
+
+	iter.stamp = e_destination_store_get_stamp (store);
+	iter.user_data = GINT_TO_POINTER (n);
+
+	gtk_tree_selection_unselect_all (selection);
+	gtk_tree_selection_select_iter (selection, &iter);
+
+	gtk_tree_path_free (path);
+
+	list->priv->rows = e_destination_store_get_destination_count (store);
+	enl_popup_size (list);
+}
+
+static void
+popup_activate_email (ENameSelectorEntry *name_selector_entry,
+                      GtkWidget *menu_item)
+{
+	EDestination *destination;
+	EContact     *contact;
+	gint          email_num;
+
+	destination = e_name_selector_entry_get_popup_destination (name_selector_entry);
+	if (!destination)
+		return;
+
+	contact = e_destination_get_contact (destination);
+	if (!contact)
+		return;
+
+	email_num = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (menu_item), "order"));
+	e_destination_set_contact (destination, contact, email_num);
+}
+
+static void
+popup_activate_list (EDestination *destination,
+                     GtkWidget *item)
+{
+	gboolean status = gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (item));
+
+	e_destination_set_ignored (destination, !status);
+}
+
+static void
+destination_set_list (GtkWidget *item,
+                      EDestination *destination)
+{
+	EContact *contact;
+	gboolean status = gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (item));
+
+	contact = e_destination_get_contact (destination);
+	if (!contact)
+		return;
+
+	e_destination_set_ignored (destination, !status);
+}
+
+static void
+destination_set_email (GtkWidget *item,
+                       EDestination *destination)
+{
+	gint email_num;
+	EContact *contact;
+
+	if (!gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (item)))
+		return;
+	contact = e_destination_get_contact (destination);
+	if (!contact)
+		return;
+
+	email_num = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (item), "order"));
+	e_destination_set_contact (destination, contact, email_num);
+}
+
+typedef struct {
+	ENameSelectorList *list;
+	GtkTreePath *path;
+}PopupDeleteRowInfo;
+
+static void
+popup_delete_row (GtkWidget *w,
+                  PopupDeleteRowInfo *row_info)
+{
+	delete_row (row_info->path, row_info->list);
+	g_free (row_info);
+}
+
+static void
+menu_deactivate (GtkMenuShell *junk,
+                 ENameSelectorList *list)
+{
+	enl_popup_grab (list, NULL);
+}
+
+static gboolean
+enl_tree_button_press_event (GtkWidget *widget,
+                             GdkEventButton *event,
+                             ENameSelectorList *list)
+{
+	GtkWidget *menu;
+	EDestination *destination;
+	ENameSelectorEntry *entry;
+	EDestinationStore *store;
+	EContact     *contact;
+	GtkWidget    *menu_item;
+	GList        *email_list = NULL, *l;
+	gint          i;
+	gint	      email_num, len;
+	gchar         *delete_label;
+	GSList	     *group = NULL;
+	gboolean      is_list;
+	gboolean      show_menu = FALSE;
+	GtkTreeSelection *selection;
+	GtkTreeView *tree_view;
+	GtkTreePath  *path;
+	PopupDeleteRowInfo *row_info;
+	GtkTreeIter   iter;
+
+	entry = E_NAME_SELECTOR_ENTRY (list);
+	tree_view = GTK_TREE_VIEW (list->priv->tree_view);
+	store = e_name_selector_entry_peek_destination_store (entry);
+
+	if (!gtk_widget_has_grab (GTK_WIDGET (list->priv->popup)))
+		enl_popup_grab (list, (GdkEvent *) event);
+
+	gtk_tree_view_get_dest_row_at_pos (
+		tree_view, event->x, event->y, &path, NULL);
+	selection = gtk_tree_view_get_selection (tree_view);
+	if (!gtk_tree_model_get_iter (GTK_TREE_MODEL (store), &iter, path))
+		return FALSE;
+
+	gtk_tree_selection_unselect_all (selection);
+	gtk_tree_selection_select_iter (selection, &iter);
+
+	if (event->button != 3) {
+		return FALSE;
+	}
+
+	destination = e_destination_store_get_destination (store, &iter);
+
+	if (!destination)
+		return FALSE;
+
+	contact = e_destination_get_contact (destination);
+	if (!contact)
+		return FALSE;
+
+	if (list->priv->menu) {
+		gtk_menu_popdown (GTK_MENU (list->priv->menu));
+	}
+	menu = gtk_menu_new ();
+	g_signal_connect (menu, "deactivate", G_CALLBACK (menu_deactivate), list);
+	list->priv->menu = menu;
+	gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, event->button, gtk_get_current_event_time ());
+
+	email_num = e_destination_get_email_num (destination);
+
+	/* Addresses */
+	is_list = e_contact_get (contact, E_CONTACT_IS_LIST) ? TRUE : FALSE;
+	if (is_list) {
+		const GList *dests = e_destination_list_get_dests (destination);
+		GList *iters;
+		gint length = g_list_length ((GList *) dests);
+
+		for (iters = (GList *) dests; iters; iters = iters->next) {
+			EDestination *dest = (EDestination *) iters->data;
+			const gchar *email = e_destination_get_email (dest);
+
+			if (!email || *email == '\0')
+				continue;
+
+			if (length > 1) {
+				menu_item = gtk_check_menu_item_new_with_label (email);
+				g_signal_connect (
+					menu_item, "toggled",
+					G_CALLBACK (destination_set_list), dest);
+			} else {
+				menu_item = gtk_menu_item_new_with_label (email);
+			}
+
+			gtk_widget_show (menu_item);
+			gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), menu_item);
+			show_menu = TRUE;
+
+			if (length > 1) {
+				gtk_check_menu_item_set_active (
+					GTK_CHECK_MENU_ITEM (menu_item),
+					!e_destination_is_ignored (dest));
+				g_signal_connect_swapped (
+					menu_item, "activate",
+					G_CALLBACK (popup_activate_list), dest);
+			}
+		}
+
+	} else {
+		email_list = e_contact_get (contact, E_CONTACT_EMAIL);
+		len = g_list_length (email_list);
+
+		for (l = email_list, i = 0; l; l = g_list_next (l), i++) {
+			gchar *email = l->data;
+
+			if (!email || *email == '\0')
+				continue;
+
+			if (len > 1) {
+				menu_item = gtk_radio_menu_item_new_with_label (group, email);
+				group = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM (menu_item));
+				g_signal_connect (
+					menu_item, "toggled",
+					G_CALLBACK (destination_set_email),
+					destination);
+			} else {
+				menu_item = gtk_menu_item_new_with_label (email);
+			}
+
+			gtk_widget_show (menu_item);
+			gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), menu_item);
+			show_menu = TRUE;
+			g_object_set_data (G_OBJECT (menu_item), "order", GINT_TO_POINTER (i));
+
+			if (i == email_num && len > 1) {
+				gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (menu_item), TRUE);
+				g_signal_connect_swapped (
+					menu_item, "activate",
+					G_CALLBACK (popup_activate_email),
+					entry);
+			}
+		}
+		g_list_foreach (email_list, (GFunc) g_free, NULL);
+		g_list_free (email_list);
+	}
+
+	/* Separator */
+
+	if (show_menu) {
+		menu_item = gtk_separator_menu_item_new ();
+		gtk_widget_show (menu_item);
+		gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), menu_item);
+	}
+
+	delete_label = g_strdup_printf (_("_Delete %s"), (gchar *) e_contact_get_const (contact, E_CONTACT_FILE_AS));
+	menu_item = gtk_menu_item_new_with_mnemonic (delete_label);
+	g_free (delete_label);
+	gtk_widget_show (menu_item);
+	gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), menu_item);
+
+	row_info = g_new (PopupDeleteRowInfo, 1);
+	row_info->list = list;
+	row_info->path = path;
+
+	g_signal_connect (
+		menu_item, "activate",
+		G_CALLBACK (popup_delete_row), row_info);
+
+	return TRUE;
+
+}
+
+static gboolean
+enl_tree_key_press_event (GtkWidget *w,
+                          GdkEventKey *event,
+                          ENameSelectorList *list)
+{
+	if (event->keyval == GDK_KEY_Escape) {
+		enl_popup_ungrab (list);
+		gtk_widget_hide ( GTK_WIDGET (list->priv->popup));
+		return TRUE;
+	} else if (event->keyval == GDK_KEY_Delete) {
+		GtkTreeSelection *selection;
+		GtkTreeView *tree_view;
+		GList *paths;
+
+		tree_view = GTK_TREE_VIEW (list->priv->tree_view);
+		selection = gtk_tree_view_get_selection (tree_view);
+		paths = gtk_tree_selection_get_selected_rows (selection, NULL);
+		paths = g_list_reverse (paths);
+		g_list_foreach (paths, (GFunc) delete_row, list);
+		g_list_free (paths);
+	} else if (event->keyval != GDK_KEY_Up && event->keyval != GDK_KEY_Down
+		   && event->keyval != GDK_KEY_Shift_R && event->keyval != GDK_KEY_Shift_L
+		   && event->keyval != GDK_KEY_Control_R && event->keyval != GDK_KEY_Control_L) {
+		enl_popup_ungrab (list);
+		gtk_widget_hide ( GTK_WIDGET (list->priv->popup));
+		gtk_widget_event (GTK_WIDGET (list), (GdkEvent *) event);
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+void
+e_name_selector_list_expand_clicked (ENameSelectorList *list)
+{
+	ENameSelectorEntry *entry;
+	EDestinationStore *store;
+
+	entry = E_NAME_SELECTOR_ENTRY (list);
+	store = e_name_selector_entry_peek_destination_store (entry);
+
+	if (!gtk_widget_get_visible (GTK_WIDGET (list->priv->popup))) {
+		enl_popup_position (list);
+		gtk_widget_show_all (GTK_WIDGET (list->priv->popup));
+		enl_popup_grab (list, NULL);
+		list->priv->rows = e_destination_store_get_destination_count (store);
+		enl_popup_size (list);
+		enl_tree_select_node (list, 1);
+	}
+	else {
+		enl_popup_ungrab (list);
+		if (list->priv->menu)
+			gtk_menu_popdown (GTK_MENU (list->priv->menu));
+		gtk_widget_hide (GTK_WIDGET (list->priv->popup));
+	}
+}
+
+static void
+name_selector_list_realize (GtkWidget *widget)
+{
+	ENameSelectorList *list;
+	ENameSelectorEntry *entry;
+	EDestinationStore *store;
+
+	/* Chain up to parent's realize() method. */
+	GTK_WIDGET_CLASS (e_name_selector_list_parent_class)->realize (widget);
+
+	list = E_NAME_SELECTOR_LIST (widget);
+	entry = E_NAME_SELECTOR_ENTRY (widget);
+	store = e_name_selector_entry_peek_destination_store (entry);
+
+	gtk_tree_view_set_model (
+		GTK_TREE_VIEW (list->priv->tree_view), GTK_TREE_MODEL (store));
+}
+
+static void
+e_name_selector_list_class_init (ENameSelectorListClass *class)
+{
+	GtkWidgetClass *widget_class;
+
+	g_type_class_add_private (class, sizeof (ENameSelectorListPrivate));
+
+	widget_class = GTK_WIDGET_CLASS (class);
+	widget_class->realize = name_selector_list_realize;
+}
+
+static void
+e_name_selector_list_init (ENameSelectorList *list)
+{
+	GtkCellRenderer *renderer;
+	GtkWidget *scroll, *popup_frame, *vgrid;
+	GtkTreeSelection *selection;
+	GtkTreeViewColumn *column;
+	ENameSelectorEntry *entry;
+	EDestinationStore *store;
+	GtkEntryCompletion *completion;
+
+	list->priv = E_NAME_SELECTOR_LIST_GET_PRIVATE (list);
+	list->priv->menu = NULL;
+
+	entry = E_NAME_SELECTOR_ENTRY (list);
+	store = e_name_selector_entry_peek_destination_store (entry);
+
+	list->priv->tree_view = gtk_tree_view_new_with_model (GTK_TREE_MODEL (store));
+	gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (list->priv->tree_view), FALSE);
+	gtk_tree_view_set_hover_selection (GTK_TREE_VIEW (list->priv->tree_view), FALSE);
+
+	selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (list->priv->tree_view));
+	gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE);
+	gtk_tree_selection_unselect_all (selection);
+	gtk_tree_view_set_enable_search (GTK_TREE_VIEW (list->priv->tree_view), FALSE);
+
+	completion = gtk_entry_get_completion (GTK_ENTRY (list));
+	gtk_entry_completion_set_inline_completion (completion, TRUE);
+	gtk_entry_completion_set_popup_completion (completion, TRUE);
+
+	renderer = gtk_cell_renderer_text_new ();
+	column = gtk_tree_view_column_new_with_attributes ("Name", renderer, "text", E_DESTINATION_STORE_COLUMN_ADDRESS, NULL);
+	gtk_tree_view_append_column (GTK_TREE_VIEW (list->priv->tree_view), column);
+	gtk_tree_view_column_set_clickable (column, TRUE);
+
+	scroll = gtk_scrolled_window_new (NULL, NULL);
+	gtk_scrolled_window_set_policy (
+		GTK_SCROLLED_WINDOW (scroll),
+		GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
+	gtk_scrolled_window_set_shadow_type (
+		GTK_SCROLLED_WINDOW (scroll), GTK_SHADOW_NONE);
+	gtk_widget_set_size_request (
+		gtk_scrolled_window_get_vscrollbar (
+		GTK_SCROLLED_WINDOW (scroll)), -1, 0);
+	gtk_widget_set_vexpand (scroll, TRUE);
+	gtk_widget_set_valign (scroll, GTK_ALIGN_FILL);
+
+	list->priv->popup =  GTK_WINDOW (gtk_window_new (GTK_WINDOW_POPUP));
+	gtk_window_set_resizable (GTK_WINDOW (list->priv->popup), FALSE);
+
+	popup_frame = gtk_frame_new (NULL);
+	gtk_frame_set_shadow_type (
+		GTK_FRAME (popup_frame), GTK_SHADOW_ETCHED_IN);
+
+	gtk_container_add (GTK_CONTAINER (list->priv->popup), popup_frame);
+
+	vgrid = g_object_new (GTK_TYPE_GRID,
+		"orientation", GTK_ORIENTATION_VERTICAL,
+		"column-homogeneous", FALSE,
+		"row-spacing", 0,
+		NULL);
+	gtk_container_add (GTK_CONTAINER (popup_frame), vgrid);
+
+	gtk_container_add (GTK_CONTAINER (scroll), list->priv->tree_view);
+	gtk_container_add (GTK_CONTAINER (vgrid), scroll);
+
+	g_signal_connect_after (
+		GTK_WIDGET (list), "focus-in-event",
+		G_CALLBACK (enl_entry_focus_in), NULL);
+	g_signal_connect (
+		GTK_WIDGET (list), "focus-out-event",
+		G_CALLBACK (enl_entry_focus_out), NULL);
+	g_signal_connect (
+		GTK_WIDGET (list), "key-press-event",
+		G_CALLBACK (enl_entry_key_press_event), NULL);
+
+	g_signal_connect_after (
+		list->priv->tree_view, "key-press-event",
+		G_CALLBACK (enl_tree_key_press_event), list);
+	g_signal_connect (
+		list->priv->tree_view, "button-press-event",
+		G_CALLBACK (enl_tree_button_press_event), list);
+
+	g_signal_connect (
+		list->priv->popup, "button-press-event",
+		G_CALLBACK (enl_popup_button_press), list);
+	g_signal_connect (
+		list->priv->popup, "focus-out-event",
+		G_CALLBACK (enl_popup_focus_out), list);
+	g_signal_connect (
+		list->priv->popup, "enter-notify-event",
+		G_CALLBACK (enl_popup_enter_notify), list);
+
+}
+
+ENameSelectorList *
+e_name_selector_list_new (ESourceRegistry *registry)
+{
+	g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), NULL);
+
+	return g_object_new (
+		E_TYPE_NAME_SELECTOR_LIST,
+		"registry", registry, NULL);
+}
diff --git a/e-util/e-name-selector-list.h b/e-util/e-name-selector-list.h
new file mode 100644
index 0000000..7b1d11c
--- /dev/null
+++ b/e-util/e-name-selector-list.h
@@ -0,0 +1,82 @@
+/*
+ * Single-line text entry widget for EDestinations.
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Srinivasa Ragavan <sragavan novell com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_NAME_SELECTOR_LIST_H
+#define E_NAME_SELECTOR_LIST_H
+
+#include <gtk/gtk.h>
+#include <libebook/libebook.h>
+
+#include <e-util/e-contact-store.h>
+#include <e-util/e-destination-store.h>
+#include <e-util/e-tree-model-generator.h>
+#include <e-util/e-name-selector-entry.h>
+
+/* Standard GObject macros */
+#define E_TYPE_NAME_SELECTOR_LIST \
+	(e_name_selector_list_get_type ())
+#define E_NAME_SELECTOR_LIST(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_NAME_SELECTOR_LIST, ENameSelectorList))
+#define E_NAME_SELECTOR_LIST_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_NAME_SELECTOR_LIST, ENameSelectorListClass))
+#define E_IS_NAME_SELECTOR_LIST(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_NAME_SELECTOR_LIST))
+#define E_IS_NAME_SELECTOR_LIST_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_NAME_SELECTOR_LIST))
+#define E_NAME_SELECTOR_LIST_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_NAME_SELECTOR_LIST, ENameSelectorListClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ENameSelectorList ENameSelectorList;
+typedef struct _ENameSelectorListClass ENameSelectorListClass;
+typedef struct _ENameSelectorListPrivate ENameSelectorListPrivate;
+
+struct _ENameSelectorList {
+	ENameSelectorEntry parent;
+	ENameSelectorListPrivate *priv;
+};
+
+struct _ENameSelectorListClass {
+	ENameSelectorEntryClass parent_class;
+};
+
+GType		e_name_selector_list_get_type	(void);
+ENameSelectorList *
+		e_name_selector_list_new	(ESourceRegistry *registry);
+void		e_name_selector_list_expand_clicked
+						(ENameSelectorList *list);
+
+G_END_DECLS
+
+#endif /* E_NAME_SELECTOR_LIST_H */
diff --git a/e-util/e-name-selector-model.c b/e-util/e-name-selector-model.c
new file mode 100644
index 0000000..770f514
--- /dev/null
+++ b/e-util/e-name-selector-model.c
@@ -0,0 +1,663 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/* e-name-selector-model.c - Model for contact selection.
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that 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 program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * Authors: Hans Petter Jansson <hpj novell com>
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <string.h>
+#include <glib/gi18n-lib.h>
+#include "e-name-selector-model.h"
+
+#define E_NAME_SELECTOR_MODEL_GET_PRIVATE(obj) \
+	(G_TYPE_INSTANCE_GET_PRIVATE \
+	((obj), E_TYPE_NAME_SELECTOR_MODEL, ENameSelectorModelPrivate))
+
+typedef struct {
+	gchar              *name;
+	gchar              *pretty_name;
+
+	EDestinationStore  *destination_store;
+}
+Section;
+
+struct _ENameSelectorModelPrivate {
+	GArray *sections;
+	EContactStore *contact_store;
+	ETreeModelGenerator *contact_filter;
+	GHashTable *destination_uid_hash;
+};
+
+static gint generate_contact_rows  (EContactStore *contact_store, GtkTreeIter *iter,
+				    ENameSelectorModel *name_selector_model);
+static void override_email_address (EContactStore *contact_store, GtkTreeIter *iter,
+				    gint permutation_n, gint column, GValue *value,
+				    ENameSelectorModel *name_selector_model);
+static void free_section           (ENameSelectorModel *name_selector_model, gint n);
+
+/* ------------------ *
+ * Class/object setup *
+ * ------------------ */
+
+/* Signals */
+
+enum {
+	SECTION_ADDED,
+	SECTION_REMOVED,
+	LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+G_DEFINE_TYPE (ENameSelectorModel, e_name_selector_model, G_TYPE_OBJECT)
+
+static void
+e_name_selector_model_init (ENameSelectorModel *name_selector_model)
+{
+	name_selector_model->priv =
+		E_NAME_SELECTOR_MODEL_GET_PRIVATE (name_selector_model);
+
+	name_selector_model->priv->sections       = g_array_new (FALSE, FALSE, sizeof (Section));
+	name_selector_model->priv->contact_store  = e_contact_store_new ();
+
+	name_selector_model->priv->contact_filter =
+		e_tree_model_generator_new (GTK_TREE_MODEL (name_selector_model->priv->contact_store));
+	e_tree_model_generator_set_generate_func (
+		name_selector_model->priv->contact_filter,
+		(ETreeModelGeneratorGenerateFunc) generate_contact_rows,
+		name_selector_model, NULL);
+	e_tree_model_generator_set_modify_func (name_selector_model->priv->contact_filter,
+						(ETreeModelGeneratorModifyFunc) override_email_address,
+						name_selector_model, NULL);
+
+	g_object_unref (name_selector_model->priv->contact_store);
+
+	name_selector_model->priv->destination_uid_hash = NULL;
+}
+
+static void
+name_selector_model_finalize (GObject *object)
+{
+	ENameSelectorModelPrivate *priv;
+	gint i;
+
+	priv = E_NAME_SELECTOR_MODEL_GET_PRIVATE (object);
+
+	for (i = 0; i < priv->sections->len; i++)
+		free_section (E_NAME_SELECTOR_MODEL (object), i);
+
+	g_array_free (priv->sections, TRUE);
+	g_object_unref (priv->contact_filter);
+
+	if (priv->destination_uid_hash)
+		g_hash_table_destroy (priv->destination_uid_hash);
+
+	/* Chain up to parent's finalize() method. */
+	G_OBJECT_CLASS (e_name_selector_model_parent_class)->finalize (object);
+}
+
+static void
+e_name_selector_model_class_init (ENameSelectorModelClass *class)
+{
+	GObjectClass *object_class;
+
+	g_type_class_add_private (class, sizeof (ENameSelectorModelPrivate));
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->finalize = name_selector_model_finalize;
+
+	signals[SECTION_ADDED] = g_signal_new (
+		"section-added",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ENameSelectorModelClass, section_added),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__STRING,
+		G_TYPE_NONE, 1,
+		G_TYPE_STRING);
+
+	signals[SECTION_REMOVED] = g_signal_new (
+		"section-removed",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ENameSelectorModelClass, section_removed),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__STRING,
+		G_TYPE_NONE, 1,
+		G_TYPE_STRING);
+}
+
+/**
+ * e_name_selector_model_new:
+ *
+ * Creates a new #ENameSelectorModel.
+ *
+ * Returns: A new #ENameSelectorModel.
+ **/
+ENameSelectorModel *
+e_name_selector_model_new (void)
+{
+	return E_NAME_SELECTOR_MODEL (g_object_new (E_TYPE_NAME_SELECTOR_MODEL, NULL));
+}
+
+/* ---------------------------- *
+ * GtkTreeModelFilter filtering *
+ * ---------------------------- */
+
+static void
+deep_free_list (GList *list)
+{
+	GList *l;
+
+	for (l = list; l; l = g_list_next (l))
+		g_free (l->data);
+
+	g_list_free (list);
+}
+
+static gint
+generate_contact_rows (EContactStore *contact_store,
+                       GtkTreeIter *iter,
+                       ENameSelectorModel *name_selector_model)
+{
+	EContact    *contact;
+	const gchar *contact_uid;
+	gint         n_rows, used_rows = 0;
+	gint         i;
+
+	contact = e_contact_store_get_contact (contact_store, iter);
+	g_assert (contact != NULL);
+
+	contact_uid = e_contact_get_const (contact, E_CONTACT_UID);
+	if (!contact_uid)
+		return 0;  /* Can happen with broken databases */
+
+	for (i = 0; i < name_selector_model->priv->sections->len; i++) {
+		Section *section;
+		GList   *destinations;
+		GList   *l;
+
+		section = &g_array_index (name_selector_model->priv->sections, Section, i);
+		destinations = e_destination_store_list_destinations (section->destination_store);
+
+		for (l = destinations; l; l = g_list_next (l)) {
+			EDestination *destination = l->data;
+			const gchar  *destination_uid;
+
+			destination_uid = e_destination_get_contact_uid (destination);
+			if (destination_uid && !strcmp (contact_uid, destination_uid)) {
+				used_rows++;
+			}
+		}
+
+		g_list_free (destinations);
+	}
+
+	if (e_contact_get (contact, E_CONTACT_IS_LIST)) {
+		n_rows = 1 - used_rows;
+	} else {
+		GList    *email_list;
+
+		email_list = e_contact_get (contact, E_CONTACT_EMAIL);
+		n_rows = g_list_length (email_list) - used_rows;
+		deep_free_list (email_list);
+	}
+
+	g_return_val_if_fail (n_rows >= 0, 0);
+
+	return n_rows;
+}
+
+static void
+override_email_address (EContactStore *contact_store,
+                        GtkTreeIter *iter,
+                        gint permutation_n,
+                        gint column,
+                        GValue *value,
+                        ENameSelectorModel *name_selector_model)
+{
+	if (column == E_CONTACT_EMAIL_1) {
+		EContact *contact;
+		GList    *email_list;
+		gchar    *email;
+
+		contact = e_contact_store_get_contact (contact_store, iter);
+		email_list = e_name_selector_model_get_contact_emails_without_used (name_selector_model, contact, TRUE);
+		g_return_if_fail (g_list_length (email_list) <= permutation_n);
+		email = g_strdup (g_list_nth_data (email_list, permutation_n));
+		g_value_set_string (value, email);
+		e_name_selector_model_free_emails_list (email_list);
+	} else {
+		gtk_tree_model_get_value (GTK_TREE_MODEL (contact_store), iter, column, value);
+	}
+}
+
+/* --------------- *
+ * Section helpers *
+ * --------------- */
+
+typedef struct
+{
+	ENameSelectorModel *name_selector_model;
+	GHashTable         *other_hash;
+}
+HashCompare;
+
+static void
+emit_destination_uid_changes_cb (gchar *uid_num,
+                                 gpointer value,
+                                 HashCompare *hash_compare)
+{
+	EContactStore *contact_store = hash_compare->name_selector_model->priv->contact_store;
+
+	if (!hash_compare->other_hash || !g_hash_table_lookup (hash_compare->other_hash, uid_num)) {
+		GtkTreeIter  iter;
+		GtkTreePath *path;
+		gchar *sep;
+
+		sep = strrchr (uid_num, ':');
+		g_return_if_fail (sep != NULL);
+
+		*sep = '\0';
+		if (e_contact_store_find_contact (contact_store, uid_num, &iter)) {
+			*sep = ':';
+
+			path = gtk_tree_model_get_path (GTK_TREE_MODEL (contact_store), &iter);
+			gtk_tree_model_row_changed (GTK_TREE_MODEL (contact_store), path, &iter);
+			gtk_tree_path_free (path);
+		} else {
+			*sep = ':';
+		}
+	}
+}
+
+static void
+destinations_changed (ENameSelectorModel *name_selector_model)
+{
+	GHashTable  *destination_uid_hash_new;
+	GHashTable  *destination_uid_hash_old;
+	HashCompare  hash_compare;
+	gint         i;
+
+	destination_uid_hash_new = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+
+	for (i = 0; i < name_selector_model->priv->sections->len; i++) {
+		Section *section = &g_array_index (name_selector_model->priv->sections, Section, i);
+		GList   *destinations;
+		GList   *l;
+
+		destinations = e_destination_store_list_destinations (section->destination_store);
+
+		for (l = destinations; l; l = g_list_next (l)) {
+			EDestination *destination = l->data;
+			const gchar  *destination_uid;
+
+			destination_uid = e_destination_get_contact_uid (destination);
+			if (destination_uid)
+				g_hash_table_insert (
+					destination_uid_hash_new,
+					g_strdup_printf (
+						"%s:%d", destination_uid,
+						e_destination_get_email_num (destination)),
+					GINT_TO_POINTER (TRUE));
+		}
+
+		g_list_free (destinations);
+	}
+
+	destination_uid_hash_old = name_selector_model->priv->destination_uid_hash;
+	name_selector_model->priv->destination_uid_hash = destination_uid_hash_new;
+
+	hash_compare.name_selector_model = name_selector_model;
+
+	hash_compare.other_hash = destination_uid_hash_old;
+	g_hash_table_foreach (
+		destination_uid_hash_new,
+		(GHFunc) emit_destination_uid_changes_cb,
+		&hash_compare);
+
+	if (destination_uid_hash_old) {
+		hash_compare.other_hash = destination_uid_hash_new;
+		g_hash_table_foreach (
+			destination_uid_hash_old,
+			(GHFunc) emit_destination_uid_changes_cb,
+			&hash_compare);
+
+		g_hash_table_destroy (destination_uid_hash_old);
+	}
+}
+
+static void
+free_section (ENameSelectorModel *name_selector_model,
+              gint n)
+{
+	Section *section;
+
+	g_assert (n >= 0);
+	g_assert (n < name_selector_model->priv->sections->len);
+
+	section = &g_array_index (name_selector_model->priv->sections, Section, n);
+
+	g_signal_handlers_disconnect_matched (
+		section->destination_store, G_SIGNAL_MATCH_DATA,
+		0, 0, NULL, NULL, name_selector_model);
+
+	g_free (section->name);
+	g_free (section->pretty_name);
+	g_object_unref (section->destination_store);
+}
+
+static gint
+find_section_by_name (ENameSelectorModel *name_selector_model,
+                      const gchar *name)
+{
+	gint i;
+
+	g_assert (name != NULL);
+
+	for (i = 0; i < name_selector_model->priv->sections->len; i++) {
+		Section *section = &g_array_index (name_selector_model->priv->sections, Section, i);
+
+		if (!strcmp (name, section->name))
+			return i;
+	}
+
+	return -1;
+}
+
+/* ---------------------- *
+ * ENameSelectorModel API *
+ * ---------------------- */
+
+/**
+ * e_name_selector_model_peek_contact_store:
+ * @name_selector_model: an #ENameSelectorModel
+ *
+ * Gets the #EContactStore associated with @name_selector_model.
+ *
+ * Returns: An #EContactStore.
+ **/
+EContactStore *
+e_name_selector_model_peek_contact_store (ENameSelectorModel *name_selector_model)
+{
+	g_return_val_if_fail (E_IS_NAME_SELECTOR_MODEL (name_selector_model), NULL);
+
+	return name_selector_model->priv->contact_store;
+}
+
+/**
+ * e_name_selector_model_peek_contact_filter:
+ * @name_selector_model: an #ENameSelectorModel
+ *
+ * Gets the #ETreeModelGenerator being used to filter and/or extend the
+ * list of contacts in @name_selector_model's #EContactStore.
+ *
+ * Returns: An #ETreeModelGenerator.
+ **/
+ETreeModelGenerator *
+e_name_selector_model_peek_contact_filter (ENameSelectorModel *name_selector_model)
+{
+	g_return_val_if_fail (E_IS_NAME_SELECTOR_MODEL (name_selector_model), NULL);
+
+	return name_selector_model->priv->contact_filter;
+}
+
+/**
+ * e_name_selector_model_list_sections:
+ * @name_selector_model: an #ENameSelectorModel
+ *
+ * Gets a list of the destination sections in @name_selector_model.
+ *
+ * Returns: A #GList of pointers to strings. The #GList and the
+ * strings belong to the caller, and must be freed when no longer needed.
+ **/
+GList *
+e_name_selector_model_list_sections (ENameSelectorModel *name_selector_model)
+{
+	GList *section_names = NULL;
+	gint   i;
+
+	g_return_val_if_fail (E_IS_NAME_SELECTOR_MODEL (name_selector_model), NULL);
+
+	/* Do this backwards so we can use g_list_prepend () and get correct order */
+	for (i = name_selector_model->priv->sections->len - 1; i >= 0; i--) {
+		Section *section = &g_array_index (name_selector_model->priv->sections, Section, i);
+		gchar   *name;
+
+		name = g_strdup (section->name);
+		section_names = g_list_prepend (section_names, name);
+	}
+
+	return section_names;
+}
+
+/**
+ * e_name_selector_model_add_section:
+ * @name_selector_model: an #ENameSelectorModel
+ * @name: internal name of this section
+ * @pretty_name: user-visible name of this section
+ * @destination_store: the #EDestinationStore to use to store the destinations for this
+ * section, or %NULL if @name_selector_model should create its own.
+ *
+ * Adds a destination section to @name_selector_model.
+ **/
+void
+e_name_selector_model_add_section (ENameSelectorModel *name_selector_model,
+                                   const gchar *name,
+                                   const gchar *pretty_name,
+                                   EDestinationStore *destination_store)
+{
+	Section section;
+
+	g_return_if_fail (E_IS_NAME_SELECTOR_MODEL (name_selector_model));
+	g_return_if_fail (name != NULL);
+	g_return_if_fail (pretty_name != NULL);
+
+	if (find_section_by_name (name_selector_model, name) >= 0) {
+		g_warning ("ENameSelectorModel already has a section called '%s'!", name);
+		return;
+	}
+
+	memset (&section, 0, sizeof (Section));
+
+	section.name              = g_strdup (name);
+	section.pretty_name       = g_strdup (pretty_name);
+
+	if (destination_store)
+		section.destination_store = g_object_ref (destination_store);
+	else
+		section.destination_store = e_destination_store_new ();
+
+	g_signal_connect_swapped (
+		section.destination_store, "row-changed",
+		G_CALLBACK (destinations_changed), name_selector_model);
+	g_signal_connect_swapped (
+		section.destination_store, "row-deleted",
+		G_CALLBACK (destinations_changed), name_selector_model);
+	g_signal_connect_swapped (
+		section.destination_store, "row-inserted",
+		G_CALLBACK (destinations_changed), name_selector_model);
+
+	g_array_append_val (name_selector_model->priv->sections, section);
+
+	destinations_changed (name_selector_model);
+	g_signal_emit (name_selector_model, signals[SECTION_ADDED], 0, name);
+}
+
+/**
+ * e_name_selector_model_remove_section:
+ * @name_selector_model: an #ENameSelectorModel
+ * @name: internal name of the section to remove
+ *
+ * Removes a destination section from @name_selector_model.
+ **/
+void
+e_name_selector_model_remove_section (ENameSelectorModel *name_selector_model,
+                                      const gchar *name)
+{
+	gint     n;
+
+	g_return_if_fail (E_IS_NAME_SELECTOR_MODEL (name_selector_model));
+	g_return_if_fail (name != NULL);
+
+	n = find_section_by_name (name_selector_model, name);
+	if (n < 0) {
+		g_warning ("ENameSelectorModel does not have a section called '%s'!", name);
+		return;
+	}
+
+	free_section (name_selector_model, n);
+	g_array_remove_index_fast (name_selector_model->priv->sections, n);  /* Order doesn't matter */
+
+	destinations_changed (name_selector_model);
+	g_signal_emit (name_selector_model, signals[SECTION_REMOVED], 0, name);
+}
+
+/**
+ * e_name_selector_model_peek_section:
+ * @name_selector_model: an #ENameSelectorModel
+ * @name: internal name of the section to peek
+ * @pretty_name: location in which to store a pointer to the user-visible name of the section,
+ * or %NULL if undesired.
+ * @destination_store: location in which to store a pointer to the #EDestinationStore being used
+ * by the section, or %NULL if undesired
+ *
+ * Gets the parameters for a destination section.
+ **/
+gboolean
+e_name_selector_model_peek_section (ENameSelectorModel *name_selector_model,
+                                    const gchar *name,
+                                    gchar **pretty_name,
+                                    EDestinationStore **destination_store)
+{
+	Section *section;
+	gint     n;
+
+	g_return_val_if_fail (E_IS_NAME_SELECTOR_MODEL (name_selector_model), FALSE);
+	g_return_val_if_fail (name != NULL, FALSE);
+
+	n = find_section_by_name (name_selector_model, name);
+	if (n < 0) {
+		g_warning ("ENameSelectorModel does not have a section called '%s'!", name);
+		return FALSE;
+	}
+
+	section = &g_array_index (name_selector_model->priv->sections, Section, n);
+
+	if (pretty_name)
+		*pretty_name = g_strdup (section->pretty_name);
+	if (destination_store)
+		*destination_store = section->destination_store;
+
+	return TRUE;
+}
+
+/**
+ * e_name_selector_model_get_contact_emails_without_used:
+ * @name_selector_model: an #ENameSelectorModel
+ * @contact: to get emails from
+ * @remove_used: set to %TRUE to remove used from a list; or set to %FALSE to
+ * set used indexes to %NULL and keep them in the returned list
+ *
+ * Returns list of all email from @contact, without all used
+ * in any section. Each item is a string, an email address.
+ * Returned list should be freed with @e_name_selector_model_free_emails_list.
+ *
+ * Since: 2.30
+ **/
+GList *
+e_name_selector_model_get_contact_emails_without_used (ENameSelectorModel *name_selector_model,
+                                                       EContact *contact,
+                                                       gboolean remove_used)
+{
+	GList *email_list;
+	gint emails;
+	gint i;
+	const gchar *contact_uid;
+
+	g_return_val_if_fail (name_selector_model != NULL, NULL);
+	g_return_val_if_fail (E_IS_NAME_SELECTOR_MODEL (name_selector_model), NULL);
+	g_return_val_if_fail (contact != NULL, NULL);
+	g_return_val_if_fail (E_IS_CONTACT (contact), NULL);
+
+	contact_uid = e_contact_get_const (contact, E_CONTACT_UID);
+	g_return_val_if_fail (contact_uid != NULL, NULL);
+
+	email_list = e_contact_get (contact, E_CONTACT_EMAIL);
+	emails = g_list_length (email_list);
+
+	for (i = 0; i < name_selector_model->priv->sections->len; i++) {
+		Section *section;
+		GList   *destinations;
+		GList   *l;
+
+		section = &g_array_index (name_selector_model->priv->sections, Section, i);
+		destinations = e_destination_store_list_destinations (section->destination_store);
+
+		for (l = destinations; l; l = g_list_next (l)) {
+			EDestination *destination = l->data;
+			const gchar  *destination_uid;
+
+			destination_uid = e_destination_get_contact_uid (destination);
+			if (destination_uid && !strcmp (contact_uid, destination_uid)) {
+				gint email_num = e_destination_get_email_num (destination);
+
+				if (email_num < 0 || email_num >= emails) {
+					g_warning ("%s: Destination's email_num %d out of bounds 0..%d", G_STRFUNC, email_num, emails - 1);
+				} else {
+					GList *nth = g_list_nth (email_list, email_num);
+
+					g_return_val_if_fail (nth != NULL, NULL);
+
+					g_free (nth->data);
+					nth->data = NULL;
+				}
+			}
+		}
+
+		g_list_free (destinations);
+	}
+
+	if (remove_used) {
+		/* remove all with data NULL, which are those used already */
+		do {
+			emails = g_list_length (email_list);
+			email_list = g_list_remove (email_list, NULL);
+		} while (g_list_length (email_list) != emails);
+	}
+
+	return email_list;
+}
+
+/**
+ * e_name_selector_model_free_emails_list:
+ * @email_list: list of emails returned from @e_name_selector_model_get_contact_emails_without_used
+ *
+ * Frees a list of emails returned from @e_name_selector_model_get_contact_emails_without_used.
+ *
+ * Since: 2.30
+ **/
+void
+e_name_selector_model_free_emails_list (GList *email_list)
+{
+	deep_free_list (email_list);
+}
diff --git a/e-util/e-name-selector-model.h b/e-util/e-name-selector-model.h
new file mode 100644
index 0000000..ed37f2a
--- /dev/null
+++ b/e-util/e-name-selector-model.h
@@ -0,0 +1,108 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/* e-name-selector-model.h - Model for contact selection.
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that 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 program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * Authors: Hans Petter Jansson <hpj novell com>
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_NAME_SELECTOR_MODEL_H
+#define E_NAME_SELECTOR_MODEL_H
+
+#include <e-util/e-tree-model-generator.h>
+#include <e-util/e-contact-store.h>
+#include <e-util/e-destination-store.h>
+
+/* Standard GObject macros */
+#define E_TYPE_NAME_SELECTOR_MODEL \
+	(e_name_selector_model_get_type ())
+#define E_NAME_SELECTOR_MODEL(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_NAME_SELECTOR_MODEL, ENameSelectorModel))
+#define E_NAME_SELECTOR_MODEL_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_NAME_SELECTOR_MODEL, ENameSelectorModelClass))
+#define E_IS_NAME_SELECTOR_MODEL(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_NAME_SELECTOR_MODEL))
+#define E_IS_NAME_SELECTOR_MODEL_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_NAME_SELECTOR_MODEL))
+#define E_NAME_SELECTOR_MODEL_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_NAME_SELECTOR_MODEL, ENameSelectorModelClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ENameSelectorModel ENameSelectorModel;
+typedef struct _ENameSelectorModelClass ENameSelectorModelClass;
+typedef struct _ENameSelectorModelPrivate ENameSelectorModelPrivate;
+
+struct _ENameSelectorModel {
+	GObject parent;
+	ENameSelectorModelPrivate *priv;
+};
+
+struct _ENameSelectorModelClass {
+	GObjectClass parent_class;
+
+	/* Signals */
+	void		(*section_added)	(gchar *name);
+	void		(*section_removed)	(gchar *name);
+};
+
+GType		e_name_selector_model_get_type	(void);
+ENameSelectorModel *
+		e_name_selector_model_new	(void);
+EContactStore *	e_name_selector_model_peek_contact_store
+						(ENameSelectorModel *name_selector_model);
+ETreeModelGenerator *
+		e_name_selector_model_peek_contact_filter
+						(ENameSelectorModel *name_selector_model);
+
+/* Deep copy of section names; free strings and list when you're done */
+GList *		e_name_selector_model_list_sections
+						(ENameSelectorModel *name_selector_model);
+
+/* pretty_name will be newly allocated, but destination_store must be reffed if you keep it */
+gboolean	e_name_selector_model_peek_section
+						(ENameSelectorModel *name_selector_model,
+						 const gchar *name,
+						 gchar **pretty_name,
+						 EDestinationStore **destination_store);
+void		e_name_selector_model_add_section
+						(ENameSelectorModel *name_selector_model,
+						 const gchar *name,
+						 const gchar *pretty_name,
+						 EDestinationStore *destination_store);
+void		e_name_selector_model_remove_section
+						(ENameSelectorModel *name_selector_model,
+						 const gchar *name);
+GList *		e_name_selector_model_get_contact_emails_without_used
+						(ENameSelectorModel *name_selector_model,
+						 EContact *contact,
+						 gboolean remove_used);
+void		e_name_selector_model_free_emails_list
+						(GList *email_list);
+
+G_END_DECLS
+
+#endif /* E_NAME_SELECTOR_MODEL_H */
diff --git a/e-util/e-name-selector.c b/e-util/e-name-selector.c
new file mode 100644
index 0000000..a94b6ff
--- /dev/null
+++ b/e-util/e-name-selector.c
@@ -0,0 +1,658 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/* e-name-selector.c - Unified context for contact/destination selection UI.
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that 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 program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * Authors: Hans Petter Jansson <hpj novell com>
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <string.h>
+#include <gtk/gtk.h>
+#include <glib/gi18n-lib.h>
+
+#include <libebook/libebook.h>
+
+#include "e-name-selector.h"
+
+#include "e-client-utils.h"
+#include "e-contact-store.h"
+#include "e-destination-store.h"
+
+#define E_NAME_SELECTOR_GET_PRIVATE(obj) \
+	(G_TYPE_INSTANCE_GET_PRIVATE \
+	((obj), E_TYPE_NAME_SELECTOR, ENameSelectorPrivate))
+
+typedef struct {
+	gchar *name;
+	ENameSelectorEntry *entry;
+} Section;
+
+typedef struct {
+	EBookClient *client;
+	guint is_completion_book : 1;
+} SourceBook;
+
+struct _ENameSelectorPrivate {
+	ESourceRegistry *registry;
+	ENameSelectorModel *model;
+	ENameSelectorDialog *dialog;
+
+	GArray *sections;
+
+	gboolean books_loaded;
+	GCancellable *cancellable;
+	GArray *source_books;
+};
+
+enum {
+	PROP_0,
+	PROP_REGISTRY
+};
+
+G_DEFINE_TYPE (ENameSelector, e_name_selector, G_TYPE_OBJECT)
+
+static void
+reset_pointer_cb (gpointer data,
+                  GObject *where_was)
+{
+	ENameSelector *name_selector = data;
+	ENameSelectorPrivate *priv;
+	guint ii;
+
+	g_return_if_fail (E_IS_NAME_SELECTOR (name_selector));
+
+	priv = E_NAME_SELECTOR_GET_PRIVATE (name_selector);
+
+	for (ii = 0; ii < priv->sections->len; ii++) {
+		Section *section;
+
+		section = &g_array_index (priv->sections, Section, ii);
+		if (section->entry == (ENameSelectorEntry *) where_was)
+			section->entry = NULL;
+	}
+}
+
+static void
+name_selector_book_loaded_cb (GObject *source_object,
+                              GAsyncResult *result,
+                              gpointer user_data)
+{
+	ENameSelector *name_selector = user_data;
+	ESource *source = E_SOURCE (source_object);
+	EBookClient *book_client;
+	EClient *client = NULL;
+	GArray *sections;
+	SourceBook source_book;
+	guint ii;
+	GError *error = NULL;
+
+	e_client_utils_open_new_finish (source, result, &client, &error);
+
+	if (error != NULL) {
+		if (!g_error_matches (error, E_CLIENT_ERROR, E_CLIENT_ERROR_REPOSITORY_OFFLINE)
+		    && !g_error_matches (error, E_CLIENT_ERROR, E_CLIENT_ERROR_OFFLINE_UNAVAILABLE)
+		    && !g_error_matches (error, E_CLIENT_ERROR, E_CLIENT_ERROR_CANCELLED)
+		    && !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+			g_warning (
+				"ENameSelector: Could not load \"%s\": %s",
+				e_source_get_display_name (source), error->message);
+		g_error_free (error);
+		goto exit;
+	}
+
+	book_client = E_BOOK_CLIENT (client);
+	g_return_if_fail (E_IS_BOOK_CLIENT (book_client));
+
+	source_book.client = book_client;
+	source_book.is_completion_book = TRUE;
+
+	g_array_append_val (name_selector->priv->source_books, source_book);
+
+	sections = name_selector->priv->sections;
+
+	for (ii = 0; ii < sections->len; ii++) {
+		EContactStore *store;
+		Section *section;
+
+		section = &g_array_index (sections, Section, ii);
+		if (section->entry == NULL)
+			continue;
+
+		store = e_name_selector_entry_peek_contact_store (
+			section->entry);
+		if (store != NULL)
+			e_contact_store_add_client (store, book_client);
+	}
+
+ exit:
+	g_object_unref (name_selector);
+}
+
+/**
+ * e_name_selector_load_books:
+ * @name_selector: an #ENameSelector
+ *
+ * Loads address books available for the @name_selector.
+ * This can be called only once and it can be cancelled
+ * by e_name_selector_cancel_loading().
+ *
+ * Since: 3.2
+ **/
+void
+e_name_selector_load_books (ENameSelector *name_selector)
+{
+	ESourceRegistry *registry;
+	GList *list, *iter;
+	const gchar *extension_name;
+
+	g_return_if_fail (E_IS_NAME_SELECTOR (name_selector));
+
+	extension_name = E_SOURCE_EXTENSION_ADDRESS_BOOK;
+	registry = e_name_selector_get_registry (name_selector);
+	list = e_source_registry_list_sources (registry, extension_name);
+
+	for (iter = list; iter != NULL; iter = g_list_next (iter)) {
+		ESource *source = E_SOURCE (iter->data);
+		ESourceAutocomplete *extension;
+		const gchar *extension_name;
+
+		extension_name = E_SOURCE_EXTENSION_AUTOCOMPLETE;
+		extension = e_source_get_extension (source, extension_name);
+
+		/* Skip disabled address books. */
+		if (!e_source_registry_check_enabled (registry, source))
+			continue;
+
+		/* Only load address books with autocomplete enabled,
+		 * so as to avoid unnecessary authentication prompts. */
+		if (!e_source_autocomplete_get_include_me (extension))
+			continue;
+
+		e_client_utils_open_new (
+			source, E_CLIENT_SOURCE_TYPE_CONTACTS,
+			TRUE, name_selector->priv->cancellable,
+			name_selector_book_loaded_cb,
+			g_object_ref (name_selector));
+	}
+
+	g_list_free_full (list, (GDestroyNotify) g_object_unref);
+}
+
+/**
+ * e_name_selector_cancel_loading:
+ * @name_selector: an #ENameSelector
+ *
+ * Cancels any pending address book load operations. This might be called
+ * before an owner unrefs this @name_selector.
+ *
+ * Since: 3.2
+ **/
+void
+e_name_selector_cancel_loading (ENameSelector *name_selector)
+{
+	g_return_if_fail (E_IS_NAME_SELECTOR (name_selector));
+	g_return_if_fail (name_selector->priv->cancellable != NULL);
+
+	g_cancellable_cancel (name_selector->priv->cancellable);
+}
+
+static void
+name_selector_set_registry (ENameSelector *name_selector,
+                            ESourceRegistry *registry)
+{
+	g_return_if_fail (E_IS_SOURCE_REGISTRY (registry));
+	g_return_if_fail (name_selector->priv->registry == NULL);
+
+	name_selector->priv->registry = g_object_ref (registry);
+}
+
+static void
+name_selector_set_property (GObject *object,
+                            guint property_id,
+                            const GValue *value,
+                            GParamSpec *pspec)
+{
+	switch (property_id) {
+		case PROP_REGISTRY:
+			name_selector_set_registry (
+				E_NAME_SELECTOR (object),
+				g_value_get_object (value));
+			return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+name_selector_get_property (GObject *object,
+                            guint property_id,
+                            GValue *value,
+                            GParamSpec *pspec)
+{
+	switch (property_id) {
+		case PROP_REGISTRY:
+			g_value_set_object (
+				value,
+				e_name_selector_get_registry (
+				E_NAME_SELECTOR (object)));
+			return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+name_selector_dispose (GObject *object)
+{
+	ENameSelectorPrivate *priv;
+	guint ii;
+
+	priv = E_NAME_SELECTOR_GET_PRIVATE (object);
+
+	if (priv->cancellable) {
+		g_cancellable_cancel (priv->cancellable);
+		g_object_unref (priv->cancellable);
+		priv->cancellable = NULL;
+	}
+
+	for (ii = 0; ii < priv->source_books->len; ii++) {
+		SourceBook *source_book;
+
+		source_book = &g_array_index (
+			priv->source_books, SourceBook, ii);
+		if (source_book->client != NULL)
+			g_object_unref (source_book->client);
+	}
+
+	for (ii = 0; ii < priv->sections->len; ii++) {
+		Section *section;
+
+		section = &g_array_index (priv->sections, Section, ii);
+		if (section->entry)
+			g_object_weak_unref (
+				G_OBJECT (section->entry),
+				reset_pointer_cb, object);
+		g_free (section->name);
+	}
+
+	g_array_set_size (priv->source_books, 0);
+	g_array_set_size (priv->sections, 0);
+
+	/* Chain up to parent's dispose() method. */
+	G_OBJECT_CLASS (e_name_selector_parent_class)->dispose (object);
+}
+
+static void
+name_selector_finalize (GObject *object)
+{
+	ENameSelectorPrivate *priv;
+
+	priv = E_NAME_SELECTOR_GET_PRIVATE (object);
+
+	g_array_free (priv->source_books, TRUE);
+	g_array_free (priv->sections, TRUE);
+
+	/* Chain up to parent's finalize() method. */
+	G_OBJECT_CLASS (e_name_selector_parent_class)->finalize (object);
+}
+
+static void
+e_name_selector_class_init (ENameSelectorClass *class)
+{
+	GObjectClass *object_class;
+
+	g_type_class_add_private (class, sizeof (ENameSelectorPrivate));
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->set_property = name_selector_set_property;
+	object_class->get_property = name_selector_get_property;
+	object_class->dispose = name_selector_dispose;
+	object_class->finalize = name_selector_finalize;
+
+	g_object_class_install_property (
+		object_class,
+		PROP_REGISTRY,
+		g_param_spec_object (
+			"registry",
+			"Registry",
+			"Data source registry",
+			E_TYPE_SOURCE_REGISTRY,
+			G_PARAM_READWRITE |
+			G_PARAM_CONSTRUCT_ONLY |
+			G_PARAM_STATIC_STRINGS));
+}
+
+static void
+e_name_selector_init (ENameSelector *name_selector)
+{
+	GArray *sections;
+	GArray *source_books;
+
+	sections = g_array_new (FALSE, FALSE, sizeof (Section));
+	source_books = g_array_new (FALSE, FALSE, sizeof (SourceBook));
+
+	name_selector->priv = E_NAME_SELECTOR_GET_PRIVATE (name_selector);
+	name_selector->priv->sections = sections;
+	name_selector->priv->model = e_name_selector_model_new ();
+	name_selector->priv->source_books = source_books;
+	name_selector->priv->cancellable = g_cancellable_new ();
+	name_selector->priv->books_loaded = FALSE;
+}
+
+/**
+ * e_name_selector_new:
+ * @registry: an #ESourceRegistry
+ *
+ * Creates a new #ENameSelector.
+ *
+ * Returns: A new #ENameSelector.
+ **/
+ENameSelector *
+e_name_selector_new (ESourceRegistry *registry)
+{
+	g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), NULL);
+
+	return g_object_new (
+		E_TYPE_NAME_SELECTOR,
+		"registry", registry, NULL);
+}
+
+/**
+ * e_name_selector_get_registry:
+ * @name_selector: an #ENameSelector
+ *
+ * Returns the #ESourceRegistry passed to e_name_selector_new().
+ *
+ * Returns: the #ESourceRegistry
+ *
+ * Since: 3.6
+ **/
+ESourceRegistry *
+e_name_selector_get_registry (ENameSelector *name_selector)
+{
+	g_return_val_if_fail (E_IS_NAME_SELECTOR (name_selector), NULL);
+
+	return name_selector->priv->registry;
+}
+
+/* ------- *
+ * Helpers *
+ * ------- */
+
+static gint
+add_section (ENameSelector *name_selector,
+             const gchar *name)
+{
+	GArray *array;
+	Section section;
+
+	g_assert (name != NULL);
+
+	memset (&section, 0, sizeof (Section));
+	section.name = g_strdup (name);
+
+	array = name_selector->priv->sections;
+	g_array_append_val (array, section);
+	return array->len - 1;
+}
+
+static gint
+find_section_by_name (ENameSelector *name_selector,
+                      const gchar *name)
+{
+	GArray *array;
+	gint i;
+
+	g_assert (name != NULL);
+
+	array = name_selector->priv->sections;
+
+	for (i = 0; i < array->len; i++) {
+		Section *section = &g_array_index (array, Section, i);
+
+		if (!strcmp (name, section->name))
+			return i;
+	}
+
+	return -1;
+}
+
+/* ----------------- *
+ * ENameSelector API *
+ * ----------------- */
+
+/**
+ * e_name_selector_peek_model:
+ * @name_selector: an #ENameSelector
+ *
+ * Gets the #ENameSelectorModel used by @name_selector.
+ *
+ * Returns: The #ENameSelectorModel used by @name_selector.
+ **/
+ENameSelectorModel *
+e_name_selector_peek_model (ENameSelector *name_selector)
+{
+	g_return_val_if_fail (E_IS_NAME_SELECTOR (name_selector), NULL);
+
+	return name_selector->priv->model;
+}
+
+/**
+ * e_name_selector_peek_dialog:
+ * @name_selector: an #ENameSelector
+ *
+ * Gets the #ENameSelectorDialog used by @name_selector.
+ *
+ * Returns: The #ENameSelectorDialog used by @name_selector.
+ **/
+ENameSelectorDialog *
+e_name_selector_peek_dialog (ENameSelector *name_selector)
+{
+	g_return_val_if_fail (E_IS_NAME_SELECTOR (name_selector), NULL);
+
+	if (name_selector->priv->dialog == NULL) {
+		ESourceRegistry *registry;
+		ENameSelectorDialog *dialog;
+		ENameSelectorModel *model;
+
+		registry = e_name_selector_get_registry (name_selector);
+		dialog = e_name_selector_dialog_new (registry);
+		name_selector->priv->dialog = dialog;
+
+		model = e_name_selector_peek_model (name_selector);
+		e_name_selector_dialog_set_model (dialog, model);
+
+		g_signal_connect (
+			dialog, "delete-event",
+			G_CALLBACK (gtk_widget_hide_on_delete), name_selector);
+	}
+
+	return name_selector->priv->dialog;
+}
+
+/**
+ * e_name_selector_show_dialog:
+ * @name_selector: an #ENameSelector
+ * @for_transient_widget: a widget parent or %NULL
+ *
+ * Shows the associated dialog, and sets the transient parent to the
+ * GtkWindow top-level of "for_transient_widget if set (it should be)
+ *
+ * Since: 2.32
+ **/
+void
+e_name_selector_show_dialog (ENameSelector *name_selector,
+                             GtkWidget *for_transient_widget)
+{
+	GtkWindow *top = NULL;
+	ENameSelectorDialog *dialog;
+
+	g_return_if_fail (E_IS_NAME_SELECTOR (name_selector));
+
+	dialog = e_name_selector_peek_dialog (name_selector);
+	if (for_transient_widget)
+		top = GTK_WINDOW (gtk_widget_get_toplevel (for_transient_widget));
+	if (top)
+		gtk_window_set_transient_for (GTK_WINDOW (dialog), top);
+
+	gtk_widget_show (GTK_WIDGET (dialog));
+}
+
+/**
+ * e_name_selector_peek_section_entry:
+ * @name_selector: an #ENameSelector
+ * @name: the name of the section to peek
+ *
+ * Gets the #ENameSelectorEntry for the section specified by @name.
+ *
+ * Returns: The #ENameSelectorEntry for the named section, or %NULL if it
+ * doesn't exist in the #ENameSelectorModel.
+ **/
+ENameSelectorEntry *
+e_name_selector_peek_section_entry (ENameSelector *name_selector,
+                                    const gchar *name)
+{
+	ENameSelectorPrivate *priv;
+	ENameSelectorModel *model;
+	EDestinationStore *destination_store;
+	Section *section;
+	gint     n;
+
+	g_return_val_if_fail (E_IS_NAME_SELECTOR (name_selector), NULL);
+	g_return_val_if_fail (name != NULL, NULL);
+
+	priv = E_NAME_SELECTOR_GET_PRIVATE (name_selector);
+	model = e_name_selector_peek_model (name_selector);
+
+	if (!e_name_selector_model_peek_section (
+		model, name, NULL, &destination_store))
+		return NULL;
+
+	n = find_section_by_name (name_selector, name);
+	if (n < 0)
+		n = add_section (name_selector, name);
+
+	section = &g_array_index (name_selector->priv->sections, Section, n);
+
+	if (!section->entry) {
+		ESourceRegistry *registry;
+		EContactStore *contact_store;
+		gchar         *text;
+		gint           i;
+
+		registry = e_name_selector_get_registry (name_selector);
+		section->entry = e_name_selector_entry_new (registry);
+		g_object_weak_ref (G_OBJECT (section->entry), reset_pointer_cb, name_selector);
+		if (pango_parse_markup (name, -1, '_', NULL,
+					&text, NULL, NULL))  {
+			atk_object_set_name (gtk_widget_get_accessible (GTK_WIDGET (section->entry)), text);
+			g_free (text);
+		}
+		e_name_selector_entry_set_destination_store (section->entry, destination_store);
+
+		/* Create a contact store for the entry and assign our already-open books to it */
+
+		contact_store = e_contact_store_new ();
+
+		for (i = 0; i < priv->source_books->len; i++) {
+			SourceBook *source_book = &g_array_index (priv->source_books, SourceBook, i);
+
+			if (source_book->is_completion_book && source_book->client)
+				e_contact_store_add_client (contact_store, source_book->client);
+		}
+
+		e_name_selector_entry_set_contact_store (section->entry, contact_store);
+		g_object_unref (contact_store);
+	}
+
+	return section->entry;
+}
+
+/**
+ * e_name_selector_peek_section_list:
+ * @name_selector: an #ENameSelector
+ * @name: the name of the section to peek
+ *
+ * Gets the #ENameSelectorList for the section specified by @name.
+ *
+ * Returns: The #ENameSelectorList for the named section, or %NULL if it
+ * doesn't exist in the #ENameSelectorModel.
+ **/
+
+ENameSelectorList *
+e_name_selector_peek_section_list (ENameSelector *name_selector,
+                                   const gchar *name)
+{
+	ENameSelectorPrivate *priv;
+	ENameSelectorModel *model;
+	EDestinationStore *destination_store;
+	Section *section;
+	gint     n;
+
+	g_return_val_if_fail (E_IS_NAME_SELECTOR (name_selector), NULL);
+	g_return_val_if_fail (name != NULL, NULL);
+
+	priv = E_NAME_SELECTOR_GET_PRIVATE (name_selector);
+	model = e_name_selector_peek_model (name_selector);
+
+	if (!e_name_selector_model_peek_section (
+		model, name, NULL, &destination_store))
+		return NULL;
+
+	n = find_section_by_name (name_selector, name);
+	if (n < 0)
+		n = add_section (name_selector, name);
+
+	section = &g_array_index (name_selector->priv->sections, Section, n);
+
+	if (!section->entry) {
+		EContactStore *contact_store;
+		ESourceRegistry *registry;
+		gchar         *text;
+		gint           i;
+
+		registry = name_selector->priv->registry;
+		section->entry = (ENameSelectorEntry *)
+			e_name_selector_list_new (registry);
+		g_object_weak_ref (G_OBJECT (section->entry), reset_pointer_cb, name_selector);
+		if (pango_parse_markup (name, -1, '_', NULL,
+					&text, NULL, NULL))  {
+			atk_object_set_name (gtk_widget_get_accessible (GTK_WIDGET (section->entry)), text);
+			g_free (text);
+		}
+		e_name_selector_entry_set_destination_store (section->entry, destination_store);
+
+		/* Create a contact store for the entry and assign our already-open books to it */
+
+		contact_store = e_contact_store_new ();
+		for (i = 0; i < priv->source_books->len; i++) {
+			SourceBook *source_book = &g_array_index (priv->source_books, SourceBook, i);
+
+			if (source_book->is_completion_book && source_book->client)
+				e_contact_store_add_client (contact_store, source_book->client);
+		}
+
+		e_name_selector_entry_set_contact_store (section->entry, contact_store);
+		g_object_unref (contact_store);
+	}
+
+	return (ENameSelectorList *) section->entry;
+}
diff --git a/e-util/e-name-selector.h b/e-util/e-name-selector.h
new file mode 100644
index 0000000..1049699
--- /dev/null
+++ b/e-util/e-name-selector.h
@@ -0,0 +1,94 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/* e-name-selector.h - Unified context for contact/destination selection UI.
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that 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 program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * Authors: Hans Petter Jansson <hpj novell com>
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_NAME_SELECTOR_H
+#define E_NAME_SELECTOR_H
+
+#include <libedataserver/libedataserver.h>
+
+#include <e-util/e-name-selector-model.h>
+#include <e-util/e-name-selector-dialog.h>
+#include <e-util/e-name-selector-entry.h>
+#include <e-util/e-name-selector-list.h>
+
+/* Standard GObject macros */
+#define E_TYPE_NAME_SELECTOR \
+	(e_name_selector_get_type ())
+#define E_NAME_SELECTOR(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_NAME_SELECTOR, ENameSelector))
+#define E_NAME_SELECTOR_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_NAME_SELECTOR, ENameSelectorClass))
+#define E_IS_NAME_SELECTOR(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_NAME_SELECTOR))
+#define E_IS_NAME_SELECTOR_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_NAME_SELECTOR))
+#define E_NAME_SELECTOR_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_NAME_SELECTOR, ENameSelectorClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ENameSelector ENameSelector;
+typedef struct _ENameSelectorClass ENameSelectorClass;
+typedef struct _ENameSelectorPrivate ENameSelectorPrivate;
+
+struct _ENameSelector {
+	GObject parent;
+	ENameSelectorPrivate *priv;
+};
+
+struct _ENameSelectorClass {
+	GObjectClass parent_class;
+};
+
+GType		e_name_selector_get_type	(void);
+ENameSelector *	e_name_selector_new		(ESourceRegistry *registry);
+ESourceRegistry *
+		e_name_selector_get_registry	(ENameSelector *name_selector);
+ENameSelectorModel *
+		e_name_selector_peek_model	(ENameSelector *name_selector);
+ENameSelectorDialog *
+		e_name_selector_peek_dialog	(ENameSelector *name_selector);
+ENameSelectorEntry *
+		e_name_selector_peek_section_entry
+						(ENameSelector *name_selector,
+						 const gchar *name);
+ENameSelectorList *
+		e_name_selector_peek_section_list
+						(ENameSelector *name_selector,
+						 const gchar *name);
+void		e_name_selector_show_dialog	(ENameSelector *name_selector,
+						 GtkWidget *for_transient_widget);
+void		e_name_selector_load_books	(ENameSelector *name_selector);
+void		e_name_selector_cancel_loading	(ENameSelector *name_selector);
+
+G_END_DECLS
+
+#endif /* E_NAME_SELECTOR_H */
diff --git a/widgets/misc/e-online-button.c b/e-util/e-online-button.c
similarity index 100%
rename from widgets/misc/e-online-button.c
rename to e-util/e-online-button.c
diff --git a/e-util/e-online-button.h b/e-util/e-online-button.h
new file mode 100644
index 0000000..073489f
--- /dev/null
+++ b/e-util/e-online-button.h
@@ -0,0 +1,69 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_ONLINE_BUTTON_H
+#define E_ONLINE_BUTTON_H
+
+#include <gtk/gtk.h>
+
+/* Standard GObject macros */
+#define E_TYPE_ONLINE_BUTTON \
+	(e_online_button_get_type ())
+#define E_ONLINE_BUTTON(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_ONLINE_BUTTON, EOnlineButton))
+#define E_ONLINE_BUTTON_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_ONLINE_BUTTON, EOnlineButtonClass))
+#define E_IS_ONLINE_BUTTON(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_ONLINE_BUTTON))
+#define E_IS_ONLINE_BUTTON_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_ONLINE_BUTTON))
+#define E_ONLINE_BUTTON_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_ONLINE_BUTTON, EOnlineButtonClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EOnlineButton EOnlineButton;
+typedef struct _EOnlineButtonClass EOnlineButtonClass;
+typedef struct _EOnlineButtonPrivate EOnlineButtonPrivate;
+
+struct _EOnlineButton {
+	GtkButton parent;
+	EOnlineButtonPrivate *priv;
+};
+
+struct _EOnlineButtonClass {
+	GtkButtonClass parent_class;
+};
+
+GType		e_online_button_get_type	(void);
+GtkWidget *	e_online_button_new		(void);
+gboolean	e_online_button_get_online	(EOnlineButton *button);
+void		e_online_button_set_online	(EOnlineButton *button,
+						 gboolean online);
+
+G_END_DECLS
+
+#endif /* E_ONLINE_BUTTON_H */
diff --git a/widgets/misc/e-paned.c b/e-util/e-paned.c
similarity index 100%
rename from widgets/misc/e-paned.c
rename to e-util/e-paned.c
diff --git a/e-util/e-paned.h b/e-util/e-paned.h
new file mode 100644
index 0000000..79fa3dd
--- /dev/null
+++ b/e-util/e-paned.h
@@ -0,0 +1,82 @@
+/*
+ * e-paned.h
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_PANED_H
+#define E_PANED_H
+
+#include <gtk/gtk.h>
+
+/* Standard GObject macros */
+#define E_TYPE_PANED \
+	(e_paned_get_type ())
+#define E_PANED(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_PANED, EPaned))
+#define E_PANED_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_PANED, EPanedClass))
+#define E_IS_PANED(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_PANED))
+#define E_IS_PANED_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_PANED))
+#define E_PANED_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_PANED, EPanedClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EPaned EPaned;
+typedef struct _EPanedClass EPanedClass;
+typedef struct _EPanedPrivate EPanedPrivate;
+
+struct _EPaned {
+	GtkPaned parent;
+	EPanedPrivate *priv;
+};
+
+struct _EPanedClass {
+	GtkPanedClass parent_class;
+};
+
+GType		e_paned_get_type		(void);
+GtkWidget *	e_paned_new			(GtkOrientation orientation);
+gint		e_paned_get_hposition		(EPaned *paned);
+void		e_paned_set_hposition		(EPaned *paned,
+						 gint hposition);
+gint		e_paned_get_vposition		(EPaned *paned);
+void		e_paned_set_vposition		(EPaned *paned,
+						 gint vposition);
+gdouble		e_paned_get_proportion		(EPaned *paned);
+void		e_paned_set_proportion		(EPaned *paned,
+						 gdouble proportion);
+gboolean	e_paned_get_fixed_resize	(EPaned *paned);
+void		e_paned_set_fixed_resize	(EPaned *paned,
+						 gboolean fixed_resize);
+
+G_END_DECLS
+
+#endif /* E_PANED_H */
diff --git a/e-util/e-passwords-win32.c b/e-util/e-passwords-win32.c
new file mode 100644
index 0000000..51c0cb2
--- /dev/null
+++ b/e-util/e-passwords-win32.c
@@ -0,0 +1,1064 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * e-passwords.c
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that 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 program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
+ * USA.
+ */
+
+/*
+ * This looks a lot more complicated than it is, and than you'd think
+ * it would need to be.  There is however, method to the madness.
+ *
+ * The code most cope with being called from any thread at any time,
+ * recursively from the main thread, and then serialising every
+ * request so that sane and correct values are always returned, and
+ * duplicate requests are never made.
+ *
+ * To this end, every call is marshalled and queued and a dispatch
+ * method invoked until that request is satisfied.  If mainloop
+ * recursion occurs, then the sub-call will necessarily return out of
+ * order, but will not be processed out of order.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+#include <gtk/gtk.h>
+#include <glib/gi18n-lib.h>
+
+#include "e-passwords.h"
+#include "libedataserver/e-data-server-util.h"
+#include "libedataserver/e-flag.h"
+#include "libedataserver/e-url.h"
+
+#define d(x)
+
+typedef struct _EPassMsg EPassMsg;
+
+struct _EPassMsg {
+	void (*dispatch) (EPassMsg *);
+	EFlag *done;
+
+	/* input */
+	GtkWindow *parent;
+	const gchar *component;
+	const gchar *key;
+	const gchar *title;
+	const gchar *prompt;
+	const gchar *oldpass;
+	guint32 flags;
+
+	/* output */
+	gboolean *remember;
+	gchar *password;
+	GError *error;
+
+	/* work variables */
+	GtkWidget *entry;
+	GtkWidget *check;
+	guint ismain:1;
+	guint noreply:1;	/* supress replies; when calling
+				 * dispatch functions from others */
+};
+
+G_LOCK_DEFINE_STATIC (passwords);
+static GThread *main_thread = NULL;
+static GHashTable *password_cache = NULL;
+static GtkDialog *password_dialog = NULL;
+static GQueue message_queue = G_QUEUE_INIT;
+static gint idle_id;
+static gint ep_online_state = TRUE;
+
+#define KEY_FILE_GROUP_PREFIX "Passwords-"
+static GKeyFile *key_file = NULL;
+
+static gboolean
+check_key_file (const gchar *funcname)
+{
+	if (!key_file)
+		g_message ("%s: Failed to create key file!", funcname);
+
+	return key_file != NULL;
+}
+
+static gchar *
+ep_key_file_get_filename (void)
+{
+	return g_build_filename (
+		e_get_user_config_dir (), "credentials", "Passwords", NULL);
+}
+
+static gchar *
+ep_key_file_get_group (const gchar *component)
+{
+	return g_strconcat (KEY_FILE_GROUP_PREFIX, component, NULL);
+}
+
+static gchar *
+ep_key_file_normalize_key (const gchar *key)
+{
+	/* XXX Previous code converted all slashes and equal signs in the
+	 * key to underscores for use with "gnome-config" functions.  While
+	 * it may not be necessary to convert slashes for use with GKeyFile,
+	 * we continue to do the same for backward-compatibility. */
+
+	gchar *normalized_key, *cp;
+
+	normalized_key = g_strdup (key);
+	for (cp = normalized_key; *cp != '\0'; cp++)
+		if (*cp == '/' || *cp == '=')
+			*cp = '_';
+
+	return normalized_key;
+}
+
+static void
+ep_key_file_load (void)
+{
+	gchar *filename;
+	GError *error = NULL;
+
+	if (!check_key_file (G_STRFUNC))
+		return;
+
+	filename = ep_key_file_get_filename ();
+
+	if (!g_file_test (filename, G_FILE_TEST_EXISTS))
+		goto exit;
+
+	g_key_file_load_from_file (
+		key_file, filename, G_KEY_FILE_KEEP_COMMENTS |
+		G_KEY_FILE_KEEP_TRANSLATIONS, &error);
+
+	if (error != NULL) {
+		g_warning ("%s: %s", filename, error->message);
+		g_error_free (error);
+	}
+
+exit:
+	g_free (filename);
+}
+
+static void
+ep_key_file_save (void)
+{
+	gchar *contents;
+	gchar *filename;
+	gchar *pathname;
+	gsize length = 0;
+	GError *error = NULL;
+
+	if (!check_key_file (G_STRFUNC))
+		return;
+
+	filename = ep_key_file_get_filename ();
+	contents = g_key_file_to_data (key_file, &length, &error);
+	pathname = g_path_get_dirname (filename);
+
+	if (!error) {
+		g_mkdir_with_parents (pathname, 0700);
+		g_file_set_contents (filename, contents, length, &error);
+	}
+
+	g_free (pathname);
+
+	if (error != NULL) {
+		g_warning ("%s: %s", filename, error->message);
+		g_error_free (error);
+	}
+
+	g_free (contents);
+	g_free (filename);
+}
+
+static gchar *
+ep_password_encode (const gchar *password)
+{
+	/* XXX The previous Base64 encoding function did not encode the
+	 * password's trailing nul byte.  This makes decoding the Base64
+	 * string into a nul-terminated password more difficult, but we
+	 * continue to do it this way for backward-compatibility. */
+
+	gsize length = strlen (password);
+	return g_base64_encode ((const guchar *) password, length);
+}
+
+static gchar *
+ep_password_decode (const gchar *encoded_password)
+{
+	/* XXX The previous Base64 encoding function did not encode the
+	 * password's trailing nul byte, so we have to append a nul byte
+	 * to the decoded data to make it a nul-terminated string. */
+
+	gchar *password;
+	gsize length = 0;
+
+	password = (gchar *) g_base64_decode (encoded_password, &length);
+	password = g_realloc (password, length + 1);
+	password[length] = '\0';
+
+	return password;
+}
+
+static gboolean
+ep_idle_dispatch (gpointer data)
+{
+	EPassMsg *msg;
+
+	/* As soon as a password window is up we stop; it will
+	   re-invoke us when it has been closed down */
+	G_LOCK (passwords);
+	while (password_dialog == NULL && (msg = g_queue_pop_head (&message_queue)) != NULL) {
+		G_UNLOCK (passwords);
+
+		msg->dispatch (msg);
+
+		G_LOCK (passwords);
+	}
+
+	idle_id = 0;
+	G_UNLOCK (passwords);
+
+	return FALSE;
+}
+
+static EPassMsg *
+ep_msg_new (void (*dispatch) (EPassMsg *))
+{
+	EPassMsg *msg;
+
+	e_passwords_init ();
+
+	msg = g_malloc0 (sizeof (*msg));
+	msg->dispatch = dispatch;
+	msg->done = e_flag_new ();
+	msg->ismain = (g_thread_self () == main_thread);
+
+	return msg;
+}
+
+static void
+ep_msg_free (EPassMsg *msg)
+{
+	/* XXX We really should be passing this back to the caller, but
+	 *     doing so will require breaking the password API. */
+	if (msg->error != NULL) {
+		g_warning ("%s", msg->error->message);
+		g_error_free (msg->error);
+	}
+
+	e_flag_free (msg->done);
+	g_free (msg->password);
+	g_free (msg);
+}
+
+static void
+ep_msg_send (EPassMsg *msg)
+{
+	gint needidle = 0;
+
+	G_LOCK (passwords);
+	g_queue_push_tail (&message_queue, msg);
+	if (!idle_id) {
+		if (!msg->ismain)
+			idle_id = g_idle_add (ep_idle_dispatch, NULL);
+		else
+			needidle = 1;
+	}
+	G_UNLOCK (passwords);
+
+	if (msg->ismain) {
+		if (needidle)
+			ep_idle_dispatch (NULL);
+		while (!e_flag_is_set (msg->done))
+			g_main_context_iteration (NULL, TRUE);
+	} else
+		e_flag_wait (msg->done);
+}
+
+/* the functions that actually do the work */
+
+static void
+ep_clear_passwords_keyfile (EPassMsg *msg)
+{
+	gchar *group;
+	GError *error = NULL;
+
+	if (!check_key_file (G_STRFUNC))
+		return;
+
+	group = ep_key_file_get_group (msg->component);
+
+	if (g_key_file_remove_group (key_file, group, &error))
+		ep_key_file_save ();
+
+	/* Not finding the requested group is acceptable, but we still
+	 * want to leave an informational message on the terminal. */
+	else if (g_error_matches (error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_GROUP_NOT_FOUND)) {
+		g_message ("%s", error->message);
+		g_error_free (error);
+
+	} else if (error != NULL)
+		g_propagate_error (&msg->error, error);
+
+	g_free (group);
+}
+
+static void
+ep_clear_passwords (EPassMsg *msg)
+{
+	ep_clear_passwords_keyfile (msg);
+
+	if (!msg->noreply)
+		e_flag_set (msg->done);
+}
+
+static void
+ep_forget_passwords_keyfile (EPassMsg *msg)
+{
+	gchar **groups;
+	gsize length = 0, ii;
+
+	if (!check_key_file (G_STRFUNC))
+		return;
+
+	groups = g_key_file_get_groups (key_file, &length);
+
+	if (!groups)
+		return;
+
+	for (ii = 0; ii < length; ii++) {
+		GError *error = NULL;
+
+		if (!g_str_has_prefix (groups[ii], KEY_FILE_GROUP_PREFIX))
+			continue;
+
+		g_key_file_remove_group (key_file, groups[ii], &error);
+
+		/* Not finding the requested group is acceptable, but we still
+		 * want to leave an informational message on the terminal. */
+		if (g_error_matches (error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_GROUP_NOT_FOUND)) {
+			g_message ("%s", error->message);
+			g_error_free (error);
+
+		/* Issue a warning if anything else goes wrong. */
+		} else if (error != NULL) {
+			g_warning ("%s", error->message);
+			g_error_free (error);
+		}
+	}
+	ep_key_file_save ();
+	g_strfreev (groups);
+}
+
+static void
+ep_forget_passwords (EPassMsg *msg)
+{
+	g_hash_table_remove_all (password_cache);
+
+	ep_forget_passwords_keyfile (msg);
+
+	if (!msg->noreply)
+		e_flag_set (msg->done);
+}
+
+static void
+ep_remember_password_keyfile (EPassMsg *msg)
+{
+	gchar *group, *key, *password;
+
+	password = g_hash_table_lookup (password_cache, msg->key);
+	if (password == NULL) {
+		g_warning ("Password for key \"%s\" not found", msg->key);
+		return;
+	}
+
+	group = ep_key_file_get_group (msg->component);
+	key = ep_key_file_normalize_key (msg->key);
+	password = ep_password_encode (password);
+
+	g_hash_table_remove (password_cache, msg->key);
+	if (check_key_file (G_STRFUNC)) {
+		g_key_file_set_string (key_file, group, key, password);
+		ep_key_file_save ();
+	}
+
+	g_free (group);
+	g_free (key);
+	g_free (password);
+}
+
+static void
+ep_remember_password (EPassMsg *msg)
+{
+	ep_remember_password_keyfile (msg);
+
+	if (!msg->noreply)
+		e_flag_set (msg->done);
+}
+
+static void
+ep_forget_password_keyfile (EPassMsg *msg)
+{
+	gchar *group, *key;
+	GError *error = NULL;
+
+	if (!check_key_file (G_STRFUNC))
+		return;
+
+	group = ep_key_file_get_group (msg->component);
+	key = ep_key_file_normalize_key (msg->key);
+
+	if (g_key_file_remove_key (key_file, group, key, &error))
+		ep_key_file_save ();
+
+	/* Not finding the requested key is acceptable, but we still
+	 * want to leave an informational message on the terminal. */
+	else if (g_error_matches (error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_KEY_NOT_FOUND)) {
+		g_message ("%s", error->message);
+		g_error_free (error);
+
+	/* Not finding the requested group is also acceptable. */
+	} else if (g_error_matches (error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_GROUP_NOT_FOUND)) {
+		g_message ("%s", error->message);
+		g_error_free (error);
+
+	} else if (error != NULL)
+		g_propagate_error (&msg->error, error);
+
+	g_free (group);
+	g_free (key);
+}
+
+static void
+ep_forget_password (EPassMsg *msg)
+{
+	g_hash_table_remove (password_cache, msg->key);
+
+	ep_forget_password_keyfile (msg);
+
+	if (!msg->noreply)
+		e_flag_set (msg->done);
+}
+
+static void
+ep_get_password_keyfile (EPassMsg *msg)
+{
+	gchar *group, *key, *password;
+	GError *error = NULL;
+
+	if (!check_key_file (G_STRFUNC))
+		return;
+
+	group = ep_key_file_get_group (msg->component);
+	key = ep_key_file_normalize_key (msg->key);
+
+	password = g_key_file_get_string (key_file, group, key, &error);
+	if (password != NULL) {
+		msg->password = ep_password_decode (password);
+		g_free (password);
+
+	/* Not finding the requested key is acceptable, but we still
+	 * want to leave an informational message on the terminal. */
+	} else if (g_error_matches (error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_KEY_NOT_FOUND)) {
+		g_message ("%s", error->message);
+		g_error_free (error);
+
+	/* Not finding the requested group is also acceptable. */
+	} else if (g_error_matches (error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_GROUP_NOT_FOUND)) {
+		g_message ("%s", error->message);
+		g_error_free (error);
+
+	} else if (error != NULL)
+		g_propagate_error (&msg->error, error);
+
+	g_free (group);
+	g_free (key);
+}
+
+static void
+ep_get_password (EPassMsg *msg)
+{
+	gchar *password;
+
+	/* Check the in-memory cache first. */
+	password = g_hash_table_lookup (password_cache, msg->key);
+	if (password != NULL) {
+		msg->password = g_strdup (password);
+
+	} else
+		ep_get_password_keyfile (msg);
+
+	if (!msg->noreply)
+		e_flag_set (msg->done);
+}
+
+static void
+ep_add_password (EPassMsg *msg)
+{
+	g_hash_table_insert (
+		password_cache, g_strdup (msg->key),
+		g_strdup (msg->oldpass));
+
+	if (!msg->noreply)
+		e_flag_set (msg->done);
+}
+
+static void ep_ask_password (EPassMsg *msg);
+
+static void
+pass_response (GtkDialog *dialog,
+               gint response,
+               gpointer data)
+{
+	EPassMsg *msg = data;
+	gint type = msg->flags & E_PASSWORDS_REMEMBER_MASK;
+	GList *iter, *trash = NULL;
+
+	if (response == GTK_RESPONSE_OK) {
+		msg->password = g_strdup (gtk_entry_get_text ((GtkEntry *)msg->entry));
+
+		if (type != E_PASSWORDS_REMEMBER_NEVER) {
+			gint noreply = msg->noreply;
+
+			*msg->remember = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (msg->check));
+
+			msg->noreply = 1;
+
+			if (*msg->remember || type == E_PASSWORDS_REMEMBER_FOREVER) {
+				msg->oldpass = msg->password;
+				ep_add_password (msg);
+			}
+			if (*msg->remember && type == E_PASSWORDS_REMEMBER_FOREVER)
+				ep_remember_password (msg);
+
+			msg->noreply = noreply;
+		}
+	}
+
+	gtk_widget_destroy ((GtkWidget *)dialog);
+	password_dialog = NULL;
+
+	/* ok, here things get interesting, we suck up any pending
+	 * operations on this specific password, and return the same
+	 * result or ignore other operations */
+
+	G_LOCK (passwords);
+	for (iter = g_queue_peek_head_link (&message_queue); iter != NULL; iter = iter->next) {
+		EPassMsg *pending = iter->data;
+
+		if ((pending->dispatch == ep_forget_password
+		     || pending->dispatch == ep_get_password
+		     || pending->dispatch == ep_ask_password)
+		    && (strcmp (pending->component, msg->component) == 0
+			&& strcmp (pending->key, msg->key) == 0)) {
+
+			/* Satisfy the pending operation. */
+			pending->password = g_strdup (msg->password);
+			e_flag_set (pending->done);
+
+			/* Mark the queue node for deletion. */
+			trash = g_list_prepend (trash, iter);
+		}
+	}
+
+	/* Expunge the message queue. */
+	for (iter = trash; iter != NULL; iter = iter->next)
+		g_queue_delete_link (&message_queue, iter->data);
+	g_list_free (trash);
+
+	G_UNLOCK (passwords);
+
+	if (!msg->noreply)
+		e_flag_set (msg->done);
+
+	ep_idle_dispatch (NULL);
+}
+
+static gboolean
+update_capslock_state (GtkDialog *dialog,
+                       GdkEvent *event,
+                       GtkWidget *label)
+{
+	GdkModifierType mask = 0;
+	GdkWindow *window;
+	gchar *markup = NULL;
+	GdkDeviceManager *device_manager;
+	GdkDevice *device;
+
+	device_manager = gdk_display_get_device_manager (gtk_widget_get_display (label));
+	device = gdk_device_manager_get_client_pointer (device_manager);
+	window = gtk_widget_get_window (GTK_WIDGET (dialog));
+	gdk_window_get_device_position (window, device, NULL, NULL, &mask);
+
+	/* The space acts as a vertical placeholder. */
+	markup = g_markup_printf_escaped (
+		"<small>%s</small>", (mask & GDK_LOCK_MASK) ?
+		_("You have the Caps Lock key on.") : " ");
+	gtk_label_set_markup (GTK_LABEL (label), markup);
+	g_free (markup);
+
+	return FALSE;
+}
+
+static void
+ep_ask_password (EPassMsg *msg)
+{
+	GtkWidget *widget;
+	GtkWidget *container;
+	GtkWidget *action_area;
+	GtkWidget *content_area;
+	gint type = msg->flags & E_PASSWORDS_REMEMBER_MASK;
+	guint noreply = msg->noreply;
+	gboolean visible;
+	AtkObject *a11y;
+
+	msg->noreply = 1;
+
+	widget = gtk_dialog_new_with_buttons (
+		msg->title, msg->parent, 0,
+		GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+		GTK_STOCK_OK, GTK_RESPONSE_OK,
+		NULL);
+#if !GTK_CHECK_VERSION(2,90,7)
+	g_object_set (widget, "has-separator", FALSE, NULL);
+#endif
+	gtk_dialog_set_default_response (
+		GTK_DIALOG (widget), GTK_RESPONSE_OK);
+	gtk_window_set_resizable (GTK_WINDOW (widget), FALSE);
+	gtk_window_set_transient_for (GTK_WINDOW (widget), msg->parent);
+	gtk_window_set_position (GTK_WINDOW (widget), GTK_WIN_POS_CENTER_ON_PARENT);
+	gtk_container_set_border_width (GTK_CONTAINER (widget), 12);
+	password_dialog = GTK_DIALOG (widget);
+
+	action_area = gtk_dialog_get_action_area (password_dialog);
+	content_area = gtk_dialog_get_content_area (password_dialog);
+
+	/* Override GtkDialog defaults */
+	gtk_box_set_spacing (GTK_BOX (action_area), 12);
+	gtk_container_set_border_width (GTK_CONTAINER (action_area), 0);
+	gtk_box_set_spacing (GTK_BOX (content_area), 12);
+	gtk_container_set_border_width (GTK_CONTAINER (content_area), 0);
+
+	/* Grid */
+	container = gtk_grid_new ();
+	gtk_grid_set_column_spacing (GTK_GRID (container), 12);
+	gtk_grid_set_row_spacing (GTK_GRID (container), 6);
+	gtk_widget_show (container);
+
+	gtk_box_pack_start (
+		GTK_BOX (content_area), container, FALSE, TRUE, 0);
+
+	/* Password Image */
+	widget = gtk_image_new_from_icon_name (
+		"dialog-password", GTK_ICON_SIZE_DIALOG);
+	gtk_misc_set_alignment (GTK_MISC (widget), 0.0, 0.0);
+	g_object_set (G_OBJECT (widget),
+		"halign", GTK_ALIGN_FILL,
+		"vexpand", TRUE,
+		"valign", GTK_ALIGN_FILL,
+		NULL);
+	gtk_widget_show (widget);
+
+	gtk_grid_attach (GTK_GRID (container), widget, 0, 0, 1, 3);
+
+	/* Password Label */
+	widget = gtk_label_new (NULL);
+	gtk_label_set_line_wrap (GTK_LABEL (widget), TRUE);
+	gtk_label_set_markup (GTK_LABEL (widget), msg->prompt);
+	gtk_misc_set_alignment (GTK_MISC (widget), 0.0, 0.5);
+	g_object_set (G_OBJECT (widget),
+		"hexpand", TRUE,
+		"halign", GTK_ALIGN_FILL,
+		NULL);
+	gtk_widget_show (widget);
+
+	gtk_grid_attach (GTK_GRID (container), widget, 1, 0, 1, 1);
+
+	/* Password Entry */
+	widget = gtk_entry_new ();
+	a11y = gtk_widget_get_accessible (widget);
+	visible = !(msg->flags & E_PASSWORDS_SECRET);
+	atk_object_set_description (a11y, msg->prompt);
+	gtk_entry_set_visibility (GTK_ENTRY (widget), visible);
+	gtk_entry_set_activates_default (GTK_ENTRY (widget), TRUE);
+	gtk_widget_grab_focus (widget);
+	g_object_set (G_OBJECT (widget),
+		"hexpand", TRUE,
+		"halign", GTK_ALIGN_FILL,
+		NULL);
+	gtk_widget_show (widget);
+	msg->entry = widget;
+
+	if ((msg->flags & E_PASSWORDS_REPROMPT)) {
+		ep_get_password (msg);
+		if (msg->password != NULL) {
+			gtk_entry_set_text (GTK_ENTRY (widget), msg->password);
+			g_free (msg->password);
+			msg->password = NULL;
+		}
+	}
+
+	gtk_grid_attach (GTK_GRID (container), widget, 1, 1, 1, 1);
+
+	/* Caps Lock Label */
+	widget = gtk_label_new (NULL);
+	g_object_set (G_OBJECT (widget),
+		"hexpand", TRUE,
+		"halign", GTK_ALIGN_FILL,
+		NULL);
+	gtk_widget_show (widget);
+
+	gtk_grid_attach (GTK_GRID (container), widget, 1, 2, 1, 1);
+
+	g_signal_connect (
+		password_dialog, "key-release-event",
+		G_CALLBACK (update_capslock_state), widget);
+	g_signal_connect (
+		password_dialog, "focus-in-event",
+		G_CALLBACK (update_capslock_state), widget);
+
+	/* static password, shouldn't be remembered between sessions,
+	   but will be remembered within the session beyond our control */
+	if (type != E_PASSWORDS_REMEMBER_NEVER) {
+		if (msg->flags & E_PASSWORDS_PASSPHRASE) {
+			widget = gtk_check_button_new_with_mnemonic (
+				(type == E_PASSWORDS_REMEMBER_FOREVER)
+				? _("_Remember this passphrase")
+				: _("_Remember this passphrase for"
+				    " the remainder of this session"));
+		} else {
+			widget = gtk_check_button_new_with_mnemonic (
+				(type == E_PASSWORDS_REMEMBER_FOREVER)
+				? _("_Remember this password")
+				: _("_Remember this password for"
+				    " the remainder of this session"));
+		}
+
+		gtk_toggle_button_set_active (
+			GTK_TOGGLE_BUTTON (widget), *msg->remember);
+		if (msg->flags & E_PASSWORDS_DISABLE_REMEMBER)
+			gtk_widget_set_sensitive (widget, FALSE);
+		g_object_set (G_OBJECT (widget),
+			"hexpand", TRUE,
+			"halign", GTK_ALIGN_FILL,
+			"valign", GTK_ALIGN_FILL,
+			NULL);
+		gtk_widget_show (widget);
+		msg->check = widget;
+
+		gtk_grid_attach (GTK_GRID (container), widget, 1, 3, 1, 1);
+	}
+
+	msg->noreply = noreply;
+
+	g_signal_connect (
+		password_dialog, "response",
+		G_CALLBACK (pass_response), msg);
+
+	if (msg->parent) {
+		gtk_dialog_run (GTK_DIALOG (password_dialog));
+	} else {
+		gtk_window_present (GTK_WINDOW (password_dialog));
+		/* workaround GTK+ bug (see Gnome's bugzilla bug #624229) */
+		gtk_grab_add (GTK_WIDGET (password_dialog));
+	}
+}
+
+/**
+ * e_passwords_init:
+ *
+ * Initializes the e_passwords routines. Must be called before any other
+ * e_passwords_* function.
+ **/
+void
+e_passwords_init (void)
+{
+	G_LOCK (passwords);
+
+	if (password_cache == NULL) {
+		password_cache = g_hash_table_new_full (
+			g_str_hash, g_str_equal,
+			(GDestroyNotify) g_free,
+			(GDestroyNotify) g_free);
+		main_thread = g_thread_self ();
+
+		/* Load the keyfile even if we're using the keyring.
+		 * We might be able to extract passwords from it. */
+		key_file = g_key_file_new ();
+		ep_key_file_load ();
+
+	}
+
+	G_UNLOCK (passwords);
+}
+
+/**
+ * e_passwords_cancel:
+ *
+ * Cancel any outstanding password operations and close any dialogues
+ * currently being shown.
+ **/
+void
+e_passwords_cancel (void)
+{
+	EPassMsg *msg;
+
+	G_LOCK (passwords);
+	while ((msg = g_queue_pop_head (&message_queue)) != NULL)
+		e_flag_set (msg->done);
+	G_UNLOCK (passwords);
+
+	if (password_dialog)
+		gtk_dialog_response (password_dialog, GTK_RESPONSE_CANCEL);
+}
+
+/**
+ * e_passwords_shutdown:
+ *
+ * Cleanup routine to call before exiting.
+ **/
+void
+e_passwords_shutdown (void)
+{
+	EPassMsg *msg;
+
+	G_LOCK (passwords);
+
+	while ((msg = g_queue_pop_head (&message_queue)) != NULL)
+		e_flag_set (msg->done);
+
+	if (password_cache != NULL) {
+		g_hash_table_destroy (password_cache);
+		password_cache = NULL;
+	}
+
+
+	G_UNLOCK (passwords);
+
+	if (password_dialog != NULL)
+		gtk_dialog_response (password_dialog, GTK_RESPONSE_CANCEL);
+}
+
+/**
+ * e_passwords_set_online:
+ * @state:
+ *
+ * Set the offline-state of the application.  This is a work-around
+ * for having the backends fully offline aware, and returns a
+ * cancellation response instead of prompting for passwords.
+ *
+ * FIXME: This is not a permanent api, review post 2.0.
+ **/
+void
+e_passwords_set_online (gint state)
+{
+	ep_online_state = state;
+	/* TODO: we could check that a request is open and close it, or maybe who cares */
+}
+
+/**
+ * e_passwords_forget_passwords:
+ *
+ * Forgets all cached passwords, in memory and on disk.
+ **/
+void
+e_passwords_forget_passwords (void)
+{
+	EPassMsg *msg = ep_msg_new (ep_forget_passwords);
+
+	ep_msg_send (msg);
+	ep_msg_free (msg);
+}
+
+/**
+ * e_passwords_clear_passwords:
+ *
+ * Forgets all disk cached passwords for the component.
+ **/
+void
+e_passwords_clear_passwords (const gchar *component_name)
+{
+	EPassMsg *msg = ep_msg_new (ep_clear_passwords);
+
+	msg->component = component_name;
+	ep_msg_send (msg);
+	ep_msg_free (msg);
+}
+
+/**
+ * e_passwords_remember_password:
+ * @key: the key
+ *
+ * Saves the password associated with @key to disk.
+ **/
+void
+e_passwords_remember_password (const gchar *component_name,
+                               const gchar *key)
+{
+	EPassMsg *msg;
+
+	g_return_if_fail (component_name != NULL);
+	g_return_if_fail (key != NULL);
+
+	msg = ep_msg_new (ep_remember_password);
+	msg->component = component_name;
+	msg->key = key;
+
+	ep_msg_send (msg);
+	ep_msg_free (msg);
+}
+
+/**
+ * e_passwords_forget_password:
+ * @key: the key
+ *
+ * Forgets the password associated with @key, in memory and on disk.
+ **/
+void
+e_passwords_forget_password (const gchar *component_name,
+                             const gchar *key)
+{
+	EPassMsg *msg;
+
+	g_return_if_fail (component_name != NULL);
+	g_return_if_fail (key != NULL);
+
+	msg = ep_msg_new (ep_forget_password);
+	msg->component = component_name;
+	msg->key = key;
+
+	ep_msg_send (msg);
+	ep_msg_free (msg);
+}
+
+/**
+ * e_passwords_get_password:
+ * @key: the key
+ *
+ * Returns: the password associated with @key, or %NULL.  Caller
+ * must free the returned password.
+ **/
+gchar *
+e_passwords_get_password (const gchar *component_name,
+                          const gchar *key)
+{
+	EPassMsg *msg;
+	gchar *passwd;
+
+	g_return_val_if_fail (component_name != NULL, NULL);
+	g_return_val_if_fail (key != NULL, NULL);
+
+	msg = ep_msg_new (ep_get_password);
+	msg->component = component_name;
+	msg->key = key;
+
+	ep_msg_send (msg);
+
+	passwd = msg->password;
+	msg->password = NULL;
+	ep_msg_free (msg);
+
+	return passwd;
+}
+
+/**
+ * e_passwords_add_password:
+ * @key: a key
+ * @passwd: the password for @key
+ *
+ * This stores the @key/@passwd pair in the current session's password
+ * hash.
+ **/
+void
+e_passwords_add_password (const gchar *key,
+                          const gchar *passwd)
+{
+	EPassMsg *msg;
+
+	g_return_if_fail (key != NULL);
+	g_return_if_fail (passwd != NULL);
+
+	msg = ep_msg_new (ep_add_password);
+	msg->key = key;
+	msg->oldpass = passwd;
+
+	ep_msg_send (msg);
+	ep_msg_free (msg);
+}
+
+/**
+ * e_passwords_ask_password:
+ * @title: title for the password dialog
+ * @component_name: the name of the component for which we're storing
+ * the password (e.g. Mail, Addressbook, etc.)
+ * @key: key to store the password under
+ * @prompt: prompt string
+ * @type: whether or not to offer to remember the password,
+ * and for how long.
+ * @remember: on input, the default state of the remember checkbox.
+ * on output, the state of the checkbox when the dialog was closed.
+ * @parent: parent window of the dialog, or %NULL
+ *
+ * Asks the user for a password.
+ *
+ * Returns: the password, which the caller must free, or %NULL if
+ * the user cancelled the operation. * remember will be set if the
+ * return value is non-%NULL and @remember_type is not
+ * E_PASSWORDS_DO_NOT_REMEMBER.
+ **/
+gchar *
+e_passwords_ask_password (const gchar *title,
+                          const gchar *component_name,
+                          const gchar *key,
+                          const gchar *prompt,
+                          EPasswordsRememberType type,
+                          gboolean *remember,
+                          GtkWindow *parent)
+{
+	gchar *passwd;
+	EPassMsg *msg;
+
+	g_return_val_if_fail (component_name != NULL, NULL);
+	g_return_val_if_fail (key != NULL, NULL);
+
+	if ((type & E_PASSWORDS_ONLINE) && !ep_online_state)
+		return NULL;
+
+	msg = ep_msg_new (ep_ask_password);
+	msg->title = title;
+	msg->component = component_name;
+	msg->key = key;
+	msg->prompt = prompt;
+	msg->flags = type;
+	msg->remember = remember;
+	msg->parent = parent;
+
+	ep_msg_send (msg);
+	passwd = msg->password;
+	msg->password = NULL;
+	ep_msg_free (msg);
+
+	return passwd;
+}
diff --git a/e-util/e-passwords.c b/e-util/e-passwords.c
new file mode 100644
index 0000000..bf4cfc1
--- /dev/null
+++ b/e-util/e-passwords.c
@@ -0,0 +1,890 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * e-passwords.c
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that 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 program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
+ * USA.
+ */
+
+/*
+ * This looks a lot more complicated than it is, and than you'd think
+ * it would need to be.  There is however, method to the madness.
+ *
+ * The code must cope with being called from any thread at any time,
+ * recursively from the main thread, and then serialising every
+ * request so that sane and correct values are always returned, and
+ * duplicate requests are never made.
+ *
+ * To this end, every call is marshalled and queued and a dispatch
+ * method invoked until that request is satisfied.  If mainloop
+ * recursion occurs, then the sub-call will necessarily return out of
+ * order, but will not be processed out of order.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+#include <gtk/gtk.h>
+#include <glib/gi18n-lib.h>
+
+/* XXX Yeah, yeah... */
+#define SECRET_API_SUBJECT_TO_CHANGE
+
+#include <libsecret/secret.h>
+
+#include <libedataserver/libedataserver.h>
+
+#include "e-passwords.h"
+
+#define d(x)
+
+typedef struct _EPassMsg EPassMsg;
+
+struct _EPassMsg {
+	void (*dispatch) (EPassMsg *);
+	EFlag *done;
+
+	/* input */
+	GtkWindow *parent;
+	const gchar *key;
+	const gchar *title;
+	const gchar *prompt;
+	const gchar *oldpass;
+	guint32 flags;
+
+	/* output */
+	gboolean *remember;
+	gchar *password;
+	GError *error;
+
+	/* work variables */
+	GtkWidget *entry;
+	GtkWidget *check;
+	guint ismain : 1;
+	guint noreply:1;	/* supress replies; when calling
+				 * dispatch functions from others */
+};
+
+/* XXX probably want to share this with evalution-source-registry-migrate-sources.c */
+static const SecretSchema e_passwords_schema = {
+	"org.gnome.Evolution.Password",
+	SECRET_SCHEMA_DONT_MATCH_NAME,
+	{
+		{ "application", SECRET_SCHEMA_ATTRIBUTE_STRING, },
+		{ "user", SECRET_SCHEMA_ATTRIBUTE_STRING, },
+		{ "server", SECRET_SCHEMA_ATTRIBUTE_STRING, },
+		{ "protocol", SECRET_SCHEMA_ATTRIBUTE_STRING, },
+	}
+};
+
+G_LOCK_DEFINE_STATIC (passwords);
+static GThread *main_thread = NULL;
+static GHashTable *password_cache = NULL;
+static GtkDialog *password_dialog = NULL;
+static GQueue message_queue = G_QUEUE_INIT;
+static gint idle_id;
+static gint ep_online_state = TRUE;
+
+static EUri *
+ep_keyring_uri_new (const gchar *string,
+                    GError **error)
+{
+	EUri *uri;
+
+	uri = e_uri_new (string);
+	g_return_val_if_fail (uri != NULL, NULL);
+
+	/* LDAP URIs do not have usernames, so use the URI as the username. */
+	if (uri->user == NULL && uri->protocol != NULL &&
+			(strcmp (uri->protocol, "ldap") == 0|| strcmp (uri->protocol, "google") == 0))
+		uri->user = g_strdelimit (g_strdup (string), "/=", '_');
+
+	/* Make sure the URI has the required components. */
+	if (uri->user == NULL && uri->host == NULL) {
+		g_set_error_literal (
+			error, G_IO_ERROR,
+			G_IO_ERROR_INVALID_ARGUMENT,
+			_("Keyring key is unusable: no user or host name"));
+		e_uri_free (uri);
+		uri = NULL;
+	}
+
+	return uri;
+}
+
+static gboolean
+ep_idle_dispatch (gpointer data)
+{
+	EPassMsg *msg;
+
+	/* As soon as a password window is up we stop; it will
+	 * re - invoke us when it has been closed down */
+	G_LOCK (passwords);
+	while (password_dialog == NULL && (msg = g_queue_pop_head (&message_queue)) != NULL) {
+		G_UNLOCK (passwords);
+
+		msg->dispatch (msg);
+
+		G_LOCK (passwords);
+	}
+
+	idle_id = 0;
+	G_UNLOCK (passwords);
+
+	return FALSE;
+}
+
+static EPassMsg *
+ep_msg_new (void (*dispatch) (EPassMsg *))
+{
+	EPassMsg *msg;
+
+	e_passwords_init ();
+
+	msg = g_malloc0 (sizeof (*msg));
+	msg->dispatch = dispatch;
+	msg->done = e_flag_new ();
+	msg->ismain = (g_thread_self () == main_thread);
+
+	return msg;
+}
+
+static void
+ep_msg_free (EPassMsg *msg)
+{
+	/* XXX We really should be passing this back to the caller, but
+	 *     doing so will require breaking the password API. */
+	if (msg->error != NULL) {
+		g_warning ("%s", msg->error->message);
+		g_error_free (msg->error);
+	}
+
+	e_flag_free (msg->done);
+	g_free (msg->password);
+	g_free (msg);
+}
+
+static void
+ep_msg_send (EPassMsg *msg)
+{
+	gint needidle = 0;
+
+	G_LOCK (passwords);
+	g_queue_push_tail (&message_queue, msg);
+	if (!idle_id) {
+		if (!msg->ismain)
+			idle_id = g_idle_add (ep_idle_dispatch, NULL);
+		else
+			needidle = 1;
+	}
+	G_UNLOCK (passwords);
+
+	if (msg->ismain) {
+		if (needidle)
+			ep_idle_dispatch (NULL);
+		while (!e_flag_is_set (msg->done))
+			g_main_context_iteration (NULL, TRUE);
+	} else
+		e_flag_wait (msg->done);
+}
+
+/* the functions that actually do the work */
+
+static void
+ep_clear_passwords (EPassMsg *msg)
+{
+	GError *error = NULL;
+
+	/* Find all Evolution passwords and delete them. */
+	secret_password_clear_sync (
+		&e_passwords_schema, NULL, &error,
+		"application", "Evolution", NULL);
+
+	if (error != NULL)
+		g_propagate_error (&msg->error, error);
+
+	if (!msg->noreply)
+		e_flag_set (msg->done);
+}
+
+static void
+ep_remember_password (EPassMsg *msg)
+{
+	gchar *password;
+	EUri *uri;
+	GError *error = NULL;
+
+	password = g_hash_table_lookup (password_cache, msg->key);
+	if (password == NULL) {
+		g_warning ("Password for key \"%s\" not found", msg->key);
+		goto exit;
+	}
+
+	uri = ep_keyring_uri_new (msg->key, &msg->error);
+	if (uri == NULL)
+		goto exit;
+
+	secret_password_store_sync (
+		&e_passwords_schema,
+		SECRET_COLLECTION_DEFAULT,
+		msg->key, password,
+		NULL, &error,
+		"application", "Evolution",
+		"user", uri->user,
+		"server", uri->host,
+		"protocol", uri->protocol,
+		NULL);
+
+	/* Only remove the password from the session hash
+	 * if the keyring insertion was successful. */
+	if (error == NULL)
+		g_hash_table_remove (password_cache, msg->key);
+	else
+		g_propagate_error (&msg->error, error);
+
+	e_uri_free (uri);
+
+exit:
+	if (!msg->noreply)
+		e_flag_set (msg->done);
+}
+
+static void
+ep_forget_password (EPassMsg *msg)
+{
+	EUri *uri;
+	GError *error = NULL;
+
+	g_hash_table_remove (password_cache, msg->key);
+
+	uri = ep_keyring_uri_new (msg->key, &msg->error);
+	if (uri == NULL)
+		goto exit;
+
+	/* Find all Evolution passwords matching the URI and delete them.
+	 *
+	 * XXX We didn't always store protocols in the keyring, so for
+	 *     backward-compatibility we need to lookup passwords by user
+	 *     and host only (no protocol).  But we do send the protocol
+	 *     to ep_keyring_delete_passwords(), which also knows about
+	 *     the backward-compatibility issue and will filter the list
+	 *     appropriately. */
+	secret_password_clear_sync (
+		&e_passwords_schema, NULL, &error,
+		"application", "Evolution",
+		"user", uri->user,
+		"server", uri->host,
+		NULL);
+
+	if (error != NULL)
+		g_propagate_error (&msg->error, error);
+
+	e_uri_free (uri);
+
+exit:
+	if (!msg->noreply)
+		e_flag_set (msg->done);
+}
+
+static void
+ep_get_password (EPassMsg *msg)
+{
+	EUri *uri;
+	gchar *password;
+	GError *error = NULL;
+
+	/* Check the in-memory cache first. */
+	password = g_hash_table_lookup (password_cache, msg->key);
+	if (password != NULL) {
+		msg->password = g_strdup (password);
+		goto exit;
+	}
+
+	uri = ep_keyring_uri_new (msg->key, &msg->error);
+	if (uri == NULL)
+		goto exit;
+
+	msg->password = secret_password_lookup_sync (
+		&e_passwords_schema, NULL, &error,
+		"application", "Evolution",
+		"user", uri->user,
+		"server", uri->host,
+		"protocol", uri->protocol,
+		NULL);
+
+	if (msg->password != NULL)
+		goto done;
+
+	/* Clear the previous error, if there was one.
+	 * It's likely to occur again. */
+	if (error != NULL)
+		g_clear_error (&error);
+
+	/* XXX We didn't always store protocols in the keyring, so for
+	 *     backward-compatibility we also need to lookup passwords
+	 *     by user and host only (no protocol). */
+	msg->password = secret_password_lookup_sync (
+		&e_passwords_schema, NULL, &error,
+		"application", "Evolution",
+		"user", uri->user,
+		"server", uri->host,
+		NULL);
+
+done:
+	if (error != NULL)
+		g_propagate_error (&msg->error, error);
+
+	e_uri_free (uri);
+
+exit:
+	if (!msg->noreply)
+		e_flag_set (msg->done);
+}
+
+static void
+ep_add_password (EPassMsg *msg)
+{
+	g_hash_table_insert (
+		password_cache, g_strdup (msg->key),
+		g_strdup (msg->oldpass));
+
+	if (!msg->noreply)
+		e_flag_set (msg->done);
+}
+
+static void ep_ask_password (EPassMsg *msg);
+
+static void
+pass_response (GtkDialog *dialog,
+               gint response,
+               gpointer data)
+{
+	EPassMsg *msg = data;
+	gint type = msg->flags & E_PASSWORDS_REMEMBER_MASK;
+	GList *iter, *trash = NULL;
+
+	if (response == GTK_RESPONSE_OK) {
+		msg->password = g_strdup (gtk_entry_get_text ((GtkEntry *) msg->entry));
+
+		if (type != E_PASSWORDS_REMEMBER_NEVER) {
+			gint noreply = msg->noreply;
+
+			*msg->remember = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (msg->check));
+
+			msg->noreply = 1;
+
+			if (*msg->remember || type == E_PASSWORDS_REMEMBER_FOREVER) {
+				msg->oldpass = msg->password;
+				ep_add_password (msg);
+			}
+			if (*msg->remember && type == E_PASSWORDS_REMEMBER_FOREVER)
+				ep_remember_password (msg);
+
+			msg->noreply = noreply;
+		}
+	}
+
+	gtk_widget_destroy ((GtkWidget *) dialog);
+	password_dialog = NULL;
+
+	/* ok, here things get interesting, we suck up any pending
+	 * operations on this specific password, and return the same
+	 * result or ignore other operations */
+
+	G_LOCK (passwords);
+	for (iter = g_queue_peek_head_link (&message_queue); iter != NULL; iter = iter->next) {
+		EPassMsg *pending = iter->data;
+
+		if ((pending->dispatch == ep_forget_password
+		     || pending->dispatch == ep_get_password
+		     || pending->dispatch == ep_ask_password)
+			&& strcmp (pending->key, msg->key) == 0) {
+
+			/* Satisfy the pending operation. */
+			pending->password = g_strdup (msg->password);
+			e_flag_set (pending->done);
+
+			/* Mark the queue node for deletion. */
+			trash = g_list_prepend (trash, iter);
+		}
+	}
+
+	/* Expunge the message queue. */
+	for (iter = trash; iter != NULL; iter = iter->next)
+		g_queue_delete_link (&message_queue, iter->data);
+	g_list_free (trash);
+
+	G_UNLOCK (passwords);
+
+	if (!msg->noreply)
+		e_flag_set (msg->done);
+
+	ep_idle_dispatch (NULL);
+}
+
+static gboolean
+update_capslock_state (GtkDialog *dialog,
+                       GdkEvent *event,
+                       GtkWidget *label)
+{
+	GdkModifierType mask = 0;
+	GdkWindow *window;
+	gchar *markup = NULL;
+	GdkDeviceManager *device_manager;
+	GdkDevice *device;
+
+	device_manager = gdk_display_get_device_manager (gtk_widget_get_display (label));
+	device = gdk_device_manager_get_client_pointer (device_manager);
+	window = gtk_widget_get_window (GTK_WIDGET (dialog));
+	gdk_window_get_device_position (window, device, NULL, NULL, &mask);
+
+	/* The space acts as a vertical placeholder. */
+	markup = g_markup_printf_escaped (
+		"<small>%s</small>", (mask & GDK_LOCK_MASK) ?
+		_("You have the Caps Lock key on.") : " ");
+	gtk_label_set_markup (GTK_LABEL (label), markup);
+	g_free (markup);
+
+	return FALSE;
+}
+
+static void
+ep_ask_password (EPassMsg *msg)
+{
+	GtkWidget *widget;
+	GtkWidget *container;
+	GtkWidget *action_area;
+	GtkWidget *content_area;
+	gint type = msg->flags & E_PASSWORDS_REMEMBER_MASK;
+	guint noreply = msg->noreply;
+	gboolean visible;
+	AtkObject *a11y;
+
+	msg->noreply = 1;
+
+	widget = gtk_dialog_new_with_buttons (
+		msg->title, msg->parent, 0,
+		GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+		GTK_STOCK_OK, GTK_RESPONSE_OK,
+		NULL);
+	gtk_dialog_set_default_response (
+		GTK_DIALOG (widget), GTK_RESPONSE_OK);
+	gtk_window_set_resizable (GTK_WINDOW (widget), FALSE);
+	gtk_window_set_transient_for (GTK_WINDOW (widget), msg->parent);
+	gtk_window_set_position (GTK_WINDOW (widget), GTK_WIN_POS_CENTER_ON_PARENT);
+	gtk_container_set_border_width (GTK_CONTAINER (widget), 12);
+	password_dialog = GTK_DIALOG (widget);
+
+	action_area = gtk_dialog_get_action_area (password_dialog);
+	content_area = gtk_dialog_get_content_area (password_dialog);
+
+	/* Override GtkDialog defaults */
+	gtk_box_set_spacing (GTK_BOX (action_area), 12);
+	gtk_container_set_border_width (GTK_CONTAINER (action_area), 0);
+	gtk_box_set_spacing (GTK_BOX (content_area), 12);
+	gtk_container_set_border_width (GTK_CONTAINER (content_area), 0);
+
+	/* Grid */
+	container = gtk_grid_new ();
+	gtk_grid_set_column_spacing (GTK_GRID (container), 12);
+	gtk_grid_set_row_spacing (GTK_GRID (container), 6);
+	gtk_widget_show (container);
+
+	gtk_box_pack_start (
+		GTK_BOX (content_area), container, FALSE, TRUE, 0);
+
+	/* Password Image */
+	widget = gtk_image_new_from_icon_name (
+		"dialog-password", GTK_ICON_SIZE_DIALOG);
+	gtk_misc_set_alignment (GTK_MISC (widget), 0.0, 0.0);
+	g_object_set (G_OBJECT (widget),
+		"halign", GTK_ALIGN_FILL,
+		"vexpand", TRUE,
+		"valign", GTK_ALIGN_FILL,
+		NULL);
+	gtk_widget_show (widget);
+
+	gtk_grid_attach (GTK_GRID (container), widget, 0, 0, 1, 3);
+
+	/* Password Label */
+	widget = gtk_label_new (NULL);
+	gtk_label_set_line_wrap (GTK_LABEL (widget), TRUE);
+	gtk_label_set_markup (GTK_LABEL (widget), msg->prompt);
+	gtk_misc_set_alignment (GTK_MISC (widget), 0.0, 0.5);
+	g_object_set (G_OBJECT (widget),
+		"hexpand", TRUE,
+		"halign", GTK_ALIGN_FILL,
+		NULL);
+	gtk_widget_show (widget);
+
+	gtk_grid_attach (GTK_GRID (container), widget, 1, 0, 1, 1);
+
+	/* Password Entry */
+	widget = gtk_entry_new ();
+	a11y = gtk_widget_get_accessible (widget);
+	visible = !(msg->flags & E_PASSWORDS_SECRET);
+	atk_object_set_description (a11y, msg->prompt);
+	gtk_entry_set_visibility (GTK_ENTRY (widget), visible);
+	gtk_entry_set_activates_default (GTK_ENTRY (widget), TRUE);
+	gtk_widget_grab_focus (widget);
+	g_object_set (G_OBJECT (widget),
+		"hexpand", TRUE,
+		"halign", GTK_ALIGN_FILL,
+		NULL);
+	gtk_widget_show (widget);
+	msg->entry = widget;
+
+	if ((msg->flags & E_PASSWORDS_REPROMPT)) {
+		ep_get_password (msg);
+		if (msg->password != NULL) {
+			gtk_entry_set_text (GTK_ENTRY (widget), msg->password);
+			g_free (msg->password);
+			msg->password = NULL;
+		}
+	}
+
+	gtk_grid_attach (GTK_GRID (container), widget, 1, 1, 1, 1);
+
+	/* Caps Lock Label */
+	widget = gtk_label_new (NULL);
+	g_object_set (G_OBJECT (widget),
+		"hexpand", TRUE,
+		"halign", GTK_ALIGN_FILL,
+		NULL);
+	gtk_widget_show (widget);
+
+	gtk_grid_attach (GTK_GRID (container), widget, 1, 2, 1, 1);
+
+	g_signal_connect (
+		password_dialog, "key-release-event",
+		G_CALLBACK (update_capslock_state), widget);
+	g_signal_connect (
+		password_dialog, "focus-in-event",
+		G_CALLBACK (update_capslock_state), widget);
+
+	/* static password, shouldn't be remembered between sessions,
+	 * but will be remembered within the session beyond our control */
+	if (type != E_PASSWORDS_REMEMBER_NEVER) {
+		if (msg->flags & E_PASSWORDS_PASSPHRASE) {
+			widget = gtk_check_button_new_with_mnemonic (
+				(type == E_PASSWORDS_REMEMBER_FOREVER)
+				? _("_Remember this passphrase")
+				: _("_Remember this passphrase for"
+				" the remainder of this session"));
+		} else {
+			widget = gtk_check_button_new_with_mnemonic (
+				(type == E_PASSWORDS_REMEMBER_FOREVER)
+				? _("_Remember this password")
+				: _("_Remember this password for"
+				" the remainder of this session"));
+		}
+
+		gtk_toggle_button_set_active (
+			GTK_TOGGLE_BUTTON (widget), *msg->remember);
+		if (msg->flags & E_PASSWORDS_DISABLE_REMEMBER)
+			gtk_widget_set_sensitive (widget, FALSE);
+		g_object_set (G_OBJECT (widget),
+			"hexpand", TRUE,
+			"halign", GTK_ALIGN_FILL,
+			"valign", GTK_ALIGN_FILL,
+			NULL);
+		gtk_widget_show (widget);
+		msg->check = widget;
+
+		gtk_grid_attach (GTK_GRID (container), widget, 1, 3, 1, 1);
+	}
+
+	msg->noreply = noreply;
+
+	g_signal_connect (
+		password_dialog, "response",
+		G_CALLBACK (pass_response), msg);
+
+	if (msg->parent) {
+		gtk_dialog_run (GTK_DIALOG (password_dialog));
+	} else {
+		gtk_window_present (GTK_WINDOW (password_dialog));
+		/* workaround GTK+ bug (see Gnome's bugzilla bug #624229) */
+		gtk_grab_add (GTK_WIDGET (password_dialog));
+	}
+}
+
+/**
+ * e_passwords_init:
+ *
+ * Initializes the e_passwords routines. Must be called before any other
+ * e_passwords_* function.
+ **/
+void
+e_passwords_init (void)
+{
+	G_LOCK (passwords);
+
+	if (password_cache == NULL) {
+		password_cache = g_hash_table_new_full (
+			g_str_hash, g_str_equal,
+			(GDestroyNotify) g_free,
+			(GDestroyNotify) g_free);
+		main_thread = g_thread_self ();
+	}
+
+	G_UNLOCK (passwords);
+}
+
+/**
+ * e_passwords_cancel:
+ *
+ * Cancel any outstanding password operations and close any dialogues
+ * currently being shown.
+ **/
+void
+e_passwords_cancel (void)
+{
+	EPassMsg *msg;
+
+	G_LOCK (passwords);
+	while ((msg = g_queue_pop_head (&message_queue)) != NULL)
+		e_flag_set (msg->done);
+	G_UNLOCK (passwords);
+
+	if (password_dialog)
+		gtk_dialog_response (password_dialog, GTK_RESPONSE_CANCEL);
+}
+
+/**
+ * e_passwords_shutdown:
+ *
+ * Cleanup routine to call before exiting.
+ **/
+void
+e_passwords_shutdown (void)
+{
+	EPassMsg *msg;
+
+	G_LOCK (passwords);
+
+	while ((msg = g_queue_pop_head (&message_queue)) != NULL)
+		e_flag_set (msg->done);
+
+	if (password_cache != NULL) {
+		g_hash_table_destroy (password_cache);
+		password_cache = NULL;
+	}
+
+	G_UNLOCK (passwords);
+
+	if (password_dialog != NULL)
+		gtk_dialog_response (password_dialog, GTK_RESPONSE_CANCEL);
+}
+
+/**
+ * e_passwords_set_online:
+ * @state:
+ *
+ * Set the offline-state of the application.  This is a work-around
+ * for having the backends fully offline aware, and returns a
+ * cancellation response instead of prompting for passwords.
+ *
+ * FIXME: This is not a permanent api, review post 2.0.
+ **/
+void
+e_passwords_set_online (gint state)
+{
+	ep_online_state = state;
+	/* TODO: we could check that a request is open and close it, or maybe who cares */
+}
+
+/**
+ * e_passwords_forget_passwords:
+ *
+ * Forgets all cached passwords, in memory and on disk.
+ **/
+void
+e_passwords_forget_passwords (void)
+{
+	EPassMsg *msg = ep_msg_new (ep_clear_passwords);
+
+	ep_msg_send (msg);
+	ep_msg_free (msg);
+}
+
+/**
+ * e_passwords_clear_passwords:
+ *
+ * Forgets all disk cached passwords for the component.
+ **/
+void
+e_passwords_clear_passwords (const gchar *unused)
+{
+	EPassMsg *msg = ep_msg_new (ep_clear_passwords);
+
+	ep_msg_send (msg);
+	ep_msg_free (msg);
+}
+
+/**
+ * e_passwords_remember_password:
+ * @key: the key
+ *
+ * Saves the password associated with @key to disk.
+ **/
+void
+e_passwords_remember_password (const gchar *unused,
+                               const gchar *key)
+{
+	EPassMsg *msg;
+
+	g_return_if_fail (key != NULL);
+
+	msg = ep_msg_new (ep_remember_password);
+	msg->key = key;
+
+	ep_msg_send (msg);
+	ep_msg_free (msg);
+}
+
+/**
+ * e_passwords_forget_password:
+ * @key: the key
+ *
+ * Forgets the password associated with @key, in memory and on disk.
+ **/
+void
+e_passwords_forget_password (const gchar *unused,
+                             const gchar *key)
+{
+	EPassMsg *msg;
+
+	g_return_if_fail (key != NULL);
+
+	msg = ep_msg_new (ep_forget_password);
+	msg->key = key;
+
+	ep_msg_send (msg);
+	ep_msg_free (msg);
+}
+
+/**
+ * e_passwords_get_password:
+ * @key: the key
+ *
+ * Returns: the password associated with @key, or %NULL.  Caller
+ * must free the returned password.
+ **/
+gchar *
+e_passwords_get_password (const gchar *unused,
+                          const gchar *key)
+{
+	EPassMsg *msg;
+	gchar *passwd;
+
+	g_return_val_if_fail (key != NULL, NULL);
+
+	msg = ep_msg_new (ep_get_password);
+	msg->key = key;
+
+	ep_msg_send (msg);
+
+	passwd = msg->password;
+	msg->password = NULL;
+	ep_msg_free (msg);
+
+	return passwd;
+}
+
+/**
+ * e_passwords_add_password:
+ * @key: a key
+ * @passwd: the password for @key
+ *
+ * This stores the @key/@passwd pair in the current session's password
+ * hash.
+ **/
+void
+e_passwords_add_password (const gchar *key,
+                          const gchar *passwd)
+{
+	EPassMsg *msg;
+
+	g_return_if_fail (key != NULL);
+	g_return_if_fail (passwd != NULL);
+
+	msg = ep_msg_new (ep_add_password);
+	msg->key = key;
+	msg->oldpass = passwd;
+
+	ep_msg_send (msg);
+	ep_msg_free (msg);
+}
+
+/**
+ * e_passwords_ask_password:
+ * @title: title for the password dialog
+ * @unused: this argument is no longer used
+ * @key: key to store the password under
+ * @prompt: prompt string
+ * @remember_type: whether or not to offer to remember the password,
+ * and for how long.
+ * @remember: on input, the default state of the remember checkbox.
+ * on output, the state of the checkbox when the dialog was closed.
+ * @parent: parent window of the dialog, or %NULL
+ *
+ * Asks the user for a password.
+ *
+ * Returns: the password, which the caller must free, or %NULL if
+ * the user cancelled the operation. * remember will be set if the
+ * return value is non-%NULL and @remember_type is not
+ * E_PASSWORDS_DO_NOT_REMEMBER.
+ **/
+gchar *
+e_passwords_ask_password (const gchar *title,
+                          const gchar *unused,
+                          const gchar *key,
+                          const gchar *prompt,
+                          EPasswordsRememberType remember_type,
+                          gboolean *remember,
+                          GtkWindow *parent)
+{
+	gchar *passwd;
+	EPassMsg *msg;
+
+	g_return_val_if_fail (key != NULL, NULL);
+
+	if ((remember_type & E_PASSWORDS_ONLINE) && !ep_online_state)
+		return NULL;
+
+	msg = ep_msg_new (ep_ask_password);
+	msg->title = title;
+	msg->key = key;
+	msg->prompt = prompt;
+	msg->flags = remember_type;
+	msg->remember = remember;
+	msg->parent = parent;
+
+	ep_msg_send (msg);
+	passwd = msg->password;
+	msg->password = NULL;
+	ep_msg_free (msg);
+
+	return passwd;
+}
diff --git a/e-util/e-passwords.h b/e-util/e-passwords.h
new file mode 100644
index 0000000..83a4a7e
--- /dev/null
+++ b/e-util/e-passwords.h
@@ -0,0 +1,81 @@
+/*
+ * e-passwords.h
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that 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 program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
+ * USA.
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef EDS_DISABLE_DEPRECATED
+
+#ifndef _E_PASSWORD_H_
+#define _E_PASSWORD_H_
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+/*
+ * initialization is now implicit when you call any of the functions
+ * below, although this is only correct if the functions are called
+ * from the main thread.
+ *
+ * e_passwords_shutdown should be called at exit time to synch the
+ * password on-disk storage, and to free up in-memory storage. */
+void e_passwords_init (void);
+
+void        e_passwords_shutdown          (void);
+void	    e_passwords_cancel (void);
+void        e_passwords_set_online (gint state);
+void        e_passwords_remember_password (const gchar *unused, const gchar *key);
+void        e_passwords_add_password      (const gchar *key, const gchar *passwd);
+gchar       *e_passwords_get_password      (const gchar *unused, const gchar *key);
+void        e_passwords_forget_password   (const gchar *unused, const gchar *key);
+void        e_passwords_forget_passwords  (void);
+void        e_passwords_clear_passwords (const gchar *unused);
+
+typedef enum {
+	E_PASSWORDS_REMEMBER_NEVER,
+	E_PASSWORDS_REMEMBER_SESSION,
+	E_PASSWORDS_REMEMBER_FOREVER,
+	E_PASSWORDS_REMEMBER_MASK = 0xf,
+
+	/* option bits */
+	E_PASSWORDS_SECRET = 1 << 8,
+	E_PASSWORDS_REPROMPT = 1 << 9,
+	E_PASSWORDS_ONLINE = 1<<10, /* only ask if we're online */
+	E_PASSWORDS_DISABLE_REMEMBER = 1<<11, /* disable the 'remember password' checkbox */
+	E_PASSWORDS_PASSPHRASE = 1<<12 /* We are asking a passphrase */
+} EPasswordsRememberType;
+
+gchar *      e_passwords_ask_password     (const gchar *title,
+					   const gchar *unused,
+					   const gchar *key,
+					   const gchar *prompt,
+					   EPasswordsRememberType remember_type,
+					   gboolean *remember,
+					   GtkWindow *parent);
+
+G_END_DECLS
+
+#endif /* _E_PASSWORD_H_ */
+
+#endif /* EDS_DISABLE_DEPRECATED */
diff --git a/e-util/e-picture-gallery.c b/e-util/e-picture-gallery.c
new file mode 100644
index 0000000..d95a0c9
--- /dev/null
+++ b/e-util/e-picture-gallery.c
@@ -0,0 +1,437 @@
+/*
+ * e-picture-gallery.c
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-picture-gallery.h"
+
+#include "e-icon-factory.h"
+
+#define E_PICTURE_GALLERY_GET_PRIVATE(obj) \
+	(G_TYPE_INSTANCE_GET_PRIVATE \
+	((obj), E_TYPE_PICTURE_GALLERY, EPictureGalleryPrivate))
+
+struct _EPictureGalleryPrivate {
+	gboolean initialized;
+	gchar *path;
+	GFileMonitor *monitor;
+};
+
+enum {
+	PROP_0,
+	PROP_PATH
+};
+
+enum {
+	COL_PIXBUF = 0,
+	COL_URI,
+	COL_FILENAME_TEXT
+};
+
+G_DEFINE_TYPE (EPictureGallery, e_picture_gallery, GTK_TYPE_ICON_VIEW)
+
+static gboolean
+update_file_iter (GtkListStore *list_store,
+                  GtkTreeIter *iter,
+                  GFile *file,
+                  gboolean force_thumbnail_update)
+{
+	GFileInfo *file_info;
+	gchar *uri;
+	gboolean res = FALSE;
+
+	g_return_val_if_fail (list_store != NULL, FALSE);
+	g_return_val_if_fail (iter != NULL, FALSE);
+	g_return_val_if_fail (file != NULL, FALSE);
+
+	uri = g_file_get_uri (file);
+
+	file_info = g_file_query_info (
+		file,
+		G_FILE_ATTRIBUTE_THUMBNAIL_PATH ","
+		G_FILE_ATTRIBUTE_THUMBNAILING_FAILED ","
+		G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME ","
+		G_FILE_ATTRIBUTE_STANDARD_SIZE,
+		G_FILE_QUERY_INFO_NONE,
+		NULL,
+		NULL);
+
+	if (file_info != NULL) {
+		const gchar *existing_thumb = g_file_info_get_attribute_byte_string (file_info, G_FILE_ATTRIBUTE_THUMBNAIL_PATH);
+		gchar *new_thumb = NULL;
+
+		if (!existing_thumb || force_thumbnail_update) {
+			gchar *filename;
+
+			filename = g_file_get_path (file);
+			if (filename) {
+				new_thumb = e_icon_factory_create_thumbnail (filename);
+				if (new_thumb)
+					existing_thumb = new_thumb;
+				g_free (filename);
+			}
+		}
+
+		if (existing_thumb && !g_file_info_get_attribute_boolean (file_info, G_FILE_ATTRIBUTE_THUMBNAILING_FAILED)) {
+			GdkPixbuf * pixbuf;
+
+			pixbuf = gdk_pixbuf_new_from_file (existing_thumb, NULL);
+
+			if (pixbuf) {
+				const gchar *filename;
+				gchar *filename_text = NULL;
+				guint64 filesize;
+
+				filename = g_file_info_get_attribute_string (file_info, G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME);
+				if (filename) {
+					filesize = g_file_info_get_attribute_uint64 (file_info, G_FILE_ATTRIBUTE_STANDARD_SIZE);
+					if (filesize) {
+						gchar *tmp = g_format_size ((goffset) filesize);
+						filename_text = g_strdup_printf ("%s (%s)", filename, tmp);
+						g_free (tmp);
+					}
+
+					res = TRUE;
+					gtk_list_store_set (
+						list_store, iter,
+						COL_PIXBUF, pixbuf,
+						COL_URI, uri,
+						COL_FILENAME_TEXT, filename_text ? filename_text : filename,
+						-1);
+				}
+
+				g_object_unref (pixbuf);
+				g_free (filename_text);
+			}
+		}
+
+		g_free (new_thumb);
+	}
+
+	g_free (uri);
+
+	return res;
+}
+
+static void
+add_file (GtkListStore *list_store,
+          GFile *file)
+{
+	GtkTreeIter iter;
+
+	g_return_if_fail (list_store != NULL);
+	g_return_if_fail (file != NULL);
+
+	gtk_list_store_append (list_store, &iter);
+	if (!update_file_iter (list_store, &iter, file, FALSE))
+		gtk_list_store_remove (list_store, &iter);
+}
+
+static gboolean
+find_file_uri (GtkListStore *list_store,
+               const gchar *uri,
+               GtkTreeIter *iter)
+{
+	GtkTreeModel *model;
+
+	g_return_val_if_fail (list_store != NULL, FALSE);
+	g_return_val_if_fail (uri != NULL, FALSE);
+	g_return_val_if_fail (iter != NULL, FALSE);
+
+	model = GTK_TREE_MODEL (list_store);
+	g_return_val_if_fail (model != NULL, FALSE);
+
+	if (!gtk_tree_model_get_iter_first (model, iter))
+		return FALSE;
+
+	do {
+		gchar *iter_uri = NULL;
+
+		gtk_tree_model_get (
+			model, iter,
+			COL_URI, &iter_uri,
+			-1);
+
+		if (iter_uri && g_ascii_strcasecmp (uri, iter_uri) == 0) {
+			g_free (iter_uri);
+			return TRUE;
+		}
+
+		g_free (iter_uri);
+	} while (gtk_tree_model_iter_next (model, iter));
+
+	return FALSE;
+}
+
+static void
+picture_gallery_dir_changed_cb (GFileMonitor *monitor,
+                                GFile *file,
+                                GFile *other_file,
+                                GFileMonitorEvent event_type,
+                                EPictureGallery *gallery)
+{
+	gchar *uri;
+	GtkListStore *list_store;
+	GtkTreeIter iter;
+
+	g_return_if_fail (file != NULL);
+
+	list_store = GTK_LIST_STORE (gtk_icon_view_get_model (GTK_ICON_VIEW (gallery)));
+	g_return_if_fail (list_store != NULL);
+
+	uri = g_file_get_uri (file);
+	if (!uri)
+		return;
+
+	switch (event_type) {
+	case G_FILE_MONITOR_EVENT_CREATED:
+		if (find_file_uri (list_store, uri, &iter)) {
+			if (!update_file_iter (list_store, &iter, file, TRUE))
+				gtk_list_store_remove (list_store, &iter);
+		} else {
+			add_file (list_store, file);
+		}
+		break;
+	case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT:
+		if (find_file_uri (list_store, uri, &iter)) {
+			if (!update_file_iter (list_store, &iter, file, TRUE))
+				gtk_list_store_remove (list_store, &iter);
+		}
+		break;
+	case G_FILE_MONITOR_EVENT_DELETED:
+		if (find_file_uri (list_store, uri, &iter))
+			gtk_list_store_remove (list_store, &iter);
+		break;
+	default:
+		break;
+	}
+
+	g_free (uri);
+}
+
+static gboolean
+picture_gallery_start_loading_cb (EPictureGallery *gallery)
+{
+	GtkIconView *icon_view;
+	GtkListStore *list_store;
+	GDir *dir;
+	const gchar *dirname;
+
+	icon_view = GTK_ICON_VIEW (gallery);
+	list_store = GTK_LIST_STORE (gtk_icon_view_get_model (icon_view));
+	g_return_val_if_fail (list_store != NULL, FALSE);
+
+	dirname = e_picture_gallery_get_path (gallery);
+	if (!dirname)
+		return FALSE;
+
+	dir = g_dir_open (dirname, 0, NULL);
+	if (dir) {
+		GFile *file;
+		const gchar *basename;
+
+		while ((basename = g_dir_read_name (dir)) != NULL) {
+			gchar *filename;
+
+			filename = g_build_filename (dirname, basename, NULL);
+			file = g_file_new_for_path (filename);
+
+			add_file (list_store, file);
+
+			g_free (filename);
+			g_object_unref (file);
+		}
+
+		g_dir_close (dir);
+
+		file = g_file_new_for_path (dirname);
+		gallery->priv->monitor = g_file_monitor_directory (file, G_FILE_MONITOR_NONE, NULL, NULL);
+		g_object_unref (file);
+
+		if (gallery->priv->monitor)
+			g_signal_connect (
+				gallery->priv->monitor, "changed",
+				G_CALLBACK (picture_gallery_dir_changed_cb),
+				gallery);
+	}
+
+	g_object_unref (icon_view);
+
+	return FALSE;
+}
+
+const gchar *
+e_picture_gallery_get_path (EPictureGallery *gallery)
+{
+	g_return_val_if_fail (gallery != NULL, NULL);
+	g_return_val_if_fail (E_IS_PICTURE_GALLERY (gallery), NULL);
+	g_return_val_if_fail (gallery->priv != NULL, NULL);
+
+	return gallery->priv->path;
+}
+
+static void
+picture_gallery_set_path (EPictureGallery *gallery,
+                          const gchar *path)
+{
+	g_return_if_fail (E_IS_PICTURE_GALLERY (gallery));
+	g_return_if_fail (gallery->priv != NULL);
+
+	g_free (gallery->priv->path);
+
+	if (!path || !*path || !g_file_test (path, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR))
+		gallery->priv->path = g_strdup (g_get_user_special_dir (G_USER_DIRECTORY_PICTURES));
+	else
+		gallery->priv->path = g_strdup (path);
+}
+
+static void
+picture_gallery_get_property (GObject *object,
+                              guint property_id,
+                              GValue *value,
+                              GParamSpec *pspec)
+{
+	switch (property_id) {
+	case PROP_PATH:
+		g_value_set_string (value, e_picture_gallery_get_path (E_PICTURE_GALLERY (object)));
+		return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+picture_gallery_set_property (GObject *object,
+                              guint property_id,
+                              const GValue *value,
+                              GParamSpec *pspec)
+{
+	switch (property_id) {
+	case PROP_PATH:
+		picture_gallery_set_path (E_PICTURE_GALLERY (object), g_value_get_string (value));
+		return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+visible_cb (EPictureGallery *gallery)
+{
+	if (!gallery->priv->initialized && gtk_widget_get_visible (GTK_WIDGET (gallery))) {
+		gallery->priv->initialized = TRUE;
+
+		g_idle_add ((GSourceFunc) picture_gallery_start_loading_cb, gallery);
+	}
+}
+
+static void
+picture_gallery_constructed (GObject *object)
+{
+	GtkIconView *icon_view;
+	GtkListStore *list_store;
+	GtkTargetEntry *targets;
+	GtkTargetList *list;
+	gint n_targets;
+
+	/* Chain up to parent's constructed() method. */
+	G_OBJECT_CLASS (e_picture_gallery_parent_class)->constructed (object);
+
+	icon_view = GTK_ICON_VIEW (object);
+
+	list_store = gtk_list_store_new (3, GDK_TYPE_PIXBUF, G_TYPE_STRING, G_TYPE_STRING);
+	gtk_icon_view_set_model (icon_view, GTK_TREE_MODEL (list_store));
+	g_object_unref (list_store);
+
+	gtk_icon_view_set_pixbuf_column (icon_view, COL_PIXBUF);
+	gtk_icon_view_set_text_column (icon_view, COL_FILENAME_TEXT);
+	gtk_icon_view_set_tooltip_column (icon_view, -1);
+
+	list = gtk_target_list_new (NULL, 0);
+	gtk_target_list_add_uri_targets (list, 0);
+	targets = gtk_target_table_new_from_list (list, &n_targets);
+
+	gtk_icon_view_enable_model_drag_source (
+		icon_view, GDK_BUTTON1_MASK,
+		targets, n_targets, GDK_ACTION_COPY);
+
+	gtk_target_table_free (targets, n_targets);
+	gtk_target_list_unref (list);
+
+	g_signal_connect (object, "notify::visible", G_CALLBACK (visible_cb), NULL);
+}
+
+static void
+picture_gallery_dispose (GObject *object)
+{
+	EPictureGallery *gallery;
+
+	gallery = E_PICTURE_GALLERY (object);
+
+	if (gallery->priv->monitor) {
+		g_object_unref (gallery->priv->monitor);
+		gallery->priv->monitor = NULL;
+	}
+
+	/* Chain up to parent's dispose() method. */
+	G_OBJECT_CLASS (e_picture_gallery_parent_class)->dispose (object);
+}
+
+static void
+e_picture_gallery_class_init (EPictureGalleryClass *class)
+{
+	GObjectClass *object_class;
+
+	g_type_class_add_private (class, sizeof (EPictureGalleryPrivate));
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->get_property = picture_gallery_get_property;
+	object_class->set_property = picture_gallery_set_property;
+	object_class->constructed = picture_gallery_constructed;
+	object_class->dispose = picture_gallery_dispose;
+
+	g_object_class_install_property (
+		object_class,
+		PROP_PATH,
+		g_param_spec_string (
+			"path",
+			"Gallery path",
+			NULL,
+			NULL,
+			G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+}
+
+static void
+e_picture_gallery_init (EPictureGallery *gallery)
+{
+	gallery->priv = E_PICTURE_GALLERY_GET_PRIVATE (gallery);
+	gallery->priv->initialized = FALSE;
+	gallery->priv->monitor = NULL;
+	picture_gallery_set_path (gallery, NULL);
+}
+
+GtkWidget *
+e_picture_gallery_new (const gchar *path)
+{
+	return g_object_new (E_TYPE_PICTURE_GALLERY, "path", path, NULL);
+}
diff --git a/e-util/e-picture-gallery.h b/e-util/e-picture-gallery.h
new file mode 100644
index 0000000..653d990
--- /dev/null
+++ b/e-util/e-picture-gallery.h
@@ -0,0 +1,71 @@
+/*
+ * e-picture-gallery.h
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_PICTURE_GALLERY_H
+#define E_PICTURE_GALLERY_H
+
+#include <gtk/gtk.h>
+
+/* Standard GObject macros */
+#define E_TYPE_PICTURE_GALLERY \
+	(e_picture_gallery_get_type ())
+#define E_PICTURE_GALLERY(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_PICTURE_GALLERY, EPictureGallery))
+#define E_PICTURE_GALLERY_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_PICTURE_GALLERY, EPictureGalleryClass))
+#define E_IS_PICTURE_GALLERY(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_PICTURE_GALLERY))
+#define E_IS_PICTURE_GALLERY_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_PICTURE_GALLERY))
+#define E_PICTURE_GALLERY_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_PICTURE_GALLERY, EPictureGalleryClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EPictureGallery EPictureGallery;
+typedef struct _EPictureGalleryClass EPictureGalleryClass;
+typedef struct _EPictureGalleryPrivate EPictureGalleryPrivate;
+
+struct _EPictureGallery {
+	GtkIconView parent;
+	EPictureGalleryPrivate *priv;
+};
+
+struct _EPictureGalleryClass {
+	GtkIconViewClass parent_class;
+};
+
+GType		e_picture_gallery_get_type	(void);
+GtkWidget *	e_picture_gallery_new		(const gchar *path);
+const gchar *	e_picture_gallery_get_path	(EPictureGallery *gallery);
+
+G_END_DECLS
+
+#endif /* E_PICTURE_GALLERY_H */
diff --git a/e-util/e-plugin-ui.c b/e-util/e-plugin-ui.c
index 6e36654..3ef863c 100644
--- a/e-util/e-plugin-ui.c
+++ b/e-util/e-plugin-ui.c
@@ -21,7 +21,6 @@
 
 #include "e-plugin-ui.h"
 
-#include "e-util.h"
 #include "e-ui-manager.h"
 
 #include <string.h>
diff --git a/e-util/e-plugin-ui.h b/e-util/e-plugin-ui.h
index e59b5f5..f56a6e0 100644
--- a/e-util/e-plugin-ui.h
+++ b/e-util/e-plugin-ui.h
@@ -15,6 +15,10 @@
  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
  */
 
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
 #ifndef E_PLUGIN_UI_H
 #define E_PLUGIN_UI_H
 
diff --git a/e-util/e-plugin.h b/e-util/e-plugin.h
index 047d944..b67bde5 100644
--- a/e-util/e-plugin.h
+++ b/e-util/e-plugin.h
@@ -19,6 +19,10 @@
  *
  */
 
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
 #ifndef _E_PLUGIN_H
 #define _E_PLUGIN_H
 
diff --git a/e-util/e-poolv.h b/e-util/e-poolv.h
index e3cfb31..f1b4654 100644
--- a/e-util/e-poolv.h
+++ b/e-util/e-poolv.h
@@ -16,6 +16,10 @@
  *
  */
 
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
 #ifndef E_POOLV_H
 #define E_POOLV_H
 
diff --git a/widgets/misc/e-popup-action.c b/e-util/e-popup-action.c
similarity index 100%
rename from widgets/misc/e-popup-action.c
rename to e-util/e-popup-action.c
diff --git a/e-util/e-popup-action.h b/e-util/e-popup-action.h
new file mode 100644
index 0000000..62e7b8e
--- /dev/null
+++ b/e-util/e-popup-action.h
@@ -0,0 +1,96 @@
+/*
+ * e-popup-action.h
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+/* A popup action is an action that lives in a popup menu.  It proxies an
+ * equivalent action in the main menu, with two differences:
+ *
+ * 1) If the main menu action is insensitive, the popup action is invisible.
+ * 2) The popup action may have a different label than the main menu action.
+ *
+ * To use:
+ *
+ * Create an array of EPopupActionEntry structs.  Add the main menu actions
+ * that serve as related actions for the popup actions to an action group
+ * first.  Then pass the same action group and the EPopupActionEntry array
+ * to e_action_group_add_popup_actions() to add popup actions.
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_POPUP_ACTION_H
+#define E_POPUP_ACTION_H
+
+#include <gtk/gtk.h>
+
+/* Standard GObject macros */
+#define E_TYPE_POPUP_ACTION \
+	(e_popup_action_get_type ())
+#define E_POPUP_ACTION(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_POPUP_ACTION, EPopupAction))
+#define E_POPUP_ACTION_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_POPUP_ACTION, EPopupActionClass))
+#define E_IS_POPUP_ACTION(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_POPUP_ACTION))
+#define E_IS_POPUP_ACTION_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_POPUP_ACTION))
+#define E_POPUP_ACTION_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_POPUP_ACTION, EPopupActionClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EPopupAction EPopupAction;
+typedef struct _EPopupActionClass EPopupActionClass;
+typedef struct _EPopupActionPrivate EPopupActionPrivate;
+typedef struct _EPopupActionEntry EPopupActionEntry;
+
+struct _EPopupAction {
+	GtkAction parent;
+	EPopupActionPrivate *priv;
+};
+
+struct _EPopupActionClass {
+	GtkActionClass parent_class;
+};
+
+struct _EPopupActionEntry {
+	const gchar *name;
+	const gchar *label;	/* optional: overrides the related action */
+	const gchar *related;	/* name of the related action */
+};
+
+GType		e_popup_action_get_type		(void);
+EPopupAction *	e_popup_action_new		(const gchar *name);
+
+void		e_action_group_add_popup_actions
+						(GtkActionGroup *action_group,
+						 const EPopupActionEntry *entries,
+						 guint n_entries);
+
+G_END_DECLS
+
+#endif /* E_POPUP_ACTION_H */
diff --git a/widgets/table/e-popup-menu.c b/e-util/e-popup-menu.c
similarity index 100%
rename from widgets/table/e-popup-menu.c
rename to e-util/e-popup-menu.c
diff --git a/e-util/e-popup-menu.h b/e-util/e-popup-menu.h
new file mode 100644
index 0000000..ec0979c
--- /dev/null
+++ b/e-util/e-popup-menu.h
@@ -0,0 +1,59 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Miguel de Icaza <miguel ximian com>
+ *		Jody Goldberg (jgoldberg home com)
+ *		Jeffrey Stedfast <fejj ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_POPUP_MENU_H
+#define E_POPUP_MENU_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define E_POPUP_SEPARATOR  { (gchar *) "", NULL, (NULL), 0 }
+#define E_POPUP_TERMINATOR { NULL, NULL, (NULL), 0 }
+
+#define E_POPUP_ITEM(name,fn,disable_mask) \
+	{ (gchar *) (name), NULL, (fn), (disable_mask) }
+
+typedef struct _EPopupMenu EPopupMenu;
+
+struct _EPopupMenu {
+	gchar *name;
+	gchar *pixname;
+	GCallback fn;
+	guint32 disable_mask;
+};
+
+GtkMenu *	e_popup_menu_create_with_domain	(EPopupMenu *menu_list,
+						 guint32 disable_mask,
+						 guint32 hide_mask,
+						 gpointer default_closure,
+						 const gchar *domain);
+
+G_END_DECLS
+
+#endif /* E_POPUP_MENU_H */
diff --git a/widgets/misc/e-port-entry.c b/e-util/e-port-entry.c
similarity index 100%
rename from widgets/misc/e-port-entry.c
rename to e-util/e-port-entry.c
diff --git a/e-util/e-port-entry.h b/e-util/e-port-entry.h
new file mode 100644
index 0000000..fc0eaa0
--- /dev/null
+++ b/e-util/e-port-entry.h
@@ -0,0 +1,91 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
+ * USA
+ *
+ * Authors:
+ *	Dan Vratil <dvratil redhat com>
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_PORT_ENTRY_H
+#define E_PORT_ENTRY_H
+
+#include <gtk/gtk.h>
+#include <camel/camel.h>
+
+/* Standard GObject macros */
+#define E_TYPE_PORT_ENTRY \
+	(e_port_entry_get_type ())
+#define E_PORT_ENTRY(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_PORT_ENTRY, EPortEntry))
+#define E_PORT_ENTRY_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_PORT_ENTRY, EPortEntryClass))
+#define E_IS_PORT_ENTRY(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_PORT_ENTRY))
+#define E_IS_PORT_ENTRY_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_PORT_ENTRY))
+#define E_PORT_ENTRY_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_PORT_ENTRY, EPortEntryClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EPortEntry EPortEntry;
+typedef struct _EPortEntryClass EPortEntryClass;
+typedef struct _EPortEntryPrivate EPortEntryPrivate;
+
+struct _EPortEntry {
+	GtkComboBox parent;
+	EPortEntryPrivate *priv;
+};
+
+struct _EPortEntryClass {
+	GtkComboBoxClass parent_class;
+};
+
+GType		e_port_entry_get_type	(void) G_GNUC_CONST;
+GtkWidget *	e_port_entry_new	(void);
+void		e_port_entry_set_camel_entries
+					(EPortEntry *port_entry,
+					 CamelProviderPortEntry *entries);
+gint		e_port_entry_get_port	(EPortEntry *port_entry);
+void		e_port_entry_set_port	(EPortEntry *port_entry,
+					 gint port);
+gboolean	e_port_entry_is_valid	(EPortEntry *port_entry);
+CamelNetworkSecurityMethod
+		e_port_entry_get_security_method
+					(EPortEntry *port_entry);
+void		e_port_entry_set_security_method
+					(EPortEntry *port_entry,
+					 CamelNetworkSecurityMethod method);
+void		e_port_entry_activate_secured_port
+					(EPortEntry *port_entry,
+					 gint index);
+void		e_port_entry_activate_nonsecured_port
+					(EPortEntry *port_entry,
+					 gint index);
+
+G_END_DECLS
+
+#endif /* E_PORT_ENTRY_H */
diff --git a/e-util/e-preferences-window.c b/e-util/e-preferences-window.c
new file mode 100644
index 0000000..fbe6c01
--- /dev/null
+++ b/e-util/e-preferences-window.c
@@ -0,0 +1,643 @@
+/*
+ * e-preferences-window.c
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-preferences-window.h"
+
+#include <glib/gi18n.h>
+#include <gdk/gdkkeysyms.h>
+
+#include "e-misc-utils.h"
+
+#define SWITCH_PAGE_INTERVAL 250
+
+#define E_PREFERENCES_WINDOW_GET_PRIVATE(obj) \
+	(G_TYPE_INSTANCE_GET_PRIVATE \
+	((obj), E_TYPE_PREFERENCES_WINDOW, EPreferencesWindowPrivate))
+
+struct _EPreferencesWindowPrivate {
+	gboolean   setup;
+	gpointer   shell;
+
+	GtkWidget *icon_view;
+	GtkWidget *scroll;
+	GtkWidget *notebook;
+	GHashTable *index;
+
+	GtkListStore *store;
+	GtkTreeModelFilter *filter;
+	const gchar *filter_view;
+};
+
+enum {
+	COLUMN_ID,	/* G_TYPE_STRING */
+	COLUMN_TEXT,	/* G_TYPE_STRING */
+	COLUMN_HELP,	/* G_TYPE_STRING */
+	COLUMN_PIXBUF,	/* GDK_TYPE_PIXBUF */
+	COLUMN_PAGE,	/* G_TYPE_INT */
+	COLUMN_SORT	/* G_TYPE_INT */
+};
+
+G_DEFINE_TYPE (
+	EPreferencesWindow,
+	e_preferences_window,
+	GTK_TYPE_WINDOW)
+
+static gboolean
+preferences_window_filter_view (GtkTreeModel *model,
+                                GtkTreeIter *iter,
+                                EPreferencesWindow *window)
+{
+	gchar *str;
+	gboolean visible = FALSE;
+
+	if (!window->priv->filter_view)
+		return TRUE;
+
+	gtk_tree_model_get (model, iter, COLUMN_ID, &str, -1);
+	if (strncmp (window->priv->filter_view, "mail", 4) == 0) {
+		/* Show everything except calendar */
+		if (str && (strncmp (str, "cal", 3) == 0))
+			visible = FALSE;
+		else
+			visible = TRUE;
+	} else if (strncmp (window->priv->filter_view, "cal", 3) == 0) {
+		/* Show only calendar and nothing else */
+		if (str && (strncmp (str, "cal", 3) != 0))
+			visible = FALSE;
+		else
+			visible = TRUE;
+
+	} else  /* In any other case, show everything */
+		visible = TRUE;
+
+	g_free (str);
+
+	return visible;
+}
+
+static GdkPixbuf *
+preferences_window_load_pixbuf (const gchar *icon_name)
+{
+	GtkIconTheme *icon_theme;
+	GtkIconInfo *icon_info;
+	GdkPixbuf *pixbuf;
+	const gchar *filename;
+	gint size;
+	GError *error = NULL;
+
+	icon_theme = gtk_icon_theme_get_default ();
+
+	if (!gtk_icon_size_lookup (GTK_ICON_SIZE_DIALOG, &size, 0))
+		return NULL;
+
+	icon_info = gtk_icon_theme_lookup_icon (
+		icon_theme, icon_name, size, 0);
+
+	if (icon_info == NULL)
+		return NULL;
+
+	filename = gtk_icon_info_get_filename (icon_info);
+
+	pixbuf = gdk_pixbuf_new_from_file (filename, &error);
+
+	gtk_icon_info_free (icon_info);
+
+	if (error != NULL) {
+		g_warning ("%s", error->message);
+		g_error_free (error);
+	}
+
+	return pixbuf;
+}
+
+static void
+preferences_window_help_clicked_cb (EPreferencesWindow *window)
+{
+	GtkTreeModel *model;
+	GtkTreeIter iter;
+	GList *list;
+	gchar *help = NULL;
+
+	g_return_if_fail (window != NULL);
+
+	model = GTK_TREE_MODEL (window->priv->filter);
+	list = gtk_icon_view_get_selected_items (
+		GTK_ICON_VIEW (window->priv->icon_view));
+
+	if (list != NULL) {
+		gtk_tree_model_get_iter (model, &iter, list->data);
+		gtk_tree_model_get (model, &iter, COLUMN_HELP, &help, -1);
+
+	} else if (gtk_tree_model_get_iter_first (model, &iter)) {
+		gint page_index, current_index;
+
+		current_index = gtk_notebook_get_current_page (
+			GTK_NOTEBOOK (window->priv->notebook));
+		do {
+			gtk_tree_model_get (
+				model, &iter, COLUMN_PAGE, &page_index, -1);
+
+			if (page_index == current_index) {
+				gtk_tree_model_get (
+					model, &iter, COLUMN_HELP, &help, -1);
+				break;
+			}
+		} while (gtk_tree_model_iter_next (model, &iter));
+	}
+
+	e_display_help (GTK_WINDOW (window), help ? help : "index");
+
+	g_free (help);
+}
+
+static void
+preferences_window_selection_changed_cb (EPreferencesWindow *window)
+{
+	GtkIconView *icon_view;
+	GtkNotebook *notebook;
+	GtkTreeModel *model;
+	GtkTreeIter iter;
+	GList *list;
+	gint page;
+
+	icon_view = GTK_ICON_VIEW (window->priv->icon_view);
+	list = gtk_icon_view_get_selected_items (icon_view);
+	if (list == NULL)
+		return;
+
+	model = GTK_TREE_MODEL (window->priv->filter);
+	gtk_tree_model_get_iter (model, &iter, list->data);
+	gtk_tree_model_get (model, &iter, COLUMN_PAGE, &page, -1);
+
+	notebook = GTK_NOTEBOOK (window->priv->notebook);
+	gtk_notebook_set_current_page (notebook, page);
+
+	g_list_foreach (list, (GFunc) gtk_tree_path_free, NULL);
+	g_list_free (list);
+
+	gtk_widget_grab_focus (GTK_WIDGET (icon_view));
+}
+
+static void
+preferences_window_dispose (GObject *object)
+{
+	EPreferencesWindowPrivate *priv;
+
+	priv = E_PREFERENCES_WINDOW_GET_PRIVATE (object);
+
+	if (priv->icon_view != NULL) {
+		g_object_unref (priv->icon_view);
+		priv->icon_view = NULL;
+	}
+
+	if (priv->notebook != NULL) {
+		g_object_unref (priv->notebook);
+		priv->notebook = NULL;
+	}
+
+	if (priv->shell) {
+		g_object_remove_weak_pointer (priv->shell, &priv->shell);
+		priv->shell = NULL;
+	}
+
+	g_hash_table_remove_all (priv->index);
+
+	/* Chain up to parent's dispose() method. */
+	G_OBJECT_CLASS (e_preferences_window_parent_class)->dispose (object);
+}
+
+static void
+preferences_window_finalize (GObject *object)
+{
+	EPreferencesWindowPrivate *priv;
+
+	priv = E_PREFERENCES_WINDOW_GET_PRIVATE (object);
+
+	g_hash_table_destroy (priv->index);
+
+	/* Chain up to parent's finalize() method. */
+	G_OBJECT_CLASS (e_preferences_window_parent_class)->finalize (object);
+}
+
+static void
+preferences_window_show (GtkWidget *widget)
+{
+	EPreferencesWindowPrivate *priv;
+	GtkIconView *icon_view;
+	GtkTreePath *path;
+
+	priv = E_PREFERENCES_WINDOW_GET_PRIVATE (widget);
+	if (!priv->setup)
+		g_warning ("Preferences window has not been setup correctly");
+
+	icon_view = GTK_ICON_VIEW (priv->icon_view);
+
+	path = gtk_tree_path_new_first ();
+	gtk_icon_view_select_path (icon_view, path);
+	gtk_icon_view_scroll_to_path (icon_view, path, FALSE, 0.0, 0.0);
+	gtk_tree_path_free (path);
+
+	gtk_widget_grab_focus (priv->icon_view);
+
+	/* Chain up to parent's show() method. */
+	GTK_WIDGET_CLASS (e_preferences_window_parent_class)->show (widget);
+}
+
+static void
+e_preferences_window_class_init (EPreferencesWindowClass *class)
+{
+	GObjectClass *object_class;
+	GtkWidgetClass *widget_class;
+
+	g_type_class_add_private (class, sizeof (EPreferencesWindowPrivate));
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->dispose = preferences_window_dispose;
+	object_class->finalize = preferences_window_finalize;
+
+	widget_class = GTK_WIDGET_CLASS (class);
+	widget_class->show = preferences_window_show;
+}
+
+static void
+e_preferences_window_init (EPreferencesWindow *window)
+{
+	GtkListStore *store;
+	GtkWidget *container;
+	GtkWidget *hbox;
+	GtkWidget *vbox;
+	GtkWidget *widget;
+	GHashTable *index;
+	const gchar *title;
+	GtkAccelGroup *accel_group;
+
+	index = g_hash_table_new_full (
+		g_str_hash, g_str_equal,
+		(GDestroyNotify) g_free,
+		(GDestroyNotify) gtk_tree_row_reference_free);
+
+	window->priv = E_PREFERENCES_WINDOW_GET_PRIVATE (window);
+	window->priv->index = index;
+	window->priv->filter_view = NULL;
+
+	store = gtk_list_store_new (
+		6, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
+		GDK_TYPE_PIXBUF, G_TYPE_INT, G_TYPE_INT);
+	gtk_tree_sortable_set_sort_column_id (
+		GTK_TREE_SORTABLE (store), COLUMN_SORT, GTK_SORT_ASCENDING);
+	window->priv->store = store;
+
+	window->priv->filter = (GtkTreeModelFilter *)
+		gtk_tree_model_filter_new (GTK_TREE_MODEL (store), NULL);
+	gtk_tree_model_filter_set_visible_func (
+		window->priv->filter, (GtkTreeModelFilterVisibleFunc)
+		preferences_window_filter_view, window, NULL);
+
+	title = _("Evolution Preferences");
+	gtk_window_set_title (GTK_WINDOW (window), title);
+	gtk_window_set_resizable (GTK_WINDOW (window), TRUE);
+	gtk_container_set_border_width (GTK_CONTAINER (window), 12);
+
+	g_signal_connect (
+		window, "delete-event",
+		G_CALLBACK (gtk_widget_hide_on_delete), NULL);
+
+	widget = gtk_vbox_new (FALSE, 12);
+	gtk_container_add (GTK_CONTAINER (window), widget);
+	gtk_widget_show (widget);
+
+	vbox = widget;
+
+	widget = gtk_hbox_new (FALSE, 12);
+	gtk_box_pack_start (GTK_BOX (vbox), widget, TRUE, TRUE, 0);
+	gtk_widget_show (widget);
+
+	hbox = widget;
+
+	widget = gtk_scrolled_window_new (NULL, NULL);
+	gtk_scrolled_window_set_policy (
+		GTK_SCROLLED_WINDOW (widget),
+		GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
+	gtk_scrolled_window_set_shadow_type (
+		GTK_SCROLLED_WINDOW (widget), GTK_SHADOW_IN);
+	gtk_box_pack_start (GTK_BOX (hbox), widget, FALSE, TRUE, 0);
+	window->priv->scroll = widget;
+	gtk_widget_show (widget);
+
+	container = widget;
+
+	widget = gtk_icon_view_new_with_model (
+		GTK_TREE_MODEL (window->priv->filter));
+	gtk_icon_view_set_columns (GTK_ICON_VIEW (widget), 1);
+	gtk_icon_view_set_text_column (GTK_ICON_VIEW (widget), COLUMN_TEXT);
+	gtk_icon_view_set_pixbuf_column (GTK_ICON_VIEW (widget), COLUMN_PIXBUF);
+	g_signal_connect_swapped (
+		widget, "selection-changed",
+		G_CALLBACK (preferences_window_selection_changed_cb), window);
+	gtk_container_add (GTK_CONTAINER (container), widget);
+	window->priv->icon_view = g_object_ref (widget);
+	gtk_widget_show (widget);
+	g_object_unref (store);
+
+	widget = gtk_notebook_new ();
+	gtk_notebook_set_show_tabs (GTK_NOTEBOOK (widget), FALSE);
+	gtk_notebook_set_show_border (GTK_NOTEBOOK (widget), FALSE);
+	gtk_box_pack_start (GTK_BOX (hbox), widget, TRUE, TRUE, 0);
+	window->priv->notebook = g_object_ref (widget);
+	gtk_widget_show (widget);
+
+	widget = gtk_hbutton_box_new ();
+	gtk_button_box_set_layout (
+		GTK_BUTTON_BOX (widget), GTK_BUTTONBOX_END);
+	gtk_box_pack_start (GTK_BOX (vbox), widget, FALSE, FALSE, 0);
+	gtk_widget_show (widget);
+
+	container = widget;
+
+	widget = gtk_button_new_from_stock (GTK_STOCK_HELP);
+	g_signal_connect_swapped (
+		widget, "clicked",
+		G_CALLBACK (preferences_window_help_clicked_cb), window);
+	gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+	gtk_button_box_set_child_secondary (
+		GTK_BUTTON_BOX (container), widget, TRUE);
+	gtk_widget_show (widget);
+
+	widget = gtk_button_new_from_stock (GTK_STOCK_CLOSE);
+	g_signal_connect_swapped (
+		widget, "clicked",
+		G_CALLBACK (gtk_widget_hide), window);
+	gtk_widget_set_can_default (widget, TRUE);
+	gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+	accel_group = gtk_accel_group_new ();
+	gtk_widget_add_accelerator (
+		widget, "activate", accel_group,
+		GDK_KEY_Escape, (GdkModifierType) 0,
+		GTK_ACCEL_VISIBLE);
+	gtk_window_add_accel_group (GTK_WINDOW (window), accel_group);
+	gtk_widget_grab_default (widget);
+	gtk_widget_show (widget);
+}
+
+GtkWidget *
+e_preferences_window_new (gpointer shell)
+{
+	EPreferencesWindow *window;
+
+	window = g_object_new (E_TYPE_PREFERENCES_WINDOW, NULL);
+
+	/* ideally should be an object property */
+	window->priv->shell = shell;
+	if (shell)
+		g_object_add_weak_pointer (shell, &window->priv->shell);
+
+	return GTK_WIDGET (window);
+}
+
+gpointer
+e_preferences_window_get_shell (EPreferencesWindow *window)
+{
+	g_return_val_if_fail (E_IS_PREFERENCES_WINDOW (window), NULL);
+
+	return window->priv->shell;
+}
+
+void
+e_preferences_window_add_page (EPreferencesWindow *window,
+                               const gchar *page_name,
+                               const gchar *icon_name,
+                               const gchar *caption,
+                               const gchar *help_target,
+                               EPreferencesWindowCreatePageFn create_fn,
+                               gint sort_order)
+{
+	GtkTreeRowReference *reference;
+	GtkIconView *icon_view;
+	GtkNotebook *notebook;
+	GtkTreeModel *model;
+	GtkTreePath *path;
+	GHashTable *index;
+	GdkPixbuf *pixbuf;
+	GtkTreeIter iter;
+	GtkWidget *align;
+	gint page;
+
+	g_return_if_fail (E_IS_PREFERENCES_WINDOW (window));
+	g_return_if_fail (create_fn != NULL);
+	g_return_if_fail (page_name != NULL);
+	g_return_if_fail (icon_name != NULL);
+	g_return_if_fail (caption != NULL);
+
+	icon_view = GTK_ICON_VIEW (window->priv->icon_view);
+	notebook = GTK_NOTEBOOK (window->priv->notebook);
+
+	page = gtk_notebook_get_n_pages (notebook);
+	model = GTK_TREE_MODEL (window->priv->store);
+	pixbuf = preferences_window_load_pixbuf (icon_name);
+
+	gtk_list_store_append (GTK_LIST_STORE (model), &iter);
+
+	gtk_list_store_set (
+		GTK_LIST_STORE (model), &iter,
+		COLUMN_ID, page_name,
+		COLUMN_TEXT, caption,
+		COLUMN_HELP, help_target,
+		COLUMN_PIXBUF, pixbuf,
+		COLUMN_PAGE, page,
+		COLUMN_SORT, sort_order,
+		-1);
+
+	index = window->priv->index;
+	path = gtk_tree_model_get_path (model, &iter);
+	reference = gtk_tree_row_reference_new (model, path);
+	g_hash_table_insert (index, g_strdup (page_name), reference);
+	gtk_tree_path_free (path);
+
+	align = g_object_new (GTK_TYPE_ALIGNMENT, NULL);
+	gtk_widget_show (GTK_WIDGET (align));
+	g_object_set_data (G_OBJECT (align), "create_fn", create_fn);
+	gtk_notebook_append_page (notebook, align, NULL);
+	gtk_container_child_set (
+		GTK_CONTAINER (notebook), align,
+		"tab-fill", FALSE, "tab-expand", FALSE, NULL);
+
+	/* Force GtkIconView to recalculate the text wrap width,
+	 * otherwise we get a really narrow icon list on the left
+	 * side of the preferences window. */
+	gtk_icon_view_set_item_width (icon_view, -1);
+	gtk_widget_queue_resize (GTK_WIDGET (window));
+}
+
+void
+e_preferences_window_show_page (EPreferencesWindow *window,
+                                const gchar *page_name)
+{
+	GtkTreeRowReference *reference;
+	GtkIconView *icon_view;
+	GtkTreePath *path;
+
+	g_return_if_fail (E_IS_PREFERENCES_WINDOW (window));
+	g_return_if_fail (page_name != NULL);
+	g_return_if_fail (window->priv->setup);
+
+	icon_view = GTK_ICON_VIEW (window->priv->icon_view);
+	reference = g_hash_table_lookup (window->priv->index, page_name);
+	g_return_if_fail (reference != NULL);
+
+	path = gtk_tree_row_reference_get_path (reference);
+	gtk_icon_view_select_path (icon_view, path);
+	gtk_icon_view_scroll_to_path (icon_view, path, FALSE, 0.0, 0.0);
+	gtk_tree_path_free (path);
+}
+
+void
+e_preferences_window_filter_page (EPreferencesWindow *window,
+                                  const gchar *page_name)
+{
+	GtkTreeRowReference *reference;
+	GtkIconView *icon_view;
+	GtkTreePath *path;
+
+	g_return_if_fail (E_IS_PREFERENCES_WINDOW (window));
+	g_return_if_fail (page_name != NULL);
+	g_return_if_fail (window->priv->setup);
+
+	icon_view = GTK_ICON_VIEW (window->priv->icon_view);
+	reference = g_hash_table_lookup (window->priv->index, page_name);
+	g_return_if_fail (reference != NULL);
+
+	path = gtk_tree_row_reference_get_path (reference);
+	gtk_icon_view_select_path (icon_view, path);
+	gtk_icon_view_scroll_to_path (icon_view, path, FALSE, 0.0, 0.0);
+	gtk_tree_path_free (path);
+
+	window->priv->filter_view = page_name;
+	gtk_tree_model_filter_refilter (window->priv->filter);
+
+	/* XXX: We need a better solution to hide the icon view when
+	 * there is just one entry */
+	if (strncmp (page_name, "cal", 3) == 0) {
+		gtk_widget_hide (window->priv->scroll);
+	} else
+		gtk_widget_show (window->priv->scroll);
+}
+
+/*
+ * Create all the deferred configuration pages.
+ */
+void
+e_preferences_window_setup (EPreferencesWindow *window)
+{
+	gint i, num;
+	GtkNotebook *notebook;
+	GtkRequisition requisition;
+	gint width = -1, height = -1, content_width = -1, content_height = -1;
+	EPreferencesWindowPrivate *priv;
+
+	g_return_if_fail (E_IS_PREFERENCES_WINDOW (window));
+
+	priv = E_PREFERENCES_WINDOW_GET_PRIVATE (window);
+
+	if (priv->setup)
+		return;
+
+	gtk_window_get_default_size (GTK_WINDOW (window), &width, &height);
+	if (width < 0 || height < 0) {
+		gtk_widget_get_preferred_size (GTK_WIDGET (window), &requisition, NULL);
+
+		width = requisition.width;
+		height = requisition.height;
+	}
+
+	notebook = GTK_NOTEBOOK (priv->notebook);
+	num = gtk_notebook_get_n_pages (notebook);
+
+	for (i = 0; i < num; i++) {
+		GtkBin *align;
+		GtkWidget *content;
+		EPreferencesWindowCreatePageFn create_fn;
+
+		align = GTK_BIN (gtk_notebook_get_nth_page (notebook, i));
+		create_fn = g_object_get_data (G_OBJECT (align), "create_fn");
+
+		if (!create_fn || gtk_bin_get_child (align))
+			continue;
+
+		content = create_fn (window);
+		if (content) {
+			GtkScrolledWindow *scrolled;
+
+			scrolled = GTK_SCROLLED_WINDOW (gtk_scrolled_window_new (NULL, NULL));
+			gtk_scrolled_window_add_with_viewport (scrolled, content);
+			gtk_scrolled_window_set_min_content_width (scrolled, 320);
+			gtk_scrolled_window_set_min_content_height (scrolled, 240);
+			gtk_scrolled_window_set_policy (scrolled, GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
+			gtk_scrolled_window_set_shadow_type (scrolled, GTK_SHADOW_NONE);
+
+			gtk_viewport_set_shadow_type (
+				GTK_VIEWPORT (gtk_bin_get_child (GTK_BIN (scrolled))),
+				GTK_SHADOW_NONE);
+
+			gtk_widget_show (content);
+
+			gtk_widget_get_preferred_size (GTK_WIDGET (content), &requisition, NULL);
+
+			if (requisition.width > content_width)
+				content_width = requisition.width;
+			if (requisition.height > content_height)
+				content_height = requisition.height;
+
+			gtk_widget_show (GTK_WIDGET (scrolled));
+
+			gtk_container_add (GTK_CONTAINER (align), GTK_WIDGET (scrolled));
+		}
+	}
+
+	if (content_width > 0 && content_height > 0 && width > 0 && height > 0) {
+		GdkScreen *screen;
+		GdkRectangle monitor_area;
+		gint x = 0, y = 0, monitor;
+
+		screen = gtk_window_get_screen (GTK_WINDOW (window));
+		gtk_window_get_position (GTK_WINDOW (window), &x, &y);
+
+		monitor = gdk_screen_get_monitor_at_point (screen, x, y);
+		if (monitor < 0 || monitor >= gdk_screen_get_n_monitors (screen))
+			monitor = 0;
+
+		gdk_screen_get_monitor_workarea (screen, monitor, &monitor_area);
+
+		if (content_width > monitor_area.width - width)
+			content_width = monitor_area.width - width;
+
+		if (content_height > monitor_area.height - height)
+			content_height = monitor_area.height - height;
+
+		if (content_width > 0 && content_height > 0)
+			gtk_window_set_default_size (GTK_WINDOW (window), width + content_width, height + content_height);
+	}
+
+	priv->setup = TRUE;
+}
diff --git a/e-util/e-preferences-window.h b/e-util/e-preferences-window.h
new file mode 100644
index 0000000..f2efa01
--- /dev/null
+++ b/e-util/e-preferences-window.h
@@ -0,0 +1,88 @@
+/*
+ * e-preferences-window.h
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_PREFERENCES_WINDOW_H
+#define E_PREFERENCES_WINDOW_H
+
+#include <gtk/gtk.h>
+
+/* Standard GObject macros */
+#define E_TYPE_PREFERENCES_WINDOW \
+	(e_preferences_window_get_type ())
+#define E_PREFERENCES_WINDOW(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_PREFERENCES_WINDOW, EPreferencesWindow))
+#define E_PREFERENCES_WINDOW_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_PREFERENCES_WINDOW, EPreferencesWindowClass))
+#define E_IS_PREFERENCES_WINDOW(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_PREFERENCES_WINDOW))
+#define E_IS_PREFERENCES_WINDOW_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((obj), E_TYPE_PREFERENCES_WINDOW))
+#define E_PREFERENCES_WINDOW_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_TYPE \
+	((obj), E_TYPE_PREFERENCES_WINDOW, EPreferencesWindowClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EPreferencesWindow EPreferencesWindow;
+typedef struct _EPreferencesWindowClass EPreferencesWindowClass;
+typedef struct _EPreferencesWindowPrivate EPreferencesWindowPrivate;
+
+struct _EPreferencesWindow {
+	GtkWindow parent;
+	EPreferencesWindowPrivate *priv;
+};
+
+struct _EPreferencesWindowClass {
+	GtkWindowClass parent_class;
+};
+
+typedef GtkWidget *
+		(*EPreferencesWindowCreatePageFn)
+						(EPreferencesWindow *window);
+
+GType		e_preferences_window_get_type	(void);
+GtkWidget *	e_preferences_window_new	(gpointer shell);
+gpointer	e_preferences_window_get_shell	(EPreferencesWindow *window);
+void		e_preferences_window_setup	(EPreferencesWindow *window);
+void		e_preferences_window_add_page	(EPreferencesWindow *window,
+						 const gchar *page_name,
+						 const gchar *icon_name,
+						 const gchar *caption,
+						 const gchar *help_target,
+						 EPreferencesWindowCreatePageFn create_fn,
+						 gint sort_order);
+void		e_preferences_window_show_page	(EPreferencesWindow *window,
+						 const gchar *page_name);
+void		e_preferences_window_filter_page
+						(EPreferencesWindow *window,
+						 const gchar *page_name);
+
+G_END_DECLS
+
+#endif /* E_PREFERENCES_WINDOW_H */
diff --git a/e-util/e-preview-pane.c b/e-util/e-preview-pane.c
new file mode 100644
index 0000000..27009ab
--- /dev/null
+++ b/e-util/e-preview-pane.c
@@ -0,0 +1,322 @@
+/*
+ * e-preview-pane.c
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-preview-pane.h"
+
+#include <gdk/gdkkeysyms.h>
+
+#include "e-alert-bar.h"
+#include "e-alert-dialog.h"
+#include "e-alert-sink.h"
+
+#define E_PREVIEW_PANE_GET_PRIVATE(obj) \
+	(G_TYPE_INSTANCE_GET_PRIVATE \
+	((obj), E_TYPE_PREVIEW_PANE, EPreviewPanePrivate))
+
+struct _EPreviewPanePrivate {
+	GtkWidget *alert_bar;
+	GtkWidget *web_view;
+	GtkWidget *search_bar;
+};
+
+enum {
+	PROP_0,
+	PROP_SEARCH_BAR,
+	PROP_WEB_VIEW
+};
+
+enum {
+	SHOW_SEARCH_BAR,
+	LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+/* Forward Declarations */
+static void	e_preview_pane_alert_sink_init
+					(EAlertSinkInterface *interface);
+
+G_DEFINE_TYPE_WITH_CODE (
+	EPreviewPane,
+	e_preview_pane,
+	GTK_TYPE_VBOX,
+	G_IMPLEMENT_INTERFACE (
+		E_TYPE_ALERT_SINK,
+		e_preview_pane_alert_sink_init))
+
+static void
+preview_pane_set_web_view (EPreviewPane *preview_pane,
+                           EWebView *web_view)
+{
+	g_return_if_fail (E_IS_WEB_VIEW (web_view));
+	g_return_if_fail (preview_pane->priv->web_view == NULL);
+
+	preview_pane->priv->web_view = g_object_ref_sink (web_view);
+}
+
+static void
+preview_pane_set_property (GObject *object,
+                           guint property_id,
+                           const GValue *value,
+                           GParamSpec *pspec)
+{
+	switch (property_id) {
+		case PROP_WEB_VIEW:
+			preview_pane_set_web_view (
+				E_PREVIEW_PANE (object),
+				g_value_get_object (value));
+			return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+preview_pane_get_property (GObject *object,
+                           guint property_id,
+                           GValue *value,
+                           GParamSpec *pspec)
+{
+	switch (property_id) {
+		case PROP_SEARCH_BAR:
+			g_value_set_object (
+				value, e_preview_pane_get_search_bar (
+				E_PREVIEW_PANE (object)));
+			return;
+
+		case PROP_WEB_VIEW:
+			g_value_set_object (
+				value, e_preview_pane_get_web_view (
+				E_PREVIEW_PANE (object)));
+			return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+preview_pane_dispose (GObject *object)
+{
+	EPreviewPanePrivate *priv;
+
+	priv = E_PREVIEW_PANE_GET_PRIVATE (object);
+
+	if (priv->alert_bar != NULL) {
+		g_object_unref (priv->alert_bar);
+		priv->alert_bar = NULL;
+	}
+
+	if (priv->search_bar != NULL) {
+		g_object_unref (priv->search_bar);
+		priv->search_bar = NULL;
+	}
+
+	if (priv->web_view != NULL) {
+		g_object_unref (priv->web_view);
+		priv->web_view = NULL;
+	}
+
+	/* Chain up to parent's dispose() method. */
+	G_OBJECT_CLASS (e_preview_pane_parent_class)->dispose (object);
+}
+
+static void
+preview_pane_constructed (GObject *object)
+{
+	EPreviewPanePrivate *priv;
+	GtkWidget *widget;
+
+	priv = E_PREVIEW_PANE_GET_PRIVATE (object);
+
+	widget = e_alert_bar_new ();
+	gtk_box_pack_start (GTK_BOX (object), widget, FALSE, FALSE, 0);
+	priv->alert_bar = g_object_ref (widget);
+	/* EAlertBar controls its own visibility. */
+
+	widget = gtk_scrolled_window_new (NULL, NULL);
+	gtk_scrolled_window_set_shadow_type (
+		GTK_SCROLLED_WINDOW (widget), GTK_SHADOW_IN);
+	gtk_box_pack_start (GTK_BOX (object), widget, TRUE, TRUE, 0);
+	gtk_container_add (GTK_CONTAINER (widget), priv->web_view);
+	gtk_widget_show (widget);
+	gtk_widget_show (priv->web_view);
+
+	widget = e_search_bar_new (E_WEB_VIEW (priv->web_view));
+	gtk_box_pack_start (GTK_BOX (object), widget, FALSE, FALSE, 0);
+	priv->search_bar = g_object_ref (widget);
+	gtk_widget_hide (widget);
+
+	/* Chain up to parent's constructed() method. */
+	G_OBJECT_CLASS (e_preview_pane_parent_class)->constructed (object);
+}
+
+static void
+preview_pane_show_search_bar (EPreviewPane *preview_pane)
+{
+	GtkWidget *search_bar;
+
+	search_bar = preview_pane->priv->search_bar;
+
+	if (!gtk_widget_get_visible (search_bar))
+		gtk_widget_show (search_bar);
+}
+
+static void
+preview_pane_submit_alert (EAlertSink *alert_sink,
+                           EAlert *alert)
+{
+	EPreviewPane *preview_pane;
+	EAlertBar *alert_bar;
+	GtkWidget *dialog;
+	GtkWindow *parent;
+
+	preview_pane = E_PREVIEW_PANE (alert_sink);
+	alert_bar = E_ALERT_BAR (preview_pane->priv->alert_bar);
+
+	switch (e_alert_get_message_type (alert)) {
+		case GTK_MESSAGE_INFO:
+		case GTK_MESSAGE_WARNING:
+		case GTK_MESSAGE_QUESTION:
+		case GTK_MESSAGE_ERROR:
+			e_alert_bar_add_alert (alert_bar, alert);
+			break;
+
+		default:
+			parent = GTK_WINDOW (alert_sink);
+			dialog = e_alert_dialog_new (parent, alert);
+			gtk_dialog_run (GTK_DIALOG (dialog));
+			gtk_widget_destroy (dialog);
+			break;
+	}
+}
+
+static void
+e_preview_pane_class_init (EPreviewPaneClass *class)
+{
+	GObjectClass *object_class;
+	GtkBindingSet *binding_set;
+
+	g_type_class_add_private (class, sizeof (EPreviewPanePrivate));
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->set_property = preview_pane_set_property;
+	object_class->get_property = preview_pane_get_property;
+	object_class->dispose = preview_pane_dispose;
+	object_class->constructed = preview_pane_constructed;
+
+	class->show_search_bar = preview_pane_show_search_bar;
+
+	g_object_class_install_property (
+		object_class,
+		PROP_SEARCH_BAR,
+		g_param_spec_object (
+			"search-bar",
+			"Search Bar",
+			NULL,
+			E_TYPE_SEARCH_BAR,
+			G_PARAM_READABLE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_WEB_VIEW,
+		g_param_spec_object (
+			"web-view",
+			"Web View",
+			NULL,
+			E_TYPE_WEB_VIEW,
+			G_PARAM_READWRITE |
+			G_PARAM_CONSTRUCT_ONLY));
+
+	signals[SHOW_SEARCH_BAR] = g_signal_new (
+		"show-search-bar",
+		G_TYPE_FROM_CLASS (class),
+		G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+		G_STRUCT_OFFSET (EPreviewPaneClass, show_search_bar),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__VOID,
+		G_TYPE_NONE, 0);
+
+	binding_set = gtk_binding_set_by_class (class);
+
+	gtk_binding_entry_add_signal (
+		binding_set, GDK_KEY_f, GDK_SHIFT_MASK | GDK_CONTROL_MASK,
+		"show-search-bar", 0);
+}
+
+static void
+e_preview_pane_alert_sink_init (EAlertSinkInterface *interface)
+{
+	interface->submit_alert = preview_pane_submit_alert;
+}
+
+static void
+e_preview_pane_init (EPreviewPane *preview_pane)
+{
+	preview_pane->priv = E_PREVIEW_PANE_GET_PRIVATE (preview_pane);
+
+	gtk_box_set_spacing (GTK_BOX (preview_pane), 1);
+}
+
+GtkWidget *
+e_preview_pane_new (EWebView *web_view)
+{
+	g_return_val_if_fail (E_IS_WEB_VIEW (web_view), NULL);
+
+	return g_object_new (
+		E_TYPE_PREVIEW_PANE,
+		"web-view", web_view, NULL);
+}
+
+EWebView *
+e_preview_pane_get_web_view (EPreviewPane *preview_pane)
+{
+	g_return_val_if_fail (E_IS_PREVIEW_PANE (preview_pane), NULL);
+
+	return E_WEB_VIEW (preview_pane->priv->web_view);
+}
+
+ESearchBar *
+e_preview_pane_get_search_bar (EPreviewPane *preview_pane)
+{
+	g_return_val_if_fail (E_IS_PREVIEW_PANE (preview_pane), NULL);
+
+	return E_SEARCH_BAR (preview_pane->priv->search_bar);
+}
+
+void
+e_preview_pane_clear_alerts (EPreviewPane *preview_pane)
+{
+	g_return_if_fail (E_IS_PREVIEW_PANE (preview_pane));
+
+	e_alert_bar_clear (E_ALERT_BAR (preview_pane->priv->alert_bar));
+}
+
+void
+e_preview_pane_show_search_bar (EPreviewPane *preview_pane)
+{
+	g_return_if_fail (E_IS_PREVIEW_PANE (preview_pane));
+
+	g_signal_emit (preview_pane, signals[SHOW_SEARCH_BAR], 0);
+}
diff --git a/e-util/e-preview-pane.h b/e-util/e-preview-pane.h
new file mode 100644
index 0000000..3720744
--- /dev/null
+++ b/e-util/e-preview-pane.h
@@ -0,0 +1,80 @@
+/*
+ * e-preview-pane.h
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_PREVIEW_PANE_H
+#define E_PREVIEW_PANE_H
+
+#include <gtk/gtk.h>
+
+#include <e-util/e-search-bar.h>
+#include <e-util/e-web-view.h>
+
+/* Standard GObject macros */
+#define E_TYPE_PREVIEW_PANE \
+	(e_preview_pane_get_type ())
+#define E_PREVIEW_PANE(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_PREVIEW_PANE, EPreviewPane))
+#define E_PREVIEW_PANE_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_PREVIEW_PANE, EPreviewPaneClass))
+#define E_IS_PREVIEW_PANE(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_PREVIEW_PANE))
+#define E_IS_PREVIEW_PANE_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_PREVIEW_PANE))
+#define E_PREVIEW_PANE_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_PREVIEW_PANE, EPreviewPaneClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EPreviewPane EPreviewPane;
+typedef struct _EPreviewPaneClass EPreviewPaneClass;
+typedef struct _EPreviewPanePrivate EPreviewPanePrivate;
+
+struct _EPreviewPane {
+	GtkBox parent;
+	EPreviewPanePrivate *priv;
+};
+
+struct _EPreviewPaneClass {
+	GtkBoxClass parent_class;
+
+	/* Signals */
+	void		(*show_search_bar)	(EPreviewPane *preview_pane);
+};
+
+GType		e_preview_pane_get_type		(void);
+GtkWidget *	e_preview_pane_new		(EWebView *web_view);
+EWebView *	e_preview_pane_get_web_view	(EPreviewPane *preview_pane);
+ESearchBar *	e_preview_pane_get_search_bar	(EPreviewPane *preview_pane);
+void		e_preview_pane_clear_alerts	(EPreviewPane *preview_pane);
+void		e_preview_pane_show_search_bar	(EPreviewPane *preview_pane);
+
+G_END_DECLS
+
+#endif /* E_PREVIEW_PANE_H */
diff --git a/e-util/e-print.c b/e-util/e-print.c
index e50ffd8..bc2f4d3 100644
--- a/e-util/e-print.c
+++ b/e-util/e-print.c
@@ -32,7 +32,7 @@
 #include <gtk/gtk.h>
 #include <glib/gi18n.h>
 
-#include "e-util.h"
+#include <libedataserver/libedataserver.h>
 
 /* XXX Would be better if GtkPrint exposed these. */
 #define PAGE_SETUP_GROUP_NAME		"Page Setup"
diff --git a/e-util/e-print.h b/e-util/e-print.h
index 9469b63..ee96e25 100644
--- a/e-util/e-print.h
+++ b/e-util/e-print.h
@@ -23,6 +23,10 @@
  *
  */
 
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
 #ifndef __E_PRINT__
 #define __E_PRINT__
 
diff --git a/e-util/e-printable.c b/e-util/e-printable.c
new file mode 100644
index 0000000..3f1403f
--- /dev/null
+++ b/e-util/e-printable.c
@@ -0,0 +1,226 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <gtk/gtk.h>
+
+#include "e-printable.h"
+
+#include "e-marshal.h"
+
+#define EP_CLASS(e) ((EPrintableClass *)((GObject *)e)->class)
+
+G_DEFINE_TYPE (
+	EPrintable,
+	e_printable,
+	G_TYPE_INITIALLY_UNOWNED)
+
+enum {
+	PRINT_PAGE,
+	DATA_LEFT,
+	RESET,
+	HEIGHT,
+	WILL_FIT,
+	LAST_SIGNAL
+};
+
+static guint e_printable_signals[LAST_SIGNAL] = { 0, };
+
+static void
+e_printable_class_init (EPrintableClass *class)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+	e_printable_signals[PRINT_PAGE] = g_signal_new (
+		"print_page",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (EPrintableClass, print_page),
+		NULL, NULL,
+		e_marshal_NONE__OBJECT_DOUBLE_DOUBLE_BOOLEAN,
+		G_TYPE_NONE, 4,
+		G_TYPE_OBJECT,
+		G_TYPE_DOUBLE,
+		G_TYPE_DOUBLE,
+		G_TYPE_BOOLEAN);
+
+	e_printable_signals[DATA_LEFT] = g_signal_new (
+		"data_left",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (EPrintableClass, data_left),
+		NULL, NULL,
+		e_marshal_BOOLEAN__NONE,
+		G_TYPE_BOOLEAN, 0,
+		G_TYPE_NONE);
+
+	e_printable_signals[RESET] = g_signal_new (
+		"reset",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (EPrintableClass, reset),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__VOID,
+		G_TYPE_NONE, 0,
+		G_TYPE_NONE);
+
+	e_printable_signals[HEIGHT] = g_signal_new (
+		"height",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (EPrintableClass, height),
+		NULL, NULL,
+		e_marshal_DOUBLE__OBJECT_DOUBLE_DOUBLE_BOOLEAN,
+		G_TYPE_DOUBLE, 4,
+		G_TYPE_OBJECT,
+		G_TYPE_DOUBLE,
+		G_TYPE_DOUBLE,
+		G_TYPE_BOOLEAN);
+
+	e_printable_signals[WILL_FIT] = g_signal_new (
+		"will_fit",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (EPrintableClass, will_fit),
+		NULL, NULL,
+		e_marshal_BOOLEAN__OBJECT_DOUBLE_DOUBLE_BOOLEAN,
+		G_TYPE_BOOLEAN, 4,
+		G_TYPE_OBJECT,
+		G_TYPE_DOUBLE,
+		G_TYPE_DOUBLE,
+		G_TYPE_BOOLEAN);
+
+	class->print_page = NULL;
+	class->data_left = NULL;
+	class->reset = NULL;
+	class->height = NULL;
+	class->will_fit = NULL;
+}
+
+static void
+e_printable_init (EPrintable *e_printable)
+{
+	/* nothing to do */
+}
+
+EPrintable *
+e_printable_new (void)
+{
+	return E_PRINTABLE (g_object_new (E_PRINTABLE_TYPE, NULL));
+}
+
+void
+e_printable_print_page (EPrintable *e_printable,
+                        GtkPrintContext *context,
+                        gdouble width,
+                        gdouble height,
+                        gboolean quantized)
+{
+	g_return_if_fail (e_printable != NULL);
+	g_return_if_fail (E_IS_PRINTABLE (e_printable));
+
+	g_signal_emit (
+		e_printable,
+		e_printable_signals[PRINT_PAGE], 0,
+		context,
+		width,
+		height,
+		quantized);
+}
+
+gboolean
+e_printable_data_left (EPrintable *e_printable)
+{
+	gboolean ret_val;
+
+	g_return_val_if_fail (e_printable != NULL, FALSE);
+	g_return_val_if_fail (E_IS_PRINTABLE (e_printable), FALSE);
+
+	g_signal_emit (
+		e_printable,
+		e_printable_signals[DATA_LEFT], 0,
+		&ret_val);
+
+	return ret_val;
+}
+
+void
+e_printable_reset (EPrintable *e_printable)
+{
+	g_return_if_fail (e_printable != NULL);
+	g_return_if_fail (E_IS_PRINTABLE (e_printable));
+
+	g_signal_emit (
+		e_printable,
+		e_printable_signals[RESET], 0);
+}
+
+gdouble
+e_printable_height (EPrintable *e_printable,
+                    GtkPrintContext *context,
+                    gdouble width,
+                    gdouble max_height,
+                    gboolean quantized)
+{
+	gdouble ret_val;
+
+	g_return_val_if_fail (e_printable != NULL, -1);
+	g_return_val_if_fail (E_IS_PRINTABLE (e_printable), -1);
+
+	g_signal_emit (
+		e_printable,
+		e_printable_signals[HEIGHT], 0,
+		context,
+		width,
+		max_height,
+		quantized,
+		&ret_val);
+
+	return ret_val;
+}
+
+gboolean
+e_printable_will_fit (EPrintable *e_printable,
+                      GtkPrintContext *context,
+                      gdouble width,
+                      gdouble max_height,
+                      gboolean quantized)
+{
+	gboolean ret_val;
+
+	g_return_val_if_fail (e_printable != NULL, FALSE);
+	g_return_val_if_fail (E_IS_PRINTABLE (e_printable), FALSE);
+
+	g_signal_emit (
+		e_printable,
+		e_printable_signals[WILL_FIT], 0,
+		context,
+		width,
+		max_height,
+		quantized,
+		&ret_val);
+
+	return ret_val;
+}
diff --git a/e-util/e-printable.h b/e-util/e-printable.h
new file mode 100644
index 0000000..2927560
--- /dev/null
+++ b/e-util/e-printable.h
@@ -0,0 +1,93 @@
+/*
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef _E_PRINTABLE_H_
+#define _E_PRINTABLE_H_
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define E_PRINTABLE_TYPE        (e_printable_get_type ())
+#define E_PRINTABLE(o)          (G_TYPE_CHECK_INSTANCE_CAST ((o), E_PRINTABLE_TYPE, EPrintable))
+#define E_PRINTABLE_CLASS(k)    (G_TYPE_CHECK_CLASS_CAST((k), E_PRINTABLE_TYPE, EPrintableClass))
+#define E_IS_PRINTABLE(o)       (G_TYPE_CHECK_INSTANCE_TYPE ((o), E_PRINTABLE_TYPE))
+#define E_IS_PRINTABLE_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), E_PRINTABLE_TYPE))
+
+typedef struct {
+	GObject   base;
+} EPrintable;
+
+typedef struct {
+	GObjectClass parent_class;
+
+	/*
+	 * Signals
+	 */
+
+	void        (*print_page)  (EPrintable *etm, GtkPrintContext *context, gdouble width, gdouble height, gboolean quantized);
+	gboolean    (*data_left)   (EPrintable *etm);
+	void        (*reset)       (EPrintable *etm);
+	gdouble     (*height)      (EPrintable *etm, GtkPrintContext *context, gdouble width, gdouble max_height, gboolean quantized);
+
+	/* e_printable_will_fit (ep, ...) should be equal in value to
+	 * (e_printable_print_page (ep, ...),
+	 * !e_printable_data_left(ep)) except that the latter has the
+	 * side effect of doing the printing and advancing the
+	 * position of the printable.
+	 */
+
+	gboolean    (*will_fit)    (EPrintable *etm, GtkPrintContext *context, gdouble width, gdouble max_height, gboolean quantized);
+} EPrintableClass;
+
+GType       e_printable_get_type (void);
+
+EPrintable *e_printable_new                 (void);
+
+/*
+ * Routines for emitting signals on the e_table */
+void        e_printable_print_page          (EPrintable        *e_printable,
+					     GtkPrintContext *context,
+					     gdouble            width,
+					     gdouble            height,
+					     gboolean           quantized);
+gboolean    e_printable_data_left           (EPrintable        *e_printable);
+void        e_printable_reset               (EPrintable        *e_printable);
+gdouble     e_printable_height              (EPrintable        *e_printable,
+					     GtkPrintContext *context,
+					     gdouble            width,
+					     gdouble            max_height,
+					     gboolean           quantized);
+gboolean    e_printable_will_fit            (EPrintable        *e_printable,
+					     GtkPrintContext *context,
+					     gdouble            width,
+					     gdouble            max_height,
+					     gboolean           quantized);
+
+G_END_DECLS
+
+#endif /* _E_PRINTABLE_H_ */
diff --git a/e-util/e-reflow-model.c b/e-util/e-reflow-model.c
new file mode 100644
index 0000000..4f5b219
--- /dev/null
+++ b/e-util/e-reflow-model.c
@@ -0,0 +1,411 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-reflow-model.h"
+
+#include "e-misc-utils.h"
+
+G_DEFINE_TYPE (EReflowModel, e_reflow_model, G_TYPE_OBJECT)
+
+#define d(x)
+
+d (static gint depth = 0;)
+
+enum {
+	MODEL_CHANGED,
+	COMPARISON_CHANGED,
+	MODEL_ITEMS_INSERTED,
+	MODEL_ITEM_CHANGED,
+	MODEL_ITEM_REMOVED,
+	LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0, };
+
+/**
+ * e_reflow_model_set_width:
+ * @e_reflow_model: The e-reflow-model to operate on
+ * @width: The new value for the width of each item.
+ */
+void
+e_reflow_model_set_width (EReflowModel *e_reflow_model,
+                          gint width)
+{
+	EReflowModelClass *class;
+
+	g_return_if_fail (E_IS_REFLOW_MODEL (e_reflow_model));
+
+	class = E_REFLOW_MODEL_GET_CLASS (e_reflow_model);
+	g_return_if_fail (class->set_width != NULL);
+
+	class->set_width (e_reflow_model, width);
+}
+
+/**
+ * e_reflow_model_count:
+ * @e_reflow_model: The e-reflow-model to operate on
+ *
+ * Returns: the number of items in the reflow model.
+ */
+gint
+e_reflow_model_count (EReflowModel *e_reflow_model)
+{
+	EReflowModelClass *class;
+
+	g_return_val_if_fail (E_IS_REFLOW_MODEL (e_reflow_model), 0);
+
+	class = E_REFLOW_MODEL_GET_CLASS (e_reflow_model);
+	g_return_val_if_fail (class->count != NULL, 0);
+
+	return class->count (e_reflow_model);
+}
+
+/**
+ * e_reflow_model_height:
+ * @e_reflow_model: The e-reflow-model to operate on
+ * @n: The item number to get the height of.
+ * @parent: The parent GnomeCanvasItem.
+ *
+ * Returns: the height of the nth item.
+ */
+gint
+e_reflow_model_height (EReflowModel *e_reflow_model,
+                       gint n,
+                       GnomeCanvasGroup *parent)
+{
+	EReflowModelClass *class;
+
+	g_return_val_if_fail (E_IS_REFLOW_MODEL (e_reflow_model), 0);
+
+	class = E_REFLOW_MODEL_GET_CLASS (e_reflow_model);
+	g_return_val_if_fail (class->height != NULL, 0);
+
+	return class->height (e_reflow_model, n, parent);
+}
+
+/**
+ * e_reflow_model_incarnate:
+ * @e_reflow_model: The e-reflow-model to operate on
+ * @n: The item to create.
+ * @parent: The parent GnomeCanvasItem to create a child of.
+ *
+ * Create a GnomeCanvasItem to represent the nth piece of data.
+ *
+ * Returns: the new GnomeCanvasItem.
+ */
+GnomeCanvasItem *
+e_reflow_model_incarnate (EReflowModel *e_reflow_model,
+                          gint n,
+                          GnomeCanvasGroup *parent)
+{
+	EReflowModelClass *class;
+
+	g_return_val_if_fail (E_IS_REFLOW_MODEL (e_reflow_model), NULL);
+
+	class = E_REFLOW_MODEL_GET_CLASS (e_reflow_model);
+	g_return_val_if_fail (class->incarnate != NULL, NULL);
+
+	return class->incarnate (e_reflow_model, n, parent);
+}
+
+/**
+ * e_reflow_model_create_cmp_cache:
+ * @e_reflow_model: The e-reflow-model to operate on
+ *
+ * Creates a compare cache for quicker sorting. The sorting function
+ * may not depend on the cache, but it should benefit from it if available.
+ *
+ * Returns: Newly created GHashTable with cached compare values. This will be
+ * automatically freed with g_hash_table_destroy() when no longer needed.
+ **/
+GHashTable *
+e_reflow_model_create_cmp_cache (EReflowModel *e_reflow_model)
+{
+	EReflowModelClass *class;
+
+	g_return_val_if_fail (E_IS_REFLOW_MODEL (e_reflow_model), NULL);
+
+	class = E_REFLOW_MODEL_GET_CLASS (e_reflow_model);
+
+	if (class->create_cmp_cache == NULL)
+		return NULL;
+
+	return class->create_cmp_cache (e_reflow_model);
+}
+
+/**
+ * e_reflow_model_compare:
+ * @e_reflow_model: The e-reflow-model to operate on
+ * @n1: The first item to compare
+ * @n2: The second item to compare
+ * @cmp_cache: #GHashTable of cached compare values, created by
+ *    e_reflow_model_create_cmp_cache(). This can be NULL, when
+ *    caching is not available, even when @e_reflow_model defines
+ *    the create_cmp_cache function.
+ *
+ * Compares item n1 and item n2 to see which should come first.
+ *
+ * Returns: strcmp like semantics for the comparison value.
+ */
+gint
+e_reflow_model_compare (EReflowModel *e_reflow_model,
+                        gint n1,
+                        gint n2,
+                        GHashTable *cmp_cache)
+{
+	EReflowModelClass *class;
+
+	g_return_val_if_fail (E_IS_REFLOW_MODEL (e_reflow_model), 0);
+
+	class = E_REFLOW_MODEL_GET_CLASS (e_reflow_model);
+	g_return_val_if_fail (class->compare != NULL, 0);
+
+	return class->compare (e_reflow_model, n1, n2, cmp_cache);
+}
+
+/**
+ * e_reflow_model_reincarnate:
+ * @e_reflow_model: The e-reflow-model to operate on
+ * @n: The item to create.
+ * @item: The item to reuse.
+ *
+ * Update item to represent the nth piece of data.
+ */
+void
+e_reflow_model_reincarnate (EReflowModel *e_reflow_model,
+                            gint n,
+                            GnomeCanvasItem *item)
+{
+	EReflowModelClass *class;
+
+	g_return_if_fail (E_IS_REFLOW_MODEL (e_reflow_model));
+
+	class = E_REFLOW_MODEL_GET_CLASS (e_reflow_model);
+	g_return_if_fail (class->reincarnate != NULL);
+
+	class->reincarnate (e_reflow_model, n, item);
+}
+
+static void
+e_reflow_model_class_init (EReflowModelClass *class)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+	class->set_width            = NULL;
+	class->count                = NULL;
+	class->height               = NULL;
+	class->incarnate            = NULL;
+	class->reincarnate          = NULL;
+
+	class->model_changed        = NULL;
+	class->comparison_changed   = NULL;
+	class->model_items_inserted = NULL;
+	class->model_item_removed   = NULL;
+	class->model_item_changed   = NULL;
+
+	signals[MODEL_CHANGED] = g_signal_new (
+		"model_changed",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (EReflowModelClass, model_changed),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__VOID,
+		G_TYPE_NONE, 0);
+
+	signals[COMPARISON_CHANGED] = g_signal_new (
+		"comparison_changed",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (EReflowModelClass, comparison_changed),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__VOID,
+		G_TYPE_NONE, 0);
+
+	signals[MODEL_ITEMS_INSERTED] = g_signal_new (
+		"model_items_inserted",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (EReflowModelClass, model_items_inserted),
+		NULL, NULL,
+		e_marshal_NONE__INT_INT,
+		G_TYPE_NONE, 2, G_TYPE_INT, G_TYPE_INT);
+
+	signals[MODEL_ITEM_CHANGED] = g_signal_new (
+		"model_item_changed",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (EReflowModelClass, model_item_changed),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__INT,
+		G_TYPE_NONE, 1, G_TYPE_INT);
+
+	signals[MODEL_ITEM_REMOVED] = g_signal_new (
+		"model_item_removed",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (EReflowModelClass, model_item_removed),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__INT,
+		G_TYPE_NONE, 1, G_TYPE_INT);
+}
+
+static void
+e_reflow_model_init (EReflowModel *e_reflow_model)
+{
+}
+
+#if d(!)0
+static void
+print_tabs (void)
+{
+	gint i;
+	for (i = 0; i < depth; i++)
+		g_print ("\t");
+}
+#endif
+
+/**
+ * e_reflow_model_changed:
+ * @e_reflow_model: the reflow model to notify of the change
+ *
+ * Use this function to notify any views of this reflow model that
+ * the contents of the reflow model have changed.  This will emit
+ * the signal "model_changed" on the @e_reflow_model object.
+ *
+ * It is preferable to use the e_reflow_model_item_changed() signal to
+ * notify of smaller changes than to invalidate the entire model, as
+ * the views might have ways of caching the information they render
+ * from the model.
+ */
+void
+e_reflow_model_changed (EReflowModel *e_reflow_model)
+{
+	g_return_if_fail (e_reflow_model != NULL);
+	g_return_if_fail (E_IS_REFLOW_MODEL (e_reflow_model));
+
+	d (print_tabs ());
+	d (g_print ("Emitting model_changed on model 0x%p.\n", e_reflow_model));
+	d (depth++);
+	g_signal_emit (e_reflow_model, signals[MODEL_CHANGED], 0);
+	d (depth--);
+}
+
+/**
+ * e_reflow_model_comparison_changed:
+ * @e_reflow_model: the reflow model to notify of the change
+ *
+ * Use this function to notify any views of this reflow model that the
+ * sorting has changed.  The actual contents of the items hasn't, so
+ * there's no need to re-query the model for the heights of the
+ * individual items.
+ */
+void
+e_reflow_model_comparison_changed (EReflowModel *e_reflow_model)
+{
+	g_return_if_fail (e_reflow_model != NULL);
+	g_return_if_fail (E_IS_REFLOW_MODEL (e_reflow_model));
+
+	d (print_tabs ());
+	d (g_print (
+		"Emitting comparison_changed on model 0x%p.\n",
+		e_reflow_model));
+	d (depth++);
+	g_signal_emit (e_reflow_model, signals[COMPARISON_CHANGED], 0);
+	d (depth--);
+}
+
+/**
+ * e_reflow_model_items_inserted:
+ * @e_reflow_model: The model changed.
+ * @position: The position the items were insert in.
+ * @count: The number of items inserted.
+ *
+ * Use this function to notify any views of the reflow model that a number
+ * of items have been inserted.
+ **/
+void
+e_reflow_model_items_inserted (EReflowModel *e_reflow_model,
+                               gint position,
+                               gint count)
+{
+	g_return_if_fail (e_reflow_model != NULL);
+	g_return_if_fail (E_IS_REFLOW_MODEL (e_reflow_model));
+
+	d (print_tabs ());
+	d (depth++);
+	g_signal_emit (
+		e_reflow_model,
+		signals[MODEL_ITEMS_INSERTED], 0,
+		position, count);
+	d (depth--);
+}
+
+/**
+ * e_reflow_model_item_removed:
+ * @e_reflow_model: The model changed.
+ * @n: The position from which the items were removed.
+ *
+ * Use this function to notify any views of the reflow model that an
+ * item has been removed.
+ **/
+void
+e_reflow_model_item_removed (EReflowModel *e_reflow_model,
+                             gint n)
+{
+	g_return_if_fail (e_reflow_model != NULL);
+	g_return_if_fail (E_IS_REFLOW_MODEL (e_reflow_model));
+
+	d (print_tabs ());
+	d (depth++);
+	g_signal_emit (e_reflow_model, signals[MODEL_ITEM_REMOVED], 0, n);
+	d (depth--);
+}
+
+/**
+ * e_reflow_model_item_changed:
+ * @e_reflow_model: the reflow model to notify of the change
+ * @item: the item that was changed in the model.
+ *
+ * Use this function to notify any views of the reflow model that the
+ * contents of item @item have changed in model such that the height
+ * has changed or the item needs to be reincarnated.  This function
+ * will emit the "model_item_changed" signal on the @e_reflow_model
+ * object
+ */
+void
+e_reflow_model_item_changed (EReflowModel *e_reflow_model,
+                             gint n)
+{
+	g_return_if_fail (e_reflow_model != NULL);
+	g_return_if_fail (E_IS_REFLOW_MODEL (e_reflow_model));
+
+	d (print_tabs ());
+	d (g_print ("Emitting item_changed on model 0x%p, n=%d.\n", e_reflow_model, n));
+	d (depth++);
+	g_signal_emit (e_reflow_model, signals[MODEL_ITEM_CHANGED], 0, n);
+	d (depth--);
+}
diff --git a/e-util/e-reflow-model.h b/e-util/e-reflow-model.h
new file mode 100644
index 0000000..4a5f710
--- /dev/null
+++ b/e-util/e-reflow-model.h
@@ -0,0 +1,129 @@
+/*
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef _E_REFLOW_MODEL_H_
+#define _E_REFLOW_MODEL_H_
+
+#include <libgnomecanvas/libgnomecanvas.h>
+
+/* Standard GObject macros */
+#define E_TYPE_REFLOW_MODEL \
+	(e_reflow_model_get_type ())
+#define E_REFLOW_MODEL(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_REFLOW_MODEL, EReflowModel))
+#define E_REFLOW_MODEL_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_REFLOW_MODEL, EReflowModelClass))
+#define E_IS_REFLOW_MODEL(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_REFLOW_MODEL))
+#define E_IS_REFLOW_MODEL_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_REFLOW_MODEL))
+#define E_REFLOW_MODEL_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_REFLOW_MODEL, EReflowModelClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EReflowModel EReflowModel;
+typedef struct _EReflowModelClass EReflowModelClass;
+
+struct _EReflowModel {
+	GObject parent;
+};
+
+struct _EReflowModelClass {
+	GObjectClass parent_class;
+
+	/*
+	 * Virtual methods
+	 */
+	void             (*set_width)      (EReflowModel *etm, gint width);
+
+	gint              (*count)          (EReflowModel *etm);
+	gint              (*height)         (EReflowModel *etm, gint n, GnomeCanvasGroup *parent);
+	GnomeCanvasItem *(*incarnate)      (EReflowModel *etm, gint n, GnomeCanvasGroup *parent);
+	GHashTable *     (*create_cmp_cache) (EReflowModel *etm);
+	gint              (*compare)         (EReflowModel *etm, gint n1, gint n2, GHashTable *cmp_cache);
+	void             (*reincarnate)    (EReflowModel *etm, gint n, GnomeCanvasItem *item);
+
+	/*
+	 * Signals
+	 */
+
+	/*
+	 * These all come after the change has been made.
+	 * Major structural changes: model_changed
+	 * Changes to the sorting of elements: comparison_changed
+	 * Changes only in an item: item_changed
+	 */
+	void        (*model_changed)       (EReflowModel *etm);
+	void        (*comparison_changed)  (EReflowModel *etm);
+	void        (*model_items_inserted) (EReflowModel *etm, gint position, gint count);
+	void        (*model_item_removed)  (EReflowModel *etm, gint position);
+	void        (*model_item_changed)  (EReflowModel *etm, gint n);
+};
+
+GType            e_reflow_model_get_type        (void);
+
+/**/
+void             e_reflow_model_set_width       (EReflowModel     *e_reflow_model,
+						 gint               width);
+gint              e_reflow_model_count           (EReflowModel     *e_reflow_model);
+gint              e_reflow_model_height          (EReflowModel     *e_reflow_model,
+						 gint               n,
+						 GnomeCanvasGroup *parent);
+GnomeCanvasItem *e_reflow_model_incarnate       (EReflowModel     *e_reflow_model,
+						 gint               n,
+						 GnomeCanvasGroup *parent);
+GHashTable *     e_reflow_model_create_cmp_cache (EReflowModel *e_reflow_model);
+gint              e_reflow_model_compare         (EReflowModel     *e_reflow_model,
+						 gint               n1,
+						 gint               n2,
+						 GHashTable        *cmp_cache);
+void             e_reflow_model_reincarnate     (EReflowModel     *e_reflow_model,
+						 gint               n,
+						 GnomeCanvasItem  *item);
+
+/*
+ * Routines for emitting signals on the e_reflow
+ */
+void             e_reflow_model_changed            (EReflowModel     *e_reflow_model);
+void             e_reflow_model_comparison_changed (EReflowModel     *e_reflow_model);
+void             e_reflow_model_items_inserted     (EReflowModel     *e_reflow_model,
+						    gint               position,
+						    gint               count);
+void             e_reflow_model_item_removed       (EReflowModel     *e_reflow_model,
+						    gint               n);
+void             e_reflow_model_item_changed       (EReflowModel     *e_reflow_model,
+						    gint               n);
+
+G_END_DECLS
+
+#endif /* _E_REFLOW_MODEL_H_ */
diff --git a/e-util/e-reflow.c b/e-util/e-reflow.c
new file mode 100644
index 0000000..0df0aad
--- /dev/null
+++ b/e-util/e-reflow.c
@@ -0,0 +1,1732 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *              Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-reflow.h"
+
+#include <math.h>
+#include <string.h>
+
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+#include <gdk/gdkkeysyms.h>
+
+#include "e-canvas-utils.h"
+#include "e-canvas.h"
+#include "e-marshal.h"
+#include "e-selection-model-simple.h"
+#include "e-text.h"
+#include "e-unicode.h"
+
+static gboolean e_reflow_event (GnomeCanvasItem *item, GdkEvent *event);
+static void e_reflow_realize (GnomeCanvasItem *item);
+static void e_reflow_unrealize (GnomeCanvasItem *item);
+static void e_reflow_draw (GnomeCanvasItem *item, cairo_t *cr,
+				    gint x, gint y, gint width, gint height);
+static void e_reflow_update (GnomeCanvasItem *item, const cairo_matrix_t *i2c, gint flags);
+static GnomeCanvasItem *e_reflow_point (GnomeCanvasItem *item, gdouble x, gdouble y, gint cx, gint cy);
+static void e_reflow_reflow (GnomeCanvasItem *item, gint flags);
+static void set_empty (EReflow *reflow);
+
+static void e_reflow_resize_children (GnomeCanvasItem *item);
+
+#define E_REFLOW_DIVIDER_WIDTH 2
+#define E_REFLOW_BORDER_WIDTH 7
+#define E_REFLOW_FULL_GUTTER (E_REFLOW_DIVIDER_WIDTH + E_REFLOW_BORDER_WIDTH * 2)
+
+G_DEFINE_TYPE (EReflow, e_reflow, GNOME_TYPE_CANVAS_GROUP)
+
+enum {
+	PROP_0,
+	PROP_MINIMUM_WIDTH,
+	PROP_WIDTH,
+	PROP_HEIGHT,
+	PROP_EMPTY_MESSAGE,
+	PROP_MODEL,
+	PROP_COLUMN_WIDTH
+};
+
+enum {
+	SELECTION_EVENT,
+	COLUMN_WIDTH_CHANGED,
+	LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = {0, };
+
+static GHashTable *
+er_create_cmp_cache (gpointer user_data)
+{
+	EReflow *reflow = user_data;
+	return e_reflow_model_create_cmp_cache (reflow->model);
+}
+
+static gint
+er_compare (gint i1,
+            gint i2,
+            GHashTable *cmp_cache,
+            gpointer user_data)
+{
+	EReflow *reflow = user_data;
+	return e_reflow_model_compare (reflow->model, i1, i2, cmp_cache);
+}
+
+static gint
+e_reflow_pick_line (EReflow *reflow,
+                    gdouble x)
+{
+	x += E_REFLOW_BORDER_WIDTH + E_REFLOW_DIVIDER_WIDTH;
+	x /= reflow->column_width + E_REFLOW_FULL_GUTTER;
+	return x;
+}
+
+static gint
+er_find_item (EReflow *reflow,
+              GnomeCanvasItem *item)
+{
+	gint i;
+	for (i = 0; i < reflow->count; i++) {
+		if (reflow->items[i] == item)
+			return i;
+	}
+	return -1;
+}
+
+static void
+e_reflow_resize_children (GnomeCanvasItem *item)
+{
+	EReflow *reflow;
+	gint i;
+	gint count;
+
+	reflow = E_REFLOW (item);
+
+	count = reflow->count;
+	for (i = 0; i < count; i++) {
+		if (reflow->items[i])
+			gnome_canvas_item_set (
+				reflow->items[i],
+				"width", (gdouble) reflow->column_width,
+				NULL);
+	}
+}
+
+static inline void
+e_reflow_update_selection_row (EReflow *reflow,
+                               gint row)
+{
+	if (reflow->items[row]) {
+		g_object_set (
+			reflow->items[row],
+			"selected", e_selection_model_is_row_selected (E_SELECTION_MODEL (reflow->selection), row),
+			NULL);
+	} else if (e_selection_model_is_row_selected (E_SELECTION_MODEL (reflow->selection), row)) {
+		reflow->items[row] = e_reflow_model_incarnate (reflow->model, row, GNOME_CANVAS_GROUP (reflow));
+		g_object_set (
+			reflow->items[row],
+			"selected", e_selection_model_is_row_selected (E_SELECTION_MODEL (reflow->selection), row),
+			"width", (gdouble) reflow->column_width,
+			NULL);
+	}
+}
+
+static void
+e_reflow_update_selection (EReflow *reflow)
+{
+	gint i;
+	gint count;
+
+	count = reflow->count;
+	for (i = 0; i < count; i++) {
+		e_reflow_update_selection_row (reflow, i);
+	}
+}
+
+static void
+selection_changed (ESelectionModel *selection,
+                   EReflow *reflow)
+{
+	e_reflow_update_selection (reflow);
+}
+
+static void
+selection_row_changed (ESelectionModel *selection,
+                       gint row,
+                       EReflow *reflow)
+{
+	e_reflow_update_selection_row (reflow, row);
+}
+
+static gboolean
+do_adjustment (gpointer user_data)
+{
+	gint row;
+	GtkLayout *layout;
+	GtkAdjustment *adjustment;
+	gdouble page_size;
+	gdouble value, min_value, max_value;
+	EReflow *reflow = user_data;
+
+	row = reflow->cursor_row;
+	if (row == -1)
+		return FALSE;
+
+	layout = GTK_LAYOUT (GNOME_CANVAS_ITEM (reflow)->canvas);
+	adjustment = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (layout));
+
+	value = gtk_adjustment_get_value (adjustment);
+	page_size = gtk_adjustment_get_page_size (adjustment);
+
+	if ((!reflow->items) || (!reflow->items[row]))
+		return TRUE;
+	min_value = reflow->items[row]->x2 - page_size;
+	max_value = reflow->items[row]->x1;
+
+	if (value < min_value)
+		value = min_value;
+
+	if (value > max_value)
+		value = max_value;
+
+	if (value != gtk_adjustment_get_value (adjustment))
+		gtk_adjustment_set_value (adjustment, value);
+
+	reflow->do_adjustment_idle_id = 0;
+
+	return FALSE;
+}
+
+static void
+cursor_changed (ESelectionModel *selection,
+                gint row,
+                gint col,
+                EReflow *reflow)
+{
+	gint count = reflow->count;
+	gint old_cursor = reflow->cursor_row;
+
+	if (old_cursor < count && old_cursor >= 0) {
+		if (reflow->items[old_cursor]) {
+			g_object_set (
+				reflow->items[old_cursor],
+				"has_cursor", FALSE,
+				NULL);
+		}
+	}
+
+	reflow->cursor_row = row;
+
+	if (row < count && row >= 0) {
+		if (reflow->items[row]) {
+			g_object_set (
+				reflow->items[row],
+				"has_cursor", TRUE,
+				NULL);
+		} else {
+			reflow->items[row] = e_reflow_model_incarnate (reflow->model, row, GNOME_CANVAS_GROUP (reflow));
+			g_object_set (
+				reflow->items[row],
+				"has_cursor", TRUE,
+				"width", (gdouble) reflow->column_width,
+				NULL);
+		}
+	}
+
+	if (reflow->do_adjustment_idle_id == 0)
+		reflow->do_adjustment_idle_id = g_idle_add (do_adjustment, reflow);
+
+}
+
+static void
+incarnate (EReflow *reflow)
+{
+	gint column_width;
+	gint first_column;
+	gint last_column;
+	gint first_cell;
+	gint last_cell;
+	gint i;
+	GtkLayout *layout;
+	GtkAdjustment *adjustment;
+	gdouble value;
+	gdouble page_size;
+
+	layout = GTK_LAYOUT (GNOME_CANVAS_ITEM (reflow)->canvas);
+	adjustment = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (layout));
+
+	value = gtk_adjustment_get_value (adjustment);
+	page_size = gtk_adjustment_get_page_size (adjustment);
+
+	column_width = reflow->column_width;
+
+	first_column = value - 1 + E_REFLOW_BORDER_WIDTH;
+	first_column /= column_width + E_REFLOW_FULL_GUTTER;
+
+	last_column = value + page_size + 1 - E_REFLOW_BORDER_WIDTH - E_REFLOW_DIVIDER_WIDTH;
+	last_column /= column_width + E_REFLOW_FULL_GUTTER;
+	last_column++;
+
+	if (first_column >= 0 && first_column < reflow->column_count)
+		first_cell = reflow->columns[first_column];
+	else
+		first_cell = 0;
+
+	if (last_column >= 0 && last_column < reflow->column_count)
+		last_cell = reflow->columns[last_column];
+	else
+		last_cell = reflow->count;
+
+	for (i = first_cell; i < last_cell; i++) {
+		gint unsorted = e_sorter_sorted_to_model (E_SORTER (reflow->sorter), i);
+		if (reflow->items[unsorted] == NULL) {
+			if (reflow->model) {
+				reflow->items[unsorted] = e_reflow_model_incarnate (reflow->model, unsorted, GNOME_CANVAS_GROUP (reflow));
+				g_object_set (
+					reflow->items[unsorted],
+					"selected", e_selection_model_is_row_selected (E_SELECTION_MODEL (reflow->selection), unsorted),
+					"width", (gdouble) reflow->column_width,
+					NULL);
+			}
+		}
+	}
+	reflow->incarnate_idle_id = 0;
+}
+
+static gboolean
+invoke_incarnate (gpointer user_data)
+{
+	EReflow *reflow = user_data;
+	incarnate (reflow);
+	return FALSE;
+}
+
+static void
+queue_incarnate (EReflow *reflow)
+{
+	if (reflow->incarnate_idle_id == 0)
+		reflow->incarnate_idle_id =
+			g_idle_add_full (25, invoke_incarnate, reflow, NULL);
+}
+
+static void
+reflow_columns (EReflow *reflow)
+{
+	GSList *list;
+	gint count;
+	gint start;
+	gint i;
+	gint column_count, column_start;
+	gdouble running_height;
+
+	if (reflow->reflow_from_column <= 1) {
+		start = 0;
+		column_count = 1;
+		column_start = 0;
+	}
+	else {
+		/* we start one column before the earliest new entry,
+		 * so we can handle the case where the new entry is
+		 * inserted at the start of the column */
+		column_start = reflow->reflow_from_column - 1;
+		start = reflow->columns[column_start];
+		column_count = column_start + 1;
+	}
+
+	list = NULL;
+
+	running_height = E_REFLOW_BORDER_WIDTH;
+
+	count = reflow->count - start;
+	for (i = start; i < count; i++) {
+		gint unsorted = e_sorter_sorted_to_model (E_SORTER (reflow->sorter), i);
+		if (i != 0 && running_height + reflow->heights[unsorted] + E_REFLOW_BORDER_WIDTH > reflow->height) {
+			list = g_slist_prepend (list, GINT_TO_POINTER (i));
+			column_count++;
+			running_height = E_REFLOW_BORDER_WIDTH * 2 + reflow->heights[unsorted];
+		} else
+			running_height += reflow->heights[unsorted] + E_REFLOW_BORDER_WIDTH;
+	}
+
+	reflow->column_count = column_count;
+	reflow->columns = g_renew (int, reflow->columns, column_count);
+	column_count--;
+
+	for (; list && column_count > column_start; column_count--) {
+		GSList *to_free;
+		reflow->columns[column_count] = GPOINTER_TO_INT (list->data);
+		to_free = list;
+		list = list->next;
+		g_slist_free_1 (to_free);
+	}
+	reflow->columns[column_start] = start;
+
+	queue_incarnate (reflow);
+
+	reflow->need_reflow_columns = FALSE;
+	reflow->reflow_from_column = -1;
+}
+
+static void
+item_changed (EReflowModel *model,
+              gint i,
+              EReflow *reflow)
+{
+	if (i < 0 || i >= reflow->count)
+		return;
+
+	reflow->heights[i] = e_reflow_model_height (reflow->model, i, GNOME_CANVAS_GROUP (reflow));
+	if (reflow->items[i] != NULL)
+		e_reflow_model_reincarnate (model, i, reflow->items[i]);
+	e_sorter_array_clean (reflow->sorter);
+	reflow->reflow_from_column = -1;
+	reflow->need_reflow_columns = TRUE;
+	e_canvas_item_request_reflow (GNOME_CANVAS_ITEM (reflow));
+}
+
+static void
+item_removed (EReflowModel *model,
+              gint i,
+              EReflow *reflow)
+{
+	gint c;
+	gint sorted;
+
+	if (i < 0 || i >= reflow->count)
+		return;
+
+	sorted = e_sorter_model_to_sorted (E_SORTER (reflow->sorter), i);
+	for (c = reflow->column_count - 1; c >= 0; c--) {
+		gint start_of_column = reflow->columns[c];
+
+		if (start_of_column <= sorted) {
+			if (reflow->reflow_from_column == -1
+			    || reflow->reflow_from_column > c) {
+				reflow->reflow_from_column = c;
+			}
+			break;
+		}
+	}
+
+	if (reflow->items[i])
+		g_object_run_dispose (G_OBJECT (reflow->items[i]));
+
+	memmove (reflow->heights + i, reflow->heights + i + 1, (reflow->count - i - 1) * sizeof (gint));
+	memmove (reflow->items + i, reflow->items + i + 1, (reflow->count - i - 1) * sizeof (GnomeCanvasItem *));
+
+	reflow->count--;
+
+	reflow->heights[reflow->count] = 0;
+	reflow->items[reflow->count] = NULL;
+
+	reflow->need_reflow_columns = TRUE;
+	set_empty (reflow);
+	e_canvas_item_request_reflow (GNOME_CANVAS_ITEM (reflow));
+
+	e_sorter_array_set_count (reflow->sorter, reflow->count);
+
+	e_selection_model_simple_delete_rows (E_SELECTION_MODEL_SIMPLE (reflow->selection), i, 1);
+}
+
+static void
+items_inserted (EReflowModel *model,
+                gint position,
+                gint count,
+                EReflow *reflow)
+{
+	gint i, oldcount;
+
+	if (position < 0 || position > reflow->count)
+		return;
+
+	oldcount = reflow->count;
+
+	reflow->count += count;
+
+	if (reflow->count > reflow->allocated_count) {
+		while (reflow->count > reflow->allocated_count)
+			reflow->allocated_count += 256;
+		reflow->heights = g_renew (int, reflow->heights, reflow->allocated_count);
+		reflow->items = g_renew (GnomeCanvasItem *, reflow->items, reflow->allocated_count);
+	}
+	memmove (reflow->heights + position + count, reflow->heights + position, (reflow->count - position - count) * sizeof (gint));
+	memmove (reflow->items + position + count, reflow->items + position, (reflow->count - position - count) * sizeof (GnomeCanvasItem *));
+	for (i = position; i < position + count; i++) {
+		reflow->items[i] = NULL;
+		reflow->heights[i] = e_reflow_model_height (reflow->model, i, GNOME_CANVAS_GROUP (reflow));
+	}
+
+	e_selection_model_simple_set_row_count (E_SELECTION_MODEL_SIMPLE (reflow->selection), reflow->count);
+	if (position == oldcount)
+		e_sorter_array_append (reflow->sorter, count);
+	else
+		e_sorter_array_set_count (reflow->sorter, reflow->count);
+
+	for (i = position; i < position + count; i++) {
+		gint sorted = e_sorter_model_to_sorted (E_SORTER (reflow->sorter), i);
+		gint c;
+
+		for (c = reflow->column_count - 1; c >= 0; c--) {
+			gint start_of_column = reflow->columns[c];
+
+			if (start_of_column <= sorted) {
+				if (reflow->reflow_from_column == -1
+				    || reflow->reflow_from_column > c) {
+					reflow->reflow_from_column = c;
+				}
+				break;
+			}
+		}
+	}
+
+	reflow->need_reflow_columns = TRUE;
+	set_empty (reflow);
+	e_canvas_item_request_reflow (GNOME_CANVAS_ITEM (reflow));
+}
+
+static void
+model_changed (EReflowModel *model,
+               EReflow *reflow)
+{
+	gint i;
+	gint count;
+	gint oldcount;
+
+	count = reflow->count;
+	oldcount = count;
+
+	for (i = 0; i < count; i++) {
+		if (reflow->items[i])
+			g_object_run_dispose (G_OBJECT (reflow->items[i]));
+	}
+	g_free (reflow->items);
+	g_free (reflow->heights);
+	reflow->count = e_reflow_model_count (model);
+	reflow->allocated_count = reflow->count;
+	reflow->items = g_new (GnomeCanvasItem *, reflow->count);
+	reflow->heights = g_new (int, reflow->count);
+
+	count = reflow->count;
+	for (i = 0; i < count; i++) {
+		reflow->items[i] = NULL;
+		reflow->heights[i] = e_reflow_model_height (reflow->model, i, GNOME_CANVAS_GROUP (reflow));
+	}
+
+	e_selection_model_simple_set_row_count (E_SELECTION_MODEL_SIMPLE (reflow->selection), count);
+	e_sorter_array_set_count (reflow->sorter, reflow->count);
+
+	reflow->need_reflow_columns = TRUE;
+	if (oldcount > reflow->count)
+		reflow_columns (reflow);
+	set_empty (reflow);
+	e_canvas_item_request_reflow (GNOME_CANVAS_ITEM (reflow));
+}
+
+static void
+comparison_changed (EReflowModel *model,
+                    EReflow *reflow)
+{
+	e_sorter_array_clean (reflow->sorter);
+	reflow->reflow_from_column = -1;
+	reflow->need_reflow_columns = TRUE;
+	e_canvas_item_request_reflow (GNOME_CANVAS_ITEM (reflow));
+}
+
+static void
+set_empty (EReflow *reflow)
+{
+	if (reflow->count == 0) {
+		if (reflow->empty_text) {
+			if (reflow->empty_message) {
+				gnome_canvas_item_set (
+					reflow->empty_text,
+					"width", reflow->minimum_width,
+					"text", reflow->empty_message,
+					NULL);
+				e_canvas_item_move_absolute (
+					reflow->empty_text,
+					reflow->minimum_width / 2,
+					0);
+			} else {
+				g_object_run_dispose (G_OBJECT (reflow->empty_text));
+				reflow->empty_text = NULL;
+			}
+		} else {
+			if (reflow->empty_message) {
+				reflow->empty_text = gnome_canvas_item_new (
+					GNOME_CANVAS_GROUP (reflow),
+					e_text_get_type (),
+					"width", reflow->minimum_width,
+					"clip", TRUE,
+					"use_ellipsis", TRUE,
+					"justification", GTK_JUSTIFY_CENTER,
+					"text", reflow->empty_message,
+					NULL);
+				e_canvas_item_move_absolute (
+					reflow->empty_text,
+					reflow->minimum_width / 2,
+					0);
+			}
+		}
+	} else {
+		if (reflow->empty_text) {
+			g_object_run_dispose (G_OBJECT (reflow->empty_text));
+			reflow->empty_text = NULL;
+		}
+	}
+}
+
+static void
+disconnect_model (EReflow *reflow)
+{
+	if (reflow->model == NULL)
+		return;
+
+	g_signal_handler_disconnect (
+		reflow->model,
+		reflow->model_changed_id);
+	g_signal_handler_disconnect (
+		reflow->model,
+		reflow->comparison_changed_id);
+	g_signal_handler_disconnect (
+		reflow->model,
+		reflow->model_items_inserted_id);
+	g_signal_handler_disconnect (
+		reflow->model,
+		reflow->model_item_removed_id);
+	g_signal_handler_disconnect (
+		reflow->model,
+		reflow->model_item_changed_id);
+	g_object_unref (reflow->model);
+
+	reflow->model_changed_id        = 0;
+	reflow->comparison_changed_id   = 0;
+	reflow->model_items_inserted_id = 0;
+	reflow->model_item_removed_id   = 0;
+	reflow->model_item_changed_id   = 0;
+	reflow->model                   = NULL;
+}
+
+static void
+disconnect_selection (EReflow *reflow)
+{
+	if (reflow->selection == NULL)
+		return;
+
+	g_signal_handler_disconnect (
+		reflow->selection,
+		reflow->selection_changed_id);
+	g_signal_handler_disconnect (
+		reflow->selection,
+		reflow->selection_row_changed_id);
+	g_signal_handler_disconnect (
+		reflow->selection,
+		reflow->cursor_changed_id);
+	g_object_unref (reflow->selection);
+
+	reflow->selection_changed_id = 0;
+	reflow->selection_row_changed_id = 0;
+	reflow->cursor_changed_id = 0;
+	reflow->selection            = NULL;
+}
+
+static void
+connect_model (EReflow *reflow,
+               EReflowModel *model)
+{
+	if (reflow->model != NULL)
+		disconnect_model (reflow);
+
+	if (model == NULL)
+		return;
+
+	reflow->model = g_object_ref (model);
+
+	reflow->model_changed_id = g_signal_connect (
+		reflow->model, "model_changed",
+		G_CALLBACK (model_changed), reflow);
+
+	reflow->comparison_changed_id = g_signal_connect (
+		reflow->model, "comparison_changed",
+		G_CALLBACK (comparison_changed), reflow);
+
+	reflow->model_items_inserted_id = g_signal_connect (
+		reflow->model, "model_items_inserted",
+		G_CALLBACK (items_inserted), reflow);
+
+	reflow->model_item_removed_id = g_signal_connect (
+		reflow->model, "model_item_removed",
+		G_CALLBACK (item_removed), reflow);
+
+	reflow->model_item_changed_id = g_signal_connect (
+		reflow->model, "model_item_changed",
+		G_CALLBACK (item_changed), reflow);
+
+	model_changed (model, reflow);
+}
+
+static void
+adjustment_changed (GtkAdjustment *adjustment,
+                    EReflow *reflow)
+{
+	queue_incarnate (reflow);
+}
+
+static void
+disconnect_adjustment (EReflow *reflow)
+{
+	if (reflow->adjustment == NULL)
+		return;
+
+	g_signal_handler_disconnect (
+		reflow->adjustment,
+		reflow->adjustment_changed_id);
+	g_signal_handler_disconnect (
+		reflow->adjustment,
+		reflow->adjustment_value_changed_id);
+
+	g_object_unref (reflow->adjustment);
+
+	reflow->adjustment_changed_id = 0;
+	reflow->adjustment_value_changed_id = 0;
+	reflow->adjustment = NULL;
+}
+
+static void
+connect_adjustment (EReflow *reflow,
+                    GtkAdjustment *adjustment)
+{
+	if (reflow->adjustment != NULL)
+		disconnect_adjustment (reflow);
+
+	if (adjustment == NULL)
+		return;
+
+	reflow->adjustment = g_object_ref (adjustment);
+
+	reflow->adjustment_changed_id = g_signal_connect (
+		adjustment, "changed",
+		G_CALLBACK (adjustment_changed), reflow);
+
+	reflow->adjustment_value_changed_id = g_signal_connect (
+		adjustment, "value_changed",
+		G_CALLBACK (adjustment_changed), reflow);
+}
+
+#if 0
+static void
+set_scroll_adjustments (GtkLayout *layout,
+                        GtkAdjustment *hadj,
+                        GtkAdjustment *vadj,
+                        EReflow *reflow)
+{
+	connect_adjustment (reflow, hadj);
+}
+
+static void
+connect_set_adjustment (EReflow *reflow)
+{
+	reflow->set_scroll_adjustments_id = g_signal_connect (
+		GNOME_CANVAS_ITEM (reflow)->canvas, "set_scroll_adjustments",
+		G_CALLBACK (set_scroll_adjustments), reflow);
+}
+#endif
+
+static void
+disconnect_set_adjustment (EReflow *reflow)
+{
+	if (reflow->set_scroll_adjustments_id != 0) {
+		g_signal_handler_disconnect (
+			GNOME_CANVAS_ITEM (reflow)->canvas,
+			reflow->set_scroll_adjustments_id);
+		reflow->set_scroll_adjustments_id = 0;
+	}
+}
+
+static void
+column_width_changed (EReflow *reflow)
+{
+	g_signal_emit (reflow, signals[COLUMN_WIDTH_CHANGED], 0, reflow->column_width);
+}
+
+/* Virtual functions */
+static void
+e_reflow_set_property (GObject *object,
+                       guint property_id,
+                       const GValue *value,
+                       GParamSpec *pspec)
+{
+	GnomeCanvasItem *item;
+	EReflow *reflow;
+
+	item = GNOME_CANVAS_ITEM (object);
+	reflow = E_REFLOW (object);
+
+	switch (property_id) {
+	case PROP_HEIGHT:
+		reflow->height = g_value_get_double (value);
+		reflow->need_reflow_columns = TRUE;
+		e_canvas_item_request_reflow (item);
+		break;
+	case PROP_MINIMUM_WIDTH:
+		reflow->minimum_width = g_value_get_double (value);
+		if (item->flags & GNOME_CANVAS_ITEM_REALIZED)
+			set_empty (reflow);
+		e_canvas_item_request_reflow (item);
+		break;
+	case PROP_EMPTY_MESSAGE:
+		g_free (reflow->empty_message);
+		reflow->empty_message = g_strdup (g_value_get_string (value));
+		if (item->flags & GNOME_CANVAS_ITEM_REALIZED)
+			set_empty (reflow);
+		break;
+	case PROP_MODEL:
+		connect_model (reflow, (EReflowModel *) g_value_get_object (value));
+		break;
+	case PROP_COLUMN_WIDTH:
+		if (reflow->column_width != g_value_get_double (value)) {
+			GtkLayout *layout;
+			GtkAdjustment *adjustment;
+			gdouble old_width = reflow->column_width;
+			gdouble step_increment;
+			gdouble page_size;
+
+			layout = GTK_LAYOUT (item->canvas);
+			adjustment = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (layout));
+			page_size = gtk_adjustment_get_page_size (adjustment);
+
+			reflow->column_width = g_value_get_double (value);
+			step_increment = (reflow->column_width +
+				E_REFLOW_FULL_GUTTER) / 2;
+			gtk_adjustment_set_step_increment (
+				adjustment, step_increment);
+			gtk_adjustment_set_page_increment (
+				adjustment, page_size - step_increment);
+			e_reflow_resize_children (item);
+			e_canvas_item_request_reflow (item);
+
+			reflow->need_column_resize = TRUE;
+			gnome_canvas_item_request_update (item);
+
+			if (old_width != reflow->column_width)
+				column_width_changed (reflow);
+		}
+		break;
+	}
+}
+
+static void
+e_reflow_get_property (GObject *object,
+                       guint property_id,
+                       GValue *value,
+                       GParamSpec *pspec)
+{
+	EReflow *reflow;
+
+	reflow = E_REFLOW (object);
+
+	switch (property_id) {
+	case PROP_MINIMUM_WIDTH:
+		g_value_set_double (value, reflow->minimum_width);
+		break;
+	case PROP_WIDTH:
+		g_value_set_double (value, reflow->width);
+		break;
+	case PROP_HEIGHT:
+		g_value_set_double (value, reflow->height);
+		break;
+	case PROP_EMPTY_MESSAGE:
+		g_value_set_string (value, reflow->empty_message);
+		break;
+	case PROP_MODEL:
+		g_value_set_object (value, reflow->model);
+		break;
+	case PROP_COLUMN_WIDTH:
+		g_value_set_double (value, reflow->column_width);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+		break;
+	}
+}
+
+static void
+e_reflow_dispose (GObject *object)
+{
+	EReflow *reflow = E_REFLOW (object);
+
+	g_free (reflow->items);
+	g_free (reflow->heights);
+	g_free (reflow->columns);
+
+	reflow->items          = NULL;
+	reflow->heights        = NULL;
+	reflow->columns        = NULL;
+	reflow->count          = 0;
+	reflow->allocated_count = 0;
+
+	if (reflow->incarnate_idle_id)
+		g_source_remove (reflow->incarnate_idle_id);
+	reflow->incarnate_idle_id = 0;
+
+	if (reflow->do_adjustment_idle_id)
+		g_source_remove (reflow->do_adjustment_idle_id);
+	reflow->do_adjustment_idle_id = 0;
+
+	disconnect_model (reflow);
+	disconnect_selection (reflow);
+
+	g_free (reflow->empty_message);
+	reflow->empty_message = NULL;
+
+	if (reflow->sorter) {
+		g_object_unref (reflow->sorter);
+		reflow->sorter = NULL;
+	}
+
+	G_OBJECT_CLASS (e_reflow_parent_class)->dispose (object);
+}
+
+static void
+e_reflow_realize (GnomeCanvasItem *item)
+{
+	EReflow *reflow;
+	GtkAdjustment *adjustment;
+	gdouble page_increment;
+	gdouble step_increment;
+	gdouble page_size;
+	gint count;
+	gint i;
+
+	reflow = E_REFLOW (item);
+
+	if (GNOME_CANVAS_ITEM_CLASS (e_reflow_parent_class)->realize)
+		(* GNOME_CANVAS_ITEM_CLASS (e_reflow_parent_class)->realize) (item);
+
+	reflow->arrow_cursor = gdk_cursor_new (GDK_SB_H_DOUBLE_ARROW);
+	reflow->default_cursor = gdk_cursor_new (GDK_LEFT_PTR);
+
+	count = reflow->count;
+	for (i = 0; i < count; i++) {
+		if (reflow->items[i])
+			gnome_canvas_item_set (
+				reflow->items[i],
+				"width", reflow->column_width,
+				NULL);
+	}
+
+	set_empty (reflow);
+
+	reflow->need_reflow_columns = TRUE;
+	e_canvas_item_request_reflow (item);
+
+	adjustment = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (item->canvas));
+
+#if 0
+	connect_set_adjustment (reflow);
+#endif
+	connect_adjustment (reflow, adjustment);
+
+	page_size = gtk_adjustment_get_page_size (adjustment);
+	step_increment = (reflow->column_width + E_REFLOW_FULL_GUTTER) / 2;
+	page_increment = page_size - step_increment;
+	gtk_adjustment_set_step_increment (adjustment, step_increment);
+	gtk_adjustment_set_page_increment (adjustment, page_increment);
+}
+
+static void
+e_reflow_unrealize (GnomeCanvasItem *item)
+{
+	EReflow *reflow;
+
+	reflow = E_REFLOW (item);
+
+	g_object_unref (reflow->arrow_cursor);
+	g_object_unref (reflow->default_cursor);
+	reflow->arrow_cursor = NULL;
+	reflow->default_cursor = NULL;
+
+	g_free (reflow->columns);
+	reflow->columns = NULL;
+
+	disconnect_set_adjustment (reflow);
+	disconnect_adjustment (reflow);
+
+	if (GNOME_CANVAS_ITEM_CLASS (e_reflow_parent_class)->unrealize)
+		(* GNOME_CANVAS_ITEM_CLASS (e_reflow_parent_class)->unrealize) (item);
+}
+
+static gboolean
+e_reflow_event (GnomeCanvasItem *item,
+                GdkEvent *event)
+{
+	EReflow *reflow;
+	gint return_val = FALSE;
+
+	reflow = E_REFLOW (item);
+
+	switch (event->type)
+		{
+		case GDK_KEY_PRESS:
+			return_val = e_selection_model_key_press (reflow->selection, (GdkEventKey *) event);
+			break;
+#if 0
+			if (event->key.keyval == GDK_Tab ||
+			    event->key.keyval == GDK_KEY_KP_Tab ||
+			    event->key.keyval == GDK_ISO_Left_Tab) {
+				gint i;
+				gint count;
+				count = reflow->count;
+				for (i = 0; i < count; i++) {
+					gint unsorted = e_sorter_sorted_to_model (E_SORTER (reflow->sorter), i);
+					GnomeCanvasItem *item = reflow->items[unsorted];
+					EFocus has_focus;
+					if (item) {
+						g_object_get (
+							item,
+							"has_focus", &has_focus,
+							NULL);
+						if (has_focus) {
+							if (event->key.state & GDK_SHIFT_MASK) {
+								if (i == 0)
+									return FALSE;
+								i--;
+							} else {
+								if (i == count - 1)
+									return FALSE;
+								i++;
+							}
+
+							unsorted = e_sorter_sorted_to_model (E_SORTER (reflow->sorter), i);
+							if (reflow->items[unsorted] == NULL) {
+								reflow->items[unsorted] = e_reflow_model_incarnate (reflow->model, unsorted, GNOME_CANVAS_GROUP (reflow));
+							}
+
+							item = reflow->items[unsorted];
+							gnome_canvas_item_set (
+								item,
+								"has_focus", (event->key.state & GDK_SHIFT_MASK) ? E_FOCUS_END : E_FOCUS_START,
+								NULL);
+							return TRUE;
+						}
+					}
+				}
+			}
+#endif
+		case GDK_BUTTON_PRESS:
+			switch (event->button.button)
+				{
+				case 1:
+					{
+						GdkEventButton *button = (GdkEventButton *) event;
+						gdouble n_x;
+						n_x = button->x;
+						n_x += E_REFLOW_BORDER_WIDTH + E_REFLOW_DIVIDER_WIDTH;
+						n_x = fmod (n_x,(reflow->column_width + E_REFLOW_FULL_GUTTER));
+
+						if (button->y >= E_REFLOW_BORDER_WIDTH && button->y <= reflow->height - E_REFLOW_BORDER_WIDTH && n_x < E_REFLOW_FULL_GUTTER) {
+							/* don't allow to drag the first line*/
+							if (e_reflow_pick_line (reflow, button->x) == 0)
+								return TRUE;
+							reflow->which_column_dragged = e_reflow_pick_line (reflow, button->x);
+							reflow->start_x = reflow->which_column_dragged * (reflow->column_width + E_REFLOW_FULL_GUTTER) - E_REFLOW_DIVIDER_WIDTH / 2;
+							reflow->temp_column_width = reflow->column_width;
+							reflow->column_drag = TRUE;
+
+							gnome_canvas_item_grab (
+								item,
+								GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK,
+								reflow->arrow_cursor,
+								button->device,
+								button->time);
+
+							reflow->previous_temp_column_width = -1;
+							reflow->need_column_resize = TRUE;
+							gnome_canvas_item_request_update (item);
+							return TRUE;
+						}
+					}
+					break;
+				case 4:
+					{
+						GtkLayout *layout;
+						GtkAdjustment *adjustment;
+						gdouble new_value;
+
+						layout = GTK_LAYOUT (item->canvas);
+						adjustment = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (layout));
+						new_value = gtk_adjustment_get_value (adjustment);
+						new_value -= gtk_adjustment_get_step_increment (adjustment);
+						gtk_adjustment_set_value (adjustment, new_value);
+					}
+					break;
+				case 5:
+					{
+						GtkLayout *layout;
+						GtkAdjustment *adjustment;
+						gdouble new_value;
+						gdouble page_size;
+						gdouble upper;
+
+						layout = GTK_LAYOUT (item->canvas);
+						adjustment = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (layout));
+						new_value = gtk_adjustment_get_value (adjustment);
+						new_value += gtk_adjustment_get_step_increment (adjustment);
+						upper = gtk_adjustment_get_upper (adjustment);
+						page_size = gtk_adjustment_get_page_size (adjustment);
+						if (new_value > upper - page_size)
+							new_value = upper - page_size;
+						gtk_adjustment_set_value (adjustment, new_value);
+					}
+					break;
+				}
+			break;
+		case GDK_BUTTON_RELEASE:
+			if (reflow->column_drag) {
+				gdouble old_width = reflow->column_width;
+				GdkEventButton *button = (GdkEventButton *) event;
+				GtkAdjustment *adjustment;
+				GtkLayout *layout;
+				gdouble value;
+
+				layout = GTK_LAYOUT (item->canvas);
+				adjustment = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (layout));
+				value = gtk_adjustment_get_value (adjustment);
+
+				reflow->temp_column_width = reflow->column_width +
+					(button->x - reflow->start_x) / (reflow->which_column_dragged - e_reflow_pick_line (reflow, value));
+				if (reflow->temp_column_width < 50)
+					reflow->temp_column_width = 50;
+				reflow->column_drag = FALSE;
+				if (old_width != reflow->temp_column_width) {
+					gdouble page_increment;
+					gdouble step_increment;
+					gdouble page_size;
+
+					page_size = gtk_adjustment_get_page_size (adjustment);
+					gtk_adjustment_set_value (adjustment, value + e_reflow_pick_line (reflow, value) * (reflow->temp_column_width - reflow->column_width));
+					reflow->column_width = reflow->temp_column_width;
+					step_increment = (reflow->column_width + E_REFLOW_FULL_GUTTER) / 2;
+					page_increment = page_size - step_increment;
+					gtk_adjustment_set_step_increment (adjustment, step_increment);
+					gtk_adjustment_set_page_increment (adjustment, page_increment);
+					e_reflow_resize_children (item);
+					e_canvas_item_request_reflow (item);
+					gnome_canvas_request_redraw (item->canvas, 0, 0, reflow->width, reflow->height);
+					column_width_changed (reflow);
+				}
+				reflow->need_column_resize = TRUE;
+				gnome_canvas_item_request_update (item);
+				gnome_canvas_item_ungrab (item, button->time);
+				return TRUE;
+			}
+			break;
+		case GDK_MOTION_NOTIFY:
+			if (reflow->column_drag) {
+				gdouble old_width = reflow->temp_column_width;
+				GdkEventMotion *motion = (GdkEventMotion *) event;
+				GtkAdjustment *adjustment;
+				GtkLayout *layout;
+				gdouble value;
+
+				layout = GTK_LAYOUT (item->canvas);
+				adjustment = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (layout));
+				value = gtk_adjustment_get_value (adjustment);
+
+				reflow->temp_column_width = reflow->column_width +
+					(motion->x - reflow->start_x) / (reflow->which_column_dragged - e_reflow_pick_line (reflow, value));
+				if (reflow->temp_column_width < 50)
+					reflow->temp_column_width = 50;
+				if (old_width != reflow->temp_column_width) {
+					reflow->need_column_resize = TRUE;
+					gnome_canvas_item_request_update (item);
+				}
+				return TRUE;
+			} else {
+				GdkEventMotion *motion = (GdkEventMotion *) event;
+				GdkWindow *window;
+				gdouble n_x;
+
+				n_x = motion->x;
+				n_x += E_REFLOW_BORDER_WIDTH + E_REFLOW_DIVIDER_WIDTH;
+				n_x = fmod (n_x,(reflow->column_width + E_REFLOW_FULL_GUTTER));
+
+				window = gtk_widget_get_window (GTK_WIDGET (item->canvas));
+
+				if (motion->y >= E_REFLOW_BORDER_WIDTH && motion->y <= reflow->height - E_REFLOW_BORDER_WIDTH && n_x < E_REFLOW_FULL_GUTTER) {
+					if (reflow->default_cursor_shown) {
+						gdk_window_set_cursor (window, reflow->arrow_cursor);
+						reflow->default_cursor_shown = FALSE;
+					}
+				} else
+					if (!reflow->default_cursor_shown) {
+						gdk_window_set_cursor (window, reflow->default_cursor);
+						reflow->default_cursor_shown = TRUE;
+					}
+
+			}
+			break;
+		case GDK_ENTER_NOTIFY:
+			if (!reflow->column_drag) {
+				GdkEventCrossing *crossing = (GdkEventCrossing *) event;
+				GdkWindow *window;
+				gdouble n_x;
+
+				n_x = crossing->x;
+				n_x += E_REFLOW_BORDER_WIDTH + E_REFLOW_DIVIDER_WIDTH;
+				n_x = fmod (n_x,(reflow->column_width + E_REFLOW_FULL_GUTTER));
+
+				window = gtk_widget_get_window (GTK_WIDGET (item->canvas));
+
+				if (crossing->y >= E_REFLOW_BORDER_WIDTH && crossing->y <= reflow->height - E_REFLOW_BORDER_WIDTH && n_x < E_REFLOW_FULL_GUTTER) {
+					if (reflow->default_cursor_shown) {
+						gdk_window_set_cursor (window, reflow->arrow_cursor);
+						reflow->default_cursor_shown = FALSE;
+					}
+				}
+			}
+			break;
+		case GDK_LEAVE_NOTIFY:
+			if (!reflow->column_drag) {
+				GdkEventCrossing *crossing = (GdkEventCrossing *) event;
+				GdkWindow *window;
+				gdouble n_x;
+
+				n_x = crossing->x;
+				n_x += E_REFLOW_BORDER_WIDTH + E_REFLOW_DIVIDER_WIDTH;
+				n_x = fmod (n_x,(reflow->column_width + E_REFLOW_FULL_GUTTER));
+
+				window = gtk_widget_get_window (GTK_WIDGET (item->canvas));
+
+				if (!(crossing->y >= E_REFLOW_BORDER_WIDTH && crossing->y <= reflow->height - E_REFLOW_BORDER_WIDTH && n_x < E_REFLOW_FULL_GUTTER)) {
+					if (!reflow->default_cursor_shown) {
+						gdk_window_set_cursor (window, reflow->default_cursor);
+						reflow->default_cursor_shown = TRUE;
+					}
+				}
+			}
+			break;
+		default:
+			break;
+		}
+	if (return_val)
+		return return_val;
+	else if (GNOME_CANVAS_ITEM_CLASS (e_reflow_parent_class)->event)
+		return (* GNOME_CANVAS_ITEM_CLASS (e_reflow_parent_class)->event) (item, event);
+	else
+		return FALSE;
+}
+
+static void
+e_reflow_draw (GnomeCanvasItem *item,
+               cairo_t *cr,
+               gint x,
+               gint y,
+               gint width,
+               gint height)
+{
+	GtkStyleContext *style_context;
+	GtkWidget *widget;
+	gint x_rect, y_rect, width_rect, height_rect;
+	gdouble running_width;
+	EReflow *reflow = E_REFLOW (item);
+	GdkRGBA color;
+	gint i;
+	gdouble column_width;
+
+	if (GNOME_CANVAS_ITEM_CLASS (e_reflow_parent_class)->draw)
+		GNOME_CANVAS_ITEM_CLASS (e_reflow_parent_class)->draw (item, cr, x, y, width, height);
+	column_width = reflow->column_width;
+	running_width = E_REFLOW_BORDER_WIDTH + column_width + E_REFLOW_BORDER_WIDTH;
+	y_rect = E_REFLOW_BORDER_WIDTH;
+	width_rect = E_REFLOW_DIVIDER_WIDTH;
+	height_rect = reflow->height - (E_REFLOW_BORDER_WIDTH * 2);
+
+	/* Compute first column to draw. */
+	i = x;
+	i /= column_width + E_REFLOW_FULL_GUTTER;
+	running_width += i * (column_width + E_REFLOW_FULL_GUTTER);
+
+	widget = GTK_WIDGET (item->canvas);
+	style_context = gtk_widget_get_style_context (widget);
+
+	cairo_save (cr);
+
+	gtk_style_context_get_background_color (
+		style_context, GTK_STATE_FLAG_ACTIVE, &color);
+	gdk_cairo_set_source_rgba (cr, &color);
+
+	for (; i < reflow->column_count; i++) {
+		if (running_width > x + width)
+			break;
+		x_rect = running_width;
+
+		gtk_render_background (
+			style_context, cr,
+			(gdouble) x_rect - x,
+			(gdouble) y_rect - y,
+			(gdouble) width_rect,
+			(gdouble) height_rect);
+
+		running_width += E_REFLOW_DIVIDER_WIDTH + E_REFLOW_BORDER_WIDTH + column_width + E_REFLOW_BORDER_WIDTH;
+	}
+
+	cairo_restore (cr);
+
+	if (reflow->column_drag) {
+		GtkAdjustment *adjustment;
+		GtkLayout *layout;
+		gdouble value;
+		gint start_line;
+
+		layout = GTK_LAYOUT (item->canvas);
+		adjustment = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (layout));
+		value = gtk_adjustment_get_value (adjustment);
+
+		start_line = e_reflow_pick_line (reflow, value);
+		i = x - start_line * (column_width + E_REFLOW_FULL_GUTTER);
+		running_width = start_line * (column_width + E_REFLOW_FULL_GUTTER);
+		column_width = reflow->temp_column_width;
+		running_width -= start_line * (column_width + E_REFLOW_FULL_GUTTER);
+		i += start_line * (column_width + E_REFLOW_FULL_GUTTER);
+		running_width += E_REFLOW_BORDER_WIDTH + column_width + E_REFLOW_BORDER_WIDTH;
+		y_rect = E_REFLOW_BORDER_WIDTH;
+		width_rect = E_REFLOW_DIVIDER_WIDTH;
+		height_rect = reflow->height - (E_REFLOW_BORDER_WIDTH * 2);
+
+		/* Compute first column to draw. */
+		i /= column_width + E_REFLOW_FULL_GUTTER;
+		running_width += i * (column_width + E_REFLOW_FULL_GUTTER);
+
+		cairo_save (cr);
+
+		gtk_style_context_get_color (
+			style_context, GTK_STATE_FLAG_NORMAL, &color);
+		gdk_cairo_set_source_rgba (cr, &color);
+
+		for (; i < reflow->column_count; i++) {
+			if (running_width > x + width)
+				break;
+			x_rect = running_width;
+			cairo_rectangle (
+				cr,
+				x_rect - x,
+				y_rect - y,
+				width_rect - 1,
+				height_rect - 1);
+			cairo_fill (cr);
+			running_width += E_REFLOW_DIVIDER_WIDTH + E_REFLOW_BORDER_WIDTH + column_width + E_REFLOW_BORDER_WIDTH;
+		}
+
+		cairo_restore (cr);
+	}
+}
+
+static void
+e_reflow_update (GnomeCanvasItem *item,
+                 const cairo_matrix_t *i2c,
+                 gint flags)
+{
+	EReflow *reflow;
+	gdouble x0, x1, y0, y1;
+
+	reflow = E_REFLOW (item);
+
+	if (GNOME_CANVAS_ITEM_CLASS (e_reflow_parent_class)->update)
+		GNOME_CANVAS_ITEM_CLASS (e_reflow_parent_class)->update (item, i2c, flags);
+
+	x0 = item->x1;
+	y0 = item->y1;
+	x1 = item->x2;
+	y1 = item->y2;
+	if (x1 < x0 + reflow->width)
+		x1 = x0 + reflow->width;
+	if (y1 < y0 + reflow->height)
+		y1 = y0 + reflow->height;
+	item->x2 = x1;
+	item->y2 = y1;
+
+	if (reflow->need_height_update) {
+		x0 = item->x1;
+		y0 = item->y1;
+		x1 = item->x2;
+		y1 = item->y2;
+		if (x0 > 0)
+			x0 = 0;
+		if (y0 > 0)
+			y0 = 0;
+		if (x1 < E_REFLOW (item)->width)
+			x1 = E_REFLOW (item)->width;
+		if (x1 < E_REFLOW (item)->height)
+			x1 = E_REFLOW (item)->height;
+
+		gnome_canvas_request_redraw (item->canvas, x0, y0, x1, y1);
+		reflow->need_height_update = FALSE;
+	} else if (reflow->need_column_resize) {
+		GtkLayout *layout;
+		GtkAdjustment *adjustment;
+		gint x_rect, y_rect, width_rect, height_rect;
+		gint start_line;
+		gdouble running_width;
+		gint i;
+		gdouble column_width;
+		gdouble value;
+
+		layout = GTK_LAYOUT (item->canvas);
+		adjustment = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (layout));
+		value = gtk_adjustment_get_value (adjustment);
+		start_line = e_reflow_pick_line (reflow, value);
+
+		if (reflow->previous_temp_column_width != -1) {
+			running_width = start_line * (reflow->column_width + E_REFLOW_FULL_GUTTER);
+			column_width = reflow->previous_temp_column_width;
+			running_width -= start_line * (column_width + E_REFLOW_FULL_GUTTER);
+			running_width += E_REFLOW_BORDER_WIDTH + column_width + E_REFLOW_BORDER_WIDTH;
+			y_rect = E_REFLOW_BORDER_WIDTH;
+			width_rect = E_REFLOW_DIVIDER_WIDTH;
+			height_rect = reflow->height - (E_REFLOW_BORDER_WIDTH * 2);
+
+			for (i = 0; i < reflow->column_count; i++) {
+				x_rect = running_width;
+				gnome_canvas_request_redraw (item->canvas, x_rect, y_rect, x_rect + width_rect, y_rect + height_rect);
+				running_width += E_REFLOW_DIVIDER_WIDTH + E_REFLOW_BORDER_WIDTH + column_width + E_REFLOW_BORDER_WIDTH;
+			}
+		}
+
+		if (reflow->temp_column_width != -1) {
+			running_width = start_line * (reflow->column_width + E_REFLOW_FULL_GUTTER);
+			column_width = reflow->temp_column_width;
+			running_width -= start_line * (column_width + E_REFLOW_FULL_GUTTER);
+			running_width += E_REFLOW_BORDER_WIDTH + column_width + E_REFLOW_BORDER_WIDTH;
+			y_rect = E_REFLOW_BORDER_WIDTH;
+			width_rect = E_REFLOW_DIVIDER_WIDTH;
+			height_rect = reflow->height - (E_REFLOW_BORDER_WIDTH * 2);
+
+			for (i = 0; i < reflow->column_count; i++) {
+				x_rect = running_width;
+				gnome_canvas_request_redraw (item->canvas, x_rect, y_rect, x_rect + width_rect, y_rect + height_rect);
+				running_width += E_REFLOW_DIVIDER_WIDTH + E_REFLOW_BORDER_WIDTH + column_width + E_REFLOW_BORDER_WIDTH;
+			}
+		}
+
+		reflow->previous_temp_column_width = reflow->temp_column_width;
+		reflow->need_column_resize = FALSE;
+	}
+}
+
+static GnomeCanvasItem *
+e_reflow_point (GnomeCanvasItem *item,
+                gdouble x,
+                gdouble y,
+                gint cx,
+                gint cy)
+{
+	GnomeCanvasItem *child = NULL;
+
+	if (GNOME_CANVAS_ITEM_CLASS (e_reflow_parent_class)->point)
+		child = GNOME_CANVAS_ITEM_CLASS (e_reflow_parent_class)->point (item, x, y, cx, cy);
+
+	return child ? child : item;
+#if 0
+	if (y >= E_REFLOW_BORDER_WIDTH && y <= reflow->height - E_REFLOW_BORDER_WIDTH) {
+		gfloat n_x;
+		n_x = x;
+		n_x += E_REFLOW_BORDER_WIDTH + E_REFLOW_DIVIDER_WIDTH;
+		n_x = fmod (n_x, (reflow->column_width + E_REFLOW_FULL_GUTTER));
+		if (n_x < E_REFLOW_FULL_GUTTER) {
+			*actual_item = item;
+			return 0;
+		}
+	}
+	return distance;
+#endif
+}
+
+static void
+e_reflow_reflow (GnomeCanvasItem *item,
+                 gint flags)
+{
+	EReflow *reflow = E_REFLOW (item);
+	gdouble old_width;
+	gdouble running_width;
+	gdouble running_height;
+	gint next_column;
+	gint i;
+
+	if (!(item->flags & GNOME_CANVAS_ITEM_REALIZED))
+		return;
+
+	if (reflow->need_reflow_columns) {
+		reflow_columns (reflow);
+	}
+
+	old_width = reflow->width;
+
+	running_width = E_REFLOW_BORDER_WIDTH;
+	running_height = E_REFLOW_BORDER_WIDTH;
+
+	next_column = 1;
+
+	for (i = 0; i < reflow->count; i++) {
+		gint unsorted = e_sorter_sorted_to_model (E_SORTER (reflow->sorter), i);
+		if (next_column < reflow->column_count && i == reflow->columns[next_column]) {
+			running_height = E_REFLOW_BORDER_WIDTH;
+			running_width += reflow->column_width + E_REFLOW_FULL_GUTTER;
+			next_column++;
+		}
+
+		if (unsorted >= 0 && reflow->items[unsorted]) {
+			e_canvas_item_move_absolute (
+				GNOME_CANVAS_ITEM (reflow->items[unsorted]),
+				(gdouble) running_width,
+				(gdouble) running_height);
+			running_height += reflow->heights[unsorted] + E_REFLOW_BORDER_WIDTH;
+		}
+	}
+	reflow->width = running_width + reflow->column_width + E_REFLOW_BORDER_WIDTH;
+	if (reflow->width < reflow->minimum_width)
+		reflow->width = reflow->minimum_width;
+	if (old_width != reflow->width)
+		e_canvas_item_request_parent_reflow (item);
+}
+
+static gint
+e_reflow_selection_event_real (EReflow *reflow,
+                               GnomeCanvasItem *item,
+                               GdkEvent *event)
+{
+	gint row;
+	gint return_val = TRUE;
+	switch (event->type) {
+	case GDK_BUTTON_PRESS:
+		switch (event->button.button) {
+		case 1: /* Fall through. */
+		case 2:
+			row = er_find_item (reflow, item);
+			if (event->button.button == 1) {
+				reflow->maybe_did_something =
+					e_selection_model_maybe_do_something (reflow->selection, row, 0, event->button.state);
+				reflow->maybe_in_drag = TRUE;
+			} else {
+				e_selection_model_do_something (reflow->selection, row, 0, event->button.state);
+			}
+			break;
+		case 3:
+			row = er_find_item (reflow, item);
+			e_selection_model_right_click_down (reflow->selection, row, 0, 0);
+			break;
+		default:
+			return_val = FALSE;
+			break;
+		}
+		break;
+	case GDK_BUTTON_RELEASE:
+		if (event->button.button == 1) {
+			if (reflow->maybe_in_drag) {
+				reflow->maybe_in_drag = FALSE;
+				if (!reflow->maybe_did_something) {
+					row = er_find_item (reflow, item);
+					e_selection_model_do_something (reflow->selection, row, 0, event->button.state);
+				}
+			}
+		}
+		break;
+	case GDK_KEY_PRESS:
+		return_val = e_selection_model_key_press (reflow->selection, (GdkEventKey *) event);
+		break;
+	default:
+		return_val = FALSE;
+		break;
+	}
+
+	return return_val;
+}
+
+static void
+e_reflow_class_init (EReflowClass *class)
+{
+	GObjectClass *object_class;
+	GnomeCanvasItemClass *item_class;
+
+	object_class = (GObjectClass *) class;
+	item_class = (GnomeCanvasItemClass *) class;
+
+	object_class->set_property  = e_reflow_set_property;
+	object_class->get_property  = e_reflow_get_property;
+	object_class->dispose  = e_reflow_dispose;
+
+	/* GnomeCanvasItem method overrides */
+	item_class->event      = e_reflow_event;
+	item_class->realize    = e_reflow_realize;
+	item_class->unrealize  = e_reflow_unrealize;
+	item_class->draw       = e_reflow_draw;
+	item_class->update     = e_reflow_update;
+	item_class->point      = e_reflow_point;
+
+	class->selection_event = e_reflow_selection_event_real;
+	class->column_width_changed = NULL;
+
+	g_object_class_install_property (
+		object_class,
+		PROP_MINIMUM_WIDTH,
+		g_param_spec_double (
+			"minimum_width",
+			"Minimum width",
+			"Minimum Width",
+			0.0, G_MAXDOUBLE, 0.0,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_WIDTH,
+		g_param_spec_double (
+			"width",
+			"Width",
+			"Width",
+			0.0, G_MAXDOUBLE, 0.0,
+			G_PARAM_READABLE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_HEIGHT,
+		g_param_spec_double (
+			"height",
+			"Height",
+			"Height",
+			0.0, G_MAXDOUBLE, 0.0,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_EMPTY_MESSAGE,
+		g_param_spec_string (
+			"empty_message",
+			"Empty message",
+			"Empty message",
+			NULL,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_MODEL,
+		g_param_spec_object (
+			"model",
+			"Reflow model",
+			"Reflow model",
+			E_TYPE_REFLOW_MODEL,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_COLUMN_WIDTH,
+		g_param_spec_double (
+			"column_width",
+			"Column width",
+			"Column width",
+			0.0, G_MAXDOUBLE, 150.0,
+			G_PARAM_READWRITE));
+
+	signals[SELECTION_EVENT] = g_signal_new (
+		"selection_event",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (EReflowClass, selection_event),
+		NULL, NULL,
+		e_marshal_INT__OBJECT_BOXED,
+		G_TYPE_INT, 2,
+		G_TYPE_OBJECT,
+		GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
+
+	signals[COLUMN_WIDTH_CHANGED] = g_signal_new (
+		"column_width_changed",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (EReflowClass, column_width_changed),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__DOUBLE,
+		G_TYPE_NONE, 1,
+		G_TYPE_DOUBLE);
+}
+
+static void
+e_reflow_init (EReflow *reflow)
+{
+	reflow->model                     = NULL;
+	reflow->items                     = NULL;
+	reflow->heights                   = NULL;
+	reflow->count                     = 0;
+
+	reflow->columns                   = NULL;
+	reflow->column_count              = 0;
+
+	reflow->empty_text                = NULL;
+	reflow->empty_message             = NULL;
+
+	reflow->minimum_width             = 10;
+	reflow->width                     = 10;
+	reflow->height                    = 10;
+
+	reflow->column_width              = 150;
+
+	reflow->column_drag               = FALSE;
+
+	reflow->need_height_update        = FALSE;
+	reflow->need_column_resize        = FALSE;
+	reflow->need_reflow_columns       = FALSE;
+
+	reflow->maybe_did_something       = FALSE;
+	reflow->maybe_in_drag             = FALSE;
+
+	reflow->default_cursor_shown      = TRUE;
+	reflow->arrow_cursor              = NULL;
+	reflow->default_cursor            = NULL;
+
+	reflow->cursor_row                = -1;
+
+	reflow->incarnate_idle_id         = 0;
+	reflow->do_adjustment_idle_id     = 0;
+	reflow->set_scroll_adjustments_id = 0;
+
+	reflow->selection                 = E_SELECTION_MODEL (e_selection_model_simple_new ());
+	reflow->sorter                    = e_sorter_array_new (er_create_cmp_cache, er_compare, reflow);
+
+	g_object_set (
+		reflow->selection,
+		"sorter", reflow->sorter,
+		NULL);
+
+	reflow->selection_changed_id = g_signal_connect (
+		reflow->selection, "selection_changed",
+		G_CALLBACK (selection_changed), reflow);
+
+	reflow->selection_row_changed_id = g_signal_connect (
+		reflow->selection, "selection_row_changed",
+		G_CALLBACK (selection_row_changed), reflow);
+
+	reflow->cursor_changed_id = g_signal_connect (
+		reflow->selection, "cursor_changed",
+		G_CALLBACK (cursor_changed), reflow);
+
+	e_canvas_item_set_reflow_callback (GNOME_CANVAS_ITEM (reflow), e_reflow_reflow);
+}
diff --git a/e-util/e-reflow.h b/e-util/e-reflow.h
new file mode 100644
index 0000000..a891e98
--- /dev/null
+++ b/e-util/e-reflow.h
@@ -0,0 +1,145 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_REFLOW_H
+#define E_REFLOW_H
+
+#include <libgnomecanvas/libgnomecanvas.h>
+
+#include <e-util/e-reflow-model.h>
+#include <e-util/e-selection-model.h>
+#include <e-util/e-sorter-array.h>
+
+/* Standard GObject macros */
+#define E_TYPE_REFLOW \
+	(e_reflow_get_type ())
+#define E_REFLOW(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_REFLOW, EReflow))
+#define E_REFLOW_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_REFLOW, EReflowClass))
+#define E_IS_REFLOW(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_REFLOW))
+#define E_IS_REFLOW_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_REFLOW))
+#define E_REFLOW_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_REFLOW, EReflowClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EReflow EReflow;
+typedef struct _EReflowClass EReflowClass;
+typedef struct _EReflowPrivate EReflowPrivate;
+
+struct _EReflow {
+	GnomeCanvasGroup parent;
+
+	/* item specific fields */
+	EReflowModel *model;
+	guint model_changed_id;
+	guint comparison_changed_id;
+	guint model_items_inserted_id;
+	guint model_item_removed_id;
+	guint model_item_changed_id;
+
+	ESelectionModel *selection;
+	guint selection_changed_id;
+	guint selection_row_changed_id;
+	guint cursor_changed_id;
+	ESorterArray *sorter;
+
+	GtkAdjustment *adjustment;
+	guint adjustment_changed_id;
+	guint adjustment_value_changed_id;
+	guint set_scroll_adjustments_id;
+
+	gint *heights;
+	GnomeCanvasItem **items;
+	gint count;
+	gint allocated_count;
+
+	gint *columns;
+	gint column_count; /* Number of columnns */
+
+	GnomeCanvasItem *empty_text;
+	gchar *empty_message;
+
+	gdouble minimum_width;
+	gdouble width;
+	gdouble height;
+
+	gdouble column_width;
+
+	gint incarnate_idle_id;
+	gint do_adjustment_idle_id;
+
+	/* These are all for when the column is being dragged. */
+	gdouble start_x;
+	gint which_column_dragged;
+	gdouble temp_column_width;
+	gdouble previous_temp_column_width;
+
+	gint cursor_row;
+
+	gint reflow_from_column;
+
+	guint column_drag : 1;
+
+	guint need_height_update : 1;
+	guint need_column_resize : 1;
+	guint need_reflow_columns : 1;
+
+	guint default_cursor_shown : 1;
+
+	guint maybe_did_something : 1;
+	guint maybe_in_drag : 1;
+	GdkCursor *arrow_cursor;
+	GdkCursor *default_cursor;
+};
+
+struct _EReflowClass
+{
+	GnomeCanvasGroupClass parent_class;
+
+	gint (*selection_event) (EReflow *reflow, GnomeCanvasItem *item, GdkEvent *event);
+	void (*column_width_changed) (EReflow *reflow, gdouble width);
+};
+
+/*
+ * To be added to a reflow, an item must have the argument "width" as
+ * a Read/Write argument and "height" as a Read Only argument.  It
+ * should also do an ECanvas parent reflow request if its size
+ * changes.
+ */
+GType    e_reflow_get_type       (void);
+
+G_END_DECLS
+
+#endif /* E_REFLOW_H */
diff --git a/e-util/e-rule-context.c b/e-util/e-rule-context.c
new file mode 100644
index 0000000..dc7ce81
--- /dev/null
+++ b/e-util/e-rule-context.c
@@ -0,0 +1,1026 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Not Zed <notzed lostzed mmc com au>
+ *      Jeffrey Stedfast <fejj ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#include <glib/gstdio.h>
+
+#include <gtk/gtk.h>
+
+#include <glib/gi18n.h>
+
+#include <libedataserver/libedataserver.h>
+
+#include "e-alert-dialog.h"
+#include "e-filter-code.h"
+#include "e-filter-color.h"
+#include "e-filter-datespec.h"
+#include "e-filter-file.h"
+#include "e-filter-input.h"
+#include "e-filter-int.h"
+#include "e-filter-option.h"
+#include "e-filter-rule.h"
+#include "e-rule-context.h"
+#include "e-xml-utils.h"
+
+#define E_RULE_CONTEXT_GET_PRIVATE(obj) \
+	(G_TYPE_INSTANCE_GET_PRIVATE \
+	((obj), E_TYPE_RULE_CONTEXT, ERuleContextPrivate))
+
+struct _ERuleContextPrivate {
+	gint frozen;
+};
+
+enum {
+	RULE_ADDED,
+	RULE_REMOVED,
+	CHANGED,
+	LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+struct _revert_data {
+	GHashTable *rules;
+	gint rank;
+};
+
+G_DEFINE_TYPE (
+	ERuleContext,
+	e_rule_context,
+	G_TYPE_OBJECT)
+
+static void
+rule_context_set_error (ERuleContext *context,
+                        gchar *error)
+{
+	g_free (context->error);
+	context->error = error;
+}
+
+static void
+new_rule_response (GtkWidget *dialog,
+                   gint button,
+                   ERuleContext *context)
+{
+	if (button == GTK_RESPONSE_OK) {
+		EFilterRule *rule = g_object_get_data ((GObject *) dialog, "rule");
+		gchar *user = g_object_get_data ((GObject *) dialog, "path");
+		EAlert *alert = NULL;
+
+		if (!e_filter_rule_validate (rule, &alert)) {
+			e_alert_run_dialog (GTK_WINDOW (dialog), alert);
+			g_object_unref (alert);
+			return;
+		}
+
+		if (e_rule_context_find_rule (context, rule->name, rule->source)) {
+			e_alert_run_dialog_for_args ((GtkWindow *) dialog,
+						     "filter:bad-name-notunique",
+						     rule->name, NULL);
+
+			return;
+		}
+
+		g_object_ref (rule);
+		e_rule_context_add_rule (context, rule);
+		if (user)
+			e_rule_context_save (context, user);
+	}
+
+	gtk_widget_destroy (dialog);
+}
+
+static void
+revert_rule_remove (gpointer key,
+                    EFilterRule *rule,
+                    ERuleContext *context)
+{
+	e_rule_context_remove_rule (context, rule);
+	g_object_unref (rule);
+}
+
+static void
+revert_source_remove (gpointer key,
+                      struct _revert_data *rest_data,
+                      ERuleContext *context)
+{
+	g_hash_table_foreach (
+		rest_data->rules, (GHFunc) revert_rule_remove, context);
+	g_hash_table_destroy (rest_data->rules);
+	g_free (rest_data);
+}
+
+static guint
+source_hashf (const gchar *a)
+{
+	return (a != NULL) ? g_str_hash (a) : 0;
+}
+
+static gint
+source_eqf (const gchar *a,
+            const gchar *b)
+{
+	return (g_strcmp0 (a, b) == 0);
+}
+
+static void
+free_part_set (struct _part_set_map *map)
+{
+	g_free (map->name);
+	g_free (map);
+}
+
+static void
+free_rule_set (struct _rule_set_map *map)
+{
+	g_free (map->name);
+	g_free (map);
+}
+
+static void
+rule_context_finalize (GObject *obj)
+{
+	ERuleContext *context =(ERuleContext *) obj;
+
+	g_list_foreach (context->rule_set_list, (GFunc) free_rule_set, NULL);
+	g_list_free (context->rule_set_list);
+	g_hash_table_destroy (context->rule_set_map);
+
+	g_list_foreach (context->part_set_list, (GFunc) free_part_set, NULL);
+	g_list_free (context->part_set_list);
+	g_hash_table_destroy (context->part_set_map);
+
+	g_free (context->error);
+
+	g_list_foreach (context->parts, (GFunc) g_object_unref, NULL);
+	g_list_free (context->parts);
+
+	g_list_foreach (context->rules, (GFunc) g_object_unref, NULL);
+	g_list_free (context->rules);
+
+	G_OBJECT_CLASS (e_rule_context_parent_class)->finalize (obj);
+}
+
+static gint
+rule_context_load (ERuleContext *context,
+                   const gchar *system,
+                   const gchar *user)
+{
+	xmlNodePtr set, rule, root;
+	xmlDocPtr systemdoc, userdoc;
+	struct _part_set_map *part_map;
+	struct _rule_set_map *rule_map;
+
+	rule_context_set_error (context, NULL);
+
+	systemdoc = e_xml_parse_file (system);
+	if (systemdoc == NULL) {
+		gchar * err_msg;
+
+		err_msg = g_strdup_printf (
+			"Unable to load system rules '%s': %s",
+			system, g_strerror (errno));
+		g_warning ("%s: %s", G_STRFUNC, err_msg);
+		rule_context_set_error (context, err_msg);
+		/* no need to free err_msg here */
+		return -1;
+	}
+
+	root = xmlDocGetRootElement (systemdoc);
+	if (root == NULL || strcmp ((gchar *) root->name, "filterdescription")) {
+		gchar * err_msg;
+
+		err_msg = g_strdup_printf (
+			"Unable to load system rules '%s': "
+			"Invalid format", system);
+		g_warning ("%s: %s", G_STRFUNC, err_msg);
+		rule_context_set_error (context, err_msg);
+		/* no need to free err_msg here */
+		xmlFreeDoc (systemdoc);
+		return -1;
+	}
+	/* doesn't matter if this doens't exist */
+	userdoc = NULL;
+	if (g_file_test (user, G_FILE_TEST_IS_REGULAR))
+		userdoc = e_xml_parse_file (user);
+
+	/* now parse structure */
+	/* get rule parts */
+	set = root->children;
+	while (set) {
+		part_map = g_hash_table_lookup (context->part_set_map, set->name);
+		if (part_map) {
+			rule = set->children;
+			while (rule) {
+				if (!strcmp ((gchar *) rule->name, "part")) {
+					EFilterPart *part =
+						E_FILTER_PART (g_object_new (
+						part_map->type, NULL, NULL));
+
+					if (e_filter_part_xml_create (part, rule, context) == 0) {
+						part_map->append (context, part);
+					} else {
+						g_object_unref (part);
+						g_warning ("Cannot load filter part");
+					}
+				}
+				rule = rule->next;
+			}
+		} else if ((rule_map = g_hash_table_lookup (
+				context->rule_set_map, set->name))) {
+			rule = set->children;
+			while (rule) {
+				if (!strcmp ((gchar *) rule->name, "rule")) {
+					EFilterRule *part =
+						E_FILTER_RULE (g_object_new (
+						rule_map->type, NULL, NULL));
+
+					if (e_filter_rule_xml_decode (part, rule, context) == 0) {
+						part->system = TRUE;
+						rule_map->append (context, part);
+					} else {
+						g_object_unref (part);
+						g_warning ("Cannot load filter part");
+					}
+				}
+				rule = rule->next;
+			}
+		}
+		set = set->next;
+	}
+
+	/* now load actual rules */
+	if (userdoc) {
+		root = xmlDocGetRootElement (userdoc);
+		set = root ? root->children : NULL;
+		while (set) {
+			rule_map = g_hash_table_lookup (context->rule_set_map, set->name);
+			if (rule_map) {
+				rule = set->children;
+				while (rule) {
+					if (!strcmp ((gchar *) rule->name, "rule")) {
+						EFilterRule *part =
+							E_FILTER_RULE (g_object_new (
+							rule_map->type, NULL, NULL));
+
+						if (e_filter_rule_xml_decode (part, rule, context) == 0) {
+							rule_map->append (context, part);
+						} else {
+							g_object_unref (part);
+							g_warning ("Cannot load filter part");
+						}
+					}
+					rule = rule->next;
+				}
+			}
+			set = set->next;
+		}
+	}
+
+	xmlFreeDoc (userdoc);
+	xmlFreeDoc (systemdoc);
+
+	return 0;
+}
+
+static gint
+rule_context_save (ERuleContext *context,
+                   const gchar *user)
+{
+	xmlDocPtr doc;
+	xmlNodePtr root, rules, work;
+	GList *l;
+	EFilterRule *rule;
+	struct _rule_set_map *map;
+	gint ret;
+
+	doc = xmlNewDoc ((xmlChar *)"1.0");
+	/* FIXME: set character encoding to UTF-8? */
+	root = xmlNewDocNode (doc, NULL, (xmlChar *)"filteroptions", NULL);
+	xmlDocSetRootElement (doc, root);
+	l = context->rule_set_list;
+	while (l) {
+		map = l->data;
+		rules = xmlNewDocNode (doc, NULL, (xmlChar *) map->name, NULL);
+		xmlAddChild (root, rules);
+		rule = NULL;
+		while ((rule = map->next (context, rule, NULL))) {
+			if (!rule->system) {
+				work = e_filter_rule_xml_encode (rule);
+				xmlAddChild (rules, work);
+			}
+		}
+		l = g_list_next (l);
+	}
+
+	ret = e_xml_save_file (user, doc);
+
+	xmlFreeDoc (doc);
+
+	return ret;
+}
+
+static gint
+rule_context_revert (ERuleContext *context,
+                     const gchar *user)
+{
+	xmlNodePtr set, rule;
+	/*struct _part_set_map *part_map;*/
+	struct _rule_set_map *rule_map;
+	struct _revert_data *rest_data;
+	GHashTable *source_hash;
+	xmlDocPtr userdoc;
+	EFilterRule *frule;
+
+	rule_context_set_error (context, NULL);
+
+	userdoc = e_xml_parse_file (user);
+	if (userdoc == NULL)
+		/* clear out anythign we have? */
+		return 0;
+
+	source_hash = g_hash_table_new (
+		(GHashFunc) source_hashf,
+		(GCompareFunc) source_eqf);
+
+	/* setup stuff we have now */
+	/* Note that we assume there is only 1 set of rules in a given rule context,
+	 * although other parts of the code dont assume this */
+	frule = NULL;
+	while ((frule = e_rule_context_next_rule (context, frule, NULL))) {
+		rest_data = g_hash_table_lookup (source_hash, frule->source);
+		if (rest_data == NULL) {
+			rest_data = g_malloc0 (sizeof (*rest_data));
+			rest_data->rules = g_hash_table_new (g_str_hash, g_str_equal);
+			g_hash_table_insert (source_hash, frule->source, rest_data);
+		}
+		g_hash_table_insert (rest_data->rules, frule->name, frule);
+	}
+
+	/* make what we have, match what we load */
+	set = xmlDocGetRootElement (userdoc);
+	set = set ? set->children : NULL;
+	while (set) {
+		rule_map = g_hash_table_lookup (context->rule_set_map, set->name);
+		if (rule_map) {
+			rule = set->children;
+			while (rule) {
+				if (!strcmp ((gchar *) rule->name, "rule")) {
+					EFilterRule *part =
+						E_FILTER_RULE (g_object_new (
+						rule_map->type, NULL, NULL));
+
+					if (e_filter_rule_xml_decode (part, rule, context) == 0) {
+						/* Use the revert data to keep
+						 * track of the right rank of
+						 * this rule part. */
+						rest_data = g_hash_table_lookup (source_hash, part->source);
+						if (rest_data == NULL) {
+							rest_data = g_malloc0 (sizeof (*rest_data));
+							rest_data->rules = g_hash_table_new (
+								g_str_hash,
+								g_str_equal);
+							g_hash_table_insert (
+								source_hash,
+								part->source,
+								rest_data);
+						}
+						frule = g_hash_table_lookup (
+							rest_data->rules,
+							part->name);
+						if (frule) {
+							if (context->priv->frozen == 0 &&
+							    !e_filter_rule_eq (frule, part))
+								e_filter_rule_copy (frule, part);
+
+							g_object_unref (part);
+							e_rule_context_rank_rule (
+								context, frule,
+								frule->source,
+								rest_data->rank);
+							g_hash_table_remove (rest_data->rules, frule->name);
+						} else {
+							e_rule_context_add_rule (context, part);
+							e_rule_context_rank_rule (
+								context,
+								part,
+								part->source,
+								rest_data->rank);
+						}
+						rest_data->rank++;
+					} else {
+						g_object_unref (part);
+						g_warning ("Cannot load filter part");
+					}
+				}
+				rule = rule->next;
+			}
+		}
+		set = set->next;
+	}
+
+	xmlFreeDoc (userdoc);
+
+	/* remove any we still have that weren't in the file */
+	g_hash_table_foreach (source_hash, (GHFunc) revert_source_remove, context);
+	g_hash_table_destroy (source_hash);
+
+	return 0;
+}
+
+static EFilterElement *
+rule_context_new_element (ERuleContext *context,
+                          const gchar *type)
+{
+	if (!strcmp (type, "string")) {
+		return (EFilterElement *) e_filter_input_new ();
+	} else if (!strcmp (type, "address")) {
+		/* FIXME: temporary ... need real address type */
+		return (EFilterElement *) e_filter_input_new_type_name (type);
+	} else if (!strcmp (type, "code")) {
+		return (EFilterElement *) e_filter_code_new (FALSE);
+	} else if (!strcmp (type, "rawcode")) {
+		return (EFilterElement *) e_filter_code_new (TRUE);
+	} else if (!strcmp (type, "colour")) {
+		return (EFilterElement *) e_filter_color_new ();
+	} else if (!strcmp (type, "optionlist")) {
+		return (EFilterElement *) e_filter_option_new ();
+	} else if (!strcmp (type, "datespec")) {
+		return (EFilterElement *) e_filter_datespec_new ();
+	} else if (!strcmp (type, "command")) {
+		return (EFilterElement *) e_filter_file_new_type_name (type);
+	} else if (!strcmp (type, "file")) {
+		return (EFilterElement *) e_filter_file_new_type_name (type);
+	} else if (!strcmp (type, "integer")) {
+		return (EFilterElement *) e_filter_int_new ();
+	} else if (!strcmp (type, "regex")) {
+		return (EFilterElement *) e_filter_input_new_type_name (type);
+	} else if (!strcmp (type, "completedpercent")) {
+		return (EFilterElement *) e_filter_int_new_type (
+			"completedpercent", 0,100);
+	} else {
+		g_warning ("Unknown filter type '%s'", type);
+		return NULL;
+	}
+}
+
+static void
+e_rule_context_class_init (ERuleContextClass *class)
+{
+	GObjectClass *object_class;
+
+	g_type_class_add_private (class, sizeof (ERuleContextPrivate));
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->finalize = rule_context_finalize;
+
+	class->load = rule_context_load;
+	class->save = rule_context_save;
+	class->revert = rule_context_revert;
+	class->new_element = rule_context_new_element;
+
+	signals[RULE_ADDED] = g_signal_new (
+		"rule-added",
+		E_TYPE_RULE_CONTEXT,
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ERuleContextClass, rule_added),
+		NULL,
+		NULL,
+		g_cclosure_marshal_VOID__POINTER,
+		G_TYPE_NONE, 1,
+		G_TYPE_POINTER);
+
+	signals[RULE_REMOVED] = g_signal_new (
+		"rule-removed",
+		E_TYPE_RULE_CONTEXT,
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ERuleContextClass, rule_removed),
+		NULL,
+		NULL,
+		g_cclosure_marshal_VOID__POINTER,
+		G_TYPE_NONE, 1,
+		G_TYPE_POINTER);
+
+	signals[CHANGED] = g_signal_new (
+		"changed",
+		E_TYPE_RULE_CONTEXT,
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ERuleContextClass, changed),
+		NULL,
+		NULL,
+		g_cclosure_marshal_VOID__VOID,
+		G_TYPE_NONE, 0);
+}
+
+static void
+e_rule_context_init (ERuleContext *context)
+{
+	context->priv = E_RULE_CONTEXT_GET_PRIVATE (context);
+
+	context->part_set_map = g_hash_table_new (g_str_hash, g_str_equal);
+	context->rule_set_map = g_hash_table_new (g_str_hash, g_str_equal);
+
+	context->flags = E_RULE_CONTEXT_GROUPING;
+}
+
+/**
+ * e_rule_context_new:
+ *
+ * Create a new ERuleContext object.
+ *
+ * Return value: A new #ERuleContext object.
+ **/
+ERuleContext *
+e_rule_context_new (void)
+{
+	return g_object_new (E_TYPE_RULE_CONTEXT, NULL);
+}
+
+void
+e_rule_context_add_part_set (ERuleContext *context,
+                             const gchar *setname,
+                             GType part_type,
+                             ERuleContextPartFunc append,
+                             ERuleContextNextPartFunc next)
+{
+	struct _part_set_map *map;
+
+	g_return_if_fail (E_IS_RULE_CONTEXT (context));
+	g_return_if_fail (setname != NULL);
+	g_return_if_fail (append != NULL);
+	g_return_if_fail (next != NULL);
+
+	map = g_hash_table_lookup (context->part_set_map, setname);
+	if (map != NULL) {
+		g_hash_table_remove (context->part_set_map, setname);
+		context->part_set_list = g_list_remove (context->part_set_list, map);
+		free_part_set (map);
+		map = NULL;
+	}
+
+	map = g_malloc0 (sizeof (*map));
+	map->type = part_type;
+	map->append = append;
+	map->next = next;
+	map->name = g_strdup (setname);
+	g_hash_table_insert (context->part_set_map, map->name, map);
+	context->part_set_list = g_list_append (context->part_set_list, map);
+}
+
+void
+e_rule_context_add_rule_set (ERuleContext *context,
+                             const gchar *setname,
+                             GType rule_type,
+                             ERuleContextRuleFunc append,
+                             ERuleContextNextRuleFunc next)
+{
+	struct _rule_set_map *map;
+
+	g_return_if_fail (E_IS_RULE_CONTEXT (context));
+	g_return_if_fail (setname != NULL);
+	g_return_if_fail (append != NULL);
+	g_return_if_fail (next != NULL);
+
+	map = g_hash_table_lookup (context->rule_set_map, setname);
+	if (map != NULL) {
+		g_hash_table_remove (context->rule_set_map, setname);
+		context->rule_set_list = g_list_remove (context->rule_set_list, map);
+		free_rule_set (map);
+		map = NULL;
+	}
+
+	map = g_malloc0 (sizeof (*map));
+	map->type = rule_type;
+	map->append = append;
+	map->next = next;
+	map->name = g_strdup (setname);
+	g_hash_table_insert (context->rule_set_map, map->name, map);
+	context->rule_set_list = g_list_append (context->rule_set_list, map);
+}
+
+/**
+ * e_rule_context_load:
+ * @f:
+ * @system:
+ * @user:
+ *
+ * Load a rule context from a system and user description file.
+ *
+ * Return value:
+ **/
+gint
+e_rule_context_load (ERuleContext *context,
+                     const gchar *system,
+                     const gchar *user)
+{
+	ERuleContextClass *class;
+	gint result;
+
+	g_return_val_if_fail (E_IS_RULE_CONTEXT (context), -1);
+	g_return_val_if_fail (system != NULL, -1);
+	g_return_val_if_fail (user != NULL, -1);
+
+	class = E_RULE_CONTEXT_GET_CLASS (context);
+	g_return_val_if_fail (class->load != NULL, -1);
+
+	context->priv->frozen++;
+	result = class->load (context, system, user);
+	context->priv->frozen--;
+
+	return result;
+}
+
+/**
+ * e_rule_context_save:
+ * @f:
+ * @user:
+ *
+ * Save a rule context to disk.
+ *
+ * Return value:
+ **/
+gint
+e_rule_context_save (ERuleContext *context,
+                     const gchar *user)
+{
+	ERuleContextClass *class;
+
+	g_return_val_if_fail (E_IS_RULE_CONTEXT (context), -1);
+	g_return_val_if_fail (user != NULL, -1);
+
+	class = E_RULE_CONTEXT_GET_CLASS (context);
+	g_return_val_if_fail (class->save != NULL, -1);
+
+	return class->save (context, user);
+}
+
+/**
+ * e_rule_context_revert:
+ * @f:
+ * @user:
+ *
+ * Reverts a rule context from a user description file.  Assumes the
+ * system description file is unchanged from when it was loaded.
+ *
+ * Return value:
+ **/
+gint
+e_rule_context_revert (ERuleContext *context,
+                       const gchar *user)
+{
+	ERuleContextClass *class;
+
+	g_return_val_if_fail (E_RULE_CONTEXT (context), 0);
+	g_return_val_if_fail (user != NULL, 0);
+
+	class = E_RULE_CONTEXT_GET_CLASS (context);
+	g_return_val_if_fail (class->revert != NULL, 0);
+
+	return class->revert (context, user);
+}
+
+EFilterPart *
+e_rule_context_find_part (ERuleContext *context,
+                          const gchar *name)
+{
+	g_return_val_if_fail (E_IS_RULE_CONTEXT (context), NULL);
+	g_return_val_if_fail (name != NULL, NULL);
+
+	return e_filter_part_find_list (context->parts, name);
+}
+
+EFilterPart *
+e_rule_context_create_part (ERuleContext *context,
+                            const gchar *name)
+{
+	EFilterPart *part;
+
+	g_return_val_if_fail (E_IS_RULE_CONTEXT (context), NULL);
+	g_return_val_if_fail (name != NULL, NULL);
+
+	part = e_rule_context_find_part (context, name);
+
+	if (part == NULL)
+		return NULL;
+
+	return e_filter_part_clone (part);
+}
+
+EFilterPart *
+e_rule_context_next_part (ERuleContext *context,
+                          EFilterPart *last)
+{
+	g_return_val_if_fail (E_IS_RULE_CONTEXT (context), NULL);
+
+	return e_filter_part_next_list (context->parts, last);
+}
+
+EFilterRule *
+e_rule_context_next_rule (ERuleContext *context,
+                          EFilterRule *last,
+                          const gchar *source)
+{
+	g_return_val_if_fail (E_IS_RULE_CONTEXT (context), NULL);
+
+	return e_filter_rule_next_list (context->rules, last, source);
+}
+
+EFilterRule *
+e_rule_context_find_rule (ERuleContext *context,
+                          const gchar *name,
+                          const gchar *source)
+{
+	g_return_val_if_fail (E_IS_RULE_CONTEXT (context), NULL);
+	g_return_val_if_fail (name != NULL, NULL);
+
+	return e_filter_rule_find_list (context->rules, name, source);
+}
+
+void
+e_rule_context_add_part (ERuleContext *context,
+                         EFilterPart *part)
+{
+	g_return_if_fail (E_IS_RULE_CONTEXT (context));
+	g_return_if_fail (E_IS_FILTER_PART (part));
+
+	context->parts = g_list_append (context->parts, part);
+}
+
+void
+e_rule_context_add_rule (ERuleContext *context,
+                         EFilterRule *rule)
+{
+	g_return_if_fail (E_IS_RULE_CONTEXT (context));
+	g_return_if_fail (E_IS_FILTER_RULE (rule));
+
+	context->rules = g_list_append (context->rules, rule);
+
+	if (context->priv->frozen == 0) {
+		g_signal_emit (context, signals[RULE_ADDED], 0, rule);
+		g_signal_emit (context, signals[CHANGED], 0);
+	}
+}
+
+/* Add a rule, with a gui, asking for confirmation first,
+ * and optionally save to path. */
+void
+e_rule_context_add_rule_gui (ERuleContext *context,
+                             EFilterRule *rule,
+                             const gchar *title,
+                             const gchar *path)
+{
+	GtkDialog *dialog;
+	GtkWidget *widget;
+	GtkWidget *content_area;
+
+	g_return_if_fail (E_IS_RULE_CONTEXT (context));
+	g_return_if_fail (E_IS_FILTER_RULE (rule));
+
+	widget = e_filter_rule_get_widget (rule, context);
+	gtk_widget_show (widget);
+
+	dialog =(GtkDialog *) gtk_dialog_new ();
+	gtk_dialog_add_buttons (
+		dialog,
+		GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+		GTK_STOCK_OK, GTK_RESPONSE_OK,
+		NULL);
+
+	gtk_window_set_title ((GtkWindow *) dialog, title);
+	gtk_window_set_default_size ((GtkWindow *) dialog, 600, 400);
+	gtk_window_set_resizable ((GtkWindow *) dialog, TRUE);
+
+	content_area = gtk_dialog_get_content_area (dialog);
+	gtk_box_pack_start (GTK_BOX (content_area), widget, TRUE, TRUE, 0);
+
+	g_object_set_data_full ((GObject *) dialog, "rule", rule, g_object_unref);
+	if (path)
+		g_object_set_data_full ((GObject *) dialog, "path", g_strdup (path), g_free);
+
+	g_signal_connect (
+		dialog, "response",
+		G_CALLBACK (new_rule_response), context);
+
+	g_object_ref (context);
+
+	g_object_set_data_full ((GObject *) dialog, "context", context, g_object_unref);
+
+	gtk_widget_show ((GtkWidget *) dialog);
+}
+
+void
+e_rule_context_remove_rule (ERuleContext *context,
+                            EFilterRule *rule)
+{
+	g_return_if_fail (E_IS_RULE_CONTEXT (context));
+	g_return_if_fail (E_IS_FILTER_RULE (rule));
+
+	context->rules = g_list_remove (context->rules, rule);
+
+	if (context->priv->frozen == 0) {
+		g_signal_emit (context, signals[RULE_REMOVED], 0, rule);
+		g_signal_emit (context, signals[CHANGED], 0);
+	}
+}
+
+void
+e_rule_context_rank_rule (ERuleContext *context,
+                          EFilterRule *rule,
+                          const gchar *source,
+                          gint rank)
+{
+	GList *node;
+	gint i = 0, index = 0;
+
+	g_return_if_fail (E_IS_RULE_CONTEXT (context));
+	g_return_if_fail (E_IS_FILTER_RULE (rule));
+
+	if (e_rule_context_get_rank_rule (context, rule, source) == rank)
+		return;
+
+	context->rules = g_list_remove (context->rules, rule);
+	node = context->rules;
+	while (node) {
+		EFilterRule *r = node->data;
+
+		if (i == rank) {
+			context->rules = g_list_insert (context->rules, rule, index);
+			if (context->priv->frozen == 0)
+				g_signal_emit (context, signals[CHANGED], 0);
+
+			return;
+		}
+
+		index++;
+		if (source == NULL || (r->source && strcmp (r->source, source) == 0))
+			i++;
+
+		node = node->next;
+	}
+
+	context->rules = g_list_append (context->rules, rule);
+	if (context->priv->frozen == 0)
+		g_signal_emit (context, signals[CHANGED], 0);
+}
+
+gint
+e_rule_context_get_rank_rule (ERuleContext *context,
+                              EFilterRule *rule,
+                              const gchar *source)
+{
+	GList *node;
+	gint i = 0;
+
+	g_return_val_if_fail (E_IS_RULE_CONTEXT (context), -1);
+	g_return_val_if_fail (E_IS_FILTER_RULE (rule), -1);
+
+	node = context->rules;
+	while (node) {
+		EFilterRule *r = node->data;
+
+		if (r == rule)
+			return i;
+
+		if (source == NULL || (r->source && strcmp (r->source, source) == 0))
+			i++;
+
+		node = node->next;
+	}
+
+	return -1;
+}
+
+EFilterRule *
+e_rule_context_find_rank_rule (ERuleContext *context,
+                               gint rank,
+                               const gchar *source)
+{
+	GList *node;
+	gint i = 0;
+
+	g_return_val_if_fail (E_IS_RULE_CONTEXT (context), NULL);
+
+	node = context->rules;
+	while (node) {
+		EFilterRule *r = node->data;
+
+		if (source == NULL || (r->source && strcmp (r->source, source) == 0)) {
+			if (rank == i)
+				return r;
+			i++;
+		}
+
+		node = node->next;
+	}
+
+	return NULL;
+}
+
+GList *
+e_rule_context_rename_uri (ERuleContext *context,
+                           const gchar *old_uri,
+                           const gchar *new_uri,
+                           GCompareFunc compare)
+{
+	ERuleContextClass *class;
+
+	g_return_val_if_fail (E_IS_RULE_CONTEXT (context), NULL);
+	g_return_val_if_fail (old_uri != NULL, NULL);
+	g_return_val_if_fail (new_uri != NULL, NULL);
+	g_return_val_if_fail (compare != NULL, NULL);
+
+	class = E_RULE_CONTEXT_GET_CLASS (context);
+
+	/* This method is optional. */
+	if (class->rename_uri == NULL)
+		return NULL;
+
+	return class->rename_uri (context, old_uri, new_uri, compare);
+}
+
+GList *
+e_rule_context_delete_uri (ERuleContext *context,
+                           const gchar *uri,
+                           GCompareFunc compare)
+{
+	ERuleContextClass *class;
+
+	g_return_val_if_fail (E_IS_RULE_CONTEXT (context), NULL);
+	g_return_val_if_fail (uri != NULL, NULL);
+	g_return_val_if_fail (compare != NULL, NULL);
+
+	class = E_RULE_CONTEXT_GET_CLASS (context);
+
+	/* This method is optional. */
+	if (class->delete_uri == NULL)
+		return NULL;
+
+	return class->delete_uri (context, uri, compare);
+}
+
+void
+e_rule_context_free_uri_list (ERuleContext *context,
+                              GList *uris)
+{
+	g_return_if_fail (E_IS_RULE_CONTEXT (context));
+
+	/* TODO: should be virtual */
+
+	g_list_foreach (uris, (GFunc) g_free, NULL);
+	g_list_free (uris);
+}
+
+/**
+ * e_rule_context_new_element:
+ * @context:
+ * @name:
+ *
+ * create a new filter element based on name.
+ *
+ * Return value:
+ **/
+EFilterElement *
+e_rule_context_new_element (ERuleContext *context,
+                            const gchar *name)
+{
+	ERuleContextClass *class;
+
+	g_return_val_if_fail (E_IS_RULE_CONTEXT (context), NULL);
+	g_return_val_if_fail (name != NULL, NULL);
+
+	class = E_RULE_CONTEXT_GET_CLASS (context);
+	g_return_val_if_fail (class->new_element != NULL, NULL);
+
+	return class->new_element (context, name);
+}
diff --git a/e-util/e-rule-context.h b/e-util/e-rule-context.h
new file mode 100644
index 0000000..f543edd
--- /dev/null
+++ b/e-util/e-rule-context.h
@@ -0,0 +1,218 @@
+/*
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Not Zed <notzed lostzed mmc com au>
+ *      Jeffrey Stedfast <fejj ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_RULE_CONTEXT_H
+#define E_RULE_CONTEXT_H
+
+#include <libxml/parser.h>
+
+#include <e-util/e-filter-part.h>
+#include <e-util/e-filter-rule.h>
+
+/* Standard GObject macros */
+#define E_TYPE_RULE_CONTEXT \
+	(e_rule_context_get_type ())
+#define E_RULE_CONTEXT(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_RULE_CONTEXT, ERuleContext))
+#define E_RULE_CONTEXT_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_RULE_CONTEXT, ERuleContextClass))
+#define E_IS_RULE_CONTEXT(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_RULE_CONTEXT))
+#define E_IS_RULE_CONTEXT_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_RULE_CONTEXT))
+#define E_RULE_CONTEXT_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_RULE_CONTEXT, ERuleContextClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ERuleContext ERuleContext;
+typedef struct _ERuleContextClass ERuleContextClass;
+typedef struct _ERuleContextPrivate ERuleContextPrivate;
+
+/* backend capabilities, this is a hack since we don't support nested rules */
+enum {
+	E_RULE_CONTEXT_GROUPING  = 1 << 0,
+	E_RULE_CONTEXT_THREADING = 1 << 1
+};
+
+typedef void	(*ERuleContextRegisterFunc)	(ERuleContext *context,
+						 EFilterRule *rule,
+						 gpointer user_data);
+typedef void	(*ERuleContextPartFunc)		(ERuleContext *context,
+						 EFilterPart *part);
+typedef void	(*ERuleContextRuleFunc)		(ERuleContext *context,
+						 EFilterRule *part);
+typedef EFilterPart *
+		(*ERuleContextNextPartFunc)	(ERuleContext *context,
+						 EFilterPart *part);
+typedef EFilterRule *
+		(*ERuleContextNextRuleFunc)	(ERuleContext *context,
+						 EFilterRule *rule,
+						 const gchar *source);
+
+struct _ERuleContext {
+	GObject parent;
+	ERuleContextPrivate *priv;
+
+	gchar *error;		/* string version of error */
+
+	guint32 flags;		/* capability flags */
+
+	GList *parts;
+	GList *rules;
+
+	GHashTable *part_set_map; /* map set types to part types */
+	GList *part_set_list;
+	GHashTable *rule_set_map; /* map set types to rule types */
+	GList *rule_set_list;
+};
+
+struct _ERuleContextClass {
+	GObjectClass parent_class;
+
+	/* methods */
+	gint		(*load)			(ERuleContext *context,
+						 const gchar *system,
+						 const gchar *user);
+	gint		(*save)			(ERuleContext *context,
+						 const gchar *user);
+	gint		(*revert)		(ERuleContext *context,
+						 const gchar *user);
+
+	GList *		(*delete_uri)		(ERuleContext *context,
+						 const gchar *uri,
+						 GCompareFunc compare_func);
+	GList *		(*rename_uri)		(ERuleContext *context,
+						 const gchar *old_uri,
+						 const gchar *new_uri,
+						 GCompareFunc compare_func);
+
+	EFilterElement *(*new_element)		(ERuleContext *context,
+						 const gchar *name);
+
+	/* signals */
+	void		(*rule_added)		(ERuleContext *context,
+						 EFilterRule *rule);
+	void		(*rule_removed)		(ERuleContext *context,
+						 EFilterRule *rule);
+	void		(*changed)		(ERuleContext *context);
+};
+
+struct _part_set_map {
+	gchar *name;
+	GType type;
+	ERuleContextPartFunc append;
+	ERuleContextNextPartFunc next;
+};
+
+struct _rule_set_map {
+	gchar *name;
+	GType type;
+	ERuleContextRuleFunc append;
+	ERuleContextNextRuleFunc next;
+};
+
+GType		e_rule_context_get_type		(void);
+ERuleContext *	e_rule_context_new		(void);
+
+gint		e_rule_context_load		(ERuleContext *context,
+						 const gchar *system,
+						 const gchar *user);
+gint		e_rule_context_save		(ERuleContext *context,
+						 const gchar *user);
+gint		e_rule_context_revert		(ERuleContext *context,
+						 const gchar *user);
+
+void		e_rule_context_add_part		(ERuleContext *context,
+						 EFilterPart *part);
+EFilterPart *	e_rule_context_find_part	(ERuleContext *context,
+						 const gchar *name);
+EFilterPart *	e_rule_context_create_part	(ERuleContext *context,
+						 const gchar *name);
+EFilterPart *	e_rule_context_next_part	(ERuleContext *context,
+						 EFilterPart *last);
+
+EFilterRule *	e_rule_context_next_rule	(ERuleContext *context,
+						 EFilterRule *last,
+						 const gchar *source);
+EFilterRule *	e_rule_context_find_rule	(ERuleContext *context,
+						 const gchar *name,
+						 const gchar *source);
+EFilterRule *	e_rule_context_find_rank_rule	(ERuleContext *context,
+						 gint rank,
+						 const gchar *source);
+void		e_rule_context_add_rule		(ERuleContext *context,
+						 EFilterRule *rule);
+void		e_rule_context_add_rule_gui	(ERuleContext *context,
+						 EFilterRule *rule,
+						 const gchar *title,
+						 const gchar *path);
+void		e_rule_context_remove_rule	(ERuleContext *context,
+						 EFilterRule *rule);
+
+void		e_rule_context_rank_rule	(ERuleContext *context,
+						 EFilterRule *rule,
+						 const gchar *source,
+						 gint rank);
+gint		e_rule_context_get_rank_rule	(ERuleContext *context,
+						 EFilterRule *rule,
+						 const gchar *source);
+
+void		e_rule_context_add_part_set	(ERuleContext *context,
+						 const gchar *setname,
+						 GType part_type,
+						 ERuleContextPartFunc append,
+						 ERuleContextNextPartFunc next);
+void		e_rule_context_add_rule_set	(ERuleContext *context,
+						 const gchar *setname,
+						 GType rule_type,
+						 ERuleContextRuleFunc append,
+						 ERuleContextNextRuleFunc next);
+
+EFilterElement *e_rule_context_new_element	(ERuleContext *context,
+						 const gchar *name);
+
+GList *		e_rule_context_delete_uri	(ERuleContext *context,
+						 const gchar *uri,
+						 GCompareFunc compare);
+GList *		e_rule_context_rename_uri	(ERuleContext *context,
+						 const gchar *old_uri,
+						 const gchar *new_uri,
+						 GCompareFunc compare);
+
+void		e_rule_context_free_uri_list	(ERuleContext *context,
+						 GList *uris);
+
+G_END_DECLS
+
+#endif /* E_RULE_CONTEXT_H */
diff --git a/e-util/e-rule-editor.c b/e-util/e-rule-editor.c
new file mode 100644
index 0000000..c063ae4
--- /dev/null
+++ b/e-util/e-rule-editor.c
@@ -0,0 +1,920 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Not Zed <notzed lostzed mmc com au>
+ *      Jeffrey Stedfast <fejj ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-rule-editor.h"
+
+/* for getenv only, remove when getenv need removed */
+#include <stdlib.h>
+#include <string.h>
+
+#include <glib/gi18n.h>
+
+#include "e-alert-dialog.h"
+#include "e-misc-utils.h"
+
+#define E_RULE_EDITOR_GET_PRIVATE(obj) \
+	(G_TYPE_INSTANCE_GET_PRIVATE \
+	((obj), E_TYPE_RULE_EDITOR, ERuleEditorPrivate))
+
+static gint enable_undo = 0;
+
+enum {
+	BUTTON_ADD,
+	BUTTON_EDIT,
+	BUTTON_DELETE,
+	BUTTON_TOP,
+	BUTTON_UP,
+	BUTTON_DOWN,
+	BUTTON_BOTTOM,
+	BUTTON_LAST
+};
+
+struct _ERuleEditorPrivate {
+	GtkButton *buttons[BUTTON_LAST];
+};
+
+G_DEFINE_TYPE (
+	ERuleEditor,
+	e_rule_editor,
+	GTK_TYPE_DIALOG)
+
+static void
+rule_editor_add_undo (ERuleEditor *editor,
+                      gint type,
+                      EFilterRule *rule,
+                      gint rank,
+                      gint newrank)
+{
+	ERuleEditorUndo *undo;
+
+	if (!editor->undo_active && enable_undo) {
+		undo = g_malloc0 (sizeof (*undo));
+		undo->rule = rule;
+		undo->type = type;
+		undo->rank = rank;
+		undo->newrank = newrank;
+
+		undo->next = editor->undo_log;
+		editor->undo_log = undo;
+	} else {
+		g_object_unref (rule);
+	}
+}
+
+static void
+rule_editor_play_undo (ERuleEditor *editor)
+{
+	ERuleEditorUndo *undo, *next;
+	EFilterRule *rule;
+
+	editor->undo_active = TRUE;
+	undo = editor->undo_log;
+	editor->undo_log = NULL;
+	while (undo) {
+		next = undo->next;
+		switch (undo->type) {
+		case E_RULE_EDITOR_LOG_EDIT:
+			rule = e_rule_context_find_rank_rule (editor->context, undo->rank, undo->rule->source);
+			if (rule) {
+				e_filter_rule_copy (rule, undo->rule);
+			} else {
+				g_warning ("Could not find the right rule to undo against?");
+			}
+			break;
+		case E_RULE_EDITOR_LOG_ADD:
+			rule = e_rule_context_find_rank_rule (editor->context, undo->rank, undo->rule->source);
+			if (rule)
+				e_rule_context_remove_rule (editor->context, rule);
+			break;
+		case E_RULE_EDITOR_LOG_REMOVE:
+			g_object_ref (undo->rule);
+			e_rule_context_add_rule (editor->context, undo->rule);
+			e_rule_context_rank_rule (editor->context, undo->rule, editor->source, undo->rank);
+			break;
+		case E_RULE_EDITOR_LOG_RANK:
+			rule = e_rule_context_find_rank_rule (editor->context, undo->newrank, undo->rule->source);
+			if (rule)
+				e_rule_context_rank_rule (editor->context, rule, editor->source, undo->rank);
+			break;
+		}
+
+		g_object_unref (undo->rule);
+		g_free (undo);
+		undo = next;
+	}
+	editor->undo_active = FALSE;
+}
+
+static void
+dialog_rule_changed (EFilterRule *fr,
+                     GtkWidget *dialog)
+{
+	g_return_if_fail (dialog != NULL);
+
+	gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog), GTK_RESPONSE_OK, fr && fr->parts);
+}
+
+static void
+add_editor_response (GtkWidget *dialog,
+                     gint button,
+                     ERuleEditor *editor)
+{
+	GtkTreeSelection *selection;
+	GtkTreePath *path;
+	GtkTreeIter iter;
+
+	if (button == GTK_RESPONSE_OK) {
+		EAlert *alert = NULL;
+		if (!e_filter_rule_validate (editor->edit, &alert)) {
+			e_alert_run_dialog (GTK_WINDOW (dialog), alert);
+			g_object_unref (alert);
+			return;
+		}
+
+		if (e_rule_context_find_rule (editor->context, editor->edit->name, editor->edit->source)) {
+			e_alert_run_dialog_for_args (
+				GTK_WINDOW (dialog),
+				"filter:bad-name-notunique",
+				editor->edit->name, NULL);
+			return;
+		}
+
+		g_object_ref (editor->edit);
+
+		gtk_list_store_append (editor->model, &iter);
+		gtk_list_store_set (
+			editor->model, &iter,
+			0, editor->edit->name,
+			1, editor->edit,
+			2, editor->edit->enabled, -1);
+		selection = gtk_tree_view_get_selection (editor->list);
+		gtk_tree_selection_select_iter (selection, &iter);
+
+		/* scroll to the newly added row */
+		path = gtk_tree_model_get_path (
+			GTK_TREE_MODEL (editor->model), &iter);
+		gtk_tree_view_scroll_to_cell (
+			editor->list, path, NULL, TRUE, 1.0, 0.0);
+		gtk_tree_path_free (path);
+
+		editor->current = editor->edit;
+		e_rule_context_add_rule (editor->context, editor->current);
+
+		g_object_ref (editor->current);
+		rule_editor_add_undo (
+			editor,
+			E_RULE_EDITOR_LOG_ADD,
+			editor->current,
+			e_rule_context_get_rank_rule (
+				editor->context,
+				editor->current,
+				editor->current->source),
+			0);
+	}
+
+	gtk_widget_destroy (dialog);
+}
+
+static void
+editor_destroy (ERuleEditor *editor,
+                GObject *deadbeef)
+{
+	if (editor->edit) {
+		g_object_unref (editor->edit);
+		editor->edit = NULL;
+	}
+
+	editor->dialog = NULL;
+
+	gtk_widget_set_sensitive (GTK_WIDGET (editor), TRUE);
+	e_rule_editor_set_sensitive (editor);
+}
+
+static gboolean
+update_selected_rule (ERuleEditor *editor)
+{
+	GtkTreeSelection *selection;
+	GtkTreeModel *model;
+	GtkTreeIter iter;
+
+	selection = gtk_tree_view_get_selection (editor->list);
+	if (selection && gtk_tree_selection_get_selected (selection, &model, &iter)) {
+		gtk_tree_model_get (GTK_TREE_MODEL (editor->model), &iter, 1, &editor->current, -1);
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+static void
+cursor_changed (GtkTreeView *treeview,
+                ERuleEditor *editor)
+{
+	if (update_selected_rule (editor)) {
+		g_return_if_fail (editor->current);
+
+		e_rule_editor_set_sensitive (editor);
+	}
+}
+
+static void
+editor_response (GtkWidget *dialog,
+                 gint button,
+                 ERuleEditor *editor)
+{
+	if (button == GTK_RESPONSE_CANCEL) {
+		if (enable_undo)
+			rule_editor_play_undo (editor);
+		else {
+			ERuleEditorUndo *undo, *next;
+
+			undo = editor->undo_log;
+			editor->undo_log = NULL;
+			while (undo) {
+				next = undo->next;
+				g_object_unref (undo->rule);
+				g_free (undo);
+				undo = next;
+			}
+		}
+	}
+}
+
+static void
+rule_add (GtkWidget *widget,
+          ERuleEditor *editor)
+{
+	GtkWidget *rules;
+	GtkWidget *content_area;
+
+	if (editor->edit != NULL)
+		return;
+
+	editor->edit = e_rule_editor_create_rule (editor);
+	e_filter_rule_set_source (editor->edit, editor->source);
+	rules = e_filter_rule_get_widget (editor->edit, editor->context);
+
+	editor->dialog = gtk_dialog_new ();
+	gtk_dialog_add_buttons (
+		GTK_DIALOG (editor->dialog),
+		GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+		GTK_STOCK_OK, GTK_RESPONSE_OK,
+		NULL);
+
+	gtk_window_set_title ((GtkWindow *) editor->dialog, _("Add Rule"));
+	gtk_window_set_default_size (GTK_WINDOW (editor->dialog), 650, 400);
+	gtk_window_set_resizable (GTK_WINDOW (editor->dialog), TRUE);
+	gtk_window_set_transient_for ((GtkWindow *) editor->dialog, (GtkWindow *) editor);
+	gtk_container_set_border_width ((GtkContainer *) editor->dialog, 6);
+
+	content_area = gtk_dialog_get_content_area (GTK_DIALOG (editor->dialog));
+	gtk_box_pack_start (GTK_BOX (content_area), rules, TRUE, TRUE, 3);
+
+	g_signal_connect (
+		editor->dialog, "response",
+		G_CALLBACK (add_editor_response), editor);
+	g_object_weak_ref ((GObject *) editor->dialog, (GWeakNotify) editor_destroy, editor);
+
+	g_signal_connect (
+		editor->edit, "changed",
+		G_CALLBACK (dialog_rule_changed), editor->dialog);
+	dialog_rule_changed (editor->edit, editor->dialog);
+
+	gtk_widget_set_sensitive (GTK_WIDGET (editor), FALSE);
+
+	gtk_widget_show (editor->dialog);
+}
+
+static void
+edit_editor_response (GtkWidget *dialog,
+                      gint button,
+                      ERuleEditor *editor)
+{
+	EFilterRule *rule;
+	GtkTreePath *path;
+	GtkTreeIter iter;
+	gint pos;
+
+	if (button == GTK_RESPONSE_OK) {
+		EAlert *alert = NULL;
+		if (!e_filter_rule_validate (editor->edit, &alert)) {
+			e_alert_run_dialog (GTK_WINDOW (dialog), alert);
+			g_object_unref (alert);
+			return;
+		}
+
+		rule = e_rule_context_find_rule (
+			editor->context,
+			editor->edit->name,
+			editor->edit->source);
+
+		if (rule != NULL && rule != editor->current) {
+			e_alert_run_dialog_for_args (
+				GTK_WINDOW (dialog),
+				"filter:bad-name-notunique",
+				rule->name, NULL);
+			return;
+		}
+
+		pos = e_rule_context_get_rank_rule (
+			editor->context,
+			editor->current,
+			editor->source);
+
+		if (pos != -1) {
+			path = gtk_tree_path_new ();
+			gtk_tree_path_append_index (path, pos);
+			gtk_tree_model_get_iter (
+				GTK_TREE_MODEL (editor->model), &iter, path);
+			gtk_tree_path_free (path);
+
+			gtk_list_store_set (
+				editor->model, &iter,
+				0, editor->edit->name, -1);
+
+			rule_editor_add_undo (
+				editor, E_RULE_EDITOR_LOG_EDIT,
+				e_filter_rule_clone (editor->current),
+				pos, 0);
+
+			/* replace the old rule with the new rule */
+			e_filter_rule_copy (editor->current, editor->edit);
+		}
+	}
+
+	gtk_widget_destroy (dialog);
+}
+
+static void
+rule_edit (GtkWidget *widget,
+           ERuleEditor *editor)
+{
+	GtkWidget *rules;
+	GtkWidget *content_area;
+
+	update_selected_rule (editor);
+
+	if (editor->current == NULL || editor->edit != NULL)
+		return;
+
+	editor->edit = e_filter_rule_clone (editor->current);
+
+	rules = e_filter_rule_get_widget (editor->edit, editor->context);
+
+	editor->dialog = gtk_dialog_new ();
+	gtk_dialog_add_buttons (
+		(GtkDialog *) editor->dialog,
+				GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+				GTK_STOCK_OK, GTK_RESPONSE_OK,
+				NULL);
+
+	gtk_window_set_title ((GtkWindow *) editor->dialog, _("Edit Rule"));
+	gtk_window_set_default_size (GTK_WINDOW (editor->dialog), 650, 400);
+	gtk_window_set_resizable (GTK_WINDOW (editor->dialog), TRUE);
+	gtk_window_set_transient_for (GTK_WINDOW (editor->dialog), GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (editor))));
+	gtk_container_set_border_width ((GtkContainer *) editor->dialog, 6);
+
+	content_area = gtk_dialog_get_content_area (GTK_DIALOG (editor->dialog));
+	gtk_box_pack_start (GTK_BOX (content_area), rules, TRUE, TRUE, 3);
+
+	g_signal_connect (
+		editor->dialog, "response",
+		G_CALLBACK (edit_editor_response), editor);
+	g_object_weak_ref ((GObject *) editor->dialog, (GWeakNotify) editor_destroy, editor);
+
+	g_signal_connect (
+		editor->edit, "changed",
+		G_CALLBACK (dialog_rule_changed), editor->dialog);
+	dialog_rule_changed (editor->edit, editor->dialog);
+
+	gtk_widget_set_sensitive (GTK_WIDGET (editor), FALSE);
+
+	gtk_widget_show (editor->dialog);
+}
+
+static void
+rule_delete (GtkWidget *widget,
+             ERuleEditor *editor)
+{
+	GtkTreeSelection *selection;
+	GtkTreePath *path;
+	GtkTreeIter iter;
+	gint pos, len;
+
+	update_selected_rule (editor);
+
+	pos = e_rule_context_get_rank_rule (editor->context, editor->current, editor->source);
+	if (pos != -1) {
+		EFilterRule *delete_rule = editor->current;
+
+		editor->current = NULL;
+
+		e_rule_context_remove_rule (editor->context, delete_rule);
+
+		path = gtk_tree_path_new ();
+		gtk_tree_path_append_index (path, pos);
+		gtk_tree_model_get_iter (GTK_TREE_MODEL (editor->model), &iter, path);
+		gtk_list_store_remove (editor->model, &iter);
+		gtk_tree_path_free (path);
+
+		rule_editor_add_undo (
+			editor,
+			E_RULE_EDITOR_LOG_REMOVE,
+			delete_rule,
+			e_rule_context_get_rank_rule (
+				editor->context,
+				delete_rule,
+				delete_rule->source),
+			0);
+#if 0
+		g_object_unref (delete_rule);
+#endif
+
+		/* now select the next rule */
+		len = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (editor->model), NULL);
+		pos = pos >= len ? len - 1 : pos;
+
+		if (pos >= 0) {
+			path = gtk_tree_path_new ();
+			gtk_tree_path_append_index (path, pos);
+			gtk_tree_model_get_iter (GTK_TREE_MODEL (editor->model), &iter, path);
+			gtk_tree_path_free (path);
+
+			/* select the new row */
+			selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (editor->list));
+			gtk_tree_selection_select_iter (selection, &iter);
+
+			/* scroll to the selected row */
+			path = gtk_tree_model_get_path ((GtkTreeModel *) editor->model, &iter);
+			gtk_tree_view_scroll_to_cell (editor->list, path, NULL, FALSE, 0.0, 0.0);
+			gtk_tree_path_free (path);
+
+			/* update our selection state */
+			cursor_changed (editor->list, editor);
+			return;
+		}
+	}
+
+	e_rule_editor_set_sensitive (editor);
+}
+
+static void
+rule_move (ERuleEditor *editor,
+           gint from,
+           gint to)
+{
+	GtkTreeSelection *selection;
+	GtkTreePath *path;
+	GtkTreeIter iter;
+	EFilterRule *rule;
+
+	rule_editor_add_undo (
+		editor, E_RULE_EDITOR_LOG_RANK,
+		g_object_ref (editor->current),
+		e_rule_context_get_rank_rule (editor->context,
+		editor->current, editor->source), to);
+
+	e_rule_context_rank_rule (
+		editor->context, editor->current, editor->source, to);
+
+	path = gtk_tree_path_new ();
+	gtk_tree_path_append_index (path, from);
+	gtk_tree_model_get_iter (GTK_TREE_MODEL (editor->model), &iter, path);
+	gtk_tree_path_free (path);
+
+	gtk_tree_model_get (GTK_TREE_MODEL (editor->model), &iter, 1, &rule, -1);
+	g_return_if_fail (rule != NULL);
+
+	/* remove and then re-insert the row at the new location */
+	gtk_list_store_remove (editor->model, &iter);
+	gtk_list_store_insert (editor->model, &iter, to);
+
+	/* set the data on the row */
+	gtk_list_store_set (editor->model, &iter, 0, rule->name, 1, rule, 2, rule->enabled, -1);
+
+	/* select the row */
+	selection = gtk_tree_view_get_selection (editor->list);
+	gtk_tree_selection_select_iter (selection, &iter);
+
+	/* scroll to the selected row */
+	path = gtk_tree_model_get_path ((GtkTreeModel *) editor->model, &iter);
+	gtk_tree_view_scroll_to_cell (editor->list, path, NULL, FALSE, 0.0, 0.0);
+	gtk_tree_path_free (path);
+
+	e_rule_editor_set_sensitive (editor);
+}
+
+static void
+rule_top (GtkWidget *widget,
+          ERuleEditor *editor)
+{
+	gint pos;
+
+	update_selected_rule (editor);
+
+	pos = e_rule_context_get_rank_rule (
+		editor->context, editor->current, editor->source);
+	if (pos > 0)
+		rule_move (editor, pos, 0);
+}
+
+static void
+rule_up (GtkWidget *widget,
+         ERuleEditor *editor)
+{
+	gint pos;
+
+	update_selected_rule (editor);
+
+	pos = e_rule_context_get_rank_rule (
+		editor->context, editor->current, editor->source);
+	if (pos > 0)
+		rule_move (editor, pos, pos - 1);
+}
+
+static void
+rule_down (GtkWidget *widget,
+           ERuleEditor *editor)
+{
+	gint pos;
+
+	update_selected_rule (editor);
+
+	pos = e_rule_context_get_rank_rule (
+		editor->context, editor->current, editor->source);
+	if (pos >= 0)
+		rule_move (editor, pos, pos + 1);
+}
+
+static void
+rule_bottom (GtkWidget *widget,
+             ERuleEditor *editor)
+{
+	gint pos;
+	gint count = 0;
+	EFilterRule *rule = NULL;
+
+	update_selected_rule (editor);
+
+	pos = e_rule_context_get_rank_rule (
+		editor->context, editor->current, editor->source);
+	/* There's probably a better/faster way to get the count of the list here */
+	while ((rule = e_rule_context_next_rule (editor->context, rule, editor->source)))
+		count++;
+	count--;
+	if (pos >= 0)
+		rule_move (editor, pos, count);
+}
+
+static struct {
+	const gchar *name;
+	GCallback func;
+} edit_buttons[] = {
+	{ "rule_add",    G_CALLBACK (rule_add)    },
+	{ "rule_edit",   G_CALLBACK (rule_edit)   },
+	{ "rule_delete", G_CALLBACK (rule_delete) },
+	{ "rule_top",    G_CALLBACK (rule_top)    },
+	{ "rule_up",     G_CALLBACK (rule_up)     },
+	{ "rule_down",   G_CALLBACK (rule_down)   },
+	{ "rule_bottom", G_CALLBACK (rule_bottom) },
+};
+
+static void
+rule_editor_finalize (GObject *object)
+{
+	ERuleEditor *editor = E_RULE_EDITOR (object);
+	ERuleEditorUndo *undo, *next;
+
+	g_object_unref (editor->context);
+
+	undo = editor->undo_log;
+	while (undo) {
+		next = undo->next;
+		g_object_unref (undo->rule);
+		g_free (undo);
+		undo = next;
+	}
+
+	/* Chain up to parent's finalize() method. */
+	G_OBJECT_CLASS (e_rule_editor_parent_class)->finalize (object);
+}
+
+static void
+rule_editor_dispose (GObject *object)
+{
+	ERuleEditor *editor = E_RULE_EDITOR (object);
+
+	if (editor->dialog != NULL) {
+		gtk_widget_destroy (GTK_WIDGET (editor->dialog));
+		editor->dialog = NULL;
+	}
+
+	/* Chain up to parent's dispose() method. */
+	G_OBJECT_CLASS (e_rule_editor_parent_class)->dispose (object);
+}
+
+static void
+rule_editor_set_source (ERuleEditor *editor,
+                        const gchar *source)
+{
+	EFilterRule *rule = NULL;
+	GtkTreeIter iter;
+
+	gtk_list_store_clear (editor->model);
+
+	while ((rule = e_rule_context_next_rule (editor->context, rule, source)) != NULL) {
+		gtk_list_store_append (editor->model, &iter);
+		gtk_list_store_set (
+			editor->model, &iter,
+			0, rule->name, 1, rule, 2, rule->enabled, -1);
+	}
+
+	g_free (editor->source);
+	editor->source = g_strdup (source);
+	editor->current = NULL;
+	e_rule_editor_set_sensitive (editor);
+}
+
+static void
+rule_editor_set_sensitive (ERuleEditor *editor)
+{
+	EFilterRule *rule = NULL;
+	gint index = -1, count = 0;
+
+	while ((rule = e_rule_context_next_rule (editor->context, rule, editor->source))) {
+		if (rule == editor->current)
+			index = count;
+		count++;
+	}
+
+	count--;
+
+	gtk_widget_set_sensitive (GTK_WIDGET (editor->priv->buttons[BUTTON_EDIT]), index != -1);
+	gtk_widget_set_sensitive (GTK_WIDGET (editor->priv->buttons[BUTTON_DELETE]), index != -1);
+	gtk_widget_set_sensitive (GTK_WIDGET (editor->priv->buttons[BUTTON_TOP]), index > 0);
+	gtk_widget_set_sensitive (GTK_WIDGET (editor->priv->buttons[BUTTON_UP]), index > 0);
+	gtk_widget_set_sensitive (GTK_WIDGET (editor->priv->buttons[BUTTON_DOWN]), index >= 0 && index < count);
+	gtk_widget_set_sensitive (GTK_WIDGET (editor->priv->buttons[BUTTON_BOTTOM]), index >= 0 && index < count);
+}
+
+static EFilterRule *
+rule_editor_create_rule (ERuleEditor *editor)
+{
+	EFilterRule *rule;
+	EFilterPart *part;
+
+	/* create a rule with 1 part in it */
+	rule = e_filter_rule_new ();
+	part = e_rule_context_next_part (editor->context, NULL);
+	e_filter_rule_add_part (rule, e_filter_part_clone (part));
+
+	return rule;
+}
+
+static void
+e_rule_editor_class_init (ERuleEditorClass *class)
+{
+	GObjectClass *object_class;
+
+	g_type_class_add_private (class, sizeof (ERuleEditorPrivate));
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->finalize = rule_editor_finalize;
+	object_class->dispose = rule_editor_dispose;
+
+	class->set_source = rule_editor_set_source;
+	class->set_sensitive = rule_editor_set_sensitive;
+	class->create_rule = rule_editor_create_rule;
+
+	/* TODO: Remove when it works (or never will) */
+	enable_undo = getenv ("EVOLUTION_RULE_UNDO") != NULL;
+}
+
+static void
+e_rule_editor_init (ERuleEditor *editor)
+{
+	editor->priv = E_RULE_EDITOR_GET_PRIVATE (editor);
+}
+
+/**
+ * rule_editor_new:
+ *
+ * Create a new ERuleEditor object.
+ *
+ * Return value: A new #ERuleEditor object.
+ **/
+ERuleEditor *
+e_rule_editor_new (ERuleContext *context,
+                   const gchar *source,
+                   const gchar *label)
+{
+	ERuleEditor *editor = (ERuleEditor *) g_object_new (E_TYPE_RULE_EDITOR, NULL);
+	GtkBuilder *builder;
+
+	builder = gtk_builder_new ();
+	e_load_ui_builder_definition (builder, "filter.ui");
+	e_rule_editor_construct (editor, context, builder, source, label);
+	gtk_widget_hide (e_builder_get_widget (builder, "label17"));
+	gtk_widget_hide (e_builder_get_widget (builder, "filter_source_combobox"));
+	g_object_unref (builder);
+
+	return editor;
+}
+
+void
+e_rule_editor_set_sensitive (ERuleEditor *editor)
+{
+	ERuleEditorClass *class;
+
+	g_return_if_fail (E_IS_RULE_EDITOR (editor));
+
+	class = E_RULE_EDITOR_GET_CLASS (editor);
+	g_return_if_fail (class->set_sensitive != NULL);
+
+	class->set_sensitive (editor);
+}
+
+void
+e_rule_editor_set_source (ERuleEditor *editor,
+                          const gchar *source)
+{
+	ERuleEditorClass *class;
+
+	g_return_if_fail (E_IS_RULE_EDITOR (editor));
+
+	class = E_RULE_EDITOR_GET_CLASS (editor);
+	g_return_if_fail (class->set_source != NULL);
+
+	class->set_source (editor, source);
+}
+
+EFilterRule *
+e_rule_editor_create_rule (ERuleEditor *editor)
+{
+	ERuleEditorClass *class;
+
+	g_return_val_if_fail (E_IS_RULE_EDITOR (editor), NULL);
+
+	class = E_RULE_EDITOR_GET_CLASS (editor);
+	g_return_val_if_fail (class->create_rule != NULL, NULL);
+
+	return class->create_rule (editor);
+}
+
+static void
+double_click (GtkTreeView *treeview,
+              GtkTreePath *path,
+              GtkTreeViewColumn *column,
+              ERuleEditor *editor)
+{
+	GtkTreeSelection *selection;
+	GtkTreeModel *model;
+	GtkTreeIter iter;
+
+	selection = gtk_tree_view_get_selection (editor->list);
+	if (gtk_tree_selection_get_selected (selection, &model, &iter))
+		gtk_tree_model_get (GTK_TREE_MODEL (editor->model), &iter, 1, &editor->current, -1);
+
+	if (editor->current)
+		rule_edit ((GtkWidget *) treeview, editor);
+}
+
+static void
+rule_able_toggled (GtkCellRendererToggle *renderer,
+                   gchar *path_string,
+                   gpointer user_data)
+{
+	GtkWidget *table = user_data;
+	GtkTreeModel *model;
+	GtkTreePath *path;
+	GtkTreeIter iter;
+
+	path = gtk_tree_path_new_from_string (path_string);
+	model = gtk_tree_view_get_model (GTK_TREE_VIEW (table));
+
+	if (gtk_tree_model_get_iter (model, &iter, path)) {
+		EFilterRule *rule = NULL;
+
+		gtk_tree_model_get (model, &iter, 1, &rule, -1);
+
+		if (rule) {
+			rule->enabled = !rule->enabled;
+			gtk_list_store_set (GTK_LIST_STORE (model), &iter, 2, rule->enabled, -1);
+		}
+	}
+
+	gtk_tree_path_free (path);
+}
+
+void
+e_rule_editor_construct (ERuleEditor *editor,
+                         ERuleContext *context,
+                         GtkBuilder *builder,
+                         const gchar *source,
+                         const gchar *label)
+{
+	GtkWidget *widget;
+	GtkWidget *action_area;
+	GtkWidget *content_area;
+	GtkTreeViewColumn *column;
+	GtkCellRenderer *renderer;
+	GtkTreeSelection *selection;
+	GObject *object;
+	GList *list;
+	gint i;
+
+	g_return_if_fail (E_IS_RULE_EDITOR (editor));
+	g_return_if_fail (E_IS_RULE_CONTEXT (context));
+	g_return_if_fail (GTK_IS_BUILDER (builder));
+
+	editor->context = g_object_ref (context);
+
+	action_area = gtk_dialog_get_action_area (GTK_DIALOG (editor));
+	content_area = gtk_dialog_get_content_area (GTK_DIALOG (editor));
+
+	gtk_window_set_resizable ((GtkWindow *) editor, TRUE);
+	gtk_window_set_default_size ((GtkWindow *) editor, 350, 400);
+	gtk_widget_realize ((GtkWidget *) editor);
+	gtk_container_set_border_width (GTK_CONTAINER (action_area), 12);
+
+	widget = e_builder_get_widget (builder, "rule_editor");
+	gtk_box_pack_start (GTK_BOX (content_area), widget, TRUE, TRUE, 0);
+
+	for (i = 0; i < BUTTON_LAST; i++) {
+		widget = e_builder_get_widget (builder, edit_buttons[i].name);
+		editor->priv->buttons[i] = GTK_BUTTON (widget);
+		g_signal_connect (
+			widget, "clicked",
+			G_CALLBACK (edit_buttons[i].func), editor);
+	}
+
+	object = gtk_builder_get_object (builder, "rule_tree_view");
+	editor->list = GTK_TREE_VIEW (object);
+
+	column = gtk_tree_view_get_column (GTK_TREE_VIEW (object), 0);
+	g_return_if_fail (column != NULL);
+
+	gtk_tree_view_column_set_visible (column, FALSE);
+	list = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (column));
+	g_return_if_fail (list != NULL);
+
+	renderer = GTK_CELL_RENDERER (list->data);
+	g_warn_if_fail (GTK_IS_CELL_RENDERER_TOGGLE (renderer));
+
+	g_signal_connect (
+		renderer, "toggled",
+		G_CALLBACK (rule_able_toggled), editor->list);
+
+	selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (object));
+	gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE);
+
+	object = gtk_builder_get_object (builder, "rule_list_store");
+	editor->model = GTK_LIST_STORE (object);
+
+	g_signal_connect (
+		editor->list, "cursor-changed",
+		G_CALLBACK (cursor_changed), editor);
+	g_signal_connect (
+		editor->list, "row-activated",
+		G_CALLBACK (double_click), editor);
+
+	widget = e_builder_get_widget (builder, "rule_label");
+	gtk_label_set_label (GTK_LABEL (widget), label);
+	gtk_label_set_mnemonic_widget (
+		GTK_LABEL (widget), GTK_WIDGET (editor->list));
+
+	g_signal_connect (
+		editor, "response",
+		G_CALLBACK (editor_response), editor);
+	rule_editor_set_source (editor, source);
+
+	gtk_dialog_add_buttons (
+		GTK_DIALOG (editor),
+		GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+		GTK_STOCK_OK, GTK_RESPONSE_OK,
+		NULL);
+}
diff --git a/e-util/e-rule-editor.h b/e-util/e-rule-editor.h
new file mode 100644
index 0000000..d983b81
--- /dev/null
+++ b/e-util/e-rule-editor.h
@@ -0,0 +1,125 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Not Zed <notzed lostzed mmc com au>
+ *      Jeffrey Stedfast <fejj ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_RULE_EDITOR_H
+#define E_RULE_EDITOR_H
+
+#include <gtk/gtk.h>
+
+#include <e-util/e-rule-context.h>
+#include <e-util/e-filter-rule.h>
+
+/* Standard GObject macros */
+#define E_TYPE_RULE_EDITOR \
+	(e_rule_editor_get_type ())
+#define E_RULE_EDITOR(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_RULE_EDITOR, ERuleEditor))
+#define E_RULE_EDITOR_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_RULE_EDITOR, ERuleEditorClass))
+#define E_IS_RULE_EDITOR(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_RULE_EDITOR))
+#define E_IS_RULE_EDITOR_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_RULE_EDITOR))
+#define E_RULE_EDITOR_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_RULE_EDITOR, ERuleEditorClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ERuleEditor ERuleEditor;
+typedef struct _ERuleEditorClass ERuleEditorClass;
+typedef struct _ERuleEditorPrivate ERuleEditorPrivate;
+
+typedef struct _ERuleEditorUndo ERuleEditorUndo;
+
+struct _ERuleEditor {
+	GtkDialog parent;
+
+	GtkListStore *model;
+	GtkTreeView *list;
+
+	ERuleContext *context;
+	EFilterRule *current;
+	EFilterRule *edit;	/* for editing/adding rules, so we only do 1 at a time */
+
+	GtkWidget *dialog;
+
+	gchar *source;
+
+	ERuleEditorUndo *undo_log;	/* cancel/undo log */
+	guint undo_active:1; /* we're performing undo */
+
+	ERuleEditorPrivate *priv;
+};
+
+struct _ERuleEditorClass {
+	GtkDialogClass parent_class;
+
+	void		(*set_sensitive)	(ERuleEditor *editor);
+	void		(*set_source)		(ERuleEditor *editor,
+						 const gchar *source);
+
+	EFilterRule *	(*create_rule)		(ERuleEditor *editor);
+};
+
+enum {
+	E_RULE_EDITOR_LOG_EDIT,
+	E_RULE_EDITOR_LOG_ADD,
+	E_RULE_EDITOR_LOG_REMOVE,
+	E_RULE_EDITOR_LOG_RANK
+};
+
+struct _ERuleEditorUndo {
+	ERuleEditorUndo *next;
+
+	guint type;
+	EFilterRule *rule;
+	gint rank;
+	gint newrank;
+};
+
+GType		e_rule_editor_get_type		(void);
+ERuleEditor *	e_rule_editor_new		(ERuleContext *context,
+						 const gchar *source,
+						 const gchar *label);
+void		e_rule_editor_construct		(ERuleEditor *editor,
+						 ERuleContext *context,
+						 GtkBuilder *builder,
+						 const gchar *source,
+						 const gchar *label);
+void		e_rule_editor_set_source	(ERuleEditor *editor,
+						 const gchar *source);
+void		e_rule_editor_set_sensitive	(ERuleEditor *editor);
+EFilterRule *	e_rule_editor_create_rule	(ERuleEditor *editor);
+
+G_END_DECLS
+
+#endif /* E_RULE_EDITOR_H */
diff --git a/widgets/misc/e-search-bar.c b/e-util/e-search-bar.c
similarity index 100%
rename from widgets/misc/e-search-bar.c
rename to e-util/e-search-bar.c
diff --git a/e-util/e-search-bar.h b/e-util/e-search-bar.h
new file mode 100644
index 0000000..43e1645
--- /dev/null
+++ b/e-util/e-search-bar.h
@@ -0,0 +1,89 @@
+/*
+ * e-search-bar.h
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_SEARCH_BAR_H
+#define E_SEARCH_BAR_H
+
+#include <gtk/gtk.h>
+
+#include <e-util/e-web-view.h>
+
+/* Standard GObject macros */
+#define E_TYPE_SEARCH_BAR \
+	(e_search_bar_get_type ())
+#define E_SEARCH_BAR(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_SEARCH_BAR, ESearchBar))
+#define E_SEARCH_BAR_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_SEARCH_BAR, ESearchBarClass))
+#define E_IS_SEARCH_BAR(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_SEARCH_BAR))
+#define E_IS_SEARCH_BAR_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_SEARCH_BAR))
+#define E_SEARCH_BAR_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_SEARCH_BAR, ESearchBarClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ESearchBar ESearchBar;
+typedef struct _ESearchBarClass ESearchBarClass;
+typedef struct _ESearchBarPrivate ESearchBarPrivate;
+
+struct _ESearchBar {
+	GtkBox parent;
+	ESearchBarPrivate *priv;
+};
+
+struct _ESearchBarClass {
+	GtkBoxClass parent_class;
+
+	/* Signals */
+	void		(*changed)		(ESearchBar *search_bar);
+	void		(*clear)		(ESearchBar *search_bar);
+};
+
+GType		e_search_bar_get_type		(void);
+GtkWidget *	e_search_bar_new		(EWebView *web_view);
+void		e_search_bar_clear		(ESearchBar *search_bar);
+void		e_search_bar_changed		(ESearchBar *search_bar);
+EWebView *	e_search_bar_get_web_view	(ESearchBar *search_bar);
+gboolean	e_search_bar_get_active_search
+						(ESearchBar *search_bar);
+gboolean	e_search_bar_get_case_sensitive
+						(ESearchBar *search_bar);
+void		e_search_bar_set_case_sensitive
+						(ESearchBar *search_bar,
+						 gboolean case_sensitive);
+gchar *		e_search_bar_get_text		(ESearchBar *search_bar);
+void		e_search_bar_set_text		(ESearchBar *search_bar,
+						 const gchar *text);
+
+G_END_DECLS
+
+#endif /* E_SEARCH_BAR_H */
diff --git a/widgets/misc/e-selectable.c b/e-util/e-selectable.c
similarity index 100%
rename from widgets/misc/e-selectable.c
rename to e-util/e-selectable.c
diff --git a/e-util/e-selectable.h b/e-util/e-selectable.h
new file mode 100644
index 0000000..4e7faa8
--- /dev/null
+++ b/e-util/e-selectable.h
@@ -0,0 +1,85 @@
+/*
+ * e-selectable.h
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_SELECTABLE_H
+#define E_SELECTABLE_H
+
+#include <gtk/gtk.h>
+
+#include <e-util/e-focus-tracker.h>
+
+/* Standard GObject macros */
+#define E_TYPE_SELECTABLE \
+	(e_selectable_get_type ())
+#define E_SELECTABLE(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_SELECTABLE, ESelectable))
+#define E_IS_SELECTABLE(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_SELECTABLE))
+#define E_SELECTABLE_GET_INTERFACE(obj) \
+	(G_TYPE_INSTANCE_GET_INTERFACE \
+	((obj), E_TYPE_SELECTABLE, ESelectableInterface))
+
+G_BEGIN_DECLS
+
+typedef struct _ESelectable ESelectable;
+typedef struct _ESelectableInterface ESelectableInterface;
+
+struct _ESelectableInterface {
+	GTypeInterface parent_iface;
+
+	/* Required Methods */
+	void		(*update_actions)	(ESelectable *selectable,
+						 EFocusTracker *focus_tracker,
+						 GdkAtom *clipboard_targets,
+						 gint n_clipboard_targets);
+
+	/* Optional Methods */
+	void		(*cut_clipboard)	(ESelectable *selectable);
+	void		(*copy_clipboard)	(ESelectable *selectable);
+	void		(*paste_clipboard)	(ESelectable *selectable);
+	void		(*delete_selection)	(ESelectable *selectable);
+	void		(*select_all)		(ESelectable *selectable);
+};
+
+GType		e_selectable_get_type		(void);
+void		e_selectable_update_actions	(ESelectable *selectable,
+						 EFocusTracker *focus_tracker,
+						 GdkAtom *clipboard_targets,
+						 gint n_clipboard_targets);
+void		e_selectable_cut_clipboard	(ESelectable *selectable);
+void		e_selectable_copy_clipboard	(ESelectable *selectable);
+void		e_selectable_paste_clipboard	(ESelectable *selectable);
+void		e_selectable_delete_selection	(ESelectable *selectable);
+void		e_selectable_select_all		(ESelectable *selectable);
+GtkTargetList *	e_selectable_get_copy_target_list
+						(ESelectable *selectable);
+GtkTargetList *	e_selectable_get_paste_target_list
+						(ESelectable *selectable);
+
+G_END_DECLS
+
+#endif /* E_SELECTABLE_H */
diff --git a/e-util/e-selection-model-array.c b/e-util/e-selection-model-array.c
new file mode 100644
index 0000000..fe73857
--- /dev/null
+++ b/e-util/e-selection-model-array.c
@@ -0,0 +1,646 @@
+/*
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <gtk/gtk.h>
+
+#include <glib/gi18n.h>
+
+#include "e-selection-model-array.h"
+
+G_DEFINE_TYPE (
+	ESelectionModelArray,
+	e_selection_model_array,
+	E_TYPE_SELECTION_MODEL)
+
+enum {
+	PROP_0,
+	PROP_CURSOR_ROW,
+	PROP_CURSOR_COL
+};
+
+void
+e_selection_model_array_confirm_row_count (ESelectionModelArray *esma)
+{
+	if (esma->eba == NULL) {
+		gint row_count = e_selection_model_array_get_row_count (esma);
+		esma->eba = e_bit_array_new (row_count);
+		esma->selected_row = -1;
+		esma->selected_range_end = -1;
+	}
+}
+
+static gint
+es_row_model_to_sorted (ESelectionModelArray *esma,
+                        gint model_row)
+{
+	if (model_row >= 0 && esma && esma->base.sorter && e_sorter_needs_sorting (esma->base.sorter))
+		return e_sorter_model_to_sorted (esma->base.sorter, model_row);
+
+	return model_row;
+}
+
+static gint
+es_row_sorted_to_model (ESelectionModelArray *esma,
+                        gint sorted_row)
+{
+	if (sorted_row >= 0 && esma && esma->base.sorter && e_sorter_needs_sorting (esma->base.sorter))
+		return e_sorter_sorted_to_model (esma->base.sorter, sorted_row);
+
+	return sorted_row;
+}
+
+/* FIXME: Should this deal with moving the selection if it's in single mode? */
+void
+e_selection_model_array_delete_rows (ESelectionModelArray *esma,
+                                     gint row,
+                                     gint count)
+{
+	if (esma->eba) {
+		if (E_SELECTION_MODEL (esma)->mode == GTK_SELECTION_SINGLE)
+			e_bit_array_delete_single_mode (esma->eba, row, count);
+		else
+			e_bit_array_delete (esma->eba, row, count);
+
+		if (esma->cursor_row >= row && esma->cursor_row < row + count) {
+			/* we should move the cursor_row, because some lines before us are going to be removed */
+			if (esma->cursor_row_sorted >= e_bit_array_bit_count (esma->eba)) {
+				esma->cursor_row_sorted = e_bit_array_bit_count (esma->eba) - 1;
+			}
+
+			if (esma->cursor_row_sorted >= 0) {
+				esma->cursor_row = es_row_sorted_to_model (esma, esma->cursor_row_sorted);
+				esma->selection_start_row = 0;
+				e_bit_array_change_one_row (esma->eba, esma->cursor_row, TRUE);
+			} else {
+				esma->cursor_row = -1;
+				esma->cursor_row_sorted = -1;
+				esma->selection_start_row = 0;
+			}
+		} else {
+			/* some code earlier changed the selected row, so just update the sorted one */
+			if (esma->cursor_row >= row)
+				esma->cursor_row = MAX (0, esma->cursor_row - count);
+
+			if (esma->cursor_row >= e_bit_array_bit_count (esma->eba))
+				esma->cursor_row = e_bit_array_bit_count (esma->eba) - 1;
+
+			if (esma->cursor_row >= 0) {
+				esma->cursor_row_sorted = es_row_model_to_sorted (esma, esma->cursor_row);
+				esma->selection_start_row = 0;
+				e_bit_array_change_one_row (esma->eba, esma->cursor_row, TRUE);
+			} else {
+				esma->cursor_row = -1;
+				esma->cursor_row_sorted = -1;
+				esma->selection_start_row = 0;
+			}
+		}
+
+		esma->selected_row = -1;
+		esma->selected_range_end = -1;
+		e_selection_model_selection_changed (E_SELECTION_MODEL (esma));
+		e_selection_model_cursor_changed (E_SELECTION_MODEL (esma), esma->cursor_row, esma->cursor_col);
+	}
+}
+
+void
+e_selection_model_array_insert_rows (ESelectionModelArray *esma,
+                                     gint row,
+                                     gint count)
+{
+	if (esma->eba) {
+		e_bit_array_insert (esma->eba, row, count);
+
+		/* just recalculate new position of the previously set cursor row */
+		esma->cursor_row = es_row_sorted_to_model (esma, esma->cursor_row_sorted);
+
+		esma->selected_row = -1;
+		esma->selected_range_end = -1;
+		e_selection_model_selection_changed (E_SELECTION_MODEL (esma));
+		e_selection_model_cursor_changed (E_SELECTION_MODEL (esma), esma->cursor_row, esma->cursor_col);
+	}
+}
+
+void
+e_selection_model_array_move_row (ESelectionModelArray *esma,
+                                  gint old_row,
+                                  gint new_row)
+{
+	ESelectionModel *esm = E_SELECTION_MODEL (esma);
+
+	if (esma->eba) {
+		gboolean selected = e_bit_array_value_at (esma->eba, old_row);
+		gboolean cursor = (esma->cursor_row == old_row);
+		gint old_row_sorted, new_row_sorted;
+
+		old_row_sorted = es_row_model_to_sorted (esma, old_row);
+		new_row_sorted = es_row_model_to_sorted (esma, new_row);
+
+		if (old_row_sorted < esma->cursor_row_sorted && esma->cursor_row_sorted < new_row_sorted)
+			esma->cursor_row_sorted--;
+		else if (new_row_sorted < esma->cursor_row_sorted && esma->cursor_row_sorted < old_row_sorted)
+			esma->cursor_row_sorted++;
+
+		e_bit_array_move_row (esma->eba, old_row, new_row);
+
+		if (selected) {
+			if (esm->mode == GTK_SELECTION_SINGLE)
+				e_bit_array_select_single_row (esma->eba, new_row);
+			else
+				e_bit_array_change_one_row (esma->eba, new_row, TRUE);
+		}
+		if (cursor) {
+			esma->cursor_row = new_row;
+			esma->cursor_row_sorted = es_row_model_to_sorted (esma, esma->cursor_row);
+		} else
+			esma->cursor_row = es_row_sorted_to_model (esma, esma->cursor_row_sorted);
+
+		esma->selected_row = -1;
+		esma->selected_range_end = -1;
+		e_selection_model_selection_changed (esm);
+		e_selection_model_cursor_changed (esm, esma->cursor_row, esma->cursor_col);
+	}
+}
+
+static void
+esma_dispose (GObject *object)
+{
+	ESelectionModelArray *esma;
+
+	esma = E_SELECTION_MODEL_ARRAY (object);
+
+	if (esma->eba) {
+		g_object_unref (esma->eba);
+		esma->eba = NULL;
+	}
+
+	/* Chain up to parent's dispose() method. */
+	G_OBJECT_CLASS (e_selection_model_array_parent_class)->dispose (object);
+}
+
+static void
+esma_get_property (GObject *object,
+                   guint property_id,
+                   GValue *value,
+                   GParamSpec *pspec)
+{
+	ESelectionModelArray *esma = E_SELECTION_MODEL_ARRAY (object);
+
+	switch (property_id) {
+	case PROP_CURSOR_ROW:
+		g_value_set_int (value, esma->cursor_row);
+		break;
+
+	case PROP_CURSOR_COL:
+		g_value_set_int (value, esma->cursor_col);
+		break;
+	}
+}
+
+static void
+esma_set_property (GObject *object,
+                   guint property_id,
+                   const GValue *value,
+                   GParamSpec *pspec)
+{
+	ESelectionModel *esm = E_SELECTION_MODEL (object);
+	ESelectionModelArray *esma = E_SELECTION_MODEL_ARRAY (object);
+
+	switch (property_id) {
+	case PROP_CURSOR_ROW:
+		e_selection_model_do_something (esm, g_value_get_int (value), esma->cursor_col, 0);
+		break;
+
+	case PROP_CURSOR_COL:
+		e_selection_model_do_something (esm, esma->cursor_row, g_value_get_int (value), 0);
+		break;
+	}
+}
+
+/**
+ * e_selection_model_is_row_selected
+ * @selection: #ESelectionModel to check
+ * @n: The row to check
+ *
+ * This routine calculates whether the given row is selected.
+ *
+ * Returns: %TRUE if the given row is selected
+ */
+static gboolean
+esma_is_row_selected (ESelectionModel *selection,
+                      gint n)
+{
+	ESelectionModelArray *esma = E_SELECTION_MODEL_ARRAY (selection);
+	if (esma->eba)
+		return e_bit_array_value_at (esma->eba, n);
+	else
+		return FALSE;
+}
+
+/**
+ * e_selection_model_foreach
+ * @selection: #ESelectionModel to traverse
+ * @callback: The callback function to call back.
+ * @closure: The closure
+ *
+ * This routine calls the given callback function once for each
+ * selected row, passing closure as the closure.
+ */
+static void
+esma_foreach (ESelectionModel *selection,
+              EForeachFunc callback,
+              gpointer closure)
+{
+	ESelectionModelArray *esma = E_SELECTION_MODEL_ARRAY (selection);
+	if (esma->eba)
+		e_bit_array_foreach (esma->eba, callback, closure);
+}
+
+/**
+ * e_selection_model_clear
+ * @selection: #ESelectionModel to clear
+ *
+ * This routine clears the selection to no rows selected.
+ */
+static void
+esma_clear (ESelectionModel *selection)
+{
+	ESelectionModelArray *esma = E_SELECTION_MODEL_ARRAY (selection);
+	if (esma->eba) {
+		g_object_unref (esma->eba);
+		esma->eba = NULL;
+	}
+	esma->cursor_row = -1;
+	esma->cursor_col = -1;
+	esma->cursor_row_sorted = -1;
+	esma->selected_row = -1;
+	esma->selected_range_end = -1;
+	e_selection_model_selection_changed (E_SELECTION_MODEL (esma));
+	e_selection_model_cursor_changed (E_SELECTION_MODEL (esma), -1, -1);
+}
+
+#define PART(x,n) (((x) & (0x01010101 << n)) >> n)
+#define SECTION(x, n) (((x) >> (n * 8)) & 0xff)
+
+/**
+ * e_selection_model_selected_count
+ * @selection: #ESelectionModel to count
+ *
+ * This routine calculates the number of rows selected.
+ *
+ * Returns: The number of rows selected in the given model.
+ */
+static gint
+esma_selected_count (ESelectionModel *selection)
+{
+	ESelectionModelArray *esma = E_SELECTION_MODEL_ARRAY (selection);
+	if (esma->eba)
+		return e_bit_array_selected_count (esma->eba);
+	else
+		return 0;
+}
+
+/**
+ * e_selection_model_select_all
+ * @selection: #ESelectionModel to select all
+ *
+ * This routine selects all the rows in the given
+ * #ESelectionModel.
+ */
+static void
+esma_select_all (ESelectionModel *selection)
+{
+	ESelectionModelArray *esma = E_SELECTION_MODEL_ARRAY (selection);
+
+	e_selection_model_array_confirm_row_count (esma);
+
+	e_bit_array_select_all (esma->eba);
+
+	esma->cursor_col = 0;
+	esma->cursor_row_sorted = 0;
+	esma->cursor_row = es_row_sorted_to_model (esma, esma->cursor_row_sorted);
+	esma->selection_start_row = esma->cursor_row;
+	esma->selected_row = -1;
+	esma->selected_range_end = -1;
+	e_selection_model_selection_changed (E_SELECTION_MODEL (esma));
+	e_selection_model_cursor_changed (E_SELECTION_MODEL (esma), 0, 0);
+}
+
+/**
+ * e_selection_model_invert_selection
+ * @selection: #ESelectionModel to invert
+ *
+ * This routine inverts all the rows in the given
+ * #ESelectionModel.
+ */
+static void
+esma_invert_selection (ESelectionModel *selection)
+{
+	ESelectionModelArray *esma = E_SELECTION_MODEL_ARRAY (selection);
+
+	e_selection_model_array_confirm_row_count (esma);
+
+	e_bit_array_invert_selection (esma->eba);
+
+	esma->cursor_col = -1;
+	esma->cursor_row = -1;
+	esma->cursor_row_sorted = -1;
+	esma->selection_start_row = 0;
+	esma->selected_row = -1;
+	esma->selected_range_end = -1;
+	e_selection_model_selection_changed (E_SELECTION_MODEL (esma));
+	e_selection_model_cursor_changed (E_SELECTION_MODEL (esma), -1, -1);
+}
+
+static gint
+esma_row_count (ESelectionModel *selection)
+{
+	ESelectionModelArray *esma = E_SELECTION_MODEL_ARRAY (selection);
+	e_selection_model_array_confirm_row_count (esma);
+	return e_bit_array_bit_count (esma->eba);
+}
+
+static void
+esma_change_one_row (ESelectionModel *selection,
+                     gint row,
+                     gboolean grow)
+{
+	ESelectionModelArray *esma = E_SELECTION_MODEL_ARRAY (selection);
+	e_selection_model_array_confirm_row_count (esma);
+	e_bit_array_change_one_row (esma->eba, row, grow);
+}
+
+static void
+esma_change_cursor (ESelectionModel *selection,
+                    gint row,
+                    gint col)
+{
+	ESelectionModelArray *esma;
+
+	g_return_if_fail (selection != NULL);
+	g_return_if_fail (E_IS_SELECTION_MODEL (selection));
+
+	esma = E_SELECTION_MODEL_ARRAY (selection);
+
+	esma->cursor_row = row;
+	esma->cursor_col = col;
+	esma->cursor_row_sorted = es_row_model_to_sorted (esma, esma->cursor_row);
+}
+
+static void
+esma_change_range (ESelectionModel *selection,
+                   gint start,
+                   gint end,
+                   gboolean grow)
+{
+	gint i;
+	ESelectionModelArray *esma = E_SELECTION_MODEL_ARRAY (selection);
+	if (start != end) {
+		if (selection->sorter && e_sorter_needs_sorting (selection->sorter)) {
+			for (i = start; i < end; i++) {
+				e_bit_array_change_one_row (esma->eba, e_sorter_sorted_to_model (selection->sorter, i), grow);
+			}
+		} else {
+			e_selection_model_array_confirm_row_count (esma);
+			e_bit_array_change_range (esma->eba, start, end, grow);
+		}
+	}
+}
+
+static gint
+esma_cursor_row (ESelectionModel *selection)
+{
+	ESelectionModelArray *esma = E_SELECTION_MODEL_ARRAY (selection);
+	return esma->cursor_row;
+}
+
+static gint
+esma_cursor_col (ESelectionModel *selection)
+{
+	ESelectionModelArray *esma = E_SELECTION_MODEL_ARRAY (selection);
+	return esma->cursor_col;
+}
+
+static void
+esma_real_select_single_row (ESelectionModel *selection,
+                             gint row)
+{
+	ESelectionModelArray *esma = E_SELECTION_MODEL_ARRAY (selection);
+
+	e_selection_model_array_confirm_row_count (esma);
+
+	e_bit_array_select_single_row (esma->eba, row);
+
+	esma->selection_start_row = row;
+	esma->selected_row = row;
+	esma->selected_range_end = row;
+}
+
+static void
+esma_select_single_row (ESelectionModel *selection,
+                        gint row)
+{
+	ESelectionModelArray *esma = E_SELECTION_MODEL_ARRAY (selection);
+	gint selected_row = esma->selected_row;
+	esma_real_select_single_row (selection, row);
+
+	if (selected_row != -1 && esma->eba && selected_row < e_bit_array_bit_count (esma->eba)) {
+		if (selected_row != row) {
+			e_selection_model_selection_row_changed (selection, selected_row);
+			e_selection_model_selection_row_changed (selection, row);
+		}
+	} else {
+		e_selection_model_selection_changed (selection);
+	}
+}
+
+static void
+esma_toggle_single_row (ESelectionModel *selection,
+                        gint row)
+{
+	ESelectionModelArray *esma = E_SELECTION_MODEL_ARRAY (selection);
+
+	e_selection_model_array_confirm_row_count (esma);
+	e_bit_array_toggle_single_row (esma->eba, row);
+
+	esma->selection_start_row = row;
+	esma->selected_row = -1;
+	esma->selected_range_end = -1;
+	e_selection_model_selection_row_changed (E_SELECTION_MODEL (esma), row);
+}
+
+static void
+esma_real_move_selection_end (ESelectionModel *selection,
+                              gint row)
+{
+	ESelectionModelArray *esma = E_SELECTION_MODEL_ARRAY (selection);
+	gint old_start;
+	gint old_end;
+	gint new_start;
+	gint new_end;
+	if (selection->sorter && e_sorter_needs_sorting (selection->sorter)) {
+		old_start = MIN (
+			e_sorter_model_to_sorted (selection->sorter, esma->selection_start_row),
+			e_sorter_model_to_sorted (selection->sorter, esma->cursor_row));
+		old_end = MAX (
+			e_sorter_model_to_sorted (selection->sorter, esma->selection_start_row),
+			e_sorter_model_to_sorted (selection->sorter, esma->cursor_row)) + 1;
+		new_start = MIN (
+			e_sorter_model_to_sorted (selection->sorter, esma->selection_start_row),
+			e_sorter_model_to_sorted (selection->sorter, row));
+		new_end = MAX (
+			e_sorter_model_to_sorted (selection->sorter, esma->selection_start_row),
+			e_sorter_model_to_sorted (selection->sorter, row)) + 1;
+	} else {
+		old_start = MIN (esma->selection_start_row, esma->cursor_row);
+		old_end = MAX (esma->selection_start_row, esma->cursor_row) + 1;
+		new_start = MIN (esma->selection_start_row, row);
+		new_end = MAX (esma->selection_start_row, row) + 1;
+	}
+	/* This wouldn't work nearly so smoothly if one end of the selection weren't held in place. */
+	if (old_start < new_start)
+		esma_change_range (selection, old_start, new_start, FALSE);
+	if (new_start < old_start)
+		esma_change_range (selection, new_start, old_start, TRUE);
+	if (old_end < new_end)
+		esma_change_range (selection, old_end, new_end, TRUE);
+	if (new_end < old_end)
+		esma_change_range (selection, new_end, old_end, FALSE);
+	esma->selected_row = -1;
+	esma->selected_range_end = -1;
+}
+
+static void
+esma_move_selection_end (ESelectionModel *selection,
+                         gint row)
+{
+	esma_real_move_selection_end (selection, row);
+	e_selection_model_selection_changed (selection);
+}
+
+static void
+esma_set_selection_end (ESelectionModel *selection,
+                        gint row)
+{
+	ESelectionModelArray *esma = E_SELECTION_MODEL_ARRAY (selection);
+	gint selected_range_end = esma->selected_range_end;
+	gint view_row = e_sorter_model_to_sorted (selection->sorter, row);
+
+	esma_real_select_single_row (selection, esma->selection_start_row);
+	esma->cursor_row = esma->selection_start_row;
+	esma->cursor_row_sorted = es_row_model_to_sorted (esma, esma->cursor_row);
+	esma_real_move_selection_end (selection, row);
+
+	esma->selected_range_end = view_row;
+	if (selected_range_end != -1 && view_row != -1) {
+		if (selected_range_end == view_row - 1 ||
+		    selected_range_end == view_row + 1) {
+			e_selection_model_selection_row_changed (selection, selected_range_end);
+			e_selection_model_selection_row_changed (selection, view_row);
+		}
+	}
+	e_selection_model_selection_changed (selection);
+}
+
+gint
+e_selection_model_array_get_row_count (ESelectionModelArray *esma)
+{
+	g_return_val_if_fail (esma != NULL, 0);
+	g_return_val_if_fail (E_IS_SELECTION_MODEL_ARRAY (esma), 0);
+
+	if (E_SELECTION_MODEL_ARRAY_GET_CLASS (esma)->get_row_count)
+		return E_SELECTION_MODEL_ARRAY_GET_CLASS (esma)->get_row_count (esma);
+	else
+		return 0;
+}
+
+static void
+e_selection_model_array_init (ESelectionModelArray *esma)
+{
+	esma->eba = NULL;
+	esma->selection_start_row = 0;
+	esma->cursor_row = -1;
+	esma->cursor_col = -1;
+	esma->cursor_row_sorted = -1;
+
+	esma->selected_row = -1;
+	esma->selected_range_end = -1;
+}
+
+static void
+e_selection_model_array_class_init (ESelectionModelArrayClass *class)
+{
+	GObjectClass *object_class;
+	ESelectionModelClass *esm_class;
+
+	object_class = G_OBJECT_CLASS (class);
+	esm_class = E_SELECTION_MODEL_CLASS (class);
+
+	object_class->dispose = esma_dispose;
+	object_class->get_property = esma_get_property;
+	object_class->set_property = esma_set_property;
+
+	esm_class->is_row_selected    = esma_is_row_selected;
+	esm_class->foreach            = esma_foreach;
+	esm_class->clear              = esma_clear;
+	esm_class->selected_count     = esma_selected_count;
+	esm_class->select_all         = esma_select_all;
+	esm_class->invert_selection   = esma_invert_selection;
+	esm_class->row_count          = esma_row_count;
+
+	esm_class->change_one_row     = esma_change_one_row;
+	esm_class->change_cursor      = esma_change_cursor;
+	esm_class->cursor_row         = esma_cursor_row;
+	esm_class->cursor_col         = esma_cursor_col;
+
+	esm_class->select_single_row  = esma_select_single_row;
+	esm_class->toggle_single_row  = esma_toggle_single_row;
+	esm_class->move_selection_end = esma_move_selection_end;
+	esm_class->set_selection_end  = esma_set_selection_end;
+
+	class->get_row_count          = NULL;
+
+	g_object_class_install_property (
+		object_class,
+		PROP_CURSOR_ROW,
+		g_param_spec_int (
+			"cursor_row",
+			"Cursor Row",
+			NULL,
+			0, G_MAXINT, 0,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_CURSOR_COL,
+		g_param_spec_int (
+			"cursor_col",
+			"Cursor Column",
+			NULL,
+			0, G_MAXINT, 0,
+			G_PARAM_READWRITE));
+}
+
diff --git a/e-util/e-selection-model-array.h b/e-util/e-selection-model-array.h
new file mode 100644
index 0000000..7292a33
--- /dev/null
+++ b/e-util/e-selection-model-array.h
@@ -0,0 +1,95 @@
+/*
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef _E_SELECTION_MODEL_ARRAY_H_
+#define _E_SELECTION_MODEL_ARRAY_H_
+
+#include <e-util/e-bit-array.h>
+#include <e-util/e-selection-model.h>
+
+G_BEGIN_DECLS
+
+#define E_SELECTION_MODEL_ARRAY_TYPE        (e_selection_model_array_get_type ())
+#define E_SELECTION_MODEL_ARRAY(o)          (G_TYPE_CHECK_INSTANCE_CAST ((o), E_SELECTION_MODEL_ARRAY_TYPE, ESelectionModelArray))
+#define E_SELECTION_MODEL_ARRAY_CLASS(k)    (G_TYPE_CHECK_CLASS_CAST((k), E_SELECTION_MODEL_ARRAY_TYPE, ESelectionModelArrayClass))
+#define E_IS_SELECTION_MODEL_ARRAY(o)       (G_TYPE_CHECK_INSTANCE_TYPE ((o), E_SELECTION_MODEL_ARRAY_TYPE))
+#define E_IS_SELECTION_MODEL_ARRAY_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), E_SELECTION_MODEL_ARRAY_TYPE))
+#define E_SELECTION_MODEL_ARRAY_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), E_SELECTION_MODEL_ARRAY_TYPE, ESelectionModelArrayClass))
+
+typedef struct {
+	ESelectionModel base;
+
+	EBitArray *eba;
+
+	gint cursor_row;
+	gint cursor_col;
+	gint selection_start_row;
+	gint cursor_row_sorted; /* cursor_row passed through base::sorter if necessary */
+
+	guint model_changed_id;
+	guint model_row_inserted_id, model_row_deleted_id;
+
+	/* Anything other than -1 means that the selection is a single
+	 * row.  This being -1 does not impart any information. */
+	gint        selected_row;
+	/* Anything other than -1 means that the selection is a all
+	 * rows between selection_start_path and cursor_path where
+	 * selected_range_end is the rwo number of cursor_path.  This
+	 * being -1 does not impart any information. */
+	gint        selected_range_end;
+
+	guint frozen : 1;
+	guint selection_model_changed : 1;
+	guint group_info_changed : 1;
+} ESelectionModelArray;
+
+typedef struct {
+	ESelectionModelClass parent_class;
+
+	gint (*get_row_count)     (ESelectionModelArray *selection);
+} ESelectionModelArrayClass;
+
+GType    e_selection_model_array_get_type           (void);
+
+/* Protected Functions */
+void     e_selection_model_array_insert_rows        (ESelectionModelArray *esm,
+						     gint                   row,
+						     gint                   count);
+void     e_selection_model_array_delete_rows        (ESelectionModelArray *esm,
+						     gint                   row,
+						     gint                   count);
+void     e_selection_model_array_move_row           (ESelectionModelArray *esm,
+						     gint                   old_row,
+						     gint                   new_row);
+void     e_selection_model_array_confirm_row_count  (ESelectionModelArray *esm);
+
+/* Protected Virtual Function */
+gint     e_selection_model_array_get_row_count      (ESelectionModelArray *esm);
+
+G_END_DECLS
+
+#endif /* _E_SELECTION_MODEL_ARRAY_H_ */
diff --git a/e-util/e-selection-model-simple.c b/e-util/e-selection-model-simple.c
new file mode 100644
index 0000000..f7123dd
--- /dev/null
+++ b/e-util/e-selection-model-simple.c
@@ -0,0 +1,117 @@
+/*
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-selection-model-array.h"
+#include "e-selection-model-simple.h"
+
+static gint esms_get_row_count (ESelectionModelArray *esma);
+
+G_DEFINE_TYPE (
+	ESelectionModelSimple,
+	e_selection_model_simple,
+	E_SELECTION_MODEL_ARRAY_TYPE)
+
+static void
+e_selection_model_simple_init (ESelectionModelSimple *selection)
+{
+	selection->row_count = 0;
+}
+
+static void
+e_selection_model_simple_class_init (ESelectionModelSimpleClass *class)
+{
+	ESelectionModelArrayClass *esma_class;
+
+	esma_class = E_SELECTION_MODEL_ARRAY_CLASS (class);
+	esma_class->get_row_count = esms_get_row_count;
+}
+
+/**
+ * e_selection_model_simple_new
+ *
+ * This routine creates a new #ESelectionModelSimple.
+ *
+ * Returns: The new #ESelectionModelSimple.
+ */
+ESelectionModelSimple *
+e_selection_model_simple_new (void)
+{
+	return g_object_new (E_SELECTION_MODEL_SIMPLE_TYPE, NULL);
+}
+
+void
+e_selection_model_simple_set_row_count (ESelectionModelSimple *esms,
+                                        gint row_count)
+{
+	if (esms->row_count != row_count) {
+		ESelectionModelArray *esma = E_SELECTION_MODEL_ARRAY (esms);
+		if (esma->eba)
+			g_object_unref (esma->eba);
+		esma->eba = NULL;
+		esma->selected_row = -1;
+		esma->selected_range_end = -1;
+	}
+
+	esms->row_count = row_count;
+}
+
+static gint
+esms_get_row_count (ESelectionModelArray *esma)
+{
+	ESelectionModelSimple *esms = E_SELECTION_MODEL_SIMPLE (esma);
+
+	return esms->row_count;
+}
+
+void
+e_selection_model_simple_insert_rows (ESelectionModelSimple *esms,
+                                      gint row,
+                                      gint count)
+{
+	esms->row_count += count;
+	e_selection_model_array_insert_rows (
+		E_SELECTION_MODEL_ARRAY (esms), row, count);
+}
+
+void
+e_selection_model_simple_delete_rows (ESelectionModelSimple *esms,
+                                      gint row,
+                                      gint count)
+{
+	esms->row_count -= count;
+	e_selection_model_array_delete_rows (
+		E_SELECTION_MODEL_ARRAY (esms), row, count);
+}
+
+void
+e_selection_model_simple_move_row (ESelectionModelSimple *esms,
+                                   gint old_row,
+                                   gint new_row)
+{
+	e_selection_model_array_move_row (
+		E_SELECTION_MODEL_ARRAY (esms), old_row, new_row);
+}
diff --git a/e-util/e-selection-model-simple.h b/e-util/e-selection-model-simple.h
new file mode 100644
index 0000000..b4551dd
--- /dev/null
+++ b/e-util/e-selection-model-simple.h
@@ -0,0 +1,70 @@
+/*
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef _E_SELECTION_MODEL_SIMPLE_H_
+#define _E_SELECTION_MODEL_SIMPLE_H_
+
+#include <e-util/e-selection-model-array.h>
+
+G_BEGIN_DECLS
+
+#define E_SELECTION_MODEL_SIMPLE_TYPE        (e_selection_model_simple_get_type ())
+#define E_SELECTION_MODEL_SIMPLE(o)          (G_TYPE_CHECK_INSTANCE_CAST ((o), E_SELECTION_MODEL_SIMPLE_TYPE, ESelectionModelSimple))
+#define E_SELECTION_MODEL_SIMPLE_CLASS(k)    (G_TYPE_CHECK_CLASS_CAST((k), E_SELECTION_MODEL_SIMPLE_TYPE, ESelectionModelSimpleClass))
+#define E_IS_SELECTION_MODEL_SIMPLE(o)       (G_TYPE_CHECK_INSTANCE_TYPE ((o), E_SELECTION_MODEL_SIMPLE_TYPE))
+#define E_IS_SELECTION_MODEL_SIMPLE_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), E_SELECTION_MODEL_SIMPLE_TYPE))
+
+typedef struct {
+	ESelectionModelArray parent;
+
+	gint row_count;
+} ESelectionModelSimple;
+
+typedef struct {
+	ESelectionModelArrayClass parent_class;
+} ESelectionModelSimpleClass;
+
+GType                  e_selection_model_simple_get_type       (void);
+ESelectionModelSimple *e_selection_model_simple_new            (void);
+
+void                   e_selection_model_simple_insert_rows     (ESelectionModelSimple *esms,
+								 gint                    row,
+								 gint count);
+void                   e_selection_model_simple_delete_rows     (ESelectionModelSimple *esms,
+								 gint                    row,
+								 gint count);
+void                   e_selection_model_simple_move_row       (ESelectionModelSimple *esms,
+								gint                    old_row,
+								gint                    new_row);
+
+void                   e_selection_model_simple_set_row_count  (ESelectionModelSimple *selection,
+								gint                    row_count);
+
+G_END_DECLS
+
+#endif /* _E_SELECTION_MODEL_SIMPLE_H_ */
+
diff --git a/e-util/e-selection-model.c b/e-util/e-selection-model.c
new file mode 100644
index 0000000..4c553f4
--- /dev/null
+++ b/e-util/e-selection-model.c
@@ -0,0 +1,813 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-selection-model.h"
+
+#include <glib/gi18n.h>
+#include <gdk/gdkkeysyms.h>
+
+#include "e-marshal.h"
+
+G_DEFINE_TYPE (
+	ESelectionModel,
+	e_selection_model,
+	G_TYPE_OBJECT)
+
+enum {
+	CURSOR_CHANGED,
+	CURSOR_ACTIVATED,
+	SELECTION_CHANGED,
+	SELECTION_ROW_CHANGED,
+	LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0, };
+
+enum {
+	PROP_0,
+	PROP_SORTER,
+	PROP_SELECTION_MODE,
+	PROP_CURSOR_MODE
+};
+
+inline static void
+add_sorter (ESelectionModel *esm,
+            ESorter *sorter)
+{
+	esm->sorter = sorter;
+	if (sorter) {
+		g_object_ref (sorter);
+	}
+}
+
+inline static void
+drop_sorter (ESelectionModel *esm)
+{
+	if (esm->sorter) {
+		g_object_unref (esm->sorter);
+	}
+	esm->sorter = NULL;
+}
+
+static void
+esm_dispose (GObject *object)
+{
+	ESelectionModel *esm;
+
+	esm = E_SELECTION_MODEL (object);
+
+	drop_sorter (esm);
+
+	/* Chain up to parent's dispose() method. */
+	G_OBJECT_CLASS (e_selection_model_parent_class)->dispose (object);
+}
+
+static void
+esm_get_property (GObject *object,
+                  guint property_id,
+                  GValue *value,
+                  GParamSpec *pspec)
+{
+	ESelectionModel *esm = E_SELECTION_MODEL (object);
+
+	switch (property_id) {
+		case PROP_SORTER:
+			g_value_set_object (value, esm->sorter);
+			break;
+
+		case PROP_SELECTION_MODE:
+			g_value_set_int (value, esm->mode);
+			break;
+
+		case PROP_CURSOR_MODE:
+			g_value_set_int (value, esm->cursor_mode);
+			break;
+	}
+}
+
+static void
+esm_set_property (GObject *object,
+                  guint property_id,
+                  const GValue *value,
+                  GParamSpec *pspec)
+{
+	ESelectionModel *esm = E_SELECTION_MODEL (object);
+
+	switch (property_id) {
+		case PROP_SORTER:
+			drop_sorter (esm);
+			add_sorter (
+				esm, g_value_get_object (value) ?
+				E_SORTER (g_value_get_object (value)) : NULL);
+			break;
+
+		case PROP_SELECTION_MODE:
+			esm->mode = g_value_get_int (value);
+			if (esm->mode == GTK_SELECTION_SINGLE) {
+				gint cursor_row = e_selection_model_cursor_row (esm);
+				gint cursor_col = e_selection_model_cursor_col (esm);
+				e_selection_model_do_something (esm, cursor_row, cursor_col, 0);
+			}
+			break;
+
+		case PROP_CURSOR_MODE:
+			esm->cursor_mode = g_value_get_int (value);
+			break;
+	}
+}
+
+static void
+e_selection_model_init (ESelectionModel *selection)
+{
+	selection->mode = GTK_SELECTION_MULTIPLE;
+	selection->cursor_mode = E_CURSOR_SIMPLE;
+	selection->old_selection = -1;
+}
+
+static void
+e_selection_model_class_init (ESelectionModelClass *class)
+{
+	GObjectClass *object_class;
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->dispose = esm_dispose;
+	object_class->get_property = esm_get_property;
+	object_class->set_property = esm_set_property;
+
+	signals[CURSOR_CHANGED] = g_signal_new (
+		"cursor_changed",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ESelectionModelClass, cursor_changed),
+		NULL, NULL,
+		e_marshal_NONE__INT_INT,
+		G_TYPE_NONE, 2,
+		G_TYPE_INT,
+		G_TYPE_INT);
+
+	signals[CURSOR_ACTIVATED] = g_signal_new (
+		"cursor_activated",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ESelectionModelClass, cursor_activated),
+		NULL, NULL,
+		e_marshal_NONE__INT_INT,
+		G_TYPE_NONE, 2,
+		G_TYPE_INT,
+		G_TYPE_INT);
+
+	signals[SELECTION_CHANGED] = g_signal_new (
+		"selection_changed",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ESelectionModelClass, selection_changed),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__VOID,
+		G_TYPE_NONE, 0);
+
+	signals[SELECTION_ROW_CHANGED] = g_signal_new (
+		"selection_row_changed",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ESelectionModelClass, selection_row_changed),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__INT,
+		G_TYPE_NONE, 1,
+		G_TYPE_INT);
+
+	g_object_class_install_property (
+		object_class,
+		PROP_SORTER,
+		g_param_spec_object (
+			"sorter",
+			"Sorter",
+			NULL,
+			E_SORTER_TYPE,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_SELECTION_MODE,
+		g_param_spec_int (
+			"selection_mode",
+			"Selection Mode",
+			NULL,
+			GTK_SELECTION_NONE,
+			GTK_SELECTION_MULTIPLE,
+			GTK_SELECTION_SINGLE,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_CURSOR_MODE,
+		g_param_spec_int (
+			"cursor_mode",
+			"Cursor Mode",
+			NULL,
+			E_CURSOR_LINE,
+			E_CURSOR_SPREADSHEET,
+			E_CURSOR_LINE,
+			G_PARAM_READWRITE));
+}
+
+/**
+ * e_selection_model_is_row_selected
+ * @selection: #ESelectionModel to check
+ * @n: The row to check
+ *
+ * This routine calculates whether the given row is selected.
+ *
+ * Returns: %TRUE if the given row is selected
+ */
+gboolean
+e_selection_model_is_row_selected (ESelectionModel *selection,
+                                   gint n)
+{
+	ESelectionModelClass *class;
+
+	g_return_val_if_fail (E_IS_SELECTION_MODEL (selection), FALSE);
+
+	class = E_SELECTION_MODEL_GET_CLASS (selection);
+	g_return_val_if_fail (class->is_row_selected != NULL, FALSE);
+
+	return class->is_row_selected (selection, n);
+}
+
+/**
+ * e_selection_model_foreach
+ * @selection: #ESelectionModel to traverse
+ * @callback: The callback function to call back.
+ * @closure: The closure
+ *
+ * This routine calls the given callback function once for each
+ * selected row, passing closure as the closure.
+ */
+void
+e_selection_model_foreach (ESelectionModel *selection,
+                           EForeachFunc callback,
+                           gpointer closure)
+{
+	ESelectionModelClass *class;
+
+	g_return_if_fail (E_IS_SELECTION_MODEL (selection));
+	g_return_if_fail (callback != NULL);
+
+	class = E_SELECTION_MODEL_GET_CLASS (selection);
+	g_return_if_fail (class->foreach != NULL);
+
+	class->foreach (selection, callback, closure);
+}
+
+/**
+ * e_selection_model_clear
+ * @selection: #ESelectionModel to clear
+ *
+ * This routine clears the selection to no rows selected.
+ */
+void
+e_selection_model_clear (ESelectionModel *selection)
+{
+	ESelectionModelClass *class;
+
+	g_return_if_fail (E_IS_SELECTION_MODEL (selection));
+
+	class = E_SELECTION_MODEL_GET_CLASS (selection);
+	g_return_if_fail (class->clear != NULL);
+
+	class->clear (selection);
+}
+
+/**
+ * e_selection_model_selected_count
+ * @selection: #ESelectionModel to count
+ *
+ * This routine calculates the number of rows selected.
+ *
+ * Returns: The number of rows selected in the given model.
+ */
+gint
+e_selection_model_selected_count (ESelectionModel *selection)
+{
+	ESelectionModelClass *class;
+
+	g_return_val_if_fail (E_IS_SELECTION_MODEL (selection), 0);
+
+	class = E_SELECTION_MODEL_GET_CLASS (selection);
+	g_return_val_if_fail (class->selected_count != NULL, 0);
+
+	return class->selected_count (selection);
+}
+
+/**
+ * e_selection_model_select_all
+ * @selection: #ESelectionModel to select all
+ *
+ * This routine selects all the rows in the given
+ * #ESelectionModel.
+ */
+void
+e_selection_model_select_all (ESelectionModel *selection)
+{
+	ESelectionModelClass *class;
+
+	g_return_if_fail (E_IS_SELECTION_MODEL (selection));
+
+	class = E_SELECTION_MODEL_GET_CLASS (selection);
+	g_return_if_fail (class->select_all != NULL);
+
+	class->select_all (selection);
+}
+
+/**
+ * e_selection_model_invert_selection
+ * @selection: #ESelectionModel to invert
+ *
+ * This routine inverts all the rows in the given
+ * #ESelectionModel.
+ */
+void
+e_selection_model_invert_selection (ESelectionModel *selection)
+{
+	ESelectionModelClass *class;
+
+	g_return_if_fail (E_IS_SELECTION_MODEL (selection));
+
+	class = E_SELECTION_MODEL_GET_CLASS (selection);
+	g_return_if_fail (class->invert_selection != NULL);
+
+	class->invert_selection (selection);
+}
+
+gint
+e_selection_model_row_count (ESelectionModel *selection)
+{
+	ESelectionModelClass *class;
+
+	g_return_val_if_fail (E_IS_SELECTION_MODEL (selection), 0);
+
+	class = E_SELECTION_MODEL_GET_CLASS (selection);
+	g_return_val_if_fail (class->row_count != NULL, 0);
+
+	return class->row_count (selection);
+}
+
+void
+e_selection_model_change_one_row (ESelectionModel *selection,
+                                  gint row,
+                                  gboolean grow)
+{
+	ESelectionModelClass *class;
+
+	g_return_if_fail (E_IS_SELECTION_MODEL (selection));
+
+	class = E_SELECTION_MODEL_GET_CLASS (selection);
+	g_return_if_fail (class->change_one_row != NULL);
+
+	return class->change_one_row (selection, row, grow);
+}
+
+void
+e_selection_model_change_cursor (ESelectionModel *selection,
+                                 gint row,
+                                 gint col)
+{
+	ESelectionModelClass *class;
+
+	g_return_if_fail (E_IS_SELECTION_MODEL (selection));
+
+	class = E_SELECTION_MODEL_GET_CLASS (selection);
+	g_return_if_fail (class->change_cursor != NULL);
+
+	class->change_cursor (selection, row, col);
+}
+
+gint
+e_selection_model_cursor_row (ESelectionModel *selection)
+{
+	ESelectionModelClass *class;
+
+	g_return_val_if_fail (E_IS_SELECTION_MODEL (selection), -1);
+
+	class = E_SELECTION_MODEL_GET_CLASS (selection);
+	g_return_val_if_fail (class->cursor_row != NULL, -1);
+
+	return class->cursor_row (selection);
+}
+
+gint
+e_selection_model_cursor_col (ESelectionModel *selection)
+{
+	ESelectionModelClass *class;
+
+	g_return_val_if_fail (E_IS_SELECTION_MODEL (selection), -1);
+
+	class = E_SELECTION_MODEL_GET_CLASS (selection);
+	g_return_val_if_fail (class->cursor_col != NULL, -1);
+
+	return class->cursor_col (selection);
+}
+
+void
+e_selection_model_select_single_row (ESelectionModel *selection,
+                                     gint row)
+{
+	ESelectionModelClass *class;
+
+	g_return_if_fail (E_IS_SELECTION_MODEL (selection));
+
+	class = E_SELECTION_MODEL_GET_CLASS (selection);
+	g_return_if_fail (class->select_single_row != NULL);
+
+	class->select_single_row (selection, row);
+}
+
+void
+e_selection_model_toggle_single_row (ESelectionModel *selection,
+                                     gint row)
+{
+	ESelectionModelClass *class;
+
+	g_return_if_fail (E_IS_SELECTION_MODEL (selection));
+
+	class = E_SELECTION_MODEL_GET_CLASS (selection);
+	g_return_if_fail (class->toggle_single_row != NULL);
+
+	class->toggle_single_row (selection, row);
+}
+
+void
+e_selection_model_move_selection_end (ESelectionModel *selection,
+                                      gint row)
+{
+	ESelectionModelClass *class;
+
+	g_return_if_fail (E_IS_SELECTION_MODEL (selection));
+
+	class = E_SELECTION_MODEL_GET_CLASS (selection);
+	g_return_if_fail (class->move_selection_end != NULL);
+
+	class->move_selection_end (selection, row);
+}
+
+void
+e_selection_model_set_selection_end (ESelectionModel *selection,
+                                     gint row)
+{
+	ESelectionModelClass *class;
+
+	g_return_if_fail (E_IS_SELECTION_MODEL (selection));
+
+	class = E_SELECTION_MODEL_GET_CLASS (selection);
+	g_return_if_fail (class->set_selection_end != NULL);
+
+	class->set_selection_end (selection, row);
+}
+
+/**
+ * e_selection_model_do_something
+ * @selection: #ESelectionModel to do something to.
+ * @row: The row to do something in.
+ * @col: The col to do something in.
+ * @state: The state in which to do something.
+ *
+ * This routine does whatever is appropriate as if the user clicked
+ * the mouse in the given row and column.
+ */
+void
+e_selection_model_do_something (ESelectionModel *selection,
+                                guint row,
+                                guint col,
+                                GdkModifierType state)
+{
+	gint shift_p = state & GDK_SHIFT_MASK;
+	gint ctrl_p = state & GDK_CONTROL_MASK;
+	gint row_count;
+
+	g_return_if_fail (E_IS_SELECTION_MODEL (selection));
+
+	selection->old_selection = -1;
+
+	if (row == -1 && col != -1)
+		row = 0;
+	if (col == -1 && row != -1)
+		col = 0;
+
+	row_count = e_selection_model_row_count (selection);
+	if (row_count >= 0 && row < row_count) {
+		switch (selection->mode) {
+		case GTK_SELECTION_SINGLE:
+			e_selection_model_select_single_row (selection, row);
+			break;
+		case GTK_SELECTION_BROWSE:
+		case GTK_SELECTION_MULTIPLE:
+			if (shift_p) {
+				e_selection_model_set_selection_end (selection, row);
+			} else {
+				if (ctrl_p) {
+					e_selection_model_toggle_single_row (selection, row);
+				} else {
+					e_selection_model_select_single_row (selection, row);
+				}
+			}
+			break;
+		default:
+			g_return_if_reached ();
+			break;
+		}
+		e_selection_model_change_cursor (selection, row, col);
+		g_signal_emit (
+			selection,
+			signals[CURSOR_CHANGED], 0,
+			row, col);
+		g_signal_emit (
+			selection,
+			signals[CURSOR_ACTIVATED], 0,
+			row, col);
+	}
+}
+
+/**
+ * e_selection_model_maybe_do_something
+ * @selection: #ESelectionModel to do something to.
+ * @row: The row to do something in.
+ * @col: The col to do something in.
+ * @state: The state in which to do something.
+ *
+ * If this row is selected, this routine just moves the cursor row and
+ * column.  Otherwise, it does the same thing as
+ * e_selection_model_do_something().  This is for being used on
+ * right clicks and other events where if the user hit the selection,
+ * they don't want it to change.
+ */
+gboolean
+e_selection_model_maybe_do_something (ESelectionModel *selection,
+                                      guint row,
+                                      guint col,
+                                      GdkModifierType state)
+{
+	g_return_val_if_fail (E_IS_SELECTION_MODEL (selection), FALSE);
+
+	selection->old_selection = -1;
+
+	if (e_selection_model_is_row_selected (selection, row)) {
+		e_selection_model_change_cursor (selection, row, col);
+		g_signal_emit (
+			selection,
+			signals[CURSOR_CHANGED], 0,
+			row, col);
+		return FALSE;
+	} else {
+		e_selection_model_do_something (selection, row, col, state);
+		return TRUE;
+	}
+}
+
+void
+e_selection_model_right_click_down (ESelectionModel *selection,
+                                    guint row,
+                                    guint col,
+                                    GdkModifierType state)
+{
+	g_return_if_fail (E_IS_SELECTION_MODEL (selection));
+
+	if (selection->mode == GTK_SELECTION_SINGLE) {
+		selection->old_selection =
+			e_selection_model_cursor_row (selection);
+		e_selection_model_select_single_row (selection, row);
+	} else {
+		e_selection_model_maybe_do_something (
+			selection, row, col, state);
+	}
+}
+
+void
+e_selection_model_right_click_up (ESelectionModel *selection)
+{
+	g_return_if_fail (E_IS_SELECTION_MODEL (selection));
+
+	if (selection->mode != GTK_SELECTION_SINGLE)
+		return;
+
+	if (selection->old_selection == -1)
+		return;
+
+	e_selection_model_select_single_row (
+		selection, selection->old_selection);
+}
+
+void
+e_selection_model_select_as_key_press (ESelectionModel *selection,
+                                       guint row,
+                                       guint col,
+                                       GdkModifierType state)
+{
+	gint cursor_activated = TRUE;
+
+	gint shift_p = state & GDK_SHIFT_MASK;
+	gint ctrl_p = state & GDK_CONTROL_MASK;
+
+	g_return_if_fail (E_IS_SELECTION_MODEL (selection));
+
+	selection->old_selection = -1;
+
+	switch (selection->mode) {
+	case GTK_SELECTION_BROWSE:
+	case GTK_SELECTION_MULTIPLE:
+		if (shift_p) {
+			e_selection_model_set_selection_end (selection, row);
+		} else if (!ctrl_p) {
+			e_selection_model_select_single_row (selection, row);
+		} else
+			cursor_activated = FALSE;
+		break;
+	case GTK_SELECTION_SINGLE:
+		e_selection_model_select_single_row (selection, row);
+		break;
+	default:
+		g_return_if_reached ();
+		break;
+	}
+	if (row != -1) {
+		e_selection_model_change_cursor (selection, row, col);
+		g_signal_emit (
+			selection,
+			signals[CURSOR_CHANGED], 0,
+			row, col);
+		if (cursor_activated)
+			g_signal_emit (
+				selection,
+				signals[CURSOR_ACTIVATED], 0,
+				row, col);
+	}
+}
+
+static gint
+move_selection (ESelectionModel *selection,
+                gboolean up,
+                GdkModifierType state)
+{
+	gint row = e_selection_model_cursor_row (selection);
+	gint col = e_selection_model_cursor_col (selection);
+	gint row_count;
+
+	/* there is no selected row when row is -1 */
+	if (row != -1)
+		row = e_sorter_model_to_sorted (selection->sorter, row);
+
+	if (up)
+		row--;
+	else
+		row++;
+	if (row < 0)
+		row = 0;
+	row_count = e_selection_model_row_count (selection);
+	if (row >= row_count)
+		row = row_count - 1;
+	row = e_sorter_sorted_to_model (selection->sorter, row);
+
+	e_selection_model_select_as_key_press (selection, row, col, state);
+	return TRUE;
+}
+
+/**
+ * e_selection_model_key_press
+ * @selection: #ESelectionModel to affect.
+ * @key: The event.
+ *
+ * This routine does whatever is appropriate as if the user pressed
+ * the given key.
+ *
+ * Returns: %TRUE if the #ESelectionModel used the key.
+ */
+gboolean
+e_selection_model_key_press (ESelectionModel *selection,
+                             GdkEventKey *key)
+{
+	g_return_val_if_fail (E_IS_SELECTION_MODEL (selection), FALSE);
+	g_return_val_if_fail (key != NULL, FALSE);
+
+	selection->old_selection = -1;
+
+	switch (key->keyval) {
+	case GDK_KEY_Up:
+	case GDK_KEY_KP_Up:
+		return move_selection (selection, TRUE, key->state);
+	case GDK_KEY_Down:
+	case GDK_KEY_KP_Down:
+		return move_selection (selection, FALSE, key->state);
+	case GDK_KEY_space:
+	case GDK_KEY_KP_Space:
+		if (selection->mode != GTK_SELECTION_SINGLE) {
+			gint row = e_selection_model_cursor_row (selection);
+			gint col = e_selection_model_cursor_col (selection);
+			if (row == -1)
+				break;
+
+			e_selection_model_toggle_single_row (selection, row);
+			g_signal_emit (
+				selection,
+				signals[CURSOR_ACTIVATED], 0,
+				row, col);
+			return TRUE;
+		}
+		break;
+	case GDK_KEY_Return:
+	case GDK_KEY_KP_Enter:
+		if (selection->mode != GTK_SELECTION_SINGLE) {
+			gint row = e_selection_model_cursor_row (selection);
+			gint col = e_selection_model_cursor_col (selection);
+			e_selection_model_select_single_row (selection, row);
+			g_signal_emit (
+				selection,
+				signals[CURSOR_ACTIVATED], 0,
+				row, col);
+			return TRUE;
+		}
+		break;
+	case GDK_KEY_Home:
+	case GDK_KEY_KP_Home:
+		if (selection->cursor_mode == E_CURSOR_LINE) {
+			gint row = 0;
+			gint cursor_col = e_selection_model_cursor_col (selection);
+
+			row = e_sorter_sorted_to_model (selection->sorter, row);
+			e_selection_model_select_as_key_press (
+				selection, row, cursor_col, key->state);
+			return TRUE;
+		}
+		break;
+	case GDK_KEY_End:
+	case GDK_KEY_KP_End:
+		if (selection->cursor_mode == E_CURSOR_LINE) {
+			gint row = e_selection_model_row_count (selection) - 1;
+			gint cursor_col = e_selection_model_cursor_col (selection);
+
+			row = e_sorter_sorted_to_model (selection->sorter, row);
+			e_selection_model_select_as_key_press (
+				selection, row, cursor_col, key->state);
+			return TRUE;
+		}
+		break;
+	}
+	return FALSE;
+}
+
+void
+e_selection_model_cursor_changed (ESelectionModel *selection,
+                                  gint row,
+                                  gint col)
+{
+	g_return_if_fail (E_IS_SELECTION_MODEL (selection));
+
+	g_signal_emit (selection, signals[CURSOR_CHANGED], 0, row, col);
+}
+
+void
+e_selection_model_cursor_activated (ESelectionModel *selection,
+                                    gint row,
+                                    gint col)
+{
+	g_return_if_fail (E_IS_SELECTION_MODEL (selection));
+
+	g_signal_emit (selection, signals[CURSOR_ACTIVATED], 0, row, col);
+}
+
+void
+e_selection_model_selection_changed (ESelectionModel *selection)
+{
+	g_return_if_fail (E_IS_SELECTION_MODEL (selection));
+
+	g_signal_emit (selection, signals[SELECTION_CHANGED], 0);
+}
+
+void
+e_selection_model_selection_row_changed (ESelectionModel *selection,
+                                         gint row)
+{
+	g_return_if_fail (E_IS_SELECTION_MODEL (selection));
+
+	g_signal_emit (selection, signals[SELECTION_ROW_CHANGED], 0, row);
+}
diff --git a/e-util/e-selection-model.h b/e-util/e-selection-model.h
new file mode 100644
index 0000000..1d59e28
--- /dev/null
+++ b/e-util/e-selection-model.h
@@ -0,0 +1,209 @@
+/*
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_SELECTION_MODEL_H
+#define E_SELECTION_MODEL_H
+
+#include <gtk/gtk.h>
+#include <e-util/e-sorter.h>
+
+/* Standard GObject macros */
+#define E_TYPE_SELECTION_MODEL \
+	(e_selection_model_get_type ())
+#define E_SELECTION_MODEL(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_SELECTION_MODEL, ESelectionModel))
+#define E_SELECTION_MODEL_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_SELECTION_MODEL, ESelectionModelClass))
+#define E_IS_SELECTION_MODEL(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_SELECTION_MODEL))
+#define E_IS_SELECTION_MODEL_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_SELECTION_MODEL))
+#define E_SELECTION_MODEL_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_SELECTION_MODEL, ESelectionModelClass))
+
+G_BEGIN_DECLS
+
+#ifndef _E_FOREACH_FUNC_H_
+#define _E_FOREACH_FUNC_H_
+typedef void (*EForeachFunc) (gint model_row,
+			      gpointer closure);
+#endif
+
+typedef struct _ESelectionModel ESelectionModel;
+typedef struct _ESelectionModelClass ESelectionModelClass;
+
+/* list selection modes */
+typedef enum {
+	E_CURSOR_LINE,
+	E_CURSOR_SIMPLE,
+	E_CURSOR_SPREADSHEET
+} ECursorMode;
+
+struct _ESelectionModel {
+	GObject parent;
+
+	ESorter *sorter;
+
+	GtkSelectionMode mode;
+	ECursorMode cursor_mode;
+
+	gint old_selection;
+};
+
+struct _ESelectionModelClass {
+	GObjectClass parent_class;
+
+	/* Virtual methods */
+	gboolean	(*is_row_selected)	(ESelectionModel *esm,
+						 gint row);
+	void		(*foreach)		(ESelectionModel *esm,
+						 EForeachFunc callback,
+						 gpointer closure);
+	void		(*clear)		(ESelectionModel *esm);
+	gint		(*selected_count)	(ESelectionModel *esm);
+	void		(*select_all)		(ESelectionModel *esm);
+	void		(*invert_selection)	(ESelectionModel *esm);
+	gint		(*row_count)		(ESelectionModel *esm);
+
+	/* Protected virtual methods. */
+	void		(*change_one_row)	(ESelectionModel *esm,
+						 gint row,
+						 gboolean on);
+	void		(*change_cursor)	(ESelectionModel *esm,
+						 gint row,
+						 gint col);
+	gint		(*cursor_row)		(ESelectionModel *esm);
+	gint		(*cursor_col)		(ESelectionModel *esm);
+
+	void		(*select_single_row)	(ESelectionModel *selection,
+						 gint row);
+	void		(*toggle_single_row)	(ESelectionModel *selection,
+						 gint row);
+	void		(*move_selection_end)	(ESelectionModel *selection,
+						 gint row);
+	void		(*set_selection_end)	(ESelectionModel *selection,
+						 gint row);
+
+	/* Signals */
+	void		(*cursor_changed)	(ESelectionModel *esm,
+						 gint row,
+						 gint col);
+	void		(*cursor_activated)	(ESelectionModel *esm,
+						 gint row,
+						 gint col);
+	void		(*selection_row_changed)(ESelectionModel *esm,
+						 gint row);
+	void		(*selection_changed)	(ESelectionModel *esm);
+};
+
+GType		e_selection_model_get_type	(void);
+void		e_selection_model_do_something	(ESelectionModel *esm,
+						 guint row,
+						 guint col,
+						 GdkModifierType state);
+gboolean	e_selection_model_maybe_do_something
+						(ESelectionModel *esm,
+						 guint row,
+						 guint col,
+						 GdkModifierType state);
+void		e_selection_model_right_click_down
+						(ESelectionModel *selection,
+						 guint row,
+						 guint col,
+						 GdkModifierType state);
+void		e_selection_model_right_click_up
+						(ESelectionModel *selection);
+gboolean	e_selection_model_key_press	(ESelectionModel *esm,
+						 GdkEventKey *key);
+void		e_selection_model_select_as_key_press
+						(ESelectionModel *esm,
+						 guint row,
+						 guint col,
+						 GdkModifierType state);
+
+/* Virtual functions */
+gboolean	e_selection_model_is_row_selected
+						(ESelectionModel *esm,
+						 gint             n);
+void		e_selection_model_foreach	(ESelectionModel *esm,
+						 EForeachFunc     callback,
+						 gpointer         closure);
+void		e_selection_model_clear		(ESelectionModel *esm);
+gint		e_selection_model_selected_count
+						(ESelectionModel *esm);
+void		e_selection_model_select_all	(ESelectionModel *esm);
+void		e_selection_model_invert_selection
+						(ESelectionModel *esm);
+gint		e_selection_model_row_count	(ESelectionModel *esm);
+
+/* Private virtual Functions */
+void		e_selection_model_change_one_row
+						(ESelectionModel *esm,
+						 gint row,
+						 gboolean on);
+void		e_selection_model_change_cursor	(ESelectionModel *esm,
+						 gint row,
+						 gint col);
+gint		e_selection_model_cursor_row	(ESelectionModel *esm);
+gint		e_selection_model_cursor_col	(ESelectionModel *esm);
+void		e_selection_model_select_single_row
+						(ESelectionModel *selection,
+						 gint row);
+void		e_selection_model_toggle_single_row
+						(ESelectionModel *selection,
+						 gint row);
+void		e_selection_model_move_selection_end
+						(ESelectionModel *selection,
+						 gint row);
+void		e_selection_model_set_selection_end
+						(ESelectionModel *selection,
+						 gint row);
+
+/* Signals */
+void		e_selection_model_cursor_changed
+						(ESelectionModel *selection,
+						 gint row,
+						 gint col);
+void		e_selection_model_cursor_activated
+						(ESelectionModel *selection,
+						 gint row,
+						 gint col);
+void		e_selection_model_selection_row_changed
+						(ESelectionModel *selection,
+						 gint row);
+void		e_selection_model_selection_changed
+						(ESelectionModel *selection);
+
+G_END_DECLS
+
+#endif /* E_SELECTION_MODEL_H */
+
diff --git a/e-util/e-selection.c b/e-util/e-selection.c
index 041b30c..4092b07 100644
--- a/e-util/e-selection.c
+++ b/e-util/e-selection.c
@@ -22,7 +22,7 @@
 /**
  * SECTION: e-selection
  * @short_description: selection and clipboard utilities
- * @include: e-util/e-selection.h
+ * @include: e-util/e-util.h
  **/
 
 #ifdef HAVE_CONFIG_H
diff --git a/e-util/e-selection.h b/e-util/e-selection.h
index 5d44cd4..75089c4 100644
--- a/e-util/e-selection.h
+++ b/e-util/e-selection.h
@@ -19,6 +19,10 @@
  *
  */
 
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
 #ifndef E_SELECTION_H
 #define E_SELECTION_H
 
diff --git a/e-util/e-send-options.c b/e-util/e-send-options.c
new file mode 100644
index 0000000..bf50dbe
--- /dev/null
+++ b/e-util/e-send-options.c
@@ -0,0 +1,767 @@
+/*
+ * Evolution calendar - Main page of the Groupwise send options Dialog
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chenthill Palanisamy <pchenthill novell com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-send-options.h"
+
+#include <string.h>
+#include <glib/gi18n.h>
+#include <time.h>
+
+#include "e-dateedit.h"
+#include "e-misc-utils.h"
+#include "e-util-private.h"
+
+#define E_SEND_OPTIONS_DIALOG_GET_PRIVATE(obj) \
+	(G_TYPE_INSTANCE_GET_PRIVATE \
+	((obj), E_TYPE_SEND_OPTIONS_DIALOG, ESendOptionsDialogPrivate))
+
+struct _ESendOptionsDialogPrivate {
+	GtkBuilder *builder;
+
+	gboolean gopts_needed;
+	gboolean global;
+
+	/* Widgets */
+
+	GtkWidget *main;
+
+	/* Noteboook to add options page and status tracking page*/
+	GtkNotebook *notebook;
+
+	GtkWidget *status;
+
+	/* priority */
+	GtkWidget *priority;
+
+	/* Security */
+	GtkWidget *security;
+
+	/* Widgets for Reply Requestion options */
+	GtkWidget *reply_request;
+	GtkWidget *reply_convenient;
+	GtkWidget *reply_within;
+	GtkWidget *within_days;
+
+	/* Widgets for delay delivery Option */
+	GtkWidget *delay_delivery;
+	GtkWidget *delay_until;
+
+	/* Widgets for Choosing expiration date */
+	GtkWidget *expiration;
+	GtkWidget *expire_after;
+
+	/* Widgets to for tracking information through sent Item */
+	GtkWidget *create_sent;
+	GtkWidget *delivered;
+	GtkWidget *delivered_opened;
+	GtkWidget *all_info;
+	GtkWidget *autodelete;
+
+	/* Widgets for setting the Return Notification */
+	GtkWidget *when_opened;
+	GtkWidget *when_declined;
+	GtkWidget *when_accepted;
+	GtkWidget *when_completed;
+
+	/* label widgets */
+	GtkWidget *security_label;
+	GtkWidget *priority_label;
+	GtkWidget *gopts_label;
+	GtkWidget *opened_label;
+	GtkWidget *declined_label;
+	GtkWidget *accepted_label;
+	GtkWidget *completed_label;
+	GtkWidget *until_label;
+        gchar *help_section;
+};
+
+static void e_send_options_dialog_finalize (GObject *object);
+static void e_send_options_cb (GtkDialog *dialog, gint state, gpointer func_data);
+
+enum {
+	SOD_RESPONSE,
+	LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = {0};
+
+G_DEFINE_TYPE (
+	ESendOptionsDialog,
+	e_send_options_dialog,
+	G_TYPE_OBJECT)
+
+static void
+e_send_options_get_widgets_data (ESendOptionsDialog *sod)
+{
+	ESendOptionsDialogPrivate *priv;
+	ESendOptionsGeneral *gopts;
+	ESendOptionsStatusTracking *sopts;
+
+	priv = sod->priv;
+	gopts = sod->data->gopts;
+	sopts = sod->data->sopts;
+
+	gopts->priority = gtk_combo_box_get_active ((GtkComboBox *) priv->priority);
+	gopts->security = gtk_combo_box_get_active ((GtkComboBox *) priv->security);
+
+	gopts->reply_enabled = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (priv->reply_request));
+	gopts->reply_convenient = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (priv->reply_convenient));
+	gopts->reply_within = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (priv->within_days));
+
+	gopts->expiration_enabled = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (priv->expiration));
+	gopts->expire_after = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (priv->expire_after));
+	gopts->delay_enabled = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (priv->delay_delivery));
+
+	if (e_date_edit_date_is_valid (E_DATE_EDIT (priv->delay_until)) &&
+								e_date_edit_time_is_valid (E_DATE_EDIT (priv->delay_until)))
+		gopts->delay_until = e_date_edit_get_time (E_DATE_EDIT (priv->delay_until));
+
+	sopts->tracking_enabled = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (priv->create_sent));
+
+	sopts->autodelete = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (priv->autodelete));
+
+	if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (priv->delivered)))
+		sopts->track_when = E_DELIVERED;
+	else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (priv->delivered_opened)))
+		sopts->track_when = E_DELIVERED_OPENED;
+	else
+		sopts->track_when = E_ALL;
+
+	sopts->opened = gtk_combo_box_get_active ((GtkComboBox *) priv->when_opened);
+	sopts->accepted = gtk_combo_box_get_active ((GtkComboBox *) priv->when_accepted);
+	sopts->declined = gtk_combo_box_get_active ((GtkComboBox *) priv->when_declined);
+	sopts->completed = gtk_combo_box_get_active ((GtkComboBox *) priv->when_completed);
+}
+
+static void
+e_send_options_fill_widgets_with_data (ESendOptionsDialog *sod)
+{
+	ESendOptionsDialogPrivate *priv;
+	ESendOptionsGeneral *gopts;
+	ESendOptionsStatusTracking *sopts;
+	time_t tmp;
+
+	priv = sod->priv;
+	gopts = sod->data->gopts;
+	sopts = sod->data->sopts;
+	tmp = time (NULL);
+
+	gtk_combo_box_set_active ((GtkComboBox *) priv->priority, gopts->priority);
+	gtk_combo_box_set_active ((GtkComboBox *) priv->security, gopts->security);
+
+	if (gopts->reply_enabled)
+		gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->reply_request), TRUE);
+	else
+		gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->reply_request), FALSE);
+
+	if (gopts->reply_convenient)
+		gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->reply_convenient), TRUE);
+	else
+		gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->reply_within), TRUE);
+
+	gtk_spin_button_set_value (GTK_SPIN_BUTTON (priv->within_days), (gdouble) gopts->reply_within);
+
+	if (gopts->expiration_enabled)
+		gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->expiration), TRUE);
+	else
+		gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->expiration), FALSE);
+
+	gtk_spin_button_set_value (GTK_SPIN_BUTTON (priv->expire_after), (gdouble) gopts->expire_after);
+
+	if (gopts->delay_enabled)
+		gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->delay_delivery), TRUE);
+	else
+		gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->delay_delivery), FALSE);
+
+	if (!gopts->delay_until || difftime (gopts->delay_until, tmp) < 0)
+		e_date_edit_set_time (E_DATE_EDIT (priv->delay_until), 0);
+	else
+		e_date_edit_set_time (E_DATE_EDIT (priv->delay_until), gopts->delay_until);
+
+	if (sopts->tracking_enabled)
+		gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->create_sent), TRUE);
+	else
+		gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->create_sent), FALSE);
+
+	if (sopts->autodelete)
+		gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->autodelete), TRUE);
+	else
+		gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->autodelete), FALSE);
+
+	switch (sopts->track_when) {
+		case E_DELIVERED:
+			gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->delivered), TRUE);
+			break;
+		case E_DELIVERED_OPENED:
+			gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->delivered_opened), TRUE);
+			break;
+		case E_ALL:
+			gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->all_info), TRUE);
+	}
+
+	gtk_combo_box_set_active ((GtkComboBox *) priv->when_opened, sopts->opened);
+	gtk_combo_box_set_active ((GtkComboBox *) priv->when_declined, sopts->declined);
+	gtk_combo_box_set_active ((GtkComboBox *) priv->when_accepted, sopts->accepted);
+	gtk_combo_box_set_active ((GtkComboBox *) priv->when_completed, sopts->completed);
+}
+
+static void
+sensitize_widgets (ESendOptionsDialog *sod)
+{
+	ESendOptionsDialogPrivate *priv;
+	ESendOptionsGeneral *gopts;
+	ESendOptionsStatusTracking *sopts;
+
+	priv = sod->priv;
+	gopts = sod->data->gopts;
+	sopts = sod->data->sopts;
+
+	if (!gopts->reply_enabled) {
+		gtk_widget_set_sensitive (priv->reply_convenient, FALSE);
+		gtk_widget_set_sensitive (priv->reply_within, FALSE);
+		gtk_widget_set_sensitive (priv->within_days, FALSE);
+	}
+
+	if (!gopts->expiration_enabled)
+		gtk_widget_set_sensitive (priv->expire_after, FALSE);
+
+	if (!gopts->delay_enabled) {
+		gtk_widget_set_sensitive (priv->delay_until, FALSE);
+	}
+
+	if (!sopts->tracking_enabled) {
+		gtk_widget_set_sensitive (priv->delivered, FALSE);
+		gtk_widget_set_sensitive (priv->delivered_opened, FALSE);
+		gtk_widget_set_sensitive (priv->all_info, FALSE);
+		gtk_widget_set_sensitive (priv->autodelete, FALSE);
+	}
+}
+
+static void
+expiration_toggled_cb (GtkToggleButton *toggle,
+                       gpointer data)
+{
+	gboolean active;
+	ESendOptionsDialog *sod;
+	ESendOptionsDialogPrivate *priv;
+
+	sod = data;
+	priv = sod->priv;
+
+	active = gtk_toggle_button_get_active (toggle);
+
+	gtk_widget_set_sensitive (priv->expire_after, active);
+}
+
+static void
+reply_request_toggled_cb (GtkToggleButton *toggle,
+                          gpointer data)
+{
+	gboolean active;
+	ESendOptionsDialog *sod;
+	ESendOptionsDialogPrivate *priv;
+
+	sod = data;
+	priv = sod->priv;
+	active = gtk_toggle_button_get_active (toggle);
+
+	gtk_widget_set_sensitive (priv->reply_convenient, active);
+	gtk_widget_set_sensitive (priv->reply_within, active);
+	gtk_widget_set_sensitive (priv->within_days, active);
+
+}
+
+static void
+delay_delivery_toggled_cb (GtkToggleButton *toggle,
+                           gpointer data)
+{
+	gboolean active;
+	ESendOptionsDialog *sod;
+	ESendOptionsDialogPrivate *priv;
+
+	sod = data;
+	priv = sod->priv;
+	active = gtk_toggle_button_get_active (toggle);
+
+	gtk_widget_set_sensitive (priv->delay_until, active);
+}
+
+static void
+sent_item_toggled_cb (GtkToggleButton *toggle,
+                      gpointer data)
+{
+	gboolean active;
+	ESendOptionsDialog *sod;
+	ESendOptionsDialogPrivate *priv;
+
+	sod = data;
+	priv = sod->priv;
+	active = gtk_toggle_button_get_active (toggle);
+
+	gtk_widget_set_sensitive (priv->delivered, active);
+	gtk_widget_set_sensitive (priv->delivered_opened, active);
+	gtk_widget_set_sensitive (priv->all_info, active);
+	gtk_widget_set_sensitive (priv->autodelete, active);
+}
+
+static void
+delay_until_date_changed_cb (GtkWidget *dedit,
+                             gpointer data)
+{
+	ESendOptionsDialog *sod;
+	ESendOptionsDialogPrivate *priv;
+	time_t tmp, current;
+
+	sod = data;
+	priv = sod->priv;
+
+	current = time (NULL);
+	tmp = e_date_edit_get_time (E_DATE_EDIT (priv->delay_until));
+
+	if ((difftime (tmp, current) < 0) || !e_date_edit_time_is_valid (E_DATE_EDIT (priv->delay_until))
+					  || !e_date_edit_date_is_valid (E_DATE_EDIT (priv->delay_until)))
+		e_date_edit_set_time (E_DATE_EDIT (priv->delay_until), 0);
+
+}
+static void
+page_changed_cb (GtkNotebook *notebook,
+                 GtkWidget *page,
+                 gint num,
+                 gpointer data)
+{
+	ESendOptionsDialog *sod = data;
+	ESendOptionsDialogPrivate *priv = sod->priv;
+
+	e_send_options_get_widgets_data (sod);
+	if (num > 0) {
+		GtkWidget *child;
+
+		if (num == 1) {
+			gtk_widget_hide (priv->accepted_label);
+			gtk_widget_hide (priv->when_accepted);
+			gtk_widget_hide (priv->completed_label);
+			gtk_widget_hide (priv->when_completed);
+			gtk_widget_set_sensitive (priv->autodelete, TRUE);
+			sod->data->sopts = sod->data->mopts;
+
+			child = gtk_notebook_get_nth_page (notebook, 1);
+			if (child != priv->status && (!GTK_IS_BIN (child) || gtk_bin_get_child (GTK_BIN (child)) != priv->status))
+				gtk_widget_reparent (priv->status, child);
+		} else if (num == 2) {
+			gtk_widget_hide (priv->completed_label);
+			gtk_widget_hide (priv->when_completed);
+			gtk_widget_set_sensitive (priv->autodelete, FALSE);
+
+			gtk_widget_show (priv->accepted_label);
+			gtk_widget_show (priv->when_accepted);
+			sod->data->sopts = sod->data->copts;
+
+			child = gtk_notebook_get_nth_page (notebook, 2);
+			if (gtk_bin_get_child (GTK_BIN (child)) != priv->status)
+				gtk_widget_reparent (priv->status, child);
+		} else {
+			gtk_widget_set_sensitive (priv->autodelete, FALSE);
+
+			gtk_widget_show (priv->completed_label);
+			gtk_widget_show (priv->when_completed);
+			gtk_widget_show (priv->accepted_label);
+			gtk_widget_show (priv->when_accepted);
+			sod->data->sopts = sod->data->topts;
+
+			child = gtk_notebook_get_nth_page (notebook, 3);
+			if (gtk_bin_get_child (GTK_BIN (child)) != priv->status)
+				gtk_widget_reparent (priv->status, child);
+		}
+	}
+	e_send_options_fill_widgets_with_data (sod);
+}
+
+static void
+init_widgets (ESendOptionsDialog *sod)
+{
+	ESendOptionsDialogPrivate *priv;
+
+	priv = sod->priv;
+
+	g_signal_connect (
+		priv->expiration, "toggled",
+		G_CALLBACK (expiration_toggled_cb), sod);
+	g_signal_connect (
+		priv->reply_request, "toggled",
+		G_CALLBACK (reply_request_toggled_cb), sod);
+	g_signal_connect (
+		priv->delay_delivery, "toggled",
+		G_CALLBACK (delay_delivery_toggled_cb), sod);
+	g_signal_connect (
+		priv->create_sent, "toggled",
+		G_CALLBACK (sent_item_toggled_cb), sod);
+
+	g_signal_connect (
+		priv->main, "response",
+		G_CALLBACK (e_send_options_cb), sod);
+	g_signal_connect (
+		priv->delay_until, "changed",
+		G_CALLBACK (delay_until_date_changed_cb), sod);
+
+	if (priv->global)
+		g_signal_connect (
+			priv->notebook, "switch-page",
+			G_CALLBACK (page_changed_cb), sod);
+
+}
+
+static gboolean
+get_widgets (ESendOptionsDialog *sod)
+{
+	ESendOptionsDialogPrivate *priv;
+	GtkBuilder *builder;
+
+	priv = sod->priv;
+	builder = sod->priv->builder;
+
+	priv->main = e_builder_get_widget (builder, "send-options-dialog");
+	if (!priv->main)
+		return FALSE;
+
+	priv->priority = e_builder_get_widget (builder, "combo-priority");
+	priv->status = e_builder_get_widget (builder, "status-tracking");
+	priv->security = e_builder_get_widget (builder, "security-combo");
+	priv->notebook = (GtkNotebook *) e_builder_get_widget (builder, "notebook");
+	priv->reply_request = e_builder_get_widget (builder, "reply-request-button");
+	priv->reply_convenient = e_builder_get_widget (builder, "reply-convinient");
+	priv->reply_within = e_builder_get_widget (builder, "reply-within");
+	priv->within_days = e_builder_get_widget (builder, "within-days");
+	priv->delay_delivery = e_builder_get_widget (builder, "delay-delivery-button");
+	priv->delay_until = e_builder_get_widget (builder, "until-date");
+	gtk_widget_show (priv->delay_until);
+	priv->expiration = e_builder_get_widget (builder, "expiration-button");
+	priv->expire_after = e_builder_get_widget (builder, "expire-after");
+	priv->create_sent = e_builder_get_widget (builder, "create-sent-button");
+	priv->delivered = e_builder_get_widget (builder, "delivered");
+	priv->delivered_opened = e_builder_get_widget (builder, "delivered-opened");
+	priv->all_info = e_builder_get_widget (builder, "all-info");
+	priv->autodelete = e_builder_get_widget (builder, "autodelete");
+	priv->when_opened = e_builder_get_widget (builder, "open-combo");
+	priv->when_declined = e_builder_get_widget (builder, "delete-combo");
+	priv->when_accepted = e_builder_get_widget (builder, "accept-combo");
+	priv->when_completed = e_builder_get_widget (builder, "complete-combo");
+	priv->security_label = e_builder_get_widget (builder, "security-label");
+	priv->gopts_label = e_builder_get_widget (builder, "gopts-label");
+	priv->priority_label = e_builder_get_widget (builder, "priority-label");
+	priv->until_label = e_builder_get_widget (builder, "until-label");
+	priv->opened_label = e_builder_get_widget (builder, "opened-label");
+	priv->declined_label = e_builder_get_widget (builder, "declined-label");
+	priv->accepted_label = e_builder_get_widget (builder, "accepted-label");
+	priv->completed_label = e_builder_get_widget (builder, "completed-label");
+
+	return (priv->priority
+		&& priv->security
+		&& priv->status
+		&& priv->reply_request
+		&& priv->reply_convenient
+		&& priv->reply_within
+		&& priv->within_days
+		&& priv->delay_delivery
+		&& priv->delay_until
+		&& priv->expiration
+		&& priv->expire_after
+		&& priv->create_sent
+		&& priv->delivered
+		&& priv->delivered_opened
+		&& priv->autodelete
+		&& priv->all_info
+		&& priv->when_opened
+		&& priv->when_declined
+		&& priv->when_accepted
+		&& priv->when_completed
+		&& priv->security_label
+		&& priv->priority_label
+		&& priv->opened_label
+		&& priv->gopts_label
+		&& priv->declined_label
+		&& priv->accepted_label
+		&& priv->completed_label);
+
+}
+
+static void
+setup_widgets (ESendOptionsDialog *sod,
+               Item_type type)
+{
+	ESendOptionsDialogPrivate *priv;
+
+	priv = sod->priv;
+
+	if (!priv->gopts_needed) {
+		gtk_notebook_set_show_tabs (priv->notebook, FALSE);
+		gtk_notebook_set_current_page (priv->notebook, 1);
+		gtk_widget_hide (priv->delay_until);
+	} else
+		gtk_notebook_set_show_tabs (priv->notebook, TRUE);
+
+	gtk_label_set_mnemonic_widget (GTK_LABEL (priv->priority_label), priv->priority);
+	gtk_label_set_mnemonic_widget (GTK_LABEL (priv->security_label), priv->security);
+	gtk_label_set_mnemonic_widget (GTK_LABEL (priv->accepted_label), priv->when_accepted);
+	gtk_label_set_mnemonic_widget (GTK_LABEL (priv->declined_label), priv->when_declined);
+	gtk_label_set_mnemonic_widget (GTK_LABEL (priv->opened_label), priv->when_opened);
+	gtk_label_set_mnemonic_widget (GTK_LABEL (priv->completed_label), priv->when_completed);
+	gtk_label_set_mnemonic_widget (GTK_LABEL (priv->until_label), priv->delay_until);
+
+	if (priv->global) {
+		GtkWidget *widget, *page;
+
+		widget = gtk_label_new (_("Mail"));
+		page = gtk_alignment_new (0.0, 0.0, 0.0, 0.0);
+		gtk_widget_reparent (priv->status, page);
+		gtk_notebook_append_page (priv->notebook, page, widget);
+		gtk_container_child_set (GTK_CONTAINER (priv->notebook), page, "tab-fill", FALSE, "tab-expand", FALSE, NULL);
+		gtk_widget_show (page);
+		gtk_widget_show (widget);
+
+		widget = gtk_label_new (_("Calendar"));
+		page = gtk_alignment_new (0.0, 0.0, 0.0, 0.0);
+		gtk_notebook_append_page (priv->notebook, page, widget);
+		gtk_container_child_set (GTK_CONTAINER (priv->notebook), page, "tab-fill", FALSE, "tab-expand", FALSE, NULL);
+		gtk_widget_show (page);
+		gtk_widget_show (widget);
+
+		widget = gtk_label_new (_("Task"));
+		page = gtk_alignment_new (0.0, 0.0, 0.0, 0.0);
+		gtk_notebook_append_page (priv->notebook, page, widget);
+		gtk_container_child_set (GTK_CONTAINER (priv->notebook), page, "tab-fill", FALSE, "tab-expand", FALSE, NULL);
+		gtk_widget_show (page);
+		gtk_widget_show (widget);
+
+		gtk_notebook_set_show_tabs (priv->notebook, TRUE);
+	}
+
+	switch (type) {
+		case E_ITEM_MAIL:
+			priv->help_section = g_strdup ("groupwise-placeholder");
+			gtk_widget_hide (priv->accepted_label);
+			gtk_widget_hide (priv->when_accepted);
+			gtk_widget_hide (priv->completed_label);
+			gtk_widget_hide (priv->when_completed);
+			gtk_label_set_text_with_mnemonic (GTK_LABEL (priv->declined_label), (_("When de_leted:")));
+			break;
+		case E_ITEM_CALENDAR:
+			priv->help_section = g_strdup ("groupwise-placeholder");
+			gtk_widget_hide (priv->completed_label);
+			gtk_widget_hide (priv->when_completed);
+		case E_ITEM_TASK:
+			priv->help_section = g_strdup ("groupwise-placeholder");
+			gtk_widget_hide (priv->security_label);
+			gtk_widget_hide (priv->security);
+			gtk_widget_set_sensitive (priv->autodelete, FALSE);
+			break;
+		default:
+			break;
+	}
+}
+
+ESendOptionsDialog *
+e_send_options_dialog_new (void)
+{
+	ESendOptionsDialog *sod;
+
+	sod = g_object_new (E_TYPE_SEND_OPTIONS_DIALOG, NULL);
+
+	return sod;
+}
+
+void
+e_send_options_set_need_general_options (ESendOptionsDialog *sod,
+                                         gboolean needed)
+{
+	g_return_if_fail (E_IS_SEND_OPTIONS_DIALOG (sod));
+
+	sod->priv->gopts_needed = needed;
+}
+
+gboolean
+e_send_options_get_need_general_options (ESendOptionsDialog *sod)
+{
+	g_return_val_if_fail (E_IS_SEND_OPTIONS_DIALOG (sod), FALSE);
+
+	return sod->priv->gopts_needed;
+}
+
+gboolean
+e_send_options_set_global (ESendOptionsDialog *sod,
+                           gboolean set)
+{
+	g_return_val_if_fail (E_IS_SEND_OPTIONS_DIALOG (sod), FALSE);
+
+	sod->priv->global = set;
+
+	return TRUE;
+}
+
+static void
+e_send_options_cb (GtkDialog *dialog,
+                   gint state,
+                   gpointer func_data)
+{
+	ESendOptionsDialogPrivate *priv;
+	ESendOptionsDialog *sod;
+
+	sod = func_data;
+	priv = sod->priv;
+
+	switch (state) {
+		case GTK_RESPONSE_OK:
+			e_send_options_get_widgets_data (sod);
+		case GTK_RESPONSE_CANCEL:
+			gtk_widget_hide (priv->main);
+			gtk_widget_destroy (priv->main);
+			g_object_unref (priv->builder);
+			break;
+		case GTK_RESPONSE_HELP:
+			e_display_help (
+				GTK_WINDOW (priv->main),
+				priv->help_section);
+			break;
+	}
+
+	g_signal_emit (func_data, signals[SOD_RESPONSE], 0, state);
+}
+
+gboolean
+e_send_options_dialog_run (ESendOptionsDialog *sod,
+                           GtkWidget *parent,
+                           Item_type type)
+{
+	ESendOptionsDialogPrivate *priv;
+	GtkWidget *toplevel;
+
+	g_return_val_if_fail (sod != NULL || E_IS_SEND_OPTIONS_DIALOG (sod), FALSE);
+
+	priv = sod->priv;
+
+	/* Make sure our custom widget classes are registered with
+	 * GType before we load the GtkBuilder definition file. */
+	E_TYPE_DATE_EDIT;
+
+	priv->builder = gtk_builder_new ();
+	e_load_ui_builder_definition (priv->builder, "e-send-options.ui");
+
+	if (!get_widgets (sod)) {
+		g_object_unref (priv->builder);
+		g_message (G_STRLOC ": Could not get the Widgets \n");
+		return FALSE;
+	}
+
+	if (priv->global) {
+		g_free (sod->data->sopts);
+		sod->data->sopts = sod->data->mopts;
+	}
+
+	setup_widgets (sod, type);
+
+	toplevel =  gtk_widget_get_toplevel (priv->main);
+	if (parent)
+		gtk_window_set_transient_for (GTK_WINDOW (toplevel),
+				      GTK_WINDOW (parent));
+
+	e_send_options_fill_widgets_with_data (sod);
+	sensitize_widgets (sod);
+	init_widgets (sod);
+	gtk_window_set_modal ((GtkWindow *) priv->main, TRUE);
+
+	gtk_widget_show (priv->main);
+
+	return TRUE;
+}
+
+static void
+e_send_options_dialog_finalize (GObject *object)
+{
+	ESendOptionsDialog *sod;
+
+	sod = E_SEND_OPTIONS_DIALOG (object);
+
+	g_free (sod->priv->help_section);
+
+	g_free (sod->data->gopts);
+
+	if (!sod->priv->global)
+		g_free (sod->data->sopts);
+
+	g_free (sod->data->mopts);
+	g_free (sod->data->copts);
+	g_free (sod->data->topts);
+	g_free (sod->data);
+
+	/* Chain up to parent's finalize() method. */
+	G_OBJECT_CLASS (e_send_options_dialog_parent_class)->finalize (object);
+}
+
+/* Object initialization function for the task page */
+static void
+e_send_options_dialog_init (ESendOptionsDialog *sod)
+{
+	ESendOptionsData *new;
+
+	new = g_new0 (ESendOptionsData, 1);
+	new->gopts = g_new0 (ESendOptionsGeneral, 1);
+	new->sopts = g_new0 (ESendOptionsStatusTracking, 1);
+	new->mopts = g_new0 (ESendOptionsStatusTracking, 1);
+	new->copts = g_new0 (ESendOptionsStatusTracking, 1);
+	new->topts = g_new0 (ESendOptionsStatusTracking, 1);
+
+	sod->priv = E_SEND_OPTIONS_DIALOG_GET_PRIVATE (sod);
+
+	sod->data = new;
+	sod->data->initialized = FALSE;
+	sod->data->gopts->security = 0;
+
+	sod->priv->gopts_needed = TRUE;
+}
+
+/* Class initialization function for the Send Options */
+static void
+e_send_options_dialog_class_init (ESendOptionsDialogClass *class)
+{
+	GObjectClass *object_class;
+
+	g_type_class_add_private (class, sizeof (ESendOptionsDialogPrivate));
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->finalize = e_send_options_dialog_finalize;
+
+	signals[SOD_RESPONSE] = g_signal_new (
+		"sod_response",
+		G_TYPE_FROM_CLASS (class),
+		G_SIGNAL_RUN_FIRST,
+		G_STRUCT_OFFSET (ESendOptionsDialogClass, sod_response),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__INT,
+		G_TYPE_NONE, 1,
+		G_TYPE_INT);
+
+}
diff --git a/e-util/e-send-options.h b/e-util/e-send-options.h
new file mode 100644
index 0000000..2cd8336
--- /dev/null
+++ b/e-util/e-send-options.h
@@ -0,0 +1,131 @@
+/*
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Damon Chaplin <damon ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef __E_SEND_OPTIONS_DIALOG_H__
+#define __E_SEND_OPTIONS_DIALOG_H__
+
+#include <gtk/gtk.h>
+#include <time.h>
+
+#define E_TYPE_SEND_OPTIONS_DIALOG       (e_send_options_dialog_get_type ())
+#define E_SEND_OPTIONS_DIALOG(obj)       (G_TYPE_CHECK_INSTANCE_CAST ((obj), E_TYPE_SEND_OPTIONS_DIALOG, ESendOptionsDialog))
+#define E_SEND_OPTIONS_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), E_TYPE_SEND_OPTIONS_DIALOG, ESendOptionsDialogClass))
+#define E_IS_SEND_OPTIONS_DIALOG(obj)    (G_TYPE_CHECK_INSTANCE_TYPE ((obj), E_TYPE_SEND_OPTIONS_DIALOG))
+#define E_IS_SEND_OPTIONS_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), E_TYPE_SEND_OPTIONS_DIALOG))
+
+typedef struct _ESendOptionsDialog		ESendOptionsDialog;
+typedef struct _ESendOptionsDialogClass		ESendOptionsDialogClass;
+typedef struct _ESendOptionsDialogPrivate	ESendOptionsDialogPrivate;
+
+typedef enum {
+	E_ITEM_NONE,
+	E_ITEM_MAIL,
+	E_ITEM_CALENDAR,
+	E_ITEM_TASK
+} Item_type;
+
+typedef enum {
+	E_PRIORITY_UNDEFINED,
+	E_PRIORITY_HIGH,
+	E_PRIORITY_STANDARD,
+	E_PRIORITY_LOW
+} ESendOptionsPriority;
+
+typedef enum {
+	E_SECURITY_NORMAL,
+	E_SECURITY_PROPRIETARY,
+	E_SECURITY_CONFIDENTIAL,
+	E_SECURITY_SECRET,
+	E_SECURITY_TOP_SECRET,
+	E_SECURITY_FOR_YOUR_EYES_ONLY
+} ESendOptionsSecurity;
+
+typedef enum {
+	E_RETURN_NOTIFY_NONE,
+	E_RETURN_NOTIFY_MAIL
+} ESendOptionsReturnNotify;
+
+typedef enum {
+	E_DELIVERED = 1,
+	E_DELIVERED_OPENED = 2,
+	E_ALL = 3
+} TrackInfo;
+
+typedef struct {
+	ESendOptionsPriority priority;
+	gint classify;
+	gboolean reply_enabled;
+	gboolean reply_convenient;
+	gint reply_within;
+	gboolean expiration_enabled;
+	gint expire_after;
+	gboolean delay_enabled;
+	time_t delay_until;
+	gint security;
+} ESendOptionsGeneral;
+
+typedef struct {
+	gboolean tracking_enabled;
+	TrackInfo track_when;
+	gboolean autodelete;
+	ESendOptionsReturnNotify opened;
+	ESendOptionsReturnNotify accepted;
+	ESendOptionsReturnNotify declined;
+	ESendOptionsReturnNotify completed;
+} ESendOptionsStatusTracking;
+
+typedef struct {
+	gboolean initialized;
+
+	ESendOptionsGeneral *gopts;
+	ESendOptionsStatusTracking *sopts;
+	ESendOptionsStatusTracking *mopts;
+	ESendOptionsStatusTracking *copts;
+	ESendOptionsStatusTracking *topts;
+
+} ESendOptionsData;
+
+struct _ESendOptionsDialog {
+	GObject object;
+
+	ESendOptionsData *data;
+	/* Private data */
+	ESendOptionsDialogPrivate *priv;
+};
+
+struct _ESendOptionsDialogClass {
+	GObjectClass parent_class;
+	void (* sod_response) (ESendOptionsDialog *sd, gint status);
+};
+
+GType  e_send_options_dialog_get_type     (void);
+ESendOptionsDialog *e_send_options_dialog_new (void);
+void e_send_options_set_need_general_options (ESendOptionsDialog *sod, gboolean needed);
+gboolean e_send_options_get_need_general_options (ESendOptionsDialog *sod);
+gboolean e_send_options_dialog_run (ESendOptionsDialog *sod, GtkWidget *parent, Item_type type);
+gboolean e_send_options_set_global (ESendOptionsDialog *sod, gboolean set);
+#endif
diff --git a/widgets/misc/e-send-options.ui b/e-util/e-send-options.ui
similarity index 100%
rename from widgets/misc/e-send-options.ui
rename to e-util/e-send-options.ui
diff --git a/e-util/e-sorter-array.c b/e-util/e-sorter-array.c
index 4caa092..1c37316 100644
--- a/e-util/e-sorter-array.c
+++ b/e-util/e-sorter-array.c
@@ -28,7 +28,8 @@
 #include <string.h>
 
 #include "e-sorter-array.h"
-#include "e-util.h"
+
+#include "e-misc-utils.h"
 
 #define d(x)
 
diff --git a/e-util/e-sorter-array.h b/e-util/e-sorter-array.h
index 5fb9c31..07a32b4 100644
--- a/e-util/e-sorter-array.h
+++ b/e-util/e-sorter-array.h
@@ -20,6 +20,10 @@
  *
  */
 
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
 #ifndef _E_SORTER_ARRAY_H_
 #define _E_SORTER_ARRAY_H_
 
diff --git a/e-util/e-sorter.c b/e-util/e-sorter.c
index b55d985..ecb597a 100644
--- a/e-util/e-sorter.c
+++ b/e-util/e-sorter.c
@@ -28,7 +28,6 @@
 #include <string.h>
 
 #include "e-sorter.h"
-#include "e-util.h"
 
 #define d(x)
 
diff --git a/e-util/e-sorter.h b/e-util/e-sorter.h
index 37015e5..94b63f3 100644
--- a/e-util/e-sorter.h
+++ b/e-util/e-sorter.h
@@ -21,6 +21,10 @@
  *
  */
 
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
 #ifndef _E_SORTER_H_
 #define _E_SORTER_H_
 
diff --git a/e-util/e-source-combo-box.c b/e-util/e-source-combo-box.c
new file mode 100644
index 0000000..d8d2273
--- /dev/null
+++ b/e-util/e-source-combo-box.c
@@ -0,0 +1,701 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* e-source-combo-box.c
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-source-combo-box.h"
+#include "e-cell-renderer-color.h"
+
+#define E_SOURCE_COMBO_BOX_GET_PRIVATE(obj) \
+	(G_TYPE_INSTANCE_GET_PRIVATE \
+	((obj), E_TYPE_SOURCE_COMBO_BOX, ESourceComboBoxPrivate))
+
+struct _ESourceComboBoxPrivate {
+	ESourceRegistry *registry;
+	gchar *extension_name;
+
+	gulong source_added_handler_id;
+	gulong source_removed_handler_id;
+	gulong source_enabled_handler_id;
+	gulong source_disabled_handler_id;
+
+	gboolean show_colors;
+};
+
+enum {
+	PROP_0,
+	PROP_EXTENSION_NAME,
+	PROP_REGISTRY,
+	PROP_SHOW_COLORS
+};
+
+enum {
+	COLUMN_COLOR,		/* GDK_TYPE_COLOR */
+	COLUMN_NAME,		/* G_TYPE_STRING */
+	COLUMN_SENSITIVE,	/* G_TYPE_BOOLEAN */
+	COLUMN_UID,		/* G_TYPE_STRING */
+	NUM_COLUMNS
+};
+
+G_DEFINE_TYPE (ESourceComboBox, e_source_combo_box, GTK_TYPE_COMBO_BOX)
+
+static gboolean
+source_combo_box_traverse (GNode *node,
+                           ESourceComboBox *combo_box)
+{
+	ESource *source;
+	ESourceSelectable *extension = NULL;
+	GtkTreeModel *model;
+	GtkTreeIter iter;
+	GString *indented;
+	GdkColor color;
+	const gchar *ext_name;
+	const gchar *display_name;
+	const gchar *uid;
+	gboolean sensitive = FALSE;
+	gboolean use_color = FALSE;
+	guint depth;
+
+	/* Skip the root node. */
+	if (G_NODE_IS_ROOT (node))
+		return FALSE;
+
+	ext_name = e_source_combo_box_get_extension_name (combo_box);
+
+	model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo_box));
+	gtk_list_store_append (GTK_LIST_STORE (model), &iter);
+
+	source = E_SOURCE (node->data);
+	uid = e_source_get_uid (source);
+	display_name = e_source_get_display_name (source);
+
+	indented = g_string_new (NULL);
+
+	depth = g_node_depth (node);
+	g_warn_if_fail (depth > 1);
+	while (--depth > 1)
+		g_string_append (indented, "    ");
+	g_string_append (indented, display_name);
+
+	if (ext_name != NULL && e_source_has_extension (source, ext_name)) {
+		extension = e_source_get_extension (source, ext_name);
+		sensitive = TRUE;
+	}
+
+	if (E_IS_SOURCE_SELECTABLE (extension)) {
+		const gchar *color_spec;
+
+		color_spec = e_source_selectable_get_color (extension);
+		if (color_spec != NULL && *color_spec != '\0')
+			use_color = gdk_color_parse (color_spec, &color);
+	}
+
+	gtk_list_store_set (
+		GTK_LIST_STORE (model), &iter,
+		COLUMN_COLOR, use_color ? &color : NULL,
+		COLUMN_NAME, indented->str,
+		COLUMN_SENSITIVE, sensitive,
+		COLUMN_UID, uid,
+		-1);
+
+	g_string_free (indented, TRUE);
+
+	return FALSE;
+}
+
+static void
+source_combo_box_build_model (ESourceComboBox *combo_box)
+{
+	ESourceRegistry *registry;
+	GtkComboBox *gtk_combo_box;
+	GtkTreeModel *model;
+	GNode *root;
+	const gchar *active_id;
+	const gchar *extension_name;
+
+	registry = e_source_combo_box_get_registry (combo_box);
+	extension_name = e_source_combo_box_get_extension_name (combo_box);
+
+	gtk_combo_box = GTK_COMBO_BOX (combo_box);
+	model = gtk_combo_box_get_model (gtk_combo_box);
+
+	/* Constructor properties trigger this function before the
+	 * list store is configured.  Detect it and return silently. */
+	if (model == NULL)
+		return;
+
+	/* Remember the active ID so we can try to restore it. */
+	active_id = gtk_combo_box_get_active_id (gtk_combo_box);
+
+	gtk_list_store_clear (GTK_LIST_STORE (model));
+
+	/* If we have no registry, leave the combo box empty. */
+	if (registry == NULL)
+		return;
+
+	/* If we have no extension name, leave the combo box empty. */
+	if (extension_name == NULL)
+		return;
+
+	root = e_source_registry_build_display_tree (registry, extension_name);
+
+	g_node_traverse (
+		root, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
+		(GNodeTraverseFunc) source_combo_box_traverse,
+		combo_box);
+
+	e_source_registry_free_display_tree (root);
+
+	/* Restore the active ID, or else set it to something reasonable. */
+	gtk_combo_box_set_active_id (gtk_combo_box, active_id);
+	if (gtk_combo_box_get_active_id (gtk_combo_box) == NULL) {
+		ESource *source;
+
+		source = e_source_registry_ref_default_for_extension_name (
+			registry, extension_name);
+		if (source != NULL) {
+			e_source_combo_box_set_active (combo_box, source);
+			g_object_unref (source);
+		}
+	}
+}
+
+static void
+source_combo_box_source_added_cb (ESourceRegistry *registry,
+                                  ESource *source,
+                                  ESourceComboBox *combo_box)
+{
+	source_combo_box_build_model (combo_box);
+}
+
+static void
+source_combo_box_source_removed_cb (ESourceRegistry *registry,
+                                    ESource *source,
+                                    ESourceComboBox *combo_box)
+{
+	source_combo_box_build_model (combo_box);
+}
+
+static void
+source_combo_box_source_enabled_cb (ESourceRegistry *registry,
+                                    ESource *source,
+                                    ESourceComboBox *combo_box)
+{
+	source_combo_box_build_model (combo_box);
+}
+
+static void
+source_combo_box_source_disabled_cb (ESourceRegistry *registry,
+                                     ESource *source,
+                                     ESourceComboBox *combo_box)
+{
+	source_combo_box_build_model (combo_box);
+}
+
+static void
+source_combo_box_set_property (GObject *object,
+                               guint property_id,
+                               const GValue *value,
+                               GParamSpec *pspec)
+{
+	switch (property_id) {
+		case PROP_EXTENSION_NAME:
+			e_source_combo_box_set_extension_name (
+				E_SOURCE_COMBO_BOX (object),
+				g_value_get_string (value));
+			return;
+
+		case PROP_REGISTRY:
+			e_source_combo_box_set_registry (
+				E_SOURCE_COMBO_BOX (object),
+				g_value_get_object (value));
+			return;
+
+		case PROP_SHOW_COLORS:
+			e_source_combo_box_set_show_colors (
+				E_SOURCE_COMBO_BOX (object),
+				g_value_get_boolean (value));
+			return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+source_combo_box_get_property (GObject *object,
+                               guint property_id,
+                               GValue *value,
+                               GParamSpec *pspec)
+{
+	switch (property_id) {
+		case PROP_EXTENSION_NAME:
+			g_value_set_string (
+				value,
+				e_source_combo_box_get_extension_name (
+				E_SOURCE_COMBO_BOX (object)));
+			return;
+
+		case PROP_REGISTRY:
+			g_value_set_object (
+				value,
+				e_source_combo_box_get_registry (
+				E_SOURCE_COMBO_BOX (object)));
+			return;
+
+		case PROP_SHOW_COLORS:
+			g_value_set_boolean (
+				value,
+				e_source_combo_box_get_show_colors (
+				E_SOURCE_COMBO_BOX (object)));
+			return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+source_combo_box_dispose (GObject *object)
+{
+	ESourceComboBoxPrivate *priv;
+
+	priv = E_SOURCE_COMBO_BOX_GET_PRIVATE (object);
+
+	if (priv->registry != NULL) {
+		g_signal_handler_disconnect (
+			priv->registry,
+			priv->source_added_handler_id);
+		g_signal_handler_disconnect (
+			priv->registry,
+			priv->source_removed_handler_id);
+		g_signal_handler_disconnect (
+			priv->registry,
+			priv->source_enabled_handler_id);
+		g_signal_handler_disconnect (
+			priv->registry,
+			priv->source_disabled_handler_id);
+		g_object_unref (priv->registry);
+		priv->registry = NULL;
+	}
+
+	/* Chain up to parent's "dispose" method. */
+	G_OBJECT_CLASS (e_source_combo_box_parent_class)->dispose (object);
+}
+
+static void
+source_combo_box_finalize (GObject *object)
+{
+	ESourceComboBoxPrivate *priv;
+
+	priv = E_SOURCE_COMBO_BOX_GET_PRIVATE (object);
+
+	g_free (priv->extension_name);
+
+	/* Chain up to parent's "finalize" method. */
+	G_OBJECT_CLASS (e_source_combo_box_parent_class)->finalize (object);
+}
+
+static void
+source_combo_box_constructed (GObject *object)
+{
+	ESourceComboBox *combo_box;
+	GtkCellRenderer *renderer;
+	GtkCellLayout *layout;
+	GtkListStore *store;
+
+	combo_box = E_SOURCE_COMBO_BOX (object);
+
+	/* Chain up to parent's constructed() method. */
+	G_OBJECT_CLASS (e_source_combo_box_parent_class)->constructed (object);
+
+	store = gtk_list_store_new (
+		NUM_COLUMNS,
+		GDK_TYPE_COLOR,		/* COLUMN_COLOR */
+		G_TYPE_STRING,		/* COLUMN_NAME */
+		G_TYPE_BOOLEAN,		/* COLUMN_SENSITIVE */
+		G_TYPE_STRING);		/* COLUMN_UID */
+	gtk_combo_box_set_model (
+		GTK_COMBO_BOX (combo_box),
+		GTK_TREE_MODEL (store));
+	g_object_unref (store);
+
+	gtk_combo_box_set_id_column (GTK_COMBO_BOX (combo_box), COLUMN_UID);
+
+	layout = GTK_CELL_LAYOUT (combo_box);
+
+	renderer = e_cell_renderer_color_new ();
+	gtk_cell_layout_pack_start (layout, renderer, FALSE);
+	gtk_cell_layout_set_attributes (
+		layout, renderer,
+		"color", COLUMN_COLOR,
+		"sensitive", COLUMN_SENSITIVE,
+		NULL);
+
+	g_object_bind_property (
+		combo_box, "show-colors",
+		renderer, "visible",
+		G_BINDING_SYNC_CREATE);
+
+	renderer = gtk_cell_renderer_text_new ();
+	gtk_cell_layout_pack_start (layout, renderer, TRUE);
+	gtk_cell_layout_set_attributes (
+		layout, renderer,
+		"text", COLUMN_NAME,
+		"sensitive", COLUMN_SENSITIVE,
+		NULL);
+
+	source_combo_box_build_model (combo_box);
+}
+
+static void
+e_source_combo_box_class_init (ESourceComboBoxClass *class)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+	g_type_class_add_private (class, sizeof (ESourceComboBoxPrivate));
+
+	object_class->set_property = source_combo_box_set_property;
+	object_class->get_property = source_combo_box_get_property;
+	object_class->dispose = source_combo_box_dispose;
+	object_class->finalize = source_combo_box_finalize;
+	object_class->constructed = source_combo_box_constructed;
+
+	g_object_class_install_property (
+		object_class,
+		PROP_EXTENSION_NAME,
+		g_param_spec_string (
+			"extension-name",
+			"Extension Name",
+			"ESource extension name to filter",
+			NULL,
+			G_PARAM_READWRITE |
+			G_PARAM_CONSTRUCT));
+
+	/* XXX Don't use G_PARAM_CONSTRUCT_ONLY here.  We need to allow
+	 *     for this class to be instantiated by a GtkBuilder with no
+	 *     special construct parameters, and then subsequently give
+	 *     it an ESourceRegistry. */
+	g_object_class_install_property (
+		object_class,
+		PROP_REGISTRY,
+		g_param_spec_object (
+			"registry",
+			"Registry",
+			"Data source registry",
+			E_TYPE_SOURCE_REGISTRY,
+			G_PARAM_READWRITE |
+			G_PARAM_CONSTRUCT |
+			G_PARAM_STATIC_STRINGS));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_SHOW_COLORS,
+		g_param_spec_boolean (
+			"show-colors",
+			"Show Colors",
+			"Whether to show colors next to names",
+			TRUE,
+			G_PARAM_READWRITE |
+			G_PARAM_CONSTRUCT |
+			G_PARAM_STATIC_STRINGS));
+}
+
+static void
+e_source_combo_box_init (ESourceComboBox *combo_box)
+{
+	combo_box->priv = E_SOURCE_COMBO_BOX_GET_PRIVATE (combo_box);
+
+}
+
+/**
+ * e_source_combo_box_new:
+ * @registry: an #ESourceRegistry, or %NULL
+ * @extension_name: an #ESource extension name
+ *
+ * Creates a new #ESourceComboBox widget that lets the user pick an #ESource
+ * from the provided #ESourceRegistry.  The displayed sources are restricted
+ * to those which have an @extension_name extension.
+ *
+ * Returns: a new #ESourceComboBox
+ *
+ * Since: 2.22
+ **/
+GtkWidget *
+e_source_combo_box_new (ESourceRegistry *registry,
+                        const gchar *extension_name)
+{
+	if (registry != NULL)
+		g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), NULL);
+
+	return g_object_new (
+		E_TYPE_SOURCE_COMBO_BOX, "registry", registry,
+		"extension-name", extension_name, NULL);
+}
+
+/**
+ * e_source_combo_box_get_registry:
+ * @combo_box: an #ESourceComboBox
+ *
+ * Returns the #ESourceRegistry used to populate @combo_box.
+ *
+ * Returns: the #ESourceRegistry, or %NULL
+ *
+ * Since: 3.6
+ **/
+ESourceRegistry *
+e_source_combo_box_get_registry (ESourceComboBox *combo_box)
+{
+	g_return_val_if_fail (E_IS_SOURCE_COMBO_BOX (combo_box), NULL);
+
+	return combo_box->priv->registry;
+}
+
+/**
+ * e_source_combo_box_set_registry:
+ * @combo_box: an #ESourceComboBox
+ * @registry: an #ESourceRegistry
+ *
+ * Sets the #ESourceRegistry used to populate @combo_box.
+ *
+ * This function is intended for cases where @combo_box is instantiated
+ * by a #GtkBuilder and has to be given an #ESourceRegistry after it is
+ * fully constructed.
+ *
+ * Since: 3.6
+ **/
+void
+e_source_combo_box_set_registry (ESourceComboBox *combo_box,
+                                 ESourceRegistry *registry)
+{
+	g_return_if_fail (E_IS_SOURCE_COMBO_BOX (combo_box));
+
+	if (combo_box->priv->registry == registry)
+		return;
+
+	if (registry != NULL) {
+		g_return_if_fail (E_IS_SOURCE_REGISTRY (registry));
+		g_object_ref (registry);
+	}
+
+	if (combo_box->priv->registry != NULL) {
+		g_signal_handler_disconnect (
+			combo_box->priv->registry,
+			combo_box->priv->source_added_handler_id);
+		g_signal_handler_disconnect (
+			combo_box->priv->registry,
+			combo_box->priv->source_removed_handler_id);
+		g_signal_handler_disconnect (
+			combo_box->priv->registry,
+			combo_box->priv->source_enabled_handler_id);
+		g_signal_handler_disconnect (
+			combo_box->priv->registry,
+			combo_box->priv->source_disabled_handler_id);
+		g_object_unref (combo_box->priv->registry);
+	}
+
+	combo_box->priv->registry = registry;
+
+	combo_box->priv->source_added_handler_id = 0;
+	combo_box->priv->source_removed_handler_id = 0;
+	combo_box->priv->source_enabled_handler_id = 0;
+	combo_box->priv->source_disabled_handler_id = 0;
+
+	if (registry != NULL) {
+		gulong handler_id;
+
+		handler_id = g_signal_connect (
+			registry, "source-added",
+			G_CALLBACK (source_combo_box_source_added_cb),
+			combo_box);
+		combo_box->priv->source_added_handler_id = handler_id;
+
+		handler_id = g_signal_connect (
+			registry, "source-removed",
+			G_CALLBACK (source_combo_box_source_removed_cb),
+			combo_box);
+		combo_box->priv->source_removed_handler_id = handler_id;
+
+		handler_id = g_signal_connect (
+			registry, "source-enabled",
+			G_CALLBACK (source_combo_box_source_enabled_cb),
+			combo_box);
+		combo_box->priv->source_enabled_handler_id = handler_id;
+
+		handler_id = g_signal_connect (
+			registry, "source-disabled",
+			G_CALLBACK (source_combo_box_source_disabled_cb),
+			combo_box);
+		combo_box->priv->source_disabled_handler_id = handler_id;
+	}
+
+	source_combo_box_build_model (combo_box);
+
+	g_object_notify (G_OBJECT (combo_box), "registry");
+}
+
+/**
+ * e_source_combo_box_get_extension_name:
+ * @combo_box: an #ESourceComboBox
+ *
+ * Returns the extension name used to filter which data sources are
+ * shown in @combo_box.
+ *
+ * Returns: the #ESource extension name
+ *
+ * Since: 3.6
+ **/
+const gchar *
+e_source_combo_box_get_extension_name (ESourceComboBox *combo_box)
+{
+	g_return_val_if_fail (E_IS_SOURCE_COMBO_BOX (combo_box), NULL);
+
+	return combo_box->priv->extension_name;
+}
+
+/**
+ * e_source_combo_box_set_extension_name:
+ * @combo_box: an #ESourceComboBox
+ * @extension_name: an #ESource extension name
+ *
+ * Sets the extension name used to filter which data sources are shown in
+ * @combo_box.
+ *
+ * Since: 3.6
+ **/
+void
+e_source_combo_box_set_extension_name (ESourceComboBox *combo_box,
+                                       const gchar *extension_name)
+{
+	g_return_if_fail (E_IS_SOURCE_COMBO_BOX (combo_box));
+
+	if (g_strcmp0 (combo_box->priv->extension_name, extension_name) == 0)
+		return;
+
+	g_free (combo_box->priv->extension_name);
+	combo_box->priv->extension_name = g_strdup (extension_name);
+
+	source_combo_box_build_model (combo_box);
+
+	g_object_notify (G_OBJECT (combo_box), "extension-name");
+}
+
+/**
+ * e_source_combo_box_get_show_colors:
+ * @combo_box: an #ESourceComboBox
+ *
+ * Returns whether colors are shown next to data sources.
+ *
+ * Returns: %TRUE if colors are being shown
+ *
+ * Since: 3.6
+ **/
+gboolean
+e_source_combo_box_get_show_colors (ESourceComboBox *combo_box)
+{
+	g_return_val_if_fail (E_IS_SOURCE_COMBO_BOX (combo_box), FALSE);
+
+	return combo_box->priv->show_colors;
+}
+
+/**
+ * e_source_combo_box_set_show_colors:
+ * @combo_box: an #ESourceComboBox
+ * @show_colors: whether to show colors
+ *
+ * Sets whether to show colors next to data sources.
+ *
+ * Since: 3.6
+ **/
+void
+e_source_combo_box_set_show_colors (ESourceComboBox *combo_box,
+                                    gboolean show_colors)
+{
+	g_return_if_fail (E_IS_SOURCE_COMBO_BOX (combo_box));
+
+	if ((show_colors ? 1 : 0) == (combo_box->priv->show_colors ? 1 : 0))
+		return;
+
+	combo_box->priv->show_colors = show_colors;
+
+	source_combo_box_build_model (combo_box);
+
+	g_object_notify (G_OBJECT (combo_box), "show-colors");
+}
+
+/**
+ * e_source_combo_box_ref_active:
+ * @combo_box: an #ESourceComboBox
+ *
+ * Returns the #ESource corresponding to the currently active item,
+ * or %NULL if there is no active item.
+ *
+ * The returned #ESource is referenced for thread-safety and must be
+ * unreferenced with g_object_unref() when finished with it.
+ *
+ * Returns: an #ESource or %NULL
+ *
+ * Since: 3.6
+ **/
+ESource *
+e_source_combo_box_ref_active (ESourceComboBox *combo_box)
+{
+	ESourceRegistry *registry;
+	GtkComboBox *gtk_combo_box;
+	const gchar *active_id;
+
+	g_return_val_if_fail (E_IS_SOURCE_COMBO_BOX (combo_box), NULL);
+
+	registry = e_source_combo_box_get_registry (combo_box);
+
+	gtk_combo_box = GTK_COMBO_BOX (combo_box);
+	active_id = gtk_combo_box_get_active_id (gtk_combo_box);
+
+	if (active_id == NULL)
+		return NULL;
+
+	return e_source_registry_ref_source (registry, active_id);
+}
+
+/**
+ * e_source_combo_box_set_active:
+ * @combo_box: an #ESourceComboBox
+ * @source: an #ESource
+ *
+ * Sets the active item to the one corresponding to @source.
+ *
+ * Since: 2.22
+ **/
+void
+e_source_combo_box_set_active (ESourceComboBox *combo_box,
+                               ESource *source)
+{
+	GtkComboBox *gtk_combo_box;
+	const gchar *uid;
+
+	g_return_if_fail (E_IS_SOURCE_COMBO_BOX (combo_box));
+	g_return_if_fail (E_IS_SOURCE (source));
+
+	uid = e_source_get_uid (source);
+
+	gtk_combo_box = GTK_COMBO_BOX (combo_box);
+	gtk_combo_box_set_active_id (gtk_combo_box, uid);
+}
+
diff --git a/e-util/e-source-combo-box.h b/e-util/e-source-combo-box.h
new file mode 100644
index 0000000..d022f4a
--- /dev/null
+++ b/e-util/e-source-combo-box.h
@@ -0,0 +1,90 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* e-source-combo-box.h
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_SOURCE_COMBO_BOX_H
+#define E_SOURCE_COMBO_BOX_H
+
+#include <gtk/gtk.h>
+#include <libedataserver/libedataserver.h>
+
+#define E_TYPE_SOURCE_COMBO_BOX \
+	(e_source_combo_box_get_type ())
+#define E_SOURCE_COMBO_BOX(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_SOURCE_COMBO_BOX, ESourceComboBox))
+#define E_SOURCE_COMBO_BOX_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_SOURCE_COMBO_BOX, ESourceComboBoxClass))
+#define E_IS_SOURCE_COMBO_BOX(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE ((obj), E_TYPE_SOURCE_COMBO_BOX))
+#define E_IS_SOURCE_COMBO_BOX_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE ((cls), E_TYPE_SOURCE_COMBO_BOX))
+#define E_SOURCE_COMBO_BOX_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_SOURCE_COMBO_BOX, ESourceComboBox))
+
+G_BEGIN_DECLS
+
+typedef struct _ESourceComboBox ESourceComboBox;
+typedef struct _ESourceComboBoxClass ESourceComboBoxClass;
+typedef struct _ESourceComboBoxPrivate ESourceComboBoxPrivate;
+
+/**
+ * ESourceComboBox:
+ *
+ * Since: 2.22
+ **/
+struct _ESourceComboBox {
+	GtkComboBox parent;
+	ESourceComboBoxPrivate *priv;
+};
+
+struct _ESourceComboBoxClass {
+	GtkComboBoxClass parent_class;
+};
+
+GType		e_source_combo_box_get_type	(void);
+GtkWidget *	e_source_combo_box_new		(ESourceRegistry *registry,
+						 const gchar *extension_name);
+ESourceRegistry *
+		e_source_combo_box_get_registry	(ESourceComboBox *combo_box);
+void		e_source_combo_box_set_registry	(ESourceComboBox *combo_box,
+						 ESourceRegistry *registry);
+const gchar *	e_source_combo_box_get_extension_name
+						(ESourceComboBox *combo_box);
+void		e_source_combo_box_set_extension_name
+						(ESourceComboBox *combo_box,
+						 const gchar *extension_name);
+gboolean	e_source_combo_box_get_show_colors
+						(ESourceComboBox *combo_box);
+void		e_source_combo_box_set_show_colors
+						(ESourceComboBox *combo_box,
+						 gboolean show_colors);
+ESource *	e_source_combo_box_ref_active	(ESourceComboBox *combo_box);
+void		e_source_combo_box_set_active	(ESourceComboBox *combo_box,
+						 ESource *source);
+
+G_END_DECLS
+
+#endif /* E_SOURCE_COMBO_BOX_H */
diff --git a/widgets/misc/e-source-config-backend.c b/e-util/e-source-config-backend.c
similarity index 100%
rename from widgets/misc/e-source-config-backend.c
rename to e-util/e-source-config-backend.c
diff --git a/e-util/e-source-config-backend.h b/e-util/e-source-config-backend.h
new file mode 100644
index 0000000..3191ca1
--- /dev/null
+++ b/e-util/e-source-config-backend.h
@@ -0,0 +1,98 @@
+/*
+ * e-source-config-backend.h
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_SOURCE_CONFIG_BACKEND_H
+#define E_SOURCE_CONFIG_BACKEND_H
+
+#include <libebackend/libebackend.h>
+
+#include <e-util/e-source-config.h>
+
+/* Standard GObject macros */
+#define E_TYPE_SOURCE_CONFIG_BACKEND \
+	(e_source_config_backend_get_type ())
+#define E_SOURCE_CONFIG_BACKEND(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_SOURCE_CONFIG_BACKEND, ESourceConfigBackend))
+#define E_SOURCE_CONFIG_BACKEND_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_SOURCE_CONFIG_BACKEND, ESourceConfigBackendClass))
+#define E_IS_SOURCE_CONFIG_BACKEND(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_SOURCE_CONFIG_BACKEND))
+#define E_IS_SOURCE_CONFIG_BACKEND_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_SOURCE_CONFIG_BACKEND))
+#define E_SOURCE_CONFIG_BACKEND_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_SOURCE_CONFIG_BACKEND, ESourceConfigBackendClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ESourceConfigBackend ESourceConfigBackend;
+typedef struct _ESourceConfigBackendClass ESourceConfigBackendClass;
+typedef struct _ESourceConfigBackendPrivate ESourceConfigBackendPrivate;
+
+struct _ESourceConfigBackend {
+	EExtension parent;
+	ESourceConfigBackendPrivate *priv;
+};
+
+struct _ESourceConfigBackendClass {
+	EExtensionClass parent_class;
+
+	/* This should match backend names used in ESourceBackend. */
+	const gchar *backend_name;
+
+	/* Optional.  Collection-based backends can leave this NULL.
+	 * This is only for sources which have a fixed parent source,
+	 * usually one of the "stub" placeholders ("local-stub", etc). */
+	const gchar *parent_uid;
+
+	gboolean	(*allow_creation)	(ESourceConfigBackend *backend);
+	void		(*insert_widgets)	(ESourceConfigBackend *backend,
+						 ESource *scratch_source);
+	gboolean	(*check_complete)	(ESourceConfigBackend *backend,
+						 ESource *scratch_source);
+	void		(*commit_changes)	(ESourceConfigBackend *backend,
+						 ESource *scratch_source);
+};
+
+GType		e_source_config_backend_get_type
+					(void) G_GNUC_CONST;
+ESourceConfig *	e_source_config_backend_get_config
+					(ESourceConfigBackend *backend);
+gboolean	e_source_config_backend_allow_creation
+					(ESourceConfigBackend *backend);
+void		e_source_config_backend_insert_widgets
+					(ESourceConfigBackend *backend,
+					 ESource *scratch_source);
+gboolean	e_source_config_backend_check_complete
+					(ESourceConfigBackend *backend,
+					 ESource *scratch_source);
+void		e_source_config_backend_commit_changes
+					(ESourceConfigBackend *backend,
+					 ESource *scratch_source);
+
+G_END_DECLS
+
+#endif /* E_SOURCE_CONFIG_BACKEND_H */
diff --git a/e-util/e-source-config-dialog.c b/e-util/e-source-config-dialog.c
new file mode 100644
index 0000000..8a311c8
--- /dev/null
+++ b/e-util/e-source-config-dialog.c
@@ -0,0 +1,394 @@
+/*
+ * e-source-config-dialog.c
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#include "e-source-config-dialog.h"
+
+#include "e-alert-bar.h"
+#include "e-alert-dialog.h"
+#include "e-alert-sink.h"
+
+#define E_SOURCE_CONFIG_DIALOG_GET_PRIVATE(obj) \
+	(G_TYPE_INSTANCE_GET_PRIVATE \
+	((obj), E_TYPE_SOURCE_CONFIG_DIALOG, ESourceConfigDialogPrivate))
+
+struct _ESourceConfigDialogPrivate {
+	ESourceConfig *config;
+	ESourceRegistry *registry;
+
+	GtkWidget *alert_bar;
+	gulong alert_bar_visible_handler_id;
+};
+
+enum {
+	PROP_0,
+	PROP_CONFIG
+};
+
+/* Forward Declarations */
+static void	e_source_config_dialog_alert_sink_init
+					(EAlertSinkInterface *interface);
+
+G_DEFINE_TYPE_WITH_CODE (
+	ESourceConfigDialog,
+	e_source_config_dialog,
+	GTK_TYPE_DIALOG,
+	G_IMPLEMENT_INTERFACE (
+		E_TYPE_ALERT_SINK,
+		e_source_config_dialog_alert_sink_init))
+
+static void
+source_config_dialog_commit_cb (GObject *object,
+                                GAsyncResult *result,
+                                gpointer user_data)
+{
+	ESourceConfig *config;
+	ESourceConfigDialog *dialog;
+	GdkWindow *gdk_window;
+	GError *error = NULL;
+
+	config = E_SOURCE_CONFIG (object);
+	dialog = E_SOURCE_CONFIG_DIALOG (user_data);
+
+	/* Set the cursor back to normal. */
+	gdk_window = gtk_widget_get_window (GTK_WIDGET (dialog));
+	gdk_window_set_cursor (gdk_window, NULL);
+
+	/* Allow user interaction with window content. */
+	gtk_widget_set_sensitive (GTK_WIDGET (dialog), TRUE);
+
+	e_source_config_commit_finish (config, result, &error);
+
+	/* Ignore cancellations. */
+	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+		g_object_unref (dialog);
+		g_error_free (error);
+
+	} else if (error != NULL) {
+		e_alert_submit (
+			E_ALERT_SINK (dialog),
+			"system:simple-error",
+			error->message, NULL);
+		g_object_unref (dialog);
+		g_error_free (error);
+
+	} else {
+		gtk_widget_destroy (GTK_WIDGET (dialog));
+	}
+}
+
+static void
+source_config_dialog_commit (ESourceConfigDialog *dialog)
+{
+	GdkCursor *gdk_cursor;
+	GdkWindow *gdk_window;
+	ESourceConfig *config;
+
+	config = e_source_config_dialog_get_config (dialog);
+
+	/* Clear any previous alerts. */
+	e_alert_bar_clear (E_ALERT_BAR (dialog->priv->alert_bar));
+
+	/* Make the cursor appear busy. */
+	gdk_cursor = gdk_cursor_new (GDK_WATCH);
+	gdk_window = gtk_widget_get_window (GTK_WIDGET (dialog));
+	gdk_window_set_cursor (gdk_window, gdk_cursor);
+	g_object_unref (gdk_cursor);
+
+	/* Prevent user interaction with window content. */
+	gtk_widget_set_sensitive (GTK_WIDGET (dialog), FALSE);
+
+	/* XXX This operation is not cancellable. */
+	e_source_config_commit (
+		config, NULL,
+		source_config_dialog_commit_cb,
+		g_object_ref (dialog));
+}
+
+static void
+source_config_dialog_source_removed_cb (ESourceRegistry *registry,
+                                        ESource *removed_source,
+                                        ESourceConfigDialog *dialog)
+{
+	ESourceConfig *config;
+	ESource *original_source;
+
+	/* If the ESource being edited is removed, cancel the dialog. */
+
+	config = e_source_config_dialog_get_config (dialog);
+	original_source = e_source_config_get_original_source (config);
+
+	if (original_source == NULL)
+		return;
+
+	if (!e_source_equal (original_source, removed_source))
+		return;
+
+	gtk_dialog_response (GTK_DIALOG (dialog), GTK_RESPONSE_CANCEL);
+}
+
+static void
+source_config_alert_bar_visible_cb (EAlertBar *alert_bar,
+                                    GParamSpec *pspec,
+                                    ESourceConfigDialog *dialog)
+{
+	e_source_config_resize_window (dialog->priv->config);
+}
+
+static void
+source_config_dialog_set_config (ESourceConfigDialog *dialog,
+                                 ESourceConfig *config)
+{
+	ESourceRegistry *registry;
+
+	g_return_if_fail (E_IS_SOURCE_CONFIG (config));
+	g_return_if_fail (dialog->priv->config == NULL);
+
+	dialog->priv->config = g_object_ref (config);
+
+	registry = e_source_config_get_registry (config);
+	dialog->priv->registry = g_object_ref (registry);
+
+	g_signal_connect (
+		registry, "source-removed",
+		G_CALLBACK (source_config_dialog_source_removed_cb), dialog);
+}
+
+static void
+source_config_dialog_set_property (GObject *object,
+                                   guint property_id,
+                                   const GValue *value,
+                                   GParamSpec *pspec)
+{
+	switch (property_id) {
+		case PROP_CONFIG:
+			source_config_dialog_set_config (
+				E_SOURCE_CONFIG_DIALOG (object),
+				g_value_get_object (value));
+			return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+source_config_dialog_get_property (GObject *object,
+                                   guint property_id,
+                                   GValue *value,
+                                   GParamSpec *pspec)
+{
+	switch (property_id) {
+		case PROP_CONFIG:
+			g_value_set_object (
+				value,
+				e_source_config_dialog_get_config (
+				E_SOURCE_CONFIG_DIALOG (object)));
+			return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+source_config_dialog_dispose (GObject *object)
+{
+	ESourceConfigDialogPrivate *priv;
+
+	priv = E_SOURCE_CONFIG_DIALOG_GET_PRIVATE (object);
+
+	if (priv->config != NULL) {
+		g_object_unref (priv->config);
+		priv->config = NULL;
+	}
+
+	if (priv->registry != NULL) {
+		g_signal_handlers_disconnect_matched (
+			priv->registry, G_SIGNAL_MATCH_DATA,
+			0, 0, NULL, NULL, object);
+		g_object_unref (priv->registry);
+		priv->registry = NULL;
+	}
+
+	if (priv->alert_bar != NULL) {
+		g_signal_handler_disconnect (
+			priv->alert_bar,
+			priv->alert_bar_visible_handler_id);
+		g_object_unref (priv->alert_bar);
+		priv->alert_bar = NULL;
+	}
+
+	/* Chain up to parent's dispose() method. */
+	G_OBJECT_CLASS (e_source_config_dialog_parent_class)->dispose (object);
+}
+
+static void
+source_config_dialog_constructed (GObject *object)
+{
+	ESourceConfigDialogPrivate *priv;
+	GtkWidget *content_area;
+	GtkWidget *config;
+	GtkWidget *widget;
+	gulong handler_id;
+
+	priv = E_SOURCE_CONFIG_DIALOG_GET_PRIVATE (object);
+
+	config = GTK_WIDGET (priv->config);
+
+	widget = gtk_dialog_get_widget_for_response (
+		GTK_DIALOG (object), GTK_RESPONSE_OK);
+
+	gtk_container_set_border_width (GTK_CONTAINER (object), 5);
+	gtk_container_set_border_width (GTK_CONTAINER (config), 5);
+
+	content_area = gtk_dialog_get_content_area (GTK_DIALOG (object));
+	gtk_box_pack_start (GTK_BOX (content_area), config, TRUE, TRUE, 0);
+	gtk_widget_show (config);
+
+	/* Don't use G_BINDING_SYNC_CREATE here.  The ESourceConfig widget
+	 * is not ready to run check_complete() until after it's realized. */
+	g_object_bind_property (
+		config, "complete",
+		widget, "sensitive",
+		G_BINDING_DEFAULT);
+
+	widget = e_alert_bar_new ();
+	gtk_box_pack_start (GTK_BOX (content_area), widget, FALSE, FALSE, 0);
+	priv->alert_bar = g_object_ref (widget);
+	/* EAlertBar controls its own visibility. */
+
+	handler_id = g_signal_connect (
+		priv->alert_bar, "notify::visible",
+		G_CALLBACK (source_config_alert_bar_visible_cb), object);
+
+	priv->alert_bar_visible_handler_id = handler_id;
+}
+
+static void
+source_config_dialog_response (GtkDialog *dialog,
+                               gint response_id)
+{
+	/* Do not chain up.  GtkDialog does not implement this method. */
+
+	switch (response_id) {
+		case GTK_RESPONSE_OK:
+			source_config_dialog_commit (
+				E_SOURCE_CONFIG_DIALOG (dialog));
+			break;
+		case GTK_RESPONSE_CANCEL:
+			gtk_widget_destroy (GTK_WIDGET (dialog));
+			break;
+		default:
+			break;
+	}
+}
+
+static void
+source_config_dialog_submit_alert (EAlertSink *alert_sink,
+                                   EAlert *alert)
+{
+	ESourceConfigDialogPrivate *priv;
+	EAlertBar *alert_bar;
+	GtkWidget *dialog;
+	GtkWindow *parent;
+
+	priv = E_SOURCE_CONFIG_DIALOG_GET_PRIVATE (alert_sink);
+
+	switch (e_alert_get_message_type (alert)) {
+		case GTK_MESSAGE_INFO:
+		case GTK_MESSAGE_WARNING:
+		case GTK_MESSAGE_ERROR:
+			alert_bar = E_ALERT_BAR (priv->alert_bar);
+			e_alert_bar_add_alert (alert_bar, alert);
+			break;
+
+		default:
+			parent = GTK_WINDOW (alert_sink);
+			dialog = e_alert_dialog_new (parent, alert);
+			gtk_dialog_run (GTK_DIALOG (dialog));
+			gtk_widget_destroy (dialog);
+			break;
+	}
+}
+
+static void
+e_source_config_dialog_class_init (ESourceConfigDialogClass *class)
+{
+	GObjectClass *object_class;
+	GtkDialogClass *dialog_class;
+
+	g_type_class_add_private (class, sizeof (ESourceConfigDialogPrivate));
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->set_property = source_config_dialog_set_property;
+	object_class->get_property = source_config_dialog_get_property;
+	object_class->dispose = source_config_dialog_dispose;
+	object_class->constructed = source_config_dialog_constructed;
+
+	dialog_class = GTK_DIALOG_CLASS (class);
+	dialog_class->response = source_config_dialog_response;
+
+	g_object_class_install_property (
+		object_class,
+		PROP_CONFIG,
+		g_param_spec_object (
+			"config",
+			"Config",
+			"The ESourceConfig instance",
+			E_TYPE_SOURCE_CONFIG,
+			G_PARAM_READWRITE |
+			G_PARAM_CONSTRUCT_ONLY |
+			G_PARAM_STATIC_STRINGS));
+}
+
+static void
+e_source_config_dialog_alert_sink_init (EAlertSinkInterface *interface)
+{
+	interface->submit_alert = source_config_dialog_submit_alert;
+}
+
+static void
+e_source_config_dialog_init (ESourceConfigDialog *dialog)
+{
+	dialog->priv = E_SOURCE_CONFIG_DIALOG_GET_PRIVATE (dialog);
+
+	gtk_dialog_add_buttons (
+		GTK_DIALOG (dialog),
+		GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+		GTK_STOCK_OK, GTK_RESPONSE_OK,
+		NULL);
+
+	gtk_dialog_set_default_response (
+		GTK_DIALOG (dialog), GTK_RESPONSE_OK);
+}
+
+GtkWidget *
+e_source_config_dialog_new (ESourceConfig *config)
+{
+	g_return_val_if_fail (E_IS_SOURCE_CONFIG (config), NULL);
+
+	return g_object_new (
+		E_TYPE_SOURCE_CONFIG_DIALOG,
+		"config", config, NULL);
+}
+
+ESourceConfig *
+e_source_config_dialog_get_config (ESourceConfigDialog *dialog)
+{
+	g_return_val_if_fail (E_IS_SOURCE_CONFIG_DIALOG (dialog), NULL);
+
+	return dialog->priv->config;
+}
diff --git a/e-util/e-source-config-dialog.h b/e-util/e-source-config-dialog.h
new file mode 100644
index 0000000..6f01c8a
--- /dev/null
+++ b/e-util/e-source-config-dialog.h
@@ -0,0 +1,69 @@
+/*
+ * e-source-config-dialog.h
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_SOURCE_CONFIG_DIALOG_H
+#define E_SOURCE_CONFIG_DIALOG_H
+
+#include <e-util/e-source-config.h>
+
+/* Standard GObject macros */
+#define E_TYPE_SOURCE_CONFIG_DIALOG \
+	(e_source_config_dialog_get_type ())
+#define E_SOURCE_CONFIG_DIALOG(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_SOURCE_CONFIG_DIALOG, ESourceConfigDialog))
+#define E_SOURCE_CONFIG_DIALOG_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_SOURCE_CONFIG_DIALOG, ESourceConfigDialogClass))
+#define E_IS_SOURCE_CONFIG_DIALOG(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_SOURCE_CONFIG_DIALOG))
+#define E_IS_SOURCE_CONFIG_DIALOG_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_SOURCE_CONFIG_DIALOG))
+#define E_SOURCE_CONFIG_DIALOG_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_SOURCE_CONFIG_DIALOG, ESourceConfigDialogClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ESourceConfigDialog ESourceConfigDialog;
+typedef struct _ESourceConfigDialogClass ESourceConfigDialogClass;
+typedef struct _ESourceConfigDialogPrivate ESourceConfigDialogPrivate;
+
+struct _ESourceConfigDialog {
+	GtkDialog parent;
+	ESourceConfigDialogPrivate *priv;
+};
+
+struct _ESourceConfigDialogClass {
+	GtkDialogClass parent_class;
+};
+
+GType		e_source_config_dialog_get_type	(void) G_GNUC_CONST;
+GtkWidget *	e_source_config_dialog_new	(ESourceConfig *config);
+ESourceConfig *	e_source_config_dialog_get_config
+						(ESourceConfigDialog *dialog);
+
+G_END_DECLS
+
+#endif /* E_SOURCE_CONFIG_DIALOG_H */
diff --git a/e-util/e-source-config.c b/e-util/e-source-config.c
new file mode 100644
index 0000000..aacb48d
--- /dev/null
+++ b/e-util/e-source-config.c
@@ -0,0 +1,1447 @@
+/*
+ * e-source-config.c
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#include "e-source-config.h"
+
+#include <config.h>
+#include <glib/gi18n-lib.h>
+
+#include <libebackend/libebackend.h>
+
+#include "e-interval-chooser.h"
+#include "e-marshal.h"
+#include "e-source-config-backend.h"
+
+#define E_SOURCE_CONFIG_GET_PRIVATE(obj) \
+	(G_TYPE_INSTANCE_GET_PRIVATE \
+	((obj), E_TYPE_SOURCE_CONFIG, ESourceConfigPrivate))
+
+typedef struct _Candidate Candidate;
+
+struct _ESourceConfigPrivate {
+	ESource *original_source;
+	ESource *collection_source;
+	ESourceRegistry *registry;
+
+	GHashTable *backends;
+	GPtrArray *candidates;
+
+	GtkWidget *type_label;
+	GtkWidget *type_combo;
+	GtkWidget *name_label;
+	GtkWidget *name_entry;
+	GtkWidget *backend_box;
+	GtkSizeGroup *size_group;
+
+	gboolean complete;
+};
+
+struct _Candidate {
+	GtkWidget *page;
+	ESource *scratch_source;
+	ESourceConfigBackend *backend;
+	gulong changed_handler_id;
+};
+
+enum {
+	PROP_0,
+	PROP_COLLECTION_SOURCE,
+	PROP_COMPLETE,
+	PROP_ORIGINAL_SOURCE,
+	PROP_REGISTRY
+};
+
+enum {
+	CHECK_COMPLETE,
+	COMMIT_CHANGES,
+	INIT_CANDIDATE,
+	RESIZE_WINDOW,
+	LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+G_DEFINE_TYPE_WITH_CODE (
+	ESourceConfig,
+	e_source_config,
+	GTK_TYPE_BOX,
+	G_IMPLEMENT_INTERFACE (
+		E_TYPE_EXTENSIBLE, NULL))
+
+static void
+source_config_init_backends (ESourceConfig *config)
+{
+	GList *list, *iter;
+
+	config->priv->backends = g_hash_table_new_full (
+		(GHashFunc) g_str_hash,
+		(GEqualFunc) g_str_equal,
+		(GDestroyNotify) g_free,
+		(GDestroyNotify) g_object_unref);
+
+	e_extensible_load_extensions (E_EXTENSIBLE (config));
+
+	list = e_extensible_list_extensions (
+		E_EXTENSIBLE (config), E_TYPE_SOURCE_CONFIG_BACKEND);
+
+	for (iter = list; iter != NULL; iter = g_list_next (iter)) {
+		ESourceConfigBackend *backend;
+		ESourceConfigBackendClass *class;
+
+		backend = E_SOURCE_CONFIG_BACKEND (iter->data);
+		class = E_SOURCE_CONFIG_BACKEND_GET_CLASS (backend);
+
+		if (class->backend_name != NULL)
+			g_hash_table_insert (
+				config->priv->backends,
+				g_strdup (class->backend_name),
+				g_object_ref (backend));
+	}
+
+	g_list_free (list);
+}
+
+static gint
+source_config_compare_sources (gconstpointer a,
+                               gconstpointer b,
+                               gpointer user_data)
+{
+	ESource *source_a;
+	ESource *source_b;
+	ESource *parent_a;
+	ESource *parent_b;
+	ESourceConfig *config;
+	ESourceRegistry *registry;
+	const gchar *parent_uid_a;
+	const gchar *parent_uid_b;
+	gint result;
+
+	source_a = E_SOURCE (a);
+	source_b = E_SOURCE (b);
+	config = E_SOURCE_CONFIG (user_data);
+
+	if (e_source_equal (source_a, source_b))
+		return 0;
+
+	/* "On This Computer" always comes first. */
+
+	parent_uid_a = e_source_get_parent (source_a);
+	parent_uid_b = e_source_get_parent (source_b);
+
+	if (g_strcmp0 (parent_uid_a, "local-stub") == 0)
+		return -1;
+
+	if (g_strcmp0 (parent_uid_b, "local-stub") == 0)
+		return 1;
+
+	registry = e_source_config_get_registry (config);
+
+	parent_a = e_source_registry_ref_source (registry, parent_uid_a);
+	parent_b = e_source_registry_ref_source (registry, parent_uid_b);
+
+	g_return_val_if_fail (parent_a != NULL, 1);
+	g_return_val_if_fail (parent_b != NULL, -1);
+
+	result = e_source_compare_by_display_name (parent_a, parent_b);
+
+	g_object_unref (parent_a);
+	g_object_unref (parent_b);
+
+	return result;
+}
+
+static void
+source_config_add_candidate (ESourceConfig *config,
+                             ESource *scratch_source,
+                             ESourceConfigBackend *backend)
+{
+	Candidate *candidate;
+	GtkBox *backend_box;
+	GtkLabel *type_label;
+	GtkComboBoxText *type_combo;
+	ESource *parent_source;
+	ESourceRegistry *registry;
+	const gchar *display_name;
+	const gchar *parent_uid;
+	gulong handler_id;
+
+	backend_box = GTK_BOX (config->priv->backend_box);
+	type_label = GTK_LABEL (config->priv->type_label);
+	type_combo = GTK_COMBO_BOX_TEXT (config->priv->type_combo);
+
+	registry = e_source_config_get_registry (config);
+	parent_uid = e_source_get_parent (scratch_source);
+	parent_source = e_source_registry_ref_source (registry, parent_uid);
+	g_return_if_fail (parent_source != NULL);
+
+	candidate = g_slice_new (Candidate);
+	candidate->backend = g_object_ref (backend);
+	candidate->scratch_source = g_object_ref (scratch_source);
+
+	/* Do not show the page here. */
+	candidate->page = g_object_ref_sink (gtk_vbox_new (FALSE, 6));
+	gtk_box_pack_start (backend_box, candidate->page, FALSE, FALSE, 0);
+
+	g_ptr_array_add (config->priv->candidates, candidate);
+
+	display_name = e_source_get_display_name (parent_source);
+	gtk_combo_box_text_append_text (type_combo, display_name);
+	gtk_label_set_text (type_label, display_name);
+
+	/* Make sure the combo box has a valid active item before
+	 * adding widgets.  Otherwise we'll get run-time warnings
+	 * as property bindings are set up. */
+	if (gtk_combo_box_get_active (GTK_COMBO_BOX (type_combo)) == -1)
+		gtk_combo_box_set_active (GTK_COMBO_BOX (type_combo), 0);
+
+	/* Bind the standard widgets to the new scratch source. */
+	g_signal_emit (
+		config, signals[INIT_CANDIDATE], 0,
+		candidate->scratch_source);
+
+	/* Insert any backend-specific widgets. */
+	e_source_config_backend_insert_widgets (
+		candidate->backend, candidate->scratch_source);
+
+	handler_id = g_signal_connect_swapped (
+		candidate->scratch_source, "changed",
+		G_CALLBACK (e_source_config_check_complete), config);
+
+	candidate->changed_handler_id = handler_id;
+
+	/* Trigger the "changed" handler we just connected to set the
+	 * initial "complete" state based on the widgets we just added. */
+	e_source_changed (candidate->scratch_source);
+
+	g_object_unref (parent_source);
+}
+
+static void
+source_config_free_candidate (Candidate *candidate)
+{
+	g_signal_handler_disconnect (
+		candidate->scratch_source,
+		candidate->changed_handler_id);
+
+	g_object_unref (candidate->page);
+	g_object_unref (candidate->scratch_source);
+	g_object_unref (candidate->backend);
+
+	g_slice_free (Candidate, candidate);
+}
+
+static Candidate *
+source_config_get_active_candidate (ESourceConfig *config)
+{
+	GtkComboBox *type_combo;
+	gint index;
+
+	type_combo = GTK_COMBO_BOX (config->priv->type_combo);
+	index = gtk_combo_box_get_active (type_combo);
+	g_return_val_if_fail (index >= 0, NULL);
+
+	return g_ptr_array_index (config->priv->candidates, index);
+}
+
+static void
+source_config_type_combo_changed_cb (GtkComboBox *type_combo,
+                                     ESourceConfig *config)
+{
+	Candidate *candidate;
+	GPtrArray *array;
+	gint index;
+
+	array = config->priv->candidates;
+
+	for (index = 0; index < array->len; index++) {
+		candidate = g_ptr_array_index (array, index);
+		gtk_widget_hide (candidate->page);
+	}
+
+	index = gtk_combo_box_get_active (type_combo);
+	if (index == CLAMP (index, 0, array->len)) {
+		candidate = g_ptr_array_index (array, index);
+		gtk_widget_show (candidate->page);
+	}
+
+	e_source_config_resize_window (config);
+	e_source_config_check_complete (config);
+}
+
+static gboolean
+source_config_init_for_adding_source_foreach (gpointer key,
+                                              gpointer value,
+                                              gpointer user_data)
+{
+	ESource *scratch_source;
+	ESourceBackend *extension;
+	ESourceConfig *config;
+	ESourceConfigBackend *backend;
+	ESourceConfigBackendClass *class;
+	const gchar *extension_name;
+
+	scratch_source = E_SOURCE (key);
+	backend = E_SOURCE_CONFIG_BACKEND (value);
+	config = E_SOURCE_CONFIG (user_data);
+
+	/* This may not be the correct backend name for the child of a
+	 * collection.  For example, the "yahoo" collection backend uses
+	 * the "caldav" calender backend for calendar children.  But the
+	 * ESourceCollectionBackend can override our setting if needed. */
+	class = E_SOURCE_CONFIG_BACKEND_GET_CLASS (backend);
+	extension_name = e_source_config_get_backend_extension_name (config);
+	extension = e_source_get_extension (scratch_source, extension_name);
+	e_source_backend_set_backend_name (extension, class->backend_name);
+
+	source_config_add_candidate (config, scratch_source, backend);
+
+	return FALSE;  /* don't stop traversal */
+}
+
+static void
+source_config_init_for_adding_source (ESourceConfig *config)
+{
+	GList *list, *link;
+	ESourceRegistry *registry;
+	GTree *scratch_source_tree;
+
+	/* Candidates are drawn from two sources:
+	 *
+	 * ESourceConfigBackend classes that specify a fixed parent UID,
+	 * meaning there exists one only possible parent source for any
+	 * scratch source created by the backend.  The fixed parent UID
+	 * should be a built-in "stub" placeholder ("local-stub", etc).
+	 *
+	 * -and-
+	 *
+	 * Collection sources.  We let ESourceConfig subclasses gather
+	 * eligible collection sources to serve as parents for scratch
+	 * sources.  A scratch source is matched to a backend based on
+	 * the collection's backend name.  The "calendar-enabled" and
+	 * "contacts-enabled" settings also factor into eligibility.
+	 */
+
+	/* Use a GTree instead of a GHashTable so inserted scratch
+	 * sources automatically sort themselves by their parent's
+	 * display name. */
+	scratch_source_tree = g_tree_new_full (
+		source_config_compare_sources, config,
+		(GDestroyNotify) g_object_unref,
+		(GDestroyNotify) g_object_unref);
+
+	registry = e_source_config_get_registry (config);
+
+	/* First pick out the backends with a fixed parent UID. */
+
+	list = g_hash_table_get_values (config->priv->backends);
+
+	for (link = list; link != NULL; link = g_list_next (link)) {
+		ESourceConfigBackend *backend;
+		ESourceConfigBackendClass *class;
+		ESource *scratch_source;
+		ESource *parent_source;
+		gboolean parent_is_disabled;
+
+		backend = E_SOURCE_CONFIG_BACKEND (link->data);
+		class = E_SOURCE_CONFIG_BACKEND_GET_CLASS (backend);
+
+		if (class->parent_uid == NULL)
+			continue;
+
+		/* Verify the fixed parent UID is valid. */
+		parent_source = e_source_registry_ref_source (
+			registry, class->parent_uid);
+		if (parent_source == NULL) {
+			g_warning (
+				"%s: %sClass specifies "
+				"an invalid parent_uid '%s'",
+				G_STRFUNC,
+				G_OBJECT_TYPE_NAME (backend),
+				class->parent_uid);
+			continue;
+		}
+		parent_is_disabled = !e_source_get_enabled (parent_source);
+		g_object_unref (parent_source);
+
+		/* It's unusual for a fixed parent source to be disabled.
+		 * A user would have to go out of his way to do this, but
+		 * we should honor it regardless. */
+		if (parent_is_disabled)
+			continue;
+
+		/* Some backends don't allow new sources to be created.
+		 * The "contacts" calendar backend is one such example. */
+		if (!e_source_config_backend_allow_creation (backend))
+			continue;
+
+		scratch_source = e_source_new (NULL, NULL, NULL);
+		g_return_if_fail (scratch_source != NULL);
+
+		e_source_set_parent (scratch_source, class->parent_uid);
+
+		g_tree_insert (
+			scratch_source_tree,
+			g_object_ref (scratch_source),
+			g_object_ref (backend));
+
+		g_object_unref (scratch_source);
+	}
+
+	g_list_free (list);
+
+	/* Next gather eligible collection sources to serve as parents. */
+
+	list = e_source_config_list_eligible_collections (config);
+
+	for (link = list; link != NULL; link = g_list_next (link)) {
+		ESource *parent_source;
+		ESource *scratch_source;
+		ESourceBackend *extension;
+		ESourceConfigBackend *backend = NULL;
+		const gchar *backend_name;
+		const gchar *parent_uid;
+
+		parent_source = E_SOURCE (link->data);
+		parent_uid = e_source_get_uid (parent_source);
+
+		extension = e_source_get_extension (
+			parent_source, E_SOURCE_EXTENSION_COLLECTION);
+		backend_name = e_source_backend_get_backend_name (extension);
+
+		if (backend_name != NULL)
+			backend = g_hash_table_lookup (
+				config->priv->backends, backend_name);
+
+		if (backend == NULL)
+			continue;
+
+		/* Some backends disallow creating certain types of
+		 * resources.  For example, the Exchange Web Services
+		 * backend disallows creating new memo lists. */
+		if (!e_source_config_backend_allow_creation (backend))
+			continue;
+
+		scratch_source = e_source_new (NULL, NULL, NULL);
+		g_return_if_fail (scratch_source != NULL);
+
+		e_source_set_parent (scratch_source, parent_uid);
+
+		g_tree_insert (
+			scratch_source_tree,
+			g_object_ref (scratch_source),
+			g_object_ref (backend));
+
+		g_object_unref (scratch_source);
+	}
+
+	g_list_free_full (list, (GDestroyNotify) g_object_unref);
+
+	/* XXX GTree doesn't get as much love as GHashTable.
+	 *     It's missing an equivalent to GHashTableIter. */
+	g_tree_foreach (
+		scratch_source_tree,
+		source_config_init_for_adding_source_foreach, config);
+
+	g_tree_unref (scratch_source_tree);
+}
+
+static void
+source_config_init_for_editing_source (ESourceConfig *config)
+{
+	ESource *original_source;
+	ESource *scratch_source;
+	ESourceBackend *extension;
+	ESourceConfigBackend *backend;
+	GDBusObject *dbus_object;
+	const gchar *backend_name;
+	const gchar *extension_name;
+
+	original_source = e_source_config_get_original_source (config);
+	g_return_if_fail (original_source != NULL);
+
+	extension_name = e_source_config_get_backend_extension_name (config);
+	extension = e_source_get_extension (original_source, extension_name);
+	backend_name = e_source_backend_get_backend_name (extension);
+	g_return_if_fail (backend_name != NULL);
+
+	backend = g_hash_table_lookup (config->priv->backends, backend_name);
+	g_return_if_fail (backend != NULL);
+
+	dbus_object = e_source_ref_dbus_object (original_source);
+	g_return_if_fail (dbus_object != NULL);
+
+	scratch_source = e_source_new (dbus_object, NULL, NULL);
+	g_return_if_fail (scratch_source != NULL);
+
+	source_config_add_candidate (config, scratch_source, backend);
+
+	g_object_unref (scratch_source);
+	g_object_unref (dbus_object);
+}
+
+static void
+source_config_set_original_source (ESourceConfig *config,
+                                   ESource *original_source)
+{
+	g_return_if_fail (config->priv->original_source == NULL);
+
+	if (original_source != NULL)
+		g_object_ref (original_source);
+
+	config->priv->original_source = original_source;
+}
+
+static void
+source_config_set_registry (ESourceConfig *config,
+                            ESourceRegistry *registry)
+{
+	g_return_if_fail (E_IS_SOURCE_REGISTRY (registry));
+	g_return_if_fail (config->priv->registry == NULL);
+
+	config->priv->registry = g_object_ref (registry);
+}
+
+static void
+source_config_set_property (GObject *object,
+                            guint property_id,
+                            const GValue *value,
+                            GParamSpec *pspec)
+{
+	switch (property_id) {
+		case PROP_ORIGINAL_SOURCE:
+			source_config_set_original_source (
+				E_SOURCE_CONFIG (object),
+				g_value_get_object (value));
+			return;
+
+		case PROP_REGISTRY:
+			source_config_set_registry (
+				E_SOURCE_CONFIG (object),
+				g_value_get_object (value));
+			return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+source_config_get_property (GObject *object,
+                            guint property_id,
+                            GValue *value,
+                            GParamSpec *pspec)
+{
+	switch (property_id) {
+		case PROP_COLLECTION_SOURCE:
+			g_value_set_object (
+				value,
+				e_source_config_get_collection_source (
+				E_SOURCE_CONFIG (object)));
+			return;
+
+		case PROP_COMPLETE:
+			g_value_set_boolean (
+				value,
+				e_source_config_check_complete (
+				E_SOURCE_CONFIG (object)));
+			return;
+
+		case PROP_ORIGINAL_SOURCE:
+			g_value_set_object (
+				value,
+				e_source_config_get_original_source (
+				E_SOURCE_CONFIG (object)));
+			return;
+
+		case PROP_REGISTRY:
+			g_value_set_object (
+				value,
+				e_source_config_get_registry (
+				E_SOURCE_CONFIG (object)));
+			return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+source_config_dispose (GObject *object)
+{
+	ESourceConfigPrivate *priv;
+
+	priv = E_SOURCE_CONFIG_GET_PRIVATE (object);
+
+	if (priv->original_source != NULL) {
+		g_object_unref (priv->original_source);
+		priv->original_source = NULL;
+	}
+
+	if (priv->collection_source != NULL) {
+		g_object_unref (priv->collection_source);
+		priv->collection_source = NULL;
+	}
+
+	if (priv->registry != NULL) {
+		g_object_unref (priv->registry);
+		priv->registry = NULL;
+	}
+
+	if (priv->type_label != NULL) {
+		g_object_unref (priv->type_label);
+		priv->type_label = NULL;
+	}
+
+	if (priv->type_combo != NULL) {
+		g_object_unref (priv->type_combo);
+		priv->type_combo = NULL;
+	}
+
+	if (priv->name_label != NULL) {
+		g_object_unref (priv->name_label);
+		priv->name_label = NULL;
+	}
+
+	if (priv->name_entry != NULL) {
+		g_object_unref (priv->name_entry);
+		priv->name_entry = NULL;
+	}
+
+	if (priv->backend_box != NULL) {
+		g_object_unref (priv->backend_box);
+		priv->backend_box = NULL;
+	}
+
+	if (priv->size_group != NULL) {
+		g_object_unref (priv->size_group);
+		priv->size_group = NULL;
+	}
+
+	g_hash_table_remove_all (priv->backends);
+	g_ptr_array_set_size (priv->candidates, 0);
+
+	/* Chain up to parent's dispose() method. */
+	G_OBJECT_CLASS (e_source_config_parent_class)->dispose (object);
+}
+
+static void
+source_config_finalize (GObject *object)
+{
+	ESourceConfigPrivate *priv;
+
+	priv = E_SOURCE_CONFIG_GET_PRIVATE (object);
+
+	g_hash_table_destroy (priv->backends);
+	g_ptr_array_free (priv->candidates, TRUE);
+
+	/* Chain up to parent's finalize() method. */
+	G_OBJECT_CLASS (e_source_config_parent_class)->finalize (object);
+}
+
+static void
+source_config_constructed (GObject *object)
+{
+	ESourceConfig *config;
+	ESourceRegistry *registry;
+	ESource *original_source;
+	ESource *collection_source = NULL;
+
+	config = E_SOURCE_CONFIG (object);
+	registry = e_source_config_get_registry (config);
+	original_source = e_source_config_get_original_source (config);
+
+	/* If we have an original source, see if it's part
+	 * of a collection and note the collection source. */
+	if (original_source != NULL) {
+		const gchar *extension_name;
+
+		extension_name = E_SOURCE_EXTENSION_COLLECTION;
+		collection_source = e_source_registry_find_extension (
+			registry, original_source, extension_name);
+		config->priv->collection_source = collection_source;
+	}
+
+	if (original_source != NULL)
+		e_source_config_insert_widget (
+			config, NULL, _("Type:"),
+			config->priv->type_label);
+	else
+		e_source_config_insert_widget (
+			config, NULL, _("Type:"),
+			config->priv->type_combo);
+
+	/* If the original source is part of a collection then we assume
+	 * the display name is server-assigned and not user-assigned, at
+	 * least not assigned through Evolution. */
+	if (collection_source != NULL)
+		e_source_config_insert_widget (
+			config, NULL, _("Name:"),
+			config->priv->name_label);
+	else
+		e_source_config_insert_widget (
+			config, NULL, _("Name:"),
+			config->priv->name_entry);
+
+	source_config_init_backends (config);
+}
+
+static void
+source_config_realize (GtkWidget *widget)
+{
+	ESourceConfig *config;
+	ESource *original_source;
+
+	/* Chain up to parent's realize() method. */
+	GTK_WIDGET_CLASS (e_source_config_parent_class)->realize (widget);
+
+	/* Do this after constructed() so subclasses can fully
+	 * initialize themselves before we add candidates. */
+
+	config = E_SOURCE_CONFIG (widget);
+	original_source = e_source_config_get_original_source (config);
+
+	if (original_source == NULL)
+		source_config_init_for_adding_source (config);
+	else
+		source_config_init_for_editing_source (config);
+}
+
+static GList *
+source_config_list_eligible_collections (ESourceConfig *config)
+{
+	ESourceRegistry *registry;
+	GQueue trash = G_QUEUE_INIT;
+	GList *list, *link;
+	const gchar *extension_name;
+
+	extension_name = E_SOURCE_EXTENSION_COLLECTION;
+	registry = e_source_config_get_registry (config);
+
+	list = e_source_registry_list_sources (registry, extension_name);
+
+	for (link = list; link != NULL; link = g_list_next (link)) {
+		ESource *source = E_SOURCE (link->data);
+		gboolean elligible;
+
+		elligible =
+			e_source_get_enabled (source) &&
+			e_source_get_remote_creatable (source);
+
+		if (!elligible)
+			g_queue_push_tail (&trash, link);
+	}
+
+	/* Remove ineligible collections from the list. */
+	while ((link = g_queue_pop_head (&trash)) != NULL) {
+		g_object_unref (link->data);
+		list = g_list_delete_link (list, link);
+	}
+
+	return list;
+}
+
+static void
+source_config_init_candidate (ESourceConfig *config,
+                              ESource *scratch_source)
+{
+	g_object_bind_property (
+		scratch_source, "display-name",
+		config->priv->name_label, "label",
+		G_BINDING_SYNC_CREATE);
+
+	g_object_bind_property (
+		scratch_source, "display-name",
+		config->priv->name_entry, "text",
+		G_BINDING_BIDIRECTIONAL |
+		G_BINDING_SYNC_CREATE);
+}
+
+static gboolean
+source_config_check_complete (ESourceConfig *config,
+                              ESource *scratch_source)
+{
+	GtkEntry *name_entry;
+	GtkComboBox *type_combo;
+	const gchar *text;
+
+	/* Make sure the Type: combo box has a valid item. */
+	type_combo = GTK_COMBO_BOX (config->priv->type_combo);
+	if (gtk_combo_box_get_active (type_combo) < 0)
+		return FALSE;
+
+	/* Make sure the Name: entry field is not empty. */
+	name_entry = GTK_ENTRY (config->priv->name_entry);
+	text = gtk_entry_get_text (name_entry);
+	if (text == NULL || *text == '\0')
+		return FALSE;
+
+	return TRUE;
+}
+
+static void
+source_config_commit_changes (ESourceConfig *config,
+                              ESource *scratch_source)
+{
+	/* Placeholder so subclasses can safely chain up. */
+}
+
+static void
+source_config_resize_window (ESourceConfig *config)
+{
+	GtkWidget *toplevel;
+
+	/* Expand or shrink our parent window vertically to accommodate
+	 * the newly selected backend's options.  Some backends have tons
+	 * of options, some have few.  This avoids the case where you
+	 * select a backend with tons of options and then a backend with
+	 * few options and wind up with lots of unused vertical space. */
+
+	toplevel = gtk_widget_get_toplevel (GTK_WIDGET (config));
+
+	if (GTK_IS_WINDOW (toplevel)) {
+		GtkWindow *window = GTK_WINDOW (toplevel);
+		GtkAllocation allocation;
+
+		gtk_widget_get_allocation (toplevel, &allocation);
+		gtk_window_resize (window, allocation.width, 1);
+	}
+}
+
+static gboolean
+source_config_check_complete_accumulator (GSignalInvocationHint *ihint,
+                                          GValue *return_accu,
+                                          const GValue *handler_return,
+                                          gpointer unused)
+{
+	gboolean v_boolean;
+
+	/* Abort emission if a handler returns FALSE. */
+	v_boolean = g_value_get_boolean (handler_return);
+	g_value_set_boolean (return_accu, v_boolean);
+
+	return v_boolean;
+}
+
+static void
+e_source_config_class_init (ESourceConfigClass *class)
+{
+	GObjectClass *object_class;
+	GtkWidgetClass *widget_class;
+
+	g_type_class_add_private (class, sizeof (ESourceConfigPrivate));
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->set_property = source_config_set_property;
+	object_class->get_property = source_config_get_property;
+	object_class->dispose = source_config_dispose;
+	object_class->finalize = source_config_finalize;
+	object_class->constructed = source_config_constructed;
+
+	widget_class = GTK_WIDGET_CLASS (class);
+	widget_class->realize = source_config_realize;
+
+	class->list_eligible_collections =
+		source_config_list_eligible_collections;
+	class->init_candidate = source_config_init_candidate;
+	class->check_complete = source_config_check_complete;
+	class->commit_changes = source_config_commit_changes;
+	class->resize_window = source_config_resize_window;
+
+	g_object_class_install_property (
+		object_class,
+		PROP_COLLECTION_SOURCE,
+		g_param_spec_object (
+			"collection-source",
+			"Collection Source",
+			"The collection ESource to which "
+			"the ESource being edited belongs",
+			E_TYPE_SOURCE,
+			G_PARAM_READABLE |
+			G_PARAM_STATIC_STRINGS));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_COMPLETE,
+		g_param_spec_boolean (
+			"complete",
+			"Complete",
+			"Are the required fields complete?",
+			FALSE,
+			G_PARAM_READABLE |
+			G_PARAM_STATIC_STRINGS));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_ORIGINAL_SOURCE,
+		g_param_spec_object (
+			"original-source",
+			"Original Source",
+			"The original ESource",
+			E_TYPE_SOURCE,
+			G_PARAM_READWRITE |
+			G_PARAM_CONSTRUCT_ONLY |
+			G_PARAM_STATIC_STRINGS));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_REGISTRY,
+		g_param_spec_object (
+			"registry",
+			"Registry",
+			"Registry of ESources",
+			E_TYPE_SOURCE_REGISTRY,
+			G_PARAM_READWRITE |
+			G_PARAM_CONSTRUCT_ONLY |
+			G_PARAM_STATIC_STRINGS));
+
+	signals[CHECK_COMPLETE] = g_signal_new (
+		"check-complete",
+		G_TYPE_FROM_CLASS (class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ESourceConfigClass, check_complete),
+		source_config_check_complete_accumulator, NULL,
+		e_marshal_BOOLEAN__OBJECT,
+		G_TYPE_BOOLEAN, 1,
+		E_TYPE_SOURCE);
+
+	signals[COMMIT_CHANGES] = g_signal_new (
+		"commit-changes",
+		G_TYPE_FROM_CLASS (class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ESourceConfigClass, commit_changes),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__OBJECT,
+		G_TYPE_NONE, 1,
+		E_TYPE_SOURCE);
+
+	signals[INIT_CANDIDATE] = g_signal_new (
+		"init-candidate",
+		G_TYPE_FROM_CLASS (class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ESourceConfigClass, init_candidate),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__OBJECT,
+		G_TYPE_NONE, 1,
+		E_TYPE_SOURCE);
+
+	signals[RESIZE_WINDOW] = g_signal_new (
+		"resize-window",
+		G_TYPE_FROM_CLASS (class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ESourceConfigClass, resize_window),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__VOID,
+		G_TYPE_NONE, 0);
+}
+
+static void
+e_source_config_init (ESourceConfig *config)
+{
+	GPtrArray *candidates;
+	GtkSizeGroup *size_group;
+	PangoAttribute *attr;
+	PangoAttrList *attr_list;
+	GtkWidget *widget;
+
+	/* The candidates array holds scratch ESources, one for each
+	 * item in the "type" combo box.  Scratch ESources are never
+	 * added to the registry, so backend extensions can make any
+	 * changes they want to them.  Whichever scratch ESource is
+	 * "active" (selected in the "type" combo box) when the user
+	 * clicks OK wins and is written to disk.  The others are
+	 * discarded. */
+	candidates = g_ptr_array_new_with_free_func (
+		(GDestroyNotify) source_config_free_candidate);
+
+	/* The size group is used for caption labels. */
+	size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
+
+	gtk_box_set_spacing (GTK_BOX (config), 6);
+
+	gtk_orientable_set_orientation (
+		GTK_ORIENTABLE (config), GTK_ORIENTATION_VERTICAL);
+
+	config->priv = E_SOURCE_CONFIG_GET_PRIVATE (config);
+	config->priv->candidates = candidates;
+	config->priv->size_group = size_group;
+
+	attr_list = pango_attr_list_new ();
+
+	attr = pango_attr_weight_new (PANGO_WEIGHT_BOLD);
+	pango_attr_list_insert (attr_list, attr);
+
+	/* Either the source type combo box or the label is shown,
+	 * never both.  But we create both widgets and keep them
+	 * both up-to-date because it makes the logic simpler. */
+
+	widget = gtk_label_new (NULL);
+	gtk_misc_set_alignment (GTK_MISC (widget), 0.0, 0.5);
+	gtk_label_set_attributes (GTK_LABEL (widget), attr_list);
+	config->priv->type_label = g_object_ref_sink (widget);
+	gtk_widget_show (widget);
+
+	widget = gtk_combo_box_text_new ();
+	config->priv->type_combo = g_object_ref_sink (widget);
+	gtk_widget_show (widget);
+
+	/* Similarly for the display name.  Either the text entry
+	 * or the label is shown, depending on whether the source
+	 * is a collection member (new sources never are). */
+
+	widget = gtk_label_new (NULL);
+	gtk_misc_set_alignment (GTK_MISC (widget), 0.0, 0.5);
+	gtk_label_set_attributes (GTK_LABEL (widget), attr_list);
+	config->priv->name_label = g_object_ref_sink (widget);
+	gtk_widget_show (widget);
+
+	widget = gtk_entry_new ();
+	gtk_entry_set_activates_default (GTK_ENTRY (widget), TRUE);
+	config->priv->name_entry = g_object_ref_sink (widget);
+	gtk_widget_show (widget);
+
+	/* The backend box holds backend-specific options.  Each backend
+	 * gets a child widget.  Only one child widget is visible at once. */
+	widget = gtk_vbox_new (FALSE, 12);
+	gtk_box_pack_end (GTK_BOX (config), widget, TRUE, TRUE, 0);
+	config->priv->backend_box = g_object_ref (widget);
+	gtk_widget_show (widget);
+
+	pango_attr_list_unref (attr_list);
+
+	g_signal_connect (
+		config->priv->type_combo, "changed",
+		G_CALLBACK (source_config_type_combo_changed_cb), config);
+}
+
+GtkWidget *
+e_source_config_new (ESourceRegistry *registry,
+                     ESource *original_source)
+{
+	g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), NULL);
+
+	if (original_source != NULL)
+		g_return_val_if_fail (E_IS_SOURCE (original_source), NULL);
+
+	return g_object_new (
+		E_TYPE_SOURCE_CONFIG, "registry", registry,
+		"original-source", original_source, NULL);
+}
+
+void
+e_source_config_insert_widget (ESourceConfig *config,
+                               ESource *scratch_source,
+                               const gchar *caption,
+                               GtkWidget *widget)
+{
+	GtkWidget *hbox;
+	GtkWidget *vbox;
+	GtkWidget *label;
+
+	g_return_if_fail (E_IS_SOURCE_CONFIG (config));
+	g_return_if_fail (GTK_IS_WIDGET (widget));
+
+	if (scratch_source == NULL)
+		vbox = GTK_WIDGET (config);
+	else
+		vbox = e_source_config_get_page (config, scratch_source);
+
+	hbox = gtk_hbox_new (FALSE, 12);
+	gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, TRUE, 0);
+
+	g_object_bind_property (
+		widget, "visible",
+		hbox, "visible",
+		G_BINDING_SYNC_CREATE);
+
+	label = gtk_label_new (caption);
+	gtk_misc_set_alignment (GTK_MISC (label), 1.0, 0.5);
+	gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, TRUE, 0);
+	gtk_size_group_add_widget (config->priv->size_group, label);
+	gtk_widget_show (label);
+
+	gtk_box_pack_start (GTK_BOX (hbox), widget, TRUE, TRUE, 0);
+}
+
+GtkWidget *
+e_source_config_get_page (ESourceConfig *config,
+                          ESource *scratch_source)
+{
+	Candidate *candidate;
+	GtkWidget *page = NULL;
+	GPtrArray *array;
+	gint index;
+
+	g_return_val_if_fail (E_IS_SOURCE_CONFIG (config), NULL);
+	g_return_val_if_fail (E_IS_SOURCE (scratch_source), NULL);
+
+	array = config->priv->candidates;
+
+	for (index = 0; page == NULL && index < array->len; index++) {
+		candidate = g_ptr_array_index (array, index);
+		if (e_source_equal (scratch_source, candidate->scratch_source))
+			page = candidate->page;
+	}
+
+	g_return_val_if_fail (GTK_IS_BOX (page), NULL);
+
+	return page;
+}
+
+const gchar *
+e_source_config_get_backend_extension_name (ESourceConfig *config)
+{
+	ESourceConfigClass *class;
+
+	g_return_val_if_fail (E_IS_SOURCE_CONFIG (config), NULL);
+
+	class = E_SOURCE_CONFIG_GET_CLASS (config);
+	g_return_val_if_fail (class->get_backend_extension_name != NULL, NULL);
+
+	return class->get_backend_extension_name (config);
+}
+
+GList *
+e_source_config_list_eligible_collections (ESourceConfig *config)
+{
+	ESourceConfigClass *class;
+
+	g_return_val_if_fail (E_IS_SOURCE_CONFIG (config), NULL);
+
+	class = E_SOURCE_CONFIG_GET_CLASS (config);
+	g_return_val_if_fail (class->list_eligible_collections != NULL, NULL);
+
+	return class->list_eligible_collections (config);
+}
+
+gboolean
+e_source_config_check_complete (ESourceConfig *config)
+{
+	Candidate *candidate;
+	gboolean complete;
+
+	g_return_val_if_fail (E_IS_SOURCE_CONFIG (config), FALSE);
+
+	candidate = source_config_get_active_candidate (config);
+	g_return_val_if_fail (candidate != NULL, FALSE);
+
+	g_signal_emit (
+		config, signals[CHECK_COMPLETE], 0,
+		candidate->scratch_source, &complete);
+
+	complete &= e_source_config_backend_check_complete (
+		candidate->backend, candidate->scratch_source);
+
+	/* XXX Emitting "notify::complete" may cause this function
+	 *     to be called repeatedly by signal handlers.  The IF
+	 *     check below should break any recursive cycles.  Not
+	 *     very efficient but I think we can live with it. */
+
+	if (complete != config->priv->complete) {
+		config->priv->complete = complete;
+		g_object_notify (G_OBJECT (config), "complete");
+	}
+
+	return complete;
+}
+
+ESource *
+e_source_config_get_original_source (ESourceConfig *config)
+{
+	g_return_val_if_fail (E_IS_SOURCE_CONFIG (config), NULL);
+
+	return config->priv->original_source;
+}
+
+ESource *
+e_source_config_get_collection_source (ESourceConfig *config)
+{
+	g_return_val_if_fail (E_IS_SOURCE_CONFIG (config), NULL);
+
+	return config->priv->collection_source;
+}
+
+ESourceRegistry *
+e_source_config_get_registry (ESourceConfig *config)
+{
+	g_return_val_if_fail (E_IS_SOURCE_CONFIG (config), NULL);
+
+	return config->priv->registry;
+}
+
+void
+e_source_config_resize_window (ESourceConfig *config)
+{
+	g_return_if_fail (E_IS_SOURCE_CONFIG (config));
+
+	g_signal_emit (config, signals[RESIZE_WINDOW], 0);
+}
+
+/* Helper for e_source_config_commit() */
+static void
+source_config_commit_cb (GObject *object,
+                         GAsyncResult *result,
+                         gpointer user_data)
+{
+	GSimpleAsyncResult *simple;
+	GError *error = NULL;
+
+	simple = G_SIMPLE_ASYNC_RESULT (user_data);
+
+	e_source_registry_commit_source_finish (
+		E_SOURCE_REGISTRY (object), result, &error);
+
+	if (error != NULL)
+		g_simple_async_result_take_error (simple, error);
+
+	g_simple_async_result_complete (simple);
+	g_object_unref (simple);
+}
+
+void
+e_source_config_commit (ESourceConfig *config,
+                        GCancellable *cancellable,
+                        GAsyncReadyCallback callback,
+                        gpointer user_data)
+{
+	GSimpleAsyncResult *simple;
+	ESourceRegistry *registry;
+	Candidate *candidate;
+
+	g_return_if_fail (E_IS_SOURCE_CONFIG (config));
+
+	registry = e_source_config_get_registry (config);
+
+	candidate = source_config_get_active_candidate (config);
+	g_return_if_fail (candidate != NULL);
+
+	e_source_config_backend_commit_changes (
+		candidate->backend, candidate->scratch_source);
+
+	g_signal_emit (
+		config, signals[COMMIT_CHANGES], 0,
+		candidate->scratch_source);
+
+	simple = g_simple_async_result_new (
+		G_OBJECT (config), callback,
+		user_data, e_source_config_commit);
+
+	e_source_registry_commit_source (
+		registry, candidate->scratch_source,
+		cancellable, source_config_commit_cb, simple);
+}
+
+gboolean
+e_source_config_commit_finish (ESourceConfig *config,
+                               GAsyncResult *result,
+                               GError **error)
+{
+	GSimpleAsyncResult *simple;
+
+	g_return_val_if_fail (
+		g_simple_async_result_is_valid (
+		result, G_OBJECT (config),
+		e_source_config_commit), FALSE);
+
+	simple = G_SIMPLE_ASYNC_RESULT (result);
+
+	/* Assume success unless a GError is set. */
+	return !g_simple_async_result_propagate_error (simple, error);
+}
+
+void
+e_source_config_add_refresh_interval (ESourceConfig *config,
+                                      ESource *scratch_source)
+{
+	GtkWidget *widget;
+	GtkWidget *container;
+	ESourceExtension *extension;
+	const gchar *extension_name;
+
+	g_return_if_fail (E_IS_SOURCE_CONFIG (config));
+	g_return_if_fail (E_IS_SOURCE (scratch_source));
+
+	extension_name = E_SOURCE_EXTENSION_REFRESH;
+	extension = e_source_get_extension (scratch_source, extension_name);
+
+	widget = gtk_alignment_new (0.0, 0.5, 0.0, 0.0);
+	e_source_config_insert_widget (config, scratch_source, NULL, widget);
+	gtk_widget_show (widget);
+
+	container = widget;
+
+	widget = gtk_hbox_new (FALSE, 6);
+	gtk_container_add (GTK_CONTAINER (container), widget);
+	gtk_widget_show (widget);
+
+	container = widget;
+
+	/* Translators: This is the first of a sequence of widgets:
+	 * "Refresh every [NUMERIC_ENTRY] [TIME_UNITS_COMBO]" */
+	widget = gtk_label_new (_("Refresh every"));
+	gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+	gtk_widget_show (widget);
+
+	widget = e_interval_chooser_new ();
+	gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+	gtk_widget_show (widget);
+
+	g_object_bind_property (
+		extension, "interval-minutes",
+		widget, "interval-minutes",
+		G_BINDING_BIDIRECTIONAL |
+		G_BINDING_SYNC_CREATE);
+}
+
+void
+e_source_config_add_secure_connection (ESourceConfig *config,
+                                       ESource *scratch_source)
+{
+	GtkWidget *widget;
+	ESourceExtension *extension;
+	const gchar *extension_name;
+	const gchar *label;
+
+	g_return_if_fail (E_IS_SOURCE_CONFIG (config));
+	g_return_if_fail (E_IS_SOURCE (scratch_source));
+
+	extension_name = E_SOURCE_EXTENSION_SECURITY;
+	extension = e_source_get_extension (scratch_source, extension_name);
+
+	label = _("Use a secure connection");
+	widget = gtk_check_button_new_with_label (label);
+	e_source_config_insert_widget (config, scratch_source, NULL, widget);
+	gtk_widget_show (widget);
+
+	g_object_bind_property (
+		extension, "secure",
+		widget, "active",
+		G_BINDING_BIDIRECTIONAL |
+		G_BINDING_SYNC_CREATE);
+}
+
+static gboolean
+secure_to_port_cb (GBinding *binding,
+                   const GValue *source_value,
+                   GValue *target_value,
+                   gpointer user_data)
+{
+	GObject *authentication_extension;
+	guint16 port;
+
+	authentication_extension = g_binding_get_target (binding);
+	g_object_get (authentication_extension, "port", &port, NULL);
+
+	if (port == 80 || port == 443 || port == 0)
+		port = g_value_get_boolean (source_value) ? 443 : 80;
+
+	g_value_set_uint (target_value, port);
+
+	return TRUE;
+}
+
+void
+e_source_config_add_secure_connection_for_webdav (ESourceConfig *config,
+                                                  ESource *scratch_source)
+{
+	GtkWidget *widget1;
+	GtkWidget *widget2;
+	ESourceExtension *extension;
+	ESourceAuthentication *authentication_extension;
+	const gchar *extension_name;
+	const gchar *label;
+
+	g_return_if_fail (E_IS_SOURCE_CONFIG (config));
+	g_return_if_fail (E_IS_SOURCE (scratch_source));
+
+	extension_name = E_SOURCE_EXTENSION_SECURITY;
+	extension = e_source_get_extension (scratch_source, extension_name);
+
+	label = _("Use a secure connection");
+	widget1 = gtk_check_button_new_with_label (label);
+	e_source_config_insert_widget (config, scratch_source, NULL, widget1);
+	gtk_widget_show (widget1);
+
+	g_object_bind_property (
+		extension, "secure",
+		widget1, "active",
+		G_BINDING_BIDIRECTIONAL |
+		G_BINDING_SYNC_CREATE);
+
+	extension_name = E_SOURCE_EXTENSION_AUTHENTICATION;
+	authentication_extension = e_source_get_extension (scratch_source, extension_name);
+
+	g_object_bind_property_full (
+		extension, "secure",
+		authentication_extension, "port",
+		G_BINDING_DEFAULT,
+		secure_to_port_cb,
+		NULL, NULL, NULL);
+
+	extension_name = E_SOURCE_EXTENSION_WEBDAV_BACKEND;
+	extension = e_source_get_extension (scratch_source, extension_name);
+
+	label = _("Ignore invalid SSL certificate");
+	widget2 = gtk_check_button_new_with_label (label);
+	gtk_widget_set_margin_left (widget2, 24);
+	e_source_config_insert_widget (config, scratch_source, NULL, widget2);
+	gtk_widget_show (widget2);
+
+	g_object_bind_property (
+		widget1, "active",
+		widget2, "sensitive",
+		G_BINDING_SYNC_CREATE);
+
+	g_object_bind_property (
+		extension, "ignore-invalid-cert",
+		widget2, "active",
+		G_BINDING_BIDIRECTIONAL |
+		G_BINDING_SYNC_CREATE);
+}
+
+void
+e_source_config_add_user_entry (ESourceConfig *config,
+                                ESource *scratch_source)
+{
+	GtkWidget *widget;
+	ESource *original_source;
+	ESourceExtension *extension;
+	const gchar *extension_name;
+
+	g_return_if_fail (E_IS_SOURCE_CONFIG (config));
+	g_return_if_fail (E_IS_SOURCE (scratch_source));
+
+	extension_name = E_SOURCE_EXTENSION_AUTHENTICATION;
+	extension = e_source_get_extension (scratch_source, extension_name);
+
+	original_source = e_source_config_get_original_source (config);
+
+	widget = gtk_entry_new ();
+	e_source_config_insert_widget (
+		config, scratch_source, _("User"), widget);
+	gtk_widget_show (widget);
+
+	g_object_bind_property (
+		extension, "user",
+		widget, "text",
+		G_BINDING_BIDIRECTIONAL |
+		G_BINDING_SYNC_CREATE);
+
+	/* If this is a new data source, initialize the
+	 * GtkEntry to the user name of the current user. */
+	if (original_source == NULL)
+		gtk_entry_set_text (GTK_ENTRY (widget), g_get_user_name ());
+}
+
diff --git a/e-util/e-source-config.h b/e-util/e-source-config.h
new file mode 100644
index 0000000..3868c03
--- /dev/null
+++ b/e-util/e-source-config.h
@@ -0,0 +1,120 @@
+/*
+ * e-source-config.h
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_SOURCE_CONFIG_H
+#define E_SOURCE_CONFIG_H
+
+#include <gtk/gtk.h>
+#include <libedataserver/libedataserver.h>
+
+/* Standard GObject macros */
+#define E_TYPE_SOURCE_CONFIG \
+	(e_source_config_get_type ())
+#define E_SOURCE_CONFIG(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_SOURCE_CONFIG, ESourceConfig))
+#define E_SOURCE_CONFIG_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_SOURCE_CONFIG, ESourceConfigClass))
+#define E_IS_SOURCE_CONFIG(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_SOURCE_CONFIG))
+#define E_IS_SOURCE_CONFIG_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_SOURCE_CONFIG))
+#define E_SOURCE_CONFIG_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_SOURCE_CONFIG, ESourceConfigClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ESourceConfig ESourceConfig;
+typedef struct _ESourceConfigClass ESourceConfigClass;
+typedef struct _ESourceConfigPrivate ESourceConfigPrivate;
+
+struct _ESourceConfig {
+	GtkBox parent;
+	ESourceConfigPrivate *priv;
+};
+
+struct _ESourceConfigClass {
+	GtkBoxClass parent_class;
+
+	/* Methods */
+	const gchar *	(*get_backend_extension_name)
+						(ESourceConfig *config);
+	GList *		(*list_eligible_collections)
+						(ESourceConfig *config);
+
+	/* Signals */
+	void		(*init_candidate)	(ESourceConfig *config,
+						 ESource *scratch_source);
+	gboolean	(*check_complete)	(ESourceConfig *config,
+						 ESource *scratch_source);
+	void		(*commit_changes)	(ESourceConfig *config,
+						 ESource *scratch_source);
+	void		(*resize_window)	(ESourceConfig *config);
+};
+
+GType		e_source_config_get_type	(void) G_GNUC_CONST;
+GtkWidget *	e_source_config_new		(ESourceRegistry *registry,
+						 ESource *original_source);
+void		e_source_config_insert_widget	(ESourceConfig *config,
+						 ESource *scratch_source,
+						 const gchar *caption,
+						 GtkWidget *widget);
+GtkWidget *	e_source_config_get_page	(ESourceConfig *config,
+						 ESource *scratch_source);
+const gchar *	e_source_config_get_backend_extension_name
+						(ESourceConfig *config);
+GList *		e_source_config_list_eligible_collections
+						(ESourceConfig *config);
+gboolean	e_source_config_check_complete	(ESourceConfig *config);
+ESource *	e_source_config_get_original_source
+						(ESourceConfig *config);
+ESource *	e_source_config_get_collection_source
+						(ESourceConfig *config);
+ESourceRegistry *
+		e_source_config_get_registry	(ESourceConfig *config);
+void		e_source_config_resize_window	(ESourceConfig *config);
+void		e_source_config_commit		(ESourceConfig *config,
+						 GCancellable *cancellable,
+						 GAsyncReadyCallback callback,
+						 gpointer user_data);
+gboolean	e_source_config_commit_finish	(ESourceConfig *config,
+						 GAsyncResult *result,
+						 GError **error);
+
+/* Convenience functions for common settings. */
+void		e_source_config_add_refresh_interval
+						(ESourceConfig *config,
+						 ESource *scratch_source);
+void		e_source_config_add_secure_connection
+						(ESourceConfig *config,
+						 ESource *scratch_source);
+void		e_source_config_add_secure_connection_for_webdav
+						(ESourceConfig *config,
+						 ESource *scratch_source);
+void		e_source_config_add_user_entry	(ESourceConfig *config,
+						 ESource *scratch_source);
+
+#endif /* E_SOURCE_CONFIG_H */
diff --git a/e-util/e-source-selector-dialog.c b/e-util/e-source-selector-dialog.c
new file mode 100644
index 0000000..68e29fd
--- /dev/null
+++ b/e-util/e-source-selector-dialog.c
@@ -0,0 +1,453 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* e-source-selector-dialog.c
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This program 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 program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * Author: Rodrigo Moya <rodrigo novell com>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <glib/gi18n-lib.h>
+#include "e-source-selector.h"
+#include "e-source-selector-dialog.h"
+
+#define E_SOURCE_SELECTOR_DIALOG_GET_PRIVATE(obj) \
+	(G_TYPE_INSTANCE_GET_PRIVATE \
+	((obj), E_TYPE_SOURCE_SELECTOR_DIALOG, ESourceSelectorDialogPrivate))
+
+struct _ESourceSelectorDialogPrivate {
+	GtkWidget *selector;
+	ESourceRegistry *registry;
+	ESource *selected_source;
+	gchar *extension_name;
+};
+
+enum {
+	PROP_0,
+	PROP_EXTENSION_NAME,
+	PROP_REGISTRY,
+	PROP_SELECTOR
+};
+
+G_DEFINE_TYPE (
+	ESourceSelectorDialog,
+	e_source_selector_dialog,
+	GTK_TYPE_DIALOG)
+
+static void
+source_selector_dialog_row_activated_cb (GtkTreeView *tree_view,
+                                         GtkTreePath *path,
+                                         GtkTreeViewColumn *column,
+                                         GtkWidget *dialog)
+{
+	gtk_dialog_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK);
+}
+
+static void
+primary_selection_changed_cb (ESourceSelector *selector,
+                              ESourceSelectorDialog *dialog)
+{
+	ESourceSelectorDialogPrivate *priv = dialog->priv;
+
+	if (priv->selected_source != NULL)
+		g_object_unref (priv->selected_source);
+	priv->selected_source =
+		e_source_selector_ref_primary_selection (selector);
+
+	/* FIXME Add an API for "except-source" or to
+	 *       get the ESourceSelector from outside. */
+	if (priv->selected_source != NULL) {
+		ESource *except_source;
+
+		except_source = g_object_get_data (
+			G_OBJECT (dialog), "except-source");
+
+		if (except_source != NULL)
+			if (e_source_equal (except_source, priv->selected_source)) {
+				g_object_unref (priv->selected_source);
+				priv->selected_source = NULL;
+			}
+	}
+
+	gtk_dialog_set_response_sensitive (
+		GTK_DIALOG (dialog), GTK_RESPONSE_OK,
+		(priv->selected_source != NULL));
+}
+
+static void
+source_selector_dialog_set_extension_name (ESourceSelectorDialog *dialog,
+                                           const gchar *extension_name)
+{
+	g_return_if_fail (extension_name != NULL);
+	g_return_if_fail (dialog->priv->extension_name == NULL);
+
+	dialog->priv->extension_name = g_strdup (extension_name);
+}
+
+static void
+source_selector_dialog_set_registry (ESourceSelectorDialog *dialog,
+                                     ESourceRegistry *registry)
+{
+	g_return_if_fail (E_IS_SOURCE_REGISTRY (registry));
+	g_return_if_fail (dialog->priv->registry == NULL);
+
+	dialog->priv->registry = g_object_ref (registry);
+}
+
+static void
+source_selector_dialog_set_property (GObject *object,
+                                     guint property_id,
+                                     const GValue *value,
+                                     GParamSpec *pspec)
+{
+	switch (property_id) {
+		case PROP_EXTENSION_NAME:
+			source_selector_dialog_set_extension_name (
+				E_SOURCE_SELECTOR_DIALOG (object),
+				g_value_get_string (value));
+			return;
+
+		case PROP_REGISTRY:
+			source_selector_dialog_set_registry (
+				E_SOURCE_SELECTOR_DIALOG (object),
+				g_value_get_object (value));
+			return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+source_selector_dialog_get_property (GObject *object,
+                                     guint property_id,
+                                     GValue *value,
+                                     GParamSpec *pspec)
+{
+	switch (property_id) {
+		case PROP_EXTENSION_NAME:
+			g_value_set_string (
+				value,
+				e_source_selector_dialog_get_extension_name (
+				E_SOURCE_SELECTOR_DIALOG (object)));
+			return;
+
+		case PROP_REGISTRY:
+			g_value_set_object (
+				value,
+				e_source_selector_dialog_get_registry (
+				E_SOURCE_SELECTOR_DIALOG (object)));
+			return;
+
+		case PROP_SELECTOR:
+			g_value_set_object (
+				value,
+				e_source_selector_dialog_get_selector (
+				E_SOURCE_SELECTOR_DIALOG (object)));
+			return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+source_selector_dialog_dispose (GObject *object)
+{
+	ESourceSelectorDialogPrivate *priv;
+
+	priv = E_SOURCE_SELECTOR_DIALOG_GET_PRIVATE (object);
+
+	if (priv->registry != NULL) {
+		g_object_unref (priv->registry);
+		priv->registry = NULL;
+	}
+
+	if (priv->selected_source != NULL) {
+		g_object_unref (priv->selected_source);
+		priv->selected_source = NULL;
+	}
+
+	/* Chain up to parent's dispose() method. */
+	G_OBJECT_CLASS (e_source_selector_dialog_parent_class)->dispose (object);
+}
+
+static void
+source_selector_dialog_finalize (GObject *object)
+{
+	ESourceSelectorDialogPrivate *priv;
+
+	priv = E_SOURCE_SELECTOR_DIALOG_GET_PRIVATE (object);
+
+	g_free (priv->extension_name);
+
+	/* Chain up to parent's finalize() method. */
+	G_OBJECT_CLASS (e_source_selector_dialog_parent_class)->finalize (object);
+}
+
+static void
+source_selector_dialog_constructed (GObject *object)
+{
+	ESourceSelectorDialog *dialog;
+	GtkWidget *label, *hgrid;
+	GtkWidget *container;
+	GtkWidget *widget;
+	gchar *label_text;
+
+	dialog = E_SOURCE_SELECTOR_DIALOG (object);
+
+	container = gtk_dialog_get_content_area (GTK_DIALOG (dialog));
+
+	widget = g_object_new (GTK_TYPE_GRID,
+		"orientation", GTK_ORIENTATION_VERTICAL,
+		"column-homogeneous", FALSE,
+		"row-spacing", 12,
+		NULL);
+	gtk_container_set_border_width (GTK_CONTAINER (widget), 12);
+	gtk_box_pack_start (GTK_BOX (container), widget, TRUE, TRUE, 0);
+	gtk_widget_show (widget);
+
+	container = widget;
+
+	label_text = g_strdup_printf ("<b>%s</b>", _("_Destination"));
+	label = gtk_label_new_with_mnemonic (label_text);
+	gtk_label_set_use_markup (GTK_LABEL (label), TRUE);
+	gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5);
+	gtk_container_add (GTK_CONTAINER (container), label);
+	gtk_widget_show (label);
+	g_free (label_text);
+
+	hgrid = g_object_new (GTK_TYPE_GRID,
+		"orientation", GTK_ORIENTATION_HORIZONTAL,
+		"row-homogeneous", FALSE,
+		"column-spacing", 12,
+		"vexpand", TRUE,
+		"valign", GTK_ALIGN_FILL,
+		NULL);
+	gtk_container_add (GTK_CONTAINER (container), hgrid);
+	gtk_widget_show (hgrid);
+
+	widget = gtk_label_new ("");
+	gtk_container_add (GTK_CONTAINER (hgrid), widget);
+	gtk_widget_show (widget);
+
+	widget = gtk_scrolled_window_new (NULL, NULL);
+	gtk_scrolled_window_set_policy (
+		GTK_SCROLLED_WINDOW (widget),
+		GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
+	gtk_scrolled_window_set_shadow_type (
+		GTK_SCROLLED_WINDOW (widget), GTK_SHADOW_IN);
+	gtk_widget_set_hexpand (widget, TRUE);
+	gtk_widget_set_halign (widget, GTK_ALIGN_FILL);
+	gtk_widget_set_vexpand (widget, TRUE);
+	gtk_widget_set_valign (widget, GTK_ALIGN_FILL);
+	gtk_container_add (GTK_CONTAINER (hgrid), widget);
+	gtk_widget_show (widget);
+
+	container = widget;
+
+	widget = e_source_selector_new (
+		dialog->priv->registry,
+		dialog->priv->extension_name);
+	e_source_selector_set_show_toggles (E_SOURCE_SELECTOR (widget), FALSE);
+	gtk_label_set_mnemonic_widget (GTK_LABEL (label), widget);
+	gtk_container_add (GTK_CONTAINER (container), widget);
+	dialog->priv->selector = widget;
+	gtk_widget_show (widget);
+
+	g_signal_connect (
+		widget, "row_activated",
+		G_CALLBACK (source_selector_dialog_row_activated_cb), dialog);
+	g_signal_connect (
+		widget, "primary_selection_changed",
+		G_CALLBACK (primary_selection_changed_cb), dialog);
+}
+
+static void
+e_source_selector_dialog_class_init (ESourceSelectorDialogClass *class)
+{
+	GObjectClass *object_class;
+
+	g_type_class_add_private (class, sizeof (ESourceSelectorDialogPrivate));
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->set_property = source_selector_dialog_set_property;
+	object_class->get_property = source_selector_dialog_get_property;
+	object_class->dispose = source_selector_dialog_dispose;
+	object_class->finalize = source_selector_dialog_finalize;
+	object_class->constructed = source_selector_dialog_constructed;
+
+	g_object_class_install_property (
+		object_class,
+		PROP_EXTENSION_NAME,
+		g_param_spec_string (
+			"extension-name",
+			NULL,
+			NULL,
+			NULL,
+			G_PARAM_WRITABLE |
+			G_PARAM_CONSTRUCT_ONLY));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_REGISTRY,
+		g_param_spec_object (
+			"registry",
+			NULL,
+			NULL,
+			E_TYPE_SOURCE_REGISTRY,
+			G_PARAM_WRITABLE |
+			G_PARAM_CONSTRUCT_ONLY));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_SELECTOR,
+		g_param_spec_object (
+			"selector",
+			NULL,
+			NULL,
+			E_TYPE_SOURCE_SELECTOR,
+			G_PARAM_READABLE));
+}
+
+static void
+e_source_selector_dialog_init (ESourceSelectorDialog *dialog)
+{
+	GtkWidget *action_area;
+	GtkWidget *content_area;
+
+	dialog->priv = E_SOURCE_SELECTOR_DIALOG_GET_PRIVATE (dialog);
+
+	action_area = gtk_dialog_get_action_area (GTK_DIALOG (dialog));
+	content_area = gtk_dialog_get_content_area (GTK_DIALOG (dialog));
+
+	gtk_window_set_title (GTK_WINDOW (dialog), _("Select destination"));
+	gtk_window_set_default_size (GTK_WINDOW (dialog), 320, 240);
+
+	gtk_widget_ensure_style (GTK_WIDGET (dialog));
+	gtk_container_set_border_width (GTK_CONTAINER (content_area), 0);
+	gtk_container_set_border_width (GTK_CONTAINER (action_area), 12);
+
+	gtk_dialog_add_buttons (
+		GTK_DIALOG (dialog),
+		GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+		GTK_STOCK_OK, GTK_RESPONSE_OK, NULL);
+	gtk_dialog_set_default_response (
+		GTK_DIALOG (dialog), GTK_RESPONSE_OK);
+	gtk_dialog_set_response_sensitive (
+		GTK_DIALOG (dialog), GTK_RESPONSE_OK, FALSE);
+}
+
+/**
+ * e_source_selector_dialog_new:
+ * @parent: a parent window
+ * @registry: an #ESourceRegistry
+ * @extension_name: the name of an #ESource extension
+ *
+ * Displays a list of sources from @registry having an extension named
+ * @extension_name in a dialog window.  The sources are grouped by backend
+ * or groupware account, which are described by the parent source.
+ *
+ * Returns: a new #ESourceSelectorDialog
+ **/
+GtkWidget *
+e_source_selector_dialog_new (GtkWindow *parent,
+                              ESourceRegistry *registry,
+                              const gchar *extension_name)
+{
+	g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), NULL);
+	g_return_val_if_fail (extension_name != NULL, NULL);
+
+	return g_object_new (
+		E_TYPE_SOURCE_SELECTOR_DIALOG,
+		"transient-for", parent,
+		"registry", registry,
+		"extension-name", extension_name,
+		NULL);
+}
+
+/**
+ * e_source_selector_dialog_get_registry:
+ * @dialog: an #ESourceSelectorDialog
+ *
+ * Returns the #ESourceRegistry passed to e_source_selector_dialog_new().
+ *
+ * Returns: the #ESourceRegistry for @dialog
+ *
+ * Since: 3.6
+ **/
+ESourceRegistry *
+e_source_selector_dialog_get_registry (ESourceSelectorDialog *dialog)
+{
+	g_return_val_if_fail (E_IS_SOURCE_SELECTOR_DIALOG (dialog), NULL);
+
+	return dialog->priv->registry;
+}
+
+/**
+ * e_source_selector_dialog_get_extension_name:
+ * @dialog: an #ESourceSelectorDialog
+ *
+ * Returns the extension name passed to e_source_selector_dialog_new().
+ *
+ * Returns: the extension name for @dialog
+ *
+ * Since: 3.6
+ **/
+const gchar *
+e_source_selector_dialog_get_extension_name (ESourceSelectorDialog *dialog)
+{
+	g_return_val_if_fail (E_IS_SOURCE_SELECTOR_DIALOG (dialog), NULL);
+
+	return dialog->priv->extension_name;
+}
+
+/**
+ * e_source_selector_dialog_get_selector:
+ * @dialog: an #ESourceSelectorDialog
+ *
+ * Returns the #ESourceSelector widget embedded in @dialog.
+ *
+ * Returns: the #ESourceSelector widget
+ *
+ * Since: 3.6
+ **/
+ESourceSelector *
+e_source_selector_dialog_get_selector (ESourceSelectorDialog *dialog)
+{
+	g_return_val_if_fail (E_IS_SOURCE_SELECTOR_DIALOG (dialog), NULL);
+
+	return E_SOURCE_SELECTOR (dialog->priv->selector);
+}
+
+/**
+ * e_source_selector_dialog_peek_primary_selection:
+ * @dialog: an #ESourceSelectorDialog
+ *
+ * Peek the currently selected source in the given @dialog.
+ *
+ * Returns: the selected #ESource
+ */
+ESource *
+e_source_selector_dialog_peek_primary_selection (ESourceSelectorDialog *dialog)
+{
+	g_return_val_if_fail (E_IS_SOURCE_SELECTOR_DIALOG (dialog), NULL);
+
+	return dialog->priv->selected_source;
+}
diff --git a/e-util/e-source-selector-dialog.h b/e-util/e-source-selector-dialog.h
new file mode 100644
index 0000000..eae45ba
--- /dev/null
+++ b/e-util/e-source-selector-dialog.h
@@ -0,0 +1,85 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* e-source-selector-dialog.h
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This program 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 program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * Author: Rodrigo Moya <rodrigo novell com>
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_SOURCE_SELECTOR_DIALOG_H
+#define E_SOURCE_SELECTOR_DIALOG_H
+
+#include <gtk/gtk.h>
+#include <e-util/e-source-selector.h>
+
+/* Standard GObject macros */
+#define E_TYPE_SOURCE_SELECTOR_DIALOG \
+	(e_source_selector_dialog_get_type ())
+#define E_SOURCE_SELECTOR_DIALOG(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_SOURCE_SELECTOR_DIALOG, ESourceSelectorDialog))
+#define E_SOURCE_SELECTOR_DIALOG_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_SOURCE_SELECTOR_DIALOG, ESourceSelectorDialogClass))
+#define E_IS_SOURCE_SELECTOR_DIALOG(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_SOURCE_SELECTOR_DIALOG))
+#define E_IS_SOURCE_SELECTOR_DIALOG_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_SOURCE_SELECTOR_DIALOG))
+#define E_SOURCE_SELECTOR_DIALOG_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_SOURCE_SELECTOR_DIALOG, ESourceSelectorDialogClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ESourceSelectorDialog ESourceSelectorDialog;
+typedef struct _ESourceSelectorDialogClass ESourceSelectorDialogClass;
+typedef struct _ESourceSelectorDialogPrivate ESourceSelectorDialogPrivate;
+
+struct _ESourceSelectorDialog {
+	GtkDialog parent;
+	ESourceSelectorDialogPrivate *priv;
+};
+
+struct _ESourceSelectorDialogClass {
+	GtkDialogClass parent_class;
+};
+
+GType		e_source_selector_dialog_get_type (void);
+GtkWidget *	e_source_selector_dialog_new	(GtkWindow *parent,
+						 ESourceRegistry *registry,
+						 const gchar *extension_name);
+ESourceRegistry *
+		e_source_selector_dialog_get_registry
+						(ESourceSelectorDialog *dialog);
+const gchar *	e_source_selector_dialog_get_extension_name
+						(ESourceSelectorDialog *dialog);
+ESourceSelector *
+		e_source_selector_dialog_get_selector
+						(ESourceSelectorDialog *dialog);
+ESource *	e_source_selector_dialog_peek_primary_selection
+						(ESourceSelectorDialog *dialog);
+
+G_END_DECLS
+
+#endif /* E_SOURCE_SELECTOR_DIALOG_H */
diff --git a/e-util/e-source-selector.c b/e-util/e-source-selector.c
new file mode 100644
index 0000000..4a75ed1
--- /dev/null
+++ b/e-util/e-source-selector.c
@@ -0,0 +1,2082 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* e-source-selector.c
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This program 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 program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * Author: Ettore Perazzoli <ettore ximian com>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+
+#include "e-cell-renderer-color.h"
+#include "e-source-selector.h"
+
+#define E_SOURCE_SELECTOR_GET_PRIVATE(obj) \
+	(G_TYPE_INSTANCE_GET_PRIVATE \
+	((obj), E_TYPE_SOURCE_SELECTOR, ESourceSelectorPrivate))
+
+typedef struct _AsyncContext AsyncContext;
+
+struct _ESourceSelectorPrivate {
+	ESourceRegistry *registry;
+	GHashTable *source_index;
+	gchar *extension_name;
+
+	GtkTreeRowReference *saved_primary_selection;
+
+	/* ESource -> GSource */
+	GHashTable *pending_writes;
+	GMainContext *main_context;
+
+	gboolean toggled_last;
+	gboolean select_new;
+	gboolean show_colors;
+	gboolean show_toggles;
+};
+
+struct _AsyncContext {
+	ESourceSelector *selector;
+	ESource *source;
+};
+
+enum {
+	PROP_0,
+	PROP_EXTENSION_NAME,
+	PROP_PRIMARY_SELECTION,
+	PROP_REGISTRY,
+	PROP_SHOW_COLORS,
+	PROP_SHOW_TOGGLES
+};
+
+enum {
+	SELECTION_CHANGED,
+	PRIMARY_SELECTION_CHANGED,
+	POPUP_EVENT,
+	DATA_DROPPED,
+	NUM_SIGNALS
+};
+
+enum {
+	COLUMN_NAME,
+	COLUMN_COLOR,
+	COLUMN_ACTIVE,
+	COLUMN_SHOW_COLOR,
+	COLUMN_SHOW_TOGGLE,
+	COLUMN_WEIGHT,
+	COLUMN_SOURCE,
+	NUM_COLUMNS
+};
+
+static guint signals[NUM_SIGNALS];
+
+G_DEFINE_TYPE (ESourceSelector, e_source_selector, GTK_TYPE_TREE_VIEW)
+
+/* ESafeToggleRenderer does not emit 'toggled' signal
+ * on 'activate' when mouse is not over the toggle. */
+
+typedef GtkCellRendererToggle ECellRendererSafeToggle;
+typedef GtkCellRendererToggleClass ECellRendererSafeToggleClass;
+
+/* Forward Declarations */
+GType e_cell_renderer_safe_toggle_get_type (void);
+
+G_DEFINE_TYPE (
+	ECellRendererSafeToggle,
+	e_cell_renderer_safe_toggle,
+	GTK_TYPE_CELL_RENDERER_TOGGLE)
+
+static gboolean
+safe_toggle_activate (GtkCellRenderer *cell,
+                      GdkEvent *event,
+                      GtkWidget *widget,
+                      const gchar *path,
+                      const GdkRectangle *background_area,
+                      const GdkRectangle *cell_area,
+                      GtkCellRendererState flags)
+{
+	gboolean point_in_cell_area = TRUE;
+
+	if (event->type == GDK_BUTTON_PRESS && cell_area != NULL) {
+		cairo_region_t *region;
+
+		region = cairo_region_create_rectangle (cell_area);
+		point_in_cell_area = cairo_region_contains_point (
+			region, event->button.x, event->button.y);
+		cairo_region_destroy (region);
+	}
+
+	if (!point_in_cell_area)
+		return FALSE;
+
+	return GTK_CELL_RENDERER_CLASS (
+		e_cell_renderer_safe_toggle_parent_class)->activate (
+		cell, event, widget, path, background_area, cell_area, flags);
+}
+
+static void
+e_cell_renderer_safe_toggle_class_init (ECellRendererSafeToggleClass *class)
+{
+	GtkCellRendererClass *cell_renderer_class;
+
+	cell_renderer_class = GTK_CELL_RENDERER_CLASS (class);
+	cell_renderer_class->activate = safe_toggle_activate;
+}
+
+static void
+e_cell_renderer_safe_toggle_init (ECellRendererSafeToggle *obj)
+{
+}
+
+static GtkCellRenderer *
+e_cell_renderer_safe_toggle_new (void)
+{
+	return g_object_new (e_cell_renderer_safe_toggle_get_type (), NULL);
+}
+
+static void
+clear_saved_primary_selection (ESourceSelector *selector)
+{
+	gtk_tree_row_reference_free (selector->priv->saved_primary_selection);
+	selector->priv->saved_primary_selection = NULL;
+}
+
+static void
+async_context_free (AsyncContext *async_context)
+{
+	if (async_context->selector != NULL)
+		g_object_unref (async_context->selector);
+
+	if (async_context->source != NULL)
+		g_object_unref (async_context->source);
+
+	g_slice_free (AsyncContext, async_context);
+}
+
+static void
+pending_writes_destroy_source (GSource *source)
+{
+	g_source_destroy (source);
+	g_source_unref (source);
+}
+
+static void
+source_selector_write_done_cb (GObject *source_object,
+                               GAsyncResult *result,
+                               gpointer user_data)
+{
+	ESource *source;
+	ESourceSelector *selector;
+	GError *error = NULL;
+
+	source = E_SOURCE (source_object);
+	selector = E_SOURCE_SELECTOR (user_data);
+
+	e_source_write_finish (source, result, &error);
+
+	/* FIXME Display the error in the selector somehow? */
+	if (error != NULL) {
+		g_warning ("%s: %s", G_STRFUNC, error->message);
+		g_error_free (error);
+	}
+
+	g_object_unref (selector);
+}
+
+static gboolean
+source_selector_write_idle_cb (gpointer user_data)
+{
+	AsyncContext *async_context = user_data;
+	GHashTable *pending_writes;
+
+	/* XXX This operation is not cancellable. */
+	e_source_write (
+		async_context->source, NULL,
+		source_selector_write_done_cb,
+		g_object_ref (async_context->selector));
+
+	pending_writes = async_context->selector->priv->pending_writes;
+	g_hash_table_remove (pending_writes, async_context->source);
+
+	return FALSE;
+}
+
+static void
+source_selector_cancel_write (ESourceSelector *selector,
+                              ESource *source)
+{
+	GHashTable *pending_writes;
+
+	/* Cancel any pending writes for this ESource so as not
+	 * to overwrite whatever change we're being notified of. */
+	pending_writes = selector->priv->pending_writes;
+	g_hash_table_remove (pending_writes, source);
+}
+
+static void
+source_selector_update_row (ESourceSelector *selector,
+                            ESource *source)
+{
+	GHashTable *source_index;
+	ESourceExtension *extension = NULL;
+	GtkTreeRowReference *reference;
+	GtkTreeModel *model;
+	GtkTreePath *path;
+	GtkTreeIter iter;
+	const gchar *extension_name;
+	const gchar *display_name;
+	gboolean selected;
+
+	source_index = selector->priv->source_index;
+	reference = g_hash_table_lookup (source_index, source);
+
+	/* This function runs when ANY ESource in the registry changes.
+	 * If the ESource is not in our tree model then return silently. */
+	if (reference == NULL)
+		return;
+
+	/* If we do have a row reference, it should be valid. */
+	g_return_if_fail (gtk_tree_row_reference_valid (reference));
+
+	model = gtk_tree_row_reference_get_model (reference);
+	path = gtk_tree_row_reference_get_path (reference);
+	gtk_tree_model_get_iter (model, &iter, path);
+	gtk_tree_path_free (path);
+
+	display_name = e_source_get_display_name (source);
+
+	extension_name = e_source_selector_get_extension_name (selector);
+	selected = e_source_selector_source_is_selected (selector, source);
+
+	if (e_source_has_extension (source, extension_name))
+		extension = e_source_get_extension (source, extension_name);
+
+	if (extension != NULL) {
+		GdkColor color;
+		const gchar *color_spec = NULL;
+		gboolean show_color = FALSE;
+		gboolean show_toggle;
+
+		show_color =
+			E_IS_SOURCE_SELECTABLE (extension) &&
+			e_source_selector_get_show_colors (selector);
+
+		if (show_color)
+			color_spec = e_source_selectable_get_color (
+				E_SOURCE_SELECTABLE (extension));
+
+		if (color_spec != NULL && *color_spec != '\0')
+			show_color = gdk_color_parse (color_spec, &color);
+
+		show_toggle = e_source_selector_get_show_toggles (selector);
+
+		gtk_tree_store_set (
+			GTK_TREE_STORE (model), &iter,
+			COLUMN_NAME, display_name,
+			COLUMN_COLOR, show_color ? &color : NULL,
+			COLUMN_ACTIVE, selected,
+			COLUMN_SHOW_COLOR, show_color,
+			COLUMN_SHOW_TOGGLE, show_toggle,
+			COLUMN_WEIGHT, PANGO_WEIGHT_NORMAL,
+			COLUMN_SOURCE, source,
+			-1);
+	} else {
+		gtk_tree_store_set (
+			GTK_TREE_STORE (model), &iter,
+			COLUMN_NAME, display_name,
+			COLUMN_COLOR, NULL,
+			COLUMN_ACTIVE, FALSE,
+			COLUMN_SHOW_COLOR, FALSE,
+			COLUMN_SHOW_TOGGLE, FALSE,
+			COLUMN_WEIGHT, PANGO_WEIGHT_BOLD,
+			COLUMN_SOURCE, source,
+			-1);
+	}
+}
+
+static gboolean
+source_selector_traverse (GNode *node,
+                          ESourceSelector *selector)
+{
+	ESource *source;
+	GHashTable *source_index;
+	GtkTreeRowReference *reference = NULL;
+	GtkTreeModel *model;
+	GtkTreePath *path;
+	GtkTreeIter iter;
+
+	/* Skip the root node. */
+	if (G_NODE_IS_ROOT (node))
+		return FALSE;
+
+	source_index = selector->priv->source_index;
+
+	model = gtk_tree_view_get_model (GTK_TREE_VIEW (selector));
+
+	if (node->parent != NULL && node->parent->data != NULL)
+		reference = g_hash_table_lookup (
+			source_index, node->parent->data);
+
+	if (gtk_tree_row_reference_valid (reference)) {
+		GtkTreeIter parent;
+
+		path = gtk_tree_row_reference_get_path (reference);
+		gtk_tree_model_get_iter (model, &parent, path);
+		gtk_tree_path_free (path);
+
+		gtk_tree_store_append (GTK_TREE_STORE (model), &iter, &parent);
+	} else
+		gtk_tree_store_append (GTK_TREE_STORE (model), &iter, NULL);
+
+	source = E_SOURCE (node->data);
+
+	path = gtk_tree_model_get_path (model, &iter);
+	reference = gtk_tree_row_reference_new (model, path);
+	g_hash_table_insert (source_index, g_object_ref (source), reference);
+	gtk_tree_path_free (path);
+
+	source_selector_update_row (selector, source);
+
+	return FALSE;
+}
+
+static void
+source_selector_save_expanded (GtkTreeView *tree_view,
+                               GtkTreePath *path,
+                               GQueue *queue)
+{
+	GtkTreeModel *model;
+	GtkTreeIter iter;
+	ESource *source;
+
+	model = gtk_tree_view_get_model (tree_view);
+	gtk_tree_model_get_iter (model, &iter, path);
+	gtk_tree_model_get (model, &iter, COLUMN_SOURCE, &source, -1);
+	g_queue_push_tail (queue, source);
+}
+
+static void
+source_selector_build_model (ESourceSelector *selector)
+{
+	ESourceRegistry *registry;
+	GQueue queue = G_QUEUE_INIT;
+	GHashTable *source_index;
+	GtkTreeView *tree_view;
+	GtkTreeModel *model;
+	ESource *selected;
+	const gchar *extension_name;
+	GNode *root;
+
+	tree_view = GTK_TREE_VIEW (selector);
+
+	registry = e_source_selector_get_registry (selector);
+	extension_name = e_source_selector_get_extension_name (selector);
+
+	/* Make sure we have what we need to build the model, since
+	 * this can get called early in the initialization phase. */
+	if (registry == NULL || extension_name == NULL)
+		return;
+
+	source_index = selector->priv->source_index;
+	selected = e_source_selector_ref_primary_selection (selector);
+
+	/* Save expanded sources to restore later. */
+	gtk_tree_view_map_expanded_rows (
+		tree_view, (GtkTreeViewMappingFunc)
+		source_selector_save_expanded, &queue);
+
+	model = gtk_tree_view_get_model (tree_view);
+	gtk_tree_store_clear (GTK_TREE_STORE (model));
+
+	g_hash_table_remove_all (source_index);
+
+	root = e_source_registry_build_display_tree (registry, extension_name);
+
+	g_node_traverse (
+		root, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
+		(GNodeTraverseFunc) source_selector_traverse,
+		selector);
+
+	e_source_registry_free_display_tree (root);
+
+	/* Restore previously expanded sources. */
+	while (!g_queue_is_empty (&queue)) {
+		GtkTreeRowReference *reference;
+		ESource *source;
+
+		source = g_queue_pop_head (&queue);
+		reference = g_hash_table_lookup (source_index, source);
+
+		if (gtk_tree_row_reference_valid (reference)) {
+			GtkTreePath *path;
+
+			path = gtk_tree_row_reference_get_path (reference);
+			gtk_tree_view_expand_to_path (tree_view, path);
+			gtk_tree_path_free (path);
+		}
+
+		g_object_unref (source);
+	}
+
+	/* Restore the primary selection. */
+	if (selected != NULL) {
+		e_source_selector_set_primary_selection (selector, selected);
+		g_object_unref (selected);
+	}
+
+	/* Make sure we have a primary selection.  If not, pick one. */
+	selected = e_source_selector_ref_primary_selection (selector);
+	if (selected == NULL) {
+		selected = e_source_registry_ref_default_for_extension_name (
+			registry, extension_name);
+		e_source_selector_set_primary_selection (selector, selected);
+	}
+	g_object_unref (selected);
+}
+
+static void
+source_selector_expand_to_source (ESourceSelector *selector,
+                                  ESource *source)
+{
+	GHashTable *source_index;
+	GtkTreeRowReference *reference;
+	GtkTreePath *path;
+
+	source_index = selector->priv->source_index;
+	reference = g_hash_table_lookup (source_index, source);
+
+	/* If the ESource is not in our tree model then return silently. */
+	if (reference == NULL)
+		return;
+
+	/* If we do have a row reference, it should be valid. */
+	g_return_if_fail (gtk_tree_row_reference_valid (reference));
+
+	/* Expand the tree view to the path containing the ESource */
+	path = gtk_tree_row_reference_get_path (reference);
+	gtk_tree_view_expand_to_path (GTK_TREE_VIEW (selector), path);
+	gtk_tree_path_free (path);
+}
+
+static void
+source_selector_source_added_cb (ESourceRegistry *registry,
+                                 ESource *source,
+                                 ESourceSelector *selector)
+{
+	source_selector_build_model (selector);
+
+	source_selector_expand_to_source (selector, source);
+}
+
+static void
+source_selector_source_changed_cb (ESourceRegistry *registry,
+                                   ESource *source,
+                                   ESourceSelector *selector)
+{
+	source_selector_cancel_write (selector, source);
+
+	source_selector_update_row (selector, source);
+}
+
+static void
+source_selector_source_removed_cb (ESourceRegistry *registry,
+                                   ESource *source,
+                                   ESourceSelector *selector)
+{
+	source_selector_build_model (selector);
+}
+
+static void
+source_selector_source_enabled_cb (ESourceRegistry *registry,
+                                   ESource *source,
+                                   ESourceSelector *selector)
+{
+	source_selector_build_model (selector);
+
+	source_selector_expand_to_source (selector, source);
+}
+
+static void
+source_selector_source_disabled_cb (ESourceRegistry *registry,
+                                    ESource *source,
+                                    ESourceSelector *selector)
+{
+	source_selector_build_model (selector);
+}
+
+static gboolean
+same_source_name_exists (ESourceSelector *selector,
+                         const gchar *display_name)
+{
+	GHashTable *source_index;
+	GHashTableIter iter;
+	gpointer key;
+
+	source_index = selector->priv->source_index;
+	g_hash_table_iter_init (&iter, source_index);
+
+	while (g_hash_table_iter_next (&iter, &key, NULL)) {
+		ESource *source = E_SOURCE (key);
+		const gchar *source_name;
+
+		source_name = e_source_get_display_name (source);
+		if (g_strcmp0 (display_name, source_name) == 0)
+			return TRUE;
+	}
+
+	return FALSE;
+}
+
+static gboolean
+selection_func (GtkTreeSelection *selection,
+                GtkTreeModel *model,
+                GtkTreePath *path,
+                gboolean path_currently_selected,
+                ESourceSelector *selector)
+{
+	ESource *source;
+	GtkTreeIter iter;
+	const gchar *extension_name;
+
+	if (selector->priv->toggled_last) {
+		selector->priv->toggled_last = FALSE;
+		return FALSE;
+	}
+
+	if (path_currently_selected)
+		return TRUE;
+
+	if (!gtk_tree_model_get_iter (model, &iter, path))
+		return FALSE;
+
+	extension_name = e_source_selector_get_extension_name (selector);
+	gtk_tree_model_get (model, &iter, COLUMN_SOURCE, &source, -1);
+
+	if (!e_source_has_extension (source, extension_name)) {
+		g_object_unref (source);
+		return FALSE;
+	}
+
+	clear_saved_primary_selection (selector);
+
+	g_object_unref (source);
+
+	return TRUE;
+}
+
+static void
+text_cell_edited_cb (ESourceSelector *selector,
+                     const gchar *path_string,
+                     const gchar *new_name)
+{
+	GtkTreeView *tree_view;
+	GtkTreeModel *model;
+	GtkTreePath *path;
+	GtkTreeIter iter;
+	ESource *source;
+
+	tree_view = GTK_TREE_VIEW (selector);
+	model = gtk_tree_view_get_model (tree_view);
+	path = gtk_tree_path_new_from_string (path_string);
+
+	gtk_tree_model_get_iter (model, &iter, path);
+	gtk_tree_model_get (model, &iter, COLUMN_SOURCE, &source, -1);
+	gtk_tree_path_free (path);
+
+	if (new_name == NULL || *new_name == '\0')
+		return;
+
+	if (same_source_name_exists (selector, new_name))
+		return;
+
+	e_source_set_display_name (source, new_name);
+
+	e_source_selector_queue_write (selector, source);
+}
+
+static void
+cell_toggled_callback (GtkCellRendererToggle *renderer,
+                       const gchar *path_string,
+                       ESourceSelector *selector)
+{
+	ESource *source;
+	GtkTreeModel *model;
+	GtkTreePath *path;
+	GtkTreeIter iter;
+
+	model = gtk_tree_view_get_model (GTK_TREE_VIEW (selector));
+	path = gtk_tree_path_new_from_string (path_string);
+
+	if (!gtk_tree_model_get_iter (model, &iter, path)) {
+		gtk_tree_path_free (path);
+		return;
+	}
+
+	gtk_tree_model_get (model, &iter, COLUMN_SOURCE, &source, -1);
+
+	if (e_source_selector_source_is_selected (selector, source))
+		e_source_selector_unselect_source (selector, source);
+	else
+		e_source_selector_select_source (selector, source);
+
+	selector->priv->toggled_last = TRUE;
+
+	gtk_tree_path_free (path);
+
+	g_object_unref (source);
+}
+
+static void
+selection_changed_callback (GtkTreeSelection *selection,
+                            ESourceSelector *selector)
+{
+	g_signal_emit (selector, signals[PRIMARY_SELECTION_CHANGED], 0);
+	g_object_notify (G_OBJECT (selector), "primary-selection");
+}
+
+static void
+source_selector_set_extension_name (ESourceSelector *selector,
+                                    const gchar *extension_name)
+{
+	g_return_if_fail (extension_name != NULL);
+	g_return_if_fail (selector->priv->extension_name == NULL);
+
+	selector->priv->extension_name = g_strdup (extension_name);
+}
+
+static void
+source_selector_set_registry (ESourceSelector *selector,
+                              ESourceRegistry *registry)
+{
+	g_return_if_fail (E_IS_SOURCE_REGISTRY (registry));
+	g_return_if_fail (selector->priv->registry == NULL);
+
+	selector->priv->registry = g_object_ref (registry);
+}
+
+static void
+source_selector_set_property (GObject *object,
+                              guint property_id,
+                              const GValue *value,
+                              GParamSpec *pspec)
+{
+	switch (property_id) {
+		case PROP_EXTENSION_NAME:
+			source_selector_set_extension_name (
+				E_SOURCE_SELECTOR (object),
+				g_value_get_string (value));
+			return;
+
+		case PROP_PRIMARY_SELECTION:
+			e_source_selector_set_primary_selection (
+				E_SOURCE_SELECTOR (object),
+				g_value_get_object (value));
+			return;
+
+		case PROP_REGISTRY:
+			source_selector_set_registry (
+				E_SOURCE_SELECTOR (object),
+				g_value_get_object (value));
+			return;
+
+		case PROP_SHOW_COLORS:
+			e_source_selector_set_show_colors (
+				E_SOURCE_SELECTOR (object),
+				g_value_get_boolean (value));
+			return;
+
+		case PROP_SHOW_TOGGLES:
+			e_source_selector_set_show_toggles (
+				E_SOURCE_SELECTOR (object),
+				g_value_get_boolean (value));
+			return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+source_selector_get_property (GObject *object,
+                              guint property_id,
+                              GValue *value,
+                              GParamSpec *pspec)
+{
+	switch (property_id) {
+		case PROP_EXTENSION_NAME:
+			g_value_set_string (
+				value,
+				e_source_selector_get_extension_name (
+				E_SOURCE_SELECTOR (object)));
+			return;
+
+		case PROP_PRIMARY_SELECTION:
+			g_value_take_object (
+				value,
+				e_source_selector_ref_primary_selection (
+				E_SOURCE_SELECTOR (object)));
+			return;
+
+		case PROP_REGISTRY:
+			g_value_set_object (
+				value,
+				e_source_selector_get_registry (
+				E_SOURCE_SELECTOR (object)));
+			return;
+
+		case PROP_SHOW_COLORS:
+			g_value_set_boolean (
+				value,
+				e_source_selector_get_show_colors (
+				E_SOURCE_SELECTOR (object)));
+			return;
+
+		case PROP_SHOW_TOGGLES:
+			g_value_set_boolean (
+				value,
+				e_source_selector_get_show_toggles (
+				E_SOURCE_SELECTOR (object)));
+			return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+source_selector_dispose (GObject *object)
+{
+	ESourceSelectorPrivate *priv;
+
+	priv = E_SOURCE_SELECTOR_GET_PRIVATE (object);
+
+	if (priv->registry != NULL) {
+		g_signal_handlers_disconnect_matched (
+			priv->registry,
+			G_SIGNAL_MATCH_DATA,
+			0, 0, NULL, NULL, object);
+		g_object_unref (priv->registry);
+		priv->registry = NULL;
+	}
+
+	g_hash_table_remove_all (priv->source_index);
+	g_hash_table_remove_all (priv->pending_writes);
+
+	clear_saved_primary_selection (E_SOURCE_SELECTOR (object));
+
+	/* Chain up to parent's dispose() method. */
+	G_OBJECT_CLASS (e_source_selector_parent_class)->dispose (object);
+}
+
+static void
+source_selector_finalize (GObject *object)
+{
+	ESourceSelectorPrivate *priv;
+
+	priv = E_SOURCE_SELECTOR_GET_PRIVATE (object);
+
+	g_hash_table_destroy (priv->source_index);
+	g_hash_table_destroy (priv->pending_writes);
+
+	g_free (priv->extension_name);
+
+	if (priv->main_context != NULL)
+		g_main_context_unref (priv->main_context);
+
+	/* Chain up to parent's finalize() method. */
+	G_OBJECT_CLASS (e_source_selector_parent_class)->finalize (object);
+}
+
+static void
+source_selector_constructed (GObject *object)
+{
+	ESourceRegistry *registry;
+	ESourceSelector *selector;
+
+	selector = E_SOURCE_SELECTOR (object);
+	registry = e_source_selector_get_registry (selector);
+
+	g_signal_connect (
+		registry, "source-added",
+		G_CALLBACK (source_selector_source_added_cb), selector);
+
+	g_signal_connect (
+		registry, "source-changed",
+		G_CALLBACK (source_selector_source_changed_cb), selector);
+
+	g_signal_connect (
+		registry, "source-removed",
+		G_CALLBACK (source_selector_source_removed_cb), selector);
+
+	g_signal_connect (
+		registry, "source-enabled",
+		G_CALLBACK (source_selector_source_enabled_cb), selector);
+
+	g_signal_connect (
+		registry, "source-disabled",
+		G_CALLBACK (source_selector_source_disabled_cb), selector);
+
+	source_selector_build_model (selector);
+
+	gtk_tree_view_expand_all (GTK_TREE_VIEW (selector));
+}
+
+static gboolean
+source_selector_button_press_event (GtkWidget *widget,
+                                    GdkEventButton *event)
+{
+	ESourceSelector *selector;
+	GtkWidgetClass *widget_class;
+	GtkTreePath *path;
+	ESource *source = NULL;
+	ESource *primary;
+	gboolean right_click = FALSE;
+	gboolean triple_click = FALSE;
+	gboolean row_exists;
+	gboolean res = FALSE;
+
+	selector = E_SOURCE_SELECTOR (widget);
+
+	selector->priv->toggled_last = FALSE;
+
+	/* Triple-clicking a source selects it exclusively. */
+
+	if (event->button == 3 && event->type == GDK_BUTTON_PRESS)
+		right_click = TRUE;
+	else if (event->button == 1 && event->type == GDK_3BUTTON_PRESS)
+		triple_click = TRUE;
+	else
+		goto chainup;
+
+	row_exists = gtk_tree_view_get_path_at_pos (
+		GTK_TREE_VIEW (widget), event->x, event->y,
+		&path, NULL, NULL, NULL);
+
+	/* Get the source/group */
+	if (row_exists) {
+		GtkTreeModel *model;
+		GtkTreeIter iter;
+
+		model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
+
+		gtk_tree_model_get_iter (model, &iter, path);
+		gtk_tree_model_get (model, &iter, COLUMN_SOURCE, &source, -1);
+	}
+
+	if (source == NULL)
+		goto chainup;
+
+	primary = e_source_selector_ref_primary_selection (selector);
+	if (source != primary)
+		e_source_selector_set_primary_selection (selector, source);
+	if (primary != NULL)
+		g_object_unref (primary);
+
+	if (right_click)
+		g_signal_emit (
+			widget, signals[POPUP_EVENT], 0, source, event, &res);
+
+	if (triple_click) {
+		e_source_selector_select_exclusive (selector, source);
+		res = TRUE;
+	}
+
+	g_object_unref (source);
+
+	return res;
+
+chainup:
+
+	/* Chain up to parent's button_press_event() method. */
+	widget_class = GTK_WIDGET_CLASS (e_source_selector_parent_class);
+	return widget_class->button_press_event (widget, event);
+}
+
+static void
+source_selector_drag_leave (GtkWidget *widget,
+                            GdkDragContext *context,
+                            guint time_)
+{
+	GtkTreeView *tree_view;
+	GtkTreeViewDropPosition pos;
+
+	tree_view = GTK_TREE_VIEW (widget);
+	pos = GTK_TREE_VIEW_DROP_BEFORE;
+
+	gtk_tree_view_set_drag_dest_row (tree_view, NULL, pos);
+}
+
+static gboolean
+source_selector_drag_motion (GtkWidget *widget,
+                             GdkDragContext *context,
+                             gint x,
+                             gint y,
+                             guint time_)
+{
+	ESource *source = NULL;
+	GtkTreeView *tree_view;
+	GtkTreeModel *model;
+	GtkTreePath *path = NULL;
+	GtkTreeIter iter;
+	GtkTreeViewDropPosition pos;
+	GdkDragAction action = 0;
+
+	tree_view = GTK_TREE_VIEW (widget);
+	model = gtk_tree_view_get_model (tree_view);
+
+	if (!gtk_tree_view_get_dest_row_at_pos (tree_view, x, y, &path, NULL))
+		goto exit;
+
+	if (!gtk_tree_model_get_iter (model, &iter, path))
+		goto exit;
+
+	gtk_tree_model_get (model, &iter, COLUMN_SOURCE, &source, -1);
+
+	if (!e_source_get_writable (source))
+		goto exit;
+
+	pos = GTK_TREE_VIEW_DROP_INTO_OR_BEFORE;
+	gtk_tree_view_set_drag_dest_row (tree_view, path, pos);
+
+	if (gdk_drag_context_get_actions (context) & GDK_ACTION_MOVE)
+		action = GDK_ACTION_MOVE;
+	else
+		action = gdk_drag_context_get_suggested_action (context);
+
+exit:
+	if (path != NULL)
+		gtk_tree_path_free (path);
+
+	if (source != NULL)
+		g_object_unref (source);
+
+	gdk_drag_status (context, action, time_);
+
+	return TRUE;
+}
+
+static gboolean
+source_selector_drag_drop (GtkWidget *widget,
+                           GdkDragContext *context,
+                           gint x,
+                           gint y,
+                           guint time_)
+{
+	ESource *source;
+	ESourceSelector *selector;
+	GtkTreeView *tree_view;
+	GtkTreeModel *model;
+	GtkTreePath *path;
+	GtkTreeIter iter;
+	const gchar *extension_name;
+	gboolean drop_zone;
+	gboolean valid;
+
+	tree_view = GTK_TREE_VIEW (widget);
+	model = gtk_tree_view_get_model (tree_view);
+
+	if (!gtk_tree_view_get_path_at_pos (
+		tree_view, x, y, &path, NULL, NULL, NULL))
+		return FALSE;
+
+	valid = gtk_tree_model_get_iter (model, &iter, path);
+	gtk_tree_path_free (path);
+	g_return_val_if_fail (valid, FALSE);
+
+	gtk_tree_model_get (model, &iter, COLUMN_SOURCE, &source, -1);
+
+	selector = E_SOURCE_SELECTOR (widget);
+	extension_name = e_source_selector_get_extension_name (selector);
+	drop_zone = e_source_has_extension (source, extension_name);
+
+	g_object_unref (source);
+
+	return drop_zone;
+}
+
+static void
+source_selector_drag_data_received (GtkWidget *widget,
+                                    GdkDragContext *context,
+                                    gint x,
+                                    gint y,
+                                    GtkSelectionData *selection_data,
+                                    guint info,
+                                    guint time_)
+{
+	ESource *source = NULL;
+	GtkTreeView *tree_view;
+	GtkTreeModel *model;
+	GtkTreePath *path = NULL;
+	GtkTreeIter iter;
+	GdkDragAction action;
+	gboolean delete;
+	gboolean success = FALSE;
+
+	tree_view = GTK_TREE_VIEW (widget);
+	model = gtk_tree_view_get_model (tree_view);
+
+	action = gdk_drag_context_get_selected_action (context);
+	delete = (action == GDK_ACTION_MOVE);
+
+	if (!gtk_tree_view_get_dest_row_at_pos (tree_view, x, y, &path, NULL))
+		goto exit;
+
+	if (!gtk_tree_model_get_iter (model, &iter, path))
+		goto exit;
+
+	gtk_tree_model_get (model, &iter, COLUMN_SOURCE, &source, -1);
+
+	if (!e_source_get_writable (source))
+		goto exit;
+
+	g_signal_emit (
+		widget, signals[DATA_DROPPED], 0, selection_data,
+		source, gdk_drag_context_get_selected_action (context),
+		info, &success);
+
+exit:
+	if (path != NULL)
+		gtk_tree_path_free (path);
+
+	if (source != NULL)
+		g_object_unref (source);
+
+	gtk_drag_finish (context, success, delete, time_);
+}
+
+static gboolean
+source_selector_popup_menu (GtkWidget *widget)
+{
+	ESourceSelector *selector;
+	ESource *source;
+	gboolean res = FALSE;
+
+	selector = E_SOURCE_SELECTOR (widget);
+	source = e_source_selector_ref_primary_selection (selector);
+	g_signal_emit (selector, signals[POPUP_EVENT], 0, source, NULL, &res);
+
+	if (source != NULL)
+		g_object_unref (source);
+
+	return res;
+}
+
+static gboolean
+source_selector_test_collapse_row (GtkTreeView *tree_view,
+                                   GtkTreeIter *iter,
+                                   GtkTreePath *path)
+{
+	ESourceSelectorPrivate *priv;
+	GtkTreeSelection *selection;
+	GtkTreeModel *model;
+	GtkTreeIter child_iter;
+
+	priv = E_SOURCE_SELECTOR_GET_PRIVATE (tree_view);
+
+	/* Clear this because something else has been clicked on now */
+	priv->toggled_last = FALSE;
+
+	if (priv->saved_primary_selection)
+		return FALSE;
+
+	selection = gtk_tree_view_get_selection (tree_view);
+
+	if (!gtk_tree_selection_get_selected (selection, &model, &child_iter))
+		return FALSE;
+
+	if (gtk_tree_store_is_ancestor (GTK_TREE_STORE (model), iter, &child_iter)) {
+		GtkTreeRowReference *reference;
+		GtkTreePath *child_path;
+
+		child_path = gtk_tree_model_get_path (model, &child_iter);
+		reference = gtk_tree_row_reference_new (model, child_path);
+		priv->saved_primary_selection = reference;
+		gtk_tree_path_free (child_path);
+	}
+
+	return FALSE;
+}
+
+static void
+source_selector_row_expanded (GtkTreeView *tree_view,
+                              GtkTreeIter *iter,
+                              GtkTreePath *path)
+{
+	ESourceSelectorPrivate *priv;
+	GtkTreeModel *model;
+	GtkTreePath *child_path;
+	GtkTreeIter child_iter;
+
+	priv = E_SOURCE_SELECTOR_GET_PRIVATE (tree_view);
+
+	if (!priv->saved_primary_selection)
+		return;
+
+	model = gtk_tree_view_get_model (tree_view);
+
+	child_path = gtk_tree_row_reference_get_path (
+		priv->saved_primary_selection);
+	gtk_tree_model_get_iter (model, &child_iter, child_path);
+
+	if (gtk_tree_store_is_ancestor (GTK_TREE_STORE (model), iter, &child_iter)) {
+		GtkTreeSelection *selection;
+
+		selection = gtk_tree_view_get_selection (tree_view);
+		gtk_tree_selection_select_iter (selection, &child_iter);
+
+		clear_saved_primary_selection (E_SOURCE_SELECTOR (tree_view));
+	}
+
+	gtk_tree_path_free (child_path);
+}
+
+static gboolean
+source_selector_get_source_selected (ESourceSelector *selector,
+                                     ESource *source)
+{
+	ESourceSelectable *extension;
+	const gchar *extension_name;
+	gboolean selected = TRUE;
+
+	extension_name = e_source_selector_get_extension_name (selector);
+
+	if (!e_source_has_extension (source, extension_name))
+		return FALSE;
+
+	extension = e_source_get_extension (source, extension_name);
+
+	if (E_IS_SOURCE_SELECTABLE (extension))
+		selected = e_source_selectable_get_selected (extension);
+
+	return selected;
+}
+
+static void
+source_selector_set_source_selected (ESourceSelector *selector,
+                                     ESource *source,
+                                     gboolean selected)
+{
+	ESourceSelectable *extension;
+	const gchar *extension_name;
+
+	extension_name = e_source_selector_get_extension_name (selector);
+
+	if (!e_source_has_extension (source, extension_name))
+		return;
+
+	extension = e_source_get_extension (source, extension_name);
+
+	if (!E_IS_SOURCE_SELECTABLE (extension))
+		return;
+
+	if (selected != e_source_selectable_get_selected (extension)) {
+		e_source_selectable_set_selected (extension, selected);
+		e_source_selector_queue_write (selector, source);
+	}
+}
+
+static gboolean
+ess_bool_accumulator (GSignalInvocationHint *ihint,
+                      GValue *out,
+                      const GValue *in,
+                      gpointer data)
+{
+	gboolean v_boolean;
+
+	v_boolean = g_value_get_boolean (in);
+	g_value_set_boolean (out, v_boolean);
+
+	return !v_boolean;
+}
+
+static void
+e_source_selector_class_init (ESourceSelectorClass *class)
+{
+	GObjectClass *object_class;
+	GtkWidgetClass *widget_class;
+	GtkTreeViewClass *tree_view_class;
+
+	g_type_class_add_private (class, sizeof (ESourceSelectorPrivate));
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->set_property = source_selector_set_property;
+	object_class->get_property = source_selector_get_property;
+	object_class->dispose  = source_selector_dispose;
+	object_class->finalize = source_selector_finalize;
+	object_class->constructed = source_selector_constructed;
+
+	widget_class = GTK_WIDGET_CLASS (class);
+	widget_class->button_press_event = source_selector_button_press_event;
+	widget_class->drag_leave = source_selector_drag_leave;
+	widget_class->drag_motion = source_selector_drag_motion;
+	widget_class->drag_drop = source_selector_drag_drop;
+	widget_class->drag_data_received = source_selector_drag_data_received;
+	widget_class->popup_menu = source_selector_popup_menu;
+
+	tree_view_class = GTK_TREE_VIEW_CLASS (class);
+	tree_view_class->test_collapse_row = source_selector_test_collapse_row;
+	tree_view_class->row_expanded = source_selector_row_expanded;
+
+	class->get_source_selected = source_selector_get_source_selected;
+	class->set_source_selected = source_selector_set_source_selected;
+
+	g_object_class_install_property (
+		object_class,
+		PROP_EXTENSION_NAME,
+		g_param_spec_string (
+			"extension-name",
+			NULL,
+			NULL,
+			NULL,
+			G_PARAM_READWRITE |
+			G_PARAM_CONSTRUCT_ONLY |
+			G_PARAM_STATIC_STRINGS));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_PRIMARY_SELECTION,
+		g_param_spec_object (
+			"primary-selection",
+			NULL,
+			NULL,
+			E_TYPE_SOURCE,
+			G_PARAM_READWRITE |
+			G_PARAM_STATIC_STRINGS));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_REGISTRY,
+		g_param_spec_object (
+			"registry",
+			NULL,
+			NULL,
+			E_TYPE_SOURCE_REGISTRY,
+			G_PARAM_READWRITE |
+			G_PARAM_CONSTRUCT_ONLY |
+			G_PARAM_STATIC_STRINGS));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_SHOW_COLORS,
+		g_param_spec_boolean (
+			"show-colors",
+			NULL,
+			NULL,
+			TRUE,
+			G_PARAM_READWRITE |
+			G_PARAM_STATIC_STRINGS));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_SHOW_TOGGLES,
+		g_param_spec_boolean (
+			"show-toggles",
+			NULL,
+			NULL,
+			TRUE,
+			G_PARAM_READWRITE |
+			G_PARAM_STATIC_STRINGS));
+
+	signals[SELECTION_CHANGED] = g_signal_new (
+		"selection-changed",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ESourceSelectorClass, selection_changed),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__VOID,
+		G_TYPE_NONE, 0);
+
+	/* XXX Consider this signal deprecated.  Connect
+	 *     to "notify::primary-selection" instead. */
+	signals[PRIMARY_SELECTION_CHANGED] = g_signal_new (
+		"primary-selection-changed",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ESourceSelectorClass, primary_selection_changed),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__VOID,
+		G_TYPE_NONE, 0);
+
+	signals[POPUP_EVENT] = g_signal_new (
+		"popup-event",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ESourceSelectorClass, popup_event),
+		ess_bool_accumulator, NULL, NULL,
+		G_TYPE_BOOLEAN, 2, G_TYPE_OBJECT,
+		GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
+
+	signals[DATA_DROPPED] = g_signal_new (
+		"data-dropped",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ESourceSelectorClass, data_dropped),
+		NULL, NULL, NULL,
+		G_TYPE_BOOLEAN, 4,
+		GTK_TYPE_SELECTION_DATA | G_SIGNAL_TYPE_STATIC_SCOPE,
+		E_TYPE_SOURCE,
+		GDK_TYPE_DRAG_ACTION,
+		G_TYPE_UINT);
+}
+
+static void
+e_source_selector_init (ESourceSelector *selector)
+{
+	GHashTable *pending_writes;
+	GtkTreeViewColumn *column;
+	GtkTreeSelection *selection;
+	GtkCellRenderer *renderer;
+	GtkTreeStore *tree_store;
+	GtkTreeView *tree_view;
+
+	pending_writes = g_hash_table_new_full (
+		(GHashFunc) g_direct_hash,
+		(GEqualFunc) g_direct_equal,
+		(GDestroyNotify) g_object_unref,
+		(GDestroyNotify) pending_writes_destroy_source);
+
+	selector->priv = E_SOURCE_SELECTOR_GET_PRIVATE (selector);
+
+	selector->priv->pending_writes = pending_writes;
+
+	selector->priv->main_context = g_main_context_get_thread_default ();
+	if (selector->priv->main_context != NULL)
+		g_main_context_ref (selector->priv->main_context);
+
+	tree_view = GTK_TREE_VIEW (selector);
+
+	gtk_tree_view_set_search_column (tree_view, COLUMN_SOURCE);
+	gtk_tree_view_set_enable_search (tree_view, TRUE);
+
+	selector->priv->toggled_last = FALSE;
+	selector->priv->select_new = FALSE;
+	selector->priv->show_colors = TRUE;
+	selector->priv->show_toggles = TRUE;
+
+	selector->priv->source_index = g_hash_table_new_full (
+		(GHashFunc) e_source_hash,
+		(GEqualFunc) e_source_equal,
+		(GDestroyNotify) g_object_unref,
+		(GDestroyNotify) gtk_tree_row_reference_free);
+
+	tree_store = gtk_tree_store_new (
+		NUM_COLUMNS,
+		G_TYPE_STRING,		/* COLUMN_NAME */
+		GDK_TYPE_COLOR,		/* COLUMN_COLOR */
+		G_TYPE_BOOLEAN,		/* COLUMN_ACTIVE */
+		G_TYPE_BOOLEAN,		/* COLUMN_SHOW_COLOR */
+		G_TYPE_BOOLEAN,		/* COLUMN_SHOW_TOGGLE */
+		G_TYPE_INT,		/* COLUMN_WEIGHT */
+		E_TYPE_SOURCE);		/* COLUMN_SOURCE */
+
+	gtk_tree_view_set_model (tree_view, GTK_TREE_MODEL (tree_store));
+
+	column = gtk_tree_view_column_new ();
+	gtk_tree_view_append_column (tree_view, column);
+
+	renderer = e_cell_renderer_color_new ();
+	g_object_set (
+		G_OBJECT (renderer), "mode",
+		GTK_CELL_RENDERER_MODE_ACTIVATABLE, NULL);
+	gtk_tree_view_column_pack_start (column, renderer, FALSE);
+	gtk_tree_view_column_add_attribute (
+		column, renderer, "color", COLUMN_COLOR);
+	gtk_tree_view_column_add_attribute (
+		column, renderer, "visible", COLUMN_SHOW_COLOR);
+
+	renderer = e_cell_renderer_safe_toggle_new ();
+	gtk_tree_view_column_pack_start (column, renderer, FALSE);
+	gtk_tree_view_column_add_attribute (
+		column, renderer, "active", COLUMN_ACTIVE);
+	gtk_tree_view_column_add_attribute (
+		column, renderer, "visible", COLUMN_SHOW_TOGGLE);
+	g_signal_connect (
+		renderer, "toggled",
+		G_CALLBACK (cell_toggled_callback), selector);
+
+	renderer = gtk_cell_renderer_text_new ();
+	g_object_set (
+		G_OBJECT (renderer),
+		"ellipsize", PANGO_ELLIPSIZE_END, NULL);
+	g_signal_connect_swapped (
+		renderer, "edited",
+		G_CALLBACK (text_cell_edited_cb), selector);
+	gtk_tree_view_column_pack_start (column, renderer, TRUE);
+	gtk_tree_view_column_set_attributes (
+		column, renderer,
+		"text", COLUMN_NAME,
+		"weight", COLUMN_WEIGHT,
+		NULL);
+
+	selection = gtk_tree_view_get_selection (tree_view);
+	gtk_tree_selection_set_select_function (
+		selection, (GtkTreeSelectionFunc)
+		selection_func, selector, NULL);
+	g_signal_connect_object (
+		selection, "changed",
+		G_CALLBACK (selection_changed_callback),
+		G_OBJECT (selector), 0);
+
+	gtk_tree_view_set_headers_visible (tree_view, FALSE);
+}
+
+/**
+ * e_source_selector_new:
+ * @registry: an #ESourceRegistry
+ * @extension_name: the name of an #ESource extension
+ *
+ * Displays a list of sources from @registry having an extension named
+ * @extension_name.  The sources are grouped by backend or groupware
+ * account, which are described by the parent source.
+ *
+ * Returns: a new #ESourceSelector
+ **/
+GtkWidget *
+e_source_selector_new (ESourceRegistry *registry,
+                       const gchar *extension_name)
+{
+	g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), NULL);
+	g_return_val_if_fail (extension_name != NULL, NULL);
+
+	return g_object_new (
+		E_TYPE_SOURCE_SELECTOR, "registry", registry,
+		"extension-name", extension_name, NULL);
+}
+
+/**
+ * e_source_selector_get_registry:
+ * @selector: an #ESourceSelector
+ *
+ * Returns the #ESourceRegistry that @selector is getting sources from.
+ *
+ * Returns: an #ESourceRegistry
+ *
+ * Since: 3.6
+ **/
+ESourceRegistry *
+e_source_selector_get_registry (ESourceSelector *selector)
+{
+	g_return_val_if_fail (E_IS_SOURCE_SELECTOR (selector), NULL);
+
+	return selector->priv->registry;
+}
+
+/**
+ * e_source_selector_get_extension_name:
+ * @selector: an #ESourceSelector
+ *
+ * Returns the extension name used to filter which sources are displayed.
+ *
+ * Returns: the #ESource extension name
+ *
+ * Since: 3.6
+ **/
+const gchar *
+e_source_selector_get_extension_name (ESourceSelector *selector)
+{
+	g_return_val_if_fail (E_IS_SOURCE_SELECTOR (selector), NULL);
+
+	return selector->priv->extension_name;
+}
+
+/**
+ * e_source_selector_get_show_colors:
+ * @selector: an #ESourceSelector
+ *
+ * Returns whether colors are shown next to data sources.
+ *
+ * Returns: %TRUE if colors are being shown
+ *
+ * Since: 3.6
+ **/
+gboolean
+e_source_selector_get_show_colors (ESourceSelector *selector)
+{
+	g_return_val_if_fail (E_IS_SOURCE_SELECTOR (selector), FALSE);
+
+	return selector->priv->show_colors;
+}
+
+/**
+ * e_source_selector_set_show_colors:
+ * @selector: an #ESourceSelector
+ * @show_colors: whether to show colors
+ *
+ * Sets whether to show colors next to data sources.
+ *
+ * Since: 3.6
+ **/
+void
+e_source_selector_set_show_colors (ESourceSelector *selector,
+                                   gboolean show_colors)
+{
+	g_return_if_fail (E_IS_SOURCE_SELECTOR (selector));
+
+	if ((show_colors ? 1 : 0) == (selector->priv->show_colors ? 1 : 0))
+		return;
+
+	selector->priv->show_colors = show_colors;
+
+	g_object_notify (G_OBJECT (selector), "show-colors");
+
+	source_selector_build_model (selector);
+}
+
+/**
+ * e_source_selector_get_show_toggles:
+ * @selector: an #ESourceSelector
+ *
+ * Returns whether toggles are shown next to data sources.
+ *
+ * Returns: %TRUE if toggles are being shown
+ *
+ * Since: 3.6
+ **/
+gboolean
+e_source_selector_get_show_toggles (ESourceSelector *selector)
+{
+	g_return_val_if_fail (E_IS_SOURCE_SELECTOR (selector), FALSE);
+
+	return selector->priv->show_toggles;
+}
+
+/**
+ * e_source_selector_set_show_toggles:
+ * @selector: an #ESourceSelector
+ * @show_toggles: whether to show toggles
+ *
+ * Sets whether to show toggles next to data sources.
+ *
+ * Since: 3.6
+ **/
+void
+e_source_selector_set_show_toggles (ESourceSelector *selector,
+                                   gboolean show_toggles)
+{
+	g_return_if_fail (E_IS_SOURCE_SELECTOR (selector));
+
+	if ((show_toggles ? 1 : 0) == (selector->priv->show_toggles ? 1 : 0))
+		return;
+
+	selector->priv->show_toggles = show_toggles;
+
+	g_object_notify (G_OBJECT (selector), "show-toggles");
+
+	source_selector_build_model (selector);
+}
+
+/* Helper for e_source_selector_get_selection() */
+static gboolean
+source_selector_check_selected (GtkTreeModel *model,
+                                GtkTreePath *path,
+                                GtkTreeIter *iter,
+                                gpointer user_data)
+{
+	ESource *source;
+
+	struct {
+		ESourceSelector *selector;
+		GSList *list;
+	} *closure = user_data;
+
+	gtk_tree_model_get (model, iter, COLUMN_SOURCE, &source, -1);
+
+	if (e_source_selector_source_is_selected (closure->selector, source))
+		closure->list = g_slist_prepend (closure->list, source);
+	else
+		g_object_unref (source);
+
+	return FALSE;
+}
+
+/**
+ * e_source_selector_get_selection:
+ * @selector: an #ESourceSelector
+ *
+ * Get the list of selected sources, i.e. those that were enabled through the
+ * corresponding checkboxes in the tree.
+ *
+ * Returns: A list of the ESources currently selected.  The sources will
+ * be in the same order as they appear on the screen, and the list should be
+ * freed using e_source_selector_free_selection().
+ **/
+GSList *
+e_source_selector_get_selection (ESourceSelector *selector)
+{
+	struct {
+		ESourceSelector *selector;
+		GSList *list;
+	} closure;
+
+	g_return_val_if_fail (E_IS_SOURCE_SELECTOR (selector), NULL);
+
+	closure.selector = selector;
+	closure.list = NULL;
+
+	gtk_tree_model_foreach (
+		gtk_tree_view_get_model (GTK_TREE_VIEW (selector)),
+		(GtkTreeModelForeachFunc) source_selector_check_selected,
+		&closure);
+
+	return g_slist_reverse (closure.list);
+}
+
+/**
+ * e_source_list_free_selection:
+ * @list: A selection list returned by e_source_selector_get_selection().
+ *
+ * Free the selection list.
+ **/
+void
+e_source_selector_free_selection (GSList *list)
+{
+	g_slist_foreach (list, (GFunc) g_object_unref, NULL);
+	g_slist_free (list);
+}
+
+/**
+ * e_source_selector_set_select_new:
+ * @selector: An #ESourceSelector widget
+ * @state: A gboolean
+ *
+ * Set whether or not to select new sources added to @selector.
+ **/
+void
+e_source_selector_set_select_new (ESourceSelector *selector,
+                                  gboolean state)
+{
+	g_return_if_fail (E_IS_SOURCE_SELECTOR (selector));
+
+	selector->priv->select_new = state;
+}
+
+/**
+ * e_source_selector_select_source:
+ * @selector: An #ESourceSelector widget
+ * @source: An #ESource.
+ *
+ * Select @source in @selector.
+ **/
+void
+e_source_selector_select_source (ESourceSelector *selector,
+                                 ESource *source)
+{
+	ESourceSelectorClass *class;
+	GtkTreeRowReference *reference;
+	GHashTable *source_index;
+
+	g_return_if_fail (E_IS_SOURCE_SELECTOR (selector));
+	g_return_if_fail (E_IS_SOURCE (source));
+
+	/* Make sure the ESource is in our tree model. */
+	source_index = selector->priv->source_index;
+	reference = g_hash_table_lookup (source_index, source);
+	g_return_if_fail (gtk_tree_row_reference_valid (reference));
+
+	class = E_SOURCE_SELECTOR_GET_CLASS (selector);
+	g_return_if_fail (class->set_source_selected != NULL);
+
+	class->set_source_selected (selector, source, TRUE);
+
+	g_signal_emit (selector, signals[SELECTION_CHANGED], 0);
+}
+
+/**
+ * e_source_selector_unselect_source:
+ * @selector: An #ESourceSelector widget
+ * @source: An #ESource.
+ *
+ * Unselect @source in @selector.
+ **/
+void
+e_source_selector_unselect_source (ESourceSelector *selector,
+                                   ESource *source)
+{
+	ESourceSelectorClass *class;
+	GtkTreeRowReference *reference;
+	GHashTable *source_index;
+
+	g_return_if_fail (E_IS_SOURCE_SELECTOR (selector));
+	g_return_if_fail (E_IS_SOURCE (source));
+
+	/* Make sure the ESource is in our tree model. */
+	source_index = selector->priv->source_index;
+	reference = g_hash_table_lookup (source_index, source);
+	g_return_if_fail (gtk_tree_row_reference_valid (reference));
+
+	class = E_SOURCE_SELECTOR_GET_CLASS (selector);
+	g_return_if_fail (class->set_source_selected != NULL);
+
+	class->set_source_selected (selector, source, FALSE);
+
+	g_signal_emit (selector, signals[SELECTION_CHANGED], 0);
+}
+
+/**
+ * e_source_selector_select_exclusive:
+ * @selector: An #ESourceSelector widget
+ * @source: An #ESource.
+ *
+ * Select @source in @selector and unselect all others.
+ *
+ * Since: 2.30
+ **/
+void
+e_source_selector_select_exclusive (ESourceSelector *selector,
+                                    ESource *source)
+{
+	ESourceSelectorClass *class;
+	GHashTable *source_index;
+	GHashTableIter iter;
+	gpointer key;
+
+	g_return_if_fail (E_IS_SOURCE_SELECTOR (selector));
+	g_return_if_fail (E_IS_SOURCE (source));
+
+	class = E_SOURCE_SELECTOR_GET_CLASS (selector);
+	g_return_if_fail (class->set_source_selected != NULL);
+
+	source_index = selector->priv->source_index;
+	g_hash_table_iter_init (&iter, source_index);
+
+	while (g_hash_table_iter_next (&iter, &key, NULL)) {
+		gboolean selected = e_source_equal (key, source);
+		class->set_source_selected (selector, key, selected);
+	}
+
+	g_signal_emit (selector, signals[SELECTION_CHANGED], 0);
+}
+
+/**
+ * e_source_selector_source_is_selected:
+ * @selector: An #ESourceSelector widget
+ * @source: An #ESource.
+ *
+ * Check whether @source is selected in @selector.
+ *
+ * Returns: %TRUE if @source is currently selected, %FALSE otherwise.
+ **/
+gboolean
+e_source_selector_source_is_selected (ESourceSelector *selector,
+                                      ESource *source)
+{
+	ESourceSelectorClass *class;
+	GtkTreeRowReference *reference;
+	GHashTable *source_index;
+
+	g_return_val_if_fail (E_IS_SOURCE_SELECTOR (selector), FALSE);
+	g_return_val_if_fail (E_IS_SOURCE (source), FALSE);
+
+	/* Make sure the ESource is in our tree model. */
+	source_index = selector->priv->source_index;
+	reference = g_hash_table_lookup (source_index, source);
+	g_return_val_if_fail (gtk_tree_row_reference_valid (reference), FALSE);
+
+	class = E_SOURCE_SELECTOR_GET_CLASS (selector);
+	g_return_val_if_fail (class->get_source_selected != NULL, FALSE);
+
+	return class->get_source_selected (selector, source);
+}
+
+/**
+ * e_source_selector_edit_primary_selection:
+ * @selector: An #ESourceSelector widget
+ *
+ * Allows the user to rename the primary selected source by opening an
+ * entry box directly in @selector.
+ *
+ * Since: 2.26
+ **/
+void
+e_source_selector_edit_primary_selection (ESourceSelector *selector)
+{
+	GtkTreeRowReference *reference;
+	GtkTreeSelection *selection;
+	GtkTreeViewColumn *column;
+	GtkCellRenderer *renderer;
+	GtkTreeView *tree_view;
+	GtkTreeModel *model;
+	GtkTreePath *path = NULL;
+	GtkTreeIter iter;
+	GList *list;
+
+	g_return_if_fail (E_IS_SOURCE_SELECTOR (selector));
+
+	tree_view = GTK_TREE_VIEW (selector);
+	column = gtk_tree_view_get_column (tree_view, 0);
+	reference = selector->priv->saved_primary_selection;
+	selection = gtk_tree_view_get_selection (tree_view);
+
+	if (reference != NULL)
+		path = gtk_tree_row_reference_get_path (reference);
+	else if (gtk_tree_selection_get_selected (selection, &model, &iter))
+		path = gtk_tree_model_get_path (model, &iter);
+
+	if (path == NULL)
+		return;
+
+	/* XXX Because we stuff three renderers in a single column,
+	 *     we have to manually hunt for the text renderer. */
+	renderer = NULL;
+	list = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (column));
+	while (list != NULL) {
+		renderer = list->data;
+		if (GTK_IS_CELL_RENDERER_TEXT (renderer))
+			break;
+		list = g_list_delete_link (list, list);
+	}
+	g_list_free (list);
+
+	/* Make the text cell renderer editable, but only temporarily.
+	 * We don't want editing to be activated by simply clicking on
+	 * the source name.  Too easy for accidental edits to occur. */
+	g_object_set (renderer, "editable", TRUE, NULL);
+	gtk_tree_view_expand_to_path (tree_view, path);
+	gtk_tree_view_set_cursor_on_cell (
+		tree_view, path, column, renderer, TRUE);
+	g_object_set (renderer, "editable", FALSE, NULL);
+
+	gtk_tree_path_free (path);
+}
+
+/**
+ * e_source_selector_ref_primary_selection:
+ * @selector: An #ESourceSelector widget
+ *
+ * Get the primary selected source.  The primary selection is the one that is
+ * highlighted through the normal #GtkTreeView selection mechanism (as opposed
+ * to the "normal" selection, which is the set of source whose checkboxes are
+ * checked).
+ *
+ * The returned #ESource is referenced for thread-safety and must be
+ * unreferenced with g_object_unref() when finished with it.
+ *
+ * Returns: The selected source.
+ *
+ * Since: 3.6
+ **/
+ESource *
+e_source_selector_ref_primary_selection (ESourceSelector *selector)
+{
+	ESource *source;
+	GtkTreeRowReference *reference;
+	GtkTreeSelection *selection;
+	GtkTreeView *tree_view;
+	GtkTreeModel *model;
+	GtkTreeIter iter;
+	const gchar *extension_name;
+	gboolean have_iter = FALSE;
+
+	g_return_val_if_fail (E_IS_SOURCE_SELECTOR (selector), NULL);
+
+	tree_view = GTK_TREE_VIEW (selector);
+	model = gtk_tree_view_get_model (tree_view);
+	selection = gtk_tree_view_get_selection (tree_view);
+
+	reference = selector->priv->saved_primary_selection;
+
+	if (gtk_tree_row_reference_valid (reference)) {
+		GtkTreePath *path;
+
+		path = gtk_tree_row_reference_get_path (reference);
+		have_iter = gtk_tree_model_get_iter (model, &iter, path);
+		gtk_tree_path_free (path);
+	}
+
+	if (!have_iter)
+		have_iter = gtk_tree_selection_get_selected (
+			selection, NULL, &iter);
+
+	if (!have_iter)
+		return NULL;
+
+	gtk_tree_model_get (model, &iter, COLUMN_SOURCE, &source, -1);
+
+	extension_name = e_source_selector_get_extension_name (selector);
+
+	if (!e_source_has_extension (source, extension_name)) {
+		g_object_unref (source);
+		return NULL;
+	}
+
+	return source;
+}
+
+/**
+ * e_source_selector_set_primary_selection:
+ * @selector: an #ESourceSelector widget
+ * @source: an #ESource to select
+ *
+ * Highlights @source in @selector.  The highlighted #ESource is called
+ * the primary selection.
+ *
+ * Do not confuse this function with e_source_selector_select_source(),
+ * which activates the check box next to an #ESource's display name in
+ * @selector.  This function does not alter the check box.
+ **/
+void
+e_source_selector_set_primary_selection (ESourceSelector *selector,
+                                         ESource *source)
+{
+	GHashTable *source_index;
+	GtkTreeRowReference *reference;
+	GtkTreeSelection *selection;
+	GtkTreeView *tree_view;
+	GtkTreePath *child_path;
+	GtkTreePath *parent_path;
+	const gchar *extension_name;
+
+	g_return_if_fail (E_IS_SOURCE_SELECTOR (selector));
+	g_return_if_fail (E_IS_SOURCE (source));
+
+	tree_view = GTK_TREE_VIEW (selector);
+	selection = gtk_tree_view_get_selection (tree_view);
+
+	source_index = selector->priv->source_index;
+	reference = g_hash_table_lookup (source_index, source);
+
+	/* XXX Maybe we should return a success/fail boolean? */
+	if (!gtk_tree_row_reference_valid (reference))
+		return;
+
+	extension_name = e_source_selector_get_extension_name (selector);
+
+	/* Return silently if attempting to select a parent node
+	 * lacking the expected extension (e.g. On This Computer). */
+	if (!e_source_has_extension (source, extension_name))
+		return;
+
+	/* We block the signal because this all needs to be atomic */
+	g_signal_handlers_block_matched (
+		selection, G_SIGNAL_MATCH_FUNC,
+		0, 0, NULL, selection_changed_callback, NULL);
+	gtk_tree_selection_unselect_all (selection);
+	g_signal_handlers_unblock_matched (
+		selection, G_SIGNAL_MATCH_FUNC,
+		0, 0, NULL, selection_changed_callback, NULL);
+
+	clear_saved_primary_selection (selector);
+
+	child_path = gtk_tree_row_reference_get_path (reference);
+
+	parent_path = gtk_tree_path_copy (child_path);
+	gtk_tree_path_up (parent_path);
+
+	if (gtk_tree_view_row_expanded (tree_view, parent_path)) {
+		gtk_tree_selection_select_path (selection, child_path);
+	} else {
+		selector->priv->saved_primary_selection =
+			gtk_tree_row_reference_copy (reference);
+		g_signal_emit (selector, signals[PRIMARY_SELECTION_CHANGED], 0);
+		g_object_notify (G_OBJECT (selector), "primary-selection");
+	}
+
+	gtk_tree_path_free (child_path);
+	gtk_tree_path_free (parent_path);
+}
+
+/**
+ * e_source_selector_ref_source_by_path:
+ * @selector: an #ESourceSelector
+ * @path: a #GtkTreePath
+ *
+ * Returns the #ESource object at @path, or %NULL if @path is invalid.
+ *
+ * The returned #ESource is referenced for thread-safety and must be
+ * unreferenced with g_object_unref() when finished with it.
+ *
+ * Returns: the #ESource object at @path, or %NULL
+ *
+ * Since: 3.6
+ **/
+ESource *
+e_source_selector_ref_source_by_path (ESourceSelector *selector,
+                                      GtkTreePath *path)
+{
+	ESource *source = NULL;
+	GtkTreeModel *model;
+	GtkTreeIter iter;
+
+	g_return_val_if_fail (E_IS_SOURCE_SELECTOR (selector), NULL);
+	g_return_val_if_fail (path != NULL, NULL);
+
+	model = gtk_tree_view_get_model (GTK_TREE_VIEW (selector));
+
+	if (gtk_tree_model_get_iter (model, &iter, path))
+		gtk_tree_model_get (model, &iter, COLUMN_SOURCE, &source, -1);
+
+	return source;
+}
+
+/**
+ * e_source_selector_queue_write:
+ * @selector: an #ESourceSelecetor
+ * @source: an #ESource with changes to be written
+ *
+ * Queues a main loop idle callback to write changes to @source back to
+ * the D-Bus registry service.
+ *
+ * Since: 3.6
+ **/
+void
+e_source_selector_queue_write (ESourceSelector *selector,
+                               ESource *source)
+{
+	GSource *idle_source;
+	GHashTable *pending_writes;
+	GMainContext *main_context;
+	AsyncContext *async_context;
+
+	g_return_if_fail (E_IS_SOURCE_SELECTOR (selector));
+	g_return_if_fail (E_IS_SOURCE (source));
+
+	main_context = selector->priv->main_context;
+	pending_writes = selector->priv->pending_writes;
+
+	idle_source = g_hash_table_lookup (pending_writes, source);
+	if (idle_source != NULL && !g_source_is_destroyed (idle_source))
+		return;
+
+	async_context = g_slice_new0 (AsyncContext);
+	async_context->selector = g_object_ref (selector);
+	async_context->source = g_object_ref (source);
+
+	/* Set a higher priority so this idle source runs before our
+	 * source_selector_cancel_write() signal handler, which will
+	 * cancel this idle source.  Cancellation is the right thing
+	 * to do when receiving changes from OTHER registry clients,
+	 * but we don't want to cancel our own changes.
+	 *
+	 * XXX This might be an argument for using etags.
+	 */
+	idle_source = g_idle_source_new ();
+	g_hash_table_insert (
+		pending_writes,
+		g_object_ref (source),
+		g_source_ref (idle_source));
+	g_source_set_callback (
+		idle_source,
+		source_selector_write_idle_cb,
+		async_context,
+		(GDestroyNotify) async_context_free);
+	g_source_set_priority (idle_source, G_PRIORITY_HIGH_IDLE);
+	g_source_attach (idle_source, main_context);
+	g_source_unref (idle_source);
+}
+
diff --git a/e-util/e-source-selector.h b/e-util/e-source-selector.h
new file mode 100644
index 0000000..d4d9228
--- /dev/null
+++ b/e-util/e-source-selector.h
@@ -0,0 +1,141 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* e-source-selector.h
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This program 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 program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * Author: Ettore Perazzoli <ettore ximian com>
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_SOURCE_SELECTOR_H
+#define E_SOURCE_SELECTOR_H
+
+#include <gtk/gtk.h>
+#include <libedataserver/libedataserver.h>
+
+/* Standard GObject macros */
+#define E_TYPE_SOURCE_SELECTOR \
+	(e_source_selector_get_type ())
+#define E_SOURCE_SELECTOR(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_SOURCE_SELECTOR, ESourceSelector))
+#define E_SOURCE_SELECTOR_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_SOURCE_SELECTOR, ESourceSelectorClass))
+#define E_IS_SOURCE_SELECTOR(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_SOURCE_SELECTOR))
+#define E_IS_SOURCE_SELECTOR_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_SOURCE_SELECTOR))
+#define E_SOURCE_SELECTOR_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_SOURCE_SELECTOR, ESourceSelectorClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ESourceSelector ESourceSelector;
+typedef struct _ESourceSelectorClass ESourceSelectorClass;
+typedef struct _ESourceSelectorPrivate ESourceSelectorPrivate;
+
+struct _ESourceSelector {
+	GtkTreeView parent;
+	ESourceSelectorPrivate *priv;
+};
+
+struct _ESourceSelectorClass {
+	GtkTreeViewClass parent_class;
+
+	/* Methods */
+	gboolean	(*get_source_selected)	(ESourceSelector *selector,
+						 ESource *source);
+	void		(*set_source_selected)	(ESourceSelector *selector,
+						 ESource *source,
+						 gboolean selected);
+
+	/* Signals */
+	void		(*selection_changed)	(ESourceSelector *selector);
+	void		(*primary_selection_changed)
+						(ESourceSelector *selector);
+	gboolean	(*popup_event)		(ESourceSelector *selector,
+						 ESource *primary,
+						 GdkEventButton *event);
+	gboolean	(*data_dropped)		(ESourceSelector *selector,
+						 GtkSelectionData *data,
+						 ESource *destination,
+						 GdkDragAction action,
+						 guint target_info);
+
+	gpointer padding1;
+	gpointer padding2;
+	gpointer padding3;
+};
+
+GType		e_source_selector_get_type	(void);
+GtkWidget *	e_source_selector_new		(ESourceRegistry *registry,
+						 const gchar *extension_name);
+ESourceRegistry *
+		e_source_selector_get_registry	(ESourceSelector *selector);
+const gchar *	e_source_selector_get_extension_name
+						(ESourceSelector *selector);
+gboolean	e_source_selector_get_show_colors
+						(ESourceSelector *selector);
+void		e_source_selector_set_show_colors
+						(ESourceSelector *selector,
+						 gboolean show_colors);
+gboolean	e_source_selector_get_show_toggles
+						(ESourceSelector *selector);
+void		e_source_selector_set_show_toggles
+						(ESourceSelector *selector,
+						 gboolean show_toggles);
+void		e_source_selector_select_source	(ESourceSelector *selector,
+						 ESource *source);
+void		e_source_selector_unselect_source
+						(ESourceSelector *selector,
+						 ESource *source);
+void		e_source_selector_select_exclusive
+						(ESourceSelector *selector,
+						 ESource *source);
+gboolean	e_source_selector_source_is_selected
+						(ESourceSelector *selector,
+						 ESource *source);
+GSList *	e_source_selector_get_selection	(ESourceSelector *selector);
+void		e_source_selector_free_selection
+						(GSList *list);
+void		e_source_selector_set_select_new
+						(ESourceSelector *selector,
+						 gboolean state);
+void		e_source_selector_edit_primary_selection
+						(ESourceSelector *selector);
+ESource *	e_source_selector_ref_primary_selection
+						(ESourceSelector *selector);
+void		e_source_selector_set_primary_selection
+						(ESourceSelector *selector,
+						 ESource *source);
+ESource *	e_source_selector_ref_source_by_path
+						(ESourceSelector *selector,
+						 GtkTreePath *path);
+void		e_source_selector_queue_write	(ESourceSelector *selector,
+						 ESource *source);
+
+G_END_DECLS
+
+#endif /* E_SOURCE_SELECTOR_H */
diff --git a/e-util/e-source-util.h b/e-util/e-source-util.h
index 452e911..e10097f 100644
--- a/e-util/e-source-util.h
+++ b/e-util/e-source-util.h
@@ -16,6 +16,10 @@
  *
  */
 
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
 /* These functions combine asynchronous ESource and ESourceRegistry methods
  * with Evolution's EActivity and EAlert facilities to offer an easy-to-use,
  * "fire-and-forget" API for ESource operations.  Use these in situations
@@ -28,7 +32,7 @@
 #include <libedataserver/libedataserver.h>
 
 #include <e-util/e-activity.h>
-#include <libevolution-utils/e-alert-sink.h>
+#include <e-util/e-alert-sink.h>
 
 G_BEGIN_DECLS
 
diff --git a/widgets/misc/e-spell-entry.c b/e-util/e-spell-entry.c
similarity index 100%
rename from widgets/misc/e-spell-entry.c
rename to e-util/e-spell-entry.c
diff --git a/e-util/e-spell-entry.h b/e-util/e-spell-entry.h
new file mode 100644
index 0000000..07c4c0d
--- /dev/null
+++ b/e-util/e-spell-entry.h
@@ -0,0 +1,63 @@
+/*
+ * e-spell-entry.h
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_SPELL_ENTRY_H
+#define E_SPELL_ENTRY_H
+
+#include <gtk/gtk.h>
+
+#define E_TYPE_SPELL_ENTRY            (e_spell_entry_get_type())
+#define E_SPELL_ENTRY(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), E_TYPE_SPELL_ENTRY, ESpellEntry))
+#define E_SPELL_ENTRY_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass), E_TYPE_SPELL_ENTRY, ESpellEntryClass))
+#define E_IS_SPELL_ENTRY(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), E_TYPE_SPELL_ENTRY))
+#define E_IS_SPELL_ENTRY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), E_TYPE_SPELL_ENTRY))
+#define E_SPELL_ENTRY_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), E_TYPE_SPELL_ENTRY, ESpellEntryClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ESpellEntry		ESpellEntry;
+typedef struct _ESpellEntryClass	ESpellEntryClass;
+typedef struct _ESpellEntryPrivate	ESpellEntryPrivate;
+
+struct _ESpellEntry
+{
+	GtkEntry parent_object;
+
+	ESpellEntryPrivate *priv;
+};
+
+struct _ESpellEntryClass
+{
+	GtkEntryClass parent_class;
+};
+
+GType		e_spell_entry_get_type			(void);
+GtkWidget *	e_spell_entry_new			(void);
+void		e_spell_entry_set_languages		(ESpellEntry *spell_entry,
+							 GList *languages);
+gboolean	e_spell_entry_get_checking_enabled	(ESpellEntry *spell_entry);
+void		e_spell_entry_set_checking_enabled	(ESpellEntry *spell_entry,
+							 gboolean enable_checking);
+
+G_END_DECLS
+
+#endif /* E_SPELL_ENTRY_H */
diff --git a/e-util/e-stock-request.c b/e-util/e-stock-request.c
index 8736dba..2b00f9f 100644
--- a/e-util/e-stock-request.c
+++ b/e-util/e-stock-request.c
@@ -21,10 +21,9 @@
 #include "e-stock-request.h"
 
 #include <stdlib.h>
+#include <gtk/gtk.h>
 #include <libsoup/soup.h>
 
-#include <e-util/e-util.h>
-
 #include <string.h>
 
 #define d(x)
diff --git a/e-util/e-stock-request.h b/e-util/e-stock-request.h
index 39a22ba..b482d7f 100644
--- a/e-util/e-stock-request.h
+++ b/e-util/e-stock-request.h
@@ -16,6 +16,10 @@
  *
  */
 
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
 #ifndef E_STOCK_REQUEST_H
 #define E_STOCK_REQUEST_H
 
diff --git a/e-util/e-table-click-to-add.c b/e-util/e-table-click-to-add.c
new file mode 100644
index 0000000..6de00f9
--- /dev/null
+++ b/e-util/e-table-click-to-add.c
@@ -0,0 +1,666 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-table-click-to-add.h"
+
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+#include <gdk/gdkkeysyms.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <libgnomecanvas/libgnomecanvas.h>
+
+#include "e-canvas-utils.h"
+#include "e-canvas.h"
+#include "e-marshal.h"
+#include "e-table-defines.h"
+#include "e-table-header.h"
+#include "e-table-one.h"
+#include "e-text.h"
+#include "gal-a11y-e-table-click-to-add.h"
+
+enum {
+	CURSOR_CHANGE,
+	STYLE_SET,
+	LAST_SIGNAL
+};
+
+static guint etcta_signals[LAST_SIGNAL] = { 0 };
+
+/* workaround for avoiding APi breakage */
+#define etcta_get_type e_table_click_to_add_get_type
+G_DEFINE_TYPE (ETableClickToAdd, etcta, GNOME_TYPE_CANVAS_GROUP)
+
+enum {
+	PROP_0,
+	PROP_HEADER,
+	PROP_MODEL,
+	PROP_MESSAGE,
+	PROP_WIDTH,
+	PROP_HEIGHT
+};
+
+static void
+etcta_cursor_change (GObject *object,
+                     gint row,
+                     gint col,
+                     ETableClickToAdd *etcta)
+{
+	g_signal_emit (
+		etcta,
+		etcta_signals[CURSOR_CHANGE], 0,
+		row, col);
+}
+
+static void
+etcta_style_set (ETableClickToAdd *etcta,
+                 GtkStyle *previous_style)
+{
+	GtkWidget *widget;
+	GtkStyle *style;
+
+	widget = GTK_WIDGET (GNOME_CANVAS_ITEM (etcta)->canvas);
+	style = gtk_widget_get_style (widget);
+
+	if (etcta->rect)
+		gnome_canvas_item_set (
+			etcta->rect,
+			"outline_color_gdk", &style->fg[GTK_STATE_NORMAL],
+			"fill_color_gdk", &style->bg[GTK_STATE_NORMAL],
+			NULL);
+
+	if (etcta->text)
+		gnome_canvas_item_set (
+			etcta->text,
+			"fill_color_gdk", &style->text[GTK_STATE_NORMAL],
+			NULL);
+}
+
+static void
+etcta_add_table_header (ETableClickToAdd *etcta,
+                        ETableHeader *header)
+{
+	etcta->eth = header;
+	if (etcta->eth)
+		g_object_ref (etcta->eth);
+	if (etcta->row)
+		gnome_canvas_item_set (
+			GNOME_CANVAS_ITEM (etcta->row),
+			"ETableHeader", header,
+			NULL);
+}
+
+static void
+etcta_drop_table_header (ETableClickToAdd *etcta)
+{
+	if (!etcta->eth)
+		return;
+
+	g_object_unref (etcta->eth);
+	etcta->eth = NULL;
+}
+
+static void
+etcta_add_one (ETableClickToAdd *etcta,
+               ETableModel *one)
+{
+	etcta->one = one;
+	if (etcta->one)
+		g_object_ref (etcta->one);
+	if (etcta->row)
+		gnome_canvas_item_set (
+			GNOME_CANVAS_ITEM (etcta->row),
+			"ETableModel", one,
+			NULL);
+	g_object_set (
+		etcta->selection,
+		"model", one,
+		NULL);
+}
+
+static void
+etcta_drop_one (ETableClickToAdd *etcta)
+{
+	if (!etcta->one)
+		return;
+	g_object_unref (etcta->one);
+	etcta->one = NULL;
+	g_object_set (
+		etcta->selection,
+		"model", NULL,
+		NULL);
+}
+
+static void
+etcta_add_model (ETableClickToAdd *etcta,
+                 ETableModel *model)
+{
+	etcta->model = model;
+	if (etcta->model)
+		g_object_ref (etcta->model);
+}
+
+static void
+etcta_drop_model (ETableClickToAdd *etcta)
+{
+	etcta_drop_one (etcta);
+	if (!etcta->model)
+		return;
+	g_object_unref (etcta->model);
+	etcta->model = NULL;
+}
+
+static void
+etcta_add_message (ETableClickToAdd *etcta,
+                   const gchar *message)
+{
+	etcta->message = g_strdup (message);
+}
+
+static void
+etcta_drop_message (ETableClickToAdd *etcta)
+{
+	g_free (etcta->message);
+	etcta->message = NULL;
+}
+
+static void
+etcta_dispose (GObject *object)
+{
+	ETableClickToAdd *etcta = E_TABLE_CLICK_TO_ADD (object);
+
+	etcta_drop_table_header (etcta);
+	etcta_drop_model (etcta);
+	etcta_drop_message (etcta);
+	if (etcta->selection)
+		g_object_unref (etcta->selection);
+	etcta->selection = NULL;
+
+	/* Chain up to parent's dispose() method. */
+	G_OBJECT_CLASS (etcta_parent_class)->dispose (object);
+}
+
+static void
+etcta_set_property (GObject *object,
+                    guint property_id,
+                    const GValue *value,
+                    GParamSpec *pspec)
+{
+	GnomeCanvasItem *item;
+	ETableClickToAdd *etcta;
+
+	item = GNOME_CANVAS_ITEM (object);
+	etcta = E_TABLE_CLICK_TO_ADD (object);
+
+	switch (property_id) {
+	case PROP_HEADER:
+		etcta_drop_table_header (etcta);
+		etcta_add_table_header (etcta, E_TABLE_HEADER (g_value_get_object (value)));
+		break;
+	case PROP_MODEL:
+		etcta_drop_model (etcta);
+		etcta_add_model (etcta, E_TABLE_MODEL (g_value_get_object (value)));
+		break;
+	case PROP_MESSAGE:
+		etcta_drop_message (etcta);
+		etcta_add_message (etcta, g_value_get_string (value));
+		break;
+	case PROP_WIDTH:
+		etcta->width = g_value_get_double (value);
+		if (etcta->row)
+			gnome_canvas_item_set (
+				etcta->row,
+				"minimum_width", etcta->width,
+				NULL);
+		if (etcta->text)
+			gnome_canvas_item_set (
+				etcta->text,
+				"width", (etcta->width < 4 ? 4 : etcta->width) - 4,
+				NULL);
+		if (etcta->rect)
+			gnome_canvas_item_set (
+				etcta->rect,
+				"x2", etcta->width - 1,
+				NULL);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+		return;
+
+	}
+	gnome_canvas_item_request_update (item);
+}
+
+static void
+create_rect_and_text (ETableClickToAdd *etcta)
+{
+	GtkWidget *widget;
+	GtkStyle *style;
+
+	widget = GTK_WIDGET (GNOME_CANVAS_ITEM (etcta)->canvas);
+	style = gtk_widget_get_style (widget);
+
+	if (!etcta->rect)
+		etcta->rect = gnome_canvas_item_new (
+			GNOME_CANVAS_GROUP (etcta),
+			gnome_canvas_rect_get_type (),
+			"x1", (gdouble) 0,
+			"y1", (gdouble) 0,
+			"x2", (gdouble) etcta->width - 1,
+			"y2", (gdouble) etcta->height - 1,
+			"outline_color_gdk", &style->fg[GTK_STATE_NORMAL],
+			"fill_color_gdk", &style->bg[GTK_STATE_NORMAL],
+			NULL);
+
+	if (!etcta->text)
+		etcta->text = gnome_canvas_item_new (
+			GNOME_CANVAS_GROUP (etcta),
+			e_text_get_type (),
+			"text", etcta->message ? etcta->message : "",
+			"width", etcta->width - 4,
+			"fill_color_gdk", &style->text[GTK_STATE_NORMAL],
+			NULL);
+}
+
+static void
+etcta_get_property (GObject *object,
+                    guint property_id,
+                    GValue *value,
+                    GParamSpec *pspec)
+{
+	ETableClickToAdd *etcta;
+
+	etcta = E_TABLE_CLICK_TO_ADD (object);
+
+	switch (property_id) {
+	case PROP_HEADER:
+		g_value_set_object (value, etcta->eth);
+		break;
+	case PROP_MODEL:
+		g_value_set_object (value, etcta->model);
+		break;
+	case PROP_MESSAGE:
+		g_value_set_string (value, etcta->message);
+		break;
+	case PROP_WIDTH:
+		g_value_set_double (value, etcta->width);
+		break;
+	case PROP_HEIGHT:
+		g_value_set_double (value, etcta->height);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+		break;
+	}
+}
+
+static void
+etcta_realize (GnomeCanvasItem *item)
+{
+	ETableClickToAdd *etcta = E_TABLE_CLICK_TO_ADD (item);
+
+	create_rect_and_text (etcta);
+	e_canvas_item_move_absolute (etcta->text, 2, 2);
+
+	if (GNOME_CANVAS_ITEM_CLASS (etcta_parent_class)->realize)
+		(*GNOME_CANVAS_ITEM_CLASS (etcta_parent_class)->realize)(item);
+
+	e_canvas_item_request_reflow (item);
+}
+
+static void
+etcta_unrealize (GnomeCanvasItem *item)
+{
+	if (GNOME_CANVAS_ITEM_CLASS (etcta_parent_class)->unrealize)
+		(*GNOME_CANVAS_ITEM_CLASS (etcta_parent_class)->unrealize)(item);
+}
+
+static void finish_editing (ETableClickToAdd *etcta);
+
+static gint
+item_key_press (ETableItem *item,
+                gint row,
+                gint col,
+                GdkEvent *event,
+                ETableClickToAdd *etcta)
+{
+	switch (event->key.keyval) {
+		case GDK_KEY_Return:
+		case GDK_KEY_KP_Enter:
+		case GDK_KEY_ISO_Enter:
+		case GDK_KEY_3270_Enter:
+			finish_editing (etcta);
+			return TRUE;
+	}
+	return FALSE;
+}
+
+static void
+set_initial_selection (ETableClickToAdd *etcta)
+{
+	e_selection_model_do_something (
+		E_SELECTION_MODEL (etcta->selection),
+		0, e_table_header_prioritized_column (etcta->eth),
+		0);
+}
+
+static void
+finish_editing (ETableClickToAdd *etcta)
+{
+	if (etcta->row) {
+		ETableModel *one;
+
+		e_table_item_leave_edit (E_TABLE_ITEM (etcta->row));
+		e_table_one_commit (E_TABLE_ONE (etcta->one));
+		etcta_drop_one (etcta);
+		g_object_run_dispose (G_OBJECT (etcta->row));
+		etcta->row = NULL;
+
+		one = e_table_one_new (etcta->model);
+		etcta_add_one (etcta, one);
+		g_object_unref (one);
+
+		e_selection_model_clear (E_SELECTION_MODEL (etcta->selection));
+
+		etcta->row = gnome_canvas_item_new (
+			GNOME_CANVAS_GROUP (etcta),
+			e_table_item_get_type (),
+			"ETableHeader", etcta->eth,
+			"ETableModel", etcta->one,
+			"minimum_width", etcta->width,
+			"horizontal_draw_grid", TRUE,
+			"vertical_draw_grid", TRUE,
+			"selection_model", etcta->selection,
+			"cursor_mode", E_CURSOR_SPREADSHEET,
+			NULL);
+
+		g_signal_connect (
+			etcta->row, "key_press",
+			G_CALLBACK (item_key_press), etcta);
+
+		set_initial_selection (etcta);
+	}
+}
+
+/* Handles the events on the ETableClickToAdd, particularly
+ * it creates the ETableItem and passes in some events. */
+static gint
+etcta_event (GnomeCanvasItem *item,
+             GdkEvent *e)
+{
+	ETableClickToAdd *etcta = E_TABLE_CLICK_TO_ADD (item);
+
+	switch (e->type) {
+	case GDK_FOCUS_CHANGE:
+		if (!e->focus_change.in)
+			return TRUE;
+
+	case GDK_BUTTON_PRESS:
+		if (etcta->text) {
+			g_object_run_dispose (G_OBJECT (etcta->text));
+			etcta->text = NULL;
+		}
+		if (etcta->rect) {
+			g_object_run_dispose (G_OBJECT (etcta->rect));
+			etcta->rect = NULL;
+		}
+		if (!etcta->row) {
+			ETableModel *one;
+
+			one = e_table_one_new (etcta->model);
+			etcta_add_one (etcta, one);
+			g_object_unref (one);
+
+			e_selection_model_clear (E_SELECTION_MODEL (etcta->selection));
+
+			etcta->row = gnome_canvas_item_new (
+				GNOME_CANVAS_GROUP (item),
+				e_table_item_get_type (),
+				"ETableHeader", etcta->eth,
+				"ETableModel", etcta->one,
+				"minimum_width", etcta->width,
+				"horizontal_draw_grid", TRUE,
+				"vertical_draw_grid", TRUE,
+				"selection_model", etcta->selection,
+				"cursor_mode", E_CURSOR_SPREADSHEET,
+				NULL);
+
+			g_signal_connect (
+				etcta->row, "key_press",
+				G_CALLBACK (item_key_press), etcta);
+
+			e_canvas_item_grab_focus (GNOME_CANVAS_ITEM (etcta->row), TRUE);
+
+			set_initial_selection (etcta);
+		}
+		break;
+
+	case GDK_KEY_PRESS:
+		switch (e->key.keyval) {
+		case GDK_KEY_Tab:
+		case GDK_KEY_KP_Tab:
+		case GDK_KEY_ISO_Left_Tab:
+			finish_editing (etcta);
+			break;
+		default:
+			return FALSE;
+		case GDK_KEY_Escape:
+			if (etcta->row) {
+				e_table_item_leave_edit (E_TABLE_ITEM (etcta->row));
+				etcta_drop_one (etcta);
+				g_object_run_dispose (G_OBJECT (etcta->row));
+				etcta->row = NULL;
+				create_rect_and_text (etcta);
+				e_canvas_item_move_absolute (etcta->text, 3, 3);
+			}
+			break;
+		}
+		break;
+
+	default:
+		return FALSE;
+	}
+	return TRUE;
+}
+
+static void
+etcta_reflow (GnomeCanvasItem *item,
+              gint flags)
+{
+	ETableClickToAdd *etcta = E_TABLE_CLICK_TO_ADD (item);
+
+	gdouble old_height = etcta->height;
+
+	if (etcta->text) {
+		g_object_get (
+			etcta->text,
+			"height", &etcta->height,
+			NULL);
+		etcta->height += 6;
+	}
+	if (etcta->row) {
+		g_object_get (
+			etcta->row,
+			"height", &etcta->height,
+			NULL);
+	}
+
+	if (etcta->rect) {
+		g_object_set (
+			etcta->rect,
+			"y2", etcta->height - 1,
+			NULL);
+	}
+
+	if (old_height != etcta->height)
+		e_canvas_item_request_parent_reflow (item);
+}
+
+static void
+etcta_class_init (ETableClickToAddClass *class)
+{
+	GnomeCanvasItemClass *item_class = GNOME_CANVAS_ITEM_CLASS (class);
+	GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+	class->cursor_change = NULL;
+	class->style_set     = etcta_style_set;
+
+	object_class->dispose      = etcta_dispose;
+	object_class->set_property = etcta_set_property;
+	object_class->get_property = etcta_get_property;
+
+	item_class->realize     = etcta_realize;
+	item_class->unrealize   = etcta_unrealize;
+	item_class->event       = etcta_event;
+
+	g_object_class_install_property (
+		object_class,
+		PROP_HEADER,
+		g_param_spec_object (
+			"header",
+			"Header",
+			NULL,
+			E_TYPE_TABLE_HEADER,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_MODEL,
+		g_param_spec_object (
+			"model",
+			"Model",
+			NULL,
+			E_TYPE_TABLE_MODEL,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_MESSAGE,
+		g_param_spec_string (
+			"message",
+			"Message",
+			NULL,
+			NULL,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_WIDTH,
+		g_param_spec_double (
+			"width",
+			"Width",
+			NULL,
+			0.0, G_MAXDOUBLE, 0.0,
+			G_PARAM_READWRITE |
+			G_PARAM_LAX_VALIDATION));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_HEIGHT,
+		g_param_spec_double (
+			"height",
+			"Height",
+			NULL,
+			0.0, G_MAXDOUBLE, 0.0,
+			G_PARAM_READABLE |
+			G_PARAM_LAX_VALIDATION));
+
+	etcta_signals[CURSOR_CHANGE] = g_signal_new (
+		"cursor_change",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ETableClickToAddClass, cursor_change),
+		NULL, NULL,
+		e_marshal_VOID__INT_INT,
+		G_TYPE_NONE, 2,
+		G_TYPE_INT,
+		G_TYPE_INT);
+
+	etcta_signals[STYLE_SET] = g_signal_new (
+		"style_set",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ETableClickToAddClass, style_set),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__OBJECT,
+		G_TYPE_NONE, 1,
+		GTK_TYPE_STYLE);
+
+	gal_a11y_e_table_click_to_add_init ();
+}
+
+static void
+etcta_init (ETableClickToAdd *etcta)
+{
+	AtkObject *a11y;
+
+	etcta->one = NULL;
+	etcta->model = NULL;
+	etcta->eth = NULL;
+
+	etcta->message = NULL;
+
+	etcta->row = NULL;
+	etcta->text = NULL;
+	etcta->rect = NULL;
+
+	/* Pick some arbitrary defaults. */
+	etcta->width = 12;
+	etcta->height = 6;
+
+	etcta->selection = e_table_selection_model_new ();
+	g_signal_connect (
+		etcta->selection, "cursor_changed",
+		G_CALLBACK (etcta_cursor_change), etcta);
+
+	e_canvas_item_set_reflow_callback (GNOME_CANVAS_ITEM (etcta), etcta_reflow);
+
+	/* create its a11y object at this time if accessibility is enabled*/
+	if (atk_get_root () != NULL) {
+		a11y = atk_gobject_accessible_for_object (G_OBJECT (etcta));
+		atk_object_set_name (a11y, _("click to add"));
+	}
+}
+
+/* The colors in this need to be themefied. */
+/**
+ * e_table_click_to_add_commit:
+ * @etcta: The %ETableClickToAdd to commit.
+ *
+ * This routine commits the current thing being edited and returns to
+ * just displaying the click to add message.
+ **/
+void
+e_table_click_to_add_commit (ETableClickToAdd *etcta)
+{
+	if (etcta->row) {
+		e_table_one_commit (E_TABLE_ONE (etcta->one));
+		etcta_drop_one (etcta);
+		g_object_run_dispose (G_OBJECT (etcta->row));
+		etcta->row = NULL;
+	}
+	create_rect_and_text (etcta);
+	e_canvas_item_move_absolute (etcta->text, 3, 3);
+}
diff --git a/e-util/e-table-click-to-add.h b/e-util/e-table-click-to-add.h
new file mode 100644
index 0000000..cd1519b
--- /dev/null
+++ b/e-util/e-table-click-to-add.h
@@ -0,0 +1,99 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef _E_TABLE_CLICK_TO_ADD_H_
+#define _E_TABLE_CLICK_TO_ADD_H_
+
+#include <libxml/tree.h>
+#include <libgnomecanvas/libgnomecanvas.h>
+
+#include <e-util/e-table-header.h>
+#include <e-util/e-table-item.h>
+#include <e-util/e-table-selection-model.h>
+#include <e-util/e-table-sort-info.h>
+
+/* Standard GObject macros */
+#define E_TYPE_TABLE_CLICK_TO_ADD \
+	(e_table_click_to_add_get_type ())
+#define E_TABLE_CLICK_TO_ADD(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_TABLE_CLICK_TO_ADD, ETableClickToAdd))
+#define E_TABLE_CLICK_TO_ADD_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_TABLE_CLICK_TO_ADD, ETableClickToAddClass))
+#define E_IS_TABLE_CLICK_TO_ADD(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_TABLE_CLICK_TO_ADD))
+#define E_IS_TABLE_CLICK_TO_ADD_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_TABLE_CLICK_TO_ADD))
+#define E_TABLE_CLICK_TO_ADD_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_TABLE_CLICK_TO_ADD, ETableClickToAddClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ETableClickToAdd ETableClickToAdd;
+typedef struct _ETableClickToAddClass ETableClickToAddClass;
+
+struct _ETableClickToAdd {
+	GnomeCanvasGroup  parent;
+
+	ETableModel      *one;    /* The ETableOne. */
+
+	ETableModel      *model;  /* The backend model. */
+	ETableHeader     *eth;    /* This is just to give to the ETableItem. */
+
+	gchar             *message;
+
+	GnomeCanvasItem  *row;    /* If row is NULL, we're sitting with
+				   * no data and a "Click here" message. */
+	GnomeCanvasItem  *text;   /* If text is NULL, row shouldn't be. */
+	GnomeCanvasItem  *rect;   /* What the heck.  Why not. */
+
+	gdouble           width;
+	gdouble           height;
+
+	ETableSelectionModel *selection;
+};
+
+struct _ETableClickToAddClass {
+	GnomeCanvasGroupClass parent_class;
+
+	/* Signals */
+	void		(*cursor_change)	(ETableClickToAdd *etcta,
+						 gint row,
+						 gint col);
+	void		(*style_set)		(ETableClickToAdd *etcta,
+						 GtkStyle *previous_style);
+};
+
+GType		e_table_click_to_add_get_type	(void) G_GNUC_CONST;
+void		e_table_click_to_add_commit	(ETableClickToAdd *etcta);
+
+G_END_DECLS
+
+#endif /* _E_TABLE_CLICK_TO_ADD_H_ */
diff --git a/e-util/e-table-col-dnd.h b/e-util/e-table-col-dnd.h
new file mode 100644
index 0000000..608e14e
--- /dev/null
+++ b/e-util/e-table-col-dnd.h
@@ -0,0 +1,43 @@
+/*
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef _E_TABLE_COL_DND_H_
+#define _E_TABLE_COL_DND_H_
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+#define TARGET_ETABLE_COL_TYPE "application/x-etable-column-header"
+
+enum {
+	TARGET_ETABLE_COL_HEADER
+};
+
+G_END_DECLS
+
+#endif /* _E_TABLE_COL_DND_H_ */
diff --git a/e-util/e-table-col.c b/e-util/e-table-col.c
new file mode 100644
index 0000000..4e5e18a
--- /dev/null
+++ b/e-util/e-table-col.c
@@ -0,0 +1,222 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Miguel de Icaza <miguel ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <glib/gi18n.h>
+
+#include "e-table-col.h"
+
+G_DEFINE_TYPE (ETableCol, e_table_col, G_TYPE_OBJECT)
+
+enum {
+	PROP_0,
+	PROP_COMPARE_COL
+};
+
+static void
+etc_load_icon (ETableCol *etc)
+{
+	/* FIXME This ignores theme changes. */
+
+	GtkIconTheme *icon_theme;
+	gint width, height;
+	GError *error = NULL;
+
+	icon_theme = gtk_icon_theme_get_default ();
+	gtk_icon_size_lookup (GTK_ICON_SIZE_MENU, &width, &height);
+
+	etc->pixbuf = gtk_icon_theme_load_icon (
+		icon_theme, etc->icon_name, height, 0, &error);
+
+	if (error != NULL) {
+		g_warning ("%s", error->message);
+		g_error_free (error);
+	}
+}
+
+static void
+etc_dispose (GObject *object)
+{
+	ETableCol *etc = E_TABLE_COL (object);
+
+	if (etc->ecell)
+		g_object_unref (etc->ecell);
+	etc->ecell = NULL;
+
+	if (etc->pixbuf)
+		g_object_unref (etc->pixbuf);
+	etc->pixbuf = NULL;
+
+	g_free (etc->text);
+	etc->text = NULL;
+
+	g_free (etc->icon_name);
+	etc->icon_name = NULL;
+
+	/* Chain up to parent's dispose() method. */
+	G_OBJECT_CLASS (e_table_col_parent_class)->dispose (object);
+}
+
+static void
+etc_set_property (GObject *object,
+                  guint property_id,
+                  const GValue *value,
+                  GParamSpec *pspec)
+{
+	ETableCol *etc = E_TABLE_COL (object);
+
+	switch (property_id) {
+	case PROP_COMPARE_COL:
+		etc->compare_col = g_value_get_int (value);
+		break;
+	default:
+		break;
+	}
+}
+
+static void
+etc_get_property (GObject *object,
+                  guint property_id,
+                  GValue *value,
+                  GParamSpec *pspec)
+{
+	ETableCol *etc = E_TABLE_COL (object);
+
+	switch (property_id) {
+	case PROP_COMPARE_COL:
+		g_value_set_int (value, etc->compare_col);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+		break;
+	}
+}
+
+static void
+e_table_col_class_init (ETableColClass *class)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+	object_class->dispose = etc_dispose;
+	object_class->set_property = etc_set_property;
+	object_class->get_property = etc_get_property;
+
+	g_object_class_install_property (
+		object_class,
+		PROP_COMPARE_COL,
+		g_param_spec_int (
+			"compare_col",
+			"Width",
+			"Width",
+			0, G_MAXINT, 0,
+			G_PARAM_READWRITE));
+}
+
+static void
+e_table_col_init (ETableCol *etc)
+{
+	etc->width = 0;
+	etc->sortable = 1;
+	etc->groupable = 1;
+	etc->justification = GTK_JUSTIFY_LEFT;
+	etc->priority = 0;
+}
+
+/**
+ * e_table_col_new:
+ * @col_idx: the column we represent in the model
+ * @text: a title for this column
+ * @icon_name: name of the icon to be used for the header, or %NULL
+ * @expansion: FIXME
+ * @min_width: minimum width in pixels for this column
+ * @ecell: the renderer to be used for this column
+ * @compare: comparision function for the elements stored in this column
+ * @resizable: whether the column can be resized interactively by the user
+ * @priority: FIXME
+ *
+ * The ETableCol represents a column to be used inside an ETable.  The
+ * ETableCol objects are inserted inside an ETableHeader (which is just a
+ * collection of ETableCols).  The ETableHeader is the definition of the
+ * order in which columns are shown to the user.
+ *
+ * The @text argument is the the text that will be shown as a header to the
+ * user. @col_idx reflects where the data for this ETableCol object will
+ * be fetch from an ETableModel.  So even if the user changes the order
+ * of the columns being viewed (the ETableCols in the ETableHeader), the
+ * column will always point to the same column inside the ETableModel.
+ *
+ * The @ecell argument is an ECell object that needs to know how to
+ * render the data in the ETableModel for this specific row.
+ *
+ * Data passed to @compare can be (if not %NULL) a cmp_cache, which
+ * can be accessed by e_table_sorting_utils_add_to_cmp_cache() and
+ * e_table_sorting_utils_lookup_cmp_cache().
+ *
+ * Returns: the newly created ETableCol object.
+ */
+ETableCol *
+e_table_col_new (gint col_idx,
+                 const gchar *text,
+                 const gchar *icon_name,
+                 gdouble expansion,
+                 gint min_width,
+                 ECell *ecell,
+                 GCompareDataFunc compare,
+                 gboolean resizable,
+                 gboolean disabled,
+                 gint priority)
+{
+	ETableCol *etc;
+
+	g_return_val_if_fail (expansion >= 0, NULL);
+	g_return_val_if_fail (min_width >= 0, NULL);
+	g_return_val_if_fail (ecell != NULL, NULL);
+	g_return_val_if_fail (compare != NULL, NULL);
+	g_return_val_if_fail (text != NULL, NULL);
+
+	etc = g_object_new (E_TYPE_TABLE_COL, NULL);
+
+	etc->col_idx = col_idx;
+	etc->compare_col = col_idx;
+	etc->text = g_strdup (text);
+	etc->icon_name = g_strdup (icon_name);
+	etc->pixbuf = NULL;
+	etc->expansion = expansion;
+	etc->min_width = min_width;
+	etc->ecell = ecell;
+	etc->compare = compare;
+	etc->disabled = disabled;
+	etc->priority = priority;
+
+	etc->selected = 0;
+	etc->resizable = resizable;
+
+	g_object_ref (etc->ecell);
+
+	if (etc->icon_name != NULL)
+		etc_load_icon (etc);
+
+	return etc;
+}
diff --git a/e-util/e-table-col.h b/e-util/e-table-col.h
new file mode 100644
index 0000000..243aa04
--- /dev/null
+++ b/e-util/e-table-col.h
@@ -0,0 +1,115 @@
+/*
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Miguel de Icaza <miguel ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_TABLE_COL_H
+#define E_TABLE_COL_H
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#include <e-util/e-cell.h>
+
+/* Standard GObject macros */
+#define E_TYPE_TABLE_COL \
+	(e_table_col_get_type ())
+#define E_TABLE_COL(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_TABLE_COL, ETableCol))
+#define E_TABLE_COL_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_TABLE_COL, ETableColClass))
+#define E_IS_TABLE_COL(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_TABLE_COL))
+#define E_IS_TABLE_COL_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_TABLE_COL))
+#define E_TABLE_COL_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_TABLE_COL, ETableColClass))
+
+G_BEGIN_DECLS
+
+typedef enum {
+	E_TABLE_COL_ARROW_NONE = 0,
+	E_TABLE_COL_ARROW_UP,
+	E_TABLE_COL_ARROW_DOWN
+} ETableColArrow;
+
+typedef struct _ETableCol ETableCol;
+typedef struct _ETableColClass ETableColClass;
+
+/*
+ * Information about a single column
+ */
+struct _ETableCol {
+	GObject parent;
+
+	gchar *text;
+	gchar *icon_name;
+	GdkPixbuf *pixbuf;
+	gint min_width;
+	gint width;
+	gdouble expansion;
+	gshort x;
+	GCompareDataFunc compare;
+	ETableSearchFunc search;
+
+	guint selected : 1;
+	guint resizable : 1;
+	guint disabled : 1;
+	guint sortable : 1;
+	guint groupable : 1;
+
+	gint col_idx;
+	gint compare_col;
+	gint priority;
+
+	GtkJustification justification;
+
+	ECell *ecell;
+};
+
+struct _ETableColClass {
+	GObjectClass parent_class;
+};
+
+GType		e_table_col_get_type		(void) G_GNUC_CONST;
+ETableCol *	e_table_col_new			(gint col_idx,
+						 const gchar *text,
+						 const gchar *icon_name,
+						 gdouble expansion,
+						 gint min_width,
+						 ECell *ecell,
+						 GCompareDataFunc compare,
+						 gboolean resizable,
+						 gboolean disabled,
+						 gint priority);
+
+G_END_DECLS
+
+#endif /* E_TABLE_COL_H */
+
diff --git a/e-util/e-table-column-specification.c b/e-util/e-table-column-specification.c
new file mode 100644
index 0000000..d1cf089
--- /dev/null
+++ b/e-util/e-table-column-specification.c
@@ -0,0 +1,157 @@
+/*
+ * e-table-column-specification.c - Savable specification of a column.
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-table-column-specification.h"
+
+#include <stdlib.h>
+
+#include <libxml/parser.h>
+#include <libxml/xmlmemory.h>
+
+#include "e-xml-utils.h"
+
+/* workaround for avoiding API breakage */
+#define etcs_get_type e_table_column_specification_get_type
+G_DEFINE_TYPE (ETableColumnSpecification, etcs, G_TYPE_OBJECT)
+
+static void
+free_strings (ETableColumnSpecification *etcs)
+{
+	g_free (etcs->title);
+	etcs->title = NULL;
+	g_free (etcs->pixbuf);
+	etcs->pixbuf = NULL;
+	g_free (etcs->cell);
+	etcs->cell = NULL;
+	g_free (etcs->compare);
+	etcs->compare = NULL;
+	g_free (etcs->search);
+	etcs->search = NULL;
+	g_free (etcs->sortable);
+	etcs->sortable = NULL;
+}
+
+static void
+etcs_finalize (GObject *object)
+{
+	ETableColumnSpecification *etcs = E_TABLE_COLUMN_SPECIFICATION (object);
+
+	free_strings (etcs);
+
+	G_OBJECT_CLASS (etcs_parent_class)->finalize (object);
+}
+
+static void
+etcs_class_init (ETableColumnSpecificationClass *class)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+	object_class->finalize = etcs_finalize;
+}
+
+static void
+etcs_init (ETableColumnSpecification *specification)
+{
+	specification->model_col     = 0;
+	specification->compare_col   = 0;
+	specification->title         = g_strdup ("");
+	specification->pixbuf        = NULL;
+
+	specification->expansion     = 0;
+	specification->minimum_width = 0;
+	specification->resizable     = FALSE;
+	specification->disabled      = FALSE;
+
+	specification->cell          = NULL;
+	specification->compare       = NULL;
+	specification->search        = NULL;
+	specification->priority      = 0;
+}
+
+ETableColumnSpecification *
+e_table_column_specification_new (void)
+{
+	return g_object_new (E_TYPE_TABLE_COLUMN_SPECIFICATION, NULL);
+}
+
+void
+e_table_column_specification_load_from_node (ETableColumnSpecification *etcs,
+                                             const xmlNode *node)
+{
+	free_strings (etcs);
+
+	etcs->model_col     = e_xml_get_integer_prop_by_name (node, (const guchar *)"model_col");
+	etcs->compare_col   = e_xml_get_integer_prop_by_name_with_default (node, (const guchar *)"compare_col", etcs->model_col);
+	etcs->title         = e_xml_get_string_prop_by_name (node, (const guchar *)"_title");
+	etcs->pixbuf        = e_xml_get_string_prop_by_name (node, (const guchar *)"pixbuf");
+
+	etcs->expansion     = e_xml_get_double_prop_by_name (node, (const guchar *)"expansion");
+	etcs->minimum_width = e_xml_get_integer_prop_by_name (node, (const guchar *)"minimum_width");
+	etcs->resizable     = e_xml_get_bool_prop_by_name (node, (const guchar *)"resizable");
+	etcs->disabled      = e_xml_get_bool_prop_by_name (node, (const guchar *)"disabled");
+
+	etcs->cell          = e_xml_get_string_prop_by_name (node, (const guchar *)"cell");
+	etcs->compare       = e_xml_get_string_prop_by_name (node, (const guchar *)"compare");
+	etcs->search        = e_xml_get_string_prop_by_name (node, (const guchar *)"search");
+	etcs->sortable	    = e_xml_get_string_prop_by_name (node, (const guchar *)"sortable");
+	etcs->priority      = e_xml_get_integer_prop_by_name_with_default (node, (const guchar *)"priority", 0);
+
+	if (etcs->title == NULL)
+		etcs->title = g_strdup ("");
+}
+
+xmlNode *
+e_table_column_specification_save_to_node (ETableColumnSpecification *specification,
+                                           xmlNode *parent)
+{
+	xmlNode *node;
+	if (parent)
+		node = xmlNewChild (parent, NULL, (const guchar *)"ETableColumn", NULL);
+	else
+		node = xmlNewNode (NULL, (const guchar *)"ETableColumn");
+
+	e_xml_set_integer_prop_by_name (node, (const guchar *)"model_col", specification->model_col);
+	if (specification->compare_col != specification->model_col)
+		e_xml_set_integer_prop_by_name (node, (const guchar *)"compare_col", specification->compare_col);
+	e_xml_set_string_prop_by_name (node, (const guchar *)"_title", specification->title);
+	e_xml_set_string_prop_by_name (node, (const guchar *)"pixbuf", specification->pixbuf);
+
+	e_xml_set_double_prop_by_name (node, (const guchar *)"expansion", specification->expansion);
+	e_xml_set_integer_prop_by_name (node, (const guchar *)"minimum_width", specification->minimum_width);
+	e_xml_set_bool_prop_by_name (node, (const guchar *)"resizable", specification->resizable);
+	e_xml_set_bool_prop_by_name (node, (const guchar *)"disabled", specification->disabled);
+
+	e_xml_set_string_prop_by_name (node, (const guchar *)"cell", specification->cell);
+	e_xml_set_string_prop_by_name (node, (const guchar *)"compare", specification->compare);
+	e_xml_set_string_prop_by_name (node, (const guchar *)"search", specification->search);
+	if (specification->priority != 0)
+		e_xml_set_integer_prop_by_name (node, (const guchar *)"priority", specification->priority);
+
+	return node;
+}
+
diff --git a/e-util/e-table-column-specification.h b/e-util/e-table-column-specification.h
new file mode 100644
index 0000000..ae1a00c
--- /dev/null
+++ b/e-util/e-table-column-specification.h
@@ -0,0 +1,93 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef _E_TABLE_COLUMN_SPECIFICATION_H_
+#define _E_TABLE_COLUMN_SPECIFICATION_H_
+
+#include <glib-object.h>
+#include <libxml/tree.h>
+
+/* Standard GObject macros */
+#define E_TYPE_TABLE_COLUMN_SPECIFICATION \
+	(e_table_column_specification_get_type ())
+#define E_TABLE_COLUMN_SPECIFICATION(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_TABLE_COLUMN_SPECIFICATION, ETableColumnSpecification))
+#define E_TABLE_COLUMN_SPECIFICATION_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_TABLE_COLUMN_SPECIFICATION, ETableColumnSpecificationClass))
+#define E_IS_TABLE_COLUMN_SPECIFICATION(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_TABLE_COLUMN_SPECIFICATION))
+#define E_IS_TABLE_COLUMN_SPECIFICATION_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_TABLE_COLUMN_SPECIFICATION))
+#define E_TABLE_COLUMN_SPECIFICATION_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_TABLE_COLUMN_SPECIFICATION, ETableColumnSpecificationClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ETableColumnSpecification ETableColumnSpecification;
+typedef struct _ETableColumnSpecificationClass ETableColumnSpecificationClass;
+
+struct _ETableColumnSpecification {
+	GObject parent;
+
+	gint model_col;
+	gint compare_col;
+	gchar *title;
+	gchar *pixbuf;
+
+	gdouble expansion;
+	gint minimum_width;
+	guint resizable : 1;
+	guint disabled : 1;
+
+	gchar *cell;
+	gchar *compare;
+	gchar *search;
+	gchar *sortable;
+	gint priority;
+};
+
+struct _ETableColumnSpecificationClass {
+	GObjectClass parent_class;
+};
+
+GType		e_table_column_specification_get_type	(void) G_GNUC_CONST;
+ETableColumnSpecification *
+		e_table_column_specification_new	(void);
+void		e_table_column_specification_load_from_node
+					(ETableColumnSpecification *state,
+					 const xmlNode *node);
+xmlNode *	e_table_column_specification_save_to_node
+					(ETableColumnSpecification *state,
+					 xmlNode *parent);
+
+G_END_DECLS
+
+#endif /* _E_TABLE_COLUMN_SPECIFICATION_H_ */
diff --git a/e-util/e-table-config.c b/e-util/e-table-config.c
new file mode 100644
index 0000000..98f89ff
--- /dev/null
+++ b/e-util/e-table-config.c
@@ -0,0 +1,1481 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *		Miguel de Icaza <miguel ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+/*
+ * FIXME:
+ *    Sort Dialog: when text is selected, the toggle button switches state.
+ *    Make Clear all work.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-table-config.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+
+#include "e-table-memory-store.h"
+#include "e-table-without.h"
+#include "e-unicode.h"
+#include "e-util-private.h"
+
+G_DEFINE_TYPE (ETableConfig, e_table_config, G_TYPE_OBJECT)
+
+enum {
+	CHANGED,
+	LAST_SIGNAL
+};
+
+enum {
+	PROP_0,
+	PROP_STATE
+};
+
+enum {
+	COLUMN_ITEM,
+	COLUMN_VALUE
+};
+
+static guint e_table_config_signals[LAST_SIGNAL] = { 0, };
+
+static void
+config_finalize (GObject *object)
+{
+	ETableConfig *config = E_TABLE_CONFIG (object);
+
+	if (config->state)
+		g_object_unref (config->state);
+	config->state = NULL;
+
+	if (config->source_state)
+		g_object_unref (config->source_state);
+	config->source_state = NULL;
+
+	if (config->source_spec)
+		g_object_unref (config->source_spec);
+	config->source_spec = NULL;
+
+	g_free (config->header);
+	config->header = NULL;
+
+	g_slist_free (config->column_names);
+	config->column_names = NULL;
+
+	g_free (config->domain);
+	config->domain = NULL;
+
+	G_OBJECT_CLASS (e_table_config_parent_class)->finalize (object);
+}
+
+static void
+e_table_config_changed (ETableConfig *config,
+                        ETableState *state)
+{
+	g_return_if_fail (E_IS_TABLE_CONFIG (config));
+
+	g_signal_emit (config, e_table_config_signals[CHANGED], 0, state);
+}
+
+static void
+config_dialog_changed (ETableConfig *config)
+{
+	/* enable the apply/ok buttons */
+	gtk_dialog_set_response_sensitive (
+		GTK_DIALOG (config->dialog_toplevel),
+		GTK_RESPONSE_APPLY, TRUE);
+	gtk_dialog_set_response_sensitive (
+		GTK_DIALOG (config->dialog_toplevel),
+		GTK_RESPONSE_OK, TRUE);
+}
+
+static void
+config_get_property (GObject *object,
+                     guint property_id,
+                     GValue *value,
+                     GParamSpec *pspec)
+{
+	ETableConfig *config = E_TABLE_CONFIG (object);
+
+	switch (property_id) {
+	case PROP_STATE:
+		g_value_set_object (value, config->state);
+		break;
+	default:
+		break;
+	}
+}
+
+static void
+e_table_config_class_init (ETableConfigClass *class)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+	class->changed        = NULL;
+
+	object_class->finalize = config_finalize;
+	object_class->get_property = config_get_property;
+
+	e_table_config_signals[CHANGED] = g_signal_new (
+		"changed",
+		G_TYPE_FROM_CLASS (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ETableConfigClass, changed),
+		(GSignalAccumulator) NULL, NULL,
+		g_cclosure_marshal_VOID__VOID,
+		G_TYPE_NONE, 0);
+
+	g_object_class_install_property (
+		object_class,
+		PROP_STATE,
+		g_param_spec_object (
+			"state",
+			"State",
+			NULL,
+			E_TYPE_TABLE_STATE,
+			G_PARAM_READABLE));
+}
+
+static void
+configure_combo_box_add (GtkComboBox *combo_box,
+                         const gchar *item,
+                         const gchar *value)
+{
+	GtkTreeRowReference *reference;
+	GtkTreeModel *model;
+	GtkTreePath *path;
+	GHashTable *index;
+	GtkTreeIter iter;
+
+	model = gtk_combo_box_get_model (combo_box);
+	gtk_list_store_append (GTK_LIST_STORE (model), &iter);
+
+	gtk_list_store_set (
+		GTK_LIST_STORE (model), &iter,
+		COLUMN_ITEM, item, COLUMN_VALUE, value, -1);
+
+	index = g_object_get_data (G_OBJECT (combo_box), "index");
+	g_return_if_fail (index != NULL);
+
+	/* Add an entry to the tree model index. */
+	path = gtk_tree_model_get_path (model, &iter);
+	reference = gtk_tree_row_reference_new (model, path);
+	g_return_if_fail (reference != NULL);
+	g_hash_table_insert (index, g_strdup (value), reference);
+	gtk_tree_path_free (path);
+}
+
+static gchar *
+configure_combo_box_get_active (GtkComboBox *combo_box)
+{
+	GtkTreeIter iter;
+	gchar *value = NULL;
+
+	if (gtk_combo_box_get_active_iter (combo_box, &iter))
+		gtk_tree_model_get (
+			gtk_combo_box_get_model (combo_box), &iter,
+			COLUMN_VALUE, &value, -1);
+
+	if (value != NULL && *value == '\0') {
+		g_free (value);
+		value = NULL;
+	}
+
+	return value;
+}
+
+static void
+configure_combo_box_set_active (GtkComboBox *combo_box,
+                                const gchar *value)
+{
+	GtkTreeRowReference *reference;
+	GHashTable *index;
+
+	index = g_object_get_data (G_OBJECT (combo_box), "index");
+	g_return_if_fail (index != NULL);
+
+	reference = g_hash_table_lookup (index, value);
+	if (reference != NULL) {
+		GtkTreeModel *model;
+		GtkTreePath *path;
+		GtkTreeIter iter;
+
+		model = gtk_tree_row_reference_get_model (reference);
+		path = gtk_tree_row_reference_get_path (reference);
+
+		if (path == NULL)
+			return;
+
+		if (gtk_tree_model_get_iter (model, &iter, path))
+			gtk_combo_box_set_active_iter (combo_box, &iter);
+
+		gtk_tree_path_free (path);
+	}
+}
+
+static ETableColumnSpecification *
+find_column_in_spec (ETableSpecification *spec,
+                     gint model_col)
+{
+	ETableColumnSpecification **column;
+
+	for (column = spec->columns; *column; column++) {
+		if ((*column)->disabled)
+			continue;
+		if ((*column)->model_col != model_col)
+			continue;
+
+		return *column;
+	}
+
+	return NULL;
+}
+
+static gint
+find_model_column_by_name (ETableSpecification *spec,
+                           const gchar *s)
+{
+	ETableColumnSpecification **column;
+
+	for (column = spec->columns; *column; column++) {
+
+		if ((*column)->disabled)
+			continue;
+		if (g_ascii_strcasecmp ((*column)->title, s) == 0)
+			return (*column)->model_col;
+	}
+	return -1;
+}
+
+static void
+update_sort_and_group_config_dialog (ETableConfig *config,
+                                     gboolean is_sort)
+{
+	ETableConfigSortWidgets *widgets;
+	gint count, i;
+
+	if (is_sort) {
+		count = e_table_sort_info_sorting_get_count (
+			config->temp_state->sort_info);
+		widgets = &config->sort[0];
+	} else {
+		count = e_table_sort_info_grouping_get_count (
+			config->temp_state->sort_info);
+		widgets = &config->group[0];
+	}
+
+	for (i = 0; i < 4; i++) {
+		gboolean sensitive = (i <= count);
+		const gchar *text = "";
+
+		gtk_widget_set_sensitive (widgets[i].frames, sensitive);
+
+		/*
+		 * Sorting is set, auto select the text
+		 */
+		g_signal_handler_block (
+			widgets[i].radio_ascending,
+			widgets[i].toggled_id);
+		g_signal_handler_block (
+			widgets[i].combo,
+			widgets[i].changed_id);
+
+		if (i < count) {
+			GtkToggleButton *a, *d;
+			ETableSortColumn col =
+				is_sort
+				? e_table_sort_info_sorting_get_nth (
+					config->temp_state->sort_info,
+					i)
+				:  e_table_sort_info_grouping_get_nth (
+					config->temp_state->sort_info,
+					i);
+
+			ETableColumnSpecification *column =
+				find_column_in_spec (config->source_spec, col.column);
+
+			if (!column) {
+				/*
+				 * This is a bug in the programmer
+				 * stuff, but by the time we arrive
+				 * here, the user has been given a
+				 * warning
+				 */
+				continue;
+			}
+
+			text = column->title;
+
+			/*
+			 * Update radio buttons
+			 */
+			a = GTK_TOGGLE_BUTTON (
+				widgets[i].radio_ascending);
+			d = GTK_TOGGLE_BUTTON (
+				widgets[i].radio_descending);
+
+			gtk_toggle_button_set_active (col.ascending ? a : d, 1);
+		} else {
+			GtkToggleButton *t;
+
+			t = GTK_TOGGLE_BUTTON (
+				widgets[i].radio_ascending);
+
+			if (is_sort)
+				g_return_if_fail (
+					widgets[i].radio_ascending !=
+					config->group[i].radio_ascending);
+			else
+				g_return_if_fail (
+					widgets[i].radio_ascending !=
+					config->sort[i].radio_ascending);
+			gtk_toggle_button_set_active (t, 1);
+		}
+
+		/* Set the text */
+		configure_combo_box_set_active (
+			GTK_COMBO_BOX (widgets[i].combo), text);
+
+		g_signal_handler_unblock (
+			widgets[i].radio_ascending,
+			widgets[i].toggled_id);
+		g_signal_handler_unblock (
+			widgets[i].combo,
+			widgets[i].changed_id);
+	}
+}
+
+static void
+config_sort_info_update (ETableConfig *config)
+{
+	ETableSortInfo *info = config->state->sort_info;
+	GString *res;
+	gint count, i;
+
+	count = e_table_sort_info_sorting_get_count (info);
+	res = g_string_new ("");
+
+	for (i = 0; i < count; i++) {
+		ETableSortColumn col = e_table_sort_info_sorting_get_nth (info, i);
+		ETableColumnSpecification *column;
+
+		column = find_column_in_spec (config->source_spec, col.column);
+		if (!column) {
+			g_warning ("Could not find column model in specification");
+			continue;
+		}
+
+		g_string_append (res, dgettext (config->domain, (column)->title));
+		g_string_append_c (res, ' ');
+		g_string_append (
+			res,
+			col.ascending ?
+			_("(Ascending)") : _("(Descending)"));
+
+		if ((i + 1) != count)
+			g_string_append (res, ", ");
+	}
+
+	if (res->str[0] == 0)
+		g_string_append (res, _("Not sorted"));
+
+	gtk_label_set_text (GTK_LABEL (config->sort_label), res->str);
+
+	g_string_free (res, TRUE);
+}
+
+static void
+config_group_info_update (ETableConfig *config)
+{
+	ETableSortInfo *info = config->state->sort_info;
+	GString *res;
+	gint count, i;
+
+	if (!e_table_sort_info_get_can_group (info))
+		return;
+
+	count = e_table_sort_info_grouping_get_count (info);
+	res = g_string_new ("");
+
+	for (i = 0; i < count; i++) {
+		ETableSortColumn col = e_table_sort_info_grouping_get_nth (info, i);
+		ETableColumnSpecification *column;
+
+		column = find_column_in_spec (config->source_spec, col.column);
+		if (!column) {
+			g_warning ("Could not find model column in specification");
+			continue;
+		}
+
+		g_string_append (res, dgettext (config->domain, (column)->title));
+		g_string_append_c (res, ' ');
+		g_string_append (
+			res,
+			col.ascending ?
+			_("(Ascending)") : _("(Descending)"));
+
+		if ((i + 1) != count)
+			g_string_append (res, ", ");
+	}
+	if (res->str[0] == 0)
+		g_string_append (res, _("No grouping"));
+
+	gtk_label_set_text (GTK_LABEL (config->group_label), res->str);
+	g_string_free (res, TRUE);
+}
+
+static void
+setup_fields (ETableConfig *config)
+{
+	gint i;
+
+	e_table_model_freeze ((ETableModel *) config->available_model);
+	e_table_model_freeze ((ETableModel *) config->shown_model);
+	e_table_without_show_all (config->available_model);
+	e_table_subset_variable_clear (config->shown_model);
+
+	if (config->temp_state) {
+		for (i = 0; i < config->temp_state->col_count; i++) {
+			gint j, idx;
+			for (j = 0, idx = 0; j < config->temp_state->columns[i]; j++)
+				if (!config->source_spec->columns[j]->disabled)
+					idx++;
+
+			e_table_subset_variable_add (config->shown_model, idx);
+			e_table_without_hide (config->available_model, GINT_TO_POINTER (idx));
+		}
+	}
+	e_table_model_thaw ((ETableModel *) config->available_model);
+	e_table_model_thaw ((ETableModel *) config->shown_model);
+}
+
+static void
+config_fields_info_update (ETableConfig *config)
+{
+	ETableColumnSpecification **column;
+	GString *res = g_string_new ("");
+	gint i, j;
+
+	for (i = 0; i < config->state->col_count; i++) {
+		for (j = 0, column = config->source_spec->columns; *column; column++, j++) {
+
+			if ((*column)->disabled)
+				continue;
+
+			if (config->state->columns[i] != j)
+				continue;
+
+			g_string_append (res, dgettext (config->domain, (*column)->title));
+			if (i + 1 < config->state->col_count)
+				g_string_append (res, ", ");
+
+			break;
+		}
+	}
+
+	gtk_label_set_text (GTK_LABEL (config->fields_label), res->str);
+	g_string_free (res, TRUE);
+}
+
+static void
+do_sort_and_group_config_dialog (ETableConfig *config,
+                                 gboolean is_sort)
+{
+	GtkDialog *dialog;
+	gint response, running = 1;
+
+	config->temp_state = e_table_state_duplicate (config->state);
+
+	update_sort_and_group_config_dialog (config, is_sort);
+
+	gtk_widget_grab_focus (GTK_WIDGET (
+		is_sort
+		? config->sort[0].combo
+		: config->group[0].combo));
+
+	if (is_sort)
+		dialog = GTK_DIALOG (config->dialog_sort);
+	else
+		dialog = GTK_DIALOG (config->dialog_group_by);
+
+	gtk_window_set_transient_for (
+		GTK_WINDOW (dialog), GTK_WINDOW (config->dialog_toplevel));
+
+	do {
+		response = gtk_dialog_run (dialog);
+		switch (response) {
+		case 0: /* clear fields */
+			if (is_sort) {
+				e_table_sort_info_sorting_truncate (
+					config->temp_state->sort_info, 0);
+			} else {
+				e_table_sort_info_grouping_truncate (
+					config->temp_state->sort_info, 0);
+			}
+			update_sort_and_group_config_dialog (config, is_sort);
+			break;
+
+		case GTK_RESPONSE_OK:
+			g_object_unref (config->state);
+			config->state = config->temp_state;
+			config->temp_state = NULL;
+			running = 0;
+			config_dialog_changed (config);
+			break;
+
+		case GTK_RESPONSE_DELETE_EVENT:
+		case GTK_RESPONSE_CANCEL:
+			g_object_unref (config->temp_state);
+			config->temp_state = NULL;
+			running = 0;
+			break;
+		}
+
+	} while (running);
+	gtk_widget_hide (GTK_WIDGET (dialog));
+
+	if (is_sort)
+		config_sort_info_update (config);
+	else
+		config_group_info_update (config);
+}
+
+static void
+do_fields_config_dialog (ETableConfig *config)
+{
+	GtkDialog *dialog;
+	GtkWidget *widget;
+	gint response, running = 1;
+
+	dialog = GTK_DIALOG (config->dialog_show_fields);
+
+	gtk_widget_ensure_style (config->dialog_show_fields);
+
+	widget = gtk_dialog_get_content_area (dialog);
+	gtk_container_set_border_width (GTK_CONTAINER (widget), 0);
+
+	widget = gtk_dialog_get_action_area (dialog);
+	gtk_container_set_border_width (GTK_CONTAINER (widget), 12);
+
+	config->temp_state = e_table_state_duplicate (config->state);
+
+	setup_fields (config);
+
+	gtk_window_set_transient_for (
+		GTK_WINDOW (config->dialog_show_fields),
+		GTK_WINDOW (config->dialog_toplevel));
+
+	do {
+		response = gtk_dialog_run (GTK_DIALOG (config->dialog_show_fields));
+		switch (response) {
+		case GTK_RESPONSE_OK:
+			g_object_unref (config->state);
+			config->state = config->temp_state;
+			config->temp_state = NULL;
+			running = 0;
+			config_dialog_changed (config);
+			break;
+
+		case GTK_RESPONSE_DELETE_EVENT:
+		case GTK_RESPONSE_CANCEL:
+			g_object_unref (config->temp_state);
+			config->temp_state = NULL;
+			running = 0;
+			break;
+		}
+
+	} while (running);
+	gtk_widget_hide (GTK_WIDGET (config->dialog_show_fields));
+
+	config_fields_info_update (config);
+}
+
+static ETableMemoryStoreColumnInfo store_columns[] = {
+	E_TABLE_MEMORY_STORE_STRING,
+	E_TABLE_MEMORY_STORE_INTEGER,
+	E_TABLE_MEMORY_STORE_TERMINATOR
+};
+
+static ETableModel *
+create_store (ETableConfig *config)
+{
+	gint i;
+	ETableModel *store;
+
+	store = e_table_memory_store_new (store_columns);
+	for (i = 0; config->source_spec->columns[i]; i++) {
+
+		gchar *text;
+
+		if (config->source_spec->columns[i]->disabled)
+			continue;
+
+		text = g_strdup (dgettext (
+			config->domain,
+			config->source_spec->columns[i]->title));
+		e_table_memory_store_insert_adopt (
+			E_TABLE_MEMORY_STORE (store), -1, NULL, text, i);
+	}
+
+	return store;
+}
+
+static const gchar *spec =
+"<ETableSpecification gettext-domain=\"" GETTEXT_PACKAGE "\""
+" no-headers=\"true\" cursor-mode=\"line\" draw-grid=\"false\" "
+" draw-focus=\"true\" selection-mode=\"browse\">"
+"<ETableColumn model_col= \"0\" _title=\"Name\" minimum_width=\"30\""
+" resizable=\"true\" cell=\"string\" compare=\"string\"/>"
+"<ETableState> <column source=\"0\"/>"
+"<grouping/>"
+"</ETableState>"
+"</ETableSpecification>";
+
+static GtkWidget *
+e_table_proxy_etable_shown_new (ETableModel *store)
+{
+	ETableModel *model = NULL;
+	GtkWidget *widget;
+
+	model = e_table_subset_variable_new (store);
+
+	widget = e_table_new (model, NULL, spec, NULL);
+
+	atk_object_set_name (
+		gtk_widget_get_accessible (widget),
+		_("Show Fields"));
+
+	return widget;
+}
+
+static GtkWidget *
+e_table_proxy_etable_available_new (ETableModel *store)
+{
+	ETableModel *model;
+	GtkWidget *widget;
+
+	model = e_table_without_new (
+		store, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
+
+	e_table_without_show_all (E_TABLE_WITHOUT (model));
+
+	widget = e_table_new (model, NULL, spec, NULL);
+
+	atk_object_set_name (
+		gtk_widget_get_accessible (widget),
+		_("Available Fields"));
+
+	return widget;
+}
+
+static void
+config_button_fields (GtkWidget *widget,
+                      ETableConfig *config)
+{
+	do_fields_config_dialog (config);
+}
+
+static void
+config_button_sort (GtkWidget *widget,
+                    ETableConfig *config)
+{
+	do_sort_and_group_config_dialog (config, TRUE);
+}
+
+static void
+config_button_group (GtkWidget *widget,
+                     ETableConfig *config)
+{
+	do_sort_and_group_config_dialog (config, FALSE);
+}
+
+static void
+dialog_destroyed (gpointer data,
+                  GObject *where_object_was)
+{
+	ETableConfig *config = data;
+	g_object_unref (config);
+}
+
+static void
+dialog_response (GtkWidget *dialog,
+                 gint response_id,
+                 ETableConfig *config)
+{
+	if (response_id == GTK_RESPONSE_APPLY
+	    || response_id == GTK_RESPONSE_OK) {
+		e_table_config_changed (config, config->state);
+	}
+
+	if (response_id == GTK_RESPONSE_CANCEL
+	    || response_id == GTK_RESPONSE_OK) {
+		gtk_widget_destroy (dialog);
+	}
+}
+
+/*
+ * Invoked by the GtkBuilder auto-connect code
+ */
+static GtkWidget *
+e_table_proxy_gtk_combo_text_new (void)
+{
+	GtkCellRenderer *renderer;
+	GtkListStore *store;
+	GtkWidget *combo_box;
+	GHashTable *index;
+
+	store = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_STRING);
+	combo_box = gtk_combo_box_new_with_model (GTK_TREE_MODEL (store));
+
+	renderer = gtk_cell_renderer_text_new ();
+	gtk_cell_layout_pack_start (
+		GTK_CELL_LAYOUT (combo_box), renderer, FALSE);
+	gtk_cell_layout_add_attribute (
+		GTK_CELL_LAYOUT (combo_box), renderer, "text", COLUMN_ITEM);
+
+	/* Embed a reverse-lookup index into the widget. */
+	index = g_hash_table_new_full (
+		g_str_hash, g_str_equal,
+		(GDestroyNotify) g_free,
+		(GDestroyNotify) gtk_tree_row_reference_free);
+	g_object_set_data_full (
+		G_OBJECT (combo_box), "index", index,
+		(GDestroyNotify) g_hash_table_destroy);
+
+	return combo_box;
+}
+
+static void
+connect_button (ETableConfig *config,
+                GtkBuilder *builder,
+                const gchar *widget_name,
+                GCallback cback)
+{
+	GtkWidget *button = e_builder_get_widget (builder, widget_name);
+
+	if (button)
+		g_signal_connect (button, "clicked", cback, config);
+}
+
+static gint
+get_source_model_col_index (ETableConfig *config,
+                            gint idx)
+{
+	gint visible_index;
+	ETableModel *src_model;
+
+	src_model = E_TABLE_SUBSET (config->available_model)->source;
+
+	visible_index = e_table_subset_view_to_model_row (
+		E_TABLE_SUBSET (config->available_model), idx);
+
+	return GPOINTER_TO_INT (e_table_model_value_at (src_model, 1, visible_index));
+}
+
+static void
+sort_combo_changed (GtkComboBox *combo_box,
+                    ETableConfigSortWidgets *sort)
+{
+	ETableConfig *config = sort->e_table_config;
+	ETableSortInfo *sort_info = config->temp_state->sort_info;
+	ETableConfigSortWidgets *base = &config->sort[0];
+	GtkToggleButton *toggle_button;
+	gint idx = sort - base;
+	gchar *s;
+
+	s = configure_combo_box_get_active (combo_box);
+
+	if (s != NULL) {
+		ETableSortColumn c;
+		gint col;
+
+		col = find_model_column_by_name (config->source_spec, s);
+		if (col == -1) {
+			g_warning ("sort: This should not happen (%s)", s);
+			g_free (s);
+			return;
+		}
+
+		toggle_button = GTK_TOGGLE_BUTTON (
+			config->sort[idx].radio_ascending);
+		c.ascending = gtk_toggle_button_get_active (toggle_button);
+		c.column = col;
+		e_table_sort_info_sorting_set_nth (sort_info, idx, c);
+
+		update_sort_and_group_config_dialog (config, TRUE);
+	}  else {
+		e_table_sort_info_sorting_truncate (sort_info, idx);
+		update_sort_and_group_config_dialog (config, TRUE);
+	}
+
+	g_free (s);
+}
+
+static void
+sort_ascending_toggled (GtkToggleButton *t,
+                        ETableConfigSortWidgets *sort)
+{
+	ETableConfig *config = sort->e_table_config;
+	ETableSortInfo *si = config->temp_state->sort_info;
+	ETableConfigSortWidgets *base = &config->sort[0];
+	gint idx = sort - base;
+	ETableSortColumn c;
+
+	c = e_table_sort_info_sorting_get_nth (si, idx);
+	c.ascending = gtk_toggle_button_get_active (t);
+	e_table_sort_info_sorting_set_nth (si, idx, c);
+}
+
+static void
+configure_sort_dialog (ETableConfig *config,
+                       GtkBuilder *builder)
+{
+	GSList *l;
+	gint i;
+
+	const gchar *algs[] = {
+		"alignment4",
+		"alignment3",
+		"alignment2",
+		"alignment1",
+		NULL
+	};
+
+	for (i = 0; i < 4; i++) {
+		gchar buffer[80];
+
+		snprintf (buffer, sizeof (buffer), "sort-combo-%d", i + 1);
+		config->sort[i].combo = e_table_proxy_gtk_combo_text_new ();
+		gtk_widget_show (GTK_WIDGET (config->sort[i].combo));
+		gtk_container_add (
+			GTK_CONTAINER (e_builder_get_widget (
+			builder, algs[i])), config->sort[i].combo);
+		configure_combo_box_add (
+			GTK_COMBO_BOX (config->sort[i].combo), "", "");
+
+		snprintf (buffer, sizeof (buffer), "frame-sort-%d", i + 1);
+		config->sort[i].frames =
+			e_builder_get_widget (builder, buffer);
+
+		snprintf (
+			buffer, sizeof (buffer),
+			"radiobutton-ascending-sort-%d", i + 1);
+		config->sort[i].radio_ascending = e_builder_get_widget (
+			builder, buffer);
+
+		snprintf (
+			buffer, sizeof (buffer),
+			"radiobutton-descending-sort-%d", i + 1);
+		config->sort[i].radio_descending = e_builder_get_widget (
+			builder, buffer);
+
+		config->sort[i].e_table_config = config;
+	}
+
+	for (l = config->column_names; l; l = l->next) {
+		gchar *label = l->data;
+
+		for (i = 0; i < 4; i++) {
+			configure_combo_box_add (
+				GTK_COMBO_BOX (config->sort[i].combo),
+				dgettext (config->domain, label), label);
+		}
+	}
+
+	/*
+	 * After we have runtime modified things, signal connect
+	 */
+	for (i = 0; i < 4; i++) {
+		config->sort[i].changed_id = g_signal_connect (
+			config->sort[i].combo,
+			"changed", G_CALLBACK (sort_combo_changed),
+			&config->sort[i]);
+
+		config->sort[i].toggled_id = g_signal_connect (
+			config->sort[i].radio_ascending,
+			"toggled", G_CALLBACK (sort_ascending_toggled),
+			&config->sort[i]);
+	}
+}
+
+static void
+group_combo_changed (GtkComboBox *combo_box,
+                     ETableConfigSortWidgets *group)
+{
+	ETableConfig *config = group->e_table_config;
+	ETableSortInfo *sort_info = config->temp_state->sort_info;
+	ETableConfigSortWidgets *base = &config->group[0];
+	gint idx = group - base;
+	gchar *s;
+
+	s = configure_combo_box_get_active (combo_box);
+
+	if (s != NULL) {
+		GtkToggleButton *toggle_button;
+		ETableSortColumn c;
+		gint col;
+
+		col = find_model_column_by_name (config->source_spec, s);
+		if (col == -1) {
+			g_warning ("grouping: this should not happen, %s", s);
+			g_free (s);
+			return;
+		}
+
+		toggle_button = GTK_TOGGLE_BUTTON (
+			config->group[idx].radio_ascending);
+		c.ascending = gtk_toggle_button_get_active (toggle_button);
+		c.column = col;
+		e_table_sort_info_grouping_set_nth (sort_info, idx, c);
+
+		update_sort_and_group_config_dialog (config, FALSE);
+	}  else {
+		e_table_sort_info_grouping_truncate (sort_info, idx);
+		update_sort_and_group_config_dialog (config, FALSE);
+	}
+
+	g_free (s);
+}
+
+static void
+group_ascending_toggled (GtkToggleButton *t,
+                         ETableConfigSortWidgets *group)
+{
+	ETableConfig *config = group->e_table_config;
+	ETableSortInfo *si = config->temp_state->sort_info;
+	ETableConfigSortWidgets *base = &config->group[0];
+	gint idx = group - base;
+	ETableSortColumn c;
+
+	c = e_table_sort_info_grouping_get_nth (si, idx);
+	c.ascending = gtk_toggle_button_get_active (t);
+	e_table_sort_info_grouping_set_nth (si, idx, c);
+}
+
+static void
+configure_group_dialog (ETableConfig *config,
+                        GtkBuilder *builder)
+{
+	GSList *l;
+	gint i;
+	const gchar *vboxes[] = {"vbox7", "vbox9", "vbox11", "vbox13", NULL};
+
+	for (i = 0; i < 4; i++) {
+		gchar buffer[80];
+
+		snprintf (buffer, sizeof (buffer), "group-combo-%d", i + 1);
+		config->group[i].combo = e_table_proxy_gtk_combo_text_new ();
+		gtk_widget_show (GTK_WIDGET (config->group[i].combo));
+		gtk_box_pack_start (
+			GTK_BOX (e_builder_get_widget (builder, vboxes[i])),
+			config->group[i].combo, FALSE, FALSE, 0);
+
+		configure_combo_box_add (
+			GTK_COMBO_BOX (config->group[i].combo), "", "");
+
+		snprintf (buffer, sizeof (buffer), "frame-group-%d", i + 1);
+		config->group[i].frames =
+			e_builder_get_widget (builder, buffer);
+
+		snprintf (
+			buffer, sizeof (buffer),
+			"radiobutton-ascending-group-%d", i + 1);
+		config->group[i].radio_ascending = e_builder_get_widget (
+			builder, buffer);
+
+		snprintf (
+			buffer, sizeof (buffer),
+			"radiobutton-descending-group-%d", i + 1);
+		config->group[i].radio_descending = e_builder_get_widget (
+			builder, buffer);
+
+		snprintf (
+			buffer, sizeof (buffer),
+			"checkbutton-group-%d", i + 1);
+		config->group[i].view_check = e_builder_get_widget (
+			builder, buffer);
+
+		config->group[i].e_table_config = config;
+	}
+
+	for (l = config->column_names; l; l = l->next) {
+		gchar *label = l->data;
+
+		for (i = 0; i < 4; i++) {
+			configure_combo_box_add (
+				GTK_COMBO_BOX (config->group[i].combo),
+				dgettext (config->domain, label), label);
+		}
+	}
+
+	/*
+	 * After we have runtime modified things, signal connect
+	 */
+	for (i = 0; i < 4; i++) {
+		config->group[i].changed_id = g_signal_connect (
+			config->group[i].combo,
+			"changed", G_CALLBACK (group_combo_changed),
+			&config->group[i]);
+
+		config->group[i].toggled_id = g_signal_connect (
+			config->group[i].radio_ascending,
+			"toggled", G_CALLBACK (group_ascending_toggled),
+			&config->group[i]);
+	}
+}
+
+static void
+add_column (gint model_row,
+            gpointer closure)
+{
+	GList **columns = closure;
+	*columns = g_list_prepend (*columns, GINT_TO_POINTER (model_row));
+}
+
+static void
+config_button_add (GtkWidget *widget,
+                   ETableConfig *config)
+{
+	GList *columns = NULL;
+	GList *column;
+	gint count;
+	gint i;
+
+	e_table_selected_row_foreach (config->available, add_column, &columns);
+	columns = g_list_reverse (columns);
+
+	count = g_list_length (columns);
+
+	config->temp_state->columns = g_renew (
+		int, config->temp_state->columns,
+		config->temp_state->col_count + count);
+	config->temp_state->expansions = g_renew (
+		gdouble, config->temp_state->expansions,
+		config->temp_state->col_count + count);
+	i = config->temp_state->col_count;
+	for (column = columns; column; column = column->next) {
+		config->temp_state->columns[i] =
+			get_source_model_col_index (
+			config, GPOINTER_TO_INT (column->data));
+		config->temp_state->expansions[i] =
+			config->source_spec->columns
+			[config->temp_state->columns[i]]->expansion;
+		i++;
+	}
+	config->temp_state->col_count += count;
+
+	g_list_free (columns);
+
+	setup_fields (config);
+}
+
+static void
+config_button_remove (GtkWidget *widget,
+                      ETableConfig *config)
+{
+	GList *columns = NULL;
+	GList *column;
+
+	e_table_selected_row_foreach (config->shown, add_column, &columns);
+
+	for (column = columns; column; column = column->next) {
+		gint row = GPOINTER_TO_INT (column->data);
+
+		memmove (
+			config->temp_state->columns + row,
+			config->temp_state->columns + row + 1,
+			sizeof (gint) * (config->temp_state->col_count - row - 1));
+		memmove (
+			config->temp_state->expansions + row,
+			config->temp_state->expansions + row + 1,
+			sizeof (gdouble) * (config->temp_state->col_count - row - 1));
+		config->temp_state->col_count--;
+	}
+	config->temp_state->columns = g_renew (
+		int, config->temp_state->columns,
+		config->temp_state->col_count);
+	config->temp_state->expansions = g_renew (
+		gdouble, config->temp_state->expansions,
+		config->temp_state->col_count);
+
+	g_list_free (columns);
+
+	setup_fields (config);
+}
+
+static void
+config_button_up (GtkWidget *widget,
+                  ETableConfig *config)
+{
+	GList *columns = NULL;
+	GList *column;
+	gint *new_shown;
+	gdouble *new_expansions;
+	gint next_col;
+	gdouble next_expansion;
+	gint i;
+
+	e_table_selected_row_foreach (config->shown, add_column, &columns);
+
+	/* if no columns left, just return */
+	if (columns == NULL)
+		return;
+
+	columns = g_list_reverse (columns);
+
+	new_shown = g_new (int, config->temp_state->col_count);
+	new_expansions = g_new (double, config->temp_state->col_count);
+
+	column = columns;
+
+	next_col = config->temp_state->columns[0];
+	next_expansion = config->temp_state->expansions[0];
+
+	for (i = 1; i < config->temp_state->col_count; i++) {
+		if (column && (GPOINTER_TO_INT (column->data) == i)) {
+			new_expansions[i - 1] = config->temp_state->expansions[i];
+			new_shown[i - 1] = config->temp_state->columns[i];
+			column = column->next;
+		} else {
+			new_shown[i - 1] = next_col;
+			next_col = config->temp_state->columns[i];
+
+			new_expansions[i - 1] = next_expansion;
+			next_expansion = config->temp_state->expansions[i];
+		}
+	}
+
+	new_shown[i - 1] = next_col;
+	new_expansions[i - 1] = next_expansion;
+
+	g_free (config->temp_state->columns);
+	g_free (config->temp_state->expansions);
+
+	config->temp_state->columns = new_shown;
+	config->temp_state->expansions = new_expansions;
+
+	g_list_free (columns);
+
+	setup_fields (config);
+}
+
+static void
+config_button_down (GtkWidget *widget,
+                    ETableConfig *config)
+{
+	GList *columns = NULL;
+	GList *column;
+	gint *new_shown;
+	gdouble *new_expansions;
+	gint next_col;
+	gdouble next_expansion;
+	gint i;
+
+	e_table_selected_row_foreach (config->shown, add_column, &columns);
+
+	/* if no columns left, just return */
+	if (columns == NULL)
+		return;
+
+	new_shown = g_new (gint, config->temp_state->col_count);
+	new_expansions = g_new (gdouble, config->temp_state->col_count);
+
+	column = columns;
+
+	next_col =
+		config->temp_state->columns[config->temp_state->col_count - 1];
+	next_expansion =
+		config->temp_state->expansions[config->temp_state->col_count - 1];
+
+	for (i = config->temp_state->col_count - 1; i > 0; i--) {
+		if (column && (GPOINTER_TO_INT (column->data) == i - 1)) {
+			new_expansions[i] = config->temp_state->expansions[i - 1];
+			new_shown[i] = config->temp_state->columns[i - 1];
+			column = column->next;
+		} else {
+			new_shown[i] = next_col;
+			next_col = config->temp_state->columns[i - 1];
+
+			new_expansions[i] = next_expansion;
+			next_expansion = config->temp_state->expansions[i - 1];
+		}
+	}
+
+	new_shown[0] = next_col;
+	new_expansions[0] = next_expansion;
+
+	g_free (config->temp_state->columns);
+	g_free (config->temp_state->expansions);
+
+	config->temp_state->columns = new_shown;
+	config->temp_state->expansions = new_expansions;
+
+	g_list_free (columns);
+
+	setup_fields (config);
+}
+
+static void
+configure_fields_dialog (ETableConfig *config,
+                         GtkBuilder *builder)
+{
+	GtkWidget *scrolled;
+	GtkWidget *etable;
+	ETableModel *store = create_store (config);
+
+	/* "custom-available" widget */
+	etable = e_table_proxy_etable_available_new (store);
+	gtk_widget_show (etable);
+	scrolled = e_builder_get_widget (builder, "available-scrolled");
+	gtk_container_add (GTK_CONTAINER (scrolled), etable);
+	config->available = E_TABLE (etable);
+	g_object_get (
+		config->available,
+		"model", &config->available_model,
+		NULL);
+	gtk_widget_show_all (etable);
+	gtk_label_set_mnemonic_widget (
+		GTK_LABEL (e_builder_get_widget (
+		builder, "label-available")), etable);
+
+	/* "custom-shown" widget */
+	etable = e_table_proxy_etable_shown_new (store);
+	gtk_widget_show (etable);
+	scrolled = e_builder_get_widget (builder, "shown-scrolled");
+	gtk_container_add (GTK_CONTAINER (scrolled), etable);
+	config->shown = E_TABLE (etable);
+	g_object_get (
+		config->shown,
+		"model", &config->shown_model,
+		NULL);
+	gtk_widget_show_all (etable);
+	gtk_label_set_mnemonic_widget (
+		GTK_LABEL (e_builder_get_widget (
+		builder, "label-displayed")), etable);
+
+	connect_button (
+		config, builder, "button-add",
+		G_CALLBACK (config_button_add));
+	connect_button (
+		config, builder, "button-remove",
+		G_CALLBACK (config_button_remove));
+	connect_button (
+		config, builder, "button-up",
+		G_CALLBACK (config_button_up));
+	connect_button (
+		config, builder, "button-down",
+		G_CALLBACK (config_button_down));
+
+	setup_fields (config);
+
+	g_object_unref (store);
+}
+
+static void
+setup_gui (ETableConfig *config)
+{
+	GtkBuilder *builder;
+	gboolean can_group;
+
+	can_group = e_table_sort_info_get_can_group (config->state->sort_info);
+	builder = gtk_builder_new ();
+	e_load_ui_builder_definition (builder, "e-table-config.ui");
+
+	config->dialog_toplevel = e_builder_get_widget (
+		builder, "e-table-config");
+
+	if (config->header)
+		gtk_window_set_title (
+			GTK_WINDOW (config->dialog_toplevel),
+			config->header);
+
+	config->dialog_show_fields = e_builder_get_widget (
+		builder, "dialog-show-fields");
+	config->dialog_group_by =  e_builder_get_widget (
+		builder, "dialog-group-by");
+	config->dialog_sort = e_builder_get_widget (
+		builder, "dialog-sort");
+
+	config->sort_label = e_builder_get_widget (
+		builder, "label-sort");
+	config->group_label = e_builder_get_widget (
+		builder, "label-group");
+	config->fields_label = e_builder_get_widget (
+		builder, "label-fields");
+
+	connect_button (
+		config, builder, "button-sort",
+		G_CALLBACK (config_button_sort));
+	connect_button (
+		config, builder, "button-group",
+		G_CALLBACK (config_button_group));
+	connect_button (
+		config, builder, "button-fields",
+		G_CALLBACK (config_button_fields));
+
+	if (!can_group) {
+		GtkWidget *w;
+
+		w = e_builder_get_widget (builder, "button-group");
+		if (w)
+			gtk_widget_hide (w);
+
+		w = e_builder_get_widget (builder, "label3");
+		if (w)
+			gtk_widget_hide (w);
+
+		w = config->group_label;
+		if (w)
+			gtk_widget_hide (w);
+	}
+
+	configure_sort_dialog (config, builder);
+	configure_group_dialog (config, builder);
+	configure_fields_dialog (config, builder);
+
+	g_object_weak_ref (
+		G_OBJECT (config->dialog_toplevel),
+		dialog_destroyed, config);
+
+	g_signal_connect (
+		config->dialog_toplevel, "response",
+		G_CALLBACK (dialog_response), config);
+
+	g_object_unref (builder);
+}
+
+static void
+e_table_config_init (ETableConfig *config)
+{
+	config->domain = NULL;
+}
+
+ETableConfig *
+e_table_config_construct (ETableConfig *config,
+                          const gchar *header,
+                          ETableSpecification *spec,
+                          ETableState *state,
+                          GtkWindow *parent_window)
+{
+	ETableColumnSpecification **column;
+
+	g_return_val_if_fail (config != NULL, NULL);
+	g_return_val_if_fail (header != NULL, NULL);
+	g_return_val_if_fail (spec != NULL, NULL);
+	g_return_val_if_fail (state != NULL, NULL);
+
+	config->source_spec = spec;
+	config->source_state = state;
+	config->header = g_strdup (header);
+
+	g_object_ref (config->source_spec);
+	g_object_ref (config->source_state);
+
+	config->state = e_table_state_duplicate (state);
+
+	config->domain = g_strdup (spec->domain);
+
+	for (column = config->source_spec->columns; *column; column++) {
+		gchar *label = (*column)->title;
+
+		if ((*column)->disabled)
+			continue;
+
+		config->column_names = g_slist_append (
+			config->column_names, label);
+	}
+
+	setup_gui (config);
+
+	gtk_window_set_transient_for (GTK_WINDOW (config->dialog_toplevel),
+				      parent_window);
+
+	config_sort_info_update   (config);
+	config_group_info_update  (config);
+	config_fields_info_update (config);
+
+	return E_TABLE_CONFIG (config);
+}
+
+/**
+ * e_table_config_new:
+ * @header: The title of the dialog for the ETableConfig.
+ * @spec: The specification for the columns to allow.
+ * @state: The current state of the configuration.
+ *
+ * Creates a new ETable config object.
+ *
+ * Returns: The config object.
+ */
+ETableConfig *
+e_table_config_new (const gchar *header,
+                    ETableSpecification *spec,
+                    ETableState *state,
+                    GtkWindow *parent_window)
+{
+	ETableConfig *config;
+	GtkDialog *dialog;
+	GtkWidget *widget;
+
+	config = g_object_new (E_TYPE_TABLE_CONFIG, NULL);
+
+	e_table_config_construct (
+		config, header, spec, state, parent_window);
+
+	dialog = GTK_DIALOG (config->dialog_toplevel);
+
+	gtk_widget_ensure_style (config->dialog_toplevel);
+
+	widget = gtk_dialog_get_content_area (dialog);
+	gtk_container_set_border_width (GTK_CONTAINER (widget), 0);
+
+	widget = gtk_dialog_get_action_area (dialog);
+	gtk_container_set_border_width (GTK_CONTAINER (widget), 12);
+
+	gtk_dialog_set_response_sensitive (
+		GTK_DIALOG (config->dialog_toplevel),
+		GTK_RESPONSE_APPLY, FALSE);
+	gtk_widget_show (config->dialog_toplevel);
+
+	return E_TABLE_CONFIG (config);
+}
+
+/**
+ * e_table_config_raise:
+ * @config: The ETableConfig object.
+ *
+ * Raises the dialog associated with this ETableConfig object.
+ */
+void
+e_table_config_raise (ETableConfig *config)
+{
+	GdkWindow *window;
+
+	window = gtk_widget_get_window (GTK_WIDGET (config->dialog_toplevel));
+	gdk_window_raise (window);
+}
+
diff --git a/e-util/e-table-config.h b/e-util/e-table-config.h
new file mode 100644
index 0000000..7fc74d9
--- /dev/null
+++ b/e-util/e-table-config.h
@@ -0,0 +1,134 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *		Miguel de Icaza <miguel ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef _E_TABLE_CONFIG_H_
+#define _E_TABLE_CONFIG_H_
+
+#include <gtk/gtk.h>
+
+#include <e-util/e-table-sort-info.h>
+#include <e-util/e-table-specification.h>
+#include <e-util/e-table-subset-variable.h>
+#include <e-util/e-table-without.h>
+#include <e-util/e-table.h>
+
+/* Standard GObject macros */
+#define E_TYPE_TABLE_CONFIG \
+	(e_table_config_get_type ())
+#define E_TABLE_CONFIG(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_TABLE_CONFIG, ETableConfig))
+#define E_TABLE_CONFIG_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_TABLE_CONFIG, ETableConfigClass))
+#define E_IS_TABLE_CONFIG(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_TABLE_CONFIG))
+#define E_IS_TABLE_CONFIG_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_TABLE_CONFIG))
+#define E_TABLE_CONFIG_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_TABLE_CONFIG, ETableConfigClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ETableConfigSortWidgets ETableConfigSortWidgets;
+
+typedef struct _ETableConfig ETableConfig;
+typedef struct _ETableConfigClass ETableConfigClass;
+
+struct _ETableConfigSortWidgets {
+	GtkWidget    *combo;
+	GtkWidget    *frames;
+	GtkWidget    *radio_ascending;
+	GtkWidget    *radio_descending;
+	GtkWidget    *view_check; /* Only for group dialog */
+	guint         changed_id, toggled_id;
+	gpointer e_table_config;
+};
+
+struct _ETableConfig {
+	GObject parent;
+
+	gchar *header;
+
+	/*
+	 * Our various dialog boxes
+	 */
+	GtkWidget *dialog_toplevel;
+	GtkWidget *dialog_show_fields;
+	GtkWidget *dialog_group_by;
+	GtkWidget *dialog_sort;
+
+	/*
+	 * The state we manipulate
+	 */
+	ETableSpecification *source_spec;
+	ETableState         *source_state, *state, *temp_state;
+
+	GtkWidget *sort_label;
+	GtkWidget *group_label;
+	GtkWidget *fields_label;
+
+	ETableConfigSortWidgets sort[4];
+	ETableConfigSortWidgets group[4];
+
+	ETable               *available;
+	ETableWithout        *available_model;
+	ETable               *shown;
+	ETableSubsetVariable *shown_model;
+	gchar *domain;
+
+	/*
+	 * List of valid column names
+	 */
+	GSList *column_names;
+};
+
+struct _ETableConfigClass {
+	GObjectClass parent_class;
+
+	/* Signals */
+	void		(*changed)		(ETableConfig *config);
+};
+
+GType		e_table_config_get_type		(void) G_GNUC_CONST;
+ETableConfig *	e_table_config_new		(const gchar *header,
+						 ETableSpecification *spec,
+						 ETableState *state,
+						 GtkWindow *parent_window);
+ETableConfig *e_table_config_construct		(ETableConfig *etco,
+						 const gchar *header,
+						 ETableSpecification *spec,
+						 ETableState *state,
+						 GtkWindow *parent_window);
+void		e_table_config_raise		(ETableConfig *config);
+
+G_END_DECLS
+
+#endif /* _E_TABLE_CONFIG_H */
diff --git a/widgets/table/e-table-config.ui b/e-util/e-table-config.ui
similarity index 100%
rename from widgets/table/e-table-config.ui
rename to e-util/e-table-config.ui
diff --git a/e-util/e-table-defines.h b/e-util/e-table-defines.h
new file mode 100644
index 0000000..0575f1c
--- /dev/null
+++ b/e-util/e-table-defines.h
@@ -0,0 +1,44 @@
+/*
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef __E_TABLE_DEFINES__
+#define __E_TABLE_DEFINES__ 1
+
+G_BEGIN_DECLS
+
+#define BUTTON_HEIGHT        10
+#define BUTTON_PADDING       2
+#define GROUP_INDENT         (BUTTON_HEIGHT + (BUTTON_PADDING * 2))
+
+/* Padding around the contents of a header button */
+#define HEADER_PADDING 3
+
+#define MIN_ARROW_SIZE 10
+
+G_END_DECLS
+
+#endif
diff --git a/e-util/e-table-extras.c b/e-util/e-table-extras.c
new file mode 100644
index 0000000..1820f35
--- /dev/null
+++ b/e-util/e-table-extras.c
@@ -0,0 +1,410 @@
+/*
+ * e-table-extras.c - Set of hash table sort of thingies.
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <gtk/gtk.h>
+
+#include "e-cell-checkbox.h"
+#include "e-cell-date.h"
+#include "e-cell-number.h"
+#include "e-cell-pixbuf.h"
+#include "e-cell-size.h"
+#include "e-cell-text.h"
+#include "e-cell-tree.h"
+#include "e-table-extras.h"
+#include "e-table-sorting-utils.h"
+
+#define E_TABLE_EXTRAS_GET_PRIVATE(obj) \
+	(G_TYPE_INSTANCE_GET_PRIVATE \
+	((obj), E_TYPE_TABLE_EXTRAS, ETableExtrasPrivate))
+
+struct _ETableExtrasPrivate {
+	GHashTable *cells;
+	GHashTable *compares;
+	GHashTable *icon_names;
+	GHashTable *searches;
+};
+
+/* workaround for avoiding API breakage */
+#define ete_get_type e_table_extras_get_type
+G_DEFINE_TYPE (ETableExtras, ete, G_TYPE_OBJECT)
+
+static void
+ete_finalize (GObject *object)
+{
+	ETableExtrasPrivate *priv;
+
+	priv = E_TABLE_EXTRAS_GET_PRIVATE (object);
+
+	if (priv->cells) {
+		g_hash_table_destroy (priv->cells);
+		priv->cells = NULL;
+	}
+
+	if (priv->compares) {
+		g_hash_table_destroy (priv->compares);
+		priv->compares = NULL;
+	}
+
+	if (priv->searches) {
+		g_hash_table_destroy (priv->searches);
+		priv->searches = NULL;
+	}
+
+	if (priv->icon_names) {
+		g_hash_table_destroy (priv->icon_names);
+		priv->icon_names = NULL;
+	}
+
+	G_OBJECT_CLASS (ete_parent_class)->finalize (object);
+}
+
+static void
+ete_class_init (ETableExtrasClass *class)
+{
+	GObjectClass *object_class;
+
+	g_type_class_add_private (class, sizeof (ETableExtrasPrivate));
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->finalize = ete_finalize;
+}
+
+static gint
+e_strint_compare (gconstpointer data1,
+                  gconstpointer data2)
+{
+	gint int1 = atoi (data1);
+	gint int2 = atoi (data2);
+
+	return e_int_compare (GINT_TO_POINTER (int1), GINT_TO_POINTER (int2));
+}
+
+/* UTF-8 strncasecmp - not optimized */
+
+static gint
+g_utf8_strncasecmp (const gchar *s1,
+                    const gchar *s2,
+                    guint n)
+{
+	gunichar c1, c2;
+
+	g_return_val_if_fail (s1 != NULL && g_utf8_validate (s1, -1, NULL), 0);
+	g_return_val_if_fail (s2 != NULL && g_utf8_validate (s2, -1, NULL), 0);
+
+	while (n && *s1 && *s2)
+		{
+
+			n -= 1;
+
+			c1 = g_unichar_tolower (g_utf8_get_char (s1));
+			c2 = g_unichar_tolower (g_utf8_get_char (s2));
+
+			/* Collation is locale-dependent, so this
+			 * totally fails to do the right thing. */
+			if (c1 != c2)
+				return c1 < c2 ? -1 : 1;
+
+			s1 = g_utf8_next_char (s1);
+			s2 = g_utf8_next_char (s2);
+		}
+
+	if (n == 0 || (*s1 == '\0' && *s2 == '\0'))
+		return 0;
+
+	return *s1 ? 1 : -1;
+}
+
+static gboolean
+e_string_search (gconstpointer haystack,
+                 const gchar *needle)
+{
+	gint length;
+	if (haystack == NULL)
+		return FALSE;
+
+	length = g_utf8_strlen (needle, -1);
+	if (g_utf8_strncasecmp (haystack, needle, length) == 0)
+		return TRUE;
+	else
+		return FALSE;
+}
+
+static gint
+e_table_str_case_compare (gconstpointer x,
+                          gconstpointer y,
+                          gpointer cmp_cache)
+{
+	const gchar *cx = NULL, *cy = NULL;
+
+	if (!cmp_cache)
+		return e_str_case_compare (x, y);
+
+	if (x == NULL || y == NULL) {
+		if (x == y)
+			return 0;
+		else
+			return x ? -1 : 1;
+	}
+
+	#define prepare_value(_z, _cz)						\
+		_cz = e_table_sorting_utils_lookup_cmp_cache (cmp_cache, _z);	\
+		if (!_cz) {							\
+			gchar *tmp = g_utf8_casefold (_z, -1);			\
+			_cz = g_utf8_collate_key (tmp, -1);			\
+			g_free (tmp);						\
+										\
+			e_table_sorting_utils_add_to_cmp_cache (		\
+				cmp_cache, _z, (gchar *) _cz);			\
+		}
+
+	prepare_value (x, cx);
+	prepare_value (y, cy);
+
+	#undef prepare_value
+
+	return strcmp (cx, cy);
+}
+
+static gint
+e_table_collate_compare (gconstpointer x,
+                         gconstpointer y,
+                         gpointer cmp_cache)
+{
+	const gchar *cx = NULL, *cy = NULL;
+
+	if (!cmp_cache)
+		return e_collate_compare (x, y);
+
+	if (x == NULL || y == NULL) {
+		if (x == y)
+			return 0;
+		else
+			return x ? -1 : 1;
+	}
+
+	#define prepare_value(_z, _cz)						\
+		_cz = e_table_sorting_utils_lookup_cmp_cache (cmp_cache, _z);	\
+		if (!_cz) {							\
+			_cz = g_utf8_collate_key (_z, -1);			\
+										\
+			e_table_sorting_utils_add_to_cmp_cache (		\
+				cmp_cache, _z, (gchar *) _cz);			\
+		}
+
+	prepare_value (x, cx);
+	prepare_value (y, cy);
+
+	#undef prepare_value
+
+	return strcmp (cx, cy);
+}
+
+static void
+safe_unref (gpointer object)
+{
+	if (object != NULL)
+		g_object_unref (object);
+}
+
+static void
+ete_init (ETableExtras *extras)
+{
+	ECell *cell, *sub_cell;
+
+	extras->priv = E_TABLE_EXTRAS_GET_PRIVATE (extras);
+
+	extras->priv->cells = g_hash_table_new_full (
+		g_str_hash, g_str_equal,
+		(GDestroyNotify) g_free,
+		(GDestroyNotify) safe_unref);
+
+	extras->priv->compares = g_hash_table_new_full (
+		g_str_hash, g_str_equal,
+		(GDestroyNotify) g_free,
+		(GDestroyNotify) NULL);
+
+	extras->priv->icon_names = g_hash_table_new_full (
+		g_str_hash, g_str_equal,
+		(GDestroyNotify) g_free,
+		(GDestroyNotify) g_free);
+
+	extras->priv->searches = g_hash_table_new_full (
+		g_str_hash, g_str_equal,
+		(GDestroyNotify) g_free,
+		(GDestroyNotify) NULL);
+
+	e_table_extras_add_compare (
+		extras, "string",
+		(GCompareDataFunc) e_str_compare);
+	e_table_extras_add_compare (
+		extras, "stringcase",
+		(GCompareDataFunc) e_table_str_case_compare);
+	e_table_extras_add_compare (
+		extras, "collate",
+		(GCompareDataFunc) e_table_collate_compare);
+	e_table_extras_add_compare (
+		extras, "integer",
+		(GCompareDataFunc) e_int_compare);
+	e_table_extras_add_compare (
+		extras, "string-integer",
+		(GCompareDataFunc) e_strint_compare);
+
+	e_table_extras_add_search (extras, "string", e_string_search);
+
+	cell = e_cell_checkbox_new ();
+	e_table_extras_add_cell (extras, "checkbox", cell);
+	g_object_unref (cell);
+
+	cell = e_cell_date_new (NULL, GTK_JUSTIFY_LEFT);
+	e_table_extras_add_cell (extras, "date", cell);
+	g_object_unref (cell);
+
+	cell = e_cell_number_new (NULL, GTK_JUSTIFY_RIGHT);
+	e_table_extras_add_cell (extras, "number", cell);
+	g_object_unref (cell);
+
+	cell = e_cell_pixbuf_new ();
+	e_table_extras_add_cell (extras, "pixbuf", cell);
+	g_object_unref (cell);
+
+	cell = e_cell_size_new (NULL, GTK_JUSTIFY_RIGHT);
+	e_table_extras_add_cell (extras, "size", cell);
+	g_object_unref (cell);
+
+	cell = e_cell_text_new (NULL, GTK_JUSTIFY_LEFT);
+	e_table_extras_add_cell (extras, "string", cell);
+	g_object_unref (cell);
+
+	sub_cell = e_cell_text_new (NULL, GTK_JUSTIFY_LEFT);
+	cell = e_cell_tree_new (TRUE, sub_cell);
+	e_table_extras_add_cell (extras, "tree-string", cell);
+	g_object_unref (sub_cell);
+	g_object_unref (cell);
+}
+
+ETableExtras *
+e_table_extras_new (void)
+{
+	return g_object_new (E_TYPE_TABLE_EXTRAS, NULL);
+}
+
+void
+e_table_extras_add_cell (ETableExtras *extras,
+                         const gchar *id,
+                         ECell *cell)
+{
+	g_return_if_fail (E_IS_TABLE_EXTRAS (extras));
+	g_return_if_fail (id != NULL);
+
+	if (cell != NULL)
+		g_object_ref_sink (cell);
+
+	g_hash_table_insert (extras->priv->cells, g_strdup (id), cell);
+}
+
+ECell *
+e_table_extras_get_cell (ETableExtras *extras,
+                         const gchar *id)
+{
+	g_return_val_if_fail (E_IS_TABLE_EXTRAS (extras), NULL);
+	g_return_val_if_fail (id != NULL, NULL);
+
+	return g_hash_table_lookup (extras->priv->cells, id);
+}
+
+void
+e_table_extras_add_compare (ETableExtras *extras,
+                            const gchar *id,
+                            GCompareDataFunc compare)
+{
+	g_return_if_fail (E_IS_TABLE_EXTRAS (extras));
+	g_return_if_fail (id != NULL);
+
+	g_hash_table_insert (
+		extras->priv->compares,
+		g_strdup (id), (gpointer) compare);
+}
+
+GCompareDataFunc
+e_table_extras_get_compare (ETableExtras *extras,
+                            const gchar *id)
+{
+	g_return_val_if_fail (E_IS_TABLE_EXTRAS (extras), NULL);
+	g_return_val_if_fail (id != NULL, NULL);
+
+	return g_hash_table_lookup (extras->priv->compares, id);
+}
+
+void
+e_table_extras_add_search (ETableExtras *extras,
+                           const gchar *id,
+                           ETableSearchFunc search)
+{
+	g_return_if_fail (E_IS_TABLE_EXTRAS (extras));
+	g_return_if_fail (id != NULL);
+
+	g_hash_table_insert (
+		extras->priv->searches,
+		g_strdup (id), (gpointer) search);
+}
+
+ETableSearchFunc
+e_table_extras_get_search (ETableExtras *extras,
+                           const gchar *id)
+{
+	g_return_val_if_fail (E_IS_TABLE_EXTRAS (extras), NULL);
+	g_return_val_if_fail (id != NULL, NULL);
+
+	return g_hash_table_lookup (extras->priv->searches, id);
+}
+
+void
+e_table_extras_add_icon_name (ETableExtras *extras,
+                              const gchar *id,
+                              const gchar *icon_name)
+{
+	g_return_if_fail (E_IS_TABLE_EXTRAS (extras));
+	g_return_if_fail (id != NULL);
+
+	g_hash_table_insert (
+		extras->priv->icon_names,
+		g_strdup (id), g_strdup (icon_name));
+}
+
+const gchar *
+e_table_extras_get_icon_name (ETableExtras *extras,
+                              const gchar *id)
+{
+	g_return_val_if_fail (E_IS_TABLE_EXTRAS (extras), NULL);
+	g_return_val_if_fail (id != NULL, NULL);
+
+	return g_hash_table_lookup (extras->priv->icon_names, id);
+}
diff --git a/e-util/e-table-extras.h b/e-util/e-table-extras.h
new file mode 100644
index 0000000..93acc4c
--- /dev/null
+++ b/e-util/e-table-extras.h
@@ -0,0 +1,94 @@
+/*
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_TABLE_EXTRAS_H
+#define E_TABLE_EXTRAS_H
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <e-util/e-cell.h>
+
+/* Standard GObject macros */
+#define E_TYPE_TABLE_EXTRAS \
+	(e_table_extras_get_type ())
+#define E_TABLE_EXTRAS(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_TABLE_EXTRAS, ETableExtras))
+#define E_TABLE_EXTRAS_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_TABLE_EXTRAS, ETableExtrasClass))
+#define E_IS_TABLE_EXTRAS(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_TABLE_EXTRAS))
+#define E_IS_TABLE_EXTRAS_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_TABLE_EXTRAS))
+#define E_TABLE_EXTRAS_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_TABLE_EXTRAS, ETableExtrasClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ETableExtras ETableExtras;
+typedef struct _ETableExtrasClass ETableExtrasClass;
+typedef struct _ETableExtrasPrivate ETableExtrasPrivate;
+
+struct _ETableExtras {
+	GObject parent;
+	ETableExtrasPrivate *priv;
+};
+
+struct _ETableExtrasClass {
+	GObjectClass parent_class;
+};
+
+GType		e_table_extras_get_type		(void) G_GNUC_CONST;
+ETableExtras *	e_table_extras_new		(void);
+void		e_table_extras_add_cell		(ETableExtras *extras,
+						 const gchar *id,
+						 ECell *cell);
+ECell *		e_table_extras_get_cell		(ETableExtras *extras,
+						 const gchar *id);
+void		e_table_extras_add_compare	(ETableExtras *extras,
+						 const gchar *id,
+						 GCompareDataFunc compare);
+GCompareDataFunc e_table_extras_get_compare	(ETableExtras *extras,
+						 const gchar *id);
+void		e_table_extras_add_search	(ETableExtras *extras,
+						 const gchar *id,
+						 ETableSearchFunc search);
+ETableSearchFunc
+		e_table_extras_get_search	(ETableExtras *extras,
+						 const gchar *id);
+void		e_table_extras_add_icon_name	(ETableExtras *extras,
+						 const gchar *id,
+						 const gchar *icon_name);
+const gchar *	e_table_extras_get_icon_name	(ETableExtras *extras,
+						 const gchar *id);
+
+G_END_DECLS
+
+#endif /* E_TABLE_EXTRAS_H */
diff --git a/e-util/e-table-field-chooser-dialog.c b/e-util/e-table-field-chooser-dialog.c
new file mode 100644
index 0000000..4c64308
--- /dev/null
+++ b/e-util/e-table-field-chooser-dialog.c
@@ -0,0 +1,235 @@
+/*
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <gtk/gtk.h>
+
+#include <glib/gi18n.h>
+
+#include "e-table-field-chooser-dialog.h"
+
+enum {
+	PROP_0,
+	PROP_DND_CODE,
+	PROP_FULL_HEADER,
+	PROP_HEADER
+};
+
+G_DEFINE_TYPE (
+	ETableFieldChooserDialog,
+	e_table_field_chooser_dialog,
+	GTK_TYPE_DIALOG)
+
+static void
+e_table_field_chooser_dialog_set_property (GObject *object,
+                                           guint property_id,
+                                           const GValue *value,
+                                           GParamSpec *pspec)
+{
+	ETableFieldChooserDialog *etfcd = E_TABLE_FIELD_CHOOSER_DIALOG (object);
+	switch (property_id) {
+	case PROP_DND_CODE:
+		g_free (etfcd->dnd_code);
+		etfcd->dnd_code = g_strdup (g_value_get_string (value));
+		if (etfcd->etfc)
+			g_object_set (
+				etfcd->etfc,
+				"dnd_code", etfcd->dnd_code,
+				NULL);
+		break;
+	case PROP_FULL_HEADER:
+		if (etfcd->full_header)
+			g_object_unref (etfcd->full_header);
+		if (g_value_get_object (value))
+			etfcd->full_header = E_TABLE_HEADER (g_value_get_object (value));
+		else
+			etfcd->full_header = NULL;
+		if (etfcd->full_header)
+			g_object_ref (etfcd->full_header);
+		if (etfcd->etfc)
+			g_object_set (
+				etfcd->etfc,
+				"full_header", etfcd->full_header,
+				NULL);
+		break;
+	case PROP_HEADER:
+		if (etfcd->header)
+			g_object_unref (etfcd->header);
+		if (g_value_get_object (value))
+			etfcd->header = E_TABLE_HEADER (g_value_get_object (value));
+		else
+			etfcd->header = NULL;
+		if (etfcd->header)
+			g_object_ref (etfcd->header);
+		if (etfcd->etfc)
+			g_object_set (
+				etfcd->etfc,
+				"header", etfcd->header,
+				NULL);
+		break;
+	default:
+		break;
+	}
+}
+
+static void
+e_table_field_chooser_dialog_get_property (GObject *object,
+                                           guint property_id,
+                                           GValue *value,
+                                           GParamSpec *pspec)
+{
+	ETableFieldChooserDialog *etfcd = E_TABLE_FIELD_CHOOSER_DIALOG (object);
+	switch (property_id) {
+	case PROP_DND_CODE:
+		g_value_set_string (value, etfcd->dnd_code);
+		break;
+	case PROP_FULL_HEADER:
+		g_value_set_object (value, etfcd->full_header);
+		break;
+	case PROP_HEADER:
+		g_value_set_object (value, etfcd->header);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+		break;
+	}
+}
+
+static void
+e_table_field_chooser_dialog_dispose (GObject *object)
+{
+	ETableFieldChooserDialog *etfcd = E_TABLE_FIELD_CHOOSER_DIALOG (object);
+
+	if (etfcd->dnd_code)
+		g_free (etfcd->dnd_code);
+	etfcd->dnd_code = NULL;
+
+	if (etfcd->full_header)
+		g_object_unref (etfcd->full_header);
+	etfcd->full_header = NULL;
+
+	if (etfcd->header)
+		g_object_unref (etfcd->header);
+	etfcd->header = NULL;
+
+	G_OBJECT_CLASS (e_table_field_chooser_dialog_parent_class)->dispose (object);
+}
+
+static void
+e_table_field_chooser_dialog_response (GtkDialog *dialog,
+                                       gint id)
+{
+	if (id == GTK_RESPONSE_OK)
+		gtk_widget_destroy (GTK_WIDGET (dialog));
+}
+
+static void
+e_table_field_chooser_dialog_class_init (ETableFieldChooserDialogClass *class)
+{
+	GObjectClass *object_class;
+	GtkDialogClass *dialog_class;
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->set_property = e_table_field_chooser_dialog_set_property;
+	object_class->get_property = e_table_field_chooser_dialog_get_property;
+	object_class->dispose = e_table_field_chooser_dialog_dispose;
+
+	dialog_class = GTK_DIALOG_CLASS (class);
+	dialog_class->response = e_table_field_chooser_dialog_response;
+
+	g_object_class_install_property (
+		object_class,
+		PROP_DND_CODE,
+		g_param_spec_string (
+			"dnd_code",
+			"DnD code",
+			NULL,
+			NULL,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_FULL_HEADER,
+		g_param_spec_object (
+			"full_header",
+			"Full Header",
+			NULL,
+			E_TYPE_TABLE_HEADER,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_HEADER,
+		g_param_spec_object (
+			"header",
+			"Header",
+			NULL,
+			E_TYPE_TABLE_HEADER,
+			G_PARAM_READWRITE));
+}
+
+static void
+e_table_field_chooser_dialog_init (ETableFieldChooserDialog *e_table_field_chooser_dialog)
+{
+	GtkDialog *dialog;
+	GtkWidget *content_area;
+	GtkWidget *widget;
+
+	dialog = GTK_DIALOG (e_table_field_chooser_dialog);
+
+	e_table_field_chooser_dialog->etfc = NULL;
+	e_table_field_chooser_dialog->dnd_code = g_strdup ("");
+	e_table_field_chooser_dialog->full_header = NULL;
+	e_table_field_chooser_dialog->header = NULL;
+
+	gtk_dialog_add_button (dialog, GTK_STOCK_CLOSE, GTK_RESPONSE_OK);
+
+	gtk_window_set_resizable (GTK_WINDOW (dialog), TRUE);
+
+	widget = e_table_field_chooser_new ();
+	e_table_field_chooser_dialog->etfc = E_TABLE_FIELD_CHOOSER (widget);
+
+	g_object_set (
+		widget,
+		"dnd_code", e_table_field_chooser_dialog->dnd_code,
+		"full_header", e_table_field_chooser_dialog->full_header,
+		"header", e_table_field_chooser_dialog->header,
+		NULL);
+
+	content_area = gtk_dialog_get_content_area (dialog);
+	gtk_box_pack_start (GTK_BOX (content_area), widget, TRUE, TRUE, 0);
+
+	gtk_widget_show (GTK_WIDGET (widget));
+
+	gtk_window_set_title (GTK_WINDOW (dialog), _("Add a Column"));
+}
+
+GtkWidget *
+e_table_field_chooser_dialog_new (void)
+{
+	return g_object_new (E_TYPE_TABLE_FIELD_CHOOSER_DIALOG, NULL);
+}
+
diff --git a/e-util/e-table-field-chooser-dialog.h b/e-util/e-table-field-chooser-dialog.h
new file mode 100644
index 0000000..15be375
--- /dev/null
+++ b/e-util/e-table-field-chooser-dialog.h
@@ -0,0 +1,77 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef __E_TABLE_FIELD_CHOOSER_DIALOG_H__
+#define __E_TABLE_FIELD_CHOOSER_DIALOG_H__
+
+#include <gtk/gtk.h>
+
+#include <e-util/e-table-field-chooser.h>
+#include <e-util/e-table-header.h>
+
+#define E_TYPE_TABLE_FIELD_CHOOSER_DIALOG \
+	(e_table_field_chooser_dialog_get_type ())
+#define E_TABLE_FIELD_CHOOSER_DIALOG(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_TABLE_FIELD_CHOOSER_DIALOG, ETableFieldChooserDialog))
+#define E_TABLE_FIELD_CHOOSER_DIALOG_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_TABLE_FIELD_CHOOSER_DIALOG, ETableFieldChooserDialogClass))
+#define E_IS_TABLE_FIELD_CHOOSER_DIALOG(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_TABLE_FIELD_CHOOSER_DIALOG))
+#define E_IS_TABLE_FIELD_CHOOSER_DIALOG_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_TABLE_FIELD_CHOOSER_DIALOG))
+#define E_TABLE_FIELD_CHOOSER_DIALOG_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_TABLE_FIELD_CHOOSER_DIALOG, ETableFieldChooserDialogClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ETableFieldChooserDialog ETableFieldChooserDialog;
+typedef struct _ETableFieldChooserDialogClass ETableFieldChooserDialogClass;
+
+struct _ETableFieldChooserDialog {
+	GtkDialog parent;
+
+	/* item specific fields */
+	ETableFieldChooser *etfc;
+	gchar              *dnd_code;
+	ETableHeader       *full_header;
+	ETableHeader       *header;
+};
+
+struct _ETableFieldChooserDialogClass {
+	GtkDialogClass parent_class;
+};
+
+GType		e_table_field_chooser_dialog_get_type	(void) G_GNUC_CONST;
+GtkWidget *	e_table_field_chooser_dialog_new	(void);
+
+G_END_DECLS
+
+#endif /* __E_TABLE_FIELD_CHOOSER_DIALOG_H__ */
diff --git a/e-util/e-table-field-chooser-item.c b/e-util/e-table-field-chooser-item.c
new file mode 100644
index 0000000..f72e059
--- /dev/null
+++ b/e-util/e-table-field-chooser-item.c
@@ -0,0 +1,749 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+#include <libgnomecanvas/libgnomecanvas.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#include "e-canvas.h"
+#include "e-table-col-dnd.h"
+#include "e-table-defines.h"
+#include "e-table-field-chooser-item.h"
+#include "e-table-header-utils.h"
+#include "e-table-header.h"
+#include "e-xml-utils.h"
+
+#define d(x)
+
+#if 0
+enum {
+	BUTTON_PRESSED,
+	LAST_SIGNAL
+};
+
+static guint etfci_signals[LAST_SIGNAL] = { 0, };
+#endif
+
+/* workaround for avoiding API breakage */
+#define etfci_get_type e_table_field_chooser_item_get_type
+G_DEFINE_TYPE (ETableFieldChooserItem, etfci, GNOME_TYPE_CANVAS_ITEM)
+
+static void etfci_drop_table_header (ETableFieldChooserItem *etfci);
+static void etfci_drop_full_header (ETableFieldChooserItem *etfci);
+
+enum {
+	PROP_0,
+	PROP_FULL_HEADER,
+	PROP_HEADER,
+	PROP_DND_CODE,
+	PROP_WIDTH,
+	PROP_HEIGHT
+};
+
+static void
+etfci_dispose (GObject *object)
+{
+	ETableFieldChooserItem *etfci = E_TABLE_FIELD_CHOOSER_ITEM (object);
+
+	etfci_drop_table_header (etfci);
+	etfci_drop_full_header (etfci);
+
+	if (etfci->combined_header)
+		g_object_unref (etfci->combined_header);
+	etfci->combined_header = NULL;
+
+	if (etfci->font_desc)
+		pango_font_description_free (etfci->font_desc);
+	etfci->font_desc = NULL;
+
+	/* Chain up to parent's dispose() method. */
+	G_OBJECT_CLASS (etfci_parent_class)->dispose (object);
+}
+
+static gint
+etfci_find_button (ETableFieldChooserItem *etfci,
+                   gdouble loc)
+{
+	gint i;
+	gint count;
+	gdouble height = 0;
+
+	count = e_table_header_count (etfci->combined_header);
+	for (i = 0; i < count; i++) {
+		ETableCol *ecol;
+
+		ecol = e_table_header_get_column (etfci->combined_header, i);
+		if (ecol->disabled)
+			continue;
+		height += e_table_header_compute_height (
+			ecol, GTK_WIDGET (GNOME_CANVAS_ITEM (etfci)->canvas));
+		if (height > loc)
+			return i;
+	}
+	return MAX (0, count - 1);
+}
+
+static void
+etfci_rebuild_combined (ETableFieldChooserItem *etfci)
+{
+	gint count;
+	GHashTable *hash;
+	gint i;
+
+	if (etfci->combined_header != NULL)
+		g_object_unref (etfci->combined_header);
+
+	etfci->combined_header = e_table_header_new ();
+
+	hash = g_hash_table_new (NULL, NULL);
+
+	count = e_table_header_count (etfci->header);
+	for (i = 0; i < count; i++) {
+		ETableCol *ecol = e_table_header_get_column (etfci->header, i);
+		if (ecol->disabled)
+			continue;
+		g_hash_table_insert (
+			hash, GINT_TO_POINTER (ecol->col_idx),
+			GINT_TO_POINTER (1));
+	}
+
+	count = e_table_header_count (etfci->full_header);
+	for (i = 0; i < count; i++) {
+		ETableCol *ecol = e_table_header_get_column (etfci->full_header, i);
+		if (ecol->disabled)
+			continue;
+		if (!(GPOINTER_TO_INT (g_hash_table_lookup (
+				hash, GINT_TO_POINTER (ecol->col_idx)))))
+			e_table_header_add_column (etfci->combined_header, ecol, -1);
+	}
+
+	g_hash_table_destroy (hash);
+}
+
+static void
+etfci_reflow (GnomeCanvasItem *item,
+              gint flags)
+{
+	ETableFieldChooserItem *etfci = E_TABLE_FIELD_CHOOSER_ITEM (item);
+	gdouble old_height;
+	gint i;
+	gint count;
+	gdouble height = 0;
+
+	etfci_rebuild_combined (etfci);
+
+	old_height = etfci->height;
+
+	count = e_table_header_count (etfci->combined_header);
+	for (i = 0; i < count; i++) {
+		ETableCol *ecol;
+
+		ecol = e_table_header_get_column (etfci->combined_header, i);
+		if (ecol->disabled)
+			continue;
+		height += e_table_header_compute_height (
+			ecol, GTK_WIDGET (GNOME_CANVAS_ITEM (etfci)->canvas));
+	}
+
+	etfci->height = height;
+
+	if (old_height != etfci->height)
+		e_canvas_item_request_parent_reflow (item);
+
+	gnome_canvas_item_request_update (item);
+}
+
+static void
+etfci_update (GnomeCanvasItem *item,
+              const cairo_matrix_t *i2c,
+              gint flags)
+{
+	ETableFieldChooserItem *etfci = E_TABLE_FIELD_CHOOSER_ITEM (item);
+	gdouble x1, y1, x2, y2;
+
+	if (GNOME_CANVAS_ITEM_CLASS (etfci_parent_class)->update)
+		GNOME_CANVAS_ITEM_CLASS (etfci_parent_class)->update (
+			item, i2c, flags);
+
+	x1 = y1 = 0;
+	x2 = etfci->width;
+	y2 = etfci->height;
+
+	gnome_canvas_matrix_transform_rect (i2c, &x1, &y1, &x2, &y2);
+
+	if (item->x1 != x1 ||
+	    item->y1 != y1 ||
+	    item->x2 != x2 ||
+	    item->y2 != y2)
+		{
+			gnome_canvas_request_redraw (
+				item->canvas, item->x1,
+				item->y1, item->x2, item->y2);
+			item->x1 = x1;
+			item->y1 = y1;
+			item->x2 = x2;
+			item->y2 = y2;
+/* FIXME: Group Child bounds !? */
+#if 0
+			gnome_canvas_group_child_bounds (
+				GNOME_CANVAS_GROUP (item->parent), item);
+#endif
+		}
+	gnome_canvas_request_redraw (
+		item->canvas, item->x1, item->y1, item->x2, item->y2);
+}
+
+static void
+etfci_font_load (ETableFieldChooserItem *etfci)
+{
+	GtkWidget *widget;
+	GtkStyle *style;
+
+	if (etfci->font_desc)
+		pango_font_description_free (etfci->font_desc);
+
+	widget = GTK_WIDGET (GNOME_CANVAS_ITEM (etfci)->canvas);
+	style = gtk_widget_get_style (widget);
+	etfci->font_desc = pango_font_description_copy (style->font_desc);
+}
+
+static void
+etfci_drop_full_header (ETableFieldChooserItem *etfci)
+{
+	GObject *header;
+
+	if (!etfci->full_header)
+		return;
+
+	header = G_OBJECT (etfci->full_header);
+	if (etfci->full_header_structure_change_id)
+		g_signal_handler_disconnect (
+			header, etfci->full_header_structure_change_id);
+	if (etfci->full_header_dimension_change_id)
+		g_signal_handler_disconnect (
+			header, etfci->full_header_dimension_change_id);
+	etfci->full_header_structure_change_id = 0;
+	etfci->full_header_dimension_change_id = 0;
+
+	if (header)
+		g_object_unref (header);
+	etfci->full_header = NULL;
+	etfci->height = 0;
+	e_canvas_item_request_reflow (GNOME_CANVAS_ITEM (etfci));
+}
+
+static void
+full_header_structure_changed (ETableHeader *header,
+                               ETableFieldChooserItem *etfci)
+{
+	e_canvas_item_request_reflow (GNOME_CANVAS_ITEM (etfci));
+}
+
+static void
+full_header_dimension_changed (ETableHeader *header,
+                               gint col,
+                               ETableFieldChooserItem *etfci)
+{
+	e_canvas_item_request_reflow (GNOME_CANVAS_ITEM (etfci));
+}
+
+static void
+etfci_add_full_header (ETableFieldChooserItem *etfci,
+                       ETableHeader *header)
+{
+	etfci->full_header = header;
+	g_object_ref (etfci->full_header);
+
+	etfci->full_header_structure_change_id = g_signal_connect (
+		header, "structure_change",
+		G_CALLBACK (full_header_structure_changed), etfci);
+	etfci->full_header_dimension_change_id = g_signal_connect (
+		header, "dimension_change",
+		G_CALLBACK (full_header_dimension_changed), etfci);
+	e_canvas_item_request_reflow (GNOME_CANVAS_ITEM (etfci));
+}
+
+static void
+etfci_drop_table_header (ETableFieldChooserItem *etfci)
+{
+	GObject *header;
+
+	if (!etfci->header)
+		return;
+
+	header = G_OBJECT (etfci->header);
+	if (etfci->table_header_structure_change_id)
+		g_signal_handler_disconnect (
+			header, etfci->table_header_structure_change_id);
+	if (etfci->table_header_dimension_change_id)
+		g_signal_handler_disconnect (
+			header, etfci->table_header_dimension_change_id);
+	etfci->table_header_structure_change_id = 0;
+	etfci->table_header_dimension_change_id = 0;
+
+	if (header)
+		g_object_unref (header);
+	etfci->header = NULL;
+	etfci->height = 0;
+	e_canvas_item_request_reflow (GNOME_CANVAS_ITEM (etfci));
+}
+
+static void
+table_header_structure_changed (ETableHeader *header,
+                                ETableFieldChooserItem *etfci)
+{
+	e_canvas_item_request_reflow (GNOME_CANVAS_ITEM (etfci));
+}
+
+static void
+table_header_dimension_changed (ETableHeader *header,
+                                gint col,
+                                ETableFieldChooserItem *etfci)
+{
+	e_canvas_item_request_reflow (GNOME_CANVAS_ITEM (etfci));
+}
+
+static void
+etfci_add_table_header (ETableFieldChooserItem *etfci,
+                        ETableHeader *header)
+{
+	etfci->header = header;
+	g_object_ref (etfci->header);
+
+	etfci->table_header_structure_change_id = g_signal_connect (
+		header, "structure_change",
+		G_CALLBACK (table_header_structure_changed), etfci);
+	etfci->table_header_dimension_change_id = g_signal_connect (
+		header, "dimension_change",
+		G_CALLBACK (table_header_dimension_changed), etfci);
+	e_canvas_item_request_reflow (GNOME_CANVAS_ITEM (etfci));
+}
+
+static void
+etfci_set_property (GObject *object,
+                    guint property_id,
+                    const GValue *value,
+                    GParamSpec *pspec)
+{
+	GnomeCanvasItem *item;
+	ETableFieldChooserItem *etfci;
+
+	item = GNOME_CANVAS_ITEM (object);
+	etfci = E_TABLE_FIELD_CHOOSER_ITEM (object);
+
+	switch (property_id) {
+	case PROP_FULL_HEADER:
+		etfci_drop_full_header (etfci);
+		if (g_value_get_object (value))
+			etfci_add_full_header (
+				etfci, E_TABLE_HEADER (
+				g_value_get_object (value)));
+		break;
+
+	case PROP_HEADER:
+		etfci_drop_table_header (etfci);
+		if (g_value_get_object (value))
+			etfci_add_table_header (
+				etfci, E_TABLE_HEADER (
+				g_value_get_object (value)));
+		break;
+
+	case PROP_DND_CODE:
+		g_free (etfci->dnd_code);
+		etfci->dnd_code = g_strdup (g_value_get_string (value));
+		break;
+
+	case PROP_WIDTH:
+		etfci->width = g_value_get_double (value);
+		gnome_canvas_item_request_update (item);
+		break;
+	}
+}
+
+static void
+etfci_get_property (GObject *object,
+                    guint property_id,
+                    GValue *value,
+                    GParamSpec *pspec)
+{
+	ETableFieldChooserItem *etfci;
+
+	etfci = E_TABLE_FIELD_CHOOSER_ITEM (object);
+
+	switch (property_id) {
+
+	case PROP_DND_CODE:
+		g_value_set_string (value, etfci->dnd_code);
+		break;
+	case PROP_WIDTH:
+		g_value_set_double (value, etfci->width);
+		break;
+	case PROP_HEIGHT:
+		g_value_set_double (value, etfci->height);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+		break;
+	}
+}
+
+static void
+etfci_drag_data_get (GtkWidget *widget,
+                     GdkDragContext *context,
+                     GtkSelectionData *selection_data,
+                     guint info,
+                     guint time,
+                     ETableFieldChooserItem *etfci)
+{
+	if (etfci->drag_col != -1) {
+		gchar *string = g_strdup_printf ("%d", etfci->drag_col);
+		gtk_selection_data_set (
+			selection_data,
+			GDK_SELECTION_TYPE_STRING,
+			sizeof (string[0]),
+			(guchar *) string,
+			strlen (string));
+		g_free (string);
+	}
+}
+
+static void
+etfci_drag_end (GtkWidget *canvas,
+                GdkDragContext *context,
+                ETableFieldChooserItem *etfci)
+{
+	etfci->drag_col = -1;
+}
+
+static void
+etfci_realize (GnomeCanvasItem *item)
+{
+	ETableFieldChooserItem *etfci = E_TABLE_FIELD_CHOOSER_ITEM (item);
+
+	if (GNOME_CANVAS_ITEM_CLASS (etfci_parent_class)-> realize)
+		(*GNOME_CANVAS_ITEM_CLASS (etfci_parent_class)->realize)(item);
+
+	if (!etfci->font_desc)
+		etfci_font_load (etfci);
+
+	etfci->drag_end_id = g_signal_connect (
+		item->canvas, "drag_end",
+		G_CALLBACK (etfci_drag_end), etfci);
+	etfci->drag_data_get_id = g_signal_connect (
+		item->canvas, "drag_data_get",
+		G_CALLBACK (etfci_drag_data_get), etfci);
+	e_canvas_item_request_reflow (GNOME_CANVAS_ITEM (etfci));
+}
+
+static void
+etfci_unrealize (GnomeCanvasItem *item)
+{
+	ETableFieldChooserItem *etfci = E_TABLE_FIELD_CHOOSER_ITEM (item);
+
+	if (etfci->font_desc)
+		pango_font_description_free (etfci->font_desc);
+	etfci->font_desc = NULL;
+
+	g_signal_handler_disconnect (item->canvas, etfci->drag_end_id);
+	etfci->drag_end_id = 0;
+	g_signal_handler_disconnect (item->canvas, etfci->drag_data_get_id);
+	etfci->drag_data_get_id = 0;
+
+	if (GNOME_CANVAS_ITEM_CLASS (etfci_parent_class)->unrealize)
+		(*GNOME_CANVAS_ITEM_CLASS (etfci_parent_class)->unrealize)(item);
+}
+
+static void
+etfci_draw (GnomeCanvasItem *item,
+            cairo_t *cr,
+            gint x,
+            gint y,
+            gint width,
+            gint height)
+{
+	ETableFieldChooserItem *etfci = E_TABLE_FIELD_CHOOSER_ITEM (item);
+	GnomeCanvas *canvas = item->canvas;
+	gint rows;
+	gint y1, y2;
+	gint row;
+
+	if (etfci->combined_header == NULL)
+		return;
+
+	rows = e_table_header_count (etfci->combined_header);
+
+	y1 = y2 = 0;
+	for (row = 0; row < rows; row++, y1 = y2) {
+		ETableCol *ecol;
+
+		ecol = e_table_header_get_column (etfci->combined_header, row);
+
+		if (ecol->disabled)
+			continue;
+
+		y2 += e_table_header_compute_height (ecol, GTK_WIDGET (canvas));
+
+		if (y1 > (y + height))
+			break;
+
+		if (y2 < y)
+			continue;
+
+		cairo_save (cr);
+
+		e_table_header_draw_button (
+			cr, ecol,
+			GTK_WIDGET (canvas),
+			-x, y1 - y,
+			width, height,
+			etfci->width, y2 - y1,
+			E_TABLE_COL_ARROW_NONE);
+
+		cairo_restore (cr);
+	}
+}
+
+static GnomeCanvasItem *
+etfci_point (GnomeCanvasItem *item,
+             gdouble x,
+             gdouble y,
+             gint cx,
+             gint cy)
+{
+	return item;
+}
+
+static gboolean
+etfci_maybe_start_drag (ETableFieldChooserItem *etfci,
+                        gint x,
+                        gint y)
+{
+	if (!etfci->maybe_drag)
+		return FALSE;
+
+	if (MAX (abs (etfci->click_x - x),
+		 abs (etfci->click_y - y)) <= 3)
+		return FALSE;
+
+	return TRUE;
+}
+
+static void
+etfci_start_drag (ETableFieldChooserItem *etfci,
+                  GdkEvent *event,
+                  gdouble x,
+                  gdouble y)
+{
+	GtkWidget *widget = GTK_WIDGET (GNOME_CANVAS_ITEM (etfci)->canvas);
+	GtkTargetList *list;
+	GdkDragContext *context;
+	ETableCol *ecol;
+	cairo_surface_t *cs;
+	cairo_t *cr;
+	gint drag_col;
+	gint button_height;
+
+	GtkTargetEntry  etfci_drag_types[] = {
+		{ (gchar *) TARGET_ETABLE_COL_TYPE, 0, TARGET_ETABLE_COL_HEADER },
+	};
+
+	if (etfci->combined_header == NULL)
+		return;
+
+	drag_col = etfci_find_button (etfci, y);
+
+	if (drag_col < 0 || drag_col > e_table_header_count (etfci->combined_header))
+		return;
+
+	ecol = e_table_header_get_column (etfci->combined_header, drag_col);
+
+	if (ecol->disabled)
+		return;
+
+	etfci->drag_col = ecol->col_idx;
+
+	etfci_drag_types[0].target = g_strdup_printf (
+		"%s-%s", etfci_drag_types[0].target, etfci->dnd_code);
+	d (g_print ("etfci - %s\n", etfci_drag_types[0].target));
+	list = gtk_target_list_new (etfci_drag_types, G_N_ELEMENTS (etfci_drag_types));
+	context = gtk_drag_begin (widget, list, GDK_ACTION_MOVE, 1, event);
+	g_free ((gpointer) etfci_drag_types[0].target);
+
+	button_height = e_table_header_compute_height (ecol, widget);
+	cs = cairo_image_surface_create (
+		CAIRO_FORMAT_ARGB32,
+		etfci->width, button_height);
+	cr = cairo_create (cs);
+
+	e_table_header_draw_button (
+		cr, ecol,
+		widget, 0, 0,
+		etfci->width, button_height,
+		etfci->width, button_height,
+		E_TABLE_COL_ARROW_NONE);
+
+	gtk_drag_set_icon_surface (context, cs);
+
+	cairo_surface_destroy (cs);
+	cairo_destroy (cr);
+	etfci->maybe_drag = FALSE;
+}
+
+/*
+ * Handles the events on the ETableFieldChooserItem
+ */
+static gint
+etfci_event (GnomeCanvasItem *item,
+             GdkEvent *e)
+{
+	ETableFieldChooserItem *etfci = E_TABLE_FIELD_CHOOSER_ITEM (item);
+	GnomeCanvas *canvas = item->canvas;
+	gint x, y;
+
+	switch (e->type) {
+	case GDK_MOTION_NOTIFY:
+		gnome_canvas_w2c (canvas, e->motion.x, e->motion.y, &x, &y);
+
+		if (etfci_maybe_start_drag (etfci, x, y))
+			etfci_start_drag (etfci, e, x, y);
+		break;
+
+	case GDK_BUTTON_PRESS:
+		gnome_canvas_w2c (canvas, e->button.x, e->button.y, &x, &y);
+
+		if (e->button.button == 1) {
+			etfci->click_x = x;
+			etfci->click_y = y;
+			etfci->maybe_drag = TRUE;
+		}
+		break;
+
+	case GDK_BUTTON_RELEASE: {
+		etfci->maybe_drag = FALSE;
+		break;
+	}
+
+	default:
+		return FALSE;
+	}
+	return TRUE;
+}
+
+static void
+etfci_class_init (ETableFieldChooserItemClass *class)
+{
+	GnomeCanvasItemClass *item_class = GNOME_CANVAS_ITEM_CLASS (class);
+	GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+	object_class->dispose = etfci_dispose;
+	object_class->set_property = etfci_set_property;
+	object_class->get_property = etfci_get_property;
+
+	item_class->update      = etfci_update;
+	item_class->realize     = etfci_realize;
+	item_class->unrealize   = etfci_unrealize;
+	item_class->draw        = etfci_draw;
+	item_class->point       = etfci_point;
+	item_class->event       = etfci_event;
+
+	g_object_class_install_property (
+		object_class,
+		PROP_DND_CODE,
+		g_param_spec_string (
+			"dnd_code",
+			"DnD code",
+			NULL,
+			NULL,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_FULL_HEADER,
+		g_param_spec_object (
+			"full_header",
+			"Full Header",
+			NULL,
+			E_TYPE_TABLE_HEADER,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_HEADER,
+		g_param_spec_object (
+			"header",
+			"Header",
+			NULL,
+			E_TYPE_TABLE_HEADER,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_WIDTH,
+		g_param_spec_double (
+			"width",
+			"Width",
+			NULL,
+			0, G_MAXDOUBLE, 0,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_HEIGHT,
+		g_param_spec_double (
+			"height",
+			"Height",
+			NULL,
+			0, G_MAXDOUBLE, 0,
+			G_PARAM_READABLE));
+}
+
+static void
+etfci_init (ETableFieldChooserItem *etfci)
+{
+	etfci->full_header = NULL;
+	etfci->header = NULL;
+	etfci->combined_header = NULL;
+
+	etfci->height = etfci->width = 0;
+
+	etfci->font_desc = NULL;
+
+	etfci->full_header_structure_change_id = 0;
+	etfci->full_header_dimension_change_id = 0;
+	etfci->table_header_structure_change_id = 0;
+	etfci->table_header_dimension_change_id = 0;
+
+	etfci->dnd_code = NULL;
+
+	etfci->maybe_drag = 0;
+	etfci->drag_end_id = 0;
+
+	e_canvas_item_set_reflow_callback (GNOME_CANVAS_ITEM (etfci), etfci_reflow);
+}
+
diff --git a/e-util/e-table-field-chooser-item.h b/e-util/e-table-field-chooser-item.h
new file mode 100644
index 0000000..08bfeb6
--- /dev/null
+++ b/e-util/e-table-field-chooser-item.h
@@ -0,0 +1,97 @@
+/*
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef _E_TABLE_FIELD_CHOOSER_ITEM_H_
+#define _E_TABLE_FIELD_CHOOSER_ITEM_H_
+
+#include <libgnomecanvas/libgnomecanvas.h>
+#include <libxml/tree.h>
+
+#include <e-util/e-table-header.h>
+
+/* Standard GObject macros */
+#define E_TYPE_TABLE_FIELD_CHOOSER_ITEM \
+	(e_table_field_chooser_item_get_type ())
+#define E_TABLE_FIELD_CHOOSER_ITEM(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_TABLE_FIELD_CHOOSER_ITEM, ETableFieldChooserItem))
+#define E_TABLE_FIELD_CHOOSER_ITEM_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_TABLE_FIELD_CHOOSER_ITEM, ETableFieldChooserItemClass))
+#define E_IS_TABLE_FIELD_CHOOSER_ITEM(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_TABLE_FIELD_CHOOSER_ITEM))
+#define E_IS_TABLE_FIELD_CHOOSER_ITEM_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_TABLE_FIELD_CHOOSER_ITEM))
+#define E_TABLE_FIELD_CHOOSER_ITEM_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_TABLE_FIELD_CHOOSER_ITEM, ETableFieldChooserItemClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ETableFieldChooserItem ETableFieldChooserItem;
+typedef struct _ETableFieldChooserItemClass ETableFieldChooserItemClass;
+
+struct _ETableFieldChooserItem {
+	GnomeCanvasItem  parent;
+
+	ETableHeader    *full_header;
+	ETableHeader    *header;
+	ETableHeader    *combined_header;
+
+	gdouble           height, width;
+
+	PangoFontDescription *font_desc;
+
+	/*
+	 * Ids
+	 */
+	gint full_header_structure_change_id, full_header_dimension_change_id;
+	gint table_header_structure_change_id, table_header_dimension_change_id;
+
+	gchar           *dnd_code;
+
+	/*
+	 * For dragging columns
+	 */
+	guint            maybe_drag : 1;
+	gint              click_x, click_y;
+	gint              drag_col;
+	guint            drag_data_get_id;
+        guint            drag_end_id;
+};
+
+struct _ETableFieldChooserItemClass {
+	GnomeCanvasItemClass parent_class;
+};
+
+GType		e_table_field_chooser_item_get_type	(void) G_GNUC_CONST;
+
+G_END_DECLS
+
+#endif /* _E_TABLE_FIELD_CHOOSER_ITEM_H_ */
diff --git a/e-util/e-table-field-chooser.c b/e-util/e-table-field-chooser.c
new file mode 100644
index 0000000..c402edb
--- /dev/null
+++ b/e-util/e-table-field-chooser.c
@@ -0,0 +1,335 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-table-field-chooser.h"
+
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+#include <libgnomecanvas/libgnomecanvas.h>
+
+#include "e-canvas.h"
+#include "e-table-field-chooser-item.h"
+#include "e-util-private.h"
+
+static void e_table_field_chooser_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec);
+static void e_table_field_chooser_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec);
+static void e_table_field_chooser_dispose (GObject *object);
+
+enum {
+	PROP_0,
+	PROP_FULL_HEADER,
+	PROP_HEADER,
+	PROP_DND_CODE
+};
+
+G_DEFINE_TYPE (ETableFieldChooser, e_table_field_chooser, GTK_TYPE_VBOX)
+
+static void
+e_table_field_chooser_class_init (ETableFieldChooserClass *class)
+{
+	GObjectClass *object_class;
+
+	object_class = (GObjectClass *) class;
+
+	object_class->set_property = e_table_field_chooser_set_property;
+	object_class->get_property = e_table_field_chooser_get_property;
+	object_class->dispose      = e_table_field_chooser_dispose;
+
+	g_object_class_install_property (
+		object_class,
+		PROP_DND_CODE,
+		g_param_spec_string (
+			"dnd_code",
+			"DnD code",
+			NULL,
+			NULL,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_FULL_HEADER,
+		g_param_spec_object (
+			"full_header",
+			"Full Header",
+			NULL,
+			E_TYPE_TABLE_HEADER,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_HEADER,
+		g_param_spec_object (
+			"header",
+			"Header",
+			NULL,
+			E_TYPE_TABLE_HEADER,
+			G_PARAM_READWRITE));
+}
+
+static void
+ensure_nonzero_step_increments (ETableFieldChooser *etfc)
+{
+	GtkAdjustment *va, *ha;
+
+	va = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (etfc->canvas));
+	ha = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (etfc->canvas));
+
+	/*
+	  it looks pretty complicated to get height of column header
+	  so use 16 pixels which should be OK
+	*/
+	if (va)
+		gtk_adjustment_set_step_increment (va, 16.0);
+	if (ha)
+		gtk_adjustment_set_step_increment (ha, 16.0);
+}
+
+static void allocate_callback (GtkWidget *canvas, GtkAllocation *allocation, ETableFieldChooser *etfc)
+{
+	gdouble height;
+	etfc->last_alloc = *allocation;
+	gnome_canvas_item_set (
+		etfc->item,
+		"width", (gdouble) allocation->width,
+		NULL);
+	g_object_get (
+		etfc->item,
+		"height", &height,
+		NULL);
+	height = MAX (height, allocation->height);
+	gnome_canvas_set_scroll_region (GNOME_CANVAS (etfc->canvas), 0, 0, allocation->width - 1, height - 1);
+	gnome_canvas_item_set (
+		etfc->rect,
+		"x2", (gdouble) allocation->width,
+		"y2", (gdouble) height,
+		NULL);
+	ensure_nonzero_step_increments (etfc);
+}
+
+static void resize (GnomeCanvas *canvas, ETableFieldChooser *etfc)
+{
+	gdouble height;
+	g_object_get (
+		etfc->item,
+		"height", &height,
+		NULL);
+
+	height = MAX (height, etfc->last_alloc.height);
+
+	gnome_canvas_set_scroll_region (GNOME_CANVAS (etfc->canvas), 0, 0, etfc->last_alloc.width - 1, height - 1);
+	gnome_canvas_item_set (
+		etfc->rect,
+		"x2", (gdouble) etfc->last_alloc.width,
+		"y2", (gdouble) height,
+		NULL);
+	ensure_nonzero_step_increments (etfc);
+}
+
+static GtkWidget *
+create_content (GnomeCanvas **canvas)
+{
+	GtkWidget *vbox_top;
+	GtkWidget *label1;
+	GtkWidget *scrolledwindow1;
+	GtkWidget *canvas_buttons;
+
+	g_return_val_if_fail (canvas != NULL, NULL);
+
+	vbox_top = gtk_vbox_new (FALSE, 4);
+	gtk_widget_show (vbox_top);
+
+	label1 = gtk_label_new (_("To add a column to your table, drag it into\nthe location in which you want it to appear."));
+	gtk_widget_show (label1);
+	gtk_box_pack_start (GTK_BOX (vbox_top), label1, FALSE, FALSE, 0);
+	gtk_label_set_justify (GTK_LABEL (label1), GTK_JUSTIFY_CENTER);
+
+	scrolledwindow1 = gtk_scrolled_window_new (NULL, NULL);
+	gtk_widget_show (scrolledwindow1);
+	gtk_box_pack_start (GTK_BOX (vbox_top), scrolledwindow1, TRUE, TRUE, 0);
+	gtk_widget_set_can_focus (scrolledwindow1, FALSE);
+	gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolledwindow1), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
+
+	canvas_buttons = e_canvas_new ();
+	gtk_widget_show (canvas_buttons);
+	gtk_container_add (GTK_CONTAINER (scrolledwindow1), canvas_buttons);
+	gtk_widget_set_can_focus (canvas_buttons, FALSE);
+	gtk_widget_set_can_default (canvas_buttons, FALSE);
+
+	*canvas = GNOME_CANVAS (canvas_buttons);
+
+	return vbox_top;
+}
+
+static void
+e_table_field_chooser_init (ETableFieldChooser *etfc)
+{
+	GtkWidget *widget;
+
+	widget = create_content (&etfc->canvas);
+	if (!widget) {
+		return;
+	}
+
+	gtk_widget_set_size_request (widget, -1, 250);
+	gtk_box_pack_start (GTK_BOX (etfc), widget, TRUE, TRUE, 0);
+
+	etfc->rect = gnome_canvas_item_new (
+		gnome_canvas_root (GNOME_CANVAS (etfc->canvas)),
+		gnome_canvas_rect_get_type (),
+		"x1", (gdouble) 0,
+		"y1", (gdouble) 0,
+		"x2", (gdouble) 100,
+		"y2", (gdouble) 100,
+		"fill_color", "white",
+		NULL);
+
+	etfc->item = gnome_canvas_item_new (
+		gnome_canvas_root (etfc->canvas),
+		e_table_field_chooser_item_get_type (),
+		"width", (gdouble) 100,
+		"full_header", etfc->full_header,
+		"header", etfc->header,
+		"dnd_code", etfc->dnd_code,
+		NULL);
+
+	g_signal_connect (
+		etfc->canvas, "reflow",
+		G_CALLBACK (resize), etfc);
+
+	gnome_canvas_set_scroll_region (
+		GNOME_CANVAS (etfc->canvas),
+		0, 0, 100, 100);
+
+	/* Connect the signals */
+	g_signal_connect (
+		etfc->canvas, "size_allocate",
+		G_CALLBACK (allocate_callback), etfc);
+
+	gtk_widget_show_all (widget);
+}
+
+static void
+e_table_field_chooser_dispose (GObject *object)
+{
+	ETableFieldChooser *etfc = E_TABLE_FIELD_CHOOSER (object);
+
+	g_free (etfc->dnd_code);
+	etfc->dnd_code = NULL;
+
+	if (etfc->full_header)
+		g_object_unref (etfc->full_header);
+	etfc->full_header = NULL;
+
+	if (etfc->header)
+		g_object_unref (etfc->header);
+	etfc->header = NULL;
+
+	/* Chain up to parent's dispose() method. */
+	G_OBJECT_CLASS (e_table_field_chooser_parent_class)->dispose (object);
+}
+
+GtkWidget *
+e_table_field_chooser_new (void)
+{
+	return g_object_new (E_TYPE_TABLE_FIELD_CHOOSER, NULL);
+}
+
+static void
+e_table_field_chooser_set_property (GObject *object,
+                                    guint property_id,
+                                    const GValue *value,
+                                    GParamSpec *pspec)
+{
+	ETableFieldChooser *etfc = E_TABLE_FIELD_CHOOSER (object);
+
+	switch (property_id) {
+	case PROP_DND_CODE:
+		g_free (etfc->dnd_code);
+		etfc->dnd_code = g_strdup (g_value_get_string (value));
+		if (etfc->item)
+			g_object_set (
+				etfc->item,
+				"dnd_code", etfc->dnd_code,
+				NULL);
+		break;
+	case PROP_FULL_HEADER:
+		if (etfc->full_header)
+			g_object_unref (etfc->full_header);
+		if (g_value_get_object (value))
+			etfc->full_header = E_TABLE_HEADER (g_value_get_object (value));
+		else
+			etfc->full_header = NULL;
+		if (etfc->full_header)
+			g_object_ref (etfc->full_header);
+		if (etfc->item)
+			g_object_set (
+				etfc->item,
+				"full_header", etfc->full_header,
+				NULL);
+		break;
+	case PROP_HEADER:
+		if (etfc->header)
+			g_object_unref (etfc->header);
+		if (g_value_get_object (value))
+			etfc->header = E_TABLE_HEADER (g_value_get_object (value));
+		else
+			etfc->header = NULL;
+		if (etfc->header)
+			g_object_ref (etfc->header);
+		if (etfc->item)
+			g_object_set (
+				etfc->item,
+				"header", etfc->header,
+				NULL);
+		break;
+	default:
+		break;
+	}
+}
+
+static void
+e_table_field_chooser_get_property (GObject *object,
+                                    guint property_id,
+                                    GValue *value,
+                                    GParamSpec *pspec)
+{
+	ETableFieldChooser *etfc = E_TABLE_FIELD_CHOOSER (object);
+
+	switch (property_id) {
+	case PROP_DND_CODE:
+		g_value_set_string (value, etfc->dnd_code);
+		break;
+	case PROP_FULL_HEADER:
+		g_value_set_object (value, etfc->full_header);
+		break;
+	case PROP_HEADER:
+		g_value_set_object (value, etfc->header);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+		break;
+	}
+}
diff --git a/e-util/e-table-field-chooser.h b/e-util/e-table-field-chooser.h
new file mode 100644
index 0000000..567b9af
--- /dev/null
+++ b/e-util/e-table-field-chooser.h
@@ -0,0 +1,84 @@
+/*
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef __E_TABLE_FIELD_CHOOSER_H__
+#define __E_TABLE_FIELD_CHOOSER_H__
+
+#include <gtk/gtk.h>
+#include <libgnomecanvas/libgnomecanvas.h>
+
+#include <e-util/e-table-header.h>
+
+/* Standard GObject macros */
+#define E_TYPE_TABLE_FIELD_CHOOSER \
+	(e_table_field_chooser_get_type ())
+#define E_TABLE_FIELD_CHOOSER(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_TABLE_FIELD_CHOOSER, ETableFieldChooser))
+#define E_TABLE_FIELD_CHOOSER_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_TABLE_FIELD_CHOOSER, ETableFieldChooserClass))
+#define E_IS_TABLE_FIELD_CHOOSER(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_TABLE_FIELD_CHOOSER))
+#define E_IS_TABLE_FIELD_CHOOSER_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_TABLE_FIELD_CHOOSER))
+#define E_TABLE_FIELD_CHOOSER_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_TABLE_FIELD_CHOOSER, ETableFieldChooserClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ETableFieldChooser ETableFieldChooser;
+typedef struct _ETableFieldChooserClass ETableFieldChooserClass;
+
+struct _ETableFieldChooser {
+	GtkBox parent;
+
+	/* item specific fields */
+	GnomeCanvas *canvas;
+	GnomeCanvasItem *item;
+
+	GnomeCanvasItem *rect;
+	GtkAllocation last_alloc;
+
+	gchar *dnd_code;
+	ETableHeader *full_header;
+	ETableHeader *header;
+};
+
+struct _ETableFieldChooserClass {
+	GtkBoxClass parent_class;
+};
+
+GType		e_table_field_chooser_get_type	(void) G_GNUC_CONST;
+GtkWidget *	e_table_field_chooser_new	(void);
+
+G_END_DECLS
+
+#endif /* __E_TABLE_FIELD_CHOOSER_H__ */
diff --git a/e-util/e-table-group-container.c b/e-util/e-table-group-container.c
new file mode 100644
index 0000000..5741cd1
--- /dev/null
+++ b/e-util/e-table-group-container.c
@@ -0,0 +1,1667 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-table-group-container.h"
+
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+#include <gdk/gdkkeysyms.h>
+#include <libgnomecanvas/libgnomecanvas.h>
+
+#include "e-canvas-utils.h"
+#include "e-canvas.h"
+#include "e-table-defines.h"
+#include "e-table-group-leaf.h"
+#include "e-table-item.h"
+#include "e-table-sorting-utils.h"
+#include "e-text.h"
+#include "e-unicode.h"
+
+#define TITLE_HEIGHT         16
+
+/* workaround for avoiding API breakage */
+#define etgc_get_type e_table_group_container_get_type
+G_DEFINE_TYPE (ETableGroupContainer, etgc, E_TYPE_TABLE_GROUP)
+
+enum {
+	PROP_0,
+	PROP_HEIGHT,
+	PROP_WIDTH,
+	PROP_MINIMUM_WIDTH,
+	PROP_FROZEN,
+	PROP_TABLE_ALTERNATING_ROW_COLORS,
+	PROP_TABLE_HORIZONTAL_DRAW_GRID,
+	PROP_TABLE_VERTICAL_DRAW_GRID,
+	PROP_TABLE_DRAW_FOCUS,
+	PROP_CURSOR_MODE,
+	PROP_SELECTION_MODEL,
+	PROP_LENGTH_THRESHOLD,
+	PROP_UNIFORM_ROW_HEIGHT
+};
+
+static EPrintable *
+etgc_get_printable (ETableGroup *etg);
+
+static void
+e_table_group_container_child_node_free (ETableGroupContainer *etgc,
+                                         ETableGroupContainerChildNode *child_node)
+{
+	ETableGroup *etg = E_TABLE_GROUP (etgc);
+	ETableGroup *child = child_node->child;
+
+	g_object_run_dispose (G_OBJECT (child));
+	e_table_model_free_value (
+		etg->model, etgc->ecol->col_idx,
+		child_node->key);
+	g_free (child_node->string);
+	g_object_run_dispose (G_OBJECT (child_node->text));
+	g_object_run_dispose (G_OBJECT (child_node->rect));
+}
+
+static void
+e_table_group_container_list_free (ETableGroupContainer *etgc)
+{
+	ETableGroupContainerChildNode *child_node;
+	GList *list;
+
+	for (list = etgc->children; list; list = g_list_next (list)) {
+		child_node = (ETableGroupContainerChildNode *) list->data;
+		e_table_group_container_child_node_free (etgc, child_node);
+	}
+
+	g_list_free (etgc->children);
+	etgc->children = NULL;
+}
+
+static void
+etgc_dispose (GObject *object)
+{
+	ETableGroupContainer *etgc = E_TABLE_GROUP_CONTAINER (object);
+
+	if (etgc->children)
+		e_table_group_container_list_free (etgc);
+
+	if (etgc->font_desc)
+		pango_font_description_free (etgc->font_desc);
+	etgc->font_desc = NULL;
+
+	if (etgc->ecol)
+		g_object_unref (etgc->ecol);
+	etgc->ecol = NULL;
+
+	if (etgc->sort_info)
+		g_object_unref (etgc->sort_info);
+	etgc->sort_info = NULL;
+
+	if (etgc->selection_model)
+		g_object_unref (etgc->selection_model);
+	etgc->selection_model = NULL;
+
+	if (etgc->rect)
+		g_object_run_dispose (G_OBJECT (etgc->rect));
+	etgc->rect = NULL;
+
+	G_OBJECT_CLASS (etgc_parent_class)->dispose (object);
+}
+
+/**
+ * e_table_group_container_construct
+ * @parent: The %GnomeCanvasGroup to create a child of.
+ * @etgc: The %ETableGroupContainer.
+ * @full_header: The full header of the %ETable.
+ * @header: The current header of the %ETable.
+ * @model: The %ETableModel of the %ETable.
+ * @sort_info: The %ETableSortInfo of the %ETable.
+ * @n: Which grouping level this is (Starts at 0 and sends n + 1 to any child %ETableGroups.
+ *
+ * This routine constructs the new %ETableGroupContainer.
+ */
+void
+e_table_group_container_construct (GnomeCanvasGroup *parent,
+                                   ETableGroupContainer *etgc,
+                                   ETableHeader *full_header,
+                                   ETableHeader *header,
+                                   ETableModel *model,
+                                   ETableSortInfo *sort_info,
+                                   gint n)
+{
+	ETableCol *col;
+	ETableSortColumn column = e_table_sort_info_grouping_get_nth (sort_info, n);
+	GtkWidget *widget;
+	GtkStyle *style;
+
+	col = e_table_header_get_column_by_col_idx (full_header, column.column);
+	if (col == NULL)
+		col = e_table_header_get_column (full_header, e_table_header_count (full_header) - 1);
+
+	e_table_group_construct (parent, E_TABLE_GROUP (etgc), full_header, header, model);
+	etgc->ecol = col;
+	g_object_ref (etgc->ecol);
+	etgc->sort_info = sort_info;
+	g_object_ref (etgc->sort_info);
+	etgc->n = n;
+	etgc->ascending = column.ascending;
+
+	widget = GTK_WIDGET (GNOME_CANVAS_ITEM (etgc)->canvas);
+	style = gtk_widget_get_style (widget);
+	etgc->font_desc = pango_font_description_copy (style->font_desc);
+
+	etgc->open = TRUE;
+}
+
+/**
+ * e_table_group_container_new
+ * @parent: The %GnomeCanvasGroup to create a child of.
+ * @full_header: The full header of the %ETable.
+ * @header: The current header of the %ETable.
+ * @model: The %ETableModel of the %ETable.
+ * @sort_info: The %ETableSortInfo of the %ETable.
+ * @n: Which grouping level this is (Starts at 0 and sends n + 1 to any child %ETableGroups.
+ *
+ * %ETableGroupContainer is an %ETableGroup which groups by the nth
+ * grouping of the %ETableSortInfo.  It creates %ETableGroups as
+ * children.
+ *
+ * Returns: The new %ETableGroupContainer.
+ */
+ETableGroup *
+e_table_group_container_new (GnomeCanvasGroup *parent,
+                             ETableHeader *full_header,
+                             ETableHeader *header,
+                             ETableModel *model,
+                             ETableSortInfo *sort_info,
+                             gint n)
+{
+	ETableGroupContainer *etgc;
+
+	g_return_val_if_fail (parent != NULL, NULL);
+
+	etgc = g_object_new (E_TYPE_TABLE_GROUP_CONTAINER, NULL);
+
+	e_table_group_container_construct (
+		parent, etgc, full_header, header,
+		model, sort_info, n);
+	return E_TABLE_GROUP (etgc);
+}
+
+static gint
+etgc_event (GnomeCanvasItem *item,
+            GdkEvent *event)
+{
+	ETableGroupContainer *etgc = E_TABLE_GROUP_CONTAINER (item);
+	gboolean return_val = TRUE;
+	gboolean change_focus = FALSE;
+	gboolean use_col = FALSE;
+	gint start_col = 0;
+	gint old_col;
+	EFocus direction = E_FOCUS_START;
+
+	switch (event->type) {
+	case GDK_KEY_PRESS:
+		if (event->key.keyval == GDK_KEY_Tab ||
+		    event->key.keyval == GDK_KEY_KP_Tab ||
+		    event->key.keyval == GDK_KEY_ISO_Left_Tab) {
+			change_focus = TRUE;
+			use_col      = TRUE;
+			start_col    = (event->key.state & GDK_SHIFT_MASK) ? -1 : 0;
+			direction    = (event->key.state & GDK_SHIFT_MASK) ? E_FOCUS_END : E_FOCUS_START;
+		} else if (event->key.keyval == GDK_KEY_Left ||
+			   event->key.keyval == GDK_KEY_KP_Left) {
+			change_focus = TRUE;
+			use_col      = TRUE;
+			start_col    = -1;
+			direction    = E_FOCUS_END;
+		} else if (event->key.keyval == GDK_KEY_Right ||
+			   event->key.keyval == GDK_KEY_KP_Right) {
+			change_focus = TRUE;
+			use_col   = TRUE;
+			start_col = 0;
+			direction = E_FOCUS_START;
+		} else if (event->key.keyval == GDK_KEY_Down ||
+			   event->key.keyval == GDK_KEY_KP_Down) {
+			change_focus = TRUE;
+			use_col      = FALSE;
+			direction    = E_FOCUS_START;
+		} else if (event->key.keyval == GDK_KEY_Up ||
+			   event->key.keyval == GDK_KEY_KP_Up) {
+			change_focus = TRUE;
+			use_col      = FALSE;
+			direction    = E_FOCUS_END;
+		} else if (event->key.keyval == GDK_KEY_Return ||
+			   event->key.keyval == GDK_KEY_KP_Enter) {
+			change_focus = TRUE;
+			use_col      = FALSE;
+			direction    = E_FOCUS_START;
+		}
+		if (change_focus) {
+			GList *list;
+			for (list = etgc->children; list; list = list->next) {
+				ETableGroupContainerChildNode *child_node;
+				ETableGroup                   *child;
+
+				child_node = (ETableGroupContainerChildNode *) list->data;
+				child      = child_node->child;
+
+				if (e_table_group_get_focus (child)) {
+					old_col = e_table_group_get_focus_column (child);
+					if (old_col == -1)
+						old_col = 0;
+					if (start_col == -1)
+						start_col = e_table_header_count (e_table_group_get_header (child)) - 1;
+
+					if (direction == E_FOCUS_END)
+						list = list->prev;
+					else
+						list = list->next;
+
+					if (list) {
+						child_node = (ETableGroupContainerChildNode *) list->data;
+						child = child_node->child;
+						if (use_col)
+							e_table_group_set_focus (child, direction, start_col);
+						else
+							e_table_group_set_focus (child, direction, old_col);
+						return 1;
+					} else {
+						return 0;
+					}
+				}
+			}
+			if (direction == E_FOCUS_END)
+				list = g_list_last (etgc->children);
+			else
+				list = etgc->children;
+			if (list) {
+				ETableGroupContainerChildNode *child_node;
+				ETableGroup                   *child;
+
+				child_node = (ETableGroupContainerChildNode *) list->data;
+				child = child_node->child;
+
+				if (start_col == -1)
+					start_col = e_table_header_count (e_table_group_get_header (child)) - 1;
+
+				e_table_group_set_focus (child, direction, start_col);
+				return 1;
+			}
+		}
+		return_val = FALSE;
+		break;
+	default:
+		return_val = FALSE;
+		break;
+	}
+	if (return_val == FALSE) {
+		if (GNOME_CANVAS_ITEM_CLASS (etgc_parent_class)->event)
+			return GNOME_CANVAS_ITEM_CLASS (etgc_parent_class)->event (item, event);
+	}
+	return return_val;
+
+}
+
+/* Realize handler for the text item */
+static void
+etgc_realize (GnomeCanvasItem *item)
+{
+	ETableGroupContainer *etgc;
+
+	if (GNOME_CANVAS_ITEM_CLASS (etgc_parent_class)->realize)
+		(* GNOME_CANVAS_ITEM_CLASS (etgc_parent_class)->realize) (item);
+
+	etgc = E_TABLE_GROUP_CONTAINER (item);
+
+	e_canvas_item_request_reflow (GNOME_CANVAS_ITEM (etgc));
+}
+
+/* Unrealize handler for the etgc item */
+static void
+etgc_unrealize (GnomeCanvasItem *item)
+{
+	if (GNOME_CANVAS_ITEM_CLASS (etgc_parent_class)->unrealize)
+		(* GNOME_CANVAS_ITEM_CLASS (etgc_parent_class)->unrealize) (item);
+}
+
+static void
+compute_text (ETableGroupContainer *etgc,
+              ETableGroupContainerChildNode *child_node)
+{
+	gchar *text;
+
+	if (etgc->ecol->text) {
+		/* Translators: This text is used as a special row when an ETable
+		 * has turned on grouping on a column, which has set a title.
+		 * The first %s is replaced with a column title.
+		 * The second %s is replaced with an actual  group value.
+		 * Finally the %d is replaced with count of items in this group.
+		 * Example: "Family name: Smith (13 items)"
+		*/
+		text = g_strdup_printf (
+			ngettext (
+				"%s: %s (%d item)",
+				"%s: %s (%d items)",
+				child_node->count),
+			etgc->ecol->text, child_node->string,
+			(gint) child_node->count);
+	} else {
+		/* Translators: This text is used as a special row when an ETable
+		 * has turned on grouping on a column, which doesn't have set a title.
+		 * The %s is replaced with an actual group value.
+		 * The %d is replaced with count of items in this group.
+		 * Example: "Smith (13 items)"
+		*/
+		text = g_strdup_printf (
+			ngettext (
+				"%s (%d item)",
+				"%s (%d items)",
+				child_node->count),
+			child_node->string,
+			(gint) child_node->count);
+	}
+	gnome_canvas_item_set (
+		child_node->text,
+		"text", text,
+		NULL);
+	g_free (text);
+}
+
+static void
+child_cursor_change (ETableGroup *etg,
+                     gint row,
+                     ETableGroupContainer *etgc)
+{
+	e_table_group_cursor_change (E_TABLE_GROUP (etgc), row);
+}
+
+static void
+child_cursor_activated (ETableGroup *etg,
+                        gint row,
+                        ETableGroupContainer *etgc)
+{
+	e_table_group_cursor_activated (E_TABLE_GROUP (etgc), row);
+}
+
+static void
+child_double_click (ETableGroup *etg,
+                    gint row,
+                    gint col,
+                    GdkEvent *event,
+                    ETableGroupContainer *etgc)
+{
+	e_table_group_double_click (E_TABLE_GROUP (etgc), row, col, event);
+}
+
+static gboolean
+child_right_click (ETableGroup *etg,
+                   gint row,
+                   gint col,
+                   GdkEvent *event,
+                   ETableGroupContainer *etgc)
+{
+	return e_table_group_right_click (E_TABLE_GROUP (etgc), row, col, event);
+}
+
+static gboolean
+child_click (ETableGroup *etg,
+             gint row,
+             gint col,
+             GdkEvent *event,
+             ETableGroupContainer *etgc)
+{
+	return e_table_group_click (E_TABLE_GROUP (etgc), row, col, event);
+}
+
+static gboolean
+child_key_press (ETableGroup *etg,
+                 gint row,
+                 gint col,
+                 GdkEvent *event,
+                 ETableGroupContainer *etgc)
+{
+	return e_table_group_key_press (E_TABLE_GROUP (etgc), row, col, event);
+}
+
+static gboolean
+child_start_drag (ETableGroup *etg,
+                  gint row,
+                  gint col,
+                  GdkEvent *event,
+                  ETableGroupContainer *etgc)
+{
+	return e_table_group_start_drag (E_TABLE_GROUP (etgc), row, col, event);
+}
+
+static ETableGroupContainerChildNode *
+create_child_node (ETableGroupContainer *etgc,
+                   gpointer val)
+{
+	ETableGroup *child;
+	ETableGroupContainerChildNode *child_node;
+	ETableGroup *etg = E_TABLE_GROUP (etgc);
+
+	child_node = g_new (ETableGroupContainerChildNode, 1);
+	child_node->rect = gnome_canvas_item_new (
+		GNOME_CANVAS_GROUP (etgc),
+		gnome_canvas_rect_get_type (),
+		"fill_color", "grey70",
+		"outline_color", "grey50",
+		NULL);
+	child_node->text = gnome_canvas_item_new (
+		GNOME_CANVAS_GROUP (etgc),
+		e_text_get_type (),
+		"fill_color", "black",
+		NULL);
+	child = e_table_group_new (
+		GNOME_CANVAS_GROUP (etgc), etg->full_header,
+		etg->header, etg->model, etgc->sort_info, etgc->n + 1);
+	gnome_canvas_item_set (
+		GNOME_CANVAS_ITEM (child),
+		"alternating_row_colors", etgc->alternating_row_colors,
+		"horizontal_draw_grid", etgc->horizontal_draw_grid,
+		"vertical_draw_grid", etgc->vertical_draw_grid,
+		"drawfocus", etgc->draw_focus,
+		"cursor_mode", etgc->cursor_mode,
+		"selection_model", etgc->selection_model,
+		"length_threshold", etgc->length_threshold,
+		"uniform_row_height", etgc->uniform_row_height,
+		"minimum_width", etgc->minimum_width - GROUP_INDENT,
+		NULL);
+
+	g_signal_connect (
+		child, "cursor_change",
+		G_CALLBACK (child_cursor_change), etgc);
+	g_signal_connect (
+		child, "cursor_activated",
+		G_CALLBACK (child_cursor_activated), etgc);
+	g_signal_connect (
+		child, "double_click",
+		G_CALLBACK (child_double_click), etgc);
+	g_signal_connect (
+		child, "right_click",
+		G_CALLBACK (child_right_click), etgc);
+	g_signal_connect (
+		child, "click",
+		G_CALLBACK (child_click), etgc);
+	g_signal_connect (
+		child, "key_press",
+		G_CALLBACK (child_key_press), etgc);
+	g_signal_connect (
+		child, "start_drag",
+		G_CALLBACK (child_start_drag), etgc);
+	child_node->child = child;
+	child_node->key = e_table_model_duplicate_value (etg->model, etgc->ecol->col_idx, val);
+	child_node->string = e_table_model_value_to_string (etg->model, etgc->ecol->col_idx, val);
+	child_node->count = 0;
+
+	return child_node;
+}
+
+static void
+etgc_add (ETableGroup *etg,
+          gint row)
+{
+	ETableGroupContainer *etgc = E_TABLE_GROUP_CONTAINER (etg);
+	gpointer val = e_table_model_value_at (etg->model, etgc->ecol->col_idx, row);
+	GCompareDataFunc comp = etgc->ecol->compare;
+	gpointer cmp_cache = e_table_sorting_utils_create_cmp_cache ();
+	GList *list = etgc->children;
+	ETableGroup *child;
+	ETableGroupContainerChildNode *child_node;
+	gint i = 0;
+
+	for (; list; list = g_list_next (list), i++) {
+		gint comp_val;
+
+		child_node = list->data;
+		comp_val = (*comp)(child_node->key, val, cmp_cache);
+		if (comp_val == 0) {
+			e_table_sorting_utils_free_cmp_cache (cmp_cache);
+			child = child_node->child;
+			child_node->count++;
+			e_table_group_add (child, row);
+			compute_text (etgc, child_node);
+			return;
+		}
+		if ((comp_val > 0 && etgc->ascending) ||
+		    (comp_val < 0 && (!etgc->ascending)))
+			break;
+	}
+	e_table_sorting_utils_free_cmp_cache (cmp_cache);
+	child_node = create_child_node (etgc, val);
+	child = child_node->child;
+	child_node->count = 1;
+	e_table_group_add (child, row);
+
+	if (list)
+		etgc->children = g_list_insert (etgc->children, child_node, i);
+	else
+		etgc->children = g_list_append (etgc->children, child_node);
+
+	compute_text (etgc, child_node);
+	e_canvas_item_request_reflow (GNOME_CANVAS_ITEM (etgc));
+}
+
+static void
+etgc_add_array (ETableGroup *etg,
+                const gint *array,
+                gint count)
+{
+	gint i;
+	ETableGroupContainer *etgc = E_TABLE_GROUP_CONTAINER (etg);
+	gpointer lastval = NULL;
+	gint laststart = 0;
+	GCompareDataFunc comp = etgc->ecol->compare;
+	gpointer cmp_cache;
+	ETableGroupContainerChildNode *child_node;
+	ETableGroup *child;
+
+	if (count <= 0)
+		return;
+
+	e_table_group_container_list_free (etgc);
+	etgc->children = NULL;
+	cmp_cache = e_table_sorting_utils_create_cmp_cache ();
+
+	lastval = e_table_model_value_at (etg->model, etgc->ecol->col_idx, array[0]);
+
+	for (i = 1; i < count; i++) {
+		gpointer val = e_table_model_value_at (etg->model, etgc->ecol->col_idx, array[i]);
+		gint comp_val;
+
+		comp_val = (*comp)(lastval, val, cmp_cache);
+		if (comp_val != 0) {
+			child_node = create_child_node (etgc, lastval);
+			child = child_node->child;
+
+			e_table_group_add_array (child, array + laststart, i - laststart);
+			child_node->count = i - laststart;
+
+			etgc->children = g_list_append (etgc->children, child_node);
+			compute_text (etgc, child_node);
+			laststart = i;
+			lastval = val;
+		}
+	}
+
+	e_table_sorting_utils_free_cmp_cache (cmp_cache);
+
+	child_node = create_child_node (etgc, lastval);
+	child = child_node->child;
+
+	e_table_group_add_array (child, array + laststart, i - laststart);
+	child_node->count = i - laststart;
+
+	etgc->children = g_list_append (etgc->children, child_node);
+	compute_text (etgc, child_node);
+
+	e_canvas_item_request_reflow (GNOME_CANVAS_ITEM (etgc));
+}
+
+static void
+etgc_add_all (ETableGroup *etg)
+{
+	ETableGroupContainer *etgc = E_TABLE_GROUP_CONTAINER (etg);
+	ESorter *sorter = etgc->selection_model->sorter;
+	gint *array;
+	gint count;
+
+	e_sorter_get_sorted_to_model_array (sorter, &array, &count);
+
+	etgc_add_array (etg, array, count);
+}
+
+static gboolean
+etgc_remove (ETableGroup *etg,
+             gint row)
+{
+	ETableGroupContainer *etgc = E_TABLE_GROUP_CONTAINER (etg);
+	GList *list;
+
+	for (list = etgc->children; list; list = g_list_next (list)) {
+		ETableGroupContainerChildNode *child_node = list->data;
+		ETableGroup                   *child = child_node->child;
+
+		if (e_table_group_remove (child, row)) {
+			child_node->count--;
+			if (child_node->count == 0) {
+				e_table_group_container_child_node_free (etgc, child_node);
+				etgc->children = g_list_remove (etgc->children, child_node);
+				g_free (child_node);
+			} else
+				compute_text (etgc, child_node);
+
+			e_canvas_item_request_reflow (GNOME_CANVAS_ITEM (etgc));
+
+			return TRUE;
+		}
+	}
+	return FALSE;
+}
+
+static gint
+etgc_row_count (ETableGroup *etg)
+{
+	ETableGroupContainer *etgc = E_TABLE_GROUP_CONTAINER (etg);
+	GList *list;
+	gint count = 0;
+	for (list = etgc->children; list; list = g_list_next (list)) {
+		ETableGroup *group = ((ETableGroupContainerChildNode *) list->data)->child;
+		gint this_count = e_table_group_row_count (group);
+		count += this_count;
+	}
+	return count;
+}
+
+static void
+etgc_increment (ETableGroup *etg,
+                gint position,
+                gint amount)
+{
+	ETableGroupContainer *etgc = E_TABLE_GROUP_CONTAINER (etg);
+	GList *list;
+
+	for (list = etgc->children; list; list = g_list_next (list))
+		e_table_group_increment (
+			((ETableGroupContainerChildNode *) list->data)->child,
+			position, amount);
+}
+
+static void
+etgc_decrement (ETableGroup *etg,
+                gint position,
+                gint amount)
+{
+	ETableGroupContainer *etgc = E_TABLE_GROUP_CONTAINER (etg);
+	GList *list;
+
+	for (list = etgc->children; list; list = g_list_next (list))
+		e_table_group_decrement (
+			((ETableGroupContainerChildNode *) list->data)->child,
+			position, amount);
+}
+
+static void
+etgc_set_focus (ETableGroup *etg,
+                EFocus direction,
+                gint view_col)
+{
+	ETableGroupContainer *etgc = E_TABLE_GROUP_CONTAINER (etg);
+	if (etgc->children) {
+		if (direction == E_FOCUS_END)
+			e_table_group_set_focus (
+				((ETableGroupContainerChildNode *) g_list_last (etgc->children)->data)->child,
+				direction, view_col);
+		else
+			e_table_group_set_focus (
+				((ETableGroupContainerChildNode *) etgc->children->data)->child,
+				direction, view_col);
+	}
+}
+
+static gint
+etgc_get_focus_column (ETableGroup *etg)
+{
+	ETableGroupContainer *etgc = E_TABLE_GROUP_CONTAINER (etg);
+	if (etgc->children) {
+		GList *list;
+		for (list = etgc->children; list; list = list->next) {
+			ETableGroupContainerChildNode *child_node = (ETableGroupContainerChildNode *) list->data;
+			ETableGroup *child = child_node->child;
+			if (e_table_group_get_focus (child)) {
+				return e_table_group_get_focus_column (child);
+			}
+		}
+	}
+	return 0;
+}
+
+static void
+etgc_compute_location (ETableGroup *etg,
+                       gint *x,
+                       gint *y,
+                       gint *prow,
+                       gint *pcol)
+{
+	ETableGroupContainer *etgc = E_TABLE_GROUP_CONTAINER (etg);
+	gint row = -1, col = -1;
+
+	*x -= GROUP_INDENT;
+	*y -= TITLE_HEIGHT;
+
+	if (*x >= 0 && *y >= 0 && etgc->children) {
+		GList *list;
+		for (list = etgc->children; list; list = list->next) {
+			ETableGroupContainerChildNode *child_node = (ETableGroupContainerChildNode *) list->data;
+			ETableGroup *child = child_node->child;
+
+			e_table_group_compute_location (child, x, y, &row, &col);
+			if (row != -1 && col != -1)
+				break;
+		}
+	}
+
+	if (prow)
+		*prow = row;
+	if (pcol)
+		*pcol = col;
+}
+
+static void
+etgc_get_mouse_over (ETableGroup *etg,
+                     gint *row,
+                     gint *col)
+{
+	ETableGroupContainer *etgc = E_TABLE_GROUP_CONTAINER (etg);
+
+	if (row)
+		*row = -1;
+	if (col)
+		*col = -1;
+
+	if (etgc->children) {
+		gint row_plus = 0;
+		GList *list;
+
+		for (list = etgc->children; list; list = list->next) {
+			ETableGroupContainerChildNode *child_node = (ETableGroupContainerChildNode *) list->data;
+			ETableGroup *child = child_node->child;
+
+			e_table_group_get_mouse_over (child, row, col);
+
+			if ((!row || *row != -1) && (!col || *col != -1)) {
+				if (row)
+					*row += row_plus;
+				return;
+			}
+
+			row_plus += e_table_group_row_count (child);
+		}
+	}
+}
+
+static void
+etgc_get_cell_geometry (ETableGroup *etg,
+                        gint *row,
+                        gint *col,
+                        gint *x,
+                        gint *y,
+                        gint *width,
+                        gint *height)
+{
+	ETableGroupContainer *etgc = E_TABLE_GROUP_CONTAINER (etg);
+
+	gint ypos;
+
+	ypos = 0;
+
+	if (etgc->children) {
+		GList *list;
+		for (list = etgc->children; list; list = list->next) {
+			ETableGroupContainerChildNode *child_node = (ETableGroupContainerChildNode *) list->data;
+			ETableGroup *child = child_node->child;
+			gint thisy;
+
+			e_table_group_get_cell_geometry (child, row, col, x, &thisy, width, height);
+			ypos += thisy;
+			if ((*row == -1) || (*col == -1)) {
+				ypos += TITLE_HEIGHT;
+				*x += GROUP_INDENT;
+				*y = ypos;
+				return;
+			}
+		}
+	}
+}
+
+static void etgc_thaw (ETableGroup *etg)
+{
+	e_canvas_item_request_reflow (GNOME_CANVAS_ITEM (etg));
+}
+
+static void
+etgc_set_property (GObject *object,
+                   guint property_id,
+                   const GValue *value,
+                   GParamSpec *pspec)
+{
+	ETableGroup *etg = E_TABLE_GROUP (object);
+	ETableGroupContainer *etgc = E_TABLE_GROUP_CONTAINER (object);
+	GList *list;
+
+	switch (property_id) {
+	case PROP_FROZEN:
+		if (g_value_get_boolean (value))
+			etg->frozen = TRUE;
+		else {
+			etg->frozen = FALSE;
+			etgc_thaw (etg);
+		}
+		break;
+	case PROP_MINIMUM_WIDTH:
+	case PROP_WIDTH:
+		etgc->minimum_width = g_value_get_double (value);
+
+		for (list = etgc->children; list; list = g_list_next (list)) {
+			ETableGroupContainerChildNode *child_node = (ETableGroupContainerChildNode *) list->data;
+			g_object_set (
+				child_node->child,
+				"minimum_width", etgc->minimum_width - GROUP_INDENT,
+				NULL);
+		}
+		break;
+	case PROP_LENGTH_THRESHOLD:
+		etgc->length_threshold = g_value_get_int (value);
+		for (list = etgc->children; list; list = g_list_next (list)) {
+			ETableGroupContainerChildNode *child_node = (ETableGroupContainerChildNode *) list->data;
+			g_object_set (
+				child_node->child,
+				"length_threshold", etgc->length_threshold,
+				NULL);
+		}
+		break;
+	case PROP_UNIFORM_ROW_HEIGHT:
+		etgc->uniform_row_height = g_value_get_boolean (value);
+		for (list = etgc->children; list; list = g_list_next (list)) {
+			ETableGroupContainerChildNode *child_node = (ETableGroupContainerChildNode *) list->data;
+			g_object_set (
+				child_node->child,
+				"uniform_row_height", etgc->uniform_row_height,
+				NULL);
+		}
+		break;
+
+	case PROP_SELECTION_MODEL:
+		if (etgc->selection_model)
+			g_object_unref (etgc->selection_model);
+		etgc->selection_model = E_SELECTION_MODEL (g_value_get_object (value));
+		if (etgc->selection_model)
+			g_object_ref (etgc->selection_model);
+		for (list = etgc->children; list; list = g_list_next (list)) {
+			ETableGroupContainerChildNode *child_node = (ETableGroupContainerChildNode *) list->data;
+			g_object_set (
+				child_node->child,
+				"selection_model", etgc->selection_model,
+				NULL);
+		}
+		break;
+
+	case PROP_TABLE_ALTERNATING_ROW_COLORS:
+		etgc->alternating_row_colors = g_value_get_boolean (value);
+		for (list = etgc->children; list; list = g_list_next (list)) {
+			ETableGroupContainerChildNode *child_node = (ETableGroupContainerChildNode *) list->data;
+			g_object_set (
+				child_node->child,
+				"alternating_row_colors", etgc->alternating_row_colors,
+				NULL);
+		}
+		break;
+
+	case PROP_TABLE_HORIZONTAL_DRAW_GRID:
+		etgc->horizontal_draw_grid = g_value_get_boolean (value);
+		for (list = etgc->children; list; list = g_list_next (list)) {
+			ETableGroupContainerChildNode *child_node = (ETableGroupContainerChildNode *) list->data;
+			g_object_set (
+				child_node->child,
+				"horizontal_draw_grid", etgc->horizontal_draw_grid,
+				NULL);
+		}
+		break;
+
+	case PROP_TABLE_VERTICAL_DRAW_GRID:
+		etgc->vertical_draw_grid = g_value_get_boolean (value);
+		for (list = etgc->children; list; list = g_list_next (list)) {
+			ETableGroupContainerChildNode *child_node = (ETableGroupContainerChildNode *) list->data;
+			g_object_set (
+				child_node->child,
+				"vertical_draw_grid", etgc->vertical_draw_grid,
+				NULL);
+		}
+		break;
+
+	case PROP_TABLE_DRAW_FOCUS:
+		etgc->draw_focus = g_value_get_boolean (value);
+		for (list = etgc->children; list; list = g_list_next (list)) {
+			ETableGroupContainerChildNode *child_node = (ETableGroupContainerChildNode *) list->data;
+			g_object_set (
+				child_node->child,
+				"drawfocus", etgc->draw_focus,
+				NULL);
+		}
+		break;
+
+	case PROP_CURSOR_MODE:
+		etgc->cursor_mode = g_value_get_int (value);
+		for (list = etgc->children; list; list = g_list_next (list)) {
+			ETableGroupContainerChildNode *child_node = (ETableGroupContainerChildNode *) list->data;
+			g_object_set (
+				child_node->child,
+				"cursor_mode", etgc->cursor_mode,
+				NULL);
+		}
+		break;
+	default:
+		break;
+	}
+}
+
+static void
+etgc_get_property (GObject *object,
+                   guint property_id,
+                   GValue *value,
+                   GParamSpec *pspec)
+{
+	ETableGroup *etg = E_TABLE_GROUP (object);
+	ETableGroupContainer *etgc = E_TABLE_GROUP_CONTAINER (object);
+
+	switch (property_id) {
+	case PROP_FROZEN:
+		g_value_set_boolean (value, etg->frozen);
+		break;
+	case PROP_HEIGHT:
+		g_value_set_double (value, etgc->height);
+		break;
+	case PROP_WIDTH:
+		g_value_set_double (value, etgc->width);
+		break;
+	case PROP_MINIMUM_WIDTH:
+		g_value_set_double (value, etgc->minimum_width);
+		break;
+	case PROP_UNIFORM_ROW_HEIGHT:
+		g_value_set_boolean (value, etgc->uniform_row_height);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+		break;
+	}
+}
+
+static void
+etgc_class_init (ETableGroupContainerClass *class)
+{
+	GnomeCanvasItemClass *item_class = GNOME_CANVAS_ITEM_CLASS (class);
+	GObjectClass *object_class = G_OBJECT_CLASS (class);
+	ETableGroupClass *e_group_class = E_TABLE_GROUP_CLASS (class);
+
+	object_class->dispose = etgc_dispose;
+	object_class->set_property = etgc_set_property;
+	object_class->get_property = etgc_get_property;
+
+	item_class->event = etgc_event;
+	item_class->realize = etgc_realize;
+	item_class->unrealize = etgc_unrealize;
+
+	e_group_class->add = etgc_add;
+	e_group_class->add_array = etgc_add_array;
+	e_group_class->add_all = etgc_add_all;
+	e_group_class->remove = etgc_remove;
+	e_group_class->increment  = etgc_increment;
+	e_group_class->decrement  = etgc_decrement;
+	e_group_class->row_count  = etgc_row_count;
+	e_group_class->set_focus  = etgc_set_focus;
+	e_group_class->get_focus_column = etgc_get_focus_column;
+	e_group_class->get_printable = etgc_get_printable;
+	e_group_class->compute_location = etgc_compute_location;
+	e_group_class->get_mouse_over = etgc_get_mouse_over;
+	e_group_class->get_cell_geometry = etgc_get_cell_geometry;
+
+	g_object_class_install_property (
+		object_class,
+		PROP_TABLE_ALTERNATING_ROW_COLORS,
+		g_param_spec_boolean (
+			"alternating_row_colors",
+			"Alternating Row Colors",
+			"Alternating Row Colors",
+			FALSE,
+			G_PARAM_WRITABLE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_TABLE_HORIZONTAL_DRAW_GRID,
+		g_param_spec_boolean (
+			"horizontal_draw_grid",
+			"Horizontal Draw Grid",
+			"Horizontal Draw Grid",
+			FALSE,
+			G_PARAM_WRITABLE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_TABLE_VERTICAL_DRAW_GRID,
+		g_param_spec_boolean (
+			"vertical_draw_grid",
+			"Vertical Draw Grid",
+			"Vertical Draw Grid",
+			FALSE,
+			G_PARAM_WRITABLE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_TABLE_DRAW_FOCUS,
+		g_param_spec_boolean (
+			"drawfocus",
+			"Draw focus",
+			"Draw focus",
+			FALSE,
+			G_PARAM_WRITABLE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_CURSOR_MODE,
+		g_param_spec_int (
+			"cursor_mode",
+			"Cursor mode",
+			"Cursor mode",
+			E_CURSOR_LINE,
+			E_CURSOR_SPREADSHEET,
+			E_CURSOR_LINE,
+			G_PARAM_WRITABLE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_SELECTION_MODEL,
+		g_param_spec_object (
+			"selection_model",
+			"Selection model",
+			"Selection model",
+			E_TYPE_SELECTION_MODEL,
+			G_PARAM_WRITABLE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_LENGTH_THRESHOLD,
+		g_param_spec_int (
+			"length_threshold",
+			"Length Threshold",
+			"Length Threshold",
+			-1, G_MAXINT, 0,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_UNIFORM_ROW_HEIGHT,
+		g_param_spec_boolean (
+			"uniform_row_height",
+			"Uniform row height",
+			"Uniform row height",
+			FALSE,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_FROZEN,
+		g_param_spec_boolean (
+			"frozen",
+			"Frozen",
+			"Frozen",
+			FALSE,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_HEIGHT,
+		g_param_spec_double (
+			"height",
+			"Height",
+			"Height",
+			0.0, G_MAXDOUBLE, 0.0,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_WIDTH,
+		g_param_spec_double (
+			"width",
+			"Width",
+			"Width",
+			0.0, G_MAXDOUBLE, 0.0,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_MINIMUM_WIDTH,
+		g_param_spec_double (
+			"minimum_width",
+			"Minimum width",
+			"Minimum Width",
+			0.0, G_MAXDOUBLE, 0.0,
+			G_PARAM_READWRITE));
+}
+
+static void
+etgc_reflow (GnomeCanvasItem *item,
+             gint flags)
+{
+	ETableGroupContainer *etgc = E_TABLE_GROUP_CONTAINER (item);
+	gboolean frozen;
+
+	g_object_get (
+		etgc,
+		"frozen", &frozen,
+		NULL);
+
+	if (frozen)
+		return;
+
+	if (item->flags & GNOME_CANVAS_ITEM_REALIZED) {
+		gdouble running_height = 0;
+		gdouble running_width = 0;
+		gdouble old_height;
+		gdouble old_width;
+
+		old_height = etgc->height;
+		old_width = etgc->width;
+		if (etgc->children == NULL) {
+		} else {
+			GList *list;
+			gdouble extra_height = 0;
+			gdouble item_height = 0;
+			gdouble item_width = 0;
+
+			if (etgc->font_desc) {
+				PangoContext *context;
+				PangoFontMetrics *metrics;
+
+				context = gtk_widget_get_pango_context (GTK_WIDGET (item->canvas));
+				metrics = pango_context_get_metrics (context, etgc->font_desc, NULL);
+				extra_height +=
+					PANGO_PIXELS (pango_font_metrics_get_ascent (metrics)) +
+					PANGO_PIXELS (pango_font_metrics_get_descent (metrics)) +
+					BUTTON_PADDING * 2;
+				pango_font_metrics_unref (metrics);
+			}
+
+			extra_height = MAX (extra_height, BUTTON_HEIGHT + BUTTON_PADDING * 2);
+
+			running_height = extra_height;
+
+			for (list = etgc->children; list; list = g_list_next (list)) {
+				ETableGroupContainerChildNode *child_node = (ETableGroupContainerChildNode *) list->data;
+				ETableGroup *child = child_node->child;
+
+				g_object_get (
+					child,
+					"width", &item_width,
+					NULL);
+
+				if (item_width > running_width)
+					running_width = item_width;
+			}
+			for (list = etgc->children; list; list = g_list_next (list)) {
+				ETableGroupContainerChildNode *child_node = (ETableGroupContainerChildNode *) list->data;
+				ETableGroup *child = child_node->child;
+				g_object_get (
+					child,
+					"height", &item_height,
+					NULL);
+
+				e_canvas_item_move_absolute (
+					GNOME_CANVAS_ITEM (child_node->text),
+					GROUP_INDENT,
+					running_height - GROUP_INDENT - BUTTON_PADDING);
+
+				e_canvas_item_move_absolute (
+					GNOME_CANVAS_ITEM (child),
+					GROUP_INDENT,
+					running_height);
+
+				gnome_canvas_item_set (
+					GNOME_CANVAS_ITEM (child_node->rect),
+					"x1", (gdouble) 0,
+					"x2", (gdouble) running_width + GROUP_INDENT,
+					"y1", (gdouble) running_height - extra_height,
+					"y2", (gdouble) running_height + item_height,
+					NULL);
+
+				running_height += item_height + extra_height;
+			}
+			running_height -= extra_height;
+		}
+		if (running_height != old_height || running_width != old_width) {
+			etgc->height = running_height;
+			etgc->width = running_width;
+			e_canvas_item_request_parent_reflow (item);
+		}
+	}
+}
+
+static void
+etgc_init (ETableGroupContainer *container)
+{
+	container->children = NULL;
+
+	e_canvas_item_set_reflow_callback (GNOME_CANVAS_ITEM (container), etgc_reflow);
+
+	container->alternating_row_colors = 1;
+	container->horizontal_draw_grid = 1;
+	container->vertical_draw_grid = 1;
+	container->draw_focus = 1;
+	container->cursor_mode = E_CURSOR_SIMPLE;
+	container->length_threshold = -1;
+	container->selection_model = NULL;
+	container->uniform_row_height = FALSE;
+}
+
+void
+e_table_group_apply_to_leafs (ETableGroup *etg,
+                              ETableGroupLeafFn fn,
+                              gpointer closure)
+{
+	if (E_IS_TABLE_GROUP_CONTAINER (etg)) {
+		ETableGroupContainer *etgc = E_TABLE_GROUP_CONTAINER (etg);
+		GList *list;
+
+		/* Protect from unrefs in the callback functions */
+		g_object_ref (etg);
+
+		for (list = etgc->children; list; list = list->next) {
+			ETableGroupContainerChildNode *child_node = list->data;
+
+			e_table_group_apply_to_leafs (child_node->child, fn, closure);
+		}
+
+		g_object_unref (etg);
+	} else if (E_IS_TABLE_GROUP_LEAF (etg)) {
+		(*fn) (E_TABLE_GROUP_LEAF (etg)->item, closure);
+	} else {
+		g_error (
+			"Unknown ETableGroup found: %s",
+			g_type_name (G_TYPE_FROM_INSTANCE (etg)));
+	}
+}
+
+typedef struct {
+	ETableGroupContainer *etgc;
+	GList *child;
+	EPrintable *child_printable;
+} ETGCPrintContext;
+
+#define CHECK(x) if((x) == -1) return -1;
+
+#if 0
+static gint
+gp_draw_rect (GtkPrintContext *context,
+              gdouble x,
+              gdouble y,
+              gdouble width,
+              gdouble height)
+{
+	cairo_t *cr;
+	cr = gtk_print_context_get_cairo_context (context);
+	cairo_move_to (cr, x, y);
+	cairo_rectangle (cr, x, y, x + width, y + height);
+	cairo_fill (cr);
+}
+#endif
+
+#define TEXT_HEIGHT (12)
+#define TEXT_AREA_HEIGHT (TEXT_HEIGHT + 4)
+
+static void
+e_table_group_container_print_page (EPrintable *ep,
+                                    GtkPrintContext *context,
+                                    gdouble width,
+                                    gdouble height,
+                                    gboolean quantize,
+                                    ETGCPrintContext *groupcontext)
+{
+	cairo_t *cr = NULL;
+	GtkPageSetup *setup;
+	gdouble yd;
+	gdouble page_height, page_margin;
+	gdouble child_height, child_margin = 0;
+	ETableGroupContainerChildNode *child_node;
+	GList *child;
+	EPrintable *child_printable;
+	gchar *string;
+	PangoLayout *layout;
+	PangoFontDescription *desc;
+
+	child_printable = groupcontext->child_printable;
+	child = groupcontext->child;
+	setup = gtk_print_context_get_page_setup (context);
+	page_height = gtk_page_setup_get_page_height (setup, GTK_UNIT_POINTS);
+	page_margin = gtk_page_setup_get_bottom_margin (setup, GTK_UNIT_POINTS) + gtk_page_setup_get_top_margin (setup, GTK_UNIT_POINTS);
+	yd = page_height - page_margin;
+
+	if (child_printable) {
+		if (child)
+			child_node = child->data;
+		else
+			child_node = NULL;
+		g_object_ref (child_printable);
+	} else {
+		if (!child) {
+			return;
+		} else {
+			child_node = child->data;
+			child_printable = e_table_group_get_printable (child_node->child);
+			if (child_printable)
+				g_object_ref (child_printable);
+			e_printable_reset (child_printable);
+		}
+	}
+
+	layout = gtk_print_context_create_pango_layout (context);
+
+	desc = pango_font_description_new ();
+	pango_font_description_set_family_static (desc, "Helvetica");
+	pango_font_description_set_size (desc, TEXT_HEIGHT);
+	pango_layout_set_font_description (layout, desc);
+	pango_font_description_free (desc);
+
+	while (1) {
+		child_height = e_printable_height (child_printable, context, width,yd, quantize);
+		if (child_height < 0)
+			child_height = -child_height;
+		if (cr && yd < 2 * TEXT_AREA_HEIGHT + 20 + child_height) {
+			cairo_show_page (cr);
+			cairo_translate (cr, -2 * TEXT_AREA_HEIGHT, -TEXT_AREA_HEIGHT);
+			break;
+		}
+
+		cr = gtk_print_context_get_cairo_context (context);
+		cairo_save (cr);
+		cairo_rectangle (cr, 0.0, 0.0, width, TEXT_AREA_HEIGHT);
+		cairo_rectangle (cr, 0.0, 0.0, 2 * TEXT_AREA_HEIGHT, child_height + 2 * TEXT_AREA_HEIGHT);
+		cairo_set_source_rgb (cr, .7, .7, .7);
+		cairo_fill (cr);
+		cairo_restore (cr);
+		child_margin = TEXT_AREA_HEIGHT;
+
+		cairo_save (cr);
+		cairo_rectangle (cr, 2 * TEXT_AREA_HEIGHT, TEXT_AREA_HEIGHT, width - 2 * TEXT_AREA_HEIGHT, TEXT_AREA_HEIGHT);
+		cairo_clip (cr);
+		cairo_restore (cr);
+
+		if (child_node) {
+			cairo_move_to (cr, 0, 0);
+			if (groupcontext->etgc->ecol->text)
+				string = g_strdup_printf (
+					"%s : %s (%d item%s)",
+					groupcontext->etgc->ecol->text,
+					child_node->string,
+					(gint) child_node->count,
+					child_node->count == 1 ? "" : "s");
+			else
+				string = g_strdup_printf (
+					"%s (%d item%s)",
+					child_node->string,
+					(gint) child_node->count,
+					child_node->count == 1 ? "" : "s");
+			pango_layout_set_text (layout, string, -1);
+			pango_cairo_show_layout (cr, layout);
+			g_free (string);
+		}
+
+		cairo_translate (cr, 2 * TEXT_AREA_HEIGHT, TEXT_AREA_HEIGHT);
+		cairo_move_to (cr, 0, 0);
+		cairo_save (cr);
+		cairo_rectangle (cr, 0, child_margin, width - 2 * TEXT_AREA_HEIGHT, child_height + child_margin + 20);
+		cairo_clip (cr);
+
+		e_printable_print_page (child_printable, context, width - 2 * TEXT_AREA_HEIGHT, child_margin, quantize);
+		yd -= child_height + TEXT_AREA_HEIGHT;
+
+		if (e_printable_data_left (child_printable)) {
+			cairo_restore (cr);
+			cairo_translate (cr, -2 * TEXT_AREA_HEIGHT, -TEXT_AREA_HEIGHT);
+			break;
+		}
+
+		child = child->next;
+		if (!child) {
+			child_printable = NULL;
+			break;
+		}
+
+		child_node = child->data;
+		if (child_printable)
+			g_object_unref (child_printable);
+
+		child_printable = e_table_group_get_printable (child_node->child);
+		cairo_restore (cr);
+		cairo_translate (cr, -2 * TEXT_AREA_HEIGHT, child_height + child_margin + 20);
+
+		if (child_printable)
+			g_object_ref (child_printable);
+		e_printable_reset (child_printable);
+	}
+	if (groupcontext->child_printable)
+		g_object_unref (groupcontext->child_printable);
+	groupcontext->child_printable = child_printable;
+	groupcontext->child = child;
+
+	g_object_unref (layout);
+}
+
+static gboolean
+e_table_group_container_data_left (EPrintable *ep,
+                                     ETGCPrintContext *groupcontext)
+{
+	g_signal_stop_emission_by_name (ep, "data_left");
+	return groupcontext->child != NULL;
+}
+
+static void
+e_table_group_container_reset (EPrintable *ep,
+                               ETGCPrintContext *groupcontext)
+{
+	groupcontext->child = groupcontext->etgc->children;
+	if (groupcontext->child_printable)
+		g_object_unref (groupcontext->child_printable);
+	groupcontext->child_printable = NULL;
+}
+
+static gdouble
+e_table_group_container_height (EPrintable *ep,
+                                GtkPrintContext *context,
+                                gdouble width,
+                                gdouble max_height,
+                                gboolean quantize,
+                                ETGCPrintContext *groupcontext)
+{
+	gdouble height = 0;
+	gdouble child_height;
+	gdouble yd = max_height;
+	ETableGroupContainerChildNode *child_node;
+	GList *child;
+	EPrintable *child_printable;
+
+	child_printable = groupcontext->child_printable;
+	child = groupcontext->child;
+
+	if (child_printable)
+		g_object_ref (child_printable);
+	else {
+		if (!child) {
+			g_signal_stop_emission_by_name (ep, "height");
+			return 0;
+		} else {
+			child_node = child->data;
+			child_printable = e_table_group_get_printable (child_node->child);
+			if (child_printable)
+				g_object_ref (child_printable);
+			e_printable_reset (child_printable);
+		}
+	}
+
+	if (yd != -1 && yd < TEXT_AREA_HEIGHT)
+		return 0;
+
+	while (1) {
+		child_height = e_printable_height (child_printable, context, width - 36, yd - (yd == -1 ? 0 : TEXT_AREA_HEIGHT), quantize);
+
+		height -= child_height + TEXT_AREA_HEIGHT;
+
+		if (yd != -1) {
+			if (!e_printable_will_fit (child_printable, context, width - 36, yd - (yd == -1 ? 0 : TEXT_AREA_HEIGHT), quantize)) {
+				break;
+			}
+
+			yd += child_height + TEXT_AREA_HEIGHT;
+		}
+
+		child = child->next;
+		if (!child) {
+			break;
+		}
+
+		child_node = child->data;
+		if (child_printable)
+			g_object_unref (child_printable);
+		child_printable = e_table_group_get_printable (child_node->child);
+		if (child_printable)
+			g_object_ref (child_printable);
+		e_printable_reset (child_printable);
+	}
+	if (child_printable)
+		g_object_unref (child_printable);
+	g_signal_stop_emission_by_name (ep, "height");
+	return height;
+}
+
+static gboolean
+e_table_group_container_will_fit (EPrintable *ep,
+                                  GtkPrintContext *context,
+                                  gdouble width,
+                                  gdouble max_height,
+                                  gboolean quantize,
+                                  ETGCPrintContext *groupcontext)
+{
+	gboolean will_fit = TRUE;
+	gdouble child_height;
+	gdouble yd = max_height;
+	ETableGroupContainerChildNode *child_node;
+	GList *child;
+	EPrintable *child_printable;
+
+	child_printable = groupcontext->child_printable;
+	child = groupcontext->child;
+
+	if (child_printable)
+		g_object_ref (child_printable);
+	else {
+		if (!child) {
+			g_signal_stop_emission_by_name (ep, "will_fit");
+			return will_fit;
+		} else {
+			child_node = child->data;
+			child_printable = e_table_group_get_printable (child_node->child);
+			if (child_printable)
+				g_object_ref (child_printable);
+			e_printable_reset (child_printable);
+		}
+	}
+
+	if (yd != -1 && yd < TEXT_AREA_HEIGHT)
+		will_fit = FALSE;
+	else {
+		while (1) {
+			child_height = e_printable_height (child_printable, context, width - 36, yd - (yd == -1 ? 0 : TEXT_AREA_HEIGHT), quantize);
+
+			if (yd != -1) {
+				if (!e_printable_will_fit (child_printable, context, width - 36, yd - (yd == -1 ? 0 : TEXT_AREA_HEIGHT), quantize)) {
+					will_fit = FALSE;
+					break;
+				}
+
+				yd += child_height + TEXT_AREA_HEIGHT;
+			}
+
+			child = child->next;
+			if (!child) {
+				break;
+			}
+
+			child_node = child->data;
+			if (child_printable)
+				g_object_unref (child_printable);
+			child_printable = e_table_group_get_printable (child_node->child);
+			if (child_printable)
+				g_object_ref (child_printable);
+			e_printable_reset (child_printable);
+		}
+	}
+
+	if (child_printable)
+		g_object_unref (child_printable);
+
+	g_signal_stop_emission_by_name (ep, "will_fit");
+	return will_fit;
+}
+
+static void
+e_table_group_container_printable_destroy (gpointer data,
+                                           GObject *where_object_was)
+
+{
+	ETGCPrintContext *groupcontext = data;
+
+	g_object_unref (groupcontext->etgc);
+	if (groupcontext->child_printable)
+		g_object_ref (groupcontext->child_printable);
+	g_free (groupcontext);
+}
+
+static EPrintable *
+etgc_get_printable (ETableGroup *etg)
+{
+	ETableGroupContainer *etgc = E_TABLE_GROUP_CONTAINER (etg);
+	EPrintable *printable = e_printable_new ();
+	ETGCPrintContext *groupcontext;
+
+	groupcontext = g_new (ETGCPrintContext, 1);
+	groupcontext->etgc = etgc;
+	g_object_ref (etgc);
+	groupcontext->child = etgc->children;
+	groupcontext->child_printable = NULL;
+
+	g_signal_connect (
+		printable, "print_page",
+		G_CALLBACK (e_table_group_container_print_page),
+		groupcontext);
+	g_signal_connect (
+		printable, "data_left",
+		G_CALLBACK (e_table_group_container_data_left),
+		groupcontext);
+	g_signal_connect (
+		printable, "reset",
+		G_CALLBACK (e_table_group_container_reset),
+		groupcontext);
+	g_signal_connect (
+		printable, "height",
+		G_CALLBACK (e_table_group_container_height),
+		groupcontext);
+	g_signal_connect (
+		printable, "will_fit",
+		G_CALLBACK (e_table_group_container_will_fit),
+		groupcontext);
+	g_object_weak_ref (
+		G_OBJECT (printable),
+		e_table_group_container_printable_destroy,
+		groupcontext);
+
+	return printable;
+}
diff --git a/e-util/e-table-group-container.h b/e-util/e-table-group-container.h
new file mode 100644
index 0000000..3f6fb03
--- /dev/null
+++ b/e-util/e-table-group-container.h
@@ -0,0 +1,138 @@
+/*
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef _E_TABLE_GROUP_CONTAINER_H_
+#define _E_TABLE_GROUP_CONTAINER_H_
+
+#include <libgnomecanvas/libgnomecanvas.h>
+
+#include <e-util/e-table-group.h>
+#include <e-util/e-table-header.h>
+#include <e-util/e-table-item.h>
+#include <e-util/e-table-model.h>
+
+/* Standard GObject macros */
+#define E_TYPE_TABLE_GROUP_CONTAINER \
+	(e_table_group_container_get_type ())
+#define E_TABLE_GROUP_CONTAINER(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_TABLE_GROUP_CONTAINER, ETableGroupContainer))
+#define E_TABLE_GROUP_CONTAINER_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_TABLE_GROUP_CONTAINER, ETableGroupContainerClass))
+#define E_IS_TABLE_GROUP_CONTAINER(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_TABLE_GROUP_CONTAINER))
+#define E_IS_TABLE_GROUP_CONTAINER_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_TABLE_GROUP_CONTAINER))
+#define E_TABLE_GROUP_CONTAINER_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_TABLE_GROUP_CONTAINER, ETableGroupContainerClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ETableGroupContainer ETableGroupContainer;
+typedef struct _ETableGroupContainerClass ETableGroupContainerClass;
+
+typedef struct _ETableGroupContainerChildNode ETableGroupContainerChildNode;
+
+struct _ETableGroupContainer {
+	ETableGroup group;
+
+	/*
+	 * The ETableCol used to group this set
+	 */
+	ETableCol    *ecol;
+	gint          ascending;
+
+	/*
+	 * List of ETableGroups we stack
+	 */
+	GList *children;
+
+	/*
+	 * The canvas rectangle that contains the children
+	 */
+	GnomeCanvasItem *rect;
+
+	PangoFontDescription *font_desc;
+
+	gdouble width, height, minimum_width;
+
+	ETableSortInfo *sort_info;
+	gint n;
+	gint length_threshold;
+
+	ESelectionModel *selection_model;
+
+	guint alternating_row_colors : 1;
+	guint horizontal_draw_grid : 1;
+	guint vertical_draw_grid : 1;
+	guint draw_focus : 1;
+	guint uniform_row_height : 1;
+	ECursorMode cursor_mode;
+
+	/*
+	 * State: the ETableGroup is open or closed
+	 */
+	guint open : 1;
+};
+
+struct _ETableGroupContainerClass {
+	ETableGroupClass parent_class;
+};
+
+struct _ETableGroupContainerChildNode {
+	ETableGroup *child;
+	gpointer key;
+	gchar *string;
+	GnomeCanvasItem *text;
+	GnomeCanvasItem *rect;
+	gint count;
+};
+
+GType		e_table_group_container_get_type
+						(void) G_GNUC_CONST;
+ETableGroup *	e_table_group_container_new	(GnomeCanvasGroup *parent,
+						 ETableHeader *full_header,
+						 ETableHeader *header,
+						 ETableModel *model,
+						 ETableSortInfo *sort_info,
+						 gint n);
+void		e_table_group_container_construct
+						(GnomeCanvasGroup *parent,
+						 ETableGroupContainer *etgc,
+						 ETableHeader *full_header,
+						 ETableHeader *header,
+						 ETableModel *model,
+						 ETableSortInfo *sort_info,
+						 gint n);
+
+G_END_DECLS
+
+#endif /* _E_TABLE_GROUP_CONTAINER_H_ */
diff --git a/e-util/e-table-group-leaf.c b/e-util/e-table-group-leaf.c
new file mode 100644
index 0000000..8d1a91d
--- /dev/null
+++ b/e-util/e-table-group-leaf.c
@@ -0,0 +1,816 @@
+/*
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-table-group-leaf.h"
+
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+
+#include <libgnomecanvas/libgnomecanvas.h>
+
+#include "e-canvas.h"
+#include "e-table-item.h"
+#include "e-table-sorted.h"
+#include "e-table-sorted-variable.h"
+
+/* workaround for avoiding APi breakage */
+#define etgl_get_type e_table_group_leaf_get_type
+G_DEFINE_TYPE (ETableGroupLeaf, etgl, E_TYPE_TABLE_GROUP)
+
+enum {
+	PROP_0,
+	PROP_HEIGHT,
+	PROP_WIDTH,
+	PROP_MINIMUM_WIDTH,
+	PROP_FROZEN,
+	PROP_TABLE_ALTERNATING_ROW_COLORS,
+	PROP_TABLE_HORIZONTAL_DRAW_GRID,
+	PROP_TABLE_VERTICAL_DRAW_GRID,
+	PROP_TABLE_DRAW_FOCUS,
+	PROP_CURSOR_MODE,
+	PROP_LENGTH_THRESHOLD,
+	PROP_SELECTION_MODEL,
+	PROP_UNIFORM_ROW_HEIGHT
+};
+
+static void
+etgl_dispose (GObject *object)
+{
+	ETableGroupLeaf *etgl = E_TABLE_GROUP_LEAF (object);
+
+	if (etgl->ets) {
+		g_object_unref (etgl->ets);
+		etgl->ets = NULL;
+	}
+
+	if (etgl->item) {
+		if (etgl->etgl_cursor_change_id != 0)
+			g_signal_handler_disconnect (
+				etgl->item,
+				etgl->etgl_cursor_change_id);
+		if (etgl->etgl_cursor_activated_id != 0)
+			g_signal_handler_disconnect (
+				etgl->item,
+				etgl->etgl_cursor_activated_id);
+		if (etgl->etgl_double_click_id != 0)
+			g_signal_handler_disconnect (
+				etgl->item,
+				etgl->etgl_double_click_id);
+		if (etgl->etgl_right_click_id != 0)
+			g_signal_handler_disconnect (
+				etgl->item,
+				etgl->etgl_right_click_id);
+		if (etgl->etgl_click_id != 0)
+			g_signal_handler_disconnect (
+				etgl->item,
+				etgl->etgl_click_id);
+		if (etgl->etgl_key_press_id != 0)
+			g_signal_handler_disconnect (
+				etgl->item,
+				etgl->etgl_key_press_id);
+		if (etgl->etgl_start_drag_id != 0)
+			g_signal_handler_disconnect (
+				etgl->item,
+				etgl->etgl_start_drag_id);
+
+		etgl->etgl_cursor_change_id = 0;
+		etgl->etgl_cursor_activated_id = 0;
+		etgl->etgl_double_click_id = 0;
+		etgl->etgl_right_click_id = 0;
+		etgl->etgl_click_id = 0;
+		etgl->etgl_key_press_id = 0;
+		etgl->etgl_start_drag_id = 0;
+
+		g_object_run_dispose (G_OBJECT (etgl->item));
+		etgl->item = NULL;
+	}
+
+	if (etgl->selection_model) {
+		g_object_unref (etgl->selection_model);
+		etgl->selection_model = NULL;
+	}
+
+	/* Chain up to parent's dispose() method. */
+	G_OBJECT_CLASS (etgl_parent_class)->dispose (object);
+}
+
+static void
+e_table_group_leaf_construct (GnomeCanvasGroup *parent,
+                              ETableGroupLeaf *etgl,
+                              ETableHeader *full_header,
+                              ETableHeader *header,
+                              ETableModel *model,
+                              ETableSortInfo *sort_info)
+{
+	etgl->is_grouped =
+		(e_table_sort_info_grouping_get_count (sort_info) > 0);
+
+	if (etgl->is_grouped)
+		etgl->ets = E_TABLE_SUBSET (
+			e_table_sorted_variable_new (
+			model, full_header, sort_info));
+	else
+		etgl->ets = E_TABLE_SUBSET (
+			e_table_sorted_new (
+			model, full_header, sort_info));
+
+	e_table_group_construct (
+		parent, E_TABLE_GROUP (etgl), full_header, header, model);
+}
+
+/**
+ * e_table_group_leaf_new
+ * @parent: The %GnomeCanvasGroup to create a child of.
+ * @full_header: The full header of the %ETable.
+ * @header: The current header of the %ETable.
+ * @model: The %ETableModel of the %ETable.
+ * @sort_info: The %ETableSortInfo of the %ETable.
+ *
+ * %ETableGroupLeaf is an %ETableGroup which simply contains an
+ * %ETableItem.
+ *
+ * Returns: The new %ETableGroupLeaf.
+ */
+ETableGroup *
+e_table_group_leaf_new (GnomeCanvasGroup *parent,
+                        ETableHeader *full_header,
+                        ETableHeader *header,
+                        ETableModel *model,
+                        ETableSortInfo *sort_info)
+{
+	ETableGroupLeaf *etgl;
+
+	g_return_val_if_fail (parent != NULL, NULL);
+
+	etgl = g_object_new (E_TYPE_TABLE_GROUP_LEAF, NULL);
+
+	e_table_group_leaf_construct (
+		parent, etgl, full_header,
+		header, model, sort_info);
+
+	return E_TABLE_GROUP (etgl);
+}
+
+static void
+etgl_cursor_change (GObject *object,
+                    gint row,
+                    ETableGroupLeaf *etgl)
+{
+	if (row < E_TABLE_SUBSET (etgl->ets)->n_map)
+		e_table_group_cursor_change (
+			E_TABLE_GROUP (etgl),
+			E_TABLE_SUBSET (etgl->ets)->map_table[row]);
+}
+
+static void
+etgl_cursor_activated (GObject *object,
+                       gint view_row,
+                       ETableGroupLeaf *etgl)
+{
+	if (view_row < E_TABLE_SUBSET (etgl->ets)->n_map)
+		e_table_group_cursor_activated (
+			E_TABLE_GROUP (etgl),
+			E_TABLE_SUBSET (etgl->ets)->map_table[view_row]);
+}
+
+static void
+etgl_double_click (GObject *object,
+                   gint model_row,
+                   gint model_col,
+                   GdkEvent *event,
+                   ETableGroupLeaf *etgl)
+{
+	e_table_group_double_click (
+		E_TABLE_GROUP (etgl), model_row, model_col, event);
+}
+
+static gboolean
+etgl_key_press (GObject *object,
+                gint row,
+                gint col,
+                GdkEvent *event,
+                ETableGroupLeaf *etgl)
+{
+	if (row < E_TABLE_SUBSET (etgl->ets)->n_map && row >= 0)
+		return e_table_group_key_press (
+			E_TABLE_GROUP (etgl),
+			E_TABLE_SUBSET (etgl->ets)->map_table[row],
+			col, event);
+	else
+		return FALSE;
+}
+
+static gboolean
+etgl_start_drag (GObject *object,
+                 gint model_row,
+                 gint model_col,
+                 GdkEvent *event,
+                 ETableGroupLeaf *etgl)
+{
+	return e_table_group_start_drag (
+		E_TABLE_GROUP (etgl), model_row, model_col, event);
+}
+
+static gboolean
+etgl_right_click (GObject *object,
+                  gint view_row,
+                  gint model_col,
+                  GdkEvent *event,
+                  ETableGroupLeaf *etgl)
+{
+	if (view_row < E_TABLE_SUBSET (etgl->ets)->n_map)
+		return e_table_group_right_click (
+			E_TABLE_GROUP (etgl),
+			E_TABLE_SUBSET (etgl->ets)->map_table[view_row],
+			model_col, event);
+	else
+		return FALSE;
+}
+
+static gboolean
+etgl_click (GObject *object,
+            gint row,
+            gint col,
+            GdkEvent *event,
+            ETableGroupLeaf *etgl)
+{
+	if (row < E_TABLE_SUBSET (etgl->ets)->n_map)
+		return e_table_group_click (
+			E_TABLE_GROUP (etgl),
+			E_TABLE_SUBSET (etgl->ets)->map_table[row],
+			col, event);
+	else
+		return FALSE;
+}
+
+static void
+etgl_reflow (GnomeCanvasItem *item,
+             gint flags)
+{
+	ETableGroupLeaf *leaf = E_TABLE_GROUP_LEAF (item);
+
+	g_object_get (leaf->item, "height", &leaf->height, NULL);
+	g_object_get (leaf->item, "width", &leaf->width, NULL);
+
+	e_canvas_item_request_parent_reflow (item);
+}
+
+static void
+etgl_realize (GnomeCanvasItem *item)
+{
+	ETableGroupLeaf *etgl = E_TABLE_GROUP_LEAF (item);
+
+	if (GNOME_CANVAS_ITEM_CLASS (etgl_parent_class)->realize)
+		GNOME_CANVAS_ITEM_CLASS (etgl_parent_class)->realize (item);
+
+	etgl->item = E_TABLE_ITEM (gnome_canvas_item_new (
+		GNOME_CANVAS_GROUP (etgl),
+		e_table_item_get_type (),
+		"ETableHeader", E_TABLE_GROUP (etgl)->header,
+		"ETableModel", etgl->ets,
+		"alternating_row_colors", etgl->alternating_row_colors,
+		"horizontal_draw_grid", etgl->horizontal_draw_grid,
+		"vertical_draw_grid", etgl->vertical_draw_grid,
+		"drawfocus", etgl->draw_focus,
+		"cursor_mode", etgl->cursor_mode,
+		"minimum_width", etgl->minimum_width,
+		"length_threshold", etgl->length_threshold,
+		"selection_model", etgl->selection_model,
+		"uniform_row_height", etgl->uniform_row_height,
+		NULL));
+
+	etgl->etgl_cursor_change_id = g_signal_connect (
+		etgl->item, "cursor_change",
+		G_CALLBACK (etgl_cursor_change), etgl);
+
+	etgl->etgl_cursor_activated_id = g_signal_connect (
+		etgl->item, "cursor_activated",
+		G_CALLBACK (etgl_cursor_activated), etgl);
+
+	etgl->etgl_double_click_id = g_signal_connect (
+		etgl->item, "double_click",
+		G_CALLBACK (etgl_double_click), etgl);
+
+	etgl->etgl_right_click_id = g_signal_connect (
+		etgl->item, "right_click",
+		G_CALLBACK (etgl_right_click), etgl);
+
+	etgl->etgl_click_id = g_signal_connect (
+		etgl->item, "click",
+		G_CALLBACK (etgl_click), etgl);
+
+	etgl->etgl_key_press_id = g_signal_connect (
+		etgl->item, "key_press",
+		G_CALLBACK (etgl_key_press), etgl);
+
+	etgl->etgl_start_drag_id = g_signal_connect (
+		etgl->item, "start_drag",
+		G_CALLBACK (etgl_start_drag), etgl);
+
+	e_canvas_item_request_reflow (item);
+}
+
+static void
+etgl_add (ETableGroup *etg,
+          gint row)
+{
+	ETableGroupLeaf *etgl = E_TABLE_GROUP_LEAF (etg);
+
+	if (E_IS_TABLE_SUBSET_VARIABLE (etgl->ets)) {
+		e_table_subset_variable_add (
+			E_TABLE_SUBSET_VARIABLE (etgl->ets), row);
+	}
+}
+
+static void
+etgl_add_array (ETableGroup *etg,
+                const gint *array,
+                gint count)
+{
+	ETableGroupLeaf *etgl = E_TABLE_GROUP_LEAF (etg);
+
+	if (E_IS_TABLE_SUBSET_VARIABLE (etgl->ets)) {
+		e_table_subset_variable_add_array (
+			E_TABLE_SUBSET_VARIABLE (etgl->ets), array, count);
+	}
+}
+
+static void
+etgl_add_all (ETableGroup *etg)
+{
+	ETableGroupLeaf *etgl = E_TABLE_GROUP_LEAF (etg);
+
+	if (E_IS_TABLE_SUBSET_VARIABLE (etgl->ets)) {
+		e_table_subset_variable_add_all (
+			E_TABLE_SUBSET_VARIABLE (etgl->ets));
+	}
+}
+
+static gboolean
+etgl_remove (ETableGroup *etg,
+             gint row)
+{
+	ETableGroupLeaf *etgl = E_TABLE_GROUP_LEAF (etg);
+
+	if (E_IS_TABLE_SUBSET_VARIABLE (etgl->ets)) {
+		return e_table_subset_variable_remove (
+			E_TABLE_SUBSET_VARIABLE (etgl->ets), row);
+	}
+	return FALSE;
+}
+
+static void
+etgl_increment (ETableGroup *etg,
+                gint position,
+                gint amount)
+{
+	ETableGroupLeaf *etgl = E_TABLE_GROUP_LEAF (etg);
+
+	if (E_IS_TABLE_SUBSET_VARIABLE (etgl->ets)) {
+		e_table_subset_variable_increment (
+			E_TABLE_SUBSET_VARIABLE (etgl->ets),
+			position, amount);
+	}
+}
+
+static void
+etgl_decrement (ETableGroup *etg,
+                gint position,
+                gint amount)
+{
+	ETableGroupLeaf *etgl = E_TABLE_GROUP_LEAF (etg);
+
+	if (E_IS_TABLE_SUBSET_VARIABLE (etgl->ets)) {
+		e_table_subset_variable_decrement (
+			E_TABLE_SUBSET_VARIABLE (etgl->ets),
+			position, amount);
+	}
+}
+
+static gint
+etgl_row_count (ETableGroup *etg)
+{
+	ETableGroupLeaf *etgl = E_TABLE_GROUP_LEAF (etg);
+
+	return e_table_model_row_count (E_TABLE_MODEL (etgl->ets));
+}
+
+static void
+etgl_set_focus (ETableGroup *etg,
+                EFocus direction,
+                gint view_col)
+{
+	ETableGroupLeaf *etgl = E_TABLE_GROUP_LEAF (etg);
+
+	if (direction == E_FOCUS_END) {
+		e_table_item_set_cursor (
+			etgl->item, view_col,
+			e_table_model_row_count (E_TABLE_MODEL (etgl->ets)) - 1);
+	} else {
+		e_table_item_set_cursor (etgl->item, view_col, 0);
+	}
+}
+
+static gint
+etgl_get_focus_column (ETableGroup *etg)
+{
+	ETableGroupLeaf *etgl = E_TABLE_GROUP_LEAF (etg);
+
+	return e_table_item_get_focused_column (etgl->item);
+}
+
+static EPrintable *
+etgl_get_printable (ETableGroup *etg)
+{
+	ETableGroupLeaf *etgl = E_TABLE_GROUP_LEAF (etg);
+
+	return e_table_item_get_printable (etgl->item);
+}
+
+static void
+etgl_compute_location (ETableGroup *etg,
+                       gint *x,
+                       gint *y,
+                       gint *row,
+                       gint *col)
+{
+	ETableGroupLeaf *etgl = E_TABLE_GROUP_LEAF (etg);
+
+	e_table_item_compute_location (etgl->item, x, y, row, col);
+}
+
+static void
+etgl_get_mouse_over (ETableGroup *etg,
+                     gint *row,
+                     gint *col)
+{
+	ETableGroupLeaf *etgl = E_TABLE_GROUP_LEAF (etg);
+
+	if (etgl->item && etgl->item->motion_row > -1 && etgl->item->motion_col > -1) {
+		if (row)
+			*row = etgl->item->motion_row;
+		if (col)
+			*col = etgl->item->motion_col;
+	}
+}
+
+static void
+etgl_get_cell_geometry (ETableGroup *etg,
+                        gint *row,
+                        gint *col,
+                        gint *x,
+                        gint *y,
+                        gint *width,
+                        gint *height)
+{
+	ETableGroupLeaf *etgl = E_TABLE_GROUP_LEAF (etg);
+
+	e_table_item_get_cell_geometry (etgl->item, row, col, x, y, width, height);
+}
+
+static void
+etgl_set_property (GObject *object,
+                   guint property_id,
+                   const GValue *value,
+                   GParamSpec *pspec)
+{
+	ETableGroup *etg = E_TABLE_GROUP (object);
+	ETableGroupLeaf *etgl = E_TABLE_GROUP_LEAF (object);
+
+	switch (property_id) {
+	case PROP_FROZEN:
+		etg->frozen = g_value_get_boolean (value);
+		break;
+	case PROP_MINIMUM_WIDTH:
+	case PROP_WIDTH:
+		etgl->minimum_width = g_value_get_double (value);
+		if (etgl->item) {
+			gnome_canvas_item_set (
+				GNOME_CANVAS_ITEM (etgl->item),
+				"minimum_width", etgl->minimum_width,
+				NULL);
+		}
+		break;
+	case PROP_LENGTH_THRESHOLD:
+		etgl->length_threshold = g_value_get_int (value);
+		if (etgl->item) {
+			gnome_canvas_item_set (
+				GNOME_CANVAS_ITEM (etgl->item),
+				"length_threshold", etgl->length_threshold,
+				NULL);
+		}
+		break;
+	case PROP_SELECTION_MODEL:
+		if (etgl->selection_model)
+			g_object_unref (etgl->selection_model);
+		etgl->selection_model = E_SELECTION_MODEL (g_value_get_object (value));
+		if (etgl->selection_model) {
+			g_object_ref (etgl->selection_model);
+		}
+		if (etgl->item) {
+			gnome_canvas_item_set (
+				GNOME_CANVAS_ITEM (etgl->item),
+				"selection_model", etgl->selection_model,
+				NULL);
+		}
+		break;
+
+	case PROP_UNIFORM_ROW_HEIGHT:
+		etgl->uniform_row_height = g_value_get_boolean (value);
+		if (etgl->item) {
+			gnome_canvas_item_set (
+				GNOME_CANVAS_ITEM (etgl->item),
+				"uniform_row_height", etgl->uniform_row_height,
+				NULL);
+		}
+		break;
+
+	case PROP_TABLE_ALTERNATING_ROW_COLORS:
+		etgl->alternating_row_colors = g_value_get_boolean (value);
+		if (etgl->item) {
+			gnome_canvas_item_set (
+				GNOME_CANVAS_ITEM (etgl->item),
+				"alternating_row_colors", etgl->alternating_row_colors,
+				NULL);
+		}
+		break;
+
+	case PROP_TABLE_HORIZONTAL_DRAW_GRID:
+		etgl->horizontal_draw_grid = g_value_get_boolean (value);
+		if (etgl->item) {
+			gnome_canvas_item_set (
+				GNOME_CANVAS_ITEM (etgl->item),
+				"horizontal_draw_grid", etgl->horizontal_draw_grid,
+				NULL);
+		}
+		break;
+
+	case PROP_TABLE_VERTICAL_DRAW_GRID:
+		etgl->vertical_draw_grid = g_value_get_boolean (value);
+		if (etgl->item) {
+			gnome_canvas_item_set (
+				GNOME_CANVAS_ITEM (etgl->item),
+				"vertical_draw_grid", etgl->vertical_draw_grid,
+				NULL);
+		}
+		break;
+
+	case PROP_TABLE_DRAW_FOCUS:
+		etgl->draw_focus = g_value_get_boolean (value);
+		if (etgl->item) {
+			gnome_canvas_item_set (
+				GNOME_CANVAS_ITEM (etgl->item),
+				"drawfocus", etgl->draw_focus,
+				NULL);
+		}
+		break;
+
+	case PROP_CURSOR_MODE:
+		etgl->cursor_mode = g_value_get_int (value);
+		if (etgl->item) {
+			gnome_canvas_item_set (
+				GNOME_CANVAS_ITEM (etgl->item),
+				"cursor_mode", etgl->cursor_mode,
+				NULL);
+		}
+		break;
+	default:
+		break;
+	}
+}
+
+static void
+etgl_get_property (GObject *object,
+                   guint property_id,
+                   GValue *value,
+                   GParamSpec *pspec)
+{
+	ETableGroup *etg = E_TABLE_GROUP (object);
+	ETableGroupLeaf *etgl = E_TABLE_GROUP_LEAF (object);
+
+	switch (property_id) {
+	case PROP_FROZEN:
+		g_value_set_boolean (value, etg->frozen);
+		break;
+	case PROP_HEIGHT:
+		g_value_set_double (value, etgl->height);
+		break;
+	case PROP_WIDTH:
+		g_value_set_double (value, etgl->width);
+		break;
+	case PROP_MINIMUM_WIDTH:
+		g_value_set_double (value, etgl->minimum_width);
+		break;
+	case PROP_UNIFORM_ROW_HEIGHT:
+		g_value_set_boolean (value, etgl->uniform_row_height);
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+		break;
+	}
+}
+
+static void
+etgl_class_init (ETableGroupLeafClass *class)
+{
+	GnomeCanvasItemClass *item_class = GNOME_CANVAS_ITEM_CLASS (class);
+	ETableGroupClass *e_group_class = E_TABLE_GROUP_CLASS (class);
+	GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+	object_class->dispose = etgl_dispose;
+	object_class->set_property = etgl_set_property;
+	object_class->get_property = etgl_get_property;
+
+	item_class->realize = etgl_realize;
+
+	e_group_class->add = etgl_add;
+	e_group_class->add_array = etgl_add_array;
+	e_group_class->add_all = etgl_add_all;
+	e_group_class->remove = etgl_remove;
+	e_group_class->increment  = etgl_increment;
+	e_group_class->decrement  = etgl_decrement;
+	e_group_class->row_count  = etgl_row_count;
+	e_group_class->set_focus  = etgl_set_focus;
+	e_group_class->get_focus_column = etgl_get_focus_column;
+	e_group_class->get_printable = etgl_get_printable;
+	e_group_class->compute_location = etgl_compute_location;
+	e_group_class->get_mouse_over = etgl_get_mouse_over;
+	e_group_class->get_cell_geometry = etgl_get_cell_geometry;
+
+	g_object_class_install_property (
+		object_class,
+		PROP_TABLE_ALTERNATING_ROW_COLORS,
+		g_param_spec_boolean (
+			"alternating_row_colors",
+			"Alternating Row Colors",
+			"Alternating Row Colors",
+			FALSE,
+			G_PARAM_WRITABLE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_TABLE_HORIZONTAL_DRAW_GRID,
+		g_param_spec_boolean (
+			"horizontal_draw_grid",
+			"Horizontal Draw Grid",
+			"Horizontal Draw Grid",
+			FALSE,
+			G_PARAM_WRITABLE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_TABLE_VERTICAL_DRAW_GRID,
+		g_param_spec_boolean (
+			"vertical_draw_grid",
+			"Vertical Draw Grid",
+			"Vertical Draw Grid",
+			FALSE,
+			G_PARAM_WRITABLE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_TABLE_DRAW_FOCUS,
+		g_param_spec_boolean (
+			"drawfocus",
+			"Draw focus",
+			"Draw focus",
+			FALSE,
+			G_PARAM_WRITABLE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_CURSOR_MODE,
+		g_param_spec_int (
+			"cursor_mode",
+			"Cursor mode",
+			"Cursor mode",
+			E_CURSOR_LINE,
+			E_CURSOR_SPREADSHEET,
+			E_CURSOR_LINE,
+			G_PARAM_WRITABLE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_LENGTH_THRESHOLD,
+		g_param_spec_int (
+			"length_threshold",
+			"Length Threshold",
+			"Length Threshold",
+			-1, G_MAXINT, 0,
+			G_PARAM_WRITABLE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_SELECTION_MODEL,
+		g_param_spec_object (
+			"selection_model",
+			"Selection model",
+			"Selection model",
+			E_TYPE_SELECTION_MODEL,
+			G_PARAM_WRITABLE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_HEIGHT,
+		g_param_spec_double (
+			"height",
+			"Height",
+			"Height",
+			0.0, G_MAXDOUBLE, 0.0,
+			G_PARAM_READABLE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_WIDTH,
+		g_param_spec_double (
+			"width",
+			"Width",
+			"Width",
+			0.0, G_MAXDOUBLE, 0.0,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_MINIMUM_WIDTH,
+		g_param_spec_double (
+			"minimum_width",
+			"Minimum width",
+			"Minimum Width",
+			0.0, G_MAXDOUBLE, 0.0,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_FROZEN,
+		g_param_spec_boolean (
+			"frozen",
+			"Frozen",
+			"Frozen",
+			FALSE,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_UNIFORM_ROW_HEIGHT,
+		g_param_spec_boolean (
+			"uniform_row_height",
+			"Uniform row height",
+			"Uniform row height",
+			FALSE,
+			G_PARAM_READWRITE));
+}
+
+static void
+etgl_init (ETableGroupLeaf *etgl)
+{
+	etgl->width = 1;
+	etgl->height = 1;
+	etgl->minimum_width = 0;
+
+	etgl->ets = NULL;
+	etgl->item = NULL;
+
+	etgl->etgl_cursor_change_id = 0;
+	etgl->etgl_cursor_activated_id = 0;
+	etgl->etgl_double_click_id = 0;
+	etgl->etgl_right_click_id = 0;
+	etgl->etgl_click_id = 0;
+	etgl->etgl_key_press_id = 0;
+	etgl->etgl_start_drag_id = 0;
+
+	etgl->alternating_row_colors = 1;
+	etgl->horizontal_draw_grid = 1;
+	etgl->vertical_draw_grid = 1;
+	etgl->draw_focus = 1;
+	etgl->cursor_mode = E_CURSOR_SIMPLE;
+	etgl->length_threshold = -1;
+
+	etgl->selection_model = NULL;
+	etgl->uniform_row_height = FALSE;
+
+	e_canvas_item_set_reflow_callback (GNOME_CANVAS_ITEM (etgl), etgl_reflow);
+}
+
diff --git a/e-util/e-table-group-leaf.h b/e-util/e-table-group-leaf.h
new file mode 100644
index 0000000..93aa2bf
--- /dev/null
+++ b/e-util/e-table-group-leaf.h
@@ -0,0 +1,110 @@
+/*
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef _E_TABLE_GROUP_LEAF_H_
+#define _E_TABLE_GROUP_LEAF_H_
+
+#include <libgnomecanvas/libgnomecanvas.h>
+
+#include <e-util/e-table-group.h>
+#include <e-util/e-table-item.h>
+#include <e-util/e-table-subset.h>
+
+/* Standard GObject macros */
+#define E_TYPE_TABLE_GROUP_LEAF \
+	(e_table_group_leaf_get_type ())
+#define E_TABLE_GROUP_LEAF(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_TABLE_GROUP_LEAF, ETableGroupLeaf))
+#define E_TABLE_GROUP_LEAF_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_TABLE_GROUP_LEAF, ETableGroupLeafClass))
+#define E_IS_TABLE_GROUP_LEAF(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_TABLE_GROUP_LEAF))
+#define E_IS_TABLE_GROUP_LEAF_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_TABLE_GROUP_LEAF))
+#define E_TABLE_GROUP_LEAF_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_TABLE_GROUP_LEAF, ETableGroupLeafClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ETableGroupLeaf ETableGroupLeaf;
+typedef struct _ETableGroupLeafClass ETableGroupLeafClass;
+
+struct _ETableGroupLeaf {
+	ETableGroup group;
+
+	/*
+	 * Item.
+	 */
+	ETableItem *item;
+
+	gdouble height;
+	gdouble width;
+	gdouble minimum_width;
+
+	gint length_threshold;
+
+	ETableSubset *ets;
+	guint is_grouped : 1;
+
+	guint alternating_row_colors : 1;
+	guint horizontal_draw_grid : 1;
+	guint vertical_draw_grid : 1;
+	guint draw_focus : 1;
+	guint uniform_row_height : 1;
+	ECursorMode cursor_mode;
+
+	gint etgl_cursor_change_id;
+	gint etgl_cursor_activated_id;
+	gint etgl_double_click_id;
+	gint etgl_right_click_id;
+	gint etgl_click_id;
+	gint etgl_key_press_id;
+	gint etgl_start_drag_id;
+
+	ESelectionModel *selection_model;
+};
+
+struct _ETableGroupLeafClass {
+	ETableGroupClass parent_class;
+};
+
+GType		e_table_group_leaf_get_type	(void) G_GNUC_CONST;
+ETableGroup *	e_table_group_leaf_new		(GnomeCanvasGroup *parent,
+						 ETableHeader *full_header,
+						 ETableHeader *header,
+						 ETableModel *model,
+						 ETableSortInfo *sort_info);
+
+G_END_DECLS
+
+#endif /* _E_TABLE_GROUP_LEAF_H_ */
+
diff --git a/e-util/e-table-group.c b/e-util/e-table-group.c
new file mode 100644
index 0000000..b119b06
--- /dev/null
+++ b/e-util/e-table-group.c
@@ -0,0 +1,771 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <gtk/gtk.h>
+#include <libgnomecanvas/libgnomecanvas.h>
+
+#include "e-table-group.h"
+#include "e-table-group-container.h"
+#include "e-table-group-leaf.h"
+#include "e-table-item.h"
+
+/* workaround for avoiding API breakage*/
+#define etg_get_type e_table_group_get_type
+G_DEFINE_TYPE (ETableGroup, etg, GNOME_TYPE_CANVAS_GROUP)
+
+#define ETG_CLASS(e) (E_TABLE_GROUP_CLASS(G_OBJECT_GET_CLASS(e)))
+
+enum {
+	CURSOR_CHANGE,
+	CURSOR_ACTIVATED,
+	DOUBLE_CLICK,
+	RIGHT_CLICK,
+	CLICK,
+	KEY_PRESS,
+	START_DRAG,
+	LAST_SIGNAL
+};
+
+static guint etg_signals[LAST_SIGNAL] = { 0, };
+
+static gboolean etg_get_focus (ETableGroup      *etg);
+
+static void
+etg_dispose (GObject *object)
+{
+	ETableGroup *etg = E_TABLE_GROUP (object);
+
+	if (etg->header) {
+		g_object_unref (etg->header);
+		etg->header = NULL;
+	}
+
+	if (etg->full_header) {
+		g_object_unref (etg->full_header);
+		etg->full_header = NULL;
+	}
+
+	if (etg->model) {
+		g_object_unref (etg->model);
+		etg->model = NULL;
+	}
+
+	/* Chain up to parent's dispose() method. */
+	G_OBJECT_CLASS (etg_parent_class)->dispose (object);
+}
+
+/**
+ * e_table_group_new
+ * @parent: The %GnomeCanvasGroup to create a child of.
+ * @full_header: The full header of the %ETable.
+ * @header: The current header of the %ETable.
+ * @model: The %ETableModel of the %ETable.
+ * @sort_info: The %ETableSortInfo of the %ETable.
+ * @n: The grouping information object to group by.
+ *
+ * %ETableGroup is a collection of rows of an %ETable.  It's a
+ * %GnomeCanvasItem.  There are two different forms.  If n < the
+ * number of groupings in the given %ETableSortInfo, then the
+ * %ETableGroup will need to contain other %ETableGroups, thus it
+ * creates an %ETableGroupContainer.  Otherwise, it will just contain
+ * an %ETableItem, and thus it creates an %ETableGroupLeaf.
+ *
+ * Returns: The new %ETableGroup.
+ */
+ETableGroup *
+e_table_group_new (GnomeCanvasGroup *parent,
+                   ETableHeader *full_header,
+                   ETableHeader *header,
+                   ETableModel *model,
+                   ETableSortInfo *sort_info,
+                   gint n)
+{
+	g_return_val_if_fail (model != NULL, NULL);
+
+	if (n < e_table_sort_info_grouping_get_count (sort_info)) {
+		return e_table_group_container_new (
+			parent, full_header, header, model, sort_info, n);
+	} else {
+		return e_table_group_leaf_new (
+			parent, full_header, header, model, sort_info);
+	}
+}
+
+/**
+ * e_table_group_construct
+ * @parent: The %GnomeCanvasGroup to create a child of.
+ * @etg: The %ETableGroup to construct.
+ * @full_header: The full header of the %ETable.
+ * @header: The current header of the %ETable.
+ * @model: The %ETableModel of the %ETable.
+ *
+ * This routine does the base construction of the %ETableGroup.
+ */
+void
+e_table_group_construct (GnomeCanvasGroup *parent,
+                         ETableGroup *etg,
+                         ETableHeader *full_header,
+                         ETableHeader *header,
+                         ETableModel *model)
+{
+	etg->full_header = full_header;
+	g_object_ref (etg->full_header);
+	etg->header = header;
+	g_object_ref (etg->header);
+	etg->model = model;
+	g_object_ref (etg->model);
+	g_object_set (etg, "parent", parent, NULL);
+}
+
+/**
+ * e_table_group_add
+ * @etg: The %ETableGroup to add a row to
+ * @row: The row to add.
+ *
+ * This routine adds the given row from the %ETableModel to this set
+ * of rows.
+ */
+void
+e_table_group_add (ETableGroup *etg,
+                   gint row)
+{
+	g_return_if_fail (etg != NULL);
+	g_return_if_fail (E_IS_TABLE_GROUP (etg));
+
+	g_return_if_fail (ETG_CLASS (etg)->add != NULL);
+	ETG_CLASS (etg)->add (etg, row);
+}
+
+/**
+ * e_table_group_add_array
+ * @etg: The %ETableGroup to add to
+ * @array: The array to add.
+ * @count: The number of times to add
+ *
+ * This routine adds all the rows in the array to this set of rows.
+ * It assumes that the array is already sorted properly.
+ */
+void
+e_table_group_add_array (ETableGroup *etg,
+                         const gint *array,
+                         gint count)
+{
+	g_return_if_fail (etg != NULL);
+	g_return_if_fail (E_IS_TABLE_GROUP (etg));
+
+	g_return_if_fail (ETG_CLASS (etg)->add_array != NULL);
+	ETG_CLASS (etg)->add_array (etg, array, count);
+}
+
+/**
+ * e_table_group_add_all
+ * @etg: The %ETableGroup to add to
+ *
+ * This routine adds all the rows from the %ETableModel to this set
+ * of rows.
+ */
+void
+e_table_group_add_all (ETableGroup *etg)
+{
+	g_return_if_fail (etg != NULL);
+	g_return_if_fail (E_IS_TABLE_GROUP (etg));
+
+	g_return_if_fail (ETG_CLASS (etg)->add_all != NULL);
+	ETG_CLASS (etg)->add_all (etg);
+}
+
+/**
+ * e_table_group_remove
+ * @etg: The %ETableGroup to remove a row from
+ * @row: The row to remove.
+ *
+ * This routine removes the given row from the %ETableModel from this
+ * set of rows.
+ *
+ * Returns: TRUE if the row was deleted and FALSE if the row was not
+ * found.
+ */
+gboolean
+e_table_group_remove (ETableGroup *etg,
+                      gint row)
+{
+	g_return_val_if_fail (etg != NULL, FALSE);
+	g_return_val_if_fail (E_IS_TABLE_GROUP (etg), FALSE);
+
+	g_return_val_if_fail (ETG_CLASS (etg)->remove != NULL, FALSE);
+	return ETG_CLASS (etg)->remove (etg, row);
+}
+
+/**
+ * e_table_group_increment
+ * @etg: The %ETableGroup to increment
+ * @position: The position to increment from
+ * @amount: The amount to increment.
+ *
+ * This routine adds amount to all rows greater than or equal to
+ * position.  This is to handle when a row gets inserted into the
+ * model.
+ */
+void
+e_table_group_increment (ETableGroup *etg,
+                         gint position,
+                         gint amount)
+{
+	g_return_if_fail (etg != NULL);
+	g_return_if_fail (E_IS_TABLE_GROUP (etg));
+
+	g_return_if_fail (ETG_CLASS (etg)->increment != NULL);
+	ETG_CLASS (etg)->increment (etg, position, amount);
+}
+
+/**
+ * e_table_group_increment
+ * @etg: The %ETableGroup to decrement
+ * @position: The position to decrement from
+ * @amount: The amount to decrement
+ *
+ * This routine removes amount from all rows greater than or equal to
+ * position.  This is to handle when a row gets deleted from the
+ * model.
+ */
+void
+e_table_group_decrement (ETableGroup *etg,
+                         gint position,
+                         gint amount)
+{
+	g_return_if_fail (etg != NULL);
+	g_return_if_fail (E_IS_TABLE_GROUP (etg));
+
+	g_return_if_fail (ETG_CLASS (etg)->decrement != NULL);
+	ETG_CLASS (etg)->decrement (etg, position, amount);
+}
+
+/**
+ * e_table_group_increment
+ * @etg: The %ETableGroup to count
+ *
+ * This routine calculates the number of rows shown in this group.
+ *
+ * Returns: The number of rows.
+ */
+gint
+e_table_group_row_count (ETableGroup *etg)
+{
+	g_return_val_if_fail (etg != NULL, 0);
+	g_return_val_if_fail (E_IS_TABLE_GROUP (etg), -1);
+
+	g_return_val_if_fail (ETG_CLASS (etg)->row_count != NULL, -1);
+	return ETG_CLASS (etg)->row_count (etg);
+}
+
+/**
+ * e_table_group_set_focus
+ * @etg: The %ETableGroup to set
+ * @direction: The direction the focus is coming from.
+ * @view_col: The column to set the focus in.
+ *
+ * Sets the focus to this widget.  Places the focus in the view column
+ * coming from direction direction.
+ */
+void
+e_table_group_set_focus (ETableGroup *etg,
+                         EFocus direction,
+                         gint view_col)
+{
+	g_return_if_fail (etg != NULL);
+	g_return_if_fail (E_IS_TABLE_GROUP (etg));
+
+	g_return_if_fail (ETG_CLASS (etg)->set_focus != NULL);
+	ETG_CLASS (etg)->set_focus (etg, direction, view_col);
+}
+
+/**
+ * e_table_group_get_focus
+ * @etg: The %ETableGroup to check
+ *
+ * Calculates if this group has the focus.
+ *
+ * Returns: TRUE if this group has the focus.
+ */
+gboolean
+e_table_group_get_focus (ETableGroup *etg)
+{
+	g_return_val_if_fail (etg != NULL, FALSE);
+	g_return_val_if_fail (E_IS_TABLE_GROUP (etg), FALSE);
+
+	g_return_val_if_fail (ETG_CLASS (etg)->get_focus != NULL, FALSE);
+	return ETG_CLASS (etg)->get_focus (etg);
+}
+
+/**
+ * e_table_group_get_focus_column
+ * @etg: The %ETableGroup to check
+ *
+ * Calculates which column in this group has the focus.
+ *
+ * Returns: The column index (view column).
+ */
+gint
+e_table_group_get_focus_column (ETableGroup *etg)
+{
+	g_return_val_if_fail (etg != NULL, -1);
+	g_return_val_if_fail (E_IS_TABLE_GROUP (etg), -1);
+
+	g_return_val_if_fail (ETG_CLASS (etg)->get_focus_column != NULL, -1);
+	return ETG_CLASS (etg)->get_focus_column (etg);
+}
+
+/**
+ * e_table_group_get_printable
+ * @etg: %ETableGroup which will be printed
+ *
+ * This routine creates and returns an %EPrintable that can be used to
+ * print the given %ETableGroup.
+ *
+ * Returns: The %EPrintable.
+ */
+EPrintable *
+e_table_group_get_printable (ETableGroup *etg)
+{
+	g_return_val_if_fail (etg != NULL, NULL);
+	g_return_val_if_fail (E_IS_TABLE_GROUP (etg), NULL);
+
+	g_return_val_if_fail (ETG_CLASS (etg)->get_printable != NULL, NULL);
+	return ETG_CLASS (etg)->get_printable (etg);
+}
+
+/**
+ * e_table_group_compute_location
+ * @eti: %ETableGroup to look in.
+ * @x: A pointer to the x location to find in the %ETableGroup.
+ * @y: A pointer to the y location to find in the %ETableGroup.
+ * @row: A pointer to the location to store the found row in.
+ * @col: A pointer to the location to store the found col in.
+ *
+ * This routine locates the pixel location (*x, *y) in the
+ * %ETableGroup.  If that location is in the %ETableGroup, *row and
+ * *col are set to the view row and column where it was found.  If
+ * that location is not in the %ETableGroup, the height of the
+ * %ETableGroup is removed from the value y points to.
+ */
+void
+e_table_group_compute_location (ETableGroup *etg,
+                                gint *x,
+                                gint *y,
+                                gint *row,
+                                gint *col)
+{
+	g_return_if_fail (etg != NULL);
+	g_return_if_fail (E_IS_TABLE_GROUP (etg));
+
+	g_return_if_fail (ETG_CLASS (etg)->compute_location != NULL);
+	ETG_CLASS (etg)->compute_location (etg, x, y, row, col);
+}
+
+void
+e_table_group_get_mouse_over (ETableGroup *etg,
+                              gint *row,
+                              gint *col)
+{
+	g_return_if_fail (etg != NULL);
+	g_return_if_fail (E_IS_TABLE_GROUP (etg));
+
+	g_return_if_fail (ETG_CLASS (etg)->get_mouse_over != NULL);
+	ETG_CLASS (etg)->get_mouse_over (etg, row, col);
+}
+
+/**
+ * e_table_group_get_position
+ * @eti: %ETableGroup to look in.
+ * @x: A pointer to the location to store the found x location in.
+ * @y: A pointer to the location to store the found y location in.
+ * @row: A pointer to the row number to find.
+ * @col: A pointer to the col number to find.
+ *
+ * This routine finds the view cell (row, col) in the #ETableGroup.
+ * If that location is in the #ETableGroup * x and * y are set to the
+ * upper left hand corner of the cell found.  If that location is not
+ * in the #ETableGroup, the number of rows in the #ETableGroup is
+ * removed from the value row points to.
+ */
+void
+e_table_group_get_cell_geometry (ETableGroup *etg,
+                                 gint *row,
+                                 gint *col,
+                                 gint *x,
+                                 gint *y,
+                                 gint *width,
+                                 gint *height)
+{
+	g_return_if_fail (etg != NULL);
+	g_return_if_fail (E_IS_TABLE_GROUP (etg));
+
+	g_return_if_fail (ETG_CLASS (etg)->get_cell_geometry != NULL);
+	ETG_CLASS (etg)->get_cell_geometry (etg, row, col, x, y, width, height);
+}
+
+/**
+ * e_table_group_cursor_change
+ * @eti: %ETableGroup to emit the signal on
+ * @row: The new cursor row (model row)
+ *
+ * This routine emits the "cursor_change" signal.
+ */
+void
+e_table_group_cursor_change (ETableGroup *e_table_group,
+                             gint row)
+{
+	g_return_if_fail (e_table_group != NULL);
+	g_return_if_fail (E_IS_TABLE_GROUP (e_table_group));
+
+	g_signal_emit (
+		e_table_group,
+		etg_signals[CURSOR_CHANGE], 0,
+		row);
+}
+
+/**
+ * e_table_group_cursor_activated
+ * @eti: %ETableGroup to emit the signal on
+ * @row: The cursor row (model row)
+ *
+ * This routine emits the "cursor_activated" signal.
+ */
+void
+e_table_group_cursor_activated (ETableGroup *e_table_group,
+                                gint row)
+{
+	g_return_if_fail (e_table_group != NULL);
+	g_return_if_fail (E_IS_TABLE_GROUP (e_table_group));
+
+	g_signal_emit (
+		e_table_group,
+		etg_signals[CURSOR_ACTIVATED], 0,
+		row);
+}
+
+/**
+ * e_table_group_double_click
+ * @eti: %ETableGroup to emit the signal on
+ * @row: The row clicked on (model row)
+ * @col: The col clicked on (model col)
+ * @event: The event that caused this signal
+ *
+ * This routine emits the "double_click" signal.
+ */
+void
+e_table_group_double_click (ETableGroup *e_table_group,
+                            gint row,
+                            gint col,
+                            GdkEvent *event)
+{
+	g_return_if_fail (e_table_group != NULL);
+	g_return_if_fail (E_IS_TABLE_GROUP (e_table_group));
+
+	g_signal_emit (
+		e_table_group,
+		etg_signals[DOUBLE_CLICK], 0,
+		row, col, event);
+}
+
+/**
+ * e_table_group_right_click
+ * @eti: %ETableGroup to emit the signal on
+ * @row: The row clicked on (model row)
+ * @col: The col clicked on (model col)
+ * @event: The event that caused this signal
+ *
+ * This routine emits the "right_click" signal.
+ */
+gboolean
+e_table_group_right_click (ETableGroup *e_table_group,
+                           gint row,
+                           gint col,
+                           GdkEvent *event)
+{
+	gboolean return_val = FALSE;
+
+	g_return_val_if_fail (e_table_group != NULL, FALSE);
+	g_return_val_if_fail (E_IS_TABLE_GROUP (e_table_group), FALSE);
+
+	g_signal_emit (
+		e_table_group,
+		etg_signals[RIGHT_CLICK], 0,
+		row, col, event, &return_val);
+
+	return return_val;
+}
+
+/**
+ * e_table_group_click
+ * @eti: %ETableGroup to emit the signal on
+ * @row: The row clicked on (model row)
+ * @col: The col clicked on (model col)
+ * @event: The event that caused this signal
+ *
+ * This routine emits the "click" signal.
+ */
+gboolean
+e_table_group_click (ETableGroup *e_table_group,
+                     gint row,
+                     gint col,
+                     GdkEvent *event)
+{
+	gboolean return_val = FALSE;
+
+	g_return_val_if_fail (e_table_group != NULL, FALSE);
+	g_return_val_if_fail (E_IS_TABLE_GROUP (e_table_group), FALSE);
+
+	g_signal_emit (
+		e_table_group,
+		etg_signals[CLICK], 0,
+		row, col, event, &return_val);
+
+	return return_val;
+}
+
+/**
+ * e_table_group_key_press
+ * @eti: %ETableGroup to emit the signal on
+ * @row: The cursor row (model row)
+ * @col: The cursor col (model col)
+ * @event: The event that caused this signal
+ *
+ * This routine emits the "key_press" signal.
+ */
+gboolean
+e_table_group_key_press (ETableGroup *e_table_group,
+                         gint row,
+                         gint col,
+                         GdkEvent *event)
+{
+	gboolean return_val = FALSE;
+
+	g_return_val_if_fail (e_table_group != NULL, FALSE);
+	g_return_val_if_fail (E_IS_TABLE_GROUP (e_table_group), FALSE);
+
+	g_signal_emit (
+		e_table_group,
+		etg_signals[KEY_PRESS], 0,
+		row, col, event, &return_val);
+
+	return return_val;
+}
+
+/**
+ * e_table_group_start_drag
+ * @eti: %ETableGroup to emit the signal on
+ * @row: The cursor row (model row)
+ * @col: The cursor col (model col)
+ * @event: The event that caused this signal
+ *
+ * This routine emits the "start_drag" signal.
+ */
+gboolean
+e_table_group_start_drag (ETableGroup *e_table_group,
+                          gint row,
+                          gint col,
+                          GdkEvent *event)
+{
+	gboolean return_val = FALSE;
+
+	g_return_val_if_fail (e_table_group != NULL, FALSE);
+	g_return_val_if_fail (E_IS_TABLE_GROUP (e_table_group), FALSE);
+
+	g_signal_emit (
+		e_table_group,
+		etg_signals[START_DRAG], 0,
+		row, col, event, &return_val);
+
+	return return_val;
+}
+
+/**
+ * e_table_group_get_header
+ * @eti: %ETableGroup to check
+ *
+ * This routine returns the %ETableGroup's header.
+ *
+ * Returns: The %ETableHeader.
+ */
+ETableHeader *
+e_table_group_get_header (ETableGroup *etg)
+{
+	g_return_val_if_fail (etg != NULL, NULL);
+	g_return_val_if_fail (E_IS_TABLE_GROUP (etg), NULL);
+
+	return etg->header;
+}
+
+static gint
+etg_event (GnomeCanvasItem *item,
+           GdkEvent *event)
+{
+	ETableGroup *etg = E_TABLE_GROUP (item);
+	gboolean return_val = TRUE;
+
+	switch (event->type) {
+
+	case GDK_FOCUS_CHANGE:
+		etg->has_focus = event->focus_change.in;
+		return_val = FALSE;
+		break;
+
+	default:
+		return_val = FALSE;
+	}
+	if (return_val == FALSE) {
+		if (GNOME_CANVAS_ITEM_CLASS (etg_parent_class)->event)
+			return GNOME_CANVAS_ITEM_CLASS (etg_parent_class)->event (item, event);
+	}
+	return return_val;
+
+}
+
+static gboolean
+etg_get_focus (ETableGroup *etg)
+{
+	return etg->has_focus;
+}
+
+static void
+etg_class_init (ETableGroupClass *class)
+{
+	GnomeCanvasItemClass *item_class = GNOME_CANVAS_ITEM_CLASS (class);
+	GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+	object_class->dispose = etg_dispose;
+
+	item_class->event = etg_event;
+
+	class->cursor_change = NULL;
+	class->cursor_activated = NULL;
+	class->double_click = NULL;
+	class->right_click = NULL;
+	class->click = NULL;
+	class->key_press = NULL;
+	class->start_drag = NULL;
+
+	class->add = NULL;
+	class->add_array = NULL;
+	class->add_all = NULL;
+	class->remove = NULL;
+	class->row_count  = NULL;
+	class->increment  = NULL;
+	class->decrement  = NULL;
+	class->set_focus  = NULL;
+	class->get_focus = etg_get_focus;
+	class->get_printable = NULL;
+	class->compute_location = NULL;
+	class->get_mouse_over = NULL;
+	class->get_cell_geometry = NULL;
+
+	etg_signals[CURSOR_CHANGE] = g_signal_new (
+		"cursor_change",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ETableGroupClass, cursor_change),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__INT,
+		G_TYPE_NONE, 1,
+		G_TYPE_INT);
+
+	etg_signals[CURSOR_ACTIVATED] = g_signal_new (
+		"cursor_activated",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ETableGroupClass, cursor_activated),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__INT,
+		G_TYPE_NONE, 1,
+		G_TYPE_INT);
+
+	etg_signals[DOUBLE_CLICK] = g_signal_new (
+		"double_click",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ETableGroupClass, double_click),
+		NULL, NULL,
+		e_marshal_NONE__INT_INT_BOXED,
+		G_TYPE_NONE, 3,
+		G_TYPE_INT,
+		G_TYPE_INT,
+		GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
+
+	etg_signals[RIGHT_CLICK] = g_signal_new (
+		"right_click",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ETableGroupClass, right_click),
+		g_signal_accumulator_true_handled, NULL,
+		e_marshal_BOOLEAN__INT_INT_BOXED,
+		G_TYPE_BOOLEAN, 3,
+		G_TYPE_INT,
+		G_TYPE_INT,
+		GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
+
+	etg_signals[CLICK] = g_signal_new (
+		"click",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ETableGroupClass, click),
+		g_signal_accumulator_true_handled, NULL,
+		e_marshal_BOOLEAN__INT_INT_BOXED,
+		G_TYPE_BOOLEAN, 3,
+		G_TYPE_INT,
+		G_TYPE_INT,
+		GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
+
+	etg_signals[KEY_PRESS] = g_signal_new (
+		"key_press",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ETableGroupClass, key_press),
+		g_signal_accumulator_true_handled, NULL,
+		e_marshal_BOOLEAN__INT_INT_BOXED,
+		G_TYPE_BOOLEAN, 3,
+		G_TYPE_INT,
+		G_TYPE_INT,
+		GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
+
+	etg_signals[START_DRAG] = g_signal_new (
+		"start_drag",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ETableGroupClass, start_drag),
+		g_signal_accumulator_true_handled, NULL,
+		e_marshal_BOOLEAN__INT_INT_BOXED,
+		G_TYPE_BOOLEAN, 3,
+		G_TYPE_INT,
+		G_TYPE_INT,
+		GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
+}
+
+static void
+etg_init (ETableGroup *etg)
+{
+	/* nothing to do */
+}
diff --git a/e-util/e-table-group.h b/e-util/e-table-group.h
new file mode 100644
index 0000000..7e9e905
--- /dev/null
+++ b/e-util/e-table-group.h
@@ -0,0 +1,242 @@
+/*
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef _E_TABLE_GROUP_H_
+#define _E_TABLE_GROUP_H_
+
+#include <libgnomecanvas/libgnomecanvas.h>
+
+#include <e-util/e-misc-utils.h>
+#include <e-util/e-printable.h>
+#include <e-util/e-table-defines.h>
+#include <e-util/e-table-header.h>
+#include <e-util/e-table-model.h>
+#include <e-util/e-table-sort-info.h>
+
+/* Standard GObject macros */
+#define E_TYPE_TABLE_GROUP \
+	(e_table_group_get_type ())
+#define E_TABLE_GROUP(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_TABLE_GROUP, ETableGroup))
+#define E_TABLE_GROUP_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_TABLE_GROUP, ETableGroupClass))
+#define E_IS_TABLE_GROUP(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_TABLE_GROUP))
+#define E_IS_TABLE_GROUP_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_TABLE_GROUP))
+#define E_TABLE_GROUP_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_TABLE_GROUP, ETableGroupClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ETableGroup ETableGroup;
+typedef struct _ETableGroupClass ETableGroupClass;
+
+struct _ETableGroup {
+	GnomeCanvasGroup group;
+
+	/*
+	 * The full header.
+	 */
+	ETableHeader *full_header;
+	ETableHeader *header;
+
+	/*
+	 * The model we pull data from.
+	 */
+	ETableModel *model;
+
+	/*
+	 * Whether we should add indentation and open/close markers,
+	 * or if we just act as containers of subtables.
+	 */
+	guint transparent : 1;
+
+	guint has_focus : 1;
+
+	guint frozen : 1;
+};
+
+struct _ETableGroupClass {
+	GnomeCanvasGroupClass parent_class;
+
+	/* Signals */
+	void		(*cursor_change)	(ETableGroup *etg,
+						 gint row);
+	void		(*cursor_activated)	(ETableGroup *etg,
+						 gint row);
+	void		(*double_click)		(ETableGroup *etg,
+						 gint row,
+						 gint col,
+						 GdkEvent *event);
+	gboolean	(*right_click)		(ETableGroup *etg,
+						 gint row,
+						 gint col,
+						 GdkEvent *event);
+	gboolean	(*click)		(ETableGroup *etg,
+						 gint row,
+						 gint col,
+						 GdkEvent *event);
+	gboolean	(*key_press)		(ETableGroup *etg,
+						 gint row,
+						 gint col,
+						 GdkEvent *event);
+	gint		(*start_drag)		(ETableGroup *etg,
+						 gint row,
+						 gint col,
+						 GdkEvent *event);
+
+	/* Virtual functions. */
+	void		(*add)			(ETableGroup *etg,
+						 gint row);
+	void		(*add_array)		(ETableGroup *etg,
+						 const gint *array,
+						 gint count);
+	void		(*add_all)		(ETableGroup *etg);
+	gboolean	(*remove)		(ETableGroup *etg,
+						 gint row);
+	gint		(*row_count)		(ETableGroup *etg);
+	void		(*increment)		(ETableGroup *etg,
+						 gint position,
+						 gint amount);
+	void		(*decrement)		(ETableGroup *etg,
+						 gint position,
+						 gint amount);
+	void		(*set_focus)		(ETableGroup *etg,
+						 EFocus direction,
+						 gint view_col);
+	gboolean	(*get_focus)		(ETableGroup *etg);
+	gint		(*get_focus_column)	(ETableGroup *etg);
+	EPrintable *	(*get_printable)	(ETableGroup *etg);
+	void		(*compute_location)	(ETableGroup *etg,
+						 gint *x,
+						 gint *y,
+						 gint *row,
+						 gint *col);
+	void		(*get_mouse_over)	(ETableGroup *etg,
+						 gint *row,
+						 gint *col);
+	void		(*get_cell_geometry)	(ETableGroup *etg,
+						 gint *row,
+						 gint *col,
+						 gint *x,
+						 gint *y,
+						 gint *width,
+						 gint *height);
+};
+
+GType		e_table_group_get_type		(void) G_GNUC_CONST;
+ETableGroup *	e_table_group_new		(GnomeCanvasGroup *parent,
+						 ETableHeader *full_header,
+						 ETableHeader *header,
+						 ETableModel *model,
+						 ETableSortInfo *sort_info,
+						 gint n);
+void		e_table_group_construct		(GnomeCanvasGroup *parent,
+						 ETableGroup *etg,
+						 ETableHeader *full_header,
+						 ETableHeader *header,
+						 ETableModel *model);
+
+/* Virtual functions */
+void		e_table_group_add		(ETableGroup *etg,
+						 gint row);
+void		e_table_group_add_array		(ETableGroup *etg,
+						 const gint *array,
+						 gint count);
+void		e_table_group_add_all		(ETableGroup *etg);
+gboolean	e_table_group_remove		(ETableGroup *etg,
+						 gint row);
+void		e_table_group_increment		(ETableGroup *etg,
+						 gint position,
+						 gint amount);
+void		e_table_group_decrement		(ETableGroup *etg,
+						 gint position,
+						 gint amount);
+gint		e_table_group_row_count		(ETableGroup *etg);
+void		e_table_group_set_focus		(ETableGroup *etg,
+						 EFocus direction,
+						 gint view_col);
+gboolean	e_table_group_get_focus		(ETableGroup *etg);
+gint		e_table_group_get_focus_column	(ETableGroup *etg);
+ETableHeader *	e_table_group_get_header	(ETableGroup *etg);
+EPrintable *	e_table_group_get_printable	(ETableGroup *etg);
+void		e_table_group_compute_location	(ETableGroup *etg,
+						 gint *x,
+						 gint *y,
+						 gint *row,
+						 gint *col);
+void		e_table_group_get_mouse_over	(ETableGroup *etg,
+						 gint *row,
+						 gint *col);
+void		e_table_group_get_cell_geometry	(ETableGroup *etg,
+						 gint *row,
+						 gint *col,
+						 gint *x,
+						 gint *y,
+						 gint *width,
+						 gint *height);
+
+/* For emitting the signals */
+void		e_table_group_cursor_change	(ETableGroup *etg,
+						 gint row);
+void		e_table_group_cursor_activated	(ETableGroup *etg,
+						 gint row);
+void		e_table_group_double_click	(ETableGroup *etg,
+						 gint row,
+						 gint col,
+						 GdkEvent *event);
+gboolean	e_table_group_right_click	(ETableGroup *etg,
+						 gint row,
+						 gint col,
+						 GdkEvent *event);
+gboolean	e_table_group_click		(ETableGroup *etg,
+						 gint row,
+						 gint col,
+						 GdkEvent *event);
+gboolean	e_table_group_key_press		(ETableGroup *etg,
+						 gint row,
+						 gint col,
+						 GdkEvent *event);
+gint		e_table_group_start_drag	(ETableGroup *etg,
+						 gint row,
+						 gint col,
+						 GdkEvent *event);
+
+typedef void (*ETableGroupLeafFn) (gpointer e_table_item, gpointer closure);
+void		e_table_group_apply_to_leafs	(ETableGroup *etg,
+						 ETableGroupLeafFn fn,
+						 gpointer closure);
+
+G_END_DECLS
+
+#endif /* _E_TABLE_GROUP_H_ */
diff --git a/e-util/e-table-header-item.c b/e-util/e-table-header-item.c
new file mode 100644
index 0000000..103ed3a
--- /dev/null
+++ b/e-util/e-table-header-item.c
@@ -0,0 +1,2226 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *		Miguel de Icaza <miguel gnu org>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-table-header-item.h"
+
+#include <string.h>
+
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+#include <gdk/gdkkeysyms.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#include <libgnomecanvas/libgnomecanvas.h>
+
+#include "e-canvas.h"
+#include "e-popup-menu.h"
+#include "e-table-col-dnd.h"
+#include "e-table-config.h"
+#include "e-table-defines.h"
+#include "e-table-field-chooser-dialog.h"
+#include "e-table-header-utils.h"
+#include "e-table-header.h"
+#include "e-table.h"
+#include "e-xml-utils.h"
+
+#include "arrow-up.xpm"
+#include "arrow-down.xpm"
+
+enum {
+	BUTTON_PRESSED,
+	LAST_SIGNAL
+};
+
+static guint ethi_signals[LAST_SIGNAL] = { 0, };
+
+#define ARROW_DOWN_HEIGHT 16
+#define ARROW_PTR          7
+
+/* Defines the tolerance for proximity of the column division to the cursor position */
+#define TOLERANCE 4
+
+#define ETHI_RESIZING(x) ((x)->resize_col != -1)
+
+#define ethi_get_type e_table_header_item_get_type
+G_DEFINE_TYPE (ETableHeaderItem, ethi, GNOME_TYPE_CANVAS_ITEM)
+
+#define d(x)
+
+static void ethi_drop_table_header (ETableHeaderItem *ethi);
+
+/*
+ * They display the arrows for the drop location.
+ */
+
+static GtkWidget *arrow_up, *arrow_down;
+
+enum {
+	PROP_0,
+	PROP_TABLE_HEADER,
+	PROP_FULL_HEADER,
+	PROP_DND_CODE,
+	PROP_TABLE_FONT_DESC,
+	PROP_SORT_INFO,
+	PROP_TABLE,
+	PROP_TREE
+};
+
+enum {
+	ET_SCROLL_UP = 1 << 0,
+	ET_SCROLL_DOWN = 1 << 1,
+	ET_SCROLL_LEFT = 1 << 2,
+	ET_SCROLL_RIGHT = 1 << 3
+};
+
+static void scroll_off (ETableHeaderItem *ethi);
+static void scroll_on (ETableHeaderItem *ethi, guint scroll_direction);
+
+static void
+ethi_dispose (GObject *object)
+{
+	ETableHeaderItem *ethi = E_TABLE_HEADER_ITEM (object);
+
+	ethi_drop_table_header (ethi);
+
+	scroll_off (ethi);
+
+	if (ethi->resize_cursor) {
+		g_object_unref (ethi->resize_cursor);
+		ethi->resize_cursor = NULL;
+	}
+
+	if (ethi->dnd_code) {
+		g_free (ethi->dnd_code);
+		ethi->dnd_code = NULL;
+	}
+
+	if (ethi->sort_info) {
+		if (ethi->sort_info_changed_id)
+			g_signal_handler_disconnect (
+				ethi->sort_info, ethi->sort_info_changed_id);
+		if (ethi->group_info_changed_id)
+			g_signal_handler_disconnect (
+				ethi->sort_info, ethi->group_info_changed_id);
+		g_object_unref (ethi->sort_info);
+		ethi->sort_info = NULL;
+	}
+
+	if (ethi->full_header)
+		g_object_unref (ethi->full_header);
+	ethi->full_header = NULL;
+
+	if (ethi->etfcd.widget)
+		g_object_remove_weak_pointer (
+			G_OBJECT (ethi->etfcd.widget), &ethi->etfcd.pointer);
+
+	if (ethi->config)
+		g_object_unref (ethi->config);
+	ethi->config = NULL;
+
+	/* Chain up to parent's dispose() method. */
+	G_OBJECT_CLASS (ethi_parent_class)->dispose (object);
+}
+
+static gint
+e_table_header_item_get_height (ETableHeaderItem *ethi)
+{
+	ETableHeader *eth;
+	gint numcols, col;
+	gint maxheight;
+
+	g_return_val_if_fail (ethi != NULL, 0);
+	g_return_val_if_fail (E_IS_TABLE_HEADER_ITEM (ethi), 0);
+
+	eth = ethi->eth;
+	numcols = e_table_header_count (eth);
+
+	maxheight = 0;
+
+	for (col = 0; col < numcols; col++) {
+		ETableCol *ecol = e_table_header_get_column (eth, col);
+		gint height;
+
+		height = e_table_header_compute_height (
+			ecol, GTK_WIDGET (GNOME_CANVAS_ITEM (ethi)->canvas));
+
+		if (height > maxheight)
+			maxheight = height;
+	}
+
+	return maxheight;
+}
+
+static void
+ethi_update (GnomeCanvasItem *item,
+             const cairo_matrix_t *i2c,
+             gint flags)
+{
+	ETableHeaderItem *ethi = E_TABLE_HEADER_ITEM (item);
+	gdouble x1, y1, x2, y2;
+
+	if (GNOME_CANVAS_ITEM_CLASS (ethi_parent_class)->update)
+		GNOME_CANVAS_ITEM_CLASS (ethi_parent_class)->update (
+			item, i2c, flags);
+
+	if (ethi->sort_info)
+		ethi->group_indent_width =
+			e_table_sort_info_grouping_get_count (ethi->sort_info)
+			* GROUP_INDENT;
+	else
+		ethi->group_indent_width = 0;
+
+	ethi->width =
+		e_table_header_total_width (ethi->eth) +
+		ethi->group_indent_width;
+
+	x1 = y1 = 0;
+	x2 = ethi->width;
+	y2 = ethi->height;
+
+	gnome_canvas_matrix_transform_rect (i2c, &x1, &y1, &x2, &y2);
+
+	if (item->x1 != x1 ||
+	    item->y1 != y1 ||
+	    item->x2 != x2 ||
+	    item->y2 != y2) {
+		gnome_canvas_request_redraw (
+			item->canvas,
+			item->x1, item->y1,
+			item->x2, item->y2);
+		item->x1 = x1;
+		item->y1 = y1;
+		item->x2 = x2;
+		item->y2 = y2;
+	}
+	gnome_canvas_request_redraw (
+		item->canvas, item->x1, item->y1, item->x2, item->y2);
+}
+
+static void
+ethi_font_set (ETableHeaderItem *ethi,
+               PangoFontDescription *font_desc)
+{
+	if (ethi->font_desc)
+		pango_font_description_free (ethi->font_desc);
+
+	ethi->font_desc = pango_font_description_copy (font_desc);
+
+	ethi->height = e_table_header_item_get_height (ethi);
+	e_canvas_item_request_reflow (GNOME_CANVAS_ITEM (ethi));
+}
+
+static void
+ethi_drop_table_header (ETableHeaderItem *ethi)
+{
+	GObject *header;
+
+	if (!ethi->eth)
+		return;
+
+	header = G_OBJECT (ethi->eth);
+	g_signal_handler_disconnect (header, ethi->structure_change_id);
+	g_signal_handler_disconnect (header, ethi->dimension_change_id);
+
+	g_object_unref (header);
+	ethi->eth = NULL;
+	ethi->width = 0;
+}
+
+static void
+structure_changed (ETableHeader *header,
+                   ETableHeaderItem *ethi)
+{
+	gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (ethi));
+}
+
+static void
+dimension_changed (ETableHeader *header,
+                   gint col,
+                   ETableHeaderItem *ethi)
+{
+	gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (ethi));
+}
+
+static void
+ethi_add_table_header (ETableHeaderItem *ethi,
+                       ETableHeader *header)
+{
+	ethi->eth = header;
+	g_object_ref (ethi->eth);
+
+	ethi->height = e_table_header_item_get_height (ethi);
+
+	ethi->structure_change_id = g_signal_connect (
+		header, "structure_change",
+		G_CALLBACK (structure_changed), ethi);
+	ethi->dimension_change_id = g_signal_connect (
+		header, "dimension_change",
+		G_CALLBACK (dimension_changed), ethi);
+	e_canvas_item_request_reflow (GNOME_CANVAS_ITEM (ethi));
+	gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (ethi));
+}
+
+static void
+ethi_sort_info_changed (ETableSortInfo *sort_info,
+                        ETableHeaderItem *ethi)
+{
+	gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (ethi));
+}
+
+static void
+ethi_set_property (GObject *object,
+                   guint property_id,
+                   const GValue *value,
+                   GParamSpec *pspec)
+{
+	GnomeCanvasItem *item;
+	ETableHeaderItem *ethi;
+
+	item = GNOME_CANVAS_ITEM (object);
+	ethi = E_TABLE_HEADER_ITEM (object);
+
+	switch (property_id) {
+	case PROP_TABLE_HEADER:
+		ethi_drop_table_header (ethi);
+		ethi_add_table_header (ethi, E_TABLE_HEADER (g_value_get_object (value)));
+		break;
+
+	case PROP_FULL_HEADER:
+		if (ethi->full_header)
+			g_object_unref (ethi->full_header);
+		ethi->full_header = E_TABLE_HEADER (g_value_get_object (value));
+		if (ethi->full_header)
+			g_object_ref (ethi->full_header);
+		break;
+
+	case PROP_DND_CODE:
+		g_free (ethi->dnd_code);
+		ethi->dnd_code = g_strdup (g_value_get_string (value));
+		break;
+
+	case PROP_TABLE_FONT_DESC:
+		ethi_font_set (ethi, g_value_get_boxed (value));
+		break;
+
+	case PROP_SORT_INFO:
+		if (ethi->sort_info) {
+			if (ethi->sort_info_changed_id)
+				g_signal_handler_disconnect (
+					ethi->sort_info,
+					ethi->sort_info_changed_id);
+
+			if (ethi->group_info_changed_id)
+				g_signal_handler_disconnect (
+					ethi->sort_info,
+					ethi->group_info_changed_id);
+			g_object_unref (ethi->sort_info);
+		}
+		ethi->sort_info = g_value_get_object (value);
+		g_object_ref (ethi->sort_info);
+		ethi->sort_info_changed_id =
+			g_signal_connect (
+				ethi->sort_info, "sort_info_changed",
+				G_CALLBACK (ethi_sort_info_changed), ethi);
+		ethi->group_info_changed_id =
+			g_signal_connect (
+				ethi->sort_info, "group_info_changed",
+				G_CALLBACK (ethi_sort_info_changed), ethi);
+		break;
+	case PROP_TABLE:
+		if (g_value_get_object (value))
+			ethi->table = E_TABLE (g_value_get_object (value));
+		else
+			ethi->table = NULL;
+		break;
+	case PROP_TREE:
+		if (g_value_get_object (value))
+			ethi->tree = E_TREE (g_value_get_object (value));
+		else
+			ethi->tree = NULL;
+		break;
+	}
+	gnome_canvas_item_request_update (item);
+}
+
+static void
+ethi_get_property (GObject *object,
+                   guint property_id,
+                   GValue *value,
+                   GParamSpec *pspec)
+{
+	ETableHeaderItem *ethi;
+
+	ethi = E_TABLE_HEADER_ITEM (object);
+
+	switch (property_id) {
+	case PROP_FULL_HEADER:
+		g_value_set_object (value, ethi->full_header);
+		break;
+	case PROP_DND_CODE:
+		g_value_set_string (value, ethi->dnd_code);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+		break;
+	}
+}
+
+static gint
+ethi_find_col_by_x (ETableHeaderItem *ethi,
+                    gint x)
+{
+	const gint cols = e_table_header_count (ethi->eth);
+	gint x1 = 0;
+	gint col;
+
+	d (g_print ("%s:%d: x = %d, x1 = %d\n", __FUNCTION__, __LINE__, x, x1));
+
+	x1 += ethi->group_indent_width;
+
+	if (x < x1) {
+		d (g_print ("%s:%d: Returning 0\n", __FUNCTION__, __LINE__));
+		return 0;
+	}
+
+	for (col = 0; col < cols; col++) {
+		ETableCol *ecol = e_table_header_get_column (ethi->eth, col);
+
+		if ((x >= x1) && (x <= x1 + ecol->width)) {
+			d (g_print ("%s:%d: Returning %d\n", __FUNCTION__, __LINE__, col));
+			return col;
+		}
+
+		x1 += ecol->width;
+	}
+	d (g_print ("%s:%d: Returning %d\n", __FUNCTION__, __LINE__, cols - 1));
+	return cols - 1;
+}
+
+static gint
+ethi_find_col_by_x_nearest (ETableHeaderItem *ethi,
+                            gint x)
+{
+	const gint cols = e_table_header_count (ethi->eth);
+	gint x1 = 0;
+	gint col;
+
+	x1 += ethi->group_indent_width;
+
+	if (x < x1)
+		return 0;
+
+	for (col = 0; col < cols; col++) {
+		ETableCol *ecol = e_table_header_get_column (ethi->eth, col);
+
+		x1 += (ecol->width / 2);
+
+		if (x <= x1)
+			return col;
+
+		x1 += (ecol->width + 1) / 2;
+	}
+	return col;
+}
+
+static void
+ethi_remove_drop_marker (ETableHeaderItem *ethi)
+{
+	if (ethi->drag_mark == -1)
+		return;
+
+	gtk_widget_hide (arrow_up);
+	gtk_widget_hide (arrow_down);
+
+	ethi->drag_mark = -1;
+}
+
+static GtkWidget *
+make_shaped_window_from_xpm (const gchar **xpm)
+{
+	GdkPixbuf *pixbuf;
+	GtkWidget *win, *pix;
+
+	pixbuf = gdk_pixbuf_new_from_xpm_data (xpm);
+
+	win = gtk_window_new (GTK_WINDOW_POPUP);
+	gtk_window_set_type_hint (GTK_WINDOW (win), GDK_WINDOW_TYPE_HINT_NOTIFICATION);
+
+	pix = gtk_image_new_from_pixbuf (pixbuf);
+	gtk_widget_realize (win);
+	gtk_container_add (GTK_CONTAINER (win), pix);
+
+	g_object_unref (pixbuf);
+
+	return win;
+}
+
+static void
+ethi_add_drop_marker (ETableHeaderItem *ethi,
+                      gint col,
+                      gboolean recreate)
+{
+	GnomeCanvas *canvas;
+	GtkAdjustment *adjustment;
+	GdkWindow *window;
+	gint rx, ry;
+	gint x;
+
+	if (!recreate && ethi->drag_mark == col)
+		return;
+
+	ethi->drag_mark = col;
+
+	x = e_table_header_col_diff (ethi->eth, 0, col);
+	if (col > 0)
+		x += ethi->group_indent_width;
+
+	if (!arrow_up) {
+		arrow_up   = make_shaped_window_from_xpm (arrow_up_xpm);
+		arrow_down = make_shaped_window_from_xpm (arrow_down_xpm);
+	}
+
+	canvas = GNOME_CANVAS_ITEM (ethi)->canvas;
+	window = gtk_widget_get_window (GTK_WIDGET (canvas));
+	gdk_window_get_origin (window, &rx, &ry);
+
+	adjustment = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (canvas));
+	rx -= gtk_adjustment_get_value (adjustment);
+
+	adjustment = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (canvas));
+	ry -= gtk_adjustment_get_value (adjustment);
+
+	gtk_window_move (
+		GTK_WINDOW (arrow_down),
+		rx + x - ARROW_PTR,
+		ry - ARROW_DOWN_HEIGHT);
+	gtk_widget_show_all (arrow_down);
+
+	gtk_window_move (
+		GTK_WINDOW (arrow_up),
+		rx + x - ARROW_PTR,
+		ry + ethi->height);
+	gtk_widget_show_all (arrow_up);
+}
+
+static void
+ethi_add_destroy_marker (ETableHeaderItem *ethi)
+{
+	gdouble x1;
+
+	if (ethi->remove_item)
+		g_object_run_dispose (G_OBJECT (ethi->remove_item));
+
+	x1 = (gdouble) e_table_header_col_diff (ethi->eth, 0, ethi->drag_col);
+	if (ethi->drag_col > 0)
+		x1 += ethi->group_indent_width;
+
+	ethi->remove_item = gnome_canvas_item_new (
+		GNOME_CANVAS_GROUP (GNOME_CANVAS_ITEM (ethi)->canvas->root),
+		gnome_canvas_rect_get_type (),
+		"x1", x1 + 1,
+		"y1", (gdouble) 1,
+		"x2", (gdouble) x1 + e_table_header_col_diff (
+			ethi->eth, ethi->drag_col, ethi->drag_col + 1) - 2,
+
+		"y2", (gdouble) ethi->height - 2,
+		"fill_color_rgba", 0xFF000080,
+		NULL);
+}
+
+static void
+ethi_remove_destroy_marker (ETableHeaderItem *ethi)
+{
+	if (!ethi->remove_item)
+		return;
+
+	g_object_run_dispose (G_OBJECT (ethi->remove_item));
+	ethi->remove_item = NULL;
+}
+
+#if 0
+static gboolean
+moved (ETableHeaderItem *ethi,
+       guint col,
+       guint model_col)
+{
+	if (col == -1)
+		return TRUE;
+	ecol = e_table_header_get_column (ethi->eth, col);
+	if (ecol->col_idx == model_col)
+		return FALSE;
+	if (col > 0) {
+		ecol = e_table_header_get_column (ethi->eth, col - 1);
+		if (ecol->col_idx == model_col)
+			return FALSE;
+	}
+	return TRUE;
+}
+#endif
+
+static void
+do_drag_motion (ETableHeaderItem *ethi,
+                GdkDragContext *context,
+                gint x,
+                gint y,
+                guint time,
+                gboolean recreate)
+{
+	if ((x >= 0) && (x <= (ethi->width)) &&
+	    (y >= 0) && (y <= (ethi->height))) {
+		GdkDragAction suggested_action;
+		gint col;
+		d (g_print ("In header\n"));
+
+		col = ethi_find_col_by_x_nearest (ethi, x);
+		suggested_action = gdk_drag_context_get_suggested_action (context);
+
+		if (ethi->drag_col != -1 && (col == ethi->drag_col ||
+		    col == ethi->drag_col + 1)) {
+			if (ethi->drag_col != -1)
+				ethi_remove_destroy_marker (ethi);
+
+			ethi_remove_drop_marker (ethi);
+			gdk_drag_status (context, suggested_action, time);
+		}
+		else if (col != -1) {
+			if (ethi->drag_col != -1)
+				ethi_remove_destroy_marker (ethi);
+
+			ethi_add_drop_marker (ethi, col, recreate);
+			gdk_drag_status (context, suggested_action, time);
+		} else {
+			ethi_remove_drop_marker (ethi);
+			if (ethi->drag_col != -1)
+				ethi_add_destroy_marker (ethi);
+		}
+	} else {
+		ethi_remove_drop_marker (ethi);
+		if (ethi->drag_col != -1)
+			ethi_add_destroy_marker (ethi);
+	}
+}
+
+static gboolean
+scroll_timeout (gpointer data)
+{
+	ETableHeaderItem *ethi = data;
+	gint dx = 0;
+	GtkAdjustment *adjustment;
+	GtkScrollable *scrollable;
+	gdouble hadjustment_value;
+	gdouble vadjustment_value;
+	gdouble page_size;
+	gdouble lower;
+	gdouble upper;
+	gdouble value;
+
+	if (ethi->scroll_direction & ET_SCROLL_RIGHT)
+		dx += 20;
+	if (ethi->scroll_direction & ET_SCROLL_LEFT)
+		dx -= 20;
+
+	scrollable = GTK_SCROLLABLE (GNOME_CANVAS_ITEM (ethi)->canvas);
+
+	adjustment = gtk_scrollable_get_hadjustment (scrollable);
+	hadjustment_value = gtk_adjustment_get_value (adjustment);
+
+	adjustment = gtk_scrollable_get_vadjustment (scrollable);
+	vadjustment_value = gtk_adjustment_get_value (adjustment);
+
+	value = hadjustment_value;
+
+	adjustment = gtk_scrollable_get_hadjustment (scrollable);
+	page_size = gtk_adjustment_get_page_size (adjustment);
+	lower = gtk_adjustment_get_lower (adjustment);
+	upper = gtk_adjustment_get_upper (adjustment);
+
+	gtk_adjustment_set_value (
+		adjustment, CLAMP (
+		hadjustment_value + dx, lower, upper - page_size));
+
+	hadjustment_value = gtk_adjustment_get_value (adjustment);
+
+	if (hadjustment_value != value)
+		do_drag_motion (
+			ethi,
+			ethi->last_drop_context,
+			ethi->last_drop_x + hadjustment_value,
+			ethi->last_drop_y + vadjustment_value,
+			ethi->last_drop_time,
+			TRUE);
+
+	return TRUE;
+}
+
+static void
+scroll_on (ETableHeaderItem *ethi,
+           guint scroll_direction)
+{
+	if (ethi->scroll_idle_id == 0 || scroll_direction != ethi->scroll_direction) {
+		if (ethi->scroll_idle_id != 0)
+			g_source_remove (ethi->scroll_idle_id);
+		ethi->scroll_direction = scroll_direction;
+		ethi->scroll_idle_id = g_timeout_add (100, scroll_timeout, ethi);
+	}
+}
+
+static void
+scroll_off (ETableHeaderItem *ethi)
+{
+	if (ethi->scroll_idle_id) {
+		g_source_remove (ethi->scroll_idle_id);
+		ethi->scroll_idle_id = 0;
+	}
+}
+
+static void
+context_destroyed (gpointer data)
+{
+	ETableHeaderItem *ethi = data;
+
+	ethi->last_drop_x       = 0;
+	ethi->last_drop_y       = 0;
+	ethi->last_drop_time    = 0;
+	ethi->last_drop_context = NULL;
+	scroll_off (ethi);
+
+	g_object_unref (ethi);
+}
+
+static void
+context_connect (ETableHeaderItem *ethi,
+                 GdkDragContext *context)
+{
+	if (g_dataset_get_data (context, "e-table-header-item") == NULL)
+		g_dataset_set_data_full (
+			context, "e-table-header-item",
+			g_object_ref (ethi), context_destroyed);
+}
+
+static gboolean
+ethi_drag_motion (GtkWidget *widget,
+                  GdkDragContext *context,
+                  gint x,
+                  gint y,
+                  guint time,
+                  ETableHeaderItem *ethi)
+{
+	GtkAllocation allocation;
+	GtkAdjustment *adjustment;
+	GList *targets;
+	gdouble hadjustment_value;
+	gdouble vadjustment_value;
+	gchar *droptype, *headertype;
+	guint direction = 0;
+
+	gdk_drag_status (context, 0, time);
+
+	targets = gdk_drag_context_list_targets (context);
+	droptype = gdk_atom_name (GDK_POINTER_TO_ATOM (targets->data));
+	headertype = g_strdup_printf (
+		"%s-%s", TARGET_ETABLE_COL_TYPE, ethi->dnd_code);
+
+	if (strcmp (droptype, headertype) != 0) {
+		g_free (headertype);
+		return FALSE;
+	}
+
+	g_free (headertype);
+
+	gtk_widget_get_allocation (widget, &allocation);
+
+	if (x < 20)
+		direction |= ET_SCROLL_LEFT;
+	if (x > allocation.width - 20)
+		direction |= ET_SCROLL_RIGHT;
+
+	ethi->last_drop_x = x;
+	ethi->last_drop_y = y;
+	ethi->last_drop_time = time;
+	ethi->last_drop_context = context;
+	context_connect (ethi, context);
+
+	adjustment = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (widget));
+	hadjustment_value = gtk_adjustment_get_value (adjustment);
+
+	adjustment = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (widget));
+	vadjustment_value = gtk_adjustment_get_value (adjustment);
+
+	do_drag_motion (
+		ethi, context,
+		x + hadjustment_value,
+		y + vadjustment_value,
+		time, FALSE);
+
+	if (direction != 0)
+		scroll_on (ethi, direction);
+	else
+		scroll_off (ethi);
+
+	return TRUE;
+}
+
+static void
+ethi_drag_end (GtkWidget *canvas,
+               GdkDragContext *context,
+               ETableHeaderItem *ethi)
+{
+	ethi_remove_drop_marker (ethi);
+	ethi_remove_destroy_marker (ethi);
+	ethi->drag_col = -1;
+	scroll_off (ethi);
+}
+
+static void
+ethi_drag_data_received (GtkWidget *canvas,
+                         GdkDragContext *drag_context,
+                         gint x,
+                         gint y,
+                         GtkSelectionData *selection_data,
+                         guint info,
+                         guint time,
+                         ETableHeaderItem *ethi)
+{
+	const guchar *data;
+	gint found = FALSE;
+	gint count;
+	gint column;
+	gint drop_col;
+	gint i;
+
+	data = gtk_selection_data_get_data (selection_data);
+
+	if (data != NULL) {
+		count = e_table_header_count (ethi->eth);
+		column = atoi ((gchar *) data);
+		drop_col = ethi->drop_col;
+		ethi->drop_col = -1;
+
+		if (column >= 0) {
+			for (i = 0; i < count; i++) {
+				ETableCol *ecol = e_table_header_get_column (ethi->eth, i);
+				if (ecol->col_idx == column) {
+					e_table_header_move (ethi->eth, i, drop_col);
+					found = TRUE;
+					break;
+				}
+			}
+			if (!found) {
+				count = e_table_header_count (ethi->full_header);
+				for (i = 0; i < count; i++) {
+					ETableCol *ecol;
+
+					ecol  = e_table_header_get_column (
+						ethi->full_header, i);
+
+					if (ecol->col_idx == column) {
+						e_table_header_add_column (
+							ethi->eth, ecol,
+							drop_col);
+						break;
+					}
+				}
+			}
+		}
+	}
+	ethi_remove_drop_marker (ethi);
+	gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (ethi));
+}
+
+static void
+ethi_drag_data_get (GtkWidget *canvas,
+                    GdkDragContext *context,
+                    GtkSelectionData *selection_data,
+                    guint info,
+                    guint time,
+                    ETableHeaderItem *ethi)
+{
+	if (ethi->drag_col != -1) {
+		ETableCol *ecol = e_table_header_get_column (ethi->eth, ethi->drag_col);
+
+		gchar *string = g_strdup_printf ("%d", ecol->col_idx);
+		gtk_selection_data_set (
+			selection_data,
+			GDK_SELECTION_TYPE_STRING,
+			sizeof (string[0]),
+			(guchar *) string,
+			strlen (string));
+		g_free (string);
+	}
+}
+
+static gboolean
+ethi_drag_drop (GtkWidget *canvas,
+                GdkDragContext *context,
+                gint x,
+                gint y,
+                guint time,
+                ETableHeaderItem *ethi)
+{
+	gboolean successful = FALSE;
+
+	if ((x >= 0) && (x <= (ethi->width)) &&
+	    (y >= 0) && (y <= (ethi->height))) {
+		gint col;
+
+		col = ethi_find_col_by_x_nearest (ethi, x);
+
+		ethi_add_drop_marker (ethi, col, FALSE);
+
+		ethi->drop_col = col;
+
+		if (col != -1) {
+			gchar *target = g_strdup_printf (
+				"%s-%s", TARGET_ETABLE_COL_TYPE, ethi->dnd_code);
+			d (g_print ("ethi -  %s\n", target));
+			gtk_drag_get_data (
+				canvas, context,
+				gdk_atom_intern (target, FALSE),
+				time);
+			g_free (target);
+		}
+	}
+	gtk_drag_finish (context, successful, successful, time);
+	scroll_off (ethi);
+	return successful;
+}
+
+static void
+ethi_drag_leave (GtkWidget *widget,
+                 GdkDragContext *context,
+                 guint time,
+                 ETableHeaderItem *ethi)
+{
+	ethi_remove_drop_marker (ethi);
+	if (ethi->drag_col != -1)
+		ethi_add_destroy_marker (ethi);
+}
+
+static void
+ethi_realize (GnomeCanvasItem *item)
+{
+	ETableHeaderItem *ethi = E_TABLE_HEADER_ITEM (item);
+	GtkStyle *style;
+	GtkTargetEntry  ethi_drop_types[] = {
+		{ (gchar *) TARGET_ETABLE_COL_TYPE, 0, TARGET_ETABLE_COL_HEADER },
+	};
+
+	if (GNOME_CANVAS_ITEM_CLASS (ethi_parent_class)-> realize)
+		(*GNOME_CANVAS_ITEM_CLASS (ethi_parent_class)->realize)(item);
+
+	style = gtk_widget_get_style (GTK_WIDGET (item->canvas));
+
+	if (!ethi->font_desc)
+		ethi_font_set (ethi, style->font_desc);
+
+	/*
+	 * Now, configure DnD
+	 */
+	ethi_drop_types[0].target = g_strdup_printf (
+		"%s-%s", ethi_drop_types[0].target, ethi->dnd_code);
+	gtk_drag_dest_set (
+		GTK_WIDGET (item->canvas), 0, ethi_drop_types,
+		G_N_ELEMENTS (ethi_drop_types), GDK_ACTION_MOVE);
+	g_free ((gpointer) ethi_drop_types[0].target);
+
+	/* Drop signals */
+	ethi->drag_motion_id = g_signal_connect (
+		item->canvas, "drag_motion",
+		G_CALLBACK (ethi_drag_motion), ethi);
+	ethi->drag_leave_id = g_signal_connect (
+		item->canvas, "drag_leave",
+		G_CALLBACK (ethi_drag_leave), ethi);
+	ethi->drag_drop_id = g_signal_connect (
+		item->canvas, "drag_drop",
+		G_CALLBACK (ethi_drag_drop), ethi);
+	ethi->drag_data_received_id = g_signal_connect (
+		item->canvas, "drag_data_received",
+		G_CALLBACK (ethi_drag_data_received), ethi);
+
+	/* Drag signals */
+	ethi->drag_end_id = g_signal_connect (
+		item->canvas, "drag_end",
+		G_CALLBACK (ethi_drag_end), ethi);
+	ethi->drag_data_get_id = g_signal_connect (
+		item->canvas, "drag_data_get",
+		G_CALLBACK (ethi_drag_data_get), ethi);
+
+}
+
+static void
+ethi_unrealize (GnomeCanvasItem *item)
+{
+	ETableHeaderItem *ethi = E_TABLE_HEADER_ITEM (item);
+
+	if (ethi->font_desc != NULL) {
+		pango_font_description_free (ethi->font_desc);
+		ethi->font_desc = NULL;
+	}
+
+	g_signal_handler_disconnect (item->canvas, ethi->drag_motion_id);
+	g_signal_handler_disconnect (item->canvas, ethi->drag_leave_id);
+	g_signal_handler_disconnect (item->canvas, ethi->drag_drop_id);
+	g_signal_handler_disconnect (item->canvas, ethi->drag_data_received_id);
+
+	g_signal_handler_disconnect (item->canvas, ethi->drag_end_id);
+	g_signal_handler_disconnect (item->canvas, ethi->drag_data_get_id);
+
+	gtk_drag_dest_unset (GTK_WIDGET (item->canvas));
+
+	if (GNOME_CANVAS_ITEM_CLASS (ethi_parent_class)->unrealize)
+		(*GNOME_CANVAS_ITEM_CLASS (ethi_parent_class)->unrealize)(item);
+}
+
+static void
+ethi_draw (GnomeCanvasItem *item,
+           cairo_t *cr,
+           gint x,
+           gint y,
+           gint width,
+           gint height)
+{
+	ETableHeaderItem *ethi = E_TABLE_HEADER_ITEM (item);
+	GnomeCanvas *canvas = item->canvas;
+	const gint cols = e_table_header_count (ethi->eth);
+	gint x1, x2;
+	gint col;
+	GHashTable *arrows = g_hash_table_new (NULL, NULL);
+	GtkStyleContext *context;
+
+	if (ethi->sort_info) {
+		gint length;
+		gint i;
+
+		length = e_table_sort_info_grouping_get_count (ethi->sort_info);
+		for (i = 0; i < length; i++) {
+			ETableSortColumn column;
+
+			column = e_table_sort_info_grouping_get_nth (
+				ethi->sort_info, i);
+
+			g_hash_table_insert (
+				arrows,
+				GINT_TO_POINTER ((gint) column.column),
+				GINT_TO_POINTER (
+					column.ascending ?
+					E_TABLE_COL_ARROW_DOWN :
+					E_TABLE_COL_ARROW_UP));
+		}
+
+		length = e_table_sort_info_sorting_get_count (ethi->sort_info);
+		for (i = 0; i < length; i++) {
+			ETableSortColumn column;
+
+			column = e_table_sort_info_sorting_get_nth (
+				ethi->sort_info, i);
+
+			g_hash_table_insert (
+				arrows,
+				GINT_TO_POINTER ((gint) column.column),
+				GINT_TO_POINTER (
+					column.ascending ?
+					E_TABLE_COL_ARROW_DOWN :
+					E_TABLE_COL_ARROW_UP));
+		}
+	}
+
+	ethi->width = e_table_header_total_width (ethi->eth) + ethi->group_indent_width;
+	x1 = x2 = 0;
+	x2 += ethi->group_indent_width;
+
+	context = gtk_widget_get_style_context (GTK_WIDGET (canvas));
+
+	for (col = 0; col < cols; col++, x1 = x2) {
+		ETableCol *ecol = e_table_header_get_column (ethi->eth, col);
+		gint col_width;
+		GtkRegionFlags flags = 0;
+
+		col_width = ecol->width;
+
+		x2 += col_width;
+
+		if (x1 > (x + width))
+			break;
+
+		if (x2 < x)
+			continue;
+
+		if (x2 <= x1)
+			continue;
+
+		if (((col + 1) % 2) == 0)
+			flags |= GTK_REGION_EVEN;
+		else
+			flags |= GTK_REGION_ODD;
+
+		if (col == 0)
+			flags |= GTK_REGION_FIRST;
+
+		if (col + 1 == cols)
+			flags |= GTK_REGION_LAST;
+
+		gtk_style_context_save (context);
+		gtk_style_context_add_region (
+			context, GTK_STYLE_REGION_COLUMN_HEADER, flags);
+
+		e_table_header_draw_button (
+			cr, ecol, GTK_WIDGET (canvas),
+			x1 - x, -y, width, height,
+			x2 - x1, ethi->height,
+			(ETableColArrow) g_hash_table_lookup (
+			arrows, GINT_TO_POINTER (ecol->col_idx)));
+
+		gtk_style_context_restore (context);
+	}
+
+	g_hash_table_destroy (arrows);
+}
+
+static GnomeCanvasItem *
+ethi_point (GnomeCanvasItem *item,
+            gdouble x,
+            gdouble y,
+            gint cx,
+            gint cy)
+{
+	return item;
+}
+
+/*
+ * is_pointer_on_division:
+ *
+ * Returns whether @pos is a column header division;  If @the_total is not NULL,
+ * then the actual position is returned here.  If @return_ecol is not NULL,
+ * then the ETableCol that actually contains this point is returned here
+ */
+static gboolean
+is_pointer_on_division (ETableHeaderItem *ethi,
+                        gint pos,
+                        gint *the_total,
+                        gint *return_col)
+{
+	const gint cols = e_table_header_count (ethi->eth);
+	gint col, total;
+
+	total = 0;
+	for (col = 0; col < cols; col++) {
+		ETableCol *ecol = e_table_header_get_column (ethi->eth, col);
+
+		if (col == 0)
+			total += ethi->group_indent_width;
+
+		total += ecol->width;
+
+		if ((total - TOLERANCE < pos) && (pos < total + TOLERANCE)) {
+			if (return_col)
+				*return_col = col;
+			if (the_total)
+				*the_total = total;
+
+			return TRUE;
+		}
+		if (return_col)
+			*return_col = col;
+
+		if (total > pos + TOLERANCE)
+			return FALSE;
+	}
+
+	return FALSE;
+}
+
+#define convert(c,sx,sy,x,y) gnome_canvas_w2c (c,sx,sy,x,y)
+
+static void
+set_cursor (ETableHeaderItem *ethi,
+            gint pos)
+{
+	GnomeCanvas *canvas;
+	GdkWindow *window;
+	gboolean resizable = FALSE;
+	gint col;
+
+	canvas = GNOME_CANVAS_ITEM (ethi)->canvas;
+	window = gtk_widget_get_window (GTK_WIDGET (canvas));
+
+	/* We might be invoked before we are realized */
+	if (window == NULL)
+		return;
+
+	if (is_pointer_on_division (ethi, pos, NULL, &col)) {
+		gint last_col = ethi->eth->col_count - 1;
+		ETableCol *ecol = e_table_header_get_column (ethi->eth, col);
+
+		/* Last column is not resizable */
+		if (ecol->resizable && col != last_col) {
+			gint c = col + 1;
+
+			/* Column is not resizable if all columns after it
+			 * are also not resizable */
+			for (; c <= last_col; c++) {
+				ETableCol *ecol2;
+
+				ecol2 = e_table_header_get_column (ethi->eth, c);
+				if (ecol2->resizable) {
+					resizable = TRUE;
+					break;
+				}
+			}
+		}
+	}
+
+	if (resizable)
+		gdk_window_set_cursor (window, ethi->resize_cursor);
+	else
+		gdk_window_set_cursor (window, NULL);
+}
+
+static void
+ethi_end_resize (ETableHeaderItem *ethi)
+{
+	ethi->resize_col = -1;
+	ethi->resize_guide = GINT_TO_POINTER (0);
+
+	if (ethi->table)
+		e_table_thaw_state_change (ethi->table);
+	else if (ethi->tree)
+		e_tree_thaw_state_change (ethi->tree);
+
+	gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (ethi));
+}
+
+static gboolean
+ethi_maybe_start_drag (ETableHeaderItem *ethi,
+                       GdkEventMotion *event)
+{
+	if (!ethi->maybe_drag)
+		return FALSE;
+
+	if (ethi->eth->col_count < 2) {
+		ethi->maybe_drag = FALSE;
+		return FALSE;
+	}
+
+	if (MAX (abs (ethi->click_x - event->x),
+		 abs (ethi->click_y - event->y)) <= 3)
+		return FALSE;
+
+	return TRUE;
+}
+
+static void
+ethi_start_drag (ETableHeaderItem *ethi,
+                 GdkEvent *event)
+{
+	GtkWidget *widget = GTK_WIDGET (GNOME_CANVAS_ITEM (ethi)->canvas);
+	GtkTargetList *list;
+	GdkDragContext *context;
+	ETableCol *ecol;
+	gint col_width;
+	cairo_surface_t *s;
+	cairo_t *cr;
+
+	gint group_indent = 0;
+	GHashTable *arrows = g_hash_table_new (NULL, NULL);
+
+	GtkTargetEntry  ethi_drag_types[] = {
+		{ (gchar *) TARGET_ETABLE_COL_TYPE, 0, TARGET_ETABLE_COL_HEADER },
+	};
+
+	widget = GTK_WIDGET (GNOME_CANVAS_ITEM (ethi)->canvas);
+	ethi->drag_col = ethi_find_col_by_x (ethi, event->motion.x);
+
+	if (ethi->drag_col == -1)
+		return;
+
+	if (ethi->sort_info) {
+		gint length = e_table_sort_info_grouping_get_count (ethi->sort_info);
+		gint i;
+		for (i = 0; i < length; i++) {
+			ETableSortColumn column =
+				e_table_sort_info_grouping_get_nth (
+					ethi->sort_info, i);
+			group_indent++;
+			g_hash_table_insert (
+				arrows,
+				GINT_TO_POINTER ((gint) column.column),
+				GINT_TO_POINTER (
+					column.ascending ?
+					E_TABLE_COL_ARROW_DOWN :
+					E_TABLE_COL_ARROW_UP));
+		}
+		length = e_table_sort_info_sorting_get_count (ethi->sort_info);
+		for (i = 0; i < length; i++) {
+			ETableSortColumn column =
+				e_table_sort_info_sorting_get_nth (
+					ethi->sort_info, i);
+
+			g_hash_table_insert (
+				arrows,
+				GINT_TO_POINTER ((gint) column.column),
+				GINT_TO_POINTER (
+					column.ascending ?
+					E_TABLE_COL_ARROW_DOWN :
+					E_TABLE_COL_ARROW_UP));
+		}
+	}
+
+	ethi_drag_types[0].target = g_strdup_printf (
+		"%s-%s", ethi_drag_types[0].target, ethi->dnd_code);
+	list = gtk_target_list_new (
+		ethi_drag_types, G_N_ELEMENTS (ethi_drag_types));
+	context = gtk_drag_begin (widget, list, GDK_ACTION_MOVE, 1, event);
+	g_free ((gpointer) ethi_drag_types[0].target);
+
+	ecol = e_table_header_get_column (ethi->eth, ethi->drag_col);
+	col_width = ecol->width;
+	s = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, col_width, ethi->height);
+	cr = cairo_create (s);
+
+	e_table_header_draw_button (
+		cr, ecol,
+		widget, 0, 0,
+		col_width, ethi->height,
+		col_width, ethi->height,
+		(ETableColArrow) g_hash_table_lookup (
+			arrows, GINT_TO_POINTER (ecol->col_idx)));
+	gtk_drag_set_icon_surface (context, s);
+	cairo_surface_destroy (s);
+
+	ethi->maybe_drag = FALSE;
+	g_hash_table_destroy (arrows);
+}
+
+typedef struct {
+	ETableHeaderItem *ethi;
+	gint col;
+} EthiHeaderInfo;
+
+static void
+ethi_popup_sort_ascending (GtkWidget *widget,
+                           EthiHeaderInfo *info)
+{
+	ETableCol *col;
+	gint model_col = -1;
+	gint length;
+	gint i;
+	gint found = FALSE;
+	ETableHeaderItem *ethi = info->ethi;
+
+	col = e_table_header_get_column (ethi->eth, info->col);
+	if (col->sortable)
+		model_col = col->col_idx;
+
+	length = e_table_sort_info_grouping_get_count (ethi->sort_info);
+	for (i = 0; i < length; i++) {
+		ETableSortColumn column = e_table_sort_info_grouping_get_nth (
+			ethi->sort_info, i);
+
+		if (model_col == column.column) {
+			column.ascending = 1;
+			e_table_sort_info_grouping_set_nth (
+				ethi->sort_info, i, column);
+			found = 1;
+			break;
+		}
+	}
+	if (!found) {
+		length = e_table_sort_info_sorting_get_count (
+			ethi->sort_info);
+		for (i = 0; i < length; i++) {
+			ETableSortColumn column =
+				e_table_sort_info_sorting_get_nth (
+					ethi->sort_info, i);
+			if (model_col == column.column || model_col == -1) {
+				column.ascending = 1;
+				e_table_sort_info_sorting_set_nth (
+					ethi->sort_info, i, column);
+				found = 1;
+				if (model_col != -1)
+					break;
+			}
+		}
+	}
+	if (!found) {
+		ETableSortColumn column;
+		column.column = model_col;
+		column.ascending =  1;
+		length = e_table_sort_info_sorting_get_count (ethi->sort_info);
+		if (length == 0)
+			length++;
+		e_table_sort_info_sorting_set_nth (ethi->sort_info, length - 1, column);
+	}
+}
+
+static void
+ethi_popup_sort_descending (GtkWidget *widget,
+                            EthiHeaderInfo *info)
+{
+	ETableCol *col;
+	gint model_col=-1;
+	gint length;
+	gint i;
+	gint found = FALSE;
+	ETableHeaderItem *ethi = info->ethi;
+
+	col = e_table_header_get_column (ethi->eth, info->col);
+	if (col->sortable)
+		model_col = col->col_idx;
+
+	length = e_table_sort_info_grouping_get_count (ethi->sort_info);
+	for (i = 0; i < length; i++) {
+		ETableSortColumn column = e_table_sort_info_grouping_get_nth (
+			ethi->sort_info, i);
+		if (model_col == column.column) {
+			column.ascending = 0;
+			e_table_sort_info_grouping_set_nth (
+				ethi->sort_info, i, column);
+			found = 1;
+			break;
+		}
+	}
+	if (!found) {
+		length = e_table_sort_info_sorting_get_count (ethi->sort_info);
+		for (i = 0; i < length; i++) {
+			ETableSortColumn column =
+				e_table_sort_info_sorting_get_nth (
+					ethi->sort_info, i);
+
+			if (model_col == column.column || model_col == -1) {
+				column.ascending = 0;
+				e_table_sort_info_sorting_set_nth (
+					ethi->sort_info, i, column);
+				found = 1;
+				if (model_col != -1)
+					break;
+			}
+		}
+	}
+	if (!found) {
+		ETableSortColumn column;
+		column.column = model_col;
+		column.ascending = 0;
+		length = e_table_sort_info_sorting_get_count (ethi->sort_info);
+		if (length == 0)
+			length++;
+		e_table_sort_info_sorting_set_nth (
+			ethi->sort_info, length - 1, column);
+	}
+}
+
+static void
+ethi_popup_unsort (GtkWidget *widget,
+                   EthiHeaderInfo *info)
+{
+	ETableHeaderItem *ethi = info->ethi;
+
+	e_table_sort_info_grouping_truncate (ethi->sort_info, 0);
+	e_table_sort_info_sorting_truncate (ethi->sort_info, 0);
+}
+
+static void
+ethi_popup_group_field (GtkWidget *widget,
+                        EthiHeaderInfo *info)
+{
+	ETableCol *col;
+	gint model_col;
+	ETableHeaderItem *ethi = info->ethi;
+	ETableSortColumn column;
+
+	col = e_table_header_get_column (ethi->eth, info->col);
+	model_col = col->col_idx;
+
+	column.column = model_col;
+	column.ascending = 1;
+	e_table_sort_info_grouping_set_nth (ethi->sort_info, 0, column);
+	e_table_sort_info_grouping_truncate (ethi->sort_info, 1);
+}
+
+static void
+ethi_popup_group_box (GtkWidget *widget,
+                      EthiHeaderInfo *info)
+{
+}
+
+static void
+ethi_popup_remove_column (GtkWidget *widget,
+                          EthiHeaderInfo *info)
+{
+	e_table_header_remove (info->ethi->eth, info->col);
+}
+
+static void
+ethi_popup_field_chooser (GtkWidget *widget,
+                          EthiHeaderInfo *info)
+{
+	GtkWidget *etfcd = info->ethi->etfcd.widget;
+
+	if (etfcd) {
+		gtk_window_present (GTK_WINDOW (etfcd));
+
+		return;
+	}
+
+	info->ethi->etfcd.widget = e_table_field_chooser_dialog_new ();
+	etfcd = info->ethi->etfcd.widget;
+
+	g_object_add_weak_pointer (G_OBJECT (etfcd), &info->ethi->etfcd.pointer);
+
+	g_object_set (
+		info->ethi->etfcd.widget,
+		"full_header", info->ethi->full_header,
+		"header", info->ethi->eth,
+		"dnd_code", info->ethi->dnd_code,
+		NULL);
+
+	gtk_widget_show (etfcd);
+}
+
+static void
+ethi_popup_alignment (GtkWidget *widget,
+                      EthiHeaderInfo *info)
+{
+}
+
+static void
+ethi_popup_best_fit (GtkWidget *widget,
+                     EthiHeaderInfo *info)
+{
+	ETableHeaderItem *ethi = info->ethi;
+	gint width;
+
+	g_signal_emit_by_name (
+		ethi->eth,
+		"request_width",
+		info->col, &width);
+	/* Add 10 to stop it from "..."ing */
+	e_table_header_set_size (ethi->eth, info->col, width + 10);
+
+	gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (ethi));
+
+}
+
+static void
+ethi_popup_format_columns (GtkWidget *widget,
+                           EthiHeaderInfo *info)
+{
+}
+
+static void
+config_destroyed (gpointer data,
+                  GObject *where_object_was)
+{
+	ETableHeaderItem *ethi = data;
+	ethi->config = NULL;
+}
+
+static void
+apply_changes (ETableConfig *config,
+               ETableHeaderItem *ethi)
+{
+	gchar *state = e_table_state_save_to_string (config->state);
+
+	if (ethi->table)
+		e_table_set_state (ethi->table, state);
+	if (ethi->tree)
+		e_tree_set_state (ethi->tree, state);
+	g_free (state);
+
+	gtk_dialog_set_response_sensitive (
+		GTK_DIALOG (config->dialog_toplevel),
+		GTK_RESPONSE_APPLY, FALSE);
+}
+
+static void
+ethi_popup_customize_view (GtkWidget *widget,
+                           EthiHeaderInfo *info)
+{
+	ETableHeaderItem *ethi = info->ethi;
+	ETableState *state;
+	ETableSpecification *spec;
+
+	if (ethi->config)
+		e_table_config_raise (E_TABLE_CONFIG (ethi->config));
+	else {
+		if (ethi->table) {
+			state = e_table_get_state_object (ethi->table);
+			spec = ethi->table->spec;
+		} else if (ethi->tree) {
+			state = e_tree_get_state_object (ethi->tree);
+			spec = e_tree_get_spec (ethi->tree);
+		} else
+			return;
+
+		ethi->config = e_table_config_new (
+				_("Customize Current View"),
+				spec, state, GTK_WINDOW (gtk_widget_get_toplevel (widget)));
+		g_object_weak_ref (
+			G_OBJECT (ethi->config),
+			config_destroyed, ethi);
+		g_signal_connect (
+			ethi->config, "changed",
+			G_CALLBACK (apply_changes), ethi);
+	}
+}
+
+static void
+free_popup_info (GtkWidget *w,
+                 EthiHeaderInfo *info)
+{
+	g_free (info);
+}
+
+/* Bit 1 is always disabled. */
+/* Bit 2 is disabled if not "sortable". */
+/* Bit 4 is disabled if we don't have a pointer to our table object. */
+static EPopupMenu ethi_context_menu[] = {
+	E_POPUP_ITEM (
+		N_("Sort _Ascending"),
+		G_CALLBACK (ethi_popup_sort_ascending), 2),
+	E_POPUP_ITEM (
+		N_("Sort _Descending"),
+		G_CALLBACK (ethi_popup_sort_descending), 2),
+	E_POPUP_ITEM (
+		N_("_Unsort"), G_CALLBACK (ethi_popup_unsort), 0),
+	E_POPUP_SEPARATOR,
+	E_POPUP_ITEM (
+		N_("Group By This _Field"),
+		G_CALLBACK (ethi_popup_group_field), 16),
+	E_POPUP_ITEM (
+		N_("Group By _Box"),
+		G_CALLBACK (ethi_popup_group_box), 128),
+	E_POPUP_SEPARATOR,
+	E_POPUP_ITEM (
+		N_("Remove This _Column"),
+		G_CALLBACK (ethi_popup_remove_column), 8),
+	E_POPUP_ITEM (
+		N_("Add a C_olumn..."),
+		G_CALLBACK (ethi_popup_field_chooser), 0),
+	E_POPUP_SEPARATOR,
+	E_POPUP_ITEM (
+		N_("A_lignment"),
+		G_CALLBACK (ethi_popup_alignment), 128),
+	E_POPUP_ITEM (
+		N_("B_est Fit"),
+		G_CALLBACK (ethi_popup_best_fit), 2),
+	E_POPUP_ITEM (
+		N_("Format Column_s..."),
+		G_CALLBACK (ethi_popup_format_columns), 128),
+	E_POPUP_SEPARATOR,
+	E_POPUP_ITEM (
+		N_("Custo_mize Current View..."),
+		G_CALLBACK (ethi_popup_customize_view), 4),
+	E_POPUP_TERMINATOR
+};
+
+static void
+sort_by_id (GtkWidget *menu_item,
+            ETableHeaderItem *ethi)
+{
+	ETableCol *ecol;
+	gboolean clearfirst;
+	gint col;
+
+	col = GPOINTER_TO_INT (g_object_get_data (
+		G_OBJECT (menu_item), "col-number"));
+	ecol = e_table_header_get_column (ethi->full_header, col);
+	clearfirst = e_table_sort_info_sorting_get_count (ethi->sort_info) > 1;
+
+	if (!clearfirst && ecol &&
+		e_table_sort_info_sorting_get_count (ethi->sort_info) == 1) {
+		ETableSortColumn column;
+
+		column = e_table_sort_info_sorting_get_nth (ethi->sort_info, 0);
+		clearfirst = ecol->sortable && ecol->col_idx != column.column;
+	}
+
+	if (clearfirst)
+		e_table_sort_info_sorting_truncate (ethi->sort_info, 0);
+
+	ethi_change_sort_state (ethi, ecol);
+}
+
+static void
+popup_custom (GtkWidget *menu_item,
+              EthiHeaderInfo *info)
+{
+	ethi_popup_customize_view (menu_item, info);
+}
+
+static void
+ethi_header_context_menu (ETableHeaderItem *ethi,
+                          GdkEvent *button_event)
+{
+	EthiHeaderInfo *info = g_new (EthiHeaderInfo, 1);
+	GtkMenu *popup;
+	gint ncol, sort_count, sort_col;
+	GtkWidget *menu_item, *sub_menu;
+	ETableSortColumn column;
+	gboolean ascending = TRUE;
+	gdouble event_x_win = 0;
+	gdouble event_y_win = 0;
+	guint event_button = 0;
+	guint32 event_time;
+
+	d (g_print ("ethi_header_context_menu: \n"));
+
+	gdk_event_get_button (button_event, &event_button);
+	gdk_event_get_coords (button_event, &event_x_win, &event_y_win);
+	event_time = gdk_event_get_time (button_event);
+
+	info->ethi = ethi;
+	info->col = ethi_find_col_by_x (ethi, event_x_win);
+
+	popup = e_popup_menu_create_with_domain (
+		ethi_context_menu,
+		1 +
+		((ethi->table || ethi->tree) ? 0 : 4) +
+		((e_table_header_count (ethi->eth) > 1) ? 0 : 8),
+		((e_table_sort_info_get_can_group (ethi->sort_info)) ? 0 : 16) +
+		128, info, GETTEXT_PACKAGE);
+
+	menu_item = gtk_menu_item_new_with_mnemonic (_("_Sort By"));
+	gtk_widget_show (menu_item);
+	sub_menu = gtk_menu_new ();
+	gtk_widget_show (sub_menu);
+	gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu_item), sub_menu);
+	gtk_menu_shell_prepend (GTK_MENU_SHELL (popup), menu_item);
+
+	sort_count = e_table_sort_info_sorting_get_count (ethi->sort_info);
+
+	if (sort_count > 1 || sort_count < 1)
+		sort_col = -1; /* Custom sorting */
+	else {
+		column = e_table_sort_info_sorting_get_nth (ethi->sort_info, 0);
+		sort_col = column.column;
+		ascending = column.ascending;
+	}
+
+	/* Custom */
+	menu_item = gtk_check_menu_item_new_with_mnemonic (_("_Custom"));
+	gtk_widget_show (menu_item);
+	gtk_menu_shell_prepend (GTK_MENU_SHELL (sub_menu), menu_item);
+	if (sort_col == -1)
+		gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (menu_item), TRUE);
+	gtk_check_menu_item_set_draw_as_radio (GTK_CHECK_MENU_ITEM (menu_item), TRUE);
+	g_signal_connect (
+		menu_item, "activate",
+		G_CALLBACK (popup_custom), info);
+
+	/* Show a seperator */
+	menu_item = gtk_separator_menu_item_new ();
+	gtk_widget_show (menu_item);
+	gtk_menu_shell_prepend (GTK_MENU_SHELL (sub_menu), menu_item);
+	/* Headers */
+	for (ncol = 0; ncol < ethi->full_header->col_count; ncol++)
+	{
+		gchar *text = NULL;
+
+		if (!ethi->full_header->columns[ncol]->sortable ||
+		    ethi->full_header->columns[ncol]->disabled)
+			continue;
+
+		if (ncol == sort_col) {
+			text = g_strdup_printf (
+				"%s (%s)",
+				ethi->full_header->columns[ncol]->text,
+				ascending ? _("Ascending"):_("Descending"));
+			menu_item = gtk_check_menu_item_new_with_label (text);
+			g_free (text);
+		} else
+			menu_item = gtk_check_menu_item_new_with_label (
+				ethi->full_header->columns[ncol]->text);
+
+		gtk_widget_show (menu_item);
+		gtk_menu_shell_prepend (GTK_MENU_SHELL (sub_menu), menu_item);
+
+		if (ncol == sort_col)
+			gtk_check_menu_item_set_active (
+				GTK_CHECK_MENU_ITEM (menu_item), TRUE);
+		gtk_check_menu_item_set_draw_as_radio (
+			GTK_CHECK_MENU_ITEM (menu_item), TRUE);
+		g_object_set_data (
+			G_OBJECT (menu_item), "col-number",
+			GINT_TO_POINTER (ncol));
+		g_signal_connect (
+			menu_item, "activate",
+			G_CALLBACK (sort_by_id), ethi);
+	}
+
+	g_object_ref_sink (popup);
+	g_signal_connect (
+		popup, "selection-done",
+		G_CALLBACK (free_popup_info), info);
+
+	gtk_menu_popup (
+		GTK_MENU (popup),
+		NULL, NULL, NULL, NULL,
+		event_button, event_time);
+}
+
+static void
+ethi_button_pressed (ETableHeaderItem *ethi,
+                     GdkEvent *button_event)
+{
+	g_signal_emit (ethi, ethi_signals[BUTTON_PRESSED], 0, button_event);
+}
+
+void
+ethi_change_sort_state (ETableHeaderItem *ethi,
+                        ETableCol *col)
+{
+	gint model_col = -1;
+	gint length;
+	gint i;
+	gboolean found = FALSE;
+
+	if (col == NULL)
+		return;
+
+	if (col->sortable)
+		model_col = col->col_idx;
+
+	length = e_table_sort_info_grouping_get_count (ethi->sort_info);
+	for (i = 0; i < length; i++) {
+		ETableSortColumn column;
+
+		column = e_table_sort_info_grouping_get_nth (
+			ethi->sort_info, i);
+
+		if (model_col == column.column || model_col == -1) {
+			gint ascending = column.ascending;
+			ascending = !ascending;
+			column.ascending = ascending;
+			e_table_sort_info_grouping_set_nth (ethi->sort_info, i, column);
+			found = TRUE;
+			if (model_col != -1)
+				break;
+		}
+	}
+
+	if (!found) {
+		length = e_table_sort_info_sorting_get_count (ethi->sort_info);
+		for (i = 0; i < length; i++) {
+			ETableSortColumn column;
+
+			column = e_table_sort_info_sorting_get_nth (
+				ethi->sort_info, i);
+
+			if (model_col == column.column || model_col == -1) {
+				gint ascending = column.ascending;
+
+				if (ascending == 0 && model_col != -1) {
+					/*
+					 * This means the user has clicked twice
+					 * already, lets kill sorting of this column now.
+					 */
+					gint j;
+
+					for (j = i + 1; j < length; j++)
+						e_table_sort_info_sorting_set_nth (
+							ethi->sort_info, j - 1,
+							e_table_sort_info_sorting_get_nth (
+								ethi->sort_info, j));
+
+					e_table_sort_info_sorting_truncate (
+						ethi->sort_info, length - 1);
+					length--;
+					i--;
+				} else {
+					ascending = !ascending;
+					column.ascending = ascending;
+					e_table_sort_info_sorting_set_nth (
+						ethi->sort_info, i, column);
+				}
+				found = TRUE;
+				if (model_col != -1)
+					break;
+			}
+		}
+	}
+
+	if (!found && model_col != -1) {
+		ETableSortColumn column;
+		column.column = model_col;
+		column.ascending = 1;
+		e_table_sort_info_sorting_truncate (ethi->sort_info, 0);
+		e_table_sort_info_sorting_set_nth (ethi->sort_info, 0, column);
+	}
+}
+
+/*
+ * Handles the events on the ETableHeaderItem, particularly it handles resizing
+ */
+static gint
+ethi_event (GnomeCanvasItem *item,
+            GdkEvent *event)
+{
+	ETableHeaderItem *ethi = E_TABLE_HEADER_ITEM (item);
+	GnomeCanvas *canvas = item->canvas;
+	GdkWindow *window;
+	const gboolean resizing = ETHI_RESIZING (ethi);
+	gint x, y, start, col;
+	gint was_maybe_drag = 0;
+	GdkModifierType event_state = 0;
+	guint event_button = 0;
+	guint event_keyval = 0;
+	gdouble event_x_win = 0;
+	gdouble event_y_win = 0;
+	guint32 event_time;
+
+	/* Don't fetch the device here.  GnomeCanvas frequently emits
+	 * synthesized events, and calling gdk_event_get_device() on them
+	 * will trigger a runtime warning.  Fetch the device where needed. */
+	gdk_event_get_button (event, &event_button);
+	gdk_event_get_coords (event, &event_x_win, &event_y_win);
+	gdk_event_get_keyval (event, &event_keyval);
+	gdk_event_get_state (event, &event_state);
+	event_time = gdk_event_get_time (event);
+
+	switch (event->type) {
+	case GDK_ENTER_NOTIFY:
+		convert (canvas, event_x_win, event_y_win, &x, &y);
+		set_cursor (ethi, x);
+		break;
+
+	case GDK_LEAVE_NOTIFY:
+		window = gtk_widget_get_window (GTK_WIDGET (canvas));
+		gdk_window_set_cursor (window, NULL);
+		break;
+
+	case GDK_MOTION_NOTIFY:
+
+		convert (canvas, event_x_win, event_y_win, &x, &y);
+		if (resizing) {
+			gint new_width;
+
+			if (ethi->resize_guide == NULL) {
+				GdkDevice *event_device;
+
+				/* Quick hack until I actually bind the views */
+				ethi->resize_guide = GINT_TO_POINTER (1);
+
+				event_device = gdk_event_get_device (event);
+
+				gnome_canvas_item_grab (
+					item,
+					GDK_POINTER_MOTION_MASK |
+					GDK_BUTTON_RELEASE_MASK,
+					ethi->resize_cursor,
+					event_device,
+					event_time);
+			}
+
+			new_width = x - ethi->resize_start_pos;
+
+			e_table_header_set_size (ethi->eth, ethi->resize_col, new_width);
+
+			gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (ethi));
+		} else if (ethi_maybe_start_drag (ethi, &event->motion)) {
+			ethi_start_drag (ethi, event);
+		} else
+			set_cursor (ethi, x);
+		break;
+
+	case GDK_BUTTON_PRESS:
+		if (event_button > 3)
+			return FALSE;
+
+		convert (canvas, event_x_win, event_y_win, &x, &y);
+
+		if (is_pointer_on_division (ethi, x, &start, &col) &&
+		    event_button == 1) {
+			ETableCol *ecol;
+
+				/*
+				 * Record the important bits.
+				 *
+				 * By setting resize_pos to a non -1 value,
+				 * we know that we are being resized (used in the
+				 * other event handlers).
+				 */
+			ecol = e_table_header_get_column (ethi->eth, col);
+
+			if (!ecol->resizable)
+				break;
+			ethi->resize_col = col;
+			ethi->resize_start_pos = start - ecol->width;
+			ethi->resize_min_width = ecol->min_width;
+
+			if (ethi->table)
+				e_table_freeze_state_change (ethi->table);
+			else if (ethi->tree)
+				e_tree_freeze_state_change (ethi->tree);
+		} else {
+			if (event_button == 1) {
+				ethi->click_x = event_x_win;
+				ethi->click_y = event_y_win;
+				ethi->maybe_drag = TRUE;
+				is_pointer_on_division (ethi, x, &start, &col);
+				ethi->selected_col = col;
+				if (gtk_widget_get_can_focus (GTK_WIDGET (item->canvas)))
+					e_canvas_item_grab_focus (item, TRUE);
+			} else if (event_button == 3) {
+				ethi_header_context_menu (ethi, event);
+			} else
+				ethi_button_pressed (ethi, event);
+		}
+		break;
+
+	case GDK_2BUTTON_PRESS:
+		if (!resizing)
+			break;
+
+		if (event_button != 1)
+			break;
+		else {
+			gint width = 0;
+			g_signal_emit_by_name (
+				ethi->eth,
+				"request_width",
+				(gint) ethi->resize_col, &width);
+			/* Add 10 to stop it from "..."ing */
+			e_table_header_set_size (ethi->eth, ethi->resize_col, width + 10);
+
+			gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (ethi));
+			ethi->maybe_drag = FALSE;
+		}
+		break;
+
+	case GDK_BUTTON_RELEASE: {
+		gboolean needs_ungrab = FALSE;
+
+		was_maybe_drag = ethi->maybe_drag;
+
+		ethi->maybe_drag = FALSE;
+
+		if (ethi->resize_col != -1) {
+			needs_ungrab = (ethi->resize_guide != NULL);
+			ethi_end_resize (ethi);
+		} else if (was_maybe_drag && ethi->sort_info) {
+			ETableCol *ecol;
+
+			col = ethi_find_col_by_x (ethi, event_x_win);
+			ecol = e_table_header_get_column (ethi->eth, col);
+			ethi_change_sort_state (ethi, ecol);
+		}
+
+		if (needs_ungrab)
+			gnome_canvas_item_ungrab (item, event_time);
+
+		break;
+	}
+	case GDK_KEY_PRESS:
+		if ((event_keyval == GDK_KEY_F10) && (event_state & GDK_SHIFT_MASK)) {
+			EthiHeaderInfo *info = g_new (EthiHeaderInfo, 1);
+			ETableCol *ecol;
+			GtkMenu *popup;
+
+			info->ethi = ethi;
+			info->col = ethi->selected_col;
+			ecol = e_table_header_get_column (ethi->eth, info->col);
+
+			popup = e_popup_menu_create_with_domain (
+				ethi_context_menu,
+				1 +
+				(ecol->sortable ? 0 : 2) +
+				((ethi->table || ethi->tree) ? 0 : 4) +
+				((e_table_header_count (ethi->eth) > 1) ? 0 : 8),
+				((e_table_sort_info_get_can_group (
+					ethi->sort_info)) ? 0 : 16) +
+				128, info, GETTEXT_PACKAGE);
+			g_object_ref_sink (popup);
+			g_signal_connect (
+				popup, "selection-done",
+				G_CALLBACK (free_popup_info), info);
+			gtk_menu_popup (
+				GTK_MENU (popup),
+				NULL, NULL, NULL, NULL,
+				0, GDK_CURRENT_TIME);
+		} else if (event_keyval == GDK_KEY_space) {
+			ETableCol *ecol;
+
+			ecol = e_table_header_get_column (ethi->eth, ethi->selected_col);
+			ethi_change_sort_state (ethi, ecol);
+		} else if ((event_keyval == GDK_KEY_Right) ||
+				(event_keyval == GDK_KEY_KP_Right)) {
+			ETableCol *ecol;
+
+			if ((ethi->selected_col < 0) ||
+			    (ethi->selected_col >= ethi->eth->col_count - 1))
+				ethi->selected_col = 0;
+			else
+				ethi->selected_col++;
+			ecol = e_table_header_get_column (ethi->eth, ethi->selected_col);
+			ethi_change_sort_state (ethi, ecol);
+		} else if ((event_keyval == GDK_KEY_Left) ||
+			   (event_keyval == GDK_KEY_KP_Left)) {
+			ETableCol *ecol;
+
+			if ((ethi->selected_col <= 0) ||
+			    (ethi->selected_col >= ethi->eth->col_count))
+				ethi->selected_col = ethi->eth->col_count - 1;
+			else
+				ethi->selected_col--;
+			ecol = e_table_header_get_column (ethi->eth, ethi->selected_col);
+			ethi_change_sort_state (ethi, ecol);
+		}
+		break;
+
+	default:
+		return FALSE;
+	}
+	return TRUE;
+}
+
+static void
+ethi_class_init (ETableHeaderItemClass *class)
+{
+	GnomeCanvasItemClass *item_class = GNOME_CANVAS_ITEM_CLASS (class);
+	GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+	object_class->dispose = ethi_dispose;
+	object_class->set_property = ethi_set_property;
+	object_class->get_property = ethi_get_property;
+
+	item_class->update      = ethi_update;
+	item_class->realize     = ethi_realize;
+	item_class->unrealize   = ethi_unrealize;
+	item_class->draw        = ethi_draw;
+	item_class->point       = ethi_point;
+	item_class->event       = ethi_event;
+
+	g_object_class_install_property (
+		object_class,
+		PROP_DND_CODE,
+		g_param_spec_string (
+			"dnd_code",
+			"DnD code",
+			NULL,
+			NULL,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_TABLE_FONT_DESC,
+		g_param_spec_boxed (
+			"font-desc",
+			"Font Description",
+			NULL,
+			PANGO_TYPE_FONT_DESCRIPTION,
+			G_PARAM_WRITABLE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_FULL_HEADER,
+		g_param_spec_object (
+			"full_header",
+			"Full Header",
+			NULL,
+			E_TYPE_TABLE_HEADER,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_TABLE_HEADER,
+		g_param_spec_object (
+			"ETableHeader",
+			"Header",
+			NULL,
+			E_TYPE_TABLE_HEADER,
+			G_PARAM_WRITABLE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_SORT_INFO,
+		g_param_spec_object (
+			"sort_info",
+			"Sort Info",
+			NULL,
+			E_TYPE_TABLE_SORT_INFO,
+			G_PARAM_WRITABLE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_TABLE,
+		g_param_spec_object (
+			"table",
+			"Table",
+			NULL,
+			E_TYPE_TABLE,
+			G_PARAM_WRITABLE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_TREE,
+		g_param_spec_object (
+			"tree",
+			"Tree",
+			NULL,
+			E_TYPE_TREE,
+			G_PARAM_WRITABLE));
+
+	ethi_signals[BUTTON_PRESSED] = g_signal_new (
+		"button_pressed",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ETableHeaderItemClass, button_pressed),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__BOXED,
+		G_TYPE_NONE, 1,
+		GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
+}
+
+static void
+ethi_init (ETableHeaderItem *ethi)
+{
+	GnomeCanvasItem *item = GNOME_CANVAS_ITEM (ethi);
+
+	ethi->resize_cursor = gdk_cursor_new (GDK_SB_H_DOUBLE_ARROW);
+
+	ethi->resize_col = -1;
+
+	item->x1 = 0;
+	item->y1 = 0;
+	item->x2 = 0;
+	item->y2 = 0;
+
+	ethi->drag_col = -1;
+	ethi->drag_mark = -1;
+
+	ethi->sort_info = NULL;
+
+	ethi->sort_info_changed_id = 0;
+	ethi->group_info_changed_id = 0;
+
+	ethi->group_indent_width = 0;
+	ethi->table = NULL;
+	ethi->tree = NULL;
+
+	ethi->selected_col = 0;
+}
+
diff --git a/e-util/e-table-header-item.h b/e-util/e-table-header-item.h
new file mode 100644
index 0000000..1cd0c71
--- /dev/null
+++ b/e-util/e-table-header-item.h
@@ -0,0 +1,148 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *		Miguel de Icaza (miguel gnu org)
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef _E_TABLE_HEADER_ITEM_H_
+#define _E_TABLE_HEADER_ITEM_H_
+
+#include <libxml/tree.h>
+#include <libgnomecanvas/libgnomecanvas.h>
+
+#include <e-util/e-table-header.h>
+#include <e-util/e-table-sort-info.h>
+#include <e-util/e-table.h>
+#include <e-util/e-tree.h>
+
+/* Standard GObject macros */
+#define E_TYPE_TABLE_HEADER_ITEM \
+	(e_table_header_item_get_type ())
+#define E_TABLE_HEADER_ITEM(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_TABLE_HEADER_ITEM, ETableHeaderItem))
+#define E_TABLE_HEADER_ITEM_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_TABLE_HEADER_ITEM, ETableHeaderItemClass))
+#define E_IS_TABLE_HEADER_ITEM(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_TABLE_HEADER_ITEM))
+#define E_IS_TABLE_HEADER_ITEM_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_TABLE_HEADER_ITEM))
+#define E_TABLE_HEADER_ITEM_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_TABLE_HEADER_ITEM, ETableHeaderItemClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ETableHeaderItem ETableHeaderItem;
+typedef struct _ETableHeaderItemClass ETableHeaderItemClass;
+
+struct _ETableHeaderItem {
+	GnomeCanvasItem parent;
+	ETableHeader *eth;
+
+	GdkCursor *change_cursor;
+	GdkCursor *resize_cursor;
+
+	gshort height, width;
+	PangoFontDescription *font_desc;
+
+	/*
+	 * Used during resizing; Could be shorts
+	 */
+	gint resize_col;
+	gint resize_start_pos;
+	gint resize_min_width;
+
+	gpointer  resize_guide;
+
+	gint group_indent_width;
+
+	/*
+	 * Ids
+	 */
+	gint structure_change_id, dimension_change_id;
+
+	/*
+	 * For dragging columns
+	 */
+	guint maybe_drag : 1;
+	guint dnd_ready : 1;
+	gint click_x, click_y;
+	gint drag_col, drop_col, drag_mark;
+	guint drag_motion_id;
+	guint drag_end_id;
+	guint drag_leave_id;
+	guint drag_drop_id;
+	guint drag_data_received_id;
+	guint drag_data_get_id;
+	guint sort_info_changed_id, group_info_changed_id;
+	GnomeCanvasItem *remove_item;
+
+	gchar *dnd_code;
+
+	/*
+	 * For column sorting info
+	 */
+	ETableSortInfo *sort_info;
+
+	guint scroll_direction : 4;
+	gint last_drop_x;
+	gint last_drop_y;
+	gint last_drop_time;
+	GdkDragContext *last_drop_context;
+	gint scroll_idle_id;
+
+	/* For adding fields. */
+	ETableHeader *full_header;
+	ETable *table;
+	ETree *tree;
+	gpointer config;
+
+	union {
+		GtkWidget *widget;
+		gpointer pointer;
+	} etfcd;
+
+	/* For keyboard navigation*/
+	gint selected_col;
+};
+
+struct _ETableHeaderItemClass {
+	GnomeCanvasItemClass parent_class;
+
+	/* Signals */
+	void		(*button_pressed)	(ETableHeaderItem *ethi,
+						 GdkEvent *button_event);
+};
+
+GType		e_table_header_item_get_type	(void) G_GNUC_CONST;
+void		ethi_change_sort_state		(ETableHeaderItem *ethi,
+						 ETableCol *col);
+
+G_END_DECLS
+
+#endif /* _E_TABLE_HEADER_ITEM_H_ */
diff --git a/e-util/e-table-header-utils.c b/e-util/e-table-header-utils.c
new file mode 100644
index 0000000..d3ee1ac
--- /dev/null
+++ b/e-util/e-table-header-utils.c
@@ -0,0 +1,282 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *      Miguel de Icaza <miguel ximian com>
+ *      Federico Mena-Quintero <federico ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-table-header-utils.h"
+
+#include <string.h> /* strlen() */
+
+#include <gtk/gtk.h>
+
+#include "e-table-defines.h"
+#include "e-unicode.h"
+
+static void
+get_button_padding (GtkWidget *widget,
+                    GtkBorder *padding)
+{
+	GtkStyleContext *context;
+	GtkStateFlags state_flags;
+
+	context = gtk_widget_get_style_context (widget);
+	state_flags = gtk_widget_get_state_flags (widget);
+
+	gtk_style_context_save (context);
+	gtk_style_context_add_class (context, GTK_STYLE_CLASS_BUTTON);
+	gtk_style_context_get_padding (context, state_flags, padding);
+
+	gtk_style_context_restore (context);
+}
+
+/**
+ * e_table_header_compute_height:
+ * @ecol: Table column description.
+ * @widget: The widget from which to build the PangoLayout.
+ *
+ * Computes the minimum height required for a table header button.
+ *
+ * Return value: The height of the button, in pixels.
+ **/
+gdouble
+e_table_header_compute_height (ETableCol *ecol,
+                               GtkWidget *widget)
+{
+	gint height;
+	PangoLayout *layout;
+	GtkBorder padding;
+
+	g_return_val_if_fail (ecol != NULL, -1);
+	g_return_val_if_fail (E_IS_TABLE_COL (ecol), -1);
+	g_return_val_if_fail (GTK_IS_WIDGET (widget), -1);
+
+	get_button_padding (widget, &padding);
+
+	layout = gtk_widget_create_pango_layout (widget, ecol->text);
+
+	pango_layout_get_pixel_size (layout, NULL, &height);
+
+	if (ecol->icon_name != NULL) {
+		g_return_val_if_fail (ecol->pixbuf != NULL, -1);
+		height = MAX (height, gdk_pixbuf_get_height (ecol->pixbuf));
+	}
+
+	height = MAX (height, MIN_ARROW_SIZE);
+	height += padding.top + padding.bottom + 2 * HEADER_PADDING;
+
+	g_object_unref (layout);
+
+	return height;
+}
+
+gdouble
+e_table_header_width_extras (GtkWidget *widget)
+{
+	GtkBorder padding;
+
+	get_button_padding (widget, &padding);
+	return padding.left + padding.right + 2 * HEADER_PADDING;
+}
+
+/**
+ * e_table_header_draw_button:
+ * @drawable: Destination drawable.
+ * @ecol: Table column for the header information.
+ * @widget: The table widget.
+ * @x: Leftmost coordinate of the button.
+ * @y: Topmost coordinate of the button.
+ * @width: Width of the region to draw.
+ * @height: Height of the region to draw.
+ * @button_width: Width for the complete button.
+ * @button_height: Height for the complete button.
+ * @arrow: Arrow type to use as a sort indicator.
+ *
+ * Draws a button suitable for a table header.
+ **/
+void
+e_table_header_draw_button (cairo_t *cr,
+                            ETableCol *ecol,
+                            GtkWidget *widget,
+                            gint x,
+                            gint y,
+                            gint width,
+                            gint height,
+                            gint button_width,
+                            gint button_height,
+                            ETableColArrow arrow)
+{
+	gint inner_x, inner_y;
+	gint inner_width, inner_height;
+	gint arrow_width = 0, arrow_height = 0;
+	PangoContext *pango_context;
+	PangoLayout *layout;
+	GtkStyleContext *context;
+	GtkBorder padding;
+	GtkStateFlags state_flags;
+
+	g_return_if_fail (cr != NULL);
+	g_return_if_fail (ecol != NULL);
+	g_return_if_fail (E_IS_TABLE_COL (ecol));
+	g_return_if_fail (widget != NULL);
+	g_return_if_fail (GTK_IS_WIDGET (widget));
+	g_return_if_fail (button_width > 0 && button_height > 0);
+
+	/* Button bevel */
+	context = gtk_widget_get_style_context (widget);
+	state_flags = gtk_widget_get_state_flags (widget);
+
+	gtk_style_context_save (context);
+	gtk_style_context_set_state (context, state_flags);
+	gtk_style_context_add_class (context, GTK_STYLE_CLASS_BUTTON);
+
+	gtk_style_context_get_padding (context, state_flags, &padding);
+
+	gtk_render_background (
+		context, cr, x, y,
+		button_width, button_height);
+	gtk_render_frame (
+		context, cr, x, y,
+		button_width, button_height);
+
+	/* Inside area */
+
+	inner_width =
+		button_width -
+		(padding.left + padding.right + 2 * HEADER_PADDING);
+	inner_height =
+		button_height -
+		(padding.top + padding.bottom + 2 * HEADER_PADDING);
+
+	if (inner_width < 1 || inner_height < 1) {
+		return; /* nothing fits */
+	}
+
+	inner_x = x + padding.left + HEADER_PADDING;
+	inner_y = y + padding.top + HEADER_PADDING;
+
+	/* Arrow space */
+
+	switch (arrow) {
+	case E_TABLE_COL_ARROW_NONE:
+		break;
+
+	case E_TABLE_COL_ARROW_UP:
+	case E_TABLE_COL_ARROW_DOWN:
+		arrow_width = MIN (MIN_ARROW_SIZE, inner_width);
+		arrow_height = MIN (MIN_ARROW_SIZE, inner_height);
+
+		if (ecol->icon_name == NULL)
+			inner_width -= arrow_width + HEADER_PADDING;
+		break;
+	default:
+		cairo_restore (cr);
+		g_return_if_reached ();
+	}
+
+	if (inner_width < 1) {
+		gtk_style_context_restore (context);
+		return; /* nothing else fits */
+	}
+
+	pango_context = gtk_widget_create_pango_context (widget);
+	layout = pango_layout_new (pango_context);
+	g_object_unref (pango_context);
+
+	pango_layout_set_text (layout, ecol->text, -1);
+	pango_layout_set_ellipsize (layout, PANGO_ELLIPSIZE_END);
+
+	/* Pixbuf or label */
+	if (ecol->icon_name != NULL) {
+		gint pwidth, pheight;
+		gint clip_height;
+		gint xpos;
+
+		g_return_if_fail (ecol->pixbuf != NULL);
+
+		pwidth = gdk_pixbuf_get_width (ecol->pixbuf);
+		pheight = gdk_pixbuf_get_height (ecol->pixbuf);
+
+		clip_height = MIN (pheight, inner_height);
+
+		xpos = inner_x;
+
+		if (inner_width - pwidth > 11) {
+			gint ypos;
+
+			pango_layout_get_pixel_size (layout, &width, NULL);
+
+			if (width < inner_width - (pwidth + 1)) {
+				xpos = inner_x + (inner_width - width - (pwidth + 1)) / 2;
+			}
+
+			ypos = inner_y;
+
+			pango_layout_set_width (
+				layout, (inner_width - (xpos - inner_x)) *
+				PANGO_SCALE);
+
+			gtk_render_layout (
+				context, cr, xpos + pwidth + 1,
+				ypos, layout);
+		}
+
+		gtk_render_icon (
+			context, cr, ecol->pixbuf, xpos,
+			inner_y + (inner_height - clip_height) / 2);
+
+	} else {
+		pango_layout_set_width (layout, inner_width * PANGO_SCALE);
+
+		gtk_render_layout (context, cr, inner_x, inner_y, layout);
+	}
+
+	switch (arrow) {
+	case E_TABLE_COL_ARROW_NONE:
+		break;
+
+	case E_TABLE_COL_ARROW_UP:
+	case E_TABLE_COL_ARROW_DOWN: {
+		if (ecol->icon_name == NULL)
+			inner_width += arrow_width + HEADER_PADDING;
+
+		gtk_render_arrow (
+			context, cr,
+			(arrow == E_TABLE_COL_ARROW_UP) ? 0 : G_PI,
+			inner_x + inner_width - arrow_width,
+			inner_y + (inner_height - arrow_height) / 2,
+			MAX (arrow_width, arrow_height));
+
+		break;
+	}
+
+	default:
+		cairo_restore (cr);
+		g_return_if_reached ();
+	}
+
+	g_object_unref (layout);
+	gtk_style_context_restore (context);
+}
diff --git a/e-util/e-table-header-utils.h b/e-util/e-table-header-utils.h
new file mode 100644
index 0000000..3022681
--- /dev/null
+++ b/e-util/e-table-header-utils.h
@@ -0,0 +1,53 @@
+/*
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *		Miguel de Icaza <miguel ximian com>
+ *		Federico Mena-Quintero <federico ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_TABLE_HEADER_UTILS_H
+#define E_TABLE_HEADER_UTILS_H
+
+#include <e-util/e-table-col.h>
+
+G_BEGIN_DECLS
+
+gdouble		e_table_header_compute_height	(ETableCol *ecol,
+						 GtkWidget *widget);
+gdouble		e_table_header_width_extras	(GtkWidget *widget);
+void		e_table_header_draw_button	(cairo_t *cr,
+						 ETableCol *ecol,
+						 GtkWidget *widget,
+						 gint x,
+						 gint y,
+						 gint width,
+						 gint height,
+						 gint button_width,
+						 gint button_height,
+						 ETableColArrow arrow);
+
+G_END_DECLS
+
+#endif /* E_TABLE_HEADER_UTILS_H */
diff --git a/e-util/e-table-header.c b/e-util/e-table-header.c
new file mode 100644
index 0000000..d06b26e
--- /dev/null
+++ b/e-util/e-table-header.c
@@ -0,0 +1,1013 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *		Miguel de Icaza <miguel ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+
+#include <gtk/gtk.h>
+
+#include "e-marshal.h"
+#include "e-table-defines.h"
+#include "e-table-header.h"
+
+enum {
+	PROP_0,
+	PROP_SORT_INFO,
+	PROP_WIDTH,
+	PROP_WIDTH_EXTRAS
+};
+
+enum {
+	STRUCTURE_CHANGE,
+	DIMENSION_CHANGE,
+	EXPANSION_CHANGE,
+	REQUEST_WIDTH,
+	LAST_SIGNAL
+};
+
+static void eth_set_size (ETableHeader *eth, gint idx, gint size);
+static void eth_calc_widths (ETableHeader *eth);
+
+static guint eth_signals[LAST_SIGNAL] = { 0, };
+
+G_DEFINE_TYPE (ETableHeader, e_table_header, G_TYPE_OBJECT)
+
+struct two_ints {
+	gint column;
+	gint width;
+};
+
+static void
+eth_set_width (ETableHeader *eth,
+               gint width)
+{
+	eth->width = width;
+}
+
+static void
+dequeue (ETableHeader *eth,
+         gint *column,
+         gint *width)
+{
+	GSList *head;
+	struct two_ints *store;
+	head = eth->change_queue;
+	eth->change_queue = eth->change_queue->next;
+	if (!eth->change_queue)
+		eth->change_tail = NULL;
+	store = head->data;
+	g_slist_free_1 (head);
+	if (column)
+		*column = store->column;
+	if (width)
+		*width = store->width;
+	g_free (store);
+}
+
+static gboolean
+dequeue_idle (ETableHeader *eth)
+{
+	gint column, width;
+
+	dequeue (eth, &column, &width);
+	while (eth->change_queue && ((struct two_ints *)
+		eth->change_queue->data)->column == column)
+		dequeue (eth, &column, &width);
+
+	if (column == -1)
+		eth_set_width (eth, width);
+	else if (column < eth->col_count)
+		eth_set_size (eth, column, width);
+	if (eth->change_queue)
+		return TRUE;
+	else {
+		eth_calc_widths (eth);
+		eth->idle = 0;
+		return FALSE;
+	}
+}
+
+static void
+enqueue (ETableHeader *eth,
+         gint column,
+         gint width)
+{
+	struct two_ints *store;
+	store = g_new (struct two_ints, 1);
+	store->column = column;
+	store->width = width;
+
+	eth->change_tail = g_slist_last (g_slist_append (eth->change_tail, store));
+	if (!eth->change_queue)
+		eth->change_queue = eth->change_tail;
+
+	if (!eth->idle) {
+		eth->idle = g_idle_add_full (
+			G_PRIORITY_LOW, (GSourceFunc)
+			dequeue_idle, eth, NULL);
+	}
+}
+
+void
+e_table_header_set_size (ETableHeader *eth,
+                         gint idx,
+                         gint size)
+{
+	g_return_if_fail (eth != NULL);
+	g_return_if_fail (E_IS_TABLE_HEADER (eth));
+
+	enqueue (eth, idx, size);
+}
+
+static void
+eth_do_remove (ETableHeader *eth,
+               gint idx,
+               gboolean do_unref)
+{
+	if (do_unref)
+		g_object_unref (eth->columns[idx]);
+
+	memmove (
+		&eth->columns[idx], &eth->columns[idx + 1],
+		sizeof (ETableCol *) * (eth->col_count - idx - 1));
+	eth->col_count--;
+}
+
+static void
+eth_finalize (GObject *object)
+{
+	ETableHeader *eth = E_TABLE_HEADER (object);
+	const gint cols = eth->col_count;
+	gint i;
+
+	if (eth->sort_info) {
+		if (eth->sort_info_group_change_id)
+			g_signal_handler_disconnect (
+				eth->sort_info,
+				eth->sort_info_group_change_id);
+		g_object_unref (eth->sort_info);
+		eth->sort_info = NULL;
+	}
+
+	if (eth->idle)
+		g_source_remove (eth->idle);
+	eth->idle = 0;
+
+	if (eth->change_queue) {
+		g_slist_foreach (eth->change_queue, (GFunc) g_free, NULL);
+		g_slist_free (eth->change_queue);
+		eth->change_queue = NULL;
+	}
+
+	/*
+	 * Destroy columns
+	 */
+	for (i = cols - 1; i >= 0; i--) {
+		eth_do_remove (eth, i, TRUE);
+	}
+	g_free (eth->columns);
+
+	eth->col_count = 0;
+	eth->columns = NULL;
+
+	/* Chain up to parent's finalize() method. */
+	G_OBJECT_CLASS (e_table_header_parent_class)->finalize (object);
+}
+
+static void
+eth_group_info_changed (ETableSortInfo *info,
+                        ETableHeader *eth)
+{
+	enqueue (eth, -1, eth->nominal_width);
+}
+
+static void
+eth_set_property (GObject *object,
+                  guint property_id,
+                  const GValue *val,
+                  GParamSpec *pspec)
+{
+	ETableHeader *eth = E_TABLE_HEADER (object);
+
+	switch (property_id) {
+	case PROP_WIDTH:
+		eth->nominal_width = g_value_get_double (val);
+		enqueue (eth, -1, eth->nominal_width);
+		break;
+	case PROP_WIDTH_EXTRAS:
+		eth->width_extras = g_value_get_double (val);
+		enqueue (eth, -1, eth->nominal_width);
+		break;
+	case PROP_SORT_INFO:
+		if (eth->sort_info) {
+			if (eth->sort_info_group_change_id)
+				g_signal_handler_disconnect (
+					eth->sort_info,
+					eth->sort_info_group_change_id);
+			g_object_unref (eth->sort_info);
+		}
+		eth->sort_info = E_TABLE_SORT_INFO (g_value_get_object (val));
+		if (eth->sort_info) {
+			g_object_ref (eth->sort_info);
+			eth->sort_info_group_change_id = g_signal_connect (
+				eth->sort_info, "group_info_changed",
+				G_CALLBACK (eth_group_info_changed), eth);
+		}
+		enqueue (eth, -1, eth->nominal_width);
+		break;
+	default:
+		break;
+	}
+}
+
+static void
+eth_get_property (GObject *object,
+                  guint property_id,
+                  GValue *val,
+                  GParamSpec *pspec)
+{
+	ETableHeader *eth = E_TABLE_HEADER (object);
+
+	switch (property_id) {
+	case PROP_SORT_INFO:
+		g_value_set_object (val, eth->sort_info);
+		break;
+	case PROP_WIDTH:
+		g_value_set_double (val, eth->nominal_width);
+		break;
+	case PROP_WIDTH_EXTRAS:
+		g_value_set_double (val, eth->width_extras);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+		break;
+	}
+}
+
+static void
+e_table_header_class_init (ETableHeaderClass *class)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+	object_class->finalize = eth_finalize;
+	object_class->set_property = eth_set_property;
+	object_class->get_property = eth_get_property;
+
+	g_object_class_install_property (
+		object_class,
+		PROP_WIDTH,
+		g_param_spec_double (
+			"width", "Width", "Width",
+			0.0, G_MAXDOUBLE, 0.0,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_WIDTH_EXTRAS,
+		g_param_spec_double (
+			"width_extras",
+			"Width of Extras",
+			"Width of Extras",
+			0.0, G_MAXDOUBLE, 0.0,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_SORT_INFO,
+		g_param_spec_object (
+			"sort_info",
+			"Sort Info",
+			"Sort Info",
+			E_TYPE_TABLE_SORT_INFO,
+			G_PARAM_READWRITE));
+
+	eth_signals[STRUCTURE_CHANGE] = g_signal_new (
+		"structure_change",
+		G_TYPE_FROM_CLASS (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ETableHeaderClass, structure_change),
+		(GSignalAccumulator) NULL, NULL,
+		g_cclosure_marshal_VOID__VOID,
+		G_TYPE_NONE, 0);
+
+	eth_signals[DIMENSION_CHANGE] = g_signal_new (
+		"dimension_change",
+		G_TYPE_FROM_CLASS (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ETableHeaderClass, dimension_change),
+		(GSignalAccumulator) NULL, NULL,
+		g_cclosure_marshal_VOID__INT,
+		G_TYPE_NONE, 1,
+		G_TYPE_INT);
+
+	eth_signals[EXPANSION_CHANGE] = g_signal_new (
+		"expansion_change",
+		G_TYPE_FROM_CLASS (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ETableHeaderClass, expansion_change),
+		(GSignalAccumulator) NULL, NULL,
+		g_cclosure_marshal_VOID__VOID,
+		G_TYPE_NONE, 0);
+
+	eth_signals[REQUEST_WIDTH] = g_signal_new (
+		"request_width",
+		G_TYPE_FROM_CLASS (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ETableHeaderClass, request_width),
+		(GSignalAccumulator) NULL, NULL,
+		e_marshal_INT__INT,
+		G_TYPE_INT, 1,
+		G_TYPE_INT);
+
+	class->structure_change = NULL;
+	class->dimension_change = NULL;
+	class->expansion_change = NULL;
+	class->request_width = NULL;
+}
+
+static void
+e_table_header_init (ETableHeader *eth)
+{
+	eth->col_count                 = 0;
+	eth->width                     = 0;
+
+	eth->sort_info                 = NULL;
+	eth->sort_info_group_change_id = 0;
+
+	eth->columns                   = NULL;
+
+	eth->change_queue              = NULL;
+	eth->change_tail               = NULL;
+
+	eth->width_extras              = 0;
+}
+
+/**
+ * e_table_header_new:
+ *
+ * Returns: A new @ETableHeader object.
+ */
+ETableHeader *
+e_table_header_new (void)
+{
+
+	return g_object_new (E_TYPE_TABLE_HEADER, NULL);
+}
+
+static void
+eth_update_offsets (ETableHeader *eth)
+{
+	gint i;
+	gint x = 0;
+
+	for (i = 0; i < eth->col_count; i++) {
+		ETableCol *etc = eth->columns[i];
+
+		etc->x = x;
+		x += etc->width;
+	}
+}
+
+static void
+eth_do_insert (ETableHeader *eth,
+               gint pos,
+               ETableCol *val)
+{
+	memmove (
+		&eth->columns[pos + 1], &eth->columns[pos],
+		sizeof (ETableCol *) * (eth->col_count - pos));
+	eth->columns[pos] = val;
+	eth->col_count++;
+}
+
+/**
+ * e_table_header_add_column:
+ * @eth: the table header to add the column to.
+ * @tc: the ETableCol definition
+ * @pos: position where the ETableCol will go.
+ *
+ * This function adds the @tc ETableCol definition into the @eth ETableHeader
+ * at position @pos.  This is the way you add new ETableCols to the
+ * ETableHeader.  The header will assume ownership of the @tc; you should not
+ * unref it after you add it.
+ *
+ * This function will emit the "structure_change" signal on the @eth object.
+ * The ETableCol is assumed
+ */
+void
+e_table_header_add_column (ETableHeader *eth,
+                           ETableCol *tc,
+                           gint pos)
+{
+	g_return_if_fail (eth != NULL);
+	g_return_if_fail (E_IS_TABLE_HEADER (eth));
+	g_return_if_fail (tc != NULL);
+	g_return_if_fail (E_IS_TABLE_COL (tc));
+	g_return_if_fail (pos >= -1 && pos <= eth->col_count);
+
+	if (pos == -1)
+		pos = eth->col_count;
+	eth->columns = g_realloc (
+		eth->columns, sizeof (ETableCol *) * (eth->col_count + 1));
+
+	/*
+	 * We are the primary owners of the column
+	 */
+	g_object_ref (tc);
+
+	eth_do_insert (eth, pos, tc);
+
+	enqueue (eth, -1, eth->nominal_width);
+	g_signal_emit (eth, eth_signals[STRUCTURE_CHANGE], 0);
+}
+
+/**
+ * e_table_header_get_column:
+ * @eth: the ETableHeader to query
+ * @column: the column inside the @eth.
+ *
+ * Returns: The ETableCol at @column in the @eth object
+ */
+ETableCol *
+e_table_header_get_column (ETableHeader *eth,
+                           gint column)
+{
+	g_return_val_if_fail (eth != NULL, NULL);
+	g_return_val_if_fail (E_IS_TABLE_HEADER (eth), NULL);
+
+	if (column < 0)
+		return NULL;
+
+	if (column >= eth->col_count)
+		return NULL;
+
+	return eth->columns[column];
+}
+
+/**
+ * e_table_header_get_column_by_col_id:
+ * @eth: the ETableHeader to query
+ * @col_id: the col_id to search for.
+ *
+ * Returns: The ETableCol with col_idx = @col_idx in the @eth object
+ */
+ETableCol *
+e_table_header_get_column_by_col_idx (ETableHeader *eth,
+                                      gint col_idx)
+{
+	gint i;
+	g_return_val_if_fail (eth != NULL, NULL);
+	g_return_val_if_fail (E_IS_TABLE_HEADER (eth), NULL);
+
+	for (i = 0; i < eth->col_count; i++) {
+		if (eth->columns[i]->col_idx == col_idx) {
+			return eth->columns[i];
+		}
+	}
+
+	return NULL;
+}
+
+/**
+ * e_table_header_count:
+ * @eth: the ETableHeader to query
+ *
+ * Returns: the number of columns in this ETableHeader.
+ */
+gint
+e_table_header_count (ETableHeader *eth)
+{
+	g_return_val_if_fail (eth != NULL, 0);
+	g_return_val_if_fail (E_IS_TABLE_HEADER (eth), 0);
+
+	return eth->col_count;
+}
+
+/**
+ * e_table_header_index:
+ * @eth: the ETableHeader to query
+ * @col: the column to fetch.
+ *
+ * ETableHeaders contain the visual list of columns that the user will
+ * view.  The visible columns will typically map to different columns
+ * in the ETableModel (because the user reordered the data for
+ * example).
+ *
+ * Returns: the column in the model that the @col column
+ * in the ETableHeader points to.  */
+gint
+e_table_header_index (ETableHeader *eth,
+                      gint col)
+{
+	g_return_val_if_fail (eth != NULL, -1);
+	g_return_val_if_fail (E_IS_TABLE_HEADER (eth), -1);
+	g_return_val_if_fail (col >= 0 && col < eth->col_count, -1);
+
+	return eth->columns[col]->col_idx;
+}
+
+/**
+ * e_table_header_get_index_at:
+ * @eth: the ETableHeader to query
+ * @x_offset: a pixel count from the beginning of the ETableHeader
+ *
+ * This will return the ETableHeader column that would contain
+ * the @x_offset pixel.
+ *
+ * Returns: the column that contains pixel @x_offset, or -1
+ * if no column inside this ETableHeader contains that pixel.
+ */
+gint
+e_table_header_get_index_at (ETableHeader *eth,
+                             gint x_offset)
+{
+	gint i, total;
+
+	g_return_val_if_fail (eth != NULL, 0);
+	g_return_val_if_fail (E_IS_TABLE_HEADER (eth), 0);
+
+	total = 0;
+	for (i = 0; i < eth->col_count; i++) {
+		total += eth->columns[i]->width;
+
+		if (x_offset < total)
+			return i;
+	}
+
+	return -1;
+}
+
+/**
+ * e_table_header_get_columns:
+ * @eth: The ETableHeader to query
+ *
+ * Returns: A NULL terminated array of the ETableCols
+ * contained in the ETableHeader @eth.  Note that every
+ * returned ETableCol in the array has been referenced, to release
+ * this information you need to g_free the buffer returned
+ * and you need to g_object_unref every element returned
+ */
+ETableCol **
+e_table_header_get_columns (ETableHeader *eth)
+{
+	ETableCol **ret;
+	gint i;
+
+	g_return_val_if_fail (eth != NULL, NULL);
+	g_return_val_if_fail (E_IS_TABLE_HEADER (eth), NULL);
+
+	ret = g_new (ETableCol *, eth->col_count + 1);
+	memcpy (ret, eth->columns, sizeof (ETableCol *) * eth->col_count);
+	ret[eth->col_count] = NULL;
+
+	for (i = 0; i < eth->col_count; i++) {
+		g_object_ref (ret[i]);
+	}
+
+	return ret;
+}
+
+/**
+ * e_table_header_get_selected:
+ * @eth: The ETableHeader to query
+ *
+ * Returns: The number of selected columns in the @eth object.
+ */
+gint
+e_table_header_get_selected (ETableHeader *eth)
+{
+	gint i;
+	gint selected = 0;
+
+	g_return_val_if_fail (eth != NULL, 0);
+	g_return_val_if_fail (E_IS_TABLE_HEADER (eth), 0);
+
+	for (i = 0; i < eth->col_count; i++) {
+		if (eth->columns[i]->selected)
+			selected++;
+	}
+
+	return selected;
+}
+
+/**
+ * e_table_header_total_width:
+ * @eth: The ETableHeader to query
+ *
+ * Returns: the number of pixels used by the @eth object
+ * when rendered on screen
+ */
+gint
+e_table_header_total_width (ETableHeader *eth)
+{
+	gint total, i;
+
+	g_return_val_if_fail (eth != NULL, 0);
+	g_return_val_if_fail (E_IS_TABLE_HEADER (eth), 0);
+
+	total = 0;
+	for (i = 0; i < eth->col_count; i++)
+		total += eth->columns[i]->width;
+
+	return total;
+}
+
+/**
+ * e_table_header_min_width:
+ * @eth: The ETableHeader to query
+ *
+ * Returns: the minimum number of pixels required by the @eth object.
+ **/
+gint
+e_table_header_min_width (ETableHeader *eth)
+{
+	gint total, i;
+
+	g_return_val_if_fail (eth != NULL, 0);
+	g_return_val_if_fail (E_IS_TABLE_HEADER (eth), 0);
+
+	total = 0;
+	for (i = 0; i < eth->col_count; i++)
+		total += eth->columns[i]->min_width;
+
+	return total;
+}
+
+/**
+ * e_table_header_move:
+ * @eth: The ETableHeader to operate on.
+ * @source_index: the source column to move.
+ * @target_index: the target location for the column
+ *
+ * This function moves the column @source_index to @target_index
+ * inside the @eth ETableHeader.  The signals "dimension_change"
+ * and "structure_change" will be emmited
+ */
+void
+e_table_header_move (ETableHeader *eth,
+                     gint source_index,
+                     gint target_index)
+{
+	ETableCol *old;
+
+	g_return_if_fail (eth != NULL);
+	g_return_if_fail (E_IS_TABLE_HEADER (eth));
+	g_return_if_fail (source_index >= 0);
+	g_return_if_fail (target_index >= 0);
+	g_return_if_fail (source_index < eth->col_count);
+
+	/* Can be moved beyond the last item. */
+	g_return_if_fail (target_index < eth->col_count + 1);
+
+	if (source_index < target_index)
+		target_index--;
+
+	old = eth->columns[source_index];
+	eth_do_remove (eth, source_index, FALSE);
+	eth_do_insert (eth, target_index, old);
+	eth_update_offsets (eth);
+
+	g_signal_emit (eth, eth_signals[DIMENSION_CHANGE], 0, eth->width);
+	g_signal_emit (eth, eth_signals[STRUCTURE_CHANGE], 0);
+}
+
+/**
+ * e_table_header_remove:
+ * @eth: The ETableHeader to operate on.
+ * @idx: the index to the column to be removed.
+ *
+ * Removes the column at @idx position in the ETableHeader @eth.
+ * This emmits the "structure_change" signal on the @eth object.
+ */
+void
+e_table_header_remove (ETableHeader *eth,
+                       gint idx)
+{
+	g_return_if_fail (eth != NULL);
+	g_return_if_fail (E_IS_TABLE_HEADER (eth));
+	g_return_if_fail (idx >= 0);
+	g_return_if_fail (idx < eth->col_count);
+
+	eth_do_remove (eth, idx, TRUE);
+	enqueue (eth, -1, eth->nominal_width);
+	g_signal_emit (eth, eth_signals[STRUCTURE_CHANGE], 0);
+}
+
+/*
+ * FIXME: deprecated?
+ */
+void
+e_table_header_set_selection (ETableHeader *eth,
+                              gboolean allow_selection)
+{
+	g_return_if_fail (eth != NULL);
+	g_return_if_fail (E_IS_TABLE_HEADER (eth));
+}
+
+static void
+eth_set_size (ETableHeader *eth,
+              gint idx,
+              gint size)
+{
+	gdouble expansion;
+	gdouble old_expansion;
+	gint min_width;
+	gint left_width;
+	gint total_extra;
+	gint expandable_count;
+	gint usable_width;
+	gint i;
+	g_return_if_fail (eth != NULL);
+	g_return_if_fail (E_IS_TABLE_HEADER (eth));
+	g_return_if_fail (idx >= 0);
+	g_return_if_fail (idx < eth->col_count);
+
+	/* If this column is not resizable, don't do anything. */
+	if (!eth->columns[idx]->resizable)
+		return;
+
+	expansion = 0;
+	min_width = 0;
+	left_width = 0;
+	expandable_count = -1;
+
+	/* Calculate usable area. */
+	for (i = 0; i < idx; i++) {
+		left_width += eth->columns[i]->width;
+	}
+	/* - 1 to account for the last pixel border. */
+	usable_width = eth->width - left_width - 1;
+
+	if (eth->sort_info)
+		usable_width -= e_table_sort_info_grouping_get_count (
+			eth->sort_info) * GROUP_INDENT;
+
+	/* Calculate minimum_width of stuff on the right as well as
+	 * total usable expansion on the right.
+	 */
+	for (; i < eth->col_count; i++) {
+		min_width += eth->columns[i]->min_width + eth->width_extras;
+		if (eth->columns[i]->resizable) {
+			expansion += eth->columns[i]->expansion;
+			expandable_count++;
+		}
+	}
+	/* If there's no room for anything, don't change. */
+	if (expansion == 0)
+		return;
+
+	/* (1) If none of the columns to the right are expandable, use
+	 * all the expansion space in this column.
+	 */
+	if (expandable_count == 0) {
+		eth->columns[idx]->expansion = expansion;
+		for (i = idx + 1; i < eth->col_count; i++) {
+			eth->columns[i]->expansion = 0;
+		}
+
+		g_signal_emit (eth, eth_signals[EXPANSION_CHANGE], 0);
+		return;
+	}
+
+	total_extra = usable_width - min_width;
+	/* If there's no extra space, set all expansions to 0. */
+	if (total_extra <= 0) {
+		for (i = idx; i < eth->col_count; i++) {
+			eth->columns[i]->expansion = 0;
+		}
+		g_signal_emit (eth, eth_signals[EXPANSION_CHANGE], 0);
+		return;
+	}
+
+	/* If you try to resize smaller than the minimum width, it
+	 * uses the minimum. */
+	if (size < eth->columns[idx]->min_width + eth->width_extras)
+		size = eth->columns[idx]->min_width + eth->width_extras;
+
+	/* If all the extra space will be used up in this column, use
+	 * all the expansion and set all others to 0.
+	 */
+	if (size >= total_extra + eth->columns[idx]->min_width + eth->width_extras) {
+		eth->columns[idx]->expansion = expansion;
+		for (i = idx + 1; i < eth->col_count; i++) {
+			eth->columns[i]->expansion = 0;
+		}
+		g_signal_emit (eth, eth_signals[EXPANSION_CHANGE], 0);
+		return;
+	}
+
+	/* The old_expansion used by columns to the right. */
+	old_expansion = expansion;
+	old_expansion -= eth->columns[idx]->expansion;
+	/* Set the new expansion so that it will generate the desired size. */
+	eth->columns[idx]->expansion =
+		expansion * (((gdouble)(size - (eth->columns[idx]->min_width +
+		eth->width_extras))) / ((gdouble) total_extra));
+	/* The expansion left for the columns on the right. */
+	expansion -= eth->columns[idx]->expansion;
+
+	/* (2) If the old columns to the right didn't have any
+	 * expansion before, expand them evenly.  old_expansion > 0 by
+	 * expansion = SUM(i=idx to col_count -1,
+	 * columns[i]->min_width) - columns[idx]->min_width) =
+	 * SUM(non-negatives).
+	 */
+	if (old_expansion == 0) {
+		for (i = idx + 1; i < eth->col_count; i++) {
+			if (eth->columns[idx]->resizable) {
+				/* expandable_count != 0 by (1) */
+				eth->columns[i]->expansion = expansion / expandable_count;
+			}
+		}
+		g_signal_emit (eth, eth_signals[EXPANSION_CHANGE], 0);
+		return;
+	}
+
+	for (i = idx + 1; i < eth->col_count; i++) {
+		if (eth->columns[idx]->resizable) {
+			/* old_expansion != 0 by (2) */
+			eth->columns[i]->expansion *= expansion / old_expansion;
+		}
+	}
+	g_signal_emit (eth, eth_signals[EXPANSION_CHANGE], 0);
+}
+
+/**
+ * e_table_header_col_diff:
+ * @eth: the ETableHeader to query.
+ * @start_col: the starting column
+ * @end_col: the ending column.
+ *
+ * Computes the number of pixels between the columns @start_col and
+ * @end_col.
+ *
+ * Returns: the number of pixels between @start_col and @end_col on the
+ * @eth ETableHeader object
+ */
+gint
+e_table_header_col_diff (ETableHeader *eth,
+                         gint start_col,
+                         gint end_col)
+{
+	gint total, col;
+
+	g_return_val_if_fail (eth != NULL, 0);
+	g_return_val_if_fail (E_IS_TABLE_HEADER (eth), 0);
+
+	if (start_col < 0)
+		start_col = 0;
+	if (end_col > eth->col_count)
+		end_col = eth->col_count;
+
+	total = 0;
+	for (col = start_col; col < end_col; col++) {
+
+		total += eth->columns[col]->width;
+	}
+
+	return total;
+}
+
+static void
+eth_calc_widths (ETableHeader *eth)
+{
+	gint i;
+	gint extra;
+	gdouble expansion;
+	gint last_position = 0;
+	gdouble next_position = 0;
+	gint last_resizable = -1;
+	gint *widths;
+	gboolean changed;
+
+	widths = g_new (int, eth->col_count);
+
+	/* - 1 to account for the last pixel border. */
+	extra = eth->width - 1;
+	expansion = 0;
+	for (i = 0; i < eth->col_count; i++) {
+		extra -= eth->columns[i]->min_width + eth->width_extras;
+		if (eth->columns[i]->resizable && eth->columns[i]->expansion > 0)
+			last_resizable = i;
+		expansion += eth->columns[i]->resizable ? eth->columns[i]->expansion : 0;
+		widths[i] = eth->columns[i]->min_width + eth->width_extras;
+	}
+	if (eth->sort_info)
+		extra -= e_table_sort_info_grouping_get_count (eth->sort_info)
+			* GROUP_INDENT;
+	if (expansion != 0 && extra > 0) {
+		for (i = 0; i < last_resizable; i++) {
+			next_position +=
+				extra * (eth->columns[i]->resizable ?
+				eth->columns[i]->expansion : 0) / expansion;
+			widths[i] += next_position - last_position;
+			last_position = next_position;
+		}
+		widths[i] += extra - last_position;
+	}
+
+	changed = FALSE;
+
+	for (i = 0; i < eth->col_count; i++) {
+		if (eth->columns[i]->width != widths[i]) {
+			changed = TRUE;
+			eth->columns[i]->width = widths[i];
+		}
+	}
+	g_free (widths);
+	if (changed)
+		g_signal_emit (eth, eth_signals[DIMENSION_CHANGE], 0, eth->width);
+	eth_update_offsets (eth);
+}
+
+void
+e_table_header_update_horizontal (ETableHeader *eth)
+{
+	gint i;
+	gint cols;
+
+	cols = eth->col_count;
+
+	for (i = 0; i < cols; i++) {
+		gint width = 0;
+
+		g_signal_emit_by_name (
+			eth, "request_width", i, &width);
+		eth->columns[i]->min_width = width + 10;
+		eth->columns[i]->expansion = 1;
+	}
+	enqueue (eth, -1, eth->nominal_width);
+	g_signal_emit (eth, eth_signals[EXPANSION_CHANGE], 0);
+}
+
+gint
+e_table_header_prioritized_column (ETableHeader *eth)
+{
+	gint best_model_col = 0;
+	gint best_priority;
+	gint i;
+	gint count;
+
+	count = e_table_header_count (eth);
+	if (count == 0)
+		return -1;
+	best_priority = e_table_header_get_column (eth, 0)->priority;
+	best_model_col = e_table_header_get_column (eth, 0)->col_idx;
+	for (i = 1; i < count; i++) {
+		gint priority = e_table_header_get_column (eth, i)->priority;
+		if (priority > best_priority) {
+			best_priority = priority;
+			best_model_col = e_table_header_get_column (eth, i)->col_idx;
+		}
+	}
+	return best_model_col;
+}
+
+ETableCol *
+e_table_header_prioritized_column_selected (ETableHeader *eth,
+                                            ETableColCheckFunc check_func,
+                                            gpointer user_data)
+{
+	ETableCol *best_col = NULL;
+	gint best_priority = G_MININT;
+	gint i;
+	gint count;
+
+	count = e_table_header_count (eth);
+	if (count == 0)
+		return NULL;
+	for (i = 1; i < count; i++) {
+		ETableCol *col = e_table_header_get_column (eth, i);
+		if (col) {
+			if ((best_col == NULL || col->priority > best_priority)
+			   && check_func (col, user_data)) {
+				best_priority = col->priority;
+				best_col = col;
+			}
+		}
+	}
+	return best_col;
+}
diff --git a/e-util/e-table-header.h b/e-util/e-table-header.h
new file mode 100644
index 0000000..298131e
--- /dev/null
+++ b/e-util/e-table-header.h
@@ -0,0 +1,144 @@
+/*
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *		Miguel de Icaza <miguel ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef _E_TABLE_COLUMN_H_
+#define _E_TABLE_COLUMN_H_
+
+#include <gdk/gdk.h>
+
+#include <e-util/e-table-col.h>
+#include <e-util/e-table-sort-info.h>
+
+/* Standard GObject macros */
+#define E_TYPE_TABLE_HEADER \
+	(e_table_header_get_type ())
+#define E_TABLE_HEADER(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_TABLE_HEADER, ETableHeader))
+#define E_TABLE_HEADER_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_TABLE_HEADER, ETableHeaderClass))
+#define E_IS_TABLE_HEADER(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_TABLE_HEADER))
+#define E_IS_TABLE_HEADER_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_TABLE_HEADER))
+#define E_TABLE_HEADER_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_TABLE_HEADER, ETableHeaderClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ETableHeader ETableHeader;
+typedef struct _ETableHeaderClass ETableHeaderClass;
+
+typedef gboolean (*ETableColCheckFunc) (ETableCol *col, gpointer user_data);
+
+/*
+ * A Column header.
+ */
+struct _ETableHeader {
+	GObject parent;
+
+	gint col_count;
+	gint width;
+	gint nominal_width;
+	gint width_extras;
+
+	ETableSortInfo *sort_info;
+	gint sort_info_group_change_id;
+
+	ETableCol **columns;
+
+	GSList *change_queue, *change_tail;
+	gint idle;
+};
+
+struct _ETableHeaderClass {
+	GObjectClass parent_class;
+
+	void		(*structure_change)	(ETableHeader *eth);
+	void		(*dimension_change)	(ETableHeader *eth,
+						 gint width);
+	void		(*expansion_change)	(ETableHeader *eth);
+	gint		(*request_width)	(ETableHeader *eth,
+						 gint col);
+};
+
+GType		e_table_header_get_type		(void) G_GNUC_CONST;
+ETableHeader *	e_table_header_new		(void);
+
+void		e_table_header_add_column	(ETableHeader *eth,
+						 ETableCol *tc,
+						 gint pos);
+ETableCol *	e_table_header_get_column	(ETableHeader *eth,
+						 gint column);
+ETableCol *	e_table_header_get_column_by_col_idx
+						(ETableHeader *eth,
+						 gint col_idx);
+gint		e_table_header_count		(ETableHeader *eth);
+gint		e_table_header_index		(ETableHeader *eth,
+						 gint col);
+gint		e_table_header_get_index_at	(ETableHeader *eth,
+						 gint x_offset);
+ETableCol **	e_table_header_get_columns	(ETableHeader *eth);
+gint		e_table_header_get_selected	(ETableHeader *eth);
+
+gint		e_table_header_total_width	(ETableHeader *eth);
+gint		e_table_header_min_width	(ETableHeader *eth);
+void		e_table_header_move		(ETableHeader *eth,
+						 gint source_index,
+						 gint target_index);
+void		e_table_header_remove		(ETableHeader *eth,
+						 gint idx);
+void		e_table_header_set_size		(ETableHeader *eth,
+						 gint idx,
+						 gint size);
+void		e_table_header_set_selection	(ETableHeader *eth,
+						 gboolean allow_selection);
+gint		e_table_header_col_diff		(ETableHeader *eth,
+						 gint start_col,
+						 gint end_col);
+
+void		e_table_header_calc_widths	(ETableHeader *eth);
+GList *		e_table_header_get_selected_indexes
+						(ETableHeader *eth);
+void		e_table_header_update_horizontal
+						(ETableHeader *eth);
+gint		e_table_header_prioritized_column
+						(ETableHeader *eth);
+ETableCol *	e_table_header_prioritized_column_selected
+						(ETableHeader *eth,
+						 ETableColCheckFunc check_func,
+						 gpointer user_data);
+
+G_END_DECLS
+
+#endif /* _E_TABLE_HEADER_H_ */
+
diff --git a/e-util/e-table-item.c b/e-util/e-table-item.c
new file mode 100644
index 0000000..de749ea
--- /dev/null
+++ b/e-util/e-table-item.c
@@ -0,0 +1,4041 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * e-table-item.c
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *		Miguel de Icaza <miguel gnu org>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ */
+/*
+ * TODO:
+ *   Add a border to the thing, so that focusing works properly.
+ */
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-table-item.h"
+
+#include <math.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+#include <gdk/gdkkeysyms.h>
+
+#include "e-canvas-utils.h"
+#include "e-canvas.h"
+#include "e-cell.h"
+#include "e-marshal.h"
+#include "e-table-subset.h"
+#include "gal-a11y-e-table-item-factory.h"
+#include "gal-a11y-e-table-item.h"
+
+/* workaround for avoiding API breakage */
+#define eti_get_type e_table_item_get_type
+G_DEFINE_TYPE (ETableItem, eti, GNOME_TYPE_CANVAS_ITEM)
+
+#define FOCUSED_BORDER 2
+
+#define d(x)
+
+#if d(!)0
+#define e_table_item_leave_edit_(x) (e_table_item_leave_edit((x)), g_print ("%s: e_table_item_leave_edit\n", __FUNCTION__))
+#else
+#define e_table_item_leave_edit_(x) (e_table_item_leave_edit((x)))
+#endif
+
+static void eti_check_cursor_bounds (ETableItem *eti);
+static void eti_cancel_drag_due_to_model_change (ETableItem *eti);
+
+/* FIXME: Do an analysis of which cell functions are needed before
+ * realize and make sure that all of them are doable by all the cells
+ * and that all of the others are only done after realization. */
+
+enum {
+	CURSOR_CHANGE,
+	CURSOR_ACTIVATED,
+	DOUBLE_CLICK,
+	RIGHT_CLICK,
+	CLICK,
+	KEY_PRESS,
+	START_DRAG,
+	STYLE_SET,
+	SELECTION_MODEL_REMOVED,
+	SELECTION_MODEL_ADDED,
+	LAST_SIGNAL
+};
+
+static guint eti_signals[LAST_SIGNAL] = { 0, };
+
+enum {
+	PROP_0,
+	PROP_TABLE_HEADER,
+	PROP_TABLE_MODEL,
+	PROP_SELECTION_MODEL,
+	PROP_TABLE_ALTERNATING_ROW_COLORS,
+	PROP_TABLE_HORIZONTAL_DRAW_GRID,
+	PROP_TABLE_VERTICAL_DRAW_GRID,
+	PROP_TABLE_DRAW_FOCUS,
+	PROP_CURSOR_MODE,
+	PROP_LENGTH_THRESHOLD,
+	PROP_CURSOR_ROW,
+	PROP_UNIFORM_ROW_HEIGHT,
+
+	PROP_MINIMUM_WIDTH,
+	PROP_WIDTH,
+	PROP_HEIGHT
+};
+
+#define DOUBLE_CLICK_TIME      250
+#define TRIPLE_CLICK_TIME      500
+
+static gint eti_get_height (ETableItem *eti);
+static gint eti_row_height (ETableItem *eti, gint row);
+static void e_table_item_focus (ETableItem *eti, gint col, gint row, GdkModifierType state);
+static void eti_cursor_change (ESelectionModel *selection, gint row, gint col, ETableItem *eti);
+static void eti_cursor_activated (ESelectionModel *selection, gint row, gint col, ETableItem *eti);
+static void eti_selection_change (ESelectionModel *selection, ETableItem *eti);
+static void eti_selection_row_change (ESelectionModel *selection, gint row, ETableItem *eti);
+static void e_table_item_redraw_row (ETableItem *eti, gint row);
+
+#define ETI_SINGLE_ROW_HEIGHT(eti) ((eti)->uniform_row_height_cache != -1 ? (eti)->uniform_row_height_cache : eti_row_height((eti), -1))
+#define ETI_MULTIPLE_ROW_HEIGHT(eti,row) ((eti)->height_cache && (eti)->height_cache[(row)] != -1 ? (eti)->height_cache[(row)] : eti_row_height((eti),(row)))
+#define ETI_ROW_HEIGHT(eti,row) ((eti)->uniform_row_height ? ETI_SINGLE_ROW_HEIGHT ((eti)) : ETI_MULTIPLE_ROW_HEIGHT((eti),(row)))
+
+/* tweak_hsv is a really tweaky function. it modifies its first argument, which
+ * should be the color you want tweaked. delta_h, delta_s and delta_v specify
+ * how much you want their respective channels modified (and in what direction).
+ * if it can't do the specified modification, it does it in the oppositon direction */
+static void
+e_hsv_tweak (GdkColor *color,
+             gdouble delta_h,
+             gdouble delta_s,
+             gdouble delta_v)
+{
+	gdouble h, s, v, r, g, b;
+
+	r = color->red   / 65535.0f;
+	g = color->green / 65535.0f;
+	b = color->blue  / 65535.0f;
+
+	gtk_rgb_to_hsv (r, g, b, &h, &s, &v);
+
+	if (h + delta_h < 0) {
+		h -= delta_h;
+	} else {
+		h += delta_h;
+	}
+
+	if (s + delta_s < 0) {
+		s -= delta_s;
+	} else {
+		s += delta_s;
+	}
+
+	if (v + delta_v < 0) {
+		v -= delta_v;
+	} else {
+		v += delta_v;
+	}
+
+	gtk_hsv_to_rgb (h, s, v, &r, &g, &b);
+
+	color->red   = r * 65535.0f;
+	color->green = g * 65535.0f;
+	color->blue  = b * 65535.0f;
+}
+
+inline static gint
+model_to_view_row (ETableItem *eti,
+                   gint row)
+{
+	gint i;
+	if (row == -1)
+		return -1;
+	if (eti->uses_source_model) {
+		ETableSubset *etss = E_TABLE_SUBSET (eti->table_model);
+		if (eti->row_guess >= 0 && eti->row_guess < etss->n_map) {
+			if (etss->map_table[eti->row_guess] == row) {
+				return eti->row_guess;
+			}
+		}
+		for (i = 0; i < etss->n_map; i++) {
+			if (etss->map_table[i] == row)
+				return i;
+		}
+		return -1;
+	} else
+		return row;
+}
+
+inline static gint
+view_to_model_row (ETableItem *eti,
+                   gint row)
+{
+	if (eti->uses_source_model) {
+		ETableSubset *etss = E_TABLE_SUBSET (eti->table_model);
+		if (row >= 0 && row < etss->n_map) {
+			eti->row_guess = row;
+			return etss->map_table[row];
+		} else
+			return -1;
+	} else
+		return row;
+}
+
+inline static gint
+model_to_view_col (ETableItem *eti,
+                   gint col)
+{
+	gint i;
+	if (col == -1)
+		return -1;
+	for (i = 0; i < eti->cols; i++) {
+		ETableCol *ecol = e_table_header_get_column (eti->header, i);
+		if (ecol->col_idx == col)
+			return i;
+	}
+	return -1;
+}
+
+inline static gint
+view_to_model_col (ETableItem *eti,
+                   gint col)
+{
+	ETableCol *ecol = e_table_header_get_column (eti->header, col);
+	return ecol ? ecol->col_idx : -1;
+}
+
+static void
+grab_cancelled (ECanvas *canvas,
+                GnomeCanvasItem *item,
+                gpointer data)
+{
+	ETableItem *eti = data;
+
+	eti->grab_cancelled = TRUE;
+}
+
+inline static void
+eti_grab (ETableItem *eti,
+          GdkDevice *device,
+          guint32 time)
+{
+	GnomeCanvasItem *item = GNOME_CANVAS_ITEM (eti);
+	d (g_print ("%s: time: %d\n", __FUNCTION__, time));
+	if (eti->grabbed_count == 0) {
+		GdkGrabStatus grab_status;
+
+		eti->gtk_grabbed = FALSE;
+		eti->grab_cancelled = FALSE;
+
+		grab_status = e_canvas_item_grab (
+			E_CANVAS (item->canvas),
+			item,
+			GDK_BUTTON1_MOTION_MASK |
+			GDK_BUTTON2_MOTION_MASK |
+			GDK_BUTTON3_MOTION_MASK |
+			GDK_POINTER_MOTION_MASK |
+			GDK_BUTTON_PRESS_MASK |
+			GDK_BUTTON_RELEASE_MASK,
+			NULL,
+			device, time,
+			grab_cancelled,
+			eti);
+
+		if (grab_status != GDK_GRAB_SUCCESS) {
+			d (g_print ("%s: gtk_grab_add\n", __FUNCTION__));
+			gtk_grab_add (GTK_WIDGET (item->canvas));
+			eti->gtk_grabbed = TRUE;
+		}
+	}
+	eti->grabbed_count++;
+}
+
+inline static void
+eti_ungrab (ETableItem *eti,
+            guint32 time)
+{
+	GnomeCanvasItem *item = GNOME_CANVAS_ITEM (eti);
+	d (g_print ("%s: time: %d\n", __FUNCTION__, time));
+	eti->grabbed_count--;
+	if (eti->grabbed_count == 0) {
+		if (eti->grab_cancelled) {
+			eti->grab_cancelled = FALSE;
+		} else {
+			if (eti->gtk_grabbed) {
+				d (g_print ("%s: gtk_grab_remove\n", __FUNCTION__));
+				gtk_grab_remove (GTK_WIDGET (item->canvas));
+				eti->gtk_grabbed = FALSE;
+			}
+			gnome_canvas_item_ungrab (item, time);
+			eti->grabbed_col = -1;
+			eti->grabbed_row = -1;
+		}
+	}
+}
+
+inline static gboolean
+eti_editing (ETableItem *eti)
+{
+	d (g_print ("%s: %s\n", __FUNCTION__, (eti->editing_col == -1) ? "false":"true"));
+
+	if (eti->editing_col == -1)
+		return FALSE;
+	else
+		return TRUE;
+}
+
+inline static GdkColor *
+eti_get_cell_background_color (ETableItem *eti,
+                               gint row,
+                               gint col,
+                               gboolean selected,
+                               gboolean *allocatedp)
+{
+	ECellView *ecell_view = eti->cell_views[col];
+	GtkWidget *canvas;
+	GdkColor *background, bg;
+	GtkStyle *style;
+	gchar *color_spec = NULL;
+	gboolean allocated = FALSE;
+
+	canvas = GTK_WIDGET (GNOME_CANVAS_ITEM (eti)->canvas);
+	style = gtk_widget_get_style (canvas);
+
+	if (selected) {
+		if (gtk_widget_has_focus (canvas))
+			background = &style->bg[GTK_STATE_SELECTED];
+		else
+			background = &style->bg[GTK_STATE_ACTIVE];
+	} else {
+		background = &style->base[GTK_STATE_NORMAL];
+	}
+
+	color_spec = e_cell_get_bg_color (ecell_view, row);
+
+	if (color_spec != NULL) {
+		if (gdk_color_parse (color_spec, &bg)) {
+			background = gdk_color_copy (&bg);
+			allocated = TRUE;
+		}
+	}
+
+	if (eti->alternating_row_colors) {
+		if (row % 2) {
+
+		} else {
+			if (!allocated) {
+				background = gdk_color_copy (background);
+				allocated = TRUE;
+			}
+			e_hsv_tweak (background, 0.0f, 0.0f, -0.07f);
+		}
+	}
+	if (allocatedp)
+		*allocatedp = allocated;
+
+	return background;
+}
+
+inline static GdkColor *
+eti_get_cell_foreground_color (ETableItem *eti,
+                               gint row,
+                               gint col,
+                               gboolean selected,
+                               gboolean *allocated)
+{
+	GtkWidget *canvas;
+	GdkColor *foreground;
+	GtkStyle *style;
+
+	canvas = GTK_WIDGET (GNOME_CANVAS_ITEM (eti)->canvas);
+	style = gtk_widget_get_style (canvas);
+
+	if (allocated)
+		*allocated = FALSE;
+
+	if (selected) {
+		if (gtk_widget_has_focus (canvas))
+			foreground = &style->fg[GTK_STATE_SELECTED];
+		else
+			foreground = &style->fg[GTK_STATE_ACTIVE];
+	} else {
+		foreground = &style->text[GTK_STATE_NORMAL];
+	}
+
+	return foreground;
+}
+
+static void
+eti_free_save_state (ETableItem *eti)
+{
+	if (eti->save_row == -1 ||
+	    !eti->cell_views_realized)
+		return;
+
+	e_cell_free_state (
+		eti->cell_views[eti->save_col], view_to_model_col (eti, eti->save_col),
+		eti->save_col, eti->save_row, eti->save_state);
+	eti->save_row = -1;
+	eti->save_col = -1;
+	eti->save_state = NULL;
+}
+
+/*
+ * During realization, we have to invoke the per-ecell realize routine
+ * (On our current setup, we have one e-cell per column.
+ *
+ * We might want to optimize this to only realize the unique e-cells:
+ * ie, a strings-only table, uses the same e-cell for every column, and
+ * we might want to avoid realizing each e-cell.
+ */
+static void
+eti_realize_cell_views (ETableItem *eti)
+{
+	GnomeCanvasItem *item;
+	gint i;
+
+	item = GNOME_CANVAS_ITEM (eti);
+
+	if (eti->cell_views_realized)
+		return;
+
+	if (!(item->flags & GNOME_CANVAS_ITEM_REALIZED))
+		return;
+
+	for (i = 0; i < eti->n_cells; i++)
+		e_cell_realize (eti->cell_views[i]);
+	eti->cell_views_realized = 1;
+}
+
+static void
+eti_attach_cell_views (ETableItem *eti)
+{
+	gint i;
+
+	g_return_if_fail (eti->header);
+	g_return_if_fail (eti->table_model);
+
+	/* this is just c&p from model pre change, but it fixes things */
+	eti_cancel_drag_due_to_model_change (eti);
+	eti_check_cursor_bounds (eti);
+	if (eti_editing (eti))
+		e_table_item_leave_edit_(eti);
+	eti->motion_row = -1;
+	eti->motion_col = -1;
+
+	/*
+	 * Now realize the various ECells
+	 */
+	eti->n_cells = eti->cols;
+	eti->cell_views = g_new (ECellView *, eti->n_cells);
+
+	for (i = 0; i < eti->n_cells; i++) {
+		ETableCol *ecol = e_table_header_get_column (eti->header, i);
+
+		eti->cell_views[i] = e_cell_new_view (ecol->ecell, eti->table_model, eti);
+	}
+
+	eti->needs_compute_height = 1;
+	e_canvas_item_request_reflow (GNOME_CANVAS_ITEM (eti));
+	eti->needs_redraw = 1;
+	gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (eti));
+}
+
+/*
+ * During unrealization: we invoke every e-cell (one per column in the current
+ * setup) to dispose all X resources allocated
+ */
+static void
+eti_unrealize_cell_views (ETableItem *eti)
+{
+	gint i;
+
+	if (eti->cell_views_realized == 0)
+		return;
+
+	eti_free_save_state (eti);
+
+	for (i = 0; i < eti->n_cells; i++)
+		e_cell_unrealize (eti->cell_views[i]);
+	eti->cell_views_realized = 0;
+}
+
+static void
+eti_detach_cell_views (ETableItem *eti)
+{
+	gint i;
+
+	eti_free_save_state (eti);
+
+	for (i = 0; i < eti->n_cells; i++) {
+		e_cell_kill_view (eti->cell_views[i]);
+		eti->cell_views[i] = NULL;
+	}
+
+	g_free (eti->cell_views);
+	eti->cell_views = NULL;
+	eti->n_cells = 0;
+}
+
+static void
+eti_bounds (GnomeCanvasItem *item,
+            gdouble *x1,
+            gdouble *y1,
+            gdouble *x2,
+            gdouble *y2)
+{
+	cairo_matrix_t i2c;
+	ETableItem *eti = E_TABLE_ITEM (item);
+
+	/* Wrong BBox's are the source of redraw nightmares */
+
+	*x1 = 0;
+	*y1 = 0;
+	*x2 = eti->width;
+	*y2 = eti->height;
+
+	gnome_canvas_item_i2c_matrix (GNOME_CANVAS_ITEM (eti), &i2c);
+	gnome_canvas_matrix_transform_rect (&i2c, x1, y1, x2, y2);
+}
+
+static void
+eti_reflow (GnomeCanvasItem *item,
+            gint flags)
+{
+	ETableItem *eti = E_TABLE_ITEM (item);
+
+	if (eti->needs_compute_height) {
+		gint new_height = eti_get_height (eti);
+
+		if (new_height != eti->height) {
+			eti->height = new_height;
+			e_canvas_item_request_parent_reflow (GNOME_CANVAS_ITEM (eti));
+			eti->needs_redraw = 1;
+			gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (eti));
+		}
+		eti->needs_compute_height = 0;
+	}
+	if (eti->needs_compute_width) {
+		gint new_width = e_table_header_total_width (eti->header);
+		if (new_width != eti->width) {
+			eti->width = new_width;
+			e_canvas_item_request_parent_reflow (GNOME_CANVAS_ITEM (eti));
+			eti->needs_redraw = 1;
+			gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (eti));
+		}
+		eti->needs_compute_width = 0;
+	}
+}
+
+/*
+ * GnomeCanvasItem::update method
+ */
+static void
+eti_update (GnomeCanvasItem *item,
+            const cairo_matrix_t *i2c,
+            gint flags)
+{
+	ETableItem *eti = E_TABLE_ITEM (item);
+	gdouble x1, x2, y1, y2;
+
+	if (GNOME_CANVAS_ITEM_CLASS (eti_parent_class)->update)
+		(*GNOME_CANVAS_ITEM_CLASS (eti_parent_class)->update)(item, i2c, flags);
+
+	x1 = item->x1;
+	y1 = item->y1;
+	x2 = item->x2;
+	y2 = item->y2;
+
+	eti_bounds (item, &item->x1, &item->y1, &item->x2, &item->y2);
+	if (item->x1 != x1 ||
+	    item->y1 != y1 ||
+	    item->x2 != x2 ||
+	    item->y2 != y2) {
+		gnome_canvas_request_redraw (item->canvas, x1, y1, x2, y2);
+		eti->needs_redraw = 1;
+	}
+
+	if (eti->needs_redraw) {
+		gnome_canvas_request_redraw (
+			item->canvas, item->x1, item->y1,
+			item->x2, item->y2);
+		eti->needs_redraw = 0;
+	}
+}
+
+/*
+ * eti_remove_table_model:
+ *
+ * Invoked to release the table model associated with this ETableItem
+ */
+static void
+eti_remove_table_model (ETableItem *eti)
+{
+	if (!eti->table_model)
+		return;
+
+	g_signal_handler_disconnect (
+		eti->table_model,
+		eti->table_model_pre_change_id);
+	g_signal_handler_disconnect (
+		eti->table_model,
+		eti->table_model_no_change_id);
+	g_signal_handler_disconnect (
+		eti->table_model,
+		eti->table_model_change_id);
+	g_signal_handler_disconnect (
+		eti->table_model,
+		eti->table_model_row_change_id);
+	g_signal_handler_disconnect (
+		eti->table_model,
+		eti->table_model_cell_change_id);
+	g_signal_handler_disconnect (
+		eti->table_model,
+		eti->table_model_rows_inserted_id);
+	g_signal_handler_disconnect (
+		eti->table_model,
+		eti->table_model_rows_deleted_id);
+	g_object_unref (eti->table_model);
+	if (eti->source_model)
+		g_object_unref (eti->source_model);
+
+	eti->table_model_pre_change_id = 0;
+	eti->table_model_no_change_id = 0;
+	eti->table_model_change_id = 0;
+	eti->table_model_row_change_id = 0;
+	eti->table_model_cell_change_id = 0;
+	eti->table_model_rows_inserted_id = 0;
+	eti->table_model_rows_deleted_id = 0;
+	eti->table_model = NULL;
+	eti->source_model = NULL;
+	eti->uses_source_model = 0;
+}
+
+/*
+ * eti_remove_table_model:
+ *
+ * Invoked to release the table model associated with this ETableItem
+ */
+static void
+eti_remove_selection_model (ETableItem *eti)
+{
+	if (!eti->selection)
+		return;
+
+	g_signal_handler_disconnect (
+		eti->selection,
+		eti->selection_change_id);
+	g_signal_handler_disconnect (
+		eti->selection,
+		eti->selection_row_change_id);
+	g_signal_handler_disconnect (
+		eti->selection,
+		eti->cursor_change_id);
+	g_signal_handler_disconnect (
+		eti->selection,
+		eti->cursor_activated_id);
+	g_object_unref (eti->selection);
+
+	eti->selection_change_id = 0;
+	eti->selection_row_change_id = 0;
+	eti->cursor_activated_id = 0;
+	eti->selection = NULL;
+}
+
+/*
+ * eti_remove_header_model:
+ *
+ * Invoked to release the header model associated with this ETableItem
+ */
+static void
+eti_remove_header_model (ETableItem *eti)
+{
+	if (!eti->header)
+		return;
+
+	g_signal_handler_disconnect (
+		eti->header,
+		eti->header_structure_change_id);
+	g_signal_handler_disconnect (
+		eti->header,
+		eti->header_dim_change_id);
+	g_signal_handler_disconnect (
+		eti->header,
+		eti->header_request_width_id);
+
+	if (eti->cell_views) {
+		eti_unrealize_cell_views (eti);
+		eti_detach_cell_views (eti);
+	}
+	g_object_unref (eti->header);
+
+	eti->header_structure_change_id = 0;
+	eti->header_dim_change_id = 0;
+	eti->header_request_width_id = 0;
+	eti->header = NULL;
+}
+
+/*
+ * eti_row_height_real:
+ *
+ * Returns the height used by row @row.  This does not include the one-pixel
+ * used as a separator between rows
+ */
+static gint
+eti_row_height_real (ETableItem *eti,
+                     gint row)
+{
+	const gint cols = e_table_header_count (eti->header);
+	gint col;
+	gint h, max_h;
+
+	g_return_val_if_fail (cols == 0 || eti->cell_views, 0);
+
+	max_h = 0;
+
+	for (col = 0; col < cols; col++) {
+		h = e_cell_height (eti->cell_views[col], view_to_model_col (eti, col), col, row);
+
+		if (h > max_h)
+			max_h = h;
+	}
+	return max_h;
+}
+
+static void
+confirm_height_cache (ETableItem *eti)
+{
+	gint i;
+
+	if (eti->uniform_row_height || eti->height_cache)
+		return;
+	eti->height_cache = g_new (int, eti->rows);
+	for (i = 0; i < eti->rows; i++) {
+		eti->height_cache[i] = -1;
+	}
+}
+
+static gboolean
+height_cache_idle (ETableItem *eti)
+{
+	gint changed = 0;
+	gint i;
+	confirm_height_cache (eti);
+	for (i = eti->height_cache_idle_count; i < eti->rows; i++) {
+		if (eti->height_cache[i] == -1) {
+			eti_row_height (eti, i);
+			changed++;
+			if (changed >= 20)
+				break;
+		}
+	}
+	if (changed >= 20) {
+		eti->height_cache_idle_count = i;
+		return TRUE;
+	}
+	eti->height_cache_idle_id = 0;
+	return FALSE;
+}
+
+static void
+free_height_cache (ETableItem *eti)
+{
+	GnomeCanvasItem *item;
+
+	item = GNOME_CANVAS_ITEM (eti);
+
+	if (item->flags & GNOME_CANVAS_ITEM_REALIZED) {
+		if (eti->height_cache)
+			g_free (eti->height_cache);
+		eti->height_cache = NULL;
+		eti->height_cache_idle_count = 0;
+		eti->uniform_row_height_cache = -1;
+
+		if (eti->uniform_row_height && eti->height_cache_idle_id != 0) {
+			g_source_remove (eti->height_cache_idle_id);
+			eti->height_cache_idle_id = 0;
+		}
+
+		if ((!eti->uniform_row_height) && eti->height_cache_idle_id == 0)
+			eti->height_cache_idle_id = g_idle_add_full (G_PRIORITY_LOW, (GSourceFunc) height_cache_idle, eti, NULL);
+	}
+}
+
+static void
+calculate_height_cache (ETableItem *eti)
+{
+	free_height_cache (eti);
+	confirm_height_cache (eti);
+}
+
+/*
+ * eti_row_height:
+ *
+ * Returns the height used by row @row.  This does not include the one-pixel
+ * used as a separator between rows
+ */
+static gint
+eti_row_height (ETableItem *eti,
+                gint row)
+{
+	if (eti->uniform_row_height) {
+		eti->uniform_row_height_cache = eti_row_height_real (eti, -1);
+		return eti->uniform_row_height_cache;
+	} else {
+		if (!eti->height_cache) {
+			calculate_height_cache (eti);
+		}
+		if (eti->height_cache[row] == -1) {
+			eti->height_cache[row] = eti_row_height_real (eti, row);
+			if (row > 0 &&
+			    eti->length_threshold != -1 &&
+			    eti->rows > eti->length_threshold &&
+			    eti->height_cache[row] != eti_row_height (eti, 0)) {
+				eti->needs_compute_height = 1;
+				e_canvas_item_request_reflow (GNOME_CANVAS_ITEM (eti));
+			}
+		}
+		return eti->height_cache[row];
+	}
+}
+
+/*
+ * eti_get_height:
+ *
+ * Returns the height of the ETableItem.
+ *
+ * The ETableItem might compute the whole height by asking every row its
+ * size.  There is a special mode (designed to work when there are too
+ * many rows in the table that performing the previous step could take
+ * too long) set by the ETableItem->length_threshold that would determine
+ * when the height is computed by using the first row as the size for
+ * every other row in the ETableItem.
+ */
+static gint
+eti_get_height (ETableItem *eti)
+{
+	const gint rows = eti->rows;
+	gint height_extra = eti->horizontal_draw_grid ? 1 : 0;
+
+	if (rows == 0)
+		return 0;
+
+	if (eti->uniform_row_height) {
+		gint row_height = ETI_ROW_HEIGHT (eti, -1);
+		return ((row_height + height_extra) * rows + height_extra);
+	} else {
+		gint height;
+		gint row;
+		if (eti->length_threshold != -1) {
+			if (rows > eti->length_threshold) {
+				gint row_height = ETI_ROW_HEIGHT (eti, 0);
+				if (eti->height_cache) {
+					height = 0;
+					for (row = 0; row < rows; row++) {
+						if (eti->height_cache[row] == -1) {
+							height += (row_height + height_extra) * (rows - row);
+							break;
+						}
+						else
+							height += eti->height_cache[row] + height_extra;
+					}
+				} else
+					height = (ETI_ROW_HEIGHT (eti, 0) + height_extra) * rows;
+
+				/*
+				 * 1 pixel at the top
+				 */
+				return height + height_extra;
+			}
+		}
+
+		height = height_extra;
+		for (row = 0; row < rows; row++)
+			height += ETI_ROW_HEIGHT (eti, row) + height_extra;
+
+		return height;
+	}
+}
+
+static void
+eti_item_region_redraw (ETableItem *eti,
+                        gint x0,
+                        gint y0,
+                        gint x1,
+                        gint y1)
+{
+	GnomeCanvasItem *item = GNOME_CANVAS_ITEM (eti);
+	gdouble dx1, dy1, dx2, dy2;
+	cairo_matrix_t i2c;
+
+	dx1 = x0;
+	dy1 = y0;
+	dx2 = x1;
+	dy2 = y1;
+
+	gnome_canvas_item_i2c_matrix (item, &i2c);
+	gnome_canvas_matrix_transform_rect (&i2c, &dx1, &dy1, &dx2, &dy2);
+
+	gnome_canvas_request_redraw (item->canvas, floor (dx1), floor (dy1), ceil (dx2), ceil (dy2));
+}
+
+/*
+ * Computes the distance between @start_row and @end_row in pixels
+ */
+gint
+e_table_item_row_diff (ETableItem *eti,
+                       gint start_row,
+                       gint end_row)
+{
+	gint height_extra = eti->horizontal_draw_grid ? 1 : 0;
+
+	if (start_row < 0)
+		start_row = 0;
+	if (end_row > eti->rows)
+		end_row = eti->rows;
+
+	if (eti->uniform_row_height) {
+		return ((end_row - start_row) * (ETI_ROW_HEIGHT (eti, -1) + height_extra));
+	} else {
+		gint row, total;
+		total = 0;
+		for (row = start_row; row < end_row; row++)
+			total += ETI_ROW_HEIGHT (eti, row) + height_extra;
+
+		return total;
+	}
+}
+
+static void
+eti_get_region (ETableItem *eti,
+                gint start_col,
+                gint start_row,
+                gint end_col,
+                gint end_row,
+                gint *x1p,
+                gint *y1p,
+                gint *x2p,
+                gint *y2p)
+{
+	gint x1, y1, x2, y2;
+
+	x1 = e_table_header_col_diff (eti->header, 0, start_col);
+	y1 = e_table_item_row_diff (eti, 0, start_row);
+	x2 = x1 + e_table_header_col_diff (eti->header, start_col, end_col + 1);
+	y2 = y1 + e_table_item_row_diff (eti, start_row, end_row + 1);
+	if (x1p)
+		*x1p = x1;
+	if (y1p)
+		*y1p = y1;
+	if (x2p)
+		*x2p = x2;
+	if (y2p)
+		*y2p = y2;
+}
+
+/*
+ * eti_request_region_redraw:
+ *
+ * Request a canvas redraw on the range (start_col, start_row) to (end_col, end_row).
+ * This is inclusive (ie, you can use: 0,0-0,0 to redraw the first cell).
+ *
+ * The @border argument is a number of pixels around the region that should also be queued
+ * for redraw.   This is typically used by the focus routines to queue a redraw for the
+ * border as well.
+ */
+static void
+eti_request_region_redraw (ETableItem *eti,
+                           gint start_col,
+                           gint start_row,
+                           gint end_col,
+                           gint end_row,
+                           gint border)
+{
+	gint x1, y1, x2, y2;
+
+	if (eti->rows > 0) {
+
+		eti_get_region (
+			eti,
+			start_col, start_row,
+			end_col, end_row,
+			&x1, &y1, &x2, &y2);
+
+		eti_item_region_redraw (
+			eti,
+			x1 - border,
+			y1 - border,
+			x2 + 1 + border,
+			y2 + 1 + border);
+	}
+}
+
+/*
+ * eti_request_region_show
+ *
+ * Request a canvas show on the range (start_col, start_row) to (end_col, end_row).
+ * This is inclusive (ie, you can use: 0,0-0,0 to show the first cell).
+ */
+static void
+eti_request_region_show (ETableItem *eti,
+                         gint start_col,
+                         gint start_row,
+                         gint end_col,
+                         gint end_row,
+                         gint delay)
+{
+	gint x1, y1, x2, y2;
+
+	eti_get_region (
+		eti,
+		start_col, start_row,
+		end_col, end_row,
+		&x1, &y1, &x2, &y2);
+
+	if (delay)
+		e_canvas_item_show_area_delayed (
+			GNOME_CANVAS_ITEM (eti), x1, y1, x2, y2, delay);
+	else
+		e_canvas_item_show_area (
+			GNOME_CANVAS_ITEM (eti), x1, y1, x2, y2);
+}
+
+static void
+eti_show_cursor (ETableItem *eti,
+                 gint delay)
+{
+	GnomeCanvasItem *item;
+	gint cursor_row;
+
+	item = GNOME_CANVAS_ITEM (eti);
+
+	if (!((item->flags & GNOME_CANVAS_ITEM_REALIZED) && eti->cell_views_realized))
+		return;
+
+	if (eti->frozen_count > 0) {
+		eti->queue_show_cursor = TRUE;
+		return;
+	}
+
+#if 0
+	g_object_get (
+		eti->selection,
+		"cursor_row", &cursor_row,
+		NULL);
+#else
+	cursor_row = e_selection_model_cursor_row (eti->selection);
+#endif
+
+	d (g_print ("%s: cursor row: %d\n", __FUNCTION__, cursor_row));
+
+	if (cursor_row != -1) {
+		cursor_row = model_to_view_row (eti, cursor_row);
+		eti_request_region_show (
+			eti,
+			0, cursor_row, eti->cols - 1, cursor_row,
+			delay);
+	}
+}
+
+static void
+eti_check_cursor_bounds (ETableItem *eti)
+{
+	GnomeCanvasItem *item;
+	gint x1, y1, x2, y2;
+	gint cursor_row;
+
+	item = GNOME_CANVAS_ITEM (eti);
+
+	if (!((item->flags & GNOME_CANVAS_ITEM_REALIZED) && eti->cell_views_realized))
+		return;
+
+	if (eti->frozen_count > 0) {
+		return;
+	}
+
+	g_object_get (
+		eti->selection,
+		"cursor_row", &cursor_row,
+		NULL);
+
+	if (cursor_row == -1) {
+		eti->cursor_x1 = -1;
+		eti->cursor_y1 = -1;
+		eti->cursor_x2 = -1;
+		eti->cursor_y2 = -1;
+		eti->cursor_on_screen = TRUE;
+		return;
+	}
+
+	d (g_print ("%s: model cursor row: %d\n", __FUNCTION__, cursor_row));
+
+	cursor_row = model_to_view_row (eti, cursor_row);
+
+	d (g_print ("%s: cursor row: %d\n", __FUNCTION__, cursor_row));
+
+	eti_get_region (
+		eti,
+		0, cursor_row, eti->cols - 1, cursor_row,
+		&x1, &y1, &x2, &y2);
+	eti->cursor_x1 = x1;
+	eti->cursor_y1 = y1;
+	eti->cursor_x2 = x2;
+	eti->cursor_y2 = y2;
+	eti->cursor_on_screen = e_canvas_item_area_shown (GNOME_CANVAS_ITEM (eti), x1, y1, x2, y2);
+
+	d (g_print ("%s: cursor on screen: %s\n", __FUNCTION__, eti->cursor_on_screen ? "TRUE" : "FALSE"));
+}
+
+static void
+eti_maybe_show_cursor (ETableItem *eti,
+                       gint delay)
+{
+	d (g_print ("%s: cursor on screen: %s\n", __FUNCTION__, eti->cursor_on_screen ? "TRUE" : "FALSE"));
+	if (eti->cursor_on_screen)
+		eti_show_cursor (eti, delay);
+	eti_check_cursor_bounds (eti);
+}
+
+static gboolean
+eti_idle_show_cursor_cb (gpointer data)
+{
+	ETableItem *eti = data;
+
+	if (eti->selection) {
+		eti_show_cursor (eti, 0);
+		eti_check_cursor_bounds (eti);
+	}
+
+	eti->cursor_idle_id = 0;
+	g_object_unref (eti);
+	return FALSE;
+}
+
+static void
+eti_idle_maybe_show_cursor (ETableItem *eti)
+{
+	d (g_print ("%s: cursor on screen: %s\n", __FUNCTION__, eti->cursor_on_screen ? "TRUE" : "FALSE"));
+	if (eti->cursor_on_screen) {
+		g_object_ref (eti);
+		if (!eti->cursor_idle_id)
+			eti->cursor_idle_id = g_idle_add (eti_idle_show_cursor_cb, eti);
+	}
+}
+
+static void
+eti_cancel_drag_due_to_model_change (ETableItem *eti)
+{
+	if (eti->maybe_in_drag) {
+		eti->maybe_in_drag = FALSE;
+		if (!eti->maybe_did_something)
+			e_selection_model_do_something (E_SELECTION_MODEL (eti->selection), eti->drag_row, eti->drag_col, eti->drag_state);
+	}
+	if (eti->in_drag) {
+		eti->in_drag = FALSE;
+	}
+}
+
+static void
+eti_freeze (ETableItem *eti)
+{
+	eti->frozen_count++;
+	d (g_print ("%s: %d\n", __FUNCTION__, eti->frozen_count));
+}
+
+static void
+eti_unfreeze (ETableItem *eti)
+{
+	if (eti->frozen_count <= 0)
+		return;
+
+	eti->frozen_count--;
+	d (g_print ("%s: %d\n", __FUNCTION__, eti->frozen_count));
+	if (eti->frozen_count == 0 && eti->queue_show_cursor) {
+		eti_show_cursor (eti, 0);
+		eti_check_cursor_bounds (eti);
+		eti->queue_show_cursor = FALSE;
+	}
+}
+
+/*
+ * Callback routine: invoked before the ETableModel suffers a change
+ */
+static void
+eti_table_model_pre_change (ETableModel *table_model,
+                            ETableItem *eti)
+{
+	eti_cancel_drag_due_to_model_change (eti);
+	eti_check_cursor_bounds (eti);
+	if (eti_editing (eti))
+		e_table_item_leave_edit_(eti);
+	eti->motion_row = -1;
+	eti->motion_col = -1;
+	eti_freeze (eti);
+}
+
+/*
+ * Callback routine: invoked when the ETableModel has not suffered a change
+ */
+static void
+eti_table_model_no_change (ETableModel *table_model,
+                           ETableItem *eti)
+{
+	eti_unfreeze (eti);
+}
+
+/*
+ * Callback routine: invoked when the ETableModel has suffered a change
+ */
+
+static void
+eti_table_model_changed (ETableModel *table_model,
+                         ETableItem *eti)
+{
+	GnomeCanvasItem *item = GNOME_CANVAS_ITEM (eti);
+
+	if (!(item->flags & GNOME_CANVAS_ITEM_REALIZED)) {
+		eti_unfreeze (eti);
+		return;
+	}
+
+	eti->rows = e_table_model_row_count (eti->table_model);
+
+	free_height_cache (eti);
+
+	eti_unfreeze (eti);
+
+	eti->needs_compute_height = 1;
+	e_canvas_item_request_reflow (GNOME_CANVAS_ITEM (eti));
+	eti->needs_redraw = 1;
+	gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (eti));
+
+	eti_idle_maybe_show_cursor (eti);
+}
+
+static void
+eti_table_model_row_changed (ETableModel *table_model,
+                             gint row,
+                             ETableItem *eti)
+{
+	GnomeCanvasItem *item = GNOME_CANVAS_ITEM (eti);
+
+	if (!(item->flags & GNOME_CANVAS_ITEM_REALIZED)) {
+		eti_unfreeze (eti);
+		return;
+	}
+
+	if ((!eti->uniform_row_height) && eti->height_cache && eti->height_cache[row] != -1 && eti_row_height_real (eti, row) != eti->height_cache[row]) {
+		eti_table_model_changed (table_model, eti);
+		return;
+	}
+
+	eti_unfreeze (eti);
+
+	e_table_item_redraw_row (eti, row);
+}
+
+static void
+eti_table_model_cell_changed (ETableModel *table_model,
+                              gint col,
+                              gint row,
+                              ETableItem *eti)
+{
+	GnomeCanvasItem *item = GNOME_CANVAS_ITEM (eti);
+
+	if (!(item->flags & GNOME_CANVAS_ITEM_REALIZED)) {
+		eti_unfreeze (eti);
+		return;
+	}
+
+	if ((!eti->uniform_row_height) && eti->height_cache && eti->height_cache[row] != -1 && eti_row_height_real (eti, row) != eti->height_cache[row]) {
+		eti_table_model_changed (table_model, eti);
+		return;
+	}
+
+	eti_unfreeze (eti);
+
+	e_table_item_redraw_row (eti, row);
+}
+
+static void
+eti_table_model_rows_inserted (ETableModel *table_model,
+                               gint row,
+                               gint count,
+                               ETableItem *eti)
+{
+	GnomeCanvasItem *item = GNOME_CANVAS_ITEM (eti);
+
+	if (!(item->flags & GNOME_CANVAS_ITEM_REALIZED)) {
+		eti_unfreeze (eti);
+		return;
+	}
+	eti->rows = e_table_model_row_count (eti->table_model);
+
+	if (eti->height_cache) {
+		gint i;
+		eti->height_cache = g_renew (int, eti->height_cache, eti->rows);
+		memmove (eti->height_cache + row + count, eti->height_cache + row, (eti->rows - count - row) * sizeof (gint));
+		for (i = row; i < row + count; i++)
+			eti->height_cache[i] = -1;
+	}
+
+	eti_unfreeze (eti);
+
+	eti_idle_maybe_show_cursor (eti);
+
+	eti->needs_compute_height = 1;
+	e_canvas_item_request_reflow (GNOME_CANVAS_ITEM (eti));
+	eti->needs_redraw = 1;
+	gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (eti));
+}
+
+static void
+eti_table_model_rows_deleted (ETableModel *table_model,
+                              gint row,
+                              gint count,
+                              ETableItem *eti)
+{
+	GnomeCanvasItem *item = GNOME_CANVAS_ITEM (eti);
+
+	if (!(item->flags & GNOME_CANVAS_ITEM_REALIZED)) {
+		eti_unfreeze (eti);
+		return;
+	}
+
+	eti->rows = e_table_model_row_count (eti->table_model);
+
+	if (eti->height_cache && (eti->rows > row)) {
+		memmove (eti->height_cache + row, eti->height_cache + row + count, (eti->rows - row) * sizeof (gint));
+	}
+
+	eti_unfreeze (eti);
+
+	eti_idle_maybe_show_cursor (eti);
+
+	eti->needs_compute_height = 1;
+	e_canvas_item_request_reflow (GNOME_CANVAS_ITEM (eti));
+	eti->needs_redraw = 1;
+	gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (eti));
+}
+
+/**
+ * e_table_item_redraw_range
+ * @eti: %ETableItem which will be redrawn
+ * @start_col: The first col to redraw.
+ * @start_row: The first row to redraw.
+ * @end_col: The last col to redraw.
+ * @end_row: The last row to redraw.
+ *
+ * This routine redraws the given %ETableItem in the range given.  The
+ * range is inclusive at both ends.
+ */
+void
+e_table_item_redraw_range (ETableItem *eti,
+                           gint start_col,
+                           gint start_row,
+                           gint end_col,
+                           gint end_row)
+{
+	gint border;
+	gint cursor_col, cursor_row;
+
+	g_return_if_fail (eti != NULL);
+	g_return_if_fail (E_IS_TABLE_ITEM (eti));
+
+	g_object_get (
+		eti->selection,
+		"cursor_col", &cursor_col,
+		"cursor_row", &cursor_row,
+		NULL);
+
+	if ((start_col == cursor_col) ||
+	    (end_col   == cursor_col) ||
+	    (view_to_model_row (eti, start_row) == cursor_row) ||
+	    (view_to_model_row (eti, end_row)   == cursor_row))
+		border = 2;
+	else
+		border = 0;
+
+	eti_request_region_redraw (eti, start_col, start_row, end_col, end_row, border);
+}
+
+static void
+e_table_item_redraw_row (ETableItem *eti,
+                         gint row)
+{
+	if (row != -1)
+		e_table_item_redraw_range (eti, 0, row, eti->cols - 1, row);
+}
+
+static void
+eti_add_table_model (ETableItem *eti,
+                     ETableModel *table_model)
+{
+	g_return_if_fail (eti->table_model == NULL);
+
+	eti->table_model = table_model;
+	g_object_ref (eti->table_model);
+
+	eti->table_model_pre_change_id = g_signal_connect (
+		table_model, "model_pre_change",
+		G_CALLBACK (eti_table_model_pre_change), eti);
+
+	eti->table_model_no_change_id = g_signal_connect (
+		table_model, "model_no_change",
+		G_CALLBACK (eti_table_model_no_change), eti);
+
+	eti->table_model_change_id = g_signal_connect (
+		table_model, "model_changed",
+		G_CALLBACK (eti_table_model_changed), eti);
+
+	eti->table_model_row_change_id = g_signal_connect (
+		table_model, "model_row_changed",
+		G_CALLBACK (eti_table_model_row_changed), eti);
+
+	eti->table_model_cell_change_id = g_signal_connect (
+		table_model, "model_cell_changed",
+		G_CALLBACK (eti_table_model_cell_changed), eti);
+
+	eti->table_model_rows_inserted_id = g_signal_connect (
+		table_model, "model_rows_inserted",
+		G_CALLBACK (eti_table_model_rows_inserted), eti);
+
+	eti->table_model_rows_deleted_id = g_signal_connect (
+		table_model, "model_rows_deleted",
+		G_CALLBACK (eti_table_model_rows_deleted), eti);
+
+	if (eti->header) {
+		eti_detach_cell_views (eti);
+		eti_attach_cell_views (eti);
+	}
+
+	if (E_IS_TABLE_SUBSET (table_model)) {
+		eti->uses_source_model = 1;
+		eti->source_model = E_TABLE_SUBSET (table_model)->source;
+		if (eti->source_model)
+			g_object_ref (eti->source_model);
+	}
+
+	eti_freeze (eti);
+
+	eti_table_model_changed (table_model, eti);
+}
+
+static void
+eti_add_selection_model (ETableItem *eti,
+                         ESelectionModel *selection)
+{
+	g_return_if_fail (eti->selection == NULL);
+
+	eti->selection = selection;
+	g_object_ref (eti->selection);
+
+	eti->selection_change_id = g_signal_connect (
+		selection, "selection_changed",
+		G_CALLBACK (eti_selection_change), eti);
+
+	eti->selection_row_change_id = g_signal_connect (
+		selection, "selection_row_changed",
+		G_CALLBACK (eti_selection_row_change), eti);
+
+	eti->cursor_change_id = g_signal_connect (
+		selection, "cursor_changed",
+		G_CALLBACK (eti_cursor_change), eti);
+
+	eti->cursor_activated_id = g_signal_connect (
+		selection, "cursor_activated",
+		G_CALLBACK (eti_cursor_activated), eti);
+
+	eti_selection_change (selection, eti);
+	g_signal_emit_by_name (eti, "selection_model_added", eti->selection);
+}
+
+static void
+eti_header_dim_changed (ETableHeader *eth,
+                        gint col,
+                        ETableItem *eti)
+{
+	eti->needs_compute_width = 1;
+	e_canvas_item_request_reflow (GNOME_CANVAS_ITEM (eti));
+	eti->needs_redraw = 1;
+	gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (eti));
+}
+
+static void
+eti_header_structure_changed (ETableHeader *eth,
+                              ETableItem *eti)
+{
+	eti->cols = e_table_header_count (eti->header);
+
+	/*
+	 * There should be at least one column
+	 *  BUT: then you can't remove all columns from a header and add new ones.
+	 */
+
+	if (eti->cell_views) {
+		eti_unrealize_cell_views (eti);
+		eti_detach_cell_views (eti);
+		eti_attach_cell_views (eti);
+		eti_realize_cell_views (eti);
+	} else {
+		if (eti->table_model) {
+			eti_attach_cell_views (eti);
+			eti_realize_cell_views (eti);
+		}
+	}
+	eti->needs_compute_width = 1;
+	e_canvas_item_request_reflow (GNOME_CANVAS_ITEM (eti));
+	eti->needs_redraw = 1;
+	gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (eti));
+}
+
+static gint
+eti_request_column_width (ETableHeader *eth,
+                          gint col,
+                          ETableItem *eti)
+{
+	gint width = 0;
+
+	if (eti->cell_views && eti->cell_views_realized) {
+		width = e_cell_max_width (eti->cell_views[col], view_to_model_col (eti, col), col);
+	}
+
+	return width;
+}
+
+static void
+eti_add_header_model (ETableItem *eti,
+                      ETableHeader *header)
+{
+	g_return_if_fail (eti->header == NULL);
+
+	eti->header = header;
+	g_object_ref (header);
+
+	eti_header_structure_changed (header, eti);
+
+	eti->header_dim_change_id = g_signal_connect (
+		header, "dimension_change",
+		G_CALLBACK (eti_header_dim_changed), eti);
+
+	eti->header_structure_change_id = g_signal_connect (
+		header, "structure_change",
+		G_CALLBACK (eti_header_structure_changed), eti);
+
+	eti->header_request_width_id = g_signal_connect (
+		header, "request_width",
+		G_CALLBACK (eti_request_column_width), eti);
+}
+
+/*
+ * GObject::dispose method
+ */
+static void
+eti_dispose (GObject *object)
+{
+	ETableItem *eti = E_TABLE_ITEM (object);
+
+	eti_remove_header_model (eti);
+	eti_remove_table_model (eti);
+	eti_remove_selection_model (eti);
+
+	if (eti->height_cache_idle_id) {
+		g_source_remove (eti->height_cache_idle_id);
+		eti->height_cache_idle_id = 0;
+	}
+	eti->height_cache_idle_count = 0;
+
+	if (eti->cursor_idle_id) {
+		g_source_remove (eti->cursor_idle_id);
+		eti->cursor_idle_id = 0;
+	}
+
+	if (eti->height_cache)
+		g_free (eti->height_cache);
+	eti->height_cache = NULL;
+
+	/* Chain up to parent's dispose() method. */
+	G_OBJECT_CLASS (eti_parent_class)->dispose (object);
+}
+
+static void
+eti_set_property (GObject *object,
+                  guint property_id,
+                  const GValue *value,
+                  GParamSpec *pspec)
+{
+	GnomeCanvasItem *item = GNOME_CANVAS_ITEM (object);
+	ETableItem *eti = E_TABLE_ITEM (object);
+	gint cursor_col;
+
+	switch (property_id) {
+	case PROP_TABLE_HEADER:
+		eti_remove_header_model (eti);
+		eti_add_header_model (eti, E_TABLE_HEADER (g_value_get_object (value)));
+		break;
+
+	case PROP_TABLE_MODEL:
+		eti_remove_table_model (eti);
+		eti_add_table_model (eti, E_TABLE_MODEL (g_value_get_object (value)));
+		break;
+
+	case PROP_SELECTION_MODEL:
+		g_signal_emit_by_name (
+			eti, "selection_model_removed", eti->selection);
+		eti_remove_selection_model (eti);
+		if (g_value_get_object (value))
+			eti_add_selection_model (eti, E_SELECTION_MODEL (g_value_get_object (value)));
+		break;
+
+	case PROP_LENGTH_THRESHOLD:
+		eti->length_threshold = g_value_get_int (value);
+		break;
+
+	case PROP_TABLE_ALTERNATING_ROW_COLORS:
+		eti->alternating_row_colors = g_value_get_boolean (value);
+		break;
+
+	case PROP_TABLE_HORIZONTAL_DRAW_GRID:
+		eti->horizontal_draw_grid = g_value_get_boolean (value);
+		break;
+
+	case PROP_TABLE_VERTICAL_DRAW_GRID:
+		eti->vertical_draw_grid = g_value_get_boolean (value);
+		break;
+
+	case PROP_TABLE_DRAW_FOCUS:
+		eti->draw_focus = g_value_get_boolean (value);
+		break;
+
+	case PROP_CURSOR_MODE:
+		eti->cursor_mode = g_value_get_int (value);
+		break;
+
+	case PROP_MINIMUM_WIDTH:
+	case PROP_WIDTH:
+		if ((eti->minimum_width == eti->width && g_value_get_double (value) > eti->width) ||
+		    g_value_get_double (value) < eti->width) {
+			eti->needs_compute_width = 1;
+			e_canvas_item_request_reflow (item);
+		}
+		eti->minimum_width = g_value_get_double (value);
+		break;
+	case PROP_CURSOR_ROW:
+		g_object_get (
+			eti->selection,
+			"cursor_col", &cursor_col,
+			NULL);
+
+		e_table_item_focus (eti, cursor_col != -1 ? cursor_col : 0, view_to_model_row (eti, g_value_get_int (value)), 0);
+		break;
+	case PROP_UNIFORM_ROW_HEIGHT:
+		if (eti->uniform_row_height != g_value_get_boolean (value)) {
+			eti->uniform_row_height = g_value_get_boolean (value);
+			if (item->flags & GNOME_CANVAS_ITEM_REALIZED) {
+				free_height_cache (eti);
+				eti->needs_compute_height = 1;
+				e_canvas_item_request_reflow (item);
+				eti->needs_redraw = 1;
+				gnome_canvas_item_request_update (item);
+			}
+		}
+		break;
+	}
+	eti->needs_redraw = 1;
+	gnome_canvas_item_request_update (item);
+}
+
+static void
+eti_get_property (GObject *object,
+                  guint property_id,
+                  GValue *value,
+                  GParamSpec *pspec)
+{
+	ETableItem *eti;
+	gint row;
+
+	eti = E_TABLE_ITEM (object);
+
+	switch (property_id) {
+	case PROP_WIDTH:
+		g_value_set_double (value, eti->width);
+		break;
+	case PROP_HEIGHT:
+		g_value_set_double (value, eti->height);
+		break;
+	case PROP_MINIMUM_WIDTH:
+		g_value_set_double (value, eti->minimum_width);
+		break;
+	case PROP_CURSOR_ROW:
+		g_object_get (
+			eti->selection,
+			"cursor_row", &row,
+			NULL);
+		g_value_set_int (value, model_to_view_row (eti, row));
+		break;
+	case PROP_UNIFORM_ROW_HEIGHT:
+		g_value_set_boolean (value, eti->uniform_row_height);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+		break;
+	}
+}
+
+static void
+eti_init (ETableItem *eti)
+{
+	eti->motion_row	       = -1;
+	eti->motion_col	       = -1;
+	eti->editing_col               = -1;
+	eti->editing_row               = -1;
+	eti->height                    = 0;
+	eti->width                     = 0;
+	eti->minimum_width             = 0;
+
+	eti->save_col                  = -1;
+	eti->save_row                  = -1;
+	eti->save_state                = NULL;
+
+	eti->click_count               = 0;
+
+	eti->height_cache              = NULL;
+	eti->height_cache_idle_id      = 0;
+	eti->height_cache_idle_count   = 0;
+
+	eti->length_threshold          = -1;
+	eti->uniform_row_height        = FALSE;
+
+	eti->uses_source_model         = 0;
+	eti->source_model              = NULL;
+
+	eti->row_guess                 = -1;
+	eti->cursor_mode               = E_CURSOR_SIMPLE;
+
+	eti->selection_change_id       = 0;
+	eti->selection_row_change_id   = 0;
+	eti->cursor_change_id          = 0;
+	eti->cursor_activated_id       = 0;
+	eti->selection                 = NULL;
+
+	eti->old_cursor_row            = -1;
+
+	eti->needs_redraw              = 0;
+	eti->needs_compute_height      = 0;
+
+	eti->in_key_press              = 0;
+
+	eti->maybe_did_something       = TRUE;
+
+	eti->grabbed_count             = 0;
+	eti->gtk_grabbed               = 0;
+
+	eti->in_drag                   = 0;
+	eti->maybe_in_drag             = 0;
+	eti->grabbed                   = 0;
+
+	eti->grabbed_col               = -1;
+	eti->grabbed_row               = -1;
+
+	eti->cursor_on_screen          = FALSE;
+	eti->cursor_x1                 = -1;
+	eti->cursor_y1                 = -1;
+	eti->cursor_x2                 = -1;
+	eti->cursor_y2                 = -1;
+
+	eti->rows                      = -1;
+	eti->cols                      = -1;
+
+	eti->frozen_count              = 0;
+	eti->queue_show_cursor         = FALSE;
+
+	e_canvas_item_set_reflow_callback (GNOME_CANVAS_ITEM (eti), eti_reflow);
+}
+
+#define gray50_width 2
+#define gray50_height 2
+static const gchar gray50_bits[] = {
+	0x02, 0x01, };
+
+static gboolean
+eti_tree_unfreeze (GtkWidget *widget,
+                   GdkEvent *event,
+                   ETableItem *eti)
+{
+
+	if (widget)
+		g_object_set_data (G_OBJECT (widget), "freeze-cursor", NULL);
+
+	return FALSE;
+}
+
+static void
+eti_realize (GnomeCanvasItem *item)
+{
+	ETableItem *eti = E_TABLE_ITEM (item);
+
+	if (GNOME_CANVAS_ITEM_CLASS (eti_parent_class)->realize)
+		(*GNOME_CANVAS_ITEM_CLASS (eti_parent_class)->realize)(item);
+
+	eti->rows = e_table_model_row_count (eti->table_model);
+
+	g_signal_connect (
+		item->canvas, "scroll_event",
+		G_CALLBACK (eti_tree_unfreeze), eti);
+
+	if (eti->cell_views == NULL)
+		eti_attach_cell_views (eti);
+
+	eti_realize_cell_views (eti);
+
+	free_height_cache (eti);
+
+	if (item->canvas->focused_item == NULL && eti->selection) {
+		gint row;
+
+		row = e_selection_model_cursor_row (E_SELECTION_MODEL (eti->selection));
+		row = model_to_view_row (eti, row);
+		if (row != -1) {
+			e_canvas_item_grab_focus (item, FALSE);
+			eti_show_cursor (eti, 0);
+			eti_check_cursor_bounds (eti);
+		}
+	}
+
+	eti->needs_compute_height = 1;
+	eti->needs_compute_width = 1;
+	e_canvas_item_request_reflow (GNOME_CANVAS_ITEM (eti));
+	eti->needs_redraw = 1;
+	gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (eti));
+}
+
+static void
+eti_unrealize (GnomeCanvasItem *item)
+{
+	ETableItem *eti = E_TABLE_ITEM (item);
+
+	if (eti->grabbed_count > 0) {
+		d (g_print ("%s: eti_ungrab\n", __FUNCTION__));
+		eti_ungrab (eti, -1);
+	}
+
+	if (eti_editing (eti))
+		e_table_item_leave_edit_(eti);
+
+	if (eti->height_cache_idle_id) {
+		g_source_remove (eti->height_cache_idle_id);
+		eti->height_cache_idle_id = 0;
+	}
+
+	if (eti->height_cache)
+		g_free (eti->height_cache);
+	eti->height_cache = NULL;
+	eti->height_cache_idle_count = 0;
+
+	eti_unrealize_cell_views (eti);
+
+	eti->height = 0;
+
+	if (GNOME_CANVAS_ITEM_CLASS (eti_parent_class)->unrealize)
+		(*GNOME_CANVAS_ITEM_CLASS (eti_parent_class)->unrealize)(item);
+}
+
+static void
+eti_draw_grid_line (ETableItem *eti,
+                    cairo_t *cr,
+                    GtkStyle *style,
+                    gint x1,
+                    gint y1,
+                    gint x2,
+                    gint y2)
+{
+	cairo_save (cr);
+
+	cairo_set_line_width (cr, 1.0);
+	gdk_cairo_set_source_color (cr, &style->dark[GTK_STATE_NORMAL]);
+
+	cairo_move_to (cr, x1 + 0.5, y1 + 0.5);
+	cairo_line_to (cr, x2 + 0.5, y2 + 0.5);
+	cairo_stroke (cr);
+
+	cairo_restore (cr);
+}
+
+static void
+eti_draw (GnomeCanvasItem *item,
+          cairo_t *cr,
+          gint x,
+          gint y,
+          gint width,
+          gint height)
+{
+	ETableItem *eti = E_TABLE_ITEM (item);
+	const gint rows = eti->rows;
+	const gint cols = eti->cols;
+	gint row, col;
+	gint first_col, last_col, x_offset;
+	gint first_row, last_row, y_offset, yd;
+	gint x1, x2;
+	gint f_x1, f_x2, f_y1, f_y2;
+	gboolean f_found;
+	cairo_matrix_t i2c;
+	gdouble eti_base_x, eti_base_y, lower_right_y, lower_right_x;
+	GtkWidget *canvas = GTK_WIDGET (item->canvas);
+	GtkStyle *style = gtk_widget_get_style (canvas);
+	gint height_extra = eti->horizontal_draw_grid ? 1 : 0;
+
+	/*
+	 * Find out our real position after grouping
+	 */
+	gnome_canvas_item_i2c_matrix (item, &i2c);
+	eti_base_x = 0;
+	eti_base_y = 0;
+	cairo_matrix_transform_point (&i2c, &eti_base_x, &eti_base_y);
+
+	lower_right_x = eti->width;
+	lower_right_y = eti->height;
+	cairo_matrix_transform_point (&i2c, &lower_right_x, &lower_right_y);
+
+	/*
+	 * First column to draw, last column to draw
+	 */
+	first_col = -1;
+	x_offset = 0;
+	x1 = floor (eti_base_x);
+	for (col = 0; col < cols; col++, x1 = x2) {
+		ETableCol *ecol = e_table_header_get_column (eti->header, col);
+
+		x2 = x1 + ecol->width;
+
+		if (x1 > (x + width))
+			break;
+		if (x2 < x)
+			continue;
+		if (first_col == -1) {
+			x_offset = x1 - x;
+			first_col = col;
+		}
+	}
+	last_col = col;
+
+	/*
+	 * Nothing to paint
+	 */
+	if (first_col == -1)
+		return;
+
+	/*
+	 * Compute row span.
+	 */
+	if (eti->uniform_row_height) {
+		first_row = (y          - floor (eti_base_y) - height_extra) / (ETI_ROW_HEIGHT (eti, -1) + height_extra);
+		last_row  = (y + height - floor (eti_base_y)               ) / (ETI_ROW_HEIGHT (eti, -1) + height_extra) + 1;
+		if (first_row > last_row)
+			return;
+		y_offset = floor (eti_base_y) - y + height_extra + first_row * (ETI_ROW_HEIGHT (eti, -1) + height_extra);
+		if (first_row < 0)
+			first_row = 0;
+		if (last_row > eti->rows)
+			last_row = eti->rows;
+	} else {
+		gint y1, y2;
+
+		y_offset = 0;
+		first_row = -1;
+
+		y1 = y2 = floor (eti_base_y) + height_extra;
+		for (row = 0; row < rows; row++, y1 = y2) {
+
+			y2 += ETI_ROW_HEIGHT (eti, row) + height_extra;
+
+			if (y1 > y + height)
+				break;
+
+			if (y2 < y)
+				continue;
+
+			if (first_row == -1) {
+				y_offset = y1 - y;
+				first_row = row;
+			}
+		}
+		last_row = row;
+
+		if (first_row == -1)
+			return;
+	}
+
+	if (first_row == -1)
+		return;
+
+	/*
+	 * Draw cells
+	 */
+	yd = y_offset;
+	f_x1 = f_x2 = f_y1 = f_y2 = -1;
+	f_found = FALSE;
+
+	if (eti->horizontal_draw_grid && first_row == 0)
+		eti_draw_grid_line (eti, cr, style, eti_base_x - x, yd, eti_base_x + eti->width - x, yd);
+
+	yd += height_extra;
+
+	for (row = first_row; row < last_row; row++) {
+		gint xd;
+		gboolean selected;
+		gint cursor_col, cursor_row;
+
+		height = ETI_ROW_HEIGHT (eti, row);
+
+		xd = x_offset;
+
+		selected = e_selection_model_is_row_selected (E_SELECTION_MODEL (eti->selection), view_to_model_row (eti,row));
+
+		g_object_get (
+			eti->selection,
+			"cursor_col", &cursor_col,
+			"cursor_row", &cursor_row,
+			NULL);
+
+		for (col = first_col; col < last_col; col++) {
+			ETableCol *ecol = e_table_header_get_column (eti->header, col);
+			ECellView *ecell_view = eti->cell_views[col];
+			gboolean col_selected = selected;
+			gboolean cursor = FALSE;
+			ECellFlags flags;
+			gboolean free_background;
+			GdkColor *background;
+			gint x1, x2, y1, y2;
+			cairo_pattern_t *pat;
+
+			switch (eti->cursor_mode) {
+			case E_CURSOR_SIMPLE:
+			case E_CURSOR_SPREADSHEET:
+				if (cursor_col == ecol->col_idx && cursor_row == view_to_model_row (eti, row)) {
+					col_selected = !col_selected;
+					cursor = TRUE;
+				}
+				break;
+			case E_CURSOR_LINE:
+				/* Nothing */
+				break;
+			}
+
+			x1 = xd;
+			y1 = yd + 1;
+			x2 = x1 + ecol->width;
+			y2 = yd + height;
+
+			background = eti_get_cell_background_color (eti, row, col, col_selected, &free_background);
+
+			cairo_save (cr);
+			pat = cairo_pattern_create_linear (0, y1, 0, y2);
+			cairo_pattern_add_color_stop_rgba (
+				pat, 0.0, background->red / 65535.0 ,
+				background->green / 65535.0,
+				background->blue / 65535.0, selected ? 0.8: 1.0);
+			if (selected)
+				cairo_pattern_add_color_stop_rgba (
+					pat, 0.5, background->red / 65535.0 ,
+					background->green / 65535.0,
+					background->blue / 65535.0, 0.9);
+
+			cairo_pattern_add_color_stop_rgba (
+				pat, 1, background->red / 65535.0 ,
+				background->green / 65535.0,
+				background->blue / 65535.0, selected ? 0.8 : 1.0);
+			cairo_rectangle (cr, x1, y1, ecol->width, height - 1);
+			cairo_set_source (cr, pat);
+			cairo_fill_preserve (cr);
+			cairo_pattern_destroy (pat);
+			cairo_set_line_width (cr, 0);
+			cairo_stroke (cr);
+			cairo_restore (cr);
+
+			cairo_save (cr);
+			cairo_set_line_width (cr, 1.0);
+			cairo_set_source_rgba (
+				cr, background->red / 65535.0 ,
+				background->green / 65535.0,
+				background->blue / 65535.0, 1);
+			cairo_move_to (cr, x1, y1);
+			cairo_line_to (cr, x2, y1);
+			cairo_stroke (cr);
+
+			cairo_set_line_width (cr, 1.0);
+			cairo_set_source_rgba (
+				cr, background->red / 65535.0 ,
+				background->green / 65535.0,
+				background->blue / 65535.0, 1);
+			cairo_move_to (cr, x1, y2);
+			cairo_line_to (cr, x2, y2);
+			cairo_stroke (cr);
+			cairo_restore (cr);
+
+			if (free_background)
+				gdk_color_free (background);
+
+			flags = col_selected ? E_CELL_SELECTED : 0;
+			flags |= gtk_widget_has_focus (canvas) ? E_CELL_FOCUSED : 0;
+			flags |= cursor ? E_CELL_CURSOR : 0;
+
+			switch (ecol->justification) {
+			case GTK_JUSTIFY_LEFT:
+				flags |= E_CELL_JUSTIFY_LEFT;
+				break;
+			case GTK_JUSTIFY_RIGHT:
+				flags |= E_CELL_JUSTIFY_RIGHT;
+				break;
+			case GTK_JUSTIFY_CENTER:
+				flags |= E_CELL_JUSTIFY_CENTER;
+				break;
+			case GTK_JUSTIFY_FILL:
+				flags |= E_CELL_JUSTIFY_FILL;
+				break;
+			}
+
+			e_cell_draw (
+				ecell_view, cr, ecol->col_idx, col, row, flags,
+				xd, yd, xd + ecol->width, yd + height);
+
+			if (!f_found && !selected) {
+				switch (eti->cursor_mode) {
+				case E_CURSOR_LINE:
+					if (view_to_model_row (eti, row) == cursor_row) {
+						f_x1 = floor (eti_base_x) - x;
+						f_x2 = floor (lower_right_x) - x;
+						f_y1 = yd + 1;
+						f_y2 = yd + height;
+						f_found = TRUE;
+					}
+					break;
+				case E_CURSOR_SIMPLE:
+				case E_CURSOR_SPREADSHEET:
+					if (view_to_model_col (eti, col) == cursor_col && view_to_model_row (eti, row) == cursor_row) {
+						f_x1 = xd;
+						f_x2 = xd + ecol->width;
+						f_y1 = yd;
+						f_y2 = yd + height;
+						f_found = TRUE;
+					}
+					break;
+				}
+			}
+
+			xd += ecol->width;
+		}
+		yd += height;
+
+		if (eti->horizontal_draw_grid) {
+			eti_draw_grid_line (eti, cr, style, eti_base_x - x, yd, eti_base_x + eti->width - x, yd);
+			yd++;
+		}
+	}
+
+	if (eti->vertical_draw_grid) {
+		gint xd = x_offset;
+
+		for (col = first_col; col <= last_col; col++) {
+			ETableCol *ecol = e_table_header_get_column (eti->header, col);
+
+			eti_draw_grid_line (eti, cr, style, xd, y_offset, xd, yd - 1);
+
+			/*
+			 * This looks wierd, but it is to draw the last line
+			 */
+			if (ecol)
+				xd += ecol->width;
+		}
+	}
+
+	/*
+	 * Draw focus
+	 */
+	if (eti->draw_focus && f_found) {
+		static const double dash[] = { 1.0, 1.0 };
+		cairo_set_line_width (cr, 1.0);
+		cairo_rectangle (
+			cr,
+			f_x1 + 0.5, f_x2 + 0.5,
+			f_x2 - f_x1 - 1, f_y2 - f_y1);
+
+		gdk_cairo_set_source_color (cr, &style->bg[GTK_STATE_NORMAL]);
+		cairo_stroke_preserve (cr);
+
+		cairo_set_dash (cr, dash, G_N_ELEMENTS (dash), 0.0);
+		gdk_cairo_set_source_color (cr, &style->fg[GTK_STATE_NORMAL]);
+		cairo_stroke (cr);
+	}
+}
+
+static GnomeCanvasItem *
+eti_point (GnomeCanvasItem *item,
+           gdouble x,
+           gdouble y,
+           gint cx,
+           gint cy)
+{
+	return item;
+}
+
+static gboolean
+find_cell (ETableItem *eti,
+           gdouble x,
+           gdouble y,
+           gint *view_col_res,
+           gint *view_row_res,
+           gdouble *x1_res,
+           gdouble *y1_res)
+{
+	const gint cols = eti->cols;
+	const gint rows = eti->rows;
+	gdouble x1, y1, x2, y2;
+	gint col, row;
+
+	gint height_extra = eti->horizontal_draw_grid ? 1 : 0;
+
+	/* FIXME: this routine is inneficient, fix later */
+
+	if (eti->grabbed_col >= 0 && eti->grabbed_row >= 0) {
+		*view_col_res = eti->grabbed_col;
+		*view_row_res = eti->grabbed_row;
+		*x1_res = x - e_table_header_col_diff (eti->header, 0, eti->grabbed_col);
+		*y1_res = y - e_table_item_row_diff (eti, 0, eti->grabbed_row);
+		return TRUE;
+	}
+
+	if (cols == 0 || rows == 0)
+		return FALSE;
+
+	x1 = 0;
+	for (col = 0; col < cols - 1; col++, x1 = x2) {
+		ETableCol *ecol = e_table_header_get_column (eti->header, col);
+
+		if (x < x1)
+			return FALSE;
+
+		x2 = x1 + ecol->width;
+
+		if (x <= x2)
+			break;
+	}
+
+	if (eti->uniform_row_height) {
+		if (y < height_extra)
+			return FALSE;
+		row = (y - height_extra) / (ETI_ROW_HEIGHT (eti, -1) + height_extra);
+		y1 = row * (ETI_ROW_HEIGHT (eti, -1) + height_extra) + height_extra;
+		if (row >= eti->rows)
+			return FALSE;
+	} else {
+		y1 = y2 = height_extra;
+		if (y < height_extra)
+			return FALSE;
+		for (row = 0; row < rows; row++, y1 = y2) {
+			y2 += ETI_ROW_HEIGHT (eti, row) + height_extra;
+
+			if (y <= y2)
+				break;
+		}
+
+		if (row == rows)
+			return FALSE;
+	}
+	*view_col_res = col;
+	if (x1_res)
+		*x1_res = x - x1;
+	*view_row_res = row;
+	if (y1_res)
+		*y1_res = y - y1;
+	return TRUE;
+}
+
+static void
+eti_cursor_move (ETableItem *eti,
+                 gint row,
+                 gint column)
+{
+	e_table_item_leave_edit_(eti);
+	e_table_item_focus (eti, view_to_model_col (eti, column), view_to_model_row (eti, row), 0);
+}
+
+static void
+eti_cursor_move_left (ETableItem *eti)
+{
+	gint cursor_col, cursor_row;
+	g_object_get (
+		eti->selection,
+		"cursor_col", &cursor_col,
+		"cursor_row", &cursor_row,
+		NULL);
+
+	eti_cursor_move (eti, model_to_view_row (eti, cursor_row), model_to_view_col (eti, cursor_col) - 1);
+}
+
+static void
+eti_cursor_move_right (ETableItem *eti)
+{
+	gint cursor_col, cursor_row;
+	g_object_get (
+		eti->selection,
+		"cursor_col", &cursor_col,
+		"cursor_row", &cursor_row,
+		NULL);
+
+	eti_cursor_move (eti, model_to_view_row (eti, cursor_row), model_to_view_col (eti, cursor_col) + 1);
+}
+
+static gint
+eti_e_cell_event (ETableItem *item,
+                  ECellView *ecell_view,
+                  GdkEvent *event,
+                  gint model_col,
+                  gint view_col,
+                  gint row,
+                  ECellFlags flags)
+{
+	ECellActions actions = 0;
+	gint ret_val;
+
+	ret_val = e_cell_event (
+		ecell_view, event, model_col, view_col, row, flags, &actions);
+
+	if (actions & E_CELL_GRAB) {
+		GdkDevice *event_device;
+		guint32 event_time;
+
+		d (g_print ("%s: eti_grab\n", __FUNCTION__));
+
+		event_device = gdk_event_get_device (event);
+		event_time = gdk_event_get_time (event);
+		eti_grab (item, event_device, event_time);
+
+		item->grabbed_col = view_col;
+		item->grabbed_row = row;
+	}
+
+	if (actions & E_CELL_UNGRAB) {
+		guint32 event_time;
+
+		d (g_print ("%s: eti_ungrab\n", __FUNCTION__));
+
+		event_time = gdk_event_get_time (event);
+		eti_ungrab (item, event_time);
+
+		item->grabbed_col = -1;
+		item->grabbed_row = -1;
+	}
+
+	return ret_val;
+}
+
+/* FIXME: cursor */
+static gint
+eti_event (GnomeCanvasItem *item,
+           GdkEvent *event)
+{
+	ETableItem *eti = E_TABLE_ITEM (item);
+	ECellView *ecell_view;
+	GdkModifierType event_state = 0;
+	GdkEvent *event_copy;
+	guint event_button = 0;
+	guint event_keyval = 0;
+	gdouble event_x_item = 0;
+	gdouble event_y_item = 0;
+	gdouble event_x_win = 0;
+	gdouble event_y_win = 0;
+	guint32 event_time;
+	gboolean return_val = TRUE;
+#if d(!)0
+	gboolean leave = FALSE;
+#endif
+
+	if (!eti->header)
+		return FALSE;
+
+	/* Don't fetch the device here.  GnomeCanvas frequently emits
+	 * synthesized events, and calling gdk_event_get_device() on them
+	 * will trigger a runtime warning.  Fetch the device where needed. */
+	gdk_event_get_button (event, &event_button);
+	gdk_event_get_coords (event, &event_x_win, &event_y_win);
+	gdk_event_get_keyval (event, &event_keyval);
+	gdk_event_get_state (event, &event_state);
+	event_time = gdk_event_get_time (event);
+
+	switch (event->type) {
+	case GDK_BUTTON_PRESS: {
+		gdouble x1, y1;
+		gint col, row;
+		gint cursor_row, cursor_col;
+		gint new_cursor_row, new_cursor_col;
+		ECellFlags flags = 0;
+
+		d (g_print ("%s: GDK_BUTTON_PRESS received, button %d\n", __FUNCTION__, event_button));
+
+		switch (event_button) {
+		case 1: /* Fall through. */
+		case 2:
+			e_canvas_item_grab_focus (GNOME_CANVAS_ITEM (eti), TRUE);
+
+			event_x_item = event_x_win;
+			event_y_item = event_y_win;
+
+			gnome_canvas_item_w2i (
+				item, &event_x_item, &event_y_item);
+
+			if (!find_cell (eti, event_x_item, event_y_item, &col, &row, &x1, &y1)) {
+				if (eti_editing (eti))
+					e_table_item_leave_edit_(eti);
+				return TRUE;
+			}
+
+			ecell_view = eti->cell_views[col];
+
+			/* Clone the event and alter its position. */
+			event_copy = gdk_event_copy (event);
+			event_copy->button.x = x1;
+			event_copy->button.y = y1;
+
+			g_object_get (
+				eti->selection,
+				"cursor_row", &cursor_row,
+				"cursor_col", &cursor_col,
+				NULL);
+
+			if (cursor_col == view_to_model_col (eti, col) && cursor_row == view_to_model_row (eti, row)) {
+				flags = E_CELL_CURSOR;
+			} else {
+				flags = 0;
+			}
+
+			return_val = eti_e_cell_event (
+				eti, ecell_view, event_copy,
+				view_to_model_col (eti, col),
+				col, row, flags);
+			if (return_val) {
+				gdk_event_free (event_copy);
+				return TRUE;
+			}
+
+			g_signal_emit (
+				eti, eti_signals[CLICK], 0,
+				row, view_to_model_col (eti, col),
+				event_copy, &return_val);
+
+			gdk_event_free (event_copy);
+
+			if (return_val) {
+				eti->click_count = 0;
+				return TRUE;
+			}
+
+			g_object_get (
+				eti->selection,
+				"cursor_row", &cursor_row,
+				"cursor_col", &cursor_col,
+				NULL);
+
+			eti->maybe_did_something =
+				e_selection_model_maybe_do_something (
+				E_SELECTION_MODEL (eti->selection),
+				view_to_model_row (eti, row),
+				view_to_model_col (eti, col),
+				event_state);
+			g_object_get (
+				eti->selection,
+				"cursor_row", &new_cursor_row,
+				"cursor_col", &new_cursor_col,
+				NULL);
+
+			if (cursor_row != new_cursor_row || cursor_col != new_cursor_col) {
+				eti->click_count = 1;
+			} else {
+				eti->click_count++;
+				eti->row_guess = row;
+
+				if ((!eti_editing (eti)) && e_table_model_is_cell_editable (eti->table_model, cursor_col, row)) {
+					e_table_item_enter_edit (eti, col, row);
+				}
+
+				/*
+				 * Adjust the event positions
+				 */
+
+				if (eti_editing (eti)) {
+					return_val = eti_e_cell_event (
+						eti, ecell_view, event,
+						view_to_model_col (eti, col),
+						col, row,
+						E_CELL_EDITING |
+						E_CELL_CURSOR);
+					if (return_val)
+						return TRUE;
+				}
+			}
+
+			if (event_button == 1) {
+				GdkDevice *event_device;
+
+				return_val = TRUE;
+
+				event_device = gdk_event_get_device (event);
+
+				eti->maybe_in_drag = TRUE;
+				eti->drag_row      = new_cursor_row;
+				eti->drag_col      = new_cursor_col;
+				eti->drag_x        = event_x_item;
+				eti->drag_y        = event_y_item;
+				eti->drag_state    = event_state;
+				eti->grabbed       = TRUE;
+				d (g_print ("%s: eti_grab\n", __FUNCTION__));
+				eti_grab (eti, event_device, event_time);
+			}
+
+			break;
+		case 3:
+			e_canvas_item_grab_focus (GNOME_CANVAS_ITEM (eti), TRUE);
+
+			event_x_item = event_x_win;
+			event_y_item = event_y_win;
+
+			gnome_canvas_item_w2i (
+				item, &event_x_item, &event_y_item);
+
+			if (!find_cell (eti, event_x_item, event_y_item, &col, &row, &x1, &y1))
+				return TRUE;
+
+			e_selection_model_right_click_down (
+				E_SELECTION_MODEL (eti->selection),
+				view_to_model_row (eti, row),
+				view_to_model_col (eti, col), 0);
+
+			/* Clone the event and alter its position. */
+			event_copy = gdk_event_copy (event);
+			event_copy->button.x = event_x_item;
+			event_copy->button.y = event_y_item;
+
+			g_signal_emit (
+				eti, eti_signals[RIGHT_CLICK], 0,
+				row, view_to_model_col (eti, col),
+				event, &return_val);
+
+			gdk_event_free (event_copy);
+
+			if (!return_val)
+				e_selection_model_right_click_up (E_SELECTION_MODEL (eti->selection));
+			break;
+		case 4:
+		case 5:
+			return FALSE;
+
+		}
+		break;
+	}
+
+	case GDK_BUTTON_RELEASE: {
+		gdouble x1, y1;
+		gint col, row;
+		gint cursor_row, cursor_col;
+
+		d (g_print ("%s: GDK_BUTTON_RELEASE received, button %d\n", __FUNCTION__, event_button));
+
+		if (eti->grabbed_count > 0) {
+			d (g_print ("%s: eti_ungrab\n", __FUNCTION__));
+			eti_ungrab (eti, event_time);
+		}
+
+		if (event_button == 1) {
+			if (eti->maybe_in_drag) {
+				eti->maybe_in_drag = FALSE;
+				if (!eti->maybe_did_something)
+					e_selection_model_do_something (E_SELECTION_MODEL (eti->selection), eti->drag_row, eti->drag_col, eti->drag_state);
+			}
+			if (eti->in_drag) {
+				eti->in_drag = FALSE;
+			}
+		}
+
+		switch (event_button) {
+		case 1: /* Fall through. */
+		case 2:
+
+			event_x_item = event_x_win;
+			event_y_item = event_y_win;
+
+			gnome_canvas_item_w2i (
+				item, &event_x_item, &event_y_item);
+#if d(!)0
+			{
+				gboolean cell_found = find_cell (
+					eti, event_x_item, event_y_item,
+					&col, &row, &x1, &y1);
+				g_print (
+					"%s: find_cell(%f, %f) = %s(%d, %d, %f, %f)\n",
+					__FUNCTION__, event_x_item, event_y_item,
+					cell_found?"true":"false", col, row, x1, y1);
+			}
+#endif
+
+			if (!find_cell (eti, event_x_item, event_y_item, &col, &row, &x1, &y1))
+				return TRUE;
+
+			g_object_get (
+				eti->selection,
+				"cursor_row", &cursor_row,
+				"cursor_col", &cursor_col,
+				NULL);
+
+			if (eti_editing (eti) && cursor_row == view_to_model_row (eti, row) && cursor_col == view_to_model_col (eti, col)) {
+
+				d (g_print ("%s: GDK_BUTTON_RELEASE received, button %d, line: %d\n", __FUNCTION__, event_button, __LINE__))
+;
+
+				ecell_view = eti->cell_views[col];
+
+				/* Clone the event and alter its position. */
+				event_copy = gdk_event_copy (event);
+				event_copy->button.x = x1;
+				event_copy->button.y = y1;
+
+				return_val = eti_e_cell_event (
+					eti, ecell_view, event_copy,
+					view_to_model_col (eti, col),
+					col, row,
+					E_CELL_EDITING |
+					E_CELL_CURSOR);
+
+				gdk_event_free (event_copy);
+			}
+			break;
+		case 3:
+			e_selection_model_right_click_up (E_SELECTION_MODEL (eti->selection));
+			return_val = TRUE;
+			break;
+		case 4:
+		case 5:
+			return FALSE;
+
+		}
+		break;
+	}
+
+	case GDK_2BUTTON_PRESS: {
+		gint model_col, model_row;
+#if 0
+		gdouble x1, y1;
+#endif
+
+		d (g_print ("%s: GDK_2BUTTON_PRESS received, button %d\n", __FUNCTION__, event_button));
+
+		/*
+		 * click_count is so that if you click on two
+		 * different rows we don't send a double click signal.
+		 */
+
+		if (eti->click_count >= 2) {
+
+			event_x_item = event_x_win;
+			event_y_item = event_y_win;
+
+			gnome_canvas_item_w2i (
+				item, &event_x_item, &event_y_item);
+
+			g_object_get (
+				eti->selection,
+				"cursor_row", &model_row,
+				"cursor_col", &model_col,
+				NULL);
+
+			/* Clone the event and alter its position. */
+			event_copy = gdk_event_copy (event);
+			event_copy->button.x = event_x_item -
+				e_table_header_col_diff (
+					eti->header, 0,
+					model_to_view_col (eti, model_col));
+			event_copy->button.y = event_y_item -
+				e_table_item_row_diff (
+					eti, 0,
+					model_to_view_row (eti, model_row));
+
+			if (event_button == 1) {
+				if (eti->maybe_in_drag) {
+					eti->maybe_in_drag = FALSE;
+					if (!eti->maybe_did_something)
+						e_selection_model_do_something (E_SELECTION_MODEL (eti->selection), eti->drag_row, eti->drag_col, eti->drag_state);
+				}
+				if (eti->in_drag) {
+					eti->in_drag = FALSE;
+				}
+				if (eti_editing (eti))
+					e_table_item_leave_edit_ (eti);
+
+			}
+
+			if (eti->grabbed_count > 0) {
+				d (g_print ("%s: eti_ungrab\n", __FUNCTION__));
+				eti_ungrab (eti, event_time);
+			}
+
+			if (model_row != -1 && model_col != -1) {
+				g_signal_emit (
+					eti, eti_signals[DOUBLE_CLICK], 0,
+					model_row, model_col, event_copy);
+			}
+
+			gdk_event_free (event_copy);
+		}
+		break;
+	}
+	case GDK_MOTION_NOTIFY: {
+		gint col, row, flags;
+		gdouble x1, y1;
+		gint cursor_col, cursor_row;
+
+		event_x_item = event_x_win;
+		event_y_item = event_y_win;
+
+		gnome_canvas_item_w2i (item, &event_x_item, &event_y_item);
+
+		if (eti->maybe_in_drag) {
+			if (abs (event_x_item - eti->drag_x) >= 3 ||
+			    abs (event_y_item - eti->drag_y) >= 3) {
+				gboolean drag_handled;
+
+				eti->maybe_in_drag = 0;
+
+				/* Clone the event and
+				 * alter its position. */
+				event_copy = gdk_event_copy (event);
+				event_copy->motion.x = event_x_item;
+				event_copy->motion.y = event_y_item;
+
+				g_signal_emit (
+					eti, eti_signals[START_DRAG], 0,
+					eti->drag_row, eti->drag_col,
+					event_copy, &drag_handled);
+
+				gdk_event_free (event_copy);
+
+				if (drag_handled)
+					eti->in_drag = 1;
+				else
+					eti->in_drag = 0;
+			}
+		}
+
+		if (!find_cell (eti, event_x_item, event_y_item, &col, &row, &x1, &y1))
+			return TRUE;
+
+		if (eti->motion_row != -1 && eti->motion_col != -1 &&
+		    (row != eti->motion_row || col != eti->motion_col)) {
+			GdkEvent *cross = gdk_event_new (GDK_LEAVE_NOTIFY);
+			cross->crossing.time = event_time;
+			return_val = eti_e_cell_event (
+				eti, eti->cell_views[eti->motion_col],
+				cross,
+				view_to_model_col (eti, eti->motion_col),
+				eti->motion_col, eti->motion_row, 0);
+		}
+
+		eti->motion_row = row;
+		eti->motion_col = col;
+
+		g_object_get (
+			eti->selection,
+			"cursor_row", &cursor_row,
+			"cursor_col", &cursor_col,
+			NULL);
+
+		flags = 0;
+		if (cursor_row == view_to_model_row (eti, row) && cursor_col == view_to_model_col (eti, col)) {
+			flags = E_CELL_EDITING | E_CELL_CURSOR;
+		}
+
+		ecell_view = eti->cell_views[col];
+
+		/* Clone the event and alter its position. */
+		event_copy = gdk_event_copy (event);
+		event_copy->motion.x = x1;
+		event_copy->motion.y = y1;
+
+		return_val = eti_e_cell_event (
+			eti, ecell_view, event_copy,
+			view_to_model_col (eti, col), col, row, flags);
+
+		gdk_event_free (event_copy);
+
+		break;
+	}
+
+	case GDK_KEY_PRESS: {
+		gint cursor_row, cursor_col;
+		gint handled = TRUE;
+
+		d (g_print ("%s: GDK_KEY_PRESS received, keyval: %d\n", __FUNCTION__, (gint) e->key.keyval));
+
+		g_object_get (
+			eti->selection,
+			"cursor_row", &cursor_row,
+			"cursor_col", &cursor_col,
+			NULL);
+
+		if (cursor_row == -1 && cursor_col == -1)
+			return FALSE;
+
+		eti->in_key_press = TRUE;
+
+		switch (event_keyval) {
+		case GDK_KEY_Left:
+		case GDK_KEY_KP_Left:
+			if (eti_editing (eti)) {
+				handled = FALSE;
+				break;
+			}
+
+			g_signal_emit (
+				eti, eti_signals[KEY_PRESS], 0,
+				model_to_view_row (eti, cursor_row),
+				cursor_col, event, &return_val);
+			if ((!return_val) &&
+			   (atk_get_root () || eti->cursor_mode != E_CURSOR_LINE) &&
+			   cursor_col != view_to_model_col (eti, 0))
+				eti_cursor_move_left (eti);
+			return_val = 1;
+			break;
+
+		case GDK_KEY_Right:
+		case GDK_KEY_KP_Right:
+			if (eti_editing (eti)) {
+				handled = FALSE;
+				break;
+			}
+
+			g_signal_emit (
+				eti, eti_signals[KEY_PRESS], 0,
+				model_to_view_row (eti, cursor_row),
+				cursor_col, event, &return_val);
+			if ((!return_val) &&
+			   (atk_get_root () || eti->cursor_mode != E_CURSOR_LINE) &&
+			   cursor_col != view_to_model_col (eti, eti->cols - 1))
+				eti_cursor_move_right (eti);
+			return_val = 1;
+			break;
+
+		case GDK_KEY_Up:
+		case GDK_KEY_KP_Up:
+		case GDK_KEY_Down:
+		case GDK_KEY_KP_Down:
+			if ((event_state & GDK_MOD1_MASK)
+			    && ((event_keyval == GDK_KEY_Down) || (event_keyval == GDK_KEY_KP_Down))) {
+				gint view_col = model_to_view_col (eti, cursor_col);
+
+				if ((view_col >= 0) && (view_col < eti->cols))
+					if (eti_e_cell_event (eti, eti->cell_views[view_col], event, cursor_col, view_col, model_to_view_row (eti, cursor_row),  E_CELL_CURSOR))
+						return TRUE;
+			} else
+			return_val = e_selection_model_key_press (E_SELECTION_MODEL (eti->selection), (GdkEventKey *) event);
+			break;
+		case GDK_KEY_Home:
+		case GDK_KEY_KP_Home:
+			if (eti_editing (eti)) {
+				handled = FALSE;
+				break;
+			}
+
+			if (eti->cursor_mode != E_CURSOR_LINE) {
+				eti_cursor_move (eti, model_to_view_row (eti, cursor_row), 0);
+				return_val = TRUE;
+			} else
+				return_val = e_selection_model_key_press (E_SELECTION_MODEL (eti->selection), (GdkEventKey *) event);
+			break;
+		case GDK_KEY_End:
+		case GDK_KEY_KP_End:
+			if (eti_editing (eti)) {
+				handled = FALSE;
+				break;
+			}
+
+			if (eti->cursor_mode != E_CURSOR_LINE) {
+				eti_cursor_move (eti, model_to_view_row (eti, cursor_row), eti->cols - 1);
+				return_val = TRUE;
+			} else
+				return_val = e_selection_model_key_press (E_SELECTION_MODEL (eti->selection), (GdkEventKey *) event);
+			break;
+		case GDK_KEY_Tab:
+		case GDK_KEY_KP_Tab:
+		case GDK_KEY_ISO_Left_Tab:
+			if ((event_state & GDK_CONTROL_MASK) != 0) {
+				return_val = FALSE;
+				break;
+			}
+			if (eti->cursor_mode == E_CURSOR_SPREADSHEET) {
+				if ((event_state & GDK_SHIFT_MASK) != 0) {
+				/* shift tab */
+					if (cursor_col != view_to_model_col (eti, 0))
+						eti_cursor_move_left (eti);
+					else if (cursor_row != view_to_model_row (eti, 0))
+						eti_cursor_move (eti, model_to_view_row (eti, cursor_row) - 1, eti->cols - 1);
+					else
+						return_val = FALSE;
+				} else {
+					if (cursor_col != view_to_model_col (eti, eti->cols - 1))
+						eti_cursor_move_right (eti);
+					else if (cursor_row != view_to_model_row (eti, eti->rows - 1))
+						eti_cursor_move (eti, model_to_view_row (eti, cursor_row) + 1, 0);
+					else
+						return_val = FALSE;
+				}
+				g_object_get (
+					eti->selection,
+					"cursor_row", &cursor_row,
+					"cursor_col", &cursor_col,
+					NULL);
+
+				if (cursor_col >= 0 && cursor_row >= 0 && return_val &&
+				    (!eti_editing (eti)) && e_table_model_is_cell_editable (eti->table_model, cursor_col, model_to_view_row (eti, cursor_row))) {
+					e_table_item_enter_edit (eti, model_to_view_col (eti, cursor_col), model_to_view_row (eti, cursor_row));
+				}
+				break;
+			} else {
+			/* Let tab send you to the next widget. */
+			return_val = FALSE;
+			break;
+			}
+
+		case GDK_KEY_Return:
+		case GDK_KEY_KP_Enter:
+		case GDK_KEY_ISO_Enter:
+		case GDK_KEY_3270_Enter:
+			if (eti_editing (eti)) {
+				ecell_view = eti->cell_views[eti->editing_col];
+				return_val = eti_e_cell_event (
+					eti, ecell_view, event,
+					view_to_model_col (eti, eti->editing_col),
+					eti->editing_col, eti->editing_row, E_CELL_EDITING | E_CELL_CURSOR | E_CELL_PREEDIT);
+				if (!return_val)
+					break;
+			}
+			g_signal_emit (
+				eti, eti_signals[KEY_PRESS], 0,
+				model_to_view_row (eti, cursor_row),
+				cursor_col, event, &return_val);
+			if (!return_val)
+				return_val = e_selection_model_key_press (E_SELECTION_MODEL (eti->selection), (GdkEventKey *) event);
+			break;
+
+		default:
+			handled = FALSE;
+			break;
+		}
+
+		if (!handled) {
+			switch (event_keyval) {
+			case GDK_KEY_Scroll_Lock:
+			case GDK_KEY_Sys_Req:
+			case GDK_KEY_Shift_L:
+			case GDK_KEY_Shift_R:
+			case GDK_KEY_Control_L:
+			case GDK_KEY_Control_R:
+			case GDK_KEY_Caps_Lock:
+			case GDK_KEY_Shift_Lock:
+			case GDK_KEY_Meta_L:
+			case GDK_KEY_Meta_R:
+			case GDK_KEY_Alt_L:
+			case GDK_KEY_Alt_R:
+			case GDK_KEY_Super_L:
+			case GDK_KEY_Super_R:
+			case GDK_KEY_Hyper_L:
+			case GDK_KEY_Hyper_R:
+			case GDK_KEY_ISO_Lock:
+				break;
+
+			default:
+				if (!eti_editing (eti)) {
+					gint col, row;
+					row = model_to_view_row (eti, cursor_row);
+					col = model_to_view_col (eti, cursor_col);
+					if (col != -1 && row != -1 && e_table_model_is_cell_editable (eti->table_model, cursor_col, row)) {
+						e_table_item_enter_edit (eti, col, row);
+					}
+				}
+				if (!eti_editing (eti)) {
+					g_signal_emit (
+						eti, eti_signals[KEY_PRESS], 0,
+						model_to_view_row (eti, cursor_row),
+						cursor_col, event, &return_val);
+					if (!return_val)
+						e_selection_model_key_press (E_SELECTION_MODEL (eti->selection), (GdkEventKey *) event);
+				} else {
+					ecell_view = eti->cell_views[eti->editing_col];
+					return_val = eti_e_cell_event (
+						eti, ecell_view, event,
+						view_to_model_col (eti, eti->editing_col),
+						eti->editing_col, eti->editing_row, E_CELL_EDITING | E_CELL_CURSOR);
+					if (!return_val)
+						e_selection_model_key_press (E_SELECTION_MODEL (eti->selection), (GdkEventKey *) event);
+				}
+				break;
+			}
+		}
+		eti->in_key_press = FALSE;
+		break;
+	}
+
+	case GDK_KEY_RELEASE: {
+		gint cursor_row, cursor_col;
+
+		d (g_print ("%s: GDK_KEY_RELEASE received, keyval: %d\n", __FUNCTION__, (gint) event_keyval));
+
+		g_object_get (
+			eti->selection,
+			"cursor_row", &cursor_row,
+			"cursor_col", &cursor_col,
+			NULL);
+
+		if (cursor_col == -1)
+			return FALSE;
+
+		if (eti_editing (eti)) {
+			ecell_view = eti->cell_views[eti->editing_col];
+			return_val = eti_e_cell_event (
+				eti, ecell_view, event,
+				view_to_model_col (eti, eti->editing_col),
+				eti->editing_col, eti->editing_row, E_CELL_EDITING | E_CELL_CURSOR);
+		}
+		break;
+	}
+
+	case GDK_LEAVE_NOTIFY:
+		d (leave = TRUE);
+	case GDK_ENTER_NOTIFY:
+		d (g_print ("%s: %s received\n", __FUNCTION__, leave ? "GDK_LEAVE_NOTIFY" : "GDK_ENTER_NOTIFY"));
+		if (eti->motion_row != -1 && eti->motion_col != -1)
+			return_val = eti_e_cell_event (
+				eti, eti->cell_views[eti->motion_col],
+				event,
+				view_to_model_col (eti, eti->motion_col),
+				eti->motion_col, eti->motion_row, 0);
+		eti->motion_row = -1;
+		eti->motion_col = -1;
+
+		break;
+
+	case GDK_FOCUS_CHANGE:
+		d (g_print ("%s: GDK_FOCUS_CHANGE received, %s\n", __FUNCTION__, e->focus_change.in ? "in": "out"));
+		if (event->focus_change.in) {
+			if (eti->save_row != -1 &&
+			    eti->save_col != -1 &&
+			    !eti_editing (eti) &&
+			    e_table_model_is_cell_editable (eti->table_model, view_to_model_col (eti, eti->save_col), eti->save_row)) {
+				e_table_item_enter_edit (eti, eti->save_col, eti->save_row);
+				e_cell_load_state (
+					eti->cell_views[eti->editing_col], view_to_model_col (eti, eti->save_col),
+					eti->save_col, eti->save_row, eti->edit_ctx, eti->save_state);
+				eti_free_save_state (eti);
+			}
+		} else {
+			if (eti_editing (eti)) {
+				eti_free_save_state (eti);
+
+				eti->save_row   = eti->editing_row;
+				eti->save_col   = eti->editing_col;
+				eti->save_state = e_cell_save_state (
+					eti->cell_views[eti->editing_col], view_to_model_col (eti, eti->editing_col),
+					eti->editing_col, eti->editing_row, eti->edit_ctx);
+				e_table_item_leave_edit_(eti);
+			}
+		}
+
+	default:
+		return_val = FALSE;
+	}
+	/* d(g_print("%s: returning: %s\n", __FUNCTION__, return_val?"true":"false"));*/
+
+	return return_val;
+}
+
+static void
+eti_style_set (ETableItem *eti,
+               GtkStyle *previous_style)
+{
+	GnomeCanvasItem *item = GNOME_CANVAS_ITEM (eti);
+
+	if (!(item->flags & GNOME_CANVAS_ITEM_REALIZED))
+		return;
+
+	if (eti->cell_views_realized) {
+		gint i;
+		gint n_cells = eti->n_cells;
+
+		for (i = 0; i < n_cells; i++) {
+			e_cell_style_set (eti->cell_views[i], previous_style);
+		}
+	}
+
+	eti->needs_compute_height = 1;
+	e_canvas_item_request_reflow (GNOME_CANVAS_ITEM (eti));
+	eti->needs_redraw = 1;
+	gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (eti));
+
+	free_height_cache (eti);
+
+	eti_idle_maybe_show_cursor (eti);
+}
+
+static void
+eti_class_init (ETableItemClass *class)
+{
+	GnomeCanvasItemClass *item_class = GNOME_CANVAS_ITEM_CLASS (class);
+	GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+	object_class->dispose       = eti_dispose;
+	object_class->set_property  = eti_set_property;
+	object_class->get_property  = eti_get_property;
+
+	item_class->update          = eti_update;
+	item_class->realize         = eti_realize;
+	item_class->unrealize       = eti_unrealize;
+	item_class->draw            = eti_draw;
+	item_class->point           = eti_point;
+	item_class->event           = eti_event;
+
+	class->cursor_change    = NULL;
+	class->cursor_activated = NULL;
+	class->double_click     = NULL;
+	class->right_click      = NULL;
+	class->click            = NULL;
+	class->key_press        = NULL;
+	class->start_drag       = NULL;
+	class->style_set        = eti_style_set;
+	class->selection_model_removed = NULL;
+	class->selection_model_added = NULL;
+
+	g_object_class_install_property (
+		object_class,
+		PROP_TABLE_HEADER,
+		g_param_spec_object (
+			"ETableHeader",
+			"Table header",
+			"Table header",
+			E_TYPE_TABLE_HEADER,
+			G_PARAM_WRITABLE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_TABLE_MODEL,
+		g_param_spec_object (
+			"ETableModel",
+			"Table model",
+			"Table model",
+			E_TYPE_TABLE_MODEL,
+			G_PARAM_WRITABLE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_SELECTION_MODEL,
+		g_param_spec_object (
+			"selection_model",
+			"Selection model",
+			"Selection model",
+			E_TYPE_SELECTION_MODEL,
+			G_PARAM_WRITABLE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_TABLE_ALTERNATING_ROW_COLORS,
+		g_param_spec_boolean (
+			"alternating_row_colors",
+			"Alternating Row Colors",
+			"Alternating Row Colors",
+			FALSE,
+			G_PARAM_WRITABLE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_TABLE_HORIZONTAL_DRAW_GRID,
+		g_param_spec_boolean (
+			"horizontal_draw_grid",
+			"Horizontal Draw Grid",
+			"Horizontal Draw Grid",
+			FALSE,
+			G_PARAM_WRITABLE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_TABLE_VERTICAL_DRAW_GRID,
+		g_param_spec_boolean (
+			"vertical_draw_grid",
+			"Vertical Draw Grid",
+			"Vertical Draw Grid",
+			FALSE,
+			G_PARAM_WRITABLE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_TABLE_DRAW_FOCUS,
+		g_param_spec_boolean (
+			"drawfocus",
+			"Draw focus",
+			"Draw focus",
+			FALSE,
+			G_PARAM_WRITABLE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_CURSOR_MODE,
+		g_param_spec_int (
+			"cursor_mode",
+			"Cursor mode",
+			"Cursor mode",
+			E_CURSOR_LINE,
+			E_CURSOR_SPREADSHEET,
+			E_CURSOR_LINE,
+			G_PARAM_WRITABLE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_LENGTH_THRESHOLD,
+		g_param_spec_int (
+			"length_threshold",
+			"Length Threshold",
+			"Length Threshold",
+			-1, G_MAXINT, 0,
+			G_PARAM_WRITABLE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_MINIMUM_WIDTH,
+		g_param_spec_double (
+			"minimum_width",
+			"Minimum width",
+			"Minimum Width",
+			0.0, G_MAXDOUBLE, 0.0,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_WIDTH,
+		g_param_spec_double (
+			"width",
+			"Width",
+			"Width",
+			0.0, G_MAXDOUBLE, 0.0,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_HEIGHT,
+		g_param_spec_double (
+			"height",
+			"Height",
+			"Height",
+			0.0, G_MAXDOUBLE, 0.0,
+			G_PARAM_READABLE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_CURSOR_ROW,
+		g_param_spec_int (
+			"cursor_row",
+			"Cursor row",
+			"Cursor row",
+			0, G_MAXINT, 0,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_UNIFORM_ROW_HEIGHT,
+		g_param_spec_boolean (
+			"uniform_row_height",
+			"Uniform row height",
+			"Uniform row height",
+			FALSE,
+			G_PARAM_READWRITE));
+
+	eti_signals[CURSOR_CHANGE] = g_signal_new (
+		"cursor_change",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ETableItemClass, cursor_change),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__INT,
+		G_TYPE_NONE, 1,
+		G_TYPE_INT);
+
+	eti_signals[CURSOR_ACTIVATED] = g_signal_new (
+		"cursor_activated",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ETableItemClass, cursor_activated),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__INT,
+		G_TYPE_NONE, 1,
+		G_TYPE_INT);
+
+	eti_signals[DOUBLE_CLICK] = g_signal_new (
+		"double_click",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ETableItemClass, double_click),
+		NULL, NULL,
+		e_marshal_NONE__INT_INT_BOXED,
+		G_TYPE_NONE, 3,
+		G_TYPE_INT,
+		G_TYPE_INT,
+		GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
+
+	eti_signals[START_DRAG] = g_signal_new (
+		"start_drag",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ETableItemClass, start_drag),
+		g_signal_accumulator_true_handled, NULL,
+		e_marshal_BOOLEAN__INT_INT_BOXED,
+		G_TYPE_BOOLEAN, 3,
+		G_TYPE_INT,
+		G_TYPE_INT,
+		GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
+
+	eti_signals[RIGHT_CLICK] = g_signal_new (
+		"right_click",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ETableItemClass, right_click),
+		g_signal_accumulator_true_handled, NULL,
+		e_marshal_BOOLEAN__INT_INT_BOXED,
+		G_TYPE_BOOLEAN, 3,
+		G_TYPE_INT,
+		G_TYPE_INT,
+		GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
+
+	eti_signals[CLICK] = g_signal_new (
+		"click",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ETableItemClass, click),
+		g_signal_accumulator_true_handled, NULL,
+		e_marshal_BOOLEAN__INT_INT_BOXED,
+		G_TYPE_BOOLEAN, 3,
+		G_TYPE_INT,
+		G_TYPE_INT,
+		GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
+
+	eti_signals[KEY_PRESS] = g_signal_new (
+		"key_press",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ETableItemClass, key_press),
+		g_signal_accumulator_true_handled, NULL,
+		e_marshal_BOOLEAN__INT_INT_BOXED,
+		G_TYPE_BOOLEAN, 3,
+		G_TYPE_INT,
+		G_TYPE_INT,
+		GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
+
+	eti_signals[STYLE_SET] = g_signal_new (
+		"style_set",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ETableItemClass, style_set),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__OBJECT,
+		G_TYPE_NONE, 1,
+		GTK_TYPE_STYLE);
+
+	eti_signals[SELECTION_MODEL_REMOVED] = g_signal_new (
+		"selection_model_removed",
+		G_TYPE_FROM_CLASS (object_class),
+		G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
+		G_STRUCT_OFFSET (ETableItemClass, selection_model_removed),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__POINTER,
+		G_TYPE_NONE, 1,
+		G_TYPE_POINTER);
+
+	eti_signals[SELECTION_MODEL_ADDED] = g_signal_new (
+		"selection_model_added",
+		G_TYPE_FROM_CLASS (object_class),
+		G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
+		G_STRUCT_OFFSET (ETableItemClass, selection_model_added),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__POINTER,
+		G_TYPE_NONE, 1,
+		G_TYPE_POINTER);
+
+	/* A11y Init */
+	gal_a11y_e_table_item_init ();
+}
+
+/**
+ * e_table_item_set_cursor:
+ * @eti: %ETableItem which will have the cursor set.
+ * @col: Column to select.  -1 means the last column.
+ * @row: Row to select.  -1 means the last row.
+ *
+ * This routine sets the cursor of the %ETableItem canvas item.
+ */
+void
+e_table_item_set_cursor (ETableItem *eti,
+                         gint col,
+                         gint row)
+{
+	e_table_item_focus (eti, col, view_to_model_row (eti, row), 0);
+}
+
+static void
+e_table_item_focus (ETableItem *eti,
+                    gint col,
+                    gint row,
+                    GdkModifierType state)
+{
+	g_return_if_fail (eti != NULL);
+	g_return_if_fail (E_IS_TABLE_ITEM (eti));
+
+	if (row == -1) {
+		row = view_to_model_row (eti, eti->rows - 1);
+	}
+
+	if (col == -1) {
+		col = eti->cols - 1;
+	}
+
+	if (row != -1) {
+		e_selection_model_do_something (
+			E_SELECTION_MODEL (eti->selection),
+			row, col, state);
+	}
+}
+
+/**
+ * e_table_item_get_focused_column:
+ * @eti: %ETableItem which will have the cursor retrieved.
+ *
+ * This routine gets the cursor of the %ETableItem canvas item.
+ *
+ * Returns: The current cursor column.
+ */
+gint
+e_table_item_get_focused_column (ETableItem *eti)
+{
+	gint cursor_col;
+
+	g_return_val_if_fail (eti != NULL, -1);
+	g_return_val_if_fail (E_IS_TABLE_ITEM (eti), -1);
+
+	g_object_get (
+		eti->selection,
+		"cursor_col", &cursor_col,
+		NULL);
+
+	return cursor_col;
+}
+
+static void
+eti_cursor_change (ESelectionModel *selection,
+                   gint row,
+                   gint col,
+                   ETableItem *eti)
+{
+	GnomeCanvasItem *item = GNOME_CANVAS_ITEM (eti);
+	gint view_row;
+
+	if (!(item->flags & GNOME_CANVAS_ITEM_REALIZED))
+		return;
+
+	view_row = model_to_view_row (eti, row);
+
+	if (eti->old_cursor_row != -1 && view_row != eti->old_cursor_row)
+		e_table_item_redraw_row (eti, eti->old_cursor_row);
+
+	if (view_row == -1) {
+		e_table_item_leave_edit_(eti);
+		eti->old_cursor_row = -1;
+		return;
+	}
+
+	if (!e_table_model_has_change_pending (eti->table_model)) {
+		if (!eti->in_key_press) {
+			eti_maybe_show_cursor (eti, DOUBLE_CLICK_TIME + 10);
+		} else {
+			eti_maybe_show_cursor (eti, 0);
+		}
+	}
+
+	e_canvas_item_grab_focus (GNOME_CANVAS_ITEM (eti), FALSE);
+	if (eti_editing (eti))
+		e_table_item_leave_edit_(eti);
+
+	g_signal_emit (eti, eti_signals[CURSOR_CHANGE], 0, view_row);
+
+	e_table_item_redraw_row (eti, view_row);
+
+	eti->old_cursor_row = view_row;
+}
+
+static void
+eti_cursor_activated (ESelectionModel *selection,
+                      gint row,
+                      gint col,
+                      ETableItem *eti)
+{
+	GnomeCanvasItem *item = GNOME_CANVAS_ITEM (eti);
+	gint view_row;
+	gint view_col;
+
+	if (!(item->flags & GNOME_CANVAS_ITEM_REALIZED))
+		return;
+
+	view_row = model_to_view_row (eti, row);
+	view_col = model_to_view_col (eti, col);
+
+	if (view_row != -1 && view_col != -1) {
+		if (!e_table_model_has_change_pending (eti->table_model)) {
+			if (!eti->in_key_press) {
+				eti_show_cursor (eti, DOUBLE_CLICK_TIME + 10);
+			} else {
+				eti_show_cursor (eti, 0);
+			}
+			eti_check_cursor_bounds (eti);
+		}
+	}
+
+	if (eti_editing (eti))
+		e_table_item_leave_edit_(eti);
+
+	if (view_row != -1)
+		g_signal_emit (
+			eti, eti_signals[CURSOR_ACTIVATED], 0, view_row);
+}
+
+static void
+eti_selection_change (ESelectionModel *selection,
+                      ETableItem *eti)
+{
+	GnomeCanvasItem *item = GNOME_CANVAS_ITEM (eti);
+
+	if (!(item->flags & GNOME_CANVAS_ITEM_REALIZED))
+		return;
+
+	eti->needs_redraw = TRUE;
+	gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (eti));
+}
+
+static void
+eti_selection_row_change (ESelectionModel *selection,
+                          gint row,
+                          ETableItem *eti)
+{
+	GnomeCanvasItem *item = GNOME_CANVAS_ITEM (eti);
+
+	if (!(item->flags & GNOME_CANVAS_ITEM_REALIZED))
+		return;
+
+	if (!eti->needs_redraw) {
+		e_table_item_redraw_row (eti, model_to_view_row (eti, row));
+	}
+}
+
+/**
+ * e_table_item_enter_edit
+ * @eti: %ETableItem which will start being edited
+ * @col: The view col to edit.
+ * @row: The view row to edit.
+ *
+ * This routine starts the given %ETableItem editing at the given view
+ * column and row.
+ */
+void
+e_table_item_enter_edit (ETableItem *eti,
+                         gint col,
+                         gint row)
+{
+	g_return_if_fail (eti != NULL);
+	g_return_if_fail (E_IS_TABLE_ITEM (eti));
+
+	d (g_print ("%s: %d, %d, eti_editing() = %s\n", __FUNCTION__, col, row, eti_editing (eti)?"true":"false"));
+
+	if (eti_editing (eti))
+		e_table_item_leave_edit_(eti);
+
+	eti->editing_col = col;
+	eti->editing_row = row;
+
+	eti->edit_ctx = e_cell_enter_edit (eti->cell_views[col], view_to_model_col (eti, col), col, row);
+}
+
+/**
+ * e_table_item_leave_edit_
+ * @eti: %ETableItem which will stop being edited
+ *
+ * This routine stops the given %ETableItem from editing.
+ */
+void
+e_table_item_leave_edit (ETableItem *eti)
+{
+	gint col, row;
+	gpointer edit_ctx;
+
+	g_return_if_fail (eti != NULL);
+	g_return_if_fail (E_IS_TABLE_ITEM (eti));
+
+	d (g_print ("%s: eti_editing() = %s\n", __FUNCTION__, eti_editing (eti)?"true":"false"));
+
+	if (!eti_editing (eti))
+		return;
+
+	col = eti->editing_col;
+	row = eti->editing_row;
+	edit_ctx = eti->edit_ctx;
+
+	eti->editing_col = -1;
+	eti->editing_row = -1;
+	eti->edit_ctx = NULL;
+
+	e_cell_leave_edit (
+		eti->cell_views[col],
+		view_to_model_col (eti, col),
+		col, row, edit_ctx);
+}
+
+/**
+ * e_table_item_compute_location
+ * @eti: %ETableItem to look in.
+ * @x: A pointer to the x location to find in the %ETableItem.
+ * @y: A pointer to the y location to find in the %ETableItem.
+ * @row: A pointer to the location to store the found row in.
+ * @col: A pointer to the location to store the found col in.
+ *
+ * This routine locates the pixel location (*x, *y) in the
+ * %ETableItem.  If that location is in the %ETableItem, *row and *col
+ * are set to the view row and column where it was found.  If that
+ * location is not in the %ETableItem, the height of the %ETableItem
+ * is removed from the value y points to.
+ */
+void
+e_table_item_compute_location (ETableItem *eti,
+                               gint *x,
+                               gint *y,
+                               gint *row,
+                               gint *col)
+{
+	/* Save the grabbed row but make sure that we don't get flawed
+	 * results because the cursor is grabbed. */
+	gint grabbed_row = eti->grabbed_row;
+	eti->grabbed_row = -1;
+
+	if (!find_cell (eti, *x, *y, col, row, NULL, NULL)) {
+		*y -= eti->height;
+	}
+
+	eti->grabbed_row = grabbed_row;
+}
+
+/**
+ * e_table_item_compute_mouse_over:
+ * Similar to e_table_item_compute_location, only here recalculating
+ * the position inside the item too.
+ **/
+void
+e_table_item_compute_mouse_over (ETableItem *eti,
+                                 gint x,
+                                 gint y,
+                                 gint *row,
+                                 gint *col)
+{
+	gdouble realx, realy;
+	/* Save the grabbed row but make sure that we don't get flawed
+	 * results because the cursor is grabbed. */
+	gint grabbed_row = eti->grabbed_row;
+	eti->grabbed_row = -1;
+
+	realx = x;
+	realy = y;
+
+	gnome_canvas_item_w2i (GNOME_CANVAS_ITEM (eti), &realx, &realy);
+
+	if (!find_cell (eti, (gint) realx, (gint) realy, col, row, NULL, NULL)) {
+		*row = -1;
+		*col = -1;
+	}
+
+	eti->grabbed_row = grabbed_row;
+}
+
+void
+e_table_item_get_cell_geometry (ETableItem *eti,
+                                gint *row,
+                                gint *col,
+                                gint *x,
+                                gint *y,
+                                gint *width,
+                                gint *height)
+{
+	if (eti->rows > *row) {
+		if (x)
+			*x = e_table_header_col_diff (eti->header, 0, *col);
+		if (y)
+			*y = e_table_item_row_diff (eti, 0, *row);
+		if (width)
+			*width = e_table_header_col_diff (eti->header, *col, *col + 1);
+		if (height)
+			*height = ETI_ROW_HEIGHT (eti, *row);
+		*row = -1;
+		*col = -1;
+	} else {
+		*row -= eti->rows;
+	}
+}
+
+typedef struct {
+	ETableItem *item;
+	gint rows_printed;
+} ETableItemPrintContext;
+
+static gdouble *
+e_table_item_calculate_print_widths (ETableHeader *eth,
+                                     gdouble width)
+{
+	gint i;
+	gdouble extra;
+	gdouble expansion;
+	gint last_resizable = -1;
+	gdouble scale = 1.0L;
+	gdouble *widths = g_new (gdouble, e_table_header_count (eth));
+	/* - 1 to account for the last pixel border. */
+	extra = width - 1;
+	expansion = 0;
+	for (i = 0; i < eth->col_count; i++) {
+		extra -= eth->columns[i]->min_width * scale;
+		if (eth->columns[i]->resizable && eth->columns[i]->expansion > 0)
+			last_resizable = i;
+		expansion += eth->columns[i]->resizable ? eth->columns[i]->expansion : 0;
+		widths[i] = eth->columns[i]->min_width * scale;
+	}
+	for (i = 0; i <= last_resizable; i++) {
+		widths[i] += extra * (eth->columns[i]->resizable ? eth->columns[i]->expansion : 0) / expansion;
+	}
+
+	return widths;
+}
+
+static gdouble
+eti_printed_row_height (ETableItem *eti,
+                        gdouble *widths,
+                        GtkPrintContext *context,
+                        gint row)
+{
+	gint col;
+	gint cols = eti->cols;
+	gdouble height = 0;
+	for (col = 0; col < cols; col++) {
+		ECellView *ecell_view = eti->cell_views[col];
+		gdouble this_height = e_cell_print_height (
+			ecell_view, context, view_to_model_col (eti, col), col, row,
+			widths[col] - 1);
+		if (this_height > height)
+			height = this_height;
+	}
+	return height;
+}
+
+#define CHECK(x) if((x) == -1) return -1;
+
+static gint
+gp_draw_rect (GtkPrintContext *context,
+              gdouble x,
+              gdouble y,
+              gdouble width,
+              gdouble height)
+{
+	cairo_t *cr;
+	cr = gtk_print_context_get_cairo_context (context);
+	cairo_save (cr);
+	cairo_rectangle (cr, x, y, width, height);
+	cairo_set_line_width (cr, 0.5);
+	cairo_stroke (cr);
+	cairo_restore (cr);
+	return 0;
+}
+
+static void
+e_table_item_print_page (EPrintable *ep,
+                         GtkPrintContext *context,
+                         gdouble width,
+                         gdouble height,
+                         gboolean quantize,
+                         ETableItemPrintContext *itemcontext)
+{
+	ETableItem *eti = itemcontext->item;
+	const gint rows = eti->rows;
+	const gint cols = eti->cols;
+	gdouble max_height;
+	gint rows_printed = itemcontext->rows_printed;
+	gint row, col, next_page = 0;
+	gdouble yd = height;
+	cairo_t *cr;
+	gdouble *widths;
+
+	cr = gtk_print_context_get_cairo_context (context);
+	max_height = gtk_print_context_get_height (context);
+	widths = e_table_item_calculate_print_widths (itemcontext->item->header, width);
+
+	/*
+	 * Draw cells
+	 */
+
+	if (eti->horizontal_draw_grid) {
+		gp_draw_rect (context, 0, yd, width, 1);
+	}
+	yd++;
+
+	for (row = rows_printed; row < rows; row++) {
+		gdouble xd = 1, row_height;
+		row_height = eti_printed_row_height (eti, widths, context, row);
+
+		if (quantize) {
+			if (yd + row_height + 1 > max_height && row != rows_printed) {
+				next_page = 1;
+				break;
+			}
+		} else {
+			if (yd > max_height) {
+				next_page = 1;
+				break;
+			}
+		}
+
+		for (col = 0; col < cols; col++) {
+			ECellView *ecell_view = eti->cell_views[col];
+
+			cairo_save (cr);
+			cairo_translate (cr, xd, yd);
+			cairo_rectangle (cr, 0, 0, widths[col] - 1, row_height);
+			cairo_clip (cr);
+
+			e_cell_print (
+				ecell_view, context,
+				view_to_model_col (eti, col),
+				col,
+				row,
+				widths[col] - 1,
+				row_height + 2);
+
+			cairo_restore (cr);
+
+			xd += widths[col];
+		}
+
+	yd += row_height;
+		if (eti->horizontal_draw_grid) {
+			gp_draw_rect (context, 0, yd, width, 1);
+		}
+		yd++;
+	}
+
+	itemcontext->rows_printed = row;
+	if (eti->vertical_draw_grid) {
+		gdouble xd = 0;
+		for (col = 0; col < cols; col++) {
+			gp_draw_rect (context, xd, height, 1, yd - height);
+			xd += widths[col];
+		}
+		gp_draw_rect (context, xd, height, 1, yd - height);
+	}
+
+	if (next_page)
+		cairo_show_page (cr);
+
+	g_free (widths);
+}
+
+static gboolean
+e_table_item_data_left (EPrintable *ep,
+                        ETableItemPrintContext *itemcontext)
+{
+	ETableItem *item = itemcontext->item;
+	gint rows_printed = itemcontext->rows_printed;
+
+	g_signal_stop_emission_by_name (ep, "data_left");
+	return rows_printed < item->rows;
+}
+
+static void
+e_table_item_reset (EPrintable *ep,
+                    ETableItemPrintContext *itemcontext)
+{
+	itemcontext->rows_printed = 0;
+}
+
+static gdouble
+e_table_item_height (EPrintable *ep,
+                     GtkPrintContext *context,
+                     gdouble width,
+                     gdouble max_height,
+                     gboolean quantize,
+                     ETableItemPrintContext *itemcontext)
+{
+	ETableItem *item = itemcontext->item;
+	const gint rows = item->rows;
+	gint rows_printed = itemcontext->rows_printed;
+	gdouble *widths;
+	gint row;
+	gdouble yd = 0;
+
+	widths = e_table_item_calculate_print_widths (itemcontext->item->header, width);
+
+	/*
+	 * Draw cells
+	 */
+	yd++;
+
+	for (row = rows_printed; row < rows; row++) {
+		gdouble row_height;
+
+		row_height = eti_printed_row_height (item, widths, context, row);
+		if (quantize) {
+			if (max_height != -1 && yd + row_height + 1 > max_height && row != rows_printed) {
+				break;
+			}
+		} else {
+			if (max_height != -1 && yd > max_height) {
+				break;
+			}
+		}
+
+		yd += row_height;
+
+		yd++;
+	}
+
+	g_free (widths);
+
+	if (max_height != -1 && (!quantize) && yd > max_height)
+		yd = max_height;
+
+	g_signal_stop_emission_by_name (ep, "height");
+	return yd;
+}
+
+static gboolean
+e_table_item_will_fit (EPrintable *ep,
+                       GtkPrintContext *context,
+                       gdouble width,
+                       gdouble max_height,
+                       gboolean quantize,
+                       ETableItemPrintContext *itemcontext)
+{
+	ETableItem *item = itemcontext->item;
+	const gint rows = item->rows;
+	gint rows_printed = itemcontext->rows_printed;
+	gdouble *widths;
+	gint row;
+	gdouble yd = 0;
+	gboolean ret_val = TRUE;
+
+	widths = e_table_item_calculate_print_widths (itemcontext->item->header, width);
+
+	/*
+	 * Draw cells
+	 */
+	yd++;
+
+	for (row = rows_printed; row < rows; row++) {
+		gdouble row_height;
+
+		row_height = eti_printed_row_height (item, widths, context, row);
+		if (quantize) {
+			if (max_height != -1 && yd + row_height + 1 > max_height && row != rows_printed) {
+				ret_val = FALSE;
+				break;
+			}
+		} else {
+			if (max_height != -1 && yd > max_height) {
+				ret_val = FALSE;
+				break;
+			}
+		}
+
+		yd += row_height;
+
+		yd++;
+	}
+
+	g_free (widths);
+
+	g_signal_stop_emission_by_name (ep, "will_fit");
+	return ret_val;
+}
+
+static void
+e_table_item_printable_destroy (gpointer data,
+                                GObject *where_object_was)
+{
+	ETableItemPrintContext *itemcontext = data;
+
+	g_object_unref (itemcontext->item);
+	g_free (itemcontext);
+}
+
+/**
+ * e_table_item_get_printable
+ * @eti: %ETableItem which will be printed
+ *
+ * This routine creates and returns an %EPrintable that can be used to
+ * print the given %ETableItem.
+ *
+ * Returns: The %EPrintable.
+ */
+EPrintable *
+e_table_item_get_printable (ETableItem *item)
+{
+	EPrintable *printable = e_printable_new ();
+	ETableItemPrintContext *itemcontext;
+
+	itemcontext = g_new (ETableItemPrintContext, 1);
+	itemcontext->item = item;
+	g_object_ref (item);
+	itemcontext->rows_printed = 0;
+
+	g_signal_connect (
+		printable, "print_page",
+		G_CALLBACK (e_table_item_print_page), itemcontext);
+	g_signal_connect (
+		printable, "data_left",
+		G_CALLBACK (e_table_item_data_left), itemcontext);
+	g_signal_connect (
+		printable, "reset",
+		G_CALLBACK (e_table_item_reset), itemcontext);
+	g_signal_connect (
+		printable, "height",
+		G_CALLBACK (e_table_item_height), itemcontext);
+	g_signal_connect (
+		printable, "will_fit",
+		G_CALLBACK (e_table_item_will_fit), itemcontext);
+
+	g_object_weak_ref (
+		G_OBJECT (printable),
+		e_table_item_printable_destroy, itemcontext);
+
+	return printable;
+}
diff --git a/e-util/e-table-item.h b/e-util/e-table-item.h
new file mode 100644
index 0000000..09fdab9
--- /dev/null
+++ b/e-util/e-table-item.h
@@ -0,0 +1,261 @@
+/*
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *		Miguel de Icaza <miguel gnu org>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef _E_TABLE_ITEM_H_
+#define _E_TABLE_ITEM_H_
+
+#include <libgnomecanvas/libgnomecanvas.h>
+
+#include <e-util/e-printable.h>
+#include <e-util/e-selection-model.h>
+#include <e-util/e-table-defines.h>
+#include <e-util/e-table-header.h>
+#include <e-util/e-table-model.h>
+
+/* Standard GObject macros */
+#define E_TYPE_TABLE_ITEM \
+	(e_table_item_get_type ())
+#define E_TABLE_ITEM(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_TABLE_ITEM, ETableItem))
+#define E_TABLE_ITEM_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_TABLE_ITEM, ETableItemClass))
+#define E_IS_TABLE_ITEM(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_TABLE_ITEM))
+#define E_IS_TABLE_ITEM_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_TABLE_ITEM))
+#define E_TABLE_ITEM_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_TABLE_ITEM, ETableItemClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ETableItem ETableItem;
+typedef struct _ETableItemClass ETableItemClass;
+
+struct _ETableItem {
+	GnomeCanvasItem  parent;
+	ETableModel     *table_model;
+	ETableHeader    *header;
+
+	ETableModel     *source_model;
+	ESelectionModel *selection;
+
+	gint              minimum_width, width, height;
+
+	gint              cols, rows;
+
+	gint              click_count;
+
+	/*
+	 * Ids for the signals we connect to
+	 */
+	gint              header_dim_change_id;
+	gint              header_structure_change_id;
+	gint              header_request_width_id;
+	gint              table_model_pre_change_id;
+	gint              table_model_no_change_id;
+	gint              table_model_change_id;
+	gint              table_model_row_change_id;
+	gint              table_model_cell_change_id;
+	gint              table_model_rows_inserted_id;
+	gint              table_model_rows_deleted_id;
+
+	gint              selection_change_id;
+	gint              selection_row_change_id;
+	gint              cursor_change_id;
+	gint              cursor_activated_id;
+
+	guint            cursor_idle_id;
+
+	/* View row, -1 means unknown */
+	gint              old_cursor_row;
+
+	guint		 alternating_row_colors : 1;
+	guint		 horizontal_draw_grid : 1;
+	guint		 vertical_draw_grid : 1;
+	guint		 draw_focus : 1;
+	guint		 uniform_row_height : 1;
+	guint		 cell_views_realized : 1;
+
+	guint		 needs_redraw : 1;
+	guint		 needs_compute_height : 1;
+	guint		 needs_compute_width : 1;
+
+	guint            uses_source_model : 1;
+
+	guint            in_key_press : 1;
+
+	guint            maybe_in_drag : 1;
+	guint            in_drag : 1;
+	guint            grabbed : 1;
+
+	guint            maybe_did_something : 1;
+
+	guint            cursor_on_screen : 1;
+	guint            gtk_grabbed : 1;
+
+	guint            queue_show_cursor : 1;
+	guint            grab_cancelled : 1;
+
+	gint              frozen_count;
+
+	gint              cursor_x1;
+	gint              cursor_y1;
+	gint              cursor_x2;
+	gint              cursor_y2;
+
+	gint		 drag_col;
+	gint		 drag_row;
+	gint		 drag_x;
+	gint		 drag_y;
+	guint            drag_state;
+
+	/*
+	 * Realized views, per column
+	 */
+	ECellView      **cell_views;
+	gint              n_cells;
+
+	gint             *height_cache;
+	gint              uniform_row_height_cache;
+	gint              height_cache_idle_id;
+	gint              height_cache_idle_count;
+
+	/*
+	 * Lengh Threshold: above this, we stop computing correctly
+	 * the size
+	 */
+	gint              length_threshold;
+
+	gint             row_guess;
+	ECursorMode      cursor_mode;
+
+	gint              motion_col, motion_row;
+
+	/*
+	 * During editing
+	 */
+	gint              editing_col, editing_row;
+	void            *edit_ctx;
+
+	gint              save_col, save_row;
+	void            *save_state;
+
+	gint grabbed_col, grabbed_row;
+	gint grabbed_count;
+};
+
+struct _ETableItemClass {
+	GnomeCanvasItemClass parent_class;
+
+	void		(*cursor_change)	(ETableItem *eti,
+						 gint row);
+	void		(*cursor_activated)	(ETableItem *eti,
+						 gint row);
+	void		(*double_click)		(ETableItem *eti,
+						 gint row,
+						 gint col,
+						 GdkEvent *event);
+	gboolean	(*right_click)		(ETableItem *eti,
+						 gint row,
+						 gint col,
+						 GdkEvent *event);
+	gboolean	(*click)		(ETableItem *eti,
+						 gint row,
+						 gint col,
+						 GdkEvent *event);
+	gboolean	(*key_press)		(ETableItem *eti,
+						 gint row,
+						 gint col,
+						 GdkEvent *event);
+	gboolean	(*start_drag)		(ETableItem *eti,
+						 gint row,
+						 gint col,
+						 GdkEvent *event);
+	void		(*style_set)		(ETableItem *eti,
+						 GtkStyle *previous_style);
+	void		(*selection_model_removed)
+						(ETableItem *eti,
+						 ESelectionModel *selection);
+	void		(*selection_model_added)
+						(ETableItem *eti,
+						 ESelectionModel *selection);
+};
+
+GType		e_table_item_get_type		(void) G_GNUC_CONST;
+
+/*
+ * Focus
+ */
+void		e_table_item_set_cursor		(ETableItem *eti,
+						 gint col,
+						 gint row);
+
+gint		e_table_item_get_focused_column	(ETableItem *eti);
+
+void		e_table_item_leave_edit		(ETableItem *eti);
+void		e_table_item_enter_edit		(ETableItem *eti,
+						 gint col,
+						 gint row);
+
+void		e_table_item_redraw_range	(ETableItem *eti,
+						 gint start_col,
+						 gint start_row,
+						 gint end_col,
+						 gint end_row);
+
+EPrintable *	e_table_item_get_printable	(ETableItem *eti);
+void		e_table_item_compute_location	(ETableItem *eti,
+						 gint *x,
+						 gint *y,
+						 gint *row,
+						 gint *col);
+void		e_table_item_compute_mouse_over	(ETableItem *eti,
+						 gint x,
+						 gint y,
+						 gint *row,
+						 gint *col);
+void		e_table_item_get_cell_geometry	(ETableItem *eti,
+						 gint *row,
+						 gint *col,
+						 gint *x,
+						 gint *y,
+						 gint *width,
+						 gint *height);
+
+gint		e_table_item_row_diff		(ETableItem *eti,
+						 gint start_row,
+						 gint end_row);
+
+G_END_DECLS
+
+#endif /* _E_TABLE_ITEM_H_ */
diff --git a/e-util/e-table-memory-callbacks.c b/e-util/e-table-memory-callbacks.c
new file mode 100644
index 0000000..a3f919b
--- /dev/null
+++ b/e-util/e-table-memory-callbacks.c
@@ -0,0 +1,234 @@
+/*
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-table-memory-callbacks.h"
+
+G_DEFINE_TYPE (ETableMemoryCallbacks, e_table_memory_callbacks, E_TYPE_TABLE_MEMORY)
+
+static gint
+etmc_column_count (ETableModel *etm)
+{
+	ETableMemoryCallbacks *etmc = E_TABLE_MEMORY_CALLBACKS (etm);
+
+	if (etmc->col_count)
+		return etmc->col_count (etm, etmc->data);
+	else
+		return 0;
+}
+
+static gpointer
+etmc_value_at (ETableModel *etm,
+               gint col,
+               gint row)
+{
+	ETableMemoryCallbacks *etmc = E_TABLE_MEMORY_CALLBACKS (etm);
+
+	if (etmc->value_at)
+		return etmc->value_at (etm, col, row, etmc->data);
+	else
+		return NULL;
+}
+
+static void
+etmc_set_value_at (ETableModel *etm,
+                   gint col,
+                   gint row,
+                   gconstpointer val)
+{
+	ETableMemoryCallbacks *etmc = E_TABLE_MEMORY_CALLBACKS (etm);
+
+	if (etmc->set_value_at)
+		etmc->set_value_at (etm, col, row, val, etmc->data);
+}
+
+static gboolean
+etmc_is_cell_editable (ETableModel *etm,
+                       gint col,
+                       gint row)
+{
+	ETableMemoryCallbacks *etmc = E_TABLE_MEMORY_CALLBACKS (etm);
+
+	if (etmc->is_cell_editable)
+		return etmc->is_cell_editable (etm, col, row, etmc->data);
+	else
+		return FALSE;
+}
+
+/* The default for etmc_duplicate_value is to return the raw value. */
+static gpointer
+etmc_duplicate_value (ETableModel *etm,
+                      gint col,
+                      gconstpointer value)
+{
+	ETableMemoryCallbacks *etmc = E_TABLE_MEMORY_CALLBACKS (etm);
+
+	if (etmc->duplicate_value)
+		return etmc->duplicate_value (etm, col, value, etmc->data);
+	else
+		return (gpointer) value;
+}
+
+static void
+etmc_free_value (ETableModel *etm,
+                 gint col,
+                 gpointer value)
+{
+	ETableMemoryCallbacks *etmc = E_TABLE_MEMORY_CALLBACKS (etm);
+
+	if (etmc->free_value)
+		etmc->free_value (etm, col, value, etmc->data);
+}
+
+static gpointer
+etmc_initialize_value (ETableModel *etm,
+                       gint col)
+{
+	ETableMemoryCallbacks *etmc = E_TABLE_MEMORY_CALLBACKS (etm);
+
+	if (etmc->initialize_value)
+		return etmc->initialize_value (etm, col, etmc->data);
+	else
+		return NULL;
+}
+
+static gboolean
+etmc_value_is_empty (ETableModel *etm,
+                     gint col,
+                     gconstpointer value)
+{
+	ETableMemoryCallbacks *etmc = E_TABLE_MEMORY_CALLBACKS (etm);
+
+	if (etmc->value_is_empty)
+		return etmc->value_is_empty (etm, col, value, etmc->data);
+	else
+		return FALSE;
+}
+
+static gchar *
+etmc_value_to_string (ETableModel *etm,
+                      gint col,
+                      gconstpointer value)
+{
+	ETableMemoryCallbacks *etmc = E_TABLE_MEMORY_CALLBACKS (etm);
+
+	if (etmc->value_to_string)
+		return etmc->value_to_string (etm, col, value, etmc->data);
+	else
+		return g_strdup ("");
+}
+
+static void
+etmc_append_row (ETableModel *etm,
+                 ETableModel *source,
+                 gint row)
+{
+	ETableMemoryCallbacks *etmc = E_TABLE_MEMORY_CALLBACKS (etm);
+
+	if (etmc->append_row)
+		etmc->append_row (etm, source, row, etmc->data);
+}
+
+static void
+e_table_memory_callbacks_class_init (ETableMemoryCallbacksClass *class)
+{
+	ETableModelClass *model_class = E_TABLE_MODEL_CLASS (class);
+
+	model_class->column_count     = etmc_column_count;
+	model_class->value_at         = etmc_value_at;
+	model_class->set_value_at     = etmc_set_value_at;
+	model_class->is_cell_editable = etmc_is_cell_editable;
+	model_class->duplicate_value  = etmc_duplicate_value;
+	model_class->free_value       = etmc_free_value;
+	model_class->initialize_value = etmc_initialize_value;
+	model_class->value_is_empty   = etmc_value_is_empty;
+	model_class->value_to_string  = etmc_value_to_string;
+	model_class->append_row       = etmc_append_row;
+
+}
+
+static void
+e_table_memory_callbacks_init (ETableMemoryCallbacks *etmc)
+{
+	/* nothing to do */
+}
+
+/**
+ * e_table_memory_callbacks_new:
+ * @col_count:
+ * @value_at:
+ * @set_value_at:
+ * @is_cell_editable:
+ * @duplicate_value:
+ * @free_value:
+ * @initialize_value:
+ * @value_is_empty:
+ * @value_to_string:
+ * @data: closure pointer.
+ *
+ * This initializes a new ETableMemoryCallbacksModel object.
+ * ETableMemoryCallbacksModel is an implementaiton of the abstract class
+ * ETableModel.  The ETableMemoryCallbacksModel is designed to allow people
+ * to easily create ETableModels without having to create a new GType
+ * derived from ETableModel every time they need one.
+ *
+ * Instead, ETableMemoryCallbacksModel uses a setup based in callback
+ * functions, every callback function signature mimics the signature of
+ * each ETableModel method and passes the extra @data pointer to each one
+ * of the method to provide them with any context they might want to use.
+ *
+ * Returns: An ETableMemoryCallbacksModel object (which is also an ETableModel
+ * object).
+ */
+ETableModel *
+e_table_memory_callbacks_new (ETableMemoryCallbacksColumnCountFn col_count,
+                              ETableMemoryCallbacksValueAtFn value_at,
+                              ETableMemoryCallbacksSetValueAtFn set_value_at,
+                              ETableMemoryCallbacksIsCellEditableFn is_cell_editable,
+                              ETableMemoryCallbacksDuplicateValueFn duplicate_value,
+                              ETableMemoryCallbacksFreeValueFn free_value,
+                              ETableMemoryCallbacksInitializeValueFn initialize_value,
+                              ETableMemoryCallbacksValueIsEmptyFn value_is_empty,
+                              ETableMemoryCallbacksValueToStringFn value_to_string,
+                              gpointer data)
+{
+	ETableMemoryCallbacks *et;
+
+	et = g_object_new (E_TYPE_TABLE_MEMORY_CALLBACKS, NULL);
+
+	et->col_count        = col_count;
+	et->value_at         = value_at;
+	et->set_value_at     = set_value_at;
+	et->is_cell_editable = is_cell_editable;
+	et->duplicate_value  = duplicate_value;
+	et->free_value       = free_value;
+	et->initialize_value = initialize_value;
+	et->value_is_empty   = value_is_empty;
+	et->value_to_string  = value_to_string;
+	et->data             = data;
+
+	return (ETableModel *) et;
+ }
diff --git a/e-util/e-table-memory-callbacks.h b/e-util/e-table-memory-callbacks.h
new file mode 100644
index 0000000..a71cac1
--- /dev/null
+++ b/e-util/e-table-memory-callbacks.h
@@ -0,0 +1,148 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef _E_TABLE_MEMORY_CALLBACKS_H_
+#define _E_TABLE_MEMORY_CALLBACKS_H_
+
+#include <e-util/e-table-memory.h>
+
+/* Standard GObject macros */
+#define E_TYPE_TABLE_MEMORY_CALLBACKS \
+	(e_table_memory_callbacks_get_type ())
+#define E_TABLE_MEMORY_CALLBACKS(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_TABLE_MEMORY_CALLBACKS, ETableMemoryCallbacks))
+#define E_TABLE_MEMORY_CALLBACKS_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_TABLE_MEMORY_CALLBACKS, ETableMemoryCallbacksClass))
+#define E_IS_TABLE_MEMORY_CALLBACKS(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_TABLE_MEMORY_CALLBACKS))
+#define E_IS_TABLE_MEMORY_CALLBACKS_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_TABLE_MEMORY_CALLBACKS))
+#define E_TABLE_MEMORY_CALLBACKS_GET_CLASS(cls) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((cls), E_TYPE_TABLE_MEMORY_CALLBACKS, ETableMemoryCallbacksClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ETableMemoryCallbacks ETableMemoryCallbacks;
+typedef struct _ETableMemoryCallbacksClass ETableMemoryCallbacksClass;
+
+typedef gint		(*ETableMemoryCallbacksColumnCountFn)
+							(ETableModel *etm,
+							 gpointer data);
+typedef void		(*ETableMemoryCallbacksAppendRowFn)
+							(ETableModel *etm,
+							 ETableModel *model,
+							 gint row,
+							 gpointer data);
+
+typedef gpointer	(*ETableMemoryCallbacksValueAtFn)
+							(ETableModel *etm,
+							 gint col,
+							 gint row,
+							 gpointer data);
+typedef void		(*ETableMemoryCallbacksSetValueAtFn)
+							(ETableModel *etm,
+							 gint col,
+							 gint row,
+							 gconstpointer val,
+							 gpointer data);
+typedef gboolean	(*ETableMemoryCallbacksIsCellEditableFn)
+							(ETableModel *etm,
+							 gint col,
+							 gint row,
+							 gpointer data);
+
+typedef gpointer	(*ETableMemoryCallbacksDuplicateValueFn)
+							(ETableModel *etm,
+							 gint col,
+							 gconstpointer val,
+							 gpointer data);
+typedef void		(*ETableMemoryCallbacksFreeValueFn)
+							(ETableModel *etm,
+							 gint col,
+							 gpointer val,
+							 gpointer data);
+typedef gpointer	(*ETableMemoryCallbacksInitializeValueFn)
+							(ETableModel *etm,
+							 gint col,
+							 gpointer data);
+typedef gboolean	(*ETableMemoryCallbacksValueIsEmptyFn)
+							(ETableModel *etm,
+							 gint col,
+							 gconstpointer val,
+							 gpointer data);
+typedef gchar *		(*ETableMemoryCallbacksValueToStringFn)
+							(ETableModel *etm,
+							 gint col,
+							 gconstpointer val,
+							 gpointer data);
+
+struct _ETableMemoryCallbacks {
+	ETableMemory parent;
+
+	ETableMemoryCallbacksColumnCountFn     col_count;
+	ETableMemoryCallbacksAppendRowFn       append_row;
+
+	ETableMemoryCallbacksValueAtFn         value_at;
+	ETableMemoryCallbacksSetValueAtFn      set_value_at;
+	ETableMemoryCallbacksIsCellEditableFn  is_cell_editable;
+
+	ETableMemoryCallbacksDuplicateValueFn  duplicate_value;
+	ETableMemoryCallbacksFreeValueFn       free_value;
+	ETableMemoryCallbacksInitializeValueFn initialize_value;
+	ETableMemoryCallbacksValueIsEmptyFn    value_is_empty;
+	ETableMemoryCallbacksValueToStringFn   value_to_string;
+	gpointer data;
+};
+
+struct _ETableMemoryCallbacksClass {
+	ETableMemoryClass parent_class;
+};
+
+GType		e_table_memory_callbacks_get_type
+			(void) G_GNUC_CONST;
+ETableModel *	e_table_memory_callbacks_new
+			(ETableMemoryCallbacksColumnCountFn col_count,
+
+			 ETableMemoryCallbacksValueAtFn value_at,
+			 ETableMemoryCallbacksSetValueAtFn set_value_at,
+			 ETableMemoryCallbacksIsCellEditableFn is_cell_editable,
+
+			 ETableMemoryCallbacksDuplicateValueFn duplicate_value,
+			 ETableMemoryCallbacksFreeValueFn free_value,
+			 ETableMemoryCallbacksInitializeValueFn initialize_value,
+			 ETableMemoryCallbacksValueIsEmptyFn value_is_empty,
+			 ETableMemoryCallbacksValueToStringFn value_to_string,
+			 gpointer data);
+
+G_END_DECLS
+
+#endif /* _E_TABLE_MEMORY_CALLBACKS_H_ */
+
diff --git a/e-util/e-table-memory-store.c b/e-util/e-table-memory-store.c
new file mode 100644
index 0000000..066d319
--- /dev/null
+++ b/e-util/e-table-memory-store.c
@@ -0,0 +1,637 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+
+#include "e-table-memory-store.h"
+
+#define E_TABLE_MEMORY_STORE_GET_PRIVATE(obj) \
+	(G_TYPE_INSTANCE_GET_PRIVATE \
+	((obj), E_TYPE_TABLE_MEMORY_STORE, ETableMemoryStorePrivate))
+
+#define STORE_LOCATOR(etms, col, row) (*((etms)->priv->store + (row) * (etms)->priv->col_count + (col)))
+
+struct _ETableMemoryStorePrivate {
+	gint col_count;
+	ETableMemoryStoreColumnInfo *columns;
+	gpointer *store;
+};
+
+G_DEFINE_TYPE (ETableMemoryStore, e_table_memory_store, E_TYPE_TABLE_MEMORY)
+
+static gpointer
+duplicate_value (ETableMemoryStore *etms,
+                 gint col,
+                 gconstpointer val)
+{
+	switch (etms->priv->columns[col].type) {
+	case E_TABLE_MEMORY_STORE_COLUMN_TYPE_STRING:
+		return g_strdup (val);
+	case E_TABLE_MEMORY_STORE_COLUMN_TYPE_PIXBUF:
+		if (val)
+			g_object_ref ((gpointer) val);
+		return (gpointer) val;
+	case E_TABLE_MEMORY_STORE_COLUMN_TYPE_OBJECT:
+		if (val)
+			g_object_ref ((gpointer) val);
+		return (gpointer) val;
+	case E_TABLE_MEMORY_STORE_COLUMN_TYPE_CUSTOM:
+		if (etms->priv->columns[col].custom.duplicate_value)
+			return etms->priv->columns[col].custom.duplicate_value (E_TABLE_MODEL (etms), col, val, NULL);
+		break;
+	default:
+		break;
+	}
+	return (gpointer) val;
+}
+
+static void
+free_value (ETableMemoryStore *etms,
+            gint col,
+            gpointer value)
+{
+	switch (etms->priv->columns[col].type) {
+	case E_TABLE_MEMORY_STORE_COLUMN_TYPE_STRING:
+		g_free (value);
+		break;
+	case E_TABLE_MEMORY_STORE_COLUMN_TYPE_PIXBUF:
+		if (value)
+			g_object_unref (value);
+		break;
+	case E_TABLE_MEMORY_STORE_COLUMN_TYPE_OBJECT:
+		if (value)
+			g_object_unref (value);
+		break;
+	case E_TABLE_MEMORY_STORE_COLUMN_TYPE_CUSTOM:
+		if (etms->priv->columns[col].custom.free_value)
+			etms->priv->columns[col].custom.free_value (E_TABLE_MODEL (etms), col, value, NULL);
+		break;
+	default:
+		break;
+	}
+}
+
+static gint
+etms_column_count (ETableModel *etm)
+{
+	ETableMemoryStore *etms = E_TABLE_MEMORY_STORE (etm);
+
+	return etms->priv->col_count;
+}
+
+static gpointer
+etms_value_at (ETableModel *etm,
+               gint col,
+               gint row)
+{
+	ETableMemoryStore *etms = E_TABLE_MEMORY_STORE (etm);
+
+	return STORE_LOCATOR (etms, col, row);
+}
+
+static void
+etms_set_value_at (ETableModel *etm,
+                   gint col,
+                   gint row,
+                   gconstpointer val)
+{
+	ETableMemoryStore *etms = E_TABLE_MEMORY_STORE (etm);
+
+	e_table_model_pre_change (etm);
+
+	STORE_LOCATOR (etms, col, row) = duplicate_value (etms, col, val);
+
+	e_table_model_cell_changed (etm, col, row);
+}
+
+static gboolean
+etms_is_cell_editable (ETableModel *etm,
+                       gint col,
+                       gint row)
+{
+	ETableMemoryStore *etms = E_TABLE_MEMORY_STORE (etm);
+
+	return etms->priv->columns[col].editable;
+}
+
+/* The default for etms_duplicate_value is to return the raw value. */
+static gpointer
+etms_duplicate_value (ETableModel *etm,
+                      gint col,
+                      gconstpointer value)
+{
+	ETableMemoryStore *etms = E_TABLE_MEMORY_STORE (etm);
+
+	return duplicate_value (etms, col, value);
+}
+
+static void
+etms_free_value (ETableModel *etm,
+                 gint col,
+                 gpointer value)
+{
+	ETableMemoryStore *etms = E_TABLE_MEMORY_STORE (etm);
+
+	free_value (etms, col, value);
+}
+
+static gpointer
+etms_initialize_value (ETableModel *etm,
+                       gint col)
+{
+	ETableMemoryStore *etms = E_TABLE_MEMORY_STORE (etm);
+
+	switch (etms->priv->columns[col].type) {
+	case E_TABLE_MEMORY_STORE_COLUMN_TYPE_STRING:
+		return g_strdup ("");
+	case E_TABLE_MEMORY_STORE_COLUMN_TYPE_PIXBUF:
+		return NULL;
+	case E_TABLE_MEMORY_STORE_COLUMN_TYPE_CUSTOM:
+	case E_TABLE_MEMORY_STORE_COLUMN_TYPE_OBJECT:
+		if (etms->priv->columns[col].custom.initialize_value)
+			return etms->priv->columns[col].custom.initialize_value (E_TABLE_MODEL (etms), col, NULL);
+		break;
+	default:
+		break;
+	}
+	return NULL;
+}
+
+static gboolean
+etms_value_is_empty (ETableModel *etm,
+                     gint col,
+                     gconstpointer value)
+{
+	ETableMemoryStore *etms = E_TABLE_MEMORY_STORE (etm);
+
+	switch (etms->priv->columns[col].type) {
+	case E_TABLE_MEMORY_STORE_COLUMN_TYPE_STRING:
+		return !(value && *(gchar *) value);
+	case E_TABLE_MEMORY_STORE_COLUMN_TYPE_PIXBUF:
+		return value == NULL;
+	case E_TABLE_MEMORY_STORE_COLUMN_TYPE_CUSTOM:
+	case E_TABLE_MEMORY_STORE_COLUMN_TYPE_OBJECT:
+		if (etms->priv->columns[col].custom.value_is_empty)
+			return etms->priv->columns[col].custom.value_is_empty (E_TABLE_MODEL (etms), col, value, NULL);
+		break;
+	default:
+		break;
+	}
+	return value == NULL;
+}
+
+static gchar *
+etms_value_to_string (ETableModel *etm,
+                      gint col,
+                      gconstpointer value)
+{
+	ETableMemoryStore *etms = E_TABLE_MEMORY_STORE (etm);
+
+	switch (etms->priv->columns[col].type) {
+	case E_TABLE_MEMORY_STORE_COLUMN_TYPE_STRING:
+		return g_strdup (value);
+	case E_TABLE_MEMORY_STORE_COLUMN_TYPE_PIXBUF:
+		return g_strdup ("");
+	case E_TABLE_MEMORY_STORE_COLUMN_TYPE_CUSTOM:
+	case E_TABLE_MEMORY_STORE_COLUMN_TYPE_OBJECT:
+		if (etms->priv->columns[col].custom.value_is_empty)
+			return etms->priv->columns[col].custom.value_to_string (E_TABLE_MODEL (etms), col, value, NULL);
+		break;
+	default:
+		break;
+	}
+	return g_strdup_printf ("%d", GPOINTER_TO_INT (value));
+}
+
+static void
+etms_append_row (ETableModel *etm,
+                 ETableModel *source,
+                 gint row)
+{
+	ETableMemoryStore *etms = E_TABLE_MEMORY_STORE (etm);
+	gpointer *new_data;
+	gint i;
+	gint row_count;
+
+	new_data = g_new (gpointer , etms->priv->col_count);
+
+	for (i = 0; i < etms->priv->col_count; i++) {
+		new_data[i] = e_table_model_value_at (source, i, row);
+	}
+
+	row_count = e_table_model_row_count (E_TABLE_MODEL (etms));
+
+	e_table_memory_store_insert_array (etms, row_count, new_data, NULL);
+}
+
+static void
+etms_finalize (GObject *object)
+{
+	ETableMemoryStorePrivate *priv;
+
+	priv = E_TABLE_MEMORY_STORE_GET_PRIVATE (object);
+
+	e_table_memory_store_clear (E_TABLE_MEMORY_STORE (object));
+
+	g_free (priv->columns);
+	g_free (priv->store);
+
+	/* Chain up to parent's finalize() method. */
+	G_OBJECT_CLASS (e_table_memory_store_parent_class)->finalize (object);
+}
+
+static void
+e_table_memory_store_init (ETableMemoryStore *etms)
+{
+	etms->priv = E_TABLE_MEMORY_STORE_GET_PRIVATE (etms);
+}
+
+static void
+e_table_memory_store_class_init (ETableMemoryStoreClass *class)
+{
+	GObjectClass *object_class;
+	ETableModelClass *model_class;
+
+	g_type_class_add_private (class, sizeof (ETableMemoryStorePrivate));
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->finalize = etms_finalize;
+
+	model_class = E_TABLE_MODEL_CLASS (class);
+	model_class->column_count = etms_column_count;
+	model_class->value_at = etms_value_at;
+	model_class->set_value_at = etms_set_value_at;
+	model_class->is_cell_editable = etms_is_cell_editable;
+	model_class->duplicate_value = etms_duplicate_value;
+	model_class->free_value = etms_free_value;
+	model_class->initialize_value = etms_initialize_value;
+	model_class->value_is_empty = etms_value_is_empty;
+	model_class->value_to_string = etms_value_to_string;
+	model_class->append_row = etms_append_row;
+}
+
+/**
+ * e_table_memory_store_new:
+ * @col_count:
+ * @value_at:
+ * @set_value_at:
+ * @is_cell_editable:
+ * @duplicate_value:
+ * @free_value:
+ * @initialize_value:
+ * @value_is_empty:
+ * @value_to_string:
+ * @data: closure pointer.
+ *
+ * This initializes a new ETableMemoryStoreModel object.  ETableMemoryStoreModel is
+ * an implementaiton of the abstract class ETableModel.  The ETableMemoryStoreModel
+ * is designed to allow people to easily create ETableModels without having
+ * to create a new GType derived from ETableModel every time they need one.
+ *
+ * Instead, ETableMemoryStoreModel uses a setup based in callback functions, every
+ * callback function signature mimics the signature of each ETableModel method
+ * and passes the extra @data pointer to each one of the method to provide them
+ * with any context they might want to use.
+ *
+ * Returns: An ETableMemoryStoreModel object (which is also an ETableModel
+ * object).
+ */
+ETableModel *
+e_table_memory_store_new (ETableMemoryStoreColumnInfo *columns)
+{
+	ETableMemoryStore *et = g_object_new (E_TYPE_TABLE_MEMORY_STORE, NULL);
+
+	if (e_table_memory_store_construct (et, columns)) {
+		return (ETableModel *) et;
+	} else {
+		g_object_unref (et);
+		return NULL;
+	}
+}
+
+ETableModel *
+e_table_memory_store_construct (ETableMemoryStore *etms,
+                                ETableMemoryStoreColumnInfo *columns)
+{
+	gint i;
+	for (i = 0; columns[i].type != E_TABLE_MEMORY_STORE_COLUMN_TYPE_TERMINATOR; i++)
+		/* Intentionally blank */;
+	etms->priv->col_count = i;
+
+	etms->priv->columns = g_new (ETableMemoryStoreColumnInfo, etms->priv->col_count + 1);
+
+	memcpy (etms->priv->columns, columns, (etms->priv->col_count + 1) * sizeof (ETableMemoryStoreColumnInfo));
+
+	return E_TABLE_MODEL (etms);
+}
+
+void
+e_table_memory_store_adopt_value_at (ETableMemoryStore *etms,
+                                     gint col,
+                                     gint row,
+                                     gpointer value)
+{
+	e_table_model_pre_change (E_TABLE_MODEL (etms));
+
+	STORE_LOCATOR (etms, col, row) = value;
+
+	e_table_model_cell_changed (E_TABLE_MODEL (etms), col, row);
+}
+
+/* The size of these arrays is the number of columns. */
+void
+e_table_memory_store_insert_array (ETableMemoryStore *etms,
+                                   gint row,
+                                   gpointer *store,
+                                   gpointer data)
+{
+	gint row_count;
+	gint i;
+
+	row_count = e_table_model_row_count (E_TABLE_MODEL (etms)) + 1;
+	if (row == -1)
+		row = row_count - 1;
+	etms->priv->store = g_realloc (etms->priv->store, etms->priv->col_count * row_count * sizeof (gpointer));
+	memmove (
+		etms->priv->store + etms->priv->col_count * (row + 1),
+		etms->priv->store + etms->priv->col_count * row,
+		etms->priv->col_count * (row_count - row - 1) * sizeof (gpointer));
+
+	for (i = 0; i < etms->priv->col_count; i++) {
+		STORE_LOCATOR (etms, i, row) = duplicate_value (etms, i, store[i]);
+	}
+
+	e_table_memory_insert (E_TABLE_MEMORY (etms), row, data);
+}
+
+void
+e_table_memory_store_insert (ETableMemoryStore *etms,
+                             gint row,
+                             gpointer data,
+                             ...)
+{
+	gpointer *store;
+	va_list args;
+	gint i;
+
+	store = g_new (gpointer , etms->priv->col_count + 1);
+
+	va_start (args, data);
+	for (i = 0; i < etms->priv->col_count; i++) {
+		store[i] = va_arg (args, gpointer);
+	}
+	va_end (args);
+
+	e_table_memory_store_insert_array (etms, row, store, data);
+
+	g_free (store);
+}
+
+void
+e_table_memory_store_insert_adopt_array (ETableMemoryStore *etms,
+                                         gint row,
+                                         gpointer *store,
+                                         gpointer data)
+{
+	gint row_count;
+	gint i;
+
+	row_count = e_table_model_row_count (E_TABLE_MODEL (etms)) + 1;
+	if (row == -1)
+		row = row_count - 1;
+	etms->priv->store = g_realloc (etms->priv->store, etms->priv->col_count * row_count * sizeof (gpointer));
+	memmove (
+		etms->priv->store + etms->priv->col_count * (row + 1),
+		etms->priv->store + etms->priv->col_count * row,
+		etms->priv->col_count * (row_count - row - 1) * sizeof (gpointer));
+
+	for (i = 0; i < etms->priv->col_count; i++) {
+		STORE_LOCATOR (etms, i, row) = store[i];
+	}
+
+	e_table_memory_insert (E_TABLE_MEMORY (etms), row, data);
+}
+
+void
+e_table_memory_store_insert_adopt (ETableMemoryStore *etms,
+                                   gint row,
+                                   gpointer data,
+                                   ...)
+{
+	gpointer *store;
+	va_list args;
+	gint i;
+
+	store = g_new (gpointer , etms->priv->col_count + 1);
+
+	va_start (args, data);
+	for (i = 0; i < etms->priv->col_count; i++) {
+		store[i] = va_arg (args, gpointer);
+	}
+	va_end (args);
+
+	e_table_memory_store_insert_adopt_array (etms, row, store, data);
+
+	g_free (store);
+}
+
+/**
+ * e_table_memory_store_change_array:
+ * @etms: the ETabelMemoryStore.
+ * @row:  the row we're changing.
+ * @store: an array of new values to fill the row
+ * @data: the new closure to associate with this row.
+ *
+ * frees existing values associated with a row and replaces them with
+ * duplicates of the values in store.
+ *
+ */
+void
+e_table_memory_store_change_array (ETableMemoryStore *etms,
+                                   gint row,
+                                   gpointer *store,
+                                   gpointer data)
+{
+	gint i;
+
+	g_return_if_fail (row >= 0 && row < e_table_model_row_count (E_TABLE_MODEL (etms)));
+
+	e_table_model_pre_change (E_TABLE_MODEL (etms));
+
+	for (i = 0; i < etms->priv->col_count; i++) {
+		free_value (etms, i, STORE_LOCATOR (etms, i, row));
+		STORE_LOCATOR (etms, i, row) = duplicate_value (etms, i, store[i]);
+	}
+
+	e_table_memory_set_data (E_TABLE_MEMORY (etms), row, data);
+	e_table_model_row_changed (E_TABLE_MODEL (etms), row);
+}
+
+/**
+ * e_table_memory_store_change:
+ * @etms: the ETabelMemoryStore.
+ * @row:  the row we're changing.
+ * @data: the new closure to associate with this row.
+ *
+ * a varargs version of e_table_memory_store_change_array.  you must
+ * pass in etms->col_count args.
+ */
+void
+e_table_memory_store_change (ETableMemoryStore *etms,
+                             gint row,
+                             gpointer data,
+                             ...)
+{
+	gpointer *store;
+	va_list args;
+	gint i;
+
+	g_return_if_fail (row >= 0 && row < e_table_model_row_count (E_TABLE_MODEL (etms)));
+
+	store = g_new0 (gpointer , etms->priv->col_count + 1);
+
+	va_start (args, data);
+	for (i = 0; i < etms->priv->col_count; i++) {
+		store[i] = va_arg (args, gpointer);
+	}
+	va_end (args);
+
+	e_table_memory_store_change_array (etms, row, store, data);
+
+	g_free (store);
+}
+
+/**
+ * e_table_memory_store_change_adopt_array:
+ * @etms: the ETableMemoryStore
+ * @row: the row we're changing.
+ * @store: an array of new values to fill the row
+ * @data: the new closure to associate with this row.
+ *
+ * frees existing values for the row and stores the values from store
+ * into it.  This function differs from
+ * e_table_memory_storage_change_adopt_array in that it does not
+ * duplicate the data.
+ */
+void
+e_table_memory_store_change_adopt_array (ETableMemoryStore *etms,
+                                         gint row,
+                                         gpointer *store,
+                                         gpointer data)
+{
+	gint i;
+
+	g_return_if_fail (row >= 0 && row < e_table_model_row_count (E_TABLE_MODEL (etms)));
+
+	for (i = 0; i < etms->priv->col_count; i++) {
+		free_value (etms, i, STORE_LOCATOR (etms, i, row));
+		STORE_LOCATOR (etms, i, row) = store[i];
+	}
+
+	e_table_memory_set_data (E_TABLE_MEMORY (etms), row, data);
+	e_table_model_row_changed (E_TABLE_MODEL (etms), row);
+}
+
+/**
+ * e_table_memory_store_change_adopt
+ * @etms: the ETabelMemoryStore.
+ * @row:  the row we're changing.
+ * @data: the new closure to associate with this row.
+ *
+ * a varargs version of e_table_memory_store_change_adopt_array.  you
+ * must pass in etms->col_count args.
+ */
+void
+e_table_memory_store_change_adopt (ETableMemoryStore *etms,
+                                   gint row,
+                                   gpointer data,
+                                   ...)
+{
+	gpointer *store;
+	va_list args;
+	gint i;
+
+	g_return_if_fail (row >= 0 && row < e_table_model_row_count (E_TABLE_MODEL (etms)));
+
+	store = g_new0 (gpointer , etms->priv->col_count + 1);
+
+	va_start (args, data);
+	for (i = 0; i < etms->priv->col_count; i++) {
+		store[i] = va_arg (args, gpointer);
+	}
+	va_end (args);
+
+	e_table_memory_store_change_adopt_array (etms, row, store, data);
+
+	g_free (store);
+}
+
+void
+e_table_memory_store_remove (ETableMemoryStore *etms,
+                             gint row)
+{
+	ETableModel *model;
+	gint column_count, row_count;
+	gint i;
+
+	model = E_TABLE_MODEL (etms);
+	column_count = e_table_model_column_count (model);
+
+	for (i = 0; i < column_count; i++)
+		e_table_model_free_value (model, i, e_table_model_value_at (model, i, row));
+
+	row_count = e_table_model_row_count (E_TABLE_MODEL (etms)) - 1;
+	memmove (
+		etms->priv->store + etms->priv->col_count * row,
+		etms->priv->store + etms->priv->col_count * (row + 1),
+		etms->priv->col_count * (row_count - row) * sizeof (gpointer));
+	etms->priv->store = g_realloc (etms->priv->store, etms->priv->col_count * row_count * sizeof (gpointer));
+
+	e_table_memory_remove (E_TABLE_MEMORY (etms), row);
+}
+
+void
+e_table_memory_store_clear (ETableMemoryStore *etms)
+{
+	ETableModel *model;
+	gint row_count, column_count;
+	gint i, j;
+
+	model = E_TABLE_MODEL (etms);
+	row_count = e_table_model_row_count (model);
+	column_count = e_table_model_column_count (model);
+
+	for (i = 0; i < row_count; i++) {
+		for (j = 0; j < column_count; j++) {
+			e_table_model_free_value (model, j, e_table_model_value_at (model, j, i));
+		}
+	}
+
+	e_table_memory_clear (E_TABLE_MEMORY (etms));
+
+	g_free (etms->priv->store);
+	etms->priv->store = NULL;
+}
diff --git a/e-util/e-table-memory-store.h b/e-util/e-table-memory-store.h
new file mode 100644
index 0000000..c8167f8
--- /dev/null
+++ b/e-util/e-table-memory-store.h
@@ -0,0 +1,155 @@
+/*
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef _E_TABLE_MEMORY_STORE_H_
+#define _E_TABLE_MEMORY_STORE_H_
+
+#include <e-util/e-table-memory.h>
+#include <e-util/e-table-memory-callbacks.h>
+
+/* Standard GObject macros */
+#define E_TYPE_TABLE_MEMORY_STORE \
+	(e_table_memory_store_get_type ())
+#define E_TABLE_MEMORY_STORE(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_TABLE_MEMORY_STORE, ETableMemoryStore))
+#define E_TABLE_MEMORY_STORE_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_TABLE_MEMORY_STORE, ETableMemoryStoreClass))
+#define E_IS_TABLE_MEMORY_STORE(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_TABLE_MEMORY_STORE))
+#define E_IS_TABLE_MEMORY_STORE_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_TABLE_MEMORY_STORE))
+#define E_TABLE_MEMORY_STORE_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_TABLE_MEMORY_STORE, ETableMemoryStoreClass))
+
+G_BEGIN_DECLS
+
+typedef enum {
+	E_TABLE_MEMORY_STORE_COLUMN_TYPE_TERMINATOR,
+	E_TABLE_MEMORY_STORE_COLUMN_TYPE_INTEGER,
+	E_TABLE_MEMORY_STORE_COLUMN_TYPE_STRING,
+	E_TABLE_MEMORY_STORE_COLUMN_TYPE_PIXBUF,
+	E_TABLE_MEMORY_STORE_COLUMN_TYPE_OBJECT,
+	E_TABLE_MEMORY_STORE_COLUMN_TYPE_CUSTOM
+} ETableMemoryStoreColumnType;
+
+typedef struct {
+	ETableMemoryCallbacksDuplicateValueFn  duplicate_value;
+	ETableMemoryCallbacksFreeValueFn       free_value;
+	ETableMemoryCallbacksInitializeValueFn initialize_value;
+	ETableMemoryCallbacksValueIsEmptyFn    value_is_empty;
+	ETableMemoryCallbacksValueToStringFn   value_to_string;
+} ETableMemoryStoreCustomColumn;
+
+typedef struct {
+	ETableMemoryStoreColumnType   type;
+	ETableMemoryStoreCustomColumn custom;
+	guint                         editable : 1;
+} ETableMemoryStoreColumnInfo;
+
+#define E_TABLE_MEMORY_STORE_TERMINATOR \
+	{ E_TABLE_MEMORY_STORE_COLUMN_TYPE_TERMINATOR, { NULL }, FALSE }
+#define E_TABLE_MEMORY_STORE_INTEGER \
+	{ E_TABLE_MEMORY_STORE_COLUMN_TYPE_INTEGER, { NULL }, FALSE }
+#define E_TABLE_MEMORY_STORE_STRING \
+	{ E_TABLE_MEMORY_STORE_COLUMN_TYPE_STRING, { NULL }, FALSE }
+
+typedef struct _ETableMemoryStore ETableMemoryStore;
+typedef struct _ETableMemoryStoreClass ETableMemoryStoreClass;
+typedef struct _ETableMemoryStorePrivate ETableMemoryStorePrivate;
+
+struct _ETableMemoryStore {
+	ETableMemory parent;
+	ETableMemoryStorePrivate *priv;
+};
+
+struct _ETableMemoryStoreClass {
+	ETableMemoryClass parent_class;
+};
+
+GType		e_table_memory_store_get_type	(void) G_GNUC_CONST;
+ETableModel *	e_table_memory_store_new	(ETableMemoryStoreColumnInfo *columns);
+ETableModel *	e_table_memory_store_construct	(ETableMemoryStore *store,
+						 ETableMemoryStoreColumnInfo *columns);
+
+/* Adopt a value instead of copying it. */
+void		e_table_memory_store_adopt_value_at
+						(ETableMemoryStore *etms,
+						 gint col,
+						 gint row,
+						 gpointer value);
+
+/* The size of these arrays is the number of columns. */
+void		e_table_memory_store_insert_array
+						(ETableMemoryStore *etms,
+						 gint row,
+						 gpointer *store,
+						 gpointer data);
+void		e_table_memory_store_insert	(ETableMemoryStore *etms,
+						 gint row,
+						 gpointer data,
+						 ...);
+void		e_table_memory_store_insert_adopt
+						(ETableMemoryStore *etms,
+						 gint row,
+						 gpointer data,
+						 ...);
+void		e_table_memory_store_insert_adopt_array
+						(ETableMemoryStore *etms,
+						 gint row,
+						 gpointer *store,
+						 gpointer data);
+void		e_table_memory_store_change_array
+						(ETableMemoryStore *etms,
+						 gint row,
+						 gpointer *store,
+						 gpointer data);
+void		e_table_memory_store_change	(ETableMemoryStore *etms,
+						 gint row,
+						 gpointer data,
+						 ...);
+void		e_table_memory_store_change_adopt
+						(ETableMemoryStore *etms,
+						 gint row,
+						 gpointer data,
+						 ...);
+void		e_table_memory_store_change_adopt_array
+						(ETableMemoryStore *etms,
+						 gint row,
+						 gpointer *store,
+						 gpointer data);
+void		e_table_memory_store_remove	(ETableMemoryStore *etms,
+						 gint row);
+void		e_table_memory_store_clear	(ETableMemoryStore *etms);
+
+G_END_DECLS
+
+#endif /* _E_TABLE_MEMORY_STORE_H_ */
diff --git a/e-util/e-table-memory.c b/e-util/e-table-memory.c
new file mode 100644
index 0000000..b9a7eb9
--- /dev/null
+++ b/e-util/e-table-memory.c
@@ -0,0 +1,271 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+
+#include <libxml/parser.h>
+#include <libxml/xmlmemory.h>
+
+#include "e-table-memory.h"
+#include "e-xml-utils.h"
+
+#define E_TABLE_MEMORY_GET_PRIVATE(obj) \
+	(G_TYPE_INSTANCE_GET_PRIVATE \
+	((obj), E_TYPE_TABLE_MEMORY, ETableMemoryPrivate))
+
+G_DEFINE_TYPE (ETableMemory, e_table_memory, E_TYPE_TABLE_MODEL)
+
+struct _ETableMemoryPrivate {
+	gpointer *data;
+	gint num_rows;
+	gint frozen;
+};
+
+static void
+etmm_finalize (GObject *object)
+{
+	ETableMemoryPrivate *priv;
+
+	priv = E_TABLE_MEMORY_GET_PRIVATE (object);
+
+	g_free (priv->data);
+
+	/* Chain up to parent's finalize() method. */
+	G_OBJECT_CLASS (e_table_memory_parent_class)->finalize (object);
+}
+
+static gint
+etmm_row_count (ETableModel *etm)
+{
+	ETableMemory *etmm = E_TABLE_MEMORY (etm);
+
+	return etmm->priv->num_rows;
+}
+
+static void
+e_table_memory_class_init (ETableMemoryClass *class)
+{
+	GObjectClass *object_class;
+	ETableModelClass *table_model_class;
+
+	g_type_class_add_private (class, sizeof (ETableMemoryPrivate));
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->finalize = etmm_finalize;
+
+	table_model_class = E_TABLE_MODEL_CLASS (class);
+	table_model_class->row_count = etmm_row_count;
+}
+
+static void
+e_table_memory_init (ETableMemory *etmm)
+{
+	etmm->priv = E_TABLE_MEMORY_GET_PRIVATE (etmm);
+}
+
+/**
+ * e_table_memory_new
+ *
+ * XXX docs here.
+ *
+ * return values: a newly constructed ETableMemory.
+ */
+ETableMemory *
+e_table_memory_new (void)
+{
+	return g_object_new (E_TYPE_TABLE_MEMORY, NULL);
+}
+
+/**
+ * e_table_memory_get_data:
+ * @etmm:
+ * @row:
+ *
+ *
+ *
+ * Return value:
+ **/
+gpointer
+e_table_memory_get_data (ETableMemory *etmm,
+                         gint row)
+{
+	g_return_val_if_fail (row >= 0, NULL);
+	g_return_val_if_fail (row < etmm->priv->num_rows, NULL);
+
+	return etmm->priv->data[row];
+}
+
+/**
+ * e_table_memory_set_data:
+ * @etmm:
+ * @row:
+ * @data:
+ *
+ *
+ **/
+void
+e_table_memory_set_data (ETableMemory *etmm,
+                         gint row,
+                         gpointer data)
+{
+	g_return_if_fail (row >= 0);
+	g_return_if_fail (row < etmm->priv->num_rows);
+
+	etmm->priv->data[row] = data;
+}
+
+/**
+ * e_table_memory_insert:
+ * @table_model:
+ * @parent_path:
+ * @position:
+ * @data:
+ *
+ *
+ *
+ * Return value:
+ **/
+void
+e_table_memory_insert (ETableMemory *etmm,
+                       gint row,
+                       gpointer data)
+{
+	g_return_if_fail (row >= -1);
+	g_return_if_fail (row <= etmm->priv->num_rows);
+
+	if (!etmm->priv->frozen)
+		e_table_model_pre_change (E_TABLE_MODEL (etmm));
+
+	if (row == -1)
+		row = etmm->priv->num_rows;
+	etmm->priv->data = g_renew (gpointer, etmm->priv->data, etmm->priv->num_rows + 1);
+	memmove (
+		etmm->priv->data + row + 1,
+		etmm->priv->data + row,
+		(etmm->priv->num_rows - row) * sizeof (gpointer));
+	etmm->priv->data[row] = data;
+	etmm->priv->num_rows++;
+	if (!etmm->priv->frozen)
+		e_table_model_row_inserted (E_TABLE_MODEL (etmm), row);
+}
+
+/**
+ * e_table_memory_remove:
+ * @etable:
+ * @path:
+ *
+ *
+ *
+ * Return value:
+ **/
+gpointer
+e_table_memory_remove (ETableMemory *etmm,
+                       gint row)
+{
+	gpointer ret;
+
+	g_return_val_if_fail (row >= 0, NULL);
+	g_return_val_if_fail (row < etmm->priv->num_rows, NULL);
+
+	if (!etmm->priv->frozen)
+		e_table_model_pre_change (E_TABLE_MODEL (etmm));
+	ret = etmm->priv->data[row];
+	memmove (
+		etmm->priv->data + row,
+		etmm->priv->data + row + 1,
+		(etmm->priv->num_rows - row - 1) * sizeof (gpointer));
+	etmm->priv->num_rows--;
+	if (!etmm->priv->frozen)
+		e_table_model_row_deleted (E_TABLE_MODEL (etmm), row);
+	return ret;
+}
+
+/**
+ * e_table_memory_clear:
+ * @etable:
+ * @path:
+ *
+ *
+ *
+ * Return value:
+ **/
+void
+e_table_memory_clear (ETableMemory *etmm)
+{
+	if (!etmm->priv->frozen)
+		e_table_model_pre_change (E_TABLE_MODEL (etmm));
+	g_free (etmm->priv->data);
+	etmm->priv->data = NULL;
+	etmm->priv->num_rows = 0;
+	if (!etmm->priv->frozen)
+		e_table_model_changed (E_TABLE_MODEL (etmm));
+}
+
+/**
+ * e_table_memory_freeze:
+ * @etmm: the ETableModel to freeze.
+ *
+ * This function prepares an ETableModel for a period of much change.
+ * All signals regarding changes to the table are deferred until we
+ * thaw the table.
+ *
+ **/
+void
+e_table_memory_freeze (ETableMemory *etmm)
+{
+	ETableMemoryPrivate *priv = etmm->priv;
+
+	if (priv->frozen == 0)
+		e_table_model_pre_change (E_TABLE_MODEL (etmm));
+
+	priv->frozen++;
+}
+
+/**
+ * e_table_memory_thaw:
+ * @etmm: the ETableMemory to thaw.
+ *
+ * This function thaws an ETableMemory.  All the defered signals can add
+ * up to a lot, we don't know - so we just emit a model_changed
+ * signal.
+ *
+ **/
+void
+e_table_memory_thaw (ETableMemory *etmm)
+{
+	ETableMemoryPrivate *priv = etmm->priv;
+
+	if (priv->frozen > 0)
+		priv->frozen--;
+	if (priv->frozen == 0) {
+		e_table_model_changed (E_TABLE_MODEL (etmm));
+	}
+}
diff --git a/e-util/e-table-memory.h b/e-util/e-table-memory.h
new file mode 100644
index 0000000..8762027
--- /dev/null
+++ b/e-util/e-table-memory.h
@@ -0,0 +1,91 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef _E_TABLE_MEMORY_H_
+#define _E_TABLE_MEMORY_H_
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#include <e-util/e-table-model.h>
+
+/* Standard GObject macros */
+#define E_TYPE_TABLE_MEMORY \
+	(e_table_memory_get_type ())
+#define E_TABLE_MEMORY(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_TABLE_MEMORY, ETableMemory))
+#define E_TABLE_MEMORY_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_TABLE_MEMORY, ETableMemoryClass))
+#define E_IS_TABLE_MEMORY(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_TABLE_MEMORY))
+#define E_IS_TABLE_MEMORY_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_TABLE_MEMORY))
+#define E_TABLE_MEMORY_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_TABLE_MEMORY, ETableMemoryClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ETableMemory ETableMemory;
+typedef struct _ETableMemoryClass ETableMemoryClass;
+typedef struct _ETableMemoryPrivate ETableMemoryPrivate;
+
+struct _ETableMemory {
+	ETableModel parent;
+	ETableMemoryPrivate *priv;
+};
+
+struct _ETableMemoryClass {
+	ETableModelClass parent_class;
+};
+
+GType		e_table_memory_get_type		(void) G_GNUC_CONST;
+ETableMemory *	e_table_memory_new		(void);
+void		e_table_memory_construct	(ETableMemory *etable);
+
+/* row operations */
+void		e_table_memory_insert		(ETableMemory *etable,
+						 gint row,
+						 gpointer data);
+gpointer	e_table_memory_remove		(ETableMemory *etable,
+						 gint row);
+void		e_table_memory_clear		(ETableMemory *etable);
+
+/* Freeze and thaw */
+void		e_table_memory_freeze		(ETableMemory *etable);
+void		e_table_memory_thaw		(ETableMemory *etable);
+gpointer	e_table_memory_get_data		(ETableMemory *etm,
+						 gint row);
+void		e_table_memory_set_data		(ETableMemory *etm,
+						 gint row,
+						 gpointer data);
+
+G_END_DECLS
+
+#endif /* _E_TABLE_MEMORY_H */
diff --git a/e-util/e-table-model.c b/e-util/e-table-model.c
new file mode 100644
index 0000000..1ae4d3e
--- /dev/null
+++ b/e-util/e-table-model.c
@@ -0,0 +1,682 @@
+/*
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-table-model.h"
+
+#include "e-marshal.h"
+
+#define ETM_FROZEN(e) \
+	(GPOINTER_TO_INT (g_object_get_data (G_OBJECT (e), "frozen")) != 0)
+
+#define d(x)
+
+d (static gint depth = 0;)
+
+G_DEFINE_TYPE (ETableModel, e_table_model, G_TYPE_OBJECT)
+
+enum {
+	MODEL_NO_CHANGE,
+	MODEL_CHANGED,
+	MODEL_PRE_CHANGE,
+	MODEL_ROW_CHANGED,
+	MODEL_CELL_CHANGED,
+	MODEL_ROWS_INSERTED,
+	MODEL_ROWS_DELETED,
+	ROW_SELECTION,
+	LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0, };
+
+/**
+ * e_table_model_column_count:
+ * @e_table_model: The e-table-model to operate on
+ *
+ * Returns: the number of columns in the table model.
+ */
+gint
+e_table_model_column_count (ETableModel *e_table_model)
+{
+	ETableModelClass *class;
+
+	g_return_val_if_fail (E_IS_TABLE_MODEL (e_table_model), 0);
+
+	class = E_TABLE_MODEL_GET_CLASS (e_table_model);
+	g_return_val_if_fail (class->column_count != NULL, 0);
+
+	return class->column_count (e_table_model);
+}
+
+/**
+ * e_table_model_row_count:
+ * @e_table_model: the e-table-model to operate on
+ *
+ * Returns: the number of rows in the Table model.
+ */
+gint
+e_table_model_row_count (ETableModel *e_table_model)
+{
+	ETableModelClass *class;
+
+	g_return_val_if_fail (E_IS_TABLE_MODEL (e_table_model), 0);
+
+	class = E_TABLE_MODEL_GET_CLASS (e_table_model);
+	g_return_val_if_fail (class->row_count != NULL, 0);
+
+	return class->row_count (e_table_model);
+}
+
+/**
+ * e_table_model_append_row:
+ * @e_table_model: the table model to append the a row to.
+ * @source:
+ * @row:
+ *
+ */
+void
+e_table_model_append_row (ETableModel *e_table_model,
+                          ETableModel *source,
+                          gint row)
+{
+	ETableModelClass *class;
+
+	g_return_if_fail (E_IS_TABLE_MODEL (e_table_model));
+
+	class = E_TABLE_MODEL_GET_CLASS (e_table_model);
+
+	if (class->append_row != NULL)
+		class->append_row (e_table_model, source, row);
+}
+
+/**
+ * e_table_value_at:
+ * @e_table_model: the e-table-model to operate on
+ * @col: column in the model to pull data from.
+ * @row: row in the model to pull data from.
+ *
+ * Return value: This function returns the value that is stored
+ * by the @e_table_model in column @col and row @row.  The data
+ * returned can be a pointer or any data value that can be stored
+ * inside a pointer.
+ *
+ * The data returned is typically used by an ECell renderer.
+ *
+ * The data returned must be valid until the model sends a signal that
+ * affect that piece of data.  model_changed affects all data.
+ * row_changed affects the data in that row.  cell_changed affects the
+ * data in that cell.  rows_deleted affects all data in those rows.
+ * rows_inserted and no_change don't affect any data in this way.
+ **/
+gpointer
+e_table_model_value_at (ETableModel *e_table_model,
+                        gint col,
+                        gint row)
+{
+	ETableModelClass *class;
+
+	g_return_val_if_fail (E_IS_TABLE_MODEL (e_table_model), NULL);
+
+	class = E_TABLE_MODEL_GET_CLASS (e_table_model);
+	g_return_val_if_fail (class->value_at != NULL, NULL);
+
+	return class->value_at (e_table_model, col, row);
+}
+
+/**
+ * e_table_model_set_value_at:
+ * @e_table_model: the table model to operate on.
+ * @col: the column where the data will be stored in the model.
+ * @row: the row where the data will be stored in the model.
+ * @value: the data to be stored.
+ *
+ * This function instructs the model to store the value in @data in the
+ * the @e_table_model at column @col and row @row.  The @data typically
+ * comes from one of the ECell rendering objects.
+ *
+ * There should be an agreement between the Table Model and the user
+ * of this function about the data being stored.  Typically it will
+ * be a pointer to a set of data, or a datum that fits inside a gpointer .
+ */
+void
+e_table_model_set_value_at (ETableModel *e_table_model,
+                            gint col,
+                            gint row,
+                            gconstpointer value)
+{
+	ETableModelClass *class;
+
+	g_return_if_fail (E_IS_TABLE_MODEL (e_table_model));
+
+	class = E_TABLE_MODEL_GET_CLASS (e_table_model);
+	g_return_if_fail (class->set_value_at != NULL);
+
+	class->set_value_at (e_table_model, col, row, value);
+}
+
+/**
+ * e_table_model_is_cell_editable:
+ * @e_table_model: the table model to query.
+ * @col: column to query.
+ * @row: row to query.
+ *
+ * Returns: %TRUE if the cell in @e_table_model at @col,@row can be
+ * edited, %FALSE otherwise
+ */
+gboolean
+e_table_model_is_cell_editable (ETableModel *e_table_model,
+                                gint col,
+                                gint row)
+{
+	ETableModelClass *class;
+
+	g_return_val_if_fail (E_IS_TABLE_MODEL (e_table_model), FALSE);
+
+	class = E_TABLE_MODEL_GET_CLASS (e_table_model);
+	g_return_val_if_fail (class->is_cell_editable != NULL, FALSE);
+
+	return class->is_cell_editable (e_table_model, col, row);
+}
+
+gpointer
+e_table_model_duplicate_value (ETableModel *e_table_model,
+                               gint col,
+                               gconstpointer value)
+{
+	ETableModelClass *class;
+
+	g_return_val_if_fail (E_IS_TABLE_MODEL (e_table_model), NULL);
+
+	class = E_TABLE_MODEL_GET_CLASS (e_table_model);
+
+	if (class->duplicate_value == NULL)
+		return NULL;
+
+	return class->duplicate_value (e_table_model, col, value);
+}
+
+void
+e_table_model_free_value (ETableModel *e_table_model,
+                          gint col,
+                          gpointer value)
+{
+	ETableModelClass *class;
+
+	g_return_if_fail (E_IS_TABLE_MODEL (e_table_model));
+
+	class = E_TABLE_MODEL_GET_CLASS (e_table_model);
+
+	if (class->free_value != NULL)
+		class->free_value (e_table_model, col, value);
+}
+
+gboolean
+e_table_model_has_save_id (ETableModel *e_table_model)
+{
+	ETableModelClass *class;
+
+	g_return_val_if_fail (E_IS_TABLE_MODEL (e_table_model), FALSE);
+
+	class = E_TABLE_MODEL_GET_CLASS (e_table_model);
+
+	if (class->has_save_id == NULL)
+		return FALSE;
+
+	return class->has_save_id (e_table_model);
+}
+
+gchar *
+e_table_model_get_save_id (ETableModel *e_table_model,
+                           gint row)
+{
+	ETableModelClass *class;
+
+	g_return_val_if_fail (E_IS_TABLE_MODEL (e_table_model), NULL);
+
+	class = E_TABLE_MODEL_GET_CLASS (e_table_model);
+
+	if (class->get_save_id == NULL)
+		return NULL;
+
+	return class->get_save_id (e_table_model, row);
+}
+
+gboolean
+e_table_model_has_change_pending (ETableModel *e_table_model)
+{
+	ETableModelClass *class;
+
+	g_return_val_if_fail (E_IS_TABLE_MODEL (e_table_model), FALSE);
+
+	class = E_TABLE_MODEL_GET_CLASS (e_table_model);
+
+	if (class->has_change_pending == NULL)
+		return FALSE;
+
+	return class->has_change_pending (e_table_model);
+}
+
+gpointer
+e_table_model_initialize_value (ETableModel *e_table_model,
+                                gint col)
+{
+	ETableModelClass *class;
+
+	g_return_val_if_fail (E_IS_TABLE_MODEL (e_table_model), NULL);
+
+	class = E_TABLE_MODEL_GET_CLASS (e_table_model);
+
+	if (class->initialize_value == NULL)
+		return NULL;
+
+	return class->initialize_value (e_table_model, col);
+}
+
+gboolean
+e_table_model_value_is_empty (ETableModel *e_table_model,
+                              gint col,
+                              gconstpointer value)
+{
+	ETableModelClass *class;
+
+	g_return_val_if_fail (E_IS_TABLE_MODEL (e_table_model), FALSE);
+
+	class = E_TABLE_MODEL_GET_CLASS (e_table_model);
+
+	if (class->value_is_empty == NULL)
+		return FALSE;
+
+	return class->value_is_empty (e_table_model, col, value);
+}
+
+gchar *
+e_table_model_value_to_string (ETableModel *e_table_model,
+                               gint col,
+                               gconstpointer value)
+{
+	ETableModelClass *class;
+
+	g_return_val_if_fail (E_IS_TABLE_MODEL (e_table_model), NULL);
+
+	class = E_TABLE_MODEL_GET_CLASS (e_table_model);
+
+	if (class->value_to_string == NULL)
+		return g_strdup ("");
+
+	return class->value_to_string (e_table_model, col, value);
+}
+
+static void
+e_table_model_class_init (ETableModelClass *class)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+	signals[MODEL_NO_CHANGE] = g_signal_new (
+		"model_no_change",
+		G_TYPE_FROM_CLASS (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ETableModelClass, model_no_change),
+		(GSignalAccumulator) NULL, NULL,
+		g_cclosure_marshal_VOID__VOID,
+		G_TYPE_NONE, 0);
+
+	signals[MODEL_CHANGED] = g_signal_new (
+		"model_changed",
+		G_TYPE_FROM_CLASS (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ETableModelClass, model_changed),
+		(GSignalAccumulator) NULL, NULL,
+		g_cclosure_marshal_VOID__VOID,
+		G_TYPE_NONE, 0);
+
+	signals[MODEL_PRE_CHANGE] = g_signal_new (
+		"model_pre_change",
+		G_TYPE_FROM_CLASS (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ETableModelClass, model_pre_change),
+		(GSignalAccumulator) NULL, NULL,
+		g_cclosure_marshal_VOID__VOID,
+		G_TYPE_NONE, 0);
+
+	signals[MODEL_ROW_CHANGED] = g_signal_new (
+		"model_row_changed",
+		G_TYPE_FROM_CLASS (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ETableModelClass, model_row_changed),
+		(GSignalAccumulator) NULL, NULL,
+		g_cclosure_marshal_VOID__INT,
+		G_TYPE_NONE, 1,
+		G_TYPE_INT);
+
+	signals[MODEL_CELL_CHANGED] = g_signal_new (
+		"model_cell_changed",
+		G_TYPE_FROM_CLASS (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ETableModelClass, model_cell_changed),
+		(GSignalAccumulator) NULL, NULL,
+		e_marshal_VOID__INT_INT,
+		G_TYPE_NONE, 2,
+		G_TYPE_INT,
+		G_TYPE_INT);
+
+	signals[MODEL_ROWS_INSERTED] = g_signal_new (
+		"model_rows_inserted",
+		G_TYPE_FROM_CLASS (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ETableModelClass, model_rows_inserted),
+		(GSignalAccumulator) NULL, NULL,
+		e_marshal_VOID__INT_INT,
+		G_TYPE_NONE, 2,
+		G_TYPE_INT,
+		G_TYPE_INT);
+
+	signals[MODEL_ROWS_DELETED] = g_signal_new (
+		"model_rows_deleted",
+		G_TYPE_FROM_CLASS (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ETableModelClass, model_rows_deleted),
+		(GSignalAccumulator) NULL, NULL,
+		e_marshal_VOID__INT_INT,
+		G_TYPE_NONE, 2,
+		G_TYPE_INT,
+		G_TYPE_INT);
+
+	class->column_count        = NULL;
+	class->row_count           = NULL;
+	class->append_row          = NULL;
+
+	class->value_at            = NULL;
+	class->set_value_at        = NULL;
+	class->is_cell_editable    = NULL;
+
+	class->has_save_id         = NULL;
+	class->get_save_id         = NULL;
+
+	class->has_change_pending  = NULL;
+
+	class->duplicate_value     = NULL;
+	class->free_value          = NULL;
+	class->initialize_value    = NULL;
+	class->value_is_empty      = NULL;
+	class->value_to_string     = NULL;
+
+	class->model_no_change     = NULL;
+	class->model_changed       = NULL;
+	class->model_row_changed   = NULL;
+	class->model_cell_changed  = NULL;
+	class->model_rows_inserted = NULL;
+	class->model_rows_deleted  = NULL;
+}
+
+static void
+e_table_model_init (ETableModel *e_table_model)
+{
+	/* nothing to do */
+}
+
+#if d(!)0
+static void
+print_tabs (void)
+{
+	gint i;
+	for (i = 0; i < depth; i++)
+		g_print ("\t");
+}
+#endif
+
+void
+e_table_model_pre_change (ETableModel *e_table_model)
+{
+	g_return_if_fail (E_IS_TABLE_MODEL (e_table_model));
+
+	if (ETM_FROZEN (e_table_model))
+		return;
+
+	d (print_tabs ());
+	d (depth++);
+	g_signal_emit (e_table_model, signals[MODEL_PRE_CHANGE], 0);
+	d (depth--);
+}
+
+/**
+ * e_table_model_no_change:
+ * @e_table_model: the table model to notify of the lack of a change
+ *
+ * Use this function to notify any views of this table model that
+ * the contents of the table model have changed.  This will emit
+ * the signal "model_no_change" on the @e_table_model object.
+ *
+ * It is preferable to use the e_table_model_row_changed() and
+ * the e_table_model_cell_changed() to notify of smaller changes
+ * than to invalidate the entire model, as the views might have
+ * ways of caching the information they render from the model.
+ */
+void
+e_table_model_no_change (ETableModel *e_table_model)
+{
+	g_return_if_fail (E_IS_TABLE_MODEL (e_table_model));
+
+	if (ETM_FROZEN (e_table_model))
+		return;
+
+	d (print_tabs ());
+	d (depth++);
+	g_signal_emit (e_table_model, signals[MODEL_NO_CHANGE], 0);
+	d (depth--);
+}
+
+/**
+ * e_table_model_changed:
+ * @e_table_model: the table model to notify of the change
+ *
+ * Use this function to notify any views of this table model that
+ * the contents of the table model have changed.  This will emit
+ * the signal "model_changed" on the @e_table_model object.
+ *
+ * It is preferable to use the e_table_model_row_changed() and
+ * the e_table_model_cell_changed() to notify of smaller changes
+ * than to invalidate the entire model, as the views might have
+ * ways of caching the information they render from the model.
+ */
+void
+e_table_model_changed (ETableModel *e_table_model)
+{
+	g_return_if_fail (E_IS_TABLE_MODEL (e_table_model));
+
+	if (ETM_FROZEN (e_table_model))
+		return;
+
+	d (print_tabs ());
+	d (depth++);
+	g_signal_emit (e_table_model, signals[MODEL_CHANGED], 0);
+	d (depth--);
+}
+
+/**
+ * e_table_model_row_changed:
+ * @e_table_model: the table model to notify of the change
+ * @row: the row that was changed in the model.
+ *
+ * Use this function to notify any views of the table model that
+ * the contents of row @row have changed in model.  This function
+ * will emit the "model_row_changed" signal on the @e_table_model
+ * object
+ */
+void
+e_table_model_row_changed (ETableModel *e_table_model,
+                           gint row)
+{
+	g_return_if_fail (E_IS_TABLE_MODEL (e_table_model));
+
+	if (ETM_FROZEN (e_table_model))
+		return;
+
+	d (print_tabs ());
+	d (depth++);
+	g_signal_emit (e_table_model, signals[MODEL_ROW_CHANGED], 0, row);
+	d (depth--);
+}
+
+/**
+ * e_table_model_cell_changed:
+ * @e_table_model: the table model to notify of the change
+ * @col: the column.
+ * @row: the row
+ *
+ * Use this function to notify any views of the table model that
+ * contents of the cell at @col,@row has changed. This will emit
+ * the "model_cell_changed" signal on the @e_table_model
+ * object
+ */
+void
+e_table_model_cell_changed (ETableModel *e_table_model,
+                            gint col,
+                            gint row)
+{
+	g_return_if_fail (E_IS_TABLE_MODEL (e_table_model));
+
+	if (ETM_FROZEN (e_table_model))
+		return;
+
+	d (print_tabs ());
+	d (depth++);
+	g_signal_emit (
+		e_table_model, signals[MODEL_CELL_CHANGED], 0, col, row);
+	d (depth--);
+}
+
+/**
+ * e_table_model_rows_inserted:
+ * @e_table_model: the table model to notify of the change
+ * @row: the row that was inserted into the model.
+ * @count: The number of rows that were inserted.
+ *
+ * Use this function to notify any views of the table model that
+ * @count rows at row @row have been inserted into the model.  This
+ * function will emit the "model_rows_inserted" signal on the
+ * @e_table_model object
+ */
+void
+e_table_model_rows_inserted (ETableModel *e_table_model,
+                             gint row,
+                             gint count)
+{
+	g_return_if_fail (E_IS_TABLE_MODEL (e_table_model));
+
+	if (ETM_FROZEN (e_table_model))
+		return;
+
+	d (print_tabs ());
+	d (depth++);
+	g_signal_emit (
+		e_table_model, signals[MODEL_ROWS_INSERTED], 0, row, count);
+	d (depth--);
+}
+
+/**
+ * e_table_model_row_inserted:
+ * @e_table_model: the table model to notify of the change
+ * @row: the row that was inserted into the model.
+ *
+ * Use this function to notify any views of the table model that the
+ * row @row has been inserted into the model.  This function will emit
+ * the "model_rows_inserted" signal on the @e_table_model object
+ */
+void
+e_table_model_row_inserted (ETableModel *e_table_model,
+                            gint row)
+{
+	e_table_model_rows_inserted (e_table_model, row, 1);
+}
+
+/**
+ * e_table_model_row_deleted:
+ * @e_table_model: the table model to notify of the change
+ * @row: the row that was deleted
+ * @count: The number of rows deleted
+ *
+ * Use this function to notify any views of the table model that
+ * @count rows at row @row have been deleted from the model.  This
+ * function will emit the "model_rows_deleted" signal on the
+ * @e_table_model object
+ */
+void
+e_table_model_rows_deleted (ETableModel *e_table_model,
+                            gint row,
+                            gint count)
+{
+	g_return_if_fail (E_IS_TABLE_MODEL (e_table_model));
+
+	if (ETM_FROZEN (e_table_model))
+		return;
+
+	d (print_tabs ());
+	d (depth++);
+	g_signal_emit (
+		e_table_model, signals[MODEL_ROWS_DELETED], 0, row, count);
+	d (depth--);
+}
+
+/**
+ * e_table_model_row_deleted:
+ * @e_table_model: the table model to notify of the change
+ * @row: the row that was deleted
+ *
+ * Use this function to notify any views of the table model that the
+ * row @row has been deleted from the model.  This function will emit
+ * the "model_rows_deleted" signal on the @e_table_model object
+ */
+void
+e_table_model_row_deleted (ETableModel *e_table_model,
+                           gint row)
+{
+	e_table_model_rows_deleted (e_table_model, row, 1);
+}
+
+void
+e_table_model_freeze (ETableModel *e_table_model)
+{
+	e_table_model_pre_change (e_table_model);
+
+	/* FIXME This expression is awesome! */
+	g_object_set_data (
+		G_OBJECT (e_table_model), "frozen",
+		GINT_TO_POINTER (GPOINTER_TO_INT (
+		g_object_get_data (G_OBJECT (e_table_model), "frozen")) + 1));
+}
+
+void
+e_table_model_thaw (ETableModel *e_table_model)
+{
+	/* FIXME This expression is awesome! */
+	g_object_set_data (
+		G_OBJECT (e_table_model), "frozen",
+		GINT_TO_POINTER (GPOINTER_TO_INT (
+		g_object_get_data (G_OBJECT (e_table_model), "frozen")) - 1));
+
+	e_table_model_changed (e_table_model);
+}
+
diff --git a/e-util/e-table-model.h b/e-util/e-table-model.h
new file mode 100644
index 0000000..3ed188e
--- /dev/null
+++ b/e-util/e-table-model.h
@@ -0,0 +1,217 @@
+/*
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef _E_TABLE_MODEL_H_
+#define _E_TABLE_MODEL_H_
+
+#include <glib-object.h>
+
+/* Standard GObject macros */
+#define E_TYPE_TABLE_MODEL \
+	(e_table_model_get_type ())
+#define E_TABLE_MODEL(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_TABLE_MODEL, ETableModel))
+#define E_TABLE_MODEL_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_TABLE_MODEL, ETableModelClass))
+#define E_IS_TABLE_MODEL(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_TABLE_MODEL))
+#define E_IS_TABLE_MODEL_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_TABLE_MODEL))
+#define E_TABLE_MODEL_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_TABLE_MODEL, ETableModelClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ETableModel ETableModel;
+typedef struct _ETableModelClass ETableModelClass;
+
+struct _ETableModel {
+	GObject parent;
+};
+
+struct _ETableModelClass {
+	GObjectClass parent_class;
+
+	/*
+	 * Virtual methods
+	 */
+	gint		(*column_count)		(ETableModel *etm);
+	gint		(*row_count)		(ETableModel *etm);
+	void		(*append_row)		(ETableModel *etm,
+						 ETableModel *source,
+						 gint row);
+
+	gpointer	(*value_at)		(ETableModel *etm,
+						 gint col,
+						 gint row);
+	void		(*set_value_at)		(ETableModel *etm,
+						 gint col,
+						 gint row,
+						 gconstpointer value);
+	gboolean	(*is_cell_editable)	(ETableModel *etm,
+						 gint col,
+						 gint row);
+
+	gboolean	(*has_save_id)		(ETableModel *etm);
+	gchar *		(*get_save_id)		(ETableModel *etm,
+						 gint row);
+
+	gboolean	(*has_change_pending)	(ETableModel *etm);
+
+	/* Allocate a copy of the given value. */
+	gpointer	(*duplicate_value)	(ETableModel *etm,
+						 gint col,
+						 gconstpointer value);
+	/* Free an allocated value. */
+	void		(*free_value)		(ETableModel *etm,
+						 gint col,
+						 gpointer value);
+	/* Return an allocated empty value. */
+	gpointer	(*initialize_value)	(ETableModel *etm,
+						 gint col);
+	/* Return TRUE if value is equivalent to an empty cell. */
+	gboolean	(*value_is_empty)	(ETableModel *etm,
+						 gint col,
+						 gconstpointer value);
+	/* Return an allocated string. */
+	gchar *		(*value_to_string)	(ETableModel *etm,
+						 gint col,
+						 gconstpointer value);
+
+	/*
+	 * Signals
+	 */
+
+	/*
+	 * These all come after the change has been made.
+	 * No changes, cancel pre_change: no_change
+	 * Major structural changes: model_changed
+	 * Changes only in a row: row_changed
+	 * Only changes in a cell: cell_changed
+	 * A row inserted: row_inserted
+	 * A row deleted: row_deleted
+	 */
+	void		(*model_pre_change)	(ETableModel *etm);
+
+	void		(*model_no_change)	(ETableModel *etm);
+	void		(*model_changed)	(ETableModel *etm);
+	void		(*model_row_changed)	(ETableModel *etm,
+						 gint row);
+	void		(*model_cell_changed)	(ETableModel *etm,
+						 gint col,
+						 gint row);
+	void		(*model_rows_inserted)	(ETableModel *etm,
+						 gint row,
+						 gint count);
+	void		(*model_rows_deleted)	(ETableModel *etm,
+						 gint row,
+						 gint count);
+};
+
+GType		e_table_model_get_type		(void) G_GNUC_CONST;
+
+/**/
+gint		e_table_model_column_count	(ETableModel *e_table_model);
+const gchar *	e_table_model_column_name	(ETableModel *e_table_model,
+						 gint col);
+gint		e_table_model_row_count		(ETableModel *e_table_model);
+void		e_table_model_append_row	(ETableModel *e_table_model,
+						 ETableModel *source,
+						 gint row);
+
+/**/
+gpointer	e_table_model_value_at		(ETableModel *e_table_model,
+						 gint col,
+						 gint row);
+void		e_table_model_set_value_at	(ETableModel *e_table_model,
+						 gint col,
+						 gint row,
+						 gconstpointer value);
+gboolean	e_table_model_is_cell_editable	(ETableModel *e_table_model,
+						gint col,
+						 gint row);
+
+/**/
+gboolean	e_table_model_has_save_id	(ETableModel *etm);
+gchar *		e_table_model_get_save_id	(ETableModel *etm,
+						 gint row);
+
+/**/
+gboolean	e_table_model_has_change_pending
+						(ETableModel *etm);
+
+/**/
+gpointer	e_table_model_duplicate_value	(ETableModel *e_table_model,
+						 gint col,
+						 gconstpointer value);
+void		e_table_model_free_value	(ETableModel *e_table_model,
+						 gint col,
+						 gpointer value);
+gpointer	e_table_model_initialize_value	(ETableModel *e_table_model,
+						 gint col);
+gboolean	e_table_model_value_is_empty	(ETableModel *e_table_model,
+						 gint col,
+						 gconstpointer value);
+gchar *		e_table_model_value_to_string	(ETableModel *e_table_model,
+						 gint col,
+						 gconstpointer value);
+
+/*
+ * Routines for emitting signals on the e_table
+ */
+void		e_table_model_pre_change	(ETableModel *e_table_model);
+void		e_table_model_no_change		(ETableModel *e_table_model);
+void		e_table_model_changed		(ETableModel *e_table_model);
+void		e_table_model_row_changed	(ETableModel *e_table_model,
+						 gint row);
+void		e_table_model_cell_changed	(ETableModel *e_table_model,
+						 gint col,
+						 gint row);
+void		e_table_model_rows_inserted	(ETableModel *e_table_model,
+						 gint row,
+						 gint count);
+void		e_table_model_rows_deleted	(ETableModel *e_table_model,
+						 gint row,
+						 gint count);
+
+/**/
+void		e_table_model_row_inserted	(ETableModel *e_table_model,
+						 gint row);
+void		e_table_model_row_deleted	(ETableModel *e_table_model,
+						 gint row);
+
+void		e_table_model_freeze		(ETableModel *e_table_model);
+void		e_table_model_thaw		(ETableModel *e_table_model);
+
+G_END_DECLS
+
+#endif /* _E_TABLE_MODEL_H_ */
diff --git a/e-util/e-table-one.c b/e-util/e-table-one.c
new file mode 100644
index 0000000..db9c27e
--- /dev/null
+++ b/e-util/e-table-one.c
@@ -0,0 +1,252 @@
+/*
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-table-one.h"
+
+G_DEFINE_TYPE (ETableOne, e_table_one, E_TYPE_TABLE_MODEL)
+
+static gint
+one_column_count (ETableModel *etm)
+{
+	ETableOne *one = E_TABLE_ONE (etm);
+
+	if (one->source)
+		return e_table_model_column_count (one->source);
+	else
+		return 0;
+}
+
+static gint
+one_row_count (ETableModel *etm)
+{
+	return 1;
+}
+
+static gpointer
+one_value_at (ETableModel *etm,
+              gint col,
+              gint row)
+{
+	ETableOne *one = E_TABLE_ONE (etm);
+
+	if (one->data)
+		return one->data[col];
+	else
+		return NULL;
+}
+
+static void
+one_set_value_at (ETableModel *etm,
+                  gint col,
+                  gint row,
+                  gconstpointer val)
+{
+	ETableOne *one = E_TABLE_ONE (etm);
+
+	if (one->data && one->source) {
+		e_table_model_free_value (one->source, col, one->data[col]);
+		one->data[col] = e_table_model_duplicate_value (one->source, col, val);
+	}
+}
+
+static gboolean
+one_is_cell_editable (ETableModel *etm,
+                      gint col,
+                      gint row)
+{
+	ETableOne *one = E_TABLE_ONE (etm);
+
+	if (one->source)
+		return e_table_model_is_cell_editable (one->source, col, -1);
+	else
+		return FALSE;
+}
+
+/* The default for one_duplicate_value is to return the raw value. */
+static gpointer
+one_duplicate_value (ETableModel *etm,
+                     gint col,
+                     gconstpointer value)
+{
+	ETableOne *one = E_TABLE_ONE (etm);
+
+	if (one->source)
+		return e_table_model_duplicate_value (one->source, col, value);
+	else
+		return (gpointer) value;
+}
+
+static void
+one_free_value (ETableModel *etm,
+                gint col,
+                gpointer value)
+{
+	ETableOne *one = E_TABLE_ONE (etm);
+
+	if (one->source)
+		e_table_model_free_value (one->source, col, value);
+}
+
+static gpointer
+one_initialize_value (ETableModel *etm,
+                      gint col)
+{
+	ETableOne *one = E_TABLE_ONE (etm);
+
+	if (one->source)
+		return e_table_model_initialize_value (one->source, col);
+	else
+		return NULL;
+}
+
+static gboolean
+one_value_is_empty (ETableModel *etm,
+                    gint col,
+                    gconstpointer value)
+{
+	ETableOne *one = E_TABLE_ONE (etm);
+
+	if (one->source)
+		return e_table_model_value_is_empty (one->source, col, value);
+	else
+		return FALSE;
+}
+
+static gchar *
+one_value_to_string (ETableModel *etm,
+                     gint col,
+                     gconstpointer value)
+{
+	ETableOne *one = E_TABLE_ONE (etm);
+
+	if (one->source)
+		return e_table_model_value_to_string (one->source, col, value);
+	else
+		return g_strdup ("");
+}
+
+static void
+one_finalize (GObject *object)
+{
+	G_OBJECT_CLASS (e_table_one_parent_class)->finalize (object);
+}
+
+static void
+one_dispose (GObject *object)
+{
+	ETableOne *one = E_TABLE_ONE (object);
+
+	if (one->data) {
+		gint i;
+		gint col_count;
+
+		if (one->source) {
+			col_count = e_table_model_column_count (one->source);
+
+			for (i = 0; i < col_count; i++)
+				e_table_model_free_value (one->source, i, one->data[i]);
+		}
+
+		g_free (one->data);
+	}
+	one->data = NULL;
+
+	if (one->source)
+		g_object_unref (one->source);
+	one->source = NULL;
+
+	G_OBJECT_CLASS (e_table_one_parent_class)->dispose (object);
+}
+
+static void
+e_table_one_class_init (ETableOneClass *class)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (class);
+	ETableModelClass *model_class = E_TABLE_MODEL_CLASS (class);
+
+	model_class->column_count = one_column_count;
+	model_class->row_count = one_row_count;
+	model_class->value_at = one_value_at;
+	model_class->set_value_at = one_set_value_at;
+	model_class->is_cell_editable = one_is_cell_editable;
+	model_class->duplicate_value = one_duplicate_value;
+	model_class->free_value = one_free_value;
+	model_class->initialize_value = one_initialize_value;
+	model_class->value_is_empty = one_value_is_empty;
+	model_class->value_to_string = one_value_to_string;
+
+	object_class->dispose = one_dispose;
+	object_class->finalize = one_finalize;
+}
+
+static void
+e_table_one_init (ETableOne *one)
+{
+	one->source = NULL;
+	one->data = NULL;
+}
+
+ETableModel *
+e_table_one_new (ETableModel *source)
+{
+	ETableOne *eto;
+	gint col_count;
+	gint i;
+
+	eto = g_object_new (E_TYPE_TABLE_ONE, NULL);
+	eto->source = source;
+
+	col_count = e_table_model_column_count (source);
+	eto->data = g_new (gpointer , col_count);
+	for (i = 0; i < col_count; i++) {
+		eto->data[i] = e_table_model_initialize_value (source, i);
+	}
+
+	if (source)
+		g_object_ref (source);
+
+	return (ETableModel *) eto;
+}
+
+void
+e_table_one_commit (ETableOne *one)
+{
+	if (one->source) {
+		gint empty = TRUE;
+		gint col;
+		gint cols = e_table_model_column_count (one->source);
+		for (col = 0; col < cols; col++) {
+			if (!e_table_model_value_is_empty (one->source, col, one->data[col])) {
+				empty = FALSE;
+				break;
+			}
+		}
+		if (!empty) {
+			e_table_model_append_row (one->source, E_TABLE_MODEL (one), 0);
+		}
+	}
+}
diff --git a/e-util/e-table-one.h b/e-util/e-table-one.h
new file mode 100644
index 0000000..86f5538
--- /dev/null
+++ b/e-util/e-table-one.h
@@ -0,0 +1,75 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef _E_TABLE_ONE_H_
+#define _E_TABLE_ONE_H_
+
+#include <e-util/e-table-model.h>
+
+/* Standard GObject macros */
+#define E_TYPE_TABLE_ONE \
+	(e_table_one_get_type ())
+#define E_TABLE_ONE(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_TABLE_ONE, ETableOne))
+#define E_TABLE_ONE_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_TABLE_ONE, ETableOneClass))
+#define E_IS_TABLE_ONE(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_TABLE_ONE))
+#define E_IS_TABLE_ONE_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_TABLE_ONE))
+#define E_TABLE_ONE_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_TABLE_ONE, ETableOneClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ETableOne ETableOne;
+typedef struct _ETableOneClass ETableOneClass;
+
+struct _ETableOne {
+	ETableModel parent;
+
+	ETableModel  *source;
+	gpointer *data;
+};
+
+struct _ETableOneClass {
+	ETableModelClass parent_class;
+};
+
+GType		e_table_one_get_type		(void) G_GNUC_CONST;
+
+ETableModel *	e_table_one_new			(ETableModel *source);
+void		e_table_one_commit		(ETableOne *one);
+
+G_END_DECLS
+
+#endif /* _E_TABLE_ONE_H_ */
+
diff --git a/e-util/e-table-search.c b/e-util/e-table-search.c
new file mode 100644
index 0000000..5b6a7bd
--- /dev/null
+++ b/e-util/e-table-search.c
@@ -0,0 +1,235 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-table-search.h"
+
+#include <string.h>
+
+#include "e-marshal.h"
+
+#define d(x)
+
+struct _ETableSearchPrivate {
+	guint timeout_id;
+
+	gchar *search_string;
+	gunichar last_character;
+};
+
+G_DEFINE_TYPE (ETableSearch, e_table_search, G_TYPE_OBJECT)
+
+enum {
+	SEARCH_SEARCH,
+	SEARCH_ACCEPT,
+	LAST_SIGNAL
+};
+
+#define E_TABLE_SEARCH_GET_PRIVATE(obj) \
+	(G_TYPE_INSTANCE_GET_PRIVATE \
+	((obj), E_TYPE_TABLE_SEARCH, ETableSearchPrivate))
+
+d (static gint depth = 0)
+
+static guint e_table_search_signals[LAST_SIGNAL] = { 0, };
+
+static gboolean
+e_table_search_search (ETableSearch *e_table_search,
+                       gchar *string,
+                       ETableSearchFlags flags)
+{
+	gboolean ret_val;
+	g_return_val_if_fail (E_IS_TABLE_SEARCH (e_table_search), FALSE);
+
+	g_signal_emit (
+		e_table_search,
+		e_table_search_signals[SEARCH_SEARCH], 0,
+		string, flags, &ret_val);
+
+	return ret_val;
+}
+
+static void
+e_table_search_accept (ETableSearch *e_table_search)
+{
+	g_return_if_fail (E_IS_TABLE_SEARCH (e_table_search));
+
+	g_signal_emit (
+		e_table_search,
+		e_table_search_signals[SEARCH_ACCEPT], 0);
+}
+
+static gboolean
+ets_accept (gpointer data)
+{
+	ETableSearch *ets = data;
+	e_table_search_accept (ets);
+	g_free (ets->priv->search_string);
+
+	ets->priv->timeout_id = 0;
+	ets->priv->search_string = g_strdup ("");
+	ets->priv->last_character = 0;
+
+	return FALSE;
+}
+
+static void
+drop_timeout (ETableSearch *ets)
+{
+	if (ets->priv->timeout_id) {
+		g_source_remove (ets->priv->timeout_id);
+	}
+	ets->priv->timeout_id = 0;
+}
+
+static void
+add_timeout (ETableSearch *ets)
+{
+	drop_timeout (ets);
+	ets->priv->timeout_id = g_timeout_add_seconds (1, ets_accept, ets);
+}
+
+static void
+e_table_search_finalize (GObject *object)
+{
+	ETableSearchPrivate *priv;
+
+	priv = E_TABLE_SEARCH_GET_PRIVATE (object);
+
+	drop_timeout (E_TABLE_SEARCH (object));
+
+	g_free (priv->search_string);
+
+	/* Chain up to parent's finalize() method. */
+	G_OBJECT_CLASS (e_table_search_parent_class)->finalize (object);
+}
+
+static void
+e_table_search_class_init (ETableSearchClass *class)
+{
+	GObjectClass *object_class;
+
+	g_type_class_add_private (class, sizeof (ETableSearchPrivate));
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->finalize = e_table_search_finalize;
+
+	e_table_search_signals[SEARCH_SEARCH] = g_signal_new (
+		"search",
+		G_TYPE_FROM_CLASS (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ETableSearchClass, search),
+		(GSignalAccumulator) NULL, NULL,
+		e_marshal_BOOLEAN__STRING_INT,
+		G_TYPE_BOOLEAN, 2,
+		G_TYPE_STRING,
+		G_TYPE_INT);
+
+	e_table_search_signals[SEARCH_ACCEPT] = g_signal_new (
+		"accept",
+		G_TYPE_FROM_CLASS (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ETableSearchClass, accept),
+		(GSignalAccumulator) NULL, NULL,
+		g_cclosure_marshal_VOID__VOID,
+		G_TYPE_NONE, 0);
+
+	class->search = NULL;
+	class->accept = NULL;
+}
+
+static void
+e_table_search_init (ETableSearch *ets)
+{
+	ets->priv = E_TABLE_SEARCH_GET_PRIVATE (ets);
+
+	ets->priv->search_string = g_strdup ("");
+}
+
+ETableSearch *
+e_table_search_new (void)
+{
+	return g_object_new (E_TYPE_TABLE_SEARCH, NULL);
+}
+
+/**
+ * e_table_search_column_count:
+ * @e_table_search: The e-table-search to operate on
+ *
+ * Returns: the number of columns in the table search.
+ */
+void
+e_table_search_input_character (ETableSearch *ets,
+                                gunichar character)
+{
+	gchar character_utf8[7];
+	gchar *temp_string;
+
+	g_return_if_fail (ets != NULL);
+	g_return_if_fail (E_IS_TABLE_SEARCH (ets));
+
+	character_utf8[g_unichar_to_utf8 (character, character_utf8)] = 0;
+
+	temp_string = g_strdup_printf ("%s%s", ets->priv->search_string, character_utf8);
+	if (e_table_search_search (
+			ets, temp_string,
+			ets->priv->last_character != 0 ?
+			E_TABLE_SEARCH_FLAGS_CHECK_CURSOR_FIRST : 0)) {
+		g_free (ets->priv->search_string);
+		ets->priv->search_string = temp_string;
+		add_timeout (ets);
+		ets->priv->last_character = character;
+		return;
+	} else {
+		g_free (temp_string);
+	}
+
+	if (character == ets->priv->last_character) {
+		if (ets->priv->search_string &&
+			e_table_search_search (ets, ets->priv->search_string, 0)) {
+			add_timeout (ets);
+		}
+	}
+}
+
+gboolean
+e_table_search_backspace (ETableSearch *ets)
+{
+	gchar *end;
+
+	g_return_val_if_fail (ets != NULL, FALSE);
+	g_return_val_if_fail (E_IS_TABLE_SEARCH (ets), FALSE);
+
+	if (!ets->priv->search_string ||
+	    !*ets->priv->search_string)
+		return FALSE;
+
+	end = ets->priv->search_string + strlen (ets->priv->search_string);
+	end = g_utf8_prev_char (end);
+	*end = 0;
+	ets->priv->last_character = 0;
+	add_timeout (ets);
+	return TRUE;
+}
diff --git a/e-util/e-table-search.h b/e-util/e-table-search.h
new file mode 100644
index 0000000..1348e64
--- /dev/null
+++ b/e-util/e-table-search.h
@@ -0,0 +1,86 @@
+/*
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef _E_TABLE_SEARCH_H_
+#define _E_TABLE_SEARCH_H_
+
+#include <glib-object.h>
+
+/* Standard GObject macros */
+#define E_TYPE_TABLE_SEARCH \
+	(e_table_search_get_type ())
+#define E_TABLE_SEARCH(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_TABLE_SEARCH, ETableSearch))
+#define E_TABLE_SEARCH_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_TABLE_SEARCH, ETableSearchClass))
+#define E_IS_TABLE_SEARCH(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_TABLE_SEARCH))
+#define E_IS_TABLE_SEARCH_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_TABLE_SEARCH))
+#define E_TABLE_SEARCH_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_TABLE_SEARCH, ETableSearchClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ETableSearch ETableSearch;
+typedef struct _ETableSearchClass ETableSearchClass;
+typedef struct _ETableSearchPrivate ETableSearchPrivate;
+
+typedef enum {
+	E_TABLE_SEARCH_FLAGS_CHECK_CURSOR_FIRST = 1 << 0
+} ETableSearchFlags;
+
+struct _ETableSearch {
+	GObject parent;
+	ETableSearchPrivate *priv;
+};
+
+struct _ETableSearchClass {
+	GObjectClass parent_class;
+
+	/* Signals */
+	gboolean	(*search)		(ETableSearch *ets,
+						 gchar *string /* utf8 */,
+						 ETableSearchFlags flags);
+	void		(*accept)		(ETableSearch *ets);
+};
+
+GType		e_table_search_get_type		(void) G_GNUC_CONST;
+ETableSearch *	e_table_search_new		(void);
+void		e_table_search_input_character	(ETableSearch *e_table_search,
+						 gunichar character);
+gboolean	e_table_search_backspace	(ETableSearch *e_table_search);
+void		e_table_search_cancel		(ETableSearch *e_table_search);
+
+G_END_DECLS
+
+#endif /* _E_TABLE_SEARCH_H_ */
diff --git a/e-util/e-table-selection-model.c b/e-util/e-table-selection-model.c
new file mode 100644
index 0000000..abe4b0c
--- /dev/null
+++ b/e-util/e-table-selection-model.c
@@ -0,0 +1,384 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+
+#include <gdk/gdkkeysyms.h>
+
+#include <glib/gi18n.h>
+
+#include "e-table-selection-model.h"
+
+G_DEFINE_TYPE (ETableSelectionModel, e_table_selection_model, E_SELECTION_MODEL_ARRAY_TYPE)
+
+static gint etsm_get_row_count (ESelectionModelArray *esm);
+
+enum {
+	PROP_0,
+	PROP_MODEL,
+	PROP_HEADER
+};
+
+static void
+save_to_hash (gint model_row,
+              gpointer closure)
+{
+	ETableSelectionModel *etsm = closure;
+	const gchar *key = e_table_model_get_save_id (etsm->model, model_row);
+
+	g_hash_table_insert (etsm->hash, (gpointer) key, (gpointer) key);
+}
+
+static void
+free_hash (ETableSelectionModel *etsm)
+{
+	if (etsm->hash) {
+		g_hash_table_destroy (etsm->hash);
+		etsm->hash = NULL;
+	}
+	if (etsm->cursor_id)
+		g_free (etsm->cursor_id);
+	etsm->cursor_id = NULL;
+}
+
+static void
+model_pre_change (ETableModel *etm,
+                  ETableSelectionModel *etsm)
+{
+	free_hash (etsm);
+
+	if (etsm->model && e_table_model_has_save_id (etsm->model)) {
+		gint cursor_row;
+
+		etsm->hash = g_hash_table_new_full (
+			g_str_hash, g_str_equal,
+			(GDestroyNotify) g_free,
+			(GDestroyNotify) NULL);
+		e_selection_model_foreach (E_SELECTION_MODEL (etsm), save_to_hash, etsm);
+
+		g_object_get (
+			etsm,
+			"cursor_row", &cursor_row,
+			NULL);
+		g_free (etsm->cursor_id);
+		if (cursor_row != -1)
+			etsm->cursor_id = e_table_model_get_save_id (etm, cursor_row);
+		else
+			etsm->cursor_id = NULL;
+	}
+}
+
+static gint
+model_changed_idle (ETableSelectionModel *etsm)
+{
+	ETableModel *etm = etsm->model;
+
+	e_selection_model_clear (E_SELECTION_MODEL (etsm));
+
+	if (etsm->cursor_id && etm && e_table_model_has_save_id (etm)) {
+		gint row_count = e_table_model_row_count (etm);
+		gint cursor_row = -1;
+		gint cursor_col = -1;
+		gint i;
+		e_selection_model_array_confirm_row_count (E_SELECTION_MODEL_ARRAY (etsm));
+		for (i = 0; i < row_count; i++) {
+			gchar *save_id = e_table_model_get_save_id (etm, i);
+			if (g_hash_table_lookup (etsm->hash, save_id))
+				e_selection_model_change_one_row (E_SELECTION_MODEL (etsm), i, TRUE);
+
+			if (etsm->cursor_id && !strcmp (etsm->cursor_id, save_id)) {
+				cursor_row = i;
+				cursor_col = e_selection_model_cursor_col (E_SELECTION_MODEL (etsm));
+				if (cursor_col == -1) {
+					if (etsm->eth) {
+						cursor_col = e_table_header_prioritized_column (etsm->eth);
+					} else
+						cursor_col = 0;
+				}
+				e_selection_model_change_cursor (E_SELECTION_MODEL (etsm), cursor_row, cursor_col);
+				g_free (etsm->cursor_id);
+				etsm->cursor_id = NULL;
+			}
+			g_free (save_id);
+		}
+		free_hash (etsm);
+		e_selection_model_cursor_changed (E_SELECTION_MODEL (etsm), cursor_row, cursor_col);
+		e_selection_model_selection_changed (E_SELECTION_MODEL (etsm));
+	}
+	etsm->model_changed_idle_id = 0;
+	return FALSE;
+}
+
+static void
+model_changed (ETableModel *etm,
+               ETableSelectionModel *etsm)
+{
+	e_selection_model_clear (E_SELECTION_MODEL (etsm));
+	if (!etsm->model_changed_idle_id && etm && e_table_model_has_save_id (etm)) {
+		etsm->model_changed_idle_id = g_idle_add_full (G_PRIORITY_HIGH, (GSourceFunc) model_changed_idle, etsm, NULL);
+	}
+}
+
+static void
+model_row_changed (ETableModel *etm,
+                   gint row,
+                   ETableSelectionModel *etsm)
+{
+	free_hash (etsm);
+}
+
+static void
+model_cell_changed (ETableModel *etm,
+                    gint col,
+                    gint row,
+                    ETableSelectionModel *etsm)
+{
+	free_hash (etsm);
+}
+
+#if 1
+static void
+model_rows_inserted (ETableModel *etm,
+                     gint row,
+                     gint count,
+                     ETableSelectionModel *etsm)
+{
+	e_selection_model_array_insert_rows (E_SELECTION_MODEL_ARRAY (etsm), row, count);
+	free_hash (etsm);
+}
+
+static void
+model_rows_deleted (ETableModel *etm,
+                    gint row,
+                    gint count,
+                    ETableSelectionModel *etsm)
+{
+	e_selection_model_array_delete_rows (E_SELECTION_MODEL_ARRAY (etsm), row, count);
+	free_hash (etsm);
+}
+
+#else
+
+static void
+model_rows_inserted (ETableModel *etm,
+                     gint row,
+                     gint count,
+                     ETableSelectionModel *etsm)
+{
+	model_changed (etm, etsm);
+}
+
+static void
+model_rows_deleted (ETableModel *etm,
+                    gint row,
+                    gint count,
+                    ETableSelectionModel *etsm)
+{
+	model_changed (etm, etsm);
+}
+#endif
+
+inline static void
+add_model (ETableSelectionModel *etsm,
+           ETableModel *model)
+{
+	etsm->model = model;
+	if (model) {
+		g_object_ref (model);
+		etsm->model_pre_change_id = g_signal_connect (
+			model, "model_pre_change",
+			G_CALLBACK (model_pre_change), etsm);
+		etsm->model_changed_id = g_signal_connect (
+			model, "model_changed",
+			G_CALLBACK (model_changed), etsm);
+		etsm->model_row_changed_id = g_signal_connect (
+			model, "model_row_changed",
+			G_CALLBACK (model_row_changed), etsm);
+		etsm->model_cell_changed_id = g_signal_connect (
+			model, "model_cell_changed",
+			G_CALLBACK (model_cell_changed), etsm);
+		etsm->model_rows_inserted_id = g_signal_connect (
+			model, "model_rows_inserted",
+			G_CALLBACK (model_rows_inserted), etsm);
+		etsm->model_rows_deleted_id = g_signal_connect (
+			model, "model_rows_deleted",
+			G_CALLBACK (model_rows_deleted), etsm);
+	}
+	e_selection_model_array_confirm_row_count (E_SELECTION_MODEL_ARRAY (etsm));
+}
+
+inline static void
+drop_model (ETableSelectionModel *etsm)
+{
+	if (etsm->model) {
+		g_signal_handler_disconnect (
+			etsm->model,
+			etsm->model_pre_change_id);
+		g_signal_handler_disconnect (
+			etsm->model,
+			etsm->model_changed_id);
+		g_signal_handler_disconnect (
+			etsm->model,
+			etsm->model_row_changed_id);
+		g_signal_handler_disconnect (
+			etsm->model,
+			etsm->model_cell_changed_id);
+		g_signal_handler_disconnect (
+			etsm->model,
+			etsm->model_rows_inserted_id);
+		g_signal_handler_disconnect (
+			etsm->model,
+			etsm->model_rows_deleted_id);
+
+		g_object_unref (etsm->model);
+	}
+	etsm->model = NULL;
+}
+
+static void
+etsm_dispose (GObject *object)
+{
+	ETableSelectionModel *etsm;
+
+	etsm = E_TABLE_SELECTION_MODEL (object);
+
+	if (etsm->model_changed_idle_id)
+		g_source_remove (etsm->model_changed_idle_id);
+	etsm->model_changed_idle_id = 0;
+
+	drop_model (etsm);
+	free_hash (etsm);
+
+	/* Chain up to parent's dispose() method. */
+	G_OBJECT_CLASS (e_table_selection_model_parent_class)->dispose (object);
+}
+
+static void
+etsm_get_property (GObject *object,
+                   guint property_id,
+                   GValue *value,
+                   GParamSpec *pspec)
+{
+	ETableSelectionModel *etsm = E_TABLE_SELECTION_MODEL (object);
+
+	switch (property_id) {
+	case PROP_MODEL:
+		g_value_set_object (value, etsm->model);
+		break;
+	case PROP_HEADER:
+		g_value_set_object (value, etsm->eth);
+		break;
+	}
+}
+
+static void
+etsm_set_property (GObject *object,
+                   guint property_id,
+                   const GValue *value,
+                   GParamSpec *pspec)
+{
+	ETableSelectionModel *etsm = E_TABLE_SELECTION_MODEL (object);
+
+	switch (property_id) {
+	case PROP_MODEL:
+		drop_model (etsm);
+		add_model (etsm, g_value_get_object (value) ? E_TABLE_MODEL (g_value_get_object (value)) : NULL);
+		break;
+	case PROP_HEADER:
+		etsm->eth = E_TABLE_HEADER (g_value_get_object (value));
+		break;
+	}
+}
+
+static void
+e_table_selection_model_init (ETableSelectionModel *selection)
+{
+	selection->model = NULL;
+	selection->hash = NULL;
+	selection->cursor_id = NULL;
+
+	selection->model_changed_idle_id = 0;
+}
+
+static void
+e_table_selection_model_class_init (ETableSelectionModelClass *class)
+{
+	GObjectClass *object_class;
+	ESelectionModelArrayClass *esma_class;
+
+	object_class             = G_OBJECT_CLASS (class);
+	esma_class               = E_SELECTION_MODEL_ARRAY_CLASS (class);
+
+	object_class->dispose      = etsm_dispose;
+	object_class->get_property = etsm_get_property;
+	object_class->set_property = etsm_set_property;
+
+	esma_class->get_row_count = etsm_get_row_count;
+
+	g_object_class_install_property (
+		object_class,
+		PROP_MODEL,
+		g_param_spec_object (
+			"model",
+			"Model",
+			NULL,
+			E_TYPE_TABLE_MODEL,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_HEADER,
+		g_param_spec_object (
+			"header",
+			"Header",
+			NULL,
+			E_TYPE_TABLE_HEADER,
+			G_PARAM_READWRITE));
+}
+
+/**
+ * e_table_selection_model_new
+ *
+ * This routine creates a new #ETableSelectionModel.
+ *
+ * Returns: The new #ETableSelectionModel.
+ */
+ETableSelectionModel *
+e_table_selection_model_new (void)
+{
+	return g_object_new (E_TYPE_TABLE_SELECTION_MODEL, NULL);
+}
+
+static gint
+etsm_get_row_count (ESelectionModelArray *esma)
+{
+	ETableSelectionModel *etsm = E_TABLE_SELECTION_MODEL (esma);
+
+	if (etsm->model)
+		return e_table_model_row_count (etsm->model);
+	else
+		return 0;
+}
diff --git a/e-util/e-table-selection-model.h b/e-util/e-table-selection-model.h
new file mode 100644
index 0000000..0f955ad
--- /dev/null
+++ b/e-util/e-table-selection-model.h
@@ -0,0 +1,91 @@
+/*
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef _E_TABLE_SELECTION_MODEL_H_
+#define _E_TABLE_SELECTION_MODEL_H_
+
+#include <e-util/e-selection-model-array.h>
+#include <e-util/e-table-header.h>
+#include <e-util/e-table-model.h>
+
+/* Standard GObject macros */
+#define E_TYPE_TABLE_SELECTION_MODEL \
+	(e_table_selection_model_get_type ())
+#define E_TABLE_SELECTION_MODEL(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_TABLE_SELECTION_MODEL, ETableSelectionModel))
+#define E_TABLE_SELECTION_MODEL_CLASS(cls) \
+	(G_TYPE - CHECK_CLASS_CAST \
+	((cls), E_TYPE_TABLE_SELECTION_MODEL, ETableSelectionModelClass))
+#define E_IS_TABLE_SELECTION_MODEL(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_TABLE_SELECTION_MODEL))
+#define E_IS_TABLE_SELECTION_MODEL_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_TABLE_SELECTION_MODEL))
+#define E_TABLE_SELECTION_MODEL_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_TABLE_SELECTION_MODEL, ETableSelectionModelClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ETableSelectionModel ETableSelectionModel;
+typedef struct _ETableSelectionModelClass ETableSelectionModelClass;
+
+struct _ETableSelectionModel {
+	ESelectionModelArray parent;
+
+	ETableModel  *model;
+	ETableHeader *eth;
+
+	guint model_pre_change_id;
+	guint model_changed_id;
+	guint model_row_changed_id;
+	guint model_cell_changed_id;
+	guint model_rows_inserted_id;
+	guint model_rows_deleted_id;
+
+	guint model_changed_idle_id;
+
+	guint selection_model_changed : 1;
+	guint group_info_changed : 1;
+
+	GHashTable *hash;
+	gchar *cursor_id;
+};
+
+struct _ETableSelectionModelClass {
+	ESelectionModelArrayClass parent_class;
+};
+
+GType		e_table_selection_model_get_type	(void) G_GNUC_CONST;
+ETableSelectionModel *
+		e_table_selection_model_new		(void);
+
+G_END_DECLS
+
+#endif /* _E_TABLE_SELECTION_MODEL_H_ */
diff --git a/e-util/e-table-sort-info.c b/e-util/e-table-sort-info.c
new file mode 100644
index 0000000..d2654c5
--- /dev/null
+++ b/e-util/e-table-sort-info.c
@@ -0,0 +1,482 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-table-sort-info.h"
+
+#include <string.h>
+
+#include "e-xml-utils.h"
+
+#define ETM_CLASS(e) (E_TABLE_SORT_INFO_GET_CLASS (e))
+
+G_DEFINE_TYPE (ETableSortInfo , e_table_sort_info, G_TYPE_OBJECT)
+
+enum {
+	SORT_INFO_CHANGED,
+	GROUP_INFO_CHANGED,
+	LAST_SIGNAL
+};
+
+static guint e_table_sort_info_signals[LAST_SIGNAL] = { 0, };
+
+static void
+etsi_finalize (GObject *object)
+{
+	ETableSortInfo *etsi = E_TABLE_SORT_INFO (object);
+
+	if (etsi->groupings)
+		g_free (etsi->groupings);
+	etsi->groupings = NULL;
+
+	if (etsi->sortings)
+		g_free (etsi->sortings);
+	etsi->sortings = NULL;
+
+	G_OBJECT_CLASS (e_table_sort_info_parent_class)->finalize (object);
+}
+
+static void
+e_table_sort_info_init (ETableSortInfo *info)
+{
+	info->group_count = 0;
+	info->groupings = NULL;
+	info->sort_count = 0;
+	info->sortings = NULL;
+	info->frozen = 0;
+	info->sort_info_changed = 0;
+	info->group_info_changed = 0;
+	info->can_group = 1;
+}
+
+static void
+e_table_sort_info_class_init (ETableSortInfoClass *class)
+{
+	GObjectClass * object_class = G_OBJECT_CLASS (class);
+
+	object_class->finalize = etsi_finalize;
+
+	e_table_sort_info_signals[SORT_INFO_CHANGED] = g_signal_new (
+		"sort_info_changed",
+		G_TYPE_FROM_CLASS (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ETableSortInfoClass, sort_info_changed),
+		(GSignalAccumulator) NULL, NULL,
+		g_cclosure_marshal_VOID__VOID,
+		G_TYPE_NONE, 0);
+
+	e_table_sort_info_signals[GROUP_INFO_CHANGED] = g_signal_new (
+		"group_info_changed",
+		G_TYPE_FROM_CLASS (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ETableSortInfoClass, group_info_changed),
+		(GSignalAccumulator) NULL, NULL,
+		g_cclosure_marshal_VOID__VOID,
+		G_TYPE_NONE, 0);
+
+	class->sort_info_changed = NULL;
+	class->group_info_changed = NULL;
+}
+
+static void
+e_table_sort_info_sort_info_changed (ETableSortInfo *info)
+{
+	g_return_if_fail (info != NULL);
+	g_return_if_fail (E_IS_TABLE_SORT_INFO (info));
+
+	if (info->frozen) {
+		info->sort_info_changed = 1;
+	} else {
+		g_signal_emit (info, e_table_sort_info_signals[SORT_INFO_CHANGED], 0);
+	}
+}
+
+static void
+e_table_sort_info_group_info_changed (ETableSortInfo *info)
+{
+	g_return_if_fail (info != NULL);
+	g_return_if_fail (E_IS_TABLE_SORT_INFO (info));
+
+	if (info->frozen) {
+		info->group_info_changed = 1;
+	} else {
+		g_signal_emit (info, e_table_sort_info_signals[GROUP_INFO_CHANGED], 0);
+	}
+}
+
+/**
+ * e_table_sort_info_freeze:
+ * @info: The ETableSortInfo object
+ *
+ * This functions allows the programmer to cluster various changes to the
+ * ETableSortInfo (grouping and sorting) without having the object emit
+ * "group_info_changed" or "sort_info_changed" signals on each change.
+ *
+ * To thaw, invoke the e_table_sort_info_thaw() function, which will
+ * trigger any signals that might have been queued.
+ */
+void
+e_table_sort_info_freeze (ETableSortInfo *info)
+{
+	info->frozen++;
+}
+
+/**
+ * e_table_sort_info_thaw:
+ * @info: The ETableSortInfo object
+ *
+ * This functions allows the programmer to cluster various changes to the
+ * ETableSortInfo (grouping and sorting) without having the object emit
+ * "group_info_changed" or "sort_info_changed" signals on each change.
+ *
+ * This function will flush any pending signals that might be emited by
+ * this object.
+ */
+void
+e_table_sort_info_thaw (ETableSortInfo *info)
+{
+	info->frozen--;
+	if (info->frozen != 0)
+		return;
+
+	if (info->sort_info_changed) {
+		info->sort_info_changed = 0;
+		e_table_sort_info_sort_info_changed (info);
+	}
+	if (info->group_info_changed) {
+		info->group_info_changed = 0;
+		e_table_sort_info_group_info_changed (info);
+	}
+}
+
+/**
+ * e_table_sort_info_grouping_get_count:
+ * @info: The ETableSortInfo object
+ *
+ * Returns: the number of grouping criteria in the object.
+ */
+guint
+e_table_sort_info_grouping_get_count (ETableSortInfo *info)
+{
+	if (info->can_group)
+		return info->group_count;
+	else
+		return 0;
+}
+
+static void
+e_table_sort_info_grouping_real_truncate (ETableSortInfo *info,
+                                          gint length)
+{
+	if (length < info->group_count) {
+		info->group_count = length;
+	}
+	if (length > info->group_count) {
+		info->groupings = g_realloc (info->groupings, length * sizeof (ETableSortColumn));
+		info->group_count = length;
+	}
+}
+
+/**
+ * e_table_sort_info_grouping_truncate:
+ * @info: The ETableSortInfo object
+ * @lenght: position where the truncation happens.
+ *
+ * This routine can be used to reduce or grow the number of grouping
+ * criteria in the object.
+ */
+void
+e_table_sort_info_grouping_truncate (ETableSortInfo *info,
+                                     gint length)
+{
+	e_table_sort_info_grouping_real_truncate (info, length);
+	e_table_sort_info_group_info_changed (info);
+}
+
+/**
+ * e_table_sort_info_grouping_get_nth:
+ * @info: The ETableSortInfo object
+ * @n: Item information to fetch.
+ *
+ * Returns: the description of the @n-th grouping criteria in the @info object.
+ */
+ETableSortColumn
+e_table_sort_info_grouping_get_nth (ETableSortInfo *info,
+                                    gint n)
+{
+	if (info->can_group && n < info->group_count) {
+		return info->groupings[n];
+	} else {
+		ETableSortColumn fake = {0, 0};
+		return fake;
+	}
+}
+
+/**
+ * e_table_sort_info_grouping_set_nth:
+ * @info: The ETableSortInfo object
+ * @n: Item information to fetch.
+ * @column: new values for the grouping
+ *
+ * Sets the grouping criteria for index @n to be given by @column (a column number and
+ * whether it is ascending or descending).
+ */
+void
+e_table_sort_info_grouping_set_nth (ETableSortInfo *info,
+                                    gint n,
+                                    ETableSortColumn column)
+{
+	if (n >= info->group_count) {
+		e_table_sort_info_grouping_real_truncate (info, n + 1);
+	}
+	info->groupings[n] = column;
+	e_table_sort_info_group_info_changed (info);
+}
+
+/**
+ * e_table_sort_info_get_count:
+ * @info: The ETableSortInfo object
+ *
+ * Returns: the number of sorting criteria in the object.
+ */
+guint
+e_table_sort_info_sorting_get_count (ETableSortInfo *info)
+{
+	return info->sort_count;
+}
+
+static void
+e_table_sort_info_sorting_real_truncate (ETableSortInfo *info,
+                                         gint length)
+{
+	if (length < info->sort_count) {
+		info->sort_count = length;
+	}
+	if (length > info->sort_count) {
+		info->sortings = g_realloc (info->sortings, length * sizeof (ETableSortColumn));
+		info->sort_count = length;
+	}
+}
+
+/**
+ * e_table_sort_info_sorting_truncate:
+ * @info: The ETableSortInfo object
+ * @lenght: position where the truncation happens.
+ *
+ * This routine can be used to reduce or grow the number of sort
+ * criteria in the object.
+ */
+void
+e_table_sort_info_sorting_truncate (ETableSortInfo *info,
+                                    gint length)
+{
+	e_table_sort_info_sorting_real_truncate  (info, length);
+	e_table_sort_info_sort_info_changed (info);
+}
+
+/**
+ * e_table_sort_info_sorting_get_nth:
+ * @info: The ETableSortInfo object
+ * @n: Item information to fetch.
+ *
+ * Returns: the description of the @n-th grouping criteria in the @info object.
+ */
+ETableSortColumn
+e_table_sort_info_sorting_get_nth (ETableSortInfo *info,
+                                   gint n)
+{
+	if (n < info->sort_count) {
+		return info->sortings[n];
+	} else {
+		ETableSortColumn fake = {0, 0};
+		return fake;
+	}
+}
+
+/**
+ * e_table_sort_info_sorting_get_nth:
+ * @info: The ETableSortInfo object
+ * @n: Item information to fetch.
+ * @column: new values for the sorting
+ *
+ * Sets the sorting criteria for index @n to be given by @column (a
+ * column number and whether it is ascending or descending).
+ */
+void
+e_table_sort_info_sorting_set_nth (ETableSortInfo *info,
+                                   gint n,
+                                   ETableSortColumn column)
+{
+	if (n >= info->sort_count) {
+		e_table_sort_info_sorting_real_truncate (info, n + 1);
+	}
+	info->sortings[n] = column;
+	e_table_sort_info_sort_info_changed (info);
+}
+
+/**
+ * e_table_sort_info_new:
+ *
+ * This creates a new e_table_sort_info object that contains no
+ * grouping and no sorting defined as of yet.  This object is used
+ * to keep track of multi-level sorting and multi-level grouping of
+ * the ETable.
+ *
+ * Returns: A new %ETableSortInfo object
+ */
+ETableSortInfo *
+e_table_sort_info_new (void)
+{
+	return g_object_new (E_TYPE_TABLE_SORT_INFO, NULL);
+}
+
+/**
+ * e_table_sort_info_load_from_node:
+ * @info: The ETableSortInfo object
+ * @node: pointer to the xmlNode that describes the sorting and grouping information
+ * @state_version:
+ *
+ * This loads the state for the %ETableSortInfo object @info from the
+ * xml node @node.
+ */
+void
+e_table_sort_info_load_from_node (ETableSortInfo *info,
+                                  xmlNode *node,
+                                  gdouble state_version)
+{
+	gint i;
+	xmlNode *grouping;
+
+	if (state_version <= 0.05) {
+		i = 0;
+		for (grouping = node->xmlChildrenNode; grouping && !strcmp ((gchar *) grouping->name, "group"); grouping = grouping->xmlChildrenNode) {
+			ETableSortColumn column;
+			column.column = e_xml_get_integer_prop_by_name (grouping, (const guchar *)"column");
+			column.ascending = e_xml_get_bool_prop_by_name (grouping, (const guchar *)"ascending");
+			e_table_sort_info_grouping_set_nth (info, i++, column);
+		}
+		i = 0;
+		for (; grouping && !strcmp ((gchar *) grouping->name, "leaf"); grouping = grouping->xmlChildrenNode) {
+			ETableSortColumn column;
+			column.column = e_xml_get_integer_prop_by_name (grouping, (const guchar *)"column");
+			column.ascending = e_xml_get_bool_prop_by_name (grouping, (const guchar *)"ascending");
+			e_table_sort_info_sorting_set_nth (info, i++, column);
+		}
+	} else {
+		gint gcnt = 0;
+		gint scnt = 0;
+		for (grouping = node->children; grouping; grouping = grouping->next) {
+			ETableSortColumn column;
+
+			if (grouping->type != XML_ELEMENT_NODE)
+				continue;
+
+			if (!strcmp ((gchar *) grouping->name, "group")) {
+				column.column = e_xml_get_integer_prop_by_name (grouping, (const guchar *)"column");
+				column.ascending = e_xml_get_bool_prop_by_name (grouping, (const guchar *)"ascending");
+				e_table_sort_info_grouping_set_nth (info, gcnt++, column);
+			} else if (!strcmp ((gchar *) grouping->name, "leaf")) {
+				column.column = e_xml_get_integer_prop_by_name (grouping, (const guchar *)"column");
+				column.ascending = e_xml_get_bool_prop_by_name (grouping, (const guchar *)"ascending");
+				e_table_sort_info_sorting_set_nth (info, scnt++, column);
+			}
+		}
+	}
+	g_signal_emit (info, e_table_sort_info_signals[SORT_INFO_CHANGED], 0);
+}
+
+/**
+ * e_table_sort_info_save_to_node:
+ * @info: The ETableSortInfo object
+ * @parent: xmlNode that will be hosting the saved state of the @info object.
+ *
+ * This function is used
+ *
+ * Returns: the node that has been appended to @parent as a child containing
+ * the sorting and grouping information for this ETableSortInfo object.
+ */
+xmlNode *
+e_table_sort_info_save_to_node (ETableSortInfo *info,
+                                xmlNode *parent)
+{
+	xmlNode *grouping;
+	gint i;
+	const gint sort_count = e_table_sort_info_sorting_get_count (info);
+	const gint group_count = e_table_sort_info_grouping_get_count (info);
+
+	grouping = xmlNewChild (parent, NULL, (const guchar *)"grouping", NULL);
+
+	for (i = 0; i < group_count; i++) {
+		ETableSortColumn column = e_table_sort_info_grouping_get_nth (info, i);
+		xmlNode *new_node = xmlNewChild (grouping, NULL, (const guchar *)"group", NULL);
+
+		e_xml_set_integer_prop_by_name (new_node, (const guchar *)"column", column.column);
+		e_xml_set_bool_prop_by_name (new_node, (const guchar *)"ascending", column.ascending);
+	}
+
+	for (i = 0; i < sort_count; i++) {
+		ETableSortColumn column = e_table_sort_info_sorting_get_nth (info, i);
+		xmlNode *new_node = xmlNewChild (grouping, NULL, (const guchar *)"leaf", NULL);
+
+		e_xml_set_integer_prop_by_name (new_node, (const guchar *)"column", column.column);
+		e_xml_set_bool_prop_by_name (new_node, (const guchar *)"ascending", column.ascending);
+	}
+
+	return grouping;
+}
+
+ETableSortInfo *
+e_table_sort_info_duplicate (ETableSortInfo *info)
+{
+	ETableSortInfo *new_info;
+
+	new_info = e_table_sort_info_new ();
+
+	new_info->group_count = info->group_count;
+	new_info->groupings = g_new (ETableSortColumn, new_info->group_count);
+	memmove (new_info->groupings, info->groupings, sizeof (ETableSortColumn) * new_info->group_count);
+
+	new_info->sort_count = info->sort_count;
+	new_info->sortings = g_new (ETableSortColumn, new_info->sort_count);
+	memmove (new_info->sortings, info->sortings, sizeof (ETableSortColumn) * new_info->sort_count);
+
+	new_info->can_group = info->can_group;
+
+	return new_info;
+}
+
+void
+e_table_sort_info_set_can_group (ETableSortInfo *info,
+                                 gboolean can_group)
+{
+	info->can_group = can_group;
+}
+
+gboolean
+e_table_sort_info_get_can_group (ETableSortInfo *info)
+{
+	return info->can_group;
+}
+
diff --git a/e-util/e-table-sort-info.h b/e-util/e-table-sort-info.h
new file mode 100644
index 0000000..c56c5b0
--- /dev/null
+++ b/e-util/e-table-sort-info.h
@@ -0,0 +1,133 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef _E_TABLE_SORT_INFO_H_
+#define _E_TABLE_SORT_INFO_H_
+
+#include <glib-object.h>
+#include <libxml/tree.h>
+
+#define E_TYPE_TABLE_SORT_INFO \
+	(e_table_sort_info_get_type ())
+#define E_TABLE_SORT_INFO(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_TABLE_SORT_INFO, ETableSortInfo))
+#define E_TABLE_SORT_INFO_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_TABLE_SORT_INFO, ETableSortInfoClass))
+#define E_IS_TABLE_SORT_INFO(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_TABLE_SORT_INFO))
+#define E_IS_TABLE_SORT_INFO_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_TABLE_SORT_INFO))
+#define E_TABLE_SORT_INFO_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_TABLE_SORT_INFO, ETableSortInfoClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ETableSortColumn ETableSortColumn;
+
+typedef struct _ETableSortInfo ETableSortInfo;
+typedef struct _ETableSortInfoClass ETableSortInfoClass;
+
+struct _ETableSortColumn {
+	guint column : 31;
+	guint ascending : 1;
+};
+
+struct _ETableSortInfo {
+	GObject parent;
+
+	gint group_count;
+	ETableSortColumn *groupings;
+	gint sort_count;
+	ETableSortColumn *sortings;
+
+	guint frozen : 1;
+	guint sort_info_changed : 1;
+	guint group_info_changed : 1;
+
+	guint can_group : 1;
+};
+
+struct _ETableSortInfoClass {
+	GObjectClass parent_class;
+
+	/* Signals */
+	void		(*sort_info_changed)	(ETableSortInfo *info);
+	void		(*group_info_changed)	(ETableSortInfo *info);
+};
+
+GType		e_table_sort_info_get_type	(void) G_GNUC_CONST;
+
+void		e_table_sort_info_freeze	(ETableSortInfo *info);
+void		e_table_sort_info_thaw		(ETableSortInfo *info);
+
+guint		e_table_sort_info_grouping_get_count
+						(ETableSortInfo *info);
+void		e_table_sort_info_grouping_truncate
+						(ETableSortInfo *info,
+						 gint length);
+ETableSortColumn
+		e_table_sort_info_grouping_get_nth
+						(ETableSortInfo *info,
+						 gint n);
+void		e_table_sort_info_grouping_set_nth
+						(ETableSortInfo *info,
+						 gint n,
+						 ETableSortColumn column);
+
+guint		e_table_sort_info_sorting_get_count
+						(ETableSortInfo *info);
+void		e_table_sort_info_sorting_truncate
+						(ETableSortInfo *info,
+						 gint length);
+ETableSortColumn
+		e_table_sort_info_sorting_get_nth
+						(ETableSortInfo *info,
+						 gint n);
+void		e_table_sort_info_sorting_set_nth
+						(ETableSortInfo *info,
+						 gint n,
+						 ETableSortColumn column);
+
+ETableSortInfo *e_table_sort_info_new		(void);
+void		e_table_sort_info_load_from_node
+						(ETableSortInfo *info,
+						 xmlNode *node,
+						 gdouble state_version);
+xmlNode *	e_table_sort_info_save_to_node	(ETableSortInfo *info,
+						 xmlNode *parent);
+ETableSortInfo *e_table_sort_info_duplicate	(ETableSortInfo *info);
+void		e_table_sort_info_set_can_group	(ETableSortInfo *info,
+						 gboolean can_group);
+gboolean	e_table_sort_info_get_can_group (ETableSortInfo *info);
+
+G_END_DECLS
+
+#endif /* _E_TABLE_SORT_INFO_H_ */
diff --git a/e-util/e-table-sorted-variable.c b/e-util/e-table-sorted-variable.c
new file mode 100644
index 0000000..17c10d5
--- /dev/null
+++ b/e-util/e-table-sorted-variable.c
@@ -0,0 +1,235 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "e-table-sorted-variable.h"
+#include "e-table-sorting-utils.h"
+
+#define d(x)
+
+#define INCREMENT_AMOUNT 100
+
+/* maximum insertions between an idle event that we will do without scheduling an idle sort */
+#define ETSV_INSERT_MAX (4)
+
+/* workaround for avoiding API breakage */
+#define etsv_get_type e_table_sorted_variable_get_type
+G_DEFINE_TYPE (ETableSortedVariable, etsv, E_TYPE_TABLE_SUBSET_VARIABLE)
+
+static void etsv_sort_info_changed        (ETableSortInfo *info, ETableSortedVariable *etsv);
+static void etsv_sort                     (ETableSortedVariable *etsv);
+static void etsv_add                      (ETableSubsetVariable *etssv, gint                  row);
+static void etsv_add_all                  (ETableSubsetVariable *etssv);
+
+static void
+etsv_dispose (GObject *object)
+{
+	ETableSortedVariable *etsv = E_TABLE_SORTED_VARIABLE (object);
+
+	if (etsv->sort_info_changed_id)
+		g_signal_handler_disconnect (
+			etsv->sort_info,
+			etsv->sort_info_changed_id);
+	etsv->sort_info_changed_id = 0;
+
+	if (etsv->sort_idle_id) {
+		g_source_remove (etsv->sort_idle_id);
+		etsv->sort_idle_id = 0;
+	}
+	if (etsv->insert_idle_id) {
+		g_source_remove (etsv->insert_idle_id);
+		etsv->insert_idle_id = 0;
+	}
+
+	if (etsv->sort_info)
+		g_object_unref (etsv->sort_info);
+	etsv->sort_info = NULL;
+
+	if (etsv->full_header)
+		g_object_unref (etsv->full_header);
+	etsv->full_header = NULL;
+
+	G_OBJECT_CLASS (etsv_parent_class)->dispose (object);
+}
+
+static void
+etsv_class_init (ETableSortedVariableClass *class)
+{
+	ETableSubsetVariableClass *etssv_class = E_TABLE_SUBSET_VARIABLE_CLASS (class);
+	GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+	object_class->dispose = etsv_dispose;
+
+	etssv_class->add = etsv_add;
+	etssv_class->add_all = etsv_add_all;
+}
+
+static void
+etsv_init (ETableSortedVariable *etsv)
+{
+	etsv->full_header = NULL;
+	etsv->sort_info = NULL;
+
+	etsv->sort_info_changed_id = 0;
+
+	etsv->sort_idle_id = 0;
+	etsv->insert_count = 0;
+}
+
+static gboolean
+etsv_sort_idle (ETableSortedVariable *etsv)
+{
+	g_object_ref (etsv);
+	etsv_sort (etsv);
+	etsv->sort_idle_id = 0;
+	etsv->insert_count = 0;
+	g_object_unref (etsv);
+	return FALSE;
+}
+
+static gboolean
+etsv_insert_idle (ETableSortedVariable *etsv)
+{
+	etsv->insert_count = 0;
+	etsv->insert_idle_id = 0;
+	return FALSE;
+}
+
+static void
+etsv_add (ETableSubsetVariable *etssv,
+          gint row)
+{
+	ETableModel *etm = E_TABLE_MODEL (etssv);
+	ETableSubset *etss = E_TABLE_SUBSET (etssv);
+	ETableSortedVariable *etsv = E_TABLE_SORTED_VARIABLE (etssv);
+	gint i;
+
+	e_table_model_pre_change (etm);
+
+	if (etss->n_map + 1 > etssv->n_vals_allocated) {
+		etssv->n_vals_allocated += INCREMENT_AMOUNT;
+		etss->map_table = g_realloc (etss->map_table, (etssv->n_vals_allocated) * sizeof (gint));
+	}
+	i = etss->n_map;
+	if (etsv->sort_idle_id == 0) {
+		/* this is to see if we're inserting a lot of things between idle loops.
+		 * If we are, we're busy, its faster to just append and perform a full sort later */
+		etsv->insert_count++;
+		if (etsv->insert_count > ETSV_INSERT_MAX) {
+			/* schedule a sort, and append instead */
+			etsv->sort_idle_id = g_idle_add_full (50, (GSourceFunc) etsv_sort_idle, etsv, NULL);
+		} else {
+			/* make sure we have an idle handler to reset the count every now and then */
+			if (etsv->insert_idle_id == 0) {
+				etsv->insert_idle_id = g_idle_add_full (40, (GSourceFunc) etsv_insert_idle, etsv, NULL);
+			}
+			i = e_table_sorting_utils_insert (etss->source, etsv->sort_info, etsv->full_header, etss->map_table, etss->n_map, row);
+			memmove (etss->map_table + i + 1, etss->map_table + i, (etss->n_map - i) * sizeof (gint));
+		}
+	}
+	etss->map_table[i] = row;
+	etss->n_map++;
+
+	e_table_model_row_inserted (etm, i);
+}
+
+static void
+etsv_add_all (ETableSubsetVariable *etssv)
+{
+	ETableModel *etm = E_TABLE_MODEL (etssv);
+	ETableSubset *etss = E_TABLE_SUBSET (etssv);
+	ETableSortedVariable *etsv = E_TABLE_SORTED_VARIABLE (etssv);
+	gint rows;
+	gint i;
+
+	e_table_model_pre_change (etm);
+
+	rows = e_table_model_row_count (etss->source);
+
+	if (etss->n_map + rows > etssv->n_vals_allocated) {
+		etssv->n_vals_allocated += MAX (INCREMENT_AMOUNT, rows);
+		etss->map_table = g_realloc (etss->map_table, etssv->n_vals_allocated * sizeof (gint));
+	}
+	for (i = 0; i < rows; i++)
+		etss->map_table[etss->n_map++] = i;
+
+	if (etsv->sort_idle_id == 0) {
+		etsv->sort_idle_id = g_idle_add_full (50, (GSourceFunc) etsv_sort_idle, etsv, NULL);
+	}
+
+	e_table_model_changed (etm);
+}
+
+ETableModel *
+e_table_sorted_variable_new (ETableModel *source,
+                             ETableHeader *full_header,
+                             ETableSortInfo *sort_info)
+{
+	ETableSortedVariable *etsv = g_object_new (E_TYPE_TABLE_SORTED_VARIABLE, NULL);
+	ETableSubsetVariable *etssv = E_TABLE_SUBSET_VARIABLE (etsv);
+
+	if (e_table_subset_variable_construct (etssv, source) == NULL) {
+		g_object_unref (etsv);
+		return NULL;
+	}
+
+	etsv->sort_info = sort_info;
+	g_object_ref (etsv->sort_info);
+	etsv->full_header = full_header;
+	g_object_ref (etsv->full_header);
+
+	etsv->sort_info_changed_id = g_signal_connect (
+		sort_info, "sort_info_changed",
+		G_CALLBACK (etsv_sort_info_changed), etsv);
+
+	return E_TABLE_MODEL (etsv);
+}
+
+static void
+etsv_sort_info_changed (ETableSortInfo *info,
+                        ETableSortedVariable *etsv)
+{
+	etsv_sort (etsv);
+}
+
+static void
+etsv_sort (ETableSortedVariable *etsv)
+{
+	ETableSubset *etss = E_TABLE_SUBSET (etsv);
+	static gint reentering = 0;
+	if (reentering)
+		return;
+	reentering = 1;
+
+	e_table_model_pre_change (E_TABLE_MODEL (etsv));
+
+	e_table_sorting_utils_sort (etss->source, etsv->sort_info, etsv->full_header, etss->map_table, etss->n_map);
+
+	e_table_model_changed (E_TABLE_MODEL (etsv));
+	reentering = 0;
+}
diff --git a/e-util/e-table-sorted-variable.h b/e-util/e-table-sorted-variable.h
new file mode 100644
index 0000000..60861e5
--- /dev/null
+++ b/e-util/e-table-sorted-variable.h
@@ -0,0 +1,85 @@
+/*
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef _E_TABLE_SORTED_VARIABLE_H_
+#define _E_TABLE_SORTED_VARIABLE_H_
+
+#include <e-util/e-table-header.h>
+#include <e-util/e-table-model.h>
+#include <e-util/e-table-sort-info.h>
+#include <e-util/e-table-subset-variable.h>
+
+/* Standard GObject macros */
+#define E_TYPE_TABLE_SORTED_VARIABLE \
+	(e_table_sorted_variable_get_type ())
+#define E_TABLE_SORTED_VARIABLE(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_TABLE_SORTED_VARIABLE, ETableSortedVariable))
+#define E_TABLE_SORTED_VARIABLE_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_TABLE_SORTED_VARIABLE, ETableSortedVariableClass))
+#define E_IS_TABLE_SORTED_VARIABLE(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_TABLE_SORTED_VARIABLE))
+#define E_IS_TABLE_SORTED_VARIABLE_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_TABLE_SORTED_VARIABLE))
+#define E_TABLE_SORTED_VARIABLE_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_TABLE_SORTED_VARIABLE, ETableSortedVariableClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ETableSortedVariable ETableSortedVariable;
+typedef struct _ETableSortedVariableClass ETableSortedVariableClass;
+
+struct _ETableSortedVariable {
+	ETableSubsetVariable parent;
+
+	ETableSortInfo *sort_info;
+
+	ETableHeader *full_header;
+
+	gint sort_info_changed_id;
+	gint sort_idle_id;
+	gint insert_idle_id;
+	gint insert_count;
+};
+
+struct _ETableSortedVariableClass {
+	ETableSubsetVariableClass parent_class;
+};
+
+GType		e_table_sorted_variable_get_type
+						(void) G_GNUC_CONST;
+ETableModel *	e_table_sorted_variable_new	(ETableModel *etm,
+						 ETableHeader *header,
+						 ETableSortInfo *sort_info);
+
+G_END_DECLS
+
+#endif /* _E_TABLE_SORTED_VARIABLE_H_ */
diff --git a/e-util/e-table-sorted.c b/e-util/e-table-sorted.c
new file mode 100644
index 0000000..3f548d3
--- /dev/null
+++ b/e-util/e-table-sorted.c
@@ -0,0 +1,328 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "e-table-sorted.h"
+#include "e-table-sorting-utils.h"
+
+#define d(x)
+
+#define INCREMENT_AMOUNT 100
+
+/* workaround for avoding API breakage */
+#define ets_get_type e_table_sorted_get_type
+G_DEFINE_TYPE (ETableSorted, ets, E_TYPE_TABLE_SUBSET)
+
+/* maximum insertions between an idle event that we will do without scheduling an idle sort */
+#define ETS_INSERT_MAX (4)
+
+static void ets_sort_info_changed        (ETableSortInfo *info, ETableSorted *ets);
+static void ets_sort                     (ETableSorted *ets);
+static void ets_proxy_model_changed      (ETableSubset *etss, ETableModel *source);
+static void ets_proxy_model_row_changed  (ETableSubset *etss, ETableModel *source, gint row);
+static void ets_proxy_model_cell_changed (ETableSubset *etss, ETableModel *source, gint col, gint row);
+static void ets_proxy_model_rows_inserted (ETableSubset *etss, ETableModel *source, gint row, gint count);
+static void ets_proxy_model_rows_deleted  (ETableSubset *etss, ETableModel *source, gint row, gint count);
+
+static void
+ets_dispose (GObject *object)
+{
+	ETableSorted *ets = E_TABLE_SORTED (object);
+
+	if (ets->sort_idle_id)
+		g_source_remove (ets->sort_idle_id);
+	ets->sort_idle_id = 0;
+
+	if (ets->insert_idle_id)
+		g_source_remove (ets->insert_idle_id);
+	ets->insert_idle_id = 0;
+
+	if (ets->sort_info) {
+		g_signal_handler_disconnect (
+			ets->sort_info,
+			ets->sort_info_changed_id);
+		g_object_unref (ets->sort_info);
+		ets->sort_info = NULL;
+	}
+
+	if (ets->full_header)
+		g_object_unref (ets->full_header);
+	ets->full_header = NULL;
+
+	G_OBJECT_CLASS (ets_parent_class)->dispose (object);
+}
+
+static void
+ets_class_init (ETableSortedClass *class)
+{
+	ETableSubsetClass *etss_class = E_TABLE_SUBSET_CLASS (class);
+	GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+	etss_class->proxy_model_changed = ets_proxy_model_changed;
+	etss_class->proxy_model_row_changed = ets_proxy_model_row_changed;
+	etss_class->proxy_model_cell_changed = ets_proxy_model_cell_changed;
+	etss_class->proxy_model_rows_inserted = ets_proxy_model_rows_inserted;
+	etss_class->proxy_model_rows_deleted = ets_proxy_model_rows_deleted;
+
+	object_class->dispose = ets_dispose;
+}
+
+static void
+ets_init (ETableSorted *ets)
+{
+	ets->full_header = NULL;
+	ets->sort_info = NULL;
+
+	ets->sort_info_changed_id = 0;
+
+	ets->sort_idle_id = 0;
+	ets->insert_count = 0;
+}
+
+static gboolean
+ets_sort_idle (ETableSorted *ets)
+{
+	g_object_ref (ets);
+	ets_sort (ets);
+	ets->sort_idle_id = 0;
+	ets->insert_count = 0;
+	g_object_unref (ets);
+	return FALSE;
+}
+
+static gboolean
+ets_insert_idle (ETableSorted *ets)
+{
+	ets->insert_count = 0;
+	ets->insert_idle_id = 0;
+	return FALSE;
+}
+
+ETableModel *
+e_table_sorted_new (ETableModel *source,
+                    ETableHeader *full_header,
+                    ETableSortInfo *sort_info)
+{
+	ETableSorted *ets = g_object_new (E_TYPE_TABLE_SORTED, NULL);
+	ETableSubset *etss = E_TABLE_SUBSET (ets);
+
+	if (E_TABLE_SUBSET_CLASS (ets_parent_class)->proxy_model_pre_change)
+		(E_TABLE_SUBSET_CLASS (ets_parent_class)->proxy_model_pre_change) (etss, source);
+
+	if (e_table_subset_construct (etss, source, 0) == NULL) {
+		g_object_unref (ets);
+		return NULL;
+	}
+
+	ets->sort_info = sort_info;
+	g_object_ref (ets->sort_info);
+	ets->full_header = full_header;
+	g_object_ref (ets->full_header);
+
+	ets_proxy_model_changed (etss, source);
+
+	ets->sort_info_changed_id = g_signal_connect (
+		sort_info, "sort_info_changed",
+		G_CALLBACK (ets_sort_info_changed), ets);
+
+	return E_TABLE_MODEL (ets);
+}
+
+static void
+ets_sort_info_changed (ETableSortInfo *info,
+                       ETableSorted *ets)
+{
+	ets_sort (ets);
+}
+
+static void
+ets_proxy_model_changed (ETableSubset *subset,
+                         ETableModel *source)
+{
+	gint rows, i;
+
+	rows = e_table_model_row_count (source);
+
+	g_free (subset->map_table);
+	subset->n_map = rows;
+	subset->map_table = g_new (int, rows);
+
+	for (i = 0; i < rows; i++) {
+		subset->map_table[i] = i;
+	}
+
+	if (!E_TABLE_SORTED (subset)->sort_idle_id)
+		E_TABLE_SORTED (subset)->sort_idle_id = g_idle_add_full (50, (GSourceFunc) ets_sort_idle, subset, NULL);
+
+	e_table_model_changed (E_TABLE_MODEL (subset));
+}
+
+static void
+ets_proxy_model_row_changed (ETableSubset *subset,
+                             ETableModel *source,
+                             gint row)
+{
+	if (!E_TABLE_SORTED (subset)->sort_idle_id)
+		E_TABLE_SORTED (subset)->sort_idle_id = g_idle_add_full (50, (GSourceFunc) ets_sort_idle, subset, NULL);
+
+	if (E_TABLE_SUBSET_CLASS (ets_parent_class)->proxy_model_row_changed)
+		(E_TABLE_SUBSET_CLASS (ets_parent_class)->proxy_model_row_changed) (subset, source, row);
+}
+
+static void
+ets_proxy_model_cell_changed (ETableSubset *subset,
+                              ETableModel *source,
+                              gint col,
+                              gint row)
+{
+	ETableSorted *ets = E_TABLE_SORTED (subset);
+	if (e_table_sorting_utils_affects_sort (ets->sort_info, ets->full_header, col))
+		ets_proxy_model_row_changed (subset, source, row);
+	else if (E_TABLE_SUBSET_CLASS (ets_parent_class)->proxy_model_cell_changed)
+		(E_TABLE_SUBSET_CLASS (ets_parent_class)->proxy_model_cell_changed) (subset, source, col, row);
+}
+
+static void
+ets_proxy_model_rows_inserted (ETableSubset *etss,
+                               ETableModel *source,
+                               gint row,
+                               gint count)
+{
+	ETableModel *etm = E_TABLE_MODEL (etss);
+	ETableSorted *ets = E_TABLE_SORTED (etss);
+	gint i;
+	gboolean full_change = FALSE;
+
+	if (count == 0) {
+		e_table_model_no_change (etm);
+		return;
+	}
+
+	if (row != etss->n_map) {
+		full_change = TRUE;
+		for (i = 0; i < etss->n_map; i++) {
+			if (etss->map_table[i] >= row) {
+				etss->map_table[i] += count;
+			}
+		}
+	}
+
+	etss->map_table = g_realloc (etss->map_table, (etss->n_map + count) * sizeof (gint));
+
+	for (; count > 0; count--) {
+		if (!full_change)
+			e_table_model_pre_change (etm);
+		i = etss->n_map;
+		if (ets->sort_idle_id == 0) {
+			/* this is to see if we're inserting a lot of things between idle loops.
+			 * If we are, we're busy, its faster to just append and perform a full sort later */
+			ets->insert_count++;
+			if (ets->insert_count > ETS_INSERT_MAX) {
+				/* schedule a sort, and append instead */
+				ets->sort_idle_id = g_idle_add_full (50, (GSourceFunc) ets_sort_idle, ets, NULL);
+			} else {
+				/* make sure we have an idle handler to reset the count every now and then */
+				if (ets->insert_idle_id == 0) {
+					ets->insert_idle_id = g_idle_add_full (40, (GSourceFunc) ets_insert_idle, ets, NULL);
+				}
+				i = e_table_sorting_utils_insert (etss->source, ets->sort_info, ets->full_header, etss->map_table, etss->n_map, row);
+				memmove (etss->map_table + i + 1, etss->map_table + i, (etss->n_map - i) * sizeof (gint));
+			}
+		}
+		etss->map_table[i] = row;
+		etss->n_map++;
+		if (!full_change) {
+			e_table_model_row_inserted (etm, i);
+		}
+
+		d (g_print ("inserted row %d", row));
+		row++;
+	}
+	if (full_change)
+		e_table_model_changed (etm);
+	else
+		e_table_model_no_change (etm);
+	d (e_table_subset_print_debugging (etss));
+}
+
+static void
+ets_proxy_model_rows_deleted (ETableSubset *etss,
+                              ETableModel *source,
+                              gint row,
+                              gint count)
+{
+	ETableModel *etm = E_TABLE_MODEL (etss);
+	gint i;
+	gboolean shift;
+	gint j;
+
+	shift = row == etss->n_map - count;
+
+	for (j = 0; j < count; j++) {
+		for (i = 0; i < etss->n_map; i++) {
+			if (etss->map_table[i] == row + j) {
+				if (shift)
+					e_table_model_pre_change (etm);
+				memmove (etss->map_table + i, etss->map_table + i + 1, (etss->n_map - i - 1) * sizeof (gint));
+				etss->n_map--;
+				if (shift)
+					e_table_model_row_deleted (etm, i);
+			}
+		}
+	}
+	if (!shift) {
+		for (i = 0; i < etss->n_map; i++) {
+			if (etss->map_table[i] >= row)
+				etss->map_table[i] -= count;
+		}
+
+		e_table_model_changed (etm);
+	} else {
+		e_table_model_no_change (etm);
+	}
+
+	d (g_print ("deleted row %d count %d", row, count));
+	d (e_table_subset_print_debugging (etss));
+}
+
+static void
+ets_sort (ETableSorted *ets)
+{
+	ETableSubset *etss = E_TABLE_SUBSET (ets);
+	static gint reentering = 0;
+	if (reentering)
+		return;
+	reentering = 1;
+
+	e_table_model_pre_change (E_TABLE_MODEL (ets));
+
+	e_table_sorting_utils_sort (etss->source, ets->sort_info, ets->full_header, etss->map_table, etss->n_map);
+
+	e_table_model_changed (E_TABLE_MODEL (ets));
+	reentering = 0;
+}
diff --git a/e-util/e-table-sorted.h b/e-util/e-table-sorted.h
new file mode 100644
index 0000000..c9f4b65
--- /dev/null
+++ b/e-util/e-table-sorted.h
@@ -0,0 +1,84 @@
+/*
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef _E_TABLE_SORTED_H_
+#define _E_TABLE_SORTED_H_
+
+#include <e-util/e-table-header.h>
+#include <e-util/e-table-model.h>
+#include <e-util/e-table-sort-info.h>
+#include <e-util/e-table-subset.h>
+
+/* Standard GObject macros */
+#define E_TYPE_TABLE_SORTED \
+	(e_table_sorted_get_type ())
+#define E_TABLE_SORTED(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_TABLE_SORTED, ETableSorted))
+#define E_TABLE_SORTED_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_TABLE_SORTED, ETableSortedClass))
+#define E_IS_TABLE_SORTED(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_TABLE_SORTED))
+#define E_IS_TABLE_SORTED_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_TABLE_SORTED))
+#define E_TABLE_SORTED_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_TABLE_SORTED, ETableSortedClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ETableSorted ETableSorted;
+typedef struct _ETableSortedClass ETableSortedClass;
+
+struct _ETableSorted {
+	ETableSubset parent;
+
+	ETableSortInfo *sort_info;
+
+	ETableHeader *full_header;
+
+	gint sort_info_changed_id;
+	gint sort_idle_id;
+	gint insert_idle_id;
+	gint insert_count;
+};
+
+struct _ETableSortedClass {
+	ETableSubsetClass parent_class;
+};
+
+GType		e_table_sorted_get_type		(void) G_GNUC_CONST;
+ETableModel *	e_table_sorted_new		(ETableModel *etm,
+						 ETableHeader *header,
+						 ETableSortInfo *sort_info);
+
+G_END_DECLS
+
+#endif /* _E_TABLE_SORTED_H_ */
diff --git a/e-util/e-table-sorter.c b/e-util/e-table-sorter.c
new file mode 100644
index 0000000..5fdc077
--- /dev/null
+++ b/e-util/e-table-sorter.c
@@ -0,0 +1,519 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <glib/gi18n.h>
+
+#include "e-table-sorter.h"
+#include "e-table-sorting-utils.h"
+
+#define d(x)
+
+enum {
+	PROP_0,
+	PROP_SORT_INFO
+};
+
+/* workaround for avoiding API breakage */
+#define ets_get_type e_table_sorter_get_type
+G_DEFINE_TYPE (ETableSorter, ets, E_SORTER_TYPE)
+
+#define INCREMENT_AMOUNT 100
+
+static void	ets_model_changed      (ETableModel *etm, ETableSorter *ets);
+static void	ets_model_row_changed  (ETableModel *etm, gint row, ETableSorter *ets);
+static void	ets_model_cell_changed (ETableModel *etm, gint col, gint row, ETableSorter *ets);
+static void	ets_model_rows_inserted (ETableModel *etm, gint row, gint count, ETableSorter *ets);
+static void	ets_model_rows_deleted (ETableModel *etm, gint row, gint count, ETableSorter *ets);
+static void	ets_sort_info_changed  (ETableSortInfo *info, ETableSorter *ets);
+static void	ets_clean              (ETableSorter *ets);
+static void	ets_sort               (ETableSorter *ets);
+static void	ets_backsort           (ETableSorter *ets);
+
+static gint	ets_model_to_sorted           (ESorter *sorter, gint row);
+static gint	ets_sorted_to_model           (ESorter *sorter, gint row);
+static void	ets_get_model_to_sorted_array (ESorter *sorter, gint **array, gint *count);
+static void	ets_get_sorted_to_model_array (ESorter *sorter, gint **array, gint *count);
+static gboolean ets_needs_sorting             (ESorter *ets);
+
+static void
+ets_dispose (GObject *object)
+{
+	ETableSorter *ets = E_TABLE_SORTER (object);
+
+	if (ets->sort_info) {
+		if (ets->table_model_changed_id)
+			g_signal_handler_disconnect (
+				ets->source,
+				ets->table_model_changed_id);
+		if (ets->table_model_row_changed_id)
+			g_signal_handler_disconnect (
+				ets->source,
+				ets->table_model_row_changed_id);
+		if (ets->table_model_cell_changed_id)
+			g_signal_handler_disconnect (
+				ets->source,
+				ets->table_model_cell_changed_id);
+		if (ets->table_model_rows_inserted_id)
+			g_signal_handler_disconnect (
+				ets->source,
+				ets->table_model_rows_inserted_id);
+		if (ets->table_model_rows_deleted_id)
+			g_signal_handler_disconnect (
+				ets->source,
+				ets->table_model_rows_deleted_id);
+		if (ets->sort_info_changed_id)
+			g_signal_handler_disconnect (
+				ets->sort_info,
+				ets->sort_info_changed_id);
+		if (ets->group_info_changed_id)
+			g_signal_handler_disconnect (
+				ets->sort_info,
+				ets->group_info_changed_id);
+
+		ets->table_model_changed_id = 0;
+		ets->table_model_row_changed_id = 0;
+		ets->table_model_cell_changed_id = 0;
+		ets->table_model_rows_inserted_id = 0;
+		ets->table_model_rows_deleted_id = 0;
+		ets->sort_info_changed_id = 0;
+		ets->group_info_changed_id = 0;
+
+		g_object_unref (ets->sort_info);
+		ets->sort_info = NULL;
+	}
+
+	if (ets->full_header)
+		g_object_unref (ets->full_header);
+	ets->full_header = NULL;
+
+	if (ets->source)
+		g_object_unref (ets->source);
+	ets->source = NULL;
+
+	G_OBJECT_CLASS (ets_parent_class)->dispose (object);
+}
+
+static void
+ets_set_property (GObject *object,
+                  guint property_id,
+                  const GValue *value,
+                  GParamSpec *pspec)
+{
+	ETableSorter *ets = E_TABLE_SORTER (object);
+
+	switch (property_id) {
+	case PROP_SORT_INFO:
+		if (ets->sort_info) {
+			if (ets->sort_info_changed_id)
+				g_signal_handler_disconnect (ets->sort_info, ets->sort_info_changed_id);
+			if (ets->group_info_changed_id)
+				g_signal_handler_disconnect (ets->sort_info, ets->group_info_changed_id);
+			g_object_unref (ets->sort_info);
+		}
+
+		ets->sort_info = E_TABLE_SORT_INFO (g_value_get_object (value));
+		g_object_ref (ets->sort_info);
+		ets->sort_info_changed_id = g_signal_connect (
+			ets->sort_info, "sort_info_changed",
+			G_CALLBACK (ets_sort_info_changed), ets);
+		ets->group_info_changed_id = g_signal_connect (
+			ets->sort_info, "group_info_changed",
+			G_CALLBACK (ets_sort_info_changed), ets);
+
+		ets_clean (ets);
+		break;
+	default:
+		break;
+	}
+}
+
+static void
+ets_get_property (GObject *object,
+                  guint property_id,
+                  GValue *value,
+                  GParamSpec *pspec)
+{
+	ETableSorter *ets = E_TABLE_SORTER (object);
+	switch (property_id) {
+	case PROP_SORT_INFO:
+		g_value_set_object (value, ets->sort_info);
+		break;
+	}
+}
+
+static void
+ets_class_init (ETableSorterClass *class)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (class);
+	ESorterClass *sorter_class = E_SORTER_CLASS (class);
+
+	object_class->dispose                   = ets_dispose;
+	object_class->set_property              = ets_set_property;
+	object_class->get_property              = ets_get_property;
+
+	sorter_class->model_to_sorted           = ets_model_to_sorted;
+	sorter_class->sorted_to_model           = ets_sorted_to_model;
+	sorter_class->get_model_to_sorted_array = ets_get_model_to_sorted_array;
+	sorter_class->get_sorted_to_model_array = ets_get_sorted_to_model_array;
+	sorter_class->needs_sorting             = ets_needs_sorting;
+
+	g_object_class_install_property (
+		object_class,
+		PROP_SORT_INFO,
+		g_param_spec_object (
+			"sort_info",
+			"Sort Info",
+			NULL,
+			E_TYPE_TABLE_SORT_INFO,
+			G_PARAM_READWRITE));
+}
+
+static void
+ets_init (ETableSorter *ets)
+{
+	ets->full_header = NULL;
+	ets->sort_info = NULL;
+	ets->source = NULL;
+
+	ets->needs_sorting = -1;
+
+	ets->table_model_changed_id = 0;
+	ets->table_model_row_changed_id = 0;
+	ets->table_model_cell_changed_id = 0;
+	ets->table_model_rows_inserted_id = 0;
+	ets->table_model_rows_deleted_id = 0;
+	ets->sort_info_changed_id = 0;
+	ets->group_info_changed_id = 0;
+}
+
+ETableSorter *
+e_table_sorter_new (ETableModel *source,
+                    ETableHeader *full_header,
+                    ETableSortInfo *sort_info)
+{
+	ETableSorter *ets = g_object_new (E_TYPE_TABLE_SORTER, NULL);
+
+	ets->sort_info = sort_info;
+	g_object_ref (ets->sort_info);
+	ets->full_header = full_header;
+	g_object_ref (ets->full_header);
+	ets->source = source;
+	g_object_ref (ets->source);
+
+	ets->table_model_changed_id = g_signal_connect (
+		source, "model_changed",
+		G_CALLBACK (ets_model_changed), ets);
+
+	ets->table_model_row_changed_id = g_signal_connect (
+		source, "model_row_changed",
+		G_CALLBACK (ets_model_row_changed), ets);
+
+	ets->table_model_cell_changed_id = g_signal_connect (
+		source, "model_cell_changed",
+		G_CALLBACK (ets_model_cell_changed), ets);
+
+	ets->table_model_rows_inserted_id = g_signal_connect (
+		source, "model_rows_inserted",
+		G_CALLBACK (ets_model_rows_inserted), ets);
+
+	ets->table_model_rows_deleted_id = g_signal_connect (
+		source, "model_rows_deleted",
+		G_CALLBACK (ets_model_rows_deleted), ets);
+
+	ets->sort_info_changed_id = g_signal_connect (
+		sort_info, "sort_info_changed",
+		G_CALLBACK (ets_sort_info_changed), ets);
+
+	ets->group_info_changed_id = g_signal_connect (
+		sort_info, "group_info_changed",
+		G_CALLBACK (ets_sort_info_changed), ets);
+
+	return ets;
+}
+
+static void
+ets_model_changed (ETableModel *etm,
+                   ETableSorter *ets)
+{
+	ets_clean (ets);
+}
+
+static void
+ets_model_row_changed (ETableModel *etm,
+                       gint row,
+                       ETableSorter *ets)
+{
+	ets_clean (ets);
+}
+
+static void
+ets_model_cell_changed (ETableModel *etm,
+                        gint col,
+                        gint row,
+                        ETableSorter *ets)
+{
+	ets_clean (ets);
+}
+
+static void
+ets_model_rows_inserted (ETableModel *etm,
+                         gint row,
+                         gint count,
+                         ETableSorter *ets)
+{
+	ets_clean (ets);
+}
+
+static void
+ets_model_rows_deleted (ETableModel *etm,
+                        gint row,
+                        gint count,
+                        ETableSorter *ets)
+{
+	ets_clean (ets);
+}
+
+static void
+ets_sort_info_changed (ETableSortInfo *info,
+                       ETableSorter *ets)
+{
+	d (g_print ("sort info changed\n"));
+	ets_clean (ets);
+}
+
+struct qsort_data {
+	ETableSorter *ets;
+	gpointer *vals;
+	gint cols;
+	gint *ascending;
+	GCompareDataFunc *compare;
+	gpointer cmp_cache;
+};
+
+/* FIXME: Make it not cache the second and later columns (as if anyone cares.) */
+
+static gint
+qsort_callback (gconstpointer data1,
+                gconstpointer data2,
+                gpointer user_data)
+{
+	struct qsort_data *qd = (struct qsort_data *) user_data;
+	gint row1 = *(gint *) data1;
+	gint row2 = *(gint *) data2;
+	gint j;
+	gint sort_count = e_table_sort_info_sorting_get_count (qd->ets->sort_info) + e_table_sort_info_grouping_get_count (qd->ets->sort_info);
+	gint comp_val = 0;
+	gint ascending = 1;
+	for (j = 0; j < sort_count; j++) {
+		comp_val = (*(qd->compare[j]))(qd->vals[qd->cols * row1 + j], qd->vals[qd->cols * row2 + j], qd->cmp_cache);
+		ascending = qd->ascending[j];
+		if (comp_val != 0)
+			break;
+	}
+	if (comp_val == 0) {
+		if (row1 < row2)
+			comp_val = -1;
+		if (row1 > row2)
+			comp_val = 1;
+	}
+	if (!ascending)
+		comp_val = -comp_val;
+	return comp_val;
+}
+
+static void
+ets_clean (ETableSorter *ets)
+{
+	g_free (ets->sorted);
+	ets->sorted = NULL;
+
+	g_free (ets->backsorted);
+	ets->backsorted = NULL;
+
+	ets->needs_sorting = -1;
+}
+
+static void
+ets_sort (ETableSorter *ets)
+{
+	gint rows;
+	gint i;
+	gint j;
+	gint cols;
+	gint group_cols;
+	struct qsort_data qd;
+
+	if (ets->sorted)
+		return;
+
+	rows = e_table_model_row_count (ets->source);
+	group_cols = e_table_sort_info_grouping_get_count (ets->sort_info);
+	cols = e_table_sort_info_sorting_get_count (ets->sort_info) + group_cols;
+
+	ets->sorted = g_new (int, rows);
+	for (i = 0; i < rows; i++)
+		ets->sorted[i] = i;
+
+	qd.cols = cols;
+	qd.ets = ets;
+
+	qd.vals = g_new (gpointer , rows * cols);
+	qd.ascending = g_new (int, cols);
+	qd.compare = g_new (GCompareDataFunc, cols);
+	qd.cmp_cache = e_table_sorting_utils_create_cmp_cache ();
+
+	for (j = 0; j < cols; j++) {
+		ETableSortColumn column;
+		ETableCol *col;
+
+		if (j < group_cols)
+			column = e_table_sort_info_grouping_get_nth (ets->sort_info, j);
+		else
+			column = e_table_sort_info_sorting_get_nth (ets->sort_info, j - group_cols);
+
+		col = e_table_header_get_column_by_col_idx (ets->full_header, column.column);
+		if (col == NULL)
+			col = e_table_header_get_column (ets->full_header, e_table_header_count (ets->full_header) - 1);
+
+		for (i = 0; i < rows; i++) {
+			qd.vals[i * cols + j] = e_table_model_value_at (ets->source, col->col_idx, i);
+		}
+
+		qd.compare[j] = col->compare;
+		qd.ascending[j] = column.ascending;
+	}
+
+	g_qsort_with_data (ets->sorted, rows, sizeof (gint), qsort_callback, &qd);
+
+	g_free (qd.vals);
+	g_free (qd.ascending);
+	g_free (qd.compare);
+	e_table_sorting_utils_free_cmp_cache (qd.cmp_cache);
+}
+
+static void
+ets_backsort (ETableSorter *ets)
+{
+	gint i, rows;
+
+	if (ets->backsorted)
+		return;
+
+	ets_sort (ets);
+
+	rows = e_table_model_row_count (ets->source);
+	ets->backsorted = g_new0 (int, rows);
+
+	for (i = 0; i < rows; i++) {
+		ets->backsorted[ets->sorted[i]] = i;
+	}
+}
+
+static gint
+ets_model_to_sorted (ESorter *es,
+                     gint row)
+{
+	ETableSorter *ets = E_TABLE_SORTER (es);
+	gint rows = e_table_model_row_count (ets->source);
+
+	g_return_val_if_fail (row >= 0, -1);
+	g_return_val_if_fail (row < rows, -1);
+
+	if (ets_needs_sorting (es))
+		ets_backsort (ets);
+
+	if (ets->backsorted)
+		return ets->backsorted[row];
+	else
+		return row;
+}
+
+static gint
+ets_sorted_to_model (ESorter *es,
+                     gint row)
+{
+	ETableSorter *ets = E_TABLE_SORTER (es);
+	gint rows = e_table_model_row_count (ets->source);
+
+	g_return_val_if_fail (row >= 0, -1);
+	g_return_val_if_fail (row < rows, -1);
+
+	if (ets_needs_sorting (es))
+		ets_sort (ets);
+
+	if (ets->sorted)
+		return ets->sorted[row];
+	else
+		return row;
+}
+
+static void
+ets_get_model_to_sorted_array (ESorter *es,
+                               gint **array,
+                               gint *count)
+{
+	ETableSorter *ets = E_TABLE_SORTER (es);
+	if (array || count) {
+		ets_backsort (ets);
+
+		if (array)
+			*array = ets->backsorted;
+		if (count)
+			*count = e_table_model_row_count(ets->source);
+	}
+}
+
+static void
+ets_get_sorted_to_model_array (ESorter *es,
+                               gint **array,
+                               gint *count)
+{
+	ETableSorter *ets = E_TABLE_SORTER (es);
+	if (array || count) {
+		ets_sort (ets);
+
+		if (array)
+			*array = ets->sorted;
+		if (count)
+			*count = e_table_model_row_count(ets->source);
+	}
+}
+
+static gboolean
+ets_needs_sorting (ESorter *es)
+{
+	ETableSorter *ets = E_TABLE_SORTER (es);
+	if (ets->needs_sorting < 0) {
+		if (e_table_sort_info_sorting_get_count (ets->sort_info) + e_table_sort_info_grouping_get_count (ets->sort_info))
+			ets->needs_sorting = 1;
+		else
+			ets->needs_sorting = 0;
+	}
+	return ets->needs_sorting;
+}
diff --git a/e-util/e-table-sorter.h b/e-util/e-table-sorter.h
new file mode 100644
index 0000000..9615a9b
--- /dev/null
+++ b/e-util/e-table-sorter.h
@@ -0,0 +1,94 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef _E_TABLE_SORTER_H_
+#define _E_TABLE_SORTER_H_
+
+#include <e-util/e-sorter.h>
+#include <e-util/e-table-header.h>
+#include <e-util/e-table-model.h>
+#include <e-util/e-table-sort-info.h>
+#include <e-util/e-table-subset-variable.h>
+
+/* Standard GObject macros */
+#define E_TYPE_TABLE_SORTER \
+	(e_table_sorter_get_type ())
+#define E_TABLE_SORTER(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_TABLE_SORTER, ETableSorter))
+#define E_TABLE_SORTER_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_TABLE_SORTER, ETableSorterClass))
+#define E_IS_TABLE_SORTER(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_TABLE_SORTER))
+#define E_IS_TABLE_SORTER_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_TABLE_SORTER))
+#define E_TABLE_SORTER_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_TABLE_SORTER, ETableSorterClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ETableSorter ETableSorter;
+typedef struct _ETableSorterClass ETableSorterClass;
+
+struct _ETableSorter {
+	ESorter parent;
+
+	ETableModel *source;
+	ETableHeader *full_header;
+	ETableSortInfo *sort_info;
+
+	/* If needs_sorting is 0, then model_to_sorted
+	 * and sorted_to_model are no-ops. */
+	gint needs_sorting;
+
+	gint *sorted;
+	gint *backsorted;
+
+	gint table_model_changed_id;
+	gint table_model_row_changed_id;
+	gint table_model_cell_changed_id;
+	gint table_model_rows_inserted_id;
+	gint table_model_rows_deleted_id;
+	gint sort_info_changed_id;
+	gint group_info_changed_id;
+};
+
+struct _ETableSorterClass {
+	ESorterClass parent_class;
+};
+
+GType		e_table_sorter_get_type		(void) G_GNUC_CONST;
+ETableSorter *	e_table_sorter_new		(ETableModel *etm,
+						 ETableHeader *full_header,
+						 ETableSortInfo *sort_info);
+
+G_END_DECLS
+
+#endif /* _E_TABLE_SORTER_H_ */
diff --git a/e-util/e-table-sorting-utils.c b/e-util/e-table-sorting-utils.c
new file mode 100644
index 0000000..23303ea
--- /dev/null
+++ b/e-util/e-table-sorting-utils.c
@@ -0,0 +1,492 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-table-sorting-utils.h"
+
+#include <string.h>
+#include <camel/camel.h>
+
+#include "e-misc-utils.h"
+
+#define d(x)
+
+/* This takes source rows. */
+static gint
+etsu_compare (ETableModel *source,
+              ETableSortInfo *sort_info,
+              ETableHeader *full_header,
+              gint row1,
+              gint row2,
+              gpointer cmp_cache)
+{
+	gint j;
+	gint sort_count = e_table_sort_info_sorting_get_count (sort_info);
+	gint comp_val = 0;
+	gint ascending = 1;
+
+	for (j = 0; j < sort_count; j++) {
+		ETableSortColumn column = e_table_sort_info_sorting_get_nth (sort_info, j);
+		ETableCol *col;
+		col = e_table_header_get_column_by_col_idx (full_header, column.column);
+		if (col == NULL)
+			col = e_table_header_get_column (full_header, e_table_header_count (full_header) - 1);
+		comp_val = (*col->compare)(e_table_model_value_at (source, col->compare_col, row1),
+					   e_table_model_value_at (source, col->compare_col, row2),
+					   cmp_cache);
+		ascending = column.ascending;
+		if (comp_val != 0)
+			break;
+	}
+	if (comp_val == 0) {
+		if (row1 < row2)
+			comp_val = -1;
+		if (row1 > row2)
+			comp_val = 1;
+	}
+	if (!ascending)
+		comp_val = -comp_val;
+	return comp_val;
+}
+
+typedef struct {
+	gint cols;
+	gpointer *vals;
+	gint *ascending;
+	GCompareDataFunc *compare;
+	gpointer cmp_cache;
+} ETableSortClosure;
+
+typedef struct {
+	ETreeModel *tree;
+	ETableSortInfo *sort_info;
+	ETableHeader *full_header;
+	gpointer cmp_cache;
+} ETreeSortClosure;
+
+/* FIXME: Make it not cache the second and later columns (as if anyone cares.) */
+
+static gint
+e_sort_callback (gconstpointer data1,
+                 gconstpointer data2,
+                 gpointer user_data)
+{
+	gint row1 = *(gint *) data1;
+	gint row2 = *(gint *) data2;
+	ETableSortClosure *closure = user_data;
+	gint j;
+	gint sort_count = closure->cols;
+	gint comp_val = 0;
+	gint ascending = 1;
+	for (j = 0; j < sort_count; j++) {
+		comp_val = (*(closure->compare[j]))(closure->vals[closure->cols * row1 + j], closure->vals[closure->cols * row2 + j], closure->cmp_cache);
+		ascending = closure->ascending[j];
+		if (comp_val != 0)
+			break;
+	}
+	if (comp_val == 0) {
+		if (row1 < row2)
+			comp_val = -1;
+		if (row1 > row2)
+			comp_val = 1;
+	}
+	if (!ascending)
+		comp_val = -comp_val;
+	return comp_val;
+}
+
+void
+e_table_sorting_utils_sort (ETableModel *source,
+                            ETableSortInfo *sort_info,
+                            ETableHeader *full_header,
+                            gint *map_table,
+                            gint rows)
+{
+	gint total_rows;
+	gint i;
+	gint j;
+	gint cols;
+	ETableSortClosure closure;
+
+	g_return_if_fail (source != NULL);
+	g_return_if_fail (E_IS_TABLE_MODEL (source));
+	g_return_if_fail (sort_info != NULL);
+	g_return_if_fail (E_IS_TABLE_SORT_INFO (sort_info));
+	g_return_if_fail (full_header != NULL);
+	g_return_if_fail (E_IS_TABLE_HEADER (full_header));
+
+	total_rows = e_table_model_row_count (source);
+	cols = e_table_sort_info_sorting_get_count (sort_info);
+	closure.cols = cols;
+
+	closure.vals = g_new (gpointer , total_rows * cols);
+	closure.ascending = g_new (int, cols);
+	closure.compare = g_new (GCompareDataFunc, cols);
+	closure.cmp_cache = e_table_sorting_utils_create_cmp_cache ();
+
+	for (j = 0; j < cols; j++) {
+		ETableSortColumn column = e_table_sort_info_sorting_get_nth (sort_info, j);
+		ETableCol *col;
+		col = e_table_header_get_column_by_col_idx (full_header, column.column);
+		if (col == NULL)
+			col = e_table_header_get_column (full_header, e_table_header_count (full_header) - 1);
+		for (i = 0; i < rows; i++) {
+			closure.vals[map_table[i] * cols + j] = e_table_model_value_at (source, col->compare_col, map_table[i]);
+		}
+		closure.compare[j] = col->compare;
+		closure.ascending[j] = column.ascending;
+	}
+
+	g_qsort_with_data (
+		map_table, rows, sizeof (gint), e_sort_callback, &closure);
+
+	g_free (closure.vals);
+	g_free (closure.ascending);
+	g_free (closure.compare);
+	e_table_sorting_utils_free_cmp_cache (closure.cmp_cache);
+}
+
+gboolean
+e_table_sorting_utils_affects_sort (ETableSortInfo *sort_info,
+                                    ETableHeader *full_header,
+                                    gint col)
+{
+	gint j;
+	gint cols;
+
+	g_return_val_if_fail (sort_info != NULL, TRUE);
+	g_return_val_if_fail (E_IS_TABLE_SORT_INFO (sort_info), TRUE);
+	g_return_val_if_fail (full_header != NULL, TRUE);
+	g_return_val_if_fail (E_IS_TABLE_HEADER (full_header), TRUE);
+
+	cols = e_table_sort_info_sorting_get_count (sort_info);
+
+	for (j = 0; j < cols; j++) {
+		ETableSortColumn column = e_table_sort_info_sorting_get_nth (sort_info, j);
+		ETableCol *tablecol;
+		tablecol = e_table_header_get_column_by_col_idx (full_header, column.column);
+		if (tablecol == NULL)
+			tablecol = e_table_header_get_column (full_header, e_table_header_count (full_header) - 1);
+		if (col == tablecol->compare_col)
+			return TRUE;
+	}
+	return FALSE;
+}
+
+/* FIXME: This could be done in time log n instead of time n with a binary search. */
+gint
+e_table_sorting_utils_insert (ETableModel *source,
+                              ETableSortInfo *sort_info,
+                              ETableHeader *full_header,
+                              gint *map_table,
+                              gint rows,
+                              gint row)
+{
+	gint i;
+	gpointer cmp_cache = e_table_sorting_utils_create_cmp_cache ();
+
+	i = 0;
+	/* handle insertions when we have a 'sort group' */
+	while (i < rows && etsu_compare (source, sort_info, full_header, map_table[i], row, cmp_cache) < 0)
+		i++;
+
+	e_table_sorting_utils_free_cmp_cache (cmp_cache);
+
+	return i;
+}
+
+/* FIXME: This could be done in time log n instead of time n with a binary search. */
+gint
+e_table_sorting_utils_check_position (ETableModel *source,
+                                      ETableSortInfo *sort_info,
+                                      ETableHeader *full_header,
+                                      gint *map_table,
+                                      gint rows,
+                                      gint view_row)
+{
+	gint i;
+	gint row;
+	gpointer cmp_cache;
+
+	i = view_row;
+	row = map_table[i];
+	cmp_cache = e_table_sorting_utils_create_cmp_cache ();
+
+	i = view_row;
+	if (i < rows - 1 && etsu_compare (source, sort_info, full_header, map_table[i + 1], row, cmp_cache) < 0) {
+		i++;
+		while (i < rows - 1 && etsu_compare (source, sort_info, full_header, map_table[i], row, cmp_cache) < 0)
+			i++;
+	} else if (i > 0 && etsu_compare (source, sort_info, full_header, map_table[i - 1], row, cmp_cache) > 0) {
+		i--;
+		while (i > 0 && etsu_compare (source, sort_info, full_header, map_table[i], row, cmp_cache) > 0)
+			i--;
+	}
+
+	e_table_sorting_utils_free_cmp_cache (cmp_cache);
+
+	return i;
+}
+
+/* This takes source rows. */
+static gint
+etsu_tree_compare (ETreeModel *source,
+                   ETableSortInfo *sort_info,
+                   ETableHeader *full_header,
+                   ETreePath path1,
+                   ETreePath path2,
+                   gpointer cmp_cache)
+{
+	gint j;
+	gint sort_count = e_table_sort_info_sorting_get_count (sort_info);
+	gint comp_val = 0;
+	gint ascending = 1;
+
+	for (j = 0; j < sort_count; j++) {
+		ETableSortColumn column = e_table_sort_info_sorting_get_nth (sort_info, j);
+		ETableCol *col;
+		col = e_table_header_get_column_by_col_idx (full_header, column.column);
+		if (col == NULL)
+			col = e_table_header_get_column (full_header, e_table_header_count (full_header) - 1);
+		comp_val = (*col->compare)(e_tree_model_value_at (source, path1, col->compare_col),
+					   e_tree_model_value_at (source, path2, col->compare_col),
+					   cmp_cache);
+		ascending = column.ascending;
+		if (comp_val != 0)
+			break;
+	}
+	if (!ascending)
+		comp_val = -comp_val;
+	return comp_val;
+}
+
+static gint
+e_sort_tree_callback (gconstpointer data1,
+                      gconstpointer data2,
+                      gpointer user_data)
+{
+	ETreePath *path1 = *(ETreePath *) data1;
+	ETreePath *path2 = *(ETreePath *) data2;
+	ETreeSortClosure *closure = user_data;
+
+	return etsu_tree_compare (closure->tree, closure->sort_info, closure->full_header, path1, path2, closure->cmp_cache);
+}
+
+void
+e_table_sorting_utils_tree_sort (ETreeModel *source,
+                                 ETableSortInfo *sort_info,
+                                 ETableHeader *full_header,
+                                 ETreePath *map_table,
+                                 gint count)
+{
+	ETableSortClosure closure;
+	gint cols;
+	gint i, j;
+	gint *map;
+	ETreePath *map_copy;
+	g_return_if_fail (source != NULL);
+	g_return_if_fail (E_IS_TREE_MODEL (source));
+	g_return_if_fail (sort_info != NULL);
+	g_return_if_fail (E_IS_TABLE_SORT_INFO (sort_info));
+	g_return_if_fail (full_header != NULL);
+	g_return_if_fail (E_IS_TABLE_HEADER (full_header));
+
+	cols = e_table_sort_info_sorting_get_count (sort_info);
+	closure.cols = cols;
+
+	closure.vals = g_new (gpointer , count * cols);
+	closure.ascending = g_new (int, cols);
+	closure.compare = g_new (GCompareDataFunc, cols);
+	closure.cmp_cache = e_table_sorting_utils_create_cmp_cache ();
+
+	for (j = 0; j < cols; j++) {
+		ETableSortColumn column = e_table_sort_info_sorting_get_nth (sort_info, j);
+		ETableCol *col;
+
+		col = e_table_header_get_column_by_col_idx (full_header, column.column);
+		if (col == NULL)
+			col = e_table_header_get_column (full_header, e_table_header_count (full_header) - 1);
+
+		for (i = 0; i < count; i++) {
+			closure.vals[i * cols + j] = e_tree_model_sort_value_at (source, map_table[i], col->compare_col);
+		}
+		closure.ascending[j] = column.ascending;
+		closure.compare[j] = col->compare;
+	}
+
+	map = g_new (int, count);
+	for (i = 0; i < count; i++) {
+		map[i] = i;
+	}
+
+	g_qsort_with_data (
+		map, count, sizeof (gint), e_sort_callback, &closure);
+
+	map_copy = g_new (ETreePath, count);
+	for (i = 0; i < count; i++) {
+		map_copy[i] = map_table[i];
+	}
+	for (i = 0; i < count; i++) {
+		map_table[i] = map_copy[map[i]];
+	}
+
+	g_free (map);
+	g_free (map_copy);
+
+	g_free (closure.vals);
+	g_free (closure.ascending);
+	g_free (closure.compare);
+	e_table_sorting_utils_free_cmp_cache (closure.cmp_cache);
+}
+
+/* FIXME: This could be done in time log n instead of time n with a binary search. */
+gint
+e_table_sorting_utils_tree_check_position (ETreeModel *source,
+                                           ETableSortInfo *sort_info,
+                                           ETableHeader *full_header,
+                                           ETreePath *map_table,
+                                           gint count,
+                                           gint old_index)
+{
+	gint i;
+	ETreePath path;
+	gpointer cmp_cache = e_table_sorting_utils_create_cmp_cache ();
+
+	i = old_index;
+	path = map_table[i];
+
+	if (i < count - 1 && etsu_tree_compare (source, sort_info, full_header, map_table[i + 1], path, cmp_cache) < 0) {
+		i++;
+		while (i < count - 1 && etsu_tree_compare (source, sort_info, full_header, map_table[i], path, cmp_cache) < 0)
+			i++;
+	} else if (i > 0 && etsu_tree_compare (source, sort_info, full_header, map_table[i - 1], path, cmp_cache) > 0) {
+		i--;
+		while (i > 0 && etsu_tree_compare (source, sort_info, full_header, map_table[i], path, cmp_cache) > 0)
+			i--;
+	}
+
+	e_table_sorting_utils_free_cmp_cache (cmp_cache);
+
+	return i;
+}
+
+/* FIXME: This does not pay attention to making sure that it's a stable insert.  This needs to be fixed. */
+gint
+e_table_sorting_utils_tree_insert (ETreeModel *source,
+                                   ETableSortInfo *sort_info,
+                                   ETableHeader *full_header,
+                                   ETreePath *map_table,
+                                   gint count,
+                                   ETreePath path)
+{
+	gsize start;
+	gsize end;
+	ETreeSortClosure closure;
+
+	closure.tree = source;
+	closure.sort_info = sort_info;
+	closure.full_header = full_header;
+	closure.cmp_cache = e_table_sorting_utils_create_cmp_cache ();
+
+	e_bsearch (&path, map_table, count, sizeof (ETreePath), e_sort_tree_callback, &closure, &start, &end);
+
+	e_table_sorting_utils_free_cmp_cache (closure.cmp_cache);
+
+	return end;
+}
+
+/**
+ * e_table_sorting_utils_create_cmp_cache:
+ *
+ * Creates a new compare cache, which is storing pairs of string keys and
+ * string values.  This can be accessed by
+ * e_table_sorting_utils_lookup_cmp_cache() and
+ * e_table_sorting_utils_add_to_cmp_cache().
+ *
+ * Returned pointer should be freed with
+ * e_table_sorting_utils_free_cmp_cache().
+ **/
+gpointer
+e_table_sorting_utils_create_cmp_cache (void)
+{
+	return g_hash_table_new_full (g_str_hash, g_str_equal, (GDestroyNotify) camel_pstring_free, g_free);
+}
+
+/**
+ * e_table_sorting_utils_free_cmp_cache:
+ * @cmp_cache: a compare cache; cannot be %NULL
+ *
+ * Frees a compare cache previously created with
+ * e_table_sorting_utils_create_cmp_cache().
+ **/
+void
+e_table_sorting_utils_free_cmp_cache (gpointer cmp_cache)
+{
+	g_return_if_fail (cmp_cache != NULL);
+
+	g_hash_table_destroy (cmp_cache);
+}
+
+/**
+ * e_table_sorting_utils_add_to_cmp_cache:
+ * @cmp_cache: a compare cache; cannot be %NULL
+ * @key: unique key to a cache; cannot be %NULL
+ * @value: value to store for a key
+ *
+ * Adds a new value for a given key to a compare cache. If such key
+ * already exists in a cache then its value will be replaced.
+ * Note: Given @value will be stolen and later freed with g_free.
+ **/
+void
+e_table_sorting_utils_add_to_cmp_cache (gpointer cmp_cache,
+                                        const gchar *key,
+                                        gchar *value)
+{
+	g_return_if_fail (cmp_cache != NULL);
+	g_return_if_fail (key != NULL);
+
+	g_hash_table_insert (cmp_cache, (gchar *) camel_pstring_strdup (key), value);
+}
+
+/**
+ * e_table_sorting_utils_lookup_cmp_cache:
+ * @cmp_cache: a compare cache
+ * @key: unique key to a cache
+ *
+ * Lookups for a key in a compare cache, which is passed in GCompareDataFunc as 'data'.
+ * Returns %NULL when not found or the cache wasn't provided, otherwise value stored
+ * with a key.
+ **/
+const gchar *
+e_table_sorting_utils_lookup_cmp_cache (gpointer cmp_cache,
+                                        const gchar *key)
+{
+	g_return_val_if_fail (key != NULL, NULL);
+
+	if (!cmp_cache)
+		return NULL;
+
+	return g_hash_table_lookup (cmp_cache, key);
+}
diff --git a/e-util/e-table-sorting-utils.h b/e-util/e-table-sorting-utils.h
new file mode 100644
index 0000000..2d5ccb4
--- /dev/null
+++ b/e-util/e-table-sorting-utils.h
@@ -0,0 +1,95 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef _E_TABLE_SORTING_UTILS_H_
+#define _E_TABLE_SORTING_UTILS_H_
+
+#include <e-util/e-table-header.h>
+#include <e-util/e-table-model.h>
+#include <e-util/e-table-sort-info.h>
+#include <e-util/e-tree-model.h>
+
+G_BEGIN_DECLS
+
+gboolean	e_table_sorting_utils_affects_sort
+						(ETableSortInfo *sort_info,
+						 ETableHeader *full_header,
+						 gint col);
+
+void		e_table_sorting_utils_sort	(ETableModel *source,
+						 ETableSortInfo *sort_info,
+						 ETableHeader *full_header,
+						 gint *map_table,
+						 gint rows);
+gint		e_table_sorting_utils_insert	(ETableModel *source,
+						 ETableSortInfo *sort_info,
+						 ETableHeader *full_header,
+						 gint *map_table,
+						 gint rows,
+						 gint row);
+gint		e_table_sorting_utils_check_position
+						(ETableModel *source,
+						 ETableSortInfo *sort_info,
+						 ETableHeader *full_header,
+						 gint *map_table,
+						 gint rows,
+						 gint view_row);
+
+void		e_table_sorting_utils_tree_sort	(ETreeModel *source,
+						 ETableSortInfo *sort_info,
+						 ETableHeader *full_header,
+						 ETreePath *map_table,
+						 gint count);
+gint		e_table_sorting_utils_tree_check_position
+						(ETreeModel *source,
+						 ETableSortInfo *sort_info,
+						 ETableHeader *full_header,
+						 ETreePath *map_table,
+						 gint count,
+						 gint old_index);
+gint		e_table_sorting_utils_tree_insert
+						(ETreeModel *source,
+						 ETableSortInfo *sort_info,
+						 ETableHeader *full_header,
+						 ETreePath *map_table,
+						 gint count,
+						 ETreePath path);
+
+gpointer	e_table_sorting_utils_create_cmp_cache
+						(void);
+void		e_table_sorting_utils_free_cmp_cache
+						(gpointer cmp_cache);
+void		e_table_sorting_utils_add_to_cmp_cache
+						(gpointer cmp_cache,
+						 const gchar *key,
+						 gchar *value);
+const gchar *	e_table_sorting_utils_lookup_cmp_cache
+						(gpointer cmp_cache,
+						 const gchar *key);
+
+G_END_DECLS
+
+#endif /* _E_TABLE_SORTING_UTILS_H_ */
diff --git a/e-util/e-table-specification.c b/e-util/e-table-specification.c
new file mode 100644
index 0000000..03cb429
--- /dev/null
+++ b/e-util/e-table-specification.c
@@ -0,0 +1,435 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-table-specification.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <glib/gstdio.h>
+#include <libxml/parser.h>
+#include <libxml/xmlmemory.h>
+
+#include <libedataserver/libedataserver.h>
+
+#include "e-xml-utils.h"
+
+/* workaround for avoiding API breakage */
+#define etsp_get_type e_table_specification_get_type
+G_DEFINE_TYPE (ETableSpecification, etsp, G_TYPE_OBJECT)
+
+static void
+etsp_finalize (GObject *object)
+{
+	ETableSpecification *etsp = E_TABLE_SPECIFICATION (object);
+	gint i;
+
+	if (etsp->columns) {
+		for (i = 0; etsp->columns[i]; i++) {
+			g_object_unref (etsp->columns[i]);
+		}
+		g_free (etsp->columns);
+		etsp->columns = NULL;
+	}
+
+	if (etsp->state)
+		g_object_unref (etsp->state);
+	etsp->state                = NULL;
+
+	g_free (etsp->click_to_add_message);
+	etsp->click_to_add_message = NULL;
+
+	g_free (etsp->domain);
+	etsp->domain		   = NULL;
+
+	G_OBJECT_CLASS (etsp_parent_class)->finalize (object);
+}
+
+static void
+etsp_class_init (ETableSpecificationClass *class)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+	object_class->finalize = etsp_finalize;
+}
+
+static void
+etsp_init (ETableSpecification *etsp)
+{
+	etsp->columns                = NULL;
+	etsp->state                  = NULL;
+
+	etsp->alternating_row_colors = TRUE;
+	etsp->no_headers             = FALSE;
+	etsp->click_to_add           = FALSE;
+	etsp->click_to_add_end       = FALSE;
+	etsp->horizontal_draw_grid   = FALSE;
+	etsp->vertical_draw_grid     = FALSE;
+	etsp->draw_focus             = TRUE;
+	etsp->horizontal_scrolling   = FALSE;
+	etsp->horizontal_resize      = FALSE;
+	etsp->allow_grouping         = TRUE;
+
+	etsp->cursor_mode            = E_CURSOR_SIMPLE;
+	etsp->selection_mode         = GTK_SELECTION_MULTIPLE;
+
+	etsp->click_to_add_message   = NULL;
+	etsp->domain                 = NULL;
+}
+
+/**
+ * e_table_specification_new:
+ *
+ * Creates a new %ETableSpecification object.   This object is used to hold the
+ * information about the rendering information for ETable.
+ *
+ * Returns: a newly created %ETableSpecification object.
+ */
+ETableSpecification *
+e_table_specification_new (void)
+{
+	ETableSpecification *etsp = g_object_new (E_TYPE_TABLE_SPECIFICATION, NULL);
+
+	return (ETableSpecification *) etsp;
+}
+
+/**
+ * e_table_specification_load_from_file:
+ * @specification: An ETableSpecification that you want to modify
+ * @filename: a filename that contains an ETableSpecification
+ *
+ * This routine modifies @specification to reflect the state described
+ * by the file @filename.
+ *
+ * Returns: TRUE on success, FALSE on failure.
+ */
+gboolean
+e_table_specification_load_from_file (ETableSpecification *specification,
+                                      const gchar *filename)
+{
+	xmlDoc *doc;
+
+	doc = e_xml_parse_file (filename);
+	if (doc) {
+		xmlNode *node = xmlDocGetRootElement (doc);
+		e_table_specification_load_from_node (specification, node);
+		xmlFreeDoc (doc);
+		return TRUE;
+	}
+	return FALSE;
+}
+
+/**
+ * e_table_specification_load_from_string:
+ * @specification: An ETableSpecification that you want to modify
+ * @xml: a stringified representation of an ETableSpecification description.
+ *
+ * This routine modifies @specification to reflect the state described
+ * by @xml.  @xml is typically returned by e_table_specification_save_to_string
+ * or it can be embedded in your source code.
+ *
+ * Returns: TRUE on success, FALSE on failure.
+ */
+gboolean
+e_table_specification_load_from_string (ETableSpecification *specification,
+                                        const gchar *xml)
+{
+	xmlDoc *doc;
+	doc = xmlParseMemory ((gchar *) xml, strlen (xml));
+	if (doc) {
+		xmlNode *node = xmlDocGetRootElement (doc);
+		e_table_specification_load_from_node (specification, node);
+		xmlFreeDoc (doc);
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+/**
+ * e_table_specification_load_from_node:
+ * @specification: An ETableSpecification that you want to modify
+ * @node: an xmlNode with an XML ETableSpecification description.
+ *
+ * This routine modifies @specification to reflect the state described
+ * by @node.
+ */
+void
+e_table_specification_load_from_node (ETableSpecification *specification,
+                                      const xmlNode *node)
+{
+	gchar *temp;
+	xmlNode *children;
+	GList *list = NULL, *list2;
+	gint i;
+
+	specification->no_headers = e_xml_get_bool_prop_by_name (node, (const guchar *)"no-headers");
+	specification->click_to_add = e_xml_get_bool_prop_by_name (node, (const guchar *)"click-to-add");
+	specification->click_to_add_end = e_xml_get_bool_prop_by_name (node, (const guchar *)"click-to-add-end") && specification->click_to_add;
+	specification->alternating_row_colors = e_xml_get_bool_prop_by_name_with_default (node, (const guchar *)"alternating-row-colors", TRUE);
+	specification->horizontal_draw_grid = e_xml_get_bool_prop_by_name (node, (const guchar *)"horizontal-draw-grid");
+	specification->vertical_draw_grid = e_xml_get_bool_prop_by_name (node, (const guchar *)"vertical-draw-grid");
+	if (e_xml_get_bool_prop_by_name_with_default (node, (const guchar *)"draw-grid", TRUE) ==
+	    e_xml_get_bool_prop_by_name_with_default (node, (const guchar *)"draw-grid", FALSE)) {
+		specification->horizontal_draw_grid =
+			specification->vertical_draw_grid = e_xml_get_bool_prop_by_name (node, (const guchar *)"draw-grid");
+	}
+	specification->draw_focus = e_xml_get_bool_prop_by_name_with_default (node, (const guchar *)"draw-focus", TRUE);
+	specification->horizontal_scrolling = e_xml_get_bool_prop_by_name_with_default (node, (const guchar *)"horizontal-scrolling", FALSE);
+	specification->horizontal_resize = e_xml_get_bool_prop_by_name_with_default (node, (const guchar *)"horizontal-resize", FALSE);
+	specification->allow_grouping = e_xml_get_bool_prop_by_name_with_default (node, (const guchar *)"allow-grouping", TRUE);
+
+	specification->selection_mode = GTK_SELECTION_MULTIPLE;
+	temp = e_xml_get_string_prop_by_name (node, (const guchar *)"selection-mode");
+	if (temp && !g_ascii_strcasecmp (temp, "single")) {
+		specification->selection_mode = GTK_SELECTION_SINGLE;
+	} else if (temp && !g_ascii_strcasecmp (temp, "browse")) {
+		specification->selection_mode = GTK_SELECTION_BROWSE;
+	} else if (temp && !g_ascii_strcasecmp (temp, "extended")) {
+		specification->selection_mode = GTK_SELECTION_MULTIPLE;
+	}
+	g_free (temp);
+
+	specification->cursor_mode = E_CURSOR_SIMPLE;
+	temp = e_xml_get_string_prop_by_name (node, (const guchar *)"cursor-mode");
+	if (temp && !g_ascii_strcasecmp (temp, "line")) {
+		specification->cursor_mode = E_CURSOR_LINE;
+	} else	if (temp && !g_ascii_strcasecmp (temp, "spreadsheet")) {
+		specification->cursor_mode = E_CURSOR_SPREADSHEET;
+	}
+	g_free (temp);
+
+	g_free (specification->click_to_add_message);
+	specification->click_to_add_message =
+		e_xml_get_string_prop_by_name (
+			node, (const guchar *)"_click-to-add-message");
+
+	g_free (specification->domain);
+	specification->domain =
+		e_xml_get_string_prop_by_name (
+			node, (const guchar *)"gettext-domain");
+	if (specification->domain && !*specification->domain) {
+		g_free (specification->domain);
+		specification->domain = NULL;
+	}
+
+	if (specification->state)
+		g_object_unref (specification->state);
+	specification->state = NULL;
+	if (specification->columns) {
+		for (i = 0; specification->columns[i]; i++) {
+			g_object_unref (specification->columns[i]);
+		}
+		g_free (specification->columns);
+	}
+	specification->columns = NULL;
+
+	for (children = node->xmlChildrenNode; children; children = children->next) {
+		if (!strcmp ((gchar *) children->name, "ETableColumn")) {
+			ETableColumnSpecification *col_spec = e_table_column_specification_new ();
+
+			e_table_column_specification_load_from_node (col_spec, children);
+			list = g_list_append (list, col_spec);
+		} else if (specification->state == NULL && !strcmp ((gchar *) children->name, "ETableState")) {
+			specification->state = e_table_state_new ();
+			e_table_state_load_from_node (specification->state, children);
+			e_table_sort_info_set_can_group (specification->state->sort_info, specification->allow_grouping);
+		}
+	}
+
+	if (specification->state == NULL) {
+		/* Make the default state.  */
+		specification->state = e_table_state_vanilla (g_list_length (list));
+	}
+
+	specification->columns = g_new (ETableColumnSpecification *, g_list_length (list) + 1);
+	for (list2 = list, i = 0; list2; list2 = g_list_next (list2), i++) {
+		specification->columns[i] = list2->data;
+	}
+	specification->columns[i] = NULL;
+	g_list_free (list);
+}
+
+/**
+ * e_table_specification_save_to_file:
+ * @specification: An %ETableSpecification that you want to save
+ * @filename: a file name to store the specification.
+ *
+ * This routine stores the @specification into @filename.
+ *
+ * Returns: 0 on success or -1 on error.
+ */
+gint
+e_table_specification_save_to_file (ETableSpecification *specification,
+                                    const gchar *filename)
+{
+	xmlDoc *doc;
+	gint ret;
+
+	g_return_val_if_fail (specification != NULL, -1);
+	g_return_val_if_fail (filename != NULL, -1);
+	g_return_val_if_fail (E_IS_TABLE_SPECIFICATION (specification), -1);
+
+	if ((doc = xmlNewDoc ((const guchar *)"1.0")) == NULL)
+		return -1;
+
+	xmlDocSetRootElement (doc, e_table_specification_save_to_node (specification, doc));
+
+	ret = e_xml_save_file (filename, doc);
+
+	xmlFreeDoc (doc);
+
+	return ret;
+}
+
+/**
+ * e_table_specification_save_to_string:
+ * @specification: An %ETableSpecification that you want to stringify
+ *
+ * Saves the state of @specification to a string.
+ *
+ * Returns: an g_alloc() allocated string containing the stringified
+ * representation of @specification.  This stringified representation
+ * uses XML as a convenience.
+ */
+gchar *
+e_table_specification_save_to_string (ETableSpecification *specification)
+{
+	gchar *ret_val;
+	xmlChar *string;
+	gint length;
+	xmlDoc *doc;
+
+	g_return_val_if_fail (specification != NULL, NULL);
+	g_return_val_if_fail (E_IS_TABLE_SPECIFICATION (specification), NULL);
+
+	doc = xmlNewDoc ((const guchar *)"1.0");
+	xmlDocSetRootElement (doc, e_table_specification_save_to_node (specification, doc));
+	xmlDocDumpMemory (doc, &string, &length);
+
+	ret_val = g_strdup ((gchar *) string);
+	xmlFree (string);
+	return ret_val;
+}
+
+/**
+ * e_table_specification_save_to_node:
+ * @specification: An ETableSpecification that you want to store.
+ * @doc: Node where the specification is saved
+ *
+ * This routine saves the %ETableSpecification state in the object @specification
+ * into the xmlDoc represented by @doc.
+ *
+ * Returns: The node that has been attached to @doc with the contents
+ * of the ETableSpecification.
+ */
+xmlNode *
+e_table_specification_save_to_node (ETableSpecification *specification,
+                                    xmlDoc *doc)
+{
+	xmlNode *node;
+	const gchar *s;
+
+	g_return_val_if_fail (doc != NULL, NULL);
+	g_return_val_if_fail (specification != NULL, NULL);
+	g_return_val_if_fail (E_IS_TABLE_SPECIFICATION (specification), NULL);
+
+	node = xmlNewNode (NULL, (const guchar *)"ETableSpecification");
+	e_xml_set_bool_prop_by_name (node, (const guchar *)"no-headers", specification->no_headers);
+	e_xml_set_bool_prop_by_name (node, (const guchar *)"click-to-add", specification->click_to_add);
+	e_xml_set_bool_prop_by_name (node, (const guchar *)"click-to-add-end", specification->click_to_add_end && specification->click_to_add);
+	e_xml_set_bool_prop_by_name (node, (const guchar *)"alternating-row-colors", specification->alternating_row_colors);
+	e_xml_set_bool_prop_by_name (node, (const guchar *)"horizontal-draw-grid", specification->horizontal_draw_grid);
+	e_xml_set_bool_prop_by_name (node, (const guchar *)"vertical-draw-grid", specification->vertical_draw_grid);
+	e_xml_set_bool_prop_by_name (node, (const guchar *)"draw-focus", specification->draw_focus);
+	e_xml_set_bool_prop_by_name (node, (const guchar *)"horizontal-scrolling", specification->horizontal_scrolling);
+	e_xml_set_bool_prop_by_name (node, (const guchar *)"horizontal-resize", specification->horizontal_resize);
+	e_xml_set_bool_prop_by_name (node, (const guchar *)"allow-grouping", specification->allow_grouping);
+
+	switch (specification->selection_mode) {
+	case GTK_SELECTION_SINGLE:
+		s = "single";
+		break;
+	case GTK_SELECTION_BROWSE:
+		s = "browse";
+		break;
+	default:
+	case GTK_SELECTION_MULTIPLE:
+		s = "extended";
+	}
+	xmlSetProp (node, (const guchar *)"selection-mode", (guchar *) s);
+	if (specification->cursor_mode == E_CURSOR_LINE)
+		s = "line";
+	else
+		s = "cell";
+	xmlSetProp (node, (const guchar *)"cursor-mode", (guchar *) s);
+
+	xmlSetProp (node, (const guchar *)"_click-to-add-message", (guchar *) specification->click_to_add_message);
+	xmlSetProp (node, (const guchar *)"gettext-domain", (guchar *) specification->domain);
+
+	if (specification->columns) {
+		gint i;
+
+		for (i = 0; specification->columns[i]; i++)
+			e_table_column_specification_save_to_node (
+				specification->columns[i],
+				node);
+	}
+
+	if (specification->state)
+		e_table_state_save_to_node (specification->state, node);
+
+	return node;
+}
+
+/**
+ * e_table_specification_duplicate:
+ * @spec: specification to duplicate
+ *
+ * This creates a copy of the %ETableSpecification @spec
+ *
+ * Returns: The duplicated %ETableSpecification.
+ */
+ETableSpecification *
+e_table_specification_duplicate (ETableSpecification *spec)
+{
+	ETableSpecification *new_spec;
+	gchar *spec_str;
+
+	g_return_val_if_fail (spec != NULL, NULL);
+	g_return_val_if_fail (E_IS_TABLE_SPECIFICATION (spec), NULL);
+
+	new_spec = e_table_specification_new ();
+	spec_str = e_table_specification_save_to_string (spec);
+	if (!e_table_specification_load_from_string (new_spec, spec_str)) {
+		g_warning ("Unable to duplicate ETable specification");
+		g_object_unref (new_spec);
+		new_spec = NULL;
+	}
+	g_free (spec_str);
+
+	return new_spec;
+}
diff --git a/e-util/e-table-specification.h b/e-util/e-table-specification.h
new file mode 100644
index 0000000..8ed43ae
--- /dev/null
+++ b/e-util/e-table-specification.h
@@ -0,0 +1,116 @@
+/*
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef _E_TABLE_SPECIFICATION_H_
+#define _E_TABLE_SPECIFICATION_H_
+
+#include <libxml/tree.h>
+
+#include <e-util/e-selection-model.h>
+#include <e-util/e-table-column-specification.h>
+#include <e-util/e-table-defines.h>
+#include <e-util/e-table-state.h>
+
+/* Standard GObject macros */
+#define E_TYPE_TABLE_SPECIFICATION \
+	(e_table_specification_get_type ())
+#define E_TABLE_SPECIFICATION(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_TABLE_SPECIFICATION, ETableSpecification))
+#define E_TABLE_SPECIFICATION_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_TABLE_SPECIFICATION, ETableSpecificationClass))
+#define E_IS_TABLE_SPECIFICATION(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_TABLE_SPECIFICATION))
+#define E_IS_TABLE_SPECIFICATION_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_TABLE_SPECIFICATION))
+#define E_TABLE_SPECIFICATION_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_TABLE_SPECIFICATION, ETableSpecificationClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ETableSpecification ETableSpecification;
+typedef struct _ETableSpecificationClass ETableSpecificationClass;
+
+struct _ETableSpecification {
+	GObject parent;
+
+	ETableColumnSpecification **columns;
+	ETableState *state;
+
+	guint alternating_row_colors : 1;
+	guint no_headers : 1;
+	guint click_to_add : 1;
+	guint click_to_add_end : 1;
+	guint horizontal_draw_grid : 1;
+	guint vertical_draw_grid : 1;
+	guint draw_focus : 1;
+	guint horizontal_scrolling : 1;
+	guint horizontal_resize : 1;
+	guint allow_grouping : 1;
+	GtkSelectionMode selection_mode;
+	ECursorMode cursor_mode;
+
+	gchar *click_to_add_message;
+	gchar *domain;
+};
+
+struct _ETableSpecificationClass {
+	GObjectClass parent_class;
+};
+
+GType		e_table_specification_get_type	(void) G_GNUC_CONST;
+ETableSpecification *
+		e_table_specification_new	(void);
+
+gboolean	e_table_specification_load_from_file
+						(ETableSpecification *specification,
+						 const gchar *filename);
+gboolean	e_table_specification_load_from_string
+						(ETableSpecification *specification,
+						 const gchar *xml);
+void		e_table_specification_load_from_node
+						(ETableSpecification *specification,
+						 const xmlNode *node);
+
+gint		e_table_specification_save_to_file
+						(ETableSpecification *specification,
+						 const gchar *filename);
+gchar *		e_table_specification_save_to_string
+						(ETableSpecification *specification);
+xmlNode *	e_table_specification_save_to_node
+						(ETableSpecification *specification,
+						 xmlDoc              *doc);
+ETableSpecification *
+		e_table_specification_duplicate	(ETableSpecification *specification);
+
+G_END_DECLS
+
+#endif /* _E_TABLE_SPECIFICATION_H_ */
diff --git a/e-util/e-table-state.c b/e-util/e-table-state.c
new file mode 100644
index 0000000..e5253be
--- /dev/null
+++ b/e-util/e-table-state.c
@@ -0,0 +1,320 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-table-state.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <libxml/parser.h>
+#include <libxml/xmlmemory.h>
+
+#include <libedataserver/libedataserver.h>
+
+#include "e-xml-utils.h"
+
+#define STATE_VERSION 0.1
+
+G_DEFINE_TYPE (ETableState, e_table_state, G_TYPE_OBJECT)
+
+static void
+etst_dispose (GObject *object)
+{
+	ETableState *etst = E_TABLE_STATE (object);
+
+	if (etst->sort_info) {
+		g_object_unref (etst->sort_info);
+		etst->sort_info = NULL;
+	}
+
+	G_OBJECT_CLASS (e_table_state_parent_class)->dispose (object);
+}
+
+static void
+etst_finalize (GObject *object)
+{
+	ETableState *etst = E_TABLE_STATE (object);
+
+	if (etst->columns) {
+		g_free (etst->columns);
+		etst->columns = NULL;
+	}
+
+	if (etst->expansions) {
+		g_free (etst->expansions);
+		etst->expansions = NULL;
+	}
+
+	G_OBJECT_CLASS (e_table_state_parent_class)->finalize (object);
+}
+
+static void
+e_table_state_class_init (ETableStateClass *class)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+	object_class->dispose = etst_dispose;
+	object_class->finalize = etst_finalize;
+}
+
+static void
+e_table_state_init (ETableState *state)
+{
+	state->columns = NULL;
+	state->expansions = NULL;
+	state->sort_info = e_table_sort_info_new ();
+}
+
+ETableState *
+e_table_state_new (void)
+{
+	return g_object_new (E_TYPE_TABLE_STATE, NULL);
+}
+
+ETableState *
+e_table_state_vanilla (gint col_count)
+{
+	GString *str;
+	gint i;
+	ETableState *res;
+
+	str = g_string_new ("<ETableState>\n");
+	for (i = 0; i < col_count; i++)
+		g_string_append_printf (str, "  <column source=\"%d\"/>\n", i);
+	g_string_append (str, "  <grouping></grouping>\n");
+	g_string_append (str, "</ETableState>\n");
+
+	res = e_table_state_new ();
+	e_table_state_load_from_string (res, str->str);
+
+	g_string_free (str, TRUE);
+	return res;
+}
+
+gboolean
+e_table_state_load_from_file (ETableState *state,
+                              const gchar *filename)
+{
+	xmlDoc *doc;
+
+	g_return_val_if_fail (E_IS_TABLE_STATE (state), FALSE);
+	g_return_val_if_fail (filename != NULL, FALSE);
+
+	doc = e_xml_parse_file (filename);
+	if (doc) {
+		xmlNode *node = xmlDocGetRootElement (doc);
+		e_table_state_load_from_node (state, node);
+		xmlFreeDoc (doc);
+		return TRUE;
+	}
+	return FALSE;
+}
+
+void
+e_table_state_load_from_string (ETableState *state,
+                                const gchar *xml)
+{
+	xmlDoc *doc;
+
+	g_return_if_fail (E_IS_TABLE_STATE (state));
+	g_return_if_fail (xml != NULL);
+
+	doc = xmlParseMemory ((gchar *) xml, strlen (xml));
+	if (doc) {
+		xmlNode *node = xmlDocGetRootElement (doc);
+		e_table_state_load_from_node (state, node);
+		xmlFreeDoc (doc);
+	}
+}
+
+typedef struct {
+	gint column;
+	gdouble expansion;
+} int_and_double;
+
+void
+e_table_state_load_from_node (ETableState *state,
+                              const xmlNode *node)
+{
+	xmlNode *children;
+	GList *list = NULL, *iterator;
+	gdouble state_version;
+	gint i;
+	gboolean can_group = TRUE;
+
+	g_return_if_fail (E_IS_TABLE_STATE (state));
+	g_return_if_fail (node != NULL);
+
+	state_version = e_xml_get_double_prop_by_name_with_default (
+		node, (const guchar *)"state-version", STATE_VERSION);
+
+	if (state->sort_info) {
+		can_group = e_table_sort_info_get_can_group (state->sort_info);
+		g_object_unref (state->sort_info);
+	}
+
+	state->sort_info = NULL;
+	children = node->xmlChildrenNode;
+	for (; children; children = children->next) {
+		if (!strcmp ((gchar *) children->name, "column")) {
+			int_and_double *column_info = g_new (int_and_double, 1);
+
+			column_info->column = e_xml_get_integer_prop_by_name (
+				children, (const guchar *)"source");
+			column_info->expansion =
+				e_xml_get_double_prop_by_name_with_default (
+					children, (const guchar *)"expansion", 1);
+
+			list = g_list_append (list, column_info);
+		} else if (state->sort_info == NULL &&
+			   !strcmp ((gchar *) children->name, "grouping")) {
+			state->sort_info = e_table_sort_info_new ();
+			e_table_sort_info_load_from_node (
+				state->sort_info, children, state_version);
+		}
+	}
+	g_free (state->columns);
+	g_free (state->expansions);
+	state->col_count = g_list_length (list);
+	state->columns = g_new (int, state->col_count);
+	state->expansions = g_new (double, state->col_count);
+
+	if (!state->sort_info)
+		state->sort_info = e_table_sort_info_new ();
+	e_table_sort_info_set_can_group (state->sort_info, can_group);
+
+	for (iterator = list, i = 0; iterator; i++) {
+		int_and_double *column_info = iterator->data;
+
+		state->columns[i] = column_info->column;
+		state->expansions[i] = column_info->expansion;
+		g_free (column_info);
+		iterator = g_list_next (iterator);
+	}
+	g_list_free (list);
+}
+
+void
+e_table_state_save_to_file (ETableState *state,
+                            const gchar *filename)
+{
+	xmlDoc *doc;
+
+	if ((doc = xmlNewDoc ((const guchar *)"1.0")) == NULL)
+		return;
+
+	xmlDocSetRootElement (doc, e_table_state_save_to_node (state, NULL));
+
+	e_xml_save_file (filename, doc);
+
+	xmlFreeDoc (doc);
+}
+
+gchar *
+e_table_state_save_to_string (ETableState *state)
+{
+	gchar *ret_val;
+	xmlChar *string;
+	gint length;
+	xmlDoc *doc;
+
+	g_return_val_if_fail (E_IS_TABLE_STATE (state), NULL);
+
+	doc = xmlNewDoc ((const guchar *)"1.0");
+	xmlDocSetRootElement (doc, e_table_state_save_to_node (state, NULL));
+	xmlDocDumpMemory (doc, &string, &length);
+	xmlFreeDoc (doc);
+
+	ret_val = g_strdup ((gchar *) string);
+	xmlFree (string);
+	return ret_val;
+}
+
+xmlNode *
+e_table_state_save_to_node (ETableState *state,
+                            xmlNode *parent)
+{
+	gint i;
+	xmlNode *node;
+
+	g_return_val_if_fail (E_IS_TABLE_STATE (state), NULL);
+
+	if (parent)
+		node = xmlNewChild (
+			parent, NULL, (const guchar *) "ETableState", NULL);
+	else
+		node = xmlNewNode (NULL, (const guchar *) "ETableState");
+
+	e_xml_set_double_prop_by_name (
+		node, (const guchar *)"state-version", STATE_VERSION);
+
+	for (i = 0; i < state->col_count; i++) {
+		gint column = state->columns[i];
+		gdouble expansion = state->expansions[i];
+		xmlNode *new_node;
+
+		new_node = xmlNewChild (
+			node, NULL, (const guchar *) "column", NULL);
+		e_xml_set_integer_prop_by_name (
+			new_node, (const guchar *) "source", column);
+		if (expansion >= -1)
+			e_xml_set_double_prop_by_name (
+				new_node, (const guchar *)
+				"expansion", expansion);
+	}
+
+	e_table_sort_info_save_to_node (state->sort_info, node);
+
+	return node;
+}
+
+/**
+ * e_table_state_duplicate:
+ * @state: The ETableState to duplicate
+ *
+ * This creates a copy of the %ETableState @state
+ *
+ * Returns: The duplicated %ETableState.
+ */
+ETableState *
+e_table_state_duplicate (ETableState *state)
+{
+	ETableState *new_state;
+	gchar *copy;
+
+	g_return_val_if_fail (E_IS_TABLE_STATE (state), NULL);
+
+	new_state = e_table_state_new ();
+	copy = e_table_state_save_to_string (state);
+	e_table_state_load_from_string (new_state, copy);
+	g_free (copy);
+
+	e_table_sort_info_set_can_group
+		(new_state->sort_info,
+		 e_table_sort_info_get_can_group (state->sort_info));
+
+	return new_state;
+}
diff --git a/e-util/e-table-state.h b/e-util/e-table-state.h
new file mode 100644
index 0000000..ac3cfc2
--- /dev/null
+++ b/e-util/e-table-state.h
@@ -0,0 +1,89 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef _E_TABLE_STATE_H_
+#define _E_TABLE_STATE_H_
+
+#include <libxml/tree.h>
+
+#include <e-util/e-table-sort-info.h>
+
+/* Standard GObject macros */
+#define E_TYPE_TABLE_STATE \
+	(e_table_state_get_type ())
+#define E_TABLE_STATE(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_TABLE_STATE, ETableState))
+#define E_TABLE_STATE_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_TABLE_STATE, ETableStateClass))
+#define E_IS_TABLE_STATE(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_TABLE_STATE))
+#define E_IS_TABLE_STATE_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_TABLE_STATE))
+#define E_TABLE_STATE_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_TABLE_STATE, ETableStateClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ETableState ETableState;
+typedef struct _ETableStateClass ETableStateClass;
+
+struct _ETableState {
+	GObject parent;
+
+	ETableSortInfo *sort_info;
+	gint col_count;
+	gint *columns;
+	gdouble *expansions;
+};
+
+struct _ETableStateClass {
+	GObjectClass parent_class;
+};
+
+GType		e_table_state_get_type		(void) G_GNUC_CONST;
+ETableState *	e_table_state_new		(void);
+ETableState *	e_table_state_vanilla		(gint col_count);
+gboolean	e_table_state_load_from_file	(ETableState *state,
+						 const gchar *filename);
+void		e_table_state_load_from_string	(ETableState *state,
+						 const gchar *xml);
+void		e_table_state_load_from_node	(ETableState *state,
+						 const xmlNode *node);
+void		e_table_state_save_to_file	(ETableState *state,
+						 const gchar *filename);
+gchar *		e_table_state_save_to_string	(ETableState *state);
+xmlNode *	e_table_state_save_to_node	(ETableState *state,
+						 xmlNode *parent);
+ETableState *	e_table_state_duplicate		(ETableState *state);
+
+G_END_DECLS
+
+#endif /* _E_TABLE_STATE_H_ */
diff --git a/e-util/e-table-subset-variable.c b/e-util/e-table-subset-variable.c
new file mode 100644
index 0000000..8d9f3d0
--- /dev/null
+++ b/e-util/e-table-subset-variable.c
@@ -0,0 +1,267 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "e-table-subset-variable.h"
+
+#define ETSSV_CLASS(e) (E_TABLE_SUBSET_VARIABLE_GET_CLASS (e))
+
+/* workaround for avoiding API breakage */
+#define etssv_get_type e_table_subset_variable_get_type
+G_DEFINE_TYPE (ETableSubsetVariable, etssv, E_TYPE_TABLE_SUBSET)
+
+#define INCREMENT_AMOUNT 10
+
+static void
+etssv_add (ETableSubsetVariable *etssv,
+           gint row)
+{
+	ETableModel *etm = E_TABLE_MODEL (etssv);
+	ETableSubset *etss = E_TABLE_SUBSET (etssv);
+
+	e_table_model_pre_change (etm);
+
+	if (etss->n_map + 1 > etssv->n_vals_allocated) {
+		etssv->n_vals_allocated += INCREMENT_AMOUNT;
+		etss->map_table = g_realloc (
+			etss->map_table,
+			etssv->n_vals_allocated * sizeof (gint));
+	}
+
+	etss->map_table[etss->n_map++] = row;
+
+	e_table_model_row_inserted (etm, etss->n_map - 1);
+}
+
+static void
+etssv_add_array (ETableSubsetVariable *etssv,
+                 const gint *array,
+                 gint count)
+{
+	ETableModel *etm = E_TABLE_MODEL (etssv);
+	ETableSubset *etss = E_TABLE_SUBSET (etssv);
+	gint i;
+
+	e_table_model_pre_change (etm);
+
+	if (etss->n_map + count > etssv->n_vals_allocated) {
+		etssv->n_vals_allocated += MAX (INCREMENT_AMOUNT, count);
+		etss->map_table = g_realloc (
+			etss->map_table,
+			etssv->n_vals_allocated * sizeof (gint));
+	}
+	for (i = 0; i < count; i++)
+		etss->map_table[etss->n_map++] = array[i];
+
+	e_table_model_changed (etm);
+}
+
+static void
+etssv_add_all (ETableSubsetVariable *etssv)
+{
+	ETableModel *etm = E_TABLE_MODEL (etssv);
+	ETableSubset *etss = E_TABLE_SUBSET (etssv);
+	gint rows;
+	gint i;
+
+	e_table_model_pre_change (etm);
+
+	rows = e_table_model_row_count (etss->source);
+	if (etss->n_map + rows > etssv->n_vals_allocated) {
+		etssv->n_vals_allocated += MAX (INCREMENT_AMOUNT, rows);
+		etss->map_table = g_realloc (
+			etss->map_table,
+			etssv->n_vals_allocated * sizeof (gint));
+	}
+	for (i = 0; i < rows; i++)
+		etss->map_table[etss->n_map++] = i;
+
+	e_table_model_changed (etm);
+}
+
+static gboolean
+etssv_remove (ETableSubsetVariable *etssv,
+              gint row)
+{
+	ETableModel *etm = E_TABLE_MODEL (etssv);
+	ETableSubset *etss = E_TABLE_SUBSET (etssv);
+	gint i;
+
+	for (i = 0; i < etss->n_map; i++) {
+		if (etss->map_table[i] == row) {
+			e_table_model_pre_change (etm);
+			memmove (
+				etss->map_table + i,
+				etss->map_table + i + 1,
+				(etss->n_map - i - 1) * sizeof (gint));
+			etss->n_map--;
+
+			e_table_model_row_deleted (etm, i);
+			return TRUE;
+		}
+	}
+	return FALSE;
+}
+
+static void
+etssv_class_init (ETableSubsetVariableClass *class)
+{
+	class->add     = etssv_add;
+	class->add_array = etssv_add_array;
+	class->add_all = etssv_add_all;
+	class->remove  = etssv_remove;
+}
+
+static void
+etssv_init (ETableSubsetVariable *etssv)
+{
+	/* nothing to do */
+}
+
+ETableModel *
+e_table_subset_variable_construct (ETableSubsetVariable *etssv,
+                                   ETableModel *source)
+{
+	if (e_table_subset_construct (E_TABLE_SUBSET (etssv), source, 1) == NULL)
+		return NULL;
+	E_TABLE_SUBSET (etssv)->n_map = 0;
+
+	return E_TABLE_MODEL (etssv);
+}
+
+ETableModel *
+e_table_subset_variable_new (ETableModel *source)
+{
+	ETableSubsetVariable *etssv = g_object_new (E_TYPE_TABLE_SUBSET_VARIABLE, NULL);
+
+	if (e_table_subset_variable_construct (etssv, source) == NULL) {
+		g_object_unref (etssv);
+		return NULL;
+	}
+
+	return (ETableModel *) etssv;
+}
+
+void
+e_table_subset_variable_add (ETableSubsetVariable *etssv,
+                             gint row)
+{
+	g_return_if_fail (etssv != NULL);
+	g_return_if_fail (E_IS_TABLE_SUBSET_VARIABLE (etssv));
+
+	if (ETSSV_CLASS (etssv)->add)
+		ETSSV_CLASS (etssv)->add (etssv, row);
+}
+
+void
+e_table_subset_variable_add_array (ETableSubsetVariable *etssv,
+                                   const gint *array,
+                                   gint count)
+{
+	g_return_if_fail (etssv != NULL);
+	g_return_if_fail (E_IS_TABLE_SUBSET_VARIABLE (etssv));
+
+	if (ETSSV_CLASS (etssv)->add_array)
+		ETSSV_CLASS (etssv)->add_array (etssv, array, count);
+}
+
+void
+e_table_subset_variable_add_all (ETableSubsetVariable *etssv)
+{
+	g_return_if_fail (etssv != NULL);
+	g_return_if_fail (E_IS_TABLE_SUBSET_VARIABLE (etssv));
+
+	if (ETSSV_CLASS (etssv)->add_all)
+		ETSSV_CLASS (etssv)->add_all (etssv);
+}
+
+gboolean
+e_table_subset_variable_remove (ETableSubsetVariable *etssv,
+                                gint row)
+{
+	g_return_val_if_fail (etssv != NULL, FALSE);
+	g_return_val_if_fail (E_IS_TABLE_SUBSET_VARIABLE (etssv), FALSE);
+
+	if (ETSSV_CLASS (etssv)->remove)
+		return ETSSV_CLASS (etssv)->remove (etssv, row);
+	else
+		return FALSE;
+}
+
+void
+e_table_subset_variable_clear (ETableSubsetVariable *etssv)
+{
+	ETableModel *etm = E_TABLE_MODEL (etssv);
+	ETableSubset *etss = E_TABLE_SUBSET (etssv);
+
+	e_table_model_pre_change (etm);
+	etss->n_map = 0;
+	g_free (etss->map_table);
+	etss->map_table = (gint *) g_new (guint, 1);
+	etssv->n_vals_allocated = 1;
+
+	e_table_model_changed (etm);
+}
+
+void
+e_table_subset_variable_increment (ETableSubsetVariable *etssv,
+                                   gint position,
+                                   gint amount)
+{
+	gint i;
+	ETableSubset *etss = E_TABLE_SUBSET (etssv);
+	for (i = 0; i < etss->n_map; i++) {
+		if (etss->map_table[i] >= position)
+			etss->map_table[i] += amount;
+	}
+}
+
+void
+e_table_subset_variable_decrement (ETableSubsetVariable *etssv,
+                                   gint position,
+                                   gint amount)
+{
+	gint i;
+	ETableSubset *etss = E_TABLE_SUBSET (etssv);
+	for (i = 0; i < etss->n_map; i++) {
+		if (etss->map_table[i] >= position)
+			etss->map_table[i] -= amount;
+	}
+}
+
+void
+e_table_subset_variable_set_allocation (ETableSubsetVariable *etssv,
+                                        gint total)
+{
+	ETableSubset *etss = E_TABLE_SUBSET (etssv);
+	if (total <= 0)
+		total = 1;
+	if (total > etss->n_map) {
+		etss->map_table = g_realloc (etss->map_table, total * sizeof (gint));
+	}
+}
diff --git a/e-util/e-table-subset-variable.h b/e-util/e-table-subset-variable.h
new file mode 100644
index 0000000..ca4addd
--- /dev/null
+++ b/e-util/e-table-subset-variable.h
@@ -0,0 +1,105 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef _E_TABLE_SUBSET_VARIABLE_H_
+#define _E_TABLE_SUBSET_VARIABLE_H_
+
+#include <e-util/e-table-subset.h>
+
+/* Standard GObject macros */
+#define E_TYPE_TABLE_SUBSET_VARIABLE \
+	(e_table_subset_variable_get_type ())
+#define E_TABLE_SUBSET_VARIABLE(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_TABLE_SUBSET_VARIABLE, ETableSubsetVariable))
+#define E_TABLE_SUBSET_VARIABLE_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_TABLE_SUBSET_VARIABLE, ETableSubsetVariableClass))
+#define E_IS_TABLE_SUBSET_VARIABLE(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_TABLE_SUBSET_VARIABLE))
+#define E_IS_TABLE_SUBSET_VARIABLE_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_TABLE_SUBSET_VARIABLE))
+#define E_TABLE_SUBSET_VARIABLE_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_TABLE_SUBSET_VARIABLE, ETableSubsetVariableClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ETableSubsetVariable ETableSubsetVariable;
+typedef struct _ETableSubsetVariableClass ETableSubsetVariableClass;
+
+struct _ETableSubsetVariable {
+	ETableSubset parent;
+	gint n_vals_allocated;
+};
+
+struct _ETableSubsetVariableClass {
+	ETableSubsetClass parent_class;
+
+	void		(*add)			(ETableSubsetVariable *ets,
+						 gint row);
+	void		(*add_array)		(ETableSubsetVariable *ets,
+						 const gint *array,
+						 gint count);
+	void		(*add_all)		(ETableSubsetVariable *ets);
+	gboolean	(*remove)		(ETableSubsetVariable *ets,
+						 gint row);
+};
+
+GType		e_table_subset_variable_get_type
+						(void) G_GNUC_CONST;
+ETableModel *	e_table_subset_variable_new	(ETableModel *etm);
+ETableModel *	e_table_subset_variable_construct
+						(ETableSubsetVariable *etssv,
+						 ETableModel *source);
+void		e_table_subset_variable_add	(ETableSubsetVariable *ets,
+						 gint row);
+void		e_table_subset_variable_add_array
+						(ETableSubsetVariable *ets,
+						 const gint *array,
+						 gint count);
+void		e_table_subset_variable_add_all	(ETableSubsetVariable *ets);
+gboolean	e_table_subset_variable_remove	(ETableSubsetVariable *ets,
+						 gint row);
+void		e_table_subset_variable_clear	(ETableSubsetVariable *ets);
+void		e_table_subset_variable_increment
+						(ETableSubsetVariable *ets,
+						 gint position,
+						 gint amount);
+void		e_table_subset_variable_decrement
+						(ETableSubsetVariable *ets,
+						 gint position,
+						 gint amount);
+void		e_table_subset_variable_set_allocation
+						(ETableSubsetVariable *ets,
+						 gint total);
+
+G_END_DECLS
+
+#endif /* _E_TABLE_SUBSET_VARIABLE_H_ */
+
diff --git a/e-util/e-table-subset.c b/e-util/e-table-subset.c
new file mode 100644
index 0000000..88532d0
--- /dev/null
+++ b/e-util/e-table-subset.c
@@ -0,0 +1,567 @@
+/*
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *		Miguel de Icaza <miguel ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+
+#include "e-table-subset.h"
+
+static void	etss_proxy_model_pre_change_real
+						(ETableSubset *etss,
+						 ETableModel *etm);
+static void	etss_proxy_model_no_change_real	(ETableSubset *etss,
+						 ETableModel *etm);
+static void	etss_proxy_model_changed_real	(ETableSubset *etss,
+						 ETableModel *etm);
+static void	etss_proxy_model_row_changed_real
+						(ETableSubset *etss,
+						 ETableModel *etm,
+						 gint row);
+static void	etss_proxy_model_cell_changed_real
+						(ETableSubset *etss,
+						 ETableModel *etm,
+						 gint col,
+						 gint row);
+static void	etss_proxy_model_rows_inserted_real
+						(ETableSubset *etss,
+						 ETableModel *etm,
+						 gint row,
+						 gint count);
+static void	etss_proxy_model_rows_deleted_real
+						(ETableSubset *etss,
+						 ETableModel *etm,
+						 gint row,
+						 gint count);
+
+#define d(x)
+
+/* workaround for avoding API breakage */
+#define etss_get_type e_table_subset_get_type
+G_DEFINE_TYPE (ETableSubset, etss, E_TYPE_TABLE_MODEL)
+
+#define ETSS_CLASS(object) (E_TABLE_SUBSET_GET_CLASS(object))
+
+#define VALID_ROW(etss, row) (row >= -1 && row < etss->n_map)
+#define MAP_ROW(etss, row) (row == -1 ? -1 : etss->map_table[row])
+
+static gint
+etss_get_view_row (ETableSubset *etss,
+                   gint row)
+{
+	const gint n = etss->n_map;
+	const gint * const map_table = etss->map_table;
+	gint i;
+
+	gint end = MIN (etss->n_map, etss->last_access + 10);
+	gint start = MAX (0, etss->last_access - 10);
+	gint initial = MAX (MIN (etss->last_access, end), start);
+
+	for (i = initial; i < end; i++) {
+		if (map_table[i] == row) {
+			d (g_print ("a) Found %d from %d\n", i, etss->last_access));
+			etss->last_access = i;
+			return i;
+		}
+	}
+
+	for (i = initial - 1; i >= start; i--) {
+		if (map_table[i] == row) {
+			d (g_print ("b) Found %d from %d\n", i, etss->last_access));
+			etss->last_access = i;
+			return i;
+		}
+	}
+
+	for (i = 0; i < n; i++) {
+		if (map_table[i] == row) {
+			d (g_print ("c) Found %d from %d\n", i, etss->last_access));
+			etss->last_access = i;
+			return i;
+		}
+	}
+	return -1;
+}
+
+static void
+etss_dispose (GObject *object)
+{
+	ETableSubset *etss = E_TABLE_SUBSET (object);
+
+	if (etss->source) {
+		g_signal_handler_disconnect (
+			etss->source,
+			etss->table_model_pre_change_id);
+		g_signal_handler_disconnect (
+			etss->source,
+			etss->table_model_no_change_id);
+		g_signal_handler_disconnect (
+			etss->source,
+			etss->table_model_changed_id);
+		g_signal_handler_disconnect (
+			etss->source,
+			etss->table_model_row_changed_id);
+		g_signal_handler_disconnect (
+			etss->source,
+			etss->table_model_cell_changed_id);
+		g_signal_handler_disconnect (
+			etss->source,
+			etss->table_model_rows_inserted_id);
+		g_signal_handler_disconnect (
+			etss->source,
+			etss->table_model_rows_deleted_id);
+
+		g_object_unref (etss->source);
+		etss->source = NULL;
+
+		etss->table_model_changed_id = 0;
+		etss->table_model_row_changed_id = 0;
+		etss->table_model_cell_changed_id = 0;
+		etss->table_model_rows_inserted_id = 0;
+		etss->table_model_rows_deleted_id = 0;
+	}
+
+	G_OBJECT_CLASS (etss_parent_class)->dispose (object);
+}
+
+static void
+etss_finalize (GObject *object)
+{
+	ETableSubset *etss = E_TABLE_SUBSET (object);
+
+	g_free (etss->map_table);
+	etss->map_table = NULL;
+
+	G_OBJECT_CLASS (etss_parent_class)->finalize (object);
+}
+
+static gint
+etss_column_count (ETableModel *etm)
+{
+	ETableSubset *etss = (ETableSubset *) etm;
+
+	return e_table_model_column_count (etss->source);
+}
+
+static gint
+etss_row_count (ETableModel *etm)
+{
+	ETableSubset *etss = (ETableSubset *) etm;
+
+	return etss->n_map;
+}
+
+static gpointer
+etss_value_at (ETableModel *etm,
+               gint col,
+               gint row)
+{
+	ETableSubset *etss = (ETableSubset *) etm;
+
+	g_return_val_if_fail (VALID_ROW (etss, row), NULL);
+
+	etss->last_access = row;
+	d (g_print ("g) Setting last_access to %d\n", row));
+	return e_table_model_value_at (etss->source, col, MAP_ROW (etss, row));
+}
+
+static void
+etss_set_value_at (ETableModel *etm,
+                   gint col,
+                   gint row,
+                   gconstpointer val)
+{
+	ETableSubset *etss = (ETableSubset *) etm;
+
+	g_return_if_fail (VALID_ROW (etss, row));
+
+	etss->last_access = row;
+	d (g_print ("h) Setting last_access to %d\n", row));
+	e_table_model_set_value_at (etss->source, col, MAP_ROW (etss, row), val);
+}
+
+static gboolean
+etss_is_cell_editable (ETableModel *etm,
+                       gint col,
+                       gint row)
+{
+	ETableSubset *etss = (ETableSubset *) etm;
+
+	g_return_val_if_fail (VALID_ROW (etss, row), FALSE);
+
+	return e_table_model_is_cell_editable (etss->source, col, MAP_ROW (etss, row));
+}
+
+static gboolean
+etss_has_save_id (ETableModel *etm)
+{
+	return TRUE;
+}
+
+static gchar *
+etss_get_save_id (ETableModel *etm,
+                  gint row)
+{
+	ETableSubset *etss = (ETableSubset *) etm;
+
+	g_return_val_if_fail (VALID_ROW (etss, row), NULL);
+
+	if (e_table_model_has_save_id (etss->source))
+		return e_table_model_get_save_id (etss->source, MAP_ROW (etss, row));
+	else
+		return g_strdup_printf ("%d", MAP_ROW (etss, row));
+}
+
+static void
+etss_append_row (ETableModel *etm,
+                 ETableModel *source,
+                 gint row)
+{
+	ETableSubset *etss = (ETableSubset *) etm;
+	e_table_model_append_row (etss->source, source, row);
+}
+
+static gpointer
+etss_duplicate_value (ETableModel *etm,
+                      gint col,
+                      gconstpointer value)
+{
+	ETableSubset *etss = (ETableSubset *) etm;
+
+	return e_table_model_duplicate_value (etss->source, col, value);
+}
+
+static void
+etss_free_value (ETableModel *etm,
+                 gint col,
+                 gpointer value)
+{
+	ETableSubset *etss = (ETableSubset *) etm;
+
+	e_table_model_free_value (etss->source, col, value);
+}
+
+static gpointer
+etss_initialize_value (ETableModel *etm,
+                       gint col)
+{
+	ETableSubset *etss = (ETableSubset *) etm;
+
+	return e_table_model_initialize_value (etss->source, col);
+}
+
+static gboolean
+etss_value_is_empty (ETableModel *etm,
+                     gint col,
+                     gconstpointer value)
+{
+	ETableSubset *etss = (ETableSubset *) etm;
+
+	return e_table_model_value_is_empty (etss->source, col, value);
+}
+
+static gchar *
+etss_value_to_string (ETableModel *etm,
+                      gint col,
+                      gconstpointer value)
+{
+	ETableSubset *etss = (ETableSubset *) etm;
+
+	return e_table_model_value_to_string (etss->source, col, value);
+}
+
+static void
+etss_class_init (ETableSubsetClass *class)
+{
+	ETableModelClass *table_class    = E_TABLE_MODEL_CLASS (class);
+	GObjectClass *object_class       = G_OBJECT_CLASS (class);
+
+	object_class->dispose            = etss_dispose;
+	object_class->finalize           = etss_finalize;
+
+	table_class->column_count        = etss_column_count;
+	table_class->row_count           = etss_row_count;
+	table_class->append_row          = etss_append_row;
+
+	table_class->value_at            = etss_value_at;
+	table_class->set_value_at        = etss_set_value_at;
+	table_class->is_cell_editable    = etss_is_cell_editable;
+
+	table_class->has_save_id         = etss_has_save_id;
+	table_class->get_save_id         = etss_get_save_id;
+
+	table_class->duplicate_value     = etss_duplicate_value;
+	table_class->free_value          = etss_free_value;
+	table_class->initialize_value    = etss_initialize_value;
+	table_class->value_is_empty      = etss_value_is_empty;
+	table_class->value_to_string     = etss_value_to_string;
+
+	class->proxy_model_pre_change    = etss_proxy_model_pre_change_real;
+	class->proxy_model_no_change     = etss_proxy_model_no_change_real;
+	class->proxy_model_changed       = etss_proxy_model_changed_real;
+	class->proxy_model_row_changed   = etss_proxy_model_row_changed_real;
+	class->proxy_model_cell_changed  = etss_proxy_model_cell_changed_real;
+	class->proxy_model_rows_inserted = etss_proxy_model_rows_inserted_real;
+	class->proxy_model_rows_deleted  = etss_proxy_model_rows_deleted_real;
+}
+
+static void
+etss_init (ETableSubset *etss)
+{
+	etss->last_access = 0;
+}
+
+static void
+etss_proxy_model_pre_change_real (ETableSubset *etss,
+                                  ETableModel *etm)
+{
+	e_table_model_pre_change (E_TABLE_MODEL (etss));
+}
+
+static void
+etss_proxy_model_no_change_real (ETableSubset *etss,
+                                 ETableModel *etm)
+{
+	e_table_model_no_change (E_TABLE_MODEL (etss));
+}
+
+static void
+etss_proxy_model_changed_real (ETableSubset *etss,
+                               ETableModel *etm)
+{
+	e_table_model_changed (E_TABLE_MODEL (etss));
+}
+
+static void
+etss_proxy_model_row_changed_real (ETableSubset *etss,
+                                   ETableModel *etm,
+                                   gint row)
+{
+	gint view_row = etss_get_view_row (etss, row);
+	if (view_row != -1)
+		e_table_model_row_changed (E_TABLE_MODEL (etss), view_row);
+	else
+		e_table_model_no_change (E_TABLE_MODEL (etss));
+}
+
+static void
+etss_proxy_model_cell_changed_real (ETableSubset *etss,
+                                    ETableModel *etm,
+                                    gint col,
+                                    gint row)
+{
+	gint view_row = etss_get_view_row (etss, row);
+	if (view_row != -1)
+		e_table_model_cell_changed (E_TABLE_MODEL (etss), col, view_row);
+	else
+		e_table_model_no_change (E_TABLE_MODEL (etss));
+}
+
+static void
+etss_proxy_model_rows_inserted_real (ETableSubset *etss,
+                                     ETableModel *etm,
+                                     gint row,
+                                     gint count)
+{
+	e_table_model_no_change (E_TABLE_MODEL (etss));
+}
+
+static void
+etss_proxy_model_rows_deleted_real (ETableSubset *etss,
+                                    ETableModel *etm,
+                                    gint row,
+                                    gint count)
+{
+	e_table_model_no_change (E_TABLE_MODEL (etss));
+}
+
+static void
+etss_proxy_model_pre_change (ETableModel *etm,
+                             ETableSubset *etss)
+{
+	if (ETSS_CLASS (etss)->proxy_model_pre_change)
+		(ETSS_CLASS (etss)->proxy_model_pre_change) (etss, etm);
+}
+
+static void
+etss_proxy_model_no_change (ETableModel *etm,
+                            ETableSubset *etss)
+{
+	if (ETSS_CLASS (etss)->proxy_model_no_change)
+		(ETSS_CLASS (etss)->proxy_model_no_change) (etss, etm);
+}
+
+static void
+etss_proxy_model_changed (ETableModel *etm,
+                          ETableSubset *etss)
+{
+	if (ETSS_CLASS (etss)->proxy_model_changed)
+		(ETSS_CLASS (etss)->proxy_model_changed) (etss, etm);
+}
+
+static void
+etss_proxy_model_row_changed (ETableModel *etm,
+                              gint row,
+                              ETableSubset *etss)
+{
+	if (ETSS_CLASS (etss)->proxy_model_row_changed)
+		(ETSS_CLASS (etss)->proxy_model_row_changed) (etss, etm, row);
+}
+
+static void
+etss_proxy_model_cell_changed (ETableModel *etm,
+                               gint col,
+                               gint row,
+                               ETableSubset *etss)
+{
+	if (ETSS_CLASS (etss)->proxy_model_cell_changed)
+		(ETSS_CLASS (etss)->proxy_model_cell_changed) (etss, etm, col, row);
+}
+
+static void
+etss_proxy_model_rows_inserted (ETableModel *etm,
+                                gint row,
+                                gint col,
+                                ETableSubset *etss)
+{
+	if (ETSS_CLASS (etss)->proxy_model_rows_inserted)
+		(ETSS_CLASS (etss)->proxy_model_rows_inserted) (etss, etm, row, col);
+}
+
+static void
+etss_proxy_model_rows_deleted (ETableModel *etm,
+                               gint row,
+                               gint col,
+                               ETableSubset *etss)
+{
+	if (ETSS_CLASS (etss)->proxy_model_rows_deleted)
+		(ETSS_CLASS (etss)->proxy_model_rows_deleted) (etss, etm, row, col);
+}
+
+ETableModel *
+e_table_subset_construct (ETableSubset *etss,
+                          ETableModel *source,
+                          gint nvals)
+{
+	guint *buffer;
+	gint i;
+
+	if (nvals) {
+		buffer = (guint *) g_malloc (sizeof (guint) * nvals);
+		if (buffer == NULL)
+			return NULL;
+	} else
+		buffer = NULL;
+	etss->map_table = (gint *) buffer;
+	etss->n_map = nvals;
+	etss->source = source;
+	g_object_ref (source);
+
+	/* Init */
+	for (i = 0; i < nvals; i++)
+		etss->map_table[i] = i;
+
+	etss->table_model_pre_change_id = g_signal_connect (
+		source, "model_pre_change",
+		G_CALLBACK (etss_proxy_model_pre_change), etss);
+	etss->table_model_no_change_id = g_signal_connect (
+		source, "model_no_change",
+		G_CALLBACK (etss_proxy_model_no_change), etss);
+	etss->table_model_changed_id = g_signal_connect (
+		source, "model_changed",
+		G_CALLBACK (etss_proxy_model_changed), etss);
+	etss->table_model_row_changed_id = g_signal_connect (
+		source, "model_row_changed",
+		G_CALLBACK (etss_proxy_model_row_changed), etss);
+	etss->table_model_cell_changed_id = g_signal_connect (
+		source, "model_cell_changed",
+		G_CALLBACK (etss_proxy_model_cell_changed), etss);
+	etss->table_model_rows_inserted_id = g_signal_connect (
+		source, "model_rows_inserted",
+		G_CALLBACK (etss_proxy_model_rows_inserted), etss);
+	etss->table_model_rows_deleted_id = g_signal_connect (
+		source, "model_rows_deleted",
+		G_CALLBACK (etss_proxy_model_rows_deleted), etss);
+
+	return E_TABLE_MODEL (etss);
+}
+
+ETableModel *
+e_table_subset_new (ETableModel *source,
+                    const gint nvals)
+{
+	ETableSubset *etss = g_object_new (E_TYPE_TABLE_SUBSET, NULL);
+
+	if (e_table_subset_construct (etss, source, nvals) == NULL) {
+		g_object_unref (etss);
+		return NULL;
+	}
+
+	return (ETableModel *) etss;
+}
+
+gint
+e_table_subset_model_to_view_row (ETableSubset *ets,
+                                  gint model_row)
+{
+	gint i;
+	for (i = 0; i < ets->n_map; i++) {
+		if (ets->map_table[i] == model_row)
+			return i;
+	}
+	return -1;
+}
+
+gint
+e_table_subset_view_to_model_row (ETableSubset *ets,
+                                  gint view_row)
+{
+	if (view_row >= 0 && view_row < ets->n_map)
+		return ets->map_table[view_row];
+	else
+		return -1;
+}
+
+ETableModel *
+e_table_subset_get_toplevel (ETableSubset *table)
+{
+	g_return_val_if_fail (table != NULL, NULL);
+	g_return_val_if_fail (E_IS_TABLE_SUBSET (table), NULL);
+
+	if (E_IS_TABLE_SUBSET (table->source))
+		return e_table_subset_get_toplevel (E_TABLE_SUBSET (table->source));
+	else
+		return table->source;
+}
+
+void
+e_table_subset_print_debugging (ETableSubset *table_model)
+{
+	gint i;
+	for (i = 0; i < table_model->n_map; i++) {
+		g_print ("%8d\n", table_model->map_table[i]);
+	}
+}
diff --git a/e-util/e-table-subset.h b/e-util/e-table-subset.h
new file mode 100644
index 0000000..9e8d694
--- /dev/null
+++ b/e-util/e-table-subset.h
@@ -0,0 +1,120 @@
+/*
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *		Miguel de Icaza <miguel ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef _E_TABLE_SUBSET_H_
+#define _E_TABLE_SUBSET_H_
+
+#include <e-util/e-table-model.h>
+
+/* Standard GObject macros */
+#define E_TYPE_TABLE_SUBSET \
+	(e_table_subset_get_type ())
+#define E_TABLE_SUBSET(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_TABLE_SUBSET, ETableSubset))
+#define E_TABLE_SUBSET_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_TABLE_SUBSET, ETableSubsetClass))
+#define E_IS_TABLE_SUBSET(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_TABLE_SUBSET))
+#define E_IS_TABLE_SUBSET_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_TABLE_SUBSET))
+#define E_TABLE_SUBSET_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_TABLE_SUBSET, ETableSubsetClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ETableSubset ETableSubset;
+typedef struct _ETableSubsetClass ETableSubsetClass;
+
+struct _ETableSubset {
+	ETableModel parent;
+
+	ETableModel *source;
+	gint n_map;
+	gint *map_table;
+
+	gint last_access;
+
+	gint table_model_pre_change_id;
+	gint table_model_no_change_id;
+	gint table_model_changed_id;
+	gint table_model_row_changed_id;
+	gint table_model_cell_changed_id;
+	gint table_model_rows_inserted_id;
+	gint table_model_rows_deleted_id;
+};
+
+struct _ETableSubsetClass {
+	ETableModelClass parent_class;
+
+	void		(*proxy_model_pre_change)	(ETableSubset *etss,
+							 ETableModel *etm);
+	void		(*proxy_model_no_change)	(ETableSubset *etss,
+							 ETableModel *etm);
+	void		(*proxy_model_changed)		(ETableSubset *etss,
+							 ETableModel *etm);
+	void		(*proxy_model_row_changed)	(ETableSubset *etss,
+							 ETableModel *etm,
+							 gint row);
+	void		(*proxy_model_cell_changed)	(ETableSubset *etss,
+							 ETableModel *etm,
+							 gint col,
+							 gint row);
+	void		(*proxy_model_rows_inserted)	(ETableSubset *etss,
+							 ETableModel *etm,
+							 gint row,
+							 gint count);
+	void		(*proxy_model_rows_deleted)	(ETableSubset *etss,
+							 ETableModel *etm,
+							 gint row,
+							 gint count);
+};
+
+GType		e_table_subset_get_type		(void) G_GNUC_CONST;
+ETableModel *	e_table_subset_new		(ETableModel  *etm,
+						 gint n_vals);
+ETableModel *	e_table_subset_construct	(ETableSubset *ets,
+						 ETableModel *source,
+						 gint nvals);
+gint		e_table_subset_model_to_view_row
+						(ETableSubset *ets,
+						 gint model_row);
+gint		e_table_subset_view_to_model_row
+						(ETableSubset *ets,
+						 gint view_row);
+ETableModel *	e_table_subset_get_toplevel	(ETableSubset *table_model);
+void		e_table_subset_print_debugging	(ETableSubset *table_model);
+
+G_END_DECLS
+
+#endif /* _E_TABLE_SUBSET_H_ */
+
diff --git a/e-util/e-table-utils.c b/e-util/e-table-utils.c
new file mode 100644
index 0000000..b914e59
--- /dev/null
+++ b/e-util/e-table-utils.c
@@ -0,0 +1,224 @@
+/*
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-table-utils.h"
+
+#include <libintl.h>		/* This file uses dgettext() but no _() */
+#include <string.h>
+
+#include "e-table-header-utils.h"
+#include "e-unicode.h"
+
+ETableHeader *
+e_table_state_to_header (GtkWidget *widget,
+                         ETableHeader *full_header,
+                         ETableState *state)
+{
+	ETableHeader *nh;
+	const gint max_cols = e_table_header_count (full_header);
+	gint column;
+	GValue *val = g_new0 (GValue, 1);
+
+	g_return_val_if_fail (widget, NULL);
+	g_return_val_if_fail (full_header, NULL);
+	g_return_val_if_fail (state, NULL);
+
+	nh = e_table_header_new ();
+	g_value_init (val, G_TYPE_DOUBLE);
+	g_value_set_double (val, e_table_header_width_extras (widget));
+	g_object_set_property (G_OBJECT (nh), "width_extras", val);
+	g_free (val);
+
+	for (column = 0; column < state->col_count; column++) {
+		gint col;
+		gdouble expansion;
+		ETableCol *table_col;
+
+		col = state->columns[column];
+		expansion = state->expansions[column];
+
+		if (col >= max_cols)
+			continue;
+
+		table_col = e_table_header_get_column (full_header, col);
+
+		if (expansion >= -1)
+			table_col->expansion = expansion;
+
+		e_table_header_add_column (nh, table_col, -1);
+	}
+
+	return nh;
+}
+
+static ETableCol *
+et_col_spec_to_col (ETableColumnSpecification *col_spec,
+                    ETableExtras *ete,
+                    const gchar *domain)
+{
+	ETableCol *col = NULL;
+	ECell *cell = NULL;
+	GCompareDataFunc compare = NULL;
+	ETableSearchFunc search = NULL;
+
+	if (col_spec->cell)
+		cell = e_table_extras_get_cell (ete, col_spec->cell);
+	if (col_spec->compare)
+		compare = e_table_extras_get_compare (ete, col_spec->compare);
+	if (col_spec->search)
+		search = e_table_extras_get_search (ete, col_spec->search);
+
+	if (cell && compare) {
+		gchar *title = dgettext (domain, col_spec->title);
+
+		title = g_strdup (title);
+
+		if (col_spec->pixbuf && *col_spec->pixbuf) {
+			const gchar *icon_name;
+
+			icon_name = e_table_extras_get_icon_name (
+				ete, col_spec->pixbuf);
+			if (icon_name != NULL) {
+				col = e_table_col_new (
+					col_spec->model_col,
+					title, icon_name,
+					col_spec->expansion,
+					col_spec->minimum_width,
+					cell, compare,
+					col_spec->resizable,
+					col_spec->disabled,
+					col_spec->priority);
+			}
+		}
+
+		if (col == NULL && col_spec->title && *col_spec->title) {
+			col = e_table_col_new (
+				col_spec->model_col, title, NULL,
+				col_spec->expansion,
+				col_spec->minimum_width,
+				cell, compare,
+				col_spec->resizable,
+				col_spec->disabled,
+				col_spec->priority);
+		}
+
+		if (col) {
+			col->search = search;
+			if (col_spec->sortable && !strcmp (col_spec->sortable, "false"))
+				col->sortable = FALSE;
+			else
+				col->sortable = TRUE;
+		}
+		g_free (title);
+	}
+	if (col && col_spec->compare_col != col_spec->model_col)
+		g_object_set (
+			col,
+			"compare_col", col_spec->compare_col,
+			NULL);
+	return col;
+}
+
+ETableHeader *
+e_table_spec_to_full_header (ETableSpecification *spec,
+                             ETableExtras *ete)
+{
+	ETableHeader *nh;
+	gint column;
+
+	g_return_val_if_fail (spec, NULL);
+	g_return_val_if_fail (ete, NULL);
+
+	nh = e_table_header_new ();
+
+	for (column = 0; spec->columns[column]; column++) {
+		ETableCol *col = et_col_spec_to_col (
+			spec->columns[column], ete, spec->domain);
+
+		if (col) {
+			e_table_header_add_column (nh, col, -1);
+			g_object_unref (col);
+		}
+	}
+
+	return nh;
+}
+
+static gboolean
+check_col (ETableCol *col,
+           gpointer user_data)
+{
+	return col->search ? TRUE : FALSE;
+}
+
+ETableCol *
+e_table_util_calculate_current_search_col (ETableHeader *header,
+                                           ETableHeader *full_header,
+                                           ETableSortInfo *sort_info,
+                                           gboolean always_search)
+{
+	gint i;
+	gint count;
+	ETableCol *col = NULL;
+
+	count = e_table_sort_info_grouping_get_count (sort_info);
+
+	for (i = 0; i < count; i++) {
+		ETableSortColumn column;
+
+		column = e_table_sort_info_grouping_get_nth (sort_info, i);
+
+		col = e_table_header_get_column (full_header, column.column);
+
+		if (col && col->search)
+			break;
+
+		col = NULL;
+	}
+
+	if (col == NULL) {
+		count = e_table_sort_info_sorting_get_count (sort_info);
+		for (i = 0; i < count; i++) {
+			ETableSortColumn column;
+
+			column = e_table_sort_info_sorting_get_nth (sort_info, i);
+
+			col = e_table_header_get_column (full_header, column.column);
+
+			if (col && col->search)
+				break;
+
+			col = NULL;
+		}
+	}
+
+	if (col == NULL && always_search) {
+		col = e_table_header_prioritized_column_selected (header, check_col, NULL);
+	}
+
+	return col;
+}
diff --git a/e-util/e-table-utils.h b/e-util/e-table-utils.h
new file mode 100644
index 0000000..1b7b144
--- /dev/null
+++ b/e-util/e-table-utils.h
@@ -0,0 +1,54 @@
+/*
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef _E_TABLE_UTILS_H_
+#define _E_TABLE_UTILS_H_
+
+#include <e-util/e-table-extras.h>
+#include <e-util/e-table-header.h>
+#include <e-util/e-table-specification.h>
+#include <e-util/e-table-state.h>
+
+G_BEGIN_DECLS
+
+ETableHeader *	e_table_state_to_header		(GtkWidget *widget,
+						 ETableHeader *full_header,
+						 ETableState *state);
+
+ETableHeader *	e_table_spec_to_full_header	(ETableSpecification *spec,
+						 ETableExtras *ete);
+
+ETableCol *	e_table_util_calculate_current_search_col
+						(ETableHeader *header,
+						 ETableHeader *full_header,
+						 ETableSortInfo *sort_info,
+						 gboolean always_search);
+
+G_END_DECLS
+
+#endif /* _E_TABLE_UTILS_H_ */
+
diff --git a/e-util/e-table-without.c b/e-util/e-table-without.c
new file mode 100644
index 0000000..7139ad1
--- /dev/null
+++ b/e-util/e-table-without.c
@@ -0,0 +1,412 @@
+/*
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "e-table-without.h"
+
+#define E_TABLE_WITHOUT_GET_PRIVATE(obj) \
+	(G_TYPE_INSTANCE_GET_PRIVATE \
+	((obj), E_TYPE_TABLE_WITHOUT, ETableWithoutPrivate))
+
+/* workaround for avoiding API breakage */
+#define etw_get_type e_table_without_get_type
+G_DEFINE_TYPE (ETableWithout, etw, E_TYPE_TABLE_SUBSET)
+
+#define INCREMENT_AMOUNT 10
+
+struct _ETableWithoutPrivate {
+	GHashTable *hash;
+
+	GHashFunc hash_func;
+	GCompareFunc compare_func;
+
+	ETableWithoutGetKeyFunc get_key_func;
+	ETableWithoutDuplicateKeyFunc duplicate_key_func;
+	ETableWithoutFreeKeyFunc free_gotten_key_func;
+	ETableWithoutFreeKeyFunc free_duplicated_key_func;
+
+	gpointer closure;
+};
+
+static gboolean
+check (ETableWithout *etw,
+       gint model_row)
+{
+	gboolean ret_val;
+	gpointer key;
+	ETableSubset *etss = E_TABLE_SUBSET (etw);
+
+	if (etw->priv->get_key_func)
+		key = etw->priv->get_key_func (etss->source, model_row, etw->priv->closure);
+	else
+		key = GINT_TO_POINTER (model_row);
+	ret_val = (g_hash_table_lookup (etw->priv->hash, key) != NULL);
+	if (etw->priv->free_gotten_key_func)
+		etw->priv->free_gotten_key_func (key, etw->priv->closure);
+	return ret_val;
+}
+
+static gboolean
+check_with_key (ETableWithout *etw,
+                gpointer key,
+                gint model_row)
+{
+	gboolean ret_val;
+	gpointer key2;
+	ETableSubset *etss = E_TABLE_SUBSET (etw);
+
+	if (etw->priv->get_key_func)
+		key2 = etw->priv->get_key_func (etss->source, model_row, etw->priv->closure);
+	else
+		key2 = GINT_TO_POINTER (model_row);
+	if (etw->priv->compare_func)
+		ret_val = (etw->priv->compare_func (key, key2));
+	else
+		ret_val = (key == key2);
+	if (etw->priv->free_gotten_key_func)
+		etw->priv->free_gotten_key_func (key2, etw->priv->closure);
+	return ret_val;
+}
+
+static gint
+etw_view_to_model_row (ETableWithout *etw,
+                       gint view_row)
+{
+	ETableSubset *etss = E_TABLE_SUBSET (etw);
+	return etss->map_table[view_row];
+}
+
+static void
+add_row (ETableWithout *etw,
+         gint model_row)
+{
+	ETableSubset *etss = E_TABLE_SUBSET (etw);
+
+	e_table_model_pre_change (E_TABLE_MODEL (etw));
+
+	etss->map_table = g_renew (int, etss->map_table, etss->n_map + 1);
+
+	etss->map_table[etss->n_map++] = model_row;
+
+	e_table_model_row_inserted (E_TABLE_MODEL (etw), etss->n_map - 1);
+}
+
+static void
+remove_row (ETableWithout *etw,
+            gint view_row)
+{
+	ETableSubset *etss = E_TABLE_SUBSET (etw);
+
+	e_table_model_pre_change (E_TABLE_MODEL (etw));
+	memmove (
+		etss->map_table + view_row,
+		etss->map_table + view_row + 1,
+		(etss->n_map - view_row - 1) * sizeof (gint));
+	etss->n_map--;
+	e_table_model_row_deleted (E_TABLE_MODEL (etw), view_row);
+}
+
+static void
+delete_hash_element (gpointer key,
+                     gpointer value,
+                     gpointer closure)
+{
+	ETableWithout *etw = closure;
+	if (etw->priv->free_duplicated_key_func)
+		etw->priv->free_duplicated_key_func (key, etw->priv->closure);
+}
+
+static void
+etw_dispose (GObject *object)
+{
+	ETableWithoutPrivate *priv;
+
+	priv = E_TABLE_WITHOUT_GET_PRIVATE (object);
+
+	if (priv->hash != NULL) {
+		g_hash_table_foreach (priv->hash, delete_hash_element, object);
+		g_hash_table_destroy (priv->hash);
+		priv->hash = NULL;
+	}
+
+	/* Chain up to parent's dispose() method. */
+	G_OBJECT_CLASS (etw_parent_class)->dispose (object);
+}
+
+static void
+etw_proxy_model_rows_inserted (ETableSubset *etss,
+                               ETableModel *etm,
+                               gint model_row,
+                               gint count)
+{
+	gint i;
+	ETableWithout *etw = E_TABLE_WITHOUT (etss);
+	gboolean shift = FALSE;
+
+	/* i is View row */
+	if (model_row != etss->n_map) {
+		for (i = 0; i < etss->n_map; i++) {
+			if (etss->map_table[i] > model_row)
+				etss->map_table[i] += count;
+		}
+		shift = TRUE;
+	}
+
+	/* i is Model row */
+	for (i = model_row; i < model_row + count; i++) {
+		if (!check (etw, i)) {
+			add_row (etw, i);
+		}
+	}
+	if (shift)
+		e_table_model_changed (E_TABLE_MODEL (etw));
+	else
+		e_table_model_no_change (E_TABLE_MODEL (etw));
+}
+
+static void
+etw_proxy_model_rows_deleted (ETableSubset *etss,
+                              ETableModel *etm,
+                              gint model_row,
+                              gint count)
+{
+	gint i; /* View row */
+	ETableWithout *etw = E_TABLE_WITHOUT (etss);
+	gboolean shift = FALSE;
+
+	for (i = 0; i < etss->n_map; i++) {
+		if (etss->map_table[i] >= model_row &&
+		    etss->map_table[i] < model_row + count) {
+			remove_row (etw, i);
+			i--;
+		} else if (etss->map_table[i] >= model_row + count) {
+			etss->map_table[i] -= count;
+			shift = TRUE;
+		}
+	}
+	if (shift)
+		e_table_model_changed (E_TABLE_MODEL (etw));
+	else
+		e_table_model_no_change (E_TABLE_MODEL (etw));
+}
+
+static void
+etw_proxy_model_changed (ETableSubset *etss,
+                         ETableModel *etm)
+{
+	gint i; /* Model row */
+	gint j; /* View row */
+	gint row_count;
+	ETableWithout *etw = E_TABLE_WITHOUT (etss);
+
+	g_free (etss->map_table);
+	row_count = e_table_model_row_count (etm);
+	etss->map_table = g_new (int, row_count);
+
+	for (i = 0, j = 0; i < row_count; i++) {
+		if (!check (etw, i)) {
+			etss->map_table[j++] = i;
+		}
+	}
+	etss->n_map = j;
+
+	if (E_TABLE_SUBSET_CLASS (etw_parent_class)->proxy_model_changed)
+		E_TABLE_SUBSET_CLASS (etw_parent_class)->proxy_model_changed (etss, etm);
+}
+
+static void
+etw_class_init (ETableWithoutClass *class)
+{
+	GObjectClass *object_class;
+	ETableSubsetClass *etss_class;
+
+	g_type_class_add_private (class, sizeof (ETableWithoutPrivate));
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->dispose = etw_dispose;
+
+	etss_class = E_TABLE_SUBSET_CLASS (class);
+	etss_class->proxy_model_rows_inserted = etw_proxy_model_rows_inserted;
+	etss_class->proxy_model_rows_deleted  = etw_proxy_model_rows_deleted;
+	etss_class->proxy_model_changed = etw_proxy_model_changed;
+}
+
+static void
+etw_init (ETableWithout *etw)
+{
+	etw->priv = E_TABLE_WITHOUT_GET_PRIVATE (etw);
+}
+
+ETableModel *
+e_table_without_construct (ETableWithout *etw,
+                           ETableModel *source,
+                           GHashFunc hash_func,
+                           GCompareFunc compare_func,
+                           ETableWithoutGetKeyFunc get_key_func,
+                           ETableWithoutDuplicateKeyFunc duplicate_key_func,
+                           ETableWithoutFreeKeyFunc free_gotten_key_func,
+                           ETableWithoutFreeKeyFunc free_duplicated_key_func,
+                           gpointer closure)
+{
+	if (e_table_subset_construct (E_TABLE_SUBSET (etw), source, 1) == NULL)
+		return NULL;
+	E_TABLE_SUBSET (etw)->n_map = 0;
+
+	etw->priv->hash_func                = hash_func;
+	etw->priv->compare_func	    = compare_func;
+	etw->priv->get_key_func	    = get_key_func;
+	etw->priv->duplicate_key_func	    = duplicate_key_func;
+	etw->priv->free_gotten_key_func     = free_gotten_key_func;
+	etw->priv->free_duplicated_key_func = free_duplicated_key_func;
+	etw->priv->closure                  = closure;
+
+	etw->priv->hash = g_hash_table_new (
+		etw->priv->hash_func, etw->priv->compare_func);
+
+	return E_TABLE_MODEL (etw);
+}
+
+ETableModel *
+e_table_without_new (ETableModel *source,
+                     GHashFunc hash_func,
+                     GCompareFunc compare_func,
+                     ETableWithoutGetKeyFunc get_key_func,
+                     ETableWithoutDuplicateKeyFunc duplicate_key_func,
+                     ETableWithoutFreeKeyFunc free_gotten_key_func,
+                     ETableWithoutFreeKeyFunc free_duplicated_key_func,
+                     gpointer closure)
+{
+	ETableWithout *etw = g_object_new (E_TYPE_TABLE_WITHOUT, NULL);
+
+	if (e_table_without_construct (etw,
+				       source,
+				       hash_func,
+				       compare_func,
+				       get_key_func,
+				       duplicate_key_func,
+				       free_gotten_key_func,
+				       free_duplicated_key_func,
+				       closure)
+	    == NULL) {
+		g_object_unref (etw);
+		return NULL;
+	}
+
+	return (ETableModel *) etw;
+}
+
+void
+e_table_without_hide (ETableWithout *etw,
+                      gpointer key)
+{
+	gint i; /* View row */
+	ETableSubset *etss = E_TABLE_SUBSET (etw);
+
+	if (etw->priv->duplicate_key_func)
+		key = etw->priv->duplicate_key_func (key, etw->priv->closure);
+
+	g_hash_table_insert (etw->priv->hash, key, key);
+	for (i = 0; i < etss->n_map; i++) {
+		if (check_with_key (etw, key, etw_view_to_model_row (etw, i))) {
+			remove_row (etw, i);
+			i--;
+		}
+	}
+}
+
+/* An adopted key will later be freed using the free_duplicated_key function. */
+void
+e_table_without_hide_adopt (ETableWithout *etw,
+                            gpointer key)
+{
+	gint i; /* View row */
+	ETableSubset *etss = E_TABLE_SUBSET (etw);
+
+	g_hash_table_insert (etw->priv->hash, key, key);
+	for (i = 0; i < etss->n_map; i++) {
+		if (check_with_key (etw, key, etw_view_to_model_row (etw, i))) {
+			remove_row (etw, i);
+			i--;
+		}
+	}
+}
+
+void
+e_table_without_show (ETableWithout *etw,
+                      gpointer key)
+{
+	gint i; /* Model row */
+	ETableSubset *etss = E_TABLE_SUBSET (etw);
+	gint count;
+	gpointer old_key;
+
+	count = e_table_model_row_count (etss->source);
+
+	for (i = 0; i < count; i++) {
+		if (check_with_key (etw, key, i)) {
+			add_row (etw, i);
+		}
+	}
+	if (g_hash_table_lookup_extended (etw->priv->hash, key, &old_key, NULL)) {
+#if 0
+		if (etw->priv->free_duplicated_key_func)
+			etw->priv->free_duplicated_key_func (key, etw->priv->closure);
+#endif
+		g_hash_table_remove (etw->priv->hash, key);
+	}
+}
+
+void
+e_table_without_show_all (ETableWithout *etw)
+{
+	gint i; /* Model row */
+	gint row_count;
+	ETableSubset *etss = E_TABLE_SUBSET (etw);
+
+	e_table_model_pre_change (E_TABLE_MODEL (etw));
+
+	if (etw->priv->hash) {
+		g_hash_table_foreach (etw->priv->hash, delete_hash_element, etw);
+		g_hash_table_destroy (etw->priv->hash);
+		etw->priv->hash = NULL;
+	}
+	etw->priv->hash = g_hash_table_new (
+		etw->priv->hash_func, etw->priv->compare_func);
+
+	row_count = e_table_model_row_count (E_TABLE_MODEL (etss->source));
+	g_free (etss->map_table);
+	etss->map_table = g_new (int, row_count);
+
+	for (i = 0; i < row_count; i++) {
+		etss->map_table[i] = i;
+	}
+	etss->n_map = row_count;
+
+	e_table_model_changed (E_TABLE_MODEL (etw));
+}
diff --git a/e-util/e-table-without.h b/e-util/e-table-without.h
new file mode 100644
index 0000000..0853c54
--- /dev/null
+++ b/e-util/e-table-without.h
@@ -0,0 +1,104 @@
+/*
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef _E_TABLE_WITHOUT_H_
+#define _E_TABLE_WITHOUT_H_
+
+#include <e-util/e-table-subset.h>
+
+/* Standard GObject macros */
+#define E_TYPE_TABLE_WITHOUT \
+	(e_table_without_get_type ())
+#define E_TABLE_WITHOUT(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_TABLE_WITHOUT, ETableWithout))
+#define E_TABLE_WITHOUT_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_TABLE_WITHOUT, ETableWithoutClass))
+#define E_IS_TABLE_WITHOUT(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_TABLE_WITHOUT))
+#define E_IS_TABLE_WITHOUT_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_TABLE_WITHOUT))
+#define E_TABLE_WITHOUT_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_TABLE_WITHOUT, ETableWithoutClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ETableWithout ETableWithout;
+typedef struct _ETableWithoutClass ETableWithoutClass;
+typedef struct _ETableWithoutPrivate ETableWithoutPrivate;
+
+typedef gpointer	(*ETableWithoutGetKeyFunc)	(ETableModel *source,
+							 gint row,
+							 gpointer closure);
+typedef gpointer	(*ETableWithoutDuplicateKeyFunc)(gconstpointer key,
+							 gpointer closure);
+typedef void		(*ETableWithoutFreeKeyFunc)	(gpointer key,
+							 gpointer closure);
+
+struct _ETableWithout {
+	ETableSubset parent;
+	ETableWithoutPrivate *priv;
+};
+
+struct _ETableWithoutClass {
+	ETableSubsetClass parent_class;
+};
+
+GType		e_table_without_get_type	(void) G_GNUC_CONST;
+ETableModel *	e_table_without_new		(ETableModel *source,
+						 GHashFunc hash_func,
+						 GCompareFunc compare_func,
+						 ETableWithoutGetKeyFunc get_key_func,
+						 ETableWithoutDuplicateKeyFunc duplicate_key_func,
+						 ETableWithoutFreeKeyFunc free_gotten_key_func,
+						 ETableWithoutFreeKeyFunc free_duplicated_key_func,
+						 gpointer closure);
+ETableModel *	e_table_without_construct	(ETableWithout *etw,
+						 ETableModel *source,
+						 GHashFunc hash_func,
+						 GCompareFunc compare_func,
+						 ETableWithoutGetKeyFunc get_key_func,
+						 ETableWithoutDuplicateKeyFunc duplicate_key_func,
+						 ETableWithoutFreeKeyFunc free_gotten_key_func,
+						 ETableWithoutFreeKeyFunc free_duplicated_key_func,
+						 gpointer closure);
+void		e_table_without_hide		(ETableWithout *etw,
+						 gpointer key);
+void		e_table_without_hide_adopt	(ETableWithout *etw,
+						 gpointer key);
+void		e_table_without_show		(ETableWithout *etw,
+						 gpointer key);
+void		e_table_without_show_all	(ETableWithout *etw);
+
+G_END_DECLS
+
+#endif /* _E_TABLE_WITHOUT_H_ */
+
diff --git a/e-util/e-table.c b/e-util/e-table.c
new file mode 100644
index 0000000..4dc90be
--- /dev/null
+++ b/e-util/e-table.c
@@ -0,0 +1,3626 @@
+/*
+ * e-table.c - A graphical view of a Table.
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *		Miguel de Icaza <miguel ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-table.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+#include <glib/gstdio.h>
+#include <gdk/gdkkeysyms.h>
+
+#include <libgnomecanvas/libgnomecanvas.h>
+
+#include "e-canvas-background.h"
+#include "e-canvas-vbox.h"
+#include "e-canvas.h"
+#include "e-table-click-to-add.h"
+#include "e-table-column-specification.h"
+#include "e-table-group-leaf.h"
+#include "e-table-header-item.h"
+#include "e-table-header-utils.h"
+#include "e-table-subset.h"
+#include "e-table-utils.h"
+#include "e-unicode.h"
+#include "gal-a11y-e-table.h"
+
+#define COLUMN_HEADER_HEIGHT 16
+
+#define d(x)
+
+#if d(!)0
+#define e_table_item_leave_edit_(x) \
+	(e_table_item_leave_edit ((x)), \
+	 g_print ("%s: e_table_item_leave_edit\n", __FUNCTION__))
+#else
+#define e_table_item_leave_edit_(x) (e_table_item_leave_edit((x)))
+#endif
+
+enum {
+	CURSOR_CHANGE,
+	CURSOR_ACTIVATED,
+	SELECTION_CHANGE,
+	DOUBLE_CLICK,
+	RIGHT_CLICK,
+	CLICK,
+	KEY_PRESS,
+	START_DRAG,
+	STATE_CHANGE,
+	WHITE_SPACE_EVENT,
+
+	CUT_CLIPBOARD,
+	COPY_CLIPBOARD,
+	PASTE_CLIPBOARD,
+	SELECT_ALL,
+
+	TABLE_DRAG_BEGIN,
+	TABLE_DRAG_END,
+	TABLE_DRAG_DATA_GET,
+	TABLE_DRAG_DATA_DELETE,
+
+	TABLE_DRAG_LEAVE,
+	TABLE_DRAG_MOTION,
+	TABLE_DRAG_DROP,
+	TABLE_DRAG_DATA_RECEIVED,
+
+	LAST_SIGNAL
+};
+
+enum {
+	PROP_0,
+	PROP_LENGTH_THRESHOLD,
+	PROP_MODEL,
+	PROP_UNIFORM_ROW_HEIGHT,
+	PROP_ALWAYS_SEARCH,
+	PROP_USE_CLICK_TO_ADD,
+	PROP_HADJUSTMENT,
+	PROP_VADJUSTMENT,
+	PROP_HSCROLL_POLICY,
+	PROP_VSCROLL_POLICY
+};
+
+enum {
+	ET_SCROLL_UP = 1 << 0,
+	ET_SCROLL_DOWN = 1 << 1,
+	ET_SCROLL_LEFT = 1 << 2,
+	ET_SCROLL_RIGHT = 1 << 3
+};
+
+static guint et_signals[LAST_SIGNAL] = { 0 };
+
+static void e_table_fill_table (ETable *e_table, ETableModel *model);
+static gboolean changed_idle (gpointer data);
+
+static void et_grab_focus (GtkWidget *widget);
+
+static void et_drag_begin (GtkWidget *widget,
+			   GdkDragContext *context,
+			   ETable *et);
+static void et_drag_end (GtkWidget *widget,
+			 GdkDragContext *context,
+			 ETable *et);
+static void et_drag_data_get (GtkWidget *widget,
+			     GdkDragContext *context,
+			     GtkSelectionData *selection_data,
+			     guint info,
+			     guint time,
+			     ETable *et);
+static void et_drag_data_delete (GtkWidget *widget,
+				GdkDragContext *context,
+				ETable *et);
+
+static void et_drag_leave (GtkWidget *widget,
+			  GdkDragContext *context,
+			  guint time,
+			  ETable *et);
+static gboolean et_drag_motion (GtkWidget *widget,
+			       GdkDragContext *context,
+			       gint x,
+			       gint y,
+			       guint time,
+			       ETable *et);
+static gboolean et_drag_drop (GtkWidget *widget,
+			     GdkDragContext *context,
+			     gint x,
+			     gint y,
+			     guint time,
+			     ETable *et);
+static void et_drag_data_received (GtkWidget *widget,
+				  GdkDragContext *context,
+				  gint x,
+				  gint y,
+				  GtkSelectionData *selection_data,
+				  guint info,
+				  guint time,
+				  ETable *et);
+
+static gint et_focus (GtkWidget *container, GtkDirectionType direction);
+
+static void scroll_off (ETable *et);
+static void scroll_on (ETable *et, guint scroll_direction);
+
+G_DEFINE_TYPE_WITH_CODE (ETable, e_table, GTK_TYPE_TABLE,
+			 G_IMPLEMENT_INTERFACE (GTK_TYPE_SCROLLABLE, NULL))
+
+static void
+et_disconnect_model (ETable *et)
+{
+	if (et->model == NULL)
+		return;
+
+	if (et->table_model_change_id != 0)
+		g_signal_handler_disconnect (
+			et->model, et->table_model_change_id);
+	if (et->table_row_change_id != 0)
+		g_signal_handler_disconnect (
+			et->model, et->table_row_change_id);
+	if (et->table_cell_change_id != 0)
+		g_signal_handler_disconnect (
+			et->model, et->table_cell_change_id);
+	if (et->table_rows_inserted_id != 0)
+		g_signal_handler_disconnect (
+			et->model, et->table_rows_inserted_id);
+	if (et->table_rows_deleted_id != 0)
+		g_signal_handler_disconnect (
+			et->model, et->table_rows_deleted_id);
+
+	et->table_model_change_id = 0;
+	et->table_row_change_id = 0;
+	et->table_cell_change_id = 0;
+	et->table_rows_inserted_id = 0;
+	et->table_rows_deleted_id = 0;
+}
+
+static void
+e_table_state_change (ETable *et)
+{
+	if (et->state_change_freeze)
+		et->state_changed = TRUE;
+	else
+		g_signal_emit (et, et_signals[STATE_CHANGE], 0);
+}
+
+#define CHECK_HORIZONTAL(et) \
+	if ((et)->horizontal_scrolling || (et)->horizontal_resize) \
+		e_table_header_update_horizontal (et->header);
+
+static void
+clear_current_search_col (ETable *et)
+{
+	et->search_col_set = FALSE;
+}
+
+static ETableCol *
+current_search_col (ETable *et)
+{
+	if (!et->search_col_set) {
+		et->current_search_col =
+			e_table_util_calculate_current_search_col (
+				et->header,
+				et->full_header,
+				et->sort_info,
+				et->always_search);
+		et->search_col_set = TRUE;
+	}
+
+	return et->current_search_col;
+}
+
+static void
+et_get_preferred_width (GtkWidget *widget,
+                        gint *minimum,
+                        gint *natural)
+{
+	ETable *et = E_TABLE (widget);
+
+	GTK_WIDGET_CLASS (e_table_parent_class)->
+		get_preferred_width (widget, minimum, natural);
+
+	if (et->horizontal_resize) {
+		*minimum = MAX (*minimum, et->header_width);
+		*natural = MAX (*natural, et->header_width);
+	}
+}
+
+static void
+et_get_preferred_height (GtkWidget *widget,
+                         gint *minimum,
+                         gint *natural)
+{
+	GTK_WIDGET_CLASS (e_table_parent_class)->
+		get_preferred_height (widget, minimum, natural);
+}
+
+static void
+set_header_width (ETable *et)
+{
+	if (et->horizontal_resize) {
+		et->header_width = e_table_header_min_width (et->header);
+		gtk_widget_queue_resize (GTK_WIDGET (et));
+	}
+}
+
+static void
+structure_changed (ETableHeader *header,
+                   ETable *et)
+{
+	e_table_state_change (et);
+	set_header_width (et);
+	clear_current_search_col (et);
+}
+
+static void
+expansion_changed (ETableHeader *header,
+                   ETable *et)
+{
+	e_table_state_change (et);
+	set_header_width (et);
+}
+
+static void
+dimension_changed (ETableHeader *header,
+                   gint total_width,
+                   ETable *et)
+{
+	set_header_width (et);
+}
+
+static void
+disconnect_header (ETable *e_table)
+{
+	if (e_table->header == NULL)
+		return;
+
+	if (e_table->structure_change_id)
+		g_signal_handler_disconnect (
+			e_table->header, e_table->structure_change_id);
+	if (e_table->expansion_change_id)
+		g_signal_handler_disconnect (
+			e_table->header, e_table->expansion_change_id);
+	if (e_table->dimension_change_id)
+		g_signal_handler_disconnect (
+			e_table->header, e_table->dimension_change_id);
+
+	g_object_unref (e_table->header);
+	e_table->header = NULL;
+}
+
+static void
+connect_header (ETable *e_table,
+                ETableState *state)
+{
+	if (e_table->header != NULL)
+		disconnect_header (e_table);
+
+	e_table->header = e_table_state_to_header (
+		GTK_WIDGET (e_table), e_table->full_header, state);
+
+	e_table->structure_change_id = g_signal_connect (
+		e_table->header, "structure_change",
+		G_CALLBACK (structure_changed), e_table);
+	e_table->expansion_change_id = g_signal_connect (
+		e_table->header, "expansion_change",
+		G_CALLBACK (expansion_changed), e_table);
+	e_table->dimension_change_id = g_signal_connect (
+		e_table->header, "dimension_change",
+		G_CALLBACK (dimension_changed), e_table);
+}
+
+static void
+et_dispose (GObject *object)
+{
+	ETable *et = E_TABLE (object);
+
+	et_disconnect_model (et);
+
+	if (et->search) {
+		if (et->search_search_id)
+			g_signal_handler_disconnect (
+				et->search, et->search_search_id);
+		if (et->search_accept_id)
+			g_signal_handler_disconnect (
+				et->search, et->search_accept_id);
+		g_object_unref (et->search);
+		et->search = NULL;
+	}
+
+	if (et->group_info_change_id) {
+		g_signal_handler_disconnect (
+			et->sort_info, et->group_info_change_id);
+		et->group_info_change_id = 0;
+	}
+
+	if (et->sort_info_change_id) {
+		g_signal_handler_disconnect (
+			et->sort_info, et->sort_info_change_id);
+		et->sort_info_change_id = 0;
+	}
+
+	if (et->reflow_idle_id) {
+		g_source_remove (et->reflow_idle_id);
+		et->reflow_idle_id = 0;
+	}
+
+	scroll_off (et);
+
+	disconnect_header (et);
+
+	if (et->model) {
+		g_object_unref (et->model);
+		et->model = NULL;
+	}
+
+	if (et->full_header) {
+		g_object_unref (et->full_header);
+		et->full_header = NULL;
+	}
+
+	if (et->sort_info) {
+		g_object_unref (et->sort_info);
+		et->sort_info = NULL;
+	}
+
+	if (et->sorter) {
+		g_object_unref (et->sorter);
+		et->sorter = NULL;
+	}
+
+	if (et->selection) {
+		g_object_unref (et->selection);
+		et->selection = NULL;
+	}
+
+	if (et->spec) {
+		g_object_unref (et->spec);
+		et->spec = NULL;
+	}
+
+	if (et->header_canvas != NULL) {
+		gtk_widget_destroy (GTK_WIDGET (et->header_canvas));
+		et->header_canvas = NULL;
+	}
+
+	if (et->site != NULL) {
+		e_table_drag_source_unset (et);
+		et->site = NULL;
+	}
+
+	if (et->table_canvas != NULL) {
+		gtk_widget_destroy (GTK_WIDGET (et->table_canvas));
+		et->table_canvas = NULL;
+	}
+
+	if (et->rebuild_idle_id != 0) {
+		g_source_remove (et->rebuild_idle_id);
+		et->rebuild_idle_id = 0;
+	}
+
+	g_free (et->click_to_add_message);
+	et->click_to_add_message = NULL;
+
+	g_free (et->domain);
+	et->domain = NULL;
+
+	G_OBJECT_CLASS (e_table_parent_class)->dispose (object);
+}
+
+static void
+et_unrealize (GtkWidget *widget)
+{
+	scroll_off (E_TABLE (widget));
+
+	if (GTK_WIDGET_CLASS (e_table_parent_class)->unrealize)
+		GTK_WIDGET_CLASS (e_table_parent_class)->unrealize (widget);
+}
+
+static gboolean
+check_row (ETable *et,
+           gint model_row,
+           gint col,
+           ETableSearchFunc search,
+           gchar *string)
+{
+	gconstpointer value;
+
+	value = e_table_model_value_at (et->model, col, model_row);
+
+	return search (value, string);
+}
+
+static gboolean
+et_search_search (ETableSearch *search,
+                  gchar *string,
+                  ETableSearchFlags flags,
+                  ETable *et)
+{
+	gint cursor;
+	gint rows;
+	gint i;
+	ETableCol *col = current_search_col (et);
+
+	if (col == NULL)
+		return FALSE;
+
+	rows = e_table_model_row_count (et->model);
+
+	g_object_get (
+		et->selection,
+		"cursor_row", &cursor,
+		NULL);
+
+	if ((flags & E_TABLE_SEARCH_FLAGS_CHECK_CURSOR_FIRST) &&
+		cursor < rows && cursor >= 0 &&
+		check_row (et, cursor, col->col_idx, col->search, string))
+		return TRUE;
+
+	cursor = e_sorter_model_to_sorted (E_SORTER (et->sorter), cursor);
+
+	for (i = cursor + 1; i < rows; i++) {
+		gint model_row = e_sorter_sorted_to_model (E_SORTER (et->sorter), i);
+		if (check_row (et, model_row, col->col_idx, col->search, string)) {
+			e_selection_model_select_as_key_press (
+				E_SELECTION_MODEL (et->selection),
+				model_row, col->col_idx, GDK_CONTROL_MASK);
+			return TRUE;
+		}
+	}
+
+	for (i = 0; i < cursor; i++) {
+		gint model_row = e_sorter_sorted_to_model (E_SORTER (et->sorter), i);
+		if (check_row (et, model_row, col->col_idx, col->search, string)) {
+			e_selection_model_select_as_key_press (
+				E_SELECTION_MODEL (et->selection),
+				model_row, col->col_idx, GDK_CONTROL_MASK);
+			return TRUE;
+		}
+	}
+
+	cursor = e_sorter_sorted_to_model (E_SORTER (et->sorter), cursor);
+
+	/* Check if the cursor row is the only matching row. */
+	return (!(flags & E_TABLE_SEARCH_FLAGS_CHECK_CURSOR_FIRST) &&
+		cursor < rows && cursor >= 0 &&
+		check_row (et, cursor, col->col_idx, col->search, string));
+}
+
+static void
+et_search_accept (ETableSearch *search,
+                  ETable *et)
+{
+	gint cursor;
+	ETableCol *col = current_search_col (et);
+
+	if (col == NULL)
+		return;
+
+	g_object_get (et->selection, "cursor_row", &cursor, NULL);
+
+	e_selection_model_select_as_key_press (
+		E_SELECTION_MODEL (et->selection), cursor, col->col_idx, 0);
+}
+
+static void
+init_search (ETable *e_table)
+{
+	if (e_table->search != NULL)
+		return;
+
+	e_table->search           = e_table_search_new ();
+
+	e_table->search_search_id = g_signal_connect (
+		e_table->search, "search",
+		G_CALLBACK (et_search_search), e_table);
+	e_table->search_accept_id = g_signal_connect (
+		e_table->search, "accept",
+		G_CALLBACK (et_search_accept), e_table);
+}
+
+static void
+et_finalize (GObject *object)
+{
+	ETable *et = E_TABLE (object);
+
+	g_free (et->click_to_add_message);
+	et->click_to_add_message = NULL;
+
+	g_free (et->domain);
+	et->domain = NULL;
+
+	G_OBJECT_CLASS (e_table_parent_class)->finalize (object);
+}
+
+static void
+e_table_init (ETable *e_table)
+{
+	gtk_widget_set_can_focus (GTK_WIDGET (e_table), TRUE);
+
+	gtk_table_set_homogeneous (GTK_TABLE (e_table), FALSE);
+
+	e_table->sort_info              = NULL;
+	e_table->group_info_change_id   = 0;
+	e_table->sort_info_change_id    = 0;
+	e_table->structure_change_id    = 0;
+	e_table->expansion_change_id    = 0;
+	e_table->dimension_change_id    = 0;
+	e_table->reflow_idle_id         = 0;
+	e_table->scroll_idle_id         = 0;
+
+	e_table->alternating_row_colors = 1;
+	e_table->horizontal_draw_grid   = 1;
+	e_table->vertical_draw_grid     = 1;
+	e_table->draw_focus             = 1;
+	e_table->cursor_mode            = E_CURSOR_SIMPLE;
+	e_table->length_threshold       = 200;
+	e_table->uniform_row_height     = FALSE;
+
+	e_table->need_rebuild           = 0;
+	e_table->rebuild_idle_id        = 0;
+
+	e_table->horizontal_scrolling   = FALSE;
+	e_table->horizontal_resize      = FALSE;
+
+	e_table->click_to_add_message   = NULL;
+	e_table->domain                 = NULL;
+
+	e_table->drop_row               = -1;
+	e_table->drop_col               = -1;
+	e_table->site                   = NULL;
+
+	e_table->do_drag                = 0;
+
+	e_table->sorter                 = NULL;
+	e_table->selection              = e_table_selection_model_new ();
+	e_table->cursor_loc             = E_TABLE_CURSOR_LOC_NONE;
+	e_table->spec                   = NULL;
+
+	e_table->always_search          = g_getenv ("GAL_ALWAYS_SEARCH") ? TRUE : FALSE;
+
+	e_table->search                 = NULL;
+	e_table->search_search_id       = 0;
+	e_table->search_accept_id       = 0;
+
+	e_table->current_search_col     = NULL;
+
+	e_table->header_width           = 0;
+
+	e_table->state_changed		= FALSE;
+	e_table->state_change_freeze	= 0;
+}
+
+/* Grab_focus handler for the ETable */
+static void
+et_grab_focus (GtkWidget *widget)
+{
+	ETable *e_table;
+
+	e_table = E_TABLE (widget);
+
+	gtk_widget_grab_focus (GTK_WIDGET (e_table->table_canvas));
+}
+
+/* Focus handler for the ETable */
+static gint
+et_focus (GtkWidget *container,
+          GtkDirectionType direction)
+{
+	ETable *e_table;
+
+	e_table = E_TABLE (container);
+
+	if (gtk_container_get_focus_child (GTK_CONTAINER (container))) {
+		gtk_container_set_focus_child (GTK_CONTAINER (container), NULL);
+		return FALSE;
+	}
+
+	return gtk_widget_child_focus (GTK_WIDGET (e_table->table_canvas), direction);
+}
+
+static void
+set_header_canvas_width (ETable *e_table)
+{
+	gdouble oldwidth, oldheight, width;
+
+	if (!(e_table->header_item && e_table->header_canvas && e_table->table_canvas))
+		return;
+
+	gnome_canvas_get_scroll_region (
+		GNOME_CANVAS (e_table->table_canvas),
+		NULL, NULL, &width, NULL);
+	gnome_canvas_get_scroll_region (
+		GNOME_CANVAS (e_table->header_canvas),
+		NULL, NULL, &oldwidth, &oldheight);
+
+	if (oldwidth != width ||
+	    oldheight != E_TABLE_HEADER_ITEM (e_table->header_item)->height - 1)
+		gnome_canvas_set_scroll_region (
+			GNOME_CANVAS (e_table->header_canvas),
+			0, 0, width, /*  COLUMN_HEADER_HEIGHT - 1 */
+			E_TABLE_HEADER_ITEM (e_table->header_item)->height - 1);
+
+}
+
+static void
+header_canvas_size_allocate (GtkWidget *widget,
+                             GtkAllocation *alloc,
+                             ETable *e_table)
+{
+	GtkAllocation allocation;
+
+	set_header_canvas_width (e_table);
+
+	gtk_widget_get_allocation (
+		GTK_WIDGET (e_table->header_canvas), &allocation);
+
+	/* When the header item is created ->height == 0,
+	 * as the font is only created when everything is realized.
+	 * So we set the usize here as well, so that the size of the
+	 * header is correct */
+	if (allocation.height != E_TABLE_HEADER_ITEM (e_table->header_item)->height)
+		g_object_set (
+			e_table->header_canvas, "height-request",
+			E_TABLE_HEADER_ITEM (e_table->header_item)->height,
+			NULL);
+}
+
+static void
+group_info_changed (ETableSortInfo *info,
+                    ETable *et)
+{
+	gboolean will_be_grouped = e_table_sort_info_grouping_get_count (info) > 0;
+	clear_current_search_col (et);
+	if (et->is_grouped || will_be_grouped) {
+		et->need_rebuild = TRUE;
+		if (!et->rebuild_idle_id) {
+			g_object_run_dispose (G_OBJECT (et->group));
+			et->group = NULL;
+			et->rebuild_idle_id = g_idle_add_full (20, changed_idle, et, NULL);
+		}
+	}
+	e_table_state_change (et);
+}
+
+static void
+sort_info_changed (ETableSortInfo *info,
+                   ETable *et)
+{
+	clear_current_search_col (et);
+	e_table_state_change (et);
+}
+
+static void
+e_table_setup_header (ETable *e_table)
+{
+	gchar *pointer;
+	e_table->header_canvas = GNOME_CANVAS (e_canvas_new ());
+
+	gtk_widget_show (GTK_WIDGET (e_table->header_canvas));
+
+	pointer = g_strdup_printf ("%p", (gpointer) e_table);
+
+	e_table->header_item = gnome_canvas_item_new (
+		gnome_canvas_root (e_table->header_canvas),
+		e_table_header_item_get_type (),
+		"ETableHeader", e_table->header,
+		"full_header", e_table->full_header,
+		"sort_info", e_table->sort_info,
+		"dnd_code", pointer,
+		"table", e_table,
+		NULL);
+
+	g_free (pointer);
+
+	g_signal_connect (
+		e_table->header_canvas, "size_allocate",
+		G_CALLBACK (header_canvas_size_allocate), e_table);
+
+	g_object_set (
+		e_table->header_canvas, "height-request",
+		E_TABLE_HEADER_ITEM (e_table->header_item)->height, NULL);
+}
+
+static gboolean
+table_canvas_reflow_idle (ETable *e_table)
+{
+	gdouble height, width;
+	gdouble oldheight, oldwidth;
+	GtkAllocation allocation;
+
+	gtk_widget_get_allocation (
+		GTK_WIDGET (e_table->table_canvas), &allocation);
+
+	g_object_get (
+		e_table->canvas_vbox,
+		"height", &height, "width", &width, NULL);
+	height = MAX ((gint) height, allocation.height);
+	width = MAX ((gint) width, allocation.width);
+	/* I have no idea why this needs to be -1, but it works. */
+	gnome_canvas_get_scroll_region (
+		GNOME_CANVAS (e_table->table_canvas),
+		NULL, NULL, &oldwidth, &oldheight);
+
+	if (oldwidth != width - 1 ||
+	    oldheight != height - 1) {
+		gnome_canvas_set_scroll_region (
+			GNOME_CANVAS (e_table->table_canvas),
+			0, 0, width - 1, height - 1);
+		set_header_canvas_width (e_table);
+	}
+	e_table->reflow_idle_id = 0;
+	return FALSE;
+}
+
+static void
+table_canvas_size_allocate (GtkWidget *widget,
+                            GtkAllocation *alloc,
+                            ETable *e_table)
+{
+	gdouble width;
+	gdouble height;
+	GValue *val = g_new0 (GValue, 1);
+	g_value_init (val, G_TYPE_DOUBLE);
+
+	width = alloc->width;
+	g_value_set_double (val, width);
+	g_object_get (
+		e_table->canvas_vbox,
+		"height", &height,
+		NULL);
+	height = MAX ((gint) height, alloc->height);
+
+	g_object_set (
+		e_table->canvas_vbox,
+		"width", width,
+		NULL);
+	g_object_set_property (G_OBJECT (e_table->header), "width", val);
+	g_free (val);
+	if (e_table->reflow_idle_id)
+		g_source_remove (e_table->reflow_idle_id);
+	table_canvas_reflow_idle (e_table);
+
+	e_table->size_allocated = TRUE;
+
+	if (e_table->need_rebuild && !e_table->rebuild_idle_id)
+		e_table->rebuild_idle_id = g_idle_add_full (20, changed_idle, e_table, NULL);
+}
+
+static void
+table_canvas_reflow (GnomeCanvas *canvas,
+                     ETable *e_table)
+{
+	if (!e_table->reflow_idle_id)
+		e_table->reflow_idle_id = g_idle_add_full (
+			400, (GSourceFunc) table_canvas_reflow_idle,
+			e_table, NULL);
+}
+
+static void
+click_to_add_cursor_change (ETableClickToAdd *etcta,
+                            gint row,
+                            gint col,
+                            ETable *et)
+{
+	if (et->cursor_loc == E_TABLE_CURSOR_LOC_TABLE) {
+		e_selection_model_clear (E_SELECTION_MODEL (et->selection));
+	}
+	et->cursor_loc = E_TABLE_CURSOR_LOC_ETCTA;
+}
+
+static void
+group_cursor_change (ETableGroup *etg,
+                     gint row,
+                     ETable *et)
+{
+	ETableCursorLoc old_cursor_loc;
+
+	old_cursor_loc = et->cursor_loc;
+
+	et->cursor_loc = E_TABLE_CURSOR_LOC_TABLE;
+	g_signal_emit (et, et_signals[CURSOR_CHANGE], 0, row);
+
+	if (old_cursor_loc == E_TABLE_CURSOR_LOC_ETCTA && et->click_to_add)
+		e_table_click_to_add_commit (E_TABLE_CLICK_TO_ADD (et->click_to_add));
+}
+
+static void
+group_cursor_activated (ETableGroup *etg,
+                        gint row,
+                        ETable *et)
+{
+	g_signal_emit (et, et_signals[CURSOR_ACTIVATED], 0, row);
+}
+
+static void
+group_double_click (ETableGroup *etg,
+                    gint row,
+                    gint col,
+                    GdkEvent *event,
+                    ETable *et)
+{
+	g_signal_emit (et, et_signals[DOUBLE_CLICK], 0, row, col, event);
+}
+
+static gboolean
+group_right_click (ETableGroup *etg,
+                   gint row,
+                   gint col,
+                   GdkEvent *event,
+                   ETable *et)
+{
+	gboolean return_val = FALSE;
+
+	g_signal_emit (
+		et, et_signals[RIGHT_CLICK], 0,
+		row, col, event, &return_val);
+
+	return return_val;
+}
+
+static gboolean
+group_click (ETableGroup *etg,
+             gint row,
+             gint col,
+             GdkEvent *event,
+             ETable *et)
+{
+	gboolean return_val = 0;
+
+	g_signal_emit (
+		et, et_signals[CLICK], 0,
+		row, col, event, &return_val);
+
+	return return_val;
+}
+
+static gboolean
+group_key_press (ETableGroup *etg,
+                 gint row,
+                 gint col,
+                 GdkEvent *event,
+                 ETable *et)
+{
+	gboolean return_val = FALSE;
+	GdkEventKey *key = (GdkEventKey *) event;
+	gint y, row_local, col_local;
+	GtkAdjustment *adjustment;
+	GtkScrollable *scrollable;
+	gdouble page_size;
+	gdouble upper;
+	gdouble value;
+
+	scrollable = GTK_SCROLLABLE (et->table_canvas);
+	adjustment = gtk_scrollable_get_vadjustment (scrollable);
+
+	switch (key->keyval) {
+	case GDK_KEY_Page_Down:
+	case GDK_KEY_KP_Page_Down:
+		page_size = gtk_adjustment_get_page_size (adjustment);
+		upper = gtk_adjustment_get_value (adjustment);
+		value = gtk_adjustment_get_value (adjustment);
+
+		y = CLAMP (value + (2 * page_size - 50), 0, upper);
+		y -= value;
+		e_table_get_cell_at (et, 30, y, &row_local, &col_local);
+
+		if (row_local == -1)
+			row_local = e_table_model_row_count (et->model) - 1;
+
+		row_local = e_table_view_to_model_row (et, row_local);
+		col_local = e_selection_model_cursor_col (E_SELECTION_MODEL (et->selection));
+		e_selection_model_select_as_key_press (
+			E_SELECTION_MODEL (et->selection),
+			row_local, col_local, key->state);
+		return_val = 1;
+		break;
+	case GDK_KEY_Page_Up:
+	case GDK_KEY_KP_Page_Up:
+		page_size = gtk_adjustment_get_page_size (adjustment);
+		upper = gtk_adjustment_get_upper (adjustment);
+		value = gtk_adjustment_get_value (adjustment);
+
+		y = CLAMP (value - (page_size - 50), 0, upper);
+		y -= value;
+		e_table_get_cell_at (et, 30, y, &row_local, &col_local);
+
+		if (row_local == -1)
+			row_local = 0;
+
+		row_local = e_table_view_to_model_row (et, row_local);
+		col_local = e_selection_model_cursor_col (E_SELECTION_MODEL (et->selection));
+		e_selection_model_select_as_key_press (
+			E_SELECTION_MODEL (et->selection),
+			row_local, col_local, key->state);
+		return_val = 1;
+		break;
+	case GDK_KEY_BackSpace:
+		init_search (et);
+		if (e_table_search_backspace (et->search))
+			return TRUE;
+		/* Fall through */
+	default:
+		init_search (et);
+		if ((key->state & ~(GDK_SHIFT_MASK | GDK_LOCK_MASK |
+			GDK_MOD1_MASK | GDK_MOD2_MASK | GDK_MOD3_MASK |
+			GDK_MOD4_MASK | GDK_MOD5_MASK)) == 0
+		    && ((key->keyval >= GDK_KEY_a && key->keyval <= GDK_KEY_z) ||
+			(key->keyval >= GDK_KEY_A && key->keyval <= GDK_KEY_Z) ||
+			(key->keyval >= GDK_KEY_0 && key->keyval <= GDK_KEY_9)))
+			e_table_search_input_character (et->search, key->keyval);
+		g_signal_emit (
+			et, et_signals[KEY_PRESS], 0,
+			row, col, event, &return_val);
+		break;
+	}
+	return return_val;
+}
+
+static gboolean
+group_start_drag (ETableGroup *etg,
+                  gint row,
+                  gint col,
+                  GdkEvent *event,
+                  ETable *et)
+{
+	gboolean return_val = TRUE;
+
+	g_signal_emit (
+		et, et_signals[START_DRAG], 0,
+		row, col, event, &return_val);
+
+	return return_val;
+}
+
+static void
+et_table_model_changed (ETableModel *model,
+                        ETable *et)
+{
+	et->need_rebuild = TRUE;
+	if (!et->rebuild_idle_id) {
+		g_object_run_dispose (G_OBJECT (et->group));
+		et->group = NULL;
+		et->rebuild_idle_id = g_idle_add_full (20, changed_idle, et, NULL);
+	}
+}
+
+static void
+et_table_row_changed (ETableModel *table_model,
+                      gint row,
+                      ETable *et)
+{
+	if (!et->need_rebuild) {
+		if (e_table_group_remove (et->group, row))
+			e_table_group_add (et->group, row);
+		CHECK_HORIZONTAL (et);
+	}
+}
+
+static void
+et_table_cell_changed (ETableModel *table_model,
+                       gint view_col,
+                       gint row,
+                       ETable *et)
+{
+	et_table_row_changed (table_model, row, et);
+}
+
+static void
+et_table_rows_inserted (ETableModel *table_model,
+                        gint row,
+                        gint count,
+                        ETable *et)
+{
+	/* This number has already been decremented. */
+	gint row_count = e_table_model_row_count (table_model);
+	if (!et->need_rebuild) {
+		gint i;
+		if (row != row_count - count)
+			e_table_group_increment (et->group, row, count);
+		for (i = 0; i < count; i++)
+			e_table_group_add (et->group, row + i);
+		CHECK_HORIZONTAL (et);
+	}
+}
+
+static void
+et_table_rows_deleted (ETableModel *table_model,
+                       gint row,
+                       gint count,
+                       ETable *et)
+{
+	gint row_count = e_table_model_row_count (table_model);
+	if (!et->need_rebuild) {
+		gint i;
+		for (i = 0; i < count; i++)
+			e_table_group_remove (et->group, row + i);
+		if (row != row_count)
+			e_table_group_decrement (et->group, row, count);
+		CHECK_HORIZONTAL (et);
+	}
+}
+
+static void
+et_build_groups (ETable *et)
+{
+	gboolean was_grouped = et->is_grouped;
+
+	et->is_grouped = e_table_sort_info_grouping_get_count (et->sort_info) > 0;
+
+	et->group = e_table_group_new (
+		GNOME_CANVAS_GROUP (et->canvas_vbox),
+		et->full_header,
+		et->header,
+		et->model,
+		et->sort_info,
+		0);
+
+	if (et->use_click_to_add_end)
+		e_canvas_vbox_add_item_start (
+			E_CANVAS_VBOX (et->canvas_vbox),
+			GNOME_CANVAS_ITEM (et->group));
+	else
+		e_canvas_vbox_add_item (
+			E_CANVAS_VBOX (et->canvas_vbox),
+			GNOME_CANVAS_ITEM (et->group));
+
+	gnome_canvas_item_set (
+		GNOME_CANVAS_ITEM (et->group),
+		"alternating_row_colors", et->alternating_row_colors,
+		"horizontal_draw_grid", et->horizontal_draw_grid,
+		"vertical_draw_grid", et->vertical_draw_grid,
+		"drawfocus", et->draw_focus,
+		"cursor_mode", et->cursor_mode,
+		"length_threshold", et->length_threshold,
+		"uniform_row_height", et->uniform_row_height,
+		"selection_model", et->selection,
+		NULL);
+
+	g_signal_connect (
+		et->group, "cursor_change",
+		G_CALLBACK (group_cursor_change), et);
+	g_signal_connect (
+		et->group, "cursor_activated",
+		G_CALLBACK (group_cursor_activated), et);
+	g_signal_connect (
+		et->group, "double_click",
+		G_CALLBACK (group_double_click), et);
+	g_signal_connect (
+		et->group, "right_click",
+		G_CALLBACK (group_right_click), et);
+	g_signal_connect (
+		et->group, "click",
+		G_CALLBACK (group_click), et);
+	g_signal_connect (
+		et->group, "key_press",
+		G_CALLBACK (group_key_press), et);
+	g_signal_connect (
+		et->group, "start_drag",
+		G_CALLBACK (group_start_drag), et);
+
+	if (!(et->is_grouped) && was_grouped)
+		et_disconnect_model (et);
+
+	if (et->is_grouped && (!was_grouped)) {
+		et->table_model_change_id = g_signal_connect (
+			et->model, "model_changed",
+			G_CALLBACK (et_table_model_changed), et);
+
+		et->table_row_change_id = g_signal_connect (
+			et->model, "model_row_changed",
+			G_CALLBACK (et_table_row_changed), et);
+
+		et->table_cell_change_id = g_signal_connect (
+			et->model, "model_cell_changed",
+			G_CALLBACK (et_table_cell_changed), et);
+
+		et->table_rows_inserted_id = g_signal_connect (
+			et->model, "model_rows_inserted",
+			G_CALLBACK (et_table_rows_inserted), et);
+
+		et->table_rows_deleted_id = g_signal_connect (
+			et->model, "model_rows_deleted",
+			G_CALLBACK (et_table_rows_deleted), et);
+
+	}
+
+	if (et->is_grouped)
+		e_table_fill_table (et, et->model);
+}
+
+static gboolean
+changed_idle (gpointer data)
+{
+	ETable *et = E_TABLE (data);
+
+	/* Wait until we have a valid size allocation. */
+	if (et->need_rebuild && et->size_allocated) {
+		GtkWidget *widget;
+		GtkAllocation allocation;
+
+		if (et->group)
+			g_object_run_dispose (G_OBJECT (et->group));
+		et_build_groups (et);
+
+		widget = GTK_WIDGET (et->table_canvas);
+		gtk_widget_get_allocation (widget, &allocation);
+
+		g_object_set (
+			et->canvas_vbox,
+			"width", (gdouble) allocation.width,
+			NULL);
+
+		table_canvas_size_allocate (widget, &allocation, et);
+
+		et->need_rebuild = 0;
+	}
+
+	et->rebuild_idle_id = 0;
+
+	CHECK_HORIZONTAL (et);
+
+	return FALSE;
+}
+
+static void
+et_canvas_realize (GtkWidget *canvas,
+                   ETable *e_table)
+{
+	GtkWidget *widget;
+	GtkStyle *style;
+
+	widget = GTK_WIDGET (e_table->table_canvas);
+	style = gtk_widget_get_style (widget);
+
+	gnome_canvas_item_set (
+		e_table->white_item,
+		"fill_color_gdk", &style->base[GTK_STATE_NORMAL],
+		NULL);
+
+	CHECK_HORIZONTAL (e_table);
+	set_header_width (e_table);
+}
+
+static gboolean
+white_item_event (GnomeCanvasItem *white_item,
+                  GdkEvent *event,
+                  ETable *e_table)
+{
+	gboolean return_val = 0;
+
+	g_signal_emit (
+		e_table, et_signals[WHITE_SPACE_EVENT], 0,
+		event, &return_val);
+
+	return return_val;
+}
+
+static void
+et_eti_leave_edit (ETable *et)
+{
+	GnomeCanvas *canvas = et->table_canvas;
+
+	if (gtk_widget_has_focus (GTK_WIDGET (canvas))) {
+		GnomeCanvasItem *item = GNOME_CANVAS (canvas)->focused_item;
+
+		if (E_IS_TABLE_ITEM (item)) {
+			e_table_item_leave_edit_(E_TABLE_ITEM (item));
+		}
+	}
+}
+
+static gint
+et_canvas_root_event (GnomeCanvasItem *root,
+                      GdkEvent *event,
+                      ETable *e_table)
+{
+	switch (event->type) {
+	case GDK_BUTTON_PRESS:
+	case GDK_2BUTTON_PRESS:
+	case GDK_BUTTON_RELEASE:
+		if (event->button.button != 4 && event->button.button != 5) {
+			et_eti_leave_edit (e_table);
+			return TRUE;
+		}
+		break;
+	default:
+		break;
+	}
+
+	return FALSE;
+}
+
+/* Finds the first descendant of the group that is an ETableItem and focuses it */
+static void
+focus_first_etable_item (ETableGroup *group)
+{
+	GnomeCanvasGroup *cgroup;
+	GList *l;
+
+	cgroup = GNOME_CANVAS_GROUP (group);
+
+	for (l = cgroup->item_list; l; l = l->next) {
+		GnomeCanvasItem *i;
+
+		i = GNOME_CANVAS_ITEM (l->data);
+
+		if (E_IS_TABLE_GROUP (i))
+			focus_first_etable_item (E_TABLE_GROUP (i));
+		else if (E_IS_TABLE_ITEM (i)) {
+			e_table_item_set_cursor (E_TABLE_ITEM (i), 0, 0);
+			gnome_canvas_item_grab_focus (i);
+		}
+	}
+}
+
+/* Handler for focus events in the table_canvas; we have to repaint ourselves
+ * always, and also give the focus to some ETableItem if we get focused.
+ */
+static gint
+table_canvas_focus_event_cb (GtkWidget *widget,
+                             GdkEventFocus *event,
+                             gpointer data)
+{
+	GnomeCanvas *canvas;
+	ECanvas *ecanvas;
+	ETable *etable;
+
+	gtk_widget_queue_draw (widget);
+	canvas = GNOME_CANVAS (widget);
+	ecanvas = E_CANVAS (widget);
+
+	if (!event->in) {
+		gtk_im_context_focus_out (ecanvas->im_context);
+		return FALSE;
+	} else {
+		gtk_im_context_focus_in (ecanvas->im_context);
+	}
+
+	etable = E_TABLE (data);
+
+	if (e_table_model_row_count (etable->model) < 1
+	    && (etable->click_to_add)
+	    && !(E_TABLE_CLICK_TO_ADD (etable->click_to_add)->row)) {
+		gnome_canvas_item_grab_focus (etable->canvas_vbox);
+		gnome_canvas_item_grab_focus (etable->click_to_add);
+	} else if (!canvas->focused_item && etable->group) {
+		focus_first_etable_item (etable->group);
+	} else if (canvas->focused_item) {
+		ESelectionModel *selection = (ESelectionModel *) etable->selection;
+
+		/* check whether click_to_add already got the focus */
+		if (etable->click_to_add) {
+			GnomeCanvasItem *row = E_TABLE_CLICK_TO_ADD (etable->click_to_add)->row;
+			if (canvas->focused_item == row)
+				return TRUE;
+		}
+
+		if (e_selection_model_cursor_row (selection) == -1)
+			focus_first_etable_item (etable->group);
+	}
+
+	return FALSE;
+}
+
+static gboolean
+canvas_vbox_event (ECanvasVbox *vbox,
+                   GdkEventKey *key,
+                   ETable *etable)
+{
+	if (key->type != GDK_KEY_PRESS  &&
+		key->type != GDK_KEY_RELEASE) {
+		return FALSE;
+	}
+	switch (key->keyval) {
+		case GDK_KEY_Tab:
+		case GDK_KEY_KP_Tab:
+		case GDK_KEY_ISO_Left_Tab:
+			if ((key->state & GDK_CONTROL_MASK) && etable->click_to_add) {
+				gnome_canvas_item_grab_focus (etable->click_to_add);
+				break;
+			}
+		default:
+			return FALSE;
+	}
+
+	return TRUE;
+}
+
+static gboolean
+click_to_add_event (ETableClickToAdd *etcta,
+                    GdkEventKey *key,
+                    ETable *etable)
+{
+	if (key->type != GDK_KEY_PRESS  &&
+		key->type != GDK_KEY_RELEASE) {
+		return FALSE;
+	}
+	switch (key->keyval) {
+		case GDK_KEY_Tab:
+		case GDK_KEY_KP_Tab:
+		case GDK_KEY_ISO_Left_Tab:
+			if (key->state & GDK_CONTROL_MASK) {
+				if (etable->group) {
+					if (e_table_model_row_count (etable->model) > 0)
+						focus_first_etable_item (etable->group);
+					else
+						gtk_widget_child_focus (
+							gtk_widget_get_toplevel (
+							GTK_WIDGET (etable->table_canvas)),
+							GTK_DIR_TAB_FORWARD);
+					break;
+				}
+			}
+		default:
+			return FALSE;
+	}
+
+	return FALSE;
+}
+
+static void
+e_table_setup_table (ETable *e_table,
+                     ETableHeader *full_header,
+                     ETableHeader *header,
+                     ETableModel *model)
+{
+	GtkWidget *widget;
+	GtkStyle *style;
+
+	e_table->table_canvas = GNOME_CANVAS (e_canvas_new ());
+	g_signal_connect (
+		e_table->table_canvas, "size_allocate",
+		G_CALLBACK (table_canvas_size_allocate), e_table);
+	g_signal_connect (
+		e_table->table_canvas, "focus_in_event",
+		G_CALLBACK (table_canvas_focus_event_cb), e_table);
+	g_signal_connect (
+		e_table->table_canvas, "focus_out_event",
+		G_CALLBACK (table_canvas_focus_event_cb), e_table);
+
+	g_signal_connect (
+		e_table, "drag_begin",
+		G_CALLBACK (et_drag_begin), e_table);
+	g_signal_connect (
+		e_table, "drag_end",
+		G_CALLBACK (et_drag_end), e_table);
+	g_signal_connect (
+		e_table, "drag_data_get",
+		G_CALLBACK (et_drag_data_get), e_table);
+	g_signal_connect (
+		e_table, "drag_data_delete",
+		G_CALLBACK (et_drag_data_delete), e_table);
+	g_signal_connect (
+		e_table, "drag_motion",
+		G_CALLBACK (et_drag_motion), e_table);
+	g_signal_connect (
+		e_table, "drag_leave",
+		G_CALLBACK (et_drag_leave), e_table);
+	g_signal_connect (
+		e_table, "drag_drop",
+		G_CALLBACK (et_drag_drop), e_table);
+	g_signal_connect (
+		e_table, "drag_data_received",
+		G_CALLBACK (et_drag_data_received), e_table);
+
+	g_signal_connect (
+		e_table->table_canvas, "reflow",
+		G_CALLBACK (table_canvas_reflow), e_table);
+
+	widget = GTK_WIDGET (e_table->table_canvas);
+	style = gtk_widget_get_style (widget);
+
+	gtk_widget_show (widget);
+
+	e_table->white_item = gnome_canvas_item_new (
+		gnome_canvas_root (e_table->table_canvas),
+		e_canvas_background_get_type (),
+		"fill_color_gdk", &style->base[GTK_STATE_NORMAL],
+		NULL);
+
+	g_signal_connect (
+		e_table->white_item, "event",
+		G_CALLBACK (white_item_event), e_table);
+
+	g_signal_connect (
+		e_table->table_canvas, "realize",
+		G_CALLBACK (et_canvas_realize), e_table);
+
+	g_signal_connect (
+		gnome_canvas_root (e_table->table_canvas), "event",
+		G_CALLBACK (et_canvas_root_event), e_table);
+
+	e_table->canvas_vbox = gnome_canvas_item_new (
+		gnome_canvas_root (e_table->table_canvas),
+		e_canvas_vbox_get_type (),
+		"spacing", 10.0,
+		NULL);
+
+	g_signal_connect (
+		e_table->canvas_vbox, "event",
+		G_CALLBACK (canvas_vbox_event), e_table);
+
+	et_build_groups (e_table);
+
+	if (e_table->use_click_to_add) {
+		e_table->click_to_add = gnome_canvas_item_new (
+			GNOME_CANVAS_GROUP (e_table->canvas_vbox),
+			e_table_click_to_add_get_type (),
+			"header", e_table->header,
+			"model", e_table->model,
+			"message", e_table->click_to_add_message,
+			NULL);
+
+		if (e_table->use_click_to_add_end)
+			e_canvas_vbox_add_item (
+				E_CANVAS_VBOX (e_table->canvas_vbox),
+				e_table->click_to_add);
+		else
+			e_canvas_vbox_add_item_start (
+				E_CANVAS_VBOX (e_table->canvas_vbox),
+				e_table->click_to_add);
+
+		g_signal_connect (
+			e_table->click_to_add, "event",
+			G_CALLBACK (click_to_add_event), e_table);
+		g_signal_connect (
+			e_table->click_to_add, "cursor_change",
+			G_CALLBACK (click_to_add_cursor_change), e_table);
+	}
+}
+
+static void
+e_table_fill_table (ETable *e_table,
+                    ETableModel *model)
+{
+	e_table_group_add_all (e_table->group);
+}
+
+/**
+ * e_table_set_state_object:
+ * @e_table: The #ETable object to modify
+ * @state: The #ETableState to use
+ *
+ * This routine sets the state of the #ETable from the given
+ * #ETableState.
+ *
+ **/
+void
+e_table_set_state_object (ETable *e_table,
+                          ETableState *state)
+{
+	GValue *val;
+	GtkWidget *widget;
+	GtkAllocation allocation;
+
+	val = g_new0 (GValue, 1);
+	g_value_init (val, G_TYPE_DOUBLE);
+
+	connect_header (e_table, state);
+
+	widget = GTK_WIDGET (e_table->table_canvas);
+	gtk_widget_get_allocation (widget, &allocation);
+
+	g_value_set_double (val, (gdouble) allocation.width);
+	g_object_set_property (G_OBJECT (e_table->header), "width", val);
+	g_free (val);
+
+	if (e_table->sort_info) {
+		if (e_table->group_info_change_id)
+			g_signal_handler_disconnect (
+				e_table->sort_info,
+				e_table->group_info_change_id);
+		if (e_table->sort_info_change_id)
+			g_signal_handler_disconnect (
+				e_table->sort_info,
+				e_table->sort_info_change_id);
+		g_object_unref (e_table->sort_info);
+	}
+	if (state->sort_info) {
+		e_table->sort_info = e_table_sort_info_duplicate (state->sort_info);
+		e_table_sort_info_set_can_group (
+			e_table->sort_info, e_table->allow_grouping);
+		e_table->group_info_change_id = g_signal_connect (
+			e_table->sort_info, "group_info_changed",
+			G_CALLBACK (group_info_changed), e_table);
+
+		e_table->sort_info_change_id = g_signal_connect (
+			e_table->sort_info, "sort_info_changed",
+			G_CALLBACK (sort_info_changed), e_table);
+	}
+	else
+		e_table->sort_info = NULL;
+
+	if (e_table->sorter)
+		g_object_set (
+			e_table->sorter,
+			"sort_info", e_table->sort_info,
+			NULL);
+	if (e_table->header_item)
+		g_object_set (
+			e_table->header_item,
+			"ETableHeader", e_table->header,
+			"sort_info", e_table->sort_info,
+			NULL);
+	if (e_table->click_to_add)
+		g_object_set (
+			e_table->click_to_add,
+			"header", e_table->header,
+			NULL);
+
+	e_table->need_rebuild = TRUE;
+	if (!e_table->rebuild_idle_id)
+		e_table->rebuild_idle_id = g_idle_add_full (20, changed_idle, e_table, NULL);
+
+	e_table_state_change (e_table);
+}
+
+/**
+ * e_table_set_state:
+ * @e_table: The #ETable object to modify
+ * @state_str: a string representing an #ETableState
+ *
+ * This routine sets the state of the #ETable from a string.
+ *
+ **/
+void
+e_table_set_state (ETable *e_table,
+                   const gchar *state_str)
+{
+	ETableState *state;
+
+	g_return_if_fail (E_IS_TABLE (e_table));
+	g_return_if_fail (state_str != NULL);
+
+	state = e_table_state_new ();
+	e_table_state_load_from_string (state, state_str);
+
+	if (state->col_count > 0)
+		e_table_set_state_object (e_table, state);
+
+	g_object_unref (state);
+}
+
+/**
+ * e_table_load_state:
+ * @e_table: The #ETable object to modify
+ * @filename: name of the file to use
+ *
+ * This routine sets the state of the #ETable from a file.
+ *
+ **/
+void
+e_table_load_state (ETable *e_table,
+                    const gchar *filename)
+{
+	ETableState *state;
+
+	g_return_if_fail (E_IS_TABLE (e_table));
+	g_return_if_fail (filename != NULL);
+
+	state = e_table_state_new ();
+	e_table_state_load_from_file (state, filename);
+
+	if (state->col_count > 0)
+		e_table_set_state_object (e_table, state);
+
+	g_object_unref (state);
+}
+
+/**
+ * e_table_get_state_object:
+ * @e_table: #ETable object to act on
+ *
+ * Builds an #ETableState corresponding to the current state of the
+ * #ETable.
+ *
+ * Return value:
+ * The %ETableState object generated.
+ **/
+ETableState *
+e_table_get_state_object (ETable *e_table)
+{
+	ETableState *state;
+	gint full_col_count;
+	gint i, j;
+
+	state = e_table_state_new ();
+	if (state->sort_info)
+		g_object_unref (state->sort_info);
+	state->sort_info = e_table->sort_info;
+	g_object_ref (state->sort_info);
+
+	state->col_count = e_table_header_count (e_table->header);
+	full_col_count = e_table_header_count (e_table->full_header);
+	state->columns = g_new (int, state->col_count);
+	state->expansions = g_new (double, state->col_count);
+	for (i = 0; i < state->col_count; i++) {
+		ETableCol *col = e_table_header_get_column (e_table->header, i);
+		state->columns[i] = -1;
+		for (j = 0; j < full_col_count; j++) {
+			if (col->col_idx == e_table_header_index (e_table->full_header, j)) {
+				state->columns[i] = j;
+				break;
+			}
+		}
+		state->expansions[i] = col->expansion;
+	}
+
+	return state;
+}
+
+/**
+ * e_table_get_state:
+ * @e_table: The #ETable to act on.
+ *
+ * Builds a state object based on the current state and returns the
+ * string corresponding to that state.
+ *
+ * Return value:
+ * A string describing the current state of the #ETable.
+ **/
+gchar          *e_table_get_state                 (ETable               *e_table)
+{
+	ETableState *state;
+	gchar *string;
+
+	state = e_table_get_state_object (e_table);
+	string = e_table_state_save_to_string (state);
+	g_object_unref (state);
+	return string;
+}
+
+/**
+ * e_table_save_state:
+ * @e_table: The #ETable to act on
+ * @filename: name of the file to save to
+ *
+ * Saves the state of the @e_table object into the file pointed by
+ * @filename.
+ *
+ **/
+void
+e_table_save_state (ETable *e_table,
+                    const gchar *filename)
+{
+	ETableState *state;
+
+	state = e_table_get_state_object (e_table);
+	e_table_state_save_to_file (state, filename);
+	g_object_unref (state);
+}
+
+static void
+et_selection_model_selection_changed (ETableGroup *etg,
+                                      ETable *et)
+{
+	g_signal_emit (et, et_signals[SELECTION_CHANGE], 0);
+}
+
+static void
+et_selection_model_selection_row_changed (ETableGroup *etg,
+                                          gint row,
+                                          ETable *et)
+{
+	g_signal_emit (et, et_signals[SELECTION_CHANGE], 0);
+}
+
+static ETable *
+et_real_construct (ETable *e_table,
+                   ETableModel *etm,
+                   ETableExtras *ete,
+                   ETableSpecification *specification,
+                   ETableState *state)
+{
+	gint row = 0;
+	gint col_count, i;
+	GValue *val;
+	GtkAdjustment *adjustment;
+	GtkScrollable *scrollable;
+
+	val = g_new0 (GValue, 1);
+	g_value_init (val, G_TYPE_OBJECT);
+
+	if (ete)
+		g_object_ref (ete);
+	else {
+		ete = e_table_extras_new ();
+	}
+
+	e_table->domain = g_strdup (specification->domain);
+
+	e_table->use_click_to_add = specification->click_to_add;
+	e_table->use_click_to_add_end = specification->click_to_add_end;
+	e_table->click_to_add_message = specification->click_to_add_message ?
+		g_strdup (
+			dgettext (e_table->domain,
+		specification->click_to_add_message)) : NULL;
+	e_table->alternating_row_colors = specification->alternating_row_colors;
+	e_table->horizontal_draw_grid = specification->horizontal_draw_grid;
+	e_table->vertical_draw_grid = specification->vertical_draw_grid;
+	e_table->draw_focus = specification->draw_focus;
+	e_table->cursor_mode = specification->cursor_mode;
+	e_table->full_header = e_table_spec_to_full_header (specification, ete);
+
+	col_count = e_table_header_count (e_table->full_header);
+	for (i = 0; i < col_count; i++) {
+		ETableCol *col = e_table_header_get_column (e_table->full_header, i);
+		if (col && col->search) {
+			e_table->current_search_col = col;
+			e_table->search_col_set = TRUE;
+			break;
+		}
+	}
+
+	e_table->model = etm;
+	g_object_ref (etm);
+
+	connect_header (e_table, state);
+	e_table->horizontal_scrolling = specification->horizontal_scrolling;
+	e_table->horizontal_resize = specification->horizontal_resize;
+	e_table->allow_grouping = specification->allow_grouping;
+
+	e_table->sort_info = g_object_ref (state->sort_info);
+
+	e_table_sort_info_set_can_group (
+		e_table->sort_info, e_table->allow_grouping);
+
+	e_table->group_info_change_id = g_signal_connect (
+		e_table->sort_info, "group_info_changed",
+		G_CALLBACK (group_info_changed), e_table);
+
+	e_table->sort_info_change_id = g_signal_connect (
+		e_table->sort_info, "sort_info_changed",
+		G_CALLBACK (sort_info_changed), e_table);
+
+	g_value_set_object (val, e_table->sort_info);
+	g_object_set_property (G_OBJECT (e_table->header), "sort_info", val);
+	g_free (val);
+
+	e_table->sorter = e_table_sorter_new (
+		etm, e_table->full_header, e_table->sort_info);
+
+	g_object_set (
+		e_table->selection,
+		"model", etm,
+		"selection_mode", specification->selection_mode,
+		"cursor_mode", specification->cursor_mode,
+		"sorter", e_table->sorter,
+		"header", e_table->header,
+		NULL);
+
+	g_signal_connect (
+		e_table->selection, "selection_changed",
+		G_CALLBACK (et_selection_model_selection_changed), e_table);
+	g_signal_connect (
+		e_table->selection, "selection_row_changed",
+		G_CALLBACK (et_selection_model_selection_row_changed), e_table);
+
+	if (!specification->no_headers)
+		e_table_setup_header (e_table);
+
+	e_table_setup_table (
+		e_table, e_table->full_header, e_table->header, etm);
+	e_table_fill_table (e_table, etm);
+
+	scrollable = GTK_SCROLLABLE (e_table->table_canvas);
+
+	adjustment = gtk_scrollable_get_vadjustment (scrollable);
+	gtk_adjustment_set_step_increment (adjustment, 20);
+
+	adjustment = gtk_scrollable_get_hadjustment (scrollable);
+	gtk_adjustment_set_step_increment (adjustment, 20);
+
+	if (!specification->no_headers) {
+		/* The header */
+		gtk_table_attach (
+			GTK_TABLE (e_table), GTK_WIDGET (e_table->header_canvas),
+			0, 1, 0 + row, 1 + row,
+			GTK_FILL | GTK_EXPAND,
+			GTK_FILL, 0, 0);
+		row++;
+	}
+	gtk_table_attach (
+		GTK_TABLE (e_table), GTK_WIDGET (e_table->table_canvas),
+		0, 1, 0 + row, 1 + row,
+		GTK_FILL | GTK_EXPAND,
+		GTK_FILL | GTK_EXPAND,
+		0, 0);
+
+	g_object_unref (ete);
+
+	return e_table;
+}
+
+/**
+ * e_table_construct:
+ * @e_table: The newly created #ETable object.
+ * @etm: The model for this table.
+ * @ete: An optional #ETableExtras.  (%NULL is valid.)
+ * @spec_str: The spec.
+ * @state_str: An optional state.  (%NULL is valid.)
+ *
+ * This is the internal implementation of e_table_new() for use by
+ * subclasses or language bindings.  See e_table_new() for details.
+ *
+ * Return value:
+ * The passed in value @e_table or %NULL if there's an error.
+ **/
+ETable *
+e_table_construct (ETable *e_table,
+                   ETableModel *etm,
+                   ETableExtras *ete,
+                   const gchar *spec_str,
+                   const gchar *state_str)
+{
+	ETableSpecification *specification;
+	ETableState *state;
+
+	g_return_val_if_fail (E_IS_TABLE (e_table), NULL);
+	g_return_val_if_fail (E_IS_TABLE_MODEL (etm), NULL);
+	g_return_val_if_fail (ete == NULL || E_IS_TABLE_EXTRAS (ete), NULL);
+	g_return_val_if_fail (spec_str != NULL, NULL);
+
+	g_object_ref (etm);
+
+	specification = e_table_specification_new ();
+	g_object_ref (specification);
+	if (!e_table_specification_load_from_string (specification, spec_str)) {
+		g_object_unref (specification);
+		return NULL;
+	}
+
+	if (state_str) {
+		state = e_table_state_new ();
+		g_object_ref (state);
+		e_table_state_load_from_string (state, state_str);
+		if (state->col_count <= 0) {
+			g_object_unref (state);
+			state = specification->state;
+			g_object_ref (state);
+		}
+	} else {
+		state = specification->state;
+		g_object_ref (state);
+	}
+
+	e_table = et_real_construct (e_table, etm, ete, specification, state);
+
+	e_table->spec = specification;
+	g_object_unref (state);
+
+	return e_table;
+}
+
+/**
+ * e_table_construct_from_spec_file:
+ * @e_table: The newly created #ETable object.
+ * @etm: The model for this table.
+ * @ete: An optional #ETableExtras.  (%NULL is valid.)
+ * @spec_fn: The filename of the spec.
+ * @state_fn: An optional state file.  (%NULL is valid.)
+ *
+ * This is the internal implementation of e_table_new_from_spec_file()
+ * for use by subclasses or language bindings.  See
+ * e_table_new_from_spec_file() for details.
+ *
+ * Return value:
+ * The passed in value @e_table or %NULL if there's an error.
+ **/
+ETable *
+e_table_construct_from_spec_file (ETable *e_table,
+                                  ETableModel *etm,
+                                  ETableExtras *ete,
+                                  const gchar *spec_fn,
+                                  const gchar *state_fn)
+{
+	ETableSpecification *specification;
+	ETableState *state;
+
+	g_return_val_if_fail (E_IS_TABLE (e_table), NULL);
+	g_return_val_if_fail (E_IS_TABLE_MODEL (etm), NULL);
+	g_return_val_if_fail (ete == NULL || E_IS_TABLE_EXTRAS (ete), NULL);
+	g_return_val_if_fail (spec_fn != NULL, NULL);
+
+	specification = e_table_specification_new ();
+	if (!e_table_specification_load_from_file (specification, spec_fn)) {
+		g_object_unref (specification);
+		return NULL;
+	}
+
+	if (state_fn) {
+		state = e_table_state_new ();
+		if (!e_table_state_load_from_file (state, state_fn)) {
+			g_object_unref (state);
+			state = specification->state;
+			g_object_ref (state);
+		}
+		if (state->col_count <= 0) {
+			g_object_unref (state);
+			state = specification->state;
+			g_object_ref (state);
+		}
+	} else {
+		state = specification->state;
+		g_object_ref (state);
+	}
+
+	e_table = et_real_construct (e_table, etm, ete, specification, state);
+
+	e_table->spec = specification;
+	g_object_unref (state);
+
+	return e_table;
+}
+
+/**
+ * e_table_new:
+ * @etm: The model for this table.
+ * @ete: An optional #ETableExtras.  (%NULL is valid.)
+ * @spec: The spec.
+ * @state: An optional state.  (%NULL is valid.)
+ *
+ * This function creates an #ETable from the given parameters.  The
+ * #ETableModel is a table model to be represented.  The #ETableExtras
+ * is an optional set of pixbufs, cells, and sorting functions to be
+ * used when interpreting the spec.  If you pass in %NULL it uses the
+ * default #ETableExtras.  (See e_table_extras_new()).
+ *
+ * @spec is the specification of the set of viewable columns and the
+ * default sorting state and such.  @state is an optional string
+ * specifying the current sorting state and such.  If @state is NULL,
+ * then the default state from the spec will be used.
+ *
+ * Return value:
+ * The newly created #ETable or %NULL if there's an error.
+ **/
+GtkWidget *
+e_table_new (ETableModel *etm,
+             ETableExtras *ete,
+             const gchar *spec,
+             const gchar *state)
+{
+	ETable *e_table;
+
+	g_return_val_if_fail (E_IS_TABLE_MODEL (etm), NULL);
+	g_return_val_if_fail (ete == NULL || E_IS_TABLE_EXTRAS (ete), NULL);
+	g_return_val_if_fail (spec != NULL, NULL);
+
+	e_table = g_object_new (E_TYPE_TABLE, NULL);
+
+	e_table = e_table_construct (e_table, etm, ete, spec, state);
+
+	return GTK_WIDGET (e_table);
+}
+
+/**
+ * e_table_new_from_spec_file:
+ * @etm: The model for this table.
+ * @ete: An optional #ETableExtras.  (%NULL is valid.)
+ * @spec_fn: The filename of the spec.
+ * @state_fn: An optional state file.  (%NULL is valid.)
+ *
+ * This is very similar to e_table_new(), except instead of passing in
+ * strings you pass in the file names of the spec and state to load.
+ *
+ * @spec_fn is the filename of the spec to load.  If this file doesn't
+ * exist, e_table_new_from_spec_file will return %NULL.
+ *
+ * @state_fn is the filename of the initial state to load.  If this is
+ * %NULL or if the specified file doesn't exist, the default state
+ * from the spec file is used.
+ *
+ * Return value:
+ * The newly created #ETable or %NULL if there's an error.
+ **/
+GtkWidget *
+e_table_new_from_spec_file (ETableModel *etm,
+                            ETableExtras *ete,
+                            const gchar *spec_fn,
+                            const gchar *state_fn)
+{
+	ETable *e_table;
+
+	g_return_val_if_fail (E_IS_TABLE_MODEL (etm), NULL);
+	g_return_val_if_fail (ete == NULL || E_IS_TABLE_EXTRAS (ete), NULL);
+	g_return_val_if_fail (spec_fn != NULL, NULL);
+
+	e_table = g_object_new (E_TYPE_TABLE, NULL);
+
+	e_table = e_table_construct_from_spec_file (e_table, etm, ete, spec_fn, state_fn);
+
+	return GTK_WIDGET (e_table);
+}
+
+/**
+ * e_table_set_cursor_row:
+ * @e_table: The #ETable to set the cursor row of
+ * @row: The row number
+ *
+ * Sets the cursor row and the selection to the given row number.
+ **/
+void
+e_table_set_cursor_row (ETable *e_table,
+                        gint row)
+{
+	g_return_if_fail (E_IS_TABLE (e_table));
+	g_return_if_fail (row >= 0);
+
+	g_object_set (
+		e_table->selection,
+		"cursor_row", row,
+		NULL);
+}
+
+/**
+ * e_table_get_cursor_row:
+ * @e_table: The #ETable to query
+ *
+ * Calculates the cursor row.  -1 means that we don't have a cursor.
+ *
+ * Return value:
+ * Cursor row
+ **/
+gint
+e_table_get_cursor_row (ETable *e_table)
+{
+	gint row;
+	g_return_val_if_fail (E_IS_TABLE (e_table), -1);
+
+	g_object_get (
+		e_table->selection,
+		"cursor_row", &row,
+		NULL);
+	return row;
+}
+
+/**
+ * e_table_selected_row_foreach:
+ * @e_table: The #ETable to act on
+ * @callback: The callback function to call
+ * @closure: The value passed to the callback's closure argument
+ *
+ * Calls the given @callback function once for every selected row.
+ *
+ * If you change the selection or delete or add rows to the table
+ * during these callbacks, problems can occur.  A standard thing to do
+ * is to create a list of rows or objects the function is called upon
+ * and then act upon that list. (In inverse order if it's rows.)
+ **/
+void
+e_table_selected_row_foreach (ETable *e_table,
+                              EForeachFunc callback,
+                              gpointer closure)
+{
+	g_return_if_fail (E_IS_TABLE (e_table));
+
+	e_selection_model_foreach (E_SELECTION_MODEL (e_table->selection),
+						     callback,
+						     closure);
+}
+
+/**
+ * e_table_selected_count:
+ * @e_table: The #ETable to query
+ *
+ * Counts the number of selected rows.
+ *
+ * Return value:
+ * The number of rows selected.
+ **/
+gint
+e_table_selected_count (ETable *e_table)
+{
+	g_return_val_if_fail (E_IS_TABLE (e_table), -1);
+
+	return e_selection_model_selected_count (E_SELECTION_MODEL (e_table->selection));
+}
+
+/**
+ * e_table_select_all:
+ * @table: The #ETable to modify
+ *
+ * Selects all the rows in @table.
+ **/
+void
+e_table_select_all (ETable *table)
+{
+	g_return_if_fail (E_IS_TABLE (table));
+
+	e_selection_model_select_all (E_SELECTION_MODEL (table->selection));
+}
+
+/**
+ * e_table_invert_selection:
+ * @table: The #ETable to modify
+ *
+ * Inverts the selection in @table.
+ **/
+void
+e_table_invert_selection (ETable *table)
+{
+	g_return_if_fail (E_IS_TABLE (table));
+
+	e_selection_model_invert_selection (E_SELECTION_MODEL (table->selection));
+}
+
+/**
+ * e_table_get_printable:
+ * @e_table: #ETable to query
+ *
+ * Used for printing your #ETable.
+ *
+ * Return value:
+ * The #EPrintable to print.
+ **/
+EPrintable *
+e_table_get_printable (ETable *e_table)
+{
+	g_return_val_if_fail (E_IS_TABLE (e_table), NULL);
+
+	return e_table_group_get_printable (e_table->group);
+}
+
+/**
+ * e_table_right_click_up:
+ * @table: The #ETable to modify.
+ *
+ * Call this function when you're done handling the right click if you
+ * return TRUE from the "right_click" signal.
+ **/
+void
+e_table_right_click_up (ETable *table)
+{
+	e_selection_model_right_click_up (E_SELECTION_MODEL (table->selection));
+}
+
+/**
+ * e_table_commit_click_to_add:
+ * @table: The #ETable to modify
+ *
+ * Commits the current values in the click to add to the table.
+ **/
+void
+e_table_commit_click_to_add (ETable *table)
+{
+	et_eti_leave_edit (table);
+	if (table->click_to_add)
+		e_table_click_to_add_commit (
+			E_TABLE_CLICK_TO_ADD (table->click_to_add));
+}
+
+static void
+et_get_property (GObject *object,
+                 guint property_id,
+                 GValue *value,
+                 GParamSpec *pspec)
+{
+	ETable *etable = E_TABLE (object);
+
+	switch (property_id) {
+	case PROP_MODEL:
+		g_value_set_object (value, etable->model);
+		break;
+	case PROP_UNIFORM_ROW_HEIGHT:
+		g_value_set_boolean (value, etable->uniform_row_height);
+		break;
+	case PROP_ALWAYS_SEARCH:
+		g_value_set_boolean (value, etable->always_search);
+		break;
+	case PROP_USE_CLICK_TO_ADD:
+		g_value_set_boolean (value, etable->use_click_to_add);
+		break;
+	case PROP_HADJUSTMENT:
+		if (etable->table_canvas)
+			g_object_get_property (
+				G_OBJECT (etable->table_canvas),
+				"hadjustment", value);
+		else
+			g_value_set_object (value, NULL);
+		break;
+	case PROP_VADJUSTMENT:
+		if (etable->table_canvas)
+			g_object_get_property (
+				G_OBJECT (etable->table_canvas),
+				"vadjustment", value);
+		else
+			g_value_set_object (value, NULL);
+		break;
+	case PROP_HSCROLL_POLICY:
+		if (etable->table_canvas)
+			g_object_get_property (
+				G_OBJECT (etable->table_canvas),
+				"hscroll-policy", value);
+		else
+			g_value_set_enum (value, 0);
+		break;
+	case PROP_VSCROLL_POLICY:
+		if (etable->table_canvas)
+			g_object_get_property (
+				G_OBJECT (etable->table_canvas),
+				"vscroll-policy", value);
+		else
+			g_value_set_enum (value, 0);
+		break;
+	default:
+		break;
+	}
+}
+
+typedef struct {
+	gchar     *arg;
+	gboolean  setting;
+} bool_closure;
+
+static void
+et_set_property (GObject *object,
+                 guint property_id,
+                 const GValue *value,
+                 GParamSpec *pspec)
+{
+	ETable *etable = E_TABLE (object);
+
+	switch (property_id) {
+	case PROP_LENGTH_THRESHOLD:
+		etable->length_threshold = g_value_get_int (value);
+		if (etable->group) {
+			gnome_canvas_item_set (
+				GNOME_CANVAS_ITEM (etable->group),
+				"length_threshold",
+				etable->length_threshold,
+				NULL);
+		}
+		break;
+	case PROP_UNIFORM_ROW_HEIGHT:
+		etable->uniform_row_height = g_value_get_boolean (value);
+		if (etable->group) {
+			gnome_canvas_item_set (
+				GNOME_CANVAS_ITEM (etable->group),
+				"uniform_row_height",
+				etable->uniform_row_height,
+				NULL);
+		}
+		break;
+	case PROP_ALWAYS_SEARCH:
+		if (etable->always_search == g_value_get_boolean (value))
+			return;
+
+		etable->always_search = g_value_get_boolean (value);
+		clear_current_search_col (etable);
+		break;
+	case PROP_USE_CLICK_TO_ADD:
+		if (etable->use_click_to_add == g_value_get_boolean (value))
+			return;
+
+		etable->use_click_to_add = g_value_get_boolean (value);
+		clear_current_search_col (etable);
+
+		if (etable->use_click_to_add) {
+			etable->click_to_add = gnome_canvas_item_new (
+				GNOME_CANVAS_GROUP (etable->canvas_vbox),
+				e_table_click_to_add_get_type (),
+				"header", etable->header,
+				"model", etable->model,
+				"message", etable->click_to_add_message,
+				NULL);
+
+			if (etable->use_click_to_add_end)
+				e_canvas_vbox_add_item (
+					E_CANVAS_VBOX (etable->canvas_vbox),
+					etable->click_to_add);
+			else
+				e_canvas_vbox_add_item_start (
+					E_CANVAS_VBOX (etable->canvas_vbox),
+					etable->click_to_add);
+
+			g_signal_connect (
+				etable->click_to_add, "cursor_change",
+				G_CALLBACK (click_to_add_cursor_change),
+				etable);
+		} else {
+			g_object_run_dispose (G_OBJECT (etable->click_to_add));
+			etable->click_to_add = NULL;
+		}
+		break;
+	case PROP_HADJUSTMENT:
+		if (etable->table_canvas)
+			g_object_set_property (
+				G_OBJECT (etable->table_canvas),
+				"hadjustment", value);
+		break;
+	case PROP_VADJUSTMENT:
+		if (etable->table_canvas)
+			g_object_set_property (
+				G_OBJECT (etable->table_canvas),
+				"vadjustment", value);
+		break;
+	case PROP_HSCROLL_POLICY:
+		if (etable->table_canvas)
+			g_object_set_property (
+				G_OBJECT (etable->table_canvas),
+				"hscroll-policy", value);
+		break;
+	case PROP_VSCROLL_POLICY:
+		if (etable->table_canvas)
+			g_object_set_property (
+				G_OBJECT (etable->table_canvas),
+				"vscroll-policy", value);
+		break;
+	}
+}
+
+/**
+ * e_table_get_next_row:
+ * @e_table: The #ETable to query
+ * @model_row: The model row to go from
+ *
+ * This function is used when your table is sorted, but you're using
+ * model row numbers.  It returns the next row in sorted order as a model row.
+ *
+ * Return value:
+ * The model row number.
+ **/
+gint
+e_table_get_next_row (ETable *e_table,
+                      gint model_row)
+{
+	g_return_val_if_fail (E_IS_TABLE (e_table), -1);
+
+	if (e_table->sorter) {
+		gint i;
+		i = e_sorter_model_to_sorted (E_SORTER (e_table->sorter), model_row);
+		i++;
+		if (i < e_table_model_row_count (e_table->model)) {
+			return e_sorter_sorted_to_model (E_SORTER (e_table->sorter), i);
+		} else
+			return -1;
+	} else
+		if (model_row < e_table_model_row_count (e_table->model) - 1)
+			return model_row + 1;
+		else
+			return -1;
+}
+
+/**
+ * e_table_get_prev_row:
+ * @e_table: The #ETable to query
+ * @model_row: The model row to go from
+ *
+ * This function is used when your table is sorted, but you're using
+ * model row numbers.  It returns the previous row in sorted order as
+ * a model row.
+ *
+ * Return value:
+ * The model row number.
+ **/
+gint
+e_table_get_prev_row (ETable *e_table,
+                      gint model_row)
+{
+	g_return_val_if_fail (E_IS_TABLE (e_table), -1);
+
+	if (e_table->sorter) {
+		gint i;
+		i = e_sorter_model_to_sorted (E_SORTER (e_table->sorter), model_row);
+		i--;
+		if (i >= 0)
+			return e_sorter_sorted_to_model (E_SORTER (e_table->sorter), i);
+		else
+			return -1;
+	} else
+		return model_row - 1;
+}
+
+/**
+ * e_table_model_to_view_row:
+ * @e_table: The #ETable to query
+ * @model_row: The model row number
+ *
+ * Turns a model row into a view row.
+ *
+ * Return value:
+ * The view row number.
+ **/
+gint
+e_table_model_to_view_row (ETable *e_table,
+                           gint model_row)
+{
+	g_return_val_if_fail (E_IS_TABLE (e_table), -1);
+
+	if (e_table->sorter)
+		return e_sorter_model_to_sorted (E_SORTER (e_table->sorter), model_row);
+	else
+		return model_row;
+}
+
+/**
+ * e_table_view_to_model_row:
+ * @e_table: The #ETable to query
+ * @view_row: The view row number
+ *
+ * Turns a view row into a model row.
+ *
+ * Return value:
+ * The model row number.
+ **/
+gint
+e_table_view_to_model_row (ETable *e_table,
+                           gint view_row)
+{
+	g_return_val_if_fail (E_IS_TABLE (e_table), -1);
+
+	if (e_table->sorter)
+		return e_sorter_sorted_to_model (E_SORTER (e_table->sorter), view_row);
+	else
+		return view_row;
+}
+
+/**
+ * e_table_get_cell_at:
+ * @table: An #ETable widget
+ * @x: X coordinate for the pixel
+ * @y: Y coordinate for the pixel
+ * @row_return: Pointer to return the row value
+ * @col_return: Pointer to return the column value
+ *
+ * Return the row and column for the cell in which the pixel at (@x, @y) is
+ * contained.
+ **/
+void
+e_table_get_cell_at (ETable *table,
+                     gint x,
+                     gint y,
+                     gint *row_return,
+                     gint *col_return)
+{
+	GtkAdjustment *adjustment;
+	GtkScrollable *scrollable;
+
+	g_return_if_fail (E_IS_TABLE (table));
+	g_return_if_fail (row_return != NULL);
+	g_return_if_fail (col_return != NULL);
+
+	/* FIXME it would be nice if it could handle a NULL row_return or
+	 * col_return gracefully.  */
+
+	scrollable = GTK_SCROLLABLE (table->table_canvas);
+
+	adjustment = gtk_scrollable_get_hadjustment (scrollable);
+	x += gtk_adjustment_get_value (adjustment);
+
+	adjustment = gtk_scrollable_get_vadjustment (scrollable);
+	y += gtk_adjustment_get_value (adjustment);
+
+	e_table_group_compute_location (
+		table->group, &x, &y, row_return, col_return);
+}
+
+/**
+ * e_table_get_cell_geometry:
+ * @table: The #ETable.
+ * @row: The row to get the geometry of.
+ * @col: The col to get the geometry of.
+ * @x_return: Returns the x coordinate of the upper left hand corner
+ *            of the cell with respect to the widget.
+ * @y_return: Returns the y coordinate of the upper left hand corner
+ *            of the cell with respect to the widget.
+ * @width_return: Returns the width of the cell.
+ * @height_return: Returns the height of the cell.
+ *
+ * Returns the x, y, width, and height of the given cell.  These can
+ * all be #NULL and they just won't be set.
+ **/
+void
+e_table_get_cell_geometry (ETable *table,
+                           gint row,
+                           gint col,
+                           gint *x_return,
+                           gint *y_return,
+                           gint *width_return,
+                           gint *height_return)
+{
+	GtkAllocation allocation;
+	GtkAdjustment *adjustment;
+	GtkScrollable *scrollable;
+
+	g_return_if_fail (E_IS_TABLE (table));
+
+	scrollable = GTK_SCROLLABLE (table->table_canvas);
+
+	e_table_group_get_cell_geometry (
+		table->group, &row, &col, x_return, y_return,
+		width_return, height_return);
+
+	if (x_return && table->table_canvas) {
+		adjustment = gtk_scrollable_get_hadjustment (scrollable);
+		(*x_return) -= gtk_adjustment_get_value (adjustment);
+	}
+
+	if (y_return) {
+		if (table->table_canvas) {
+			adjustment = gtk_scrollable_get_vadjustment (scrollable);
+			(*y_return) -= gtk_adjustment_get_value (adjustment);
+		}
+
+		if (table->header_canvas) {
+			gtk_widget_get_allocation (
+				GTK_WIDGET (table->header_canvas),
+				&allocation);
+			(*y_return) += allocation.height;
+		}
+	}
+}
+
+/**
+ * e_table_get_mouse_over_cell:
+ *
+ * Similar to e_table_get_cell_at, only here we check
+ * based on the mouse motion information in the group.
+ **/
+void
+e_table_get_mouse_over_cell (ETable *table,
+                             gint *row,
+                             gint *col)
+{
+	g_return_if_fail (E_IS_TABLE (table));
+
+	if (!table->group)
+		return;
+
+	e_table_group_get_mouse_over (table->group, row, col);
+}
+
+/**
+ * e_table_get_selection_model:
+ * @table: The #ETable to query
+ *
+ * Returns the table's #ESelectionModel in case you want to access it
+ * directly.
+ *
+ * Return value:
+ * The #ESelectionModel.
+ **/
+ESelectionModel *
+e_table_get_selection_model (ETable *table)
+{
+	g_return_val_if_fail (E_IS_TABLE (table), NULL);
+
+	return E_SELECTION_MODEL (table->selection);
+}
+
+struct _ETableDragSourceSite
+{
+	GdkModifierType    start_button_mask;
+	GtkTargetList     *target_list;        /* Targets for drag data */
+	GdkDragAction      actions;            /* Possible actions */
+	GdkPixbuf         *pixbuf;             /* Icon for drag data */
+
+	/* Stored button press information to detect drag beginning */
+	gint               state;
+	gint               x, y;
+	gint               row, col;
+};
+
+typedef enum
+{
+  GTK_DRAG_STATUS_DRAG,
+  GTK_DRAG_STATUS_WAIT,
+  GTK_DRAG_STATUS_DROP
+} GtkDragStatus;
+
+typedef struct _GtkDragDestInfo GtkDragDestInfo;
+typedef struct _GtkDragSourceInfo GtkDragSourceInfo;
+
+struct _GtkDragDestInfo
+{
+  GtkWidget         *widget;	   /* Widget in which drag is in */
+  GdkDragContext    *context;	   /* Drag context */
+  GtkDragSourceInfo *proxy_source; /* Set if this is a proxy drag */
+  GtkSelectionData  *proxy_data;   /* Set while retrieving proxied data */
+  guint              dropped : 1;     /* Set after we receive a drop */
+  guint32            proxy_drop_time; /* Timestamp for proxied drop */
+  guint              proxy_drop_wait : 1; /* Set if we are waiting for a
+					   * status reply before sending
+					   * a proxied drop on.
+					   */
+  gint               drop_x, drop_y; /* Position of drop */
+};
+
+struct _GtkDragSourceInfo
+{
+  GtkWidget         *widget;
+  GtkTargetList     *target_list; /* Targets for drag data */
+  GdkDragAction      possible_actions; /* Actions allowed by source */
+  GdkDragContext    *context;	  /* drag context */
+  GtkWidget         *icon_window; /* Window for drag */
+  GtkWidget         *ipc_widget;  /* GtkInvisible for grab, message passing */
+  GdkCursor         *cursor;	  /* Cursor for drag */
+  gint hot_x, hot_y;		  /* Hot spot for drag */
+  gint button;			  /* mouse button starting drag */
+
+  GtkDragStatus      status;	  /* drag status */
+  GdkEvent          *last_event;  /* motion event waiting for response */
+
+  gint               start_x, start_y; /* Initial position */
+  gint               cur_x, cur_y;     /* Current Position */
+
+  GList             *selections;  /* selections we've claimed */
+
+  GtkDragDestInfo   *proxy_dest;  /* Set if this is a proxy drag */
+
+  guint              drop_timeout;     /* Timeout for aborting drop */
+  guint              destroy_icon : 1; /* If true, destroy icon_window
+					*/
+};
+
+/* Drag & drop stuff. */
+/* Target */
+
+/**
+ * e_table_drag_get_data:
+ * @table:
+ * @row:
+ * @col:
+ * @context:
+ * @target:
+ * @time:
+ *
+ *
+ **/
+void
+e_table_drag_get_data (ETable *table,
+                       gint row,
+                       gint col,
+                       GdkDragContext *context,
+                       GdkAtom target,
+                       guint32 time)
+{
+	g_return_if_fail (E_IS_TABLE (table));
+
+	gtk_drag_get_data (
+		GTK_WIDGET (table),
+		context,
+		target,
+		time);
+}
+
+/**
+ * e_table_drag_highlight:
+ * @table: The #ETable to highlight
+ * @row: The row number of the cell to highlight
+ * @col: The column number of the cell to highlight
+ *
+ * Set col to -1 to highlight the entire row.  If row is -1, this is
+ * identical to e_table_drag_unhighlight().
+ **/
+void
+e_table_drag_highlight (ETable *table,
+                        gint row,
+                        gint col)
+{
+	GtkAllocation allocation;
+	GtkAdjustment *adjustment;
+	GtkScrollable *scrollable;
+	GtkStyle *style;
+
+	g_return_if_fail (E_IS_TABLE (table));
+
+	scrollable = GTK_SCROLLABLE (table->table_canvas);
+	style = gtk_widget_get_style (GTK_WIDGET (table));
+	gtk_widget_get_allocation (GTK_WIDGET (scrollable), &allocation);
+
+	if (row != -1) {
+		gint x, y, width, height;
+		if (col == -1) {
+			e_table_get_cell_geometry (table, row, 0, &x, &y, &width, &height);
+			x = 0;
+			width = allocation.width;
+		} else {
+			e_table_get_cell_geometry (table, row, col, &x, &y, &width, &height);
+			adjustment = gtk_scrollable_get_hadjustment (scrollable);
+			x += gtk_adjustment_get_value (adjustment);
+		}
+
+		adjustment = gtk_scrollable_get_vadjustment (scrollable);
+		y += gtk_adjustment_get_value (adjustment);
+
+		if (table->drop_highlight == NULL) {
+			table->drop_highlight = gnome_canvas_item_new (
+				gnome_canvas_root (table->table_canvas),
+				gnome_canvas_rect_get_type (),
+				"fill_color", NULL,
+				"outline_color_gdk", &style->fg[GTK_STATE_NORMAL],
+				NULL);
+		}
+		gnome_canvas_item_set (
+			table->drop_highlight,
+			"x1", (gdouble) x,
+			"x2", (gdouble) x + width - 1,
+			"y1", (gdouble) y,
+			"y2", (gdouble) y + height - 1,
+			NULL);
+	} else {
+		if (table->drop_highlight) {
+			g_object_run_dispose (G_OBJECT (table->drop_highlight));
+			table->drop_highlight = NULL;
+		}
+	}
+}
+
+/**
+ * e_table_drag_unhighlight:
+ * @table: The #ETable to unhighlight
+ *
+ * Removes the highlight from an #ETable.
+ **/
+void
+e_table_drag_unhighlight (ETable *table)
+{
+	g_return_if_fail (E_IS_TABLE (table));
+
+	if (table->drop_highlight) {
+		g_object_run_dispose (G_OBJECT (table->drop_highlight));
+		table->drop_highlight = NULL;
+	}
+}
+
+void
+e_table_drag_dest_set (ETable *table,
+                       GtkDestDefaults flags,
+                       const GtkTargetEntry *targets,
+                       gint n_targets,
+                       GdkDragAction actions)
+{
+	g_return_if_fail (E_IS_TABLE (table));
+
+	gtk_drag_dest_set (
+		GTK_WIDGET (table), flags, targets, n_targets, actions);
+}
+
+void
+e_table_drag_dest_set_proxy (ETable *table,
+                             GdkWindow *proxy_window,
+                             GdkDragProtocol protocol,
+                             gboolean use_coordinates)
+{
+	g_return_if_fail (E_IS_TABLE (table));
+
+	gtk_drag_dest_set_proxy (
+		GTK_WIDGET (table), proxy_window, protocol, use_coordinates);
+}
+
+/*
+ * There probably should be functions for setting the targets
+ * as a GtkTargetList
+ */
+
+void
+e_table_drag_dest_unset (GtkWidget *widget)
+{
+	g_return_if_fail (E_IS_TABLE (widget));
+
+	gtk_drag_dest_unset (widget);
+}
+
+/* Source side */
+
+static gint
+et_real_start_drag (ETable *table,
+                    gint row,
+                    gint col,
+                    GdkEvent *event)
+{
+	GtkDragSourceInfo *info;
+	GdkDragContext *context;
+	ETableDragSourceSite *site;
+
+	if (table->do_drag) {
+		site = table->site;
+
+		site->state = 0;
+		context = e_table_drag_begin (
+			table, row, col,
+			site->target_list,
+			site->actions,
+			1, event);
+
+		if (context) {
+			info = g_dataset_get_data (context, "gtk-info");
+
+			if (info && !info->icon_window) {
+				if (site->pixbuf)
+					gtk_drag_set_icon_pixbuf (
+						context,
+						site->pixbuf,
+						-2, -2);
+				else
+					gtk_drag_set_icon_default (context);
+			}
+		}
+		return TRUE;
+	}
+	return FALSE;
+}
+
+/**
+ * e_table_drag_source_set:
+ * @table: The #ETable to set up as a drag site
+ * @start_button_mask: Mask of allowed buttons to start drag
+ * @targets: Table of targets for this source
+ * @n_targets: Number of targets in @targets
+ * @actions: Actions allowed for this source
+ *
+ * Registers this table as a drag site, and possibly adds default behaviors.
+ **/
+void
+e_table_drag_source_set (ETable *table,
+                         GdkModifierType start_button_mask,
+                         const GtkTargetEntry *targets,
+                         gint n_targets,
+                         GdkDragAction actions)
+{
+	ETableDragSourceSite *site;
+	GtkWidget *canvas;
+
+	g_return_if_fail (E_IS_TABLE (table));
+
+	canvas = GTK_WIDGET (table->table_canvas);
+	site = table->site;
+
+	gtk_widget_add_events (
+		canvas,
+		gtk_widget_get_events (canvas) |
+		GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
+		GDK_BUTTON_MOTION_MASK | GDK_STRUCTURE_MASK);
+
+	table->do_drag = TRUE;
+
+	if (site) {
+		if (site->target_list)
+			gtk_target_list_unref (site->target_list);
+	} else {
+		site = g_new0 (ETableDragSourceSite, 1);
+		table->site = site;
+	}
+
+	site->start_button_mask = start_button_mask;
+
+	if (targets)
+		site->target_list = gtk_target_list_new (targets, n_targets);
+	else
+		site->target_list = NULL;
+
+	site->actions = actions;
+}
+
+/**
+ * e_table_drag_source_unset:
+ * @table: The #ETable to un set up as a drag site
+ *
+ * Unregisters this #ETable as a drag site.
+ **/
+void
+e_table_drag_source_unset (ETable *table)
+{
+	ETableDragSourceSite *site;
+
+	g_return_if_fail (E_IS_TABLE (table));
+
+	site = table->site;
+
+	if (site) {
+		if (site->target_list)
+			gtk_target_list_unref (site->target_list);
+		g_free (site);
+		table->site = NULL;
+	}
+	table->do_drag = FALSE;
+}
+
+/* There probably should be functions for setting the targets
+ * as a GtkTargetList
+ */
+
+/**
+ * e_table_drag_begin:
+ * @table: The #ETable to drag from
+ * @row: The row number of the cell
+ * @col: The col number of the cell
+ * @targets: The list of targets supported by the drag
+ * @actions: The available actions supported by the drag
+ * @button: The button held down for the drag
+ * @event: The event that initiated the drag
+ *
+ * Start a drag from this cell.
+ *
+ * Return value:
+ * The drag context.
+ **/
+GdkDragContext *
+e_table_drag_begin (ETable *table,
+                    gint row,
+                    gint col,
+                    GtkTargetList *targets,
+                    GdkDragAction actions,
+                    gint button,
+                    GdkEvent *event)
+{
+	g_return_val_if_fail (E_IS_TABLE (table), NULL);
+
+	table->drag_row = row;
+	table->drag_col = col;
+
+	return gtk_drag_begin (
+		GTK_WIDGET (table), targets, actions, button, event);
+}
+
+static void
+et_drag_begin (GtkWidget *widget,
+               GdkDragContext *context,
+               ETable *et)
+{
+	g_signal_emit (
+		et, et_signals[TABLE_DRAG_BEGIN], 0,
+		et->drag_row, et->drag_col, context);
+}
+
+static void
+et_drag_end (GtkWidget *widget,
+             GdkDragContext *context,
+             ETable *et)
+{
+	g_signal_emit (
+		et, et_signals[TABLE_DRAG_END], 0,
+		et->drag_row, et->drag_col, context);
+}
+
+static void
+et_drag_data_get (GtkWidget *widget,
+                  GdkDragContext *context,
+                  GtkSelectionData *selection_data,
+                  guint info,
+                  guint time,
+                  ETable *et)
+{
+	g_signal_emit (
+		et, et_signals[TABLE_DRAG_DATA_GET], 0,
+		et->drag_row, et->drag_col, context, selection_data,
+		info, time);
+}
+
+static void
+et_drag_data_delete (GtkWidget *widget,
+                     GdkDragContext *context,
+                     ETable *et)
+{
+	g_signal_emit (
+		et, et_signals[TABLE_DRAG_DATA_DELETE], 0,
+		et->drag_row, et->drag_col, context);
+}
+
+static gboolean
+do_drag_motion (ETable *et,
+                GdkDragContext *context,
+                gint x,
+                gint y,
+                guint time)
+{
+	gboolean ret_val;
+	gint row = -1, col = -1;
+
+	e_table_get_cell_at (et, x, y, &row, &col);
+
+	if (row != et->drop_row && col != et->drop_row) {
+		g_signal_emit (
+			et, et_signals[TABLE_DRAG_LEAVE], 0,
+			et->drop_row, et->drop_col, context, time);
+	}
+
+	et->drop_row = row;
+	et->drop_col = col;
+
+	g_signal_emit (
+		et, et_signals[TABLE_DRAG_MOTION], 0,
+		et->drop_row, et->drop_col, context, x, y, time, &ret_val);
+
+	return ret_val;
+}
+
+static gboolean
+scroll_timeout (gpointer data)
+{
+	ETable *et = data;
+	gint dx = 0, dy = 0;
+	GtkAdjustment *adjustment;
+	GtkScrollable *scrollable;
+	gdouble old_h_value;
+	gdouble new_h_value;
+	gdouble old_v_value;
+	gdouble new_v_value;
+	gdouble page_size;
+	gdouble lower;
+	gdouble upper;
+
+	if (et->scroll_direction & ET_SCROLL_DOWN)
+		dy += 20;
+	if (et->scroll_direction & ET_SCROLL_UP)
+		dy -= 20;
+
+	if (et->scroll_direction & ET_SCROLL_RIGHT)
+		dx += 20;
+	if (et->scroll_direction & ET_SCROLL_LEFT)
+		dx -= 20;
+
+	scrollable = GTK_SCROLLABLE (et->table_canvas);
+
+	adjustment = gtk_scrollable_get_hadjustment (scrollable);
+
+	lower = gtk_adjustment_get_lower (adjustment);
+	upper = gtk_adjustment_get_upper (adjustment);
+	page_size = gtk_adjustment_get_page_size (adjustment);
+
+	old_h_value = gtk_adjustment_get_value (adjustment);
+	new_h_value = CLAMP (old_h_value + dx, lower, upper - page_size);
+
+	gtk_adjustment_set_value (adjustment, new_h_value);
+
+	adjustment = gtk_scrollable_get_vadjustment (scrollable);
+
+	lower = gtk_adjustment_get_lower (adjustment);
+	upper = gtk_adjustment_get_upper (adjustment);
+	page_size = gtk_adjustment_get_page_size (adjustment);
+
+	old_v_value = gtk_adjustment_get_value (adjustment);
+	new_v_value = CLAMP (old_v_value + dy, lower, upper - page_size);
+
+	gtk_adjustment_set_value (adjustment, new_v_value);
+
+	if (new_h_value != old_h_value || new_v_value != old_v_value)
+		do_drag_motion (
+			et,
+			et->last_drop_context,
+			et->last_drop_x,
+			et->last_drop_y,
+			et->last_drop_time);
+
+	return TRUE;
+}
+
+static void
+scroll_on (ETable *et,
+           guint scroll_direction)
+{
+	if (et->scroll_idle_id == 0 || scroll_direction != et->scroll_direction) {
+		if (et->scroll_idle_id != 0)
+			g_source_remove (et->scroll_idle_id);
+		et->scroll_direction = scroll_direction;
+		et->scroll_idle_id = g_timeout_add (100, scroll_timeout, et);
+	}
+}
+
+static void
+scroll_off (ETable *et)
+{
+	if (et->scroll_idle_id) {
+		g_source_remove (et->scroll_idle_id);
+		et->scroll_idle_id = 0;
+	}
+}
+
+static void
+context_destroyed (gpointer data)
+{
+	ETable *et = data;
+	/* if (!G_OBJECT_DESTROYED (et)) */
+/* FIXME: */
+	{
+		et->last_drop_x       = 0;
+		et->last_drop_y       = 0;
+		et->last_drop_time    = 0;
+		et->last_drop_context = NULL;
+		scroll_off (et);
+	}
+	g_object_unref (et);
+}
+
+static void
+context_connect (ETable *et,
+                 GdkDragContext *context)
+{
+	if (g_dataset_get_data (context, "e-table") == NULL) {
+		g_object_ref (et);
+		g_dataset_set_data_full (context, "e-table", et, context_destroyed);
+	}
+}
+
+static void
+et_drag_leave (GtkWidget *widget,
+               GdkDragContext *context,
+               guint time,
+               ETable *et)
+{
+	g_signal_emit (
+		et, et_signals[TABLE_DRAG_LEAVE], 0,
+		et->drop_row, et->drop_col, context, time);
+
+	et->drop_row = -1;
+	et->drop_col = -1;
+
+	scroll_off (et);
+}
+
+static gboolean
+et_drag_motion (GtkWidget *widget,
+                GdkDragContext *context,
+                gint x,
+                gint y,
+                guint time,
+                ETable *et)
+{
+	GtkAllocation allocation;
+	gboolean ret_val;
+	guint direction = 0;
+
+	gtk_widget_get_allocation (widget, &allocation);
+
+	et->last_drop_x = x;
+	et->last_drop_y = y;
+	et->last_drop_time = time;
+	et->last_drop_context = context;
+	context_connect (et, context);
+
+	ret_val = do_drag_motion (et, context, x, y, time);
+
+	if (y < 20)
+		direction |= ET_SCROLL_UP;
+	if (y > allocation.height - 20)
+		direction |= ET_SCROLL_DOWN;
+	if (x < 20)
+		direction |= ET_SCROLL_LEFT;
+	if (x > allocation.width - 20)
+		direction |= ET_SCROLL_RIGHT;
+
+	if (direction != 0)
+		scroll_on (et, direction);
+	else
+		scroll_off (et);
+
+	return ret_val;
+}
+
+static gboolean
+et_drag_drop (GtkWidget *widget,
+              GdkDragContext *context,
+              gint x,
+              gint y,
+              guint time,
+              ETable *et)
+{
+	gboolean ret_val;
+	gint row, col;
+
+	e_table_get_cell_at (et, x, y, &row, &col);
+
+	if (row != et->drop_row && col != et->drop_row) {
+		g_signal_emit (
+			et, et_signals[TABLE_DRAG_LEAVE], 0,
+			et->drop_row, et->drop_col, context, time);
+		g_signal_emit (
+			et, et_signals[TABLE_DRAG_MOTION], 0,
+			row, col, context, x, y, time, &ret_val);
+	}
+	et->drop_row = row;
+	et->drop_col = col;
+	g_signal_emit (
+		et, et_signals[TABLE_DRAG_DROP], 0,
+		et->drop_row, et->drop_col, context, x, y, time, &ret_val);
+	et->drop_row = -1;
+	et->drop_col = -1;
+
+	scroll_off (et);
+
+	return ret_val;
+}
+
+static void
+et_drag_data_received (GtkWidget *widget,
+                       GdkDragContext *context,
+                       gint x,
+                       gint y,
+                       GtkSelectionData *selection_data,
+                       guint info,
+                       guint time,
+                       ETable *et)
+{
+	gint row, col;
+
+	e_table_get_cell_at (et, x, y, &row, &col);
+
+	g_signal_emit (
+		et, et_signals[TABLE_DRAG_DATA_RECEIVED], 0,
+		row, col, context, x, y, selection_data, info, time);
+}
+
+static void
+e_table_class_init (ETableClass *class)
+{
+	GObjectClass *object_class;
+	GtkWidgetClass *widget_class;
+
+	object_class                    = (GObjectClass *) class;
+	widget_class                    = (GtkWidgetClass *) class;
+
+	object_class->dispose           = et_dispose;
+	object_class->finalize          = et_finalize;
+	object_class->set_property      = et_set_property;
+	object_class->get_property      = et_get_property;
+
+	widget_class->grab_focus        = et_grab_focus;
+	widget_class->unrealize         = et_unrealize;
+	widget_class->get_preferred_width = et_get_preferred_width;
+	widget_class->get_preferred_height = et_get_preferred_height;
+
+	widget_class->focus             = et_focus;
+
+	class->cursor_change            = NULL;
+	class->cursor_activated         = NULL;
+	class->selection_change         = NULL;
+	class->double_click             = NULL;
+	class->right_click              = NULL;
+	class->click                    = NULL;
+	class->key_press                = NULL;
+	class->start_drag               = et_real_start_drag;
+	class->state_change             = NULL;
+	class->white_space_event        = NULL;
+
+	class->table_drag_begin         = NULL;
+	class->table_drag_end           = NULL;
+	class->table_drag_data_get      = NULL;
+	class->table_drag_data_delete   = NULL;
+
+	class->table_drag_leave         = NULL;
+	class->table_drag_motion        = NULL;
+	class->table_drag_drop          = NULL;
+	class->table_drag_data_received = NULL;
+
+	et_signals[CURSOR_CHANGE] = g_signal_new (
+		"cursor_change",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ETableClass, cursor_change),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__INT,
+		G_TYPE_NONE, 1, G_TYPE_INT);
+
+	et_signals[CURSOR_ACTIVATED] = g_signal_new (
+		"cursor_activated",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ETableClass, cursor_activated),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__INT,
+		G_TYPE_NONE, 1, G_TYPE_INT);
+
+	et_signals[SELECTION_CHANGE] = g_signal_new (
+		"selection_change",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ETableClass, selection_change),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__VOID,
+		G_TYPE_NONE, 0);
+
+	et_signals[DOUBLE_CLICK] = g_signal_new (
+		"double_click",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ETableClass, double_click),
+		NULL, NULL,
+		e_marshal_NONE__INT_INT_BOXED,
+		G_TYPE_NONE, 3,
+		G_TYPE_INT,
+		G_TYPE_INT,
+		GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
+
+	et_signals[RIGHT_CLICK] = g_signal_new (
+		"right_click",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ETableClass, right_click),
+		g_signal_accumulator_true_handled, NULL,
+		e_marshal_BOOLEAN__INT_INT_BOXED,
+		G_TYPE_BOOLEAN, 3,
+		G_TYPE_INT,
+		G_TYPE_INT,
+		GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
+
+	et_signals[CLICK] = g_signal_new (
+		"click",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ETableClass, click),
+		g_signal_accumulator_true_handled, NULL,
+		e_marshal_BOOLEAN__INT_INT_BOXED,
+		G_TYPE_BOOLEAN, 3,
+		G_TYPE_INT,
+		G_TYPE_INT,
+		GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
+
+	et_signals[KEY_PRESS] = g_signal_new (
+		"key_press",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ETableClass, key_press),
+		g_signal_accumulator_true_handled, NULL,
+		e_marshal_BOOLEAN__INT_INT_BOXED,
+		G_TYPE_BOOLEAN, 3,
+		G_TYPE_INT,
+		G_TYPE_INT,
+		GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
+
+	et_signals[START_DRAG] = g_signal_new (
+		"start_drag",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ETableClass, start_drag),
+		g_signal_accumulator_true_handled, NULL,
+		e_marshal_BOOLEAN__INT_INT_BOXED,
+		G_TYPE_BOOLEAN, 3,
+		G_TYPE_INT,
+		G_TYPE_INT,
+		GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
+
+	et_signals[STATE_CHANGE] = g_signal_new (
+		"state_change",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ETableClass, state_change),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__VOID,
+		G_TYPE_NONE, 0);
+
+	et_signals[WHITE_SPACE_EVENT] = g_signal_new (
+		"white_space_event",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ETableClass, white_space_event),
+		g_signal_accumulator_true_handled, NULL,
+		e_marshal_BOOLEAN__BOXED,
+		G_TYPE_BOOLEAN, 1,
+		GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
+
+	et_signals[TABLE_DRAG_BEGIN] = g_signal_new (
+		"table_drag_begin",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ETableClass, table_drag_begin),
+		NULL, NULL,
+		e_marshal_NONE__INT_INT_OBJECT,
+		G_TYPE_NONE, 3,
+		G_TYPE_INT,
+		G_TYPE_INT,
+		GDK_TYPE_DRAG_CONTEXT);
+
+	et_signals[TABLE_DRAG_END] = g_signal_new (
+		"table_drag_end",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ETableClass, table_drag_end),
+		NULL, NULL,
+		e_marshal_NONE__INT_INT_OBJECT,
+		G_TYPE_NONE, 3,
+		G_TYPE_INT,
+		G_TYPE_INT,
+		GDK_TYPE_DRAG_CONTEXT);
+
+	et_signals[TABLE_DRAG_DATA_GET] = g_signal_new (
+		"table_drag_data_get",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ETableClass, table_drag_data_get),
+		NULL, NULL,
+		e_marshal_NONE__INT_INT_OBJECT_BOXED_UINT_UINT,
+		G_TYPE_NONE, 6,
+		G_TYPE_INT,
+		G_TYPE_INT,
+		GDK_TYPE_DRAG_CONTEXT,
+		GTK_TYPE_SELECTION_DATA | G_SIGNAL_TYPE_STATIC_SCOPE,
+		G_TYPE_UINT,
+		G_TYPE_UINT);
+
+	et_signals[TABLE_DRAG_DATA_DELETE] = g_signal_new (
+		"table_drag_data_delete",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ETableClass, table_drag_data_delete),
+		NULL, NULL,
+		e_marshal_NONE__INT_INT_OBJECT,
+		G_TYPE_NONE, 3,
+		G_TYPE_INT,
+		G_TYPE_INT,
+		GDK_TYPE_DRAG_CONTEXT);
+
+	et_signals[TABLE_DRAG_LEAVE] = g_signal_new (
+		"table_drag_leave",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ETableClass, table_drag_leave),
+		NULL, NULL,
+		e_marshal_NONE__INT_INT_OBJECT_UINT,
+		G_TYPE_NONE, 4,
+		G_TYPE_INT,
+		G_TYPE_INT,
+		GDK_TYPE_DRAG_CONTEXT,
+		G_TYPE_UINT);
+
+	et_signals[TABLE_DRAG_MOTION] = g_signal_new (
+		"table_drag_motion",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ETableClass, table_drag_motion),
+		NULL, NULL,
+		e_marshal_BOOLEAN__INT_INT_OBJECT_INT_INT_UINT,
+		G_TYPE_BOOLEAN, 6,
+		G_TYPE_INT,
+		G_TYPE_INT,
+		GDK_TYPE_DRAG_CONTEXT,
+		G_TYPE_INT,
+		G_TYPE_INT,
+		G_TYPE_UINT);
+
+	et_signals[TABLE_DRAG_DROP] = g_signal_new (
+		"table_drag_drop",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ETableClass, table_drag_drop),
+		NULL, NULL,
+		e_marshal_BOOLEAN__INT_INT_OBJECT_INT_INT_UINT,
+		G_TYPE_BOOLEAN, 6,
+		G_TYPE_INT,
+		G_TYPE_INT,
+		GDK_TYPE_DRAG_CONTEXT,
+		G_TYPE_INT,
+		G_TYPE_INT,
+		G_TYPE_UINT);
+
+	et_signals[TABLE_DRAG_DATA_RECEIVED] = g_signal_new (
+		"table_drag_data_received",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ETableClass, table_drag_data_received),
+		NULL, NULL,
+		e_marshal_NONE__INT_INT_OBJECT_INT_INT_BOXED_UINT_UINT,
+		G_TYPE_NONE, 8,
+		G_TYPE_INT,
+		G_TYPE_INT,
+		GDK_TYPE_DRAG_CONTEXT,
+		G_TYPE_INT,
+		G_TYPE_INT,
+		GTK_TYPE_SELECTION_DATA | G_SIGNAL_TYPE_STATIC_SCOPE,
+		G_TYPE_UINT,
+		G_TYPE_UINT);
+
+	g_object_class_install_property (
+		object_class,
+		PROP_LENGTH_THRESHOLD,
+		g_param_spec_int (
+			"length_threshold",
+			"Length Threshold",
+			NULL,
+			0, G_MAXINT, 0,
+			G_PARAM_WRITABLE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_UNIFORM_ROW_HEIGHT,
+		g_param_spec_boolean (
+			"uniform_row_height",
+			"Uniform row height",
+			NULL,
+			FALSE,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_ALWAYS_SEARCH,
+		g_param_spec_boolean (
+			"always_search",
+			"Always search",
+			NULL,
+			FALSE,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_USE_CLICK_TO_ADD,
+		g_param_spec_boolean (
+			"use_click_to_add",
+			"Use click to add",
+			NULL,
+			FALSE,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_MODEL,
+		g_param_spec_object (
+			"model",
+			"Model",
+			NULL,
+			E_TYPE_TABLE_MODEL,
+			G_PARAM_READABLE));
+
+	gtk_widget_class_install_style_property (
+		widget_class,
+		g_param_spec_int (
+			"vertical-spacing",
+			"Vertical Row Spacing",
+			"Vertical space between rows. "
+			"It is added to top and to bottom of a row",
+			0, G_MAXINT, 3,
+			G_PARAM_READABLE |
+			G_PARAM_STATIC_STRINGS));
+
+	/* Scrollable interface */
+	g_object_class_override_property (
+		object_class, PROP_HADJUSTMENT, "hadjustment");
+	g_object_class_override_property (
+		object_class, PROP_VADJUSTMENT, "vadjustment");
+	g_object_class_override_property (
+		object_class, PROP_HSCROLL_POLICY, "hscroll-policy");
+	g_object_class_override_property (
+		object_class, PROP_VSCROLL_POLICY, "vscroll-policy");
+
+	gal_a11y_e_table_init ();
+}
+
+void
+e_table_freeze_state_change (ETable *table)
+{
+	g_return_if_fail (table != NULL);
+
+	table->state_change_freeze++;
+	if (table->state_change_freeze == 1)
+		table->state_changed = FALSE;
+
+	g_return_if_fail (table->state_change_freeze != 0);
+}
+
+void
+e_table_thaw_state_change (ETable *table)
+{
+	g_return_if_fail (table != NULL);
+	g_return_if_fail (table->state_change_freeze != 0);
+
+	table->state_change_freeze--;
+	if (table->state_change_freeze == 0 && table->state_changed) {
+		table->state_changed = FALSE;
+		e_table_state_change (table);
+	}
+}
diff --git a/e-util/e-table.h b/e-util/e-table.h
new file mode 100644
index 0000000..8370e44
--- /dev/null
+++ b/e-util/e-table.h
@@ -0,0 +1,403 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *		Miguel de Icaza <miguel ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef _E_TABLE_H_
+#define _E_TABLE_H_
+
+#include <libgnomecanvas/libgnomecanvas.h>
+#include <gtk/gtk.h>
+#include <libxml/tree.h>
+
+#include <e-util/e-printable.h>
+#include <e-util/e-table-extras.h>
+#include <e-util/e-table-group.h>
+#include <e-util/e-table-header.h>
+#include <e-util/e-table-item.h>
+#include <e-util/e-table-model.h>
+#include <e-util/e-table-search.h>
+#include <e-util/e-table-selection-model.h>
+#include <e-util/e-table-sort-info.h>
+#include <e-util/e-table-sorter.h>
+#include <e-util/e-table-specification.h>
+#include <e-util/e-table-state.h>
+
+/* Standard GObject macros */
+#define E_TYPE_TABLE \
+	(e_table_get_type ())
+#define E_TABLE(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_TABLE, ETable))
+#define E_TABLE_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_TABLE, ETableClass))
+#define E_IS_TABLE(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_TABLE))
+#define E_IS_TABLE_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_TABLE))
+#define E_TABLE_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_TABLE, ETableClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ETable ETable;
+typedef struct _ETableClass ETableClass;
+
+typedef struct _ETableDragSourceSite ETableDragSourceSite;
+
+typedef enum {
+	E_TABLE_CURSOR_LOC_NONE = 0,
+	E_TABLE_CURSOR_LOC_ETCTA = 1 << 0,
+	E_TABLE_CURSOR_LOC_TABLE = 1 << 1
+} ETableCursorLoc;
+
+struct _ETable {
+	GtkTable parent;
+
+	ETableModel *model;
+
+	ETableHeader *full_header, *header;
+
+	GnomeCanvasItem *canvas_vbox;
+	ETableGroup *group;
+
+	ETableSortInfo *sort_info;
+	ETableSorter *sorter;
+
+	ETableSelectionModel *selection;
+	ETableCursorLoc cursor_loc;
+	ETableSpecification *spec;
+
+	ETableSearch *search;
+
+	ETableCol *current_search_col;
+
+	guint	 search_search_id;
+	guint	 search_accept_id;
+
+	gint table_model_change_id;
+	gint table_row_change_id;
+	gint table_cell_change_id;
+	gint table_rows_inserted_id;
+	gint table_rows_deleted_id;
+
+	gint group_info_change_id;
+	gint sort_info_change_id;
+
+	gint structure_change_id;
+	gint expansion_change_id;
+	gint dimension_change_id;
+
+	gint reflow_idle_id;
+	gint scroll_idle_id;
+
+	GnomeCanvas *header_canvas, *table_canvas;
+
+	GnomeCanvasItem *header_item, *root;
+
+	GnomeCanvasItem *white_item;
+
+	gint length_threshold;
+
+	gint rebuild_idle_id;
+	guint need_rebuild : 1;
+	guint size_allocated : 1;
+
+	/*
+	 * Configuration settings
+	 */
+	guint alternating_row_colors : 1;
+	guint horizontal_draw_grid : 1;
+	guint vertical_draw_grid : 1;
+	guint draw_focus : 1;
+	guint row_selection_active : 1;
+
+	guint horizontal_scrolling : 1;
+	guint horizontal_resize : 1;
+
+	guint is_grouped : 1;
+
+	guint scroll_direction : 4;
+
+	guint do_drag : 1;
+
+	guint uniform_row_height : 1;
+	guint allow_grouping : 1;
+
+	guint always_search : 1;
+	guint search_col_set : 1;
+
+	gchar *click_to_add_message;
+	GnomeCanvasItem *click_to_add;
+	gboolean use_click_to_add;
+	gboolean use_click_to_add_end;
+
+	ECursorMode cursor_mode;
+
+	gint drop_row;
+	gint drop_col;
+	GnomeCanvasItem *drop_highlight;
+	gint last_drop_x;
+	gint last_drop_y;
+	gint last_drop_time;
+	GdkDragContext *last_drop_context;
+
+	gint drag_row;
+	gint drag_col;
+	ETableDragSourceSite *site;
+
+	gint header_width;
+
+	gchar *domain;
+
+	gboolean state_changed;
+	guint state_change_freeze;
+};
+
+struct _ETableClass {
+	GtkTableClass parent_class;
+
+	void		(*cursor_change)	(ETable *et,
+						 gint row);
+	void		(*cursor_activated)	(ETable *et,
+						 gint row);
+	void		(*selection_change)	(ETable *et);
+	void		(*double_click)		(ETable *et,
+						 gint row,
+						 gint col,
+						 GdkEvent *event);
+	gboolean	(*right_click)		(ETable *et,
+						 gint row,
+						 gint col,
+						 GdkEvent *event);
+	gboolean	(*click)		(ETable *et,
+						 gint row,
+						 gint col,
+						 GdkEvent *event);
+	gboolean	(*key_press)		(ETable *et,
+						 gint row,
+						 gint col,
+						 GdkEvent *event);
+	gboolean	(*start_drag)		(ETable *et,
+						 gint row,
+						 gint col,
+						 GdkEvent *event);
+	void		(*state_change)		(ETable *et);
+	gboolean	(*white_space_event)	(ETable *et,
+						 GdkEvent *event);
+
+	/* Source side drag signals */
+	void		(*table_drag_begin)	 (ETable *table,
+						 gint row,
+						 gint col,
+						 GdkDragContext *context);
+	void		(*table_drag_end)	(ETable *table,
+						 gint row,
+						 gint col,
+						 GdkDragContext *context);
+	void		(*table_drag_data_get)	(ETable *table,
+						 gint row,
+						 gint col,
+						 GdkDragContext *context,
+						 GtkSelectionData *selection_data,
+						 guint info,
+						 guint time);
+	void		(*table_drag_data_delete)
+						(ETable *table,
+						 gint row,
+						 gint col,
+						 GdkDragContext *context);
+
+	/* Target side drag signals */
+	void		(*table_drag_leave)	(ETable *table,
+						 gint row,
+						 gint col,
+						 GdkDragContext *context,
+						 guint time);
+	gboolean	(*table_drag_motion)	(ETable *table,
+						 gint row,
+						 gint col,
+						 GdkDragContext *context,
+						 gint x,
+						 gint y,
+						 guint time);
+	gboolean	(*table_drag_drop)	(ETable *table,
+						 gint row,
+						 gint col,
+						 GdkDragContext *context,
+						 gint x,
+						 gint y,
+						 guint time);
+	void		(*table_drag_data_received)
+						(ETable *table,
+						 gint row,
+						 gint col,
+						 GdkDragContext *context,
+						 gint x,
+						 gint y,
+						 GtkSelectionData *selection_data,
+						 guint info,
+						 guint time);
+};
+
+GType		e_table_get_type		(void) G_GNUC_CONST;
+ETable *	e_table_construct		(ETable *e_table,
+						 ETableModel *etm,
+						 ETableExtras *ete,
+						 const gchar *spec,
+						 const gchar *state);
+GtkWidget *	e_table_new			(ETableModel *etm,
+						 ETableExtras *ete,
+						 const gchar *spec,
+						 const gchar *state);
+
+/* Create an ETable using files. */
+ETable *	e_table_construct_from_spec_file
+						(ETable *e_table,
+						 ETableModel *etm,
+						 ETableExtras *ete,
+						 const gchar *spec_fn,
+						 const gchar *state_fn);
+GtkWidget *	e_table_new_from_spec_file	(ETableModel *etm,
+						 ETableExtras *ete,
+						 const gchar *spec_fn,
+						 const gchar *state_fn);
+
+/* To save the state */
+gchar *		e_table_get_state		(ETable *e_table);
+void		e_table_save_state		(ETable *e_table,
+						 const gchar *filename);
+ETableState *e_table_get_state_object		(ETable *e_table);
+
+/* note that it is more efficient to provide the state at creation time */
+void		e_table_set_state		(ETable *e_table,
+						 const gchar *state);
+void		e_table_set_state_object	(ETable *e_table,
+						 ETableState *state);
+void		e_table_load_state		(ETable *e_table,
+						 const gchar *filename);
+void		e_table_set_cursor_row		(ETable *e_table,
+						 gint row);
+
+/* -1 means we don't have the cursor. This is in model rows. */
+gint		e_table_get_cursor_row		(ETable *e_table);
+void		e_table_selected_row_foreach	(ETable *e_table,
+						 EForeachFunc callback,
+						 gpointer closure);
+gint		e_table_selected_count		(ETable *e_table);
+EPrintable *	e_table_get_printable		(ETable *e_table);
+gint		e_table_get_next_row		(ETable *e_table,
+						 gint model_row);
+gint		e_table_get_prev_row		(ETable *e_table,
+						 gint model_row);
+gint		e_table_model_to_view_row	(ETable *e_table,
+						 gint model_row);
+gint		e_table_view_to_model_row	(ETable *e_table,
+						 gint view_row);
+void		e_table_get_cell_at		(ETable *table,
+						 gint x,
+						 gint y,
+						 gint *row_return,
+						 gint *col_return);
+void		e_table_get_mouse_over_cell	(ETable *table,
+						 gint *row,
+						 gint *col);
+void		e_table_get_cell_geometry	(ETable *table,
+						 gint row,
+						 gint col,
+						 gint *x_return,
+						 gint *y_return,
+						 gint *width_return,
+						 gint *height_return);
+
+/* Useful accessor functions. */
+ESelectionModel *e_table_get_selection_model	(ETable *table);
+
+/* Drag & drop stuff. */
+/* Target */
+void		e_table_drag_get_data		(ETable *table,
+						 gint row,
+						 gint col,
+						 GdkDragContext *context,
+						 GdkAtom target,
+						 guint32 time);
+void		e_table_drag_highlight	(ETable *table,
+						 gint row,
+						 gint col); /* col == -1 to highlight entire row. */
+void		e_table_drag_unhighlight	(ETable *table);
+void		e_table_drag_dest_set		(ETable *table,
+						 GtkDestDefaults flags,
+						 const GtkTargetEntry *targets,
+						 gint n_targets,
+						 GdkDragAction actions);
+void		e_table_drag_dest_set_proxy	(ETable *table,
+						 GdkWindow *proxy_window,
+						 GdkDragProtocol protocol,
+						 gboolean use_coordinates);
+
+/* There probably should be functions for setting the targets
+ * as a GtkTargetList
+ */
+void		e_table_drag_dest_unset		(GtkWidget *widget);
+
+/* Source side */
+void		e_table_drag_source_set		(ETable *table,
+						 GdkModifierType start_button_mask,
+						 const GtkTargetEntry *targets,
+						 gint n_targets,
+						 GdkDragAction actions);
+void		e_table_drag_source_unset	(ETable *table);
+
+/* There probably should be functions for setting the targets
+ * as a GtkTargetList
+ */
+GdkDragContext *e_table_drag_begin		(ETable *table,
+						 gint row,
+						 gint col,
+						 GtkTargetList *targets,
+						 GdkDragAction actions,
+						 gint button,
+						 GdkEvent *event);
+
+/* selection stuff */
+void		e_table_select_all		(ETable *table);
+void		e_table_invert_selection	(ETable *table);
+
+/* This function is only needed in single_selection_mode. */
+void		e_table_right_click_up		(ETable *table);
+
+void		e_table_commit_click_to_add	(ETable *table);
+
+void		e_table_freeze_state_change	(ETable *table);
+void		e_table_thaw_state_change	(ETable *table);
+
+G_END_DECLS
+
+#endif /* _E_TABLE_H_ */
+
diff --git a/e-util/e-text-event-processor-emacs-like.c b/e-util/e-text-event-processor-emacs-like.c
index 2a42ae9..c734cf8 100644
--- a/e-util/e-text-event-processor-emacs-like.c
+++ b/e-util/e-text-event-processor-emacs-like.c
@@ -29,7 +29,6 @@
 #include <gdk/gdkkeysyms.h>
 
 #include "e-text-event-processor-emacs-like.h"
-#include "e-util.h"
 
 static gint	e_text_event_processor_emacs_like_event
 					(ETextEventProcessor *tep,
diff --git a/e-util/e-text-event-processor-emacs-like.h b/e-util/e-text-event-processor-emacs-like.h
index 0b9c6c1..5a8890d 100644
--- a/e-util/e-text-event-processor-emacs-like.h
+++ b/e-util/e-text-event-processor-emacs-like.h
@@ -21,6 +21,10 @@
  *
  */
 
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
 #ifndef __E_TEXT_EVENT_PROCESSOR_EMACS_LIKE_H__
 #define __E_TEXT_EVENT_PROCESSOR_EMACS_LIKE_H__
 
diff --git a/e-util/e-text-event-processor-types.h b/e-util/e-text-event-processor-types.h
index d7d0bb3..cf7da4f 100644
--- a/e-util/e-text-event-processor-types.h
+++ b/e-util/e-text-event-processor-types.h
@@ -21,6 +21,10 @@
  *
  */
 
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
 #ifndef __E_TEXT_EVENT_PROCESSOR_TYPES_H__
 #define __E_TEXT_EVENT_PROCESSOR_TYPES_H__
 
diff --git a/e-util/e-text-event-processor.c b/e-util/e-text-event-processor.c
index a5da781..7988bd6 100644
--- a/e-util/e-text-event-processor.c
+++ b/e-util/e-text-event-processor.c
@@ -27,7 +27,6 @@
 #include <glib/gi18n.h>
 
 #include "e-text-event-processor.h"
-#include "e-util.h"
 
 static void e_text_event_processor_set_property (GObject *object,
 						 guint property_id,
diff --git a/e-util/e-text-event-processor.h b/e-util/e-text-event-processor.h
index cf14ebb..203e2de 100644
--- a/e-util/e-text-event-processor.h
+++ b/e-util/e-text-event-processor.h
@@ -20,6 +20,10 @@
  *
  */
 
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
 #ifndef __E_TEXT_EVENT_PROCESSOR_H__
 #define __E_TEXT_EVENT_PROCESSOR_H__
 
diff --git a/widgets/text/e-text-model-repos.c b/e-util/e-text-model-repos.c
similarity index 100%
rename from widgets/text/e-text-model-repos.c
rename to e-util/e-text-model-repos.c
diff --git a/e-util/e-text-model-repos.h b/e-util/e-text-model-repos.h
new file mode 100644
index 0000000..1450c02
--- /dev/null
+++ b/e-util/e-text-model-repos.h
@@ -0,0 +1,58 @@
+/*
+ * e-text-model-repos.h - Standard ETextModelReposFn definitions
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Jon Trowbridge <trow ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_TEXT_MODEL_REPOS_H
+#define E_TEXT_MODEL_REPOS_H
+
+#include "e-text-model.h"
+
+typedef struct {
+	ETextModel *model;
+	gint pos;  /* Position to move to.  Negative values count from the end buffer.
+		      (i.e. -1 puts cursor at the end, -2 one character from end, etc.) */
+} EReposAbsolute;
+
+gint e_repos_absolute (gint pos, gpointer data);
+
+typedef struct {
+	ETextModel *model;
+	gint pos;  /* Location of first inserted character. */
+	gint len;  /* Number of characters inserted. */
+} EReposInsertShift;
+
+gint e_repos_insert_shift (gint pos, gpointer data);
+
+typedef struct {
+	ETextModel *model;
+	gint pos;  /* Location of first deleted character. */
+	gint len;  /* Number of characters deleted. */
+} EReposDeleteShift;
+
+gint e_repos_delete_shift (gint pos, gpointer data);
+
+#endif
diff --git a/e-util/e-text-model.c b/e-util/e-text-model.c
new file mode 100644
index 0000000..ab6bff8
--- /dev/null
+++ b/e-util/e-text-model.c
@@ -0,0 +1,642 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#undef  PARANOID_DEBUGGING
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-text-model.h"
+
+#include <ctype.h>
+#include <string.h>
+
+#include <gtk/gtk.h>
+
+#include "e-marshal.h"
+#include "e-text-model-repos.h"
+
+#define E_TEXT_MODEL_GET_PRIVATE(obj) \
+	(G_TYPE_INSTANCE_GET_PRIVATE \
+	((obj), E_TYPE_TEXT_MODEL, ETextModelPrivate))
+
+enum {
+	E_TEXT_MODEL_CHANGED,
+	E_TEXT_MODEL_REPOSITION,
+	E_TEXT_MODEL_OBJECT_ACTIVATED,
+	E_TEXT_MODEL_CANCEL_COMPLETION,
+	E_TEXT_MODEL_LAST_SIGNAL
+};
+
+static guint signals[E_TEXT_MODEL_LAST_SIGNAL] = { 0 };
+
+struct _ETextModelPrivate {
+	GString *text;
+};
+
+static gint	e_text_model_real_validate_position
+						(ETextModel *, gint pos);
+static const gchar *
+		e_text_model_real_get_text	(ETextModel *model);
+static gint	e_text_model_real_get_text_length
+						(ETextModel *model);
+static void	e_text_model_real_set_text	(ETextModel *model,
+						 const gchar *text);
+static void	e_text_model_real_insert	(ETextModel *model,
+						 gint postion,
+						 const gchar *text);
+static void	e_text_model_real_insert_length	(ETextModel *model,
+						 gint postion,
+						 const gchar *text,
+						 gint length);
+static void	e_text_model_real_delete	(ETextModel *model,
+						 gint postion,
+						 gint length);
+
+G_DEFINE_TYPE (ETextModel, e_text_model, G_TYPE_OBJECT)
+
+static void
+e_text_model_finalize (GObject *object)
+{
+	ETextModelPrivate *priv;
+
+	priv = E_TEXT_MODEL_GET_PRIVATE (object);
+
+	g_string_free (priv->text, TRUE);
+
+	/* Chain up to parent's finalize() method. */
+	G_OBJECT_CLASS (e_text_model_parent_class)->finalize (object);
+}
+
+static void
+e_text_model_class_init (ETextModelClass *class)
+{
+	GObjectClass *object_class;
+
+	g_type_class_add_private (class, sizeof (ETextModelPrivate));
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->finalize = e_text_model_finalize;
+
+	signals[E_TEXT_MODEL_CHANGED] = g_signal_new (
+		"changed",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ETextModelClass, changed),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__VOID,
+		G_TYPE_NONE, 0);
+
+	signals[E_TEXT_MODEL_REPOSITION] = g_signal_new (
+		"reposition",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ETextModelClass, reposition),
+		NULL, NULL,
+		e_marshal_NONE__POINTER_POINTER,
+		G_TYPE_NONE, 2,
+		G_TYPE_POINTER,
+		G_TYPE_POINTER);
+
+	signals[E_TEXT_MODEL_OBJECT_ACTIVATED] = g_signal_new (
+		"object_activated",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ETextModelClass, object_activated),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__INT,
+		G_TYPE_NONE, 1,
+		G_TYPE_INT);
+
+	signals[E_TEXT_MODEL_CANCEL_COMPLETION] = g_signal_new (
+		"cancel_completion",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ETextModelClass, cancel_completion),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__VOID,
+		G_TYPE_NONE, 0);
+
+	/* No default signal handlers. */
+	class->changed          = NULL;
+	class->reposition       = NULL;
+	class->object_activated = NULL;
+
+	class->validate_pos  = e_text_model_real_validate_position;
+
+	class->get_text      = e_text_model_real_get_text;
+	class->get_text_len  = e_text_model_real_get_text_length;
+	class->set_text      = e_text_model_real_set_text;
+	class->insert        = e_text_model_real_insert;
+	class->insert_length = e_text_model_real_insert_length;
+	class->delete        = e_text_model_real_delete;
+
+	/* We explicitly don't define default handlers for these. */
+	class->objectify        = NULL;
+	class->obj_count        = NULL;
+	class->get_nth_obj      = NULL;
+}
+
+static void
+e_text_model_init (ETextModel *model)
+{
+	model->priv = E_TEXT_MODEL_GET_PRIVATE (model);
+	model->priv->text = g_string_new ("");
+}
+
+static gint
+e_text_model_real_validate_position (ETextModel *model,
+                                     gint pos)
+{
+	gint len = e_text_model_get_text_length (model);
+
+	if (pos < 0)
+		pos = 0;
+	else if (pos > len)
+		pos = len;
+
+	return pos;
+}
+
+static const gchar *
+e_text_model_real_get_text (ETextModel *model)
+{
+	if (model->priv->text)
+		return model->priv->text->str;
+	else
+		return "";
+}
+
+static gint
+e_text_model_real_get_text_length (ETextModel *model)
+{
+	return g_utf8_strlen (model->priv->text->str, -1);
+}
+
+static void
+e_text_model_real_set_text (ETextModel *model,
+                            const gchar *text)
+{
+	EReposAbsolute repos;
+	gboolean changed = FALSE;
+
+	if (text == NULL) {
+		changed = (*model->priv->text->str != '\0');
+
+		g_string_set_size (model->priv->text, 0);
+
+	} else if (*model->priv->text->str == '\0' ||
+		strcmp (model->priv->text->str, text)) {
+
+		g_string_assign (model->priv->text, text);
+
+		changed = TRUE;
+	}
+
+	if (changed) {
+		e_text_model_changed (model);
+		repos.model = model;
+		repos.pos = -1;
+		e_text_model_reposition (model, e_repos_absolute, &repos);
+	}
+}
+
+static void
+e_text_model_real_insert (ETextModel *model,
+                          gint position,
+                          const gchar *text)
+{
+	e_text_model_insert_length (model, position, text, strlen (text));
+}
+
+static void
+e_text_model_real_insert_length (ETextModel *model,
+                                 gint position,
+                                 const gchar *text,
+                                 gint length)
+{
+	EReposInsertShift repos;
+	gint model_len = e_text_model_real_get_text_length (model);
+	gchar *offs;
+	const gchar *p;
+	gint byte_length, l;
+
+	if (position > model_len)
+		return;
+
+	offs = g_utf8_offset_to_pointer (model->priv->text->str, position);
+
+	for (p = text, l = 0;
+	     l < length;
+	     p = g_utf8_next_char (p), l++);
+
+	byte_length = p - text;
+
+	g_string_insert_len (
+		model->priv->text,
+		offs - model->priv->text->str,
+		text, byte_length);
+
+	e_text_model_changed (model);
+
+	repos.model = model;
+	repos.pos = position;
+	repos.len = length;
+
+	e_text_model_reposition (model, e_repos_insert_shift, &repos);
+}
+
+static void
+e_text_model_real_delete (ETextModel *model,
+                          gint position,
+                          gint length)
+{
+	EReposDeleteShift repos;
+	gint byte_position, byte_length;
+	gchar *offs, *p;
+	gint l;
+
+	offs = g_utf8_offset_to_pointer (model->priv->text->str, position);
+	byte_position = offs - model->priv->text->str;
+
+	for (p = offs, l = 0;
+	     l < length;
+	     p = g_utf8_next_char (p), l++);
+
+	byte_length = p - offs;
+
+	g_string_erase (
+		model->priv->text,
+		byte_position, byte_length);
+
+	e_text_model_changed (model);
+
+	repos.model = model;
+	repos.pos   = position;
+	repos.len   = length;
+
+	e_text_model_reposition (model, e_repos_delete_shift, &repos);
+}
+
+void
+e_text_model_changed (ETextModel *model)
+{
+	ETextModelClass *class;
+
+	g_return_if_fail (E_IS_TEXT_MODEL (model));
+
+	class = E_TEXT_MODEL_GET_CLASS (model);
+
+	/*
+	  Objectify before emitting any signal.
+	  While this method could, in theory, do pretty much anything, it is meant
+	  for scanning objects and converting substrings into embedded objects.
+	*/
+	if (class->objectify != NULL)
+		class->objectify (model);
+
+	g_signal_emit (model, signals[E_TEXT_MODEL_CHANGED], 0);
+}
+
+void
+e_text_model_cancel_completion (ETextModel *model)
+{
+	g_return_if_fail (E_IS_TEXT_MODEL (model));
+
+	g_signal_emit (model, signals[E_TEXT_MODEL_CANCEL_COMPLETION], 0);
+}
+
+void
+e_text_model_reposition (ETextModel *model,
+                         ETextModelReposFn fn,
+                         gpointer repos_data)
+{
+	g_return_if_fail (E_IS_TEXT_MODEL (model));
+	g_return_if_fail (fn != NULL);
+
+	g_signal_emit (
+		model, signals[E_TEXT_MODEL_REPOSITION], 0, fn, repos_data);
+}
+
+gint
+e_text_model_validate_position (ETextModel *model,
+                                gint pos)
+{
+	ETextModelClass *class;
+
+	g_return_val_if_fail (E_IS_TEXT_MODEL (model), 0);
+
+	class = E_TEXT_MODEL_GET_CLASS (model);
+
+	if (class->validate_pos != NULL)
+		pos = class->validate_pos (model, pos);
+
+	return pos;
+}
+
+const gchar *
+e_text_model_get_text (ETextModel *model)
+{
+	ETextModelClass *class;
+
+	g_return_val_if_fail (E_IS_TEXT_MODEL (model), NULL);
+
+	class = E_TEXT_MODEL_GET_CLASS (model);
+
+	if (class->get_text == NULL)
+		return "";
+
+	return class->get_text (model);
+}
+
+gint
+e_text_model_get_text_length (ETextModel *model)
+{
+	ETextModelClass *class;
+
+	g_return_val_if_fail (E_IS_TEXT_MODEL (model), 0);
+
+	class = E_TEXT_MODEL_GET_CLASS (model);
+
+	if (class->get_text_len (model)) {
+
+		gint len = class->get_text_len (model);
+
+#ifdef PARANOID_DEBUGGING
+		const gchar *str = e_text_model_get_text (model);
+		gint len2 = str ? g_utf8_strlen (str, -1) : 0;
+		if (len != len)
+			g_error ("\"%s\" length reported as %d, not %d.", str, len, len2);
+#endif
+
+		return len;
+
+	} else {
+		/* Calculate length the old-fashioned way... */
+		const gchar *str = e_text_model_get_text (model);
+		return str ? g_utf8_strlen (str, -1) : 0;
+	}
+}
+
+void
+e_text_model_set_text (ETextModel *model,
+                       const gchar *text)
+{
+	ETextModelClass *class;
+
+	g_return_if_fail (E_IS_TEXT_MODEL (model));
+
+	class = E_TEXT_MODEL_GET_CLASS (model);
+
+	if (class->set_text != NULL)
+		class->set_text (model, text);
+}
+
+void
+e_text_model_insert (ETextModel *model,
+                     gint position,
+                     const gchar *text)
+{
+	ETextModelClass *class;
+
+	g_return_if_fail (E_IS_TEXT_MODEL (model));
+
+	if (text == NULL)
+		return;
+
+	class = E_TEXT_MODEL_GET_CLASS (model);
+
+	if (class->insert != NULL)
+		class->insert (model, position, text);
+}
+
+void
+e_text_model_insert_length (ETextModel *model,
+                            gint position,
+                            const gchar *text,
+                            gint length)
+{
+	ETextModelClass *class;
+
+	g_return_if_fail (E_IS_TEXT_MODEL (model));
+	g_return_if_fail (length >= 0);
+
+	if (text == NULL || length == 0)
+		return;
+
+	class = E_TEXT_MODEL_GET_CLASS (model);
+
+	if (class->insert_length != NULL)
+		class->insert_length (model, position, text, length);
+}
+
+void
+e_text_model_prepend (ETextModel *model,
+                      const gchar *text)
+{
+	g_return_if_fail (E_IS_TEXT_MODEL (model));
+
+	if (text == NULL)
+		return;
+
+	e_text_model_insert (model, 0, text);
+}
+
+void
+e_text_model_append (ETextModel *model,
+                     const gchar *text)
+{
+	g_return_if_fail (E_IS_TEXT_MODEL (model));
+
+	if (text == NULL)
+		return;
+
+	e_text_model_insert (model, e_text_model_get_text_length (model), text);
+}
+
+void
+e_text_model_delete (ETextModel *model,
+                     gint position,
+                     gint length)
+{
+	ETextModelClass *class;
+	gint txt_len;
+
+	g_return_if_fail (E_IS_TEXT_MODEL (model));
+	g_return_if_fail (length >= 0);
+
+	txt_len = e_text_model_get_text_length (model);
+	if (position + length > txt_len)
+		length = txt_len - position;
+
+	if (length <= 0)
+		return;
+
+	class = E_TEXT_MODEL_GET_CLASS (model);
+
+	if (class->delete != NULL)
+		class->delete (model, position, length);
+}
+
+gint
+e_text_model_object_count (ETextModel *model)
+{
+	ETextModelClass *class;
+
+	g_return_val_if_fail (E_IS_TEXT_MODEL (model), 0);
+
+	class = E_TEXT_MODEL_GET_CLASS (model);
+
+	if (class->obj_count == NULL)
+		return 0;
+
+	return class->obj_count (model);
+}
+
+const gchar *
+e_text_model_get_nth_object (ETextModel *model,
+                             gint n,
+                             gint *len)
+{
+	ETextModelClass *class;
+
+	g_return_val_if_fail (E_IS_TEXT_MODEL (model), NULL);
+
+	if (n < 0 || n >= e_text_model_object_count (model))
+		return NULL;
+
+	class = E_TEXT_MODEL_GET_CLASS (model);
+
+	if (class->get_nth_obj == NULL)
+		return NULL;
+
+	return class->get_nth_obj (model, n, len);
+}
+
+gchar *
+e_text_model_strdup_nth_object (ETextModel *model,
+                                gint n)
+{
+	const gchar *obj;
+	gint len = 0;
+
+	g_return_val_if_fail (E_IS_TEXT_MODEL (model), NULL);
+
+	obj = e_text_model_get_nth_object (model, n, &len);
+
+	if (obj) {
+		gint byte_len;
+		byte_len = g_utf8_offset_to_pointer (obj, len) - obj;
+		return g_strndup (obj, byte_len);
+	}
+	else {
+		return NULL;
+	}
+}
+
+void
+e_text_model_get_nth_object_bounds (ETextModel *model,
+                                    gint n,
+                                    gint *start,
+                                    gint *end)
+{
+	const gchar *txt = NULL, *obj = NULL;
+	gint len = 0;
+
+	g_return_if_fail (E_IS_TEXT_MODEL (model));
+
+	txt = e_text_model_get_text (model);
+	obj = e_text_model_get_nth_object (model, n, &len);
+
+	g_return_if_fail (obj != NULL);
+
+	if (start)
+		*start = g_utf8_pointer_to_offset (txt, obj);
+	if (end)
+		*end = (start ? *start : 0) + len;
+}
+
+gint
+e_text_model_get_object_at_offset (ETextModel *model,
+                                   gint offset)
+{
+	ETextModelClass *class;
+
+	g_return_val_if_fail (E_IS_TEXT_MODEL (model), -1);
+
+	if (offset < 0 || offset >= e_text_model_get_text_length (model))
+		return -1;
+
+	class = E_TEXT_MODEL_GET_CLASS (model);
+
+	/* If an optimized version has been provided, we use it. */
+	if (class->obj_at_offset != NULL) {
+		return class->obj_at_offset (model, offset);
+
+	} else {
+		/* If not, we fake it.*/
+
+		gint i, N, pos0, pos1;
+
+		N = e_text_model_object_count (model);
+
+		for (i = 0; i < N; ++i) {
+			e_text_model_get_nth_object_bounds (model, i, &pos0, &pos1);
+			if (pos0 <= offset && offset < pos1)
+				return i;
+		}
+
+	}
+
+	return -1;
+}
+
+gint
+e_text_model_get_object_at_pointer (ETextModel *model,
+                                    const gchar *s)
+{
+	g_return_val_if_fail (E_IS_TEXT_MODEL (model), -1);
+	g_return_val_if_fail (s != NULL, -1);
+
+	return e_text_model_get_object_at_offset (
+		model, s - e_text_model_get_text (model));
+}
+
+void
+e_text_model_activate_nth_object (ETextModel *model,
+                                  gint n)
+{
+	g_return_if_fail (model != NULL);
+	g_return_if_fail (E_IS_TEXT_MODEL (model));
+	g_return_if_fail (n >= 0);
+	g_return_if_fail (n < e_text_model_object_count (model));
+
+	g_signal_emit (model, signals[E_TEXT_MODEL_OBJECT_ACTIVATED], 0, n);
+}
+
+ETextModel *
+e_text_model_new (void)
+{
+	ETextModel *model = g_object_new (E_TYPE_TEXT_MODEL, NULL);
+	return model;
+}
diff --git a/e-util/e-text-model.h b/e-util/e-text-model.h
new file mode 100644
index 0000000..3426c18
--- /dev/null
+++ b/e-util/e-text-model.h
@@ -0,0 +1,112 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_TEXT_MODEL_H
+#define E_TEXT_MODEL_H
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define E_TYPE_TEXT_MODEL            (e_text_model_get_type ())
+#define E_TEXT_MODEL(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), E_TYPE_TEXT_MODEL, ETextModel))
+#define E_TEXT_MODEL_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), E_TYPE_TEXT_MODEL, ETextModelClass))
+#define E_IS_TEXT_MODEL(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), E_TYPE_TEXT_MODEL))
+#define E_IS_TEXT_MODEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), E_TYPE_TEXT_MODEL))
+#define E_TEXT_MODEL_GET_CLASS(o)    (G_TYPE_INSTANCE_GET_CLASS ((o), E_TYPE_TEXT_MODEL_TYPE, ETextModelClass))
+
+typedef struct _ETextModel ETextModel;
+typedef struct _ETextModelClass ETextModelClass;
+typedef struct _ETextModelPrivate ETextModelPrivate;
+
+typedef gint (*ETextModelReposFn) (gint, gpointer);
+
+struct _ETextModel {
+	GObject item;
+
+	ETextModelPrivate *priv;
+};
+
+struct _ETextModelClass {
+	GObjectClass parent_class;
+
+	/* Signal */
+	void  (* changed)           (ETextModel *model);
+	void  (* reposition)        (ETextModel *model, ETextModelReposFn fn, gpointer repos_fn_data);
+	void  (* object_activated)  (ETextModel *model, gint obj_num);
+	void  (* cancel_completion) (ETextModel *model);
+
+	/* Virtual methods */
+
+	gint  (* validate_pos) (ETextModel *model, gint pos);
+
+	const gchar *(* get_text)      (ETextModel *model);
+	gint        (* get_text_len)  (ETextModel *model);
+	void        (* set_text)      (ETextModel *model, const gchar *text);
+	void        (* insert)        (ETextModel *model, gint position, const gchar *text);
+	void        (* insert_length) (ETextModel *model, gint position, const gchar *text, gint length);
+	void        (* delete)        (ETextModel *model, gint position, gint length);
+
+	void         (* objectify)          (ETextModel *model);
+	gint         (* obj_count)          (ETextModel *model);
+	const gchar *(* get_nth_obj)        (ETextModel *model, gint n, gint *len);
+	gint         (* obj_at_offset)      (ETextModel *model, gint offset);
+};
+
+GType       e_text_model_get_type (void);
+
+ETextModel *e_text_model_new (void);
+
+void        e_text_model_changed (ETextModel *model);
+void        e_text_model_cancel_completion (ETextModel *model);
+
+void        e_text_model_reposition        (ETextModel *model, ETextModelReposFn fn, gpointer repos_data);
+gint        e_text_model_validate_position (ETextModel *model, gint pos);
+
+/* Functions for manipulating the underlying text. */
+
+const gchar *e_text_model_get_text        (ETextModel *model);
+gint         e_text_model_get_text_length (ETextModel *model);
+void         e_text_model_set_text        (ETextModel *model, const gchar *text);
+void         e_text_model_insert          (ETextModel *model, gint position, const gchar *text);
+void         e_text_model_insert_length   (ETextModel *model, gint position, const gchar *text, gint length);
+void         e_text_model_prepend         (ETextModel *model, const gchar *text);
+void         e_text_model_append          (ETextModel *model, const gchar *text);
+void         e_text_model_delete          (ETextModel *model, gint position, gint length);
+
+/* Functions for accessing embedded objects. */
+
+gint         e_text_model_object_count          (ETextModel *model);
+const gchar *e_text_model_get_nth_object        (ETextModel *model, gint n, gint *len);
+gchar       *e_text_model_strdup_nth_object     (ETextModel *model, gint n);
+void         e_text_model_get_nth_object_bounds (ETextModel *model, gint n, gint *start_pos, gint *end_pos);
+gint         e_text_model_get_object_at_offset  (ETextModel *model, gint offset);
+gint         e_text_model_get_object_at_pointer (ETextModel *model, const gchar *c);
+void         e_text_model_activate_nth_object   (ETextModel *model, gint n);
+
+G_END_DECLS
+
+#endif
diff --git a/e-util/e-text.c b/e-util/e-text.c
new file mode 100644
index 0000000..d23deca
--- /dev/null
+++ b/e-util/e-text.c
@@ -0,0 +1,3405 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * e-text.c - Text item for evolution.
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * Authors:
+ *   Chris Lahey <clahey ximian com>
+ *   Jon Trowbridge <trow ximian com>
+ *
+ * A majority of code taken from:
+ *
+ * Text item type for GnomeCanvas widget
+ *
+ * GnomeCanvas is basically a port of the Tk toolkit's most excellent
+ * canvas widget.  Tk is copyrighted by the Regents of the University
+ * of California, Sun Microsystems, and other parties.
+ *
+ * Copyright (C) 1998 The Free Software Foundation
+ *
+ * Author: Federico Mena <federico nuclecu unam mx>
+ *
+ * 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., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-text.h"
+
+#include <math.h>
+#include <ctype.h>
+#include <string.h>
+
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+
+#include "e-canvas-utils.h"
+#include "e-canvas.h"
+#include "e-marshal.h"
+#include "e-text-event-processor-emacs-like.h"
+#include "e-unicode.h"
+#include "gal-a11y-e-text.h"
+
+G_DEFINE_TYPE (EText, e_text, GNOME_TYPE_CANVAS_ITEM)
+
+enum {
+	E_TEXT_CHANGED,
+	E_TEXT_ACTIVATE,
+	E_TEXT_KEYPRESS,
+	E_TEXT_POPULATE_POPUP,
+	E_TEXT_LAST_SIGNAL
+};
+
+static GQuark e_text_signals[E_TEXT_LAST_SIGNAL] = { 0 };
+
+/* Object argument IDs */
+enum {
+	PROP_0,
+	PROP_MODEL,
+	PROP_EVENT_PROCESSOR,
+	PROP_TEXT,
+	PROP_BOLD,
+	PROP_STRIKEOUT,
+	PROP_ANCHOR,
+	PROP_JUSTIFICATION,
+	PROP_CLIP_WIDTH,
+	PROP_CLIP_HEIGHT,
+	PROP_CLIP,
+	PROP_FILL_CLIP_RECTANGLE,
+	PROP_X_OFFSET,
+	PROP_Y_OFFSET,
+	PROP_FILL_COLOR,
+	PROP_FILL_COLOR_GDK,
+	PROP_FILL_COLOR_RGBA,
+	PROP_TEXT_WIDTH,
+	PROP_TEXT_HEIGHT,
+	PROP_EDITABLE,
+	PROP_USE_ELLIPSIS,
+	PROP_ELLIPSIS,
+	PROP_LINE_WRAP,
+	PROP_BREAK_CHARACTERS,
+	PROP_MAX_LINES,
+	PROP_WIDTH,
+	PROP_HEIGHT,
+	PROP_ALLOW_NEWLINES,
+	PROP_CURSOR_POS,
+	PROP_IM_CONTEXT,
+	PROP_HANDLE_POPUP
+};
+
+static void	e_text_command			(ETextEventProcessor *tep,
+						 ETextEventProcessorCommand *command,
+						 gpointer data);
+
+static void	e_text_text_model_changed	(ETextModel *model,
+						 EText *text);
+static void	e_text_text_model_reposition	(ETextModel *model,
+						 ETextModelReposFn fn,
+						 gpointer repos_data,
+						 gpointer data);
+
+static void _get_tep (EText *text);
+
+static void calc_height (EText *text);
+
+static gboolean show_pango_rectangle (EText *text, PangoRectangle rect);
+
+static void	e_text_do_popup			(EText *text,
+						 GdkEvent *event_button,
+						 gint position);
+
+static void e_text_update_primary_selection (EText *text);
+static void e_text_paste (EText *text, GdkAtom selection);
+static void e_text_insert (EText *text, const gchar *string);
+static void e_text_reset_im_context (EText *text);
+
+static void reset_layout_attrs (EText *text);
+
+/* IM Context Callbacks */
+static void     e_text_commit_cb               (GtkIMContext *context,
+						const gchar  *str,
+						EText        *text);
+static void     e_text_preedit_changed_cb      (GtkIMContext *context,
+						EText        *text);
+static gboolean e_text_retrieve_surrounding_cb (GtkIMContext *context,
+						EText        *text);
+static gboolean e_text_delete_surrounding_cb   (GtkIMContext *context,
+						gint          offset,
+						gint          n_chars,
+						EText        *text);
+
+static GdkAtom clipboard_atom = GDK_NONE;
+
+static void
+disconnect_im_context (EText *text)
+{
+	if (!text || !text->im_context)
+		return;
+
+	g_signal_handlers_disconnect_matched (
+		text->im_context, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, text);
+	text->im_context_signals_registered = FALSE;
+}
+
+/* Dispose handler for the text item */
+static void
+e_text_dispose (GObject *object)
+{
+	EText *text;
+
+	g_return_if_fail (object != NULL);
+	g_return_if_fail (E_IS_TEXT (object));
+
+	text = E_TEXT (object);
+
+	if (text->model_changed_signal_id)
+		g_signal_handler_disconnect (
+			text->model,
+			text->model_changed_signal_id);
+	text->model_changed_signal_id = 0;
+
+	if (text->model_repos_signal_id)
+		g_signal_handler_disconnect (
+			text->model,
+			text->model_repos_signal_id);
+	text->model_repos_signal_id = 0;
+
+	if (text->model)
+		g_object_unref (text->model);
+	text->model = NULL;
+
+	if (text->tep_command_id)
+		g_signal_handler_disconnect (
+			text->tep,
+			text->tep_command_id);
+	text->tep_command_id = 0;
+
+	if (text->tep)
+		g_object_unref (text->tep);
+	text->tep = NULL;
+
+	g_free (text->revert);
+	text->revert = NULL;
+
+	if (text->timeout_id) {
+		g_source_remove (text->timeout_id);
+		text->timeout_id = 0;
+	}
+
+	if (text->timer) {
+		g_timer_stop (text->timer);
+		g_timer_destroy (text->timer);
+		text->timer = NULL;
+	}
+
+	if (text->dbl_timeout) {
+		g_source_remove (text->dbl_timeout);
+		text->dbl_timeout = 0;
+	}
+
+	if (text->tpl_timeout) {
+		g_source_remove (text->tpl_timeout);
+		text->tpl_timeout = 0;
+	}
+
+	if (text->layout) {
+		g_object_unref (text->layout);
+		text->layout = NULL;
+	}
+
+	if (text->im_context) {
+		disconnect_im_context (text);
+		g_object_unref (text->im_context);
+		text->im_context = NULL;
+	}
+
+	if (text->font_desc) {
+		pango_font_description_free (text->font_desc);
+		text->font_desc = NULL;
+	}
+
+	/* Chain up to parent's dispose() method. */
+	G_OBJECT_CLASS (e_text_parent_class)->dispose (object);
+}
+
+static void
+insert_preedit_text (EText *text)
+{
+	PangoAttrList *attrs = NULL;
+	PangoAttrList *preedit_attrs = NULL;
+	gchar *preedit_string = NULL;
+	GString *tmp_string = g_string_new (NULL);
+	gint length = 0, cpos = 0;
+	gboolean new_attrs = FALSE;
+
+	if (text->layout == NULL || !GTK_IS_IM_CONTEXT (text->im_context))
+		return;
+
+	text->text = e_text_model_get_text (text->model);
+	length = strlen (text->text);
+
+	g_string_prepend_len (tmp_string, text->text,length);
+
+	/* we came into this function only when text->preedit_len was not 0
+	 * so we can safely fetch the preedit string */
+	gtk_im_context_get_preedit_string (
+		text->im_context, &preedit_string, &preedit_attrs, NULL);
+
+	if (preedit_string && g_utf8_validate (preedit_string, -1, NULL)) {
+
+		text->preedit_len = strlen (preedit_string);
+
+		cpos = g_utf8_offset_to_pointer (
+			text->text, text->selection_start) - text->text;
+
+		g_string_insert (tmp_string, cpos, preedit_string);
+
+		reset_layout_attrs (text);
+
+		attrs = pango_layout_get_attributes (text->layout);
+		if (!attrs) {
+			attrs = pango_attr_list_new ();
+			new_attrs = TRUE;
+		}
+
+		pango_layout_set_text (text->layout, tmp_string->str, tmp_string->len);
+
+		pango_attr_list_splice (attrs, preedit_attrs, cpos, text->preedit_len);
+
+		if (new_attrs) {
+			pango_layout_set_attributes (text->layout, attrs);
+			pango_attr_list_unref (attrs);
+		}
+	} else
+		text->preedit_len = 0;
+
+	if (preedit_string)
+		g_free (preedit_string);
+	if (preedit_attrs)
+		pango_attr_list_unref (preedit_attrs);
+	if (tmp_string)
+		g_string_free (tmp_string, TRUE);
+}
+
+static void
+reset_layout_attrs (EText *text)
+{
+	PangoAttrList *attrs = NULL;
+	gint object_count;
+
+	if (text->layout == NULL)
+		return;
+
+	object_count = e_text_model_object_count (text->model);
+
+	if (text->bold || text->strikeout || object_count > 0) {
+		gint length = 0;
+		gint i;
+
+		attrs = pango_attr_list_new ();
+
+		for (i = 0; i < object_count; i++) {
+			gint start_pos, end_pos;
+			PangoAttribute *attr;
+
+			attr = pango_attr_underline_new (PANGO_UNDERLINE_SINGLE);
+
+			e_text_model_get_nth_object_bounds (
+				text->model, i, &start_pos, &end_pos);
+
+			attr->start_index = g_utf8_offset_to_pointer (
+				text->text, start_pos) - text->text;
+			attr->end_index = g_utf8_offset_to_pointer (
+				text->text, end_pos) - text->text;
+
+			pango_attr_list_insert (attrs, attr);
+		}
+
+		if (text->bold || text->strikeout)
+			length = strlen (text->text);
+
+		if (text->bold) {
+			PangoAttribute *attr = pango_attr_weight_new (PANGO_WEIGHT_BOLD);
+			attr->start_index = 0;
+			attr->end_index = length;
+
+			pango_attr_list_insert_before (attrs, attr);
+		}
+		if (text->strikeout) {
+			PangoAttribute *attr = pango_attr_strikethrough_new (TRUE);
+			attr->start_index = 0;
+			attr->end_index = length;
+
+			pango_attr_list_insert_before (attrs, attr);
+		}
+	}
+
+	pango_layout_set_attributes (text->layout, attrs);
+
+	if (attrs)
+		pango_attr_list_unref (attrs);
+
+	calc_height (text);
+}
+
+static void
+create_layout (EText *text)
+{
+	GnomeCanvasItem *item = GNOME_CANVAS_ITEM (text);
+
+	if (text->layout)
+		return;
+
+	text->layout = gtk_widget_create_pango_layout (
+		GTK_WIDGET (item->canvas), text->text);
+	if (text->line_wrap)
+		pango_layout_set_width (
+			text->layout, text->clip_width < 0
+			? -1 : text->clip_width * PANGO_SCALE);
+	reset_layout_attrs (text);
+}
+
+static void
+reset_layout (EText *text)
+{
+	GnomeCanvasItem *item = GNOME_CANVAS_ITEM (text);
+
+	if (text->layout == NULL) {
+		create_layout (text);
+	}
+	else {
+		GtkStyle *style;
+
+		style = gtk_widget_get_style (GTK_WIDGET (item->canvas));
+
+		if (text->font_desc) {
+			pango_font_description_free (text->font_desc);
+		}
+		text->font_desc = pango_font_description_new ();
+		if (!pango_font_description_get_size_is_absolute (style->font_desc))
+			pango_font_description_set_size (
+				text->font_desc,
+				pango_font_description_get_size (style->font_desc));
+		else
+			pango_font_description_set_absolute_size (
+				text->font_desc,
+				pango_font_description_get_size (style->font_desc));
+		pango_font_description_set_family (
+			text->font_desc,
+			pango_font_description_get_family (style->font_desc));
+		pango_layout_set_font_description (text->layout, text->font_desc);
+
+		pango_layout_set_text (text->layout, text->text, -1);
+		reset_layout_attrs (text);
+	}
+
+	if (!text->button_down) {
+		PangoRectangle strong_pos, weak_pos;
+		gchar *offs = g_utf8_offset_to_pointer (text->text, text->selection_start);
+
+		pango_layout_get_cursor_pos (
+			text->layout, offs - text->text,
+			&strong_pos, &weak_pos);
+
+		if (strong_pos.x != weak_pos.x ||
+		    strong_pos.y != weak_pos.y ||
+		    strong_pos.width != weak_pos.width ||
+		    strong_pos.height != weak_pos.height)
+			show_pango_rectangle (text, weak_pos);
+
+		show_pango_rectangle (text, strong_pos);
+	}
+}
+
+static void
+e_text_text_model_changed (ETextModel *model,
+                           EText *text)
+{
+	gint model_len = e_text_model_get_text_length (model);
+	text->text = e_text_model_get_text (model);
+
+	/* Make sure our selection doesn't extend past the bounds of our text. */
+	text->selection_start = CLAMP (text->selection_start, 0, model_len);
+	text->selection_end   = CLAMP (text->selection_end,   0, model_len);
+
+	text->needs_reset_layout = 1;
+	text->needs_split_into_lines = 1;
+	text->needs_redraw = 1;
+	e_canvas_item_request_reflow (GNOME_CANVAS_ITEM (text));
+	gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (text));
+
+	g_signal_emit (text, e_text_signals[E_TEXT_CHANGED], 0);
+}
+
+static void
+e_text_text_model_reposition (ETextModel *model,
+                              ETextModelReposFn fn,
+                              gpointer repos_data,
+                              gpointer user_data)
+{
+	EText *text = E_TEXT (user_data);
+	gint model_len = e_text_model_get_text_length (model);
+
+	text->selection_start = fn (text->selection_start, repos_data);
+	text->selection_end   = fn (text->selection_end,   repos_data);
+
+	/* Our repos function should make sure we don't overrun the buffer, but it never
+	 * hurts to be paranoid. */
+	text->selection_start = CLAMP (text->selection_start, 0, model_len);
+	text->selection_end   = CLAMP (text->selection_end,   0, model_len);
+
+	if (text->selection_start > text->selection_end) {
+		gint tmp = text->selection_start;
+		text->selection_start = text->selection_end;
+		text->selection_end = tmp;
+	}
+}
+
+static void
+get_bounds (EText *text,
+            gdouble *px1,
+            gdouble *py1,
+            gdouble *px2,
+            gdouble *py2)
+{
+	GnomeCanvasItem *item;
+	gdouble wx, wy, clip_width, clip_height;
+
+	item = GNOME_CANVAS_ITEM (text);
+
+	/* Get canvas pixel coordinates for text position */
+
+	wx = 0;
+	wy = 0;
+	gnome_canvas_item_i2w (item, &wx, &wy);
+	gnome_canvas_w2c (item->canvas, wx, wy, &text->cx, &text->cy);
+	gnome_canvas_w2c (item->canvas, wx, wy, &text->clip_cx, &text->clip_cy);
+
+	if (text->clip_width < 0)
+		clip_width = text->width;
+	else
+		clip_width = text->clip_width;
+
+	if (text->clip_height < 0)
+		clip_height = text->height;
+	else
+		clip_height = text->clip_height;
+
+	/* Get canvas pixel coordinates for clip rectangle position */
+	text->clip_cwidth = clip_width;
+	text->clip_cheight = clip_height;
+
+	text->text_cx = text->cx;
+	text->text_cy = text->cy;
+
+	/* Bounds */
+
+	if (text->clip) {
+		*px1 = text->clip_cx;
+		*py1 = text->clip_cy;
+		*px2 = text->clip_cx + text->clip_cwidth;
+		*py2 = text->clip_cy + text->clip_cheight;
+	} else {
+		*px1 = text->cx;
+		*py1 = text->cy;
+		*px2 = text->cx + text->width;
+		*py2 = text->cy + text->height;
+	}
+}
+
+static void
+calc_height (EText *text)
+{
+	GnomeCanvasItem *item;
+	gint old_height;
+	gint old_width;
+	gint width = 0;
+	gint height = 0;
+
+	item = GNOME_CANVAS_ITEM (text);
+
+	/* Calculate text dimensions */
+
+	old_height = text->height;
+	old_width = text->width;
+
+	if (text->layout)
+		pango_layout_get_pixel_size (text->layout, &width, &height);
+
+	text->height = height;
+	text->width = width;
+
+	if (old_height != text->height || old_width != text->width)
+		e_canvas_item_request_parent_reflow (item);
+}
+
+static void
+calc_ellipsis (EText *text)
+{
+/* FIXME: a pango layout per calc_ellipsis sucks */
+	gint width;
+	PangoLayout *layout = gtk_widget_create_pango_layout (
+		GTK_WIDGET (GNOME_CANVAS_ITEM (text)->canvas),
+		text->ellipsis ? text->ellipsis : "...");
+	pango_layout_get_size (layout, &width, NULL);
+
+	text->ellipsis_width = width;
+
+	g_object_unref (layout);
+}
+
+static void
+split_into_lines (EText *text)
+{
+	text->num_lines = pango_layout_get_line_count (text->layout);
+}
+
+/* Set_arg handler for the text item */
+static void
+e_text_set_property (GObject *object,
+                    guint property_id,
+                    const GValue *value,
+                    GParamSpec *pspec)
+{
+	GnomeCanvasItem *item;
+	EText *text;
+	GdkColor color = { 0, 0, 0, 0, };
+	GdkColor *pcolor;
+
+	gboolean needs_update = 0;
+	gboolean needs_reflow = 0;
+
+	item = GNOME_CANVAS_ITEM (object);
+	text = E_TEXT (object);
+
+	switch (property_id) {
+	case PROP_MODEL:
+
+		if (text->model_changed_signal_id)
+			g_signal_handler_disconnect (
+				text->model,
+				text->model_changed_signal_id);
+
+		if (text->model_repos_signal_id)
+			g_signal_handler_disconnect (
+				text->model,
+				text->model_repos_signal_id);
+
+		g_object_unref (text->model);
+		text->model = E_TEXT_MODEL (g_value_get_object (value));
+		g_object_ref (text->model);
+
+		text->model_changed_signal_id = g_signal_connect (
+			text->model, "changed",
+			G_CALLBACK (e_text_text_model_changed), text);
+
+		text->model_repos_signal_id = g_signal_connect (
+			text->model, "reposition",
+			G_CALLBACK (e_text_text_model_reposition), text);
+
+		text->text = e_text_model_get_text (text->model);
+		g_signal_emit (text, e_text_signals[E_TEXT_CHANGED], 0);
+
+		text->needs_split_into_lines = 1;
+		needs_reflow = 1;
+		break;
+
+	case PROP_EVENT_PROCESSOR:
+		if (text->tep && text->tep_command_id)
+			g_signal_handler_disconnect (
+				text->tep,
+				text->tep_command_id);
+		if (text->tep) {
+			g_object_unref (text->tep);
+		}
+		text->tep = E_TEXT_EVENT_PROCESSOR (g_value_get_object (value));
+		g_object_ref (text->tep);
+
+		text->tep_command_id = g_signal_connect (
+			text->tep, "command",
+			G_CALLBACK (e_text_command), text);
+
+		if (!text->allow_newlines)
+			g_object_set (
+				text->tep,
+				"allow_newlines", FALSE,
+				NULL);
+		break;
+
+	case PROP_TEXT:
+		e_text_model_set_text (text->model, g_value_get_string (value));
+		break;
+
+	case PROP_BOLD:
+		text->bold = g_value_get_boolean (value);
+
+		text->needs_redraw = 1;
+		text->needs_recalc_bounds = 1;
+		if (text->line_wrap)
+			text->needs_split_into_lines = 1;
+		else {
+			text->needs_calc_height = 1;
+		}
+		needs_update = 1;
+		needs_reflow = 1;
+		break;
+
+	case PROP_STRIKEOUT:
+		text->strikeout = g_value_get_boolean (value);
+		text->needs_redraw = 1;
+		needs_update = 1;
+		break;
+
+	case PROP_JUSTIFICATION:
+		text->justification = g_value_get_enum (value);
+		text->needs_redraw = 1;
+		needs_update = 1;
+		break;
+
+	case PROP_CLIP_WIDTH:
+		text->clip_width = fabs (g_value_get_double (value));
+		calc_ellipsis (text);
+		if (text->line_wrap) {
+			if (text->layout)
+				pango_layout_set_width (
+					text->layout, text->clip_width < 0
+					? -1 : text->clip_width * PANGO_SCALE);
+			text->needs_split_into_lines = 1;
+		} else {
+			text->needs_calc_height = 1;
+		}
+		needs_reflow = 1;
+		break;
+
+	case PROP_CLIP_HEIGHT:
+		text->clip_height = fabs (g_value_get_double (value));
+		text->needs_recalc_bounds = 1;
+		/* toshok: kind of a hack - set needs_reset_layout
+		 * here so when something about the style/them
+		 * changes, we redraw the text at the proper size/with
+		 * the proper font. */
+		text->needs_reset_layout = 1;
+		needs_reflow = 1;
+		break;
+
+	case PROP_CLIP:
+		text->clip = g_value_get_boolean (value);
+		calc_ellipsis (text);
+		if (text->line_wrap)
+			text->needs_split_into_lines = 1;
+		else {
+			text->needs_calc_height = 1;
+		}
+		needs_reflow = 1;
+		break;
+
+	case PROP_FILL_CLIP_RECTANGLE:
+		text->fill_clip_rectangle = g_value_get_boolean (value);
+		needs_update = 1;
+		break;
+
+	case PROP_X_OFFSET:
+		text->xofs = g_value_get_double (value);
+		text->needs_recalc_bounds = 1;
+		needs_update = 1;
+		break;
+
+	case PROP_Y_OFFSET:
+		text->yofs = g_value_get_double (value);
+		text->needs_recalc_bounds = 1;
+		needs_update = 1;
+		break;
+
+	case PROP_FILL_COLOR:
+		if (g_value_get_string (value))
+			gdk_color_parse (g_value_get_string (value), &color);
+
+		text->rgba = ((color.red & 0xff00) << 16 |
+			      (color.green & 0xff00) << 8 |
+			      (color.blue & 0xff00) |
+			      0xff);
+		text->rgba_set = TRUE;
+		text->needs_redraw = 1;
+		needs_update = 1;
+		break;
+
+	case PROP_FILL_COLOR_GDK:
+		pcolor = g_value_get_boxed (value);
+		if (pcolor) {
+			color = *pcolor;
+		}
+
+		text->rgba = ((color.red & 0xff00) << 16 |
+			      (color.green & 0xff00) << 8 |
+			      (color.blue & 0xff00) |
+			      0xff);
+		text->rgba_set = TRUE;
+		text->needs_redraw = 1;
+		needs_update = 1;
+		break;
+
+	case PROP_FILL_COLOR_RGBA:
+		text->rgba = g_value_get_uint (value);
+		color.red = ((text->rgba >> 24) & 0xff) * 0x101;
+		color.green = ((text->rgba >> 16) & 0xff) * 0x101;
+		color.blue = ((text->rgba >> 8) & 0xff) * 0x101;
+		text->rgba_set = TRUE;
+		text->needs_redraw = 1;
+		needs_update = 1;
+		break;
+
+	case PROP_EDITABLE:
+		text->editable = g_value_get_boolean (value);
+		text->needs_redraw = 1;
+		needs_update = 1;
+		break;
+
+	case PROP_USE_ELLIPSIS:
+		text->use_ellipsis = g_value_get_boolean (value);
+		needs_reflow = 1;
+		break;
+
+	case PROP_ELLIPSIS:
+		if (text->ellipsis)
+			g_free (text->ellipsis);
+
+		text->ellipsis = g_strdup (g_value_get_string (value));
+		calc_ellipsis (text);
+		needs_reflow = 1;
+		break;
+
+	case PROP_LINE_WRAP:
+		text->line_wrap = g_value_get_boolean (value);
+		if (text->line_wrap) {
+			if (text->layout) {
+				pango_layout_set_width (
+					text->layout, text->width < 0
+					? -1 : text->width * PANGO_SCALE);
+			}
+		}
+		text->needs_split_into_lines = 1;
+		needs_reflow = 1;
+		break;
+
+	case PROP_BREAK_CHARACTERS:
+		if (text->break_characters) {
+			g_free (text->break_characters);
+			text->break_characters = NULL;
+		}
+		if (g_value_get_string (value))
+			text->break_characters = g_strdup (g_value_get_string (value));
+		text->needs_split_into_lines = 1;
+		needs_reflow = 1;
+		break;
+
+	case PROP_MAX_LINES:
+		text->max_lines = g_value_get_int (value);
+		text->needs_split_into_lines = 1;
+		needs_reflow = 1;
+		break;
+
+	case PROP_WIDTH:
+		text->clip_width = fabs (g_value_get_double (value));
+		calc_ellipsis (text);
+		if (text->line_wrap) {
+			if (text->layout) {
+				pango_layout_set_width (
+					text->layout, text->width < 0 ?
+					-1 : text->width * PANGO_SCALE);
+			}
+			text->needs_split_into_lines = 1;
+		}
+		else {
+			text->needs_calc_height = 1;
+		}
+		needs_reflow = 1;
+		break;
+
+	case PROP_ALLOW_NEWLINES:
+		text->allow_newlines = g_value_get_boolean (value);
+		_get_tep (text);
+		g_object_set (
+			text->tep,
+			"allow_newlines", g_value_get_boolean (value),
+			NULL);
+		break;
+
+	case PROP_CURSOR_POS: {
+		ETextEventProcessorCommand command;
+
+		command.action = E_TEP_MOVE;
+		command.position = E_TEP_VALUE;
+		command.value = g_value_get_int (value);
+		command.time = GDK_CURRENT_TIME;
+		e_text_command (text->tep, &command, text);
+		break;
+	}
+
+	case PROP_IM_CONTEXT:
+		if (text->im_context) {
+			disconnect_im_context (text);
+			g_object_unref (text->im_context);
+		}
+
+		text->im_context = g_value_get_object (value);
+		if (text->im_context)
+			g_object_ref (text->im_context);
+
+		text->need_im_reset = TRUE;
+		break;
+
+	case PROP_HANDLE_POPUP:
+		text->handle_popup = g_value_get_boolean (value);
+		break;
+
+	default:
+		return;
+	}
+
+	if (needs_reflow)
+		e_canvas_item_request_reflow (item);
+	if (needs_update)
+		gnome_canvas_item_request_update (item);
+}
+
+/* Get_arg handler for the text item */
+static void
+e_text_get_property (GObject *object,
+                    guint property_id,
+                    GValue *value,
+                    GParamSpec *pspec)
+{
+	EText *text;
+
+	text = E_TEXT (object);
+
+	switch (property_id) {
+	case PROP_MODEL:
+		g_value_set_object (value, text->model);
+		break;
+
+	case PROP_EVENT_PROCESSOR:
+		_get_tep (text);
+		g_value_set_object (value, text->tep);
+		break;
+
+	case PROP_TEXT:
+		g_value_set_string (value, text->text);
+		break;
+
+	case PROP_BOLD:
+		g_value_set_boolean (value, text->bold);
+		break;
+
+	case PROP_STRIKEOUT:
+		g_value_set_boolean (value, text->strikeout);
+		break;
+
+	case PROP_JUSTIFICATION:
+		g_value_set_enum (value, text->justification);
+		break;
+
+	case PROP_CLIP_WIDTH:
+		g_value_set_double (value, text->clip_width);
+		break;
+
+	case PROP_CLIP_HEIGHT:
+		g_value_set_double (value, text->clip_height);
+		break;
+
+	case PROP_CLIP:
+		g_value_set_boolean (value, text->clip);
+		break;
+
+	case PROP_FILL_CLIP_RECTANGLE:
+		g_value_set_boolean (value, text->fill_clip_rectangle);
+		break;
+
+	case PROP_X_OFFSET:
+		g_value_set_double (value, text->xofs);
+		break;
+
+	case PROP_Y_OFFSET:
+		g_value_set_double (value, text->yofs);
+		break;
+
+	case PROP_FILL_COLOR_RGBA:
+		g_value_set_uint (value, text->rgba);
+		break;
+
+	case PROP_TEXT_WIDTH:
+		g_value_set_double (value, text->width);
+		break;
+
+	case PROP_TEXT_HEIGHT:
+		g_value_set_double (value, text->height);
+		break;
+
+	case PROP_EDITABLE:
+		g_value_set_boolean (value, text->editable);
+		break;
+
+	case PROP_USE_ELLIPSIS:
+		g_value_set_boolean (value, text->use_ellipsis);
+		break;
+
+	case PROP_ELLIPSIS:
+		g_value_set_string (value, text->ellipsis);
+		break;
+
+	case PROP_LINE_WRAP:
+		g_value_set_boolean (value, text->line_wrap);
+		break;
+
+	case PROP_BREAK_CHARACTERS:
+		g_value_set_string (value, text->break_characters);
+		break;
+
+	case PROP_MAX_LINES:
+		g_value_set_int (value, text->max_lines);
+		break;
+
+	case PROP_WIDTH:
+		g_value_set_double (value, text->clip_width);
+		break;
+
+	case PROP_HEIGHT:
+		g_value_set_double (
+			value, text->clip &&
+			text->clip_height != -1 ?
+			text->clip_height : text->height);
+		break;
+
+	case PROP_ALLOW_NEWLINES:
+		g_value_set_boolean (value, text->allow_newlines);
+		break;
+
+	case PROP_CURSOR_POS:
+		g_value_set_int (value, text->selection_start);
+		break;
+
+	case PROP_IM_CONTEXT:
+		g_value_set_object (value, text->im_context);
+		break;
+
+	case PROP_HANDLE_POPUP:
+		g_value_set_boolean (value, text->handle_popup);
+		break;
+
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+		break;
+	}
+}
+
+/* Update handler for the text item */
+static void
+e_text_reflow (GnomeCanvasItem *item,
+               gint flags)
+{
+	EText *text;
+
+	text = E_TEXT (item);
+
+	if (text->needs_reset_layout) {
+		reset_layout (text);
+		text->needs_reset_layout = 0;
+		text->needs_calc_height = 1;
+	}
+
+	if (text->needs_split_into_lines) {
+		split_into_lines (text);
+
+		text->needs_split_into_lines = 0;
+		text->needs_calc_height = 1;
+	}
+
+	if (text->needs_calc_height) {
+		calc_height (text);
+		gnome_canvas_item_request_update (item);
+		text->needs_calc_height = 0;
+		text->needs_recalc_bounds = 1;
+	}
+}
+
+/* Update handler for the text item */
+static void
+e_text_update (GnomeCanvasItem *item,
+               const cairo_matrix_t *i2c,
+               gint flags)
+{
+	EText *text;
+	gdouble x1, y1, x2, y2;
+
+	text = E_TEXT (item);
+
+	if (GNOME_CANVAS_ITEM_CLASS (e_text_parent_class)->update)
+		GNOME_CANVAS_ITEM_CLASS (e_text_parent_class)->update (
+			item, i2c, flags);
+
+	if (text->needs_recalc_bounds
+	     || (flags & GNOME_CANVAS_UPDATE_AFFINE)) {
+		get_bounds (text, &x1, &y1, &x2, &y2);
+		if (item->x1 != x1 ||
+		    item->x2 != x2 ||
+		    item->y1 != y1 ||
+		    item->y2 != y2) {
+			gnome_canvas_request_redraw (
+				item->canvas, item->x1, item->y1,
+				item->x2, item->y2);
+			item->x1 = x1;
+			item->y1 = y1;
+			item->x2 = x2;
+			item->y2 = y2;
+			text->needs_redraw = 1;
+			item->canvas->need_repick = TRUE;
+		}
+		if (!text->fill_clip_rectangle)
+			item->canvas->need_repick = TRUE;
+		text->needs_recalc_bounds = 0;
+	}
+	if (text->needs_redraw) {
+		gnome_canvas_request_redraw (
+			item->canvas, item->x1, item->y1, item->x2, item->y2);
+		text->needs_redraw = 0;
+	}
+}
+
+/* Realize handler for the text item */
+static void
+e_text_realize (GnomeCanvasItem *item)
+{
+	EText *text;
+
+	text = E_TEXT (item);
+
+	if (GNOME_CANVAS_ITEM_CLASS (e_text_parent_class)->realize)
+		(* GNOME_CANVAS_ITEM_CLASS (e_text_parent_class)->realize) (item);
+
+	create_layout (text);
+
+	text->i_cursor = gdk_cursor_new (GDK_XTERM);
+	text->default_cursor = gdk_cursor_new (GDK_LEFT_PTR);
+}
+
+/* Unrealize handler for the text item */
+static void
+e_text_unrealize (GnomeCanvasItem *item)
+{
+	EText *text;
+
+	text = E_TEXT (item);
+
+	g_object_unref (text->i_cursor);
+	text->i_cursor = NULL;
+	g_object_unref (text->default_cursor);
+	text->default_cursor = NULL;
+
+	if (GNOME_CANVAS_ITEM_CLASS (e_text_parent_class)->unrealize)
+		(* GNOME_CANVAS_ITEM_CLASS (e_text_parent_class)->unrealize) (item);
+}
+
+static void
+_get_tep (EText *text)
+{
+	if (!text->tep) {
+		text->tep = e_text_event_processor_emacs_like_new ();
+
+		text->tep_command_id = g_signal_connect (
+			text->tep, "command",
+			G_CALLBACK (e_text_command), text);
+	}
+}
+
+static void
+draw_pango_rectangle (cairo_t *cr,
+                      gint x1,
+                      gint y1,
+                      PangoRectangle rect)
+{
+	gint width = rect.width / PANGO_SCALE;
+	gint height = rect.height / PANGO_SCALE;
+
+	if (width <= 0)
+		width = 1;
+	if (height <= 0)
+		height = 1;
+
+	cairo_rectangle (
+		cr, x1 + rect.x / PANGO_SCALE,
+		y1 + rect.y / PANGO_SCALE, width, height);
+	cairo_fill (cr);
+}
+
+static gboolean
+show_pango_rectangle (EText *text,
+                      PangoRectangle rect)
+{
+	gint x1 = rect.x / PANGO_SCALE;
+	gint x2 = (rect.x + rect.width) / PANGO_SCALE;
+
+	gint y1 = rect.y / PANGO_SCALE;
+	gint y2 = (rect.y + rect.height) / PANGO_SCALE;
+
+	gint new_xofs_edit = text->xofs_edit;
+	gint new_yofs_edit = text->yofs_edit;
+
+	gint clip_width, clip_height;
+
+	clip_width = text->clip_width;
+	clip_height = text->clip_height;
+
+	if (x1 < new_xofs_edit)
+		new_xofs_edit = x1;
+
+	if (y1 < new_yofs_edit)
+		new_yofs_edit = y1;
+
+	if (clip_width >= 0) {
+		if (2 + x2 - clip_width > new_xofs_edit)
+			new_xofs_edit = 2 + x2 - clip_width;
+	} else {
+		new_xofs_edit = 0;
+	}
+
+	if (clip_height >= 0) {
+		if (y2 - clip_height > new_yofs_edit)
+			new_yofs_edit = y2 - clip_height;
+	} else {
+		new_yofs_edit = 0;
+	}
+
+	if (new_xofs_edit < 0)
+		new_xofs_edit = 0;
+	if (new_yofs_edit < 0)
+		new_yofs_edit = 0;
+
+	if (new_xofs_edit != text->xofs_edit ||
+	    new_yofs_edit != text->yofs_edit) {
+		text->xofs_edit = new_xofs_edit;
+		text->yofs_edit = new_yofs_edit;
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+/* Draw handler for the text item */
+static void
+e_text_draw (GnomeCanvasItem *item,
+             cairo_t *cr,
+             gint x,
+             gint y,
+             gint width,
+             gint height)
+{
+	EText *text;
+	gint xpos, ypos;
+	GnomeCanvas *canvas;
+	GtkWidget *widget;
+	GtkStyle *style;
+	GtkStateType state;
+
+	text = E_TEXT (item);
+	canvas = GNOME_CANVAS_ITEM (text)->canvas;
+	widget = GTK_WIDGET (canvas);
+	state = gtk_widget_get_state (widget);
+	style = gtk_widget_get_style (widget);
+
+	cairo_save (cr);
+
+	if (!text->rgba_set) {
+		gdk_cairo_set_source_color (cr, &style->fg[state]);
+	} else {
+		cairo_set_source_rgba (
+			cr,
+			((text->rgba >> 24) & 0xff) / 255.0,
+			((text->rgba >> 16) & 0xff) / 255.0,
+			((text->rgba >>  8) & 0xff) / 255.0,
+			( text->rgba        & 0xff) / 255.0);
+	}
+
+	/* Insert preedit text only when im_context signals are connected &
+	 * text->preedit_len is not zero */
+	if (text->im_context_signals_registered && text->preedit_len)
+		insert_preedit_text (text);
+
+	/* Need to reset the layout to cleanly clear the preedit buffer when
+	 * typing in CJK & using backspace on the preedit */
+	if (!text->preedit_len)
+		reset_layout (text);
+
+	if (!pango_layout_get_text (text->layout)) {
+		cairo_restore (cr);
+		return;
+	}
+
+	xpos = text->text_cx;
+	ypos = text->text_cy;
+
+	xpos = xpos - x + text->xofs;
+	ypos = ypos - y + text->yofs;
+
+	cairo_save (cr);
+
+	if (text->clip) {
+		cairo_rectangle (
+			cr, xpos, ypos,
+			text->clip_cwidth - text->xofs,
+			text->clip_cheight - text->yofs);
+		cairo_clip (cr);
+	}
+
+	if (text->editing) {
+		xpos -= text->xofs_edit;
+		ypos -= text->yofs_edit;
+	}
+
+	cairo_move_to (cr, xpos, ypos);
+	pango_cairo_show_layout (cr, text->layout);
+
+	if (text->editing) {
+		if (text->selection_start != text->selection_end) {
+			cairo_region_t *clip_region = cairo_region_create ();
+			gint indices[2];
+			GtkStateType state;
+
+			state = GTK_STATE_ACTIVE;
+
+			indices[0] = MIN (
+				text->selection_start,
+				text->selection_end);
+			indices[1] = MAX (
+				text->selection_start,
+				text->selection_end);
+
+			/* convert these into byte indices */
+			indices[0] = g_utf8_offset_to_pointer (
+				text->text, indices[0]) - text->text;
+			indices[1] = g_utf8_offset_to_pointer (
+				text->text, indices[1]) - text->text;
+
+			clip_region = gdk_pango_layout_get_clip_region (
+				text->layout, xpos, ypos, indices, 1);
+			gdk_cairo_region (cr, clip_region);
+			cairo_clip (cr);
+			cairo_region_destroy (clip_region);
+
+			gdk_cairo_set_source_color (cr, &style->base[state]);
+			cairo_paint (cr);
+
+			gdk_cairo_set_source_color (cr, &style->text[state]);
+			cairo_move_to (cr, xpos, ypos);
+			pango_cairo_show_layout (cr, text->layout);
+		} else {
+			if (text->show_cursor) {
+				PangoRectangle strong_pos, weak_pos;
+				gchar *offs;
+
+				offs = g_utf8_offset_to_pointer (
+					text->text, text->selection_start);
+
+				pango_layout_get_cursor_pos (
+					text->layout, offs - text->text +
+					text->preedit_len, &strong_pos,
+					&weak_pos);
+				draw_pango_rectangle (cr, xpos, ypos, strong_pos);
+				if (strong_pos.x != weak_pos.x ||
+				    strong_pos.y != weak_pos.y ||
+				    strong_pos.width != weak_pos.width ||
+				    strong_pos.height != weak_pos.height)
+					draw_pango_rectangle (cr, xpos, ypos, weak_pos);
+			}
+		}
+	}
+
+	cairo_restore (cr);
+	cairo_restore (cr);
+}
+
+/* Point handler for the text item */
+static GnomeCanvasItem *
+e_text_point (GnomeCanvasItem *item,
+              gdouble x,
+              gdouble y,
+              gint cx,
+              gint cy)
+{
+	EText *text;
+	gdouble clip_width;
+	gdouble clip_height;
+
+	text = E_TEXT (item);
+
+	/* The idea is to build bounding rectangles for each of the lines of
+	 * text (clipped by the clipping rectangle, if it is activated) and see
+	 * whether the point is inside any of these.  If it is, we are done.
+	 * Otherwise, calculate the distance to the nearest rectangle.
+	 */
+
+	if (text->clip_width < 0)
+		clip_width = text->width;
+	else
+		clip_width = text->clip_width;
+
+	if (text->clip_height < 0)
+		clip_height = text->height;
+	else
+		clip_height = text->clip_height;
+
+	if (cx < text->clip_cx ||
+	    cx > text->clip_cx + clip_width ||
+	    cy < text->clip_cy ||
+	    cy > text->clip_cy + clip_height)
+		return NULL;
+
+	if (text->fill_clip_rectangle || !text->text || !*text->text)
+		return item;
+
+	cx -= text->cx;
+
+	if (pango_layout_xy_to_index (text->layout, cx, cy, NULL, NULL))
+		return item;
+
+	return NULL;
+}
+
+/* Bounds handler for the text item */
+static void
+e_text_bounds (GnomeCanvasItem *item,
+               gdouble *x1,
+               gdouble *y1,
+               gdouble *x2,
+               gdouble *y2)
+{
+	EText *text;
+	gdouble width, height;
+
+	text = E_TEXT (item);
+
+	*x1 = 0;
+	*y1 = 0;
+
+	width = text->width;
+	height = text->height;
+
+	if (text->clip) {
+		if (text->clip_width >= 0)
+			width = text->clip_width;
+		if (text->clip_height >= 0)
+			height = text->clip_height;
+	}
+
+	*x2 = *x1 + width;
+	*y2 = *y1 + height;
+}
+
+static gint
+get_position_from_xy (EText *text,
+                      gint x,
+                      gint y)
+{
+	gint index;
+	gint trailing;
+
+	x -= text->xofs;
+	y -= text->yofs;
+
+	if (text->editing) {
+		x += text->xofs_edit;
+		y += text->yofs_edit;
+	}
+
+	x -= text->cx;
+	y -= text->cy;
+
+	pango_layout_xy_to_index (
+		text->layout, x * PANGO_SCALE,
+		y * PANGO_SCALE, &index, &trailing);
+
+	return g_utf8_pointer_to_offset (text->text, text->text + index + trailing);
+}
+
+#define SCROLL_WAIT_TIME 30000
+
+static gboolean
+_blink_scroll_timeout (gpointer data)
+{
+	EText *text = E_TEXT (data);
+	gulong current_time;
+	gboolean scroll = FALSE;
+	gboolean redraw = FALSE;
+
+	g_timer_elapsed (text->timer, &current_time);
+
+	if (text->scroll_start + SCROLL_WAIT_TIME > 1000000) {
+		if (current_time > text->scroll_start - (1000000 - SCROLL_WAIT_TIME) &&
+		    current_time < text->scroll_start)
+			scroll = TRUE;
+	} else {
+		if (current_time > text->scroll_start + SCROLL_WAIT_TIME ||
+		    current_time < text->scroll_start)
+			scroll = TRUE;
+	}
+	if (scroll && text->button_down && text->clip) {
+		gint old_xofs_edit = text->xofs_edit;
+		gint old_yofs_edit = text->yofs_edit;
+
+		if (text->clip_cwidth >= 0 &&
+		    text->lastx - text->clip_cx > text->clip_cwidth &&
+		    text->xofs_edit < text->width - text->clip_cwidth) {
+			text->xofs_edit += 4;
+			if (text->xofs_edit > text->width - text->clip_cwidth + 1)
+				text->xofs_edit = text->width - text->clip_cwidth + 1;
+		}
+		if (text->lastx - text->clip_cx < 0 &&
+		    text->xofs_edit > 0) {
+			text->xofs_edit -= 4;
+			if (text->xofs_edit < 0)
+				text->xofs_edit = 0;
+		}
+
+		if (text->clip_cheight >= 0 &&
+		    text->lasty - text->clip_cy > text->clip_cheight &&
+		    text->yofs_edit < text->height - text->clip_cheight) {
+			text->yofs_edit += 4;
+			if (text->yofs_edit > text->height - text->clip_cheight + 1)
+				text->yofs_edit = text->height - text->clip_cheight + 1;
+		}
+		if (text->lasty - text->clip_cy < 0 &&
+		    text->yofs_edit > 0) {
+			text->yofs_edit -= 4;
+			if (text->yofs_edit < 0)
+				text->yofs_edit = 0;
+		}
+
+		if (old_xofs_edit != text->xofs_edit ||
+		    old_yofs_edit != text->yofs_edit) {
+			ETextEventProcessorEvent e_tep_event;
+			e_tep_event.type = GDK_MOTION_NOTIFY;
+			e_tep_event.motion.state = text->last_state;
+			e_tep_event.motion.time = 0;
+			e_tep_event.motion.position =
+				get_position_from_xy (
+				text, text->lastx, text->lasty);
+			_get_tep (text);
+			e_text_event_processor_handle_event (
+				text->tep,
+				&e_tep_event);
+			text->scroll_start = current_time;
+			redraw = TRUE;
+		}
+	}
+
+	if (!((current_time / 500000) % 2)) {
+		if (!text->show_cursor)
+			redraw = TRUE;
+		text->show_cursor = TRUE;
+	} else {
+		if (text->show_cursor)
+			redraw = TRUE;
+		text->show_cursor = FALSE;
+	}
+	if (redraw) {
+		text->needs_redraw = 1;
+		gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (text));
+	}
+	return TRUE;
+}
+
+static void
+start_editing (EText *text)
+{
+	if (text->editing)
+		return;
+
+	e_text_reset_im_context (text);
+
+	g_free (text->revert);
+	text->revert = g_strdup (text->text);
+
+	text->editing = TRUE;
+	if (text->pointer_in) {
+		GdkWindow *window;
+
+		window = gtk_widget_get_window (
+			GTK_WIDGET (GNOME_CANVAS_ITEM (text)->canvas));
+
+		if (text->default_cursor_shown) {
+			gdk_window_set_cursor (window, text->i_cursor);
+			text->default_cursor_shown = FALSE;
+		}
+	}
+	text->select_by_word = FALSE;
+	text->xofs_edit = 0;
+	text->yofs_edit = 0;
+	if (text->timeout_id == 0)
+		text->timeout_id = g_timeout_add (10, _blink_scroll_timeout, text);
+	text->timer = g_timer_new ();
+	g_timer_elapsed (text->timer, &(text->scroll_start));
+	g_timer_start (text->timer);
+}
+
+void
+e_text_stop_editing (EText *text)
+{
+	if (!text->editing)
+		return;
+
+	g_free (text->revert);
+	text->revert = NULL;
+
+	text->editing = FALSE;
+	if (!text->default_cursor_shown) {
+		GdkWindow *window;
+
+		window = gtk_widget_get_window (
+			GTK_WIDGET (GNOME_CANVAS_ITEM (text)->canvas));
+		gdk_window_set_cursor (window, text->default_cursor);
+		text->default_cursor_shown = TRUE;
+	}
+	if (text->timer) {
+		g_timer_stop (text->timer);
+		g_timer_destroy (text->timer);
+		text->timer = NULL;
+	}
+
+	text->need_im_reset = TRUE;
+	text->preedit_len = 0;
+	text->preedit_pos = 0;
+}
+
+void
+e_text_cancel_editing (EText *text)
+{
+	if (text->revert)
+		e_text_model_set_text (text->model, text->revert);
+	e_text_stop_editing (text);
+}
+
+static gboolean
+_click (gpointer data)
+{
+	*(gint *)data = 0;
+	return FALSE;
+}
+
+static gint
+e_text_event (GnomeCanvasItem *item,
+              GdkEvent *event)
+{
+	EText *text = E_TEXT (item);
+	ETextEventProcessorEvent e_tep_event;
+	GdkWindow *window;
+	gint return_val = 0;
+
+	if (!text->model)
+		return 0;
+
+	window = gtk_widget_get_window (GTK_WIDGET (item->canvas));
+
+	e_tep_event.type = event->type;
+	switch (event->type) {
+	case GDK_FOCUS_CHANGE:
+		if (text->editable) {
+			GdkEventFocus *focus_event;
+			focus_event = (GdkEventFocus *) event;
+			if (focus_event->in) {
+				if (text->im_context) {
+					if (!text->im_context_signals_registered) {
+						g_signal_connect (
+							text->im_context, "commit",
+							G_CALLBACK (e_text_commit_cb), text);
+						g_signal_connect (
+							text->im_context, "preedit_changed",
+							G_CALLBACK (e_text_preedit_changed_cb), text);
+						g_signal_connect (
+							text->im_context, "retrieve_surrounding",
+							G_CALLBACK (e_text_retrieve_surrounding_cb), text);
+						g_signal_connect (
+							text->im_context, "delete_surrounding",
+							G_CALLBACK (e_text_delete_surrounding_cb), text);
+						text->im_context_signals_registered = TRUE;
+					}
+					gtk_im_context_focus_in (text->im_context);
+				}
+
+				start_editing (text);
+
+				/* So we'll redraw and the
+				 * cursor will be shown. */
+				text->show_cursor = FALSE;
+			} else {
+				if (text->im_context) {
+					gtk_im_context_focus_out (text->im_context);
+					disconnect_im_context (text);
+					text->need_im_reset = TRUE;
+				}
+
+				e_text_stop_editing (text);
+				if (text->timeout_id) {
+					g_source_remove (text->timeout_id);
+					text->timeout_id = 0;
+				}
+				if (text->show_cursor) {
+					text->show_cursor = FALSE;
+					text->needs_redraw = 1;
+					gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (text));
+				}
+			}
+			if (text->line_wrap)
+				text->needs_split_into_lines = 1;
+			e_canvas_item_request_reflow (GNOME_CANVAS_ITEM (text));
+		}
+		return_val = 0;
+		break;
+	case GDK_KEY_PRESS:
+
+		/* Handle S-F10 key binding here. */
+
+		if (event->key.keyval == GDK_KEY_F10
+		    && (event->key.state & GDK_SHIFT_MASK)
+		    && text->handle_popup) {
+
+			/* Simulate a GdkEventButton here, so that we can
+			 * call e_text_do_popup directly */
+
+			GdkEvent *button_event;
+
+			button_event = gdk_event_new (GDK_BUTTON_PRESS);
+			button_event->button.time = event->key.time;
+			button_event->button.button = 0;
+			e_text_do_popup (text, button_event, 0);
+			return 1;
+		}
+
+		/* Fall Through */
+
+	case GDK_KEY_RELEASE:
+
+		if (text->editing) {
+			GdkEventKey key;
+			gint ret;
+
+			if (text->im_context &&
+				gtk_im_context_filter_keypress (
+					text->im_context,
+					(GdkEventKey *) event)) {
+				text->need_im_reset = TRUE;
+				return 1;
+			}
+
+			key = event->key;
+			e_tep_event.key.time = key.time;
+			e_tep_event.key.state = key.state;
+			e_tep_event.key.keyval = key.keyval;
+
+			/* This is probably ugly hack, but we
+			 * have to handle UTF-8 input somehow. */
+			e_tep_event.key.string = e_utf8_from_gtk_event_key (
+				GTK_WIDGET (item->canvas),
+				key.keyval, key.string);
+			if (e_tep_event.key.string != NULL) {
+				e_tep_event.key.length = strlen (e_tep_event.key.string);
+			} else {
+				e_tep_event.key.length = 0;
+			}
+
+			_get_tep (text);
+			ret = e_text_event_processor_handle_event (text->tep, &e_tep_event);
+
+			if (event->type == GDK_KEY_PRESS)
+				g_signal_emit (
+					text, e_text_signals[E_TEXT_KEYPRESS], 0,
+					e_tep_event.key.keyval, e_tep_event.key.state);
+
+			if (e_tep_event.key.string)
+				g_free ((gpointer) e_tep_event.key.string);
+
+			return ret;
+		}
+		break;
+	case GDK_BUTTON_PRESS: /* Fall Through */
+	case GDK_BUTTON_RELEASE:
+		if ((!text->editing)
+		    && text->editable
+		    && (event->button.button == 1 ||
+			event->button.button == 2)) {
+			e_canvas_item_grab_focus (item, TRUE);
+			start_editing (text);
+		}
+
+		/* We follow convention and emit popup events on right-clicks. */
+		if (event->type == GDK_BUTTON_PRESS && event->button.button == 3) {
+			if (text->handle_popup) {
+				e_text_do_popup (
+					text, event,
+					get_position_from_xy (
+						text, event->button.x,
+						event->button.y));
+				return 1;
+			}
+			else {
+				break;
+			}
+		}
+
+		/* Create our own double and triple click events,
+		 * as gnome-canvas doesn't forward them to us */
+		if (event->type == GDK_BUTTON_PRESS) {
+			if (text->dbl_timeout == 0 &&
+			    text->tpl_timeout == 0) {
+				text->dbl_timeout = g_timeout_add (
+					200, _click, &(text->dbl_timeout));
+			} else {
+				if (text->tpl_timeout == 0) {
+					e_tep_event.type = GDK_2BUTTON_PRESS;
+					text->tpl_timeout = g_timeout_add (
+						200, _click, &(text->tpl_timeout));
+				} else {
+					e_tep_event.type = GDK_3BUTTON_PRESS;
+				}
+			}
+		}
+
+		if (text->editing) {
+			GdkEventButton button = event->button;
+			e_tep_event.button.time = button.time;
+			e_tep_event.button.state = button.state;
+			e_tep_event.button.button = button.button;
+			e_tep_event.button.position =
+				get_position_from_xy (
+				text, button.x, button.y);
+			e_tep_event.button.device =
+				gdk_event_get_device (event);
+			_get_tep (text);
+			return_val = e_text_event_processor_handle_event (
+				text->tep, &e_tep_event);
+			if (event->button.button == 1) {
+				if (event->type == GDK_BUTTON_PRESS)
+					text->button_down = TRUE;
+				else
+					text->button_down = FALSE;
+			}
+			text->lastx = button.x;
+			text->lasty = button.y;
+			text->last_state = button.state;
+		}
+		break;
+	case GDK_MOTION_NOTIFY:
+		if (text->editing) {
+			GdkEventMotion motion = event->motion;
+			e_tep_event.motion.time = motion.time;
+			e_tep_event.motion.state = motion.state;
+			e_tep_event.motion.position =
+				get_position_from_xy (
+				text, motion.x, motion.y);
+			_get_tep (text);
+			return_val = e_text_event_processor_handle_event (
+				text->tep, &e_tep_event);
+			text->lastx = motion.x;
+			text->lasty = motion.y;
+			text->last_state = motion.state;
+		}
+		break;
+	case GDK_ENTER_NOTIFY:
+		text->pointer_in = TRUE;
+		if (text->editing) {
+			if (text->default_cursor_shown) {
+				gdk_window_set_cursor (window, text->i_cursor);
+				text->default_cursor_shown = FALSE;
+			}
+		}
+		break;
+	case GDK_LEAVE_NOTIFY:
+		text->pointer_in = FALSE;
+		if (text->editing) {
+			if (!text->default_cursor_shown) {
+				gdk_window_set_cursor (
+					window, text->default_cursor);
+				text->default_cursor_shown = TRUE;
+			}
+		}
+		break;
+	default:
+		break;
+	}
+	if (return_val)
+		return return_val;
+	if (GNOME_CANVAS_ITEM_CLASS (e_text_parent_class)->event)
+		return GNOME_CANVAS_ITEM_CLASS (e_text_parent_class)->event (item, event);
+	else
+		return 0;
+}
+
+void
+e_text_copy_clipboard (EText *text)
+{
+	gint selection_start_pos;
+	gint selection_end_pos;
+
+	selection_start_pos = MIN (text->selection_start, text->selection_end);
+	selection_end_pos = MAX (text->selection_start, text->selection_end);
+
+	/* convert sel_start/sel_end to byte indices */
+	selection_start_pos = g_utf8_offset_to_pointer (
+		text->text, selection_start_pos) - text->text;
+	selection_end_pos = g_utf8_offset_to_pointer (
+		text->text, selection_end_pos) - text->text;
+
+	gtk_clipboard_set_text (
+		gtk_widget_get_clipboard (
+		GTK_WIDGET (GNOME_CANVAS_ITEM (text)->canvas),
+		GDK_SELECTION_CLIPBOARD),
+		text->text + selection_start_pos,
+		selection_end_pos - selection_start_pos);
+}
+
+void
+e_text_delete_selection (EText *text)
+{
+	gint sel_start, sel_end;
+
+	sel_start = MIN (text->selection_start, text->selection_end);
+	sel_end   = MAX (text->selection_start, text->selection_end);
+
+	if (sel_start != sel_end)
+		e_text_model_delete (text->model, sel_start, sel_end - sel_start);
+	text->need_im_reset = TRUE;
+}
+
+void
+e_text_cut_clipboard (EText *text)
+{
+	e_text_copy_clipboard (text);
+	e_text_delete_selection (text);
+}
+
+void
+e_text_paste_clipboard (EText *text)
+{
+	ETextEventProcessorCommand command;
+
+	command.action = E_TEP_PASTE;
+	command.position = E_TEP_SELECTION;
+	command.string = "";
+	command.value = 0;
+	e_text_command (text->tep, &command, text);
+}
+
+void
+e_text_select_all (EText *text)
+{
+	ETextEventProcessorCommand command;
+
+	command.action = E_TEP_SELECT;
+	command.position = E_TEP_SELECT_ALL;
+	command.string = "";
+	command.value = 0;
+	e_text_command (text->tep, &command, text);
+}
+
+static void
+primary_get_cb (GtkClipboard *clipboard,
+                GtkSelectionData *selection_data,
+                guint info,
+                gpointer data)
+{
+	EText *text = E_TEXT (data);
+	gint sel_start, sel_end;
+
+	sel_start = MIN (text->selection_start, text->selection_end);
+	sel_end   = MAX (text->selection_start, text->selection_end);
+
+	/* convert sel_start/sel_end to byte indices */
+	sel_start = g_utf8_offset_to_pointer (text->text, sel_start) - text->text;
+	sel_end = g_utf8_offset_to_pointer (text->text, sel_end) - text->text;
+
+	if (sel_start != sel_end) {
+		gtk_selection_data_set_text (
+			selection_data,
+			text->text + sel_start,
+			sel_end - sel_start);
+	}
+}
+
+static void
+primary_clear_cb (GtkClipboard *clipboard,
+                  gpointer data)
+{
+#ifdef notyet
+	/* XXX */
+	gtk_editable_select_region (
+		GTK_EDITABLE (entry), entry->current_pos, entry->current_pos);
+#endif
+}
+
+static void
+e_text_update_primary_selection (EText *text)
+{
+	static const GtkTargetEntry targets[] = {
+		{ (gchar *) "UTF8_STRING", 0, 0 },
+		{ (gchar *) "UTF-8", 0, 0 },
+		{ (gchar *) "STRING", 0, 0 },
+		{ (gchar *) "TEXT", 0, 0 },
+		{ (gchar *) "COMPOUND_TEXT", 0, 0 }
+	};
+	GtkClipboard *clipboard;
+
+	clipboard = gtk_widget_get_clipboard (
+		GTK_WIDGET (GNOME_CANVAS_ITEM (text)->canvas),
+		GDK_SELECTION_PRIMARY);
+
+	if (text->selection_start != text->selection_end) {
+		if (!gtk_clipboard_set_with_owner (
+			clipboard, targets, G_N_ELEMENTS (targets),
+			primary_get_cb, primary_clear_cb, G_OBJECT (text)))
+			primary_clear_cb (clipboard, text);
+	} else {
+		if (gtk_clipboard_get_owner (clipboard) == G_OBJECT (text))
+			gtk_clipboard_clear (clipboard);
+	}
+}
+
+static void
+paste_received (GtkClipboard *clipboard,
+                const gchar *text,
+                gpointer data)
+{
+	EText *etext = E_TEXT (data);
+
+	if (text && g_utf8_validate (text, strlen (text), NULL)) {
+		if (etext->selection_end != etext->selection_start)
+			e_text_delete_selection (etext);
+
+		e_text_insert (etext, text);
+	}
+
+	g_object_unref (etext);
+}
+
+static void
+e_text_paste (EText *text,
+              GdkAtom selection)
+{
+	g_object_ref (text);
+	gtk_clipboard_request_text (
+		gtk_widget_get_clipboard (
+		GTK_WIDGET (GNOME_CANVAS_ITEM (text)->canvas),
+		selection), paste_received, text);
+}
+
+typedef struct {
+	EText *text;
+	GdkEvent *event;
+	gint position;
+} PopupClosure;
+
+static void
+popup_menu_detach (GtkWidget *attach_widget,
+                   GtkMenu *menu)
+{
+}
+
+static void
+popup_menu_placement_cb (GtkMenu *menu,
+                         gint *x,
+                         gint *y,
+                         gboolean *push_in,
+                         gpointer user_data)
+{
+	EText *text = E_TEXT (user_data);
+	GnomeCanvasItem *item = &text->item;
+	GnomeCanvas *parent = item->canvas;
+
+	if (parent) {
+		GdkWindow *window;
+
+		window = gtk_widget_get_window (GTK_WIDGET (parent));
+		gdk_window_get_origin (window, x, y);
+		*x += item->x1 + text->width / 2;
+		*y += item->y1 + text->height / 2;
+	}
+
+	return;
+}
+
+static void
+popup_targets_received (GtkClipboard *clipboard,
+                        GtkSelectionData *data,
+                        gpointer user_data)
+{
+	PopupClosure *closure = user_data;
+	EText *text = closure->text;
+	GdkEvent *event = closure->event;
+	gint position = closure->position;
+	GtkWidget *popup_menu = gtk_menu_new ();
+	GtkWidget *menuitem, *submenu;
+	guint event_button = 0;
+	guint32 event_time;
+
+	gdk_event_get_button (event, &event_button);
+	event_time = gdk_event_get_time (event);
+
+	g_free (closure);
+
+	gtk_menu_attach_to_widget (
+		GTK_MENU (popup_menu),
+		GTK_WIDGET (GNOME_CANVAS_ITEM (text)->canvas),
+		popup_menu_detach);
+
+	/* cut menu item */
+	menuitem = gtk_image_menu_item_new_from_stock (GTK_STOCK_CUT, NULL);
+	gtk_widget_show (menuitem);
+	gtk_menu_shell_append (GTK_MENU_SHELL (popup_menu), menuitem);
+	g_signal_connect_swapped (
+		menuitem, "activate",
+		G_CALLBACK (e_text_cut_clipboard), text);
+	gtk_widget_set_sensitive (
+		menuitem, text->editable &&
+		(text->selection_start != text->selection_end));
+
+	/* copy menu item */
+	menuitem = gtk_image_menu_item_new_from_stock (GTK_STOCK_COPY, NULL);
+	gtk_widget_show (menuitem);
+	gtk_menu_shell_append (GTK_MENU_SHELL (popup_menu), menuitem);
+	g_signal_connect_swapped (
+		menuitem, "activate",
+		G_CALLBACK (e_text_copy_clipboard), text);
+	gtk_widget_set_sensitive (menuitem, text->selection_start != text->selection_end);
+
+	/* paste menu item */
+	menuitem = gtk_image_menu_item_new_from_stock (GTK_STOCK_PASTE, NULL);
+	gtk_widget_show (menuitem);
+	gtk_menu_shell_append (GTK_MENU_SHELL (popup_menu), menuitem);
+	g_signal_connect_swapped (
+		menuitem, "activate",
+		G_CALLBACK (e_text_paste_clipboard), text);
+	gtk_widget_set_sensitive (
+		menuitem, text->editable &&
+		gtk_selection_data_targets_include_text (data));
+
+	menuitem = gtk_menu_item_new_with_label (_("Select All"));
+	gtk_widget_show (menuitem);
+	gtk_menu_shell_append (GTK_MENU_SHELL (popup_menu), menuitem);
+	g_signal_connect_swapped (
+		menuitem, "activate",
+		G_CALLBACK (e_text_select_all), text);
+	gtk_widget_set_sensitive (menuitem, strlen (text->text) > 0);
+
+	menuitem = gtk_separator_menu_item_new ();
+	gtk_widget_show (menuitem);
+	gtk_menu_shell_append (GTK_MENU_SHELL (popup_menu), menuitem);
+
+	if (text->im_context && GTK_IS_IM_MULTICONTEXT (text->im_context)) {
+		menuitem = gtk_menu_item_new_with_label (_("Input Methods"));
+		gtk_widget_show (menuitem);
+		submenu = gtk_menu_new ();
+		gtk_menu_item_set_submenu (GTK_MENU_ITEM (menuitem), submenu);
+
+		gtk_menu_shell_append (GTK_MENU_SHELL (popup_menu), menuitem);
+
+		gtk_im_multicontext_append_menuitems (
+			GTK_IM_MULTICONTEXT (text->im_context),
+			GTK_MENU_SHELL (submenu));
+	}
+
+	g_signal_emit (
+		text,
+		e_text_signals[E_TEXT_POPULATE_POPUP],
+		0,
+		event,
+		position,
+		popup_menu);
+
+	/* If invoked by S-F10 key binding, button will be 0. */
+	if (event_button == 0) {
+		gtk_menu_popup (
+			GTK_MENU (popup_menu), NULL, NULL,
+			popup_menu_placement_cb, (gpointer) text,
+			event_button, GDK_CURRENT_TIME);
+	} else {
+		gtk_menu_popup (
+			GTK_MENU (popup_menu), NULL, NULL,
+			NULL, NULL,
+			event_button, event_time);
+	}
+
+	g_object_unref (text);
+	gdk_event_free (event);
+}
+
+static void
+e_text_do_popup (EText *text,
+                 GdkEvent *button_event,
+                 gint position)
+{
+	PopupClosure *closure = g_new (PopupClosure, 1);
+
+	closure->text = g_object_ref (text);
+	closure->event = gdk_event_copy (button_event);
+	closure->position = position;
+
+	gtk_clipboard_request_contents (
+		gtk_widget_get_clipboard (
+			GTK_WIDGET (GNOME_CANVAS_ITEM (text)->canvas),
+			GDK_SELECTION_CLIPBOARD),
+		gdk_atom_intern ("TARGETS", FALSE),
+		popup_targets_received,
+		closure);
+}
+
+static void
+e_text_reset_im_context (EText *text)
+{
+	if (text->need_im_reset && text->im_context) {
+		text->need_im_reset = FALSE;
+		gtk_im_context_reset (text->im_context);
+	}
+}
+
+/* fixme: */
+
+static gint
+next_word (EText *text,
+           gint start)
+{
+	gchar *p = g_utf8_offset_to_pointer (text->text, start);
+	gint length;
+
+	length = g_utf8_strlen (text->text, -1);
+
+	if (start >= length) {
+		return length;
+	} else {
+		p = g_utf8_next_char (p);
+		start++;
+
+		while (p && *p) {
+			gunichar unival = g_utf8_get_char (p);
+			if (g_unichar_isspace (unival)) {
+				return start + 1;
+			}
+			else {
+				p = g_utf8_next_char (p);
+				start++;
+			}
+		}
+	}
+
+	return g_utf8_pointer_to_offset (text->text, p);
+}
+
+static gint
+find_offset_into_line (EText *text,
+                       gint offset_into_text,
+                       gchar **start_of_line)
+{
+	gchar *p;
+
+	p = g_utf8_offset_to_pointer (text->text, offset_into_text);
+
+	if (p == text->text) {
+		if (start_of_line)
+			*start_of_line = (gchar *)text->text;
+		return 0;
+	}
+	else {
+		p = g_utf8_find_prev_char (text->text, p);
+
+		while (p && p > text->text) {
+			if (*p == '\n') {
+				if (start_of_line)
+					*start_of_line = p+1;
+				return offset_into_text -
+					g_utf8_pointer_to_offset (
+					text->text, p + 1);
+			}
+			p = g_utf8_find_prev_char (text->text, p);
+		}
+
+		if (start_of_line)
+			*start_of_line = (gchar *)text->text;
+		return offset_into_text;
+	}
+}
+
+/* direction = TRUE (move forward), FALSE (move backward)
+ * Any error shall return length (text->text) or 0 or
+ * text->selection_end (as deemed fit) */
+static gint
+_get_updated_position (EText *text,
+                       gboolean direction)
+{
+	PangoLogAttr *log_attrs = NULL;
+	gint n_attrs;
+	gchar *p = NULL;
+	gint new_pos = 0;
+	gint length = 0;
+
+	/* Basic sanity test, return whatever position we are currently at. */
+	g_return_val_if_fail (text->layout != NULL, text->selection_end);
+
+	length = g_utf8_strlen (text->text, -1);
+
+	/* length checks to make sure we are not wandering
+	 * off into nonexistant memory... */
+	if ((text->selection_end >= length) && (TRUE == direction))	/* forward */
+		return length;
+	/* checking for -ve value wont hurt! */
+	if ((text->selection_end <= 0) && (FALSE == direction))		/* backward */
+		return 0;
+
+	/* check for validness of full text->text */
+	if (!g_utf8_validate (text->text, -1, NULL))
+		return text->selection_end;
+
+	/* get layout's PangoLogAttr to facilitate moving when
+	 * moving across grapheme cluster as in indic langs */
+	pango_layout_get_log_attrs (text->layout, &log_attrs, &n_attrs);
+
+	/* Fetch the current gchar index in the line & keep moving
+	 * forward until we can display cursor */
+	p = g_utf8_offset_to_pointer (text->text, text->selection_end);
+
+	new_pos = text->selection_end;
+	while (1)
+	{
+		/* check before moving forward/backwards
+		 * if we have more chars to move or not */
+		if (TRUE == direction)
+			p = g_utf8_next_char (p);
+		else
+			p = g_utf8_prev_char (p);
+
+		/* validate the new string & return with original position if check fails */
+		if (!g_utf8_validate (p, -1, NULL))
+			break;	/* will return old value of new_pos */
+
+		new_pos = g_utf8_pointer_to_offset (text->text, p);
+
+		/* if is_cursor_position is set, cursor can appear in front of character.
+		 * i.e. this is a grapheme boundary AND make some sanity checks */
+		if ((new_pos >=0) && (new_pos < n_attrs) &&
+			(log_attrs[new_pos].is_cursor_position))
+			break;
+		else if ((new_pos < 0) || (new_pos >= n_attrs))
+		{
+			new_pos = text->selection_end;
+			break;
+		}
+	}
+
+	if (log_attrs)
+		g_free (log_attrs);
+
+	return new_pos;
+}
+
+static gint
+_get_position (EText *text,
+               ETextEventProcessorCommand *command)
+{
+	gint length, obj_num;
+	gunichar unival;
+	gchar *p = NULL;
+	gint new_pos = 0;
+
+	switch (command->position) {
+
+	case E_TEP_VALUE:
+		new_pos = command->value;
+		break;
+
+	case E_TEP_SELECTION:
+		new_pos = text->selection_end;
+		break;
+
+	case E_TEP_START_OF_BUFFER:
+		new_pos = 0;
+		break;
+
+	case E_TEP_END_OF_BUFFER:
+		new_pos = strlen (text->text);
+		break;
+
+	case E_TEP_START_OF_LINE:
+
+		if (text->selection_end >= 1) {
+
+			p = g_utf8_offset_to_pointer (text->text, text->selection_end);
+			if (p != text->text) {
+				p = g_utf8_find_prev_char (text->text, p);
+				while (p && p > text->text) {
+					if (*p == '\n') {
+						new_pos = g_utf8_pointer_to_offset (text->text, p) + 1;
+						break;
+					}
+					p = g_utf8_find_prev_char (text->text, p);
+				}
+			}
+		}
+
+		break;
+
+	case E_TEP_END_OF_LINE:
+		new_pos = -1;
+		length = g_utf8_strlen (text->text, -1);
+
+		if (text->selection_end >= length) {
+			new_pos = length;
+		} else {
+
+			p = g_utf8_offset_to_pointer (text->text, text->selection_end);
+
+			while (p && *p) {
+				if (*p == '\n') {
+					new_pos = g_utf8_pointer_to_offset (text->text, p);
+					p = NULL;
+				} else
+					p = g_utf8_next_char (p);
+			}
+		}
+
+		if (new_pos == -1)
+			new_pos = g_utf8_pointer_to_offset (text->text, p);
+
+		break;
+
+	case E_TEP_FORWARD_CHARACTER:
+		length = g_utf8_strlen (text->text, -1);
+
+		if (text->selection_end >= length)
+			new_pos = length;
+		else
+			/* get updated position to display cursor */
+			new_pos = _get_updated_position (text, TRUE);
+
+		break;
+
+	case E_TEP_BACKWARD_CHARACTER:
+		new_pos = 0;
+		if (text->selection_end >= 1)
+			/* get updated position to display cursor */
+			new_pos = _get_updated_position (text, FALSE);
+
+		break;
+
+	case E_TEP_FORWARD_WORD:
+		new_pos = next_word (text, text->selection_end);
+		break;
+
+	case E_TEP_BACKWARD_WORD:
+		new_pos = 0;
+		if (text->selection_end >= 1) {
+			gint pos = text->selection_end;
+
+			p = g_utf8_find_prev_char (
+				text->text, g_utf8_offset_to_pointer (
+				text->text, text->selection_end));
+			pos--;
+
+			if (p != text->text) {
+				p = g_utf8_find_prev_char (text->text, p);
+				pos--;
+
+				while (p && p > text->text) {
+					unival = g_utf8_get_char (p);
+					if (g_unichar_isspace (unival)) {
+						new_pos = pos + 1;
+						p = NULL;
+					}
+					else {
+						p = g_utf8_find_prev_char (text->text, p);
+						pos--;
+					}
+				}
+			}
+		}
+
+		break;
+
+	case E_TEP_FORWARD_LINE: {
+		gint offset_into_line;
+
+		offset_into_line = find_offset_into_line (text, text->selection_end, NULL);
+		if (offset_into_line == -1)
+			return text->selection_end;
+
+		/* now we search forward til we hit a \n, and then
+		 * offset_into_line more characters */
+		p = g_utf8_offset_to_pointer (text->text, text->selection_end);
+		while (p && *p) {
+			if (*p == '\n')
+				break;
+			p = g_utf8_next_char (p);
+		}
+		if (p && *p == '\n') {
+			/* now we loop forward offset_into_line
+			 * characters, or until we hit \n or \0 */
+
+			p = g_utf8_next_char (p);
+			while (offset_into_line > 0 && p && *p != '\n' && *p != '\0') {
+				p = g_utf8_next_char (p);
+				offset_into_line--;
+			}
+		}
+
+		/* at this point, p points to the new location,
+		 * convert it to an offset and we're done */
+		new_pos = g_utf8_pointer_to_offset (text->text, p);
+		break;
+	}
+	case E_TEP_BACKWARD_LINE: {
+		gint offset_into_line;
+
+		offset_into_line = find_offset_into_line (
+			text, text->selection_end, &p);
+
+		if (offset_into_line == -1)
+			return text->selection_end;
+
+		/* p points to the first character on our line.  if we
+		 * have a \n before it, skip it and scan til we hit
+		 * the next one */
+		if (p != text->text) {
+			p = g_utf8_find_prev_char (text->text, p);
+			if (*p == '\n') {
+				p = g_utf8_find_prev_char (text->text, p);
+				while (p > text->text) {
+					if (*p == '\n') {
+						p++;
+						break;
+					}
+					p = g_utf8_find_prev_char (text->text, p);
+				}
+			}
+		}
+
+		/* at this point 'p' points to the start of the
+		 * previous line, move forward 'offset_into_line'
+		 * times. */
+
+		while (offset_into_line > 0 && p && *p != '\n' && *p != '\0') {
+			p = g_utf8_next_char (p);
+			offset_into_line--;
+		}
+
+		/* at this point, p points to the new location,
+		 * convert it to an offset and we're done */
+		new_pos = g_utf8_pointer_to_offset (text->text, p);
+		break;
+	}
+	case E_TEP_SELECT_WORD:
+		/* This is a silly hack to cause double-clicking on an object
+		 * to activate that object.
+		 * (Normally, double click == select word, which is why this is here.) */
+
+		obj_num = e_text_model_get_object_at_offset (
+			text->model, text->selection_start);
+		if (obj_num != -1) {
+			e_text_model_activate_nth_object (text->model, obj_num);
+			new_pos = text->selection_start;
+			break;
+		}
+
+		if (text->selection_end < 1) {
+			new_pos = 0;
+			break;
+		}
+
+		p = g_utf8_offset_to_pointer (text->text, text->selection_end);
+
+		p = g_utf8_find_prev_char (text->text, p);
+
+		while (p && p > text->text) {
+			unival = g_utf8_get_char (p);
+			if (g_unichar_isspace (unival)) {
+				p = g_utf8_next_char (p);
+				break;
+			}
+			p = g_utf8_find_prev_char (text->text, p);
+		}
+
+		if (!p)
+			text->selection_start = 0;
+		else
+			text->selection_start = g_utf8_pointer_to_offset (text->text, p);
+
+		text->selection_start =
+			e_text_model_validate_position (
+			text->model, text->selection_start);
+
+		length = g_utf8_strlen (text->text, -1);
+		if (text->selection_end >= length) {
+			new_pos = length;
+			break;
+		}
+
+		p = g_utf8_offset_to_pointer (text->text, text->selection_end);
+		while (p && *p) {
+			unival = g_utf8_get_char (p);
+			if (g_unichar_isspace (unival)) {
+				new_pos =  g_utf8_pointer_to_offset (text->text, p);
+				break;
+			} else
+				p = g_utf8_next_char (p);
+		}
+
+		if (!new_pos)
+			new_pos = g_utf8_strlen (text->text, -1);
+
+		return new_pos;
+
+	case E_TEP_SELECT_ALL:
+		text->selection_start = 0;
+		new_pos = g_utf8_strlen (text->text, -1);
+		break;
+
+	case E_TEP_FORWARD_PARAGRAPH:
+	case E_TEP_BACKWARD_PARAGRAPH:
+
+	case E_TEP_FORWARD_PAGE:
+	case E_TEP_BACKWARD_PAGE:
+		new_pos = text->selection_end;
+		break;
+
+	default:
+		new_pos = text->selection_end;
+		break;
+	}
+
+	new_pos = e_text_model_validate_position (text->model, new_pos);
+
+	return new_pos;
+}
+
+static void
+e_text_insert (EText *text,
+               const gchar *string)
+{
+	gint len = strlen (string);
+
+	if (len > 0) {
+		gint utf8len = 0;
+
+		if (!text->allow_newlines) {
+			const gchar *i;
+			gchar *new_string = g_malloc (len + 1);
+			gchar *j = new_string;
+
+			for (i = string; *i; i = g_utf8_next_char (i)) {
+				if (*i != '\n') {
+					gunichar c;
+					gint charlen;
+
+					c = g_utf8_get_char (i);
+					charlen = g_unichar_to_utf8 (c, j);
+					j += charlen;
+					utf8len++;
+				}
+			}
+			*j = 0;
+			e_text_model_insert_length (
+				text->model, text->selection_start,
+				new_string, utf8len);
+			g_free (new_string);
+		}
+		else {
+			utf8len = g_utf8_strlen (string, -1);
+			e_text_model_insert_length (
+				text->model, text->selection_start,
+				string, utf8len);
+		}
+	}
+}
+
+static void
+capitalize (EText *text,
+            gint start,
+            gint end,
+            ETextEventProcessorCaps type)
+{
+	gboolean first = TRUE;
+	const gchar *p = g_utf8_offset_to_pointer (text->text, start);
+	const gchar *text_end = g_utf8_offset_to_pointer (text->text, end);
+	gint utf8len = text_end - p;
+
+	if (utf8len > 0) {
+		gchar *new_text = g_new0 (char, utf8len * 6);
+		gchar *output = new_text;
+
+		while (p && *p && p < text_end) {
+			gunichar unival = g_utf8_get_char (p);
+			gunichar newval = unival;
+
+			switch (type) {
+			case E_TEP_CAPS_UPPER:
+				newval = g_unichar_toupper (unival);
+				break;
+			case E_TEP_CAPS_LOWER:
+				newval = g_unichar_tolower (unival);
+				break;
+			case E_TEP_CAPS_TITLE:
+				if (g_unichar_isalpha (unival)) {
+					if (first)
+						newval = g_unichar_totitle (unival);
+					else
+						newval = g_unichar_tolower (unival);
+					first = FALSE;
+				} else {
+					first = TRUE;
+				}
+				break;
+			}
+			g_unichar_to_utf8 (newval, output);
+			output = g_utf8_next_char (output);
+
+			p = g_utf8_next_char (p);
+		}
+		*output = 0;
+
+		e_text_model_delete (text->model, start, utf8len);
+		e_text_model_insert_length (text->model, start, new_text, utf8len);
+		g_free (new_text);
+	}
+}
+
+static void
+e_text_command (ETextEventProcessor *tep,
+                ETextEventProcessorCommand *command,
+                gpointer data)
+{
+	EText *text = E_TEXT (data);
+	gboolean scroll = TRUE;
+	gboolean use_start = TRUE;
+
+	switch (command->action) {
+	case E_TEP_MOVE:
+		text->selection_start = _get_position (text, command);
+		text->selection_end = text->selection_start;
+		if (text->timer) {
+			g_timer_reset (text->timer);
+		}
+
+		text->need_im_reset = TRUE;
+		use_start = TRUE;
+		break;
+	case E_TEP_SELECT:
+		text->selection_start =
+			e_text_model_validate_position (
+			text->model, text->selection_start); /* paranoia */
+		text->selection_end = _get_position (text, command);
+
+		e_text_update_primary_selection (text);
+
+		text->need_im_reset = TRUE;
+		use_start = FALSE;
+
+		break;
+	case E_TEP_DELETE:
+		if (text->selection_end == text->selection_start) {
+			text->selection_end = _get_position (text, command);
+		}
+		e_text_delete_selection (text);
+		if (text->timer) {
+			g_timer_reset (text->timer);
+		}
+
+		text->need_im_reset = TRUE;
+		use_start = FALSE;
+
+		break;
+
+	case E_TEP_INSERT:
+		if (g_utf8_validate (command->string, command->value, NULL)) {
+			if (text->selection_end != text->selection_start) {
+				e_text_delete_selection (text);
+			}
+			e_text_insert (text, command->string);
+			if (text->timer) {
+				g_timer_reset (text->timer);
+			}
+			text->need_im_reset = TRUE;
+		}
+		break;
+	case E_TEP_COPY:
+		e_text_copy_clipboard (text);
+
+		if (text->timer) {
+			g_timer_reset (text->timer);
+		}
+		scroll = FALSE;
+		break;
+	case E_TEP_PASTE:
+		e_text_paste (text, GDK_NONE);
+		if (text->timer) {
+			g_timer_reset (text->timer);
+		}
+		text->need_im_reset = TRUE;
+		break;
+	case E_TEP_GET_SELECTION:
+		e_text_paste (text, GDK_SELECTION_PRIMARY);
+		break;
+	case E_TEP_ACTIVATE:
+		g_signal_emit (text, e_text_signals[E_TEXT_ACTIVATE], 0);
+		if (text->timer) {
+			g_timer_reset (text->timer);
+		}
+		break;
+	case E_TEP_SET_SELECT_BY_WORD:
+		text->select_by_word = command->value;
+		break;
+	case E_TEP_GRAB:
+		e_canvas_item_grab (
+			E_CANVAS (GNOME_CANVAS_ITEM (text)->canvas),
+			GNOME_CANVAS_ITEM (text),
+			GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK,
+			text->i_cursor,
+			command->device,
+			command->time,
+			NULL,
+			NULL);
+		scroll = FALSE;
+		break;
+	case E_TEP_UNGRAB:
+		e_canvas_item_ungrab (
+			E_CANVAS (GNOME_CANVAS_ITEM (text)->canvas),
+			GNOME_CANVAS_ITEM (text),
+			command->time);
+		scroll = FALSE;
+		break;
+	case E_TEP_CAPS:
+		if (text->selection_start == text->selection_end) {
+			capitalize (
+				text, text->selection_start,
+				next_word (text, text->selection_start),
+				command->value);
+		} else {
+			gint selection_start = MIN (
+				text->selection_start, text->selection_end);
+			gint selection_end = MAX (
+				text->selection_start, text->selection_end);
+			capitalize (
+				text, selection_start,
+				selection_end, command->value);
+		}
+		break;
+	case E_TEP_NOP:
+		scroll = FALSE;
+		break;
+	}
+
+	e_text_reset_im_context (text);
+
+	/* it's possible to get here without ever having been realized
+	 * by our canvas (if the e-text started completely obscured.)
+	 * so let's create our layout object if we don't already have
+	 * one. */
+	if (!text->layout)
+		create_layout (text);
+
+	/* We move cursor only if scroll is TRUE */
+	if (scroll && !text->button_down) {
+		/* XXX do we really need the @trailing logic here?  if
+		 * we don't we can scrap the loop and just use
+		 * pango_layout_index_to_pos */
+		PangoLayoutLine *cur_line = NULL;
+		gint selection_index;
+		PangoLayoutIter *iter = pango_layout_get_iter (text->layout);
+
+		/* check if we are using selection_start or selection_end for moving? */
+		selection_index = use_start ? text->selection_start : text->selection_end;
+
+		/* convert to a byte index */
+		selection_index = g_utf8_offset_to_pointer (
+			text->text, selection_index) - text->text;
+
+		do {
+			PangoLayoutLine *line = pango_layout_iter_get_line (iter);
+
+			if (selection_index >= line->start_index &&
+				selection_index <= line->start_index + line->length) {
+				/* found the line with the start of the selection */
+				cur_line = line;
+				break;
+			}
+
+		} while (pango_layout_iter_next_line (iter));
+
+		if (cur_line) {
+			gint xpos, ypos;
+			gdouble clip_width, clip_height;
+			/* gboolean trailing = FALSE; */
+			PangoRectangle pango_pos;
+
+			if (selection_index > 0 && selection_index ==
+				cur_line->start_index + cur_line->length) {
+				selection_index--;
+				/* trailing = TRUE; */
+			}
+
+			pango_layout_index_to_pos (text->layout, selection_index, &pango_pos);
+
+			pango_pos.x = PANGO_PIXELS (pango_pos.x);
+			pango_pos.y = PANGO_PIXELS (pango_pos.y);
+			pango_pos.width = (pango_pos.width + PANGO_SCALE / 2) / PANGO_SCALE;
+			pango_pos.height = (pango_pos.height + PANGO_SCALE / 2) / PANGO_SCALE;
+
+			/* scroll for X */
+			xpos = pango_pos.x; /* + (trailing ? 0 : pango_pos.width);*/
+
+			if (xpos + 2 < text->xofs_edit) {
+				text->xofs_edit = xpos;
+			}
+
+			clip_width = text->clip_width;
+
+			if (xpos + pango_pos.width - clip_width > text->xofs_edit) {
+				text->xofs_edit = xpos + pango_pos.width - clip_width;
+			}
+
+			/* scroll for Y */
+			if (pango_pos.y + 2 < text->yofs_edit) {
+				ypos = pango_pos.y;
+				text->yofs_edit = ypos;
+			}
+			else {
+				ypos = pango_pos.y + pango_pos.height;
+			}
+
+			if (text->clip_height < 0)
+				clip_height = text->height;
+			else
+				clip_height = text->clip_height;
+
+			if (ypos - clip_height > text->yofs_edit) {
+				text->yofs_edit = ypos - clip_height;
+			}
+
+		}
+
+		pango_layout_iter_free (iter);
+	}
+
+	text->needs_redraw = 1;
+	gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (text));
+}
+
+/* Class initialization function for the text item */
+static void
+e_text_class_init (ETextClass *class)
+{
+	GObjectClass *gobject_class;
+	GnomeCanvasItemClass *item_class;
+
+	gobject_class = (GObjectClass *) class;
+	item_class = (GnomeCanvasItemClass *) class;
+
+	gobject_class->dispose = e_text_dispose;
+	gobject_class->set_property = e_text_set_property;
+	gobject_class->get_property = e_text_get_property;
+
+	item_class->update = e_text_update;
+	item_class->realize = e_text_realize;
+	item_class->unrealize = e_text_unrealize;
+	item_class->draw = e_text_draw;
+	item_class->point = e_text_point;
+	item_class->bounds = e_text_bounds;
+	item_class->event = e_text_event;
+
+	class->changed = NULL;
+	class->activate = NULL;
+
+	e_text_signals[E_TEXT_CHANGED] = g_signal_new (
+		"changed",
+		G_OBJECT_CLASS_TYPE (gobject_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ETextClass, changed),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__VOID,
+		G_TYPE_NONE, 0);
+
+	e_text_signals[E_TEXT_ACTIVATE] = g_signal_new (
+		"activate",
+		G_OBJECT_CLASS_TYPE (gobject_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ETextClass, activate),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__VOID,
+		G_TYPE_NONE, 0);
+
+	e_text_signals[E_TEXT_KEYPRESS] = g_signal_new (
+		"keypress",
+		G_OBJECT_CLASS_TYPE (gobject_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ETextClass, keypress),
+		NULL, NULL,
+		e_marshal_NONE__INT_INT,
+		G_TYPE_NONE, 2,
+		G_TYPE_UINT,
+		G_TYPE_UINT);
+
+	e_text_signals[E_TEXT_POPULATE_POPUP] = g_signal_new (
+		"populate_popup",
+		G_OBJECT_CLASS_TYPE (gobject_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ETextClass, populate_popup),
+		NULL, NULL,
+		e_marshal_NONE__POINTER_INT_OBJECT,
+		G_TYPE_NONE, 3,
+		G_TYPE_POINTER,
+		G_TYPE_INT,
+		GTK_TYPE_MENU);
+
+	g_object_class_install_property (
+		gobject_class,
+		PROP_MODEL,
+		g_param_spec_object (
+			"model",
+			"Model",
+			"Model",
+			E_TYPE_TEXT_MODEL,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		gobject_class,
+		PROP_EVENT_PROCESSOR,
+		g_param_spec_object (
+			"event_processor",
+			"Event Processor",
+			"Event Processor",
+			E_TEXT_EVENT_PROCESSOR_TYPE,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		gobject_class,
+		PROP_TEXT,
+		g_param_spec_string (
+			"text",
+			"Text",
+			"Text",
+			NULL,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		gobject_class,
+		PROP_BOLD,
+		g_param_spec_boolean (
+			"bold",
+			"Bold",
+			"Bold",
+			FALSE,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		gobject_class,
+		PROP_STRIKEOUT,
+		g_param_spec_boolean (
+			"strikeout",
+			"Strikeout",
+			"Strikeout",
+			FALSE,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		gobject_class,
+		PROP_JUSTIFICATION,
+		g_param_spec_enum (
+			"justification",
+			"Justification",
+			"Justification",
+			GTK_TYPE_JUSTIFICATION,
+			GTK_JUSTIFY_LEFT,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		gobject_class,
+		PROP_CLIP_WIDTH,
+		g_param_spec_double (
+			"clip_width",
+			"Clip Width",
+			"Clip Width",
+			0.0, G_MAXDOUBLE, 0.0,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		gobject_class,
+		PROP_CLIP_HEIGHT,
+		g_param_spec_double (
+			"clip_height",
+			"Clip Height",
+			"Clip Height",
+			0.0, G_MAXDOUBLE, 0.0,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		gobject_class,
+		PROP_CLIP,
+		g_param_spec_boolean (
+			"clip",
+			"Clip",
+			"Clip",
+			FALSE,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		gobject_class,
+		PROP_FILL_CLIP_RECTANGLE,
+		g_param_spec_boolean (
+			"fill_clip_rectangle",
+			"Fill clip rectangle",
+			"Fill clip rectangle",
+			FALSE,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		gobject_class,
+		PROP_X_OFFSET,
+		g_param_spec_double (
+			"x_offset",
+			"X Offset",
+			"X Offset",
+			0.0, G_MAXDOUBLE, 0.0,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		gobject_class,
+		PROP_Y_OFFSET,
+		g_param_spec_double (
+			"y_offset",
+			"Y Offset",
+			"Y Offset",
+			0.0, G_MAXDOUBLE, 0.0,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		gobject_class,
+		PROP_FILL_COLOR,
+		g_param_spec_string (
+			"fill_color",
+			"Fill color",
+			"Fill color",
+			NULL,
+			G_PARAM_WRITABLE));
+
+	g_object_class_install_property (
+		gobject_class,
+		PROP_FILL_COLOR_GDK,
+		g_param_spec_boxed (
+			"fill_color_gdk",
+			"GDK fill color",
+			"GDK fill color",
+			GDK_TYPE_COLOR,
+			G_PARAM_WRITABLE));
+
+	g_object_class_install_property (
+		gobject_class,
+		PROP_FILL_COLOR_RGBA,
+		g_param_spec_uint (
+			"fill_color_rgba",
+			"GDK fill color",
+			"GDK fill color",
+			0, G_MAXUINT, 0,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		gobject_class,
+		PROP_TEXT_WIDTH,
+		g_param_spec_double (
+			"text_width",
+			"Text width",
+			"Text width",
+			0.0, G_MAXDOUBLE, 0.0,
+			G_PARAM_READABLE));
+
+	g_object_class_install_property (
+		gobject_class,
+		PROP_TEXT_HEIGHT,
+		g_param_spec_double (
+			"text_height",
+			"Text height",
+			"Text height",
+			0.0, G_MAXDOUBLE, 0.0,
+			G_PARAM_READABLE));
+
+	g_object_class_install_property (
+		gobject_class,
+		PROP_EDITABLE,
+		g_param_spec_boolean (
+			"editable",
+			"Editable",
+			"Editable",
+			FALSE,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		gobject_class,
+		PROP_USE_ELLIPSIS,
+		g_param_spec_boolean (
+			"use_ellipsis",
+			"Use ellipsis",
+			"Use ellipsis",
+			FALSE,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		gobject_class,
+		PROP_ELLIPSIS,
+		g_param_spec_string (
+			"ellipsis",
+			"Ellipsis",
+			"Ellipsis",
+			NULL,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		gobject_class,
+		PROP_LINE_WRAP,
+		g_param_spec_boolean (
+			"line_wrap",
+			"Line wrap",
+			"Line wrap",
+			FALSE,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		gobject_class,
+		PROP_BREAK_CHARACTERS,
+		g_param_spec_string (
+			"break_characters",
+			"Break characters",
+			"Break characters",
+			NULL,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		gobject_class, PROP_MAX_LINES,
+		g_param_spec_int (
+			"max_lines",
+			"Max lines",
+			"Max lines",
+			0, G_MAXINT, 0,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		gobject_class,
+		PROP_WIDTH,
+		g_param_spec_double (
+			"width",
+			"Width",
+			"Width",
+			0.0, G_MAXDOUBLE, 0.0,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		gobject_class,
+		PROP_HEIGHT,
+		g_param_spec_double (
+			"height",
+			"Height",
+			"Height",
+			0.0, G_MAXDOUBLE, 0.0,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		gobject_class,
+		PROP_ALLOW_NEWLINES,
+		g_param_spec_boolean (
+			"allow_newlines",
+			"Allow newlines",
+			"Allow newlines",
+			FALSE,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		gobject_class,
+		PROP_CURSOR_POS,
+		g_param_spec_int (
+			"cursor_pos",
+			"Cursor position",
+			"Cursor position",
+			0, G_MAXINT, 0,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		gobject_class,
+		PROP_IM_CONTEXT,
+		g_param_spec_object (
+			"im_context",
+			"IM Context",
+			"IM Context",
+			GTK_TYPE_IM_CONTEXT,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		gobject_class,
+		PROP_HANDLE_POPUP,
+		g_param_spec_boolean (
+			"handle_popup",
+			"Handle Popup",
+			"Handle Popup",
+			FALSE,
+			G_PARAM_READWRITE));
+
+	if (!clipboard_atom)
+		clipboard_atom = gdk_atom_intern ("CLIPBOARD", FALSE);
+
+	gal_a11y_e_text_init ();
+}
+
+/* Object initialization function for the text item */
+static void
+e_text_init (EText *text)
+{
+	text->model                   = e_text_model_new ();
+	text->text                    = e_text_model_get_text (text->model);
+	text->preedit_len	      = 0;
+	text->preedit_pos	      = 0;
+	text->layout                  = NULL;
+
+	text->revert                  = NULL;
+
+	text->model_changed_signal_id = g_signal_connect (
+		text->model, "changed",
+		G_CALLBACK (e_text_text_model_changed), text);
+
+	text->model_repos_signal_id = g_signal_connect (
+		text->model, "reposition",
+		G_CALLBACK (e_text_text_model_reposition), text);
+
+	text->justification           = GTK_JUSTIFY_LEFT;
+	text->clip_width              = -1.0;
+	text->clip_height             = -1.0;
+	text->xofs                    = 0.0;
+	text->yofs                    = 0.0;
+
+	text->ellipsis                = NULL;
+	text->use_ellipsis            = FALSE;
+	text->ellipsis_width          = 0;
+
+	text->editable                = FALSE;
+	text->editing                 = FALSE;
+	text->xofs_edit               = 0;
+	text->yofs_edit               = 0;
+
+	text->selection_start         = 0;
+	text->selection_end           = 0;
+	text->select_by_word          = FALSE;
+
+	text->timeout_id              = 0;
+	text->timer                   = NULL;
+
+	text->lastx                   = 0;
+	text->lasty                   = 0;
+	text->last_state              = 0;
+
+	text->scroll_start            = 0;
+	text->show_cursor             = TRUE;
+	text->button_down             = FALSE;
+
+	text->tep                     = NULL;
+	text->tep_command_id          = 0;
+
+	text->pointer_in              = FALSE;
+	text->default_cursor_shown    = TRUE;
+	text->line_wrap               = FALSE;
+	text->break_characters        = NULL;
+	text->max_lines               = -1;
+	text->dbl_timeout             = 0;
+	text->tpl_timeout             = 0;
+
+	text->bold                    = FALSE;
+	text->strikeout               = FALSE;
+
+	text->allow_newlines          = TRUE;
+
+	text->last_type_request       = -1;
+	text->last_time_request       = 0;
+	text->queued_requests         = NULL;
+
+	text->im_context              = NULL;
+	text->need_im_reset           = FALSE;
+	text->im_context_signals_registered = FALSE;
+
+	text->handle_popup            = FALSE;
+	text->rgba_set		      = FALSE;
+
+	e_canvas_item_set_reflow_callback (GNOME_CANVAS_ITEM (text), e_text_reflow);
+}
+
+/* IM Context Callbacks */
+static void
+e_text_commit_cb (GtkIMContext *context,
+                  const gchar *str,
+                  EText *text)
+{
+	if (g_utf8_validate (str, strlen (str), NULL)) {
+		if (text->selection_end != text->selection_start)
+			e_text_delete_selection (text);
+		e_text_insert (text, str);
+		g_signal_emit (text, e_text_signals[E_TEXT_KEYPRESS], 0, 0, 0);
+	}
+}
+
+static void
+e_text_preedit_changed_cb (GtkIMContext *context,
+                           EText *etext)
+{
+	gchar *preedit_string = NULL;
+	gint cursor_pos;
+
+	gtk_im_context_get_preedit_string (
+		context, &preedit_string,
+		NULL, &cursor_pos);
+
+	cursor_pos = CLAMP (cursor_pos, 0, g_utf8_strlen (preedit_string, -1));
+	etext->preedit_len = strlen (preedit_string);
+	etext->preedit_pos = g_utf8_offset_to_pointer (
+		preedit_string, cursor_pos) - preedit_string;
+	g_free (preedit_string);
+
+	g_signal_emit (etext, e_text_signals[E_TEXT_KEYPRESS], 0, 0, 0);
+}
+
+static gboolean
+e_text_retrieve_surrounding_cb (GtkIMContext *context,
+                                EText *text)
+{
+	gtk_im_context_set_surrounding (
+		context, text->text, strlen (text->text),
+		g_utf8_offset_to_pointer (text->text, MIN (
+		text->selection_start, text->selection_end)) - text->text);
+
+	return TRUE;
+}
+
+static gboolean
+e_text_delete_surrounding_cb (GtkIMContext *context,
+                              gint offset,
+                              gint n_chars,
+                              EText *text)
+{
+	e_text_model_delete (
+		text->model,
+		MIN (text->selection_start, text->selection_end) + offset,
+		n_chars);
+
+	return TRUE;
+}
diff --git a/e-util/e-text.h b/e-util/e-text.h
new file mode 100644
index 0000000..40e92bf
--- /dev/null
+++ b/e-util/e-text.h
@@ -0,0 +1,236 @@
+/*
+ * e-text.h - Text item for evolution.
+ *
+ * This program 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 program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *		Jon Trowbridge <trow ximian com>
+ *
+ * A majority of code taken from:
+ *
+ * Text item type for GnomeCanvas widget
+ *
+ * GnomeCanvas is basically a port of the Tk toolkit's most excellent
+ * canvas widget.  Tk is copyrighted by the Regents of the University
+ * of California, Sun Microsystems, and other parties.
+ *
+ * Copyright (C) 1998 The Free Software Foundation
+ *
+ * Author: Federico Mena <federico nuclecu unam mx>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_TEXT_H
+#define E_TEXT_H
+
+#include <gtk/gtk.h>
+
+#include <e-util/e-canvas.h>
+#include <e-util/e-text-event-processor.h>
+#include <e-util/e-text-model.h>
+
+G_BEGIN_DECLS
+
+/* Text item for the canvas.  Text items are positioned by an anchor point and an anchor direction.
+ *
+ * A clipping rectangle may be specified for the text.  The rectangle is anchored at the text's anchor
+ * point, and is specified by clipping width and height parameters.  If the clipping rectangle is
+ * enabled, it will clip the text.
+ *
+ * In addition, x and y offset values may be specified.  These specify an offset from the anchor
+ * position.  If used in conjunction with the clipping rectangle, these could be used to implement
+ * simple scrolling of the text within the clipping rectangle.
+ *
+ * The following object arguments are available:
+ *
+ * name			type			read/write	description
+ * ------------------------------------------------------------------------------------------
+ * text			string			RW		The string of the text label
+ * bold                 boolean                 RW              Bold?
+ * justification	GtkJustification	RW		Justification for multiline text
+ * fill_color		string			W		X color specification for text
+ * fill_color_gdk	GdkColor*		RW		Pointer to an allocated GdkColor
+ * clip_width		gdouble			RW		Width of clip rectangle
+ * clip_height		gdouble			RW		Height of clip rectangle
+ * clip			boolean			RW		Use clipping rectangle?
+ * fill_clip_rect       boolean                 RW              Whether the text item represents itself as being the size of the clipping rectangle.
+ * x_offset		gdouble			RW		Horizontal offset distance from anchor position
+ * y_offset		gdouble			RW		Vertical offset distance from anchor position
+ * text_width		gdouble			R		Used to query the width of the rendered text
+ * text_height		gdouble			R		Used to query the rendered height of the text
+ * width                gdouble                  RW              A synonym for clip_width
+ * height               gdouble                  R               A synonym for text_height
+ *
+ * These are currently ignored in the AA version:
+ * editable             boolean                 RW              Can this item be edited
+ * use_ellipsis         boolean                 RW              Whether to use ellipsises if text gets cut off.  Meaningless if clip == false.
+ * ellipsis             string                  RW              The characters to use as ellipsis.  NULL = "...".
+ * line_wrap            boolean                 RW              Line wrap when not editing.
+ * break_characters     string                  RW              List of characters to optionally break on.
+ * max_lines            gint                     RW              Number of lines possible when doing line wrap.
+ */
+
+#define E_TYPE_TEXT            (e_text_get_type ())
+#define E_TEXT(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), E_TYPE_TEXT, EText))
+#define E_TEXT_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), E_TYPE_TEXT, ETextClass))
+#define E_IS_TEXT(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), E_TYPE_TEXT))
+#define E_IS_TEXT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), E_TYPE_TEXT))
+
+typedef struct _EText EText;
+typedef struct _ETextClass ETextClass;
+
+struct _EText {
+	GnomeCanvasItem item;
+
+	ETextModel *model;
+	gint model_changed_signal_id;
+	gint model_repos_signal_id;
+
+	const gchar *text;              /* Text to display --- from the ETextModel */
+	gint preedit_len;		/* preedit length to display */
+	gint preedit_pos;		/* preedit cursor position */
+	PangoLayout *layout;
+	gint num_lines;			/* Number of lines of text */
+
+	gchar *revert;                  /* Text to revert to */
+
+	GtkJustification justification;	/* Justification for text */
+
+	gdouble clip_width;		/* Width of optional clip rectangle */
+	gdouble clip_height;		/* Height of optional clip rectangle */
+
+	gdouble xofs, yofs;		/* Text offset distance from anchor position */
+
+	gint cx, cy;			/* Top-left canvas coordinates for text */
+	gint text_cx, text_cy;		/* Top-left canvas coordinates for text */
+	gint clip_cx, clip_cy;		/* Top-left canvas coordinates for clip rectangle */
+	gint clip_cwidth, clip_cheight;	/* Size of clip rectangle in pixels */
+	gint max_width;			/* Maximum width of text lines */
+	gint width;                      /* Rendered text width in pixels */
+	gint height;			/* Rendered text height in pixels */
+
+	guint32 rgba;			/* RGBA color for text */
+	gboolean rgba_set;		/* whether RGBA is set */
+
+	gchar *ellipsis;                 /* The ellipsis characters.  NULL = "...". */
+	gdouble ellipsis_width;          /* The width of the ellipsis. */
+
+	gint xofs_edit;                  /* Offset because of editing */
+	gint yofs_edit;                  /* Offset because of editing */
+
+	/* This needs to be reworked a bit once we get line wrapping. */
+	gint selection_start;            /* Start of selection IN BYTES */
+	gint selection_end;              /* End of selection IN BYTES */
+	gboolean select_by_word;        /* Current selection is by word */
+
+	/* This section is for drag scrolling and blinking cursor. */
+	gint timeout_id;                /* Current timeout id for scrolling */
+	GTimer *timer;                  /* Timer for blinking cursor and scrolling */
+
+	gint lastx, lasty;              /* Last x and y motion events */
+	gint last_state;                /* Last state */
+	gulong scroll_start;            /* Starting time for scroll (microseconds) */
+
+	gint show_cursor;               /* Is cursor currently shown */
+	gboolean button_down;           /* Is mouse button 1 down */
+
+	ETextEventProcessor *tep;       /* Text Event Processor */
+	gint tep_command_id;
+
+	gboolean has_selection;         /* TRUE if we have the selection */
+
+	guint clip : 1;			/* Use clip rectangle? */
+	guint fill_clip_rectangle : 1;  /* Fill the clipping rectangle. */
+
+	guint pointer_in : 1;           /* Is the pointer currently over us? */
+	guint default_cursor_shown : 1; /* Is the default cursor currently shown? */
+
+	guint line_wrap : 1;            /* Do line wrap */
+
+	guint needs_redraw : 1;         /* Needs redraw */
+	guint needs_recalc_bounds : 1;  /* Need recalc_bounds */
+	guint needs_calc_height : 1;    /* Need calc_height */
+	guint needs_split_into_lines : 1; /* Needs split_into_lines */
+	guint needs_reset_layout : 1; /* Needs split_into_lines */
+
+	guint bold : 1;
+	guint strikeout : 1;
+
+	guint tooltip_owner : 1;
+	guint allow_newlines : 1;
+
+	guint use_ellipsis : 1;         /* Whether to use the ellipsis. */
+
+	guint editable : 1;             /* Item is editable */
+	guint editing : 1;              /* Item is currently being edited */
+
+	gchar *break_characters;        /* Characters to optionally break after */
+
+	gint max_lines;                 /* Max number of lines (-1 = infinite) */
+
+	GdkCursor *default_cursor;      /* Default cursor (arrow) */
+	GdkCursor *i_cursor;            /* I beam cursor */
+
+	gint tooltip_timeout;           /* Timeout for the tooltip */
+	gint tooltip_count;             /* GDK_ENTER_NOTIFY count. */
+
+	gint dbl_timeout;               /* Double click timeout */
+	gint tpl_timeout;               /* Triple click timeout */
+
+	gint     last_type_request;       /* Last selection type requested. */
+	guint32  last_time_request;       /* The time of the last selection request. */
+	GdkAtom  last_selection_request;  /* The time of the last selection request. */
+	GList   *queued_requests;         /* Queued selection requests. */
+
+	GtkIMContext *im_context;
+	gboolean      need_im_reset;
+	gboolean      im_context_signals_registered;
+
+	gboolean      handle_popup;
+
+	PangoFontDescription *font_desc;
+};
+
+struct _ETextClass {
+	GnomeCanvasItemClass parent_class;
+
+	void (* changed)         (EText *text);
+	void (* activate)        (EText *text);
+	void (* keypress)        (EText *text, guint keyval, guint state);
+	void (* populate_popup)  (EText *text, GdkEvent *button_event, gint pos, GtkMenu *menu);
+	void (* style_set)       (EText *text, GtkStyle *previous_style);
+};
+
+/* Standard Gtk function */
+GType    e_text_get_type        (void);
+void     e_text_cancel_editing  (EText *text);
+void     e_text_stop_editing    (EText *text);
+
+void     e_text_delete_selection (EText *text);
+void     e_text_cut_clipboard    (EText *text);
+void     e_text_copy_clipboard   (EText *text);
+void     e_text_paste_clipboard  (EText *text);
+void     e_text_select_all       (EText *text);
+
+G_END_DECLS
+
+#endif
diff --git a/e-util/e-timezone-dialog.c b/e-util/e-timezone-dialog.c
new file mode 100644
index 0000000..431287c
--- /dev/null
+++ b/e-util/e-timezone-dialog.c
@@ -0,0 +1,870 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Damon Chaplin <damon ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-timezone-dialog.h"
+
+#include <time.h>
+#include <string.h>
+#include <glib/gi18n.h>
+
+#include <libecal/libecal.h>
+
+#include "e-map.h"
+#include "e-misc-utils.h"
+#include "e-util-private.h"
+
+#ifdef G_OS_WIN32
+#ifdef gmtime_r
+#undef gmtime_r
+#endif
+#ifdef localtime_r
+#undef localtime_r
+#endif
+
+/* The gmtime() and localtime() in Microsoft's C library are MT-safe */
+#define gmtime_r(tp,tmp) (gmtime(tp)?(*(tmp)=*gmtime(tp),(tmp)):0)
+#define localtime_r(tp,tmp) (localtime(tp)?(*(tmp)=*localtime(tp),(tmp)):0)
+#endif
+
+#define E_TIMEZONE_DIALOG_MAP_POINT_NORMAL_RGBA 0xc070a0ff
+#define E_TIMEZONE_DIALOG_MAP_POINT_HOVER_RGBA 0xffff60ff
+#define E_TIMEZONE_DIALOG_MAP_POINT_SELECTED_1_RGBA 0xff60e0ff
+#define E_TIMEZONE_DIALOG_MAP_POINT_SELECTED_2_RGBA 0x000000ff
+
+#define E_TIMEZONE_DIALOG_GET_PRIVATE(obj) \
+	(G_TYPE_INSTANCE_GET_PRIVATE \
+	((obj), E_TYPE_TIMEZONE_DIALOG, ETimezoneDialogPrivate))
+
+struct _ETimezoneDialogPrivate {
+	/* The selected timezone. May be NULL for a 'local time' (i.e. when
+	 * the displayed name is ""). */
+	icaltimezone *zone;
+
+	GtkBuilder *builder;
+
+	EMapPoint *point_selected;
+	EMapPoint *point_hover;
+
+	EMap *map;
+
+	/* The timeout used to flash the nearest point. */
+	guint timeout_id;
+
+	/* Widgets from the UI file */
+	GtkWidget *app;
+	GtkWidget *table;
+	GtkWidget *map_window;
+	GtkWidget *timezone_combo;
+	GtkWidget *preview_label;
+};
+
+static void e_timezone_dialog_dispose		(GObject	*object);
+
+static gboolean get_widgets			(ETimezoneDialog *etd);
+static gboolean on_map_timeout			(gpointer	 data);
+static gboolean on_map_motion			(GtkWidget	*widget,
+						 GdkEventMotion *event,
+						 gpointer	 data);
+static gboolean on_map_leave			(GtkWidget	*widget,
+						 GdkEventCrossing *event,
+						 gpointer	 data);
+static gboolean on_map_visibility_changed	(GtkWidget	*w,
+						 GdkEventVisibility *event,
+						 gpointer	 data);
+static gboolean on_map_button_pressed		(GtkWidget	*w,
+						 GdkEvent	*button_event,
+						 gpointer	 data);
+
+static icaltimezone * get_zone_from_point	(ETimezoneDialog *etd,
+						 EMapPoint	*point);
+static void	set_map_timezone		(ETimezoneDialog *etd,
+						 icaltimezone    *zone);
+static void	on_combo_changed		(GtkComboBox	*combo,
+						 ETimezoneDialog *etd);
+
+static void timezone_combo_get_active_text	(GtkComboBox *combo,
+						 gchar **zone_name);
+static gboolean timezone_combo_set_active_text	(GtkComboBox *combo,
+						 const gchar *zone_name);
+
+static void	map_destroy_cb			(gpointer data,
+						 GObject *where_object_was);
+
+G_DEFINE_TYPE (ETimezoneDialog, e_timezone_dialog, G_TYPE_OBJECT)
+
+/* Class initialization function for the event editor */
+static void
+e_timezone_dialog_class_init (ETimezoneDialogClass *class)
+{
+	GObjectClass *object_class;
+
+	g_type_class_add_private (class, sizeof (ETimezoneDialogPrivate));
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->dispose  = e_timezone_dialog_dispose;
+}
+
+/* Object initialization function for the event editor */
+static void
+e_timezone_dialog_init (ETimezoneDialog *etd)
+{
+	etd->priv = E_TIMEZONE_DIALOG_GET_PRIVATE (etd);
+}
+
+/* Dispose handler for the event editor */
+static void
+e_timezone_dialog_dispose (GObject *object)
+{
+	ETimezoneDialogPrivate *priv;
+
+	priv = E_TIMEZONE_DIALOG_GET_PRIVATE (object);
+
+	/* Destroy the actual dialog. */
+	if (priv->app != NULL) {
+		gtk_widget_destroy (priv->app);
+		priv->app = NULL;
+	}
+
+	if (priv->timeout_id) {
+		g_source_remove (priv->timeout_id);
+		priv->timeout_id = 0;
+	}
+
+	if (priv->builder) {
+		g_object_unref (priv->builder);
+		priv->builder = NULL;
+	}
+
+	/* Chain up to parent's dispose() method. */
+	G_OBJECT_CLASS (e_timezone_dialog_parent_class)->dispose (object);
+}
+
+static void
+e_timezone_dialog_add_timezones (ETimezoneDialog *etd)
+{
+	ETimezoneDialogPrivate *priv;
+	icalarray *zones;
+	GtkComboBox *combo;
+	GList *l, *list_items = NULL;
+	GtkListStore *list_store;
+	GtkTreeIter iter;
+	GtkCellRenderer *cell;
+	GtkCssProvider *css_provider;
+	GtkStyleContext *style_context;
+	GHashTable *index;
+	const gchar *css;
+	gint i;
+	GError *error = NULL;
+
+	priv = etd->priv;
+
+	/* Get the array of builtin timezones. */
+	zones = icaltimezone_get_builtin_timezones ();
+
+	for (i = 0; i < zones->num_elements; i++) {
+		icaltimezone *zone;
+		gchar *location;
+
+		zone = icalarray_element_at (zones, i);
+
+		location = _(icaltimezone_get_location (zone));
+
+		e_map_add_point (
+			priv->map, location,
+			icaltimezone_get_longitude (zone),
+			icaltimezone_get_latitude (zone),
+			E_TIMEZONE_DIALOG_MAP_POINT_NORMAL_RGBA);
+
+		list_items = g_list_prepend (list_items, location);
+	}
+
+	list_items = g_list_sort (list_items, (GCompareFunc) g_utf8_collate);
+
+	/* Put the "UTC" entry at the top of the combo's list. */
+	list_items = g_list_prepend (list_items, _("UTC"));
+
+	combo = GTK_COMBO_BOX (priv->timezone_combo);
+
+	cell = gtk_cell_renderer_text_new ();
+	gtk_cell_layout_pack_start ((GtkCellLayout *) combo, cell, TRUE);
+	gtk_cell_layout_set_attributes ((GtkCellLayout *) combo, cell, "text", 0, NULL);
+
+	list_store = gtk_list_store_new (1, G_TYPE_STRING);
+	index = g_hash_table_new (g_str_hash, g_str_equal);
+	for (l = list_items, i = 0; l != NULL; l = l->next, ++i) {
+		gtk_list_store_append (list_store, &iter);
+		gtk_list_store_set (list_store, &iter, 0, (gchar *)(l->data), -1);
+		g_hash_table_insert (index, (gchar *)(l->data), GINT_TO_POINTER (i));
+	}
+
+	g_object_set_data_full (
+		G_OBJECT (list_store), "index", index,
+		(GDestroyNotify) g_hash_table_destroy);
+
+	gtk_combo_box_set_model (combo, (GtkTreeModel *) list_store);
+
+	css_provider = gtk_css_provider_new ();
+	css = "GtkComboBox { -GtkComboBox-appears-as-list: 1; }";
+	gtk_css_provider_load_from_data (css_provider, css, -1, &error);
+	style_context = gtk_widget_get_style_context (priv->timezone_combo);
+	if (error == NULL) {
+		gtk_style_context_add_provider (
+			style_context,
+			GTK_STYLE_PROVIDER (css_provider),
+			GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+	} else {
+		g_warning ("%s: %s", G_STRFUNC, error->message);
+		g_clear_error (&error);
+	}
+	g_object_unref (css_provider);
+
+	g_list_free (list_items);
+}
+
+ETimezoneDialog *
+e_timezone_dialog_construct (ETimezoneDialog *etd)
+{
+	ETimezoneDialogPrivate *priv;
+	GtkWidget *widget;
+	GtkWidget *map;
+
+	g_return_val_if_fail (etd != NULL, NULL);
+	g_return_val_if_fail (E_IS_TIMEZONE_DIALOG (etd), NULL);
+
+	priv = etd->priv;
+
+	/* Load the content widgets */
+
+	priv->builder = gtk_builder_new ();
+	e_load_ui_builder_definition (priv->builder, "e-timezone-dialog.ui");
+
+	if (!get_widgets (etd)) {
+		g_message (
+			"%s(): Could not find all widgets in the XML file!",
+			G_STRFUNC);
+		goto error;
+	}
+
+	widget = gtk_dialog_get_content_area (GTK_DIALOG (priv->app));
+	gtk_container_set_border_width (GTK_CONTAINER (widget), 0);
+
+	widget = gtk_dialog_get_action_area (GTK_DIALOG (priv->app));
+	gtk_container_set_border_width (GTK_CONTAINER (widget), 12);
+
+	priv->map = e_map_new ();
+	map = GTK_WIDGET (priv->map);
+
+	g_object_weak_ref (G_OBJECT (map), map_destroy_cb, priv);
+
+	gtk_widget_set_events (
+		map,
+		gtk_widget_get_events (map) |
+		GDK_LEAVE_NOTIFY_MASK |
+		GDK_VISIBILITY_NOTIFY_MASK);
+
+	e_timezone_dialog_add_timezones (etd);
+
+	gtk_container_add (GTK_CONTAINER (priv->map_window), map);
+	gtk_widget_show (map);
+
+	/* Ensure a reasonable minimum amount of map is visible */
+	gtk_widget_set_size_request (priv->map_window, 200, 200);
+
+	g_signal_connect (
+		map, "motion-notify-event",
+		G_CALLBACK (on_map_motion), etd);
+	g_signal_connect (
+		map, "leave-notify-event",
+		G_CALLBACK (on_map_leave), etd);
+	g_signal_connect (
+		map, "visibility-notify-event",
+		G_CALLBACK (on_map_visibility_changed), etd);
+	g_signal_connect (
+		map, "button-press-event",
+		G_CALLBACK (on_map_button_pressed), etd);
+
+	g_signal_connect (
+		priv->timezone_combo, "changed",
+		G_CALLBACK (on_combo_changed), etd);
+
+	return etd;
+
+ error:
+
+	g_object_unref (etd);
+	return NULL;
+}
+
+#if 0
+static gint
+get_local_offset (void)
+{
+	time_t now = time (NULL), t_gmt, t_local;
+	struct tm gmt, local;
+	gint diff;
+
+	gmtime_r (&now, &gmt);
+	localtime_r (&now, &local);
+	t_gmt = mktime (&gmt);
+	t_local = mktime (&local);
+	diff = t_local - t_gmt;
+
+	return diff;
+}
+#endif
+
+static icaltimezone *
+get_local_timezone (void)
+{
+	icaltimezone *zone;
+	gchar *location;
+
+	tzset ();
+	location = e_cal_system_timezone_get_location ();
+
+	if (location)
+		zone = icaltimezone_get_builtin_timezone (location);
+	else
+		zone = icaltimezone_get_utc_timezone ();
+
+	g_free (location);
+
+	return zone;
+}
+
+/* Gets the widgets from the XML file and returns if they are all available.
+ * For the widgets whose values can be simply set with e-dialog-utils, it does
+ * that as well.
+ */
+static gboolean
+get_widgets (ETimezoneDialog *etd)
+{
+	ETimezoneDialogPrivate *priv;
+	GtkBuilder *builder;
+
+	priv = etd->priv;
+	builder = etd->priv->builder;
+
+	priv->app = e_builder_get_widget (builder, "timezone-dialog");
+	priv->map_window = e_builder_get_widget (builder, "map-window");
+	priv->timezone_combo = e_builder_get_widget (builder, "timezone-combo");
+	priv->table = e_builder_get_widget (builder, "timezone-table");
+	priv->preview_label = e_builder_get_widget (builder, "preview-label");
+
+	return (priv->app
+		&& priv->map_window
+		&& priv->timezone_combo
+		&& priv->table
+		&& priv->preview_label);
+}
+
+/**
+ * e_timezone_dialog_new:
+ *
+ * Creates a new event editor dialog.
+ *
+ * Return value: A newly-created event editor dialog, or NULL if the event
+ * editor could not be created.
+ **/
+ETimezoneDialog *
+e_timezone_dialog_new (void)
+{
+	ETimezoneDialog *etd;
+
+	etd = E_TIMEZONE_DIALOG (g_object_new (E_TYPE_TIMEZONE_DIALOG, NULL));
+	return e_timezone_dialog_construct (E_TIMEZONE_DIALOG (etd));
+}
+
+static void
+format_utc_offset (gint utc_offset,
+                   gchar *buffer)
+{
+	const gchar *sign = "+";
+	gint hours, minutes, seconds;
+
+	if (utc_offset < 0) {
+		utc_offset = -utc_offset;
+		sign = "-";
+	}
+
+	hours = utc_offset / 3600;
+	minutes = (utc_offset % 3600) / 60;
+	seconds = utc_offset % 60;
+
+	/* Sanity check. Standard timezone offsets shouldn't be much more
+	 * than 12 hours, and daylight saving shouldn't change it by more
+	 * than a few hours.  (The maximum offset is 15 hours 56 minutes
+	 * at present.) */
+	if (hours < 0 || hours >= 24 || minutes < 0 || minutes >= 60
+	    || seconds < 0 || seconds >= 60) {
+		fprintf (
+			stderr, "Warning: Strange timezone offset: "
+			"H:%i M:%i S:%i\n", hours, minutes, seconds);
+	}
+
+	if (hours == 0 && minutes == 0 && seconds == 0)
+		strcpy (buffer, _("UTC"));
+	else if (seconds == 0)
+		sprintf (
+			buffer, "%s %s%02i:%02i",
+			_("UTC"), sign, hours, minutes);
+	else
+		sprintf (
+			buffer, "%s %s%02i:%02i:%02i",
+			_("UTC"), sign, hours, minutes, seconds);
+}
+
+static gchar *
+zone_display_name_with_offset (icaltimezone *zone)
+{
+	const gchar *display_name;
+	struct tm local;
+	struct icaltimetype tt;
+	gint offset;
+	gchar buffer[100];
+	time_t now = time (NULL);
+
+	gmtime_r ((const time_t *) &now, &local);
+	tt = tm_to_icaltimetype (&local, TRUE);
+	offset = icaltimezone_get_utc_offset (zone, &tt, NULL);
+
+	format_utc_offset (offset, buffer);
+
+	display_name = icaltimezone_get_display_name (zone);
+	if (icaltimezone_get_builtin_timezone (display_name))
+		display_name = _(display_name);
+
+	return g_strdup_printf ("%s (%s)", display_name, buffer);
+}
+
+static const gchar *
+zone_display_name (icaltimezone *zone)
+{
+	const gchar *display_name;
+
+	display_name = icaltimezone_get_display_name (zone);
+	if (icaltimezone_get_builtin_timezone (display_name))
+		display_name = _(display_name);
+
+	return display_name;
+}
+
+/* This flashes the currently selected timezone in the map. */
+static gboolean
+on_map_timeout (gpointer data)
+{
+	ETimezoneDialog *etd;
+	ETimezoneDialogPrivate *priv;
+
+	etd = E_TIMEZONE_DIALOG (data);
+	priv = etd->priv;
+
+	if (!priv->point_selected)
+		return TRUE;
+
+	if (e_map_point_get_color_rgba (priv->point_selected)
+	    == E_TIMEZONE_DIALOG_MAP_POINT_SELECTED_1_RGBA)
+		e_map_point_set_color_rgba (
+			priv->map, priv->point_selected,
+			E_TIMEZONE_DIALOG_MAP_POINT_SELECTED_2_RGBA);
+	else
+		e_map_point_set_color_rgba (
+			priv->map, priv->point_selected,
+			E_TIMEZONE_DIALOG_MAP_POINT_SELECTED_1_RGBA);
+
+	return TRUE;
+}
+
+static gboolean
+on_map_motion (GtkWidget *widget,
+               GdkEventMotion *event,
+               gpointer data)
+{
+	ETimezoneDialog *etd;
+	ETimezoneDialogPrivate *priv;
+	gdouble longitude, latitude;
+	icaltimezone *new_zone;
+	gchar *display = NULL;
+
+	etd = E_TIMEZONE_DIALOG (data);
+	priv = etd->priv;
+
+	e_map_window_to_world (
+		priv->map, (gdouble) event->x, (gdouble) event->y,
+		&longitude, &latitude);
+
+	if (priv->point_hover && priv->point_hover != priv->point_selected)
+		e_map_point_set_color_rgba (
+			priv->map, priv->point_hover,
+			E_TIMEZONE_DIALOG_MAP_POINT_NORMAL_RGBA);
+
+	priv->point_hover = e_map_get_closest_point (
+		priv->map, longitude,
+		latitude, TRUE);
+
+	if (priv->point_hover != priv->point_selected)
+		e_map_point_set_color_rgba (
+			priv->map, priv->point_hover,
+			E_TIMEZONE_DIALOG_MAP_POINT_HOVER_RGBA);
+
+	new_zone = get_zone_from_point (etd, priv->point_hover);
+
+	display = zone_display_name_with_offset (new_zone);
+	gtk_label_set_text (GTK_LABEL (priv->preview_label), display);
+
+	g_free (display);
+
+	return TRUE;
+}
+
+static gboolean
+on_map_leave (GtkWidget *widget,
+              GdkEventCrossing *event,
+              gpointer data)
+{
+	ETimezoneDialog *etd;
+	ETimezoneDialogPrivate *priv;
+
+	etd = E_TIMEZONE_DIALOG (data);
+	priv = etd->priv;
+
+	/* We only want to reset the hover point if this is a normal leave
+	 * event. For some reason we are getting leave events when the
+	 * button is pressed in the map, which causes problems. */
+	if (event->mode != GDK_CROSSING_NORMAL)
+		return FALSE;
+
+	if (priv->point_hover && priv->point_hover != priv->point_selected)
+		e_map_point_set_color_rgba (
+			priv->map, priv->point_hover,
+			E_TIMEZONE_DIALOG_MAP_POINT_NORMAL_RGBA);
+
+	timezone_combo_set_active_text (
+		GTK_COMBO_BOX (priv->timezone_combo),
+		zone_display_name (priv->zone));
+	gtk_label_set_text (GTK_LABEL (priv->preview_label), "");
+
+	priv->point_hover = NULL;
+
+	return FALSE;
+}
+
+static gboolean
+on_map_visibility_changed (GtkWidget *w,
+                           GdkEventVisibility *event,
+                           gpointer data)
+{
+	ETimezoneDialog *etd;
+	ETimezoneDialogPrivate *priv;
+
+	etd = E_TIMEZONE_DIALOG (data);
+	priv = etd->priv;
+
+	if (event->state != GDK_VISIBILITY_FULLY_OBSCURED) {
+		/* Map is visible, at least partly, so make sure we flash the
+		 * selected point. */
+		if (!priv->timeout_id)
+			priv->timeout_id = g_timeout_add (100, on_map_timeout, etd);
+	} else {
+		/* Map is invisible, so don't waste resources on the timeout.*/
+		if (priv->timeout_id) {
+			g_source_remove (priv->timeout_id);
+			priv->timeout_id = 0;
+		}
+	}
+
+	return FALSE;
+}
+
+static gboolean
+on_map_button_pressed (GtkWidget *w,
+                       GdkEvent *button_event,
+                       gpointer data)
+{
+	ETimezoneDialog *etd;
+	ETimezoneDialogPrivate *priv;
+	guint event_button = 0;
+	gdouble event_x_win = 0;
+	gdouble event_y_win = 0;
+	gdouble longitude, latitude;
+
+	etd = E_TIMEZONE_DIALOG (data);
+	priv = etd->priv;
+
+	gdk_event_get_button (button_event, &event_button);
+	gdk_event_get_coords (button_event, &event_x_win, &event_y_win);
+
+	e_map_window_to_world (
+		priv->map, event_x_win, event_y_win, &longitude, &latitude);
+
+	if (event_button != 1) {
+		e_map_zoom_out (priv->map);
+	} else {
+		if (e_map_get_magnification (priv->map) <= 1.0)
+			e_map_zoom_to_location (
+				priv->map, longitude, latitude);
+
+		if (priv->point_selected)
+			e_map_point_set_color_rgba (
+				priv->map,
+				priv->point_selected,
+				E_TIMEZONE_DIALOG_MAP_POINT_NORMAL_RGBA);
+		priv->point_selected = priv->point_hover;
+
+		priv->zone = get_zone_from_point (etd, priv->point_selected);
+		timezone_combo_set_active_text (
+			GTK_COMBO_BOX (priv->timezone_combo),
+			zone_display_name (priv->zone));
+	}
+
+	return TRUE;
+}
+
+/* Returns the translated timezone location of the given EMapPoint,
+ * e.g. "Europe/London". */
+static icaltimezone *
+get_zone_from_point (ETimezoneDialog *etd,
+                     EMapPoint *point)
+{
+	icalarray *zones;
+	gdouble longitude, latitude;
+	gint i;
+
+	if (point == NULL)
+		return NULL;
+
+	e_map_point_get_location (point, &longitude, &latitude);
+
+	/* Get the array of builtin timezones. */
+	zones = icaltimezone_get_builtin_timezones ();
+
+	for (i = 0; i < zones->num_elements; i++) {
+		icaltimezone *zone;
+		gdouble zone_longitude, zone_latitude;
+
+		zone = icalarray_element_at (zones, i);
+		zone_longitude = icaltimezone_get_longitude (zone);
+		zone_latitude = icaltimezone_get_latitude (zone);
+
+		if (zone_longitude - 0.005 <= longitude &&
+		    zone_longitude + 0.005 >= longitude &&
+		    zone_latitude - 0.005 <= latitude &&
+		    zone_latitude + 0.005 >= latitude)
+		{
+			return zone;
+		}
+	}
+
+	g_return_val_if_reached (NULL);
+}
+
+/**
+ * e_timezone_dialog_get_timezone:
+ * @etd: the timezone dialog
+ *
+ * Return value: the currently-selected timezone, or %NULL if no timezone
+ * is selected.
+ **/
+icaltimezone *
+e_timezone_dialog_get_timezone (ETimezoneDialog *etd)
+{
+	ETimezoneDialogPrivate *priv;
+
+	g_return_val_if_fail (E_IS_TIMEZONE_DIALOG (etd), NULL);
+
+	priv = etd->priv;
+
+	return priv->zone;
+}
+
+/**
+ * e_timezone_dialog_set_timezone:
+ * @etd: the timezone dialog
+ * @zone: the timezone
+ *
+ * Sets the timezone of @etd to @zone. Updates the display name and
+ * selected location. The caller must ensure that @zone is not freed
+ * before @etd is destroyed.
+ **/
+
+void
+e_timezone_dialog_set_timezone (ETimezoneDialog *etd,
+                                icaltimezone *zone)
+{
+	ETimezoneDialogPrivate *priv;
+	gchar *display = NULL;
+
+	g_return_if_fail (E_IS_TIMEZONE_DIALOG (etd));
+
+	if (!zone)
+		zone = get_local_timezone ();
+
+	if (zone)
+		display = zone_display_name_with_offset (zone);
+
+	priv = etd->priv;
+
+	priv->zone = zone;
+
+	gtk_label_set_text (
+		GTK_LABEL (priv->preview_label),
+		zone ? display : "");
+	timezone_combo_set_active_text (
+		GTK_COMBO_BOX (priv->timezone_combo),
+		zone ? zone_display_name (zone) : "");
+
+	set_map_timezone (etd, zone);
+	g_free (display);
+}
+
+GtkWidget *
+e_timezone_dialog_get_toplevel (ETimezoneDialog *etd)
+{
+	ETimezoneDialogPrivate *priv;
+
+	g_return_val_if_fail (etd != NULL, NULL);
+	g_return_val_if_fail (E_IS_TIMEZONE_DIALOG (etd), NULL);
+
+	priv = etd->priv;
+
+	return priv->app;
+}
+
+static void
+set_map_timezone (ETimezoneDialog *etd,
+                  icaltimezone *zone)
+{
+	ETimezoneDialogPrivate *priv;
+	EMapPoint *point;
+	gdouble zone_longitude, zone_latitude;
+
+	priv = etd->priv;
+
+	if (zone) {
+		zone_longitude = icaltimezone_get_longitude (zone);
+		zone_latitude = icaltimezone_get_latitude (zone);
+		point = e_map_get_closest_point (
+			priv->map,
+			zone_longitude,
+			zone_latitude,
+			FALSE);
+	} else
+		point = NULL;
+
+	if (priv->point_selected)
+		e_map_point_set_color_rgba (
+			priv->map, priv->point_selected,
+			E_TIMEZONE_DIALOG_MAP_POINT_NORMAL_RGBA);
+
+	priv->point_selected = point;
+}
+
+static void
+on_combo_changed (GtkComboBox *combo_box,
+                  ETimezoneDialog *etd)
+{
+	ETimezoneDialogPrivate *priv;
+	gchar *new_zone_name;
+	icalarray *zones;
+	icaltimezone *map_zone = NULL;
+	gchar *location;
+	gint i;
+
+	priv = etd->priv;
+
+	timezone_combo_get_active_text (
+		GTK_COMBO_BOX (priv->timezone_combo), &new_zone_name);
+
+	if (!new_zone_name || !*new_zone_name)
+		priv->zone = NULL;
+	else if (!g_utf8_collate (new_zone_name, _("UTC")))
+		priv->zone = icaltimezone_get_utc_timezone ();
+	else {
+		priv->zone = NULL;
+
+		zones = icaltimezone_get_builtin_timezones ();
+		for (i = 0; i < zones->num_elements; i++) {
+			map_zone = icalarray_element_at (zones, i);
+			location = _(icaltimezone_get_location (map_zone));
+			if (!g_utf8_collate (new_zone_name, location)) {
+				priv->zone = map_zone;
+				break;
+			}
+		}
+	}
+
+	set_map_timezone (etd, map_zone);
+
+	g_free (new_zone_name);
+}
+
+static void
+timezone_combo_get_active_text (GtkComboBox *combo,
+                                gchar **zone_name)
+{
+	GtkTreeModel *list_store;
+	GtkTreeIter iter;
+
+	list_store = gtk_combo_box_get_model (combo);
+
+	/* Get the active iter in the list */
+	if (gtk_combo_box_get_active_iter (combo, &iter))
+		gtk_tree_model_get (list_store, &iter, 0, zone_name, -1);
+	else
+		*zone_name = NULL;
+}
+
+static gboolean
+timezone_combo_set_active_text (GtkComboBox *combo,
+                                const gchar *zone_name)
+{
+	GtkTreeModel *list_store;
+	GHashTable *index;
+	gpointer id = NULL;
+
+	list_store = gtk_combo_box_get_model (combo);
+	index = (GHashTable *) g_object_get_data (G_OBJECT (list_store), "index");
+
+	if (zone_name && *zone_name)
+		id = g_hash_table_lookup (index, zone_name);
+
+	gtk_combo_box_set_active (combo, GPOINTER_TO_INT (id));
+
+	return (id != NULL);
+}
+
+static void
+map_destroy_cb (gpointer data,
+                GObject *where_object_was)
+{
+
+	ETimezoneDialogPrivate *priv = data;
+	if (priv->timeout_id) {
+		g_source_remove (priv->timeout_id);
+		priv->timeout_id = 0;
+	}
+	return;
+}
diff --git a/e-util/e-timezone-dialog.h b/e-util/e-timezone-dialog.h
new file mode 100644
index 0000000..df87e80
--- /dev/null
+++ b/e-util/e-timezone-dialog.h
@@ -0,0 +1,77 @@
+/*
+ * Evolution calendar - Timezone selector dialog
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Damon Chaplin <damon ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_TIMEZONE_DIALOG_H
+#define E_TIMEZONE_DIALOG_H
+
+#include <gtk/gtk.h>
+#include <libical/ical.h>
+
+/* Standard GObject macros */
+#define E_TYPE_TIMEZONE_DIALOG \
+	(e_timezone_dialog_get_type ())
+#define E_TIMEZONE_DIALOG(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_TIMEZONE_DIALOG, ETimezoneDialog))
+#define E_TIMEZONE_DIALOG_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_TIMEZONE_DIALOG, ETimezoneDialogClass))
+#define E_IS_TIMEZONE_DIALOG(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_TIMEZONE_DIALOG))
+#define E_IS_TIMEZONE_DIALOG_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_TIMEZONE_DIALOG))
+#define E_TIMEZONE_DIALOG_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_TIMEZONE_DIALOG, ETimezoneDialogClass))
+
+typedef struct _ETimezoneDialog ETimezoneDialog;
+typedef struct _ETimezoneDialogClass ETimezoneDialogClass;
+typedef struct _ETimezoneDialogPrivate ETimezoneDialogPrivate;
+
+struct _ETimezoneDialog {
+	GObject object;
+	ETimezoneDialogPrivate *priv;
+};
+
+struct _ETimezoneDialogClass {
+	GObjectClass parent_class;
+};
+
+GType		e_timezone_dialog_get_type	(void);
+ETimezoneDialog *
+		e_timezone_dialog_construct	(ETimezoneDialog  *etd);
+ETimezoneDialog *
+		e_timezone_dialog_new		(void);
+icaltimezone *	e_timezone_dialog_get_timezone	(ETimezoneDialog *etd);
+void		e_timezone_dialog_set_timezone	(ETimezoneDialog *etd,
+						 icaltimezone *zone);
+GtkWidget *	e_timezone_dialog_get_toplevel	(ETimezoneDialog *etd);
+
+#endif /* E_TIMEZONE_DIALOG_H */
diff --git a/widgets/e-timezone-dialog/e-timezone-dialog.ui b/e-util/e-timezone-dialog.ui
similarity index 100%
rename from widgets/e-timezone-dialog/e-timezone-dialog.ui
rename to e-util/e-timezone-dialog.ui
diff --git a/e-util/e-tree-memory-callbacks.c b/e-util/e-tree-memory-callbacks.c
new file mode 100644
index 0000000..9d2fda6
--- /dev/null
+++ b/e-util/e-tree-memory-callbacks.c
@@ -0,0 +1,314 @@
+/*
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <gtk/gtk.h>
+
+#include "e-tree-memory-callbacks.h"
+
+G_DEFINE_TYPE (ETreeMemoryCallbacks, e_tree_memory_callbacks, E_TYPE_TREE_MEMORY)
+
+static GdkPixbuf *
+etmc_icon_at (ETreeModel *etm,
+              ETreePath node)
+{
+	ETreeMemoryCallbacks *etmc = E_TREE_MEMORY_CALLBACKS (etm);
+
+	return etmc->icon_at (etm, node, etmc->model_data);
+}
+
+static gint
+etmc_column_count (ETreeModel *etm)
+{
+	ETreeMemoryCallbacks *etmc = E_TREE_MEMORY_CALLBACKS (etm);
+
+	if (etmc->column_count)
+		return etmc->column_count (etm, etmc->model_data);
+	else
+		return 0;
+}
+
+static gboolean
+etmc_has_save_id (ETreeModel *etm)
+{
+	ETreeMemoryCallbacks *etmc = E_TREE_MEMORY_CALLBACKS (etm);
+
+	if (etmc->has_save_id)
+		return etmc->has_save_id (etm, etmc->model_data);
+	else
+		return FALSE;
+}
+
+static gchar *
+etmc_get_save_id (ETreeModel *etm,
+                  ETreePath node)
+{
+	ETreeMemoryCallbacks *etmc = E_TREE_MEMORY_CALLBACKS (etm);
+
+	if (etmc->get_save_id)
+		return etmc->get_save_id (etm, node, etmc->model_data);
+	else
+		return NULL;
+}
+
+static gboolean
+etmc_has_get_node_by_id (ETreeModel *etm)
+{
+	ETreeMemoryCallbacks *etmc = E_TREE_MEMORY_CALLBACKS (etm);
+
+	if (etmc->has_get_node_by_id)
+		return etmc->has_get_node_by_id (etm, etmc->model_data);
+	else
+		return FALSE;
+}
+
+static ETreePath
+etmc_get_node_by_id (ETreeModel *etm,
+                     const gchar *save_id)
+{
+	ETreeMemoryCallbacks *etmc = E_TREE_MEMORY_CALLBACKS (etm);
+
+	if (etmc->get_node_by_id)
+		return etmc->get_node_by_id (etm, save_id, etmc->model_data);
+	else
+		return NULL;
+}
+
+static gpointer
+etmc_sort_value_at (ETreeModel *etm,
+                    ETreePath node,
+                    gint col)
+{
+	ETreeMemoryCallbacks *etmc = E_TREE_MEMORY_CALLBACKS (etm);
+
+	if (etmc->sort_value_at)
+		return etmc->sort_value_at (etm, node, col, etmc->model_data);
+	else
+		return etmc->value_at (etm, node, col, etmc->model_data);
+}
+
+static gpointer
+etmc_value_at (ETreeModel *etm,
+               ETreePath node,
+               gint col)
+{
+	ETreeMemoryCallbacks *etmc = E_TREE_MEMORY_CALLBACKS (etm);
+
+	return etmc->value_at (etm, node, col, etmc->model_data);
+}
+
+static void
+etmc_set_value_at (ETreeModel *etm,
+                   ETreePath node,
+                   gint col,
+                   gconstpointer val)
+{
+	ETreeMemoryCallbacks *etmc = E_TREE_MEMORY_CALLBACKS (etm);
+
+	etmc->set_value_at (etm, node, col, val, etmc->model_data);
+}
+
+static gboolean
+etmc_is_editable (ETreeModel *etm,
+                  ETreePath node,
+                  gint col)
+{
+	ETreeMemoryCallbacks *etmc = E_TREE_MEMORY_CALLBACKS (etm);
+
+	return etmc->is_editable (etm, node, col, etmc->model_data);
+}
+
+/* The default for etmc_duplicate_value is to return the raw value. */
+static gpointer
+etmc_duplicate_value (ETreeModel *etm,
+                      gint col,
+                      gconstpointer value)
+{
+	ETreeMemoryCallbacks *etmc = E_TREE_MEMORY_CALLBACKS (etm);
+
+	if (etmc->duplicate_value)
+		return etmc->duplicate_value (etm, col, value, etmc->model_data);
+	else
+		return (gpointer) value;
+}
+
+static void
+etmc_free_value (ETreeModel *etm,
+                 gint col,
+                 gpointer value)
+{
+	ETreeMemoryCallbacks *etmc = E_TREE_MEMORY_CALLBACKS (etm);
+
+	if (etmc->free_value)
+		etmc->free_value (etm, col, value, etmc->model_data);
+}
+
+static gpointer
+etmc_initialize_value (ETreeModel *etm,
+                       gint col)
+{
+	ETreeMemoryCallbacks *etmc = E_TREE_MEMORY_CALLBACKS (etm);
+
+	if (etmc->initialize_value)
+		return etmc->initialize_value (etm, col, etmc->model_data);
+	else
+		return NULL;
+}
+
+static gboolean
+etmc_value_is_empty (ETreeModel *etm,
+                     gint col,
+                     gconstpointer value)
+{
+	ETreeMemoryCallbacks *etmc = E_TREE_MEMORY_CALLBACKS (etm);
+
+	if (etmc->value_is_empty)
+		return etmc->value_is_empty (etm, col, value, etmc->model_data);
+	else
+		return FALSE;
+}
+
+static gchar *
+etmc_value_to_string (ETreeModel *etm,
+                      gint col,
+                      gconstpointer value)
+{
+	ETreeMemoryCallbacks *etmc = E_TREE_MEMORY_CALLBACKS (etm);
+
+	if (etmc->value_to_string)
+		return etmc->value_to_string (etm, col, value, etmc->model_data);
+	else
+		return g_strdup ("");
+}
+
+static void
+e_tree_memory_callbacks_class_init (ETreeMemoryCallbacksClass *class)
+{
+	ETreeModelClass *model_class        = E_TREE_MODEL_CLASS (class);
+
+	model_class->icon_at            = etmc_icon_at;
+
+	model_class->column_count       = etmc_column_count;
+
+	model_class->has_save_id        = etmc_has_save_id;
+	model_class->get_save_id        = etmc_get_save_id;
+
+	model_class->has_get_node_by_id = etmc_has_get_node_by_id;
+	model_class->get_node_by_id     = etmc_get_node_by_id;
+
+	model_class->sort_value_at	= etmc_sort_value_at;
+	model_class->value_at           = etmc_value_at;
+	model_class->set_value_at       = etmc_set_value_at;
+	model_class->is_editable        = etmc_is_editable;
+
+	model_class->duplicate_value    = etmc_duplicate_value;
+	model_class->free_value         = etmc_free_value;
+	model_class->initialize_value   = etmc_initialize_value;
+	model_class->value_is_empty     = etmc_value_is_empty;
+	model_class->value_to_string    = etmc_value_to_string;
+}
+
+static void
+e_tree_memory_callbacks_init (ETreeMemoryCallbacks *etmc)
+{
+	/* nothing to do */
+}
+
+/**
+ * e_tree_memory_callbacks_new:
+ *
+ * This initializes a new ETreeMemoryCallbacksModel object.
+ * ETreeMemoryCallbacksModel is an implementaiton of the somewhat
+ * abstract class ETreeMemory.  The ETreeMemoryCallbacksModel is
+ * designed to allow people to easily create ETreeMemorys without
+ * having to create a new GType derived from ETreeMemory every time
+ * they need one.
+ *
+ * Instead, ETreeMemoryCallbacksModel uses a setup based in callback functions, every
+ * callback function signature mimics the signature of each ETreeModel method
+ * and passes the extra @data pointer to each one of the method to provide them
+ * with any context they might want to use.
+ *
+ * ETreeMemoryCallbacks is to ETreeMemory as ETableSimple is to ETableModel.
+ *
+ * Return value: An ETreeMemoryCallbacks object (which is also an
+ * ETreeMemory and thus an ETreeModel object).
+ *
+ */
+ETreeModel *
+e_tree_memory_callbacks_new (ETreeMemoryCallbacksIconAtFn icon_at,
+
+                             ETreeMemoryCallbacksColumnCountFn column_count,
+
+                             ETreeMemoryCallbacksHasSaveIdFn has_save_id,
+                             ETreeMemoryCallbacksGetSaveIdFn get_save_id,
+
+                             ETreeMemoryCallbacksHasGetNodeByIdFn has_get_node_by_id,
+                             ETreeMemoryCallbacksGetNodeByIdFn get_node_by_id,
+
+                             ETreeMemoryCallbacksValueAtFn sort_value_at,
+                             ETreeMemoryCallbacksValueAtFn value_at,
+                             ETreeMemoryCallbacksSetValueAtFn set_value_at,
+                             ETreeMemoryCallbacksIsEditableFn is_editable,
+
+                             ETreeMemoryCallbacksDuplicateValueFn duplicate_value,
+                             ETreeMemoryCallbacksFreeValueFn free_value,
+                             ETreeMemoryCallbacksInitializeValueFn initialize_value,
+                             ETreeMemoryCallbacksValueIsEmptyFn value_is_empty,
+                             ETreeMemoryCallbacksValueToStringFn value_to_string,
+
+                             gpointer model_data)
+{
+	ETreeMemoryCallbacks *etmc;
+
+	etmc = g_object_new (E_TYPE_TREE_MEMORY_CALLBACKS, NULL);
+
+	etmc->icon_at            = icon_at;
+
+	etmc->column_count       = column_count;
+
+	etmc->has_save_id        = has_save_id;
+	etmc->get_save_id        = get_save_id;
+
+	etmc->has_get_node_by_id = has_get_node_by_id;
+	etmc->get_node_by_id     = get_node_by_id;
+
+	etmc->sort_value_at	 = sort_value_at;
+	etmc->value_at           = value_at;
+	etmc->set_value_at       = set_value_at;
+	etmc->is_editable        = is_editable;
+
+	etmc->duplicate_value    = duplicate_value;
+	etmc->free_value         = free_value;
+	etmc->initialize_value   = initialize_value;
+	etmc->value_is_empty     = value_is_empty;
+	etmc->value_to_string    = value_to_string;
+
+	etmc->model_data         = model_data;
+
+	return (ETreeModel *) etmc;
+}
+
diff --git a/e-util/e-tree-memory-callbacks.h b/e-util/e-tree-memory-callbacks.h
new file mode 100644
index 0000000..df47e9a
--- /dev/null
+++ b/e-util/e-tree-memory-callbacks.h
@@ -0,0 +1,182 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef _E_TREE_MEMORY_CALLBACKS_H_
+#define _E_TREE_MEMORY_CALLBACKS_H_
+
+#include <e-util/e-tree-memory.h>
+
+/* Standard GObject macros */
+#define E_TYPE_TREE_MEMORY_CALLBACKS \
+	(e_tree_memory_callbacks_get_type ())
+#define E_TREE_MEMORY_CALLBACKS(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_TREE_MEMORY_CALLBACKS, ETreeMemoryCallbacks))
+#define E_TREE_MEMORY_CALLBACKS_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_TREE_MEMORY_CALLBACKS, ETreeMemoryCallbacksClass))
+#define E_IS_TREE_MEMORY_CALLBACKS(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_TREE_MEMORY_CALLBACKS))
+#define E_IS_TREE_MEMORY_CALLBACKS_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_TREE_MEMORY_CALLBACKS))
+#define E_TREE_MEMORY_CALLBACKS_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_TREE_MEMORY_CALLBACKS, ETreeMemoryCallbacksClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ETreeMemoryCallbacks ETreeMemoryCallbacks;
+typedef struct _ETreeMemoryCallbacksClass ETreeMemoryCallbacksClass;
+
+typedef GdkPixbuf *	(*ETreeMemoryCallbacksIconAtFn)
+						(ETreeModel *etree,
+						 ETreePath path,
+						 gpointer model_data);
+
+typedef gint		(*ETreeMemoryCallbacksColumnCountFn)
+						(ETreeModel *etree,
+						 gpointer model_data);
+
+typedef gboolean	(*ETreeMemoryCallbacksHasSaveIdFn)
+						(ETreeModel *etree,
+						 gpointer model_data);
+typedef gchar *		(*ETreeMemoryCallbacksGetSaveIdFn)
+						(ETreeModel *etree,
+						 ETreePath path,
+						 gpointer model_data);
+
+typedef gboolean	(*ETreeMemoryCallbacksHasGetNodeByIdFn)
+						(ETreeModel *etree,
+						 gpointer model_data);
+typedef ETreePath	(*ETreeMemoryCallbacksGetNodeByIdFn)
+						(ETreeModel *etree,
+						 const gchar *save_id,
+						 gpointer model_data);
+
+typedef gpointer	(*ETreeMemoryCallbacksValueAtFn)
+						(ETreeModel *etree,
+						 ETreePath path,
+						 gint col,
+						 gpointer model_data);
+typedef void		(*ETreeMemoryCallbacksSetValueAtFn)
+						(ETreeModel *etree,
+						 ETreePath path,
+						 gint col,
+						 gconstpointer val,
+						 gpointer model_data);
+typedef gboolean	(*ETreeMemoryCallbacksIsEditableFn)
+						(ETreeModel *etree,
+						 ETreePath path,
+						 gint col,
+						 gpointer model_data);
+
+typedef gpointer	(*ETreeMemoryCallbacksDuplicateValueFn)
+						(ETreeModel *etm,
+						 gint col,
+						 gconstpointer val,
+						 gpointer data);
+typedef void		(*ETreeMemoryCallbacksFreeValueFn)
+						(ETreeModel *etm,
+						 gint col,
+						 gpointer val,
+						 gpointer data);
+typedef gpointer	(*ETreeMemoryCallbacksInitializeValueFn)
+						(ETreeModel *etm,
+						 gint col,
+						 gpointer data);
+typedef gboolean	(*ETreeMemoryCallbacksValueIsEmptyFn)
+						(ETreeModel *etm,
+						 gint col,
+						 gconstpointer val,
+						 gpointer data);
+typedef gchar *		(*ETreeMemoryCallbacksValueToStringFn)
+						(ETreeModel *etm,
+						 gint col,
+						 gconstpointer val,
+						 gpointer data);
+
+struct _ETreeMemoryCallbacks {
+	ETreeMemory parent;
+
+	ETreeMemoryCallbacksIconAtFn icon_at;
+
+	ETreeMemoryCallbacksColumnCountFn     column_count;
+
+	ETreeMemoryCallbacksHasSaveIdFn       has_save_id;
+	ETreeMemoryCallbacksGetSaveIdFn       get_save_id;
+
+	ETreeMemoryCallbacksHasGetNodeByIdFn  has_get_node_by_id;
+	ETreeMemoryCallbacksGetNodeByIdFn     get_node_by_id;
+
+	ETreeMemoryCallbacksValueAtFn         sort_value_at;
+	ETreeMemoryCallbacksValueAtFn         value_at;
+	ETreeMemoryCallbacksSetValueAtFn      set_value_at;
+	ETreeMemoryCallbacksIsEditableFn      is_editable;
+
+	ETreeMemoryCallbacksDuplicateValueFn  duplicate_value;
+	ETreeMemoryCallbacksFreeValueFn       free_value;
+	ETreeMemoryCallbacksInitializeValueFn initialize_value;
+	ETreeMemoryCallbacksValueIsEmptyFn    value_is_empty;
+	ETreeMemoryCallbacksValueToStringFn   value_to_string;
+
+	gpointer model_data;
+};
+
+struct _ETreeMemoryCallbacksClass {
+	ETreeMemoryClass parent_class;
+};
+
+GType		e_tree_memory_callbacks_get_type
+				(void) G_GNUC_CONST;
+ETreeModel *	e_tree_memory_callbacks_new
+				(ETreeMemoryCallbacksIconAtFn icon_at,
+
+				 ETreeMemoryCallbacksColumnCountFn column_count,
+
+				 ETreeMemoryCallbacksHasSaveIdFn has_save_id,
+				 ETreeMemoryCallbacksGetSaveIdFn get_save_id,
+
+				 ETreeMemoryCallbacksHasGetNodeByIdFn has_get_node_by_id,
+				 ETreeMemoryCallbacksGetNodeByIdFn get_node_by_id,
+
+				 ETreeMemoryCallbacksValueAtFn sort_value_at,
+				 ETreeMemoryCallbacksValueAtFn value_at,
+				 ETreeMemoryCallbacksSetValueAtFn set_value_at,
+				 ETreeMemoryCallbacksIsEditableFn is_editable,
+
+				 ETreeMemoryCallbacksDuplicateValueFn duplicate_value,
+				 ETreeMemoryCallbacksFreeValueFn free_value,
+				 ETreeMemoryCallbacksInitializeValueFn initialize_value,
+				 ETreeMemoryCallbacksValueIsEmptyFn value_is_empty,
+				 ETreeMemoryCallbacksValueToStringFn value_to_string,
+
+				 gpointer model_data);
+
+G_END_DECLS
+
+#endif /* _E_TREE_MEMORY_CALLBACKS_H_ */
diff --git a/e-util/e-tree-memory.c b/e-util/e-tree-memory.c
new file mode 100644
index 0000000..0af5d27
--- /dev/null
+++ b/e-util/e-tree-memory.c
@@ -0,0 +1,743 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *		Chris Toshok <toshok ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-tree-memory.h"
+
+#include <stdio.h>
+#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdlib.h>
+
+#include <libxml/parser.h>
+#include <libxml/xmlmemory.h>
+
+#include "e-xml-utils.h"
+
+#define E_TREE_MEMORY_GET_PRIVATE(obj) \
+	(G_TYPE_INSTANCE_GET_PRIVATE \
+	((obj), E_TYPE_TREE_MEMORY, ETreeMemoryPrivate))
+
+G_DEFINE_TYPE (ETreeMemory, e_tree_memory, E_TYPE_TREE_MODEL)
+
+enum {
+	FILL_IN_CHILDREN,
+	LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0, };
+
+typedef struct ETreeMemoryPath ETreeMemoryPath;
+
+struct ETreeMemoryPath {
+	gpointer         node_data;
+
+	guint            children_computed : 1;
+
+	/* parent/child/sibling pointers */
+	ETreeMemoryPath *parent;
+	ETreeMemoryPath *next_sibling;
+	ETreeMemoryPath *prev_sibling;
+	ETreeMemoryPath *first_child;
+	ETreeMemoryPath *last_child;
+
+	gint             num_children;
+};
+
+struct _ETreeMemoryPrivate {
+	ETreeMemoryPath *root;
+
+	/* whether nodes are created expanded
+	 * or collapsed by default */
+	gboolean         expanded_default;
+
+	gint             frozen;
+	GFunc            destroy_func;
+	gpointer         destroy_user_data;
+};
+
+/* ETreeMemoryPath functions */
+
+static inline void
+check_children (ETreeMemory *memory,
+                ETreePath node)
+{
+	ETreeMemoryPath *path = node;
+	if (!path->children_computed) {
+		g_signal_emit (memory, signals[FILL_IN_CHILDREN], 0, node);
+		path->children_computed = TRUE;
+	}
+}
+
+static gint
+e_tree_memory_path_depth (ETreeMemoryPath *path)
+{
+	gint depth = 0;
+
+	g_return_val_if_fail (path != NULL, -1);
+
+	for (path = path->parent; path; path = path->parent)
+		depth++;
+	return depth;
+}
+
+static void
+e_tree_memory_path_insert (ETreeMemoryPath *parent,
+                           gint position,
+                           ETreeMemoryPath *child)
+{
+	g_return_if_fail (position <= parent->num_children && position >= -1);
+
+	child->parent = parent;
+
+	if (parent->first_child == NULL)
+		parent->first_child = child;
+
+	if (position == -1 || position == parent->num_children) {
+		child->prev_sibling = parent->last_child;
+		if (parent->last_child)
+			parent->last_child->next_sibling = child;
+		parent->last_child = child;
+	} else {
+		ETreeMemoryPath *c;
+		for (c = parent->first_child; c; c = c->next_sibling) {
+			if (position == 0) {
+				child->next_sibling = c;
+				child->prev_sibling = c->prev_sibling;
+
+				if (child->next_sibling)
+					child->next_sibling->prev_sibling = child;
+				if (child->prev_sibling)
+					child->prev_sibling->next_sibling = child;
+
+				if (parent->first_child == c)
+					parent->first_child = child;
+				break;
+			}
+			position--;
+		}
+	}
+
+	parent->num_children++;
+}
+
+static void
+e_tree_path_unlink (ETreeMemoryPath *path)
+{
+	ETreeMemoryPath *parent = path->parent;
+
+	/* unlink first/last child if applicable */
+	if (parent) {
+		if (path == parent->first_child)
+			parent->first_child = path->next_sibling;
+		if (path == parent->last_child)
+			parent->last_child = path->prev_sibling;
+
+		parent->num_children--;
+	}
+
+	/* unlink prev/next sibling links */
+	if (path->next_sibling)
+		path->next_sibling->prev_sibling = path->prev_sibling;
+	if (path->prev_sibling)
+		path->prev_sibling->next_sibling = path->next_sibling;
+
+	path->parent = NULL;
+	path->next_sibling = NULL;
+	path->prev_sibling = NULL;
+}
+
+/**
+ * e_tree_memory_freeze:
+ * @etmm: the ETreeModel to freeze.
+ *
+ * This function prepares an ETreeModel for a period of much change.
+ * All signals regarding changes to the tree are deferred until we
+ * thaw the tree.
+ *
+ **/
+void
+e_tree_memory_freeze (ETreeMemory *etmm)
+{
+	ETreeMemoryPrivate *priv = etmm->priv;
+
+	if (priv->frozen == 0)
+		e_tree_model_pre_change (E_TREE_MODEL (etmm));
+
+	priv->frozen++;
+}
+
+/**
+ * e_tree_memory_thaw:
+ * @etmm: the ETreeMemory to thaw.
+ *
+ * This function thaws an ETreeMemory.  All the defered signals can add
+ * up to a lot, we don't know - so we just emit a model_changed
+ * signal.
+ *
+ **/
+void
+e_tree_memory_thaw (ETreeMemory *etmm)
+{
+	ETreeMemoryPrivate *priv = etmm->priv;
+
+	if (priv->frozen > 0)
+		priv->frozen--;
+	if (priv->frozen == 0) {
+		e_tree_model_node_changed (E_TREE_MODEL (etmm), priv->root);
+	}
+}
+
+/* virtual methods */
+
+static void
+etmm_dispose (GObject *object)
+{
+	ETreeMemoryPrivate *priv;
+
+	priv = E_TREE_MEMORY_GET_PRIVATE (object);
+
+	if (priv->root)
+		e_tree_memory_node_remove (
+			E_TREE_MEMORY (object), priv->root);
+
+	G_OBJECT_CLASS (e_tree_memory_parent_class)->dispose (object);
+}
+
+static ETreePath
+etmm_get_root (ETreeModel *etm)
+{
+	ETreeMemoryPrivate *priv = E_TREE_MEMORY (etm)->priv;
+	return priv->root;
+}
+
+static ETreePath
+etmm_get_parent (ETreeModel *etm,
+                 ETreePath node)
+{
+	ETreeMemoryPath *path = node;
+	return path->parent;
+}
+
+static ETreePath
+etmm_get_first_child (ETreeModel *etm,
+                      ETreePath node)
+{
+	ETreeMemoryPath *path = node;
+
+	check_children (E_TREE_MEMORY (etm), node);
+	return path->first_child;
+}
+
+static ETreePath
+etmm_get_last_child (ETreeModel *etm,
+                     ETreePath node)
+{
+	ETreeMemoryPath *path = node;
+
+	check_children (E_TREE_MEMORY (etm), node);
+	return path->last_child;
+}
+
+static ETreePath
+etmm_get_next (ETreeModel *etm,
+               ETreePath node)
+{
+	ETreeMemoryPath *path = node;
+	return path->next_sibling;
+}
+
+static ETreePath
+etmm_get_prev (ETreeModel *etm,
+               ETreePath node)
+{
+	ETreeMemoryPath *path = node;
+	return path->prev_sibling;
+}
+
+static gboolean
+etmm_is_root (ETreeModel *etm,
+              ETreePath node)
+{
+	ETreeMemoryPath *path = node;
+	return e_tree_memory_path_depth (path) == 0;
+}
+
+static gboolean
+etmm_is_expandable (ETreeModel *etm,
+                    ETreePath node)
+{
+	ETreeMemoryPath *path = node;
+
+	check_children (E_TREE_MEMORY (etm), node);
+	return path->first_child != NULL;
+}
+
+static guint
+etmm_get_children (ETreeModel *etm,
+                   ETreePath node,
+                   ETreePath **nodes)
+{
+	ETreeMemoryPath *path = node;
+	guint n_children;
+
+	check_children (E_TREE_MEMORY (etm), node);
+
+	n_children = path->num_children;
+
+	if (nodes) {
+		ETreeMemoryPath *p;
+		gint i = 0;
+
+		(*nodes) = g_new (ETreePath, n_children);
+		for (p = path->first_child; p; p = p->next_sibling) {
+			(*nodes)[i++] = p;
+		}
+	}
+
+	return n_children;
+}
+
+static guint
+etmm_depth (ETreeModel *etm,
+            ETreePath path)
+{
+	return e_tree_memory_path_depth (path);
+}
+
+static gboolean
+etmm_get_expanded_default (ETreeModel *etm)
+{
+	ETreeMemory *etmm = E_TREE_MEMORY (etm);
+	ETreeMemoryPrivate *priv = etmm->priv;
+
+	return priv->expanded_default;
+}
+
+static void
+etmm_clear_children_computed (ETreeMemoryPath *path)
+{
+	for (path = path->first_child; path; path = path->next_sibling) {
+		path->children_computed = FALSE;
+		etmm_clear_children_computed (path);
+	}
+}
+
+static void
+etmm_node_request_collapse (ETreeModel *etm,
+                            ETreePath node)
+{
+	ETreeModelClass *parent_class;
+
+	if (node)
+		etmm_clear_children_computed (node);
+
+	parent_class = E_TREE_MODEL_CLASS (e_tree_memory_parent_class);
+
+	if (parent_class->node_request_collapse != NULL)
+		parent_class->node_request_collapse (etm, node);
+}
+
+static void
+e_tree_memory_class_init (ETreeMemoryClass *class)
+{
+	GObjectClass *object_class;
+	ETreeModelClass *tree_model_class;
+
+	g_type_class_add_private (class, sizeof (ETreeMemoryPrivate));
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->dispose = etmm_dispose;
+
+	tree_model_class = E_TREE_MODEL_CLASS (class);
+	tree_model_class->get_root = etmm_get_root;
+	tree_model_class->get_prev = etmm_get_prev;
+	tree_model_class->get_next = etmm_get_next;
+	tree_model_class->get_first_child = etmm_get_first_child;
+	tree_model_class->get_last_child = etmm_get_last_child;
+	tree_model_class->get_parent = etmm_get_parent;
+
+	tree_model_class->is_root = etmm_is_root;
+	tree_model_class->is_expandable = etmm_is_expandable;
+	tree_model_class->get_children = etmm_get_children;
+	tree_model_class->depth = etmm_depth;
+	tree_model_class->get_expanded_default = etmm_get_expanded_default;
+
+	tree_model_class->node_request_collapse = etmm_node_request_collapse;
+
+	class->fill_in_children = NULL;
+
+	signals[FILL_IN_CHILDREN] = g_signal_new (
+		"fill_in_children",
+		G_TYPE_FROM_CLASS (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ETreeMemoryClass, fill_in_children),
+		(GSignalAccumulator) NULL, NULL,
+		g_cclosure_marshal_VOID__POINTER,
+		G_TYPE_NONE, 1,
+		G_TYPE_POINTER);
+}
+
+static void
+e_tree_memory_init (ETreeMemory *etmm)
+{
+	etmm->priv = E_TREE_MEMORY_GET_PRIVATE (etmm);
+}
+
+/**
+ * e_tree_memory_construct:
+ * @etree:
+ *
+ *
+ **/
+void
+e_tree_memory_construct (ETreeMemory *etmm)
+{
+}
+
+/**
+ * e_tree_memory_new
+ *
+ * XXX docs here.
+ *
+ * return values: a newly constructed ETreeMemory.
+ */
+ETreeMemory *
+e_tree_memory_new (void)
+{
+	return g_object_new (E_TYPE_TREE_MEMORY, NULL);
+}
+
+/**
+ * e_tree_memory_set_expanded_default
+ *
+ * Sets the state of nodes to be append to a thread.
+ * They will either be expanded or collapsed, according to
+ * the value of @expanded.
+ */
+void
+e_tree_memory_set_expanded_default (ETreeMemory *etree,
+                                    gboolean expanded)
+{
+	g_return_if_fail (etree != NULL);
+
+	etree->priv->expanded_default = expanded;
+}
+
+/**
+ * e_tree_memory_node_get_data:
+ * @etmm:
+ * @node:
+ *
+ *
+ *
+ * Return value:
+ **/
+gpointer
+e_tree_memory_node_get_data (ETreeMemory *etmm,
+                             ETreePath node)
+{
+	ETreeMemoryPath *path = node;
+
+	g_return_val_if_fail (path, NULL);
+
+	return path->node_data;
+}
+
+/**
+ * e_tree_memory_node_set_data:
+ * @etmm:
+ * @node:
+ * @node_data:
+ *
+ *
+ **/
+void
+e_tree_memory_node_set_data (ETreeMemory *etmm,
+                             ETreePath node,
+                             gpointer node_data)
+{
+	ETreeMemoryPath *path = node;
+
+	g_return_if_fail (path);
+
+	path->node_data = node_data;
+}
+
+/**
+ * e_tree_memory_node_insert:
+ * @tree_model:
+ * @parent_path:
+ * @position:
+ * @node_data:
+ *
+ *
+ *
+ * Return value:
+ **/
+ETreePath
+e_tree_memory_node_insert (ETreeMemory *tree_model,
+                           ETreePath parent_node,
+                           gint position,
+                           gpointer node_data)
+{
+	ETreeMemoryPrivate *priv;
+	ETreeMemoryPath *new_path;
+	ETreeMemoryPath *parent_path = parent_node;
+
+	g_return_val_if_fail (tree_model != NULL, NULL);
+
+	priv = tree_model->priv;
+
+	g_return_val_if_fail (parent_path != NULL || priv->root == NULL, NULL);
+
+	priv = tree_model->priv;
+
+	if (!tree_model->priv->frozen)
+		e_tree_model_pre_change (E_TREE_MODEL (tree_model));
+
+	new_path = g_slice_new0 (ETreeMemoryPath);
+
+	new_path->node_data = node_data;
+	new_path->children_computed = FALSE;
+
+	if (parent_path != NULL) {
+		e_tree_memory_path_insert (parent_path, position, new_path);
+		if (!tree_model->priv->frozen)
+			e_tree_model_node_inserted (
+				E_TREE_MODEL (tree_model),
+				parent_path, new_path);
+	} else {
+		priv->root = new_path;
+		if (!tree_model->priv->frozen)
+			e_tree_model_node_changed (
+				E_TREE_MODEL (tree_model), new_path);
+	}
+
+	return new_path;
+}
+
+ETreePath
+e_tree_memory_node_insert_id (ETreeMemory *etree,
+                              ETreePath parent,
+                              gint position,
+                              gpointer node_data,
+                              gchar *id)
+{
+	return e_tree_memory_node_insert (etree, parent, position, node_data);
+}
+
+/**
+ * e_tree_memory_node_insert_before:
+ * @etree:
+ * @parent:
+ * @sibling:
+ * @node_data:
+ *
+ *
+ *
+ * Return value:
+ **/
+ETreePath
+e_tree_memory_node_insert_before (ETreeMemory *etree,
+                                  ETreePath parent,
+                                  ETreePath sibling,
+                                  gpointer node_data)
+{
+	ETreeMemoryPath *child;
+	ETreeMemoryPath *parent_path = parent;
+	ETreeMemoryPath *sibling_path = sibling;
+	gint position = 0;
+
+	g_return_val_if_fail (etree != NULL, NULL);
+
+	if (sibling != NULL) {
+		for (child = parent_path->first_child; child; child = child->next_sibling) {
+			if (child == sibling_path)
+				break;
+			position++;
+		}
+	} else
+		position = parent_path->num_children;
+	return e_tree_memory_node_insert (etree, parent, position, node_data);
+}
+
+/* just blows away child data, doesn't take into account unlinking/etc */
+static void
+child_free (ETreeMemory *etree,
+            ETreeMemoryPath *node)
+{
+	ETreeMemoryPath *child, *next;
+
+	child = node->first_child;
+	while (child) {
+		next = child->next_sibling;
+		child_free (etree, child);
+		child = next;
+	}
+
+	if (etree->priv->destroy_func) {
+		etree->priv->destroy_func (node->node_data, etree->priv->destroy_user_data);
+	}
+
+	g_slice_free (ETreeMemoryPath, node);
+}
+
+/**
+ * e_tree_memory_node_remove:
+ * @etree:
+ * @path:
+ *
+ *
+ *
+ * Return value:
+ **/
+gpointer
+e_tree_memory_node_remove (ETreeMemory *etree,
+                           ETreePath node)
+{
+	ETreeMemoryPath *path = node;
+	ETreeMemoryPath *parent = path->parent;
+	ETreeMemoryPath *sibling;
+	gpointer ret = path->node_data;
+	gint old_position = 0;
+
+	g_return_val_if_fail (etree != NULL, NULL);
+
+	if (!etree->priv->frozen) {
+		e_tree_model_pre_change (E_TREE_MODEL (etree));
+		for (old_position = 0, sibling = path;
+		     sibling;
+		     old_position++, sibling = sibling->prev_sibling)
+			/* Empty intentionally*/;
+		old_position--;
+	}
+
+	/* unlink this node - we only have to unlink the root node being removed,
+	 * since the others are only references from this node */
+	e_tree_path_unlink (path);
+
+	/*printf("removing %d nodes from position %d\n", visible, base);*/
+	if (!etree->priv->frozen)
+		e_tree_model_node_removed (E_TREE_MODEL (etree), parent, path, old_position);
+
+	child_free (etree, path);
+
+	if (path == etree->priv->root)
+		etree->priv->root = NULL;
+
+	if (!etree->priv->frozen)
+		e_tree_model_node_deleted (E_TREE_MODEL (etree), path);
+
+	return ret;
+}
+
+typedef struct {
+	ETreeMemory *memory;
+	gpointer closure;
+	ETreeMemorySortCallback callback;
+} MemoryAndClosure;
+
+static gint
+sort_callback (gconstpointer data1,
+               gconstpointer data2,
+               gpointer user_data)
+{
+	ETreePath path1 = *(ETreePath *) data1;
+	ETreePath path2 = *(ETreePath *) data2;
+	MemoryAndClosure *mac = user_data;
+	return (*mac->callback) (mac->memory, path1, path2, mac->closure);
+}
+
+void
+e_tree_memory_sort_node (ETreeMemory *etmm,
+                         ETreePath node,
+                         ETreeMemorySortCallback callback,
+                         gpointer user_data)
+{
+	ETreeMemoryPath **children;
+	ETreeMemoryPath *child;
+	gint count;
+	gint i;
+	ETreeMemoryPath *path = node;
+	MemoryAndClosure mac;
+	ETreeMemoryPath *last;
+
+	e_tree_model_pre_change (E_TREE_MODEL (etmm));
+
+	i = 0;
+	for (child = path->first_child; child; child = child->next_sibling)
+		i++;
+
+	children = g_new (ETreeMemoryPath *, i);
+
+	count = i;
+
+	for (child = path->first_child, i = 0;
+	     child;
+	     child = child->next_sibling, i++) {
+		children[i] = child;
+	}
+
+	mac.memory = etmm;
+	mac.closure = user_data;
+	mac.callback = callback;
+
+	g_qsort_with_data (
+		children, count, sizeof (ETreeMemoryPath *),
+		sort_callback, &mac);
+
+	path->first_child = NULL;
+	last = NULL;
+	for (i = 0;
+	     i < count;
+	     i++) {
+		children[i]->prev_sibling = last;
+		if (last)
+			last->next_sibling = children[i];
+		else
+			path->first_child = children[i];
+		last = children[i];
+	}
+	if (last)
+		last->next_sibling = NULL;
+
+	path->last_child = last;
+
+	g_free (children);
+
+	e_tree_model_node_changed (E_TREE_MODEL (etmm), node);
+}
+
+void
+e_tree_memory_set_node_destroy_func (ETreeMemory *etmm,
+                                     GFunc destroy_func,
+                                     gpointer user_data)
+{
+	etmm->priv->destroy_func = destroy_func;
+	etmm->priv->destroy_user_data = user_data;
+}
diff --git a/e-util/e-tree-memory.h b/e-util/e-tree-memory.h
new file mode 100644
index 0000000..3e58952
--- /dev/null
+++ b/e-util/e-tree-memory.h
@@ -0,0 +1,124 @@
+/*
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *		Chris Toshok <toshok ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef _E_TREE_MEMORY_H_
+#define _E_TREE_MEMORY_H_
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#include <e-util/e-tree-model.h>
+
+/* Standard GObject macros */
+#define E_TYPE_TREE_MEMORY \
+	(e_tree_memory_get_type ())
+#define E_TREE_MEMORY(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_TREE_MEMORY, ETreeMemory))
+#define E_TREE_MEMORY_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_TREE_MEMORY, ETreeMemoryClass))
+#define E_IS_TREE_MEMORY(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_TREE_MEMORY))
+#define E_IS_TREE_MEMORY_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_TREE_MEMORY))
+#define E_TREE_MEMORY_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_TREE_MEMORY, ETreeMemoryClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ETreeMemory ETreeMemory;
+typedef struct _ETreeMemoryClass ETreeMemoryClass;
+typedef struct _ETreeMemoryPrivate ETreeMemoryPrivate;
+
+typedef gint	(*ETreeMemorySortCallback)	(ETreeMemory *etmm,
+						 ETreePath path1,
+						 ETreePath path2,
+						 gpointer closure);
+
+struct _ETreeMemory {
+	ETreeModel parent;
+	ETreeMemoryPrivate *priv;
+};
+
+struct _ETreeMemoryClass {
+	ETreeModelClass parent_class;
+
+	/* Signals */
+	void		(*fill_in_children)	(ETreeMemory *model,
+						 ETreePath node);
+};
+
+GType		e_tree_memory_get_type		(void) G_GNUC_CONST;
+void		e_tree_memory_construct		(ETreeMemory *etree);
+ETreeMemory *	e_tree_memory_new		(void);
+
+/* node operations */
+ETreePath	e_tree_memory_node_insert	(ETreeMemory *etree,
+						 ETreePath parent,
+						 gint position,
+						 gpointer node_data);
+ETreePath	e_tree_memory_node_insert_id	(ETreeMemory *etree,
+						 ETreePath parent,
+						 gint position,
+						 gpointer node_data,
+						 gchar *id);
+ETreePath	e_tree_memory_node_insert_before
+						(ETreeMemory *etree,
+						 ETreePath parent,
+						 ETreePath sibling,
+						 gpointer node_data);
+gpointer	e_tree_memory_node_remove	(ETreeMemory *etree,
+						 ETreePath path);
+
+/* Freeze and thaw */
+void		e_tree_memory_freeze		(ETreeMemory *etree);
+void		e_tree_memory_thaw		(ETreeMemory *etree);
+void		e_tree_memory_set_expanded_default
+						(ETreeMemory *etree,
+						 gboolean expanded);
+gpointer	e_tree_memory_node_get_data	(ETreeMemory *etm,
+						 ETreePath node);
+void		e_tree_memory_node_set_data	(ETreeMemory *etm,
+						 ETreePath node,
+						 gpointer node_data);
+void		e_tree_memory_sort_node		(ETreeMemory *etm,
+						 ETreePath node,
+						 ETreeMemorySortCallback callback,
+						 gpointer user_data);
+void		e_tree_memory_set_node_destroy_func
+						(ETreeMemory *etmm,
+						 GFunc destroy_func,
+						 gpointer user_data);
+
+G_END_DECLS
+
+#endif /* _E_TREE_MEMORY_H */
+
diff --git a/e-util/e-tree-model-generator.c b/e-util/e-tree-model-generator.c
new file mode 100644
index 0000000..aff9129
--- /dev/null
+++ b/e-util/e-tree-model-generator.c
@@ -0,0 +1,1345 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/* e-tree-model-generator.c - Model wrapper that permutes underlying rows.
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that 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 program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * Authors: Hans Petter Jansson <hpj novell com>
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <string.h>
+#include <glib/gi18n-lib.h>
+#include "e-tree-model-generator.h"
+
+#define ETMG_DEBUG(x)
+
+#define ITER_IS_VALID(tree_model_generator, iter) \
+	((iter)->stamp == (tree_model_generator)->priv->stamp)
+#define ITER_GET(iter, group, index) \
+	G_STMT_START { \
+	*(group) = (iter)->user_data; \
+	*(index) = GPOINTER_TO_INT ((iter)->user_data2); \
+	} G_STMT_END
+
+#define ITER_SET(tree_model_generator, iter, group, index) \
+	G_STMT_START { \
+	(iter)->stamp = (tree_model_generator)->priv->stamp; \
+	(iter)->user_data = group; \
+	(iter)->user_data2 = GINT_TO_POINTER (index); \
+	} G_STMT_END
+
+#define E_TREE_MODEL_GENERATOR_GET_PRIVATE(obj) \
+	(G_TYPE_INSTANCE_GET_PRIVATE \
+	((obj), E_TYPE_TREE_MODEL_GENERATOR, ETreeModelGeneratorPrivate))
+
+struct _ETreeModelGeneratorPrivate {
+	GtkTreeModel *child_model;
+	GArray *root_nodes;
+	gint stamp;
+
+	ETreeModelGeneratorGenerateFunc generate_func;
+	gpointer generate_func_data;
+
+	ETreeModelGeneratorModifyFunc modify_func;
+	gpointer modify_func_data;
+};
+
+static void e_tree_model_generator_tree_model_init (GtkTreeModelIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (
+	ETreeModelGenerator, e_tree_model_generator, G_TYPE_OBJECT,
+	G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_MODEL, e_tree_model_generator_tree_model_init))
+
+static GtkTreeModelFlags e_tree_model_generator_get_flags       (GtkTreeModel       *tree_model);
+static gint         e_tree_model_generator_get_n_columns   (GtkTreeModel       *tree_model);
+static GType        e_tree_model_generator_get_column_type (GtkTreeModel       *tree_model,
+							    gint                index);
+static gboolean     e_tree_model_generator_get_iter        (GtkTreeModel       *tree_model,
+							    GtkTreeIter        *iter,
+							    GtkTreePath        *path);
+static GtkTreePath *e_tree_model_generator_get_path        (GtkTreeModel       *tree_model,
+							    GtkTreeIter        *iter);
+static void         e_tree_model_generator_get_value       (GtkTreeModel       *tree_model,
+							    GtkTreeIter        *iter,
+							    gint                column,
+							    GValue             *value);
+static gboolean     e_tree_model_generator_iter_next       (GtkTreeModel       *tree_model,
+							    GtkTreeIter        *iter);
+static gboolean     e_tree_model_generator_iter_children   (GtkTreeModel       *tree_model,
+							    GtkTreeIter        *iter,
+							    GtkTreeIter        *parent);
+static gboolean     e_tree_model_generator_iter_has_child  (GtkTreeModel       *tree_model,
+							    GtkTreeIter        *iter);
+static gint         e_tree_model_generator_iter_n_children (GtkTreeModel       *tree_model,
+							    GtkTreeIter        *iter);
+static gboolean     e_tree_model_generator_iter_nth_child  (GtkTreeModel       *tree_model,
+							    GtkTreeIter        *iter,
+							    GtkTreeIter        *parent,
+							    gint                n);
+static gboolean     e_tree_model_generator_iter_parent     (GtkTreeModel       *tree_model,
+							    GtkTreeIter        *iter,
+							    GtkTreeIter        *child);
+
+static GArray *build_node_map     (ETreeModelGenerator *tree_model_generator, GtkTreeIter *parent_iter,
+				   GArray *parent_group, gint parent_index);
+static void    release_node_map   (GArray *group);
+
+static void    child_row_changed  (ETreeModelGenerator *tree_model_generator, GtkTreePath *path, GtkTreeIter *iter);
+static void    child_row_inserted (ETreeModelGenerator *tree_model_generator, GtkTreePath *path, GtkTreeIter *iter);
+static void    child_row_deleted  (ETreeModelGenerator *tree_model_generator, GtkTreePath *path);
+
+typedef struct {
+	GArray *parent_group;
+	gint    parent_index;
+
+	gint    n_generated;
+	GArray *child_nodes;
+}
+Node;
+
+enum {
+	PROP_0,
+	PROP_CHILD_MODEL
+};
+
+/* ------------------ *
+ * Class/object setup *
+ * ------------------ */
+
+static void
+tree_model_generator_set_property (GObject *object,
+                                   guint prop_id,
+                                   const GValue *value,
+                                   GParamSpec *pspec)
+{
+	ETreeModelGenerator *tree_model_generator = E_TREE_MODEL_GENERATOR (object);
+
+	switch (prop_id)
+	{
+		case PROP_CHILD_MODEL:
+			tree_model_generator->priv->child_model = g_value_get_object (value);
+			g_object_ref (tree_model_generator->priv->child_model);
+
+			if (tree_model_generator->priv->root_nodes)
+				release_node_map (tree_model_generator->priv->root_nodes);
+			tree_model_generator->priv->root_nodes =
+				build_node_map (tree_model_generator, NULL, NULL, -1);
+
+			g_signal_connect_swapped (
+				tree_model_generator->priv->child_model, "row-changed",
+				G_CALLBACK (child_row_changed), tree_model_generator);
+			g_signal_connect_swapped (
+				tree_model_generator->priv->child_model, "row-deleted",
+				G_CALLBACK (child_row_deleted), tree_model_generator);
+			g_signal_connect_swapped (
+				tree_model_generator->priv->child_model, "row-inserted",
+				G_CALLBACK (child_row_inserted), tree_model_generator);
+			break;
+
+		default:
+			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+			break;
+	}
+}
+
+static void
+tree_model_generator_get_property (GObject *object,
+                                   guint prop_id,
+                                   GValue *value,
+                                   GParamSpec *pspec)
+{
+	ETreeModelGenerator *tree_model_generator = E_TREE_MODEL_GENERATOR (object);
+
+	switch (prop_id)
+	{
+		case PROP_CHILD_MODEL:
+			g_value_set_object (value, tree_model_generator->priv->child_model);
+			break;
+
+		default:
+			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+			break;
+	}
+}
+
+static void
+tree_model_generator_finalize (GObject *object)
+{
+	ETreeModelGenerator *tree_model_generator = E_TREE_MODEL_GENERATOR (object);
+
+	if (tree_model_generator->priv->child_model) {
+		g_signal_handlers_disconnect_matched (
+			tree_model_generator->priv->child_model,
+			G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL,
+			tree_model_generator);
+		g_object_unref (tree_model_generator->priv->child_model);
+	}
+
+	if (tree_model_generator->priv->root_nodes)
+		release_node_map (tree_model_generator->priv->root_nodes);
+
+	/* Chain up to parent's finalize() method. */
+	G_OBJECT_CLASS (e_tree_model_generator_parent_class)->finalize (object);
+}
+
+static void
+e_tree_model_generator_class_init (ETreeModelGeneratorClass *class)
+{
+	GObjectClass *object_class;
+
+	g_type_class_add_private (class, sizeof (ETreeModelGeneratorPrivate));
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->get_property = tree_model_generator_get_property;
+	object_class->set_property = tree_model_generator_set_property;
+	object_class->finalize = tree_model_generator_finalize;
+
+	g_object_class_install_property (
+		object_class,
+		PROP_CHILD_MODEL,
+		g_param_spec_object (
+			"child-model",
+			"Child Model",
+			"The child model to extend",
+			G_TYPE_OBJECT,
+			G_PARAM_READWRITE |
+			G_PARAM_CONSTRUCT_ONLY));
+}
+
+static void
+e_tree_model_generator_tree_model_init (GtkTreeModelIface *iface)
+{
+	iface->get_flags       = e_tree_model_generator_get_flags;
+	iface->get_n_columns   = e_tree_model_generator_get_n_columns;
+	iface->get_column_type = e_tree_model_generator_get_column_type;
+	iface->get_iter        = e_tree_model_generator_get_iter;
+	iface->get_path        = e_tree_model_generator_get_path;
+	iface->get_value       = e_tree_model_generator_get_value;
+	iface->iter_next       = e_tree_model_generator_iter_next;
+	iface->iter_children   = e_tree_model_generator_iter_children;
+	iface->iter_has_child  = e_tree_model_generator_iter_has_child;
+	iface->iter_n_children = e_tree_model_generator_iter_n_children;
+	iface->iter_nth_child  = e_tree_model_generator_iter_nth_child;
+	iface->iter_parent     = e_tree_model_generator_iter_parent;
+}
+
+static void
+e_tree_model_generator_init (ETreeModelGenerator *tree_model_generator)
+{
+	tree_model_generator->priv =
+		E_TREE_MODEL_GENERATOR_GET_PRIVATE (tree_model_generator);
+
+	tree_model_generator->priv->stamp      = g_random_int ();
+	tree_model_generator->priv->root_nodes = g_array_new (FALSE, FALSE, sizeof (Node));
+}
+
+/* ------------------ *
+ * Row update helpers *
+ * ------------------ */
+
+static void
+row_deleted (ETreeModelGenerator *tree_model_generator,
+             GtkTreePath *path)
+{
+	g_assert (path);
+
+	ETMG_DEBUG (g_print ("row_deleted emitting\n"));
+	gtk_tree_model_row_deleted (GTK_TREE_MODEL (tree_model_generator), path);
+}
+
+static void
+row_inserted (ETreeModelGenerator *tree_model_generator,
+              GtkTreePath *path)
+{
+	GtkTreeIter iter;
+
+	g_assert (path);
+
+	if (gtk_tree_model_get_iter (GTK_TREE_MODEL (tree_model_generator), &iter, path)) {
+		ETMG_DEBUG (g_print ("row_inserted emitting\n"));
+		gtk_tree_model_row_inserted (GTK_TREE_MODEL (tree_model_generator), path, &iter);
+	} else {
+		ETMG_DEBUG (g_print ("row_inserted could not get iter!\n"));
+	}
+}
+
+static void
+row_changed (ETreeModelGenerator *tree_model_generator,
+             GtkTreePath *path)
+{
+	GtkTreeIter iter;
+
+	g_assert (path);
+
+	if (gtk_tree_model_get_iter (GTK_TREE_MODEL (tree_model_generator), &iter, path)) {
+		ETMG_DEBUG (g_print ("row_changed emitting\n"));
+		gtk_tree_model_row_changed (GTK_TREE_MODEL (tree_model_generator), path, &iter);
+	} else {
+		ETMG_DEBUG (g_print ("row_changed could not get iter!\n"));
+	}
+}
+
+/* -------------------- *
+ * Node map translation *
+ * -------------------- */
+
+static gint
+generated_offset_to_child_offset (GArray *group,
+                                  gint offset,
+                                  gint *internal_offset)
+{
+	gboolean success      = FALSE;
+	gint     accum_offset = 0;
+	gint     i;
+
+	for (i = 0; i < group->len; i++) {
+		Node *node = &g_array_index (group, Node, i);
+
+		accum_offset += node->n_generated;
+		if (accum_offset > offset) {
+			accum_offset -= node->n_generated;
+			success = TRUE;
+			break;
+		}
+	}
+
+	if (!success)
+		return -1;
+
+	if (internal_offset)
+		*internal_offset = offset - accum_offset;
+
+	return i;
+}
+
+static gint
+child_offset_to_generated_offset (GArray *group,
+                                  gint offset)
+{
+	gint accum_offset = 0;
+	gint i;
+
+	g_return_val_if_fail (group != NULL, -1);
+
+	for (i = 0; i < group->len && i < offset; i++) {
+		Node *node = &g_array_index (group, Node, i);
+
+		accum_offset += node->n_generated;
+	}
+
+	return accum_offset;
+}
+
+static gint
+count_generated_nodes (GArray *group)
+{
+	gint accum_offset = 0;
+	gint i;
+
+	for (i = 0; i < group->len; i++) {
+		Node *node = &g_array_index (group, Node, i);
+
+		accum_offset += node->n_generated;
+	}
+
+	return accum_offset;
+}
+
+/* ------------------- *
+ * Node map management *
+ * ------------------- */
+
+static void
+release_node_map (GArray *group)
+{
+	gint i;
+
+	for (i = 0; i < group->len; i++) {
+		Node *node = &g_array_index (group, Node, i);
+
+		if (node->child_nodes)
+			release_node_map (node->child_nodes);
+	}
+
+	g_array_free (group, TRUE);
+}
+
+static gint
+append_node (GArray *group)
+{
+	g_array_set_size (group, group->len + 1);
+	return group->len - 1;
+}
+
+static GArray *
+build_node_map (ETreeModelGenerator *tree_model_generator,
+                GtkTreeIter *parent_iter,
+                GArray *parent_group,
+                gint parent_index)
+{
+	GArray      *group;
+	GtkTreeIter  iter;
+	gboolean     result;
+
+	if (parent_iter)
+		result = gtk_tree_model_iter_children (tree_model_generator->priv->child_model, &iter, parent_iter);
+	else
+		result = gtk_tree_model_get_iter_first (tree_model_generator->priv->child_model, &iter);
+
+	if (!result)
+		return NULL;
+
+	group = g_array_new (FALSE, FALSE, sizeof (Node));
+
+	do {
+		Node *node;
+		gint  i;
+
+		i = append_node (group);
+		node = &g_array_index (group, Node, i);
+
+		node->parent_group = parent_group;
+		node->parent_index = parent_index;
+
+		if (tree_model_generator->priv->generate_func)
+			node->n_generated =
+				tree_model_generator->priv->generate_func (tree_model_generator->priv->child_model,
+								     &iter, tree_model_generator->priv->generate_func_data);
+		else
+			node->n_generated = 1;
+
+		node->child_nodes = build_node_map (tree_model_generator, &iter, group, i);
+	} while (gtk_tree_model_iter_next (tree_model_generator->priv->child_model, &iter));
+
+	return group;
+}
+
+static gint
+get_first_visible_index_from (GArray *group,
+                              gint index)
+{
+	gint i;
+
+	for (i = index; i < group->len; i++) {
+		Node *node = &g_array_index (group, Node, i);
+
+		if (node->n_generated)
+			break;
+	}
+
+	if (i >= group->len)
+		i = -1;
+
+	return i;
+}
+
+static Node *
+get_node_by_child_path (ETreeModelGenerator *tree_model_generator,
+                        GtkTreePath *path,
+                        GArray **node_group)
+{
+	Node   *node = NULL;
+	GArray *group;
+	gint    depth;
+
+	group = tree_model_generator->priv->root_nodes;
+
+	for (depth = 0; depth < gtk_tree_path_get_depth (path); depth++) {
+		gint  index;
+
+		if (!group) {
+			g_warning ("ETreeModelGenerator got unknown child element!");
+			break;
+		}
+
+		index = gtk_tree_path_get_indices (path)[depth];
+		node = &g_array_index (group, Node, index);
+
+		if (depth + 1 < gtk_tree_path_get_depth (path))
+		    group = node->child_nodes;
+	}
+
+	if (!node)
+		group = NULL;
+
+	if (node_group)
+		*node_group = group;
+
+	return node;
+}
+
+static Node *
+create_node_at_child_path (ETreeModelGenerator *tree_model_generator,
+                           GtkTreePath *path)
+{
+	GtkTreePath *parent_path;
+	gint         parent_index;
+	GArray      *parent_group;
+	GArray      *group;
+	gint         index;
+	Node        *node;
+
+	parent_path = gtk_tree_path_copy (path);
+	gtk_tree_path_up (parent_path);
+	node = get_node_by_child_path (tree_model_generator, parent_path, &parent_group);
+
+	if (node) {
+		if (!node->child_nodes)
+			node->child_nodes = g_array_new (FALSE, FALSE, sizeof (Node));
+
+		group = node->child_nodes;
+		parent_index = gtk_tree_path_get_indices (parent_path)[gtk_tree_path_get_depth (parent_path) - 1];
+	} else {
+		if (!tree_model_generator->priv->root_nodes)
+			tree_model_generator->priv->root_nodes = g_array_new (FALSE, FALSE, sizeof (Node));
+
+		group = tree_model_generator->priv->root_nodes;
+		parent_index = -1;
+	}
+
+	gtk_tree_path_free (parent_path);
+
+	index = gtk_tree_path_get_indices (path)[gtk_tree_path_get_depth (path) - 1];
+	ETMG_DEBUG (g_print ("Inserting index %d into group of length %d\n", index, group->len));
+	index = MIN (index, group->len);
+
+	append_node (group);
+
+	if (group->len - 1 - index > 0) {
+		gint i;
+
+		memmove (
+			(Node *) group->data + index + 1,
+			(Node *) group->data + index,
+			(group->len - 1 - index) * sizeof (Node));
+
+		/* Update parent pointers */
+		for (i = index + 1; i < group->len; i++) {
+			Node   *pnode = &g_array_index (group, Node, i);
+			GArray *child_group;
+			gint    j;
+
+			child_group = pnode->child_nodes;
+			if (!child_group)
+				continue;
+
+			for (j = 0; j < child_group->len; j++) {
+				Node *child_node = &g_array_index (child_group, Node, j);
+				child_node->parent_index = i;
+			}
+		}
+	}
+
+	node = &g_array_index (group, Node, index);
+	node->parent_group = parent_group;
+	node->parent_index = parent_index;
+	node->n_generated  = 0;
+	node->child_nodes  = NULL;
+
+	ETMG_DEBUG (
+		g_print ("Created node at offset %d, parent_group = %p, parent_index = %d\n",
+		index, node->parent_group, node->parent_index));
+
+	return node;
+}
+
+ETMG_DEBUG (
+
+static void
+dump_group (GArray *group)
+{
+	gint i;
+
+	g_print ("\nGroup %p:\n", group);
+
+	for (i = 0; i < group->len; i++) {
+		Node *node = &g_array_index (group, Node, i);
+		g_print (
+			"  %04d: pgroup=%p, pindex=%d, n_generated=%d, child_nodes=%p\n",
+			i, node->parent_group, node->parent_index, node->n_generated, node->child_nodes);
+	}
+}
+
+)
+
+static void
+delete_node_at_child_path (ETreeModelGenerator *tree_model_generator,
+                           GtkTreePath *path)
+{
+	GtkTreePath *parent_path;
+	GArray      *parent_group;
+	GArray      *group;
+	gint         index;
+	Node        *node;
+	gint         i;
+
+	parent_path = gtk_tree_path_copy (path);
+	gtk_tree_path_up (parent_path);
+	node = get_node_by_child_path (tree_model_generator, parent_path, &parent_group);
+
+	if (node) {
+		group = node->child_nodes;
+	} else {
+		group = tree_model_generator->priv->root_nodes;
+	}
+
+	gtk_tree_path_free (parent_path);
+
+	if (!group)
+		return;
+
+	index = gtk_tree_path_get_indices (path)[gtk_tree_path_get_depth (path) - 1];
+	if (index >= group->len)
+		return;
+
+	node = &g_array_index (group, Node, index);
+	if (node->child_nodes)
+		release_node_map (node->child_nodes);
+	g_array_remove_index (group, index);
+
+	/* Update parent pointers */
+	for (i = index; i < group->len; i++) {
+		Node   *pnode = &g_array_index (group, Node, i);
+		GArray *child_group;
+		gint    j;
+
+		child_group = pnode->child_nodes;
+		if (!child_group)
+			continue;
+
+		for (j = 0; j < child_group->len; j++) {
+			Node *child_node = &g_array_index (child_group, Node, j);
+			child_node->parent_index = i;
+		}
+	}
+}
+
+static void
+child_row_changed (ETreeModelGenerator *tree_model_generator,
+                   GtkTreePath *path,
+                   GtkTreeIter *iter)
+{
+	GtkTreePath *generated_path;
+	Node        *node;
+	gint         n_generated;
+	gint         i;
+
+	if (tree_model_generator->priv->generate_func)
+		n_generated =
+			tree_model_generator->priv->generate_func (tree_model_generator->priv->child_model,
+							     iter, tree_model_generator->priv->generate_func_data);
+	else
+		n_generated = 1;
+
+	node = get_node_by_child_path (tree_model_generator, path, NULL);
+	if (!node)
+		return;
+
+	generated_path = e_tree_model_generator_convert_child_path_to_path (tree_model_generator, path);
+
+	/* FIXME: Converting the path to an iter every time is inefficient */
+
+	for (i = 0; i < n_generated && i < node->n_generated; i++) {
+		row_changed (tree_model_generator, generated_path);
+		gtk_tree_path_next (generated_path);
+	}
+
+	for (; i < node->n_generated; ) {
+		node->n_generated--;
+		row_deleted (tree_model_generator, generated_path);
+	}
+
+	for (; i < n_generated; i++) {
+		node->n_generated++;
+		row_inserted (tree_model_generator, generated_path);
+		gtk_tree_path_next (generated_path);
+	}
+
+	gtk_tree_path_free (generated_path);
+}
+
+static void
+child_row_inserted (ETreeModelGenerator *tree_model_generator,
+                    GtkTreePath *path,
+                    GtkTreeIter *iter)
+{
+	GtkTreePath *generated_path;
+	Node        *node;
+	gint         n_generated;
+
+	if (tree_model_generator->priv->generate_func)
+		n_generated =
+			tree_model_generator->priv->generate_func (tree_model_generator->priv->child_model,
+							     iter, tree_model_generator->priv->generate_func_data);
+	else
+		n_generated = 1;
+
+	node = create_node_at_child_path (tree_model_generator, path);
+	if (!node)
+		return;
+
+	generated_path = e_tree_model_generator_convert_child_path_to_path (tree_model_generator, path);
+
+	/* FIXME: Converting the path to an iter every time is inefficient */
+
+	for (node->n_generated = 0; node->n_generated < n_generated; ) {
+		node->n_generated++;
+		row_inserted (tree_model_generator, generated_path);
+		gtk_tree_path_next (generated_path);
+	}
+
+	gtk_tree_path_free (generated_path);
+}
+
+static void
+child_row_deleted (ETreeModelGenerator *tree_model_generator,
+                   GtkTreePath *path)
+{
+	GtkTreePath *generated_path;
+	Node        *node;
+
+	node = get_node_by_child_path (tree_model_generator, path, NULL);
+	if (!node)
+		return;
+
+	generated_path = e_tree_model_generator_convert_child_path_to_path (tree_model_generator, path);
+
+	/* FIXME: Converting the path to an iter every time is inefficient */
+
+	for (; node->n_generated; ) {
+		node->n_generated--;
+		row_deleted (tree_model_generator, generated_path);
+	}
+
+	delete_node_at_child_path (tree_model_generator, path);
+	gtk_tree_path_free (generated_path);
+}
+
+/* ----------------------- *
+ * ETreeModelGenerator API *
+ * ----------------------- */
+
+/**
+ * e_tree_model_generator_new:
+ * @child_model: a #GtkTreeModel
+ *
+ * Creates a new #ETreeModelGenerator wrapping @child_model.
+ *
+ * Returns: A new #ETreeModelGenerator.
+ **/
+ETreeModelGenerator *
+e_tree_model_generator_new (GtkTreeModel *child_model)
+{
+	g_return_val_if_fail (GTK_IS_TREE_MODEL (child_model), NULL);
+
+	return E_TREE_MODEL_GENERATOR (
+		g_object_new (E_TYPE_TREE_MODEL_GENERATOR,
+		"child-model", child_model, NULL));
+}
+
+/**
+ * e_tree_model_generator_get_model:
+ * @tree_model_generator: an #ETreeModelGenerator
+ *
+ * Gets the child model being wrapped by @tree_model_generator.
+ *
+ * Returns: A #GtkTreeModel being wrapped.
+ **/
+GtkTreeModel *
+e_tree_model_generator_get_model (ETreeModelGenerator *tree_model_generator)
+{
+	g_return_val_if_fail (E_IS_TREE_MODEL_GENERATOR (tree_model_generator), NULL);
+
+	return tree_model_generator->priv->child_model;
+}
+
+/**
+ * e_tree_model_generator_set_generate_func:
+ * @tree_model_generator: an #ETreeModelGenerator
+ * @func: an #ETreeModelGeneratorGenerateFunc, or %NULL
+ * @data: user data to pass to @func
+ * @destroy:
+ *
+ * Sets the callback function used to filter or generate additional rows
+ * based on the child model's data. This function is called for each child
+ * row, and returns a value indicating the number of rows that will be
+ * used to represent the child row - 0 or more.
+ *
+ * If @func is %NULL, a filtering/generating function will not be applied.
+ **/
+void
+e_tree_model_generator_set_generate_func (ETreeModelGenerator *tree_model_generator,
+                                          ETreeModelGeneratorGenerateFunc func,
+                                          gpointer data,
+                                          GDestroyNotify destroy)
+{
+	g_return_if_fail (E_IS_TREE_MODEL_GENERATOR (tree_model_generator));
+
+	tree_model_generator->priv->generate_func      = func;
+	tree_model_generator->priv->generate_func_data = data;
+}
+
+/**
+ * e_tree_model_generator_set_modify_func:
+ * @tree_model_generator: an #ETreeModelGenerator
+ * @func: an @ETreeModelGeneratorModifyFunc, or %NULL
+ * @data: user data to pass to @func
+ * @destroy:
+ *
+ * Sets the callback function used to override values for the child row's
+ * columns and specify values for generated rows' columns.
+ *
+ * If @func is %NULL, the child model's values will always be used.
+ **/
+void
+e_tree_model_generator_set_modify_func (ETreeModelGenerator *tree_model_generator,
+                                        ETreeModelGeneratorModifyFunc func,
+                                        gpointer data,
+                                        GDestroyNotify destroy)
+{
+	g_return_if_fail (E_IS_TREE_MODEL_GENERATOR (tree_model_generator));
+
+	tree_model_generator->priv->modify_func      = func;
+	tree_model_generator->priv->modify_func_data = data;
+}
+
+/**
+ * e_tree_model_generator_convert_child_path_to_path:
+ * @tree_model_generator: an #ETreeModelGenerator
+ * @child_path: a #GtkTreePath
+ *
+ * Convert a path to a child row to a path to a @tree_model_generator row.
+ *
+ * Returns: A new GtkTreePath, owned by the caller.
+ **/
+GtkTreePath *
+e_tree_model_generator_convert_child_path_to_path (ETreeModelGenerator *tree_model_generator,
+                                                   GtkTreePath *child_path)
+{
+	GtkTreePath *path;
+	GArray      *group;
+	gint         depth;
+
+	g_return_val_if_fail (E_IS_TREE_MODEL_GENERATOR (tree_model_generator), NULL);
+	g_return_val_if_fail (child_path != NULL, NULL);
+
+	path = gtk_tree_path_new ();
+
+	group = tree_model_generator->priv->root_nodes;
+
+	for (depth = 0; depth < gtk_tree_path_get_depth (child_path); depth++) {
+		Node *node;
+		gint  index;
+		gint  generated_index;
+
+		if (!group) {
+			g_warning ("ETreeModelGenerator was asked for path to unknown child element!");
+			break;
+		}
+
+		index = gtk_tree_path_get_indices (child_path)[depth];
+		generated_index = child_offset_to_generated_offset (group, index);
+		node = &g_array_index (group, Node, index);
+		group = node->child_nodes;
+
+		gtk_tree_path_append_index (path, generated_index);
+	}
+
+	return path;
+}
+
+/**
+ * e_tree_model_generator_convert_child_iter_to_iter:
+ * @tree_model_generator: an #ETreeModelGenerator
+ * @generator_iter: a #GtkTreeIter to set
+ * @child_iter: a #GtkTreeIter to convert
+ *
+ * Convert @child_iter to a corresponding #GtkTreeIter for @tree_model_generator,
+ * storing the result in @generator_iter.
+ **/
+void
+e_tree_model_generator_convert_child_iter_to_iter (ETreeModelGenerator *tree_model_generator,
+                                                   GtkTreeIter *generator_iter,
+                                                   GtkTreeIter *child_iter)
+{
+	GtkTreePath *path;
+	GArray      *group;
+	gint         depth;
+	gint         index = 0;
+
+	g_return_if_fail (E_IS_TREE_MODEL_GENERATOR (tree_model_generator));
+
+	path = gtk_tree_model_get_path (tree_model_generator->priv->child_model, child_iter);
+	if (!path)
+		return;
+
+	group = tree_model_generator->priv->root_nodes;
+
+	for (depth = 0; depth < gtk_tree_path_get_depth (path); depth++) {
+		Node *node;
+
+		index = gtk_tree_path_get_indices (path)[depth];
+		node = &g_array_index (group, Node, index);
+
+		if (depth + 1 < gtk_tree_path_get_depth (path))
+			group = node->child_nodes;
+
+		if (!group) {
+			g_warning ("ETreeModelGenerator was asked for iter to unknown child element!");
+			break;
+		}
+	}
+
+	g_return_if_fail (group != NULL);
+
+	index = child_offset_to_generated_offset (group, index);
+	ITER_SET (tree_model_generator, generator_iter, group, index);
+	gtk_tree_path_free (path);
+}
+
+/**
+ * e_tree_model_generator_convert_path_to_child_path:
+ * @tree_model_generator: an #ETreeModelGenerator
+ * @generator_path: a #GtkTreePath to a @tree_model_generator row
+ *
+ * Converts @generator_path to a corresponding #GtkTreePath in the child model.
+ *
+ * Returns: A new #GtkTreePath, owned by the caller.
+ **/
+GtkTreePath *
+e_tree_model_generator_convert_path_to_child_path (ETreeModelGenerator *tree_model_generator,
+                                                   GtkTreePath *generator_path)
+{
+	GtkTreePath *path;
+	GArray      *group;
+	gint         depth;
+
+	g_return_val_if_fail (E_IS_TREE_MODEL_GENERATOR (tree_model_generator), NULL);
+	g_return_val_if_fail (generator_path != NULL, NULL);
+
+	path = gtk_tree_path_new ();
+
+	group = tree_model_generator->priv->root_nodes;
+
+	for (depth = 0; depth < gtk_tree_path_get_depth (generator_path); depth++) {
+		Node *node;
+		gint  index;
+		gint  child_index;
+
+		if (!group) {
+			g_warning ("ETreeModelGenerator was asked for path to unknown child element!");
+			break;
+		}
+
+		index = gtk_tree_path_get_indices (generator_path)[depth];
+		child_index = generated_offset_to_child_offset (group, index, NULL);
+		node = &g_array_index (group, Node, child_index);
+		group = node->child_nodes;
+
+		gtk_tree_path_append_index (path, child_index);
+	}
+
+	return path;
+}
+
+/**
+ * e_tree_model_generator_convert_iter_to_child_iter:
+ * @tree_model_generator: an #ETreeModelGenerator
+ * @child_iter: a #GtkTreeIter to set
+ * @permutation_n: a permutation index to set
+ * @generator_iter: a #GtkTreeIter indicating the row to convert
+ *
+ * Converts a @tree_model_generator row into a child row and permutation index.
+ * The permutation index is the index of the generated row based on this
+ * child row, with the first generated row based on this child row being 0.
+ **/
+void
+e_tree_model_generator_convert_iter_to_child_iter (ETreeModelGenerator *tree_model_generator,
+                                                   GtkTreeIter *child_iter,
+                                                   gint *permutation_n,
+                                                   GtkTreeIter *generator_iter)
+{
+	GtkTreePath *path;
+	GArray      *group;
+	gint         index;
+	gint         internal_offset = 0;
+
+	g_return_if_fail (E_IS_TREE_MODEL_GENERATOR (tree_model_generator));
+	g_return_if_fail (ITER_IS_VALID (tree_model_generator, generator_iter));
+
+	path = gtk_tree_path_new ();
+	ITER_GET (generator_iter, &group, &index);
+
+	index = generated_offset_to_child_offset (group, index, &internal_offset);
+	gtk_tree_path_prepend_index (path, index);
+
+	while (group) {
+		Node *node = &g_array_index (group, Node, index);
+
+		group = node->parent_group;
+		index = node->parent_index;
+
+		if (group)
+			gtk_tree_path_prepend_index (path, index);
+	}
+
+	if (child_iter)
+		gtk_tree_model_get_iter (tree_model_generator->priv->child_model, child_iter, path);
+	if (permutation_n)
+		*permutation_n = internal_offset;
+
+	gtk_tree_path_free (path);
+}
+
+/* ---------------- *
+ * GtkTreeModel API *
+ * ---------------- */
+
+static GtkTreeModelFlags
+e_tree_model_generator_get_flags (GtkTreeModel *tree_model)
+{
+	ETreeModelGenerator *tree_model_generator = E_TREE_MODEL_GENERATOR (tree_model);
+
+	g_return_val_if_fail (E_IS_TREE_MODEL_GENERATOR (tree_model), 0);
+
+	return gtk_tree_model_get_flags (tree_model_generator->priv->child_model);
+}
+
+static gint
+e_tree_model_generator_get_n_columns (GtkTreeModel *tree_model)
+{
+	ETreeModelGenerator *tree_model_generator = E_TREE_MODEL_GENERATOR (tree_model);
+
+	g_return_val_if_fail (E_IS_TREE_MODEL_GENERATOR (tree_model), 0);
+
+	return gtk_tree_model_get_n_columns (tree_model_generator->priv->child_model);
+}
+
+static GType
+e_tree_model_generator_get_column_type (GtkTreeModel *tree_model,
+                                        gint index)
+{
+	ETreeModelGenerator *tree_model_generator = E_TREE_MODEL_GENERATOR (tree_model);
+
+	g_return_val_if_fail (E_IS_TREE_MODEL_GENERATOR (tree_model), G_TYPE_INVALID);
+
+	return gtk_tree_model_get_column_type (tree_model_generator->priv->child_model, index);
+}
+
+static gboolean
+e_tree_model_generator_get_iter (GtkTreeModel *tree_model,
+                                 GtkTreeIter *iter,
+                                 GtkTreePath *path)
+{
+	ETreeModelGenerator *tree_model_generator = E_TREE_MODEL_GENERATOR (tree_model);
+	GArray              *group;
+	gint                 depth;
+	gint                 index = 0;
+
+	g_return_val_if_fail (E_IS_TREE_MODEL_GENERATOR (tree_model), FALSE);
+	g_return_val_if_fail (gtk_tree_path_get_depth (path) > 0, FALSE);
+
+	group = tree_model_generator->priv->root_nodes;
+	if (!group)
+		return FALSE;
+
+	for (depth = 0; depth < gtk_tree_path_get_depth (path); depth++) {
+		Node *node;
+		gint  child_index;
+
+		index = gtk_tree_path_get_indices (path)[depth];
+		child_index = generated_offset_to_child_offset (group, index, NULL);
+		if (child_index < 0)
+			return FALSE;
+
+		node = &g_array_index (group, Node, child_index);
+
+		if (depth + 1 < gtk_tree_path_get_depth (path)) {
+			group = node->child_nodes;
+			if (!group)
+				return FALSE;
+		}
+	}
+
+	ITER_SET (tree_model_generator, iter, group, index);
+	return TRUE;
+}
+
+static GtkTreePath *
+e_tree_model_generator_get_path (GtkTreeModel *tree_model,
+                                 GtkTreeIter *iter)
+{
+	ETreeModelGenerator *tree_model_generator = E_TREE_MODEL_GENERATOR (tree_model);
+	GtkTreePath         *path;
+	GArray              *group;
+	gint                 index;
+
+	g_return_val_if_fail (E_IS_TREE_MODEL_GENERATOR (tree_model), NULL);
+	g_return_val_if_fail (ITER_IS_VALID (tree_model_generator, iter), NULL);
+
+	ITER_GET (iter, &group, &index);
+	path = gtk_tree_path_new ();
+
+	/* FIXME: Converting a path to an iter is a destructive operation, because
+	 * we don't store a node for each generated entry... Doesn't matter for
+	 * lists, not sure about trees. */
+
+	gtk_tree_path_prepend_index (path, index);
+	index = generated_offset_to_child_offset (group, index, NULL);
+
+	while (group) {
+		Node *node = &g_array_index (group, Node, index);
+		gint  generated_index;
+
+		group = node->parent_group;
+		index = node->parent_index;
+		if (group) {
+			generated_index = child_offset_to_generated_offset (group, index);
+			gtk_tree_path_prepend_index (path, generated_index);
+		}
+	}
+
+	return path;
+}
+
+static gboolean
+e_tree_model_generator_iter_next (GtkTreeModel *tree_model,
+                                  GtkTreeIter *iter)
+{
+	ETreeModelGenerator *tree_model_generator = E_TREE_MODEL_GENERATOR (tree_model);
+	Node                *node;
+	GArray              *group;
+	gint                 index;
+	gint                 child_index;
+	gint                 internal_offset = 0;
+
+	g_return_val_if_fail (E_IS_TREE_MODEL_GENERATOR (tree_model), FALSE);
+	g_return_val_if_fail (ITER_IS_VALID (tree_model_generator, iter), FALSE);
+
+	ITER_GET (iter, &group, &index);
+	child_index = generated_offset_to_child_offset (group, index, &internal_offset);
+	node = &g_array_index (group, Node, child_index);
+
+	if (internal_offset + 1 < node->n_generated ||
+	    get_first_visible_index_from (group, child_index + 1) >= 0) {
+		ITER_SET (tree_model_generator, iter, group, index + 1);
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+static gboolean
+e_tree_model_generator_iter_children (GtkTreeModel *tree_model,
+                                      GtkTreeIter *iter,
+                                      GtkTreeIter *parent)
+{
+	ETreeModelGenerator *tree_model_generator = E_TREE_MODEL_GENERATOR (tree_model);
+	Node                *node;
+	GArray              *group;
+	gint                 index;
+
+	g_return_val_if_fail (E_IS_TREE_MODEL_GENERATOR (tree_model), FALSE);
+
+	if (!parent) {
+		if (!tree_model_generator->priv->root_nodes ||
+		    !count_generated_nodes (tree_model_generator->priv->root_nodes))
+			return FALSE;
+
+		ITER_SET (tree_model_generator, iter, tree_model_generator->priv->root_nodes, 0);
+		return TRUE;
+	}
+
+	ITER_GET (parent, &group, &index);
+	index = generated_offset_to_child_offset (group, index, NULL);
+	if (index < 0)
+		return FALSE;
+
+	node = &g_array_index (group, Node, index);
+
+	if (!node->child_nodes)
+		return FALSE;
+
+	if (!count_generated_nodes (node->child_nodes))
+		return FALSE;
+
+	ITER_SET (tree_model_generator, iter, node->child_nodes, 0);
+	return TRUE;
+}
+
+static gboolean
+e_tree_model_generator_iter_has_child (GtkTreeModel *tree_model,
+                                       GtkTreeIter *iter)
+{
+	ETreeModelGenerator *tree_model_generator = E_TREE_MODEL_GENERATOR (tree_model);
+	Node                *node;
+	GArray              *group;
+	gint                 index;
+
+	g_return_val_if_fail (E_IS_TREE_MODEL_GENERATOR (tree_model), FALSE);
+
+	if (iter == NULL) {
+		if (!tree_model_generator->priv->root_nodes ||
+		    !count_generated_nodes (tree_model_generator->priv->root_nodes))
+			return FALSE;
+
+		return TRUE;
+	}
+
+	ITER_GET (iter, &group, &index);
+	index = generated_offset_to_child_offset (group, index, NULL);
+	if (index < 0)
+		return FALSE;
+
+	node = &g_array_index (group, Node, index);
+
+	if (!node->child_nodes)
+		return FALSE;
+
+	if (!count_generated_nodes (node->child_nodes))
+		return FALSE;
+
+	return TRUE;
+}
+
+static gint
+e_tree_model_generator_iter_n_children (GtkTreeModel *tree_model,
+                                        GtkTreeIter *iter)
+{
+	ETreeModelGenerator *tree_model_generator = E_TREE_MODEL_GENERATOR (tree_model);
+	Node                *node;
+	GArray              *group;
+	gint                 index;
+
+	g_return_val_if_fail (E_IS_TREE_MODEL_GENERATOR (tree_model), 0);
+
+	if (iter == NULL)
+		return tree_model_generator->priv->root_nodes ?
+			count_generated_nodes (tree_model_generator->priv->root_nodes) : 0;
+
+	ITER_GET (iter, &group, &index);
+	index = generated_offset_to_child_offset (group, index, NULL);
+	if (index < 0)
+		return 0;
+
+	node = &g_array_index (group, Node, index);
+
+	if (!node->child_nodes)
+		return 0;
+
+	return count_generated_nodes (node->child_nodes);
+}
+
+static gboolean
+e_tree_model_generator_iter_nth_child (GtkTreeModel *tree_model,
+                                       GtkTreeIter *iter,
+                                       GtkTreeIter *parent,
+                                       gint n)
+{
+	ETreeModelGenerator *tree_model_generator = E_TREE_MODEL_GENERATOR (tree_model);
+	Node                *node;
+	GArray              *group;
+	gint                 index;
+
+	g_return_val_if_fail (E_IS_TREE_MODEL_GENERATOR (tree_model), FALSE);
+
+	if (!parent) {
+		if (!tree_model_generator->priv->root_nodes)
+			return FALSE;
+
+		if (n >= count_generated_nodes (tree_model_generator->priv->root_nodes))
+			return FALSE;
+
+		ITER_SET (tree_model_generator, iter, tree_model_generator->priv->root_nodes, n);
+		return TRUE;
+	}
+
+	ITER_GET (parent, &group, &index);
+	index = generated_offset_to_child_offset (group, index, NULL);
+	if (index < 0)
+		return FALSE;
+
+	node = &g_array_index (group, Node, index);
+
+	if (!node->child_nodes)
+		return FALSE;
+
+	if (n >= count_generated_nodes (node->child_nodes))
+		return FALSE;
+
+	ITER_SET (tree_model_generator, iter, node->child_nodes, n);
+	return TRUE;
+}
+
+static gboolean
+e_tree_model_generator_iter_parent (GtkTreeModel *tree_model,
+                                    GtkTreeIter *iter,
+                                    GtkTreeIter *child)
+{
+	ETreeModelGenerator *tree_model_generator = E_TREE_MODEL_GENERATOR (tree_model);
+	Node                *node;
+	GArray              *group;
+	gint                 index;
+
+	g_return_val_if_fail (E_IS_TREE_MODEL_GENERATOR (tree_model), FALSE);
+	g_return_val_if_fail (ITER_IS_VALID (tree_model_generator, iter), FALSE);
+
+	ITER_GET (child, &group, &index);
+	index = generated_offset_to_child_offset (group, index, NULL);
+	if (index < 0)
+		return FALSE;
+
+	node = &g_array_index (group, Node, index);
+
+	group = node->parent_group;
+	if (!group)
+		return FALSE;
+
+	ITER_SET (tree_model_generator, iter, group, node->parent_index);
+	return TRUE;
+}
+
+static void
+e_tree_model_generator_get_value (GtkTreeModel *tree_model,
+                                  GtkTreeIter *iter,
+                                  gint column,
+                                  GValue *value)
+{
+	ETreeModelGenerator *tree_model_generator = E_TREE_MODEL_GENERATOR (tree_model);
+	GtkTreeIter          child_iter;
+	gint                 permutation_n;
+
+	g_return_if_fail (E_IS_TREE_MODEL_GENERATOR (tree_model));
+	g_return_if_fail (ITER_IS_VALID (tree_model_generator, iter));
+
+	e_tree_model_generator_convert_iter_to_child_iter (
+		tree_model_generator, &child_iter,
+		&permutation_n, iter);
+
+	if (tree_model_generator->priv->modify_func) {
+		tree_model_generator->priv->modify_func (tree_model_generator->priv->child_model,
+						   &child_iter, permutation_n,
+						   column, value,
+						   tree_model_generator->priv->modify_func_data);
+		return;
+	}
+
+	gtk_tree_model_get_value (tree_model_generator->priv->child_model, &child_iter, column, value);
+}
diff --git a/e-util/e-tree-model-generator.h b/e-util/e-tree-model-generator.h
new file mode 100644
index 0000000..e85a1ad
--- /dev/null
+++ b/e-util/e-tree-model-generator.h
@@ -0,0 +1,104 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/* e-tree-model-generator.h - Model wrapper that permutes underlying rows.
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that 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 program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * Authors: Hans Petter Jansson <hpj novell com>
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_TREE_MODEL_GENERATOR_H
+#define E_TREE_MODEL_GENERATOR_H
+
+#include <gtk/gtk.h>
+
+/* Standard GObject macros */
+#define E_TYPE_TREE_MODEL_GENERATOR \
+	(e_tree_model_generator_get_type ())
+#define E_TREE_MODEL_GENERATOR(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_TREE_MODEL_GENERATOR, ETreeModelGenerator))
+#define E_TREE_MODEL_GENERATOR_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_TREE_MODEL_GENERATOR, ETreeModelGeneratorClass))
+#define E_IS_TREE_MODEL_GENERATOR(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_TREE_MODEL_GENERATOR))
+#define E_IS_TREE_MODEL_GENERATOR_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_TREE_MODEL_GENERATOR))
+#define E_TREE_MODEL_GENERATOR_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_TREE_MODEL_GENERATOR, ETreeModelGeneratorClass))
+
+G_BEGIN_DECLS
+
+typedef gint (*ETreeModelGeneratorGenerateFunc) (GtkTreeModel *model, GtkTreeIter *child_iter,
+						 gpointer data);
+typedef void (*ETreeModelGeneratorModifyFunc)   (GtkTreeModel *model, GtkTreeIter *child_iter,
+						 gint permutation_n, gint column, GValue *value,
+						 gpointer data);
+
+typedef struct _ETreeModelGenerator ETreeModelGenerator;
+typedef struct _ETreeModelGeneratorClass ETreeModelGeneratorClass;
+typedef struct _ETreeModelGeneratorPrivate ETreeModelGeneratorPrivate;
+
+struct _ETreeModelGenerator {
+	GObject parent;
+	ETreeModelGeneratorPrivate *priv;
+};
+
+struct _ETreeModelGeneratorClass {
+	GObjectClass parent_class;
+};
+
+GType		e_tree_model_generator_get_type	(void);
+ETreeModelGenerator *
+		e_tree_model_generator_new	(GtkTreeModel *child_model);
+GtkTreeModel *	e_tree_model_generator_get_model (ETreeModelGenerator *tree_model_generator);
+void		e_tree_model_generator_set_generate_func
+						(ETreeModelGenerator *tree_model_generator,
+						 ETreeModelGeneratorGenerateFunc func,
+						 gpointer data,
+						 GDestroyNotify destroy);
+void		e_tree_model_generator_set_modify_func
+						(ETreeModelGenerator *tree_model_generator,
+						 ETreeModelGeneratorModifyFunc func,
+						 gpointer data,
+						 GDestroyNotify destroy);
+GtkTreePath *	e_tree_model_generator_convert_child_path_to_path
+						(ETreeModelGenerator *tree_model_generator,
+						 GtkTreePath *child_path);
+void		e_tree_model_generator_convert_child_iter_to_iter
+						(ETreeModelGenerator *tree_model_generator,
+						 GtkTreeIter *generator_iter,
+						 GtkTreeIter *child_iter);
+GtkTreePath *	e_tree_model_generator_convert_path_to_child_path
+						(ETreeModelGenerator *tree_model_generator,
+						 GtkTreePath *generator_path);
+void		e_tree_model_generator_convert_iter_to_child_iter
+						(ETreeModelGenerator *tree_model_generator,
+						 GtkTreeIter *child_iter,
+						 gint *permutation_n,
+						 GtkTreeIter *generator_iter);
+
+G_END_DECLS
+
+#endif  /* E_TREE_MODEL_GENERATOR_H */
diff --git a/e-util/e-tree-model.c b/e-util/e-tree-model.c
new file mode 100644
index 0000000..db763cf
--- /dev/null
+++ b/e-util/e-tree-model.c
@@ -0,0 +1,1177 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey  <clahey ximian com>
+ *		Chris Toshok <toshok ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-tree-model.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include <gtk/gtk.h>
+#include <libxml/parser.h>
+#include <libxml/xmlmemory.h>
+
+#include "e-marshal.h"
+#include "e-xml-utils.h"
+
+#define ETM_CLASS(e) (E_TREE_MODEL_GET_CLASS(e))
+
+#define d(x)
+
+G_DEFINE_TYPE (ETreeModel, e_tree_model, G_TYPE_OBJECT)
+
+enum {
+	PRE_CHANGE,
+	NO_CHANGE,
+	NODE_CHANGED,
+	NODE_DATA_CHANGED,
+	NODE_COL_CHANGED,
+	NODE_INSERTED,
+	NODE_REMOVED,
+	NODE_DELETED,
+	NODE_REQUEST_COLLAPSE,
+	REBUILT,
+	LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = {0, };
+
+static void
+e_tree_model_class_init (ETreeModelClass *class)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+	signals[PRE_CHANGE] = g_signal_new (
+		"pre_change",
+		G_TYPE_FROM_CLASS (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ETreeModelClass, pre_change),
+		(GSignalAccumulator) NULL, NULL,
+		g_cclosure_marshal_VOID__VOID,
+		G_TYPE_NONE, 0);
+
+	signals[NO_CHANGE] = g_signal_new (
+		"no_change",
+		G_TYPE_FROM_CLASS (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ETreeModelClass, no_change),
+		(GSignalAccumulator) NULL, NULL,
+		g_cclosure_marshal_VOID__VOID,
+		G_TYPE_NONE, 0);
+
+	signals[REBUILT] = g_signal_new (
+		"rebuilt",
+		G_TYPE_FROM_CLASS (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ETreeModelClass, rebuilt),
+		(GSignalAccumulator) NULL, NULL,
+		g_cclosure_marshal_VOID__VOID,
+		G_TYPE_NONE, 0);
+
+	signals[NODE_CHANGED] = g_signal_new (
+		"node_changed",
+		G_TYPE_FROM_CLASS (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ETreeModelClass, node_changed),
+		(GSignalAccumulator) NULL, NULL,
+		g_cclosure_marshal_VOID__POINTER,
+		G_TYPE_NONE, 1,
+		G_TYPE_POINTER);
+
+	signals[NODE_DATA_CHANGED] = g_signal_new (
+		"node_data_changed",
+		G_TYPE_FROM_CLASS (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ETreeModelClass, node_data_changed),
+		(GSignalAccumulator) NULL, NULL,
+		g_cclosure_marshal_VOID__POINTER,
+		G_TYPE_NONE, 1,
+		G_TYPE_POINTER);
+
+	signals[NODE_COL_CHANGED] = g_signal_new (
+		"node_col_changed",
+		G_TYPE_FROM_CLASS (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ETreeModelClass, node_col_changed),
+		(GSignalAccumulator) NULL, NULL,
+		e_marshal_VOID__POINTER_INT,
+		G_TYPE_NONE, 2,
+		G_TYPE_POINTER,
+		G_TYPE_INT);
+
+	signals[NODE_INSERTED] = g_signal_new (
+		"node_inserted",
+		G_TYPE_FROM_CLASS (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ETreeModelClass, node_inserted),
+		(GSignalAccumulator) NULL, NULL,
+		e_marshal_VOID__POINTER_POINTER,
+		G_TYPE_NONE, 2,
+		G_TYPE_POINTER,
+		G_TYPE_POINTER);
+
+	signals[NODE_REMOVED] = g_signal_new (
+		"node_removed",
+		G_TYPE_FROM_CLASS (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ETreeModelClass, node_removed),
+		(GSignalAccumulator) NULL, NULL,
+		e_marshal_VOID__POINTER_POINTER_INT,
+		G_TYPE_NONE, 3,
+		G_TYPE_POINTER,
+		G_TYPE_POINTER,
+		G_TYPE_INT);
+
+	signals[NODE_DELETED] = g_signal_new (
+		"node_deleted",
+		G_TYPE_FROM_CLASS (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ETreeModelClass, node_deleted),
+		(GSignalAccumulator) NULL, NULL,
+		g_cclosure_marshal_VOID__POINTER,
+		G_TYPE_NONE, 1,
+		G_TYPE_POINTER);
+
+	signals[NODE_REQUEST_COLLAPSE] = g_signal_new (
+		"node_request_collapse",
+		G_TYPE_FROM_CLASS (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ETreeModelClass, node_request_collapse),
+		(GSignalAccumulator) NULL, NULL,
+		g_cclosure_marshal_VOID__POINTER,
+		G_TYPE_NONE, 1,
+		G_TYPE_POINTER);
+
+	class->get_root              = NULL;
+
+	class->get_parent            = NULL;
+	class->get_first_child       = NULL;
+	class->get_last_child        = NULL;
+	class->get_next              = NULL;
+	class->get_prev              = NULL;
+
+	class->is_root               = NULL;
+	class->is_expandable         = NULL;
+	class->get_children          = NULL;
+	class->depth                 = NULL;
+
+	class->icon_at               = NULL;
+
+	class->get_expanded_default  = NULL;
+	class->column_count          = NULL;
+
+	class->has_save_id           = NULL;
+	class->get_save_id           = NULL;
+	class->has_get_node_by_id    = NULL;
+	class->get_node_by_id        = NULL;
+
+	class->has_change_pending    = NULL;
+
+	class->sort_value_at	  = NULL;
+	class->value_at              = NULL;
+	class->set_value_at          = NULL;
+	class->is_editable           = NULL;
+
+	class->duplicate_value       = NULL;
+	class->free_value            = NULL;
+	class->initialize_value      = NULL;
+	class->value_is_empty        = NULL;
+	class->value_to_string       = NULL;
+
+	class->pre_change            = NULL;
+	class->no_change             = NULL;
+	class->rebuilt		     = NULL;
+	class->node_changed          = NULL;
+	class->node_data_changed     = NULL;
+	class->node_col_changed      = NULL;
+	class->node_inserted         = NULL;
+	class->node_removed          = NULL;
+	class->node_deleted          = NULL;
+	class->node_request_collapse = NULL;
+}
+
+static void
+e_tree_model_init (ETreeModel *tree_model)
+{
+	/* nothing to do */
+}
+
+/* signals */
+
+/**
+ * e_tree_model_node_changed:
+ * @tree_model:
+ * @node:
+ *
+ *
+ *
+ * Return value:
+ **/
+void
+e_tree_model_pre_change (ETreeModel *tree_model)
+{
+	g_return_if_fail (E_IS_TREE_MODEL (tree_model));
+
+	g_signal_emit (tree_model, signals[PRE_CHANGE], 0);
+}
+
+/**
+ * e_tree_model_node_changed:
+ * @tree_model:
+ * @node:
+ *
+ *
+ *
+ * Return value:
+ **/
+void
+e_tree_model_no_change (ETreeModel *tree_model)
+{
+	g_return_if_fail (E_IS_TREE_MODEL (tree_model));
+
+	g_signal_emit (tree_model, signals[NO_CHANGE], 0);
+}
+
+/**
+ * e_tree_model_rebuilt:
+ * @tree_model:
+ * @node:
+ *
+ *
+ *
+ * Return value:
+ **/
+void
+e_tree_model_rebuilt (ETreeModel *tree_model)
+{
+	g_return_if_fail (E_IS_TREE_MODEL (tree_model));
+
+	g_signal_emit (tree_model, signals[REBUILT], 0);
+}
+/**
+ * e_tree_model_node_changed:
+ * @tree_model:
+ * @node:
+ *
+ *
+ *
+ * Return value:
+ **/
+void
+e_tree_model_node_changed (ETreeModel *tree_model,
+                           ETreePath node)
+{
+	g_return_if_fail (E_IS_TREE_MODEL (tree_model));
+
+	g_signal_emit (tree_model, signals[NODE_CHANGED], 0, node);
+}
+
+/**
+ * e_tree_model_node_data_changed:
+ * @tree_model:
+ * @node:
+ *
+ *
+ *
+ * Return value:
+ **/
+void
+e_tree_model_node_data_changed (ETreeModel *tree_model,
+                                ETreePath node)
+{
+	g_return_if_fail (E_IS_TREE_MODEL (tree_model));
+
+	g_signal_emit (tree_model, signals[NODE_DATA_CHANGED], 0, node);
+}
+
+/**
+ * e_tree_model_node_col_changed:
+ * @tree_model:
+ * @node:
+ *
+ *
+ *
+ * Return value:
+ **/
+void
+e_tree_model_node_col_changed (ETreeModel *tree_model,
+                               ETreePath node,
+                               gint col)
+{
+	g_return_if_fail (E_IS_TREE_MODEL (tree_model));
+
+	g_signal_emit (tree_model, signals[NODE_COL_CHANGED], 0, node, col);
+}
+
+/**
+ * e_tree_model_node_inserted:
+ * @tree_model:
+ * @parent_node:
+ * @inserted_node:
+ *
+ *
+ **/
+void
+e_tree_model_node_inserted (ETreeModel *tree_model,
+                            ETreePath parent_node,
+                            ETreePath inserted_node)
+{
+	g_return_if_fail (E_IS_TREE_MODEL (tree_model));
+
+	g_signal_emit (
+		tree_model, signals[NODE_INSERTED], 0,
+		parent_node, inserted_node);
+}
+
+/**
+ * e_tree_model_node_removed:
+ * @tree_model:
+ * @parent_node:
+ * @removed_node:
+ *
+ *
+ **/
+void
+e_tree_model_node_removed (ETreeModel *tree_model,
+                           ETreePath parent_node,
+                           ETreePath removed_node,
+                           gint old_position)
+{
+	g_return_if_fail (E_IS_TREE_MODEL (tree_model));
+
+	g_signal_emit (
+		tree_model, signals[NODE_REMOVED], 0,
+		parent_node, removed_node, old_position);
+}
+
+/**
+ * e_tree_model_node_deleted:
+ * @tree_model:
+ * @deleted_node:
+ *
+ *
+ **/
+void
+e_tree_model_node_deleted (ETreeModel *tree_model,
+                           ETreePath deleted_node)
+{
+	g_return_if_fail (E_IS_TREE_MODEL (tree_model));
+
+	g_signal_emit (tree_model, signals[NODE_DELETED], 0, deleted_node);
+}
+
+/**
+ * e_tree_model_node_request_collapse:
+ * @tree_model:
+ * @collapsed_node:
+ *
+ *
+ **/
+void
+e_tree_model_node_request_collapse (ETreeModel *tree_model,
+                                    ETreePath collapsed_node)
+{
+	g_return_if_fail (E_IS_TREE_MODEL (tree_model));
+
+	g_signal_emit (tree_model, signals[NODE_REQUEST_COLLAPSE], 0, collapsed_node);
+}
+
+/**
+ * e_tree_model_new
+ *
+ * XXX docs here.
+ *
+ * return values: a newly constructed ETreeModel.
+ */
+ETreeModel *
+e_tree_model_new (void)
+{
+	return g_object_new (E_TYPE_TREE_MODEL, NULL);
+}
+
+/**
+ * e_tree_model_get_root
+ * @etree: the ETreeModel of which we want the root node.
+ *
+ * Accessor for the root node of @etree.
+ *
+ * return values: the ETreePath corresponding to the root node.
+ */
+ETreePath
+e_tree_model_get_root (ETreeModel *etree)
+{
+	g_return_val_if_fail (E_IS_TREE_MODEL (etree), NULL);
+
+	if (ETM_CLASS (etree)->get_root)
+		return ETM_CLASS (etree)->get_root (etree);
+	else
+		return NULL;
+}
+
+/**
+ * e_tree_model_node_get_parent:
+ * @etree:
+ * @path:
+ *
+ *
+ *
+ * Return value:
+ **/
+ETreePath
+e_tree_model_node_get_parent (ETreeModel *etree,
+                              ETreePath node)
+{
+	g_return_val_if_fail (E_IS_TREE_MODEL (etree), NULL);
+
+	if (ETM_CLASS (etree)->get_parent)
+		return ETM_CLASS (etree)->get_parent (etree, node);
+	else
+		return NULL;
+}
+
+/**
+ * e_tree_model_node_get_first_child:
+ * @etree:
+ * @node:
+ *
+ *
+ *
+ * Return value:
+ **/
+ETreePath
+e_tree_model_node_get_first_child (ETreeModel *etree,
+                                   ETreePath node)
+{
+	g_return_val_if_fail (E_IS_TREE_MODEL (etree), NULL);
+
+	if (ETM_CLASS (etree)->get_first_child)
+		return ETM_CLASS (etree)->get_first_child (etree, node);
+	else
+		return NULL;
+}
+
+/**
+ * e_tree_model_node_get_last_child:
+ * @etree:
+ * @node:
+ *
+ *
+ *
+ * Return value:
+ **/
+ETreePath
+e_tree_model_node_get_last_child (ETreeModel *etree,
+                                  ETreePath node)
+{
+	g_return_val_if_fail (E_IS_TREE_MODEL (etree), NULL);
+
+	if (ETM_CLASS (etree)->get_last_child)
+		return ETM_CLASS (etree)->get_last_child (etree, node);
+	else
+		return NULL;
+}
+
+/**
+ * e_tree_model_node_get_next:
+ * @etree:
+ * @node:
+ *
+ *
+ *
+ * Return value:
+ **/
+ETreePath
+e_tree_model_node_get_next (ETreeModel *etree,
+                            ETreePath node)
+{
+	g_return_val_if_fail (E_IS_TREE_MODEL (etree), NULL);
+
+	if (ETM_CLASS (etree)->get_next)
+		return ETM_CLASS (etree)->get_next (etree, node);
+	else
+		return NULL;
+}
+
+/**
+ * e_tree_model_node_get_prev:
+ * @etree:
+ * @node:
+ *
+ *
+ *
+ * Return value:
+ **/
+ETreePath
+e_tree_model_node_get_prev (ETreeModel *etree,
+                            ETreePath node)
+{
+	g_return_val_if_fail (E_IS_TREE_MODEL (etree), NULL);
+
+	if (ETM_CLASS (etree)->get_prev)
+		return ETM_CLASS (etree)->get_prev (etree, node);
+	else
+		return NULL;
+}
+
+/**
+ * e_tree_model_node_is_root:
+ * @etree:
+ * @path:
+ *
+ *
+ *
+ * Return value:
+ **/
+gboolean
+e_tree_model_node_is_root (ETreeModel *etree,
+                           ETreePath node)
+{
+	g_return_val_if_fail (etree != NULL, FALSE);
+
+	if (ETM_CLASS (etree)->is_root)
+		return ETM_CLASS (etree)->is_root (etree, node);
+	else
+		return FALSE;
+}
+
+/**
+ * e_tree_model_node_is_expandable:
+ * @etree:
+ * @path:
+ *
+ *
+ *
+ * Return value:
+ **/
+gboolean
+e_tree_model_node_is_expandable (ETreeModel *etree,
+                                 ETreePath node)
+{
+	g_return_val_if_fail (etree != NULL, FALSE);
+	g_return_val_if_fail (node != NULL, FALSE);
+
+	if (ETM_CLASS (etree)->is_expandable)
+		return ETM_CLASS (etree)->is_expandable (etree, node);
+	else
+		return FALSE;
+}
+
+guint
+e_tree_model_node_get_children (ETreeModel *etree,
+                                ETreePath node,
+                                ETreePath **nodes)
+{
+	g_return_val_if_fail (etree != NULL, 0);
+	if (ETM_CLASS (etree)->get_children)
+		return ETM_CLASS (etree)->get_children (etree, node, nodes);
+	else
+		return 0;
+}
+
+/**
+ * e_tree_model_node_depth:
+ * @etree:
+ * @path:
+ *
+ *
+ *
+ * Return value:
+ **/
+guint
+e_tree_model_node_depth (ETreeModel *etree,
+                         ETreePath node)
+{
+	g_return_val_if_fail (E_IS_TREE_MODEL (etree), 0);
+
+	if (ETM_CLASS (etree)->depth)
+		return ETM_CLASS (etree)->depth (etree, node);
+	else
+		return 0;
+}
+
+/**
+ * e_tree_model_icon_at
+ * @etree: The ETreeModel.
+ * @path: The ETreePath to the node we're getting the icon of.
+ *
+ * XXX docs here.
+ *
+ * return values: the GdkPixbuf associated with this node.
+ */
+GdkPixbuf *
+e_tree_model_icon_at (ETreeModel *etree,
+                      ETreePath node)
+{
+	g_return_val_if_fail (E_IS_TREE_MODEL (etree), NULL);
+
+	if (ETM_CLASS (etree)->icon_at)
+		return ETM_CLASS (etree)->icon_at (etree, node);
+	else
+		return NULL;
+}
+
+/**
+ * e_tree_model_get_expanded_default
+ * @etree: The ETreeModel.
+ *
+ * XXX docs here.
+ *
+ * return values: Whether nodes should be expanded by default.
+ */
+gboolean
+e_tree_model_get_expanded_default (ETreeModel *etree)
+{
+	g_return_val_if_fail (E_IS_TREE_MODEL (etree), FALSE);
+
+	if (ETM_CLASS (etree)->get_expanded_default)
+		return ETM_CLASS (etree)->get_expanded_default (etree);
+	else
+		return FALSE;
+}
+
+/**
+ * e_tree_model_column_count
+ * @etree: The ETreeModel.
+ *
+ * XXX docs here.
+ *
+ * return values: The number of columns
+ */
+gint
+e_tree_model_column_count (ETreeModel *etree)
+{
+	g_return_val_if_fail (E_IS_TREE_MODEL (etree), 0);
+
+	if (ETM_CLASS (etree)->column_count)
+		return ETM_CLASS (etree)->column_count (etree);
+	else
+		return 0;
+}
+
+/**
+ * e_tree_model_has_save_id
+ * @etree: The ETreeModel.
+ *
+ * XXX docs here.
+ *
+ * return values: Whether this tree has valid save id data.
+ */
+gboolean
+e_tree_model_has_save_id (ETreeModel *etree)
+{
+	g_return_val_if_fail (E_IS_TREE_MODEL (etree), FALSE);
+
+	if (ETM_CLASS (etree)->has_save_id)
+		return ETM_CLASS (etree)->has_save_id (etree);
+	else
+		return FALSE;
+}
+
+/**
+ * e_tree_model_get_save_id
+ * @etree: The ETreeModel.
+ * @node: The ETreePath.
+ *
+ * XXX docs here.
+ *
+ * return values: The save id for this path.
+ */
+gchar *
+e_tree_model_get_save_id (ETreeModel *etree,
+                          ETreePath node)
+{
+	g_return_val_if_fail (E_IS_TREE_MODEL (etree), NULL);
+
+	if (ETM_CLASS (etree)->get_save_id)
+		return ETM_CLASS (etree)->get_save_id (etree, node);
+	else
+		return NULL;
+}
+
+/**
+ * e_tree_model_has_get_node_by_id
+ * @etree: The ETreeModel.
+ *
+ * XXX docs here.
+ *
+ * return values: Whether this tree can quickly get a node from its save id.
+ */
+gboolean
+e_tree_model_has_get_node_by_id (ETreeModel *etree)
+{
+	g_return_val_if_fail (E_IS_TREE_MODEL (etree), FALSE);
+
+	if (ETM_CLASS (etree)->has_get_node_by_id)
+		return ETM_CLASS (etree)->has_get_node_by_id (etree);
+	else
+		return FALSE;
+}
+
+/**
+ * e_tree_model_get_node_by_id
+ * @etree: The ETreeModel.
+ * @node: The ETreePath.
+ *
+ * get_node_by_id(get_save_id(node)) should be the original node.
+ * Likewise if get_node_by_id is not NULL, then
+ * get_save_id(get_node_by_id(string)) should be a copy of the
+ * original string.
+ *
+ * return values: The path for this save id.
+ */
+ETreePath
+e_tree_model_get_node_by_id (ETreeModel *etree,
+                             const gchar *save_id)
+{
+	g_return_val_if_fail (E_IS_TREE_MODEL (etree), NULL);
+
+	if (ETM_CLASS (etree)->get_node_by_id)
+		return ETM_CLASS (etree)->get_node_by_id (etree, save_id);
+	else
+		return NULL;
+}
+
+/**
+ * e_tree_model_has_change_pending
+ * @etree: The ETreeModel.
+ *
+ * XXX docs here.
+ *
+ * return values: Whether this tree has valid save id data.
+ */
+gboolean
+e_tree_model_has_change_pending (ETreeModel *etree)
+{
+	g_return_val_if_fail (E_IS_TREE_MODEL (etree), FALSE);
+
+	if (ETM_CLASS (etree)->has_change_pending)
+		return ETM_CLASS (etree)->has_change_pending (etree);
+	else
+		return FALSE;
+}
+
+/**
+ * e_tree_model_sort_value_at:
+ * @etree: The ETreeModel.
+ * @node: The ETreePath to the node we're getting the data from.
+ * @col: the column to retrieve data from
+ *
+ * Return value: This function returns the value that is stored by the
+ * @etree in column @col and node @node.  The data returned can be a
+ * pointer or any data value that can be stored inside a pointer.
+ *
+ * The data returned is typically used by an sort renderer if it wants
+ * to proxy the data of cell value_at at a better sorting order.
+ *
+ * The data returned must be valid until the model sends a signal that
+ * affect that piece of data.  node_changed and node_deleted affect
+ * all data in tha t node and all nodes under that node.
+ * node_data_changed affects the data in that node.  node_col_changed
+ * affects the data in that node for that column.  node_inserted,
+ * node_removed, and no_change don't affect any data in this way.
+ **/
+gpointer
+e_tree_model_sort_value_at (ETreeModel *etree,
+                            ETreePath node,
+                            gint col)
+{
+	g_return_val_if_fail (E_IS_TREE_MODEL (etree), NULL);
+
+	if (ETM_CLASS (etree)->sort_value_at)
+		return ETM_CLASS (etree)->sort_value_at (etree, node, col);
+	else
+		return NULL;
+}
+
+/**
+ * e_tree_model_value_at:
+ * @etree: The ETreeModel.
+ * @node: The ETreePath to the node we're getting the data from.
+ * @col: the column to retrieve data from
+ *
+ * Return value: This function returns the value that is stored by the
+ * @etree in column @col and node @node.  The data returned can be a
+ * pointer or any data value that can be stored inside a pointer.
+ *
+ * The data returned is typically used by an ECell renderer.
+ *
+ * The data returned must be valid until the model sends a signal that
+ * affect that piece of data.  node_changed and node_deleted affect
+ * all data in tha t node and all nodes under that node.
+ * node_data_changed affects the data in that node.  node_col_changed
+ * affects the data in that node for that column.  node_inserted,
+ * node_removed, and no_change don't affect any data in this way.
+ **/
+gpointer
+e_tree_model_value_at (ETreeModel *etree,
+                       ETreePath node,
+                       gint col)
+{
+	g_return_val_if_fail (E_IS_TREE_MODEL (etree), NULL);
+
+	if (ETM_CLASS (etree)->value_at)
+		return ETM_CLASS (etree)->value_at (etree, node, col);
+	else
+		return NULL;
+}
+
+void
+e_tree_model_set_value_at (ETreeModel *etree,
+                           ETreePath node,
+                           gint col,
+                           gconstpointer val)
+{
+	g_return_if_fail (E_IS_TREE_MODEL (etree));
+
+	if (ETM_CLASS (etree)->set_value_at)
+		ETM_CLASS (etree)->set_value_at (etree, node, col, val);
+}
+
+/**
+ * e_tree_model_node_is_editable:
+ * @etree:
+ * @path:
+ *
+ *
+ *
+ * Return value:
+ **/
+gboolean
+e_tree_model_node_is_editable (ETreeModel *etree,
+                               ETreePath node,
+                               gint col)
+{
+	g_return_val_if_fail (etree != NULL, FALSE);
+
+	if (ETM_CLASS (etree)->is_editable)
+		return ETM_CLASS (etree)->is_editable (etree, node, col);
+	else
+		return FALSE;
+}
+
+/**
+ * e_tree_model_duplicate_value:
+ * @etree:
+ * @path:
+ *
+ *
+ *
+ * Return value:
+ **/
+gpointer
+e_tree_model_duplicate_value (ETreeModel *etree,
+                              gint col,
+                              gconstpointer value)
+{
+	g_return_val_if_fail (etree != NULL, NULL);
+
+	if (ETM_CLASS (etree)->duplicate_value)
+		return ETM_CLASS (etree)->duplicate_value (etree, col, value);
+	else
+		return NULL;
+}
+
+/**
+ * e_tree_model_free_value:
+ * @etree:
+ * @path:
+ *
+ *
+ *
+ * Return value:
+ **/
+void
+e_tree_model_free_value (ETreeModel *etree,
+                         gint col,
+                         gpointer value)
+{
+	g_return_if_fail (etree != NULL);
+
+	if (ETM_CLASS (etree)->free_value)
+		ETM_CLASS (etree)->free_value (etree, col, value);
+}
+
+/**
+ * e_tree_model_initialize_value:
+ * @etree:
+ * @path:
+ *
+ *
+ *
+ * Return value:
+ **/
+gpointer
+e_tree_model_initialize_value (ETreeModel *etree,
+                               gint col)
+{
+	g_return_val_if_fail (etree != NULL, NULL);
+
+	if (ETM_CLASS (etree)->initialize_value)
+		return ETM_CLASS (etree)->initialize_value (etree, col);
+	else
+		return NULL;
+}
+
+/**
+ * e_tree_model_value_is_empty:
+ * @etree:
+ * @path:
+ *
+ *
+ *
+ * Return value:
+ **/
+gboolean
+e_tree_model_value_is_empty (ETreeModel *etree,
+                             gint col,
+                             gconstpointer value)
+{
+	g_return_val_if_fail (etree != NULL, TRUE);
+
+	if (ETM_CLASS (etree)->value_is_empty)
+		return ETM_CLASS (etree)->value_is_empty (etree, col, value);
+	else
+		return TRUE;
+}
+
+/**
+ * e_tree_model_value_to_string:
+ * @etree:
+ * @path:
+ *
+ *
+ *
+ * Return value:
+ **/
+gchar *
+e_tree_model_value_to_string (ETreeModel *etree,
+                              gint col,
+                              gconstpointer value)
+{
+	g_return_val_if_fail (etree != NULL, g_strdup (""));
+
+	if (ETM_CLASS (etree)->value_to_string)
+		return ETM_CLASS (etree)->value_to_string (etree, col, value);
+	else
+		return g_strdup ("");
+}
+
+/**
+ * e_tree_model_node_traverse:
+ * @model:
+ * @path:
+ * @func:
+ * @data:
+ *
+ *
+ **/
+void
+e_tree_model_node_traverse (ETreeModel *model,
+                            ETreePath path,
+                            ETreePathFunc func,
+                            gpointer data)
+{
+	ETreePath child;
+
+	g_return_if_fail (E_IS_TREE_MODEL (model));
+	g_return_if_fail (path != NULL);
+
+	child = e_tree_model_node_get_first_child (model, path);
+
+	while (child) {
+		ETreePath next_child;
+
+		next_child = e_tree_model_node_get_next (model, child);
+		e_tree_model_node_traverse (model, child, func, data);
+		if (func (model, child, data))
+			return;
+
+		child = next_child;
+	}
+}
+
+/**
+ * e_tree_model_node_traverse_preorder:
+ * @model:
+ * @path:
+ * @func:
+ * @data:
+ *
+ *
+ **/
+void
+e_tree_model_node_traverse_preorder (ETreeModel *model,
+                                     ETreePath path,
+                                     ETreePathFunc func,
+                                     gpointer data)
+{
+	ETreePath child;
+
+	g_return_if_fail (E_IS_TREE_MODEL (model));
+	g_return_if_fail (path != NULL);
+
+	child = e_tree_model_node_get_first_child (model, path);
+
+	while (child) {
+		ETreePath next_child;
+
+		if (func (model, child, data))
+			return;
+
+		next_child = e_tree_model_node_get_next (model, child);
+		e_tree_model_node_traverse_preorder (model, child, func, data);
+
+		child = next_child;
+	}
+}
+
+/**
+ * e_tree_model_node_traverse_preorder:
+ * @model:
+ * @path:
+ * @func:
+ * @data:
+ *
+ *
+ **/
+static ETreePath
+e_tree_model_node_real_traverse (ETreeModel *model,
+                                 ETreePath path,
+                                 ETreePath end_path,
+                                 gboolean forward_direction,
+                                 ETreePathFunc func,
+                                 gpointer data)
+{
+	ETreePath child;
+
+	g_return_val_if_fail (E_IS_TREE_MODEL (model), NULL);
+	g_return_val_if_fail (path != NULL, NULL);
+
+	if (forward_direction)
+		child = e_tree_model_node_get_first_child (model, path);
+	else
+		child = e_tree_model_node_get_last_child (model, path);
+
+	while (child) {
+		ETreePath result;
+
+		if (forward_direction && (child == end_path || func (model, child, data)))
+			return child;
+
+		if ((result = e_tree_model_node_real_traverse (
+			model, child, end_path,
+			forward_direction, func, data)))
+			return result;
+
+		if (!forward_direction && (child == end_path || func (model, child, data)))
+			return child;
+
+		if (forward_direction)
+			child = e_tree_model_node_get_next (model, child);
+		else
+			child = e_tree_model_node_get_prev (model, child);
+	}
+	return NULL;
+}
+
+/**
+ * e_tree_model_node_traverse_preorder:
+ * @model:
+ * @path:
+ * @func:
+ * @data:
+ *
+ *
+ **/
+ETreePath
+e_tree_model_node_find (ETreeModel *model,
+                        ETreePath path,
+                        ETreePath end_path,
+                        gboolean forward_direction,
+                        ETreePathFunc func,
+                        gpointer data)
+{
+	ETreePath result;
+	ETreePath next;
+
+	g_return_val_if_fail (E_IS_TREE_MODEL (model), NULL);
+
+	/* Just search the whole tree in this case. */
+	if (path == NULL) {
+		ETreePath root;
+		root = e_tree_model_get_root (model);
+
+		if (forward_direction && (end_path == root || func (model, root, data)))
+			return root;
+
+		result = e_tree_model_node_real_traverse (
+			model, root, end_path, forward_direction, func, data);
+		if (result)
+			return result;
+
+		if (!forward_direction && (end_path == root || func (model, root, data)))
+			return root;
+
+		return NULL;
+	}
+
+	while (1) {
+
+		if (forward_direction) {
+			if ((result = e_tree_model_node_real_traverse (
+				model, path, end_path,
+				forward_direction, func, data)))
+				return result;
+			next = e_tree_model_node_get_next (model, path);
+		} else {
+			next = e_tree_model_node_get_prev (model, path);
+			if (next && (result = e_tree_model_node_real_traverse (
+				model, next, end_path,
+				forward_direction, func, data)))
+				return result;
+		}
+
+		while (next == NULL) {
+			path = e_tree_model_node_get_parent (model, path);
+
+			if (path == NULL)
+				return NULL;
+
+			if (forward_direction)
+				next = e_tree_model_node_get_next (model, path);
+			else
+				next = path;
+		}
+
+		if (end_path == next || func (model, next, data))
+			return next;
+
+		path = next;
+	}
+}
+
diff --git a/e-util/e-tree-model.h b/e-util/e-tree-model.h
new file mode 100644
index 0000000..1d02615
--- /dev/null
+++ b/e-util/e-tree-model.h
@@ -0,0 +1,298 @@
+/*
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *		Chris Toshok <toshok ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef _E_TREE_MODEL_H_
+#define _E_TREE_MODEL_H_
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+/* Standard GObject macros */
+#define E_TYPE_TREE_MODEL \
+	(e_tree_model_get_type ())
+#define E_TREE_MODEL(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_TREE_MODEL, ETreeModel))
+#define E_TREE_MODEL_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_TREE_MODEL, ETreeModelClass))
+#define E_IS_TREE_MODEL(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_TREE_MODEL))
+#define E_IS_TREE_MODEL_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_TREE_MODEL))
+#define E_TREE_MODEL_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_TREE_MODEL, ETreeModelClass))
+
+G_BEGIN_DECLS
+
+typedef gpointer ETreePath;
+
+typedef struct _ETreeModel ETreeModel;
+typedef struct _ETreeModelClass ETreeModelClass;
+
+typedef gint		(*ETreePathCompareFunc)	(ETreeModel *model,
+						 ETreePath path1,
+						 ETreePath path2);
+typedef gboolean	(*ETreePathFunc)	(ETreeModel *model,
+						 ETreePath path,
+						 gpointer data);
+
+struct _ETreeModel {
+	GObject parent;
+};
+
+struct _ETreeModelClass {
+	GObjectClass parent_class;
+
+	/*
+	 * Virtual methods
+	 */
+	ETreePath	(*get_root)		(ETreeModel *etm);
+
+	ETreePath	(*get_parent)		(ETreeModel *etm,
+						 ETreePath node);
+	ETreePath	(*get_first_child)	(ETreeModel *etm,
+						 ETreePath node);
+	ETreePath	(*get_last_child)	(ETreeModel *etm,
+						 ETreePath node);
+	ETreePath	(*get_next)		(ETreeModel *etm,
+						 ETreePath node);
+	ETreePath	(*get_prev)		(ETreeModel *etm,
+						 ETreePath node);
+
+	gboolean	(*is_root)		(ETreeModel *etm,
+						 ETreePath node);
+	gboolean	(*is_expandable)	(ETreeModel *etm,
+						 ETreePath node);
+	guint		(*get_children)		(ETreeModel *etm,
+						 ETreePath node,
+						 ETreePath **paths);
+	guint		(*depth)		(ETreeModel *etm,
+						 ETreePath node);
+
+	GdkPixbuf *	(*icon_at)		(ETreeModel *etm,
+						 ETreePath node);
+
+	gboolean	(*get_expanded_default)	(ETreeModel *etm);
+	gint		(*column_count)		(ETreeModel *etm);
+
+	gboolean	(*has_save_id)		(ETreeModel *etm);
+	gchar *		(*get_save_id)		(ETreeModel *etm,
+						 ETreePath node);
+
+	gboolean	(*has_get_node_by_id)	(ETreeModel *etm);
+	ETreePath	(*get_node_by_id)	(ETreeModel *etm,
+						 const gchar *save_id);
+
+	gboolean	(*has_change_pending)	(ETreeModel *etm);
+
+	/*
+	 * ETable analogs
+	 */
+	gpointer	(*sort_value_at)	(ETreeModel *etm,
+						 ETreePath node,
+						 gint col);
+	gpointer	(*value_at)		(ETreeModel *etm,
+						 ETreePath node,
+						 gint col);
+	void		(*set_value_at)		(ETreeModel *etm,
+						 ETreePath node,
+						 gint col,
+						 gconstpointer val);
+	gboolean	(*is_editable)		(ETreeModel *etm,
+						 ETreePath node,
+						 gint col);
+
+	gpointer	(*duplicate_value)	(ETreeModel *etm,
+						 gint col,
+						 gconstpointer value);
+	void		(*free_value)		(ETreeModel *etm,
+						 gint col,
+						 gpointer value);
+	gpointer	(*initialize_value)	(ETreeModel *etm,
+						 gint col);
+	gboolean	(*value_is_empty)	(ETreeModel *etm,
+						 gint col,
+						 gconstpointer value);
+	gchar *		(*value_to_string)	(ETreeModel *etm,
+						 gint col,
+						 gconstpointer value);
+
+	/*
+	 * Signals
+	 */
+
+	/* During node_remove, the ETreePath of the child is removed
+	 * from the tree but is still a valid ETreePath.  At
+	 * node_deleted, the ETreePath is no longer valid.
+	 */
+
+	void		(*pre_change)		(ETreeModel *etm);
+	void		(*no_change)		(ETreeModel *etm);
+	void		(*node_changed)		(ETreeModel *etm,
+						 ETreePath node);
+	void		(*node_data_changed)	(ETreeModel *etm,
+						 ETreePath node);
+	void		(*node_col_changed)	(ETreeModel *etm,
+						 ETreePath node,
+						 gint col);
+	void		(*node_inserted)	(ETreeModel *etm,
+						 ETreePath parent,
+						 ETreePath inserted_node);
+	void		(*node_removed)		(ETreeModel *etm,
+						 ETreePath parent,
+						 ETreePath removed_node,
+						 gint old_position);
+	void		(*node_deleted)		(ETreeModel *etm,
+						 ETreePath deleted_node);
+	void		(*rebuilt)		(ETreeModel *etm);
+
+	/* This signal requests that any viewers of the tree that
+	 * collapse and expand nodes collapse this node.
+	 */
+	void		(*node_request_collapse)
+						(ETreeModel *etm,
+						 ETreePath node);
+};
+
+GType		e_tree_model_get_type		(void) G_GNUC_CONST;
+ETreeModel *	e_tree_model_new		(void);
+
+/* tree traversal operations */
+ETreePath	e_tree_model_get_root		(ETreeModel *etree);
+ETreePath	e_tree_model_node_get_parent	(ETreeModel *etree,
+						 ETreePath path);
+ETreePath	e_tree_model_node_get_first_child
+						(ETreeModel *etree,
+						 ETreePath path);
+ETreePath	e_tree_model_node_get_last_child
+						(ETreeModel *etree,
+						 ETreePath path);
+ETreePath	e_tree_model_node_get_next	(ETreeModel *etree,
+						 ETreePath path);
+ETreePath	e_tree_model_node_get_prev	(ETreeModel *etree,
+						 ETreePath path);
+
+/* node accessors */
+gboolean	e_tree_model_node_is_root	(ETreeModel *etree,
+						 ETreePath path);
+gboolean	e_tree_model_node_is_expandable	(ETreeModel *etree,
+						 ETreePath path);
+guint		e_tree_model_node_get_children	(ETreeModel *etree,
+						 ETreePath path,
+						 ETreePath **paths);
+guint		e_tree_model_node_depth		(ETreeModel *etree,
+						 ETreePath path);
+GdkPixbuf *	e_tree_model_icon_at		(ETreeModel *etree,
+						 ETreePath path);
+gboolean	e_tree_model_get_expanded_default
+						(ETreeModel *model);
+gint		e_tree_model_column_count	(ETreeModel *model);
+gboolean	e_tree_model_has_save_id	(ETreeModel *model);
+gchar *		e_tree_model_get_save_id	(ETreeModel *model,
+						 ETreePath node);
+gboolean	e_tree_model_has_get_node_by_id	(ETreeModel *model);
+ETreePath	e_tree_model_get_node_by_id	(ETreeModel *model,
+						 const gchar *save_id);
+gboolean	e_tree_model_has_change_pending	(ETreeModel *model);
+void		*e_tree_model_sort_value_at	(ETreeModel *etree,
+						 ETreePath node,
+						 gint col);
+void		*e_tree_model_value_at		(ETreeModel *etree,
+						 ETreePath node,
+						 gint col);
+void		e_tree_model_set_value_at	(ETreeModel *etree,
+						 ETreePath node,
+						 gint col,
+						 gconstpointer val);
+gboolean	e_tree_model_node_is_editable	(ETreeModel *etree,
+						 ETreePath node,
+						 gint col);
+void		*e_tree_model_duplicate_value	(ETreeModel *etree,
+						 gint col,
+						 gconstpointer value);
+void		e_tree_model_free_value		(ETreeModel *etree,
+						 gint col,
+						 gpointer value);
+void		*e_tree_model_initialize_value	(ETreeModel *etree,
+						 gint col);
+gboolean	e_tree_model_value_is_empty	(ETreeModel *etree,
+						 gint col,
+						 gconstpointer value);
+gchar *		e_tree_model_value_to_string	(ETreeModel *etree,
+						 gint col,
+						 gconstpointer value);
+
+/* depth first traversal of path's descendents, calling func on each one */
+void		e_tree_model_node_traverse	(ETreeModel *model,
+						 ETreePath path,
+						 ETreePathFunc func,
+						 gpointer data);
+void		e_tree_model_node_traverse_preorder
+						(ETreeModel *model,
+						 ETreePath path,
+						 ETreePathFunc func,
+						 gpointer data);
+ETreePath	e_tree_model_node_find		(ETreeModel *model,
+						 ETreePath path,
+						 ETreePath end_path,
+						 gboolean forward_direction,
+						 ETreePathFunc func,
+						 gpointer data);
+
+/*
+** Routines for emitting signals on the ETreeModel
+*/
+void		e_tree_model_pre_change		(ETreeModel *tree_model);
+void		e_tree_model_no_change		(ETreeModel *tree_model);
+void		e_tree_model_rebuilt		(ETreeModel *tree_model);
+void		e_tree_model_node_changed	(ETreeModel *tree_model,
+						 ETreePath node);
+void		e_tree_model_node_data_changed	(ETreeModel *tree_model,
+						 ETreePath node);
+void		e_tree_model_node_col_changed	(ETreeModel *tree_model,
+						 ETreePath node,
+						 gint col);
+void		e_tree_model_node_inserted	(ETreeModel *tree_model,
+						 ETreePath parent_node,
+						 ETreePath inserted_node);
+void		e_tree_model_node_removed	(ETreeModel *tree_model,
+						 ETreePath parent_node,
+						 ETreePath removed_node,
+						 gint old_position);
+void		e_tree_model_node_deleted	(ETreeModel *tree_model,
+						 ETreePath deleted_node);
+void		e_tree_model_node_request_collapse
+						(ETreeModel *tree_model,
+						 ETreePath deleted_node);
+
+G_END_DECLS
+
+#endif /* _E_TREE_MODEL_H */
diff --git a/e-util/e-tree-selection-model.c b/e-util/e-tree-selection-model.c
new file mode 100644
index 0000000..480b5a4
--- /dev/null
+++ b/e-util/e-tree-selection-model.c
@@ -0,0 +1,939 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *		Mike Kestner <mkestner ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-tree-selection-model.h"
+
+#include <glib/gi18n.h>
+
+#include "e-tree-table-adapter.h"
+
+#define E_TREE_SELECTION_MODEL_GET_PRIVATE(obj) \
+	(G_TYPE_INSTANCE_GET_PRIVATE \
+	((obj), E_TYPE_TREE_SELECTION_MODEL, ETreeSelectionModelPrivate))
+
+G_DEFINE_TYPE (
+	ETreeSelectionModel, e_tree_selection_model, E_TYPE_SELECTION_MODEL)
+
+enum {
+	PROP_0,
+	PROP_CURSOR_ROW,
+	PROP_CURSOR_COL,
+	PROP_MODEL,
+	PROP_ETTA
+};
+
+struct _ETreeSelectionModelPrivate {
+	ETreeTableAdapter *etta;
+	ETreeModel *model;
+
+	GHashTable *paths;
+	ETreePath cursor_path;
+	ETreePath start_path;
+	gint cursor_col;
+	gchar *cursor_save_id;
+
+	gint tree_model_pre_change_id;
+	gint tree_model_no_change_id;
+	gint tree_model_node_changed_id;
+	gint tree_model_node_data_changed_id;
+	gint tree_model_node_col_changed_id;
+	gint tree_model_node_inserted_id;
+	gint tree_model_node_removed_id;
+	gint tree_model_node_deleted_id;
+};
+
+static gint
+get_cursor_row (ETreeSelectionModel *etsm)
+{
+	if (etsm->priv->cursor_path)
+		return e_tree_table_adapter_row_of_node (
+			etsm->priv->etta, etsm->priv->cursor_path);
+
+	return -1;
+}
+
+static void
+clear_selection (ETreeSelectionModel *etsm)
+{
+	g_hash_table_destroy (etsm->priv->paths);
+	etsm->priv->paths = g_hash_table_new (NULL, NULL);
+}
+
+static void
+change_one_path (ETreeSelectionModel *etsm,
+                 ETreePath path,
+                 gboolean grow)
+{
+	if (!path)
+		return;
+
+	if (grow)
+		g_hash_table_insert (etsm->priv->paths, path, path);
+	else if (g_hash_table_lookup (etsm->priv->paths, path))
+		g_hash_table_remove (etsm->priv->paths, path);
+}
+
+static void
+select_single_path (ETreeSelectionModel *etsm,
+                    ETreePath path)
+{
+	clear_selection (etsm);
+	change_one_path (etsm, path, TRUE);
+	etsm->priv->cursor_path = path;
+	etsm->priv->start_path = NULL;
+}
+
+static void
+select_range (ETreeSelectionModel *etsm,
+              gint start,
+              gint end)
+{
+	gint i;
+
+	if (start > end) {
+		i = start;
+		start = end;
+		end = i;
+	}
+
+	for (i = start; i <= end; i++) {
+		ETreePath path = e_tree_table_adapter_node_at_row (etsm->priv->etta, i);
+		if (path)
+			g_hash_table_insert (etsm->priv->paths, path, path);
+	}
+}
+
+static void
+free_id (ETreeSelectionModel *etsm)
+{
+	g_free (etsm->priv->cursor_save_id);
+	etsm->priv->cursor_save_id = NULL;
+}
+
+static void
+restore_cursor (ETreeSelectionModel *etsm,
+                ETreeModel *etm)
+{
+	clear_selection (etsm);
+	etsm->priv->cursor_path = NULL;
+
+	if (etsm->priv->cursor_save_id) {
+		etsm->priv->cursor_path = e_tree_model_get_node_by_id (
+			etm, etsm->priv->cursor_save_id);
+		if (etsm->priv->cursor_path != NULL && etsm->priv->cursor_col == -1)
+			etsm->priv->cursor_col = 0;
+
+		select_single_path (etsm, etsm->priv->cursor_path);
+	}
+
+	e_selection_model_selection_changed (E_SELECTION_MODEL (etsm));
+
+	if (etsm->priv->cursor_path) {
+		gint cursor_row = get_cursor_row (etsm);
+		e_selection_model_cursor_changed (
+			E_SELECTION_MODEL (etsm),
+			cursor_row, etsm->priv->cursor_col);
+	} else {
+		e_selection_model_cursor_changed (
+			E_SELECTION_MODEL (etsm), -1, -1);
+		e_selection_model_cursor_activated (
+			E_SELECTION_MODEL (etsm), -1, -1);
+
+	}
+
+	free_id (etsm);
+}
+
+static void
+etsm_pre_change (ETreeModel *etm,
+                 ETreeSelectionModel *etsm)
+{
+	g_free (etsm->priv->cursor_save_id);
+	etsm->priv->cursor_save_id = NULL;
+
+	if (e_tree_model_has_get_node_by_id (etm) &&
+	    e_tree_model_has_save_id (etm) &&
+	    etsm->priv->cursor_path) {
+		etsm->priv->cursor_save_id = e_tree_model_get_save_id (
+			etm, etsm->priv->cursor_path);
+	}
+}
+
+static void
+etsm_no_change (ETreeModel *etm,
+                ETreeSelectionModel *etsm)
+{
+	free_id (etsm);
+}
+
+static void
+etsm_node_changed (ETreeModel *etm,
+                   ETreePath node,
+                   ETreeSelectionModel *etsm)
+{
+	restore_cursor (etsm, etm);
+}
+
+static void
+etsm_node_data_changed (ETreeModel *etm,
+                        ETreePath node,
+                        ETreeSelectionModel *etsm)
+{
+	free_id (etsm);
+}
+
+static void
+etsm_node_col_changed (ETreeModel *etm,
+                       ETreePath node,
+                       gint col,
+                       ETreeSelectionModel *etsm)
+{
+	free_id (etsm);
+}
+
+static void
+etsm_node_inserted (ETreeModel *etm,
+                    ETreePath parent,
+                    ETreePath child,
+                    ETreeSelectionModel *etsm)
+{
+	restore_cursor (etsm, etm);
+}
+
+static void
+etsm_node_removed (ETreeModel *etm,
+                   ETreePath parent,
+                   ETreePath child,
+                   gint old_position,
+                   ETreeSelectionModel *etsm)
+{
+	restore_cursor (etsm, etm);
+}
+
+static void
+etsm_node_deleted (ETreeModel *etm,
+                   ETreePath child,
+                   ETreeSelectionModel *etsm)
+{
+	restore_cursor (etsm, etm);
+}
+
+static void
+add_model (ETreeSelectionModel *etsm,
+           ETreeModel *model)
+{
+	ETreeSelectionModelPrivate *priv = etsm->priv;
+
+	priv->model = model;
+
+	if (!priv->model)
+		return;
+
+	g_object_ref (priv->model);
+
+	priv->tree_model_pre_change_id = g_signal_connect_after (
+		priv->model, "pre_change",
+		G_CALLBACK (etsm_pre_change), etsm);
+
+	priv->tree_model_no_change_id = g_signal_connect_after (
+		priv->model, "no_change",
+		G_CALLBACK (etsm_no_change), etsm);
+
+	priv->tree_model_node_changed_id = g_signal_connect_after (
+		priv->model, "node_changed",
+		G_CALLBACK (etsm_node_changed), etsm);
+
+	priv->tree_model_node_data_changed_id = g_signal_connect_after (
+		priv->model, "node_data_changed",
+		G_CALLBACK (etsm_node_data_changed), etsm);
+
+	priv->tree_model_node_col_changed_id = g_signal_connect_after (
+		priv->model, "node_col_changed",
+		G_CALLBACK (etsm_node_col_changed), etsm);
+
+	priv->tree_model_node_inserted_id = g_signal_connect_after (
+		priv->model, "node_inserted",
+		G_CALLBACK (etsm_node_inserted), etsm);
+
+	priv->tree_model_node_removed_id = g_signal_connect_after (
+		priv->model, "node_removed",
+		G_CALLBACK (etsm_node_removed), etsm);
+
+	priv->tree_model_node_deleted_id = g_signal_connect_after (
+		priv->model, "node_deleted",
+		G_CALLBACK (etsm_node_deleted), etsm);
+}
+
+static void
+drop_model (ETreeSelectionModel *etsm)
+{
+	ETreeSelectionModelPrivate *priv = etsm->priv;
+
+	if (!priv->model)
+		return;
+
+	g_signal_handler_disconnect (
+		priv->model, priv->tree_model_pre_change_id);
+	g_signal_handler_disconnect (
+		priv->model, priv->tree_model_no_change_id);
+	g_signal_handler_disconnect (
+		priv->model, priv->tree_model_node_changed_id);
+	g_signal_handler_disconnect (
+		priv->model, priv->tree_model_node_data_changed_id);
+	g_signal_handler_disconnect (
+		priv->model, priv->tree_model_node_col_changed_id);
+	g_signal_handler_disconnect (
+		priv->model, priv->tree_model_node_inserted_id);
+	g_signal_handler_disconnect (
+		priv->model, priv->tree_model_node_removed_id);
+	g_signal_handler_disconnect (
+		priv->model, priv->tree_model_node_deleted_id);
+
+	g_object_unref (priv->model);
+	priv->model = NULL;
+
+	priv->tree_model_pre_change_id = 0;
+	priv->tree_model_no_change_id = 0;
+	priv->tree_model_node_changed_id = 0;
+	priv->tree_model_node_data_changed_id = 0;
+	priv->tree_model_node_col_changed_id = 0;
+	priv->tree_model_node_inserted_id = 0;
+	priv->tree_model_node_removed_id = 0;
+	priv->tree_model_node_deleted_id = 0;
+}
+
+static void
+etsm_dispose (GObject *object)
+{
+	ETreeSelectionModel *etsm = E_TREE_SELECTION_MODEL (object);
+
+	drop_model (etsm);
+
+	/* Chain up to parent's dispose() method. */
+	G_OBJECT_CLASS (e_tree_selection_model_parent_class)->dispose (object);
+}
+
+static void
+etsm_finalize (GObject *object)
+{
+	ETreeSelectionModelPrivate *priv;
+
+	priv = E_TREE_SELECTION_MODEL_GET_PRIVATE (object);
+
+	clear_selection (E_TREE_SELECTION_MODEL (object));
+	g_hash_table_destroy (priv->paths);
+
+	/* Chain up to parent's finalize() method. */
+	G_OBJECT_CLASS (e_tree_selection_model_parent_class)->finalize (object);
+}
+
+static void
+etsm_get_property (GObject *object,
+                   guint property_id,
+                   GValue *value,
+                   GParamSpec *pspec)
+{
+	ETreeSelectionModel *etsm = E_TREE_SELECTION_MODEL (object);
+
+	switch (property_id) {
+	case PROP_CURSOR_ROW:
+		g_value_set_int (value, get_cursor_row (etsm));
+		break;
+
+	case PROP_CURSOR_COL:
+		g_value_set_int (value, etsm->priv->cursor_col);
+		break;
+
+	case PROP_MODEL:
+		g_value_set_object (value, etsm->priv->model);
+		break;
+
+	case PROP_ETTA:
+		g_value_set_object (value, etsm->priv->etta);
+		break;
+	}
+}
+
+static void
+etsm_set_property (GObject *object,
+                   guint property_id,
+                   const GValue *value,
+                   GParamSpec *pspec)
+{
+	ESelectionModel *esm = E_SELECTION_MODEL (object);
+	ETreeSelectionModel *etsm = E_TREE_SELECTION_MODEL (object);
+
+	switch (property_id) {
+	case PROP_CURSOR_ROW:
+		e_selection_model_do_something (
+			esm, g_value_get_int (value),
+			etsm->priv->cursor_col, 0);
+		break;
+
+	case PROP_CURSOR_COL:
+		e_selection_model_do_something (
+			esm, get_cursor_row (etsm),
+			g_value_get_int (value), 0);
+		break;
+
+	case PROP_MODEL:
+		drop_model (etsm);
+		add_model (etsm, E_TREE_MODEL (g_value_get_object (value)));
+		break;
+
+	case PROP_ETTA:
+		etsm->priv->etta =
+			E_TREE_TABLE_ADAPTER (g_value_get_object (value));
+		break;
+	}
+}
+
+static gboolean
+etsm_is_path_selected (ETreeSelectionModel *etsm,
+                       ETreePath path)
+{
+	if (path && g_hash_table_lookup (etsm->priv->paths, path))
+		return TRUE;
+
+	return FALSE;
+}
+
+/**
+ * e_selection_model_is_row_selected
+ * @selection: #ESelectionModel to check
+ * @n: The row to check
+ *
+ * This routine calculates whether the given row is selected.
+ *
+ * Returns: %TRUE if the given row is selected
+ */
+static gboolean
+etsm_is_row_selected (ESelectionModel *selection,
+                      gint row)
+{
+	ETreeSelectionModel *etsm = E_TREE_SELECTION_MODEL (selection);
+	ETreePath path;
+
+	g_return_val_if_fail (
+		row < e_table_model_row_count (
+		E_TABLE_MODEL (etsm->priv->etta)), FALSE);
+	g_return_val_if_fail (row >= 0, FALSE);
+	g_return_val_if_fail (etsm != NULL, FALSE);
+
+	path = e_tree_table_adapter_node_at_row (etsm->priv->etta, row);
+	return etsm_is_path_selected (etsm, path);
+}
+
+typedef struct {
+	ETreeSelectionModel *etsm;
+	EForeachFunc callback;
+	gpointer closure;
+} ModelAndCallback;
+
+static void
+etsm_row_foreach_cb (gpointer key,
+                     gpointer value,
+                     gpointer user_data)
+{
+	ETreePath path = key;
+	ModelAndCallback *mac = user_data;
+	gint row = e_tree_table_adapter_row_of_node (
+		mac->etsm->priv->etta, path);
+	if (row >= 0)
+		mac->callback (row, mac->closure);
+}
+
+/**
+ * e_selection_model_foreach
+ * @selection: #ESelectionModel to traverse
+ * @callback: The callback function to call back.
+ * @closure: The closure
+ *
+ * This routine calls the given callback function once for each
+ * selected row, passing closure as the closure.
+ */
+static void
+etsm_foreach (ESelectionModel *selection,
+              EForeachFunc callback,
+              gpointer closure)
+{
+	ETreeSelectionModel *etsm = E_TREE_SELECTION_MODEL (selection);
+	ModelAndCallback mac;
+
+	mac.etsm = etsm;
+	mac.callback = callback;
+	mac.closure = closure;
+
+	g_hash_table_foreach (etsm->priv->paths, etsm_row_foreach_cb, &mac);
+}
+
+/**
+ * e_selection_model_clear
+ * @selection: #ESelectionModel to clear
+ *
+ * This routine clears the selection to no rows selected.
+ */
+static void
+etsm_clear (ESelectionModel *selection)
+{
+	ETreeSelectionModel *etsm = E_TREE_SELECTION_MODEL (selection);
+
+	clear_selection (etsm);
+
+	etsm->priv->cursor_path = NULL;
+	e_selection_model_selection_changed (E_SELECTION_MODEL (etsm));
+	e_selection_model_cursor_changed (E_SELECTION_MODEL (etsm), -1, -1);
+}
+
+/**
+ * e_selection_model_selected_count
+ * @selection: #ESelectionModel to count
+ *
+ * This routine calculates the number of rows selected.
+ *
+ * Returns: The number of rows selected in the given model.
+ */
+static gint
+etsm_selected_count (ESelectionModel *selection)
+{
+	ETreeSelectionModel *etsm = E_TREE_SELECTION_MODEL (selection);
+
+	return g_hash_table_size (etsm->priv->paths);
+}
+
+static gint
+etsm_row_count (ESelectionModel *selection)
+{
+	ETreeSelectionModel *etsm = E_TREE_SELECTION_MODEL (selection);
+	return e_table_model_row_count (E_TABLE_MODEL (etsm->priv->etta));
+}
+
+/**
+ * e_selection_model_select_all
+ * @selection: #ESelectionModel to select all
+ *
+ * This routine selects all the rows in the given
+ * #ESelectionModel.
+ */
+static void
+etsm_select_all (ESelectionModel *selection)
+{
+	ETreeSelectionModel *etsm = E_TREE_SELECTION_MODEL (selection);
+	ETreePath root;
+
+	root = e_tree_model_get_root (etsm->priv->model);
+	if (root == NULL)
+		return;
+
+	clear_selection (etsm);
+	select_range (etsm, 0, etsm_row_count (selection) - 1);
+
+	if (etsm->priv->cursor_path == NULL)
+		etsm->priv->cursor_path = e_tree_table_adapter_node_at_row (
+			etsm->priv->etta, 0);
+
+	e_selection_model_selection_changed (E_SELECTION_MODEL (etsm));
+
+	e_selection_model_cursor_changed (
+		E_SELECTION_MODEL (etsm),
+		get_cursor_row (etsm), etsm->priv->cursor_col);
+}
+
+/**
+ * e_selection_model_invert_selection
+ * @selection: #ESelectionModel to invert
+ *
+ * This routine inverts all the rows in the given
+ * #ESelectionModel.
+ */
+static void
+etsm_invert_selection (ESelectionModel *selection)
+{
+	ETreeSelectionModel *etsm = E_TREE_SELECTION_MODEL (selection);
+	gint count = etsm_row_count (selection);
+	gint i;
+
+	for (i = 0; i < count; i++) {
+		ETreePath path;
+
+		path = e_tree_table_adapter_node_at_row (etsm->priv->etta, i);
+		if (!path)
+			continue;
+		if (g_hash_table_lookup (etsm->priv->paths, path))
+			g_hash_table_remove (etsm->priv->paths, path);
+		else
+			g_hash_table_insert (etsm->priv->paths, path, path);
+	}
+
+	etsm->priv->cursor_col = -1;
+	etsm->priv->cursor_path = NULL;
+	etsm->priv->start_path = NULL;
+	e_selection_model_selection_changed (E_SELECTION_MODEL (etsm));
+	e_selection_model_cursor_changed (E_SELECTION_MODEL (etsm), -1, -1);
+}
+
+static void
+etsm_change_one_row (ESelectionModel *selection,
+                     gint row,
+                     gboolean grow)
+{
+	ETreeSelectionModel *etsm = E_TREE_SELECTION_MODEL (selection);
+	ETreePath path;
+
+	g_return_if_fail (
+		row < e_table_model_row_count (
+		E_TABLE_MODEL (etsm->priv->etta)));
+	g_return_if_fail (row >= 0);
+	g_return_if_fail (selection != NULL);
+
+	path = e_tree_table_adapter_node_at_row (etsm->priv->etta, row);
+
+	if (!path)
+		return;
+
+	change_one_path (etsm, path, grow);
+}
+
+static void
+etsm_change_cursor (ESelectionModel *selection,
+                    gint row,
+                    gint col)
+{
+	ETreeSelectionModel *etsm;
+
+	g_return_if_fail (selection != NULL);
+	g_return_if_fail (E_IS_SELECTION_MODEL (selection));
+
+	etsm = E_TREE_SELECTION_MODEL (selection);
+
+	if (row == -1) {
+		etsm->priv->cursor_path = NULL;
+	} else {
+		etsm->priv->cursor_path =
+			e_tree_table_adapter_node_at_row (
+			etsm->priv->etta, row);
+	}
+	etsm->priv->cursor_col = col;
+}
+
+static gint
+etsm_cursor_row (ESelectionModel *selection)
+{
+	return get_cursor_row (E_TREE_SELECTION_MODEL (selection));
+}
+
+static gint
+etsm_cursor_col (ESelectionModel *selection)
+{
+	ETreeSelectionModel *etsm = E_TREE_SELECTION_MODEL (selection);
+	return etsm->priv->cursor_col;
+}
+
+static void
+etsm_get_rows (gint row,
+               gpointer d)
+{
+	gint **rowp = d;
+
+	**rowp = row;
+	(*rowp)++;
+}
+
+static void
+etsm_select_single_row (ESelectionModel *selection,
+                        gint row)
+{
+	ETreeSelectionModel *etsm = E_TREE_SELECTION_MODEL (selection);
+	ETreePath path;
+	gint rows[5], *rowp = NULL, size;
+
+	path = e_tree_table_adapter_node_at_row (etsm->priv->etta, row);
+	g_return_if_fail (path != NULL);
+
+	/* we really only care about the size=1 case (cursor changed),
+	 * but this doesn't cost much */
+	size = g_hash_table_size (etsm->priv->paths);
+	if (size > 0 && size <= 5) {
+		rowp = rows;
+		etsm_foreach (selection, etsm_get_rows, &rowp);
+	}
+
+	select_single_path (etsm, path);
+
+	if (size > 5) {
+		e_selection_model_selection_changed (E_SELECTION_MODEL (etsm));
+	} else {
+		if (rowp) {
+			gint *p = rows;
+
+			while (p < rowp)
+				e_selection_model_selection_row_changed (
+					(ESelectionModel *) etsm, *p++);
+		}
+		e_selection_model_selection_row_changed (
+			(ESelectionModel *) etsm, row);
+	}
+}
+
+static void
+etsm_toggle_single_row (ESelectionModel *selection,
+                        gint row)
+{
+	ETreeSelectionModel *etsm = E_TREE_SELECTION_MODEL (selection);
+	ETreePath path;
+
+	path = e_tree_table_adapter_node_at_row (etsm->priv->etta, row);
+	g_return_if_fail (path);
+
+	if (g_hash_table_lookup (etsm->priv->paths, path))
+		g_hash_table_remove (etsm->priv->paths, path);
+	else
+		g_hash_table_insert (etsm->priv->paths, path, path);
+
+	etsm->priv->start_path = NULL;
+
+	e_selection_model_selection_row_changed ((ESelectionModel *) etsm, row);
+}
+
+static void
+etsm_real_move_selection_end (ETreeSelectionModel *etsm,
+                              gint row)
+{
+	ETreePath end_path;
+	gint start;
+
+	end_path = e_tree_table_adapter_node_at_row (etsm->priv->etta, row);
+	g_return_if_fail (end_path);
+
+	start = e_tree_table_adapter_row_of_node (
+		etsm->priv->etta, etsm->priv->start_path);
+	clear_selection (etsm);
+	select_range (etsm, start, row);
+}
+
+static void
+etsm_move_selection_end (ESelectionModel *selection,
+                         gint row)
+{
+	ETreeSelectionModel *etsm = E_TREE_SELECTION_MODEL (selection);
+
+	g_return_if_fail (etsm->priv->cursor_path);
+
+	etsm_real_move_selection_end (etsm, row);
+	e_selection_model_selection_changed (E_SELECTION_MODEL (selection));
+}
+
+static void
+etsm_set_selection_end (ESelectionModel *selection,
+                        gint row)
+{
+	ETreeSelectionModel *etsm = E_TREE_SELECTION_MODEL (selection);
+
+	g_return_if_fail (etsm->priv->cursor_path);
+
+	if (!etsm->priv->start_path)
+		etsm->priv->start_path = etsm->priv->cursor_path;
+	etsm_real_move_selection_end (etsm, row);
+	e_selection_model_selection_changed (E_SELECTION_MODEL (etsm));
+}
+
+struct foreach_path_t {
+	ETreeForeachFunc callback;
+	gpointer closure;
+};
+
+static void
+foreach_path (gpointer key,
+              gpointer value,
+              gpointer data)
+{
+	ETreePath path = key;
+	struct foreach_path_t *c = data;
+	c->callback (path, c->closure);
+}
+
+void
+e_tree_selection_model_foreach (ETreeSelectionModel *etsm,
+                                ETreeForeachFunc callback,
+                                gpointer closure)
+{
+	if (etsm->priv->paths) {
+		struct foreach_path_t c;
+		c.callback = callback;
+		c.closure = closure;
+		g_hash_table_foreach (etsm->priv->paths, foreach_path, &c);
+		return;
+	}
+}
+
+void
+e_tree_selection_model_select_single_path (ETreeSelectionModel *etsm,
+                                           ETreePath path)
+{
+	select_single_path (etsm, path);
+
+	e_selection_model_selection_changed (E_SELECTION_MODEL (etsm));
+}
+
+void
+e_tree_selection_model_select_paths (ETreeSelectionModel *etsm,
+                                     GPtrArray *paths)
+{
+	ETreePath path;
+	gint i;
+
+	for (i = 0; i < paths->len; i++) {
+		path = paths->pdata[i];
+		change_one_path (etsm, path, TRUE);
+	}
+
+	e_selection_model_selection_changed (E_SELECTION_MODEL (etsm));
+}
+
+void
+e_tree_selection_model_add_to_selection (ETreeSelectionModel *etsm,
+                                         ETreePath path)
+{
+	change_one_path (etsm, path, TRUE);
+
+	e_selection_model_selection_changed (E_SELECTION_MODEL (etsm));
+}
+
+void
+e_tree_selection_model_change_cursor (ETreeSelectionModel *etsm,
+                                      ETreePath path)
+{
+	gint row;
+
+	etsm->priv->cursor_path = path;
+
+	row = get_cursor_row (etsm);
+
+	E_SELECTION_MODEL (etsm)->old_selection = -1;
+
+	e_selection_model_cursor_changed (
+		E_SELECTION_MODEL (etsm), row, etsm->priv->cursor_col);
+	e_selection_model_cursor_activated (
+		E_SELECTION_MODEL (etsm), row, etsm->priv->cursor_col);
+}
+
+ETreePath
+e_tree_selection_model_get_cursor (ETreeSelectionModel *etsm)
+{
+	return etsm->priv->cursor_path;
+}
+
+static void
+e_tree_selection_model_init (ETreeSelectionModel *etsm)
+{
+	etsm->priv = E_TREE_SELECTION_MODEL_GET_PRIVATE (etsm);
+
+	etsm->priv->paths = g_hash_table_new (NULL, NULL);
+	etsm->priv->cursor_col = -1;
+}
+
+static void
+e_tree_selection_model_class_init (ETreeSelectionModelClass *class)
+{
+	GObjectClass *object_class;
+	ESelectionModelClass *esm_class;
+
+	g_type_class_add_private (class, sizeof (ETreeSelectionModelPrivate));
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->dispose = etsm_dispose;
+	object_class->finalize = etsm_finalize;
+	object_class->get_property = etsm_get_property;
+	object_class->set_property = etsm_set_property;
+
+	esm_class = E_SELECTION_MODEL_CLASS (class);
+	esm_class->is_row_selected = etsm_is_row_selected;
+	esm_class->foreach = etsm_foreach;
+	esm_class->clear = etsm_clear;
+	esm_class->selected_count = etsm_selected_count;
+	esm_class->select_all = etsm_select_all;
+	esm_class->invert_selection = etsm_invert_selection;
+	esm_class->row_count = etsm_row_count;
+
+	esm_class->change_one_row = etsm_change_one_row;
+	esm_class->change_cursor = etsm_change_cursor;
+	esm_class->cursor_row = etsm_cursor_row;
+	esm_class->cursor_col = etsm_cursor_col;
+
+	esm_class->select_single_row = etsm_select_single_row;
+	esm_class->toggle_single_row = etsm_toggle_single_row;
+	esm_class->move_selection_end = etsm_move_selection_end;
+	esm_class->set_selection_end = etsm_set_selection_end;
+
+	g_object_class_install_property (
+		object_class,
+		PROP_CURSOR_ROW,
+		g_param_spec_int (
+			"cursor_row",
+			"Cursor Row",
+			NULL,
+			0, G_MAXINT, 0,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_CURSOR_COL,
+		g_param_spec_int (
+			"cursor_col",
+			"Cursor Column",
+			NULL,
+			0, G_MAXINT, 0,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_MODEL,
+		g_param_spec_object (
+			"model",
+			"Model",
+			NULL,
+			E_TYPE_TREE_MODEL,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_ETTA,
+		g_param_spec_object (
+			"etta",
+			"ETTA",
+			NULL,
+			E_TYPE_TREE_TABLE_ADAPTER,
+			G_PARAM_READWRITE));
+
+}
+
+ESelectionModel *
+e_tree_selection_model_new (void)
+{
+	return g_object_new (E_TYPE_TREE_SELECTION_MODEL, NULL);
+}
+
diff --git a/e-util/e-tree-selection-model.h b/e-util/e-tree-selection-model.h
new file mode 100644
index 0000000..eafa66e
--- /dev/null
+++ b/e-util/e-tree-selection-model.h
@@ -0,0 +1,95 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef _E_TREE_SELECTION_MODEL_H_
+#define _E_TREE_SELECTION_MODEL_H_
+
+#include <e-util/e-selection-model.h>
+#include <e-util/e-sorter.h>
+#include <e-util/e-tree-model.h>
+
+/* Standard GObject macros */
+#define E_TYPE_TREE_SELECTION_MODEL \
+	(e_tree_selection_model_get_type ())
+#define E_TREE_SELECTION_MODEL(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_TREE_SELECTION_MODEL, ETreeSelectionModel))
+#define E_TREE_SELECTION_MODEL_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_TREE_SELECTION_MODEL, ETreeSelectionModelClass))
+#define E_IS_TREE_SELECTION_MODEL(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_TREE_SELECTION_MODEL))
+#define E_IS_TREE_SELECTION_MODEL_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_TREE_SELECTION_MODEL))
+#define E_TREE_SELECTION_MODEL_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_TREE_SELECTION_MODEL, ETreeSelectionModelClass))
+
+G_BEGIN_DECLS
+
+typedef void	(*ETreeForeachFunc)		(ETreePath path,
+						 gpointer closure);
+
+typedef struct _ETreeSelectionModel ETreeSelectionModel;
+typedef struct _ETreeSelectionModelClass ETreeSelectionModelClass;
+typedef struct _ETreeSelectionModelPrivate ETreeSelectionModelPrivate;
+
+struct _ETreeSelectionModel {
+	ESelectionModel parent;
+	ETreeSelectionModelPrivate *priv;
+};
+
+struct _ETreeSelectionModelClass {
+	ESelectionModelClass parent_class;
+};
+
+GType		e_tree_selection_model_get_type	(void) G_GNUC_CONST;
+ESelectionModel *
+		e_tree_selection_model_new	(void);
+void		e_tree_selection_model_foreach	(ETreeSelectionModel *etsm,
+						 ETreeForeachFunc callback,
+						 gpointer closure);
+void		e_tree_selection_model_select_single_path
+						(ETreeSelectionModel *etsm,
+						 ETreePath path);
+void		e_tree_selection_model_select_paths
+						(ETreeSelectionModel *etsm,
+						 GPtrArray *paths);
+
+void		e_tree_selection_model_add_to_selection
+						(ETreeSelectionModel *etsm,
+						 ETreePath path);
+void		e_tree_selection_model_change_cursor
+						(ETreeSelectionModel *etsm,
+						 ETreePath path);
+ETreePath	e_tree_selection_model_get_cursor
+						(ETreeSelectionModel *etsm);
+
+G_END_DECLS
+
+#endif /* _E_TREE_SELECTION_MODEL_H_ */
diff --git a/e-util/e-tree-sorted.c b/e-util/e-tree-sorted.c
new file mode 100644
index 0000000..25cfceb
--- /dev/null
+++ b/e-util/e-tree-sorted.c
@@ -0,0 +1,1433 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *		Chris Toshok <toshok ximian com>
+ *
+ * Adapted from the gtree code and ETableModel.
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+/* FIXME: Overall e-tree-sorted.c needs to be made more efficient. */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-tree-sorted.h"
+
+#include <stdio.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+
+#include <libxml/parser.h>
+#include <libxml/xmlmemory.h>
+
+#include "e-table-sorting-utils.h"
+#include "e-xml-utils.h"
+
+/* maximum insertions between an idle event that we will do without scheduling an idle sort */
+#define ETS_INSERT_MAX (4)
+
+#define d(x)
+
+#define E_TREE_SORTED_GET_PRIVATE(obj) \
+	(G_TYPE_INSTANCE_GET_PRIVATE \
+	((obj), E_TYPE_TREE_SORTED, ETreeSortedPrivate))
+
+G_DEFINE_TYPE (ETreeSorted, e_tree_sorted, E_TYPE_TREE_MODEL)
+
+enum {
+	NODE_RESORTED,
+	LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = {0, };
+
+typedef struct ETreeSortedPath ETreeSortedPath;
+
+struct ETreeSortedPath {
+	ETreePath         corresponding;
+
+	/* parent/child/sibling pointers */
+	ETreeSortedPath  *parent;
+	gint              num_children;
+	ETreeSortedPath **children;
+	gint               position;
+	gint               orig_position;
+
+	guint             needs_resort : 1;
+	guint             child_needs_resort : 1;
+	guint             resort_all_children : 1;
+	guint             needs_regen_to_sort : 1;
+};
+
+struct _ETreeSortedPrivate {
+	ETreeModel      *source;
+	ETreeSortedPath *root;
+
+	ETableSortInfo   *sort_info;
+	ETableHeader     *full_header;
+
+	ETreeSortedPath  *last_access;
+
+	gint          tree_model_pre_change_id;
+	gint          tree_model_no_change_id;
+	gint          tree_model_node_changed_id;
+	gint          tree_model_node_data_changed_id;
+	gint          tree_model_node_col_changed_id;
+	gint          tree_model_node_inserted_id;
+	gint          tree_model_node_removed_id;
+	gint          tree_model_node_deleted_id;
+	gint          tree_model_node_request_collapse_id;
+
+	gint          sort_info_changed_id;
+	gint          sort_idle_id;
+	gint          insert_idle_id;
+	gint          insert_count;
+
+	guint        in_resort_idle : 1;
+	guint        nested_resort_idle : 1;
+};
+
+enum {
+	ARG_0,
+
+	ARG_SORT_INFO
+};
+
+static void ets_sort_info_changed (ETableSortInfo *sort_info, ETreeSorted *ets);
+static void resort_node (ETreeSorted *ets, ETreeSortedPath *path, gboolean resort_all_children, gboolean needs_regen, gboolean send_signals);
+static void mark_path_needs_resort (ETreeSorted *ets, ETreeSortedPath *path, gboolean needs_rebuild, gboolean resort_all_children);
+static void schedule_resort (ETreeSorted *ets, ETreeSortedPath *path, gboolean needs_regen, gboolean resort_all_children);
+static void free_path (ETreeSortedPath *path);
+static void generate_children (ETreeSorted *ets, ETreeSortedPath *path);
+static void regenerate_children (ETreeSorted *ets, ETreeSortedPath *path);
+
+/* idle callbacks */
+
+static gboolean
+ets_sort_idle (gpointer user_data)
+{
+	ETreeSorted *ets = user_data;
+	if (ets->priv->in_resort_idle) {
+		ets->priv->nested_resort_idle = TRUE;
+		return FALSE;
+	}
+	ets->priv->in_resort_idle = TRUE;
+	if (ets->priv->root) {
+		do {
+			ets->priv->nested_resort_idle = FALSE;
+			resort_node (ets, ets->priv->root, FALSE, FALSE, TRUE);
+		} while (ets->priv->nested_resort_idle);
+	}
+	ets->priv->in_resort_idle = FALSE;
+	ets->priv->sort_idle_id = 0;
+	return FALSE;
+}
+
+#define ETS_SORT_IDLE_ACTIVATED(ets) ((ets)->priv->sort_idle_id != 0)
+
+inline static void
+ets_stop_sort_idle (ETreeSorted *ets)
+{
+	if (ets->priv->sort_idle_id) {
+		g_source_remove (ets->priv->sort_idle_id);
+		ets->priv->sort_idle_id = 0;
+	}
+}
+
+static gboolean
+ets_insert_idle (ETreeSorted *ets)
+{
+	ets->priv->insert_count = 0;
+	ets->priv->insert_idle_id = 0;
+	return FALSE;
+}
+
+/* Helper functions */
+
+#define CHECK_AROUND_LAST_ACCESS
+
+static inline ETreeSortedPath *
+check_last_access (ETreeSorted *ets,
+                   ETreePath corresponding)
+{
+#ifdef CHECK_AROUND_LAST_ACCESS
+	ETreeSortedPath *parent;
+#endif
+
+	if (ets->priv->last_access == NULL)
+		return NULL;
+
+	if (ets->priv->last_access == corresponding) {
+		d (g_print ("Found last access %p at %p.", ets->priv->last_access, ets->priv->last_access));
+		return ets->priv->last_access;
+	}
+
+#ifdef CHECK_AROUND_LAST_ACCESS
+	parent = ets->priv->last_access->parent;
+	if (parent && parent->children) {
+		gint position = ets->priv->last_access->position;
+		gint end = MIN (parent->num_children, position + 10);
+		gint start = MAX (0, position - 10);
+		gint initial = MAX (MIN (position, end), start);
+		gint i;
+
+		for (i = initial; i < end; i++) {
+			if (parent->children[i] && parent->children[i]->corresponding == corresponding) {
+				d (g_print ("Found last access %p at %p.", ets->priv->last_access, parent->children[i]));
+				return parent->children[i];
+			}
+		}
+
+		for (i = initial - 1; i >= start; i--) {
+			if (parent->children[i] && parent->children[i]->corresponding == corresponding) {
+				d (g_print ("Found last access %p at %p.", ets->priv->last_access, parent->children[i]));
+				return parent->children[i];
+			}
+		}
+	}
+#endif
+	return NULL;
+}
+
+static ETreeSortedPath *
+find_path (ETreeSorted *ets,
+           ETreePath corresponding)
+{
+	gint depth;
+	ETreePath *sequence;
+	gint i;
+	ETreeSortedPath *path;
+	ETreeSortedPath *check_last;
+
+	if (corresponding == NULL)
+		return NULL;
+
+	check_last = check_last_access (ets, corresponding);
+	if (check_last) {
+		d (g_print (" (find_path)\n"));
+		return check_last;
+	}
+
+	depth = e_tree_model_node_depth (ets->priv->source, corresponding);
+
+	sequence = g_new (ETreePath, depth + 1);
+
+	sequence[0] = corresponding;
+
+	for (i = 0; i < depth; i++)
+		sequence[i + 1] = e_tree_model_node_get_parent (ets->priv->source, sequence[i]);
+
+	path = ets->priv->root;
+
+	for (i = depth - 1; i >= 0 && path != NULL; i--) {
+		gint j;
+
+		if (path->num_children == -1) {
+			path = NULL;
+			break;
+		}
+
+		for (j = 0; j < path->num_children; j++) {
+			if (path->children[j]->corresponding == sequence[i]) {
+				break;
+			}
+		}
+
+		if (j < path->num_children) {
+			path = path->children[j];
+		} else {
+			path = NULL;
+		}
+	}
+	g_free (sequence);
+
+	d (g_print ("Didn't find last access %p.  Setting to %p. (find_path)\n", ets->priv->last_access, path));
+	ets->priv->last_access = path;
+
+	return path;
+}
+
+static ETreeSortedPath *
+find_child_path (ETreeSorted *ets,
+                 ETreeSortedPath *parent,
+                 ETreePath corresponding)
+{
+	gint i;
+
+	if (corresponding == NULL)
+		return NULL;
+
+	if (parent->num_children == -1) {
+		return NULL;
+	}
+
+	for (i = 0; i < parent->num_children; i++)
+		if (parent->children[i]->corresponding == corresponding)
+			return parent->children[i];
+
+	return NULL;
+}
+
+static ETreeSortedPath *
+find_or_create_path (ETreeSorted *ets,
+                     ETreePath corresponding)
+{
+	gint depth;
+	ETreePath *sequence;
+	gint i;
+	ETreeSortedPath *path;
+	ETreeSortedPath *check_last;
+
+	if (corresponding == NULL)
+		return NULL;
+
+	check_last = check_last_access (ets, corresponding);
+	if (check_last) {
+		d (g_print (" (find_or_create_path)\n"));
+		return check_last;
+	}
+
+	depth = e_tree_model_node_depth (ets->priv->source, corresponding);
+
+	sequence = g_new (ETreePath, depth + 1);
+
+	sequence[0] = corresponding;
+
+	for (i = 0; i < depth; i++)
+		sequence[i + 1] = e_tree_model_node_get_parent (ets->priv->source, sequence[i]);
+
+	path = ets->priv->root;
+
+	for (i = depth - 1; i >= 0 && path != NULL; i--) {
+		gint j;
+
+		if (path->num_children == -1) {
+			generate_children (ets, path);
+		}
+
+		for (j = 0; j < path->num_children; j++) {
+			if (path->children[j]->corresponding == sequence[i]) {
+				break;
+			}
+		}
+
+		if (j < path->num_children) {
+			path = path->children[j];
+		} else {
+			path = NULL;
+		}
+	}
+	g_free (sequence);
+
+	d (g_print ("Didn't find last access %p.  Setting to %p. (find_or_create_path)\n", ets->priv->last_access, path));
+	ets->priv->last_access = path;
+
+	return path;
+}
+
+static void
+free_children (ETreeSortedPath *path)
+{
+	gint i;
+
+	if (path == NULL)
+		return;
+
+	for (i = 0; i < path->num_children; i++) {
+		free_path (path->children[i]);
+	}
+
+	g_free (path->children);
+	path->children = NULL;
+	path->num_children = -1;
+}
+
+static void
+free_path (ETreeSortedPath *path)
+{
+	free_children (path);
+	g_slice_free (ETreeSortedPath, path);
+}
+
+static ETreeSortedPath *
+new_path (ETreeSortedPath *parent,
+          ETreePath corresponding)
+{
+	ETreeSortedPath *path;
+
+	path = g_slice_new0 (ETreeSortedPath);
+
+	path->corresponding = corresponding;
+	path->parent = parent;
+	path->num_children = -1;
+	path->children = NULL;
+	path->position = -1;
+	path->orig_position = -1;
+	path->child_needs_resort = 0;
+	path->resort_all_children = 0;
+	path->needs_resort = 0;
+	path->needs_regen_to_sort = 0;
+
+	return path;
+}
+
+static gboolean
+reposition_path (ETreeSorted *ets,
+                 ETreeSortedPath *path)
+{
+	gint new_index;
+	gint old_index = path->position;
+	ETreeSortedPath *parent = path->parent;
+	gboolean changed = FALSE;
+	if (parent) {
+		if (ets->priv->sort_idle_id == 0) {
+			if (ets->priv->insert_count > ETS_INSERT_MAX) {
+				/* schedule a sort, and append instead */
+				schedule_resort (ets, parent, TRUE, FALSE);
+			} else {
+				/* make sure we have an idle handler to reset the count every now and then */
+				if (ets->priv->insert_idle_id == 0) {
+					ets->priv->insert_idle_id = g_idle_add_full (40, (GSourceFunc) ets_insert_idle, ets, NULL);
+				}
+
+				new_index = e_table_sorting_utils_tree_check_position
+					(E_TREE_MODEL (ets),
+					 ets->priv->sort_info,
+					 ets->priv->full_header,
+					 (ETreePath *) parent->children,
+					 parent->num_children,
+					 old_index);
+
+				if (new_index > old_index) {
+					gint i;
+					ets->priv->insert_count++;
+					memmove (parent->children + old_index, parent->children + old_index + 1, sizeof (ETreePath) * (new_index - old_index));
+					parent->children[new_index] = path;
+					for (i = old_index; i <= new_index; i++)
+						parent->children[i]->position = i;
+					changed = TRUE;
+					e_tree_model_node_changed (E_TREE_MODEL (ets), parent);
+					e_tree_sorted_node_resorted (ets, parent);
+				} else if (new_index < old_index) {
+					gint i;
+					ets->priv->insert_count++;
+					memmove (parent->children + new_index + 1, parent->children + new_index, sizeof (ETreePath) * (old_index - new_index));
+					parent->children[new_index] = path;
+					for (i = new_index; i <= old_index; i++)
+						parent->children[i]->position = i;
+					changed = TRUE;
+					e_tree_model_node_changed (E_TREE_MODEL (ets), parent);
+					e_tree_sorted_node_resorted (ets, parent);
+				}
+			}
+		} else
+			mark_path_needs_resort (ets, parent, TRUE, FALSE);
+	}
+	return changed;
+}
+
+static void
+regenerate_children (ETreeSorted *ets,
+                     ETreeSortedPath *path)
+{
+	ETreeSortedPath **children;
+	gint i;
+
+	children = g_new (ETreeSortedPath *, path->num_children);
+	for (i = 0; i < path->num_children; i++)
+		children[path->children[i]->orig_position] = path->children[i];
+	g_free (path->children);
+	path->children = children;
+}
+
+static void
+generate_children (ETreeSorted *ets,
+                   ETreeSortedPath *path)
+{
+	ETreePath child;
+	gint i;
+	gint count;
+
+	free_children (path);
+
+	count = 0;
+	for (child = e_tree_model_node_get_first_child (ets->priv->source, path->corresponding);
+	     child;
+	     child = e_tree_model_node_get_next (ets->priv->source, child)) {
+		count++;
+	}
+
+	path->num_children = count;
+	path->children = g_new (ETreeSortedPath *, count);
+	for (child = e_tree_model_node_get_first_child (ets->priv->source, path->corresponding), i = 0;
+	     child;
+	     child = e_tree_model_node_get_next (ets->priv->source, child), i++) {
+		path->children[i] = new_path (path, child);
+		path->children[i]->position = i;
+		path->children[i]->orig_position = i;
+	}
+	if (path->num_children > 0)
+		schedule_resort (ets, path, FALSE, TRUE);
+}
+
+static void
+resort_node (ETreeSorted *ets,
+             ETreeSortedPath *path,
+             gboolean resort_all_children,
+             gboolean needs_regen,
+             gboolean send_signals)
+{
+	gboolean needs_resort;
+	if (path) {
+		needs_resort = path->needs_resort || resort_all_children;
+		needs_regen = path->needs_regen_to_sort || needs_regen;
+		if (path->num_children > 0) {
+			if (needs_resort && send_signals)
+				e_tree_model_pre_change (E_TREE_MODEL (ets));
+			if (needs_resort) {
+				gint i;
+				d (g_print ("Start sort of node %p\n", path));
+				if (needs_regen)
+					regenerate_children (ets, path);
+				d (g_print ("Regened sort of node %p\n", path));
+				e_table_sorting_utils_tree_sort (
+					E_TREE_MODEL (ets),
+					ets->priv->sort_info,
+					ets->priv->full_header,
+					(ETreePath *) path->children,
+					path->num_children);
+				d (g_print ("Renumbering sort of node %p\n", path));
+				for (i = 0; i < path->num_children; i++) {
+					path->children[i]->position = i;
+				}
+				d (g_print ("End sort of node %p\n", path));
+			}
+			if (path->resort_all_children)
+				resort_all_children = TRUE;
+			if ((resort_all_children || path->child_needs_resort) && path->num_children >= 0) {
+				gint i;
+				for (i = 0; i < path->num_children; i++) {
+					resort_node (ets, path->children[i], resort_all_children, needs_regen, send_signals && !needs_resort);
+				}
+				path->child_needs_resort = 0;
+			}
+		}
+		path->needs_resort = 0;
+		path->child_needs_resort = 0;
+		path->needs_regen_to_sort = 0;
+		path->resort_all_children = 0;
+		if (needs_resort && send_signals && path->num_children > 0) {
+			e_tree_model_node_changed (E_TREE_MODEL (ets), path);
+			e_tree_sorted_node_resorted (ets, path);
+		}
+	}
+}
+
+static void
+mark_path_child_needs_resort (ETreeSorted *ets,
+                              ETreeSortedPath *path)
+{
+	if (path == NULL)
+		return;
+	if (!path->child_needs_resort) {
+		path->child_needs_resort = 1;
+		mark_path_child_needs_resort (ets, path->parent);
+	}
+}
+
+static void
+mark_path_needs_resort (ETreeSorted *ets,
+                        ETreeSortedPath *path,
+                        gboolean needs_regen,
+                        gboolean resort_all_children)
+{
+	if (path == NULL)
+		return;
+	if (path->num_children == 0)
+		return;
+	path->needs_resort = 1;
+	path->needs_regen_to_sort = needs_regen;
+	path->resort_all_children = resort_all_children;
+	mark_path_child_needs_resort (ets, path->parent);
+}
+
+static void
+schedule_resort (ETreeSorted *ets,
+                 ETreeSortedPath *path,
+                 gboolean needs_regen,
+                 gboolean resort_all_children)
+{
+	ets->priv->insert_count = 0;
+	if (ets->priv->insert_idle_id != 0) {
+		g_source_remove (ets->priv->insert_idle_id);
+		ets->priv->insert_idle_id = 0;
+	}
+
+	if (path == NULL)
+		return;
+	if (path->num_children == 0)
+		return;
+
+	mark_path_needs_resort (ets, path, needs_regen, resort_all_children);
+	if (ets->priv->sort_idle_id == 0) {
+		ets->priv->sort_idle_id = g_idle_add_full (50, (GSourceFunc) ets_sort_idle, ets, NULL);
+	} else if (ets->priv->in_resort_idle) {
+		ets->priv->nested_resort_idle = TRUE;
+	}
+}
+
+/* virtual methods */
+
+static void
+ets_dispose (GObject *object)
+{
+	ETreeSortedPrivate *priv;
+
+	priv = E_TREE_SORTED_GET_PRIVATE (object);
+
+	if (priv->source) {
+		g_signal_handler_disconnect (
+			priv->source, priv->tree_model_pre_change_id);
+		g_signal_handler_disconnect (
+			priv->source, priv->tree_model_no_change_id);
+		g_signal_handler_disconnect (
+			priv->source, priv->tree_model_node_changed_id);
+		g_signal_handler_disconnect (
+			priv->source, priv->tree_model_node_data_changed_id);
+		g_signal_handler_disconnect (
+			priv->source, priv->tree_model_node_col_changed_id);
+		g_signal_handler_disconnect (
+			priv->source, priv->tree_model_node_inserted_id);
+		g_signal_handler_disconnect (
+			priv->source, priv->tree_model_node_removed_id);
+		g_signal_handler_disconnect (
+			priv->source, priv->tree_model_node_deleted_id);
+		g_signal_handler_disconnect (
+			priv->source, priv->tree_model_node_request_collapse_id);
+
+		g_object_unref (priv->source);
+		priv->source = NULL;
+
+		priv->tree_model_pre_change_id = 0;
+		priv->tree_model_no_change_id = 0;
+		priv->tree_model_node_changed_id = 0;
+		priv->tree_model_node_data_changed_id = 0;
+		priv->tree_model_node_col_changed_id = 0;
+		priv->tree_model_node_inserted_id = 0;
+		priv->tree_model_node_removed_id = 0;
+		priv->tree_model_node_deleted_id = 0;
+		priv->tree_model_node_request_collapse_id = 0;
+	}
+
+	if (priv->sort_info) {
+		g_signal_handler_disconnect (
+			priv->sort_info, priv->sort_info_changed_id);
+		priv->sort_info_changed_id = 0;
+
+		g_object_unref (priv->sort_info);
+		priv->sort_info = NULL;
+	}
+
+	ets_stop_sort_idle (E_TREE_SORTED (object));
+
+	if (priv->insert_idle_id) {
+		g_source_remove (priv->insert_idle_id);
+		priv->insert_idle_id = 0;
+	}
+
+	if (priv->full_header) {
+		g_object_unref (priv->full_header);
+		priv->full_header = NULL;
+	}
+
+	/* Chain up to parent's dispose() method. */
+	G_OBJECT_CLASS (e_tree_sorted_parent_class)->dispose (object);
+}
+
+static void
+ets_finalize (GObject *object)
+{
+	ETreeSortedPrivate *priv;
+
+	priv = E_TREE_SORTED_GET_PRIVATE (object);
+
+	if (priv->root)
+		free_path (priv->root);
+
+	/* Chain up to parent's finalize() method. */
+	G_OBJECT_CLASS (e_tree_sorted_parent_class)->finalize (object);
+}
+
+static ETreePath
+ets_get_root (ETreeModel *etm)
+{
+	ETreeSortedPrivate *priv = E_TREE_SORTED (etm)->priv;
+	if (priv->root == NULL) {
+		ETreeSorted *ets = E_TREE_SORTED (etm);
+		ETreePath corresponding = e_tree_model_get_root (ets->priv->source);
+
+		if (corresponding) {
+			priv->root = new_path (NULL, corresponding);
+		}
+	}
+	if (priv->root && priv->root->num_children == -1) {
+		generate_children (E_TREE_SORTED (etm), priv->root);
+	}
+
+	return priv->root;
+}
+
+static ETreePath
+ets_get_parent (ETreeModel *etm,
+                ETreePath node)
+{
+	ETreeSortedPath *path = node;
+	return path->parent;
+}
+
+static ETreePath
+ets_get_first_child (ETreeModel *etm,
+                     ETreePath node)
+{
+	ETreeSortedPath *path = node;
+	ETreeSorted *ets = E_TREE_SORTED (etm);
+
+	if (path->num_children == -1)
+		generate_children (ets, path);
+
+	if (path->num_children > 0)
+		return path->children[0];
+	else
+		return NULL;
+}
+
+static ETreePath
+ets_get_last_child (ETreeModel *etm,
+                    ETreePath node)
+{
+	ETreeSortedPath *path = node;
+	ETreeSorted *ets = E_TREE_SORTED (etm);
+
+	if (path->num_children == -1)
+		generate_children (ets, path);
+
+	if (path->num_children > 0)
+		return path->children[path->num_children - 1];
+	else
+		return NULL;
+}
+
+static ETreePath
+ets_get_next (ETreeModel *etm,
+              ETreePath node)
+{
+	ETreeSortedPath *path = node;
+	ETreeSortedPath *parent = path->parent;
+	if (parent) {
+		if (parent->num_children > path->position + 1)
+			return parent->children[path->position + 1];
+		else
+			return NULL;
+	} else
+		  return NULL;
+}
+
+static ETreePath
+ets_get_prev (ETreeModel *etm,
+              ETreePath node)
+{
+	ETreeSortedPath *path = node;
+	ETreeSortedPath *parent = path->parent;
+	if (parent) {
+		if (path->position - 1 >= 0)
+			return parent->children[path->position - 1];
+		else
+			return NULL;
+	} else
+		  return NULL;
+}
+
+static gboolean
+ets_is_root (ETreeModel *etm,
+             ETreePath node)
+{
+	ETreeSortedPath *path = node;
+	ETreeSorted *ets = E_TREE_SORTED (etm);
+
+	return e_tree_model_node_is_root (ets->priv->source, path->corresponding);
+}
+
+static gboolean
+ets_is_expandable (ETreeModel *etm,
+                   ETreePath node)
+{
+	ETreeSortedPath *path = node;
+	ETreeSorted *ets = E_TREE_SORTED (etm);
+	gboolean expandable = e_tree_model_node_is_expandable (ets->priv->source, path->corresponding);
+
+	if (path->num_children == -1) {
+		generate_children (ets, node);
+	}
+
+	return expandable;
+}
+
+static guint
+ets_get_children (ETreeModel *etm,
+                  ETreePath node,
+                  ETreePath **nodes)
+{
+	ETreeSortedPath *path = node;
+	guint n_children;
+
+	if (path->num_children == -1) {
+		generate_children (E_TREE_SORTED (etm), node);
+	}
+
+	n_children = path->num_children;
+
+	if (nodes) {
+		gint i;
+
+		(*nodes) = g_malloc (sizeof (ETreePath) * n_children);
+		for (i = 0; i < n_children; i++) {
+			(*nodes)[i] = path->children[i];
+		}
+	}
+
+	return n_children;
+}
+
+static guint
+ets_depth (ETreeModel *etm,
+           ETreePath node)
+{
+	ETreeSortedPath *path = node;
+	ETreeSorted *ets = E_TREE_SORTED (etm);
+
+	return e_tree_model_node_depth (ets->priv->source, path->corresponding);
+}
+
+static GdkPixbuf *
+ets_icon_at (ETreeModel *etm,
+             ETreePath node)
+{
+	ETreeSortedPath *path = node;
+	ETreeSorted *ets = E_TREE_SORTED (etm);
+
+	return e_tree_model_icon_at (ets->priv->source, path->corresponding);
+}
+
+static gboolean
+ets_get_expanded_default (ETreeModel *etm)
+{
+	ETreeSorted *ets = E_TREE_SORTED (etm);
+
+	return e_tree_model_get_expanded_default (ets->priv->source);
+}
+
+static gint
+ets_column_count (ETreeModel *etm)
+{
+	ETreeSorted *ets = E_TREE_SORTED (etm);
+
+	return e_tree_model_column_count (ets->priv->source);
+}
+
+static gboolean
+ets_has_save_id (ETreeModel *etm)
+{
+	return TRUE;
+}
+
+static gchar *
+ets_get_save_id (ETreeModel *etm,
+                 ETreePath node)
+{
+	ETreeSorted *ets = E_TREE_SORTED (etm);
+	ETreeSortedPath *path = node;
+
+	if (e_tree_model_has_save_id (ets->priv->source))
+		return e_tree_model_get_save_id (ets->priv->source, path->corresponding);
+	else
+		return g_strdup_printf ("%p", path->corresponding);
+}
+
+static gboolean
+ets_has_get_node_by_id (ETreeModel *etm)
+{
+	ETreeSorted *ets = E_TREE_SORTED (etm);
+	return e_tree_model_has_get_node_by_id (ets->priv->source);
+}
+
+static ETreePath
+ets_get_node_by_id (ETreeModel *etm,
+                    const gchar *save_id)
+{
+	ETreeSorted *ets = E_TREE_SORTED (etm);
+	ETreePath node;
+
+	node = e_tree_model_get_node_by_id (ets->priv->source, save_id);
+
+	return find_path (ets, node);
+}
+
+static gboolean
+ets_has_change_pending (ETreeModel *etm)
+{
+	ETreeSorted *ets = E_TREE_SORTED (etm);
+
+	return ets->priv->sort_idle_id != 0;
+}
+
+static gpointer
+ets_value_at (ETreeModel *etm,
+              ETreePath node,
+              gint col)
+{
+	ETreeSorted *ets = E_TREE_SORTED (etm);
+	ETreeSortedPath *path = node;
+
+	return e_tree_model_value_at (ets->priv->source, path->corresponding, col);
+}
+
+static void
+ets_set_value_at (ETreeModel *etm,
+                  ETreePath node,
+                  gint col,
+                  gconstpointer val)
+{
+	ETreeSorted *ets = E_TREE_SORTED (etm);
+	ETreeSortedPath *path = node;
+
+	e_tree_model_set_value_at (ets->priv->source, path->corresponding, col, val);
+}
+
+static gboolean
+ets_is_editable (ETreeModel *etm,
+                 ETreePath node,
+                 gint col)
+{
+	ETreeSorted *ets = E_TREE_SORTED (etm);
+	ETreeSortedPath *path = node;
+
+	return e_tree_model_node_is_editable (ets->priv->source, path->corresponding, col);
+}
+
+/* The default for ets_duplicate_value is to return the raw value. */
+static gpointer
+ets_duplicate_value (ETreeModel *etm,
+                     gint col,
+                     gconstpointer value)
+{
+	ETreeSorted *ets = E_TREE_SORTED (etm);
+
+	return e_tree_model_duplicate_value (ets->priv->source, col, value);
+}
+
+static void
+ets_free_value (ETreeModel *etm,
+                gint col,
+                gpointer value)
+{
+	ETreeSorted *ets = E_TREE_SORTED (etm);
+
+	e_tree_model_free_value (ets->priv->source, col, value);
+}
+
+static gpointer
+ets_initialize_value (ETreeModel *etm,
+                      gint col)
+{
+	ETreeSorted *ets = E_TREE_SORTED (etm);
+
+	return e_tree_model_initialize_value (ets->priv->source, col);
+}
+
+static gboolean
+ets_value_is_empty (ETreeModel *etm,
+                    gint col,
+                    gconstpointer value)
+{
+	ETreeSorted *ets = E_TREE_SORTED (etm);
+
+	return e_tree_model_value_is_empty (ets->priv->source, col, value);
+}
+
+static gchar *
+ets_value_to_string (ETreeModel *etm,
+                     gint col,
+                     gconstpointer value)
+{
+	ETreeSorted *ets = E_TREE_SORTED (etm);
+
+	return e_tree_model_value_to_string (ets->priv->source, col, value);
+}
+
+/* Proxy functions */
+
+static void
+ets_proxy_pre_change (ETreeModel *etm,
+                      ETreeSorted *ets)
+{
+	e_tree_model_pre_change (E_TREE_MODEL (ets));
+}
+
+static void
+ets_proxy_no_change (ETreeModel *etm,
+                     ETreeSorted *ets)
+{
+	e_tree_model_no_change (E_TREE_MODEL (ets));
+}
+
+static void
+ets_proxy_node_changed (ETreeModel *etm,
+                        ETreePath node,
+                        ETreeSorted *ets)
+{
+	ets->priv->last_access = NULL;
+	d (g_print ("Setting last access %p. (ets_proxy_node_changed)\n", ets->priv->last_access));
+
+	if (e_tree_model_node_is_root (ets->priv->source, node)) {
+		ets_stop_sort_idle (ets);
+
+		if (ets->priv->root) {
+			free_path (ets->priv->root);
+		}
+		ets->priv->root = new_path (NULL, node);
+		e_tree_model_node_changed (E_TREE_MODEL (ets), ets->priv->root);
+		return;
+	} else {
+		ETreeSortedPath *path = find_path (ets, node);
+
+		if (path) {
+			free_children (path);
+			if (!reposition_path (ets, path)) {
+				e_tree_model_node_changed (E_TREE_MODEL (ets), path);
+			} else {
+				e_tree_model_no_change (E_TREE_MODEL (ets));
+			}
+		} else {
+			e_tree_model_no_change (E_TREE_MODEL (ets));
+		}
+	}
+}
+
+static void
+ets_proxy_node_data_changed (ETreeModel *etm,
+                             ETreePath node,
+                             ETreeSorted *ets)
+{
+	ETreeSortedPath *path = find_path (ets, node);
+
+	if (path) {
+		if (!reposition_path (ets, path))
+			e_tree_model_node_data_changed (E_TREE_MODEL (ets), path);
+		else
+			e_tree_model_no_change (E_TREE_MODEL (ets));
+	} else
+		e_tree_model_no_change (E_TREE_MODEL (ets));
+}
+
+static void
+ets_proxy_node_col_changed (ETreeModel *etm,
+                            ETreePath node,
+                            gint col,
+                            ETreeSorted *ets)
+{
+	ETreeSortedPath *path = find_path (ets, node);
+
+	if (path) {
+		gboolean changed = FALSE;
+		if (e_table_sorting_utils_affects_sort (ets->priv->sort_info, ets->priv->full_header, col))
+			changed = reposition_path (ets, path);
+		if (!changed)
+			e_tree_model_node_col_changed (E_TREE_MODEL (ets), path, col);
+		else
+			e_tree_model_no_change (E_TREE_MODEL (ets));
+	} else
+		e_tree_model_no_change (E_TREE_MODEL (ets));
+}
+
+static void
+ets_proxy_node_inserted (ETreeModel *etm,
+                         ETreePath parent,
+                         ETreePath child,
+                         ETreeSorted *ets)
+{
+	ETreeSortedPath *parent_path = find_path (ets, parent);
+
+	if (parent_path && parent_path->num_children != -1) {
+		gint i;
+		gint j;
+		ETreeSortedPath *path;
+		gint position = parent_path->num_children;
+		ETreePath counter;
+
+		for (counter = e_tree_model_node_get_next (etm, child);
+		     counter;
+		     counter = e_tree_model_node_get_next (etm, counter))
+			position--;
+
+		if (position != parent_path->num_children) {
+			for (i = 0; i < parent_path->num_children; i++) {
+				if (parent_path->children[i]->orig_position >= position)
+					parent_path->children[i]->orig_position++;
+			}
+		}
+
+		i = parent_path->num_children;
+		path = new_path (parent_path, child);
+		path->orig_position = position;
+		if (!ETS_SORT_IDLE_ACTIVATED (ets)) {
+			ets->priv->insert_count++;
+			if (ets->priv->insert_count > ETS_INSERT_MAX) {
+				/* schedule a sort, and append instead */
+				schedule_resort (ets, parent_path, TRUE, FALSE);
+			} else {
+				/* make sure we have an idle handler to reset the count every now and then */
+				if (ets->priv->insert_idle_id == 0) {
+					ets->priv->insert_idle_id = g_idle_add_full (40, (GSourceFunc) ets_insert_idle, ets, NULL);
+				}
+				i = e_table_sorting_utils_tree_insert
+					(ets->priv->source,
+					 ets->priv->sort_info,
+					 ets->priv->full_header,
+					 (ETreePath *) parent_path->children,
+					 parent_path->num_children,
+					 path);
+			}
+		} else {
+			mark_path_needs_resort (ets, parent_path, TRUE, FALSE);
+		}
+		parent_path->num_children++;
+		parent_path->children = g_renew (ETreeSortedPath *, parent_path->children, parent_path->num_children);
+		memmove (parent_path->children + i + 1, parent_path->children + i, (parent_path->num_children - 1 - i) * sizeof (gint));
+		parent_path->children[i] = path;
+		for (j = i; j < parent_path->num_children; j++) {
+			parent_path->children[j]->position = j;
+		}
+		e_tree_model_node_inserted (E_TREE_MODEL (ets), parent_path, parent_path->children[i]);
+	} else if (ets->priv->root == NULL && parent == NULL) {
+		if (child) {
+			ets->priv->root = new_path (NULL, child);
+			e_tree_model_node_inserted (E_TREE_MODEL (ets), NULL, ets->priv->root);
+		} else {
+			e_tree_model_no_change (E_TREE_MODEL (ets));
+		}
+	} else {
+		e_tree_model_no_change (E_TREE_MODEL (ets));
+	}
+}
+
+static void
+ets_proxy_node_removed (ETreeModel *etm,
+                        ETreePath parent,
+                        ETreePath child,
+                        gint old_position,
+                        ETreeSorted *ets)
+{
+	ETreeSortedPath *parent_path = find_path (ets, parent);
+	ETreeSortedPath *path;
+
+	if (parent_path)
+		path = find_child_path (ets, parent_path, child);
+	else
+		path = find_path (ets, child);
+
+	d (g_print ("Setting last access %p. (ets_proxy_node_removed)\n ", ets->priv->last_access));
+	ets->priv->last_access = NULL;
+
+	if (path && parent_path && parent_path->num_children != -1) {
+		gint i;
+		for (i = 0; i < parent_path->num_children; i++) {
+			if (parent_path->children[i]->orig_position > old_position)
+				parent_path->children[i]->orig_position--;
+		}
+
+		i = path->position;
+
+		parent_path->num_children--;
+		memmove (parent_path->children + i, parent_path->children + i + 1, sizeof (ETreeSortedPath *) * (parent_path->num_children - i));
+		for (; i < parent_path->num_children; i++) {
+			parent_path->children[i]->position = i;
+		}
+		e_tree_model_node_removed (E_TREE_MODEL (ets), parent_path, path, path->position);
+		free_path (path);
+	} else if (path && path == ets->priv->root) {
+		ets->priv->root = NULL;
+		e_tree_model_node_removed (E_TREE_MODEL (ets), NULL, path, -1);
+		free_path (path);
+	}
+}
+
+static void
+ets_proxy_node_deleted (ETreeModel *etm,
+                        ETreePath child,
+                        ETreeSorted *ets)
+{
+	e_tree_model_node_deleted (E_TREE_MODEL (ets), NULL);
+}
+
+static void
+ets_proxy_node_request_collapse (ETreeModel *etm,
+                                 ETreePath node,
+                                 ETreeSorted *ets)
+{
+	ETreeSortedPath *path = find_path (ets, node);
+	if (path) {
+		e_tree_model_node_request_collapse (E_TREE_MODEL (ets), path);
+	}
+}
+
+static void
+ets_sort_info_changed (ETableSortInfo *sort_info,
+                       ETreeSorted *ets)
+{
+	schedule_resort (ets, ets->priv->root, TRUE, TRUE);
+}
+
+/* Initialization and creation */
+
+static void
+e_tree_sorted_class_init (ETreeSortedClass *class)
+{
+	GObjectClass *object_class;
+	ETreeModelClass *tree_model_class;
+
+	g_type_class_add_private (class, sizeof (ETreeSortedPrivate));
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->dispose = ets_dispose;
+	object_class->finalize = ets_finalize;
+
+	tree_model_class = E_TREE_MODEL_CLASS (class);
+	tree_model_class->get_root = ets_get_root;
+	tree_model_class->get_parent = ets_get_parent;
+	tree_model_class->get_first_child = ets_get_first_child;
+	tree_model_class->get_last_child = ets_get_last_child;
+	tree_model_class->get_prev = ets_get_prev;
+	tree_model_class->get_next = ets_get_next;
+
+	tree_model_class->is_root = ets_is_root;
+	tree_model_class->is_expandable = ets_is_expandable;
+	tree_model_class->get_children = ets_get_children;
+	tree_model_class->depth = ets_depth;
+
+	tree_model_class->icon_at = ets_icon_at;
+
+	tree_model_class->get_expanded_default = ets_get_expanded_default;
+	tree_model_class->column_count = ets_column_count;
+
+	tree_model_class->has_save_id = ets_has_save_id;
+	tree_model_class->get_save_id = ets_get_save_id;
+
+	tree_model_class->has_get_node_by_id = ets_has_get_node_by_id;
+	tree_model_class->get_node_by_id = ets_get_node_by_id;
+
+	tree_model_class->has_change_pending = ets_has_change_pending;
+
+	tree_model_class->value_at = ets_value_at;
+	tree_model_class->set_value_at = ets_set_value_at;
+	tree_model_class->is_editable = ets_is_editable;
+
+	tree_model_class->duplicate_value = ets_duplicate_value;
+	tree_model_class->free_value = ets_free_value;
+	tree_model_class->initialize_value = ets_initialize_value;
+	tree_model_class->value_is_empty = ets_value_is_empty;
+	tree_model_class->value_to_string = ets_value_to_string;
+
+	signals[NODE_RESORTED] = g_signal_new (
+		"node_resorted",
+		G_TYPE_FROM_CLASS (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ETreeSortedClass, node_resorted),
+		(GSignalAccumulator) NULL, NULL,
+		g_cclosure_marshal_VOID__POINTER,
+		G_TYPE_NONE, 1,
+		G_TYPE_POINTER);
+}
+
+static void
+e_tree_sorted_init (ETreeSorted *ets)
+{
+	ets->priv = E_TREE_SORTED_GET_PRIVATE (ets);
+}
+
+/**
+ * e_tree_sorted_construct:
+ * @etree:
+ *
+ *
+ **/
+void
+e_tree_sorted_construct (ETreeSorted *ets,
+                         ETreeModel *source,
+                         ETableHeader *full_header,
+                         ETableSortInfo *sort_info)
+{
+	ets->priv->source                              = source;
+	if (source)
+		g_object_ref (source);
+
+	ets->priv->full_header                         = full_header;
+	if (full_header)
+		g_object_ref (full_header);
+
+	e_tree_sorted_set_sort_info (ets, sort_info);
+
+	ets->priv->tree_model_pre_change_id = g_signal_connect (
+		source, "pre_change",
+		G_CALLBACK (ets_proxy_pre_change), ets);
+	ets->priv->tree_model_no_change_id = g_signal_connect (
+		source, "no_change",
+		G_CALLBACK (ets_proxy_no_change), ets);
+	ets->priv->tree_model_node_changed_id = g_signal_connect (
+		source, "node_changed",
+		G_CALLBACK (ets_proxy_node_changed), ets);
+	ets->priv->tree_model_node_data_changed_id = g_signal_connect (
+		source, "node_data_changed",
+		G_CALLBACK (ets_proxy_node_data_changed), ets);
+	ets->priv->tree_model_node_col_changed_id = g_signal_connect (
+		source, "node_col_changed",
+		G_CALLBACK (ets_proxy_node_col_changed), ets);
+	ets->priv->tree_model_node_inserted_id = g_signal_connect (
+		source, "node_inserted",
+		G_CALLBACK (ets_proxy_node_inserted), ets);
+	ets->priv->tree_model_node_removed_id = g_signal_connect (
+		source, "node_removed",
+		G_CALLBACK (ets_proxy_node_removed), ets);
+	ets->priv->tree_model_node_deleted_id = g_signal_connect (
+		source, "node_deleted",
+		G_CALLBACK (ets_proxy_node_deleted), ets);
+	ets->priv->tree_model_node_request_collapse_id = g_signal_connect (
+		source, "node_request_collapse",
+		G_CALLBACK (ets_proxy_node_request_collapse), ets);
+
+}
+
+/**
+ * e_tree_sorted_new
+ *
+ * FIXME docs here.
+ *
+ * return values: a newly constructed ETreeSorted.
+ */
+ETreeSorted *
+e_tree_sorted_new (ETreeModel *source,
+                   ETableHeader *full_header,
+                   ETableSortInfo *sort_info)
+{
+	ETreeSorted *ets = g_object_new (E_TYPE_TREE_SORTED, NULL);
+
+	e_tree_sorted_construct (ets, source, full_header, sort_info);
+
+	return ets;
+}
+
+ETreePath
+e_tree_sorted_view_to_model_path (ETreeSorted *ets,
+                                  ETreePath view_path)
+{
+	ETreeSortedPath *path = view_path;
+	if (path) {
+		ets->priv->last_access = path;
+		d (g_print ("Setting last access %p. (e_tree_sorted_view_to_model_path)\n", ets->priv->last_access));
+		return path->corresponding;
+	} else
+		return NULL;
+}
+
+ETreePath
+e_tree_sorted_model_to_view_path (ETreeSorted *ets,
+                                  ETreePath model_path)
+{
+	return find_or_create_path (ets, model_path);
+}
+
+gint
+e_tree_sorted_orig_position (ETreeSorted *ets,
+                             ETreePath path)
+{
+	ETreeSortedPath *sorted_path = path;
+	return sorted_path->orig_position;
+}
+
+gint
+e_tree_sorted_node_num_children (ETreeSorted *ets,
+                                 ETreePath path)
+{
+	ETreeSortedPath *sorted_path = path;
+
+	if (sorted_path->num_children == -1) {
+		generate_children (ets, sorted_path);
+	}
+
+	return sorted_path->num_children;
+}
+
+void
+e_tree_sorted_node_resorted (ETreeSorted *sorted,
+                             ETreePath node)
+{
+	g_return_if_fail (sorted != NULL);
+	g_return_if_fail (E_IS_TREE_SORTED (sorted));
+
+	g_signal_emit (sorted, signals[NODE_RESORTED], 0, node);
+}
+
+void
+e_tree_sorted_set_sort_info (ETreeSorted *ets,
+                             ETableSortInfo *sort_info)
+{
+
+	g_return_if_fail (ets != NULL);
+
+	if (ets->priv->sort_info) {
+		if (ets->priv->sort_info_changed_id != 0)
+			g_signal_handler_disconnect (
+				ets->priv->sort_info,
+				ets->priv->sort_info_changed_id);
+		ets->priv->sort_info_changed_id = 0;
+		g_object_unref (ets->priv->sort_info);
+	}
+
+	ets->priv->sort_info = sort_info;
+	if (sort_info) {
+		g_object_ref (sort_info);
+		ets->priv->sort_info_changed_id = g_signal_connect (
+			ets->priv->sort_info, "sort_info_changed",
+			G_CALLBACK (ets_sort_info_changed), ets);
+	}
+
+	if (ets->priv->root)
+		schedule_resort (ets, ets->priv->root, TRUE, TRUE);
+}
+
+ETableSortInfo *
+e_tree_sorted_get_sort_info (ETreeSorted *ets)
+{
+	return ets->priv->sort_info;
+}
+
diff --git a/e-util/e-tree-sorted.h b/e-util/e-tree-sorted.h
new file mode 100644
index 0000000..b6dacaf
--- /dev/null
+++ b/e-util/e-tree-sorted.h
@@ -0,0 +1,104 @@
+/*
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef _E_TREE_SORTED_H_
+#define _E_TREE_SORTED_H_
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#include <e-util/e-table-header.h>
+#include <e-util/e-table-sort-info.h>
+#include <e-util/e-tree-model.h>
+
+/* Standard GObject macros */
+#define E_TYPE_TREE_SORTED \
+	(e_tree_sorted_get_type ())
+#define E_TREE_SORTED(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_TREE_SORTED, ETreeSorted))
+#define E_TREE_SORTED_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_TREE_SORTED, ETreeSortedClass))
+#define E_IS_TREE_SORTED(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_TREE_SORTED))
+#define E_IS_TREE_SORTED_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_TREE_SORTED))
+#define E_TREE_SORTED_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_TREE_SORTED, ETreeSortedClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ETreeSorted ETreeSorted;
+typedef struct _ETreeSortedClass ETreeSortedClass;
+typedef struct _ETreeSortedPrivate ETreeSortedPrivate;
+
+struct _ETreeSorted {
+	ETreeModel parent;
+	ETreeSortedPrivate *priv;
+};
+
+struct _ETreeSortedClass {
+	ETreeModelClass parent_class;
+
+	/* Signals */
+	void		(*node_resorted)	(ETreeSorted *etm,
+						 ETreePath node);
+};
+
+GType		e_tree_sorted_get_type		(void) G_GNUC_CONST;
+void		e_tree_sorted_construct		(ETreeSorted *etree,
+						 ETreeModel *source,
+						 ETableHeader *full_header,
+						 ETableSortInfo *sort_info);
+ETreeSorted *	e_tree_sorted_new		(ETreeModel *source,
+						 ETableHeader *full_header,
+						 ETableSortInfo *sort_info);
+
+ETreePath	e_tree_sorted_view_to_model_path
+						(ETreeSorted *ets,
+						 ETreePath view_path);
+ETreePath	e_tree_sorted_model_to_view_path
+						(ETreeSorted *ets,
+						 ETreePath model_path);
+gint		e_tree_sorted_orig_position	(ETreeSorted *ets,
+						 ETreePath path);
+gint		e_tree_sorted_node_num_children	(ETreeSorted *ets,
+						 ETreePath path);
+
+void		e_tree_sorted_node_resorted	(ETreeSorted *tree_model,
+						 ETreePath node);
+
+ETableSortInfo *e_tree_sorted_get_sort_info	(ETreeSorted *tree_model);
+void		e_tree_sorted_set_sort_info	(ETreeSorted *tree_model,
+						 ETableSortInfo *sort_info);
+
+G_END_DECLS
+
+#endif /* _E_TREE_SORTED_H */
diff --git a/e-util/e-tree-table-adapter.c b/e-util/e-tree-table-adapter.c
new file mode 100644
index 0000000..f76f11b
--- /dev/null
+++ b/e-util/e-tree-table-adapter.c
@@ -0,0 +1,1414 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *		Chris Toshok <toshok ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-tree-table-adapter.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <glib/gstdio.h>
+
+#include <libxml/tree.h>
+#include <libxml/parser.h>
+
+#include <libedataserver/libedataserver.h>
+
+#include "e-marshal.h"
+#include "e-table-sorting-utils.h"
+#include "e-xml-utils.h"
+
+#define E_TREE_TABLE_ADAPTER_GET_PRIVATE(obj) \
+	(G_TYPE_INSTANCE_GET_PRIVATE \
+	((obj), E_TYPE_TREE_TABLE_ADAPTER, ETreeTableAdapterPrivate))
+
+/* workaround for avoiding API breakage */
+#define etta_get_type e_tree_table_adapter_get_type
+G_DEFINE_TYPE (ETreeTableAdapter, etta, E_TYPE_TABLE_MODEL)
+#define d(x)
+
+#define INCREMENT_AMOUNT 100
+
+enum {
+	SORTING_CHANGED,
+	LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0, };
+
+typedef struct {
+	ETreePath path;
+	guint32 num_visible_children;
+	guint32 index;
+
+	guint expanded : 1;
+	guint expandable : 1;
+	guint expandable_set : 1;
+} node_t;
+
+struct _ETreeTableAdapterPrivate {
+	ETreeModel     *source;
+	ETableSortInfo *sort_info;
+	ETableHeader   *header;
+
+	gint	     n_map;
+	gint	     n_vals_allocated;
+	node_t     **map_table;
+	GHashTable  *nodes;
+	GNode       *root;
+
+	guint        root_visible : 1;
+	guint        remap_needed : 1;
+
+	gint          last_access;
+
+	gint          pre_change_id;
+	gint          no_change_id;
+	gint	     rebuilt_id;
+	gint          node_changed_id;
+	gint          node_data_changed_id;
+	gint          node_col_changed_id;
+	gint          node_inserted_id;
+	gint          node_removed_id;
+	gint          node_request_collapse_id;
+	gint          sort_info_changed_id;
+
+	guint        resort_idle_id;
+
+	gint          force_expanded_state; /* use this instead of model's default if not 0; <0 ... collapse, >0 ... expand */
+};
+
+static void etta_sort_info_changed (ETableSortInfo *sort_info, ETreeTableAdapter *etta);
+
+static GNode *
+lookup_gnode (ETreeTableAdapter *etta,
+              ETreePath path)
+{
+	GNode *gnode;
+
+	if (!path)
+		return NULL;
+
+	gnode = g_hash_table_lookup (etta->priv->nodes, path);
+
+	return gnode;
+}
+
+static void
+resize_map (ETreeTableAdapter *etta,
+            gint size)
+{
+	if (size > etta->priv->n_vals_allocated) {
+		etta->priv->n_vals_allocated = MAX (etta->priv->n_vals_allocated + INCREMENT_AMOUNT, size);
+		etta->priv->map_table = g_renew (node_t *, etta->priv->map_table, etta->priv->n_vals_allocated);
+	}
+
+	etta->priv->n_map = size;
+}
+
+static void
+move_map_elements (ETreeTableAdapter *etta,
+                   gint to,
+                   gint from,
+                   gint count)
+{
+	if (count <= 0 || from >= etta->priv->n_map)
+		return;
+	memmove (etta->priv->map_table + to, etta->priv->map_table + from, count * sizeof (node_t *));
+	etta->priv->remap_needed = TRUE;
+}
+
+static gint
+fill_map (ETreeTableAdapter *etta,
+          gint index,
+          GNode *gnode)
+{
+	GNode *p;
+
+	if ((gnode != etta->priv->root) || etta->priv->root_visible)
+		etta->priv->map_table[index++] = gnode->data;
+
+	for (p = gnode->children; p; p = p->next)
+		index = fill_map (etta, index, p);
+
+	etta->priv->remap_needed = TRUE;
+	return index;
+}
+
+static void
+remap_indices (ETreeTableAdapter *etta)
+{
+	gint i;
+	for (i = 0; i < etta->priv->n_map; i++)
+		etta->priv->map_table[i]->index = i;
+	etta->priv->remap_needed = FALSE;
+}
+
+static node_t *
+get_node (ETreeTableAdapter *etta,
+          ETreePath path)
+{
+	GNode *gnode = lookup_gnode (etta, path);
+
+	if (!gnode)
+		return NULL;
+
+	return (node_t *) gnode->data;
+}
+
+static void
+resort_node (ETreeTableAdapter *etta,
+             GNode *gnode,
+             gboolean recurse)
+{
+	node_t *node = (node_t *) gnode->data;
+	ETreePath *paths, path;
+	GNode *prev, *curr;
+	gint i, count;
+	gboolean sort_needed;
+
+	if (node->num_visible_children == 0)
+		return;
+
+	sort_needed = etta->priv->sort_info && e_table_sort_info_sorting_get_count (etta->priv->sort_info) > 0;
+
+	for (i = 0, path = e_tree_model_node_get_first_child (etta->priv->source, node->path); path;
+	     path = e_tree_model_node_get_next (etta->priv->source, path), i++);
+
+	count = i;
+	if (count <= 1)
+		return;
+
+	paths = g_new0 (ETreePath, count);
+
+	for (i = 0, path = e_tree_model_node_get_first_child (etta->priv->source, node->path); path;
+	     path = e_tree_model_node_get_next (etta->priv->source, path), i++)
+		paths[i] = path;
+
+	if (count > 1 && sort_needed)
+		e_table_sorting_utils_tree_sort (etta->priv->source, etta->priv->sort_info, etta->priv->header, paths, count);
+
+	prev = NULL;
+	for (i = 0; i < count; i++) {
+		curr = lookup_gnode (etta, paths[i]);
+		if (!curr)
+			continue;
+
+		if (prev)
+			prev->next = curr;
+		else
+			gnode->children = curr;
+
+		curr->prev = prev;
+		curr->next = NULL;
+		prev = curr;
+		if (recurse)
+			resort_node (etta, curr, recurse);
+	}
+
+	g_free (paths);
+}
+
+static gint
+get_row (ETreeTableAdapter *etta,
+         ETreePath path)
+{
+	node_t *node = get_node (etta, path);
+	if (!node)
+		return -1;
+
+	if (etta->priv->remap_needed)
+		remap_indices (etta);
+
+	return node->index;
+}
+
+static ETreePath
+get_path (ETreeTableAdapter *etta,
+          gint row)
+{
+	if (row == -1 && etta->priv->n_map > 0)
+		row = etta->priv->n_map - 1;
+	else if (row < 0 || row >= etta->priv->n_map)
+		return NULL;
+
+	return etta->priv->map_table[row]->path;
+}
+
+static void
+kill_gnode (GNode *node,
+            ETreeTableAdapter *etta)
+{
+	g_hash_table_remove (etta->priv->nodes, ((node_t *) node->data)->path);
+
+	while (node->children) {
+		GNode *next = node->children->next;
+		kill_gnode (node->children, etta);
+		node->children = next;
+	}
+
+	g_free (node->data);
+	if (node == etta->priv->root)
+		etta->priv->root = NULL;
+	g_node_destroy (node);
+}
+
+static void
+update_child_counts (GNode *gnode,
+                     gint delta)
+{
+	while (gnode) {
+		node_t *node = (node_t *) gnode->data;
+		node->num_visible_children += delta;
+		gnode = gnode->parent;
+	}
+}
+
+static gint
+delete_children (ETreeTableAdapter *etta,
+                 GNode *gnode)
+{
+	node_t *node = (node_t *) gnode->data;
+	gint to_remove = node ? node->num_visible_children : 0;
+
+	if (to_remove == 0)
+		return 0;
+
+	while (gnode->children) {
+		GNode *next = gnode->children->next;
+		kill_gnode (gnode->children, etta);
+		gnode->children = next;
+	}
+
+	return to_remove;
+}
+
+static void
+delete_node (ETreeTableAdapter *etta,
+             ETreePath parent,
+             ETreePath path)
+{
+	gint to_remove = 1;
+	gint parent_row = get_row (etta, parent);
+	gint row = get_row (etta, path);
+	GNode *gnode = lookup_gnode (etta, path);
+	GNode *parent_gnode = lookup_gnode (etta, parent);
+
+	e_table_model_pre_change (E_TABLE_MODEL (etta));
+
+	if (row == -1) {
+		e_table_model_no_change (E_TABLE_MODEL (etta));
+		return;
+	}
+
+	to_remove += delete_children (etta, gnode);
+	kill_gnode (gnode, etta);
+
+	move_map_elements (etta, row, row + to_remove, etta->priv->n_map - row - to_remove);
+	resize_map (etta, etta->priv->n_map - to_remove);
+
+	if (parent_gnode != NULL) {
+		node_t *parent_node = parent_gnode->data;
+		gboolean expandable = e_tree_model_node_is_expandable (etta->priv->source, parent);
+
+		update_child_counts (parent_gnode, - to_remove);
+		if (parent_node->expandable != expandable) {
+			e_table_model_pre_change (E_TABLE_MODEL (etta));
+			parent_node->expandable = expandable;
+			e_table_model_row_changed (E_TABLE_MODEL (etta), parent_row);
+		}
+
+		resort_node (etta, parent_gnode, FALSE);
+	}
+
+	e_table_model_rows_deleted (E_TABLE_MODEL (etta), row, to_remove);
+}
+
+static GNode *
+create_gnode (ETreeTableAdapter *etta,
+              ETreePath path)
+{
+	GNode *gnode;
+	node_t *node;
+
+	node = g_new0 (node_t, 1);
+	node->path = path;
+	node->index = -1;
+	node->expanded = etta->priv->force_expanded_state == 0 ? e_tree_model_get_expanded_default (etta->priv->source) : etta->priv->force_expanded_state > 0;
+	node->expandable = e_tree_model_node_is_expandable (etta->priv->source, path);
+	node->expandable_set = 1;
+	node->num_visible_children = 0;
+	gnode = g_node_new (node);
+	g_hash_table_insert (etta->priv->nodes, path, gnode);
+	return gnode;
+}
+
+static gint
+insert_children (ETreeTableAdapter *etta,
+                 GNode *gnode)
+{
+	ETreePath path, tmp;
+	gint count = 0;
+	gint pos = 0;
+
+	path = ((node_t *) gnode->data)->path;
+	for (tmp = e_tree_model_node_get_first_child (etta->priv->source, path);
+	     tmp;
+	     tmp = e_tree_model_node_get_next (etta->priv->source, tmp), pos++) {
+		GNode *child = create_gnode (etta, tmp);
+		node_t *node = (node_t *) child->data;
+		if (node->expanded)
+			node->num_visible_children = insert_children (etta, child);
+		g_node_prepend (gnode, child);
+		count += node->num_visible_children + 1;
+	}
+	g_node_reverse_children (gnode);
+	return count;
+}
+
+static void
+generate_tree (ETreeTableAdapter *etta,
+               ETreePath path)
+{
+	GNode *gnode;
+	node_t *node;
+	gint size;
+
+	e_table_model_pre_change (E_TABLE_MODEL (etta));
+
+	g_return_if_fail (e_tree_model_node_is_root (etta->priv->source, path));
+
+	if (etta->priv->root)
+		kill_gnode (etta->priv->root, etta);
+	resize_map (etta, 0);
+
+	gnode = create_gnode (etta, path);
+	node = (node_t *) gnode->data;
+	node->expanded = TRUE;
+	node->num_visible_children = insert_children (etta, gnode);
+	if (etta->priv->sort_info && e_table_sort_info_sorting_get_count (etta->priv->sort_info) > 0)
+		resort_node (etta, gnode, TRUE);
+
+	etta->priv->root = gnode;
+	size =  etta->priv->root_visible ? node->num_visible_children + 1 : node->num_visible_children;
+	resize_map (etta, size);
+	fill_map (etta, 0, gnode);
+	e_table_model_changed (E_TABLE_MODEL (etta));
+}
+
+static void
+insert_node (ETreeTableAdapter *etta,
+             ETreePath parent,
+             ETreePath path)
+{
+	GNode *gnode, *parent_gnode;
+	node_t *node, *parent_node;
+	gboolean expandable;
+	gint size, row;
+
+	e_table_model_pre_change (E_TABLE_MODEL (etta));
+
+	if (get_node (etta, path)) {
+		e_table_model_no_change (E_TABLE_MODEL (etta));
+		return;
+	}
+
+	parent_gnode = lookup_gnode (etta, parent);
+	if (!parent_gnode) {
+		ETreePath grandparent = e_tree_model_node_get_parent (etta->priv->source, parent);
+		if (e_tree_model_node_is_root (etta->priv->source, parent))
+			generate_tree (etta, parent);
+		else
+			insert_node (etta, grandparent, parent);
+		e_table_model_changed (E_TABLE_MODEL (etta));
+		return;
+	}
+
+	parent_node = (node_t *) parent_gnode->data;
+
+	if (parent_gnode != etta->priv->root) {
+		expandable = e_tree_model_node_is_expandable (etta->priv->source, parent);
+		if (parent_node->expandable != expandable) {
+			e_table_model_pre_change (E_TABLE_MODEL (etta));
+			parent_node->expandable = expandable;
+			parent_node->expandable_set = 1;
+			e_table_model_row_changed (E_TABLE_MODEL (etta), parent_node->index);
+		}
+	}
+
+	if (!e_tree_table_adapter_node_is_expanded (etta, parent)) {
+		e_table_model_no_change (E_TABLE_MODEL (etta));
+		return;
+	}
+
+	gnode = create_gnode (etta, path);
+	node = (node_t *) gnode->data;
+
+	if (node->expanded)
+		node->num_visible_children = insert_children (etta, gnode);
+
+	g_node_append (parent_gnode, gnode);
+	update_child_counts (parent_gnode, node->num_visible_children + 1);
+	resort_node (etta, parent_gnode, FALSE);
+	resort_node (etta, gnode, TRUE);
+
+	size = node->num_visible_children + 1;
+	resize_map (etta, etta->priv->n_map + size);
+	if (parent_gnode == etta->priv->root)
+		row = 0;
+	else {
+		gint new_size = parent_node->num_visible_children + 1;
+		gint old_size = new_size - size;
+		row = parent_node->index;
+		move_map_elements (etta, row + new_size, row + old_size, etta->priv->n_map - row - new_size);
+	}
+	fill_map (etta, row, parent_gnode);
+	e_table_model_rows_inserted (E_TABLE_MODEL (etta), get_row (etta, path), size);
+}
+
+typedef struct {
+	GSList *paths;
+	gboolean expanded;
+} check_expanded_closure;
+
+static gboolean
+check_expanded (GNode *gnode,
+                gpointer data)
+{
+	check_expanded_closure *closure = (check_expanded_closure *) data;
+	node_t *node = (node_t *) gnode->data;
+
+	if (node->expanded != closure->expanded)
+		closure->paths = g_slist_prepend (closure->paths, node->path);
+
+	return FALSE;
+}
+
+static void
+update_node (ETreeTableAdapter *etta,
+             ETreePath path)
+{
+	check_expanded_closure closure;
+	ETreePath parent = e_tree_model_node_get_parent (etta->priv->source, path);
+	GNode *gnode = lookup_gnode (etta, path);
+	GSList *l;
+
+	closure.expanded = e_tree_model_get_expanded_default (etta->priv->source);
+	closure.paths = NULL;
+
+	if (gnode)
+		g_node_traverse (gnode, G_POST_ORDER, G_TRAVERSE_ALL, -1, check_expanded, &closure);
+
+	if (e_tree_model_node_is_root (etta->priv->source, path))
+		generate_tree (etta, path);
+	else {
+		delete_node (etta, parent, path);
+		insert_node (etta, parent, path);
+	}
+
+	for (l = closure.paths; l; l = l->next)
+		if (lookup_gnode (etta, l->data))
+			e_tree_table_adapter_node_set_expanded (etta, l->data, !closure.expanded);
+
+	g_slist_free (closure.paths);
+}
+
+static void
+etta_finalize (GObject *object)
+{
+	ETreeTableAdapterPrivate *priv;
+
+	priv = E_TREE_TABLE_ADAPTER_GET_PRIVATE (object);
+
+	if (priv->resort_idle_id) {
+		g_source_remove (priv->resort_idle_id);
+		priv->resort_idle_id = 0;
+	}
+
+	if (priv->root) {
+		kill_gnode (priv->root, E_TREE_TABLE_ADAPTER (object));
+		priv->root = NULL;
+	}
+
+	g_hash_table_destroy (priv->nodes);
+
+	g_free (priv->map_table);
+
+	/* Chain up to parent's finalize() method. */
+	G_OBJECT_CLASS (etta_parent_class)->finalize (object);
+}
+
+static void
+etta_dispose (GObject *object)
+{
+	ETreeTableAdapterPrivate *priv;
+
+	priv = E_TREE_TABLE_ADAPTER_GET_PRIVATE (object);
+
+	if (priv->sort_info) {
+		g_signal_handler_disconnect (
+			priv->sort_info, priv->sort_info_changed_id);
+		g_object_unref (priv->sort_info);
+		priv->sort_info = NULL;
+	}
+
+	if (priv->header) {
+		g_object_unref (priv->header);
+		priv->header = NULL;
+	}
+
+	if (priv->source) {
+		g_signal_handler_disconnect (
+			priv->source, priv->pre_change_id);
+		g_signal_handler_disconnect (
+			priv->source, priv->no_change_id);
+		g_signal_handler_disconnect (
+			priv->source, priv->rebuilt_id);
+		g_signal_handler_disconnect (
+			priv->source, priv->node_changed_id);
+		g_signal_handler_disconnect (
+			priv->source, priv->node_data_changed_id);
+		g_signal_handler_disconnect (
+			priv->source, priv->node_col_changed_id);
+		g_signal_handler_disconnect (
+			priv->source, priv->node_inserted_id);
+		g_signal_handler_disconnect (
+			priv->source, priv->node_removed_id);
+		g_signal_handler_disconnect (
+			priv->source, priv->node_request_collapse_id);
+
+		g_object_unref (priv->source);
+		priv->source = NULL;
+	}
+
+	/* Chain up to parent's dispose() method. */
+	G_OBJECT_CLASS (etta_parent_class)->dispose (object);
+}
+
+static gint
+etta_column_count (ETableModel *etm)
+{
+	ETreeTableAdapter *etta = (ETreeTableAdapter *) etm;
+
+	return e_tree_model_column_count (etta->priv->source);
+}
+
+static gboolean
+etta_has_save_id (ETableModel *etm)
+{
+	ETreeTableAdapter *etta = (ETreeTableAdapter *) etm;
+
+	return e_tree_model_has_save_id (etta->priv->source);
+}
+
+static gchar *
+etta_get_save_id (ETableModel *etm,
+                  gint row)
+{
+	ETreeTableAdapter *etta = (ETreeTableAdapter *) etm;
+
+	return e_tree_model_get_save_id (etta->priv->source, get_path (etta, row));
+}
+
+static gboolean
+etta_has_change_pending (ETableModel *etm)
+{
+	ETreeTableAdapter *etta = (ETreeTableAdapter *) etm;
+
+	return e_tree_model_has_change_pending (etta->priv->source);
+}
+
+static gint
+etta_row_count (ETableModel *etm)
+{
+	ETreeTableAdapter *etta = (ETreeTableAdapter *) etm;
+
+	return etta->priv->n_map;
+}
+
+static gpointer
+etta_value_at (ETableModel *etm,
+               gint col,
+               gint row)
+{
+	ETreeTableAdapter *etta = (ETreeTableAdapter *) etm;
+
+	switch (col) {
+	case -1:
+		if (row == -1)
+			return NULL;
+		return get_path (etta, row);
+	case -2:
+		return etta->priv->source;
+	case -3:
+		return etta;
+	default:
+		return e_tree_model_value_at (etta->priv->source, get_path (etta, row), col);
+	}
+}
+
+static void
+etta_set_value_at (ETableModel *etm,
+                   gint col,
+                   gint row,
+                   gconstpointer val)
+{
+	ETreeTableAdapter *etta = (ETreeTableAdapter *) etm;
+
+	e_tree_model_set_value_at (etta->priv->source, get_path (etta, row), col, val);
+}
+
+static gboolean
+etta_is_cell_editable (ETableModel *etm,
+                       gint col,
+                       gint row)
+{
+	ETreeTableAdapter *etta = (ETreeTableAdapter *) etm;
+
+	return e_tree_model_node_is_editable (etta->priv->source, get_path (etta, row), col);
+}
+
+static void
+etta_append_row (ETableModel *etm,
+                 ETableModel *source,
+                 gint row)
+{
+}
+
+static gpointer
+etta_duplicate_value (ETableModel *etm,
+                      gint col,
+                      gconstpointer value)
+{
+	ETreeTableAdapter *etta = (ETreeTableAdapter *) etm;
+
+	return e_tree_model_duplicate_value (etta->priv->source, col, value);
+}
+
+static void
+etta_free_value (ETableModel *etm,
+                 gint col,
+                 gpointer value)
+{
+	ETreeTableAdapter *etta = (ETreeTableAdapter *) etm;
+
+	e_tree_model_free_value (etta->priv->source, col, value);
+}
+
+static gpointer
+etta_initialize_value (ETableModel *etm,
+                       gint col)
+{
+	ETreeTableAdapter *etta = (ETreeTableAdapter *) etm;
+
+	return e_tree_model_initialize_value (etta->priv->source, col);
+}
+
+static gboolean
+etta_value_is_empty (ETableModel *etm,
+                     gint col,
+                     gconstpointer value)
+{
+	ETreeTableAdapter *etta = (ETreeTableAdapter *) etm;
+
+	return e_tree_model_value_is_empty (etta->priv->source, col, value);
+}
+
+static gchar *
+etta_value_to_string (ETableModel *etm,
+                      gint col,
+                      gconstpointer value)
+{
+	ETreeTableAdapter *etta = (ETreeTableAdapter *) etm;
+
+	return e_tree_model_value_to_string (etta->priv->source, col, value);
+}
+
+static void
+etta_class_init (ETreeTableAdapterClass *class)
+{
+	GObjectClass *object_class;
+	ETableModelClass *table_model_class;
+
+	g_type_class_add_private (class, sizeof (ETreeTableAdapterPrivate));
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->dispose = etta_dispose;
+	object_class->finalize = etta_finalize;
+
+	table_model_class = E_TABLE_MODEL_CLASS (class);
+	table_model_class->column_count = etta_column_count;
+	table_model_class->row_count = etta_row_count;
+	table_model_class->append_row = etta_append_row;
+
+	table_model_class->value_at = etta_value_at;
+	table_model_class->set_value_at = etta_set_value_at;
+	table_model_class->is_cell_editable = etta_is_cell_editable;
+
+	table_model_class->has_save_id = etta_has_save_id;
+	table_model_class->get_save_id = etta_get_save_id;
+
+	table_model_class->has_change_pending = etta_has_change_pending;
+	table_model_class->duplicate_value = etta_duplicate_value;
+	table_model_class->free_value = etta_free_value;
+	table_model_class->initialize_value = etta_initialize_value;
+	table_model_class->value_is_empty = etta_value_is_empty;
+	table_model_class->value_to_string = etta_value_to_string;
+
+	class->sorting_changed = NULL;
+
+	signals[SORTING_CHANGED] = g_signal_new (
+		"sorting_changed",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ETreeTableAdapterClass, sorting_changed),
+		NULL, NULL,
+		e_marshal_BOOLEAN__NONE,
+		G_TYPE_BOOLEAN, 0,
+		G_TYPE_NONE);
+}
+
+static void
+etta_init (ETreeTableAdapter *etta)
+{
+	etta->priv = E_TREE_TABLE_ADAPTER_GET_PRIVATE (etta);
+
+	etta->priv->root_visible = TRUE;
+	etta->priv->remap_needed = TRUE;
+}
+
+static void
+etta_proxy_pre_change (ETreeModel *etm,
+                       ETreeTableAdapter *etta)
+{
+	e_table_model_pre_change (E_TABLE_MODEL (etta));
+}
+
+static void
+etta_proxy_no_change (ETreeModel *etm,
+                      ETreeTableAdapter *etta)
+{
+	e_table_model_no_change (E_TABLE_MODEL (etta));
+}
+
+static void
+etta_proxy_rebuilt (ETreeModel *etm,
+                    ETreeTableAdapter *etta)
+{
+	if (!etta->priv->root)
+		return;
+	kill_gnode (etta->priv->root, etta);
+	etta->priv->root = NULL;
+	g_hash_table_destroy (etta->priv->nodes);
+	etta->priv->nodes = g_hash_table_new (NULL, NULL);
+}
+
+static gboolean
+resort_model (ETreeTableAdapter *etta)
+{
+	etta_sort_info_changed (NULL, etta);
+	etta->priv->resort_idle_id = 0;
+	return FALSE;
+}
+
+static void
+etta_proxy_node_changed (ETreeModel *etm,
+                         ETreePath path,
+                         ETreeTableAdapter *etta)
+{
+	update_node (etta, path);
+	e_table_model_changed (E_TABLE_MODEL (etta));
+
+	/* FIXME: Really it shouldnt be required. But a lot of thread
+	 * which were supposed to be present in the list is way below
+	 */
+	if (!etta->priv->resort_idle_id)
+		etta->priv->resort_idle_id = g_idle_add ((GSourceFunc) resort_model, etta);
+}
+
+static void
+etta_proxy_node_data_changed (ETreeModel *etm,
+                              ETreePath path,
+                              ETreeTableAdapter *etta)
+{
+	gint row = get_row (etta, path);
+
+	if (row == -1) {
+		e_table_model_no_change (E_TABLE_MODEL (etta));
+		return;
+	}
+
+	e_table_model_row_changed (E_TABLE_MODEL (etta), row);
+}
+
+static void
+etta_proxy_node_col_changed (ETreeModel *etm,
+                             ETreePath path,
+                             gint col,
+                             ETreeTableAdapter *etta)
+{
+	gint row = get_row (etta, path);
+
+	if (row == -1) {
+		e_table_model_no_change (E_TABLE_MODEL (etta));
+		return;
+	}
+
+	e_table_model_cell_changed (E_TABLE_MODEL (etta), col, row);
+}
+
+static void
+etta_proxy_node_inserted (ETreeModel *etm,
+                          ETreePath parent,
+                          ETreePath child,
+                          ETreeTableAdapter *etta)
+{
+	if (e_tree_model_node_is_root (etm, child))
+		generate_tree (etta, child);
+	else
+		insert_node (etta, parent, child);
+
+	e_table_model_changed (E_TABLE_MODEL (etta));
+}
+
+static void
+etta_proxy_node_removed (ETreeModel *etm,
+                         ETreePath parent,
+                         ETreePath child,
+                         gint old_position,
+                         ETreeTableAdapter *etta)
+{
+	delete_node (etta, parent, child);
+	e_table_model_changed (E_TABLE_MODEL (etta));
+}
+
+static void
+etta_proxy_node_request_collapse (ETreeModel *etm,
+                                  ETreePath node,
+                                  ETreeTableAdapter *etta)
+{
+	e_tree_table_adapter_node_set_expanded (etta, node, FALSE);
+}
+
+static void
+etta_sort_info_changed (ETableSortInfo *sort_info,
+                        ETreeTableAdapter *etta)
+{
+	if (!etta->priv->root)
+		return;
+
+	/* the function is called also internally, with sort_info = NULL,
+	 * thus skip those in signal emit */
+	if (sort_info) {
+		gboolean handled = FALSE;
+
+		g_signal_emit (etta, signals[SORTING_CHANGED], 0, &handled);
+
+		if (handled)
+			return;
+	}
+
+	e_table_model_pre_change (E_TABLE_MODEL (etta));
+	resort_node (etta, etta->priv->root, TRUE);
+	fill_map (etta, 0, etta->priv->root);
+	e_table_model_changed (E_TABLE_MODEL (etta));
+}
+
+ETableModel *
+e_tree_table_adapter_construct (ETreeTableAdapter *etta,
+                                ETreeModel *source,
+                                ETableSortInfo *sort_info,
+                                ETableHeader *header)
+{
+	ETreePath root;
+
+	etta->priv->source = source;
+	g_object_ref (source);
+
+	etta->priv->sort_info = sort_info;
+	if (sort_info) {
+		g_object_ref (sort_info);
+		etta->priv->sort_info_changed_id = g_signal_connect (
+			sort_info, "sort_info_changed",
+			G_CALLBACK (etta_sort_info_changed), etta);
+	}
+
+	etta->priv->header = header;
+	if (header)
+		g_object_ref (header);
+
+	etta->priv->nodes = g_hash_table_new (NULL, NULL);
+
+	root = e_tree_model_get_root (source);
+
+	if (root)
+		generate_tree (etta, root);
+
+	etta->priv->pre_change_id = g_signal_connect (
+		source, "pre_change",
+		G_CALLBACK (etta_proxy_pre_change), etta);
+	etta->priv->no_change_id = g_signal_connect (
+		source, "no_change",
+		G_CALLBACK (etta_proxy_no_change), etta);
+	etta->priv->rebuilt_id = g_signal_connect (
+		source, "rebuilt",
+		G_CALLBACK (etta_proxy_rebuilt), etta);
+	etta->priv->node_changed_id = g_signal_connect (
+		source, "node_changed",
+		G_CALLBACK (etta_proxy_node_changed), etta);
+	etta->priv->node_data_changed_id = g_signal_connect (
+		source, "node_data_changed",
+		G_CALLBACK (etta_proxy_node_data_changed), etta);
+	etta->priv->node_col_changed_id = g_signal_connect (
+		source, "node_col_changed",
+		G_CALLBACK (etta_proxy_node_col_changed), etta);
+	etta->priv->node_inserted_id = g_signal_connect (
+		source, "node_inserted",
+		G_CALLBACK (etta_proxy_node_inserted), etta);
+	etta->priv->node_removed_id = g_signal_connect (
+		source, "node_removed",
+		G_CALLBACK (etta_proxy_node_removed), etta);
+	etta->priv->node_request_collapse_id = g_signal_connect (
+		source, "node_request_collapse",
+		G_CALLBACK (etta_proxy_node_request_collapse), etta);
+
+	return E_TABLE_MODEL (etta);
+}
+
+ETableModel *
+e_tree_table_adapter_new (ETreeModel *source,
+                          ETableSortInfo *sort_info,
+                          ETableHeader *header)
+{
+	ETreeTableAdapter *etta = g_object_new (E_TYPE_TREE_TABLE_ADAPTER, NULL);
+
+	e_tree_table_adapter_construct (etta, source, sort_info, header);
+
+	return (ETableModel *) etta;
+}
+
+typedef struct {
+	xmlNode *root;
+	gboolean expanded_default;
+	ETreeModel *model;
+} TreeAndRoot;
+
+static void
+save_expanded_state_func (gpointer keyp,
+                          gpointer value,
+                          gpointer data)
+{
+	ETreePath path = keyp;
+	node_t *node = ((GNode *) value)->data;
+	TreeAndRoot *tar = data;
+	xmlNode *xmlnode;
+
+	if (node->expanded != tar->expanded_default) {
+		gchar *save_id = e_tree_model_get_save_id (tar->model, path);
+		xmlnode = xmlNewChild (tar->root, NULL, (const guchar *)"node", NULL);
+		e_xml_set_string_prop_by_name (xmlnode, (const guchar *)"id", save_id);
+		g_free (save_id);
+	}
+}
+
+xmlDoc *
+e_tree_table_adapter_save_expanded_state_xml (ETreeTableAdapter *etta)
+{
+	TreeAndRoot tar;
+	xmlDocPtr doc;
+	xmlNode *root;
+
+	g_return_val_if_fail (etta != NULL, NULL);
+
+	doc = xmlNewDoc ((const guchar *)"1.0");
+	root = xmlNewDocNode (doc, NULL, (const guchar *)"expanded_state", NULL);
+	xmlDocSetRootElement (doc, root);
+
+	tar.model = etta->priv->source;
+	tar.root = root;
+	tar.expanded_default = e_tree_model_get_expanded_default (etta->priv->source);
+
+	e_xml_set_integer_prop_by_name (root, (const guchar *)"vers", 2);
+	e_xml_set_bool_prop_by_name (root, (const guchar *)"default", tar.expanded_default);
+
+	g_hash_table_foreach (etta->priv->nodes, save_expanded_state_func, &tar);
+
+	return doc;
+}
+
+void
+e_tree_table_adapter_save_expanded_state (ETreeTableAdapter *etta,
+                                          const gchar *filename)
+{
+	xmlDoc *doc;
+
+	g_return_if_fail (etta != NULL);
+
+	doc = e_tree_table_adapter_save_expanded_state_xml (etta);
+	if (doc) {
+		e_xml_save_file (filename, doc);
+		xmlFreeDoc (doc);
+	}
+}
+
+static xmlDoc *
+open_file (ETreeTableAdapter *etta,
+           const gchar *filename)
+{
+	xmlDoc *doc;
+	xmlNode *root;
+	gint vers;
+	gboolean model_default, saved_default;
+
+	if (!g_file_test (filename, G_FILE_TEST_EXISTS))
+		return NULL;
+
+#ifdef G_OS_WIN32
+	{
+		gchar *locale_filename = g_win32_locale_filename_from_utf8 (filename);
+		doc = xmlParseFile (locale_filename);
+		g_free (locale_filename);
+	}
+#else
+	doc = xmlParseFile (filename);
+#endif
+
+	if (!doc)
+		return NULL;
+
+	root = xmlDocGetRootElement (doc);
+	if (root == NULL || strcmp ((gchar *) root->name, "expanded_state")) {
+		xmlFreeDoc (doc);
+		return NULL;
+	}
+
+	vers = e_xml_get_integer_prop_by_name_with_default (root, (const guchar *)"vers", 0);
+	if (vers > 2) {
+		xmlFreeDoc (doc);
+		return NULL;
+	}
+	model_default = e_tree_model_get_expanded_default (etta->priv->source);
+	saved_default = e_xml_get_bool_prop_by_name_with_default (root, (const guchar *)"default", !model_default);
+	if (saved_default != model_default) {
+		xmlFreeDoc (doc);
+		return NULL;
+	}
+
+	return doc;
+}
+
+/* state: <0 ... collapse;  0 ... use default; >0 ... expand */
+void
+e_tree_table_adapter_force_expanded_state (ETreeTableAdapter *etta,
+                                           gint state)
+{
+	g_return_if_fail (etta != NULL);
+
+	etta->priv->force_expanded_state = state;
+}
+
+void
+e_tree_table_adapter_load_expanded_state_xml (ETreeTableAdapter *etta,
+                                              xmlDoc *doc)
+{
+	xmlNode *root, *child;
+	gboolean model_default;
+	gboolean file_default = FALSE;
+
+	g_return_if_fail (etta != NULL);
+	g_return_if_fail (doc != NULL);
+
+	root = xmlDocGetRootElement (doc);
+
+	e_table_model_pre_change (E_TABLE_MODEL (etta));
+
+	model_default = e_tree_model_get_expanded_default (etta->priv->source);
+
+	if (!strcmp ((gchar *) root->name, "expanded_state")) {
+		gchar *state;
+
+		state = e_xml_get_string_prop_by_name_with_default (root, (const guchar *)"default", "");
+
+		if (state[0] == 't')
+			file_default = TRUE;
+		else
+			file_default = FALSE; /* Even unspecified we'll consider as false */
+
+		g_free (state);
+	}
+
+	/* Incase the default is changed, lets forget the changes and stick to default */
+
+	if (file_default != model_default) {
+		xmlFreeDoc (doc);
+		return;
+	}
+
+	for (child = root->xmlChildrenNode; child; child = child->next) {
+		gchar *id;
+		ETreePath path;
+
+		if (strcmp ((gchar *) child->name, "node")) {
+			d (g_warning ("unknown node '%s' in %s", child->name, filename));
+			continue;
+		}
+
+		id = e_xml_get_string_prop_by_name_with_default (child, (const guchar *)"id", "");
+
+		if (!strcmp (id, "")) {
+			g_free (id);
+			continue;
+		}
+
+		path = e_tree_model_get_node_by_id (etta->priv->source, id);
+		if (path)
+			e_tree_table_adapter_node_set_expanded (etta, path, !model_default);
+
+		g_free (id);
+	}
+
+	e_table_model_changed (E_TABLE_MODEL (etta));
+}
+
+void
+e_tree_table_adapter_load_expanded_state (ETreeTableAdapter *etta,
+                                          const gchar *filename)
+{
+	xmlDoc *doc;
+
+	g_return_if_fail (etta != NULL);
+
+	doc = open_file (etta, filename);
+	if (!doc)
+		return;
+
+	e_tree_table_adapter_load_expanded_state_xml  (etta, doc);
+
+	xmlFreeDoc (doc);
+}
+
+void
+e_tree_table_adapter_root_node_set_visible (ETreeTableAdapter *etta,
+                                            gboolean visible)
+{
+	gint size;
+
+	g_return_if_fail (etta != NULL);
+
+	if (etta->priv->root_visible == visible)
+		return;
+
+	e_table_model_pre_change (E_TABLE_MODEL (etta));
+
+	etta->priv->root_visible = visible;
+	if (!visible) {
+		ETreePath root = e_tree_model_get_root (etta->priv->source);
+		if (root)
+			e_tree_table_adapter_node_set_expanded (etta, root, TRUE);
+	}
+	size = (visible ? 1 : 0) + (etta->priv->root ? ((node_t *) etta->priv->root->data)->num_visible_children : 0);
+	resize_map (etta, size);
+	if (etta->priv->root)
+		fill_map (etta, 0, etta->priv->root);
+	e_table_model_changed (E_TABLE_MODEL (etta));
+}
+
+void
+e_tree_table_adapter_node_set_expanded (ETreeTableAdapter *etta,
+                                        ETreePath path,
+                                        gboolean expanded)
+{
+	GNode *gnode = lookup_gnode (etta, path);
+	node_t *node;
+	gint row;
+
+	if (!expanded && (!gnode || (e_tree_model_node_is_root (etta->priv->source, path) && !etta->priv->root_visible)))
+		return;
+
+	if (!gnode && expanded) {
+		ETreePath parent = e_tree_model_node_get_parent (etta->priv->source, path);
+		g_return_if_fail (parent != NULL);
+		e_tree_table_adapter_node_set_expanded (etta, parent, expanded);
+		gnode = lookup_gnode (etta, path);
+	}
+	g_return_if_fail (gnode != NULL);
+
+	node = (node_t *) gnode->data;
+
+	if (expanded == node->expanded)
+		return;
+
+	node->expanded = expanded;
+
+	row = get_row (etta, path);
+	if (row == -1)
+		return;
+
+	e_table_model_pre_change (E_TABLE_MODEL (etta));
+	e_table_model_pre_change (E_TABLE_MODEL (etta));
+	e_table_model_row_changed (E_TABLE_MODEL (etta), row);
+
+	if (expanded) {
+		gint num_children = insert_children (etta, gnode);
+		update_child_counts (gnode, num_children);
+		if (etta->priv->sort_info && e_table_sort_info_sorting_get_count (etta->priv->sort_info) > 0)
+			resort_node (etta, gnode, TRUE);
+		resize_map (etta, etta->priv->n_map + num_children);
+		move_map_elements (etta, row + 1 + num_children, row + 1, etta->priv->n_map - row - 1 - num_children);
+		fill_map (etta, row, gnode);
+		if (num_children != 0) {
+			e_table_model_rows_inserted (E_TABLE_MODEL (etta), row + 1, num_children);
+		} else
+			e_table_model_no_change (E_TABLE_MODEL (etta));
+	} else {
+		gint num_children = delete_children (etta, gnode);
+		if (num_children == 0) {
+			e_table_model_no_change (E_TABLE_MODEL (etta));
+			return;
+		}
+		move_map_elements (etta, row + 1, row + 1 + num_children, etta->priv->n_map - row - 1 - num_children);
+		update_child_counts (gnode, - num_children);
+		resize_map (etta, etta->priv->n_map - num_children);
+		e_table_model_rows_deleted (E_TABLE_MODEL (etta), row + 1, num_children);
+	}
+}
+
+void
+e_tree_table_adapter_node_set_expanded_recurse (ETreeTableAdapter *etta,
+                                                ETreePath path,
+                                                gboolean expanded)
+{
+	ETreePath children;
+
+	e_tree_table_adapter_node_set_expanded (etta, path, expanded);
+
+	for (children = e_tree_model_node_get_first_child (etta->priv->source, path);
+	     children;
+	     children = e_tree_model_node_get_next (etta->priv->source, children)) {
+		e_tree_table_adapter_node_set_expanded_recurse (etta, children, expanded);
+	}
+}
+
+ETreePath
+e_tree_table_adapter_node_at_row (ETreeTableAdapter *etta,
+                                  gint row)
+{
+	return get_path (etta, row);
+}
+
+gint
+e_tree_table_adapter_row_of_node (ETreeTableAdapter *etta,
+                                  ETreePath path)
+{
+	return get_row (etta, path);
+}
+
+gboolean
+e_tree_table_adapter_root_node_is_visible (ETreeTableAdapter *etta)
+{
+	return etta->priv->root_visible;
+}
+
+void
+e_tree_table_adapter_show_node (ETreeTableAdapter *etta,
+                                ETreePath path)
+{
+	ETreePath parent;
+
+	parent = e_tree_model_node_get_parent (etta->priv->source, path);
+
+	while (parent) {
+		e_tree_table_adapter_node_set_expanded (etta, parent, TRUE);
+		parent = e_tree_model_node_get_parent (etta->priv->source, parent);
+	}
+}
+
+gboolean
+e_tree_table_adapter_node_is_expanded (ETreeTableAdapter *etta,
+                                       ETreePath path)
+{
+	node_t *node = get_node (etta, path);
+	if (!e_tree_model_node_is_expandable (etta->priv->source, path) || !node)
+		return FALSE;
+
+	return node->expanded;
+}
+
+void
+e_tree_table_adapter_set_sort_info (ETreeTableAdapter *etta,
+                                    ETableSortInfo *sort_info)
+{
+	if (etta->priv->sort_info) {
+		g_signal_handler_disconnect (
+			etta->priv->sort_info,
+			etta->priv->sort_info_changed_id);
+		g_object_unref (etta->priv->sort_info);
+	}
+
+	etta->priv->sort_info = sort_info;
+	if (sort_info) {
+		g_object_ref (sort_info);
+		etta->priv->sort_info_changed_id = g_signal_connect (
+			sort_info, "sort_info_changed",
+			G_CALLBACK (etta_sort_info_changed), etta);
+	}
+
+	if (!etta->priv->root)
+		return;
+
+	e_table_model_pre_change (E_TABLE_MODEL (etta));
+	resort_node (etta, etta->priv->root, TRUE);
+	fill_map (etta, 0, etta->priv->root);
+	e_table_model_changed (E_TABLE_MODEL (etta));
+}
+
+ETableSortInfo *
+e_tree_table_adapter_get_sort_info (ETreeTableAdapter *etta)
+{
+	g_return_val_if_fail (etta != NULL, NULL);
+
+	return etta->priv->sort_info;
+}
+
+ETableHeader *
+e_tree_table_adapter_get_header (ETreeTableAdapter *etta)
+{
+	g_return_val_if_fail (etta != NULL, NULL);
+
+	return etta->priv->header;
+}
+
+ETreePath
+e_tree_table_adapter_node_get_next (ETreeTableAdapter *etta,
+                                    ETreePath path)
+{
+	GNode *node = lookup_gnode (etta, path);
+
+	if (node && node->next)
+		return ((node_t *) node->next->data)->path;
+
+	return NULL;
+}
diff --git a/e-util/e-tree-table-adapter.h b/e-util/e-tree-table-adapter.h
new file mode 100644
index 0000000..17f3304
--- /dev/null
+++ b/e-util/e-tree-table-adapter.h
@@ -0,0 +1,138 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *		Chris Toshok <toshok ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef _E_TREE_TABLE_ADAPTER_H_
+#define _E_TREE_TABLE_ADAPTER_H_
+
+#include <libxml/tree.h>
+
+#include <e-util/e-table-header.h>
+#include <e-util/e-table-model.h>
+#include <e-util/e-table-sort-info.h>
+#include <e-util/e-tree-model.h>
+
+/* Standard GObject macros */
+#define E_TYPE_TREE_TABLE_ADAPTER \
+	(e_tree_table_adapter_get_type ())
+#define E_TREE_TABLE_ADAPTER(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_TREE_TABLE_ADAPTER, ETreeTableAdapter))
+#define E_TREE_TABLE_ADAPTER_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_TREE_TABLE_ADAPTER, ETreeTableAdapterClass))
+#define E_IS_TREE_TABLE_ADAPTER(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_TREE_TABLE_ADAPTER))
+#define E_IS_TREE_TABLE_ADAPTER_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_TREE_TABLE_ADAPTER))
+#define E_TREE_TABLE_ADAPTER_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_TREE_TABLE_ADAPTER, ETreeTableAdapterClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ETreeTableAdapter ETreeTableAdapter;
+typedef struct _ETreeTableAdapterClass ETreeTableAdapterClass;
+typedef struct _ETreeTableAdapterPrivate ETreeTableAdapterPrivate;
+
+struct _ETreeTableAdapter {
+	ETableModel parent;
+	ETreeTableAdapterPrivate *priv;
+};
+
+struct _ETreeTableAdapterClass {
+	ETableModelClass parent_class;
+
+	/* Signals */
+	gboolean	(*sorting_changed)	(ETreeTableAdapter *etta);
+};
+
+GType		e_tree_table_adapter_get_type	(void) G_GNUC_CONST;
+ETableModel *	e_tree_table_adapter_new	(ETreeModel *source,
+						 ETableSortInfo *sort_info,
+						 ETableHeader *header);
+ETableModel *	e_tree_table_adapter_construct	(ETreeTableAdapter *ets,
+						 ETreeModel *source,
+						 ETableSortInfo *sort_info,
+						 ETableHeader	*header);
+
+ETreePath	e_tree_table_adapter_node_get_next
+						(ETreeTableAdapter *etta,
+						 ETreePath path);
+gboolean	e_tree_table_adapter_node_is_expanded
+						(ETreeTableAdapter *etta,
+						 ETreePath path);
+void		e_tree_table_adapter_node_set_expanded
+						(ETreeTableAdapter *etta,
+						 ETreePath path,
+						 gboolean expanded);
+void		e_tree_table_adapter_node_set_expanded_recurse
+						(ETreeTableAdapter *etta,
+						 ETreePath path,
+						 gboolean expanded);
+void		e_tree_table_adapter_force_expanded_state
+						(ETreeTableAdapter *etta,
+						 gint state);
+void		e_tree_table_adapter_root_node_set_visible
+						(ETreeTableAdapter *etta,
+						 gboolean visible);
+ETreePath	e_tree_table_adapter_node_at_row
+						(ETreeTableAdapter *etta,
+						 gint row);
+gint		e_tree_table_adapter_row_of_node
+						(ETreeTableAdapter *etta,
+						 ETreePath path);
+gboolean	e_tree_table_adapter_root_node_is_visible
+						(ETreeTableAdapter *etta);
+
+void		e_tree_table_adapter_show_node	(ETreeTableAdapter *etta,
+						 ETreePath path);
+
+void		e_tree_table_adapter_save_expanded_state
+						(ETreeTableAdapter *etta,
+						 const gchar *filename);
+void		e_tree_table_adapter_load_expanded_state
+						(ETreeTableAdapter *etta,
+						 const gchar *filename);
+
+xmlDoc *	e_tree_table_adapter_save_expanded_state_xml
+						(ETreeTableAdapter *etta);
+void		e_tree_table_adapter_load_expanded_state_xml
+						(ETreeTableAdapter *etta,
+						 xmlDoc *doc);
+
+void		e_tree_table_adapter_set_sort_info
+						(ETreeTableAdapter *etta,
+						 ETableSortInfo *sort_info);
+ETableSortInfo *e_tree_table_adapter_get_sort_info
+						 (ETreeTableAdapter *etta);
+ETableHeader *	e_tree_table_adapter_get_header	 (ETreeTableAdapter *etta);
+
+G_END_DECLS
+
+#endif /* _E_TREE_TABLE_ADAPTER_H_ */
diff --git a/e-util/e-tree.c b/e-util/e-tree.c
new file mode 100644
index 0000000..ee451cd
--- /dev/null
+++ b/e-util/e-tree.c
@@ -0,0 +1,3956 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+#include <gdk/gdkkeysyms.h>
+
+#include <libgnomecanvas/libgnomecanvas.h>
+
+#include "e-canvas-background.h"
+#include "e-canvas-utils.h"
+#include "e-canvas.h"
+#include "e-table-column-specification.h"
+#include "e-table-header-item.h"
+#include "e-table-header.h"
+#include "e-table-item.h"
+#include "e-table-sort-info.h"
+#include "e-table-utils.h"
+#include "e-text.h"
+#include "e-tree-table-adapter.h"
+#include "e-tree.h"
+#include "gal-a11y-e-tree.h"
+
+#ifdef E_TREE_USE_TREE_SELECTION
+#include "e-tree-selection-model.h"
+#else
+#include "e-table-selection-model.h"
+#endif
+
+#define COLUMN_HEADER_HEIGHT 16
+
+#define d(x)
+
+#define E_TREE_GET_PRIVATE(obj) \
+	(G_TYPE_INSTANCE_GET_PRIVATE \
+	((obj), E_TYPE_TREE, ETreePrivate))
+
+enum {
+	CURSOR_CHANGE,
+	CURSOR_ACTIVATED,
+	SELECTION_CHANGE,
+	DOUBLE_CLICK,
+	RIGHT_CLICK,
+	CLICK,
+	KEY_PRESS,
+	START_DRAG,
+	STATE_CHANGE,
+	WHITE_SPACE_EVENT,
+
+	CUT_CLIPBOARD,
+	COPY_CLIPBOARD,
+	PASTE_CLIPBOARD,
+	SELECT_ALL,
+
+	TREE_DRAG_BEGIN,
+	TREE_DRAG_END,
+	TREE_DRAG_DATA_GET,
+	TREE_DRAG_DATA_DELETE,
+
+	TREE_DRAG_LEAVE,
+	TREE_DRAG_MOTION,
+	TREE_DRAG_DROP,
+	TREE_DRAG_DATA_RECEIVED,
+
+	LAST_SIGNAL
+};
+
+enum {
+	PROP_0,
+	PROP_LENGTH_THRESHOLD,
+	PROP_HORIZONTAL_DRAW_GRID,
+	PROP_VERTICAL_DRAW_GRID,
+	PROP_DRAW_FOCUS,
+	PROP_ETTA,
+	PROP_UNIFORM_ROW_HEIGHT,
+	PROP_ALWAYS_SEARCH,
+	PROP_HADJUSTMENT,
+	PROP_VADJUSTMENT,
+	PROP_HSCROLL_POLICY,
+	PROP_VSCROLL_POLICY
+};
+
+enum {
+	ET_SCROLL_UP = 1 << 0,
+	ET_SCROLL_DOWN = 1 << 1,
+	ET_SCROLL_LEFT = 1 << 2,
+	ET_SCROLL_RIGHT = 1 << 3
+};
+
+struct _ETreePrivate {
+	ETreeModel *model;
+	ETreeTableAdapter *etta;
+
+	ETableHeader *full_header, *header;
+
+	guint structure_change_id, expansion_change_id;
+
+	ETableSortInfo *sort_info;
+	ESorter   *sorter;
+
+	guint sort_info_change_id, group_info_change_id;
+
+	ESelectionModel *selection;
+	ETableSpecification *spec;
+
+	ETableSearch     *search;
+
+	ETableCol        *current_search_col;
+
+	guint	  search_search_id;
+	guint	  search_accept_id;
+
+	gint reflow_idle_id;
+	gint scroll_idle_id;
+	gint hover_idle_id;
+
+	gboolean show_cursor_after_reflow;
+
+	gint table_model_change_id;
+	gint table_row_change_id;
+	gint table_cell_change_id;
+	gint table_rows_delete_id;
+
+	GnomeCanvasItem *info_text;
+	guint info_text_resize_id;
+
+	GnomeCanvas *header_canvas, *table_canvas;
+
+	GnomeCanvasItem *header_item, *root;
+
+	GnomeCanvasItem *white_item;
+	GnomeCanvasItem *item;
+
+	gint length_threshold;
+
+	/*
+	 * Configuration settings
+	 */
+	guint alternating_row_colors : 1;
+	guint horizontal_draw_grid : 1;
+	guint vertical_draw_grid : 1;
+	guint draw_focus : 1;
+	guint row_selection_active : 1;
+
+	guint horizontal_scrolling : 1;
+
+	guint scroll_direction : 4;
+
+	guint do_drag : 1;
+
+	guint uniform_row_height : 1;
+
+	guint search_col_set : 1;
+	guint always_search : 1;
+
+	ECursorMode cursor_mode;
+
+	gint drop_row;
+	ETreePath drop_path;
+	gint drop_col;
+
+	GnomeCanvasItem *drop_highlight;
+	gint last_drop_x;
+	gint last_drop_y;
+	gint last_drop_time;
+	GdkDragContext *last_drop_context;
+
+	gint hover_x;
+	gint hover_y;
+
+	gint drag_row;
+	ETreePath drag_path;
+	gint drag_col;
+	ETreeDragSourceSite *site;
+
+	GList *expanded_list;
+
+	gboolean state_changed;
+	guint state_change_freeze;
+
+	gboolean is_dragging;
+};
+
+static guint et_signals[LAST_SIGNAL] = { 0, };
+
+static void et_grab_focus (GtkWidget *widget);
+
+static void et_drag_begin (GtkWidget *widget,
+			   GdkDragContext *context,
+			   ETree *et);
+static void et_drag_end (GtkWidget *widget,
+			 GdkDragContext *context,
+			 ETree *et);
+static void et_drag_data_get (GtkWidget *widget,
+			     GdkDragContext *context,
+			     GtkSelectionData *selection_data,
+			     guint info,
+			     guint time,
+			     ETree *et);
+static void et_drag_data_delete (GtkWidget *widget,
+				GdkDragContext *context,
+				ETree *et);
+
+static void et_drag_leave (GtkWidget *widget,
+			  GdkDragContext *context,
+			  guint time,
+			  ETree *et);
+static gboolean et_drag_motion (GtkWidget *widget,
+			       GdkDragContext *context,
+			       gint x,
+			       gint y,
+			       guint time,
+			       ETree *et);
+static gboolean et_drag_drop (GtkWidget *widget,
+			     GdkDragContext *context,
+			     gint x,
+			     gint y,
+			     guint time,
+			     ETree *et);
+static void et_drag_data_received (GtkWidget *widget,
+				  GdkDragContext *context,
+				  gint x,
+				  gint y,
+				  GtkSelectionData *selection_data,
+				  guint info,
+				  guint time,
+				  ETree *et);
+
+static void scroll_off (ETree *et);
+static void scroll_on (ETree *et, guint scroll_direction);
+static void hover_off (ETree *et);
+static void hover_on (ETree *et, gint x, gint y);
+static void context_destroyed (gpointer data, GObject *ctx);
+
+G_DEFINE_TYPE_WITH_CODE (ETree, e_tree, GTK_TYPE_TABLE,
+			 G_IMPLEMENT_INTERFACE (GTK_TYPE_SCROLLABLE, NULL))
+
+static void
+et_disconnect_from_etta (ETree *et)
+{
+	if (et->priv->table_model_change_id != 0)
+		g_signal_handler_disconnect (
+			et->priv->etta,
+			et->priv->table_model_change_id);
+	if (et->priv->table_row_change_id != 0)
+		g_signal_handler_disconnect (
+			et->priv->etta,
+			et->priv->table_row_change_id);
+	if (et->priv->table_cell_change_id != 0)
+		g_signal_handler_disconnect (
+			et->priv->etta,
+			et->priv->table_cell_change_id);
+	if (et->priv->table_rows_delete_id != 0)
+		g_signal_handler_disconnect (
+			et->priv->etta,
+			et->priv->table_rows_delete_id);
+
+	et->priv->table_model_change_id = 0;
+	et->priv->table_row_change_id = 0;
+	et->priv->table_cell_change_id = 0;
+	et->priv->table_rows_delete_id = 0;
+}
+
+static void
+clear_current_search_col (ETree *et)
+{
+	et->priv->search_col_set = FALSE;
+}
+
+static ETableCol *
+current_search_col (ETree *et)
+{
+	if (!et->priv->search_col_set) {
+		et->priv->current_search_col =
+			e_table_util_calculate_current_search_col (
+				et->priv->header,
+				et->priv->full_header,
+				et->priv->sort_info,
+				et->priv->always_search);
+		et->priv->search_col_set = TRUE;
+	}
+
+	return et->priv->current_search_col;
+}
+
+static void
+e_tree_state_change (ETree *et)
+{
+	if (et->priv->state_change_freeze)
+		et->priv->state_changed = TRUE;
+	else
+		g_signal_emit (et, et_signals[STATE_CHANGE], 0);
+}
+
+static void
+change_trigger (GObject *object,
+                ETree *et)
+{
+	e_tree_state_change (et);
+}
+
+static void
+search_col_change_trigger (GObject *object,
+                           ETree *et)
+{
+	clear_current_search_col (et);
+	e_tree_state_change (et);
+}
+
+static void
+disconnect_header (ETree *e_tree)
+{
+	if (e_tree->priv->header == NULL)
+		return;
+
+	if (e_tree->priv->structure_change_id)
+		g_signal_handler_disconnect (
+			e_tree->priv->header,
+			e_tree->priv->structure_change_id);
+	if (e_tree->priv->expansion_change_id)
+		g_signal_handler_disconnect (
+			e_tree->priv->header,
+			e_tree->priv->expansion_change_id);
+	if (e_tree->priv->sort_info) {
+		if (e_tree->priv->sort_info_change_id)
+			g_signal_handler_disconnect (
+				e_tree->priv->sort_info,
+				e_tree->priv->sort_info_change_id);
+		if (e_tree->priv->group_info_change_id)
+			g_signal_handler_disconnect (
+				e_tree->priv->sort_info,
+				e_tree->priv->group_info_change_id);
+
+		g_object_unref (e_tree->priv->sort_info);
+	}
+	g_object_unref (e_tree->priv->header);
+	e_tree->priv->header = NULL;
+	e_tree->priv->sort_info = NULL;
+}
+
+static void
+connect_header (ETree *e_tree,
+                ETableState *state)
+{
+	GValue *val = g_new0 (GValue, 1);
+
+	if (e_tree->priv->header != NULL)
+		disconnect_header (e_tree);
+
+	e_tree->priv->header = e_table_state_to_header (
+		GTK_WIDGET (e_tree), e_tree->priv->full_header, state);
+
+	e_tree->priv->structure_change_id = g_signal_connect (
+		e_tree->priv->header, "structure_change",
+		G_CALLBACK (search_col_change_trigger), e_tree);
+
+	e_tree->priv->expansion_change_id = g_signal_connect (
+		e_tree->priv->header, "expansion_change",
+		G_CALLBACK (change_trigger), e_tree);
+
+	if (state->sort_info) {
+		e_tree->priv->sort_info = e_table_sort_info_duplicate (state->sort_info);
+		e_table_sort_info_set_can_group (e_tree->priv->sort_info, FALSE);
+		e_tree->priv->sort_info_change_id = g_signal_connect (
+			e_tree->priv->sort_info, "sort_info_changed",
+			G_CALLBACK (search_col_change_trigger), e_tree);
+
+		e_tree->priv->group_info_change_id = g_signal_connect (
+			e_tree->priv->sort_info, "group_info_changed",
+			G_CALLBACK (search_col_change_trigger), e_tree);
+	} else
+		e_tree->priv->sort_info = NULL;
+
+	g_value_init (val, G_TYPE_OBJECT);
+	g_value_set_object (val, e_tree->priv->sort_info);
+	g_object_set_property (G_OBJECT (e_tree->priv->header), "sort_info", val);
+	g_free (val);
+}
+
+static void
+et_dispose (GObject *object)
+{
+	ETreePrivate *priv;
+
+	priv = E_TREE_GET_PRIVATE (object);
+
+	if (priv->search != NULL) {
+		g_signal_handler_disconnect (
+			priv->search, priv->search_search_id);
+		g_signal_handler_disconnect (
+			priv->search, priv->search_accept_id);
+		g_object_unref (priv->search);
+		priv->search = NULL;
+	}
+
+	if (priv->reflow_idle_id > 0) {
+		g_source_remove (priv->reflow_idle_id);
+		priv->reflow_idle_id = 0;
+	}
+
+	scroll_off (E_TREE (object));
+	hover_off (E_TREE (object));
+	g_list_foreach (
+		priv->expanded_list,
+		(GFunc) g_free, NULL);
+	g_list_free (priv->expanded_list);
+	priv->expanded_list = NULL;
+
+	et_disconnect_from_etta (E_TREE (object));
+
+	if (priv->etta != NULL) {
+		g_object_unref (priv->etta);
+		priv->etta = NULL;
+	}
+
+	if (priv->model != NULL) {
+		g_object_unref (priv->model);
+		priv->model = NULL;
+	}
+
+	if (priv->full_header != NULL) {
+		g_object_unref (priv->full_header);
+		priv->full_header = NULL;
+	}
+
+	disconnect_header (E_TREE (object));
+
+	if (priv->selection != NULL) {
+		g_object_unref (priv->selection);
+		priv->selection = NULL;
+	}
+
+	if (priv->spec != NULL) {
+		g_object_unref (priv->spec);
+		priv->spec = NULL;
+	}
+
+	if (priv->sorter != NULL) {
+		g_object_unref (priv->sorter);
+		priv->sorter = NULL;
+	}
+
+	if (priv->header_canvas != NULL) {
+		gtk_widget_destroy (GTK_WIDGET (priv->header_canvas));
+		priv->header_canvas = NULL;
+	}
+
+	if (priv->site)
+		e_tree_drag_source_unset (E_TREE (object));
+
+	if (priv->last_drop_context != NULL) {
+		g_object_weak_unref (
+			G_OBJECT (priv->last_drop_context),
+			context_destroyed, object);
+		priv->last_drop_context = NULL;
+	}
+
+	if (priv->info_text != NULL) {
+		g_object_run_dispose (G_OBJECT (priv->info_text));
+		priv->info_text = NULL;
+	}
+	priv->info_text_resize_id = 0;
+
+	if (priv->table_canvas != NULL) {
+		gtk_widget_destroy (GTK_WIDGET (priv->table_canvas));
+		priv->table_canvas = NULL;
+	}
+
+	/* do not unref it, it was owned by priv->table_canvas */
+	priv->item = NULL;
+
+	/* Chain up to parent's dispose() method. */
+	G_OBJECT_CLASS (e_tree_parent_class)->dispose (object);
+}
+
+static void
+et_unrealize (GtkWidget *widget)
+{
+	scroll_off (E_TREE (widget));
+	hover_off (E_TREE (widget));
+
+	if (GTK_WIDGET_CLASS (e_tree_parent_class)->unrealize)
+		GTK_WIDGET_CLASS (e_tree_parent_class)->unrealize (widget);
+}
+
+typedef struct {
+	ETree *et;
+	gchar *string;
+} SearchSearchStruct;
+
+static gboolean
+search_search_callback (ETreeModel *model,
+                        ETreePath path,
+                        gpointer data)
+{
+	SearchSearchStruct *cb_data = data;
+	gconstpointer value;
+	ETableCol *col = current_search_col (cb_data->et);
+
+	value = e_tree_model_value_at (
+		model, path, cb_data->et->priv->current_search_col->col_idx);
+
+	return col->search (value, cb_data->string);
+}
+
+static gboolean
+et_search_search (ETableSearch *search,
+                  gchar *string,
+                  ETableSearchFlags flags,
+                  ETree *et)
+{
+	ETreePath cursor;
+	ETreePath found;
+	SearchSearchStruct cb_data;
+	ETableCol *col = current_search_col (et);
+
+	if (col == NULL)
+		return FALSE;
+
+	cb_data.et = et;
+	cb_data.string = string;
+
+	cursor = e_tree_get_cursor (et);
+
+	if (cursor && (flags & E_TABLE_SEARCH_FLAGS_CHECK_CURSOR_FIRST)) {
+		gconstpointer value;
+
+		value = e_tree_model_value_at (et->priv->model, cursor, col->col_idx);
+
+		if (col->search (value, string)) {
+			return TRUE;
+		}
+	}
+
+	found = e_tree_model_node_find (
+			et->priv->model, cursor, NULL,
+			E_TREE_FIND_NEXT_FORWARD,
+			search_search_callback, &cb_data);
+	if (found == NULL)
+		found = e_tree_model_node_find (
+			et->priv->model, NULL, cursor,
+			E_TREE_FIND_NEXT_FORWARD,
+			search_search_callback, &cb_data);
+
+	if (found && found != cursor) {
+		gint model_row;
+
+		e_tree_table_adapter_show_node (et->priv->etta, found);
+		model_row = e_tree_table_adapter_row_of_node (et->priv->etta, found);
+
+		e_selection_model_select_as_key_press (
+			E_SELECTION_MODEL (et->priv->selection),
+			model_row, col->col_idx, GDK_CONTROL_MASK);
+		return TRUE;
+	} else if (cursor && !(flags & E_TABLE_SEARCH_FLAGS_CHECK_CURSOR_FIRST)) {
+		gconstpointer value;
+
+		value = e_tree_model_value_at (et->priv->model, cursor, col->col_idx);
+
+		return col->search (value, string);
+	} else
+		return FALSE;
+}
+
+static void
+et_search_accept (ETableSearch *search,
+                  ETree *et)
+{
+	ETableCol *col = current_search_col (et);
+	gint cursor;
+
+	if (col == NULL)
+		return;
+
+	g_object_get (et->priv->selection, "cursor_row", &cursor, NULL);
+
+	e_selection_model_select_as_key_press (
+		E_SELECTION_MODEL (et->priv->selection),
+		cursor, col->col_idx, 0);
+}
+
+static void
+e_tree_init (ETree *e_tree)
+{
+	gtk_widget_set_can_focus (GTK_WIDGET (e_tree), TRUE);
+
+	gtk_table_set_homogeneous (GTK_TABLE (e_tree), FALSE);
+
+	e_tree->priv = E_TREE_GET_PRIVATE (e_tree);
+
+	e_tree->priv->alternating_row_colors = 1;
+	e_tree->priv->horizontal_draw_grid = 1;
+	e_tree->priv->vertical_draw_grid = 1;
+	e_tree->priv->draw_focus = 1;
+	e_tree->priv->cursor_mode = E_CURSOR_SIMPLE;
+	e_tree->priv->length_threshold = 200;
+
+	e_tree->priv->drop_row = -1;
+	e_tree->priv->drop_col = -1;
+
+	e_tree->priv->drag_row = -1;
+	e_tree->priv->drag_col = -1;
+
+#ifdef E_TREE_USE_TREE_SELECTION
+	e_tree->priv->selection =
+		E_SELECTION_MODEL (e_tree_selection_model_new ());
+#else
+	e_tree->priv->selection =
+		E_SELECTION_MODEL (e_table_selection_model_new ());
+#endif
+
+	e_tree->priv->search = e_table_search_new ();
+
+	e_tree->priv->search_search_id = g_signal_connect (
+		e_tree->priv->search, "search",
+		G_CALLBACK (et_search_search), e_tree);
+
+	e_tree->priv->search_accept_id = g_signal_connect (
+		e_tree->priv->search, "accept",
+		G_CALLBACK (et_search_accept), e_tree);
+
+	e_tree->priv->always_search = g_getenv ("GAL_ALWAYS_SEARCH") ? TRUE : FALSE;
+
+	e_tree->priv->state_changed = FALSE;
+	e_tree->priv->state_change_freeze = 0;
+
+	e_tree->priv->is_dragging = FALSE;
+}
+
+/* Grab_focus handler for the ETree */
+static void
+et_grab_focus (GtkWidget *widget)
+{
+	ETree *e_tree;
+
+	e_tree = E_TREE (widget);
+
+	gtk_widget_grab_focus (GTK_WIDGET (e_tree->priv->table_canvas));
+}
+
+/* Focus handler for the ETree */
+static gint
+et_focus (GtkWidget *container,
+          GtkDirectionType direction)
+{
+	ETree *e_tree;
+
+	e_tree = E_TREE (container);
+
+	if (gtk_container_get_focus_child (GTK_CONTAINER (container))) {
+		gtk_container_set_focus_child (GTK_CONTAINER (container), NULL);
+		return FALSE;
+	}
+
+	return gtk_widget_child_focus (
+		GTK_WIDGET (e_tree->priv->table_canvas), direction);
+}
+
+static void
+set_header_canvas_width (ETree *e_tree)
+{
+	gdouble oldwidth, oldheight, width;
+
+	if (!(e_tree->priv->header_item &&
+		e_tree->priv->header_canvas && e_tree->priv->table_canvas))
+		return;
+
+	gnome_canvas_get_scroll_region (
+		GNOME_CANVAS (e_tree->priv->table_canvas),
+		NULL, NULL, &width, NULL);
+	gnome_canvas_get_scroll_region (
+		GNOME_CANVAS (e_tree->priv->header_canvas),
+		NULL, NULL, &oldwidth, &oldheight);
+
+	if (oldwidth != width ||
+	    oldheight != E_TABLE_HEADER_ITEM (e_tree->priv->header_item)->height - 1)
+		gnome_canvas_set_scroll_region (
+			GNOME_CANVAS (e_tree->priv->header_canvas),
+			0, 0, width, /*  COLUMN_HEADER_HEIGHT - 1 */
+			E_TABLE_HEADER_ITEM (e_tree->priv->header_item)->height - 1);
+
+}
+
+static void
+header_canvas_size_allocate (GtkWidget *widget,
+                             GtkAllocation *alloc,
+                             ETree *e_tree)
+{
+	GtkAllocation allocation;
+
+	set_header_canvas_width (e_tree);
+
+	widget = GTK_WIDGET (e_tree->priv->header_canvas);
+	gtk_widget_get_allocation (widget, &allocation);
+
+	/* When the header item is created ->height == 0,
+	 * as the font is only created when everything is realized.
+	 * So we set the usize here as well, so that the size of the
+	 * header is correct */
+	if (allocation.height != E_TABLE_HEADER_ITEM (e_tree->priv->header_item)->height)
+		gtk_widget_set_size_request (
+			widget, -1,
+			E_TABLE_HEADER_ITEM (e_tree->priv->header_item)->height);
+}
+
+static void
+e_tree_setup_header (ETree *e_tree)
+{
+	GtkWidget *widget;
+	gchar *pointer;
+
+	widget = e_canvas_new ();
+	gtk_widget_set_can_focus (widget, FALSE);
+	e_tree->priv->header_canvas = GNOME_CANVAS (widget);
+	gtk_widget_show (widget);
+
+	pointer = g_strdup_printf ("%p", (gpointer) e_tree);
+
+	e_tree->priv->header_item = gnome_canvas_item_new (
+		gnome_canvas_root (e_tree->priv->header_canvas),
+		e_table_header_item_get_type (),
+		"ETableHeader", e_tree->priv->header,
+		"full_header", e_tree->priv->full_header,
+		"sort_info", e_tree->priv->sort_info,
+		"dnd_code", pointer,
+		"tree", e_tree,
+		NULL);
+
+	g_free (pointer);
+
+	g_signal_connect (
+		e_tree->priv->header_canvas, "size_allocate",
+		G_CALLBACK (header_canvas_size_allocate), e_tree);
+
+	gtk_widget_set_size_request (
+		GTK_WIDGET (e_tree->priv->header_canvas), -1,
+		E_TABLE_HEADER_ITEM (e_tree->priv->header_item)->height);
+}
+
+static void
+scroll_to_cursor (ETree *e_tree)
+{
+	ETreePath path;
+	GtkAdjustment *adjustment;
+	GtkScrollable *scrollable;
+	gint x, y, w, h;
+	gdouble page_size;
+	gdouble lower;
+	gdouble upper;
+	gdouble value;
+
+	path = e_tree_get_cursor (e_tree);
+	x = y = w = h = 0;
+
+	if (path) {
+		gint row = e_tree_row_of_node (e_tree, path);
+		gint col = 0;
+
+		if (row >= 0)
+			e_table_item_get_cell_geometry (
+				E_TABLE_ITEM (e_tree->priv->item),
+				&row, &col, &x, &y, &w, &h);
+	}
+
+	scrollable = GTK_SCROLLABLE (e_tree->priv->table_canvas);
+	adjustment = gtk_scrollable_get_vadjustment (scrollable);
+
+	page_size = gtk_adjustment_get_page_size (adjustment);
+	lower = gtk_adjustment_get_lower (adjustment);
+	upper = gtk_adjustment_get_upper (adjustment);
+	value = gtk_adjustment_get_value (adjustment);
+
+	if (y < value || y + h > value + page_size) {
+		value = CLAMP (y - page_size / 2, lower, upper - page_size);
+		gtk_adjustment_set_value (adjustment, value);
+	}
+}
+
+static gboolean
+tree_canvas_reflow_idle (ETree *e_tree)
+{
+	gdouble height, width;
+	gdouble oldheight, oldwidth;
+	GtkAllocation allocation;
+	GtkWidget *widget;
+
+	widget = GTK_WIDGET (e_tree->priv->table_canvas);
+	gtk_widget_get_allocation (widget, &allocation);
+
+	g_object_get (
+		e_tree->priv->item,
+		"height", &height, "width", &width, NULL);
+
+	height = MAX ((gint) height, allocation.height);
+	width = MAX ((gint) width, allocation.width);
+
+	/* I have no idea why this needs to be -1, but it works. */
+	gnome_canvas_get_scroll_region (
+		GNOME_CANVAS (e_tree->priv->table_canvas),
+		NULL, NULL, &oldwidth, &oldheight);
+
+	if (oldwidth != width - 1 ||
+	    oldheight != height - 1) {
+		gnome_canvas_set_scroll_region (
+			GNOME_CANVAS (e_tree->priv->table_canvas),
+			0, 0, width - 1, height - 1);
+		set_header_canvas_width (e_tree);
+	}
+
+	e_tree->priv->reflow_idle_id = 0;
+
+	if (e_tree->priv->show_cursor_after_reflow) {
+		e_tree->priv->show_cursor_after_reflow = FALSE;
+		scroll_to_cursor (e_tree);
+	}
+
+	return FALSE;
+}
+
+static void
+tree_canvas_size_allocate (GtkWidget *widget,
+                           GtkAllocation *alloc,
+                           ETree *e_tree)
+{
+	gdouble width;
+	gdouble height;
+	GValue *val = g_new0 (GValue, 1);
+	g_value_init (val, G_TYPE_DOUBLE);
+
+	width = alloc->width;
+	g_value_set_double (val, width);
+	g_object_get (
+		e_tree->priv->item,
+		"height", &height,
+		NULL);
+	height = MAX ((gint) height, alloc->height);
+
+	g_object_set (
+		e_tree->priv->item,
+		"width", width,
+		NULL);
+	g_object_set_property (G_OBJECT (e_tree->priv->header), "width", val);
+	g_free (val);
+
+	if (e_tree->priv->reflow_idle_id)
+		g_source_remove (e_tree->priv->reflow_idle_id);
+	tree_canvas_reflow_idle (e_tree);
+}
+
+static void
+tree_canvas_reflow (GnomeCanvas *canvas,
+                    ETree *e_tree)
+{
+	if (!e_tree->priv->reflow_idle_id)
+		e_tree->priv->reflow_idle_id = g_idle_add_full (
+			400, (GSourceFunc) tree_canvas_reflow_idle,
+			e_tree, NULL);
+}
+
+static void
+item_cursor_change (ETableItem *eti,
+                    gint row,
+                    ETree *et)
+{
+	ETreePath path = e_tree_table_adapter_node_at_row (et->priv->etta, row);
+
+	g_signal_emit (et, et_signals[CURSOR_CHANGE], 0, row, path);
+}
+
+static void
+item_cursor_activated (ETableItem *eti,
+                       gint row,
+                       ETree *et)
+{
+	ETreePath path = e_tree_table_adapter_node_at_row (et->priv->etta, row);
+
+	g_signal_emit (et, et_signals[CURSOR_ACTIVATED], 0, row, path);
+}
+
+static void
+item_double_click (ETableItem *eti,
+                   gint row,
+                   gint col,
+                   GdkEvent *event,
+                   ETree *et)
+{
+	ETreePath path = e_tree_table_adapter_node_at_row (et->priv->etta, row);
+
+	g_signal_emit (et, et_signals[DOUBLE_CLICK], 0, row, path, col, event);
+}
+
+static gboolean
+item_right_click (ETableItem *eti,
+                  gint row,
+                  gint col,
+                  GdkEvent *event,
+                  ETree *et)
+{
+	ETreePath path = e_tree_table_adapter_node_at_row (et->priv->etta, row);
+	gboolean return_val = 0;
+
+	g_signal_emit (
+		et, et_signals[RIGHT_CLICK], 0,
+		row, path, col, event, &return_val);
+
+	return return_val;
+}
+
+static gboolean
+item_click (ETableItem *eti,
+            gint row,
+            gint col,
+            GdkEvent *event,
+            ETree *et)
+{
+	gboolean return_val = 0;
+	ETreePath path = e_tree_table_adapter_node_at_row (et->priv->etta, row);
+
+	g_signal_emit (
+		et, et_signals[CLICK], 0, row, path, col, event, &return_val);
+
+	return return_val;
+}
+
+static gint
+item_key_press (ETableItem *eti,
+                gint row,
+                gint col,
+                GdkEvent *event,
+                ETree *et)
+{
+	gint return_val = 0;
+	GdkEventKey *key = (GdkEventKey *) event;
+	ETreePath path;
+	gint y, row_local, col_local;
+	GtkAdjustment *adjustment;
+	GtkScrollable *scrollable;
+	gdouble page_size;
+	gdouble upper;
+	gdouble value;
+
+	scrollable = GTK_SCROLLABLE (et->priv->table_canvas);
+	adjustment = gtk_scrollable_get_vadjustment (scrollable);
+
+	page_size = gtk_adjustment_get_page_size (adjustment);
+	upper = gtk_adjustment_get_upper (adjustment);
+	value = gtk_adjustment_get_value (adjustment);
+
+	switch (key->keyval) {
+	case GDK_KEY_Page_Down:
+	case GDK_KEY_KP_Page_Down:
+		y = CLAMP (value + (2 * page_size - 50), 0, upper);
+		y -= value;
+		e_tree_get_cell_at (et, 30, y, &row_local, &col_local);
+
+		if (row_local == -1)
+			row_local = e_table_model_row_count (
+				E_TABLE_MODEL (et->priv->etta)) - 1;
+
+		row_local = e_tree_view_to_model_row (et, row_local);
+		col_local = e_selection_model_cursor_col (
+			E_SELECTION_MODEL (et->priv->selection));
+		e_selection_model_select_as_key_press (
+			E_SELECTION_MODEL (et->priv->selection),
+			row_local, col_local, key->state);
+
+		return_val = 1;
+		break;
+	case GDK_KEY_Page_Up:
+	case GDK_KEY_KP_Page_Up:
+		y = CLAMP (value - (page_size - 50), 0, upper);
+		y -= value;
+		e_tree_get_cell_at (et, 30, y, &row_local, &col_local);
+
+		if (row_local == -1)
+			row_local = e_table_model_row_count (
+				E_TABLE_MODEL (et->priv->etta)) - 1;
+
+		row_local = e_tree_view_to_model_row (et, row_local);
+		col_local = e_selection_model_cursor_col (
+			E_SELECTION_MODEL (et->priv->selection));
+		e_selection_model_select_as_key_press (
+			E_SELECTION_MODEL (et->priv->selection),
+			row_local, col_local, key->state);
+
+		return_val = 1;
+		break;
+	case GDK_KEY_plus:
+	case GDK_KEY_KP_Add:
+	case GDK_KEY_Right:
+	case GDK_KEY_KP_Right:
+		/* Only allow if the Shift modifier is used.
+		 * eg. Ctrl-Equal shouldn't be handled.  */
+		if ((key->state & (GDK_SHIFT_MASK | GDK_LOCK_MASK |
+			GDK_MOD1_MASK)) != GDK_SHIFT_MASK)
+			break;
+		if (row != -1) {
+			path = e_tree_table_adapter_node_at_row (
+				et->priv->etta, row);
+			if (path)
+				e_tree_table_adapter_node_set_expanded (
+					et->priv->etta, path, TRUE);
+		}
+		return_val = 1;
+		break;
+	case GDK_KEY_underscore:
+	case GDK_KEY_KP_Subtract:
+	case GDK_KEY_Left:
+	case GDK_KEY_KP_Left:
+		/* Only allow if the Shift modifier is used.
+		 * eg. Ctrl-Minus shouldn't be handled.  */
+		if ((key->state & (GDK_SHIFT_MASK | GDK_LOCK_MASK |
+			GDK_MOD1_MASK)) != GDK_SHIFT_MASK)
+			break;
+		if (row != -1) {
+			path = e_tree_table_adapter_node_at_row (
+				et->priv->etta, row);
+			if (path)
+				e_tree_table_adapter_node_set_expanded (
+					et->priv->etta, path, FALSE);
+		}
+		return_val = 1;
+		break;
+	case GDK_KEY_BackSpace:
+		if (e_table_search_backspace (et->priv->search))
+			return TRUE;
+		/* Fallthrough */
+	default:
+		if ((key->state & ~(GDK_SHIFT_MASK | GDK_LOCK_MASK |
+			GDK_MOD1_MASK | GDK_MOD2_MASK | GDK_MOD3_MASK |
+			GDK_MOD4_MASK | GDK_MOD5_MASK)) == 0
+		    && ((key->keyval >= GDK_KEY_a && key->keyval <= GDK_KEY_z) ||
+			(key->keyval >= GDK_KEY_A && key->keyval <= GDK_KEY_Z) ||
+			(key->keyval >= GDK_KEY_0 && key->keyval <= GDK_KEY_9))) {
+			e_table_search_input_character (et->priv->search, key->keyval);
+		}
+		path = e_tree_table_adapter_node_at_row (et->priv->etta, row);
+		g_signal_emit (
+			et,
+			et_signals[KEY_PRESS], 0,
+			row, path, col, event, &return_val);
+		break;
+	}
+	return return_val;
+}
+
+static gint
+item_start_drag (ETableItem *eti,
+                 gint row,
+                 gint col,
+                 GdkEvent *event,
+                 ETree *et)
+{
+	ETreePath path;
+	gint return_val = 0;
+
+	path = e_tree_table_adapter_node_at_row (et->priv->etta, row);
+
+	g_signal_emit (
+		et, et_signals[START_DRAG], 0,
+		row, path, col, event, &return_val);
+
+	return return_val;
+}
+
+static void
+et_selection_model_selection_changed (ETableSelectionModel *etsm,
+                                      ETree *et)
+{
+	g_signal_emit (et, et_signals[SELECTION_CHANGE], 0);
+}
+
+static void
+et_selection_model_selection_row_changed (ETableSelectionModel *etsm,
+                                          gint row,
+                                          ETree *et)
+{
+	g_signal_emit (et, et_signals[SELECTION_CHANGE], 0);
+}
+
+static void
+et_build_item (ETree *et)
+{
+	et->priv->item = gnome_canvas_item_new (
+		GNOME_CANVAS_GROUP (
+			gnome_canvas_root (et->priv->table_canvas)),
+		e_table_item_get_type (),
+		"ETableHeader", et->priv->header,
+		"ETableModel", et->priv->etta,
+		"selection_model", et->priv->selection,
+		"alternating_row_colors", et->priv->alternating_row_colors,
+		"horizontal_draw_grid", et->priv->horizontal_draw_grid,
+		"vertical_draw_grid", et->priv->vertical_draw_grid,
+		"drawfocus", et->priv->draw_focus,
+		"cursor_mode", et->priv->cursor_mode,
+		"length_threshold", et->priv->length_threshold,
+		"uniform_row_height", et->priv->uniform_row_height,
+		NULL);
+
+	g_signal_connect (
+		et->priv->item, "cursor_change",
+		G_CALLBACK (item_cursor_change), et);
+	g_signal_connect (
+		et->priv->item, "cursor_activated",
+		G_CALLBACK (item_cursor_activated), et);
+	g_signal_connect (
+		et->priv->item, "double_click",
+		G_CALLBACK (item_double_click), et);
+	g_signal_connect (
+		et->priv->item, "right_click",
+		G_CALLBACK (item_right_click), et);
+	g_signal_connect (
+		et->priv->item, "click",
+		G_CALLBACK (item_click), et);
+	g_signal_connect (
+		et->priv->item, "key_press",
+		G_CALLBACK (item_key_press), et);
+	g_signal_connect (
+		et->priv->item, "start_drag",
+		G_CALLBACK (item_start_drag), et);
+}
+
+static void
+et_canvas_style_set (GtkWidget *widget,
+                     GtkStyle *prev_style)
+{
+	GtkStyle *style;
+
+	style = gtk_widget_get_style (widget);
+
+	gnome_canvas_item_set (
+		E_TREE (widget)->priv->white_item,
+		"fill_color_gdk", &style->base[GTK_STATE_NORMAL],
+		NULL);
+}
+
+static gboolean
+white_item_event (GnomeCanvasItem *white_item,
+                  GdkEvent *event,
+                  ETree *e_tree)
+{
+	gboolean return_val = 0;
+	g_signal_emit (
+		e_tree,
+		et_signals[WHITE_SPACE_EVENT], 0,
+		event, &return_val);
+	return return_val;
+}
+
+static gint
+et_canvas_root_event (GnomeCanvasItem *root,
+                      GdkEvent *event,
+                      ETree *e_tree)
+{
+	switch (event->type) {
+	case GDK_BUTTON_PRESS:
+	case GDK_2BUTTON_PRESS:
+	case GDK_BUTTON_RELEASE:
+		if (event->button.button != 4 && event->button.button != 5) {
+			if (gtk_widget_has_focus (GTK_WIDGET (root->canvas))) {
+				GnomeCanvasItem *item = GNOME_CANVAS (root->canvas)->focused_item;
+
+				if (E_IS_TABLE_ITEM (item)) {
+					e_table_item_leave_edit (E_TABLE_ITEM (item));
+					return TRUE;
+				}
+			}
+		}
+		break;
+	default:
+		break;
+	}
+
+	return FALSE;
+}
+
+/* Handler for focus events in the table_canvas; we have to repaint ourselves
+ * and give the focus to some ETableItem.
+ */
+static gboolean
+table_canvas_focus_event_cb (GtkWidget *widget,
+                             GdkEventFocus *event,
+                             gpointer data)
+{
+	GnomeCanvas *canvas;
+	ETree *tree;
+
+	gtk_widget_queue_draw (widget);
+
+	if (!event->in)
+		return TRUE;
+
+	canvas = GNOME_CANVAS (widget);
+	tree = E_TREE (data);
+
+	if (!canvas->focused_item ||
+		(e_selection_model_cursor_row (tree->priv->selection) == -1)) {
+		e_table_item_set_cursor (E_TABLE_ITEM (tree->priv->item), 0, 0);
+		gnome_canvas_item_grab_focus (tree->priv->item);
+	}
+
+	return TRUE;
+}
+
+static void
+e_tree_setup_table (ETree *e_tree)
+{
+	GtkWidget *widget;
+	GtkStyle *style;
+
+	e_tree->priv->table_canvas = GNOME_CANVAS (e_canvas_new ());
+	g_signal_connect (
+		e_tree->priv->table_canvas, "size_allocate",
+		G_CALLBACK (tree_canvas_size_allocate), e_tree);
+	g_signal_connect (
+		e_tree->priv->table_canvas, "focus_in_event",
+		G_CALLBACK (table_canvas_focus_event_cb), e_tree);
+	g_signal_connect (
+		e_tree->priv->table_canvas, "focus_out_event",
+		G_CALLBACK (table_canvas_focus_event_cb), e_tree);
+
+	g_signal_connect (
+		e_tree->priv->table_canvas, "drag_begin",
+		G_CALLBACK (et_drag_begin), e_tree);
+	g_signal_connect (
+		e_tree->priv->table_canvas, "drag_end",
+		G_CALLBACK (et_drag_end), e_tree);
+	g_signal_connect (
+		e_tree->priv->table_canvas, "drag_data_get",
+		G_CALLBACK (et_drag_data_get), e_tree);
+	g_signal_connect (
+		e_tree->priv->table_canvas, "drag_data_delete",
+		G_CALLBACK (et_drag_data_delete), e_tree);
+	g_signal_connect (
+		e_tree, "drag_motion",
+		G_CALLBACK (et_drag_motion), e_tree);
+	g_signal_connect (
+		e_tree, "drag_leave",
+		G_CALLBACK (et_drag_leave), e_tree);
+	g_signal_connect (
+		e_tree, "drag_drop",
+		G_CALLBACK (et_drag_drop), e_tree);
+	g_signal_connect (
+		e_tree, "drag_data_received",
+		G_CALLBACK (et_drag_data_received), e_tree);
+
+	g_signal_connect (
+		e_tree->priv->table_canvas, "reflow",
+		G_CALLBACK (tree_canvas_reflow), e_tree);
+
+	widget = GTK_WIDGET (e_tree->priv->table_canvas);
+	style = gtk_widget_get_style (widget);
+
+	gtk_widget_show (widget);
+
+	e_tree->priv->white_item = gnome_canvas_item_new (
+		gnome_canvas_root (e_tree->priv->table_canvas),
+		e_canvas_background_get_type (),
+		"fill_color_gdk", &style->base[GTK_STATE_NORMAL],
+		NULL);
+
+	g_signal_connect (
+		e_tree->priv->white_item, "event",
+		G_CALLBACK (white_item_event), e_tree);
+	g_signal_connect (
+		gnome_canvas_root (e_tree->priv->table_canvas), "event",
+		G_CALLBACK (et_canvas_root_event), e_tree);
+
+	et_build_item (e_tree);
+}
+
+/**
+ * e_tree_set_search_column:
+ * @e_tree: #ETree object that will be modified
+ * @col: Column index to use for searches
+ *
+ * This routine sets the current search column to be used for keypress
+ * searches of the #ETree. If -1 is passed in for column, the current
+ * search column is cleared.
+ */
+void
+e_tree_set_search_column (ETree *e_tree,
+                          gint col)
+{
+	if (col == -1) {
+		clear_current_search_col (e_tree);
+		return;
+	}
+
+	e_tree->priv->search_col_set = TRUE;
+	e_tree->priv->current_search_col = e_table_header_get_column (
+		e_tree->priv->full_header, col);
+}
+
+void
+e_tree_set_state_object (ETree *e_tree,
+                         ETableState *state)
+{
+	GValue *val;
+	GtkAllocation allocation;
+	GtkWidget *widget;
+
+	val = g_new0 (GValue, 1);
+	g_value_init (val, G_TYPE_DOUBLE);
+
+	connect_header (e_tree, state);
+
+	widget = GTK_WIDGET (e_tree->priv->table_canvas);
+	gtk_widget_get_allocation (widget, &allocation);
+
+	g_value_set_double (val, (gdouble) allocation.width);
+	g_object_set_property (G_OBJECT (e_tree->priv->header), "width", val);
+	g_free (val);
+
+	if (e_tree->priv->header_item)
+		g_object_set (
+			e_tree->priv->header_item,
+			"ETableHeader", e_tree->priv->header,
+			"sort_info", e_tree->priv->sort_info,
+			NULL);
+
+	if (e_tree->priv->item)
+		g_object_set (
+			e_tree->priv->item,
+			"ETableHeader", e_tree->priv->header,
+			NULL);
+
+	if (e_tree->priv->etta)
+		e_tree_table_adapter_set_sort_info (
+			e_tree->priv->etta, e_tree->priv->sort_info);
+
+	e_tree_state_change (e_tree);
+}
+
+/**
+ * e_tree_set_state:
+ * @e_tree: #ETree object that will be modified
+ * @state_str: a string with the XML representation of the #ETableState.
+ *
+ * This routine sets the state (as described by #ETableState) of the
+ * #ETree object.
+ */
+void
+e_tree_set_state (ETree *e_tree,
+                  const gchar *state_str)
+{
+	ETableState *state;
+
+	g_return_if_fail (e_tree != NULL);
+	g_return_if_fail (E_IS_TREE (e_tree));
+	g_return_if_fail (state_str != NULL);
+
+	state = e_table_state_new ();
+	e_table_state_load_from_string (state, state_str);
+
+	if (state->col_count > 0)
+		e_tree_set_state_object (e_tree, state);
+
+	g_object_unref (state);
+}
+
+/**
+ * e_tree_load_state:
+ * @e_tree: #ETree object that will be modified
+ * @filename: name of the file containing the state to be loaded into the #ETree
+ *
+ * An #ETableState will be loaded form the file pointed by @filename into the
+ * @e_tree object.
+ */
+void
+e_tree_load_state (ETree *e_tree,
+                   const gchar *filename)
+{
+	ETableState *state;
+
+	g_return_if_fail (e_tree != NULL);
+	g_return_if_fail (E_IS_TREE (e_tree));
+	g_return_if_fail (filename != NULL);
+
+	state = e_table_state_new ();
+	e_table_state_load_from_file (state, filename);
+
+	if (state->col_count > 0)
+		e_tree_set_state_object (e_tree, state);
+
+	g_object_unref (state);
+}
+
+/**
+ * e_tree_get_state_object:
+ * @e_tree: #ETree object to act on
+ *
+ * Builds an #ETableState corresponding to the current state of the
+ * #ETree.
+ *
+ * Return value:
+ * The %ETableState object generated.
+ **/
+ETableState *
+e_tree_get_state_object (ETree *e_tree)
+{
+	ETableState *state;
+	gint full_col_count;
+	gint i, j;
+
+	state = e_table_state_new ();
+	state->sort_info = e_tree->priv->sort_info;
+	if (state->sort_info)
+		g_object_ref (state->sort_info);
+
+	state->col_count = e_table_header_count (e_tree->priv->header);
+	full_col_count = e_table_header_count (e_tree->priv->full_header);
+	state->columns = g_new (int, state->col_count);
+	state->expansions = g_new (double, state->col_count);
+	for (i = 0; i < state->col_count; i++) {
+		ETableCol *col = e_table_header_get_column (e_tree->priv->header, i);
+		state->columns[i] = -1;
+		for (j = 0; j < full_col_count; j++) {
+			if (col->col_idx == e_table_header_index (e_tree->priv->full_header, j)) {
+				state->columns[i] = j;
+				break;
+			}
+		}
+		state->expansions[i] = col->expansion;
+	}
+
+	return state;
+}
+
+/**
+ * e_tree_get_state:
+ * @e_tree: The #ETree to act on
+ *
+ * Builds a state object based on the current state and returns the
+ * string corresponding to that state.
+ *
+ * Return value:
+ * A string describing the current state of the #ETree.
+ **/
+gchar *
+e_tree_get_state (ETree *e_tree)
+{
+	ETableState *state;
+	gchar *string;
+
+	state = e_tree_get_state_object (e_tree);
+	string = e_table_state_save_to_string (state);
+	g_object_unref (state);
+	return string;
+}
+
+/**
+ * e_tree_save_state:
+ * @e_tree: The #ETree to act on
+ * @filename: name of the file to save to
+ *
+ * Saves the state of the @e_tree object into the file pointed by
+ * @filename.
+ **/
+void
+e_tree_save_state (ETree *e_tree,
+                   const gchar *filename)
+{
+	ETableState *state;
+
+	state = e_tree_get_state_object (e_tree);
+	e_table_state_save_to_file (state, filename);
+	g_object_unref (state);
+}
+
+/**
+ * e_tree_get_spec:
+ * @e_tree: The #ETree to query
+ *
+ * Returns the specification object.
+ *
+ * Return value:
+ **/
+ETableSpecification *
+e_tree_get_spec (ETree *e_tree)
+{
+	return e_tree->priv->spec;
+}
+
+static void
+et_table_model_changed (ETableModel *model,
+                        ETree *et)
+{
+	if (et->priv->horizontal_scrolling)
+		e_table_header_update_horizontal (et->priv->header);
+}
+
+static void
+et_table_row_changed (ETableModel *table_model,
+                      gint row,
+                      ETree *et)
+{
+	et_table_model_changed (table_model, et);
+}
+
+static void
+et_table_cell_changed (ETableModel *table_model,
+                       gint view_col,
+                       gint row,
+                       ETree *et)
+{
+	et_table_model_changed (table_model, et);
+}
+
+static void
+et_table_rows_deleted (ETableModel *table_model,
+                       gint row,
+                       gint count,
+                       ETree *et)
+{
+	ETreePath * node, * prev_node;
+
+	/* If the cursor is still valid after this deletion, we're done */
+	if (e_selection_model_cursor_row (et->priv->selection) >= 0
+			|| row == 0)
+		return;
+
+	prev_node = e_tree_node_at_row (et, row - 1);
+	node = e_tree_get_cursor (et);
+
+	/* Check if the cursor is a child of the node directly before the
+	 * deleted region (implying that an expander was collapsed with
+	 * the cursor inside it) */
+	while (node) {
+		node = e_tree_model_node_get_parent (et->priv->model, node);
+		if (node == prev_node) {
+			/* Set the cursor to the still-visible parent */
+			e_tree_set_cursor (et, prev_node);
+			return;
+		}
+	}
+}
+
+static void
+et_connect_to_etta (ETree *et)
+{
+	et->priv->table_model_change_id = g_signal_connect (
+		et->priv->etta, "model_changed",
+		G_CALLBACK (et_table_model_changed), et);
+
+	et->priv->table_row_change_id = g_signal_connect (
+		et->priv->etta, "model_row_changed",
+		G_CALLBACK (et_table_row_changed), et);
+
+	et->priv->table_cell_change_id = g_signal_connect (
+		et->priv->etta, "model_cell_changed",
+		G_CALLBACK (et_table_cell_changed), et);
+
+	et->priv->table_rows_delete_id = g_signal_connect (
+		et->priv->etta, "model_rows_deleted",
+		G_CALLBACK (et_table_rows_deleted), et);
+
+}
+
+static gboolean
+et_real_construct (ETree *e_tree,
+                   ETreeModel *etm,
+                   ETableExtras *ete,
+                   ETableSpecification *specification,
+                   ETableState *state)
+{
+	GtkAdjustment *adjustment;
+	GtkScrollable *scrollable;
+	gint row = 0;
+
+	if (ete)
+		g_object_ref (ete);
+	else
+		ete = e_table_extras_new ();
+
+	e_tree->priv->alternating_row_colors = specification->alternating_row_colors;
+	e_tree->priv->horizontal_draw_grid = specification->horizontal_draw_grid;
+	e_tree->priv->vertical_draw_grid = specification->vertical_draw_grid;
+	e_tree->priv->draw_focus = specification->draw_focus;
+	e_tree->priv->cursor_mode = specification->cursor_mode;
+	e_tree->priv->full_header = e_table_spec_to_full_header (specification, ete);
+
+	connect_header (e_tree, state);
+
+	e_tree->priv->horizontal_scrolling = specification->horizontal_scrolling;
+
+	e_tree->priv->model = etm;
+	g_object_ref (etm);
+
+	e_tree->priv->etta = E_TREE_TABLE_ADAPTER (
+		e_tree_table_adapter_new (e_tree->priv->model,
+		e_tree->priv->sort_info, e_tree->priv->full_header));
+
+	et_connect_to_etta (e_tree);
+
+	e_tree->priv->sorter = e_sorter_new ();
+
+	g_object_set (
+		e_tree->priv->selection,
+		"sorter", e_tree->priv->sorter,
+#ifdef E_TREE_USE_TREE_SELECTION
+		"model", e_tree->priv->model,
+		"etta", e_tree->priv->etta,
+#else
+		"model", e_tree->priv->etta,
+#endif
+		"selection_mode", specification->selection_mode,
+		"cursor_mode", specification->cursor_mode,
+		NULL);
+
+	g_signal_connect (
+		e_tree->priv->selection, "selection_changed",
+		G_CALLBACK (et_selection_model_selection_changed), e_tree);
+	g_signal_connect (
+		e_tree->priv->selection, "selection_row_changed",
+		G_CALLBACK (et_selection_model_selection_row_changed), e_tree);
+
+	if (!specification->no_headers) {
+		e_tree_setup_header (e_tree);
+	}
+	e_tree_setup_table (e_tree);
+
+	scrollable = GTK_SCROLLABLE (e_tree->priv->table_canvas);
+
+	adjustment = gtk_scrollable_get_vadjustment (scrollable);
+	gtk_adjustment_set_step_increment (adjustment, 20);
+
+	adjustment = gtk_scrollable_get_hadjustment (scrollable);
+	gtk_adjustment_set_step_increment (adjustment, 20);
+
+	if (!specification->no_headers) {
+		/*
+		 * The header
+		 */
+		gtk_table_attach (
+			GTK_TABLE (e_tree),
+			GTK_WIDGET (e_tree->priv->header_canvas),
+			0, 1, 0 + row, 1 + row,
+			GTK_FILL | GTK_EXPAND,
+			GTK_FILL, 0, 0);
+		row++;
+	}
+
+	gtk_table_attach (
+		GTK_TABLE (e_tree),
+		GTK_WIDGET (e_tree->priv->table_canvas),
+		0, 1, 0 + row, 1 + row,
+		GTK_FILL | GTK_EXPAND,
+		GTK_FILL | GTK_EXPAND,
+		0, 0);
+
+	g_object_unref (ete);
+
+	return TRUE;
+}
+
+/**
+ * e_tree_construct:
+ * @e_tree: The newly created #ETree object.
+ * @etm: The model for this table.
+ * @ete: An optional #ETableExtras.  (%NULL is valid.)
+ * @spec_str: The spec.
+ * @state_str: An optional state.  (%NULL is valid.)
+ *
+ * This is the internal implementation of e_tree_new() for use by
+ * subclasses or language bindings.  See e_tree_new() for details.
+ *
+ * Return value: %TRUE on success, %FALSE if an error occurred
+ **/
+gboolean
+e_tree_construct (ETree *e_tree,
+                  ETreeModel *etm,
+                  ETableExtras *ete,
+                  const gchar *spec_str,
+                  const gchar *state_str)
+{
+	ETableSpecification *specification;
+	ETableState *state;
+
+	g_return_val_if_fail (e_tree != NULL, FALSE);
+	g_return_val_if_fail (E_IS_TREE (e_tree), FALSE);
+	g_return_val_if_fail (etm != NULL, FALSE);
+	g_return_val_if_fail (E_IS_TREE_MODEL (etm), FALSE);
+	g_return_val_if_fail (ete == NULL || E_IS_TABLE_EXTRAS (ete), FALSE);
+	g_return_val_if_fail (spec_str != NULL, FALSE);
+
+	specification = e_table_specification_new ();
+	if (!e_table_specification_load_from_string (specification, spec_str)) {
+		g_object_unref (specification);
+		return FALSE;
+	}
+	if (state_str) {
+		state = e_table_state_new ();
+		e_table_state_load_from_string (state, state_str);
+		if (state->col_count <= 0) {
+			g_object_unref (state);
+			state = specification->state;
+			g_object_ref (state);
+		}
+	} else {
+		state = specification->state;
+		g_object_ref (state);
+	}
+
+	if (!et_real_construct (e_tree, etm, ete, specification, state)) {
+		g_object_unref (specification);
+		g_object_unref (state);
+		return FALSE;
+	}
+
+	e_tree->priv->spec = specification;
+	e_tree->priv->spec->allow_grouping = FALSE;
+
+	g_object_unref (state);
+
+	return TRUE;
+}
+
+/**
+ * e_tree_construct_from_spec_file:
+ * @e_tree: The newly created #ETree object.
+ * @etm: The model for this tree
+ * @ete: An optional #ETableExtras  (%NULL is valid.)
+ * @spec_fn: The filename of the spec
+ * @state_fn: An optional state file  (%NULL is valid.)
+ *
+ * This is the internal implementation of e_tree_new_from_spec_file()
+ * for use by subclasses or language bindings.  See
+ * e_tree_new_from_spec_file() for details.
+ *
+ * Return value: %TRUE on success, %FALSE if an error occurred
+ **/
+gboolean
+e_tree_construct_from_spec_file (ETree *e_tree,
+                                 ETreeModel *etm,
+                                 ETableExtras *ete,
+                                 const gchar *spec_fn,
+                                 const gchar *state_fn)
+{
+	ETableSpecification *specification;
+	ETableState *state;
+
+	g_return_val_if_fail (e_tree != NULL, FALSE);
+	g_return_val_if_fail (E_IS_TREE (e_tree), FALSE);
+	g_return_val_if_fail (etm != NULL, FALSE);
+	g_return_val_if_fail (E_IS_TREE_MODEL (etm), FALSE);
+	g_return_val_if_fail (ete == NULL || E_IS_TABLE_EXTRAS (ete), FALSE);
+	g_return_val_if_fail (spec_fn != NULL, FALSE);
+
+	specification = e_table_specification_new ();
+	if (!e_table_specification_load_from_file (specification, spec_fn)) {
+		g_object_unref (specification);
+		return FALSE;
+	}
+	if (state_fn) {
+		state = e_table_state_new ();
+		if (!e_table_state_load_from_file (state, state_fn)) {
+			g_object_unref (state);
+			state = specification->state;
+			g_object_ref (state);
+		}
+		if (state->col_count <= 0) {
+			g_object_unref (state);
+			state = specification->state;
+			g_object_ref (state);
+		}
+	} else {
+		state = specification->state;
+		g_object_ref (state);
+	}
+
+	if (!et_real_construct (e_tree, etm, ete, specification, state)) {
+		g_object_unref (specification);
+		g_object_unref (state);
+		return FALSE;
+	}
+
+	e_tree->priv->spec = specification;
+	e_tree->priv->spec->allow_grouping = FALSE;
+
+	g_object_unref (state);
+
+	return TRUE;
+}
+
+/**
+ * e_tree_new:
+ * @etm: The model for this tree
+ * @ete: An optional #ETableExtras  (%NULL is valid.)
+ * @spec: The spec
+ * @state: An optional state  (%NULL is valid.)
+ *
+ * This function creates an #ETree from the given parameters.  The
+ * #ETreeModel is a tree model to be represented.  The #ETableExtras
+ * is an optional set of pixbufs, cells, and sorting functions to be
+ * used when interpreting the spec.  If you pass in %NULL it uses the
+ * default #ETableExtras.  (See e_table_extras_new()).
+ *
+ * @spec is the specification of the set of viewable columns and the
+ * default sorting state and such.  @state is an optional string
+ * specifying the current sorting state and such.  If @state is NULL,
+ * then the default state from the spec will be used.
+ *
+ * Return value:
+ * The newly created #ETree or %NULL if there's an error.
+ **/
+GtkWidget *
+e_tree_new (ETreeModel *etm,
+            ETableExtras *ete,
+            const gchar *spec,
+            const gchar *state)
+{
+	ETree *e_tree;
+
+	g_return_val_if_fail (etm != NULL, NULL);
+	g_return_val_if_fail (E_IS_TREE_MODEL (etm), NULL);
+	g_return_val_if_fail (ete == NULL || E_IS_TABLE_EXTRAS (ete), NULL);
+	g_return_val_if_fail (spec != NULL, NULL);
+
+	e_tree = g_object_new (E_TYPE_TREE, NULL);
+
+	if (!e_tree_construct (e_tree, etm, ete, spec, state)) {
+		g_object_unref (e_tree);
+		return NULL;
+	}
+
+	return (GtkWidget *) e_tree;
+}
+
+/**
+ * e_tree_new_from_spec_file:
+ * @etm: The model for this tree.
+ * @ete: An optional #ETableExtras.  (%NULL is valid.)
+ * @spec_fn: The filename of the spec.
+ * @state_fn: An optional state file.  (%NULL is valid.)
+ *
+ * This is very similar to e_tree_new(), except instead of passing in
+ * strings you pass in the file names of the spec and state to load.
+ *
+ * @spec_fn is the filename of the spec to load.  If this file doesn't
+ * exist, e_tree_new_from_spec_file will return %NULL.
+ *
+ * @state_fn is the filename of the initial state to load.  If this is
+ * %NULL or if the specified file doesn't exist, the default state
+ * from the spec file is used.
+ *
+ * Return value:
+ * The newly created #ETree or %NULL if there's an error.
+ **/
+GtkWidget *
+e_tree_new_from_spec_file (ETreeModel *etm,
+                           ETableExtras *ete,
+                           const gchar *spec_fn,
+                           const gchar *state_fn)
+{
+	ETree *e_tree;
+
+	g_return_val_if_fail (etm != NULL, NULL);
+	g_return_val_if_fail (E_IS_TREE_MODEL (etm), NULL);
+	g_return_val_if_fail (ete == NULL || E_IS_TABLE_EXTRAS (ete), NULL);
+	g_return_val_if_fail (spec_fn != NULL, NULL);
+
+	e_tree = g_object_new (E_TYPE_TREE, NULL);
+
+	if (!e_tree_construct_from_spec_file (e_tree, etm, ete, spec_fn, state_fn)) {
+		g_object_unref (e_tree);
+		return NULL;
+	}
+
+	return (GtkWidget *) e_tree;
+}
+
+void
+e_tree_show_cursor_after_reflow (ETree *e_tree)
+{
+	g_return_if_fail (e_tree != NULL);
+	g_return_if_fail (E_IS_TREE (e_tree));
+
+	e_tree->priv->show_cursor_after_reflow = TRUE;
+}
+
+void
+e_tree_set_cursor (ETree *e_tree,
+                   ETreePath path)
+{
+#ifndef E_TREE_USE_TREE_SELECTION
+	gint row;
+#endif
+	g_return_if_fail (e_tree != NULL);
+	g_return_if_fail (E_IS_TREE (e_tree));
+	g_return_if_fail (path != NULL);
+
+#ifdef E_TREE_USE_TREE_SELECTION
+	e_tree_selection_model_select_single_path (
+		E_TREE_SELECTION_MODEL (e_tree->priv->selection), path);
+	e_tree_selection_model_change_cursor (
+		E_TREE_SELECTION_MODEL (e_tree->priv->selection), path);
+#else
+	row = e_tree_table_adapter_row_of_node (
+		E_TREE_TABLE_ADAPTER (e_tree->priv->etta), path);
+
+	if (row == -1)
+		return;
+
+	g_object_set (
+		e_tree->priv->selection,
+		"cursor_row", row,
+		NULL);
+#endif
+}
+
+ETreePath
+e_tree_get_cursor (ETree *e_tree)
+{
+#ifdef E_TREE_USE_TREE_SELECTION
+	return e_tree_selection_model_get_cursor (
+		E_TREE_SELECTION_MODEL (e_tree->priv->selection));
+#else
+	gint row;
+	g_return_val_if_fail (e_tree != NULL, NULL);
+	g_return_val_if_fail (E_IS_TREE (e_tree), NULL);
+
+	g_object_get (
+		e_tree->priv->selection,
+		"cursor_row", &row,
+		NULL);
+	if (row == -1)
+		return NULL;
+
+	return e_tree_table_adapter_node_at_row (
+		E_TREE_TABLE_ADAPTER (e_tree->priv->etta), row);
+#endif
+}
+
+void
+e_tree_selected_row_foreach (ETree *e_tree,
+                             EForeachFunc callback,
+                             gpointer closure)
+{
+	g_return_if_fail (e_tree != NULL);
+	g_return_if_fail (E_IS_TREE (e_tree));
+
+	e_selection_model_foreach (e_tree->priv->selection,
+				  callback,
+				  closure);
+}
+
+#ifdef E_TREE_USE_TREE_SELECTION
+void
+e_tree_selected_path_foreach (ETree *e_tree,
+                              ETreeForeachFunc callback,
+                              gpointer closure)
+{
+	g_return_if_fail (e_tree != NULL);
+	g_return_if_fail (E_IS_TREE (e_tree));
+
+	e_tree_selection_model_foreach (
+		E_TREE_SELECTION_MODEL (e_tree->priv->selection),
+		callback, closure);
+}
+
+/* Standard functions */
+static void
+et_foreach_recurse (ETreeModel *model,
+                    ETreePath path,
+                    ETreeForeachFunc callback,
+                    gpointer closure)
+{
+	ETreePath child;
+
+	callback (path, closure);
+
+	child = e_tree_model_node_get_first_child (E_TREE_MODEL (model), path);
+	for (; child; child = e_tree_model_node_get_next (E_TREE_MODEL (model), child))
+		if (child)
+			et_foreach_recurse (model, child, callback, closure);
+}
+
+void
+e_tree_path_foreach (ETree *e_tree,
+                     ETreeForeachFunc callback,
+                     gpointer closure)
+{
+	ETreePath root;
+
+	g_return_if_fail (e_tree != NULL);
+	g_return_if_fail (E_IS_TREE (e_tree));
+
+	root = e_tree_model_get_root (e_tree->priv->model);
+
+	if (root)
+		et_foreach_recurse (e_tree->priv->model,
+				    root,
+				    callback,
+				    closure);
+}
+#endif
+
+EPrintable *
+e_tree_get_printable (ETree *e_tree)
+{
+	g_return_val_if_fail (e_tree != NULL, NULL);
+	g_return_val_if_fail (E_IS_TREE (e_tree), NULL);
+
+	return e_table_item_get_printable (E_TABLE_ITEM (e_tree->priv->item));
+}
+
+static void
+et_get_property (GObject *object,
+                 guint property_id,
+                 GValue *value,
+                 GParamSpec *pspec)
+{
+	ETree *etree = E_TREE (object);
+
+	switch (property_id) {
+	case PROP_ETTA:
+		g_value_set_object (value, etree->priv->etta);
+		break;
+
+	case PROP_UNIFORM_ROW_HEIGHT:
+		g_value_set_boolean (value, etree->priv->uniform_row_height);
+		break;
+
+	case PROP_ALWAYS_SEARCH:
+		g_value_set_boolean (value, etree->priv->always_search);
+		break;
+
+	case PROP_HADJUSTMENT:
+		if (etree->priv->table_canvas)
+			g_object_get_property (
+				G_OBJECT (etree->priv->table_canvas),
+				"hadjustment", value);
+		else
+			g_value_set_object (value, NULL);
+		break;
+
+	case PROP_VADJUSTMENT:
+		if (etree->priv->table_canvas)
+			g_object_get_property (
+				G_OBJECT (etree->priv->table_canvas),
+				"vadjustment", value);
+		else
+			g_value_set_object (value, NULL);
+		break;
+
+	case PROP_HSCROLL_POLICY:
+		if (etree->priv->table_canvas)
+			g_object_get_property (
+				G_OBJECT (etree->priv->table_canvas),
+				"hscroll-policy", value);
+		else
+			g_value_set_enum (value, 0);
+		break;
+
+	case PROP_VSCROLL_POLICY:
+		if (etree->priv->table_canvas)
+			g_object_get_property (
+				G_OBJECT (etree->priv->table_canvas),
+				"vscroll-policy", value);
+		else
+			g_value_set_enum (value, 0);
+		break;
+
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+		break;
+	}
+}
+
+typedef struct {
+	gchar     *arg;
+	gboolean  setting;
+} bool_closure;
+
+static void
+et_set_property (GObject *object,
+                 guint property_id,
+                 const GValue *value,
+                 GParamSpec *pspec)
+{
+	ETree *etree = E_TREE (object);
+
+	switch (property_id) {
+	case PROP_LENGTH_THRESHOLD:
+		etree->priv->length_threshold = g_value_get_int (value);
+		if (etree->priv->item) {
+			gnome_canvas_item_set (
+				GNOME_CANVAS_ITEM (etree->priv->item),
+				"length_threshold",
+				etree->priv->length_threshold,
+				NULL);
+		}
+		break;
+
+	case PROP_HORIZONTAL_DRAW_GRID:
+		etree->priv->horizontal_draw_grid = g_value_get_boolean (value);
+		if (etree->priv->item) {
+			gnome_canvas_item_set (
+				GNOME_CANVAS_ITEM (etree->priv->item),
+				"horizontal_draw_grid",
+				etree->priv->horizontal_draw_grid,
+				NULL);
+		}
+		break;
+
+	case PROP_VERTICAL_DRAW_GRID:
+		etree->priv->vertical_draw_grid = g_value_get_boolean (value);
+		if (etree->priv->item) {
+			gnome_canvas_item_set (
+				GNOME_CANVAS_ITEM (etree->priv->item),
+				"vertical_draw_grid",
+				etree->priv->vertical_draw_grid,
+				NULL);
+		}
+		break;
+
+	case PROP_DRAW_FOCUS:
+		etree->priv->draw_focus = g_value_get_boolean (value);
+		if (etree->priv->item) {
+			gnome_canvas_item_set (
+				GNOME_CANVAS_ITEM (etree->priv->item),
+				"drawfocus",
+				etree->priv->draw_focus,
+				NULL);
+		}
+		break;
+
+	case PROP_UNIFORM_ROW_HEIGHT:
+		etree->priv->uniform_row_height = g_value_get_boolean (value);
+		if (etree->priv->item) {
+			gnome_canvas_item_set (
+				GNOME_CANVAS_ITEM (etree->priv->item),
+				"uniform_row_height",
+				etree->priv->uniform_row_height,
+				NULL);
+		}
+		break;
+
+	case PROP_ALWAYS_SEARCH:
+		if (etree->priv->always_search == g_value_get_boolean (value))
+			return;
+		etree->priv->always_search = g_value_get_boolean (value);
+		clear_current_search_col (etree);
+		break;
+
+	case PROP_HADJUSTMENT:
+		if (etree->priv->table_canvas)
+			g_object_set_property (
+				G_OBJECT (etree->priv->table_canvas),
+				"hadjustment", value);
+		break;
+
+	case PROP_VADJUSTMENT:
+		if (etree->priv->table_canvas)
+			g_object_set_property (
+				G_OBJECT (etree->priv->table_canvas),
+				"vadjustment", value);
+		break;
+
+	case PROP_HSCROLL_POLICY:
+		if (etree->priv->table_canvas)
+			g_object_set_property (
+				G_OBJECT (etree->priv->table_canvas),
+				"hscroll-policy", value);
+		break;
+
+	case PROP_VSCROLL_POLICY:
+		if (etree->priv->table_canvas)
+			g_object_set_property (
+				G_OBJECT (etree->priv->table_canvas),
+				"vscroll-policy", value);
+		break;
+	}
+}
+
+gint
+e_tree_get_next_row (ETree *e_tree,
+                     gint model_row)
+{
+	g_return_val_if_fail (e_tree != NULL, -1);
+	g_return_val_if_fail (E_IS_TREE (e_tree), -1);
+
+	if (e_tree->priv->sorter) {
+		gint i;
+		i = e_sorter_model_to_sorted (E_SORTER (e_tree->priv->sorter), model_row);
+		i++;
+		if (i < e_table_model_row_count (E_TABLE_MODEL (e_tree->priv->etta))) {
+			return e_sorter_sorted_to_model (E_SORTER (e_tree->priv->sorter), i);
+		} else
+			return -1;
+	} else {
+		gint row_count;
+
+		row_count = e_table_model_row_count (
+			E_TABLE_MODEL (e_tree->priv->etta));
+
+		if (model_row < row_count - 1)
+			return model_row + 1;
+		else
+			return -1;
+	}
+}
+
+gint
+e_tree_get_prev_row (ETree *e_tree,
+                     gint model_row)
+{
+	g_return_val_if_fail (e_tree != NULL, -1);
+	g_return_val_if_fail (E_IS_TREE (e_tree), -1);
+
+	if (e_tree->priv->sorter) {
+		gint i;
+		i = e_sorter_model_to_sorted (E_SORTER (e_tree->priv->sorter), model_row);
+		i--;
+		if (i >= 0)
+			return e_sorter_sorted_to_model (E_SORTER (e_tree->priv->sorter), i);
+		else
+			return -1;
+	} else
+		return model_row - 1;
+}
+
+gint
+e_tree_model_to_view_row (ETree *e_tree,
+                          gint model_row)
+{
+	g_return_val_if_fail (e_tree != NULL, -1);
+	g_return_val_if_fail (E_IS_TREE (e_tree), -1);
+
+	if (e_tree->priv->sorter)
+		return e_sorter_model_to_sorted (E_SORTER (e_tree->priv->sorter), model_row);
+	else
+		return model_row;
+}
+
+gint
+e_tree_view_to_model_row (ETree *e_tree,
+                          gint view_row)
+{
+	g_return_val_if_fail (e_tree != NULL, -1);
+	g_return_val_if_fail (E_IS_TREE (e_tree), -1);
+
+	if (e_tree->priv->sorter)
+		return e_sorter_sorted_to_model (E_SORTER (e_tree->priv->sorter), view_row);
+	else
+		return view_row;
+}
+
+gboolean
+e_tree_node_is_expanded (ETree *et,
+                         ETreePath path)
+{
+	g_return_val_if_fail (path, FALSE);
+
+	return e_tree_table_adapter_node_is_expanded (et->priv->etta, path);
+}
+
+void
+e_tree_node_set_expanded (ETree *et,
+                          ETreePath path,
+                          gboolean expanded)
+{
+	g_return_if_fail (et != NULL);
+	g_return_if_fail (E_IS_TREE (et));
+
+	e_tree_table_adapter_node_set_expanded (et->priv->etta, path, expanded);
+}
+
+void
+e_tree_node_set_expanded_recurse (ETree *et,
+                                  ETreePath path,
+                                  gboolean expanded)
+{
+	g_return_if_fail (et != NULL);
+	g_return_if_fail (E_IS_TREE (et));
+
+	e_tree_table_adapter_node_set_expanded_recurse (et->priv->etta, path, expanded);
+}
+
+void
+e_tree_root_node_set_visible (ETree *et,
+                              gboolean visible)
+{
+	g_return_if_fail (et != NULL);
+	g_return_if_fail (E_IS_TREE (et));
+
+	e_tree_table_adapter_root_node_set_visible (et->priv->etta, visible);
+}
+
+ETreePath
+e_tree_node_at_row (ETree *et,
+                    gint row)
+{
+	ETreePath path = { 0 };
+
+	g_return_val_if_fail (et != NULL, path);
+
+	path = e_tree_table_adapter_node_at_row (et->priv->etta, row);
+
+	return path;
+}
+
+gint
+e_tree_row_of_node (ETree *et,
+                    ETreePath path)
+{
+	g_return_val_if_fail (et != NULL, -1);
+
+	return e_tree_table_adapter_row_of_node (et->priv->etta, path);
+}
+
+gboolean
+e_tree_root_node_is_visible (ETree *et)
+{
+	g_return_val_if_fail (et != NULL, FALSE);
+
+	return e_tree_table_adapter_root_node_is_visible (et->priv->etta);
+}
+
+void
+e_tree_show_node (ETree *et,
+                  ETreePath path)
+{
+	g_return_if_fail (et != NULL);
+	g_return_if_fail (E_IS_TREE (et));
+
+	e_tree_table_adapter_show_node (et->priv->etta, path);
+}
+
+void
+e_tree_save_expanded_state (ETree *et,
+                            gchar *filename)
+{
+	g_return_if_fail (et != NULL);
+	g_return_if_fail (E_IS_TREE (et));
+
+	e_tree_table_adapter_save_expanded_state (et->priv->etta, filename);
+}
+
+void
+e_tree_load_expanded_state (ETree *et,
+                            gchar *filename)
+{
+	g_return_if_fail (et != NULL);
+
+	e_tree_table_adapter_load_expanded_state (et->priv->etta, filename);
+}
+
+xmlDoc *
+e_tree_save_expanded_state_xml (ETree *et)
+{
+	g_return_val_if_fail (et != NULL, NULL);
+	g_return_val_if_fail (E_IS_TREE (et), NULL);
+
+	return e_tree_table_adapter_save_expanded_state_xml (et->priv->etta);
+}
+
+void
+e_tree_load_expanded_state_xml (ETree *et,
+                                xmlDoc *doc)
+{
+	g_return_if_fail (et != NULL);
+	g_return_if_fail (E_IS_TREE (et));
+	g_return_if_fail (doc != NULL);
+
+	e_tree_table_adapter_load_expanded_state_xml (et->priv->etta, doc);
+}
+
+/* state: <0 ... collapse; 0 ... no force - use default; >0 ... expand;
+ * when using this, be sure to reset to 0 once no forcing is required
+ * anymore, aka the build of the tree is done */
+void
+e_tree_force_expanded_state (ETree *et,
+                             gint state)
+{
+	g_return_if_fail (et != NULL);
+
+	e_tree_table_adapter_force_expanded_state (et->priv->etta, state);
+}
+
+gint
+e_tree_row_count (ETree *et)
+{
+	g_return_val_if_fail (et != NULL, -1);
+
+	return e_table_model_row_count (E_TABLE_MODEL (et->priv->etta));
+}
+
+GtkWidget *
+e_tree_get_tooltip (ETree *et)
+{
+	g_return_val_if_fail (et != NULL, NULL);
+
+	return E_CANVAS (et->priv->table_canvas)->tooltip_window;
+}
+
+static ETreePath
+find_next_in_range (ETree *et,
+                    gint start,
+                    gint end,
+                    ETreePathFunc func,
+                    gpointer data)
+{
+	ETreePath path;
+	gint row;
+
+	for (row = start; row <= end; row++) {
+		path = e_tree_table_adapter_node_at_row (et->priv->etta, row);
+		if (path && func (et->priv->model, path, data))
+			return path;
+	}
+
+	return NULL;
+}
+
+static ETreePath
+find_prev_in_range (ETree *et,
+                    gint start,
+                    gint end,
+                    ETreePathFunc func,
+                    gpointer data)
+{
+	ETreePath path;
+	gint row;
+
+	for (row = start; row >= end; row--) {
+		path = e_tree_table_adapter_node_at_row (et->priv->etta, row);
+		if (path && func (et->priv->model, path, data))
+			return path;
+	}
+
+	return NULL;
+}
+
+gboolean
+e_tree_find_next (ETree *et,
+                  ETreeFindNextParams params,
+                  ETreePathFunc func,
+                  gpointer data)
+{
+	ETreePath cursor, found;
+	gint row, row_count;
+
+	cursor = e_tree_get_cursor (et);
+	row = e_tree_table_adapter_row_of_node (et->priv->etta, cursor);
+	row_count = e_table_model_row_count (E_TABLE_MODEL (et->priv->etta));
+
+	if (params & E_TREE_FIND_NEXT_FORWARD)
+		found = find_next_in_range (et, row + 1, row_count - 1, func, data);
+	else
+		found = find_prev_in_range (et, row == -1 ? -1 : row - 1, 0, func, data);
+
+	if (found) {
+		e_tree_table_adapter_show_node (et->priv->etta, found);
+		e_tree_set_cursor (et, found);
+		return TRUE;
+	}
+
+	if (params & E_TREE_FIND_NEXT_WRAP) {
+		if (params & E_TREE_FIND_NEXT_FORWARD)
+			found = find_next_in_range (et, 0, row, func, data);
+		else
+			found = find_prev_in_range (et, row_count - 1, row, func, data);
+
+		if (found && found != cursor) {
+			e_tree_table_adapter_show_node (et->priv->etta, found);
+			e_tree_set_cursor (et, found);
+			return TRUE;
+		}
+	}
+
+	return FALSE;
+}
+
+void
+e_tree_right_click_up (ETree *et)
+{
+	e_selection_model_right_click_up (et->priv->selection);
+}
+
+/**
+ * e_tree_get_model:
+ * @et: the ETree
+ *
+ * Returns the model upon which this ETree is based.
+ *
+ * Returns: the model
+ **/
+ETreeModel *
+e_tree_get_model (ETree *et)
+{
+	g_return_val_if_fail (et != NULL, NULL);
+	g_return_val_if_fail (E_IS_TREE (et), NULL);
+
+	return et->priv->model;
+}
+
+/**
+ * e_tree_get_selection_model:
+ * @et: the ETree
+ *
+ * Returns the selection model of this ETree.
+ *
+ * Returns: the selection model
+ **/
+ESelectionModel *
+e_tree_get_selection_model (ETree *et)
+{
+	g_return_val_if_fail (et != NULL, NULL);
+	g_return_val_if_fail (E_IS_TREE (et), NULL);
+
+	return et->priv->selection;
+}
+
+/**
+ * e_tree_get_table_adapter:
+ * @et: the ETree
+ *
+ * Returns the table adapter this ETree uses.
+ *
+ * Returns: the model
+ **/
+ETreeTableAdapter *
+e_tree_get_table_adapter (ETree *et)
+{
+	g_return_val_if_fail (et != NULL, NULL);
+	g_return_val_if_fail (E_IS_TREE (et), NULL);
+
+	return et->priv->etta;
+}
+
+ETableItem *
+e_tree_get_item (ETree *et)
+{
+	g_return_val_if_fail (et != NULL, NULL);
+	g_return_val_if_fail (E_IS_TREE (et), NULL);
+
+	return E_TABLE_ITEM (et->priv->item);
+}
+
+GnomeCanvasItem *
+e_tree_get_header_item (ETree *et)
+{
+	g_return_val_if_fail (et != NULL, NULL);
+	g_return_val_if_fail (E_IS_TREE (et), NULL);
+
+	return et->priv->header_item;
+}
+
+struct _ETreeDragSourceSite
+{
+	GdkModifierType    start_button_mask;
+	GtkTargetList     *target_list;        /* Targets for drag data */
+	GdkDragAction      actions;            /* Possible actions */
+	GdkPixbuf         *pixbuf;             /* Icon for drag data */
+
+	/* Stored button press information to detect drag beginning */
+	gint               state;
+	gint               x, y;
+	gint               row, col;
+};
+
+typedef enum
+{
+  GTK_DRAG_STATUS_DRAG,
+  GTK_DRAG_STATUS_WAIT,
+  GTK_DRAG_STATUS_DROP
+} GtkDragStatus;
+
+typedef struct _GtkDragDestInfo GtkDragDestInfo;
+typedef struct _GtkDragSourceInfo GtkDragSourceInfo;
+
+struct _GtkDragDestInfo
+{
+  GtkWidget         *widget;	   /* Widget in which drag is in */
+  GdkDragContext    *context;	   /* Drag context */
+  GtkDragSourceInfo *proxy_source; /* Set if this is a proxy drag */
+  GtkSelectionData  *proxy_data;   /* Set while retrieving proxied data */
+  guint              dropped : 1;     /* Set after we receive a drop */
+  guint32            proxy_drop_time; /* Timestamp for proxied drop */
+  guint              proxy_drop_wait : 1; /* Set if we are waiting for a
+					   * status reply before sending
+					   * a proxied drop on.
+					   */
+  gint               drop_x, drop_y; /* Position of drop */
+};
+
+struct _GtkDragSourceInfo
+{
+  GtkWidget         *widget;
+  GtkTargetList     *target_list; /* Targets for drag data */
+  GdkDragAction      possible_actions; /* Actions allowed by source */
+  GdkDragContext    *context;	  /* drag context */
+  GtkWidget         *icon_window; /* Window for drag */
+  GtkWidget         *ipc_widget;  /* GtkInvisible for grab, message passing */
+  GdkCursor         *cursor;	  /* Cursor for drag */
+  gint hot_x, hot_y;		  /* Hot spot for drag */
+  gint button;			  /* mouse button starting drag */
+
+  GtkDragStatus      status;	  /* drag status */
+  GdkEvent          *last_event;  /* motion event waiting for response */
+
+  gint               start_x, start_y; /* Initial position */
+  gint               cur_x, cur_y;     /* Current Position */
+
+  GList             *selections;  /* selections we've claimed */
+
+  GtkDragDestInfo   *proxy_dest;  /* Set if this is a proxy drag */
+
+  guint              drop_timeout;     /* Timeout for aborting drop */
+  guint              destroy_icon : 1; /* If true, destroy icon_window
+					*/
+};
+
+/* Drag & drop stuff. */
+/* Target */
+
+void
+e_tree_drag_get_data (ETree *tree,
+                      gint row,
+                      gint col,
+                      GdkDragContext *context,
+                      GdkAtom target,
+                      guint32 time)
+{
+	g_return_if_fail (tree != NULL);
+	g_return_if_fail (E_IS_TREE (tree));
+
+	gtk_drag_get_data (
+		GTK_WIDGET (tree),
+		context,
+		target,
+		time);
+
+}
+
+/**
+ * e_tree_drag_highlight:
+ * @tree:
+ * @row:
+ * @col:
+ *
+ * Set col to -1 to highlight the entire row.
+ * Set row to -1 to turn off the highlight.
+ */
+void
+e_tree_drag_highlight (ETree *tree,
+                       gint row,
+                       gint col)
+{
+	GtkAllocation allocation;
+	GtkAdjustment *adjustment;
+	GtkScrollable *scrollable;
+	GtkStyle *style;
+
+	g_return_if_fail (E_IS_TREE (tree));
+
+	scrollable = GTK_SCROLLABLE (tree->priv->table_canvas);
+	style = gtk_widget_get_style (GTK_WIDGET (tree));
+	gtk_widget_get_allocation (GTK_WIDGET (scrollable), &allocation);
+
+	if (row != -1) {
+		gint x, y, width, height;
+		if (col == -1) {
+			e_tree_get_cell_geometry (tree, row, 0, &x, &y, &width, &height);
+			x = 0;
+			width = allocation.width;
+		} else {
+			e_tree_get_cell_geometry (tree, row, col, &x, &y, &width, &height);
+			adjustment = gtk_scrollable_get_hadjustment (scrollable);
+			x += gtk_adjustment_get_value (adjustment);
+		}
+
+		adjustment = gtk_scrollable_get_vadjustment (scrollable);
+		y += gtk_adjustment_get_value (adjustment);
+
+		if (tree->priv->drop_highlight == NULL) {
+			tree->priv->drop_highlight = gnome_canvas_item_new (
+				gnome_canvas_root (tree->priv->table_canvas),
+				gnome_canvas_rect_get_type (),
+				"fill_color", NULL,
+				"outline_color_gdk", &style->fg[GTK_STATE_NORMAL],
+				NULL);
+		}
+
+		gnome_canvas_item_set (
+			tree->priv->drop_highlight,
+			"x1", (gdouble) x,
+			"x2", (gdouble) x + width - 1,
+			"y1", (gdouble) y,
+			"y2", (gdouble) y + height - 1,
+			NULL);
+	} else {
+		g_object_run_dispose (G_OBJECT (tree->priv->drop_highlight));
+		tree->priv->drop_highlight = NULL;
+	}
+}
+
+void
+e_tree_drag_unhighlight (ETree *tree)
+{
+	g_return_if_fail (tree != NULL);
+	g_return_if_fail (E_IS_TREE (tree));
+
+	if (tree->priv->drop_highlight) {
+		g_object_run_dispose (G_OBJECT (tree->priv->drop_highlight));
+		tree->priv->drop_highlight = NULL;
+	}
+}
+
+void e_tree_drag_dest_set   (ETree               *tree,
+			     GtkDestDefaults       flags,
+			     const GtkTargetEntry *targets,
+			     gint                  n_targets,
+			     GdkDragAction         actions)
+{
+	g_return_if_fail (tree != NULL);
+	g_return_if_fail (E_IS_TREE (tree));
+
+	gtk_drag_dest_set (
+		GTK_WIDGET (tree),
+		flags,
+		targets,
+		n_targets,
+		actions);
+}
+
+void e_tree_drag_dest_set_proxy (ETree         *tree,
+				 GdkWindow      *proxy_window,
+				 GdkDragProtocol protocol,
+				 gboolean        use_coordinates)
+{
+	g_return_if_fail (tree != NULL);
+	g_return_if_fail (E_IS_TREE (tree));
+
+	gtk_drag_dest_set_proxy (
+		GTK_WIDGET (tree),
+		proxy_window,
+		protocol,
+		use_coordinates);
+}
+
+/*
+ * There probably should be functions for setting the targets
+ * as a GtkTargetList
+ */
+
+void
+e_tree_drag_dest_unset (GtkWidget *widget)
+{
+	g_return_if_fail (widget != NULL);
+	g_return_if_fail (E_IS_TREE (widget));
+
+	gtk_drag_dest_unset (widget);
+}
+
+/* Source side */
+
+static gint
+et_real_start_drag (ETree *tree,
+                    gint row,
+                    ETreePath path,
+                    gint col,
+                    GdkEvent *event)
+{
+	GtkDragSourceInfo *info;
+	GdkDragContext *context;
+	ETreeDragSourceSite *site;
+
+	if (tree->priv->do_drag) {
+		site = tree->priv->site;
+
+		site->state = 0;
+		context = e_tree_drag_begin (
+			tree, row, col,
+			site->target_list,
+			site->actions,
+			1, event);
+
+		if (context) {
+			info = g_dataset_get_data (context, "gtk-info");
+
+			if (info && !info->icon_window) {
+				if (site->pixbuf)
+					gtk_drag_set_icon_pixbuf (
+						context,
+						site->pixbuf,
+						-2, -2);
+				else
+					gtk_drag_set_icon_default (context);
+			}
+		}
+		return TRUE;
+	}
+	return FALSE;
+}
+
+void
+e_tree_drag_source_set (ETree *tree,
+                        GdkModifierType start_button_mask,
+                        const GtkTargetEntry *targets,
+                        gint n_targets,
+                        GdkDragAction actions)
+{
+	ETreeDragSourceSite *site;
+	GtkWidget *canvas;
+
+	g_return_if_fail (tree != NULL);
+	g_return_if_fail (E_IS_TREE (tree));
+
+	canvas = GTK_WIDGET (tree->priv->table_canvas);
+	site = tree->priv->site;
+
+	tree->priv->do_drag = TRUE;
+
+	gtk_widget_add_events (
+		canvas,
+		gtk_widget_get_events (canvas) |
+		GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
+		GDK_BUTTON_MOTION_MASK | GDK_STRUCTURE_MASK);
+
+	if (site) {
+		if (site->target_list)
+			gtk_target_list_unref (site->target_list);
+	} else {
+		site = g_new0 (ETreeDragSourceSite, 1);
+		tree->priv->site = site;
+	}
+
+	site->start_button_mask = start_button_mask;
+
+	if (targets)
+		site->target_list = gtk_target_list_new (targets, n_targets);
+	else
+		site->target_list = NULL;
+
+	site->actions = actions;
+}
+
+void
+e_tree_drag_source_unset (ETree *tree)
+{
+	ETreeDragSourceSite *site;
+
+	g_return_if_fail (tree != NULL);
+	g_return_if_fail (E_IS_TREE (tree));
+
+	site = tree->priv->site;
+
+	if (site) {
+		if (site->target_list)
+			gtk_target_list_unref (site->target_list);
+		g_free (site);
+		tree->priv->site = NULL;
+	}
+}
+
+/* There probably should be functions for setting the targets
+ * as a GtkTargetList
+ */
+
+GdkDragContext *
+e_tree_drag_begin (ETree *tree,
+                   gint row,
+                   gint col,
+                   GtkTargetList *targets,
+                   GdkDragAction actions,
+                   gint button,
+                   GdkEvent *event)
+{
+	ETreePath path;
+	g_return_val_if_fail (tree != NULL, NULL);
+	g_return_val_if_fail (E_IS_TREE (tree), NULL);
+
+	path = e_tree_table_adapter_node_at_row (tree->priv->etta, row);
+
+	tree->priv->drag_row = row;
+	tree->priv->drag_path = path;
+	tree->priv->drag_col = col;
+
+	return gtk_drag_begin (
+		GTK_WIDGET (tree->priv->table_canvas),
+		targets,
+		actions,
+		button,
+		event);
+}
+
+/**
+ * e_tree_is_dragging:
+ * @tree: An #ETree widget
+ *
+ * Returns whether is @tree in a drag&drop operation.
+ **/
+gboolean
+e_tree_is_dragging (ETree *tree)
+{
+	g_return_val_if_fail (tree != NULL, FALSE);
+	g_return_val_if_fail (tree->priv != NULL, FALSE);
+
+	return tree->priv->is_dragging;
+}
+
+/**
+ * e_tree_get_cell_at:
+ * @tree: An ETree widget
+ * @x: X coordinate for the pixel
+ * @y: Y coordinate for the pixel
+ * @row_return: Pointer to return the row value
+ * @col_return: Pointer to return the column value
+ *
+ * Return the row and column for the cell in which the pixel at (@x, @y) is
+ * contained.
+ **/
+void
+e_tree_get_cell_at (ETree *tree,
+                    gint x,
+                    gint y,
+                    gint *row_return,
+                    gint *col_return)
+{
+	GtkAdjustment *adjustment;
+	GtkScrollable *scrollable;
+
+	g_return_if_fail (E_IS_TREE (tree));
+	g_return_if_fail (row_return != NULL);
+	g_return_if_fail (col_return != NULL);
+
+	/* FIXME it would be nice if it could handle a NULL row_return or
+	 * col_return gracefully.  */
+
+	if (row_return)
+		*row_return = -1;
+	if (col_return)
+		*col_return = -1;
+
+	scrollable = GTK_SCROLLABLE (tree->priv->table_canvas);
+
+	adjustment = gtk_scrollable_get_hadjustment (scrollable);
+	x += gtk_adjustment_get_value (adjustment);
+
+	adjustment = gtk_scrollable_get_vadjustment (scrollable);
+	y += gtk_adjustment_get_value (adjustment);
+
+	e_table_item_compute_location (
+		E_TABLE_ITEM (tree->priv->item),
+		&x, &y, row_return, col_return);
+}
+
+/**
+ * e_tree_get_cell_geometry:
+ * @tree: The tree.
+ * @row: The row to get the geometry of.
+ * @col: The col to get the geometry of.
+ * @x_return: Returns the x coordinate of the upper right hand corner
+ * of the cell with respect to the widget.
+ * @y_return: Returns the y coordinate of the upper right hand corner
+ * of the cell with respect to the widget.
+ * @width_return: Returns the width of the cell.
+ * @height_return: Returns the height of the cell.
+ *
+ * Computes the data about this cell.
+ **/
+void
+e_tree_get_cell_geometry (ETree *tree,
+                          gint row,
+                          gint col,
+                          gint *x_return,
+                          gint *y_return,
+                          gint *width_return,
+                          gint *height_return)
+{
+	GtkAdjustment *adjustment;
+	GtkScrollable *scrollable;
+
+	g_return_if_fail (E_IS_TREE (tree));
+	g_return_if_fail (row >= 0);
+	g_return_if_fail (col >= 0);
+
+	/* FIXME it would be nice if it could handle a NULL row_return or
+	 * col_return gracefully.  */
+
+	e_table_item_get_cell_geometry (
+		E_TABLE_ITEM (tree->priv->item),
+		&row, &col, x_return, y_return,
+		width_return, height_return);
+
+	scrollable = GTK_SCROLLABLE (tree->priv->table_canvas);
+
+	if (x_return) {
+		adjustment = gtk_scrollable_get_hadjustment (scrollable);
+		(*x_return) -= gtk_adjustment_get_value (adjustment);
+	}
+
+	if (y_return) {
+		adjustment = gtk_scrollable_get_vadjustment (scrollable);
+		(*y_return) -= gtk_adjustment_get_value (adjustment);
+	}
+}
+
+static void
+et_drag_begin (GtkWidget *widget,
+               GdkDragContext *context,
+               ETree *et)
+{
+	et->priv->is_dragging = TRUE;
+
+	g_signal_emit (
+		et,
+		et_signals[TREE_DRAG_BEGIN], 0,
+		et->priv->drag_row,
+		et->priv->drag_path,
+		et->priv->drag_col,
+		context);
+}
+
+static void
+et_drag_end (GtkWidget *widget,
+             GdkDragContext *context,
+             ETree *et)
+{
+	et->priv->is_dragging = FALSE;
+
+	g_signal_emit (
+		et,
+		et_signals[TREE_DRAG_END], 0,
+		et->priv->drag_row,
+		et->priv->drag_path,
+		et->priv->drag_col,
+		context);
+}
+
+static void
+et_drag_data_get (GtkWidget *widget,
+                  GdkDragContext *context,
+                  GtkSelectionData *selection_data,
+                  guint info,
+                  guint time,
+                  ETree *et)
+{
+	g_signal_emit (
+		et,
+		et_signals[TREE_DRAG_DATA_GET], 0,
+		et->priv->drag_row,
+		et->priv->drag_path,
+		et->priv->drag_col,
+		context,
+		selection_data,
+		info,
+		time);
+}
+
+static void
+et_drag_data_delete (GtkWidget *widget,
+                     GdkDragContext *context,
+                     ETree *et)
+{
+	g_signal_emit (
+		et,
+		et_signals[TREE_DRAG_DATA_DELETE], 0,
+		et->priv->drag_row,
+		et->priv->drag_path,
+		et->priv->drag_col,
+		context);
+}
+
+static gboolean
+do_drag_motion (ETree *et,
+                GdkDragContext *context,
+                gint x,
+                gint y,
+                guint time)
+{
+	gboolean ret_val = FALSE;
+	gint row, col;
+	ETreePath path;
+
+	e_tree_get_cell_at (et, x, y, &row, &col);
+
+	if (row != et->priv->drop_row && col != et->priv->drop_col) {
+		g_signal_emit (
+			et, et_signals[TREE_DRAG_LEAVE], 0,
+			et->priv->drop_row,
+			et->priv->drop_path,
+			et->priv->drop_col,
+			context,
+			time);
+	}
+
+	path = e_tree_table_adapter_node_at_row (et->priv->etta, row);
+
+	et->priv->drop_row = row;
+	et->priv->drop_path = path;
+	et->priv->drop_col = col;
+	g_signal_emit (
+		et, et_signals[TREE_DRAG_MOTION], 0,
+		et->priv->drop_row,
+		et->priv->drop_path,
+		et->priv->drop_col,
+		context,
+		x, y,
+		time,
+		&ret_val);
+
+	return ret_val;
+}
+
+static gboolean
+scroll_timeout (gpointer data)
+{
+	ETree *et = data;
+	gint dx = 0, dy = 0;
+	GtkAdjustment *adjustment;
+	GtkScrollable *scrollable;
+	gdouble old_h_value;
+	gdouble new_h_value;
+	gdouble old_v_value;
+	gdouble new_v_value;
+	gdouble page_size;
+	gdouble lower;
+	gdouble upper;
+
+	if (et->priv->scroll_direction & ET_SCROLL_DOWN)
+		dy += 20;
+	if (et->priv->scroll_direction & ET_SCROLL_UP)
+		dy -= 20;
+
+	if (et->priv->scroll_direction & ET_SCROLL_RIGHT)
+		dx += 20;
+	if (et->priv->scroll_direction & ET_SCROLL_LEFT)
+		dx -= 20;
+
+	scrollable = GTK_SCROLLABLE (et->priv->table_canvas);
+
+	adjustment = gtk_scrollable_get_hadjustment (scrollable);
+
+	page_size = gtk_adjustment_get_page_size (adjustment);
+	lower = gtk_adjustment_get_lower (adjustment);
+	upper = gtk_adjustment_get_upper (adjustment);
+
+	old_h_value = gtk_adjustment_get_value (adjustment);
+	new_h_value = CLAMP (old_h_value + dx, lower, upper - page_size);
+
+	gtk_adjustment_set_value (adjustment, new_h_value);
+
+	adjustment = gtk_scrollable_get_vadjustment (scrollable);
+
+	page_size = gtk_adjustment_get_page_size (adjustment);
+	lower = gtk_adjustment_get_lower (adjustment);
+	upper = gtk_adjustment_get_upper (adjustment);
+
+	old_v_value = gtk_adjustment_get_value (adjustment);
+	new_v_value = CLAMP (old_v_value + dy, lower, upper - page_size);
+
+	gtk_adjustment_set_value (adjustment, new_v_value);
+
+	if (new_h_value != old_h_value || new_v_value != old_v_value)
+		do_drag_motion (
+			et,
+			et->priv->last_drop_context,
+			et->priv->last_drop_x,
+			et->priv->last_drop_y,
+			et->priv->last_drop_time);
+
+	return TRUE;
+}
+
+static void
+scroll_on (ETree *et,
+           guint scroll_direction)
+{
+	if (et->priv->scroll_idle_id == 0 ||
+			scroll_direction != et->priv->scroll_direction) {
+		if (et->priv->scroll_idle_id != 0)
+			g_source_remove (et->priv->scroll_idle_id);
+		et->priv->scroll_direction = scroll_direction;
+		et->priv->scroll_idle_id = g_timeout_add (100, scroll_timeout, et);
+	}
+}
+
+static void
+scroll_off (ETree *et)
+{
+	if (et->priv->scroll_idle_id) {
+		g_source_remove (et->priv->scroll_idle_id);
+		et->priv->scroll_idle_id = 0;
+	}
+}
+
+static gboolean
+hover_timeout (gpointer data)
+{
+	ETree *et = data;
+	gint x = et->priv->hover_x;
+	gint y = et->priv->hover_y;
+	gint row, col;
+	ETreePath path;
+
+	e_tree_get_cell_at (et, x, y, &row, &col);
+
+	path = e_tree_table_adapter_node_at_row (et->priv->etta, row);
+	if (path && e_tree_model_node_is_expandable (et->priv->model, path)) {
+		if (!e_tree_table_adapter_node_is_expanded (et->priv->etta, path)) {
+			if (e_tree_model_has_save_id (et->priv->model) &&
+				e_tree_model_has_get_node_by_id (et->priv->model))
+				et->priv->expanded_list = g_list_prepend (
+					et->priv->expanded_list,
+					e_tree_model_get_save_id (
+					et->priv->model, path));
+
+			e_tree_table_adapter_node_set_expanded (
+				et->priv->etta, path, TRUE);
+		}
+	}
+
+	return TRUE;
+}
+
+static void
+hover_on (ETree *et,
+          gint x,
+          gint y)
+{
+	et->priv->hover_x = x;
+	et->priv->hover_y = y;
+	if (et->priv->hover_idle_id != 0)
+		g_source_remove (et->priv->hover_idle_id);
+	et->priv->hover_idle_id = g_timeout_add (500, hover_timeout, et);
+}
+
+static void
+hover_off (ETree *et)
+{
+	if (et->priv->hover_idle_id) {
+		g_source_remove (et->priv->hover_idle_id);
+		et->priv->hover_idle_id = 0;
+	}
+}
+
+static void
+collapse_drag (ETree *et,
+               ETreePath drop)
+{
+	GList *list;
+
+	/* We only want to leave open parents of the node dropped in.
+	 * Not the node itself. */
+	if (drop) {
+		drop = e_tree_model_node_get_parent (et->priv->model, drop);
+	}
+
+	for (list = et->priv->expanded_list; list; list = list->next) {
+		gchar *save_id = list->data;
+		ETreePath path;
+
+		path = e_tree_model_get_node_by_id (et->priv->model, save_id);
+		if (path) {
+			ETreePath search;
+			gboolean found = FALSE;
+
+			for (search = drop; search;
+				search = e_tree_model_node_get_parent (
+				et->priv->model, search)) {
+				if (path == search) {
+					found = TRUE;
+					break;
+				}
+			}
+
+			if (!found)
+				e_tree_table_adapter_node_set_expanded (
+					et->priv->etta, path, FALSE);
+		}
+		g_free (save_id);
+	}
+	g_list_free (et->priv->expanded_list);
+	et->priv->expanded_list = NULL;
+}
+
+static void
+context_destroyed (gpointer data,
+                   GObject *ctx)
+{
+	ETree *et = data;
+	if (et->priv) {
+		et->priv->last_drop_x       = 0;
+		et->priv->last_drop_y       = 0;
+		et->priv->last_drop_time    = 0;
+		et->priv->last_drop_context = NULL;
+		collapse_drag (et, NULL);
+		scroll_off (et);
+		hover_off (et);
+	}
+	g_object_unref (et);
+}
+
+static void
+context_connect (ETree *et,
+                 GdkDragContext *context)
+{
+	if (context == et->priv->last_drop_context)
+		return;
+
+	if (et->priv->last_drop_context)
+		g_object_weak_unref (
+			G_OBJECT (et->priv->last_drop_context),
+			context_destroyed, et);
+	else
+		g_object_ref (et);
+
+	g_object_weak_ref (G_OBJECT (context), context_destroyed, et);
+}
+
+static void
+et_drag_leave (GtkWidget *widget,
+               GdkDragContext *context,
+               guint time,
+               ETree *et)
+{
+	g_signal_emit (
+		et,
+		et_signals[TREE_DRAG_LEAVE], 0,
+		et->priv->drop_row,
+		et->priv->drop_path,
+		et->priv->drop_col,
+		context,
+		time);
+	et->priv->drop_row = -1;
+	et->priv->drop_col = -1;
+
+	scroll_off (et);
+	hover_off (et);
+}
+
+static gboolean
+et_drag_motion (GtkWidget *widget,
+                GdkDragContext *context,
+                gint x,
+                gint y,
+                guint time,
+                ETree *et)
+{
+	GtkAllocation allocation;
+	gint ret_val;
+	guint direction = 0;
+
+	et->priv->last_drop_x = x;
+	et->priv->last_drop_y = y;
+	et->priv->last_drop_time = time;
+	context_connect (et, context);
+	et->priv->last_drop_context = context;
+
+	if (et->priv->hover_idle_id != 0) {
+		if (abs (et->priv->hover_x - x) > 3 ||
+		    abs (et->priv->hover_y - y) > 3) {
+			hover_on (et, x, y);
+		}
+	} else {
+		hover_on (et, x, y);
+	}
+
+	ret_val = do_drag_motion (et, context, x, y, time);
+
+	gtk_widget_get_allocation (widget, &allocation);
+
+	if (y < 20)
+		direction |= ET_SCROLL_UP;
+	if (y > allocation.height - 20)
+		direction |= ET_SCROLL_DOWN;
+	if (x < 20)
+		direction |= ET_SCROLL_LEFT;
+	if (x > allocation.width - 20)
+		direction |= ET_SCROLL_RIGHT;
+
+	if (direction != 0)
+		scroll_on (et, direction);
+	else
+		scroll_off (et);
+
+	return ret_val;
+}
+
+static gboolean
+et_drag_drop (GtkWidget *widget,
+              GdkDragContext *context,
+              gint x,
+              gint y,
+              guint time,
+              ETree *et)
+{
+	gboolean ret_val = FALSE;
+	gint row, col;
+	ETreePath path;
+
+	e_tree_get_cell_at (et, x, y, &row, &col);
+
+	path = e_tree_table_adapter_node_at_row (et->priv->etta, row);
+
+	if (row != et->priv->drop_row && col != et->priv->drop_row) {
+		g_signal_emit (
+			et, et_signals[TREE_DRAG_LEAVE], 0,
+			et->priv->drop_row,
+			et->priv->drop_path,
+			et->priv->drop_col,
+			context,
+			time);
+		g_signal_emit (
+			et, et_signals[TREE_DRAG_MOTION], 0,
+			row,
+			path,
+			col,
+			context,
+			x,
+			y,
+			time,
+			&ret_val);
+	}
+	et->priv->drop_row = row;
+	et->priv->drop_path = path;
+	et->priv->drop_col = col;
+
+	g_signal_emit (
+		et, et_signals[TREE_DRAG_DROP], 0,
+		et->priv->drop_row,
+		et->priv->drop_path,
+		et->priv->drop_col,
+		context,
+		x,
+		y,
+		time,
+		&ret_val);
+
+	et->priv->drop_row = -1;
+	et->priv->drop_path = NULL;
+	et->priv->drop_col = -1;
+
+	collapse_drag (et, path);
+
+	scroll_off (et);
+	return ret_val;
+}
+
+static void
+et_drag_data_received (GtkWidget *widget,
+                       GdkDragContext *context,
+                       gint x,
+                       gint y,
+                       GtkSelectionData *selection_data,
+                       guint info,
+                       guint time,
+                       ETree *et)
+{
+	gint row, col;
+	ETreePath path;
+
+	e_tree_get_cell_at (et, x, y, &row, &col);
+
+	path = e_tree_table_adapter_node_at_row (et->priv->etta, row);
+	g_signal_emit (
+		et, et_signals[TREE_DRAG_DATA_RECEIVED], 0,
+		row,
+		path,
+		col,
+		context,
+		x,
+		y,
+		selection_data,
+		info,
+		time);
+}
+
+static void
+e_tree_class_init (ETreeClass *class)
+{
+	GObjectClass *object_class;
+	GtkWidgetClass *widget_class;
+
+	g_type_class_add_private (class, sizeof (ETreePrivate));
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->dispose = et_dispose;
+	object_class->set_property = et_set_property;
+	object_class->get_property = et_get_property;
+
+	widget_class = GTK_WIDGET_CLASS (class);
+	widget_class->grab_focus = et_grab_focus;
+	widget_class->unrealize = et_unrealize;
+	widget_class->style_set = et_canvas_style_set;
+	widget_class->focus = et_focus;
+
+	class->start_drag = et_real_start_drag;
+
+	et_signals[CURSOR_CHANGE] = g_signal_new (
+		"cursor_change",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ETreeClass, cursor_change),
+		NULL, NULL,
+		e_marshal_NONE__INT_POINTER,
+		G_TYPE_NONE, 2,
+		G_TYPE_INT,
+		G_TYPE_POINTER);
+
+	et_signals[CURSOR_ACTIVATED] = g_signal_new (
+		"cursor_activated",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ETreeClass, cursor_activated),
+		NULL, NULL,
+		e_marshal_NONE__INT_POINTER,
+		G_TYPE_NONE, 2,
+		G_TYPE_INT,
+		G_TYPE_POINTER);
+
+	et_signals[SELECTION_CHANGE] = g_signal_new (
+		"selection_change",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ETreeClass, selection_change),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__VOID,
+		G_TYPE_NONE, 0);
+
+	et_signals[DOUBLE_CLICK] = g_signal_new (
+		"double_click",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ETreeClass, double_click),
+		NULL, NULL,
+		e_marshal_NONE__INT_POINTER_INT_BOXED,
+		G_TYPE_NONE, 4,
+		G_TYPE_INT,
+		G_TYPE_POINTER,
+		G_TYPE_INT,
+		GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
+
+	et_signals[RIGHT_CLICK] = g_signal_new (
+		"right_click",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ETreeClass, right_click),
+		g_signal_accumulator_true_handled, NULL,
+		e_marshal_BOOLEAN__INT_POINTER_INT_BOXED,
+		G_TYPE_BOOLEAN, 4,
+		G_TYPE_INT,
+		G_TYPE_POINTER,
+		G_TYPE_INT,
+		GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
+
+	et_signals[CLICK] = g_signal_new (
+		"click",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ETreeClass, click),
+		g_signal_accumulator_true_handled, NULL,
+		e_marshal_BOOLEAN__INT_POINTER_INT_BOXED,
+		G_TYPE_BOOLEAN, 4,
+		G_TYPE_INT,
+		G_TYPE_POINTER,
+		G_TYPE_INT,
+		GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
+
+	et_signals[KEY_PRESS] = g_signal_new (
+		"key_press",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ETreeClass, key_press),
+		g_signal_accumulator_true_handled, NULL,
+		e_marshal_BOOLEAN__INT_POINTER_INT_BOXED,
+		G_TYPE_BOOLEAN, 4,
+		G_TYPE_INT,
+		G_TYPE_POINTER,
+		G_TYPE_INT,
+		GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
+
+	et_signals[START_DRAG] = g_signal_new (
+		"start_drag",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ETreeClass, start_drag),
+		NULL, NULL,
+		e_marshal_NONE__INT_POINTER_INT_BOXED,
+		G_TYPE_NONE, 4,
+		G_TYPE_INT,
+		G_TYPE_POINTER,
+		G_TYPE_INT,
+		GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
+
+	et_signals[STATE_CHANGE] = g_signal_new (
+		"state_change",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ETreeClass, state_change),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__VOID,
+		G_TYPE_NONE, 0);
+
+	et_signals[WHITE_SPACE_EVENT] = g_signal_new (
+		"white_space_event",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ETreeClass, white_space_event),
+		g_signal_accumulator_true_handled, NULL,
+		e_marshal_BOOLEAN__POINTER,
+		G_TYPE_BOOLEAN, 1,
+		GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
+
+	et_signals[TREE_DRAG_BEGIN] = g_signal_new (
+		"tree_drag_begin",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ETreeClass, tree_drag_begin),
+		NULL, NULL,
+		e_marshal_NONE__INT_POINTER_INT_BOXED,
+		G_TYPE_NONE, 4,
+		G_TYPE_INT,
+		G_TYPE_POINTER,
+		G_TYPE_INT,
+		GDK_TYPE_DRAG_CONTEXT);
+
+	et_signals[TREE_DRAG_END] = g_signal_new (
+		"tree_drag_end",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ETreeClass, tree_drag_end),
+		NULL, NULL,
+		e_marshal_NONE__INT_POINTER_INT_BOXED,
+		G_TYPE_NONE, 4,
+		G_TYPE_INT,
+		G_TYPE_POINTER,
+		G_TYPE_INT,
+		GDK_TYPE_DRAG_CONTEXT);
+
+	et_signals[TREE_DRAG_DATA_GET] = g_signal_new (
+		"tree_drag_data_get",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ETreeClass, tree_drag_data_get),
+		NULL, NULL,
+		e_marshal_NONE__INT_POINTER_INT_OBJECT_BOXED_UINT_UINT,
+		G_TYPE_NONE, 7,
+		G_TYPE_INT,
+		G_TYPE_POINTER,
+		G_TYPE_INT,
+		GDK_TYPE_DRAG_CONTEXT,
+		GTK_TYPE_SELECTION_DATA | G_SIGNAL_TYPE_STATIC_SCOPE,
+		G_TYPE_UINT,
+		G_TYPE_UINT);
+
+	et_signals[TREE_DRAG_DATA_DELETE] = g_signal_new (
+		"tree_drag_data_delete",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ETreeClass, tree_drag_data_delete),
+		NULL, NULL,
+		e_marshal_NONE__INT_POINTER_INT_OBJECT,
+		G_TYPE_NONE, 4,
+		G_TYPE_INT,
+		G_TYPE_POINTER,
+		G_TYPE_INT,
+		GDK_TYPE_DRAG_CONTEXT);
+
+	et_signals[TREE_DRAG_LEAVE] = g_signal_new (
+		"tree_drag_leave",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ETreeClass, tree_drag_leave),
+		NULL, NULL,
+		e_marshal_NONE__INT_POINTER_INT_OBJECT_UINT,
+		G_TYPE_NONE, 5,
+		G_TYPE_INT,
+		G_TYPE_POINTER,
+		G_TYPE_INT,
+		GDK_TYPE_DRAG_CONTEXT,
+		G_TYPE_UINT);
+
+	et_signals[TREE_DRAG_MOTION] = g_signal_new (
+		"tree_drag_motion",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ETreeClass, tree_drag_motion),
+		NULL, NULL,
+		e_marshal_BOOLEAN__INT_POINTER_INT_OBJECT_INT_INT_UINT,
+		G_TYPE_BOOLEAN, 7,
+		G_TYPE_INT,
+		G_TYPE_POINTER,
+		G_TYPE_INT,
+		GDK_TYPE_DRAG_CONTEXT,
+		G_TYPE_INT,
+		G_TYPE_INT,
+		G_TYPE_UINT);
+
+	et_signals[TREE_DRAG_DROP] = g_signal_new (
+		"tree_drag_drop",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ETreeClass, tree_drag_drop),
+		NULL, NULL,
+		e_marshal_BOOLEAN__INT_POINTER_INT_OBJECT_INT_INT_UINT,
+		G_TYPE_BOOLEAN, 7,
+		G_TYPE_INT,
+		G_TYPE_POINTER,
+		G_TYPE_INT,
+		GDK_TYPE_DRAG_CONTEXT,
+		G_TYPE_INT,
+		G_TYPE_INT,
+		G_TYPE_UINT);
+
+	et_signals[TREE_DRAG_DATA_RECEIVED] = g_signal_new (
+		"tree_drag_data_received",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (ETreeClass, tree_drag_data_received),
+		NULL, NULL,
+		e_marshal_NONE__INT_POINTER_INT_OBJECT_INT_INT_BOXED_UINT_UINT,
+		G_TYPE_NONE, 9,
+		G_TYPE_INT,
+		G_TYPE_POINTER,
+		G_TYPE_INT,
+		GDK_TYPE_DRAG_CONTEXT,
+		G_TYPE_INT,
+		G_TYPE_INT,
+		GTK_TYPE_SELECTION_DATA,
+		G_TYPE_UINT,
+		G_TYPE_UINT);
+
+	g_object_class_install_property (
+		object_class,
+		PROP_LENGTH_THRESHOLD,
+		g_param_spec_int (
+			"length_threshold",
+			"Length Threshold",
+			"Length Threshold",
+			0, G_MAXINT, 0,
+			G_PARAM_WRITABLE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_HORIZONTAL_DRAW_GRID,
+		g_param_spec_boolean (
+			"horizontal_draw_grid",
+			"Horizontal Draw Grid",
+			"Horizontal Draw Grid",
+			FALSE,
+			G_PARAM_WRITABLE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_VERTICAL_DRAW_GRID,
+		g_param_spec_boolean (
+			"vertical_draw_grid",
+			"Vertical Draw Grid",
+			"Vertical Draw Grid",
+			FALSE,
+			G_PARAM_WRITABLE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_DRAW_FOCUS,
+		g_param_spec_boolean (
+			"drawfocus",
+			"Draw focus",
+			"Draw focus",
+			FALSE,
+			G_PARAM_WRITABLE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_ETTA,
+		g_param_spec_object (
+			"ETreeTableAdapter",
+			"ETree table adapter",
+			"ETree table adapter",
+			E_TYPE_TREE_TABLE_ADAPTER,
+			G_PARAM_READABLE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_UNIFORM_ROW_HEIGHT,
+		g_param_spec_boolean (
+			"uniform_row_height",
+			"Uniform row height",
+			"Uniform row height",
+			FALSE,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_ALWAYS_SEARCH,
+		g_param_spec_boolean (
+			"always_search",
+			"Always search",
+			"Always search",
+			FALSE,
+			G_PARAM_READWRITE));
+
+	gtk_widget_class_install_style_property (
+		widget_class,
+		g_param_spec_int (
+			"expander_size",
+			"Expander Size",
+			"Size of the expander arrow",
+			0, G_MAXINT, 10,
+			G_PARAM_READABLE));
+
+	gtk_widget_class_install_style_property (
+		widget_class,
+		g_param_spec_int (
+			"vertical-spacing",
+			"Vertical Row Spacing",
+			"Vertical space between rows. "
+			"It is added to top and to bottom of a row",
+			0, G_MAXINT, 3,
+			G_PARAM_READABLE |
+			G_PARAM_STATIC_STRINGS));
+
+	/* Scrollable interface */
+	g_object_class_override_property (
+		object_class, PROP_HADJUSTMENT, "hadjustment");
+	g_object_class_override_property (
+		object_class, PROP_VADJUSTMENT, "vadjustment");
+	g_object_class_override_property (
+		object_class, PROP_HSCROLL_POLICY, "hscroll-policy");
+	g_object_class_override_property (
+		object_class, PROP_VSCROLL_POLICY, "vscroll-policy");
+
+	gal_a11y_e_tree_init ();
+}
+
+static void
+tree_size_allocate (GtkWidget *widget,
+                    GtkAllocation *alloc,
+                    ETree *tree)
+{
+	gdouble width;
+
+	g_return_if_fail (tree != NULL);
+	g_return_if_fail (tree->priv != NULL);
+	g_return_if_fail (tree->priv->info_text != NULL);
+
+	gnome_canvas_get_scroll_region (
+		GNOME_CANVAS (tree->priv->table_canvas),
+		NULL, NULL, &width, NULL);
+
+	width -= 60.0;
+
+	g_object_set (
+		tree->priv->info_text, "width", width,
+		"clip_width", width, NULL);
+}
+
+/**
+ * e_tree_set_info_message:
+ * @tree: #ETree instance
+ * @info_message: Message to set. Can be NULL.
+ *
+ * Creates an info message in table area, or removes old.
+ **/
+void
+e_tree_set_info_message (ETree *tree,
+                         const gchar *info_message)
+{
+	GtkAllocation allocation;
+	GtkWidget *widget;
+
+	g_return_if_fail (tree != NULL);
+	g_return_if_fail (tree->priv != NULL);
+
+	if (!tree->priv->info_text && (!info_message || !*info_message))
+		return;
+
+	if (!info_message || !*info_message) {
+		g_signal_handler_disconnect (tree, tree->priv->info_text_resize_id);
+		g_object_run_dispose (G_OBJECT (tree->priv->info_text));
+		tree->priv->info_text = NULL;
+		return;
+	}
+
+	widget = GTK_WIDGET (tree->priv->table_canvas);
+	gtk_widget_get_allocation (widget, &allocation);
+
+	if (!tree->priv->info_text) {
+		if (allocation.width > 60) {
+			tree->priv->info_text = gnome_canvas_item_new (
+				GNOME_CANVAS_GROUP (gnome_canvas_root (tree->priv->table_canvas)),
+				e_text_get_type (),
+				"line_wrap", TRUE,
+				"clip", TRUE,
+				"justification", GTK_JUSTIFY_LEFT,
+				"text", info_message,
+				"width", (gdouble) allocation.width - 60.0,
+				"clip_width", (gdouble) allocation.width - 60.0,
+				NULL);
+
+			e_canvas_item_move_absolute (tree->priv->info_text, 30, 30);
+
+			tree->priv->info_text_resize_id = g_signal_connect (
+				tree, "size_allocate",
+				G_CALLBACK (tree_size_allocate), tree);
+		}
+	} else
+		gnome_canvas_item_set (tree->priv->info_text, "text", info_message, NULL);
+}
+
+void
+e_tree_freeze_state_change (ETree *tree)
+{
+	g_return_if_fail (tree != NULL);
+
+	tree->priv->state_change_freeze++;
+	if (tree->priv->state_change_freeze == 1)
+		tree->priv->state_changed = FALSE;
+
+	g_return_if_fail (tree->priv->state_change_freeze != 0);
+}
+
+void
+e_tree_thaw_state_change (ETree *tree)
+{
+	g_return_if_fail (tree != NULL);
+	g_return_if_fail (tree->priv->state_change_freeze != 0);
+
+	tree->priv->state_change_freeze--;
+	if (tree->priv->state_change_freeze == 0 && tree->priv->state_changed) {
+		tree->priv->state_changed = FALSE;
+		e_tree_state_change (tree);
+	}
+}
diff --git a/e-util/e-tree.h b/e-util/e-tree.h
new file mode 100644
index 0000000..1d6243c
--- /dev/null
+++ b/e-util/e-tree.h
@@ -0,0 +1,376 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef _E_TREE_H_
+#define _E_TREE_H_
+
+#include <gtk/gtk.h>
+#include <libxml/tree.h>
+#include <libgnomecanvas/libgnomecanvas.h>
+
+#include <e-util/e-printable.h>
+#include <e-util/e-table-extras.h>
+#include <e-util/e-table-item.h>
+#include <e-util/e-table-specification.h>
+#include <e-util/e-table-state.h>
+#include <e-util/e-tree-model.h>
+#include <e-util/e-tree-table-adapter.h>
+
+#define E_TREE_USE_TREE_SELECTION
+
+#ifdef E_TREE_USE_TREE_SELECTION
+#include <e-util/e-tree-selection-model.h>
+#endif
+
+/* Standard GObject macros */
+#define E_TYPE_TREE \
+	(e_tree_get_type ())
+#define E_TREE(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_TREE, ETree))
+#define E_TREE_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_TREE, ETreeClass))
+#define E_IS_TREE(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_TREE))
+#define E_IS_TREE_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_TREE))
+#define E_TREE_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_TREE))
+
+G_BEGIN_DECLS
+
+typedef struct _ETreeDragSourceSite ETreeDragSourceSite;
+
+typedef struct _ETree ETree;
+typedef struct _ETreeClass ETreeClass;
+typedef struct _ETreePrivate ETreePrivate;
+
+struct _ETree {
+	GtkTable parent;
+	ETreePrivate *priv;
+};
+
+struct _ETreeClass {
+	GtkTableClass parent_class;
+
+	void		(*cursor_change)	(ETree *et,
+						 gint row,
+						 ETreePath path);
+	void		(*cursor_activated)	(ETree *et,
+						 gint row,
+						 ETreePath path);
+	void		(*selection_change)	(ETree *et);
+	void		(*double_click)		(ETree *et,
+						 gint row,
+						 ETreePath path,
+						 gint col,
+						 GdkEvent *event);
+	gboolean	(*right_click)		(ETree *et,
+						 gint row,
+						 ETreePath path,
+						 gint col,
+						 GdkEvent *event);
+	gboolean	(*click)		(ETree *et,
+						 gint row,
+						 ETreePath path,
+						 gint col,
+						 GdkEvent *event);
+	gboolean	(*key_press)		(ETree *et,
+						 gint row,
+						 ETreePath path,
+						 gint col,
+						 GdkEvent *event);
+	gboolean	(*start_drag)		(ETree *et,
+						 gint row,
+						 ETreePath path,
+						 gint col,
+						 GdkEvent *event);
+	void		(*state_change)		(ETree *et);
+	gboolean	(*white_space_event)	(ETree *et,
+						 GdkEvent *event);
+
+	/* Source side drag signals */
+	void		(*tree_drag_begin)	(ETree	 *tree,
+						 gint row,
+						 ETreePath path,
+						 gint col,
+						 GdkDragContext *context);
+	void		(*tree_drag_end)	(ETree	 *tree,
+						 gint row,
+						 ETreePath path,
+						 gint col,
+						 GdkDragContext *context);
+	void		(*tree_drag_data_get)	(ETree *tree,
+						 gint row,
+						 ETreePath path,
+						 gint col,
+						 GdkDragContext *context,
+						 GtkSelectionData *selection_data,
+						 guint info,
+						 guint time);
+	void		(*tree_drag_data_delete)
+						(ETree	 *tree,
+						 gint row,
+						 ETreePath path,
+						 gint col,
+						 GdkDragContext *context);
+
+	/* Target side drag signals */
+	void		(*tree_drag_leave)	(ETree	 *tree,
+						 gint row,
+						 ETreePath path,
+						 gint col,
+						 GdkDragContext *context,
+						 guint time);
+	gboolean	(*tree_drag_motion)	(ETree	 *tree,
+						 gint row,
+						 ETreePath path,
+						 gint col,
+						 GdkDragContext *context,
+						 gint x,
+						 gint y,
+						 guint time);
+	gboolean	(*tree_drag_drop)	(ETree	 *tree,
+						 gint row,
+						 ETreePath path,
+						 gint col,
+						 GdkDragContext *context,
+						 gint x,
+						 gint y,
+						 guint time);
+	void		(*tree_drag_data_received)
+						(ETree *tree,
+						 gint row,
+						 ETreePath path,
+						 gint col,
+						 GdkDragContext *context,
+						 gint x,
+						 gint y,
+						 GtkSelectionData *selection_data,
+						 guint info,
+						 guint time);
+};
+
+GType		e_tree_get_type			(void) G_GNUC_CONST;
+gboolean	e_tree_construct		(ETree *e_tree,
+						 ETreeModel *etm,
+						 ETableExtras *ete,
+						 const gchar *spec,
+						 const gchar *state);
+GtkWidget *	e_tree_new			(ETreeModel *etm,
+						 ETableExtras *ete,
+						 const gchar *spec,
+						 const gchar *state);
+
+/* Create an ETree using files. */
+gboolean	e_tree_construct_from_spec_file	(ETree *e_tree,
+						 ETreeModel *etm,
+						 ETableExtras *ete,
+						 const gchar *spec_fn,
+						 const gchar *state_fn);
+GtkWidget *	e_tree_new_from_spec_file	(ETreeModel *etm,
+						 ETableExtras *ete,
+						 const gchar *spec_fn,
+						 const gchar *state_fn);
+
+/* To save the state */
+gchar *		e_tree_get_state		(ETree *e_tree);
+void		e_tree_save_state		(ETree *e_tree,
+						 const gchar *filename);
+ETableState *	e_tree_get_state_object		(ETree *e_tree);
+ETableSpecification *
+		e_tree_get_spec			(ETree *e_tree);
+
+/* note that it is more efficient to provide the state at creation time */
+void		e_tree_set_search_column	(ETree *e_tree,
+						 gint col);
+void		e_tree_set_state		(ETree *e_tree,
+						 const gchar *state);
+void		e_tree_set_state_object		(ETree *e_tree,
+						 ETableState *state);
+void		e_tree_load_state		(ETree *e_tree,
+						 const gchar *filename);
+void		e_tree_show_cursor_after_reflow	(ETree *e_tree);
+
+void		e_tree_set_cursor		(ETree *e_tree,
+						 ETreePath path);
+
+/* NULL means we don't have the cursor. */
+ETreePath	e_tree_get_cursor		(ETree *e_tree);
+void		e_tree_selected_row_foreach	(ETree *e_tree,
+						 EForeachFunc callback,
+						 gpointer closure);
+#ifdef E_TREE_USE_TREE_SELECTION
+void		e_tree_selected_path_foreach	(ETree *e_tree,
+						 ETreeForeachFunc callback,
+						 gpointer closure);
+void		e_tree_path_foreach		(ETree *e_tree,
+						 ETreeForeachFunc callback,
+						 gpointer closure);
+#endif
+EPrintable *e_tree_get_printable		(ETree *e_tree);
+gint		e_tree_get_next_row		(ETree *e_tree,
+						 gint model_row);
+gint		e_tree_get_prev_row		(ETree *e_tree,
+						 gint model_row);
+gint		e_tree_model_to_view_row	(ETree *e_tree,
+						 gint model_row);
+gint		e_tree_view_to_model_row	(ETree *e_tree,
+						 gint view_row);
+void		e_tree_get_cell_at		(ETree *tree,
+						 gint x,
+						 gint y,
+						 gint *row_return,
+						 gint *col_return);
+void		e_tree_get_cell_geometry	(ETree *tree,
+						 gint row,
+						 gint col,
+						 gint *x_return,
+						 gint *y_return,
+						 gint *width_return,
+						 gint *height_return);
+
+/* Useful accessors */
+ETreeModel *	e_tree_get_model		(ETree *et);
+ESelectionModel *
+		e_tree_get_selection_model	(ETree *et);
+ETreeTableAdapter *
+		e_tree_get_table_adapter	(ETree *et);
+
+/* Drag & drop stuff. */
+/* Target */
+void		e_tree_drag_get_data		(ETree *tree,
+						 gint row,
+						 gint col,
+						 GdkDragContext *context,
+						 GdkAtom target,
+						 guint32 time);
+void		e_tree_drag_highlight		(ETree *tree,
+						 gint row,
+						 gint col); /* col == -1 to highlight entire row. */
+void		e_tree_drag_unhighlight		(ETree *tree);
+void		e_tree_drag_dest_set		(ETree *tree,
+						 GtkDestDefaults flags,
+						 const GtkTargetEntry *targets,
+						 gint n_targets,
+						 GdkDragAction actions);
+void		e_tree_drag_dest_set_proxy	(ETree *tree,
+						 GdkWindow *proxy_window,
+						 GdkDragProtocol protocol,
+						 gboolean use_coordinates);
+
+/* There probably should be functions for setting the targets
+ * as a GtkTargetList
+ */
+void		e_tree_drag_dest_unset		(GtkWidget *widget);
+
+/* Source side */
+void		e_tree_drag_source_set		(ETree *tree,
+						 GdkModifierType start_button_mask,
+						 const GtkTargetEntry *targets,
+						 gint n_targets,
+						 GdkDragAction actions);
+void		e_tree_drag_source_unset	(ETree *tree);
+
+/* There probably should be functions for setting the targets
+ * as a GtkTargetList
+ */
+GdkDragContext *e_tree_drag_begin		(ETree *tree,
+						 gint row,
+						 gint col,
+						 GtkTargetList *targets,
+						 GdkDragAction actions,
+						 gint button,
+						 GdkEvent *event);
+
+gboolean	e_tree_is_dragging		(ETree *tree);
+
+/* Adapter functions */
+gboolean	e_tree_node_is_expanded		(ETree *et,
+						 ETreePath path);
+void		e_tree_node_set_expanded	(ETree *et,
+						 ETreePath path,
+						 gboolean expanded);
+void		e_tree_node_set_expanded_recurse
+						(ETree *et,
+						 ETreePath path,
+						 gboolean expanded);
+void		e_tree_root_node_set_visible	(ETree *et,
+						 gboolean visible);
+ETreePath	e_tree_node_at_row		(ETree *et,
+						 gint row);
+gint		e_tree_row_of_node		(ETree *et,
+						 ETreePath path);
+gboolean	e_tree_root_node_is_visible	(ETree *et);
+void		e_tree_show_node		(ETree *et,
+						 ETreePath path);
+void		e_tree_save_expanded_state	(ETree *et,
+						 gchar *filename);
+void		e_tree_load_expanded_state	(ETree *et,
+						 gchar *filename);
+
+xmlDoc *	e_tree_save_expanded_state_xml	(ETree *et);
+void		e_tree_load_expanded_state_xml	(ETree *et,
+						 xmlDoc *doc);
+
+gint		e_tree_row_count		(ETree *et);
+GtkWidget *	e_tree_get_tooltip		(ETree *et);
+void		e_tree_force_expanded_state	(ETree *et,
+						 gint state);
+
+typedef enum {
+	E_TREE_FIND_NEXT_BACKWARD = 0,
+	E_TREE_FIND_NEXT_FORWARD = 1 << 0,
+	E_TREE_FIND_NEXT_WRAP = 1 << 1
+} ETreeFindNextParams;
+
+gboolean	e_tree_find_next		(ETree *et,
+						 ETreeFindNextParams params,
+						 ETreePathFunc func,
+						 gpointer data);
+
+/* This function is only needed in single_selection_mode. */
+void		e_tree_right_click_up		(ETree *et);
+
+ETableItem *	e_tree_get_item			(ETree *et);
+
+GnomeCanvasItem *
+		e_tree_get_header_item		(ETree *et);
+
+void		e_tree_set_info_message		(ETree *tree,
+						 const gchar *info_message);
+
+void		e_tree_freeze_state_change	(ETree *table);
+void		e_tree_thaw_state_change	(ETree *table);
+
+G_END_DECLS
+
+#endif /* _E_TREE_H_ */
+
diff --git a/e-util/e-ui-manager.c b/e-util/e-ui-manager.c
index 3308b50..e494d35 100644
--- a/e-util/e-ui-manager.c
+++ b/e-util/e-ui-manager.c
@@ -19,7 +19,7 @@
 /**
  * SECTION: e-ui-manager
  * @short_description: construct menus and toolbars from a UI definition
- * @include: e-util/e-ui-manager.h
+ * @include: e-util/e-util.h
  *
  * This is a #GtkUIManager with support for Evolution's "express" mode,
  * which influences the parsing of UI definitions.
diff --git a/e-util/e-ui-manager.h b/e-util/e-ui-manager.h
index 9b1f389..4c29588 100644
--- a/e-util/e-ui-manager.h
+++ b/e-util/e-ui-manager.h
@@ -16,6 +16,10 @@
  *
  */
 
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
 #ifndef E_UI_MANAGER_H
 #define E_UI_MANAGER_H
 
diff --git a/e-util/e-unicode.h b/e-util/e-unicode.h
index c519c2e..2901744 100644
--- a/e-util/e-unicode.h
+++ b/e-util/e-unicode.h
@@ -22,6 +22,10 @@
  *
  */
 
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
 #ifndef _E_UNICODE_H_
 #define _E_UNICODE_H_
 
diff --git a/e-util/e-url-entry.c b/e-util/e-url-entry.c
new file mode 100644
index 0000000..7752732
--- /dev/null
+++ b/e-util/e-url-entry.c
@@ -0,0 +1,159 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		JP Rosevear <jpr novell com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-url-entry.h"
+
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+
+#include "e-misc-utils.h"
+
+#define E_URL_ENTRY_GET_PRIVATE(obj) \
+	(G_TYPE_INSTANCE_GET_PRIVATE \
+	((obj), E_TYPE_URL_ENTRY, EUrlEntryPrivate))
+
+struct _EUrlEntryPrivate {
+	GtkWidget *entry;
+	GtkWidget *button;
+};
+
+static void button_clicked_cb (GtkWidget *widget, gpointer data);
+static void entry_changed_cb (GtkEditable *editable, gpointer data);
+
+static gboolean mnemonic_activate (GtkWidget *widget, gboolean group_cycling);
+
+G_DEFINE_TYPE (
+	EUrlEntry,
+	e_url_entry,
+	GTK_TYPE_HBOX)
+
+static void
+e_url_entry_class_init (EUrlEntryClass *class)
+{
+	GtkWidgetClass *widget_class;
+
+	g_type_class_add_private (class, sizeof (EUrlEntryPrivate));
+
+	widget_class = GTK_WIDGET_CLASS (class);
+	widget_class->mnemonic_activate = mnemonic_activate;
+}
+
+static void
+e_url_entry_init (EUrlEntry *url_entry)
+{
+	GtkWidget *pixmap;
+
+	url_entry->priv = E_URL_ENTRY_GET_PRIVATE (url_entry);
+
+	url_entry->priv->entry = gtk_entry_new ();
+	gtk_box_pack_start (
+		GTK_BOX (url_entry), url_entry->priv->entry, TRUE, TRUE, 0);
+	url_entry->priv->button = gtk_button_new ();
+	gtk_widget_set_sensitive (url_entry->priv->button, FALSE);
+	gtk_box_pack_start (
+		GTK_BOX (url_entry), url_entry->priv->button, FALSE, FALSE, 0);
+	atk_object_set_name (
+		gtk_widget_get_accessible (url_entry->priv->button),
+		_("Click here to go to URL"));
+	pixmap = gtk_image_new_from_icon_name ("go-jump", GTK_ICON_SIZE_BUTTON);
+	gtk_container_add (GTK_CONTAINER (url_entry->priv->button), pixmap);
+	gtk_widget_show (pixmap);
+
+	gtk_widget_show (url_entry->priv->button);
+	gtk_widget_show (url_entry->priv->entry);
+
+	g_signal_connect (
+		url_entry->priv->button, "clicked",
+		G_CALLBACK (button_clicked_cb), url_entry);
+	g_signal_connect (
+		url_entry->priv->entry, "changed",
+		G_CALLBACK (entry_changed_cb), url_entry);
+}
+
+/* GtkWidget::mnemonic_activate() handler for the EUrlEntry */
+static gboolean
+mnemonic_activate (GtkWidget *widget,
+                   gboolean group_cycling)
+{
+	EUrlEntry *url_entry;
+	EUrlEntryPrivate *priv;
+
+	url_entry = E_URL_ENTRY (widget);
+	priv = url_entry->priv;
+
+	return gtk_widget_mnemonic_activate (priv->entry, group_cycling);
+}
+
+GtkWidget *
+e_url_entry_new (void)
+{
+	return g_object_new (E_TYPE_URL_ENTRY, NULL);
+}
+
+GtkWidget *
+e_url_entry_get_entry (EUrlEntry *url_entry)
+{
+	EUrlEntryPrivate *priv;
+
+	g_return_val_if_fail (url_entry != NULL, NULL);
+	g_return_val_if_fail (E_IS_URL_ENTRY (url_entry), NULL);
+
+	priv = url_entry->priv;
+
+	return priv->entry;
+}
+
+static void
+button_clicked_cb (GtkWidget *widget,
+                   gpointer data)
+{
+	EUrlEntry *url_entry;
+	EUrlEntryPrivate *priv;
+	const gchar *uri;
+
+	url_entry = E_URL_ENTRY (data);
+	priv = url_entry->priv;
+
+	uri = gtk_entry_get_text (GTK_ENTRY (priv->entry));
+
+	/* FIXME Pass a parent window. */
+	e_show_uri (NULL, uri);
+}
+
+static void
+entry_changed_cb (GtkEditable *editable,
+                  gpointer data)
+{
+	EUrlEntry *url_entry;
+	EUrlEntryPrivate *priv;
+	const gchar *url;
+
+	url_entry = E_URL_ENTRY (data);
+	priv = url_entry->priv;
+
+	url = gtk_entry_get_text (GTK_ENTRY (priv->entry));
+	gtk_widget_set_sensitive (priv->button, url != NULL && *url != '\0');
+}
diff --git a/e-util/e-url-entry.h b/e-util/e-url-entry.h
new file mode 100644
index 0000000..0925287
--- /dev/null
+++ b/e-util/e-url-entry.h
@@ -0,0 +1,60 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		JP Rosevear <jpr novell com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef _E_URL_ENTRY_H_
+#define _E_URL_ENTRY_H_
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define E_TYPE_URL_ENTRY			(e_url_entry_get_type ())
+#define E_URL_ENTRY(obj)			(G_TYPE_CHECK_INSTANCE_CAST ((obj), E_TYPE_URL_ENTRY, EUrlEntry))
+#define E_URL_ENTRY_CLASS(klass)		(G_TYPE_CHECK_CLASS_CAST ((klass), E_TYPE_URL_ENTRY, EUrlEntryClass))
+#define E_IS_URL_ENTRY(obj)			(G_TYPE_CHECK_INSTANCE_TYPE ((obj), E_TYPE_URL_ENTRY))
+#define E_IS_URL_ENTRY_CLASS(klass)		(G_TYPE_CHECK_CLASS_TYPE ((obj), E_TYPE_URL_ENTRY))
+
+typedef struct _EUrlEntry        EUrlEntry;
+typedef struct _EUrlEntryPrivate EUrlEntryPrivate;
+typedef struct _EUrlEntryClass   EUrlEntryClass;
+
+struct _EUrlEntry {
+	GtkBox parent;
+
+	EUrlEntryPrivate *priv;
+};
+
+struct _EUrlEntryClass {
+	GtkBoxClass parent_class;
+};
+
+GType      e_url_entry_get_type  (void);
+GtkWidget *e_url_entry_new       (void);
+GtkWidget *e_url_entry_get_entry (EUrlEntry *url_entry);
+
+G_END_DECLS
+
+#endif /* _E_URL_ENTRY_H_ */
diff --git a/e-util/e-util-enums.h b/e-util/e-util-enums.h
index 5f5ef75..b2da504 100644
--- a/e-util/e-util-enums.h
+++ b/e-util/e-util-enums.h
@@ -16,6 +16,10 @@
  *
  */
 
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
 #ifndef E_UTIL_ENUMS_H
 #define E_UTIL_ENUMS_H
 
diff --git a/e-util/e-util.h b/e-util/e-util.h
index fa98153..a5ab42b 100644
--- a/e-util/e-util.h
+++ b/e-util/e-util.h
@@ -1,4 +1,6 @@
 /*
+ * e-util.h
+ *
  * This program 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
@@ -12,145 +14,229 @@
  * You should have received a copy of the GNU Lesser General Public
  * License along with the program; if not, see <http://www.gnu.org/licenses/>
  *
- *
- * Authors:
- *		Chris Lahey <clahey ximian com>
- *
- * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
- *
  */
 
 #ifndef E_UTIL_H
 #define E_UTIL_H
 
-#include <sys/types.h>
-#include <gtk/gtk.h>
-#include <limits.h>
+#define __E_UTIL_H_INSIDE__
 
 #include <libedataserver/libedataserver.h>
 
-#include <libevolution-utils/evolution-util.h>
-
-#include <e-util/e-marshal.h>
+#include <e-util/e-action-combo-box.h>
+#include <e-util/e-activity-bar.h>
+#include <e-util/e-activity-proxy.h>
+#include <e-util/e-activity.h>
+#include <e-util/e-alarm-selector.h>
+#include <e-util/e-alert-bar.h>
+#include <e-util/e-alert-dialog.h>
+#include <e-util/e-alert-sink.h>
+#include <e-util/e-alert.h>
+#include <e-util/e-attachment-bar.h>
+#include <e-util/e-attachment-button.h>
+#include <e-util/e-attachment-dialog.h>
+#include <e-util/e-attachment-handler-image.h>
+#include <e-util/e-attachment-handler-sendto.h>
+#include <e-util/e-attachment-handler.h>
+#include <e-util/e-attachment-icon-view.h>
+#include <e-util/e-attachment-paned.h>
+#include <e-util/e-attachment-store.h>
+#include <e-util/e-attachment-tree-view.h>
+#include <e-util/e-attachment-view.h>
+#include <e-util/e-attachment.h>
+#include <e-util/e-auth-combo-box.h>
+#include <e-util/e-autocomplete-selector.h>
+#include <e-util/e-bit-array.h>
+#include <e-util/e-book-source-config.h>
+#include <e-util/e-buffer-tagger.h>
+#include <e-util/e-cal-source-config.h>
+#include <e-util/e-calendar-item.h>
+#include <e-util/e-calendar.h>
+#include <e-util/e-canvas-background.h>
+#include <e-util/e-canvas-utils.h>
+#include <e-util/e-canvas-vbox.h>
+#include <e-util/e-canvas.h>
+#include <e-util/e-categories-config.h>
+#include <e-util/e-categories-dialog.h>
+#include <e-util/e-categories-editor.h>
+#include <e-util/e-categories-selector.h>
+#include <e-util/e-category-completion.h>
+#include <e-util/e-category-editor.h>
+#include <e-util/e-cell-checkbox.h>
+#include <e-util/e-cell-combo.h>
+#include <e-util/e-cell-date-edit.h>
+#include <e-util/e-cell-date.h>
+#include <e-util/e-cell-hbox.h>
+#include <e-util/e-cell-number.h>
+#include <e-util/e-cell-percent.h>
+#include <e-util/e-cell-pixbuf.h>
+#include <e-util/e-cell-popup.h>
+#include <e-util/e-cell-renderer-color.h>
+#include <e-util/e-cell-size.h>
+#include <e-util/e-cell-text.h>
+#include <e-util/e-cell-toggle.h>
+#include <e-util/e-cell-tree.h>
+#include <e-util/e-cell-vbox.h>
+#include <e-util/e-cell.h>
+#include <e-util/e-charset-combo-box.h>
+#include <e-util/e-charset.h>
+#include <e-util/e-client-utils.h>
+#include <e-util/e-config.h>
+#include <e-util/e-contact-map-window.h>
+#include <e-util/e-contact-map.h>
+#include <e-util/e-contact-marker.h>
+#include <e-util/e-contact-store.h>
+#include <e-util/e-dateedit.h>
+#include <e-util/e-datetime-format.h>
+#include <e-util/e-destination-store.h>
+#include <e-util/e-dialog-utils.h>
+#include <e-util/e-dialog-widgets.h>
+#include <e-util/e-event.h>
+#include <e-util/e-file-request.h>
+#include <e-util/e-file-utils.h>
+#include <e-util/e-filter-code.h>
+#include <e-util/e-filter-color.h>
+#include <e-util/e-filter-datespec.h>
+#include <e-util/e-filter-element.h>
+#include <e-util/e-filter-file.h>
+#include <e-util/e-filter-input.h>
+#include <e-util/e-filter-int.h>
+#include <e-util/e-filter-option.h>
+#include <e-util/e-filter-part.h>
+#include <e-util/e-filter-rule.h>
+#include <e-util/e-focus-tracker.h>
+#include <e-util/e-html-utils.h>
+#include <e-util/e-icon-factory.h>
+#include <e-util/e-image-chooser.h>
+#include <e-util/e-import-assistant.h>
+#include <e-util/e-import.h>
+#include <e-util/e-interval-chooser.h>
+#include <e-util/e-mail-identity-combo-box.h>
+#include <e-util/e-mail-signature-combo-box.h>
+#include <e-util/e-mail-signature-editor.h>
+#include <e-util/e-mail-signature-manager.h>
+#include <e-util/e-mail-signature-preview.h>
+#include <e-util/e-mail-signature-script-dialog.h>
+#include <e-util/e-mail-signature-tree-view.h>
+#include <e-util/e-map.h>
+#include <e-util/e-menu-tool-action.h>
+#include <e-util/e-menu-tool-button.h>
+#include <e-util/e-misc-utils.h>
+#include <e-util/e-mktemp.h>
+#include <e-util/e-name-selector-dialog.h>
+#include <e-util/e-name-selector-entry.h>
+#include <e-util/e-name-selector-list.h>
+#include <e-util/e-name-selector-model.h>
+#include <e-util/e-name-selector.h>
+#include <e-util/e-online-button.h>
+#include <e-util/e-paned.h>
+#include <e-util/e-passwords.h>
+#include <e-util/e-picture-gallery.h>
+#include <e-util/e-plugin-ui.h>
+#include <e-util/e-plugin.h>
+#include <e-util/e-poolv.h>
+#include <e-util/e-popup-action.h>
+#include <e-util/e-popup-menu.h>
+#include <e-util/e-port-entry.h>
+#include <e-util/e-preferences-window.h>
+#include <e-util/e-preview-pane.h>
+#include <e-util/e-print.h>
+#include <e-util/e-printable.h>
+#include <e-util/e-reflow-model.h>
+#include <e-util/e-reflow.h>
+#include <e-util/e-rule-context.h>
+#include <e-util/e-rule-editor.h>
+#include <e-util/e-search-bar.h>
+#include <e-util/e-selectable.h>
+#include <e-util/e-selection-model-array.h>
+#include <e-util/e-selection-model-simple.h>
+#include <e-util/e-selection-model.h>
+#include <e-util/e-selection.h>
+#include <e-util/e-send-options.h>
+#include <e-util/e-sorter-array.h>
+#include <e-util/e-sorter.h>
+#include <e-util/e-source-combo-box.h>
+#include <e-util/e-source-config-backend.h>
+#include <e-util/e-source-config-dialog.h>
+#include <e-util/e-source-config.h>
+#include <e-util/e-source-selector-dialog.h>
+#include <e-util/e-source-selector.h>
+#include <e-util/e-source-util.h>
+#include <e-util/e-spell-entry.h>
+#include <e-util/e-stock-request.h>
+#include <e-util/e-table-click-to-add.h>
+#include <e-util/e-table-col-dnd.h>
+#include <e-util/e-table-col.h>
+#include <e-util/e-table-column-specification.h>
+#include <e-util/e-table-config.h>
+#include <e-util/e-table-defines.h>
+#include <e-util/e-table-extras.h>
+#include <e-util/e-table-field-chooser-dialog.h>
+#include <e-util/e-table-field-chooser-item.h>
+#include <e-util/e-table-field-chooser.h>
+#include <e-util/e-table-group-container.h>
+#include <e-util/e-table-group-leaf.h>
+#include <e-util/e-table-group.h>
+#include <e-util/e-table-header-item.h>
+#include <e-util/e-table-header-utils.h>
+#include <e-util/e-table-header.h>
+#include <e-util/e-table-item.h>
+#include <e-util/e-table-memory-callbacks.h>
+#include <e-util/e-table-memory-store.h>
+#include <e-util/e-table-memory.h>
+#include <e-util/e-table-model.h>
+#include <e-util/e-table-one.h>
+#include <e-util/e-table-search.h>
+#include <e-util/e-table-selection-model.h>
+#include <e-util/e-table-sort-info.h>
+#include <e-util/e-table-sorted-variable.h>
+#include <e-util/e-table-sorted.h>
+#include <e-util/e-table-sorter.h>
+#include <e-util/e-table-sorting-utils.h>
+#include <e-util/e-table-specification.h>
+#include <e-util/e-table-state.h>
+#include <e-util/e-table-subset-variable.h>
+#include <e-util/e-table-subset.h>
+#include <e-util/e-table-utils.h>
+#include <e-util/e-table-without.h>
+#include <e-util/e-table.h>
+#include <e-util/e-text-event-processor-emacs-like.h>
+#include <e-util/e-text-event-processor-types.h>
+#include <e-util/e-text-event-processor.h>
+#include <e-util/e-text-model-repos.h>
+#include <e-util/e-text-model.h>
+#include <e-util/e-text.h>
+#include <e-util/e-timezone-dialog.h>
+#include <e-util/e-tree-memory-callbacks.h>
+#include <e-util/e-tree-memory.h>
+#include <e-util/e-tree-model-generator.h>
+#include <e-util/e-tree-model.h>
+#include <e-util/e-tree-selection-model.h>
+#include <e-util/e-tree-sorted.h>
+#include <e-util/e-tree-table-adapter.h>
+#include <e-util/e-tree.h>
+#include <e-util/e-ui-manager.h>
+#include <e-util/e-unicode.h>
+#include <e-util/e-url-entry.h>
 #include <e-util/e-util-enums.h>
-
-G_BEGIN_DECLS
-
-typedef enum {
-	E_FOCUS_NONE,
-	E_FOCUS_CURRENT,
-	E_FOCUS_START,
-	E_FOCUS_END
-} EFocus;
-
-typedef enum {
-	E_RESTORE_WINDOW_SIZE     = 1 << 0,
-	E_RESTORE_WINDOW_POSITION = 1 << 1
-} ERestoreWindowFlags;
-
-const gchar *	e_get_accels_filename		(void);
-void		e_show_uri			(GtkWindow *parent,
-						 const gchar *uri);
-void		e_display_help			(GtkWindow *parent,
-						 const gchar *link_id);
-void		e_restore_window		(GtkWindow *window,
-						 const gchar *settings_path,
-						 ERestoreWindowFlags flags);
-GtkAction *	e_lookup_action			(GtkUIManager *ui_manager,
-						 const gchar *action_name);
-GtkActionGroup *e_lookup_action_group		(GtkUIManager *ui_manager,
-						 const gchar *group_name);
-gint		e_action_compare_by_label	(GtkAction *action1,
-						 GtkAction *action2);
-void		e_action_group_remove_all_actions
-						(GtkActionGroup *action_group);
-GtkRadioAction *e_radio_action_get_current_action
-						(GtkRadioAction *radio_action);
-void		e_action_group_add_actions_localized
-						(GtkActionGroup *action_group,
-						 const gchar *translation_domain,
-						 const GtkActionEntry *entries,
-						 guint n_entries,
-						 gpointer user_data);
-void		e_categories_add_change_hook	(GHookFunc func,
-						 gpointer object);
-
-gchar *		e_str_without_underscores	(const gchar *string);
-gint		e_str_compare			(gconstpointer x,
-						 gconstpointer y);
-gint		e_str_case_compare		(gconstpointer x,
-						 gconstpointer y);
-gint		e_collate_compare		(gconstpointer x,
-						 gconstpointer y);
-gint		e_int_compare                   (gconstpointer x,
-						 gconstpointer y);
-guint32		e_color_to_value		(GdkColor *color);
-
-guint32		e_rgba_to_value			(GdkRGBA *rgba);
-
-/* This only makes a filename safe for usage as a filename.
- * It still may have shell meta-characters in it. */
-gchar *		e_format_number			(gint number);
-
-typedef gint	(*ESortCompareFunc)		(gconstpointer first,
-						 gconstpointer second,
-						 gpointer closure);
-
-void		e_bsearch			(gconstpointer key,
-						 gconstpointer base,
-						 gsize nmemb,
-						 gsize size,
-						 ESortCompareFunc compare,
-						 gpointer closure,
-						 gsize *start,
-						 gsize *end);
-
-gsize		e_strftime_fix_am_pm		(gchar *str,
-						 gsize max,
-						 const gchar *fmt,
-						 const struct tm *tm);
-gsize		e_utf8_strftime_fix_am_pm	(gchar *str,
-						 gsize max,
-						 const gchar *fmt,
-						 const struct tm *tm);
-const gchar *	e_get_month_name		(GDateMonth month,
-						 gboolean abbreviated);
-const gchar *	e_get_weekday_name		(GDateWeekday weekday,
-						 gboolean abbreviated);
-
-gboolean	e_file_lock_create		(void);
-void		e_file_lock_destroy		(void);
-gboolean	e_file_lock_exists		(void);
-
-gchar *		e_util_guess_mime_type		(const gchar *filename,
-                                                 gboolean localfile);
-
-GSList *	e_util_get_category_filter_options
-						(void);
-GList *		e_util_get_searchable_categories (void);
-
-/* Useful GBinding transform functions */
-gboolean	e_binding_transform_color_to_string
-						(GBinding *binding,
-						 const GValue *source_value,
-						 GValue *target_value,
-						 gpointer not_used);
-gboolean	e_binding_transform_string_to_color
-						(GBinding *binding,
-						 const GValue *source_value,
-						 GValue *target_value,
-						 gpointer not_used);
-gboolean	e_binding_transform_source_to_uid
-						(GBinding *binding,
-						 const GValue *source_value,
-						 GValue *target_value,
-						 ESourceRegistry *registry);
-gboolean	e_binding_transform_uid_to_source
-						(GBinding *binding,
-						 const GValue *source_value,
-						 GValue *target_value,
-						 ESourceRegistry *registry);
-
-G_END_DECLS
+#include <e-util/e-web-view-gtkhtml.h>
+#include <e-util/e-web-view-preview.h>
+#include <e-util/e-web-view.h>
+#include <e-util/e-xml-utils.h>
+#include <e-util/ea-cell-table.h>
+#include <e-util/ea-factory.h>
+#include <e-util/gal-define-views-dialog.h>
+#include <e-util/gal-define-views-model.h>
+#include <e-util/gal-view-collection.h>
+#include <e-util/gal-view-etable.h>
+#include <e-util/gal-view-factory-etable.h>
+#include <e-util/gal-view-factory.h>
+#include <e-util/gal-view-instance-save-as-dialog.h>
+#include <e-util/gal-view-instance.h>
+#include <e-util/gal-view-new-dialog.h>
+#include <e-util/gal-view.h>
+
+#undef __E_UTIL_H_INSIDE__
 
 #endif /* E_UTIL_H */
+
diff --git a/e-util/e-web-view-gtkhtml.c b/e-util/e-web-view-gtkhtml.c
new file mode 100644
index 0000000..7303277
--- /dev/null
+++ b/e-util/e-web-view-gtkhtml.c
@@ -0,0 +1,2317 @@
+/*
+ * e-web-view-gtkhtml.c
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-web-view-gtkhtml.h"
+
+#include <string.h>
+#include <glib/gi18n-lib.h>
+
+#include <camel/camel.h>
+#include <libebackend/libebackend.h>
+
+#include "e-alert-dialog.h"
+#include "e-alert-sink.h"
+#include "e-misc-utils.h"
+#include "e-plugin-ui.h"
+#include "e-popup-action.h"
+#include "e-selectable.h"
+
+#define E_WEB_VIEW_GTKHTML_GET_PRIVATE(obj) \
+	(G_TYPE_INSTANCE_GET_PRIVATE \
+	((obj), E_TYPE_WEB_VIEW_GTKHTML, EWebViewGtkHTMLPrivate))
+
+typedef struct _EWebViewGtkHTMLRequest EWebViewGtkHTMLRequest;
+
+struct _EWebViewGtkHTMLPrivate {
+	GList *requests;
+	GtkUIManager *ui_manager;
+	gchar *selected_uri;
+	GdkPixbufAnimation *cursor_image;
+
+	GtkAction *open_proxy;
+	GtkAction *print_proxy;
+	GtkAction *save_as_proxy;
+
+	GtkTargetList *copy_target_list;
+	GtkTargetList *paste_target_list;
+
+	/* Lockdown Options */
+	guint disable_printing     : 1;
+	guint disable_save_to_disk : 1;
+};
+
+struct _EWebViewGtkHTMLRequest {
+	GFile *file;
+	EWebViewGtkHTML *web_view;
+	GCancellable *cancellable;
+	GInputStream *input_stream;
+	GtkHTMLStream *output_stream;
+	gchar buffer[4096];
+};
+
+enum {
+	PROP_0,
+	PROP_ANIMATE,
+	PROP_CARET_MODE,
+	PROP_COPY_TARGET_LIST,
+	PROP_DISABLE_PRINTING,
+	PROP_DISABLE_SAVE_TO_DISK,
+	PROP_EDITABLE,
+	PROP_INLINE_SPELLING,
+	PROP_MAGIC_LINKS,
+	PROP_MAGIC_SMILEYS,
+	PROP_OPEN_PROXY,
+	PROP_PASTE_TARGET_LIST,
+	PROP_PRINT_PROXY,
+	PROP_SAVE_AS_PROXY,
+	PROP_SELECTED_URI,
+	PROP_CURSOR_IMAGE
+};
+
+enum {
+	COPY_CLIPBOARD,
+	CUT_CLIPBOARD,
+	PASTE_CLIPBOARD,
+	POPUP_EVENT,
+	STATUS_MESSAGE,
+	STOP_LOADING,
+	UPDATE_ACTIONS,
+	PROCESS_MAILTO,
+	LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+static const gchar *ui =
+"<ui>"
+"  <popup name='context'>"
+"    <menuitem action='copy-clipboard'/>"
+"    <separator/>"
+"    <placeholder name='custom-actions-1'>"
+"      <menuitem action='open'/>"
+"      <menuitem action='save-as'/>"
+"      <menuitem action='http-open'/>"
+"      <menuitem action='send-message'/>"
+"      <menuitem action='print'/>"
+"    </placeholder>"
+"    <placeholder name='custom-actions-2'>"
+"      <menuitem action='uri-copy'/>"
+"      <menuitem action='mailto-copy'/>"
+"      <menuitem action='image-copy'/>"
+"    </placeholder>"
+"    <placeholder name='custom-actions-3'/>"
+"    <separator/>"
+"    <menuitem action='select-all'/>"
+"  </popup>"
+"</ui>";
+
+/* Forward Declarations */
+static void e_web_view_gtkhtml_alert_sink_init (EAlertSinkInterface *interface);
+static void e_web_view_gtkhtml_selectable_init (ESelectableInterface *interface);
+
+G_DEFINE_TYPE_WITH_CODE (
+	EWebViewGtkHTML,
+	e_web_view_gtkhtml,
+	GTK_TYPE_HTML,
+	G_IMPLEMENT_INTERFACE (
+		E_TYPE_EXTENSIBLE, NULL)
+	G_IMPLEMENT_INTERFACE (
+		E_TYPE_ALERT_SINK,
+		e_web_view_gtkhtml_alert_sink_init)
+	G_IMPLEMENT_INTERFACE (
+		E_TYPE_SELECTABLE,
+		e_web_view_gtkhtml_selectable_init))
+
+static EWebViewGtkHTMLRequest *
+web_view_gtkhtml_request_new (EWebViewGtkHTML *web_view,
+                              const gchar *uri,
+                              GtkHTMLStream *stream)
+{
+	EWebViewGtkHTMLRequest *request;
+	GList *list;
+
+	request = g_slice_new (EWebViewGtkHTMLRequest);
+
+	/* Try to detect file paths posing as URIs. */
+	if (*uri == '/')
+		request->file = g_file_new_for_path (uri);
+	else
+		request->file = g_file_new_for_uri (uri);
+
+	request->web_view = g_object_ref (web_view);
+	request->cancellable = g_cancellable_new ();
+	request->input_stream = NULL;
+	request->output_stream = stream;
+
+	list = request->web_view->priv->requests;
+	list = g_list_prepend (list, request);
+	request->web_view->priv->requests = list;
+
+	return request;
+}
+
+static void
+web_view_gtkhtml_request_free (EWebViewGtkHTMLRequest *request)
+{
+	GList *list;
+
+	list = request->web_view->priv->requests;
+	list = g_list_remove (list, request);
+	request->web_view->priv->requests = list;
+
+	g_object_unref (request->file);
+	g_object_unref (request->web_view);
+	g_object_unref (request->cancellable);
+
+	if (request->input_stream != NULL)
+		g_object_unref (request->input_stream);
+
+	g_slice_free (EWebViewGtkHTMLRequest, request);
+}
+
+static void
+web_view_gtkhtml_request_cancel (EWebViewGtkHTMLRequest *request)
+{
+	g_cancellable_cancel (request->cancellable);
+}
+
+static gboolean
+web_view_gtkhtml_request_check_for_error (EWebViewGtkHTMLRequest *request,
+                                          GError *error)
+{
+	GtkHTML *html;
+	GtkHTMLStream *stream;
+
+	if (error == NULL)
+		return FALSE;
+
+	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED)) {
+		/* use this error, but do not close the stream */
+		g_error_free (error);
+		return TRUE;
+	}
+
+	/* XXX Should we log errors that are not cancellations? */
+
+	html = GTK_HTML (request->web_view);
+	stream = request->output_stream;
+
+	gtk_html_end (html, stream, GTK_HTML_STREAM_ERROR);
+	web_view_gtkhtml_request_free (request);
+	g_error_free (error);
+
+	return TRUE;
+}
+
+static void
+web_view_gtkhtml_request_stream_read_cb (GInputStream *input_stream,
+                                         GAsyncResult *result,
+                                         EWebViewGtkHTMLRequest *request)
+{
+	gssize bytes_read;
+	GError *error = NULL;
+
+	bytes_read = g_input_stream_read_finish (input_stream, result, &error);
+
+	if (web_view_gtkhtml_request_check_for_error (request, error))
+		return;
+
+	if (bytes_read == 0) {
+		gtk_html_end (
+			GTK_HTML (request->web_view),
+			request->output_stream, GTK_HTML_STREAM_OK);
+		web_view_gtkhtml_request_free (request);
+		return;
+	}
+
+	gtk_html_write (
+		GTK_HTML (request->web_view),
+		request->output_stream, request->buffer, bytes_read);
+
+	g_input_stream_read_async (
+		request->input_stream, request->buffer,
+		sizeof (request->buffer), G_PRIORITY_DEFAULT,
+		request->cancellable, (GAsyncReadyCallback)
+		web_view_gtkhtml_request_stream_read_cb, request);
+}
+
+static void
+web_view_gtkhtml_request_read_cb (GFile *file,
+                                  GAsyncResult *result,
+                                  EWebViewGtkHTMLRequest *request)
+{
+	GFileInputStream *input_stream;
+	GError *error = NULL;
+
+	/* Input stream might be NULL, so don't use cast macro. */
+	input_stream = g_file_read_finish (file, result, &error);
+	request->input_stream = (GInputStream *) input_stream;
+
+	if (web_view_gtkhtml_request_check_for_error (request, error))
+		return;
+
+	g_input_stream_read_async (
+		request->input_stream, request->buffer,
+		sizeof (request->buffer), G_PRIORITY_DEFAULT,
+		request->cancellable, (GAsyncReadyCallback)
+		web_view_gtkhtml_request_stream_read_cb, request);
+}
+
+static void
+action_copy_clipboard_cb (GtkAction *action,
+                          EWebViewGtkHTML *web_view)
+{
+	e_web_view_gtkhtml_copy_clipboard (web_view);
+}
+
+static void
+action_http_open_cb (GtkAction *action,
+                     EWebViewGtkHTML *web_view)
+{
+	const gchar *uri;
+	gpointer parent;
+
+	parent = gtk_widget_get_toplevel (GTK_WIDGET (web_view));
+	parent = gtk_widget_is_toplevel (parent) ? parent : NULL;
+
+	uri = e_web_view_gtkhtml_get_selected_uri (web_view);
+	g_return_if_fail (uri != NULL);
+
+	e_show_uri (parent, uri);
+}
+
+static void
+action_mailto_copy_cb (GtkAction *action,
+                       EWebViewGtkHTML *web_view)
+{
+	CamelURL *curl;
+	CamelInternetAddress *inet_addr;
+	GtkClipboard *clipboard;
+	const gchar *uri;
+	gchar *text;
+
+	uri = e_web_view_gtkhtml_get_selected_uri (web_view);
+	g_return_if_fail (uri != NULL);
+
+	/* This should work because we checked it in update_actions(). */
+	curl = camel_url_new (uri, NULL);
+	g_return_if_fail (curl != NULL);
+
+	inet_addr = camel_internet_address_new ();
+	camel_address_decode (CAMEL_ADDRESS (inet_addr), curl->path);
+	text = camel_address_format (CAMEL_ADDRESS (inet_addr));
+	if (text == NULL || *text == '\0')
+		text = g_strdup (uri + strlen ("mailto:";));
+
+	g_object_unref (inet_addr);
+	camel_url_free (curl);
+
+	clipboard = gtk_clipboard_get (GDK_SELECTION_PRIMARY);
+	gtk_clipboard_set_text (clipboard, text, -1);
+	gtk_clipboard_store (clipboard);
+
+	clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
+	gtk_clipboard_set_text (clipboard, text, -1);
+	gtk_clipboard_store (clipboard);
+
+	g_free (text);
+}
+
+static void
+action_select_all_cb (GtkAction *action,
+                      EWebViewGtkHTML *web_view)
+{
+	e_web_view_gtkhtml_select_all (web_view);
+}
+
+static void
+action_send_message_cb (GtkAction *action,
+                        EWebViewGtkHTML *web_view)
+{
+	const gchar *uri;
+	gpointer parent;
+	gboolean handled;
+
+	parent = gtk_widget_get_toplevel (GTK_WIDGET (web_view));
+	parent = gtk_widget_is_toplevel (parent) ? parent : NULL;
+
+	uri = e_web_view_gtkhtml_get_selected_uri (web_view);
+	g_return_if_fail (uri != NULL);
+
+	handled = FALSE;
+	g_signal_emit (web_view, signals[PROCESS_MAILTO], 0, uri, &handled);
+
+	if (!handled)
+		e_show_uri (parent, uri);
+}
+
+static void
+action_uri_copy_cb (GtkAction *action,
+                    EWebViewGtkHTML *web_view)
+{
+	GtkClipboard *clipboard;
+	const gchar *uri;
+
+	clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
+	uri = e_web_view_gtkhtml_get_selected_uri (web_view);
+	g_return_if_fail (uri != NULL);
+
+	gtk_clipboard_set_text (clipboard, uri, -1);
+	gtk_clipboard_store (clipboard);
+}
+
+static void
+action_image_copy_cb (GtkAction *action,
+                      EWebViewGtkHTML *web_view)
+{
+	GtkClipboard *clipboard;
+	GdkPixbufAnimation *animation;
+	GdkPixbuf *pixbuf;
+
+	clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
+	animation = e_web_view_gtkhtml_get_cursor_image (web_view);
+	g_return_if_fail (animation != NULL);
+
+	pixbuf = gdk_pixbuf_animation_get_static_image (animation);
+	if (!pixbuf)
+		return;
+
+	gtk_clipboard_set_image (clipboard, pixbuf);
+	gtk_clipboard_store (clipboard);
+}
+
+static GtkActionEntry uri_entries[] = {
+
+	{ "uri-copy",
+	  GTK_STOCK_COPY,
+	  N_("_Copy Link Location"),
+	  NULL,
+	  N_("Copy the link to the clipboard"),
+	  G_CALLBACK (action_uri_copy_cb) }
+};
+
+static GtkActionEntry http_entries[] = {
+
+	{ "http-open",
+	  "emblem-web",
+	  N_("_Open Link in Browser"),
+	  NULL,
+	  N_("Open the link in a web browser"),
+	  G_CALLBACK (action_http_open_cb) }
+};
+
+static GtkActionEntry mailto_entries[] = {
+
+	{ "mailto-copy",
+	  GTK_STOCK_COPY,
+	  N_("_Copy Email Address"),
+	  NULL,
+	  N_("Copy the email address to the clipboard"),
+	  G_CALLBACK (action_mailto_copy_cb) },
+
+	{ "send-message",
+	  "mail-message-new",
+	  N_("_Send New Message To..."),
+	  NULL,
+	  N_("Send a mail message to this address"),
+	  G_CALLBACK (action_send_message_cb) }
+};
+
+static GtkActionEntry image_entries[] = {
+
+	{ "image-copy",
+	  GTK_STOCK_COPY,
+	  N_("_Copy Image"),
+	  NULL,
+	  N_("Copy the image to the clipboard"),
+	  G_CALLBACK (action_image_copy_cb) }
+};
+
+static GtkActionEntry selection_entries[] = {
+
+	{ "copy-clipboard",
+	  GTK_STOCK_COPY,
+	  NULL,
+	  NULL,
+	  N_("Copy the selection"),
+	  G_CALLBACK (action_copy_clipboard_cb) },
+};
+
+static GtkActionEntry standard_entries[] = {
+
+	{ "select-all",
+	  GTK_STOCK_SELECT_ALL,
+	  NULL,
+	  NULL,
+	  N_("Select all text and images"),
+	  G_CALLBACK (action_select_all_cb) }
+};
+
+static gboolean
+web_view_gtkhtml_button_press_event_cb (EWebViewGtkHTML *web_view,
+                                        GdkEventButton *event,
+                                        GtkHTML *frame)
+{
+	gboolean event_handled = FALSE;
+	gchar *uri = NULL;
+
+	if (event) {
+		GdkPixbufAnimation *anim;
+
+		if (frame == NULL)
+			frame = GTK_HTML (web_view);
+
+		anim = gtk_html_get_image_at (frame, event->x, event->y);
+		e_web_view_gtkhtml_set_cursor_image (web_view, anim);
+		if (anim != NULL)
+			g_object_unref (anim);
+	}
+
+	if (event != NULL && event->button != 3)
+		return FALSE;
+
+	/* Only extract a URI if no selection is active.  Selected text
+	 * implies the user is more likely to want to copy the selection
+	 * to the clipboard than open a link within the selection. */
+	if (!e_web_view_gtkhtml_is_selection_active (web_view))
+		uri = e_web_view_gtkhtml_extract_uri (web_view, event, frame);
+
+	if (uri != NULL && g_str_has_prefix (uri, "##")) {
+		g_free (uri);
+		return FALSE;
+	}
+
+	g_signal_emit (
+		web_view, signals[POPUP_EVENT], 0,
+		event, uri, &event_handled);
+
+	g_free (uri);
+
+	return event_handled;
+}
+
+static void
+web_view_gtkhtml_menu_item_select_cb (EWebViewGtkHTML *web_view,
+                                      GtkWidget *widget)
+{
+	GtkAction *action;
+	GtkActivatable *activatable;
+	const gchar *tooltip;
+
+	activatable = GTK_ACTIVATABLE (widget);
+	action = gtk_activatable_get_related_action (activatable);
+	tooltip = gtk_action_get_tooltip (action);
+
+	if (tooltip == NULL)
+		return;
+
+	e_web_view_gtkhtml_status_message (web_view, tooltip);
+}
+
+static void
+web_view_gtkhtml_menu_item_deselect_cb (EWebViewGtkHTML *web_view)
+{
+	e_web_view_gtkhtml_status_message (web_view, NULL);
+}
+
+static void
+web_view_gtkhtml_connect_proxy_cb (EWebViewGtkHTML *web_view,
+                                   GtkAction *action,
+                                   GtkWidget *proxy)
+{
+	if (!GTK_IS_MENU_ITEM (proxy))
+		return;
+
+	g_signal_connect_swapped (
+		proxy, "select",
+		G_CALLBACK (web_view_gtkhtml_menu_item_select_cb), web_view);
+
+	g_signal_connect_swapped (
+		proxy, "deselect",
+		G_CALLBACK (web_view_gtkhtml_menu_item_deselect_cb), web_view);
+}
+
+static void
+web_view_gtkhtml_set_property (GObject *object,
+                               guint property_id,
+                               const GValue *value,
+                               GParamSpec *pspec)
+{
+	switch (property_id) {
+		case PROP_ANIMATE:
+			e_web_view_gtkhtml_set_animate (
+				E_WEB_VIEW_GTKHTML (object),
+				g_value_get_boolean (value));
+			return;
+
+		case PROP_CARET_MODE:
+			e_web_view_gtkhtml_set_caret_mode (
+				E_WEB_VIEW_GTKHTML (object),
+				g_value_get_boolean (value));
+			return;
+
+		case PROP_DISABLE_PRINTING:
+			e_web_view_gtkhtml_set_disable_printing (
+				E_WEB_VIEW_GTKHTML (object),
+				g_value_get_boolean (value));
+			return;
+
+		case PROP_DISABLE_SAVE_TO_DISK:
+			e_web_view_gtkhtml_set_disable_save_to_disk (
+				E_WEB_VIEW_GTKHTML (object),
+				g_value_get_boolean (value));
+			return;
+
+		case PROP_EDITABLE:
+			e_web_view_gtkhtml_set_editable (
+				E_WEB_VIEW_GTKHTML (object),
+				g_value_get_boolean (value));
+			return;
+
+		case PROP_INLINE_SPELLING:
+			e_web_view_gtkhtml_set_inline_spelling (
+				E_WEB_VIEW_GTKHTML (object),
+				g_value_get_boolean (value));
+			return;
+
+		case PROP_MAGIC_LINKS:
+			e_web_view_gtkhtml_set_magic_links (
+				E_WEB_VIEW_GTKHTML (object),
+				g_value_get_boolean (value));
+			return;
+
+		case PROP_MAGIC_SMILEYS:
+			e_web_view_gtkhtml_set_magic_smileys (
+				E_WEB_VIEW_GTKHTML (object),
+				g_value_get_boolean (value));
+			return;
+
+		case PROP_OPEN_PROXY:
+			e_web_view_gtkhtml_set_open_proxy (
+				E_WEB_VIEW_GTKHTML (object),
+				g_value_get_object (value));
+			return;
+
+		case PROP_PRINT_PROXY:
+			e_web_view_gtkhtml_set_print_proxy (
+				E_WEB_VIEW_GTKHTML (object),
+				g_value_get_object (value));
+			return;
+
+		case PROP_SAVE_AS_PROXY:
+			e_web_view_gtkhtml_set_save_as_proxy (
+				E_WEB_VIEW_GTKHTML (object),
+				g_value_get_object (value));
+			return;
+
+		case PROP_SELECTED_URI:
+			e_web_view_gtkhtml_set_selected_uri (
+				E_WEB_VIEW_GTKHTML (object),
+				g_value_get_string (value));
+			return;
+		case PROP_CURSOR_IMAGE:
+			e_web_view_gtkhtml_set_cursor_image (
+				E_WEB_VIEW_GTKHTML (object),
+				g_value_get_object (value));
+			return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+web_view_gtkhtml_get_property (GObject *object,
+                               guint property_id,
+                               GValue *value,
+                               GParamSpec *pspec)
+{
+	switch (property_id) {
+		case PROP_ANIMATE:
+			g_value_set_boolean (
+				value, e_web_view_gtkhtml_get_animate (
+				E_WEB_VIEW_GTKHTML (object)));
+			return;
+
+		case PROP_CARET_MODE:
+			g_value_set_boolean (
+				value, e_web_view_gtkhtml_get_caret_mode (
+				E_WEB_VIEW_GTKHTML (object)));
+			return;
+
+		case PROP_COPY_TARGET_LIST:
+			g_value_set_boxed (
+				value, e_web_view_gtkhtml_get_copy_target_list (
+				E_WEB_VIEW_GTKHTML (object)));
+			return;
+
+		case PROP_DISABLE_PRINTING:
+			g_value_set_boolean (
+				value, e_web_view_gtkhtml_get_disable_printing (
+				E_WEB_VIEW_GTKHTML (object)));
+			return;
+
+		case PROP_DISABLE_SAVE_TO_DISK:
+			g_value_set_boolean (
+				value, e_web_view_gtkhtml_get_disable_save_to_disk (
+				E_WEB_VIEW_GTKHTML (object)));
+			return;
+
+		case PROP_EDITABLE:
+			g_value_set_boolean (
+				value, e_web_view_gtkhtml_get_editable (
+				E_WEB_VIEW_GTKHTML (object)));
+			return;
+
+		case PROP_INLINE_SPELLING:
+			g_value_set_boolean (
+				value, e_web_view_gtkhtml_get_inline_spelling (
+				E_WEB_VIEW_GTKHTML (object)));
+			return;
+
+		case PROP_MAGIC_LINKS:
+			g_value_set_boolean (
+				value, e_web_view_gtkhtml_get_magic_links (
+				E_WEB_VIEW_GTKHTML (object)));
+			return;
+
+		case PROP_MAGIC_SMILEYS:
+			g_value_set_boolean (
+				value, e_web_view_gtkhtml_get_magic_smileys (
+				E_WEB_VIEW_GTKHTML (object)));
+			return;
+
+		case PROP_OPEN_PROXY:
+			g_value_set_object (
+				value, e_web_view_gtkhtml_get_open_proxy (
+				E_WEB_VIEW_GTKHTML (object)));
+			return;
+
+		case PROP_PASTE_TARGET_LIST:
+			g_value_set_boxed (
+				value, e_web_view_gtkhtml_get_paste_target_list (
+				E_WEB_VIEW_GTKHTML (object)));
+			return;
+
+		case PROP_PRINT_PROXY:
+			g_value_set_object (
+				value, e_web_view_gtkhtml_get_print_proxy (
+				E_WEB_VIEW_GTKHTML (object)));
+			return;
+
+		case PROP_SAVE_AS_PROXY:
+			g_value_set_object (
+				value, e_web_view_gtkhtml_get_save_as_proxy (
+				E_WEB_VIEW_GTKHTML (object)));
+			return;
+
+		case PROP_SELECTED_URI:
+			g_value_set_string (
+				value, e_web_view_gtkhtml_get_selected_uri (
+				E_WEB_VIEW_GTKHTML (object)));
+			return;
+
+		case PROP_CURSOR_IMAGE:
+			g_value_set_object (
+				value, e_web_view_gtkhtml_get_cursor_image (
+				E_WEB_VIEW_GTKHTML (object)));
+			return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+web_view_gtkhtml_dispose (GObject *object)
+{
+	EWebViewGtkHTMLPrivate *priv;
+
+	priv = E_WEB_VIEW_GTKHTML_GET_PRIVATE (object);
+
+	if (priv->ui_manager != NULL) {
+		g_object_unref (priv->ui_manager);
+		priv->ui_manager = NULL;
+	}
+
+	if (priv->open_proxy != NULL) {
+		g_object_unref (priv->open_proxy);
+		priv->open_proxy = NULL;
+	}
+
+	if (priv->print_proxy != NULL) {
+		g_object_unref (priv->print_proxy);
+		priv->print_proxy = NULL;
+	}
+
+	if (priv->save_as_proxy != NULL) {
+		g_object_unref (priv->save_as_proxy);
+		priv->save_as_proxy = NULL;
+	}
+
+	if (priv->copy_target_list != NULL) {
+		gtk_target_list_unref (priv->copy_target_list);
+		priv->copy_target_list = NULL;
+	}
+
+	if (priv->paste_target_list != NULL) {
+		gtk_target_list_unref (priv->paste_target_list);
+		priv->paste_target_list = NULL;
+	}
+
+	if (priv->cursor_image != NULL) {
+		g_object_unref (priv->cursor_image);
+		priv->cursor_image = NULL;
+	}
+
+	/* Chain up to parent's dispose() method. */
+	G_OBJECT_CLASS (e_web_view_gtkhtml_parent_class)->dispose (object);
+}
+
+static void
+web_view_gtkhtml_finalize (GObject *object)
+{
+	EWebViewGtkHTMLPrivate *priv;
+
+	priv = E_WEB_VIEW_GTKHTML_GET_PRIVATE (object);
+
+	/* All URI requests should be complete or cancelled by now. */
+	if (priv->requests != NULL)
+		g_warning ("Finalizing EWebViewGtkHTML with active URI requests");
+
+	g_free (priv->selected_uri);
+
+	/* Chain up to parent's finalize() method. */
+	G_OBJECT_CLASS (e_web_view_gtkhtml_parent_class)->finalize (object);
+}
+
+static void
+web_view_gtkhtml_constructed (GObject *object)
+{
+#ifndef G_OS_WIN32
+	GSettings *settings;
+
+	settings = g_settings_new ("org.gnome.desktop.lockdown");
+
+	g_settings_bind (
+		settings, "disable-printing",
+		object, "disable-printing",
+		G_SETTINGS_BIND_GET);
+
+	g_settings_bind (
+		settings, "disable-save-to-disk",
+		object, "disable-save-to-disk",
+		G_SETTINGS_BIND_GET);
+
+	g_object_unref (settings);
+#endif
+
+	/* Chain up to parent's constructed() method. */
+	G_OBJECT_CLASS (e_web_view_gtkhtml_parent_class)->constructed (object);
+}
+
+static gboolean
+web_view_gtkhtml_button_press_event (GtkWidget *widget,
+                                     GdkEventButton *event)
+{
+	GtkWidgetClass *widget_class;
+	EWebViewGtkHTML *web_view;
+
+	web_view = E_WEB_VIEW_GTKHTML (widget);
+
+	if (web_view_gtkhtml_button_press_event_cb (web_view, event, NULL))
+		return TRUE;
+
+	/* Chain up to parent's button_press_event() method. */
+	widget_class = GTK_WIDGET_CLASS (e_web_view_gtkhtml_parent_class);
+	return widget_class->button_press_event (widget, event);
+}
+
+static gboolean
+web_view_gtkhtml_scroll_event (GtkWidget *widget,
+                               GdkEventScroll *event)
+{
+	if (event->state & GDK_CONTROL_MASK) {
+		GdkScrollDirection direction = event->direction;
+
+		#if GTK_CHECK_VERSION(3,3,18)
+		if (direction == GDK_SCROLL_SMOOTH) {
+			static gdouble total_delta_y = 0.0;
+
+			total_delta_y += event->delta_y;
+
+			if (total_delta_y >= 1.0) {
+				total_delta_y = 0.0;
+				direction = GDK_SCROLL_DOWN;
+			} else if (total_delta_y <= -1.0) {
+				total_delta_y = 0.0;
+				direction = GDK_SCROLL_UP;
+			} else {
+				return FALSE;
+			}
+		}
+		#endif
+
+		switch (direction) {
+			case GDK_SCROLL_UP:
+				gtk_html_zoom_in (GTK_HTML (widget));
+				return TRUE;
+			case GDK_SCROLL_DOWN:
+				gtk_html_zoom_out (GTK_HTML (widget));
+				return TRUE;
+			default:
+				break;
+		}
+	}
+
+	return FALSE;
+}
+
+static void
+web_view_gtkhtml_url_requested (GtkHTML *html,
+                                const gchar *uri,
+                                GtkHTMLStream *stream)
+{
+	EWebViewGtkHTMLRequest *request;
+
+	request = web_view_gtkhtml_request_new (E_WEB_VIEW_GTKHTML (html), uri, stream);
+
+	g_file_read_async (
+		request->file, G_PRIORITY_DEFAULT,
+		request->cancellable, (GAsyncReadyCallback)
+		web_view_gtkhtml_request_read_cb, request);
+}
+
+static void
+web_view_gtkhtml_gtkhtml_link_clicked (GtkHTML *html,
+                                       const gchar *uri)
+{
+	EWebViewGtkHTMLClass *class;
+	EWebViewGtkHTML *web_view;
+
+	web_view = E_WEB_VIEW_GTKHTML (html);
+
+	class = E_WEB_VIEW_GTKHTML_GET_CLASS (web_view);
+	g_return_if_fail (class->link_clicked != NULL);
+
+	class->link_clicked (web_view, uri);
+}
+
+static void
+web_view_gtkhtml_on_url (GtkHTML *html,
+                         const gchar *uri)
+{
+	EWebViewGtkHTMLClass *class;
+	EWebViewGtkHTML *web_view;
+
+	web_view = E_WEB_VIEW_GTKHTML (html);
+
+	class = E_WEB_VIEW_GTKHTML_GET_CLASS (web_view);
+	g_return_if_fail (class->hovering_over_link != NULL);
+
+	/* XXX WebKit would supply a title here. */
+	class->hovering_over_link (web_view, NULL, uri);
+}
+
+static void
+web_view_gtkhtml_iframe_created (GtkHTML *html,
+                                 GtkHTML *iframe)
+{
+	g_signal_connect_swapped (
+		iframe, "button-press-event",
+		G_CALLBACK (web_view_gtkhtml_button_press_event_cb), html);
+}
+
+static gchar *
+web_view_gtkhtml_extract_uri (EWebViewGtkHTML *web_view,
+                              GdkEventButton *event,
+                              GtkHTML *html)
+{
+	gchar *uri;
+
+	if (event != NULL)
+		uri = gtk_html_get_url_at (html, event->x, event->y);
+	else
+		uri = gtk_html_get_cursor_url (html);
+
+	return uri;
+}
+
+static void
+web_view_gtkhtml_hovering_over_link (EWebViewGtkHTML *web_view,
+                                     const gchar *title,
+                                     const gchar *uri)
+{
+	CamelInternetAddress *address;
+	CamelURL *curl;
+	const gchar *format = NULL;
+	gchar *message = NULL;
+	gchar *who;
+
+	if (uri == NULL || *uri == '\0')
+		goto exit;
+
+	if (g_str_has_prefix (uri, "mailto:";))
+		format = _("Click to mail %s");
+	else if (g_str_has_prefix (uri, "callto:"))
+		format = _("Click to call %s");
+	else if (g_str_has_prefix (uri, "h323:"))
+		format = _("Click to call %s");
+	else if (g_str_has_prefix (uri, "sip:"))
+		format = _("Click to call %s");
+	else if (g_str_has_prefix (uri, "##"))
+		message = g_strdup (_("Click to hide/unhide addresses"));
+	else
+		message = g_strdup_printf (_("Click to open %s"), uri);
+
+	if (format == NULL)
+		goto exit;
+
+	/* XXX Use something other than Camel here.  Surely
+	 *     there's other APIs around that can do this. */
+	curl = camel_url_new (uri, NULL);
+	address = camel_internet_address_new ();
+	camel_address_decode (CAMEL_ADDRESS (address), curl->path);
+	who = camel_address_format (CAMEL_ADDRESS (address));
+	g_object_unref (address);
+	camel_url_free (curl);
+
+	if (who == NULL)
+		who = g_strdup (strchr (uri, ':') + 1);
+
+	message = g_strdup_printf (format, who);
+
+	g_free (who);
+
+exit:
+	e_web_view_gtkhtml_status_message (web_view, message);
+
+	g_free (message);
+}
+
+static void
+web_view_gtkhtml_link_clicked (EWebViewGtkHTML *web_view,
+                               const gchar *uri)
+{
+	gpointer parent;
+
+	parent = gtk_widget_get_toplevel (GTK_WIDGET (web_view));
+	parent = gtk_widget_is_toplevel (parent) ? parent : NULL;
+
+	e_show_uri (parent, uri);
+}
+
+static void
+web_view_gtkhtml_load_string (EWebViewGtkHTML *web_view,
+                              const gchar *string)
+{
+	if (string != NULL && *string != '\0')
+		gtk_html_load_from_string (GTK_HTML (web_view), string, -1);
+	else
+		e_web_view_gtkhtml_clear (web_view);
+}
+
+static void
+web_view_gtkhtml_copy_clipboard (EWebViewGtkHTML *web_view)
+{
+	gtk_html_command (GTK_HTML (web_view), "copy");
+}
+
+static void
+web_view_gtkhtml_cut_clipboard (EWebViewGtkHTML *web_view)
+{
+	if (e_web_view_gtkhtml_get_editable (web_view))
+		gtk_html_command (GTK_HTML (web_view), "cut");
+}
+
+static void
+web_view_gtkhtml_paste_clipboard (EWebViewGtkHTML *web_view)
+{
+	if (e_web_view_gtkhtml_get_editable (web_view))
+		gtk_html_command (GTK_HTML (web_view), "paste");
+}
+
+static gboolean
+web_view_gtkhtml_popup_event (EWebViewGtkHTML *web_view,
+                              GdkEventButton *event,
+                              const gchar *uri)
+{
+	e_web_view_gtkhtml_set_selected_uri (web_view, uri);
+	e_web_view_gtkhtml_show_popup_menu (web_view, event, NULL, NULL);
+
+	return TRUE;
+}
+
+static void
+web_view_gtkhtml_stop_loading (EWebViewGtkHTML *web_view)
+{
+	g_list_foreach (
+		web_view->priv->requests, (GFunc)
+		web_view_gtkhtml_request_cancel, NULL);
+
+	gtk_html_stop (GTK_HTML (web_view));
+}
+
+static void
+web_view_gtkhtml_update_actions (EWebViewGtkHTML *web_view)
+{
+	GtkActionGroup *action_group;
+	gboolean have_selection;
+	gboolean scheme_is_http = FALSE;
+	gboolean scheme_is_mailto = FALSE;
+	gboolean uri_is_valid = FALSE;
+	gboolean has_cursor_image;
+	gboolean visible;
+	const gchar *group_name;
+	const gchar *uri;
+
+	uri = e_web_view_gtkhtml_get_selected_uri (web_view);
+	have_selection = e_web_view_gtkhtml_is_selection_active (web_view);
+	has_cursor_image = e_web_view_gtkhtml_get_cursor_image (web_view) != NULL;
+
+	/* Parse the URI early so we know if the actions will work. */
+	if (uri != NULL) {
+		CamelURL *curl;
+
+		curl = camel_url_new (uri, NULL);
+		uri_is_valid = (curl != NULL);
+		camel_url_free (curl);
+
+		scheme_is_http =
+			(g_ascii_strncasecmp (uri, "http:", 5) == 0) ||
+			(g_ascii_strncasecmp (uri, "https:", 6) == 0);
+
+		scheme_is_mailto =
+			(g_ascii_strncasecmp (uri, "mailto:";, 7) == 0);
+	}
+
+	/* Allow copying the URI even if it's malformed. */
+	group_name = "uri";
+	visible = (uri != NULL) && !scheme_is_mailto;
+	action_group = e_web_view_gtkhtml_get_action_group (web_view, group_name);
+	gtk_action_group_set_visible (action_group, visible);
+
+	group_name = "http";
+	visible = uri_is_valid && scheme_is_http;
+	action_group = e_web_view_gtkhtml_get_action_group (web_view, group_name);
+	gtk_action_group_set_visible (action_group, visible);
+
+	group_name = "mailto";
+	visible = uri_is_valid && scheme_is_mailto;
+	action_group = e_web_view_gtkhtml_get_action_group (web_view, group_name);
+	gtk_action_group_set_visible (action_group, visible);
+
+	group_name = "image";
+	visible = has_cursor_image;
+	action_group = e_web_view_gtkhtml_get_action_group (web_view, group_name);
+	gtk_action_group_set_visible (action_group, visible);
+
+	group_name = "selection";
+	visible = have_selection;
+	action_group = e_web_view_gtkhtml_get_action_group (web_view, group_name);
+	gtk_action_group_set_visible (action_group, visible);
+
+	group_name = "standard";
+	visible = (uri == NULL);
+	action_group = e_web_view_gtkhtml_get_action_group (web_view, group_name);
+	gtk_action_group_set_visible (action_group, visible);
+
+	group_name = "lockdown-printing";
+	visible = (uri == NULL) && !web_view->priv->disable_printing;
+	action_group = e_web_view_gtkhtml_get_action_group (web_view, group_name);
+	gtk_action_group_set_visible (action_group, visible);
+
+	group_name = "lockdown-save-to-disk";
+	visible = (uri == NULL) && !web_view->priv->disable_save_to_disk;
+	action_group = e_web_view_gtkhtml_get_action_group (web_view, group_name);
+	gtk_action_group_set_visible (action_group, visible);
+}
+
+static void
+web_view_gtkhtml_submit_alert (EAlertSink *alert_sink,
+                               EAlert *alert)
+{
+	GtkIconInfo *icon_info;
+	EWebViewGtkHTML *web_view;
+	GtkWidget *dialog;
+	GString *buffer;
+	const gchar *icon_name = NULL;
+	const gchar *filename;
+	gpointer parent;
+	gchar *icon_uri;
+	gint size = 0;
+	GError *error = NULL;
+
+	web_view = E_WEB_VIEW_GTKHTML (alert_sink);
+
+	parent = gtk_widget_get_toplevel (GTK_WIDGET (web_view));
+	parent = gtk_widget_is_toplevel (parent) ? parent : NULL;
+
+	/* We use equivalent named icons instead of stock IDs,
+	 * since it's easier to get the filename of the icon. */
+	switch (e_alert_get_message_type (alert)) {
+		case GTK_MESSAGE_INFO:
+			icon_name = "dialog-information";
+			break;
+
+		case GTK_MESSAGE_WARNING:
+			icon_name = "dialog-warning";
+			break;
+
+		case GTK_MESSAGE_ERROR:
+			icon_name = "dialog-error";
+			break;
+
+		default:
+			dialog = e_alert_dialog_new (parent, alert);
+			gtk_dialog_run (GTK_DIALOG (dialog));
+			gtk_widget_destroy (dialog);
+			return;
+	}
+
+	gtk_icon_size_lookup (GTK_ICON_SIZE_DIALOG, &size, NULL);
+
+	icon_info = gtk_icon_theme_lookup_icon (
+		gtk_icon_theme_get_default (),
+		icon_name, size, GTK_ICON_LOOKUP_NO_SVG);
+	g_return_if_fail (icon_info != NULL);
+
+	filename = gtk_icon_info_get_filename (icon_info);
+	icon_uri = g_filename_to_uri (filename, NULL, &error);
+
+	if (error != NULL) {
+		g_warning ("%s", error->message);
+		g_clear_error (&error);
+	}
+
+	buffer = g_string_sized_new (512);
+
+	g_string_append (
+		buffer,
+		"<html>"
+		"<head>"
+		"<meta http-equiv=\"content-type\""
+		" content=\"text/html; charset=utf-8\">"
+		"</head>"
+		"<body>");
+
+	g_string_append (
+		buffer,
+		"<table bgcolor='#000000' width='100%'"
+		" cellpadding='1' cellspacing='0'>"
+		"<tr>"
+		"<td>"
+		"<table bgcolor='#dddddd' width='100%' cellpadding='6'>"
+		"<tr>");
+
+	g_string_append_printf (
+		buffer,
+		"<tr>"
+		"<td valign='top'>"
+		"<img src='%s'/>"
+		"</td>"
+		"<td align='left' width='100%%'>"
+		"<h3>%s</h3>"
+		"%s"
+		"</td>"
+		"</tr>",
+		icon_uri,
+		e_alert_get_primary_text (alert),
+		e_alert_get_secondary_text (alert));
+
+	g_string_append (
+		buffer,
+		"</table>"
+		"</td>"
+		"</tr>"
+		"</table>"
+		"</body>"
+		"</html>");
+
+	e_web_view_gtkhtml_load_string (web_view, buffer->str);
+
+	g_string_free (buffer, TRUE);
+
+	gtk_icon_info_free (icon_info);
+	g_free (icon_uri);
+}
+
+static void
+web_view_gtkhtml_selectable_update_actions (ESelectable *selectable,
+                                            EFocusTracker *focus_tracker,
+                                            GdkAtom *clipboard_targets,
+                                            gint n_clipboard_targets)
+{
+	EWebViewGtkHTML *web_view;
+	GtkAction *action;
+	/*GtkTargetList *target_list;*/
+	gboolean can_paste = FALSE;
+	gboolean editable;
+	gboolean have_selection;
+	gboolean sensitive;
+	const gchar *tooltip;
+	/*gint ii;*/
+
+	web_view = E_WEB_VIEW_GTKHTML (selectable);
+	editable = e_web_view_gtkhtml_get_editable (web_view);
+	have_selection = e_web_view_gtkhtml_is_selection_active (web_view);
+
+	/* XXX GtkHtml implements its own clipboard instead of using
+	 *     GDK_SELECTION_CLIPBOARD, so we don't get notifications
+	 *     when the clipboard contents change.  The logic below
+	 *     is what we would do if GtkHtml worked properly.
+	 *     Instead, we need to keep the Paste action sensitive so
+	 *     its accelerator overrides GtkHtml's key binding. */
+#if 0
+	target_list = e_selectable_get_paste_target_list (selectable);
+	for (ii = 0; ii < n_clipboard_targets && !can_paste; ii++)
+		can_paste = gtk_target_list_find (
+			target_list, clipboard_targets[ii], NULL);
+#endif
+	can_paste = TRUE;
+
+	action = e_focus_tracker_get_cut_clipboard_action (focus_tracker);
+	sensitive = editable && have_selection;
+	tooltip = _("Cut the selection");
+	gtk_action_set_sensitive (action, sensitive);
+	gtk_action_set_tooltip (action, tooltip);
+
+	action = e_focus_tracker_get_copy_clipboard_action (focus_tracker);
+	sensitive = have_selection;
+	tooltip = _("Copy the selection");
+	gtk_action_set_sensitive (action, sensitive);
+	gtk_action_set_tooltip (action, tooltip);
+
+	action = e_focus_tracker_get_paste_clipboard_action (focus_tracker);
+	sensitive = editable && can_paste;
+	tooltip = _("Paste the clipboard");
+	gtk_action_set_sensitive (action, sensitive);
+	gtk_action_set_tooltip (action, tooltip);
+
+	action = e_focus_tracker_get_select_all_action (focus_tracker);
+	sensitive = TRUE;
+	tooltip = _("Select all text and images");
+	gtk_action_set_sensitive (action, sensitive);
+	gtk_action_set_tooltip (action, tooltip);
+}
+
+static void
+web_view_gtkhtml_selectable_cut_clipboard (ESelectable *selectable)
+{
+	e_web_view_gtkhtml_cut_clipboard (E_WEB_VIEW_GTKHTML (selectable));
+}
+
+static void
+web_view_gtkhtml_selectable_copy_clipboard (ESelectable *selectable)
+{
+	e_web_view_gtkhtml_copy_clipboard (E_WEB_VIEW_GTKHTML (selectable));
+}
+
+static void
+web_view_gtkhtml_selectable_paste_clipboard (ESelectable *selectable)
+{
+	e_web_view_gtkhtml_paste_clipboard (E_WEB_VIEW_GTKHTML (selectable));
+}
+
+static void
+web_view_gtkhtml_selectable_select_all (ESelectable *selectable)
+{
+	e_web_view_gtkhtml_select_all (E_WEB_VIEW_GTKHTML (selectable));
+}
+
+static void
+e_web_view_gtkhtml_class_init (EWebViewGtkHTMLClass *class)
+{
+	GObjectClass *object_class;
+	GtkWidgetClass *widget_class;
+	GtkHTMLClass *html_class;
+
+	g_type_class_add_private (class, sizeof (EWebViewGtkHTMLPrivate));
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->set_property = web_view_gtkhtml_set_property;
+	object_class->get_property = web_view_gtkhtml_get_property;
+	object_class->dispose = web_view_gtkhtml_dispose;
+	object_class->finalize = web_view_gtkhtml_finalize;
+	object_class->constructed = web_view_gtkhtml_constructed;
+
+	widget_class = GTK_WIDGET_CLASS (class);
+	widget_class->button_press_event = web_view_gtkhtml_button_press_event;
+	widget_class->scroll_event = web_view_gtkhtml_scroll_event;
+
+	html_class = GTK_HTML_CLASS (class);
+	html_class->url_requested = web_view_gtkhtml_url_requested;
+	html_class->link_clicked = web_view_gtkhtml_gtkhtml_link_clicked;
+	html_class->on_url = web_view_gtkhtml_on_url;
+	html_class->iframe_created = web_view_gtkhtml_iframe_created;
+
+	class->extract_uri = web_view_gtkhtml_extract_uri;
+	class->hovering_over_link = web_view_gtkhtml_hovering_over_link;
+	class->link_clicked = web_view_gtkhtml_link_clicked;
+	class->load_string = web_view_gtkhtml_load_string;
+	class->copy_clipboard = web_view_gtkhtml_copy_clipboard;
+	class->cut_clipboard = web_view_gtkhtml_cut_clipboard;
+	class->paste_clipboard = web_view_gtkhtml_paste_clipboard;
+	class->popup_event = web_view_gtkhtml_popup_event;
+	class->stop_loading = web_view_gtkhtml_stop_loading;
+	class->update_actions = web_view_gtkhtml_update_actions;
+
+	g_object_class_install_property (
+		object_class,
+		PROP_ANIMATE,
+		g_param_spec_boolean (
+			"animate",
+			"Animate Images",
+			NULL,
+			FALSE,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_CARET_MODE,
+		g_param_spec_boolean (
+			"caret-mode",
+			"Caret Mode",
+			NULL,
+			FALSE,
+			G_PARAM_READWRITE));
+
+	/* Inherited from ESelectableInterface */
+	g_object_class_override_property (
+		object_class,
+		PROP_COPY_TARGET_LIST,
+		"copy-target-list");
+
+	g_object_class_install_property (
+		object_class,
+		PROP_DISABLE_PRINTING,
+		g_param_spec_boolean (
+			"disable-printing",
+			"Disable Printing",
+			NULL,
+			FALSE,
+			G_PARAM_READWRITE |
+			G_PARAM_CONSTRUCT));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_DISABLE_SAVE_TO_DISK,
+		g_param_spec_boolean (
+			"disable-save-to-disk",
+			"Disable Save-to-Disk",
+			NULL,
+			FALSE,
+			G_PARAM_READWRITE |
+			G_PARAM_CONSTRUCT));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_EDITABLE,
+		g_param_spec_boolean (
+			"editable",
+			"Editable",
+			NULL,
+			FALSE,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_INLINE_SPELLING,
+		g_param_spec_boolean (
+			"inline-spelling",
+			"Inline Spelling",
+			NULL,
+			FALSE,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_MAGIC_LINKS,
+		g_param_spec_boolean (
+			"magic-links",
+			"Magic Links",
+			NULL,
+			FALSE,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_MAGIC_SMILEYS,
+		g_param_spec_boolean (
+			"magic-smileys",
+			"Magic Smileys",
+			NULL,
+			FALSE,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_OPEN_PROXY,
+		g_param_spec_object (
+			"open-proxy",
+			"Open Proxy",
+			NULL,
+			GTK_TYPE_ACTION,
+			G_PARAM_READWRITE));
+
+	/* Inherited from ESelectableInterface */
+	g_object_class_override_property (
+		object_class,
+		PROP_PASTE_TARGET_LIST,
+		"paste-target-list");
+
+	g_object_class_install_property (
+		object_class,
+		PROP_PRINT_PROXY,
+		g_param_spec_object (
+			"print-proxy",
+			"Print Proxy",
+			NULL,
+			GTK_TYPE_ACTION,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_SAVE_AS_PROXY,
+		g_param_spec_object (
+			"save-as-proxy",
+			"Save As Proxy",
+			NULL,
+			GTK_TYPE_ACTION,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_SELECTED_URI,
+		g_param_spec_string (
+			"selected-uri",
+			"Selected URI",
+			NULL,
+			NULL,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_CURSOR_IMAGE,
+		g_param_spec_object (
+			"cursor-image",
+			"Image animation at the mouse cursor",
+			NULL,
+			GDK_TYPE_PIXBUF_ANIMATION,
+			G_PARAM_READWRITE));
+
+	signals[COPY_CLIPBOARD] = g_signal_new (
+		"copy-clipboard",
+		G_TYPE_FROM_CLASS (class),
+		G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+		G_STRUCT_OFFSET (EWebViewGtkHTMLClass, copy_clipboard),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__VOID,
+		G_TYPE_NONE, 0);
+
+	signals[CUT_CLIPBOARD] = g_signal_new (
+		"cut-clipboard",
+		G_TYPE_FROM_CLASS (class),
+		G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+		G_STRUCT_OFFSET (EWebViewGtkHTMLClass, cut_clipboard),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__VOID,
+		G_TYPE_NONE, 0);
+
+	signals[PASTE_CLIPBOARD] = g_signal_new (
+		"paste-clipboard",
+		G_TYPE_FROM_CLASS (class),
+		G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+		G_STRUCT_OFFSET (EWebViewGtkHTMLClass, paste_clipboard),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__VOID,
+		G_TYPE_NONE, 0);
+
+	signals[POPUP_EVENT] = g_signal_new (
+		"popup-event",
+		G_TYPE_FROM_CLASS (class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (EWebViewGtkHTMLClass, popup_event),
+		g_signal_accumulator_true_handled, NULL,
+		e_marshal_BOOLEAN__BOXED_STRING,
+		G_TYPE_BOOLEAN, 2,
+		GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE,
+		G_TYPE_STRING);
+
+	signals[STATUS_MESSAGE] = g_signal_new (
+		"status-message",
+		G_TYPE_FROM_CLASS (class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (EWebViewGtkHTMLClass, status_message),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__STRING,
+		G_TYPE_NONE, 1,
+		G_TYPE_STRING);
+
+	signals[STOP_LOADING] = g_signal_new (
+		"stop-loading",
+		G_TYPE_FROM_CLASS (class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (EWebViewGtkHTMLClass, stop_loading),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__VOID,
+		G_TYPE_NONE, 0);
+
+	signals[UPDATE_ACTIONS] = g_signal_new (
+		"update-actions",
+		G_TYPE_FROM_CLASS (class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (EWebViewGtkHTMLClass, update_actions),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__VOID,
+		G_TYPE_NONE, 0);
+
+	/* return TRUE when a signal handler processed the mailto URI */
+	signals[PROCESS_MAILTO] = g_signal_new (
+		"process-mailto",
+		G_TYPE_FROM_CLASS (class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (EWebViewGtkHTMLClass, process_mailto),
+		NULL, NULL,
+		e_marshal_BOOLEAN__STRING,
+		G_TYPE_BOOLEAN, 1, G_TYPE_STRING);
+}
+
+static void
+e_web_view_gtkhtml_alert_sink_init (EAlertSinkInterface *interface)
+{
+	interface->submit_alert = web_view_gtkhtml_submit_alert;
+}
+
+static void
+e_web_view_gtkhtml_selectable_init (ESelectableInterface *interface)
+{
+	interface->update_actions = web_view_gtkhtml_selectable_update_actions;
+	interface->cut_clipboard = web_view_gtkhtml_selectable_cut_clipboard;
+	interface->copy_clipboard = web_view_gtkhtml_selectable_copy_clipboard;
+	interface->paste_clipboard = web_view_gtkhtml_selectable_paste_clipboard;
+	interface->select_all = web_view_gtkhtml_selectable_select_all;
+}
+
+static void
+e_web_view_gtkhtml_init (EWebViewGtkHTML *web_view)
+{
+	GtkUIManager *ui_manager;
+	GtkActionGroup *action_group;
+	GtkTargetList *target_list;
+	EPopupAction *popup_action;
+	const gchar *domain = GETTEXT_PACKAGE;
+	const gchar *id;
+	GError *error = NULL;
+
+	web_view->priv = E_WEB_VIEW_GTKHTML_GET_PRIVATE (web_view);
+
+	ui_manager = gtk_ui_manager_new ();
+	web_view->priv->ui_manager = ui_manager;
+
+	g_signal_connect_swapped (
+		ui_manager, "connect-proxy",
+		G_CALLBACK (web_view_gtkhtml_connect_proxy_cb), web_view);
+
+	target_list = gtk_target_list_new (NULL, 0);
+	web_view->priv->copy_target_list = target_list;
+
+	target_list = gtk_target_list_new (NULL, 0);
+	web_view->priv->paste_target_list = target_list;
+
+	action_group = gtk_action_group_new ("uri");
+	gtk_action_group_set_translation_domain (action_group, domain);
+	gtk_ui_manager_insert_action_group (ui_manager, action_group, 0);
+	g_object_unref (action_group);
+
+	gtk_action_group_add_actions (
+		action_group, uri_entries,
+		G_N_ELEMENTS (uri_entries), web_view);
+
+	action_group = gtk_action_group_new ("http");
+	gtk_action_group_set_translation_domain (action_group, domain);
+	gtk_ui_manager_insert_action_group (ui_manager, action_group, 0);
+	g_object_unref (action_group);
+
+	gtk_action_group_add_actions (
+		action_group, http_entries,
+		G_N_ELEMENTS (http_entries), web_view);
+
+	action_group = gtk_action_group_new ("mailto");
+	gtk_action_group_set_translation_domain (action_group, domain);
+	gtk_ui_manager_insert_action_group (ui_manager, action_group, 0);
+	g_object_unref (action_group);
+
+	gtk_action_group_add_actions (
+		action_group, mailto_entries,
+		G_N_ELEMENTS (mailto_entries), web_view);
+
+	action_group = gtk_action_group_new ("image");
+	gtk_action_group_set_translation_domain (action_group, domain);
+	gtk_ui_manager_insert_action_group (ui_manager, action_group, 0);
+	g_object_unref (action_group);
+
+	gtk_action_group_add_actions (
+		action_group, image_entries,
+		G_N_ELEMENTS (image_entries), web_view);
+
+	action_group = gtk_action_group_new ("selection");
+	gtk_action_group_set_translation_domain (action_group, domain);
+	gtk_ui_manager_insert_action_group (ui_manager, action_group, 0);
+	g_object_unref (action_group);
+
+	gtk_action_group_add_actions (
+		action_group, selection_entries,
+		G_N_ELEMENTS (selection_entries), web_view);
+
+	action_group = gtk_action_group_new ("standard");
+	gtk_action_group_set_translation_domain (action_group, domain);
+	gtk_ui_manager_insert_action_group (ui_manager, action_group, 0);
+	g_object_unref (action_group);
+
+	gtk_action_group_add_actions (
+		action_group, standard_entries,
+		G_N_ELEMENTS (standard_entries), web_view);
+
+	popup_action = e_popup_action_new ("open");
+	gtk_action_group_add_action (action_group, GTK_ACTION (popup_action));
+	g_object_unref (popup_action);
+
+	g_object_bind_property (
+		web_view, "open-proxy",
+		popup_action, "related-action",
+		G_BINDING_BIDIRECTIONAL |
+		G_BINDING_SYNC_CREATE);
+
+	/* Support lockdown. */
+
+	action_group = gtk_action_group_new ("lockdown-printing");
+	gtk_action_group_set_translation_domain (action_group, domain);
+	gtk_ui_manager_insert_action_group (ui_manager, action_group, 0);
+	g_object_unref (action_group);
+
+	popup_action = e_popup_action_new ("print");
+	gtk_action_group_add_action (action_group, GTK_ACTION (popup_action));
+	g_object_unref (popup_action);
+
+	g_object_bind_property (
+		web_view, "print-proxy",
+		popup_action, "related-action",
+		G_BINDING_BIDIRECTIONAL |
+		G_BINDING_SYNC_CREATE);
+
+	action_group = gtk_action_group_new ("lockdown-save-to-disk");
+	gtk_action_group_set_translation_domain (action_group, domain);
+	gtk_ui_manager_insert_action_group (ui_manager, action_group, 0);
+	g_object_unref (action_group);
+
+	popup_action = e_popup_action_new ("save-as");
+	gtk_action_group_add_action (action_group, GTK_ACTION (popup_action));
+	g_object_unref (popup_action);
+
+	g_object_bind_property (
+		web_view, "save-as-proxy",
+		popup_action, "related-action",
+		G_BINDING_BIDIRECTIONAL |
+		G_BINDING_SYNC_CREATE);
+
+	/* Because we are loading from a hard-coded string, there is
+	 * no chance of I/O errors.  Failure here implies a malformed
+	 * UI definition.  Full stop. */
+	gtk_ui_manager_add_ui_from_string (ui_manager, ui, -1, &error);
+	if (error != NULL)
+		g_error ("%s", error->message);
+
+	id = "org.gnome.evolution.webview";
+	e_plugin_ui_register_manager (ui_manager, id, web_view);
+	e_plugin_ui_enable_manager (ui_manager, id);
+
+	e_extensible_load_extensions (E_EXTENSIBLE (web_view));
+}
+
+GtkWidget *
+e_web_view_gtkhtml_new (void)
+{
+	return g_object_new (E_TYPE_WEB_VIEW_GTKHTML, NULL);
+}
+
+void
+e_web_view_gtkhtml_clear (EWebViewGtkHTML *web_view)
+{
+	g_return_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view));
+
+	gtk_html_load_empty (GTK_HTML (web_view));
+}
+
+void
+e_web_view_gtkhtml_load_string (EWebViewGtkHTML *web_view,
+                                const gchar *string)
+{
+	EWebViewGtkHTMLClass *class;
+
+	g_return_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view));
+
+	class = E_WEB_VIEW_GTKHTML_GET_CLASS (web_view);
+	g_return_if_fail (class->load_string != NULL);
+
+	class->load_string (web_view, string);
+}
+
+gboolean
+e_web_view_gtkhtml_get_animate (EWebViewGtkHTML *web_view)
+{
+	/* XXX This is just here to maintain symmetry
+	 *     with e_web_view_set_animate(). */
+
+	g_return_val_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view), FALSE);
+
+	return gtk_html_get_animate (GTK_HTML (web_view));
+}
+
+void
+e_web_view_gtkhtml_set_animate (EWebViewGtkHTML *web_view,
+                                gboolean animate)
+{
+	/* XXX GtkHTML does not utilize GObject properties as well
+	 *     as it could.  This just wraps gtk_html_set_animate()
+	 *     so we can get a "notify::animate" signal. */
+
+	g_return_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view));
+
+	gtk_html_set_animate (GTK_HTML (web_view), animate);
+
+	g_object_notify (G_OBJECT (web_view), "animate");
+}
+
+gboolean
+e_web_view_gtkhtml_get_caret_mode (EWebViewGtkHTML *web_view)
+{
+	/* XXX This is just here to maintain symmetry
+	 *     with e_web_view_set_caret_mode(). */
+
+	g_return_val_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view), FALSE);
+
+	return gtk_html_get_caret_mode (GTK_HTML (web_view));
+}
+
+void
+e_web_view_gtkhtml_set_caret_mode (EWebViewGtkHTML *web_view,
+                                   gboolean caret_mode)
+{
+	/* XXX GtkHTML does not utilize GObject properties as well
+	 *     as it could.  This just wraps gtk_html_set_caret_mode()
+	 *     so we can get a "notify::caret-mode" signal. */
+
+	g_return_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view));
+
+	gtk_html_set_caret_mode (GTK_HTML (web_view), caret_mode);
+
+	g_object_notify (G_OBJECT (web_view), "caret-mode");
+}
+
+GtkTargetList *
+e_web_view_gtkhtml_get_copy_target_list (EWebViewGtkHTML *web_view)
+{
+	g_return_val_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view), NULL);
+
+	return web_view->priv->copy_target_list;
+}
+
+gboolean
+e_web_view_gtkhtml_get_disable_printing (EWebViewGtkHTML *web_view)
+{
+	g_return_val_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view), FALSE);
+
+	return web_view->priv->disable_printing;
+}
+
+void
+e_web_view_gtkhtml_set_disable_printing (EWebViewGtkHTML *web_view,
+                                         gboolean disable_printing)
+{
+	g_return_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view));
+
+	web_view->priv->disable_printing = disable_printing;
+
+	g_object_notify (G_OBJECT (web_view), "disable-printing");
+}
+
+gboolean
+e_web_view_gtkhtml_get_disable_save_to_disk (EWebViewGtkHTML *web_view)
+{
+	g_return_val_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view), FALSE);
+
+	return web_view->priv->disable_save_to_disk;
+}
+
+void
+e_web_view_gtkhtml_set_disable_save_to_disk (EWebViewGtkHTML *web_view,
+                                             gboolean disable_save_to_disk)
+{
+	g_return_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view));
+
+	web_view->priv->disable_save_to_disk = disable_save_to_disk;
+
+	g_object_notify (G_OBJECT (web_view), "disable-save-to-disk");
+}
+
+gboolean
+e_web_view_gtkhtml_get_editable (EWebViewGtkHTML *web_view)
+{
+	/* XXX This is just here to maintain symmetry
+	 *     with e_web_view_set_editable(). */
+
+	g_return_val_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view), FALSE);
+
+	return gtk_html_get_editable (GTK_HTML (web_view));
+}
+
+void
+e_web_view_gtkhtml_set_editable (EWebViewGtkHTML *web_view,
+                                 gboolean editable)
+{
+	/* XXX GtkHTML does not utilize GObject properties as well
+	 *     as it could.  This just wraps gtk_html_set_editable()
+	 *     so we can get a "notify::editable" signal. */
+
+	g_return_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view));
+
+	gtk_html_set_editable (GTK_HTML (web_view), editable);
+
+	g_object_notify (G_OBJECT (web_view), "editable");
+}
+
+gboolean
+e_web_view_gtkhtml_get_inline_spelling (EWebViewGtkHTML *web_view)
+{
+	/* XXX This is just here to maintain symmetry
+	 *     with e_web_view_set_inline_spelling(). */
+
+	g_return_val_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view), FALSE);
+
+	return gtk_html_get_inline_spelling (GTK_HTML (web_view));
+}
+
+void
+e_web_view_gtkhtml_set_inline_spelling (EWebViewGtkHTML *web_view,
+                                        gboolean inline_spelling)
+{
+	/* XXX GtkHTML does not utilize GObject properties as well
+	 *     as it could.  This just wraps gtk_html_set_inline_spelling()
+	 *     so we get a "notify::inline-spelling" signal. */
+
+	g_return_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view));
+
+	gtk_html_set_inline_spelling (GTK_HTML (web_view), inline_spelling);
+
+	g_object_notify (G_OBJECT (web_view), "inline-spelling");
+}
+
+gboolean
+e_web_view_gtkhtml_get_magic_links (EWebViewGtkHTML *web_view)
+{
+	/* XXX This is just here to maintain symmetry
+	 *     with e_web_view_set_magic_links(). */
+
+	g_return_val_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view), FALSE);
+
+	return gtk_html_get_magic_links (GTK_HTML (web_view));
+}
+
+void
+e_web_view_gtkhtml_set_magic_links (EWebViewGtkHTML *web_view,
+                                    gboolean magic_links)
+{
+	/* XXX GtkHTML does not utilize GObject properties as well
+	 *     as it could.  This just wraps gtk_html_set_magic_links()
+	 *     so we can get a "notify::magic-links" signal. */
+
+	g_return_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view));
+
+	gtk_html_set_magic_links (GTK_HTML (web_view), magic_links);
+
+	g_object_notify (G_OBJECT (web_view), "magic-links");
+}
+
+gboolean
+e_web_view_gtkhtml_get_magic_smileys (EWebViewGtkHTML *web_view)
+{
+	/* XXX This is just here to maintain symmetry
+	 *     with e_web_view_set_magic_smileys(). */
+
+	g_return_val_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view), FALSE);
+
+	return gtk_html_get_magic_smileys (GTK_HTML (web_view));
+}
+
+void
+e_web_view_gtkhtml_set_magic_smileys (EWebViewGtkHTML *web_view,
+                                      gboolean magic_smileys)
+{
+	/* XXX GtkHTML does not utilize GObject properties as well
+	 *     as it could.  This just wraps gtk_html_set_magic_smileys()
+	 *     so we can get a "notify::magic-smileys" signal. */
+
+	g_return_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view));
+
+	gtk_html_set_magic_smileys (GTK_HTML (web_view), magic_smileys);
+
+	g_object_notify (G_OBJECT (web_view), "magic-smileys");
+}
+
+const gchar *
+e_web_view_gtkhtml_get_selected_uri (EWebViewGtkHTML *web_view)
+{
+	g_return_val_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view), NULL);
+
+	return web_view->priv->selected_uri;
+}
+
+void
+e_web_view_gtkhtml_set_selected_uri (EWebViewGtkHTML *web_view,
+                                     const gchar *selected_uri)
+{
+	g_return_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view));
+
+	g_free (web_view->priv->selected_uri);
+	web_view->priv->selected_uri = g_strdup (selected_uri);
+
+	g_object_notify (G_OBJECT (web_view), "selected-uri");
+}
+
+GdkPixbufAnimation *
+e_web_view_gtkhtml_get_cursor_image (EWebViewGtkHTML *web_view)
+{
+	g_return_val_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view), NULL);
+
+	return web_view->priv->cursor_image;
+}
+
+void
+e_web_view_gtkhtml_set_cursor_image (EWebViewGtkHTML *web_view,
+                                     GdkPixbufAnimation *image)
+{
+	g_return_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view));
+
+	if (image != NULL)
+		g_object_ref (image);
+
+	if (web_view->priv->cursor_image != NULL)
+		g_object_unref (web_view->priv->cursor_image);
+
+	web_view->priv->cursor_image = image;
+
+	g_object_notify (G_OBJECT (web_view), "cursor-image");
+}
+
+GtkAction *
+e_web_view_gtkhtml_get_open_proxy (EWebViewGtkHTML *web_view)
+{
+	g_return_val_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view), FALSE);
+
+	return web_view->priv->open_proxy;
+}
+
+void
+e_web_view_gtkhtml_set_open_proxy (EWebViewGtkHTML *web_view,
+                                   GtkAction *open_proxy)
+{
+	g_return_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view));
+
+	if (open_proxy != NULL) {
+		g_return_if_fail (GTK_IS_ACTION (open_proxy));
+		g_object_ref (open_proxy);
+	}
+
+	if (web_view->priv->open_proxy != NULL)
+		g_object_unref (web_view->priv->open_proxy);
+
+	web_view->priv->open_proxy = open_proxy;
+
+	g_object_notify (G_OBJECT (web_view), "open-proxy");
+}
+
+GtkTargetList *
+e_web_view_gtkhtml_get_paste_target_list (EWebViewGtkHTML *web_view)
+{
+	g_return_val_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view), NULL);
+
+	return web_view->priv->paste_target_list;
+}
+
+GtkAction *
+e_web_view_gtkhtml_get_print_proxy (EWebViewGtkHTML *web_view)
+{
+	g_return_val_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view), FALSE);
+
+	return web_view->priv->print_proxy;
+}
+
+void
+e_web_view_gtkhtml_set_print_proxy (EWebViewGtkHTML *web_view,
+                                    GtkAction *print_proxy)
+{
+	g_return_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view));
+
+	if (print_proxy != NULL) {
+		g_return_if_fail (GTK_IS_ACTION (print_proxy));
+		g_object_ref (print_proxy);
+	}
+
+	if (web_view->priv->print_proxy != NULL)
+		g_object_unref (web_view->priv->print_proxy);
+
+	web_view->priv->print_proxy = print_proxy;
+
+	g_object_notify (G_OBJECT (web_view), "print-proxy");
+}
+
+GtkAction *
+e_web_view_gtkhtml_get_save_as_proxy (EWebViewGtkHTML *web_view)
+{
+	g_return_val_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view), FALSE);
+
+	return web_view->priv->save_as_proxy;
+}
+
+void
+e_web_view_gtkhtml_set_save_as_proxy (EWebViewGtkHTML *web_view,
+                                      GtkAction *save_as_proxy)
+{
+	g_return_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view));
+
+	if (save_as_proxy != NULL) {
+		g_return_if_fail (GTK_IS_ACTION (save_as_proxy));
+		g_object_ref (save_as_proxy);
+	}
+
+	if (web_view->priv->save_as_proxy != NULL)
+		g_object_unref (web_view->priv->save_as_proxy);
+
+	web_view->priv->save_as_proxy = save_as_proxy;
+
+	g_object_notify (G_OBJECT (web_view), "save-as-proxy");
+}
+
+GtkAction *
+e_web_view_gtkhtml_get_action (EWebViewGtkHTML *web_view,
+                               const gchar *action_name)
+{
+	GtkUIManager *ui_manager;
+
+	g_return_val_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view), NULL);
+	g_return_val_if_fail (action_name != NULL, NULL);
+
+	ui_manager = e_web_view_gtkhtml_get_ui_manager (web_view);
+
+	return e_lookup_action (ui_manager, action_name);
+}
+
+GtkActionGroup *
+e_web_view_gtkhtml_get_action_group (EWebViewGtkHTML *web_view,
+                                     const gchar *group_name)
+{
+	GtkUIManager *ui_manager;
+
+	g_return_val_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view), NULL);
+	g_return_val_if_fail (group_name != NULL, NULL);
+
+	ui_manager = e_web_view_gtkhtml_get_ui_manager (web_view);
+
+	return e_lookup_action_group (ui_manager, group_name);
+}
+
+gchar *
+e_web_view_gtkhtml_extract_uri (EWebViewGtkHTML *web_view,
+                                GdkEventButton *event,
+                                GtkHTML *frame)
+{
+	EWebViewGtkHTMLClass *class;
+
+	g_return_val_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view), NULL);
+
+	if (frame == NULL)
+		frame = GTK_HTML (web_view);
+
+	class = E_WEB_VIEW_GTKHTML_GET_CLASS (web_view);
+	g_return_val_if_fail (class->extract_uri != NULL, NULL);
+
+	return class->extract_uri (web_view, event, frame);
+}
+
+void
+e_web_view_gtkhtml_copy_clipboard (EWebViewGtkHTML *web_view)
+{
+	g_return_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view));
+
+	g_signal_emit (web_view, signals[COPY_CLIPBOARD], 0);
+}
+
+void
+e_web_view_gtkhtml_cut_clipboard (EWebViewGtkHTML *web_view)
+{
+	g_return_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view));
+
+	g_signal_emit (web_view, signals[CUT_CLIPBOARD], 0);
+}
+
+gboolean
+e_web_view_gtkhtml_is_selection_active (EWebViewGtkHTML *web_view)
+{
+	g_return_val_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view), FALSE);
+
+	return gtk_html_command (GTK_HTML (web_view), "is-selection-active");
+}
+
+void
+e_web_view_gtkhtml_paste_clipboard (EWebViewGtkHTML *web_view)
+{
+	g_return_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view));
+
+	g_signal_emit (web_view, signals[PASTE_CLIPBOARD], 0);
+}
+
+gboolean
+e_web_view_gtkhtml_scroll_forward (EWebViewGtkHTML *web_view)
+{
+	g_return_val_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view), FALSE);
+
+	return gtk_html_command (GTK_HTML (web_view), "scroll-forward");
+}
+
+gboolean
+e_web_view_gtkhtml_scroll_backward (EWebViewGtkHTML *web_view)
+{
+	g_return_val_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view), FALSE);
+
+	return gtk_html_command (GTK_HTML (web_view), "scroll-backward");
+}
+
+void
+e_web_view_gtkhtml_select_all (EWebViewGtkHTML *web_view)
+{
+	g_return_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view));
+
+	gtk_html_command (GTK_HTML (web_view), "select-all");
+}
+
+void
+e_web_view_gtkhtml_unselect_all (EWebViewGtkHTML *web_view)
+{
+	g_return_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view));
+
+	gtk_html_command (GTK_HTML (web_view), "unselect-all");
+}
+
+void
+e_web_view_gtkhtml_zoom_100 (EWebViewGtkHTML *web_view)
+{
+	g_return_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view));
+
+	gtk_html_command (GTK_HTML (web_view), "zoom-reset");
+}
+
+void
+e_web_view_gtkhtml_zoom_in (EWebViewGtkHTML *web_view)
+{
+	g_return_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view));
+
+	gtk_html_command (GTK_HTML (web_view), "zoom-in");
+}
+
+void
+e_web_view_gtkhtml_zoom_out (EWebViewGtkHTML *web_view)
+{
+	g_return_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view));
+
+	gtk_html_command (GTK_HTML (web_view), "zoom-out");
+}
+
+GtkUIManager *
+e_web_view_gtkhtml_get_ui_manager (EWebViewGtkHTML *web_view)
+{
+	g_return_val_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view), NULL);
+
+	return web_view->priv->ui_manager;
+}
+
+GtkWidget *
+e_web_view_gtkhtml_get_popup_menu (EWebViewGtkHTML *web_view)
+{
+	GtkUIManager *ui_manager;
+	GtkWidget *menu;
+
+	g_return_val_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view), NULL);
+
+	ui_manager = e_web_view_gtkhtml_get_ui_manager (web_view);
+	menu = gtk_ui_manager_get_widget (ui_manager, "/context");
+	g_return_val_if_fail (GTK_IS_MENU (menu), NULL);
+
+	return menu;
+}
+
+void
+e_web_view_gtkhtml_show_popup_menu (EWebViewGtkHTML *web_view,
+                                    GdkEventButton *event,
+                                    GtkMenuPositionFunc func,
+                                    gpointer user_data)
+{
+	GtkWidget *menu;
+
+	g_return_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view));
+
+	e_web_view_gtkhtml_update_actions (web_view);
+
+	menu = e_web_view_gtkhtml_get_popup_menu (web_view);
+
+	if (event != NULL)
+		gtk_menu_popup (
+			GTK_MENU (menu), NULL, NULL, func,
+			user_data, event->button, event->time);
+	else
+		gtk_menu_popup (
+			GTK_MENU (menu), NULL, NULL, func,
+			user_data, 0, gtk_get_current_event_time ());
+}
+
+void
+e_web_view_gtkhtml_status_message (EWebViewGtkHTML *web_view,
+                                   const gchar *status_message)
+{
+	g_return_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view));
+
+	g_signal_emit (web_view, signals[STATUS_MESSAGE], 0, status_message);
+}
+
+void
+e_web_view_gtkhtml_stop_loading (EWebViewGtkHTML *web_view)
+{
+	g_return_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view));
+
+	g_signal_emit (web_view, signals[STOP_LOADING], 0);
+}
+
+void
+e_web_view_gtkhtml_update_actions (EWebViewGtkHTML *web_view)
+{
+	g_return_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view));
+
+	g_signal_emit (web_view, signals[UPDATE_ACTIONS], 0);
+}
diff --git a/e-util/e-web-view-gtkhtml.h b/e-util/e-web-view-gtkhtml.h
new file mode 100644
index 0000000..ebe965c
--- /dev/null
+++ b/e-util/e-web-view-gtkhtml.h
@@ -0,0 +1,177 @@
+/*
+ * e-web-view-gtkhtml.h
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+/* This is intended to serve as a common base class for all HTML viewing
+ * needs in Evolution.  Currently based on GtkHTML, the idea is to wrap
+ * the GtkHTML API enough that we no longer have to make direct calls to
+ * it.  This should help smooth the transition to WebKit/GTK+.
+ *
+ * This class handles basic tasks like mouse hovers over links, clicked
+ * links, and servicing URI requests asynchronously via GIO. */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_WEB_VIEW_GTKHTML_H
+#define E_WEB_VIEW_GTKHTML_H
+
+#include <gtkhtml/gtkhtml.h>
+
+/* Standard GObject macros */
+#define E_TYPE_WEB_VIEW_GTKHTML \
+	(e_web_view_gtkhtml_get_type ())
+#define E_WEB_VIEW_GTKHTML(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_WEB_VIEW_GTKHTML, EWebViewGtkHTML))
+#define E_WEB_VIEW_GTKHTML_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_WEB_VIEW_GTKHTML, EWebViewGtkHTMLClass))
+#define E_IS_WEB_VIEW_GTKHTML(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_WEB_VIEW_GTKHTML))
+#define E_IS_WEB_VIEW_GTKHTML_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_WEB_VIEW_GTKHTML))
+#define E_WEB_VIEW_GTKHTML_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_WEB_VIEW_GTKHTML, EWebViewGtkHTMLClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EWebViewGtkHTML EWebViewGtkHTML;
+typedef struct _EWebViewGtkHTMLClass EWebViewGtkHTMLClass;
+typedef struct _EWebViewGtkHTMLPrivate EWebViewGtkHTMLPrivate;
+
+struct _EWebViewGtkHTML {
+	GtkHTML parent;
+	EWebViewGtkHTMLPrivate *priv;
+};
+
+struct _EWebViewGtkHTMLClass {
+	GtkHTMLClass parent_class;
+
+	/* Methods */
+	gchar *		(*extract_uri)		(EWebViewGtkHTML *web_view,
+						 GdkEventButton *event,
+						 GtkHTML *frame);
+	void		(*hovering_over_link)	(EWebViewGtkHTML *web_view,
+						 const gchar *title,
+						 const gchar *uri);
+	void		(*link_clicked)		(EWebViewGtkHTML *web_view,
+						 const gchar *uri);
+	void		(*load_string)		(EWebViewGtkHTML *web_view,
+						 const gchar *load_string);
+
+	/* Signals */
+	void		(*copy_clipboard)	(EWebViewGtkHTML *web_view);
+	void		(*cut_clipboard)	(EWebViewGtkHTML *web_view);
+	void		(*paste_clipboard)	(EWebViewGtkHTML *web_view);
+	gboolean	(*popup_event)		(EWebViewGtkHTML *web_view,
+						 GdkEventButton *event,
+						 const gchar *uri);
+	void		(*status_message)	(EWebViewGtkHTML *web_view,
+						 const gchar *status_message);
+	void		(*stop_loading)		(EWebViewGtkHTML *web_view);
+	void		(*update_actions)	(EWebViewGtkHTML *web_view);
+	gboolean	(*process_mailto)	(EWebViewGtkHTML *web_view,
+						 const gchar *mailto_uri);
+};
+
+GType		e_web_view_gtkhtml_get_type		(void);
+GtkWidget *	e_web_view_gtkhtml_new			(void);
+void		e_web_view_gtkhtml_clear		(EWebViewGtkHTML *web_view);
+void		e_web_view_gtkhtml_load_string		(EWebViewGtkHTML *web_view,
+							 const gchar *string);
+gboolean	e_web_view_gtkhtml_get_animate		(EWebViewGtkHTML *web_view);
+void		e_web_view_gtkhtml_set_animate		(EWebViewGtkHTML *web_view,
+							 gboolean animate);
+gboolean	e_web_view_gtkhtml_get_caret_mode	(EWebViewGtkHTML *web_view);
+void		e_web_view_gtkhtml_set_caret_mode	(EWebViewGtkHTML *web_view,
+							 gboolean caret_mode);
+GtkTargetList *	e_web_view_gtkhtml_get_copy_target_list	(EWebViewGtkHTML *web_view);
+gboolean	e_web_view_gtkhtml_get_disable_printing	(EWebViewGtkHTML *web_view);
+void		e_web_view_gtkhtml_set_disable_printing	(EWebViewGtkHTML *web_view,
+							 gboolean disable_printing);
+gboolean	e_web_view_gtkhtml_get_disable_save_to_disk
+							(EWebViewGtkHTML *web_view);
+void		e_web_view_gtkhtml_set_disable_save_to_disk
+							(EWebViewGtkHTML *web_view,
+							 gboolean disable_save_to_disk);
+gboolean	e_web_view_gtkhtml_get_editable		(EWebViewGtkHTML *web_view);
+void		e_web_view_gtkhtml_set_editable		(EWebViewGtkHTML *web_view,
+							 gboolean editable);
+gboolean	e_web_view_gtkhtml_get_inline_spelling	(EWebViewGtkHTML *web_view);
+void		e_web_view_gtkhtml_set_inline_spelling	(EWebViewGtkHTML *web_view,
+							 gboolean inline_spelling);
+gboolean	e_web_view_gtkhtml_get_magic_links	(EWebViewGtkHTML *web_view);
+void		e_web_view_gtkhtml_set_magic_links	(EWebViewGtkHTML *web_view,
+							 gboolean magic_links);
+gboolean	e_web_view_gtkhtml_get_magic_smileys	(EWebViewGtkHTML *web_view);
+void		e_web_view_gtkhtml_set_magic_smileys	(EWebViewGtkHTML *web_view,
+							 gboolean magic_smileys);
+const gchar *	e_web_view_gtkhtml_get_selected_uri	(EWebViewGtkHTML *web_view);
+void		e_web_view_gtkhtml_set_selected_uri	(EWebViewGtkHTML *web_view,
+							 const gchar *selected_uri);
+GdkPixbufAnimation *
+		e_web_view_gtkhtml_get_cursor_image	(EWebViewGtkHTML *web_view);
+void		e_web_view_gtkhtml_set_cursor_image	(EWebViewGtkHTML *web_view,
+							 GdkPixbufAnimation *animation);
+GtkAction *	e_web_view_gtkhtml_get_open_proxy	(EWebViewGtkHTML *web_view);
+void		e_web_view_gtkhtml_set_open_proxy	(EWebViewGtkHTML *web_view,
+							 GtkAction *open_proxy);
+GtkTargetList *	e_web_view_gtkhtml_get_paste_target_list
+							(EWebViewGtkHTML *web_view);
+GtkAction *	e_web_view_gtkhtml_get_print_proxy	(EWebViewGtkHTML *web_view);
+void		e_web_view_gtkhtml_set_print_proxy	(EWebViewGtkHTML *web_view,
+							 GtkAction *print_proxy);
+GtkAction *	e_web_view_gtkhtml_get_save_as_proxy	(EWebViewGtkHTML *web_view);
+void		e_web_view_gtkhtml_set_save_as_proxy	(EWebViewGtkHTML *web_view,
+							 GtkAction *save_as_proxy);
+GtkAction *	e_web_view_gtkhtml_get_action		(EWebViewGtkHTML *web_view,
+							 const gchar *action_name);
+GtkActionGroup *e_web_view_gtkhtml_get_action_group	(EWebViewGtkHTML *web_view,
+							 const gchar *group_name);
+gchar *		e_web_view_gtkhtml_extract_uri		(EWebViewGtkHTML *web_view,
+							 GdkEventButton *event,
+							 GtkHTML *frame);
+void		e_web_view_gtkhtml_copy_clipboard	(EWebViewGtkHTML *web_view);
+void		e_web_view_gtkhtml_cut_clipboard	(EWebViewGtkHTML *web_view);
+gboolean	e_web_view_gtkhtml_is_selection_active	(EWebViewGtkHTML *web_view);
+void		e_web_view_gtkhtml_paste_clipboard	(EWebViewGtkHTML *web_view);
+gboolean	e_web_view_gtkhtml_scroll_forward	(EWebViewGtkHTML *web_view);
+gboolean	e_web_view_gtkhtml_scroll_backward	(EWebViewGtkHTML *web_view);
+void		e_web_view_gtkhtml_select_all		(EWebViewGtkHTML *web_view);
+void		e_web_view_gtkhtml_unselect_all		(EWebViewGtkHTML *web_view);
+void		e_web_view_gtkhtml_zoom_100		(EWebViewGtkHTML *web_view);
+void		e_web_view_gtkhtml_zoom_in		(EWebViewGtkHTML *web_view);
+void		e_web_view_gtkhtml_zoom_out		(EWebViewGtkHTML *web_view);
+GtkUIManager *	e_web_view_gtkhtml_get_ui_manager	(EWebViewGtkHTML *web_view);
+GtkWidget *	e_web_view_gtkhtml_get_popup_menu	(EWebViewGtkHTML *web_view);
+void		e_web_view_gtkhtml_show_popup_menu	(EWebViewGtkHTML *web_view,
+							 GdkEventButton *event,
+							 GtkMenuPositionFunc func,
+							 gpointer user_data);
+void		e_web_view_gtkhtml_status_message	(EWebViewGtkHTML *web_view,
+							const gchar *status_message);
+void		e_web_view_gtkhtml_stop_loading		(EWebViewGtkHTML *web_view);
+void		e_web_view_gtkhtml_update_actions	(EWebViewGtkHTML *web_view);
+
+G_END_DECLS
+
+#endif /* E_WEB_VIEW_GTKHTML_H */
diff --git a/widgets/misc/e-web-view-preview.c b/e-util/e-web-view-preview.c
similarity index 100%
rename from widgets/misc/e-web-view-preview.c
rename to e-util/e-web-view-preview.c
diff --git a/e-util/e-web-view-preview.h b/e-util/e-web-view-preview.h
new file mode 100644
index 0000000..54963b6
--- /dev/null
+++ b/e-util/e-web-view-preview.h
@@ -0,0 +1,115 @@
+/*
+ * e-web-view-preview.h
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+/* This is intended to serve as a common widget for previews before import.
+ * It contains a GtkTreeView at the top and an EWebView at the bottom.
+ * The tree view is not shown initially, it should be forced with
+ * e_web_view_preview_show_tree_view().
+ *
+ * The internal default EWebView can be accessed by e_web_view_preview_get_preview()
+ * and it should be updated for each change of the selected item in the tree
+ * view, when it's shown.
+ *
+ * Updating an EWebView content through helper functions of an EWebViewPreview
+ * begins with call of e_web_view_preview_begin_update(), which starts an empty
+ * page construction, which is finished by e_web_view_preview_end_update(),
+ * and the content of the EWebView is updated.
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_WEB_VIEW_PREVIEW_H
+#define E_WEB_VIEW_PREVIEW_H
+
+#include <e-util/e-web-view.h>
+
+/* Standard GObject macros */
+#define E_TYPE_WEB_VIEW_PREVIEW \
+	(e_web_view_preview_get_type ())
+#define E_WEB_VIEW_PREVIEW(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_WEB_VIEW_PREVIEW, EWebViewPreview))
+#define E_WEB_VIEW_PREVIEW_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_WEB_VIEW_PREVIEW, EWebViewPreviewClass))
+#define E_IS_WEB_VIEW_PREVIEW(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_WEB_VIEW_PREVIEW))
+#define E_IS_WEB_VIEW_PREVIEW_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_WEB_VIEW_PREVIEW))
+#define E_WEB_VIEW_PREVIEW_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_WEB_VIEW_PREVIEW, EWebViewPreviewClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EWebViewPreview EWebViewPreview;
+typedef struct _EWebViewPreviewClass EWebViewPreviewClass;
+typedef struct _EWebViewPreviewPrivate EWebViewPreviewPrivate;
+
+struct _EWebViewPreview {
+	GtkVPaned parent;
+	EWebViewPreviewPrivate *priv;
+};
+
+struct _EWebViewPreviewClass {
+	GtkVPanedClass parent_class;
+};
+
+GType		e_web_view_preview_get_type	(void);
+GtkWidget *	e_web_view_preview_new		(void);
+GtkTreeView *	e_web_view_preview_get_tree_view
+						(EWebViewPreview *preview);
+GtkWidget *	e_web_view_preview_get_preview	(EWebViewPreview *preview);
+void		e_web_view_preview_set_preview	(EWebViewPreview *preview,
+						 GtkWidget *preview_widget);
+void		e_web_view_preview_show_tree_view
+						(EWebViewPreview *preview);
+void		e_web_view_preview_hide_tree_view
+						(EWebViewPreview *preview);
+void		e_web_view_preview_set_escape_values
+						(EWebViewPreview *preview,
+						 gboolean escape);
+gboolean	e_web_view_preview_get_escape_values
+						(EWebViewPreview *preview);
+void		e_web_view_preview_begin_update	(EWebViewPreview *preview);
+void		e_web_view_preview_end_update	(EWebViewPreview *preview);
+void		e_web_view_preview_add_header	(EWebViewPreview *preview,
+						 gint index,
+						 const gchar *header);
+void		e_web_view_preview_add_text	(EWebViewPreview *preview,
+						 const gchar *text);
+void		e_web_view_preview_add_raw_html	(EWebViewPreview *preview,
+						 const gchar *raw_html);
+void		e_web_view_preview_add_separator
+						(EWebViewPreview *preview);
+void		e_web_view_preview_add_empty_line
+						(EWebViewPreview *preview);
+void		e_web_view_preview_add_section	(EWebViewPreview *preview,
+						 const gchar *section,
+						 const gchar *value);
+
+G_END_DECLS
+
+#endif /* E_WEB_VIEW_PREVIEW_H */
diff --git a/e-util/e-web-view.c b/e-util/e-web-view.c
new file mode 100644
index 0000000..2fefe4f
--- /dev/null
+++ b/e-util/e-web-view.c
@@ -0,0 +1,2945 @@
+/*
+ * e-web-view.c
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-web-view.h"
+
+#include <math.h>
+
+#include <stdlib.h>
+#include <string.h>
+#include <glib/gi18n-lib.h>
+#include <pango/pango.h>
+
+#include <camel/camel.h>
+#include <libebackend/libebackend.h>
+
+#define LIBSOUP_USE_UNSTABLE_REQUEST_API
+#include <libsoup/soup.h>
+#include <libsoup/soup-requester.h>
+
+#include "e-alert-dialog.h"
+#include "e-alert-sink.h"
+#include "e-file-request.h"
+#include "e-misc-utils.h"
+#include "e-plugin-ui.h"
+#include "e-popup-action.h"
+#include "e-selectable.h"
+#include "e-stock-request.h"
+
+#define E_WEB_VIEW_GET_PRIVATE(obj) \
+	(G_TYPE_INSTANCE_GET_PRIVATE \
+	((obj), E_TYPE_WEB_VIEW, EWebViewPrivate))
+
+typedef struct _EWebViewRequest EWebViewRequest;
+
+struct _EWebViewPrivate {
+	GList *requests;
+	GtkUIManager *ui_manager;
+	gchar *selected_uri;
+	GdkPixbufAnimation *cursor_image;
+	gchar *cursor_image_src;
+
+        GSList *highlights;
+
+	GtkAction *open_proxy;
+	GtkAction *print_proxy;
+	GtkAction *save_as_proxy;
+
+	/* Lockdown Options */
+	guint disable_printing     : 1;
+	guint disable_save_to_disk : 1;
+
+	guint caret_mode : 1;
+
+	GSettings *font_settings;
+	GSettings *aliasing_settings;
+};
+
+enum {
+	PROP_0,
+	PROP_CARET_MODE,
+	PROP_COPY_TARGET_LIST,
+	PROP_CURSOR_IMAGE,
+	PROP_CURSOR_IMAGE_SRC,
+	PROP_DISABLE_PRINTING,
+	PROP_DISABLE_SAVE_TO_DISK,
+	PROP_INLINE_SPELLING,
+	PROP_MAGIC_LINKS,
+	PROP_MAGIC_SMILEYS,
+	PROP_OPEN_PROXY,
+	PROP_PRINT_PROXY,
+	PROP_SAVE_AS_PROXY,
+	PROP_SELECTED_URI
+};
+
+enum {
+	POPUP_EVENT,
+	STATUS_MESSAGE,
+	STOP_LOADING,
+	UPDATE_ACTIONS,
+	PROCESS_MAILTO,
+	LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+static const gchar *ui =
+"<ui>"
+"  <popup name='context'>"
+"    <menuitem action='copy-clipboard'/>"
+"    <separator/>"
+"    <placeholder name='custom-actions-1'>"
+"      <menuitem action='open'/>"
+"      <menuitem action='save-as'/>"
+"      <menuitem action='http-open'/>"
+"      <menuitem action='send-message'/>"
+"      <menuitem action='print'/>"
+"    </placeholder>"
+"    <placeholder name='custom-actions-2'>"
+"      <menuitem action='uri-copy'/>"
+"      <menuitem action='mailto-copy'/>"
+"      <menuitem action='image-copy'/>"
+"    </placeholder>"
+"    <placeholder name='custom-actions-3'/>"
+"    <separator/>"
+"    <menuitem action='select-all'/>"
+"    <placeholder name='inspect-menu' />"
+"  </popup>"
+"</ui>";
+
+/* Forward Declarations */
+static void e_web_view_alert_sink_init (EAlertSinkInterface *interface);
+static void e_web_view_selectable_init (ESelectableInterface *interface);
+
+G_DEFINE_TYPE_WITH_CODE (
+	EWebView,
+	e_web_view,
+	WEBKIT_TYPE_WEB_VIEW,
+	G_IMPLEMENT_INTERFACE (
+		E_TYPE_EXTENSIBLE, NULL)
+	G_IMPLEMENT_INTERFACE (
+		E_TYPE_ALERT_SINK,
+		e_web_view_alert_sink_init)
+	G_IMPLEMENT_INTERFACE (
+		E_TYPE_SELECTABLE,
+		e_web_view_selectable_init))
+
+static void
+action_copy_clipboard_cb (GtkAction *action,
+                          EWebView *web_view)
+{
+	e_web_view_copy_clipboard (web_view);
+}
+
+static void
+action_http_open_cb (GtkAction *action,
+                     EWebView *web_view)
+{
+	const gchar *uri;
+	gpointer parent;
+
+	parent = gtk_widget_get_toplevel (GTK_WIDGET (web_view));
+	parent = gtk_widget_is_toplevel (parent) ? parent : NULL;
+
+	uri = e_web_view_get_selected_uri (web_view);
+	g_return_if_fail (uri != NULL);
+
+	e_show_uri (parent, uri);
+}
+
+static void
+action_mailto_copy_cb (GtkAction *action,
+                       EWebView *web_view)
+{
+	CamelURL *curl;
+	CamelInternetAddress *inet_addr;
+	GtkClipboard *clipboard;
+	const gchar *uri;
+	gchar *text;
+
+	uri = e_web_view_get_selected_uri (web_view);
+	g_return_if_fail (uri != NULL);
+
+	/* This should work because we checked it in update_actions(). */
+	curl = camel_url_new (uri, NULL);
+	g_return_if_fail (curl != NULL);
+
+	inet_addr = camel_internet_address_new ();
+	camel_address_decode (CAMEL_ADDRESS (inet_addr), curl->path);
+	text = camel_address_format (CAMEL_ADDRESS (inet_addr));
+	if (text == NULL || *text == '\0')
+		text = g_strdup (uri + strlen ("mailto:";));
+
+	g_object_unref (inet_addr);
+	camel_url_free (curl);
+
+	clipboard = gtk_clipboard_get (GDK_SELECTION_PRIMARY);
+	gtk_clipboard_set_text (clipboard, text, -1);
+	gtk_clipboard_store (clipboard);
+
+	clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
+	gtk_clipboard_set_text (clipboard, text, -1);
+	gtk_clipboard_store (clipboard);
+
+	g_free (text);
+}
+
+static void
+action_select_all_cb (GtkAction *action,
+                      EWebView *web_view)
+{
+	e_web_view_select_all (web_view);
+}
+
+static void
+action_send_message_cb (GtkAction *action,
+                        EWebView *web_view)
+{
+	const gchar *uri;
+	gpointer parent;
+	gboolean handled;
+
+	parent = gtk_widget_get_toplevel (GTK_WIDGET (web_view));
+	parent = gtk_widget_is_toplevel (parent) ? parent : NULL;
+
+	uri = e_web_view_get_selected_uri (web_view);
+	g_return_if_fail (uri != NULL);
+
+	handled = FALSE;
+	g_signal_emit (web_view, signals[PROCESS_MAILTO], 0, uri, &handled);
+
+	if (!handled)
+		e_show_uri (parent, uri);
+}
+
+static void
+action_uri_copy_cb (GtkAction *action,
+                    EWebView *web_view)
+{
+	GtkClipboard *clipboard;
+	const gchar *uri;
+
+	clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
+	uri = e_web_view_get_selected_uri (web_view);
+	g_return_if_fail (uri != NULL);
+
+	gtk_clipboard_set_text (clipboard, uri, -1);
+	gtk_clipboard_store (clipboard);
+}
+
+static void
+action_image_copy_cb (GtkAction *action,
+                    EWebView *web_view)
+{
+	GtkClipboard *clipboard;
+	GdkPixbufAnimation *animation;
+	GdkPixbuf *pixbuf;
+
+	clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
+	animation = e_web_view_get_cursor_image (web_view);
+	g_return_if_fail (animation != NULL);
+
+	pixbuf = gdk_pixbuf_animation_get_static_image (animation);
+	if (pixbuf == NULL)
+		return;
+
+	gtk_clipboard_set_image (clipboard, pixbuf);
+	gtk_clipboard_store (clipboard);
+}
+
+static GtkActionEntry uri_entries[] = {
+
+	{ "uri-copy",
+	  GTK_STOCK_COPY,
+	  N_("_Copy Link Location"),
+	  NULL,
+	  N_("Copy the link to the clipboard"),
+	  G_CALLBACK (action_uri_copy_cb) }
+};
+
+static GtkActionEntry http_entries[] = {
+
+	{ "http-open",
+	  "emblem-web",
+	  N_("_Open Link in Browser"),
+	  NULL,
+	  N_("Open the link in a web browser"),
+	  G_CALLBACK (action_http_open_cb) }
+};
+
+static GtkActionEntry mailto_entries[] = {
+
+	{ "mailto-copy",
+	  GTK_STOCK_COPY,
+	  N_("_Copy Email Address"),
+	  NULL,
+	  N_("Copy the email address to the clipboard"),
+	  G_CALLBACK (action_mailto_copy_cb) },
+
+	{ "send-message",
+	  "mail-message-new",
+	  N_("_Send New Message To..."),
+	  NULL,
+	  N_("Send a mail message to this address"),
+	  G_CALLBACK (action_send_message_cb) }
+};
+
+static GtkActionEntry image_entries[] = {
+
+	{ "image-copy",
+	  GTK_STOCK_COPY,
+	  N_("_Copy Image"),
+	  NULL,
+	  N_("Copy the image to the clipboard"),
+	  G_CALLBACK (action_image_copy_cb) }
+};
+
+static GtkActionEntry selection_entries[] = {
+
+	{ "copy-clipboard",
+	  GTK_STOCK_COPY,
+	  NULL,
+	  NULL,
+	  N_("Copy the selection"),
+	  G_CALLBACK (action_copy_clipboard_cb) },
+};
+
+static GtkActionEntry standard_entries[] = {
+
+	{ "select-all",
+	  GTK_STOCK_SELECT_ALL,
+	  NULL,
+	  NULL,
+	  N_("Select all text and images"),
+	  G_CALLBACK (action_select_all_cb) }
+};
+
+static void
+web_view_menu_item_select_cb (EWebView *web_view,
+                              GtkWidget *widget)
+{
+	GtkAction *action;
+	GtkActivatable *activatable;
+	const gchar *tooltip;
+
+	activatable = GTK_ACTIVATABLE (widget);
+	action = gtk_activatable_get_related_action (activatable);
+	tooltip = gtk_action_get_tooltip (action);
+
+	if (tooltip == NULL)
+		return;
+
+	e_web_view_status_message (web_view, tooltip);
+}
+
+static void
+replace_text (WebKitDOMNode *node,
+              const gchar *text,
+              WebKitDOMNode *replacement)
+{
+	/* NodeType 3 = TEXT_NODE */
+	if (webkit_dom_node_get_node_type (node) == 3) {
+		gint text_length = strlen (text);
+
+		while (node) {
+
+			WebKitDOMNode *current_node, *replacement_node;
+			const gchar *node_data, *offset;
+			goffset split_offset;
+			gint data_length;
+
+			current_node = node;
+
+			/* Don't use the WEBKIT_DOM_CHARACTER_DATA macro for
+			 * casting. WebKit lies about type of the object and
+			 * GLib will throw runtime warning about node not being
+			 * WebKitDOMCharacterData, but the function will return
+			 * correct and valid data.
+			 * IMO it's bug in the Gtk bindings and WebKit internally
+			 * handles it by the nodeType so therefor it works
+			 * event for "invalid" objects. But really, who knows..?
+			 */
+			node_data = webkit_dom_character_data_get_data (
+				(WebKitDOMCharacterData *) node);
+
+			offset = strstr (node_data, text);
+			if (offset == NULL) {
+				node = NULL;
+				continue;
+			}
+
+			split_offset = offset - node_data + text_length;
+			replacement_node =
+				webkit_dom_node_clone_node (replacement, TRUE);
+
+			data_length = webkit_dom_character_data_get_length (
+				(WebKitDOMCharacterData *) node);
+			if (split_offset < data_length) {
+
+				WebKitDOMNode *parent_node;
+
+				node = WEBKIT_DOM_NODE (
+					webkit_dom_text_split_text (
+						(WebKitDOMText *) node,
+						offset - node_data + text_length,
+						NULL));
+				parent_node = webkit_dom_node_get_parent_node (node);
+				webkit_dom_node_insert_before (
+					parent_node, replacement_node,
+					node, NULL);
+
+			} else {
+				WebKitDOMNode *parent_node;
+
+				parent_node = webkit_dom_node_get_parent_node (node);
+				webkit_dom_node_append_child (
+					parent_node,
+					replacement_node, NULL);
+			}
+
+			webkit_dom_character_data_delete_data (
+				(WebKitDOMCharacterData *) (current_node),
+				offset - node_data, text_length, NULL);
+		}
+
+	} else {
+		WebKitDOMNode *child, *next_child;
+
+                /* Iframe? Let's traverse inside! */
+		if (WEBKIT_DOM_IS_HTML_IFRAME_ELEMENT (node)) {
+
+			WebKitDOMDocument *frame_document;
+
+			frame_document =
+				webkit_dom_html_iframe_element_get_content_document (
+					WEBKIT_DOM_HTML_IFRAME_ELEMENT (node));
+			replace_text (
+				WEBKIT_DOM_NODE (frame_document),
+				text, replacement);
+
+		} else {
+			child = webkit_dom_node_get_first_child (node);
+			while (child != NULL) {
+				next_child = webkit_dom_node_get_next_sibling (child);
+				replace_text (child, text, replacement);
+				child = next_child;
+			}
+		}
+	}
+}
+
+static void
+web_view_update_document_highlights (EWebView *web_view)
+{
+	WebKitDOMDocument *document;
+	GSList *iter;
+
+	document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (web_view));
+
+	for (iter = web_view->priv->highlights; iter; iter = iter->next) {
+
+		WebKitDOMDocumentFragment *frag;
+		WebKitDOMElement *span;
+
+		span = webkit_dom_document_create_element (document, "span", NULL);
+
+		/* See https://bugzilla.gnome.org/show_bug.cgi?id=681400
+		 * FIXME: This can be removed once we require WebKitGtk 1.10+ */
+		#if WEBKIT_CHECK_VERSION (1, 9, 6)
+			webkit_dom_element_set_class_name (
+				span, "__evo-highlight");
+		#else
+			webkit_dom_html_element_set_class_name (
+				WEBKIT_DOM_HTML_ELEMENT (span), "__evo-highlight");
+		#endif
+
+		webkit_dom_html_element_set_inner_text (
+			WEBKIT_DOM_HTML_ELEMENT (span), iter->data, NULL);
+
+		frag = webkit_dom_document_create_document_fragment (document);
+		webkit_dom_node_append_child (
+			WEBKIT_DOM_NODE (frag), WEBKIT_DOM_NODE (span), NULL);
+
+		replace_text (
+			WEBKIT_DOM_NODE (document),
+			iter->data, WEBKIT_DOM_NODE (frag));
+	}
+}
+
+static void
+web_view_menu_item_deselect_cb (EWebView *web_view)
+{
+	e_web_view_status_message (web_view, NULL);
+}
+
+static void
+web_view_connect_proxy_cb (EWebView *web_view,
+                           GtkAction *action,
+                           GtkWidget *proxy)
+{
+	if (!GTK_IS_MENU_ITEM (proxy))
+		return;
+
+	g_signal_connect_swapped (
+		proxy, "select",
+		G_CALLBACK (web_view_menu_item_select_cb), web_view);
+
+	g_signal_connect_swapped (
+		proxy, "deselect",
+		G_CALLBACK (web_view_menu_item_deselect_cb), web_view);
+}
+
+static GtkWidget *
+web_view_create_plugin_widget_cb (EWebView *web_view,
+                                  const gchar *mime_type,
+                                  const gchar *uri,
+                                  GHashTable *param)
+{
+	EWebViewClass *class;
+
+	/* XXX WebKitWebView does not provide a class method for
+	 *     this signal, so we do so we can override the default
+	 *     behavior from subclasses for special URI types. */
+
+	class = E_WEB_VIEW_GET_CLASS (web_view);
+	g_return_val_if_fail (class->create_plugin_widget != NULL, NULL);
+
+	return class->create_plugin_widget (web_view, mime_type, uri, param);
+}
+
+static void
+web_view_hovering_over_link_cb (EWebView *web_view,
+                                const gchar *title,
+                                const gchar *uri)
+{
+	EWebViewClass *class;
+
+	/* XXX WebKitWebView does not provide a class method for
+	 *     this signal, so we do so we can override the default
+	 *     behavior from subclasses for special URI types. */
+
+	class = E_WEB_VIEW_GET_CLASS (web_view);
+	g_return_if_fail (class->hovering_over_link != NULL);
+
+	class->hovering_over_link (web_view, title, uri);
+}
+
+static gboolean
+web_view_navigation_policy_decision_requested_cb (EWebView *web_view,
+                                                  WebKitWebFrame *frame,
+                                                  WebKitNetworkRequest *request,
+                                                  WebKitWebNavigationAction *navigation_action,
+                                                  WebKitWebPolicyDecision *policy_decision)
+{
+	EWebViewClass *class;
+	WebKitWebNavigationReason reason;
+	const gchar *uri;
+
+	reason = webkit_web_navigation_action_get_reason (navigation_action);
+	if (reason != WEBKIT_WEB_NAVIGATION_REASON_LINK_CLICKED)
+		return FALSE;
+
+	/* XXX WebKitWebView does not provide a class method for
+	 *     this signal, so we do so we can override the default
+	 *     behavior from subclasses for special URI types. */
+
+	class = E_WEB_VIEW_GET_CLASS (web_view);
+	g_return_val_if_fail (class->link_clicked != NULL, FALSE);
+
+	webkit_web_policy_decision_ignore (policy_decision);
+
+	uri = webkit_network_request_get_uri (request);
+
+	class->link_clicked (web_view, uri);
+
+	return TRUE;
+}
+
+static void
+web_view_load_status_changed_cb (WebKitWebView *webkit_web_view,
+                                 GParamSpec *pspec,
+                                 gpointer user_data)
+{
+	WebKitLoadStatus status;
+	EWebView *web_view;
+
+	status = webkit_web_view_get_load_status (webkit_web_view);
+	if (status != WEBKIT_LOAD_FINISHED)
+		return;
+
+	web_view = E_WEB_VIEW (webkit_web_view);
+	web_view_update_document_highlights (web_view);
+
+	/* Workaround webkit bug https://bugs.webkit.org/show_bug.cgi?id=89553 */
+	e_web_view_zoom_in (web_view);
+	e_web_view_zoom_out (web_view);
+}
+
+static void
+web_view_set_property (GObject *object,
+                       guint property_id,
+                       const GValue *value,
+                       GParamSpec *pspec)
+{
+	switch (property_id) {
+		case PROP_CARET_MODE:
+			e_web_view_set_caret_mode (
+				E_WEB_VIEW (object),
+				g_value_get_boolean (value));
+			return;
+
+		case PROP_CURSOR_IMAGE:
+			e_web_view_set_cursor_image (
+				E_WEB_VIEW (object),
+				g_value_get_object (value));
+			return;
+
+		case PROP_CURSOR_IMAGE_SRC:
+			e_web_view_set_cursor_image_src (
+				E_WEB_VIEW (object),
+				g_value_get_string (value));
+			return;
+
+		case PROP_DISABLE_PRINTING:
+			e_web_view_set_disable_printing (
+				E_WEB_VIEW (object),
+				g_value_get_boolean (value));
+			return;
+
+		case PROP_DISABLE_SAVE_TO_DISK:
+			e_web_view_set_disable_save_to_disk (
+				E_WEB_VIEW (object),
+				g_value_get_boolean (value));
+			return;
+
+		case PROP_INLINE_SPELLING:
+			e_web_view_set_inline_spelling (
+				E_WEB_VIEW (object),
+				g_value_get_boolean (value));
+			return;
+
+		case PROP_MAGIC_LINKS:
+			e_web_view_set_magic_links (
+				E_WEB_VIEW (object),
+				g_value_get_boolean (value));
+			return;
+
+		case PROP_MAGIC_SMILEYS:
+			e_web_view_set_magic_smileys (
+				E_WEB_VIEW (object),
+				g_value_get_boolean (value));
+			return;
+
+		case PROP_OPEN_PROXY:
+			e_web_view_set_open_proxy (
+				E_WEB_VIEW (object),
+				g_value_get_object (value));
+			return;
+
+		case PROP_PRINT_PROXY:
+			e_web_view_set_print_proxy (
+				E_WEB_VIEW (object),
+				g_value_get_object (value));
+			return;
+
+		case PROP_SAVE_AS_PROXY:
+			e_web_view_set_save_as_proxy (
+				E_WEB_VIEW (object),
+				g_value_get_object (value));
+			return;
+
+		case PROP_SELECTED_URI:
+			e_web_view_set_selected_uri (
+				E_WEB_VIEW (object),
+				g_value_get_string (value));
+			return;
+	}
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+web_view_get_property (GObject *object,
+                       guint property_id,
+                       GValue *value,
+                       GParamSpec *pspec)
+{
+	switch (property_id) {
+		case PROP_CARET_MODE:
+			g_value_set_boolean (
+				value, e_web_view_get_caret_mode (
+				E_WEB_VIEW (object)));
+			return;
+
+		case PROP_CURSOR_IMAGE:
+			g_value_set_object (
+				value, e_web_view_get_cursor_image (
+				E_WEB_VIEW (object)));
+			return;
+
+		case PROP_CURSOR_IMAGE_SRC:
+			g_value_set_string (
+				value, e_web_view_get_cursor_image_src (
+				E_WEB_VIEW (object)));
+			return;
+
+		case PROP_DISABLE_PRINTING:
+			g_value_set_boolean (
+				value, e_web_view_get_disable_printing (
+				E_WEB_VIEW (object)));
+			return;
+
+		case PROP_DISABLE_SAVE_TO_DISK:
+			g_value_set_boolean (
+				value, e_web_view_get_disable_save_to_disk (
+				E_WEB_VIEW (object)));
+			return;
+
+		case PROP_INLINE_SPELLING:
+			g_value_set_boolean (
+				value, e_web_view_get_inline_spelling (
+				E_WEB_VIEW (object)));
+			return;
+
+		case PROP_MAGIC_LINKS:
+			g_value_set_boolean (
+				value, e_web_view_get_magic_links (
+				E_WEB_VIEW (object)));
+			return;
+
+		case PROP_MAGIC_SMILEYS:
+			g_value_set_boolean (
+				value, e_web_view_get_magic_smileys (
+				E_WEB_VIEW (object)));
+			return;
+
+		case PROP_OPEN_PROXY:
+			g_value_set_object (
+				value, e_web_view_get_open_proxy (
+				E_WEB_VIEW (object)));
+			return;
+
+		case PROP_PRINT_PROXY:
+			g_value_set_object (
+				value, e_web_view_get_print_proxy (
+				E_WEB_VIEW (object)));
+			return;
+
+		case PROP_SAVE_AS_PROXY:
+			g_value_set_object (
+				value, e_web_view_get_save_as_proxy (
+				E_WEB_VIEW (object)));
+			return;
+
+		case PROP_SELECTED_URI:
+			g_value_set_string (
+				value, e_web_view_get_selected_uri (
+				E_WEB_VIEW (object)));
+			return;
+
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+web_view_dispose (GObject *object)
+{
+	EWebViewPrivate *priv;
+
+	priv = E_WEB_VIEW_GET_PRIVATE (object);
+
+	if (priv->ui_manager != NULL) {
+		g_object_unref (priv->ui_manager);
+		priv->ui_manager = NULL;
+	}
+
+	if (priv->open_proxy != NULL) {
+		g_object_unref (priv->open_proxy);
+		priv->open_proxy = NULL;
+	}
+
+	if (priv->print_proxy != NULL) {
+		g_object_unref (priv->print_proxy);
+		priv->print_proxy = NULL;
+	}
+
+	if (priv->save_as_proxy != NULL) {
+		g_object_unref (priv->save_as_proxy);
+		priv->save_as_proxy = NULL;
+	}
+
+	if (priv->cursor_image != NULL) {
+		g_object_unref (priv->cursor_image);
+		priv->cursor_image = NULL;
+	}
+
+	if (priv->cursor_image_src != NULL) {
+		g_free (priv->cursor_image_src);
+		priv->cursor_image_src = NULL;
+	}
+
+	if (priv->highlights != NULL) {
+		g_slist_free_full (priv->highlights, g_free);
+		priv->highlights = NULL;
+	}
+
+	if (priv->aliasing_settings != NULL) {
+		g_signal_handlers_disconnect_matched (
+			priv->aliasing_settings, G_SIGNAL_MATCH_DATA,
+			0, 0, NULL, NULL, object);
+		g_object_unref (priv->aliasing_settings);
+		priv->aliasing_settings = NULL;
+	}
+
+	if (priv->font_settings != NULL) {
+		g_signal_handlers_disconnect_matched (
+			priv->font_settings, G_SIGNAL_MATCH_DATA,
+			0, 0, NULL, NULL, object);
+		g_object_unref (priv->font_settings);
+		priv->font_settings = NULL;
+	}
+
+	/* Chain up to parent's dispose() method. */
+	G_OBJECT_CLASS (e_web_view_parent_class)->dispose (object);
+}
+
+static void
+web_view_finalize (GObject *object)
+{
+	EWebViewPrivate *priv;
+
+	priv = E_WEB_VIEW_GET_PRIVATE (object);
+
+	/* All URI requests should be complete or cancelled by now. */
+	if (priv->requests != NULL)
+		g_warning ("Finalizing EWebView with active URI requests");
+
+	g_free (priv->selected_uri);
+
+	/* Chain up to parent's finalize() method. */
+	G_OBJECT_CLASS (e_web_view_parent_class)->finalize (object);
+}
+
+static void
+web_view_constructed (GObject *object)
+{
+#ifndef G_OS_WIN32
+	GSettings *settings;
+
+	settings = g_settings_new ("org.gnome.desktop.lockdown");
+
+	g_settings_bind (
+		settings, "disable-printing",
+		object, "disable-printing",
+		G_SETTINGS_BIND_GET);
+
+	g_settings_bind (
+		settings, "disable-save-to-disk",
+		object, "disable-save-to-disk",
+		G_SETTINGS_BIND_GET);
+
+	g_object_unref (settings);
+#endif
+
+	e_extensible_load_extensions (E_EXTENSIBLE (object));
+
+	/* Chain up to parent's constructed() method. */
+	G_OBJECT_CLASS (e_web_view_parent_class)->constructed (object);
+}
+
+static gboolean
+web_view_context_menu_cb (WebKitWebView *webkit_web_view,
+                          GtkWidget *default_menu,
+                          WebKitHitTestResult *hit_test_result,
+                          gboolean triggered_with_keyboard)
+{
+	WebKitHitTestResultContext context;
+	EWebView *web_view;
+	gboolean event_handled = FALSE;
+	gchar *uri;
+
+	web_view = E_WEB_VIEW (webkit_web_view);
+
+	if (web_view->priv->cursor_image != NULL) {
+		g_object_unref (web_view->priv->cursor_image);
+		web_view->priv->cursor_image = NULL;
+	}
+
+	if (web_view->priv->cursor_image_src != NULL) {
+		g_free (web_view->priv->cursor_image_src);
+		web_view->priv->cursor_image_src = NULL;
+	}
+
+	if (hit_test_result == NULL)
+		return FALSE;
+
+	g_object_get (G_OBJECT (hit_test_result), "context", &context, NULL);
+
+	if (context & WEBKIT_HIT_TEST_RESULT_CONTEXT_IMAGE) {
+		WebKitWebDataSource *data_source;
+		WebKitWebFrame *frame;
+		GList *subresources, *res;
+
+		g_object_get (
+			G_OBJECT (hit_test_result), "image-uri", &uri, NULL);
+
+		if (uri == NULL)
+			return FALSE;
+
+		g_free (web_view->priv->cursor_image_src);
+		web_view->priv->cursor_image_src = uri;
+
+		/* Iterate through all resources of the loaded webpage and
+		 * try to find resource with URI matching cursor_image_src */
+		frame = webkit_web_view_get_main_frame (WEBKIT_WEB_VIEW (web_view));
+		data_source = webkit_web_frame_get_data_source (frame);
+		subresources = webkit_web_data_source_get_subresources (data_source);
+		for (res = subresources; res; res = res->next) {
+			WebKitWebResource *src = res->data;
+			GdkPixbufLoader *loader;
+			GString *data;
+
+			if (g_strcmp0 (webkit_web_resource_get_uri (src),
+				web_view->priv->cursor_image_src) != 0)
+				continue;
+
+			data = webkit_web_resource_get_data (src);
+			if (data == NULL)
+				break;
+
+			loader = gdk_pixbuf_loader_new ();
+			if (!gdk_pixbuf_loader_write (loader,
+				(guchar *) data->str, data->len, NULL)) {
+				g_object_unref (loader);
+				break;
+			}
+			gdk_pixbuf_loader_close (loader, NULL);
+
+			if (web_view->priv->cursor_image != NULL)
+				g_object_unref (web_view->priv->cursor_image);
+
+			web_view->priv->cursor_image =
+				g_object_ref (gdk_pixbuf_loader_get_animation (loader));
+
+			g_object_unref (loader);
+			break;
+		}
+		g_list_free (subresources);
+	}
+
+	g_object_get (hit_test_result, "link-uri", &uri, NULL);
+
+	if (!(context & WEBKIT_HIT_TEST_RESULT_CONTEXT_LINK)) {
+		g_free (uri);
+		uri = NULL;
+	}
+
+	g_signal_emit (web_view, signals[POPUP_EVENT], 0, uri, &event_handled);
+
+	g_free (uri);
+
+	return event_handled;
+}
+
+static gboolean
+web_view_scroll_event (GtkWidget *widget,
+                       GdkEventScroll *event)
+{
+	if (event->state & GDK_CONTROL_MASK) {
+		GdkScrollDirection direction = event->direction;
+
+		if (direction == GDK_SCROLL_SMOOTH) {
+			static gdouble total_delta_y = 0.0;
+
+			total_delta_y += event->delta_y;
+
+			if (total_delta_y >= 1.0) {
+				total_delta_y = 0.0;
+				direction = GDK_SCROLL_DOWN;
+			} else if (total_delta_y <= -1.0) {
+				total_delta_y = 0.0;
+				direction = GDK_SCROLL_UP;
+			} else {
+				return FALSE;
+			}
+		}
+
+		switch (direction) {
+			case GDK_SCROLL_UP:
+				e_web_view_zoom_in (E_WEB_VIEW (widget));
+				return TRUE;
+			case GDK_SCROLL_DOWN:
+				e_web_view_zoom_out (E_WEB_VIEW (widget));
+				return TRUE;
+			default:
+				break;
+		}
+	}
+
+	return FALSE;
+}
+
+static GtkWidget *
+web_view_create_plugin_widget (EWebView *web_view,
+                               const gchar *mime_type,
+                               const gchar *uri,
+                               GHashTable *param)
+{
+	GtkWidget *widget = NULL;
+
+	if (g_strcmp0 (mime_type, "image/x-themed-icon") == 0) {
+		GtkIconTheme *icon_theme;
+		GdkPixbuf *pixbuf;
+		gpointer data;
+		glong size = 0;
+		GError *error = NULL;
+
+		icon_theme = gtk_icon_theme_get_default ();
+
+		if (size == 0) {
+			data = g_hash_table_lookup (param, "width");
+			if (data != NULL)
+				size = MAX (size, strtol (data, NULL, 10));
+		}
+
+		if (size == 0) {
+			data = g_hash_table_lookup (param, "height");
+			if (data != NULL)
+				size = MAX (size, strtol (data, NULL, 10));
+		}
+
+		if (size == 0)
+			size = 32;  /* arbitrary default */
+
+		pixbuf = gtk_icon_theme_load_icon (
+			icon_theme, uri, size, 0, &error);
+		if (pixbuf != NULL) {
+			widget = gtk_image_new_from_pixbuf (pixbuf);
+			g_object_unref (pixbuf);
+		} else if (error != NULL) {
+			g_warning ("%s", error->message);
+			g_error_free (error);
+		}
+	}
+
+	return widget;
+}
+
+static gchar *
+web_view_extract_uri (EWebView *web_view,
+                      GdkEventButton *event)
+{
+	WebKitHitTestResult *result;
+	WebKitHitTestResultContext context;
+	gchar *uri = NULL;
+
+	result = webkit_web_view_get_hit_test_result (
+		WEBKIT_WEB_VIEW (web_view), event);
+
+	g_object_get (result, "context", &context, "link-uri", &uri, NULL);
+	g_object_unref (result);
+
+	if (context & WEBKIT_HIT_TEST_RESULT_CONTEXT_LINK)
+		return uri;
+
+	g_free (uri);
+
+	return NULL;
+}
+
+static void
+web_view_hovering_over_link (EWebView *web_view,
+                             const gchar *title,
+                             const gchar *uri)
+{
+	CamelInternetAddress *address;
+	CamelURL *curl;
+	const gchar *format = NULL;
+	gchar *message = NULL;
+	gchar *who;
+
+	if (uri == NULL || *uri == '\0')
+		goto exit;
+
+	if (g_str_has_prefix (uri, "mailto:";))
+		format = _("Click to mail %s");
+	else if (g_str_has_prefix (uri, "callto:"))
+		format = _("Click to call %s");
+	else if (g_str_has_prefix (uri, "h323:"))
+		format = _("Click to call %s");
+	else if (g_str_has_prefix (uri, "sip:"))
+		format = _("Click to call %s");
+	else if (g_str_has_prefix (uri, "##"))
+		message = g_strdup (_("Click to hide/unhide addresses"));
+	else
+		message = g_strdup_printf (_("Click to open %s"), uri);
+
+	if (format == NULL)
+		goto exit;
+
+	/* XXX Use something other than Camel here.  Surely
+	 *     there's other APIs around that can do this. */
+	curl = camel_url_new (uri, NULL);
+	address = camel_internet_address_new ();
+	camel_address_decode (CAMEL_ADDRESS (address), curl->path);
+	who = camel_address_format (CAMEL_ADDRESS (address));
+	g_object_unref (address);
+	camel_url_free (curl);
+
+	if (who == NULL)
+		who = g_strdup (strchr (uri, ':') + 1);
+
+	message = g_strdup_printf (format, who);
+
+	g_free (who);
+
+exit:
+	e_web_view_status_message (web_view, message);
+
+	g_free (message);
+}
+
+static void
+web_view_link_clicked (EWebView *web_view,
+                       const gchar *uri)
+{
+	gpointer parent;
+
+	if (uri && g_ascii_strncasecmp (uri, "mailto:";, 7) == 0) {
+		gboolean handled = FALSE;
+
+		g_signal_emit (
+			web_view, signals[PROCESS_MAILTO], 0, uri, &handled);
+
+		if (handled)
+			return;
+	}
+
+	parent = gtk_widget_get_toplevel (GTK_WIDGET (web_view));
+	parent = gtk_widget_is_toplevel (parent) ? parent : NULL;
+
+	e_show_uri (parent, uri);
+}
+
+static void
+web_view_load_string (EWebView *web_view,
+                      const gchar *string)
+{
+	if (string == NULL)
+		string = "";
+
+	webkit_web_view_load_string (
+		WEBKIT_WEB_VIEW (web_view),
+		string, "text/html", "UTF-8", "evo-file:///");
+}
+
+static void
+web_view_load_uri (EWebView *web_view,
+                   const gchar *uri)
+{
+	if (uri == NULL)
+		uri = "about:blank";
+
+	webkit_web_view_load_uri (WEBKIT_WEB_VIEW (web_view), uri);
+}
+
+static void
+web_view_frame_load_string (EWebView *web_view,
+                            const gchar *frame_name,
+                            const gchar *string)
+{
+	WebKitWebFrame *main_frame;
+
+	if (string == NULL)
+		string = "";
+
+	main_frame = webkit_web_view_get_main_frame (WEBKIT_WEB_VIEW (web_view));
+	if (main_frame != NULL) {
+		WebKitWebFrame *frame;
+
+		frame = webkit_web_frame_find_frame (main_frame, frame_name);
+
+		if (frame != NULL)
+			webkit_web_frame_load_string (
+				frame, string, "text/html",
+				"UTF-8", "evo-file:///");
+	}
+}
+
+static void
+web_view_frame_load_uri (EWebView *web_view,
+                         const gchar *frame_name,
+                         const gchar *uri)
+{
+	WebKitWebFrame *main_frame;
+
+	if (uri == NULL)
+		uri = "about:blank";
+
+	main_frame = webkit_web_view_get_main_frame (WEBKIT_WEB_VIEW (web_view));
+	if (main_frame != NULL) {
+		WebKitWebFrame *frame;
+
+		frame = webkit_web_frame_find_frame (main_frame, frame_name);
+
+		if (frame != NULL)
+			webkit_web_frame_load_uri (frame, uri);
+	}
+}
+
+static gboolean
+web_view_popup_event (EWebView *web_view,
+                      const gchar *uri)
+{
+	e_web_view_set_selected_uri (web_view, uri);
+	e_web_view_show_popup_menu (web_view);
+
+	return TRUE;
+}
+
+static void
+web_view_stop_loading (EWebView *web_view)
+{
+	webkit_web_view_stop_loading (WEBKIT_WEB_VIEW (web_view));
+}
+
+static void
+web_view_update_actions (EWebView *web_view)
+{
+	GtkActionGroup *action_group;
+	gboolean can_copy;
+	gboolean scheme_is_http = FALSE;
+	gboolean scheme_is_mailto = FALSE;
+	gboolean uri_is_valid = FALSE;
+	gboolean has_cursor_image;
+	gboolean visible;
+	const gchar *group_name;
+	const gchar *uri;
+
+	uri = e_web_view_get_selected_uri (web_view);
+	can_copy = webkit_web_view_can_copy_clipboard (WEBKIT_WEB_VIEW (web_view));
+	has_cursor_image = e_web_view_get_cursor_image_src (web_view) ||
+			   e_web_view_get_cursor_image (web_view);
+
+	/* Parse the URI early so we know if the actions will work. */
+	if (uri != NULL) {
+		CamelURL *curl;
+
+		curl = camel_url_new (uri, NULL);
+		uri_is_valid = (curl != NULL);
+		camel_url_free (curl);
+
+		scheme_is_http =
+			(g_ascii_strncasecmp (uri, "http:", 5) == 0) ||
+			(g_ascii_strncasecmp (uri, "https:", 6) == 0);
+
+		scheme_is_mailto =
+			(g_ascii_strncasecmp (uri, "mailto:";, 7) == 0);
+	}
+
+	/* Allow copying the URI even if it's malformed. */
+	group_name = "uri";
+	visible = (uri != NULL) && !scheme_is_mailto;
+	action_group = e_web_view_get_action_group (web_view, group_name);
+	gtk_action_group_set_visible (action_group, visible);
+
+	group_name = "http";
+	visible = uri_is_valid && scheme_is_http;
+	action_group = e_web_view_get_action_group (web_view, group_name);
+	gtk_action_group_set_visible (action_group, visible);
+
+	group_name = "mailto";
+	visible = uri_is_valid && scheme_is_mailto;
+	action_group = e_web_view_get_action_group (web_view, group_name);
+	gtk_action_group_set_visible (action_group, visible);
+
+	group_name = "image";
+	visible = has_cursor_image;
+	action_group = e_web_view_get_action_group (web_view, group_name);
+	gtk_action_group_set_visible (action_group, visible);
+
+	group_name = "selection";
+	visible = can_copy;
+	action_group = e_web_view_get_action_group (web_view, group_name);
+	gtk_action_group_set_visible (action_group, visible);
+
+	group_name = "standard";
+	visible = (uri == NULL);
+	action_group = e_web_view_get_action_group (web_view, group_name);
+	gtk_action_group_set_visible (action_group, visible);
+
+	group_name = "lockdown-printing";
+	visible = (uri == NULL) && !web_view->priv->disable_printing;
+	action_group = e_web_view_get_action_group (web_view, group_name);
+	gtk_action_group_set_visible (action_group, visible);
+
+	group_name = "lockdown-save-to-disk";
+	visible = (uri == NULL) && !web_view->priv->disable_save_to_disk;
+	action_group = e_web_view_get_action_group (web_view, group_name);
+	gtk_action_group_set_visible (action_group, visible);
+}
+
+static void
+web_view_submit_alert (EAlertSink *alert_sink,
+                       EAlert *alert)
+{
+	EWebView *web_view;
+	GtkWidget *dialog;
+	GString *buffer;
+	const gchar *icon_name = NULL;
+	gpointer parent;
+
+	web_view = E_WEB_VIEW (alert_sink);
+
+	parent = gtk_widget_get_toplevel (GTK_WIDGET (web_view));
+	parent = gtk_widget_is_toplevel (parent) ? parent : NULL;
+
+	switch (e_alert_get_message_type (alert)) {
+		case GTK_MESSAGE_INFO:
+			icon_name = GTK_STOCK_DIALOG_INFO;
+			break;
+
+		case GTK_MESSAGE_WARNING:
+			icon_name = GTK_STOCK_DIALOG_WARNING;
+			break;
+
+		case GTK_MESSAGE_ERROR:
+			icon_name = GTK_STOCK_DIALOG_ERROR;
+			break;
+
+		default:
+			dialog = e_alert_dialog_new (parent, alert);
+			gtk_dialog_run (GTK_DIALOG (dialog));
+			gtk_widget_destroy (dialog);
+			return;
+	}
+
+	buffer = g_string_sized_new (512);
+
+	g_string_append (
+		buffer,
+		"<html>"
+		"<head>"
+		"<meta http-equiv=\"content-type\""
+		" content=\"text/html; charset=utf-8\">"
+		"</head>"
+		"<body>");
+
+	g_string_append (
+		buffer,
+		"<table bgcolor='#000000' width='100%'"
+		" cellpadding='1' cellspacing='0'>"
+		"<tr>"
+		"<td>"
+		"<table bgcolor='#dddddd' width='100%' cellpadding='6'>"
+		"<tr>");
+
+	g_string_append_printf (
+		buffer,
+		"<tr>"
+		"<td valign='top'>"
+		"<img src='gtk-stock://%s/?size=%d'/>"
+		"</td>"
+		"<td align='left' width='100%%'>"
+		"<h3>%s</h3>"
+		"%s"
+		"</td>"
+		"</tr>",
+		icon_name,
+		GTK_ICON_SIZE_DIALOG,
+		e_alert_get_primary_text (alert),
+		e_alert_get_secondary_text (alert));
+
+	g_string_append (
+		buffer,
+		"</table>"
+		"</td>"
+		"</tr>"
+		"</table>"
+		"</body>"
+		"</html>");
+
+	e_web_view_load_string (web_view, buffer->str);
+
+	g_string_free (buffer, TRUE);
+}
+
+static void
+web_view_selectable_update_actions (ESelectable *selectable,
+                                    EFocusTracker *focus_tracker,
+                                    GdkAtom *clipboard_targets,
+                                    gint n_clipboard_targets)
+{
+	WebKitWebView *web_view;
+	GtkAction *action;
+	gboolean sensitive;
+	const gchar *tooltip;
+
+	web_view = WEBKIT_WEB_VIEW (selectable);
+
+	action = e_focus_tracker_get_cut_clipboard_action (focus_tracker);
+	sensitive = webkit_web_view_can_cut_clipboard (web_view);
+	tooltip = _("Cut the selection");
+	gtk_action_set_sensitive (action, sensitive);
+	gtk_action_set_tooltip (action, tooltip);
+
+	action = e_focus_tracker_get_copy_clipboard_action (focus_tracker);
+	sensitive = webkit_web_view_can_copy_clipboard (web_view);
+	tooltip = _("Copy the selection");
+	gtk_action_set_sensitive (action, sensitive);
+	gtk_action_set_tooltip (action, tooltip);
+
+	action = e_focus_tracker_get_paste_clipboard_action (focus_tracker);
+	sensitive = webkit_web_view_can_paste_clipboard (web_view);
+	tooltip = _("Paste the clipboard");
+	gtk_action_set_sensitive (action, sensitive);
+	gtk_action_set_tooltip (action, tooltip);
+
+	action = e_focus_tracker_get_select_all_action (focus_tracker);
+	sensitive = TRUE;
+	tooltip = _("Select all text and images");
+	gtk_action_set_sensitive (action, sensitive);
+	gtk_action_set_tooltip (action, tooltip);
+}
+
+static void
+web_view_selectable_cut_clipboard (ESelectable *selectable)
+{
+	e_web_view_cut_clipboard (E_WEB_VIEW (selectable));
+}
+
+static void
+web_view_selectable_copy_clipboard (ESelectable *selectable)
+{
+	e_web_view_copy_clipboard (E_WEB_VIEW (selectable));
+}
+
+static void
+web_view_selectable_paste_clipboard (ESelectable *selectable)
+{
+	e_web_view_paste_clipboard (E_WEB_VIEW (selectable));
+}
+
+static void
+web_view_selectable_select_all (ESelectable *selectable)
+{
+	e_web_view_select_all (E_WEB_VIEW (selectable));
+}
+
+static gboolean
+web_view_drag_motion (GtkWidget *widget,
+                      GdkDragContext *context,
+                      gint x,
+                      gint y,
+                      guint time_)
+{
+	return FALSE;
+}
+
+static void
+e_web_view_class_init (EWebViewClass *class)
+{
+	GObjectClass *object_class;
+	GtkWidgetClass *widget_class;
+
+	g_type_class_add_private (class, sizeof (EWebViewPrivate));
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->set_property = web_view_set_property;
+	object_class->get_property = web_view_get_property;
+	object_class->dispose = web_view_dispose;
+	object_class->finalize = web_view_finalize;
+	object_class->constructed = web_view_constructed;
+
+	widget_class = GTK_WIDGET_CLASS (class);
+	widget_class->scroll_event = web_view_scroll_event;
+	widget_class->drag_motion = web_view_drag_motion;
+
+#if 0  /* WEBKIT */
+	html_class = GTK_HTML_CLASS (class);
+	html_class->url_requested = web_view_url_requested;
+#endif
+
+	class->create_plugin_widget = web_view_create_plugin_widget;
+	class->extract_uri = web_view_extract_uri;
+	class->hovering_over_link = web_view_hovering_over_link;
+	class->link_clicked = web_view_link_clicked;
+	class->load_string = web_view_load_string;
+	class->load_uri = web_view_load_uri;
+	class->frame_load_string = web_view_frame_load_string;
+	class->frame_load_uri = web_view_frame_load_uri;
+	class->popup_event = web_view_popup_event;
+	class->stop_loading = web_view_stop_loading;
+	class->update_actions = web_view_update_actions;
+
+	g_object_class_install_property (
+		object_class,
+		PROP_CARET_MODE,
+		g_param_spec_boolean (
+			"caret-mode",
+			"Caret Mode",
+			NULL,
+			FALSE,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_CURSOR_IMAGE,
+		g_param_spec_object (
+			"cursor-image",
+			"Image animation at the mouse cursor",
+			NULL,
+			GDK_TYPE_PIXBUF_ANIMATION,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_CURSOR_IMAGE_SRC,
+		g_param_spec_string (
+			"cursor-image-src",
+			"Image source uri at the mouse cursor",
+			NULL,
+			NULL,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_DISABLE_PRINTING,
+		g_param_spec_boolean (
+			"disable-printing",
+			"Disable Printing",
+			NULL,
+			FALSE,
+			G_PARAM_READWRITE |
+			G_PARAM_CONSTRUCT));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_DISABLE_SAVE_TO_DISK,
+		g_param_spec_boolean (
+			"disable-save-to-disk",
+			"Disable Save-to-Disk",
+			NULL,
+			FALSE,
+			G_PARAM_READWRITE |
+			G_PARAM_CONSTRUCT));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_INLINE_SPELLING,
+		g_param_spec_boolean (
+			"inline-spelling",
+			"Inline Spelling",
+			NULL,
+			FALSE,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_MAGIC_LINKS,
+		g_param_spec_boolean (
+			"magic-links",
+			"Magic Links",
+			NULL,
+			FALSE,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_MAGIC_SMILEYS,
+		g_param_spec_boolean (
+			"magic-smileys",
+			"Magic Smileys",
+			NULL,
+			FALSE,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_OPEN_PROXY,
+		g_param_spec_object (
+			"open-proxy",
+			"Open Proxy",
+			NULL,
+			GTK_TYPE_ACTION,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_PRINT_PROXY,
+		g_param_spec_object (
+			"print-proxy",
+			"Print Proxy",
+			NULL,
+			GTK_TYPE_ACTION,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_SAVE_AS_PROXY,
+		g_param_spec_object (
+			"save-as-proxy",
+			"Save As Proxy",
+			NULL,
+			GTK_TYPE_ACTION,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_SELECTED_URI,
+		g_param_spec_string (
+			"selected-uri",
+			"Selected URI",
+			NULL,
+			NULL,
+			G_PARAM_READWRITE));
+
+	signals[POPUP_EVENT] = g_signal_new (
+		"popup-event",
+		G_TYPE_FROM_CLASS (class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (EWebViewClass, popup_event),
+		g_signal_accumulator_true_handled, NULL,
+		e_marshal_BOOLEAN__STRING,
+		G_TYPE_BOOLEAN, 1, G_TYPE_STRING);
+
+	signals[STATUS_MESSAGE] = g_signal_new (
+		"status-message",
+		G_TYPE_FROM_CLASS (class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (EWebViewClass, status_message),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__STRING,
+		G_TYPE_NONE, 1,
+		G_TYPE_STRING);
+
+	signals[STOP_LOADING] = g_signal_new (
+		"stop-loading",
+		G_TYPE_FROM_CLASS (class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (EWebViewClass, stop_loading),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__VOID,
+		G_TYPE_NONE, 0);
+
+	signals[UPDATE_ACTIONS] = g_signal_new (
+		"update-actions",
+		G_TYPE_FROM_CLASS (class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (EWebViewClass, update_actions),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__VOID,
+		G_TYPE_NONE, 0);
+
+	/* return TRUE when a signal handler processed the mailto URI */
+	signals[PROCESS_MAILTO] = g_signal_new (
+		"process-mailto",
+		G_TYPE_FROM_CLASS (class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (EWebViewClass, process_mailto),
+		NULL, NULL,
+		e_marshal_BOOLEAN__STRING,
+		G_TYPE_BOOLEAN, 1, G_TYPE_STRING);
+}
+
+static void
+e_web_view_alert_sink_init (EAlertSinkInterface *interface)
+{
+	interface->submit_alert = web_view_submit_alert;
+}
+
+static void
+e_web_view_selectable_init (ESelectableInterface *interface)
+{
+	interface->update_actions = web_view_selectable_update_actions;
+	interface->cut_clipboard = web_view_selectable_cut_clipboard;
+	interface->copy_clipboard = web_view_selectable_copy_clipboard;
+	interface->paste_clipboard = web_view_selectable_paste_clipboard;
+	interface->select_all = web_view_selectable_select_all;
+}
+
+static void
+e_web_view_init (EWebView *web_view)
+{
+	GtkUIManager *ui_manager;
+	GtkActionGroup *action_group;
+	EPopupAction *popup_action;
+	WebKitWebSettings *web_settings;
+	GSettingsSchema *settings_schema;
+	GSettings *settings;
+	const gchar *domain = GETTEXT_PACKAGE;
+	const gchar *id;
+	GError *error = NULL;
+
+	web_view->priv = E_WEB_VIEW_GET_PRIVATE (web_view);
+
+	web_view->priv->highlights = NULL;
+
+	g_signal_connect (
+		web_view, "create-plugin-widget",
+		G_CALLBACK (web_view_create_plugin_widget_cb), NULL);
+
+	g_signal_connect (
+		web_view, "hovering-over-link",
+		G_CALLBACK (web_view_hovering_over_link_cb), NULL);
+
+	g_signal_connect (
+		web_view, "navigation-policy-decision-requested",
+		G_CALLBACK (web_view_navigation_policy_decision_requested_cb),
+		NULL);
+
+	g_signal_connect (
+		web_view, "new-window-policy-decision-requested",
+		G_CALLBACK (web_view_navigation_policy_decision_requested_cb),
+		NULL);
+
+	g_signal_connect (
+		web_view, "context-menu",
+		G_CALLBACK (web_view_context_menu_cb), NULL);
+
+	g_signal_connect (
+		web_view, "notify::load-status",
+		G_CALLBACK (web_view_load_status_changed_cb), NULL);
+
+	ui_manager = gtk_ui_manager_new ();
+	web_view->priv->ui_manager = ui_manager;
+
+	g_signal_connect_swapped (
+		ui_manager, "connect-proxy",
+		G_CALLBACK (web_view_connect_proxy_cb), web_view);
+
+	web_settings = e_web_view_get_default_settings ();
+	e_web_view_set_settings (web_view, web_settings);
+	g_object_unref (web_settings);
+
+	e_web_view_install_request_handler (web_view, E_TYPE_FILE_REQUEST);
+	e_web_view_install_request_handler (web_view, E_TYPE_STOCK_REQUEST);
+
+	settings = g_settings_new ("org.gnome.desktop.interface");
+	g_signal_connect_swapped (
+		settings, "changed::font-name",
+		G_CALLBACK (e_web_view_update_fonts), web_view);
+	g_signal_connect_swapped (
+		settings, "changed::monospace-font-name",
+		G_CALLBACK (e_web_view_update_fonts), web_view);
+	web_view->priv->font_settings = settings;
+
+	/* This schema is optional.  Use if available. */
+	id = "org.gnome.settings-daemon.plugins.xsettings";
+	settings_schema = g_settings_schema_source_lookup (
+		g_settings_schema_source_get_default (), id, FALSE);
+	if (settings_schema != NULL) {
+		settings = g_settings_new (id);
+		g_signal_connect_swapped (
+			settings, "changed::antialiasing",
+			G_CALLBACK (e_web_view_update_fonts), web_view);
+		web_view->priv->aliasing_settings = settings;
+	}
+
+	e_web_view_update_fonts (web_view);
+
+	action_group = gtk_action_group_new ("uri");
+	gtk_action_group_set_translation_domain (action_group, domain);
+	gtk_ui_manager_insert_action_group (ui_manager, action_group, 0);
+	g_object_unref (action_group);
+
+	gtk_action_group_add_actions (
+		action_group, uri_entries,
+		G_N_ELEMENTS (uri_entries), web_view);
+
+	action_group = gtk_action_group_new ("http");
+	gtk_action_group_set_translation_domain (action_group, domain);
+	gtk_ui_manager_insert_action_group (ui_manager, action_group, 0);
+	g_object_unref (action_group);
+
+	gtk_action_group_add_actions (
+		action_group, http_entries,
+		G_N_ELEMENTS (http_entries), web_view);
+
+	action_group = gtk_action_group_new ("mailto");
+	gtk_action_group_set_translation_domain (action_group, domain);
+	gtk_ui_manager_insert_action_group (ui_manager, action_group, 0);
+	g_object_unref (action_group);
+
+	gtk_action_group_add_actions (
+		action_group, mailto_entries,
+		G_N_ELEMENTS (mailto_entries), web_view);
+
+	action_group = gtk_action_group_new ("image");
+	gtk_action_group_set_translation_domain (action_group, domain);
+	gtk_ui_manager_insert_action_group (ui_manager, action_group, 0);
+	g_object_unref (action_group);
+
+	gtk_action_group_add_actions (
+		action_group, image_entries,
+		G_N_ELEMENTS (image_entries), web_view);
+
+	action_group = gtk_action_group_new ("selection");
+	gtk_action_group_set_translation_domain (action_group, domain);
+	gtk_ui_manager_insert_action_group (ui_manager, action_group, 0);
+	g_object_unref (action_group);
+
+	gtk_action_group_add_actions (
+		action_group, selection_entries,
+		G_N_ELEMENTS (selection_entries), web_view);
+
+	action_group = gtk_action_group_new ("standard");
+	gtk_action_group_set_translation_domain (action_group, domain);
+	gtk_ui_manager_insert_action_group (ui_manager, action_group, 0);
+	g_object_unref (action_group);
+
+	gtk_action_group_add_actions (
+		action_group, standard_entries,
+		G_N_ELEMENTS (standard_entries), web_view);
+
+	popup_action = e_popup_action_new ("open");
+	gtk_action_group_add_action (action_group, GTK_ACTION (popup_action));
+	g_object_unref (popup_action);
+
+	g_object_bind_property (
+		web_view, "open-proxy",
+		popup_action, "related-action",
+		G_BINDING_BIDIRECTIONAL |
+		G_BINDING_SYNC_CREATE);
+
+	/* Support lockdown. */
+
+	action_group = gtk_action_group_new ("lockdown-printing");
+	gtk_action_group_set_translation_domain (action_group, domain);
+	gtk_ui_manager_insert_action_group (ui_manager, action_group, 0);
+	g_object_unref (action_group);
+
+	popup_action = e_popup_action_new ("print");
+	gtk_action_group_add_action (action_group, GTK_ACTION (popup_action));
+	g_object_unref (popup_action);
+
+	g_object_bind_property (
+		web_view, "print-proxy",
+		popup_action, "related-action",
+		G_BINDING_BIDIRECTIONAL |
+		G_BINDING_SYNC_CREATE);
+
+	action_group = gtk_action_group_new ("lockdown-save-to-disk");
+	gtk_action_group_set_translation_domain (action_group, domain);
+	gtk_ui_manager_insert_action_group (ui_manager, action_group, 0);
+	g_object_unref (action_group);
+
+	popup_action = e_popup_action_new ("save-as");
+	gtk_action_group_add_action (action_group, GTK_ACTION (popup_action));
+	g_object_unref (popup_action);
+
+	g_object_bind_property (
+		web_view, "save-as-proxy",
+		popup_action, "related-action",
+		G_BINDING_BIDIRECTIONAL |
+		G_BINDING_SYNC_CREATE);
+
+	/* Because we are loading from a hard-coded string, there is
+	 * no chance of I/O errors.  Failure here implies a malformed
+	 * UI definition.  Full stop. */
+	gtk_ui_manager_add_ui_from_string (ui_manager, ui, -1, &error);
+	if (error != NULL)
+		g_error ("%s", error->message);
+
+	id = "org.gnome.evolution.webview";
+	e_plugin_ui_register_manager (ui_manager, id, web_view);
+	e_plugin_ui_enable_manager (ui_manager, id);
+}
+
+GtkWidget *
+e_web_view_new (void)
+{
+	return g_object_new (E_TYPE_WEB_VIEW, NULL);
+}
+
+void
+e_web_view_clear (EWebView *web_view)
+{
+	GtkStyle *style;
+	gchar *html;
+
+	g_return_if_fail (E_IS_WEB_VIEW (web_view));
+
+	style = gtk_widget_get_style (GTK_WIDGET (web_view));
+
+	html = g_strdup_printf (
+		"<html><head></head><body bgcolor=\"#%06x\"></body></html>",
+		e_color_to_value (&style->base[GTK_STATE_NORMAL]));
+
+	webkit_web_view_load_html_string (
+		WEBKIT_WEB_VIEW (web_view), html, NULL);
+
+	g_free (html);
+}
+
+void
+e_web_view_load_string (EWebView *web_view,
+                        const gchar *string)
+{
+	EWebViewClass *class;
+
+	g_return_if_fail (E_IS_WEB_VIEW (web_view));
+
+	class = E_WEB_VIEW_GET_CLASS (web_view);
+	g_return_if_fail (class->load_string != NULL);
+
+	class->load_string (web_view, string);
+}
+
+void
+e_web_view_load_uri (EWebView *web_view,
+                     const gchar *uri)
+{
+	EWebViewClass *class;
+
+	g_return_if_fail (E_IS_WEB_VIEW (web_view));
+
+	class = E_WEB_VIEW_GET_CLASS (web_view);
+	g_return_if_fail (class->load_uri != NULL);
+
+	class->load_uri (web_view, uri);
+}
+
+void
+e_web_view_reload (EWebView *web_view)
+{
+	g_return_if_fail (E_IS_WEB_VIEW (web_view));
+
+	webkit_web_view_reload (WEBKIT_WEB_VIEW (web_view));
+}
+
+const gchar *
+e_web_view_get_uri (EWebView *web_view)
+{
+	g_return_val_if_fail (E_IS_WEB_VIEW (web_view), NULL);
+
+	return webkit_web_view_get_uri (WEBKIT_WEB_VIEW (web_view));
+}
+
+void
+e_web_view_frame_load_string (EWebView *web_view,
+                              const gchar *frame_name,
+                              const gchar *string)
+{
+	EWebViewClass *class;
+
+	g_return_if_fail (E_IS_WEB_VIEW (web_view));
+	g_return_if_fail (frame_name != NULL);
+
+	class = E_WEB_VIEW_GET_CLASS (web_view);
+	g_return_if_fail (class->frame_load_string != NULL);
+
+	class->frame_load_string (web_view, frame_name, string);
+}
+
+void
+e_web_view_frame_load_uri (EWebView *web_view,
+                           const gchar *frame_name,
+                           const gchar *uri)
+{
+	EWebViewClass *class;
+
+	g_return_if_fail (E_IS_WEB_VIEW (web_view));
+	g_return_if_fail (frame_name != NULL);
+
+	class = E_WEB_VIEW_GET_CLASS (web_view);
+	g_return_if_fail (class->frame_load_uri != NULL);
+
+	class->frame_load_uri (web_view, frame_name, uri);
+}
+
+const gchar *
+e_web_view_frame_get_uri (EWebView *web_view,
+                          const gchar *frame_name)
+{
+	WebKitWebFrame *main_frame;
+
+	g_return_val_if_fail (E_IS_WEB_VIEW (web_view), NULL);
+	g_return_val_if_fail (frame_name != NULL, NULL);
+
+	main_frame = webkit_web_view_get_main_frame (WEBKIT_WEB_VIEW (web_view));
+	if (main_frame != NULL) {
+		WebKitWebFrame *frame;
+
+		frame = webkit_web_frame_find_frame (main_frame, frame_name);
+
+		if (frame != NULL)
+			return webkit_web_frame_get_uri (frame);
+	}
+
+	return NULL;
+}
+
+gchar *
+e_web_view_get_html (EWebView *web_view)
+{
+	WebKitDOMDocument *document;
+	WebKitDOMElement *element;
+
+	g_return_val_if_fail (E_IS_WEB_VIEW (web_view), NULL);
+
+	document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (web_view));
+	element = webkit_dom_document_get_document_element (document);
+
+	return webkit_dom_html_element_get_outer_html (
+		WEBKIT_DOM_HTML_ELEMENT (element));
+}
+
+gboolean
+e_web_view_get_caret_mode (EWebView *web_view)
+{
+	g_return_val_if_fail (E_IS_WEB_VIEW (web_view), FALSE);
+
+	return web_view->priv->caret_mode;
+}
+
+void
+e_web_view_set_caret_mode (EWebView *web_view,
+                           gboolean caret_mode)
+{
+	g_return_if_fail (E_IS_WEB_VIEW (web_view));
+
+	if (web_view->priv->caret_mode == caret_mode)
+		return;
+
+	web_view->priv->caret_mode = caret_mode;
+
+	g_object_notify (G_OBJECT (web_view), "caret-mode");
+}
+
+GtkTargetList *
+e_web_view_get_copy_target_list (EWebView *web_view)
+{
+	g_return_val_if_fail (E_IS_WEB_VIEW (web_view), NULL);
+
+	return webkit_web_view_get_copy_target_list (
+		WEBKIT_WEB_VIEW (web_view));
+}
+
+gboolean
+e_web_view_get_disable_printing (EWebView *web_view)
+{
+	g_return_val_if_fail (E_IS_WEB_VIEW (web_view), FALSE);
+
+	return web_view->priv->disable_printing;
+}
+
+void
+e_web_view_set_disable_printing (EWebView *web_view,
+                                 gboolean disable_printing)
+{
+	g_return_if_fail (E_IS_WEB_VIEW (web_view));
+
+	if (web_view->priv->disable_printing == disable_printing)
+		return;
+
+	web_view->priv->disable_printing = disable_printing;
+
+	g_object_notify (G_OBJECT (web_view), "disable-printing");
+}
+
+gboolean
+e_web_view_get_disable_save_to_disk (EWebView *web_view)
+{
+	g_return_val_if_fail (E_IS_WEB_VIEW (web_view), FALSE);
+
+	return web_view->priv->disable_save_to_disk;
+}
+
+void
+e_web_view_set_disable_save_to_disk (EWebView *web_view,
+                                     gboolean disable_save_to_disk)
+{
+	g_return_if_fail (E_IS_WEB_VIEW (web_view));
+
+	if (web_view->priv->disable_save_to_disk == disable_save_to_disk)
+		return;
+
+	web_view->priv->disable_save_to_disk = disable_save_to_disk;
+
+	g_object_notify (G_OBJECT (web_view), "disable-save-to-disk");
+}
+
+gboolean
+e_web_view_get_enable_frame_flattening (EWebView *web_view)
+{
+	WebKitWebSettings *settings;
+	gboolean flattening;
+
+	/* Return TRUE with fail since it's default value we set in _init(). */
+	g_return_val_if_fail (E_IS_WEB_VIEW (web_view), TRUE);
+
+	settings = webkit_web_view_get_settings (WEBKIT_WEB_VIEW (web_view));
+	g_return_val_if_fail (settings != NULL, TRUE);
+
+	g_object_get (
+		G_OBJECT (settings),
+		"enable-frame-flattening", &flattening, NULL);
+
+	return flattening;
+}
+
+void
+e_web_view_set_enable_frame_flattening (EWebView *web_view,
+                                        gboolean enable_frame_flattening)
+{
+	WebKitWebSettings *settings;
+
+	g_return_if_fail (E_IS_WEB_VIEW (web_view));
+
+	settings = webkit_web_view_get_settings (WEBKIT_WEB_VIEW (web_view));
+	g_return_if_fail (settings != NULL);
+
+	g_object_set (
+		G_OBJECT (settings), "enable-frame-flattening",
+		enable_frame_flattening, NULL);
+}
+
+gboolean
+e_web_view_get_editable (EWebView *web_view)
+{
+	g_return_val_if_fail (E_IS_WEB_VIEW (web_view), FALSE);
+
+	return webkit_web_view_get_editable (WEBKIT_WEB_VIEW (web_view));
+}
+
+void
+e_web_view_set_editable (EWebView *web_view,
+                         gboolean editable)
+{
+	g_return_if_fail (E_IS_WEB_VIEW (web_view));
+
+	webkit_web_view_set_editable (WEBKIT_WEB_VIEW (web_view), editable);
+}
+
+gboolean
+e_web_view_get_inline_spelling (EWebView *web_view)
+{
+#if 0  /* WEBKIT - XXX No equivalent property? */
+	/* XXX This is just here to maintain symmetry
+	 *     with e_web_view_set_inline_spelling(). */
+
+	g_return_val_if_fail (E_IS_WEB_VIEW (web_view), FALSE);
+
+	return gtk_html_get_inline_spelling (GTK_HTML (web_view));
+#endif
+
+	return FALSE;
+}
+
+void
+e_web_view_set_inline_spelling (EWebView *web_view,
+                                gboolean inline_spelling)
+{
+#if 0  /* WEBKIT - XXX No equivalent property? */
+	/* XXX GtkHTML does not utilize GObject properties as well
+	 *     as it could.  This just wraps gtk_html_set_inline_spelling()
+	 *     so we get a "notify::inline-spelling" signal. */
+
+	g_return_if_fail (E_IS_WEB_VIEW (web_view));
+
+	gtk_html_set_inline_spelling (GTK_HTML (web_view), inline_spelling);
+
+	g_object_notify (G_OBJECT (web_view), "inline-spelling");
+#endif
+}
+
+gboolean
+e_web_view_get_magic_links (EWebView *web_view)
+{
+#if 0  /* WEBKIT - XXX No equivalent property? */
+	/* XXX This is just here to maintain symmetry
+	 *     with e_web_view_set_magic_links(). */
+
+	g_return_val_if_fail (E_IS_WEB_VIEW (web_view), FALSE);
+
+	return gtk_html_get_magic_links (GTK_HTML (web_view));
+#endif
+
+	return FALSE;
+}
+
+void
+e_web_view_set_magic_links (EWebView *web_view,
+                            gboolean magic_links)
+{
+#if 0  /* WEBKIT - XXX No equivalent property? */
+	/* XXX GtkHTML does not utilize GObject properties as well
+	 *     as it could.  This just wraps gtk_html_set_magic_links()
+	 *     so we can get a "notify::magic-links" signal. */
+
+	g_return_if_fail (E_IS_WEB_VIEW (web_view));
+
+	gtk_html_set_magic_links (GTK_HTML (web_view), magic_links);
+
+	g_object_notify (G_OBJECT (web_view), "magic-links");
+#endif
+}
+
+gboolean
+e_web_view_get_magic_smileys (EWebView *web_view)
+{
+#if 0  /* WEBKIT - No equivalent property? */
+	/* XXX This is just here to maintain symmetry
+	 *     with e_web_view_set_magic_smileys(). */
+
+	g_return_val_if_fail (E_IS_WEB_VIEW (web_view), FALSE);
+
+	return gtk_html_get_magic_smileys (GTK_HTML (web_view));
+#endif
+
+	return FALSE;
+}
+
+void
+e_web_view_set_magic_smileys (EWebView *web_view,
+                              gboolean magic_smileys)
+{
+#if 0  /* WEBKIT - No equivalent property? */
+	/* XXX GtkHTML does not utilize GObject properties as well
+	 *     as it could.  This just wraps gtk_html_set_magic_smileys()
+	 *     so we can get a "notify::magic-smileys" signal. */
+
+	g_return_if_fail (E_IS_WEB_VIEW (web_view));
+
+	gtk_html_set_magic_smileys (GTK_HTML (web_view), magic_smileys);
+
+	g_object_notify (G_OBJECT (web_view), "magic-smileys");
+#endif
+}
+
+const gchar *
+e_web_view_get_selected_uri (EWebView *web_view)
+{
+	g_return_val_if_fail (E_IS_WEB_VIEW (web_view), NULL);
+
+	return web_view->priv->selected_uri;
+}
+
+void
+e_web_view_set_selected_uri (EWebView *web_view,
+                             const gchar *selected_uri)
+{
+	g_return_if_fail (E_IS_WEB_VIEW (web_view));
+
+	if (g_strcmp0 (web_view->priv->selected_uri, selected_uri) == 0)
+		return;
+
+	g_free (web_view->priv->selected_uri);
+	web_view->priv->selected_uri = g_strdup (selected_uri);
+
+	g_object_notify (G_OBJECT (web_view), "selected-uri");
+}
+
+GdkPixbufAnimation *
+e_web_view_get_cursor_image (EWebView *web_view)
+{
+	g_return_val_if_fail (E_IS_WEB_VIEW (web_view), NULL);
+
+	return web_view->priv->cursor_image;
+}
+
+void
+e_web_view_set_cursor_image (EWebView *web_view,
+                             GdkPixbufAnimation *image)
+{
+	g_return_if_fail (E_IS_WEB_VIEW (web_view));
+
+	if (web_view->priv->cursor_image == image)
+		return;
+
+	if (image != NULL)
+		g_object_ref (image);
+
+	if (web_view->priv->cursor_image != NULL)
+		g_object_unref (web_view->priv->cursor_image);
+
+	web_view->priv->cursor_image = image;
+
+	g_object_notify (G_OBJECT (web_view), "cursor-image");
+}
+
+const gchar *
+e_web_view_get_cursor_image_src (EWebView *web_view)
+{
+	g_return_val_if_fail (E_IS_WEB_VIEW (web_view), NULL);
+
+	return web_view->priv->cursor_image_src;
+}
+
+void
+e_web_view_set_cursor_image_src (EWebView *web_view,
+                                 const gchar *src_uri)
+{
+	g_return_if_fail (E_IS_WEB_VIEW (web_view));
+
+	if (g_strcmp0 (web_view->priv->cursor_image_src, src_uri) == 0)
+		return;
+
+	g_free (web_view->priv->cursor_image_src);
+	web_view->priv->cursor_image_src = g_strdup (src_uri);
+
+	g_object_notify (G_OBJECT (web_view), "cursor-image-src");
+}
+
+GtkAction *
+e_web_view_get_open_proxy (EWebView *web_view)
+{
+	g_return_val_if_fail (E_IS_WEB_VIEW (web_view), FALSE);
+
+	return web_view->priv->open_proxy;
+}
+
+void
+e_web_view_set_open_proxy (EWebView *web_view,
+                           GtkAction *open_proxy)
+{
+	g_return_if_fail (E_IS_WEB_VIEW (web_view));
+
+	if (web_view->priv->open_proxy == open_proxy)
+		return;
+
+	if (open_proxy != NULL) {
+		g_return_if_fail (GTK_IS_ACTION (open_proxy));
+		g_object_ref (open_proxy);
+	}
+
+	if (web_view->priv->open_proxy != NULL)
+		g_object_unref (web_view->priv->open_proxy);
+
+	web_view->priv->open_proxy = open_proxy;
+
+	g_object_notify (G_OBJECT (web_view), "open-proxy");
+}
+
+GtkTargetList *
+e_web_view_get_paste_target_list (EWebView *web_view)
+{
+	g_return_val_if_fail (E_IS_WEB_VIEW (web_view), NULL);
+
+	return webkit_web_view_get_paste_target_list (
+		WEBKIT_WEB_VIEW (web_view));
+}
+
+GtkAction *
+e_web_view_get_print_proxy (EWebView *web_view)
+{
+	g_return_val_if_fail (E_IS_WEB_VIEW (web_view), FALSE);
+
+	return web_view->priv->print_proxy;
+}
+
+void
+e_web_view_set_print_proxy (EWebView *web_view,
+                            GtkAction *print_proxy)
+{
+	g_return_if_fail (E_IS_WEB_VIEW (web_view));
+
+	if (web_view->priv->print_proxy == print_proxy)
+		return;
+
+	if (print_proxy != NULL) {
+		g_return_if_fail (GTK_IS_ACTION (print_proxy));
+		g_object_ref (print_proxy);
+	}
+
+	if (web_view->priv->print_proxy != NULL)
+		g_object_unref (web_view->priv->print_proxy);
+
+	web_view->priv->print_proxy = print_proxy;
+
+	g_object_notify (G_OBJECT (web_view), "print-proxy");
+}
+
+GtkAction *
+e_web_view_get_save_as_proxy (EWebView *web_view)
+{
+	g_return_val_if_fail (E_IS_WEB_VIEW (web_view), FALSE);
+
+	return web_view->priv->save_as_proxy;
+}
+
+void
+e_web_view_set_save_as_proxy (EWebView *web_view,
+                              GtkAction *save_as_proxy)
+{
+	g_return_if_fail (E_IS_WEB_VIEW (web_view));
+
+	if (web_view->priv->save_as_proxy == save_as_proxy)
+		return;
+
+	if (save_as_proxy != NULL) {
+		g_return_if_fail (GTK_IS_ACTION (save_as_proxy));
+		g_object_ref (save_as_proxy);
+	}
+
+	if (web_view->priv->save_as_proxy != NULL)
+		g_object_unref (web_view->priv->save_as_proxy);
+
+	web_view->priv->save_as_proxy = save_as_proxy;
+
+	g_object_notify (G_OBJECT (web_view), "save-as-proxy");
+}
+
+GSList *
+e_web_view_get_highlights (EWebView *web_view)
+{
+	g_return_val_if_fail (E_IS_WEB_VIEW (web_view), NULL);
+
+	return web_view->priv->highlights;
+}
+
+void
+e_web_view_add_highlight (EWebView *web_view,
+                          const gchar *highlight)
+{
+	g_return_if_fail (E_IS_WEB_VIEW (web_view));
+	g_return_if_fail (highlight && *highlight);
+
+	web_view->priv->highlights = g_slist_append (
+		web_view->priv->highlights, g_strdup (highlight));
+
+	web_view_update_document_highlights (web_view);
+}
+
+void e_web_view_clear_highlights (EWebView *web_view)
+{
+	g_return_if_fail (E_IS_WEB_VIEW (web_view));
+
+	if (!web_view->priv->highlights)
+		return;
+
+	g_slist_free_full (web_view->priv->highlights, g_free);
+	web_view->priv->highlights = NULL;
+
+	web_view_update_document_highlights (web_view);
+}
+
+GtkAction *
+e_web_view_get_action (EWebView *web_view,
+                       const gchar *action_name)
+{
+	GtkUIManager *ui_manager;
+
+	g_return_val_if_fail (E_IS_WEB_VIEW (web_view), NULL);
+	g_return_val_if_fail (action_name != NULL, NULL);
+
+	ui_manager = e_web_view_get_ui_manager (web_view);
+
+	return e_lookup_action (ui_manager, action_name);
+}
+
+GtkActionGroup *
+e_web_view_get_action_group (EWebView *web_view,
+                             const gchar *group_name)
+{
+	GtkUIManager *ui_manager;
+
+	g_return_val_if_fail (E_IS_WEB_VIEW (web_view), NULL);
+	g_return_val_if_fail (group_name != NULL, NULL);
+
+	ui_manager = e_web_view_get_ui_manager (web_view);
+
+	return e_lookup_action_group (ui_manager, group_name);
+}
+
+gchar *
+e_web_view_extract_uri (EWebView *web_view,
+                        GdkEventButton *event)
+{
+	EWebViewClass *class;
+
+	g_return_val_if_fail (E_IS_WEB_VIEW (web_view), NULL);
+
+	class = E_WEB_VIEW_GET_CLASS (web_view);
+	g_return_val_if_fail (class->extract_uri != NULL, NULL);
+
+	return class->extract_uri (web_view, event);
+}
+
+void
+e_web_view_copy_clipboard (EWebView *web_view)
+{
+	g_return_if_fail (E_IS_WEB_VIEW (web_view));
+
+	webkit_web_view_copy_clipboard (WEBKIT_WEB_VIEW (web_view));
+}
+
+void
+e_web_view_cut_clipboard (EWebView *web_view)
+{
+	g_return_if_fail (E_IS_WEB_VIEW (web_view));
+
+	webkit_web_view_cut_clipboard (WEBKIT_WEB_VIEW (web_view));
+}
+
+gboolean
+e_web_view_is_selection_active (EWebView *web_view)
+{
+	g_return_val_if_fail (E_IS_WEB_VIEW (web_view), FALSE);
+
+	return webkit_web_view_has_selection (WEBKIT_WEB_VIEW (web_view));
+}
+
+void
+e_web_view_paste_clipboard (EWebView *web_view)
+{
+	g_return_if_fail (E_IS_WEB_VIEW (web_view));
+
+	webkit_web_view_paste_clipboard (WEBKIT_WEB_VIEW (web_view));
+}
+
+gboolean
+e_web_view_scroll_forward (EWebView *web_view)
+{
+	g_return_val_if_fail (E_IS_WEB_VIEW (web_view), FALSE);
+
+	webkit_web_view_move_cursor (
+		WEBKIT_WEB_VIEW (web_view), GTK_MOVEMENT_PAGES, 1);
+
+	return TRUE;  /* XXX This means nothing. */
+}
+
+gboolean
+e_web_view_scroll_backward (EWebView *web_view)
+{
+	g_return_val_if_fail (E_IS_WEB_VIEW (web_view), FALSE);
+
+	webkit_web_view_move_cursor (
+		WEBKIT_WEB_VIEW (web_view), GTK_MOVEMENT_PAGES, -1);
+
+	return TRUE;  /* XXX This means nothing. */
+}
+
+void
+e_web_view_select_all (EWebView *web_view)
+{
+	g_return_if_fail (E_IS_WEB_VIEW (web_view));
+
+	webkit_web_view_select_all (WEBKIT_WEB_VIEW (web_view));
+}
+
+void
+e_web_view_unselect_all (EWebView *web_view)
+{
+#if 0  /* WEBKIT */
+	g_return_if_fail (E_IS_WEB_VIEW (web_view));
+
+	gtk_html_command (GTK_HTML (web_view), "unselect-all");
+#endif
+}
+
+void
+e_web_view_zoom_100 (EWebView *web_view)
+{
+	g_return_if_fail (E_IS_WEB_VIEW (web_view));
+
+	webkit_web_view_set_zoom_level (WEBKIT_WEB_VIEW (web_view), 1.0);
+}
+
+void
+e_web_view_zoom_in (EWebView *web_view)
+{
+	g_return_if_fail (E_IS_WEB_VIEW (web_view));
+
+	webkit_web_view_zoom_in (WEBKIT_WEB_VIEW (web_view));
+}
+
+void
+e_web_view_zoom_out (EWebView *web_view)
+{
+	g_return_if_fail (E_IS_WEB_VIEW (web_view));
+
+	webkit_web_view_zoom_out (WEBKIT_WEB_VIEW (web_view));
+}
+
+GtkUIManager *
+e_web_view_get_ui_manager (EWebView *web_view)
+{
+	g_return_val_if_fail (E_IS_WEB_VIEW (web_view), NULL);
+
+	return web_view->priv->ui_manager;
+}
+
+GtkWidget *
+e_web_view_get_popup_menu (EWebView *web_view)
+{
+	GtkUIManager *ui_manager;
+	GtkWidget *menu;
+
+	g_return_val_if_fail (E_IS_WEB_VIEW (web_view), NULL);
+
+	ui_manager = e_web_view_get_ui_manager (web_view);
+	menu = gtk_ui_manager_get_widget (ui_manager, "/context");
+	g_return_val_if_fail (GTK_IS_MENU (menu), NULL);
+
+	return menu;
+}
+
+void
+e_web_view_show_popup_menu (EWebView *web_view)
+{
+	GtkWidget *menu;
+
+	g_return_if_fail (E_IS_WEB_VIEW (web_view));
+
+	e_web_view_update_actions (web_view);
+
+	menu = e_web_view_get_popup_menu (web_view);
+
+	gtk_menu_popup (
+		GTK_MENU (menu), NULL, NULL, NULL, NULL,
+		0, gtk_get_current_event_time ());
+}
+
+void
+e_web_view_status_message (EWebView *web_view,
+                           const gchar *status_message)
+{
+	g_return_if_fail (E_IS_WEB_VIEW (web_view));
+
+	g_signal_emit (web_view, signals[STATUS_MESSAGE], 0, status_message);
+}
+
+void
+e_web_view_stop_loading (EWebView *web_view)
+{
+	g_return_if_fail (E_IS_WEB_VIEW (web_view));
+
+	g_signal_emit (web_view, signals[STOP_LOADING], 0);
+}
+
+void
+e_web_view_update_actions (EWebView *web_view)
+{
+	g_return_if_fail (E_IS_WEB_VIEW (web_view));
+
+	g_signal_emit (web_view, signals[UPDATE_ACTIONS], 0);
+}
+
+static gchar *
+web_view_get_frame_selection_html (WebKitDOMElement *iframe)
+{
+	WebKitDOMDocument *document;
+	WebKitDOMDOMWindow *window;
+	WebKitDOMDOMSelection *selection;
+	WebKitDOMNodeList *frames;
+	gulong ii, length;
+
+	document = webkit_dom_html_iframe_element_get_content_document (
+		WEBKIT_DOM_HTML_IFRAME_ELEMENT (iframe));
+	window = webkit_dom_document_get_default_view (document);
+	selection = webkit_dom_dom_window_get_selection (window);
+	if (selection && (webkit_dom_dom_selection_get_range_count (selection) > 0)) {
+		WebKitDOMRange *range;
+		WebKitDOMElement *element;
+		WebKitDOMDocumentFragment *fragment;
+
+		range = webkit_dom_dom_selection_get_range_at (selection, 0, NULL);
+		if (range != NULL) {
+			fragment = webkit_dom_range_clone_contents (
+				range, NULL);
+
+			element = webkit_dom_document_create_element (
+				document, "DIV", NULL);
+			webkit_dom_node_append_child (
+				WEBKIT_DOM_NODE (element),
+				WEBKIT_DOM_NODE (fragment), NULL);
+
+			return webkit_dom_html_element_get_inner_html (
+				WEBKIT_DOM_HTML_ELEMENT (element));
+		}
+	}
+
+	frames = webkit_dom_document_get_elements_by_tag_name (
+		document, "IFRAME");
+	length = webkit_dom_node_list_get_length (frames);
+	for (ii = 0; ii < length; ii++) {
+		WebKitDOMNode *node;
+		gchar *text;
+
+		node = webkit_dom_node_list_item (frames, ii);
+
+		text = web_view_get_frame_selection_html (
+			WEBKIT_DOM_ELEMENT (node));
+
+		if (text != NULL)
+			return text;
+	}
+
+	return NULL;
+}
+
+gchar *
+e_web_view_get_selection_html (EWebView *web_view)
+{
+	WebKitDOMDocument *document;
+	WebKitDOMNodeList *frames;
+	gulong ii, length;
+
+	g_return_val_if_fail (E_IS_WEB_VIEW (web_view), NULL);
+
+	if (!webkit_web_view_has_selection (WEBKIT_WEB_VIEW (web_view)))
+		return NULL;
+
+	document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (web_view));
+	frames = webkit_dom_document_get_elements_by_tag_name (document, "IFRAME");
+	length = webkit_dom_node_list_get_length (frames);
+
+	for (ii = 0; ii < length; ii++) {
+		gchar *text;
+		WebKitDOMNode *node;
+
+		node = webkit_dom_node_list_item (frames, ii);
+
+		text = web_view_get_frame_selection_html (
+			WEBKIT_DOM_ELEMENT (node));
+
+		if (text != NULL)
+			return text;
+	}
+
+	return NULL;
+}
+
+void
+e_web_view_set_settings (EWebView *web_view,
+                         WebKitWebSettings *settings)
+{
+	g_return_if_fail (E_IS_WEB_VIEW (web_view));
+
+	if (settings == webkit_web_view_get_settings (WEBKIT_WEB_VIEW (web_view)))
+		return;
+
+	g_object_bind_property (
+		settings, "enable-caret-browsing",
+		web_view, "caret-mode",
+		G_BINDING_BIDIRECTIONAL |
+		G_BINDING_SYNC_CREATE);
+
+	webkit_web_view_set_settings (WEBKIT_WEB_VIEW (web_view), settings);
+}
+
+WebKitWebSettings *
+e_web_view_get_default_settings (void)
+{
+	WebKitWebSettings *settings;
+
+	settings = webkit_web_settings_new ();
+
+	g_object_set (
+		G_OBJECT (settings),
+		"enable-frame-flattening", TRUE,
+		"enable-java-applet", FALSE,
+		"enable-html5-database", FALSE,
+		"enable-html5-local-storage", FALSE,
+		"enable-offline-web-application-cache", FALSE,
+		"enable-site-specific-quirks", TRUE,
+		"enable-scripts", FALSE,
+		NULL);
+
+	return settings;
+}
+
+void
+e_web_view_update_fonts (EWebView *web_view)
+{
+	EWebViewClass *class;
+	GString *stylesheet;
+	gchar *base64;
+	gchar *aa = NULL;
+	WebKitWebSettings *settings;
+	PangoFontDescription *min_size, *ms, *vw;
+	const gchar *styles[] = { "normal", "oblique", "italic" };
+	const gchar *smoothing = NULL;
+	GtkStyleContext *context;
+	GdkColor *link = NULL;
+	GdkColor *visited = NULL;
+
+	g_return_if_fail (E_IS_WEB_VIEW (web_view));
+
+	ms = NULL;
+	vw = NULL;
+
+	class = E_WEB_VIEW_GET_CLASS (web_view);
+	if (class->set_fonts != NULL)
+		class->set_fonts (web_view, &ms, &vw);
+
+	if (ms == NULL) {
+		gchar *font;
+
+		font = g_settings_get_string (
+			web_view->priv->font_settings,
+			"monospace-font-name");
+
+		ms = pango_font_description_from_string (
+			(font != NULL) ? font : "monospace 10");
+
+		g_free (font);
+	}
+
+	if (vw == NULL) {
+		gchar *font;
+
+		font = g_settings_get_string (
+			web_view->priv->font_settings,
+			"font-name");
+
+		vw = pango_font_description_from_string (
+			(font != NULL) ? font : "serif 10");
+
+		g_free (font);
+	}
+
+	if (pango_font_description_get_size (ms) < pango_font_description_get_size (vw)) {
+		min_size = ms;
+	} else {
+		min_size = vw;
+	}
+
+	stylesheet = g_string_new ("");
+	g_string_append_printf (
+		stylesheet,
+		"body {\n"
+		"  font-family: '%s';\n"
+		"  font-size: %dpt;\n"
+		"  font-weight: %d;\n"
+		"  font-style: %s;\n",
+		pango_font_description_get_family (vw),
+		pango_font_description_get_size (vw) / PANGO_SCALE,
+		pango_font_description_get_weight (vw),
+		styles[pango_font_description_get_style (vw)]);
+
+	if (web_view->priv->aliasing_settings != NULL)
+		aa = g_settings_get_string (
+			web_view->priv->aliasing_settings, "antialiasing");
+
+	if (g_strcmp0 (aa, "none") == 0)
+		smoothing = "none";
+	else if (g_strcmp0 (aa, "grayscale") == 0)
+		smoothing = "antialiased";
+	else if (g_strcmp0 (aa, "rgba") == 0)
+		smoothing = "subpixel-antialiased";
+
+	if (smoothing != NULL)
+		g_string_append_printf (
+			stylesheet,
+			" -webkit-font-smoothing: %s;\n",
+			smoothing);
+
+	g_free (aa);
+
+	g_string_append (stylesheet, "}\n");
+
+	g_string_append_printf (
+		stylesheet,
+		"pre,code,.pre {\n"
+		"  font-family: '%s';\n"
+		"  font-size: %dpt;\n"
+		"  font-weight: %d;\n"
+		"  font-style: %s;\n"
+		"}",
+		pango_font_description_get_family (ms),
+		pango_font_description_get_size (ms) / PANGO_SCALE,
+		pango_font_description_get_weight (ms),
+		styles[pango_font_description_get_style (ms)]);
+
+	context = gtk_widget_get_style_context (GTK_WIDGET (web_view));
+	gtk_style_context_get_style (
+		context,
+		"link-color", &link,
+		"visited-link-color", &visited,
+		NULL);
+
+	if (link == NULL) {
+		link = g_slice_new0 (GdkColor);
+		link->blue = G_MAXINT16;
+	}
+
+	if (visited == NULL) {
+		visited = g_slice_new0 (GdkColor);
+		visited->red = G_MAXINT16;
+	}
+
+	g_string_append_printf (
+		stylesheet,
+		"a {\n"
+		"  color: #%06x;\n"
+		"}\n"
+		"a:visited {\n"
+		"  color: #%06x;\n"
+		"}\n",
+		e_color_to_value (link),
+		e_color_to_value (visited));
+
+	gdk_color_free (link);
+	gdk_color_free (visited);
+
+	base64 = g_base64_encode ((guchar *) stylesheet->str, stylesheet->len);
+	g_string_free (stylesheet, TRUE);
+
+	stylesheet = g_string_new ("data:text/css;charset=utf-8;base64,");
+	g_string_append (stylesheet, base64);
+	g_free (base64);
+
+	settings = webkit_web_view_get_settings (WEBKIT_WEB_VIEW (web_view));
+	g_object_set (
+		G_OBJECT (settings),
+		"default-font-size", pango_font_description_get_size (vw) / PANGO_SCALE,
+		"default-font-family", pango_font_description_get_family (vw),
+		"monospace-font-family", pango_font_description_get_family (ms),
+		"default-monospace-font-size", (pango_font_description_get_size (ms) / PANGO_SCALE),
+		"minimum-font-size", (pango_font_description_get_size (min_size) / PANGO_SCALE),
+		"user-stylesheet-uri", stylesheet->str,
+		NULL);
+
+	g_string_free (stylesheet, TRUE);
+
+	pango_font_description_free (ms);
+	pango_font_description_free (vw);
+}
+
+void
+e_web_view_install_request_handler (EWebView *web_view,
+                                    GType handler_type)
+{
+	SoupSession *session;
+	SoupSessionFeature *feature;
+	gboolean new;
+
+	session = webkit_get_default_session ();
+
+	feature = soup_session_get_feature (session, SOUP_TYPE_REQUESTER);
+	new = FALSE;
+	if (feature == NULL) {
+		feature = SOUP_SESSION_FEATURE (soup_requester_new ());
+		soup_session_add_feature (session, feature);
+		new = TRUE;
+	}
+
+	soup_session_feature_add_feature (feature, handler_type);
+
+	if (new) {
+		g_object_unref (feature);
+	}
+}
+
diff --git a/e-util/e-web-view.h b/e-util/e-web-view.h
new file mode 100644
index 0000000..6690725
--- /dev/null
+++ b/e-util/e-web-view.h
@@ -0,0 +1,224 @@
+/*
+ * e-web-view.h
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+/* This is intended to serve as a common base class for all HTML viewing
+ * needs in Evolution.  Currently based on GtkHTML, the idea is to wrap
+ * the GtkHTML API enough that we no longer have to make direct calls to
+ * it.  This should help smooth the transition to WebKit/GTK+.
+ *
+ * This class handles basic tasks like mouse hovers over links, clicked
+ * links, and servicing URI requests asynchronously via GIO. */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_WEB_VIEW_H
+#define E_WEB_VIEW_H
+
+#include <webkit/webkit.h>
+
+/* Standard GObject macros */
+#define E_TYPE_WEB_VIEW \
+	(e_web_view_get_type ())
+#define E_WEB_VIEW(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_WEB_VIEW, EWebView))
+#define E_WEB_VIEW_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_WEB_VIEW, EWebViewClass))
+#define E_IS_WEB_VIEW(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_WEB_VIEW))
+#define E_IS_WEB_VIEW_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_WEB_VIEW))
+#define E_WEB_VIEW_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_WEB_VIEW, EWebViewClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EWebView EWebView;
+typedef struct _EWebViewClass EWebViewClass;
+typedef struct _EWebViewPrivate EWebViewPrivate;
+struct PangoFontDescription;
+
+struct _EWebView {
+	WebKitWebView parent;
+	EWebViewPrivate *priv;
+};
+
+typedef void (*EWebViewJSFunctionCallback)	(EWebView *web_view,
+						 size_t arg_count,
+						 const JSValueRef args[],
+						 gpointer user_data);
+
+struct _EWebViewClass {
+	WebKitWebViewClass parent_class;
+
+	/* Methods */
+	GtkWidget *	(*create_plugin_widget)	(EWebView *web_view,
+						 const gchar *mime_type,
+						 const gchar *uri,
+						 GHashTable *param);
+	gchar *		(*extract_uri)		(EWebView *web_view,
+						 GdkEventButton *event);
+	void		(*hovering_over_link)	(EWebView *web_view,
+						 const gchar *title,
+						 const gchar *uri);
+	void		(*link_clicked)		(EWebView *web_view,
+						 const gchar *uri);
+	void		(*load_string)		(EWebView *web_view,
+						 const gchar *load_string);
+	void		(*load_uri)		(EWebView *web_view,
+						 const gchar *load_uri);
+	void		(*frame_load_string)	(EWebView *web_view,
+						 const gchar *frame_name,
+						 const gchar *string);
+	void		(*frame_load_uri)	(EWebView *web_view,
+						 const gchar *frame_name,
+						 const gchar *uri);
+	void		(*set_fonts)		(EWebView *web_view,
+						 PangoFontDescription **monospace,
+						 PangoFontDescription **variable_width);
+
+	/* Signals */
+	gboolean	(*popup_event)		(EWebView *web_view,
+						 const gchar *uri);
+	void		(*status_message)	(EWebView *web_view,
+						 const gchar *status_message);
+	void		(*stop_loading)		(EWebView *web_view);
+	void		(*update_actions)	(EWebView *web_view);
+	gboolean	(*process_mailto)	(EWebView *web_view,
+						 const gchar *mailto_uri);
+};
+
+GType		e_web_view_get_type		(void);
+GtkWidget *	e_web_view_new			(void);
+void		e_web_view_clear		(EWebView *web_view);
+void		e_web_view_load_string		(EWebView *web_view,
+						 const gchar *string);
+void		e_web_view_load_uri		(EWebView *web_view,
+						 const gchar *uri);
+const gchar *	e_web_view_get_uri		(EWebView *web_view);
+void		e_web_view_reload		(EWebView *web_view);
+void		e_web_view_frame_load_string	(EWebView *web_view,
+						 const gchar *frame_name,
+						 const gchar *string);
+void		e_web_view_frame_load_uri	(EWebView *web_view,
+						 const gchar *frame_name,
+						 const gchar *uri);
+const gchar *	e_web_view_frame_get_uri	(EWebView *web_view,
+						 const gchar *frame_name);
+gchar *		e_web_view_get_html		(EWebView *web_view);
+gboolean	e_web_view_get_caret_mode	(EWebView *web_view);
+void		e_web_view_set_caret_mode	(EWebView *web_view,
+						 gboolean caret_mode);
+GtkTargetList *	e_web_view_get_copy_target_list	(EWebView *web_view);
+gboolean	e_web_view_get_disable_printing	(EWebView *web_view);
+void		e_web_view_set_disable_printing	(EWebView *web_view,
+						 gboolean disable_printing);
+gboolean	e_web_view_get_disable_save_to_disk
+						(EWebView *web_view);
+void		e_web_view_set_disable_save_to_disk
+						(EWebView *web_view,
+						 gboolean disable_save_to_disk);
+gboolean        e_web_view_get_enable_frame_flattening
+                                                (EWebView *web_view);
+void            e_web_view_set_enable_frame_flattening
+                                                (EWebView *web_view,
+                                                 gboolean enable_frame_flattening);
+gboolean	e_web_view_get_editable		(EWebView *web_view);
+void		e_web_view_set_editable		(EWebView *web_view,
+						 gboolean editable);
+gboolean	e_web_view_get_inline_spelling	(EWebView *web_view);
+void		e_web_view_set_inline_spelling	(EWebView *web_view,
+						 gboolean inline_spelling);
+gboolean	e_web_view_get_magic_links	(EWebView *web_view);
+void		e_web_view_set_magic_links	(EWebView *web_view,
+						 gboolean magic_links);
+gboolean	e_web_view_get_magic_smileys	(EWebView *web_view);
+void		e_web_view_set_magic_smileys	(EWebView *web_view,
+						 gboolean magic_smileys);
+const gchar *	e_web_view_get_selected_uri	(EWebView *web_view);
+void		e_web_view_set_selected_uri	(EWebView *web_view,
+						 const gchar *selected_uri);
+GdkPixbufAnimation *
+		e_web_view_get_cursor_image	(EWebView *web_view);
+void		e_web_view_set_cursor_image	(EWebView *web_view,
+						 GdkPixbufAnimation *animation);
+const gchar *	e_web_view_get_cursor_image_src	(EWebView *web_view);
+void		e_web_view_set_cursor_image_src	(EWebView *web_view,
+						 const gchar *src_uri);
+GtkAction *	e_web_view_get_open_proxy	(EWebView *web_view);
+void		e_web_view_set_open_proxy	(EWebView *web_view,
+						 GtkAction *open_proxy);
+GtkTargetList *	e_web_view_get_paste_target_list
+						(EWebView *web_view);
+GtkAction *	e_web_view_get_print_proxy	(EWebView *web_view);
+void		e_web_view_set_print_proxy	(EWebView *web_view,
+						 GtkAction *print_proxy);
+GtkAction *	e_web_view_get_save_as_proxy	(EWebView *web_view);
+void		e_web_view_set_save_as_proxy	(EWebView *web_view,
+						 GtkAction *save_as_proxy);
+GSList *         e_web_view_get_highlights       (EWebView *web_view);
+void            e_web_view_add_highlight        (EWebView *web_view,
+                                                 const gchar *highlight);
+void            e_web_view_clear_highlights     (EWebView *web_view);
+GtkAction *	e_web_view_get_action		(EWebView *web_view,
+						 const gchar *action_name);
+GtkActionGroup *e_web_view_get_action_group	(EWebView *web_view,
+						 const gchar *group_name);
+gchar *		e_web_view_extract_uri		(EWebView *web_view,
+						 GdkEventButton *event);
+void		e_web_view_copy_clipboard	(EWebView *web_view);
+void		e_web_view_cut_clipboard	(EWebView *web_view);
+gboolean	e_web_view_is_selection_active	(EWebView *web_view);
+void		e_web_view_paste_clipboard	(EWebView *web_view);
+gboolean	e_web_view_scroll_forward	(EWebView *web_view);
+gboolean	e_web_view_scroll_backward	(EWebView *web_view);
+void		e_web_view_select_all		(EWebView *web_view);
+void		e_web_view_unselect_all		(EWebView *web_view);
+void		e_web_view_zoom_100		(EWebView *web_view);
+void		e_web_view_zoom_in		(EWebView *web_view);
+void		e_web_view_zoom_out		(EWebView *web_view);
+GtkUIManager *	e_web_view_get_ui_manager	(EWebView *web_view);
+GtkWidget *	e_web_view_get_popup_menu	(EWebView *web_view);
+void		e_web_view_show_popup_menu	(EWebView *web_view);
+void		e_web_view_status_message	(EWebView *web_view,
+						 const gchar *status_message);
+void		e_web_view_stop_loading		(EWebView *web_view);
+void		e_web_view_update_actions	(EWebView *web_view);
+gchar *         e_web_view_get_selection_html   (EWebView *web_view);
+
+void		e_web_view_set_settings		(EWebView *web_view,
+						 WebKitWebSettings *settings);
+
+void		e_web_view_update_fonts		(EWebView *web_view);
+
+WebKitWebSettings *
+		e_web_view_get_default_settings (void);
+
+void		e_web_view_install_request_handler
+						(EWebView *web_view,
+						 GType handler_type);
+
+G_END_DECLS
+
+#endif /* E_WEB_VIEW_H */
diff --git a/e-util/e-xml-utils.c b/e-util/e-xml-utils.c
new file mode 100644
index 0000000..aaa66b6
--- /dev/null
+++ b/e-util/e-xml-utils.c
@@ -0,0 +1,448 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-xml-utils.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <locale.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <math.h>
+#include <string.h>
+
+#include <glib/gi18n.h>
+#include <glib/gstdio.h>
+#include <libxml/parser.h>
+#include <libxml/xmlmemory.h>
+
+#include "e-misc-utils.h"
+
+/* Returns the first child with the name child_name and the "lang"
+ * attribute that matches the current LC_MESSAGES, or else, the first
+ * child with the name child_name and no "lang" attribute.
+ */
+xmlNode *
+e_xml_get_child_by_name_by_lang (const xmlNode *parent,
+                                 const xmlChar *child_name,
+                                 const gchar *lang)
+{
+#ifdef G_OS_WIN32
+	gchar *freeme = NULL;
+#endif
+	xmlNode *child;
+	/* This is the default version of the string. */
+	xmlNode *C = NULL;
+
+	g_return_val_if_fail (parent != NULL, NULL);
+	g_return_val_if_fail (child_name != NULL, NULL);
+
+	if (lang == NULL) {
+#ifndef G_OS_WIN32
+#ifdef HAVE_LC_MESSAGES
+		lang = setlocale (LC_MESSAGES, NULL);
+#else
+		lang = setlocale (LC_CTYPE, NULL);
+#endif
+#else
+		lang = freeme = g_win32_getlocale ();
+#endif
+	}
+	for (child = parent->xmlChildrenNode; child != NULL; child = child->next) {
+		if (xmlStrcmp (child->name, child_name) == 0) {
+			xmlChar *this_lang = xmlGetProp (
+				child, (const guchar *)"lang");
+			if (this_lang == NULL) {
+				C = child;
+			} else if (xmlStrcmp (this_lang, (xmlChar *) lang) == 0) {
+#ifdef G_OS_WIN32
+				g_free (freeme);
+#endif
+				return child;
+			}
+		}
+	}
+#ifdef G_OS_WIN32
+	g_free (freeme);
+#endif
+	return C;
+}
+
+static xmlNode *
+e_xml_get_child_by_name_by_lang_list_with_score (const xmlNode *parent,
+                                                 const gchar *name,
+                                                 const GList *lang_list,
+                                                 gint *best_lang_score)
+{
+	xmlNodePtr best_node = NULL, node;
+
+	for (node = parent->xmlChildrenNode; node != NULL; node = node->next) {
+		xmlChar *lang;
+
+		if (node->name == NULL || strcmp ((gchar *) node->name, name) != 0) {
+			continue;
+		}
+		lang = xmlGetProp (node, (const guchar *)"xml:lang");
+		if (lang != NULL) {
+			const GList *l;
+			gint i;
+
+			for (l = lang_list, i = 0;
+			     l != NULL && i < *best_lang_score;
+			     l = l->next, i++) {
+				if (strcmp ((gchar *) l->data, (gchar *) lang) == 0) {
+					best_node = node;
+					*best_lang_score = i;
+				}
+			}
+		} else {
+			if (best_node == NULL) {
+				best_node = node;
+			}
+		}
+		xmlFree (lang);
+		if (*best_lang_score == 0) {
+			return best_node;
+		}
+	}
+
+	return best_node;
+}
+
+xmlNode *
+e_xml_get_child_by_name_by_lang_list (const xmlNode *parent,
+                                      const gchar *name,
+                                      const GList *lang_list)
+{
+	gint best_lang_score = INT_MAX;
+
+	g_return_val_if_fail (parent != NULL, NULL);
+	g_return_val_if_fail (name != NULL, NULL);
+
+	if (lang_list == NULL) {
+		const gchar * const *language_names;
+
+		language_names = g_get_language_names ();
+		while (*language_names != NULL)
+			lang_list = g_list_append (
+				(GList *) lang_list, (gchar *) * language_names++);
+	}
+	return e_xml_get_child_by_name_by_lang_list_with_score
+		(parent,name,
+		 lang_list,
+		 &best_lang_score);
+}
+
+xmlNode *
+e_xml_get_child_by_name_no_lang (const xmlNode *parent,
+                                 const gchar *name)
+{
+	xmlNodePtr node;
+
+	g_return_val_if_fail (parent != NULL, NULL);
+	g_return_val_if_fail (name != NULL, NULL);
+
+	for (node = parent->xmlChildrenNode; node != NULL; node = node->next) {
+		xmlChar *lang;
+
+		if (node->name == NULL || strcmp ((gchar *) node->name, name) != 0) {
+			continue;
+		}
+		lang = xmlGetProp (node, (const guchar *)"xml:lang");
+		if (lang == NULL) {
+			return node;
+		}
+		xmlFree (lang);
+	}
+
+	return NULL;
+}
+
+gint
+e_xml_get_integer_prop_by_name (const xmlNode *parent,
+                                const xmlChar *prop_name)
+{
+	g_return_val_if_fail (parent != NULL, 0);
+	g_return_val_if_fail (prop_name != NULL, 0);
+
+	return e_xml_get_integer_prop_by_name_with_default (parent, prop_name, 0);
+}
+
+gint
+e_xml_get_integer_prop_by_name_with_default (const xmlNode *parent,
+                                             const xmlChar *prop_name,
+                                             gint def)
+{
+	xmlChar *prop;
+	gint ret_val = def;
+
+	g_return_val_if_fail (parent != NULL, 0);
+	g_return_val_if_fail (prop_name != NULL, 0);
+
+	prop = xmlGetProp ((xmlNode *) parent, prop_name);
+	if (prop != NULL) {
+		(void) sscanf ((gchar *) prop, "%d", &ret_val);
+		xmlFree (prop);
+	}
+	return ret_val;
+}
+
+void
+e_xml_set_integer_prop_by_name (xmlNode *parent,
+                                const xmlChar *prop_name,
+                                gint value)
+{
+	gchar *valuestr;
+
+	g_return_if_fail (parent != NULL);
+	g_return_if_fail (prop_name != NULL);
+
+	valuestr = g_strdup_printf ("%d", value);
+	xmlSetProp (parent, prop_name, (guchar *) valuestr);
+	g_free (valuestr);
+}
+
+guint
+e_xml_get_uint_prop_by_name (const xmlNode *parent,
+                             const xmlChar *prop_name)
+{
+	g_return_val_if_fail (parent != NULL, 0);
+	g_return_val_if_fail (prop_name != NULL, 0);
+
+	return e_xml_get_uint_prop_by_name_with_default (parent, prop_name, 0);
+}
+
+guint
+e_xml_get_uint_prop_by_name_with_default (const xmlNode *parent,
+                                          const xmlChar *prop_name,
+                                          guint def)
+{
+	xmlChar *prop;
+	guint ret_val = def;
+
+	g_return_val_if_fail (parent != NULL, 0);
+	g_return_val_if_fail (prop_name != NULL, 0);
+
+	prop = xmlGetProp ((xmlNode *) parent, prop_name);
+	if (prop != NULL) {
+		(void) sscanf ((gchar *) prop, "%u", &ret_val);
+		xmlFree (prop);
+	}
+	return ret_val;
+}
+
+void
+e_xml_set_uint_prop_by_name (xmlNode *parent,
+                             const xmlChar *prop_name,
+                             guint value)
+{
+	gchar *valuestr;
+
+	g_return_if_fail (parent != NULL);
+	g_return_if_fail (prop_name != NULL);
+
+	valuestr = g_strdup_printf ("%u", value);
+	xmlSetProp (parent, prop_name, (guchar *) valuestr);
+	g_free (valuestr);
+}
+
+gboolean
+e_xml_get_bool_prop_by_name (const xmlNode *parent,
+                             const xmlChar *prop_name)
+{
+	g_return_val_if_fail (parent != NULL, 0);
+	g_return_val_if_fail (prop_name != NULL, 0);
+
+	return e_xml_get_bool_prop_by_name_with_default (
+		parent, prop_name, FALSE);
+}
+
+gboolean
+e_xml_get_bool_prop_by_name_with_default (const xmlNode *parent,
+                                          const xmlChar *prop_name,
+                                          gboolean def)
+{
+	xmlChar *prop;
+	gboolean ret_val = def;
+
+	g_return_val_if_fail (parent != NULL, 0);
+	g_return_val_if_fail (prop_name != NULL, 0);
+
+	prop = xmlGetProp ((xmlNode *) parent, prop_name);
+	if (prop != NULL) {
+		if (g_ascii_strcasecmp ((gchar *) prop, "true") == 0) {
+			ret_val = TRUE;
+		} else if (g_ascii_strcasecmp ((gchar *) prop, "false") == 0) {
+			ret_val = FALSE;
+		}
+		xmlFree (prop);
+	}
+	return ret_val;
+}
+
+void
+e_xml_set_bool_prop_by_name (xmlNode *parent,
+                             const xmlChar *prop_name,
+                             gboolean value)
+{
+	g_return_if_fail (parent != NULL);
+	g_return_if_fail (prop_name != NULL);
+
+	if (value) {
+		xmlSetProp (parent, prop_name, (const guchar *)"true");
+	} else {
+		xmlSetProp (parent, prop_name, (const guchar *)"false");
+	}
+}
+
+gdouble
+e_xml_get_double_prop_by_name (const xmlNode *parent,
+                               const xmlChar *prop_name)
+{
+	g_return_val_if_fail (parent != NULL, 0);
+	g_return_val_if_fail (prop_name != NULL, 0);
+
+	return e_xml_get_double_prop_by_name_with_default (parent, prop_name, 0.0);
+}
+
+gdouble
+e_xml_get_double_prop_by_name_with_default (const xmlNode *parent,
+                                            const xmlChar *prop_name,
+                                            gdouble def)
+{
+	xmlChar *prop;
+	gdouble ret_val = def;
+
+	g_return_val_if_fail (parent != NULL, 0);
+	g_return_val_if_fail (prop_name != NULL, 0);
+
+	prop = xmlGetProp ((xmlNode *) parent, prop_name);
+	if (prop != NULL) {
+		ret_val = e_flexible_strtod ((gchar *) prop, NULL);
+		xmlFree (prop);
+	}
+	return ret_val;
+}
+
+void
+e_xml_set_double_prop_by_name (xmlNode *parent,
+                               const xmlChar *prop_name,
+                               gdouble value)
+{
+	gchar buffer[E_ASCII_DTOSTR_BUF_SIZE];
+	gchar *format;
+
+	g_return_if_fail (parent != NULL);
+	g_return_if_fail (prop_name != NULL);
+
+	if (fabs (value) < 1e9 && fabs (value) > 1e-5) {
+		format = g_strdup_printf ("%%.%df", DBL_DIG);
+	} else {
+		format = g_strdup_printf ("%%.%dg", DBL_DIG);
+	}
+	e_ascii_dtostr (buffer, sizeof (buffer), format, value);
+	g_free (format);
+
+	xmlSetProp (parent, prop_name, (const guchar *) buffer);
+}
+
+gchar *
+e_xml_get_string_prop_by_name (const xmlNode *parent,
+                               const xmlChar *prop_name)
+{
+	g_return_val_if_fail (parent != NULL, NULL);
+	g_return_val_if_fail (prop_name != NULL, NULL);
+
+	return e_xml_get_string_prop_by_name_with_default (parent, prop_name, NULL);
+}
+
+gchar *
+e_xml_get_string_prop_by_name_with_default (const xmlNode *parent,
+                                            const xmlChar *prop_name,
+                                            const gchar *def)
+{
+	xmlChar *prop;
+	gchar *ret_val;
+
+	g_return_val_if_fail (parent != NULL, NULL);
+	g_return_val_if_fail (prop_name != NULL, NULL);
+
+	prop = xmlGetProp ((xmlNode *) parent, prop_name);
+	if (prop != NULL) {
+		ret_val = g_strdup ((gchar *) prop);
+		xmlFree (prop);
+	} else {
+		ret_val = g_strdup (def);
+	}
+	return ret_val;
+}
+
+void
+e_xml_set_string_prop_by_name (xmlNode *parent,
+                               const xmlChar *prop_name,
+                               const gchar *value)
+{
+	g_return_if_fail (parent != NULL);
+	g_return_if_fail (prop_name != NULL);
+
+	if (value != NULL) {
+		xmlSetProp (parent, prop_name, (guchar *) value);
+	}
+}
+
+gchar *
+e_xml_get_translated_string_prop_by_name (const xmlNode *parent,
+                                          const xmlChar *prop_name)
+{
+	xmlChar *prop;
+	gchar *ret_val = NULL;
+	gchar *combined_name;
+
+	g_return_val_if_fail (parent != NULL, NULL);
+	g_return_val_if_fail (prop_name != NULL, NULL);
+
+	prop = xmlGetProp ((xmlNode *) parent, prop_name);
+	if (prop != NULL) {
+		ret_val = g_strdup ((gchar *) prop);
+		xmlFree (prop);
+		return ret_val;
+	}
+
+	combined_name = g_strdup_printf ("_%s", prop_name);
+	prop = xmlGetProp ((xmlNode *) parent, (guchar *) combined_name);
+	if (prop != NULL) {
+		ret_val = g_strdup (gettext ((gchar *) prop));
+		xmlFree (prop);
+	}
+	g_free (combined_name);
+
+	return ret_val;
+}
+
diff --git a/libevolution-utils/e-xml-utils.h b/e-util/e-xml-utils.h
similarity index 100%
rename from libevolution-utils/e-xml-utils.h
rename to e-util/e-xml-utils.h
diff --git a/e-util/ea-calendar-cell.c b/e-util/ea-calendar-cell.c
new file mode 100644
index 0000000..a9784df
--- /dev/null
+++ b/e-util/ea-calendar-cell.c
@@ -0,0 +1,404 @@
+/*
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Bolian Yin <bolian yin sun com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <gtk/gtk.h>
+#include "ea-calendar-cell.h"
+#include "ea-calendar-item.h"
+#include "ea-factory.h"
+
+/* ECalendarCell */
+
+static void e_calendar_cell_class_init (ECalendarCellClass *class);
+
+EA_FACTORY_GOBJECT (EA_TYPE_CALENDAR_CELL, ea_calendar_cell, ea_calendar_cell_new)
+
+GType
+e_calendar_cell_get_type (void)
+{
+	static GType type = 0;
+
+	if (!type) {
+		static GTypeInfo tinfo = {
+			sizeof (ECalendarCellClass),
+			(GBaseInitFunc) NULL, /* base init */
+			(GBaseFinalizeFunc) NULL, /* base finalize */
+			(GClassInitFunc) e_calendar_cell_class_init, /* class init */
+			(GClassFinalizeFunc) NULL, /* class finalize */
+			NULL, /* class data */
+			sizeof (ECalendarCell), /* instance size */
+			0, /* nb preallocs */
+			(GInstanceInitFunc) NULL, /* instance init */
+			NULL /* value table */
+		};
+
+		type = g_type_register_static (
+			G_TYPE_OBJECT,
+			"ECalendarCell", &tinfo, 0);
+	}
+
+	return type;
+}
+
+static void
+e_calendar_cell_class_init (ECalendarCellClass *class)
+{
+    EA_SET_FACTORY (e_calendar_cell_get_type (), ea_calendar_cell);
+}
+
+ECalendarCell *
+e_calendar_cell_new (ECalendarItem *calitem,
+                     gint row,
+                     gint column)
+{
+	GObject *object;
+	ECalendarCell *cell;
+
+	g_return_val_if_fail (E_IS_CALENDAR_ITEM (calitem), NULL);
+
+	object = g_object_new (E_TYPE_CALENDAR_CELL, NULL);
+	cell = E_CALENDAR_CELL (object);
+	cell->calitem = calitem;
+	cell->row = row;
+	cell->column = column;
+
+#ifdef ACC_DEBUG
+	g_print ("EvoAcc: e_calendar_cell created %p\n", (gpointer) cell);
+#endif
+
+	return cell;
+}
+
+/* EaCalendarCell */
+
+static void ea_calendar_cell_class_init (EaCalendarCellClass *klass);
+static void ea_calendar_cell_init (EaCalendarCell *a11y);
+
+static const gchar * ea_calendar_cell_get_name (AtkObject *accessible);
+static const gchar * ea_calendar_cell_get_description (AtkObject *accessible);
+static AtkObject * ea_calendar_cell_get_parent (AtkObject *accessible);
+static gint ea_calendar_cell_get_index_in_parent (AtkObject *accessible);
+static AtkStateSet *ea_calendar_cell_ref_state_set (AtkObject *accessible);
+
+/* component interface */
+static void atk_component_interface_init (AtkComponentIface *iface);
+static void component_interface_get_extents (AtkComponent *component,
+					     gint *x, gint *y,
+					     gint *width, gint *height,
+					     AtkCoordType coord_type);
+static gboolean component_interface_grab_focus (AtkComponent *component);
+
+static gpointer parent_class = NULL;
+
+#ifdef ACC_DEBUG
+static gint n_ea_calendar_cell_created = 0, n_ea_calendar_cell_destroyed = 0;
+static void ea_calendar_cell_finalize (GObject *object);
+#endif
+
+GType
+ea_calendar_cell_get_type (void)
+{
+	static GType type = 0;
+
+	if (!type) {
+		static GTypeInfo tinfo = {
+			sizeof (EaCalendarCellClass),
+			(GBaseInitFunc) NULL, /* base init */
+			(GBaseFinalizeFunc) NULL, /* base finalize */
+			(GClassInitFunc) ea_calendar_cell_class_init, /* class init */
+			(GClassFinalizeFunc) NULL, /* class finalize */
+			NULL, /* class data */
+			sizeof (EaCalendarCell), /* instance size */
+			0, /* nb preallocs */
+			(GInstanceInitFunc) ea_calendar_cell_init, /* instance init */
+			NULL /* value table */
+		};
+
+		static const GInterfaceInfo atk_component_info = {
+			(GInterfaceInitFunc) atk_component_interface_init,
+			(GInterfaceFinalizeFunc) NULL,
+			NULL
+		};
+
+		type = g_type_register_static (
+			ATK_TYPE_GOBJECT_ACCESSIBLE,
+			"EaCalendarCell", &tinfo, 0);
+		g_type_add_interface_static (
+			type, ATK_TYPE_COMPONENT,
+			&atk_component_info);
+	}
+
+	return type;
+}
+
+static void
+ea_calendar_cell_class_init (EaCalendarCellClass *klass)
+{
+	AtkObjectClass *class = ATK_OBJECT_CLASS (klass);
+
+#ifdef ACC_DEBUG
+	GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+	gobject_class->finalize = ea_calendar_cell_finalize;
+#endif
+
+	parent_class = g_type_class_peek_parent (klass);
+
+	class->get_name = ea_calendar_cell_get_name;
+	class->get_description = ea_calendar_cell_get_description;
+
+	class->get_parent = ea_calendar_cell_get_parent;
+	class->get_index_in_parent = ea_calendar_cell_get_index_in_parent;
+	class->ref_state_set = ea_calendar_cell_ref_state_set;
+}
+
+static void
+ea_calendar_cell_init (EaCalendarCell *a11y)
+{
+	a11y->state_set = atk_state_set_new ();
+	atk_state_set_add_state (a11y->state_set, ATK_STATE_TRANSIENT);
+	atk_state_set_add_state (a11y->state_set, ATK_STATE_ENABLED);
+	atk_state_set_add_state (a11y->state_set, ATK_STATE_SENSITIVE);
+	atk_state_set_add_state (a11y->state_set, ATK_STATE_SELECTABLE);
+	atk_state_set_add_state (a11y->state_set, ATK_STATE_SHOWING);
+	atk_state_set_add_state (a11y->state_set, ATK_STATE_FOCUSABLE);
+}
+
+AtkObject *
+ea_calendar_cell_new (GObject *obj)
+{
+	gpointer object;
+	AtkObject *atk_object;
+
+	g_return_val_if_fail (E_IS_CALENDAR_CELL (obj), NULL);
+	object = g_object_new (EA_TYPE_CALENDAR_CELL, NULL);
+	atk_object = ATK_OBJECT (object);
+	atk_object_initialize (atk_object, obj);
+	atk_object->role = ATK_ROLE_TABLE_CELL;
+
+#ifdef ACC_DEBUG
+	++n_ea_calendar_cell_created;
+	g_print (
+		"ACC_DEBUG: n_ea_calendar_cell_created = %d\n",
+		n_ea_calendar_cell_created);
+#endif
+	return atk_object;
+}
+
+#ifdef ACC_DEBUG
+static void ea_calendar_cell_finalize (GObject *object)
+{
+	G_OBJECT_CLASS (parent_class)->finalize (object);
+
+	++n_ea_calendar_cell_destroyed;
+	g_print (
+		"ACC_DEBUG: n_ea_calendar_cell_destroyed = %d\n",
+		n_ea_calendar_cell_destroyed);
+}
+#endif
+
+static const gchar *
+ea_calendar_cell_get_name (AtkObject *accessible)
+{
+	GObject *g_obj;
+
+	g_return_val_if_fail (EA_IS_CALENDAR_CELL (accessible), NULL);
+
+	g_obj = atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (accessible));
+	if (!g_obj)
+		/* defunct object*/
+		return NULL;
+
+	if (!accessible->name) {
+		AtkObject *atk_obj;
+		EaCalendarItem *ea_calitem;
+		ECalendarCell *cell;
+		gint day_index;
+		gint year, month, day;
+		gchar buffer[128];
+
+		cell = E_CALENDAR_CELL (g_obj);
+		atk_obj = ea_calendar_cell_get_parent (accessible);
+		ea_calitem = EA_CALENDAR_ITEM (atk_obj);
+		day_index = atk_table_get_index_at (
+			ATK_TABLE (ea_calitem),
+			cell->row, cell->column);
+		e_calendar_item_get_date_for_offset (cell->calitem, day_index,
+						     &year, &month, &day);
+
+		g_snprintf (buffer, 128, "%d-%d-%d", year, month + 1, day);
+		ATK_OBJECT_CLASS (parent_class)->set_name (accessible, buffer);
+	}
+	return accessible->name;
+}
+
+static const gchar *
+ea_calendar_cell_get_description (AtkObject *accessible)
+{
+	return ea_calendar_cell_get_name (accessible);
+}
+
+static AtkObject *
+ea_calendar_cell_get_parent (AtkObject *accessible)
+{
+	GObject *g_obj;
+	ECalendarCell *cell;
+	ECalendarItem *calitem;
+
+	g_return_val_if_fail (EA_IS_CALENDAR_CELL (accessible), NULL);
+
+	g_obj = atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (accessible));
+	if (!g_obj)
+		/* defunct object*/
+		return NULL;
+
+	cell = E_CALENDAR_CELL (g_obj);
+	calitem = cell->calitem;
+	return atk_gobject_accessible_for_object (G_OBJECT (calitem));
+}
+
+static gint
+ea_calendar_cell_get_index_in_parent (AtkObject *accessible)
+{
+	GObject *g_obj;
+	ECalendarCell *cell;
+	AtkObject *parent;
+
+	g_return_val_if_fail (EA_IS_CALENDAR_CELL (accessible), -1);
+
+	g_obj = atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (accessible));
+	if (!g_obj)
+		return -1;
+	cell = E_CALENDAR_CELL (g_obj);
+	parent = atk_object_get_parent (accessible);
+	return atk_table_get_index_at (
+		ATK_TABLE (parent),
+		cell->row, cell->column);
+}
+
+static AtkStateSet *
+ea_calendar_cell_ref_state_set (AtkObject *accessible)
+{
+	EaCalendarCell *atk_cell = EA_CALENDAR_CELL (accessible);
+
+	g_return_val_if_fail (atk_cell->state_set, NULL);
+
+	g_object_ref (atk_cell->state_set);
+
+	return atk_cell->state_set;
+
+}
+
+/* Atk Component Interface */
+
+static void
+atk_component_interface_init (AtkComponentIface *iface)
+{
+	g_return_if_fail (iface != NULL);
+
+	iface->get_extents = component_interface_get_extents;
+	iface->grab_focus  = component_interface_grab_focus;
+}
+
+static void
+component_interface_get_extents (AtkComponent *component,
+                                 gint *x,
+                                 gint *y,
+                                 gint *width,
+                                 gint *height,
+                                 AtkCoordType coord_type)
+{
+	GObject *g_obj;
+	AtkObject *atk_obj, *atk_canvas;
+	ECalendarCell *cell;
+	ECalendarItem *calitem;
+	EaCalendarItem *ea_calitem;
+	gint day_index;
+	gint year, month, day;
+	gint canvas_x, canvas_y, canvas_width, canvas_height;
+
+	*x = *y = *width = *height = 0;
+
+	g_return_if_fail (EA_IS_CALENDAR_CELL (component));
+
+	g_obj = atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (component));
+	if (!g_obj)
+		/* defunct object*/
+		return;
+
+	cell = E_CALENDAR_CELL (g_obj);
+	calitem = cell->calitem;
+	atk_obj = atk_gobject_accessible_for_object (G_OBJECT (calitem));
+	ea_calitem = EA_CALENDAR_ITEM (atk_obj);
+	day_index = atk_table_get_index_at (
+		ATK_TABLE (ea_calitem),
+		cell->row, cell->column);
+	e_calendar_item_get_date_for_offset (calitem, day_index,
+					     &year, &month, &day);
+
+	if (!e_calendar_item_get_day_extents (calitem,
+					      year, month, day,
+					      x, y, width, height))
+	    return;
+	atk_canvas = atk_object_get_parent (ATK_OBJECT (ea_calitem));
+	atk_component_get_extents (
+		ATK_COMPONENT (atk_canvas),
+		&canvas_x, &canvas_y,
+		&canvas_width, &canvas_height,
+		coord_type);
+	*x += canvas_x;
+	*y += canvas_y;
+}
+
+static gboolean
+component_interface_grab_focus (AtkComponent *component)
+{
+	GObject *g_obj;
+	GtkWidget *toplevel;
+	AtkObject *ea_calitem;
+	ECalendarItem *calitem;
+	EaCalendarCell *a11y;
+	gint index;
+
+	a11y = EA_CALENDAR_CELL (component);
+	ea_calitem = ea_calendar_cell_get_parent (ATK_OBJECT (a11y));
+
+	g_obj = atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (ea_calitem));
+	calitem = E_CALENDAR_ITEM (g_obj);
+
+	index = atk_object_get_index_in_parent (ATK_OBJECT (a11y));
+
+	atk_selection_clear_selection (ATK_SELECTION (ea_calitem));
+	atk_selection_add_selection (ATK_SELECTION (ea_calitem), index);
+
+	gtk_widget_grab_focus (GTK_WIDGET (GNOME_CANVAS_ITEM (calitem)->canvas));
+	toplevel = gtk_widget_get_toplevel (
+		GTK_WIDGET (GNOME_CANVAS_ITEM (calitem)->canvas));
+	if (toplevel && gtk_widget_is_toplevel (toplevel))
+		gtk_window_present (GTK_WINDOW (toplevel));
+
+	return TRUE;
+
+}
diff --git a/e-util/ea-calendar-cell.h b/e-util/ea-calendar-cell.h
new file mode 100644
index 0000000..2d228c2
--- /dev/null
+++ b/e-util/ea-calendar-cell.h
@@ -0,0 +1,90 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Bolian Yin <bolian yin sun com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef __EA_CALENDAR_CELL_H__
+#define __EA_CALENDAR_CELL_H__
+
+#include <atk/atkgobjectaccessible.h>
+#include <e-util/e-calendar-item.h>
+
+G_BEGIN_DECLS
+
+#define E_TYPE_CALENDAR_CELL                     (e_calendar_cell_get_type ())
+#define E_CALENDAR_CELL(obj)                     (G_TYPE_CHECK_INSTANCE_CAST ((obj), E_TYPE_CALENDAR_CELL, ECalendarCell))
+#define E_CALENDAR_CELL_CLASS(klass)             (G_TYPE_CHECK_CLASS_CAST ((klass), E_TYPE_CALENDAR_CELL, ECalendarCellClass))
+#define E_IS_CALENDAR_CELL(obj)                  (G_TYPE_CHECK_INSTANCE_TYPE ((obj), E_TYPE_CALENDAR_CELL))
+#define E_IS_CALENDAR_CELL_CLASS(klass)          (G_TYPE_CHECK_CLASS_TYPE ((klass), E_TYPE_CALENDAR_CELL))
+#define E_CALENDAR_CELL_GET_CLASS(obj)           (G_TYPE_INSTANCE_GET_CLASS ((obj), E_TYPE_CALENDAR_CELL, ECalendarCellClass))
+
+typedef struct _ECalendarCell                   ECalendarCell;
+typedef struct _ECalendarCellClass              ECalendarCellClass;
+
+struct _ECalendarCell
+{
+	GObject parent;
+	ECalendarItem *calitem;
+	gint row;
+	gint column;
+};
+
+GType e_calendar_cell_get_type (void);
+
+struct _ECalendarCellClass
+{
+	GObjectClass parent_class;
+};
+
+ECalendarCell * e_calendar_cell_new (ECalendarItem *calitem,
+				     gint row, gint column);
+
+#define EA_TYPE_CALENDAR_CELL                     (ea_calendar_cell_get_type ())
+#define EA_CALENDAR_CELL(obj)                     (G_TYPE_CHECK_INSTANCE_CAST ((obj), EA_TYPE_CALENDAR_CELL, EaCalendarCell))
+#define EA_CALENDAR_CELL_CLASS(klass)             (G_TYPE_CHECK_CLASS_CAST ((klass), EA_TYPE_CALENDAR_CELL, EaCalendarCellClass))
+#define EA_IS_CALENDAR_CELL(obj)                  (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EA_TYPE_CALENDAR_CELL))
+#define EA_IS_CALENDAR_CELL_CLASS(klass)          (G_TYPE_CHECK_CLASS_TYPE ((klass), EA_TYPE_CALENDAR_CELL))
+#define EA_CALENDAR_CELL_GET_CLASS(obj)           (G_TYPE_INSTANCE_GET_CLASS ((obj), EA_TYPE_CALENDAR_CELL, EaCalendarCellClass))
+
+typedef struct _EaCalendarCell                   EaCalendarCell;
+typedef struct _EaCalendarCellClass              EaCalendarCellClass;
+
+struct _EaCalendarCell
+{
+	AtkGObjectAccessible parent;
+	AtkStateSet *state_set;
+};
+
+GType ea_calendar_cell_get_type (void);
+
+struct _EaCalendarCellClass
+{
+	AtkGObjectAccessibleClass parent_class;
+};
+
+AtkObject *     ea_calendar_cell_new         (GObject *gobj);
+
+G_END_DECLS
+
+#endif /* __EA_CALENDAR_CELL_H__ */
diff --git a/e-util/ea-calendar-item.c b/e-util/ea-calendar-item.c
new file mode 100644
index 0000000..2f5ac91
--- /dev/null
+++ b/e-util/ea-calendar-item.c
@@ -0,0 +1,1373 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Bolian Yin <bolian yin sun com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <time.h>
+#include <string.h>
+#include <libgnomecanvas/gnome-canvas.h>
+#include <glib/gi18n.h>
+
+#include <libedataserver/libedataserver.h>
+
+#include "ea-calendar-item.h"
+#include "ea-calendar-cell.h"
+#include "ea-cell-table.h"
+
+#include "e-misc-utils.h"
+
+#define EA_CALENDAR_COLUMN_NUM E_CALENDAR_COLS_PER_MONTH
+
+/* EaCalendarItem */
+static void ea_calendar_item_class_init (EaCalendarItemClass *class);
+static void ea_calendar_item_finalize (GObject *object);
+
+static const gchar * ea_calendar_item_get_name (AtkObject *accessible);
+static const gchar * ea_calendar_item_get_description (AtkObject *accessible);
+static gint ea_calendar_item_get_n_children (AtkObject *accessible);
+static AtkObject *ea_calendar_item_ref_child (AtkObject *accessible, gint index);
+static AtkStateSet * ea_calendar_item_ref_state_set (AtkObject *accessible);
+
+/* atk table interface */
+static void atk_table_interface_init (AtkTableIface *iface);
+static gint table_interface_get_index_at (AtkTable *table,
+					  gint     row,
+					  gint     column);
+static gint table_interface_get_column_at_index (AtkTable *table,
+						 gint     index);
+static gint table_interface_get_row_at_index (AtkTable *table,
+					      gint     index);
+static AtkObject * table_interface_ref_at (AtkTable *table,
+					  gint     row,
+					  gint     column);
+static gint table_interface_get_n_rows (AtkTable *table);
+static gint table_interface_get_n_columns (AtkTable *table);
+static gint table_interface_get_column_extent_at (AtkTable      *table,
+						  gint          row,
+						  gint          column);
+static gint table_interface_get_row_extent_at (AtkTable      *table,
+					       gint          row,
+					       gint          column);
+
+static gboolean table_interface_is_row_selected (AtkTable *table,
+						 gint     row);
+static gboolean table_interface_is_column_selected (AtkTable *table,
+						    gint     row);
+static gboolean table_interface_is_selected (AtkTable *table,
+					     gint     row,
+					     gint     column);
+static gint table_interface_get_selected_rows (AtkTable *table,
+					       gint **rows_selected);
+static gint table_interface_get_selected_columns (AtkTable *table,
+						  gint     **columns_selected);
+static gboolean table_interface_add_row_selection (AtkTable *table, gint row);
+static gboolean table_interface_remove_row_selection (AtkTable *table,
+						      gint row);
+static gboolean table_interface_add_column_selection (AtkTable *table,
+						      gint column);
+static gboolean table_interface_remove_column_selection (AtkTable *table,
+							 gint column);
+static AtkObject * table_interface_get_row_header (AtkTable *table, gint row);
+static AtkObject * table_interface_get_column_header (AtkTable *table,
+						     gint in_col);
+static AtkObject * table_interface_get_caption (AtkTable *table);
+
+static const gchar *
+table_interface_get_column_description (AtkTable *table,
+                                        gint in_col);
+
+static const gchar *
+table_interface_get_row_description (AtkTable *table,
+                                     gint row);
+
+static AtkObject *table_interface_get_summary (AtkTable *table);
+
+/* atk selection interface */
+static void atk_selection_interface_init (AtkSelectionIface *iface);
+static gboolean selection_interface_add_selection (AtkSelection *selection,
+                                                   gint i);
+static gboolean selection_interface_clear_selection (AtkSelection *selection);
+static AtkObject *selection_interface_ref_selection (AtkSelection *selection,
+                                                     gint i);
+static gint selection_interface_get_selection_count (AtkSelection *selection);
+static gboolean selection_interface_is_child_selected (AtkSelection *selection,
+                                                       gint i);
+
+/* callbacks */
+static void selection_preview_change_cb (ECalendarItem *calitem);
+static void date_range_changed_cb (ECalendarItem *calitem);
+
+/* helpers */
+static EaCellTable *ea_calendar_item_get_cell_data (EaCalendarItem *ea_calitem);
+static void ea_calendar_item_destory_cell_data (EaCalendarItem *ea_calitem);
+static gboolean ea_calendar_item_get_column_label (EaCalendarItem *ea_calitem,
+                                                   gint column,
+                                                   gchar *buffer,
+                                                   gint buffer_size);
+static gboolean ea_calendar_item_get_row_label (EaCalendarItem *ea_calitem,
+                                                gint row,
+                                                gchar *buffer,
+                                                gint buffer_size);
+static gboolean e_calendar_item_get_offset_for_date (ECalendarItem *calitem,
+                                                     gint year,
+                                                     gint month,
+                                                     gint day,
+                                                     gint *offset);
+static void ea_calendar_set_focus_object (EaCalendarItem *ea_calitem,
+                                          AtkObject *item_cell);
+
+#ifdef ACC_DEBUG
+static gint n_ea_calendar_item_created = 0;
+static gint n_ea_calendar_item_destroyed = 0;
+#endif
+
+static gpointer parent_class = NULL;
+
+GType
+ea_calendar_item_get_type (void)
+{
+	static GType type = 0;
+	AtkObjectFactory *factory;
+	GTypeQuery query;
+	GType derived_atk_type;
+
+	if (!type) {
+		static GTypeInfo tinfo = {
+			sizeof (EaCalendarItemClass),
+			(GBaseInitFunc) NULL, /* base init */
+			(GBaseFinalizeFunc) NULL, /* base finalize */
+			(GClassInitFunc) ea_calendar_item_class_init, /* class init */
+			(GClassFinalizeFunc) NULL, /* class finalize */
+			NULL, /* class data */
+			sizeof (EaCalendarItem), /* instance size */
+			0, /* nb preallocs */
+			(GInstanceInitFunc) NULL, /* instance init */
+			NULL /* value table */
+		};
+
+		static const GInterfaceInfo atk_table_info = {
+			(GInterfaceInitFunc) atk_table_interface_init,
+			(GInterfaceFinalizeFunc) NULL,
+			NULL
+		};
+		static const GInterfaceInfo atk_selection_info = {
+			(GInterfaceInitFunc) atk_selection_interface_init,
+			(GInterfaceFinalizeFunc) NULL,
+			NULL
+		};
+
+		/*
+		 * Figure out the size of the class and instance
+		 * we are run-time deriving from (GailCanvasItem, in this case)
+		 */
+
+		factory = atk_registry_get_factory (
+			atk_get_default_registry (),
+			GNOME_TYPE_CANVAS_ITEM);
+		derived_atk_type = atk_object_factory_get_accessible_type (factory);
+		g_type_query (derived_atk_type, &query);
+
+		tinfo.class_size = query.class_size;
+		tinfo.instance_size = query.instance_size;
+
+		type = g_type_register_static (
+			derived_atk_type,
+			"EaCalendarItem", &tinfo, 0);
+		g_type_add_interface_static (
+			type, ATK_TYPE_TABLE,
+			&atk_table_info);
+		g_type_add_interface_static (
+			type, ATK_TYPE_SELECTION,
+			&atk_selection_info);
+	}
+
+	return type;
+}
+
+static void
+ea_calendar_item_class_init (EaCalendarItemClass *klass)
+{
+	GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+	AtkObjectClass *class = ATK_OBJECT_CLASS (klass);
+
+	gobject_class->finalize = ea_calendar_item_finalize;
+	parent_class = g_type_class_peek_parent (klass);
+
+	class->get_name = ea_calendar_item_get_name;
+	class->get_description = ea_calendar_item_get_description;
+	class->ref_state_set = ea_calendar_item_ref_state_set;
+
+	class->get_n_children = ea_calendar_item_get_n_children;
+	class->ref_child = ea_calendar_item_ref_child;
+}
+
+AtkObject *
+ea_calendar_item_new (GObject *obj)
+{
+	gpointer object;
+	AtkObject *atk_object;
+	AtkObject *item_cell;
+
+	g_return_val_if_fail (E_IS_CALENDAR_ITEM (obj), NULL);
+	object = g_object_new (EA_TYPE_CALENDAR_ITEM, NULL);
+	atk_object = ATK_OBJECT (object);
+	atk_object_initialize (atk_object, obj);
+	atk_object->role = ATK_ROLE_CALENDAR;
+
+	item_cell = atk_selection_ref_selection (
+		ATK_SELECTION (atk_object), 0);
+	if (item_cell)
+		ea_calendar_set_focus_object (EA_CALENDAR_ITEM (atk_object), item_cell);
+
+#ifdef ACC_DEBUG
+	++n_ea_calendar_item_created;
+	g_print (
+		"ACC_DEBUG: n_ea_calendar_item_created = %d\n",
+		n_ea_calendar_item_created);
+#endif
+	/* connect signal handlers */
+	g_signal_connect (
+		obj, "selection_preview_changed",
+		G_CALLBACK (selection_preview_change_cb), atk_object);
+	g_signal_connect (
+		obj, "date_range_changed",
+		G_CALLBACK (date_range_changed_cb), atk_object);
+
+	return atk_object;
+}
+
+static void
+ea_calendar_item_finalize (GObject *object)
+{
+	EaCalendarItem *ea_calitem;
+
+	g_return_if_fail (EA_IS_CALENDAR_ITEM (object));
+
+	ea_calitem = EA_CALENDAR_ITEM (object);
+
+	/* Free the allocated cell data */
+	ea_calendar_item_destory_cell_data (ea_calitem);
+
+	G_OBJECT_CLASS (parent_class)->finalize (object);
+#ifdef ACC_DEBUG
+	++n_ea_calendar_item_destroyed;
+	printf (
+		"ACC_DEBUG: n_ea_calendar_item_destroyed = %d\n",
+		n_ea_calendar_item_destroyed);
+#endif
+}
+
+static const gchar *
+ea_calendar_item_get_name (AtkObject *accessible)
+{
+	GObject *g_obj;
+	ECalendarItem *calitem;
+	gint start_year, start_month, start_day;
+	gint end_year, end_month, end_day;
+	gchar *name_str = NULL;
+	gchar buffer_start[128] = "";
+	gchar buffer_end[128] = "";
+	struct tm day_start = { 0 };
+	struct tm day_end = { 0 };
+
+	g_return_val_if_fail (EA_IS_CALENDAR_ITEM (accessible), NULL);
+
+	g_obj = atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (accessible));
+	if (!g_obj)
+		return NULL;
+	g_return_val_if_fail (E_IS_CALENDAR_ITEM (g_obj), NULL);
+
+	calitem = E_CALENDAR_ITEM (g_obj);
+	if (e_calendar_item_get_date_range (
+		calitem,
+		&start_year, &start_month, &start_day,
+		&end_year, &end_month, &end_day)) {
+
+		day_start.tm_year = start_year - 1900;
+		day_start.tm_mon = start_month;
+		day_start.tm_mday = start_day;
+		day_start.tm_isdst = -1;
+
+		e_utf8_strftime (
+			buffer_start, sizeof (buffer_start),
+			_("%d %B %Y"), &day_start);
+
+		day_end.tm_year = end_year - 1900;
+		day_end.tm_mon = end_month;
+		day_end.tm_mday = end_day;
+		day_end.tm_isdst = -1;
+
+		e_utf8_strftime (
+			buffer_end, sizeof (buffer_end),
+			_("%d %B %Y"), &day_end);
+
+		name_str = g_strdup_printf (
+			_("Calendar: from %s to %s"),
+			buffer_start, buffer_end);
+	}
+
+#if 0
+	if (e_calendar_item_get_selection (calitem, &select_start, &select_end)) {
+		GDate select_start, select_end;
+		gint year1, year2, month1, month2, day1, day2;
+
+		year1 = g_date_get_year (&select_start);
+		month1  = g_date_get_month (&select_start);
+		day1 = g_date_get_day (&select_start);
+
+		year2 = g_date_get_year (&select_end);
+		month2  = g_date_get_month (&select_end);
+		day2 = g_date_get_day (&select_end);
+
+		sprintf (
+			new_name + strlen (new_name),
+			" : current selection: from %d-%d-%d to %d-%d-%d.",
+			year1, month1, day1,
+			year2, month2, day2);
+	}
+#endif
+
+	ATK_OBJECT_CLASS (parent_class)->set_name (accessible, name_str);
+	g_free (name_str);
+
+	return accessible->name;
+}
+
+static const gchar *
+ea_calendar_item_get_description (AtkObject *accessible)
+{
+	if (accessible->description)
+		return accessible->description;
+
+	return _("evolution calendar item");
+}
+
+static AtkStateSet *
+ea_calendar_item_ref_state_set (AtkObject *accessible)
+{
+	AtkStateSet *state_set;
+	GObject *g_obj;
+
+	state_set = ATK_OBJECT_CLASS (parent_class)->ref_state_set (accessible);
+	g_obj = atk_gobject_accessible_get_object (
+		ATK_GOBJECT_ACCESSIBLE (accessible));
+	if (!g_obj)
+		return state_set;
+
+	atk_state_set_add_state (state_set, ATK_STATE_ENABLED);
+	atk_state_set_add_state (state_set, ATK_STATE_SENSITIVE);
+
+	return state_set;
+}
+
+static gint
+ea_calendar_item_get_n_children (AtkObject *accessible)
+{
+	AtkGObjectAccessible *atk_gobj;
+	GObject *g_obj;
+	ECalendarItem *calitem;
+	gint n_children = 0;
+	gint start_year, start_month, start_day;
+	gint end_year, end_month, end_day;
+	GDate *start_date, *end_date;
+
+	g_return_val_if_fail (EA_IS_CALENDAR_ITEM (accessible), -1);
+
+	atk_gobj = ATK_GOBJECT_ACCESSIBLE (accessible);
+	g_obj = atk_gobject_accessible_get_object (atk_gobj);
+	if (!g_obj)
+		return -1;
+
+	calitem = E_CALENDAR_ITEM (g_obj);
+	if (!e_calendar_item_get_date_range (calitem, &start_year,
+					     &start_month, &start_day,
+					     &end_year, &end_month,
+					     &end_day))
+		return 0;
+
+	start_date = g_date_new_dmy (start_day, start_month + 1, start_year);
+	end_date = g_date_new_dmy (end_day, end_month + 1, end_year);
+
+	n_children = g_date_days_between (start_date, end_date) + 1;
+	g_free (start_date);
+	g_free (end_date);
+	return n_children;
+}
+
+static AtkObject *
+ea_calendar_item_ref_child (AtkObject *accessible,
+                            gint index)
+{
+	AtkGObjectAccessible *atk_gobj;
+	GObject *g_obj;
+	ECalendarItem *calitem;
+	gint n_children;
+	ECalendarCell *cell;
+	EaCellTable *cell_data;
+	EaCalendarItem *ea_calitem;
+
+	g_return_val_if_fail (EA_IS_CALENDAR_ITEM (accessible), NULL);
+
+	atk_gobj = ATK_GOBJECT_ACCESSIBLE (accessible);
+	g_obj = atk_gobject_accessible_get_object (atk_gobj);
+	if (!g_obj)
+		return NULL;
+
+	calitem = E_CALENDAR_ITEM (g_obj);
+
+	n_children = ea_calendar_item_get_n_children (accessible);
+	if (index < 0 || index >= n_children)
+		return NULL;
+
+	ea_calitem = EA_CALENDAR_ITEM (accessible);
+	cell_data = ea_calendar_item_get_cell_data (ea_calitem);
+	if (!cell_data)
+		return NULL;
+
+	cell = ea_cell_table_get_cell_at_index (cell_data, index);
+	if (!cell) {
+		cell = e_calendar_cell_new (
+			calitem,
+			index / EA_CALENDAR_COLUMN_NUM,
+			index % EA_CALENDAR_COLUMN_NUM);
+		ea_cell_table_set_cell_at_index (cell_data, index, cell);
+		g_object_unref (cell);
+	}
+
+#ifdef ACC_DEBUG
+	g_print (
+		"AccDebug: ea_calendar_item children[%d]=%p\n", index,
+		(gpointer) cell);
+#endif
+	return g_object_ref (atk_gobject_accessible_for_object (G_OBJECT (cell)));
+}
+
+/* atk table interface */
+
+static void
+atk_table_interface_init (AtkTableIface *iface)
+{
+	g_return_if_fail (iface != NULL);
+
+	iface->ref_at = table_interface_ref_at;
+
+	iface->get_n_rows = table_interface_get_n_rows;
+	iface->get_n_columns = table_interface_get_n_columns;
+	iface->get_index_at = table_interface_get_index_at;
+	iface->get_column_at_index = table_interface_get_column_at_index;
+	iface->get_row_at_index = table_interface_get_row_at_index;
+	iface->get_column_extent_at = table_interface_get_column_extent_at;
+	iface->get_row_extent_at = table_interface_get_row_extent_at;
+
+	iface->is_selected = table_interface_is_selected;
+	iface->get_selected_rows = table_interface_get_selected_rows;
+	iface->get_selected_columns = table_interface_get_selected_columns;
+	iface->is_row_selected = table_interface_is_row_selected;
+	iface->is_column_selected = table_interface_is_column_selected;
+	iface->add_row_selection = table_interface_add_row_selection;
+	iface->remove_row_selection = table_interface_remove_row_selection;
+	iface->add_column_selection = table_interface_add_column_selection;
+	iface->remove_column_selection = table_interface_remove_column_selection;
+
+	iface->get_row_header = table_interface_get_row_header;
+	iface->get_column_header = table_interface_get_column_header;
+	iface->get_caption = table_interface_get_caption;
+	iface->get_summary = table_interface_get_summary;
+	iface->get_row_description = table_interface_get_row_description;
+	iface->get_column_description = table_interface_get_column_description;
+}
+
+static AtkObject *
+table_interface_ref_at (AtkTable *table,
+                        gint row,
+                        gint column)
+{
+	gint index;
+
+	EaCalendarItem * ea_calitem = EA_CALENDAR_ITEM (table);
+	index = EA_CALENDAR_COLUMN_NUM * row + column;
+	return ea_calendar_item_ref_child (ATK_OBJECT (ea_calitem), index);
+}
+
+static gint
+table_interface_get_n_rows (AtkTable *table)
+{
+	AtkGObjectAccessible *atk_gobj;
+	GObject *g_obj;
+	EaCalendarItem * ea_calitem = EA_CALENDAR_ITEM (table);
+	gint n_children;
+
+	atk_gobj = ATK_GOBJECT_ACCESSIBLE (ea_calitem);
+	g_obj = atk_gobject_accessible_get_object (atk_gobj);
+	if (!g_obj)
+		return -1;
+
+	n_children = ea_calendar_item_get_n_children (ATK_OBJECT (ea_calitem));
+	return (n_children - 1) / EA_CALENDAR_COLUMN_NUM + 1;
+}
+
+static gint
+table_interface_get_n_columns (AtkTable *table)
+{
+	AtkGObjectAccessible *atk_gobj;
+	GObject *g_obj;
+	EaCalendarItem * ea_calitem = EA_CALENDAR_ITEM (table);
+
+	atk_gobj = ATK_GOBJECT_ACCESSIBLE (ea_calitem);
+	g_obj = atk_gobject_accessible_get_object (atk_gobj);
+	if (!g_obj)
+		return -1;
+
+	return EA_CALENDAR_COLUMN_NUM;
+}
+
+static gint
+table_interface_get_index_at (AtkTable *table,
+                              gint row,
+                              gint column)
+{
+	AtkGObjectAccessible *atk_gobj;
+	GObject *g_obj;
+	EaCalendarItem * ea_calitem = EA_CALENDAR_ITEM (table);
+
+	atk_gobj = ATK_GOBJECT_ACCESSIBLE (ea_calitem);
+	g_obj = atk_gobject_accessible_get_object (atk_gobj);
+	if (!g_obj)
+		return -1;
+
+	return row * EA_CALENDAR_COLUMN_NUM + column;
+}
+
+static gint
+table_interface_get_column_at_index (AtkTable *table,
+                                     gint index)
+{
+	AtkGObjectAccessible *atk_gobj;
+	GObject *g_obj;
+	EaCalendarItem * ea_calitem = EA_CALENDAR_ITEM (table);
+	gint n_children;
+
+	atk_gobj = ATK_GOBJECT_ACCESSIBLE (ea_calitem);
+	g_obj = atk_gobject_accessible_get_object (atk_gobj);
+	if (!g_obj)
+		return -1;
+
+	n_children = ea_calendar_item_get_n_children (ATK_OBJECT (ea_calitem));
+	if (index >= 0 && index < n_children)
+		return index % EA_CALENDAR_COLUMN_NUM;
+	return -1;
+}
+
+static gint
+table_interface_get_row_at_index (AtkTable *table,
+                                  gint index)
+{
+	AtkGObjectAccessible *atk_gobj;
+	GObject *g_obj;
+	EaCalendarItem * ea_calitem = EA_CALENDAR_ITEM (table);
+	gint n_children;
+
+	atk_gobj = ATK_GOBJECT_ACCESSIBLE (ea_calitem);
+	g_obj = atk_gobject_accessible_get_object (atk_gobj);
+	if (!g_obj)
+		return -1;
+
+	n_children = ea_calendar_item_get_n_children (ATK_OBJECT (ea_calitem));
+	if (index >= 0 && index < n_children)
+		return index / EA_CALENDAR_COLUMN_NUM;
+	return -1;
+}
+
+static gint
+table_interface_get_column_extent_at (AtkTable *table,
+                                      gint row,
+                                      gint column)
+{
+	AtkGObjectAccessible *atk_gobj;
+	GObject *g_obj;
+	ECalendarItem *calitem;
+	EaCalendarItem * ea_calitem = EA_CALENDAR_ITEM (table);
+
+	atk_gobj = ATK_GOBJECT_ACCESSIBLE (ea_calitem);
+	g_obj = atk_gobject_accessible_get_object (atk_gobj);
+	if (!g_obj)
+		return FALSE;
+
+	calitem = E_CALENDAR_ITEM (g_obj);
+	return calitem->cell_width;
+}
+
+static gint
+table_interface_get_row_extent_at (AtkTable *table,
+                                   gint row,
+                                   gint column)
+{
+	AtkGObjectAccessible *atk_gobj;
+	GObject *g_obj;
+	ECalendarItem *calitem;
+	EaCalendarItem * ea_calitem = EA_CALENDAR_ITEM (table);
+
+	atk_gobj = ATK_GOBJECT_ACCESSIBLE (ea_calitem);
+	g_obj = atk_gobject_accessible_get_object (atk_gobj);
+	if (!g_obj)
+		return FALSE;
+
+	calitem = E_CALENDAR_ITEM (g_obj);
+	return calitem->cell_height;
+}
+
+/* any day in the row is selected, the row is selected */
+static gboolean
+table_interface_is_row_selected (AtkTable *table,
+                                 gint row)
+{
+	AtkGObjectAccessible *atk_gobj;
+	GObject *g_obj;
+	gint n_rows;
+	ECalendarItem *calitem;
+	gint row_index_start, row_index_end;
+	gint sel_index_start, sel_index_end;
+
+	GDate start_date, end_date;
+
+	g_return_val_if_fail (EA_IS_CALENDAR_ITEM (table), FALSE);
+
+	atk_gobj = ATK_GOBJECT_ACCESSIBLE (table);
+	g_obj = atk_gobject_accessible_get_object (atk_gobj);
+	if (!g_obj)
+		return FALSE;
+
+	n_rows = table_interface_get_n_rows (table);
+	if (row < 0 || row >= n_rows)
+		return FALSE;
+
+	row_index_start = row * EA_CALENDAR_COLUMN_NUM;
+	row_index_end = row_index_start + EA_CALENDAR_COLUMN_NUM - 1;
+
+	calitem = E_CALENDAR_ITEM (g_obj);
+	if (!e_calendar_item_get_selection (calitem, &start_date, &end_date))
+		return FALSE;
+
+	e_calendar_item_get_offset_for_date (calitem,
+					     g_date_get_year (&start_date),
+					     g_date_get_month (&start_date),
+					     g_date_get_day (&start_date),
+					     &sel_index_start);
+	e_calendar_item_get_offset_for_date (calitem,
+					     g_date_get_year (&end_date),
+					     g_date_get_month (&end_date),
+					     g_date_get_day (&end_date),
+					     &sel_index_end);
+
+	if ((sel_index_start < row_index_start &&
+	     sel_index_end >= row_index_start) ||
+	    (sel_index_start >= row_index_start &&
+	     sel_index_start <= row_index_end))
+	    return TRUE;
+	return FALSE;
+}
+
+static gboolean
+table_interface_is_selected (AtkTable *table,
+                             gint row,
+                             gint column)
+{
+	AtkGObjectAccessible *atk_gobj;
+	GObject *g_obj;
+	gint n_rows, n_columns;
+	ECalendarItem *calitem;
+	gint index;
+	gint sel_index_start, sel_index_end;
+
+	GDate start_date, end_date;
+
+	g_return_val_if_fail (EA_IS_CALENDAR_ITEM (table), FALSE);
+
+	atk_gobj = ATK_GOBJECT_ACCESSIBLE (table);
+	g_obj = atk_gobject_accessible_get_object (atk_gobj);
+	if (!g_obj)
+		return FALSE;
+
+	n_rows = table_interface_get_n_rows (table);
+	if (row < 0 || row >= n_rows)
+		return FALSE;
+	n_columns = table_interface_get_n_columns (table);
+	if (column < 0 || column >= n_columns)
+		return FALSE;
+
+	index = table_interface_get_index_at (table, row, column);
+
+	calitem = E_CALENDAR_ITEM (g_obj);
+	if (!e_calendar_item_get_selection (calitem, &start_date, &end_date))
+		return FALSE;
+
+	e_calendar_item_get_offset_for_date (calitem,
+					     g_date_get_year (&start_date),
+					     g_date_get_month (&start_date),
+					     g_date_get_day (&start_date),
+					     &sel_index_start);
+	e_calendar_item_get_offset_for_date (calitem,
+					     g_date_get_year (&end_date),
+					     g_date_get_month (&end_date),
+					     g_date_get_day (&end_date), &sel_index_end);
+
+	if (sel_index_start <= index && sel_index_end >= index)
+	    return TRUE;
+	return FALSE;
+}
+
+static gboolean
+table_interface_is_column_selected (AtkTable *table,
+                                    gint column)
+{
+	return FALSE;
+}
+
+static gint
+table_interface_get_selected_rows (AtkTable *table,
+                                   gint **rows_selected)
+{
+	*rows_selected = NULL;
+	return -1;
+}
+
+static gint
+table_interface_get_selected_columns (AtkTable *table,
+                                      gint **columns_selected)
+{
+	*columns_selected = NULL;
+	return -1;
+}
+
+static gboolean
+table_interface_add_row_selection (AtkTable *table,
+                                   gint row)
+{
+	return FALSE;
+}
+
+static gboolean
+table_interface_remove_row_selection (AtkTable *table,
+                                      gint row)
+{
+	return FALSE;
+}
+
+static gboolean
+table_interface_add_column_selection (AtkTable *table,
+                                      gint column)
+{
+	return FALSE;
+}
+
+static gboolean
+table_interface_remove_column_selection (AtkTable *table,
+                                         gint column)
+{
+	/* FIXME: NOT IMPLEMENTED */
+	return FALSE;
+}
+
+static AtkObject *
+table_interface_get_row_header (AtkTable *table,
+                                gint row)
+{
+	/* FIXME: NOT IMPLEMENTED */
+	return NULL;
+}
+
+static AtkObject *
+table_interface_get_column_header (AtkTable *table,
+                                   gint in_col)
+{
+	/* FIXME: NOT IMPLEMENTED */
+	return NULL;
+}
+
+static AtkObject *
+table_interface_get_caption (AtkTable *table)
+{
+	/* FIXME: NOT IMPLEMENTED */
+	return NULL;
+}
+
+static const gchar *
+table_interface_get_column_description (AtkTable *table,
+                                        gint in_col)
+{
+	AtkGObjectAccessible *atk_gobj;
+	GObject *g_obj;
+	EaCalendarItem * ea_calitem = EA_CALENDAR_ITEM (table);
+	const gchar *description = NULL;
+	EaCellTable *cell_data;
+	gint n_columns;
+
+	atk_gobj = ATK_GOBJECT_ACCESSIBLE (ea_calitem);
+	g_obj = atk_gobject_accessible_get_object (atk_gobj);
+	if (!g_obj)
+		return NULL;
+
+	n_columns = table_interface_get_n_columns (table);
+	if (in_col < 0 || in_col >= n_columns)
+		return NULL;
+	cell_data = ea_calendar_item_get_cell_data (ea_calitem);
+	if (!cell_data)
+		return NULL;
+
+	description = ea_cell_table_get_column_label (cell_data, in_col);
+	if (!description) {
+		gchar buffer[128] = "column description";
+		ea_calendar_item_get_column_label (
+			ea_calitem, in_col,
+			buffer, sizeof (buffer));
+		ea_cell_table_set_column_label (cell_data, in_col, buffer);
+		description = ea_cell_table_get_column_label (
+			cell_data, in_col);
+	}
+	return description;
+}
+
+static const gchar *
+table_interface_get_row_description (AtkTable *table,
+                                     gint row)
+{
+	AtkGObjectAccessible *atk_gobj;
+	GObject *g_obj;
+	EaCalendarItem * ea_calitem = EA_CALENDAR_ITEM (table);
+	const gchar *description = NULL;
+	EaCellTable *cell_data;
+	gint n_rows;
+
+	atk_gobj = ATK_GOBJECT_ACCESSIBLE (ea_calitem);
+	g_obj = atk_gobject_accessible_get_object (atk_gobj);
+	if (!g_obj)
+		return NULL;
+
+	n_rows = table_interface_get_n_rows (table);
+	if (row < 0 || row >= n_rows)
+		return NULL;
+	cell_data = ea_calendar_item_get_cell_data (ea_calitem);
+	if (!cell_data)
+		return NULL;
+
+	description = ea_cell_table_get_row_label (cell_data, row);
+	if (!description) {
+		gchar buffer[128] = "row description";
+		ea_calendar_item_get_row_label (
+			ea_calitem, row,
+						buffer, sizeof (buffer));
+		ea_cell_table_set_row_label (cell_data, row, buffer);
+		description = ea_cell_table_get_row_label (
+			cell_data,
+			row);
+	}
+	return description;
+}
+
+static AtkObject *
+table_interface_get_summary (AtkTable *table)
+{
+	/* FIXME: NOT IMPLEMENTED */
+	return NULL;
+}
+
+/* atkselection interface */
+
+static void
+atk_selection_interface_init (AtkSelectionIface *iface)
+{
+	g_return_if_fail (iface != NULL);
+
+	iface->add_selection = selection_interface_add_selection;
+	iface->clear_selection = selection_interface_clear_selection;
+	iface->ref_selection = selection_interface_ref_selection;
+	iface->get_selection_count = selection_interface_get_selection_count;
+	iface->is_child_selected = selection_interface_is_child_selected;
+}
+
+static gboolean
+selection_interface_add_selection (AtkSelection *selection,
+                                   gint index)
+{
+	AtkGObjectAccessible *atk_gobj;
+	GObject *g_obj;
+	ECalendarItem *calitem;
+	EaCalendarItem * ea_calitem = EA_CALENDAR_ITEM (selection);
+	gint year, month, day;
+	GDate start_date, end_date;
+
+	atk_gobj = ATK_GOBJECT_ACCESSIBLE (ea_calitem);
+	g_obj = atk_gobject_accessible_get_object (atk_gobj);
+	if (!g_obj)
+		return FALSE;
+
+	calitem = E_CALENDAR_ITEM (g_obj);
+	if (!e_calendar_item_get_date_for_offset (calitem, index,
+						  &year, &month, &day))
+		return FALSE;
+
+	/* FIXME: not support mulit-selection */
+	g_date_set_dmy (&start_date, day, month + 1, year);
+	end_date = start_date;
+	e_calendar_item_set_selection (calitem, &start_date, &end_date);
+	return TRUE;
+}
+
+static gboolean
+selection_interface_clear_selection (AtkSelection *selection)
+{
+	AtkGObjectAccessible *atk_gobj;
+	GObject *g_obj;
+	ECalendarItem *calitem;
+	EaCalendarItem * ea_calitem = EA_CALENDAR_ITEM (selection);
+
+	atk_gobj = ATK_GOBJECT_ACCESSIBLE (ea_calitem);
+	g_obj = atk_gobject_accessible_get_object (atk_gobj);
+	if (!g_obj)
+		return FALSE;
+
+	calitem = E_CALENDAR_ITEM (g_obj);
+	e_calendar_item_set_selection (calitem, NULL, NULL);
+
+	return TRUE;
+}
+
+static AtkObject *
+selection_interface_ref_selection (AtkSelection *selection,
+                                   gint i)
+{
+	GObject *g_obj;
+	ECalendarItem *calitem;
+	EaCalendarItem * ea_calitem = EA_CALENDAR_ITEM (selection);
+	gint count, sel_offset;
+	GDate start_date, end_date;
+
+	count = selection_interface_get_selection_count (selection);
+	if (i < 0 || i >= count)
+		return NULL;
+
+	g_obj = atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (ea_calitem));
+
+	calitem = E_CALENDAR_ITEM (g_obj);
+	if (!e_calendar_item_get_selection (calitem, &start_date, &end_date))
+		return NULL;
+	if (!e_calendar_item_get_offset_for_date (calitem,
+						  g_date_get_year (&start_date),
+						  g_date_get_month (&start_date) - 1,
+						  g_date_get_day (&start_date),
+						  &sel_offset))
+		return NULL;
+
+	return ea_calendar_item_ref_child (ATK_OBJECT (selection), sel_offset + i);
+}
+
+static gint
+selection_interface_get_selection_count (AtkSelection *selection)
+{
+	AtkGObjectAccessible *atk_gobj;
+	GObject *g_obj;
+	ECalendarItem *calitem;
+	EaCalendarItem * ea_calitem = EA_CALENDAR_ITEM (selection);
+	GDate start_date, end_date;
+
+	atk_gobj = ATK_GOBJECT_ACCESSIBLE (ea_calitem);
+	g_obj = atk_gobject_accessible_get_object (atk_gobj);
+	if (!g_obj)
+		return 0;
+
+	calitem = E_CALENDAR_ITEM (g_obj);
+	if (e_calendar_item_get_selection (calitem, &start_date, &end_date))
+		return g_date_days_between (&start_date, &end_date) + 1;
+	else
+		return 0;
+}
+
+static gboolean
+selection_interface_is_child_selected (AtkSelection *selection,
+                                       gint index)
+{
+	AtkGObjectAccessible *atk_gobj;
+	GObject *g_obj;
+	EaCalendarItem * ea_calitem = EA_CALENDAR_ITEM (selection);
+	gint row, column, n_children;
+
+	atk_gobj = ATK_GOBJECT_ACCESSIBLE (ea_calitem);
+	g_obj = atk_gobject_accessible_get_object (atk_gobj);
+	if (!g_obj)
+		return FALSE;
+
+	n_children = atk_object_get_n_accessible_children (ATK_OBJECT (selection));
+	if (index < 0 || index >= n_children)
+		return FALSE;
+
+	row = index / EA_CALENDAR_COLUMN_NUM;
+	column = index % EA_CALENDAR_COLUMN_NUM;
+
+	return table_interface_is_selected (ATK_TABLE (selection), row, column);
+}
+
+/* callbacks */
+
+static void
+selection_preview_change_cb (ECalendarItem *calitem)
+{
+	AtkObject *atk_obj;
+	AtkObject *item_cell;
+
+	g_return_if_fail (E_IS_CALENDAR_ITEM (calitem));
+	atk_obj = atk_gobject_accessible_for_object (G_OBJECT (calitem));
+	ea_calendar_item_destory_cell_data (EA_CALENDAR_ITEM (atk_obj));
+
+	/* only deal with the first selected child, for now */
+	item_cell = atk_selection_ref_selection (
+		ATK_SELECTION (atk_obj), 0);
+
+	if (item_cell)
+		ea_calendar_set_focus_object (EA_CALENDAR_ITEM (atk_obj), item_cell);
+
+	g_signal_emit_by_name (
+		atk_obj,
+		"active-descendant-changed",
+		item_cell);
+	g_signal_emit_by_name (atk_obj, "selection_changed");
+}
+
+static void
+date_range_changed_cb (ECalendarItem *calitem)
+{
+	AtkObject *atk_obj;
+	AtkObject *item_cell;
+
+	g_return_if_fail (E_IS_CALENDAR_ITEM (calitem));
+	atk_obj = atk_gobject_accessible_for_object (G_OBJECT (calitem));
+	ea_calendar_item_destory_cell_data (EA_CALENDAR_ITEM (atk_obj));
+
+	item_cell = atk_selection_ref_selection (
+		ATK_SELECTION (atk_obj), 0);
+	if (item_cell)
+		ea_calendar_set_focus_object (EA_CALENDAR_ITEM (atk_obj), item_cell);
+
+	g_signal_emit_by_name (atk_obj, "model_changed");
+}
+
+/* helpers */
+
+static EaCellTable *
+ea_calendar_item_get_cell_data (EaCalendarItem *ea_calitem)
+{
+	AtkGObjectAccessible *atk_gobj;
+	GObject *g_obj;
+	EaCellTable *cell_data;
+
+	g_return_val_if_fail (ea_calitem, NULL);
+
+	atk_gobj = ATK_GOBJECT_ACCESSIBLE (ea_calitem);
+	g_obj = atk_gobject_accessible_get_object (atk_gobj);
+	if (!g_obj)
+		return NULL;
+
+	cell_data = g_object_get_data (
+		G_OBJECT (ea_calitem),
+		"ea-calendar-cell-table");
+
+	if (!cell_data) {
+		gint n_cells = ea_calendar_item_get_n_children (ATK_OBJECT (ea_calitem));
+		cell_data = ea_cell_table_create (
+			n_cells / EA_CALENDAR_COLUMN_NUM,
+			EA_CALENDAR_COLUMN_NUM,
+			FALSE);
+		g_object_set_data (
+			G_OBJECT (ea_calitem),
+			"ea-calendar-cell-table", cell_data);
+	}
+	return cell_data;
+}
+
+static void
+ea_calendar_item_destory_cell_data (EaCalendarItem *ea_calitem)
+{
+	EaCellTable *cell_data;
+
+	g_return_if_fail (ea_calitem);
+
+	cell_data = g_object_get_data (
+		G_OBJECT (ea_calitem),
+		"ea-calendar-cell-table");
+	if (cell_data) {
+		g_object_set_data (
+			G_OBJECT (ea_calitem),
+			"ea-calendar-cell-table", NULL);
+		ea_cell_table_destroy (cell_data);
+	}
+}
+
+static gboolean
+ea_calendar_item_get_row_label (EaCalendarItem *ea_calitem,
+                                gint row,
+                                gchar *buffer,
+                                gint buffer_size)
+{
+	AtkGObjectAccessible *atk_gobj;
+	GObject *g_obj;
+	ECalendarItem *calitem;
+	gint index, week_num;
+	gint year, month, day;
+
+	g_return_val_if_fail (ea_calitem, FALSE);
+
+	atk_gobj = ATK_GOBJECT_ACCESSIBLE (ea_calitem);
+	g_obj = atk_gobject_accessible_get_object (atk_gobj);
+	if (!g_obj)
+		return FALSE;
+
+	calitem = E_CALENDAR_ITEM (g_obj);
+
+	index = atk_table_get_index_at (ATK_TABLE (ea_calitem), row, 0);
+	if (!e_calendar_item_get_date_for_offset (calitem, index,
+						  &year, &month, &day))
+		return FALSE;
+
+	week_num = e_calendar_item_get_week_number (
+		calitem, day, month, year);
+
+	g_snprintf (buffer, buffer_size, "week number : %d", week_num);
+	return TRUE;
+}
+
+static gboolean
+ea_calendar_item_get_column_label (EaCalendarItem *ea_calitem,
+                                   gint column,
+                                   gchar *buffer,
+                                   gint buffer_size)
+{
+	AtkGObjectAccessible *atk_gobj;
+	GObject *g_obj;
+	const gchar *abbr_name;
+
+	g_return_val_if_fail (ea_calitem, FALSE);
+
+	atk_gobj = ATK_GOBJECT_ACCESSIBLE (ea_calitem);
+	g_obj = atk_gobject_accessible_get_object (atk_gobj);
+	if (!g_obj)
+		return FALSE;
+
+	/* Columns are 0 = Monday ... 6 = Sunday */
+	abbr_name = e_get_weekday_name (column + 1, TRUE);
+	g_strlcpy (buffer, abbr_name, buffer_size);
+
+	return TRUE;
+}
+
+/* the coordinate the e-calendar canvas coord */
+gboolean
+e_calendar_item_get_day_extents (ECalendarItem *calitem,
+                                 gint year,
+                                 gint month,
+                                 gint date,
+                                 gint *x,
+                                 gint *y,
+                                 gint *width,
+                                 gint *height)
+{
+	GnomeCanvasItem *item;
+	GtkWidget *widget;
+	GtkStyle *style;
+	PangoFontDescription *font_desc;
+	PangoContext *pango_context;
+	PangoFontMetrics *font_metrics;
+	gint char_height, xthickness, ythickness, text_y;
+	gint new_year, new_month, num_months, months_offset;
+	gint month_x, month_y, month_cell_x, month_cell_y;
+	gint month_row, month_col;
+	gint day_row, day_col;
+	gint days_from_week_start;
+
+	g_return_val_if_fail (E_IS_CALENDAR_ITEM (calitem), FALSE);
+
+	item = GNOME_CANVAS_ITEM (calitem);
+	widget = GTK_WIDGET (item->canvas);
+	style = gtk_widget_get_style (widget);
+
+	/* Set up Pango prerequisites */
+	font_desc = calitem->font_desc;
+	if (!font_desc)
+		font_desc = style->font_desc;
+	pango_context = gtk_widget_get_pango_context (widget);
+	font_metrics = pango_context_get_metrics (
+		pango_context, font_desc,
+		pango_context_get_language (pango_context));
+
+	char_height =
+		PANGO_PIXELS (pango_font_metrics_get_ascent (font_metrics)) +
+		PANGO_PIXELS (pango_font_metrics_get_descent (font_metrics));
+
+	xthickness = style->xthickness;
+	ythickness = style->ythickness;
+
+	new_year = year;
+	new_month = month;
+	e_calendar_item_normalize_date (calitem, &new_year, &new_month);
+	num_months = calitem->rows * calitem->cols;
+	months_offset = (new_year - calitem->year) * 12
+		+ new_month - calitem->month;
+
+	if (months_offset > num_months || months_offset < 0)
+		return FALSE;
+
+	month_row = months_offset / calitem->cols;
+	month_col = months_offset % calitem->cols;
+
+	month_x = item->x1 + xthickness + calitem->x_offset
+		+ month_col * calitem->month_width;
+	month_y = item->y1 + ythickness + month_row * calitem->month_height;
+
+	month_cell_x = month_x + E_CALENDAR_ITEM_XPAD_BEFORE_WEEK_NUMBERS
+		+ calitem->month_lpad + E_CALENDAR_ITEM_XPAD_BEFORE_CELLS;
+	text_y = month_y + ythickness * 2
+		+ E_CALENDAR_ITEM_YPAD_ABOVE_MONTH_NAME
+		+ char_height + E_CALENDAR_ITEM_YPAD_BELOW_MONTH_NAME
+		+ E_CALENDAR_ITEM_YPAD_ABOVE_DAY_LETTERS + calitem->month_tpad;
+
+	month_cell_y = text_y + char_height
+		+ E_CALENDAR_ITEM_YPAD_BELOW_DAY_LETTERS + 1
+		+ E_CALENDAR_ITEM_YPAD_ABOVE_CELLS;
+
+	days_from_week_start = e_calendar_item_get_n_days_from_week_start (
+		calitem, new_year, new_month);
+	day_row = (date + days_from_week_start - 1) / EA_CALENDAR_COLUMN_NUM;
+	day_col = (date + days_from_week_start - 1) % EA_CALENDAR_COLUMN_NUM;
+
+	*x = month_cell_x + day_col * calitem->cell_width;
+	*y = month_cell_y + day_row * calitem->cell_height;
+	*width = calitem->cell_width;
+	*height = calitem->cell_height;
+
+	return TRUE;
+}
+
+/* month is from 0 to 11 */
+gboolean
+e_calendar_item_get_date_for_offset (ECalendarItem *calitem,
+                                     gint day_offset,
+                                     gint *year,
+                                     gint *month,
+                                     gint *day)
+{
+	gint start_year, start_month, start_day;
+	gint end_year, end_month, end_day;
+	GDate *start_date;
+
+	g_return_val_if_fail (E_IS_CALENDAR_ITEM (calitem), FALSE);
+
+	if (!e_calendar_item_get_date_range (calitem, &start_year,
+					     &start_month, &start_day,
+					     &end_year, &end_month,
+					     &end_day))
+		return FALSE;
+
+	start_date = g_date_new_dmy (start_day, start_month + 1, start_year);
+
+	g_date_add_days (start_date, day_offset);
+
+	*year = g_date_get_year (start_date);
+	*month = g_date_get_month (start_date) - 1;
+	*day = g_date_get_day (start_date);
+
+	return TRUE;
+}
+
+/* the arg month is from 0 to 11 */
+static gboolean
+e_calendar_item_get_offset_for_date (ECalendarItem *calitem,
+                                     gint year,
+                                     gint month,
+                                     gint day,
+                                     gint *offset)
+{
+	gint start_year, start_month, start_day;
+	gint end_year, end_month, end_day;
+	GDate *start_date, *end_date;
+
+	*offset = 0;
+	g_return_val_if_fail (E_IS_CALENDAR_ITEM (calitem), FALSE);
+
+	if (!e_calendar_item_get_date_range (calitem, &start_year,
+					     &start_month, &start_day,
+					     &end_year, &end_month,
+					     &end_day))
+		return FALSE;
+
+	start_date = g_date_new_dmy (start_day, start_month + 1, start_year);
+	end_date = g_date_new_dmy (day, month + 1, year);
+
+	*offset = g_date_days_between (start_date, end_date);
+	g_free (start_date);
+	g_free (end_date);
+
+	return TRUE;
+}
+
+gint
+e_calendar_item_get_n_days_from_week_start (ECalendarItem *calitem,
+                                            gint year,
+                                            gint month)
+{
+	struct tm tmp_tm;
+	gint start_weekday, days_from_week_start;
+
+	memset (&tmp_tm, 0, sizeof (tmp_tm));
+	tmp_tm.tm_year = year - 1900;
+	tmp_tm.tm_mon = month;
+	tmp_tm.tm_mday = 1;
+	tmp_tm.tm_isdst = -1;
+	mktime (&tmp_tm);
+	start_weekday = (tmp_tm.tm_wday + 6) % 7;   /* 0 to 6 */
+	days_from_week_start = (start_weekday + 7 - calitem->week_start_day)
+		% 7;
+	return days_from_week_start;
+}
+
+static void
+ea_calendar_set_focus_object (EaCalendarItem *ea_calitem,
+                              AtkObject *item_cell)
+{
+	AtkStateSet *state_set, *old_state_set;
+	AtkObject *old_cell;
+
+	old_cell = (AtkObject *) g_object_get_data (
+		G_OBJECT (ea_calitem), "gail-focus-object");
+	if (old_cell && EA_IS_CALENDAR_CELL (old_cell)) {
+		old_state_set = atk_object_ref_state_set (old_cell);
+		atk_state_set_remove_state (old_state_set, ATK_STATE_FOCUSED);
+		g_object_unref (old_state_set);
+	}
+	if (old_cell)
+		g_object_unref (old_cell);
+
+	state_set = atk_object_ref_state_set (item_cell);
+	atk_state_set_add_state (state_set, ATK_STATE_FOCUSED);
+	g_object_set_data (G_OBJECT (ea_calitem), "gail-focus-object", item_cell);
+	g_object_unref (state_set);
+}
diff --git a/e-util/ea-calendar-item.h b/e-util/ea-calendar-item.h
new file mode 100644
index 0000000..db2e342
--- /dev/null
+++ b/e-util/ea-calendar-item.h
@@ -0,0 +1,71 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Bolian Yin <bolian yin sun com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef __EA_CALENDAR_ITEM_H__
+#define __EA_CALENDAR_ITEM_H__
+
+#include <atk/atkgobjectaccessible.h>
+#include <e-util/e-calendar-item.h>
+
+G_BEGIN_DECLS
+
+#define EA_TYPE_CALENDAR_ITEM                   (ea_calendar_item_get_type ())
+#define EA_CALENDAR_ITEM(obj)                   (G_TYPE_CHECK_INSTANCE_CAST ((obj), EA_TYPE_CALENDAR_ITEM, EaCalendarItem))
+#define EA_CALENDAR_ITEM_CLASS(klass)           (G_TYPE_CHECK_CLASS_CAST ((klass), EA_TYPE_CALENDAR_ITEM, EaCalendarItemClass))
+#define EA_IS_CALENDAR_ITEM(obj)                (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EA_TYPE_CALENDAR_ITEM))
+#define EA_IS_CALENDAR_ITEM_CLASS(klass)        (G_TYPE_CHECK_CLASS_TYPE ((klass), EA_TYPE_CALENDAR_ITEM))
+#define EA_CALENDAR_ITEM_GET_CLASS(obj)         (G_TYPE_INSTANCE_GET_CLASS ((obj), EA_TYPE_CALENDAR_ITEM, EaCalendarItemClass))
+
+typedef struct _EaCalendarItem                   EaCalendarItem;
+typedef struct _EaCalendarItemClass              EaCalendarItemClass;
+
+struct _EaCalendarItem
+{
+	AtkGObjectAccessible parent;
+};
+
+GType ea_calendar_item_get_type (void);
+
+struct _EaCalendarItemClass
+{
+	AtkGObjectAccessibleClass parent_class;
+};
+
+AtkObject *ea_calendar_item_new (GObject *obj);
+gboolean e_calendar_item_get_day_extents (ECalendarItem *calitem,
+					  gint year, gint month, gint date,
+					  gint *x, gint *y,
+					  gint *width, gint *height);
+gboolean e_calendar_item_get_date_for_offset (ECalendarItem *calitem,
+					      gint day_offset,
+					      gint *year, gint *month,
+					      gint *day);
+gint e_calendar_item_get_n_days_from_week_start (ECalendarItem *calitem,
+						 gint year, gint month);
+
+G_END_DECLS
+
+#endif /* __EA_CALENDAR_ITEM_H__ */
diff --git a/widgets/misc/ea-cell-table.c b/e-util/ea-cell-table.c
similarity index 100%
rename from widgets/misc/ea-cell-table.c
rename to e-util/ea-cell-table.c
diff --git a/e-util/ea-cell-table.h b/e-util/ea-cell-table.h
new file mode 100644
index 0000000..3ddd749
--- /dev/null
+++ b/e-util/ea-cell-table.h
@@ -0,0 +1,63 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Bolian Yin <bolian yin sun com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+/* EaCellTable */
+
+#include <glib-object.h>
+
+struct _EaCellTable {
+	gint columns;
+	gint rows;
+	gboolean column_first;     /* index order */
+	gchar **column_labels;
+	gchar **row_labels;
+	gpointer *cells;
+};
+
+typedef struct _EaCellTable EaCellTable;
+
+EaCellTable * ea_cell_table_create (gint rows, gint columns,
+				    gboolean column_first);
+void ea_cell_table_destroy (EaCellTable * cell_data);
+gpointer ea_cell_table_get_cell (EaCellTable * cell_data,
+				   gint row, gint column);
+gboolean ea_cell_table_set_cell (EaCellTable * cell_data,
+				 gint row, gint column, gpointer cell);
+gpointer ea_cell_table_get_cell_at_index (EaCellTable * cell_data,
+					  gint index);
+gboolean ea_cell_table_set_cell_at_index (EaCellTable * cell_data,
+					  gint index, gpointer cell);
+
+const gchar *
+ea_cell_table_get_column_label (EaCellTable * cell_data, gint column);
+void ea_cell_table_set_column_label (EaCellTable * cell_data,
+				     gint column, const gchar *label);
+const gchar *
+ea_cell_table_get_row_label (EaCellTable * cell_data, gint row);
+void ea_cell_table_set_row_label (EaCellTable * cell_data,
+				  gint row, const gchar *label);
+gint ea_cell_table_get_index (EaCellTable *cell_data,
+			      gint row, gint column);
diff --git a/e-util/ea-factory.h b/e-util/ea-factory.h
new file mode 100644
index 0000000..c244697
--- /dev/null
+++ b/e-util/ea-factory.h
@@ -0,0 +1,118 @@
+/*
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Bolian Yin <bolian yin sun com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+/* Evolution Accessibility
+*/
+
+#ifndef _EA_FACTORY_H__
+#define _EA_FACTORY_H__
+
+#include <atk/atkobject.h>
+
+#define EA_FACTORY_PARTA_GOBJECT(type, type_as_function, opt_create_accessible)	\
+static AtkObject *								\
+type_as_function ## _factory_create_accessible (GObject *obj)			\
+{                                                                               \
+  AtkObject *accessible;							\
+  g_return_val_if_fail (G_IS_OBJECT (obj), NULL);				\
+  accessible = opt_create_accessible (G_OBJECT (obj));				\
+  return accessible;								\
+}
+
+#define EA_FACTORY_PARTA(type, type_as_function, opt_create_accessible)		\
+static AtkObject *								\
+type_as_function ## _factory_create_accessible (GObject *obj)			\
+{                                                                               \
+  GtkWidget *widget;								\
+  AtkObject *accessible;							\
+										\
+  g_return_val_if_fail (GTK_IS_WIDGET (obj), NULL);				\
+										\
+  widget = GTK_WIDGET (obj);							\
+										\
+  accessible = opt_create_accessible (widget);					\
+  return accessible;								\
+}
+
+#define EA_FACTORY_PARTB(type, type_as_function, opt_create_accessible)		\
+										\
+static GType									\
+type_as_function ## _factory_get_accessible_type (void)				\
+{										\
+  return type;									\
+}										\
+										\
+										\
+static void									\
+type_as_function ## _factory_class_init (AtkObjectFactoryClass *klass)		\
+{										\
+  klass->create_accessible   = type_as_function ## _factory_create_accessible;	\
+  klass->get_accessible_type = type_as_function ## _factory_get_accessible_type;\
+}										\
+										\
+static GType									\
+type_as_function ## _factory_get_type (void)					\
+{										\
+  static GType t = 0;								\
+										\
+  if (!t)									\
+  {										\
+    gchar *name;									\
+    static const GTypeInfo tinfo =						\
+    {										\
+      sizeof (AtkObjectFactoryClass),						\
+      NULL, NULL, (GClassInitFunc) type_as_function ## _factory_class_init,	\
+      NULL, NULL, sizeof (AtkObjectFactory), 0, NULL, NULL			\
+    };										\
+										\
+    name = g_strconcat (g_type_name (type), "Factory", NULL);			\
+    t = g_type_register_static (						\
+	    ATK_TYPE_OBJECT_FACTORY, name, &tinfo, 0);				\
+    g_free (name);								\
+  }										\
+										\
+  return t;									\
+}
+
+#define EA_FACTORY(type, type_as_function, opt_create_accessible)		\
+        EA_FACTORY_PARTA (type, type_as_function, opt_create_accessible)		\
+        EA_FACTORY_PARTB (type, type_as_function, opt_create_accessible)
+
+#define EA_FACTORY_GOBJECT(type, type_as_function, opt_create_accessible)	\
+        EA_FACTORY_PARTA_GOBJECT (type, type_as_function, opt_create_accessible)	\
+        EA_FACTORY_PARTB (type, type_as_function, opt_create_accessible)
+
+#define EA_SET_FACTORY(obj_type, type_as_function)				\
+{                                                                               \
+	if (atk_get_root ()) {                                                  \
+		atk_registry_set_factory_type (atk_get_default_registry (),     \
+				      obj_type,                                 \
+				      type_as_function ## _factory_get_type ());\
+	}									\
+}
+
+#endif /* _EA_FACTORY_H__ */
diff --git a/e-util/ea-widgets.c b/e-util/ea-widgets.c
new file mode 100644
index 0000000..0a65730
--- /dev/null
+++ b/e-util/ea-widgets.c
@@ -0,0 +1,36 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Bolian Yin <bolian yin sun com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "ea-factory.h"
+#include "ea-calendar-item.h"
+#include "ea-widgets.h"
+
+EA_FACTORY_GOBJECT (EA_TYPE_CALENDAR_ITEM, ea_calendar_item, ea_calendar_item_new)
+
+void e_calendar_item_a11y_init (void)
+{
+    EA_SET_FACTORY (e_calendar_item_get_type (), ea_calendar_item);
+}
diff --git a/e-util/ea-widgets.h b/e-util/ea-widgets.h
new file mode 100644
index 0000000..3fd212f
--- /dev/null
+++ b/e-util/ea-widgets.h
@@ -0,0 +1,36 @@
+/*
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Bolian Yin <bolian yin sun com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+/* Evolution Accessibility
+*/
+
+#ifndef _EA_WIDGETS_H__
+#define _EA_WIDGETS_H__
+
+void e_calendar_item_a11y_init (void);
+
+#endif /* _EA_WIDGETS_H__ */
diff --git a/e-util/evolution-source-viewer.c b/e-util/evolution-source-viewer.c
new file mode 100644
index 0000000..9f5fb11
--- /dev/null
+++ b/e-util/evolution-source-viewer.c
@@ -0,0 +1,1176 @@
+/*
+ * evolution-source-viewer.c
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#include <config.h>
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+
+#include <libedataserver/libedataserver.h>
+
+/* XXX Even though this is all one file, I'm still being pedantic about data
+ *     encapsulation (except for a private struct, even I'm not that anal!).
+ *     I expect this program will eventually be too complex for one file
+ *     and we'll want to split off an e-source-viewer.[ch]. */
+
+/* Standard GObject macros */
+#define E_TYPE_SOURCE_VIEWER \
+	(e_source_viewer_get_type ())
+#define E_SOURCE_VIEWER(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_SOURCE_VIEWER, ESourceViewer))
+#define E_SOURCE_VIEWER_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_SOURCE_VIEWER, ESourceViewerClass))
+#define E_IS_SOURCE_VIEWER(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_SOURCE_VIEWER))
+#define E_IS_SOURCE_VIEWER_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_SOURCE_VIEWER))
+#define E_SOURCE_VIEWER_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_SOURCE_VIEWER, ESourceViewerClass))
+
+typedef struct _ESourceViewer ESourceViewer;
+typedef struct _ESourceViewerClass ESourceViewerClass;
+
+struct _ESourceViewer {
+	GtkWindow parent;
+	ESourceRegistry *registry;
+
+	GtkTreeStore *tree_store;
+	GHashTable *source_index;
+
+	GCancellable *delete_operation;
+
+	GtkWidget *tree_view;		/* not referenced */
+	GtkWidget *text_view;		/* not referenced */
+	GtkWidget *top_panel;		/* not referenced */
+
+	/* Viewing Page */
+	GtkWidget *viewing_label;	/* not referenced */
+	GtkWidget *delete_button;	/* not referenced */
+
+	/* Deleting Page */
+	GtkWidget *deleting_label;	/* not referenced */
+	GtkWidget *deleting_cancel;	/* not referenced */
+};
+
+struct _ESourceViewerClass {
+	GtkWindowClass parent_class;
+};
+
+enum {
+	PAGE_VIEWING,
+	PAGE_DELETING
+};
+
+enum {
+	PROP_0,
+	PROP_REGISTRY
+};
+
+enum {
+	COLUMN_DISPLAY_NAME,
+	COLUMN_SOURCE_UID,
+	COLUMN_REMOVABLE,
+	COLUMN_WRITABLE,
+	COLUMN_REMOTE_CREATABLE,
+	COLUMN_REMOTE_DELETABLE,
+	COLUMN_SOURCE,
+	NUM_COLUMNS
+};
+
+/* Forward Declarations */
+GType		e_source_viewer_get_type	(void) G_GNUC_CONST;
+GtkWidget *	e_source_viewer_new		(GCancellable *cancellable,
+						 GError **error);
+ESourceRegistry *
+		e_source_viewer_get_registry	(ESourceViewer *viewer);
+GtkTreePath *	e_source_viewer_dup_selected_path
+						(ESourceViewer *viewer);
+gboolean	e_source_viewer_set_selected_path
+						(ESourceViewer *viewer,
+						 GtkTreePath *path);
+ESource *	e_source_viewer_ref_selected_source
+						(ESourceViewer *viewer);
+gboolean	e_source_viewer_set_selected_source
+						(ESourceViewer *viewer,
+						 ESource *source);
+GNode *		e_source_viewer_build_display_tree
+						(ESourceViewer *viewer);
+
+static void	e_source_viewer_initable_init	(GInitableIface *interface);
+
+G_DEFINE_TYPE_WITH_CODE (
+	ESourceViewer,
+	e_source_viewer,
+	GTK_TYPE_WINDOW,
+	G_IMPLEMENT_INTERFACE (
+		G_TYPE_INITABLE,
+		e_source_viewer_initable_init));
+
+static GIcon *
+source_view_new_remote_creatable_icon (void)
+{
+	GEmblem *emblem;
+	GIcon *emblem_icon;
+	GIcon *folder_icon;
+	GIcon *icon;
+
+	emblem_icon = g_themed_icon_new ("emblem-new");
+	folder_icon = g_themed_icon_new ("folder-remote");
+
+	emblem = g_emblem_new (emblem_icon);
+	icon = g_emblemed_icon_new (folder_icon, emblem);
+	g_object_unref (emblem);
+
+	g_object_unref (folder_icon);
+	g_object_unref (emblem_icon);
+
+	return icon;
+}
+
+static GIcon *
+source_view_new_remote_deletable_icon (void)
+{
+	GEmblem *emblem;
+	GIcon *emblem_icon;
+	GIcon *folder_icon;
+	GIcon *icon;
+
+	emblem_icon = g_themed_icon_new ("edit-delete");
+	folder_icon = g_themed_icon_new ("folder-remote");
+
+	emblem = g_emblem_new (emblem_icon);
+	icon = g_emblemed_icon_new (folder_icon, emblem);
+	g_object_unref (emblem);
+
+	g_object_unref (folder_icon);
+	g_object_unref (emblem_icon);
+
+	return icon;
+}
+
+static gchar *
+source_viewer_get_monospace_font_name (void)
+{
+	GSettings *settings;
+	gchar *font_name;
+
+	settings = g_settings_new ("org.gnome.desktop.interface");
+	font_name = g_settings_get_string (settings, "monospace-font-name");
+	g_object_unref (settings);
+
+	/* Fallback to a reasonable default. */
+	if (font_name == NULL)
+		font_name = g_strdup ("Monospace 10");
+
+	return font_name;
+}
+
+static void
+source_viewer_set_text (ESourceViewer *viewer,
+                        ESource *source)
+{
+	GtkTextView *text_view;
+	GtkTextBuffer *buffer;
+	GtkTextIter start;
+	GtkTextIter end;
+
+	text_view = GTK_TEXT_VIEW (viewer->text_view);
+	buffer = gtk_text_view_get_buffer (text_view);
+
+	gtk_text_buffer_get_start_iter (buffer, &start);
+	gtk_text_buffer_get_end_iter (buffer, &end);
+	gtk_text_buffer_delete (buffer, &start, &end);
+
+	if (source != NULL) {
+		gchar *string;
+		gsize length;
+
+		gtk_text_buffer_get_start_iter (buffer, &start);
+
+		string = e_source_to_string (source, &length);
+		gtk_text_buffer_insert (buffer, &start, string, length);
+		g_free (string);
+	}
+}
+
+static void
+source_viewer_update_row (ESourceViewer *viewer,
+                          ESource *source)
+{
+	GHashTable *source_index;
+	GtkTreeRowReference *reference;
+	GtkTreeModel *model;
+	GtkTreePath *path;
+	GtkTreeIter iter;
+	const gchar *display_name;
+	const gchar *source_uid;
+	gboolean removable;
+	gboolean writable;
+	gboolean remote_creatable;
+	gboolean remote_deletable;
+
+	source_index = viewer->source_index;
+	reference = g_hash_table_lookup (source_index, source);
+
+	/* We show all sources, so the reference should be valid. */
+	g_return_if_fail (gtk_tree_row_reference_valid (reference));
+
+	model = gtk_tree_row_reference_get_model (reference);
+	path = gtk_tree_row_reference_get_path (reference);
+	gtk_tree_model_get_iter (model, &iter, path);
+	gtk_tree_path_free (path);
+
+	source_uid = e_source_get_uid (source);
+	display_name = e_source_get_display_name (source);
+	removable = e_source_get_removable (source);
+	writable = e_source_get_writable (source);
+	remote_creatable = e_source_get_remote_creatable (source);
+	remote_deletable = e_source_get_remote_deletable (source);
+
+	gtk_tree_store_set (
+		GTK_TREE_STORE (model), &iter,
+		COLUMN_DISPLAY_NAME, display_name,
+		COLUMN_SOURCE_UID, source_uid,
+		COLUMN_REMOVABLE, removable,
+		COLUMN_WRITABLE, writable,
+		COLUMN_REMOTE_CREATABLE, remote_creatable,
+		COLUMN_REMOTE_DELETABLE, remote_deletable,
+		COLUMN_SOURCE, source,
+		-1);
+}
+
+static gboolean
+source_viewer_traverse (GNode *node,
+                        gpointer user_data)
+{
+	ESourceViewer *viewer;
+	ESource *source;
+	GHashTable *source_index;
+	GtkTreeRowReference *reference = NULL;
+	GtkTreeView *tree_view;
+	GtkTreeModel *model;
+	GtkTreePath *path;
+	GtkTreeIter iter;
+
+	/* Skip the root node. */
+	if (G_NODE_IS_ROOT (node))
+		return FALSE;
+
+	viewer = E_SOURCE_VIEWER (user_data);
+
+	source_index = viewer->source_index;
+
+	tree_view = GTK_TREE_VIEW (viewer->tree_view);
+	model = gtk_tree_view_get_model (tree_view);
+
+	if (node->parent != NULL && node->parent->data != NULL)
+		reference = g_hash_table_lookup (
+			source_index, node->parent->data);
+
+	if (gtk_tree_row_reference_valid (reference)) {
+		GtkTreeIter parent;
+
+		path = gtk_tree_row_reference_get_path (reference);
+		gtk_tree_model_get_iter (model, &parent, path);
+		gtk_tree_path_free (path);
+
+		gtk_tree_store_append (GTK_TREE_STORE (model), &iter, &parent);
+	} else
+		gtk_tree_store_append (GTK_TREE_STORE (model), &iter, NULL);
+
+	/* Source index takes ownership. */
+	source = g_object_ref (node->data);
+
+	path = gtk_tree_model_get_path (model, &iter);
+	reference = gtk_tree_row_reference_new (model, path);
+	g_hash_table_insert (source_index, source, reference);
+	gtk_tree_path_free (path);
+
+	source_viewer_update_row (viewer, source);
+
+	return FALSE;
+}
+
+static void
+source_viewer_save_expanded (GtkTreeView *tree_view,
+                             GtkTreePath *path,
+                             GQueue *queue)
+{
+	GtkTreeModel *model;
+	GtkTreeIter iter;
+	ESource *source;
+
+	model = gtk_tree_view_get_model (tree_view);
+	gtk_tree_model_get_iter (model, &iter, path);
+	gtk_tree_model_get (model, &iter, COLUMN_SOURCE, &source, -1);
+	g_queue_push_tail (queue, source);
+}
+
+static void
+source_viewer_build_model (ESourceViewer *viewer)
+{
+	GQueue queue = G_QUEUE_INIT;
+	GHashTable *source_index;
+	GtkTreeView *tree_view;
+	GtkTreeModel *model;
+	GtkTreePath *sel_path;
+	ESource *sel_source;
+	GNode *root;
+
+	tree_view = GTK_TREE_VIEW (viewer->tree_view);
+
+	source_index = viewer->source_index;
+	sel_path = e_source_viewer_dup_selected_path (viewer);
+	sel_source = e_source_viewer_ref_selected_source (viewer);
+
+	/* Save expanded sources to restore later. */
+	gtk_tree_view_map_expanded_rows (
+		tree_view, (GtkTreeViewMappingFunc)
+		source_viewer_save_expanded, &queue);
+
+	model = gtk_tree_view_get_model (tree_view);
+	gtk_tree_store_clear (GTK_TREE_STORE (model));
+
+	g_hash_table_remove_all (source_index);
+
+	root = e_source_viewer_build_display_tree (viewer);
+
+	g_node_traverse (
+		root, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
+		(GNodeTraverseFunc) source_viewer_traverse, viewer);
+
+	e_source_registry_free_display_tree (root);
+
+	/* Restore previously expanded sources. */
+	while (!g_queue_is_empty (&queue)) {
+		GtkTreeRowReference *reference;
+		ESource *source;
+
+		source = g_queue_pop_head (&queue);
+		reference = g_hash_table_lookup (source_index, source);
+
+		if (gtk_tree_row_reference_valid (reference)) {
+			GtkTreePath *path;
+
+			path = gtk_tree_row_reference_get_path (reference);
+			gtk_tree_view_expand_to_path (tree_view, path);
+			gtk_tree_path_free (path);
+		}
+
+		g_object_unref (source);
+	}
+
+	/* Restore the selection. */
+	if (sel_source != NULL && sel_path != NULL) {
+		if (!e_source_viewer_set_selected_source (viewer, sel_source))
+			e_source_viewer_set_selected_path (viewer, sel_path);
+	}
+
+	if (sel_path != NULL)
+		gtk_tree_path_free (sel_path);
+
+	if (sel_source != NULL)
+		g_object_unref (sel_source);
+}
+
+static void
+source_viewer_expand_to_source (ESourceViewer *viewer,
+                                ESource *source)
+{
+	GHashTable *source_index;
+	GtkTreeRowReference *reference;
+	GtkTreeView *tree_view;
+	GtkTreePath *path;
+
+	source_index = viewer->source_index;
+	reference = g_hash_table_lookup (source_index, source);
+
+	/* We show all sources, so the reference should be valid. */
+	g_return_if_fail (gtk_tree_row_reference_valid (reference));
+
+	/* Expand the tree view to the path containing the ESource. */
+	tree_view = GTK_TREE_VIEW (viewer->tree_view);
+	path = gtk_tree_row_reference_get_path (reference);
+	gtk_tree_view_expand_to_path (tree_view, path);
+	gtk_tree_path_free (path);
+}
+
+static void
+source_viewer_source_added_cb (ESourceRegistry *registry,
+                               ESource *source,
+                               ESourceViewer *viewer)
+{
+	source_viewer_build_model (viewer);
+
+	source_viewer_expand_to_source (viewer, source);
+}
+
+static void
+source_viewer_source_changed_cb (ESourceRegistry *registry,
+                                 ESource *source,
+                                 ESourceViewer *viewer)
+{
+	ESource *selected;
+
+	source_viewer_update_row (viewer, source);
+
+	selected = e_source_viewer_ref_selected_source (viewer);
+	if (selected != NULL) {
+		if (e_source_equal (source, selected))
+			source_viewer_set_text (viewer, source);
+		g_object_unref (selected);
+	}
+}
+
+static void
+source_viewer_source_removed_cb (ESourceRegistry *registry,
+                                 ESource *source,
+                                 ESourceViewer *viewer)
+{
+	source_viewer_build_model (viewer);
+}
+
+static void
+source_viewer_selection_changed_cb (GtkTreeSelection *selection,
+                                    ESourceViewer *viewer)
+{
+	ESource *source;
+	const gchar *uid = NULL;
+	gboolean removable = FALSE;
+
+	source = e_source_viewer_ref_selected_source (viewer);
+
+	source_viewer_set_text (viewer, source);
+
+	if (source != NULL) {
+		uid = e_source_get_uid (source);
+		removable = e_source_get_removable (source);
+	}
+
+	gtk_label_set_text (GTK_LABEL (viewer->viewing_label), uid);
+	gtk_widget_set_visible (viewer->delete_button, removable);
+
+	if (source != NULL)
+		g_object_unref (source);
+}
+
+static void
+source_viewer_delete_done_cb (GObject *source_object,
+                              GAsyncResult *result,
+                              gpointer user_data)
+{
+	ESource *source;
+	ESourceViewer *viewer;
+	GError *error = NULL;
+
+	source = E_SOURCE (source_object);
+	viewer = E_SOURCE_VIEWER (user_data);
+
+	e_source_remove_finish (source, result, &error);
+
+	/* Ignore cancellations. */
+	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+		g_clear_error (&error);
+
+	/* FIXME Show an info bar with the error message. */
+	} else if (error != NULL) {
+		g_warning ("%s: %s", G_STRFUNC, error->message);
+		g_clear_error (&error);
+	}
+
+	gtk_notebook_set_current_page (
+		GTK_NOTEBOOK (viewer->top_panel), PAGE_VIEWING);
+	gtk_widget_set_sensitive (viewer->tree_view, TRUE);
+
+	g_object_unref (viewer->delete_operation);
+	viewer->delete_operation = NULL;
+
+	g_object_unref (viewer);
+}
+
+static void
+source_viewer_delete_button_clicked_cb (GtkButton *delete_button,
+                                        ESourceViewer *viewer)
+{
+	ESource *source;
+	const gchar *uid;
+
+	g_return_if_fail (viewer->delete_operation == NULL);
+
+	source = e_source_viewer_ref_selected_source (viewer);
+	g_return_if_fail (source != NULL);
+
+	uid = e_source_get_uid (source);
+	gtk_label_set_text (GTK_LABEL (viewer->deleting_label), uid);
+
+	gtk_notebook_set_current_page (
+		GTK_NOTEBOOK (viewer->top_panel), PAGE_DELETING);
+	gtk_widget_set_sensitive (viewer->tree_view, FALSE);
+
+	viewer->delete_operation = g_cancellable_new ();
+
+	e_source_remove (
+		source,
+		viewer->delete_operation,
+		source_viewer_delete_done_cb,
+		g_object_ref (viewer));
+
+	g_object_unref (source);
+}
+
+static void
+source_viewer_deleting_cancel_clicked_cb (GtkButton *deleting_cancel,
+                                          ESourceViewer *viewer)
+{
+	g_return_if_fail (viewer->delete_operation != NULL);
+
+	g_cancellable_cancel (viewer->delete_operation);
+}
+
+static void
+source_viewer_get_property (GObject *object,
+                            guint property_id,
+                            GValue *value,
+                            GParamSpec *pspec)
+{
+	switch (property_id) {
+		case PROP_REGISTRY:
+			g_value_set_object (
+				value,
+				e_source_viewer_get_registry (
+				E_SOURCE_VIEWER (object)));
+			return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+source_viewer_dispose (GObject *object)
+{
+	ESourceViewer *viewer = E_SOURCE_VIEWER (object);
+
+	if (viewer->registry != NULL) {
+		g_signal_handlers_disconnect_matched (
+			viewer->registry,
+			G_SIGNAL_MATCH_DATA,
+			0, 0, NULL, NULL, object);
+		g_object_unref (viewer->registry);
+		viewer->registry = NULL;
+	}
+
+	if (viewer->tree_store != NULL) {
+		g_object_unref (viewer->tree_store);
+		viewer->tree_store = NULL;
+	}
+
+	g_hash_table_remove_all (viewer->source_index);
+
+	if (viewer->delete_operation != NULL) {
+		g_object_unref (viewer->delete_operation);
+		viewer->delete_operation = NULL;
+	}
+
+	/* Chain up to parent's dispose() method. */
+	G_OBJECT_CLASS (e_source_viewer_parent_class)->dispose (object);
+}
+
+static void
+source_viewer_finalize (GObject *object)
+{
+	ESourceViewer *viewer = E_SOURCE_VIEWER (object);
+
+	g_hash_table_destroy (viewer->source_index);
+
+	/* Chain up to parent's finalize() method. */
+	G_OBJECT_CLASS (e_source_viewer_parent_class)->finalize (object);
+}
+
+static void
+source_viewer_constructed (GObject *object)
+{
+	ESourceViewer *viewer;
+	GtkTreeViewColumn *column;
+	GtkTreeSelection *selection;
+	GtkCellRenderer *renderer;
+	GtkWidget *container;
+	GtkWidget *paned;
+	GtkWidget *widget;
+	PangoAttribute *attr;
+	PangoAttrList *bold;
+	PangoFontDescription *desc;
+	GIcon *icon;
+	const gchar *title;
+	gchar *font_name;
+	gint page_num;
+
+	viewer = E_SOURCE_VIEWER (object);
+
+	/* Chain up to parent's constructed() method. */
+	G_OBJECT_CLASS (e_source_viewer_parent_class)->constructed (object);
+
+	bold = pango_attr_list_new ();
+	attr = pango_attr_weight_new (PANGO_WEIGHT_BOLD);
+	pango_attr_list_insert (bold, attr);
+
+	title = _("Evolution Source Viewer");
+	gtk_window_set_title (GTK_WINDOW (viewer), title);
+	gtk_window_set_default_size (GTK_WINDOW (viewer), 800, 600);
+
+	paned = gtk_paned_new (GTK_ORIENTATION_HORIZONTAL);
+	gtk_paned_set_position (GTK_PANED (paned), 400);
+	gtk_container_add (GTK_CONTAINER (viewer), paned);
+	gtk_widget_show (paned);
+
+	/* Left panel */
+
+	widget = gtk_scrolled_window_new (NULL, NULL);
+	gtk_scrolled_window_set_policy (
+		GTK_SCROLLED_WINDOW (widget),
+		GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
+	gtk_scrolled_window_set_shadow_type (
+		GTK_SCROLLED_WINDOW (widget), GTK_SHADOW_IN);
+	gtk_paned_add1 (GTK_PANED (paned), widget);
+	gtk_widget_show (widget);
+
+	container = widget;
+
+	widget = gtk_tree_view_new_with_model (
+		GTK_TREE_MODEL (viewer->tree_store));
+	gtk_container_add (GTK_CONTAINER (container), widget);
+	viewer->tree_view = widget;  /* do not reference */
+	gtk_widget_show (widget);
+
+	column = gtk_tree_view_column_new ();
+	/* Translators: The name that is displayed in the user interface */
+	gtk_tree_view_column_set_title (column, _("Display Name"));
+	gtk_tree_view_append_column (GTK_TREE_VIEW (widget), column);
+
+	renderer = gtk_cell_renderer_text_new ();
+	gtk_tree_view_column_pack_start (column, renderer, TRUE);
+	gtk_tree_view_column_add_attribute (
+		column, renderer, "text", COLUMN_DISPLAY_NAME);
+
+	column = gtk_tree_view_column_new ();
+	gtk_tree_view_column_set_title (column, _("Flags"));
+	gtk_tree_view_append_column (GTK_TREE_VIEW (widget), column);
+
+	renderer = gtk_cell_renderer_pixbuf_new ();
+	g_object_set (
+		renderer,
+		"stock-id", GTK_STOCK_EDIT,
+		"stock-size", GTK_ICON_SIZE_MENU,
+		NULL);
+	gtk_tree_view_column_pack_start (column, renderer, FALSE);
+	gtk_tree_view_column_add_attribute (
+		column, renderer, "visible", COLUMN_WRITABLE);
+
+	renderer = gtk_cell_renderer_pixbuf_new ();
+	g_object_set (
+		renderer,
+		"stock-id", GTK_STOCK_DELETE,
+		"stock-size", GTK_ICON_SIZE_MENU,
+		NULL);
+	gtk_tree_view_column_pack_start (column, renderer, FALSE);
+	gtk_tree_view_column_add_attribute (
+		column, renderer, "visible", COLUMN_REMOVABLE);
+
+	icon = source_view_new_remote_creatable_icon ();
+	renderer = gtk_cell_renderer_pixbuf_new ();
+	g_object_set (
+		renderer,
+		"gicon", icon,
+		"stock-size", GTK_ICON_SIZE_MENU,
+		NULL);
+	gtk_tree_view_column_pack_start (column, renderer, FALSE);
+	gtk_tree_view_column_add_attribute (
+		column, renderer, "visible", COLUMN_REMOTE_CREATABLE);
+	g_object_unref (icon);
+
+	icon = source_view_new_remote_deletable_icon ();
+	renderer = gtk_cell_renderer_pixbuf_new ();
+	g_object_set (
+		renderer,
+		"gicon", icon,
+		"stock-size", GTK_ICON_SIZE_MENU,
+		NULL);
+	gtk_tree_view_column_pack_start (column, renderer, FALSE);
+	gtk_tree_view_column_add_attribute (
+		column, renderer, "visible", COLUMN_REMOTE_DELETABLE);
+	g_object_unref (icon);
+
+	/* Append an empty pixbuf renderer to fill leftover space. */
+	renderer = gtk_cell_renderer_pixbuf_new ();
+	gtk_tree_view_column_pack_start (column, renderer, TRUE);
+
+	column = gtk_tree_view_column_new ();
+	gtk_tree_view_column_set_title (column, _("Identity"));
+	gtk_tree_view_append_column (GTK_TREE_VIEW (widget), column);
+
+	renderer = gtk_cell_renderer_text_new ();
+	gtk_tree_view_column_pack_start (column, renderer, FALSE);
+	gtk_tree_view_column_add_attribute (
+		column, renderer, "text", COLUMN_SOURCE_UID);
+
+	selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
+
+	/* Right panel */
+
+	widget = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
+	gtk_paned_add2 (GTK_PANED (paned), widget);
+	gtk_widget_show (widget);
+
+	container = widget;
+
+	widget = gtk_notebook_new ();
+	gtk_widget_set_margin_top (widget, 3);
+	gtk_widget_set_margin_right (widget, 3);
+	gtk_widget_set_margin_bottom (widget, 3);
+	/* leave left margin at zero */
+	gtk_notebook_set_show_tabs (GTK_NOTEBOOK (widget), FALSE);
+	gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+	viewer->top_panel = widget;  /* do not reference */
+	gtk_widget_show (widget);
+
+	widget = gtk_scrolled_window_new (NULL, NULL);
+	gtk_scrolled_window_set_policy (
+		GTK_SCROLLED_WINDOW (widget),
+		GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
+	gtk_scrolled_window_set_shadow_type (
+		GTK_SCROLLED_WINDOW (widget), GTK_SHADOW_IN);
+	gtk_box_pack_start (GTK_BOX (container), widget, TRUE, TRUE, 0);
+	gtk_widget_show (widget);
+
+	container = widget;
+
+	widget = gtk_text_view_new ();
+	gtk_text_view_set_editable (GTK_TEXT_VIEW (widget), FALSE);
+	gtk_container_add (GTK_CONTAINER (container), widget);
+	viewer->text_view = widget;  /* do not reference */
+	gtk_widget_show (widget);
+
+	font_name = source_viewer_get_monospace_font_name ();
+	desc = pango_font_description_from_string (font_name);
+	gtk_widget_override_font (widget, desc);
+	pango_font_description_free (desc);
+	g_free (font_name);
+
+	/* Top panel: Viewing */
+
+	container = viewer->top_panel;
+
+	widget = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
+	page_num = gtk_notebook_append_page (
+		GTK_NOTEBOOK (container), widget, NULL);
+	g_warn_if_fail (page_num == PAGE_VIEWING);
+	gtk_widget_show (widget);
+
+	container = widget;
+
+	widget = gtk_label_new ("Identity:");
+	gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+	gtk_widget_show (widget);
+
+	widget = gtk_label_new (NULL);
+	gtk_label_set_attributes (GTK_LABEL (widget), bold);
+	gtk_label_set_ellipsize (GTK_LABEL (widget), PANGO_ELLIPSIZE_END);
+	gtk_misc_set_alignment (GTK_MISC (widget), 0.0, 0.5);
+	gtk_box_pack_start (GTK_BOX (container), widget, TRUE, TRUE, 0);
+	viewer->viewing_label = widget;  /* do not reference */
+	gtk_widget_show (widget);
+
+	widget = gtk_button_new_from_stock (GTK_STOCK_DELETE);
+	gtk_box_pack_end (GTK_BOX (container), widget, FALSE, FALSE, 0);
+	viewer->delete_button = widget;  /* do not reference */
+	gtk_widget_hide (widget);
+
+	/* Top panel: Deleting */
+
+	container = viewer->top_panel;
+
+	widget = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
+	page_num = gtk_notebook_append_page (
+		GTK_NOTEBOOK (container), widget, NULL);
+	g_warn_if_fail (page_num == PAGE_DELETING);
+	gtk_widget_show (widget);
+
+	container = widget;
+
+	widget = gtk_label_new ("Deleting");
+	gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+	gtk_widget_show (widget);
+
+	widget = gtk_label_new (NULL);
+	gtk_label_set_attributes (GTK_LABEL (widget), bold);
+	gtk_label_set_ellipsize (GTK_LABEL (widget), PANGO_ELLIPSIZE_END);
+	gtk_misc_set_alignment (GTK_MISC (widget), 0.0, 0.5);
+	gtk_box_pack_start (GTK_BOX (container), widget, TRUE, TRUE, 0);
+	viewer->deleting_label = widget;  /* do not reference */
+	gtk_widget_show (widget);
+
+	widget = gtk_button_new_from_stock (GTK_STOCK_CANCEL);
+	gtk_box_pack_end (GTK_BOX (container), widget, FALSE, FALSE, 0);
+	viewer->deleting_cancel = widget;  /* do not reference */
+	gtk_widget_show (widget);
+
+	pango_attr_list_unref (bold);
+
+	g_signal_connect (
+		selection, "changed",
+		G_CALLBACK (source_viewer_selection_changed_cb), viewer);
+
+	g_signal_connect (
+		viewer->delete_button, "clicked",
+		G_CALLBACK (source_viewer_delete_button_clicked_cb), viewer);
+
+	g_signal_connect (
+		viewer->deleting_cancel, "clicked",
+		G_CALLBACK (source_viewer_deleting_cancel_clicked_cb), viewer);
+}
+
+static gboolean
+source_viewer_initable_init (GInitable *initable,
+                             GCancellable *cancellable,
+                             GError **error)
+{
+	ESourceViewer *viewer;
+	ESourceRegistry *registry;
+
+	viewer = E_SOURCE_VIEWER (initable);
+
+	registry = e_source_registry_new_sync (cancellable, error);
+
+	if (registry == NULL)
+		return FALSE;
+
+	viewer->registry = registry;  /* takes ownership */
+
+	g_signal_connect (
+		registry, "source-added",
+		G_CALLBACK (source_viewer_source_added_cb), viewer);
+
+	g_signal_connect (
+		registry, "source-changed",
+		G_CALLBACK (source_viewer_source_changed_cb), viewer);
+
+	g_signal_connect (
+		registry, "source-removed",
+		G_CALLBACK (source_viewer_source_removed_cb), viewer);
+
+	source_viewer_build_model (viewer);
+
+	gtk_tree_view_expand_all (GTK_TREE_VIEW (viewer->tree_view));
+
+	return TRUE;
+}
+
+static void
+e_source_viewer_class_init (ESourceViewerClass *class)
+{
+	GObjectClass *object_class;
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->get_property = source_viewer_get_property;
+	object_class->dispose = source_viewer_dispose;
+	object_class->finalize = source_viewer_finalize;
+	object_class->constructed = source_viewer_constructed;
+
+	g_object_class_install_property (
+		object_class,
+		PROP_REGISTRY,
+		g_param_spec_object (
+			"registry",
+			"Registry",
+			"Data source registry",
+			E_TYPE_SOURCE_REGISTRY,
+			G_PARAM_READABLE |
+			G_PARAM_STATIC_STRINGS));
+}
+
+static void
+e_source_viewer_initable_init (GInitableIface *interface)
+{
+	interface->init = source_viewer_initable_init;
+}
+
+static void
+e_source_viewer_init (ESourceViewer *viewer)
+{
+	viewer->tree_store = gtk_tree_store_new (
+		NUM_COLUMNS,
+		G_TYPE_STRING,		/* COLUMN_DISPLAY_NAME */
+		G_TYPE_STRING,		/* COLUMN_SOURCE_UID */
+		G_TYPE_BOOLEAN,		/* COLUMN_REMOVABLE */
+		G_TYPE_BOOLEAN,		/* COLUMN_WRITABLE */
+		G_TYPE_BOOLEAN,		/* COLUMN_REMOTE_CREATABLE */
+		G_TYPE_BOOLEAN,		/* COLUMN_REMOTE_DELETABLE */
+		E_TYPE_SOURCE);		/* COLUMN_SOURCE */
+
+	viewer->source_index = g_hash_table_new_full (
+		(GHashFunc) e_source_hash,
+		(GEqualFunc) e_source_equal,
+		(GDestroyNotify) g_object_unref,
+		(GDestroyNotify) gtk_tree_row_reference_free);
+}
+
+GtkWidget *
+e_source_viewer_new (GCancellable *cancellable,
+                     GError **error)
+{
+	return g_initable_new (
+		E_TYPE_SOURCE_VIEWER,
+		cancellable, error, NULL);
+}
+
+ESourceRegistry *
+e_source_viewer_get_registry (ESourceViewer *viewer)
+{
+	g_return_val_if_fail (E_IS_SOURCE_VIEWER (viewer), NULL);
+
+	return viewer->registry;
+}
+
+GtkTreePath *
+e_source_viewer_dup_selected_path (ESourceViewer *viewer)
+{
+	GtkTreeSelection *selection;
+	GtkTreeView *tree_view;
+	GtkTreeModel *model;
+	GtkTreeIter iter;
+
+	g_return_val_if_fail (E_IS_SOURCE_VIEWER (viewer), NULL);
+
+	tree_view = GTK_TREE_VIEW (viewer->tree_view);
+	selection = gtk_tree_view_get_selection (tree_view);
+
+	if (!gtk_tree_selection_get_selected (selection, &model, &iter))
+		return NULL;
+
+	return gtk_tree_model_get_path (model, &iter);
+}
+
+gboolean
+e_source_viewer_set_selected_path (ESourceViewer *viewer,
+                                   GtkTreePath *path)
+{
+	GtkTreeSelection *selection;
+	GtkTreeView *tree_view;
+	GtkTreeModel *model;
+	GtkTreeIter iter;
+
+	g_return_val_if_fail (E_IS_SOURCE_VIEWER (viewer), FALSE);
+	g_return_val_if_fail (path != NULL, FALSE);
+
+	tree_view = GTK_TREE_VIEW (viewer->tree_view);
+	selection = gtk_tree_view_get_selection (tree_view);
+
+	/* Check that the path is valid. */
+	model = gtk_tree_view_get_model (tree_view);
+	if (!gtk_tree_model_get_iter (model, &iter, path))
+		return FALSE;
+
+	gtk_tree_selection_unselect_all (selection);
+
+	gtk_tree_view_expand_to_path (tree_view, path);
+	gtk_tree_selection_select_path (selection, path);
+
+	return TRUE;
+}
+
+ESource *
+e_source_viewer_ref_selected_source (ESourceViewer *viewer)
+{
+	ESource *source;
+	GtkTreeSelection *selection;
+	GtkTreeView *tree_view;
+	GtkTreeModel *model;
+	GtkTreeIter iter;
+
+	g_return_val_if_fail (E_IS_SOURCE_VIEWER (viewer), NULL);
+
+	tree_view = GTK_TREE_VIEW (viewer->tree_view);
+	selection = gtk_tree_view_get_selection (tree_view);
+
+	if (!gtk_tree_selection_get_selected (selection, &model, &iter))
+		return NULL;
+
+	gtk_tree_model_get (model, &iter, COLUMN_SOURCE, &source, -1);
+
+	return source;
+}
+
+gboolean
+e_source_viewer_set_selected_source (ESourceViewer *viewer,
+                                     ESource *source)
+{
+	GHashTable *source_index;
+	GtkTreeRowReference *reference;
+	GtkTreePath *path;
+	gboolean success;
+
+	g_return_val_if_fail (E_IS_SOURCE_VIEWER (viewer), FALSE);
+	g_return_val_if_fail (E_IS_SOURCE (source), FALSE);
+
+	source_index = viewer->source_index;
+	reference = g_hash_table_lookup (source_index, source);
+
+	if (!gtk_tree_row_reference_valid (reference))
+		return FALSE;
+
+	path = gtk_tree_row_reference_get_path (reference);
+	success = e_source_viewer_set_selected_path (viewer, path);
+	gtk_tree_path_free (path);
+
+	return success;
+}
+
+/* Helper for e_source_viewer_build_display_tree() */
+static gint
+source_viewer_compare_nodes (GNode *node_a,
+                             GNode *node_b)
+{
+	ESource *source_a = E_SOURCE (node_a->data);
+	ESource *source_b = E_SOURCE (node_b->data);
+
+	return e_source_compare_by_display_name (source_a, source_b);
+}
+
+/* Helper for e_source_viewer_build_display_tree() */
+static gboolean
+source_viewer_sort_nodes (GNode *node,
+                          gpointer unused)
+{
+	GQueue queue = G_QUEUE_INIT;
+	GNode *child_node;
+
+	/* Unlink all the child nodes and place them in a queue. */
+	while ((child_node = g_node_first_child (node)) != NULL) {
+		g_node_unlink (child_node);
+		g_queue_push_tail (&queue, child_node);
+	}
+
+	/* Sort the queue by source name. */
+	g_queue_sort (
+		&queue, (GCompareDataFunc)
+		source_viewer_compare_nodes, NULL);
+
+	/* Pop nodes off the head of the queue and put them back
+	 * under the parent node (preserving the sorted order). */
+	while ((child_node = g_queue_pop_head (&queue)) != NULL)
+		g_node_append (node, child_node);
+
+	return FALSE;
+}
+
+GNode *
+e_source_viewer_build_display_tree (ESourceViewer *viewer)
+{
+	GNode *root;
+	GHashTable *index;
+	GList *list, *link;
+	GHashTableIter iter;
+	gpointer value;
+
+	/* This is just like e_source_registry_build_display_tree()
+	 * except it includes all data sources, even disabled ones.
+	 * Free the tree with e_source_registry_free_display_tree(). */
+
+	g_return_val_if_fail (E_IS_SOURCE_VIEWER (viewer), NULL);
+
+	root = g_node_new (NULL);
+	index = g_hash_table_new (g_str_hash, g_str_equal);
+
+	/* Add a GNode for each ESource to the index.
+	 * The GNodes take ownership of the ESource references. */
+	list = e_source_registry_list_sources (viewer->registry, NULL);
+	for (link = list; link != NULL; link = g_list_next (link)) {
+		ESource *source = E_SOURCE (link->data);
+		gpointer key = (gpointer) e_source_get_uid (source);
+		g_hash_table_insert (index, key, g_node_new (source));
+	}
+	g_list_free (list);
+
+	/* Traverse the index and link the nodes together. */
+	g_hash_table_iter_init (&iter, index);
+	while (g_hash_table_iter_next (&iter, NULL, &value)) {
+		ESource *source;
+		GNode *source_node;
+		GNode *parent_node;
+		const gchar *parent_uid;
+
+		source_node = (GNode *) value;
+		source = E_SOURCE (source_node->data);
+		parent_uid = e_source_get_parent (source);
+
+		if (parent_uid == NULL || *parent_uid == '\0') {
+			parent_node = root;
+		} else {
+			parent_node = g_hash_table_lookup (index, parent_uid);
+		}
+
+		/* This could be NULL if the registry service was
+		 * shutdown or reloaded.  All sources will vanish. */
+		if (parent_node != NULL)
+			g_node_append (parent_node, source_node);
+	}
+
+	/* Sort nodes by display name in post order. */
+	g_node_traverse (
+		root, G_POST_ORDER, G_TRAVERSE_ALL,
+		-1, source_viewer_sort_nodes, NULL);
+
+	g_hash_table_destroy (index);
+
+	return root;
+}
+
+gint
+main (gint argc,
+      gchar **argv)
+{
+	GtkWidget *viewer;
+	GError *error = NULL;
+
+	bindtextdomain (GETTEXT_PACKAGE, EVOLUTION_LOCALEDIR);
+	bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+	textdomain (GETTEXT_PACKAGE);
+
+	gtk_init (&argc, &argv);
+
+	viewer = e_source_viewer_new (NULL, &error);
+
+	if (error != NULL) {
+		g_warn_if_fail (viewer == NULL);
+		g_error ("%s", error->message);
+		g_assert_not_reached ();
+	}
+
+	g_signal_connect (
+		viewer, "delete-event",
+		G_CALLBACK (gtk_main_quit), NULL);
+
+	gtk_widget_show (viewer);
+
+	gtk_main ();
+
+	return 0;
+}
diff --git a/filter/filter.error.xml b/e-util/filter.error.xml
similarity index 100%
rename from filter/filter.error.xml
rename to e-util/filter.error.xml
diff --git a/filter/filter.ui b/e-util/filter.ui
similarity index 100%
rename from filter/filter.ui
rename to e-util/filter.ui
diff --git a/e-util/gal-a11y-e-cell-popup.c b/e-util/gal-a11y-e-cell-popup.c
new file mode 100644
index 0000000..523869b
--- /dev/null
+++ b/e-util/gal-a11y-e-cell-popup.c
@@ -0,0 +1,153 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Yang Wu <yang wu sun com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "gal-a11y-e-cell-popup.h"
+
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+#include <gdk/gdkkeysyms.h>
+
+#include "e-cell-popup.h"
+#include "gal-a11y-e-cell-registry.h"
+#include "gal-a11y-util.h"
+
+static AtkObjectClass *parent_class = NULL;
+#define PARENT_TYPE (gal_a11y_e_cell_get_type ())
+
+static void gal_a11y_e_cell_popup_class_init (GalA11yECellPopupClass *class);
+static void popup_cell_action (GalA11yECell *cell);
+
+/**
+ * gal_a11y_e_cell_popup_get_type:
+ * @void:
+ *
+ * Registers the &GalA11yECellPopup class if necessary, and returns the type ID
+ * associated to it.
+ *
+ * Return value: The type ID of the &GalA11yECellPopup class.
+ **/
+GType
+gal_a11y_e_cell_popup_get_type (void)
+{
+	static GType type = 0;
+
+	if (!type) {
+		GTypeInfo info = {
+			sizeof (GalA11yECellPopupClass),
+			(GBaseInitFunc) NULL,
+			(GBaseFinalizeFunc) NULL,
+			(GClassInitFunc) gal_a11y_e_cell_popup_class_init,
+			(GClassFinalizeFunc) NULL,
+			NULL, /* class_data */
+			sizeof (GalA11yECellPopup),
+			0,
+			(GInstanceInitFunc) NULL,
+			NULL /* value_cell_popup */
+		};
+
+		type = g_type_register_static (PARENT_TYPE, "GalA11yECellPopup", &info, 0);
+		gal_a11y_e_cell_type_add_action_interface (type);
+	}
+
+	return type;
+}
+
+static void
+gal_a11y_e_cell_popup_class_init (GalA11yECellPopupClass *class)
+{
+	parent_class = g_type_class_ref (PARENT_TYPE);
+}
+
+AtkObject *
+gal_a11y_e_cell_popup_new (ETableItem *item,
+                           ECellView *cell_view,
+                           AtkObject *parent,
+                           gint model_col,
+                           gint view_col,
+                           gint row)
+{
+	AtkObject *a11y;
+	GalA11yECell *cell;
+	ECellPopup *popupcell;
+	ECellView * child_view = NULL;
+
+	popupcell=  E_CELL_POPUP (cell_view->ecell);
+
+	if (popupcell && popupcell->popup_cell_view)
+		child_view = popupcell->popup_cell_view->child_view;
+
+	if (child_view && child_view->ecell) {
+		a11y = gal_a11y_e_cell_registry_get_object (
+			NULL,
+			item,
+			child_view,
+			parent,
+			model_col,
+			view_col,
+			row);
+	} else {
+		a11y = g_object_new (GAL_A11Y_TYPE_E_CELL_POPUP, NULL);
+		gal_a11y_e_cell_construct (
+			a11y,
+			item,
+			cell_view,
+			parent,
+			model_col,
+			view_col,
+			row);
+		}
+	g_return_val_if_fail (a11y != NULL, NULL);
+	cell = GAL_A11Y_E_CELL (a11y);
+	gal_a11y_e_cell_add_action (
+		cell,
+		"popup",
+		/* Translators: description of a "popup" action */
+		_("popup a child"),
+		"<Alt>Down",              /* action keybinding */
+		popup_cell_action);
+
+	a11y->role  = ATK_ROLE_TABLE_CELL;
+	return a11y;
+}
+
+static void
+popup_cell_action (GalA11yECell *cell)
+{
+	gint finished;
+	GdkEvent event;
+	GtkLayout *layout;
+
+	layout = GTK_LAYOUT (GNOME_CANVAS_ITEM (cell->item)->canvas);
+
+	event.key.type = GDK_KEY_PRESS;
+	event.key.window = gtk_layout_get_bin_window (layout);
+	event.key.send_event = TRUE;
+	event.key.time = GDK_CURRENT_TIME;
+	event.key.state = GDK_MOD1_MASK;
+	event.key.keyval = GDK_KEY_Down;
+
+	g_signal_emit_by_name (cell->item, "event", &event, &finished);
+}
diff --git a/e-util/gal-a11y-e-cell-popup.h b/e-util/gal-a11y-e-cell-popup.h
new file mode 100644
index 0000000..30ce4a7
--- /dev/null
+++ b/e-util/gal-a11y-e-cell-popup.h
@@ -0,0 +1,65 @@
+/*
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Yang Wu <yang wu sun com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef __GAL_A11Y_E_CELL_POPUP_H__
+#define __GAL_A11Y_E_CELL_POPUP_H__
+
+#include <atk/atkgobjectaccessible.h>
+
+#include <e-util/e-table-item.h>
+#include <e-util/gal-a11y-e-cell.h>
+
+#define GAL_A11Y_TYPE_E_CELL_POPUP            (gal_a11y_e_cell_popup_get_type ())
+#define GAL_A11Y_E_CELL_POPUP(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GAL_A11Y_TYPE_E_CELL_POPUP, GalA11yECellPopup))
+#define GAL_A11Y_E_CELL_POPUP_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GAL_A11Y_TYPE_E_CELL_POPUP, GalA11yECellPopupClass))
+#define GAL_A11Y_IS_E_CELL_POPUP(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GAL_A11Y_TYPE_E_CELL_POPUP))
+#define GAL_A11Y_IS_E_CELL_POPUP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GAL_A11Y_TYPE_E_CELL_POPUP))
+
+typedef struct _GalA11yECellPopup GalA11yECellPopup;
+typedef struct _GalA11yECellPopupClass GalA11yECellPopupClass;
+
+/* This struct should actually be larger as this isn't what we derive from.
+ * The GalA11yECellPopupPrivate comes right after the parent class structure.
+ **/
+struct _GalA11yECellPopup {
+	GalA11yECell object;
+};
+
+struct _GalA11yECellPopupClass {
+	GalA11yECellClass parent_class;
+};
+
+/* Standard Glib function */
+GType      gal_a11y_e_cell_popup_get_type   (void);
+AtkObject *gal_a11y_e_cell_popup_new        (ETableItem *item,
+					    ECellView  *cell_view,
+					    AtkObject  *parent,
+					    gint         model_col,
+					    gint         view_col,
+					    gint         row);
+
+#endif /* __GAL_A11Y_E_CELL_POPUP_H__ */
diff --git a/widgets/table/gal-a11y-e-cell-registry.c b/e-util/gal-a11y-e-cell-registry.c
similarity index 100%
rename from widgets/table/gal-a11y-e-cell-registry.c
rename to e-util/gal-a11y-e-cell-registry.c
diff --git a/e-util/gal-a11y-e-cell-registry.h b/e-util/gal-a11y-e-cell-registry.h
new file mode 100644
index 0000000..fdfd9dc
--- /dev/null
+++ b/e-util/gal-a11y-e-cell-registry.h
@@ -0,0 +1,75 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Christopher James Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef __GAL_A11Y_E_CELL_REGISTRY_H__
+#define __GAL_A11Y_E_CELL_REGISTRY_H__
+
+#include <atk/atkobject.h>
+
+#include <e-util/e-table-item.h>
+#include <e-util/e-cell.h>
+
+#define GAL_A11Y_TYPE_E_CELL_REGISTRY            (gal_a11y_e_cell_registry_get_type ())
+#define GAL_A11Y_E_CELL_REGISTRY(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GAL_A11Y_TYPE_E_CELL_REGISTRY, GalA11yECellRegistry))
+#define GAL_A11Y_E_CELL_REGISTRY_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GAL_A11Y_TYPE_E_CELL_REGISTRY, GalA11yECellRegistryClass))
+#define GAL_A11Y_IS_E_CELL_REGISTRY(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GAL_A11Y_TYPE_E_CELL_REGISTRY))
+#define GAL_A11Y_IS_E_CELL_REGISTRY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GAL_A11Y_TYPE_E_CELL_REGISTRY))
+
+typedef struct _GalA11yECellRegistry GalA11yECellRegistry;
+typedef struct _GalA11yECellRegistryClass GalA11yECellRegistryClass;
+typedef struct _GalA11yECellRegistryPrivate GalA11yECellRegistryPrivate;
+
+typedef AtkObject *(*GalA11yECellRegistryFunc) (ETableItem *item,
+						ECellView  *cell_view,
+						AtkObject  *parent,
+						gint         model_col,
+						gint         view_col,
+						gint         row);
+
+struct _GalA11yECellRegistry {
+	GObject object;
+
+	GalA11yECellRegistryPrivate *priv;
+};
+
+struct _GalA11yECellRegistryClass {
+	GObjectClass parent_class;
+};
+
+/* Standard Glib function */
+GType      gal_a11y_e_cell_registry_get_type       (void);
+AtkObject *gal_a11y_e_cell_registry_get_object     (GalA11yECellRegistry     *registry,
+						    ETableItem               *item,
+						    ECellView                *cell_view,
+						    AtkObject                *parent,
+						    gint                       model_col,
+						    gint                       view_col,
+						    gint                       row);
+void       gal_a11y_e_cell_registry_add_cell_type  (GalA11yECellRegistry     *registry,
+						    GType                     type,
+						    GalA11yECellRegistryFunc  func);
+
+#endif /* __GAL_A11Y_E_CELL_REGISTRY_H__ */
diff --git a/e-util/gal-a11y-e-cell-toggle.c b/e-util/gal-a11y-e-cell-toggle.c
new file mode 100644
index 0000000..8be7f44
--- /dev/null
+++ b/e-util/gal-a11y-e-cell-toggle.c
@@ -0,0 +1,198 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "gal-a11y-e-cell-toggle.h"
+
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+
+#include "e-cell-toggle.h"
+#include "e-table-model.h"
+
+#define PARENT_TYPE  (gal_a11y_e_cell_get_type ())
+static GObjectClass *parent_class;
+
+static void gal_a11y_e_cell_toggle_class_init (GalA11yECellToggleClass *class);
+
+static void
+gal_a11y_e_cell_toggle_dispose (GObject *object)
+{
+	GalA11yECellToggle *a11y = GAL_A11Y_E_CELL_TOGGLE (object);
+
+	ETableModel *e_table_model = GAL_A11Y_E_CELL (a11y)->item->table_model;
+
+	if (e_table_model && a11y->model_id > 0) {
+		g_signal_handler_disconnect (e_table_model, a11y->model_id);
+		a11y->model_id = 0;
+	}
+
+	if (parent_class->dispose)
+		parent_class->dispose (object);
+}
+
+GType
+gal_a11y_e_cell_toggle_get_type (void)
+{
+  static GType type = 0;
+
+  if (!type)
+    {
+      static const GTypeInfo tinfo =
+      {
+	sizeof (GalA11yECellToggleClass),
+	(GBaseInitFunc) NULL, /* base init */
+	(GBaseFinalizeFunc) NULL, /* base finalize */
+	(GClassInitFunc) gal_a11y_e_cell_toggle_class_init, /* class init */
+	(GClassFinalizeFunc) NULL, /* class finalize */
+	NULL, /* class data */
+	sizeof (GalA11yECellToggle), /* instance size */
+	0, /* nb preallocs */
+	NULL, /* instance init */
+	NULL /* value table */
+      };
+
+      type = g_type_register_static (GAL_A11Y_TYPE_E_CELL,
+				     "GalA11yECellToggle", &tinfo, 0);
+      gal_a11y_e_cell_type_add_action_interface (type);
+
+    }
+  return type;
+}
+
+static void
+gal_a11y_e_cell_toggle_class_init (GalA11yECellToggleClass *class)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+	object_class->dispose      = gal_a11y_e_cell_toggle_dispose;
+	parent_class               = g_type_class_ref (PARENT_TYPE);
+}
+
+static void
+toggle_cell_action (GalA11yECell *cell)
+{
+	gint finished;
+	GtkLayout *layout;
+	GdkEventButton event;
+	gint x, y, width, height;
+	gint row, col;
+
+	row = cell->row;
+	col = cell->view_col;
+
+	layout = GTK_LAYOUT (GNOME_CANVAS_ITEM (cell->item)->canvas);
+
+	e_table_item_get_cell_geometry (
+		cell->item, &row, &col, &x, &y, &width, &height);
+
+	event.x = x + width / 2 + (gint)(GNOME_CANVAS_ITEM (cell->item)->x1);
+	event.y = y + height / 2 + (gint)(GNOME_CANVAS_ITEM (cell->item)->y1);
+
+	event.type = GDK_BUTTON_PRESS;
+	event.window = gtk_layout_get_bin_window (layout);
+	event.button = 1;
+	event.send_event = TRUE;
+	event.time = GDK_CURRENT_TIME;
+	event.axes = NULL;
+
+	g_signal_emit_by_name (cell->item, "event", &event, &finished);
+}
+
+static void
+model_change_cb (ETableModel *etm,
+                 gint col,
+                 gint row,
+                 GalA11yECell *cell)
+{
+	gint value;
+
+	if (col == cell->model_col && row == cell->row) {
+
+		value = GPOINTER_TO_INT (
+			e_table_model_value_at (cell->cell_view->e_table_model,
+						cell->model_col, cell->row));
+		/* Cheat gnopernicus, or it will ignore the state change signal  */
+		atk_focus_tracker_notify (ATK_OBJECT (cell));
+
+		if (value)
+			gal_a11y_e_cell_add_state (cell, ATK_STATE_CHECKED, TRUE);
+		else
+			gal_a11y_e_cell_remove_state (cell, ATK_STATE_CHECKED, TRUE);
+	}
+}
+
+AtkObject *
+gal_a11y_e_cell_toggle_new (ETableItem *item,
+                            ECellView *cell_view,
+                            AtkObject *parent,
+                            gint model_col,
+                            gint view_col,
+                            gint row)
+{
+	AtkObject *a11y;
+	GalA11yECell *cell;
+	GalA11yECellToggle *toggle_cell;
+	gint value;
+
+	a11y = ATK_OBJECT (g_object_new (GAL_A11Y_TYPE_E_CELL_TOGGLE, NULL));
+
+	g_return_val_if_fail (a11y != NULL, NULL);
+
+	cell = GAL_A11Y_E_CELL (a11y);
+	toggle_cell = GAL_A11Y_E_CELL_TOGGLE (a11y);
+	a11y->role  = ATK_ROLE_TABLE_CELL;
+
+	gal_a11y_e_cell_construct (
+		a11y,
+		item,
+		cell_view,
+		parent,
+		model_col,
+		view_col,
+		row);
+
+	gal_a11y_e_cell_add_action (
+		cell,
+		"toggle",
+		/* Translators: description of a "toggle" action */
+		_("toggle the cell"),
+		NULL,              /* action keybinding */
+		toggle_cell_action);
+
+	toggle_cell->model_id = g_signal_connect (
+		item->table_model, "model_cell_changed",
+		(GCallback) model_change_cb, a11y);
+
+	value = GPOINTER_TO_INT (
+			e_table_model_value_at (
+				cell->cell_view->e_table_model,
+				cell->model_col, cell->row));
+	if (value)
+		gal_a11y_e_cell_add_state (cell, ATK_STATE_CHECKED, FALSE);
+	else
+		gal_a11y_e_cell_remove_state (cell, ATK_STATE_CHECKED, FALSE);
+
+	return a11y;
+}
diff --git a/e-util/gal-a11y-e-cell-toggle.h b/e-util/gal-a11y-e-cell-toggle.h
new file mode 100644
index 0000000..bd3670e
--- /dev/null
+++ b/e-util/gal-a11y-e-cell-toggle.h
@@ -0,0 +1,67 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef __GAL_A11Y_E_CELL_TOGGLE_H__
+#define __GAL_A11Y_E_CELL_TOGGLE_H__
+
+#include <atk/atk.h>
+#include "gal-a11y-e-cell.h"
+#include "gal-a11y-e-cell-toggle.h"
+
+G_BEGIN_DECLS
+
+#define GAL_A11Y_TYPE_E_CELL_TOGGLE            (gal_a11y_e_cell_toggle_get_type ())
+#define GAL_A11Y_E_CELL_TOGGLE(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GAL_A11Y_TYPE_E_CELL_TOGGLE, GalA11yECellToggle))
+#define GAL_A11Y_E_CELL_TOGGLE_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GAL_A11Y_E_CELL_TOGGLE, GalA11yECellToggleClass))
+#define GAL_A11Y_IS_E_CELL_TOGGLE(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GAL_A11Y_TYPE_E_CELL_TOGGLE))
+#define GAL_A11Y_IS_E_CELL_TOGGLE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GAL_A11Y_TYPE_E_CELL_TOGGLE))
+#define GAL_A11Y_E_CELL_TOGGLE_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), GAL_A11Y_TYPE_E_CELL_TOGGLE, GalA11yECellToggleClass))
+
+typedef struct _GalA11yECellToggle                  GalA11yECellToggle;
+typedef struct _GalA11yECellToggleClass             GalA11yECellToggleClass;
+
+struct _GalA11yECellToggle
+{
+  GalA11yECell parent;
+  gint         model_id;
+};
+
+GType gal_a11y_e_cell_toggle_get_type (void);
+
+struct _GalA11yECellToggleClass
+{
+  GalA11yECellClass parent_class;
+};
+
+AtkObject *gal_a11y_e_cell_toggle_new  (ETableItem *item,
+                                        ECellView  *cell_view,
+                                        AtkObject  *parent,
+                                        gint         model_col,
+                                        gint         view_col,
+                                        gint         row);
+
+G_END_DECLS
+
+#endif /* __GAL_A11Y_E_CELL_TOGGLE_H__ */
diff --git a/e-util/gal-a11y-e-cell-tree.c b/e-util/gal-a11y-e-cell-tree.c
new file mode 100644
index 0000000..e0757f5
--- /dev/null
+++ b/e-util/gal-a11y-e-cell-tree.c
@@ -0,0 +1,266 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Tim Wo <tim wo sun com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "gal-a11y-e-cell-tree.h"
+
+#include <atk/atk.h>
+#include <glib/gi18n.h>
+
+#include "e-cell-tree.h"
+#include "e-table.h"
+#include "e-tree-table-adapter.h"
+#include "gal-a11y-e-cell-registry.h"
+#include "gal-a11y-util.h"
+
+#define CS_CLASS(a11y) (G_TYPE_INSTANCE_GET_CLASS ((a11y), C_TYPE_STREAM, GalA11yECellTreeClass))
+static AtkObjectClass *a11y_parent_class;
+#define A11Y_PARENT_TYPE (gal_a11y_e_cell_get_type ())
+
+#define d(x)
+
+static void
+ectr_model_row_changed_cb (ETableModel *etm,
+                           gint row,
+                           GalA11yECell *a11y)
+{
+	ETreePath node;
+	ETreeModel *tree_model;
+	ETreeTableAdapter *tree_table_adapter;
+
+	g_return_if_fail (a11y);
+	if (a11y->row != row)
+		return;
+
+	node = e_table_model_value_at (etm, -1, a11y->row);
+	tree_model = e_table_model_value_at (etm, -2, a11y->row);
+	tree_table_adapter = e_table_model_value_at (etm, -3, a11y->row);
+
+	if (e_tree_model_node_is_expandable (tree_model, node)) {
+		gboolean is_exp = e_tree_table_adapter_node_is_expanded (tree_table_adapter, node);
+		if (is_exp)
+			gal_a11y_e_cell_add_state (a11y, ATK_STATE_EXPANDED, TRUE);
+		else
+			gal_a11y_e_cell_remove_state (a11y, ATK_STATE_EXPANDED, TRUE);
+	}
+}
+
+static void
+kill_view_cb (ECellView *subcell_view,
+             gpointer psubcell_a11ies)
+{
+	GList *node;
+	GList *subcell_a11ies = (GList *) psubcell_a11ies;
+	GalA11yECell *subcell;
+
+	for (node = subcell_a11ies; node != NULL; node = g_list_next (node))
+	{
+	    subcell = GAL_A11Y_E_CELL (node->data);
+	    if (subcell && subcell->cell_view == subcell_view)
+	    {
+		d (fprintf (stderr, "subcell_view %p deleted before the a11y object %p\n", subcell_view, subcell));
+		subcell->cell_view = NULL;
+	    }
+	}
+}
+
+static void
+ectr_subcell_weak_ref (GalA11yECellTree *a11y,
+                       GalA11yECell *subcell_a11y)
+{
+	ECellView *subcell_view = subcell_a11y ? subcell_a11y->cell_view : NULL;
+	if (subcell_a11y && subcell_view && subcell_view->kill_view_cb_data)
+	    subcell_view->kill_view_cb_data = g_list_remove (subcell_view->kill_view_cb_data, subcell_a11y);
+
+	g_signal_handler_disconnect (
+		GAL_A11Y_E_CELL (a11y)->item->table_model,
+		a11y->model_row_changed_id);
+	g_object_unref (a11y);
+}
+
+static void
+ectr_do_action_expand (AtkAction *action)
+{
+	GalA11yECell *a11y;
+	ETableModel *table_model;
+	ETreePath node;
+	ETreeModel *tree_model;
+	ETreeTableAdapter *tree_table_adapter;
+
+	a11y = GAL_A11Y_E_CELL (action);
+	table_model = a11y->item->table_model;
+	node = e_table_model_value_at (table_model, -1, a11y->row);
+	tree_model = e_table_model_value_at (table_model, -2, a11y->row);
+	tree_table_adapter = e_table_model_value_at (table_model, -3, a11y->row);
+
+	if (e_tree_model_node_is_expandable (tree_model, node)) {
+		e_tree_table_adapter_node_set_expanded (
+			tree_table_adapter, node, TRUE);
+		gal_a11y_e_cell_add_state (a11y, ATK_STATE_EXPANDED, TRUE);
+	}
+}
+
+static void
+ectr_do_action_collapse (AtkAction *action)
+{
+	GalA11yECell *a11y;
+	ETableModel *table_model;
+	ETreePath node;
+	ETreeModel *tree_model;
+	ETreeTableAdapter *tree_table_adapter;
+
+	a11y = GAL_A11Y_E_CELL (action);
+	table_model = a11y->item->table_model;
+	node = e_table_model_value_at (table_model, -1, a11y->row);
+	tree_model = e_table_model_value_at (table_model, -2, a11y->row);
+	tree_table_adapter = e_table_model_value_at (table_model, -3, a11y->row);
+
+	if (e_tree_model_node_is_expandable (tree_model, node)) {
+		e_tree_table_adapter_node_set_expanded (
+			tree_table_adapter, node, FALSE);
+		gal_a11y_e_cell_remove_state (a11y, ATK_STATE_EXPANDED, TRUE);
+	}
+}
+
+static void
+ectr_class_init (GalA11yECellTreeClass *class)
+{
+	a11y_parent_class        = g_type_class_ref (A11Y_PARENT_TYPE);
+}
+
+static void
+ectr_init (GalA11yECellTree *a11y)
+{
+}
+
+GType
+gal_a11y_e_cell_tree_get_type (void)
+{
+	static GType type = 0;
+
+	if (!type) {
+		GTypeInfo info = {
+			sizeof (GalA11yECellTreeClass),
+			(GBaseInitFunc) NULL,
+			(GBaseFinalizeFunc) NULL,
+			(GClassInitFunc) ectr_class_init,
+			(GClassFinalizeFunc) NULL,
+			NULL, /* class_data */
+			sizeof (GalA11yECellTree),
+			0,
+			(GInstanceInitFunc) ectr_init,
+			NULL /* value_cell_text */
+		};
+
+		type = g_type_register_static (A11Y_PARENT_TYPE, "GalA11yECellTree", &info, 0);
+		gal_a11y_e_cell_type_add_action_interface (type);
+	}
+
+	return type;
+}
+
+AtkObject *
+gal_a11y_e_cell_tree_new (ETableItem *item,
+                          ECellView *cell_view,
+                          AtkObject *parent,
+                          gint model_col,
+                          gint view_col,
+                          gint row)
+{
+	AtkObject *subcell_a11y;
+	GalA11yECellTree *a11y;
+
+	ETreePath node;
+	ETreeModel *tree_model;
+	ETreeTableAdapter *tree_table_adapter;
+
+	ECellView *subcell_view;
+	subcell_view = e_cell_tree_view_get_subcell_view (cell_view);
+
+	if (subcell_view->ecell) {
+		subcell_a11y = gal_a11y_e_cell_registry_get_object (
+			NULL,
+			item,
+			subcell_view,
+			parent,
+			model_col,
+			view_col,
+			row);
+		gal_a11y_e_cell_add_action (
+			GAL_A11Y_E_CELL (subcell_a11y),
+			"expand",
+			/* Translators: description of an "expand" action */
+			_("expands the row in the ETree containing this cell"),
+			NULL,
+			(ACTION_FUNC) ectr_do_action_expand);
+
+		gal_a11y_e_cell_add_action (
+			GAL_A11Y_E_CELL (subcell_a11y),
+			"collapse",
+			/* Translators: description of a "collapse" action */
+			_("collapses the row in the ETree containing this cell"),
+			NULL,
+			(ACTION_FUNC) ectr_do_action_collapse);
+
+		/* init AtkStates for the cell's a11y object */
+		node = e_table_model_value_at (item->table_model, -1, row);
+		tree_model = e_table_model_value_at (item->table_model, -2, row);
+		tree_table_adapter = e_table_model_value_at (item->table_model, -3, row);
+		if (e_tree_model_node_is_expandable (tree_model, node)) {
+			gal_a11y_e_cell_add_state (GAL_A11Y_E_CELL (subcell_a11y), ATK_STATE_EXPANDABLE, FALSE);
+			if (e_tree_table_adapter_node_is_expanded (tree_table_adapter, node))
+				gal_a11y_e_cell_add_state (GAL_A11Y_E_CELL (subcell_a11y), ATK_STATE_EXPANDED, FALSE);
+		}
+	}
+	else
+		subcell_a11y = NULL;
+
+	/* create a companion a11y object, this object has type GalA11yECellTree
+	 * and it connects to some signals to determine whether a tree cell is
+	 * expanded or collapsed */
+	a11y = g_object_new (gal_a11y_e_cell_tree_get_type (), NULL);
+	gal_a11y_e_cell_construct (
+		ATK_OBJECT (a11y),
+		item,
+		cell_view,
+		parent,
+		model_col,
+		view_col,
+		row);
+	a11y->model_row_changed_id = g_signal_connect (
+		item->table_model, "model_row_changed",
+		G_CALLBACK (ectr_model_row_changed_cb), subcell_a11y);
+
+	if (subcell_a11y && subcell_view)
+	{
+	    subcell_view->kill_view_cb = kill_view_cb;
+	    if (!g_list_find (subcell_view->kill_view_cb_data, subcell_a11y))
+		subcell_view->kill_view_cb_data = g_list_append (subcell_view->kill_view_cb_data, subcell_a11y);
+	}
+
+	g_object_weak_ref (G_OBJECT (subcell_a11y), (GWeakNotify) ectr_subcell_weak_ref, a11y);
+
+	return subcell_a11y;
+}
diff --git a/e-util/gal-a11y-e-cell-tree.h b/e-util/gal-a11y-e-cell-tree.h
new file mode 100644
index 0000000..caa5f40
--- /dev/null
+++ b/e-util/gal-a11y-e-cell-tree.h
@@ -0,0 +1,66 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Tim Wo <tim wo sun com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef __GAL_A11Y_E_CELL_TREE_H__
+#define __GAL_A11Y_E_CELL_TREE_H__
+
+#include <e-util/e-table-item.h>
+#include <e-util/e-cell-tree.h>
+#include <e-util/gal-a11y-e-cell.h>
+
+#define GAL_A11Y_TYPE_E_CELL_TREE            (gal_a11y_e_cell_tree_get_type ())
+#define GAL_A11Y_E_CELL_TREE(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GAL_A11Y_TYPE_E_CELL_TREE, GalA11yECellTree))
+#define GAL_A11Y_E_CELL_TREE_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GAL_A11Y_TYPE_E_CELL_TREE, GalA11yECellTreeClass))
+#define GAL_A11Y_IS_E_CELL_TREE(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GAL_A11Y_TYPE_E_CELL_TREE))
+#define GAL_A11Y_IS_E_CELL_TREE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GAL_A11Y_TYPE_E_CELL_TREE))
+
+typedef struct _GalA11yECellTree GalA11yECellTree;
+typedef struct _GalA11yECellTreeClass GalA11yECellTreeClass;
+typedef struct _GalA11yECellTreePrivate GalA11yECellTreePrivate;
+
+/* This struct should actually be larger as this isn't what we derive from.
+ * The GalA11yECellTreePrivate comes right after the parent class structure.
+ **/
+struct _GalA11yECellTree {
+	GalA11yECell object;
+
+	gint model_row_changed_id;
+};
+
+struct _GalA11yECellTreeClass {
+	GalA11yECellClass parent_class;
+};
+
+/* Standard Glib function */
+GType      gal_a11y_e_cell_tree_get_type   (void);
+AtkObject *gal_a11y_e_cell_tree_new	   (ETableItem *item,
+					    ECellView  *cell_view,
+					    AtkObject  *parent,
+					    gint         model_col,
+					    gint         view_col,
+					    gint         row);
+
+#endif /* __GAL_A11Y_E_CELL_TREE_H__ */
diff --git a/e-util/gal-a11y-e-cell-vbox.c b/e-util/gal-a11y-e-cell-vbox.c
new file mode 100644
index 0000000..7864dc0
--- /dev/null
+++ b/e-util/gal-a11y-e-cell-vbox.c
@@ -0,0 +1,235 @@
+/*
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Eric Zhao <eric zhao sun com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ * Copyright (C) 2004 Sun Microsystem, Inc.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "gal-a11y-e-cell-vbox.h"
+
+#include <atk/atk.h>
+
+#include "e-cell-vbox.h"
+#include "gal-a11y-e-cell-registry.h"
+
+static GObjectClass *parent_class;
+static AtkComponentIface *component_parent_iface;
+#define PARENT_TYPE (gal_a11y_e_cell_get_type ())
+
+static gint
+ecv_get_n_children (AtkObject *a11y)
+{
+	g_return_val_if_fail (GAL_A11Y_IS_E_CELL_VBOX (a11y), 0);
+
+	return GAL_A11Y_E_CELL_VBOX (a11y)->a11y_subcell_count;
+}
+
+static void
+subcell_destroyed (gpointer data)
+{
+	GalA11yECell *cell;
+	AtkObject *parent;
+	GalA11yECellVbox *gaev;
+
+	g_return_if_fail (GAL_A11Y_IS_E_CELL (data));
+	cell = GAL_A11Y_E_CELL (data);
+
+	parent = atk_object_get_parent (ATK_OBJECT (cell));
+	g_return_if_fail (GAL_A11Y_IS_E_CELL_VBOX (parent));
+	gaev = GAL_A11Y_E_CELL_VBOX (parent);
+
+	if (cell->view_col < gaev->a11y_subcell_count)
+		gaev->a11y_subcells[cell->view_col] = NULL;
+}
+
+static AtkObject *
+ecv_ref_child (AtkObject *a11y,
+               gint i)
+{
+	GalA11yECellVbox *gaev = GAL_A11Y_E_CELL_VBOX (a11y);
+	GalA11yECell *gaec = GAL_A11Y_E_CELL (a11y);
+	ECellVboxView *ecvv = (ECellVboxView *) (gaec->cell_view);
+	AtkObject *ret;
+	if (i < gaev->a11y_subcell_count) {
+		if (gaev->a11y_subcells[i] == NULL) {
+			ECellView *subcell_view;
+			gint model_col, row;
+			row = gaec->row;
+			model_col = ecvv->model_cols[i];
+			subcell_view = ecvv->subcell_views[i];
+			/* FIXME Should the view column use a fake
+			 *       one or the same as its parent? */
+			ret = gal_a11y_e_cell_registry_get_object (
+				NULL,
+				gaec->item,
+				subcell_view,
+				a11y,
+				model_col,
+				gaec->view_col,
+				row);
+			gaev->a11y_subcells[i] = ret;
+			g_object_ref (ret);
+			g_object_weak_ref (
+				G_OBJECT (ret),
+				(GWeakNotify) subcell_destroyed,
+				ret);
+		} else {
+			ret = (AtkObject *) gaev->a11y_subcells[i];
+			if (ATK_IS_OBJECT (ret))
+				g_object_ref (ret);
+			else
+				ret = NULL;
+		}
+	} else {
+		ret = NULL;
+	}
+
+	return ret;
+}
+
+static void
+ecv_dispose (GObject *object)
+{
+	GalA11yECellVbox *gaev = GAL_A11Y_E_CELL_VBOX (object);
+	if (gaev->a11y_subcells)
+		g_free (gaev->a11y_subcells);
+
+	if (parent_class->dispose)
+		parent_class->dispose (object);
+}
+
+/* AtkComponet interface */
+static AtkObject *
+ecv_ref_accessible_at_point (AtkComponent *component,
+                             gint x,
+                             gint y,
+                             AtkCoordType coord_type)
+{
+	gint x0, y0, width, height;
+	gint subcell_height, i;
+
+	GalA11yECell *gaec = GAL_A11Y_E_CELL (component);
+	ECellVboxView *ecvv = (ECellVboxView *) (gaec->cell_view);
+
+	atk_component_get_extents (component, &x0, &y0, &width, &height, coord_type);
+	x -= x0;
+	y -= y0;
+	if (x < 0 || x > width || y < 0 || y > height)
+		return NULL;
+
+	for (i = 0; i < ecvv->subcell_view_count; i++) {
+		subcell_height = e_cell_height (
+			ecvv->subcell_views[i], ecvv->model_cols[i],
+			gaec->view_col, gaec->row);
+		if (0 <= y && y <= subcell_height) {
+			return ecv_ref_child ((AtkObject *) component, i);
+		} else
+			y -= subcell_height;
+	}
+
+	return NULL;
+}
+
+static void
+ecv_class_init (GalA11yECellVboxClass *class)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (class);
+	AtkObjectClass *a11y_class = ATK_OBJECT_CLASS (class);
+	parent_class		   = g_type_class_ref (PARENT_TYPE);
+
+	object_class->dispose	   = ecv_dispose;
+
+	a11y_class->get_n_children = ecv_get_n_children;
+	a11y_class->ref_child	   = ecv_ref_child;
+}
+
+static void
+ecv_init (GalA11yECellVbox *a11y)
+{
+}
+
+static void
+ecv_atk_component_iface_init (AtkComponentIface *iface)
+{
+	component_parent_iface         = g_type_interface_peek_parent (iface);
+
+	iface->ref_accessible_at_point = ecv_ref_accessible_at_point;
+}
+
+GType
+gal_a11y_e_cell_vbox_get_type (void)
+{
+	static GType type = 0;
+	if (!type) {
+		GTypeInfo info = {
+			sizeof (GalA11yECellVboxClass),
+			(GBaseInitFunc) NULL,
+			(GBaseFinalizeFunc) NULL,
+			(GClassInitFunc) ecv_class_init,
+			(GClassFinalizeFunc) NULL,
+			NULL, /* class_data */
+			sizeof (GalA11yECellVbox),
+			0,
+			(GInstanceInitFunc) ecv_init,
+			NULL /* value_cell */
+		};
+
+		static const GInterfaceInfo atk_component_info = {
+			(GInterfaceInitFunc) ecv_atk_component_iface_init,
+			(GInterfaceFinalizeFunc) NULL,
+			NULL
+		};
+
+		type = g_type_register_static (PARENT_TYPE, "GalA11yECellVbox", &info, 0);
+		gal_a11y_e_cell_type_add_action_interface (type);
+		g_type_add_interface_static (type, ATK_TYPE_COMPONENT, &atk_component_info);
+	}
+
+	return type;
+}
+
+AtkObject *gal_a11y_e_cell_vbox_new	(ETableItem *item,
+					 ECellView  *cell_view,
+					 AtkObject  *parent,
+					 gint         model_col,
+					 gint         view_col,
+					 gint         row)
+{
+	AtkObject *a11y;
+	GalA11yECell *gaec;
+	GalA11yECellVbox *gaev;
+	ECellVboxView *ecvv;
+
+	a11y = g_object_new (gal_a11y_e_cell_vbox_get_type (), NULL);
+
+	gal_a11y_e_cell_construct (
+		a11y, item, cell_view, parent, model_col, view_col, row);
+
+	gaec = GAL_A11Y_E_CELL (a11y);
+	gaev = GAL_A11Y_E_CELL_VBOX (a11y);
+	ecvv = (ECellVboxView *) (gaec->cell_view);
+	gaev->a11y_subcell_count = ecvv->subcell_view_count;
+	gaev->a11y_subcells = g_malloc0 (sizeof (AtkObject *) * gaev->a11y_subcell_count);
+	return a11y;
+}
diff --git a/e-util/gal-a11y-e-cell-vbox.h b/e-util/gal-a11y-e-cell-vbox.h
new file mode 100644
index 0000000..cb6807e
--- /dev/null
+++ b/e-util/gal-a11y-e-cell-vbox.h
@@ -0,0 +1,67 @@
+/*
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Eric Zhao <eric zhao sun com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ * Copyright (C) 2004 Sun Microsystem, Inc.
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef __GAL_A11Y_E_CELL_VBOX_H__
+#define __GAL_A11Y_E_CELL_VBOX_H__
+
+#include "gal-a11y-e-cell.h"
+
+G_BEGIN_DECLS
+
+#define GAL_A11Y_TYPE_E_CELL_VBOX            (gal_a11y_e_cell_vbox_get_type ())
+#define GAL_A11Y_E_CELL_VBOX(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GAL_A11Y_TYPE_E_CELL_VBOX, GalA11yECellVbox))
+#define GAL_A11Y_E_CELL_VBOX_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GAL_A11Y_E_CELL_VBOX, GalA11yECellVboxClass))
+#define GAL_A11Y_IS_E_CELL_VBOX(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GAL_A11Y_TYPE_E_CELL_VBOX))
+#define GAL_A11Y_IS_E_CELL_VBOX_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GAL_A11Y_TYPE_E_CELL_VBOX))
+#define GAL_A11Y_E_CELL_VBOX_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), GAL_A11Y_TYPE_E_CELL_VBOX, GalA11yECellVboxClass))
+
+typedef struct _GalA11yECellVbox	GalA11yECellVbox;
+typedef struct _GalA11yECellVboxClass	GalA11yECellVboxClass;
+
+struct _GalA11yECellVbox
+{
+	GalA11yECell	object;
+	gint		a11y_subcell_count;
+	gpointer       *a11y_subcells;
+};
+
+struct _GalA11yECellVboxClass
+{
+	GalA11yECellClass parent_class;
+};
+
+GType gal_a11y_e_cell_vbox_get_type	(void);
+AtkObject *gal_a11y_e_cell_vbox_new	(ETableItem *item,
+					 ECellView  *cell_view,
+					 AtkObject  *parent,
+					 gint         model_col,
+					 gint         view_col,
+					 gint         row);
+
+G_END_DECLS
+#endif /* __GAL_A11Y_E_CELL_VBOX_H__ */
diff --git a/e-util/gal-a11y-e-cell.c b/e-util/gal-a11y-e-cell.c
new file mode 100644
index 0000000..9f16b34
--- /dev/null
+++ b/e-util/gal-a11y-e-cell.c
@@ -0,0 +1,648 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Christopher James Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "gal-a11y-e-cell.h"
+
+#include <string.h>
+
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+
+#include "e-table.h"
+#include "e-tree.h"
+#include "gal-a11y-e-cell-vbox.h"
+#include "gal-a11y-e-table-item.h"
+#include "gal-a11y-util.h"
+
+static GObjectClass *parent_class;
+#define PARENT_TYPE (atk_object_get_type ())
+
+#if 0
+static void
+unref_item (gpointer user_data,
+            GObject *obj_loc)
+{
+	GalA11yECell *a11y = GAL_A11Y_E_CELL (user_data);
+	a11y->item = NULL;
+	g_object_unref (a11y);
+}
+
+static void
+unref_cell (gpointer user_data,
+            GObject *obj_loc)
+{
+	GalA11yECell *a11y = GAL_A11Y_E_CELL (user_data);
+	a11y->cell_view = NULL;
+	g_object_unref (a11y);
+}
+#endif
+
+static gboolean
+is_valid (AtkObject *cell)
+{
+	GalA11yECell *a11y = GAL_A11Y_E_CELL (cell);
+	GalA11yETableItem *a11yItem = GAL_A11Y_E_TABLE_ITEM (a11y->parent);
+	AtkStateSet *item_ss;
+	gboolean ret = TRUE;
+
+	item_ss = atk_object_ref_state_set (ATK_OBJECT (a11yItem));
+	if (atk_state_set_contains_state (item_ss, ATK_STATE_DEFUNCT))
+		ret = FALSE;
+
+	g_object_unref (item_ss);
+
+	if (ret && atk_state_set_contains_state (a11y->state_set, ATK_STATE_DEFUNCT))
+		ret = FALSE;
+
+	return ret;
+}
+
+static void
+gal_a11y_e_cell_dispose (GObject *object)
+{
+	GalA11yECell *a11y = GAL_A11Y_E_CELL (object);
+
+#if 0
+	if (a11y->item)
+		g_object_unref (a11y->item);  /*, unref_item, a11y); */
+	if (a11y->cell_view)
+		g_object_unref (a11y->cell_view); /*, unref_cell, a11y); */
+	if (a11y->parent)
+		g_object_unref (a11y->parent);
+#endif
+
+	if (a11y->state_set) {
+		g_object_unref (a11y->state_set);
+		a11y->state_set = NULL;
+	}
+
+	if (parent_class->dispose)
+		parent_class->dispose (object);
+
+}
+
+/* Static functions */
+static const gchar *
+gal_a11y_e_cell_get_name (AtkObject *a11y)
+{
+	GalA11yECell *cell = GAL_A11Y_E_CELL (a11y);
+	ETableCol *ecol;
+
+	if (a11y->name != NULL && strcmp (a11y->name, ""))
+		return a11y->name;
+
+	if (cell->item != NULL) {
+		ecol = e_table_header_get_column (cell->item->header, cell->view_col);
+		if (ecol != NULL)
+			return ecol->text;
+	}
+
+	return _("Table Cell");
+}
+
+static AtkStateSet *
+gal_a11y_e_cell_ref_state_set (AtkObject *accessible)
+{
+	GalA11yECell *cell = GAL_A11Y_E_CELL (accessible);
+
+	g_return_val_if_fail (cell->state_set, NULL);
+
+	g_object_ref (cell->state_set);
+
+	return cell->state_set;
+}
+
+static AtkObject *
+gal_a11y_e_cell_get_parent (AtkObject *accessible)
+{
+	GalA11yECell *a11y = GAL_A11Y_E_CELL (accessible);
+	return a11y->parent;
+}
+
+static gint
+gal_a11y_e_cell_get_index_in_parent (AtkObject *accessible)
+{
+	GalA11yECell *a11y = GAL_A11Y_E_CELL (accessible);
+
+	if (!is_valid (accessible))
+		return -1;
+
+	return (a11y->row + 1) * a11y->item->cols + a11y->view_col;
+}
+
+/* Component IFace */
+static void
+gal_a11y_e_cell_get_extents (AtkComponent *component,
+                             gint *x,
+                             gint *y,
+                             gint *width,
+                             gint *height,
+                             AtkCoordType coord_type)
+{
+	GalA11yECell *a11y = GAL_A11Y_E_CELL (component);
+	GtkWidget *tableOrTree;
+	gint row;
+	gint col;
+	gint xval;
+	gint yval;
+
+	row = a11y->row;
+	col = a11y->view_col;
+
+	tableOrTree = gtk_widget_get_parent (GTK_WIDGET (a11y->item->parent.canvas));
+	if (E_IS_TREE (tableOrTree)) {
+		e_tree_get_cell_geometry (
+			E_TREE (tableOrTree),
+			row, col, &xval, &yval,
+			width, height);
+	} else {
+		e_table_get_cell_geometry (
+			E_TABLE (tableOrTree),
+			row, col, &xval, &yval,
+			width, height);
+	}
+
+	atk_component_get_position (
+		ATK_COMPONENT (a11y->parent),
+		x, y, coord_type);
+	if (x && *x != G_MININT)
+		*x += xval;
+	if (y && *y != G_MININT)
+		*y += yval;
+}
+
+static gboolean
+gal_a11y_e_cell_grab_focus (AtkComponent *component)
+{
+	GalA11yECell *a11y;
+	gint index;
+	GtkWidget *toplevel;
+	GalA11yETableItem *a11yTableItem;
+
+	a11y = GAL_A11Y_E_CELL (component);
+
+	/* for e_cell_vbox's children, we just grab the e_cell_vbox */
+	if (GAL_A11Y_IS_E_CELL_VBOX (a11y->parent)) {
+		return atk_component_grab_focus (ATK_COMPONENT (a11y->parent));
+	}
+
+	a11yTableItem = GAL_A11Y_E_TABLE_ITEM (a11y->parent);
+	index = atk_object_get_index_in_parent (ATK_OBJECT (a11y));
+
+	atk_selection_clear_selection (ATK_SELECTION (a11yTableItem));
+	atk_selection_add_selection (ATK_SELECTION (a11yTableItem), index);
+
+	gtk_widget_grab_focus (
+		GTK_WIDGET (GNOME_CANVAS_ITEM (a11y->item)->canvas));
+	toplevel = gtk_widget_get_toplevel (
+		GTK_WIDGET (GNOME_CANVAS_ITEM (a11y->item)->canvas));
+	if (toplevel && gtk_widget_is_toplevel (toplevel))
+		gtk_window_present (GTK_WINDOW (toplevel));
+
+	return TRUE;
+}
+
+/* Table IFace */
+
+static void
+gal_a11y_e_cell_atk_component_iface_init (AtkComponentIface *iface)
+{
+	iface->get_extents = gal_a11y_e_cell_get_extents;
+	iface->grab_focus  = gal_a11y_e_cell_grab_focus;
+}
+
+static void
+gal_a11y_e_cell_class_init (GalA11yECellClass *class)
+{
+	AtkObjectClass *atk_object_class = ATK_OBJECT_CLASS (class);
+	GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+	parent_class                          = g_type_class_ref (PARENT_TYPE);
+
+	object_class->dispose                 = gal_a11y_e_cell_dispose;
+
+	atk_object_class->get_parent          = gal_a11y_e_cell_get_parent;
+	atk_object_class->get_index_in_parent = gal_a11y_e_cell_get_index_in_parent;
+	atk_object_class->ref_state_set       = gal_a11y_e_cell_ref_state_set;
+	atk_object_class->get_name            = gal_a11y_e_cell_get_name;
+}
+
+static void
+gal_a11y_e_cell_init (GalA11yECell *a11y)
+{
+	a11y->item = NULL;
+	a11y->cell_view = NULL;
+	a11y->parent = NULL;
+	a11y->model_col = -1;
+	a11y->view_col = -1;
+	a11y->row = -1;
+
+	a11y->state_set = atk_state_set_new ();
+	atk_state_set_add_state (a11y->state_set, ATK_STATE_TRANSIENT);
+	atk_state_set_add_state (a11y->state_set, ATK_STATE_ENABLED);
+	atk_state_set_add_state (a11y->state_set, ATK_STATE_SENSITIVE);
+	atk_state_set_add_state (a11y->state_set, ATK_STATE_SELECTABLE);
+	atk_state_set_add_state (a11y->state_set, ATK_STATE_SHOWING);
+	atk_state_set_add_state (a11y->state_set, ATK_STATE_FOCUSABLE);
+	atk_state_set_add_state (a11y->state_set, ATK_STATE_VISIBLE);
+}
+
+static ActionInfo *
+_gal_a11y_e_cell_get_action_info (GalA11yECell *cell,
+                                  gint index)
+{
+	GList *list_node;
+
+	g_return_val_if_fail (GAL_A11Y_IS_E_CELL (cell), NULL);
+	if (cell->action_list == NULL)
+		return NULL;
+	list_node = g_list_nth (cell->action_list, index);
+	if (!list_node)
+		return NULL;
+	return (ActionInfo *) (list_node->data);
+}
+
+static void
+_gal_a11y_e_cell_destroy_action_info (gpointer action_info,
+                                      gpointer user_data)
+{
+	ActionInfo *info = (ActionInfo *) action_info;
+
+	g_return_if_fail (info != NULL);
+	g_free (info->name);
+	g_free (info->description);
+	g_free (info->keybinding);
+	g_free (info);
+}
+
+gboolean
+gal_a11y_e_cell_add_action (GalA11yECell *cell,
+                            const gchar *action_name,
+                            const gchar *action_description,
+                            const gchar *action_keybinding,
+                            ACTION_FUNC action_func)
+{
+	ActionInfo *info;
+	g_return_val_if_fail (GAL_A11Y_IS_E_CELL (cell), FALSE);
+	info = g_new (ActionInfo, 1);
+
+	if (action_name != NULL)
+		info->name = g_strdup (action_name);
+	else
+		info->name = NULL;
+
+	if (action_description != NULL)
+		info->description = g_strdup (action_description);
+	else
+		info->description = NULL;
+	if (action_keybinding != NULL)
+		info->keybinding = g_strdup (action_keybinding);
+	else
+		info->keybinding = NULL;
+	info->do_action_func = action_func;
+
+	cell->action_list = g_list_append (cell->action_list, (gpointer) info);
+	return TRUE;
+}
+
+gboolean
+gal_a11y_e_cell_remove_action (GalA11yECell *cell,
+                               gint action_index)
+{
+	GList *list_node;
+
+	g_return_val_if_fail (GAL_A11Y_IS_E_CELL (cell), FALSE);
+	list_node = g_list_nth (cell->action_list, action_index);
+	if (!list_node)
+		return FALSE;
+	g_return_val_if_fail (list_node->data != NULL, FALSE);
+	_gal_a11y_e_cell_destroy_action_info (list_node->data, NULL);
+	cell->action_list = g_list_remove_link (cell->action_list, list_node);
+
+	return TRUE;
+}
+
+gboolean
+gal_a11y_e_cell_remove_action_by_name (GalA11yECell *cell,
+                                       const gchar *action_name)
+{
+	GList *list_node;
+
+	g_return_val_if_fail (GAL_A11Y_IS_E_CELL (cell), FALSE);
+
+	for (list_node = cell->action_list; list_node; list_node = list_node->next) {
+		if (!g_ascii_strcasecmp (((ActionInfo *)(list_node->data))->name, action_name)) {
+			break;
+		}
+	}
+
+	g_return_val_if_fail (list_node != NULL, FALSE);
+
+	_gal_a11y_e_cell_destroy_action_info (list_node->data, NULL);
+	cell->action_list = g_list_remove_link (cell->action_list, list_node);
+
+	return TRUE;
+}
+
+static gint
+gal_a11y_e_cell_action_get_n_actions (AtkAction *action)
+{
+	GalA11yECell *cell = GAL_A11Y_E_CELL (action);
+	if (cell->action_list != NULL)
+		return g_list_length (cell->action_list);
+	else
+		return 0;
+}
+
+static const gchar *
+gal_a11y_e_cell_action_get_name (AtkAction *action,
+                                 gint index)
+{
+	GalA11yECell *cell = GAL_A11Y_E_CELL (action);
+	ActionInfo *info = _gal_a11y_e_cell_get_action_info (cell, index);
+
+	if (info == NULL)
+		return NULL;
+	return info->name;
+}
+
+static const gchar *
+gal_a11y_e_cell_action_get_description (AtkAction *action,
+                                        gint index)
+{
+	GalA11yECell *cell = GAL_A11Y_E_CELL (action);
+	ActionInfo *info = _gal_a11y_e_cell_get_action_info (cell, index);
+
+	if (info == NULL)
+		return NULL;
+	return info->description;
+}
+
+static gboolean
+gal_a11y_e_cell_action_set_description (AtkAction *action,
+                                        gint index,
+                                        const gchar *desc)
+{
+	GalA11yECell *cell = GAL_A11Y_E_CELL (action);
+	ActionInfo *info = _gal_a11y_e_cell_get_action_info (cell, index);
+
+	if (info == NULL)
+		return FALSE;
+	g_free (info->description);
+	info->description = g_strdup (desc);
+	return TRUE;
+}
+
+static const gchar *
+gal_a11y_e_cell_action_get_keybinding (AtkAction *action,
+                                       gint index)
+{
+	GalA11yECell *cell = GAL_A11Y_E_CELL (action);
+	ActionInfo *info = _gal_a11y_e_cell_get_action_info (cell, index);
+	if (info == NULL)
+		return NULL;
+
+	return info->keybinding;
+}
+
+static gboolean
+idle_do_action (gpointer data)
+{
+	GalA11yECell *cell;
+
+	cell = GAL_A11Y_E_CELL (data);
+
+	if (!is_valid (ATK_OBJECT (cell)))
+		return FALSE;
+
+	cell->action_idle_handler = 0;
+	cell->action_func (cell);
+	g_object_unref (cell);
+
+	return FALSE;
+}
+
+static gboolean
+gal_a11y_e_cell_action_do_action (AtkAction *action,
+                                  gint index)
+{
+	GalA11yECell *cell = GAL_A11Y_E_CELL (action);
+	ActionInfo *info = _gal_a11y_e_cell_get_action_info (cell, index);
+
+	if (!is_valid (ATK_OBJECT (action)))
+		return FALSE;
+
+	if (info == NULL)
+		return FALSE;
+	g_return_val_if_fail (info->do_action_func, FALSE);
+	if (cell->action_idle_handler)
+		return FALSE;
+	cell->action_func = info->do_action_func;
+	g_object_ref (cell);
+	cell->action_idle_handler = g_idle_add (idle_do_action, cell);
+
+	return TRUE;
+}
+
+static void
+gal_a11y_e_cell_atk_action_interface_init (AtkActionIface *iface)
+{
+  g_return_if_fail (iface != NULL);
+
+  iface->get_n_actions = gal_a11y_e_cell_action_get_n_actions;
+  iface->do_action = gal_a11y_e_cell_action_do_action;
+  iface->get_name = gal_a11y_e_cell_action_get_name;
+  iface->get_description = gal_a11y_e_cell_action_get_description;
+  iface->set_description = gal_a11y_e_cell_action_set_description;
+  iface->get_keybinding = gal_a11y_e_cell_action_get_keybinding;
+}
+
+void
+gal_a11y_e_cell_type_add_action_interface (GType type)
+{
+	static const GInterfaceInfo atk_action_info =
+	{
+	(GInterfaceInitFunc) gal_a11y_e_cell_atk_action_interface_init,
+	(GInterfaceFinalizeFunc) NULL,
+	NULL
+	};
+
+	g_type_add_interface_static (
+		type, ATK_TYPE_ACTION,
+		&atk_action_info);
+}
+
+gboolean
+gal_a11y_e_cell_add_state (GalA11yECell *cell,
+                           AtkStateType state_type,
+                           gboolean emit_signal)
+{
+	if (!atk_state_set_contains_state (cell->state_set, state_type)) {
+		gboolean rc;
+
+		rc = atk_state_set_add_state (cell->state_set, state_type);
+		/*
+		 * The signal should only be generated if the value changed,
+		 * not when the cell is set up.  So states that are set
+		 * initially should pass FALSE as the emit_signal argument.
+		 */
+
+		if (emit_signal) {
+			atk_object_notify_state_change (ATK_OBJECT (cell), state_type, TRUE);
+			/* If state_type is ATK_STATE_VISIBLE, additional
+			 * notification */
+			if (state_type == ATK_STATE_VISIBLE)
+				g_signal_emit_by_name (cell, "visible_data_changed");
+		}
+
+		return rc;
+	}
+	else
+		return FALSE;
+}
+
+gboolean
+gal_a11y_e_cell_remove_state (GalA11yECell *cell,
+                              AtkStateType state_type,
+                              gboolean emit_signal)
+{
+	if (atk_state_set_contains_state (cell->state_set, state_type)) {
+		gboolean rc;
+
+		rc = atk_state_set_remove_state (cell->state_set, state_type);
+		/*
+		 * The signal should only be generated if the value changed,
+		 * not when the cell is set up.  So states that are set
+		 * initially should pass FALSE as the emit_signal argument.
+		 */
+
+		if (emit_signal) {
+			atk_object_notify_state_change (ATK_OBJECT (cell), state_type, FALSE);
+			/* If state_type is ATK_STATE_VISIBLE, additional notification */
+			if (state_type == ATK_STATE_VISIBLE)
+				g_signal_emit_by_name (cell, "visible_data_changed");
+		}
+
+		return rc;
+	}
+	else
+		return FALSE;
+}
+
+/**
+ * gal_a11y_e_cell_get_type:
+ * @void:
+ *
+ * Registers the &GalA11yECell class if necessary, and returns the type ID
+ * associated to it.
+ *
+ * Return value: The type ID of the &GalA11yECell class.
+ **/
+GType
+gal_a11y_e_cell_get_type (void)
+{
+	static GType type = 0;
+
+	if (!type) {
+		GTypeInfo info = {
+			sizeof (GalA11yECellClass),
+			(GBaseInitFunc) NULL,
+			(GBaseFinalizeFunc) NULL,
+			(GClassInitFunc) gal_a11y_e_cell_class_init,
+			(GClassFinalizeFunc) NULL,
+			NULL, /* class_data */
+			sizeof (GalA11yECell),
+			0,
+			(GInstanceInitFunc) gal_a11y_e_cell_init,
+			NULL /* value_cell */
+		};
+
+		static const GInterfaceInfo atk_component_info = {
+			(GInterfaceInitFunc) gal_a11y_e_cell_atk_component_iface_init,
+			(GInterfaceFinalizeFunc) NULL,
+			NULL
+		};
+
+		type = g_type_register_static (PARENT_TYPE, "GalA11yECell", &info, 0);
+		g_type_add_interface_static (type, ATK_TYPE_COMPONENT, &atk_component_info);
+	}
+
+	return type;
+}
+
+AtkObject *
+gal_a11y_e_cell_new (ETableItem *item,
+                     ECellView *cell_view,
+                     AtkObject *parent,
+                     gint model_col,
+                     gint view_col,
+                     gint row)
+{
+	AtkObject *a11y;
+
+	a11y = g_object_new (gal_a11y_e_cell_get_type (), NULL);
+
+	gal_a11y_e_cell_construct (
+		a11y,
+		item,
+		cell_view,
+		parent,
+		model_col,
+		view_col,
+		row);
+	return a11y;
+}
+
+void
+gal_a11y_e_cell_construct (AtkObject *object,
+                           ETableItem *item,
+                           ECellView *cell_view,
+                           AtkObject *parent,
+                           gint model_col,
+                           gint view_col,
+                           gint row)
+{
+	GalA11yECell *a11y = GAL_A11Y_E_CELL (object);
+	a11y->item      = item;
+	a11y->cell_view = cell_view;
+	a11y->parent    = parent;
+	a11y->model_col = model_col;
+	a11y->view_col  = view_col;
+	a11y->row       = row;
+	ATK_OBJECT (a11y) ->role	= ATK_ROLE_TABLE_CELL;
+
+	if (item)
+		g_object_ref (item);
+
+#if 0
+	if (parent)
+		g_object_ref (parent);
+
+	if (cell_view)
+		g_object_ref (cell_view);
+
+#endif
+}
diff --git a/e-util/gal-a11y-e-cell.h b/e-util/gal-a11y-e-cell.h
new file mode 100644
index 0000000..63e8ecf
--- /dev/null
+++ b/e-util/gal-a11y-e-cell.h
@@ -0,0 +1,112 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Christopher James Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef __GAL_A11Y_E_CELL_H__
+#define __GAL_A11Y_E_CELL_H__
+
+#include <e-util/e-table-item.h>
+#include <e-util/e-cell.h>
+
+#define GAL_A11Y_TYPE_E_CELL            (gal_a11y_e_cell_get_type ())
+#define GAL_A11Y_E_CELL(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GAL_A11Y_TYPE_E_CELL, GalA11yECell))
+#define GAL_A11Y_E_CELL_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GAL_A11Y_TYPE_E_CELL, GalA11yECellClass))
+#define GAL_A11Y_IS_E_CELL(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GAL_A11Y_TYPE_E_CELL))
+#define GAL_A11Y_IS_E_CELL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GAL_A11Y_TYPE_E_CELL))
+
+typedef struct _GalA11yECell GalA11yECell;
+typedef struct _GalA11yECellClass GalA11yECellClass;
+typedef struct _GalA11yECellPrivate GalA11yECellPrivate;
+typedef struct _ActionInfo ActionInfo;
+typedef void (*ACTION_FUNC) (GalA11yECell *cell);
+
+/* This struct should actually be larger as this isn't what we derive from.
+ * The GalA11yECellPrivate comes right after the parent class structure.
+ **/
+struct _GalA11yECell {
+	AtkObject object;
+
+	ETableItem *item;
+	ECellView  *cell_view;
+	AtkObject  *parent;
+	gint         model_col;
+	gint         view_col;
+	gint         row;
+	AtkStateSet *state_set;
+	GList       *action_list;
+	gint         action_idle_handler;
+	ACTION_FUNC  action_func;
+};
+
+struct _GalA11yECellClass {
+	AtkObjectClass parent_class;
+};
+
+struct _ActionInfo {
+	gchar *name;
+	gchar *description;
+	gchar *keybinding;
+	ACTION_FUNC do_action_func;
+};
+
+/* Standard Glib function */
+GType      gal_a11y_e_cell_get_type   (void);
+AtkObject *gal_a11y_e_cell_new        (ETableItem *item,
+				       ECellView  *cell_view,
+				       AtkObject  *parent,
+				       gint         model_col,
+				       gint         view_col,
+				       gint         row);
+void       gal_a11y_e_cell_construct  (AtkObject  *object,
+				       ETableItem *item,
+				       ECellView  *cell_view,
+				       AtkObject  *parent,
+				       gint         model_col,
+				       gint         view_col,
+				       gint         row);
+
+void	gal_a11y_e_cell_type_add_action_interface (GType type);
+
+gboolean gal_a11y_e_cell_add_action	(GalA11yECell	*cell,
+					 const gchar     *action_name,
+					 const gchar     *action_description,
+					 const gchar     *action_keybinding,
+					 ACTION_FUNC     action_func);
+
+gboolean gal_a11y_e_cell_remove_action	(GalA11yECell	*cell,
+                                         gint           action_id);
+
+gboolean gal_a11y_e_cell_remove_action_by_name (GalA11yECell        *cell,
+						const gchar     *action_name);
+
+gboolean gal_a11y_e_cell_add_state     (GalA11yECell *cell,
+					AtkStateType state_type,
+					gboolean     emit_signal);
+
+gboolean gal_a11y_e_cell_remove_state  (GalA11yECell *cell,
+					AtkStateType state_type,
+					gboolean     emit_signal);
+
+#endif /* __GAL_A11Y_E_CELL_H__ */
diff --git a/e-util/gal-a11y-e-table-click-to-add-factory.c b/e-util/gal-a11y-e-table-click-to-add-factory.c
new file mode 100644
index 0000000..ff923d8
--- /dev/null
+++ b/e-util/gal-a11y-e-table-click-to-add-factory.c
@@ -0,0 +1,108 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Yuedong Du <yuedong du sun com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "gal-a11y-e-table-click-to-add-factory.h"
+
+#include <atk/atk.h>
+
+#include "e-table-click-to-add.h"
+#include "e-table.h"
+#include "gal-a11y-e-table-click-to-add.h"
+#include "gal-a11y-e-table.h"
+
+#define CS_CLASS(factory) (G_TYPE_INSTANCE_GET_CLASS ((factory), C_TYPE_STREAM, GalA11yETableClickToAddFactoryClass))
+static AtkObjectFactoryClass *parent_class;
+#define PARENT_TYPE (ATK_TYPE_OBJECT_FACTORY)
+
+/* Static functions */
+
+static GType
+gal_a11y_e_table_click_to_add_factory_get_accessible_type (void)
+{
+	return GAL_A11Y_TYPE_E_TABLE_CLICK_TO_ADD;
+}
+
+static AtkObject *
+gal_a11y_e_table_click_to_add_factory_create_accessible (GObject *obj)
+{
+	AtkObject * atk_object;
+
+	g_return_val_if_fail (E_IS_TABLE_CLICK_TO_ADD (obj), NULL);
+
+	atk_object = gal_a11y_e_table_click_to_add_new (obj);
+
+	return atk_object;
+}
+
+static void
+gal_a11y_e_table_click_to_add_factory_class_init (GalA11yETableClickToAddFactoryClass *class)
+{
+	AtkObjectFactoryClass *factory_class = ATK_OBJECT_FACTORY_CLASS (class);
+
+	parent_class = g_type_class_ref (PARENT_TYPE);
+
+	factory_class->create_accessible   = gal_a11y_e_table_click_to_add_factory_create_accessible;
+	factory_class->get_accessible_type = gal_a11y_e_table_click_to_add_factory_get_accessible_type;
+}
+
+static void
+gal_a11y_e_table_click_to_add_factory_init (GalA11yETableClickToAddFactory *factory)
+{
+}
+
+/**
+ * gal_a11y_e_table_factory_get_type:
+ * @void:
+ *
+ * Registers the &GalA11yETableFactory class if necessary, and returns the type ID
+ * associated to it.
+ *
+ * Return value: The type ID of the &GalA11yETableFactory class.
+ **/
+GType
+gal_a11y_e_table_click_to_add_factory_get_type (void)
+{
+	static GType type = 0;
+
+	if (!type) {
+		GTypeInfo info = {
+			sizeof (GalA11yETableClickToAddFactoryClass),
+			(GBaseInitFunc) NULL,
+			(GBaseFinalizeFunc) NULL,
+			(GClassInitFunc) gal_a11y_e_table_click_to_add_factory_class_init,
+			(GClassFinalizeFunc) NULL,
+			NULL, /* class_data */
+			sizeof (GalA11yETableClickToAddFactory),
+			0,
+			(GInstanceInitFunc) gal_a11y_e_table_click_to_add_factory_init,
+			NULL /* value_table */
+		};
+
+		type = g_type_register_static (PARENT_TYPE, "GalA11yETableClickToAddFactory", &info, 0);
+	}
+
+	return type;
+}
diff --git a/e-util/gal-a11y-e-table-click-to-add-factory.h b/e-util/gal-a11y-e-table-click-to-add-factory.h
new file mode 100644
index 0000000..cc6d47f
--- /dev/null
+++ b/e-util/gal-a11y-e-table-click-to-add-factory.h
@@ -0,0 +1,52 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Yuedong Du <yuedong du sun com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef __GAL_A11Y_E_TABLE_CLICK_TO_ADD_FACTORY_H__
+#define __GAL_A11Y_E_TABLE_CLICK_TO_ADD_FACTORY_H__
+
+#include <atk/atkobjectfactory.h>
+
+#define GAL_A11Y_TYPE_E_TABLE_CLICK_TO_ADD_FACTORY            (gal_a11y_e_table_item_click_to_add_factory_get_type ())
+#define GAL_A11Y_E_TABLE_CLICK_TO_ADD_FACTORY(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GAL_A11Y_TYPE_E_TABLE_CLICK_TO_ADD_FACTORY, GalA11yETableClickToAddFactory))
+#define GAL_A11Y_E_TABLE_CLICK_TO_ADD_FACTORY_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GAL_A11Y_TYPE_E_TABLE_CLICK_TO_ADD_FACTORY, GalA11yETableClickToAddFactoryClass))
+#define GAL_A11Y_IS_E_TABLE_CLICK_TO_ADD_FACTORY(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GAL_A11Y_TYPE_E_TABLE_CLICK_TO_ADD_FACTORY))
+#define GAL_A11Y_IS_E_TABLE_CLICK_TO_ADD_FACTORY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GAL_A11Y_TYPE_E_TABLE_CLICK_TO_ADD_FACTORY))
+
+typedef struct _GalA11yETableClickToAddFactory GalA11yETableClickToAddFactory;
+typedef struct _GalA11yETableClickToAddFactoryClass GalA11yETableClickToAddFactoryClass;
+
+struct _GalA11yETableClickToAddFactory {
+	AtkObject object;
+};
+
+struct _GalA11yETableClickToAddFactoryClass {
+	AtkObjectClass parent_class;
+};
+
+/* Standard Glib function */
+GType              gal_a11y_e_table_click_to_add_factory_get_type (void);
+
+#endif
diff --git a/e-util/gal-a11y-e-table-click-to-add.c b/e-util/gal-a11y-e-table-click-to-add.c
new file mode 100644
index 0000000..bebe8c4
--- /dev/null
+++ b/e-util/gal-a11y-e-table-click-to-add.c
@@ -0,0 +1,358 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Yuedong Du <yuedong du sun com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "gal-a11y-e-table-click-to-add.h"
+
+#include <atk/atk.h>
+#include <glib/gi18n.h>
+
+#include "e-table-click-to-add.h"
+#include "e-table-group-leaf.h"
+#include "e-table-group.h"
+#include "gal-a11y-e-table-click-to-add-factory.h"
+#include "gal-a11y-util.h"
+
+static AtkObjectClass *parent_class;
+static GType parent_type;
+static gint priv_offset;
+#define GET_PRIVATE(object) \
+	((GalA11yETableClickToAddPrivate *) \
+	(((gchar *) object) + priv_offset))
+#define PARENT_TYPE (parent_type)
+
+struct _GalA11yETableClickToAddPrivate {
+	gpointer rect;
+	gpointer row;
+};
+
+static gint
+etcta_get_n_actions (AtkAction *action)
+{
+	return 1;
+}
+
+static const gchar *
+etcta_get_description (AtkAction *action,
+                       gint i)
+{
+	if (i == 0)
+		return _("click to add");
+
+	return NULL;
+}
+
+static const gchar *
+etcta_action_get_name (AtkAction *action,
+                       gint i)
+{
+	if (i == 0)
+		return _("click");
+
+	return NULL;
+}
+
+static gboolean
+idle_do_action (gpointer data)
+{
+	GtkLayout *layout;
+	GdkEventButton event;
+	ETableClickToAdd * etcta;
+	gint finished;
+
+	g_return_val_if_fail (data!= NULL, FALSE);
+
+	etcta = E_TABLE_CLICK_TO_ADD (
+		atk_gobject_accessible_get_object (
+		ATK_GOBJECT_ACCESSIBLE (data)));
+	g_return_val_if_fail (etcta, FALSE);
+
+	layout = GTK_LAYOUT (GNOME_CANVAS_ITEM (etcta)->canvas);
+
+	event.x = 0;
+	event.y = 0;
+	event.type = GDK_BUTTON_PRESS;
+	event.window = gtk_layout_get_bin_window (layout);
+	event.button = 1;
+	event.send_event = TRUE;
+	event.time = GDK_CURRENT_TIME;
+	event.axes = NULL;
+
+	g_signal_emit_by_name (etcta, "event", &event, &finished);
+
+	return FALSE;
+}
+
+static gboolean
+etcta_do_action (AtkAction *action,
+                 gint i)
+{
+	g_return_val_if_fail (i == 0, FALSE);
+
+	g_idle_add (idle_do_action, action);
+
+	return TRUE;
+}
+
+static void
+atk_action_interface_init (AtkActionIface *iface)
+{
+	g_return_if_fail (iface != NULL);
+
+	iface->do_action = etcta_do_action;
+	iface->get_n_actions = etcta_get_n_actions;
+	iface->get_description = etcta_get_description;
+	iface->get_name = etcta_action_get_name;
+}
+
+static const gchar *
+etcta_get_name (AtkObject *obj)
+{
+	ETableClickToAdd * etcta;
+
+	g_return_val_if_fail (GAL_A11Y_IS_E_TABLE_CLICK_TO_ADD (obj), NULL);
+
+	etcta = E_TABLE_CLICK_TO_ADD (
+		atk_gobject_accessible_get_object (
+		ATK_GOBJECT_ACCESSIBLE (obj)));
+	if (etcta && etcta->message != NULL)
+		return etcta->message;
+
+	return _("click to add");
+}
+
+static gint
+etcta_get_n_children (AtkObject *accessible)
+{
+	return 1;
+}
+
+static AtkObject *
+etcta_ref_child (AtkObject *accessible,
+                 gint i)
+{
+	AtkObject * atk_obj = NULL;
+	ETableClickToAdd * etcta;
+
+	if (i != 0)
+		return NULL;
+
+	etcta  = E_TABLE_CLICK_TO_ADD (
+		atk_gobject_accessible_get_object (
+		ATK_GOBJECT_ACCESSIBLE (accessible)));
+
+	g_return_val_if_fail (etcta, NULL);
+
+	if (etcta->rect) {
+		atk_obj = atk_gobject_accessible_for_object (
+			G_OBJECT (etcta->rect));
+	} else if (etcta->row) {
+		atk_obj = atk_gobject_accessible_for_object (
+			G_OBJECT (etcta->row));
+	}
+
+	g_object_ref (atk_obj);
+
+	return atk_obj;
+}
+
+static AtkStateSet *
+etcta_ref_state_set (AtkObject *accessible)
+{
+	AtkStateSet * state_set = NULL;
+
+	state_set = ATK_OBJECT_CLASS (parent_class)->ref_state_set (accessible);
+	if (state_set != NULL) {
+		atk_state_set_add_state (state_set, ATK_STATE_SENSITIVE);
+		atk_state_set_add_state (state_set, ATK_STATE_SHOWING);
+	}
+
+	return state_set;
+}
+
+static void
+etcta_class_init (GalA11yETableClickToAddClass *class)
+{
+	AtkObjectClass *atk_object_class = ATK_OBJECT_CLASS (class);
+
+	parent_class = g_type_class_ref (PARENT_TYPE);
+
+	atk_object_class->get_name = etcta_get_name;
+	atk_object_class->get_n_children = etcta_get_n_children;
+	atk_object_class->ref_child = etcta_ref_child;
+	atk_object_class->ref_state_set = etcta_ref_state_set;
+}
+
+static void
+etcta_init (GalA11yETableClickToAdd *a11y)
+{
+}
+
+GType
+gal_a11y_e_table_click_to_add_get_type (void)
+{
+	static GType type = 0;
+
+	if (!type) {
+		AtkObjectFactory *factory;
+
+		GTypeInfo info = {
+			sizeof (GalA11yETableClickToAddClass),
+			(GBaseInitFunc) NULL,
+			(GBaseFinalizeFunc) NULL,
+			(GClassInitFunc) etcta_class_init,
+			(GClassFinalizeFunc) NULL,
+			NULL, /* class_data */
+			sizeof (GalA11yETableClickToAdd),
+			0,
+			(GInstanceInitFunc) etcta_init,
+			NULL /* value_table */
+		};
+
+		static const GInterfaceInfo atk_action_info = {
+			(GInterfaceInitFunc) atk_action_interface_init,
+			(GInterfaceFinalizeFunc) NULL,
+			NULL
+		};
+
+		factory = atk_registry_get_factory (
+			atk_get_default_registry (),
+			GNOME_TYPE_CANVAS_ITEM);
+
+		parent_type = atk_object_factory_get_accessible_type (factory);
+		type = gal_a11y_type_register_static_with_private (
+			PARENT_TYPE, "GalA11yETableClickToAdd", &info, 0,
+			sizeof (GalA11yETableClickToAddPrivate), &priv_offset);
+
+		g_type_add_interface_static (type, ATK_TYPE_ACTION, &atk_action_info);
+
+	}
+
+	return type;
+}
+
+static gboolean
+etcta_event (GnomeCanvasItem *item,
+             GdkEvent *e,
+             gpointer data)
+{
+	ETableClickToAdd *etcta = E_TABLE_CLICK_TO_ADD (item);
+	GalA11yETableClickToAdd *a11y;
+	GalA11yETableClickToAddPrivate *priv;
+
+	g_return_val_if_fail (item, TRUE);
+
+	g_return_val_if_fail (GAL_A11Y_IS_E_TABLE_CLICK_TO_ADD (data), FALSE);
+	a11y = GAL_A11Y_E_TABLE_CLICK_TO_ADD (data);
+
+	priv = GET_PRIVATE (a11y);
+
+	/* rect replaced by row. */
+	if (etcta->rect == NULL && priv->rect != NULL) {
+		g_signal_emit_by_name (a11y, "children_changed::remove", 0, NULL, NULL);
+
+	}
+	/* row inserted, and/or replaced by a new row. */
+	if (etcta->row != NULL && priv->row == NULL) {
+		g_signal_emit_by_name (a11y, "children_changed::add", 0, NULL, NULL);
+	} else if (etcta->row != NULL && priv->row != NULL && etcta->row != priv->row) {
+		g_signal_emit_by_name (a11y, "children_changed::remove", 0, NULL, NULL);
+		g_signal_emit_by_name (a11y, "children_changed::add", 0, NULL, NULL);
+	}
+
+	priv->rect = etcta->rect;
+	priv->row = etcta->row;
+
+	return FALSE;
+}
+
+static void
+etcta_selection_cursor_changed (ESelectionModel *esm,
+                                gint row,
+                                gint col,
+                                GalA11yETableClickToAdd *a11y)
+{
+	ETableClickToAdd *etcta;
+	AtkObject *row_a11y;
+
+	etcta = E_TABLE_CLICK_TO_ADD (
+		atk_gobject_accessible_get_object (
+		ATK_GOBJECT_ACCESSIBLE (a11y)));
+
+	if (etcta == NULL || etcta->row == NULL)
+		return;
+
+	row_a11y = atk_gobject_accessible_for_object (G_OBJECT (etcta->row));
+	if (row_a11y) {
+		AtkObject *cell_a11y;
+
+		cell_a11y = g_object_get_data (
+			G_OBJECT (row_a11y), "gail-focus-object");
+		if (cell_a11y) {
+			atk_focus_tracker_notify (cell_a11y);
+		}
+	}
+}
+
+AtkObject *
+gal_a11y_e_table_click_to_add_new (GObject *widget)
+{
+	GalA11yETableClickToAdd *a11y;
+	ETableClickToAdd * etcta;
+	GalA11yETableClickToAddPrivate *priv;
+
+	g_return_val_if_fail (widget != NULL, NULL);
+
+	a11y = g_object_new (gal_a11y_e_table_click_to_add_get_type (), NULL);
+	priv = GET_PRIVATE (a11y);
+
+	etcta = E_TABLE_CLICK_TO_ADD (widget);
+
+	atk_object_initialize (ATK_OBJECT (a11y), etcta);
+
+	priv->rect = etcta->rect;
+	priv->row = etcta->row;
+
+	g_signal_connect_after (
+		widget, "event",
+		G_CALLBACK (etcta_event), a11y);
+
+	g_signal_connect (
+		etcta->selection, "cursor_changed",
+		G_CALLBACK (etcta_selection_cursor_changed), a11y);
+
+	return ATK_OBJECT (a11y);
+}
+
+void
+gal_a11y_e_table_click_to_add_init (void)
+{
+	if (atk_get_root ())
+		atk_registry_set_factory_type (
+			atk_get_default_registry (),
+			E_TYPE_TABLE_CLICK_TO_ADD,
+			gal_a11y_e_table_click_to_add_factory_get_type ());
+}
+
diff --git a/e-util/gal-a11y-e-table-click-to-add.h b/e-util/gal-a11y-e-table-click-to-add.h
new file mode 100644
index 0000000..46f3939
--- /dev/null
+++ b/e-util/gal-a11y-e-table-click-to-add.h
@@ -0,0 +1,58 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef __GAL_A11Y_E_TABLE_CLICK_TO_ADD_H__
+#define __GAL_A11Y_E_TABLE_CLICK_TO_ADD_H__
+
+#include <atk/atkgobjectaccessible.h>
+#include <e-util/e-table-item.h>
+
+#define GAL_A11Y_TYPE_E_TABLE_CLICK_TO_ADD            (gal_a11y_e_table_click_to_add_get_type ())
+#define GAL_A11Y_E_TABLE_CLICK_TO_ADD(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GAL_A11Y_TYPE_E_TABLE_CLICK_TO_ADD, GalA11yETableClickToAdd))
+#define GAL_A11Y_E_TABLE_CLICK_TO_ADD_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GAL_A11Y_TYPE_E_TABLE_CLICK_TO_ADD, GalA11yETableClickToAddClass))
+#define GAL_A11Y_IS_E_TABLE_CLICK_TO_ADD(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GAL_A11Y_TYPE_E_TABLE_CLICK_TO_ADD))
+#define GAL_A11Y_IS_E_TABLE_CLICK_TO_ADD_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GAL_A11Y_TYPE_E_TABLE_CLICK_TO_ADD))
+
+typedef struct _GalA11yETableClickToAdd GalA11yETableClickToAdd;
+typedef struct _GalA11yETableClickToAddClass GalA11yETableClickToAddClass;
+typedef struct _GalA11yETableClickToAddPrivate GalA11yETableClickToAddPrivate;
+
+/* This struct should actually be larger as this isn't what we derive from.
+ * The GalA11yETableClickToAddPrivate comes right after the parent class structure.
+ **/
+struct _GalA11yETableClickToAdd {
+	AtkGObjectAccessible parent;
+};
+
+struct _GalA11yETableClickToAddClass {
+	AtkGObjectAccessibleClass parent_class;
+};
+
+/* Standard Glib function */
+GType      gal_a11y_e_table_click_to_add_get_type  (void);
+AtkObject *gal_a11y_e_table_click_to_add_new       (GObject *widget);
+
+void       gal_a11y_e_table_click_to_add_init      (void);
+#endif /* __GAL_A11Y_E_TABLE_CLICK_TO_ADD_H__ */
diff --git a/e-util/gal-a11y-e-table-column-header.c b/e-util/gal-a11y-e-table-column-header.c
new file mode 100644
index 0000000..46fb374
--- /dev/null
+++ b/e-util/gal-a11y-e-table-column-header.c
@@ -0,0 +1,243 @@
+/*
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Li Yuan <li yuan sun com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "gal-a11y-e-table-column-header.h"
+
+#include <glib/gi18n.h>
+#include <atk/atkobject.h>
+#include <atk/atkregistry.h>
+
+#include "e-table-header-item.h"
+#include "gal-a11y-util.h"
+
+static GObjectClass *parent_class;
+static gint priv_offset;
+
+#define GET_PRIVATE(object) \
+	((GalA11yETableColumnHeaderPrivate *) \
+	(((gchar *) object) + priv_offset))
+#define PARENT_TYPE (atk_gobject_accessible_get_type ())
+
+struct _GalA11yETableColumnHeaderPrivate {
+	ETableItem *item;
+	AtkObject  *parent;
+	AtkStateSet *state_set;
+};
+
+static void
+etch_init (GalA11yETableColumnHeader *a11y)
+{
+	GET_PRIVATE (a11y)->item = NULL;
+	GET_PRIVATE (a11y)->parent = NULL;
+	GET_PRIVATE (a11y)->state_set = NULL;
+}
+
+static AtkStateSet *
+gal_a11y_e_table_column_header_ref_state_set (AtkObject *accessible)
+{
+	GalA11yETableColumnHeaderPrivate *priv = GET_PRIVATE (accessible);
+
+	g_return_val_if_fail (priv->state_set, NULL);
+
+	g_object_ref (priv->state_set);
+
+	return priv->state_set;
+}
+
+static void
+gal_a11y_e_table_column_header_real_initialize (AtkObject *obj,
+                                                gpointer data)
+{
+	ATK_OBJECT_CLASS (parent_class)->initialize (obj, data);
+}
+
+static void
+gal_a11y_e_table_column_header_dispose (GObject *object)
+{
+	GalA11yETableColumnHeader *a11y = GAL_A11Y_E_TABLE_COLUMN_HEADER (object);
+	GalA11yETableColumnHeaderPrivate *priv = GET_PRIVATE (a11y);
+
+	if (priv->state_set) {
+		g_object_unref (priv->state_set);
+		priv->state_set = NULL;
+	}
+
+	if (parent_class->dispose)
+		parent_class->dispose (object);
+
+}
+
+static void
+etch_class_init (GalA11yETableColumnHeaderClass *class)
+{
+	AtkObjectClass *atk_object_class = ATK_OBJECT_CLASS (class);
+	GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+	parent_class = g_type_class_ref (PARENT_TYPE);
+
+	object_class->dispose = gal_a11y_e_table_column_header_dispose;
+
+	atk_object_class->ref_state_set = gal_a11y_e_table_column_header_ref_state_set;
+	atk_object_class->initialize = gal_a11y_e_table_column_header_real_initialize;
+}
+
+inline static GObject *
+etch_a11y_get_gobject (AtkGObjectAccessible *accessible)
+{
+	return atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (accessible));
+}
+
+static gboolean
+gal_a11y_e_table_column_header_do_action (AtkAction *action,
+                                          gint i)
+{
+	gboolean return_value = TRUE;
+	GtkWidget *widget;
+	GalA11yETableColumnHeader *a11y;
+	ETableHeaderItem *ethi;
+	ETableItem *item;
+	ETableCol *col;
+
+	switch (i) {
+		case 0:
+			a11y = GAL_A11Y_E_TABLE_COLUMN_HEADER (action);
+			col = E_TABLE_COL (etch_a11y_get_gobject (
+				ATK_GOBJECT_ACCESSIBLE (a11y)));
+			item = GET_PRIVATE (a11y)->item;
+			widget = gtk_widget_get_parent (GTK_WIDGET (item->parent.canvas));
+			if (E_IS_TREE (widget)) {
+				ethi = E_TABLE_HEADER_ITEM (
+					e_tree_get_header_item (E_TREE (widget)));
+			}
+			else if (E_IS_TABLE (widget))
+				ethi = E_TABLE_HEADER_ITEM (
+					E_TABLE (widget)->header_item);
+			else
+				break;
+			ethi_change_sort_state (ethi, col);
+		default:
+			return_value = FALSE;
+			break;
+	}
+	return return_value;
+}
+
+static gint
+gal_a11y_e_table_column_header_get_n_actions (AtkAction *action)
+{
+	return 1;
+}
+
+static const gchar *
+gal_a11y_e_table_column_header_action_get_name (AtkAction *action,
+                                                gint i)
+{
+	const gchar *return_value;
+
+	switch (i) {
+		case 0:
+			return_value = _("sort");
+			break;
+		default:
+			return_value = NULL;
+			break;
+	}
+	return return_value;
+}
+
+static void
+atk_action_interface_init (AtkActionIface *iface)
+{
+	g_return_if_fail (iface != NULL);
+
+	iface->do_action = gal_a11y_e_table_column_header_do_action;
+	iface->get_n_actions = gal_a11y_e_table_column_header_get_n_actions;
+	iface->get_name = gal_a11y_e_table_column_header_action_get_name;
+}
+
+GType
+gal_a11y_e_table_column_header_get_type (void)
+{
+	static GType type = 0;
+
+	if (!type) {
+		GTypeInfo info = {
+			sizeof (GalA11yETableColumnHeaderClass),
+			(GBaseInitFunc) NULL,
+			(GBaseFinalizeFunc) NULL,
+			(GClassInitFunc) etch_class_init,
+			(GClassFinalizeFunc) NULL,
+			NULL,
+			sizeof (GalA11yETableColumnHeader),
+			0,
+			(GInstanceInitFunc) etch_init,
+			NULL
+		};
+		static const GInterfaceInfo atk_action_info = {
+			(GInterfaceInitFunc) atk_action_interface_init,
+			(GInterfaceFinalizeFunc) NULL,
+			NULL
+		};
+
+		type = gal_a11y_type_register_static_with_private (
+			PARENT_TYPE, "GalA11yETableColumnHeader", &info, 0,
+			sizeof (GalA11yETableColumnHeaderPrivate), &priv_offset);
+
+		g_type_add_interface_static (
+			type, ATK_TYPE_ACTION, &atk_action_info);
+	}
+
+	return type;
+}
+
+AtkObject *
+gal_a11y_e_table_column_header_new (ETableCol *ecol,
+                                    ETableItem *item)
+{
+	GalA11yETableColumnHeader *a11y;
+	AtkObject *accessible;
+
+	g_return_val_if_fail (E_IS_TABLE_COL (ecol), NULL);
+
+	a11y = g_object_new (gal_a11y_e_table_column_header_get_type (), NULL);
+	accessible  = ATK_OBJECT (a11y);
+	atk_object_initialize (accessible, ecol);
+
+	GET_PRIVATE (a11y)->item = item;
+	GET_PRIVATE (a11y)->state_set = atk_state_set_new ();
+
+	atk_state_set_add_state (GET_PRIVATE (a11y)->state_set, ATK_STATE_VISIBLE);
+	atk_state_set_add_state (GET_PRIVATE (a11y)->state_set, ATK_STATE_SHOWING);
+	atk_state_set_add_state (GET_PRIVATE (a11y)->state_set, ATK_STATE_SENSITIVE);
+	atk_state_set_add_state (GET_PRIVATE (a11y)->state_set, ATK_STATE_ENABLED);
+
+	if (ecol->text)
+		atk_object_set_name (accessible, ecol->text);
+	atk_object_set_role (accessible, ATK_ROLE_TABLE_COLUMN_HEADER);
+
+	return ATK_OBJECT (a11y);
+}
diff --git a/e-util/gal-a11y-e-table-column-header.h b/e-util/gal-a11y-e-table-column-header.h
new file mode 100644
index 0000000..9d77467
--- /dev/null
+++ b/e-util/gal-a11y-e-table-column-header.h
@@ -0,0 +1,59 @@
+/*
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Li Yuan <li yuan sun com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef __GAL_A11Y_E_TABLE_COLUMN_HEADER_H__
+#define __GAL_A11Y_E_TABLE_COLUMN_HEADER_H__
+
+#include <atk/atkgobjectaccessible.h>
+
+#include <e-util/e-table-col.h>
+#include <e-util/e-table-item.h>
+
+#define GAL_A11Y_TYPE_E_TABLE_COLUMN_HEADER            (gal_a11y_e_table_column_header_get_type ())
+#define GAL_A11Y_E_TABLE_COLUMN_HEADER(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GAL_A11Y_TYPE_E_TABLE_COLUMN_HEADER, GalA11yETableColumnHeader))
+#define GAL_A11Y_E_TABLE_COLUMN_HEADER_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GAL_A11Y_TYPE_E_TABLE_COLUMN_HEADER, GalA11yETableColumnHeaderClass))
+#define GAL_A11Y_IS_E_TABLE_COLUMN_HEADER(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GAL_A11Y_TYPE_E_TABLE_COLUMN_HEADER))
+#define GAL_A11Y_IS_E_TABLE_COLUMN_HEADER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GAL_A11Y_TYPE_E_TABLE_COLUMN_HEADER))
+
+typedef struct _GalA11yETableColumnHeader GalA11yETableColumnHeader;
+typedef struct _GalA11yETableColumnHeaderClass GalA11yETableColumnHeaderClass;
+typedef struct _GalA11yETableColumnHeaderPrivate GalA11yETableColumnHeaderPrivate;
+
+struct _GalA11yETableColumnHeader {
+	AtkGObjectAccessible parent;
+};
+
+struct _GalA11yETableColumnHeaderClass {
+	AtkGObjectAccessibleClass parent_class;
+};
+
+/* Standard Glib function */
+GType      gal_a11y_e_table_column_header_get_type  (void);
+AtkObject *gal_a11y_e_table_column_header_new       (ETableCol *etc, ETableItem *item);
+void gal_a11y_e_table_column_header_init (void);
+
+#endif /* __GAL_A11Y_E_TABLE_COLUMN_HEADER_H__ */
diff --git a/widgets/table/gal-a11y-e-table-factory.c b/e-util/gal-a11y-e-table-factory.c
similarity index 100%
rename from widgets/table/gal-a11y-e-table-factory.c
rename to e-util/gal-a11y-e-table-factory.c
diff --git a/e-util/gal-a11y-e-table-factory.h b/e-util/gal-a11y-e-table-factory.h
new file mode 100644
index 0000000..3a8b18f
--- /dev/null
+++ b/e-util/gal-a11y-e-table-factory.h
@@ -0,0 +1,53 @@
+/*
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Christopher James Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef __GAL_A11Y_E_TABLE_FACTORY_H__
+#define __GAL_A11Y_E_TABLE_FACTORY_H__
+
+#include <atk/atkobjectfactory.h>
+
+#define GAL_A11Y_TYPE_E_TABLE_FACTORY            (gal_a11y_e_table_factory_get_type ())
+#define GAL_A11Y_E_TABLE_FACTORY(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GAL_A11Y_TYPE_E_TABLE_FACTORY, GalA11yETableFactory))
+#define GAL_A11Y_E_TABLE_FACTORY_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GAL_A11Y_TYPE_E_TABLE_FACTORY, GalA11yETableFactoryClass))
+#define GAL_A11Y_IS_E_TABLE_FACTORY(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GAL_A11Y_TYPE_E_TABLE_FACTORY))
+#define GAL_A11Y_IS_E_TABLE_FACTORY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GAL_A11Y_TYPE_E_TABLE_FACTORY))
+
+typedef struct _GalA11yETableFactory GalA11yETableFactory;
+typedef struct _GalA11yETableFactoryClass GalA11yETableFactoryClass;
+
+struct _GalA11yETableFactory {
+	AtkObject object;
+};
+
+struct _GalA11yETableFactoryClass {
+	AtkObjectClass parent_class;
+};
+
+/* Standard Glib function */
+GType              gal_a11y_e_table_factory_get_type         (void);
+
+#endif /* __GAL_A11Y_E_TABLE_FACTORY_H__ */
diff --git a/e-util/gal-a11y-e-table-item-factory.c b/e-util/gal-a11y-e-table-item-factory.c
new file mode 100644
index 0000000..3ef551d
--- /dev/null
+++ b/e-util/gal-a11y-e-table-item-factory.c
@@ -0,0 +1,107 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Yuedong Du <yuedong du sun com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "gal-a11y-e-table-item-factory.h"
+
+#include <atk/atk.h>
+
+#include "e-table.h"
+#include "e-tree.h"
+#include "gal-a11y-e-table-item.h"
+#include "gal-a11y-e-table.h"
+
+#define CS_CLASS(factory) (G_TYPE_INSTANCE_GET_CLASS ((factory), C_TYPE_STREAM, GalA11yETableItemFactoryClass))
+static AtkObjectFactoryClass *parent_class;
+#define PARENT_TYPE (ATK_TYPE_OBJECT_FACTORY)
+
+/* Static functions */
+
+static GType
+gal_a11y_e_table_item_factory_get_accessible_type (void)
+{
+	return GAL_A11Y_TYPE_E_TABLE_ITEM;
+}
+
+static AtkObject *
+gal_a11y_e_table_item_factory_create_accessible (GObject *obj)
+{
+	AtkObject *accessible;
+
+	g_return_val_if_fail (E_IS_TABLE_ITEM (obj), NULL);
+	accessible = gal_a11y_e_table_item_new (E_TABLE_ITEM (obj));
+
+	return accessible;
+}
+
+static void
+gal_a11y_e_table_item_factory_class_init (GalA11yETableItemFactoryClass *class)
+{
+	AtkObjectFactoryClass *factory_class = ATK_OBJECT_FACTORY_CLASS (class);
+
+	parent_class = g_type_class_ref (PARENT_TYPE);
+
+	factory_class->create_accessible   = gal_a11y_e_table_item_factory_create_accessible;
+	factory_class->get_accessible_type = gal_a11y_e_table_item_factory_get_accessible_type;
+}
+
+static void
+gal_a11y_e_table_item_factory_init (GalA11yETableItemFactory *factory)
+{
+}
+
+/**
+ * gal_a11y_e_table_factory_get_type:
+ * @void:
+ *
+ * Registers the &GalA11yETableFactory class if necessary, and returns the type ID
+ * associated to it.
+ *
+ * Return value: The type ID of the &GalA11yETableFactory class.
+ **/
+GType
+gal_a11y_e_table_item_factory_get_type (void)
+{
+	static GType type = 0;
+
+	if (!type) {
+		GTypeInfo info = {
+			sizeof (GalA11yETableItemFactoryClass),
+			(GBaseInitFunc) NULL,
+			(GBaseFinalizeFunc) NULL,
+			(GClassInitFunc) gal_a11y_e_table_item_factory_class_init,
+			(GClassFinalizeFunc) NULL,
+			NULL, /* class_data */
+			sizeof (GalA11yETableItemFactory),
+			0,
+			(GInstanceInitFunc) gal_a11y_e_table_item_factory_init,
+			NULL /* value_table */
+		};
+
+		type = g_type_register_static (PARENT_TYPE, "GalA11yETableItemFactory", &info, 0);
+	}
+
+	return type;
+}
diff --git a/e-util/gal-a11y-e-table-item-factory.h b/e-util/gal-a11y-e-table-item-factory.h
new file mode 100644
index 0000000..4aef02d
--- /dev/null
+++ b/e-util/gal-a11y-e-table-item-factory.h
@@ -0,0 +1,52 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Yuedong Du <yuedong du sun com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef __GAL_A11Y_E_TABLE_ITEM_FACTORY_H__
+#define __GAL_A11Y_E_TABLE_ITEM_FACTORY_H__
+
+#include <atk/atkobjectfactory.h>
+
+#define GAL_A11Y_TYPE_E_TABLE_ITEM_FACTORY            (gal_a11y_e_table_item_factory_get_type ())
+#define GAL_A11Y_E_TABLE_ITEM_FACTORY(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GAL_A11Y_TYPE_E_TABLE_ITEM_FACTORY, GalA11yETableItemFactory))
+#define GAL_A11Y_E_TABLE_ITEM_FACTORY_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GAL_A11Y_TYPE_E_TABLE_ITEM_FACTORY, GalA11yETableItemFactoryClass))
+#define GAL_A11Y_IS_E_TABLE_ITEM_FACTORY(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GAL_A11Y_TYPE_E_TABLE_ITEM_FACTORY))
+#define GAL_A11Y_IS_E_TABLE_ITEM_FACTORY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GAL_A11Y_TYPE_E_TABLE_ITEM_FACTORY))
+
+typedef struct _GalA11yETableItemFactory GalA11yETableItemFactory;
+typedef struct _GalA11yETableItemFactoryClass GalA11yETableItemFactoryClass;
+
+struct _GalA11yETableItemFactory {
+	AtkObject object;
+};
+
+struct _GalA11yETableItemFactoryClass {
+	AtkObjectClass parent_class;
+};
+
+/* Standard Glib function */
+GType              gal_a11y_e_table_item_factory_get_type         (void);
+
+#endif /* __GAL_A11Y_E_TABLE_FACTORY_H__ */
diff --git a/e-util/gal-a11y-e-table-item.c b/e-util/gal-a11y-e-table-item.c
new file mode 100644
index 0000000..9f5c407
--- /dev/null
+++ b/e-util/gal-a11y-e-table-item.c
@@ -0,0 +1,1437 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Christopher James Lahey <clahey ximian com>
+ *		Bolian Yin <bolian yin sun com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "gal-a11y-e-table-item.h"
+
+#include <string.h>
+
+#include <atk/atk.h>
+
+#include "e-canvas.h"
+#include "e-selection-model.h"
+#include "e-table-click-to-add.h"
+#include "e-table-subset.h"
+#include "e-table.h"
+#include "e-tree.h"
+#include "gal-a11y-e-cell-registry.h"
+#include "gal-a11y-e-cell.h"
+#include "gal-a11y-e-table-click-to-add.h"
+#include "gal-a11y-e-table-column-header.h"
+#include "gal-a11y-e-table-item-factory.h"
+#include "gal-a11y-util.h"
+
+static GObjectClass *parent_class;
+static AtkComponentIface *component_parent_iface;
+static GType parent_type;
+static gint priv_offset;
+static GQuark		quark_accessible_object = 0;
+#define GET_PRIVATE(object) \
+	((GalA11yETableItemPrivate *) (((gchar *) object) + priv_offset))
+#define PARENT_TYPE (parent_type)
+
+struct _GalA11yETableItemPrivate {
+	ETableItem *item;
+	gint cols;
+	gint rows;
+	gint selection_change_id;
+	gint cursor_change_id;
+	ETableCol ** columns;
+	ESelectionModel *selection;
+	AtkStateSet *state_set;
+	GtkWidget *widget;
+};
+
+static gboolean gal_a11y_e_table_item_ref_selection (GalA11yETableItem *a11y,
+						     ESelectionModel *selection);
+static gboolean gal_a11y_e_table_item_unref_selection (GalA11yETableItem *a11y);
+
+static AtkObject * eti_ref_at (AtkTable *table, gint row, gint column);
+
+static void
+free_columns (ETableCol **columns)
+{
+	gint ii;
+
+	if (!columns)
+		return;
+
+	for (ii = 0; columns[ii]; ii++) {
+		g_object_unref (columns[ii]);
+	}
+
+	g_free (columns);
+}
+
+static void
+item_finalized (gpointer user_data,
+                GObject *gone_item)
+{
+	GalA11yETableItem *a11y;
+	GalA11yETableItemPrivate *priv;
+
+	a11y = GAL_A11Y_E_TABLE_ITEM (user_data);
+	priv = GET_PRIVATE (a11y);
+
+	priv->item = NULL;
+
+	atk_state_set_add_state (priv->state_set, ATK_STATE_DEFUNCT);
+	atk_object_notify_state_change (ATK_OBJECT (a11y), ATK_STATE_DEFUNCT, TRUE);
+
+	if (priv->selection)
+		gal_a11y_e_table_item_unref_selection (a11y);
+
+	g_object_unref (a11y);
+}
+
+static AtkStateSet *
+eti_ref_state_set (AtkObject *accessible)
+{
+	GalA11yETableItemPrivate *priv = GET_PRIVATE (accessible);
+
+	g_object_ref (priv->state_set);
+
+	return priv->state_set;
+}
+
+inline static gint
+view_to_model_row (ETableItem *eti,
+                   gint row)
+{
+	if (eti->uses_source_model) {
+		ETableSubset *etss = E_TABLE_SUBSET (eti->table_model);
+		if (row >= 0 && row < etss->n_map) {
+			eti->row_guess = row;
+			return etss->map_table[row];
+		} else
+			return -1;
+	} else
+		return row;
+}
+
+inline static gint
+view_to_model_col (ETableItem *eti,
+                   gint col)
+{
+	ETableCol *ecol = e_table_header_get_column (eti->header, col);
+	return ecol ? ecol->col_idx : -1;
+}
+
+inline static gint
+model_to_view_row (ETableItem *eti,
+                   gint row)
+{
+	gint i;
+	if (row == -1)
+		return -1;
+	if (eti->uses_source_model) {
+		ETableSubset *etss = E_TABLE_SUBSET (eti->table_model);
+		if (eti->row_guess >= 0 && eti->row_guess < etss->n_map) {
+			if (etss->map_table[eti->row_guess] == row) {
+				return eti->row_guess;
+			}
+		}
+		for (i = 0; i < etss->n_map; i++) {
+			if (etss->map_table[i] == row)
+				return i;
+		}
+		return -1;
+	} else
+		return row;
+}
+
+inline static gint
+model_to_view_col (ETableItem *eti,
+                   gint col)
+{
+	gint i;
+	if (col == -1)
+		return -1;
+	for (i = 0; i < eti->cols; i++) {
+		ETableCol *ecol = e_table_header_get_column (eti->header, i);
+		if (ecol->col_idx == col)
+			return i;
+	}
+	return -1;
+}
+
+inline static GObject *
+eti_a11y_get_gobject (AtkObject *accessible)
+{
+	return atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (accessible));
+}
+
+static void
+eti_a11y_reset_focus_object (GalA11yETableItem *a11y,
+                             ETableItem *item,
+                             gboolean notify)
+{
+	ESelectionModel * esm;
+	gint cursor_row, cursor_col, view_row, view_col;
+	AtkObject *cell, *old_cell;
+
+	esm = item->selection;
+	g_return_if_fail (esm);
+
+	cursor_row = e_selection_model_cursor_row (esm);
+	cursor_col = e_selection_model_cursor_col (esm);
+
+	view_row = model_to_view_row (item, cursor_row);
+	view_col = model_to_view_col (item, cursor_col);
+
+	if (view_row == -1)
+		view_row = 0;
+	if (view_col == -1)
+		view_col = 0;
+
+	old_cell = (AtkObject *) g_object_get_data (G_OBJECT (a11y), "gail-focus-object");
+	if (old_cell && GAL_A11Y_IS_E_CELL (old_cell))
+		gal_a11y_e_cell_remove_state (
+			GAL_A11Y_E_CELL (old_cell), ATK_STATE_FOCUSED, FALSE);
+	if (old_cell)
+		g_object_unref (old_cell);
+
+	cell = eti_ref_at (ATK_TABLE (a11y), view_row, view_col);
+
+	if (cell != NULL) {
+		g_object_set_data (G_OBJECT (a11y), "gail-focus-object", cell);
+		gal_a11y_e_cell_add_state (
+			GAL_A11Y_E_CELL (cell), ATK_STATE_FOCUSED, FALSE);
+	} else
+		g_object_set_data (G_OBJECT (a11y), "gail-focus-object", NULL);
+
+	if (notify && cell)
+		atk_focus_tracker_notify (cell);
+}
+
+static void
+eti_dispose (GObject *object)
+{
+	GalA11yETableItem *a11y = GAL_A11Y_E_TABLE_ITEM (object);
+	GalA11yETableItemPrivate *priv = GET_PRIVATE (a11y);
+
+	if (priv->columns) {
+		free_columns (priv->columns);
+		priv->columns = NULL;
+	}
+
+	if (priv->item) {
+		g_object_weak_unref (G_OBJECT (priv->item), item_finalized, a11y);
+		priv->item = NULL;
+	}
+
+	if (parent_class->dispose)
+		parent_class->dispose (object);
+}
+
+/* Static functions */
+static gint
+eti_get_n_children (AtkObject *accessible)
+{
+	g_return_val_if_fail (GAL_A11Y_IS_E_TABLE_ITEM (accessible), 0);
+	if (!eti_a11y_get_gobject (accessible))
+		return 0;
+
+	return atk_table_get_n_columns (ATK_TABLE (accessible)) *
+		(atk_table_get_n_rows (ATK_TABLE (accessible)) + 1);
+}
+
+static AtkObject *
+eti_ref_child (AtkObject *accessible,
+               gint index)
+{
+	ETableItem *item;
+	gint col, row;
+
+	g_return_val_if_fail (GAL_A11Y_IS_E_TABLE_ITEM (accessible), NULL);
+	item = E_TABLE_ITEM (eti_a11y_get_gobject (accessible));
+	if (!item)
+		return NULL;
+
+	if (index < item->cols) {
+		ETableCol *ecol;
+		AtkObject *child;
+
+		ecol = e_table_header_get_column (item->header, index);
+		child = gal_a11y_e_table_column_header_new (ecol, item);
+		return child;
+	}
+	index -= item->cols;
+
+	col = index % item->cols;
+	row = index / item->cols;
+
+	return eti_ref_at (ATK_TABLE (accessible), row, col);
+}
+
+static void
+eti_get_extents (AtkComponent *component,
+                 gint *x,
+                 gint *y,
+                 gint *width,
+                 gint *height,
+                 AtkCoordType coord_type)
+{
+	ETableItem *item;
+	AtkObject *parent;
+
+	item = E_TABLE_ITEM (eti_a11y_get_gobject (ATK_OBJECT (component)));
+	if (!item)
+		return;
+
+	parent = ATK_OBJECT (component)->accessible_parent;
+	if (parent && ATK_IS_COMPONENT (parent))
+		atk_component_get_extents (
+			ATK_COMPONENT (parent),
+			x, y,
+			width, height,
+			coord_type);
+
+	if (parent && GAL_A11Y_IS_E_TABLE_CLICK_TO_ADD (parent)) {
+		ETableClickToAdd *etcta = E_TABLE_CLICK_TO_ADD (
+			atk_gobject_accessible_get_object (
+			ATK_GOBJECT_ACCESSIBLE (parent)));
+		if (etcta) {
+			*width = etcta->width;
+			*height = etcta->height;
+		}
+	}
+}
+
+static AtkObject *
+eti_ref_accessible_at_point (AtkComponent *component,
+                             gint x,
+                             gint y,
+                             AtkCoordType coord_type)
+{
+	gint row = -1;
+	gint col = -1;
+	gint x_origin, y_origin;
+	ETableItem *item;
+	GtkWidget *tableOrTree;
+
+	item = E_TABLE_ITEM (eti_a11y_get_gobject (ATK_OBJECT (component)));
+	if (!item)
+		return NULL;
+
+	atk_component_get_position (
+		component,
+		&x_origin,
+		&y_origin,
+		coord_type);
+	x -= x_origin;
+	y -= y_origin;
+
+	tableOrTree = gtk_widget_get_parent (GTK_WIDGET (item->parent.canvas));
+
+	if (E_IS_TREE (tableOrTree))
+		e_tree_get_cell_at (E_TREE (tableOrTree), x, y, &row, &col);
+	else
+		e_table_get_cell_at (E_TABLE (tableOrTree), x, y, &row, &col);
+
+	if (row != -1 && col != -1) {
+		return eti_ref_at (ATK_TABLE (component), row, col);
+	} else {
+		return NULL;
+	}
+}
+
+static void
+cell_destroyed (gpointer data)
+{
+	GalA11yECell * cell;
+
+	g_return_if_fail (GAL_A11Y_IS_E_CELL (data));
+	cell = GAL_A11Y_E_CELL (data);
+
+	g_return_if_fail (cell->item && G_IS_OBJECT (cell->item));
+
+	if (cell->item) {
+		g_object_unref (cell->item);
+		cell->item = NULL;
+	}
+
+}
+
+/* atk table */
+static AtkObject *
+eti_ref_at (AtkTable *table,
+            gint row,
+            gint column)
+{
+	ETableItem *item;
+	AtkObject * ret;
+	GalA11yETableItemPrivate *priv = GET_PRIVATE (table);
+
+	if (atk_state_set_contains_state (priv->state_set, ATK_STATE_DEFUNCT))
+		return NULL;
+
+	item = E_TABLE_ITEM (eti_a11y_get_gobject (ATK_OBJECT (table)));
+	if (!item)
+		return NULL;
+
+	if (column >= 0 &&
+	    column < item->cols &&
+	    row >= 0 &&
+	    row < item->rows &&
+	    item->cell_views_realized) {
+		ECellView *cell_view = item->cell_views[column];
+		ETableCol *ecol = e_table_header_get_column (item->header, column);
+		ret = gal_a11y_e_cell_registry_get_object (
+			NULL,
+			item,
+			cell_view,
+			ATK_OBJECT (table),
+			ecol->col_idx,
+			column,
+			row);
+		if (ATK_IS_OBJECT (ret)) {
+			g_object_weak_ref (
+				G_OBJECT (ret),
+				(GWeakNotify) cell_destroyed,
+				ret);
+			/* if current cell is focused, add FOCUSED state */
+			if (e_selection_model_cursor_row (item->selection) ==
+				GAL_A11Y_E_CELL (ret)->row &&
+				e_selection_model_cursor_col (item->selection) ==
+				GAL_A11Y_E_CELL (ret)->model_col)
+				gal_a11y_e_cell_add_state (
+					GAL_A11Y_E_CELL (ret),
+					ATK_STATE_FOCUSED, FALSE);
+		} else
+			ret = NULL;
+
+		return ret;
+	}
+
+	return NULL;
+}
+
+static gint
+eti_get_index_at (AtkTable *table,
+                  gint row,
+                  gint column)
+{
+	ETableItem *item;
+
+	item = E_TABLE_ITEM (eti_a11y_get_gobject (ATK_OBJECT (table)));
+	if (!item)
+		return -1;
+
+	return column + (row + 1) * item->cols;
+}
+
+static gint
+eti_get_column_at_index (AtkTable *table,
+                         gint index)
+{
+	ETableItem *item;
+
+	item = E_TABLE_ITEM (eti_a11y_get_gobject (ATK_OBJECT (table)));
+	if (!item)
+		return -1;
+
+	return index % item->cols;
+}
+
+static gint
+eti_get_row_at_index (AtkTable *table,
+                      gint index)
+{
+	ETableItem *item;
+
+	item = E_TABLE_ITEM (eti_a11y_get_gobject (ATK_OBJECT (table)));
+	if (!item)
+		return -1;
+
+	return index / item->cols - 1;
+}
+
+static gint
+eti_get_n_columns (AtkTable *table)
+{
+	ETableItem *item;
+
+	item = E_TABLE_ITEM (eti_a11y_get_gobject (ATK_OBJECT (table)));
+	if (!item)
+		return -1;
+
+	return item->cols;
+}
+
+static gint
+eti_get_n_rows (AtkTable *table)
+{
+	ETableItem *item;
+
+	item = E_TABLE_ITEM (eti_a11y_get_gobject (ATK_OBJECT (table)));
+	if (!item)
+		return -1;
+
+	return item->rows;
+}
+
+static gint
+eti_get_column_extent_at (AtkTable *table,
+                          gint row,
+                          gint column)
+{
+	ETableItem *item;
+	gint width;
+
+	item = E_TABLE_ITEM (eti_a11y_get_gobject (ATK_OBJECT (table)));
+	if (!item)
+		return -1;
+
+	e_table_item_get_cell_geometry (
+		item,
+		&row,
+		&column,
+		NULL,
+		NULL,
+		&width,
+		NULL);
+
+	return width;
+}
+
+static gint
+eti_get_row_extent_at (AtkTable *table,
+                       gint row,
+                       gint column)
+{
+	ETableItem *item;
+	gint height;
+
+	item = E_TABLE_ITEM (eti_a11y_get_gobject (ATK_OBJECT (table)));
+	if (!item)
+		return -1;
+
+	e_table_item_get_cell_geometry (
+		item,
+		&row,
+		&column,
+		NULL,
+		NULL,
+		NULL,
+		&height);
+
+	return height;
+}
+
+static AtkObject *
+eti_get_caption (AtkTable *table)
+{
+	/* Unimplemented */
+	return NULL;
+}
+
+static const gchar *
+eti_get_column_description (AtkTable *table,
+                            gint column)
+{
+	ETableItem *item;
+	ETableCol *ecol;
+
+	item = E_TABLE_ITEM (eti_a11y_get_gobject (ATK_OBJECT (table)));
+	if (!item)
+		return NULL;
+
+	ecol = e_table_header_get_column (item->header, column);
+
+	return ecol->text;
+}
+
+static AtkObject *
+eti_get_column_header (AtkTable *table,
+                       gint column)
+{
+	ETableItem *item;
+	ETableCol *ecol;
+	AtkObject *atk_obj = NULL;
+
+	item = E_TABLE_ITEM (eti_a11y_get_gobject (ATK_OBJECT (table)));
+	if (!item)
+		return NULL;
+
+	ecol = e_table_header_get_column (item->header, column);
+	if (ecol) {
+		atk_obj = gal_a11y_e_table_column_header_new (ecol, item);
+	}
+
+	return atk_obj;
+}
+
+static const gchar *
+eti_get_row_description (AtkTable *table,
+                         gint row)
+{
+	/* Unimplemented */
+	return NULL;
+}
+
+static AtkObject *
+eti_get_row_header (AtkTable *table,
+                    gint row)
+{
+	/* Unimplemented */
+	return NULL;
+}
+
+static AtkObject *
+eti_get_summary (AtkTable *table)
+{
+	/* Unimplemented */
+	return NULL;
+}
+
+static gboolean
+table_is_row_selected (AtkTable *table,
+                       gint row)
+{
+	ETableItem *item;
+	GalA11yETableItemPrivate *priv = GET_PRIVATE (table);
+
+	if (row < 0)
+		return FALSE;
+
+	if (atk_state_set_contains_state (priv->state_set, ATK_STATE_DEFUNCT))
+		return FALSE;
+
+	item = E_TABLE_ITEM (eti_a11y_get_gobject (ATK_OBJECT (table)));
+	if (!item)
+		return FALSE;
+
+	return e_selection_model_is_row_selected (
+		item->selection, view_to_model_row (item, row));
+}
+
+static gboolean
+table_is_selected (AtkTable *table,
+                   gint row,
+                   gint column)
+{
+	return table_is_row_selected (table, row);
+}
+
+static gint
+table_get_selected_rows (AtkTable *table,
+                         gint **rows_selected)
+{
+	ETableItem *item;
+	gint n_selected, row, index_selected;
+	GalA11yETableItemPrivate *priv = GET_PRIVATE (table);
+
+	if (atk_state_set_contains_state (priv->state_set, ATK_STATE_DEFUNCT))
+		return 0;
+
+	item = E_TABLE_ITEM (eti_a11y_get_gobject (ATK_OBJECT (table)));
+	if (!item)
+		return 0;
+
+	n_selected = e_selection_model_selected_count (item->selection);
+	if (rows_selected) {
+		*rows_selected = (gint *) g_malloc (n_selected * sizeof (gint));
+
+		index_selected = 0;
+		for (row = 0; row < item->rows && index_selected < n_selected; ++row) {
+			if (atk_table_is_row_selected (table, row)) {
+				(*rows_selected)[index_selected] = row;
+				++index_selected;
+			}
+		}
+	}
+	return n_selected;
+}
+
+static gboolean
+table_add_row_selection (AtkTable *table,
+                         gint row)
+{
+	ETableItem *item;
+
+	item = E_TABLE_ITEM (eti_a11y_get_gobject (ATK_OBJECT (table)));
+	if (!item)
+		return FALSE;
+
+	if (table_is_row_selected (table, row))
+		return TRUE;
+	e_selection_model_toggle_single_row (
+		item->selection,
+		view_to_model_row (item, row));
+
+	return TRUE;
+}
+
+static gboolean
+table_remove_row_selection (AtkTable *table,
+                            gint row)
+{
+	ETableItem *item;
+	GalA11yETableItemPrivate *priv = GET_PRIVATE (table);
+
+	if (atk_state_set_contains_state (priv->state_set, ATK_STATE_DEFUNCT))
+		return FALSE;
+
+	item = E_TABLE_ITEM (eti_a11y_get_gobject (ATK_OBJECT (table)));
+	if (!item)
+		return FALSE;
+
+	if (!atk_table_is_row_selected (table, row))
+		return TRUE;
+
+	e_selection_model_toggle_single_row (
+		item->selection, view_to_model_row (item, row));
+
+	return TRUE;
+}
+
+static void
+eti_atk_table_iface_init (AtkTableIface *iface)
+{
+	iface->ref_at = eti_ref_at;
+	iface->get_index_at = eti_get_index_at;
+	iface->get_column_at_index = eti_get_column_at_index;
+	iface->get_row_at_index = eti_get_row_at_index;
+	iface->get_n_columns = eti_get_n_columns;
+	iface->get_n_rows = eti_get_n_rows;
+	iface->get_column_extent_at = eti_get_column_extent_at;
+	iface->get_row_extent_at = eti_get_row_extent_at;
+	iface->get_caption = eti_get_caption;
+	iface->get_column_description = eti_get_column_description;
+	iface->get_column_header = eti_get_column_header;
+	iface->get_row_description = eti_get_row_description;
+	iface->get_row_header = eti_get_row_header;
+	iface->get_summary = eti_get_summary;
+
+	iface->is_row_selected = table_is_row_selected;
+	iface->is_selected = table_is_selected;
+	iface->get_selected_rows = table_get_selected_rows;
+	iface->add_row_selection = table_add_row_selection;
+	iface->remove_row_selection = table_remove_row_selection;
+}
+
+static void
+eti_atk_component_iface_init (AtkComponentIface *iface)
+{
+	component_parent_iface         = g_type_interface_peek_parent (iface);
+
+	iface->ref_accessible_at_point = eti_ref_accessible_at_point;
+	iface->get_extents             = eti_get_extents;
+}
+
+static void
+eti_rows_inserted (ETableModel *model,
+                   gint row,
+                   gint count,
+                   AtkObject *table_item)
+{
+	gint n_cols,n_rows,i,j;
+	GalA11yETableItem * item_a11y;
+	gint old_nrows;
+
+	g_return_if_fail (table_item);
+	item_a11y = GAL_A11Y_E_TABLE_ITEM (table_item);
+
+	n_cols = atk_table_get_n_columns (ATK_TABLE (table_item));
+	n_rows = atk_table_get_n_rows (ATK_TABLE (table_item));
+
+	old_nrows = GET_PRIVATE (item_a11y)->rows;
+
+	g_return_if_fail (n_cols > 0 && n_rows > 0);
+	g_return_if_fail (old_nrows == n_rows - count);
+
+	GET_PRIVATE (table_item)->rows = n_rows;
+
+	g_signal_emit_by_name (
+		table_item, "row-inserted", row,
+		count, NULL);
+
+	for (i = row; i < (row + count); i++) {
+		for (j = 0; j < n_cols; j++) {
+			g_signal_emit_by_name (
+				table_item,
+				"children_changed::add",
+				(((i + 1) * n_cols) + j), NULL, NULL);
+		}
+	}
+
+	g_signal_emit_by_name (table_item, "visible-data-changed");
+}
+
+static void
+eti_rows_deleted (ETableModel *model,
+                  gint row,
+                  gint count,
+                  AtkObject *table_item)
+{
+	gint i,j, n_rows, n_cols, old_nrows;
+	ETableItem *item = E_TABLE_ITEM (
+		atk_gobject_accessible_get_object (
+		ATK_GOBJECT_ACCESSIBLE (table_item)));
+
+	n_rows = atk_table_get_n_rows (ATK_TABLE (table_item));
+	n_cols = atk_table_get_n_columns (ATK_TABLE (table_item));
+
+	old_nrows = GET_PRIVATE (table_item)->rows;
+
+	g_return_if_fail (row + count <= old_nrows);
+	g_return_if_fail (old_nrows == n_rows + count);
+	GET_PRIVATE (table_item)->rows = n_rows;
+
+	g_signal_emit_by_name (
+		table_item, "row-deleted", row,
+		count, NULL);
+
+	for (i = row; i < (row + count); i++) {
+		for (j = 0; j < n_cols; j++) {
+			g_signal_emit_by_name (
+				table_item,
+				"children_changed::remove",
+				(((i + 1) * n_cols) + j), NULL, NULL);
+		}
+	}
+	g_signal_emit_by_name (table_item, "visible-data-changed");
+	eti_a11y_reset_focus_object ((GalA11yETableItem *) table_item, item, TRUE);
+}
+
+static void
+eti_tree_model_node_changed_cb (ETreeModel *model,
+                                ETreePath node,
+                                ETableItem *eti)
+{
+	AtkObject *atk_obj;
+	GalA11yETableItem *a11y;
+
+	g_return_if_fail (E_IS_TABLE_ITEM (eti));
+
+	atk_obj = atk_gobject_accessible_for_object (G_OBJECT (eti));
+	a11y = GAL_A11Y_E_TABLE_ITEM (atk_obj);
+
+	/* we can't figure out which rows are changed, so just send out a signal ... */
+	if  (GET_PRIVATE (a11y)->rows > 0)
+		g_signal_emit_by_name (a11y, "visible-data-changed");
+}
+
+enum {
+        ETI_HEADER_UNCHANGED = 0,
+        ETI_HEADER_REORDERED,
+        ETI_HEADER_NEW_ADDED,
+        ETI_HEADER_REMOVED
+};
+
+/*
+ * 1. Check what actually happened: column reorder, remove or add
+ * 2. Update cache
+ * 3. Emit signals
+ */
+static void
+eti_header_structure_changed (ETableHeader *eth,
+                              AtkObject *a11y)
+{
+
+	gboolean reorder_found = FALSE, added_found = FALSE, removed_found = FALSE;
+	GalA11yETableItem * a11y_item;
+	ETableCol ** cols, **prev_cols;
+	GalA11yETableItemPrivate *priv;
+	gint *state = NULL, *prev_state = NULL, *reorder = NULL;
+	gint i,j,n_rows,n_cols, prev_n_cols;
+
+	a11y_item = GAL_A11Y_E_TABLE_ITEM (a11y);
+	priv = GET_PRIVATE (a11y_item);
+
+	/* Assume rows do not changed. */
+	n_rows = priv->rows;
+
+	prev_n_cols = priv->cols;
+	prev_cols = priv->columns;
+
+	cols = e_table_header_get_columns (eth);
+	n_cols = eth->col_count;
+
+	g_return_if_fail (cols && prev_cols && n_cols > 0);
+
+        /* Init to ETI_HEADER_UNCHANGED. */
+	state = g_malloc0 (sizeof (gint) * n_cols);
+	prev_state = g_malloc0 (sizeof (gint) * prev_n_cols);
+	reorder = g_malloc0 (sizeof (gint) * n_cols);
+
+        /* Compare with previously saved column headers. */
+	for (i = 0; i < n_cols && cols[i]; i++) {
+		for (j = 0; j < prev_n_cols && prev_cols[j]; j++) {
+			if (prev_cols[j] == cols[i] && i != j) {
+
+				reorder_found = TRUE;
+				state[i] = ETI_HEADER_REORDERED;
+				reorder[i] = j;
+
+				break;
+			} else if (prev_cols[j] == cols[i]) {
+                                /* OK, this column is not changed. */
+				break;
+			}
+		}
+
+                /* cols[i] is new added column. */
+		if (j == prev_n_cols) {
+			added_found = TRUE;
+			state[i] = ETI_HEADER_NEW_ADDED;
+		}
+	}
+
+        /* Now try to find if there are removed columns. */
+	for (i = 0; i < prev_n_cols && prev_cols[i]; i++) {
+		for (j = 0; j < n_cols && cols[j]; j++)
+			if (prev_cols[j] == cols[i])
+				break;
+
+                /* Removed columns found. */
+		if (j == n_cols) {
+			removed_found = TRUE;
+			prev_state[j] = ETI_HEADER_REMOVED;
+		}
+	}
+
+	/* If nothing interesting just return. */
+	if (!reorder_found && !added_found && !removed_found)
+		return;
+
+	/* Emit signals */
+	if (reorder_found)
+		g_signal_emit_by_name (a11y_item, "column_reordered");
+
+	if (removed_found) {
+		for (i = 0; i < prev_n_cols; i++) {
+			if (prev_state[i] == ETI_HEADER_REMOVED) {
+				g_signal_emit_by_name (
+					a11y_item, "column-deleted", i, 1);
+				for (j = 0; j < n_rows; j++)
+					g_signal_emit_by_name (
+						a11y_item,
+						"children_changed::remove",
+						((j + 1) * prev_n_cols + i),
+						NULL, NULL);
+			}
+		}
+	}
+
+	if (added_found) {
+		for (i = 0; i < n_cols; i++) {
+			if (state[i] == ETI_HEADER_NEW_ADDED) {
+				g_signal_emit_by_name (
+					a11y_item, "column-inserted", i, 1);
+				for (j = 0; j < n_rows; j++)
+					g_signal_emit_by_name (
+						a11y_item,
+						"children_changed::add",
+						((j + 1) * n_cols + i),
+						NULL, NULL);
+			}
+		}
+	}
+
+	priv->cols = n_cols;
+
+	g_free (state);
+	g_free (reorder);
+	g_free (prev_state);
+
+	free_columns (priv->columns);
+	priv->columns = cols;
+}
+
+static void
+eti_real_initialize (AtkObject *obj,
+                     gpointer data)
+{
+	ETableItem * eti;
+	ETableModel * model;
+
+	ATK_OBJECT_CLASS (parent_class)->initialize (obj, data);
+	eti = E_TABLE_ITEM (data);
+
+	model = eti->table_model;
+
+	g_signal_connect (
+		model, "model-rows-inserted",
+		G_CALLBACK (eti_rows_inserted), obj);
+	g_signal_connect (
+		model, "model-rows-deleted",
+		G_CALLBACK (eti_rows_deleted), obj);
+	g_signal_connect (
+		eti->header, "structure_change",
+		G_CALLBACK (eti_header_structure_changed), obj);
+
+}
+
+static void
+eti_class_init (GalA11yETableItemClass *class)
+{
+	AtkObjectClass *atk_object_class = ATK_OBJECT_CLASS (class);
+	GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+	quark_accessible_object =
+		g_quark_from_static_string ("gtk-accessible-object");
+
+	parent_class = g_type_class_ref (PARENT_TYPE);
+
+	object_class->dispose = eti_dispose;
+
+	atk_object_class->get_n_children = eti_get_n_children;
+	atk_object_class->ref_child = eti_ref_child;
+	atk_object_class->initialize = eti_real_initialize;
+	atk_object_class->ref_state_set = eti_ref_state_set;
+}
+
+static void
+eti_init (GalA11yETableItem *a11y)
+{
+	GalA11yETableItemPrivate *priv;
+
+	priv = GET_PRIVATE (a11y);
+
+	priv->selection_change_id = 0;
+	priv->cursor_change_id = 0;
+	priv->selection = NULL;
+}
+
+/* atk selection */
+
+static void	atk_selection_interface_init	(AtkSelectionIface *iface);
+static gboolean	selection_add_selection		(AtkSelection *selection,
+						 gint i);
+static gboolean	selection_clear_selection	(AtkSelection *selection);
+static AtkObject *
+		selection_ref_selection		(AtkSelection *selection,
+						 gint i);
+static gint	selection_get_selection_count	(AtkSelection *selection);
+static gboolean	selection_is_child_selected	(AtkSelection *selection,
+						 gint i);
+
+/* callbacks */
+static void eti_a11y_selection_model_removed_cb (ETableItem *eti,
+						 ESelectionModel *selection,
+						 gpointer data);
+static void eti_a11y_selection_model_added_cb (ETableItem *eti,
+					       ESelectionModel *selection,
+					       gpointer data);
+static void eti_a11y_selection_changed_cb (ESelectionModel *selection,
+					   GalA11yETableItem *a11y);
+static void eti_a11y_cursor_changed_cb (ESelectionModel *selection,
+					gint row, gint col,
+					GalA11yETableItem *a11y);
+
+/**
+ * gal_a11y_e_table_item_get_type:
+ * @void:
+ *
+ * Registers the &GalA11yETableItem class if necessary, and returns the type ID
+ * associated to it.
+ *
+ * Return value: The type ID of the &GalA11yETableItem class.
+ **/
+GType
+gal_a11y_e_table_item_get_type (void)
+{
+	static GType type = 0;
+
+	if (!type) {
+		AtkObjectFactory *factory;
+
+		GTypeInfo info = {
+			sizeof (GalA11yETableItemClass),
+			(GBaseInitFunc) NULL,
+			(GBaseFinalizeFunc) NULL,
+			(GClassInitFunc) eti_class_init,
+			(GClassFinalizeFunc) NULL,
+			NULL, /* class_data */
+			sizeof (GalA11yETableItem),
+			0,
+			(GInstanceInitFunc) eti_init,
+			NULL /* value_table_item */
+		};
+
+		static const GInterfaceInfo atk_component_info = {
+			(GInterfaceInitFunc) eti_atk_component_iface_init,
+			(GInterfaceFinalizeFunc) NULL,
+			NULL
+		};
+		static const GInterfaceInfo atk_table_info = {
+			(GInterfaceInitFunc) eti_atk_table_iface_init,
+			(GInterfaceFinalizeFunc) NULL,
+			NULL
+		};
+
+		static const GInterfaceInfo atk_selection_info = {
+			(GInterfaceInitFunc) atk_selection_interface_init,
+			(GInterfaceFinalizeFunc) NULL,
+			NULL
+		};
+
+		factory = atk_registry_get_factory (
+			atk_get_default_registry (), GNOME_TYPE_CANVAS_ITEM);
+		parent_type = atk_object_factory_get_accessible_type (factory);
+
+		type = gal_a11y_type_register_static_with_private (
+			PARENT_TYPE, "GalA11yETableItem", &info, 0,
+			sizeof (GalA11yETableItemPrivate), &priv_offset);
+
+		g_type_add_interface_static (type, ATK_TYPE_COMPONENT, &atk_component_info);
+		g_type_add_interface_static (type, ATK_TYPE_TABLE, &atk_table_info);
+		g_type_add_interface_static (type, ATK_TYPE_SELECTION, &atk_selection_info);
+	}
+
+	return type;
+}
+
+AtkObject *
+gal_a11y_e_table_item_new (ETableItem *item)
+{
+	GalA11yETableItem *a11y;
+	AtkObject *accessible;
+	ESelectionModel * esm;
+	AtkObject *parent;
+	const gchar *name;
+
+	g_return_val_if_fail (item && item->cols >= 0 && item->rows >= 0, NULL);
+	a11y = g_object_new (gal_a11y_e_table_item_get_type (), NULL);
+
+	atk_object_initialize (ATK_OBJECT (a11y), item);
+
+	GET_PRIVATE (a11y)->state_set = atk_state_set_new ();
+
+	atk_state_set_add_state (GET_PRIVATE (a11y)->state_set, ATK_STATE_TRANSIENT);
+	atk_state_set_add_state (GET_PRIVATE (a11y)->state_set, ATK_STATE_ENABLED);
+	atk_state_set_add_state (GET_PRIVATE (a11y)->state_set, ATK_STATE_SENSITIVE);
+	atk_state_set_add_state (GET_PRIVATE (a11y)->state_set, ATK_STATE_SHOWING);
+	atk_state_set_add_state (GET_PRIVATE (a11y)->state_set, ATK_STATE_VISIBLE);
+
+	accessible  = ATK_OBJECT (a11y);
+
+	GET_PRIVATE (a11y)->item = item;
+	/* Initialize cell data. */
+	GET_PRIVATE (a11y)->cols = item->cols;
+	GET_PRIVATE (a11y)->rows = item->rows;
+
+	GET_PRIVATE (a11y)->columns = e_table_header_get_columns (item->header);
+	if (GET_PRIVATE (a11y)->columns == NULL)
+		return NULL;
+
+	if (item) {
+		g_signal_connect (
+			item, "selection_model_removed",
+			G_CALLBACK (eti_a11y_selection_model_removed_cb), NULL);
+		g_signal_connect (
+			item, "selection_model_added",
+			G_CALLBACK (eti_a11y_selection_model_added_cb), NULL);
+		if (item->selection)
+			gal_a11y_e_table_item_ref_selection (
+				a11y,
+				item->selection);
+
+		/* find the TableItem's parent: table or tree */
+		GET_PRIVATE (a11y)->widget = gtk_widget_get_parent (
+			GTK_WIDGET (item->parent.canvas));
+		parent = gtk_widget_get_accessible (GET_PRIVATE (a11y)->widget);
+		name = atk_object_get_name (parent);
+		if (name)
+			atk_object_set_name (accessible, name);
+		atk_object_set_parent (accessible, parent);
+
+		if (E_IS_TREE (GET_PRIVATE (a11y)->widget)) {
+			ETreeModel *model;
+			model = e_tree_get_model (E_TREE (GET_PRIVATE (a11y)->widget));
+			g_signal_connect (
+				model, "node_changed",
+				G_CALLBACK (eti_tree_model_node_changed_cb), item);
+			accessible->role = ATK_ROLE_TREE_TABLE;
+		} else if (E_IS_TABLE (GET_PRIVATE (a11y)->widget)) {
+			accessible->role = ATK_ROLE_TABLE;
+		}
+	}
+
+	if (item)
+		g_object_weak_ref (G_OBJECT (item), item_finalized, g_object_ref (a11y));
+
+	esm = item->selection;
+
+	if (esm != NULL) {
+		eti_a11y_reset_focus_object (a11y, item, FALSE);
+	}
+
+	return ATK_OBJECT (a11y);
+}
+
+static gboolean
+gal_a11y_e_table_item_ref_selection (GalA11yETableItem *a11y,
+                                     ESelectionModel *selection)
+{
+	GalA11yETableItemPrivate *priv;
+
+	g_return_val_if_fail (a11y && selection, FALSE);
+
+	priv = GET_PRIVATE (a11y);
+	priv->selection_change_id = g_signal_connect (
+		selection, "selection_changed",
+		G_CALLBACK (eti_a11y_selection_changed_cb), a11y);
+	priv->cursor_change_id = g_signal_connect (
+		selection, "cursor_changed",
+		G_CALLBACK (eti_a11y_cursor_changed_cb), a11y);
+
+	priv->selection = selection;
+	g_object_ref (selection);
+
+	return TRUE;
+}
+
+static gboolean
+gal_a11y_e_table_item_unref_selection (GalA11yETableItem *a11y)
+{
+	GalA11yETableItemPrivate *priv;
+
+	g_return_val_if_fail (a11y, FALSE);
+
+	priv = GET_PRIVATE (a11y);
+
+	g_return_val_if_fail (priv->selection_change_id != 0, FALSE);
+	g_return_val_if_fail (priv->cursor_change_id != 0, FALSE);
+
+	g_signal_handler_disconnect (
+		priv->selection,
+		priv->selection_change_id);
+	g_signal_handler_disconnect (
+		priv->selection,
+		priv->cursor_change_id);
+	priv->cursor_change_id = 0;
+	priv->selection_change_id = 0;
+
+	g_object_unref (priv->selection);
+	priv->selection = NULL;
+
+	return TRUE;
+}
+
+/* callbacks */
+
+static void
+eti_a11y_selection_model_removed_cb (ETableItem *eti,
+                                     ESelectionModel *selection,
+                                     gpointer data)
+{
+	AtkObject *atk_obj;
+	GalA11yETableItem *a11y;
+
+	g_return_if_fail (E_IS_TABLE_ITEM (eti));
+	g_return_if_fail (E_IS_SELECTION_MODEL (selection));
+
+	atk_obj = atk_gobject_accessible_for_object (G_OBJECT (eti));
+	a11y = GAL_A11Y_E_TABLE_ITEM (atk_obj);
+
+	if (selection == GET_PRIVATE (a11y)->selection)
+		gal_a11y_e_table_item_unref_selection (a11y);
+}
+
+static void
+eti_a11y_selection_model_added_cb (ETableItem *eti,
+                                   ESelectionModel *selection,
+                                   gpointer data)
+{
+	AtkObject *atk_obj;
+	GalA11yETableItem *a11y;
+
+	g_return_if_fail (E_IS_TABLE_ITEM (eti));
+	g_return_if_fail (E_IS_SELECTION_MODEL (selection));
+
+	atk_obj = atk_gobject_accessible_for_object (G_OBJECT (eti));
+	a11y = GAL_A11Y_E_TABLE_ITEM (atk_obj);
+
+	if (GET_PRIVATE (a11y)->selection)
+		gal_a11y_e_table_item_unref_selection (a11y);
+	gal_a11y_e_table_item_ref_selection (a11y, selection);
+}
+
+static void
+eti_a11y_selection_changed_cb (ESelectionModel *selection,
+                               GalA11yETableItem *a11y)
+{
+	GalA11yETableItemPrivate *priv = GET_PRIVATE (a11y);
+
+	if (atk_state_set_contains_state (priv->state_set, ATK_STATE_DEFUNCT))
+		return;
+
+	g_return_if_fail (GAL_A11Y_IS_E_TABLE_ITEM (a11y));
+
+	g_signal_emit_by_name (a11y, "selection_changed");
+}
+
+static void
+eti_a11y_cursor_changed_cb (ESelectionModel *selection,
+                            gint row,
+                            gint col,
+                            GalA11yETableItem *a11y)
+{
+	ETableItem *item;
+	GalA11yETableItemPrivate *priv = GET_PRIVATE (a11y);
+
+	g_return_if_fail (GAL_A11Y_IS_E_TABLE_ITEM (a11y));
+
+	if (atk_state_set_contains_state (priv->state_set, ATK_STATE_DEFUNCT))
+		return;
+
+	item = E_TABLE_ITEM (eti_a11y_get_gobject (ATK_OBJECT (a11y)));
+
+	g_return_if_fail (item);
+
+	if (row == -1 && col == -1)
+		return;
+	eti_a11y_reset_focus_object (a11y, item, TRUE);
+}
+
+/* atk selection */
+
+static void atk_selection_interface_init (AtkSelectionIface *iface)
+{
+	g_return_if_fail (iface != NULL);
+	iface->add_selection = selection_add_selection;
+	iface->clear_selection = selection_clear_selection;
+	iface->ref_selection = selection_ref_selection;
+	iface->get_selection_count = selection_get_selection_count;
+	iface->is_child_selected = selection_is_child_selected;
+}
+
+static gboolean
+selection_add_selection (AtkSelection *selection,
+                         gint index)
+{
+	AtkTable *table;
+	gint row, col, cursor_row, cursor_col, model_row, model_col;
+	ETableItem *item;
+
+	item = E_TABLE_ITEM (eti_a11y_get_gobject (ATK_OBJECT (selection)));
+	if (!item)
+		return FALSE;
+
+	table = ATK_TABLE (selection);
+
+	row = atk_table_get_row_at_index (table, index);
+	col = atk_table_get_column_at_index (table, index);
+
+	model_row = view_to_model_row (item, row);
+	model_col = view_to_model_col (item, col);
+
+	cursor_row = e_selection_model_cursor_row (item->selection);
+	cursor_col = e_selection_model_cursor_col (item->selection);
+
+	/* check whether is selected already */
+	if (model_row == cursor_row && model_col == cursor_col)
+		return TRUE;
+
+	if (model_row != cursor_row) {
+		/* we need to make the item get focus */
+		e_canvas_item_grab_focus (GNOME_CANVAS_ITEM (item), TRUE);
+
+		/* FIXME, currently we only support single row selection */
+		atk_selection_clear_selection (selection);
+		atk_table_add_row_selection (table, row);
+	}
+
+	e_selection_model_change_cursor (
+		item->selection,
+		model_row,
+		model_col);
+	e_selection_model_cursor_changed (
+		item->selection,
+		model_row,
+		model_col);
+	e_selection_model_cursor_activated (
+		item->selection,
+		model_row,
+		model_col);
+	return TRUE;
+}
+
+static gboolean
+selection_clear_selection (AtkSelection *selection)
+{
+	ETableItem *item;
+
+	item = E_TABLE_ITEM (eti_a11y_get_gobject (ATK_OBJECT (selection)));
+	if (!item)
+		return FALSE;
+
+	e_selection_model_clear (item->selection);
+	return TRUE;
+}
+
+static AtkObject *
+selection_ref_selection (AtkSelection *selection,
+                         gint index)
+{
+	AtkTable *table;
+	gint row, col;
+
+	table = ATK_TABLE (selection);
+	row = atk_table_get_row_at_index (table, index);
+	col = atk_table_get_column_at_index (table, index);
+	if (!atk_table_is_row_selected (table, row))
+		return NULL;
+
+	return eti_ref_at (table, row, col);
+}
+
+static gint
+selection_get_selection_count (AtkSelection *selection)
+{
+	AtkTable *table;
+	gint n_selected;
+
+	table = ATK_TABLE (selection);
+	n_selected = atk_table_get_selected_rows (table, NULL);
+	if (n_selected > 0)
+		n_selected *= atk_table_get_n_columns (table);
+	return n_selected;
+}
+
+static gboolean
+selection_is_child_selected (AtkSelection *selection,
+                             gint i)
+{
+	gint row;
+
+	row = atk_table_get_row_at_index (ATK_TABLE (selection), i);
+	return atk_table_is_row_selected (ATK_TABLE (selection), row);
+}
+
+void
+gal_a11y_e_table_item_init (void)
+{
+	if (atk_get_root ())
+		atk_registry_set_factory_type (
+			atk_get_default_registry (),
+			E_TYPE_TABLE_ITEM,
+			gal_a11y_e_table_item_factory_get_type ());
+}
+
diff --git a/e-util/gal-a11y-e-table-item.h b/e-util/gal-a11y-e-table-item.h
new file mode 100644
index 0000000..4791a70
--- /dev/null
+++ b/e-util/gal-a11y-e-table-item.h
@@ -0,0 +1,62 @@
+/*
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Christopher James Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef __GAL_A11Y_E_TABLE_ITEM_H__
+#define __GAL_A11Y_E_TABLE_ITEM_H__
+
+#include <atk/atkgobjectaccessible.h>
+
+#include <e-util/e-table-item.h>
+
+#define GAL_A11Y_TYPE_E_TABLE_ITEM            (gal_a11y_e_table_item_get_type ())
+#define GAL_A11Y_E_TABLE_ITEM(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GAL_A11Y_TYPE_E_TABLE_ITEM, GalA11yETableItem))
+#define GAL_A11Y_E_TABLE_ITEM_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GAL_A11Y_TYPE_E_TABLE_ITEM, GalA11yETableItemClass))
+#define GAL_A11Y_IS_E_TABLE_ITEM(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GAL_A11Y_TYPE_E_TABLE_ITEM))
+#define GAL_A11Y_IS_E_TABLE_ITEM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GAL_A11Y_TYPE_E_TABLE_ITEM))
+
+typedef struct _GalA11yETableItem GalA11yETableItem;
+typedef struct _GalA11yETableItemClass GalA11yETableItemClass;
+typedef struct _GalA11yETableItemPrivate GalA11yETableItemPrivate;
+
+/* This struct should actually be larger as this isn't what we derive from.
+ * The GalA11yETableItemPrivate comes right after the parent class structure.
+ **/
+struct _GalA11yETableItem {
+	AtkGObjectAccessible parent;
+};
+
+struct _GalA11yETableItemClass {
+	AtkGObjectAccessibleClass parent_class;
+};
+
+/* Standard Glib function */
+GType      gal_a11y_e_table_item_get_type  (void);
+AtkObject *gal_a11y_e_table_item_new       (ETableItem *item);
+
+void gal_a11y_e_table_item_init (void);
+
+#endif /* __GAL_A11Y_E_TABLE_ITEM_H__ */
diff --git a/e-util/gal-a11y-e-table.c b/e-util/gal-a11y-e-table.c
new file mode 100644
index 0000000..f9178bd
--- /dev/null
+++ b/e-util/gal-a11y-e-table.c
@@ -0,0 +1,315 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Christopher James Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "gal-a11y-e-table.h"
+
+#include "e-table-click-to-add.h"
+#include "e-table-group-container.h"
+#include "e-table-group-leaf.h"
+#include "e-table-group.h"
+#include "e-table.h"
+#include "gal-a11y-e-table-factory.h"
+#include "gal-a11y-e-table-item.h"
+#include "gal-a11y-util.h"
+
+#define CS_CLASS(a11y) (G_TYPE_INSTANCE_GET_CLASS ((a11y), C_TYPE_STREAM, GalA11yETableClass))
+static AtkObjectClass *parent_class;
+static GType parent_type;
+static gint priv_offset;
+#define GET_PRIVATE(object) ((GalA11yETablePrivate *) (((gchar *) object) + priv_offset))
+#define PARENT_TYPE (parent_type)
+
+struct _GalA11yETablePrivate {
+	AtkObject *child_item;
+};
+
+/* Static functions */
+static ETableItem *
+find_first_table_item (ETableGroup *group)
+{
+	GnomeCanvasGroup *cgroup;
+	GList *l;
+
+	cgroup = GNOME_CANVAS_GROUP (group);
+
+	for (l = cgroup->item_list; l; l = l->next) {
+		GnomeCanvasItem *i;
+
+		i = GNOME_CANVAS_ITEM (l->data);
+
+		if (E_IS_TABLE_GROUP (i))
+			return find_first_table_item (E_TABLE_GROUP (i));
+		else if (E_IS_TABLE_ITEM (i)) {
+			return E_TABLE_ITEM (i);
+		}
+	}
+
+	return NULL;
+}
+
+static AtkObject *
+eti_get_accessible (ETableItem *eti,
+                    AtkObject *parent)
+{
+	AtkObject *a11y = NULL;
+
+	g_return_val_if_fail (eti, NULL);
+
+	a11y = atk_gobject_accessible_for_object (G_OBJECT (eti));
+	g_return_val_if_fail (a11y, NULL);
+
+	return a11y;
+}
+
+static gboolean
+init_child_item (GalA11yETable *a11y)
+{
+	ETable *table;
+
+	if (!a11y || !GTK_IS_ACCESSIBLE (a11y))
+		return FALSE;
+
+	table = E_TABLE (gtk_accessible_get_widget (GTK_ACCESSIBLE (a11y)));
+	if (table && gtk_widget_get_mapped (GTK_WIDGET (table)) && table->group && E_IS_TABLE_GROUP_CONTAINER (table->group)) {
+		ETableGroupContainer *etgc =  (ETableGroupContainer *) table->group;
+		GList *list;
+
+		for (list = etgc->children; list; list = g_list_next (list)) {
+			ETableGroupContainerChildNode *child_node = list->data;
+			ETableGroup *child = child_node->child;
+			ETableItem *eti = find_first_table_item (child);
+
+			eti_get_accessible (eti, ATK_OBJECT (a11y));
+		}
+	}
+	g_object_unref (a11y);
+	g_object_unref (table);
+
+	return FALSE;
+}
+
+static AtkObject *
+et_ref_accessible_at_point (AtkComponent *component,
+                            gint x,
+                            gint y,
+                            AtkCoordType coord_type)
+{
+	GalA11yETable *a11y = GAL_A11Y_E_TABLE (component);
+	if (GET_PRIVATE (a11y)->child_item)
+		g_object_ref (GET_PRIVATE (a11y)->child_item);
+	return GET_PRIVATE (a11y)->child_item;
+}
+
+static gint
+et_get_n_children (AtkObject *accessible)
+{
+	GalA11yETable *a11y = GAL_A11Y_E_TABLE (accessible);
+	ETable * et;
+	gint n = 0;
+
+	et = E_TABLE (gtk_accessible_get_widget (GTK_ACCESSIBLE (a11y)));
+
+	if (et && et->group) {
+		if (E_IS_TABLE_GROUP_LEAF (et->group))
+			n = 1;
+		else if (E_IS_TABLE_GROUP_CONTAINER (et->group)) {
+			ETableGroupContainer *etgc = (ETableGroupContainer *) et->group;
+			n = g_list_length (etgc->children);
+		}
+	}
+
+	if (et && et->use_click_to_add && et->click_to_add) {
+		n++;
+	}
+	return n;
+}
+
+static AtkObject *
+et_ref_child (AtkObject *accessible,
+              gint i)
+{
+	GalA11yETable *a11y = GAL_A11Y_E_TABLE (accessible);
+	ETable * et;
+	gint child_no;
+
+	et = E_TABLE (gtk_accessible_get_widget (GTK_ACCESSIBLE (a11y)));
+	if (!et)
+		return NULL;
+
+	child_no = et_get_n_children (accessible);
+	if (i == 0 || i < child_no - 1) {
+		if (E_IS_TABLE_GROUP_LEAF (et->group)) {
+			ETableItem *eti = find_first_table_item (et->group);
+			AtkObject *aeti = eti_get_accessible (eti, accessible);
+			if (aeti)
+				g_object_ref (aeti);
+			return aeti;
+
+		} else if (E_IS_TABLE_GROUP_CONTAINER (et->group)) {
+			ETableGroupContainer *etgc =  (ETableGroupContainer *) et->group;
+			ETableGroupContainerChildNode *child_node = g_list_nth_data (etgc->children, i);
+			if (child_node) {
+				ETableGroup *child = child_node->child;
+				ETableItem * eti = find_first_table_item (child);
+				AtkObject *aeti =  eti_get_accessible (eti, accessible);
+				if (aeti)
+					g_object_ref (aeti);
+				return aeti;
+			}
+		}
+	} else if (i == child_no -1) {
+		ETableClickToAdd * etcta;
+
+		if (et && et->use_click_to_add && et->click_to_add) {
+			etcta = E_TABLE_CLICK_TO_ADD (et->click_to_add);
+			accessible = atk_gobject_accessible_for_object (G_OBJECT (etcta));
+			if (accessible)
+				g_object_ref (accessible);
+			return accessible;
+		}
+	}
+
+	return NULL;
+}
+
+static AtkLayer
+et_get_layer (AtkComponent *component)
+{
+	return ATK_LAYER_WIDGET;
+}
+
+static void
+et_class_init (GalA11yETableClass *class)
+{
+	AtkObjectClass *atk_object_class = ATK_OBJECT_CLASS (class);
+
+	parent_class                              = g_type_class_ref (PARENT_TYPE);
+
+	atk_object_class->get_n_children          = et_get_n_children;
+	atk_object_class->ref_child               = et_ref_child;
+}
+
+static void
+et_atk_component_iface_init (AtkComponentIface *iface)
+{
+	iface->ref_accessible_at_point = et_ref_accessible_at_point;
+	iface->get_layer = et_get_layer;
+}
+
+static void
+et_init (GalA11yETable *a11y)
+{
+	GalA11yETablePrivate *priv;
+
+	priv = GET_PRIVATE (a11y);
+
+	priv->child_item = NULL;
+}
+
+/**
+ * gal_a11y_e_table_get_type:
+ * @void:
+ *
+ * Registers the &GalA11yETable class if necessary, and returns the type ID
+ * associated to it.
+ *
+ * Return value: The type ID of the &GalA11yETable class.
+ **/
+GType
+gal_a11y_e_table_get_type (void)
+{
+	static GType type = 0;
+
+	if (!type) {
+		AtkObjectFactory *factory;
+
+		GTypeInfo info = {
+			sizeof (GalA11yETableClass),
+			(GBaseInitFunc) NULL,
+			(GBaseFinalizeFunc) NULL,
+			(GClassInitFunc) et_class_init,
+			(GClassFinalizeFunc) NULL,
+			NULL, /* class_data */
+			sizeof (GalA11yETable),
+			0,
+			(GInstanceInitFunc) et_init,
+			NULL /* value_table */
+		};
+
+		static const GInterfaceInfo atk_component_info = {
+			(GInterfaceInitFunc) et_atk_component_iface_init,
+			(GInterfaceFinalizeFunc) NULL,
+			NULL
+		};
+
+		factory = atk_registry_get_factory (atk_get_default_registry (), GTK_TYPE_WIDGET);
+		parent_type = atk_object_factory_get_accessible_type (factory);
+
+		type = gal_a11y_type_register_static_with_private (
+			PARENT_TYPE, "GalA11yETable", &info, 0,
+			sizeof (GalA11yETablePrivate), &priv_offset);
+		g_type_add_interface_static (type, ATK_TYPE_COMPONENT, &atk_component_info);
+	}
+
+	return type;
+}
+
+AtkObject *
+gal_a11y_e_table_new (GObject *widget)
+{
+	GalA11yETable *a11y;
+	ETable *table;
+
+	table = E_TABLE (widget);
+
+	a11y = g_object_new (gal_a11y_e_table_get_type (), NULL);
+
+	gtk_accessible_set_widget (GTK_ACCESSIBLE (a11y), GTK_WIDGET (widget));
+
+	/* we need to init all the children for multiple table items */
+	if (table && gtk_widget_get_mapped (GTK_WIDGET (table)) && table->group && E_IS_TABLE_GROUP_CONTAINER (table->group)) {
+		/* Ref it here so that it is still valid in the idle function */
+		/* It will be unrefed in the idle function */
+		g_object_ref (a11y);
+		g_object_ref (widget);
+
+		g_idle_add ((GSourceFunc) init_child_item, a11y);
+	}
+
+	return ATK_OBJECT (a11y);
+}
+
+void
+gal_a11y_e_table_init (void)
+{
+	if (atk_get_root ())
+		atk_registry_set_factory_type (
+			atk_get_default_registry (),
+						E_TYPE_TABLE,
+						gal_a11y_e_table_factory_get_type ());
+
+}
+
diff --git a/e-util/gal-a11y-e-table.h b/e-util/gal-a11y-e-table.h
new file mode 100644
index 0000000..1e47965
--- /dev/null
+++ b/e-util/gal-a11y-e-table.h
@@ -0,0 +1,62 @@
+/*
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Christopher James Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef __GAL_A11Y_E_TABLE_H__
+#define __GAL_A11Y_E_TABLE_H__
+
+#include <gtk/gtk.h>
+#include <atk/atkobject.h>
+#include <atk/atkcomponent.h>
+
+#define GAL_A11Y_TYPE_E_TABLE            (gal_a11y_e_table_get_type ())
+#define GAL_A11Y_E_TABLE(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GAL_A11Y_TYPE_E_TABLE, GalA11yETable))
+#define GAL_A11Y_E_TABLE_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GAL_A11Y_TYPE_E_TABLE, GalA11yETableClass))
+#define GAL_A11Y_IS_E_TABLE(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GAL_A11Y_TYPE_E_TABLE))
+#define GAL_A11Y_IS_E_TABLE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GAL_A11Y_TYPE_E_TABLE))
+
+typedef struct _GalA11yETable GalA11yETable;
+typedef struct _GalA11yETableClass GalA11yETableClass;
+typedef struct _GalA11yETablePrivate GalA11yETablePrivate;
+
+/* This struct should actually be larger as this isn't what we derive from.
+ * The GalA11yETablePrivate comes right after the parent class structure.
+ **/
+struct _GalA11yETable {
+	GtkAccessible object;
+};
+
+struct _GalA11yETableClass {
+	GtkAccessibleClass parent_class;
+};
+
+/* Standard Glib function */
+GType      gal_a11y_e_table_get_type  (void);
+AtkObject *gal_a11y_e_table_new       (GObject *table);
+
+void       gal_a11y_e_table_init (void);
+
+#endif /* __GAL_A11Y_E_TABLE_H__ */
diff --git a/e-util/gal-a11y-e-text-factory.c b/e-util/gal-a11y-e-text-factory.c
new file mode 100644
index 0000000..191b30d
--- /dev/null
+++ b/e-util/gal-a11y-e-text-factory.c
@@ -0,0 +1,103 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Christopher James Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-text.h"
+#include "gal-a11y-e-text-factory.h"
+#include "gal-a11y-e-text.h"
+
+static AtkObjectFactoryClass *parent_class;
+#define PARENT_TYPE (ATK_TYPE_OBJECT_FACTORY)
+
+/* Static functions */
+
+static GType
+gal_a11y_e_text_factory_get_accessible_type (void)
+{
+	return GAL_A11Y_TYPE_E_TEXT;
+}
+
+static AtkObject *
+gal_a11y_e_text_factory_create_accessible (GObject *obj)
+{
+	AtkObject *atk_object;
+
+	g_return_val_if_fail (E_IS_TEXT (obj), NULL);
+
+	atk_object = g_object_new (GAL_A11Y_TYPE_E_TEXT, NULL);
+	atk_object_initialize (atk_object, obj);
+
+	return atk_object;
+}
+
+static void
+gal_a11y_e_text_factory_class_init (GalA11yETextFactoryClass *class)
+{
+	AtkObjectFactoryClass *factory_class = ATK_OBJECT_FACTORY_CLASS (class);
+
+	parent_class = g_type_class_ref (PARENT_TYPE);
+
+	factory_class->create_accessible   = gal_a11y_e_text_factory_create_accessible;
+	factory_class->get_accessible_type = gal_a11y_e_text_factory_get_accessible_type;
+}
+
+static void
+gal_a11y_e_text_factory_init (GalA11yETextFactory *factory)
+{
+}
+
+/**
+ * gal_a11y_e_text_factory_get_type:
+ * @void:
+ *
+ * Registers the &GalA11yETextFactory class if necessary, and returns the type ID
+ * associated to it.
+ *
+ * Return value: The type ID of the &GalA11yETextFactory class.
+ **/
+GType
+gal_a11y_e_text_factory_get_type (void)
+{
+	static GType type = 0;
+
+	if (!type) {
+		GTypeInfo info = {
+			sizeof (GalA11yETextFactoryClass),
+			(GBaseInitFunc) NULL,
+			(GBaseFinalizeFunc) NULL,
+			(GClassInitFunc) gal_a11y_e_text_factory_class_init,
+			(GClassFinalizeFunc) NULL,
+			NULL, /* class_data */
+			sizeof (GalA11yETextFactory),
+			0,
+			(GInstanceInitFunc) gal_a11y_e_text_factory_init,
+			NULL /* value_text */
+		};
+
+		type = g_type_register_static (PARENT_TYPE, "GalA11yETextFactory", &info, 0);
+	}
+
+	return type;
+}
diff --git a/e-util/gal-a11y-e-text-factory.h b/e-util/gal-a11y-e-text-factory.h
new file mode 100644
index 0000000..4647fe3
--- /dev/null
+++ b/e-util/gal-a11y-e-text-factory.h
@@ -0,0 +1,52 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Christopher James Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef __GAL_A11Y_E_TEXT_FACTORY_H__
+#define __GAL_A11Y_E_TEXT_FACTORY_H__
+
+#include <atk/atkobjectfactory.h>
+
+#define GAL_A11Y_TYPE_E_TEXT_FACTORY            (gal_a11y_e_text_factory_get_type ())
+#define GAL_A11Y_E_TEXT_FACTORY(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GAL_A11Y_TYPE_E_TEXT_FACTORY, GalA11yETextFactory))
+#define GAL_A11Y_E_TEXT_FACTORY_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GAL_A11Y_TYPE_E_TEXT_FACTORY, GalA11yETextFactoryClass))
+#define GAL_A11Y_IS_E_TEXT_FACTORY(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GAL_A11Y_TYPE_E_TEXT_FACTORY))
+#define GAL_A11Y_IS_E_TEXT_FACTORY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GAL_A11Y_TYPE_E_TEXT_FACTORY))
+
+typedef struct _GalA11yETextFactory GalA11yETextFactory;
+typedef struct _GalA11yETextFactoryClass GalA11yETextFactoryClass;
+
+struct _GalA11yETextFactory {
+	AtkObjectFactory object;
+};
+
+struct _GalA11yETextFactoryClass {
+	AtkObjectFactoryClass parent_class;
+};
+
+/* Standard Glib function */
+GType              gal_a11y_e_text_factory_get_type         (void);
+
+#endif /* __GAL_A11Y_E_TEXT_FACTORY_H__ */
diff --git a/e-util/gal-a11y-e-text.c b/e-util/gal-a11y-e-text.c
new file mode 100644
index 0000000..9b03a78
--- /dev/null
+++ b/e-util/gal-a11y-e-text.c
@@ -0,0 +1,1141 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Christopher James Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "gal-a11y-e-text.h"
+
+#include <string.h>
+
+#include <gtk/gtk.h>
+
+#include "e-text.h"
+#include "e-text-model-repos.h"
+#include "gal-a11y-e-text-factory.h"
+#include "gal-a11y-util.h"
+
+static GObjectClass *parent_class;
+static AtkComponentIface *component_parent_iface;
+static GType parent_type;
+static gint priv_offset;
+static GQuark		quark_accessible_object = 0;
+#define PARENT_TYPE (parent_type)
+
+struct _GalA11yETextPrivate {
+	gint dummy;
+};
+
+static void
+et_dispose (GObject *object)
+{
+	if (parent_class->dispose)
+		parent_class->dispose (object);
+}
+
+/* Static functions */
+
+static void
+et_get_extents (AtkComponent *component,
+                gint *x,
+                gint *y,
+                gint *width,
+                gint *height,
+                AtkCoordType coord_type)
+{
+	EText *item = E_TEXT (atk_gobject_accessible_get_object (
+		ATK_GOBJECT_ACCESSIBLE (component)));
+	gdouble real_width;
+	gdouble real_height;
+	gint fake_width;
+	gint fake_height;
+
+	if (component_parent_iface &&
+	    component_parent_iface->get_extents)
+		component_parent_iface->get_extents (component,
+						     x,
+						     y,
+						     &fake_width,
+						     &fake_height,
+						     coord_type);
+
+	g_object_get (
+		item,
+		"text_width", &real_width,
+		"text_height", &real_height,
+		NULL);
+
+	if (width)
+		*width = real_width;
+	if (height)
+		*height = real_height;
+}
+
+static const gchar *
+et_get_full_text (AtkText *text)
+{
+	GObject *obj;
+	EText *etext;
+	ETextModel *model;
+	const gchar *full_text;
+
+	obj = atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (text));
+	if (obj == NULL)
+		return "";
+
+	etext = E_TEXT (obj);
+	g_object_get (etext, "model", &model, NULL);
+
+	full_text = e_text_model_get_text (model);
+
+	return full_text;
+}
+
+static void
+et_set_full_text (AtkEditableText *text,
+                  const gchar *full_text)
+{
+	GObject *obj;
+	EText *etext;
+	ETextModel *model;
+
+	obj = atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (text));
+	if (obj == NULL)
+		return;
+
+	etext = E_TEXT (obj);
+	g_object_get (etext, "model", &model, NULL);
+
+	e_text_model_set_text (model, full_text);
+}
+
+static gchar *
+et_get_text (AtkText *text,
+             gint start_offset,
+             gint end_offset)
+{
+	gint start, end, real_start, real_end, len;
+	const gchar *full_text = et_get_full_text (text);
+	if (full_text == NULL)
+		return NULL;
+	len = g_utf8_strlen (full_text, -1);
+
+	start = MIN (MAX (0, start_offset), len);
+	end = MIN (MAX (-1, end_offset), len);
+
+	if (end_offset == -1)
+		end = strlen (full_text);
+	else
+		end = g_utf8_offset_to_pointer (full_text, end) - full_text;
+
+	start = g_utf8_offset_to_pointer (full_text, start) - full_text;
+
+	real_start = MIN (start, end);
+	real_end = MAX (start, end);
+
+	return g_strndup (full_text + real_start, real_end - real_start);
+}
+
+static gboolean
+is_a_seperator (gunichar c)
+{
+	return g_unichar_ispunct (c) || g_unichar_isspace (c);
+}
+
+static gint
+find_word_start (const gchar *text,
+                 gint begin_offset,
+                 gint step)
+{
+	gint offset;
+	gchar *at_offset;
+	gunichar current, previous;
+	gint len;
+
+	offset = begin_offset;
+	len = g_utf8_strlen (text, -1);
+
+	while (offset > 0 && offset < len) {
+		at_offset = g_utf8_offset_to_pointer (text, offset);
+		current = g_utf8_get_char_validated (at_offset, -1);
+		at_offset = g_utf8_offset_to_pointer (text, offset - 1);
+		previous = g_utf8_get_char_validated (at_offset, -1);
+		if ((!is_a_seperator (current)) && is_a_seperator (previous))
+			break;
+		offset += step;
+	}
+
+	return offset;
+}
+
+static gint
+find_word_end (const gchar *text,
+               gint begin_offset,
+               gint step)
+{
+	gint offset;
+	gchar *at_offset;
+	gunichar current, previous;
+	gint len;
+
+	offset = begin_offset;
+	len = g_utf8_strlen (text, -1);
+
+	while (offset > 0 && offset < len) {
+		at_offset = g_utf8_offset_to_pointer (text, offset);
+		current = g_utf8_get_char_validated (at_offset, -1);
+		at_offset = g_utf8_offset_to_pointer (text, offset - 1);
+		previous = g_utf8_get_char_validated (at_offset, -1);
+		if (is_a_seperator (current) && (!is_a_seperator (previous)))
+			break;
+		offset += step;
+	}
+
+	return offset;
+}
+
+static gint
+find_sentence_start (const gchar *text,
+                     gint begin_offset,
+                     gint step)
+{
+	gint offset, last_word_end, len;
+	gchar *at_offset;
+	gunichar ch;
+	gint i;
+
+	offset = find_word_start (text, begin_offset, step);
+	len = g_utf8_strlen (text, -1);
+
+	while (offset > 0 && offset <len) {
+		last_word_end = find_word_end (text, offset - 1, -1);
+		if (last_word_end == 0)
+			break;
+		for (i = last_word_end; i < offset; i++) {
+			at_offset = g_utf8_offset_to_pointer (text, i);
+			ch = g_utf8_get_char_validated (at_offset, -1);
+			if (ch == '.' || ch == '!' || ch == '?')
+				return offset;
+		}
+
+		offset = find_word_start (text, offset + step, step);
+	}
+
+	return offset;
+}
+
+static gint
+find_sentence_end (const gchar *text,
+                   gint begin_offset,
+                   gint step)
+{
+	gint offset;
+	gchar *at_offset;
+	gunichar previous;
+	gint len;
+
+	offset = begin_offset;
+	len = g_utf8_strlen (text, -1);
+
+	while (offset > 0 && offset < len) {
+		at_offset = g_utf8_offset_to_pointer (text, offset - 1);
+		previous = g_utf8_get_char_validated (at_offset, -1);
+		if (previous == '.' || previous == '!' || previous == '?')
+			break;
+		offset += step;
+	}
+
+	return offset;
+}
+
+static gint
+find_line_start (const gchar *text,
+                     gint begin_offset,
+                     gint step)
+{
+	gint offset;
+	gchar *at_offset;
+	gunichar previous;
+	gint len;
+
+	offset = begin_offset;
+	len = g_utf8_strlen (text, -1);
+
+	while (offset > 0 && offset < len) {
+		at_offset = g_utf8_offset_to_pointer (text, offset - 1);
+		previous = g_utf8_get_char_validated (at_offset, -1);
+		if (previous == '\n' || previous == '\r')
+			break;
+		offset += step;
+	}
+
+	return offset;
+}
+
+static gint
+find_line_end (const gchar *text,
+                     gint begin_offset,
+                     gint step)
+{
+	gint offset;
+	gchar *at_offset;
+	gunichar current;
+	gint len;
+
+	offset = begin_offset;
+	len = g_utf8_strlen (text, -1);
+
+	while (offset >= 0 && offset < len) {
+		at_offset = g_utf8_offset_to_pointer (text, offset);
+		current = g_utf8_get_char_validated (at_offset, -1);
+		if (current == '\n' || current == '\r')
+			break;
+		offset += step;
+	}
+
+	return offset;
+}
+
+static gchar *
+et_get_text_after_offset (AtkText *text,
+                          gint offset,
+                          AtkTextBoundary boundary_type,
+                          gint *start_offset,
+                          gint *end_offset)
+{
+	gint start, end, len;
+	const gchar *full_text = et_get_full_text (text);
+	g_return_val_if_fail (full_text, NULL);
+
+	switch (boundary_type)
+	{
+	case ATK_TEXT_BOUNDARY_CHAR:
+		start = offset + 1;
+		end = offset + 2;
+		break;
+	case ATK_TEXT_BOUNDARY_WORD_START:
+		start = find_word_start (full_text, offset + 1, 1);
+		end = find_word_start (full_text, start + 1, 1);
+		break;
+	case ATK_TEXT_BOUNDARY_WORD_END:
+		start = find_word_end (full_text, offset + 1, 1);
+		end = find_word_end (full_text, start + 1, 1);
+		break;
+	case ATK_TEXT_BOUNDARY_SENTENCE_START:
+		start = find_sentence_start (full_text, offset + 1, 1);
+		end = find_sentence_start (full_text, start + 1, 1);
+		break;
+	case ATK_TEXT_BOUNDARY_SENTENCE_END:
+		start = find_sentence_end (full_text, offset + 1, 1);
+		end = find_sentence_end (full_text, start + 1, 1);
+		break;
+	case ATK_TEXT_BOUNDARY_LINE_START:
+		start = find_line_start (full_text, offset + 1, 1);
+		end = find_line_start (full_text, start + 1, 1);
+		break;
+	case ATK_TEXT_BOUNDARY_LINE_END:
+		start = find_line_end (full_text, offset + 1, 1);
+		end = find_line_end (full_text, start + 1, 1);
+		break;
+	default:
+		return NULL;
+	}
+
+	len = g_utf8_strlen (full_text, -1);
+	if (start_offset)
+		*start_offset = MIN (MAX (0, start), len);
+	if (end_offset)
+		*end_offset = MIN (MAX (0, end), len);
+	return et_get_text (text, start, end);
+}
+
+static gchar *
+et_get_text_at_offset (AtkText *text,
+                       gint offset,
+                       AtkTextBoundary boundary_type,
+                       gint *start_offset,
+                       gint *end_offset)
+{
+	gint start, end, len;
+	const gchar *full_text = et_get_full_text (text);
+	g_return_val_if_fail (full_text, NULL);
+
+	switch (boundary_type)
+	{
+	case ATK_TEXT_BOUNDARY_CHAR:
+		start = offset;
+		end = offset + 1;
+		break;
+	case ATK_TEXT_BOUNDARY_WORD_START:
+		start = find_word_start (full_text, offset - 1, -1);
+		end = find_word_start (full_text, offset, 1);
+		break;
+	case ATK_TEXT_BOUNDARY_WORD_END:
+		start = find_word_end (full_text, offset, -1);
+		end = find_word_end (full_text, offset + 1, 1);
+		break;
+	case ATK_TEXT_BOUNDARY_SENTENCE_START:
+		start = find_sentence_start (full_text, offset - 1, -1);
+		end = find_sentence_start (full_text, offset, 1);
+		break;
+	case ATK_TEXT_BOUNDARY_SENTENCE_END:
+		start = find_sentence_end (full_text, offset, -1);
+		end = find_sentence_end (full_text, offset + 1, 1);
+		break;
+	case ATK_TEXT_BOUNDARY_LINE_START:
+		start = find_line_start (full_text, offset - 1, -1);
+		end = find_line_start (full_text, offset, 1);
+		break;
+	case ATK_TEXT_BOUNDARY_LINE_END:
+		start = find_line_end (full_text, offset, -1);
+		end = find_line_end (full_text, offset + 1, 1);
+		break;
+	default:
+		return NULL;
+	}
+
+	len = g_utf8_strlen (full_text, -1);
+	if (start_offset)
+		*start_offset = MIN (MAX (0, start), len);
+	if (end_offset)
+		*end_offset = MIN (MAX (0, end), len);
+	return et_get_text (text, start, end);
+}
+
+static gunichar
+et_get_character_at_offset (AtkText *text,
+                            gint offset)
+{
+	const gchar *full_text = et_get_full_text (text);
+	gchar *at_offset;
+
+	at_offset = g_utf8_offset_to_pointer (full_text, offset);
+	return g_utf8_get_char_validated (at_offset, -1);
+}
+
+static gchar *
+et_get_text_before_offset (AtkText *text,
+                           gint offset,
+                           AtkTextBoundary boundary_type,
+                           gint *start_offset,
+                           gint *end_offset)
+{
+	gint start, end, len;
+	const gchar *full_text = et_get_full_text (text);
+	g_return_val_if_fail (full_text, NULL);
+
+	switch (boundary_type)
+	{
+	case ATK_TEXT_BOUNDARY_CHAR:
+		start = offset - 1;
+		end = offset;
+		break;
+	case ATK_TEXT_BOUNDARY_WORD_START:
+		end = find_word_start (full_text, offset - 1, -1);
+		start = find_word_start (full_text, end - 1, -1);
+		break;
+	case ATK_TEXT_BOUNDARY_WORD_END:
+		end = find_word_end (full_text, offset, -1);
+		start = find_word_end (full_text, end - 1, -1);
+		break;
+	case ATK_TEXT_BOUNDARY_SENTENCE_START:
+		end = find_sentence_start (full_text, offset, -1);
+		start = find_sentence_start (full_text, end - 1, -1);
+		break;
+	case ATK_TEXT_BOUNDARY_SENTENCE_END:
+		end = find_sentence_end (full_text, offset, -1);
+		start = find_sentence_end (full_text, end - 1, -1);
+		break;
+	case ATK_TEXT_BOUNDARY_LINE_START:
+		end = find_line_start (full_text, offset, -1);
+		start = find_line_start (full_text, end - 1, -1);
+		break;
+	case ATK_TEXT_BOUNDARY_LINE_END:
+		end = find_line_end (full_text, offset, -1);
+		start = find_line_end (full_text, end - 1, -1);
+		break;
+	default:
+		return NULL;
+	}
+
+	len = g_utf8_strlen (full_text, -1);
+	if (start_offset)
+		*start_offset = MIN (MAX (0, start), len);
+	if (end_offset)
+		*end_offset = MIN (MAX (0, end), len);
+	return et_get_text (text, start, end);
+}
+
+static gint
+et_get_caret_offset (AtkText *text)
+{
+	GObject *obj;
+	EText *etext;
+	gint offset;
+
+	g_return_val_if_fail (ATK_IS_GOBJECT_ACCESSIBLE (text), -1);
+	obj = atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (text));
+	if (obj == NULL)
+		return -1;
+
+	g_return_val_if_fail (E_IS_TEXT (obj), -1);
+	etext = E_TEXT (obj);
+
+	g_object_get (etext, "cursor_pos", &offset, NULL);
+	return offset;
+}
+
+static AtkAttributeSet *
+et_get_run_attributes (AtkText *text,
+                       gint offset,
+                       gint *start_offset,
+                       gint *end_offset)
+{
+	/* Unimplemented */
+	return NULL;
+}
+
+static AtkAttributeSet *
+et_get_default_attributes (AtkText *text)
+{
+	/* Unimplemented */
+	return NULL;
+}
+
+static void
+et_get_character_extents (AtkText *text,
+                          gint offset,
+                          gint *x,
+                          gint *y,
+                          gint *width,
+                          gint *height,
+                          AtkCoordType coords)
+{
+	GObject *obj;
+	EText *etext;
+	GnomeCanvas *canvas;
+	gint x_widget, y_widget, x_window, y_window;
+	GdkWindow *window;
+	GtkWidget *widget;
+	PangoRectangle pango_pos;
+
+	g_return_if_fail (ATK_IS_GOBJECT_ACCESSIBLE (text));
+	obj = atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (text));
+	if (obj == NULL)
+	    return;
+	g_return_if_fail (E_IS_TEXT (obj));
+	etext = E_TEXT (obj);
+	canvas = GNOME_CANVAS_ITEM (etext)->canvas;
+	widget = GTK_WIDGET (canvas);
+	window = gtk_widget_get_window (widget);
+	gdk_window_get_origin (window, &x_widget, &y_widget);
+
+	pango_layout_index_to_pos (etext->layout, offset, &pango_pos);
+	pango_pos.x = PANGO_PIXELS (pango_pos.x);
+	pango_pos.y = PANGO_PIXELS (pango_pos.y);
+	pango_pos.width = (pango_pos.width + PANGO_SCALE / 2) / PANGO_SCALE;
+	pango_pos.height = (pango_pos.height + PANGO_SCALE / 2) / PANGO_SCALE;
+
+        *x = pango_pos.x + x_widget;
+        *y = pango_pos.y + y_widget;
+
+	*width  = pango_pos.width;
+	*height = pango_pos.height;
+
+        *x += etext->xofs;
+        *y += etext->yofs;
+
+	if (etext->editing) {
+                *x -= etext->xofs_edit;
+                *y -= etext->yofs_edit;
+	}
+
+        *x += etext->cx;
+        *y += etext->cy;
+
+	if (coords == ATK_XY_WINDOW) {
+		window = gdk_window_get_toplevel (window);
+		gdk_window_get_origin (window, &x_window, &y_window);
+		*x -= x_window;
+		*y -= y_window;
+	}
+	else if (coords == ATK_XY_SCREEN) {
+	}
+	else {
+		*x = 0;
+		*y = 0;
+		*height = 0;
+		*width = 0;
+	}
+}
+
+static gint
+et_get_character_count (AtkText *text)
+{
+	const gchar *full_text = et_get_full_text (text);
+
+	return g_utf8_strlen (full_text, -1);
+}
+
+static gint
+et_get_offset_at_point (AtkText *text,
+                        gint x,
+                        gint y,
+                        AtkCoordType coords)
+{
+	GObject *obj;
+	EText *etext;
+	GnomeCanvas *canvas;
+	gint x_widget, y_widget, x_window, y_window;
+	GdkWindow *window;
+	GtkWidget *widget;
+	gint index;
+	gint trailing;
+
+	g_return_val_if_fail (ATK_IS_GOBJECT_ACCESSIBLE (text), -1);
+	obj = atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (text));
+	if (obj == NULL)
+	    return -1;
+	g_return_val_if_fail (E_IS_TEXT (obj), -1);
+	etext = E_TEXT (obj);
+	canvas = GNOME_CANVAS_ITEM (etext)->canvas;
+	widget = GTK_WIDGET (canvas);
+	window = gtk_widget_get_window (widget);
+	gdk_window_get_origin (window, &x_widget, &y_widget);
+
+	if (coords == ATK_XY_SCREEN) {
+		x = x - x_widget;
+		y = y - y_widget;
+	}
+	else if (coords == ATK_XY_WINDOW) {
+		window = gdk_window_get_toplevel (window);
+		gdk_window_get_origin (window, &x_window, &y_window);
+		x = x - x_widget + x_window;
+		y = y - y_widget + y_window;
+	}
+	else
+		return -1;
+
+	x -= etext->xofs;
+	y -= etext->yofs;
+
+	if (etext->editing) {
+		x += etext->xofs_edit;
+		y += etext->yofs_edit;
+	}
+
+	x -= etext->cx;
+	y -= etext->cy;
+
+	pango_layout_xy_to_index (
+		etext->layout,
+		x * PANGO_SCALE - PANGO_SCALE / 2,
+		y * PANGO_SCALE - PANGO_SCALE / 2,
+		&index,
+		&trailing);
+
+	return g_utf8_pointer_to_offset (etext->text, etext->text + index + trailing);
+}
+
+static gint
+et_get_n_selections (AtkText *text)
+{
+	EText *etext;
+	GObject *obj =  atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (text));
+
+	if (obj == NULL)
+		return -1;
+	etext = E_TEXT (obj);
+
+	if (etext->selection_start !=
+	    etext->selection_end)
+		return 1;
+	return 0;
+}
+
+static gchar *
+et_get_selection (AtkText *text,
+                  gint selection_num,
+                  gint *start_offset,
+                  gint *end_offset)
+{
+	gint start, end, real_start, real_end, len;
+	EText *etext;
+	if (selection_num == 0) {
+		const gchar *full_text = et_get_full_text (text);
+		if (full_text == NULL)
+			return NULL;
+		len = g_utf8_strlen (full_text, -1);
+		etext = E_TEXT (atk_gobject_accessible_get_object (
+			ATK_GOBJECT_ACCESSIBLE (text)));
+		start = MIN (etext->selection_start, etext->selection_end);
+		end = MAX (etext->selection_start, etext->selection_end);
+		start = MIN (MAX (0, start), len);
+		end = MIN (MAX (0, end), len);
+		if (start != end) {
+			if (start_offset)
+				*start_offset = start;
+			if (end_offset)
+				*end_offset = end;
+			real_start = g_utf8_offset_to_pointer (full_text, start) - full_text;
+			real_end = g_utf8_offset_to_pointer (full_text, end) - full_text;
+			return g_strndup (full_text + real_start, real_end - real_start);
+		}
+	}
+
+	return NULL;
+}
+
+static gboolean
+et_add_selection (AtkText *text,
+                  gint start_offset,
+                  gint end_offset)
+{
+	GObject *obj;
+	EText *etext;
+
+	g_return_val_if_fail (ATK_IS_GOBJECT_ACCESSIBLE (text), FALSE);
+	obj = atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (text));
+	if (obj == NULL)
+		return FALSE;
+	g_return_val_if_fail (E_IS_TEXT (obj), FALSE);
+	etext = E_TEXT (obj);
+
+	g_return_val_if_fail (start_offset >= 0, FALSE);
+	g_return_val_if_fail (start_offset >= -1, FALSE);
+	if (end_offset == -1)
+		end_offset = et_get_character_count (text);
+
+	if (start_offset != end_offset) {
+		gint real_start, real_end;
+		real_start = MIN (start_offset, end_offset);
+		real_end = MAX (start_offset, end_offset);
+		etext->selection_start = real_start;
+		etext->selection_end = real_end;
+
+		gnome_canvas_item_grab_focus (GNOME_CANVAS_ITEM (etext));
+		gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (etext));
+
+		g_signal_emit_by_name (ATK_OBJECT (text), "text_selection_changed");
+
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+static gboolean
+et_remove_selection (AtkText *text,
+                     gint selection_num)
+{
+	GObject *obj;
+	EText *etext;
+
+	g_return_val_if_fail (ATK_IS_GOBJECT_ACCESSIBLE (text), FALSE);
+	obj = atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (text));
+	if (obj == NULL)
+		return FALSE;
+	g_return_val_if_fail (E_IS_TEXT (obj), FALSE);
+	etext = E_TEXT (obj);
+
+	if (selection_num == 0
+	    && etext->selection_start != etext->selection_end) {
+		etext->selection_end = etext->selection_start;
+		g_signal_emit_by_name (ATK_OBJECT (text), "text_selection_changed");
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+static gboolean
+et_set_selection (AtkText *text,
+                  gint selection_num,
+                  gint start_offset,
+                  gint end_offset)
+{
+	GObject *obj;
+
+	g_return_val_if_fail (ATK_IS_GOBJECT_ACCESSIBLE (text), FALSE);
+	obj = atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (text));
+	if (obj == NULL)
+		return FALSE;
+	g_return_val_if_fail (E_IS_TEXT (obj), FALSE);
+	if (selection_num == 0)
+		return et_add_selection (text, start_offset, end_offset);
+	return FALSE;
+}
+
+static gboolean
+et_set_caret_offset (AtkText *text,
+                     gint offset)
+{
+	GObject *obj;
+	EText *etext;
+
+	g_return_val_if_fail (ATK_IS_GOBJECT_ACCESSIBLE (text), FALSE);
+	obj = atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (text));
+	if (obj == NULL)
+		return FALSE;
+
+	g_return_val_if_fail (E_IS_TEXT (obj), FALSE);
+	etext = E_TEXT (obj);
+
+	if (offset < -1)
+		return FALSE;
+	else {
+		ETextEventProcessorCommand command;
+
+		if (offset == -1)
+			offset = et_get_character_count (text);
+
+		command.action = E_TEP_MOVE;
+		command.position = E_TEP_VALUE;
+		command.value = offset;
+		command.time = GDK_CURRENT_TIME;
+		g_signal_emit_by_name (etext->tep, "command", &command);
+		return TRUE;
+	}
+}
+
+static gboolean
+et_set_run_attributes (AtkEditableText *text,
+                       AtkAttributeSet *attrib_set,
+                       gint start_offset,
+                       gint end_offset)
+{
+	/* Unimplemented */
+	return FALSE;
+}
+
+static void
+et_set_text_contents (AtkEditableText *text,
+                      const gchar *string)
+{
+	et_set_full_text (text, string);
+}
+
+static void
+et_insert_text (AtkEditableText *text,
+                const gchar *string,
+                gint length,
+                gint *position)
+{
+	/* Utf8 unimplemented */
+	gchar *result;
+
+	const gchar *full_text = et_get_full_text (ATK_TEXT (text));
+	if (full_text == NULL)
+		return;
+
+	result = g_strdup_printf (
+		"%.*s%.*s%s", *position, full_text,
+		length, string, full_text + *position);
+
+	et_set_full_text (text, result);
+
+	*position += length;
+
+	g_free (result);
+}
+
+static void
+et_copy_text (AtkEditableText *text,
+              gint start_pos,
+              gint end_pos)
+{
+	GObject *obj;
+	EText *etext;
+
+	g_return_if_fail (ATK_IS_GOBJECT_ACCESSIBLE (text));
+	obj = atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (text));
+	if (obj == NULL)
+		return;
+
+	g_return_if_fail (E_IS_TEXT (obj));
+	etext = E_TEXT (obj);
+
+	if (start_pos != end_pos) {
+		etext->selection_start = start_pos;
+		etext->selection_end = end_pos;
+		e_text_copy_clipboard (etext);
+	}
+}
+
+static void
+et_delete_text (AtkEditableText *text,
+                gint start_pos,
+                gint end_pos)
+{
+	GObject *obj;
+	EText *etext;
+
+	g_return_if_fail (ATK_IS_GOBJECT_ACCESSIBLE (text));
+	obj = atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (text));
+	if (obj == NULL)
+		return;
+
+	g_return_if_fail (E_IS_TEXT (obj));
+	etext = E_TEXT (obj);
+
+	etext->selection_start = start_pos;
+	etext->selection_end = end_pos;
+
+	e_text_delete_selection (etext);
+}
+
+static void
+et_cut_text (AtkEditableText *text,
+             gint start_pos,
+             gint end_pos)
+{
+	et_copy_text (text, start_pos, end_pos);
+	et_delete_text (text, start_pos, end_pos);
+}
+
+static void
+et_paste_text (AtkEditableText *text,
+               gint position)
+{
+	GObject *obj;
+	EText *etext;
+
+	g_return_if_fail (ATK_IS_GOBJECT_ACCESSIBLE (text));
+	obj = atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (text));
+	if (obj == NULL)
+		return;
+
+	g_return_if_fail (E_IS_TEXT (obj));
+	etext = E_TEXT (obj);
+
+	g_object_set (etext, "cursor_pos", position, NULL);
+	e_text_paste_clipboard (etext);
+}
+
+static void
+et_atk_component_iface_init (AtkComponentIface *iface)
+{
+	iface->get_extents = et_get_extents;
+}
+
+static void
+et_atk_text_iface_init (AtkTextIface *iface)
+{
+	iface->get_text                = et_get_text;
+	iface->get_text_after_offset   = et_get_text_after_offset;
+	iface->get_text_at_offset      = et_get_text_at_offset;
+	iface->get_character_at_offset = et_get_character_at_offset;
+	iface->get_text_before_offset  = et_get_text_before_offset;
+	iface->get_caret_offset        = et_get_caret_offset;
+	iface->get_run_attributes      = et_get_run_attributes;
+	iface->get_default_attributes  = et_get_default_attributes;
+	iface->get_character_extents   = et_get_character_extents;
+	iface->get_character_count     = et_get_character_count;
+	iface->get_offset_at_point     = et_get_offset_at_point;
+	iface->get_n_selections        = et_get_n_selections;
+	iface->get_selection           = et_get_selection;
+	iface->add_selection           = et_add_selection;
+	iface->remove_selection        = et_remove_selection;
+	iface->set_selection           = et_set_selection;
+	iface->set_caret_offset        = et_set_caret_offset;
+}
+
+static void
+et_atk_editable_text_iface_init (AtkEditableTextIface *iface)
+{
+	iface->set_run_attributes = et_set_run_attributes;
+	iface->set_text_contents  = et_set_text_contents;
+	iface->insert_text        = et_insert_text;
+	iface->copy_text          = et_copy_text;
+	iface->cut_text           = et_cut_text;
+	iface->delete_text        = et_delete_text;
+	iface->paste_text         = et_paste_text;
+}
+
+static void
+_et_reposition_cb (ETextModel *model,
+                   ETextModelReposFn fn,
+                   gpointer repos_data,
+                   gpointer user_data)
+{
+	AtkObject *accessible;
+	AtkText *text;
+
+	accessible = ATK_OBJECT (user_data);
+	text = ATK_TEXT (accessible);
+
+	if (fn == e_repos_delete_shift) {
+		EReposDeleteShift *info = (EReposDeleteShift *) repos_data;
+		g_signal_emit_by_name (text, "text-changed::delete", info->pos, info->len);
+	}
+	else if (fn == e_repos_insert_shift) {
+		EReposInsertShift *info = (EReposInsertShift *) repos_data;
+		g_signal_emit_by_name (text, "text-changed::insert", info->pos, info->len);
+	}
+}
+
+static void
+_et_command_cb (ETextEventProcessor *tep,
+                ETextEventProcessorCommand *command,
+                gpointer user_data)
+{
+	AtkObject *accessible;
+	AtkText *text;
+
+	accessible = ATK_OBJECT (user_data);
+	text = ATK_TEXT (accessible);
+
+	switch (command->action) {
+	case E_TEP_MOVE:
+		g_signal_emit_by_name (text, "text-caret-moved", et_get_caret_offset (text));
+		break;
+	case E_TEP_SELECT:
+		g_signal_emit_by_name (text, "text-selection-changed");
+		break;
+	default:
+		break;
+	}
+}
+
+static void
+et_real_initialize (AtkObject *obj,
+                    gpointer data)
+{
+	EText *etext;
+
+	ATK_OBJECT_CLASS (parent_class)->initialize (obj, data);
+
+	g_return_if_fail (GAL_A11Y_IS_E_TEXT (obj));
+	g_return_if_fail (E_IS_TEXT (data));
+
+	etext = E_TEXT (data);
+
+	/* Set up signal callbacks */
+	g_signal_connect (
+		etext->model, "reposition",
+		G_CALLBACK (_et_reposition_cb), obj);
+
+	if (etext->tep)
+		g_signal_connect_after (
+			etext->tep, "command",
+			(GCallback) _et_command_cb, obj);
+
+	obj->role = ATK_ROLE_TEXT;
+}
+
+static void
+et_class_init (GalA11yETextClass *class)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (class);
+	AtkObjectClass *atk_class = ATK_OBJECT_CLASS (class);
+
+	quark_accessible_object =
+		g_quark_from_static_string ("gtk-accessible-object");
+	parent_class = g_type_class_ref (PARENT_TYPE);
+	component_parent_iface =
+		g_type_interface_peek (parent_class, ATK_TYPE_COMPONENT);
+	object_class->dispose = et_dispose;
+	atk_class->initialize = et_real_initialize;
+}
+
+static void
+et_init (GalA11yEText *a11y)
+{
+}
+
+/**
+ * gal_a11y_e_text_get_type:
+ * @void:
+ *
+ * Registers the &GalA11yEText class if necessary, and returns the type ID
+ * associated to it.
+ *
+ * Return value: The type ID of the &GalA11yEText class.
+ **/
+GType
+gal_a11y_e_text_get_type (void)
+{
+	static GType type = 0;
+
+	if (!type) {
+		AtkObjectFactory *factory;
+
+		GTypeInfo info = {
+			sizeof (GalA11yETextClass),
+			(GBaseInitFunc) NULL,
+			(GBaseFinalizeFunc) NULL,
+			(GClassInitFunc) et_class_init,
+			(GClassFinalizeFunc) NULL,
+			NULL, /* class_data */
+			sizeof (GalA11yEText),
+			0,
+			(GInstanceInitFunc) et_init,
+			NULL /* value_text */
+		};
+
+		static const GInterfaceInfo atk_component_info = {
+			(GInterfaceInitFunc) et_atk_component_iface_init,
+			(GInterfaceFinalizeFunc) NULL,
+			NULL
+		};
+		static const GInterfaceInfo atk_text_info = {
+			(GInterfaceInitFunc) et_atk_text_iface_init,
+			(GInterfaceFinalizeFunc) NULL,
+			NULL
+		};
+		static const GInterfaceInfo atk_editable_text_info = {
+			(GInterfaceInitFunc) et_atk_editable_text_iface_init,
+			(GInterfaceFinalizeFunc) NULL,
+			NULL
+		};
+
+		factory = atk_registry_get_factory (
+			atk_get_default_registry (), GNOME_TYPE_CANVAS_ITEM);
+		parent_type = atk_object_factory_get_accessible_type (factory);
+
+		type = gal_a11y_type_register_static_with_private (
+			PARENT_TYPE, "GalA11yEText", &info, 0,
+			sizeof (GalA11yETextPrivate), &priv_offset);
+
+		g_type_add_interface_static (
+			type, ATK_TYPE_COMPONENT, &atk_component_info);
+		g_type_add_interface_static (
+			type, ATK_TYPE_TEXT, &atk_text_info);
+		g_type_add_interface_static (
+			type, ATK_TYPE_EDITABLE_TEXT, &atk_editable_text_info);
+	}
+
+	return type;
+}
+
+void
+gal_a11y_e_text_init (void)
+{
+	if (atk_get_root ())
+		atk_registry_set_factory_type (
+			atk_get_default_registry (),
+			E_TYPE_TEXT,
+			gal_a11y_e_text_factory_get_type ());
+
+}
+
diff --git a/e-util/gal-a11y-e-text.h b/e-util/gal-a11y-e-text.h
new file mode 100644
index 0000000..22ebe09
--- /dev/null
+++ b/e-util/gal-a11y-e-text.h
@@ -0,0 +1,59 @@
+/*
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Christopher James Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef __GAL_A11Y_E_TEXT_H__
+#define __GAL_A11Y_E_TEXT_H__
+
+#include <atk/atk.h>
+
+#define GAL_A11Y_TYPE_E_TEXT            (gal_a11y_e_text_get_type ())
+#define GAL_A11Y_E_TEXT(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GAL_A11Y_TYPE_E_TEXT, GalA11yEText))
+#define GAL_A11Y_E_TEXT_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GAL_A11Y_TYPE_E_TEXT, GalA11yETextClass))
+#define GAL_A11Y_IS_E_TEXT(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GAL_A11Y_TYPE_E_TEXT))
+#define GAL_A11Y_IS_E_TEXT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GAL_A11Y_TYPE_E_TEXT))
+
+typedef struct _GalA11yEText GalA11yEText;
+typedef struct _GalA11yETextClass GalA11yETextClass;
+typedef struct _GalA11yETextPrivate GalA11yETextPrivate;
+
+/* This struct should actually be larger as this isn't what we derive from.
+ * The GalA11yETextPrivate comes right after the parent class structure.
+ **/
+struct _GalA11yEText {
+	AtkObject object;
+};
+
+struct _GalA11yETextClass {
+	AtkObject parent_class;
+};
+
+/* Standard Glib function */
+GType      gal_a11y_e_text_get_type  (void);
+
+void       gal_a11y_e_text_init (void);
+
+#endif /* __GAL_A11Y_E_TEXT_H__ */
diff --git a/widgets/table/gal-a11y-e-tree-factory.c b/e-util/gal-a11y-e-tree-factory.c
similarity index 100%
rename from widgets/table/gal-a11y-e-tree-factory.c
rename to e-util/gal-a11y-e-tree-factory.c
diff --git a/e-util/gal-a11y-e-tree-factory.h b/e-util/gal-a11y-e-tree-factory.h
new file mode 100644
index 0000000..5919ab2
--- /dev/null
+++ b/e-util/gal-a11y-e-tree-factory.h
@@ -0,0 +1,52 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Yuedong Du <yuedong du ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef __GAL_A11Y_E_TREE_FACTORY_H__
+#define __GAL_A11Y_E_TREE_FACTORY_H__
+
+#include <atk/atkobjectfactory.h>
+
+#define GAL_A11Y_TYPE_E_TREE_FACTORY            (gal_a11y_e_table_factory_get_type ())
+#define GAL_A11Y_E_TREE_FACTORY(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GAL_A11Y_TYPE_E_TREE_FACTORY, GalA11yETreeFactory))
+#define GAL_A11Y_E_TREE_FACTORY_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GAL_A11Y_TYPE_E_TREE_FACTORY, GalA11yETreeFactoryClass))
+#define GAL_A11Y_IS_E_TREE_FACTORY(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GAL_A11Y_TYPE_E_TREE_FACTORY))
+#define GAL_A11Y_IS_E_TREE_FACTORY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GAL_A11Y_TYPE_E_TREE_FACTORY))
+
+typedef struct _GalA11yETreeFactory GalA11yETreeFactory;
+typedef struct _GalA11yETreeFactoryClass GalA11yETreeFactoryClass;
+
+struct _GalA11yETreeFactory {
+	AtkObject object;
+};
+
+struct _GalA11yETreeFactoryClass {
+	AtkObjectClass parent_class;
+};
+
+/* Standard Glib function */
+GType              gal_a11y_e_tree_factory_get_type         (void);
+
+#endif /* __GAL_A11Y_E_TREE_FACTORY_H__ */
diff --git a/e-util/gal-a11y-e-tree.c b/e-util/gal-a11y-e-tree.c
new file mode 100644
index 0000000..52c34f3
--- /dev/null
+++ b/e-util/gal-a11y-e-tree.c
@@ -0,0 +1,196 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Yuedong Du <yuedong du sun com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "gal-a11y-e-tree.h"
+
+#include "e-table-item.h"
+#include "e-tree.h"
+#include "gal-a11y-e-table-item.h"
+#include "gal-a11y-e-tree-factory.h"
+#include "gal-a11y-util.h"
+
+#define CS_CLASS(a11y) (G_TYPE_INSTANCE_GET_CLASS ((a11y), C_TYPE_STREAM, GalA11yETreeClass))
+static AtkObjectClass *parent_class;
+static GType parent_type;
+static gint priv_offset;
+#define GET_PRIVATE(object) ((GalA11yETreePrivate *) (((gchar *) object) + priv_offset))
+#define PARENT_TYPE (parent_type)
+
+struct _GalA11yETreePrivate {
+	AtkObject *child_item;
+};
+
+/* Static functions */
+
+static void
+init_child_item (GalA11yETree *a11y)
+{
+	GalA11yETreePrivate *priv = GET_PRIVATE (a11y);
+	ETree *tree;
+	ETableItem * eti;
+
+	tree = E_TREE (gtk_accessible_get_widget (GTK_ACCESSIBLE (a11y)));
+	g_return_if_fail (tree);
+
+	eti = e_tree_get_item (tree);
+	if (priv->child_item == NULL) {
+		priv->child_item = atk_gobject_accessible_for_object (G_OBJECT (eti));
+	}
+}
+
+static AtkObject *
+et_ref_accessible_at_point (AtkComponent *component,
+                             gint x,
+                             gint y,
+                             AtkCoordType coord_type)
+{
+	GalA11yETree *a11y = GAL_A11Y_E_TREE (component);
+	init_child_item (a11y);
+	return GET_PRIVATE (a11y)->child_item;
+}
+
+static gint
+et_get_n_children (AtkObject *accessible)
+{
+	return 1;
+}
+
+static AtkObject *
+et_ref_child (AtkObject *accessible,
+              gint i)
+{
+	GalA11yETree *a11y = GAL_A11Y_E_TREE (accessible);
+	if (i != 0)
+		return NULL;
+	init_child_item (a11y);
+	g_object_ref (GET_PRIVATE (a11y)->child_item);
+	return GET_PRIVATE (a11y)->child_item;
+}
+
+static AtkLayer
+et_get_layer (AtkComponent *component)
+{
+	return ATK_LAYER_WIDGET;
+}
+
+static void
+et_class_init (GalA11yETreeClass *class)
+{
+	AtkObjectClass *atk_object_class = ATK_OBJECT_CLASS (class);
+
+	parent_class                              = g_type_class_ref (PARENT_TYPE);
+
+	atk_object_class->get_n_children          = et_get_n_children;
+	atk_object_class->ref_child               = et_ref_child;
+}
+
+static void
+et_atk_component_iface_init (AtkComponentIface *iface)
+{
+	iface->ref_accessible_at_point = et_ref_accessible_at_point;
+	iface->get_layer = et_get_layer;
+}
+
+static void
+et_init (GalA11yETree *a11y)
+{
+	GalA11yETreePrivate *priv;
+
+	priv = GET_PRIVATE (a11y);
+
+	priv->child_item = NULL;
+}
+
+/**
+ * gal_a11y_e_tree_get_type:
+ * @void:
+ *
+ * Registers the &GalA11yETree class if necessary, and returns the type ID
+ * associated to it.
+ *
+ * Return value: The type ID of the &GalA11yETree class.
+ **/
+GType
+gal_a11y_e_tree_get_type (void)
+{
+	static GType type = 0;
+
+	if (!type) {
+		AtkObjectFactory *factory;
+
+		GTypeInfo info = {
+			sizeof (GalA11yETreeClass),
+			(GBaseInitFunc) NULL,
+			(GBaseFinalizeFunc) NULL,
+			(GClassInitFunc) et_class_init,
+			(GClassFinalizeFunc) NULL,
+			NULL, /* class_data */
+			sizeof (GalA11yETree),
+			0,
+			(GInstanceInitFunc) et_init,
+			NULL /* value_tree */
+		};
+
+		static const GInterfaceInfo atk_component_info = {
+			(GInterfaceInitFunc) et_atk_component_iface_init,
+			(GInterfaceFinalizeFunc) NULL,
+			NULL
+		};
+
+		factory = atk_registry_get_factory (atk_get_default_registry (), GTK_TYPE_WIDGET);
+		parent_type = atk_object_factory_get_accessible_type (factory);
+
+		type = gal_a11y_type_register_static_with_private (
+			PARENT_TYPE, "GalA11yETree", &info, 0,
+			sizeof (GalA11yETreePrivate), &priv_offset);
+		g_type_add_interface_static (type, ATK_TYPE_COMPONENT, &atk_component_info);
+	}
+
+	return type;
+}
+
+AtkObject *
+gal_a11y_e_tree_new (GObject *widget)
+{
+	GalA11yETree *a11y;
+
+	a11y = g_object_new (gal_a11y_e_tree_get_type (), NULL);
+
+	gtk_accessible_set_widget (GTK_ACCESSIBLE (a11y), GTK_WIDGET (widget));
+
+	return ATK_OBJECT (a11y);
+}
+
+void
+gal_a11y_e_tree_init (void)
+{
+	if (atk_get_root ())
+		atk_registry_set_factory_type (
+			atk_get_default_registry (),
+			E_TYPE_TREE,
+			gal_a11y_e_tree_factory_get_type ());
+}
+
diff --git a/e-util/gal-a11y-e-tree.h b/e-util/gal-a11y-e-tree.h
new file mode 100644
index 0000000..709fce0
--- /dev/null
+++ b/e-util/gal-a11y-e-tree.h
@@ -0,0 +1,61 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Yuedong Du <yuedong du sun com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef __GAL_A11Y_E_TREE_H__
+#define __GAL_A11Y_E_TREE_H__
+
+#include <gtk/gtk.h>
+#include <atk/atkobject.h>
+#include <atk/atkcomponent.h>
+
+#define GAL_A11Y_TYPE_E_TREE            (gal_a11y_e_tree_get_type ())
+#define GAL_A11Y_E_TREE(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GAL_A11Y_TYPE_E_TREE, GalA11yETree))
+#define GAL_A11Y_E_TREE_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GAL_A11Y_TYPE_E_TREE, GalA11yETreeClass))
+#define GAL_A11Y_IS_E_TREE(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GAL_A11Y_TYPE_E_TREE))
+#define GAL_A11Y_IS_E_TREE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GAL_A11Y_TYPE_E_TREE))
+
+typedef struct _GalA11yETree GalA11yETree;
+typedef struct _GalA11yETreeClass GalA11yETreeClass;
+typedef struct _GalA11yETreePrivate GalA11yETreePrivate;
+
+/* This struct should actually be larger as this isn't what we derive from.
+ * The GalA11yETablePrivate comes right after the parent class structure.
+ **/
+struct _GalA11yETree {
+	GtkAccessible object;
+};
+
+struct _GalA11yETreeClass {
+	GtkAccessibleClass parent_class;
+};
+
+/* Standard Glib function */
+GType      gal_a11y_e_tree_get_type  (void);
+AtkObject *gal_a11y_e_tree_new       (GObject *tree);
+
+void       gal_a11y_e_tree_init      (void);
+
+#endif /* __GAL_A11Y_E_TREE_H__ */
diff --git a/e-util/gal-a11y-factory.h b/e-util/gal-a11y-factory.h
new file mode 100644
index 0000000..79ffcf2
--- /dev/null
+++ b/e-util/gal-a11y-factory.h
@@ -0,0 +1,89 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Gilbert Fang <gilbert fang sun com>
+ *
+ * This file is mainly from the gailfactory.h of GAIL.
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef _GAL_A11Y_FACTORY_H__
+#define _GAL_A11Y_FACTORY_H__
+
+#include <atk/atkobject.h>
+#include <atk/atkobjectfactory.h>
+
+#define GAL_A11Y_FACTORY(type, type_as_function, opt_create_accessible)	\
+										\
+static GType									\
+type_as_function ## _factory_get_accessible_type (void)				\
+{										\
+  return type;									\
+}										\
+										\
+static AtkObject *								\
+type_as_function ## _factory_create_accessible (GObject *obj)			\
+{										\
+  GtkWidget *widget;								\
+  AtkObject *accessible;							\
+										\
+  g_return_val_if_fail (GTK_IS_WIDGET (obj), NULL);				\
+										\
+  widget = GTK_WIDGET (obj);							\
+										\
+  accessible = opt_create_accessible (widget);					\
+										\
+  return accessible;								\
+}										\
+										\
+static void									\
+type_as_function ## _factory_class_init (AtkObjectFactoryClass *klass)		\
+{										\
+  klass->create_accessible   = type_as_function ## _factory_create_accessible;	\
+  klass->get_accessible_type = type_as_function ## _factory_get_accessible_type;\
+}										\
+										\
+static GType									\
+type_as_function ## _factory_get_type (void)					\
+{										\
+  static GType t = 0;								\
+										\
+  if (!t)									\
+  {										\
+    gchar *name;									\
+    static const GTypeInfo tinfo =						\
+    {										\
+      sizeof (AtkObjectFactoryClass),					\
+      NULL, NULL, (GClassInitFunc) type_as_function ## _factory_class_init,	\
+      NULL, NULL, sizeof (AtkObjectFactory), 0, NULL, NULL			\
+    };										\
+										\
+    name = g_strconcat (g_type_name (type), "Factory", NULL);			\
+    t = g_type_register_static (						\
+	    ATK_TYPE_OBJECT_FACTORY, name, &tinfo, 0);				\
+    g_free (name);								\
+  }										\
+										\
+  return t;									\
+}
+
+#endif /* _GAL_A11Y_FACTORY_H__ */
diff --git a/a11y/gal-a11y-util.c b/e-util/gal-a11y-util.c
similarity index 100%
rename from a11y/gal-a11y-util.c
rename to e-util/gal-a11y-util.c
diff --git a/e-util/gal-a11y-util.h b/e-util/gal-a11y-util.h
new file mode 100644
index 0000000..7564262
--- /dev/null
+++ b/e-util/gal-a11y-util.h
@@ -0,0 +1,40 @@
+/*
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Christopher James Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef __GAL_A11Y_UTIL_H__
+#define __GAL_A11Y_UTIL_H__
+
+#include <glib-object.h>
+
+GType  gal_a11y_type_register_static_with_private  (GType        parent_type,
+						    const gchar *type_name,
+						    GTypeInfo   *info,
+						    GTypeFlags   flags,
+						    gint          priv_size,
+						    gint        *priv_offset);
+
+#endif /* __GAL_A11Y_UTIL_H__ */
diff --git a/e-util/gal-define-views-dialog.c b/e-util/gal-define-views-dialog.c
new file mode 100644
index 0000000..4bed594
--- /dev/null
+++ b/e-util/gal-define-views-dialog.c
@@ -0,0 +1,451 @@
+/*
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <glib/gi18n.h>
+
+#include "e-misc-utils.h"
+#include "e-util-private.h"
+
+#include "gal-define-views-dialog.h"
+#include "gal-define-views-model.h"
+#include "gal-view-new-dialog.h"
+
+static void gal_define_views_dialog_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec);
+static void gal_define_views_dialog_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec);
+static void gal_define_views_dialog_dispose	 (GObject *object);
+
+/* The properties we support */
+enum {
+	PROP_0,
+	PROP_COLLECTION
+};
+
+enum {
+	COL_GALVIEW_NAME,
+	COL_GALVIEW_DATA
+};
+
+typedef struct {
+	gchar         *title;
+
+	GtkTreeView *treeview;
+	GtkTreeModel *model;
+
+	GalDefineViewsDialog *names;
+} GalDefineViewsDialogChild;
+
+G_DEFINE_TYPE (GalDefineViewsDialog, gal_define_views_dialog, GTK_TYPE_DIALOG)
+
+static void
+gal_define_views_dialog_class_init (GalDefineViewsDialogClass *class)
+{
+	GObjectClass *object_class;
+
+	object_class = (GObjectClass *) class;
+
+	object_class->set_property = gal_define_views_dialog_set_property;
+	object_class->get_property = gal_define_views_dialog_get_property;
+	object_class->dispose = gal_define_views_dialog_dispose;
+
+	g_object_class_install_property (
+		object_class,
+		PROP_COLLECTION,
+		g_param_spec_object (
+			"collection",
+			"Collection",
+			NULL,
+			GAL_VIEW_COLLECTION_TYPE,
+			G_PARAM_READWRITE));
+}
+
+/* Button callbacks */
+
+static void
+gdvd_button_new_dialog_callback (GtkWidget *widget,
+                                 gint id,
+                                 GalDefineViewsDialog *dialog)
+{
+	gchar *name;
+	GtkTreeIter iter;
+	GalView *view;
+	GalViewCollectionItem *item;
+	GalViewFactory *factory;
+
+	switch (id) {
+	case GTK_RESPONSE_OK:
+		g_object_get (
+			widget,
+			"name", &name,
+			"factory", &factory,
+			NULL);
+
+		if (name && factory) {
+			g_strchomp (name);
+			if (*name != '\0') {
+				view = gal_view_factory_new_view (factory, name);
+				gal_view_collection_append (dialog->collection, view);
+
+				item = dialog->collection->view_data[dialog->collection->view_count - 1];
+				gtk_list_store_append (GTK_LIST_STORE (dialog->model), &iter);
+				gtk_list_store_set (
+					GTK_LIST_STORE (dialog->model), &iter,
+					COL_GALVIEW_NAME, name,
+					COL_GALVIEW_DATA, item,
+					-1);
+
+				if (view && GAL_VIEW_GET_CLASS (view)->edit)
+					gal_view_edit (view, GTK_WINDOW (dialog));
+				g_object_unref (view);
+			}
+		}
+		g_object_unref (factory);
+		g_free (name);
+		break;
+	}
+	gtk_widget_destroy (widget);
+}
+
+static void
+gdvd_button_new_callback (GtkWidget *widget,
+                          GalDefineViewsDialog *dialog)
+{
+	GtkWidget *view_new_dialog = gal_view_new_dialog_new (dialog->collection);
+	gtk_window_set_transient_for (GTK_WINDOW (view_new_dialog), GTK_WINDOW (dialog));
+	g_signal_connect (
+		view_new_dialog, "response",
+		G_CALLBACK (gdvd_button_new_dialog_callback), dialog);
+	gtk_widget_show (view_new_dialog);
+}
+
+static void
+gdvd_button_modify_callback (GtkWidget *widget,
+                             GalDefineViewsDialog *dialog)
+{
+	GtkTreeIter iter;
+	GalViewCollectionItem *item;
+
+	if (gtk_tree_selection_get_selected (gtk_tree_view_get_selection (dialog->treeview),
+					 &dialog->model,
+					 &iter)) {
+		gtk_tree_model_get (dialog->model, &iter, COL_GALVIEW_DATA, &item, -1);
+
+		g_return_if_fail (item && !item->built_in);
+
+		gal_view_edit (item->view, GTK_WINDOW (dialog));
+	}
+}
+
+static void
+gdvd_button_delete_callback (GtkWidget *widget,
+                             GalDefineViewsDialog *dialog)
+{
+	gint row;
+	GtkTreeIter iter;
+	GtkTreePath *path;
+	GtkTreeSelection *selection;
+	GalViewCollectionItem *item;
+
+	selection = gtk_tree_view_get_selection (dialog->treeview);
+
+	if (gtk_tree_selection_get_selected (selection,
+					 &dialog->model,
+					 &iter)) {
+		gtk_tree_model_get (dialog->model, &iter, COL_GALVIEW_DATA, &item, -1);
+
+		g_return_if_fail (item && !item->built_in);
+
+		for (row = 0; row < dialog->collection->view_count; row++) {
+			if (item == dialog->collection->view_data[row]) {
+				gal_view_collection_delete_view (dialog->collection, row);
+				path = gtk_tree_model_get_path (dialog->model, &iter);
+				gtk_list_store_remove (GTK_LIST_STORE (dialog->model), &iter);
+
+				if (gtk_tree_path_prev (path)) {
+					gtk_tree_model_get_iter (dialog->model, &iter, path);
+				} else {
+					gtk_tree_model_get_iter_first (dialog->model, &iter);
+				}
+
+				gtk_tree_selection_select_iter (selection, &iter);
+				break;
+			}
+		}
+	}
+}
+
+static void
+gdvd_selection_changed_callback (GtkTreeSelection *selection,
+                                 GalDefineViewsDialog *dialog)
+{
+	GtkWidget *button;
+	GtkTreeIter iter;
+	GalViewCollectionItem *item = NULL;
+	GalViewClass *gvclass = NULL;
+
+	if (gtk_tree_selection_get_selected (selection, &dialog->model, &iter)) {
+		gtk_tree_model_get (dialog->model, &iter, COL_GALVIEW_DATA, &item, -1);
+
+		if (item && item->view)
+			gvclass = GAL_VIEW_GET_CLASS (item->view);
+	}
+
+	button = e_builder_get_widget (dialog->builder, "button-delete");
+	gtk_widget_set_sensitive (GTK_WIDGET (button), item && !item->built_in);
+
+	button = e_builder_get_widget (dialog->builder, "button-modify");
+	gtk_widget_set_sensitive (GTK_WIDGET (button), item && !item->built_in && gvclass && gvclass->edit != NULL);
+}
+
+static void
+gdvd_connect_signal (GalDefineViewsDialog *dialog,
+                     const gchar *widget_name,
+                     const gchar *signal,
+                     GCallback handler)
+{
+	GtkWidget *widget;
+
+	widget = e_builder_get_widget (dialog->builder, widget_name);
+
+	if (widget)
+		g_signal_connect (widget, signal, handler, dialog);
+}
+
+static void
+dialog_response (GalDefineViewsDialog *dialog,
+                 gint response_id,
+                 gpointer data)
+{
+	gal_view_collection_save (dialog->collection);
+}
+
+static void
+gal_define_views_dialog_init (GalDefineViewsDialog *dialog)
+{
+	GtkWidget *content_area;
+	GtkWidget *parent;
+	GtkWidget *widget;
+	GtkTreeSelection *selection;
+
+	dialog->collection = NULL;
+
+	dialog->builder = gtk_builder_new ();
+	e_load_ui_builder_definition (dialog->builder, "gal-define-views.ui");
+
+	widget = e_builder_get_widget (dialog->builder, "table-top");
+	if (!widget) {
+		return;
+	}
+
+	g_object_ref (widget);
+
+	parent = gtk_widget_get_parent (widget);
+	gtk_container_remove (GTK_CONTAINER (parent), widget);
+	gtk_window_set_default_size (GTK_WINDOW (dialog), 360, 270);
+	gtk_container_set_border_width (GTK_CONTAINER (dialog), 6);
+	gtk_container_set_border_width (GTK_CONTAINER (widget), 6);
+
+	content_area = gtk_dialog_get_content_area (GTK_DIALOG (dialog));
+	gtk_box_pack_start (GTK_BOX (content_area), widget, TRUE, TRUE, 0);
+
+	g_object_unref (widget);
+
+	gtk_dialog_add_buttons (
+		GTK_DIALOG (dialog),
+		GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
+		NULL);
+
+	dialog->treeview = GTK_TREE_VIEW (e_builder_get_widget (dialog->builder, "treeview1"));
+	gtk_tree_view_set_reorderable (GTK_TREE_VIEW (dialog->treeview), FALSE);
+	gtk_tree_view_set_headers_visible (dialog->treeview, TRUE);
+
+	gtk_window_set_resizable (GTK_WINDOW (dialog), TRUE);
+
+	gdvd_connect_signal (dialog, "button-new",    "clicked", G_CALLBACK (gdvd_button_new_callback));
+	gdvd_connect_signal (dialog, "button-modify", "clicked", G_CALLBACK (gdvd_button_modify_callback));
+	gdvd_connect_signal (dialog, "button-delete", "clicked", G_CALLBACK (gdvd_button_delete_callback));
+	g_signal_connect (
+		dialog, "response",
+		G_CALLBACK (dialog_response), NULL);
+
+	selection = gtk_tree_view_get_selection (dialog->treeview);
+	g_signal_connect (
+		selection, "changed",
+		G_CALLBACK (gdvd_selection_changed_callback), dialog);
+	gdvd_selection_changed_callback (selection, dialog);
+
+	gtk_widget_show (GTK_WIDGET (dialog));
+}
+
+static void
+gal_define_views_dialog_dispose (GObject *object)
+{
+	GalDefineViewsDialog *gal_define_views_dialog = GAL_DEFINE_VIEWS_DIALOG (object);
+
+	if (gal_define_views_dialog->builder)
+		g_object_unref (gal_define_views_dialog->builder);
+	gal_define_views_dialog->builder = NULL;
+
+	/* Chain up to parent's dispose() method. */
+	G_OBJECT_CLASS (gal_define_views_dialog_parent_class)->dispose (object);
+}
+
+static void
+gal_define_views_dialog_set_collection (GalDefineViewsDialog *dialog,
+                                        GalViewCollection *collection)
+{
+	gint i;
+	GtkListStore *store;
+	GtkCellRenderer *renderer;
+	dialog->collection = collection;
+
+	store = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_POINTER);
+
+	for (i = 0; i < collection->view_count; i++) {
+		GalViewCollectionItem *item = collection->view_data[i];
+		GtkTreeIter iter;
+
+		/* hide built in views */
+		/*if (item->built_in == 1)
+			continue;*/
+
+		gchar *title = NULL;
+		title = e_str_without_underscores (item->title);
+
+		gtk_list_store_append (store, &iter);
+		gtk_list_store_set (
+			store, &iter,
+			COL_GALVIEW_NAME, title,
+			COL_GALVIEW_DATA, item,
+			-1);
+
+		g_free (title);
+	}
+
+	gtk_tree_sortable_set_sort_column_id (
+		GTK_TREE_SORTABLE (store),
+		COL_GALVIEW_NAME, GTK_SORT_ASCENDING);
+
+	/* attaching treeview to model */
+	gtk_tree_view_set_model (dialog->treeview, GTK_TREE_MODEL (store));
+	gtk_tree_view_set_search_column (dialog->treeview, COL_GALVIEW_NAME);
+
+	dialog->model = GTK_TREE_MODEL (store);
+
+	renderer = gtk_cell_renderer_text_new ();
+	gtk_tree_view_insert_column_with_attributes (
+		dialog->treeview,
+		COL_GALVIEW_NAME, _("Name"),
+		renderer, "text", COL_GALVIEW_NAME,
+		NULL);
+
+	/* set sort column */
+	gtk_tree_sortable_set_sort_column_id (
+		GTK_TREE_SORTABLE (dialog->model),
+		COL_GALVIEW_NAME, GTK_SORT_ASCENDING);
+
+	if (dialog->builder) {
+		GtkWidget *widget = e_builder_get_widget (dialog->builder, "label-views");
+		if (widget && GTK_IS_LABEL (widget)) {
+			if (collection->title) {
+				gchar *text = g_strdup_printf (
+					_("Define Views for %s"),
+					collection->title);
+				gtk_label_set_text (
+					GTK_LABEL (widget),
+					text);
+				gtk_window_set_title (GTK_WINDOW (dialog), text);
+				g_free (text);
+			} else {
+				gtk_label_set_text (
+					GTK_LABEL (widget),
+					_("Define Views"));
+				gtk_window_set_title (
+					GTK_WINDOW (dialog),
+					_("Define Views"));
+			}
+		}
+	}
+}
+
+/**
+ * gal_define_views_dialog_new
+ *
+ * Returns a new dialog for defining views.
+ *
+ * Returns: The GalDefineViewsDialog.
+ */
+GtkWidget *
+gal_define_views_dialog_new (GalViewCollection *collection)
+{
+	GtkWidget *widget = g_object_new (GAL_TYPE_DEFINE_VIEWS_DIALOG, NULL);
+	gal_define_views_dialog_set_collection (GAL_DEFINE_VIEWS_DIALOG (widget), collection);
+	return widget;
+}
+
+static void
+gal_define_views_dialog_set_property (GObject *object,
+                                      guint property_id,
+                                      const GValue *value,
+                                      GParamSpec *pspec)
+{
+	GalDefineViewsDialog *dialog;
+
+	dialog = GAL_DEFINE_VIEWS_DIALOG (object);
+
+	switch (property_id) {
+	case PROP_COLLECTION:
+		if (g_value_get_object (value))
+			gal_define_views_dialog_set_collection (dialog, GAL_VIEW_COLLECTION (g_value_get_object (value)));
+		else
+			gal_define_views_dialog_set_collection (dialog, NULL);
+		break;
+
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+		return;
+	}
+}
+
+static void
+gal_define_views_dialog_get_property (GObject *object,
+                                      guint property_id,
+                                      GValue *value,
+                                      GParamSpec *pspec)
+{
+	GalDefineViewsDialog *dialog;
+
+	dialog = GAL_DEFINE_VIEWS_DIALOG (object);
+
+	switch (property_id) {
+	case PROP_COLLECTION:
+		g_value_set_object (value, dialog->collection);
+
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+		break;
+	}
+}
diff --git a/e-util/gal-define-views-dialog.h b/e-util/gal-define-views-dialog.h
new file mode 100644
index 0000000..a3b6973
--- /dev/null
+++ b/e-util/gal-define-views-dialog.h
@@ -0,0 +1,77 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef GAL_DEFINE_VIEWS_DIALOG_H
+#define GAL_DEFINE_VIEWS_DIALOG_H
+
+#include <gtk/gtk.h>
+#include <e-util/gal-view-collection.h>
+
+/* Standard GObject macros */
+#define GAL_TYPE_DEFINE_VIEWS_DIALOG \
+	(gal_define_views_dialog_get_type ())
+#define GAL_DEFINE_VIEWS_DIALOG(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), GAL_TYPE_DEFINE_VIEWS_DIALOG, GalDefineViewsDialog))
+#define GAL_DEFINE_VIEWS_DIALOG_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), GAL_TYPE_DEFINE_VIEWS_DIALOG, GalDefineViewsDialogClass))
+#define GAL_IS_DEFINE_VIEWS_DIALOG(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), GAL_TYPE_DEFINE_VIEWS_DIALOG))
+#define GAL_IS_DEFINE_VIEWS_DIALOG_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), GAL_TYPE_DEFINE_VIEWS_DIALOG))
+#define GAL_DEFINE_VIEWS_DIALOG_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), GAL_TYPE_DEFINE_VIEWS_DIALOG, GalDefineViewsDialogClass))
+
+G_BEGIN_DECLS
+
+typedef struct _GalDefineViewsDialog GalDefineViewsDialog;
+typedef struct _GalDefineViewsDialogClass GalDefineViewsDialogClass;
+
+struct _GalDefineViewsDialog {
+	GtkDialog parent;
+
+	/* item specific fields */
+	GtkBuilder *builder;
+	GtkTreeView *treeview;
+	GtkTreeModel *model;
+
+	GalViewCollection *collection;
+};
+
+struct _GalDefineViewsDialogClass {
+	GtkDialogClass parent_class;
+};
+
+GType		gal_define_views_dialog_get_type (void);
+GtkWidget *	gal_define_views_dialog_new	(GalViewCollection *collection);
+
+G_END_DECLS
+
+#endif /* GAL_DEFINE_VIEWS_DIALOG_H */
diff --git a/e-util/gal-define-views-model.c b/e-util/gal-define-views-model.c
new file mode 100644
index 0000000..f9963ac
--- /dev/null
+++ b/e-util/gal-define-views-model.c
@@ -0,0 +1,352 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <libxml/tree.h>
+#include <libxml/parser.h>
+#include <libxml/xmlmemory.h>
+
+#include <glib/gi18n.h>
+
+#include "gal-define-views-model.h"
+
+G_DEFINE_TYPE (GalDefineViewsModel, gal_define_views_model, E_TYPE_TABLE_MODEL)
+
+enum {
+	PROP_0,
+	PROP_EDITABLE,
+	PROP_COLLECTION
+};
+
+static void
+gal_define_views_model_set_property (GObject *object,
+                                     guint property_id,
+                                     const GValue *value,
+                                     GParamSpec *pspec)
+{
+	GalDefineViewsModel *model;
+
+	model = GAL_DEFINE_VIEWS_MODEL (object);
+
+	switch (property_id) {
+		case PROP_EDITABLE:
+			model->editable = g_value_get_boolean (value);
+			return;
+
+		case PROP_COLLECTION:
+			e_table_model_pre_change (E_TABLE_MODEL (object));
+			if (g_value_get_object (value))
+				model->collection = GAL_VIEW_COLLECTION (
+					g_value_get_object (value));
+			else
+				model->collection = NULL;
+			e_table_model_changed (E_TABLE_MODEL (object));
+			return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+gal_define_views_model_get_property (GObject *object,
+                                     guint property_id,
+                                     GValue *value,
+                                     GParamSpec *pspec)
+{
+	GalDefineViewsModel *model;
+
+	model = GAL_DEFINE_VIEWS_MODEL (object);
+
+	switch (property_id) {
+		case PROP_EDITABLE:
+			g_value_set_boolean (value, model->editable);
+			return;
+
+		case PROP_COLLECTION:
+			g_value_set_object (value, model->collection);
+			return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+gdvm_dispose (GObject *object)
+{
+	GalDefineViewsModel *model = GAL_DEFINE_VIEWS_MODEL (object);
+
+	if (model->collection)
+		g_object_unref (model->collection);
+	model->collection = NULL;
+
+	/* Chain up to parent's dispose() method. */
+	G_OBJECT_CLASS (gal_define_views_model_parent_class)->dispose (object);
+}
+
+/* This function returns the number of columns in our ETableModel. */
+static gint
+gdvm_col_count (ETableModel *etc)
+{
+	return 1;
+}
+
+/* This function returns the number of rows in our ETableModel. */
+static gint
+gdvm_row_count (ETableModel *etc)
+{
+	GalDefineViewsModel *views = GAL_DEFINE_VIEWS_MODEL (etc);
+	if (views->collection)
+		return gal_view_collection_get_count (views->collection);
+	else
+		return 0;
+}
+
+/* This function returns the value at a particular point in our ETableModel. */
+static gpointer
+gdvm_value_at (ETableModel *etc,
+               gint col,
+               gint row)
+{
+	GalDefineViewsModel *views = GAL_DEFINE_VIEWS_MODEL (etc);
+	GalView *view;
+	const gchar *value;
+
+	view = gal_view_collection_get_view (views->collection, row);
+	value = gal_view_get_title (view);
+
+	return (gpointer) ((value != NULL) ? value : "");
+}
+
+/* This function sets the value at a particular point in our ETableModel. */
+static void
+gdvm_set_value_at (ETableModel *etc,
+                   gint col,
+                   gint row,
+                   gconstpointer val)
+{
+	GalDefineViewsModel *views = GAL_DEFINE_VIEWS_MODEL (etc);
+	if (views->editable) {
+		GalView *view;
+
+		view = gal_view_collection_get_view (views->collection, row);
+
+		e_table_model_pre_change (etc);
+		gal_view_set_title (view, val);
+		e_table_model_cell_changed (etc, col, row);
+	}
+}
+
+/* This function returns whether a particular cell is editable. */
+static gboolean
+gdvm_is_cell_editable (ETableModel *etc,
+                       gint col,
+                       gint row)
+{
+	return GAL_DEFINE_VIEWS_MODEL (etc)->editable;
+}
+
+static void
+gdvm_append_row (ETableModel *etm,
+                 ETableModel *source,
+                 gint row)
+{
+}
+
+/* This function duplicates the value passed to it. */
+static gpointer
+gdvm_duplicate_value (ETableModel *etc,
+                      gint col,
+                      gconstpointer value)
+{
+	return g_strdup (value);
+}
+
+/* This function frees the value passed to it. */
+static void
+gdvm_free_value (ETableModel *etc,
+                 gint col,
+                 gpointer value)
+{
+	g_free (value);
+}
+
+static gpointer
+gdvm_initialize_value (ETableModel *etc,
+                       gint col)
+{
+	return g_strdup ("");
+}
+
+static gboolean
+gdvm_value_is_empty (ETableModel *etc,
+                     gint col,
+                     gconstpointer value)
+{
+	return !(value && *(gchar *) value);
+}
+
+static gchar *
+gdvm_value_to_string (ETableModel *etc,
+                      gint col,
+                      gconstpointer value)
+{
+	return g_strdup (value);
+}
+
+/**
+ * gal_define_views_model_append
+ * @model: The model to add to.
+ * @view: The view to add.
+ *
+ * Adds the given view to the gal define views model.
+ */
+void
+gal_define_views_model_append (GalDefineViewsModel *model,
+                               GalView *view)
+{
+	ETableModel *etm = E_TABLE_MODEL (model);
+
+	e_table_model_pre_change (etm);
+	gal_view_collection_append (model->collection, view);
+	e_table_model_row_inserted (
+		etm, gal_view_collection_get_count (model->collection) - 1);
+}
+
+static void
+gal_define_views_model_class_init (GalDefineViewsModelClass *class)
+{
+	ETableModelClass *model_class = E_TABLE_MODEL_CLASS (class);
+	GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+	object_class->dispose        = gdvm_dispose;
+	object_class->set_property   = gal_define_views_model_set_property;
+	object_class->get_property   = gal_define_views_model_get_property;
+
+	g_object_class_install_property (
+		object_class,
+		PROP_EDITABLE,
+		g_param_spec_boolean (
+			"editable",
+			"Editable",
+			NULL,
+			FALSE,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_COLLECTION,
+		g_param_spec_object (
+			"collection",
+			"Collection",
+			NULL,
+			GAL_VIEW_COLLECTION_TYPE,
+			G_PARAM_READWRITE));
+
+	model_class->column_count     = gdvm_col_count;
+	model_class->row_count        = gdvm_row_count;
+	model_class->value_at         = gdvm_value_at;
+	model_class->set_value_at     = gdvm_set_value_at;
+	model_class->is_cell_editable = gdvm_is_cell_editable;
+	model_class->append_row       = gdvm_append_row;
+	model_class->duplicate_value  = gdvm_duplicate_value;
+	model_class->free_value       = gdvm_free_value;
+	model_class->initialize_value = gdvm_initialize_value;
+	model_class->value_is_empty   = gdvm_value_is_empty;
+	model_class->value_to_string  = gdvm_value_to_string;
+}
+
+static void
+gal_define_views_model_init (GalDefineViewsModel *model)
+{
+	model->collection = NULL;
+}
+
+/**
+ * gal_define_views_model_new
+ *
+ * Returns a new define views model.  This is a list of views as an
+ * ETable for use in the GalDefineViewsDialog.
+ *
+ * Returns: The new GalDefineViewsModel.
+ */
+ETableModel *
+gal_define_views_model_new (void)
+{
+	GalDefineViewsModel *et;
+
+	et = g_object_new (GAL_DEFINE_VIEWS_MODEL_TYPE, NULL);
+
+	return E_TABLE_MODEL (et);
+}
+
+/**
+ * gal_define_views_model_get_view:
+ * @model: The GalDefineViewsModel.
+ * @n: Which view to get.
+ *
+ * Gets the nth view.
+ *
+ * Returns: The view.
+ */
+GalView *
+gal_define_views_model_get_view (GalDefineViewsModel *model,
+                                 gint n)
+{
+	return gal_view_collection_get_view (model->collection, n);
+}
+
+/**
+ * gal_define_views_model_delete_view:
+ * @model: The GalDefineViewsModel.
+ * @n: Which view to delete.
+ *
+ * Deletes the nth view.
+ */
+void
+gal_define_views_model_delete_view (GalDefineViewsModel *model,
+                                    gint n)
+{
+	e_table_model_pre_change (E_TABLE_MODEL (model));
+	gal_view_collection_delete_view (model->collection, n);
+	e_table_model_row_deleted (E_TABLE_MODEL (model), n);
+}
+
+/**
+ * gal_define_views_model_copy_view:
+ * @model: The GalDefineViewsModel.
+ * @n: Which view to copy.
+ *
+ * Copys the nth view.
+ */
+void
+gal_define_views_model_copy_view (GalDefineViewsModel *model,
+                                  gint n)
+{
+	ETableModel *etm = E_TABLE_MODEL (model);
+	e_table_model_pre_change (etm);
+	gal_view_collection_copy_view (model->collection, n);
+	e_table_model_row_inserted (
+		etm, gal_view_collection_get_count (model->collection) - 1);
+}
diff --git a/e-util/gal-define-views-model.h b/e-util/gal-define-views-model.h
new file mode 100644
index 0000000..7219384
--- /dev/null
+++ b/e-util/gal-define-views-model.h
@@ -0,0 +1,70 @@
+/*
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef _GAL_DEFINE_VIEWS_MODEL_H_
+#define _GAL_DEFINE_VIEWS_MODEL_H_
+
+#include <e-util/e-table-model.h>
+#include <e-util/gal-view.h>
+#include <e-util/gal-view-collection.h>
+
+G_BEGIN_DECLS
+
+#define GAL_DEFINE_VIEWS_MODEL_TYPE        (gal_define_views_model_get_type ())
+#define GAL_DEFINE_VIEWS_MODEL(o)          (G_TYPE_CHECK_INSTANCE_CAST ((o), GAL_DEFINE_VIEWS_MODEL_TYPE, GalDefineViewsModel))
+#define GAL_DEFINE_VIEWS_MODEL_CLASS(k)    (G_TYPE_CHECK_CLASS_CAST((k), GAL_DEFINE_VIEWS_MODEL_TYPE, GalDefineViewsModelClass))
+#define GAL_IS_DEFINE_VIEWS_MODEL(o)       (G_TYPE_CHECK_INSTANCE_TYPE ((o), GAL_DEFINE_VIEWS_MODEL_TYPE))
+#define GAL_IS_DEFINE_VIEWS_MODEL_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GAL_DEFINE_VIEWS_MODEL_TYPE))
+
+typedef struct {
+	ETableModel parent;
+
+	/* item specific fields */
+	GalViewCollection *collection;
+
+	guint editable : 1;
+} GalDefineViewsModel;
+
+typedef struct {
+	ETableModelClass parent_class;
+} GalDefineViewsModelClass;
+
+GType        gal_define_views_model_get_type     (void);
+ETableModel *gal_define_views_model_new          (void);
+
+void         gal_define_views_model_append       (GalDefineViewsModel *model,
+						  GalView             *view);
+GalView     *gal_define_views_model_get_view     (GalDefineViewsModel *model,
+						  gint                  i);
+void         gal_define_views_model_delete_view  (GalDefineViewsModel *model,
+						  gint                  i);
+void         gal_define_views_model_copy_view    (GalDefineViewsModel *model,
+						  gint                  i);
+
+G_END_DECLS
+
+#endif /* _GAL_DEFINE_VIEWS_MODEL_H_ */
diff --git a/widgets/menus/gal-define-views.ui b/e-util/gal-define-views.ui
similarity index 100%
rename from widgets/menus/gal-define-views.ui
rename to e-util/gal-define-views.ui
diff --git a/e-util/gal-view-collection.c b/e-util/gal-view-collection.c
new file mode 100644
index 0000000..bcbad52
--- /dev/null
+++ b/e-util/gal-view-collection.c
@@ -0,0 +1,829 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "gal-view-collection.h"
+
+#include <ctype.h>
+#include <string.h>
+#include <errno.h>
+
+#include <libxml/parser.h>
+#include <libedataserver/libedataserver.h>
+
+#include <glib/gi18n.h>
+
+#include "e-unicode.h"
+#include "e-xml-utils.h"
+
+G_DEFINE_TYPE (GalViewCollection, gal_view_collection, G_TYPE_OBJECT)
+
+#define d(x)
+
+enum {
+	DISPLAY_VIEW,
+	CHANGED,
+	LAST_SIGNAL
+};
+
+static guint gal_view_collection_signals[LAST_SIGNAL] = { 0, };
+
+/**
+ * gal_view_collection_display_view:
+ * @collection: The GalViewCollection to send the signal on.
+ * @view: The view to display.
+ *
+ */
+void
+gal_view_collection_display_view (GalViewCollection *collection,
+                                  GalView *view)
+{
+	g_return_if_fail (GAL_IS_VIEW_COLLECTION (collection));
+	g_return_if_fail (GAL_IS_VIEW (view));
+
+	g_signal_emit (
+		collection,
+		gal_view_collection_signals[DISPLAY_VIEW], 0,
+		view);
+}
+
+static void
+gal_view_collection_changed (GalViewCollection *collection)
+{
+	g_return_if_fail (GAL_IS_VIEW_COLLECTION (collection));
+
+	g_signal_emit (
+		collection,
+		gal_view_collection_signals[CHANGED], 0);
+}
+
+static void
+gal_view_collection_item_free (GalViewCollectionItem *item)
+{
+	g_free (item->id);
+	if (item->view) {
+		if (item->view_changed_id)
+			g_signal_handler_disconnect (
+				item->view,
+				item->view_changed_id);
+		g_object_unref (item->view);
+	}
+	g_free (item);
+}
+
+static gchar *
+gal_view_generate_string (GalViewCollection *collection,
+                          GalView *view,
+                          gint which)
+{
+	gchar *ret_val;
+	gchar *pointer;
+
+	if (which == 1)
+		ret_val = g_strdup (gal_view_get_title (view));
+	else
+		ret_val = g_strdup_printf ("%s_%d", gal_view_get_title (view), which);
+	for (pointer = ret_val; *pointer; pointer = g_utf8_next_char (pointer)) {
+		if (!g_unichar_isalnum (g_utf8_get_char (pointer))) {
+			gchar *ptr = pointer;
+			for (; ptr < g_utf8_next_char (pointer); *ptr = '_', ptr++)
+				;
+		}
+	}
+	return ret_val;
+}
+
+static gint
+gal_view_check_string (GalViewCollection *collection,
+                       gchar *string)
+{
+	gint i;
+
+	if (!strcmp (string, "current_view"))
+		return FALSE;
+
+	for (i = 0; i < collection->view_count; i++) {
+		if (!strcmp (string, collection->view_data[i]->id))
+			return FALSE;
+	}
+	for (i = 0; i < collection->removed_view_count; i++) {
+		if (!strcmp (string, collection->removed_view_data[i]->id))
+			return FALSE;
+	}
+	return TRUE;
+}
+
+static gchar *
+gal_view_generate_id (GalViewCollection *collection,
+                      GalView *view)
+{
+	gint i;
+	for (i = 1; TRUE; i++) {
+		gchar *try;
+
+		try = gal_view_generate_string (collection, view, i);
+		if (gal_view_check_string (collection, try))
+			return try;
+		g_free (try);
+	}
+}
+
+static void
+gal_view_collection_dispose (GObject *object)
+{
+	GalViewCollection *collection = GAL_VIEW_COLLECTION (object);
+	gint i;
+
+	for (i = 0; i < collection->view_count; i++) {
+		gal_view_collection_item_free (collection->view_data[i]);
+	}
+	g_free (collection->view_data);
+	collection->view_data = NULL;
+	collection->view_count = 0;
+
+	g_list_foreach (
+		collection->factory_list,
+		(GFunc) g_object_unref, NULL);
+	g_list_free (collection->factory_list);
+	collection->factory_list = NULL;
+
+	for (i = 0; i < collection->removed_view_count; i++) {
+		gal_view_collection_item_free (collection->removed_view_data[i]);
+	}
+	g_free (collection->removed_view_data);
+	collection->removed_view_data  = NULL;
+	collection->removed_view_count = 0;
+
+	g_free (collection->system_dir);
+	collection->system_dir = NULL;
+
+	g_free (collection->local_dir);
+	collection->local_dir = NULL;
+
+	g_free (collection->default_view);
+	collection->default_view = NULL;
+
+	g_free (collection->title);
+	collection->title = NULL;
+
+	/* Chain up to parent's dispose() method. */
+	G_OBJECT_CLASS (gal_view_collection_parent_class)->dispose (object);
+}
+
+static void
+gal_view_collection_class_init (GalViewCollectionClass *class)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+	object_class->dispose = gal_view_collection_dispose;
+
+	gal_view_collection_signals[DISPLAY_VIEW] = g_signal_new (
+		"display_view",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (GalViewCollectionClass, display_view),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__OBJECT,
+		G_TYPE_NONE, 1,
+		GAL_TYPE_VIEW);
+
+	gal_view_collection_signals[CHANGED] = g_signal_new (
+		"changed",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (GalViewCollectionClass, changed),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__VOID,
+		G_TYPE_NONE, 0);
+
+	class->display_view = NULL;
+	class->changed      = NULL;
+}
+
+static void
+gal_view_collection_init (GalViewCollection *collection)
+{
+	collection->view_data             = NULL;
+	collection->view_count            = 0;
+	collection->factory_list          = NULL;
+
+	collection->removed_view_data     = NULL;
+	collection->removed_view_count    = 0;
+
+	collection->system_dir            = NULL;
+	collection->local_dir             = NULL;
+
+	collection->loaded                = FALSE;
+	collection->default_view          = NULL;
+	collection->default_view_built_in = TRUE;
+
+	collection->title                 = NULL;
+}
+
+/**
+ * gal_view_collection_new:
+ *
+ * A collection of views and view factories.
+ */
+GalViewCollection *
+gal_view_collection_new (void)
+{
+	return g_object_new (GAL_VIEW_COLLECTION_TYPE, NULL);
+}
+
+void
+gal_view_collection_set_title (GalViewCollection *collection,
+                               const gchar *title)
+{
+	g_free (collection->title);
+	collection->title = g_strdup (title);
+}
+
+/**
+ * gal_view_collection_set_storage_directories
+ * @collection: The view collection to initialize
+ * @system_dir: The location of the system built in views
+ * @local_dir: The location to store the users set up views
+ *
+ * Sets up the GalViewCollection.
+ */
+void
+gal_view_collection_set_storage_directories (GalViewCollection *collection,
+                                             const gchar *system_dir,
+                                             const gchar *local_dir)
+{
+	g_return_if_fail (GAL_IS_VIEW_COLLECTION (collection));
+	g_return_if_fail (system_dir != NULL);
+	g_return_if_fail (local_dir != NULL);
+
+	g_free (collection->system_dir);
+	g_free (collection->local_dir);
+
+	collection->system_dir = g_strdup (system_dir);
+	collection->local_dir = g_strdup (local_dir);
+}
+
+/**
+ * gal_view_collection_add_factory
+ * @collection: The view collection to add a factory to
+ * @factory: The factory to add.  The @collection will add a reference
+ * to the factory object, so you should unref it after calling this
+ * function if you no longer need it.
+ *
+ * Adds the given factory to this collection.  This list is used both
+ * when loading views from their xml description as well as when the
+ * user tries to create a new view.
+ */
+void
+gal_view_collection_add_factory (GalViewCollection *collection,
+                                 GalViewFactory *factory)
+{
+	g_return_if_fail (GAL_IS_VIEW_COLLECTION (collection));
+	g_return_if_fail (GAL_IS_VIEW_FACTORY (factory));
+
+	g_object_ref (factory);
+	collection->factory_list = g_list_prepend (collection->factory_list, factory);
+}
+
+static void
+view_changed (GalView *view,
+              GalViewCollectionItem *item)
+{
+	item->changed = TRUE;
+	item->ever_changed = TRUE;
+
+	g_signal_handler_block (item->view, item->view_changed_id);
+	gal_view_collection_changed (item->collection);
+	g_signal_handler_unblock (item->view, item->view_changed_id);
+}
+
+/* Use factory list to load a GalView file. */
+static GalView *
+gal_view_collection_real_load_view_from_file (GalViewCollection *collection,
+                                              const gchar *type,
+                                              const gchar *title,
+                                              const gchar *dir,
+                                              const gchar *filename)
+{
+	GalViewFactory *factory;
+	GList *factories;
+
+	factory = NULL;
+	for (factories = collection->factory_list; factories; factories = factories->next) {
+		if (type && !strcmp (gal_view_factory_get_type_code (factories->data), type)) {
+			factory = factories->data;
+			break;
+		}
+	}
+	if (factory) {
+		GalView *view;
+
+		view = gal_view_factory_new_view (factory, title);
+		gal_view_set_title (view, title);
+		gal_view_load (view, filename);
+		return view;
+	}
+	return NULL;
+}
+
+GalView *
+gal_view_collection_load_view_from_file (GalViewCollection *collection,
+                                         const gchar *type,
+                                         const gchar *filename)
+{
+	return gal_view_collection_real_load_view_from_file (collection, type, "", collection->local_dir, filename);
+}
+
+static GalViewCollectionItem *
+load_single_file (GalViewCollection *collection,
+                  gchar *dir,
+                  gboolean local,
+                  xmlNode *node)
+{
+	GalViewCollectionItem *item;
+	item = g_new (GalViewCollectionItem, 1);
+	item->ever_changed = local;
+	item->changed = FALSE;
+	item->built_in = !local;
+	item->id = e_xml_get_string_prop_by_name (node, (const guchar *)"id");
+	item->filename = e_xml_get_string_prop_by_name (node, (const guchar *)"filename");
+	item->title = e_xml_get_translated_utf8_string_prop_by_name (node, (const guchar *)"title");
+	item->type = e_xml_get_string_prop_by_name (node, (const guchar *)"type");
+	item->collection = collection;
+	item->view_changed_id = 0;
+
+	if (item->filename) {
+		gchar *fullpath;
+		fullpath = g_build_filename (dir, item->filename, NULL);
+		item->view = gal_view_collection_real_load_view_from_file (collection, item->type, item->title, dir, fullpath);
+		g_free (fullpath);
+		if (item->view) {
+			item->view_changed_id = g_signal_connect (
+				item->view, "changed",
+				G_CALLBACK (view_changed), item);
+		}
+	}
+	return item;
+}
+
+static void
+load_single_dir (GalViewCollection *collection,
+                 gchar *dir,
+                 gboolean local)
+{
+	xmlDoc *doc = NULL;
+	xmlNode *root;
+	xmlNode *child;
+	gchar *filename = g_build_filename (dir, "galview.xml", NULL);
+	gchar *default_view;
+
+	if (g_file_test (filename, G_FILE_TEST_IS_REGULAR)) {
+#ifdef G_OS_WIN32
+		gchar *locale_filename = g_win32_locale_filename_from_utf8 (filename);
+		if (locale_filename != NULL)
+			doc = xmlParseFile (locale_filename);
+		g_free (locale_filename);
+#else
+		doc = xmlParseFile (filename);
+#endif
+	}
+
+	if (!doc) {
+		g_free (filename);
+		return;
+	}
+	root = xmlDocGetRootElement (doc);
+	for (child = root->xmlChildrenNode; child; child = child->next) {
+		gchar *id;
+		gboolean found = FALSE;
+		gint i;
+
+		if (!strcmp ((gchar *) child->name, "text"))
+			continue;
+
+		id = e_xml_get_string_prop_by_name (child, (const guchar *)"id");
+		for (i = 0; i < collection->view_count; i++) {
+			if (!strcmp (id, collection->view_data[i]->id)) {
+				if (!local)
+					collection->view_data[i]->built_in = TRUE;
+				found = TRUE;
+				break;
+			}
+		}
+		if (!found) {
+			for (i = 0; i < collection->removed_view_count; i++) {
+				if (!strcmp (id, collection->removed_view_data[i]->id)) {
+					if (!local)
+						collection->removed_view_data[i]->built_in = TRUE;
+					found = TRUE;
+					break;
+				}
+			}
+		}
+
+		if (!found) {
+			GalViewCollectionItem *item = load_single_file (collection, dir, local, child);
+			if (item->filename && *item->filename) {
+				collection->view_data = g_renew (GalViewCollectionItem *, collection->view_data, collection->view_count + 1);
+				collection->view_data[collection->view_count] = item;
+				collection->view_count++;
+			} else {
+				collection->removed_view_data = g_renew (GalViewCollectionItem *, collection->removed_view_data, collection->removed_view_count + 1);
+				collection->removed_view_data[collection->removed_view_count] = item;
+				collection->removed_view_count++;
+			}
+		}
+		g_free (id);
+	}
+
+	default_view = e_xml_get_string_prop_by_name (root, (const guchar *)"default-view");
+	if (default_view) {
+		if (local)
+			collection->default_view_built_in = FALSE;
+		else
+			collection->default_view_built_in = TRUE;
+		g_free (collection->default_view);
+		collection->default_view = default_view;
+	}
+
+	g_free (filename);
+	xmlFreeDoc (doc);
+}
+
+/**
+ * gal_view_collection_load
+ * @collection: The view collection to load information for
+ *
+ * Loads the data from the system and user directories specified in
+ * set storage directories.  This is primarily for internal use by
+ * other parts of gal_view.
+ */
+void
+gal_view_collection_load (GalViewCollection *collection)
+{
+	g_return_if_fail (GAL_IS_VIEW_COLLECTION (collection));
+	g_return_if_fail (collection->local_dir != NULL);
+	g_return_if_fail (collection->system_dir != NULL);
+	g_return_if_fail (!collection->loaded);
+
+	if ((g_mkdir_with_parents (collection->local_dir, 0777) == -1) && (errno != EEXIST))
+		g_warning ("Unable to create dir %s: %s", collection->local_dir, g_strerror (errno));
+
+	load_single_dir (collection, collection->local_dir, TRUE);
+	load_single_dir (collection, collection->system_dir, FALSE);
+	gal_view_collection_changed (collection);
+
+	collection->loaded = TRUE;
+}
+
+/**
+ * gal_view_collection_save
+ * @collection: The view collection to save information for
+ *
+ * Saves the data to the user directory specified in set storage
+ * directories.  This is primarily for internal use by other parts of
+ * gal_view.
+ */
+void
+gal_view_collection_save (GalViewCollection *collection)
+{
+	gint i;
+	xmlDoc *doc;
+	xmlNode *root;
+	gchar *filename;
+
+	g_return_if_fail (GAL_IS_VIEW_COLLECTION (collection));
+	g_return_if_fail (collection->local_dir != NULL);
+
+	doc = xmlNewDoc ((const guchar *)"1.0");
+	root = xmlNewNode (NULL, (const guchar *)"GalViewCollection");
+	xmlDocSetRootElement (doc, root);
+
+	if (collection->default_view && !collection->default_view_built_in) {
+		e_xml_set_string_prop_by_name (root, (const guchar *)"default-view", collection->default_view);
+	}
+
+	for (i = 0; i < collection->view_count; i++) {
+		xmlNode *child;
+		GalViewCollectionItem *item;
+
+		item = collection->view_data[i];
+		if (item->ever_changed) {
+			child = xmlNewChild (root, NULL, (const guchar *)"GalView", NULL);
+			e_xml_set_string_prop_by_name (child, (const guchar *)"id", item->id);
+			e_xml_set_string_prop_by_name (child, (const guchar *)"title", item->title);
+			e_xml_set_string_prop_by_name (child, (const guchar *)"filename", item->filename);
+			e_xml_set_string_prop_by_name (child, (const guchar *)"type", item->type);
+
+			if (item->changed) {
+				filename = g_build_filename (collection->local_dir, item->filename, NULL);
+				gal_view_save (item->view, filename);
+				g_free (filename);
+			}
+		}
+	}
+	for (i = 0; i < collection->removed_view_count; i++) {
+		xmlNode *child;
+		GalViewCollectionItem *item;
+
+		item = collection->removed_view_data[i];
+
+		child = xmlNewChild (root, NULL, (const guchar *)"GalView", NULL);
+		e_xml_set_string_prop_by_name (child, (const guchar *)"id", item->id);
+		e_xml_set_string_prop_by_name (child, (const guchar *)"title", item->title);
+		e_xml_set_string_prop_by_name (child, (const guchar *)"type", item->type);
+	}
+	filename = g_build_filename (collection->local_dir, "galview.xml", NULL);
+	if (e_xml_save_file (filename, doc) == -1)
+		g_warning ("Unable to save view to %s - %s", filename, g_strerror (errno));
+	xmlFreeDoc (doc);
+	g_free (filename);
+}
+
+/**
+ * gal_view_collection_get_count
+ * @collection: The view collection to count
+ *
+ * Calculates the number of views in the given collection.
+ *
+ * Returns: The number of views in the collection.
+ */
+gint
+gal_view_collection_get_count (GalViewCollection *collection)
+{
+	g_return_val_if_fail (GAL_IS_VIEW_COLLECTION (collection), -1);
+
+	return collection->view_count;
+}
+
+/**
+ * gal_view_collection_get_view
+ * @collection: The view collection to query
+ * @n: The view to get.
+ *
+ * Returns: The nth view in the collection
+ */
+GalView *
+gal_view_collection_get_view (GalViewCollection *collection,
+                              gint n)
+{
+	g_return_val_if_fail (GAL_IS_VIEW_COLLECTION (collection), NULL);
+	g_return_val_if_fail (n < collection->view_count, NULL);
+	g_return_val_if_fail (n >= 0, NULL);
+
+	return collection->view_data[n]->view;
+}
+
+/**
+ * gal_view_collection_get_view_item
+ * @collection: The view collection to query
+ * @n: The view item to get.
+ *
+ * Returns: The nth view item in the collection
+ */
+GalViewCollectionItem *
+gal_view_collection_get_view_item (GalViewCollection *collection,
+                                   gint n)
+{
+	g_return_val_if_fail (GAL_IS_VIEW_COLLECTION (collection), NULL);
+	g_return_val_if_fail (n < collection->view_count, NULL);
+	g_return_val_if_fail (n >= 0, NULL);
+
+	return collection->view_data[n];
+}
+
+gint
+gal_view_collection_get_view_index_by_id (GalViewCollection *collection,
+                                          const gchar *view_id)
+{
+	gint i;
+	for (i = 0; i < collection->view_count; i++) {
+		if (!strcmp (collection->view_data[i]->id, view_id))
+			return i;
+	}
+	return -1;
+}
+
+gchar *
+gal_view_collection_get_view_id_by_index (GalViewCollection *collection,
+                                          gint n)
+{
+	g_return_val_if_fail (GAL_IS_VIEW_COLLECTION (collection), NULL);
+	g_return_val_if_fail (n < collection->view_count, NULL);
+	g_return_val_if_fail (n >= 0, NULL);
+
+	return g_strdup (collection->view_data[n]->id);
+}
+
+void
+gal_view_collection_append (GalViewCollection *collection,
+                            GalView *view)
+{
+	GalViewCollectionItem *item;
+
+	g_return_if_fail (GAL_IS_VIEW_COLLECTION (collection));
+	g_return_if_fail (GAL_IS_VIEW (view));
+
+	item = g_new (GalViewCollectionItem, 1);
+	item->ever_changed = TRUE;
+	item->changed = TRUE;
+	item->built_in = FALSE;
+	item->title = g_strdup (gal_view_get_title (view));
+	item->type = g_strdup (gal_view_get_type_code (view));
+	item->id = gal_view_generate_id (collection, view);
+	item->filename = g_strdup_printf ("%s.galview", item->id);
+	item->view = view;
+	item->collection = collection;
+	g_object_ref (view);
+
+	item->view_changed_id = g_signal_connect (
+		item->view, "changed",
+		G_CALLBACK (view_changed), item);
+
+	collection->view_data = g_renew (GalViewCollectionItem *, collection->view_data, collection->view_count + 1);
+	collection->view_data[collection->view_count] = item;
+	collection->view_count++;
+
+	gal_view_collection_changed (collection);
+}
+
+void
+gal_view_collection_delete_view (GalViewCollection *collection,
+                                 gint i)
+{
+	GalViewCollectionItem *item;
+
+	g_return_if_fail (GAL_IS_VIEW_COLLECTION (collection));
+	g_return_if_fail (i >= 0 && i < collection->view_count);
+
+	item = collection->view_data[i];
+	memmove (collection->view_data + i, collection->view_data + i + 1, (collection->view_count - i - 1) * sizeof (GalViewCollectionItem *));
+	collection->view_count--;
+	if (item->built_in) {
+		g_free (item->filename);
+		item->filename = NULL;
+
+		collection->removed_view_data = g_renew (GalViewCollectionItem *, collection->removed_view_data, collection->removed_view_count + 1);
+		collection->removed_view_data[collection->removed_view_count] = item;
+		collection->removed_view_count++;
+	} else {
+		gal_view_collection_item_free (item);
+	}
+
+	gal_view_collection_changed (collection);
+}
+
+void
+gal_view_collection_copy_view (GalViewCollection *collection,
+                               gint i)
+{
+	GalViewCollectionItem *item;
+	GalView *view;
+
+	g_return_if_fail (GAL_IS_VIEW_COLLECTION (collection));
+	g_return_if_fail (i >= 0 && i < collection->view_count);
+
+	view = collection->view_data[i]->view;
+
+	item = g_new (GalViewCollectionItem, 1);
+	item->ever_changed = TRUE;
+	item->changed = FALSE;
+	item->built_in = FALSE;
+	item->title = g_strdup (gal_view_get_title (view));
+	item->type = g_strdup (gal_view_get_type_code (view));
+	item->id = gal_view_generate_id (collection, view);
+	item->filename = g_strdup_printf ("%s.galview", item->id);
+	item->view = gal_view_clone (view);
+	item->collection = collection;
+
+	item->view_changed_id = g_signal_connect (
+		item->view, "changed",
+		G_CALLBACK (view_changed), item);
+
+	collection->view_data = g_renew (GalViewCollectionItem *, collection->view_data, collection->view_count + 1);
+	collection->view_data[collection->view_count] = item;
+	collection->view_count++;
+
+	gal_view_collection_changed (collection);
+}
+
+gboolean
+gal_view_collection_loaded (GalViewCollection *collection)
+{
+	return collection->loaded;
+}
+
+const gchar *
+gal_view_collection_append_with_title (GalViewCollection *collection,
+                                       const gchar *title,
+                                       GalView *view)
+{
+	GalViewCollectionItem *item;
+
+	g_return_val_if_fail (GAL_IS_VIEW_COLLECTION (collection), NULL);
+	g_return_val_if_fail (GAL_IS_VIEW (view), NULL);
+
+	gal_view_set_title (view, title);
+
+	d (g_print ("%s: %p\n", G_STRFUNC, view));
+
+	item = g_new (GalViewCollectionItem, 1);
+	item->ever_changed = TRUE;
+	item->changed = TRUE;
+	item->built_in = FALSE;
+	item->title = g_strdup (gal_view_get_title (view));
+	item->type = g_strdup (gal_view_get_type_code (view));
+	item->id = gal_view_generate_id (collection, view);
+	item->filename = g_strdup_printf ("%s.galview", item->id);
+	item->view = view;
+	item->collection = collection;
+	g_object_ref (view);
+
+	item->view_changed_id = g_signal_connect (
+		item->view, "changed",
+		G_CALLBACK (view_changed), item);
+
+	collection->view_data = g_renew (GalViewCollectionItem *, collection->view_data, collection->view_count + 1);
+	collection->view_data[collection->view_count] = item;
+	collection->view_count++;
+
+	gal_view_collection_changed (collection);
+	return item->id;
+}
+
+const gchar *
+gal_view_collection_set_nth_view (GalViewCollection *collection,
+                                  gint i,
+                                  GalView *view)
+{
+	GalViewCollectionItem *item;
+
+	g_return_val_if_fail (GAL_IS_VIEW_COLLECTION (collection), NULL);
+	g_return_val_if_fail (GAL_IS_VIEW (view), NULL);
+	g_return_val_if_fail (i >= 0, NULL);
+	g_return_val_if_fail (i < collection->view_count, NULL);
+
+	d (g_print ("%s: %p\n", G_STRFUNC, view));
+
+	item = collection->view_data[i];
+
+	gal_view_set_title (view, item->title);
+	g_object_ref (view);
+	if (item->view) {
+		g_signal_handler_disconnect (
+			item->view,
+			item->view_changed_id);
+		g_object_unref (item->view);
+	}
+	item->view = view;
+
+	item->ever_changed = TRUE;
+	item->changed = TRUE;
+	item->type = g_strdup (gal_view_get_type_code (view));
+
+	item->view_changed_id = g_signal_connect (
+		item->view, "changed",
+		G_CALLBACK (view_changed), item);
+
+	gal_view_collection_changed (collection);
+	return item->id;
+}
+
+const gchar *
+gal_view_collection_get_default_view (GalViewCollection *collection)
+{
+	return collection->default_view;
+}
+
+void
+gal_view_collection_set_default_view (GalViewCollection *collection,
+                                      const gchar *id)
+{
+	g_free (collection->default_view);
+	collection->default_view = g_strdup (id);
+	gal_view_collection_changed (collection);
+	collection->default_view_built_in = FALSE;
+}
+
diff --git a/e-util/gal-view-collection.h b/e-util/gal-view-collection.h
new file mode 100644
index 0000000..980f7c0
--- /dev/null
+++ b/e-util/gal-view-collection.h
@@ -0,0 +1,150 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef _GAL_VIEW_SET_H_
+#define _GAL_VIEW_SET_H_
+
+#include <e-util/gal-view-factory.h>
+
+G_BEGIN_DECLS
+
+#define GAL_VIEW_COLLECTION_TYPE        (gal_view_collection_get_type ())
+#define GAL_VIEW_COLLECTION(o)          (G_TYPE_CHECK_INSTANCE_CAST ((o), GAL_VIEW_COLLECTION_TYPE, GalViewCollection))
+#define GAL_VIEW_COLLECTION_CLASS(k)    (G_TYPE_CHECK_CLASS_CAST((k), GAL_VIEW_COLLECTION_TYPE, GalViewCollectionClass))
+#define GAL_IS_VIEW_COLLECTION(o)       (G_TYPE_CHECK_INSTANCE_TYPE ((o), GAL_VIEW_COLLECTION_TYPE))
+#define GAL_IS_VIEW_COLLECTION_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GAL_VIEW_COLLECTION_TYPE))
+#define GAL_VIEW_COLLECTION_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GAL_VIEW_COLLECTION_TYPE, GalViewCollectionClass))
+
+typedef struct GalViewCollectionItem GalViewCollectionItem;
+
+typedef struct {
+	GObject base;
+
+	GalViewCollectionItem **view_data;
+	gint view_count;
+
+	GList *factory_list;
+
+	GalViewCollectionItem **removed_view_data;
+	gint removed_view_count;
+
+	guint loaded : 1;
+	guint default_view_built_in : 1;
+
+	gchar *system_dir;
+	gchar *local_dir;
+
+	gchar *default_view;
+
+	gchar *title;
+} GalViewCollection;
+
+typedef struct {
+	GObjectClass parent_class;
+
+	/*
+	 * Signals
+	 */
+	void (*display_view) (GalViewCollection *collection,
+			      GalView    *view);
+	void (*changed)      (GalViewCollection *collection);
+} GalViewCollectionClass;
+
+struct GalViewCollectionItem {
+	GalView *view;
+	gchar *id;
+	guint changed : 1;
+	guint ever_changed : 1;
+	guint built_in : 1;
+	gchar *filename;
+	gchar *title;
+	gchar *type;
+	GalViewCollection *collection;
+	guint view_changed_id;
+};
+
+/* Standard functions */
+GType                  gal_view_collection_get_type                 (void);
+GalViewCollection     *gal_view_collection_new                      (void);
+
+void                   gal_view_collection_set_title                (GalViewCollection *collection,
+								     const gchar        *title);
+/* Set up the view collection.  Call these two functions before ever doing load or save and never call them again. */
+void                   gal_view_collection_set_storage_directories  (GalViewCollection *collection,
+								     const gchar        *system_dir,
+								     const gchar        *local_dir);
+void                   gal_view_collection_add_factory              (GalViewCollection *collection,
+								     GalViewFactory    *factory);
+
+/* Send the display view signal.  This function is deprecated. */
+void                   gal_view_collection_display_view             (GalViewCollection *collection,
+								     GalView           *view);
+
+/* Query the view collection. */
+gint                   gal_view_collection_get_count                (GalViewCollection *collection);
+GalView               *gal_view_collection_get_view                 (GalViewCollection *collection,
+								     gint                n);
+GalViewCollectionItem *gal_view_collection_get_view_item            (GalViewCollection *collection,
+								     gint                n);
+gint                    gal_view_collection_get_view_index_by_id     (GalViewCollection *collection,
+								     const gchar        *view_id);
+gchar                  *gal_view_collection_get_view_id_by_index     (GalViewCollection *collection,
+								     gint                n);
+
+/* Manipulate the view collection */
+void                   gal_view_collection_append                   (GalViewCollection *collection,
+								     GalView           *view);
+void                   gal_view_collection_delete_view              (GalViewCollection *collection,
+								     gint                i);
+void                   gal_view_collection_copy_view                (GalViewCollection *collection,
+								     gint                i);
+/* Call set_storage_directories and add factories for anything that
+ * might be found there before doing either of these. */
+void                   gal_view_collection_load                     (GalViewCollection *collection);
+void                   gal_view_collection_save                     (GalViewCollection *collection);
+gboolean               gal_view_collection_loaded                   (GalViewCollection *collection);
+
+/* Use factory list to load a GalView file. */
+GalView               *gal_view_collection_load_view_from_file      (GalViewCollection *collection,
+								     const gchar        *type,
+								     const gchar        *filename);
+
+/* Returns id of the new view.  These functions are used for
+ * GalViewInstanceSaveAsDialog. */
+const gchar            *gal_view_collection_append_with_title        (GalViewCollection *collection,
+								     const gchar        *title,
+								     GalView           *view);
+const gchar            *gal_view_collection_set_nth_view             (GalViewCollection *collection,
+								     gint                i,
+								     GalView           *view);
+
+const gchar            *gal_view_collection_get_default_view         (GalViewCollection *collection);
+void                   gal_view_collection_set_default_view         (GalViewCollection *collection,
+								     const gchar        *id);
+
+G_END_DECLS
+
+#endif /* _GAL_VIEW_COLLECTION_H_ */
diff --git a/e-util/gal-view-etable.c b/e-util/gal-view-etable.c
new file mode 100644
index 0000000..3f50e28
--- /dev/null
+++ b/e-util/gal-view-etable.c
@@ -0,0 +1,335 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "gal-view-etable.h"
+
+#include "e-table-config.h"
+
+G_DEFINE_TYPE (GalViewEtable, gal_view_etable, GAL_TYPE_VIEW)
+
+static void
+detach_table (GalViewEtable *view)
+{
+	if (view->table == NULL)
+		return;
+	if (view->table_state_changed_id) {
+		g_signal_handler_disconnect (
+			view->table,
+			view->table_state_changed_id);
+		view->table_state_changed_id = 0;
+	}
+	g_object_unref (view->table);
+	view->table = NULL;
+}
+
+static void
+detach_tree (GalViewEtable *view)
+{
+	if (view->tree == NULL)
+		return;
+	if (view->tree_state_changed_id) {
+		g_signal_handler_disconnect (
+			view->tree,
+			view->tree_state_changed_id);
+		view->tree_state_changed_id = 0;
+	}
+	g_object_unref (view->tree);
+	view->tree = NULL;
+}
+
+static void
+config_changed (ETableConfig *config,
+                GalViewEtable *view)
+{
+	ETableState *state;
+	if (view->state)
+		g_object_unref (view->state);
+	g_object_get (
+		config,
+		"state", &state,
+		NULL);
+	view->state = e_table_state_duplicate (state);
+	g_object_unref (state);
+
+	gal_view_changed (GAL_VIEW (view));
+}
+
+static void
+gal_view_etable_edit (GalView *view,
+                      GtkWindow *parent)
+{
+	GalViewEtable *etable_view = GAL_VIEW_ETABLE (view);
+	ETableConfig *config;
+
+	config = e_table_config_new (
+		etable_view->title,
+		etable_view->spec,
+		etable_view->state,
+		parent);
+
+	g_signal_connect (
+		config, "changed",
+		G_CALLBACK (config_changed), view);
+}
+
+static void
+gal_view_etable_load (GalView *view,
+                      const gchar *filename)
+{
+	e_table_state_load_from_file (GAL_VIEW_ETABLE (view)->state, filename);
+}
+
+static void
+gal_view_etable_save (GalView *view,
+                      const gchar *filename)
+{
+	e_table_state_save_to_file (GAL_VIEW_ETABLE (view)->state, filename);
+}
+
+static const gchar *
+gal_view_etable_get_title (GalView *view)
+{
+	return GAL_VIEW_ETABLE (view)->title;
+}
+
+static void
+gal_view_etable_set_title (GalView *view,
+                           const gchar *title)
+{
+	g_free (GAL_VIEW_ETABLE (view)->title);
+	GAL_VIEW_ETABLE (view)->title = g_strdup (title);
+}
+
+static const gchar *
+gal_view_etable_get_type_code (GalView *view)
+{
+	return "etable";
+}
+
+static GalView *
+gal_view_etable_clone (GalView *view)
+{
+	GalViewEtable *gve, *new;
+
+	gve = GAL_VIEW_ETABLE (view);
+
+	new = g_object_new (GAL_TYPE_VIEW_ETABLE, NULL);
+	new->spec  = gve->spec;
+	new->title = g_strdup (gve->title);
+	g_object_unref (new->state);
+	new->state = e_table_state_duplicate (gve->state);
+
+	g_object_ref (new->spec);
+
+	return GAL_VIEW (new);
+}
+
+static void
+gal_view_etable_dispose (GObject *object)
+{
+	GalViewEtable *view = GAL_VIEW_ETABLE (object);
+
+	gal_view_etable_detach (view);
+
+	g_free (view->title);
+	view->title = NULL;
+
+	if (view->spec)
+		g_object_unref (view->spec);
+	view->spec = NULL;
+
+	if (view->state)
+		g_object_unref (view->state);
+	view->state = NULL;
+
+	/* Chain up to parent's dispose() method. */
+	G_OBJECT_CLASS (gal_view_etable_parent_class)->dispose (object);
+}
+
+static void
+gal_view_etable_class_init (GalViewEtableClass *class)
+{
+	GalViewClass *gal_view_class  = GAL_VIEW_CLASS (class);
+	GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+	gal_view_class->edit          = gal_view_etable_edit;
+	gal_view_class->load          = gal_view_etable_load;
+	gal_view_class->save          = gal_view_etable_save;
+	gal_view_class->get_title     = gal_view_etable_get_title;
+	gal_view_class->set_title     = gal_view_etable_set_title;
+	gal_view_class->get_type_code = gal_view_etable_get_type_code;
+	gal_view_class->clone         = gal_view_etable_clone;
+
+	object_class->dispose         = gal_view_etable_dispose;
+}
+
+static void
+gal_view_etable_init (GalViewEtable *gve)
+{
+	gve->spec  = NULL;
+	gve->state = e_table_state_new ();
+	gve->title = NULL;
+}
+
+/**
+ * gal_view_etable_new
+ * @spec: The ETableSpecification that this view will be based upon.
+ * @title: The name of the new view.
+ *
+ * Returns a new GalViewEtable.  This is primarily for use by
+ * GalViewFactoryEtable.
+ *
+ * Returns: The new GalViewEtable.
+ */
+GalView *
+gal_view_etable_new (ETableSpecification *spec,
+                     const gchar *title)
+{
+	GalViewEtable *view;
+
+	g_return_val_if_fail (E_IS_TABLE_SPECIFICATION (spec), NULL);
+
+	view = g_object_new (GAL_TYPE_VIEW_ETABLE, NULL);
+
+	return gal_view_etable_construct (view, spec, title);
+}
+
+/**
+ * gal_view_etable_construct
+ * @view: The view to construct.
+ * @spec: The ETableSpecification that this view will be based upon.
+ * @title: The name of the new view.
+ *
+ * constructs the GalViewEtable.  To be used by subclasses and
+ * language bindings.
+ *
+ * Returns: The GalViewEtable.
+ */
+GalView *
+gal_view_etable_construct (GalViewEtable *view,
+                           ETableSpecification *spec,
+                           const gchar *title)
+{
+	g_return_val_if_fail (GAL_IS_VIEW_ETABLE (view), NULL);
+	g_return_val_if_fail (E_IS_TABLE_SPECIFICATION (spec), NULL);
+
+	view->spec = g_object_ref (spec);
+
+	if (view->state)
+		g_object_unref (view->state);
+	view->state = e_table_state_duplicate (spec->state);
+
+	view->title = g_strdup (title);
+
+	return GAL_VIEW (view);
+}
+
+void
+gal_view_etable_set_state (GalViewEtable *view,
+                           ETableState *state)
+{
+	g_return_if_fail (GAL_IS_VIEW_ETABLE (view));
+	g_return_if_fail (E_IS_TABLE_STATE (state));
+
+	if (view->state)
+		g_object_unref (view->state);
+	view->state = e_table_state_duplicate (state);
+
+	gal_view_changed (GAL_VIEW (view));
+}
+
+static void
+table_state_changed (ETable *table,
+                     GalViewEtable *view)
+{
+	ETableState *state;
+
+	state = e_table_get_state_object (table);
+	g_object_unref (view->state);
+	view->state = state;
+
+	gal_view_changed (GAL_VIEW (view));
+}
+
+static void
+tree_state_changed (ETree *tree,
+                    GalViewEtable *view)
+{
+	ETableState *state;
+
+	state = e_tree_get_state_object (tree);
+	g_object_unref (view->state);
+	view->state = state;
+
+	gal_view_changed (GAL_VIEW (view));
+}
+
+void
+gal_view_etable_attach_table (GalViewEtable *view,
+                              ETable *table)
+{
+	g_return_if_fail (GAL_IS_VIEW_ETABLE (view));
+	g_return_if_fail (E_IS_TABLE (table));
+
+	gal_view_etable_detach (view);
+
+	view->table = table;
+
+	e_table_set_state_object (view->table, view->state);
+	g_object_ref (view->table);
+	view->table_state_changed_id = g_signal_connect (
+		view->table, "state_change",
+		G_CALLBACK (table_state_changed), view);
+}
+
+void
+gal_view_etable_attach_tree (GalViewEtable *view,
+                             ETree *tree)
+{
+	g_return_if_fail (GAL_IS_VIEW_ETABLE (view));
+	g_return_if_fail (E_IS_TREE (tree));
+
+	gal_view_etable_detach (view);
+
+	view->tree = tree;
+
+	e_tree_set_state_object (view->tree, view->state);
+	g_object_ref (view->tree);
+	view->tree_state_changed_id = g_signal_connect (
+		view->tree, "state_change",
+		G_CALLBACK (tree_state_changed), view);
+}
+
+void
+gal_view_etable_detach (GalViewEtable *view)
+{
+	g_return_if_fail (GAL_IS_VIEW_ETABLE (view));
+
+	if (view->table != NULL)
+		detach_table (view);
+	if (view->tree != NULL)
+		detach_tree (view);
+}
diff --git a/e-util/gal-view-etable.h b/e-util/gal-view-etable.h
new file mode 100644
index 0000000..92f7e64
--- /dev/null
+++ b/e-util/gal-view-etable.h
@@ -0,0 +1,96 @@
+/*
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef GAL_VIEW_ETABLE_H
+#define GAL_VIEW_ETABLE_H
+
+#include <gtk/gtk.h>
+#include <e-util/gal-view.h>
+#include <e-util/e-table-state.h>
+#include <e-util/e-table-specification.h>
+#include <e-util/e-table.h>
+#include <e-util/e-tree.h>
+
+/* Standard GObject macros */
+#define GAL_TYPE_VIEW_ETABLE \
+	(gal_view_etable_get_type ())
+#define GAL_VIEW_ETABLE(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), GAL_TYPE_VIEW_ETABLE, GalViewEtable))
+#define GAL_VIEW_ETABLE_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), GAL_TYPE_VIEW_ETABLE, GalViewEtableClass))
+#define GAL_IS_VIEW_ETABLE(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), GAL_TYPE_VIEW_ETABLE))
+#define GAL_IS_VIEW_ETABLE_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), GAL_TYPE_VIEW_ETABLE))
+#define GAL_VIEW_ETABLE_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), GAL_TYPE_VIEW_ETABLE, GalViewEtableClass))
+
+G_BEGIN_DECLS
+
+typedef struct _GalViewEtable GalViewEtable;
+typedef struct _GalViewEtableClass GalViewEtableClass;
+
+struct _GalViewEtable {
+	GalView parent;
+
+	ETableSpecification *spec;
+	ETableState *state;
+	gchar *title;
+
+	ETable *table;
+	guint table_state_changed_id;
+
+	ETree *tree;
+	guint tree_state_changed_id;
+};
+
+struct _GalViewEtableClass {
+	GalViewClass parent_class;
+};
+
+GType		gal_view_etable_get_type	(void);
+GalView *	gal_view_etable_new		(ETableSpecification *spec,
+						 const gchar *title);
+GalView *	gal_view_etable_construct	(GalViewEtable *view,
+						 ETableSpecification *spec,
+						 const gchar *title);
+void		gal_view_etable_set_state	(GalViewEtable *view,
+						 ETableState *state);
+void		gal_view_etable_attach_table	(GalViewEtable *view,
+						 ETable *table);
+void		gal_view_etable_attach_tree	(GalViewEtable *view,
+						 ETree *tree);
+void		gal_view_etable_detach		(GalViewEtable *view);
+
+G_END_DECLS
+
+#endif /* GAL_VIEW_ETABLE_H */
diff --git a/e-util/gal-view-factory-etable.c b/e-util/gal-view-factory-etable.c
new file mode 100644
index 0000000..632c959
--- /dev/null
+++ b/e-util/gal-view-factory-etable.c
@@ -0,0 +1,195 @@
+/*
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <glib/gi18n.h>
+
+#include "gal-view-etable.h"
+#include "gal-view-factory-etable.h"
+
+#define GAL_VIEW_FACTORY_ETABLE_GET_PRIVATE(obj) \
+	(G_TYPE_INSTANCE_GET_PRIVATE \
+	((obj), GAL_TYPE_VIEW_FACTORY_ETABLE, GalViewFactoryEtablePrivate))
+
+struct _GalViewFactoryEtablePrivate {
+	ETableSpecification *specification;
+};
+
+enum {
+	PROP_0,
+	PROP_SPECIFICATION
+};
+
+G_DEFINE_TYPE (
+	GalViewFactoryEtable,
+	gal_view_factory_etable, GAL_TYPE_VIEW_FACTORY)
+
+static void
+view_factory_etable_set_specification (GalViewFactoryEtable *factory,
+                                       ETableSpecification *specification)
+{
+	g_return_if_fail (factory->priv->specification == NULL);
+	g_return_if_fail (E_IS_TABLE_SPECIFICATION (specification));
+
+	factory->priv->specification = g_object_ref (specification);
+}
+
+static void
+view_factory_etable_set_property (GObject *object,
+                                  guint property_id,
+                                  const GValue *value,
+                                  GParamSpec *pspec)
+{
+	switch (property_id) {
+		case PROP_SPECIFICATION:
+			view_factory_etable_set_specification (
+				GAL_VIEW_FACTORY_ETABLE (object),
+				g_value_get_object (value));
+			return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+view_factory_etable_get_property (GObject *object,
+                                  guint property_id,
+                                  GValue *value,
+                                  GParamSpec *pspec)
+{
+	switch (property_id) {
+		case PROP_SPECIFICATION:
+			g_value_set_object (
+				value,
+				gal_view_factory_etable_get_specification (
+				GAL_VIEW_FACTORY_ETABLE (object)));
+			return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+view_factory_etable_dispose (GObject *object)
+{
+	GalViewFactoryEtablePrivate *priv;
+
+	priv = GAL_VIEW_FACTORY_ETABLE_GET_PRIVATE (object);
+
+	if (priv->specification != NULL) {
+		g_object_unref (priv->specification);
+		priv->specification = NULL;
+	}
+
+	/* Chain up to parent's dispose() method. */
+	G_OBJECT_CLASS (gal_view_factory_etable_parent_class)->dispose (object);
+}
+
+static const gchar *
+view_factory_etable_get_title (GalViewFactory *factory)
+{
+	return _("Table");
+}
+
+static const gchar *
+view_factory_etable_get_type_code (GalViewFactory *factory)
+{
+	return "etable";
+}
+
+static GalView *
+view_factory_etable_new_view (GalViewFactory *factory,
+                              const gchar *name)
+{
+	GalViewFactoryEtablePrivate *priv;
+
+	priv = GAL_VIEW_FACTORY_ETABLE_GET_PRIVATE (factory);
+
+	return gal_view_etable_new (priv->specification, name);
+}
+
+static void
+gal_view_factory_etable_class_init (GalViewFactoryEtableClass *class)
+{
+	GObjectClass *object_class;
+	GalViewFactoryClass *view_factory_class;
+
+	g_type_class_add_private (class, sizeof (GalViewFactoryEtablePrivate));
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->set_property = view_factory_etable_set_property;
+	object_class->get_property = view_factory_etable_get_property;
+	object_class->dispose = view_factory_etable_dispose;
+
+	view_factory_class = GAL_VIEW_FACTORY_CLASS (class);
+	view_factory_class->get_title = view_factory_etable_get_title;
+	view_factory_class->get_type_code = view_factory_etable_get_type_code;
+	view_factory_class->new_view = view_factory_etable_new_view;
+
+	g_object_class_install_property (
+		object_class,
+		PROP_SPECIFICATION,
+		g_param_spec_object (
+			"specification",
+			NULL,
+			NULL,
+			E_TYPE_TABLE_SPECIFICATION,
+			G_PARAM_READWRITE |
+			G_PARAM_CONSTRUCT_ONLY));
+}
+
+static void
+gal_view_factory_etable_init (GalViewFactoryEtable *factory)
+{
+	factory->priv = GAL_VIEW_FACTORY_ETABLE_GET_PRIVATE (factory);
+}
+
+/**
+ * gal_view_etable_new:
+ * @specification: The spec to create GalViewEtables based upon.
+ *
+ * A new GalViewFactory for creating ETable views.  Create one of
+ * these and pass it to GalViewCollection for use.
+ *
+ * Returns: The new GalViewFactoryEtable.
+ */
+GalViewFactory *
+gal_view_factory_etable_new (ETableSpecification *specification)
+{
+	g_return_val_if_fail (E_IS_TABLE_SPECIFICATION (specification), NULL);
+
+	return g_object_new (
+		GAL_TYPE_VIEW_FACTORY_ETABLE,
+		"specification", specification, NULL);
+}
+
+ETableSpecification *
+gal_view_factory_etable_get_specification (GalViewFactoryEtable *factory)
+{
+	g_return_val_if_fail (GAL_IS_VIEW_FACTORY_ETABLE (factory), NULL);
+
+	return factory->priv->specification;
+}
diff --git a/e-util/gal-view-factory-etable.h b/e-util/gal-view-factory-etable.h
new file mode 100644
index 0000000..cc4b617
--- /dev/null
+++ b/e-util/gal-view-factory-etable.h
@@ -0,0 +1,77 @@
+/*
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef GAL_VIEW_FACTORY_ETABLE_H
+#define GAL_VIEW_FACTORY_ETABLE_H
+
+#include <gtk/gtk.h>
+#include <e-util/gal-view-factory.h>
+#include <e-util/e-table-specification.h>
+
+/* Standard GObject macros */
+#define GAL_TYPE_VIEW_FACTORY_ETABLE \
+	(gal_view_factory_etable_get_type ())
+#define GAL_VIEW_FACTORY_ETABLE(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), GAL_TYPE_VIEW_FACTORY_ETABLE, GalViewFactoryEtable))
+#define GAL_VIEW_FACTORY_ETABLE_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), GAL_TYPE_VIEW_FACTORY_ETABLE, GalViewFactoryEtableClass))
+#define GAL_IS_VIEW_FACTORY_ETABLE(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), GAL_TYPE_VIEW_FACTORY_ETABLE))
+#define GAL_IS_VIEW_FACTORY_ETABLE_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), GAL_TYPE_VIEW_FACTORY_ETABLE))
+#define GAL_VIEW_FACTORY_ETABLE_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), GAL_TYPE_VIEW_FACTORY_ETABLE, GalViewFactoryEtableClass))
+
+G_BEGIN_DECLS
+
+typedef struct _GalViewFactoryEtable GalViewFactoryEtable;
+typedef struct _GalViewFactoryEtableClass GalViewFactoryEtableClass;
+typedef struct _GalViewFactoryEtablePrivate GalViewFactoryEtablePrivate;
+
+struct _GalViewFactoryEtable {
+	GalViewFactory parent;
+	GalViewFactoryEtablePrivate *priv;
+};
+
+struct _GalViewFactoryEtableClass {
+	GalViewFactoryClass parent_class;
+};
+
+GType		gal_view_factory_etable_get_type (void);
+ETableSpecification *
+		gal_view_factory_etable_get_specification
+						(GalViewFactoryEtable *factory);
+GalViewFactory *gal_view_factory_etable_new	(ETableSpecification *specification);
+
+G_END_DECLS
+
+#endif /* GAL_VIEW_FACTORY_ETABLE_H */
diff --git a/e-util/gal-view-factory.c b/e-util/gal-view-factory.c
new file mode 100644
index 0000000..0e0dde0
--- /dev/null
+++ b/e-util/gal-view-factory.c
@@ -0,0 +1,101 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "gal-view-factory.h"
+
+G_DEFINE_TYPE (GalViewFactory, gal_view_factory, G_TYPE_OBJECT)
+
+/* XXX Should GalViewFactory be a GInterface? */
+
+static void
+gal_view_factory_class_init (GalViewFactoryClass *class)
+{
+}
+
+static void
+gal_view_factory_init (GalViewFactory *factory)
+{
+}
+
+/**
+ * gal_view_factory_get_title:
+ * @factory: a #GalViewFactory
+ *
+ * Returns: The title of the factory.
+ */
+const gchar *
+gal_view_factory_get_title (GalViewFactory *factory)
+{
+	GalViewFactoryClass *class;
+
+	g_return_val_if_fail (GAL_IS_VIEW_FACTORY (factory), NULL);
+
+	class = GAL_VIEW_FACTORY_GET_CLASS (factory);
+	g_return_val_if_fail (class->get_title != NULL, NULL);
+
+	return class->get_title (factory);
+}
+
+/**
+ * gal_view_factory_get_type_code:
+ * @factory: a #GalViewFactory
+ *
+ * Returns: The type code
+ */
+const gchar *
+gal_view_factory_get_type_code (GalViewFactory *factory)
+{
+	GalViewFactoryClass *class;
+
+	g_return_val_if_fail (GAL_IS_VIEW_FACTORY (factory), NULL);
+
+	class = GAL_VIEW_FACTORY_GET_CLASS (factory);
+	g_return_val_if_fail (class->get_type_code != NULL, NULL);
+
+	return class->get_type_code (factory);
+}
+
+/**
+ * gal_view_factory_new_view:
+ * @factory: a #GalViewFactory
+ * @name: the name for the view
+ *
+ * Returns: The new view
+ */
+GalView *
+gal_view_factory_new_view (GalViewFactory *factory,
+                           const gchar *name)
+{
+	GalViewFactoryClass *class;
+
+	g_return_val_if_fail (GAL_IS_VIEW_FACTORY (factory), NULL);
+
+	class = GAL_VIEW_FACTORY_GET_CLASS (factory);
+	g_return_val_if_fail (class->new_view != NULL, NULL);
+
+	return class->new_view (factory, name);
+}
+
diff --git a/e-util/gal-view-factory.h b/e-util/gal-view-factory.h
new file mode 100644
index 0000000..abdcacd
--- /dev/null
+++ b/e-util/gal-view-factory.h
@@ -0,0 +1,85 @@
+/*
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef GAL_VIEW_FACTORY_H
+#define GAL_VIEW_FACTORY_H
+
+#include <e-util/gal-view.h>
+
+/* Standard GObject macros */
+#define GAL_TYPE_VIEW_FACTORY \
+	(gal_view_factory_get_type ())
+#define GAL_VIEW_FACTORY(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), GAL_TYPE_VIEW_FACTORY, GalViewFactory))
+#define GAL_VIEW_FACTORY_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), GAL_TYPE_VIEW_FACTORY, GalViewFactoryClass))
+#define GAL_IS_VIEW_FACTORY(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), GAL_TYPE_VIEW_FACTORY))
+#define GAL_IS_VIEW_FACTORY_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), GAL_TYPE_VIEW_FACTORY))
+#define GAL_VIEW_FACTORY_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), GAL_TYPE_VIEW_FACTORY, GalViewFactoryClass))
+
+G_BEGIN_DECLS
+
+typedef struct _GalViewFactory GalViewFactory;
+typedef struct _GalViewFactoryClass GalViewFactoryClass;
+
+struct _GalViewFactory {
+	GObject parent;
+};
+
+struct _GalViewFactoryClass {
+	GObjectClass parent_class;
+
+	/* Methods */
+	const gchar *	(*get_title)		(GalViewFactory *factory);
+	const gchar *	(*get_type_code)	(GalViewFactory *factory);
+	GalView *	(*new_view)		(GalViewFactory *factory,
+						 const gchar *name);
+};
+
+GType		gal_view_factory_get_type	(void);
+const gchar *	gal_view_factory_get_title	(GalViewFactory *factory);
+
+/* Returns the code for use in identifying this type of object in the
+ * view list.  This identifier should identify this as being the
+ * unique factory for xml files which were written out with this
+ * identifier.  Thus each factory should have a unique type code.  */
+const gchar *	gal_view_factory_get_type_code	(GalViewFactory *factory);
+
+GalView *	gal_view_factory_new_view	(GalViewFactory *factory,
+						 const gchar *name);
+
+G_END_DECLS
+
+#endif /* GAL_VIEW_FACTORY_H */
diff --git a/e-util/gal-view-instance-save-as-dialog.c b/e-util/gal-view-instance-save-as-dialog.c
new file mode 100644
index 0000000..c71892e
--- /dev/null
+++ b/e-util/gal-view-instance-save-as-dialog.c
@@ -0,0 +1,359 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "gal-view-instance-save-as-dialog.h"
+
+#include <glib/gi18n.h>
+
+#include "e-misc-utils.h"
+#include "e-util-private.h"
+#include "gal-define-views-model.h"
+#include "gal-view-new-dialog.h"
+
+G_DEFINE_TYPE (GalViewInstanceSaveAsDialog, gal_view_instance_save_as_dialog, GTK_TYPE_DIALOG)
+
+enum {
+	PROP_0,
+	PROP_INSTANCE
+};
+
+enum {
+	COL_GALVIEW_NAME,
+	COL_GALVIEW_DATA
+};
+
+/* Static functions */
+static void
+gal_view_instance_save_as_dialog_set_instance (GalViewInstanceSaveAsDialog *dialog,
+                                               GalViewInstance *instance)
+{
+	gint i;
+	GtkListStore *store;
+	GtkCellRenderer *renderer;
+	dialog->instance = instance;
+
+	store = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_POINTER);
+
+	for (i = 0; i < instance->collection->view_count; i++) {
+		GalViewCollectionItem *item = instance->collection->view_data[i];
+		GtkTreeIter iter;
+		gchar *title = NULL;
+
+		/* hide built in views */
+		/*if (item->built_in == 1)
+			continue;*/
+
+		title = e_str_without_underscores (item->title);
+
+		gtk_list_store_append (store, &iter);
+		gtk_list_store_set (
+			store, &iter,
+			COL_GALVIEW_NAME, title,
+			COL_GALVIEW_DATA, item,
+			-1);
+
+		g_free (title);
+	}
+
+	gtk_tree_sortable_set_sort_column_id (
+		GTK_TREE_SORTABLE (store),
+			COL_GALVIEW_NAME, GTK_SORT_ASCENDING);
+
+	/* attaching treeview to model */
+	gtk_tree_view_set_model (dialog->treeview, GTK_TREE_MODEL (store));
+	gtk_tree_view_set_search_column (dialog->treeview, COL_GALVIEW_NAME);
+
+	dialog->model = GTK_TREE_MODEL (store);
+
+	renderer = gtk_cell_renderer_text_new ();
+
+	gtk_tree_view_insert_column_with_attributes (
+		dialog->treeview,
+		COL_GALVIEW_NAME, _("Name"),
+		renderer, "text", COL_GALVIEW_NAME,
+		NULL);
+
+	/* set sort column */
+	gtk_tree_sortable_set_sort_column_id (
+		GTK_TREE_SORTABLE (dialog->model),
+		COL_GALVIEW_NAME, GTK_SORT_ASCENDING);
+}
+
+static void
+gvisad_setup_validate_button (GalViewInstanceSaveAsDialog *dialog)
+{
+	if ((dialog->toggle == GAL_VIEW_INSTANCE_SAVE_AS_DIALOG_TOGGLE_CREATE
+	      && g_utf8_strlen (gtk_entry_get_text (GTK_ENTRY (dialog->entry_create)), -1) > 0)
+	    || dialog->toggle == GAL_VIEW_INSTANCE_SAVE_AS_DIALOG_TOGGLE_REPLACE) {
+		gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog), GTK_RESPONSE_OK, TRUE);
+	} else {
+		gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog), GTK_RESPONSE_OK, FALSE);
+	}
+}
+
+static void
+gvisad_setup_radio_buttons (GalViewInstanceSaveAsDialog *dialog)
+{
+	GtkWidget        *widget;
+
+	widget = dialog->scrolledwindow;
+	if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (dialog->radiobutton_replace))) {
+		GtkTreeIter       iter;
+		GtkTreeSelection *selection;
+
+		selection = gtk_tree_view_get_selection (dialog->treeview);
+		if (!gtk_tree_selection_get_selected (selection, &dialog->model, &iter)) {
+			if (gtk_tree_model_get_iter_first (dialog->model, &iter)) {
+				gtk_tree_selection_select_iter (selection, &iter);
+			}
+		}
+
+		gtk_widget_set_sensitive (widget, TRUE);
+		dialog->toggle = GAL_VIEW_INSTANCE_SAVE_AS_DIALOG_TOGGLE_REPLACE;
+	} else {
+		gtk_widget_set_sensitive (widget, FALSE);
+	}
+
+	widget = dialog->entry_create;
+	if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (dialog->radiobutton_create))) {
+		gtk_widget_set_sensitive (widget, TRUE);
+		dialog->toggle = GAL_VIEW_INSTANCE_SAVE_AS_DIALOG_TOGGLE_CREATE;
+	} else {
+		gtk_widget_set_sensitive (widget, FALSE);
+	}
+
+	gvisad_setup_validate_button (dialog);
+}
+
+static void
+gvisad_radio_toggled (GtkWidget *widget,
+                      GalViewInstanceSaveAsDialog *dialog)
+{
+	gvisad_setup_radio_buttons (dialog);
+}
+
+static void
+gvisad_entry_changed (GtkWidget *widget,
+                      GalViewInstanceSaveAsDialog *dialog)
+{
+	gvisad_setup_validate_button (dialog);
+}
+
+/* Method override implementations */
+static void
+gal_view_instance_save_as_dialog_set_property (GObject *object,
+                                               guint property_id,
+                                               const GValue *value,
+                                               GParamSpec *pspec)
+{
+	GalViewInstanceSaveAsDialog *dialog;
+
+	dialog = GAL_VIEW_INSTANCE_SAVE_AS_DIALOG (object);
+
+	switch (property_id) {
+	case PROP_INSTANCE:
+		if (g_value_get_object (value))
+			gal_view_instance_save_as_dialog_set_instance (dialog, GAL_VIEW_INSTANCE (g_value_get_object (value)));
+		else
+			gal_view_instance_save_as_dialog_set_instance (dialog, NULL);
+		break;
+
+	default:
+		return;
+	}
+}
+
+static void
+gal_view_instance_save_as_dialog_get_property (GObject *object,
+                                               guint property_id,
+                                               GValue *value,
+                                               GParamSpec *pspec)
+{
+	GalViewInstanceSaveAsDialog *dialog;
+
+	dialog = GAL_VIEW_INSTANCE_SAVE_AS_DIALOG (object);
+
+	switch (property_id) {
+	case PROP_INSTANCE:
+		g_value_set_object (value, dialog->instance);
+		break;
+
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+		break;
+	}
+}
+
+static void
+gal_view_instance_save_as_dialog_dispose (GObject *object)
+{
+	GalViewInstanceSaveAsDialog *gal_view_instance_save_as_dialog = GAL_VIEW_INSTANCE_SAVE_AS_DIALOG (object);
+
+	if (gal_view_instance_save_as_dialog->builder)
+		g_object_unref (gal_view_instance_save_as_dialog->builder);
+	gal_view_instance_save_as_dialog->builder = NULL;
+
+	/* Chain up to parent's dispose() method. */
+	G_OBJECT_CLASS (gal_view_instance_save_as_dialog_parent_class)->dispose (object);
+}
+
+/* Init functions */
+static void
+gal_view_instance_save_as_dialog_class_init (GalViewInstanceSaveAsDialogClass *class)
+{
+	GObjectClass *object_class;
+
+	object_class = (GObjectClass *) class;
+
+	object_class->set_property = gal_view_instance_save_as_dialog_set_property;
+	object_class->get_property = gal_view_instance_save_as_dialog_get_property;
+	object_class->dispose      = gal_view_instance_save_as_dialog_dispose;
+
+	g_object_class_install_property (
+		object_class,
+		PROP_INSTANCE,
+		g_param_spec_object (
+			"instance",
+			"Instance",
+			NULL,
+			GAL_VIEW_INSTANCE_TYPE,
+			G_PARAM_READWRITE));
+}
+
+static void
+gal_view_instance_save_as_dialog_init (GalViewInstanceSaveAsDialog *dialog)
+{
+	GtkWidget *content_area;
+	GtkWidget *widget;
+
+	dialog->instance = NULL;
+	dialog->model = NULL;
+	dialog->collection = NULL;
+
+	dialog->builder = gtk_builder_new ();
+	e_load_ui_builder_definition (
+		dialog->builder, "gal-view-instance-save-as-dialog.ui");
+
+	widget = e_builder_get_widget (dialog->builder, "vbox-top");
+	content_area = gtk_dialog_get_content_area (GTK_DIALOG (dialog));
+	gtk_box_pack_start (GTK_BOX (content_area), widget, TRUE, TRUE, 0);
+
+	/* TODO: add position/size saving/restoring */
+	gtk_container_set_border_width (GTK_CONTAINER (dialog), 5);
+	gtk_window_set_default_size (GTK_WINDOW (dialog), 300, 360);
+
+	gtk_dialog_add_buttons (
+		GTK_DIALOG (dialog),
+		GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+		GTK_STOCK_SAVE, GTK_RESPONSE_OK,
+		NULL);
+
+	dialog->scrolledwindow = e_builder_get_widget (dialog->builder, "scrolledwindow2");
+	dialog->treeview = GTK_TREE_VIEW (e_builder_get_widget (dialog->builder, "custom-replace"));
+	dialog->entry_create = e_builder_get_widget (dialog->builder, "entry-create");
+	dialog->radiobutton_replace = e_builder_get_widget (dialog->builder, "radiobutton-replace");
+	dialog->radiobutton_create = e_builder_get_widget (dialog->builder, "radiobutton-create");
+
+	gtk_tree_view_set_reorderable (GTK_TREE_VIEW (dialog->treeview), FALSE);
+	gtk_tree_view_set_headers_visible (dialog->treeview, FALSE);
+
+	g_signal_connect (
+		dialog->radiobutton_replace, "toggled",
+		G_CALLBACK (gvisad_radio_toggled), dialog);
+	g_signal_connect (
+		dialog->radiobutton_create, "toggled",
+		G_CALLBACK (gvisad_radio_toggled), dialog);
+	g_signal_connect (
+		dialog->entry_create, "changed",
+		G_CALLBACK (gvisad_entry_changed), dialog);
+
+	gvisad_setup_radio_buttons (dialog);
+	gvisad_setup_validate_button (dialog);
+
+	gtk_window_set_title (GTK_WINDOW (dialog), _("Save Current View"));
+	gtk_widget_show (GTK_WIDGET (dialog));
+}
+
+/* External methods */
+/**
+ * gal_view_instance_save_as_dialog_new
+ *
+ * Returns a new dialog for defining views.
+ *
+ * Returns: The GalViewInstanceSaveAsDialog.
+ */
+GtkWidget *
+gal_view_instance_save_as_dialog_new (GalViewInstance *instance)
+{
+	GtkWidget *widget = g_object_new (GAL_TYPE_VIEW_INSTANCE_SAVE_AS_DIALOG, NULL);
+	gal_view_instance_save_as_dialog_set_instance (GAL_VIEW_INSTANCE_SAVE_AS_DIALOG (widget), instance);
+	return widget;
+}
+
+void
+gal_view_instance_save_as_dialog_save (GalViewInstanceSaveAsDialog *dialog)
+{
+	GalView *view = gal_view_instance_get_current_view (dialog->instance);
+	const gchar *title;
+	gint n;
+	const gchar *id = NULL;
+	GalViewCollectionItem *item;
+
+	view = gal_view_clone (view);
+	switch (dialog->toggle) {
+	case GAL_VIEW_INSTANCE_SAVE_AS_DIALOG_TOGGLE_REPLACE:
+		if (dialog->treeview) {
+			GtkTreeIter iter;
+			GtkTreeSelection *selection;
+
+			selection = gtk_tree_view_get_selection (dialog->treeview);
+			if (gtk_tree_selection_get_selected (selection, &dialog->model, &iter)) {
+				gtk_tree_model_get (dialog->model, &iter, COL_GALVIEW_DATA, &item, -1);
+
+				for (n = 0; n < dialog->instance->collection->view_count; n++) {
+					if (item == dialog->instance->collection->view_data[n]) {
+						id = gal_view_collection_set_nth_view (dialog->instance->collection, n, view);
+						gal_view_collection_save (dialog->instance->collection);
+					}
+				}
+			}
+
+		}
+		break;
+
+	case GAL_VIEW_INSTANCE_SAVE_AS_DIALOG_TOGGLE_CREATE:
+		if (dialog->entry_create && GTK_IS_ENTRY (dialog->entry_create)) {
+			title = gtk_entry_get_text (GTK_ENTRY (dialog->entry_create));
+			id = gal_view_collection_append_with_title (dialog->instance->collection, title, view);
+			gal_view_collection_save (dialog->instance->collection);
+		}
+		break;
+	}
+
+	if (id) {
+		gal_view_instance_set_current_view_id (dialog->instance, id);
+	}
+}
diff --git a/e-util/gal-view-instance-save-as-dialog.h b/e-util/gal-view-instance-save-as-dialog.h
new file mode 100644
index 0000000..47b76b1
--- /dev/null
+++ b/e-util/gal-view-instance-save-as-dialog.h
@@ -0,0 +1,92 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef GAL_VIEW_INSTANCE_SAVE_AS_DIALOG_H
+#define GAL_VIEW_INSTANCE_SAVE_AS_DIALOG_H
+
+#include <gtk/gtk.h>
+#include <e-util/gal-view-collection.h>
+#include <e-util/gal-view-instance.h>
+
+/* Standard GObject macros */
+#define GAL_TYPE_VIEW_INSTANCE_SAVE_AS_DIALOG \
+	(gal_view_instance_save_as_dialog_get_type ())
+#define GAL_VIEW_INSTANCE_SAVE_AS_DIALOG(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), GAL_TYPE_VIEW_INSTANCE_SAVE_AS_DIALOG, GalViewInstanceSaveAsDialog))
+#define GAL_VIEW_INSTANCE_SAVE_AS_DIALOG_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), GAL_TYPE_VIEW_INSTANCE_SAVE_AS_DIALOG, GalViewInstanceSaveAsDialogClass))
+#define GAL_IS_VIEW_INSTANCE_SAVE_AS_DIALOG(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), GAL_TYPE_VIEW_INSTANCE_SAVE_AS_DIALOG))
+#define GAL_IS_VIEW_INSTANCE_SAVE_AS_DIALOG_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), GAL_TYPE_VIEW_INSTANCE_SAVE_AS_DIALOG))
+#define GAL_VIEW_INSTANCE_SAVE_AS_DIALOG_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), GAL_TYPE_VIEW_INSTANCE_SAVE_AS_DIALOG, GalViewInstanceSaveAsDialogClass))
+
+G_BEGIN_DECLS
+
+typedef struct _GalViewInstanceSaveAsDialog GalViewInstanceSaveAsDialog;
+typedef struct _GalViewInstanceSaveAsDialogClass GalViewInstanceSaveAsDialogClass;
+
+typedef enum {
+	GAL_VIEW_INSTANCE_SAVE_AS_DIALOG_TOGGLE_REPLACE,
+	GAL_VIEW_INSTANCE_SAVE_AS_DIALOG_TOGGLE_CREATE
+} GalViewInstanceSaveAsDialogToggle;
+
+struct _GalViewInstanceSaveAsDialog {
+	GtkDialog parent;
+
+	/* item specific fields */
+	GtkBuilder *builder;
+	GtkTreeView *treeview;
+	GtkTreeModel *model;
+
+	GtkWidget *scrolledwindow, *radiobutton_replace;
+	GtkWidget *entry_create, *radiobutton_create;
+
+	GalViewInstance *instance;
+	GalViewCollection *collection;
+
+	GalViewInstanceSaveAsDialogToggle toggle;
+};
+
+struct _GalViewInstanceSaveAsDialogClass {
+	GtkDialogClass parent_class;
+};
+
+GType		gal_view_instance_save_as_dialog_get_type (void);
+GtkWidget *	gal_view_instance_save_as_dialog_new
+					(GalViewInstance *instance);
+void		gal_view_instance_save_as_dialog_save
+					(GalViewInstanceSaveAsDialog *dialog);
+
+G_END_DECLS
+
+#endif /* GAL_VIEW_INSTANCE_SAVE_AS_DIALOG_H */
diff --git a/widgets/menus/gal-view-instance-save-as-dialog.ui b/e-util/gal-view-instance-save-as-dialog.ui
similarity index 100%
rename from widgets/menus/gal-view-instance-save-as-dialog.ui
rename to e-util/gal-view-instance-save-as-dialog.ui
diff --git a/e-util/gal-view-instance.c b/e-util/gal-view-instance.c
new file mode 100644
index 0000000..e0a107f
--- /dev/null
+++ b/e-util/gal-view-instance.c
@@ -0,0 +1,502 @@
+/*
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "gal-view-instance.h"
+
+#include <ctype.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <sys/stat.h>
+
+#include <gtk/gtk.h>
+#include <libxml/parser.h>
+#include <glib/gstdio.h>
+#include <glib/gi18n.h>
+
+#include <libedataserver/libedataserver.h>
+
+#include "e-unicode.h"
+#include "e-xml-utils.h"
+#include "gal-define-views-dialog.h"
+#include "gal-view-instance-save-as-dialog.h"
+
+G_DEFINE_TYPE (GalViewInstance, gal_view_instance, G_TYPE_OBJECT)
+
+#define d(x)
+
+enum {
+	DISPLAY_VIEW,
+	CHANGED,
+	LOADED,
+	LAST_SIGNAL
+};
+
+static guint gal_view_instance_signals[LAST_SIGNAL] = { 0, };
+
+static void
+gal_view_instance_changed (GalViewInstance *instance)
+{
+	g_return_if_fail (instance != NULL);
+	g_return_if_fail (GAL_IS_VIEW_INSTANCE (instance));
+
+	g_signal_emit (
+		instance,
+		gal_view_instance_signals[CHANGED], 0);
+}
+
+static void
+gal_view_instance_display_view (GalViewInstance *instance,
+                                GalView *view)
+{
+	g_return_if_fail (instance != NULL);
+	g_return_if_fail (GAL_IS_VIEW_INSTANCE (instance));
+
+	g_signal_emit (
+		instance,
+		gal_view_instance_signals[DISPLAY_VIEW], 0,
+		view);
+}
+
+static void
+save_current_view (GalViewInstance *instance)
+{
+	xmlDoc *doc;
+	xmlNode *root;
+
+	doc = xmlNewDoc ((const guchar *)"1.0");
+	root = xmlNewNode (NULL, (const guchar *)"GalViewCurrentView");
+	xmlDocSetRootElement (doc, root);
+
+	if (instance->current_id)
+		e_xml_set_string_prop_by_name (root, (const guchar *)"current_view", instance->current_id);
+	if (instance->current_type)
+		e_xml_set_string_prop_by_name (root, (const guchar *)"current_view_type", instance->current_type);
+
+	if (e_xml_save_file (instance->current_view_filename, doc) == -1)
+		g_warning ("Unable to save view to %s - %s", instance->current_view_filename, g_strerror (errno));
+	xmlFreeDoc (doc);
+}
+
+static void
+view_changed (GalView *view,
+              GalViewInstance *instance)
+{
+	if (instance->current_id != NULL) {
+		g_free (instance->current_id);
+		instance->current_id = NULL;
+		save_current_view (instance);
+		gal_view_instance_changed (instance);
+	}
+
+	gal_view_save (view, instance->custom_filename);
+}
+
+static void
+disconnect_view (GalViewInstance *instance)
+{
+	if (instance->current_view) {
+		if (instance->view_changed_id) {
+			g_signal_handler_disconnect (
+				instance->current_view,
+				instance->view_changed_id);
+		}
+
+		g_object_unref (instance->current_view);
+	}
+	g_free (instance->current_type);
+	g_free (instance->current_title);
+	instance->current_title = NULL;
+	instance->current_type = NULL;
+	instance->view_changed_id = 0;
+	instance->current_view = NULL;
+}
+
+static void
+connect_view (GalViewInstance *instance,
+              GalView *view)
+{
+	if (instance->current_view)
+		disconnect_view (instance);
+	instance->current_view = view;
+
+	instance->current_title = g_strdup (gal_view_get_title (view));
+	instance->current_type = g_strdup (gal_view_get_type_code (view));
+	instance->view_changed_id = g_signal_connect (
+		instance->current_view, "changed",
+		G_CALLBACK (view_changed), instance);
+
+	gal_view_instance_display_view (instance, instance->current_view);
+}
+
+static void
+gal_view_instance_dispose (GObject *object)
+{
+	GalViewInstance *instance = GAL_VIEW_INSTANCE (object);
+
+	if (instance->collection) {
+		if (instance->collection_changed_id) {
+			g_signal_handler_disconnect (
+				instance->collection,
+				instance->collection_changed_id);
+		}
+		g_object_unref (instance->collection);
+	}
+
+	g_free (instance->instance_id);
+	g_free (instance->custom_filename);
+	g_free (instance->current_view_filename);
+
+	g_free (instance->current_id);
+	disconnect_view (instance);
+
+	g_free (instance->default_view);
+
+	/* Chain up to parent's dispose() method. */
+	G_OBJECT_CLASS (gal_view_instance_parent_class)->dispose (object);
+}
+
+static void
+gal_view_instance_class_init (GalViewInstanceClass *class)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+	object_class->dispose = gal_view_instance_dispose;
+
+	gal_view_instance_signals[DISPLAY_VIEW] = g_signal_new (
+		"display_view",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (GalViewInstanceClass, display_view),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__OBJECT,
+		G_TYPE_NONE, 1,
+		GAL_TYPE_VIEW);
+
+	gal_view_instance_signals[CHANGED] = g_signal_new (
+		"changed",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (GalViewInstanceClass, changed),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__VOID,
+		G_TYPE_NONE, 0);
+
+	gal_view_instance_signals[LOADED] = g_signal_new (
+		"loaded",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_FIRST,
+		G_STRUCT_OFFSET (GalViewInstanceClass, loaded),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__VOID,
+		G_TYPE_NONE, 0);
+
+	class->display_view = NULL;
+	class->changed      = NULL;
+}
+
+static void
+gal_view_instance_init (GalViewInstance *instance)
+{
+	instance->collection            = NULL;
+
+	instance->instance_id           = NULL;
+	instance->custom_filename       = NULL;
+	instance->current_view_filename = NULL;
+
+	instance->current_title         = NULL;
+	instance->current_type          = NULL;
+	instance->current_id            = NULL;
+	instance->current_view          = NULL;
+
+	instance->view_changed_id       = 0;
+	instance->collection_changed_id       = 0;
+
+	instance->loaded = FALSE;
+	instance->default_view = NULL;
+}
+
+static void
+collection_changed (GalView *view,
+                    GalViewInstance *instance)
+{
+	if (instance->current_id) {
+		gchar *view_id = instance->current_id;
+		instance->current_id = NULL;
+		gal_view_instance_set_current_view_id (instance, view_id);
+		g_free (view_id);
+	}
+}
+
+static void
+load_current_view (GalViewInstance *instance)
+{
+	xmlDoc *doc = NULL;
+	xmlNode *root;
+	GalView *view = NULL;
+
+	if (g_file_test (instance->current_view_filename, G_FILE_TEST_IS_REGULAR)) {
+#ifdef G_OS_WIN32
+		gchar *locale_filename = g_win32_locale_filename_from_utf8 (instance->current_view_filename);
+		if (locale_filename != NULL)
+			doc = xmlParseFile (locale_filename);
+		g_free (locale_filename);
+#else
+		doc = xmlParseFile (instance->current_view_filename);
+#endif
+	}
+
+	if (doc == NULL) {
+		instance->current_id = g_strdup (gal_view_instance_get_default_view (instance));
+
+		if (instance->current_id) {
+			gint index = gal_view_collection_get_view_index_by_id (
+				instance->collection,
+				instance->current_id);
+
+			if (index != -1) {
+				view = gal_view_collection_get_view (
+					instance->collection, index);
+				view = gal_view_clone (view);
+				connect_view (instance, view);
+			}
+		}
+		return;
+	}
+
+	root = xmlDocGetRootElement (doc);
+	instance->current_id = e_xml_get_string_prop_by_name_with_default (root, (const guchar *)"current_view", NULL);
+
+	if (instance->current_id != NULL) {
+		gint index = gal_view_collection_get_view_index_by_id (
+			instance->collection,
+			instance->current_id);
+
+		if (index != -1) {
+			view = gal_view_collection_get_view (
+				instance->collection, index);
+			view = gal_view_clone (view);
+		}
+	}
+	if (view == NULL) {
+		gchar *type;
+		type = e_xml_get_string_prop_by_name_with_default (root, (const guchar *)"current_view_type", NULL);
+		view = gal_view_collection_load_view_from_file (
+			instance->collection, type,
+			instance->custom_filename);
+		g_free (type);
+	}
+
+	connect_view (instance, view);
+
+	xmlFreeDoc (doc);
+}
+
+/**
+ * gal_view_instance_new:
+ * @collection: This %GalViewCollection should be loaded before being passed to this function.
+ * @instance_id: Which instance of this type of object is this (for most of evo, this is the folder id.)
+ *
+ * Create a new %GalViewInstance.
+ *
+ * Return value: The new %GalViewInstance.
+ **/
+GalViewInstance *
+gal_view_instance_new (GalViewCollection *collection,
+                       const gchar *instance_id)
+{
+	GalViewInstance *instance = g_object_new (GAL_VIEW_INSTANCE_TYPE, NULL);
+	if (gal_view_instance_construct (instance, collection, instance_id))
+		return instance;
+	else {
+		g_object_unref (instance);
+		return NULL;
+	}
+}
+
+GalViewInstance *
+gal_view_instance_construct (GalViewInstance *instance,
+                             GalViewCollection *collection,
+                             const gchar *instance_id)
+{
+	gchar *filename;
+	gchar *safe_id;
+
+	g_return_val_if_fail (gal_view_collection_loaded (collection), NULL);
+
+	instance->collection = collection;
+	if (collection)
+		g_object_ref (collection);
+	instance->collection_changed_id = g_signal_connect (
+		collection, "changed",
+		G_CALLBACK (collection_changed), instance);
+
+	if (instance_id)
+		instance->instance_id = g_strdup (instance_id);
+	else
+		instance->instance_id = g_strdup ("");
+
+	safe_id = g_strdup (instance->instance_id);
+	e_filename_make_safe (safe_id);
+
+	filename = g_strdup_printf ("custom_view-%s.xml", safe_id);
+	instance->custom_filename = g_build_filename (instance->collection->local_dir, filename, NULL);
+	g_free (filename);
+
+	filename = g_strdup_printf ("current_view-%s.xml", safe_id);
+	instance->current_view_filename = g_build_filename (instance->collection->local_dir, filename, NULL);
+	g_free (filename);
+
+	g_free (safe_id);
+
+	return instance;
+}
+
+/* Manipulate the current view. */
+gchar *
+gal_view_instance_get_current_view_id (GalViewInstance *instance)
+{
+	if (instance->current_id && gal_view_collection_get_view_index_by_id (instance->collection, instance->current_id) != -1)
+		return g_strdup (instance->current_id);
+	else
+		return NULL;
+}
+
+void
+gal_view_instance_set_current_view_id (GalViewInstance *instance,
+                                       const gchar *view_id)
+{
+	GalView *view;
+	gint index;
+
+	g_return_if_fail (instance != NULL);
+	g_return_if_fail (GAL_IS_VIEW_INSTANCE (instance));
+
+	d (g_print ("%s: view_id set to %s\n", G_STRFUNC, view_id));
+
+	if (instance->current_id && !strcmp (instance->current_id, view_id))
+		return;
+
+	g_free (instance->current_id);
+	instance->current_id = g_strdup (view_id);
+
+	index = gal_view_collection_get_view_index_by_id (instance->collection, view_id);
+	if (index != -1) {
+		view = gal_view_collection_get_view (instance->collection, index);
+		connect_view (instance, gal_view_clone (view));
+	}
+
+	if (instance->loaded)
+		save_current_view (instance);
+	gal_view_instance_changed (instance);
+}
+
+GalView *
+gal_view_instance_get_current_view (GalViewInstance *instance)
+{
+	return instance->current_view;
+}
+
+void
+gal_view_instance_set_custom_view (GalViewInstance *instance,
+                                   GalView *view)
+{
+	g_free (instance->current_id);
+	instance->current_id = NULL;
+
+	view = gal_view_clone (view);
+	connect_view (instance, view);
+	gal_view_save (view, instance->custom_filename);
+	save_current_view (instance);
+	gal_view_instance_changed (instance);
+}
+
+static void
+dialog_response (GtkWidget *dialog,
+                 gint id,
+                 GalViewInstance *instance)
+{
+	if (id == GTK_RESPONSE_OK) {
+		gal_view_instance_save_as_dialog_save (GAL_VIEW_INSTANCE_SAVE_AS_DIALOG (dialog));
+	}
+	gtk_widget_destroy (dialog);
+}
+
+void
+gal_view_instance_save_as (GalViewInstance *instance)
+{
+	GtkWidget *dialog;
+
+	g_return_if_fail (instance != NULL);
+
+	dialog = gal_view_instance_save_as_dialog_new (instance);
+	g_signal_connect (
+		dialog, "response",
+		G_CALLBACK (dialog_response), instance);
+	gtk_widget_show (dialog);
+}
+
+/* This is idempotent.  Once it's been called once, the rest of the calls are ignored. */
+void
+gal_view_instance_load (GalViewInstance *instance)
+{
+	if (!instance->loaded) {
+		load_current_view (instance);
+		instance->loaded = TRUE;
+		g_signal_emit (instance, gal_view_instance_signals[LOADED], 0);
+	}
+}
+
+/* These only mean anything before gal_view_instance_load is called the first time.  */
+const gchar *
+gal_view_instance_get_default_view (GalViewInstance *instance)
+{
+	if (instance->default_view)
+		return instance->default_view;
+	else
+		return gal_view_collection_get_default_view (instance->collection);
+}
+
+void
+gal_view_instance_set_default_view (GalViewInstance *instance,
+                                    const gchar *id)
+{
+	g_free (instance->default_view);
+	instance->default_view = g_strdup (id);
+}
+
+gboolean
+gal_view_instance_exists (GalViewInstance *instance)
+{
+	struct stat st;
+
+	if (instance->current_view_filename && g_stat (instance->current_view_filename, &st) == 0 && st.st_size > 0 && S_ISREG (st.st_mode))
+		return TRUE;
+	else
+		return FALSE;
+
+}
diff --git a/e-util/gal-view-instance.h b/e-util/gal-view-instance.h
new file mode 100644
index 0000000..c5debd1
--- /dev/null
+++ b/e-util/gal-view-instance.h
@@ -0,0 +1,114 @@
+/*
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef _GAL_VIEW_INSTANCE_H_
+#define _GAL_VIEW_INSTANCE_H_
+
+#include <e-util/gal-view-collection.h>
+
+G_BEGIN_DECLS
+
+#define GAL_VIEW_INSTANCE_TYPE        (gal_view_instance_get_type ())
+#define GAL_VIEW_INSTANCE(o)          (G_TYPE_CHECK_INSTANCE_CAST ((o), GAL_VIEW_INSTANCE_TYPE, GalViewInstance))
+#define GAL_VIEW_INSTANCE_CLASS(k)    (G_TYPE_CHECK_CLASS_CAST((k), GAL_VIEW_INSTANCE_TYPE, GalViewInstanceClass))
+#define GAL_IS_VIEW_INSTANCE(o)       (G_TYPE_CHECK_INSTANCE_TYPE ((o), GAL_VIEW_INSTANCE_TYPE))
+#define GAL_IS_VIEW_INSTANCE_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GAL_VIEW_INSTANCE_TYPE))
+
+typedef struct {
+	GObject base;
+
+	GalViewCollection *collection;
+
+	gchar *instance_id;
+	gchar *current_view_filename;
+	gchar *custom_filename;
+
+	gchar *current_title;
+	gchar *current_type;
+	gchar *current_id;
+
+	GalView *current_view;
+
+	guint view_changed_id;
+	guint collection_changed_id;
+
+	guint loaded : 1;
+	gchar *default_view;
+} GalViewInstance;
+
+typedef struct {
+	GObjectClass parent_class;
+
+	/*
+	 * Signals
+	 */
+	void (*display_view) (GalViewInstance *instance,
+			      GalView    *view);
+	void (*changed)      (GalViewInstance *instance);
+	void (*loaded)       (GalViewInstance *instance);
+} GalViewInstanceClass;
+
+/* Standard functions */
+GType            gal_view_instance_get_type             (void);
+
+/* */
+/*collection should be loaded when you call this.
+  instance_id: Which instance of this type of object is this (for most of evo, this is the folder id.) */
+GalViewInstance *gal_view_instance_new                  (GalViewCollection *collection,
+							 const gchar        *instance_id);
+GalViewInstance *gal_view_instance_construct            (GalViewInstance   *instance,
+							 GalViewCollection *collection,
+							 const gchar        *instance_id);
+
+/* Manipulate the current view. */
+gchar            *gal_view_instance_get_current_view_id  (GalViewInstance   *instance);
+void             gal_view_instance_set_current_view_id  (GalViewInstance   *instance,
+							 const gchar        *view_id);
+GalView         *gal_view_instance_get_current_view     (GalViewInstance   *instance);
+
+/* Sets the current view to the given custom view. */
+void             gal_view_instance_set_custom_view      (GalViewInstance   *instance,
+							 GalView           *view);
+
+/* Returns true if this instance has ever been used before. */
+gboolean         gal_view_instance_exists               (GalViewInstance   *instance);
+
+/* Manipulate the view collection */
+/* void             gal_view_instance_set_as_default       (GalViewInstance   *instance); */
+void             gal_view_instance_save_as              (GalViewInstance   *instance);
+
+/* This is idempotent.  Once it's been called once, the rest of the calls are ignored. */
+void             gal_view_instance_load                 (GalViewInstance   *instance);
+
+/* These only mean anything before gal_view_instance_load is called the first time.  */
+const gchar      *gal_view_instance_get_default_view     (GalViewInstance   *instance);
+void             gal_view_instance_set_default_view     (GalViewInstance   *instance,
+							 const gchar        *id);
+
+G_END_DECLS
+
+#endif /* _GAL_VIEW_INSTANCE_H_ */
diff --git a/e-util/gal-view-new-dialog.c b/e-util/gal-view-new-dialog.c
new file mode 100644
index 0000000..1df95a1
--- /dev/null
+++ b/e-util/gal-view-new-dialog.c
@@ -0,0 +1,291 @@
+/*
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "gal-view-new-dialog.h"
+
+#include <glib/gi18n.h>
+
+#include "e-misc-utils.h"
+#include "e-util-private.h"
+#include "e-unicode.h"
+#include "gal-define-views-model.h"
+
+enum {
+	PROP_0,
+	PROP_NAME,
+	PROP_FACTORY
+};
+
+G_DEFINE_TYPE (GalViewNewDialog, gal_view_new_dialog, GTK_TYPE_DIALOG)
+
+static void
+gal_view_new_dialog_set_property (GObject *object,
+                                  guint property_id,
+                                  const GValue *value,
+                                  GParamSpec *pspec)
+{
+	GalViewNewDialog *dialog;
+	GtkWidget *entry;
+
+	dialog = GAL_VIEW_NEW_DIALOG (object);
+
+	switch (property_id) {
+	case PROP_NAME:
+		entry = e_builder_get_widget (dialog->builder, "entry-name");
+		if (entry && GTK_IS_ENTRY (entry)) {
+			gtk_entry_set_text (GTK_ENTRY (entry), g_value_get_string (value));
+		}
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+		return;
+	}
+}
+
+static void
+gal_view_new_dialog_get_property (GObject *object,
+                                  guint property_id,
+                                  GValue *value,
+                                  GParamSpec *pspec)
+{
+	GalViewNewDialog *dialog;
+	GtkWidget *entry;
+
+	dialog = GAL_VIEW_NEW_DIALOG (object);
+
+	switch (property_id) {
+	case PROP_NAME:
+		entry = e_builder_get_widget (dialog->builder, "entry-name");
+		if (entry && GTK_IS_ENTRY (entry)) {
+			g_value_set_string (value, gtk_entry_get_text (GTK_ENTRY (entry)));
+		}
+		break;
+	case PROP_FACTORY:
+		g_value_set_object (value, dialog->selected_factory);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+		break;
+	}
+}
+
+static void
+gal_view_new_dialog_dispose (GObject *object)
+{
+	GalViewNewDialog *gal_view_new_dialog = GAL_VIEW_NEW_DIALOG (object);
+
+	if (gal_view_new_dialog->builder)
+		g_object_unref (gal_view_new_dialog->builder);
+	gal_view_new_dialog->builder = NULL;
+
+	/* Chain up to parent's dispose() method. */
+	G_OBJECT_CLASS (gal_view_new_dialog_parent_class)->dispose (object);
+}
+
+static void
+gal_view_new_dialog_class_init (GalViewNewDialogClass *class)
+{
+	GObjectClass *object_class;
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->set_property = gal_view_new_dialog_set_property;
+	object_class->get_property = gal_view_new_dialog_get_property;
+	object_class->dispose = gal_view_new_dialog_dispose;
+
+	g_object_class_install_property (
+		object_class,
+		PROP_NAME,
+		g_param_spec_string (
+			"name",
+			"Name",
+			NULL,
+			NULL,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_FACTORY,
+		g_param_spec_object (
+			"factory",
+			"Factory",
+			NULL,
+			GAL_TYPE_VIEW_FACTORY,
+			G_PARAM_READWRITE));
+}
+
+static void
+gal_view_new_dialog_init (GalViewNewDialog *dialog)
+{
+	GtkWidget *content_area;
+	GtkWidget *parent;
+	GtkWidget *widget;
+
+	dialog->builder = gtk_builder_new ();
+	e_load_ui_builder_definition (
+		dialog->builder, "gal-view-new-dialog.ui");
+
+	widget = e_builder_get_widget (dialog->builder, "table-top");
+	if (!widget) {
+		return;
+	}
+
+	g_object_ref (widget);
+
+	parent = gtk_widget_get_parent (widget);
+	gtk_container_remove (GTK_CONTAINER (parent), widget);
+
+	content_area = gtk_dialog_get_content_area (GTK_DIALOG (dialog));
+	gtk_box_pack_start (GTK_BOX (content_area), widget, TRUE, TRUE, 0);
+
+	g_object_unref (widget);
+
+	gtk_dialog_add_buttons (
+		GTK_DIALOG (dialog),
+		GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+		GTK_STOCK_OK, GTK_RESPONSE_OK, NULL);
+
+	gtk_window_set_resizable (GTK_WINDOW (dialog), TRUE);
+	gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
+	gtk_window_set_title (GTK_WINDOW (dialog), _("Define New View"));
+
+	dialog->collection = NULL;
+	dialog->selected_factory = NULL;
+}
+
+GtkWidget *
+gal_view_new_dialog_new (GalViewCollection *collection)
+{
+	GtkWidget *widget =
+		gal_view_new_dialog_construct (
+			g_object_new (GAL_VIEW_NEW_DIALOG_TYPE, NULL),
+			collection);
+	return widget;
+}
+
+static void
+sensitize_ok_response (GalViewNewDialog *dialog)
+{
+	gboolean ok = TRUE;
+	const gchar *text;
+
+	text = gtk_entry_get_text (GTK_ENTRY (dialog->entry));
+	if (!text || !text[0])
+		ok = FALSE;
+
+	if (!dialog->selected_factory)
+		ok = FALSE;
+
+	gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog), GTK_RESPONSE_OK, ok);
+}
+
+static gboolean
+selection_func (GtkTreeSelection *selection,
+                GtkTreeModel *model,
+                GtkTreePath *path,
+                gboolean path_currently_selected,
+                gpointer data)
+{
+	GtkTreeIter iter;
+	GalViewNewDialog *dialog = data;
+
+	if (path_currently_selected)
+		return TRUE;
+
+	model = GTK_TREE_MODEL (dialog->list_store);
+
+	gtk_tree_model_get_iter (model, &iter, path);
+	gtk_tree_model_get (model, &iter, 1, &dialog->selected_factory, -1);
+
+	sensitize_ok_response (dialog);
+
+	return TRUE;
+}
+
+static void
+entry_changed (GtkWidget *entry,
+               gpointer data)
+{
+	GalViewNewDialog *dialog = data;
+
+	sensitize_ok_response (dialog);
+}
+
+GtkWidget *
+gal_view_new_dialog_construct (GalViewNewDialog *dialog,
+                               GalViewCollection *collection)
+{
+	GList *iterator;
+	GtkTreeSelection *selection;
+	GtkTreeViewColumn *column;
+	GtkCellRenderer *rend;
+
+	dialog->collection = collection;
+	dialog->list = e_builder_get_widget (dialog->builder,"list-type-list");
+	dialog->entry = e_builder_get_widget (dialog->builder, "entry-name");
+
+	dialog->list_store = gtk_list_store_new (
+		2, G_TYPE_STRING, G_TYPE_POINTER);
+
+	rend = gtk_cell_renderer_text_new ();
+	column = gtk_tree_view_column_new_with_attributes (
+		"factory title", rend, "text", 0, NULL);
+
+	gtk_tree_view_append_column (GTK_TREE_VIEW (dialog->list), column);
+
+	iterator = dialog->collection->factory_list;
+	for (; iterator; iterator = g_list_next (iterator)) {
+		GalViewFactory *factory = iterator->data;
+		GtkTreeIter iter;
+
+		g_object_ref (factory);
+		gtk_list_store_append (
+			dialog->list_store, &iter);
+		gtk_list_store_set (
+			dialog->list_store, &iter,
+			0, gal_view_factory_get_title (factory),
+			1, factory,
+			-1);
+	}
+
+	gtk_tree_view_set_model (
+		GTK_TREE_VIEW (dialog->list),
+		GTK_TREE_MODEL (dialog->list_store));
+
+	selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (dialog->list));
+
+	gtk_tree_selection_set_select_function (
+		selection, selection_func, dialog, NULL);
+
+	g_signal_connect (
+		dialog->entry, "changed",
+		G_CALLBACK (entry_changed), dialog);
+
+	sensitize_ok_response (dialog);
+
+	return GTK_WIDGET (dialog);
+}
+
diff --git a/e-util/gal-view-new-dialog.h b/e-util/gal-view-new-dialog.h
new file mode 100644
index 0000000..503a594
--- /dev/null
+++ b/e-util/gal-view-new-dialog.h
@@ -0,0 +1,81 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef __GAL_VIEW_NEW_DIALOG_H__
+#define __GAL_VIEW_NEW_DIALOG_H__
+
+#include <gtk/gtk.h>
+#include <e-util/gal-view-collection.h>
+
+G_BEGIN_DECLS
+
+/* GalViewNewDialog - A dialog displaying information about a contact.
+ *
+ * The following arguments are available:
+ *
+ * name		type		read/write	description
+ * --------------------------------------------------------------------------------
+ */
+
+#define GAL_VIEW_NEW_DIALOG_TYPE		(gal_view_new_dialog_get_type ())
+#define GAL_VIEW_NEW_DIALOG(obj)		(G_TYPE_CHECK_INSTANCE_CAST ((obj), GAL_VIEW_NEW_DIALOG_TYPE, GalViewNewDialog))
+#define GAL_VIEW_NEW_DIALOG_CLASS(klass)	(G_TYPE_CHECK_CLASS_CAST ((klass), GAL_VIEW_NEW_DIALOG_TYPE, GalViewNewDialogClass))
+#define GAL_IS_VIEW_NEW_DIALOG(obj)		(G_TYPE_CHECK_INSTANCE_TYPE ((obj), GAL_VIEW_NEW_DIALOG_TYPE))
+#define GAL_IS_VIEW_NEW_DIALOG_CLASS(klass)	(G_TYPE_CHECK_CLASS_TYPE ((obj), GAL_VIEW_NEW_DIALOG_TYPE))
+
+typedef struct _GalViewNewDialog       GalViewNewDialog;
+typedef struct _GalViewNewDialogClass  GalViewNewDialogClass;
+
+struct _GalViewNewDialog
+{
+	GtkDialog parent;
+
+	/* item specific fields */
+	GtkBuilder *builder;
+
+	GalViewCollection *collection;
+	GalViewFactory *selected_factory;
+
+	GtkListStore *list_store;
+
+	GtkWidget *entry;
+	GtkWidget *list;
+};
+
+struct _GalViewNewDialogClass
+{
+	GtkDialogClass parent_class;
+};
+
+GtkWidget *gal_view_new_dialog_new        (GalViewCollection *collection);
+GType      gal_view_new_dialog_get_type   (void);
+
+GtkWidget *gal_view_new_dialog_construct  (GalViewNewDialog  *dialog,
+					   GalViewCollection *collection);
+
+G_END_DECLS
+
+#endif /* __GAL_VIEW_NEW_DIALOG_H__ */
diff --git a/widgets/menus/gal-view-new-dialog.ui b/e-util/gal-view-new-dialog.ui
similarity index 100%
rename from widgets/menus/gal-view-new-dialog.ui
rename to e-util/gal-view-new-dialog.ui
diff --git a/e-util/gal-view.c b/e-util/gal-view.c
new file mode 100644
index 0000000..4302988
--- /dev/null
+++ b/e-util/gal-view.c
@@ -0,0 +1,280 @@
+/*
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "gal-view.h"
+
+#define d(x)
+
+enum {
+	PROP_0,
+	PROP_TITLE,
+	PROP_TYPE_CODE
+};
+
+enum {
+	CHANGED,
+	LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+G_DEFINE_ABSTRACT_TYPE (GalView, gal_view, G_TYPE_OBJECT)
+
+static void
+view_set_property (GObject *object,
+                   guint property_id,
+                   const GValue *value,
+                   GParamSpec *pspec)
+{
+	switch (property_id) {
+		case PROP_TITLE:
+			gal_view_set_title (
+				GAL_VIEW (object),
+				g_value_get_string (value));
+			return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+view_get_property (GObject *object,
+                   guint property_id,
+                   GValue *value,
+                   GParamSpec *pspec)
+{
+	switch (property_id) {
+		case PROP_TITLE:
+			g_value_set_string (
+				value, gal_view_get_title (
+				GAL_VIEW (object)));
+			return;
+
+		case PROP_TYPE_CODE:
+			g_value_set_string (
+				value, gal_view_get_type_code (
+				GAL_VIEW (object)));
+			return;
+	}
+
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+gal_view_class_init (GalViewClass *class)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->set_property = view_set_property;
+	object_class->get_property = view_get_property;
+
+	g_object_class_install_property (
+		object_class,
+		PROP_TITLE,
+		g_param_spec_string (
+			"title",
+			NULL,
+			NULL,
+			NULL,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
+		PROP_TYPE_CODE,
+		g_param_spec_string (
+			"type-code",
+			NULL,
+			NULL,
+			NULL,
+			G_PARAM_READABLE));
+
+	signals[CHANGED] = g_signal_new (
+		"changed",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (GalViewClass, changed),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__VOID,
+		G_TYPE_NONE, 0);
+}
+
+static void
+gal_view_init (GalView *view)
+{
+}
+
+/**
+ * gal_view_edit
+ * @view: The view to edit
+ * @parent: the parent window.
+ */
+void
+gal_view_edit (GalView *view,
+               GtkWindow *parent)
+{
+	GalViewClass *class;
+
+	g_return_if_fail (GAL_IS_VIEW (view));
+	g_return_if_fail (GTK_IS_WINDOW (parent));
+
+	class = GAL_VIEW_GET_CLASS (view);
+	g_return_if_fail (class->edit != NULL);
+
+	class->edit (view, parent);
+}
+
+/**
+ * gal_view_load
+ * @view: The view to load to
+ * @filename: The file to load from
+ */
+void
+gal_view_load (GalView *view,
+               const gchar *filename)
+{
+	GalViewClass *class;
+
+	g_return_if_fail (GAL_IS_VIEW (view));
+	g_return_if_fail (filename != NULL);
+
+	class = GAL_VIEW_GET_CLASS (view);
+	g_return_if_fail (class->load != NULL);
+
+	class->load (view, filename);
+}
+
+/**
+ * gal_view_save
+ * @view: The view to save
+ * @filename: The file to save to
+ */
+void
+gal_view_save (GalView *view,
+               const gchar *filename)
+{
+	GalViewClass *class;
+
+	g_return_if_fail (GAL_IS_VIEW (view));
+	g_return_if_fail (filename != NULL);
+
+	class = GAL_VIEW_GET_CLASS (view);
+	g_return_if_fail (class->save != NULL);
+
+	class->save (view, filename);
+}
+
+/**
+ * gal_view_get_title
+ * @view: The view to query.
+ *
+ * Returns: The title of the view.
+ */
+const gchar *
+gal_view_get_title (GalView *view)
+{
+	GalViewClass *class;
+
+	g_return_val_if_fail (GAL_IS_VIEW (view), NULL);
+
+	class = GAL_VIEW_GET_CLASS (view);
+	g_return_val_if_fail (class->get_title != NULL, NULL);
+
+	return class->get_title (view);
+}
+
+/**
+ * gal_view_set_title
+ * @view: The view to set.
+ * @title: The new title value.
+ */
+void
+gal_view_set_title (GalView *view,
+                    const gchar *title)
+{
+	GalViewClass *class;
+
+	g_return_if_fail (GAL_IS_VIEW (view));
+
+	class = GAL_VIEW_GET_CLASS (view);
+	g_return_if_fail (class->set_title != NULL);
+
+	class->set_title (view, title);
+
+	g_object_notify (G_OBJECT (view), "title");
+}
+
+/**
+ * gal_view_get_type_code
+ * @view: The view to get.
+ *
+ * Returns: The type of the view.
+ */
+const gchar *
+gal_view_get_type_code (GalView *view)
+{
+	GalViewClass *class;
+
+	g_return_val_if_fail (GAL_IS_VIEW (view), NULL);
+
+	class = GAL_VIEW_GET_CLASS (view);
+	g_return_val_if_fail (class->get_type_code != NULL, NULL);
+
+	return class->get_type_code (view);
+}
+
+/**
+ * gal_view_clone
+ * @view: The view to clone.
+ *
+ * Returns: The clone.
+ */
+GalView *
+gal_view_clone (GalView *view)
+{
+	GalViewClass *class;
+
+	g_return_val_if_fail (GAL_IS_VIEW (view), NULL);
+
+	class = GAL_VIEW_GET_CLASS (view);
+	g_return_val_if_fail (class->clone != NULL, NULL);
+
+	return class->clone (view);
+}
+
+/**
+ * gal_view_changed
+ * @view: The view that changed.
+ */
+void
+gal_view_changed (GalView *view)
+{
+	g_return_if_fail (GAL_IS_VIEW (view));
+
+	g_signal_emit (view, signals[CHANGED], 0);
+}
+
diff --git a/e-util/gal-view.h b/e-util/gal-view.h
new file mode 100644
index 0000000..d769895
--- /dev/null
+++ b/e-util/gal-view.h
@@ -0,0 +1,98 @@
+/*
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Chris Lahey <clahey ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef GAL_VIEW_H
+#define GAL_VIEW_H
+
+#include <gtk/gtk.h>
+#include <libxml/tree.h>
+
+/* Standard GObject macros */
+#define GAL_TYPE_VIEW \
+	(gal_view_get_type ())
+#define GAL_VIEW(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), GAL_TYPE_VIEW, GalView))
+#define GAL_VIEW_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), GAL_TYPE_VIEW, GalViewClass))
+#define GAL_IS_VIEW(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), GAL_TYPE_VIEW))
+#define GAL_IS_VIEW_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), GAL_TYPE_VIEW))
+#define GAL_VIEW_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), GAL_TYPE_VIEW, GalViewClass))
+
+G_BEGIN_DECLS
+
+typedef struct _GalView GalView;
+typedef struct _GalViewClass GalViewClass;
+
+struct _GalView {
+	GObject parent;
+};
+
+struct _GalViewClass {
+	GObjectClass parent_class;
+
+	/* Methods */
+	void		(*edit)			(GalView *view,
+						 GtkWindow *parent_window);
+	void		(*load)			(GalView *view,
+						 const gchar *filename);
+	void		(*save)			(GalView *view,
+						 const gchar *filename);
+	const gchar *	(*get_title)		(GalView *view);
+	void		(*set_title)		(GalView *view,
+						 const gchar *title);
+	const gchar *	(*get_type_code)	(GalView *view);
+	GalView *	(*clone)		(GalView *view);
+
+	/* Signals */
+	void		(*changed)		(GalView *view);
+};
+
+GType		gal_view_get_type		(void);
+void		gal_view_edit			(GalView *view,
+						 GtkWindow *parent);
+void		gal_view_load			(GalView *view,
+						 const gchar *filename);
+void		gal_view_save			(GalView *view,
+						 const gchar *filename);
+const gchar *	gal_view_get_title		(GalView *view);
+void		gal_view_set_title		(GalView *view,
+						 const gchar *title);
+const gchar *	gal_view_get_type_code		(GalView *view);
+GalView *	gal_view_clone			(GalView *view);
+void		gal_view_changed		(GalView *view);
+
+G_END_DECLS
+
+#endif /* GAL_VIEW_H */
diff --git a/e-util/test-calendar.c b/e-util/test-calendar.c
new file mode 100644
index 0000000..718c80e
--- /dev/null
+++ b/e-util/test-calendar.c
@@ -0,0 +1,145 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Damon Chaplin <damon ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+/*
+ * test-calendar - tests the ECalendar widget.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <gtk/gtk.h>
+#include <e-util/e-util.h>
+
+/* Drag and Drop stuff. */
+enum {
+	TARGET_SHORTCUT
+};
+
+static GtkTargetEntry target_table[] = {
+	{ (gchar *) "E-SHORTCUT", 0, TARGET_SHORTCUT }
+};
+
+static void on_date_range_changed	(ECalendarItem *calitem);
+static void on_selection_changed	(ECalendarItem *calitem);
+
+static void
+delete_event_cb (GtkWidget *widget,
+                 GdkEventAny *event,
+                 gpointer data)
+{
+	gtk_main_quit ();
+}
+
+gint
+main (gint argc,
+      gchar **argv)
+{
+	GtkWidget *window;
+	GtkWidget *cal;
+	GtkWidget *vbox;
+	ECalendarItem *calitem;
+
+	gtk_init (&argc, &argv);
+
+	window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+	gtk_window_set_title (GTK_WINDOW (window), "ECalendar Test");
+	gtk_window_set_default_size (GTK_WINDOW (window), 400, 400);
+	gtk_window_set_resizable (GTK_WINDOW (window), TRUE);
+	gtk_container_set_border_width (GTK_CONTAINER (window), 8);
+
+	g_signal_connect (
+		window, "delete_event",
+		G_CALLBACK (delete_event_cb), NULL);
+
+	cal = e_calendar_new ();
+	e_calendar_set_minimum_size (E_CALENDAR (cal), 1, 1);
+	calitem = E_CALENDAR (cal)->calitem;
+	gtk_widget_show (cal);
+
+	g_signal_connect (
+		calitem, "date_range_changed",
+		G_CALLBACK (on_date_range_changed), NULL);
+	g_signal_connect (
+		calitem, "selection_changed",
+		G_CALLBACK (on_selection_changed), NULL);
+
+	gtk_drag_dest_set (
+		cal,
+		GTK_DEST_DEFAULT_ALL,
+		target_table, G_N_ELEMENTS (target_table),
+		GDK_ACTION_COPY | GDK_ACTION_MOVE);
+
+	vbox = gtk_vbox_new (FALSE, 0);
+	gtk_box_pack_start (GTK_BOX (vbox), cal, TRUE, TRUE, 0);
+	gtk_widget_show (vbox);
+
+	gtk_container_add (GTK_CONTAINER (window), vbox);
+	gtk_widget_show (window);
+
+	gtk_main ();
+
+	return 0;
+}
+
+static void
+on_date_range_changed (ECalendarItem *calitem)
+{
+	gint start_year, start_month, start_day;
+	gint end_year, end_month, end_day;
+
+	e_calendar_item_get_date_range (
+		calitem,
+		&start_year, &start_month, &start_day,
+		&end_year, &end_month, &end_day);
+
+	g_print (
+		"Date range changed (D/M/Y): %i/%i/%i - %i/%i/%i\n",
+		start_day, start_month + 1, start_year,
+		end_day, end_month + 1, end_year);
+
+	/* These days should windowear bold. Remember month is 0 to 11. */
+	e_calendar_item_mark_day (
+		calitem, 2000, 7, 26, /* 26th Aug 2000. */
+		E_CALENDAR_ITEM_MARK_BOLD, FALSE);
+	e_calendar_item_mark_day (
+		calitem, 2000, 8, 13, /* 13th Sep 2000. */
+		E_CALENDAR_ITEM_MARK_BOLD, FALSE);
+}
+
+static void
+on_selection_changed (ECalendarItem *calitem)
+{
+	GDate start_date, end_date;
+
+	e_calendar_item_get_selection (calitem, &start_date, &end_date);
+
+	g_print (
+		"Selection changed (D/M/Y): %i/%i/%i - %i/%i/%i\n",
+		g_date_get_day (&start_date),
+		g_date_get_month (&start_date),
+		g_date_get_year (&start_date),
+		g_date_get_day (&end_date),
+		g_date_get_month (&end_date),
+		g_date_get_year (&end_date));
+}
diff --git a/e-util/test-category-completion.c b/e-util/test-category-completion.c
new file mode 100644
index 0000000..d9e1473
--- /dev/null
+++ b/e-util/test-category-completion.c
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include <e-util/e-util.h>
+
+static gboolean
+on_idle_create_widget (void)
+{
+	GtkWidget *window;
+	GtkWidget *vgrid;
+	GtkWidget *entry;
+	GtkEntryCompletion *completion;
+
+	window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+	gtk_window_set_default_size (GTK_WINDOW (window), 400, 200);
+
+	g_signal_connect (
+		window, "delete-event",
+		G_CALLBACK (gtk_main_quit), NULL);
+
+	vgrid = g_object_new (GTK_TYPE_GRID,
+		"orientation", GTK_ORIENTATION_VERTICAL,
+		"column-homogeneous", FALSE,
+		"row-spacing", 3,
+		NULL);
+	gtk_container_add (GTK_CONTAINER (window), vgrid);
+
+	entry = gtk_entry_new ();
+	completion = e_category_completion_new ();
+	gtk_entry_set_completion (GTK_ENTRY (entry), completion);
+	gtk_widget_set_vexpand (entry, TRUE);
+	gtk_widget_set_hexpand (entry, TRUE);
+	gtk_widget_set_halign (entry, GTK_ALIGN_FILL);
+	gtk_container_add (GTK_CONTAINER (vgrid), entry);
+
+	gtk_widget_show_all (window);
+
+	return FALSE;
+}
+
+gint
+main (gint argc,
+      gchar **argv)
+{
+	gtk_init (&argc, &argv);
+
+	g_idle_add ((GSourceFunc) on_idle_create_widget, NULL);
+
+	gtk_main ();
+
+	return 0;
+}
diff --git a/e-util/test-contact-store.c b/e-util/test-contact-store.c
new file mode 100644
index 0000000..59ba425
--- /dev/null
+++ b/e-util/test-contact-store.c
@@ -0,0 +1,145 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* test-contact-store.c - Test program for EContactStore.
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * Author: Hans Petter Jansson <hpj novell com>
+ */
+
+#include <e-util/e-util.h>
+
+static void
+entry_changed (GtkWidget *entry,
+               EContactStore *contact_store)
+{
+	const gchar *text;
+	EBookQuery  *query;
+
+	text = gtk_entry_get_text (GTK_ENTRY (entry));
+
+	query = e_book_query_any_field_contains (text);
+	e_contact_store_set_query (contact_store, query);
+	e_book_query_unref (query);
+}
+
+static GtkTreeViewColumn *
+create_text_column_for_field (EContactField field_id)
+{
+	GtkTreeViewColumn *column;
+	GtkCellRenderer   *cell_renderer;
+
+	column = gtk_tree_view_column_new ();
+	cell_renderer = GTK_CELL_RENDERER (gtk_cell_renderer_text_new ());
+	gtk_tree_view_column_pack_start (column, cell_renderer, TRUE);
+	gtk_tree_view_column_set_resizable (column, TRUE);
+	gtk_tree_view_column_set_title (column, e_contact_pretty_name (field_id));
+	gtk_tree_view_column_add_attribute (column, cell_renderer, "text", field_id);
+	gtk_tree_view_column_set_sort_column_id (column, field_id);
+
+	return column;
+}
+
+static gint
+start_test (const gchar *param)
+{
+	EContactStore *contact_store;
+	GtkTreeModel *model_sort;
+	GtkWidget *scrolled_window;
+	GtkWidget *window;
+	GtkWidget *tree_view;
+	GtkWidget *vgrid;
+	GtkWidget *entry;
+	GtkTreeViewColumn *column;
+#if 0  /* ACCOUNT_MGMT */
+	EBookClient *book_client;
+#endif /* ACCOUNT_MGMT */
+	EBookQuery *book_query;
+
+	window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+
+	vgrid = g_object_new (GTK_TYPE_GRID,
+		"orientation", GTK_ORIENTATION_VERTICAL,
+		"column-homogeneous", FALSE,
+		"row-spacing", 2,
+		NULL);
+	gtk_container_add (GTK_CONTAINER (window), vgrid);
+
+	entry = gtk_entry_new ();
+	gtk_widget_set_halign (entry, GTK_ALIGN_FILL);
+	gtk_container_add (GTK_CONTAINER (vgrid), entry);
+
+	scrolled_window = gtk_scrolled_window_new (NULL, NULL);
+	gtk_widget_set_hexpand (scrolled_window, TRUE);
+	gtk_widget_set_halign (scrolled_window, GTK_ALIGN_FILL);
+	gtk_widget_set_vexpand (scrolled_window, TRUE);
+	gtk_widget_set_valign (scrolled_window, GTK_ALIGN_FILL);
+	gtk_container_add (GTK_CONTAINER (vgrid), scrolled_window);
+
+	contact_store = e_contact_store_new ();
+	model_sort = gtk_tree_model_sort_new_with_model (GTK_TREE_MODEL (contact_store));
+	tree_view = GTK_WIDGET (gtk_tree_view_new ());
+	gtk_tree_view_set_model (GTK_TREE_VIEW (tree_view), model_sort);
+
+	column = create_text_column_for_field (E_CONTACT_FILE_AS);
+	gtk_tree_view_append_column (GTK_TREE_VIEW (tree_view), column);
+
+	column = create_text_column_for_field (E_CONTACT_FULL_NAME);
+	gtk_tree_view_append_column (GTK_TREE_VIEW (tree_view), column);
+
+	column = create_text_column_for_field (E_CONTACT_EMAIL_1);
+	gtk_tree_view_append_column (GTK_TREE_VIEW (tree_view), column);
+
+	gtk_container_add (GTK_CONTAINER (scrolled_window), tree_view);
+
+#if 0  /* ACCOUNT_MGMT */
+	book_client = e_book_client_new_default (NULL);
+	g_warn_if_fail (e_client_open_sync (E_CLIENT (book_client), TRUE, NULL, NULL));
+	e_contact_store_add_client (contact_store, book_client);
+	g_object_unref (book_client);
+#endif /* ACCOUNT_MGMT */
+
+	book_query = e_book_query_any_field_contains ("");
+	e_contact_store_set_query (contact_store, book_query);
+	e_book_query_unref (book_query);
+
+	g_signal_connect (entry, "changed", G_CALLBACK (entry_changed), contact_store);
+
+	gtk_widget_show_all (window);
+
+	return FALSE;
+}
+
+gint
+main (gint argc,
+      gchar **argv)
+{
+	const gchar *param;
+
+	gtk_init (&argc, &argv);
+
+	if (argc < 2)
+		param = "???";
+	else
+		param = argv[1];
+
+	g_idle_add ((GSourceFunc) start_test, (gpointer) param);
+
+	gtk_main ();
+
+	return 0;
+}
diff --git a/widgets/misc/test-dateedit.c b/e-util/test-dateedit.c
similarity index 100%
rename from widgets/misc/test-dateedit.c
rename to e-util/test-dateedit.c
diff --git a/e-util/test-mail-signatures.c b/e-util/test-mail-signatures.c
new file mode 100644
index 0000000..3dc5f0a
--- /dev/null
+++ b/e-util/test-mail-signatures.c
@@ -0,0 +1,195 @@
+/*
+ * test-mail-signatures.c
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#include <stdlib.h>
+
+#include <libedataserver/libedataserver.h>
+
+#include <e-util/e-util.h>
+
+static GCancellable *cancellable = NULL;
+
+static void
+signature_loaded_cb (EMailSignatureComboBox *combo_box,
+                     GAsyncResult *result,
+                     EWebView *web_view)
+{
+	gchar *contents = NULL;
+	gboolean is_html;
+	GError *error = NULL;
+
+	e_mail_signature_combo_box_load_selected_finish (
+		combo_box, result, &contents, NULL, &is_html, &error);
+
+	/* Ignore cancellations. */
+	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+		g_warn_if_fail (contents == NULL);
+		g_object_unref (web_view);
+		g_error_free (error);
+		return;
+
+	} else if (error != NULL) {
+		g_warn_if_fail (contents == NULL);
+		e_alert_submit (
+			E_ALERT_SINK (web_view),
+			"widgets:no-load-signature",
+			error->message, NULL);
+		g_object_unref (web_view);
+		g_error_free (error);
+		return;
+	}
+
+	if (contents == NULL)
+		e_web_view_clear (web_view);
+	else if (is_html)
+		e_web_view_load_string (web_view, contents);
+	else {
+		gchar *string;
+
+		string = g_markup_printf_escaped ("<pre>%s</pre>", contents);
+		e_web_view_load_string (web_view, string);
+		g_free (string);
+	}
+
+	g_free (contents);
+
+	g_object_unref (web_view);
+}
+
+static void
+signature_combo_changed_cb (EMailSignatureComboBox *combo_box,
+                            EWebView *web_view)
+{
+	if (cancellable != NULL) {
+		g_cancellable_cancel (cancellable);
+		g_object_unref (cancellable);
+	}
+
+	cancellable = g_cancellable_new ();
+
+	e_mail_signature_combo_box_load_selected (
+		combo_box, G_PRIORITY_DEFAULT, cancellable,
+		(GAsyncReadyCallback) signature_loaded_cb,
+		g_object_ref (web_view));
+}
+
+gint
+main (gint argc,
+      gchar **argv)
+{
+	ESourceRegistry *registry;
+	GtkWidget *container;
+	GtkWidget *widget;
+	GtkWidget *vbox;
+	GtkWidget *identity_combo;
+	GtkWidget *signature_combo;
+	GError *error = NULL;
+
+	gtk_init (&argc, &argv);
+
+	registry = e_source_registry_new_sync (NULL, &error);
+
+	if (error != NULL) {
+		g_printerr ("%s\n", error->message);
+		exit (EXIT_FAILURE);
+	}
+
+	/* Construct the widgets. */
+
+	widget = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+	gtk_window_set_title (GTK_WINDOW (widget), "Mail Signatures");
+	gtk_window_set_default_size (GTK_WINDOW (widget), 400, 400);
+	gtk_container_set_border_width (GTK_CONTAINER (widget), 12);
+	gtk_widget_show (widget);
+
+	g_signal_connect (
+		widget, "delete-event",
+		G_CALLBACK (gtk_main_quit), NULL);
+
+	container = widget;
+
+	widget = gtk_vbox_new (FALSE, 12);
+	gtk_container_add (GTK_CONTAINER (container), widget);
+	gtk_widget_show (widget);
+
+	container = vbox = widget;
+
+	widget = gtk_label_new ("<b>EMailSignatureComboBox</b>");
+	gtk_label_set_use_markup (GTK_LABEL (widget), TRUE);
+	gtk_misc_set_alignment (GTK_MISC (widget), 0.0, 0.5);
+	gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+	gtk_widget_show (widget);
+
+	widget = gtk_vbox_new (FALSE, 6);
+	gtk_widget_set_margin_left (widget, 12);
+	gtk_box_pack_start (GTK_BOX (container), widget, TRUE, TRUE, 0);
+	gtk_widget_show (widget);
+
+	container = widget;
+
+	widget = e_mail_signature_combo_box_new (registry);
+	gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+	signature_combo = widget;
+	gtk_widget_show (widget);
+
+	widget = e_mail_identity_combo_box_new (registry);
+	gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+	identity_combo = widget;
+	gtk_widget_show (widget);
+
+	widget = gtk_scrolled_window_new (NULL, NULL);
+	gtk_scrolled_window_set_policy (
+		GTK_SCROLLED_WINDOW (widget),
+		GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
+	gtk_scrolled_window_set_shadow_type (
+		GTK_SCROLLED_WINDOW (widget), GTK_SHADOW_IN);
+	gtk_box_pack_start (GTK_BOX (container), widget, TRUE, TRUE, 0);
+	gtk_widget_show (widget);
+
+	container = widget;
+
+	widget = e_web_view_new ();
+	gtk_container_add (GTK_CONTAINER (container), widget);
+	gtk_widget_show (widget);
+
+	g_signal_connect (
+		signature_combo, "changed",
+		G_CALLBACK (signature_combo_changed_cb), widget);
+
+	container = vbox;
+
+	widget = gtk_label_new ("<b>EMailSignatureManager</b>");
+	gtk_label_set_use_markup (GTK_LABEL (widget), TRUE);
+	gtk_misc_set_alignment (GTK_MISC (widget), 0.0, 0.5);
+	gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+	gtk_widget_show (widget);
+
+	widget = e_mail_signature_manager_new (registry);
+	gtk_widget_set_margin_left (widget, 12);
+	gtk_box_pack_start (GTK_BOX (container), widget, TRUE, TRUE, 0);
+	gtk_widget_show (widget);
+
+	g_object_bind_property (
+		identity_combo, "active-id",
+		signature_combo, "identity-uid",
+		G_BINDING_SYNC_CREATE);
+
+	gtk_main ();
+
+	return 0;
+}
diff --git a/e-util/test-name-selector.c b/e-util/test-name-selector.c
new file mode 100644
index 0000000..3744ad9
--- /dev/null
+++ b/e-util/test-name-selector.c
@@ -0,0 +1,102 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* test-name-selector.c - Test for name selector components.
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * Author: Hans Petter Jansson <hpj novell com>
+ */
+
+#include <camel/camel.h>
+#include <e-util/e-util.h>
+
+static ENameSelectorDialog *name_selector_dialog;
+static GtkWidget           *name_selector_entry_window;
+
+static void
+close_dialog (GtkWidget *widget,
+              gint response,
+              gpointer data)
+{
+	gtk_widget_destroy (GTK_WIDGET (name_selector_dialog));
+	gtk_widget_destroy (name_selector_entry_window);
+
+	g_timeout_add (4000, (GSourceFunc) gtk_main_quit, NULL);
+}
+
+static gboolean
+start_test (ESourceRegistry *registry)
+{
+	ENameSelectorModel  *name_selector_model;
+	ENameSelectorEntry  *name_selector_entry;
+	EDestinationStore   *destination_store;
+	GtkWidget           *container;
+
+	destination_store = e_destination_store_new ();
+	name_selector_model = e_name_selector_model_new ();
+
+	e_name_selector_model_add_section (name_selector_model, "to", "To", destination_store);
+	e_name_selector_model_add_section (name_selector_model, "cc", "Cc", NULL);
+	e_name_selector_model_add_section (name_selector_model, "bcc", "Bcc", NULL);
+
+	name_selector_dialog = e_name_selector_dialog_new (registry);
+	e_name_selector_dialog_set_model (name_selector_dialog, name_selector_model);
+	gtk_window_set_modal (GTK_WINDOW (name_selector_dialog), FALSE);
+
+	name_selector_entry = e_name_selector_entry_new (registry);
+	e_name_selector_entry_set_destination_store (name_selector_entry, destination_store);
+
+	g_signal_connect (name_selector_dialog, "response", G_CALLBACK (close_dialog), name_selector_dialog);
+	gtk_widget_show (GTK_WIDGET (name_selector_dialog));
+
+	container = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+	gtk_container_add (GTK_CONTAINER (container), GTK_WIDGET (name_selector_entry));
+	gtk_widget_show_all (container);
+
+	name_selector_entry_window = container;
+
+	g_object_unref (name_selector_model);
+	g_object_unref (destination_store);
+	return FALSE;
+}
+
+gint
+main (gint argc,
+      gchar **argv)
+{
+	ESourceRegistry *registry;
+	GError *error = NULL;
+
+	gtk_init (&argc, &argv);
+
+	camel_init (NULL, 0);
+
+	registry = e_source_registry_new_sync (NULL, &error);
+
+	if (error != NULL) {
+		g_error (
+			"Failed to load ESource registry: %s",
+			error->message);
+		g_assert_not_reached ();
+	}
+
+	g_idle_add ((GSourceFunc) start_test, registry);
+
+	gtk_main ();
+
+	return 0;
+}
diff --git a/widgets/misc/test-preferences-window.c b/e-util/test-preferences-window.c
similarity index 100%
rename from widgets/misc/test-preferences-window.c
rename to e-util/test-preferences-window.c
diff --git a/e-util/test-source-combo-box.c b/e-util/test-source-combo-box.c
new file mode 100644
index 0000000..cb40f6e
--- /dev/null
+++ b/e-util/test-source-combo-box.c
@@ -0,0 +1,107 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* test-source-combo-box.c - Test for ESourceComboBox.
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * Author: Ettore Perazzoli <ettore ximian com>
+ */
+
+#include <config.h>
+#include <gtk/gtk.h>
+
+#include <e-util/e-util.h>
+
+static const gchar *extension_name;
+
+static void
+source_changed_cb (ESourceComboBox *combo_box)
+{
+	ESource *source;
+
+	source = e_source_combo_box_ref_active (combo_box);
+	if (source != NULL) {
+		const gchar *display_name;
+		display_name = e_source_get_display_name (source);
+		g_print ("source selected: \"%s\"\n", display_name);
+		g_object_unref (source);
+	} else {
+		g_print ("source selected: (none)\n");
+	}
+}
+
+static gint
+on_idle_create_widget (ESourceRegistry *registry)
+{
+	GtkWidget *window;
+	GtkWidget *box;
+	GtkWidget *combo_box;
+	GtkWidget *button;
+
+	window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+
+	box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
+	gtk_container_add (GTK_CONTAINER (window), box);
+
+	combo_box = e_source_combo_box_new (registry, extension_name);
+	g_signal_connect (
+		combo_box, "changed",
+		G_CALLBACK (source_changed_cb), NULL);
+	gtk_box_pack_start (GTK_BOX (box), combo_box, FALSE, FALSE, 0);
+
+	button = gtk_toggle_button_new_with_label ("Show Colors");
+	gtk_box_pack_start (GTK_BOX (box), button, FALSE, FALSE, 0);
+
+	g_object_bind_property (
+		combo_box, "show-colors",
+		button, "active",
+		G_BINDING_SYNC_CREATE |
+		G_BINDING_BIDIRECTIONAL);
+
+	gtk_widget_show_all (window);
+
+	return FALSE;
+}
+
+gint
+main (gint argc,
+      gchar **argv)
+{
+	ESourceRegistry *registry;
+	GError *error = NULL;
+
+	gtk_init (&argc, &argv);
+
+	if (argc < 2)
+		extension_name = E_SOURCE_EXTENSION_ADDRESS_BOOK;
+	else
+		extension_name = argv[1];
+
+	registry = e_source_registry_new_sync (NULL, &error);
+
+	if (error != NULL) {
+		g_error (
+			"Failed to load ESource registry: %s",
+			error->message);
+		g_assert_not_reached ();
+	}
+
+	g_idle_add ((GSourceFunc) on_idle_create_widget, registry);
+
+	gtk_main ();
+
+	return 0;
+}
diff --git a/widgets/misc/test-source-config.c b/e-util/test-source-config.c
similarity index 100%
rename from widgets/misc/test-source-config.c
rename to e-util/test-source-config.c
diff --git a/e-util/test-source-selector.c b/e-util/test-source-selector.c
new file mode 100644
index 0000000..0c1a772
--- /dev/null
+++ b/e-util/test-source-selector.c
@@ -0,0 +1,157 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* test-source-list-selector.c - Test program for the ESourceListSelector
+ * widget.
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * Author: Ettore Perazzoli <ettore ximian com>
+ */
+
+#include <e-util/e-util.h>
+
+static const gchar *extension_name;
+
+static void
+dump_selection (ESourceSelector *selector)
+{
+	GSList *selection = e_source_selector_get_selection (selector);
+
+	g_print ("Current selection:\n");
+	if (selection == NULL) {
+		g_print ("\t(None)\n");
+	} else {
+		GSList *p;
+
+		for (p = selection; p != NULL; p = p->next) {
+			ESource *source = E_SOURCE (p->data);
+			ESourceBackend *extension;
+
+			extension = e_source_get_extension (
+				source, extension_name);
+
+			g_print (
+				"\tSource %s (backend %s)\n",
+				e_source_get_display_name (source),
+				e_source_backend_get_backend_name (extension));
+		}
+	}
+
+	e_source_selector_free_selection (selection);
+}
+
+static void
+selection_changed_callback (ESourceSelector *selector)
+{
+	g_print ("Selection changed!\n");
+	dump_selection (selector);
+}
+
+static gint
+on_idle_create_widget (ESourceRegistry *registry)
+{
+	GtkWidget *window;
+	GtkWidget *vgrid;
+	GtkWidget *selector;
+	GtkWidget *scrolled_window;
+	GtkWidget *check;
+
+	window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+	gtk_window_set_default_size (GTK_WINDOW (window), 200, 300);
+
+	g_signal_connect (
+		window, "delete-event",
+		G_CALLBACK (gtk_main_quit), NULL);
+
+	vgrid = g_object_new (GTK_TYPE_GRID,
+		"orientation", GTK_ORIENTATION_VERTICAL,
+		"column-homogeneous", FALSE,
+		"row-spacing", 6,
+		NULL);
+	gtk_container_add (GTK_CONTAINER (window), vgrid);
+
+	selector = e_source_selector_new (registry, extension_name);
+	g_signal_connect (
+		selector, "selection_changed",
+		G_CALLBACK (selection_changed_callback), NULL);
+
+	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_IN);
+	gtk_container_add (GTK_CONTAINER (scrolled_window), selector);
+	gtk_widget_set_hexpand (scrolled_window, TRUE);
+	gtk_widget_set_halign (scrolled_window, GTK_ALIGN_FILL);
+	gtk_widget_set_vexpand (scrolled_window, TRUE);
+	gtk_widget_set_valign (scrolled_window, GTK_ALIGN_FILL);
+	gtk_container_add (GTK_CONTAINER (vgrid), scrolled_window);
+
+	check = gtk_check_button_new_with_label ("Show colors");
+	gtk_widget_set_halign (check, GTK_ALIGN_FILL);
+	gtk_container_add (GTK_CONTAINER (vgrid), check);
+
+	g_object_bind_property (
+		selector, "show-colors",
+		check, "active",
+		G_BINDING_BIDIRECTIONAL |
+		G_BINDING_SYNC_CREATE);
+
+	check = gtk_check_button_new_with_label ("Show toggles");
+	gtk_widget_set_halign (check, GTK_ALIGN_FILL);
+	gtk_container_add (GTK_CONTAINER (vgrid), check);
+
+	g_object_bind_property (
+		selector, "show-toggles",
+		check, "active",
+		G_BINDING_BIDIRECTIONAL |
+		G_BINDING_SYNC_CREATE);
+
+	gtk_widget_show_all (window);
+
+	return FALSE;
+}
+
+gint
+main (gint argc,
+      gchar **argv)
+{
+	ESourceRegistry *registry;
+	GError *error = NULL;
+
+	gtk_init (&argc, &argv);
+
+	if (argc < 2)
+		extension_name = E_SOURCE_EXTENSION_ADDRESS_BOOK;
+	else
+		extension_name = argv[1];
+
+	registry = e_source_registry_new_sync (NULL, &error);
+
+	if (error != NULL) {
+		g_error (
+			"Failed to load ESource registry: %s",
+			error->message);
+		g_assert_not_reached ();
+	}
+
+	g_idle_add ((GSourceFunc) on_idle_create_widget, registry);
+
+	gtk_main ();
+
+	return 0;
+}
diff --git a/widgets/table/tree-expanded.xpm b/e-util/tree-expanded.xpm
similarity index 100%
rename from widgets/table/tree-expanded.xpm
rename to e-util/tree-expanded.xpm
diff --git a/widgets/table/tree-unexpanded.xpm b/e-util/tree-unexpanded.xpm
similarity index 100%
rename from widgets/table/tree-unexpanded.xpm
rename to e-util/tree-unexpanded.xpm
diff --git a/widgets/misc/widgets.error.xml b/e-util/widgets.error.xml
similarity index 100%
rename from widgets/misc/widgets.error.xml
rename to e-util/widgets.error.xml
diff --git a/em-format/Makefile.am b/em-format/Makefile.am
index 164f300..5b2f4aa 100644
--- a/em-format/Makefile.am
+++ b/em-format/Makefile.am
@@ -25,12 +25,13 @@ libemformat_la_CPPFLAGS =				\
 	-I$(top_srcdir)/em-format			\
 	-I$(top_srcdir)/smime/lib			\
 	-I$(top_srcdir)/smime/gui			\
-	-I$(top_srcdir)/widgets				\
+	-DEVOLUTION_IMAGESDIR=\""$(imagesdir)"\"	\
+	-DEVOLUTION_PRIVDATADIR=\""$(privdatadir)"\"	\
 	$(EVOLUTION_DATA_SERVER_CFLAGS)			\
 	$(GNOME_PLATFORM_CFLAGS)			\
-	$(LIBSOUP_CFLAGS)				\
-	-DEVOLUTION_IMAGESDIR=\""$(imagesdir)"\"	\
-	-DEVOLUTION_PRIVDATADIR=\""$(privdatadir)"\"
+	$(CHAMPLAIN_CFLAGS)				\
+	$(GTKHTML_CFLAGS)				\
+	$(LIBSOUP_CFLAGS)
 
 if ENABLE_SMIME
 SMIME_EXTENSIONS = e-mail-parser-application-smime.c
@@ -104,14 +105,13 @@ SMIME_LIBS =						\
 endif
 
 libemformat_la_LIBADD =					\
-	$(top_builddir)/libevolution-utils/libevolution-utils.la \
 	$(top_builddir)/e-util/libeutil.la		\
 	$(top_builddir)/shell/libeshell.la		\
-	$(top_builddir)/libemail-utils/libemail-utils.la \
 	$(top_builddir)/libemail-engine/libemail-engine.la \
-	$(top_builddir)/widgets/misc/libemiscwidgets.la	\
 	$(EVOLUTION_DATA_SERVER_LIBS)			\
 	$(GNOME_PLATFORM_LIBS)				\
+	$(CHAMPLAIN_LIBS)				\
+	$(GTKHTML_LIBS)					\
 	$(LIBSOUP_LIBS)					\
 	$(SMIME_LIBS)
 
diff --git a/em-format/e-mail-extension-registry.c b/em-format/e-mail-extension-registry.c
index e6a78e4..7e5f3a9 100644
--- a/em-format/e-mail-extension-registry.c
+++ b/em-format/e-mail-extension-registry.c
@@ -16,18 +16,15 @@
  *
  */
 
-#include <glib-object.h>
-
 #include "e-mail-extension-registry.h"
-#include "e-mail-formatter-extension.h"
-#include "e-mail-parser-extension.h"
-#include <libebackend/libebackend.h>
-#include <camel/camel.h>
-
-#include <glib-object.h>
 
 #include <string.h>
 
+#include <libebackend/libebackend.h>
+
+#include "e-mail-formatter-extension.h"
+#include "e-mail-parser-extension.h"
+
 #define E_MAIL_EXTENSION_REGISTRY_GET_PRIVATE(obj) \
 	(G_TYPE_INSTANCE_GET_PRIVATE \
 	((obj), E_TYPE_MAIL_EXTENSION_REGISTRY, EMailExtensionRegistryPrivate))
diff --git a/em-format/e-mail-formatter-attachment-bar.c b/em-format/e-mail-formatter-attachment-bar.c
index d0e52cb..4591501 100644
--- a/em-format/e-mail-formatter-attachment-bar.c
+++ b/em-format/e-mail-formatter-attachment-bar.c
@@ -19,13 +19,12 @@
 #ifdef HAVE_CONFIG_H
 #include <config.h>
 #endif
+
 #include "e-mail-part-attachment-bar.h"
-#include <misc/e-attachment-bar.h>
 
 #include <glib/gi18n-lib.h>
 
-#include <em-format/e-mail-formatter-extension.h>
-#include <em-format/e-mail-formatter.h>
+#include "e-mail-formatter-extension.h"
 
 typedef EMailFormatterExtension EMailFormatterAttachmentBar;
 typedef EMailFormatterExtensionClass EMailFormatterAttachmentBarClass;
diff --git a/em-format/e-mail-formatter-attachment.c b/em-format/e-mail-formatter-attachment.c
index 663bf84..f800ca2 100644
--- a/em-format/e-mail-formatter-attachment.c
+++ b/em-format/e-mail-formatter-attachment.c
@@ -20,22 +20,16 @@
 #include <config.h>
 #endif
 
-#include "e-mail-part-attachment.h"
-#include "e-mail-part-attachment-bar.h"
-
-#include <em-format/e-mail-formatter-extension.h>
-#include <em-format/e-mail-formatter.h>
-#include <em-format/e-mail-part-utils.h>
-#include <em-format/e-mail-inline-filter.h>
-#include <e-util/e-util.h>
+#include <glib/gi18n-lib.h>
 
 #include <shell/e-shell.h>
 #include <shell/e-shell-window.h>
 
-#include <widgets/misc/e-attachment-button.h>
-
-#include <glib/gi18n-lib.h>
-#include <camel/camel.h>
+#include "e-mail-formatter-extension.h"
+#include "e-mail-inline-filter.h"
+#include "e-mail-part-attachment-bar.h"
+#include "e-mail-part-attachment.h"
+#include "e-mail-part-utils.h"
 
 #define d(x)
 
diff --git a/em-format/e-mail-formatter-error.c b/em-format/e-mail-formatter-error.c
index 16cfccb..3adaab9 100644
--- a/em-format/e-mail-formatter-error.c
+++ b/em-format/e-mail-formatter-error.c
@@ -22,11 +22,9 @@
 
 #include <glib/gi18n-lib.h>
 
-#include <em-format/e-mail-formatter-extension.h>
-#include <em-format/e-mail-formatter.h>
 #include <e-util/e-util.h>
 
-#include <camel/camel.h>
+#include "e-mail-formatter-extension.h"
 
 typedef EMailFormatterExtension EMailFormatterError;
 typedef EMailFormatterExtensionClass EMailFormatterErrorClass;
diff --git a/em-format/e-mail-formatter-extension.h b/em-format/e-mail-formatter-extension.h
index a921696..9c7e4b9 100644
--- a/em-format/e-mail-formatter-extension.h
+++ b/em-format/e-mail-formatter-extension.h
@@ -19,10 +19,10 @@
 #ifndef E_MAIL_FORMATTER_EXTENSION_H
 #define E_MAIL_FORMATTER_EXTENSION_H
 
+#include <gtk/gtk.h>
+#include <camel/camel.h>
 #include <em-format/e-mail-part.h>
 #include <em-format/e-mail-formatter.h>
-#include <camel/camel.h>
-#include <gtk/gtk.h>
 
 /* Standard GObject macros */
 #define E_TYPE_MAIL_FORMATTER_EXTENSION \
diff --git a/em-format/e-mail-formatter-headers.c b/em-format/e-mail-formatter-headers.c
index 56026b2..3161d88 100644
--- a/em-format/e-mail-formatter-headers.c
+++ b/em-format/e-mail-formatter-headers.c
@@ -20,20 +20,17 @@
 #include <config.h>
 #endif
 
+#include <string.h>
 #include <glib/gi18n-lib.h>
 
-#include <em-format/e-mail-formatter-extension.h>
-#include <em-format/e-mail-formatter.h>
-#include <em-format/e-mail-formatter-utils.h>
-#include <em-format/e-mail-inline-filter.h>
 #include <libemail-engine/e-mail-utils.h>
 #include <libedataserver/libedataserver.h>
 #include <e-util/e-util.h>
 #include <shell/e-shell.h>
 
-#include <camel/camel.h>
-
-#include <string.h>
+#include "e-mail-formatter-extension.h"
+#include "e-mail-formatter-utils.h"
+#include "e-mail-inline-filter.h"
 
 typedef EMailFormatterExtension EMailFormatterHeaders;
 typedef EMailFormatterExtensionClass EMailFormatterHeadersClass;
diff --git a/em-format/e-mail-formatter-image.c b/em-format/e-mail-formatter-image.c
index 68bef6a..4cdd844 100644
--- a/em-format/e-mail-formatter-image.c
+++ b/em-format/e-mail-formatter-image.c
@@ -20,16 +20,14 @@
 #include <config.h>
 #endif
 
-#include <em-format/e-mail-parser-extension.h>
-#include <em-format/e-mail-formatter-extension.h>
-#include <em-format/e-mail-part-utils.h>
-#include <em-format/e-mail-parser.h>
-#include <em-format/e-mail-formatter.h>
-#include <em-format/e-mail-inline-filter.h>
+#include <glib/gi18n-lib.h>
+
 #include <e-util/e-util.h>
 
-#include <glib/gi18n-lib.h>
-#include <camel/camel.h>
+#include "e-mail-formatter-extension.h"
+#include "e-mail-inline-filter.h"
+#include "e-mail-parser-extension.h"
+#include "e-mail-part-utils.h"
 
 typedef EMailFormatterExtension EMailFormatterImage;
 typedef EMailFormatterExtensionClass EMailFormatterImageClass;
diff --git a/em-format/e-mail-formatter-message-rfc822.c b/em-format/e-mail-formatter-message-rfc822.c
index 261dfe8..c0179ab 100644
--- a/em-format/e-mail-formatter-message-rfc822.c
+++ b/em-format/e-mail-formatter-message-rfc822.c
@@ -20,18 +20,14 @@
 #include <config.h>
 #endif
 
+#include <string.h>
 #include <glib/gi18n-lib.h>
-#include <glib-object.h>
 
-#include <em-format/e-mail-formatter-extension.h>
-#include <em-format/e-mail-formatter.h>
-#include <em-format/e-mail-part-list.h>
-#include <em-format/e-mail-part-utils.h>
 #include <e-util/e-util.h>
 
-#include <camel/camel.h>
-
-#include <string.h>
+#include "e-mail-formatter-extension.h"
+#include "e-mail-part-list.h"
+#include "e-mail-part-utils.h"
 
 typedef EMailFormatterExtension EMailFormatterMessageRFC822;
 typedef EMailFormatterExtensionClass EMailFormatterMessageRFC822Class;
diff --git a/em-format/e-mail-formatter-print-headers.c b/em-format/e-mail-formatter-print-headers.c
index 8d183a0..c3f683f 100644
--- a/em-format/e-mail-formatter-print-headers.c
+++ b/em-format/e-mail-formatter-print-headers.c
@@ -20,17 +20,17 @@
 #include <config.h>
 #endif
 
+#include <string.h>
 #include <glib/gi18n-lib.h>
 
-#include <em-format/e-mail-formatter-print.h>
-#include <em-format/e-mail-formatter-utils.h>
-#include <em-format/e-mail-inline-filter.h>
-#include <libemail-engine/e-mail-utils.h>
-#include <e-util/e-util.h>
-
 #include <camel/camel.h>
 
-#include <string.h>
+#include <e-util/e-util.h>
+#include <libemail-engine/e-mail-utils.h>
+
+#include "e-mail-formatter-print.h"
+#include "e-mail-formatter-utils.h"
+#include "e-mail-inline-filter.h"
 
 typedef EMailFormatterExtension EMailFormatterPrintHeaders;
 typedef EMailFormatterExtensionClass EMailFormatterPrintHeadersClass;
diff --git a/em-format/e-mail-formatter-print.c b/em-format/e-mail-formatter-print.c
index 4466bed..89f0608 100644
--- a/em-format/e-mail-formatter-print.c
+++ b/em-format/e-mail-formatter-print.c
@@ -18,8 +18,6 @@
 
 #include "e-mail-formatter-print.h"
 
-#include <camel/camel.h>
-
 #include "e-mail-part-attachment.h"
 #include "e-mail-formatter-extension.h"
 #include "e-mail-formatter-utils.h"
diff --git a/em-format/e-mail-formatter-quote-attachment.c b/em-format/e-mail-formatter-quote-attachment.c
index 5086a3f..628ef97 100644
--- a/em-format/e-mail-formatter-quote-attachment.c
+++ b/em-format/e-mail-formatter-quote-attachment.c
@@ -20,14 +20,15 @@
 #include <config.h>
 #endif
 
-#include "e-mail-part-attachment.h"
+#include <glib/gi18n-lib.h>
+
+#include <camel/camel.h>
 
-#include <em-format/e-mail-formatter-quote.h>
-#include <em-format/e-mail-part-utils.h>
 #include <e-util/e-util.h>
 
-#include <glib/gi18n-lib.h>
-#include <camel/camel.h>
+#include "e-mail-formatter-quote.h"
+#include "e-mail-part-attachment.h"
+#include "e-mail-part-utils.h"
 
 #define d(x)
 
diff --git a/em-format/e-mail-formatter-quote-headers.c b/em-format/e-mail-formatter-quote-headers.c
index 4af4841..8fed82d 100644
--- a/em-format/e-mail-formatter-quote-headers.c
+++ b/em-format/e-mail-formatter-quote-headers.c
@@ -20,17 +20,17 @@
 #include <config.h>
 #endif
 
+#include <string.h>
 #include <glib/gi18n-lib.h>
 
-#include <em-format/e-mail-formatter-quote.h>
-#include <em-format/e-mail-formatter-utils.h>
-#include <em-format/e-mail-inline-filter.h>
-#include <libemail-engine/e-mail-utils.h>
-#include <e-util/e-util.h>
-
 #include <camel/camel.h>
 
-#include <string.h>
+#include <e-util/e-util.h>
+#include <libemail-engine/e-mail-utils.h>
+
+#include "e-mail-formatter-quote.h"
+#include "e-mail-formatter-utils.h"
+#include "e-mail-inline-filter.h"
 
 typedef EMailFormatterExtension EMailFormatterQuoteHeaders;
 typedef EMailFormatterExtensionClass EMailFormatterQuoteHeadersClass;
diff --git a/em-format/e-mail-formatter-quote-message-rfc822.c b/em-format/e-mail-formatter-quote-message-rfc822.c
index e891bce..42b3ba5 100644
--- a/em-format/e-mail-formatter-quote-message-rfc822.c
+++ b/em-format/e-mail-formatter-quote-message-rfc822.c
@@ -20,17 +20,16 @@
 #include <config.h>
 #endif
 
+#include <string.h>
 #include <glib/gi18n-lib.h>
-#include <glib-object.h>
-
-#include <em-format/e-mail-formatter-quote.h>
-#include <em-format/e-mail-part-list.h>
-#include <em-format/e-mail-part-utils.h>
-#include <e-util/e-util.h>
 
 #include <camel/camel.h>
 
-#include <string.h>
+#include <e-util/e-util.h>
+
+#include "e-mail-formatter-quote.h"
+#include "e-mail-part-list.h"
+#include "e-mail-part-utils.h"
 
 typedef EMailFormatterExtension EMailFormatterQuoteMessageRFC822;
 typedef EMailFormatterExtensionClass EMailFormatterQuoteMessageRFC822Class;
diff --git a/em-format/e-mail-formatter-quote-text-enriched.c b/em-format/e-mail-formatter-quote-text-enriched.c
index 9bbbc53..031cc66 100644
--- a/em-format/e-mail-formatter-quote-text-enriched.c
+++ b/em-format/e-mail-formatter-quote-text-enriched.c
@@ -20,13 +20,15 @@
 #include <config.h>
 #endif
 
-#include <em-format/e-mail-formatter-quote.h>
-#include <em-format/e-mail-inline-filter.h>
-#include <e-util/e-util.h>
-
 #include <glib/gi18n-lib.h>
+
 #include <camel/camel.h>
 
+#include <e-util/e-util.h>
+
+#include "e-mail-formatter-quote.h"
+#include "e-mail-inline-filter.h"
+
 typedef EMailFormatterExtension EMailFormatterQuoteTextEnriched;
 typedef EMailFormatterExtensionClass EMailFormatterQuoteTextEnrichedClass;
 
diff --git a/em-format/e-mail-formatter-quote-text-html.c b/em-format/e-mail-formatter-quote-text-html.c
index 4d3a71f..71c7d63 100644
--- a/em-format/e-mail-formatter-quote-text-html.c
+++ b/em-format/e-mail-formatter-quote-text-html.c
@@ -20,15 +20,16 @@
 #include <config.h>
 #endif
 
-#include <em-format/e-mail-formatter-quote.h>
-#include <em-format/e-mail-stripsig-filter.h>
-#include <em-format/e-mail-part-utils.h>
-#include <e-util/e-util.h>
-
+#include <string.h>
 #include <glib/gi18n-lib.h>
+
 #include <camel/camel.h>
 
-#include <string.h>
+#include <e-util/e-util.h>
+
+#include "e-mail-formatter-quote.h"
+#include "e-mail-part-utils.h"
+#include "e-mail-stripsig-filter.h"
 
 typedef EMailFormatterExtension EMailFormatterQuoteTextHTML;
 typedef EMailFormatterExtensionClass EMailFormatterQuoteTextHTMLClass;
diff --git a/em-format/e-mail-formatter-quote-text-plain.c b/em-format/e-mail-formatter-quote-text-plain.c
index 5f14c8e..71f1762 100644
--- a/em-format/e-mail-formatter-quote-text-plain.c
+++ b/em-format/e-mail-formatter-quote-text-plain.c
@@ -20,14 +20,16 @@
 #include <config.h>
 #endif
 
-#include <em-format/e-mail-formatter-quote.h>
-#include <em-format/e-mail-part-utils.h>
-#include <em-format/e-mail-stripsig-filter.h>
-#include <e-util/e-util.h>
-
 #include <glib/gi18n-lib.h>
+
 #include <camel/camel.h>
 
+#include <e-util/e-util.h>
+
+#include "e-mail-formatter-quote.h"
+#include "e-mail-part-utils.h"
+#include "e-mail-stripsig-filter.h"
+
 typedef EMailFormatterExtension EMailFormatterQuoteTextPlain;
 typedef EMailFormatterExtensionClass EMailFormatterQuoteTextPlainClass;
 
diff --git a/em-format/e-mail-formatter-secure-button.c b/em-format/e-mail-formatter-secure-button.c
index 2b61a74..c07e042 100644
--- a/em-format/e-mail-formatter-secure-button.c
+++ b/em-format/e-mail-formatter-secure-button.c
@@ -22,8 +22,6 @@
 
 #include <glib/gi18n-lib.h>
 
-#include <em-format/e-mail-formatter-extension.h>
-#include <em-format/e-mail-formatter.h>
 #include <e-util/e-util.h>
 
 #if defined (HAVE_NSS) && defined (ENABLE_SMIME)
@@ -31,7 +29,7 @@
 #include "e-cert-db.h"
 #endif
 
-#include <camel/camel.h>
+#include "e-mail-formatter-extension.h"
 
 typedef EMailFormatterExtension EMailFormatterSecureButton;
 typedef EMailFormatterExtensionClass EMailFormatterSecureButtonClass;
diff --git a/em-format/e-mail-formatter-source.c b/em-format/e-mail-formatter-source.c
index b37af49..e4ee288 100644
--- a/em-format/e-mail-formatter-source.c
+++ b/em-format/e-mail-formatter-source.c
@@ -20,13 +20,12 @@
 #include <config.h>
 #endif
 
-#include <em-format/e-mail-formatter-extension.h>
-#include <em-format/e-mail-formatter.h>
-#include <em-format/e-mail-inline-filter.h>
+#include <glib/gi18n-lib.h>
+
 #include <e-util/e-util.h>
 
-#include <glib/gi18n-lib.h>
-#include <camel/camel.h>
+#include "e-mail-formatter-extension.h"
+#include "e-mail-inline-filter.h"
 
 typedef EMailFormatterExtension EMailFormatterSource;
 typedef EMailFormatterExtensionClass EMailFormatterSourceClass;
diff --git a/em-format/e-mail-formatter-text-enriched.c b/em-format/e-mail-formatter-text-enriched.c
index be3b1b0..1ea46e8 100644
--- a/em-format/e-mail-formatter-text-enriched.c
+++ b/em-format/e-mail-formatter-text-enriched.c
@@ -20,13 +20,12 @@
 #include <config.h>
 #endif
 
-#include <em-format/e-mail-formatter-extension.h>
-#include <em-format/e-mail-formatter.h>
-#include <em-format/e-mail-inline-filter.h>
+#include <glib/gi18n-lib.h>
+
 #include <e-util/e-util.h>
 
-#include <glib/gi18n-lib.h>
-#include <camel/camel.h>
+#include "e-mail-formatter-extension.h"
+#include "e-mail-inline-filter.h"
 
 typedef EMailFormatterExtension EMailFormatterTextEnriched;
 typedef EMailFormatterExtensionClass EMailFormatterTextEnrichedClass;
diff --git a/em-format/e-mail-formatter-text-html.c b/em-format/e-mail-formatter-text-html.c
index aabfa72..4809c35 100644
--- a/em-format/e-mail-formatter-text-html.c
+++ b/em-format/e-mail-formatter-text-html.c
@@ -20,17 +20,15 @@
 #include <config.h>
 #endif
 
-#include <em-format/e-mail-formatter-extension.h>
-#include <em-format/e-mail-formatter.h>
-#include <em-format/e-mail-inline-filter.h>
-#include <em-format/e-mail-part-utils.h>
-#include <e-util/e-util.h>
-
-#include <glib/gi18n-lib.h>
-#include <camel/camel.h>
-
 #include <ctype.h>
 #include <string.h>
+#include <glib/gi18n-lib.h>
+
+#include <e-util/e-util.h>
+
+#include "e-mail-formatter-extension.h"
+#include "e-mail-inline-filter.h"
+#include "e-mail-part-utils.h"
 
 typedef EMailFormatterExtension EMailFormatterTextHTML;
 typedef EMailFormatterExtensionClass EMailFormatterTextHTMLClass;
diff --git a/em-format/e-mail-formatter-text-plain.c b/em-format/e-mail-formatter-text-plain.c
index afe5ed2..3d0de82 100644
--- a/em-format/e-mail-formatter-text-plain.c
+++ b/em-format/e-mail-formatter-text-plain.c
@@ -20,14 +20,13 @@
 #include <config.h>
 #endif
 
-#include <em-format/e-mail-formatter-extension.h>
-#include <em-format/e-mail-formatter.h>
-#include <em-format/e-mail-inline-filter.h>
-#include <em-format/e-mail-part-utils.h>
+#include <glib/gi18n-lib.h>
+
 #include <e-util/e-util.h>
 
-#include <glib/gi18n-lib.h>
-#include <camel/camel.h>
+#include "e-mail-formatter-extension.h"
+#include "e-mail-inline-filter.h"
+#include "e-mail-part-utils.h"
 
 typedef EMailFormatterExtension EMailFormatterTextPlain;
 typedef EMailFormatterExtensionClass EMailFormatterTextPlainClass;
diff --git a/em-format/e-mail-formatter-utils.c b/em-format/e-mail-formatter-utils.c
index 11dcc0e..5ceeb92 100644
--- a/em-format/e-mail-formatter-utils.c
+++ b/em-format/e-mail-formatter-utils.c
@@ -22,17 +22,15 @@
 
 #include "e-mail-formatter-utils.h"
 
+#include <string.h>
+#include <glib/gi18n.h>
+
 #include <camel/camel.h>
+#include <libedataserver/libedataserver.h>
 
+#include <e-util/e-util.h>
 #include <libemail-engine/e-mail-utils.h>
 #include <libemail-engine/mail-config.h>
-#include <e-util/e-util.h>
-#include <e-util/e-datetime-format.h>
-#include <libedataserver/libedataserver.h>
-
-#include <glib/gi18n.h>
-
-#include <string.h>
 
 static const gchar *addrspec_hdrs[] = {
 	"Sender", "From", "Reply-To", "To", "Cc", "Bcc",
diff --git a/em-format/e-mail-formatter.c b/em-format/e-mail-formatter.c
index bc671b8..dcea081 100644
--- a/em-format/e-mail-formatter.c
+++ b/em-format/e-mail-formatter.c
@@ -18,8 +18,6 @@
 
 #include "e-mail-formatter.h"
 
-#include <camel/camel.h>
-
 #include "e-mail-formatter-extension.h"
 #include "e-mail-formatter-utils.h"
 #include "e-mail-part.h"
diff --git a/em-format/e-mail-formatter.h b/em-format/e-mail-formatter.h
index de625a2..d17a3ac 100644
--- a/em-format/e-mail-formatter.h
+++ b/em-format/e-mail-formatter.h
@@ -19,11 +19,12 @@
 #ifndef E_MAIL_FORMATTER_H_
 #define E_MAIL_FORMATTER_H_
 
-#include <em-format/e-mail-part-list.h>
-#include <em-format/e-mail-extension-registry.h>
 #include <gdk/gdk.h>
 #include <libemail-engine/e-mail-enums.h>
 
+#include <em-format/e-mail-extension-registry.h>
+#include <em-format/e-mail-part-list.h>
+
 /* Standard GObject macros */
 #define E_TYPE_MAIL_FORMATTER \
 	(e_mail_formatter_get_type ())
diff --git a/em-format/e-mail-parser-application-mbox.c b/em-format/e-mail-parser-application-mbox.c
index 7007b89..b0fde83 100644
--- a/em-format/e-mail-parser-application-mbox.c
+++ b/em-format/e-mail-parser-application-mbox.c
@@ -20,17 +20,13 @@
 #include <config.h>
 #endif
 
-#include <glib-object.h>
+#include <string.h>
 #include <glib/gi18n-lib.h>
 
-#include <em-format/e-mail-parser-extension.h>
-#include <em-format/e-mail-parser.h>
-#include <em-format/e-mail-part-utils.h>
 #include <e-util/e-util.h>
 
-#include <camel/camel.h>
-
-#include <string.h>
+#include "e-mail-parser-extension.h"
+#include "e-mail-part-utils.h"
 
 typedef EMailParserExtension EMailParserApplicationMBox;
 typedef EMailParserExtensionClass EMailParserApplicationMBoxClass;
diff --git a/em-format/e-mail-parser-application-smime.c b/em-format/e-mail-parser-application-smime.c
index 4f42b26..13c3d73 100644
--- a/em-format/e-mail-parser-application-smime.c
+++ b/em-format/e-mail-parser-application-smime.c
@@ -20,17 +20,13 @@
 #include <config.h>
 #endif
 
-#include <glib-object.h>
+#include <string.h>
 #include <glib/gi18n-lib.h>
 
-#include <em-format/e-mail-parser-extension.h>
-#include <em-format/e-mail-parser.h>
-#include <em-format/e-mail-part-utils.h>
 #include <e-util/e-util.h>
 
-#include <camel/camel.h>
-
-#include <string.h>
+#include "e-mail-parser-extension.h"
+#include "e-mail-part-utils.h"
 
 typedef EMailParserExtension EMailParserApplicationSMIME;
 typedef EMailParserExtensionClass EMailParserApplicationSMIMEClass;
diff --git a/em-format/e-mail-parser-attachment-bar.c b/em-format/e-mail-parser-attachment-bar.c
index a893937..44fbb9c 100644
--- a/em-format/e-mail-parser-attachment-bar.c
+++ b/em-format/e-mail-parser-attachment-bar.c
@@ -20,16 +20,13 @@
 #include <config.h>
 #endif
 
-#include <glib/gi18n-lib.h>
 #include "e-mail-part-attachment-bar.h"
 
-#include <em-format/e-mail-parser-extension.h>
-#include <em-format/e-mail-parser.h>
-#include <e-util/e-util.h>
+#include <glib/gi18n-lib.h>
 
-#include <widgets/misc/e-attachment-bar.h>
+#include <e-util/e-util.h>
 
-#include <camel/camel.h>
+#include "e-mail-parser-extension.h"
 
 static void
 mail_part_attachment_bar_free (EMailPart *part)
diff --git a/em-format/e-mail-parser-extension.c b/em-format/e-mail-parser-extension.c
index 05fd952..28b3be3 100644
--- a/em-format/e-mail-parser-extension.c
+++ b/em-format/e-mail-parser-extension.c
@@ -16,8 +16,6 @@
  *
  */
 
-#include <camel/camel.h>
-
 #include "e-mail-parser-extension.h"
 
 G_DEFINE_ABSTRACT_TYPE (
diff --git a/em-format/e-mail-parser-extension.h b/em-format/e-mail-parser-extension.h
index 4c0b88f..2b486a6 100644
--- a/em-format/e-mail-parser-extension.h
+++ b/em-format/e-mail-parser-extension.h
@@ -19,8 +19,8 @@
 #ifndef E_MAIL_PARSER_EXTENSION_H
 #define E_MAIL_PARSER_EXTENSION_H
 
-#include <em-format/e-mail-parser.h>
 #include <camel/camel.h>
+#include <em-format/e-mail-parser.h>
 
 /* Standard GObject macros */
 #define E_TYPE_MAIL_PARSER_EXTENSION \
diff --git a/em-format/e-mail-parser-headers.c b/em-format/e-mail-parser-headers.c
index d81089d..f290094 100644
--- a/em-format/e-mail-parser-headers.c
+++ b/em-format/e-mail-parser-headers.c
@@ -20,16 +20,13 @@
 #include <config.h>
 #endif
 
+#include <string.h>
 #include <glib/gi18n-lib.h>
 
-#include <em-format/e-mail-parser-extension.h>
-#include <em-format/e-mail-parser.h>
-#include <libemail-engine/e-mail-utils.h>
 #include <e-util/e-util.h>
+#include <libemail-engine/e-mail-utils.h>
 
-#include <camel/camel.h>
-
-#include <string.h>
+#include "e-mail-parser-extension.h"
 
 typedef EMailParserExtension EMailParserHeaders;
 typedef EMailParserExtensionClass EMailParserHeadersClass;
diff --git a/em-format/e-mail-parser-image.c b/em-format/e-mail-parser-image.c
index 4ad4b61..a602a9b 100644
--- a/em-format/e-mail-parser-image.c
+++ b/em-format/e-mail-parser-image.c
@@ -20,13 +20,12 @@
 #include <config.h>
 #endif
 
-#include <em-format/e-mail-parser-extension.h>
-#include <em-format/e-mail-part-utils.h>
-#include <em-format/e-mail-parser.h>
+#include <glib/gi18n-lib.h>
+
 #include <e-util/e-util.h>
 
-#include <glib/gi18n-lib.h>
-#include <camel/camel.h>
+#include "e-mail-parser-extension.h"
+#include "e-mail-part-utils.h"
 
 typedef EMailParserExtension EMailParserImage;
 typedef EMailParserExtensionClass EMailParserImageClass;
diff --git a/em-format/e-mail-parser-inlinepgp-encrypted.c b/em-format/e-mail-parser-inlinepgp-encrypted.c
index 5db01ae..b7d5738 100644
--- a/em-format/e-mail-parser-inlinepgp-encrypted.c
+++ b/em-format/e-mail-parser-inlinepgp-encrypted.c
@@ -20,15 +20,13 @@
 #include <config.h>
 #endif
 
-#include <em-format/e-mail-parser-extension.h>
-#include <em-format/e-mail-parser.h>
-#include <em-format/e-mail-part-utils.h>
-#include <e-util/e-util.h>
-
+#include <string.h>
 #include <glib/gi18n-lib.h>
-#include <camel/camel.h>
 
-#include <string.h>
+#include <e-util/e-util.h>
+
+#include "e-mail-parser-extension.h"
+#include "e-mail-part-utils.h"
 
 typedef EMailParserExtension EMailParserInlinePGPEncrypted;
 typedef EMailParserExtensionClass EMailParserInlinePGPEncryptedClass;
diff --git a/em-format/e-mail-parser-inlinepgp-signed.c b/em-format/e-mail-parser-inlinepgp-signed.c
index cec1db0..2b11d6e 100644
--- a/em-format/e-mail-parser-inlinepgp-signed.c
+++ b/em-format/e-mail-parser-inlinepgp-signed.c
@@ -20,15 +20,13 @@
 #include <config.h>
 #endif
 
-#include <em-format/e-mail-parser-extension.h>
-#include <em-format/e-mail-parser.h>
-#include <em-format/e-mail-part-utils.h>
-#include <e-util/e-util.h>
-
+#include <string.h>
 #include <glib/gi18n-lib.h>
-#include <camel/camel.h>
 
-#include <string.h>
+#include <e-util/e-util.h>
+
+#include "e-mail-parser-extension.h"
+#include "e-mail-part-utils.h"
 
 typedef EMailParserExtension EMailParserInlinePGPSigned;
 typedef EMailParserExtensionClass EMailParserInlinePGPSignedClass;
diff --git a/em-format/e-mail-parser-message-deliverystatus.c b/em-format/e-mail-parser-message-deliverystatus.c
index e99adbc..d02bbf9 100644
--- a/em-format/e-mail-parser-message-deliverystatus.c
+++ b/em-format/e-mail-parser-message-deliverystatus.c
@@ -20,15 +20,11 @@
 #include <config.h>
 #endif
 
-#include <glib-object.h>
+#include <string.h>
 
-#include <em-format/e-mail-parser-extension.h>
-#include <em-format/e-mail-parser.h>
 #include <e-util/e-util.h>
 
-#include <camel/camel.h>
-
-#include <string.h>
+#include "e-mail-parser-extension.h"
 
 typedef EMailParserExtension EMailParserMessageDeliveryStatus;
 typedef EMailParserExtensionClass EMailParserMessageDeliveryStatusClass;
diff --git a/em-format/e-mail-parser-message-external.c b/em-format/e-mail-parser-message-external.c
index 0c49497..d24bb65 100644
--- a/em-format/e-mail-parser-message-external.c
+++ b/em-format/e-mail-parser-message-external.c
@@ -20,15 +20,13 @@
 #include <config.h>
 #endif
 
-#include <em-format/e-mail-parser-extension.h>
-#include <em-format/e-mail-parser.h>
-#include <e-util/e-util.h>
-
-#include <glib/gi18n-lib.h>
-#include <camel/camel.h>
-
 #include <string.h>
 #include <ctype.h>
+#include <glib/gi18n-lib.h>
+
+#include <e-util/e-util.h>
+
+#include "e-mail-parser-extension.h"
 
 typedef EMailParserExtension EMailParserMessageExternal;
 typedef EMailParserExtensionClass EMailParserMessageExternalClass;
diff --git a/em-format/e-mail-parser-message-rfc822.c b/em-format/e-mail-parser-message-rfc822.c
index 2fbfb2d..ca9dd67 100644
--- a/em-format/e-mail-parser-message-rfc822.c
+++ b/em-format/e-mail-parser-message-rfc822.c
@@ -20,18 +20,14 @@
 #include <config.h>
 #endif
 
+#include <string.h>
 #include <glib/gi18n-lib.h>
-#include <glib-object.h>
 
-#include <em-format/e-mail-parser-extension.h>
-#include <em-format/e-mail-parser.h>
-#include <em-format/e-mail-part-list.h>
-#include <em-format/e-mail-part-utils.h>
 #include <e-util/e-util.h>
 
-#include <camel/camel.h>
-
-#include <string.h>
+#include "e-mail-parser-extension.h"
+#include "e-mail-part-list.h"
+#include "e-mail-part-utils.h"
 
 typedef EMailParserExtension EMailParserMessageRFC822;
 typedef EMailParserExtensionClass EMailParserMessageRFC822Class;
diff --git a/em-format/e-mail-parser-message.c b/em-format/e-mail-parser-message.c
index 3adbc55..9cd0a2d 100644
--- a/em-format/e-mail-parser-message.c
+++ b/em-format/e-mail-parser-message.c
@@ -20,17 +20,14 @@
 #include <config.h>
 #endif
 
+#include <string.h>
 #include <glib/gi18n-lib.h>
 
-#include <em-format/e-mail-parser-extension.h>
-#include <em-format/e-mail-parser.h>
-#include "e-mail-part-utils.h"
-#include <libemail-engine/e-mail-utils.h>
 #include <e-util/e-util.h>
+#include <libemail-engine/e-mail-utils.h>
 
-#include <camel/camel.h>
-
-#include <string.h>
+#include "e-mail-parser-extension.h"
+#include "e-mail-part-utils.h"
 
 typedef EMailParserExtension EMailParserMessage;
 typedef EMailParserExtensionClass EMailParserMessageClass;
diff --git a/em-format/e-mail-parser-multipart-alternative.c b/em-format/e-mail-parser-multipart-alternative.c
index 902812b..cc1c872 100644
--- a/em-format/e-mail-parser-multipart-alternative.c
+++ b/em-format/e-mail-parser-multipart-alternative.c
@@ -20,14 +20,12 @@
 #include <config.h>
 #endif
 
-#include <em-format/e-mail-parser-extension.h>
-#include <em-format/e-mail-parser.h>
-#include <em-format/e-mail-part-utils.h>
-#include <e-util/e-util.h>
+#include <string.h>
 
-#include <camel/camel.h>
+#include <e-util/e-util.h>
 
-#include <string.h>
+#include "e-mail-parser-extension.h"
+#include "e-mail-part-utils.h"
 
 typedef EMailParserExtension EMailParserMultipartAlternative;
 typedef EMailParserExtensionClass EMailParserMultipartAlternativeClass;
diff --git a/em-format/e-mail-parser-multipart-appledouble.c b/em-format/e-mail-parser-multipart-appledouble.c
index add3fad..a866363 100644
--- a/em-format/e-mail-parser-multipart-appledouble.c
+++ b/em-format/e-mail-parser-multipart-appledouble.c
@@ -20,10 +20,7 @@
 #include <config.h>
 #endif
 
-#include <em-format/e-mail-parser-extension.h>
-#include <em-format/e-mail-parser.h>
-
-#include <camel/camel.h>
+#include "e-mail-parser-extension.h"
 
 typedef EMailParserExtension EMailParserMultipartAppleDouble;
 typedef EMailParserExtensionClass EMailParserMultipartAppleDoubleClass;
diff --git a/em-format/e-mail-parser-multipart-digest.c b/em-format/e-mail-parser-multipart-digest.c
index b62d066..64d4882 100644
--- a/em-format/e-mail-parser-multipart-digest.c
+++ b/em-format/e-mail-parser-multipart-digest.c
@@ -20,13 +20,11 @@
 #include <config.h>
 #endif
 
-#include <em-format/e-mail-parser-extension.h>
-#include <em-format/e-mail-parser.h>
-#include <e-util/e-util.h>
+#include <string.h>
 
-#include <camel/camel.h>
+#include <e-util/e-util.h>
 
-#include <string.h>
+#include "e-mail-parser-extension.h"
 
 typedef EMailParserExtension EMailParserMultipartDigest;
 typedef EMailParserExtensionClass EMailParserMultipartDigestClass;
diff --git a/em-format/e-mail-parser-multipart-encrypted.c b/em-format/e-mail-parser-multipart-encrypted.c
index fa35801..c815ab5 100644
--- a/em-format/e-mail-parser-multipart-encrypted.c
+++ b/em-format/e-mail-parser-multipart-encrypted.c
@@ -20,14 +20,13 @@
 #include <config.h>
 #endif
 
-#include <em-format/e-mail-parser-extension.h>
-#include <em-format/e-mail-parser.h>
-#include <em-format/e-mail-part-utils.h>
-
 #include <glib/gi18n-lib.h>
-#include <camel/camel.h>
+
 #include <libedataserver/libedataserver.h>
 
+#include "e-mail-parser-extension.h"
+#include "e-mail-part-utils.h"
+
 typedef EMailParserExtension EMailParserMultipartEncrypted;
 typedef EMailParserExtensionClass EMailParserMultipartEncryptedClass;
 
diff --git a/em-format/e-mail-parser-multipart-mixed.c b/em-format/e-mail-parser-multipart-mixed.c
index 8a2bfd9..9e876ee 100644
--- a/em-format/e-mail-parser-multipart-mixed.c
+++ b/em-format/e-mail-parser-multipart-mixed.c
@@ -20,14 +20,12 @@
 #include <config.h>
 #endif
 
-#include <em-format/e-mail-parser-extension.h>
-#include <em-format/e-mail-parser.h>
-#include <e-util/e-util.h>
-#include <em-format/e-mail-part-utils.h>
+#include <string.h>
 
-#include <camel/camel.h>
+#include <e-util/e-util.h>
 
-#include <string.h>
+#include "e-mail-parser-extension.h"
+#include "e-mail-part-utils.h"
 
 typedef EMailParserExtension EMailParserMultipartMixed;
 typedef EMailParserExtensionClass EMailParserMultipartMixedClass;
diff --git a/em-format/e-mail-parser-multipart-related.c b/em-format/e-mail-parser-multipart-related.c
index a9beac5..f58a5a0 100644
--- a/em-format/e-mail-parser-multipart-related.c
+++ b/em-format/e-mail-parser-multipart-related.c
@@ -20,14 +20,12 @@
 #include <config.h>
 #endif
 
-#include <em-format/e-mail-parser-extension.h>
-#include <em-format/e-mail-parser.h>
-#include <em-format/e-mail-part-utils.h>
-#include <e-util/e-util.h>
+#include <string.h>
 
-#include <camel/camel.h>
+#include <e-util/e-util.h>
 
-#include <string.h>
+#include "e-mail-parser-extension.h"
+#include "e-mail-part-utils.h"
 
 typedef EMailParserExtension EMailParserMultipartRelated;
 typedef EMailParserExtensionClass EMailParserMultipartRelatedClass;
diff --git a/em-format/e-mail-parser-multipart-signed.c b/em-format/e-mail-parser-multipart-signed.c
index 05e64a8..514d40f 100644
--- a/em-format/e-mail-parser-multipart-signed.c
+++ b/em-format/e-mail-parser-multipart-signed.c
@@ -20,14 +20,13 @@
 #include <config.h>
 #endif
 
-#include <em-format/e-mail-parser-extension.h>
-#include <em-format/e-mail-parser.h>
-#include <em-format/e-mail-part-utils.h>
-
 #include <glib/gi18n-lib.h>
-#include <camel/camel.h>
+
 #include <libedataserver/libedataserver.h>
 
+#include "e-mail-parser-extension.h"
+#include "e-mail-part-utils.h"
+
 typedef EMailParserExtension EMailParserMultipartSigned;
 typedef EMailParserExtensionClass EMailParserMultipartSignedClass;
 
diff --git a/em-format/e-mail-parser-secure-button.c b/em-format/e-mail-parser-secure-button.c
index cb1f98e..3836859 100644
--- a/em-format/e-mail-parser-secure-button.c
+++ b/em-format/e-mail-parser-secure-button.c
@@ -22,11 +22,9 @@
 
 #include <glib/gi18n-lib.h>
 
-#include <em-format/e-mail-parser-extension.h>
-#include <em-format/e-mail-parser.h>
 #include <e-util/e-util.h>
 
-#include <camel/camel.h>
+#include "e-mail-parser-extension.h"
 
 typedef EMailParserExtension EMailParserSecureButton;
 typedef EMailParserExtensionClass EMailParserSecureButtonClass;
diff --git a/em-format/e-mail-parser-source.c b/em-format/e-mail-parser-source.c
index 0521650..ff8355d 100644
--- a/em-format/e-mail-parser-source.c
+++ b/em-format/e-mail-parser-source.c
@@ -20,12 +20,11 @@
 #include <config.h>
 #endif
 
-#include <em-format/e-mail-parser-extension.h>
-#include <em-format/e-mail-parser.h>
+#include <glib/gi18n-lib.h>
+
 #include <e-util/e-util.h>
 
-#include <glib/gi18n-lib.h>
-#include <camel/camel.h>
+#include "e-mail-parser-extension.h"
 
 typedef EMailParserExtension EMailParserSource;
 typedef EMailParserExtensionClass EMailParserSourceClass;
diff --git a/em-format/e-mail-parser-text-enriched.c b/em-format/e-mail-parser-text-enriched.c
index 18b7251..024d160 100644
--- a/em-format/e-mail-parser-text-enriched.c
+++ b/em-format/e-mail-parser-text-enriched.c
@@ -20,13 +20,12 @@
 #include <config.h>
 #endif
 
-#include <em-format/e-mail-parser-extension.h>
-#include <em-format/e-mail-parser.h>
-#include <em-format/e-mail-part-utils.h>
+#include <glib/gi18n-lib.h>
+
 #include <e-util/e-util.h>
 
-#include <glib/gi18n-lib.h>
-#include <camel/camel.h>
+#include "e-mail-parser-extension.h"
+#include "e-mail-part-utils.h"
 
 typedef EMailParserExtension EMailParserTextEnriched;
 typedef EMailParserExtensionClass EMailParserTextEnrichedClass;
diff --git a/em-format/e-mail-parser-text-html.c b/em-format/e-mail-parser-text-html.c
index 0014e2c..ccfc5d6 100644
--- a/em-format/e-mail-parser-text-html.c
+++ b/em-format/e-mail-parser-text-html.c
@@ -20,15 +20,13 @@
 #include <config.h>
 #endif
 
-#include <em-format/e-mail-parser-extension.h>
-#include <em-format/e-mail-parser.h>
-#include <em-format/e-mail-part-utils.h>
-#include <e-util/e-util.h>
-
+#include <string.h>
 #include <glib/gi18n-lib.h>
-#include <camel/camel.h>
 
-#include <string.h>
+#include <e-util/e-util.h>
+
+#include "e-mail-parser-extension.h"
+#include "e-mail-part-utils.h"
 
 typedef EMailParserExtension EMailParserTextHTML;
 typedef EMailParserExtensionClass EMailParserTextHTMLClass;
diff --git a/em-format/e-mail-parser-text-plain.c b/em-format/e-mail-parser-text-plain.c
index f5e3064..f0b34e4 100644
--- a/em-format/e-mail-parser-text-plain.c
+++ b/em-format/e-mail-parser-text-plain.c
@@ -20,15 +20,14 @@
 #include <config.h>
 #endif
 
-#include <em-format/e-mail-parser-extension.h>
-#include <em-format/e-mail-parser.h>
-#include <em-format/e-mail-inline-filter.h>
-#include <em-format/e-mail-part-utils.h>
+#include <ctype.h>
+#include <glib/gi18n-lib.h>
+
 #include <e-util/e-util.h>
 
-#include <glib/gi18n-lib.h>
-#include <camel/camel.h>
-#include <ctype.h>
+#include "e-mail-inline-filter.h"
+#include "e-mail-parser-extension.h"
+#include "e-mail-part-utils.h"
 
 typedef EMailParserExtension EMailParserTextPlain;
 typedef EMailParserExtensionClass EMailParserTextPlainClass;
diff --git a/em-format/e-mail-parser.c b/em-format/e-mail-parser.c
index 4ba17bb..359efe7 100644
--- a/em-format/e-mail-parser.c
+++ b/em-format/e-mail-parser.c
@@ -17,21 +17,17 @@
  */
 
 #include "e-mail-parser.h"
-#include "e-mail-parser-extension.h"
-#include "e-mail-part-attachment.h"
-#include "e-mail-part-utils.h"
 
-#include <camel/camel.h>
-#include <libebackend/libebackend.h>
+#include <string.h>
 
-#include <e-util/e-util.h>
+#include <libebackend/libebackend.h>
 
 #include <shell/e-shell.h>
 #include <shell/e-shell-window.h>
 
-#include <widgets/misc/e-attachment.h>
-
-#include <string.h>
+#include "e-mail-parser-extension.h"
+#include "e-mail-part-attachment.h"
+#include "e-mail-part-utils.h"
 
 #define E_MAIL_PARSER_GET_PRIVATE(obj) \
 	(G_TYPE_INSTANCE_GET_PRIVATE \
diff --git a/em-format/e-mail-part-attachment-bar.h b/em-format/e-mail-part-attachment-bar.h
index e6d6542..87b6311 100644
--- a/em-format/e-mail-part-attachment-bar.h
+++ b/em-format/e-mail-part-attachment-bar.h
@@ -23,8 +23,6 @@
 
 #include <em-format/e-mail-part.h>
 
-#include <widgets/misc/e-attachment-store.h>
-
 typedef struct _EMailPartAttachmentBar {
 	EMailPart parent;
 
diff --git a/em-format/e-mail-part-utils.c b/em-format/e-mail-part-utils.c
index 665a7b5..2e2b5f6 100644
--- a/em-format/e-mail-part-utils.c
+++ b/em-format/e-mail-part-utils.c
@@ -25,7 +25,6 @@
 #include "e-mail-part-utils.h"
 #include "e-mail-parser-extension.h"
 
-#include <camel/camel.h>
 #include <e-util/e-util.h>
 #include <gdk/gdk.h>
 
diff --git a/em-format/e-mail-part.h b/em-format/e-mail-part.h
index 82e417c..774828e 100644
--- a/em-format/e-mail-part.h
+++ b/em-format/e-mail-part.h
@@ -20,9 +20,10 @@
 #define E_MAIL_PART_H_
 
 #include <camel/camel.h>
-#include <misc/e-attachment.h>
 #include <webkit/webkitdom.h>
 
+#include <e-util/e-util.h>
+
 #define E_MAIL_PART_IS(p,s_t) \
 		((p != NULL) && (e_mail_part_get_instance_size (p) == sizeof (s_t)))
 #define E_MAIL_PART(o) ((EMailPart *) o)
diff --git a/evolution-shell.pc.in b/evolution-shell.pc.in
index e92df94..fd85124 100644
--- a/evolution-shell.pc.in
+++ b/evolution-shell.pc.in
@@ -17,7 +17,7 @@ execversion= BASE_VERSION@
 Name: evolution-shell
 Description: libraries needed for Evolution shell components
 Version: @VERSION@
-Requires: gtk+-3.0 libebackend-1.2 libedataserverui-3.0 webkitgtk-3.0
+Requires: gtk+-3.0 libebackend-1.2 libedataserverui-3.0 webkitgtk-3.0 libgtkhtml-4.0 gtkhtml-editor-4.0
 Requires.private: gnome-desktop-3.0
-Libs: -L${privlibdir} -leshell -lemiscwidgets -leutil -Wl,-R${privlibdir}
+Libs: -L${privlibdir} -leshell -leutil -Wl,-R${privlibdir}
 Cflags: -I${privincludedir}
diff --git a/evolution-zip.in b/evolution-zip.in
index 9183c3c..eb8acd5 100755
--- a/evolution-zip.in
+++ b/evolution-zip.in
@@ -15,14 +15,9 @@ bin/libeabutil-0.dll
 bin/libecontacteditor-0.dll
 bin/libecontactlisteditor-0.dll
 bin/libefilterbar-0.dll
-bin/libemiscwidgets-0.dll
 bin/libeshell-0.dll
 bin/libessmime-0.dll
-bin/libetable-0.dll
-bin/libetext-0.dll
-bin/libetimezonedialog-0.dll
 bin/libeutil-0.dll
-bin/libevolution-a11y-0.dll
 bin/libevolution-addressbook-a11y-0.dll
 bin/libevolution-addressbook-importers-0.dll
 bin/libevolution-calendar-a11y-0.dll
@@ -30,8 +25,6 @@ bin/libevolution-calendar-importers-0.dll
 bin/libevolution-mail-importers-0.dll
 bin/libevolution-smime-0.dll
 bin/libevolution-widgets-a11y-0.dll
-bin/libfilter-0.dll
-bin/libmenus-0.dll
 bin/evolution.exe
 lib/bonobo/servers/GNOME_Evolution_Addressbook.server
 lib/bonobo/servers/GNOME_Evolution_Calendar.server
diff --git a/libemail-engine/Makefile.am b/libemail-engine/Makefile.am
index 5589fe4..55fbef2 100644
--- a/libemail-engine/Makefile.am
+++ b/libemail-engine/Makefile.am
@@ -18,7 +18,9 @@ libemail_engine_la_CPPFLAGS = \
 	-I$(top_builddir) \
 	$(EVOLUTION_DATA_SERVER_CFLAGS) \
 	$(GNOME_PLATFORM_CFLAGS) \
-	-DEVOLUTION_PRIVDATADIR=\""$(privdatadir)"\"	\
+	$(CHAMPLAIN_CFLAGS) \
+	$(GTKHTML_CFLAGS) \
+	-DEVOLUTION_PRIVDATADIR=\""$(privdatadir)"\" \
 	$(NULL)
 
 libmailengineincludedir = $(privincludedir)/libemail-engine
@@ -33,11 +35,15 @@ libmailengineinclude_HEADERS =  \
 	e-mail-session.h \
 	e-mail-store-utils.h \
 	e-mail-utils.h \
+	em-filter-folder-element.h \
+	em-vfolder-context.h \
+	em-vfolder-rule.h \
 	mail-config.h \
 	mail-folder-cache.h \
+	mail-mt.h \
 	mail-ops.h \
 	mail-tools.h \
-	mail-vfolder.h	\
+	mail-vfolder.h \
 	$(NULL)
 
 libemail_engine_la_SOURCES =  \
@@ -51,19 +57,23 @@ libemail_engine_la_SOURCES =  \
 	e-mail-session.c \
 	e-mail-store-utils.c \
 	e-mail-utils.c \
+	em-filter-folder-element.c \
+	em-vfolder-context.c \
+	em-vfolder-rule.c \
 	mail-config.c \
 	mail-folder-cache.c \
+	mail-mt.c \
 	mail-ops.c \
 	mail-tools.c \
-	mail-vfolder.c	\
+	mail-vfolder.c \
 	$(NULL)
 
 libemail_engine_la_LIBADD = \
-	$(top_builddir)/libemail-utils/libemail-utils.la \
-	$(top_builddir)/filter/libfilter.la \
-	$(top_builddir)/libevolution-utils/libevolution-utils.la \
+	$(top_builddir)/e-util/libeutil.la \
 	$(EVOLUTION_DATA_SERVER_LIBS) \
 	$(GNOME_PLATFORM_LIBS) \
+	$(CHAMPLAIN_LIBS) \
+	$(GTKHTML_LIBS) \
 	$(NULL)
 
 libemail_engine_la_LDFLAGS = -avoid-version $(NO_UNDEFINED)
diff --git a/libemail-engine/e-mail-session.c b/libemail-engine/e-mail-session.c
index aa3adef..18e336a 100644
--- a/libemail-engine/e-mail-session.c
+++ b/libemail-engine/e-mail-session.c
@@ -43,9 +43,10 @@
 #endif
 
 #include <libebackend/libebackend.h>
-#include <libedataserverui/libedataserverui.h>
 
-#include "libemail-utils/mail-mt.h"
+#include "libemail-engine/mail-mt.h"
+
+#include "e-util/e-util.h"
 
 /* This is our hack, not part of libcamel. */
 #include "camel-null-store.h"
diff --git a/libemail-engine/e-mail-session.h b/libemail-engine/e-mail-session.h
index bd4b2f6..9445e0b 100644
--- a/libemail-engine/e-mail-session.h
+++ b/libemail-engine/e-mail-session.h
@@ -28,8 +28,8 @@
 #include <camel/camel.h>
 #include <libedataserver/libedataserver.h>
 #include <libemail-engine/e-mail-enums.h>
+#include <libemail-engine/em-vfolder-context.h>
 #include <libemail-engine/mail-folder-cache.h>
-#include <libemail-utils/em-vfolder-context.h>
 
 /* Standard GObject macros */
 #define E_TYPE_MAIL_SESSION \
diff --git a/libemail-engine/e-mail-utils.c b/libemail-engine/e-mail-utils.c
index d142966..33ff0d1 100644
--- a/libemail-engine/e-mail-utils.c
+++ b/libemail-engine/e-mail-utils.c
@@ -39,7 +39,7 @@
 #include <glib/gi18n.h>
 #include <libebook/libebook.h>
 
-#include <libemail-utils/mail-mt.h>
+#include <libemail-engine/mail-mt.h>
 
 #include "e-mail-folder-utils.h"
 #include "e-mail-session.h"
diff --git a/libemail-engine/em-filter-folder-element.c b/libemail-engine/em-filter-folder-element.c
new file mode 100644
index 0000000..77569b0
--- /dev/null
+++ b/libemail-engine/em-filter-folder-element.c
@@ -0,0 +1,226 @@
+/*
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Not Zed <notzed lostzed mmc com au>
+ *      Jeffrey Stedfast <fejj ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "em-filter-folder-element.h"
+
+#include <string.h>
+
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+
+#define EM_FILTER_FOLDER_ELEMENT_GET_PRIVATE(obj) \
+	(G_TYPE_INSTANCE_GET_PRIVATE \
+	((obj), EM_TYPE_FILTER_FOLDER_ELEMENT, EMFilterFolderElementPrivate))
+
+struct _EMFilterFolderElementPrivate {
+	gchar *uri;
+};
+
+G_DEFINE_TYPE (
+	EMFilterFolderElement,
+	em_filter_folder_element,
+	E_TYPE_FILTER_ELEMENT)
+
+static void
+filter_folder_element_finalize (GObject *object)
+{
+	EMFilterFolderElementPrivate *priv;
+
+	priv = EM_FILTER_FOLDER_ELEMENT_GET_PRIVATE (object);
+
+	g_free (priv->uri);
+
+	/* Chain up to parent's finalize() method. */
+	G_OBJECT_CLASS (em_filter_folder_element_parent_class)->finalize (object);
+}
+
+static gboolean
+filter_folder_element_validate (EFilterElement *fe,
+                                EAlert **alert)
+{
+	EMFilterFolderElement *ff = (EMFilterFolderElement *) fe;
+
+	g_warn_if_fail (alert == NULL || *alert == NULL);
+
+	if (ff->priv->uri != NULL && *ff->priv->uri != '\0')
+		return TRUE;
+
+	if (alert)
+		*alert = e_alert_new ("mail:no-folder", NULL);
+
+	return FALSE;
+}
+
+static gint
+filter_folder_element_eq (EFilterElement *fe,
+                          EFilterElement *cm)
+{
+	return E_FILTER_ELEMENT_CLASS (
+		em_filter_folder_element_parent_class)->eq (fe, cm) &&
+		strcmp (((EMFilterFolderElement *) fe)->priv->uri,
+		((EMFilterFolderElement *) cm)->priv->uri)== 0;
+}
+
+static xmlNodePtr
+filter_folder_element_xml_encode (EFilterElement *fe)
+{
+	xmlNodePtr value, work;
+	EMFilterFolderElement *ff = (EMFilterFolderElement *) fe;
+
+	value = xmlNewNode (NULL, (xmlChar *) "value");
+	xmlSetProp (value, (xmlChar *) "name", (xmlChar *) fe->name);
+	xmlSetProp (value, (xmlChar *) "type", (xmlChar *) "folder");
+
+	work = xmlNewChild (value, NULL, (xmlChar *) "folder", NULL);
+	xmlSetProp (work, (xmlChar *) "uri", (xmlChar *) ff->priv->uri);
+
+	return value;
+}
+
+static gint
+filter_folder_element_xml_decode (EFilterElement *fe,
+                                  xmlNodePtr node)
+{
+	EMFilterFolderElement *ff = (EMFilterFolderElement *) fe;
+	xmlNodePtr n;
+
+	xmlFree (fe->name);
+	fe->name = (gchar *) xmlGetProp (node, (xmlChar *) "name");
+
+	n = node->children;
+	while (n) {
+		if (!strcmp ((gchar *) n->name, "folder")) {
+			gchar *uri;
+
+			uri = (gchar *) xmlGetProp (n, (xmlChar *) "uri");
+			g_free (ff->priv->uri);
+			ff->priv->uri = g_strdup (uri);
+			xmlFree (uri);
+			break;
+		}
+		n = n->next;
+	}
+
+	return 0;
+}
+
+static GtkWidget *
+filter_folder_element_get_widget (EFilterElement *fe)
+{
+	GtkWidget *widget;
+
+	widget = E_FILTER_ELEMENT_CLASS (em_filter_folder_element_parent_class)->
+		get_widget (fe);
+
+	return widget;
+}
+
+static void
+filter_folder_element_build_code (EFilterElement *fe,
+                                  GString *out,
+                                  EFilterPart *ff)
+{
+	/* We are doing nothing on purpose. */
+}
+
+static void
+filter_folder_element_format_sexp (EFilterElement *fe,
+                                   GString *out)
+{
+	EMFilterFolderElement *ff = (EMFilterFolderElement *) fe;
+
+	camel_sexp_encode_string (out, ff->priv->uri);
+}
+
+static void
+filter_folder_element_copy_value (EFilterElement *de,
+                                  EFilterElement *se)
+{
+	if (EM_IS_FILTER_FOLDER_ELEMENT (se)) {
+		em_filter_folder_element_set_uri (
+			EM_FILTER_FOLDER_ELEMENT (de),
+			EM_FILTER_FOLDER_ELEMENT (se)->priv->uri);
+	} else {
+		E_FILTER_ELEMENT_CLASS (
+		em_filter_folder_element_parent_class)->copy_value (de, se);
+	}
+}
+static void
+em_filter_folder_element_class_init (EMFilterFolderElementClass *class)
+{
+	GObjectClass *object_class;
+	EFilterElementClass *filter_element_class;
+
+	g_type_class_add_private (class, sizeof (EMFilterFolderElementPrivate));
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->finalize = filter_folder_element_finalize;
+
+	filter_element_class = E_FILTER_ELEMENT_CLASS (class);
+	filter_element_class->validate = filter_folder_element_validate;
+	filter_element_class->eq = filter_folder_element_eq;
+	filter_element_class->xml_encode = filter_folder_element_xml_encode;
+	filter_element_class->xml_decode = filter_folder_element_xml_decode;
+	filter_element_class->get_widget = filter_folder_element_get_widget;
+	filter_element_class->build_code = filter_folder_element_build_code;
+	filter_element_class->format_sexp = filter_folder_element_format_sexp;
+	filter_element_class->copy_value = filter_folder_element_copy_value;
+}
+
+static void
+em_filter_folder_element_init (EMFilterFolderElement *element)
+{
+	element->priv = EM_FILTER_FOLDER_ELEMENT_GET_PRIVATE (element);
+}
+
+EFilterElement *
+em_filter_folder_element_new ()
+{
+	return g_object_new (
+		EM_TYPE_FILTER_FOLDER_ELEMENT,
+		NULL);
+}
+
+const gchar *
+em_filter_folder_element_get_uri (EMFilterFolderElement *element)
+{
+	g_return_val_if_fail (EM_IS_FILTER_FOLDER_ELEMENT (element), NULL);
+
+	return element->priv->uri;
+}
+
+void
+em_filter_folder_element_set_uri (EMFilterFolderElement *element,
+                                  const gchar *uri)
+{
+	g_return_if_fail (EM_IS_FILTER_FOLDER_ELEMENT (element));
+
+	g_free (element->priv->uri);
+	element->priv->uri = g_strdup (uri);
+}
+
diff --git a/libemail-engine/em-filter-folder-element.h b/libemail-engine/em-filter-folder-element.h
new file mode 100644
index 0000000..8c61901
--- /dev/null
+++ b/libemail-engine/em-filter-folder-element.h
@@ -0,0 +1,74 @@
+/*
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Not Zed <notzed lostzed mmc com au>
+ *      Jeelementrey Stedfast <fejj ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifndef EM_FILTER_FOLDER_ELEMENT_H
+#define EM_FILTER_FOLDER_ELEMENT_H
+
+#include <e-util/e-util.h>
+
+/* Standard GObject macros */
+#define EM_TYPE_FILTER_FOLDER_ELEMENT \
+	(em_filter_folder_element_get_type ())
+#define EM_FILTER_FOLDER_ELEMENT(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), EM_TYPE_FILTER_FOLDER_ELEMENT, EMFilterFolderElement))
+#define EM_FILTER_FOLDER_ELEMENT_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), EM_TYPE_FILTER_FOLDER_ELEMENT, EMFilterFolderElementClass))
+#define EM_IS_FILTER_FOLDER_ELEMENT(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), EM_TYPE_FILTER_FOLDER_ELEMENT))
+#define EM_IS_FILTER_FOLDER_ELEMENT_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), EM_TYPE_FILTER_FOLDER_ELEMENT))
+#define EM_FILTER_FOLDER_ELEMENT_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), EM_TYPE_FILTER_FOLDER_ELEMENT, EMFilterFolderElementClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EMFilterFolderElement EMFilterFolderElement;
+typedef struct _EMFilterFolderElementClass EMFilterFolderElementClass;
+typedef struct _EMFilterFolderElementPrivate EMFilterFolderElementPrivate;
+
+struct _EMFilterFolderElement {
+	EFilterElement parent;
+	EMFilterFolderElementPrivate *priv;
+};
+
+struct _EMFilterFolderElementClass {
+	EFilterElementClass parent_class;
+};
+
+GType		em_filter_folder_element_get_type (void);
+EFilterElement *em_filter_folder_element_new	(void);
+const gchar *	em_filter_folder_element_get_uri
+						(EMFilterFolderElement *element);
+void		em_filter_folder_element_set_uri
+						(EMFilterFolderElement *element,
+						 const gchar *uri);
+
+G_END_DECLS
+
+#endif /* EM_FILTER_FOLDER_ELEMENT_H */
diff --git a/libemail-engine/em-vfolder-context.c b/libemail-engine/em-vfolder-context.c
new file mode 100644
index 0000000..69b82eb
--- /dev/null
+++ b/libemail-engine/em-vfolder-context.c
@@ -0,0 +1,110 @@
+/*
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Not Zed <notzed lostzed mmc com au>
+ *      Jeffrey Stedfast <fejj ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "em-vfolder-context.h"
+
+#include <string.h>
+
+#include "em-filter-folder-element.h"
+#include "em-vfolder-rule.h"
+
+#define EM_VFOLDER_CONTEXT_GET_PRIVATE(obj) \
+	(G_TYPE_INSTANCE_GET_PRIVATE \
+	((obj), EM_TYPE_VFOLDER_CONTEXT, EMVFolderContextPrivate))
+
+struct _EMVFolderContextPrivate {
+	gint placeholder;
+};
+
+enum {
+	PROP_0,
+	PROP_SESSION
+};
+
+G_DEFINE_TYPE (
+	EMVFolderContext,
+	em_vfolder_context,
+	E_TYPE_RULE_CONTEXT)
+
+static EFilterElement *
+vfolder_context_new_element (ERuleContext *context,
+                             const gchar *type)
+{
+	if (strcmp (type, "system-flag") == 0)
+		return e_filter_option_new ();
+
+	if (strcmp (type, "score") == 0)
+		return e_filter_int_new_type ("score", -3, 3);
+
+	if (strcmp (type, "folder") == 0)
+		return em_filter_folder_element_new ();
+
+	/* XXX Legacy type name.  Same as "folder" now. */
+	if (strcmp (type, "folder-curi") == 0)
+		return em_filter_folder_element_new ();
+
+	return E_RULE_CONTEXT_CLASS (em_vfolder_context_parent_class)->
+		new_element (context, type);
+}
+
+static void
+em_vfolder_context_class_init (EMVFolderContextClass *class)
+{
+	ERuleContextClass *rule_context_class;
+
+	g_type_class_add_private (class, sizeof (EMVFolderContextPrivate));
+
+	rule_context_class = E_RULE_CONTEXT_CLASS (class);
+	rule_context_class->new_element = vfolder_context_new_element;
+}
+
+static void
+em_vfolder_context_init (EMVFolderContext *context)
+{
+	context->priv = EM_VFOLDER_CONTEXT_GET_PRIVATE (context);
+
+	e_rule_context_add_part_set (
+		E_RULE_CONTEXT (context), "partset", E_TYPE_FILTER_PART,
+		(ERuleContextPartFunc) e_rule_context_add_part,
+		(ERuleContextNextPartFunc) e_rule_context_next_part);
+
+	e_rule_context_add_rule_set (
+		E_RULE_CONTEXT (context), "ruleset", EM_TYPE_VFOLDER_RULE,
+		(ERuleContextRuleFunc) e_rule_context_add_rule,
+		(ERuleContextNextRuleFunc) e_rule_context_next_rule);
+
+	E_RULE_CONTEXT (context)->flags =
+		E_RULE_CONTEXT_THREADING | E_RULE_CONTEXT_GROUPING;
+}
+
+EMVFolderContext *
+em_vfolder_context_new ()
+{
+	return g_object_new (
+		EM_TYPE_VFOLDER_CONTEXT, NULL);
+}
diff --git a/libemail-engine/em-vfolder-context.h b/libemail-engine/em-vfolder-context.h
new file mode 100644
index 0000000..9e2faf3
--- /dev/null
+++ b/libemail-engine/em-vfolder-context.h
@@ -0,0 +1,70 @@
+/*
+ *
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Not Zed <notzed lostzed mmc com au>
+ *      Jeffrey Stedfast <fejj ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifndef EM_VFOLDER_CONTEXT_H
+#define EM_VFOLDER_CONTEXT_H
+
+#include <e-util/e-util.h>
+
+/* Standard GObject macros */
+#define EM_TYPE_VFOLDER_CONTEXT \
+	(em_vfolder_context_get_type ())
+#define EM_VFOLDER_CONTEXT(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), EM_TYPE_VFOLDER_CONTEXT, EMVFolderContext))
+#define EM_VFOLDER_CONTEXT_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), EM_TYPE_VFOLDER_CONTEXT, EMVFolderContextClass))
+#define EM_IS_VFOLDER_CONTEXT(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), EM_TYPE_VFOLDER_CONTEXT))
+#define EM_IS_VFOLDER_CONTEXT_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), EM_TYPE_VFOLDER_CONTEXT))
+#define EM_VFOLDER_CONTEXT_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), EM_TYPE_VFOLDER_CONTEXT, EMVFolderContextClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EMVFolderContext EMVFolderContext;
+typedef struct _EMVFolderContextClass EMVFolderContextClass;
+typedef struct _EMVFolderContextPrivate EMVFolderContextPrivate;
+
+struct _EMVFolderContext {
+	ERuleContext parent;
+	EMVFolderContextPrivate *priv;
+};
+
+struct _EMVFolderContextClass {
+	ERuleContextClass parent_class;
+};
+
+GType		em_vfolder_context_get_type	(void);
+EMVFolderContext *
+		em_vfolder_context_new		(void);
+
+G_END_DECLS
+
+#endif /* EM_VFOLDER_CONTEXT_H */
diff --git a/libemail-engine/em-vfolder-rule.c b/libemail-engine/em-vfolder-rule.c
new file mode 100644
index 0000000..0d6b07a
--- /dev/null
+++ b/libemail-engine/em-vfolder-rule.c
@@ -0,0 +1,494 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Not Zed <notzed lostzed mmc com au>
+ *      Jeffrey Stedfast <fejj ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+
+#include <libemail-engine/e-mail-folder-utils.h>
+
+#include "em-vfolder-context.h"
+#include "em-vfolder-rule.h"
+
+#define EM_VFOLDER_RULE_GET_PRIVATE(obj) \
+	(G_TYPE_INSTANCE_GET_PRIVATE \
+	((obj), EM_TYPE_VFOLDER_RULE, EMVFolderRulePrivate))
+
+#define EM_VFOLDER_RULE_GET_PRIVATE(obj) \
+	(G_TYPE_INSTANCE_GET_PRIVATE \
+	((obj), EM_TYPE_VFOLDER_RULE, EMVFolderRulePrivate))
+
+struct _EMVFolderRulePrivate {
+	em_vfolder_rule_with_t with;
+	GQueue sources;		/* uri's of the source folders */
+	gboolean autoupdate;
+	GHashTable *include_subfolders;
+};
+
+static gint validate (EFilterRule *, EAlert **alert);
+static gint vfolder_eq (EFilterRule *fr, EFilterRule *cm);
+static xmlNodePtr xml_encode (EFilterRule *);
+static gint xml_decode (EFilterRule *, xmlNodePtr, ERuleContext *f);
+static void rule_copy (EFilterRule *dest, EFilterRule *src);
+static GtkWidget *get_widget (EFilterRule *fr, ERuleContext *f);
+
+/* DO NOT internationalise these strings */
+static const gchar *with_names[] = {
+	"specific",
+	"local_remote_active",
+	"remote_active",
+	"local"
+};
+
+G_DEFINE_TYPE (
+	EMVFolderRule,
+	em_vfolder_rule,
+	E_TYPE_FILTER_RULE)
+
+static void
+vfolder_rule_finalize (GObject *object)
+{
+	EMVFolderRule *rule = EM_VFOLDER_RULE (object);
+	gchar *uri;
+
+	while ((uri = g_queue_pop_head (&rule->priv->sources)) != NULL)
+		g_free (uri);
+
+	g_hash_table_destroy (rule->priv->include_subfolders);
+
+	/* Chain up to parent's finalize() method. */
+	G_OBJECT_CLASS (em_vfolder_rule_parent_class)->finalize (object);
+}
+
+static void
+em_vfolder_rule_class_init (EMVFolderRuleClass *class)
+{
+	GObjectClass *object_class;
+	EFilterRuleClass *filter_rule_class;
+
+	g_type_class_add_private (class, sizeof (EMVFolderRulePrivate));
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->finalize = vfolder_rule_finalize;
+
+	filter_rule_class = E_FILTER_RULE_CLASS (class);
+	filter_rule_class->validate = validate;
+	filter_rule_class->eq = vfolder_eq;
+	filter_rule_class->xml_encode = xml_encode;
+	filter_rule_class->xml_decode = xml_decode;
+	filter_rule_class->copy = rule_copy;
+	filter_rule_class->get_widget = get_widget;
+}
+
+static void
+em_vfolder_rule_init (EMVFolderRule *rule)
+{
+	rule->priv = EM_VFOLDER_RULE_GET_PRIVATE (rule);
+	rule->priv->with = EM_VFOLDER_RULE_WITH_SPECIFIC;
+	rule->priv->autoupdate = TRUE;
+	/* it's using pointers from priv::sources, and those
+	 * included has include_subfolders set to true */
+	rule->priv->include_subfolders = g_hash_table_new (g_direct_hash, g_direct_equal);
+
+	rule->rule.source = g_strdup ("incoming");
+}
+
+EFilterRule *
+em_vfolder_rule_new (void)
+{
+	return g_object_new (
+		EM_TYPE_VFOLDER_RULE, NULL);
+}
+
+void
+em_vfolder_rule_add_source (EMVFolderRule *rule,
+                            const gchar *uri)
+{
+	g_return_if_fail (EM_IS_VFOLDER_RULE (rule));
+	g_return_if_fail (uri);
+
+	g_queue_push_tail (&rule->priv->sources, g_strdup (uri));
+
+	e_filter_rule_emit_changed (E_FILTER_RULE (rule));
+}
+
+const gchar *
+em_vfolder_rule_find_source (EMVFolderRule *rule,
+                             const gchar *uri)
+{
+	GList *link;
+
+	g_return_val_if_fail (EM_IS_VFOLDER_RULE (rule), NULL);
+
+	/* only does a simple string or address comparison, should
+	 * probably do a decoded url comparison */
+	link = g_queue_find_custom (
+		&rule->priv->sources, uri, (GCompareFunc) strcmp);
+
+	return (link != NULL) ? link->data : NULL;
+}
+
+void
+em_vfolder_rule_remove_source (EMVFolderRule *rule,
+                               const gchar *uri)
+{
+	gchar *found;
+
+	g_return_if_fail (EM_IS_VFOLDER_RULE (rule));
+
+	found =(gchar *) em_vfolder_rule_find_source (rule, uri);
+	if (found != NULL) {
+		g_queue_remove (&rule->priv->sources, found);
+		g_hash_table_remove (rule->priv->include_subfolders, found);
+		g_free (found);
+		e_filter_rule_emit_changed (E_FILTER_RULE (rule));
+	}
+}
+
+const gchar *
+em_vfolder_rule_next_source (EMVFolderRule *rule,
+                             const gchar *last)
+{
+	GList *link;
+
+	if (last == NULL) {
+		link = g_queue_peek_head_link (&rule->priv->sources);
+	} else {
+		link = g_queue_find (&rule->priv->sources, last);
+		if (link == NULL)
+			link = g_queue_peek_head_link (&rule->priv->sources);
+		else
+			link = g_list_next (link);
+	}
+
+	return (link != NULL) ? link->data : NULL;
+}
+
+GQueue *
+em_vfolder_rule_get_sources (EMVFolderRule *rule)
+{
+	g_return_val_if_fail (rule != NULL, NULL);
+
+	return &rule->priv->sources;
+}
+
+static gboolean
+check_queue_has_key (gpointer key,
+                     gpointer value,
+                     gpointer user_data)
+{
+	EMVFolderRule *rule = user_data;
+
+	g_return_val_if_fail (rule != NULL, FALSE);
+
+	return g_queue_find (&rule->priv->sources, key) == NULL;
+}
+
+void
+em_vfolder_rule_sources_changed (EMVFolderRule *rule)
+{
+	g_return_if_fail (rule != NULL);
+
+	g_hash_table_foreach_remove (rule->priv->include_subfolders,
+		check_queue_has_key, rule);
+}
+
+gboolean
+em_vfolder_rule_source_get_include_subfolders (EMVFolderRule *rule,
+                                               const gchar *source)
+{
+	g_return_val_if_fail (rule != NULL, FALSE);
+	g_return_val_if_fail (source != NULL, FALSE);
+
+	source = em_vfolder_rule_find_source (rule, source);
+
+	return source && g_hash_table_lookup (rule->priv->include_subfolders, source);
+}
+
+void
+em_vfolder_rule_source_set_include_subfolders (EMVFolderRule *rule,
+                                               const gchar *source,
+                                               gboolean include_subfolders)
+{
+	g_return_if_fail (rule != NULL);
+	g_return_if_fail (source != NULL);
+
+	source = em_vfolder_rule_find_source (rule, source);
+	g_return_if_fail (source != NULL);
+
+	if (include_subfolders)
+		g_hash_table_insert (rule->priv->include_subfolders, (gpointer) source, GINT_TO_POINTER (1));
+	else
+		g_hash_table_remove (rule->priv->include_subfolders, (gpointer) source);
+}
+
+void
+em_vfolder_rule_set_with (EMVFolderRule *rule,
+                          em_vfolder_rule_with_t with)
+{
+	g_return_if_fail (rule != NULL);
+
+	rule->priv->with = with;
+}
+
+em_vfolder_rule_with_t
+em_vfolder_rule_get_with (EMVFolderRule *rule)
+{
+	g_return_val_if_fail (rule != NULL, FALSE);
+
+	return rule->priv->with;
+}
+
+void
+em_vfolder_rule_set_autoupdate (EMVFolderRule *rule,
+                                gboolean autoupdate)
+{
+	g_return_if_fail (rule != NULL);
+
+	rule->priv->autoupdate = autoupdate;
+}
+
+gboolean
+em_vfolder_rule_get_autoupdate (EMVFolderRule *rule)
+{
+	g_return_val_if_fail (rule != NULL, EM_VFOLDER_RULE_WITH_SPECIFIC);
+
+	return rule->priv->autoupdate;
+}
+
+static gint
+validate (EFilterRule *fr,
+          EAlert **alert)
+{
+	g_return_val_if_fail (fr != NULL, 0);
+	g_warn_if_fail (alert == NULL || *alert == NULL);
+
+	if (!fr->name || !*fr->name) {
+		if (alert)
+			*alert = e_alert_new ("mail:no-name-vfolder", NULL);
+		return 0;
+	}
+
+	/* We have to have at least one source set in the "specific" case.
+	 * Do not translate this string! */
+	if (((EMVFolderRule *) fr)->priv->with == EM_VFOLDER_RULE_WITH_SPECIFIC &&
+		g_queue_is_empty (&((EMVFolderRule *) fr)->priv->sources)) {
+		if (alert)
+			*alert = e_alert_new ("mail:vfolder-no-source", NULL);
+		return 0;
+	}
+
+	return E_FILTER_RULE_CLASS (em_vfolder_rule_parent_class)->validate (fr, alert);
+}
+
+static gint
+queue_eq (GQueue *queue_a,
+          GQueue *queue_b)
+{
+	GList *link_a;
+	GList *link_b;
+	gint truth = TRUE;
+
+	link_a = g_queue_peek_head_link (queue_a);
+	link_b = g_queue_peek_head_link (queue_b);
+
+	while (truth && link_a != NULL && link_b != NULL) {
+		gchar *uri_a = link_a->data;
+		gchar *uri_b = link_b->data;
+
+		truth = (strcmp (uri_a, uri_b)== 0);
+
+		link_a = g_list_next (link_a);
+		link_b = g_list_next (link_b);
+	}
+
+	return truth && link_a == NULL && link_b == NULL;
+}
+
+static gint
+vfolder_eq (EFilterRule *fr,
+            EFilterRule *cm)
+{
+	return E_FILTER_RULE_CLASS (em_vfolder_rule_parent_class)->eq (fr, cm)
+		&& queue_eq (
+			&((EMVFolderRule *) fr)->priv->sources,
+			&((EMVFolderRule *) cm)->priv->sources);
+}
+
+static xmlNodePtr
+xml_encode (EFilterRule *fr)
+{
+	EMVFolderRule *vr =(EMVFolderRule *) fr;
+	xmlNodePtr node, set, work;
+	GList *head, *link;
+
+	node = E_FILTER_RULE_CLASS (em_vfolder_rule_parent_class)->xml_encode (fr);
+	g_return_val_if_fail (node != NULL, NULL);
+	g_return_val_if_fail (vr->priv->with < G_N_ELEMENTS (with_names), NULL);
+
+	set = xmlNewNode (NULL, (const guchar *)"sources");
+	xmlAddChild (node, set);
+	xmlSetProp (set, (const guchar *)"with", (guchar *) with_names[vr->priv->with]);
+	xmlSetProp (set, (const guchar *)"autoupdate", (guchar *) (vr->priv->autoupdate ? "true" : "false"));
+
+	head = g_queue_peek_head_link (&vr->priv->sources);
+	for (link = head; link != NULL; link = g_list_next (link)) {
+		const gchar *uri = link->data;
+
+		work = xmlNewNode (NULL, (const guchar *) "folder");
+		xmlSetProp (work, (const guchar *) "uri", (guchar *) uri);
+		xmlSetProp (work, (const guchar *) "include-subfolders", (guchar *)
+			(em_vfolder_rule_source_get_include_subfolders (vr, uri) ? "true" : "false"));
+		xmlAddChild (set, work);
+	}
+
+	return node;
+}
+
+static void
+set_with (EMVFolderRule *vr,
+          const gchar *name)
+{
+	gint i;
+
+	for (i = 0; i < G_N_ELEMENTS (with_names); i++) {
+		if (!strcmp (name, with_names[i])) {
+			vr->priv->with = i;
+			return;
+		}
+	}
+
+	vr->priv->with = 0;
+}
+
+static gint
+xml_decode (EFilterRule *fr,
+            xmlNodePtr node,
+            ERuleContext *f)
+{
+	xmlNodePtr set, work;
+	gint result;
+	EMVFolderRule *vr =(EMVFolderRule *) fr;
+	gchar *tmp;
+
+	result = E_FILTER_RULE_CLASS (em_vfolder_rule_parent_class)->
+		xml_decode (fr, node, f);
+	if (result != 0)
+		return result;
+
+	/* handle old format file, vfolder source is in filterrule */
+	if (strcmp (fr->source, "incoming")!= 0) {
+		set_with (vr, fr->source);
+		g_free (fr->source);
+		fr->source = g_strdup ("incoming");
+	}
+
+	set = node->children;
+	while (set) {
+		if (!strcmp ((gchar *) set->name, "sources")) {
+			tmp = (gchar *) xmlGetProp (set, (const guchar *)"with");
+			if (tmp) {
+				set_with (vr, tmp);
+				xmlFree (tmp);
+			}
+			tmp = (gchar *) xmlGetProp (set, (const guchar *) "autoupdate");
+			if (tmp) {
+				vr->priv->autoupdate = g_str_equal (tmp, "true");
+				xmlFree (tmp);
+			}
+			work = set->children;
+			while (work) {
+				if (!strcmp ((gchar *) work->name, "folder")) {
+					tmp = (gchar *) xmlGetProp (work, (const guchar *)"uri");
+					if (tmp) {
+						gchar *include_subfolders;
+
+						g_queue_push_tail (&vr->priv->sources, g_strdup (tmp));
+
+						include_subfolders = (gchar *) xmlGetProp (work, (const guchar *) "include-subfolders");
+						if (include_subfolders) {
+							em_vfolder_rule_source_set_include_subfolders (
+								vr,
+								tmp, g_str_equal (include_subfolders, "true"));
+							xmlFree (include_subfolders);
+						}
+
+						xmlFree (tmp);
+					}
+				}
+				work = work->next;
+			}
+		}
+		set = set->next;
+	}
+	return 0;
+}
+
+static void
+rule_copy (EFilterRule *dest,
+           EFilterRule *src)
+{
+	EMVFolderRule *vdest, *vsrc;
+	GList *head, *link;
+	gchar *uri;
+
+	vdest =(EMVFolderRule *) dest;
+	vsrc =(EMVFolderRule *) src;
+
+	while ((uri = g_queue_pop_head (&vdest->priv->sources)) != NULL)
+		g_free (uri);
+
+	em_vfolder_rule_sources_changed (vdest);
+
+	head = g_queue_peek_head_link (&vsrc->priv->sources);
+	for (link = head; link != NULL; link = g_list_next (link)) {
+		const gchar *uri = link->data;
+		g_queue_push_tail (&vdest->priv->sources, g_strdup (uri));
+
+		em_vfolder_rule_source_set_include_subfolders (
+			vdest, uri,
+			em_vfolder_rule_source_get_include_subfolders (vsrc, uri));
+	}
+
+	vdest->priv->with = vsrc->priv->with;
+	vdest->priv->autoupdate = vsrc->priv->autoupdate;
+
+	E_FILTER_RULE_CLASS (em_vfolder_rule_parent_class)->copy (dest, src);
+}
+
+static GtkWidget *
+get_widget (EFilterRule *fr,
+            ERuleContext *rc)
+{
+	GtkWidget *widget;
+
+	widget = E_FILTER_RULE_CLASS (em_vfolder_rule_parent_class)->
+		get_widget (fr, rc);
+
+	return widget;
+}
diff --git a/libemail-engine/em-vfolder-rule.h b/libemail-engine/em-vfolder-rule.h
new file mode 100644
index 0000000..312ae17
--- /dev/null
+++ b/libemail-engine/em-vfolder-rule.h
@@ -0,0 +1,102 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		NotZed <notzed ximian com>
+ *      Jeffrey Stedfast <fejj ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifndef EM_VFOLDER_RULE_H
+#define EM_VFOLDER_RULE_H
+
+#include <e-util/e-util.h>
+
+/* Standard GObject macros */
+#define EM_TYPE_VFOLDER_RULE \
+	(em_vfolder_rule_get_type ())
+#define EM_VFOLDER_RULE(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), EM_TYPE_VFOLDER_RULE, EMVFolderRule))
+#define EM_VFOLDER_RULE_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), EM_TYPE_VFOLDER_RULE, EMVFolderRuleClass))
+#define EM_IS_VFOLDER_RULE(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), EM_TYPE_VFOLDER_RULE))
+#define EM_IS_VFOLDER_RULE_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), EM_TYPE_VFOLDER_RULE))
+#define EM_VFOLDER_RULE_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), EM_TYPE_VFOLDER_RULE, EMVFolderRuleClass))
+
+G_BEGIN_DECLS
+
+/* perhaps should be bits? */
+enum _em_vfolder_rule_with_t {
+	EM_VFOLDER_RULE_WITH_SPECIFIC,
+	EM_VFOLDER_RULE_WITH_LOCAL_REMOTE_ACTIVE,
+	EM_VFOLDER_RULE_WITH_REMOTE_ACTIVE,
+	EM_VFOLDER_RULE_WITH_LOCAL
+};
+
+typedef struct _EMVFolderRule EMVFolderRule;
+typedef struct _EMVFolderRuleClass EMVFolderRuleClass;
+typedef struct _EMVFolderRulePrivate EMVFolderRulePrivate;
+
+typedef enum _em_vfolder_rule_with_t em_vfolder_rule_with_t;
+
+struct _EMVFolderRule {
+	EFilterRule rule;
+	EMVFolderRulePrivate *priv;
+};
+
+struct _EMVFolderRuleClass {
+	EFilterRuleClass parent_class;
+};
+
+GType		em_vfolder_rule_get_type	(void);
+EFilterRule *	em_vfolder_rule_new		(void);
+void		em_vfolder_rule_add_source	(EMVFolderRule *rule,
+						 const gchar *uri);
+void		em_vfolder_rule_remove_source	(EMVFolderRule *rule,
+						 const gchar *uri);
+const gchar *	em_vfolder_rule_find_source	(EMVFolderRule *rule,
+						 const gchar *uri);
+const gchar *	em_vfolder_rule_next_source	(EMVFolderRule *rule,
+						 const gchar *last);
+GQueue *	em_vfolder_rule_get_sources	(EMVFolderRule *rule);
+void		em_vfolder_rule_sources_changed	(EMVFolderRule *rule);
+gboolean	em_vfolder_rule_source_get_include_subfolders
+						(EMVFolderRule *rule,
+						 const gchar *source);
+void		em_vfolder_rule_source_set_include_subfolders
+						(EMVFolderRule *rule,
+						 const gchar *source,
+						 gboolean include_subfolders);
+void		em_vfolder_rule_set_with	(EMVFolderRule *rule,
+						 em_vfolder_rule_with_t with);
+em_vfolder_rule_with_t
+		em_vfolder_rule_get_with	(EMVFolderRule *rule);
+void		em_vfolder_rule_set_autoupdate	(EMVFolderRule *rule,
+						 gboolean autoupdate);
+gboolean	em_vfolder_rule_get_autoupdate	(EMVFolderRule *rule);
+
+G_END_DECLS
+
+#endif /* EM_VFOLDER_RULE_H */
diff --git a/libemail-engine/libemail-engine.pc.in b/libemail-engine/libemail-engine.pc.in
index 2457e01..ea5ffab 100644
--- a/libemail-engine/libemail-engine.pc.in
+++ b/libemail-engine/libemail-engine.pc.in
@@ -11,6 +11,6 @@ privincludedir= privincludedir@
 Name: libemail-engine
 Description: Client library for evolution mail
 Version: @VERSION@
-Requires: libemail-utils
+Requires: camel-1.2 libedataserver-1.2 libebackend-1.2 gio-2.0
 Libs: -L${privlibdir} -lemail-engine
 Cflags: -I${privincludedir}
diff --git a/libemail-engine/mail-folder-cache.c b/libemail-engine/mail-folder-cache.c
index 4d82557..35e6208 100644
--- a/libemail-engine/mail-folder-cache.c
+++ b/libemail-engine/mail-folder-cache.c
@@ -40,7 +40,7 @@
 
 #include <libedataserver/libedataserver.h>
 
-#include <libemail-utils/mail-mt.h>
+#include <libemail-engine/mail-mt.h>
 
 #include "mail-folder-cache.h"
 #include "e-mail-utils.h"
diff --git a/libemail-utils/mail-mt.c b/libemail-engine/mail-mt.c
similarity index 100%
rename from libemail-utils/mail-mt.c
rename to libemail-engine/mail-mt.c
diff --git a/libemail-engine/mail-mt.h b/libemail-engine/mail-mt.h
new file mode 100644
index 0000000..03cea96
--- /dev/null
+++ b/libemail-engine/mail-mt.h
@@ -0,0 +1,125 @@
+/*
+ * This program 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) version 3.
+ *
+ * This program is distributed in the hope that 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ *		Michael Zucchi <notzed ximian com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifndef _MAIL_MT
+#define _MAIL_MT
+
+#include <camel/camel.h>
+
+#include <e-util/e-util.h>
+
+typedef struct _MailMsg MailMsg;
+typedef struct _MailMsgInfo MailMsgInfo;
+
+typedef gchar *	(*MailMsgDescFunc)		(MailMsg *msg);
+typedef void	(*MailMsgExecFunc)		(MailMsg *msg,
+						 GCancellable *cancellable,
+						 GError **error);
+typedef void	(*MailMsgDoneFunc)		(MailMsg *msg);
+typedef void	(*MailMsgFreeFunc)		(MailMsg *msg);
+typedef void	(*MailMsgDispatchFunc)		(gpointer msg);
+
+typedef void    (*MailMsgCreateActivityFunc)	(GCancellable *cancellable);
+typedef void    (*MailMsgSubmitActivityFunc)	(GCancellable *cancellable);
+typedef void    (*MailMsgFreeActivityFunc)	(GCancellable *cancellable);
+typedef void    (*MailMsgCompleteActivityFunc)	(GCancellable *cancellable);
+typedef void    (*MailMsgCancelActivityFunc)	(GCancellable *cancellable);
+typedef void    (*MailMsgAlertErrorFunc)	(GCancellable *cancellable,
+						 const gchar *what,
+						 const gchar *message);
+typedef EAlertSink *
+		(*MailMsgGetAlertSinkFunc)	(void);
+
+struct _MailMsg {
+	MailMsgInfo *info;
+	volatile gint ref_count;
+	guint seq;			/* seq number for synchronisation */
+	gint priority;			/* priority (default = 0) */
+	GCancellable *cancellable;
+	GError *error;			/* up to the caller to use this */
+};
+
+struct _MailMsgInfo {
+	gsize size;
+	MailMsgDescFunc desc;
+	MailMsgExecFunc exec;
+	MailMsgDoneFunc done;
+	MailMsgFreeFunc free;
+};
+
+/* Just till we move this out to EDS */
+EAlertSink *	mail_msg_get_alert_sink (void);
+
+/* setup ports */
+void mail_msg_init (void);
+void mail_msg_register_activities (MailMsgCreateActivityFunc,
+				   MailMsgSubmitActivityFunc,
+				   MailMsgFreeActivityFunc,
+				   MailMsgCompleteActivityFunc,
+				   MailMsgCancelActivityFunc,
+				   MailMsgAlertErrorFunc,
+				   MailMsgGetAlertSinkFunc);
+
+gboolean mail_in_main_thread (void);
+
+/* allocate a new message */
+gpointer mail_msg_new (MailMsgInfo *info);
+gpointer mail_msg_ref (gpointer msg);
+void mail_msg_unref (gpointer msg);
+void mail_msg_check_error (gpointer msg);
+void mail_msg_cancel (guint msgid);
+gboolean mail_msg_active (void);
+
+/* dispatch a message */
+void mail_msg_main_loop_push (gpointer msg);
+void mail_msg_unordered_push (gpointer msg);
+void mail_msg_fast_ordered_push (gpointer msg);
+void mail_msg_slow_ordered_push (gpointer msg);
+
+/* To implement the stop button */
+GHook * mail_cancel_hook_add (GHookFunc func, gpointer data);
+void mail_cancel_hook_remove (GHook *hook);
+void mail_cancel_all (void);
+
+/* request a string/password */
+gchar *mail_get_password (CamelService *service, const gchar *prompt,
+			 gboolean secret, gboolean *cache);
+
+void mail_mt_set_backend (gchar *backend);
+
+/* Call a function in the GUI thread, wait for it to return, type is
+ * the marshaller to use.  FIXME This thing is horrible, please put
+ * it out of its misery. */
+typedef enum {
+	MAIL_CALL_p_p,
+	MAIL_CALL_p_pp,
+	MAIL_CALL_p_ppp,
+	MAIL_CALL_p_pppp,
+	MAIL_CALL_p_ppppp,
+	MAIL_CALL_p_ppippp
+} mail_call_t;
+
+typedef gpointer (*MailMainFunc)();
+
+gpointer mail_call_main (mail_call_t type, MailMainFunc func, ...);
+
+#endif /* _MAIL_MT */
diff --git a/libemail-engine/mail-ops.c b/libemail-engine/mail-ops.c
index e2bcaa6..24fc772 100644
--- a/libemail-engine/mail-ops.c
+++ b/libemail-engine/mail-ops.c
@@ -36,7 +36,7 @@
 
 #include <libedataserver/libedataserver.h>
 
-#include <libemail-utils/mail-mt.h>
+#include <libemail-engine/mail-mt.h>
 
 #include "e-mail-utils.h"
 #include "mail-ops.h"
diff --git a/libemail-engine/mail-ops.h b/libemail-engine/mail-ops.h
index 42f3118..dd115ff 100644
--- a/libemail-engine/mail-ops.h
+++ b/libemail-engine/mail-ops.h
@@ -27,8 +27,8 @@
 G_BEGIN_DECLS
 
 #include <camel/camel.h>
-#include <libemail-utils/mail-mt.h>
 #include <libemail-engine/e-mail-session.h>
+#include <libemail-engine/mail-mt.h>
 
 void		mail_transfer_messages		(EMailSession *session,
 						 CamelFolder *source,
diff --git a/libemail-engine/mail-vfolder.c b/libemail-engine/mail-vfolder.c
index eef4d38..b23ce0c 100644
--- a/libemail-engine/mail-vfolder.c
+++ b/libemail-engine/mail-vfolder.c
@@ -24,24 +24,21 @@
 #include <config.h>
 #endif
 
-#include <string.h>
+#include "mail-vfolder.h"
 
+#include <string.h>
 #include <glib/gi18n.h>
 
-#include "libevolution-utils/e-alert-dialog.h"
-
-#include "libemail-utils/mail-mt.h"
-#include "libemail-engine/mail-folder-cache.h"
 #include "libemail-engine/e-mail-folder-utils.h"
 #include "libemail-engine/e-mail-session.h"
 #include "libemail-engine/e-mail-utils.h"
+#include "libemail-engine/em-vfolder-context.h"
+#include "libemail-engine/em-vfolder-rule.h"
+#include "libemail-engine/mail-folder-cache.h"
+#include "libemail-engine/mail-mt.h"
 #include "libemail-engine/mail-ops.h"
 #include "libemail-engine/mail-tools.h"
 
-#include <libemail-utils/em-vfolder-context.h>
-#include <libemail-utils/em-vfolder-rule.h>
-#include "mail-vfolder.h"
-
 #define d(x)  /* (printf("%s:%s: ",  G_STRLOC, G_STRFUNC), (x))*/
 
 /* Note: Once we completely move mail to EDS, this context wont be available for UI. 
diff --git a/libemail-engine/mail-vfolder.h b/libemail-engine/mail-vfolder.h
index eafd6ba..8732526 100644
--- a/libemail-engine/mail-vfolder.h
+++ b/libemail-engine/mail-vfolder.h
@@ -24,10 +24,9 @@
 
 #include <camel/camel.h>
 
-#include <filter/e-filter-part.h>
-#include <filter/e-filter-rule.h>
+#include <e-util/e-util.h>
 #include <libemail-engine/e-mail-session.h>
-#include <libemail-utils/em-vfolder-rule.h>
+#include <libemail-engine/em-vfolder-rule.h>
 
 void		vfolder_load_storage		(EMailSession *session);
 
diff --git a/mail/Makefile.am b/mail/Makefile.am
index 1995e64..52b26cc 100644
--- a/mail/Makefile.am
+++ b/mail/Makefile.am
@@ -6,8 +6,6 @@ mailincludedir = $(privincludedir)/mail
 
 libevolution_mail_la_CPPFLAGS =				\
 	$(AM_CPPFLAGS)					\
-	-I$(top_srcdir)/widgets				\
-	-I$(top_srcdir)/widgets/misc			\
 	-I$(top_srcdir)					\
 	-I$(top_srcdir)/em-format			\
 	-I$(top_srcdir)/mail				\
@@ -17,13 +15,6 @@ libevolution_mail_la_CPPFLAGS =				\
 	-I$(top_srcdir)/shell				\
 	-I$(top_srcdir)/smime/lib			\
 	-I$(top_srcdir)/smime/gui			\
-	$(EVOLUTION_DATA_SERVER_CFLAGS)			\
-	$(GNOME_PLATFORM_CFLAGS)			\
-	$(CERT_UI_CFLAGS)				\
-	$(CANBERRA_CFLAGS)				\
-	$(CLUTTER_CFLAGS)				\
-	$(GTKHTML_CFLAGS)                               \
-	$(LIBSOUP_CFLAGS)				\
 	-DEVOLUTION_DATADIR=\""$(datadir)"\"		\
 	-DEVOLUTION_PRIVDATADIR=\""$(privdatadir)"\"	\
 	-DEVOLUTION_ETSPECDIR=\""$(etspecdir)"\"	\
@@ -35,7 +26,14 @@ libevolution_mail_la_CPPFLAGS =				\
 	-DEVOLUTION_UIDIR=\""$(uidir)"\"		\
 	-DCAMEL_PROVIDERDIR=\""$(camel_providerdir)"\"	\
 	-DPREFIX=\""$(prefix)"\"			\
-	-DG_LOG_DOMAIN=\"evolution-mail\"
+	-DG_LOG_DOMAIN=\"evolution-mail\"		\
+	$(EVOLUTION_DATA_SERVER_CFLAGS)			\
+	$(GNOME_PLATFORM_CFLAGS)			\
+	$(CERT_UI_CFLAGS)				\
+	$(CANBERRA_CFLAGS)				\
+	$(CHAMPLAIN_CFLAGS)				\
+	$(GTKHTML_CFLAGS)				\
+	$(LIBSOUP_CFLAGS)
 
 mailinclude_HEADERS =					\
 	e-http-request.h				\
@@ -193,28 +191,19 @@ SMIME_LIBS =						\
 endif
 
 libevolution_mail_la_LIBADD =				\
-	$(top_builddir)/libemail-utils/libemail-utils.la		\
 	$(top_builddir)/libemail-engine/libemail-engine.la		\
 	$(top_builddir)/e-util/libeutil.la		\
 	$(top_builddir)/shell/libeshell.la		\
 	$(top_builddir)/composer/libcomposer.la		\
-	$(top_builddir)/widgets/table/libetable.la	\
-	$(top_builddir)/widgets/text/libetext.la	\
-	$(top_builddir)/widgets/menus/libmenus.la	\
-	$(top_builddir)/widgets/misc/libemiscwidgets.la	\
-	$(top_builddir)/widgets/menus/libmenus.la	\
 	$(top_builddir)/addressbook/gui/contact-editor/libecontacteditor.la \
 	$(top_builddir)/addressbook/gui/contact-list-editor/libecontactlisteditor.la \
 	$(top_builddir)/em-format/libemformat.la	\
-	$(top_builddir)/filter/libfilter.la		\
-	$(top_builddir)/libevolution-utils/libevolution-utils.la \
 	$(EVOLUTION_DATA_SERVER_LIBS)			\
 	$(GNOME_PLATFORM_LIBS)				\
 	$(CERT_UI_LIBS)					\
 	$(CANBERRA_LIBS)				\
-	$(CLUTTER_LIBS)					\
+	$(CHAMPLAIN_LIBS)				\
 	$(GTKHTML_LIBS)					\
-	$(E_WIDGETS_LIBS)				\
 	$(SMIME_LIBS)					\
 	$(LIBSOUP_LIBS)					\
 	-lresolv
diff --git a/mail/e-mail-account-store.c b/mail/e-mail-account-store.c
index 8d8f867..a2a639d 100644
--- a/mail/e-mail-account-store.c
+++ b/mail/e-mail-account-store.c
@@ -24,9 +24,6 @@
 
 #include <libebackend/libebackend.h>
 
-#include <e-util/e-marshal.h>
-#include <libevolution-utils/e-alert-dialog.h>
-
 #include <libemail-engine/mail-ops.h>
 
 #include <mail/mail-vfolder-ui.h>
diff --git a/mail/e-mail-backend.c b/mail/e-mail-backend.c
index 4fb3291..0940a78 100644
--- a/mail/e-mail-backend.c
+++ b/mail/e-mail-backend.c
@@ -34,9 +34,6 @@
 
 #include <shell/e-shell.h>
 
-#include <libevolution-utils/e-alert-dialog.h>
-#include <libevolution-utils/e-alert-sink.h>
-
 #include <libemail-engine/e-mail-folder-utils.h>
 #include <libemail-engine/e-mail-session.h>
 #include <libemail-engine/e-mail-store-utils.h>
diff --git a/mail/e-mail-browser.c b/mail/e-mail-browser.c
index d0aa556..495bf64 100644
--- a/mail/e-mail-browser.c
+++ b/mail/e-mail-browser.c
@@ -28,13 +28,9 @@
 #include <string.h>
 #include <glib/gi18n.h>
 
-#include "e-util/e-util.h"
-#include "e-util/e-plugin-ui.h"
 #include "shell/e-shell.h"
 #include "shell/e-shell-utils.h"
 #include "shell/e-shell-settings.h"
-#include "widgets/misc/e-popup-action.h"
-#include "widgets/misc/e-preview-pane.h"
 
 #include "mail/e-mail-reader.h"
 #include "mail/e-mail-reader-utils.h"
diff --git a/mail/e-mail-browser.h b/mail/e-mail-browser.h
index 849daeb..bd636cd 100644
--- a/mail/e-mail-browser.h
+++ b/mail/e-mail-browser.h
@@ -22,8 +22,8 @@
 #ifndef E_MAIL_BROWSER_H
 #define E_MAIL_BROWSER_H
 
+#include <e-util/e-util.h>
 #include <mail/e-mail-backend.h>
-#include <misc/e-focus-tracker.h>
 #include <mail/e-mail-display.h>
 
 /* Standard GObject macros */
diff --git a/mail/e-mail-config-activity-page.c b/mail/e-mail-config-activity-page.c
index 4328555..790d1f9 100644
--- a/mail/e-mail-config-activity-page.c
+++ b/mail/e-mail-config-activity-page.c
@@ -20,11 +20,6 @@
 
 #include <camel/camel.h>
 
-#include <libevolution-utils/e-alert-sink.h>
-#include <libevolution-utils/e-alert-dialog.h>
-#include <misc/e-activity-bar.h>
-#include <misc/e-alert-bar.h>
-
 #define E_MAIL_CONFIG_ACTIVITY_PAGE_GET_PRIVATE(obj) \
 	(G_TYPE_INSTANCE_GET_PRIVATE \
 	((obj), E_TYPE_MAIL_CONFIG_ACTIVITY_PAGE, EMailConfigActivityPagePrivate))
diff --git a/mail/e-mail-config-activity-page.h b/mail/e-mail-config-activity-page.h
index 62defe2..43cc094 100644
--- a/mail/e-mail-config-activity-page.h
+++ b/mail/e-mail-config-activity-page.h
@@ -25,7 +25,7 @@
 #define E_MAIL_CONFIG_ACTIVITY_PAGE_H
 
 #include <gtk/gtk.h>
-#include <e-util/e-activity.h>
+#include <e-util/e-util.h>
 
 /* Standard GObject macros */
 #define E_TYPE_MAIL_CONFIG_ACTIVITY_PAGE \
diff --git a/mail/e-mail-config-assistant.c b/mail/e-mail-config-assistant.c
index 7caa43f..92bd971 100644
--- a/mail/e-mail-config-assistant.c
+++ b/mail/e-mail-config-assistant.c
@@ -23,8 +23,6 @@
 
 #include <libebackend/libebackend.h>
 
-#include <libevolution-utils/e-alert-sink.h>
-
 #include <mail/e-mail-config-confirm-page.h>
 #include <mail/e-mail-config-identity-page.h>
 #include <mail/e-mail-config-lookup-page.h>
diff --git a/mail/e-mail-config-auth-check.c b/mail/e-mail-config-auth-check.c
index 8abfa6f..d60e95f 100644
--- a/mail/e-mail-config-auth-check.c
+++ b/mail/e-mail-config-auth-check.c
@@ -19,10 +19,9 @@
 #include <config.h>
 #include <glib/gi18n-lib.h>
 
-#include <libevolution-utils/e-alert.h>
-#include <e-util/e-mktemp.h>
-#include <misc/e-auth-combo-box.h>
-#include <mail/e-mail-config-service-page.h>
+#include "e-util/e-util.h"
+#include "mail/e-mail-config-service-page.h"
+
 #include "e-mail-ui-session.h"
 
 #include "e-mail-config-auth-check.h"
diff --git a/mail/e-mail-config-identity-page.c b/mail/e-mail-config-identity-page.c
index 40b18f4..5db923d 100644
--- a/mail/e-mail-config-identity-page.c
+++ b/mail/e-mail-config-identity-page.c
@@ -23,9 +23,7 @@
 
 #include <libebackend/libebackend.h>
 
-#include <e-util/e-marshal.h>
-#include <misc/e-mail-signature-combo-box.h>
-#include <misc/e-mail-signature-editor.h>
+#include "e-util/e-util.h"
 
 #define E_MAIL_CONFIG_IDENTITY_PAGE_GET_PRIVATE(obj) \
 	(G_TYPE_INSTANCE_GET_PRIVATE \
diff --git a/mail/e-mail-config-provider-page.h b/mail/e-mail-config-provider-page.h
index 1faf187..8cf21f3 100644
--- a/mail/e-mail-config-provider-page.h
+++ b/mail/e-mail-config-provider-page.h
@@ -25,7 +25,7 @@
 #include <gtk/gtk.h>
 #include <camel/camel.h>
 
-#include <e-util/e-activity.h>
+#include <e-util/e-util.h>
 #include <mail/e-mail-config-page.h>
 #include <mail/e-mail-config-activity-page.h>
 #include <mail/e-mail-config-service-backend.h>
diff --git a/mail/e-mail-config-window.c b/mail/e-mail-config-window.c
index 5062abc..218f751 100644
--- a/mail/e-mail-config-window.c
+++ b/mail/e-mail-config-window.c
@@ -21,12 +21,10 @@
 #include <config.h>
 #include <glib/gi18n-lib.h>
 
-#include <libevolution-utils/e-alert-dialog.h>
-#include <libevolution-utils/e-alert-sink.h>
-#include <misc/e-alert-bar.h>
+#include "e-util/e-util.h"
 
-#include <mail/e-mail-config-notebook.h>
-#include <mail/e-mail-config-sidebar.h>
+#include "mail/e-mail-config-notebook.h"
+#include "mail/e-mail-config-sidebar.h"
 
 #define E_MAIL_CONFIG_WINDOW_GET_PRIVATE(obj) \
 	(G_TYPE_INSTANCE_GET_PRIVATE \
diff --git a/mail/e-mail-display.c b/mail/e-mail-display.c
index 4f706ee..3181a90 100644
--- a/mail/e-mail-display.c
+++ b/mail/e-mail-display.c
@@ -24,31 +24,24 @@
 #endif
 
 #include "e-mail-display.h"
-#include "e-mail-display-popup-extension.h"
 
 #include <glib/gi18n.h>
 #include <gdk/gdk.h>
 
-#include <em-format/e-mail-part-utils.h>
-#include <em-format/e-mail-formatter-extension.h>
-#include <em-format/e-mail-extension-registry.h>
-#include <em-format/e-mail-part-attachment.h>
-#include <em-format/e-mail-formatter-print.h>
-
-#include "e-util/e-marshal.h"
-#include "e-util/e-util.h"
-#include "e-util/e-plugin-ui.h"
-#include "e-util/e-file-request.h"
-#include "e-util/e-stock-request.h"
-#include "mail/em-composer-utils.h"
-#include "mail/em-utils.h"
-#include "mail/e-mail-request.h"
-#include "mail/e-http-request.h"
-#include "widgets/misc/e-attachment-bar.h"
-#include "widgets/misc/e-attachment-button.h"
-
 #include <camel/camel.h>
 
+#include "em-format/e-mail-part-utils.h"
+#include "em-format/e-mail-formatter-extension.h"
+#include "em-format/e-mail-extension-registry.h"
+#include "em-format/e-mail-part-attachment.h"
+#include "em-format/e-mail-formatter-print.h"
+
+#include "e-http-request.h"
+#include "e-mail-display-popup-extension.h"
+#include "e-mail-request.h"
+#include "em-composer-utils.h"
+#include "em-utils.h"
+
 #define d(x)
 
 G_DEFINE_TYPE (
diff --git a/mail/e-mail-display.h b/mail/e-mail-display.h
index 99ad73a..7bf4aee 100644
--- a/mail/e-mail-display.h
+++ b/mail/e-mail-display.h
@@ -22,8 +22,7 @@
 #ifndef E_MAIL_DISPLAY_H
 #define E_MAIL_DISPLAY_H
 
-#include <misc/e-web-view.h>
-#include <misc/e-search-bar.h>
+#include <e-util/e-util.h>
 
 #include <em-format/e-mail-formatter.h>
 
diff --git a/mail/e-mail-folder-pane.c b/mail/e-mail-folder-pane.c
index e6c0e92..985f47b 100644
--- a/mail/e-mail-folder-pane.c
+++ b/mail/e-mail-folder-pane.c
@@ -28,24 +28,18 @@
 #include <string.h>
 #include <glib/gi18n.h>
 
-#include "e-util/e-util.h"
-#include "e-util/e-plugin-ui.h"
-
 #include "shell/e-shell.h"
 #include "shell/e-shell-utils.h"
 
-#include "widgets/misc/e-popup-action.h"
-#include "widgets/misc/e-preview-pane.h"
-
 #include "libemail-engine/e-mail-utils.h"
 #include "libemail-engine/mail-tools.h"
 
-#include "mail/e-mail-reader.h"
-#include "mail/e-mail-reader-utils.h"
-#include "mail/em-folder-tree-model.h"
-#include "mail/em-composer-utils.h"
-#include "mail/em-utils.h"
-#include "mail/message-list.h"
+#include "e-mail-reader.h"
+#include "e-mail-reader-utils.h"
+#include "em-folder-tree-model.h"
+#include "em-composer-utils.h"
+#include "em-utils.h"
+#include "message-list.h"
 
 #define E_MAIL_FOLDER_PANE_GET_PRIVATE(obj) \
 	(G_TYPE_INSTANCE_GET_PRIVATE \
diff --git a/mail/e-mail-migrate.c b/mail/e-mail-migrate.c
index 603bf53..24335c9 100644
--- a/mail/e-mail-migrate.c
+++ b/mail/e-mail-migrate.c
@@ -46,17 +46,10 @@
 #include <libxml/parser.h>
 #include <libxml/xmlmemory.h>
 
-#include <shell/e-shell.h>
-#include <shell/e-shell-migrate.h>
+#include "shell/e-shell.h"
+#include "shell/e-shell-migrate.h"
 
-#include <e-util/e-util.h>
-#include <libevolution-utils/e-xml-utils.h>
-
-#include <libevolution-utils/e-alert-dialog.h>
-#include <e-util/e-util-private.h>
-#include <e-util/e-plugin.h>
-
-#include <libemail-engine/e-mail-folder-utils.h>
+#include "libemail-engine/e-mail-folder-utils.h"
 
 #include "e-mail-backend.h"
 #include "em-utils.h"
diff --git a/mail/e-mail-notebook-view.h b/mail/e-mail-notebook-view.h
index 04b0bd4..4e1fff6 100644
--- a/mail/e-mail-notebook-view.h
+++ b/mail/e-mail-notebook-view.h
@@ -25,7 +25,6 @@
 
 #include <mail/e-mail-view.h>
 #include <shell/e-shell-searchbar.h>
-#include <menus/gal-view-instance.h>
 
 /* Standard GObject macros */
 #define E_TYPE_MAIL_NOTEBOOK_VIEW \
diff --git a/mail/e-mail-paned-view.c b/mail/e-mail-paned-view.c
index 08bedb7..2fbf48a 100644
--- a/mail/e-mail-paned-view.c
+++ b/mail/e-mail-paned-view.c
@@ -24,22 +24,16 @@
 #include <config.h>
 #endif
 
-#include <glib/gi18n.h>
 #include "e-mail-paned-view.h"
 
-#include "e-util/e-util-private.h"
-#include "widgets/menus/gal-view-etable.h"
-#include "widgets/menus/gal-view-instance.h"
-#include "widgets/misc/e-paned.h"
-#include "widgets/misc/e-preview-pane.h"
-#include "widgets/misc/e-search-bar.h"
+#include <glib/gi18n.h>
 
-#include <shell/e-shell-window-actions.h>
+#include "shell/e-shell-window-actions.h"
 
-#include <libemail-engine/e-mail-folder-utils.h>
-#include <libemail-engine/e-mail-utils.h>
-#include <libemail-engine/mail-config.h>
-#include <libemail-engine/mail-ops.h>
+#include "libemail-engine/e-mail-folder-utils.h"
+#include "libemail-engine/e-mail-utils.h"
+#include "libemail-engine/mail-config.h"
+#include "libemail-engine/mail-ops.h"
 
 #include "em-utils.h"
 #include "message-list.h"
diff --git a/mail/e-mail-printer.c b/mail/e-mail-printer.c
index 30c8517..2f7e136 100644
--- a/mail/e-mail-printer.c
+++ b/mail/e-mail-printer.c
@@ -24,13 +24,12 @@
 #include <glib/gi18n.h>
 #include <gtk/gtk.h>
 
-#include <em-format/e-mail-formatter-print.h>
-#include <em-format/e-mail-part-utils.h>
+#include <webkit/webkitdom.h>
 
-#include <e-util/e-print.h>
-#include <e-util/e-marshal.h>
+#include "e-util/e-util.h"
 
-#include <webkit/webkitdom.h>
+#include "em-format/e-mail-formatter-print.h"
+#include "em-format/e-mail-part-utils.h"
 
 #include "e-mail-printer.h"
 #include "e-mail-display.h"
diff --git a/mail/e-mail-reader-utils.c b/mail/e-mail-reader-utils.c
index 9f027a6..cce0046 100644
--- a/mail/e-mail-reader-utils.c
+++ b/mail/e-mail-reader-utils.c
@@ -32,30 +32,27 @@
 #include <gtkhtml/gtkhtml.h>
 #include <camel/camel.h>
 
-#include "libevolution-utils/e-alert-dialog.h"
-#include "filter/e-filter-rule.h"
-#include "misc/e-web-view.h"
 #include "shell/e-shell-utils.h"
 
-#include <libemail-engine/e-mail-folder-utils.h>
-#include <libemail-engine/e-mail-utils.h>
-#include <libemail-engine/mail-ops.h>
-#include <libemail-engine/mail-tools.h>
+#include "libemail-engine/e-mail-folder-utils.h"
+#include "libemail-engine/e-mail-utils.h"
+#include "libemail-engine/mail-ops.h"
+#include "libemail-engine/mail-tools.h"
+
+#include "em-format/e-mail-parser.h"
+#include "em-format/e-mail-part-utils.h"
 
 #include "composer/e-composer-actions.h"
 
-#include "mail/e-mail-backend.h"
-#include "mail/e-mail-browser.h"
-#include "mail/e-mail-printer.h"
-#include "mail/e-mail-display.h"
-#include "mail/em-composer-utils.h"
-#include "mail/em-utils.h"
-#include "mail/mail-autofilter.h"
-#include "mail/mail-vfolder-ui.h"
-#include "mail/message-list.h"
-
-#include <em-format/e-mail-parser.h>
-#include <em-format/e-mail-part-utils.h>
+#include "e-mail-backend.h"
+#include "e-mail-browser.h"
+#include "e-mail-printer.h"
+#include "e-mail-display.h"
+#include "em-composer-utils.h"
+#include "em-utils.h"
+#include "mail-autofilter.h"
+#include "mail-vfolder-ui.h"
+#include "message-list.h"
 
 #define d(x)
 
diff --git a/mail/e-mail-reader.c b/mail/e-mail-reader.c
index a659404..c2500a5 100644
--- a/mail/e-mail-reader.c
+++ b/mail/e-mail-reader.c
@@ -32,36 +32,30 @@
 #include <X11/XF86keysym.h>
 #endif
 
-#include "e-util/e-charset.h"
-#include "e-util/e-util.h"
-#include "libevolution-utils/e-alert-dialog.h"
 #include "shell/e-shell-utils.h"
-#include "widgets/misc/e-popup-action.h"
-#include "widgets/misc/e-menu-tool-action.h"
 
-#include "libemail-utils/mail-mt.h"
-
-#include "libemail-engine/mail-ops.h"
-#include "libemail-engine/e-mail-utils.h"
 #include "libemail-engine/e-mail-enumtypes.h"
+#include "libemail-engine/e-mail-utils.h"
+#include "libemail-engine/mail-mt.h"
+#include "libemail-engine/mail-ops.h"
 
-#include "mail/e-mail-backend.h"
-#include "mail/e-mail-browser.h"
-#include "mail/e-mail-reader-utils.h"
-#include "mail/e-mail-ui-session.h"
-#include "mail/e-mail-view.h"
-#include "mail/em-composer-utils.h"
-#include "mail/em-event.h"
-#include "mail/em-folder-selector.h"
-#include "mail/em-folder-tree.h"
-#include "mail/em-utils.h"
-#include "mail/mail-autofilter.h"
-#include "mail/mail-vfolder-ui.h"
-#include "mail/message-list.h"
-
-#include <em-format/e-mail-formatter.h>
-#include <em-format/e-mail-parser.h>
-#include <em-format/e-mail-part-utils.h>
+#include "em-format/e-mail-formatter.h"
+#include "em-format/e-mail-parser.h"
+#include "em-format/e-mail-part-utils.h"
+
+#include "e-mail-backend.h"
+#include "e-mail-browser.h"
+#include "e-mail-reader-utils.h"
+#include "e-mail-ui-session.h"
+#include "e-mail-view.h"
+#include "em-composer-utils.h"
+#include "em-event.h"
+#include "em-folder-selector.h"
+#include "em-folder-tree.h"
+#include "em-utils.h"
+#include "mail-autofilter.h"
+#include "mail-vfolder-ui.h"
+#include "message-list.h"
 
 #define E_MAIL_READER_GET_PRIVATE(obj) \
 	((EMailReaderPrivate *) g_object_get_qdata \
diff --git a/mail/e-mail-reader.h b/mail/e-mail-reader.h
index b33aee7..1d513f0 100644
--- a/mail/e-mail-reader.h
+++ b/mail/e-mail-reader.h
@@ -29,10 +29,10 @@
 
 #include <gtk/gtk.h>
 #include <camel/camel.h>
-#include <libevolution-utils/e-alert-sink.h>
+#include <e-util/e-util.h>
+
 #include <mail/e-mail-backend.h>
 #include <mail/e-mail-display.h>
-#include <misc/e-preview-pane.h>
 
 /* Standard GObject macros */
 #define E_TYPE_MAIL_READER \
diff --git a/mail/e-mail-request.c b/mail/e-mail-request.c
index 665711f..000a0b0 100644
--- a/mail/e-mail-request.c
+++ b/mail/e-mail-request.c
@@ -30,14 +30,11 @@
 #include <glib/gi18n.h>
 #include <camel/camel.h>
 
-#include <em-format/e-mail-formatter.h>
-#include <em-format/e-mail-formatter-utils.h>
-#include <em-format/e-mail-formatter-print.h>
+#include "shell/e-shell.h"
 
-#include <e-util/e-icon-factory.h>
-#include <e-util/e-util.h>
-
-#include <shell/e-shell.h>
+#include "em-format/e-mail-formatter.h"
+#include "em-format/e-mail-formatter-utils.h"
+#include "em-format/e-mail-formatter-print.h"
 
 #define d(x)
 #define dd(x)
diff --git a/mail/e-mail-tag-editor.c b/mail/e-mail-tag-editor.c
index 9039366..babc4d9 100644
--- a/mail/e-mail-tag-editor.c
+++ b/mail/e-mail-tag-editor.c
@@ -31,7 +31,6 @@
 #include <glib/gi18n-lib.h>
 
 #include "e-util/e-util.h"
-#include "widgets/misc/e-dateedit.h"
 
 #define E_MAIL_TAG_EDITOR_GET_PRIVATE(obj) \
 	(G_TYPE_INSTANCE_GET_PRIVATE \
diff --git a/mail/e-mail-ui-session.c b/mail/e-mail-ui-session.c
index 47eeea2..7f80d8b 100644
--- a/mail/e-mail-ui-session.c
+++ b/mail/e-mail-ui-session.c
@@ -43,12 +43,10 @@
 #endif
 
 #include <libebackend/libebackend.h>
-#include <libedataserverui/libedataserverui.h>
 
 #include "e-mail-account-store.h"
 
 #include "e-util/e-util.h"
-#include "libevolution-utils/e-alert-dialog.h"
 #include "e-util/e-util-private.h"
 
 #include "shell/e-shell.h"
@@ -66,7 +64,7 @@
 #include "em-filter-rule.h"
 #include "em-utils.h"
 #include "libemail-engine/mail-config.h"
-#include "libemail-utils/mail-mt.h"
+#include "libemail-engine/mail-mt.h"
 #include "libemail-engine/mail-ops.h"
 #include "mail-send-recv.h"
 #include "libemail-engine/mail-tools.h"
diff --git a/mail/e-mail-ui-session.h b/mail/e-mail-ui-session.h
index 86ba4dd..e6a6501 100644
--- a/mail/e-mail-ui-session.h
+++ b/mail/e-mail-ui-session.h
@@ -28,11 +28,10 @@
 #define E_MAIL_UI_SESSION_H
 
 #include <camel/camel.h>
+#include <e-util/e-util.h>
 #include <libemail-engine/e-mail-enums.h>
 #include <libemail-engine/e-mail-session.h>
 #include <libemail-engine/mail-folder-cache.h>
-#include <mail/e-mail-account-store.h>
-#include <e-util/e-activity.h>
 
 #include <mail/e-mail-account-store.h>
 #include <mail/e-mail-label-list-store.h>
diff --git a/mail/e-mail-view.h b/mail/e-mail-view.h
index 98072ae..1ff9878 100644
--- a/mail/e-mail-view.h
+++ b/mail/e-mail-view.h
@@ -25,7 +25,6 @@
 
 #include <shell/e-shell-view.h>
 #include <shell/e-shell-searchbar.h>
-#include <menus/gal-view-instance.h>
 
 /* Standard GObject macros */
 #define E_TYPE_MAIL_VIEW \
diff --git a/mail/em-composer-utils.c b/mail/em-composer-utils.c
index 2d8dcc9..8f0da61 100644
--- a/mail/em-composer-utils.c
+++ b/mail/em-composer-utils.c
@@ -30,16 +30,13 @@
 #include <gtk/gtk.h>
 #include <glib/gi18n.h>
 
-#include <libevolution-utils/e-alert-dialog.h>
-#include <libevolution-utils/e-alert-sink.h>
 #include <e-util/e-util.h>
 
-#include <libemail-utils/mail-mt.h>
-
 #include <libemail-engine/e-mail-folder-utils.h>
-#include <libemail-engine/e-mail-session.h>
 #include <libemail-engine/e-mail-session-utils.h>
+#include <libemail-engine/e-mail-session.h>
 #include <libemail-engine/e-mail-utils.h>
+#include <libemail-engine/mail-mt.h>
 #include <libemail-engine/mail-ops.h>
 #include <libemail-engine/mail-tools.h>
 
diff --git a/mail/em-config.h b/mail/em-config.h
index ede4a0a..5de022b 100644
--- a/mail/em-config.h
+++ b/mail/em-config.h
@@ -24,8 +24,7 @@
 #define EM_CONFIG_H
 
 #include <camel/camel.h>
-
-#include "e-util/e-config.h"
+#include <e-util/e-util.h>
 
 /* Standard GObject macros */
 #define EM_TYPE_CONFIG \
diff --git a/mail/em-event.h b/mail/em-event.h
index ef91542..8b4c510 100644
--- a/mail/em-event.h
+++ b/mail/em-event.h
@@ -24,8 +24,7 @@
 #ifndef EM_EVENT_H
 #define EM_EVENT_H
 
-#include "e-util/e-event.h"
-#include "composer/e-msg-composer.h"
+#include <composer/e-msg-composer.h>
 
 /* Standard GObject macros */
 #define EM_TYPE_EVENT \
diff --git a/mail/em-filter-context.c b/mail/em-filter-context.c
index 41c27ff..c0fd8be 100644
--- a/mail/em-filter-context.c
+++ b/mail/em-filter-context.c
@@ -29,8 +29,6 @@
 
 #include "em-filter-context.h"
 #include "em-filter-rule.h"
-#include "filter/e-filter-option.h"
-#include "filter/e-filter-int.h"
 #include "em-filter-source-element.h"
 
 /* For poking into filter-folder guts */
diff --git a/mail/em-filter-context.h b/mail/em-filter-context.h
index 1f8889d..c3f39d6 100644
--- a/mail/em-filter-context.h
+++ b/mail/em-filter-context.h
@@ -25,7 +25,7 @@
 #ifndef EM_FILTER_CONTEXT_H
 #define EM_FILTER_CONTEXT_H
 
-#include <filter/e-rule-context.h>
+#include <e-util/e-util.h>
 #include <libemail-engine/e-mail-session.h>
 
 /* Standard GObject macros */
diff --git a/mail/em-filter-editor-folder-element.c b/mail/em-filter-editor-folder-element.c
index 6179384..0d2f5d6 100644
--- a/mail/em-filter-editor-folder-element.c
+++ b/mail/em-filter-editor-folder-element.c
@@ -26,17 +26,16 @@
 #include <config.h>
 #endif
 
+#include "em-filter-editor-folder-element.h"
+
 #include <string.h>
 
 #include <gtk/gtk.h>
 #include <glib/gi18n.h>
 
-#include "em-filter-editor-folder-element.h"
 #include "mail/em-folder-selection-button.h"
 #include "mail/em-utils.h"
 #include "shell/e-shell.h"
-#include "filter/e-filter-part.h"
-#include "libevolution-utils/e-alert.h"
 
 #define EM_FILTER_EDITOR_FOLDER_ELEMENT_GET_PRIVATE(obj) \
 	(G_TYPE_INSTANCE_GET_PRIVATE \
diff --git a/mail/em-filter-editor-folder-element.h b/mail/em-filter-editor-folder-element.h
index acb84bb..a1d7381 100644
--- a/mail/em-filter-editor-folder-element.h
+++ b/mail/em-filter-editor-folder-element.h
@@ -25,9 +25,9 @@
 #ifndef EM_FILTER_EDITOR_FOLDER_ELEMENT_H
 #define EM_FILTER_EDITOR_FOLDER_ELEMENT_H
 
-#include <filter/e-filter-element.h>
-#include <libemail-utils/em-filter-folder-element.h>
+#include <e-util/e-util.h>
 #include <libemail-engine/e-mail-session.h>
+#include <libemail-engine/em-filter-folder-element.h>
 
 /* Standard GObject macros */
 #define EM_TYPE_FILTER_EDITOR_FOLDER_ELEMENT \
diff --git a/mail/em-filter-editor.h b/mail/em-filter-editor.h
index 3df5b75..7c5da50 100644
--- a/mail/em-filter-editor.h
+++ b/mail/em-filter-editor.h
@@ -25,7 +25,8 @@
 #ifndef EM_FILTER_EDITOR_H
 #define EM_FILTER_EDITOR_H
 
-#include "filter/e-rule-editor.h"
+#include <e-util/e-util.h>
+
 #include "em-filter-context.h"
 
 /* Standard GObject macros */
diff --git a/mail/em-filter-rule.h b/mail/em-filter-rule.h
index 3b5fbd7..805adad 100644
--- a/mail/em-filter-rule.h
+++ b/mail/em-filter-rule.h
@@ -25,7 +25,7 @@
 #ifndef EM_FILTER_RULE_H
 #define EM_FILTER_RULE_H
 
-#include "filter/e-filter-rule.h"
+#include <e-util/e-util.h>
 
 #define EM_TYPE_FILTER_RULE \
 	(em_filter_rule_get_type ())
diff --git a/mail/em-filter-source-element.c b/mail/em-filter-source-element.c
index cdb5905..b08c117 100644
--- a/mail/em-filter-source-element.c
+++ b/mail/em-filter-source-element.c
@@ -25,17 +25,17 @@
 #include <config.h>
 #endif
 
-#include <string.h>
-
 #include "em-filter-source-element.h"
-#include "e-mail-account-store.h"
-#include "e-mail-ui-session.h"
+
+#include <string.h>
 
 #include <gtk/gtk.h>
 #include <camel/camel.h>
 
-#include <shell/e-shell.h>
-#include <filter/e-filter-part.h>
+#include "shell/e-shell.h"
+
+#include "e-mail-account-store.h"
+#include "e-mail-ui-session.h"
 
 #define EM_FILTER_SOURCE_ELEMENT_GET_PRIVATE(obj) \
 	(G_TYPE_INSTANCE_GET_PRIVATE \
diff --git a/mail/em-filter-source-element.h b/mail/em-filter-source-element.h
index 061a487..f66a389 100644
--- a/mail/em-filter-source-element.h
+++ b/mail/em-filter-source-element.h
@@ -24,7 +24,7 @@
 #ifndef EM_FILTER_SOURCE_ELEMENT_H
 #define EM_FILTER_SOURCE_ELEMENT_H
 
-#include <filter/e-filter-element.h>
+#include <e-util/e-util.h>
 #include <libemail-engine/e-mail-session.h>
 
 /* Standard GObject macros */
diff --git a/mail/em-folder-properties.c b/mail/em-folder-properties.c
index d6ab5ac..d17fd69 100644
--- a/mail/em-folder-properties.c
+++ b/mail/em-folder-properties.c
@@ -31,8 +31,8 @@
 #include <gtk/gtk.h>
 #include <glib/gi18n.h>
 
-#include <libemail-utils/mail-mt.h>
 #include <libemail-engine/e-mail-folder-utils.h>
+#include <libemail-engine/mail-mt.h>
 #include <libemail-engine/mail-ops.h>
 
 #include "e-mail-backend.h"
diff --git a/mail/em-folder-properties.h b/mail/em-folder-properties.h
index a94354d..08eb2f0 100644
--- a/mail/em-folder-properties.h
+++ b/mail/em-folder-properties.h
@@ -25,7 +25,6 @@
 #define __EM_FOLDER_PROPERTIES_H__
 
 #include <camel/camel.h>
-#include <libevolution-utils/e-alert-sink.h>
 #include <libemail-engine/e-mail-session.h>
 
 G_BEGIN_DECLS
diff --git a/mail/em-folder-tree-model.c b/mail/em-folder-tree-model.c
index 7eca514..750c0af 100644
--- a/mail/em-folder-tree-model.c
+++ b/mail/em-folder-tree-model.c
@@ -39,12 +39,11 @@
 #include <e-util/e-util.h>
 #include <shell/e-shell.h>
 
-#include <libemail-utils/mail-mt.h>
-
 #include <libemail-engine/e-mail-folder-utils.h>
 #include <libemail-engine/mail-folder-cache.h>
-#include <libemail-engine/mail-tools.h>
+#include <libemail-engine/mail-mt.h>
 #include <libemail-engine/mail-ops.h>
+#include <libemail-engine/mail-tools.h>
 
 #include <e-mail-account-store.h>
 #include <e-mail-ui-session.h>
diff --git a/mail/em-folder-tree.c b/mail/em-folder-tree.c
index 7793f87..33a89b1 100644
--- a/mail/em-folder-tree.c
+++ b/mail/em-folder-tree.c
@@ -39,18 +39,11 @@
 #include <gdk/gdkkeysyms.h>
 #include <glib/gi18n.h>
 
-#include "e-util/e-mktemp.h"
-#include "e-util/e-icon-factory.h"
-#include "libevolution-utils/e-alert-dialog.h"
-#include "e-util/e-util.h"
-
-#include "misc/e-selectable.h"
-
 #include "em-vfolder-editor-rule.h"
 
-#include "libemail-utils/mail-mt.h"
 #include "libemail-engine/e-mail-folder-utils.h"
 #include "libemail-engine/e-mail-session.h"
+#include "libemail-engine/mail-mt.h"
 #include "libemail-engine/mail-ops.h"
 #include "libemail-engine/mail-tools.h"
 
diff --git a/mail/em-folder-tree.h b/mail/em-folder-tree.h
index 74eb111..97c985c 100644
--- a/mail/em-folder-tree.h
+++ b/mail/em-folder-tree.h
@@ -25,8 +25,7 @@
 #define EM_FOLDER_TREE_H
 
 #include <gtk/gtk.h>
-#include <e-util/e-activity.h>
-#include <libevolution-utils/e-alert-sink.h>
+#include <e-util/e-util.h>
 #include <mail/em-folder-tree-model.h>
 #include <libemail-engine/e-mail-session.h>
 
diff --git a/mail/em-folder-utils.c b/mail/em-folder-utils.c
index c211c03..686750a 100644
--- a/mail/em-folder-utils.c
+++ b/mail/em-folder-utils.c
@@ -39,21 +39,18 @@
 #include <gdk-pixbuf/gdk-pixbuf.h>
 #include <glib/gi18n.h>
 
-#include "e-util/e-mktemp.h"
-
-#include "libevolution-utils/e-alert-dialog.h"
-#include "e-util/e-dialog-utils.h"
+#include "e-util/e-util.h"
 
 #include "em-vfolder-editor-rule.h"
 
-#include "libemail-utils/mail-mt.h"
 #include "libemail-engine/e-mail-folder-utils.h"
 #include "libemail-engine/e-mail-session.h"
 #include "libemail-engine/e-mail-store-utils.h"
 #include "libemail-engine/e-mail-utils.h"
+#include "libemail-engine/mail-folder-cache.h"
+#include "libemail-engine/mail-mt.h"
 #include "libemail-engine/mail-ops.h"
 #include "libemail-engine/mail-tools.h"
-#include "libemail-engine/mail-folder-cache.h"
 
 #include "e-mail-ui-session.h"
 #include "em-utils.h"
diff --git a/mail/em-search-context.c b/mail/em-search-context.c
index 2a90075..95fb097 100644
--- a/mail/em-search-context.c
+++ b/mail/em-search-context.c
@@ -26,12 +26,9 @@
 #include <config.h>
 #endif
 
-#include <string.h>
-
 #include "em-search-context.h"
-#include "filter/e-filter-rule.h"
-#include "filter/e-filter-option.h"
-#include "filter/e-filter-int.h"
+
+#include <string.h>
 
 G_DEFINE_TYPE (EMSearchContext, em_search_context, E_TYPE_RULE_CONTEXT)
 
diff --git a/mail/em-search-context.h b/mail/em-search-context.h
index 316a53a..8a104db 100644
--- a/mail/em-search-context.h
+++ b/mail/em-search-context.h
@@ -25,7 +25,7 @@
 #ifndef EM_SEARCH_CONTEXT_H
 #define EM_SEARCH_CONTEXT_H
 
-#include <filter/e-rule-context.h>
+#include <e-util/e-util.h>
 
 /* Standard GObject macros */
 #define EM_SEARCH_TYPE_CONTEXT \
diff --git a/mail/em-subscription-editor.c b/mail/em-subscription-editor.c
index 1ee5269..5a372b9 100644
--- a/mail/em-subscription-editor.c
+++ b/mail/em-subscription-editor.c
@@ -25,13 +25,11 @@
 #include <string.h>
 #include <glib/gi18n-lib.h>
 
-#include <libemail-utils/mail-mt.h>
-#include <libemail-engine/mail-tools.h>
-#include <libemail-engine/mail-ops.h>
+#include "libemail-engine/mail-mt.h"
+#include "libemail-engine/mail-tools.h"
+#include "libemail-engine/mail-ops.h"
 
-#include <e-util/e-dialog-utils.h>
-#include <e-util/e-util.h>
-#include <e-util/e-util-private.h>
+#include "e-util/e-util.h"
 
 #include "em-folder-utils.h"
 
diff --git a/mail/em-utils.c b/mail/em-utils.c
index d533be7..3bb4b9f 100644
--- a/mail/em-utils.c
+++ b/mail/em-utils.c
@@ -31,6 +31,7 @@
 #include <errno.h>
 #include <time.h>
 
+#include <glib/gi18n.h>
 #include <glib/gstdio.h>
 
 #ifdef G_OS_WIN32
@@ -43,28 +44,18 @@
 
 #include "em-filter-editor.h"
 
-#include <glib/gi18n.h>
-
 #include <libebook/libebook.h>
 
-#include <e-util/e-util.h>
-#include <e-util/e-util-private.h>
-#include <e-util/e-mktemp.h>
-#include <e-util/e-dialog-utils.h>
-#include <libevolution-utils/e-alert-dialog.h>
-
-#include <shell/e-shell.h>
-#include <widgets/misc/e-attachment.h>
-
-#include <em-format/e-mail-parser.h>
-#include <em-format/e-mail-formatter-quote.h>
+#include "shell/e-shell.h"
 
-#include <libemail-utils/mail-mt.h>
+#include "em-format/e-mail-parser.h"
+#include "em-format/e-mail-formatter-quote.h"
 
-#include <libemail-engine/e-mail-folder-utils.h>
-#include <libemail-engine/e-mail-session.h>
-#include <libemail-engine/mail-ops.h>
-#include <libemail-engine/mail-tools.h>
+#include "libemail-engine/e-mail-folder-utils.h"
+#include "libemail-engine/e-mail-session.h"
+#include "libemail-engine/mail-mt.h"
+#include "libemail-engine/mail-ops.h"
+#include "libemail-engine/mail-tools.h"
 
 #include "e-mail-tag-editor.h"
 #include "em-composer-utils.h"
diff --git a/mail/em-vfolder-editor-context.c b/mail/em-vfolder-editor-context.c
index 31a2c20..622bb5c 100644
--- a/mail/em-vfolder-editor-context.c
+++ b/mail/em-vfolder-editor-context.c
@@ -26,15 +26,15 @@
 #include <config.h>
 #endif
 
-#include <string.h>
-#include <shell/e-shell.h>
-#include "mail/em-utils.h"
 #include "em-vfolder-editor-context.h"
-#include "em-vfolder-editor-rule.h"
-#include "filter/e-filter-option.h"
-#include "filter/e-filter-int.h"
+
+#include <string.h>
+
+#include "shell/e-shell.h"
 
 #include "em-filter-editor-folder-element.h"
+#include "em-utils.h"
+#include "em-vfolder-editor-rule.h"
 
 #define EM_VFOLDER_EDITOR_CONTEXT_GET_PRIVATE(obj) \
 	(G_TYPE_INSTANCE_GET_PRIVATE \
diff --git a/mail/em-vfolder-editor-context.h b/mail/em-vfolder-editor-context.h
index 062d8bf..e131d92 100644
--- a/mail/em-vfolder-editor-context.h
+++ b/mail/em-vfolder-editor-context.h
@@ -25,9 +25,9 @@
 #ifndef EM_VFOLDER_EDITOR_CONTEXT_H
 #define EM_VFOLDER_EDITOR_CONTEXT_H
 
-#include <filter/e-rule-context.h>
-#include <libemail-utils/em-vfolder-context.h>
+#include <e-util/e-util.h>
 #include <libemail-engine/e-mail-session.h>
+#include <libemail-engine/em-vfolder-context.h>
 
 /* Standard GObject macros */
 #define EM_TYPE_VFOLDER_EDITOR_CONTEXT \
diff --git a/mail/em-vfolder-editor-rule.c b/mail/em-vfolder-editor-rule.c
index 6a4cd6c..59c3713 100644
--- a/mail/em-vfolder-editor-rule.c
+++ b/mail/em-vfolder-editor-rule.c
@@ -33,7 +33,6 @@
 #include <shell/e-shell.h>
 
 #include <e-util/e-util.h>
-#include <libevolution-utils/e-alert.h>
 #include <e-util/e-util-private.h>
 
 #include <libemail-engine/e-mail-folder-utils.h>
diff --git a/mail/em-vfolder-editor-rule.h b/mail/em-vfolder-editor-rule.h
index 39ad097..080ce49 100644
--- a/mail/em-vfolder-editor-rule.h
+++ b/mail/em-vfolder-editor-rule.h
@@ -24,9 +24,9 @@
 #ifndef EM_VFOLDER_EDITOR_RULE_H
 #define EM_VFOLDER_EDITOR_RULE_H
 
-#include <filter/e-filter-rule.h>
-#include <libemail-utils/em-vfolder-rule.h>
+#include <e-util/e-util.h>
 #include <libemail-engine/e-mail-session.h>
+#include <libemail-engine/em-vfolder-rule.h>
 
 /* Standard GObject macros */
 #define EM_TYPE_VFOLDER_EDITOR_RULE \
diff --git a/mail/em-vfolder-editor.h b/mail/em-vfolder-editor.h
index 311a637..252dffe 100644
--- a/mail/em-vfolder-editor.h
+++ b/mail/em-vfolder-editor.h
@@ -24,7 +24,8 @@
 #ifndef EM_VFOLDER_EDITOR_H
 #define EM_VFOLDER_EDITOR_H
 
-#include "filter/e-rule-editor.h"
+#include <e-util/e-util.h>
+
 #include "em-vfolder-editor-context.h"
 
 /* Standard GObject macros */
diff --git a/mail/importers/Makefile.am b/mail/importers/Makefile.am
index 6d40087..089dae4 100644
--- a/mail/importers/Makefile.am
+++ b/mail/importers/Makefile.am
@@ -5,11 +5,11 @@ libevolution_mail_importers_la_CPPFLAGS = \
 	-I.. 						\
 	-I$(srcdir)/..					\
 	-I$(top_srcdir)					\
-	-I$(top_srcdir)/widgets				\
 	-DG_LOG_DOMAIN=\"evolution-mail-importer\"	\
 	-DEVOLUTION_PRIVDATADIR=\""$(privdatadir)"\"	\
 	$(EVOLUTION_DATA_SERVER_CFLAGS)			\
 	$(GNOME_PLATFORM_CFLAGS)			\
+	$(CHAMPLAIN_CFLAGS)				\
 	$(GTKHTML_CFLAGS)
 
 libevolution_mail_importers_la_SOURCES =	\
@@ -24,14 +24,12 @@ libevolution_mail_importers_la_LDFLAGS = -avoid-version $(NO_UNDEFINED)
 libevolution_mail_importers_la_LIBADD =				\
 	$(top_builddir)/e-util/libeutil.la			\
 	$(top_builddir)/shell/libeshell.la			\
-	$(top_builddir)/filter/libfilter.la			\
 	$(top_builddir)/mail/libevolution-mail.la		\
 	$(top_builddir)/shell/libeshell.la			\
-	$(top_builddir)/widgets/misc/libemiscwidgets.la		\
 	$(top_builddir)/libemail-engine/libemail-engine.la \
-	$(top_builddir)/libemail-utils/libemail-utils.la		\
 	$(EVOLUTION_DATA_SERVER_LIBS)				\
 	$(GNOME_PLATFORM_LIBS)					\
+	$(CHAMPLAIN_LIBS)					\
 	$(GTKHTML_LIBS)
 
 -include $(top_srcdir)/git.mk
diff --git a/mail/importers/elm-importer.c b/mail/importers/elm-importer.c
index 02d29f5..8722a8d 100644
--- a/mail/importers/elm-importer.c
+++ b/mail/importers/elm-importer.c
@@ -38,9 +38,8 @@
 
 #include "mail-importer.h"
 
-#include "libemail-utils/mail-mt.h"
+#include "libemail-engine/mail-mt.h"
 #include "mail/e-mail-backend.h"
-#include "e-util/e-import.h"
 #include "shell/e-shell.h"
 
 #define d(x)
diff --git a/mail/importers/evolution-mbox-importer.c b/mail/importers/evolution-mbox-importer.c
index 0f1e507..ddbc798 100644
--- a/mail/importers/evolution-mbox-importer.c
+++ b/mail/importers/evolution-mbox-importer.c
@@ -44,17 +44,15 @@
 #include "shell/e-shell-view.h"
 #include "shell/e-shell-sidebar.h"
 
+#include "libemail-engine/mail-mt.h"
+
 #include "mail/e-mail-backend.h"
 #include "mail/em-folder-selection-button.h"
 #include "mail/em-folder-tree-model.h"
 #include "mail/em-folder-tree.h"
-#include "libemail-utils/mail-mt.h"
 
 #include "mail-importer.h"
 
-#include "e-util/e-import.h"
-#include "misc/e-web-view-preview.h"
-
 typedef struct {
 	EImport *import;
 	EImportTarget *target;
diff --git a/mail/importers/mail-importer.c b/mail/importers/mail-importer.c
index be82106..19df23d 100644
--- a/mail/importers/mail-importer.c
+++ b/mail/importers/mail-importer.c
@@ -39,9 +39,9 @@
 #include "e-util/e-util-private.h"
 #include "shell/e-shell-backend.h"
 
-#include "libemail-utils/mail-mt.h"
-#include "libemail-engine/mail-tools.h"
 #include "libemail-engine/e-mail-session.h"
+#include "libemail-engine/mail-mt.h"
+#include "libemail-engine/mail-tools.h"
 
 #include "mail-importer.h"
 
diff --git a/mail/importers/mail-importer.h b/mail/importers/mail-importer.h
index bd0335c..11e9795 100644
--- a/mail/importers/mail-importer.h
+++ b/mail/importers/mail-importer.h
@@ -24,8 +24,8 @@
 #ifndef __MAIL_IMPORTER_H__
 #define __MAIL_IMPORTER_H__
 
-#include <e-util/e-import.h>
 #include <camel/camel.h>
+#include <e-util/e-util.h>
 #include <libemail-engine/e-mail-session.h>
 
 EImportImporter *mbox_importer_peek (void);
diff --git a/mail/importers/pine-importer.c b/mail/importers/pine-importer.c
index c8904c4..eb7e5ad 100644
--- a/mail/importers/pine-importer.c
+++ b/mail/importers/pine-importer.c
@@ -40,9 +40,8 @@
 
 #include "mail-importer.h"
 
-#include "libemail-utils/mail-mt.h"
+#include "libemail-engine/mail-mt.h"
 #include "mail/e-mail-backend.h"
-#include "e-util/e-import.h"
 #include "shell/e-shell.h"
 
 #define d(x)
diff --git a/mail/mail-autofilter.c b/mail/mail-autofilter.c
index 5ce81dd..c7cefb3 100644
--- a/mail/mail-autofilter.c
+++ b/mail/mail-autofilter.c
@@ -35,7 +35,6 @@
 #include "mail-vfolder-ui.h"
 #include "mail-autofilter.h"
 #include "em-utils.h"
-#include "libevolution-utils/e-alert-dialog.h"
 #include "e-util/e-util-private.h"
 
 #include "em-vfolder-editor-context.h"
@@ -45,8 +44,6 @@
 #include "em-filter-context.h"
 #include "em-filter-rule.h"
 #include "em-filter-editor.h"
-#include "filter/e-filter-option.h"
-#include "filter/e-filter-input.h"
 
 #define d(x)
 
diff --git a/mail/mail-autofilter.h b/mail/mail-autofilter.h
index a9e4254..79c8793 100644
--- a/mail/mail-autofilter.h
+++ b/mail/mail-autofilter.h
@@ -26,10 +26,9 @@
 
 #include <camel/camel.h>
 
-#include <filter/e-filter-rule.h>
 #include <mail/em-filter-context.h>
-#include <libemail-utils/em-vfolder-context.h>
 #include <libemail-engine/e-mail-session.h>
+#include <libemail-engine/em-vfolder-context.h>
 
 enum {
 	AUTO_SUBJECT = 1,
diff --git a/mail/mail-send-recv.c b/mail/mail-send-recv.c
index e500115..7d7f0f9 100644
--- a/mail/mail-send-recv.c
+++ b/mail/mail-send-recv.c
@@ -32,14 +32,13 @@
 #include <shell/e-shell.h>
 #include <e-util/e-util.h>
 
-#include <libemail-utils/mail-mt.h>
-
 /* This is our hack, not part of libcamel. */
 #include <libemail-engine/camel-null-store.h>
 
 #include <libemail-engine/e-mail-folder-utils.h>
 #include <libemail-engine/e-mail-session.h>
 #include <libemail-engine/mail-folder-cache.h>
+#include <libemail-engine/mail-mt.h>
 #include <libemail-engine/mail-ops.h>
 #include <libemail-engine/mail-tools.h>
 
diff --git a/mail/mail-vfolder-ui.c b/mail/mail-vfolder-ui.c
index efe086c..48b7fce 100644
--- a/mail/mail-vfolder-ui.c
+++ b/mail/mail-vfolder-ui.c
@@ -28,14 +28,13 @@
 
 #include <glib/gi18n.h>
 
-#include "libevolution-utils/e-alert-dialog.h"
 #include "e-util/e-util-private.h"
 
-#include "libemail-utils/mail-mt.h"
-#include "libemail-engine/mail-folder-cache.h"
 #include "libemail-engine/e-mail-folder-utils.h"
 #include "libemail-engine/e-mail-session.h"
 #include "libemail-engine/e-mail-utils.h"
+#include "libemail-engine/mail-folder-cache.h"
+#include "libemail-engine/mail-mt.h"
 #include "libemail-engine/mail-ops.h"
 #include "libemail-engine/mail-tools.h"
 
diff --git a/mail/mail-vfolder-ui.h b/mail/mail-vfolder-ui.h
index 55b838c..07bcaa3 100644
--- a/mail/mail-vfolder-ui.h
+++ b/mail/mail-vfolder-ui.h
@@ -24,12 +24,11 @@
 
 #include <camel/camel.h>
 
-#include <filter/e-filter-part.h>
-#include <filter/e-filter-rule.h>
+#include <libemail-engine/em-vfolder-rule.h>
+#include <libemail-engine/mail-vfolder.h>
+
 #include <mail/e-mail-backend.h>
-#include <libemail-utils/em-vfolder-rule.h>
 #include <shell/e-shell-view.h>
-#include <libemail-engine/mail-vfolder.h>
 
 void		vfolder_edit			(EMailBackend *backend,
 						 GtkWindow *parent_window);
diff --git a/mail/message-list.c b/mail/message-list.c
index 473cc4f..71bc8bb 100644
--- a/mail/message-list.c
+++ b/mail/message-list.c
@@ -26,6 +26,8 @@
 #include <config.h>
 #endif
 
+#include "message-list.h"
+
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <unistd.h>
@@ -36,38 +38,18 @@
 #include <glib/gi18n.h>
 #include <glib/gstdio.h>
 
-#include "e-util/e-icon-factory.h"
-#include "e-util/e-poolv.h"
-#include "e-util/e-util-private.h"
-#include "e-util/e-util.h"
-
-#include "misc/e-selectable.h"
-
 #include "shell/e-shell.h"
 #include "shell/e-shell-settings.h"
 
-#include "table/e-cell-checkbox.h"
-#include "table/e-cell-hbox.h"
-#include "table/e-cell-date.h"
-#include "table/e-cell-size.h"
-#include "table/e-cell-text.h"
-#include "table/e-cell-toggle.h"
-#include "table/e-cell-tree.h"
-#include "table/e-cell-vbox.h"
-#include "table/e-table-sorting-utils.h"
-#include "table/e-tree-memory-callbacks.h"
-#include "table/e-tree-memory.h"
-
-#include "libemail-utils/mail-mt.h"
 #include "libemail-engine/e-mail-utils.h"
 #include "libemail-engine/mail-config.h"
+#include "libemail-engine/mail-mt.h"
 #include "libemail-engine/mail-ops.h"
 #include "libemail-engine/mail-tools.h"
 
-#include "mail/e-mail-label-list-store.h"
-#include "mail/e-mail-ui-session.h"
-#include "mail/em-utils.h"
-#include "mail/message-list.h"
+#include "e-mail-label-list-store.h"
+#include "e-mail-ui-session.h"
+#include "em-utils.h"
 
 /*#define TIMEIT */
 
diff --git a/mail/message-list.h b/mail/message-list.h
index 9c93c46..daa27f2 100644
--- a/mail/message-list.h
+++ b/mail/message-list.h
@@ -26,7 +26,7 @@
 #include <gtk/gtk.h>
 #include <camel/camel.h>
 
-#include <table/e-tree.h>
+#include <e-util/e-util.h>
 #include <libemail-engine/e-mail-session.h>
 
 /* Standard GObject macros */
diff --git a/maint/Makefile.am b/maint/Makefile.am
index 175832e..e53a1d7 100644
--- a/maint/Makefile.am
+++ b/maint/Makefile.am
@@ -12,9 +12,9 @@ libgladeevolution_la_LDFLAGS = \
 	-module -avoid-version
 
 libgladeevolution_la_LIBADD = \
+	$(top_srcdir)/e-util/libeutil.la \
 	$(top_srcdir)/mail/libevolution-mail.la \
 	$(top_srcdir)/calendar/gui/libevolution-calendar.la \
-	$(top_builddir)/widgets/misc/libemiscwidgets.la \
 	$(EVOLUTION_DATA_SERVER_LIBS) \
 	$(GNOME_PLATFORM_LIBS)
 
diff --git a/modules/addressbook/Makefile.am b/modules/addressbook/Makefile.am
index 49b24e2..63b1f4b 100644
--- a/modules/addressbook/Makefile.am
+++ b/modules/addressbook/Makefile.am
@@ -4,11 +4,8 @@ module_addressbook_la_CPPFLAGS = \
 	$(AM_CPPFLAGS)						\
 	-DG_LOG_DOMAIN=\"evolution-addressbook\"		\
 	-I$(top_srcdir)						\
-	-I$(top_srcdir)/widgets					\
 	-I$(top_srcdir)/shell					\
 	-I$(top_builddir)/shell					\
-	-I$(top_srcdir)/widgets/menus				\
-	-I$(top_srcdir)/widgets/misc				\
 	-I$(top_srcdir)/addressbook/util			\
 	-I$(top_srcdir)/addressbook/gui/contact-editor		\
 	-I$(top_srcdir)/addressbook/gui/contact-list-editor	\
@@ -64,14 +61,9 @@ module_addressbook_la_LIBADD = \
 	$(top_builddir)/shell/libeshell.la			\
 	$(top_builddir)/addressbook/gui/merging/libeabbookmerging.la \
 	$(top_builddir)/addressbook/gui/widgets/libeabwidgets.la \
-	$(top_builddir)/filter/libfilter.la			\
 	$(top_builddir)/addressbook/util/libeabutil.la		\
 	$(top_builddir)/addressbook/gui/contact-editor/libecontacteditor.la \
 	$(top_builddir)/addressbook/gui/contact-list-editor/libecontactlisteditor.la \
-	$(top_builddir)/widgets/table/libetable.la		\
-	$(top_builddir)/widgets/text/libetext.la		\
-	$(top_builddir)/widgets/misc/libemiscwidgets.la		\
-	$(top_builddir)/widgets/menus/libmenus.la		\
 	$(top_builddir)/addressbook/importers/libevolution-addressbook-importers.la \
 	$(EVOLUTION_DATA_SERVER_LIBS)				\
 	$(GNOME_PLATFORM_LIBS)					\
diff --git a/modules/addressbook/autocompletion-config.c b/modules/addressbook/autocompletion-config.c
index 6153289..d39ea53 100644
--- a/modules/addressbook/autocompletion-config.c
+++ b/modules/addressbook/autocompletion-config.c
@@ -25,10 +25,6 @@
 #include "autocompletion-config.h"
 
 #include <glib/gi18n.h>
-#include <libedataserverui/libedataserverui.h>
-
-#include "e-util/e-datetime-format.h"
-#include "misc/e-autocomplete-selector.h"
 
 static GtkWidget *
 add_section (GtkWidget *container,
diff --git a/modules/addressbook/autocompletion-config.h b/modules/addressbook/autocompletion-config.h
index 2cebea1..2c60301 100644
--- a/modules/addressbook/autocompletion-config.h
+++ b/modules/addressbook/autocompletion-config.h
@@ -26,7 +26,6 @@
 #define _AUTOCOMPLETION_CONFIG_H
 
 #include <shell/e-shell.h>
-#include <widgets/misc/e-preferences-window.h>
 
 G_BEGIN_DECLS
 
diff --git a/modules/addressbook/e-book-config-hook.c b/modules/addressbook/e-book-config-hook.c
index dd4b38b..8ee3424 100644
--- a/modules/addressbook/e-book-config-hook.c
+++ b/modules/addressbook/e-book-config-hook.c
@@ -25,7 +25,6 @@
 
 #include "e-book-config-hook.h"
 
-#include "e-util/e-config.h"
 #include "addressbook/gui/widgets/eab-config.h"
 
 static const EConfigHookTargetMask no_masks[] = {
diff --git a/modules/addressbook/e-book-config-name-selector-entry.c b/modules/addressbook/e-book-config-name-selector-entry.c
index ac7ccf4..acc38a7 100644
--- a/modules/addressbook/e-book-config-name-selector-entry.c
+++ b/modules/addressbook/e-book-config-name-selector-entry.c
@@ -22,7 +22,7 @@
 
 #include "e-book-config-name-selector-entry.h"
 
-#include <libedataserverui/libedataserverui.h>
+#include <e-util/e-util.h>
 
 #define E_BOOK_CONFIG_NAME_SELECTOR_ENTRY_GET_PRIVATE(obj) \
 	(G_TYPE_INSTANCE_GET_PRIVATE \
diff --git a/modules/addressbook/e-book-shell-backend.c b/modules/addressbook/e-book-shell-backend.c
index 2475312..5dded83 100644
--- a/modules/addressbook/e-book-shell-backend.c
+++ b/modules/addressbook/e-book-shell-backend.c
@@ -28,14 +28,9 @@
 #include <string.h>
 #include <glib/gi18n.h>
 #include <libebook/libebook.h>
-#include <libedataserverui/libedataserverui.h>
 
-#include "e-util/e-import.h"
 #include "shell/e-shell.h"
 #include "shell/e-shell-window.h"
-#include "widgets/misc/e-book-source-config.h"
-#include "widgets/misc/e-preferences-window.h"
-#include "widgets/misc/e-source-config-dialog.h"
 
 #include "addressbook/gui/widgets/eab-gui-util.h"
 #include "addressbook/gui/contact-editor/e-contact-editor.h"
diff --git a/modules/addressbook/e-book-shell-content.c b/modules/addressbook/e-book-shell-content.c
index 84c8b31..26b370f 100644
--- a/modules/addressbook/e-book-shell-content.c
+++ b/modules/addressbook/e-book-shell-content.c
@@ -27,10 +27,7 @@
 
 #include <glib/gi18n.h>
 
-#include "e-util/e-selection.h"
 #include "shell/e-shell-utils.h"
-#include "widgets/misc/e-paned.h"
-#include "widgets/misc/e-preview-pane.h"
 #include "e-book-shell-view.h"
 
 #define E_BOOK_SHELL_CONTENT_GET_PRIVATE(obj) \
diff --git a/modules/addressbook/e-book-shell-content.h b/modules/addressbook/e-book-shell-content.h
index 63c395c..b0258d8 100644
--- a/modules/addressbook/e-book-shell-content.h
+++ b/modules/addressbook/e-book-shell-content.h
@@ -28,8 +28,6 @@
 #include <shell/e-shell-searchbar.h>
 #include <shell/e-shell-view.h>
 
-#include <misc/e-preview-pane.h>
-
 #include "addressbook/gui/widgets/e-addressbook-view.h"
 #include "eab-composer-util.h"
 
diff --git a/modules/addressbook/e-book-shell-sidebar.h b/modules/addressbook/e-book-shell-sidebar.h
index d2bac8c..703d67c 100644
--- a/modules/addressbook/e-book-shell-sidebar.h
+++ b/modules/addressbook/e-book-shell-sidebar.h
@@ -22,8 +22,6 @@
 #ifndef E_BOOK_SHELL_SIDEBAR_H
 #define E_BOOK_SHELL_SIDEBAR_H
 
-#include <libedataserverui/libedataserverui.h>
-
 #include <shell/e-shell-sidebar.h>
 #include <shell/e-shell-view.h>
 
diff --git a/modules/addressbook/e-book-shell-view-actions.c b/modules/addressbook/e-book-shell-view-actions.c
index 54865b7..01064f2 100644
--- a/modules/addressbook/e-book-shell-view-actions.c
+++ b/modules/addressbook/e-book-shell-view-actions.c
@@ -25,13 +25,7 @@
 
 #include "e-book-shell-view-private.h"
 
-#include <libevolution-utils/e-alert-dialog.h>
 #include <e-util/e-util.h>
-#include <filter/e-filter-rule.h>
-
-#ifdef WITH_CONTACT_MAPS
-#include <widgets/misc/e-contact-map-window.h>
-#endif
 
 static void
 action_address_book_copy_cb (GtkAction *action,
diff --git a/modules/addressbook/e-book-shell-view-private.c b/modules/addressbook/e-book-shell-view-private.c
index 947f680..884477f 100644
--- a/modules/addressbook/e-book-shell-view-private.c
+++ b/modules/addressbook/e-book-shell-view-private.c
@@ -27,7 +27,6 @@
 
 #include "e-book-shell-view-private.h"
 
-#include "widgets/menus/gal-view-factory-etable.h"
 #include "addressbook/gui/widgets/gal-view-factory-minicard.h"
 
 static void
diff --git a/modules/addressbook/e-book-shell-view-private.h b/modules/addressbook/e-book-shell-view-private.h
index 8338476..8aa12d1 100644
--- a/modules/addressbook/e-book-shell-view-private.h
+++ b/modules/addressbook/e-book-shell-view-private.h
@@ -28,18 +28,11 @@
 #include <glib/gi18n.h>
 #include <gdk/gdkkeysyms.h>
 #include <libebook/libebook.h>
-#include <libedataserverui/libedataserverui.h>
 
-#include "e-util/e-util.h"
-#include "e-util/e-file-utils.h"
 #include "shell/e-shell-content.h"
 #include "shell/e-shell-searchbar.h"
 #include "shell/e-shell-sidebar.h"
 #include "shell/e-shell-utils.h"
-#include "misc/e-book-source-config.h"
-#include "misc/e-popup-action.h"
-#include "misc/e-selectable.h"
-#include "misc/e-source-config-dialog.h"
 
 #include "addressbook/util/eab-book-util.h"
 #include "addressbook/gui/contact-editor/e-contact-editor.h"
diff --git a/modules/audio-inline/Makefile.am b/modules/audio-inline/Makefile.am
index 64af11c..147379f 100644
--- a/modules/audio-inline/Makefile.am
+++ b/modules/audio-inline/Makefile.am
@@ -3,11 +3,12 @@ module_LTLIBRARIES = module-audio-inline.la
 module_audio_inline_la_CPPFLAGS =				\
 	$(AM_CPPFLAGS)							\
 	-I$(top_srcdir)							\
-	-I$(top_srcdir)/widgets						\
 	-DEVOLUTION_PRIVDATADIR=\""$(privdatadir)"\"			\
 	-DG_LOG_DOMAIN=\"evolution-module-audio-inline\"		\
 	$(EVOLUTION_DATA_SERVER_CFLAGS)					\
 	$(GNOME_PLATFORM_CFLAGS)					\
+	$(CHAMPLAIN_CFLAGS)						\
+	$(GTKHTML_CFLAGS)						\
 	$(GSTREAMER_CFLAGS)
 
 module_audio_inline_la_SOURCES =				\
@@ -24,6 +25,8 @@ module_audio_inline_la_LIBADD =				\
 	$(top_builddir)/em-format/libemformat.la			\
 	$(EVOLUTION_DATA_SERVER_LIBS)					\
 	$(GNOME_PLATFORM_LIBS)						\
+	$(CHAMPLAIN_LIBS)						\
+	$(GTKHTML_LIBS)							\
 	$(GSTREAMER_LIBS)
 
 module_audio_inline_la_LDFLAGS =				\
diff --git a/modules/audio-inline/e-mail-formatter-audio-inline.c b/modules/audio-inline/e-mail-formatter-audio-inline.c
index cf31f15..86cd6f1 100644
--- a/modules/audio-inline/e-mail-formatter-audio-inline.c
+++ b/modules/audio-inline/e-mail-formatter-audio-inline.c
@@ -24,16 +24,14 @@
 
 #include <glib/gi18n-lib.h>
 
+#include <camel/camel.h>
+#include <gst/gst.h>
+
 #include <libebackend/libebackend.h>
 
 #include <em-format/e-mail-formatter-extension.h>
 #include <em-format/e-mail-formatter.h>
 
-#include "e-util/e-mktemp.h"
-
-#include <camel/camel.h>
-#include <gst/gst.h>
-
 #include "e-mail-part-audio-inline.h"
 
 #define d(x)
diff --git a/modules/backup-restore/Makefile.am b/modules/backup-restore/Makefile.am
index 7d952e7..4b59707 100644
--- a/modules/backup-restore/Makefile.am
+++ b/modules/backup-restore/Makefile.am
@@ -5,7 +5,6 @@ module_LTLIBRARIES = module-backup-restore.la
 module_backup_restore_la_CPPFLAGS = \
 	$(AM_CPPFLAGS)						\
 	-I$(top_srcdir)						\
-	-I$(top_srcdir)/widgets					\
 	-DG_LOG_DOMAIN=\"evolution-backup-restore\"		\
 	-DEVOLUTION_LOCALEDIR=\""$(localedir)"\"		\
 	-DEVOLUTION_TOOLSDIR=\""$(privlibexecdir)"\"		\
@@ -15,6 +14,7 @@ module_backup_restore_la_CPPFLAGS = \
 	-DLIBDIR=\""$(libdir)"\"				\
 	$(EVOLUTION_DATA_SERVER_CFLAGS)				\
 	$(GNOME_PLATFORM_CFLAGS)				\
+	$(CHAMPLAIN_CFLAGS)					\
 	$(GTKHTML_CFLAGS)					\
 	$(NULL)
 
@@ -30,11 +30,10 @@ module_backup_restore_la_LIBADD = \
 	$(top_builddir)/e-util/libeutil.la			\
 	$(top_builddir)/shell/libeshell.la			\
 	$(top_builddir)/mail/libevolution-mail.la		\
-	$(top_builddir)/widgets/misc/libemiscwidgets.la		\
 	$(top_builddir)/libemail-engine/libemail-engine.la	\
-	$(top_builddir)/libevolution-utils/libevolution-utils.la \
 	$(EVOLUTION_DATA_SERVER_LIBS)				\
 	$(GNOME_PLATFORM_LIBS)					\
+	$(CHAMPLAIN_LIBS)					\
 	$(GTKHTML_LIBS)						\
 	$(NULL)
 
@@ -46,7 +45,6 @@ privlibexec_PROGRAMS = evolution-backup
 evolution_backup_CPPFLAGS =					\
 	$(AM_CPPFLAGS)						\
 	-I$(top_srcdir)						\
-	-I$(top_srcdir)/widgets					\
 	-DEVOLUTION_LOCALEDIR=\""$(localedir)"\"		\
 	-DEVOLUTION_TOOLSDIR=\""$(privlibexecdir)"\"		\
 	-DPREFIX=\""$(prefix)"\"				\
@@ -56,6 +54,8 @@ evolution_backup_CPPFLAGS =					\
 	-DDBUS_SERVICES_DIR=\"'${datadir}'/dbus-1/services\"	\
 	$(EVOLUTION_DATA_SERVER_CFLAGS)				\
 	$(GNOME_PLATFORM_CFLAGS)				\
+	$(CHAMPLAIN_CFLAGS)					\
+	$(GTKHTML_CFLAGS)					\
 	$(NULL)
 
 evolution_backup_SOURCES =					\
@@ -66,6 +66,8 @@ evolution_backup_LDADD =					\
 	$(top_builddir)/e-util/libeutil.la			\
 	$(EVOLUTION_DATA_SERVER_LIBS)				\
 	$(GNOME_PLATFORM_LIBS)					\
+	$(CHAMPLAIN_LIBS)					\
+	$(GTKHTML_LIBS)						\
 	$(NULL)
 
 if OS_WIN32
diff --git a/modules/backup-restore/e-mail-config-restore-page.c b/modules/backup-restore/e-mail-config-restore-page.c
index 7a99766..33b192c 100644
--- a/modules/backup-restore/e-mail-config-restore-page.c
+++ b/modules/backup-restore/e-mail-config-restore-page.c
@@ -27,9 +27,7 @@
 #endif
 #include <glib/gi18n-lib.h>
 
-#include <libevolution-utils/e-alert-sink.h>
-#include <libevolution-utils/e-alert-dialog.h>
-#include <misc/e-alert-bar.h>
+#include <e-util/e-util.h>
 
 #define E_MAIL_CONFIG_RESTORE_PAGE_GET_PRIVATE(obj) \
 	(G_TYPE_INSTANCE_GET_PRIVATE \
diff --git a/modules/backup-restore/evolution-backup-restore.c b/modules/backup-restore/evolution-backup-restore.c
index 925315b..01028e5 100644
--- a/modules/backup-restore/evolution-backup-restore.c
+++ b/modules/backup-restore/evolution-backup-restore.c
@@ -30,13 +30,13 @@
 
 #include <libebackend/libebackend.h>
 
-#include <mail/e-mail-config-assistant.h>
-#include <libevolution-utils/e-alert-dialog.h>
 #include <e-util/e-util.h>
-#include <e-util/e-dialog-utils.h>
+
 #include <shell/e-shell-utils.h>
 #include <shell/e-shell-window.h>
 
+#include <mail/e-mail-config-assistant.h>
+
 #include "e-mail-config-restore-page.h"
 #include "e-mail-config-restore-ready-page.h"
 
diff --git a/modules/bogofilter/Makefile.am b/modules/bogofilter/Makefile.am
index aaa72f9..a3e3019 100644
--- a/modules/bogofilter/Makefile.am
+++ b/modules/bogofilter/Makefile.am
@@ -15,7 +15,6 @@ module_bogofilter_la_LIBADD = \
 	$(top_builddir)/e-util/libeutil.la			\
 	$(top_builddir)/mail/libevolution-mail.la		\
 	$(top_builddir)/libemail-engine/libemail-engine.la \
-	$(top_builddir)/libemail-utils/libemail-utils.la  \
 	$(EVOLUTION_DATA_SERVER_LIBS)				\
 	$(GNOME_PLATFORM_LIBS)
 
diff --git a/modules/book-config-google/Makefile.am b/modules/book-config-google/Makefile.am
index edf8789..b1f8d71 100644
--- a/modules/book-config-google/Makefile.am
+++ b/modules/book-config-google/Makefile.am
@@ -3,19 +3,21 @@ module_LTLIBRARIES = module-book-config-google.la
 module_book_config_google_la_CPPFLAGS = \
 	$(AM_CPPFLAGS)						\
 	-I$(top_srcdir)						\
-	-I$(top_srcdir)/widgets					\
 	-DG_LOG_DOMAIN=\"evolution-book-config-google\"		\
 	$(EVOLUTION_DATA_SERVER_CFLAGS)				\
-	$(GNOME_PLATFORM_CFLAGS)
+	$(GNOME_PLATFORM_CFLAGS)				\
+	$(CHAMPLAIN_CFLAGS)					\
+	$(GTKHTML_CFLAGS)
 
 module_book_config_google_la_SOURCES = \
 	evolution-book-config-google.c
 
 module_book_config_google_la_LIBADD = \
 	$(top_builddir)/e-util/libeutil.la			\
-	$(top_builddir)/widgets/misc/libemiscwidgets.la		\
 	$(EVOLUTION_DATA_SERVER_LIBS)				\
-	$(GNOME_PLATFORM_LIBS)
+	$(GNOME_PLATFORM_LIBS)					\
+	$(CHAMPLAIN_LIBS)					\
+	$(GTKHTML_LIBS)
 
 module_book_config_google_la_LDFLAGS = \
 	-module -avoid-version $(NO_UNDEFINED)
diff --git a/modules/book-config-google/evolution-book-config-google.c b/modules/book-config-google/evolution-book-config-google.c
index e097c0a..709a6f3 100644
--- a/modules/book-config-google/evolution-book-config-google.c
+++ b/modules/book-config-google/evolution-book-config-google.c
@@ -21,9 +21,7 @@
 
 #include <libebackend/libebackend.h>
 
-#include <misc/e-book-source-config.h>
-#include <misc/e-interval-chooser.h>
-#include <misc/e-source-config-backend.h>
+#include <e-util/e-util.h>
 
 typedef ESourceConfigBackend EBookConfigGoogle;
 typedef ESourceConfigBackendClass EBookConfigGoogleClass;
diff --git a/modules/book-config-ldap/Makefile.am b/modules/book-config-ldap/Makefile.am
index 6e957dc..df17cb3 100644
--- a/modules/book-config-ldap/Makefile.am
+++ b/modules/book-config-ldap/Makefile.am
@@ -3,10 +3,11 @@ module_LTLIBRARIES = module-book-config-ldap.la
 module_book_config_ldap_la_CPPFLAGS = \
 	$(AM_CPPFLAGS)						\
 	-I$(top_srcdir)						\
-	-I$(top_srcdir)/widgets					\
 	-DG_LOG_DOMAIN=\"evolution-book-config-ldap\"		\
 	$(EVOLUTION_DATA_SERVER_CFLAGS)				\
 	$(GNOME_PLATFORM_CFLAGS)				\
+	$(CHAMPLAIN_CFLAGS)					\
+	$(GTKHTML_CFLAGS)					\
 	$(LDAP_CFLAGS)
 
 module_book_config_ldap_la_SOURCES = \
@@ -15,11 +16,11 @@ module_book_config_ldap_la_SOURCES = \
 	e-source-ldap.h
 
 module_book_config_ldap_la_LIBADD = \
-	$(top_builddir)/libevolution-utils/libevolution-utils.la \
 	$(top_builddir)/e-util/libeutil.la			\
-	$(top_builddir)/widgets/misc/libemiscwidgets.la		\
 	$(EVOLUTION_DATA_SERVER_LIBS)				\
 	$(GNOME_PLATFORM_LIBS)					\
+	$(CHAMPLAIN_LIBS)					\
+	$(GTKHTML_LIBS)						\
 	$(LDAP_LIBS)
 
 module_book_config_ldap_la_LDFLAGS = \
diff --git a/modules/book-config-ldap/evolution-book-config-ldap.c b/modules/book-config-ldap/evolution-book-config-ldap.c
index 42cea3b..56fc0f2 100644
--- a/modules/book-config-ldap/evolution-book-config-ldap.c
+++ b/modules/book-config-ldap/evolution-book-config-ldap.c
@@ -22,9 +22,7 @@
 
 #include <libebackend/libebackend.h>
 
-#include <libevolution-utils/e-alert-dialog.h>
-#include <misc/e-book-source-config.h>
-#include <misc/e-source-config-backend.h>
+#include <e-util/e-util.h>
 
 #include "e-source-ldap.h"
 
diff --git a/modules/book-config-local/Makefile.am b/modules/book-config-local/Makefile.am
index 518ea3b..eca745d 100644
--- a/modules/book-config-local/Makefile.am
+++ b/modules/book-config-local/Makefile.am
@@ -3,19 +3,21 @@ module_LTLIBRARIES = module-book-config-local.la
 module_book_config_local_la_CPPFLAGS = \
 	$(AM_CPPFLAGS)						\
 	-I$(top_srcdir)						\
-	-I$(top_srcdir)/widgets					\
 	-DG_LOG_DOMAIN=\"evolution-book-config-local\"		\
 	$(EVOLUTION_DATA_SERVER_CFLAGS)				\
-	$(GNOME_PLATFORM_CFLAGS)
+	$(GNOME_PLATFORM_CFLAGS)				\
+	$(CHAMPLAIN_CFLAGS)					\
+	$(GTKHTML_CFLAGS)
 
 module_book_config_local_la_SOURCES = \
 	evolution-book-config-local.c
 
 module_book_config_local_la_LIBADD = \
 	$(top_builddir)/e-util/libeutil.la			\
-	$(top_builddir)/widgets/misc/libemiscwidgets.la		\
 	$(EVOLUTION_DATA_SERVER_LIBS)				\
-	$(GNOME_PLATFORM_LIBS)
+	$(GNOME_PLATFORM_LIBS)					\
+	$(CHAMPLAIN_LIBS)					\
+	$(GTKHTML_LIBS)
 
 module_book_config_local_la_LDFLAGS = \
 	-module -avoid-version $(NO_UNDEFINED)
diff --git a/modules/book-config-local/evolution-book-config-local.c b/modules/book-config-local/evolution-book-config-local.c
index b6c31a7..2726b84 100644
--- a/modules/book-config-local/evolution-book-config-local.c
+++ b/modules/book-config-local/evolution-book-config-local.c
@@ -21,8 +21,7 @@
 
 #include <libebackend/libebackend.h>
 
-#include <misc/e-book-source-config.h>
-#include <misc/e-source-config-backend.h>
+#include <e-util/e-util.h>
 
 typedef ESourceConfigBackend EBookConfigLocal;
 typedef ESourceConfigBackendClass EBookConfigLocalClass;
diff --git a/modules/book-config-webdav/Makefile.am b/modules/book-config-webdav/Makefile.am
index 5f3f09c..5c919cc 100644
--- a/modules/book-config-webdav/Makefile.am
+++ b/modules/book-config-webdav/Makefile.am
@@ -3,19 +3,21 @@ module_LTLIBRARIES = module-book-config-webdav.la
 module_book_config_webdav_la_CPPFLAGS = \
 	$(AM_CPPFLAGS)						\
 	-I$(top_srcdir)						\
-	-I$(top_srcdir)/widgets					\
 	-DG_LOG_DOMAIN=\"evolution-book-config-webdav\"		\
 	$(EVOLUTION_DATA_SERVER_CFLAGS)				\
-	$(GNOME_PLATFORM_CFLAGS)
+	$(GNOME_PLATFORM_CFLAGS)				\
+	$(CHAMPLAIN_CFLAGS)					\
+	$(GTKHTML_CFLAGS)
 
 module_book_config_webdav_la_SOURCES = \
 	evolution-book-config-webdav.c
 
 module_book_config_webdav_la_LIBADD = \
 	$(top_builddir)/e-util/libeutil.la			\
-	$(top_builddir)/widgets/misc/libemiscwidgets.la		\
 	$(EVOLUTION_DATA_SERVER_LIBS)				\
-	$(GNOME_PLATFORM_LIBS)
+	$(GNOME_PLATFORM_LIBS)					\
+	$(CHAMPLAIN_LIBS)					\
+	$(GTKHTML_LIBS)
 
 module_book_config_webdav_la_LDFLAGS = \
 	-module -avoid-version $(NO_UNDEFINED)
diff --git a/modules/book-config-webdav/evolution-book-config-webdav.c b/modules/book-config-webdav/evolution-book-config-webdav.c
index 6b6b1b4..418de91 100644
--- a/modules/book-config-webdav/evolution-book-config-webdav.c
+++ b/modules/book-config-webdav/evolution-book-config-webdav.c
@@ -21,8 +21,7 @@
 
 #include <libebackend/libebackend.h>
 
-#include <misc/e-book-source-config.h>
-#include <misc/e-source-config-backend.h>
+#include <e-util/e-util.h>
 
 typedef ESourceConfigBackend EBookConfigWebdav;
 typedef ESourceConfigBackendClass EBookConfigWebdavClass;
diff --git a/modules/cal-config-caldav/Makefile.am b/modules/cal-config-caldav/Makefile.am
index 6905aff..51a2b58 100644
--- a/modules/cal-config-caldav/Makefile.am
+++ b/modules/cal-config-caldav/Makefile.am
@@ -3,10 +3,11 @@ module_LTLIBRARIES = module-cal-config-caldav.la
 module_cal_config_caldav_la_CPPFLAGS = \
 	$(AM_CPPFLAGS)						\
 	-I$(top_srcdir)						\
-	-I$(top_srcdir)/widgets					\
 	-DG_LOG_DOMAIN=\"evolution-cal-config-caldav\"		\
 	$(EVOLUTION_DATA_SERVER_CFLAGS)				\
 	$(GNOME_PLATFORM_CFLAGS)				\
+	$(CHAMPLAIN_CFLAGS)					\
+	$(GTKHTML_CFLAGS)					\
 	$(LIBSOUP_CFLAGS)
 
 module_cal_config_caldav_la_SOURCES = \
@@ -18,9 +19,10 @@ module_cal_config_caldav_la_SOURCES = \
 
 module_cal_config_caldav_la_LIBADD = \
 	$(top_builddir)/e-util/libeutil.la			\
-	$(top_builddir)/widgets/misc/libemiscwidgets.la		\
 	$(EVOLUTION_DATA_SERVER_LIBS)				\
 	$(GNOME_PLATFORM_LIBS)					\
+	$(CHAMPLAIN_LIBS)					\
+	$(GTKHTML_LIBS)						\
 	$(LIBSOUP_LIBS)
 
 module_cal_config_caldav_la_LDFLAGS = \
diff --git a/modules/cal-config-caldav/e-caldav-chooser.c b/modules/cal-config-caldav/e-caldav-chooser.c
index b9176c4..8bb5fae 100644
--- a/modules/cal-config-caldav/e-caldav-chooser.c
+++ b/modules/cal-config-caldav/e-caldav-chooser.c
@@ -28,7 +28,7 @@
 #include <libxml/xpath.h>
 #include <libxml/xpathInternals.h>
 
-#include <libedataserverui/libedataserverui.h>
+#include <e-util/e-util.h>
 
 #define E_CALDAV_CHOOSER_GET_PRIVATE(obj) \
 	(G_TYPE_INSTANCE_GET_PRIVATE \
diff --git a/modules/cal-config-caldav/evolution-cal-config-caldav.c b/modules/cal-config-caldav/evolution-cal-config-caldav.c
index 0ca90cb..249892e 100644
--- a/modules/cal-config-caldav/evolution-cal-config-caldav.c
+++ b/modules/cal-config-caldav/evolution-cal-config-caldav.c
@@ -21,9 +21,7 @@
 
 #include <libebackend/libebackend.h>
 
-#include <misc/e-cal-source-config.h>
-#include <misc/e-interval-chooser.h>
-#include <misc/e-source-config-backend.h>
+#include <e-util/e-util.h>
 
 #include "e-caldav-chooser.h"
 #include "e-caldav-chooser-dialog.h"
diff --git a/modules/cal-config-contacts/Makefile.am b/modules/cal-config-contacts/Makefile.am
index d27d3a0..166773a 100644
--- a/modules/cal-config-contacts/Makefile.am
+++ b/modules/cal-config-contacts/Makefile.am
@@ -3,10 +3,11 @@ module_LTLIBRARIES = module-cal-config-contacts.la
 module_cal_config_contacts_la_CPPFLAGS = \
 	$(AM_CPPFLAGS)						\
 	-I$(top_srcdir)						\
-	-I$(top_srcdir)/widgets					\
 	-DG_LOG_DOMAIN=\"evolution-cal-config-contacts\"	\
 	$(EVOLUTION_DATA_SERVER_CFLAGS)				\
-	$(GNOME_PLATFORM_CFLAGS)
+	$(GNOME_PLATFORM_CFLAGS)				\
+	$(CHAMPLAIN_CFLAGS)					\
+	$(GTKHTML_CFLAGS)
 
 module_cal_config_contacts_la_SOURCES = \
 	evolution-cal-config-contacts.c				\
@@ -17,9 +18,10 @@ module_cal_config_contacts_la_SOURCES = \
 
 module_cal_config_contacts_la_LIBADD = \
 	$(top_builddir)/e-util/libeutil.la			\
-	$(top_builddir)/widgets/misc/libemiscwidgets.la		\
 	$(EVOLUTION_DATA_SERVER_LIBS)				\
-	$(GNOME_PLATFORM_LIBS)
+	$(GNOME_PLATFORM_LIBS)					\
+	$(CHAMPLAIN_LIBS)					\
+	$(GTKHTML_LIBS)
 
 module_cal_config_contacts_la_LDFLAGS = \
 	-module -avoid-version $(NO_UNDEFINED)
diff --git a/modules/cal-config-contacts/e-contacts-selector.h b/modules/cal-config-contacts/e-contacts-selector.h
index 5ba1ea9..7d567cd 100644
--- a/modules/cal-config-contacts/e-contacts-selector.h
+++ b/modules/cal-config-contacts/e-contacts-selector.h
@@ -19,7 +19,7 @@
 #ifndef E_CONTACTS_SELECTOR_H
 #define E_CONTACTS_SELECTOR_H
 
-#include <libedataserverui/libedataserverui.h>
+#include <e-util/e-util.h>
 
 /* Standard GObject macros */
 #define E_TYPE_CONTACTS_SELECTOR \
diff --git a/modules/cal-config-contacts/evolution-cal-config-contacts.c b/modules/cal-config-contacts/evolution-cal-config-contacts.c
index 5efc8b0..e61d6af 100644
--- a/modules/cal-config-contacts/evolution-cal-config-contacts.c
+++ b/modules/cal-config-contacts/evolution-cal-config-contacts.c
@@ -21,9 +21,7 @@
 
 #include <libebackend/libebackend.h>
 
-#include <misc/e-cal-source-config.h>
-#include <misc/e-book-source-config.h>
-#include <misc/e-source-config-backend.h>
+#include <e-util/e-util.h>
 
 #include "e-contacts-selector.h"
 #include "e-source-contacts.h"
diff --git a/modules/cal-config-google/Makefile.am b/modules/cal-config-google/Makefile.am
index 6467c43..3477c61 100644
--- a/modules/cal-config-google/Makefile.am
+++ b/modules/cal-config-google/Makefile.am
@@ -3,10 +3,11 @@ module_LTLIBRARIES = module-cal-config-google.la
 module_cal_config_google_la_CPPFLAGS = \
 	$(AM_CPPFLAGS)						\
 	-I$(top_srcdir)						\
-	-I$(top_srcdir)/widgets					\
 	-DG_LOG_DOMAIN=\"evolution-cal-config-google\"		\
 	$(EVOLUTION_DATA_SERVER_CFLAGS)				\
 	$(GNOME_PLATFORM_CFLAGS)				\
+	$(CHAMPLAIN_CFLAGS)					\
+	$(GTKHTML_CFLAGS)					\
 	$(GDATA_CFLAGS)
 
 module_cal_config_google_la_SOURCES = \
@@ -20,9 +21,10 @@ module_cal_config_google_la_SOURCES = \
 
 module_cal_config_google_la_LIBADD = \
 	$(top_builddir)/e-util/libeutil.la			\
-	$(top_builddir)/widgets/misc/libemiscwidgets.la		\
 	$(EVOLUTION_DATA_SERVER_LIBS)				\
 	$(GNOME_PLATFORM_LIBS)					\
+	$(CHAMPLAIN_LIBS)					\
+	$(GTKHTML_LIBS)						\
 	$(GDATA_LIBS)
 
 module_cal_config_google_la_LDFLAGS = \
diff --git a/modules/cal-config-google/e-google-chooser.c b/modules/cal-config-google/e-google-chooser.c
index 9dd2b32..d03c5dc 100644
--- a/modules/cal-config-google/e-google-chooser.c
+++ b/modules/cal-config-google/e-google-chooser.c
@@ -23,7 +23,7 @@
 #include <gdata/gdata.h>
 #include <glib/gi18n-lib.h>
 
-#include <libedataserverui/libedataserverui.h>
+#include <e-util/e-util.h>
 
 #define E_GOOGLE_CHOOSER_GET_PRIVATE(obj) \
 	(G_TYPE_INSTANCE_GET_PRIVATE \
diff --git a/modules/cal-config-google/evolution-cal-config-google.c b/modules/cal-config-google/evolution-cal-config-google.c
index 2447e70..97b4c7e 100644
--- a/modules/cal-config-google/evolution-cal-config-google.c
+++ b/modules/cal-config-google/evolution-cal-config-google.c
@@ -21,8 +21,7 @@
 
 #include <libebackend/libebackend.h>
 
-#include <misc/e-cal-source-config.h>
-#include <misc/e-source-config-backend.h>
+#include <e-util/e-util.h>
 
 #include "e-google-chooser-button.h"
 #include "e-google-chooser-dialog.h"
diff --git a/modules/cal-config-local/Makefile.am b/modules/cal-config-local/Makefile.am
index 477d31b..84a478d 100644
--- a/modules/cal-config-local/Makefile.am
+++ b/modules/cal-config-local/Makefile.am
@@ -3,10 +3,11 @@ module_LTLIBRARIES = module-cal-config-local.la
 module_cal_config_local_la_CPPFLAGS = \
 	$(AM_CPPFLAGS)						\
 	-I$(top_srcdir)						\
-	-I$(top_srcdir)/widgets					\
 	-DG_LOG_DOMAIN=\"evolution-cal-config-local\"		\
 	$(EVOLUTION_DATA_SERVER_CFLAGS)				\
-	$(GNOME_PLATFORM_CFLAGS)
+	$(GNOME_PLATFORM_CFLAGS)				\
+	$(CHAMPLAIN_CFLAGS)					\
+	$(GTKHTML_CFLAGS)
 
 module_cal_config_local_la_SOURCES = \
 	evolution-cal-config-local.c				\
@@ -15,9 +16,10 @@ module_cal_config_local_la_SOURCES = \
 
 module_cal_config_local_la_LIBADD = \
 	$(top_builddir)/e-util/libeutil.la			\
-	$(top_builddir)/widgets/misc/libemiscwidgets.la		\
 	$(EVOLUTION_DATA_SERVER_LIBS)				\
-	$(GNOME_PLATFORM_LIBS)
+	$(GNOME_PLATFORM_LIBS)					\
+	$(CHAMPLAIN_LIBS)					\
+	$(GTKHTML_LIBS)
 
 module_cal_config_local_la_LDFLAGS = \
 	-module -avoid-version $(NO_UNDEFINED)
diff --git a/modules/cal-config-local/evolution-cal-config-local.c b/modules/cal-config-local/evolution-cal-config-local.c
index 2e1200a..13b066e 100644
--- a/modules/cal-config-local/evolution-cal-config-local.c
+++ b/modules/cal-config-local/evolution-cal-config-local.c
@@ -21,8 +21,7 @@
 
 #include <libebackend/libebackend.h>
 
-#include <misc/e-cal-source-config.h>
-#include <misc/e-source-config-backend.h>
+#include <e-util/e-util.h>
 
 #include "e-source-local.h"
 
diff --git a/modules/cal-config-weather/Makefile.am b/modules/cal-config-weather/Makefile.am
index 7276a9d..ef72e26 100644
--- a/modules/cal-config-weather/Makefile.am
+++ b/modules/cal-config-weather/Makefile.am
@@ -3,10 +3,11 @@ module_LTLIBRARIES = module-cal-config-weather.la
 module_cal_config_weather_la_CPPFLAGS = \
 	$(AM_CPPFLAGS)						\
 	-I$(top_srcdir)						\
-	-I$(top_srcdir)/widgets					\
 	-DG_LOG_DOMAIN=\"evolution-cal-config-weather\"		\
 	$(EVOLUTION_DATA_SERVER_CFLAGS)				\
 	$(GNOME_PLATFORM_CFLAGS)				\
+	$(CHAMPLAIN_CFLAGS)					\
+	$(GTKHTML_CFLAGS)					\
 	$(GWEATHER_CFLAGS)
 
 module_cal_config_weather_la_SOURCES = \
@@ -16,9 +17,10 @@ module_cal_config_weather_la_SOURCES = \
 
 module_cal_config_weather_la_LIBADD = \
 	$(top_builddir)/e-util/libeutil.la			\
-	$(top_builddir)/widgets/misc/libemiscwidgets.la		\
 	$(EVOLUTION_DATA_SERVER_LIBS)				\
 	$(GNOME_PLATFORM_LIBS)					\
+	$(CHAMPLAIN_LIBS)					\
+	$(GTKHTML_LIBS)						\
 	$(GWEATHER_LIBS)
 
 module_cal_config_weather_la_LDFLAGS = \
diff --git a/modules/cal-config-weather/evolution-cal-config-weather.c b/modules/cal-config-weather/evolution-cal-config-weather.c
index d5a4e2c..2e5532e 100644
--- a/modules/cal-config-weather/evolution-cal-config-weather.c
+++ b/modules/cal-config-weather/evolution-cal-config-weather.c
@@ -25,8 +25,7 @@
 #include <libgweather/location-entry.h>
 #undef GWEATHER_I_KNOW_THIS_IS_UNSTABLE
 
-#include <misc/e-cal-source-config.h>
-#include <misc/e-source-config-backend.h>
+#include <e-util/e-util.h>
 
 #include "e-source-weather.h"
 
diff --git a/modules/cal-config-webcal/Makefile.am b/modules/cal-config-webcal/Makefile.am
index 29f9cf2..aedc04b 100644
--- a/modules/cal-config-webcal/Makefile.am
+++ b/modules/cal-config-webcal/Makefile.am
@@ -3,19 +3,21 @@ module_LTLIBRARIES = module-cal-config-webcal.la
 module_cal_config_webcal_la_CPPFLAGS = \
 	$(AM_CPPFLAGS)						\
 	-I$(top_srcdir)						\
-	-I$(top_srcdir)/widgets					\
 	-DG_LOG_DOMAIN=\"evolution-cal-config-webcal\"		\
 	$(EVOLUTION_DATA_SERVER_CFLAGS)				\
-	$(GNOME_PLATFORM_CFLAGS)
+	$(GNOME_PLATFORM_CFLAGS)				\
+	$(CHAMPLAIN_CFLAGS)					\
+	$(GTKHTML_CFLAGS)
 
 module_cal_config_webcal_la_SOURCES = \
 	evolution-cal-config-webcal.c
 
 module_cal_config_webcal_la_LIBADD = \
 	$(top_builddir)/e-util/libeutil.la			\
-	$(top_builddir)/widgets/misc/libemiscwidgets.la		\
 	$(EVOLUTION_DATA_SERVER_LIBS)				\
-	$(GNOME_PLATFORM_LIBS)
+	$(GNOME_PLATFORM_LIBS)					\
+	$(CHAMPLAIN_LIBS)					\
+	$(GTKHTML_LIBS)
 
 module_cal_config_webcal_la_LDFLAGS = \
 	-module -avoid-version $(NO_UNDEFINED)
diff --git a/modules/cal-config-webcal/evolution-cal-config-webcal.c b/modules/cal-config-webcal/evolution-cal-config-webcal.c
index 70eb06c..82a40ee 100644
--- a/modules/cal-config-webcal/evolution-cal-config-webcal.c
+++ b/modules/cal-config-webcal/evolution-cal-config-webcal.c
@@ -21,9 +21,7 @@
 
 #include <libebackend/libebackend.h>
 
-#include <misc/e-cal-source-config.h>
-#include <misc/e-interval-chooser.h>
-#include <misc/e-source-config-backend.h>
+#include <e-util/e-util.h>
 
 typedef ESourceConfigBackend ECalConfigWebcal;
 typedef ESourceConfigBackendClass ECalConfigWebcalClass;
diff --git a/modules/calendar/Makefile.am b/modules/calendar/Makefile.am
index c5e5475..7425f0d 100644
--- a/modules/calendar/Makefile.am
+++ b/modules/calendar/Makefile.am
@@ -6,10 +6,10 @@ module_calendar_la_CPPFLAGS = \
 	-DEVOLUTION_BINDIR=\""$(bindir)"\"		\
 	-DEVOLUTION_PRIVLIBEXECDIR=\""$(PRIVLIBEXECDIR)"\" \
 	-I$(top_srcdir)					\
-	-I$(top_srcdir)/widgets				\
 	-DEVOLUTION_ETSPECDIR=\""$(etspecdir)"\"	\
 	$(EVOLUTION_DATA_SERVER_CFLAGS)			\
 	$(GNOME_PLATFORM_CFLAGS)			\
+	$(CHAMPLAIN_CFLAGS)				\
 	$(GTKHTML_CFLAGS)
 
 module_calendar_la_SOURCES = \
@@ -90,15 +90,10 @@ module_calendar_la_LIBADD = \
 	$(top_builddir)/mail/libevolution-mail.la \
 	$(top_builddir)/addressbook/gui/contact-editor/libecontacteditor.la	\
 	$(top_builddir)/addressbook/gui/contact-list-editor/libecontactlisteditor.la \
-	$(top_builddir)/libemail-utils/libemail-utils.la  \
 	$(top_builddir)/e-util/libeutil.la				\
-	$(top_builddir)/filter/libfilter.la				\
-	$(top_builddir)/widgets/menus/libmenus.la			\
-	$(top_builddir)/widgets/misc/libemiscwidgets.la			\
-	$(top_builddir)/widgets/table/libetable.la			\
-	$(top_builddir)/libevolution-utils/libevolution-utils.la	\
 	$(EVOLUTION_DATA_SERVER_LIBS)					\
 	$(GNOME_PLATFORM_LIBS)						\
+	$(CHAMPLAIN_LIBS)						\
 	$(GTKHTML_LIBS)
 
 module_calendar_la_LDFLAGS = \
diff --git a/modules/calendar/e-cal-attachment-handler.c b/modules/calendar/e-cal-attachment-handler.c
index efe087d..372b826 100644
--- a/modules/calendar/e-cal-attachment-handler.c
+++ b/modules/calendar/e-cal-attachment-handler.c
@@ -29,7 +29,6 @@
 #include <libical/ical.h>
 #include <camel/camel.h>
 #include <libecal/libecal.h>
-#include <libedataserverui/libedataserverui.h>
 
 #include <shell/e-shell.h>
 
diff --git a/modules/calendar/e-cal-attachment-handler.h b/modules/calendar/e-cal-attachment-handler.h
index b792fbf..549199e 100644
--- a/modules/calendar/e-cal-attachment-handler.h
+++ b/modules/calendar/e-cal-attachment-handler.h
@@ -22,7 +22,7 @@
 #ifndef E_CAL_ATTACHMENT_HANDLER_H
 #define E_CAL_ATTACHMENT_HANDLER_H
 
-#include <misc/e-attachment-handler.h>
+#include <e-util/e-util.h>
 
 /* Standard GObject macros */
 #define E_TYPE_CAL_ATTACHMENT_HANDLER \
diff --git a/modules/calendar/e-cal-config-calendar-item.c b/modules/calendar/e-cal-config-calendar-item.c
index 1efc8a9..ddf6835 100644
--- a/modules/calendar/e-cal-config-calendar-item.c
+++ b/modules/calendar/e-cal-config-calendar-item.c
@@ -23,7 +23,6 @@
 #include "e-cal-config-calendar-item.h"
 
 #include <shell/e-shell.h>
-#include <misc/e-calendar-item.h>
 
 #define E_CAL_CONFIG_CALENDAR_ITEM_GET_PRIVATE(obj) \
 	(G_TYPE_INSTANCE_GET_PRIVATE \
diff --git a/modules/calendar/e-cal-config-date-edit.c b/modules/calendar/e-cal-config-date-edit.c
index 3f28520..0ac7e6e 100644
--- a/modules/calendar/e-cal-config-date-edit.c
+++ b/modules/calendar/e-cal-config-date-edit.c
@@ -23,7 +23,6 @@
 #include "e-cal-config-date-edit.h"
 
 #include <shell/e-shell.h>
-#include <misc/e-dateedit.h>
 
 #define E_CAL_CONFIG_DATE_EDIT_GET_PRIVATE(obj) \
 	(G_TYPE_INSTANCE_GET_PRIVATE \
diff --git a/modules/calendar/e-cal-config-hook.c b/modules/calendar/e-cal-config-hook.c
index 80050fc..57fb8b8 100644
--- a/modules/calendar/e-cal-config-hook.c
+++ b/modules/calendar/e-cal-config-hook.c
@@ -25,7 +25,6 @@
 
 #include "e-cal-config-hook.h"
 
-#include "e-util/e-config.h"
 #include "calendar/gui/e-cal-config.h"
 
 static const EConfigHookTargetMask no_masks[] = {
diff --git a/modules/calendar/e-cal-event-hook.c b/modules/calendar/e-cal-event-hook.c
index 67db069..9f38e70 100644
--- a/modules/calendar/e-cal-event-hook.c
+++ b/modules/calendar/e-cal-event-hook.c
@@ -25,7 +25,6 @@
 
 #include "e-cal-event-hook.h"
 
-#include "e-util/e-event.h"
 #include "calendar/gui/e-cal-event.h"
 
 static const EEventHookTargetMask masks[] = {
diff --git a/modules/calendar/e-cal-shell-backend.c b/modules/calendar/e-cal-shell-backend.c
index 84193d9..4768319 100644
--- a/modules/calendar/e-cal-shell-backend.c
+++ b/modules/calendar/e-cal-shell-backend.c
@@ -28,15 +28,10 @@
 #include <string.h>
 #include <glib/gi18n.h>
 #include <libecal/libecal.h>
-#include <libedataserverui/libedataserverui.h>
 
-#include "e-util/e-import.h"
 #include "shell/e-shell.h"
 #include "shell/e-shell-backend.h"
 #include "shell/e-shell-window.h"
-#include "widgets/misc/e-cal-source-config.h"
-#include "widgets/misc/e-preferences-window.h"
-#include "widgets/misc/e-source-config-dialog.h"
 
 #include "calendar/gui/comp-util.h"
 #include "calendar/gui/dialogs/event-editor.h"
diff --git a/modules/calendar/e-cal-shell-content.c b/modules/calendar/e-cal-shell-content.c
index 5d8e7e6..66ea4a4 100644
--- a/modules/calendar/e-cal-shell-content.c
+++ b/modules/calendar/e-cal-shell-content.c
@@ -28,10 +28,6 @@
 #include <string.h>
 #include <glib/gi18n.h>
 
-#include "widgets/menus/gal-view-etable.h"
-#include "widgets/misc/e-paned.h"
-#include "widgets/misc/e-selectable.h"
-
 #include "calendar/gui/calendar-config.h"
 #include "calendar/gui/calendar-view.h"
 #include "calendar/gui/e-cal-list-view.h"
diff --git a/modules/calendar/e-cal-shell-content.h b/modules/calendar/e-cal-shell-content.h
index 21e971b..45aedf9 100644
--- a/modules/calendar/e-cal-shell-content.h
+++ b/modules/calendar/e-cal-shell-content.h
@@ -29,7 +29,6 @@
 #include <calendar/gui/e-memo-table.h>
 #include <calendar/gui/e-task-table.h>
 #include <calendar/gui/gnome-cal.h>
-#include <menus/gal-view-instance.h>
 
 /* Standard GObject macros */
 #define E_TYPE_CAL_SHELL_CONTENT \
diff --git a/modules/calendar/e-cal-shell-sidebar.c b/modules/calendar/e-cal-shell-sidebar.c
index 809ccb5..7555950 100644
--- a/modules/calendar/e-cal-shell-sidebar.c
+++ b/modules/calendar/e-cal-shell-sidebar.c
@@ -27,10 +27,6 @@
 
 #include <string.h>
 #include <glib/gi18n.h>
-#include <libedataserverui/libedataserverui.h>
-
-#include "libevolution-utils/e-alert-dialog.h"
-#include "widgets/misc/e-paned.h"
 
 #include "calendar/gui/e-calendar-selector.h"
 #include "calendar/gui/misc.h"
diff --git a/modules/calendar/e-cal-shell-sidebar.h b/modules/calendar/e-cal-shell-sidebar.h
index 4a4ab6d..6daabe2 100644
--- a/modules/calendar/e-cal-shell-sidebar.h
+++ b/modules/calendar/e-cal-shell-sidebar.h
@@ -23,11 +23,9 @@
 #define E_CAL_SHELL_SIDEBAR_H
 
 #include <libecal/libecal.h>
-#include <libedataserverui/libedataserverui.h>
 
 #include <shell/e-shell-sidebar.h>
 #include <shell/e-shell-view.h>
-#include <misc/e-calendar.h>
 
 /* Standard GObject macros */
 #define E_TYPE_CAL_SHELL_SIDEBAR \
diff --git a/modules/calendar/e-cal-shell-view-actions.c b/modules/calendar/e-cal-shell-view-actions.c
index f72e765..f3523c9 100644
--- a/modules/calendar/e-cal-shell-view-actions.c
+++ b/modules/calendar/e-cal-shell-view-actions.c
@@ -23,7 +23,6 @@
 #include <config.h>
 #endif
 
-#include "libevolution-utils/e-alert-dialog.h"
 #include "e-cal-shell-view-private.h"
 
 /* This is for radio action groups whose value is persistent.  We
diff --git a/modules/calendar/e-cal-shell-view-private.c b/modules/calendar/e-cal-shell-view-private.c
index 8159e3d..d62b977b 100644
--- a/modules/calendar/e-cal-shell-view-private.c
+++ b/modules/calendar/e-cal-shell-view-private.c
@@ -28,7 +28,6 @@
 #include "e-cal-shell-view-private.h"
 
 #include "calendar/gui/calendar-view-factory.h"
-#include "widgets/menus/gal-view-factory-etable.h"
 
 #define CHECK_NB	5
 
diff --git a/modules/calendar/e-cal-shell-view-private.h b/modules/calendar/e-cal-shell-view-private.h
index 9764138..b589907 100644
--- a/modules/calendar/e-cal-shell-view-private.h
+++ b/modules/calendar/e-cal-shell-view-private.h
@@ -28,20 +28,9 @@
 #include <glib/gi18n.h>
 
 #include <libecal/libecal.h>
-#include <libedataserverui/libedataserverui.h>
-
-#include <e-util/e-selection.h>
-#include <e-util/e-dialog-utils.h>
-#include <e-util/e-file-utils.h>
-#include <e-util/e-util.h>
 
 #include <shell/e-shell-utils.h>
 
-#include <misc/e-cal-source-config.h>
-#include <misc/e-popup-action.h>
-#include <misc/e-selectable.h>
-#include <misc/e-source-config-dialog.h>
-
 #include <calendar/gui/calendar-config.h>
 #include <calendar/gui/comp-util.h>
 #include <calendar/gui/e-cal-list-view.h>
diff --git a/modules/calendar/e-calendar-preferences.c b/modules/calendar/e-calendar-preferences.c
index b11a943..2902a50 100644
--- a/modules/calendar/e-calendar-preferences.c
+++ b/modules/calendar/e-calendar-preferences.c
@@ -34,12 +34,6 @@
 #include "calendar/gui/e-cal-config.h"
 #include "calendar/gui/e-timezone-entry.h"
 #include "calendar/gui/calendar-config.h"
-#include "widgets/misc/e-alarm-selector.h"
-#include "widgets/misc/e-dateedit.h"
-#include "e-util/e-util.h"
-#include "e-util/e-datetime-format.h"
-#include "e-util/e-dialog-widgets.h"
-#include "e-util/e-util-private.h"
 #include "shell/e-shell-utils.h"
 
 /* same is used for Birthdays & Anniversaries calendar */
diff --git a/modules/calendar/e-calendar-preferences.h b/modules/calendar/e-calendar-preferences.h
index e7b9543..d1e8ede 100644
--- a/modules/calendar/e-calendar-preferences.h
+++ b/modules/calendar/e-calendar-preferences.h
@@ -24,10 +24,7 @@
 #ifndef CAL_PREFERENCES_H
 #define CAL_PREFERENCES_H
 
-#include <libedataserverui/libedataserverui.h>
-
 #include <shell/e-shell.h>
-#include <widgets/misc/e-preferences-window.h>
 
 /* Standard GObject macros */
 #define E_TYPE_CALENDAR_PREFERENCES \
diff --git a/modules/calendar/e-memo-shell-backend.c b/modules/calendar/e-memo-shell-backend.c
index fa4884b..75fabd6 100644
--- a/modules/calendar/e-memo-shell-backend.c
+++ b/modules/calendar/e-memo-shell-backend.c
@@ -28,13 +28,10 @@
 #include <string.h>
 #include <glib/gi18n.h>
 #include <libecal/libecal.h>
-#include <libedataserverui/libedataserverui.h>
 
 #include "shell/e-shell.h"
 #include "shell/e-shell-backend.h"
 #include "shell/e-shell-window.h"
-#include "widgets/misc/e-cal-source-config.h"
-#include "widgets/misc/e-source-config-dialog.h"
 
 #include "calendar/gui/comp-util.h"
 #include "calendar/gui/dialogs/memo-editor.h"
diff --git a/modules/calendar/e-memo-shell-content.c b/modules/calendar/e-memo-shell-content.c
index bb92852..abb1ada 100644
--- a/modules/calendar/e-memo-shell-content.c
+++ b/modules/calendar/e-memo-shell-content.c
@@ -27,11 +27,7 @@
 
 #include <glib/gi18n.h>
 
-#include "e-util/e-selection.h"
 #include "shell/e-shell-utils.h"
-#include "widgets/menus/gal-view-etable.h"
-#include "widgets/misc/e-paned.h"
-#include "widgets/misc/e-preview-pane.h"
 
 #include "calendar/gui/comp-util.h"
 #include "calendar/gui/e-cal-component-preview.h"
diff --git a/modules/calendar/e-memo-shell-content.h b/modules/calendar/e-memo-shell-content.h
index 18ee590..03a7fdc 100644
--- a/modules/calendar/e-memo-shell-content.h
+++ b/modules/calendar/e-memo-shell-content.h
@@ -28,9 +28,6 @@
 
 #include <calendar/gui/e-memo-table.h>
 
-#include <menus/gal-view-instance.h>
-#include <misc/e-preview-pane.h>
-
 /* Standard GObject macros */
 #define E_TYPE_MEMO_SHELL_CONTENT \
 	(e_memo_shell_content_get_type ())
diff --git a/modules/calendar/e-memo-shell-sidebar.c b/modules/calendar/e-memo-shell-sidebar.c
index 7d46384..64e7466 100644
--- a/modules/calendar/e-memo-shell-sidebar.c
+++ b/modules/calendar/e-memo-shell-sidebar.c
@@ -27,9 +27,7 @@
 
 #include <string.h>
 #include <glib/gi18n.h>
-#include <libedataserverui/libedataserverui.h>
 
-#include "libevolution-utils/e-alert-dialog.h"
 #include "e-util/e-util.h"
 #include "calendar/gui/e-memo-list-selector.h"
 #include "calendar/gui/misc.h"
diff --git a/modules/calendar/e-memo-shell-sidebar.h b/modules/calendar/e-memo-shell-sidebar.h
index ed9e051..17814eb 100644
--- a/modules/calendar/e-memo-shell-sidebar.h
+++ b/modules/calendar/e-memo-shell-sidebar.h
@@ -23,7 +23,6 @@
 #define E_MEMO_SHELL_SIDEBAR_H
 
 #include <libecal/libecal.h>
-#include <libedataserverui/libedataserverui.h>
 
 #include <shell/e-shell-sidebar.h>
 #include <shell/e-shell-view.h>
diff --git a/modules/calendar/e-memo-shell-view-actions.c b/modules/calendar/e-memo-shell-view-actions.c
index f1ae7ff..0e0d28e 100644
--- a/modules/calendar/e-memo-shell-view-actions.c
+++ b/modules/calendar/e-memo-shell-view-actions.c
@@ -23,7 +23,6 @@
 #include <config.h>
 #endif
 
-#include "libevolution-utils/e-alert-dialog.h"
 #include "e-memo-shell-view-private.h"
 
 static void
diff --git a/modules/calendar/e-memo-shell-view-private.c b/modules/calendar/e-memo-shell-view-private.c
index 9f31e44..27ffad3 100644
--- a/modules/calendar/e-memo-shell-view-private.c
+++ b/modules/calendar/e-memo-shell-view-private.c
@@ -27,8 +27,6 @@
 
 #include "e-memo-shell-view-private.h"
 
-#include "widgets/menus/gal-view-factory-etable.h"
-
 static void
 memo_shell_view_model_row_appended_cb (EMemoShellView *memo_shell_view,
                                        ECalModel *model)
diff --git a/modules/calendar/e-memo-shell-view-private.h b/modules/calendar/e-memo-shell-view-private.h
index 4e65d00..0e0a2da 100644
--- a/modules/calendar/e-memo-shell-view-private.h
+++ b/modules/calendar/e-memo-shell-view-private.h
@@ -27,14 +27,7 @@
 #include <string.h>
 #include <glib/gi18n.h>
 
-#include "e-util/e-dialog-utils.h"
-#include "e-util/e-file-utils.h"
-#include "e-util/e-util.h"
 #include "shell/e-shell-utils.h"
-#include "misc/e-cal-source-config.h"
-#include "misc/e-popup-action.h"
-#include "misc/e-selectable.h"
-#include "misc/e-source-config-dialog.h"
 
 #include "calendar/gui/comp-util.h"
 #include "calendar/gui/e-cal-component-preview.h"
diff --git a/modules/calendar/e-task-shell-backend.c b/modules/calendar/e-task-shell-backend.c
index 4d76e50..90ffb3c 100644
--- a/modules/calendar/e-task-shell-backend.c
+++ b/modules/calendar/e-task-shell-backend.c
@@ -28,13 +28,10 @@
 #include <string.h>
 #include <glib/gi18n.h>
 #include <libecal/libecal.h>
-#include <libedataserverui/libedataserverui.h>
 
 #include "shell/e-shell.h"
 #include "shell/e-shell-backend.h"
 #include "shell/e-shell-window.h"
-#include "widgets/misc/e-cal-source-config.h"
-#include "widgets/misc/e-source-config-dialog.h"
 
 #include "calendar/gui/comp-util.h"
 #include "calendar/gui/dialogs/task-editor.h"
diff --git a/modules/calendar/e-task-shell-content.c b/modules/calendar/e-task-shell-content.c
index 89d4bde..07d11a4 100644
--- a/modules/calendar/e-task-shell-content.c
+++ b/modules/calendar/e-task-shell-content.c
@@ -27,11 +27,7 @@
 
 #include <glib/gi18n.h>
 
-#include "e-util/e-selection.h"
 #include "shell/e-shell-utils.h"
-#include "widgets/menus/gal-view-etable.h"
-#include "widgets/misc/e-paned.h"
-#include "widgets/misc/e-preview-pane.h"
 
 #include "calendar/gui/comp-util.h"
 #include "calendar/gui/e-cal-component-preview.h"
diff --git a/modules/calendar/e-task-shell-content.h b/modules/calendar/e-task-shell-content.h
index 84a5f67..778dbde 100644
--- a/modules/calendar/e-task-shell-content.h
+++ b/modules/calendar/e-task-shell-content.h
@@ -29,9 +29,6 @@
 #include <calendar/gui/e-cal-model.h>
 #include <calendar/gui/e-task-table.h>
 
-#include <menus/gal-view-instance.h>
-#include <misc/e-preview-pane.h>
-
 /* Standard GObject macros */
 #define E_TYPE_TASK_SHELL_CONTENT \
 	(e_task_shell_content_get_type ())
diff --git a/modules/calendar/e-task-shell-sidebar.c b/modules/calendar/e-task-shell-sidebar.c
index d88d5e7..1f46e29 100644
--- a/modules/calendar/e-task-shell-sidebar.c
+++ b/modules/calendar/e-task-shell-sidebar.c
@@ -27,9 +27,7 @@
 
 #include <string.h>
 #include <glib/gi18n.h>
-#include <libedataserverui/libedataserverui.h>
 
-#include "libevolution-utils/e-alert-dialog.h"
 #include "e-util/e-util.h"
 #include "calendar/gui/e-task-list-selector.h"
 #include "calendar/gui/misc.h"
diff --git a/modules/calendar/e-task-shell-sidebar.h b/modules/calendar/e-task-shell-sidebar.h
index d3c5358..d9a143e 100644
--- a/modules/calendar/e-task-shell-sidebar.h
+++ b/modules/calendar/e-task-shell-sidebar.h
@@ -23,7 +23,6 @@
 #define E_TASK_SHELL_SIDEBAR_H
 
 #include <libecal/libecal.h>
-#include <libedataserverui/libedataserverui.h>
 
 #include <shell/e-shell-sidebar.h>
 #include <shell/e-shell-view.h>
diff --git a/modules/calendar/e-task-shell-view-actions.c b/modules/calendar/e-task-shell-view-actions.c
index 21043fb..6709a6e 100644
--- a/modules/calendar/e-task-shell-view-actions.c
+++ b/modules/calendar/e-task-shell-view-actions.c
@@ -23,7 +23,6 @@
 #include <config.h>
 #endif
 
-#include "libevolution-utils/e-alert-dialog.h"
 #include "e-task-shell-view-private.h"
 
 static void
diff --git a/modules/calendar/e-task-shell-view-private.c b/modules/calendar/e-task-shell-view-private.c
index ba66c59..816b853 100644
--- a/modules/calendar/e-task-shell-view-private.c
+++ b/modules/calendar/e-task-shell-view-private.c
@@ -25,8 +25,6 @@
 
 #include "e-task-shell-view-private.h"
 
-#include "widgets/menus/gal-view-factory-etable.h"
-
 #include "e-util/e-util-private.h"
 
 static void
diff --git a/modules/calendar/e-task-shell-view-private.h b/modules/calendar/e-task-shell-view-private.h
index 062ac45..45bf20b 100644
--- a/modules/calendar/e-task-shell-view-private.h
+++ b/modules/calendar/e-task-shell-view-private.h
@@ -27,16 +27,8 @@
 #include <string.h>
 #include <glib/gi18n.h>
 #include <libecal/libecal.h>
-#include <libedataserverui/libedataserverui.h>
 
-#include "e-util/e-dialog-utils.h"
-#include "e-util/e-file-utils.h"
-#include "e-util/e-util.h"
 #include "shell/e-shell-utils.h"
-#include "misc/e-cal-source-config.h"
-#include "misc/e-popup-action.h"
-#include "misc/e-selectable.h"
-#include "misc/e-source-config-dialog.h"
 
 #include "calendar/gui/calendar-config.h"
 #include "calendar/gui/comp-util.h"
diff --git a/modules/composer-autosave/Makefile.am b/modules/composer-autosave/Makefile.am
index 3f62e4c..b1175a5 100644
--- a/modules/composer-autosave/Makefile.am
+++ b/modules/composer-autosave/Makefile.am
@@ -3,10 +3,10 @@ module_LTLIBRARIES = module-composer-autosave.la
 module_composer_autosave_la_CPPFLAGS = \
 	$(AM_CPPFLAGS)						\
 	-I$(top_srcdir)						\
-	-I$(top_srcdir)/widgets					\
 	-DG_LOG_DOMAIN=\"evolution-composer-autosave\"		\
 	$(EVOLUTION_DATA_SERVER_CFLAGS)				\
 	$(GNOME_PLATFORM_CFLAGS)				\
+	$(CHAMPLAIN_CFLAGS)					\
 	$(GTKHTML_CFLAGS)
 
 module_composer_autosave_la_SOURCES = \
@@ -19,11 +19,10 @@ module_composer_autosave_la_SOURCES = \
 module_composer_autosave_la_LIBADD = \
 	$(top_builddir)/shell/libeshell.la			\
 	$(top_builddir)/composer/libcomposer.la			\
-	$(top_builddir)/widgets/misc/libemiscwidgets.la		\
 	$(top_builddir)/e-util/libeutil.la			\
-	$(top_builddir)/libevolution-utils/libevolution-utils.la \
 	$(EVOLUTION_DATA_SERVER_LIBS)				\
 	$(GNOME_PLATFORM_LIBS)					\
+	$(CHAMPLAIN_LIBS)					\
 	$(GTKHTML_LIBS)
 
 module_composer_autosave_la_LDFLAGS = \
diff --git a/modules/composer-autosave/e-composer-autosave.c b/modules/composer-autosave/e-composer-autosave.c
index e24b164..cf0dd3b 100644
--- a/modules/composer-autosave/e-composer-autosave.c
+++ b/modules/composer-autosave/e-composer-autosave.c
@@ -22,7 +22,6 @@
 
 #include <libebackend/libebackend.h>
 
-#include <libevolution-utils/e-alert-dialog.h>
 #include <composer/e-msg-composer.h>
 
 #include "e-autosave-utils.h"
diff --git a/modules/composer-autosave/e-composer-registry.c b/modules/composer-autosave/e-composer-registry.c
index 76736c8..bd4bb93 100644
--- a/modules/composer-autosave/e-composer-registry.c
+++ b/modules/composer-autosave/e-composer-registry.c
@@ -25,7 +25,6 @@
 
 #include <shell/e-shell.h>
 #include <shell/e-shell-window.h>
-#include <libevolution-utils/e-alert-dialog.h>
 #include <composer/e-msg-composer.h>
 
 #include "e-autosave-utils.h"
diff --git a/modules/imap-features/Makefile.am b/modules/imap-features/Makefile.am
index d49ddfa..b88161c 100644
--- a/modules/imap-features/Makefile.am
+++ b/modules/imap-features/Makefile.am
@@ -5,10 +5,11 @@ module_LTLIBRARIES = module-imap-features.la
 module_imap_features_la_CPPFLAGS = \
 	$(AM_CPPFLAGS)						\
 	-I$(top_srcdir)						\
-	-I$(top_srcdir)/widgets					\
 	-DG_LOG_DOMAIN=\"evolution-imap-features\"		\
 	$(EVOLUTION_DATA_SERVER_CFLAGS)				\
 	$(GNOME_PLATFORM_CFLAGS)				\
+	$(CHAMPLAIN_CFLAGS)					\
+	$(GTKHTML_CFLAGS)					\
 	$(NULL)
 
 module_imap_features_la_SOURCES = \
@@ -21,10 +22,11 @@ module_imap_features_la_SOURCES = \
 
 module_imap_features_la_LIBADD = \
 	$(top_builddir)/mail/libevolution-mail.la		\
-	$(top_builddir)/widgets/misc/libemiscwidgets.la		\
 	$(top_builddir)/libemail-engine/libemail-engine.la	\
 	$(EVOLUTION_DATA_SERVER_LIBS)				\
 	$(GNOME_PLATFORM_LIBS)					\
+	$(CHAMPLAIN_LIBS)					\
+	$(GTKHTML_LIBS)						\
 	$(NULL)
 
 module_imap_features_la_LDFLAGS = \
diff --git a/modules/itip-formatter/Makefile.am b/modules/itip-formatter/Makefile.am
index 76464ca..596cc6a 100644
--- a/modules/itip-formatter/Makefile.am
+++ b/modules/itip-formatter/Makefile.am
@@ -7,11 +7,12 @@ module_LTLIBRARIES = module-itip-formatter.la
 module_itip_formatter_la_CPPFLAGS =					\
 	$(AM_CPPFLAGS)							\
 	-I$(top_srcdir)							\
-	-I$(top_srcdir)/widgets						\
 	-DEVOLUTION_PRIVDATADIR=\""$(privdatadir)"\"			\
 	-DG_LOG_DOMAIN=\"evolution-module-itip-formatter\"		\
 	$(EVOLUTION_DATA_SERVER_CFLAGS)					\
-	$(GNOME_PLATFORM_CFLAGS)
+	$(GNOME_PLATFORM_CFLAGS)					\
+	$(CHAMPLAIN_CFLAGS)						\
+	$(GTKHTML_CFLAGS)
 
 module_itip_formatter_la_SOURCES =					\
 	e-conflict-search-selector.c					\
@@ -33,12 +34,11 @@ module_itip_formatter_la_LIBADD =					\
 	$(top_builddir)/mail/libevolution-mail.la			\
 	$(top_builddir)/shell/libeshell.la				\
 	$(top_builddir)/em-format/libemformat.la			\
-	$(top_builddir)/widgets/misc/libemiscwidgets.la			\
-	$(top_builddir)/libemail-utils/libemail-utils.la		\
 	$(top_builddir)/libemail-engine/libemail-engine.la		\
-	$(top_builddir)/libevolution-utils/libevolution-utils.la	\
 	$(EVOLUTION_DATA_SERVER_LIBS)					\
-	$(GNOME_PLATFORM_LIBS)
+	$(GNOME_PLATFORM_LIBS)						\
+	$(CHAMPLAIN_LIBS)						\
+	$(GTKHTML_LIBS)
 
 module_itip_formatter_la_LDFLAGS =					\
 	-avoid-version -module $(NO_UNDEFINED)
diff --git a/modules/itip-formatter/e-conflict-search-selector.h b/modules/itip-formatter/e-conflict-search-selector.h
index 091e1c9..bcd3ce1 100644
--- a/modules/itip-formatter/e-conflict-search-selector.h
+++ b/modules/itip-formatter/e-conflict-search-selector.h
@@ -19,7 +19,7 @@
 #ifndef E_CONFLICT_SEARCH_SELECTOR_H
 #define E_CONFLICT_SEARCH_SELECTOR_H
 
-#include <libedataserverui/libedataserverui.h>
+#include <e-util/e-util.h>
 
 /* Standard GObject macros */
 #define E_TYPE_CONFLICT_SEARCH_SELECTOR \
diff --git a/modules/itip-formatter/e-mail-parser-itip.c b/modules/itip-formatter/e-mail-parser-itip.c
index d0ab563..9e5ff46 100644
--- a/modules/itip-formatter/e-mail-parser-itip.c
+++ b/modules/itip-formatter/e-mail-parser-itip.c
@@ -30,15 +30,14 @@
 
 #include "e-mail-parser-itip.h"
 
+#include <shell/e-shell.h>
+
 #include <em-format/e-mail-extension-registry.h>
 #include <em-format/e-mail-parser-extension.h>
 #include <em-format/e-mail-part.h>
 
-#include <misc/e-attachment.h>
-
 #include "e-mail-part-itip.h"
 #include "itip-view.h"
-#include <shell/e-shell.h>
 
 #define CONF_KEY_DELETE "delete-processed"
 
diff --git a/modules/itip-formatter/e-mail-part-itip.h b/modules/itip-formatter/e-mail-part-itip.h
index d2e681b..7a9cbd7 100644
--- a/modules/itip-formatter/e-mail-part-itip.h
+++ b/modules/itip-formatter/e-mail-part-itip.h
@@ -22,7 +22,6 @@
 #endif
 
 #include <libecal/libecal.h>
-#include <libedataserverui/libedataserverui.h>
 #include <libebackend/libebackend.h>
 
 #include <em-format/e-mail-part.h>
diff --git a/modules/itip-formatter/itip-view.c b/modules/itip-formatter/itip-view.c
index f155e72..1ee2dff 100644
--- a/modules/itip-formatter/itip-view.c
+++ b/modules/itip-formatter/itip-view.c
@@ -26,23 +26,16 @@
 
 #include <string.h>
 #include <glib/gi18n.h>
-#include <libedataserverui/libedataserverui.h>
-#include <libedataserver/libedataserver.h>
-
-#include <e-util/e-util.h>
-#include <e-util/e-unicode.h>
-#include <calendar/gui/itip-utils.h>
 #include <webkit/webkitdom.h>
-
-#include <libevolution-utils/e-alert-dialog.h>
-#include <e-util/e-mktemp.h>
+#include <libedataserver/libedataserver.h>
 
 #include <shell/e-shell.h>
 #include <shell/e-shell-utils.h>
 
-#include <libemail-utils/mail-mt.h>
+#include <calendar/gui/itip-utils.h>
 
 #include <libemail-engine/mail-folder-cache.h>
+#include <libemail-engine/mail-mt.h>
 #include <libemail-engine/mail-tools.h>
 
 #include <mail/em-config.h>
diff --git a/modules/itip-formatter/plugin/Makefile.am b/modules/itip-formatter/plugin/Makefile.am
index 31c886a..6777219 100644
--- a/modules/itip-formatter/plugin/Makefile.am
+++ b/modules/itip-formatter/plugin/Makefile.am
@@ -6,11 +6,12 @@ plugin_LTLIBRARIES = liborg-gnome-itip-formatter.la
 liborg_gnome_itip_formatter_la_CPPFLAGS =		\
 	$(AM_CPPFLAGS)					\
 	-I$(top_srcdir)					\
-	-I$(top_srcdir)/widgets				\
 	-I$(top_srcdir)/modules/itip-formatter		\
 	-DEVOLUTION_PRIVDATADIR=\""$(privdatadir)"\"	\
 	$(EVOLUTION_DATA_SERVER_CFLAGS)			\
-	$(GNOME_PLATFORM_CFLAGS)
+	$(GNOME_PLATFORM_CFLAGS)			\
+	$(CHAMPLAIN_CFLAGS)				\
+	$(GTKHTML_CFLAGS)
 
 liborg_gnome_itip_formatter_la_SOURCES =		\
 	config-ui.c					\
@@ -27,9 +28,10 @@ liborg_gnome_itip_formatter_la_LIBADD =				\
 	$(top_builddir)/mail/libevolution-mail.la		\
 	$(top_builddir)/shell/libeshell.la			\
 	$(top_builddir)/em-format/libemformat.la		\
-	$(top_builddir)/widgets/misc/libemiscwidgets.la		\
 	$(EVOLUTION_DATA_SERVER_LIBS)				\
-	$(GNOME_PLATFORM_LIBS)
+	$(GNOME_PLATFORM_LIBS)					\
+	$(CHAMPLAIN_LIBS)					\
+	$(GTKHTML_LIBS)
 
 BUILT_SOURCES = $(plugin_DATA)
 
diff --git a/modules/mail-config/Makefile.am b/modules/mail-config/Makefile.am
index 1efd510..e42e3a2 100644
--- a/modules/mail-config/Makefile.am
+++ b/modules/mail-config/Makefile.am
@@ -5,10 +5,11 @@ module_LTLIBRARIES = module-mail-config.la
 module_mail_config_la_CPPFLAGS = \
 	$(AM_CPPFLAGS)						\
 	-I$(top_srcdir)						\
-	-I$(top_srcdir)/widgets					\
 	-DG_LOG_DOMAIN=\"evolution-mail-config\"		\
 	$(EVOLUTION_DATA_SERVER_CFLAGS)				\
 	$(GNOME_PLATFORM_CFLAGS)				\
+	$(CHAMPLAIN_CFLAGS)					\
+	$(GTKHTML_CFLAGS)					\
 	$(NULL)
 
 module_mail_config_la_SOURCES = \
@@ -26,11 +27,13 @@ module_mail_config_la_SOURCES = \
 	$(NULL)
 
 module_mail_config_la_LIBADD = \
+	$(top_builddir)/e-util/libeutil.la			\
 	$(top_builddir)/mail/libevolution-mail.la		\
-	$(top_builddir)/widgets/misc/libemiscwidgets.la		\
 	$(top_builddir)/libemail-engine/libemail-engine.la	\
 	$(EVOLUTION_DATA_SERVER_LIBS)				\
 	$(GNOME_PLATFORM_LIBS)					\
+	$(CHAMPLAIN_LIBS)					\
+	$(GTKHTML_LIBS)						\
 	$(NULL)
 
 module_mail_config_la_LDFLAGS = \
diff --git a/modules/mail-config/e-mail-config-remote-accounts.c b/modules/mail-config/e-mail-config-remote-accounts.c
index 491db02..aac3ede 100644
--- a/modules/mail-config/e-mail-config-remote-accounts.c
+++ b/modules/mail-config/e-mail-config-remote-accounts.c
@@ -22,8 +22,6 @@
 #include <camel/camel.h>
 #include <libebackend/libebackend.h>
 
-#include <misc/e-port-entry.h>
-
 #include <mail/e-mail-config-auth-check.h>
 #include <mail/e-mail-config-service-page.h>
 
diff --git a/modules/mail-config/e-mail-config-smtp-backend.c b/modules/mail-config/e-mail-config-smtp-backend.c
index fe992b3..ac54cd4 100644
--- a/modules/mail-config/e-mail-config-smtp-backend.c
+++ b/modules/mail-config/e-mail-config-smtp-backend.c
@@ -24,8 +24,6 @@
 #include <camel/camel.h>
 #include <libebackend/libebackend.h>
 
-#include <misc/e-port-entry.h>
-
 #include <mail/e-mail-config-auth-check.h>
 #include <mail/e-mail-config-service-page.h>
 
diff --git a/modules/mail/Makefile.am b/modules/mail/Makefile.am
index 97050ae..de1aa7c 100644
--- a/modules/mail/Makefile.am
+++ b/modules/mail/Makefile.am
@@ -3,13 +3,13 @@ module_LTLIBRARIES = module-mail.la
 module_mail_la_CPPFLAGS = \
 	$(AM_CPPFLAGS)							\
 	-I$(top_srcdir)							\
-	-I$(top_srcdir)/widgets						\
 	-DEVOLUTION_ETSPECDIR=\""$(etspecdir)"\"			\
 	-DEVOLUTION_UIDIR=\""$(uidir)"\"				\
 	-DEVOLUTION_PRIVDATADIR=\""$(privdatadir)"\"			\
 	-DG_LOG_DOMAIN=\"evolution-module-mail\"			\
 	$(EVOLUTION_DATA_SERVER_CFLAGS)					\
 	$(GNOME_PLATFORM_CFLAGS)					\
+	$(CHAMPLAIN_CFLAGS)						\
 	$(GTKHTML_CFLAGS)
 
 module_mail_la_SOURCES = \
@@ -52,26 +52,18 @@ module_mail_la_SOURCES = \
 	em-network-prefs.h
 
 module_mail_la_LIBADD = \
-	$(top_builddir)/libemail-utils/libemail-utils.la		\
 	$(top_builddir)/libemail-engine/libemail-engine.la		\
 	$(top_builddir)/e-util/libeutil.la				\
 	$(top_builddir)/em-format/libemformat.la			\
-	$(top_builddir)/filter/libfilter.la				\
 	$(top_builddir)/shell/libeshell.la				\
 	$(top_builddir)/composer/libcomposer.la				\
-	$(top_builddir)/widgets/table/libetable.la			\
-	$(top_builddir)/widgets/text/libetext.la			\
-	$(top_builddir)/widgets/misc/libemiscwidgets.la			\
-	$(top_builddir)/widgets/menus/libmenus.la			\
 	$(top_builddir)/mail/libevolution-mail.la			\
 	$(top_builddir)/mail/importers/libevolution-mail-importers.la	\
 	$(top_builddir)/em-format/libemformat.la			\
-	$(top_builddir)/widgets/menus/libmenus.la			\
-	$(top_builddir)/filter/libfilter.la				\
-	$(top_builddir)/libevolution-utils/libevolution-utils.la	\
 	$(libevolution_mail_settings_la)				\
 	$(EVOLUTION_DATA_SERVER_LIBS)					\
 	$(GNOME_PLATFORM_LIBS)						\
+	$(CHAMPLAIN_LIBS)						\
 	$(GTKHTML_LIBS)
 
 module_mail_la_LDFLAGS = \
diff --git a/modules/mail/e-mail-attachment-handler.c b/modules/mail/e-mail-attachment-handler.c
index 8bc2aad..887ab53 100644
--- a/modules/mail/e-mail-attachment-handler.c
+++ b/modules/mail/e-mail-attachment-handler.c
@@ -27,7 +27,6 @@
 
 #include <glib/gi18n.h>
 
-#include "libevolution-utils/e-alert-dialog.h"
 #include "mail/e-mail-backend.h"
 #include "mail/em-composer-utils.h"
 
diff --git a/modules/mail/e-mail-attachment-handler.h b/modules/mail/e-mail-attachment-handler.h
index 13032b4..52e1b30 100644
--- a/modules/mail/e-mail-attachment-handler.h
+++ b/modules/mail/e-mail-attachment-handler.h
@@ -22,7 +22,7 @@
 #ifndef E_MAIL_ATTACHMENT_HANDLER_H
 #define E_MAIL_ATTACHMENT_HANDLER_H
 
-#include <widgets/misc/e-attachment-handler.h>
+#include <e-util/e-util.h>
 
 /* Standard GObject macros */
 #define E_TYPE_MAIL_ATTACHMENT_HANDLER \
diff --git a/modules/mail/e-mail-config-hook.c b/modules/mail/e-mail-config-hook.c
index cc97346..2dee70e 100644
--- a/modules/mail/e-mail-config-hook.c
+++ b/modules/mail/e-mail-config-hook.c
@@ -25,7 +25,6 @@
 
 #include "e-mail-config-hook.h"
 
-#include "e-util/e-config.h"
 #include "mail/em-config.h"
 
 static const EConfigHookTargetMask no_masks[] = {
diff --git a/modules/mail/e-mail-config-web-view-gtkhtml.c b/modules/mail/e-mail-config-web-view-gtkhtml.c
index 2721d5e..40360d4 100644
--- a/modules/mail/e-mail-config-web-view-gtkhtml.c
+++ b/modules/mail/e-mail-config-web-view-gtkhtml.c
@@ -26,7 +26,6 @@
 #include "e-mail-config-web-view-gtkhtml.h"
 
 #include <shell/e-shell.h>
-#include <misc/e-web-view-gtkhtml.h>
 
 #define E_MAIL_CONFIG_WEB_VIEW_GTKHTML_GET_PRIVATE(obj) \
 	(G_TYPE_INSTANCE_GET_PRIVATE \
diff --git a/modules/mail/e-mail-config-web-view.c b/modules/mail/e-mail-config-web-view.c
index 7a5bc4d..5d2fb92 100644
--- a/modules/mail/e-mail-config-web-view.c
+++ b/modules/mail/e-mail-config-web-view.c
@@ -26,7 +26,6 @@
 #include "e-mail-config-web-view.h"
 
 #include <shell/e-shell.h>
-#include <misc/e-web-view.h>
 
 #define E_MAIL_CONFIG_WEB_VIEW_GET_PRIVATE(obj) \
 	(G_TYPE_INSTANCE_GET_PRIVATE \
diff --git a/modules/mail/e-mail-event-hook.c b/modules/mail/e-mail-event-hook.c
index cd1dc63..376a5e8 100644
--- a/modules/mail/e-mail-event-hook.c
+++ b/modules/mail/e-mail-event-hook.c
@@ -25,7 +25,6 @@
 
 #include "e-mail-event-hook.h"
 
-#include "e-util/e-event.h"
 #include "mail/em-event.h"
 
 static const EEventHookTargetMask folder_masks[] = {
diff --git a/modules/mail/e-mail-shell-backend.c b/modules/mail/e-mail-shell-backend.c
index 9a56c00..1abcb8a 100644
--- a/modules/mail/e-mail-shell-backend.c
+++ b/modules/mail/e-mail-shell-backend.c
@@ -27,17 +27,11 @@
 
 #include <glib/gi18n.h>
 
-#include <e-util/e-import.h>
-#include <e-util/e-util.h>
-
 #include <shell/e-shell.h>
 #include <shell/e-shell-window.h>
 
 #include <composer/e-msg-composer.h>
 
-#include <widgets/misc/e-preferences-window.h>
-#include <widgets/misc/e-web-view.h>
-
 #include <libemail-engine/e-mail-folder-utils.h>
 #include <libemail-engine/e-mail-session.h>
 #include <libemail-engine/mail-config.h>
@@ -858,7 +852,6 @@ e_mail_shell_backend_edit_account (EMailShellBackend *mail_shell_backend,
 
 /******************* Code below here belongs elsewhere. *******************/
 
-#include "filter/e-filter-option.h"
 #include "shell/e-shell-settings.h"
 
 static GSList *
diff --git a/modules/mail/e-mail-shell-backend.h b/modules/mail/e-mail-shell-backend.h
index 5b2da24..7ee68af 100644
--- a/modules/mail/e-mail-shell-backend.h
+++ b/modules/mail/e-mail-shell-backend.h
@@ -22,9 +22,8 @@
 #ifndef E_MAIL_SHELL_BACKEND_H
 #define E_MAIL_SHELL_BACKEND_H
 
+#include <e-util/e-util.h>
 #include <mail/e-mail-backend.h>
-#include <filter/e-filter-element.h>
-#include <filter/e-filter-rule.h>
 
 /* Standard GObject macros */
 #define E_TYPE_MAIL_SHELL_BACKEND \
diff --git a/modules/mail/e-mail-shell-content.c b/modules/mail/e-mail-shell-content.c
index 15d477a..12667c2 100644
--- a/modules/mail/e-mail-shell-content.c
+++ b/modules/mail/e-mail-shell-content.c
@@ -29,12 +29,6 @@
 
 #include <e-util/e-util-private.h>
 
-#include <widgets/menus/gal-view-etable.h>
-#include <widgets/menus/gal-view-instance.h>
-#include <widgets/misc/e-paned.h>
-#include <widgets/misc/e-preview-pane.h>
-#include <widgets/misc/e-search-bar.h>
-
 #include <libemail-engine/mail-ops.h>
 
 #include <mail/e-mail-paned-view.h>
diff --git a/modules/mail/e-mail-shell-view-private.c b/modules/mail/e-mail-shell-view-private.c
index 9752461..cb37b6e 100644
--- a/modules/mail/e-mail-shell-view-private.c
+++ b/modules/mail/e-mail-shell-view-private.c
@@ -25,9 +25,6 @@
 
 #include "e-mail-shell-view-private.h"
 
-#include "widgets/menus/gal-view-factory-etable.h"
-#include "widgets/misc/e-menu-tool-button.h"
-
 #include "e-util/e-util-private.h"
 
 typedef struct _AsyncContext AsyncContext;
diff --git a/modules/mail/e-mail-shell-view-private.h b/modules/mail/e-mail-shell-view-private.h
index 0e9feb2..bd9db02 100644
--- a/modules/mail/e-mail-shell-view-private.h
+++ b/modules/mail/e-mail-shell-view-private.h
@@ -28,11 +28,6 @@
 #include <gtkhtml/gtkhtml.h>
 #include <camel/camel-search-private.h>  /* for camel_search_word */
 
-#include <e-util/e-util.h>
-#include <e-util/e-ui-manager.h>
-
-#include <filter/e-filter-part.h>
-
 #include <libemail-engine/e-mail-folder-utils.h>
 #include <libemail-engine/e-mail-session.h>
 #include <libemail-engine/e-mail-session-utils.h>
@@ -41,10 +36,6 @@
 #include <libemail-engine/mail-ops.h>
 #include <libemail-engine/mail-tools.h>
 
-#include <misc/e-web-view.h>
-#include <misc/e-popup-action.h>
-#include <menus/gal-view-instance.h>
-
 #include <mail/e-mail-label-action.h>
 #include <mail/e-mail-label-dialog.h>
 #include <mail/e-mail-label-list-store.h>
diff --git a/modules/mail/e-mail-shell-view.c b/modules/mail/e-mail-shell-view.c
index 419ad3e..4631dcc 100644
--- a/modules/mail/e-mail-shell-view.c
+++ b/modules/mail/e-mail-shell-view.c
@@ -24,7 +24,6 @@
 #endif
 
 #include "e-mail-shell-view-private.h"
-#include "filter/e-filter-input.h"
 
 static gpointer parent_class;
 static GType mail_shell_view_type;
diff --git a/modules/mail/em-account-prefs.c b/modules/mail/em-account-prefs.c
index 17af8a4..7a45c2d 100644
--- a/modules/mail/em-account-prefs.c
+++ b/modules/mail/em-account-prefs.c
@@ -34,8 +34,6 @@
 
 #include <glib/gi18n.h>
 
-#include <libevolution-utils/e-alert-dialog.h>
-
 #include <shell/e-shell.h>
 
 #include <mail/e-mail-backend.h>
diff --git a/modules/mail/em-account-prefs.h b/modules/mail/em-account-prefs.h
index d2aafdb..f5cae38 100644
--- a/modules/mail/em-account-prefs.h
+++ b/modules/mail/em-account-prefs.h
@@ -23,10 +23,8 @@
 #define EM_ACCOUNT_PREFS_H
 
 #include <gtk/gtk.h>
-#include <table/e-table.h>
 #include <mail/e-mail-backend.h>
 #include <mail/e-mail-account-manager.h>
-#include <widgets/misc/e-preferences-window.h>
 
 /* Standard GObject macros */
 #define EM_TYPE_ACCOUNT_PREFS \
diff --git a/modules/mail/em-composer-prefs.c b/modules/mail/em-composer-prefs.c
index 4a72169..f962009 100644
--- a/modules/mail/em-composer-prefs.c
+++ b/modules/mail/em-composer-prefs.c
@@ -38,16 +38,10 @@
 #include <gtkhtml/gtkhtml.h>
 #include <editor/gtkhtml-spell-language.h>
 
-#include <e-util/e-util.h>
-#include <e-util/e-util-private.h>
-
 #include <composer/e-msg-composer.h>
 
 #include <shell/e-shell-utils.h>
 
-#include <misc/e-charset-combo-box.h>
-#include <misc/e-mail-signature-manager.h>
-
 #include <mail/em-config.h>
 #include <mail/em-folder-selection-button.h>
 #include <mail/e-mail-junk-options.h>
diff --git a/modules/mail/em-composer-prefs.h b/modules/mail/em-composer-prefs.h
index 945e2d7..211f414 100644
--- a/modules/mail/em-composer-prefs.h
+++ b/modules/mail/em-composer-prefs.h
@@ -27,7 +27,6 @@
 #include <gtkhtml/gtkhtml.h>
 
 #include <shell/e-shell.h>
-#include <widgets/misc/e-preferences-window.h>
 
 /* Standard GObject macros */
 #define EM_TYPE_COMPOSER_PREFS \
diff --git a/modules/mail/em-mailer-prefs.c b/modules/mail/em-mailer-prefs.c
index 0dd3d38..3e5e588 100644
--- a/modules/mail/em-mailer-prefs.c
+++ b/modules/mail/em-mailer-prefs.c
@@ -32,12 +32,6 @@
 #include <gtkhtml/gtkhtml-properties.h>
 #include <libxml/tree.h>
 
-#include <e-util/e-util.h>
-#include <e-util/e-datetime-format.h>
-#include <e-util/e-util-private.h>
-
-#include <misc/e-charset-combo-box.h>
-#include <misc/e-port-entry.h>
 #include <shell/e-shell-utils.h>
 
 #include <mail/e-mail-backend.h>
diff --git a/modules/mail/em-mailer-prefs.h b/modules/mail/em-mailer-prefs.h
index 8e66476..afd88af 100644
--- a/modules/mail/em-mailer-prefs.h
+++ b/modules/mail/em-mailer-prefs.h
@@ -26,7 +26,6 @@
 #include <gtk/gtk.h>
 
 #include <shell/e-shell.h>
-#include <widgets/misc/e-preferences-window.h>
 
 /* Standard GObject macros */
 #define EM_TYPE_MAILER_PREFS \
diff --git a/modules/mail/em-network-prefs.c b/modules/mail/em-network-prefs.c
index b43c8a1..fe21b27 100644
--- a/modules/mail/em-network-prefs.c
+++ b/modules/mail/em-network-prefs.c
@@ -35,7 +35,6 @@
 #include <glib/gstdio.h>
 #include <gdk/gdkkeysyms.h>
 
-#include <e-util/e-util.h>
 #include <e-util/e-util-private.h>
 
 #include <mail/em-config.h>
diff --git a/modules/mail/em-network-prefs.h b/modules/mail/em-network-prefs.h
index 8d67530..8fd062f 100644
--- a/modules/mail/em-network-prefs.h
+++ b/modules/mail/em-network-prefs.h
@@ -24,7 +24,7 @@
 #define EM_NETWORK_PREFS_H
 
 #include <gtk/gtk.h>
-#include <widgets/misc/e-preferences-window.h>
+#include <e-util/e-util.h>
 
 /* Standard GObject macros */
 #define EM_TYPE_NETWORK_PREFS \
diff --git a/modules/mailto-handler/Makefile.am b/modules/mailto-handler/Makefile.am
index e33a120..c7bfe3a 100644
--- a/modules/mailto-handler/Makefile.am
+++ b/modules/mailto-handler/Makefile.am
@@ -6,6 +6,7 @@ module_mailto_handler_la_CPPFLAGS = \
 	-DG_LOG_DOMAIN=\"evolution-mailto-handler\"		\
 	$(EVOLUTION_DATA_SERVER_CFLAGS)				\
 	$(GNOME_PLATFORM_CFLAGS)				\
+	$(CHAMPLAIN_CFLAGS)					\
 	$(GTKHTML_CFLAGS)
 
 module_mailto_handler_la_SOURCES = \
@@ -16,6 +17,7 @@ module_mailto_handler_la_LIBADD = \
 	$(top_builddir)/shell/libeshell.la			\
 	$(EVOLUTION_DATA_SERVER_LIBS)				\
 	$(GNOME_PLATFORM_LIBS)					\
+	$(CHAMPLAIN_LIBS)					\
 	$(GTKHTML_LIBS)
 
 module_mailto_handler_la_LDFLAGS = \
diff --git a/modules/mdn/Makefile.am b/modules/mdn/Makefile.am
index bd6cbb9..df5e539 100644
--- a/modules/mdn/Makefile.am
+++ b/modules/mdn/Makefile.am
@@ -3,10 +3,10 @@ module_LTLIBRARIES = module-mdn.la
 module_mdn_la_CPPFLAGS = \
 	$(AM_CPPFLAGS)						\
 	-I$(top_srcdir)						\
-	-I$(top_srcdir)/widgets					\
 	-DG_LOG_DOMAIN=\"evolution-mdn\"			\
 	$(EVOLUTION_DATA_SERVER_CFLAGS)				\
 	$(GNOME_PLATFORM_CFLAGS)				\
+	$(CHAMPLAIN_CFLAGS)					\
 	$(GTKHTML_CFLAGS)
 
 module_mdn_la_SOURCES = \
@@ -17,9 +17,9 @@ module_mdn_la_LIBADD = \
 	$(top_builddir)/mail/libevolution-mail.la		\
 	$(top_builddir)/e-util/libeutil.la			\
 	$(top_builddir)/shell/libeshell.la			\
-	$(top_builddir)/libevolution-utils/libevolution-utils.la \
 	$(EVOLUTION_DATA_SERVER_LIBS)				\
 	$(GNOME_PLATFORM_LIBS)					\
+	$(CHAMPLAIN_LIBS)					\
 	$(GTKHTML_LIBS)
 
 module_mdn_la_LDFLAGS = \
diff --git a/modules/mdn/evolution-mdn.c b/modules/mdn/evolution-mdn.c
index 5b32cd0..441b063 100644
--- a/modules/mdn/evolution-mdn.c
+++ b/modules/mdn/evolution-mdn.c
@@ -22,8 +22,6 @@
 
 #include <libebackend/libebackend.h>
 
-#include <libevolution-utils/e-alert-dialog.h>
-
 #include <libemail-engine/e-mail-session-utils.h>
 
 #include <mail/em-utils.h>
diff --git a/modules/offline-alert/Makefile.am b/modules/offline-alert/Makefile.am
index 9673a27..d8e9906 100644
--- a/modules/offline-alert/Makefile.am
+++ b/modules/offline-alert/Makefile.am
@@ -3,10 +3,11 @@ module_LTLIBRARIES = module-offline-alert.la
 module_offline_alert_la_CPPFLAGS = \
 	$(AM_CPPFLAGS)						\
 	-I$(top_srcdir)						\
-	-I$(top_srcdir)/widgets					\
 	-DG_LOG_DOMAIN=\"evolution-offline-alert\"		\
 	$(EVOLUTION_DATA_SERVER_CFLAGS)				\
-	$(GNOME_PLATFORM_CFLAGS)
+	$(GNOME_PLATFORM_CFLAGS)				\
+	$(CHAMPLAIN_CFLAGS)					\
+	$(GTKHTML_CFLAGS)
 
 module_offline_alert_la_SOURCES = \
 	evolution-offline-alert.c
@@ -14,9 +15,10 @@ module_offline_alert_la_SOURCES = \
 module_offline_alert_la_LIBADD = \
 	$(top_builddir)/e-util/libeutil.la			\
 	$(top_builddir)/shell/libeshell.la			\
-	$(top_builddir)/libevolution-utils/libevolution-utils.la \
 	$(EVOLUTION_DATA_SERVER_LIBS)				\
-	$(GNOME_PLATFORM_LIBS)
+	$(GNOME_PLATFORM_LIBS)					\
+	$(CHAMPLAIN_LIBS)					\
+	$(GTKHTML_LIBS)
 
 module_offline_alert_la_LDFLAGS = \
 	-module -avoid-version $(NO_UNDEFINED)
diff --git a/modules/offline-alert/evolution-offline-alert.c b/modules/offline-alert/evolution-offline-alert.c
index 3a600c0..767f41a 100644
--- a/modules/offline-alert/evolution-offline-alert.c
+++ b/modules/offline-alert/evolution-offline-alert.c
@@ -24,7 +24,6 @@
 
 #include <shell/e-shell-view.h>
 #include <shell/e-shell-window-actions.h>
-#include <libevolution-utils/e-alert-sink.h>
 
 /* Standard GObject macros */
 #define E_TYPE_OFFLINE_ALERT \
diff --git a/modules/online-accounts/Makefile.am b/modules/online-accounts/Makefile.am
index d12fd08..846e5cd 100644
--- a/modules/online-accounts/Makefile.am
+++ b/modules/online-accounts/Makefile.am
@@ -8,6 +8,8 @@ module_online_accounts_la_CPPFLAGS = \
 	-DG_LOG_DOMAIN=\"evolution-online-accounts\"		\
 	$(EVOLUTION_DATA_SERVER_CFLAGS)				\
 	$(GNOME_PLATFORM_CFLAGS)				\
+	$(CHAMPLAIN_CFLAGS)					\
+	$(GTKHTML_CFLAGS)					\
 	$(GOA_CFLAGS)						\
 	$(NULL)
 
@@ -23,9 +25,10 @@ module_online_accounts_la_LIBADD = \
 	$(top_builddir)/e-util/libeutil.la			\
 	$(top_builddir)/shell/libeshell.la			\
 	$(top_builddir)/libemail-engine/libemail-engine.la	\
-	$(top_builddir)/libemail-utils/libemail-utils.la	\
 	$(EVOLUTION_DATA_SERVER_LIBS)				\
 	$(GNOME_PLATFORM_LIBS)					\
+	$(CHAMPLAIN_LIBS)					\
+	$(GTKHTML_LIBS)						\
 	$(GOA_LIBS)						\
 	$(NULL)
 
diff --git a/modules/plugin-lib/Makefile.am b/modules/plugin-lib/Makefile.am
index 0933861..950e472 100644
--- a/modules/plugin-lib/Makefile.am
+++ b/modules/plugin-lib/Makefile.am
@@ -5,7 +5,10 @@ module_plugin_lib_la_CPPFLAGS = \
 	-DG_LOG_DOMAIN=\"evolution-plugin-lib\"			\
 	-I$(top_srcdir)						\
 	-DEVOLUTION_PREFIX=\""$(prefix)"\"			\
-	$(EVOLUTION_DATA_SERVER_CFLAGS)
+	$(EVOLUTION_DATA_SERVER_CFLAGS)				\
+	$(GNOME_PLATFORM_CFLAGS)				\
+	$(CHAMPLAIN_CFLAGS)					\
+	$(GTKHTML_CFLAGS)
 
 module_plugin_lib_la_SOURCES = \
 	evolution-module-plugin-lib.c				\
@@ -14,7 +17,10 @@ module_plugin_lib_la_SOURCES = \
 
 module_plugin_lib_la_LIBADD = \
 	$(top_builddir)/e-util/libeutil.la			\
-	$(EVOLUTION_DATA_SERVER_LIBS)
+	$(EVOLUTION_DATA_SERVER_LIBS)				\
+	$(GNOME_PLATFORM_LIBS)					\
+	$(CHAMPLAIN_LIBS)					\
+	$(GTKHTML_LIBS)
 
 module_plugin_lib_la_LDFLAGS = \
 	-module -avoid-version $(NO_UNDEFINED)
diff --git a/modules/plugin-lib/e-plugin-lib.h b/modules/plugin-lib/e-plugin-lib.h
index 91f440b..db1056a 100644
--- a/modules/plugin-lib/e-plugin-lib.h
+++ b/modules/plugin-lib/e-plugin-lib.h
@@ -23,7 +23,7 @@
 #define E_PLUGIN_LIB_H
 
 #include <gmodule.h>
-#include <e-util/e-plugin.h>
+#include <e-util/e-util.h>
 
 /* Standard GObject macros */
 #define E_TYPE_PLUGIN_LIB \
diff --git a/modules/plugin-manager/Makefile.am b/modules/plugin-manager/Makefile.am
index 03eaa3d..0c6a38a 100644
--- a/modules/plugin-manager/Makefile.am
+++ b/modules/plugin-manager/Makefile.am
@@ -3,10 +3,11 @@ module_LTLIBRARIES = module-plugin-manager.la
 module_plugin_manager_la_CPPFLAGS = \
 	$(AM_CPPFLAGS)						\
 	-I$(top_srcdir)						\
-	-I$(top_srcdir)/widgets					\
 	-DG_LOG_DOMAIN=\"evolution-plugin-manager\"		\
 	$(EVOLUTION_DATA_SERVER_CFLAGS)				\
-	$(GNOME_PLATFORM_CFLAGS)
+	$(GNOME_PLATFORM_CFLAGS)				\
+	$(CHAMPLAIN_CFLAGS)					\
+	$(GTKHTML_CFLAGS)
 
 module_plugin_manager_la_SOURCES = \
 	evolution-plugin-manager.c
@@ -15,7 +16,9 @@ module_plugin_manager_la_LIBADD = \
 	$(top_builddir)/e-util/libeutil.la			\
 	$(top_builddir)/shell/libeshell.la			\
 	$(EVOLUTION_DATA_SERVER_LIBS)				\
-	$(GNOME_PLATFORM_LIBS)
+	$(GNOME_PLATFORM_LIBS)					\
+	$(CHAMPLAIN_LIBS)					\
+	$(GTKHTML_LIBS)
 
 module_plugin_manager_la_LDFLAGS = \
 	-module -avoid-version $(NO_UNDEFINED)
diff --git a/modules/plugin-manager/evolution-plugin-manager.c b/modules/plugin-manager/evolution-plugin-manager.c
index 39bc9b9..759b3fd 100644
--- a/modules/plugin-manager/evolution-plugin-manager.c
+++ b/modules/plugin-manager/evolution-plugin-manager.c
@@ -25,7 +25,6 @@
 #include <glib/gi18n-lib.h>
 #include <libebackend/libebackend.h>
 
-#include <e-util/e-plugin.h>
 #include <shell/e-shell-window.h>
 #include <shell/e-shell-window-actions.h>
 
diff --git a/modules/prefer-plain/Makefile.am b/modules/prefer-plain/Makefile.am
index ac471df..1b0289e 100644
--- a/modules/prefer-plain/Makefile.am
+++ b/modules/prefer-plain/Makefile.am
@@ -5,11 +5,12 @@ module_LTLIBRARIES = module-prefer-plain.la
 module_prefer_plain_la_CPPFLAGS =				\
 	$(AM_CPPFLAGS)							\
 	-I$(top_srcdir)							\
-	-I$(top_srcdir)/widgets						\
 	-DEVOLUTION_PRIVDATADIR=\""$(privdatadir)"\"			\
 	-DG_LOG_DOMAIN=\"evolution-module-prefer-plain\"		\
 	$(EVOLUTION_DATA_SERVER_CFLAGS)					\
-	$(GNOME_PLATFORM_CFLAGS)
+	$(GNOME_PLATFORM_CFLAGS)					\
+	$(CHAMPLAIN_CFLAGS)						\
+	$(GTKHTML_CFLAGS)
 
 module_prefer_plain_la_SOURCES =				\
 	e-mail-parser-prefer-plain.c					\
@@ -19,12 +20,14 @@ module_prefer_plain_la_SOURCES =				\
 	evolution-module-prefer-plain.c
 
 module_prefer_plain_la_LIBADD =				\
+	$(top_builddir)/e-util/libeutil.la				\
 	$(top_builddir)/mail/libevolution-mail.la			\
 	$(top_builddir)/em-format/libemformat.la			\
 	$(top_builddir)/shell/libeshell.la				\
-	$(top_builddir)/widgets/misc/libemiscwidgets.la			\
 	$(EVOLUTION_DATA_SERVER_LIBS)					\
-	$(GNOME_PLATFORM_LIBS)
+	$(GNOME_PLATFORM_LIBS)						\
+	$(CHAMPLAIN_LIBS)						\
+	$(GTKHTML_LIBS)
 
 module_prefer_plain_la_LDFLAGS =				\
 	-avoid-version -module $(NO_UNDEFINED)
diff --git a/modules/prefer-plain/plugin/Makefile.am b/modules/prefer-plain/plugin/Makefile.am
index d1064cb..255b914 100644
--- a/modules/prefer-plain/plugin/Makefile.am
+++ b/modules/prefer-plain/plugin/Makefile.am
@@ -6,10 +6,11 @@ plugin_LTLIBRARIES = liborg-gnome-prefer-plain.la
 liborg_gnome_prefer_plain_la_CPPFLAGS =		\
 	$(AM_CPPFLAGS)					\
 	-I$(top_srcdir)					\
-	-I$(top_srcdir)/widgets				\
 	-DEVOLUTION_PRIVDATADIR=\""$(privdatadir)"\"	\
 	$(EVOLUTION_DATA_SERVER_CFLAGS)			\
-	$(GNOME_PLATFORM_CFLAGS)
+	$(GNOME_PLATFORM_CFLAGS)			\
+	$(CHAMPLAIN_CFLAGS)				\
+	$(GTKHTML_CFLAGS)
 
 liborg_gnome_prefer_plain_la_SOURCES =		\
 	config-ui.c
@@ -18,7 +19,9 @@ liborg_gnome_prefer_plain_la_LDFLAGS = -module -avoid-version $(NO_UNDEFINED)
 
 liborg_gnome_prefer_plain_la_LIBADD =				\
 	$(EVOLUTION_DATA_SERVER_LIBS)				\
-	$(GNOME_PLATFORM_LIBS)
+	$(GNOME_PLATFORM_LIBS)					\
+	$(CHAMPLAIN_LIBS)					\
+	$(GTKHTML_LIBS)
 
 BUILT_SOURCES = $(plugin_DATA)
 
diff --git a/modules/prefer-plain/plugin/config-ui.c b/modules/prefer-plain/plugin/config-ui.c
index 411ef24..5ed5891 100644
--- a/modules/prefer-plain/plugin/config-ui.c
+++ b/modules/prefer-plain/plugin/config-ui.c
@@ -23,8 +23,6 @@
 
 #include <mail/em-config.h>
 
-#include <libedataserverui/libedataserverui.h>
-
 GtkWidget *prefer_plain_page_factory (EPlugin *ep, EConfigHookItemFactoryData *hook_data);
 
 enum {
diff --git a/modules/spamassassin/Makefile.am b/modules/spamassassin/Makefile.am
index 8475660..c5ce4c2 100644
--- a/modules/spamassassin/Makefile.am
+++ b/modules/spamassassin/Makefile.am
@@ -5,7 +5,9 @@ module_spamassassin_la_CPPFLAGS = \
 	-I$(top_srcdir)						\
 	-DG_LOG_DOMAIN=\"evolution-spamassassin\"		\
 	$(EVOLUTION_DATA_SERVER_CFLAGS)				\
-	$(GNOME_PLATFORM_CFLAGS)
+	$(GNOME_PLATFORM_CFLAGS)				\
+	$(CHAMPLAIN_CFLAGS)					\
+	$(GTKHTML_CFLAGS)
 
 module_spamassassin_la_SOURCES = \
 	evolution-spamassassin.c
@@ -15,9 +17,10 @@ module_spamassassin_la_LIBADD = \
 	$(top_builddir)/mail/libevolution-mail.la		\
 	$(top_builddir)/shell/libeshell.la			\
 	$(top_builddir)/libemail-engine/libemail-engine.la \
-	$(top_builddir)/libemail-utils/libemail-utils.la  \
 	$(EVOLUTION_DATA_SERVER_LIBS)				\
-	$(GNOME_PLATFORM_LIBS)
+	$(GNOME_PLATFORM_LIBS)					\
+	$(CHAMPLAIN_LIBS)					\
+	$(GTKHTML_LIBS)
 
 module_spamassassin_la_LDFLAGS = \
 	-module -avoid-version $(NO_UNDEFINED)
diff --git a/modules/spamassassin/evolution-spamassassin.c b/modules/spamassassin/evolution-spamassassin.c
index 2202f71..fa08c56 100644
--- a/modules/spamassassin/evolution-spamassassin.c
+++ b/modules/spamassassin/evolution-spamassassin.c
@@ -26,7 +26,6 @@
 #include <camel/camel.h>
 
 #include <shell/e-shell.h>
-#include <e-util/e-mktemp.h>
 #include <libemail-engine/e-mail-junk-filter.h>
 
 /* Standard GObject macros */
diff --git a/modules/startup-wizard/Makefile.am b/modules/startup-wizard/Makefile.am
index 0642691..747b90a 100644
--- a/modules/startup-wizard/Makefile.am
+++ b/modules/startup-wizard/Makefile.am
@@ -5,10 +5,11 @@ module_LTLIBRARIES = module-startup-wizard.la
 module_startup_wizard_la_CPPFLAGS = \
 	$(AM_CPPFLAGS)						\
 	-I$(top_srcdir)						\
-	-I$(top_srcdir)/widgets					\
 	-DG_LOG_DOMAIN=\"evolution-startup-wizard\"		\
 	$(EVOLUTION_DATA_SERVER_CFLAGS)				\
 	$(GNOME_PLATFORM_CFLAGS)				\
+	$(CHAMPLAIN_CFLAGS)					\
+	$(GTKHTML_CFLAGS)					\
 	$(NULL)
 
 module_startup_wizard_la_SOURCES = \
@@ -24,15 +25,14 @@ module_startup_wizard_la_SOURCES = \
 module_startup_wizard_la_LIBADD = \
 	$(top_builddir)/e-util/libeutil.la			\
 	$(top_builddir)/shell/libeshell.la			\
-	$(top_builddir)/widgets/e-timezone-dialog/libetimezonedialog.la \
-	$(top_builddir)/widgets/misc/libemiscwidgets.la		\
 	$(top_builddir)/calendar/gui/libevolution-calendar.la	\
 	$(top_builddir)/mail/libevolution-mail.la		\
 	$(top_builddir)/libemail-engine/libemail-engine.la	\
-	$(top_builddir)/libemail-utils/libemail-utils.la	\
 	$(libevolution_mail_settings_la)			\
 	$(EVOLUTION_DATA_SERVER_LIBS)				\
 	$(GNOME_PLATFORM_LIBS)					\
+	$(CHAMPLAIN_LIBS)					\
+	$(GTKHTML_LIBS)						\
 	$(NULL)
 
 module_startup_wizard_la_LDFLAGS = \
diff --git a/modules/startup-wizard/e-mail-config-import-page.c b/modules/startup-wizard/e-mail-config-import-page.c
index 8d2ad1f..4028fee 100644
--- a/modules/startup-wizard/e-mail-config-import-page.c
+++ b/modules/startup-wizard/e-mail-config-import-page.c
@@ -21,8 +21,6 @@
 #include <config.h>
 #include <glib/gi18n-lib.h>
 
-#include <e-util/e-import.h>
-
 #define E_MAIL_CONFIG_IMPORT_PAGE_GET_PRIVATE(obj) \
 	(G_TYPE_INSTANCE_GET_PRIVATE \
 	((obj), E_TYPE_MAIL_CONFIG_IMPORT_PAGE, EMailConfigImportPagePrivate))
diff --git a/modules/startup-wizard/e-mail-config-import-page.h b/modules/startup-wizard/e-mail-config-import-page.h
index 9f37807..79dce20 100644
--- a/modules/startup-wizard/e-mail-config-import-page.h
+++ b/modules/startup-wizard/e-mail-config-import-page.h
@@ -21,7 +21,7 @@
 
 #include <gtk/gtk.h>
 
-#include <e-util/e-activity.h>
+#include <e-util/e-util.h>
 
 #include <mail/e-mail-config-page.h>
 #include <mail/e-mail-config-summary-page.h>
diff --git a/modules/startup-wizard/e-mail-config-import-progress-page.h b/modules/startup-wizard/e-mail-config-import-progress-page.h
index 387e6cc..f95a937 100644
--- a/modules/startup-wizard/e-mail-config-import-progress-page.h
+++ b/modules/startup-wizard/e-mail-config-import-progress-page.h
@@ -21,8 +21,6 @@
 
 #include <gtk/gtk.h>
 
-#include <e-util/e-activity.h>
-
 #include <mail/e-mail-config-page.h>
 
 #include "e-mail-config-import-page.h"
diff --git a/modules/startup-wizard/e-startup-assistant.c b/modules/startup-wizard/e-startup-assistant.c
index 86d6de1..1902324 100644
--- a/modules/startup-wizard/e-startup-assistant.c
+++ b/modules/startup-wizard/e-startup-assistant.c
@@ -21,6 +21,8 @@
 #include <config.h>
 #include <glib/gi18n-lib.h>
 
+#include <e-util/e-util.h>
+
 #include <mail/e-mail-config-welcome-page.h>
 
 #include "e-mail-config-import-page.h"
diff --git a/modules/startup-wizard/evolution-startup-wizard.c b/modules/startup-wizard/evolution-startup-wizard.c
index 1ddeaa5..03c70b7 100644
--- a/modules/startup-wizard/evolution-startup-wizard.c
+++ b/modules/startup-wizard/evolution-startup-wizard.c
@@ -25,9 +25,6 @@
 
 #include <shell/e-shell.h>
 
-#include <libevolution-utils/e-alert-dialog.h>
-#include <e-util/e-import.h>
-
 #include <mail/e-mail-backend.h>
 #include <mail/e-mail-config-assistant.h>
 #include <mail/e-mail-config-welcome-page.h>
diff --git a/modules/text-highlight/Makefile.am b/modules/text-highlight/Makefile.am
index fb0094a..8086663 100644
--- a/modules/text-highlight/Makefile.am
+++ b/modules/text-highlight/Makefile.am
@@ -3,11 +3,12 @@ module_LTLIBRARIES = module-text-highlight.la
 module_text_highlight_la_CPPFLAGS =					\
 	$(AM_CPPFLAGS)							\
 	-I$(top_srcdir)							\
-	-I$(top_srcdir)/widgets						\
 	-DEVOLUTION_PRIVDATADIR=\""$(privdatadir)"\"			\
 	-DG_LOG_DOMAIN=\"evolution-module-text-highlight\"		\
 	$(EVOLUTION_DATA_SERVER_CFLAGS)					\
-	$(GNOME_PLATFORM_CFLAGS)
+	$(GNOME_PLATFORM_CFLAGS)					\
+	$(CHAMPLAIN_CFLAGS)						\
+	$(GTKHTML_CFLAGS)
 
 module_text_highlight_la_SOURCES =					\
 	e-mail-display-popup-text-highlight.c				\
@@ -25,9 +26,10 @@ module_text_highlight_la_LIBADD =					\
 	$(top_builddir)/mail/libevolution-mail.la			\
 	$(top_builddir)/em-format/libemformat.la			\
 	$(top_builddir)/shell/libeshell.la				\
-	$(top_builddir)/widgets/misc/libemiscwidgets.la			\
 	$(EVOLUTION_DATA_SERVER_LIBS)					\
-	$(GNOME_PLATFORM_LIBS)
+	$(GNOME_PLATFORM_LIBS)						\
+	$(CHAMPLAIN_LIBS)						\
+	$(GTKHTML_LIBS)
 
 module_text_highlight_la_LDFLAGS =					\
 	-avoid-version -module $(NO_UNDEFINED)
diff --git a/modules/tnef-attachment/Makefile.am b/modules/tnef-attachment/Makefile.am
index ff5b412..b8ff625 100644
--- a/modules/tnef-attachment/Makefile.am
+++ b/modules/tnef-attachment/Makefile.am
@@ -9,11 +9,12 @@ module_LTLIBRARIES = module-tnef-attachment.la
 module_tnef_attachment_la_CPPFLAGS =			\
 	$(AM_CPPFLAGS)							\
 	-I$(top_srcdir)							\
-	-I$(top_srcdir)/widgets						\
 	-DEVOLUTION_PRIVDATADIR=\""$(privdatadir)"\"			\
 	-DG_LOG_DOMAIN=\"evolution-module-tnef-attachment\"		\
 	$(EVOLUTION_DATA_SERVER_CFLAGS)					\
 	$(GNOME_PLATFORM_CFLAGS)					\
+	$(CHAMPLAIN_CFLAGS)						\
+	$(GTKHTML_CFLAGS)						\
 	$(TNEF_CFLAGS)
 
 module_tnef_attachment_la_SOURCES =			\
@@ -26,6 +27,8 @@ module_tnef_attachment_la_LIBADD =				\
 	$(top_builddir)/em-format/libemformat.la			\
 	$(EVOLUTION_DATA_SERVER_LIBS)					\
 	$(GNOME_PLATFORM_LIBS)						\
+	$(CHAMPLAIN_LIBS)						\
+	$(GTKHTML_LIBS)							\
 	-lytnef
 
 module_tnef_attachment_la_LDFLAGS =			\
diff --git a/modules/tnef-attachment/e-mail-parser-tnef-attachment.c b/modules/tnef-attachment/e-mail-parser-tnef-attachment.c
index 6ff54b0..dc93b91 100644
--- a/modules/tnef-attachment/e-mail-parser-tnef-attachment.c
+++ b/modules/tnef-attachment/e-mail-parser-tnef-attachment.c
@@ -17,19 +17,14 @@
 #include <config.h>
 #endif
 
+#include "e-mail-parser-tnef-attachment.h"
+
 #include <string.h>
 #include <gtk/gtk.h>
 #include <glib/gi18n.h>
 #include <glib/gprintf.h>
 #include <stdio.h>
 
-#include "e-mail-parser-tnef-attachment.h"
-
-#include <em-format/e-mail-extension-registry.h>
-#include <em-format/e-mail-parser-extension.h>
-#include <em-format/e-mail-part.h>
-#include <em-format/e-mail-part-utils.h>
-
 #include <sys/types.h>
 #include <dirent.h>
 #include <sys/stat.h>
@@ -42,11 +37,15 @@
 #include <libytnef/ytnef.h>
 #endif
 
-#include <mail/em-utils.h>
-#include <e-util/e-mktemp.h>
-
 #include <libebackend/libebackend.h>
 
+#include <em-format/e-mail-extension-registry.h>
+#include <em-format/e-mail-parser-extension.h>
+#include <em-format/e-mail-part.h>
+#include <em-format/e-mail-part-utils.h>
+
+#include <mail/em-utils.h>
+
 #define d(x)
 
 typedef struct _EMailParserTnefAttachment {
diff --git a/modules/vcard-inline/Makefile.am b/modules/vcard-inline/Makefile.am
index 18423f2..452c977 100644
--- a/modules/vcard-inline/Makefile.am
+++ b/modules/vcard-inline/Makefile.am
@@ -3,11 +3,12 @@ module_LTLIBRARIES = module-vcard-inline.la
 module_vcard_inline_la_CPPFLAGS =				\
 	$(AM_CPPFLAGS)							\
 	-I$(top_srcdir)							\
-	-I$(top_srcdir)/widgets						\
 	-DEVOLUTION_PRIVDATADIR=\""$(privdatadir)"\"			\
 	-DG_LOG_DOMAIN=\"evolution-module-vcard-inline\"		\
 	$(EVOLUTION_DATA_SERVER_CFLAGS)					\
-	$(GNOME_PLATFORM_CFLAGS)
+	$(GNOME_PLATFORM_CFLAGS)					\
+	$(CHAMPLAIN_CFLAGS)						\
+	$(GTKHTML_CFLAGS)
 
 module_vcard_inline_la_SOURCES =				\
 	e-mail-formatter-vcard-inline.c					\
@@ -25,7 +26,9 @@ module_vcard_inline_la_LIBADD =				\
 	$(top_builddir)/addressbook/gui/merging/libeabbookmerging.la	\
 	$(top_builddir)/addressbook/printing/libecontactprint.la	\
 	$(EVOLUTION_DATA_SERVER_LIBS)					\
-	$(GNOME_PLATFORM_LIBS)
+	$(GNOME_PLATFORM_LIBS)						\
+	$(CHAMPLAIN_LIBS)						\
+	$(GTKHTML_LIBS)
 
 module_vcard_inline_la_LDFLAGS =				\
 	-avoid-version -module $(NO_UNDEFINED)
diff --git a/modules/vcard-inline/e-mail-parser-vcard-inline.c b/modules/vcard-inline/e-mail-parser-vcard-inline.c
index bd40502..44032d4 100644
--- a/modules/vcard-inline/e-mail-parser-vcard-inline.c
+++ b/modules/vcard-inline/e-mail-parser-vcard-inline.c
@@ -38,7 +38,6 @@
 
 #include <libebook/libebook.h>
 #include <libedataserver/libedataserver.h>
-#include <libedataserverui/libedataserverui.h>
 
 #include <shell/e-shell.h>
 #include <addressbook/gui/merging/eab-contact-merging.h>
diff --git a/modules/web-inspector/Makefile.am b/modules/web-inspector/Makefile.am
index dfd999c..90da25e 100644
--- a/modules/web-inspector/Makefile.am
+++ b/modules/web-inspector/Makefile.am
@@ -3,19 +3,21 @@ module_LTLIBRARIES = module-web-inspector.la
 module_web_inspector_la_CPPFLAGS = \
 	$(AM_CPPFLAGS)						\
 	-I$(top_srcdir)						\
-	-I$(top_srcdir)/widgets					\
 	-DG_LOG_DOMAIN=\"evolution-web-inspector\"		\
 	$(EVOLUTION_DATA_SERVER_CFLAGS)				\
-	$(GNOME_PLATFORM_CFLAGS)
+	$(GNOME_PLATFORM_CFLAGS)				\
+	$(CHAMPLAIN_CFLAGS)					\
+	$(GTKHTML_CFLAGS)
 
 module_web_inspector_la_SOURCES = \
 	evolution-web-inspector.c
 
 module_web_inspector_la_LIBADD = \
 	$(top_builddir)/e-util/libeutil.la			\
-	$(top_builddir)/widgets/misc/libemiscwidgets.la		\
 	$(EVOLUTION_DATA_SERVER_LIBS)				\
-	$(GNOME_PLATFORM_LIBS)
+	$(GNOME_PLATFORM_LIBS)					\
+	$(CHAMPLAIN_LIBS)					\
+	$(GTKHTML_LIBS)
 
 module_web_inspector_la_LDFLAGS = \
 	-module -avoid-version $(NO_UNDEFINED)
diff --git a/modules/web-inspector/evolution-web-inspector.c b/modules/web-inspector/evolution-web-inspector.c
index 5a7585f..8028ec7 100644
--- a/modules/web-inspector/evolution-web-inspector.c
+++ b/modules/web-inspector/evolution-web-inspector.c
@@ -19,9 +19,10 @@
 #include <config.h>
 #include <glib/gi18n-lib.h>
 
-#include <misc/e-web-view.h>
 #include <libebackend/libebackend.h>
 
+#include <e-util/e-util.h>
+
 /* Standard GObject macros */
 #define E_TYPE_WEB_INSPECTOR \
 	(e_web_inspector_get_type ())
diff --git a/plugins/attachment-reminder/Makefile.am b/plugins/attachment-reminder/Makefile.am
index 0339e01..393f4d3 100644
--- a/plugins/attachment-reminder/Makefile.am
+++ b/plugins/attachment-reminder/Makefile.am
@@ -13,10 +13,10 @@ liborg_gnome_evolution_attachment_reminder_la_CPPFLAGS = \
 	$(AM_CPPFLAGS)					\
 	-I$(top_builddir)/composer			\
 	-I$(top_srcdir)					\
-	-I$(top_srcdir)/widgets				\
 	-DEVOLUTION_PLUGINDIR="\"$(plugindir)\""	\
 	$(EVOLUTION_DATA_SERVER_CFLAGS)			\
 	$(GNOME_PLATFORM_CFLAGS)			\
+	$(CHAMPLAIN_CFLAGS)				\
 	$(GTKHTML_CFLAGS)
 
 liborg_gnome_evolution_attachment_reminder_la_SOURCES = attachment-reminder.c 
@@ -29,11 +29,10 @@ liborg_gnome_evolution_attachment_reminder_la_LIBADD =	\
 	$(top_builddir)/addressbook/gui/contact-editor/libecontacteditor.la \
 	$(top_builddir)/addressbook/gui/contact-list-editor/libecontactlisteditor.la	\
 	$(top_builddir)/e-util/libeutil.la		\
-	$(top_builddir)/widgets/misc/libemiscwidgets.la	\
 	$(top_builddir)/mail/libevolution-mail.la	\
-	$(top_builddir)/libevolution-utils/libevolution-utils.la \
 	$(EVOLUTION_DATA_SERVER_LIBS)			\
 	$(GNOME_PLATFORM_LIBS)				\
+	$(CHAMPLAIN_LIBS)				\
 	$(GTKHTML_LIBS)
 
 EXTRA_DIST = org-gnome-evolution-attachment-reminder.eplug.xml \
diff --git a/plugins/attachment-reminder/attachment-reminder.c b/plugins/attachment-reminder/attachment-reminder.c
index 0079014..e005fbd 100644
--- a/plugins/attachment-reminder/attachment-reminder.c
+++ b/plugins/attachment-reminder/attachment-reminder.c
@@ -28,20 +28,13 @@
 #include <glib/gi18n.h>
 #include <string.h>
 
-#include <e-util/e-util.h>
-#include <e-util/e-config.h>
 #include <mail/em-config.h>
 #include <mail/em-event.h>
 
-#include <libevolution-utils/e-alert-dialog.h>
-#include <e-util/e-plugin.h>
-
 #include <mail/em-utils.h>
 
 #include "composer/e-msg-composer.h"
 #include "composer/e-composer-actions.h"
-#include "widgets/misc/e-attachment-view.h"
-#include "widgets/misc/e-attachment-store.h"
 
 #define CONF_KEY_ATTACH_REMINDER_CLUES "attachment-reminder-clues"
 
diff --git a/plugins/bbdb/Makefile.am b/plugins/bbdb/Makefile.am
index d3e006e..c2b58db 100644
--- a/plugins/bbdb/Makefile.am
+++ b/plugins/bbdb/Makefile.am
@@ -15,9 +15,9 @@ liborg_gnome_evolution_bbdb_la_CPPFLAGS =		\
 	$(AM_CPPFLAGS)					\
 	-I$(top_builddir)/composer			\
 	-I$(top_srcdir)					\
-	-I$(top_srcdir)/widgets				\
 	$(EVOLUTION_DATA_SERVER_CFLAGS)			\
 	$(GNOME_PLATFORM_CFLAGS)			\
+	$(CHAMPLAIN_CFLAGS)				\
 	$(GTKHTML_CFLAGS)
 
 liborg_gnome_evolution_bbdb_la_SOURCES = bbdb.c bbdb.h gaimbuddies.c
@@ -30,9 +30,9 @@ liborg_gnome_evolution_bbdb_la_LIBADD =		\
 	$(top_builddir)/composer/libcomposer.la	\
 	$(top_builddir)/addressbook/gui/contact-editor/libecontacteditor.la \
 	$(top_builddir)/addressbook/gui/contact-list-editor/libecontactlisteditor.la \
-	$(top_builddir)/libevolution-utils/libevolution-utils.la \
 	$(EVOLUTION_DATA_SERVER_LIBS)		\
 	$(GNOME_PLATFORM_LIBS)			\
+	$(CHAMPLAIN_LIBS)			\
 	$(GTKHTML_LIBS)
 
 EXTRA_DIST = org-gnome-evolution-bbdb.eplug.xml
diff --git a/plugins/bbdb/bbdb.c b/plugins/bbdb/bbdb.c
index c195519..c6e60b0 100644
--- a/plugins/bbdb/bbdb.c
+++ b/plugins/bbdb/bbdb.c
@@ -28,9 +28,6 @@
 #include <glib/gi18n.h>
 #include <string.h>
 
-#include <libedataserverui/libedataserverui.h>
-
-#include <e-util/e-config.h>
 #include <addressbook/gui/widgets/eab-config.h>
 #include <mail/em-event.h>
 #include <composer/e-msg-composer.h>
diff --git a/plugins/bbdb/gaimbuddies.c b/plugins/bbdb/gaimbuddies.c
index b59e683..d9a0cd9 100644
--- a/plugins/bbdb/gaimbuddies.c
+++ b/plugins/bbdb/gaimbuddies.c
@@ -36,18 +36,15 @@
 #include <libxml/tree.h>
 #include <libxml/parser.h>
 #include <libxml/xmlmemory.h>
-#include <libevolution-utils/e-xml-utils.h>
 
 #include <gtk/gtk.h>
 #include <glib/gi18n.h>
 #include <string.h>
 
-#include <libedataserverui/libedataserverui.h>
-
 #include <sys/time.h>
 #include <sys/stat.h>
 
-#include <e-util/e-config.h>
+#include <e-util/e-util.h>
 
 #include "bbdb.h"
 
diff --git a/plugins/dbx-import/Makefile.am b/plugins/dbx-import/Makefile.am
index 5bead7d..011f10f 100644
--- a/plugins/dbx-import/Makefile.am
+++ b/plugins/dbx-import/Makefile.am
@@ -13,10 +13,11 @@ plugin_LTLIBRARIES = liborg-gnome-dbx-import.la
 liborg_gnome_dbx_import_la_CPPFLAGS =			\
 	$(AM_CPPFLAGS)					\
 	-I$(top_srcdir)					\
-	-I$(top_srcdir)/widgets				\
 	-I$(top_builddir)				\
 	$(EVOLUTION_DATA_SERVER_CFLAGS)			\
-	$(GNOME_PLATFORM_CFLAGS)
+	$(GNOME_PLATFORM_CFLAGS)			\
+	$(CHAMPLAIN_CFLAGS)				\
+	$(GTKHTML_CFLAGS)
 
 liborg_gnome_dbx_import_la_SOURCES = dbx-importer.c
 
@@ -27,9 +28,10 @@ liborg_gnome_dbx_import_la_LIBADD =			\
 	$(top_builddir)/e-util/libeutil.la		\
 	$(top_builddir)/shell/libeshell.la		\
 	$(top_builddir)/libemail-engine/libemail-engine.la \
-	$(top_builddir)/libemail-utils/libemail-utils.la  \
 	$(EVOLUTION_DATA_SERVER_LIBS)			\
-	$(GNOME_PLATFORM_LIBS)
+	$(GNOME_PLATFORM_LIBS)				\
+	$(CHAMPLAIN_LIBS)				\
+	$(GTKHTML_LIBS)
 
 EXTRA_DIST = org-gnome-dbx-import.eplug.xml
 
diff --git a/plugins/dbx-import/dbx-importer.c b/plugins/dbx-import/dbx-importer.c
index 5d00da5..0eb5f4f 100644
--- a/plugins/dbx-import/dbx-importer.c
+++ b/plugins/dbx-import/dbx-importer.c
@@ -49,17 +49,12 @@
 #include <gtk/gtk.h>
 #include <libecal/libecal.h>
 #include <libebook/libebook.h>
-#include <libedataserverui/libedataserverui.h>
-
-#include <e-util/e-import.h>
-#include <e-util/e-plugin.h>
-#include <e-util/e-mktemp.h>
 
 #include <shell/e-shell.h>
 #include <shell/e-shell-window.h>
 #include <shell/e-shell-view.h>
 
-#include <libemail-utils/mail-mt.h>
+#include <libemail-engine/mail-mt.h>
 #include <libemail-engine/mail-tools.h>
 
 #include <mail/e-mail-backend.h>
diff --git a/plugins/email-custom-header/Makefile.am b/plugins/email-custom-header/Makefile.am
index a8d6016..446a528 100644
--- a/plugins/email-custom-header/Makefile.am
+++ b/plugins/email-custom-header/Makefile.am
@@ -8,12 +8,11 @@ liborg_gnome_email_custom_header_la_CPPFLAGS = \
 	$(AM_CPPFLAGS)					\
 	-I.	 					\
 	-I$(top_srcdir)					\
-	-I$(top_srcdir)/widgets				\
-	-I$(top_srcdir)/widgets/misc			\
 	-I$(top_builddir)/mail				\
 	-I$(top_builddir)/composer			\
 	$(EVOLUTION_DATA_SERVER_CFLAGS)			\
 	$(GNOME_PLATFORM_CFLAGS)			\
+	$(CHAMPLAIN_CFLAGS)				\
 	$(GTKHTML_CFLAGS)
 
 liborg_gnome_email_custom_header_la_SOURCES = 		\
@@ -23,11 +22,10 @@ liborg_gnome_email_custom_header_la_SOURCES = 		\
 liborg_gnome_email_custom_header_la_LIBADD = 			\
 	$(top_builddir)/e-util/libeutil.la 			\
 	$(top_builddir)/composer/libcomposer.la 		\
-	$(top_builddir)/widgets/misc/libemiscwidgets.la		\
 	$(top_builddir)/mail/libevolution-mail.la 		\
-	$(top_builddir)/libevolution-utils/libevolution-utils.la \
 	$(EVOLUTION_DATA_SERVER_LIBS)				\
 	$(GNOME_PLATFORM_LIBS)					\
+	$(CHAMPLAIN_LIBS)					\
 	$(GTKHTML_LIBS)
 
 liborg_gnome_email_custom_header_la_LDFLAGS = -module -avoid-version $(NO_UNDEFINED)
diff --git a/plugins/email-custom-header/email-custom-header.c b/plugins/email-custom-header/email-custom-header.c
index 5f66489..25f1fef 100644
--- a/plugins/email-custom-header/email-custom-header.c
+++ b/plugins/email-custom-header/email-custom-header.c
@@ -26,11 +26,10 @@
 
 #include <string.h>
 #include <glib/gi18n.h>
+
 #include "mail/em-utils.h"
 #include "mail/em-event.h"
 #include "composer/e-msg-composer.h"
-#include "e-util/e-config.h"
-#include "e-util/e-util.h"
 #include "email-custom-header.h"
 
 #define d(x)
diff --git a/plugins/external-editor/Makefile.am b/plugins/external-editor/Makefile.am
index f0ffd1a..080e4e0 100644
--- a/plugins/external-editor/Makefile.am
+++ b/plugins/external-editor/Makefile.am
@@ -26,9 +26,9 @@ liborg_gnome_external_editor_la_CPPFLAGS =		\
 	-DLIBDIR=\""$(libdir)"\"			\
 	-I$(top_srcdir)					\
 	-I$(top_srcdir)/composer			\
-	-I$(top_srcdir)/widgets				\
 	$(EVOLUTION_DATA_SERVER_CFLAGS)			\
 	$(GNOME_PLATFORM_CFLAGS)			\
+	$(CHAMPLAIN_CFLAGS)				\
 	$(GTKHTML_CFLAGS)
 
 liborg_gnome_external_editor_la_SOURCES = 		\
@@ -43,9 +43,9 @@ liborg_gnome_external_editor_la_LIBADD = 			\
 	$(top_builddir)/addressbook/gui/contact-editor/libecontacteditor.la 		\
 	$(top_builddir)/addressbook/gui/contact-list-editor/libecontactlisteditor.la	\
 	$(top_builddir)/mail/libevolution-mail.la		\
-	$(top_builddir)/libevolution-utils/libevolution-utils.la \
 	$(EVOLUTION_DATA_SERVER_LIBS)				\
 	$(GNOME_PLATFORM_LIBS)					\
+	$(CHAMPLAIN_LIBS)					\
 	$(GTKHTML_LIBS)
 
 EXTRA_DIST = 					\
diff --git a/plugins/external-editor/external-editor.c b/plugins/external-editor/external-editor.c
index 0c43acb..19a8159 100644
--- a/plugins/external-editor/external-editor.c
+++ b/plugins/external-editor/external-editor.c
@@ -28,7 +28,6 @@
 
 #include <mail/em-config.h>
 #include <mail/em-composer-utils.h>
-#include <libevolution-utils/e-alert-dialog.h>
 #include <e-msg-composer.h>
 
 #include <glib/gi18n-lib.h>
diff --git a/plugins/face/Makefile.am b/plugins/face/Makefile.am
index 0522cc0..1fe52d0 100644
--- a/plugins/face/Makefile.am
+++ b/plugins/face/Makefile.am
@@ -8,12 +8,11 @@ liborg_gnome_face_la_CPPFLAGS =				\
 	$(AM_CPPFLAGS)					\
 	-I. 						\
 	-I$(top_srcdir)					\
-	-I$(top_srcdir)/widgets				\
-	-I$(top_srcdir)/widgets/misc			\
 	-I$(top_builddir)/mail				\
 	-I$(top_builddir)/composer			\
 	$(EVOLUTION_DATA_SERVER_CFLAGS)			\
 	$(GNOME_PLATFORM_CFLAGS)			\
+	$(CHAMPLAIN_CFLAGS)				\
 	$(GTKHTML_CFLAGS)
 
 liborg_gnome_face_la_SOURCES = face.c
@@ -21,11 +20,10 @@ liborg_gnome_face_la_SOURCES = face.c
 liborg_gnome_face_la_LIBADD = 					\
 	$(top_builddir)/e-util/libeutil.la 			\
 	$(top_builddir)/composer/libcomposer.la 		\
-	$(top_builddir)/widgets/misc/libemiscwidgets.la		\
 	$(top_builddir)/mail/libevolution-mail.la 		\
-	$(top_builddir)/libevolution-utils/libevolution-utils.la \
 	$(EVOLUTION_DATA_SERVER_LIBS)				\
 	$(GNOME_PLATFORM_LIBS)					\
+	$(CHAMPLAIN_LIBS)					\
 	$(GTKHTML_LIBS)
 
 liborg_gnome_face_la_LDFLAGS = -module -avoid-version $(NO_UNDEFINED)
diff --git a/plugins/face/face.c b/plugins/face/face.c
index c9023dc..45d3d2a 100644
--- a/plugins/face/face.c
+++ b/plugins/face/face.c
@@ -29,9 +29,6 @@
 #include <gtk/gtk.h>
 #include <glib/gi18n-lib.h>
 #include <mail/em-event.h>
-#include <libevolution-utils/e-alert-dialog.h>
-#include <e-util/e-util.h>
-#include <e-util/e-icon-factory.h>
 
 #define d(x)
 
diff --git a/plugins/mail-notification/Makefile.am b/plugins/mail-notification/Makefile.am
index 042dcd0..c04d644 100644
--- a/plugins/mail-notification/Makefile.am
+++ b/plugins/mail-notification/Makefile.am
@@ -14,11 +14,11 @@ plugin_LTLIBRARIES = liborg-gnome-mail-notification.la
 liborg_gnome_mail_notification_la_CPPFLAGS =	\
 	$(AM_CPPFLAGS)				\
 	-I$(top_srcdir)				\
-	-I$(top_srcdir)/widgets			\
 	$(EVOLUTION_DATA_SERVER_CFLAGS)		\
 	$(GNOME_PLATFORM_CFLAGS)		\
 	$(LIBNOTIFY_CFLAGS)			\
 	$(CANBERRA_CFLAGS)			\
+	$(CHAMPLAIN_CFLAGS)			\
 	$(GTKHTML_CFLAGS)
 
 liborg_gnome_mail_notification_la_SOURCES = mail-notification.c
@@ -29,12 +29,12 @@ liborg_gnome_mail_notification_la_LIBADD = 			\
 	$(top_builddir)/e-util/libeutil.la 			\
 	$(top_builddir)/mail/libevolution-mail.la		\
 	$(top_builddir)/shell/libeshell.la			\
-	$(top_builddir)/libemail-utils/libemail-utils.la	\
 	$(top_builddir)/libemail-engine/libemail-engine.la	\
 	$(EVOLUTION_DATA_SERVER_LIBS)				\
 	$(GNOME_PLATFORM_LIBS)					\
 	$(LIBNOTIFY_LIBS)					\
 	$(CANBERRA_LIBS)					\
+	$(CHAMPLAIN_LIBS)					\
 	$(GTKHTML_LIBS)
 
 BUILT_SOURCES = $(plugin_DATA)
diff --git a/plugins/mail-notification/mail-notification.c b/plugins/mail-notification/mail-notification.c
index cc01d65..e0973d2 100644
--- a/plugins/mail-notification/mail-notification.c
+++ b/plugins/mail-notification/mail-notification.c
@@ -35,7 +35,6 @@
 
 #include <time.h>
 
-#include <e-util/e-config.h>
 #include <libemail-engine/e-mail-folder-utils.h>
 #include <mail/em-utils.h>
 #include <mail/em-event.h>
diff --git a/plugins/mail-to-task/Makefile.am b/plugins/mail-to-task/Makefile.am
index da78753..a8bdfef 100644
--- a/plugins/mail-to-task/Makefile.am
+++ b/plugins/mail-to-task/Makefile.am
@@ -7,9 +7,10 @@ plugin_LTLIBRARIES = liborg-gnome-mail-to-task.la
 liborg_gnome_mail_to_task_la_CPPFLAGS =			\
 	$(AM_CPPFLAGS)					\
 	-I$(top_srcdir)					\
-	-I$(top_srcdir)/widgets				\
 	$(EVOLUTION_DATA_SERVER_CFLAGS)			\
-	$(GNOME_PLATFORM_CFLAGS)
+	$(GNOME_PLATFORM_CFLAGS)			\
+	$(CHAMPLAIN_CFLAGS)				\
+	$(GTKHTML_CFLAGS)
 
 liborg_gnome_mail_to_task_la_SOURCES = mail-to-task.c
 
@@ -21,11 +22,11 @@ liborg_gnome_mail_to_task_la_LIBADD =	\
 	$(top_builddir)/em-format/libemformat.la			\
 	$(top_builddir)/calendar/gui/libevolution-calendar.la		\
 	$(top_builddir)/mail/libevolution-mail.la			\
-	$(top_builddir)/widgets/misc/libemiscwidgets.la			\
-	$(top_builddir)/libemail-utils/libemail-utils.la		\
 	$(top_builddir)/libemail-engine/libemail-engine.la		\
 	$(EVOLUTION_DATA_SERVER_LIBS)					\
-	$(GNOME_PLATFORM_LIBS)
+	$(GNOME_PLATFORM_LIBS)						\
+	$(CHAMPLAIN_LIBS)						\
+	$(GTKHTML_LIBS)
 
 EXTRA_DIST = org-gnome-mail-to-task.eplug.xml
 
diff --git a/plugins/mail-to-task/mail-to-task.c b/plugins/mail-to-task/mail-to-task.c
index 042c2d3..cf15d40 100644
--- a/plugins/mail-to-task/mail-to-task.c
+++ b/plugins/mail-to-task/mail-to-task.c
@@ -33,15 +33,9 @@
 #include <glib/gi18n-lib.h>
 
 #include <libecal/libecal.h>
-#include <libedataserverui/libedataserverui.h>
 
 #include <libemail-engine/e-mail-utils.h>
 
-#include <e-util/e-dialog-utils.h>
-
-#include <misc/e-popup-action.h>
-#include <misc/e-attachment-store.h>
-
 #include <shell/e-shell-view.h>
 #include <shell/e-shell-window-actions.h>
 
diff --git a/plugins/mailing-list-actions/Makefile.am b/plugins/mailing-list-actions/Makefile.am
index ea1ce2e..f40cd9d 100644
--- a/plugins/mailing-list-actions/Makefile.am
+++ b/plugins/mailing-list-actions/Makefile.am
@@ -6,10 +6,10 @@ plugin_LTLIBRARIES = liborg-gnome-mailing-list-actions.la
 liborg_gnome_mailing_list_actions_la_CPPFLAGS =		\
 	$(AM_CPPFLAGS)					\
 	-I$(top_srcdir)					\
-	-I$(top_srcdir)/widgets				\
 	-I$(top_builddir)/composer 			\
 	$(EVOLUTION_DATA_SERVER_CFLAGS)			\
 	$(GNOME_PLATFORM_CFLAGS)			\
+	$(CHAMPLAIN_CFLAGS)				\
 	$(GTKHTML_CFLAGS)
 
 liborg_gnome_mailing_list_actions_la_SOURCES = mailing-list-actions.c
@@ -22,10 +22,9 @@ liborg_gnome_mailing_list_actions_la_LIBADD =		\
 	$(top_builddir)/mail/libevolution-mail.la	\
 	$(top_builddir)/shell/libeshell.la		\
 	$(top_builddir)/libemail-engine/libemail-engine.la \
-	$(top_builddir)/libemail-utils/libemail-utils.la \
-	$(top_builddir)/libevolution-utils/libevolution-utils.la \
 	$(EVOLUTION_DATA_SERVER_LIBS)			\
 	$(GNOME_PLATFORM_LIBS)				\
+	$(CHAMPLAIN_LIBS)				\
 	$(GTKHTML_LIBS)
 
 error_DATA = org-gnome-mailing-list-actions.error
diff --git a/plugins/mailing-list-actions/mailing-list-actions.c b/plugins/mailing-list-actions/mailing-list-actions.c
index fb6be6b..c6a3cab 100644
--- a/plugins/mailing-list-actions/mailing-list-actions.c
+++ b/plugins/mailing-list-actions/mailing-list-actions.c
@@ -30,7 +30,6 @@
 #include <glib/gi18n-lib.h>
 
 #include <e-util/e-util.h>
-#include <libevolution-utils/e-alert-dialog.h>
 
 #include <shell/e-shell-view.h>
 #include <shell/e-shell-window.h>
@@ -38,7 +37,7 @@
 
 #include <composer/e-msg-composer.h>
 
-#include <libemail-utils/mail-mt.h>
+#include <libemail-engine/mail-mt.h>
 #include <libemail-engine/mail-ops.h>
 
 #include <mail/e-mail-browser.h>
diff --git a/plugins/mark-all-read/Makefile.am b/plugins/mark-all-read/Makefile.am
index 4740bff..2f1f340 100644
--- a/plugins/mark-all-read/Makefile.am
+++ b/plugins/mark-all-read/Makefile.am
@@ -7,9 +7,10 @@ plugin_LTLIBRARIES = liborg-gnome-mark-all-read.la
 liborg_gnome_mark_all_read_la_CPPFLAGS =		\
 	$(AM_CPPFLAGS)					\
 	-I$(top_srcdir)					\
-	-I$(top_srcdir)/widgets				\
 	$(EVOLUTION_DATA_SERVER_CFLAGS)			\
-	$(GNOME_PLATFORM_CFLAGS)
+	$(GNOME_PLATFORM_CFLAGS)			\
+	$(CHAMPLAIN_CFLAGS)				\
+	$(GTKHTML_CFLAGS)
 
 liborg_gnome_mark_all_read_la_SOURCES = mark-all-read.c  
 
@@ -19,9 +20,10 @@ liborg_gnome_mark_all_read_la_LIBADD = 			\
 	$(top_builddir)/e-util/libeutil.la		\
 	$(top_builddir)/mail/libevolution-mail.la	\
 	$(top_builddir)/shell/libeshell.la		\
-	$(top_builddir)/libevolution-utils/libevolution-utils.la \
 	$(EVOLUTION_DATA_SERVER_LIBS)			\
-	$(GNOME_PLATFORM_LIBS)
+	$(GNOME_PLATFORM_LIBS)				\
+	$(CHAMPLAIN_LIBS)				\
+	$(GTKHTML_LIBS)
 
 EXTRA_DIST = org-gnome-mark-all-read.eplug.xml
 
diff --git a/plugins/mark-all-read/mark-all-read.c b/plugins/mark-all-read/mark-all-read.c
index 4596949..e4213fd 100644
--- a/plugins/mark-all-read/mark-all-read.c
+++ b/plugins/mark-all-read/mark-all-read.c
@@ -28,7 +28,6 @@
 #include <string.h>
 #include <gtk/gtk.h>
 #include <glib/gi18n.h>
-#include <e-util/e-plugin-ui.h>
 
 #include <libemail-engine/e-mail-folder-utils.h>
 
diff --git a/plugins/pst-import/Makefile.am b/plugins/pst-import/Makefile.am
index b81e0d2..8d5d294 100644
--- a/plugins/pst-import/Makefile.am
+++ b/plugins/pst-import/Makefile.am
@@ -14,10 +14,11 @@ plugin_LTLIBRARIES = liborg-gnome-pst-import.la
 liborg_gnome_pst_import_la_CPPFLAGS =			\
 	$(AM_CPPFLAGS)					\
 	-I$(top_srcdir)					\
-	-I$(top_srcdir)/widgets				\
 	-I$(top_builddir)				\
 	$(EVOLUTION_DATA_SERVER_CFLAGS)			\
 	$(GNOME_PLATFORM_CFLAGS)			\
+	$(CHAMPLAIN_CFLAGS)				\
+	$(GTKHTML_CFLAGS)				\
 	$(LIBPST_CFLAGS)
 
 liborg_gnome_pst_import_la_SOURCES = pst-importer.c
@@ -29,10 +30,10 @@ liborg_gnome_pst_import_la_LIBADD =				\
 	$(top_builddir)/mail/libevolution-mail.la		\
 	$(top_builddir)/e-util/libeutil.la			\
 	$(top_builddir)/libemail-engine/libemail-engine.la	\
-	$(top_builddir)/libemail-utils/libemail-utils.la	\
-	$(top_builddir)/libevolution-utils/libevolution-utils.la \
 	$(EVOLUTION_DATA_SERVER_LIBS)				\
 	$(GNOME_PLATFORM_LIBS)					\
+	$(CHAMPLAIN_LIBS)					\
+	$(GTKHTML_LIBS)						\
 	$(LIBPST_LIBS)
 
 EXTRA_DIST = org-gnome-pst-import.eplug.xml
diff --git a/plugins/pst-import/pst-importer.c b/plugins/pst-import/pst-importer.c
index 768d1b1..eced3ba 100644
--- a/plugins/pst-import/pst-importer.c
+++ b/plugins/pst-import/pst-importer.c
@@ -41,22 +41,19 @@
 #include <gtk/gtk.h>
 #include <libecal/libecal.h>
 #include <libebook/libebook.h>
-#include <libedataserverui/libedataserverui.h>
 
-#include <e-util/e-import.h>
-#include <e-util/e-plugin.h>
-
-#include <libemail-utils/mail-mt.h>
+#include <libemail-engine/mail-mt.h>
 #include <libemail-engine/mail-tools.h>
 
-#include <mail/e-mail-backend.h>
-#include <mail/em-folder-selection-button.h>
-#include <mail/em-utils.h>
 #include <shell/e-shell.h>
 #include <shell/e-shell-window.h>
 #include <shell/e-shell-view.h>
 #include <shell/e-shell-sidebar.h>
 
+#include <mail/e-mail-backend.h>
+#include <mail/em-folder-selection-button.h>
+#include <mail/em-utils.h>
+
 #include <libpst/libpst.h>
 #include <libpst/timeconv.h>
 
diff --git a/plugins/publish-calendar/Makefile.am b/plugins/publish-calendar/Makefile.am
index 2005866..0786c40 100644
--- a/plugins/publish-calendar/Makefile.am
+++ b/plugins/publish-calendar/Makefile.am
@@ -11,13 +11,14 @@ plugin_LTLIBRARIES = liborg-gnome-publish-calendar.la
 liborg_gnome_publish_calendar_la_CPPFLAGS =		\
 	$(AM_CPPFLAGS)					\
 	-I$(top_srcdir)					\
-	-I$(top_srcdir)/widgets				\
 	-I$(top_builddir)/shell				\
+	-DEVOLUTION_PLUGINDIR="\"$(plugindir)\""	\
+	-DEVOLUTION_UIDIR=\""$(uidir)"\"		\
 	$(EVOLUTION_DATA_SERVER_CFLAGS)			\
 	$(GNOME_PLATFORM_CFLAGS)			\
-	$(LIBNOTIFY_CFLAGS)				\
-	-DEVOLUTION_PLUGINDIR="\"$(plugindir)\""	\
-	-DEVOLUTION_UIDIR=\""$(uidir)"\"
+	$(CHAMPLAIN_CFLAGS)				\
+	$(GTKHTML_CFLAGS)				\
+	$(LIBNOTIFY_CFLAGS)
 
 liborg_gnome_publish_calendar_la_SOURCES =	\
 	publish-calendar.c			\
@@ -36,9 +37,10 @@ liborg_gnome_publish_calendar_la_LIBADD =			\
 	$(top_builddir)/e-util/libeutil.la			\
 	$(top_builddir)/shell/libeshell.la			\
 	$(top_builddir)/calendar/gui/libevolution-calendar.la	\
-	$(top_builddir)/libevolution-utils/libevolution-utils.la \
 	$(EVOLUTION_DATA_SERVER_LIBS)				\
 	$(GNOME_PLATFORM_LIBS)					\
+	$(CHAMPLAIN_LIBS)					\
+	$(GTKHTML_LIBS)						\
 	$(LIBNOTIFY_LIBS)
 
 EXTRA_DIST =					\
diff --git a/plugins/publish-calendar/publish-calendar.c b/plugins/publish-calendar/publish-calendar.c
index 7175050..00afe6b 100644
--- a/plugins/publish-calendar/publish-calendar.c
+++ b/plugins/publish-calendar/publish-calendar.c
@@ -31,9 +31,6 @@
 
 #include <calendar/gui/e-cal-config.h>
 #include <shell/es-event.h>
-#include <e-util/e-util.h>
-#include <e-util/e-util-private.h>
-#include <e-util/e-dialog-utils.h>
 
 #include <shell/e-shell.h>
 #include <shell/e-shell-view.h>
diff --git a/plugins/publish-calendar/publish-location.c b/plugins/publish-calendar/publish-location.c
index 465fff1..f84d589 100644
--- a/plugins/publish-calendar/publish-location.c
+++ b/plugins/publish-calendar/publish-location.c
@@ -29,7 +29,8 @@
 
 #include <string.h>
 #include <libxml/tree.h>
-#include <libedataserverui/libedataserverui.h>
+
+#include "e-util/e-util.h"
 
 static EPublishUri *
 migrateURI (const gchar *xml,
diff --git a/plugins/publish-calendar/url-editor-dialog.h b/plugins/publish-calendar/url-editor-dialog.h
index 96fb154..0f22172 100644
--- a/plugins/publish-calendar/url-editor-dialog.h
+++ b/plugins/publish-calendar/url-editor-dialog.h
@@ -24,7 +24,6 @@
 #define URL_EDITOR_DIALOG_H
 
 #include <gtk/gtk.h>
-#include <libedataserverui/libedataserverui.h>
 #include "publish-location.h"
 
 G_BEGIN_DECLS
diff --git a/plugins/save-calendar/Makefile.am b/plugins/save-calendar/Makefile.am
index b9bbc93..7203ece 100644
--- a/plugins/save-calendar/Makefile.am
+++ b/plugins/save-calendar/Makefile.am
@@ -7,9 +7,10 @@ plugin_LTLIBRARIES = liborg-gnome-save-calendar.la
 liborg_gnome_save_calendar_la_CPPFLAGS =		\
 	$(AM_CPPFLAGS)					\
 	-I$(top_srcdir)					\
-	-I$(top_srcdir)/widgets				\
 	$(EVOLUTION_DATA_SERVER_CFLAGS)			\
-	$(GNOME_PLATFORM_CFLAGS)
+	$(GNOME_PLATFORM_CFLAGS)			\
+	$(CHAMPLAIN_CFLAGS)				\
+	$(GTKHTML_CFLAGS)
 
 liborg_gnome_save_calendar_la_SOURCES = \
 	save-calendar.c			\
@@ -22,9 +23,10 @@ liborg_gnome_save_calendar_la_LDFLAGS = -module -avoid-version $(NO_UNDEFINED)
 liborg_gnome_save_calendar_la_LIBADD =	\
 	$(top_builddir)/e-util/libeutil.la	\
 	$(top_builddir)/shell/libeshell.la	\
-	$(top_builddir)/libevolution-utils/libevolution-utils.la \
 	$(EVOLUTION_DATA_SERVER_LIBS)		\
-	$(GNOME_PLATFORM_LIBS)
+	$(GNOME_PLATFORM_LIBS)			\
+	$(CHAMPLAIN_LIBS)			\
+	$(GTKHTML_LIBS)
 
 EXTRA_DIST = org-gnome-save-calendar.eplug.xml
 
diff --git a/plugins/save-calendar/format-handler.h b/plugins/save-calendar/format-handler.h
index 4a2dbab..8381258 100644
--- a/plugins/save-calendar/format-handler.h
+++ b/plugins/save-calendar/format-handler.h
@@ -20,8 +20,10 @@
  *
  */
 
+#include <gtk/gtk.h>
 #include <libecal/libecal.h>
-#include <libedataserverui/libedataserverui.h>
+
+#include <e-util/e-util.h>
 
 typedef struct _FormatHandler FormatHandler;
 
diff --git a/plugins/save-calendar/save-calendar.c b/plugins/save-calendar/save-calendar.c
index 9e2c997..8e8ca93 100644
--- a/plugins/save-calendar/save-calendar.c
+++ b/plugins/save-calendar/save-calendar.c
@@ -30,9 +30,6 @@
 #include <string.h>
 #include <glib/gi18n.h>
 
-#include <libevolution-utils/e-alert-dialog.h>
-#include <e-util/e-plugin.h>
-
 #include <shell/e-shell-sidebar.h>
 #include <shell/e-shell-view.h>
 #include <shell/e-shell-window.h>
diff --git a/plugins/templates/Makefile.am b/plugins/templates/Makefile.am
index d4a637d..b20f11f 100644
--- a/plugins/templates/Makefile.am
+++ b/plugins/templates/Makefile.am
@@ -7,11 +7,11 @@ plugin_LTLIBRARIES = liborg-gnome-templates.la
 liborg_gnome_templates_la_CPPFLAGS =			\
 	$(AM_CPPFLAGS)					\
 	-I$(top_srcdir)					\
-	-I$(top_srcdir)/widgets				\
 	-I$(top_builddir)				\
 	-I$(top_builddir)/composer			\
 	$(EVOLUTION_DATA_SERVER_CFLAGS)			\
 	$(GNOME_PLATFORM_CFLAGS)			\
+	$(CHAMPLAIN_CFLAGS)				\
 	$(GTKHTML_CFLAGS)
 
 liborg_gnome_templates_la_SOURCES = templates.c
@@ -24,9 +24,9 @@ liborg_gnome_templates_la_LIBADD =	\
 	$(top_builddir)/shell/libeshell.la		\
 	$(top_builddir)/mail/libevolution-mail.la	\
 	$(top_builddir)/libemail-engine/libemail-engine.la \
-	$(top_builddir)/libevolution-utils/libevolution-utils.la \
 	$(EVOLUTION_DATA_SERVER_LIBS)			\
 	$(GNOME_PLATFORM_LIBS)				\
+	$(CHAMPLAIN_LIBS)				\
 	$(GTKHTML_LIBS)
 
 EXTRA_DIST = 	org-gnome-templates.eplug.xml
diff --git a/plugins/templates/templates.c b/plugins/templates/templates.c
index 5416a8f..e5c0525 100644
--- a/plugins/templates/templates.c
+++ b/plugins/templates/templates.c
@@ -30,11 +30,6 @@
 #include <glib/gi18n.h>
 #include <string.h>
 
-#include <libevolution-utils/e-alert-dialog.h>
-#include <e-util/e-config.h>
-#include <e-util/e-plugin.h>
-#include <e-util/e-util.h>
-
 #include <shell/e-shell-view.h>
 
 #include <libemail-engine/e-mail-session-utils.h>
diff --git a/po/POTFILES.in b/po/POTFILES.in
index cb65d2e..bb98e79 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -196,16 +196,15 @@ filter/filter.error.xml
 libemail-engine/camel-null-store.c
 libemail-engine/e-mail-authenticator.c
 libemail-engine/e-mail-folder-utils.c
-libemail-engine/e-mail-session.c
 libemail-engine/e-mail-session-utils.c
+libemail-engine/e-mail-session.c
 libemail-engine/e-mail-store-utils.c
 libemail-engine/mail-config.c
 libemail-engine/mail-folder-cache.c
+libemail-engine/mail-mt.c
 libemail-engine/mail-ops.c
 libemail-engine/mail-tools.c
 libemail-engine/mail-vfolder.c
-libemail-utils/mail-mt.c
-libevolution-utils/e-alert-dialog.c
 mail/e-mail-account-manager.c
 mail/e-mail-account-tree-view.c
 mail/e-mail-autoconfig.c
diff --git a/shell/Makefile.am b/shell/Makefile.am
index 8e21c7e..16f3626 100644
--- a/shell/Makefile.am
+++ b/shell/Makefile.am
@@ -36,9 +36,6 @@ eshellinclude_HEADERS = 			\
 libeshell_la_CPPFLAGS =						\
 	$(AM_CPPFLAGS)						\
 	$(NM_CPPFLAGS)						\
-	-I$(top_srcdir)/widgets					\
-	-I$(top_srcdir)/widgets/menus				\
-	-I$(top_srcdir)/widgets/misc				\
 	-I$(top_srcdir)						\
 	-I$(top_srcdir)/shell					\
 	-I$(top_builddir)					\
@@ -60,8 +57,8 @@ libeshell_la_CPPFLAGS =						\
 	-DG_LOG_DOMAIN=\"evolution-shell\"			\
 	$(EVOLUTION_DATA_SERVER_CFLAGS)				\
 	$(GNOME_PLATFORM_CFLAGS)				\
-	$(EGG_SMCLIENT_CFLAGS)					\
 	$(GTKHTML_CFLAGS)					\
+	$(CHAMPLAIN_CFLAGS)					\
 	$(CLUTTER_GTK_CFLAGS)
 
 libeshell_la_SOURCES =				\
@@ -92,14 +89,9 @@ libeshell_la_LDFLAGS = -avoid-version $(NO_UNDEFINED)
 
 libeshell_la_LIBADD =					\
 	$(top_builddir)/e-util/libeutil.la		\
-	$(top_builddir)/filter/libfilter.la		\
-	$(top_builddir)/widgets/misc/libemiscwidgets.la	\
-	$(top_builddir)/widgets/menus/libmenus.la	\
-	$(top_builddir)/libemail-utils/libemail-utils.la \
-	$(top_builddir)/libevolution-utils/libevolution-utils.la \
 	$(EVOLUTION_DATA_SERVER_LIBS)			\
 	$(GNOME_PLATFORM_LIBS)				\
-	$(EGG_SMCLIENT_LIBS)				\
+	$(CHAMPLAIN_LIBS)				\
 	$(CLUTTER_GTK_LIBS)
 
 # Evolution executable
@@ -111,9 +103,6 @@ endif
 evolution_CPPFLAGS =						\
 	$(AM_CPPFLAGS)						\
 	$(NM_CPPFLAGS)						\
-	-I$(top_srcdir)/widgets					\
-	-I$(top_srcdir)/widgets/menus				\
-	-I$(top_srcdir)/widgets/misc				\
 	-I$(top_srcdir)						\
 	-I$(top_srcdir)/shell					\
 	-DEVOLUTION_BINDIR=\""$(bindir)"\"			\
@@ -134,7 +123,7 @@ evolution_CPPFLAGS =						\
 	$(EVOLUTION_DATA_SERVER_CFLAGS)				\
 	$(GNOME_PLATFORM_CFLAGS)				\
 	$(GTKHTML_CFLAGS)					\
-	$(DBUS_GLIB_CFLAGS)					\
+	$(CHAMPLAIN_CFLAGS)					\
 	$(CLUTTER_GTK_CFLAGS)
 
 evolution_SOURCES =				\
@@ -143,16 +132,10 @@ evolution_SOURCES =				\
 
 evolution_LDADD =							\
 	libeshell.la							\
-	$(top_builddir)/widgets/e-timezone-dialog/libetimezonedialog.la	\
-	$(top_builddir)/widgets/menus/libmenus.la			\
-	$(top_builddir)/widgets/misc/libemiscwidgets.la			\
-	$(top_builddir)/libemail-utils/libemail-utils.la		\
 	$(top_builddir)/e-util/libeutil.la				\
-	$(top_builddir)/filter/libfilter.la				\
-	$(top_builddir)/libevolution-utils/libevolution-utils.la \
 	$(EVOLUTION_DATA_SERVER_LIBS)					\
 	$(GNOME_PLATFORM_LIBS)						\
-	$(DBUS_GLIB_LIBS)						\
+	$(CHAMPLAIN_LIBS)						\
 	$(CLUTTER_GTK_LIBS)						\
 	$(EVOLUTIONICON)
 
diff --git a/shell/e-convert-local-mail.c b/shell/e-convert-local-mail.c
index 7e94ee7..f7e9ac3 100644
--- a/shell/e-convert-local-mail.c
+++ b/shell/e-convert-local-mail.c
@@ -22,7 +22,6 @@
 #include <camel/camel.h>
 
 #include <shell/e-shell.h>
-#include <libevolution-utils/e-alert-dialog.h>
 
 /* Forward Declarations */
 void e_convert_local_mail (EShell *shell);
diff --git a/shell/e-shell-backend.c b/shell/e-shell-backend.c
index 3310122..d546bc7 100644
--- a/shell/e-shell-backend.c
+++ b/shell/e-shell-backend.c
@@ -502,6 +502,24 @@ e_shell_backend_is_busy (EShellBackend *shell_backend)
 }
 
 /**
+ * e_shell_backend_get_prefer_new_item:
+ * @shell_backend: an #EShellBackend
+ *
+ * Returns: Name of a preferred item in New toolbar button, %NULL or
+ * an empty string for no preference.
+ *
+ * Since: 3.4
+ **/
+const gchar *
+e_shell_backend_get_prefer_new_item (EShellBackend *shell_backend)
+{
+	g_return_val_if_fail (shell_backend != NULL, NULL);
+	g_return_val_if_fail (E_IS_SHELL_BACKEND (shell_backend), NULL);
+
+	return shell_backend->priv->prefer_new_item;
+}
+
+/**
  * e_shell_backend_set_prefer_new_item:
  * @shell_backend: an #EShellBackend
  * @prefer_new_item: name of an item
@@ -528,24 +546,6 @@ e_shell_backend_set_prefer_new_item (EShellBackend *shell_backend,
 }
 
 /**
- * e_shell_backend_get_prefer_new_item:
- * @shell_backend: an #EShellBackend
- *
- * Returns: Name of a preferred item in New toolbar button, %NULL or
- * an empty string for no preference.
- *
- * Since: 3.4
- **/
-const gchar *
-e_shell_backend_get_prefer_new_item (EShellBackend *shell_backend)
-{
-	g_return_val_if_fail (shell_backend != NULL, NULL);
-	g_return_val_if_fail (E_IS_SHELL_BACKEND (shell_backend), NULL);
-
-	return shell_backend->priv->prefer_new_item;
-}
-
-/**
  * e_shell_backend_cancel_all:
  * @shell_backend: an #EShellBackend
  *
diff --git a/shell/e-shell-backend.h b/shell/e-shell-backend.h
index cd8cf14..0a68d42 100644
--- a/shell/e-shell-backend.h
+++ b/shell/e-shell-backend.h
@@ -25,7 +25,7 @@
 #include <libebackend/libebackend.h>
 
 #include <shell/e-shell-common.h>
-#include <e-util/e-activity.h>
+#include <e-util/e-util.h>
 
 /* Standard GObject macros */
 #define E_TYPE_SHELL_BACKEND \
diff --git a/shell/e-shell-content.c b/shell/e-shell-content.c
index fe35f66..942e10b 100644
--- a/shell/e-shell-content.c
+++ b/shell/e-shell-content.c
@@ -34,13 +34,6 @@
 #include <glib/gi18n.h>
 #include <libebackend/libebackend.h>
 
-#include "e-util/e-util.h"
-#include "libevolution-utils/e-alert-dialog.h"
-#include "libevolution-utils/e-alert-sink.h"
-#include "filter/e-rule-editor.h"
-#include "widgets/misc/e-action-combo-box.h"
-#include "widgets/misc/e-alert-bar.h"
-
 #include "e-shell-backend.h"
 #include "e-shell-searchbar.h"
 #include "e-shell-view.h"
diff --git a/shell/e-shell-migrate.c b/shell/e-shell-migrate.c
index 6ebc585..22552da 100644
--- a/shell/e-shell-migrate.c
+++ b/shell/e-shell-migrate.c
@@ -33,10 +33,6 @@
 #include <glib/gi18n.h>
 #include <glib/gstdio.h>
 
-#include "libevolution-utils/e-alert-dialog.h"
-#include "e-util/e-file-utils.h"
-#include "e-util/e-util.h"
-
 #include "es-event.h"
 #include "evo-version.h"
 
diff --git a/shell/e-shell-searchbar.c b/shell/e-shell-searchbar.c
index 46fbdf2..b5d490d 100644
--- a/shell/e-shell-searchbar.c
+++ b/shell/e-shell-searchbar.c
@@ -34,9 +34,6 @@
 #include <glib/gi18n-lib.h>
 #include <libebackend/libebackend.h>
 
-#include "e-util/e-util.h"
-#include "widgets/misc/e-action-combo-box.h"
-
 #include "e-shell-window-actions.h"
 
 #define E_SHELL_SEARCHBAR_GET_PRIVATE(obj) \
diff --git a/shell/e-shell-searchbar.h b/shell/e-shell-searchbar.h
index 6c68e72..965cc97 100644
--- a/shell/e-shell-searchbar.h
+++ b/shell/e-shell-searchbar.h
@@ -24,7 +24,6 @@
 
 #include <shell/e-shell-common.h>
 #include <shell/e-shell-view.h>
-#include <misc/e-action-combo-box.h>
 
 /* Standard GObject macros */
 #define E_TYPE_SHELL_SEARCHBAR \
diff --git a/shell/e-shell-sidebar.c b/shell/e-shell-sidebar.c
index 95afe57..b75cd8d 100644
--- a/shell/e-shell-sidebar.c
+++ b/shell/e-shell-sidebar.c
@@ -33,8 +33,6 @@
 
 #include <libebackend/libebackend.h>
 
-#include <libevolution-utils/e-alert-sink.h>
-#include <e-util/e-unicode.h>
 #include <shell/e-shell-view.h>
 
 #define E_SHELL_SIDEBAR_GET_PRIVATE(obj) \
diff --git a/shell/e-shell-taskbar.c b/shell/e-shell-taskbar.c
index 3a3d311..9a0624f 100644
--- a/shell/e-shell-taskbar.c
+++ b/shell/e-shell-taskbar.c
@@ -34,7 +34,6 @@
 #include <libebackend/libebackend.h>
 
 #include <e-shell-view.h>
-#include <misc/e-activity-proxy.h>
 
 #define E_SHELL_TASKBAR_GET_PRIVATE(obj) \
 	(G_TYPE_INSTANCE_GET_PRIVATE \
diff --git a/shell/e-shell-utils.c b/shell/e-shell-utils.c
index 1fd63a9..e790d58 100644
--- a/shell/e-shell-utils.c
+++ b/shell/e-shell-utils.c
@@ -35,8 +35,6 @@
 
 #include <libedataserver/libedataserver.h>
 
-#include "widgets/misc/e-import-assistant.h"
-
 /**
  * e_shell_configure_ui_manager:
  * @shell: an #EShell
diff --git a/shell/e-shell-utils.h b/shell/e-shell-utils.h
index 897981e..fb33c3c 100644
--- a/shell/e-shell-utils.h
+++ b/shell/e-shell-utils.h
@@ -23,8 +23,6 @@
 #define E_SHELL_UTILS_H
 
 #include <shell/e-shell.h>
-#include <misc/e-web-view.h>
-#include <e-util/e-ui-manager.h>
 
 G_BEGIN_DECLS
 
diff --git a/shell/e-shell-view.c b/shell/e-shell-view.c
index 16d09fc..e662666 100644
--- a/shell/e-shell-view.c
+++ b/shell/e-shell-view.c
@@ -35,14 +35,6 @@
 #include <glib/gi18n.h>
 #include <libebackend/libebackend.h>
 
-#include "e-util/e-file-utils.h"
-#include "e-util/e-plugin-ui.h"
-#include "e-util/e-source-util.h"
-#include "e-util/e-ui-manager.h"
-#include "e-util/e-util-private.h"
-#include "e-util/e-util.h"
-#include "filter/e-rule-context.h"
-
 #include "e-shell-searchbar.h"
 #include "e-shell-window-actions.h"
 
diff --git a/shell/e-shell-view.h b/shell/e-shell-view.h
index fe2b63c..6aea66f 100644
--- a/shell/e-shell-view.h
+++ b/shell/e-shell-view.h
@@ -24,6 +24,8 @@
 
 #include <libedataserver/libedataserver.h>
 
+#include <e-util/e-util.h>
+
 #include <shell/e-shell-common.h>
 #include <shell/e-shell-backend.h>
 #include <shell/e-shell-content.h>
@@ -31,11 +33,6 @@
 #include <shell/e-shell-taskbar.h>
 #include <shell/e-shell-window.h>
 
-#include <filter/e-filter-rule.h>
-#include <filter/e-rule-context.h>
-#include <menus/gal-view-collection.h>
-#include <menus/gal-view-instance.h>
-
 /* Standard GObject macros */
 #define E_TYPE_SHELL_VIEW \
 	(e_shell_view_get_type ())
diff --git a/shell/e-shell-window-actions.c b/shell/e-shell-window-actions.c
index cecc168..a1d6649 100644
--- a/shell/e-shell-window-actions.c
+++ b/shell/e-shell-window-actions.c
@@ -24,13 +24,6 @@
 #endif
 
 #include "e-shell-window-private.h"
-#include "e-preferences-window.h"
-
-#include <e-util/e-dialog-utils.h>
-#include <e-util/e-print.h>
-#include <gal-define-views-dialog.h>
-
-#include <libedataserverui/libedataserverui.h>
 
 #define EVOLUTION_COPYRIGHT \
 	"Copyright \xC2\xA9 1999 - 2008 Novell, Inc. and Others\n" \
diff --git a/shell/e-shell-window-private.h b/shell/e-shell-window-private.h
index 7a2a40c..d69546c 100644
--- a/shell/e-shell-window-private.h
+++ b/shell/e-shell-window-private.h
@@ -29,17 +29,6 @@
 
 #include <libebackend/libebackend.h>
 
-#include <e-util/e-util.h>
-#include <e-util/e-util-private.h>
-#include <libevolution-utils/e-alert-dialog.h>
-#include <libevolution-utils/e-alert-sink.h>
-#include <e-util/e-plugin-ui.h>
-#include <widgets/misc/e-alert-bar.h>
-#include <widgets/misc/e-import-assistant.h>
-#include <widgets/misc/e-menu-tool-button.h>
-#include <widgets/misc/e-online-button.h>
-#include <widgets/misc/e-popup-action.h>
-
 #include <e-shell.h>
 #include <e-shell-content.h>
 #include <e-shell-view.h>
diff --git a/shell/e-shell-window.c b/shell/e-shell-window.c
index 2202757..3e9be0f 100644
--- a/shell/e-shell-window.c
+++ b/shell/e-shell-window.c
@@ -1576,59 +1576,59 @@ e_shell_window_set_toolbar_visible (EShellWindow *shell_window,
 }
 
 /**
- * e_shell_window_set_toolbar_new_prefer_item:
+ * e_shell_window_get_toolbar_new_prefer_item:
  * @shell_window: an #EShellWindow
- * @prefer_item: prefer-item name to be set
  *
- * Sets prefer item on the New button for current view.
+ * Returns: name of preferred item on the New button for current view.
  *
  * Since: 3.4
  **/
-void
-e_shell_window_set_toolbar_new_prefer_item (EShellWindow *shell_window,
-                                            const gchar *prefer_item)
+const gchar *
+e_shell_window_get_toolbar_new_prefer_item (EShellWindow *shell_window)
 {
 	GtkWidget *toolbar;
 	GtkToolItem *item;
 
-	g_return_if_fail (shell_window != NULL);
-	g_return_if_fail (E_IS_SHELL_WINDOW (shell_window));
+	g_return_val_if_fail (shell_window != NULL, NULL);
+	g_return_val_if_fail (E_IS_SHELL_WINDOW (shell_window), NULL);
 
 	toolbar = e_shell_window_get_managed_widget (shell_window, "/main-toolbar");
-	g_return_if_fail (toolbar != NULL);
+	g_return_val_if_fail (toolbar != NULL, NULL);
 
 	item = gtk_toolbar_get_nth_item (GTK_TOOLBAR (toolbar), 0);
-	g_return_if_fail (item != NULL);
-	g_return_if_fail (E_IS_MENU_TOOL_BUTTON (item));
+	g_return_val_if_fail (item != NULL, NULL);
+	g_return_val_if_fail (E_IS_MENU_TOOL_BUTTON (item), NULL);
 
-	e_menu_tool_button_set_prefer_item (E_MENU_TOOL_BUTTON (item), prefer_item);
+	return e_menu_tool_button_get_prefer_item (E_MENU_TOOL_BUTTON (item));
 }
 
 /**
- * e_shell_window_get_toolbar_new_prefer_item:
+ * e_shell_window_set_toolbar_new_prefer_item:
  * @shell_window: an #EShellWindow
+ * @prefer_item: prefer-item name to be set
  *
- * Returns: name of preferred item on the New button for current view.
+ * Sets prefer item on the New button for current view.
  *
  * Since: 3.4
  **/
-const gchar *
-e_shell_window_get_toolbar_new_prefer_item (EShellWindow *shell_window)
+void
+e_shell_window_set_toolbar_new_prefer_item (EShellWindow *shell_window,
+                                            const gchar *prefer_item)
 {
 	GtkWidget *toolbar;
 	GtkToolItem *item;
 
-	g_return_val_if_fail (shell_window != NULL, NULL);
-	g_return_val_if_fail (E_IS_SHELL_WINDOW (shell_window), NULL);
+	g_return_if_fail (shell_window != NULL);
+	g_return_if_fail (E_IS_SHELL_WINDOW (shell_window));
 
 	toolbar = e_shell_window_get_managed_widget (shell_window, "/main-toolbar");
-	g_return_val_if_fail (toolbar != NULL, NULL);
+	g_return_if_fail (toolbar != NULL);
 
 	item = gtk_toolbar_get_nth_item (GTK_TOOLBAR (toolbar), 0);
-	g_return_val_if_fail (item != NULL, NULL);
-	g_return_val_if_fail (E_IS_MENU_TOOL_BUTTON (item), NULL);
+	g_return_if_fail (item != NULL);
+	g_return_if_fail (E_IS_MENU_TOOL_BUTTON (item));
 
-	return e_menu_tool_button_get_prefer_item (E_MENU_TOOL_BUTTON (item));
+	e_menu_tool_button_set_prefer_item (E_MENU_TOOL_BUTTON (item), prefer_item);
 }
 
 /**
diff --git a/shell/e-shell-window.h b/shell/e-shell-window.h
index 366a218..abd940f 100644
--- a/shell/e-shell-window.h
+++ b/shell/e-shell-window.h
@@ -23,7 +23,6 @@
 #define E_SHELL_WINDOW_H
 
 #include <shell/e-shell.h>
-#include <misc/e-focus-tracker.h>
 
 /* Standard GObject macros */
 #define E_TYPE_SHELL_WINDOW \
@@ -135,11 +134,11 @@ gboolean	e_shell_window_get_toolbar_visible
 void		e_shell_window_set_toolbar_visible
 						(EShellWindow *shell_window,
 						 gboolean toolbar_visible);
+const gchar *	e_shell_window_get_toolbar_new_prefer_item
+						(EShellWindow *shell_window);
 void		e_shell_window_set_toolbar_new_prefer_item
 						(EShellWindow *shell_window,
 						 const gchar *prefer_item);
-const gchar *	e_shell_window_get_toolbar_new_prefer_item
-						(EShellWindow *shell_window);
 
 /* These should be called from the shell backend's window_created() handler. */
 
diff --git a/shell/e-shell.c b/shell/e-shell.c
index b96cbb9..f938f45 100644
--- a/shell/e-shell.c
+++ b/shell/e-shell.c
@@ -33,11 +33,8 @@
 
 #include <glib/gi18n.h>
 #include <libebackend/libebackend.h>
-#include <libedataserverui/libedataserverui.h>
 
-#include "e-util/e-util.h"
 #include "e-util/e-util-private.h"
-#include "widgets/misc/e-preferences-window.h"
 
 #include "e-shell-backend.h"
 #include "e-shell-enumtypes.h"
diff --git a/shell/e-shell.h b/shell/e-shell.h
index 4dcd7e7..84bb9f2 100644
--- a/shell/e-shell.h
+++ b/shell/e-shell.h
@@ -24,8 +24,7 @@
 
 #include <libedataserver/libedataserver.h>
 
-#include <e-util/e-activity.h>
-#include <libevolution-utils/e-alert.h>
+#include <e-util/e-util.h>
 
 #include <shell/e-shell-common.h>
 #include <shell/e-shell-backend.h>
diff --git a/shell/es-event.h b/shell/es-event.h
index eba3f3d..67ff1b4 100644
--- a/shell/es-event.h
+++ b/shell/es-event.h
@@ -24,7 +24,7 @@
 #ifndef __ES_EVENT_H__
 #define __ES_EVENT_H__
 
-#include "e-util/e-event.h"
+#include <e-util/e-util.h>
 
 G_BEGIN_DECLS
 
diff --git a/shell/main.c b/shell/main.c
index 0869ef6..d4e65b1 100644
--- a/shell/main.c
+++ b/shell/main.c
@@ -55,8 +55,6 @@
 #include <libxml/parser.h>
 #include <libxml/tree.h>
 
-#include <libedataserverui/libedataserverui.h>
-
 #include <webkit/webkit.h>
 
 #include "e-shell.h"
@@ -64,12 +62,6 @@
 #include "e-shell-meego.h"
 #include "es-event.h"
 
-#include "e-util/e-dialog-utils.h"
-#include "e-util/e-import.h"
-#include "e-util/e-plugin.h"
-#include "e-util/e-plugin-ui.h"
-#include "e-util/e-util-private.h"
-#include "e-util/e-util.h"
 #ifdef G_OS_WIN32
 #include "e-util/e-win32-defaults.h"
 #endif
diff --git a/smime/gui/Makefile.am b/smime/gui/Makefile.am
index d4e04f2..d346bf5 100644
--- a/smime/gui/Makefile.am
+++ b/smime/gui/Makefile.am
@@ -9,8 +9,6 @@ libevolution_smime_la_CPPFLAGS =			\
 	-I$(top_builddir)/smime/lib			\
 	-I$(top_srcdir)/shell				\
 	-I$(top_builddir)/shell				\
-	-I$(top_srcdir)/widgets				\
-	-I$(top_builddir)/widgets			\
 	-DEVOLUTION_DATADIR=\""$(datadir)"\"		\
 	-DEVOLUTION_ETSPECDIR=\""$(etspecdir)"\"	\
 	-DEVOLUTION_IMAGESDIR=\""$(imagesdir)"\"	\
@@ -19,6 +17,8 @@ libevolution_smime_la_CPPFLAGS =			\
 	-DPREFIX=\""$(prefix)"\"			\
 	$(EVOLUTION_DATA_SERVER_CFLAGS)			\
 	$(GNOME_PLATFORM_CFLAGS)			\
+	$(CHAMPLAIN_CFLAGS)				\
+	$(GTKHTML_CFLAGS)				\
 	$(CERT_UI_CFLAGS)
 
 libevolution_smime_la_SOURCES = 	\
@@ -40,10 +40,10 @@ libevolution_smime_la_LIBADD =				\
 	$(top_builddir)/e-util/libeutil.la		\
 	$(top_builddir)/shell/libeshell.la		\
 	$(top_builddir)/smime/lib/libessmime.la		\
-	$(top_builddir)/widgets/misc/libemiscwidgets.la	\
-	$(top_builddir)/libevolution-utils/libevolution-utils.la \
 	$(EVOLUTION_DATA_SERVER_LIBS)			\
 	$(GNOME_PLATFORM_LIBS)				\
+	$(CHAMPLAIN_LIBS)				\
+	$(GTKHTML_LIBS)					\
 	$(CERT_UI_LIBS)
 
 libevolution_smime_la_LDFLAGS = -avoid-version $(NO_UNDEFINED)
diff --git a/smime/gui/certificate-manager.c b/smime/gui/certificate-manager.c
index 109bbba..55f951e 100644
--- a/smime/gui/certificate-manager.c
+++ b/smime/gui/certificate-manager.c
@@ -46,10 +46,6 @@
 #include <pk11func.h>
 
 #include "shell/e-shell.h"
-#include "e-util/e-dialog-utils.h"
-#include "e-util/e-util.h"
-#include "e-util/e-util-private.h"
-#include "widgets/misc/e-preferences-window.h"
 
 #define E_CERT_MANAGER_CONFIG_GET_PRIVATE(obj) \
 	(G_TYPE_INSTANCE_GET_PRIVATE \
diff --git a/smime/gui/certificate-manager.h b/smime/gui/certificate-manager.h
index 0f74d30..0b873f8 100644
--- a/smime/gui/certificate-manager.h
+++ b/smime/gui/certificate-manager.h
@@ -25,7 +25,6 @@
 
 #include <gtk/gtk.h>
 #include <shell/e-shell.h>
-#include <widgets/misc/e-preferences-window.h>
 
 /* Standard GObject macros */
 #define E_TYPE_CERT_MANAGER_CONFIG \
diff --git a/smime/gui/component.c b/smime/gui/component.c
index bf17219..576d890 100644
--- a/smime/gui/component.c
+++ b/smime/gui/component.c
@@ -29,7 +29,8 @@
 #include <gtk/gtk.h>
 
 #include <glib/gi18n.h>
-#include <libedataserverui/libedataserverui.h>
+
+#include "e-util/e-util.h"
 
 #include "ca-trust-dialog.h"
 #include "e-cert-db.h"
diff --git a/smime/lib/Makefile.am b/smime/lib/Makefile.am
index 5ef4707..ed052bf 100644
--- a/smime/lib/Makefile.am
+++ b/smime/lib/Makefile.am
@@ -14,6 +14,8 @@ libessmime_la_CPPFLAGS =				\
 	-DPREFIX=\""$(prefix)"\"			\
 	$(EVOLUTION_DATA_SERVER_CFLAGS)			\
 	$(GNOME_PLATFORM_CFLAGS)			\
+	$(CHAMPLAIN_CFLAGS)				\
+	$(GTKHTML_CFLAGS)				\
 	$(CERT_UI_CFLAGS)
 
 libessmime_la_SOURCES = 	\
@@ -29,10 +31,11 @@ libessmime_la_SOURCES = 	\
 	e-pkcs12.h
 
 libessmime_la_LIBADD =				\
-	$(top_builddir)/libemail-utils/libemail-utils.la	\
 	$(top_builddir)/e-util/libeutil.la	\
 	$(EVOLUTION_DATA_SERVER_LIBS)		\
 	$(GNOME_PLATFORM_LIBS)			\
+	$(CHAMPLAIN_LIBS)			\
+	$(GTKHTML_LIBS)				\
 	$(CERT_UI_LIBS)
 
 libessmime_la_LDFLAGS = -avoid-version $(NO_UNDEFINED)
diff --git a/smime/lib/e-cert-db.c b/smime/lib/e-cert-db.c
index a98d085..1a5803f 100644
--- a/smime/lib/e-cert-db.c
+++ b/smime/lib/e-cert-db.c
@@ -48,9 +48,7 @@
 #include <glib/gi18n.h>
 #include <glib/gstdio.h>
 
-#include <camel/camel.h>        /* FIXME: this is where camel_init is defined; it shouldn't include everything else */
-
-#include <libedataserverui/libedataserverui.h>
+#include <camel/camel.h>
 
 /* private NSS defines used by PSM */
 /* (must be declated before cert.h) */
diff --git a/smime/lib/e-pkcs12.c b/smime/lib/e-pkcs12.c
index fa61033..eef6acd 100644
--- a/smime/lib/e-pkcs12.c
+++ b/smime/lib/e-pkcs12.c
@@ -51,7 +51,7 @@
 #include <fcntl.h>
 #include <unistd.h>
 
-#include <libedataserverui/libedataserverui.h>
+#include "e-util/e-util.h"
 
 #include "e-cert-db.h"
 #include "e-pkcs12.h"



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]