aboutsummaryrefslogtreecommitdiffstats
path: root/e-util
diff options
context:
space:
mode:
authorMatthew Barnes <mbarnes@redhat.com>2012-12-10 21:09:59 +0800
committerMatthew Barnes <mbarnes@redhat.com>2012-12-13 03:33:43 +0800
commitd09d8de870b6697c8a8b262e7e077b871a69b315 (patch)
tree3b718882e7a0bb0a996daf2967a033d91714c9b5 /e-util
parentb61331ed03ac1c7a9b8614e25510040b9c60ae02 (diff)
downloadgsoc2013-evolution-d09d8de870b6697c8a8b262e7e077b871a69b315.tar
gsoc2013-evolution-d09d8de870b6697c8a8b262e7e077b871a69b315.tar.gz
gsoc2013-evolution-d09d8de870b6697c8a8b262e7e077b871a69b315.tar.bz2
gsoc2013-evolution-d09d8de870b6697c8a8b262e7e077b871a69b315.tar.lz
gsoc2013-evolution-d09d8de870b6697c8a8b262e7e077b871a69b315.tar.xz
gsoc2013-evolution-d09d8de870b6697c8a8b262e7e077b871a69b315.tar.zst
gsoc2013-evolution-d09d8de870b6697c8a8b262e7e077b871a69b315.zip
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.
Diffstat (limited to 'e-util')
-rw-r--r--e-util/Makefile.am755
-rw-r--r--e-util/arrow-down.xpm21
-rw-r--r--e-util/arrow-up.xpm21
-rw-r--r--e-util/check-empty.xpm21
-rw-r--r--e-util/check-filled.xpm21
-rw-r--r--e-util/e-action-combo-box.c578
-rw-r--r--e-util/e-action-combo-box.h89
-rw-r--r--e-util/e-activity-bar.c366
-rw-r--r--e-util/e-activity-bar.h71
-rw-r--r--e-util/e-activity-proxy.c381
-rw-r--r--e-util/e-activity-proxy.h74
-rw-r--r--e-util/e-activity.c3
-rw-r--r--e-util/e-activity.h7
-rw-r--r--e-util/e-alarm-selector.c94
-rw-r--r--e-util/e-alarm-selector.h67
-rw-r--r--e-util/e-alert-bar.c390
-rw-r--r--e-util/e-alert-bar.h72
-rw-r--r--e-util/e-alert-dialog.c403
-rw-r--r--e-util/e-alert-dialog.h81
-rw-r--r--e-util/e-alert-sink.c93
-rw-r--r--e-util/e-alert-sink.h63
-rw-r--r--e-util/e-alert.c1003
-rw-r--r--e-util/e-alert.h119
-rw-r--r--e-util/e-attachment-bar.c778
-rw-r--r--e-util/e-attachment-bar.h83
-rw-r--r--e-util/e-attachment-button.c868
-rw-r--r--e-util/e-attachment-button.h91
-rw-r--r--e-util/e-attachment-dialog.c425
-rw-r--r--e-util/e-attachment-dialog.h77
-rw-r--r--e-util/e-attachment-handler-image.c246
-rw-r--r--e-util/e-attachment-handler-image.h69
-rw-r--r--e-util/e-attachment-handler-sendto.c229
-rw-r--r--e-util/e-attachment-handler-sendto.h66
-rw-r--r--e-util/e-attachment-handler.c133
-rw-r--r--e-util/e-attachment-handler.h84
-rw-r--r--e-util/e-attachment-icon-view.c570
-rw-r--r--e-util/e-attachment-icon-view.h71
-rw-r--r--e-util/e-attachment-paned.c904
-rw-r--r--e-util/e-attachment-paned.h99
-rw-r--r--e-util/e-attachment-store.c1280
-rw-r--r--e-util/e-attachment-store.h137
-rw-r--r--e-util/e-attachment-tree-view.c623
-rw-r--r--e-util/e-attachment-tree-view.h70
-rw-r--r--e-util/e-attachment-view.c1906
-rw-r--r--e-util/e-attachment-view.h244
-rw-r--r--e-util/e-attachment.c2882
-rw-r--r--e-util/e-attachment.h159
-rw-r--r--e-util/e-auth-combo-box.c266
-rw-r--r--e-util/e-auth-combo-box.h75
-rw-r--r--e-util/e-autocomplete-selector.c96
-rw-r--r--e-util/e-autocomplete-selector.h68
-rw-r--r--e-util/e-bit-array.c1
-rw-r--r--e-util/e-bit-array.h4
-rw-r--r--e-util/e-book-source-config.c287
-rw-r--r--e-util/e-book-source-config.h71
-rw-r--r--e-util/e-buffer-tagger.c692
-rw-r--r--e-util/e-buffer-tagger.h39
-rw-r--r--e-util/e-cal-source-config.c431
-rw-r--r--e-util/e-cal-source-config.h76
-rw-r--r--e-util/e-calendar-item.c3773
-rw-r--r--e-util/e-calendar-item.h392
-rw-r--r--e-util/e-calendar.c848
-rw-r--r--e-util/e-calendar.h112
-rw-r--r--e-util/e-canvas-background.c279
-rw-r--r--e-util/e-canvas-background.h75
-rw-r--r--e-util/e-canvas-utils.c222
-rw-r--r--e-util/e-canvas-utils.h59
-rw-r--r--e-util/e-canvas-vbox.c410
-rw-r--r--e-util/e-canvas-vbox.h92
-rw-r--r--e-util/e-canvas.c880
-rw-r--r--e-util/e-canvas.h141
-rw-r--r--e-util/e-categories-config.c5
-rw-r--r--e-util/e-categories-config.h4
-rw-r--r--e-util/e-categories-dialog.c155
-rw-r--r--e-util/e-categories-dialog.h73
-rw-r--r--e-util/e-categories-editor.c435
-rw-r--r--e-util/e-categories-editor.h88
-rw-r--r--e-util/e-categories-selector.c587
-rw-r--r--e-util/e-categories-selector.h97
-rw-r--r--e-util/e-category-completion.c505
-rw-r--r--e-util/e-category-completion.h72
-rw-r--r--e-util/e-category-editor.c343
-rw-r--r--e-util/e-category-editor.h81
-rw-r--r--e-util/e-cell-checkbox.c102
-rw-r--r--e-util/e-cell-checkbox.h71
-rw-r--r--e-util/e-cell-combo.c838
-rw-r--r--e-util/e-cell-combo.h89
-rw-r--r--e-util/e-cell-date-edit.c1039
-rw-r--r--e-util/e-cell-date-edit.h124
-rw-r--r--e-util/e-cell-date.c130
-rw-r--r--e-util/e-cell-date.h74
-rw-r--r--e-util/e-cell-hbox.c353
-rw-r--r--e-util/e-cell-hbox.h91
-rw-r--r--e-util/e-cell-number.c95
-rw-r--r--e-util/e-cell-number.h71
-rw-r--r--e-util/e-cell-percent.c160
-rw-r--r--e-util/e-cell-percent.h76
-rw-r--r--e-util/e-cell-pixbuf.c389
-rw-r--r--e-util/e-cell-pixbuf.h75
-rw-r--r--e-util/e-cell-popup.c550
-rw-r--r--e-util/e-cell-popup.h118
-rw-r--r--e-util/e-cell-renderer-color.c243
-rw-r--r--e-util/e-cell-renderer-color.h79
-rw-r--r--e-util/e-cell-size.c112
-rw-r--r--e-util/e-cell-size.h72
-rw-r--r--e-util/e-cell-text.c2810
-rw-r--r--e-util/e-cell-text.h195
-rw-r--r--e-util/e-cell-toggle.c469
-rw-r--r--e-util/e-cell-toggle.h83
-rw-r--r--e-util/e-cell-tree.c880
-rw-r--r--e-util/e-cell-tree.h90
-rw-r--r--e-util/e-cell-vbox.c341
-rw-r--r--e-util/e-cell-vbox.h93
-rw-r--r--e-util/e-cell.c679
-rw-r--r--e-util/e-cell.h299
-rw-r--r--e-util/e-charset-combo-box.c407
-rw-r--r--e-util/e-charset-combo-box.h73
-rw-r--r--e-util/e-charset.h4
-rw-r--r--e-util/e-client-utils.c445
-rw-r--r--e-util/e-client-utils.h64
-rw-r--r--e-util/e-config.h6
-rw-r--r--e-util/e-contact-map-window.c500
-rw-r--r--e-util/e-contact-map-window.h85
-rw-r--r--e-util/e-contact-map.c407
-rw-r--r--e-util/e-contact-map.h110
-rw-r--r--e-util/e-contact-marker.c624
-rw-r--r--e-util/e-contact-marker.h88
-rw-r--r--e-util/e-contact-store.c1370
-rw-r--r--e-util/e-contact-store.h94
-rw-r--r--e-util/e-dateedit.c2497
-rw-r--r--e-util/e-dateedit.h219
-rw-r--r--e-util/e-datetime-format.c5
-rw-r--r--e-util/e-datetime-format.h4
-rw-r--r--e-util/e-destination-store.c751
-rw-r--r--e-util/e-destination-store.h106
-rw-r--r--e-util/e-dialog-utils.h4
-rw-r--r--e-util/e-dialog-widgets.h4
-rw-r--r--e-util/e-event.h6
-rw-r--r--e-util/e-file-request.c2
-rw-r--r--e-util/e-file-request.h4
-rw-r--r--e-util/e-file-utils.h4
-rw-r--r--e-util/e-filter-code.c102
-rw-r--r--e-util/e-filter-code.h72
-rw-r--r--e-util/e-filter-color.c163
-rw-r--r--e-util/e-filter-color.h74
-rw-r--r--e-util/e-filter-datespec.c513
-rw-r--r--e-util/e-filter-datespec.h91
-rw-r--r--e-util/e-filter-element.c446
-rw-r--r--e-util/e-filter-element.h125
-rw-r--r--e-util/e-filter-file.c261
-rw-r--r--e-util/e-filter-file.h78
-rw-r--r--e-util/e-filter-input.c304
-rw-r--r--e-util/e-filter-input.h78
-rw-r--r--e-util/e-filter-int.c230
-rw-r--r--e-util/e-filter-int.h81
-rw-r--r--e-util/e-filter-option.c566
-rw-r--r--e-util/e-filter-option.h101
-rw-r--r--e-util/e-filter-part.c513
-rw-r--r--e-util/e-filter-part.h112
-rw-r--r--e-util/e-filter-rule.c1241
-rw-r--r--e-util/e-filter-rule.h163
-rw-r--r--e-util/e-focus-tracker.c886
-rw-r--r--e-util/e-focus-tracker.h104
-rw-r--r--e-util/e-html-utils.h4
-rw-r--r--e-util/e-icon-factory.h4
-rw-r--r--e-util/e-image-chooser.c562
-rw-r--r--e-util/e-image-chooser.h80
-rw-r--r--e-util/e-import-assistant.c1436
-rw-r--r--e-util/e-import-assistant.h72
-rw-r--r--e-util/e-import.h6
-rw-r--r--e-util/e-interval-chooser.c214
-rw-r--r--e-util/e-interval-chooser.h72
-rw-r--r--e-util/e-mail-identity-combo-box.c385
-rw-r--r--e-util/e-mail-identity-combo-box.h75
-rw-r--r--e-util/e-mail-signature-combo-box.c668
-rw-r--r--e-util/e-mail-signature-combo-box.h95
-rw-r--r--e-util/e-mail-signature-editor.c914
-rw-r--r--e-util/e-mail-signature-editor.h87
-rw-r--r--e-util/e-mail-signature-manager.c708
-rw-r--r--e-util/e-mail-signature-manager.h93
-rw-r--r--e-util/e-mail-signature-preview.c358
-rw-r--r--e-util/e-mail-signature-preview.h84
-rw-r--r--e-util/e-mail-signature-script-dialog.c731
-rw-r--r--e-util/e-mail-signature-script-dialog.h94
-rw-r--r--e-util/e-mail-signature-tree-view.c395
-rw-r--r--e-util/e-mail-signature-tree-view.h80
-rw-r--r--e-util/e-map.c1429
-rw-r--r--e-util/e-map.h155
-rw-r--r--e-util/e-menu-tool-action.c59
-rw-r--r--e-util/e-menu-tool-action.h75
-rw-r--r--e-util/e-menu-tool-button.c273
-rw-r--r--e-util/e-menu-tool-button.h77
-rw-r--r--e-util/e-misc-utils.c (renamed from e-util/e-util.c)306
-rw-r--r--e-util/e-misc-utils.h175
-rw-r--r--e-util/e-mktemp.c3
-rw-r--r--e-util/e-mktemp.h4
-rw-r--r--e-util/e-name-selector-dialog.c1863
-rw-r--r--e-util/e-name-selector-dialog.h100
-rw-r--r--e-util/e-name-selector-entry.c3541
-rw-r--r--e-util/e-name-selector-entry.h124
-rw-r--r--e-util/e-name-selector-list.c790
-rw-r--r--e-util/e-name-selector-list.h82
-rw-r--r--e-util/e-name-selector-model.c663
-rw-r--r--e-util/e-name-selector-model.h108
-rw-r--r--e-util/e-name-selector.c658
-rw-r--r--e-util/e-name-selector.h94
-rw-r--r--e-util/e-online-button.c210
-rw-r--r--e-util/e-online-button.h69
-rw-r--r--e-util/e-paned.c503
-rw-r--r--e-util/e-paned.h82
-rw-r--r--e-util/e-passwords-win32.c1064
-rw-r--r--e-util/e-passwords.c890
-rw-r--r--e-util/e-passwords.h81
-rw-r--r--e-util/e-picture-gallery.c437
-rw-r--r--e-util/e-picture-gallery.h71
-rw-r--r--e-util/e-plugin-ui.c1
-rw-r--r--e-util/e-plugin-ui.h4
-rw-r--r--e-util/e-plugin.h4
-rw-r--r--e-util/e-poolv.h4
-rw-r--r--e-util/e-popup-action.c408
-rw-r--r--e-util/e-popup-action.h96
-rw-r--r--e-util/e-popup-menu.c112
-rw-r--r--e-util/e-popup-menu.h59
-rw-r--r--e-util/e-port-entry.c549
-rw-r--r--e-util/e-port-entry.h91
-rw-r--r--e-util/e-preferences-window.c643
-rw-r--r--e-util/e-preferences-window.h88
-rw-r--r--e-util/e-preview-pane.c322
-rw-r--r--e-util/e-preview-pane.h80
-rw-r--r--e-util/e-print.c2
-rw-r--r--e-util/e-print.h4
-rw-r--r--e-util/e-printable.c226
-rw-r--r--e-util/e-printable.h93
-rw-r--r--e-util/e-reflow-model.c411
-rw-r--r--e-util/e-reflow-model.h129
-rw-r--r--e-util/e-reflow.c1732
-rw-r--r--e-util/e-reflow.h145
-rw-r--r--e-util/e-rule-context.c1026
-rw-r--r--e-util/e-rule-context.h218
-rw-r--r--e-util/e-rule-editor.c920
-rw-r--r--e-util/e-rule-editor.h125
-rw-r--r--e-util/e-search-bar.c771
-rw-r--r--e-util/e-search-bar.h89
-rw-r--r--e-util/e-selectable.c168
-rw-r--r--e-util/e-selectable.h85
-rw-r--r--e-util/e-selection-model-array.c646
-rw-r--r--e-util/e-selection-model-array.h95
-rw-r--r--e-util/e-selection-model-simple.c117
-rw-r--r--e-util/e-selection-model-simple.h70
-rw-r--r--e-util/e-selection-model.c813
-rw-r--r--e-util/e-selection-model.h209
-rw-r--r--e-util/e-selection.c2
-rw-r--r--e-util/e-selection.h4
-rw-r--r--e-util/e-send-options.c767
-rw-r--r--e-util/e-send-options.h131
-rw-r--r--e-util/e-send-options.ui949
-rw-r--r--e-util/e-sorter-array.c3
-rw-r--r--e-util/e-sorter-array.h4
-rw-r--r--e-util/e-sorter.c1
-rw-r--r--e-util/e-sorter.h4
-rw-r--r--e-util/e-source-combo-box.c701
-rw-r--r--e-util/e-source-combo-box.h90
-rw-r--r--e-util/e-source-config-backend.c140
-rw-r--r--e-util/e-source-config-backend.h98
-rw-r--r--e-util/e-source-config-dialog.c394
-rw-r--r--e-util/e-source-config-dialog.h69
-rw-r--r--e-util/e-source-config.c1447
-rw-r--r--e-util/e-source-config.h120
-rw-r--r--e-util/e-source-selector-dialog.c453
-rw-r--r--e-util/e-source-selector-dialog.h85
-rw-r--r--e-util/e-source-selector.c2082
-rw-r--r--e-util/e-source-selector.h141
-rw-r--r--e-util/e-source-util.h6
-rw-r--r--e-util/e-spell-entry.c866
-rw-r--r--e-util/e-spell-entry.h63
-rw-r--r--e-util/e-stock-request.c3
-rw-r--r--e-util/e-stock-request.h4
-rw-r--r--e-util/e-table-click-to-add.c666
-rw-r--r--e-util/e-table-click-to-add.h99
-rw-r--r--e-util/e-table-col-dnd.h43
-rw-r--r--e-util/e-table-col.c222
-rw-r--r--e-util/e-table-col.h115
-rw-r--r--e-util/e-table-column-specification.c157
-rw-r--r--e-util/e-table-column-specification.h93
-rw-r--r--e-util/e-table-config.c1481
-rw-r--r--e-util/e-table-config.h134
-rw-r--r--e-util/e-table-config.ui1594
-rw-r--r--e-util/e-table-defines.h44
-rw-r--r--e-util/e-table-extras.c410
-rw-r--r--e-util/e-table-extras.h94
-rw-r--r--e-util/e-table-field-chooser-dialog.c235
-rw-r--r--e-util/e-table-field-chooser-dialog.h77
-rw-r--r--e-util/e-table-field-chooser-item.c749
-rw-r--r--e-util/e-table-field-chooser-item.h97
-rw-r--r--e-util/e-table-field-chooser.c335
-rw-r--r--e-util/e-table-field-chooser.h84
-rw-r--r--e-util/e-table-group-container.c1667
-rw-r--r--e-util/e-table-group-container.h138
-rw-r--r--e-util/e-table-group-leaf.c816
-rw-r--r--e-util/e-table-group-leaf.h110
-rw-r--r--e-util/e-table-group.c771
-rw-r--r--e-util/e-table-group.h242
-rw-r--r--e-util/e-table-header-item.c2226
-rw-r--r--e-util/e-table-header-item.h148
-rw-r--r--e-util/e-table-header-utils.c282
-rw-r--r--e-util/e-table-header-utils.h53
-rw-r--r--e-util/e-table-header.c1013
-rw-r--r--e-util/e-table-header.h144
-rw-r--r--e-util/e-table-item.c4041
-rw-r--r--e-util/e-table-item.h261
-rw-r--r--e-util/e-table-memory-callbacks.c234
-rw-r--r--e-util/e-table-memory-callbacks.h148
-rw-r--r--e-util/e-table-memory-store.c637
-rw-r--r--e-util/e-table-memory-store.h155
-rw-r--r--e-util/e-table-memory.c271
-rw-r--r--e-util/e-table-memory.h91
-rw-r--r--e-util/e-table-model.c682
-rw-r--r--e-util/e-table-model.h217
-rw-r--r--e-util/e-table-one.c252
-rw-r--r--e-util/e-table-one.h75
-rw-r--r--e-util/e-table-search.c235
-rw-r--r--e-util/e-table-search.h86
-rw-r--r--e-util/e-table-selection-model.c384
-rw-r--r--e-util/e-table-selection-model.h91
-rw-r--r--e-util/e-table-sort-info.c482
-rw-r--r--e-util/e-table-sort-info.h133
-rw-r--r--e-util/e-table-sorted-variable.c235
-rw-r--r--e-util/e-table-sorted-variable.h85
-rw-r--r--e-util/e-table-sorted.c328
-rw-r--r--e-util/e-table-sorted.h84
-rw-r--r--e-util/e-table-sorter.c519
-rw-r--r--e-util/e-table-sorter.h94
-rw-r--r--e-util/e-table-sorting-utils.c492
-rw-r--r--e-util/e-table-sorting-utils.h95
-rw-r--r--e-util/e-table-specification.c435
-rw-r--r--e-util/e-table-specification.h116
-rw-r--r--e-util/e-table-state.c320
-rw-r--r--e-util/e-table-state.h89
-rw-r--r--e-util/e-table-subset-variable.c267
-rw-r--r--e-util/e-table-subset-variable.h105
-rw-r--r--e-util/e-table-subset.c567
-rw-r--r--e-util/e-table-subset.h120
-rw-r--r--e-util/e-table-utils.c224
-rw-r--r--e-util/e-table-utils.h54
-rw-r--r--e-util/e-table-without.c412
-rw-r--r--e-util/e-table-without.h104
-rw-r--r--e-util/e-table.c3626
-rw-r--r--e-util/e-table.h403
-rw-r--r--e-util/e-text-event-processor-emacs-like.c1
-rw-r--r--e-util/e-text-event-processor-emacs-like.h4
-rw-r--r--e-util/e-text-event-processor-types.h4
-rw-r--r--e-util/e-text-event-processor.c1
-rw-r--r--e-util/e-text-event-processor.h4
-rw-r--r--e-util/e-text-model-repos.c74
-rw-r--r--e-util/e-text-model-repos.h58
-rw-r--r--e-util/e-text-model.c642
-rw-r--r--e-util/e-text-model.h112
-rw-r--r--e-util/e-text.c3405
-rw-r--r--e-util/e-text.h236
-rw-r--r--e-util/e-timezone-dialog.c870
-rw-r--r--e-util/e-timezone-dialog.h77
-rw-r--r--e-util/e-timezone-dialog.ui312
-rw-r--r--e-util/e-tree-memory-callbacks.c314
-rw-r--r--e-util/e-tree-memory-callbacks.h182
-rw-r--r--e-util/e-tree-memory.c743
-rw-r--r--e-util/e-tree-memory.h124
-rw-r--r--e-util/e-tree-model-generator.c1345
-rw-r--r--e-util/e-tree-model-generator.h104
-rw-r--r--e-util/e-tree-model.c1177
-rw-r--r--e-util/e-tree-model.h298
-rw-r--r--e-util/e-tree-selection-model.c939
-rw-r--r--e-util/e-tree-selection-model.h95
-rw-r--r--e-util/e-tree-sorted.c1433
-rw-r--r--e-util/e-tree-sorted.h104
-rw-r--r--e-util/e-tree-table-adapter.c1414
-rw-r--r--e-util/e-tree-table-adapter.h138
-rw-r--r--e-util/e-tree.c3956
-rw-r--r--e-util/e-tree.h376
-rw-r--r--e-util/e-ui-manager.c2
-rw-r--r--e-util/e-ui-manager.h4
-rw-r--r--e-util/e-unicode.h4
-rw-r--r--e-util/e-url-entry.c159
-rw-r--r--e-util/e-url-entry.h60
-rw-r--r--e-util/e-util-enums.h4
-rw-r--r--e-util/e-util.h348
-rw-r--r--e-util/e-web-view-gtkhtml.c2317
-rw-r--r--e-util/e-web-view-gtkhtml.h177
-rw-r--r--e-util/e-web-view-preview.c474
-rw-r--r--e-util/e-web-view-preview.h115
-rw-r--r--e-util/e-web-view.c2945
-rw-r--r--e-util/e-web-view.h224
-rw-r--r--e-util/e-xml-utils.c448
-rw-r--r--e-util/e-xml-utils.h93
-rw-r--r--e-util/ea-calendar-cell.c404
-rw-r--r--e-util/ea-calendar-cell.h90
-rw-r--r--e-util/ea-calendar-item.c1373
-rw-r--r--e-util/ea-calendar-item.h71
-rw-r--r--e-util/ea-cell-table.c215
-rw-r--r--e-util/ea-cell-table.h63
-rw-r--r--e-util/ea-factory.h118
-rw-r--r--e-util/ea-widgets.c36
-rw-r--r--e-util/ea-widgets.h36
-rw-r--r--e-util/evolution-source-viewer.c1176
-rw-r--r--e-util/filter.error.xml34
-rw-r--r--e-util/filter.ui591
-rw-r--r--e-util/gal-a11y-e-cell-popup.c153
-rw-r--r--e-util/gal-a11y-e-cell-popup.h65
-rw-r--r--e-util/gal-a11y-e-cell-registry.c151
-rw-r--r--e-util/gal-a11y-e-cell-registry.h75
-rw-r--r--e-util/gal-a11y-e-cell-toggle.c198
-rw-r--r--e-util/gal-a11y-e-cell-toggle.h67
-rw-r--r--e-util/gal-a11y-e-cell-tree.c266
-rw-r--r--e-util/gal-a11y-e-cell-tree.h66
-rw-r--r--e-util/gal-a11y-e-cell-vbox.c235
-rw-r--r--e-util/gal-a11y-e-cell-vbox.h67
-rw-r--r--e-util/gal-a11y-e-cell.c648
-rw-r--r--e-util/gal-a11y-e-cell.h112
-rw-r--r--e-util/gal-a11y-e-table-click-to-add-factory.c108
-rw-r--r--e-util/gal-a11y-e-table-click-to-add-factory.h52
-rw-r--r--e-util/gal-a11y-e-table-click-to-add.c358
-rw-r--r--e-util/gal-a11y-e-table-click-to-add.h58
-rw-r--r--e-util/gal-a11y-e-table-column-header.c243
-rw-r--r--e-util/gal-a11y-e-table-column-header.h59
-rw-r--r--e-util/gal-a11y-e-table-factory.c101
-rw-r--r--e-util/gal-a11y-e-table-factory.h53
-rw-r--r--e-util/gal-a11y-e-table-item-factory.c107
-rw-r--r--e-util/gal-a11y-e-table-item-factory.h52
-rw-r--r--e-util/gal-a11y-e-table-item.c1437
-rw-r--r--e-util/gal-a11y-e-table-item.h62
-rw-r--r--e-util/gal-a11y-e-table.c315
-rw-r--r--e-util/gal-a11y-e-table.h62
-rw-r--r--e-util/gal-a11y-e-text-factory.c103
-rw-r--r--e-util/gal-a11y-e-text-factory.h52
-rw-r--r--e-util/gal-a11y-e-text.c1141
-rw-r--r--e-util/gal-a11y-e-text.h59
-rw-r--r--e-util/gal-a11y-e-tree-factory.c99
-rw-r--r--e-util/gal-a11y-e-tree-factory.h52
-rw-r--r--e-util/gal-a11y-e-tree.c196
-rw-r--r--e-util/gal-a11y-e-tree.h61
-rw-r--r--e-util/gal-a11y-factory.h89
-rw-r--r--e-util/gal-a11y-util.c49
-rw-r--r--e-util/gal-a11y-util.h40
-rw-r--r--e-util/gal-define-views-dialog.c451
-rw-r--r--e-util/gal-define-views-dialog.h77
-rw-r--r--e-util/gal-define-views-model.c352
-rw-r--r--e-util/gal-define-views-model.h70
-rw-r--r--e-util/gal-define-views.ui177
-rw-r--r--e-util/gal-view-collection.c829
-rw-r--r--e-util/gal-view-collection.h150
-rw-r--r--e-util/gal-view-etable.c335
-rw-r--r--e-util/gal-view-etable.h96
-rw-r--r--e-util/gal-view-factory-etable.c195
-rw-r--r--e-util/gal-view-factory-etable.h77
-rw-r--r--e-util/gal-view-factory.c101
-rw-r--r--e-util/gal-view-factory.h85
-rw-r--r--e-util/gal-view-instance-save-as-dialog.c359
-rw-r--r--e-util/gal-view-instance-save-as-dialog.h92
-rw-r--r--e-util/gal-view-instance-save-as-dialog.ui133
-rw-r--r--e-util/gal-view-instance.c502
-rw-r--r--e-util/gal-view-instance.h114
-rw-r--r--e-util/gal-view-new-dialog.c291
-rw-r--r--e-util/gal-view-new-dialog.h81
-rw-r--r--e-util/gal-view-new-dialog.ui177
-rw-r--r--e-util/gal-view.c280
-rw-r--r--e-util/gal-view.h98
-rw-r--r--e-util/test-calendar.c145
-rw-r--r--e-util/test-category-completion.c67
-rw-r--r--e-util/test-contact-store.c145
-rw-r--r--e-util/test-dateedit.c299
-rw-r--r--e-util/test-mail-signatures.c195
-rw-r--r--e-util/test-name-selector.c102
-rw-r--r--e-util/test-preferences-window.c108
-rw-r--r--e-util/test-source-combo-box.c107
-rw-r--r--e-util/test-source-config.c57
-rw-r--r--e-util/test-source-selector.c157
-rw-r--r--e-util/tree-expanded.xpm23
-rw-r--r--e-util/tree-unexpanded.xpm23
-rw-r--r--e-util/widgets.error.xml28
478 files changed, 169300 insertions, 283 deletions
diff --git a/e-util/Makefile.am b/e-util/Makefile.am
index 4464f93045..15a9bcd8ac 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/e-util/arrow-down.xpm b/e-util/arrow-down.xpm
new file mode 100644
index 0000000000..f1e6cb4b3c
--- /dev/null
+++ b/e-util/arrow-down.xpm
@@ -0,0 +1,21 @@
+/* XPM */
+static const char * arrow_down_xpm[] = {
+"13 16 2 1",
+" c None",
+". c #FF0000",
+" ... ",
+" ... ",
+" ... ",
+" ... ",
+" ... ",
+" ... ",
+" ... ",
+" ... ",
+" ... ",
+".............",
+" ........... ",
+" ......... ",
+" ....... ",
+" ..... ",
+" ... ",
+" . "};
diff --git a/e-util/arrow-up.xpm b/e-util/arrow-up.xpm
new file mode 100644
index 0000000000..0cc5b9a00c
--- /dev/null
+++ b/e-util/arrow-up.xpm
@@ -0,0 +1,21 @@
+/* XPM */
+static const char * arrow_up_xpm[] = {
+"13 16 2 1",
+" c None",
+". c #FF0000",
+" . ",
+" ... ",
+" ..... ",
+" ....... ",
+" ......... ",
+" ........... ",
+".............",
+" ... ",
+" ... ",
+" ... ",
+" ... ",
+" ... ",
+" ... ",
+" ... ",
+" ... ",
+" ... "};
diff --git a/e-util/check-empty.xpm b/e-util/check-empty.xpm
new file mode 100644
index 0000000000..746b20234e
--- /dev/null
+++ b/e-util/check-empty.xpm
@@ -0,0 +1,21 @@
+/* XPM */
+static const char * check_empty_xpm[] = {
+"16 16 2 1",
+" c None",
+". c #000000",
+" ",
+" ",
+" ............ ",
+" . . ",
+" . . ",
+" . . ",
+" . . ",
+" . . ",
+" . . ",
+" . . ",
+" . . ",
+" . . ",
+" . . ",
+" ............ ",
+" ",
+" "};
diff --git a/e-util/check-filled.xpm b/e-util/check-filled.xpm
new file mode 100644
index 0000000000..c0468fc25b
--- /dev/null
+++ b/e-util/check-filled.xpm
@@ -0,0 +1,21 @@
+/* XPM */
+static const char * check_filled_xpm[] = {
+"16 16 2 1",
+" c None",
+". c #000000",
+" ",
+" ",
+" ............ ",
+" . . ",
+" . . . ",
+" . .. . ",
+" . ... . ",
+" . . ... . ",
+" . .. ... . ",
+" . ..... . ",
+" . ... . ",
+" . . . ",
+" . . ",
+" ............ ",
+" ",
+" "};
diff --git a/e-util/e-action-combo-box.c b/e-util/e-action-combo-box.c
new file mode 100644
index 0000000000..0747a6ed27
--- /dev/null
+++ b/e-util/e-action-combo-box.c
@@ -0,0 +1,578 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* e-action-combo-box.c
+ *
+ * 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-action-combo-box.h"
+
+#include <glib/gi18n.h>
+
+#define E_ACTION_COMBO_BOX_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_ACTION_COMBO_BOX, EActionComboBoxPrivate))
+
+enum {
+ COLUMN_ACTION,
+ COLUMN_SORT
+};
+
+enum {
+ PROP_0,
+ PROP_ACTION
+};
+
+struct _EActionComboBoxPrivate {
+ GtkRadioAction *action;
+ GtkActionGroup *action_group;
+ GHashTable *index;
+ guint changed_handler_id; /* action::changed */
+ guint group_sensitive_handler_id; /* action-group::sensitive */
+ guint group_visible_handler_id; /* action-group::visible */
+ gboolean group_has_icons : 1;
+};
+
+G_DEFINE_TYPE (
+ EActionComboBox,
+ e_action_combo_box,
+ GTK_TYPE_COMBO_BOX)
+
+static void
+action_combo_box_action_changed_cb (GtkRadioAction *action,
+ GtkRadioAction *current,
+ EActionComboBox *combo_box)
+{
+ GtkTreeRowReference *reference;
+ GtkTreeModel *model;
+ GtkTreePath *path;
+ GtkTreeIter iter;
+ gboolean valid;
+
+ reference = g_hash_table_lookup (
+ combo_box->priv->index, GINT_TO_POINTER (
+ gtk_radio_action_get_current_value (current)));
+ g_return_if_fail (reference != NULL);
+
+ model = gtk_tree_row_reference_get_model (reference);
+ path = gtk_tree_row_reference_get_path (reference);
+ valid = gtk_tree_model_get_iter (model, &iter, path);
+ gtk_tree_path_free (path);
+ g_return_if_fail (valid);
+
+ gtk_combo_box_set_active_iter (GTK_COMBO_BOX (combo_box), &iter);
+}
+
+static void
+action_combo_box_action_group_notify_cb (GtkActionGroup *action_group,
+ GParamSpec *pspec,
+ EActionComboBox *combo_box)
+{
+ g_object_set (
+ combo_box, "sensitive",
+ gtk_action_group_get_sensitive (action_group), "visible",
+ gtk_action_group_get_visible (action_group), NULL);
+}
+
+static void
+action_combo_box_render_pixbuf (GtkCellLayout *layout,
+ GtkCellRenderer *renderer,
+ GtkTreeModel *model,
+ GtkTreeIter *iter,
+ EActionComboBox *combo_box)
+{
+ GtkRadioAction *action;
+ gchar *icon_name;
+ gchar *stock_id;
+ gboolean sensitive;
+ gboolean visible;
+ gint width;
+
+ gtk_tree_model_get (model, iter, COLUMN_ACTION, &action, -1);
+
+ /* Do any of the actions have an icon? */
+ if (!combo_box->priv->group_has_icons)
+ return;
+
+ /* A NULL action means the row is a separator. */
+ if (action == NULL)
+ return;
+
+ g_object_get (
+ G_OBJECT (action),
+ "icon-name", &icon_name,
+ "sensitive", &sensitive,
+ "stock-id", &stock_id,
+ "visible", &visible,
+ NULL);
+
+ /* Keep the pixbuf renderer a fixed size for proper alignment. */
+ gtk_icon_size_lookup (GTK_ICON_SIZE_MENU, &width, NULL);
+
+ /* We can't set both "icon-name" and "stock-id" because setting
+ * one unsets the other. So pick the one that has a non-NULL
+ * value. If both are non-NULL, "stock-id" wins. */
+
+ if (stock_id != NULL)
+ g_object_set (
+ G_OBJECT (renderer),
+ "sensitive", sensitive,
+ "icon-name", NULL,
+ "stock-id", stock_id,
+ "stock-size", GTK_ICON_SIZE_MENU,
+ "visible", visible,
+ "width", width,
+ NULL);
+ else
+ g_object_set (
+ G_OBJECT (renderer),
+ "sensitive", sensitive,
+ "icon-name", icon_name,
+ "stock-id", NULL,
+ "stock-size", GTK_ICON_SIZE_MENU,
+ "visible", visible,
+ "width", width,
+ NULL);
+
+ g_free (icon_name);
+ g_free (stock_id);
+}
+
+static void
+action_combo_box_render_text (GtkCellLayout *layout,
+ GtkCellRenderer *renderer,
+ GtkTreeModel *model,
+ GtkTreeIter *iter,
+ EActionComboBox *combo_box)
+{
+ GtkRadioAction *action;
+ gchar **strv;
+ gchar *label;
+ gboolean sensitive;
+ gboolean visible;
+ gint xpad;
+
+ gtk_tree_model_get (model, iter, COLUMN_ACTION, &action, -1);
+
+ /* A NULL action means the row is a separator. */
+ if (action == NULL)
+ return;
+
+ g_object_get (
+ G_OBJECT (action),
+ "label", &label,
+ "sensitive", &sensitive,
+ "visible", &visible,
+ NULL);
+
+ /* Strip out underscores. */
+ strv = g_strsplit (label, "_", -1);
+ g_free (label);
+ label = g_strjoinv (NULL, strv);
+ g_strfreev (strv);
+
+ xpad = combo_box->priv->group_has_icons ? 3 : 0;
+
+ g_object_set (
+ G_OBJECT (renderer),
+ "sensitive", sensitive,
+ "text", label,
+ "visible", visible,
+ "xpad", xpad,
+ NULL);
+
+ g_free (label);
+}
+
+static gboolean
+action_combo_box_is_row_separator (GtkTreeModel *model,
+ GtkTreeIter *iter)
+{
+ GtkAction *action;
+ gboolean separator;
+
+ /* NULL actions are rendered as separators. */
+ gtk_tree_model_get (model, iter, COLUMN_ACTION, &action, -1);
+ separator = (action == NULL);
+ if (action != NULL)
+ g_object_unref (action);
+
+ return separator;
+}
+
+static void
+action_combo_box_update_model (EActionComboBox *combo_box)
+{
+ GtkListStore *list_store;
+ GSList *list;
+
+ g_hash_table_remove_all (combo_box->priv->index);
+
+ if (combo_box->priv->action == NULL) {
+ gtk_combo_box_set_model (GTK_COMBO_BOX (combo_box), NULL);
+ return;
+ }
+
+ /* We store values in the sort column as floats so that we can
+ * insert separators in between consecutive integer values and
+ * still maintain the proper ordering. */
+ list_store = gtk_list_store_new (
+ 2, GTK_TYPE_RADIO_ACTION, G_TYPE_FLOAT);
+
+ list = gtk_radio_action_get_group (combo_box->priv->action);
+ combo_box->priv->group_has_icons = FALSE;
+
+ while (list != NULL) {
+ GtkTreeRowReference *reference;
+ GtkRadioAction *action = list->data;
+ GtkTreePath *path;
+ GtkTreeIter iter;
+ gchar *icon_name;
+ gchar *stock_id;
+ gint value;
+
+ g_object_get (
+ action, "icon-name", &icon_name,
+ "stock-id", &stock_id, NULL);
+ combo_box->priv->group_has_icons |=
+ (icon_name != NULL || stock_id != NULL);
+ g_free (icon_name);
+ g_free (stock_id);
+
+ gtk_list_store_append (list_store, &iter);
+ g_object_get (action, "value", &value, NULL);
+ gtk_list_store_set (
+ list_store, &iter, COLUMN_ACTION,
+ list->data, COLUMN_SORT, (gfloat) value, -1);
+
+ path = gtk_tree_model_get_path (
+ GTK_TREE_MODEL (list_store), &iter);
+ reference = gtk_tree_row_reference_new (
+ GTK_TREE_MODEL (list_store), path);
+ g_hash_table_insert (
+ combo_box->priv->index,
+ GINT_TO_POINTER (value), reference);
+ gtk_tree_path_free (path);
+
+ list = g_slist_next (list);
+ }
+
+ gtk_tree_sortable_set_sort_column_id (
+ GTK_TREE_SORTABLE (list_store),
+ COLUMN_SORT, GTK_SORT_ASCENDING);
+ gtk_combo_box_set_model (
+ GTK_COMBO_BOX (combo_box), GTK_TREE_MODEL (list_store));
+
+ action_combo_box_action_changed_cb (
+ combo_box->priv->action,
+ combo_box->priv->action,
+ combo_box);
+}
+
+static void
+action_combo_box_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_ACTION:
+ e_action_combo_box_set_action (
+ E_ACTION_COMBO_BOX (object),
+ g_value_get_object (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+action_combo_box_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_ACTION:
+ g_value_set_object (
+ value, e_action_combo_box_get_action (
+ E_ACTION_COMBO_BOX (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+action_combo_box_dispose (GObject *object)
+{
+ EActionComboBoxPrivate *priv = E_ACTION_COMBO_BOX_GET_PRIVATE (object);
+
+ if (priv->action != NULL) {
+ g_object_unref (priv->action);
+ priv->action = NULL;
+ }
+
+ if (priv->action_group != NULL) {
+ g_object_unref (priv->action_group);
+ priv->action_group = NULL;
+ }
+
+ g_hash_table_remove_all (priv->index);
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (e_action_combo_box_parent_class)->dispose (object);
+}
+
+static void
+action_combo_box_finalize (GObject *object)
+{
+ EActionComboBoxPrivate *priv = E_ACTION_COMBO_BOX_GET_PRIVATE (object);
+
+ g_hash_table_destroy (priv->index);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (e_action_combo_box_parent_class)->finalize (object);
+}
+
+static void
+action_combo_box_constructed (GObject *object)
+{
+ GtkComboBox *combo_box;
+ GtkCellRenderer *renderer;
+
+ combo_box = GTK_COMBO_BOX (object);
+
+ /* This needs to happen after constructor properties are set
+ * so that GtkCellLayout.get_area() returns something valid. */
+
+ renderer = gtk_cell_renderer_pixbuf_new ();
+ gtk_cell_layout_pack_start (
+ GTK_CELL_LAYOUT (combo_box), renderer, FALSE);
+ gtk_cell_layout_set_cell_data_func (
+ GTK_CELL_LAYOUT (combo_box), renderer,
+ (GtkCellLayoutDataFunc) action_combo_box_render_pixbuf,
+ combo_box, NULL);
+
+ renderer = gtk_cell_renderer_text_new ();
+ gtk_cell_layout_pack_start (
+ GTK_CELL_LAYOUT (combo_box), renderer, TRUE);
+ gtk_cell_layout_set_cell_data_func (
+ GTK_CELL_LAYOUT (combo_box), renderer,
+ (GtkCellLayoutDataFunc) action_combo_box_render_text,
+ combo_box, NULL);
+
+ gtk_combo_box_set_row_separator_func (
+ combo_box, (GtkTreeViewRowSeparatorFunc)
+ action_combo_box_is_row_separator, NULL, NULL);
+}
+
+static void
+action_combo_box_changed (GtkComboBox *combo_box)
+{
+ GtkRadioAction *action;
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ gint value;
+
+ /* This method is virtual, so no need to chain up. */
+
+ if (!gtk_combo_box_get_active_iter (combo_box, &iter))
+ return;
+
+ model = gtk_combo_box_get_model (combo_box);
+ gtk_tree_model_get (model, &iter, COLUMN_ACTION, &action, -1);
+ g_object_get (action, "value", &value, NULL);
+ gtk_radio_action_set_current_value (action, value);
+}
+
+static void
+e_action_combo_box_class_init (EActionComboBoxClass *class)
+{
+ GObjectClass *object_class;
+ GtkComboBoxClass *combo_box_class;
+
+ g_type_class_add_private (class, sizeof (EActionComboBoxPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = action_combo_box_set_property;
+ object_class->get_property = action_combo_box_get_property;
+ object_class->dispose = action_combo_box_dispose;
+ object_class->finalize = action_combo_box_finalize;
+ object_class->constructed = action_combo_box_constructed;
+
+ combo_box_class = GTK_COMBO_BOX_CLASS (class);
+ combo_box_class->changed = action_combo_box_changed;
+
+ g_object_class_install_property (
+ object_class,
+ PROP_ACTION,
+ g_param_spec_object (
+ "action",
+ "Action",
+ "A GtkRadioAction",
+ GTK_TYPE_RADIO_ACTION,
+ G_PARAM_READWRITE));
+}
+
+static void
+e_action_combo_box_init (EActionComboBox *combo_box)
+{
+ combo_box->priv = E_ACTION_COMBO_BOX_GET_PRIVATE (combo_box);
+
+ combo_box->priv->index = g_hash_table_new_full (
+ g_direct_hash, g_direct_equal,
+ (GDestroyNotify) NULL,
+ (GDestroyNotify) gtk_tree_row_reference_free);
+}
+
+GtkWidget *
+e_action_combo_box_new (void)
+{
+ return e_action_combo_box_new_with_action (NULL);
+}
+
+GtkWidget *
+e_action_combo_box_new_with_action (GtkRadioAction *action)
+{
+ return g_object_new (E_TYPE_ACTION_COMBO_BOX, "action", action, NULL);
+}
+
+GtkRadioAction *
+e_action_combo_box_get_action (EActionComboBox *combo_box)
+{
+ g_return_val_if_fail (E_ACTION_IS_COMBO_BOX (combo_box), NULL);
+
+ return combo_box->priv->action;
+}
+
+void
+e_action_combo_box_set_action (EActionComboBox *combo_box,
+ GtkRadioAction *action)
+{
+ g_return_if_fail (E_ACTION_IS_COMBO_BOX (combo_box));
+
+ if (action != NULL)
+ g_return_if_fail (GTK_IS_RADIO_ACTION (action));
+
+ if (combo_box->priv->action != NULL) {
+ g_signal_handler_disconnect (
+ combo_box->priv->action,
+ combo_box->priv->changed_handler_id);
+ g_object_unref (combo_box->priv->action);
+ }
+
+ if (combo_box->priv->action_group != NULL) {
+ g_signal_handler_disconnect (
+ combo_box->priv->action_group,
+ combo_box->priv->group_sensitive_handler_id);
+ g_signal_handler_disconnect (
+ combo_box->priv->action_group,
+ combo_box->priv->group_visible_handler_id);
+ g_object_unref (combo_box->priv->action_group);
+ combo_box->priv->action_group = NULL;
+ }
+
+ if (action != NULL)
+ g_object_get (
+ g_object_ref (action), "action-group",
+ &combo_box->priv->action_group, NULL);
+
+ combo_box->priv->action = action;
+ action_combo_box_update_model (combo_box);
+
+ if (combo_box->priv->action != NULL)
+ combo_box->priv->changed_handler_id = g_signal_connect (
+ combo_box->priv->action, "changed",
+ G_CALLBACK (action_combo_box_action_changed_cb),
+ combo_box);
+
+ if (combo_box->priv->action_group != NULL) {
+ g_object_ref (combo_box->priv->action_group);
+ combo_box->priv->group_sensitive_handler_id =
+ g_signal_connect (
+ combo_box->priv->action_group,
+ "notify::sensitive", G_CALLBACK (
+ action_combo_box_action_group_notify_cb),
+ combo_box);
+ combo_box->priv->group_visible_handler_id =
+ g_signal_connect (
+ combo_box->priv->action_group,
+ "notify::visible", G_CALLBACK (
+ action_combo_box_action_group_notify_cb),
+ combo_box);
+ }
+
+ g_object_notify (G_OBJECT (combo_box), "action");
+}
+
+gint
+e_action_combo_box_get_current_value (EActionComboBox *combo_box)
+{
+ g_return_val_if_fail (E_ACTION_IS_COMBO_BOX (combo_box), 0);
+ g_return_val_if_fail (combo_box->priv->action != NULL, 0);
+
+ return gtk_radio_action_get_current_value (combo_box->priv->action);
+}
+
+void
+e_action_combo_box_set_current_value (EActionComboBox *combo_box,
+ gint current_value)
+{
+ g_return_if_fail (E_ACTION_IS_COMBO_BOX (combo_box));
+ g_return_if_fail (combo_box->priv->action != NULL);
+
+ gtk_radio_action_set_current_value (
+ combo_box->priv->action, current_value);
+}
+
+void
+e_action_combo_box_add_separator_before (EActionComboBox *combo_box,
+ gint action_value)
+{
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+
+ g_return_if_fail (E_ACTION_IS_COMBO_BOX (combo_box));
+
+ /* NULL actions are rendered as separators. */
+ model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo_box));
+ gtk_list_store_append (GTK_LIST_STORE (model), &iter);
+ gtk_list_store_set (
+ GTK_LIST_STORE (model), &iter, COLUMN_ACTION,
+ NULL, COLUMN_SORT, (gfloat) action_value - 0.5, -1);
+}
+
+void
+e_action_combo_box_add_separator_after (EActionComboBox *combo_box,
+ gint action_value)
+{
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+
+ g_return_if_fail (E_ACTION_IS_COMBO_BOX (combo_box));
+
+ /* NULL actions are rendered as separators. */
+ model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo_box));
+ gtk_list_store_append (GTK_LIST_STORE (model), &iter);
+ gtk_list_store_set (
+ GTK_LIST_STORE (model), &iter, COLUMN_ACTION,
+ NULL, COLUMN_SORT, (gfloat) action_value + 0.5, -1);
+}
diff --git a/e-util/e-action-combo-box.h b/e-util/e-action-combo-box.h
new file mode 100644
index 0000000000..43adeaff7a
--- /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/e-util/e-activity-bar.c b/e-util/e-activity-bar.c
new file mode 100644
index 0000000000..f85b403bd7
--- /dev/null
+++ b/e-util/e-activity-bar.c
@@ -0,0 +1,366 @@
+/*
+ * e-activity-bar.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-activity-bar.h"
+
+#define E_ACTIVITY_BAR_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_ACTIVITY_BAR, EActivityBarPrivate))
+
+#define FEEDBACK_PERIOD 1 /* seconds */
+#define COMPLETED_ICON_NAME "emblem-default"
+
+struct _EActivityBarPrivate {
+ EActivity *activity; /* weak reference */
+ GtkWidget *image; /* not referenced */
+ GtkWidget *label; /* not referenced */
+ GtkWidget *cancel; /* not referenced */
+ GtkWidget *spinner; /* not referenced */
+
+ /* If the user clicks the Cancel button, keep the cancelled
+ * EActivity object alive for a short duration so the user
+ * gets some visual feedback that cancellation worked. */
+ guint timeout_id;
+};
+
+enum {
+ PROP_0,
+ PROP_ACTIVITY
+};
+
+G_DEFINE_TYPE (
+ EActivityBar,
+ e_activity_bar,
+ GTK_TYPE_INFO_BAR)
+
+static void
+activity_bar_feedback (EActivityBar *bar)
+{
+ EActivity *activity;
+ EActivityState state;
+
+ activity = e_activity_bar_get_activity (bar);
+ g_return_if_fail (E_IS_ACTIVITY (activity));
+
+ state = e_activity_get_state (activity);
+ if (state != E_ACTIVITY_CANCELLED && state != E_ACTIVITY_COMPLETED)
+ return;
+
+ if (bar->priv->timeout_id > 0)
+ g_source_remove (bar->priv->timeout_id);
+
+ /* Hold a reference on the EActivity for a short
+ * period so the activity bar stays visible. */
+ bar->priv->timeout_id = g_timeout_add_seconds_full (
+ G_PRIORITY_LOW, FEEDBACK_PERIOD, (GSourceFunc) gtk_false,
+ g_object_ref (activity), (GDestroyNotify) g_object_unref);
+}
+
+static void
+activity_bar_update (EActivityBar *bar)
+{
+ EActivity *activity;
+ EActivityState state;
+ GCancellable *cancellable;
+ const gchar *icon_name;
+ gboolean sensitive;
+ gboolean visible;
+ gchar *description;
+
+ activity = e_activity_bar_get_activity (bar);
+
+ if (activity == NULL) {
+ gtk_widget_hide (GTK_WIDGET (bar));
+ return;
+ }
+
+ cancellable = e_activity_get_cancellable (activity);
+ icon_name = e_activity_get_icon_name (activity);
+ state = e_activity_get_state (activity);
+
+ description = e_activity_describe (activity);
+ gtk_label_set_text (GTK_LABEL (bar->priv->label), description);
+
+ if (state == E_ACTIVITY_CANCELLED) {
+ PangoAttribute *attr;
+ PangoAttrList *attr_list;
+
+ attr_list = pango_attr_list_new ();
+
+ attr = pango_attr_strikethrough_new (TRUE);
+ pango_attr_list_insert (attr_list, attr);
+
+ gtk_label_set_attributes (
+ GTK_LABEL (bar->priv->label), attr_list);
+
+ pango_attr_list_unref (attr_list);
+ } else
+ gtk_label_set_attributes (
+ GTK_LABEL (bar->priv->label), NULL);
+
+ if (state == E_ACTIVITY_COMPLETED)
+ icon_name = COMPLETED_ICON_NAME;
+
+ if (state == E_ACTIVITY_CANCELLED) {
+ gtk_image_set_from_stock (
+ GTK_IMAGE (bar->priv->image),
+ GTK_STOCK_CANCEL, GTK_ICON_SIZE_BUTTON);
+ gtk_widget_show (bar->priv->image);
+ } else if (icon_name != NULL) {
+ gtk_image_set_from_icon_name (
+ GTK_IMAGE (bar->priv->image),
+ icon_name, GTK_ICON_SIZE_BUTTON);
+ gtk_widget_show (bar->priv->image);
+ } else {
+ gtk_widget_hide (bar->priv->image);
+ }
+
+ visible = (cancellable != NULL);
+ gtk_widget_set_visible (bar->priv->cancel, visible);
+
+ sensitive = (state == E_ACTIVITY_RUNNING);
+ gtk_widget_set_sensitive (bar->priv->cancel, sensitive);
+
+ visible = (description != NULL && *description != '\0');
+ gtk_widget_set_visible (GTK_WIDGET (bar), visible);
+
+ g_free (description);
+}
+
+static void
+activity_bar_cancel (EActivityBar *bar)
+{
+ EActivity *activity;
+ GCancellable *cancellable;
+
+ activity = e_activity_bar_get_activity (bar);
+ g_return_if_fail (E_IS_ACTIVITY (activity));
+
+ cancellable = e_activity_get_cancellable (activity);
+ g_cancellable_cancel (cancellable);
+
+ activity_bar_update (bar);
+}
+
+static void
+activity_bar_weak_notify_cb (EActivityBar *bar,
+ GObject *where_the_object_was)
+{
+ g_return_if_fail (E_IS_ACTIVITY_BAR (bar));
+
+ bar->priv->activity = NULL;
+ e_activity_bar_set_activity (bar, NULL);
+}
+
+static void
+activity_bar_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_ACTIVITY:
+ e_activity_bar_set_activity (
+ E_ACTIVITY_BAR (object),
+ g_value_get_object (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+activity_bar_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_ACTIVITY:
+ g_value_set_object (
+ value, e_activity_bar_get_activity (
+ E_ACTIVITY_BAR (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+activity_bar_dispose (GObject *object)
+{
+ EActivityBarPrivate *priv;
+
+ priv = E_ACTIVITY_BAR_GET_PRIVATE (object);
+
+ if (priv->timeout_id > 0) {
+ g_source_remove (priv->timeout_id);
+ priv->timeout_id = 0;
+ }
+
+ if (priv->activity != NULL) {
+ g_signal_handlers_disconnect_matched (
+ priv->activity, G_SIGNAL_MATCH_DATA,
+ 0, 0, NULL, NULL, object);
+ g_object_weak_unref (
+ G_OBJECT (priv->activity), (GWeakNotify)
+ activity_bar_weak_notify_cb, object);
+ priv->activity = NULL;
+ }
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (e_activity_bar_parent_class)->dispose (object);
+}
+
+static void
+e_activity_bar_class_init (EActivityBarClass *class)
+{
+ GObjectClass *object_class;
+
+ g_type_class_add_private (class, sizeof (EActivityBarPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = activity_bar_set_property;
+ object_class->get_property = activity_bar_get_property;
+ object_class->dispose = activity_bar_dispose;
+
+ g_object_class_install_property (
+ object_class,
+ PROP_ACTIVITY,
+ g_param_spec_object (
+ "activity",
+ NULL,
+ NULL,
+ E_TYPE_ACTIVITY,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+}
+
+static void
+e_activity_bar_init (EActivityBar *bar)
+{
+ GtkWidget *container;
+ GtkWidget *widget;
+
+ bar->priv = E_ACTIVITY_BAR_GET_PRIVATE (bar);
+
+ container = gtk_info_bar_get_content_area (GTK_INFO_BAR (bar));
+
+ widget = gtk_hbox_new (FALSE, 12);
+ gtk_container_add (GTK_CONTAINER (container), widget);
+ gtk_widget_show (widget);
+
+ container = widget;
+
+ widget = gtk_image_new ();
+ gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+ bar->priv->image = widget;
+
+ widget = gtk_spinner_new ();
+ gtk_spinner_start (GTK_SPINNER (widget));
+ gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+ bar->priv->spinner = widget;
+
+ /* The spinner is only visible when the image is not. */
+ g_object_bind_property (
+ bar->priv->image, "visible",
+ bar->priv->spinner, "visible",
+ G_BINDING_BIDIRECTIONAL |
+ G_BINDING_SYNC_CREATE |
+ G_BINDING_INVERT_BOOLEAN);
+
+ widget = gtk_label_new (NULL);
+ gtk_misc_set_alignment (GTK_MISC (widget), 0.0, 0.5);
+ gtk_label_set_ellipsize (GTK_LABEL (widget), PANGO_ELLIPSIZE_END);
+ gtk_box_pack_start (GTK_BOX (container), widget, TRUE, TRUE, 0);
+ bar->priv->label = widget;
+ gtk_widget_show (widget);
+
+ /* This is only shown if the EActivity has a GCancellable. */
+ widget = gtk_button_new_from_stock (GTK_STOCK_CANCEL);
+ gtk_info_bar_add_action_widget (
+ GTK_INFO_BAR (bar), widget, GTK_RESPONSE_CANCEL);
+ bar->priv->cancel = widget;
+ gtk_widget_hide (widget);
+
+ g_signal_connect_swapped (
+ widget, "clicked",
+ G_CALLBACK (activity_bar_cancel), bar);
+}
+
+GtkWidget *
+e_activity_bar_new (void)
+{
+ return g_object_new (E_TYPE_ACTIVITY_BAR, NULL);
+}
+
+EActivity *
+e_activity_bar_get_activity (EActivityBar *bar)
+{
+ g_return_val_if_fail (E_IS_ACTIVITY_BAR (bar), NULL);
+
+ return bar->priv->activity;
+}
+
+void
+e_activity_bar_set_activity (EActivityBar *bar,
+ EActivity *activity)
+{
+ g_return_if_fail (E_IS_ACTIVITY_BAR (bar));
+
+ if (activity != NULL)
+ g_return_if_fail (E_IS_ACTIVITY (activity));
+
+ if (bar->priv->timeout_id > 0) {
+ g_source_remove (bar->priv->timeout_id);
+ bar->priv->timeout_id = 0;
+ }
+
+ if (bar->priv->activity != NULL) {
+ g_signal_handlers_disconnect_matched (
+ bar->priv->activity, G_SIGNAL_MATCH_DATA,
+ 0, 0, NULL, NULL, bar);
+ g_object_weak_unref (
+ G_OBJECT (bar->priv->activity),
+ (GWeakNotify) activity_bar_weak_notify_cb, bar);
+ }
+
+ bar->priv->activity = activity;
+
+ if (activity != NULL) {
+ g_object_weak_ref (
+ G_OBJECT (activity), (GWeakNotify)
+ activity_bar_weak_notify_cb, bar);
+
+ g_signal_connect_swapped (
+ activity, "notify::state",
+ G_CALLBACK (activity_bar_feedback), bar);
+
+ g_signal_connect_swapped (
+ activity, "notify",
+ G_CALLBACK (activity_bar_update), bar);
+ }
+
+ activity_bar_update (bar);
+
+ g_object_notify (G_OBJECT (bar), "activity");
+}
diff --git a/e-util/e-activity-bar.h b/e-util/e-activity-bar.h
new file mode 100644
index 0000000000..d56378e2c1
--- /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/e-util/e-activity-proxy.c b/e-util/e-activity-proxy.c
new file mode 100644
index 0000000000..7547088aac
--- /dev/null
+++ b/e-util/e-activity-proxy.c
@@ -0,0 +1,381 @@
+/*
+ * e-activity-proxy.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-activity-proxy.h"
+
+#include <glib/gi18n.h>
+
+#define E_ACTIVITY_PROXY_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_ACTIVITY_PROXY, EActivityProxyPrivate))
+
+#define FEEDBACK_PERIOD 1 /* seconds */
+#define COMPLETED_ICON_NAME "emblem-default"
+
+struct _EActivityProxyPrivate {
+ EActivity *activity; /* weak reference */
+ GtkWidget *image; /* not referenced */
+ GtkWidget *label; /* not referenced */
+ GtkWidget *cancel; /* not referenced */
+ GtkWidget *spinner; /* not referenced */
+
+ /* If the user clicks the Cancel button, keep the cancelled
+ * EActivity object alive for a short duration so the user
+ * gets some visual feedback that cancellation worked. */
+ guint timeout_id;
+};
+
+enum {
+ PROP_0,
+ PROP_ACTIVITY
+};
+
+G_DEFINE_TYPE (
+ EActivityProxy,
+ e_activity_proxy,
+ GTK_TYPE_FRAME)
+
+static void
+activity_proxy_feedback (EActivityProxy *proxy)
+{
+ EActivity *activity;
+ EActivityState state;
+
+ activity = e_activity_proxy_get_activity (proxy);
+ g_return_if_fail (E_IS_ACTIVITY (activity));
+
+ state = e_activity_get_state (activity);
+ if (state != E_ACTIVITY_CANCELLED)
+ return;
+
+ if (proxy->priv->timeout_id > 0)
+ g_source_remove (proxy->priv->timeout_id);
+
+ /* Hold a reference on the EActivity for a short
+ * period so the activity proxy stays visible. */
+ proxy->priv->timeout_id = g_timeout_add_seconds_full (
+ G_PRIORITY_LOW, FEEDBACK_PERIOD, (GSourceFunc) gtk_false,
+ g_object_ref (activity), (GDestroyNotify) g_object_unref);
+}
+
+static void
+activity_proxy_update (EActivityProxy *proxy)
+{
+ EActivity *activity;
+ EActivityState state;
+ GCancellable *cancellable;
+ const gchar *icon_name;
+ gboolean sensitive;
+ gboolean visible;
+ gchar *description;
+
+ activity = e_activity_proxy_get_activity (proxy);
+
+ if (activity == NULL) {
+ gtk_widget_hide (GTK_WIDGET (proxy));
+ return;
+ }
+
+ cancellable = e_activity_get_cancellable (activity);
+ icon_name = e_activity_get_icon_name (activity);
+ state = e_activity_get_state (activity);
+
+ description = e_activity_describe (activity);
+ gtk_widget_set_tooltip_text (GTK_WIDGET (proxy), description);
+ gtk_label_set_text (GTK_LABEL (proxy->priv->label), description);
+
+ if (state == E_ACTIVITY_CANCELLED) {
+ PangoAttribute *attr;
+ PangoAttrList *attr_list;
+
+ attr_list = pango_attr_list_new ();
+
+ attr = pango_attr_strikethrough_new (TRUE);
+ pango_attr_list_insert (attr_list, attr);
+
+ gtk_label_set_attributes (
+ GTK_LABEL (proxy->priv->label), attr_list);
+
+ pango_attr_list_unref (attr_list);
+ } else
+ gtk_label_set_attributes (
+ GTK_LABEL (proxy->priv->label), NULL);
+
+ if (state == E_ACTIVITY_COMPLETED)
+ icon_name = COMPLETED_ICON_NAME;
+
+ if (state == E_ACTIVITY_CANCELLED) {
+ gtk_image_set_from_stock (
+ GTK_IMAGE (proxy->priv->image),
+ GTK_STOCK_CANCEL, GTK_ICON_SIZE_BUTTON);
+ gtk_widget_show (proxy->priv->image);
+ } else if (icon_name != NULL) {
+ gtk_image_set_from_icon_name (
+ GTK_IMAGE (proxy->priv->image),
+ icon_name, GTK_ICON_SIZE_MENU);
+ gtk_widget_show (proxy->priv->image);
+ } else {
+ gtk_widget_hide (proxy->priv->image);
+ }
+
+ visible = (cancellable != NULL);
+ gtk_widget_set_visible (proxy->priv->cancel, visible);
+
+ sensitive = (state == E_ACTIVITY_RUNNING);
+ gtk_widget_set_sensitive (proxy->priv->cancel, sensitive);
+
+ visible = (description != NULL && *description != '\0');
+ gtk_widget_set_visible (GTK_WIDGET (proxy), visible);
+
+ g_free (description);
+}
+
+static void
+activity_proxy_cancel (EActivityProxy *proxy)
+{
+ EActivity *activity;
+ GCancellable *cancellable;
+
+ activity = e_activity_proxy_get_activity (proxy);
+ g_return_if_fail (E_IS_ACTIVITY (activity));
+
+ cancellable = e_activity_get_cancellable (activity);
+ g_cancellable_cancel (cancellable);
+
+ activity_proxy_update (proxy);
+}
+
+static void
+activity_proxy_weak_notify_cb (EActivityProxy *proxy,
+ GObject *where_the_object_was)
+{
+ g_return_if_fail (E_IS_ACTIVITY_PROXY (proxy));
+
+ proxy->priv->activity = NULL;
+ e_activity_proxy_set_activity (proxy, NULL);
+}
+
+static void
+activity_proxy_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_ACTIVITY:
+ e_activity_proxy_set_activity (
+ E_ACTIVITY_PROXY (object),
+ g_value_get_object (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+activity_proxy_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_ACTIVITY:
+ g_value_set_object (
+ value, e_activity_proxy_get_activity (
+ E_ACTIVITY_PROXY (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+activity_proxy_dispose (GObject *object)
+{
+ EActivityProxyPrivate *priv;
+
+ priv = E_ACTIVITY_PROXY_GET_PRIVATE (object);
+
+ if (priv->timeout_id > 0) {
+ g_source_remove (priv->timeout_id);
+ priv->timeout_id = 0;
+ }
+
+ if (priv->activity != NULL) {
+ g_signal_handlers_disconnect_matched (
+ priv->activity, G_SIGNAL_MATCH_DATA,
+ 0, 0, NULL, NULL, object);
+ g_object_weak_unref (
+ G_OBJECT (priv->activity), (GWeakNotify)
+ activity_proxy_weak_notify_cb, object);
+ priv->activity = NULL;
+ }
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (e_activity_proxy_parent_class)->dispose (object);
+}
+
+static void
+e_activity_proxy_class_init (EActivityProxyClass *class)
+{
+ GObjectClass *object_class;
+
+ g_type_class_add_private (class, sizeof (EActivityProxyPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = activity_proxy_set_property;
+ object_class->get_property = activity_proxy_get_property;
+ object_class->dispose = activity_proxy_dispose;
+
+ g_object_class_install_property (
+ object_class,
+ PROP_ACTIVITY,
+ g_param_spec_object (
+ "activity",
+ NULL,
+ NULL,
+ E_TYPE_ACTIVITY,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+}
+
+static void
+e_activity_proxy_init (EActivityProxy *proxy)
+{
+ GtkWidget *container;
+ GtkWidget *widget;
+
+ proxy->priv = E_ACTIVITY_PROXY_GET_PRIVATE (proxy);
+
+ gtk_frame_set_shadow_type (GTK_FRAME (proxy), GTK_SHADOW_IN);
+
+ container = GTK_WIDGET (proxy);
+
+ widget = gtk_hbox_new (FALSE, 3);
+ gtk_container_add (GTK_CONTAINER (container), widget);
+ gtk_widget_show (widget);
+
+ container = widget;
+
+ widget = gtk_image_new ();
+ gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+ proxy->priv->image = widget;
+
+ widget = gtk_spinner_new ();
+ gtk_spinner_start (GTK_SPINNER (widget));
+ gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 3);
+ proxy->priv->spinner = widget;
+
+ /* The spinner is only visible when the image is not. */
+ g_object_bind_property (
+ proxy->priv->image, "visible",
+ proxy->priv->spinner, "visible",
+ G_BINDING_BIDIRECTIONAL |
+ G_BINDING_SYNC_CREATE |
+ G_BINDING_INVERT_BOOLEAN);
+
+ widget = gtk_label_new (NULL);
+ gtk_misc_set_alignment (GTK_MISC (widget), 0.0, 0.5);
+ gtk_label_set_ellipsize (GTK_LABEL (widget), PANGO_ELLIPSIZE_END);
+ gtk_box_pack_start (GTK_BOX (container), widget, TRUE, TRUE, 0);
+ proxy->priv->label = widget;
+ gtk_widget_show (widget);
+
+ /* This is only shown if the EActivity has a GCancellable. */
+ widget = gtk_button_new ();
+ gtk_button_set_image (
+ GTK_BUTTON (widget), gtk_image_new_from_stock (
+ GTK_STOCK_CANCEL, GTK_ICON_SIZE_MENU));
+ gtk_button_set_relief (GTK_BUTTON (widget), GTK_RELIEF_NONE);
+ gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+ gtk_widget_set_tooltip_text (widget, _("Cancel"));
+ proxy->priv->cancel = widget;
+ gtk_widget_show (widget);
+
+ g_signal_connect_swapped (
+ widget, "clicked",
+ G_CALLBACK (activity_proxy_cancel), proxy);
+}
+
+GtkWidget *
+e_activity_proxy_new (EActivity *activity)
+{
+ g_return_val_if_fail (E_IS_ACTIVITY (activity), NULL);
+
+ return g_object_new (
+ E_TYPE_ACTIVITY_PROXY, "activity", activity, NULL);
+}
+
+EActivity *
+e_activity_proxy_get_activity (EActivityProxy *proxy)
+{
+ g_return_val_if_fail (E_IS_ACTIVITY_PROXY (proxy), NULL);
+
+ return proxy->priv->activity;
+}
+
+void
+e_activity_proxy_set_activity (EActivityProxy *proxy,
+ EActivity *activity)
+{
+ g_return_if_fail (E_IS_ACTIVITY_PROXY (proxy));
+
+ if (activity != NULL)
+ g_return_if_fail (E_IS_ACTIVITY (activity));
+
+ if (proxy->priv->timeout_id > 0) {
+ g_source_remove (proxy->priv->timeout_id);
+ proxy->priv->timeout_id = 0;
+ }
+
+ if (proxy->priv->activity != NULL) {
+ g_signal_handlers_disconnect_matched (
+ proxy->priv->activity, G_SIGNAL_MATCH_DATA,
+ 0, 0, NULL, NULL, proxy);
+ g_object_weak_unref (
+ G_OBJECT (proxy->priv->activity),
+ (GWeakNotify) activity_proxy_weak_notify_cb, proxy);
+ }
+
+ proxy->priv->activity = activity;
+
+ if (activity != NULL) {
+ g_object_weak_ref (
+ G_OBJECT (activity), (GWeakNotify)
+ activity_proxy_weak_notify_cb, proxy);
+
+ g_signal_connect_swapped (
+ activity, "notify::state",
+ G_CALLBACK (activity_proxy_feedback), proxy);
+
+ g_signal_connect_swapped (
+ activity, "notify",
+ G_CALLBACK (activity_proxy_update), proxy);
+ }
+
+ activity_proxy_update (proxy);
+
+ g_object_notify (G_OBJECT (proxy), "activity");
+}
diff --git a/e-util/e-activity-proxy.h b/e-util/e-activity-proxy.h
new file mode 100644
index 0000000000..75125351f4
--- /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 cd1c5699b2..5eefb652b0 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 4cc9951fde..ac380a030c 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/e-util/e-alarm-selector.c b/e-util/e-alarm-selector.c
new file mode 100644
index 0000000000..bdc1b7e35e
--- /dev/null
+++ b/e-util/e-alarm-selector.c
@@ -0,0 +1,94 @@
+/*
+ * e-alarm-selector.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-alarm-selector.h"
+
+G_DEFINE_TYPE (
+ EAlarmSelector,
+ e_alarm_selector,
+ E_TYPE_SOURCE_SELECTOR)
+
+static gboolean
+alarm_selector_get_source_selected (ESourceSelector *selector,
+ ESource *source)
+{
+ ESourceAlarms *extension;
+ const gchar *extension_name;
+
+ /* Make sure this source is a calendar. */
+ extension_name = e_source_selector_get_extension_name (selector);
+ if (!e_source_has_extension (source, extension_name))
+ return FALSE;
+
+ extension_name = E_SOURCE_EXTENSION_ALARMS;
+ extension = e_source_get_extension (source, extension_name);
+ g_return_val_if_fail (E_IS_SOURCE_ALARMS (extension), FALSE);
+
+ return e_source_alarms_get_include_me (extension);
+}
+
+static void
+alarm_selector_set_source_selected (ESourceSelector *selector,
+ ESource *source,
+ gboolean selected)
+{
+ ESourceAlarms *extension;
+ const gchar *extension_name;
+
+ /* Make sure this source is a calendar. */
+ extension_name = e_source_selector_get_extension_name (selector);
+ if (!e_source_has_extension (source, extension_name))
+ return;
+
+ extension_name = E_SOURCE_EXTENSION_ALARMS;
+ extension = e_source_get_extension (source, extension_name);
+ g_return_if_fail (E_IS_SOURCE_ALARMS (extension));
+
+ if (selected != e_source_alarms_get_include_me (extension)) {
+ e_source_alarms_set_include_me (extension, selected);
+ e_source_selector_queue_write (selector, source);
+ }
+}
+
+static void
+e_alarm_selector_class_init (EAlarmSelectorClass *class)
+{
+ ESourceSelectorClass *source_selector_class;
+
+ source_selector_class = E_SOURCE_SELECTOR_CLASS (class);
+ source_selector_class->get_source_selected =
+ alarm_selector_get_source_selected;
+ source_selector_class->set_source_selected =
+ alarm_selector_set_source_selected;
+}
+
+static void
+e_alarm_selector_init (EAlarmSelector *selector)
+{
+}
+
+GtkWidget *
+e_alarm_selector_new (ESourceRegistry *registry)
+{
+ g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), NULL);
+
+ return g_object_new (
+ E_TYPE_ALARM_SELECTOR,
+ "extension-name", E_SOURCE_EXTENSION_CALENDAR,
+ "registry", registry, NULL);
+}
diff --git a/e-util/e-alarm-selector.h b/e-util/e-alarm-selector.h
new file mode 100644
index 0000000000..c545a46cf1
--- /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/e-util/e-alert-bar.c b/e-util/e-alert-bar.c
new file mode 100644
index 0000000000..2022af99f1
--- /dev/null
+++ b/e-util/e-alert-bar.c
@@ -0,0 +1,390 @@
+/*
+ * e-alert-bar.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-alert-bar.h"
+
+#include <config.h>
+#include <glib/gi18n-lib.h>
+
+#define E_ALERT_BAR_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_ALERT_BAR, EAlertBarPrivate))
+
+#define E_ALERT_BAR_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_ALERT_BAR, EAlertBarPrivate))
+
+/* GTK_ICON_SIZE_DIALOG is a tad too big. */
+#define ICON_SIZE GTK_ICON_SIZE_DND
+
+/* Dismiss warnings automatically after 5 minutes. */
+#define WARNING_TIMEOUT_SECONDS (5 * 60)
+
+struct _EAlertBarPrivate {
+ GQueue alerts;
+ GtkWidget *image; /* not referenced */
+ GtkWidget *primary_label; /* not referenced */
+ GtkWidget *secondary_label; /* not referenced */
+};
+
+G_DEFINE_TYPE (
+ EAlertBar,
+ e_alert_bar,
+ GTK_TYPE_INFO_BAR)
+
+static void
+alert_bar_response_close (EAlert *alert)
+{
+ e_alert_response (alert, GTK_RESPONSE_CLOSE);
+}
+
+static void
+alert_bar_show_alert (EAlertBar *alert_bar)
+{
+ GtkImage *image;
+ GtkInfoBar *info_bar;
+ GtkWidget *action_area;
+ GtkWidget *widget;
+ EAlert *alert;
+ GList *actions;
+ GList *children;
+ GtkMessageType message_type;
+ const gchar *primary_text;
+ const gchar *secondary_text;
+ const gchar *stock_id;
+ gboolean have_primary_text;
+ gboolean have_secondary_text;
+ gboolean visible;
+ gint response_id;
+ gchar *markup;
+
+ info_bar = GTK_INFO_BAR (alert_bar);
+ action_area = gtk_info_bar_get_action_area (info_bar);
+
+ alert = g_queue_peek_head (&alert_bar->priv->alerts);
+ g_return_if_fail (E_IS_ALERT (alert));
+
+ /* Remove all buttons from the previous alert. */
+ children = gtk_container_get_children (GTK_CONTAINER (action_area));
+ while (children != NULL) {
+ GtkWidget *child = GTK_WIDGET (children->data);
+ gtk_container_remove (GTK_CONTAINER (action_area), child);
+ children = g_list_delete_link (children, children);
+ }
+
+ /* Add alert-specific buttons. */
+ actions = e_alert_peek_actions (alert);
+ while (actions != NULL) {
+ /* These actions are already wired to trigger an
+ * EAlert::response signal when activated, which
+ * will in turn call gtk_info_bar_response(), so
+ * we can add buttons directly to the action
+ * area without knowning their response IDs. */
+
+ widget = gtk_button_new ();
+
+ gtk_activatable_set_related_action (
+ GTK_ACTIVATABLE (widget),
+ GTK_ACTION (actions->data));
+
+ gtk_box_pack_end (
+ GTK_BOX (action_area), widget, FALSE, FALSE, 0);
+
+ actions = g_list_next (actions);
+ }
+
+ /* Add a dismiss button. */
+ widget = gtk_button_new ();
+ gtk_button_set_image (
+ GTK_BUTTON (widget),
+ gtk_image_new_from_stock (
+ GTK_STOCK_CLOSE, GTK_ICON_SIZE_MENU));
+ gtk_button_set_relief (
+ GTK_BUTTON (widget), GTK_RELIEF_NONE);
+ gtk_widget_set_tooltip_text (
+ widget, _("Close this message"));
+ gtk_box_pack_end (
+ GTK_BOX (action_area), widget, FALSE, FALSE, 0);
+ gtk_button_box_set_child_non_homogeneous (
+ GTK_BUTTON_BOX (action_area), widget, TRUE);
+ gtk_widget_show (widget);
+
+ g_signal_connect_swapped (
+ widget, "clicked",
+ G_CALLBACK (alert_bar_response_close), alert);
+
+ primary_text = e_alert_get_primary_text (alert);
+ secondary_text = e_alert_get_secondary_text (alert);
+
+ if (primary_text == NULL)
+ primary_text = "";
+
+ if (secondary_text == NULL)
+ secondary_text = "";
+
+ have_primary_text = (*primary_text != '\0');
+ have_secondary_text = (*secondary_text != '\0');
+
+ response_id = e_alert_get_default_response (alert);
+ gtk_info_bar_set_default_response (info_bar, response_id);
+
+ message_type = e_alert_get_message_type (alert);
+ gtk_info_bar_set_message_type (info_bar, message_type);
+
+ widget = alert_bar->priv->primary_label;
+ if (have_primary_text && have_secondary_text)
+ markup = g_markup_printf_escaped (
+ "<b>%s</b>", primary_text);
+ else
+ markup = g_markup_escape_text (primary_text, -1);
+ gtk_label_set_markup (GTK_LABEL (widget), markup);
+ gtk_widget_set_visible (widget, have_primary_text);
+ g_free (markup);
+
+ widget = alert_bar->priv->secondary_label;
+ if (have_primary_text && have_secondary_text)
+ markup = g_markup_printf_escaped (
+ "<small>%s</small>", secondary_text);
+ else
+ markup = g_markup_escape_text (secondary_text, -1);
+ gtk_label_set_markup (GTK_LABEL (widget), markup);
+ gtk_widget_set_visible (widget, have_secondary_text);
+ g_free (markup);
+
+ stock_id = e_alert_get_stock_id (alert);
+ image = GTK_IMAGE (alert_bar->priv->image);
+ gtk_image_set_from_stock (image, stock_id, ICON_SIZE);
+
+ /* Avoid showing an image for one-line alerts,
+ * which are usually questions or informational. */
+ visible = have_primary_text && have_secondary_text;
+ gtk_widget_set_visible (alert_bar->priv->image, visible);
+
+ gtk_widget_show (GTK_WIDGET (alert_bar));
+
+ /* Warnings are generally meant for transient errors.
+ * No need to leave them up indefinitely. Close them
+ * automatically if the user hasn't responded after a
+ * reasonable period of time has elapsed. */
+ if (message_type == GTK_MESSAGE_WARNING)
+ e_alert_start_timer (alert, WARNING_TIMEOUT_SECONDS);
+}
+
+static void
+alert_bar_response_cb (EAlert *alert,
+ gint response_id,
+ EAlertBar *alert_bar)
+{
+ GQueue *queue;
+ EAlert *head;
+ gboolean was_head;
+
+ queue = &alert_bar->priv->alerts;
+ head = g_queue_peek_head (queue);
+ was_head = (alert == head);
+
+ g_signal_handlers_disconnect_by_func (
+ alert, alert_bar_response_cb, alert_bar);
+
+ if (g_queue_remove (queue, alert))
+ g_object_unref (alert);
+
+ if (g_queue_is_empty (queue))
+ gtk_widget_hide (GTK_WIDGET (alert_bar));
+ else if (was_head) {
+ GtkInfoBar *info_bar = GTK_INFO_BAR (alert_bar);
+ gtk_info_bar_response (info_bar, response_id);
+ alert_bar_show_alert (alert_bar);
+ }
+}
+
+static void
+alert_bar_dispose (GObject *object)
+{
+ EAlertBarPrivate *priv;
+
+ priv = E_ALERT_BAR_GET_PRIVATE (object);
+
+ while (!g_queue_is_empty (&priv->alerts)) {
+ EAlert *alert = g_queue_pop_head (&priv->alerts);
+ g_signal_handlers_disconnect_by_func (
+ alert, alert_bar_response_cb, object);
+ g_object_unref (alert);
+ }
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (e_alert_bar_parent_class)->dispose (object);
+}
+
+static void
+alert_bar_constructed (GObject *object)
+{
+ EAlertBarPrivate *priv;
+ GtkInfoBar *info_bar;
+ GtkWidget *action_area;
+ GtkWidget *content_area;
+ GtkWidget *container;
+ GtkWidget *widget;
+
+ priv = E_ALERT_BAR_GET_PRIVATE (object);
+
+ /* Chain up to parent's constructed() method. */
+ G_OBJECT_CLASS (e_alert_bar_parent_class)->constructed (object);
+
+ g_queue_init (&priv->alerts);
+
+ info_bar = GTK_INFO_BAR (object);
+ action_area = gtk_info_bar_get_action_area (info_bar);
+ content_area = gtk_info_bar_get_content_area (info_bar);
+
+ gtk_orientable_set_orientation (
+ GTK_ORIENTABLE (action_area), GTK_ORIENTATION_HORIZONTAL);
+ gtk_widget_set_valign (action_area, GTK_ALIGN_START);
+
+ container = content_area;
+
+ widget = gtk_image_new ();
+ gtk_misc_set_alignment (GTK_MISC (widget), 0.5, 0.0);
+ gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+ priv->image = widget;
+ gtk_widget_show (widget);
+
+ widget = gtk_vbox_new (FALSE, 12);
+ gtk_box_pack_start (GTK_BOX (container), widget, TRUE, TRUE, 0);
+ gtk_widget_show (widget);
+
+ container = widget;
+
+ widget = gtk_label_new (NULL);
+ gtk_label_set_line_wrap (GTK_LABEL (widget), TRUE);
+ gtk_label_set_selectable (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);
+ priv->primary_label = widget;
+ gtk_widget_show (widget);
+
+ widget = gtk_label_new (NULL);
+ gtk_label_set_line_wrap (GTK_LABEL (widget), TRUE);
+ gtk_label_set_selectable (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);
+ priv->secondary_label = widget;
+ gtk_widget_show (widget);
+
+ container = action_area;
+}
+
+static GtkSizeRequestMode
+alert_bar_get_request_mode (GtkWidget *widget)
+{
+ /* GtkBox does width-for-height by default. But we
+ * want the alert bar to be as short as possible. */
+ return GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH;
+}
+
+static void
+e_alert_bar_class_init (EAlertBarClass *class)
+{
+ GObjectClass *object_class;
+ GtkWidgetClass *widget_class;
+
+ g_type_class_add_private (class, sizeof (EAlertBarPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->dispose = alert_bar_dispose;
+ object_class->constructed = alert_bar_constructed;
+
+ widget_class = GTK_WIDGET_CLASS (class);
+ widget_class->get_request_mode = alert_bar_get_request_mode;
+}
+
+static void
+e_alert_bar_init (EAlertBar *alert_bar)
+{
+ alert_bar->priv = E_ALERT_BAR_GET_PRIVATE (alert_bar);
+}
+
+GtkWidget *
+e_alert_bar_new (void)
+{
+ return g_object_new (E_TYPE_ALERT_BAR, NULL);
+}
+
+void
+e_alert_bar_clear (EAlertBar *alert_bar)
+{
+ GQueue *queue;
+ EAlert *alert;
+
+ g_return_if_fail (E_IS_ALERT_BAR (alert_bar));
+
+ queue = &alert_bar->priv->alerts;
+
+ while ((alert = g_queue_pop_head (queue)) != NULL)
+ alert_bar_response_close (alert);
+}
+
+typedef struct {
+ gboolean found;
+ EAlert *looking_for;
+} DuplicateData;
+
+static void
+alert_bar_find_duplicate_cb (EAlert *alert,
+ DuplicateData *dd)
+{
+ g_return_if_fail (dd->looking_for != NULL);
+
+ dd->found |= (
+ e_alert_get_message_type (alert) ==
+ e_alert_get_message_type (dd->looking_for) &&
+ g_strcmp0 (
+ e_alert_get_primary_text (alert),
+ e_alert_get_primary_text (dd->looking_for)) == 0 &&
+ g_strcmp0 (
+ e_alert_get_secondary_text (alert),
+ e_alert_get_secondary_text (dd->looking_for)) == 0);
+}
+
+void
+e_alert_bar_add_alert (EAlertBar *alert_bar,
+ EAlert *alert)
+{
+ DuplicateData dd;
+
+ g_return_if_fail (E_IS_ALERT_BAR (alert_bar));
+ g_return_if_fail (E_IS_ALERT (alert));
+
+ dd.found = FALSE;
+ dd.looking_for = alert;
+
+ g_queue_foreach (
+ &alert_bar->priv->alerts,
+ (GFunc) alert_bar_find_duplicate_cb, &dd);
+
+ if (dd.found)
+ return;
+
+ g_signal_connect (
+ alert, "response",
+ G_CALLBACK (alert_bar_response_cb), alert_bar);
+
+ g_queue_push_head (&alert_bar->priv->alerts, g_object_ref (alert));
+
+ alert_bar_show_alert (alert_bar);
+}
diff --git a/e-util/e-alert-bar.h b/e-util/e-alert-bar.h
new file mode 100644
index 0000000000..ae5b315b40
--- /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/e-util/e-alert-dialog.c b/e-util/e-alert-dialog.c
new file mode 100644
index 0000000000..75650902ae
--- /dev/null
+++ b/e-util/e-alert-dialog.c
@@ -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:
+ * 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
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <glib/gi18n.h>
+
+#include "e-alert-dialog.h"
+
+#define E_ALERT_DIALOG_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_ALERT_DIALOG, EAlertDialogPrivate))
+
+struct _EAlertDialogPrivate {
+ GtkWidget *content_area; /* not referenced */
+ EAlert *alert;
+};
+
+enum {
+ PROP_0,
+ PROP_ALERT
+};
+
+G_DEFINE_TYPE (
+ EAlertDialog,
+ e_alert_dialog,
+ GTK_TYPE_DIALOG)
+
+static void
+alert_dialog_set_alert (EAlertDialog *dialog,
+ EAlert *alert)
+{
+ g_return_if_fail (E_IS_ALERT (alert));
+ g_return_if_fail (dialog->priv->alert == NULL);
+
+ dialog->priv->alert = g_object_ref (alert);
+}
+
+static void
+alert_dialog_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_ALERT:
+ alert_dialog_set_alert (
+ E_ALERT_DIALOG (object),
+ g_value_get_object (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+alert_dialog_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_ALERT:
+ g_value_set_object (
+ value, e_alert_dialog_get_alert (
+ E_ALERT_DIALOG (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+alert_dialog_dispose (GObject *object)
+{
+ EAlertDialogPrivate *priv;
+
+ priv = E_ALERT_DIALOG_GET_PRIVATE (object);
+
+ if (priv->alert) {
+ g_signal_handlers_disconnect_matched (
+ priv->alert, G_SIGNAL_MATCH_DATA,
+ 0, 0, NULL, NULL, object);
+ g_object_unref (priv->alert);
+ priv->alert = NULL;
+ }
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (e_alert_dialog_parent_class)->dispose (object);
+}
+
+static void
+alert_dialog_constructed (GObject *object)
+{
+ EAlert *alert;
+ EAlertDialog *dialog;
+ GtkWidget *action_area;
+ GtkWidget *content_area;
+ GtkWidget *container;
+ GtkWidget *widget;
+ PangoAttribute *attr;
+ PangoAttrList *list;
+ GList *actions;
+ const gchar *primary, *secondary;
+ gint default_response;
+ gint min_width = -1, prefer_width = -1;
+ gint height;
+
+ /* Chain up to parent's constructed() method. */
+ G_OBJECT_CLASS (e_alert_dialog_parent_class)->constructed (object);
+
+ dialog = E_ALERT_DIALOG (object);
+ alert = e_alert_dialog_get_alert (dialog);
+
+ default_response = e_alert_get_default_response (alert);
+
+ gtk_window_set_title (GTK_WINDOW (dialog), " ");
+
+ action_area = gtk_dialog_get_action_area (GTK_DIALOG (dialog));
+ content_area = gtk_dialog_get_content_area (GTK_DIALOG (dialog));
+
+ gtk_widget_ensure_style (GTK_WIDGET (dialog));
+ gtk_container_set_border_width (GTK_CONTAINER (action_area), 12);
+ gtk_container_set_border_width (GTK_CONTAINER (content_area), 0);
+
+ gtk_window_set_destroy_with_parent (GTK_WINDOW (dialog), TRUE);
+
+ /* Forward EAlert::response signals to GtkDialog::response. */
+ g_signal_connect_swapped (
+ alert, "response",
+ G_CALLBACK (gtk_dialog_response), dialog);
+
+ /* Add buttons from actions. */
+ actions = e_alert_peek_actions (alert);
+ if (!actions) {
+ GtkAction *action;
+
+ /* Make sure there is at least one action, thus the dialog can be closed. */
+ action = gtk_action_new (
+ "alert-response-0", _("_Dismiss"), NULL, NULL);
+ e_alert_add_action (alert, action, GTK_RESPONSE_CLOSE);
+ g_object_unref (action);
+
+ actions = e_alert_peek_actions (alert);
+ }
+
+ while (actions != NULL) {
+ GtkWidget *button;
+ gpointer data;
+
+ /* These actions are already wired to trigger an
+ * EAlert::response signal when activated, which
+ * will in turn call to gtk_dialog_response(),
+ * so we can add buttons directly to the action
+ * area without knowing their response IDs.
+ * (XXX Well, kind of. See below.) */
+
+ button = gtk_button_new ();
+
+ gtk_widget_set_can_default (button, TRUE);
+
+ gtk_activatable_set_related_action (
+ GTK_ACTIVATABLE (button),
+ GTK_ACTION (actions->data));
+
+ gtk_box_pack_end (
+ GTK_BOX (action_area),
+ button, FALSE, FALSE, 0);
+
+ /* This is set in e_alert_add_action(). */
+ data = g_object_get_data (
+ actions->data, "e-alert-response-id");
+
+ /* Normally GtkDialog sets the initial focus widget to
+ * the button corresponding to the default response, but
+ * because the buttons are not directly tied to response
+ * IDs, we have set both the default widget and the
+ * initial focus widget ourselves. */
+ if (GPOINTER_TO_INT (data) == default_response) {
+ gtk_widget_grab_default (button);
+ gtk_widget_grab_focus (button);
+ }
+
+ actions = g_list_next (actions);
+ }
+
+ widget = gtk_hbox_new (FALSE, 12);
+ gtk_container_set_border_width (GTK_CONTAINER (widget), 12);
+ gtk_box_pack_start (GTK_BOX (content_area), widget, FALSE, FALSE, 0);
+ gtk_widget_show (widget);
+
+ container = widget;
+
+ widget = e_alert_create_image (alert, GTK_ICON_SIZE_DIALOG);
+ gtk_misc_set_alignment (GTK_MISC (widget), 0.0, 0.0);
+ gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+ gtk_widget_show (widget);
+
+ widget = gtk_vbox_new (FALSE, 12);
+ gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+ dialog->priv->content_area = widget;
+ gtk_widget_show (widget);
+
+ container = widget;
+
+ primary = e_alert_get_primary_text (alert);
+ secondary = e_alert_get_secondary_text (alert);
+
+ list = pango_attr_list_new ();
+ attr = pango_attr_scale_new (PANGO_SCALE_LARGE);
+ pango_attr_list_insert (list, attr);
+ attr = pango_attr_weight_new (PANGO_WEIGHT_BOLD);
+ pango_attr_list_insert (list, attr);
+
+ widget = gtk_label_new (primary);
+ gtk_label_set_attributes (GTK_LABEL (widget), list);
+ gtk_label_set_line_wrap (GTK_LABEL (widget), TRUE);
+ gtk_label_set_selectable (GTK_LABEL (widget), TRUE);
+ gtk_misc_set_alignment (GTK_MISC (widget), 0.0, 0.0);
+ gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+ gtk_widget_set_can_focus (widget, FALSE);
+ gtk_widget_show (widget);
+
+ widget = gtk_label_new (secondary);
+ gtk_label_set_line_wrap (GTK_LABEL (widget), TRUE);
+ gtk_label_set_selectable (GTK_LABEL (widget), TRUE);
+ gtk_misc_set_alignment (GTK_MISC (widget), 0.0, 0.0);
+ gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+ gtk_widget_set_can_focus (widget, FALSE);
+ gtk_widget_show (widget);
+
+ widget = GTK_WIDGET (dialog);
+
+ height = gtk_widget_get_allocated_height (widget);
+ gtk_widget_get_preferred_width_for_height (
+ widget, height, &min_width, &prefer_width);
+ if (min_width < prefer_width)
+ gtk_window_set_default_size (
+ GTK_WINDOW (dialog), MIN (
+ (min_width + prefer_width) / 2,
+ min_width * 5 / 4), -1);
+
+ pango_attr_list_unref (list);
+}
+
+static void
+e_alert_dialog_class_init (EAlertDialogClass *class)
+{
+ GObjectClass *object_class;
+
+ g_type_class_add_private (class, sizeof (EAlertDialogPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = alert_dialog_set_property;
+ object_class->get_property = alert_dialog_get_property;
+ object_class->dispose = alert_dialog_dispose;
+ object_class->constructed = alert_dialog_constructed;
+
+ g_object_class_install_property (
+ object_class,
+ PROP_ALERT,
+ g_param_spec_object (
+ "alert",
+ "Alert",
+ "Alert to be displayed",
+ E_TYPE_ALERT,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+}
+
+static void
+e_alert_dialog_init (EAlertDialog *dialog)
+{
+ dialog->priv = E_ALERT_DIALOG_GET_PRIVATE (dialog);
+}
+
+GtkWidget *
+e_alert_dialog_new (GtkWindow *parent,
+ EAlert *alert)
+{
+ g_return_val_if_fail (E_IS_ALERT (alert), NULL);
+
+ return g_object_new (
+ E_TYPE_ALERT_DIALOG,
+ "alert", alert, "transient-for", parent, NULL);
+}
+
+GtkWidget *
+e_alert_dialog_new_for_args (GtkWindow *parent,
+ const gchar *tag,
+ ...)
+{
+ GtkWidget *dialog;
+ EAlert *alert;
+ va_list ap;
+
+ g_return_val_if_fail (tag != NULL, NULL);
+
+ va_start (ap, tag);
+ alert = e_alert_new_valist (tag, ap);
+ va_end (ap);
+
+ dialog = e_alert_dialog_new (parent, alert);
+
+ g_object_unref (alert);
+
+ return dialog;
+}
+
+gint
+e_alert_run_dialog (GtkWindow *parent,
+ EAlert *alert)
+{
+ GtkWidget *dialog;
+ gint response;
+
+ g_return_val_if_fail (E_IS_ALERT (alert), 0);
+
+ dialog = e_alert_dialog_new (parent, alert);
+ response = gtk_dialog_run (GTK_DIALOG (dialog));
+ gtk_widget_destroy (dialog);
+
+ return response;
+}
+
+gint
+e_alert_run_dialog_for_args (GtkWindow *parent,
+ const gchar *tag,
+ ...)
+{
+ EAlert *alert;
+ gint response;
+ va_list ap;
+
+ g_return_val_if_fail (tag != NULL, 0);
+
+ va_start (ap, tag);
+ alert = e_alert_new_valist (tag, ap);
+ va_end (ap);
+
+ response = e_alert_run_dialog (parent, alert);
+
+ g_object_unref (alert);
+
+ return response;
+}
+
+/**
+ * e_alert_dialog_get_alert:
+ * @dialog: an #EAlertDialog
+ *
+ * Returns the #EAlert associated with @dialog.
+ *
+ * Returns: the #EAlert associated with @dialog
+ **/
+EAlert *
+e_alert_dialog_get_alert (EAlertDialog *dialog)
+{
+ g_return_val_if_fail (E_IS_ALERT_DIALOG (dialog), NULL);
+
+ return dialog->priv->alert;
+}
+
+/**
+ * e_alert_dialog_get_content_area:
+ * @dialog: an #EAlertDialog
+ *
+ * Returns the vertical box containing the primary and secondary labels.
+ * Use this to pack additional widgets into the dialog with the proper
+ * horizontal alignment (maintaining the left margin below the image).
+ *
+ * Returns: the content area #GtkBox
+ **/
+GtkWidget *
+e_alert_dialog_get_content_area (EAlertDialog *dialog)
+{
+ g_return_val_if_fail (E_IS_ALERT_DIALOG (dialog), NULL);
+
+ return dialog->priv->content_area;
+}
diff --git a/e-util/e-alert-dialog.h b/e-util/e-alert-dialog.h
new file mode 100644
index 0000000000..3d2662a398
--- /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 0000000000..3077261a90
--- /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 0000000000..c8fd5127e7
--- /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/e-util/e-alert.c b/e-util/e-alert.c
new file mode 100644
index 0000000000..5a08e07122
--- /dev/null
+++ b/e-util/e-alert.c
@@ -0,0 +1,1003 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You 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
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <sys/types.h>
+
+#include <libxml/parser.h>
+#include <libxml/xmlmemory.h>
+
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+
+#include <libedataserver/libedataserver.h>
+
+#include "e-alert.h"
+#include "e-alert-sink.h"
+
+#define d(x)
+
+#define E_ALERT_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_ALERT, EAlertPrivate))
+
+typedef struct _EAlertButton EAlertButton;
+
+struct _e_alert {
+ const gchar *id;
+ GtkMessageType message_type;
+ gint default_response;
+ const gchar *primary_text;
+ const gchar *secondary_text;
+ EAlertButton *buttons;
+};
+
+struct _e_alert_table {
+ const gchar *domain;
+ const gchar *translation_domain;
+ GHashTable *alerts;
+};
+
+struct _EAlertButton {
+ EAlertButton *next;
+ const gchar *stock_id;
+ const gchar *label;
+ gint response_id;
+};
+
+static GHashTable *alert_table;
+
+/* ********************************************************************** */
+
+static EAlertButton default_ok_button = {
+ NULL, GTK_STOCK_OK, NULL, GTK_RESPONSE_OK
+};
+
+static struct _e_alert default_alerts[] = {
+ { "error", GTK_MESSAGE_ERROR, GTK_RESPONSE_OK,
+ "{0}", "{1}", &default_ok_button },
+ { "warning", GTK_MESSAGE_WARNING, GTK_RESPONSE_OK,
+ "{0}", "{1}", &default_ok_button }
+};
+
+/* ********************************************************************** */
+
+struct _EAlertPrivate {
+ gchar *tag;
+ GPtrArray *args;
+ gchar *primary_text;
+ gchar *secondary_text;
+ struct _e_alert *definition;
+ GtkMessageType message_type;
+ gint default_response;
+ guint timeout_id;
+
+ /* It may occur to one that we could use a GtkActionGroup here,
+ * but we need to preserve the button order and GtkActionGroup
+ * uses a hash table, which does not preserve order. */
+ GQueue actions;
+};
+
+enum {
+ PROP_0,
+ PROP_ARGS,
+ PROP_TAG,
+ PROP_MESSAGE_TYPE,
+ PROP_PRIMARY_TEXT,
+ PROP_SECONDARY_TEXT
+};
+
+enum {
+ RESPONSE,
+ LAST_SIGNAL
+};
+
+static gulong signals[LAST_SIGNAL];
+
+G_DEFINE_TYPE (
+ EAlert,
+ e_alert,
+ G_TYPE_OBJECT)
+
+static gint
+map_response (const gchar *name)
+{
+ GEnumClass *class;
+ GEnumValue *value;
+
+ class = g_type_class_ref (GTK_TYPE_RESPONSE_TYPE);
+ value = g_enum_get_value_by_name (class, name);
+ g_type_class_unref (class);
+
+ return (value != NULL) ? value->value : 0;
+}
+
+static GtkMessageType
+map_type (const gchar *nick)
+{
+ GEnumClass *class;
+ GEnumValue *value;
+
+ class = g_type_class_ref (GTK_TYPE_MESSAGE_TYPE);
+ value = g_enum_get_value_by_nick (class, nick);
+ g_type_class_unref (class);
+
+ return (value != NULL) ? value->value : GTK_MESSAGE_ERROR;
+}
+
+/*
+ * XML format:
+ *
+ * <error id="error-id" type="info|warning|question|error"?
+ * response="default_response"? >
+ * <primary> Primary error text.</primary>?
+ * <secondary> Secondary error text.</secondary>?
+ * <button stock="stock-button-id"? label="button label"?
+ * response="response_id"? /> *
+ * </error>
+ */
+
+static void
+e_alert_load (const gchar *path)
+{
+ xmlDocPtr doc = NULL;
+ xmlNodePtr root, error, scan;
+ struct _e_alert *e;
+ EAlertButton *lastbutton;
+ struct _e_alert_table *table;
+ gchar *tmp;
+
+ d (printf ("loading error file %s\n", path));
+
+ doc = e_xml_parse_file (path);
+ if (doc == NULL) {
+ g_warning ("Error file '%s' not found", path);
+ return;
+ }
+
+ root = xmlDocGetRootElement (doc);
+ if (root == NULL
+ || strcmp ((gchar *) root->name, "error-list") != 0
+ || (tmp = (gchar *) xmlGetProp (root, (const guchar *)"domain")) == NULL) {
+ g_warning ("Error file '%s' invalid format", path);
+ xmlFreeDoc (doc);
+ return;
+ }
+
+ table = g_hash_table_lookup (alert_table, tmp);
+ if (table == NULL) {
+ gchar *tmp2;
+
+ table = g_malloc0 (sizeof (*table));
+ table->domain = g_strdup (tmp);
+ table->alerts = g_hash_table_new (g_str_hash, g_str_equal);
+ g_hash_table_insert (alert_table, (gpointer) table->domain, table);
+
+ tmp2 = (gchar *) xmlGetProp (
+ root, (const guchar *) "translation-domain");
+ if (tmp2) {
+ table->translation_domain = g_strdup (tmp2);
+ xmlFree (tmp2);
+
+ tmp2 = (gchar *) xmlGetProp (
+ root, (const guchar *) "translation-localedir");
+ if (tmp2) {
+ bindtextdomain (table->translation_domain, tmp2);
+ xmlFree (tmp2);
+ }
+ }
+ } else
+ g_warning (
+ "Error file '%s', domain '%s' "
+ "already used, merging", path, tmp);
+ xmlFree (tmp);
+
+ for (error = root->children; error; error = error->next) {
+ if (!strcmp ((gchar *) error->name, "error")) {
+ tmp = (gchar *) xmlGetProp (error, (const guchar *)"id");
+ if (tmp == NULL)
+ continue;
+
+ e = g_malloc0 (sizeof (*e));
+ e->id = g_strdup (tmp);
+
+ xmlFree (tmp);
+ lastbutton = (EAlertButton *) &e->buttons;
+
+ tmp = (gchar *) xmlGetProp (error, (const guchar *)"type");
+ e->message_type = map_type (tmp);
+ if (tmp)
+ xmlFree (tmp);
+
+ tmp = (gchar *) xmlGetProp (error, (const guchar *)"default");
+ if (tmp) {
+ e->default_response = map_response (tmp);
+ xmlFree (tmp);
+ }
+
+ for (scan = error->children; scan; scan = scan->next) {
+ if (!strcmp ((gchar *) scan->name, "primary")) {
+ if ((tmp = (gchar *) xmlNodeGetContent (scan))) {
+ e->primary_text = g_strdup (
+ dgettext (table->
+ translation_domain, tmp));
+ xmlFree (tmp);
+ }
+ } else if (!strcmp ((gchar *) scan->name, "secondary")) {
+ if ((tmp = (gchar *) xmlNodeGetContent (scan))) {
+ e->secondary_text = g_strdup (
+ dgettext (table->
+ translation_domain, tmp));
+ xmlFree (tmp);
+ }
+ } else if (!strcmp ((gchar *) scan->name, "button")) {
+ EAlertButton *button;
+ gchar *label = NULL;
+ gchar *stock_id = NULL;
+
+ button = g_new0 (EAlertButton, 1);
+ tmp = (gchar *) xmlGetProp (scan, (const guchar *)"stock");
+ if (tmp) {
+ stock_id = g_strdup (tmp);
+ button->stock_id = stock_id;
+ xmlFree (tmp);
+ }
+ tmp = (gchar *) xmlGetProp (
+ scan, (xmlChar *) "label");
+ if (tmp) {
+ label = g_strdup (
+ dgettext (table->
+ translation_domain,
+ tmp));
+ button->label = label;
+ xmlFree (tmp);
+ }
+ tmp = (gchar *) xmlGetProp (
+ scan, (xmlChar *) "response");
+ if (tmp) {
+ button->response_id =
+ map_response (tmp);
+ xmlFree (tmp);
+ }
+
+ if (stock_id == NULL && label == NULL) {
+ g_warning (
+ "Error file '%s': "
+ "missing button "
+ "details in error "
+ "'%s'", path, e->id);
+ g_free (stock_id);
+ g_free (label);
+ g_free (button);
+ } else {
+ lastbutton->next = button;
+ lastbutton = button;
+ }
+ }
+ }
+
+ g_hash_table_insert (table->alerts, (gpointer) e->id, e);
+ }
+ }
+
+ xmlFreeDoc (doc);
+}
+
+static void
+e_alert_load_tables (void)
+{
+ GDir *dir;
+ const gchar *d;
+ gchar *base;
+ struct _e_alert_table *table;
+ gint i;
+
+ if (alert_table != NULL)
+ return;
+
+ alert_table = g_hash_table_new (g_str_hash, g_str_equal);
+
+ /* setup system alert types */
+ table = g_malloc0 (sizeof (*table));
+ table->domain = "builtin";
+ table->alerts = g_hash_table_new (g_str_hash, g_str_equal);
+ for (i = 0; i < G_N_ELEMENTS (default_alerts); i++)
+ g_hash_table_insert (
+ table->alerts, (gpointer)
+ default_alerts[i].id, &default_alerts[i]);
+ g_hash_table_insert (alert_table, (gpointer) table->domain, table);
+
+ /* look for installed alert tables */
+ base = g_build_filename (EVOLUTION_PRIVDATADIR, "errors", NULL);
+ dir = g_dir_open (base, 0, NULL);
+ if (dir == NULL) {
+ g_free (base);
+ return;
+ }
+
+ while ((d = g_dir_read_name (dir))) {
+ gchar *path;
+
+ if (d[0] == '.')
+ continue;
+
+ path = g_build_filename (base, d, NULL);
+ e_alert_load (path);
+ g_free (path);
+ }
+
+ g_dir_close (dir);
+ g_free (base);
+}
+
+static void
+alert_action_activate (EAlert *alert,
+ GtkAction *action)
+{
+ GObject *object;
+ gpointer data;
+
+ object = G_OBJECT (action);
+ data = g_object_get_data (object, "e-alert-response-id");
+ e_alert_response (alert, GPOINTER_TO_INT (data));
+}
+
+static gchar *
+alert_format_string (const gchar *format,
+ GPtrArray *args)
+{
+ GString *string;
+ const gchar *end, *newstart;
+ gint id;
+
+ string = g_string_sized_new (strlen (format));
+
+ while (format
+ && (newstart = strchr (format, '{'))
+ && (end = strchr (newstart + 1, '}'))) {
+ g_string_append_len (string, format, newstart - format);
+ id = atoi (newstart + 1);
+ if (id < args->len) {
+ g_string_append (string, args->pdata[id]);
+ } else
+ g_warning (
+ "Error references argument %d "
+ "not supplied by caller", id);
+ format = end + 1;
+ }
+
+ g_string_append (string, format);
+
+ return g_string_free (string, FALSE);
+}
+
+static void
+alert_set_tag (EAlert *alert,
+ const gchar *tag)
+{
+ struct _e_alert *definition;
+ struct _e_alert_table *table;
+ gchar *domain, *id;
+
+ alert->priv->tag = g_strdup (tag);
+
+ g_return_if_fail (alert_table);
+
+ domain = g_alloca (strlen (tag) + 1);
+ strcpy (domain, tag);
+ id = strchr (domain, ':');
+ if (id)
+ *id++ = 0;
+ else {
+ g_warning ("Alert tag '%s' is missing a domain", tag);
+ return;
+ }
+
+ table = g_hash_table_lookup (alert_table, domain);
+ g_return_if_fail (table);
+
+ definition = g_hash_table_lookup (table->alerts, id);
+ g_warn_if_fail (definition);
+
+ alert->priv->definition = definition;
+}
+
+static gboolean
+alert_timeout_cb (EAlert *alert)
+{
+ e_alert_response (alert, alert->priv->default_response);
+
+ return FALSE;
+}
+
+static void
+alert_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ EAlert *alert = (EAlert *) object;
+
+ switch (property_id) {
+ case PROP_TAG:
+ alert_set_tag (
+ E_ALERT (object),
+ g_value_get_string (value));
+ return;
+
+ case PROP_ARGS:
+ alert->priv->args = g_value_dup_boxed (value);
+ return;
+
+ case PROP_MESSAGE_TYPE:
+ e_alert_set_message_type (
+ E_ALERT (object),
+ g_value_get_enum (value));
+ return;
+
+ case PROP_PRIMARY_TEXT:
+ e_alert_set_primary_text (
+ E_ALERT (object),
+ g_value_get_string (value));
+ return;
+
+ case PROP_SECONDARY_TEXT:
+ e_alert_set_secondary_text (
+ E_ALERT (object),
+ g_value_get_string (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+alert_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ EAlert *alert = (EAlert *) object;
+
+ switch (property_id) {
+ case PROP_TAG:
+ g_value_set_string (value, alert->priv->tag);
+ return;
+
+ case PROP_ARGS:
+ g_value_set_boxed (value, alert->priv->args);
+ return;
+
+ case PROP_MESSAGE_TYPE:
+ g_value_set_enum (
+ value, e_alert_get_message_type (
+ E_ALERT (object)));
+ return;
+
+ case PROP_PRIMARY_TEXT:
+ g_value_set_string (
+ value, e_alert_get_primary_text (
+ E_ALERT (object)));
+ return;
+
+ case PROP_SECONDARY_TEXT:
+ g_value_set_string (
+ value, e_alert_get_secondary_text (
+ E_ALERT (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+alert_dispose (GObject *object)
+{
+ EAlert *alert = E_ALERT (object);
+
+ if (alert->priv->timeout_id > 0) {
+ g_source_remove (alert->priv->timeout_id);
+ alert->priv->timeout_id = 0;
+ }
+
+ while (!g_queue_is_empty (&alert->priv->actions)) {
+ GtkAction *action;
+
+ action = g_queue_pop_head (&alert->priv->actions);
+ g_signal_handlers_disconnect_by_func (
+ action, G_CALLBACK (alert_action_activate), object);
+ g_object_unref (action);
+ }
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (e_alert_parent_class)->dispose (object);
+}
+
+static void
+alert_finalize (GObject *object)
+{
+ EAlertPrivate *priv;
+
+ priv = E_ALERT_GET_PRIVATE (object);
+
+ g_free (priv->tag);
+ g_free (priv->primary_text);
+ g_free (priv->secondary_text);
+
+ g_ptr_array_free (priv->args, TRUE);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (e_alert_parent_class)->finalize (object);
+}
+
+static void
+alert_constructed (GObject *object)
+{
+ EAlert *alert;
+ EAlertButton *button;
+ struct _e_alert *definition;
+ gint ii = 0;
+
+ alert = E_ALERT (object);
+ definition = alert->priv->definition;
+ g_return_if_fail (definition != NULL);
+
+ e_alert_set_message_type (alert, definition->message_type);
+ e_alert_set_default_response (alert, definition->default_response);
+
+ /* Build actions out of the button definitions. */
+ button = definition->buttons;
+ while (button != NULL) {
+ GtkAction *action;
+ gchar *action_name;
+
+ action_name = g_strdup_printf ("alert-response-%d", ii++);
+
+ if (button->stock_id != NULL) {
+ action = gtk_action_new (
+ action_name, NULL, NULL, button->stock_id);
+ e_alert_add_action (
+ alert, action, button->response_id);
+ g_object_unref (action);
+
+ } else if (button->label != NULL) {
+ action = gtk_action_new (
+ action_name, button->label, NULL, NULL);
+ e_alert_add_action (
+ alert, action, button->response_id);
+ g_object_unref (action);
+ }
+
+ g_free (action_name);
+
+ button = button->next;
+ }
+
+ /* Chain up to parent's constructed() method. */
+ G_OBJECT_CLASS (e_alert_parent_class)->constructed (object);
+}
+
+static void
+e_alert_class_init (EAlertClass *class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+ g_type_class_add_private (class, sizeof (EAlertPrivate));
+
+ object_class->set_property = alert_set_property;
+ object_class->get_property = alert_get_property;
+ object_class->dispose = alert_dispose;
+ object_class->finalize = alert_finalize;
+ object_class->constructed = alert_constructed;
+
+ g_object_class_install_property (
+ object_class,
+ PROP_ARGS,
+ g_param_spec_boxed (
+ "args",
+ "Arguments",
+ "Arguments for formatting the alert",
+ G_TYPE_PTR_ARRAY,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_TAG,
+ g_param_spec_string (
+ "tag",
+ "alert tag",
+ "A tag describing the alert",
+ "",
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_MESSAGE_TYPE,
+ g_param_spec_enum (
+ "message-type",
+ NULL,
+ NULL,
+ GTK_TYPE_MESSAGE_TYPE,
+ GTK_MESSAGE_ERROR,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_PRIMARY_TEXT,
+ g_param_spec_string (
+ "primary-text",
+ NULL,
+ NULL,
+ NULL,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_SECONDARY_TEXT,
+ g_param_spec_string (
+ "secondary-text",
+ NULL,
+ NULL,
+ NULL,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ signals[RESPONSE] = g_signal_new (
+ "response",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (EAlertClass, response),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__INT,
+ G_TYPE_NONE, 1,
+ G_TYPE_INT);
+
+ e_alert_load_tables ();
+}
+
+static void
+e_alert_init (EAlert *alert)
+{
+ alert->priv = E_ALERT_GET_PRIVATE (alert);
+
+ g_queue_init (&alert->priv->actions);
+}
+
+/**
+ * e_alert_new:
+ * @tag: alert identifier
+ * @arg0: The first argument for the alert formatter. The list must
+ * be NULL terminated.
+ *
+ * Creates a new EAlert. The @tag argument is used to determine
+ * which alert to use, it is in the format domain:alert-id. The NULL
+ * terminated list of arguments, starting with @arg0 is used to fill
+ * out the alert definition.
+ *
+ * Returns: a new #EAlert
+ **/
+EAlert *
+e_alert_new (const gchar *tag,
+ ...)
+{
+ EAlert *e;
+ va_list va;
+
+ va_start (va, tag);
+ e = e_alert_new_valist (tag, va);
+ va_end (va);
+
+ return e;
+}
+
+EAlert *
+e_alert_new_valist (const gchar *tag,
+ va_list va)
+{
+ EAlert *alert;
+ GPtrArray *args;
+ gchar *tmp;
+
+ args = g_ptr_array_new_with_free_func (g_free);
+
+ tmp = va_arg (va, gchar *);
+ while (tmp) {
+ g_ptr_array_add (args, g_strdup (tmp));
+ tmp = va_arg (va, gchar *);
+ }
+
+ alert = e_alert_new_array (tag, args);
+
+ g_ptr_array_unref (args);
+
+ return alert;
+}
+
+EAlert *
+e_alert_new_array (const gchar *tag,
+ GPtrArray *args)
+{
+ return g_object_new (E_TYPE_ALERT, "tag", tag, "args", args, NULL);
+}
+
+gint
+e_alert_get_default_response (EAlert *alert)
+{
+ g_return_val_if_fail (E_IS_ALERT (alert), 0);
+
+ return alert->priv->default_response;
+}
+
+void
+e_alert_set_default_response (EAlert *alert,
+ gint response_id)
+{
+ g_return_if_fail (E_IS_ALERT (alert));
+
+ alert->priv->default_response = response_id;
+}
+
+GtkMessageType
+e_alert_get_message_type (EAlert *alert)
+{
+ g_return_val_if_fail (E_IS_ALERT (alert), GTK_MESSAGE_OTHER);
+
+ return alert->priv->message_type;
+}
+
+void
+e_alert_set_message_type (EAlert *alert,
+ GtkMessageType message_type)
+{
+ g_return_if_fail (E_IS_ALERT (alert));
+
+ if (alert->priv->message_type == message_type)
+ return;
+
+ alert->priv->message_type = message_type;
+
+ g_object_notify (G_OBJECT (alert), "message-type");
+}
+
+const gchar *
+e_alert_get_primary_text (EAlert *alert)
+{
+ g_return_val_if_fail (E_IS_ALERT (alert), NULL);
+
+ if (alert->priv->primary_text != NULL)
+ goto exit;
+
+ if (alert->priv->definition == NULL)
+ goto exit;
+
+ if (alert->priv->definition->primary_text == NULL)
+ goto exit;
+
+ if (alert->priv->args == NULL)
+ goto exit;
+
+ alert->priv->primary_text = alert_format_string (
+ alert->priv->definition->primary_text,
+ alert->priv->args);
+
+exit:
+ return alert->priv->primary_text;
+}
+
+void
+e_alert_set_primary_text (EAlert *alert,
+ const gchar *primary_text)
+{
+ g_return_if_fail (E_IS_ALERT (alert));
+
+ if (g_strcmp0 (alert->priv->primary_text, primary_text) == 0)
+ return;
+
+ g_free (alert->priv->primary_text);
+ alert->priv->primary_text = g_strdup (primary_text);
+
+ g_object_notify (G_OBJECT (alert), "primary-text");
+}
+
+const gchar *
+e_alert_get_secondary_text (EAlert *alert)
+{
+ g_return_val_if_fail (E_IS_ALERT (alert), NULL);
+
+ if (alert->priv->secondary_text != NULL)
+ goto exit;
+
+ if (alert->priv->definition == NULL)
+ goto exit;
+
+ if (alert->priv->definition->secondary_text == NULL)
+ goto exit;
+
+ if (alert->priv->args == NULL)
+ goto exit;
+
+ alert->priv->secondary_text = alert_format_string (
+ alert->priv->definition->secondary_text,
+ alert->priv->args);
+
+exit:
+ return alert->priv->secondary_text;
+}
+
+void
+e_alert_set_secondary_text (EAlert *alert,
+ const gchar *secondary_text)
+{
+ g_return_if_fail (E_IS_ALERT (alert));
+
+ if (g_strcmp0 (alert->priv->secondary_text, secondary_text) == 0)
+ return;
+
+ g_free (alert->priv->secondary_text);
+ alert->priv->secondary_text = g_strdup (secondary_text);
+
+ g_object_notify (G_OBJECT (alert), "secondary-text");
+}
+
+const gchar *
+e_alert_get_stock_id (EAlert *alert)
+{
+ const gchar *stock_id;
+
+ g_return_val_if_fail (E_IS_ALERT (alert), NULL);
+
+ switch (e_alert_get_message_type (alert)) {
+ case GTK_MESSAGE_INFO:
+ stock_id = GTK_STOCK_DIALOG_INFO;
+ break;
+ case GTK_MESSAGE_WARNING:
+ stock_id = GTK_STOCK_DIALOG_WARNING;
+ break;
+ case GTK_MESSAGE_QUESTION:
+ stock_id = GTK_STOCK_DIALOG_QUESTION;
+ break;
+ case GTK_MESSAGE_ERROR:
+ stock_id = GTK_STOCK_DIALOG_ERROR;
+ break;
+ default:
+ stock_id = GTK_STOCK_MISSING_IMAGE;
+ g_warn_if_reached ();
+ break;
+ }
+
+ return stock_id;
+}
+
+void
+e_alert_add_action (EAlert *alert,
+ GtkAction *action,
+ gint response_id)
+{
+ g_return_if_fail (E_IS_ALERT (alert));
+ g_return_if_fail (GTK_ACTION (action));
+
+ g_object_set_data (
+ G_OBJECT (action), "e-alert-response-id",
+ GINT_TO_POINTER (response_id));
+
+ g_signal_connect_swapped (
+ action, "activate",
+ G_CALLBACK (alert_action_activate), alert);
+
+ g_queue_push_tail (&alert->priv->actions, g_object_ref (action));
+}
+
+GList *
+e_alert_peek_actions (EAlert *alert)
+{
+ g_return_val_if_fail (E_IS_ALERT (alert), NULL);
+
+ return g_queue_peek_head_link (&alert->priv->actions);
+}
+
+GtkWidget *
+e_alert_create_image (EAlert *alert,
+ GtkIconSize size)
+{
+ const gchar *stock_id;
+
+ g_return_val_if_fail (E_IS_ALERT (alert), NULL);
+
+ stock_id = e_alert_get_stock_id (alert);
+
+ return gtk_image_new_from_stock (stock_id, size);
+}
+
+void
+e_alert_response (EAlert *alert,
+ gint response_id)
+{
+ g_return_if_fail (E_IS_ALERT (alert));
+
+ g_signal_emit (alert, signals[RESPONSE], 0, response_id);
+}
+
+/**
+ * e_alert_start_timer:
+ * @alert: an #EAlert
+ * @seconds: seconds until timeout occurs
+ *
+ * Starts an internal timer for @alert. When the timer expires, @alert
+ * will emit the default response. There is only one timer per #EAlert,
+ * so calling this function repeatedly on the same #EAlert will restart
+ * its timer each time. If @seconds is zero, the timer is cancelled and
+ * no response will be emitted.
+ **/
+void
+e_alert_start_timer (EAlert *alert,
+ guint seconds)
+{
+ g_return_if_fail (E_IS_ALERT (alert));
+
+ if (alert->priv->timeout_id > 0) {
+ g_source_remove (alert->priv->timeout_id);
+ alert->priv->timeout_id = 0;
+ }
+
+ if (seconds > 0)
+ alert->priv->timeout_id = g_timeout_add_seconds (
+ seconds, (GSourceFunc) alert_timeout_cb, alert);
+}
+
+void
+e_alert_submit (EAlertSink *alert_sink,
+ const gchar *tag,
+ ...)
+{
+ va_list va;
+
+ va_start (va, tag);
+ e_alert_submit_valist (alert_sink, tag, va);
+ va_end (va);
+}
+
+void
+e_alert_submit_valist (EAlertSink *alert_sink,
+ const gchar *tag,
+ va_list va)
+{
+ EAlert *alert;
+
+ g_return_if_fail (E_IS_ALERT_SINK (alert_sink));
+ g_return_if_fail (tag != NULL);
+
+ alert = e_alert_new_valist (tag, va);
+ e_alert_sink_submit_alert (alert_sink, alert);
+ g_object_unref (alert);
+}
diff --git a/e-util/e-alert.h b/e-util/e-alert.h
new file mode 100644
index 0000000000..f62e612235
--- /dev/null
+++ b/e-util/e-alert.h
@@ -0,0 +1,119 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You 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_H
+#define E_ALERT_H
+
+#include <stdarg.h>
+#include <gtk/gtk.h>
+
+/* Standard GObject macros */
+#define E_TYPE_ALERT \
+ (e_alert_get_type ())
+#define E_ALERT(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), E_TYPE_ALERT, EAlert))
+#define E_ALERT_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), E_TYPE_ALERT, EAlertClass))
+#define E_IS_ALERT(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), E_TYPE_ALERT))
+#define E_IS_ALERT_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), E_TYPE_ALERT))
+#define E_ALERT_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), E_TYPE_ALERT, EAlertClass))
+
+/* takes filename, returns OK if yes */
+#define E_ALERT_ASK_FILE_EXISTS_OVERWRITE \
+ "system:ask-save-file-exists-overwrite"
+/* takes filename, reason */
+#define E_ALERT_NO_SAVE_FILE "system:no-save-file"
+/* takes filename, reason */
+#define E_ALERT_NO_LOAD_FILE "system:no-save-file"
+
+G_BEGIN_DECLS
+
+struct _EAlertSink;
+
+typedef struct _EAlert EAlert;
+typedef struct _EAlertClass EAlertClass;
+typedef struct _EAlertPrivate EAlertPrivate;
+
+struct _EAlert {
+ GObject parent;
+ EAlertPrivate *priv;
+};
+
+struct _EAlertClass {
+ GObjectClass parent_class;
+
+ /* Signals */
+ void (*response) (EAlert *alert,
+ gint response_id);
+};
+
+GType e_alert_get_type (void);
+EAlert * e_alert_new (const gchar *tag,
+ ...) G_GNUC_NULL_TERMINATED;
+EAlert * e_alert_new_valist (const gchar *tag,
+ va_list va);
+EAlert * e_alert_new_array (const gchar *tag,
+ GPtrArray *args);
+gint e_alert_get_default_response (EAlert *alert);
+void e_alert_set_default_response (EAlert *alert,
+ gint response_id);
+GtkMessageType e_alert_get_message_type (EAlert *alert);
+void e_alert_set_message_type (EAlert *alert,
+ GtkMessageType message_type);
+const gchar * e_alert_get_primary_text (EAlert *alert);
+void e_alert_set_primary_text (EAlert *alert,
+ const gchar *primary_text);
+const gchar * e_alert_get_secondary_text (EAlert *alert);
+void e_alert_set_secondary_text (EAlert *alert,
+ const gchar *secondary_text);
+const gchar * e_alert_get_stock_id (EAlert *alert);
+void e_alert_add_action (EAlert *alert,
+ GtkAction *action,
+ gint response_id);
+GList * e_alert_peek_actions (EAlert *alert);
+GtkWidget * e_alert_create_image (EAlert *alert,
+ GtkIconSize size);
+void e_alert_response (EAlert *alert,
+ gint response_id);
+void e_alert_start_timer (EAlert *alert,
+ guint seconds);
+
+void e_alert_submit (struct _EAlertSink *alert_sink,
+ const gchar *tag,
+ ...) G_GNUC_NULL_TERMINATED;
+void e_alert_submit_valist (struct _EAlertSink *alert_sink,
+ const gchar *tag,
+ va_list va);
+
+G_END_DECLS
+
+#endif /* E_ALERT_H */
diff --git a/e-util/e-attachment-bar.c b/e-util/e-attachment-bar.c
new file mode 100644
index 0000000000..3fc4753055
--- /dev/null
+++ b/e-util/e-attachment-bar.c
@@ -0,0 +1,778 @@
+/*
+ * e-attachment-bar.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-bar.h"
+
+#include <glib/gi18n.h>
+
+#include "e-attachment-store.h"
+#include "e-attachment-icon-view.h"
+#include "e-attachment-tree-view.h"
+
+#define E_ATTACHMENT_BAR_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_ATTACHMENT_BAR, EAttachmentBarPrivate))
+
+#define NUM_VIEWS 2
+
+struct _EAttachmentBarPrivate {
+ GtkTreeModel *model;
+ GtkWidget *vbox;
+ GtkWidget *expander;
+ GtkWidget *combo_box;
+ GtkWidget *icon_view;
+ GtkWidget *tree_view;
+ GtkWidget *icon_frame;
+ GtkWidget *tree_frame;
+ GtkWidget *status_icon;
+ GtkWidget *status_label;
+ GtkWidget *save_all_button;
+ GtkWidget *save_one_button;
+
+ gint active_view;
+ guint expanded : 1;
+};
+
+enum {
+ PROP_0,
+ PROP_ACTIVE_VIEW,
+ PROP_DRAGGING,
+ PROP_EDITABLE,
+ PROP_EXPANDED,
+ PROP_STORE
+};
+
+/* Forward Declarations */
+static void e_attachment_bar_interface_init
+ (EAttachmentViewInterface *interface);
+
+G_DEFINE_TYPE_WITH_CODE (
+ EAttachmentBar,
+ e_attachment_bar,
+ GTK_TYPE_VBOX,
+ G_IMPLEMENT_INTERFACE (
+ E_TYPE_ATTACHMENT_VIEW,
+ e_attachment_bar_interface_init))
+
+static void
+attachment_bar_update_status (EAttachmentBar *bar)
+{
+ EAttachmentStore *store;
+ GtkActivatable *activatable;
+ GtkAction *action;
+ GtkLabel *label;
+ gint num_attachments;
+ guint64 total_size;
+ gchar *display_size;
+ gchar *markup;
+
+ store = E_ATTACHMENT_STORE (bar->priv->model);
+ label = GTK_LABEL (bar->priv->status_label);
+
+ num_attachments = e_attachment_store_get_num_attachments (store);
+ total_size = e_attachment_store_get_total_size (store);
+ display_size = g_format_size (total_size);
+
+ if (total_size > 0)
+ markup = g_strdup_printf (
+ "<b>%d</b> %s (%s)", num_attachments, ngettext (
+ "Attachment", "Attachments", num_attachments),
+ display_size);
+ else
+ markup = g_strdup_printf (
+ "<b>%d</b> %s", num_attachments, ngettext (
+ "Attachment", "Attachments", num_attachments));
+ gtk_label_set_markup (label, markup);
+ g_free (markup);
+
+ activatable = GTK_ACTIVATABLE (bar->priv->save_all_button);
+ action = gtk_activatable_get_related_action (activatable);
+ gtk_action_set_visible (action, (num_attachments > 1));
+
+ activatable = GTK_ACTIVATABLE (bar->priv->save_one_button);
+ action = gtk_activatable_get_related_action (activatable);
+ gtk_action_set_visible (action, (num_attachments == 1));
+
+ g_free (display_size);
+}
+
+static void
+attachment_bar_set_store (EAttachmentBar *bar,
+ EAttachmentStore *store)
+{
+ g_return_if_fail (E_IS_ATTACHMENT_STORE (store));
+
+ bar->priv->model = g_object_ref (store);
+
+ gtk_icon_view_set_model (
+ GTK_ICON_VIEW (bar->priv->icon_view),
+ bar->priv->model);
+ gtk_tree_view_set_model (
+ GTK_TREE_VIEW (bar->priv->tree_view),
+ bar->priv->model);
+
+ g_signal_connect_object (
+ bar->priv->model, "notify::num-attachments",
+ G_CALLBACK (attachment_bar_update_status), bar,
+ G_CONNECT_SWAPPED);
+
+ g_signal_connect_object (
+ bar->priv->model, "notify::total-size",
+ G_CALLBACK (attachment_bar_update_status), bar,
+ G_CONNECT_SWAPPED);
+
+ /* Initialize */
+ attachment_bar_update_status (bar);
+}
+
+static void
+attachment_bar_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_ACTIVE_VIEW:
+ e_attachment_bar_set_active_view (
+ E_ATTACHMENT_BAR (object),
+ g_value_get_int (value));
+ return;
+
+ case PROP_DRAGGING:
+ e_attachment_view_set_dragging (
+ E_ATTACHMENT_VIEW (object),
+ g_value_get_boolean (value));
+ return;
+
+ case PROP_EDITABLE:
+ e_attachment_view_set_editable (
+ E_ATTACHMENT_VIEW (object),
+ g_value_get_boolean (value));
+ return;
+
+ case PROP_EXPANDED:
+ e_attachment_bar_set_expanded (
+ E_ATTACHMENT_BAR (object),
+ g_value_get_boolean (value));
+ return;
+ case PROP_STORE:
+ attachment_bar_set_store (
+ E_ATTACHMENT_BAR (object),
+ g_value_get_object (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+attachment_bar_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_ACTIVE_VIEW:
+ g_value_set_int (
+ value,
+ e_attachment_bar_get_active_view (
+ E_ATTACHMENT_BAR (object)));
+ return;
+
+ case PROP_DRAGGING:
+ g_value_set_boolean (
+ value,
+ e_attachment_view_get_dragging (
+ E_ATTACHMENT_VIEW (object)));
+ return;
+
+ case PROP_EDITABLE:
+ g_value_set_boolean (
+ value,
+ e_attachment_view_get_editable (
+ E_ATTACHMENT_VIEW (object)));
+ return;
+
+ case PROP_EXPANDED:
+ g_value_set_boolean (
+ value,
+ e_attachment_bar_get_expanded (
+ E_ATTACHMENT_BAR (object)));
+ return;
+ case PROP_STORE:
+ g_value_set_object (
+ value,
+ e_attachment_bar_get_store (
+ E_ATTACHMENT_BAR (object)));
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+attachment_bar_dispose (GObject *object)
+{
+ EAttachmentBarPrivate *priv;
+
+ priv = E_ATTACHMENT_BAR_GET_PRIVATE (object);
+
+ if (priv->model != NULL) {
+ g_object_unref (priv->model);
+ priv->model = NULL;
+ }
+
+ if (priv->vbox != NULL) {
+ g_object_unref (priv->vbox);
+ priv->vbox = NULL;
+ }
+
+ if (priv->expander != NULL) {
+ g_object_unref (priv->expander);
+ priv->expander = NULL;
+ }
+
+ if (priv->combo_box != NULL) {
+ g_object_unref (priv->combo_box);
+ priv->combo_box = NULL;
+ }
+
+ if (priv->icon_view != NULL) {
+ g_object_unref (priv->icon_view);
+ priv->icon_view = NULL;
+ }
+
+ if (priv->tree_view != NULL) {
+ g_object_unref (priv->tree_view);
+ priv->tree_view = NULL;
+ }
+
+ if (priv->icon_frame != NULL) {
+ g_object_unref (priv->icon_frame);
+ priv->icon_frame = NULL;
+ }
+
+ if (priv->tree_frame != NULL) {
+ g_object_unref (priv->tree_frame);
+ priv->tree_frame = NULL;
+ }
+
+ if (priv->status_icon != NULL) {
+ g_object_unref (priv->status_icon);
+ priv->status_icon = NULL;
+ }
+
+ if (priv->status_label != NULL) {
+ g_object_unref (priv->status_label);
+ priv->status_label = NULL;
+ }
+
+ if (priv->save_all_button != NULL) {
+ g_object_unref (priv->save_all_button);
+ priv->save_all_button = NULL;
+ }
+
+ if (priv->save_one_button != NULL) {
+ g_object_unref (priv->save_one_button);
+ priv->save_one_button = NULL;
+ }
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (e_attachment_bar_parent_class)->dispose (object);
+}
+
+static void
+attachment_bar_constructed (GObject *object)
+{
+ EAttachmentBarPrivate *priv;
+ GSettings *settings;
+
+ priv = E_ATTACHMENT_BAR_GET_PRIVATE (object);
+
+ /* Set up property-to-property bindings. */
+
+ g_object_bind_property (
+ object, "active-view",
+ priv->combo_box, "active",
+ G_BINDING_BIDIRECTIONAL |
+ G_BINDING_SYNC_CREATE);
+
+ g_object_bind_property (
+ object, "dragging",
+ priv->icon_view, "dragging",
+ G_BINDING_BIDIRECTIONAL |
+ G_BINDING_SYNC_CREATE);
+
+ g_object_bind_property (
+ object, "dragging",
+ priv->tree_view, "dragging",
+ G_BINDING_BIDIRECTIONAL |
+ G_BINDING_SYNC_CREATE);
+
+ g_object_bind_property (
+ object, "editable",
+ priv->icon_view, "editable",
+ G_BINDING_BIDIRECTIONAL |
+ G_BINDING_SYNC_CREATE);
+
+ g_object_bind_property (
+ object, "editable",
+ priv->tree_view, "editable",
+ G_BINDING_BIDIRECTIONAL |
+ G_BINDING_SYNC_CREATE);
+
+ g_object_bind_property (
+ object, "expanded",
+ priv->expander, "expanded",
+ G_BINDING_BIDIRECTIONAL |
+ G_BINDING_SYNC_CREATE);
+
+ g_object_bind_property (
+ object, "expanded",
+ priv->combo_box, "visible",
+ G_BINDING_BIDIRECTIONAL |
+ G_BINDING_SYNC_CREATE);
+
+ g_object_bind_property (
+ object, "expanded",
+ priv->vbox, "visible",
+ G_BINDING_BIDIRECTIONAL |
+ G_BINDING_SYNC_CREATE);
+
+ /* Set up property-to-GSettings bindings. */
+ settings = g_settings_new ("org.gnome.evolution.shell");
+ g_settings_bind (
+ settings, "attachment-view",
+ object, "active-view",
+ G_SETTINGS_BIND_DEFAULT);
+ g_object_unref (settings);
+
+ /* Chain up to parent's constructed() method. */
+ G_OBJECT_CLASS (e_attachment_bar_parent_class)->constructed (object);
+}
+
+static EAttachmentViewPrivate *
+attachment_bar_get_private (EAttachmentView *view)
+{
+ EAttachmentBar *bar;
+
+ bar = E_ATTACHMENT_BAR (view);
+ view = E_ATTACHMENT_VIEW (bar->priv->icon_view);
+
+ return e_attachment_view_get_private (view);
+}
+
+static GtkTreePath *
+attachment_bar_get_path_at_pos (EAttachmentView *view,
+ gint x,
+ gint y)
+{
+ EAttachmentBar *bar;
+
+ bar = E_ATTACHMENT_BAR (view);
+ view = E_ATTACHMENT_VIEW (bar->priv->icon_view);
+
+ return e_attachment_view_get_path_at_pos (view, x, y);
+}
+
+static EAttachmentStore *
+attachment_bar_get_store (EAttachmentView *view)
+{
+ return e_attachment_bar_get_store (E_ATTACHMENT_BAR (view));
+}
+
+static GList *
+attachment_bar_get_selected_paths (EAttachmentView *view)
+{
+ EAttachmentBar *bar;
+
+ bar = E_ATTACHMENT_BAR (view);
+ view = E_ATTACHMENT_VIEW (bar->priv->icon_view);
+
+ return e_attachment_view_get_selected_paths (view);
+}
+
+static gboolean
+attachment_bar_path_is_selected (EAttachmentView *view,
+ GtkTreePath *path)
+{
+ EAttachmentBar *bar;
+
+ bar = E_ATTACHMENT_BAR (view);
+ view = E_ATTACHMENT_VIEW (bar->priv->icon_view);
+
+ return e_attachment_view_path_is_selected (view, path);
+}
+
+static void
+attachment_bar_select_path (EAttachmentView *view,
+ GtkTreePath *path)
+{
+ EAttachmentBar *bar;
+
+ bar = E_ATTACHMENT_BAR (view);
+ view = E_ATTACHMENT_VIEW (bar->priv->icon_view);
+
+ e_attachment_view_select_path (view, path);
+}
+
+static void
+attachment_bar_unselect_path (EAttachmentView *view,
+ GtkTreePath *path)
+{
+ EAttachmentBar *bar;
+
+ bar = E_ATTACHMENT_BAR (view);
+ view = E_ATTACHMENT_VIEW (bar->priv->icon_view);
+
+ e_attachment_view_unselect_path (view, path);
+}
+
+static void
+attachment_bar_select_all (EAttachmentView *view)
+{
+ EAttachmentBar *bar;
+
+ bar = E_ATTACHMENT_BAR (view);
+ view = E_ATTACHMENT_VIEW (bar->priv->icon_view);
+
+ e_attachment_view_select_all (view);
+}
+
+static void
+attachment_bar_unselect_all (EAttachmentView *view)
+{
+ EAttachmentBar *bar;
+
+ bar = E_ATTACHMENT_BAR (view);
+ view = E_ATTACHMENT_VIEW (bar->priv->icon_view);
+
+ e_attachment_view_unselect_all (view);
+}
+
+static void
+attachment_bar_update_actions (EAttachmentView *view)
+{
+ EAttachmentBar *bar;
+
+ bar = E_ATTACHMENT_BAR (view);
+ view = E_ATTACHMENT_VIEW (bar->priv->icon_view);
+
+ e_attachment_view_update_actions (view);
+}
+
+static void
+e_attachment_bar_class_init (EAttachmentBarClass *class)
+{
+ GObjectClass *object_class;
+
+ g_type_class_add_private (class, sizeof (EAttachmentBarPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = attachment_bar_set_property;
+ object_class->get_property = attachment_bar_get_property;
+ object_class->dispose = attachment_bar_dispose;
+ object_class->constructed = attachment_bar_constructed;
+
+ g_object_class_install_property (
+ object_class,
+ PROP_ACTIVE_VIEW,
+ g_param_spec_int (
+ "active-view",
+ "Active View",
+ NULL,
+ 0,
+ NUM_VIEWS,
+ 0,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_EXPANDED,
+ g_param_spec_boolean (
+ "expanded",
+ "Expanded",
+ NULL,
+ FALSE,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_STORE,
+ g_param_spec_object (
+ "store",
+ "Attachment Store",
+ NULL,
+ E_TYPE_ATTACHMENT_STORE,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+
+ g_object_class_override_property (
+ object_class, PROP_DRAGGING, "dragging");
+
+ g_object_class_override_property (
+ object_class, PROP_EDITABLE, "editable");
+}
+
+static void
+e_attachment_bar_interface_init (EAttachmentViewInterface *interface)
+{
+ interface->get_private = attachment_bar_get_private;
+ interface->get_store = attachment_bar_get_store;
+ interface->get_path_at_pos = attachment_bar_get_path_at_pos;
+ interface->get_selected_paths = attachment_bar_get_selected_paths;
+ interface->path_is_selected = attachment_bar_path_is_selected;
+ interface->select_path = attachment_bar_select_path;
+ interface->unselect_path = attachment_bar_unselect_path;
+ interface->select_all = attachment_bar_select_all;
+ interface->unselect_all = attachment_bar_unselect_all;
+ interface->update_actions = attachment_bar_update_actions;
+}
+
+static void
+e_attachment_bar_init (EAttachmentBar *bar)
+{
+ EAttachmentView *view;
+ GtkSizeGroup *size_group;
+ GtkWidget *container;
+ GtkWidget *widget;
+ GtkAction *action;
+
+ bar->priv = E_ATTACHMENT_BAR_GET_PRIVATE (bar);
+
+ gtk_box_set_spacing (GTK_BOX (bar), 6);
+
+ /* Keep the expander label and save button the same height. */
+ size_group = gtk_size_group_new (GTK_SIZE_GROUP_VERTICAL);
+
+ /* Construct the Attachment Views */
+
+ container = GTK_WIDGET (bar);
+
+ widget = gtk_vbox_new (FALSE, 0);
+ gtk_box_pack_end (GTK_BOX (container), widget, FALSE, FALSE, 0);
+ bar->priv->vbox = g_object_ref (widget);
+ gtk_widget_show (widget);
+
+ container = bar->priv->vbox;
+
+ widget = gtk_frame_new (NULL);
+ gtk_frame_set_shadow_type (GTK_FRAME (widget), GTK_SHADOW_IN);
+ gtk_box_pack_start (GTK_BOX (container), widget, TRUE, TRUE, 0);
+ bar->priv->icon_frame = g_object_ref (widget);
+ gtk_widget_show (widget);
+
+ container = widget;
+
+ widget = e_attachment_icon_view_new ();
+ gtk_widget_set_can_focus (widget, TRUE);
+ gtk_icon_view_set_model (GTK_ICON_VIEW (widget), bar->priv->model);
+ gtk_container_add (GTK_CONTAINER (container), widget);
+ bar->priv->icon_view = g_object_ref (widget);
+ gtk_widget_show (widget);
+
+ container = bar->priv->vbox;
+
+ widget = gtk_frame_new (NULL);
+ gtk_frame_set_shadow_type (GTK_FRAME (widget), GTK_SHADOW_IN);
+ gtk_box_pack_start (GTK_BOX (container), widget, TRUE, TRUE, 0);
+ bar->priv->tree_frame = g_object_ref (widget);
+ gtk_widget_hide (widget);
+
+ container = widget;
+
+ widget = e_attachment_tree_view_new ();
+ gtk_widget_set_can_focus (widget, TRUE);
+ gtk_tree_view_set_model (GTK_TREE_VIEW (widget), bar->priv->model);
+ gtk_container_add (GTK_CONTAINER (container), widget);
+ bar->priv->tree_view = g_object_ref (widget);
+ gtk_widget_show (widget);
+
+ /* Construct the Controls */
+
+ container = GTK_WIDGET (bar);
+
+ widget = gtk_hbox_new (FALSE, 12);
+ gtk_box_pack_end (GTK_BOX (container), widget, FALSE, FALSE, 0);
+ gtk_widget_show (widget);
+
+ container = widget;
+
+ widget = gtk_expander_new (NULL);
+ gtk_expander_set_spacing (GTK_EXPANDER (widget), 0);
+ gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+ bar->priv->expander = g_object_ref (widget);
+ gtk_widget_show (widget);
+
+ /* The "Save All" button proxies the "save-all" action from
+ * one of the two attachment views. Doesn't matter which. */
+ widget = gtk_button_new ();
+ view = E_ATTACHMENT_VIEW (bar->priv->icon_view);
+ action = e_attachment_view_get_action (view, "save-all");
+ gtk_button_set_image (GTK_BUTTON (widget), gtk_image_new ());
+ gtk_activatable_set_related_action (GTK_ACTIVATABLE (widget), action);
+ gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+ bar->priv->save_all_button = g_object_ref (widget);
+ gtk_widget_show (widget);
+
+ /* Same deal with the "Save" button. */
+ widget = gtk_button_new ();
+ view = E_ATTACHMENT_VIEW (bar->priv->icon_view);
+ action = e_attachment_view_get_action (view, "save-one");
+ gtk_button_set_image (GTK_BUTTON (widget), gtk_image_new ());
+ gtk_activatable_set_related_action (GTK_ACTIVATABLE (widget), action);
+ gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+ bar->priv->save_one_button = g_object_ref (widget);
+ gtk_widget_show (widget);
+
+ widget = gtk_alignment_new (1.0, 0.5, 0.0, 0.0);
+ gtk_box_pack_start (GTK_BOX (container), widget, TRUE, TRUE, 0);
+ gtk_widget_show (widget);
+
+ container = widget;
+
+ widget = gtk_combo_box_text_new ();
+ gtk_size_group_add_widget (size_group, widget);
+ gtk_combo_box_text_append_text (
+ GTK_COMBO_BOX_TEXT (widget), _("Icon View"));
+ gtk_combo_box_text_append_text (
+ GTK_COMBO_BOX_TEXT (widget), _("List View"));
+ gtk_container_add (GTK_CONTAINER (container), widget);
+ bar->priv->combo_box = g_object_ref (widget);
+ gtk_widget_show (widget);
+
+ container = bar->priv->expander;
+
+ widget = gtk_hbox_new (FALSE, 6);
+ gtk_size_group_add_widget (size_group, widget);
+ gtk_expander_set_label_widget (GTK_EXPANDER (container), widget);
+ gtk_widget_show (widget);
+
+ container = widget;
+
+ widget = gtk_image_new_from_icon_name (
+ "mail-attachment", GTK_ICON_SIZE_MENU);
+ gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+ bar->priv->status_icon = g_object_ref (widget);
+ gtk_widget_show (widget);
+
+ widget = gtk_label_new (NULL);
+ gtk_label_set_use_markup (GTK_LABEL (widget), TRUE);
+ gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+ bar->priv->status_label = g_object_ref (widget);
+ gtk_widget_show (widget);
+
+ g_object_unref (size_group);
+}
+
+GtkWidget *
+e_attachment_bar_new (EAttachmentStore *store)
+{
+ g_return_val_if_fail (E_IS_ATTACHMENT_STORE (store), NULL);
+
+ return g_object_new (
+ E_TYPE_ATTACHMENT_BAR,
+ "editable", FALSE,
+ "store", store, NULL);
+}
+
+gint
+e_attachment_bar_get_active_view (EAttachmentBar *bar)
+{
+ g_return_val_if_fail (E_IS_ATTACHMENT_BAR (bar), 0);
+
+ return bar->priv->active_view;
+}
+
+void
+e_attachment_bar_set_active_view (EAttachmentBar *bar,
+ gint active_view)
+{
+ EAttachmentView *source;
+ EAttachmentView *target;
+
+ g_return_if_fail (E_IS_ATTACHMENT_BAR (bar));
+ g_return_if_fail (active_view >= 0 && active_view < NUM_VIEWS);
+
+ if (active_view == bar->priv->active_view)
+ return;
+
+ bar->priv->active_view = active_view;
+
+ if (active_view == 0) {
+ gtk_widget_show (bar->priv->icon_frame);
+ gtk_widget_hide (bar->priv->tree_frame);
+ } else {
+ gtk_widget_hide (bar->priv->icon_frame);
+ gtk_widget_show (bar->priv->tree_frame);
+ }
+
+ /* Synchronize the item selection of the view we're
+ * switching TO with the view we're switching FROM. */
+ if (active_view == 0) {
+ /* from tree view to icon view */
+ source = E_ATTACHMENT_VIEW (bar->priv->tree_view);
+ target = E_ATTACHMENT_VIEW (bar->priv->icon_view);
+ } else {
+ /* from icon view to tree view */
+ source = E_ATTACHMENT_VIEW (bar->priv->icon_view);
+ target = E_ATTACHMENT_VIEW (bar->priv->tree_view);
+ }
+
+ e_attachment_view_sync_selection (source, target);
+
+ g_object_notify (G_OBJECT (bar), "active-view");
+}
+
+gboolean
+e_attachment_bar_get_expanded (EAttachmentBar *bar)
+{
+ g_return_val_if_fail (E_IS_ATTACHMENT_BAR (bar), FALSE);
+
+ return bar->priv->expanded;
+}
+
+void
+e_attachment_bar_set_expanded (EAttachmentBar *bar,
+ gboolean expanded)
+{
+ g_return_if_fail (E_IS_ATTACHMENT_BAR (bar));
+
+ if (bar->priv->expanded == expanded)
+ return;
+
+ bar->priv->expanded = expanded;
+
+ g_object_notify (G_OBJECT (bar), "expanded");
+}
+
+EAttachmentStore *
+e_attachment_bar_get_store (EAttachmentBar *bar)
+{
+ g_return_val_if_fail (E_IS_ATTACHMENT_BAR (bar), NULL);
+
+ return E_ATTACHMENT_STORE (bar->priv->model);
+}
diff --git a/e-util/e-attachment-bar.h b/e-util/e-attachment-bar.h
new file mode 100644
index 0000000000..9f35ae2aba
--- /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/e-util/e-attachment-button.c b/e-util/e-attachment-button.c
new file mode 100644
index 0000000000..a2057e3354
--- /dev/null
+++ b/e-util/e-attachment-button.c
@@ -0,0 +1,868 @@
+/*
+ * e-attachment-button.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)
+ *
+ */
+
+/* Much of the popup menu logic here was ripped from GtkMenuToolButton. */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-attachment-button.h"
+
+#define E_ATTACHMENT_BUTTON_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_ATTACHMENT_BUTTON, EAttachmentButtonPrivate))
+
+struct _EAttachmentButtonPrivate {
+
+ EAttachmentView *view;
+ EAttachment *attachment;
+ gulong reference_handler_id;
+
+ GBinding *can_show_binding;
+ GBinding *shown_binding;
+
+ GtkWidget *expand_button;
+ GtkWidget *toggle_button;
+ GtkWidget *cell_view;
+ GtkWidget *popup_menu;
+
+ guint expandable : 1;
+ guint expanded : 1;
+};
+
+enum {
+ PROP_0,
+ PROP_ATTACHMENT,
+ PROP_EXPANDABLE,
+ PROP_EXPANDED,
+ PROP_VIEW
+};
+
+G_DEFINE_TYPE (
+ EAttachmentButton,
+ e_attachment_button,
+ GTK_TYPE_HBOX)
+
+static void
+attachment_button_menu_deactivate_cb (EAttachmentButton *button)
+{
+ EAttachmentView *view;
+ GtkActionGroup *action_group;
+ GtkToggleButton *toggle_button;
+
+ view = e_attachment_button_get_view (button);
+ action_group = e_attachment_view_get_action_group (view, "inline");
+ toggle_button = GTK_TOGGLE_BUTTON (button->priv->toggle_button);
+
+ gtk_toggle_button_set_active (toggle_button, FALSE);
+
+ gtk_action_group_set_visible (action_group, FALSE);
+}
+
+static void
+attachment_button_menu_position (GtkMenu *menu,
+ gint *x,
+ gint *y,
+ gboolean *push_in,
+ EAttachmentButton *button)
+{
+ GtkRequisition menu_requisition;
+ GtkTextDirection direction;
+ GtkAllocation allocation;
+ GdkRectangle monitor;
+ GdkScreen *screen;
+ GdkWindow *window;
+ GtkWidget *widget;
+ GtkWidget *toggle_button;
+ gint monitor_num;
+
+ widget = GTK_WIDGET (button);
+ toggle_button = button->priv->toggle_button;
+ gtk_widget_get_preferred_size (GTK_WIDGET (menu), &menu_requisition, NULL);
+
+ window = gtk_widget_get_parent_window (widget);
+ screen = gtk_widget_get_screen (GTK_WIDGET (menu));
+ monitor_num = gdk_screen_get_monitor_at_window (screen, window);
+ if (monitor_num < 0)
+ monitor_num = 0;
+ gdk_screen_get_monitor_geometry (screen, monitor_num, &monitor);
+
+ gtk_widget_get_allocation (widget, &allocation);
+
+ gdk_window_get_origin (window, x, y);
+ *x += allocation.x;
+ *y += allocation.y;
+
+ direction = gtk_widget_get_direction (widget);
+ if (direction == GTK_TEXT_DIR_LTR)
+ *x += MAX (allocation.width - menu_requisition.width, 0);
+ else if (menu_requisition.width > allocation.width)
+ *x -= menu_requisition.width - allocation.width;
+
+ gtk_widget_get_allocation (toggle_button, &allocation);
+
+ if ((*y + allocation.height +
+ menu_requisition.height) <= monitor.y + monitor.height)
+ *y += allocation.height;
+ else if ((*y - menu_requisition.height) >= monitor.y)
+ *y -= menu_requisition.height;
+ else if (monitor.y + monitor.height -
+ (*y + allocation.height) > *y)
+ *y += allocation.height;
+ else
+ *y -= menu_requisition.height;
+
+ *push_in = FALSE;
+}
+
+static void
+attachment_button_select_path (EAttachmentButton *button)
+{
+ EAttachmentView *view;
+ EAttachment *attachment;
+ GtkTreeRowReference *reference;
+ GtkTreePath *path;
+
+ attachment = e_attachment_button_get_attachment (button);
+ g_return_if_fail (E_IS_ATTACHMENT (attachment));
+
+ reference = e_attachment_get_reference (attachment);
+ g_return_if_fail (gtk_tree_row_reference_valid (reference));
+
+ view = e_attachment_button_get_view (button);
+ path = gtk_tree_row_reference_get_path (reference);
+
+ e_attachment_view_unselect_all (view);
+ e_attachment_view_select_path (view, path);
+
+ gtk_tree_path_free (path);
+}
+
+static void
+attachment_button_show_popup_menu (EAttachmentButton *button,
+ GdkEventButton *event)
+{
+ EAttachmentView *view;
+ GtkActionGroup *action_group;
+ GtkToggleButton *toggle_button;
+
+ view = e_attachment_button_get_view (button);
+ action_group = e_attachment_view_get_action_group (view, "inline");
+ toggle_button = GTK_TOGGLE_BUTTON (button->priv->toggle_button);
+
+ attachment_button_select_path (button);
+ gtk_toggle_button_set_active (toggle_button, TRUE);
+
+ e_attachment_view_show_popup_menu (
+ view, event, (GtkMenuPositionFunc)
+ attachment_button_menu_position, button);
+
+ gtk_action_group_set_visible (action_group, TRUE);
+}
+
+static void
+attachment_button_update_cell_view (EAttachmentButton *button)
+{
+ GtkCellView *cell_view;
+ EAttachment *attachment;
+ GtkTreeRowReference *reference;
+ GtkTreeModel *model = NULL;
+ GtkTreePath *path = NULL;
+
+ cell_view = GTK_CELL_VIEW (button->priv->cell_view);
+
+ attachment = e_attachment_button_get_attachment (button);
+ if (attachment == NULL)
+ goto exit;
+
+ reference = e_attachment_get_reference (attachment);
+ if (reference == NULL)
+ goto exit;
+
+ model = gtk_tree_row_reference_get_model (reference);
+ path = gtk_tree_row_reference_get_path (reference);
+
+exit:
+ gtk_cell_view_set_model (cell_view, model);
+ gtk_cell_view_set_displayed_row (cell_view, path);
+
+ if (path != NULL)
+ gtk_tree_path_free (path);
+}
+
+static void
+attachment_button_update_pixbufs (EAttachmentButton *button)
+{
+ GtkCellLayout *cell_layout;
+ GtkCellRenderer *renderer;
+ GdkPixbuf *pixbuf_expander_open;
+ GdkPixbuf *pixbuf_expander_closed;
+ GList *list;
+
+ /* Grab the first cell renderer. */
+ cell_layout = GTK_CELL_LAYOUT (button->priv->cell_view);
+ list = gtk_cell_layout_get_cells (cell_layout);
+ renderer = GTK_CELL_RENDERER (list->data);
+ g_list_free (list);
+
+ pixbuf_expander_open = gtk_widget_render_icon (
+ GTK_WIDGET (button), GTK_STOCK_GO_DOWN,
+ GTK_ICON_SIZE_BUTTON, NULL);
+
+ pixbuf_expander_closed = gtk_widget_render_icon (
+ GTK_WIDGET (button), GTK_STOCK_GO_FORWARD,
+ GTK_ICON_SIZE_BUTTON, NULL);
+
+ g_object_set (
+ renderer,
+ "pixbuf-expander-open", pixbuf_expander_open,
+ "pixbuf-expander-closed", pixbuf_expander_closed,
+ NULL);
+
+ g_object_unref (pixbuf_expander_open);
+ g_object_unref (pixbuf_expander_closed);
+}
+
+static void
+attachment_button_expand_clicked_cb (EAttachmentButton *button)
+{
+ gboolean expanded;
+
+ expanded = e_attachment_button_get_expanded (button);
+ e_attachment_button_set_expanded (button, !expanded);
+}
+
+static void
+attachment_button_expand_drag_begin_cb (EAttachmentButton *button,
+ GdkDragContext *context)
+{
+ EAttachmentView *view;
+
+ view = e_attachment_button_get_view (button);
+
+ attachment_button_select_path (button);
+ e_attachment_view_drag_begin (view, context);
+}
+
+static void
+attachment_button_expand_drag_data_get_cb (EAttachmentButton *button,
+ GdkDragContext *context,
+ GtkSelectionData *selection,
+ guint info,
+ guint time)
+{
+ EAttachmentView *view;
+
+ if (button->priv->attachment) {
+ gchar *mime_type;
+
+ mime_type = e_attachment_get_mime_type (
+ button->priv->attachment);
+
+ if (mime_type) {
+ gboolean processed = FALSE;
+ GdkAtom atom;
+ gchar *atom_name;
+
+ atom = gtk_selection_data_get_target (selection);
+ atom_name = gdk_atom_name (atom);
+
+ if (g_strcmp0 (atom_name, mime_type) == 0) {
+ CamelMimePart *mime_part;
+
+ mime_part = e_attachment_get_mime_part (
+ button->priv->attachment);
+
+ if (CAMEL_IS_MIME_PART (mime_part)) {
+ CamelDataWrapper *wrapper;
+ CamelStream *stream;
+ GByteArray *buffer;
+
+ 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);
+
+ gtk_selection_data_set (
+ selection, atom, 8,
+ buffer->data, buffer->len);
+ processed = TRUE;
+
+ g_byte_array_free (buffer, TRUE);
+ }
+ }
+
+ g_free (atom_name);
+ g_free (mime_type);
+
+ if (processed)
+ return;
+ }
+ }
+
+ view = e_attachment_button_get_view (button);
+
+ e_attachment_view_drag_data_get (
+ view, context, selection, info, time);
+}
+
+static void
+attachment_button_expand_drag_end_cb (EAttachmentButton *button,
+ GdkDragContext *context)
+{
+ EAttachmentView *view;
+
+ view = e_attachment_button_get_view (button);
+
+ e_attachment_view_drag_end (view, context);
+}
+
+static gboolean
+attachment_button_toggle_button_press_event_cb (EAttachmentButton *button,
+ GdkEventButton *event)
+{
+ if (event->button == 1) {
+ attachment_button_show_popup_menu (button, event);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void
+attachment_button_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_ATTACHMENT:
+ e_attachment_button_set_attachment (
+ E_ATTACHMENT_BUTTON (object),
+ g_value_get_object (value));
+ return;
+
+ case PROP_EXPANDABLE:
+ e_attachment_button_set_expandable (
+ E_ATTACHMENT_BUTTON (object),
+ g_value_get_boolean (value));
+ return;
+
+ case PROP_EXPANDED:
+ e_attachment_button_set_expanded (
+ E_ATTACHMENT_BUTTON (object),
+ g_value_get_boolean (value));
+ return;
+
+ case PROP_VIEW:
+ e_attachment_button_set_view (
+ E_ATTACHMENT_BUTTON (object),
+ g_value_get_object (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+attachment_button_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_ATTACHMENT:
+ g_value_set_object (
+ value,
+ e_attachment_button_get_attachment (
+ E_ATTACHMENT_BUTTON (object)));
+ return;
+
+ case PROP_EXPANDABLE:
+ g_value_set_boolean (
+ value,
+ e_attachment_button_get_expandable (
+ E_ATTACHMENT_BUTTON (object)));
+ return;
+
+ case PROP_EXPANDED:
+ g_value_set_boolean (
+ value,
+ e_attachment_button_get_expanded (
+ E_ATTACHMENT_BUTTON (object)));
+ return;
+
+ case PROP_VIEW:
+ g_value_set_object (
+ value,
+ e_attachment_button_get_view (
+ E_ATTACHMENT_BUTTON (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+attachment_button_dispose (GObject *object)
+{
+ EAttachmentButtonPrivate *priv;
+
+ priv = E_ATTACHMENT_BUTTON_GET_PRIVATE (object);
+
+ if (priv->view != NULL) {
+ g_object_unref (priv->view);
+ priv->view = NULL;
+ }
+
+ if (priv->attachment != NULL) {
+ g_signal_handler_disconnect (
+ priv->attachment,
+ priv->reference_handler_id);
+ g_object_unref (priv->attachment);
+ priv->attachment = NULL;
+ }
+
+ if (priv->expand_button != NULL) {
+ g_object_unref (priv->expand_button);
+ priv->expand_button = NULL;
+ }
+
+ if (priv->toggle_button != NULL) {
+ g_object_unref (priv->toggle_button);
+ priv->toggle_button = NULL;
+ }
+
+ if (priv->cell_view != NULL) {
+ g_object_unref (priv->cell_view);
+ priv->cell_view = NULL;
+ }
+
+ if (priv->popup_menu != NULL) {
+ g_signal_handlers_disconnect_matched (
+ priv->popup_menu, G_SIGNAL_MATCH_DATA,
+ 0, 0, NULL, NULL, object);
+ g_object_unref (priv->popup_menu);
+ priv->popup_menu = NULL;
+ }
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (e_attachment_button_parent_class)->dispose (object);
+}
+
+static void
+attachment_button_style_set (GtkWidget *widget,
+ GtkStyle *previous_style)
+{
+ EAttachmentButton *button;
+
+ /* Chain up to parent's style_set() method. */
+ GTK_WIDGET_CLASS (e_attachment_button_parent_class)->
+ style_set (widget, previous_style);
+
+ button = E_ATTACHMENT_BUTTON (widget);
+ attachment_button_update_pixbufs (button);
+}
+
+static void
+e_attachment_button_class_init (EAttachmentButtonClass *class)
+{
+ GObjectClass *object_class;
+ GtkWidgetClass *widget_class;
+
+ g_type_class_add_private (class, sizeof (EAttachmentButtonPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = attachment_button_set_property;
+ object_class->get_property = attachment_button_get_property;
+ object_class->dispose = attachment_button_dispose;
+
+ widget_class = GTK_WIDGET_CLASS (class);
+ widget_class->style_set = attachment_button_style_set;
+
+ g_object_class_install_property (
+ object_class,
+ PROP_ATTACHMENT,
+ g_param_spec_object (
+ "attachment",
+ "Attachment",
+ NULL,
+ E_TYPE_ATTACHMENT,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_EXPANDABLE,
+ g_param_spec_boolean (
+ "expandable",
+ "Expandable",
+ NULL,
+ TRUE,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_EXPANDED,
+ g_param_spec_boolean (
+ "expanded",
+ "Expanded",
+ NULL,
+ FALSE,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_VIEW,
+ g_param_spec_object (
+ "view",
+ "View",
+ NULL,
+ E_TYPE_ATTACHMENT_VIEW,
+ G_PARAM_READWRITE));
+}
+
+static void
+e_attachment_button_init (EAttachmentButton *button)
+{
+ GtkCellRenderer *renderer;
+ GtkCellLayout *cell_layout;
+ GtkTargetEntry *targets;
+ GtkTargetList *list;
+ GtkWidget *container;
+ GtkWidget *widget;
+ GtkStyleContext *context;
+ gint n_targets;
+
+ button->priv = E_ATTACHMENT_BUTTON_GET_PRIVATE (button);
+
+ /* Configure Widgets */
+
+ container = GTK_WIDGET (button);
+ context = gtk_widget_get_style_context (container);
+ gtk_style_context_add_class (context, "linked");
+
+ widget = gtk_button_new ();
+ gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+ button->priv->expand_button = g_object_ref (widget);
+ gtk_widget_show (widget);
+
+ g_object_bind_property (
+ button, "expandable",
+ widget, "sensitive",
+ G_BINDING_BIDIRECTIONAL |
+ G_BINDING_SYNC_CREATE);
+
+ widget = gtk_toggle_button_new ();
+ gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+ button->priv->toggle_button = g_object_ref (widget);
+ gtk_widget_show (widget);
+
+ container = button->priv->expand_button;
+
+ widget = gtk_cell_view_new ();
+ gtk_container_add (GTK_CONTAINER (container), widget);
+ button->priv->cell_view = g_object_ref (widget);
+ gtk_widget_show (widget);
+
+ container = button->priv->toggle_button;
+
+ widget = gtk_arrow_new (GTK_ARROW_DOWN, GTK_SHADOW_NONE);
+ gtk_container_add (GTK_CONTAINER (container), widget);
+ gtk_widget_show (widget);
+
+ /* Configure Renderers */
+
+ cell_layout = GTK_CELL_LAYOUT (button->priv->cell_view);
+
+ renderer = gtk_cell_renderer_pixbuf_new ();
+ g_object_set (renderer, "is-expander", TRUE, NULL);
+ gtk_cell_layout_pack_start (cell_layout, renderer, FALSE);
+
+ g_object_bind_property (
+ button, "expanded",
+ renderer, "is-expanded",
+ G_BINDING_BIDIRECTIONAL |
+ G_BINDING_SYNC_CREATE);
+
+ renderer = gtk_cell_renderer_pixbuf_new ();
+ g_object_set (renderer, "stock-size", GTK_ICON_SIZE_BUTTON, NULL);
+ gtk_cell_layout_pack_start (cell_layout, renderer, FALSE);
+
+ gtk_cell_layout_add_attribute (
+ cell_layout, renderer, "gicon",
+ E_ATTACHMENT_STORE_COLUMN_ICON);
+
+ /* Configure Drag and Drop */
+
+ 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_drag_source_set (
+ button->priv->expand_button, GDK_BUTTON1_MASK,
+ targets, n_targets, GDK_ACTION_COPY);
+
+ gtk_drag_source_set (
+ button->priv->toggle_button, GDK_BUTTON1_MASK,
+ targets, n_targets, GDK_ACTION_COPY);
+
+ gtk_target_table_free (targets, n_targets);
+ gtk_target_list_unref (list);
+
+ /* Configure Signal Handlers */
+
+ g_signal_connect_swapped (
+ button->priv->expand_button, "clicked",
+ G_CALLBACK (attachment_button_expand_clicked_cb), button);
+
+ g_signal_connect_swapped (
+ button->priv->expand_button, "drag-begin",
+ G_CALLBACK (attachment_button_expand_drag_begin_cb),
+ button);
+
+ g_signal_connect_swapped (
+ button->priv->expand_button, "drag-data-get",
+ G_CALLBACK (attachment_button_expand_drag_data_get_cb),
+ button);
+
+ g_signal_connect_swapped (
+ button->priv->expand_button, "drag-end",
+ G_CALLBACK (attachment_button_expand_drag_end_cb),
+ button);
+
+ g_signal_connect_swapped (
+ button->priv->toggle_button, "button-press-event",
+ G_CALLBACK (attachment_button_toggle_button_press_event_cb),
+ button);
+
+ g_signal_connect_swapped (
+ button->priv->toggle_button, "drag-begin",
+ G_CALLBACK (attachment_button_expand_drag_begin_cb),
+ button);
+
+ g_signal_connect_swapped (
+ button->priv->toggle_button, "drag-data-get",
+ G_CALLBACK (attachment_button_expand_drag_data_get_cb),
+ button);
+
+ g_signal_connect_swapped (
+ button->priv->toggle_button, "drag-end",
+ G_CALLBACK (attachment_button_expand_drag_end_cb),
+ button);
+}
+
+GtkWidget *
+e_attachment_button_new ()
+{
+ return g_object_new (
+ E_TYPE_ATTACHMENT_BUTTON, NULL);
+}
+
+EAttachmentView *
+e_attachment_button_get_view (EAttachmentButton *button)
+{
+ g_return_val_if_fail (E_IS_ATTACHMENT_BUTTON (button), NULL);
+
+ return button->priv->view;
+}
+
+void
+e_attachment_button_set_view (EAttachmentButton *button,
+ EAttachmentView *view)
+{
+ GtkWidget *popup_menu;
+
+ g_return_if_fail (button->priv->view == NULL);
+
+ g_object_ref (view);
+ if (button->priv->view)
+ g_object_unref (button->priv->view);
+ button->priv->view = view;
+
+ popup_menu = e_attachment_view_get_popup_menu (view);
+
+ g_signal_connect_swapped (
+ popup_menu, "deactivate",
+ G_CALLBACK (attachment_button_menu_deactivate_cb), button);
+
+ /* Keep a reference to the popup menu so we can
+ * disconnect the signal handler in dispose(). */
+ if (button->priv->popup_menu)
+ g_object_unref (button->priv->popup_menu);
+ button->priv->popup_menu = g_object_ref (popup_menu);
+}
+
+EAttachment *
+e_attachment_button_get_attachment (EAttachmentButton *button)
+{
+ g_return_val_if_fail (E_IS_ATTACHMENT_BUTTON (button), NULL);
+
+ return button->priv->attachment;
+}
+
+void
+e_attachment_button_set_attachment (EAttachmentButton *button,
+ EAttachment *attachment)
+{
+ GtkTargetEntry *targets;
+ GtkTargetList *list;
+ gint n_targets;
+
+ g_return_if_fail (E_IS_ATTACHMENT_BUTTON (button));
+
+ if (attachment != NULL) {
+ g_return_if_fail (E_IS_ATTACHMENT (attachment));
+ g_object_ref (attachment);
+ }
+
+ if (button->priv->attachment != NULL) {
+ g_object_unref (button->priv->can_show_binding);
+ button->priv->can_show_binding = NULL;
+ g_object_unref (button->priv->shown_binding);
+ button->priv->shown_binding = NULL;
+ g_signal_handler_disconnect (
+ button->priv->attachment,
+ button->priv->reference_handler_id);
+ g_object_unref (button->priv->attachment);
+ }
+
+ button->priv->attachment = attachment;
+
+ if (attachment != NULL) {
+ GBinding *binding;
+ gulong handler_id;
+
+ binding = g_object_bind_property (
+ attachment, "can-show",
+ button, "expandable",
+ G_BINDING_BIDIRECTIONAL |
+ G_BINDING_SYNC_CREATE);
+ button->priv->can_show_binding = binding;
+
+ binding = g_object_bind_property (
+ attachment, "shown",
+ button, "expanded",
+ G_BINDING_BIDIRECTIONAL |
+ G_BINDING_SYNC_CREATE);
+ button->priv->shown_binding = binding;
+
+ handler_id = g_signal_connect_swapped (
+ attachment, "notify::reference",
+ G_CALLBACK (attachment_button_update_cell_view),
+ button);
+ button->priv->reference_handler_id = handler_id;
+
+ attachment_button_update_cell_view (button);
+ attachment_button_update_pixbufs (button);
+ }
+
+ /* update drag sources */
+ list = gtk_target_list_new (NULL, 0);
+ gtk_target_list_add_uri_targets (list, 0);
+
+ if (attachment) {
+ gchar *simple_type;
+
+ simple_type = e_attachment_get_mime_type (attachment);
+ if (simple_type) {
+ GtkTargetEntry attach_entry[] = { { NULL, 0, 2 } };
+
+ attach_entry[0].target = simple_type;
+
+ gtk_target_list_add_table (
+ list, attach_entry,
+ G_N_ELEMENTS (attach_entry));
+
+ g_free (simple_type);
+ }
+ }
+
+ targets = gtk_target_table_new_from_list (list, &n_targets);
+
+ gtk_drag_source_set (
+ button->priv->expand_button, GDK_BUTTON1_MASK,
+ targets, n_targets, GDK_ACTION_COPY);
+
+ gtk_drag_source_set (
+ button->priv->toggle_button, GDK_BUTTON1_MASK,
+ targets, n_targets, GDK_ACTION_COPY);
+
+ gtk_target_table_free (targets, n_targets);
+ gtk_target_list_unref (list);
+
+ g_object_notify (G_OBJECT (button), "attachment");
+}
+
+gboolean
+e_attachment_button_get_expandable (EAttachmentButton *button)
+{
+ g_return_val_if_fail (E_IS_ATTACHMENT_BUTTON (button), FALSE);
+
+ return button->priv->expandable;
+}
+
+void
+e_attachment_button_set_expandable (EAttachmentButton *button,
+ gboolean expandable)
+{
+ g_return_if_fail (E_IS_ATTACHMENT_BUTTON (button));
+
+ if (button->priv->expandable == expandable)
+ return;
+
+ button->priv->expandable = expandable;
+
+ if (!expandable)
+ e_attachment_button_set_expanded (button, FALSE);
+
+ g_object_notify (G_OBJECT (button), "expandable");
+}
+
+gboolean
+e_attachment_button_get_expanded (EAttachmentButton *button)
+{
+ g_return_val_if_fail (E_IS_ATTACHMENT_BUTTON (button), FALSE);
+
+ return button->priv->expanded;
+}
+
+void
+e_attachment_button_set_expanded (EAttachmentButton *button,
+ gboolean expanded)
+{
+ g_return_if_fail (E_IS_ATTACHMENT_BUTTON (button));
+
+ if (button->priv->expanded == expanded)
+ return;
+
+ button->priv->expanded = expanded;
+
+ g_object_notify (G_OBJECT (button), "expanded");
+}
diff --git a/e-util/e-attachment-button.h b/e-util/e-attachment-button.h
new file mode 100644
index 0000000000..abe5fa4dc9
--- /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/e-util/e-attachment-dialog.c b/e-util/e-attachment-dialog.c
new file mode 100644
index 0000000000..9a9a1e7942
--- /dev/null
+++ b/e-util/e-attachment-dialog.c
@@ -0,0 +1,425 @@
+/*
+ * e-attachment-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/>
+ *
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-attachment-dialog.h"
+
+#include <glib/gi18n.h>
+
+#define E_ATTACHMENT_DIALOG_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_ATTACHMENT_DIALOG, EAttachmentDialogPrivate))
+
+struct _EAttachmentDialogPrivate {
+ EAttachment *attachment;
+ GtkWidget *display_name_entry;
+ GtkWidget *description_entry;
+ GtkWidget *content_type_label;
+ GtkWidget *disposition_checkbox;
+};
+
+enum {
+ PROP_0,
+ PROP_ATTACHMENT
+};
+
+G_DEFINE_TYPE (
+ EAttachmentDialog,
+ e_attachment_dialog,
+ GTK_TYPE_DIALOG)
+
+static void
+attachment_dialog_update (EAttachmentDialog *dialog)
+{
+ EAttachment *attachment;
+ GFileInfo *file_info;
+ GtkWidget *widget;
+ const gchar *content_type;
+ const gchar *display_name;
+ const gchar *description;
+ const gchar *disposition;
+ gchar *type_description = NULL;
+ gboolean sensitive;
+ gboolean active;
+
+ attachment = e_attachment_dialog_get_attachment (dialog);
+
+ if (attachment != NULL) {
+ file_info = e_attachment_get_file_info (attachment);
+ description = e_attachment_get_description (attachment);
+ disposition = e_attachment_get_disposition (attachment);
+ } else {
+ file_info = NULL;
+ description = NULL;
+ disposition = NULL;
+ }
+
+ if (file_info != NULL) {
+ content_type = g_file_info_get_content_type (file_info);
+ display_name = g_file_info_get_display_name (file_info);
+ } else {
+ content_type = NULL;
+ display_name = NULL;
+ }
+
+ if (content_type != NULL) {
+ gchar *comment;
+ gchar *mime_type;
+
+ comment = g_content_type_get_description (content_type);
+ mime_type = g_content_type_get_mime_type (content_type);
+
+ type_description =
+ g_strdup_printf ("%s (%s)", comment, mime_type);
+
+ g_free (comment);
+ g_free (mime_type);
+ }
+
+ sensitive = G_IS_FILE_INFO (file_info);
+
+ gtk_dialog_set_response_sensitive (
+ GTK_DIALOG (dialog), GTK_RESPONSE_OK, sensitive);
+
+ widget = dialog->priv->display_name_entry;
+ gtk_widget_set_sensitive (widget, sensitive);
+ if (display_name != NULL)
+ gtk_entry_set_text (GTK_ENTRY (widget), display_name);
+
+ widget = dialog->priv->description_entry;
+ gtk_widget_set_sensitive (widget, sensitive);
+ if (description != NULL)
+ gtk_entry_set_text (GTK_ENTRY (widget), description);
+
+ widget = dialog->priv->content_type_label;
+ gtk_label_set_text (GTK_LABEL (widget), type_description);
+
+ active = (g_strcmp0 (disposition, "inline") == 0);
+ widget = dialog->priv->disposition_checkbox;
+ gtk_widget_set_sensitive (widget, sensitive);
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (widget), active);
+
+ g_free (type_description);
+}
+
+static void
+attachment_dialog_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_ATTACHMENT:
+ e_attachment_dialog_set_attachment (
+ E_ATTACHMENT_DIALOG (object),
+ g_value_get_object (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+attachment_dialog_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_ATTACHMENT:
+ g_value_set_object (
+ value, e_attachment_dialog_get_attachment (
+ E_ATTACHMENT_DIALOG (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+attachment_dialog_dispose (GObject *object)
+{
+ EAttachmentDialogPrivate *priv;
+
+ priv = E_ATTACHMENT_DIALOG_GET_PRIVATE (object);
+
+ if (priv->attachment != NULL) {
+ g_object_unref (priv->attachment);
+ priv->attachment = NULL;
+ }
+
+ if (priv->display_name_entry != NULL) {
+ g_object_unref (priv->display_name_entry);
+ priv->display_name_entry = NULL;
+ }
+
+ if (priv->description_entry != NULL) {
+ g_object_unref (priv->description_entry);
+ priv->description_entry = NULL;
+ }
+
+ if (priv->content_type_label != NULL) {
+ g_object_unref (priv->content_type_label);
+ priv->content_type_label = NULL;
+ }
+
+ if (priv->disposition_checkbox != NULL) {
+ g_object_unref (priv->disposition_checkbox);
+ priv->disposition_checkbox = NULL;
+ }
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (e_attachment_dialog_parent_class)->dispose (object);
+}
+
+static void
+attachment_dialog_map (GtkWidget *widget)
+{
+ GtkWidget *action_area;
+ GtkWidget *content_area;
+
+ /* Chain up to parent's map() method. */
+ GTK_WIDGET_CLASS (e_attachment_dialog_parent_class)->map (widget);
+
+ /* XXX Override GtkDialog's broken style property defaults. */
+ action_area = gtk_dialog_get_action_area (GTK_DIALOG (widget));
+ content_area = gtk_dialog_get_content_area (GTK_DIALOG (widget));
+
+ gtk_box_set_spacing (GTK_BOX (content_area), 12);
+ gtk_container_set_border_width (GTK_CONTAINER (action_area), 0);
+ gtk_container_set_border_width (GTK_CONTAINER (content_area), 12);
+}
+
+static void
+attachment_dialog_response (GtkDialog *dialog,
+ gint response_id)
+{
+ EAttachmentDialogPrivate *priv;
+ EAttachment *attachment;
+ GtkToggleButton *button;
+ GFileInfo *file_info;
+ CamelMimePart *mime_part;
+ const gchar *attribute;
+ const gchar *text;
+ gboolean active;
+
+ if (response_id != GTK_RESPONSE_OK)
+ return;
+
+ priv = E_ATTACHMENT_DIALOG_GET_PRIVATE (dialog);
+ g_return_if_fail (E_IS_ATTACHMENT (priv->attachment));
+ attachment = priv->attachment;
+
+ file_info = e_attachment_get_file_info (attachment);
+ g_return_if_fail (G_IS_FILE_INFO (file_info));
+
+ mime_part = e_attachment_get_mime_part (attachment);
+
+ attribute = G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME;
+ text = gtk_entry_get_text (GTK_ENTRY (priv->display_name_entry));
+ g_file_info_set_attribute_string (file_info, attribute, text);
+
+ if (mime_part != NULL)
+ camel_mime_part_set_filename (mime_part, text);
+
+ attribute = G_FILE_ATTRIBUTE_STANDARD_DESCRIPTION;
+ text = gtk_entry_get_text (GTK_ENTRY (priv->description_entry));
+ g_file_info_set_attribute_string (file_info, attribute, text);
+
+ if (mime_part != NULL)
+ camel_mime_part_set_description (mime_part, text);
+
+ button = GTK_TOGGLE_BUTTON (priv->disposition_checkbox);
+ active = gtk_toggle_button_get_active (button);
+ text = active ? "inline" : "attachment";
+ e_attachment_set_disposition (attachment, text);
+
+ if (mime_part != NULL)
+ camel_mime_part_set_disposition (mime_part, text);
+
+ g_object_notify (G_OBJECT (attachment), "file-info");
+}
+
+static void
+e_attachment_dialog_class_init (EAttachmentDialogClass *class)
+{
+ GObjectClass *object_class;
+ GtkWidgetClass *widget_class;
+ GtkDialogClass *dialog_class;
+
+ g_type_class_add_private (class, sizeof (EAttachmentDialogPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = attachment_dialog_set_property;
+ object_class->get_property = attachment_dialog_get_property;
+ object_class->dispose = attachment_dialog_dispose;
+
+ widget_class = GTK_WIDGET_CLASS (class);
+ widget_class->map = attachment_dialog_map;
+
+ dialog_class = GTK_DIALOG_CLASS (class);
+ dialog_class->response = attachment_dialog_response;
+
+ g_object_class_install_property (
+ object_class,
+ PROP_ATTACHMENT,
+ g_param_spec_object (
+ "attachment",
+ "Attachment",
+ NULL,
+ E_TYPE_ATTACHMENT,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+}
+
+static void
+e_attachment_dialog_init (EAttachmentDialog *dialog)
+{
+ GtkWidget *container;
+ GtkWidget *widget;
+
+ dialog->priv = E_ATTACHMENT_DIALOG_GET_PRIVATE (dialog);
+
+ gtk_dialog_add_button (
+ GTK_DIALOG (dialog), GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL);
+ gtk_dialog_add_button (
+ GTK_DIALOG (dialog), GTK_STOCK_OK, GTK_RESPONSE_OK);
+ gtk_window_set_icon_name (
+ GTK_WINDOW (dialog), "mail-attachment");
+ gtk_window_set_title (
+ GTK_WINDOW (dialog), _("Attachment Properties"));
+
+ gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK);
+
+ container = gtk_dialog_get_content_area (GTK_DIALOG (dialog));
+
+ widget = gtk_table_new (4, 2, FALSE);
+ gtk_table_set_col_spacings (GTK_TABLE (widget), 6);
+ gtk_table_set_row_spacings (GTK_TABLE (widget), 6);
+ gtk_box_pack_start (GTK_BOX (container), widget, TRUE, TRUE, 0);
+ gtk_widget_show (widget);
+
+ container = widget;
+
+ widget = gtk_entry_new ();
+ gtk_entry_set_activates_default (GTK_ENTRY (widget), TRUE);
+ gtk_table_attach (
+ GTK_TABLE (container), widget,
+ 1, 2, 0, 1, GTK_FILL | GTK_EXPAND, 0, 0, 0);
+ dialog->priv->display_name_entry = g_object_ref (widget);
+ gtk_widget_show (widget);
+
+ widget = gtk_label_new_with_mnemonic (_("F_ilename:"));
+ gtk_misc_set_alignment (GTK_MISC (widget), 1.0, 0.5);
+ gtk_label_set_mnemonic_widget (
+ GTK_LABEL (widget), dialog->priv->display_name_entry);
+ gtk_table_attach (
+ GTK_TABLE (container), widget,
+ 0, 1, 0, 1, GTK_FILL, 0, 0, 0);
+ gtk_widget_show (widget);
+
+ widget = gtk_entry_new ();
+ gtk_entry_set_activates_default (GTK_ENTRY (widget), TRUE);
+ gtk_table_attach (
+ GTK_TABLE (container), widget,
+ 1, 2, 1, 2, GTK_FILL | GTK_EXPAND, 0, 0, 0);
+ dialog->priv->description_entry = g_object_ref (widget);
+ gtk_widget_show (widget);
+
+ widget = gtk_label_new_with_mnemonic (_("_Description:"));
+ gtk_misc_set_alignment (GTK_MISC (widget), 1.0, 0.5);
+ gtk_label_set_mnemonic_widget (
+ GTK_LABEL (widget), dialog->priv->description_entry);
+ gtk_table_attach (
+ GTK_TABLE (container), widget,
+ 0, 1, 1, 2, GTK_FILL, 0, 0, 0);
+ gtk_widget_show (widget);
+
+ widget = gtk_label_new (NULL);
+ gtk_label_set_selectable (GTK_LABEL (widget), TRUE);
+ gtk_misc_set_alignment (GTK_MISC (widget), 0.0, 0.5);
+ gtk_table_attach (
+ GTK_TABLE (container), widget,
+ 1, 2, 2, 3, GTK_FILL | GTK_EXPAND, 0, 0, 0);
+ dialog->priv->content_type_label = g_object_ref (widget);
+ gtk_widget_show (widget);
+
+ widget = gtk_label_new (_("MIME Type:"));
+ gtk_misc_set_alignment (GTK_MISC (widget), 1.0, 0.5);
+ gtk_table_attach (
+ GTK_TABLE (container), widget,
+ 0, 1, 2, 3, GTK_FILL, 0, 0, 0);
+ gtk_widget_show (widget);
+
+ widget = gtk_check_button_new_with_mnemonic (
+ _("_Suggest automatic display of attachment"));
+ gtk_table_attach (
+ GTK_TABLE (container), widget,
+ 0, 2, 3, 4, GTK_FILL | GTK_EXPAND, 0, 0, 0);
+ dialog->priv->disposition_checkbox = g_object_ref (widget);
+ gtk_widget_show (widget);
+}
+
+GtkWidget *
+e_attachment_dialog_new (GtkWindow *parent,
+ EAttachment *attachment)
+{
+ if (parent != NULL)
+ g_return_val_if_fail (GTK_IS_WINDOW (parent), NULL);
+ if (attachment != NULL)
+ g_return_val_if_fail (E_IS_ATTACHMENT (attachment), NULL);
+
+ return g_object_new (
+ E_TYPE_ATTACHMENT_DIALOG,
+ "transient-for", parent, "attachment", attachment, NULL);
+}
+
+EAttachment *
+e_attachment_dialog_get_attachment (EAttachmentDialog *dialog)
+{
+ g_return_val_if_fail (E_IS_ATTACHMENT_DIALOG (dialog), NULL);
+
+ return dialog->priv->attachment;
+}
+
+void
+e_attachment_dialog_set_attachment (EAttachmentDialog *dialog,
+ EAttachment *attachment)
+{
+ g_return_if_fail (E_IS_ATTACHMENT_DIALOG (dialog));
+
+ if (attachment != NULL) {
+ g_return_if_fail (E_IS_ATTACHMENT (attachment));
+ g_object_ref (attachment);
+ }
+
+ if (dialog->priv->attachment != NULL)
+ g_object_unref (dialog->priv->attachment);
+
+ dialog->priv->attachment = attachment;
+
+ attachment_dialog_update (dialog);
+
+ g_object_notify (G_OBJECT (dialog), "attachment");
+}
diff --git a/e-util/e-attachment-dialog.h b/e-util/e-attachment-dialog.h
new file mode 100644
index 0000000000..af7141190e
--- /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 0000000000..36c3a83614
--- /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 0000000000..e0e0cb3b23
--- /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/e-util/e-attachment-handler-sendto.c b/e-util/e-attachment-handler-sendto.c
new file mode 100644
index 0000000000..f0fe698713
--- /dev/null
+++ b/e-util/e-attachment-handler-sendto.c
@@ -0,0 +1,229 @@
+/*
+ * e-attachment-handler-sendto.c
+ *
+ * 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/>
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-attachment-handler-sendto.h"
+
+#include <errno.h>
+
+#include <glib/gi18n-lib.h>
+
+static const gchar *ui =
+"<ui>"
+" <popup name='context'>"
+" <placeholder name='custom-actions'>"
+" <menuitem action='sendto'/>"
+" </placeholder>"
+" </popup>"
+"</ui>";
+
+G_DEFINE_TYPE (
+ EAttachmentHandlerSendto,
+ e_attachment_handler_sendto,
+ E_TYPE_ATTACHMENT_HANDLER)
+
+static void
+sendto_save_finished_cb (EAttachment *attachment,
+ GAsyncResult *result,
+ EAttachmentHandler *handler)
+{
+ EAttachmentView *view;
+ EAttachmentStore *store;
+ GtkWidget *dialog;
+ gchar **uris;
+ gpointer parent;
+ gchar *arguments;
+ gchar *command_line;
+ guint n_uris = 1;
+ GError *error = NULL;
+
+ view = e_attachment_handler_get_view (handler);
+ store = e_attachment_view_get_store (view);
+
+ uris = e_attachment_store_get_uris_finish (store, result, &error);
+
+ if (uris != NULL)
+ n_uris = g_strv_length (uris);
+
+ if (error != NULL)
+ goto error;
+
+ arguments = g_strjoinv (" ", uris);
+ command_line = g_strdup_printf ("nautilus-sendto %s", arguments);
+
+ g_message ("Command: %s", command_line);
+ g_spawn_command_line_async (command_line, &error);
+
+ g_free (command_line);
+ g_free (arguments);
+
+ if (error != NULL)
+ goto error;
+
+ 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>",
+ ngettext ("Could not send attachment",
+ "Could not send attachments", n_uris));
+
+ 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 (handler);
+ g_strfreev (uris);
+}
+
+static void
+action_sendto_cb (GtkAction *action,
+ EAttachmentHandler *handler)
+{
+ EAttachmentView *view;
+ EAttachmentStore *store;
+ GList *selected;
+
+ view = e_attachment_handler_get_view (handler);
+ store = e_attachment_view_get_store (view);
+
+ selected = e_attachment_view_get_selected_attachments (view);
+ g_return_if_fail (selected != NULL);
+
+ e_attachment_store_get_uris_async (
+ store, selected, (GAsyncReadyCallback)
+ sendto_save_finished_cb, g_object_ref (handler));
+
+ g_list_foreach (selected, (GFunc) g_object_unref, NULL);
+ g_list_free (selected);
+}
+
+static GtkActionEntry standard_entries[] = {
+
+ { "sendto",
+ "document-send",
+ N_("_Send To..."),
+ NULL,
+ N_("Send the selected attachments somewhere"),
+ G_CALLBACK (action_sendto_cb) }
+};
+
+static void
+attachment_handler_sendto_update_actions_cb (EAttachmentView *view,
+ EAttachmentHandler *handler)
+{
+ GtkActionGroup *action_group;
+ GList *selected, *iter;
+ gboolean visible = FALSE;
+ gchar *program;
+
+ program = g_find_program_in_path ("nautilus-sendto");
+ selected = e_attachment_view_get_selected_attachments (view);
+
+ if (program == NULL || selected == NULL)
+ goto exit;
+
+ /* Make sure no file transfers are in progress. */
+ for (iter = selected; iter != NULL; iter = iter->next) {
+ EAttachment *attachment = iter->data;
+
+ if (e_attachment_get_loading (attachment))
+ goto exit;
+
+ if (e_attachment_get_saving (attachment))
+ goto exit;
+ }
+
+ visible = TRUE;
+
+exit:
+ action_group = e_attachment_view_get_action_group (view, "sendto");
+ gtk_action_group_set_visible (action_group, visible);
+
+ g_list_foreach (selected, (GFunc) g_object_unref, NULL);
+ g_list_free (selected);
+
+ g_free (program);
+}
+
+static void
+attachment_handler_sendto_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_sendto_parent_class)->constructed (object);
+
+ view = e_attachment_handler_get_view (handler);
+ ui_manager = e_attachment_view_get_ui_manager (view);
+
+ action_group = gtk_action_group_new ("sendto");
+ gtk_action_group_set_translation_domain (
+ action_group, GETTEXT_PACKAGE);
+ gtk_action_group_add_actions (
+ action_group, standard_entries,
+ G_N_ELEMENTS (standard_entries), object);
+ gtk_ui_manager_insert_action_group (ui_manager, action_group, 0);
+
+ 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_sendto_update_actions_cb),
+ object);
+}
+
+static void
+e_attachment_handler_sendto_class_init (EAttachmentHandlerSendtoClass *class)
+{
+ GObjectClass *object_class;
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->constructed = attachment_handler_sendto_constructed;
+}
+
+static void
+e_attachment_handler_sendto_init (EAttachmentHandlerSendto *handler)
+{
+}
diff --git a/e-util/e-attachment-handler-sendto.h b/e-util/e-attachment-handler-sendto.h
new file mode 100644
index 0000000000..17115c4104
--- /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/e-util/e-attachment-handler.c b/e-util/e-attachment-handler.c
new file mode 100644
index 0000000000..87b9abddb5
--- /dev/null
+++ b/e-util/e-attachment-handler.c
@@ -0,0 +1,133 @@
+/*
+ * e-attachment-handler.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.h"
+
+#define E_ATTACHMENT_HANDLER_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_ATTACHMENT_HANDLER, EAttachmentHandlerPrivate))
+
+struct _EAttachmentHandlerPrivate {
+ gpointer placeholder;
+};
+
+G_DEFINE_TYPE (
+ EAttachmentHandler,
+ e_attachment_handler,
+ E_TYPE_EXTENSION)
+
+static void
+attachment_handler_constructed (GObject *object)
+{
+ EAttachmentView *view;
+ EAttachmentHandler *handler;
+ GdkDragAction drag_actions;
+ GtkTargetList *target_list;
+ const GtkTargetEntry *targets;
+ guint n_targets;
+
+ handler = E_ATTACHMENT_HANDLER (object);
+ drag_actions = e_attachment_handler_get_drag_actions (handler);
+ targets = e_attachment_handler_get_target_table (handler, &n_targets);
+
+ view = e_attachment_handler_get_view (handler);
+
+ target_list = e_attachment_view_get_target_list (view);
+ gtk_target_list_add_table (target_list, targets, n_targets);
+
+ e_attachment_view_add_drag_actions (view, drag_actions);
+
+ /* Chain up to parent's constructed() method. */
+ G_OBJECT_CLASS (e_attachment_handler_parent_class)->constructed (object);
+}
+
+static void
+e_attachment_handler_class_init (EAttachmentHandlerClass *class)
+{
+ GObjectClass *object_class;
+ EExtensionClass *extension_class;
+
+ g_type_class_add_private (class, sizeof (EAttachmentHandlerPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->constructed = attachment_handler_constructed;
+
+ extension_class = E_EXTENSION_CLASS (class);
+ extension_class->extensible_type = E_TYPE_ATTACHMENT_VIEW;
+}
+
+static void
+e_attachment_handler_init (EAttachmentHandler *handler)
+{
+ handler->priv = E_ATTACHMENT_HANDLER_GET_PRIVATE (handler);
+}
+
+EAttachmentView *
+e_attachment_handler_get_view (EAttachmentHandler *handler)
+{
+ EExtensible *extensible;
+
+ /* This is purely a convenience function. */
+
+ g_return_val_if_fail (E_IS_ATTACHMENT_HANDLER (handler), NULL);
+
+ extensible = e_extension_get_extensible (E_EXTENSION (handler));
+
+ return E_ATTACHMENT_VIEW (extensible);
+}
+
+GdkDragAction
+e_attachment_handler_get_drag_actions (EAttachmentHandler *handler)
+{
+ EAttachmentHandlerClass *class;
+
+ g_return_val_if_fail (E_IS_ATTACHMENT_HANDLER (handler), 0);
+
+ class = E_ATTACHMENT_HANDLER_GET_CLASS (handler);
+
+ if (class->get_drag_actions != NULL)
+ return class->get_drag_actions (handler);
+
+ return 0;
+}
+
+const GtkTargetEntry *
+e_attachment_handler_get_target_table (EAttachmentHandler *handler,
+ guint *n_targets)
+{
+ EAttachmentHandlerClass *class;
+
+ g_return_val_if_fail (E_IS_ATTACHMENT_HANDLER (handler), NULL);
+
+ class = E_ATTACHMENT_HANDLER_GET_CLASS (handler);
+
+ if (class->get_target_table != NULL)
+ return class->get_target_table (handler, n_targets);
+
+ if (n_targets != NULL)
+ *n_targets = 0;
+
+ return NULL;
+}
diff --git a/e-util/e-attachment-handler.h b/e-util/e-attachment-handler.h
new file mode 100644
index 0000000000..086ba8ff6a
--- /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/e-util/e-attachment-icon-view.c b/e-util/e-attachment-icon-view.c
new file mode 100644
index 0000000000..2be8009e8a
--- /dev/null
+++ b/e-util/e-attachment-icon-view.c
@@ -0,0 +1,570 @@
+/*
+ * e-attachment-icon-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-icon-view.h"
+
+#include <glib/gi18n.h>
+#include <libebackend/libebackend.h>
+
+#include "e-attachment.h"
+#include "e-attachment-store.h"
+#include "e-attachment-view.h"
+
+#define E_ATTACHMENT_ICON_VIEW_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_ATTACHMENT_ICON_VIEW, EAttachmentIconViewPrivate))
+
+struct _EAttachmentIconViewPrivate {
+ EAttachmentViewPrivate view_priv;
+};
+
+enum {
+ PROP_0,
+ PROP_DRAGGING,
+ PROP_EDITABLE
+};
+
+static gint icon_size = GTK_ICON_SIZE_DIALOG;
+
+/* Forward Declarations */
+static void e_attachment_icon_view_interface_init
+ (EAttachmentViewInterface *interface);
+
+G_DEFINE_TYPE_WITH_CODE (
+ EAttachmentIconView,
+ e_attachment_icon_view,
+ GTK_TYPE_ICON_VIEW,
+ G_IMPLEMENT_INTERFACE (
+ E_TYPE_ATTACHMENT_VIEW,
+ e_attachment_icon_view_interface_init)
+ G_IMPLEMENT_INTERFACE (
+ E_TYPE_EXTENSIBLE, NULL))
+
+void
+e_attachment_icon_view_set_default_icon_size (gint size)
+{
+ icon_size = size;
+}
+
+static void
+attachment_icon_view_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_DRAGGING:
+ e_attachment_view_set_dragging (
+ E_ATTACHMENT_VIEW (object),
+ g_value_get_boolean (value));
+ return;
+
+ case PROP_EDITABLE:
+ e_attachment_view_set_editable (
+ E_ATTACHMENT_VIEW (object),
+ g_value_get_boolean (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+attachment_icon_view_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_DRAGGING:
+ g_value_set_boolean (
+ value, e_attachment_view_get_dragging (
+ E_ATTACHMENT_VIEW (object)));
+ return;
+
+ case PROP_EDITABLE:
+ g_value_set_boolean (
+ value, e_attachment_view_get_editable (
+ E_ATTACHMENT_VIEW (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+attachment_icon_view_dispose (GObject *object)
+{
+ e_attachment_view_dispose (E_ATTACHMENT_VIEW (object));
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (e_attachment_icon_view_parent_class)->dispose (object);
+}
+
+static void
+attachment_icon_view_finalize (GObject *object)
+{
+ e_attachment_view_finalize (E_ATTACHMENT_VIEW (object));
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (e_attachment_icon_view_parent_class)->finalize (object);
+}
+
+static void
+attachment_icon_view_constructed (GObject *object)
+{
+ GtkCellLayout *cell_layout;
+ GtkCellRenderer *renderer;
+
+ cell_layout = GTK_CELL_LAYOUT (object);
+
+ /* This needs to happen after constructor properties are set
+ * so that GtkCellLayout.get_area() returns something valid. */
+
+ renderer = gtk_cell_renderer_pixbuf_new ();
+ g_object_set (renderer, "stock-size", icon_size, NULL);
+ gtk_cell_layout_pack_start (cell_layout, renderer, FALSE);
+
+ gtk_cell_layout_add_attribute (
+ cell_layout, renderer, "gicon",
+ E_ATTACHMENT_STORE_COLUMN_ICON);
+
+ renderer = gtk_cell_renderer_text_new ();
+ g_object_set (
+ renderer, "alignment", PANGO_ALIGN_CENTER,
+ "wrap-mode", PANGO_WRAP_WORD, "wrap-width", 150,
+ "yalign", 0.0, NULL);
+ gtk_cell_layout_pack_start (cell_layout, renderer, FALSE);
+
+ gtk_cell_layout_add_attribute (
+ cell_layout, renderer, "text",
+ E_ATTACHMENT_STORE_COLUMN_CAPTION);
+
+ renderer = gtk_cell_renderer_progress_new ();
+ g_object_set (renderer, "text", _("Loading"), NULL);
+ gtk_cell_layout_pack_start (cell_layout, renderer, TRUE);
+
+ gtk_cell_layout_add_attribute (
+ cell_layout, renderer, "value",
+ E_ATTACHMENT_STORE_COLUMN_PERCENT);
+
+ gtk_cell_layout_add_attribute (
+ cell_layout, renderer, "visible",
+ E_ATTACHMENT_STORE_COLUMN_LOADING);
+
+ renderer = gtk_cell_renderer_progress_new ();
+ g_object_set (renderer, "text", _("Saving"), NULL);
+ gtk_cell_layout_pack_start (cell_layout, renderer, TRUE);
+
+ gtk_cell_layout_add_attribute (
+ cell_layout, renderer, "value",
+ E_ATTACHMENT_STORE_COLUMN_PERCENT);
+
+ gtk_cell_layout_add_attribute (
+ cell_layout, renderer, "visible",
+ E_ATTACHMENT_STORE_COLUMN_SAVING);
+
+ e_extensible_load_extensions (E_EXTENSIBLE (object));
+}
+
+static gboolean
+attachment_icon_view_button_press_event (GtkWidget *widget,
+ GdkEventButton *event)
+{
+ EAttachmentView *view = E_ATTACHMENT_VIEW (widget);
+
+ if (e_attachment_view_button_press_event (view, event))
+ return TRUE;
+
+ /* Chain up to parent's button_press_event() method. */
+ return GTK_WIDGET_CLASS (e_attachment_icon_view_parent_class)->
+ button_press_event (widget, event);
+}
+
+static gboolean
+attachment_icon_view_button_release_event (GtkWidget *widget,
+ GdkEventButton *event)
+{
+ EAttachmentView *view = E_ATTACHMENT_VIEW (widget);
+
+ if (e_attachment_view_button_release_event (view, event))
+ return TRUE;
+
+ /* Chain up to parent's button_release_event() method. */
+ return GTK_WIDGET_CLASS (e_attachment_icon_view_parent_class)->
+ button_release_event (widget, event);
+}
+
+static gboolean
+attachment_icon_view_motion_notify_event (GtkWidget *widget,
+ GdkEventMotion *event)
+{
+ EAttachmentView *view = E_ATTACHMENT_VIEW (widget);
+
+ if (e_attachment_view_motion_notify_event (view, event))
+ return TRUE;
+
+ /* Chain up to parent's motion_notify_event() method. */
+ return GTK_WIDGET_CLASS (e_attachment_icon_view_parent_class)->
+ motion_notify_event (widget, event);
+}
+
+static gboolean
+attachment_icon_view_key_press_event (GtkWidget *widget,
+ GdkEventKey *event)
+{
+ EAttachmentView *view = E_ATTACHMENT_VIEW (widget);
+
+ if (e_attachment_view_key_press_event (view, event))
+ return TRUE;
+
+ /* Chain up to parent's key_press_event() method. */
+ return GTK_WIDGET_CLASS (e_attachment_icon_view_parent_class)->
+ key_press_event (widget, event);
+}
+
+static void
+attachment_icon_view_drag_begin (GtkWidget *widget,
+ GdkDragContext *context)
+{
+ EAttachmentView *view = E_ATTACHMENT_VIEW (widget);
+
+ /* Chain up to parent's drag_begin() method. */
+ GTK_WIDGET_CLASS (e_attachment_icon_view_parent_class)->
+ drag_begin (widget, context);
+
+ e_attachment_view_drag_begin (view, context);
+}
+
+static void
+attachment_icon_view_drag_end (GtkWidget *widget,
+ GdkDragContext *context)
+{
+ EAttachmentView *view = E_ATTACHMENT_VIEW (widget);
+
+ /* Chain up to parent's drag_end() method. */
+ GTK_WIDGET_CLASS (e_attachment_icon_view_parent_class)->
+ drag_end (widget, context);
+
+ e_attachment_view_drag_end (view, context);
+}
+
+static void
+attachment_icon_view_drag_data_get (GtkWidget *widget,
+ GdkDragContext *context,
+ GtkSelectionData *selection,
+ guint info,
+ guint time)
+{
+ EAttachmentView *view = E_ATTACHMENT_VIEW (widget);
+
+ e_attachment_view_drag_data_get (
+ view, context, selection, info, time);
+}
+
+static gboolean
+attachment_icon_view_drag_motion (GtkWidget *widget,
+ GdkDragContext *context,
+ gint x,
+ gint y,
+ guint time)
+{
+ EAttachmentView *view = E_ATTACHMENT_VIEW (widget);
+
+ return e_attachment_view_drag_motion (view, context, x, y, time);
+}
+
+static gboolean
+attachment_icon_view_drag_drop (GtkWidget *widget,
+ GdkDragContext *context,
+ gint x,
+ gint y,
+ guint time)
+{
+ EAttachmentView *view = E_ATTACHMENT_VIEW (widget);
+
+ if (!e_attachment_view_drag_drop (view, context, x, y, time))
+ return FALSE;
+
+ /* Chain up to parent's drag_drop() method. */
+ return GTK_WIDGET_CLASS (e_attachment_icon_view_parent_class)->
+ drag_drop (widget, context, x, y, time);
+}
+
+static void
+attachment_icon_view_drag_data_received (GtkWidget *widget,
+ GdkDragContext *context,
+ gint x,
+ gint y,
+ GtkSelectionData *selection,
+ guint info,
+ guint time)
+{
+ EAttachmentView *view = E_ATTACHMENT_VIEW (widget);
+
+ e_attachment_view_drag_data_received (
+ view, context, x, y, selection, info, time);
+}
+
+static gboolean
+attachment_icon_view_popup_menu (GtkWidget *widget)
+{
+ EAttachmentView *view = E_ATTACHMENT_VIEW (widget);
+
+ e_attachment_view_show_popup_menu (view, NULL, NULL, NULL);
+
+ return TRUE;
+}
+
+static void
+attachment_icon_view_item_activated (GtkIconView *icon_view,
+ GtkTreePath *path)
+{
+ EAttachmentView *view = E_ATTACHMENT_VIEW (icon_view);
+
+ e_attachment_view_open_path (view, path, NULL);
+}
+
+static EAttachmentViewPrivate *
+attachment_icon_view_get_private (EAttachmentView *view)
+{
+ EAttachmentIconViewPrivate *priv;
+
+ priv = E_ATTACHMENT_ICON_VIEW_GET_PRIVATE (view);
+
+ return &priv->view_priv;
+}
+
+static EAttachmentStore *
+attachment_icon_view_get_store (EAttachmentView *view)
+{
+ GtkIconView *icon_view;
+ GtkTreeModel *model;
+
+ icon_view = GTK_ICON_VIEW (view);
+ model = gtk_icon_view_get_model (icon_view);
+
+ return E_ATTACHMENT_STORE (model);
+}
+
+static GtkTreePath *
+attachment_icon_view_get_path_at_pos (EAttachmentView *view,
+ gint x,
+ gint y)
+{
+ GtkIconView *icon_view;
+
+ icon_view = GTK_ICON_VIEW (view);
+
+ return gtk_icon_view_get_path_at_pos (icon_view, x, y);
+}
+
+static GList *
+attachment_icon_view_get_selected_paths (EAttachmentView *view)
+{
+ GtkIconView *icon_view;
+
+ icon_view = GTK_ICON_VIEW (view);
+
+ return gtk_icon_view_get_selected_items (icon_view);
+}
+
+static gboolean
+attachment_icon_view_path_is_selected (EAttachmentView *view,
+ GtkTreePath *path)
+{
+ GtkIconView *icon_view;
+
+ icon_view = GTK_ICON_VIEW (view);
+
+ return gtk_icon_view_path_is_selected (icon_view, path);
+}
+
+static void
+attachment_icon_view_select_path (EAttachmentView *view,
+ GtkTreePath *path)
+{
+ GtkIconView *icon_view;
+
+ icon_view = GTK_ICON_VIEW (view);
+
+ gtk_icon_view_select_path (icon_view, path);
+}
+
+static void
+attachment_icon_view_unselect_path (EAttachmentView *view,
+ GtkTreePath *path)
+{
+ GtkIconView *icon_view;
+
+ icon_view = GTK_ICON_VIEW (view);
+
+ gtk_icon_view_unselect_path (icon_view, path);
+}
+
+static void
+attachment_icon_view_select_all (EAttachmentView *view)
+{
+ GtkIconView *icon_view;
+
+ icon_view = GTK_ICON_VIEW (view);
+
+ gtk_icon_view_select_all (icon_view);
+}
+
+static void
+attachment_icon_view_unselect_all (EAttachmentView *view)
+{
+ GtkIconView *icon_view;
+
+ icon_view = GTK_ICON_VIEW (view);
+
+ gtk_icon_view_unselect_all (icon_view);
+}
+
+static void
+attachment_icon_view_drag_source_set (EAttachmentView *view,
+ GdkModifierType start_button_mask,
+ const GtkTargetEntry *targets,
+ gint n_targets,
+ GdkDragAction actions)
+{
+ GtkIconView *icon_view;
+
+ icon_view = GTK_ICON_VIEW (view);
+
+ gtk_icon_view_enable_model_drag_source (
+ icon_view, start_button_mask, targets, n_targets, actions);
+}
+
+static void
+attachment_icon_view_drag_dest_set (EAttachmentView *view,
+ const GtkTargetEntry *targets,
+ gint n_targets,
+ GdkDragAction actions)
+{
+ GtkIconView *icon_view;
+
+ icon_view = GTK_ICON_VIEW (view);
+
+ gtk_icon_view_enable_model_drag_dest (
+ icon_view, targets, n_targets, actions);
+}
+
+static void
+attachment_icon_view_drag_source_unset (EAttachmentView *view)
+{
+ GtkIconView *icon_view;
+
+ icon_view = GTK_ICON_VIEW (view);
+
+ gtk_icon_view_unset_model_drag_source (icon_view);
+}
+
+static void
+attachment_icon_view_drag_dest_unset (EAttachmentView *view)
+{
+ GtkIconView *icon_view;
+
+ icon_view = GTK_ICON_VIEW (view);
+
+ gtk_icon_view_unset_model_drag_dest (icon_view);
+}
+
+static void
+e_attachment_icon_view_class_init (EAttachmentIconViewClass *class)
+{
+ GObjectClass *object_class;
+ GtkWidgetClass *widget_class;
+ GtkIconViewClass *icon_view_class;
+
+ g_type_class_add_private (class, sizeof (EAttachmentViewPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = attachment_icon_view_set_property;
+ object_class->get_property = attachment_icon_view_get_property;
+ object_class->dispose = attachment_icon_view_dispose;
+ object_class->finalize = attachment_icon_view_finalize;
+ object_class->constructed = attachment_icon_view_constructed;
+
+ widget_class = GTK_WIDGET_CLASS (class);
+ widget_class->button_press_event = attachment_icon_view_button_press_event;
+ widget_class->button_release_event = attachment_icon_view_button_release_event;
+ widget_class->motion_notify_event = attachment_icon_view_motion_notify_event;
+ widget_class->key_press_event = attachment_icon_view_key_press_event;
+ widget_class->drag_begin = attachment_icon_view_drag_begin;
+ widget_class->drag_end = attachment_icon_view_drag_end;
+ widget_class->drag_data_get = attachment_icon_view_drag_data_get;
+ widget_class->drag_motion = attachment_icon_view_drag_motion;
+ widget_class->drag_drop = attachment_icon_view_drag_drop;
+ widget_class->drag_data_received = attachment_icon_view_drag_data_received;
+ widget_class->popup_menu = attachment_icon_view_popup_menu;
+
+ icon_view_class = GTK_ICON_VIEW_CLASS (class);
+ icon_view_class->item_activated = attachment_icon_view_item_activated;
+
+ g_object_class_override_property (
+ object_class, PROP_DRAGGING, "dragging");
+
+ g_object_class_override_property (
+ object_class, PROP_EDITABLE, "editable");
+}
+
+static void
+e_attachment_icon_view_init (EAttachmentIconView *icon_view)
+{
+ icon_view->priv = E_ATTACHMENT_ICON_VIEW_GET_PRIVATE (icon_view);
+
+ e_attachment_view_init (E_ATTACHMENT_VIEW (icon_view));
+
+ gtk_icon_view_set_selection_mode (
+ GTK_ICON_VIEW (icon_view), GTK_SELECTION_MULTIPLE);
+}
+
+static void
+e_attachment_icon_view_interface_init (EAttachmentViewInterface *interface)
+{
+ interface->get_private = attachment_icon_view_get_private;
+ interface->get_store = attachment_icon_view_get_store;
+
+ interface->get_path_at_pos = attachment_icon_view_get_path_at_pos;
+ interface->get_selected_paths = attachment_icon_view_get_selected_paths;
+ interface->path_is_selected = attachment_icon_view_path_is_selected;
+ interface->select_path = attachment_icon_view_select_path;
+ interface->unselect_path = attachment_icon_view_unselect_path;
+ interface->select_all = attachment_icon_view_select_all;
+ interface->unselect_all = attachment_icon_view_unselect_all;
+
+ interface->drag_source_set = attachment_icon_view_drag_source_set;
+ interface->drag_dest_set = attachment_icon_view_drag_dest_set;
+ interface->drag_source_unset = attachment_icon_view_drag_source_unset;
+ interface->drag_dest_unset = attachment_icon_view_drag_dest_unset;
+}
+
+GtkWidget *
+e_attachment_icon_view_new (void)
+{
+ return g_object_new (E_TYPE_ATTACHMENT_ICON_VIEW, NULL);
+}
diff --git a/e-util/e-attachment-icon-view.h b/e-util/e-attachment-icon-view.h
new file mode 100644
index 0000000000..bd3d2109db
--- /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/e-util/e-attachment-paned.c b/e-util/e-attachment-paned.c
new file mode 100644
index 0000000000..a3c4efb187
--- /dev/null
+++ b/e-util/e-attachment-paned.c
@@ -0,0 +1,904 @@
+/*
+ * e-attachment-paned.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-paned.h"
+
+#include <glib/gi18n.h>
+
+#include "e-attachment-view.h"
+#include "e-attachment-store.h"
+#include "e-attachment-icon-view.h"
+#include "e-attachment-tree-view.h"
+
+#define E_ATTACHMENT_PANED_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_ATTACHMENT_PANED, EAttachmentPanedPrivate))
+
+#define NUM_VIEWS 2
+
+/* Initial height of the lower pane. */
+static gint initial_height = 150;
+
+struct _EAttachmentPanedPrivate {
+ GtkTreeModel *model;
+ GtkWidget *expander;
+ GtkWidget *notebook;
+ GtkWidget *combo_box;
+ GtkWidget *controls_container;
+ GtkWidget *icon_view;
+ GtkWidget *tree_view;
+ GtkWidget *show_hide_label;
+ GtkWidget *status_icon;
+ GtkWidget *status_label;
+ GtkWidget *content_area;
+
+ gint active_view;
+ gboolean expanded;
+ gboolean resize_toplevel;
+};
+
+enum {
+ PROP_0,
+ PROP_ACTIVE_VIEW,
+ PROP_DRAGGING,
+ PROP_EDITABLE,
+ PROP_EXPANDED,
+ PROP_RESIZE_TOPLEVEL
+};
+
+/* Forward Declarations */
+static void e_attachment_paned_interface_init
+ (EAttachmentViewInterface *interface);
+
+G_DEFINE_TYPE_WITH_CODE (
+ EAttachmentPaned,
+ e_attachment_paned,
+ GTK_TYPE_VPANED,
+ G_IMPLEMENT_INTERFACE (
+ E_TYPE_ATTACHMENT_VIEW,
+ e_attachment_paned_interface_init))
+
+void
+e_attachment_paned_set_default_height (gint height)
+{
+ initial_height = height;
+}
+
+static void
+attachment_paned_notify_cb (EAttachmentPaned *paned,
+ GParamSpec *pspec,
+ GtkExpander *expander)
+{
+ GtkAllocation toplevel_allocation;
+ GtkWidget *toplevel;
+ GtkWidget *child;
+ GtkLabel *label;
+ const gchar *text;
+
+ label = GTK_LABEL (paned->priv->show_hide_label);
+
+ /* Update the expander label. */
+ if (gtk_expander_get_expanded (expander))
+ text = _("Hide Attachment _Bar");
+ else
+ text = _("Show Attachment _Bar");
+
+ gtk_label_set_text_with_mnemonic (label, text);
+
+ /* Resize the top-level window if required conditions are met.
+ * This is based on gtk_expander_resize_toplevel(), but adapted
+ * to the fact our GtkExpander has no direct child widget. */
+
+ if (!e_attachment_paned_get_resize_toplevel (paned))
+ return;
+
+ if (!gtk_widget_get_realized (GTK_WIDGET (paned)))
+ return;
+
+ child = gtk_paned_get_child2 (GTK_PANED (paned));
+ toplevel = gtk_widget_get_toplevel (GTK_WIDGET (paned));
+
+ if (toplevel == NULL)
+ return;
+
+ if (!gtk_widget_get_realized (GTK_WIDGET (toplevel)))
+ return;
+
+ gtk_widget_get_allocation (toplevel, &toplevel_allocation);
+
+ if (gtk_expander_get_expanded (expander)) {
+ GtkRequisition child_requisition;
+
+ gtk_widget_get_preferred_size (
+ child, &child_requisition, NULL);
+
+ toplevel_allocation.height += child_requisition.height;
+ } else {
+ GtkAllocation child_allocation;
+
+ gtk_widget_get_allocation (child, &child_allocation);
+
+ toplevel_allocation.height -= child_allocation.height;
+ }
+
+ gtk_window_resize (
+ GTK_WINDOW (toplevel),
+ toplevel_allocation.width,
+ toplevel_allocation.height);
+}
+
+static void
+attachment_paned_update_status (EAttachmentPaned *paned)
+{
+ EAttachmentView *view;
+ EAttachmentStore *store;
+ GtkExpander *expander;
+ GtkLabel *label;
+ guint num_attachments;
+ guint64 total_size;
+ gchar *display_size;
+ gchar *markup;
+
+ view = E_ATTACHMENT_VIEW (paned);
+ store = e_attachment_view_get_store (view);
+ expander = GTK_EXPANDER (paned->priv->expander);
+ label = GTK_LABEL (paned->priv->status_label);
+
+ num_attachments = e_attachment_store_get_num_attachments (store);
+ total_size = e_attachment_store_get_total_size (store);
+ display_size = g_format_size (total_size);
+
+ if (total_size > 0)
+ markup = g_strdup_printf (
+ "<b>%d</b> %s (%s)", num_attachments, ngettext (
+ "Attachment", "Attachments", num_attachments),
+ display_size);
+ else
+ markup = g_strdup_printf (
+ "<b>%d</b> %s", num_attachments, ngettext (
+ "Attachment", "Attachments", num_attachments));
+ gtk_label_set_markup (label, markup);
+ g_free (markup);
+
+ g_free (display_size);
+
+ if (num_attachments > 0) {
+ gtk_widget_show (paned->priv->status_icon);
+ gtk_widget_show (paned->priv->status_label);
+ gtk_expander_set_expanded (expander, TRUE);
+ } else {
+ gtk_widget_hide (paned->priv->status_icon);
+ gtk_widget_hide (paned->priv->status_label);
+ gtk_expander_set_expanded (expander, FALSE);
+ }
+}
+
+static void
+attachment_paned_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_ACTIVE_VIEW:
+ e_attachment_paned_set_active_view (
+ E_ATTACHMENT_PANED (object),
+ g_value_get_int (value));
+ return;
+
+ case PROP_DRAGGING:
+ e_attachment_view_set_dragging (
+ E_ATTACHMENT_VIEW (object),
+ g_value_get_boolean (value));
+ return;
+
+ case PROP_EDITABLE:
+ e_attachment_view_set_editable (
+ E_ATTACHMENT_VIEW (object),
+ g_value_get_boolean (value));
+ return;
+
+ case PROP_EXPANDED:
+ e_attachment_paned_set_expanded (
+ E_ATTACHMENT_PANED (object),
+ g_value_get_boolean (value));
+ return;
+
+ case PROP_RESIZE_TOPLEVEL:
+ e_attachment_paned_set_resize_toplevel (
+ E_ATTACHMENT_PANED (object),
+ g_value_get_boolean (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+attachment_paned_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_ACTIVE_VIEW:
+ g_value_set_int (
+ value,
+ e_attachment_paned_get_active_view (
+ E_ATTACHMENT_PANED (object)));
+ return;
+
+ case PROP_DRAGGING:
+ g_value_set_boolean (
+ value,
+ e_attachment_view_get_dragging (
+ E_ATTACHMENT_VIEW (object)));
+ return;
+
+ case PROP_EDITABLE:
+ g_value_set_boolean (
+ value,
+ e_attachment_view_get_editable (
+ E_ATTACHMENT_VIEW (object)));
+ return;
+
+ case PROP_EXPANDED:
+ g_value_set_boolean (
+ value,
+ e_attachment_paned_get_expanded (
+ E_ATTACHMENT_PANED (object)));
+ return;
+
+ case PROP_RESIZE_TOPLEVEL:
+ g_value_set_boolean (
+ value,
+ e_attachment_paned_get_resize_toplevel (
+ E_ATTACHMENT_PANED (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+attachment_paned_dispose (GObject *object)
+{
+ EAttachmentPanedPrivate *priv;
+
+ priv = E_ATTACHMENT_PANED_GET_PRIVATE (object);
+
+ if (priv->model != NULL) {
+ e_attachment_store_remove_all (E_ATTACHMENT_STORE (priv->model));
+ g_object_unref (priv->model);
+ priv->model = NULL;
+ }
+
+ if (priv->expander != NULL) {
+ g_object_unref (priv->expander);
+ priv->expander = NULL;
+ }
+
+ if (priv->notebook != NULL) {
+ g_object_unref (priv->notebook);
+ priv->notebook = NULL;
+ }
+
+ if (priv->combo_box != NULL) {
+ g_object_unref (priv->combo_box);
+ priv->combo_box = NULL;
+ }
+
+ if (priv->icon_view != NULL) {
+ g_object_unref (priv->icon_view);
+ priv->icon_view = NULL;
+ }
+
+ if (priv->tree_view != NULL) {
+ g_object_unref (priv->tree_view);
+ priv->tree_view = NULL;
+ }
+
+ if (priv->show_hide_label != NULL) {
+ g_object_unref (priv->show_hide_label);
+ priv->show_hide_label = NULL;
+ }
+
+ if (priv->status_icon != NULL) {
+ g_object_unref (priv->status_icon);
+ priv->status_icon = NULL;
+ }
+
+ if (priv->status_label != NULL) {
+ g_object_unref (priv->status_label);
+ priv->status_label = NULL;
+ }
+
+ if (priv->content_area != NULL) {
+ g_object_unref (priv->content_area);
+ priv->content_area = NULL;
+ }
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (e_attachment_paned_parent_class)->dispose (object);
+}
+
+static void
+attachment_paned_constructed (GObject *object)
+{
+ EAttachmentPanedPrivate *priv;
+ GSettings *settings;
+
+ priv = E_ATTACHMENT_PANED_GET_PRIVATE (object);
+
+ settings = g_settings_new ("org.gnome.evolution.shell");
+
+ /* Set up property-to-property bindings. */
+
+ g_object_bind_property (
+ object, "active-view",
+ priv->combo_box, "active",
+ G_BINDING_BIDIRECTIONAL |
+ G_BINDING_SYNC_CREATE);
+
+ g_object_bind_property (
+ object, "active-view",
+ priv->notebook, "page",
+ G_BINDING_BIDIRECTIONAL |
+ G_BINDING_SYNC_CREATE);
+
+ g_object_bind_property (
+ object, "dragging",
+ priv->icon_view, "dragging",
+ G_BINDING_BIDIRECTIONAL |
+ G_BINDING_SYNC_CREATE);
+
+ g_object_bind_property (
+ object, "dragging",
+ priv->tree_view, "dragging",
+ G_BINDING_BIDIRECTIONAL |
+ G_BINDING_SYNC_CREATE);
+
+ g_object_bind_property (
+ object, "editable",
+ priv->icon_view, "editable",
+ G_BINDING_BIDIRECTIONAL |
+ G_BINDING_SYNC_CREATE);
+
+ g_object_bind_property (
+ object, "editable",
+ priv->tree_view, "editable",
+ G_BINDING_BIDIRECTIONAL |
+ G_BINDING_SYNC_CREATE);
+
+ g_object_bind_property (
+ object, "expanded",
+ priv->expander, "expanded",
+ G_BINDING_BIDIRECTIONAL |
+ G_BINDING_SYNC_CREATE);
+
+ g_object_bind_property (
+ object, "expanded",
+ priv->combo_box, "sensitive",
+ G_BINDING_BIDIRECTIONAL |
+ G_BINDING_SYNC_CREATE);
+
+ g_object_bind_property (
+ object, "expanded",
+ priv->notebook, "visible",
+ G_BINDING_BIDIRECTIONAL |
+ G_BINDING_SYNC_CREATE);
+
+ /* Set up property-to-GSettings bindings. */
+ g_settings_bind (
+ settings, "attachment-view",
+ object, "active-view",
+ G_SETTINGS_BIND_DEFAULT);
+
+ g_object_unref (settings);
+
+ /* Chain up to parent's constructed() method. */
+ G_OBJECT_CLASS (e_attachment_paned_parent_class)->constructed (object);
+}
+
+static EAttachmentViewPrivate *
+attachment_paned_get_private (EAttachmentView *view)
+{
+ EAttachmentPanedPrivate *priv;
+
+ priv = E_ATTACHMENT_PANED_GET_PRIVATE (view);
+ view = E_ATTACHMENT_VIEW (priv->icon_view);
+
+ return e_attachment_view_get_private (view);
+}
+
+static EAttachmentStore *
+attachment_paned_get_store (EAttachmentView *view)
+{
+ EAttachmentPanedPrivate *priv;
+
+ priv = E_ATTACHMENT_PANED_GET_PRIVATE (view);
+ view = E_ATTACHMENT_VIEW (priv->icon_view);
+
+ return e_attachment_view_get_store (view);
+}
+
+static GtkTreePath *
+attachment_paned_get_path_at_pos (EAttachmentView *view,
+ gint x,
+ gint y)
+{
+ EAttachmentPanedPrivate *priv;
+
+ priv = E_ATTACHMENT_PANED_GET_PRIVATE (view);
+ view = E_ATTACHMENT_VIEW (priv->icon_view);
+
+ return e_attachment_view_get_path_at_pos (view, x, y);
+}
+
+static GList *
+attachment_paned_get_selected_paths (EAttachmentView *view)
+{
+ EAttachmentPanedPrivate *priv;
+
+ priv = E_ATTACHMENT_PANED_GET_PRIVATE (view);
+ view = E_ATTACHMENT_VIEW (priv->icon_view);
+
+ return e_attachment_view_get_selected_paths (view);
+}
+
+static gboolean
+attachment_paned_path_is_selected (EAttachmentView *view,
+ GtkTreePath *path)
+{
+ EAttachmentPanedPrivate *priv;
+
+ priv = E_ATTACHMENT_PANED_GET_PRIVATE (view);
+ view = E_ATTACHMENT_VIEW (priv->icon_view);
+
+ return e_attachment_view_path_is_selected (view, path);
+}
+
+static void
+attachment_paned_select_path (EAttachmentView *view,
+ GtkTreePath *path)
+{
+ EAttachmentPanedPrivate *priv;
+
+ priv = E_ATTACHMENT_PANED_GET_PRIVATE (view);
+ view = E_ATTACHMENT_VIEW (priv->icon_view);
+
+ e_attachment_view_select_path (view, path);
+}
+
+static void
+attachment_paned_unselect_path (EAttachmentView *view,
+ GtkTreePath *path)
+{
+ EAttachmentPanedPrivate *priv;
+
+ priv = E_ATTACHMENT_PANED_GET_PRIVATE (view);
+ view = E_ATTACHMENT_VIEW (priv->icon_view);
+
+ e_attachment_view_unselect_path (view, path);
+}
+
+static void
+attachment_paned_select_all (EAttachmentView *view)
+{
+ EAttachmentPanedPrivate *priv;
+
+ priv = E_ATTACHMENT_PANED_GET_PRIVATE (view);
+ view = E_ATTACHMENT_VIEW (priv->icon_view);
+
+ e_attachment_view_select_all (view);
+}
+
+static void
+attachment_paned_unselect_all (EAttachmentView *view)
+{
+ EAttachmentPanedPrivate *priv;
+
+ priv = E_ATTACHMENT_PANED_GET_PRIVATE (view);
+ view = E_ATTACHMENT_VIEW (priv->icon_view);
+
+ e_attachment_view_unselect_all (view);
+}
+
+static void
+attachment_paned_update_actions (EAttachmentView *view)
+{
+ EAttachmentPanedPrivate *priv;
+
+ priv = E_ATTACHMENT_PANED_GET_PRIVATE (view);
+ view = E_ATTACHMENT_VIEW (priv->icon_view);
+
+ e_attachment_view_update_actions (view);
+}
+
+static void
+e_attachment_paned_class_init (EAttachmentPanedClass *class)
+{
+ GObjectClass *object_class;
+
+ g_type_class_add_private (class, sizeof (EAttachmentPanedPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = attachment_paned_set_property;
+ object_class->get_property = attachment_paned_get_property;
+ object_class->dispose = attachment_paned_dispose;
+ object_class->constructed = attachment_paned_constructed;
+
+ g_object_class_install_property (
+ object_class,
+ PROP_ACTIVE_VIEW,
+ g_param_spec_int (
+ "active-view",
+ "Active View",
+ NULL,
+ 0,
+ NUM_VIEWS,
+ 0,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_override_property (
+ object_class, PROP_DRAGGING, "dragging");
+
+ g_object_class_override_property (
+ object_class, PROP_EDITABLE, "editable");
+
+ g_object_class_install_property (
+ object_class,
+ PROP_EXPANDED,
+ g_param_spec_boolean (
+ "expanded",
+ "Expanded",
+ NULL,
+ FALSE,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_RESIZE_TOPLEVEL,
+ g_param_spec_boolean (
+ "resize-toplevel",
+ "Resize-Toplevel",
+ NULL,
+ FALSE,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+}
+
+static void
+e_attachment_paned_init (EAttachmentPaned *paned)
+{
+ EAttachmentView *view;
+ GtkSizeGroup *size_group;
+ GtkWidget *container;
+ GtkWidget *widget;
+ GtkAction *action;
+
+ paned->priv = E_ATTACHMENT_PANED_GET_PRIVATE (paned);
+ paned->priv->model = e_attachment_store_new ();
+
+ /* Keep the expander label and combo box the same height. */
+ size_group = gtk_size_group_new (GTK_SIZE_GROUP_VERTICAL);
+
+ /* Construct the Attachment Views */
+
+ container = GTK_WIDGET (paned);
+
+ widget = gtk_notebook_new ();
+ gtk_widget_set_size_request (widget, -1, initial_height);
+ gtk_notebook_set_show_tabs (GTK_NOTEBOOK (widget), FALSE);
+ gtk_notebook_set_show_border (GTK_NOTEBOOK (widget), FALSE);
+ gtk_paned_pack2 (GTK_PANED (container), widget, FALSE, FALSE);
+ paned->priv->notebook = g_object_ref (widget);
+ gtk_widget_hide (widget);
+
+ container = paned->priv->notebook;
+
+ 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_notebook_append_page (GTK_NOTEBOOK (container), widget, NULL);
+ gtk_widget_show (widget);
+
+ container = widget;
+
+ widget = e_attachment_icon_view_new ();
+ gtk_widget_set_can_focus (widget, TRUE);
+ gtk_icon_view_set_model (GTK_ICON_VIEW (widget), paned->priv->model);
+ gtk_container_add (GTK_CONTAINER (container), widget);
+ paned->priv->icon_view = g_object_ref (widget);
+ gtk_widget_show (widget);
+
+ container = paned->priv->notebook;
+
+ 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_notebook_append_page (GTK_NOTEBOOK (container), widget, NULL);
+ gtk_widget_show (widget);
+
+ container = widget;
+
+ widget = e_attachment_tree_view_new ();
+ gtk_widget_set_can_focus (widget, TRUE);
+ gtk_tree_view_set_model (GTK_TREE_VIEW (widget), paned->priv->model);
+ gtk_container_add (GTK_CONTAINER (container), widget);
+ paned->priv->tree_view = g_object_ref (widget);
+ gtk_widget_show (widget);
+
+ /* Construct the Controls */
+
+ container = GTK_WIDGET (paned);
+
+ widget = gtk_vbox_new (FALSE, 6);
+ gtk_paned_pack1 (GTK_PANED (container), widget, TRUE, FALSE);
+ paned->priv->content_area = g_object_ref (widget);
+ gtk_widget_show (widget);
+
+ container = widget;
+
+ widget = gtk_hbox_new (FALSE, 6);
+ gtk_box_pack_end (GTK_BOX (container), widget, FALSE, FALSE, 0);
+ paned->priv->controls_container = widget;
+ gtk_widget_show (widget);
+
+ container = widget;
+
+ widget = gtk_expander_new (NULL);
+ gtk_expander_set_spacing (GTK_EXPANDER (widget), 0);
+ gtk_expander_set_label_fill (GTK_EXPANDER (widget), TRUE);
+ gtk_box_pack_start (GTK_BOX (container), widget, TRUE, TRUE, 0);
+ paned->priv->expander = g_object_ref (widget);
+ gtk_widget_show (widget);
+
+ /* The "Add Attachment" button proxies the "add" action from
+ * one of the two attachment views. Doesn't matter which. */
+ widget = gtk_button_new ();
+ view = E_ATTACHMENT_VIEW (paned->priv->icon_view);
+ action = e_attachment_view_get_action (view, "add");
+ gtk_button_set_image (GTK_BUTTON (widget), gtk_image_new ());
+ gtk_activatable_set_related_action (GTK_ACTIVATABLE (widget), action);
+ gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+ gtk_widget_show (widget);
+
+ widget = gtk_combo_box_text_new ();
+ gtk_size_group_add_widget (size_group, widget);
+ gtk_combo_box_text_append_text (
+ GTK_COMBO_BOX_TEXT (widget), _("Icon View"));
+ gtk_combo_box_text_append_text (
+ GTK_COMBO_BOX_TEXT (widget), _("List View"));
+ gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+ paned->priv->combo_box = g_object_ref (widget);
+ gtk_widget_show (widget);
+
+ container = paned->priv->expander;
+
+ widget = gtk_hbox_new (FALSE, 6);
+ gtk_size_group_add_widget (size_group, widget);
+ gtk_expander_set_label_widget (GTK_EXPANDER (container), widget);
+ gtk_widget_show (widget);
+
+ container = widget;
+
+ widget = gtk_label_new_with_mnemonic (_("Show Attachment _Bar"));
+ gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+ paned->priv->show_hide_label = g_object_ref (widget);
+ gtk_widget_show (widget);
+
+ widget = gtk_alignment_new (0.5, 0.5, 0.0, 1.0);
+ gtk_box_pack_start (GTK_BOX (container), widget, TRUE, TRUE, 0);
+ 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;
+
+ widget = gtk_image_new_from_icon_name (
+ "mail-attachment", GTK_ICON_SIZE_MENU);
+ gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+ paned->priv->status_icon = g_object_ref (widget);
+ gtk_widget_hide (widget);
+
+ widget = gtk_label_new (NULL);
+ gtk_label_set_use_markup (GTK_LABEL (widget), TRUE);
+ gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+ paned->priv->status_label = g_object_ref (widget);
+ gtk_widget_hide (widget);
+
+ g_signal_connect_swapped (
+ paned->priv->expander, "notify::expanded",
+ G_CALLBACK (attachment_paned_notify_cb), paned);
+
+ g_signal_connect_swapped (
+ paned->priv->model, "notify::num-attachments",
+ G_CALLBACK (attachment_paned_update_status), paned);
+
+ g_signal_connect_swapped (
+ paned->priv->model, "notify::total-size",
+ G_CALLBACK (attachment_paned_update_status), paned);
+
+ g_object_unref (size_group);
+}
+
+static void
+e_attachment_paned_interface_init (EAttachmentViewInterface *interface)
+{
+ interface->get_private = attachment_paned_get_private;
+ interface->get_store = attachment_paned_get_store;
+ interface->get_path_at_pos = attachment_paned_get_path_at_pos;
+ interface->get_selected_paths = attachment_paned_get_selected_paths;
+ interface->path_is_selected = attachment_paned_path_is_selected;
+ interface->select_path = attachment_paned_select_path;
+ interface->unselect_path = attachment_paned_unselect_path;
+ interface->select_all = attachment_paned_select_all;
+ interface->unselect_all = attachment_paned_unselect_all;
+ interface->update_actions = attachment_paned_update_actions;
+}
+
+GtkWidget *
+e_attachment_paned_new (void)
+{
+ return g_object_new (E_TYPE_ATTACHMENT_PANED, NULL);
+}
+
+GtkWidget *
+e_attachment_paned_get_content_area (EAttachmentPaned *paned)
+{
+ g_return_val_if_fail (E_IS_ATTACHMENT_PANED (paned), NULL);
+
+ return paned->priv->content_area;
+}
+
+gint
+e_attachment_paned_get_active_view (EAttachmentPaned *paned)
+{
+ g_return_val_if_fail (E_IS_ATTACHMENT_PANED (paned), 0);
+
+ return paned->priv->active_view;
+}
+
+void
+e_attachment_paned_set_active_view (EAttachmentPaned *paned,
+ gint active_view)
+{
+ EAttachmentView *source;
+ EAttachmentView *target;
+
+ g_return_if_fail (E_IS_ATTACHMENT_PANED (paned));
+ g_return_if_fail (active_view >= 0 && active_view < NUM_VIEWS);
+
+ if (active_view == paned->priv->active_view)
+ return;
+
+ paned->priv->active_view = active_view;
+
+ /* Synchronize the item selection of the view we're
+ * switching TO with the view we're switching FROM. */
+ if (active_view == 0) {
+ /* from tree view to icon view */
+ source = E_ATTACHMENT_VIEW (paned->priv->tree_view);
+ target = E_ATTACHMENT_VIEW (paned->priv->icon_view);
+ } else {
+ /* from icon view to tree view */
+ source = E_ATTACHMENT_VIEW (paned->priv->icon_view);
+ target = E_ATTACHMENT_VIEW (paned->priv->tree_view);
+ }
+
+ e_attachment_view_sync_selection (source, target);
+
+ g_object_notify (G_OBJECT (paned), "active-view");
+}
+
+gboolean
+e_attachment_paned_get_expanded (EAttachmentPaned *paned)
+{
+ g_return_val_if_fail (E_IS_ATTACHMENT_PANED (paned), FALSE);
+
+ return paned->priv->expanded;
+}
+
+void
+e_attachment_paned_set_expanded (EAttachmentPaned *paned,
+ gboolean expanded)
+{
+ g_return_if_fail (E_IS_ATTACHMENT_PANED (paned));
+
+ if (paned->priv->expanded == expanded)
+ return;
+
+ paned->priv->expanded = expanded;
+
+ g_object_notify (G_OBJECT (paned), "expanded");
+}
+
+gboolean
+e_attachment_paned_get_resize_toplevel (EAttachmentPaned *paned)
+{
+ g_return_val_if_fail (E_IS_ATTACHMENT_PANED (paned), FALSE);
+
+ return paned->priv->resize_toplevel;
+}
+
+void
+e_attachment_paned_set_resize_toplevel (EAttachmentPaned *paned,
+ gboolean resize_toplevel)
+{
+ g_return_if_fail (E_IS_ATTACHMENT_PANED (paned));
+
+ if (paned->priv->resize_toplevel == resize_toplevel)
+ return;
+
+ paned->priv->resize_toplevel = resize_toplevel;
+
+ g_object_notify (G_OBJECT (paned), "resize-toplevel");
+}
+
+void
+e_attachment_paned_drag_data_received (EAttachmentPaned *paned,
+ GdkDragContext *context,
+ gint x,
+ gint y,
+ GtkSelectionData *selection,
+ guint info,
+ guint time)
+{
+ g_return_if_fail (E_IS_ATTACHMENT_PANED (paned));
+
+ /* XXX Dirty hack for forwarding drop events. */
+ g_signal_emit_by_name (
+ paned->priv->icon_view, "drag-data-received",
+ context, x, y, selection, info, time);
+}
+
+GtkWidget *
+e_attachment_paned_get_controls_container (EAttachmentPaned *paned)
+{
+ return paned->priv->controls_container;
+}
+
+GtkWidget *
+e_attachment_paned_get_view_combo (EAttachmentPaned *paned)
+{
+ return paned->priv->combo_box;
+}
+
diff --git a/e-util/e-attachment-paned.h b/e-util/e-attachment-paned.h
new file mode 100644
index 0000000000..af44cd6d67
--- /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 0000000000..f434f5e81c
--- /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 0000000000..a112b0e56c
--- /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/e-util/e-attachment-tree-view.c b/e-util/e-attachment-tree-view.c
new file mode 100644
index 0000000000..b73751fbbd
--- /dev/null
+++ b/e-util/e-attachment-tree-view.c
@@ -0,0 +1,623 @@
+/*
+ * e-attachment-tree-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-tree-view.h"
+
+#include <glib/gi18n.h>
+#include <libebackend/libebackend.h>
+
+#include "e-attachment.h"
+#include "e-attachment-store.h"
+#include "e-attachment-view.h"
+
+#define E_ATTACHMENT_TREE_VIEW_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_ATTACHMENT_TREE_VIEW, EAttachmentTreeViewPrivate))
+
+struct _EAttachmentTreeViewPrivate {
+ EAttachmentViewPrivate view_priv;
+};
+
+enum {
+ PROP_0,
+ PROP_DRAGGING,
+ PROP_EDITABLE
+};
+
+/* Forward Declarations */
+static void e_attachment_tree_view_interface_init
+ (EAttachmentViewInterface *interface);
+
+G_DEFINE_TYPE_WITH_CODE (
+ EAttachmentTreeView,
+ e_attachment_tree_view,
+ GTK_TYPE_TREE_VIEW,
+ G_IMPLEMENT_INTERFACE (
+ E_TYPE_ATTACHMENT_VIEW,
+ e_attachment_tree_view_interface_init)
+ G_IMPLEMENT_INTERFACE (
+ E_TYPE_EXTENSIBLE, NULL))
+
+static void
+attachment_tree_view_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_DRAGGING:
+ e_attachment_view_set_dragging (
+ E_ATTACHMENT_VIEW (object),
+ g_value_get_boolean (value));
+ return;
+
+ case PROP_EDITABLE:
+ e_attachment_view_set_editable (
+ E_ATTACHMENT_VIEW (object),
+ g_value_get_boolean (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+attachment_tree_view_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_DRAGGING:
+ g_value_set_boolean (
+ value, e_attachment_view_get_dragging (
+ E_ATTACHMENT_VIEW (object)));
+ return;
+
+ case PROP_EDITABLE:
+ g_value_set_boolean (
+ value, e_attachment_view_get_editable (
+ E_ATTACHMENT_VIEW (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+attachment_tree_view_dispose (GObject *object)
+{
+ e_attachment_view_dispose (E_ATTACHMENT_VIEW (object));
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (e_attachment_tree_view_parent_class)->dispose (object);
+}
+
+static void
+attachment_tree_view_finalize (GObject *object)
+{
+ e_attachment_view_finalize (E_ATTACHMENT_VIEW (object));
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (e_attachment_tree_view_parent_class)->finalize (object);
+}
+
+static void
+attachment_tree_view_render_size (GtkTreeViewColumn *column,
+ GtkCellRenderer *renderer,
+ GtkTreeModel *model,
+ GtkTreeIter *iter)
+{
+ gchar *display_size = NULL;
+ gint column_id;
+ guint64 size;
+
+ column_id = E_ATTACHMENT_STORE_COLUMN_SIZE;
+ gtk_tree_model_get (model, iter, column_id, &size, -1);
+
+ if (size > 0)
+ display_size = g_format_size ((goffset) size);
+
+ g_object_set (renderer, "text", display_size, NULL);
+
+ g_free (display_size);
+}
+
+static gboolean
+attachment_tree_view_button_press_event (GtkWidget *widget,
+ GdkEventButton *event)
+{
+ EAttachmentView *view = E_ATTACHMENT_VIEW (widget);
+
+ if (e_attachment_view_button_press_event (view, event))
+ return TRUE;
+
+ /* Chain up to parent's button_press_event() method. */
+ return GTK_WIDGET_CLASS (e_attachment_tree_view_parent_class)->
+ button_press_event (widget, event);
+}
+
+static gboolean
+attachment_tree_view_button_release_event (GtkWidget *widget,
+ GdkEventButton *event)
+{
+ EAttachmentView *view = E_ATTACHMENT_VIEW (widget);
+
+ if (e_attachment_view_button_release_event (view, event))
+ return TRUE;
+
+ /* Chain up to parent's button_release_event() method. */
+ return GTK_WIDGET_CLASS (e_attachment_tree_view_parent_class)->
+ button_release_event (widget, event);
+}
+
+static gboolean
+attachment_tree_view_motion_notify_event (GtkWidget *widget,
+ GdkEventMotion *event)
+{
+ EAttachmentView *view = E_ATTACHMENT_VIEW (widget);
+
+ if (e_attachment_view_motion_notify_event (view, event))
+ return TRUE;
+
+ /* Chain up to parent's motion_notify_event() method. */
+ return GTK_WIDGET_CLASS (e_attachment_tree_view_parent_class)->
+ motion_notify_event (widget, event);
+}
+
+static gboolean
+attachment_tree_view_key_press_event (GtkWidget *widget,
+ GdkEventKey *event)
+{
+ EAttachmentView *view = E_ATTACHMENT_VIEW (widget);
+
+ if (e_attachment_view_key_press_event (view, event))
+ return TRUE;
+
+ /* Chain up to parent's key_press_event() method. */
+ return GTK_WIDGET_CLASS (e_attachment_tree_view_parent_class)->
+ key_press_event (widget, event);
+}
+
+static void
+attachment_tree_view_drag_begin (GtkWidget *widget,
+ GdkDragContext *context)
+{
+ EAttachmentView *view = E_ATTACHMENT_VIEW (widget);
+
+ /* Chain up to parent's drag_begin() method. */
+ GTK_WIDGET_CLASS (e_attachment_tree_view_parent_class)->
+ drag_begin (widget, context);
+
+ e_attachment_view_drag_begin (view, context);
+}
+
+static void
+attachment_tree_view_drag_end (GtkWidget *widget,
+ GdkDragContext *context)
+{
+ EAttachmentView *view = E_ATTACHMENT_VIEW (widget);
+
+ /* Chain up to parent's drag_end() method. */
+ GTK_WIDGET_CLASS (e_attachment_tree_view_parent_class)->
+ drag_end (widget, context);
+
+ e_attachment_view_drag_end (view, context);
+}
+
+static void
+attachment_tree_view_drag_data_get (GtkWidget *widget,
+ GdkDragContext *context,
+ GtkSelectionData *selection,
+ guint info,
+ guint time)
+{
+ EAttachmentView *view = E_ATTACHMENT_VIEW (widget);
+
+ e_attachment_view_drag_data_get (
+ view, context, selection, info, time);
+}
+
+static gboolean
+attachment_tree_view_drag_motion (GtkWidget *widget,
+ GdkDragContext *context,
+ gint x,
+ gint y,
+ guint time)
+{
+ EAttachmentView *view = E_ATTACHMENT_VIEW (widget);
+
+ return e_attachment_view_drag_motion (view, context, x, y, time);
+}
+
+static gboolean
+attachment_tree_view_drag_drop (GtkWidget *widget,
+ GdkDragContext *context,
+ gint x,
+ gint y,
+ guint time)
+{
+ EAttachmentView *view = E_ATTACHMENT_VIEW (widget);
+
+ if (!e_attachment_view_drag_drop (view, context, x, y, time))
+ return FALSE;
+
+ /* Chain up to parent's drag_drop() method. */
+ return GTK_WIDGET_CLASS (e_attachment_tree_view_parent_class)->
+ drag_drop (widget, context, x, y, time);
+}
+
+static void
+attachment_tree_view_drag_data_received (GtkWidget *widget,
+ GdkDragContext *context,
+ gint x,
+ gint y,
+ GtkSelectionData *selection,
+ guint info,
+ guint time)
+{
+ EAttachmentView *view = E_ATTACHMENT_VIEW (widget);
+
+ e_attachment_view_drag_data_received (
+ view, context, x, y, selection, info, time);
+}
+
+static gboolean
+attachment_tree_view_popup_menu (GtkWidget *widget)
+{
+ EAttachmentView *view = E_ATTACHMENT_VIEW (widget);
+
+ e_attachment_view_show_popup_menu (view, NULL, NULL, NULL);
+
+ return TRUE;
+}
+
+static void
+attachment_tree_view_row_activated (GtkTreeView *tree_view,
+ GtkTreePath *path,
+ GtkTreeViewColumn *column)
+{
+ EAttachmentView *view = E_ATTACHMENT_VIEW (tree_view);
+
+ e_attachment_view_open_path (view, path, NULL);
+}
+
+static EAttachmentViewPrivate *
+attachment_tree_view_get_private (EAttachmentView *view)
+{
+ EAttachmentTreeViewPrivate *priv;
+
+ priv = E_ATTACHMENT_TREE_VIEW_GET_PRIVATE (view);
+
+ return &priv->view_priv;
+}
+
+static EAttachmentStore *
+attachment_tree_view_get_store (EAttachmentView *view)
+{
+ GtkTreeView *tree_view;
+ GtkTreeModel *model;
+
+ tree_view = GTK_TREE_VIEW (view);
+ model = gtk_tree_view_get_model (tree_view);
+
+ return E_ATTACHMENT_STORE (model);
+}
+
+static GtkTreePath *
+attachment_tree_view_get_path_at_pos (EAttachmentView *view,
+ gint x,
+ gint y)
+{
+ GtkTreeView *tree_view;
+ GtkTreePath *path;
+ gboolean row_exists;
+
+ tree_view = GTK_TREE_VIEW (view);
+
+ row_exists = gtk_tree_view_get_path_at_pos (
+ tree_view, x, y, &path, NULL, NULL, NULL);
+
+ return row_exists ? path : NULL;
+}
+
+static GList *
+attachment_tree_view_get_selected_paths (EAttachmentView *view)
+{
+ GtkTreeView *tree_view;
+ GtkTreeSelection *selection;
+
+ tree_view = GTK_TREE_VIEW (view);
+ selection = gtk_tree_view_get_selection (tree_view);
+
+ return gtk_tree_selection_get_selected_rows (selection, NULL);
+}
+
+static gboolean
+attachment_tree_view_path_is_selected (EAttachmentView *view,
+ GtkTreePath *path)
+{
+ GtkTreeView *tree_view;
+ GtkTreeSelection *selection;
+
+ tree_view = GTK_TREE_VIEW (view);
+ selection = gtk_tree_view_get_selection (tree_view);
+
+ return gtk_tree_selection_path_is_selected (selection, path);
+}
+
+static void
+attachment_tree_view_select_path (EAttachmentView *view,
+ GtkTreePath *path)
+{
+ GtkTreeView *tree_view;
+ GtkTreeSelection *selection;
+
+ tree_view = GTK_TREE_VIEW (view);
+ selection = gtk_tree_view_get_selection (tree_view);
+
+ gtk_tree_selection_select_path (selection, path);
+}
+
+static void
+attachment_tree_view_unselect_path (EAttachmentView *view,
+ GtkTreePath *path)
+{
+ GtkTreeView *tree_view;
+ GtkTreeSelection *selection;
+
+ tree_view = GTK_TREE_VIEW (view);
+ selection = gtk_tree_view_get_selection (tree_view);
+
+ gtk_tree_selection_unselect_path (selection, path);
+}
+
+static void
+attachment_tree_view_select_all (EAttachmentView *view)
+{
+ GtkTreeView *tree_view;
+ GtkTreeSelection *selection;
+
+ tree_view = GTK_TREE_VIEW (view);
+ selection = gtk_tree_view_get_selection (tree_view);
+
+ gtk_tree_selection_select_all (selection);
+}
+
+static void
+attachment_tree_view_unselect_all (EAttachmentView *view)
+{
+ GtkTreeView *tree_view;
+ GtkTreeSelection *selection;
+
+ tree_view = GTK_TREE_VIEW (view);
+ selection = gtk_tree_view_get_selection (tree_view);
+
+ gtk_tree_selection_unselect_all (selection);
+}
+
+static void
+attachment_tree_view_drag_source_set (EAttachmentView *view,
+ GdkModifierType start_button_mask,
+ const GtkTargetEntry *targets,
+ gint n_targets,
+ GdkDragAction actions)
+{
+ GtkTreeView *tree_view;
+
+ tree_view = GTK_TREE_VIEW (view);
+
+ gtk_tree_view_enable_model_drag_source (
+ tree_view, start_button_mask, targets, n_targets, actions);
+}
+
+static void
+attachment_tree_view_drag_dest_set (EAttachmentView *view,
+ const GtkTargetEntry *targets,
+ gint n_targets,
+ GdkDragAction actions)
+{
+ GtkTreeView *tree_view;
+
+ tree_view = GTK_TREE_VIEW (view);
+
+ gtk_tree_view_enable_model_drag_dest (
+ tree_view, targets, n_targets, actions);
+}
+
+static void
+attachment_tree_view_drag_source_unset (EAttachmentView *view)
+{
+ GtkTreeView *tree_view;
+
+ tree_view = GTK_TREE_VIEW (view);
+
+ gtk_tree_view_unset_rows_drag_source (tree_view);
+}
+
+static void
+attachment_tree_view_drag_dest_unset (EAttachmentView *view)
+{
+ GtkTreeView *tree_view;
+
+ tree_view = GTK_TREE_VIEW (view);
+
+ gtk_tree_view_unset_rows_drag_dest (tree_view);
+}
+
+static void
+e_attachment_tree_view_class_init (EAttachmentTreeViewClass *class)
+{
+ GObjectClass *object_class;
+ GtkWidgetClass *widget_class;
+ GtkTreeViewClass *tree_view_class;
+
+ g_type_class_add_private (class, sizeof (EAttachmentViewPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = attachment_tree_view_set_property;
+ object_class->get_property = attachment_tree_view_get_property;
+ object_class->dispose = attachment_tree_view_dispose;
+ object_class->finalize = attachment_tree_view_finalize;
+
+ widget_class = GTK_WIDGET_CLASS (class);
+ widget_class->button_press_event = attachment_tree_view_button_press_event;
+ widget_class->button_release_event = attachment_tree_view_button_release_event;
+ widget_class->motion_notify_event = attachment_tree_view_motion_notify_event;
+ widget_class->key_press_event = attachment_tree_view_key_press_event;
+ widget_class->drag_begin = attachment_tree_view_drag_begin;
+ widget_class->drag_end = attachment_tree_view_drag_end;
+ widget_class->drag_data_get = attachment_tree_view_drag_data_get;
+ widget_class->drag_motion = attachment_tree_view_drag_motion;
+ widget_class->drag_drop = attachment_tree_view_drag_drop;
+ widget_class->drag_data_received = attachment_tree_view_drag_data_received;
+ widget_class->popup_menu = attachment_tree_view_popup_menu;
+
+ tree_view_class = GTK_TREE_VIEW_CLASS (class);
+ tree_view_class->row_activated = attachment_tree_view_row_activated;
+
+ g_object_class_override_property (
+ object_class, PROP_DRAGGING, "dragging");
+
+ g_object_class_override_property (
+ object_class, PROP_EDITABLE, "editable");
+}
+
+static void
+e_attachment_tree_view_init (EAttachmentTreeView *tree_view)
+{
+ GtkTreeSelection *selection;
+ GtkTreeViewColumn *column;
+ GtkCellRenderer *renderer;
+
+ tree_view->priv = E_ATTACHMENT_TREE_VIEW_GET_PRIVATE (tree_view);
+
+ e_attachment_view_init (E_ATTACHMENT_VIEW (tree_view));
+
+ gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (tree_view), TRUE);
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree_view));
+ gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE);
+
+ /* Name Column */
+ column = gtk_tree_view_column_new ();
+ gtk_tree_view_column_set_expand (column, TRUE);
+ gtk_tree_view_column_set_spacing (column, 3);
+ gtk_tree_view_column_set_title (column, _("Description"));
+ gtk_tree_view_append_column (GTK_TREE_VIEW (tree_view), column);
+
+ renderer = gtk_cell_renderer_pixbuf_new ();
+ gtk_tree_view_column_pack_start (column, renderer, FALSE);
+
+ g_object_set (renderer, "stock-size", GTK_ICON_SIZE_MENU, NULL);
+
+ gtk_tree_view_column_add_attribute (
+ column, renderer, "gicon",
+ E_ATTACHMENT_STORE_COLUMN_ICON);
+
+ renderer = gtk_cell_renderer_text_new ();
+ g_object_set (renderer, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
+ gtk_tree_view_column_pack_start (column, renderer, TRUE);
+
+ gtk_tree_view_column_add_attribute (
+ column, renderer, "text",
+ E_ATTACHMENT_STORE_COLUMN_DESCRIPTION);
+
+ renderer = gtk_cell_renderer_progress_new ();
+ g_object_set (renderer, "text", _("Loading"), NULL);
+ gtk_tree_view_column_pack_start (column, renderer, TRUE);
+
+ gtk_tree_view_column_add_attribute (
+ column, renderer, "value",
+ E_ATTACHMENT_STORE_COLUMN_PERCENT);
+
+ gtk_tree_view_column_add_attribute (
+ column, renderer, "visible",
+ E_ATTACHMENT_STORE_COLUMN_LOADING);
+
+ renderer = gtk_cell_renderer_progress_new ();
+ g_object_set (renderer, "text", _("Saving"), NULL);
+ gtk_tree_view_column_pack_start (column, renderer, TRUE);
+
+ gtk_tree_view_column_add_attribute (
+ column, renderer, "value",
+ E_ATTACHMENT_STORE_COLUMN_PERCENT);
+
+ gtk_tree_view_column_add_attribute (
+ column, renderer, "visible",
+ E_ATTACHMENT_STORE_COLUMN_SAVING);
+
+ /* Size Column */
+ column = gtk_tree_view_column_new ();
+ gtk_tree_view_column_set_title (column, _("Size"));
+ gtk_tree_view_append_column (GTK_TREE_VIEW (tree_view), column);
+
+ renderer = gtk_cell_renderer_text_new ();
+ gtk_tree_view_column_pack_start (column, renderer, TRUE);
+
+ gtk_tree_view_column_set_cell_data_func (
+ column, renderer, (GtkTreeCellDataFunc)
+ attachment_tree_view_render_size, NULL, NULL);
+
+ /* Type Column */
+ column = gtk_tree_view_column_new ();
+ gtk_tree_view_column_set_title (column, _("Type"));
+ gtk_tree_view_append_column (GTK_TREE_VIEW (tree_view), 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",
+ E_ATTACHMENT_STORE_COLUMN_CONTENT_TYPE);
+
+ e_extensible_load_extensions (E_EXTENSIBLE (tree_view));
+}
+
+static void
+e_attachment_tree_view_interface_init (EAttachmentViewInterface *interface)
+{
+ interface->get_private = attachment_tree_view_get_private;
+ interface->get_store = attachment_tree_view_get_store;
+
+ interface->get_path_at_pos = attachment_tree_view_get_path_at_pos;
+ interface->get_selected_paths = attachment_tree_view_get_selected_paths;
+ interface->path_is_selected = attachment_tree_view_path_is_selected;
+ interface->select_path = attachment_tree_view_select_path;
+ interface->unselect_path = attachment_tree_view_unselect_path;
+ interface->select_all = attachment_tree_view_select_all;
+ interface->unselect_all = attachment_tree_view_unselect_all;
+
+ interface->drag_source_set = attachment_tree_view_drag_source_set;
+ interface->drag_dest_set = attachment_tree_view_drag_dest_set;
+ interface->drag_source_unset = attachment_tree_view_drag_source_unset;
+ interface->drag_dest_unset = attachment_tree_view_drag_dest_unset;
+}
+
+GtkWidget *
+e_attachment_tree_view_new (void)
+{
+ return g_object_new (E_TYPE_ATTACHMENT_TREE_VIEW, NULL);
+}
diff --git a/e-util/e-attachment-tree-view.h b/e-util/e-attachment-tree-view.h
new file mode 100644
index 0000000000..416a09b7f6
--- /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 0000000000..e468c14120
--- /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 0000000000..174181541a
--- /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 0000000000..3357775111
--- /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 0000000000..268bcf68b9
--- /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/e-util/e-auth-combo-box.c b/e-util/e-auth-combo-box.c
new file mode 100644
index 0000000000..bd3d8c78ea
--- /dev/null
+++ b/e-util/e-auth-combo-box.c
@@ -0,0 +1,266 @@
+/*
+ * e-auth-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/>
+ *
+ */
+
+#include "e-auth-combo-box.h"
+
+#define E_AUTH_COMBO_BOX_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_AUTH_COMBO_BOX, EAuthComboBoxPrivate))
+
+struct _EAuthComboBoxPrivate {
+ CamelProvider *provider;
+};
+
+enum {
+ PROP_0,
+ PROP_PROVIDER
+};
+
+enum {
+ COLUMN_MECHANISM,
+ COLUMN_DISPLAY_NAME,
+ COLUMN_STRIKETHROUGH,
+ COLUMN_AUTHTYPE,
+ NUM_COLUMNS
+};
+
+G_DEFINE_TYPE (
+ EAuthComboBox,
+ e_auth_combo_box,
+ GTK_TYPE_COMBO_BOX)
+
+static void
+auth_combo_box_rebuild_model (EAuthComboBox *combo_box)
+{
+ GtkComboBox *gtk_combo_box;
+ CamelProvider *provider;
+ GtkTreeModel *model;
+ GList *link;
+ const gchar *active_id;
+
+ provider = e_auth_combo_box_get_provider (combo_box);
+
+ gtk_combo_box = GTK_COMBO_BOX (combo_box);
+ model = gtk_combo_box_get_model (gtk_combo_box);
+ active_id = gtk_combo_box_get_active_id (gtk_combo_box);
+
+ gtk_list_store_clear (GTK_LIST_STORE (model));
+
+ if (provider == NULL)
+ return;
+
+ for (link = provider->authtypes; link != NULL; link = link->next) {
+ CamelServiceAuthType *authtype = link->data;
+ GtkTreeIter iter;
+
+ gtk_list_store_append (GTK_LIST_STORE (model), &iter);
+
+ gtk_list_store_set (
+ GTK_LIST_STORE (model), &iter,
+ COLUMN_MECHANISM, authtype->authproto,
+ COLUMN_DISPLAY_NAME, authtype->name,
+ COLUMN_AUTHTYPE, authtype,
+ -1);
+ }
+
+ /* Try selecting the previous mechanism. */
+ if (active_id != NULL)
+ gtk_combo_box_set_active_id (gtk_combo_box, active_id);
+
+ /* Or else fall back to the first mechanism. */
+ if (gtk_combo_box_get_active (gtk_combo_box) == -1)
+ gtk_combo_box_set_active (gtk_combo_box, 0);
+}
+
+static void
+auth_combo_box_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_PROVIDER:
+ e_auth_combo_box_set_provider (
+ E_AUTH_COMBO_BOX (object),
+ g_value_get_pointer (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+auth_combo_box_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_PROVIDER:
+ g_value_set_pointer (
+ value,
+ e_auth_combo_box_get_provider (
+ E_AUTH_COMBO_BOX (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+auth_combo_box_constructed (GObject *object)
+{
+ GtkComboBox *combo_box;
+ GtkListStore *list_store;
+ GtkCellLayout *cell_layout;
+ GtkCellRenderer *cell_renderer;
+
+ /* Chain up to parent's constructed() method. */
+ G_OBJECT_CLASS (e_auth_combo_box_parent_class)->constructed (object);
+
+ list_store = gtk_list_store_new (
+ NUM_COLUMNS,
+ G_TYPE_STRING, /* COLUMN_MECHANISM */
+ G_TYPE_STRING, /* COLUMN_DISPLAY_NAME */
+ G_TYPE_BOOLEAN, /* COLUMN_STRIKETHROUGH */
+ G_TYPE_POINTER); /* COLUMN_AUTHTYPE */
+
+ combo_box = GTK_COMBO_BOX (object);
+ gtk_combo_box_set_model (combo_box, GTK_TREE_MODEL (list_store));
+ gtk_combo_box_set_id_column (combo_box, COLUMN_MECHANISM);
+ g_object_unref (list_store);
+
+ cell_layout = GTK_CELL_LAYOUT (object);
+ cell_renderer = gtk_cell_renderer_text_new ();
+ gtk_cell_layout_pack_start (cell_layout, cell_renderer, TRUE);
+
+ gtk_cell_layout_set_attributes (
+ cell_layout, cell_renderer,
+ "text", COLUMN_DISPLAY_NAME,
+ "strikethrough", COLUMN_STRIKETHROUGH,
+ NULL);
+}
+
+static void
+e_auth_combo_box_class_init (EAuthComboBoxClass *class)
+{
+ GObjectClass *object_class;
+
+ g_type_class_add_private (class, sizeof (EAuthComboBoxPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = auth_combo_box_set_property;
+ object_class->get_property = auth_combo_box_get_property;
+ object_class->constructed = auth_combo_box_constructed;
+
+ g_object_class_install_property (
+ object_class,
+ PROP_PROVIDER,
+ g_param_spec_pointer (
+ "provider",
+ "Provider",
+ "The provider to query for auth mechanisms",
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+}
+
+static void
+e_auth_combo_box_init (EAuthComboBox *combo_box)
+{
+ combo_box->priv = E_AUTH_COMBO_BOX_GET_PRIVATE (combo_box);
+}
+
+GtkWidget *
+e_auth_combo_box_new (void)
+{
+ return g_object_new (E_TYPE_AUTH_COMBO_BOX, NULL);
+}
+
+CamelProvider *
+e_auth_combo_box_get_provider (EAuthComboBox *combo_box)
+{
+ g_return_val_if_fail (E_IS_AUTH_COMBO_BOX (combo_box), NULL);
+
+ return combo_box->priv->provider;
+}
+
+void
+e_auth_combo_box_set_provider (EAuthComboBox *combo_box,
+ CamelProvider *provider)
+{
+ g_return_if_fail (E_IS_AUTH_COMBO_BOX (combo_box));
+
+ if (provider == combo_box->priv->provider)
+ return;
+
+ combo_box->priv->provider = provider;
+
+ g_object_notify (G_OBJECT (combo_box), "provider");
+
+ auth_combo_box_rebuild_model (combo_box);
+}
+
+void
+e_auth_combo_box_update_available (EAuthComboBox *combo_box,
+ GList *available_authtypes)
+{
+ GtkComboBox *gtk_combo_box;
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ gint active_index;
+ gint available_index = -1;
+ gint index = 0;
+ gboolean iter_set;
+
+ g_return_if_fail (E_IS_AUTH_COMBO_BOX (combo_box));
+
+ gtk_combo_box = GTK_COMBO_BOX (combo_box);
+ model = gtk_combo_box_get_model (gtk_combo_box);
+ active_index = gtk_combo_box_get_active (gtk_combo_box);
+
+ iter_set = gtk_tree_model_get_iter_first (model, &iter);
+
+ while (iter_set) {
+ CamelServiceAuthType *authtype;
+ gboolean available;
+
+ gtk_tree_model_get (
+ model, &iter, COLUMN_AUTHTYPE, &authtype, -1);
+
+ available = (g_list_find (
+ available_authtypes, authtype) != NULL);
+
+ gtk_list_store_set (
+ GTK_LIST_STORE (model), &iter,
+ COLUMN_STRIKETHROUGH, !available, -1);
+
+ if (index == active_index && !available)
+ active_index = -1;
+
+ if (available && available_index == -1)
+ available_index = index;
+
+ iter_set = gtk_tree_model_iter_next (model, &iter);
+ index++;
+ }
+
+ /* If the active combo_box item turned out to be unavailable
+ * (or there was no active item), select the first available. */
+ if (active_index == -1 && available_index != -1)
+ gtk_combo_box_set_active (gtk_combo_box, available_index);
+}
diff --git a/e-util/e-auth-combo-box.h b/e-util/e-auth-combo-box.h
new file mode 100644
index 0000000000..a8ec3f9bf6
--- /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/e-util/e-autocomplete-selector.c b/e-util/e-autocomplete-selector.c
new file mode 100644
index 0000000000..c0bf207bbd
--- /dev/null
+++ b/e-util/e-autocomplete-selector.c
@@ -0,0 +1,96 @@
+/*
+ * e-autocomplete-selector.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-autocomplete-selector.h"
+
+G_DEFINE_TYPE (
+ EAutocompleteSelector,
+ e_autocomplete_selector,
+ E_TYPE_SOURCE_SELECTOR)
+
+static gboolean
+autocomplete_selector_get_source_selected (ESourceSelector *selector,
+ ESource *source)
+{
+ ESourceAutocomplete *extension;
+ const gchar *extension_name;
+
+ /* Make sure this source is an address book. */
+ extension_name = e_source_selector_get_extension_name (selector);
+ if (!e_source_has_extension (source, extension_name))
+ return FALSE;
+
+ extension_name = E_SOURCE_EXTENSION_AUTOCOMPLETE;
+ extension = e_source_get_extension (source, extension_name);
+ g_return_val_if_fail (E_IS_SOURCE_AUTOCOMPLETE (extension), FALSE);
+
+ return e_source_autocomplete_get_include_me (extension);
+}
+
+static void
+autocomplete_selector_set_source_selected (ESourceSelector *selector,
+ ESource *source,
+ gboolean selected)
+{
+ ESourceAutocomplete *extension;
+ const gchar *extension_name;
+
+ /* Make sure this source is an address book. */
+ extension_name = e_source_selector_get_extension_name (selector);
+ if (!e_source_has_extension (source, extension_name))
+ return;
+
+ extension_name = E_SOURCE_EXTENSION_AUTOCOMPLETE;
+ extension = e_source_get_extension (source, extension_name);
+ g_return_if_fail (E_IS_SOURCE_AUTOCOMPLETE (extension));
+
+ if (selected != e_source_autocomplete_get_include_me (extension)) {
+ e_source_autocomplete_set_include_me (extension, selected);
+ e_source_selector_queue_write (selector, source);
+ }
+}
+
+static void
+e_autocomplete_selector_class_init (EAutocompleteSelectorClass *class)
+{
+ ESourceSelectorClass *source_selector_class;
+
+ source_selector_class = E_SOURCE_SELECTOR_CLASS (class);
+ source_selector_class->get_source_selected =
+ autocomplete_selector_get_source_selected;
+ source_selector_class->set_source_selected =
+ autocomplete_selector_set_source_selected;
+}
+
+static void
+e_autocomplete_selector_init (EAutocompleteSelector *selector)
+{
+ e_source_selector_set_show_colors (
+ E_SOURCE_SELECTOR (selector), FALSE);
+}
+
+GtkWidget *
+e_autocomplete_selector_new (ESourceRegistry *registry)
+{
+ g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), NULL);
+
+ return g_object_new (
+ E_TYPE_AUTOCOMPLETE_SELECTOR,
+ "extension-name", E_SOURCE_EXTENSION_ADDRESS_BOOK,
+ "registry", registry, NULL);
+}
diff --git a/e-util/e-autocomplete-selector.h b/e-util/e-autocomplete-selector.h
new file mode 100644
index 0000000000..af17f21828
--- /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 456a4d495d..b17f8a089b 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 717fe585e7..39b55d906c 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/e-util/e-book-source-config.c b/e-util/e-book-source-config.c
new file mode 100644
index 0000000000..56d9771d9f
--- /dev/null
+++ b/e-util/e-book-source-config.c
@@ -0,0 +1,287 @@
+/*
+ * e-book-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-book-source-config.h"
+
+#include <config.h>
+#include <glib/gi18n-lib.h>
+
+#define E_BOOK_SOURCE_CONFIG_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_BOOK_SOURCE_CONFIG, EBookSourceConfigPrivate))
+
+struct _EBookSourceConfigPrivate {
+ GtkWidget *default_button;
+ GtkWidget *autocomplete_button;
+};
+
+G_DEFINE_TYPE (
+ EBookSourceConfig,
+ e_book_source_config,
+ E_TYPE_SOURCE_CONFIG)
+
+static ESource *
+book_source_config_ref_default (ESourceConfig *config)
+{
+ ESourceRegistry *registry;
+
+ registry = e_source_config_get_registry (config);
+
+ return e_source_registry_ref_default_address_book (registry);
+}
+
+static void
+book_source_config_set_default (ESourceConfig *config,
+ ESource *source)
+{
+ ESourceRegistry *registry;
+
+ registry = e_source_config_get_registry (config);
+
+ e_source_registry_set_default_address_book (registry, source);
+}
+
+static void
+book_source_config_dispose (GObject *object)
+{
+ EBookSourceConfigPrivate *priv;
+
+ priv = E_BOOK_SOURCE_CONFIG_GET_PRIVATE (object);
+
+ if (priv->default_button != NULL) {
+ g_object_unref (priv->default_button);
+ priv->default_button = NULL;
+ }
+
+ if (priv->autocomplete_button != NULL) {
+ g_object_unref (priv->autocomplete_button);
+ priv->autocomplete_button = NULL;
+ }
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (e_book_source_config_parent_class)->dispose (object);
+}
+
+static void
+book_source_config_constructed (GObject *object)
+{
+ EBookSourceConfigPrivate *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_book_source_config_parent_class);
+ class->constructed (object);
+
+ config = E_SOURCE_CONFIG (object);
+ priv = E_BOOK_SOURCE_CONFIG_GET_PRIVATE (object);
+
+ label = _("Mark as default address book");
+ widget = gtk_check_button_new_with_label (label);
+ priv->default_button = g_object_ref_sink (widget);
+ gtk_widget_show (widget);
+
+ label = _("Autocomplete with this address book");
+ widget = gtk_check_button_new_with_label (label);
+ priv->autocomplete_button = g_object_ref_sink (widget);
+ gtk_widget_show (widget);
+
+ default_source = book_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, NULL, priv->default_button);
+
+ e_source_config_insert_widget (
+ config, NULL, NULL, priv->autocomplete_button);
+}
+
+static const gchar *
+book_source_config_get_backend_extension_name (ESourceConfig *config)
+{
+ return E_SOURCE_EXTENSION_ADDRESS_BOOK;
+}
+
+static GList *
+book_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_book_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_contacts_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
+book_source_config_init_candidate (ESourceConfig *config,
+ ESource *scratch_source)
+{
+ EBookSourceConfigPrivate *priv;
+ ESourceConfigClass *class;
+ ESourceExtension *extension;
+ const gchar *extension_name;
+
+ /* Chain up to parent's init_candidate() method. */
+ class = E_SOURCE_CONFIG_CLASS (e_book_source_config_parent_class);
+ class->init_candidate (config, scratch_source);
+
+ priv = E_BOOK_SOURCE_CONFIG_GET_PRIVATE (config);
+
+ extension_name = E_SOURCE_EXTENSION_AUTOCOMPLETE;
+ extension = e_source_get_extension (scratch_source, extension_name);
+
+ g_object_bind_property (
+ extension, "include-me",
+ priv->autocomplete_button, "active",
+ G_BINDING_BIDIRECTIONAL |
+ G_BINDING_SYNC_CREATE);
+}
+
+static void
+book_source_config_commit_changes (ESourceConfig *config,
+ ESource *scratch_source)
+{
+ EBookSourceConfigPrivate *priv;
+ ESourceConfigClass *class;
+ ESource *default_source;
+ GtkToggleButton *toggle_button;
+
+ priv = E_BOOK_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_book_source_config_parent_class);
+ class->commit_changes (config, scratch_source);
+
+ default_source = book_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))
+ book_source_config_set_default (config, scratch_source);
+ else if (e_source_equal (scratch_source, default_source))
+ book_source_config_set_default (config, NULL);
+
+ g_object_unref (default_source);
+}
+
+static void
+e_book_source_config_class_init (EBookSourceConfigClass *class)
+{
+ GObjectClass *object_class;
+ ESourceConfigClass *source_config_class;
+
+ g_type_class_add_private (class, sizeof (EBookSourceConfigPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->dispose = book_source_config_dispose;
+ object_class->constructed = book_source_config_constructed;
+
+ source_config_class = E_SOURCE_CONFIG_CLASS (class);
+ source_config_class->get_backend_extension_name =
+ book_source_config_get_backend_extension_name;
+ source_config_class->list_eligible_collections =
+ book_source_config_list_eligible_collections;
+ source_config_class->init_candidate = book_source_config_init_candidate;
+ source_config_class->commit_changes = book_source_config_commit_changes;
+}
+
+static void
+e_book_source_config_init (EBookSourceConfig *config)
+{
+ config->priv = E_BOOK_SOURCE_CONFIG_GET_PRIVATE (config);
+}
+
+GtkWidget *
+e_book_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_BOOK_SOURCE_CONFIG, "registry", registry,
+ "original-source", original_source, NULL);
+}
+
+void
+e_book_source_config_add_offline_toggle (EBookSourceConfig *config,
+ ESource *scratch_source)
+{
+ GtkWidget *widget;
+ ESourceExtension *extension;
+ const gchar *extension_name;
+
+ g_return_if_fail (E_IS_BOOK_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);
+
+ widget = gtk_check_button_new_with_label (
+ _("Copy book content locally for offline operation"));
+ 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-book-source-config.h b/e-util/e-book-source-config.h
new file mode 100644
index 0000000000..3e000789e9
--- /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 0000000000..c05a854020
--- /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 0000000000..f00606ea44
--- /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 0000000000..e009ac6650
--- /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 0000000000..dc78f70e87
--- /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 0000000000..3e7715414c
--- /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 0000000000..a4c0867b66
--- /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/e-util/e-calendar.c b/e-util/e-calendar.c
new file mode 100644
index 0000000000..38336cb618
--- /dev/null
+++ b/e-util/e-calendar.c
@@ -0,0 +1,848 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You 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)
+ *
+ */
+
+/*
+ * 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-calendar.h"
+
+#include <gtk/gtk.h>
+#include <libgnomecanvas/gnome-canvas-widget.h>
+#include <glib/gi18n.h>
+
+#define E_CALENDAR_SMALL_FONT_PTSIZE 6
+
+#define E_CALENDAR_SMALL_FONT \
+ "-adobe-utopia-regular-r-normal-*-*-100-*-*-p-*-iso8859-*"
+#define E_CALENDAR_SMALL_FONT_FALLBACK \
+ "-adobe-helvetica-medium-r-normal-*-*-80-*-*-p-*-iso8859-*"
+
+/* The space between the arrow buttons and the edge of the widget. */
+#define E_CALENDAR_ARROW_BUTTON_X_PAD 2
+#define E_CALENDAR_ARROW_BUTTON_Y_PAD 0
+
+/* Vertical padding. The padding above the button includes the space for the
+ * horizontal line. */
+#define E_CALENDAR_YPAD_ABOVE_LOWER_BUTTONS 4
+#define E_CALENDAR_YPAD_BELOW_LOWER_BUTTONS 3
+
+/* Horizontal padding inside & between buttons. */
+#define E_CALENDAR_IXPAD_BUTTONS 4
+#define E_CALENDAR_XPAD_BUTTONS 8
+
+/* The time between steps when the prev/next buttons is pressed, in 1/1000ths
+ * of a second, and the number of timeouts we skip before we start
+ * automatically moving back/forward. */
+#define E_CALENDAR_AUTO_MOVE_TIMEOUT 150
+#define E_CALENDAR_AUTO_MOVE_TIMEOUT_DELAY 2
+
+static void e_calendar_dispose (GObject *object);
+static void e_calendar_realize (GtkWidget *widget);
+static void e_calendar_style_set (GtkWidget *widget,
+ GtkStyle *previous_style);
+static void e_calendar_get_preferred_width (GtkWidget *widget,
+ gint *minimal_width,
+ gint *natural_width);
+static void e_calendar_get_preferred_height (GtkWidget *widget,
+ gint *minimal_height,
+ gint *natural_height);
+static void e_calendar_size_allocate (GtkWidget *widget,
+ GtkAllocation *allocation);
+static gint e_calendar_drag_motion (GtkWidget *widget,
+ GdkDragContext *context,
+ gint x,
+ gint y,
+ guint time);
+static void e_calendar_drag_leave (GtkWidget *widget,
+ GdkDragContext *context,
+ guint time);
+static gboolean e_calendar_button_has_focus (ECalendar *cal);
+static gboolean e_calendar_focus (GtkWidget *widget,
+ GtkDirectionType direction);
+
+static void e_calendar_on_prev_pressed (ECalendar *cal);
+static void e_calendar_on_prev_released (ECalendar *cal);
+static void e_calendar_on_prev_clicked (ECalendar *cal);
+static void e_calendar_on_next_pressed (ECalendar *cal);
+static void e_calendar_on_next_released (ECalendar *cal);
+static void e_calendar_on_next_clicked (ECalendar *cal);
+static void e_calendar_on_prev_year_pressed (ECalendar *cal);
+static void e_calendar_on_prev_year_released (ECalendar *cal);
+static void e_calendar_on_prev_year_clicked (ECalendar *cal);
+static void e_calendar_on_next_year_pressed (ECalendar *cal);
+static void e_calendar_on_next_year_released (ECalendar *cal);
+static void e_calendar_on_next_year_clicked (ECalendar *cal);
+
+static void e_calendar_start_auto_move (ECalendar *cal,
+ gboolean moving_forward);
+static gboolean e_calendar_auto_move_handler (gpointer data);
+static void e_calendar_start_auto_move_year (ECalendar *cal,
+ gboolean moving_forward);
+static gboolean e_calendar_auto_move_year_handler (gpointer data);
+static void e_calendar_stop_auto_move (ECalendar *cal);
+
+G_DEFINE_TYPE (
+ ECalendar,
+ e_calendar,
+ E_TYPE_CANVAS)
+
+static void
+e_calendar_class_init (ECalendarClass *class)
+{
+ GObjectClass *object_class;
+ GtkWidgetClass *widget_class;
+
+ object_class = (GObjectClass *) class;
+ widget_class = (GtkWidgetClass *) class;
+
+ object_class->dispose = e_calendar_dispose;
+
+ widget_class->realize = e_calendar_realize;
+ widget_class->style_set = e_calendar_style_set;
+ widget_class->get_preferred_width = e_calendar_get_preferred_width;
+ widget_class->get_preferred_height = e_calendar_get_preferred_height;
+ widget_class->size_allocate = e_calendar_size_allocate;
+ widget_class->drag_motion = e_calendar_drag_motion;
+ widget_class->drag_leave = e_calendar_drag_leave;
+ widget_class->focus = e_calendar_focus;
+}
+
+static void
+e_calendar_init (ECalendar *cal)
+{
+ GnomeCanvasGroup *canvas_group;
+ PangoFontDescription *small_font_desc;
+ GtkWidget *button, *pixmap;
+ AtkObject *a11y;
+
+ /* Create the small font. */
+
+ small_font_desc = pango_font_description_copy (
+ gtk_widget_get_style (GTK_WIDGET (cal))->font_desc);
+ pango_font_description_set_size (
+ small_font_desc,
+ E_CALENDAR_SMALL_FONT_PTSIZE * PANGO_SCALE);
+
+ canvas_group = GNOME_CANVAS_GROUP (GNOME_CANVAS (cal)->root);
+
+ cal->calitem = E_CALENDAR_ITEM (
+ gnome_canvas_item_new (
+ canvas_group,
+ e_calendar_item_get_type (),
+ "week_number_font_desc", small_font_desc,
+ NULL));
+
+ pango_font_description_free (small_font_desc);
+
+ /* Create the arrow buttons to move to the previous/next month. */
+ button = gtk_button_new ();
+ gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
+ gtk_widget_show (button);
+ g_signal_connect_swapped (
+ button, "pressed",
+ G_CALLBACK (e_calendar_on_prev_pressed), cal);
+ g_signal_connect_swapped (
+ button, "released",
+ G_CALLBACK (e_calendar_on_prev_released), cal);
+ g_signal_connect_swapped (
+ button, "clicked",
+ G_CALLBACK (e_calendar_on_prev_clicked), cal);
+
+ pixmap = gtk_arrow_new (GTK_ARROW_LEFT, GTK_SHADOW_NONE);
+ gtk_widget_show (pixmap);
+ gtk_container_add (GTK_CONTAINER (button), pixmap);
+
+ cal->prev_item = gnome_canvas_item_new (
+ canvas_group,
+ gnome_canvas_widget_get_type (),
+ "widget", button,
+ NULL);
+ a11y = gtk_widget_get_accessible (button);
+ atk_object_set_name (a11y, _("Previous month"));
+
+ button = gtk_button_new ();
+ gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
+ gtk_widget_show (button);
+ g_signal_connect_swapped (
+ button, "pressed",
+ G_CALLBACK (e_calendar_on_next_pressed), cal);
+ g_signal_connect_swapped (
+ button, "released",
+ G_CALLBACK (e_calendar_on_next_released), cal);
+ g_signal_connect_swapped (
+ button, "clicked",
+ G_CALLBACK (e_calendar_on_next_clicked), cal);
+
+ pixmap = gtk_arrow_new (GTK_ARROW_RIGHT, GTK_SHADOW_NONE);
+ gtk_widget_show (pixmap);
+ gtk_container_add (GTK_CONTAINER (button), pixmap);
+
+ cal->next_item = gnome_canvas_item_new (
+ canvas_group,
+ gnome_canvas_widget_get_type (),
+ "widget", button,
+ NULL);
+ a11y = gtk_widget_get_accessible (button);
+ atk_object_set_name (a11y, _("Next month"));
+
+ /* Create the arrow buttons to move to the previous/next year. */
+ button = gtk_button_new ();
+ gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
+ gtk_widget_show (button);
+ g_signal_connect_swapped (
+ button, "pressed",
+ G_CALLBACK (e_calendar_on_prev_year_pressed), cal);
+ g_signal_connect_swapped (
+ button, "released",
+ G_CALLBACK (e_calendar_on_prev_year_released), cal);
+ g_signal_connect_swapped (
+ button, "clicked",
+ G_CALLBACK (e_calendar_on_prev_year_clicked), cal);
+
+ pixmap = gtk_arrow_new (GTK_ARROW_LEFT, GTK_SHADOW_NONE);
+ gtk_widget_show (pixmap);
+ gtk_container_add (GTK_CONTAINER (button), pixmap);
+
+ cal->prev_item_year = gnome_canvas_item_new (
+ canvas_group,
+ gnome_canvas_widget_get_type (),
+ "widget", button,
+ NULL);
+ a11y = gtk_widget_get_accessible (button);
+ atk_object_set_name (a11y, _("Previous year"));
+
+ button = gtk_button_new ();
+ gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
+ gtk_widget_show (button);
+ g_signal_connect_swapped (
+ button, "pressed",
+ G_CALLBACK (e_calendar_on_next_year_pressed), cal);
+ g_signal_connect_swapped (
+ button, "released",
+ G_CALLBACK (e_calendar_on_next_year_released), cal);
+ g_signal_connect_swapped (
+ button, "clicked",
+ G_CALLBACK (e_calendar_on_next_year_clicked), cal);
+
+ pixmap = gtk_arrow_new (GTK_ARROW_RIGHT, GTK_SHADOW_NONE);
+ gtk_widget_show (pixmap);
+ gtk_container_add (GTK_CONTAINER (button), pixmap);
+
+ cal->next_item_year = gnome_canvas_item_new (
+ canvas_group,
+ gnome_canvas_widget_get_type (),
+ "widget", button,
+ NULL);
+ a11y = gtk_widget_get_accessible (button);
+ atk_object_set_name (a11y, _("Next year"));
+
+ cal->min_rows = 1;
+ cal->min_cols = 1;
+ cal->max_rows = -1;
+ cal->max_cols = -1;
+
+ cal->timeout_id = 0;
+}
+
+/**
+ * e_calendar_new:
+ * @Returns: a new #ECalendar.
+ *
+ * Creates a new #ECalendar.
+ **/
+GtkWidget *
+e_calendar_new (void)
+{
+ GtkWidget *cal;
+ AtkObject *a11y;
+
+ cal = g_object_new (e_calendar_get_type (), NULL);
+ a11y = gtk_widget_get_accessible (cal);
+ atk_object_set_name (a11y, _("Month Calendar"));
+
+ return cal;
+}
+
+static void
+e_calendar_dispose (GObject *object)
+{
+ ECalendar *cal;
+
+ g_return_if_fail (object != NULL);
+ g_return_if_fail (E_IS_CALENDAR (object));
+
+ cal = E_CALENDAR (object);
+
+ if (cal->timeout_id != 0) {
+ g_source_remove (cal->timeout_id);
+ cal->timeout_id = 0;
+ }
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (e_calendar_parent_class)->dispose (object);
+}
+
+static void
+e_calendar_realize (GtkWidget *widget)
+{
+ GtkStyle *style;
+ GdkWindow *window;
+
+ (*GTK_WIDGET_CLASS (e_calendar_parent_class)->realize) (widget);
+
+ /* Set the background of the canvas window to the normal color,
+ * or the arrow buttons are not displayed properly. */
+ style = gtk_widget_get_style (widget);
+ window = gtk_layout_get_bin_window (GTK_LAYOUT (widget));
+ gdk_window_set_background (window, &style->bg[GTK_STATE_NORMAL]);
+}
+
+static void
+e_calendar_style_set (GtkWidget *widget,
+ GtkStyle *previous_style)
+{
+ ECalendar *e_calendar;
+
+ e_calendar = E_CALENDAR (widget);
+ if (GTK_WIDGET_CLASS (e_calendar_parent_class)->style_set)
+ (*GTK_WIDGET_CLASS (e_calendar_parent_class)->style_set) (widget,
+ previous_style);
+
+ /* Set the background of the canvas window to the normal color,
+ * or the arrow buttons are not displayed properly. */
+ if (gtk_widget_get_realized (widget)) {
+ GtkStyle *style;
+ GdkWindow *window;
+
+ style = gtk_widget_get_style (widget);
+ window = gtk_layout_get_bin_window (GTK_LAYOUT (widget));
+ gdk_window_set_background (window, &style->bg[GTK_STATE_NORMAL]);
+ }
+ e_calendar_item_style_set (widget, e_calendar->calitem);
+}
+
+static void
+e_calendar_get_preferred_width (GtkWidget *widget,
+ gint *minimum,
+ gint *natural)
+{
+ ECalendar *cal;
+ GtkStyle *style;
+ gint col_width;
+
+ cal = E_CALENDAR (widget);
+ style = gtk_widget_get_style (GTK_WIDGET (cal));
+
+ g_object_get ((cal->calitem), "column_width", &col_width, NULL);
+
+ *minimum = *natural = col_width * cal->min_cols + style->xthickness * 2;
+}
+
+static void
+e_calendar_get_preferred_height (GtkWidget *widget,
+ gint *minimum,
+ gint *natural)
+{
+ ECalendar *cal;
+ GtkStyle *style;
+ gint row_height;
+
+ cal = E_CALENDAR (widget);
+ style = gtk_widget_get_style (GTK_WIDGET (cal));
+
+ g_object_get ((cal->calitem), "row_height", &row_height, NULL);
+
+ *minimum = *natural = row_height * cal->min_rows + style->ythickness * 2;
+}
+
+static void
+e_calendar_size_allocate (GtkWidget *widget,
+ GtkAllocation *allocation)
+{
+ ECalendar *cal;
+ GtkStyle *style;
+ GtkAllocation old_allocation;
+ PangoFontDescription *font_desc;
+ PangoContext *pango_context;
+ PangoFontMetrics *font_metrics;
+ gdouble old_x2, old_y2, new_x2, new_y2;
+ gdouble xthickness, ythickness, arrow_button_size, current_x, month_width;
+ gboolean is_rtl;
+
+ cal = E_CALENDAR (widget);
+ style = gtk_widget_get_style (widget);
+ xthickness = style->xthickness;
+ ythickness = style->ythickness;
+
+ (*GTK_WIDGET_CLASS (e_calendar_parent_class)->size_allocate) (widget, allocation);
+
+ /* Set up Pango prerequisites */
+ font_desc = gtk_widget_get_style (widget)->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));
+
+ /* Set the scroll region to its allocated size, if changed. */
+ gnome_canvas_get_scroll_region (
+ GNOME_CANVAS (cal),
+ NULL, NULL, &old_x2, &old_y2);
+ gtk_widget_get_allocation (widget, &old_allocation);
+ new_x2 = old_allocation.width - 1;
+ new_y2 = old_allocation.height - 1;
+ if (old_x2 != new_x2 || old_y2 != new_y2)
+ gnome_canvas_set_scroll_region (
+ GNOME_CANVAS (cal),
+ 0, 0, new_x2, new_y2);
+
+ /* Take off space for line & buttons if shown. */
+ gnome_canvas_item_set (
+ GNOME_CANVAS_ITEM (cal->calitem),
+ "x1", 0.0,
+ "y1", 0.0,
+ "x2", new_x2,
+ "y2", new_y2,
+ NULL);
+
+ if (cal->calitem->month_width > 0)
+ month_width = cal->calitem->month_width;
+ else
+ month_width = new_x2;
+ month_width -= E_CALENDAR_ITEM_MIN_CELL_XPAD + E_CALENDAR_ARROW_BUTTON_X_PAD;
+
+ /* Position the arrow buttons. */
+ 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
+ - E_CALENDAR_ARROW_BUTTON_Y_PAD * 2 - 2;
+
+ is_rtl = gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL;
+ current_x = is_rtl ?
+ (month_width - 2 * xthickness - E_CALENDAR_ARROW_BUTTON_X_PAD - arrow_button_size) :
+ (xthickness);
+
+ gnome_canvas_item_set (
+ cal->prev_item,
+ "x", current_x,
+ "y", ythickness + E_CALENDAR_ARROW_BUTTON_Y_PAD,
+ "width", arrow_button_size,
+ "height", arrow_button_size,
+ NULL);
+
+ current_x += (is_rtl ? -1.0 : +1.0) * (cal->calitem->max_month_name_width - xthickness + 2 * arrow_button_size);
+
+ gnome_canvas_item_set (
+ cal->next_item,
+ "x", current_x,
+ "y", ythickness + E_CALENDAR_ARROW_BUTTON_Y_PAD,
+ "width", arrow_button_size,
+ "height", arrow_button_size,
+ NULL);
+
+ current_x = is_rtl ?
+ (xthickness) :
+ (month_width - 2 * xthickness - E_CALENDAR_ARROW_BUTTON_X_PAD - arrow_button_size);
+
+ gnome_canvas_item_set (
+ cal->next_item_year,
+ "x", current_x,
+ "y", ythickness + E_CALENDAR_ARROW_BUTTON_Y_PAD,
+ "width", arrow_button_size,
+ "height", arrow_button_size,
+ NULL);
+
+ current_x += (is_rtl ? +1.0 : -1.0) * (cal->calitem->max_digit_width * 5 - xthickness + 2 * arrow_button_size);
+
+ gnome_canvas_item_set (
+ cal->prev_item_year,
+ "x", current_x,
+ "y", ythickness + E_CALENDAR_ARROW_BUTTON_Y_PAD,
+ "width", arrow_button_size,
+ "height", arrow_button_size,
+ NULL);
+
+ pango_font_metrics_unref (font_metrics);
+}
+
+void
+e_calendar_set_minimum_size (ECalendar *cal,
+ gint rows,
+ gint cols)
+{
+ g_return_if_fail (E_IS_CALENDAR (cal));
+
+ cal->min_rows = rows;
+ cal->min_cols = cols;
+
+ gnome_canvas_item_set (
+ GNOME_CANVAS_ITEM (cal->calitem),
+ "minimum_rows", rows,
+ "minimum_columns", cols,
+ NULL);
+
+ gtk_widget_queue_resize (GTK_WIDGET (cal));
+}
+
+void
+e_calendar_set_maximum_size (ECalendar *cal,
+ gint rows,
+ gint cols)
+{
+ g_return_if_fail (E_IS_CALENDAR (cal));
+
+ cal->max_rows = rows;
+ cal->max_cols = cols;
+
+ gnome_canvas_item_set (
+ GNOME_CANVAS_ITEM (cal->calitem),
+ "maximum_rows", rows,
+ "maximum_columns", cols,
+ NULL);
+
+ gtk_widget_queue_resize (GTK_WIDGET (cal));
+}
+
+/* Returns the border size on each side of the month displays. */
+void
+e_calendar_get_border_size (ECalendar *cal,
+ gint *top,
+ gint *bottom,
+ gint *left,
+ gint *right)
+{
+ GtkStyle *style;
+
+ g_return_if_fail (E_IS_CALENDAR (cal));
+
+ style = gtk_widget_get_style (GTK_WIDGET (cal));
+
+ if (style) {
+ *top = style->ythickness;
+ *bottom = style->ythickness;
+ *left = style->xthickness;
+ *right = style->xthickness;
+ } else {
+ *top = *bottom = *left = *right = 0;
+ }
+}
+
+static void
+e_calendar_on_prev_pressed (ECalendar *cal)
+{
+ e_calendar_start_auto_move (cal, FALSE);
+}
+
+static void
+e_calendar_on_next_pressed (ECalendar *cal)
+{
+ e_calendar_start_auto_move (cal, TRUE);
+}
+
+static void
+e_calendar_on_prev_year_pressed (ECalendar *cal)
+{
+ e_calendar_start_auto_move_year (cal, FALSE);
+}
+
+static void
+e_calendar_on_next_year_pressed (ECalendar *cal)
+{
+ e_calendar_start_auto_move_year (cal, TRUE);
+}
+
+static void
+e_calendar_start_auto_move (ECalendar *cal,
+ gboolean moving_forward)
+{
+ if (cal->timeout_id == 0) {
+ cal->timeout_id = g_timeout_add (
+ E_CALENDAR_AUTO_MOVE_TIMEOUT,
+ e_calendar_auto_move_handler,
+ cal);
+ }
+ cal->timeout_delay = E_CALENDAR_AUTO_MOVE_TIMEOUT_DELAY;
+ cal->moving_forward = moving_forward;
+
+}
+
+static void
+e_calendar_start_auto_move_year (ECalendar *cal,
+ gboolean moving_forward)
+{
+ if (cal->timeout_id == 0) {
+ cal->timeout_id = g_timeout_add (
+ E_CALENDAR_AUTO_MOVE_TIMEOUT,
+ e_calendar_auto_move_year_handler,
+ cal);
+ }
+ cal->timeout_delay = E_CALENDAR_AUTO_MOVE_TIMEOUT_DELAY;
+ cal->moving_forward = moving_forward;
+}
+
+static gboolean
+e_calendar_auto_move_year_handler (gpointer data)
+{
+ ECalendar *cal;
+ ECalendarItem *calitem;
+ gint offset;
+
+ g_return_val_if_fail (E_IS_CALENDAR (data), FALSE);
+
+ cal = E_CALENDAR (data);
+ calitem = cal->calitem;
+
+ if (cal->timeout_delay > 0) {
+ cal->timeout_delay--;
+ } else {
+ offset = cal->moving_forward ? 12 : -12;
+ e_calendar_item_set_first_month (
+ calitem, calitem->year,
+ calitem->month + offset);
+ }
+
+ return TRUE;
+}
+
+static gboolean
+e_calendar_auto_move_handler (gpointer data)
+{
+ ECalendar *cal;
+ ECalendarItem *calitem;
+ gint offset;
+
+ g_return_val_if_fail (E_IS_CALENDAR (data), FALSE);
+
+ cal = E_CALENDAR (data);
+ calitem = cal->calitem;
+
+ if (cal->timeout_delay > 0) {
+ cal->timeout_delay--;
+ } else {
+ offset = cal->moving_forward ? 1 : -1;
+ e_calendar_item_set_first_month (
+ calitem, calitem->year,
+ calitem->month + offset);
+ }
+
+ return TRUE;
+}
+
+static void
+e_calendar_on_prev_released (ECalendar *cal)
+{
+ e_calendar_stop_auto_move (cal);
+}
+
+static void
+e_calendar_on_next_released (ECalendar *cal)
+{
+ e_calendar_stop_auto_move (cal);
+}
+
+static void
+e_calendar_on_prev_year_released (ECalendar *cal)
+{
+ e_calendar_stop_auto_move (cal);
+}
+
+static void
+e_calendar_on_next_year_released (ECalendar *cal)
+{
+ e_calendar_stop_auto_move (cal);
+}
+
+static void
+e_calendar_stop_auto_move (ECalendar *cal)
+{
+ if (cal->timeout_id != 0) {
+ g_source_remove (cal->timeout_id);
+ cal->timeout_id = 0;
+ }
+}
+
+static void
+e_calendar_on_prev_clicked (ECalendar *cal)
+{
+ e_calendar_item_set_first_month (
+ cal->calitem, cal->calitem->year,
+ cal->calitem->month - 1);
+}
+
+static void
+e_calendar_on_next_clicked (ECalendar *cal)
+{
+ e_calendar_item_set_first_month (
+ cal->calitem, cal->calitem->year,
+ cal->calitem->month + 1);
+}
+
+static void
+e_calendar_on_prev_year_clicked (ECalendar *cal)
+{
+ e_calendar_item_set_first_month (
+ cal->calitem, cal->calitem->year,
+ cal->calitem->month - 12);
+}
+
+static void
+e_calendar_on_next_year_clicked (ECalendar *cal)
+{
+ e_calendar_item_set_first_month (
+ cal->calitem, cal->calitem->year,
+ cal->calitem->month + 12);
+}
+
+static gint
+e_calendar_drag_motion (GtkWidget *widget,
+ GdkDragContext *context,
+ gint x,
+ gint y,
+ guint time)
+{
+ return FALSE;
+}
+
+static void
+e_calendar_drag_leave (GtkWidget *widget,
+ GdkDragContext *context,
+ guint time)
+{
+}
+
+static gboolean
+e_calendar_button_has_focus (ECalendar *cal)
+{
+ GtkWidget *prev_widget, *next_widget;
+ gboolean ret_val;
+
+ g_return_val_if_fail (E_IS_CALENDAR (cal), FALSE);
+
+ prev_widget = GNOME_CANVAS_WIDGET (cal->prev_item)->widget;
+ next_widget = GNOME_CANVAS_WIDGET (cal->next_item)->widget;
+ ret_val = gtk_widget_has_focus (prev_widget) ||
+ gtk_widget_has_focus (next_widget);
+ return ret_val;
+}
+
+static gboolean
+e_calendar_focus (GtkWidget *widget,
+ GtkDirectionType direction)
+{
+#define E_CALENDAR_FOCUS_CHILDREN_NUM 5
+ ECalendar *cal;
+ GnomeCanvas *canvas;
+ GnomeCanvasItem *children[E_CALENDAR_FOCUS_CHILDREN_NUM];
+ gint focused_index = -1;
+ gint index;
+
+ g_return_val_if_fail (widget != NULL, FALSE);
+ g_return_val_if_fail (E_IS_CALENDAR (widget), FALSE);
+ cal = E_CALENDAR (widget);
+ canvas = GNOME_CANVAS (widget);
+
+ if (!gtk_widget_get_can_focus (widget))
+ return FALSE;
+
+ children[0] = GNOME_CANVAS_ITEM (cal->calitem);
+ children[1] = cal->prev_item;
+ children[2] = cal->next_item;
+ children[3] = cal->prev_item_year;
+ children[4] = cal->next_item_year;
+
+ /* get current focused item, if e-calendar has had focus */
+ if (gtk_widget_has_focus (widget) || e_calendar_button_has_focus (cal))
+ for (index = 0; index < E_CALENDAR_FOCUS_CHILDREN_NUM; ++index) {
+ if (canvas->focused_item == NULL)
+ break;
+
+ if (children[index] == canvas->focused_item) {
+ focused_index = index;
+ break;
+ }
+ }
+
+ if (focused_index == -1)
+ if (direction == GTK_DIR_TAB_FORWARD)
+ focused_index = 0;
+ else
+ focused_index = E_CALENDAR_FOCUS_CHILDREN_NUM - 1;
+ else
+ if (direction == GTK_DIR_TAB_FORWARD)
+ ++focused_index;
+ else
+ --focused_index;
+
+ if (focused_index < 0 ||
+ focused_index >= E_CALENDAR_FOCUS_CHILDREN_NUM)
+ /* move out of e-calendar */
+ return FALSE;
+ gnome_canvas_item_grab_focus (children[focused_index]);
+ if (GNOME_IS_CANVAS_WIDGET (children[focused_index])) {
+ widget = GNOME_CANVAS_WIDGET (children[focused_index])->widget;
+ gtk_widget_grab_focus (widget);
+ }
+ return TRUE;
+}
+
+void
+e_calendar_set_focusable (ECalendar *cal,
+ gboolean focusable)
+{
+ GtkWidget *widget;
+ GtkWidget *prev_widget, *next_widget;
+ GtkWidget *toplevel;
+
+ g_return_if_fail (E_IS_CALENDAR (cal));
+
+ widget = GTK_WIDGET (cal);
+ prev_widget = GNOME_CANVAS_WIDGET (cal->prev_item)->widget;
+ next_widget = GNOME_CANVAS_WIDGET (cal->next_item)->widget;
+
+ if (focusable) {
+ gtk_widget_set_can_focus (widget, TRUE);
+ gtk_widget_set_can_focus (prev_widget, TRUE);
+ gtk_widget_set_can_focus (next_widget, TRUE);
+ }
+ else {
+ if (gtk_widget_has_focus (GTK_WIDGET (cal)) ||
+ e_calendar_button_has_focus (cal)) {
+ toplevel = gtk_widget_get_toplevel (widget);
+ if (toplevel)
+ gtk_widget_grab_focus (toplevel);
+ }
+ gtk_widget_set_can_focus (widget, FALSE);
+ gtk_widget_set_can_focus (prev_widget, FALSE);
+ gtk_widget_set_can_focus (next_widget, FALSE);
+ }
+}
diff --git a/e-util/e-calendar.h b/e-util/e-calendar.h
new file mode 100644
index 0000000000..9a3651348c
--- /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 0000000000..6379697719
--- /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 0000000000..c57c64f787
--- /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/e-util/e-canvas-utils.c b/e-util/e-canvas-utils.c
new file mode 100644
index 0000000000..ec3aad3858
--- /dev/null
+++ b/e-util/e-canvas-utils.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:
+ * 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-utils.h"
+
+void
+e_canvas_item_move_absolute (GnomeCanvasItem *item,
+ gdouble dx,
+ gdouble dy)
+{
+ cairo_matrix_t translate;
+
+ g_return_if_fail (GNOME_IS_CANVAS_ITEM (item));
+
+ cairo_matrix_init_translate (&translate, dx, dy);
+
+ gnome_canvas_item_set_matrix (item, &translate);
+}
+
+static double
+compute_offset (gint top,
+ gint bottom,
+ gint page_top,
+ gint page_bottom)
+{
+ gint size = bottom - top;
+ gint offset = 0;
+
+ if (top <= page_top && bottom >= page_bottom)
+ return 0;
+
+ if (bottom > page_bottom)
+ offset = (bottom - page_bottom);
+ if (top < page_top + offset)
+ offset = (top - page_top);
+
+ if (top <= page_top + offset && bottom >= page_bottom + offset)
+ return offset;
+
+ if (top < page_top + size * 3 / 2 + offset)
+ offset = top - (page_top + size * 3 / 2);
+ if (bottom > page_bottom - size * 3 / 2 + offset)
+ offset = bottom - (page_bottom - size * 3 / 2);
+ if (top < page_top + size * 3 / 2 + offset)
+ offset = top - ((page_top + page_bottom - (bottom - top)) / 2);
+
+ return offset;
+}
+
+static void
+e_canvas_show_area (GnomeCanvas *canvas,
+ gdouble x1,
+ gdouble y1,
+ gdouble x2,
+ gdouble y2)
+{
+ GtkAdjustment *h, *v;
+ gint dx = 0, dy = 0;
+ gdouble page_size;
+ gdouble lower;
+ gdouble upper;
+ gdouble value;
+
+ g_return_if_fail (canvas != NULL);
+ g_return_if_fail (GNOME_IS_CANVAS (canvas));
+
+ h = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (canvas));
+ page_size = gtk_adjustment_get_page_size (h);
+ lower = gtk_adjustment_get_lower (h);
+ upper = gtk_adjustment_get_upper (h);
+ value = gtk_adjustment_get_value (h);
+ dx = compute_offset (x1, x2, value, value + page_size);
+ if (dx)
+ gtk_adjustment_set_value (h, CLAMP (value + dx, lower, upper - page_size));
+
+ v = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (canvas));
+ page_size = gtk_adjustment_get_page_size (v);
+ lower = gtk_adjustment_get_lower (v);
+ upper = gtk_adjustment_get_upper (v);
+ value = gtk_adjustment_get_value (v);
+ dy = compute_offset (y1, y2, value, value + page_size);
+ if (dy)
+ gtk_adjustment_set_value (v, CLAMP (value + dy, lower, upper - page_size));
+}
+
+void
+e_canvas_item_show_area (GnomeCanvasItem *item,
+ gdouble x1,
+ gdouble y1,
+ gdouble x2,
+ gdouble y2)
+{
+ g_return_if_fail (item != NULL);
+ g_return_if_fail (GNOME_IS_CANVAS_ITEM (item));
+
+ gnome_canvas_item_i2w (item, &x1, &y1);
+ gnome_canvas_item_i2w (item, &x2, &y2);
+
+ e_canvas_show_area (item->canvas, x1, y1, x2, y2);
+}
+
+static gboolean
+e_canvas_area_shown (GnomeCanvas *canvas,
+ gdouble x1,
+ gdouble y1,
+ gdouble x2,
+ gdouble y2)
+{
+ GtkAdjustment *h, *v;
+ gint dx = 0, dy = 0;
+ gdouble page_size;
+ gdouble lower;
+ gdouble upper;
+ gdouble value;
+
+ g_return_val_if_fail (canvas != NULL, FALSE);
+ g_return_val_if_fail (GNOME_IS_CANVAS (canvas), FALSE);
+
+ h = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (canvas));
+ page_size = gtk_adjustment_get_page_size (h);
+ lower = gtk_adjustment_get_lower (h);
+ upper = gtk_adjustment_get_upper (h);
+ value = gtk_adjustment_get_value (h);
+ dx = compute_offset (x1, x2, value, value + page_size);
+ if (CLAMP (value + dx, lower, upper - page_size) - value != 0)
+ return FALSE;
+
+ v = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (canvas));
+ page_size = gtk_adjustment_get_page_size (v);
+ lower = gtk_adjustment_get_lower (v);
+ upper = gtk_adjustment_get_upper (v);
+ value = gtk_adjustment_get_value (v);
+ dy = compute_offset (y1, y2, value, value + page_size);
+ if (CLAMP (value + dy, lower, upper - page_size) - value != 0)
+ return FALSE;
+ return TRUE;
+}
+
+gboolean
+e_canvas_item_area_shown (GnomeCanvasItem *item,
+ gdouble x1,
+ gdouble y1,
+ gdouble x2,
+ gdouble y2)
+{
+ g_return_val_if_fail (item != NULL, FALSE);
+ g_return_val_if_fail (GNOME_IS_CANVAS_ITEM (item), FALSE);
+
+ gnome_canvas_item_i2w (item, &x1, &y1);
+ gnome_canvas_item_i2w (item, &x2, &y2);
+
+ return e_canvas_area_shown (item->canvas, x1, y1, x2, y2);
+}
+
+typedef struct {
+ gdouble x1;
+ gdouble y1;
+ gdouble x2;
+ gdouble y2;
+ GnomeCanvas *canvas;
+} DoubsAndCanvas;
+
+static gboolean
+show_area_timeout (gpointer data)
+{
+ DoubsAndCanvas *dac = data;
+
+ e_canvas_show_area (dac->canvas, dac->x1, dac->y1, dac->x2, dac->y2);
+ g_object_unref (dac->canvas);
+ g_free (dac);
+ return FALSE;
+}
+
+void
+e_canvas_item_show_area_delayed (GnomeCanvasItem *item,
+ gdouble x1,
+ gdouble y1,
+ gdouble x2,
+ gdouble y2,
+ gint delay)
+{
+ DoubsAndCanvas *dac;
+
+ g_return_if_fail (item != NULL);
+ g_return_if_fail (GNOME_IS_CANVAS_ITEM (item));
+
+ gnome_canvas_item_i2w (item, &x1, &y1);
+ gnome_canvas_item_i2w (item, &x2, &y2);
+
+ dac = g_new (DoubsAndCanvas, 1);
+ dac->x1 = x1;
+ dac->y1 = y1;
+ dac->x2 = x2;
+ dac->y2 = y2;
+ dac->canvas = item->canvas;
+ g_object_ref (item->canvas);
+ g_timeout_add (delay, show_area_timeout, dac);
+}
diff --git a/e-util/e-canvas-utils.h b/e-util/e-canvas-utils.h
new file mode 100644
index 0000000000..c12f156c59
--- /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 0000000000..f8de013557
--- /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 0000000000..5255e7683d
--- /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 0000000000..d39f9f7684
--- /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 0000000000..8d704b963c
--- /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 519fc5db32..611c7fa4af 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 82294ae6b9..f778e0116f 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 0000000000..8f46b1041d
--- /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 0000000000..5ad35e098c
--- /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 0000000000..ecbebf6083
--- /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 0000000000..07c9dc6987
--- /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 0000000000..5a05238626
--- /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 0000000000..6ffc9f82ef
--- /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 0000000000..095df50b45
--- /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 0000000000..477a036cd3
--- /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 0000000000..33ad6dde6c
--- /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 0000000000..bf5ebbe1a8
--- /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 0000000000..c4340426bd
--- /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 0000000000..2d1d9db053
--- /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 0000000000..dba6b53c50
--- /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 0000000000..04f17c60fa
--- /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 0000000000..4f35fbb266
--- /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 0000000000..ea70731457
--- /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 0000000000..b8067f208e
--- /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 0000000000..a0968b050b
--- /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 0000000000..669dd4416c
--- /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 0000000000..6a0495cf50
--- /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 0000000000..5ecccaded1
--- /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 0000000000..a5ef2ab52f
--- /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/e-util/e-cell-percent.c b/e-util/e-cell-percent.c
new file mode 100644
index 0000000000..81465d5a62
--- /dev/null
+++ b/e-util/e-cell-percent.c
@@ -0,0 +1,160 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <ctype.h>
+
+#include <sys/time.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <glib/gi18n.h>
+
+#include "e-cell-percent.h"
+
+G_DEFINE_TYPE (ECellPercent, e_cell_percent, E_TYPE_CELL_TEXT)
+
+static gchar *
+ecp_get_text (ECellText *cell,
+ ETableModel *model,
+ gint col,
+ gint row)
+{
+ gint percent;
+ static gchar buffer[8];
+
+ percent = GPOINTER_TO_INT (e_table_model_value_at (model, col, row));
+
+ /* A -ve value means the property is not set. */
+ if (percent < 0) {
+ buffer[0] = '\0';
+ } else {
+ g_snprintf (buffer, sizeof (buffer), "%i%%", percent);
+ }
+
+ return buffer;
+}
+
+static void
+ecp_free_text (ECellText *cell,
+ gchar *text)
+{
+ /* Do Nothing. */
+}
+
+/* FIXME: We need to set the "transient_for" property for the dialog. */
+static void
+show_percent_warning (void)
+{
+ GtkWidget *dialog;
+
+ dialog = gtk_message_dialog_new (
+ NULL, 0,
+ GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
+ "%s", _("The percent value must be between 0 and 100, inclusive"));
+ gtk_dialog_run (GTK_DIALOG (dialog));
+ gtk_widget_destroy (dialog);
+}
+
+static void
+ecp_set_value (ECellText *cell,
+ ETableModel *model,
+ gint col,
+ gint row,
+ const gchar *text)
+{
+ gint matched, percent;
+ gboolean empty = TRUE;
+ const gchar *p;
+
+ if (text) {
+ p = text;
+ while (*p) {
+ if (!isspace ((guchar) *p)) {
+ empty = FALSE;
+ break;
+ }
+ p++;
+ }
+ }
+
+ if (empty) {
+ percent = -1;
+ } else {
+ matched = sscanf (text, "%i", &percent);
+
+ if (matched != 1 || percent < 0 || percent > 100) {
+ show_percent_warning ();
+ return;
+ }
+ }
+
+ e_table_model_set_value_at (
+ model, col, row,
+ GINT_TO_POINTER (percent));
+}
+
+static void
+e_cell_percent_class_init (ECellPercentClass *ecpc)
+{
+ ECellTextClass *ectc = (ECellTextClass *) ecpc;
+
+ ectc->get_text = ecp_get_text;
+ ectc->free_text = ecp_free_text;
+ ectc->set_value = ecp_set_value;
+}
+
+static void
+e_cell_percent_init (ECellPercent *ecp)
+{
+}
+
+/**
+ * e_cell_percent_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 an integer
+ * percentage that comes 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_percent_new (const gchar *fontname,
+ GtkJustification justify)
+{
+ ECellPercent *ecn = g_object_new (E_TYPE_CELL_PERCENT, NULL);
+
+ e_cell_text_construct (E_CELL_TEXT (ecn), fontname, justify);
+
+ return (ECell *) ecn;
+}
diff --git a/e-util/e-cell-percent.h b/e-util/e-cell-percent.h
new file mode 100644
index 0000000000..57ce41d1a3
--- /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/e-util/e-cell-pixbuf.c b/e-util/e-cell-pixbuf.c
new file mode 100644
index 0000000000..41b030ec5a
--- /dev/null
+++ b/e-util/e-cell-pixbuf.c
@@ -0,0 +1,389 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You 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)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+
+#include <libgnomecanvas/libgnomecanvas.h>
+
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include "e-cell-pixbuf.h"
+
+G_DEFINE_TYPE (ECellPixbuf, e_cell_pixbuf, E_TYPE_CELL)
+
+typedef struct _ECellPixbufView ECellPixbufView;
+
+struct _ECellPixbufView {
+ ECellView cell_view;
+ GnomeCanvas *canvas;
+};
+
+/* Object argument IDs */
+enum {
+ PROP_0,
+
+ PROP_SELECTED_COLUMN,
+ PROP_FOCUSED_COLUMN,
+ PROP_UNSELECTED_COLUMN
+};
+
+/*
+ * ECellPixbuf functions
+ */
+
+ECell *
+e_cell_pixbuf_new (void)
+{
+ return g_object_new (E_TYPE_CELL_PIXBUF, NULL);
+}
+
+/*
+ * ECell methods
+ */
+
+static ECellView *
+pixbuf_new_view (ECell *ecell,
+ ETableModel *table_model,
+ gpointer e_table_item_view)
+{
+ ECellPixbufView *pixbuf_view = g_new0 (ECellPixbufView, 1);
+ ETableItem *eti = E_TABLE_ITEM (e_table_item_view);
+ GnomeCanvas *canvas = GNOME_CANVAS_ITEM (eti)->canvas;
+
+ pixbuf_view->cell_view.ecell = ecell;
+ pixbuf_view->cell_view.e_table_model = table_model;
+ pixbuf_view->cell_view.e_table_item_view = e_table_item_view;
+ pixbuf_view->cell_view.kill_view_cb = NULL;
+ pixbuf_view->cell_view.kill_view_cb_data = NULL;
+
+ pixbuf_view->canvas = canvas;
+
+ return (ECellView *) pixbuf_view;
+}
+
+static void
+pixbuf_kill_view (ECellView *ecell_view)
+{
+ ECellPixbufView *pixbuf_view = (ECellPixbufView *) ecell_view;
+
+ if (pixbuf_view->cell_view.kill_view_cb)
+ pixbuf_view->cell_view.kill_view_cb (
+ ecell_view, pixbuf_view->cell_view.kill_view_cb_data);
+
+ if (pixbuf_view->cell_view.kill_view_cb_data)
+ g_list_free (pixbuf_view->cell_view.kill_view_cb_data);
+
+ g_free (pixbuf_view);
+}
+
+static void
+pixbuf_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)
+{
+ GdkPixbuf *cell_pixbuf;
+ gint real_x, real_y;
+ gint pix_w, pix_h;
+
+ cell_pixbuf = e_table_model_value_at (ecell_view->e_table_model,
+ 1, row);
+
+ /* we can't make sure we really got a pixbuf since, well, it's a Gdk thing */
+
+ if (x2 - x1 == 0)
+ return;
+
+ if (!cell_pixbuf)
+ return;
+
+ pix_w = gdk_pixbuf_get_width (cell_pixbuf);
+ pix_h = gdk_pixbuf_get_height (cell_pixbuf);
+
+ /* We center the pixbuf within our allocated space */
+ if (x2 - x1 > pix_w) {
+ gint diff = (x2 - x1) - pix_w;
+ real_x = x1 + diff / 2;
+ } else {
+ real_x = x1;
+ }
+
+ if (y2 - y1 > pix_h) {
+ gint diff = (y2 - y1) - pix_h;
+ real_y = y1 + diff / 2;
+ } else {
+ real_y = y1;
+ }
+
+ cairo_save (cr);
+ gdk_cairo_set_source_pixbuf (cr, cell_pixbuf, real_x, real_y);
+ cairo_paint_with_alpha (cr, 1);
+ cairo_restore (cr);
+}
+
+static gint
+pixbuf_event (ECellView *ecell_view,
+ GdkEvent *event,
+ gint model_col,
+ gint view_col,
+ gint row,
+ ECellFlags flags,
+ ECellActions *actions)
+{
+ /* noop */
+
+ return FALSE;
+}
+
+static gint
+pixbuf_height (ECellView *ecell_view,
+ gint model_col,
+ gint view_col,
+ gint row)
+{
+ GdkPixbuf *pixbuf;
+ if (row == -1) {
+ if (e_table_model_row_count (ecell_view->e_table_model) > 0) {
+ row = 0;
+ } else {
+ return 6;
+ }
+ }
+
+ pixbuf = (GdkPixbuf *) e_table_model_value_at (ecell_view->e_table_model, 1, row);
+ if (!pixbuf)
+ return 0;
+
+ /* We give ourselves 3 pixels of padding on either side */
+ return gdk_pixbuf_get_height (pixbuf) + 6;
+}
+
+/*
+ * ECell::print method
+ */
+static void
+pixbuf_print (ECellView *ecell_view,
+ GtkPrintContext *context,
+ gint model_col,
+ gint view_col,
+ gint row,
+ gdouble width,
+ gdouble height)
+{
+ GdkPixbuf *pixbuf;
+ gint scale;
+ cairo_t *cr = gtk_print_context_get_cairo_context (context);
+
+ pixbuf = (GdkPixbuf *) e_table_model_value_at (ecell_view->e_table_model, 1, row);
+ if (pixbuf == NULL)
+ return;
+
+ scale = gdk_pixbuf_get_height (pixbuf);
+ cairo_save (cr);
+ cairo_translate (cr, 0, (gdouble)(height - scale) / (gdouble) 2);
+ gdk_cairo_set_source_pixbuf (cr, pixbuf, (gdouble) scale, (gdouble) scale);
+ cairo_paint (cr);
+ cairo_restore (cr);
+}
+
+static gdouble
+pixbuf_print_height (ECellView *ecell_view,
+ GtkPrintContext *context,
+ gint model_col,
+ gint view_col,
+ gint row,
+ gdouble width)
+{
+ GdkPixbuf *pixbuf;
+
+ if (row == -1) {
+ if (e_table_model_row_count (ecell_view->e_table_model) > 0) {
+ row = 0;
+ } else {
+ return 6;
+ }
+ }
+
+ pixbuf = (GdkPixbuf *) e_table_model_value_at (ecell_view->e_table_model, 1, row);
+ if (!pixbuf)
+ return 0;
+
+ /* We give ourselves 3 pixels of padding on either side */
+ return gdk_pixbuf_get_height (pixbuf);
+}
+
+static gint
+pixbuf_max_width (ECellView *ecell_view,
+ gint model_col,
+ gint view_col)
+{
+ gint pw;
+ gint num_rows, i;
+ gint max_width = -1;
+
+ if (model_col == 0) {
+ num_rows = e_table_model_row_count (ecell_view->e_table_model);
+
+ for (i = 0; i <= num_rows; i++) {
+ GdkPixbuf *pixbuf = (GdkPixbuf *) e_table_model_value_at
+ (ecell_view->e_table_model,
+ 1,
+ i);
+ if (!pixbuf)
+ continue;
+ pw = gdk_pixbuf_get_width (pixbuf);
+ if (max_width < pw)
+ max_width = pw;
+ }
+ } else {
+ return -1;
+ }
+
+ return max_width;
+}
+
+static void
+pixbuf_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ ECellPixbuf *pixbuf;
+
+ pixbuf = E_CELL_PIXBUF (object);
+
+ switch (property_id) {
+ case PROP_SELECTED_COLUMN:
+ pixbuf->selected_column = g_value_get_int (value);
+ break;
+
+ case PROP_FOCUSED_COLUMN:
+ pixbuf->focused_column = g_value_get_int (value);
+ break;
+
+ case PROP_UNSELECTED_COLUMN:
+ pixbuf->unselected_column = g_value_get_int (value);
+ break;
+
+ default:
+ return;
+ }
+}
+
+/* Get_arg handler for the pixbuf item */
+static void
+pixbuf_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ ECellPixbuf *pixbuf;
+
+ pixbuf = E_CELL_PIXBUF (object);
+
+ switch (property_id) {
+ case PROP_SELECTED_COLUMN:
+ g_value_set_int (value, pixbuf->selected_column);
+ break;
+
+ case PROP_FOCUSED_COLUMN:
+ g_value_set_int (value, pixbuf->focused_column);
+ break;
+
+ case PROP_UNSELECTED_COLUMN:
+ g_value_set_int (value, pixbuf->unselected_column);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+e_cell_pixbuf_init (ECellPixbuf *ecp)
+{
+ ecp->selected_column = -1;
+ ecp->focused_column = -1;
+ ecp->unselected_column = -1;
+}
+
+static void
+e_cell_pixbuf_class_init (ECellPixbufClass *class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (class);
+ ECellClass *ecc = E_CELL_CLASS (class);
+
+ object_class->set_property = pixbuf_set_property;
+ object_class->get_property = pixbuf_get_property;
+
+ ecc->new_view = pixbuf_new_view;
+ ecc->kill_view = pixbuf_kill_view;
+ ecc->draw = pixbuf_draw;
+ ecc->event = pixbuf_event;
+ ecc->height = pixbuf_height;
+ ecc->print = pixbuf_print;
+ ecc->print_height = pixbuf_print_height;
+ ecc->max_width = pixbuf_max_width;
+
+ g_object_class_install_property (
+ object_class,
+ PROP_SELECTED_COLUMN,
+ g_param_spec_int (
+ "selected_column",
+ "Selected Column",
+ NULL,
+ 0, G_MAXINT, 0,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_FOCUSED_COLUMN,
+ g_param_spec_int (
+ "focused_column",
+ "Focused Column",
+ NULL,
+ 0, G_MAXINT, 0,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_UNSELECTED_COLUMN,
+ g_param_spec_int (
+ "unselected_column",
+ "Unselected Column",
+ NULL,
+ 0, G_MAXINT, 0,
+ G_PARAM_READWRITE));
+}
+
diff --git a/e-util/e-cell-pixbuf.h b/e-util/e-cell-pixbuf.h
new file mode 100644
index 0000000000..e76fcab2c7
--- /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 0000000000..19c32a658d
--- /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 0000000000..0b973d8f18
--- /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 0000000000..4bbb1318b3
--- /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 0000000000..00dd615607
--- /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 0000000000..02bde88638
--- /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 0000000000..b04134cb03
--- /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 0000000000..577d41ccf2
--- /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 0000000000..740b87fec7
--- /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 0000000000..2f2bcd359c
--- /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 0000000000..657836f142
--- /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 0000000000..085fb0cabe
--- /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 0000000000..044c14bfed
--- /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 0000000000..ef34a0a097
--- /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 0000000000..690d78f7d9
--- /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 0000000000..34046e1b38
--- /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 0000000000..4c1354259c
--- /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 0000000000..1423a592d8
--- /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 0000000000..54c5527875
--- /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 57b6976a1f..29bdc508c6 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 0000000000..ed0688b637
--- /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 0000000000..3f81d1f1ff
--- /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 2922a25ddb..a372601cb2 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/e-util/e-contact-map-window.c b/e-util/e-contact-map-window.c
new file mode 100644
index 0000000000..2e3aec5bcb
--- /dev/null
+++ b/e-util/e-contact-map-window.c
@@ -0,0 +1,500 @@
+/*
+ * e-contact-map-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) 2011 Dan Vratil <dvratil@redhat.com>
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#ifdef WITH_CONTACT_MAPS
+
+#include "e-contact-map.h"
+#include "e-contact-map-window.h"
+#include "e-contact-marker.h"
+
+#include <champlain/champlain.h>
+
+#include <string.h>
+
+#include <glib/gi18n.h>
+#include <glib-object.h>
+
+#define E_CONTACT_MAP_WINDOW_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_CONTACT_MAP_WINDOW, EContactMapWindowPrivate))
+
+G_DEFINE_TYPE (EContactMapWindow, e_contact_map_window, GTK_TYPE_WINDOW)
+
+struct _EContactMapWindowPrivate {
+ EContactMap *map;
+
+ GtkWidget *zoom_in_btn;
+ GtkWidget *zoom_out_btn;
+
+ GtkWidget *search_entry;
+ GtkListStore *completion_model;
+
+ GHashTable *hash_table; /* Hash table contact-name -> marker */
+
+ GtkWidget *spinner;
+ gint tasks_cnt;
+};
+
+enum {
+ SHOW_CONTACT_EDITOR,
+ LAST_SIGNAL
+};
+
+static gint signals[LAST_SIGNAL] = {0};
+
+static void
+marker_doubleclick_cb (ClutterActor *actor,
+ gpointer user_data)
+{
+ EContactMapWindow *window = user_data;
+ EContactMarker *marker;
+ const gchar *contact_uid;
+
+ marker = E_CONTACT_MARKER (actor);
+ contact_uid = e_contact_marker_get_contact_uid (marker);
+
+ g_signal_emit (window, signals[SHOW_CONTACT_EDITOR], 0, contact_uid);
+}
+
+static void
+book_contacts_received_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ EContactMapWindow *window = user_data;
+ EBookClient *client = E_BOOK_CLIENT (source_object);
+ GSList *contacts = NULL, *p;
+ GError *error = NULL;
+
+ if (!e_book_client_get_contacts_finish (client, result, &contacts, &error))
+ contacts = NULL;
+
+ if (error != NULL) {
+ g_warning (
+ "%s: Failed to get contacts: %s",
+ G_STRFUNC, error->message);
+ g_error_free (error);
+ }
+
+ for (p = contacts; p; p = p->next)
+ e_contact_map_add_contact (
+ window->priv->map, (EContact *) p->data);
+
+ e_client_util_free_object_slist (contacts);
+ g_object_unref (client);
+}
+
+static void
+contact_map_window_zoom_in_cb (GtkButton *button,
+ gpointer user_data)
+{
+ EContactMapWindow *window = user_data;
+ ChamplainView *view;
+
+ view = e_contact_map_get_view (window->priv->map);
+
+ champlain_view_zoom_in (view);
+}
+
+static void
+contact_map_window_zoom_out_cb (GtkButton *button,
+ gpointer user_data)
+{
+ EContactMapWindow *window = user_data;
+ ChamplainView *view;
+
+ view = e_contact_map_get_view (window->priv->map);
+
+ champlain_view_zoom_out (view);
+}
+static void
+zoom_level_changed_cb (ChamplainView *view,
+ GParamSpec *pspec,
+ gpointer user_data)
+{
+ EContactMapWindow *window = user_data;
+ gint zoom_level = champlain_view_get_zoom_level (view);
+
+ gtk_widget_set_sensitive (
+ window->priv->zoom_in_btn,
+ (zoom_level < champlain_view_get_max_zoom_level (view)));
+
+ gtk_widget_set_sensitive (
+ window->priv->zoom_out_btn,
+ (zoom_level > champlain_view_get_min_zoom_level (view)));
+}
+
+/**
+ * Add contact to hash_table only when EContactMap tells us
+ * that the contact has really been added to map.
+ */
+static void
+map_contact_added_cb (EContactMap *map,
+ ClutterActor *marker,
+ gpointer user_data)
+{
+ EContactMapWindowPrivate *priv = E_CONTACT_MAP_WINDOW (user_data)->priv;
+ const gchar *name;
+ GtkTreeIter iter;
+
+ name = champlain_label_get_text (CHAMPLAIN_LABEL (marker));
+
+ g_hash_table_insert (
+ priv->hash_table,
+ g_strdup (name), marker);
+
+ gtk_list_store_append (priv->completion_model, &iter);
+ gtk_list_store_set (
+ priv->completion_model, &iter,
+ 0, name, -1);
+
+ g_signal_connect (
+ marker, "double-clicked",
+ G_CALLBACK (marker_doubleclick_cb), user_data);
+
+ priv->tasks_cnt--;
+ if (priv->tasks_cnt == 0) {
+ gtk_spinner_stop (GTK_SPINNER (priv->spinner));
+ gtk_widget_hide (priv->spinner);
+ }
+}
+
+static void
+map_contact_removed_cb (EContactMap *map,
+ const gchar *name,
+ gpointer user_data)
+{
+ EContactMapWindowPrivate *priv = E_CONTACT_MAP_WINDOW (user_data)->priv;
+ GtkTreeIter iter;
+ GtkTreeModel *model = GTK_TREE_MODEL (priv->completion_model);
+
+ g_hash_table_remove (priv->hash_table, name);
+
+ if (gtk_tree_model_get_iter_first (model, &iter)) {
+ do {
+ gchar *name_str;
+ gtk_tree_model_get (model, &iter, 0, &name_str, -1);
+ if (g_ascii_strcasecmp (name_str, name) == 0) {
+ g_free (name_str);
+ gtk_list_store_remove (priv->completion_model, &iter);
+ break;
+ }
+ g_free (name_str);
+ } while (gtk_tree_model_iter_next (model, &iter));
+ }
+}
+
+static void
+map_contact_geocoding_started_cb (EContactMap *map,
+ ClutterActor *marker,
+ gpointer user_data)
+{
+ EContactMapWindowPrivate *priv = E_CONTACT_MAP_WINDOW (user_data)->priv;
+
+ gtk_spinner_start (GTK_SPINNER (priv->spinner));
+ gtk_widget_show (priv->spinner);
+
+ priv->tasks_cnt++;
+}
+
+static void
+map_contact_geocoding_failed_cb (EContactMap *map,
+ const gchar *name,
+ gpointer user_data)
+{
+ EContactMapWindowPrivate *priv = E_CONTACT_MAP_WINDOW (user_data)->priv;
+
+ priv->tasks_cnt--;
+
+ if (priv->tasks_cnt == 0) {
+ gtk_spinner_stop (GTK_SPINNER (priv->spinner));
+ gtk_widget_hide (priv->spinner);
+ }
+}
+
+static void
+contact_map_window_find_contact_cb (GtkButton *button,
+ gpointer user_data)
+{
+ EContactMapWindowPrivate *priv = E_CONTACT_MAP_WINDOW (user_data)->priv;
+ ClutterActor *marker;
+
+ marker = g_hash_table_lookup (
+ priv->hash_table,
+ gtk_entry_get_text (GTK_ENTRY (priv->search_entry)));
+
+ if (marker)
+ e_contact_map_zoom_on_marker (priv->map, marker);
+}
+
+static gboolean
+contact_map_window_entry_key_pressed_cb (GtkWidget *entry,
+ GdkEventKey *event,
+ gpointer user_data)
+{
+ if (event->keyval == GDK_KEY_Return)
+ contact_map_window_find_contact_cb (NULL, user_data);
+
+ return FALSE;
+}
+
+static gboolean
+entry_completion_match_selected_cb (GtkEntryCompletion *widget,
+ GtkTreeModel *model,
+ GtkTreeIter *iter,
+ gpointer user_data)
+{
+ GValue name_val = {0};
+ EContactMapWindowPrivate *priv = E_CONTACT_MAP_WINDOW (user_data)->priv;
+ const gchar *name;
+
+ gtk_tree_model_get_value (model, iter, 0, &name_val);
+ g_return_val_if_fail (G_VALUE_HOLDS_STRING (&name_val), FALSE);
+
+ name = g_value_get_string (&name_val);
+ gtk_entry_set_text (GTK_ENTRY (priv->search_entry), name);
+
+ contact_map_window_find_contact_cb (NULL, user_data);
+
+ return TRUE;
+}
+
+static void
+contact_map_window_finalize (GObject *object)
+{
+ EContactMapWindowPrivate *priv;
+
+ priv = E_CONTACT_MAP_WINDOW (object)->priv;
+
+ if (priv->hash_table) {
+ g_hash_table_destroy (priv->hash_table);
+ priv->hash_table = NULL;
+ }
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (e_contact_map_window_parent_class)->finalize (object);
+}
+
+static void
+contact_map_window_dispose (GObject *object)
+{
+ EContactMapWindowPrivate *priv;
+
+ priv = E_CONTACT_MAP_WINDOW (object)->priv;
+
+ if (priv->map) {
+ gtk_widget_destroy (GTK_WIDGET (priv->map));
+ priv->map = NULL;
+ }
+
+ if (priv->completion_model) {
+ g_object_unref (priv->completion_model);
+ priv->completion_model = NULL;
+ }
+
+ G_OBJECT_CLASS (e_contact_map_window_parent_class)->dispose (object);
+}
+
+static void
+e_contact_map_window_class_init (EContactMapWindowClass *class)
+{
+ GObjectClass *object_class;
+
+ g_type_class_add_private (class, sizeof (EContactMapWindowPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->finalize = contact_map_window_finalize;
+ object_class->dispose = contact_map_window_dispose;
+
+ signals[SHOW_CONTACT_EDITOR] = g_signal_new (
+ "show-contact-editor",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (EContactMapWindowClass, show_contact_editor),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__STRING,
+ G_TYPE_NONE, 1, G_TYPE_STRING);
+}
+
+static void
+e_contact_map_window_init (EContactMapWindow *window)
+{
+ EContactMapWindowPrivate *priv;
+ GtkWidget *map;
+ GtkWidget *button, *entry;
+ GtkWidget *hbox, *vbox, *viewport;
+ GtkEntryCompletion *entry_completion;
+ GtkListStore *completion_model;
+ ChamplainView *view;
+ GHashTable *hash_table;
+
+ priv = E_CONTACT_MAP_WINDOW_GET_PRIVATE (window);
+ window->priv = priv;
+
+ priv->tasks_cnt = 0;
+
+ hash_table = g_hash_table_new_full (
+ (GHashFunc) g_str_hash,
+ (GEqualFunc) g_str_equal,
+ (GDestroyNotify) g_free,
+ (GDestroyNotify) NULL);
+ priv->hash_table = hash_table;
+
+ gtk_window_set_title (GTK_WINDOW (window), _("Contacts Map"));
+ gtk_container_set_border_width (GTK_CONTAINER (window), 12);
+ gtk_widget_set_size_request (GTK_WIDGET (window), 800, 600);
+
+ /* The map view itself */
+ map = e_contact_map_new ();
+ view = e_contact_map_get_view (E_CONTACT_MAP (map));
+ champlain_view_set_zoom_level (view, 2);
+ priv->map = E_CONTACT_MAP (map);
+ g_signal_connect (
+ view, "notify::zoom-level",
+ G_CALLBACK (zoom_level_changed_cb), window);
+ g_signal_connect (
+ map, "contact-added",
+ G_CALLBACK (map_contact_added_cb), window);
+ g_signal_connect (
+ map, "contact-removed",
+ G_CALLBACK (map_contact_removed_cb), window);
+ g_signal_connect (
+ map, "geocoding-started",
+ G_CALLBACK (map_contact_geocoding_started_cb), window);
+ g_signal_connect (
+ map, "geocoding-failed",
+ G_CALLBACK (map_contact_geocoding_failed_cb), window);
+
+ /* HBox container */
+ hbox = gtk_hbox_new (FALSE, 7);
+
+ /* Spinner */
+ button = gtk_spinner_new ();
+ gtk_container_add (GTK_CONTAINER (hbox), button);
+ gtk_widget_hide (button);
+ priv->spinner = button;
+
+ /* Zoom-in button */
+ button = gtk_button_new_from_stock (GTK_STOCK_ZOOM_IN);
+ g_signal_connect (
+ button, "clicked",
+ G_CALLBACK (contact_map_window_zoom_in_cb), window);
+ priv->zoom_in_btn = button;
+ gtk_container_add (GTK_CONTAINER (hbox), button);
+
+ /* Zoom-out button */
+ button = gtk_button_new_from_stock (GTK_STOCK_ZOOM_OUT);
+ g_signal_connect (
+ button, "clicked",
+ G_CALLBACK (contact_map_window_zoom_out_cb), window);
+ priv->zoom_out_btn = button;
+ gtk_container_add (GTK_CONTAINER (hbox), button);
+
+ /* Completion model */
+ completion_model = gtk_list_store_new (1, G_TYPE_STRING);
+ priv->completion_model = completion_model;
+
+ /* Entry completion */
+ entry_completion = gtk_entry_completion_new ();
+ gtk_entry_completion_set_model (
+ entry_completion, GTK_TREE_MODEL (completion_model));
+ gtk_entry_completion_set_text_column (entry_completion, 0);
+ g_signal_connect (
+ entry_completion, "match-selected",
+ G_CALLBACK (entry_completion_match_selected_cb), window);
+
+ /* Search entry */
+ entry = gtk_entry_new ();
+ gtk_entry_set_completion (GTK_ENTRY (entry), entry_completion);
+ g_signal_connect (
+ entry, "key-press-event",
+ G_CALLBACK (contact_map_window_entry_key_pressed_cb), window);
+ window->priv->search_entry = entry;
+ gtk_container_add (GTK_CONTAINER (hbox), entry);
+
+ /* Search button */
+ button = gtk_button_new_from_stock (GTK_STOCK_FIND);
+ g_signal_connect (
+ button, "clicked",
+ G_CALLBACK (contact_map_window_find_contact_cb), window);
+ gtk_container_add (GTK_CONTAINER (hbox), button);
+
+ viewport = gtk_frame_new (NULL);
+ gtk_container_add (GTK_CONTAINER (viewport), map);
+
+ vbox = gtk_vbox_new (FALSE, 6);
+ gtk_container_add (GTK_CONTAINER (vbox), viewport);
+ gtk_box_pack_end (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
+
+ gtk_container_add (GTK_CONTAINER (window), vbox);
+
+ gtk_widget_show_all (vbox);
+ gtk_widget_hide (priv->spinner);
+}
+
+EContactMapWindow *
+e_contact_map_window_new (void)
+{
+ return g_object_new (
+ E_TYPE_CONTACT_MAP_WINDOW, NULL);
+}
+
+/**
+ * Gets all contacts from @book and puts them
+ * on the map view
+ */
+void
+e_contact_map_window_load_addressbook (EContactMapWindow *map,
+ EBookClient *book_client)
+{
+ EBookQuery *book_query;
+ gchar *query_string;
+
+ g_return_if_fail (E_IS_CONTACT_MAP_WINDOW (map));
+ g_return_if_fail (E_IS_BOOK_CLIENT (book_client));
+
+ /* Reference book, so that it does not get deleted before the callback is
+ * involved. The book is unrefed in the callback */
+ g_object_ref (book_client);
+
+ book_query = e_book_query_field_exists (E_CONTACT_ADDRESS);
+ query_string = e_book_query_to_string (book_query);
+ e_book_query_unref (book_query);
+
+ e_book_client_get_contacts (
+ book_client, query_string, NULL,
+ book_contacts_received_cb, map);
+
+ g_free (query_string);
+}
+
+EContactMap *
+e_contact_map_window_get_map (EContactMapWindow *window)
+{
+ g_return_val_if_fail (E_IS_CONTACT_MAP_WINDOW (window), NULL);
+
+ return window->priv->map;
+}
+
+#endif /* WITH_CONTACT_MAPS */
diff --git a/e-util/e-contact-map-window.h b/e-util/e-contact-map-window.h
new file mode 100644
index 0000000000..f18def51c2
--- /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 0000000000..24f5ac121f
--- /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 0000000000..90b7a6a911
--- /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/e-util/e-contact-marker.c b/e-util/e-contact-marker.c
new file mode 100644
index 0000000000..9ac9417c9f
--- /dev/null
+++ b/e-util/e-contact-marker.c
@@ -0,0 +1,624 @@
+/*
+ * e-contact-marker.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) 2008 Pierre-Luc Beaudoin <pierre-luc@pierlux.com>
+ * Copyright (C) 2011 Jiri Techet <techet@gmail.com>
+ * Copyright (C) 2011 Dan Vratil <dvratil@redhat.com>
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#ifdef WITH_CONTACT_MAPS
+
+#include "e-contact-marker.h"
+
+#include <champlain/champlain.h>
+#include <gtk/gtk.h>
+#include <clutter/clutter.h>
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <glib-object.h>
+#include <cairo.h>
+#include <math.h>
+#include <string.h>
+
+#define E_CONTACT_MARKER_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_CONTACT_MARKER, EContactMarkerPrivate))
+
+G_DEFINE_TYPE (EContactMarker, e_contact_marker, CHAMPLAIN_TYPE_LABEL);
+
+struct _EContactMarkerPrivate
+{
+ gchar *contact_uid;
+
+ ClutterActor *image;
+ ClutterActor *text_actor;
+
+ ClutterActor *shadow;
+ ClutterActor *background;
+
+ guint total_width;
+ guint total_height;
+
+ ClutterGroup *content_group;
+
+ guint redraw_id;
+};
+
+enum {
+ DOUBLE_CLICKED,
+ LAST_SIGNAL
+};
+
+static gint signals[LAST_SIGNAL] = {0};
+
+#define DEFAULT_FONT_NAME "Serif 9"
+
+static ClutterColor DEFAULT_COLOR = { 0x33, 0x33, 0x33, 0xff };
+
+#define RADIUS 10
+#define PADDING (RADIUS / 2)
+
+static gboolean
+contact_marker_clicked_cb (ClutterActor *actor,
+ ClutterEvent *event,
+ gpointer user_data)
+{
+ gint click_count = clutter_event_get_click_count (event);
+
+ if (click_count == 2)
+ g_signal_emit (E_CONTACT_MARKER (actor), signals[DOUBLE_CLICKED], 0);
+
+ return TRUE;
+}
+
+static ClutterActor *
+texture_new_from_pixbuf (GdkPixbuf *pixbuf,
+ GError **error)
+{
+ ClutterActor *texture = NULL;
+ const guchar *data;
+ gboolean has_alpha, success;
+ gint width, height, rowstride;
+ ClutterTextureFlags flags = 0;
+
+ data = gdk_pixbuf_get_pixels (pixbuf);
+ width = gdk_pixbuf_get_width (pixbuf);
+ height = gdk_pixbuf_get_height (pixbuf);
+ has_alpha = gdk_pixbuf_get_has_alpha (pixbuf);
+ rowstride = gdk_pixbuf_get_rowstride (pixbuf);
+
+ texture = clutter_texture_new ();
+ success = clutter_texture_set_from_rgb_data (
+ CLUTTER_TEXTURE (texture),
+ data, has_alpha, width, height, rowstride,
+ (has_alpha ? 4: 3), flags, NULL);
+
+ if (!success) {
+ clutter_actor_destroy (CLUTTER_ACTOR (texture));
+ texture = NULL;
+ }
+
+ return texture;
+}
+
+static ClutterActor *
+contact_photo_to_texture (EContactPhoto *photo)
+{
+ GdkPixbuf *pixbuf;
+
+ if (photo->type == E_CONTACT_PHOTO_TYPE_INLINED) {
+ GError *error = NULL;
+
+ GdkPixbufLoader *loader = gdk_pixbuf_loader_new ();
+ gdk_pixbuf_loader_write (
+ loader, 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 (error) {
+ g_error_free (error);
+ return NULL;
+ }
+ } else if (photo->type == E_CONTACT_PHOTO_TYPE_URI) {
+ GError *error = NULL;
+
+ pixbuf = gdk_pixbuf_new_from_file (photo->data.uri, &error);
+
+ if (error) {
+ g_error_free (error);
+ return NULL;
+ }
+ } else
+ return NULL;
+
+ if (pixbuf) {
+ ClutterActor *texture;
+ GError *error = NULL;
+
+ texture = texture_new_from_pixbuf (pixbuf, &error);
+ if (error) {
+ g_error_free (error);
+ g_object_unref (pixbuf);
+ return NULL;
+ }
+
+ g_object_unref (pixbuf);
+ return texture;
+ }
+
+ return NULL;
+}
+
+static void
+draw_box (cairo_t *cr,
+ gint width,
+ gint height,
+ gint point)
+{
+ cairo_move_to (cr, RADIUS, 0);
+ cairo_line_to (cr, width - RADIUS, 0);
+ cairo_arc (cr, width - RADIUS, RADIUS, RADIUS - 1, 3 * M_PI / 2.0, 0);
+ cairo_line_to (cr, width, height - RADIUS);
+ cairo_arc (cr, width - RADIUS, height - RADIUS, RADIUS - 1, 0, M_PI / 2.0);
+ cairo_line_to (cr, point, height);
+ cairo_line_to (cr, 0, height + point);
+ cairo_arc (cr, RADIUS, RADIUS, RADIUS - 1, M_PI, 3 * M_PI / 2.0);
+ cairo_close_path (cr);
+}
+
+static void
+draw_shadow (EContactMarker *marker,
+ gint width,
+ gint height,
+ gint point)
+{
+ EContactMarkerPrivate *priv = marker->priv;
+ ClutterActor *shadow = NULL;
+ cairo_t *cr;
+ gdouble slope;
+ gdouble scaling;
+ gint x;
+ cairo_matrix_t matrix;
+
+ slope = -0.3;
+ scaling = 0.65;
+ x = -40 * slope;
+
+ shadow = clutter_cairo_texture_new (width + x, (height + point));
+ cr = clutter_cairo_texture_create (CLUTTER_CAIRO_TEXTURE (shadow));
+
+ cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR);
+ cairo_paint (cr);
+ cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
+
+ cairo_matrix_init (&matrix, 1, 0, slope, scaling, x, 0);
+ cairo_set_matrix (cr, &matrix);
+
+ draw_box (cr, width, height, point);
+
+ cairo_set_source_rgba (cr, 0, 0, 0, 0.15);
+ cairo_fill (cr);
+
+ cairo_destroy (cr);
+
+ clutter_actor_set_position (shadow, 0, height / 2.0);
+
+ clutter_container_add_actor (CLUTTER_CONTAINER (priv->content_group), shadow);
+
+ if (priv->shadow != NULL) {
+ clutter_container_remove_actor (
+ CLUTTER_CONTAINER (priv->content_group),
+ priv->shadow);
+ }
+
+ priv->shadow = shadow;
+}
+
+static void
+draw_background (EContactMarker *marker,
+ gint width,
+ gint height,
+ gint point)
+{
+ EContactMarkerPrivate *priv = marker->priv;
+ ClutterActor *bg = NULL;
+ const ClutterColor *color;
+ ClutterColor darker_color;
+ cairo_t *cr;
+
+ bg = clutter_cairo_texture_new (width, height + point);
+ cr = clutter_cairo_texture_create (CLUTTER_CAIRO_TEXTURE (bg));
+
+ cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR);
+ cairo_paint (cr);
+ cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
+
+ /* If selected, add the selection color to the marker's color */
+ if (champlain_marker_get_selected (CHAMPLAIN_MARKER (marker)))
+ color = champlain_marker_get_selection_color ();
+ else
+ color = &DEFAULT_COLOR;
+
+ draw_box (cr, width, height, point);
+
+ clutter_color_darken (color, &darker_color);
+
+ cairo_set_source_rgba (
+ cr,
+ color->red / 255.0,
+ color->green / 255.0,
+ color->blue / 255.0,
+ color->alpha / 255.0);
+ cairo_fill_preserve (cr);
+
+ cairo_set_line_width (cr, 1.0);
+ cairo_set_source_rgba (
+ cr,
+ darker_color.red / 255.0,
+ darker_color.green / 255.0,
+ darker_color.blue / 255.0,
+ darker_color.alpha / 255.0);
+ cairo_stroke (cr);
+ cairo_destroy (cr);
+
+ clutter_container_add_actor (CLUTTER_CONTAINER (priv->content_group), bg);
+
+ if (priv->background != NULL) {
+ clutter_container_remove_actor (
+ CLUTTER_CONTAINER (priv->content_group),
+ priv->background);
+ }
+
+ priv->background = bg;
+}
+
+static void
+draw_marker (EContactMarker *marker)
+{
+ EContactMarkerPrivate *priv = marker->priv;
+ ChamplainLabel *label = CHAMPLAIN_LABEL (marker);
+ guint height = 0, point = 0;
+ guint total_width = 0, total_height = 0;
+ ClutterText *text;
+
+ if (priv->image) {
+ clutter_actor_set_position (priv->image, 2 *PADDING, 2 *PADDING);
+ if (clutter_actor_get_parent (priv->image) == NULL)
+ clutter_container_add_actor (
+ CLUTTER_CONTAINER (priv->content_group),
+ priv->image);
+ }
+
+ if (priv->text_actor == NULL) {
+ priv->text_actor = clutter_text_new_with_text (
+ "Serif 8",
+ champlain_label_get_text (label));
+ champlain_label_set_font_name (label, "Serif 8");
+ }
+
+ text = CLUTTER_TEXT (priv->text_actor);
+ clutter_text_set_text (
+ text,
+ champlain_label_get_text (label));
+ clutter_text_set_font_name (
+ text,
+ champlain_label_get_font_name (label));
+ clutter_text_set_line_alignment (text, PANGO_ALIGN_CENTER);
+ clutter_text_set_line_wrap (text, TRUE);
+ clutter_text_set_line_wrap_mode (text, PANGO_WRAP_WORD);
+ clutter_text_set_ellipsize (
+ text,
+ champlain_label_get_ellipsize (label));
+ clutter_text_set_attributes (
+ text,
+ champlain_label_get_attributes (label));
+ clutter_text_set_use_markup (
+ text,
+ champlain_label_get_use_markup (label));
+
+ if (priv->image) {
+ clutter_actor_set_width (
+ priv->text_actor,
+ clutter_actor_get_width (priv->image));
+ total_height = clutter_actor_get_height (priv->image) + 2 *PADDING +
+ clutter_actor_get_height (priv->text_actor) + 2 *PADDING;
+ total_width = clutter_actor_get_width (priv->image) + 4 *PADDING;
+ clutter_actor_set_position (
+ priv->text_actor, PADDING,
+ clutter_actor_get_height (priv->image) + 2 *PADDING + 3);
+ } else {
+ total_height = clutter_actor_get_height (priv->text_actor) + 2 *PADDING;
+ total_width = clutter_actor_get_width (priv->text_actor) + 4 *PADDING;
+ clutter_actor_set_position (priv->text_actor, 2 * PADDING, PADDING);
+ }
+
+ height += 2 * PADDING;
+ if (height > total_height)
+ total_height = height;
+
+ clutter_text_set_color (
+ CLUTTER_TEXT (priv->text_actor),
+ (champlain_marker_get_selected (CHAMPLAIN_MARKER (marker)) ?
+ champlain_marker_get_selection_text_color () :
+ champlain_label_get_text_color (CHAMPLAIN_LABEL (marker))));
+ if (clutter_actor_get_parent (priv->text_actor) == NULL)
+ clutter_container_add_actor (
+ CLUTTER_CONTAINER (priv->content_group),
+ priv->text_actor);
+
+ if (priv->text_actor == NULL && priv->image == NULL) {
+ total_width = 6 * PADDING;
+ total_height = 6 * PADDING;
+ }
+
+ point = (total_height + 2 * PADDING) / 4.0;
+ priv->total_width = total_width;
+ priv->total_height = total_height;
+
+ draw_shadow (marker, total_width, total_height, point);
+ draw_background (marker, total_width, total_height, point);
+
+ if (priv->text_actor != NULL && priv->background != NULL)
+ clutter_actor_raise (priv->text_actor, priv->background);
+ if (priv->image != NULL && priv->background != NULL)
+ clutter_actor_raise (priv->image, priv->background);
+
+ clutter_actor_set_anchor_point (CLUTTER_ACTOR (marker), 0, total_height + point);
+}
+
+static gboolean
+redraw_on_idle (gpointer gobject)
+{
+ EContactMarker *marker = E_CONTACT_MARKER (gobject);
+
+ draw_marker (marker);
+ marker->priv->redraw_id = 0;
+ return FALSE;
+}
+
+static void
+queue_redraw (EContactMarker *marker)
+{
+ EContactMarkerPrivate *priv = marker->priv;
+
+ if (!priv->redraw_id) {
+ priv->redraw_id = g_idle_add_full (
+ G_PRIORITY_DEFAULT,
+ (GSourceFunc) redraw_on_idle,
+ g_object_ref (marker),
+ (GDestroyNotify) g_object_unref);
+ }
+}
+
+static void
+allocate (ClutterActor *self,
+ const ClutterActorBox *box,
+ ClutterAllocationFlags flags)
+{
+ ClutterActorBox child_box;
+ EContactMarkerPrivate *priv = E_CONTACT_MARKER (self)->priv;
+
+ CLUTTER_ACTOR_CLASS (e_contact_marker_parent_class)->allocate (self, box, flags);
+
+ child_box.x1 = 0;
+ child_box.x2 = box->x2 - box->x1;
+ child_box.y1 = 0;
+ child_box.y2 = box->y2 - box->y1;
+ clutter_actor_allocate (CLUTTER_ACTOR (priv->content_group), &child_box, flags);
+}
+
+static void
+paint (ClutterActor *self)
+{
+ EContactMarkerPrivate *priv = E_CONTACT_MARKER (self)->priv;
+
+ clutter_actor_paint (CLUTTER_ACTOR (priv->content_group));
+}
+
+static void
+map (ClutterActor *self)
+{
+ EContactMarkerPrivate *priv = E_CONTACT_MARKER (self)->priv;
+
+ CLUTTER_ACTOR_CLASS (e_contact_marker_parent_class)->map (self);
+
+ clutter_actor_map (CLUTTER_ACTOR (priv->content_group));
+}
+
+static void
+unmap (ClutterActor *self)
+{
+ EContactMarkerPrivate *priv = E_CONTACT_MARKER (self)->priv;
+
+ CLUTTER_ACTOR_CLASS (e_contact_marker_parent_class)->unmap (self);
+
+ clutter_actor_unmap (CLUTTER_ACTOR (priv->content_group));
+}
+
+static void
+pick (ClutterActor *self,
+ const ClutterColor *color)
+{
+ EContactMarkerPrivate *priv = E_CONTACT_MARKER (self)->priv;
+ gfloat width, height;
+
+ if (!clutter_actor_should_pick_paint (self))
+ return;
+
+ width = priv->total_width;
+ height = priv->total_height;
+
+ cogl_path_new ();
+
+ cogl_set_source_color4ub (
+ color->red,
+ color->green,
+ color->blue,
+ color->alpha);
+
+ cogl_path_move_to (RADIUS, 0);
+ cogl_path_line_to (width - RADIUS, 0);
+ cogl_path_arc (width - RADIUS, RADIUS, RADIUS, RADIUS, -90, 0);
+ cogl_path_line_to (width, height - RADIUS);
+ cogl_path_arc (width - RADIUS, height - RADIUS, RADIUS, RADIUS, 0, 90);
+ cogl_path_line_to (RADIUS, height);
+ cogl_path_arc (RADIUS, height - RADIUS, RADIUS, RADIUS, 90, 180);
+ cogl_path_line_to (0, RADIUS);
+ cogl_path_arc (RADIUS, RADIUS, RADIUS, RADIUS, 180, 270);
+ cogl_path_close ();
+ cogl_path_fill ();
+}
+
+static void
+notify_selected (GObject *gobject,
+ G_GNUC_UNUSED GParamSpec *pspec,
+ G_GNUC_UNUSED gpointer user_data)
+{
+ queue_redraw (E_CONTACT_MARKER (gobject));
+}
+
+static void
+e_contact_marker_finalize (GObject *object)
+{
+ EContactMarkerPrivate *priv = E_CONTACT_MARKER (object)->priv;
+
+ if (priv->contact_uid) {
+ g_free (priv->contact_uid);
+ priv->contact_uid = NULL;
+ }
+
+ if (priv->redraw_id) {
+ g_source_remove (priv->redraw_id);
+ priv->redraw_id = 0;
+ }
+
+ G_OBJECT_CLASS (e_contact_marker_parent_class)->finalize (object);
+}
+
+static void
+e_contact_marker_dispose (GObject *object)
+{
+ EContactMarkerPrivate *priv = E_CONTACT_MARKER (object)->priv;
+
+ priv->background = NULL;
+ priv->shadow = NULL;
+ priv->text_actor = NULL;
+
+ if (priv->content_group) {
+ clutter_actor_unparent (CLUTTER_ACTOR (priv->content_group));
+ priv->content_group = NULL;
+ }
+
+ G_OBJECT_CLASS (e_contact_marker_parent_class)->dispose (object);
+}
+
+static void
+e_contact_marker_class_init (EContactMarkerClass *class)
+{
+ ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (class);
+ GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+ g_type_class_add_private (class, sizeof (EContactMarkerPrivate));
+
+ object_class->dispose = e_contact_marker_dispose;
+ object_class->finalize = e_contact_marker_finalize;
+
+ actor_class->paint = paint;
+ actor_class->allocate = allocate;
+ actor_class->map = map;
+ actor_class->unmap = unmap;
+ actor_class->pick = pick;
+
+ signals[DOUBLE_CLICKED] = g_signal_new (
+ "double-clicked",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (EContactMarkerClass, double_clicked),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+}
+
+static void
+e_contact_marker_init (EContactMarker *marker)
+{
+ EContactMarkerPrivate *priv;
+
+ priv = E_CONTACT_MARKER_GET_PRIVATE (marker);
+
+ marker->priv = priv;
+ priv->contact_uid = NULL;
+ priv->image = NULL;
+ priv->background = NULL;
+ priv->shadow = NULL;
+ priv->text_actor = NULL;
+ priv->content_group = CLUTTER_GROUP (clutter_group_new ());
+ priv->redraw_id = 0;
+
+ clutter_actor_set_parent (
+ CLUTTER_ACTOR (priv->content_group), CLUTTER_ACTOR (marker));
+ clutter_actor_queue_relayout (CLUTTER_ACTOR (marker));
+
+ priv->total_width = 0;
+ priv->total_height = 0;
+
+ g_signal_connect (
+ marker, "notify::selected",
+ G_CALLBACK (notify_selected), NULL);
+ g_signal_connect (
+ marker, "button-release-event",
+ G_CALLBACK (contact_marker_clicked_cb), NULL);
+}
+
+ClutterActor *
+e_contact_marker_new (const gchar *name,
+ const gchar *contact_uid,
+ EContactPhoto *photo)
+{
+ ClutterActor *marker = CLUTTER_ACTOR (g_object_new (E_TYPE_CONTACT_MARKER, NULL));
+ EContactMarkerPrivate *priv = E_CONTACT_MARKER (marker)->priv;
+
+ g_return_val_if_fail (name && *name, NULL);
+ g_return_val_if_fail (contact_uid && *contact_uid, NULL);
+
+ champlain_label_set_text (CHAMPLAIN_LABEL (marker), name);
+ priv->contact_uid = g_strdup (contact_uid);
+ if (photo)
+ priv->image = contact_photo_to_texture (photo);
+
+ queue_redraw (E_CONTACT_MARKER (marker));
+
+ return marker;
+}
+
+const gchar *
+e_contact_marker_get_contact_uid (EContactMarker *marker)
+{
+ g_return_val_if_fail (marker && E_IS_CONTACT_MARKER (marker), NULL);
+
+ return marker->priv->contact_uid;
+}
+
+#endif /* WITH_CONTACT_MAPS */
diff --git a/e-util/e-contact-marker.h b/e-util/e-contact-marker.h
new file mode 100644
index 0000000000..e6e10db855
--- /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 0000000000..4e49399e82
--- /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 0000000000..c0754afab0
--- /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 0000000000..ab6085f44b
--- /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 0000000000..b415847b23
--- /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 fcd93ebfc6..d0066fbc70 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 28eed151b3..5974349e06 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 0000000000..82801f2091
--- /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 0000000000..630db11f58
--- /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 f4f04b0eac..36c1730a09 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 5b3f650ed2..4c8ade4426 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 0b834c879d..28caded6fe 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 724680a280..4ec56d2829 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 b8dd278c87..5d1cb3a8d6 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 e1e8b29872..5d5df061e3 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/e-util/e-filter-code.c b/e-util/e-filter-code.c
new file mode 100644
index 0000000000..0352703638
--- /dev/null
+++ b/e-util/e-filter-code.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:
+ * 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-filter-code.h"
+#include "e-filter-part.h"
+
+G_DEFINE_TYPE (
+ EFilterCode,
+ e_filter_code,
+ E_TYPE_FILTER_INPUT)
+
+/* here, the string IS the code */
+static void
+filter_code_build_code (EFilterElement *element,
+ GString *out,
+ EFilterPart *part)
+{
+ GList *l;
+ EFilterInput *fi = (EFilterInput *) element;
+ gboolean is_rawcode = fi->type && g_str_equal (fi->type, "rawcode");
+
+ if (!is_rawcode)
+ g_string_append (out, "(match-all ");
+
+ l = fi->values;
+ while (l) {
+ g_string_append (out, (gchar *) l->data);
+ l = g_list_next (l);
+ }
+
+ if (!is_rawcode)
+ g_string_append (out, ")");
+}
+
+/* and we have no value */
+static void
+filter_code_format_sexp (EFilterElement *element,
+ GString *out)
+{
+}
+
+static void
+e_filter_code_class_init (EFilterCodeClass *class)
+{
+ EFilterElementClass *filter_element_class;
+
+ filter_element_class = E_FILTER_ELEMENT_CLASS (class);
+ filter_element_class->build_code = filter_code_build_code;
+ filter_element_class->format_sexp = filter_code_format_sexp;
+}
+
+static void
+e_filter_code_init (EFilterCode *code)
+{
+ EFilterInput *input = E_FILTER_INPUT (code);
+
+ input->type = (gchar *) xmlStrdup ((xmlChar *) "code");
+}
+
+/**
+ * filter_code_new:
+ *
+ * Create a new EFilterCode object.
+ *
+ * Return value: A new #EFilterCode object.
+ **/
+EFilterCode *
+e_filter_code_new (gboolean raw_code)
+{
+ EFilterCode *fc = g_object_new (E_TYPE_FILTER_CODE, NULL, NULL);
+
+ if (fc && raw_code) {
+ xmlFree (((EFilterInput *) fc)->type);
+ ((EFilterInput *) fc)->type = (gchar *) xmlStrdup ((xmlChar *)"rawcode");
+ }
+
+ return fc;
+}
diff --git a/e-util/e-filter-code.h b/e-util/e-filter-code.h
new file mode 100644
index 0000000000..45e1922ba4
--- /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/e-util/e-filter-color.c b/e-util/e-filter-color.c
new file mode 100644
index 0000000000..213530fbb2
--- /dev/null
+++ b/e-util/e-filter-color.c
@@ -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)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <gtk/gtk.h>
+
+#include "e-filter-color.h"
+
+G_DEFINE_TYPE (
+ EFilterColor,
+ e_filter_color,
+ E_TYPE_FILTER_ELEMENT)
+
+static void
+set_color (GtkColorButton *color_button,
+ EFilterColor *fc)
+{
+ gtk_color_button_get_color (color_button, &fc->color);
+}
+
+static gint
+filter_color_eq (EFilterElement *element_a,
+ EFilterElement *element_b)
+{
+ EFilterColor *color_a = E_FILTER_COLOR (element_a);
+ EFilterColor *color_b = E_FILTER_COLOR (element_b);
+
+ return E_FILTER_ELEMENT_CLASS (e_filter_color_parent_class)->
+ eq (element_a, element_b) &&
+ gdk_color_equal (&color_a->color, &color_b->color);
+}
+
+static xmlNodePtr
+filter_color_xml_encode (EFilterElement *element)
+{
+ EFilterColor *fc = E_FILTER_COLOR (element);
+ xmlNodePtr value;
+ gchar spec[16];
+
+ g_snprintf (
+ spec, sizeof (spec), "#%04x%04x%04x",
+ fc->color.red, fc->color.green, fc->color.blue);
+
+ value = xmlNewNode (NULL, (xmlChar *)"value");
+ xmlSetProp (value, (xmlChar *)"type", (xmlChar *)"colour");
+ xmlSetProp (value, (xmlChar *)"name", (xmlChar *) element->name);
+ xmlSetProp (value, (xmlChar *)"spec", (xmlChar *) spec);
+
+ return value;
+}
+
+static gint
+filter_color_xml_decode (EFilterElement *element,
+ xmlNodePtr node)
+{
+ EFilterColor *fc = E_FILTER_COLOR (element);
+ xmlChar *prop;
+
+ xmlFree (element->name);
+ element->name = (gchar *) xmlGetProp (node, (xmlChar *)"name");
+
+ prop = xmlGetProp (node, (xmlChar *)"spec");
+ if (prop != NULL) {
+ gdk_color_parse ((gchar *) prop, &fc->color);
+ xmlFree (prop);
+ } else {
+ /* try reading the old RGB properties */
+ prop = xmlGetProp (node, (xmlChar *)"red");
+ sscanf ((gchar *) prop, "%" G_GINT16_MODIFIER "x", &fc->color.red);
+ xmlFree (prop);
+ prop = xmlGetProp (node, (xmlChar *)"green");
+ sscanf ((gchar *) prop, "%" G_GINT16_MODIFIER "x", &fc->color.green);
+ xmlFree (prop);
+ prop = xmlGetProp (node, (xmlChar *)"blue");
+ sscanf ((gchar *) prop, "%" G_GINT16_MODIFIER "x", &fc->color.blue);
+ xmlFree (prop);
+ }
+
+ return 0;
+}
+
+static GtkWidget *
+filter_color_get_widget (EFilterElement *element)
+{
+ EFilterColor *fc = E_FILTER_COLOR (element);
+ GtkWidget *color_button;
+
+ color_button = gtk_color_button_new_with_color (&fc->color);
+ gtk_widget_show (color_button);
+
+ g_signal_connect (
+ color_button, "color_set",
+ G_CALLBACK (set_color), element);
+
+ return color_button;
+}
+
+static void
+filter_color_format_sexp (EFilterElement *element,
+ GString *out)
+{
+ EFilterColor *fc = E_FILTER_COLOR (element);
+ gchar spec[16];
+
+ g_snprintf (
+ spec, sizeof (spec), "#%04x%04x%04x",
+ fc->color.red, fc->color.green, fc->color.blue);
+ camel_sexp_encode_string (out, spec);
+}
+
+static void
+e_filter_color_class_init (EFilterColorClass *class)
+{
+ EFilterElementClass *filter_element_class;
+
+ filter_element_class = E_FILTER_ELEMENT_CLASS (class);
+ filter_element_class->eq = filter_color_eq;
+ filter_element_class->xml_encode = filter_color_xml_encode;
+ filter_element_class->xml_decode = filter_color_xml_decode;
+ filter_element_class->get_widget = filter_color_get_widget;
+ filter_element_class->format_sexp = filter_color_format_sexp;
+}
+
+static void
+e_filter_color_init (EFilterColor *filter)
+{
+}
+
+/**
+ * filter_color_new:
+ *
+ * Create a new EFilterColor object.
+ *
+ * Return value: A new #EFilterColor object.
+ **/
+EFilterColor *
+e_filter_color_new (void)
+{
+ return g_object_new (E_TYPE_FILTER_COLOR, NULL);
+}
diff --git a/e-util/e-filter-color.h b/e-util/e-filter-color.h
new file mode 100644
index 0000000000..acecf7d08c
--- /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 0000000000..d135358e2b
--- /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 0000000000..ecc15bfdc9
--- /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/e-util/e-filter-element.c b/e-util/e-filter-element.c
new file mode 100644
index 0000000000..e00651ec03
--- /dev/null
+++ b/e-util/e-filter-element.c
@@ -0,0 +1,446 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You 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 "e-filter-element.h"
+#include "e-filter-part.h"
+
+struct _element_type {
+ gchar *name;
+
+ EFilterElementFunc create;
+ gpointer data;
+};
+
+G_DEFINE_TYPE (
+ EFilterElement,
+ e_filter_element,
+ G_TYPE_OBJECT)
+
+static gboolean
+filter_element_validate (EFilterElement *element,
+ EAlert **alert)
+{
+ return TRUE;
+}
+
+static gint
+filter_element_eq (EFilterElement *element_a,
+ EFilterElement *element_b)
+{
+ return (g_strcmp0 (element_a->name, element_b->name) == 0);
+}
+
+static void
+filter_element_xml_create (EFilterElement *element,
+ xmlNodePtr node)
+{
+ element->name = (gchar *) xmlGetProp (node, (xmlChar *) "name");
+}
+
+static EFilterElement *
+filter_element_clone (EFilterElement *element)
+{
+ EFilterElement *clone;
+ xmlNodePtr node;
+
+ clone = g_object_new (G_OBJECT_TYPE (element), NULL);
+
+ node = e_filter_element_xml_encode (element);
+ e_filter_element_xml_decode (clone, node);
+ xmlFreeNodeList (node);
+
+ return clone;
+}
+
+/* This is somewhat hackish, implement all the base cases in here */
+#include "e-filter-input.h"
+#include "e-filter-option.h"
+#include "e-filter-code.h"
+#include "e-filter-color.h"
+#include "e-filter-datespec.h"
+#include "e-filter-int.h"
+#include "e-filter-file.h"
+
+static void
+filter_element_copy_value (EFilterElement *dst_element,
+ EFilterElement *src_element)
+{
+ if (E_IS_FILTER_INPUT (src_element)) {
+ EFilterInput *src_input;
+
+ src_input = E_FILTER_INPUT (src_element);
+
+ if (E_IS_FILTER_INPUT (dst_element)) {
+ EFilterInput *dst_input;
+
+ dst_input = E_FILTER_INPUT (dst_element);
+
+ if (src_input->values)
+ e_filter_input_set_value (
+ dst_input,
+ src_input->values->data);
+
+ } else if (E_IS_FILTER_INT (dst_element)) {
+ EFilterInt *dst_int;
+
+ dst_int = E_FILTER_INT (dst_element);
+
+ dst_int->val = atoi (src_input->values->data);
+ }
+
+ } else if (E_IS_FILTER_COLOR (src_element)) {
+ EFilterColor *src_color;
+
+ src_color = E_FILTER_COLOR (src_element);
+
+ if (E_IS_FILTER_COLOR (dst_element)) {
+ EFilterColor *dst_color;
+
+ dst_color = E_FILTER_COLOR (dst_element);
+
+ dst_color->color = src_color->color;
+ }
+
+ } else if (E_IS_FILTER_DATESPEC (src_element)) {
+ EFilterDatespec *src_datespec;
+
+ src_datespec = E_FILTER_DATESPEC (src_element);
+
+ if (E_IS_FILTER_DATESPEC (dst_element)) {
+ EFilterDatespec *dst_datespec;
+
+ dst_datespec = E_FILTER_DATESPEC (dst_element);
+
+ dst_datespec->type = src_datespec->type;
+ dst_datespec->value = src_datespec->value;
+ }
+
+ } else if (E_IS_FILTER_INT (src_element)) {
+ EFilterInt *src_int;
+
+ src_int = E_FILTER_INT (src_element);
+
+ if (E_IS_FILTER_INT (dst_element)) {
+ EFilterInt *dst_int;
+
+ dst_int = E_FILTER_INT (dst_element);
+
+ dst_int->val = src_int->val;
+
+ } else if (E_IS_FILTER_INPUT (dst_element)) {
+ EFilterInput *dst_input;
+ gchar *values;
+
+ dst_input = E_FILTER_INPUT (dst_element);
+
+ values = g_strdup_printf ("%d", src_int->val);
+ e_filter_input_set_value (dst_input, values);
+ g_free (values);
+ }
+
+ } else if (E_IS_FILTER_OPTION (src_element)) {
+ EFilterOption *src_option;
+
+ src_option = E_FILTER_OPTION (src_element);
+
+ if (E_IS_FILTER_OPTION (dst_element)) {
+ EFilterOption *dst_option;
+
+ dst_option = E_FILTER_OPTION (dst_element);
+
+ if (src_option->current)
+ e_filter_option_set_current (
+ dst_option,
+ src_option->current->value);
+ }
+ }
+}
+
+static void
+filter_element_finalize (GObject *object)
+{
+ EFilterElement *element = E_FILTER_ELEMENT (object);
+
+ xmlFree (element->name);
+
+ /* Chain up to parent's finalize () method. */
+ G_OBJECT_CLASS (e_filter_element_parent_class)->finalize (object);
+}
+
+static void
+e_filter_element_class_init (EFilterElementClass *class)
+{
+ GObjectClass *object_class;
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->finalize = filter_element_finalize;
+
+ class->validate = filter_element_validate;
+ class->eq = filter_element_eq;
+ class->xml_create = filter_element_xml_create;
+ class->clone = filter_element_clone;
+ class->copy_value = filter_element_copy_value;
+}
+
+static void
+e_filter_element_init (EFilterElement *element)
+{
+}
+
+/**
+ * filter_element_new:
+ *
+ * Create a new EFilterElement object.
+ *
+ * Return value: A new #EFilterElement object.
+ **/
+EFilterElement *
+e_filter_element_new (void)
+{
+ return g_object_new (E_TYPE_FILTER_ELEMENT, NULL);
+}
+
+gboolean
+e_filter_element_validate (EFilterElement *element,
+ EAlert **alert)
+{
+ EFilterElementClass *class;
+
+ g_return_val_if_fail (E_IS_FILTER_ELEMENT (element), FALSE);
+
+ class = E_FILTER_ELEMENT_GET_CLASS (element);
+ g_return_val_if_fail (class->validate != NULL, FALSE);
+
+ return class->validate (element, alert);
+}
+
+gint
+e_filter_element_eq (EFilterElement *element_a,
+ EFilterElement *element_b)
+{
+ EFilterElementClass *class;
+
+ g_return_val_if_fail (E_IS_FILTER_ELEMENT (element_a), FALSE);
+ g_return_val_if_fail (E_IS_FILTER_ELEMENT (element_b), FALSE);
+
+ /* The elements must be the same type. */
+ if (G_OBJECT_TYPE (element_a) != G_OBJECT_TYPE (element_b))
+ return FALSE;
+
+ class = E_FILTER_ELEMENT_GET_CLASS (element_a);
+ g_return_val_if_fail (class->eq != NULL, FALSE);
+
+ return class->eq (element_a, element_b);
+}
+
+/**
+ * filter_element_xml_create:
+ * @fe: filter element
+ * @node: xml node
+ *
+ * Create a new filter element based on an xml definition of
+ * that element.
+ **/
+void
+e_filter_element_xml_create (EFilterElement *element,
+ xmlNodePtr node)
+{
+ EFilterElementClass *class;
+
+ g_return_if_fail (E_IS_FILTER_ELEMENT (element));
+ g_return_if_fail (node != NULL);
+
+ class = E_FILTER_ELEMENT_GET_CLASS (element);
+ g_return_if_fail (class->xml_create != NULL);
+
+ class->xml_create (element, node);
+}
+
+/**
+ * filter_element_xml_encode:
+ * @fe: filter element
+ *
+ * Encode the values of a filter element into xml format.
+ *
+ * Return value:
+ **/
+xmlNodePtr
+e_filter_element_xml_encode (EFilterElement *element)
+{
+ EFilterElementClass *class;
+
+ g_return_val_if_fail (E_IS_FILTER_ELEMENT (element), NULL);
+
+ class = E_FILTER_ELEMENT_GET_CLASS (element);
+ g_return_val_if_fail (class->xml_encode != NULL, NULL);
+
+ return class->xml_encode (element);
+}
+
+/**
+ * filter_element_xml_decode:
+ * @fe: filter element
+ * @node: xml node
+ *
+ * Decode the values of a fitler element from xml format.
+ *
+ * Return value:
+ **/
+gint
+e_filter_element_xml_decode (EFilterElement *element,
+ xmlNodePtr node)
+{
+ EFilterElementClass *class;
+
+ g_return_val_if_fail (E_IS_FILTER_ELEMENT (element), FALSE);
+ g_return_val_if_fail (node != NULL, FALSE);
+
+ class = E_FILTER_ELEMENT_GET_CLASS (element);
+ g_return_val_if_fail (class->xml_decode != NULL, FALSE);
+
+ return class->xml_decode (element, node);
+}
+
+/**
+ * filter_element_clone:
+ * @fe: filter element
+ *
+ * Clones the EFilterElement @fe.
+ *
+ * Return value:
+ **/
+EFilterElement *
+e_filter_element_clone (EFilterElement *element)
+{
+ EFilterElementClass *class;
+
+ g_return_val_if_fail (E_IS_FILTER_ELEMENT (element), NULL);
+
+ class = E_FILTER_ELEMENT_GET_CLASS (element);
+ g_return_val_if_fail (class->clone != NULL, NULL);
+
+ return class->clone (element);
+}
+
+/**
+ * filter_element_get_widget:
+ * @fe: filter element
+ * @node: xml node
+ *
+ * Create a widget to represent this element.
+ *
+ * Return value:
+ **/
+GtkWidget *
+e_filter_element_get_widget (EFilterElement *element)
+{
+ EFilterElementClass *class;
+
+ g_return_val_if_fail (E_IS_FILTER_ELEMENT (element), NULL);
+
+ class = E_FILTER_ELEMENT_GET_CLASS (element);
+ g_return_val_if_fail (class->get_widget != NULL, NULL);
+
+ return class->get_widget (element);
+}
+
+/**
+ * filter_element_build_code:
+ * @fe: filter element
+ * @out: output buffer
+ * @ff:
+ *
+ * Add the code representing this element to the output string @out.
+ **/
+void
+e_filter_element_build_code (EFilterElement *element,
+ GString *out,
+ EFilterPart *part)
+{
+ EFilterElementClass *class;
+
+ g_return_if_fail (E_IS_FILTER_ELEMENT (element));
+ g_return_if_fail (out != NULL);
+ g_return_if_fail (E_IS_FILTER_PART (part));
+
+ class = E_FILTER_ELEMENT_GET_CLASS (element);
+
+ /* This method is optional. */
+ if (class->build_code != NULL)
+ class->build_code (element, out, part);
+}
+
+/**
+ * filter_element_format_sexp:
+ * @fe: filter element
+ * @out: output buffer
+ *
+ * Format the value(s) of this element in a method suitable for the context of
+ * sexp where it is used. Usually as space separated, double-quoted strings.
+ **/
+void
+e_filter_element_format_sexp (EFilterElement *element,
+ GString *out)
+{
+ EFilterElementClass *class;
+
+ g_return_if_fail (E_IS_FILTER_ELEMENT (element));
+ g_return_if_fail (out != NULL);
+
+ class = E_FILTER_ELEMENT_GET_CLASS (element);
+ g_return_if_fail (class->format_sexp != NULL);
+
+ class->format_sexp (element, out);
+}
+
+void
+e_filter_element_set_data (EFilterElement *element,
+ gpointer data)
+{
+ g_return_if_fail (E_IS_FILTER_ELEMENT (element));
+
+ element->data = data;
+}
+
+/* only copies the value, not the name/type */
+void
+e_filter_element_copy_value (EFilterElement *dst_element,
+ EFilterElement *src_element)
+{
+ EFilterElementClass *class;
+
+ g_return_if_fail (E_IS_FILTER_ELEMENT (dst_element));
+ g_return_if_fail (E_IS_FILTER_ELEMENT (src_element));
+
+ class = E_FILTER_ELEMENT_GET_CLASS (dst_element);
+ g_return_if_fail (class->copy_value != NULL);
+
+ class->copy_value (dst_element, src_element);
+}
diff --git a/e-util/e-filter-element.h b/e-util/e-filter-element.h
new file mode 100644
index 0000000000..ecec9db7b9
--- /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 0000000000..8e46b52a97
--- /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 0000000000..c78062b4e0
--- /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 0000000000..424363e2dd
--- /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 0000000000..782be404e9
--- /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/e-util/e-filter-int.c b/e-util/e-filter-int.c
new file mode 100644
index 0000000000..ba4eacde2e
--- /dev/null
+++ b/e-util/e-filter-int.c
@@ -0,0 +1,230 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You 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 <stdlib.h>
+#include <gtk/gtk.h>
+
+#include "e-filter-int.h"
+
+G_DEFINE_TYPE (
+ EFilterInt,
+ e_filter_int,
+ E_TYPE_FILTER_ELEMENT)
+
+static void
+filter_int_spin_changed (GtkSpinButton *spin_button,
+ EFilterElement *element)
+{
+ EFilterInt *filter_int = E_FILTER_INT (element);
+
+ filter_int->val = gtk_spin_button_get_value_as_int (spin_button);
+}
+
+static void
+filter_int_finalize (GObject *object)
+{
+ EFilterInt *filter_int = E_FILTER_INT (object);
+
+ g_free (filter_int->type);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (e_filter_int_parent_class)->finalize (object);
+}
+
+static gint
+filter_int_eq (EFilterElement *element_a,
+ EFilterElement *element_b)
+{
+ EFilterInt *filter_int_a = E_FILTER_INT (element_a);
+ EFilterInt *filter_int_b = E_FILTER_INT (element_b);
+
+ /* Chain up to parent's eq() method. */
+ if (!E_FILTER_ELEMENT_CLASS (e_filter_int_parent_class)->
+ eq (element_a, element_b))
+ return FALSE;
+
+ return (filter_int_a->val == filter_int_b->val);
+}
+
+static EFilterElement *
+filter_int_clone (EFilterElement *element)
+{
+ EFilterInt *filter_int = E_FILTER_INT (element);
+ EFilterInt *clone;
+
+ clone = (EFilterInt *) e_filter_int_new_type (
+ filter_int->type, filter_int->min, filter_int->max);
+ clone->val = filter_int->val;
+
+ E_FILTER_ELEMENT (clone)->name = g_strdup (element->name);
+
+ return E_FILTER_ELEMENT (clone);
+}
+
+static xmlNodePtr
+filter_int_xml_encode (EFilterElement *element)
+{
+ EFilterInt *filter_int = E_FILTER_INT (element);
+ xmlNodePtr value;
+ gchar intval[32];
+ const gchar *type;
+
+ type = filter_int->type ? filter_int->type : "integer";
+
+ value = xmlNewNode (NULL, (xmlChar *)"value");
+ xmlSetProp (value, (xmlChar *) "name", (xmlChar *) element->name);
+ xmlSetProp (value, (xmlChar *) "type", (xmlChar *) type);
+
+ sprintf (intval, "%d", filter_int->val);
+ xmlSetProp (value, (xmlChar *) type, (xmlChar *) intval);
+
+ return value;
+}
+
+static gint
+filter_int_xml_decode (EFilterElement *element,
+ xmlNodePtr node)
+{
+ EFilterInt *filter_int = E_FILTER_INT (element);
+ gchar *name, *type;
+ gchar *intval;
+
+ name = (gchar *) xmlGetProp (node, (xmlChar *)"name");
+ xmlFree (element->name);
+ element->name = name;
+
+ type = (gchar *) xmlGetProp (node, (xmlChar *)"type");
+ g_free (filter_int->type);
+ filter_int->type = g_strdup (type);
+ xmlFree (type);
+
+ intval = (gchar *) xmlGetProp (
+ node, (xmlChar *) (filter_int->type ?
+ filter_int->type : "integer"));
+ if (intval) {
+ filter_int->val = atoi (intval);
+ xmlFree (intval);
+ } else {
+ filter_int->val = 0;
+ }
+
+ return 0;
+}
+
+static GtkWidget *
+filter_int_get_widget (EFilterElement *element)
+{
+ EFilterInt *filter_int = E_FILTER_INT (element);
+ GtkWidget *widget;
+ GtkAdjustment *adjustment;
+
+ adjustment = GTK_ADJUSTMENT (gtk_adjustment_new (
+ 0.0, (gfloat) filter_int->min,
+ (gfloat) filter_int->max, 1.0, 1.0, 0));
+ widget = gtk_spin_button_new (
+ adjustment,
+ filter_int->max > filter_int->min + 1000 ? 5.0 : 1.0, 0);
+ gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (widget), TRUE);
+
+ if (filter_int->val)
+ gtk_spin_button_set_value (
+ GTK_SPIN_BUTTON (widget), (gfloat) filter_int->val);
+
+ g_signal_connect (
+ widget, "value-changed",
+ G_CALLBACK (filter_int_spin_changed), element);
+
+ return widget;
+}
+
+static void
+filter_int_format_sexp (EFilterElement *element,
+ GString *out)
+{
+ EFilterInt *filter_int = E_FILTER_INT (element);
+
+ if (filter_int->val < 0)
+ /* See #364731 #457523 C6*/
+ g_string_append_printf (out, "(- 0 %d)", (filter_int->val * -1));
+ else
+ g_string_append_printf (out, "%d", filter_int->val);
+}
+
+static void
+e_filter_int_class_init (EFilterIntClass *class)
+{
+ GObjectClass *object_class;
+ EFilterElementClass *filter_element_class;
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->finalize = filter_int_finalize;
+
+ filter_element_class = E_FILTER_ELEMENT_CLASS (class);
+ filter_element_class->eq = filter_int_eq;
+ filter_element_class->clone = filter_int_clone;
+ filter_element_class->xml_encode = filter_int_xml_encode;
+ filter_element_class->xml_decode = filter_int_xml_decode;
+ filter_element_class->get_widget = filter_int_get_widget;
+ filter_element_class->format_sexp = filter_int_format_sexp;
+}
+
+static void
+e_filter_int_init (EFilterInt *filter_int)
+{
+ filter_int->min = 0;
+ filter_int->max = G_MAXINT;
+}
+
+EFilterElement *
+e_filter_int_new (void)
+{
+ return g_object_new (E_TYPE_FILTER_INT, NULL);
+}
+
+EFilterElement *
+e_filter_int_new_type (const gchar *type,
+ gint min,
+ gint max)
+{
+ EFilterInt *filter_int;
+
+ filter_int = g_object_new (E_TYPE_FILTER_INT, NULL);
+
+ filter_int->type = g_strdup (type);
+ filter_int->min = min;
+ filter_int->max = max;
+
+ return E_FILTER_ELEMENT (filter_int);
+}
+
+void
+e_filter_int_set_value (EFilterInt *filter_int,
+ gint value)
+{
+ g_return_if_fail (E_IS_FILTER_INT (filter_int));
+
+ filter_int->val = value;
+}
diff --git a/e-util/e-filter-int.h b/e-util/e-filter-int.h
new file mode 100644
index 0000000000..40b0c9e7a4
--- /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/e-util/e-filter-option.c b/e-util/e-filter-option.c
new file mode 100644
index 0000000000..630ab31916
--- /dev/null
+++ b/e-util/e-filter-option.c
@@ -0,0 +1,566 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You 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 <gmodule.h>
+
+#include "e-filter-option.h"
+#include "e-filter-part.h"
+
+G_DEFINE_TYPE (
+ EFilterOption,
+ e_filter_option,
+ E_TYPE_FILTER_ELEMENT)
+
+static void
+free_option (struct _filter_option *opt)
+{
+ g_free (opt->title);
+ g_free (opt->value);
+ g_free (opt->code);
+ g_free (opt->code_gen_func);
+ g_free (opt);
+}
+
+static struct _filter_option *
+find_option (EFilterOption *option,
+ const gchar *name)
+{
+ GList *link;
+
+ for (link = option->options; link != NULL; link = g_list_next (link)) {
+ struct _filter_option *opt = link->data;
+
+ if (strcmp (name, opt->value) == 0)
+ return opt;
+ }
+
+ return NULL;
+}
+
+static void
+filter_option_combobox_changed (GtkComboBox *combo_box,
+ EFilterElement *element)
+{
+ EFilterOption *option = E_FILTER_OPTION (element);
+ gint active;
+
+ active = gtk_combo_box_get_active (combo_box);
+ option->current = g_list_nth_data (option->options, active);
+}
+
+static GSList *
+filter_option_get_dynamic_options (EFilterOption *option)
+{
+ GModule *module;
+ GSList *(*get_func)(void);
+ GSList *res = NULL;
+
+ if (!option || !option->dynamic_func)
+ return res;
+
+ module = g_module_open (NULL, G_MODULE_BIND_LAZY);
+
+ if (g_module_symbol (module, option->dynamic_func, (gpointer) &get_func)) {
+ res = get_func ();
+ } else {
+ g_warning (
+ "optionlist dynamic fill function '%s' not found",
+ option->dynamic_func);
+ }
+
+ g_module_close (module);
+
+ return res;
+}
+
+static void
+filter_option_generate_code (EFilterOption *option,
+ GString *out,
+ EFilterPart *part)
+{
+ GModule *module;
+ void (*code_gen_func) (EFilterElement *element, GString *out, EFilterPart *part);
+
+ if (!option || !option->current || !option->current->code_gen_func)
+ return;
+
+ module = g_module_open (NULL, G_MODULE_BIND_LAZY);
+
+ if (g_module_symbol (module, option->current->code_gen_func, (gpointer) &code_gen_func)) {
+ code_gen_func (E_FILTER_ELEMENT (option), out, part);
+ } else {
+ g_warning (
+ "optionlist dynamic code function '%s' not found",
+ option->current->code_gen_func);
+ }
+
+ g_module_close (module);
+}
+
+static void
+filter_option_finalize (GObject *object)
+{
+ EFilterOption *option = E_FILTER_OPTION (object);
+
+ g_list_foreach (option->options, (GFunc) free_option, NULL);
+ g_list_free (option->options);
+
+ g_free (option->dynamic_func);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (e_filter_option_parent_class)->finalize (object);
+}
+
+static gint
+filter_option_eq (EFilterElement *element_a,
+ EFilterElement *element_b)
+{
+ EFilterOption *option_a = E_FILTER_OPTION (element_a);
+ EFilterOption *option_b = E_FILTER_OPTION (element_b);
+
+ /* Chain up to parent's eq() method. */
+ if (!E_FILTER_ELEMENT_CLASS (e_filter_option_parent_class)->
+ eq (element_a, element_b))
+ return FALSE;
+
+ if (option_a->current == NULL && option_b->current == NULL)
+ return TRUE;
+
+ if (option_a->current == NULL || option_b->current == NULL)
+ return FALSE;
+
+ return (g_strcmp0 (option_a->current->value, option_b->current->value) == 0);
+}
+
+static void
+filter_option_xml_create (EFilterElement *element,
+ xmlNodePtr node)
+{
+ EFilterOption *option = E_FILTER_OPTION (element);
+ xmlNodePtr n, work;
+
+ /* Chain up to parent's xml_create() method. */
+ E_FILTER_ELEMENT_CLASS (e_filter_option_parent_class)->
+ xml_create (element, node);
+
+ n = node->children;
+ while (n) {
+ if (!strcmp ((gchar *) n->name, "option")) {
+ gchar *tmp, *value, *title = NULL, *code = NULL, *code_gen_func = NULL;
+
+ value = (gchar *) xmlGetProp (n, (xmlChar *)"value");
+ work = n->children;
+ while (work) {
+ if (!strcmp ((gchar *) work->name, "title") ||
+ !strcmp ((gchar *) work->name, "_title")) {
+ if (!title) {
+ if (!(tmp = (gchar *) xmlNodeGetContent (work)))
+ tmp = (gchar *) xmlStrdup ((xmlChar *)"");
+
+ title = g_strdup (tmp);
+ xmlFree (tmp);
+ }
+ } else if (!strcmp ((gchar *) work->name, "code")) {
+ if (code || code_gen_func) {
+ g_warning (
+ "Element 'code' defined twice in '%s'",
+ element->name);
+ } else {
+ xmlChar *fn;
+
+ /* if element 'code' has attribute 'func', then
+ * the content of the element is ignored and only
+ * the 'func' is used to generate actual rule code;
+ * The function prototype is:
+ * void code_gen_func (EFilterElement *element, GString *out, EFilterPart *part);
+ * where @element is the one on which was called,
+ * @out is GString where to add the code, and
+ * @part is part which contains @element and other options of it.
+ */
+ fn = xmlGetProp (work, (xmlChar *)"func");
+ if (fn && *fn) {
+ code_gen_func = g_strdup ((const gchar *) fn);
+ } else {
+ if (!(tmp = (gchar *) xmlNodeGetContent (work)))
+ tmp = (gchar *) xmlStrdup ((xmlChar *)"");
+
+ code = g_strdup (tmp);
+ xmlFree (tmp);
+ }
+
+ xmlFree (fn);
+ }
+ }
+ work = work->next;
+ }
+
+ e_filter_option_add (option, value, title, code, code_gen_func, FALSE);
+ xmlFree (value);
+ g_free (title);
+ g_free (code);
+ g_free (code_gen_func);
+ } else if (g_str_equal ((gchar *) n->name, "dynamic")) {
+ if (option->dynamic_func) {
+ g_warning (
+ "Only one 'dynamic' node is "
+ "acceptable in the optionlist '%s'",
+ element->name);
+ } else {
+ /* Expecting only one <dynamic func="cb" />
+ * in the option list,
+ * The 'cb' should be of this prototype:
+ * GSList *cb (void);
+ * returning GSList of struct _filter_option,
+ * all newly allocated, because it'll be
+ * freed with g_free and g_slist_free.
+ * 'is_dynamic' member is ignored here.
+ */
+ xmlChar *fn;
+
+ fn = xmlGetProp (n, (xmlChar *)"func");
+ if (fn && *fn) {
+ GSList *items, *i;
+ struct _filter_option *op;
+
+ option->dynamic_func = g_strdup ((const gchar *) fn);
+
+ /* Get options now, to have them
+ * available when reading saved
+ * rules. */
+ items = filter_option_get_dynamic_options (option);
+ for (i = items; i; i = i->next) {
+ op = i->data;
+
+ if (op) {
+ e_filter_option_add (
+ option,
+ op->value,
+ op->title,
+ op->code,
+ op->code_gen_func,
+ TRUE);
+ free_option (op);
+ }
+ }
+
+ g_slist_free (items);
+ } else {
+ g_warning (
+ "Missing 'func' attribute within "
+ "'%s' node in optionlist '%s'",
+ n->name, element->name);
+ }
+
+ xmlFree (fn);
+ }
+ } else if (n->type == XML_ELEMENT_NODE) {
+ g_warning ("Unknown xml node within optionlist: %s\n", n->name);
+ }
+ n = n->next;
+ }
+}
+
+static xmlNodePtr
+filter_option_xml_encode (EFilterElement *element)
+{
+ EFilterOption *option = E_FILTER_OPTION (element);
+ xmlNodePtr value;
+
+ value = xmlNewNode (NULL, (xmlChar *) "value");
+ xmlSetProp (value, (xmlChar *) "name", (xmlChar *) element->name);
+ xmlSetProp (value, (xmlChar *) "type", (xmlChar *) option->type);
+ if (option->current)
+ xmlSetProp (value, (xmlChar *) "value", (xmlChar *) option->current->value);
+
+ return value;
+}
+
+static gint
+filter_option_xml_decode (EFilterElement *element,
+ xmlNodePtr node)
+{
+ EFilterOption *option = E_FILTER_OPTION (element);
+ gchar *value;
+
+ xmlFree (element->name);
+ element->name = (gchar *) xmlGetProp (node, (xmlChar *)"name");
+
+ value = (gchar *) xmlGetProp (node, (xmlChar *)"value");
+ if (value) {
+ option->current = find_option (option, value);
+ xmlFree (value);
+ } else {
+ option->current = NULL;
+ }
+
+ return 0;
+}
+
+static EFilterElement *
+filter_option_clone (EFilterElement *element)
+{
+ EFilterOption *option = E_FILTER_OPTION (element);
+ EFilterOption *clone_option;
+ EFilterElement *clone;
+ GList *link;
+
+ /* Chain up to parent's clone() method. */
+ clone = E_FILTER_ELEMENT_CLASS (e_filter_option_parent_class)->
+ clone (element);
+
+ clone_option = E_FILTER_OPTION (clone);
+
+ for (link = option->options; link != NULL; link = g_list_next (link)) {
+ struct _filter_option *op = link->data;
+ struct _filter_option *newop;
+
+ newop = e_filter_option_add (
+ clone_option, op->value,
+ op->title, op->code, op->code_gen_func, op->is_dynamic);
+ if (option->current == op)
+ clone_option->current = newop;
+ }
+
+ clone_option->dynamic_func = g_strdup (option->dynamic_func);
+
+ return clone;
+}
+
+static GtkWidget *
+filter_option_get_widget (EFilterElement *element)
+{
+ EFilterOption *option = E_FILTER_OPTION (element);
+ GtkWidget *combobox;
+ GList *l;
+ struct _filter_option *op;
+ gint index = 0, current = 0;
+
+ if (option->dynamic_func) {
+ /* it is dynamically filled, thus remove all dynamics
+ * and put there the fresh ones */
+ GSList *items, *i;
+ GList *old_ops;
+ struct _filter_option *old_cur;
+
+ old_ops = option->options;
+ old_cur = option->current;
+
+ /* start with an empty list */
+ option->current = NULL;
+ option->options = NULL;
+
+ for (l = option->options; l; l = l->next) {
+ op = l->data;
+
+ if (op->is_dynamic) {
+ break;
+ } else {
+ e_filter_option_add (
+ option, op->value, op->title,
+ op->code, op->code_gen_func, FALSE);
+ }
+ }
+
+ items = filter_option_get_dynamic_options (option);
+ for (i = items; i; i = i->next) {
+ op = i->data;
+
+ if (op) {
+ e_filter_option_add (
+ option, op->value, op->title,
+ op->code, op->code_gen_func, TRUE);
+ free_option (op);
+ }
+ }
+
+ g_slist_free (items);
+
+ /* maybe some static left after those dynamic, add them too */
+ for (; l; l = l->next) {
+ op = l->data;
+
+ if (!op->is_dynamic)
+ e_filter_option_add (
+ option, op->value, op->title,
+ op->code, op->code_gen_func, FALSE);
+ }
+
+ if (old_cur)
+ e_filter_option_set_current (option, old_cur->value);
+
+ /* free old list */
+ g_list_foreach (old_ops, (GFunc) free_option, NULL);
+ g_list_free (old_ops);
+ }
+
+ combobox = gtk_combo_box_text_new ();
+ l = option->options;
+ while (l) {
+ op = l->data;
+ gtk_combo_box_text_append_text (
+ GTK_COMBO_BOX_TEXT (combobox), _(op->title));
+
+ if (op == option->current)
+ current = index;
+
+ l = g_list_next (l);
+ index++;
+ }
+
+ g_signal_connect (
+ combobox, "changed",
+ G_CALLBACK (filter_option_combobox_changed), element);
+
+ gtk_combo_box_set_active (GTK_COMBO_BOX (combobox), current);
+
+ return combobox;
+}
+
+static void
+filter_option_build_code (EFilterElement *element,
+ GString *out,
+ EFilterPart *part)
+{
+ EFilterOption *option = E_FILTER_OPTION (element);
+
+ if (option->current && option->current->code_gen_func) {
+ filter_option_generate_code (option, out, part);
+ } else if (option->current && option->current->code) {
+ e_filter_part_expand_code (part, option->current->code, out);
+ }
+}
+
+static void
+filter_option_format_sexp (EFilterElement *element,
+ GString *out)
+{
+ EFilterOption *option = E_FILTER_OPTION (element);
+
+ if (option->current)
+ camel_sexp_encode_string (out, option->current->value);
+}
+
+static void
+e_filter_option_class_init (EFilterOptionClass *class)
+{
+ GObjectClass *object_class;
+ EFilterElementClass *filter_element_class;
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->finalize = filter_option_finalize;
+
+ filter_element_class = E_FILTER_ELEMENT_CLASS (class);
+ filter_element_class->eq = filter_option_eq;
+ filter_element_class->xml_create = filter_option_xml_create;
+ filter_element_class->xml_encode = filter_option_xml_encode;
+ filter_element_class->xml_decode = filter_option_xml_decode;
+ filter_element_class->clone = filter_option_clone;
+ filter_element_class->get_widget = filter_option_get_widget;
+ filter_element_class->build_code = filter_option_build_code;
+ filter_element_class->format_sexp = filter_option_format_sexp;
+}
+
+static void
+e_filter_option_init (EFilterOption *option)
+{
+ option->type = "option";
+ option->dynamic_func = NULL;
+}
+
+EFilterElement *
+e_filter_option_new (void)
+{
+ return g_object_new (E_TYPE_FILTER_OPTION, NULL);
+}
+
+void
+e_filter_option_set_current (EFilterOption *option,
+ const gchar *name)
+{
+ g_return_if_fail (E_IS_FILTER_OPTION (option));
+
+ option->current = find_option (option, name);
+}
+
+/* used by implementers to add additional options */
+struct _filter_option *
+e_filter_option_add (EFilterOption *option,
+ const gchar *value,
+ const gchar *title,
+ const gchar *code,
+ const gchar *code_gen_func,
+ gboolean is_dynamic)
+{
+ struct _filter_option *op;
+
+ g_return_val_if_fail (E_IS_FILTER_OPTION (option), NULL);
+ g_return_val_if_fail (find_option (option, value) == NULL, NULL);
+
+ if (code_gen_func && !*code_gen_func)
+ code_gen_func = NULL;
+
+ op = g_malloc (sizeof (*op));
+ op->title = g_strdup (title);
+ op->value = g_strdup (value);
+ op->code = g_strdup (code);
+ op->code_gen_func = g_strdup (code_gen_func);
+ op->is_dynamic = is_dynamic;
+
+ option->options = g_list_append (option->options, op);
+
+ if (option->current == NULL)
+ option->current = op;
+
+ return op;
+}
+
+const gchar *
+e_filter_option_get_current (EFilterOption *option)
+{
+ g_return_val_if_fail (E_IS_FILTER_OPTION (option), NULL);
+
+ if (option->current == NULL)
+ return NULL;
+
+ return option->current->value;
+}
+
+void
+e_filter_option_remove_all (EFilterOption *option)
+{
+ g_return_if_fail (E_IS_FILTER_OPTION (option));
+
+ g_list_foreach (option->options, (GFunc) free_option, NULL);
+ g_list_free (option->options);
+
+ option->options = NULL;
+ option->current = NULL;
+}
diff --git a/e-util/e-filter-option.h b/e-util/e-filter-option.h
new file mode 100644
index 0000000000..9bd7543ba6
--- /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/e-util/e-filter-part.c b/e-util/e-filter-part.c
new file mode 100644
index 0000000000..c9e14e30c6
--- /dev/null
+++ b/e-util/e-filter-part.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>
+ * Jepartrey Stedfast <fejj@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 <glib/gi18n.h>
+
+#include "e-filter-file.h"
+#include "e-filter-part.h"
+#include "e-rule-context.h"
+
+G_DEFINE_TYPE (
+ EFilterPart,
+ e_filter_part,
+ G_TYPE_OBJECT)
+
+static void
+filter_part_finalize (GObject *object)
+{
+ EFilterPart *part = E_FILTER_PART (object);
+
+ g_list_foreach (part->elements, (GFunc) g_object_unref, NULL);
+ g_list_free (part->elements);
+
+ g_free (part->name);
+ g_free (part->title);
+ g_free (part->code);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (e_filter_part_parent_class)->finalize (object);
+}
+
+static void
+e_filter_part_class_init (EFilterPartClass *class)
+{
+ GObjectClass *object_class;
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->finalize = filter_part_finalize;
+}
+
+static void
+e_filter_part_init (EFilterPart *part)
+{
+}
+
+/**
+ * e_filter_part_new:
+ *
+ * Create a new EFilterPart object.
+ *
+ * Return value: A new #EFilterPart object.
+ **/
+EFilterPart *
+e_filter_part_new (void)
+{
+ return g_object_new (E_TYPE_FILTER_PART, NULL);
+}
+
+gboolean
+e_filter_part_validate (EFilterPart *part,
+ EAlert **alert)
+{
+ GList *link;
+
+ g_return_val_if_fail (E_IS_FILTER_PART (part), FALSE);
+
+ /* The part is valid if all of its elements are valid. */
+ for (link = part->elements; link != NULL; link = g_list_next (link)) {
+ EFilterElement *element = link->data;
+
+ if (!e_filter_element_validate (element, alert))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+gint
+e_filter_part_eq (EFilterPart *part_a,
+ EFilterPart *part_b)
+{
+ GList *link_a, *link_b;
+
+ g_return_val_if_fail (E_IS_FILTER_PART (part_a), FALSE);
+ g_return_val_if_fail (E_IS_FILTER_PART (part_b), FALSE);
+
+ if (g_strcmp0 (part_a->name, part_b->name) != 0)
+ return FALSE;
+
+ if (g_strcmp0 (part_a->title, part_b->title) != 0)
+ return FALSE;
+
+ if (g_strcmp0 (part_a->code, part_b->code) != 0)
+ return FALSE;
+
+ link_a = part_a->elements;
+ link_b = part_b->elements;
+
+ while (link_a != NULL && link_b != NULL) {
+ EFilterElement *element_a = link_a->data;
+ EFilterElement *element_b = link_b->data;
+
+ if (!e_filter_element_eq (element_a, element_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;
+}
+
+gint
+e_filter_part_xml_create (EFilterPart *part,
+ xmlNodePtr node,
+ ERuleContext *context)
+{
+ xmlNodePtr n;
+ gchar *type, *str;
+ EFilterElement *el;
+
+ g_return_val_if_fail (E_IS_FILTER_PART (part), FALSE);
+ g_return_val_if_fail (node != NULL, FALSE);
+ g_return_val_if_fail (E_IS_RULE_CONTEXT (context), FALSE);
+
+ str = (gchar *) xmlGetProp (node, (xmlChar *)"name");
+ part->name = g_strdup (str);
+ if (str)
+ xmlFree (str);
+
+ n = node->children;
+ while (n) {
+ if (!strcmp ((gchar *) n->name, "input")) {
+ type = (gchar *) xmlGetProp (n, (xmlChar *)"type");
+ if (type != NULL
+ && (el = e_rule_context_new_element (context, type)) != NULL) {
+ e_filter_element_xml_create (el, n);
+ xmlFree (type);
+ part->elements = g_list_append (part->elements, el);
+ } else {
+ g_warning ("Invalid xml format, missing/unknown input type");
+ }
+ } else if (!strcmp ((gchar *) n->name, "title") ||
+ !strcmp ((gchar *) n->name, "_title")) {
+ if (!part->title) {
+ str = (gchar *) xmlNodeGetContent (n);
+ part->title = g_strdup (str);
+ if (str)
+ xmlFree (str);
+ }
+ } else if (!strcmp ((gchar *) n->name, "code")) {
+ if (!part->code) {
+ str = (gchar *) xmlNodeGetContent (n);
+ part->code = g_strdup (str);
+ if (str)
+ xmlFree (str);
+ }
+ } else if (n->type == XML_ELEMENT_NODE) {
+ g_warning ("Unknown part element in xml: %s\n", n->name);
+ }
+ n = n->next;
+ }
+
+ return 0;
+}
+
+xmlNodePtr
+e_filter_part_xml_encode (EFilterPart *part)
+{
+ xmlNodePtr node;
+ GList *link;
+
+ g_return_val_if_fail (E_IS_FILTER_PART (part), NULL);
+
+ node = xmlNewNode (NULL, (xmlChar *)"part");
+ xmlSetProp (node, (xmlChar *)"name", (xmlChar *) part->name);
+
+ for (link = part->elements; link != NULL; link = g_list_next (link)) {
+ EFilterElement *element = link->data;
+ xmlNodePtr value;
+
+ value = e_filter_element_xml_encode (element);
+ xmlAddChild (node, value);
+ }
+
+ return node;
+}
+
+gint
+e_filter_part_xml_decode (EFilterPart *part,
+ xmlNodePtr node)
+{
+ xmlNodePtr child;
+
+ g_return_val_if_fail (E_IS_FILTER_PART (part), -1);
+ g_return_val_if_fail (node != NULL, -1);
+
+ for (child = node->children; child != NULL; child = child->next) {
+ EFilterElement *element;
+ xmlChar *name;
+
+ if (strcmp ((gchar *) child->name, "value") != 0)
+ continue;
+
+ name = xmlGetProp (child, (xmlChar *) "name");
+ element = e_filter_part_find_element (part, (gchar *) name);
+ xmlFree (name);
+
+ if (element != NULL)
+ e_filter_element_xml_decode (element, child);
+ }
+
+ return 0;
+}
+
+EFilterPart *
+e_filter_part_clone (EFilterPart *part)
+{
+ EFilterPart *clone;
+ GList *link;
+
+ g_return_val_if_fail (E_IS_FILTER_PART (part), NULL);
+
+ clone = g_object_new (G_OBJECT_TYPE (part), NULL, NULL);
+ clone->name = g_strdup (part->name);
+ clone->title = g_strdup (part->title);
+ clone->code = g_strdup (part->code);
+
+ for (link = part->elements; link != NULL; link = g_list_next (link)) {
+ EFilterElement *element = link->data;
+ EFilterElement *clone_element;
+
+ clone_element = e_filter_element_clone (element);
+ clone->elements = g_list_append (clone->elements, clone_element);
+ }
+
+ return clone;
+}
+
+/* only copies values of matching parts in the right order */
+void
+e_filter_part_copy_values (EFilterPart *dst_part,
+ EFilterPart *src_part)
+{
+ GList *dst_link, *src_link;
+
+ g_return_if_fail (E_IS_FILTER_PART (dst_part));
+ g_return_if_fail (E_IS_FILTER_PART (src_part));
+
+ /* NOTE: we go backwards, it just works better that way */
+
+ /* for each source type, search the dest type for
+ * a matching type in the same order */
+ src_link = g_list_last (src_part->elements);
+ dst_link = g_list_last (dst_part->elements);
+
+ while (src_link != NULL && dst_link != NULL) {
+ EFilterElement *src_element = src_link->data;
+ GList *link = dst_link;
+
+ while (link != NULL) {
+ EFilterElement *dst_element = link->data;
+ GType dst_type = G_OBJECT_TYPE (dst_element);
+ GType src_type = G_OBJECT_TYPE (src_element);
+
+ if (dst_type == src_type) {
+ e_filter_element_copy_value (
+ dst_element, src_element);
+ dst_link = g_list_previous (link);
+ break;
+ }
+
+ link = g_list_previous (link);
+ }
+
+ src_link = g_list_previous (src_link);
+ }
+}
+
+EFilterElement *
+e_filter_part_find_element (EFilterPart *part,
+ const gchar *name)
+{
+ GList *link;
+
+ g_return_val_if_fail (E_IS_FILTER_PART (part), NULL);
+
+ if (name == NULL)
+ return NULL;
+
+ for (link = part->elements; link != NULL; link = g_list_next (link)) {
+ EFilterElement *element = link->data;
+
+ if (g_strcmp0 (element->name, name) == 0)
+ return element;
+ }
+
+ return NULL;
+}
+
+GtkWidget *
+e_filter_part_get_widget (EFilterPart *part)
+{
+ GtkWidget *hbox;
+ GList *link;
+
+ g_return_val_if_fail (E_IS_FILTER_PART (part), NULL);
+
+ hbox = gtk_hbox_new (FALSE, 3);
+
+ for (link = part->elements; link != NULL; link = g_list_next (link)) {
+ EFilterElement *element = link->data;
+ GtkWidget *widget;
+
+ widget = e_filter_element_get_widget (element);
+ if (widget != NULL)
+ gtk_box_pack_start (
+ GTK_BOX (hbox), widget,
+ E_IS_FILTER_FILE (element),
+ E_IS_FILTER_FILE (element), 3);
+ }
+
+ gtk_widget_show_all (hbox);
+
+ return hbox;
+}
+
+/**
+ * e_filter_part_build_code:
+ * @part:
+ * @out:
+ *
+ * Outputs the code of a part.
+ **/
+void
+e_filter_part_build_code (EFilterPart *part,
+ GString *out)
+{
+ GList *link;
+
+ g_return_if_fail (E_IS_FILTER_PART (part));
+ g_return_if_fail (out != NULL);
+
+ if (part->code != NULL)
+ e_filter_part_expand_code (part, part->code, out);
+
+ for (link = part->elements; link != NULL; link = g_list_next (link)) {
+ EFilterElement *element = link->data;
+ e_filter_element_build_code (element, out, part);
+ }
+}
+
+/**
+ * e_filter_part_build_code_list:
+ * @l:
+ * @out:
+ *
+ * Construct a list of the filter parts code into
+ * a single string.
+ **/
+void
+e_filter_part_build_code_list (GList *list,
+ GString *out)
+{
+ GList *link;
+
+ g_return_if_fail (out != NULL);
+
+ for (link = list; link != NULL; link = g_list_next (link)) {
+ EFilterPart *part = link->data;
+
+ e_filter_part_build_code (part, out);
+ g_string_append (out, "\n ");
+ }
+}
+
+/**
+ * e_filter_part_find_list:
+ * @l:
+ * @name:
+ *
+ * Find a filter part stored in a list.
+ *
+ * Return value:
+ **/
+EFilterPart *
+e_filter_part_find_list (GList *list,
+ const gchar *name)
+{
+ GList *link;
+
+ g_return_val_if_fail (name != NULL, NULL);
+
+ for (link = list; link != NULL; link = g_list_next (link)) {
+ EFilterPart *part = link->data;
+
+ if (g_strcmp0 (part->name, name) == 0)
+ return part;
+ }
+
+ return NULL;
+}
+
+/**
+ * e_filter_part_next_list:
+ * @l:
+ * @last: The last item retrieved, or NULL to start
+ * from the beginning of the list.
+ *
+ * Iterate through a filter part list.
+ *
+ * Return value: The next value in the list, or NULL if the
+ * list is expired.
+ **/
+EFilterPart *
+e_filter_part_next_list (GList *list,
+ EFilterPart *last)
+{
+ GList *link = list;
+
+ if (last != NULL) {
+ link = g_list_find (list, last);
+ if (link == NULL)
+ link = list;
+ else
+ link = link->next;
+ }
+
+ return (link != NULL) ? link->data : NULL;
+}
+
+/**
+ * e_filter_part_expand_code:
+ * @part:
+ * @str:
+ * @out:
+ *
+ * Expands the variables in string @str based on the values of the part.
+ **/
+void
+e_filter_part_expand_code (EFilterPart *part,
+ const gchar *source,
+ GString *out)
+{
+ const gchar *newstart, *start, *end;
+ gchar *name = g_alloca (32);
+ gint len, namelen = 32;
+
+ g_return_if_fail (E_IS_FILTER_PART (part));
+ g_return_if_fail (source != NULL);
+ g_return_if_fail (out != NULL);
+
+ start = source;
+
+ while (start && (newstart = strstr (start, "${"))
+ && (end = strstr (newstart + 2, "}"))) {
+ EFilterElement *element;
+
+ len = end - newstart - 2;
+ if (len + 1 > namelen) {
+ namelen = (len + 1) * 2;
+ name = g_alloca (namelen);
+ }
+ memcpy (name, newstart + 2, len);
+ name[len] = 0;
+
+ element = e_filter_part_find_element (part, name);
+ if (element != NULL) {
+ g_string_append_printf (out, "%.*s", (gint)(newstart - start), start);
+ e_filter_element_format_sexp (element, out);
+#if 0
+ } else if ((val = g_hash_table_lookup (part->globals, name))) {
+ g_string_append_printf (out, "%.*s", newstart - start, start);
+ camel_sexp_encode_string (out, val);
+#endif
+ } else {
+ g_string_append_printf (out, "%.*s", (gint)(end - start + 1), start);
+ }
+ start = end + 1;
+ }
+
+ g_string_append (out, start);
+}
diff --git a/e-util/e-filter-part.h b/e-util/e-filter-part.h
new file mode 100644
index 0000000000..b5ee2c46f2
--- /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 0000000000..111073bbad
--- /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 0000000000..8670265d88
--- /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 0000000000..a610605987
--- /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 0000000000..e633d0f57c
--- /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 f87e82f9b1..2fe67fffd5 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 89a7d5a6a4..c75c72fec7 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 0000000000..20c2f0e473
--- /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 0000000000..d9bfb34b71
--- /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 0000000000..ae48e5c217
--- /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 0000000000..0ee580e31e
--- /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 9d0eb5127a..69d40cfced 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 0000000000..70e90bdf92
--- /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 0000000000..477ae1895c
--- /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/e-util/e-mail-identity-combo-box.c b/e-util/e-mail-identity-combo-box.c
new file mode 100644
index 0000000000..b76e0ee6bc
--- /dev/null
+++ b/e-util/e-mail-identity-combo-box.c
@@ -0,0 +1,385 @@
+/*
+ * e-mail-identity-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/>
+ *
+ */
+
+#include "e-mail-identity-combo-box.h"
+
+#define E_MAIL_IDENTITY_COMBO_BOX_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_MAIL_IDENTITY_COMBO_BOX, EMailIdentityComboBoxPrivate))
+
+#define SOURCE_IS_MAIL_IDENTITY(source) \
+ (e_source_has_extension ((source), E_SOURCE_EXTENSION_MAIL_IDENTITY))
+
+struct _EMailIdentityComboBoxPrivate {
+ ESourceRegistry *registry;
+ guint refresh_idle_id;
+};
+
+enum {
+ PROP_0,
+ PROP_REGISTRY
+};
+
+enum {
+ COLUMN_DISPLAY_NAME,
+ COLUMN_UID
+};
+
+G_DEFINE_TYPE (
+ EMailIdentityComboBox,
+ e_mail_identity_combo_box,
+ GTK_TYPE_COMBO_BOX)
+
+static gboolean
+mail_identity_combo_box_refresh_idle_cb (EMailIdentityComboBox *combo_box)
+{
+ /* The refresh function will clear the idle ID. */
+ e_mail_identity_combo_box_refresh (combo_box);
+
+ return FALSE;
+}
+
+static void
+mail_identity_combo_box_registry_changed (ESourceRegistry *registry,
+ ESource *source,
+ EMailIdentityComboBox *combo_box)
+{
+ /* If the ESource in question has a "Mail Identity" extension,
+ * schedule a refresh of the tree model. Otherwise ignore it.
+ * We use an idle callback to limit how frequently we refresh
+ * the tree model, in case the registry is emitting lots of
+ * signals at once. */
+
+ if (!SOURCE_IS_MAIL_IDENTITY (source))
+ return;
+
+ if (combo_box->priv->refresh_idle_id > 0)
+ return;
+
+ combo_box->priv->refresh_idle_id = g_idle_add (
+ (GSourceFunc) mail_identity_combo_box_refresh_idle_cb,
+ combo_box);
+}
+
+static void
+mail_identity_combo_box_activate_default (EMailIdentityComboBox *combo_box)
+{
+ ESource *source;
+ ESourceRegistry *registry;
+
+ registry = e_mail_identity_combo_box_get_registry (combo_box);
+ source = e_source_registry_ref_default_mail_identity (registry);
+
+ if (source != NULL) {
+ const gchar *uid = e_source_get_uid (source);
+ gtk_combo_box_set_active_id (GTK_COMBO_BOX (combo_box), uid);
+ g_object_unref (source);
+ }
+}
+
+static void
+mail_identity_combo_box_set_registry (EMailIdentityComboBox *combo_box,
+ ESourceRegistry *registry)
+{
+ g_return_if_fail (E_IS_SOURCE_REGISTRY (registry));
+ g_return_if_fail (combo_box->priv->registry == NULL);
+
+ combo_box->priv->registry = g_object_ref (registry);
+
+ g_signal_connect (
+ registry, "source-added",
+ G_CALLBACK (mail_identity_combo_box_registry_changed),
+ combo_box);
+
+ g_signal_connect (
+ registry, "source-changed",
+ G_CALLBACK (mail_identity_combo_box_registry_changed),
+ combo_box);
+
+ g_signal_connect (
+ registry, "source-removed",
+ G_CALLBACK (mail_identity_combo_box_registry_changed),
+ combo_box);
+}
+
+static void
+mail_identity_combo_box_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_REGISTRY:
+ mail_identity_combo_box_set_registry (
+ E_MAIL_IDENTITY_COMBO_BOX (object),
+ g_value_get_object (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+mail_identity_combo_box_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_REGISTRY:
+ g_value_set_object (
+ value,
+ e_mail_identity_combo_box_get_registry (
+ E_MAIL_IDENTITY_COMBO_BOX (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+mail_identity_combo_box_dispose (GObject *object)
+{
+ EMailIdentityComboBoxPrivate *priv;
+
+ priv = E_MAIL_IDENTITY_COMBO_BOX_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;
+ }
+
+ if (priv->refresh_idle_id > 0) {
+ g_source_remove (priv->refresh_idle_id);
+ priv->refresh_idle_id = 0;
+ }
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (e_mail_identity_combo_box_parent_class)->
+ dispose (object);
+}
+
+static void
+mail_identity_combo_box_constructed (GObject *object)
+{
+ GtkListStore *list_store;
+ GtkComboBox *combo_box;
+ GtkCellLayout *cell_layout;
+ GtkCellRenderer *cell_renderer;
+
+ /* Chain up to parent's constructed() method. */
+ G_OBJECT_CLASS (e_mail_identity_combo_box_parent_class)->
+ constructed (object);
+
+ combo_box = GTK_COMBO_BOX (object);
+ cell_layout = GTK_CELL_LAYOUT (object);
+
+ list_store = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_STRING);
+ gtk_combo_box_set_model (combo_box, GTK_TREE_MODEL (list_store));
+ gtk_combo_box_set_id_column (combo_box, COLUMN_UID);
+ g_object_unref (list_store);
+
+ cell_renderer = gtk_cell_renderer_text_new ();
+ gtk_cell_layout_pack_start (cell_layout, cell_renderer, TRUE);
+ gtk_cell_layout_add_attribute (
+ cell_layout, cell_renderer, "text", COLUMN_DISPLAY_NAME);
+
+ e_mail_identity_combo_box_refresh (E_MAIL_IDENTITY_COMBO_BOX (object));
+}
+
+static void
+e_mail_identity_combo_box_class_init (EMailIdentityComboBoxClass *class)
+{
+ GObjectClass *object_class;
+
+ g_type_class_add_private (
+ class, sizeof (EMailIdentityComboBoxPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = mail_identity_combo_box_set_property;
+ object_class->get_property = mail_identity_combo_box_get_property;
+ object_class->dispose = mail_identity_combo_box_dispose;
+ object_class->constructed = mail_identity_combo_box_constructed;
+
+ 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));
+}
+
+static void
+e_mail_identity_combo_box_init (EMailIdentityComboBox *combo_box)
+{
+ combo_box->priv = E_MAIL_IDENTITY_COMBO_BOX_GET_PRIVATE (combo_box);
+}
+
+GtkWidget *
+e_mail_identity_combo_box_new (ESourceRegistry *registry)
+{
+ g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), NULL);
+
+ return g_object_new (
+ E_TYPE_MAIL_IDENTITY_COMBO_BOX,
+ "registry", registry, NULL);
+}
+
+void
+e_mail_identity_combo_box_refresh (EMailIdentityComboBox *combo_box)
+{
+ ESourceRegistry *registry;
+ GtkTreeModel *tree_model;
+ GtkComboBox *gtk_combo_box;
+ ESource *source;
+ GList *list, *link;
+ GHashTable *address_table;
+ const gchar *extension_name;
+ const gchar *saved_uid;
+
+ g_return_if_fail (E_IS_MAIL_IDENTITY_COMBO_BOX (combo_box));
+
+ if (combo_box->priv->refresh_idle_id > 0) {
+ g_source_remove (combo_box->priv->refresh_idle_id);
+ combo_box->priv->refresh_idle_id = 0;
+ }
+
+ gtk_combo_box = GTK_COMBO_BOX (combo_box);
+ tree_model = gtk_combo_box_get_model (gtk_combo_box);
+
+ /* This is an interned string, which means it's safe
+ * to use even after clearing the combo box model. */
+ saved_uid = gtk_combo_box_get_active_id (gtk_combo_box);
+
+ gtk_list_store_clear (GTK_LIST_STORE (tree_model));
+
+ extension_name = E_SOURCE_EXTENSION_MAIL_IDENTITY;
+ registry = e_mail_identity_combo_box_get_registry (combo_box);
+ list = e_source_registry_list_sources (registry, extension_name);
+
+ /* Build a hash table of GQueues by email address so we can
+ * spot duplicate email addresses. Then if the GQueue for a
+ * given email address has multiple elements, we use a more
+ * verbose description in the combo box. */
+
+ address_table = g_hash_table_new_full (
+ (GHashFunc) g_str_hash,
+ (GEqualFunc) g_str_equal,
+ (GDestroyNotify) g_free,
+ (GDestroyNotify) g_queue_free);
+
+ for (link = list; link != NULL; link = g_list_next (link)) {
+ ESourceMailIdentity *extension;
+ GQueue *queue;
+ const gchar *address;
+
+ source = E_SOURCE (link->data);
+ extension = e_source_get_extension (source, extension_name);
+ address = e_source_mail_identity_get_address (extension);
+
+ if (address == NULL)
+ continue;
+
+ queue = g_hash_table_lookup (address_table, address);
+ if (queue == NULL) {
+ queue = g_queue_new ();
+ g_hash_table_insert (
+ address_table,
+ g_strdup (address), queue);
+ }
+
+ g_queue_push_tail (queue, source);
+ }
+
+ for (link = list; link != NULL; link = g_list_next (link)) {
+ ESourceMailIdentity *extension;
+ GtkTreeIter iter;
+ GQueue *queue;
+ GString *string;
+ const gchar *address;
+ const gchar *display_name;
+ const gchar *name;
+ const gchar *uid;
+
+ source = E_SOURCE (link->data);
+
+ if (!e_source_registry_check_enabled (registry, source))
+ continue;
+
+ extension = e_source_get_extension (source, extension_name);
+ name = e_source_mail_identity_get_name (extension);
+ address = e_source_mail_identity_get_address (extension);
+
+ if (name == NULL || address == NULL)
+ continue;
+
+ queue = g_hash_table_lookup (address_table, address);
+
+ display_name = e_source_get_display_name (source);
+ uid = e_source_get_uid (source);
+
+ string = g_string_sized_new (512);
+ g_string_append_printf (string, "%s <%s>", name, address);
+
+ /* Show the account name for duplicate email addresses. */
+ if (queue != NULL && g_queue_get_length (queue) > 1)
+ g_string_append_printf (string, " (%s)", display_name);
+
+ gtk_list_store_append (GTK_LIST_STORE (tree_model), &iter);
+
+ gtk_list_store_set (
+ GTK_LIST_STORE (tree_model), &iter,
+ COLUMN_DISPLAY_NAME, string->str,
+ COLUMN_UID, uid, -1);
+
+ g_string_free (string, TRUE);
+ }
+
+ g_hash_table_destroy (address_table);
+
+ g_list_free_full (list, (GDestroyNotify) g_object_unref);
+
+ /* Try and restore the previous selected source, or else pick
+ * the default identity of the default mail account. If even
+ * that fails, just pick the first item. */
+
+ if (saved_uid != NULL)
+ gtk_combo_box_set_active_id (gtk_combo_box, saved_uid);
+
+ if (gtk_combo_box_get_active_id (gtk_combo_box) == NULL)
+ mail_identity_combo_box_activate_default (combo_box);
+
+ if (gtk_combo_box_get_active_id (gtk_combo_box) == NULL)
+ gtk_combo_box_set_active (gtk_combo_box, 0);
+}
+
+ESourceRegistry *
+e_mail_identity_combo_box_get_registry (EMailIdentityComboBox *combo_box)
+{
+ g_return_val_if_fail (E_IS_MAIL_IDENTITY_COMBO_BOX (combo_box), NULL);
+
+ return combo_box->priv->registry;
+}
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 0000000000..8c395b362f
--- /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/e-util/e-mail-signature-combo-box.c b/e-util/e-mail-signature-combo-box.c
new file mode 100644
index 0000000000..275c2538b9
--- /dev/null
+++ b/e-util/e-mail-signature-combo-box.c
@@ -0,0 +1,668 @@
+/*
+ * e-mail-signature-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/>
+ *
+ */
+
+#include "e-mail-signature-combo-box.h"
+
+#include <config.h>
+#include <glib/gi18n-lib.h>
+
+#define E_MAIL_SIGNATURE_COMBO_BOX_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_MAIL_SIGNATURE_COMBO_BOX, EMailSignatureComboBoxPrivate))
+
+#define SOURCE_IS_MAIL_SIGNATURE(source) \
+ (e_source_has_extension ((source), E_SOURCE_EXTENSION_MAIL_SIGNATURE))
+
+struct _EMailSignatureComboBoxPrivate {
+ ESourceRegistry *registry;
+ guint refresh_idle_id;
+ gchar *identity_uid;
+};
+
+enum {
+ PROP_0,
+ PROP_IDENTITY_UID,
+ PROP_REGISTRY
+};
+
+enum {
+ COLUMN_DISPLAY_NAME,
+ COLUMN_UID
+};
+
+G_DEFINE_TYPE (
+ EMailSignatureComboBox,
+ e_mail_signature_combo_box,
+ GTK_TYPE_COMBO_BOX)
+
+static gboolean
+mail_signature_combo_box_refresh_idle_cb (EMailSignatureComboBox *combo_box)
+{
+ /* The refresh function will clear the idle ID. */
+ e_mail_signature_combo_box_refresh (combo_box);
+
+ return FALSE;
+}
+
+static void
+mail_signature_combo_box_registry_changed (ESourceRegistry *registry,
+ ESource *source,
+ EMailSignatureComboBox *combo_box)
+{
+ /* If the ESource in question has a "Mail Signature" extension,
+ * schedule a refresh of the tree model. Otherwise ignore it.
+ * We use an idle callback to limit how frequently we refresh
+ * the tree model, in case the registry is emitting lots of
+ * signals at once. */
+
+ if (!SOURCE_IS_MAIL_SIGNATURE (source))
+ return;
+
+ if (combo_box->priv->refresh_idle_id > 0)
+ return;
+
+ combo_box->priv->refresh_idle_id = g_idle_add (
+ (GSourceFunc) mail_signature_combo_box_refresh_idle_cb,
+ combo_box);
+}
+
+static gboolean
+mail_signature_combo_box_identity_to_signature (GBinding *binding,
+ const GValue *source_value,
+ GValue *target_value,
+ gpointer user_data)
+{
+ EMailSignatureComboBox *combo_box;
+ ESourceRegistry *registry;
+ GObject *source_object;
+ ESource *source;
+ ESourceMailIdentity *extension;
+ const gchar *identity_uid;
+ const gchar *signature_uid = "none";
+ const gchar *extension_name;
+
+ /* Source and target are the same object. */
+ source_object = g_binding_get_source (binding);
+ combo_box = E_MAIL_SIGNATURE_COMBO_BOX (source_object);
+ registry = e_mail_signature_combo_box_get_registry (combo_box);
+
+ identity_uid = g_value_get_string (source_value);
+ if (identity_uid == NULL)
+ return FALSE;
+
+ source = e_source_registry_ref_source (registry, identity_uid);
+ if (source == NULL)
+ return FALSE;
+
+ extension_name = E_SOURCE_EXTENSION_MAIL_IDENTITY;
+ if (!e_source_has_extension (source, extension_name)) {
+ g_object_unref (source);
+ return FALSE;
+ }
+
+ extension = e_source_get_extension (source, extension_name);
+ signature_uid = e_source_mail_identity_get_signature_uid (extension);
+ g_value_set_string (target_value, signature_uid);
+
+ g_object_unref (source);
+
+ return TRUE;
+}
+
+static void
+mail_signature_combo_box_set_registry (EMailSignatureComboBox *combo_box,
+ ESourceRegistry *registry)
+{
+ g_return_if_fail (E_IS_SOURCE_REGISTRY (registry));
+ g_return_if_fail (combo_box->priv->registry == NULL);
+
+ combo_box->priv->registry = g_object_ref (registry);
+
+ g_signal_connect (
+ registry, "source-added",
+ G_CALLBACK (mail_signature_combo_box_registry_changed),
+ combo_box);
+
+ g_signal_connect (
+ registry, "source-changed",
+ G_CALLBACK (mail_signature_combo_box_registry_changed),
+ combo_box);
+
+ g_signal_connect (
+ registry, "source-removed",
+ G_CALLBACK (mail_signature_combo_box_registry_changed),
+ combo_box);
+}
+
+static void
+mail_signature_combo_box_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_IDENTITY_UID:
+ e_mail_signature_combo_box_set_identity_uid (
+ E_MAIL_SIGNATURE_COMBO_BOX (object),
+ g_value_get_string (value));
+ return;
+
+ case PROP_REGISTRY:
+ mail_signature_combo_box_set_registry (
+ E_MAIL_SIGNATURE_COMBO_BOX (object),
+ g_value_get_object (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+mail_signature_combo_box_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_IDENTITY_UID:
+ g_value_set_string (
+ value,
+ e_mail_signature_combo_box_get_identity_uid (
+ E_MAIL_SIGNATURE_COMBO_BOX (object)));
+ return;
+
+ case PROP_REGISTRY:
+ g_value_set_object (
+ value,
+ e_mail_signature_combo_box_get_registry (
+ E_MAIL_SIGNATURE_COMBO_BOX (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+mail_signature_combo_box_dispose (GObject *object)
+{
+ EMailSignatureComboBoxPrivate *priv;
+
+ priv = E_MAIL_SIGNATURE_COMBO_BOX_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;
+ }
+
+ if (priv->refresh_idle_id > 0) {
+ g_source_remove (priv->refresh_idle_id);
+ priv->refresh_idle_id = 0;
+ }
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (e_mail_signature_combo_box_parent_class)->
+ dispose (object);
+}
+
+static void
+mail_signature_combo_box_finalize (GObject *object)
+{
+ EMailSignatureComboBoxPrivate *priv;
+
+ priv = E_MAIL_SIGNATURE_COMBO_BOX_GET_PRIVATE (object);
+
+ g_free (priv->identity_uid);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (e_mail_signature_combo_box_parent_class)->
+ finalize (object);
+}
+
+static void
+mail_signature_combo_box_constructed (GObject *object)
+{
+ GtkListStore *list_store;
+ GtkComboBox *combo_box;
+ GtkCellLayout *cell_layout;
+ GtkCellRenderer *cell_renderer;
+
+ /* Chain up to parent's constructed() method. */
+ G_OBJECT_CLASS (e_mail_signature_combo_box_parent_class)->
+ constructed (object);
+
+ combo_box = GTK_COMBO_BOX (object);
+ cell_layout = GTK_CELL_LAYOUT (object);
+
+ list_store = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_STRING);
+ gtk_combo_box_set_model (combo_box, GTK_TREE_MODEL (list_store));
+ gtk_combo_box_set_id_column (combo_box, COLUMN_UID);
+ g_object_unref (list_store);
+
+ cell_renderer = gtk_cell_renderer_text_new ();
+ gtk_cell_layout_pack_start (cell_layout, cell_renderer, TRUE);
+ gtk_cell_layout_add_attribute (
+ cell_layout, cell_renderer, "text", COLUMN_DISPLAY_NAME);
+
+ g_object_bind_property_full (
+ combo_box, "identity-uid",
+ combo_box, "active-id",
+ G_BINDING_DEFAULT,
+ mail_signature_combo_box_identity_to_signature,
+ NULL,
+ NULL, (GDestroyNotify) NULL);
+
+ e_mail_signature_combo_box_refresh (
+ E_MAIL_SIGNATURE_COMBO_BOX (object));
+}
+
+static void
+e_mail_signature_combo_box_class_init (EMailSignatureComboBoxClass *class)
+{
+ GObjectClass *object_class;
+
+ g_type_class_add_private (
+ class, sizeof (EMailSignatureComboBoxPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = mail_signature_combo_box_set_property;
+ object_class->get_property = mail_signature_combo_box_get_property;
+ object_class->dispose = mail_signature_combo_box_dispose;
+ object_class->finalize = mail_signature_combo_box_finalize;
+ object_class->constructed = mail_signature_combo_box_constructed;
+
+ g_object_class_install_property (
+ object_class,
+ PROP_IDENTITY_UID,
+ g_param_spec_string (
+ "identity-uid",
+ "Identity UID",
+ NULL,
+ NULL,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ 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));
+}
+
+static void
+e_mail_signature_combo_box_init (EMailSignatureComboBox *combo_box)
+{
+ combo_box->priv = E_MAIL_SIGNATURE_COMBO_BOX_GET_PRIVATE (combo_box);
+}
+
+GtkWidget *
+e_mail_signature_combo_box_new (ESourceRegistry *registry)
+{
+ g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), NULL);
+
+ return g_object_new (
+ E_TYPE_MAIL_SIGNATURE_COMBO_BOX,
+ "registry", registry, NULL);
+}
+
+void
+e_mail_signature_combo_box_refresh (EMailSignatureComboBox *combo_box)
+{
+ ESourceRegistry *registry;
+ GtkComboBox *gtk_combo_box;
+ GtkTreeModel *tree_model;
+ GtkTreeIter iter;
+ ESource *source;
+ GList *list, *link;
+ const gchar *extension_name;
+ const gchar *saved_uid;
+
+ g_return_if_fail (E_IS_MAIL_SIGNATURE_COMBO_BOX (combo_box));
+
+ if (combo_box->priv->refresh_idle_id > 0) {
+ g_source_remove (combo_box->priv->refresh_idle_id);
+ combo_box->priv->refresh_idle_id = 0;
+ }
+
+ gtk_combo_box = GTK_COMBO_BOX (combo_box);
+ tree_model = gtk_combo_box_get_model (gtk_combo_box);
+
+ /* This is an interned string, which means it's safe
+ * to use even after clearing the combo box model. */
+ saved_uid = gtk_combo_box_get_active_id (gtk_combo_box);
+
+ gtk_list_store_clear (GTK_LIST_STORE (tree_model));
+
+ extension_name = E_SOURCE_EXTENSION_MAIL_SIGNATURE;
+ registry = e_mail_signature_combo_box_get_registry (combo_box);
+ list = e_source_registry_list_sources (registry, extension_name);
+
+ /* The "None" option always comes first. */
+
+ gtk_list_store_append (GTK_LIST_STORE (tree_model), &iter);
+
+ gtk_list_store_set (
+ GTK_LIST_STORE (tree_model), &iter,
+ COLUMN_DISPLAY_NAME, _("None"),
+ COLUMN_UID, "none", -1);
+
+ /* The "autogenerated" UID has special meaning. */
+
+ gtk_list_store_append (GTK_LIST_STORE (tree_model), &iter);
+
+ gtk_list_store_set (
+ GTK_LIST_STORE (tree_model), &iter,
+ COLUMN_DISPLAY_NAME, _("Autogenerated"),
+ COLUMN_UID, E_MAIL_SIGNATURE_AUTOGENERATED_UID, -1);
+
+ /* Followed by the other mail signatures, alphabetized. */
+
+ for (link = list; link != NULL; link = g_list_next (link)) {
+ GtkTreeIter iter;
+ const gchar *display_name;
+ const gchar *uid;
+
+ source = E_SOURCE (link->data);
+ display_name = e_source_get_display_name (source);
+ uid = e_source_get_uid (source);
+
+ gtk_list_store_append (GTK_LIST_STORE (tree_model), &iter);
+
+ gtk_list_store_set (
+ GTK_LIST_STORE (tree_model), &iter,
+ COLUMN_DISPLAY_NAME, display_name,
+ COLUMN_UID, uid, -1);
+ }
+
+ g_list_free_full (list, (GDestroyNotify) g_object_unref);
+
+ /* Try and restore the previous selected source, or else "None". */
+
+ if (saved_uid != NULL)
+ gtk_combo_box_set_active_id (gtk_combo_box, saved_uid);
+
+ if (gtk_combo_box_get_active_id (gtk_combo_box) == NULL)
+ gtk_combo_box_set_active (gtk_combo_box, 0);
+}
+
+ESourceRegistry *
+e_mail_signature_combo_box_get_registry (EMailSignatureComboBox *combo_box)
+{
+ g_return_val_if_fail (E_IS_MAIL_SIGNATURE_COMBO_BOX (combo_box), NULL);
+
+ return combo_box->priv->registry;
+}
+
+const gchar *
+e_mail_signature_combo_box_get_identity_uid (EMailSignatureComboBox *combo_box)
+{
+ g_return_val_if_fail (E_IS_MAIL_SIGNATURE_COMBO_BOX (combo_box), NULL);
+
+ return combo_box->priv->identity_uid;
+}
+
+void
+e_mail_signature_combo_box_set_identity_uid (EMailSignatureComboBox *combo_box,
+ const gchar *identity_uid)
+{
+ const gchar *active_id;
+
+ g_return_if_fail (E_IS_MAIL_SIGNATURE_COMBO_BOX (combo_box));
+
+ if (g_strcmp0 (combo_box->priv->identity_uid, identity_uid) == 0)
+ return;
+
+ g_free (combo_box->priv->identity_uid);
+ combo_box->priv->identity_uid = g_strdup (identity_uid);
+
+ g_object_notify (G_OBJECT (combo_box), "identity-uid");
+
+ /* If "Autogenerated" is selected, emit a "changed" signal as
+ * a hint to whomever is listening to reload the signature. */
+ active_id = gtk_combo_box_get_active_id (GTK_COMBO_BOX (combo_box));
+ if (g_strcmp0 (active_id, E_MAIL_SIGNATURE_AUTOGENERATED_UID) == 0)
+ g_signal_emit_by_name (combo_box, "changed");
+}
+
+/**************** e_mail_signature_combo_box_load_selected() *****************/
+
+typedef struct _LoadContext LoadContext;
+
+struct _LoadContext {
+ gchar *contents;
+ gsize length;
+ gboolean is_html;
+};
+
+static void
+load_context_free (LoadContext *context)
+{
+ g_free (context->contents);
+ g_slice_free (LoadContext, context);
+}
+
+static void
+mail_signature_combo_box_autogenerate (EMailSignatureComboBox *combo_box,
+ LoadContext *context)
+{
+ ESourceMailIdentity *extension;
+ ESourceRegistry *registry;
+ ESource *source;
+ GString *buffer;
+ const gchar *extension_name;
+ const gchar *identity_uid;
+ const gchar *text;
+ gchar *escaped;
+
+ identity_uid = e_mail_signature_combo_box_get_identity_uid (combo_box);
+
+ /* If we have no mail identity UID, handle it as though
+ * "None" were selected. No need to report an error. */
+ if (identity_uid == NULL)
+ return;
+
+ registry = e_mail_signature_combo_box_get_registry (combo_box);
+ source = e_source_registry_ref_source (registry, identity_uid);
+
+ /* If the mail identity lookup fails, handle it as though
+ * "None" were selected. No need to report an error. */
+ if (source == NULL)
+ return;
+
+ /* If the source is not actually a mail identity, handle it as
+ * though "None" were selected. No need to report an error. */
+ extension_name = E_SOURCE_EXTENSION_MAIL_IDENTITY;
+ if (!e_source_has_extension (source, extension_name)) {
+ g_object_unref (source);
+ return;
+ }
+
+ extension = e_source_get_extension (source, extension_name);
+
+ /* The autogenerated signature format is:
+ *
+ * <NAME> <ADDRESS>
+ * <ORGANIZATION>
+ *
+ * The <ADDRESS> is a mailto link and
+ * the <ORGANIZATION> line is optional.
+ */
+
+ buffer = g_string_sized_new (512);
+
+ text = e_source_mail_identity_get_name (extension);
+ escaped = (text != NULL) ? g_markup_escape_text (text, -1) : NULL;
+ if (escaped != NULL && *escaped != '\0')
+ g_string_append (buffer, escaped);
+ g_free (escaped);
+
+ text = e_source_mail_identity_get_address (extension);
+ escaped = (text != NULL) ? g_markup_escape_text (text, -1) : NULL;
+ if (escaped != NULL && *escaped != '\0')
+ g_string_append_printf (
+ buffer, " &lt;<a href=\"mailto:%s\">%s</a>&gt;",
+ escaped, escaped);
+ g_free (escaped);
+
+ text = e_source_mail_identity_get_organization (extension);
+ escaped = (text != NULL) ? g_markup_escape_text (text, -1) : NULL;
+ if (escaped != NULL && *escaped != '\0')
+ g_string_append_printf (buffer, "<br>%s", escaped);
+ g_free (escaped);
+
+ context->length = buffer->len;
+ context->contents = g_string_free (buffer, FALSE);
+ context->is_html = TRUE;
+
+ g_object_unref (source);
+}
+
+static void
+mail_signature_combo_box_load_cb (ESource *source,
+ GAsyncResult *result,
+ GSimpleAsyncResult *simple)
+{
+ ESourceMailSignature *extension;
+ LoadContext *context;
+ const gchar *extension_name;
+ const gchar *mime_type;
+ GError *error = NULL;
+
+ context = g_simple_async_result_get_op_res_gpointer (simple);
+
+ e_source_mail_signature_load_finish (
+ source, result, &context->contents, &context->length, &error);
+
+ if (error != NULL) {
+ g_simple_async_result_set_from_error (simple, error);
+ g_simple_async_result_complete (simple);
+ g_object_unref (simple);
+ g_error_free (error);
+ return;
+ }
+
+ 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);
+ context->is_html = (g_strcmp0 (mime_type, "text/html") == 0);
+
+ g_simple_async_result_complete (simple);
+
+ g_object_unref (simple);
+}
+
+void
+e_mail_signature_combo_box_load_selected (EMailSignatureComboBox *combo_box,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *simple;
+ ESourceRegistry *registry;
+ LoadContext *context;
+ ESource *source;
+ const gchar *active_id;
+
+ g_return_if_fail (E_IS_MAIL_SIGNATURE_COMBO_BOX (combo_box));
+
+ context = g_slice_new0 (LoadContext);
+
+ simple = g_simple_async_result_new (
+ G_OBJECT (combo_box), callback, user_data,
+ e_mail_signature_combo_box_load_selected);
+
+ g_simple_async_result_set_op_res_gpointer (
+ simple, context, (GDestroyNotify) load_context_free);
+
+ active_id = gtk_combo_box_get_active_id (GTK_COMBO_BOX (combo_box));
+
+ if (active_id == NULL) {
+ g_simple_async_result_complete_in_idle (simple);
+ g_object_unref (simple);
+ return;
+ }
+
+ if (g_strcmp0 (active_id, E_MAIL_SIGNATURE_AUTOGENERATED_UID) == 0) {
+ mail_signature_combo_box_autogenerate (combo_box, context);
+ g_simple_async_result_complete_in_idle (simple);
+ g_object_unref (simple);
+ return;
+ }
+
+ registry = e_mail_signature_combo_box_get_registry (combo_box);
+ source = e_source_registry_ref_source (registry, active_id);
+
+ /* If for some reason the ESource lookup fails, handle it as
+ * though "None" were selected. No need to report an error. */
+ if (source == NULL) {
+ g_simple_async_result_complete_in_idle (simple);
+ g_object_unref (simple);
+ return;
+ }
+
+ e_source_mail_signature_load (
+ source, io_priority, cancellable, (GAsyncReadyCallback)
+ mail_signature_combo_box_load_cb, simple);
+
+ g_object_unref (source);
+}
+
+gboolean
+e_mail_signature_combo_box_load_selected_finish (EMailSignatureComboBox *combo_box,
+ GAsyncResult *result,
+ gchar **contents,
+ gsize *length,
+ gboolean *is_html,
+ GError **error)
+{
+ GSimpleAsyncResult *simple;
+ LoadContext *context;
+
+ g_return_val_if_fail (
+ g_simple_async_result_is_valid (
+ result, G_OBJECT (combo_box),
+ e_mail_signature_combo_box_load_selected), FALSE);
+
+ simple = G_SIMPLE_ASYNC_RESULT (result);
+ context = g_simple_async_result_get_op_res_gpointer (simple);
+
+ if (g_simple_async_result_propagate_error (simple, error))
+ return FALSE;
+
+ if (contents != NULL) {
+ *contents = context->contents;
+ context->contents = NULL;
+ }
+
+ if (length != NULL)
+ *length = context->length;
+
+ if (is_html != NULL)
+ *is_html = context->is_html;
+
+ return TRUE;
+}
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 0000000000..d39ba96e75
--- /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 0000000000..961edf14ca
--- /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 0000000000..c525d5a8a4
--- /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/e-util/e-mail-signature-manager.c b/e-util/e-mail-signature-manager.c
new file mode 100644
index 0000000000..66463336ea
--- /dev/null
+++ b/e-util/e-mail-signature-manager.c
@@ -0,0 +1,708 @@
+/*
+ * e-mail-signature-manager.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-manager.h"
+
+#include <glib/gi18n.h>
+#include <glib/gstdio.h>
+#include <gdk/gdkkeysyms.h>
+
+#include <libedataserver/libedataserver.h>
+
+#include "e-mail-signature-preview.h"
+#include "e-mail-signature-tree-view.h"
+#include "e-mail-signature-script-dialog.h"
+
+#define E_MAIL_SIGNATURE_MANAGER_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_MAIL_SIGNATURE_MANAGER, EMailSignatureManagerPrivate))
+
+#define PREVIEW_HEIGHT 200
+
+struct _EMailSignatureManagerPrivate {
+ ESourceRegistry *registry;
+
+ GtkWidget *tree_view; /* not referenced */
+ GtkWidget *add_button; /* not referenced */
+ GtkWidget *add_script_button; /* not referenced */
+ GtkWidget *edit_button; /* not referenced */
+ GtkWidget *remove_button; /* not referenced */
+ GtkWidget *preview; /* not referenced */
+
+ gboolean prefer_html;
+};
+
+enum {
+ PROP_0,
+ PROP_PREFER_HTML,
+ PROP_REGISTRY
+};
+
+enum {
+ ADD_SIGNATURE,
+ ADD_SIGNATURE_SCRIPT,
+ EDITOR_CREATED,
+ EDIT_SIGNATURE,
+ REMOVE_SIGNATURE,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+G_DEFINE_TYPE (
+ EMailSignatureManager,
+ e_mail_signature_manager,
+ GTK_TYPE_PANED)
+
+static void
+mail_signature_manager_emit_editor_created (EMailSignatureManager *manager,
+ GtkWidget *editor)
+{
+ g_return_if_fail (E_IS_MAIL_SIGNATURE_EDITOR (editor));
+
+ g_signal_emit (manager, signals[EDITOR_CREATED], 0, editor);
+}
+
+static gboolean
+mail_signature_manager_key_press_event_cb (EMailSignatureManager *manager,
+ GdkEventKey *event)
+{
+ if (event->keyval == GDK_KEY_Delete) {
+ e_mail_signature_manager_remove_signature (manager);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void
+mail_signature_manager_run_script_dialog (EMailSignatureManager *manager,
+ ESource *source,
+ const gchar *title)
+{
+ ESourceRegistry *registry;
+ GtkWidget *dialog;
+ gpointer parent;
+
+ registry = e_mail_signature_manager_get_registry (manager);
+
+ parent = gtk_widget_get_toplevel (GTK_WIDGET (manager));
+ parent = gtk_widget_is_toplevel (parent) ? parent : NULL;
+
+ dialog = e_mail_signature_script_dialog_new (registry, parent, source);
+ gtk_window_set_title (GTK_WINDOW (dialog), title);
+
+ if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_OK) {
+ EAsyncClosure *closure;
+ GAsyncResult *result;
+ GError *error = NULL;
+
+ closure = e_async_closure_new ();
+
+ /* FIXME Make this cancellable. */
+ e_mail_signature_script_dialog_commit (
+ E_MAIL_SIGNATURE_SCRIPT_DIALOG (dialog), NULL,
+ e_async_closure_callback, closure);
+
+ result = e_async_closure_wait (closure);
+
+ e_mail_signature_script_dialog_commit_finish (
+ E_MAIL_SIGNATURE_SCRIPT_DIALOG (dialog),
+ result, &error);
+
+ e_async_closure_free (closure);
+
+ /* FIXME Make this into an EAlert. */
+ if (error != NULL) {
+ g_warning ("%s: %s", G_STRFUNC, error->message);
+ g_error_free (error);
+ }
+ }
+
+ gtk_widget_destroy (dialog);
+}
+
+static void
+mail_signature_manager_selection_changed_cb (EMailSignatureManager *manager,
+ GtkTreeSelection *selection)
+{
+ EMailSignaturePreview *preview;
+ EMailSignatureTreeView *tree_view;
+ ESource *source;
+ GtkWidget *edit_button;
+ GtkWidget *remove_button;
+ gboolean sensitive;
+ const gchar *uid = NULL;
+
+ edit_button = manager->priv->edit_button;
+ remove_button = manager->priv->remove_button;
+
+ tree_view = E_MAIL_SIGNATURE_TREE_VIEW (manager->priv->tree_view);
+ source = e_mail_signature_tree_view_ref_selected_source (tree_view);
+
+ if (source != NULL)
+ uid = e_source_get_uid (source);
+
+ preview = E_MAIL_SIGNATURE_PREVIEW (manager->priv->preview);
+ e_mail_signature_preview_set_source_uid (preview, uid);
+
+ sensitive = (source != NULL);
+ gtk_widget_set_sensitive (edit_button, sensitive);
+ gtk_widget_set_sensitive (remove_button, sensitive);
+
+ if (source != NULL)
+ g_object_unref (source);
+}
+
+static void
+mail_signature_manager_set_registry (EMailSignatureManager *manager,
+ ESourceRegistry *registry)
+{
+ g_return_if_fail (E_IS_SOURCE_REGISTRY (registry));
+ g_return_if_fail (manager->priv->registry == NULL);
+
+ manager->priv->registry = g_object_ref (registry);
+}
+
+static void
+mail_signature_manager_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_PREFER_HTML:
+ e_mail_signature_manager_set_prefer_html (
+ E_MAIL_SIGNATURE_MANAGER (object),
+ g_value_get_boolean (value));
+ return;
+
+ case PROP_REGISTRY:
+ mail_signature_manager_set_registry (
+ E_MAIL_SIGNATURE_MANAGER (object),
+ g_value_get_object (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+mail_signature_manager_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_PREFER_HTML:
+ g_value_set_boolean (
+ value,
+ e_mail_signature_manager_get_prefer_html (
+ E_MAIL_SIGNATURE_MANAGER (object)));
+ return;
+
+ case PROP_REGISTRY:
+ g_value_set_object (
+ value,
+ e_mail_signature_manager_get_registry (
+ E_MAIL_SIGNATURE_MANAGER (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+mail_signature_manager_dispose (GObject *object)
+{
+ EMailSignatureManagerPrivate *priv;
+
+ priv = E_MAIL_SIGNATURE_MANAGER_GET_PRIVATE (object);
+
+ if (priv->registry != NULL) {
+ g_object_unref (priv->registry);
+ priv->registry = NULL;
+ }
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (e_mail_signature_manager_parent_class)->
+ dispose (object);
+}
+
+static void
+mail_signature_manager_constructed (GObject *object)
+{
+ EMailSignatureManager *manager;
+ GtkTreeSelection *selection;
+ ESourceRegistry *registry;
+ GSettings *settings;
+ GtkWidget *container;
+ GtkWidget *widget;
+ GtkWidget *hbox;
+
+ /* Chain up to parent's constructed() method. */
+ G_OBJECT_CLASS (e_mail_signature_manager_parent_class)->
+ constructed (object);
+
+ manager = E_MAIL_SIGNATURE_MANAGER (object);
+ registry = e_mail_signature_manager_get_registry (manager);
+
+ gtk_orientable_set_orientation (
+ GTK_ORIENTABLE (manager), GTK_ORIENTATION_VERTICAL);
+
+ container = GTK_WIDGET (manager);
+
+ widget = gtk_alignment_new (0.0, 0.0, 1.0, 1.0);
+ gtk_alignment_set_padding (GTK_ALIGNMENT (widget), 0, 12, 0, 0);
+ gtk_paned_pack1 (GTK_PANED (container), widget, TRUE, FALSE);
+ gtk_widget_show (widget);
+
+ container = widget;
+
+ widget = gtk_hbox_new (FALSE, 6);
+ gtk_container_add (GTK_CONTAINER (container), widget);
+ gtk_widget_show (widget);
+
+ container = hbox = 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_mail_signature_tree_view_new (registry);
+ gtk_container_add (GTK_CONTAINER (container), widget);
+ manager->priv->tree_view = widget; /* not referenced */
+ gtk_widget_show (widget);
+
+ g_signal_connect_swapped (
+ widget, "key-press-event",
+ G_CALLBACK (mail_signature_manager_key_press_event_cb),
+ manager);
+
+ g_signal_connect_swapped (
+ widget, "row-activated",
+ G_CALLBACK (e_mail_signature_manager_edit_signature),
+ manager);
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
+
+ g_signal_connect_swapped (
+ selection, "changed",
+ G_CALLBACK (mail_signature_manager_selection_changed_cb),
+ manager);
+
+ container = hbox;
+
+ widget = gtk_vbutton_box_new ();
+ gtk_button_box_set_layout (
+ GTK_BUTTON_BOX (widget), GTK_BUTTONBOX_START);
+ gtk_box_set_spacing (GTK_BOX (widget), 6);
+ gtk_box_pack_start (GTK_BOX (container), widget, FALSE, TRUE, 0);
+ gtk_widget_show (widget);
+
+ container = widget;
+
+ widget = gtk_button_new_from_stock (GTK_STOCK_ADD);
+ gtk_box_pack_start (GTK_BOX (container), widget, TRUE, TRUE, 0);
+ manager->priv->add_button = widget; /* not referenced */
+ gtk_widget_show (widget);
+
+ g_signal_connect_swapped (
+ widget, "clicked",
+ G_CALLBACK (e_mail_signature_manager_add_signature),
+ manager);
+
+ widget = gtk_button_new_with_mnemonic (_("Add _Script"));
+ gtk_button_set_image (
+ GTK_BUTTON (widget), gtk_image_new_from_stock (
+ GTK_STOCK_EXECUTE, GTK_ICON_SIZE_BUTTON));
+ gtk_box_pack_start (GTK_BOX (container), widget, TRUE, TRUE, 0);
+ manager->priv->add_script_button = widget; /* not referenced */
+ gtk_widget_show (widget);
+
+ settings = g_settings_new ("org.gnome.desktop.lockdown");
+
+ g_settings_bind (
+ settings, "disable-command-line",
+ widget, "visible",
+ G_SETTINGS_BIND_GET |
+ G_SETTINGS_BIND_INVERT_BOOLEAN);
+
+ g_object_unref (settings);
+
+ g_signal_connect_swapped (
+ widget, "clicked",
+ G_CALLBACK (e_mail_signature_manager_add_signature_script),
+ manager);
+
+ widget = gtk_button_new_from_stock (GTK_STOCK_EDIT);
+ gtk_box_pack_start (GTK_BOX (container), widget, TRUE, TRUE, 0);
+ manager->priv->edit_button = widget; /* not referenced */
+ gtk_widget_show (widget);
+
+ g_signal_connect_swapped (
+ widget, "clicked",
+ G_CALLBACK (e_mail_signature_manager_edit_signature),
+ manager);
+
+ widget = gtk_button_new_from_stock (GTK_STOCK_REMOVE);
+ gtk_box_pack_start (GTK_BOX (container), widget, TRUE, TRUE, 0);
+ manager->priv->remove_button = widget; /* not referenced */
+ gtk_widget_show (widget);
+
+ g_signal_connect_swapped (
+ widget, "clicked",
+ G_CALLBACK (e_mail_signature_manager_remove_signature),
+ manager);
+
+ container = GTK_WIDGET (manager);
+
+ 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_pack2 (GTK_PANED (container), widget, FALSE, FALSE);
+ gtk_widget_show (widget);
+
+ container = widget;
+
+ widget = e_mail_signature_preview_new (registry);
+ gtk_container_add (GTK_CONTAINER (container), widget);
+ manager->priv->preview = widget; /* not referenced */
+ gtk_widget_show (widget);
+
+ gtk_paned_set_position (GTK_PANED (manager), PREVIEW_HEIGHT);
+}
+
+static void
+mail_signature_manager_add_signature (EMailSignatureManager *manager)
+{
+ ESourceRegistry *registry;
+ GtkWidget *editor;
+
+ registry = e_mail_signature_manager_get_registry (manager);
+
+ editor = e_mail_signature_editor_new (registry, NULL);
+ gtkhtml_editor_set_html_mode (
+ GTKHTML_EDITOR (editor), manager->priv->prefer_html);
+ mail_signature_manager_emit_editor_created (manager, editor);
+
+ gtk_widget_grab_focus (manager->priv->tree_view);
+}
+
+static void
+mail_signature_manager_add_signature_script (EMailSignatureManager *manager)
+{
+ const gchar *title;
+
+ title = _("Add Signature Script");
+ mail_signature_manager_run_script_dialog (manager, NULL, title);
+
+ gtk_widget_grab_focus (manager->priv->tree_view);
+}
+
+static void
+mail_signature_manager_editor_created (EMailSignatureManager *manager,
+ EMailSignatureEditor *editor)
+{
+ GtkWindowPosition position;
+ gpointer parent;
+
+ position = GTK_WIN_POS_CENTER_ON_PARENT;
+ parent = gtk_widget_get_toplevel (GTK_WIDGET (manager));
+ parent = gtk_widget_is_toplevel (parent) ? parent : NULL;
+
+ gtk_window_set_transient_for (GTK_WINDOW (editor), parent);
+ gtk_window_set_position (GTK_WINDOW (editor), position);
+ gtk_widget_show (GTK_WIDGET (editor));
+}
+
+static void
+mail_signature_manager_edit_signature (EMailSignatureManager *manager)
+{
+ EMailSignatureTreeView *tree_view;
+ ESourceMailSignature *extension;
+ ESourceRegistry *registry;
+ GtkWidget *editor;
+ ESource *source;
+ GFileInfo *file_info;
+ GFile *file;
+ const gchar *attribute;
+ const gchar *extension_name;
+ const gchar *title;
+ GError *error = NULL;
+
+ registry = e_mail_signature_manager_get_registry (manager);
+ tree_view = E_MAIL_SIGNATURE_TREE_VIEW (manager->priv->tree_view);
+ source = e_mail_signature_tree_view_ref_selected_source (tree_view);
+ g_return_if_fail (source != NULL);
+
+ extension_name = E_SOURCE_EXTENSION_MAIL_SIGNATURE;
+ extension = e_source_get_extension (source, extension_name);
+ file = e_source_mail_signature_get_file (extension);
+
+ attribute = G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE;
+
+ /* XXX This blocks but it should just be a local file. */
+ file_info = g_file_query_info (
+ file, attribute, G_FILE_QUERY_INFO_NONE, NULL, &error);
+
+ /* FIXME Make this into an EAlert. */
+ if (error != NULL) {
+ g_warn_if_fail (file_info == NULL);
+ g_warning ("%s: %s", G_STRFUNC, error->message);
+ g_object_unref (source);
+ g_error_free (error);
+ return;
+ }
+
+ if (g_file_info_get_attribute_boolean (file_info, attribute))
+ goto script;
+
+ editor = e_mail_signature_editor_new (registry, source);
+ mail_signature_manager_emit_editor_created (manager, editor);
+
+ goto exit;
+
+script:
+ title = _("Edit Signature Script");
+ mail_signature_manager_run_script_dialog (manager, source, title);
+
+exit:
+ gtk_widget_grab_focus (GTK_WIDGET (tree_view));
+
+ g_object_unref (file_info);
+ g_object_unref (source);
+}
+
+static void
+mail_signature_manager_remove_signature (EMailSignatureManager *manager)
+{
+ EMailSignatureTreeView *tree_view;
+ ESourceMailSignature *extension;
+ ESource *source;
+ GFile *file;
+ const gchar *extension_name;
+ GError *error = NULL;
+
+ tree_view = E_MAIL_SIGNATURE_TREE_VIEW (manager->priv->tree_view);
+ source = e_mail_signature_tree_view_ref_selected_source (tree_view);
+
+ if (source == NULL)
+ return;
+
+ extension_name = E_SOURCE_EXTENSION_MAIL_SIGNATURE;
+ extension = e_source_get_extension (source, extension_name);
+
+ file = e_source_mail_signature_get_file (extension);
+
+ /* XXX This blocks but it should just be a local file. */
+ if (!g_file_delete (file, NULL, &error)) {
+ g_warning ("%s", error->message);
+ g_clear_error (&error);
+ }
+
+ /* Remove the mail signature data source asynchronously.
+ * XXX No callback function because there's not much we can do
+ * if this fails. We should probably implement EAlertSink. */
+ e_source_remove (source, NULL, NULL, NULL);
+
+ gtk_widget_grab_focus (GTK_WIDGET (tree_view));
+
+ g_object_unref (source);
+}
+
+static void
+e_mail_signature_manager_class_init (EMailSignatureManagerClass *class)
+{
+ GObjectClass *object_class;
+
+ g_type_class_add_private (
+ class, sizeof (EMailSignatureManagerPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = mail_signature_manager_set_property;
+ object_class->get_property = mail_signature_manager_get_property;
+ object_class->dispose = mail_signature_manager_dispose;
+ object_class->constructed = mail_signature_manager_constructed;
+
+ class->add_signature = mail_signature_manager_add_signature;
+ class->add_signature_script =
+ mail_signature_manager_add_signature_script;
+ class->editor_created = mail_signature_manager_editor_created;
+ class->edit_signature = mail_signature_manager_edit_signature;
+ class->remove_signature = mail_signature_manager_remove_signature;
+
+ g_object_class_install_property (
+ object_class,
+ PROP_PREFER_HTML,
+ g_param_spec_boolean (
+ "prefer-html",
+ "Prefer HTML",
+ NULL,
+ TRUE,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+
+ 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 |
+ G_PARAM_STATIC_STRINGS));
+
+ signals[ADD_SIGNATURE] = g_signal_new (
+ "add-signature",
+ G_OBJECT_CLASS_TYPE (class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (EMailSignatureManagerClass, add_signature),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ signals[ADD_SIGNATURE_SCRIPT] = g_signal_new (
+ "add-signature-script",
+ G_OBJECT_CLASS_TYPE (class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (
+ EMailSignatureManagerClass, add_signature_script),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ signals[EDITOR_CREATED] = g_signal_new (
+ "editor-created",
+ G_OBJECT_CLASS_TYPE (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (EMailSignatureManagerClass, editor_created),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1,
+ E_TYPE_MAIL_SIGNATURE_EDITOR);
+
+ signals[EDIT_SIGNATURE] = g_signal_new (
+ "edit-signature",
+ G_OBJECT_CLASS_TYPE (class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (EMailSignatureManagerClass, edit_signature),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ signals[REMOVE_SIGNATURE] = g_signal_new (
+ "remove-signature",
+ G_OBJECT_CLASS_TYPE (class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (EMailSignatureManagerClass, remove_signature),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+}
+
+static void
+e_mail_signature_manager_init (EMailSignatureManager *manager)
+{
+ manager->priv = E_MAIL_SIGNATURE_MANAGER_GET_PRIVATE (manager);
+}
+
+GtkWidget *
+e_mail_signature_manager_new (ESourceRegistry *registry)
+{
+ g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), NULL);
+
+ return g_object_new (
+ E_TYPE_MAIL_SIGNATURE_MANAGER,
+ "registry", registry, NULL);
+}
+
+void
+e_mail_signature_manager_add_signature (EMailSignatureManager *manager)
+{
+ g_return_if_fail (E_IS_MAIL_SIGNATURE_MANAGER (manager));
+
+ g_signal_emit (manager, signals[ADD_SIGNATURE], 0);
+}
+
+void
+e_mail_signature_manager_add_signature_script (EMailSignatureManager *manager)
+{
+ g_return_if_fail (E_IS_MAIL_SIGNATURE_MANAGER (manager));
+
+ g_signal_emit (manager, signals[ADD_SIGNATURE_SCRIPT], 0);
+}
+
+void
+e_mail_signature_manager_edit_signature (EMailSignatureManager *manager)
+{
+ g_return_if_fail (E_IS_MAIL_SIGNATURE_MANAGER (manager));
+
+ g_signal_emit (manager, signals[EDIT_SIGNATURE], 0);
+}
+
+void
+e_mail_signature_manager_remove_signature (EMailSignatureManager *manager)
+{
+ g_return_if_fail (E_IS_MAIL_SIGNATURE_MANAGER (manager));
+
+ g_signal_emit (manager, signals[REMOVE_SIGNATURE], 0);
+}
+
+gboolean
+e_mail_signature_manager_get_prefer_html (EMailSignatureManager *manager)
+{
+ g_return_val_if_fail (E_IS_MAIL_SIGNATURE_MANAGER (manager), FALSE);
+
+ return manager->priv->prefer_html;
+}
+
+void
+e_mail_signature_manager_set_prefer_html (EMailSignatureManager *manager,
+ gboolean prefer_html)
+{
+ g_return_if_fail (E_IS_MAIL_SIGNATURE_MANAGER (manager));
+
+ if (manager->priv->prefer_html == prefer_html)
+ return;
+
+ manager->priv->prefer_html = prefer_html;
+
+ g_object_notify (G_OBJECT (manager), "prefer-html");
+}
+
+ESourceRegistry *
+e_mail_signature_manager_get_registry (EMailSignatureManager *manager)
+{
+ g_return_val_if_fail (E_IS_MAIL_SIGNATURE_MANAGER (manager), NULL);
+
+ return manager->priv->registry;
+}
diff --git a/e-util/e-mail-signature-manager.h b/e-util/e-mail-signature-manager.h
new file mode 100644
index 0000000000..4b749c14a6
--- /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 0000000000..8bf27fdb52
--- /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 0000000000..e7b0302e52
--- /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/e-util/e-mail-signature-script-dialog.c b/e-util/e-mail-signature-script-dialog.c
new file mode 100644
index 0000000000..58e8c43157
--- /dev/null
+++ b/e-util/e-mail-signature-script-dialog.c
@@ -0,0 +1,731 @@
+/*
+ * e-mail-signature-script-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-mail-signature-script-dialog.h"
+
+#include <config.h>
+#include <glib/gi18n-lib.h>
+
+#define E_MAIL_SIGNATURE_SCRIPT_DIALOG_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_MAIL_SIGNATURE_SCRIPT_DIALOG, \
+ EMailSignatureScriptDialogPrivate))
+
+typedef struct _AsyncContext AsyncContext;
+
+struct _EMailSignatureScriptDialogPrivate {
+ ESourceRegistry *registry;
+ ESource *source;
+
+ GtkWidget *entry; /* not referenced */
+ GtkWidget *file_chooser; /* not referenced */
+ GtkWidget *alert; /* not referenced */
+
+ gchar *symlink_target;
+};
+
+struct _AsyncContext {
+ ESource *source;
+ GCancellable *cancellable;
+ gchar *symlink_target;
+};
+
+enum {
+ PROP_0,
+ PROP_REGISTRY,
+ PROP_SOURCE,
+ PROP_SYMLINK_TARGET
+};
+
+G_DEFINE_TYPE (
+ EMailSignatureScriptDialog,
+ e_mail_signature_script_dialog,
+ GTK_TYPE_DIALOG)
+
+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->symlink_target);
+
+ g_slice_free (AsyncContext, async_context);
+}
+
+static gboolean
+mail_signature_script_dialog_filter_cb (const GtkFileFilterInfo *filter_info)
+{
+ return g_file_test (filter_info->filename, G_FILE_TEST_IS_EXECUTABLE);
+}
+
+static void
+mail_signature_script_dialog_update_status (EMailSignatureScriptDialog *dialog)
+{
+ ESource *source;
+ const gchar *display_name;
+ const gchar *symlink_target;
+ gboolean show_alert;
+ gboolean sensitive;
+
+ source = e_mail_signature_script_dialog_get_source (dialog);
+
+ display_name = e_source_get_display_name (source);
+ sensitive = (display_name != NULL && *display_name != '\0');
+
+ symlink_target =
+ e_mail_signature_script_dialog_get_symlink_target (dialog);
+
+ if (symlink_target != NULL) {
+ gboolean executable;
+
+ executable = g_file_test (
+ symlink_target, G_FILE_TEST_IS_EXECUTABLE);
+
+ show_alert = !executable;
+ sensitive &= executable;
+ } else {
+ sensitive = FALSE;
+ show_alert = FALSE;
+ }
+
+ if (show_alert)
+ gtk_widget_show (dialog->priv->alert);
+ else
+ gtk_widget_hide (dialog->priv->alert);
+
+ gtk_dialog_set_response_sensitive (
+ GTK_DIALOG (dialog), GTK_RESPONSE_OK, sensitive);
+}
+
+static void
+mail_signature_script_dialog_file_set_cb (GtkFileChooserButton *button,
+ EMailSignatureScriptDialog *dialog)
+{
+ ESource *source;
+ ESourceMailSignature *extension;
+ GtkFileChooser *file_chooser;
+ const gchar *extension_name;
+ gchar *filename;
+
+ file_chooser = GTK_FILE_CHOOSER (button);
+ filename = gtk_file_chooser_get_filename (file_chooser);
+
+ g_free (dialog->priv->symlink_target);
+ dialog->priv->symlink_target = filename; /* takes ownership */
+
+ /* Invalidate the saved MIME type. */
+ extension_name = E_SOURCE_EXTENSION_MAIL_SIGNATURE;
+ source = e_mail_signature_script_dialog_get_source (dialog);
+ extension = e_source_get_extension (source, extension_name);
+ e_source_mail_signature_set_mime_type (extension, NULL);
+
+ g_object_notify (G_OBJECT (dialog), "symlink-target");
+
+ mail_signature_script_dialog_update_status (dialog);
+}
+
+static void
+mail_signature_script_dialog_query_cb (GFile *file,
+ GAsyncResult *result,
+ EMailSignatureScriptDialog *dialog)
+{
+ GFileInfo *file_info;
+ const gchar *symlink_target;
+ GError *error = NULL;
+
+ file_info = g_file_query_info_finish (file, result, &error);
+
+ /* Ignore cancellations. */
+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+ g_warn_if_fail (file_info == NULL);
+ g_object_unref (dialog);
+ g_error_free (error);
+ return;
+
+ } else if (error != NULL) {
+ g_warn_if_fail (file_info == NULL);
+ g_warning ("%s", error->message);
+ g_object_unref (dialog);
+ g_error_free (error);
+ return;
+ }
+
+ g_return_if_fail (G_IS_FILE_INFO (file_info));
+
+ symlink_target = g_file_info_get_symlink_target (file_info);
+
+ e_mail_signature_script_dialog_set_symlink_target (
+ dialog, symlink_target);
+
+ g_object_unref (file_info);
+ g_object_unref (dialog);
+}
+
+static void
+mail_signature_script_dialog_set_registry (EMailSignatureScriptDialog *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
+mail_signature_script_dialog_set_source (EMailSignatureScriptDialog *dialog,
+ 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 (dialog->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. */
+ dialog->priv->source = e_source_new (dbus_object, NULL, &error);
+
+ /* 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 (dialog->priv->source, extension_name);
+
+ /* If we're editing an existing signature, query the symbolic
+ * link target of the signature file so we can initialize the
+ * file chooser button. Note: The asynchronous callback will
+ * run after the dialog initialization is complete. */
+ if (dbus_object != NULL) {
+ ESourceMailSignature *extension;
+ const gchar *extension_name;
+ GFile *file;
+
+ extension_name = E_SOURCE_EXTENSION_MAIL_SIGNATURE;
+ extension = e_source_get_extension (source, extension_name);
+ file = e_source_mail_signature_get_file (extension);
+
+ g_file_query_info_async (
+ file, G_FILE_ATTRIBUTE_STANDARD_SYMLINK_TARGET,
+ G_FILE_QUERY_INFO_NONE, G_PRIORITY_DEFAULT,
+ NULL, (GAsyncReadyCallback)
+ mail_signature_script_dialog_query_cb,
+ g_object_ref (dialog));
+
+ g_object_unref (dbus_object);
+ }
+}
+
+static void
+mail_signature_script_dialog_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_REGISTRY:
+ mail_signature_script_dialog_set_registry (
+ E_MAIL_SIGNATURE_SCRIPT_DIALOG (object),
+ g_value_get_object (value));
+ return;
+
+ case PROP_SOURCE:
+ mail_signature_script_dialog_set_source (
+ E_MAIL_SIGNATURE_SCRIPT_DIALOG (object),
+ g_value_get_object (value));
+ return;
+
+ case PROP_SYMLINK_TARGET:
+ e_mail_signature_script_dialog_set_symlink_target (
+ E_MAIL_SIGNATURE_SCRIPT_DIALOG (object),
+ g_value_get_string (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+mail_signature_script_dialog_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_script_dialog_get_registry (
+ E_MAIL_SIGNATURE_SCRIPT_DIALOG (object)));
+ return;
+
+ case PROP_SOURCE:
+ g_value_set_object (
+ value,
+ e_mail_signature_script_dialog_get_source (
+ E_MAIL_SIGNATURE_SCRIPT_DIALOG (object)));
+ return;
+
+ case PROP_SYMLINK_TARGET:
+ g_value_set_string (
+ value,
+ e_mail_signature_script_dialog_get_symlink_target (
+ E_MAIL_SIGNATURE_SCRIPT_DIALOG (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+mail_signature_script_dialog_dispose (GObject *object)
+{
+ EMailSignatureScriptDialogPrivate *priv;
+
+ priv = E_MAIL_SIGNATURE_SCRIPT_DIALOG_GET_PRIVATE (object);
+
+ 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_script_dialog_parent_class)->
+ dispose (object);
+}
+
+static void
+mail_signature_script_dialog_finalize (GObject *object)
+{
+ EMailSignatureScriptDialogPrivate *priv;
+
+ priv = E_MAIL_SIGNATURE_SCRIPT_DIALOG_GET_PRIVATE (object);
+
+ g_free (priv->symlink_target);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (e_mail_signature_script_dialog_parent_class)->
+ finalize (object);
+}
+
+static void
+mail_signature_script_dialog_constructed (GObject *object)
+{
+ EMailSignatureScriptDialog *dialog;
+ GtkFileFilter *filter;
+ GtkWidget *container;
+ GtkWidget *widget;
+ ESource *source;
+ const gchar *display_name;
+ gchar *markup;
+
+ /* Chain up to parent's constructed() method. */
+ G_OBJECT_CLASS (e_mail_signature_script_dialog_parent_class)->
+ constructed (object);
+
+ dialog = E_MAIL_SIGNATURE_SCRIPT_DIALOG (object);
+
+ source = e_mail_signature_script_dialog_get_source (dialog);
+ display_name = e_source_get_display_name (source);
+
+ gtk_container_set_border_width (GTK_CONTAINER (dialog), 5);
+
+ gtk_dialog_add_button (
+ GTK_DIALOG (dialog),
+ GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL);
+
+ gtk_dialog_add_button (
+ GTK_DIALOG (dialog),
+ GTK_STOCK_SAVE, GTK_RESPONSE_OK);
+
+ gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK);
+
+ container = gtk_dialog_get_content_area (GTK_DIALOG (dialog));
+
+ widget = gtk_table_new (4, 2, FALSE);
+ gtk_table_set_col_spacings (GTK_TABLE (widget), 6);
+ gtk_table_set_row_spacings (GTK_TABLE (widget), 6);
+ gtk_table_set_row_spacing (GTK_TABLE (widget), 0, 12);
+ gtk_container_set_border_width (GTK_CONTAINER (widget), 5);
+ gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+ gtk_widget_show (widget);
+
+ container = widget;
+
+ widget = gtk_image_new_from_stock (
+ GTK_STOCK_DIALOG_INFO, GTK_ICON_SIZE_DIALOG);
+ gtk_table_attach (
+ GTK_TABLE (container), widget,
+ 0, 1, 0, 1, 0, 0, 0, 0);
+ gtk_widget_show (widget);
+
+ widget = gtk_label_new (_(
+ "The output of this script will be used as your\n"
+ "signature. The name you specify will be used\n"
+ "for display purposes only."));
+ gtk_table_attach (
+ GTK_TABLE (container), widget,
+ 1, 2, 0, 1, GTK_FILL | GTK_EXPAND, 0, 0, 0);
+ gtk_widget_show (widget);
+
+ widget = gtk_entry_new ();
+ gtk_entry_set_text (GTK_ENTRY (widget), display_name);
+ gtk_entry_set_activates_default (GTK_ENTRY (widget), TRUE);
+ gtk_table_attach (
+ GTK_TABLE (container), widget,
+ 1, 2, 1, 2, GTK_FILL | GTK_EXPAND, 0, 0, 0);
+ dialog->priv->entry = widget; /* not referenced */
+ gtk_widget_show (widget);
+
+ g_object_bind_property (
+ widget, "text",
+ source, "display-name",
+ G_BINDING_DEFAULT);
+
+ widget = gtk_label_new_with_mnemonic (_("_Name:"));
+ gtk_label_set_mnemonic_widget (
+ GTK_LABEL (widget), dialog->priv->entry);
+ gtk_misc_set_alignment (GTK_MISC (widget), 1.0, 0.5);
+ gtk_table_attach (
+ GTK_TABLE (container), widget,
+ 0, 1, 1, 2, GTK_FILL, 0, 0, 0);
+ gtk_widget_show (widget);
+
+ widget = gtk_file_chooser_button_new (
+ NULL, GTK_FILE_CHOOSER_ACTION_OPEN);
+ gtk_table_attach (
+ GTK_TABLE (container), widget,
+ 1, 2, 2, 3, GTK_FILL | GTK_EXPAND, 0, 0, 0);
+ dialog->priv->file_chooser = widget; /* not referenced */
+ gtk_widget_show (widget);
+
+ /* Restrict file selection to executable files. */
+ filter = gtk_file_filter_new ();
+ gtk_file_filter_add_custom (
+ filter, GTK_FILE_FILTER_FILENAME,
+ (GtkFileFilterFunc) mail_signature_script_dialog_filter_cb,
+ NULL, NULL);
+ gtk_file_chooser_set_filter (GTK_FILE_CHOOSER (widget), filter);
+
+ /* We create symbolic links to script files from the "signatures"
+ * directory, so restrict the selection to local files only. */
+ gtk_file_chooser_set_local_only (GTK_FILE_CHOOSER (widget), TRUE);
+
+ widget = gtk_label_new_with_mnemonic (_("S_cript:"));
+ gtk_label_set_mnemonic_widget (
+ GTK_LABEL (widget), dialog->priv->file_chooser);
+ gtk_table_attach (
+ GTK_TABLE (container), widget,
+ 0, 1, 2, 3, GTK_FILL, 0, 0, 0);
+ gtk_widget_show (widget);
+
+ /* This is just a placeholder. */
+ widget = gtk_label_new (NULL);
+ gtk_table_attach (
+ GTK_TABLE (container), widget,
+ 0, 1, 3, 4, GTK_FILL, 0, 0, 0);
+ gtk_widget_show (widget);
+
+ widget = gtk_hbox_new (FALSE, 6);
+ gtk_table_attach (
+ GTK_TABLE (container), widget,
+ 1, 2, 3, 4, 0, 0, 0, 0);
+ dialog->priv->alert = widget; /* not referenced */
+ gtk_widget_show (widget);
+
+ container = widget;
+
+ widget = gtk_image_new_from_stock (
+ GTK_STOCK_DIALOG_WARNING, GTK_ICON_SIZE_MENU);
+ gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+ gtk_widget_show (widget);
+
+ markup = g_markup_printf_escaped (
+ "<small>%s</small>",
+ _("Script file must be executable."));
+ widget = gtk_label_new (markup);
+ 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, TRUE, TRUE, 0);
+ gtk_widget_show (widget);
+ g_free (markup);
+
+ g_signal_connect (
+ dialog->priv->file_chooser, "file-set",
+ G_CALLBACK (mail_signature_script_dialog_file_set_cb), dialog);
+
+ g_signal_connect_swapped (
+ dialog->priv->entry, "changed",
+ G_CALLBACK (mail_signature_script_dialog_update_status), dialog);
+
+ mail_signature_script_dialog_update_status (dialog);
+}
+
+static void
+e_mail_signature_script_dialog_class_init (EMailSignatureScriptDialogClass *class)
+{
+ GObjectClass *object_class;
+
+ g_type_class_add_private (
+ class, sizeof (EMailSignatureScriptDialogPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = mail_signature_script_dialog_set_property;
+ object_class->get_property = mail_signature_script_dialog_get_property;
+ object_class->dispose = mail_signature_script_dialog_dispose;
+ object_class->finalize = mail_signature_script_dialog_finalize;
+ object_class->constructed = mail_signature_script_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));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_SOURCE,
+ g_param_spec_object (
+ "source",
+ "Source",
+ NULL,
+ E_TYPE_SOURCE,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_SYMLINK_TARGET,
+ g_param_spec_string (
+ "symlink-target",
+ "Symlink Target",
+ NULL,
+ NULL,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+}
+
+static void
+e_mail_signature_script_dialog_init (EMailSignatureScriptDialog *dialog)
+{
+ dialog->priv = E_MAIL_SIGNATURE_SCRIPT_DIALOG_GET_PRIVATE (dialog);
+}
+
+GtkWidget *
+e_mail_signature_script_dialog_new (ESourceRegistry *registry,
+ GtkWindow *parent,
+ 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_SCRIPT_DIALOG,
+ "registry", registry,
+ "transient-for", parent,
+ "source", source, NULL);
+}
+
+ESourceRegistry *
+e_mail_signature_script_dialog_get_registry (EMailSignatureScriptDialog *dialog)
+{
+ g_return_val_if_fail (
+ E_IS_MAIL_SIGNATURE_SCRIPT_DIALOG (dialog), NULL);
+
+ return dialog->priv->registry;
+}
+
+ESource *
+e_mail_signature_script_dialog_get_source (EMailSignatureScriptDialog *dialog)
+{
+ g_return_val_if_fail (
+ E_IS_MAIL_SIGNATURE_SCRIPT_DIALOG (dialog), NULL);
+
+ return dialog->priv->source;
+}
+
+const gchar *
+e_mail_signature_script_dialog_get_symlink_target (EMailSignatureScriptDialog *dialog)
+{
+ g_return_val_if_fail (
+ E_IS_MAIL_SIGNATURE_SCRIPT_DIALOG (dialog), NULL);
+
+ return dialog->priv->symlink_target;
+}
+
+void
+e_mail_signature_script_dialog_set_symlink_target (EMailSignatureScriptDialog *dialog,
+ const gchar *symlink_target)
+{
+ GtkFileChooser *file_chooser;
+
+ g_return_if_fail (E_IS_MAIL_SIGNATURE_SCRIPT_DIALOG (dialog));
+ g_return_if_fail (symlink_target != NULL);
+
+ g_free (dialog->priv->symlink_target);
+ dialog->priv->symlink_target = g_strdup (symlink_target);
+
+ file_chooser = GTK_FILE_CHOOSER (dialog->priv->file_chooser);
+ gtk_file_chooser_set_filename (file_chooser, symlink_target);
+
+ g_object_notify (G_OBJECT (dialog), "symlink-target");
+
+ mail_signature_script_dialog_update_status (dialog);
+}
+
+/****************** e_mail_signature_script_dialog_commit() ******************/
+
+static void
+mail_signature_script_dialog_symlink_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *simple;
+ GError *error = NULL;
+
+ simple = G_SIMPLE_ASYNC_RESULT (user_data);
+
+ e_source_mail_signature_symlink_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_script_dialog_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_symlink (
+ async_context->source,
+ async_context->symlink_target,
+ G_PRIORITY_DEFAULT,
+ async_context->cancellable,
+ mail_signature_script_dialog_symlink_cb,
+ simple);
+}
+
+void
+e_mail_signature_script_dialog_commit (EMailSignatureScriptDialog *dialog,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *simple;
+ AsyncContext *async_context;
+ ESourceRegistry *registry;
+ ESource *source;
+ const gchar *symlink_target;
+
+ g_return_if_fail (E_IS_MAIL_SIGNATURE_SCRIPT_DIALOG (dialog));
+
+ registry = e_mail_signature_script_dialog_get_registry (dialog);
+ source = e_mail_signature_script_dialog_get_source (dialog);
+
+ symlink_target =
+ e_mail_signature_script_dialog_get_symlink_target (dialog);
+
+ async_context = g_slice_new0 (AsyncContext);
+ async_context->source = g_object_ref (source);
+ async_context->symlink_target = g_strdup (symlink_target);
+
+ if (G_IS_CANCELLABLE (cancellable))
+ async_context->cancellable = g_object_ref (cancellable);
+
+ simple = g_simple_async_result_new (
+ G_OBJECT (dialog), callback, user_data,
+ e_mail_signature_script_dialog_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_script_dialog_commit_cb,
+ simple);
+}
+
+gboolean
+e_mail_signature_script_dialog_commit_finish (EMailSignatureScriptDialog *dialog,
+ GAsyncResult *result,
+ GError **error)
+{
+ GSimpleAsyncResult *simple;
+
+ g_return_val_if_fail (
+ g_simple_async_result_is_valid (
+ result, G_OBJECT (dialog),
+ e_mail_signature_script_dialog_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-script-dialog.h b/e-util/e-mail-signature-script-dialog.h
new file mode 100644
index 0000000000..6a266b557d
--- /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/e-util/e-mail-signature-tree-view.c b/e-util/e-mail-signature-tree-view.c
new file mode 100644
index 0000000000..05a2580d78
--- /dev/null
+++ b/e-util/e-mail-signature-tree-view.c
@@ -0,0 +1,395 @@
+/*
+ * e-mail-signature-tree-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/>
+ *
+ */
+
+#include "e-mail-signature-tree-view.h"
+
+#define E_MAIL_SIGNATURE_TREE_VIEW_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_MAIL_SIGNATURE_TREE_VIEW, EMailSignatureTreeViewPrivate))
+
+#define SOURCE_IS_MAIL_SIGNATURE(source) \
+ (e_source_has_extension ((source), E_SOURCE_EXTENSION_MAIL_SIGNATURE))
+
+struct _EMailSignatureTreeViewPrivate {
+ ESourceRegistry *registry;
+ guint refresh_idle_id;
+};
+
+enum {
+ PROP_0,
+ PROP_REGISTRY
+};
+
+enum {
+ COLUMN_DISPLAY_NAME,
+ COLUMN_UID,
+ NUM_COLUMNS
+};
+
+G_DEFINE_TYPE (
+ EMailSignatureTreeView,
+ e_mail_signature_tree_view,
+ GTK_TYPE_TREE_VIEW)
+
+static gboolean
+mail_signature_tree_view_refresh_idle_cb (EMailSignatureTreeView *tree_view)
+{
+ /* The refresh function will clear the idle ID. */
+ e_mail_signature_tree_view_refresh (tree_view);
+
+ return FALSE;
+}
+
+static void
+mail_signature_tree_view_registry_changed (ESourceRegistry *registry,
+ ESource *source,
+ EMailSignatureTreeView *tree_view)
+{
+ /* If the ESource in question has a "Mail Signature" extension,
+ * schedule a refresh of the tree model. Otherwise ignore it.
+ * We use an idle callback to limit how frequently we refresh
+ * the tree model, in case the registry is emitting lots of
+ * signals at once. */
+
+ if (!SOURCE_IS_MAIL_SIGNATURE (source))
+ return;
+
+ if (tree_view->priv->refresh_idle_id > 0)
+ return;
+
+ tree_view->priv->refresh_idle_id = g_idle_add (
+ (GSourceFunc) mail_signature_tree_view_refresh_idle_cb,
+ tree_view);
+}
+
+static void
+mail_signature_tree_view_set_registry (EMailSignatureTreeView *tree_view,
+ ESourceRegistry *registry)
+{
+ g_return_if_fail (E_IS_SOURCE_REGISTRY (registry));
+ g_return_if_fail (tree_view->priv->registry == NULL);
+
+ tree_view->priv->registry = g_object_ref (registry);
+
+ g_signal_connect (
+ registry, "source-added",
+ G_CALLBACK (mail_signature_tree_view_registry_changed),
+ tree_view);
+
+ g_signal_connect (
+ registry, "source-changed",
+ G_CALLBACK (mail_signature_tree_view_registry_changed),
+ tree_view);
+
+ g_signal_connect (
+ registry, "source-removed",
+ G_CALLBACK (mail_signature_tree_view_registry_changed),
+ tree_view);
+}
+
+static void
+mail_signature_tree_view_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_REGISTRY:
+ mail_signature_tree_view_set_registry (
+ E_MAIL_SIGNATURE_TREE_VIEW (object),
+ g_value_get_object (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+mail_signature_tree_view_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_tree_view_get_registry (
+ E_MAIL_SIGNATURE_TREE_VIEW (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+mail_signature_tree_view_dispose (GObject *object)
+{
+ EMailSignatureTreeViewPrivate *priv;
+
+ priv = E_MAIL_SIGNATURE_TREE_VIEW_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;
+ }
+
+ if (priv->refresh_idle_id > 0) {
+ g_source_remove (priv->refresh_idle_id);
+ priv->refresh_idle_id = 0;
+ }
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (e_mail_signature_tree_view_parent_class)->
+ dispose (object);
+}
+
+static void
+mail_signature_tree_view_constructed (GObject *object)
+{
+ GtkTreeView *tree_view;
+ GtkTreeViewColumn *column;
+ GtkCellRenderer *cell_renderer;
+ GtkListStore *list_store;
+
+ /* Chain up to parent's constructed() method. */
+ G_OBJECT_CLASS (e_mail_signature_tree_view_parent_class)->
+ constructed (object);
+
+ list_store = gtk_list_store_new (
+ NUM_COLUMNS,
+ G_TYPE_STRING, /* COLUMN_DISPLAY_NAME */
+ G_TYPE_STRING); /* COLUMN_UID */
+
+ tree_view = GTK_TREE_VIEW (object);
+ gtk_tree_view_set_headers_visible (tree_view, FALSE);
+ gtk_tree_view_set_model (tree_view, GTK_TREE_MODEL (list_store));
+
+ g_object_unref (list_store);
+
+ /* Column: Signature Name */
+
+ column = gtk_tree_view_column_new ();
+ gtk_tree_view_column_set_expand (column, TRUE);
+
+ cell_renderer = gtk_cell_renderer_text_new ();
+ g_object_set (cell_renderer, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
+ gtk_tree_view_column_pack_start (column, cell_renderer, TRUE);
+
+ gtk_tree_view_column_add_attribute (
+ column, cell_renderer, "text", COLUMN_DISPLAY_NAME);
+
+ gtk_tree_view_append_column (tree_view, column);
+
+ e_mail_signature_tree_view_refresh (
+ E_MAIL_SIGNATURE_TREE_VIEW (object));
+}
+
+static void
+e_mail_signature_tree_view_class_init (EMailSignatureTreeViewClass *class)
+{
+ GObjectClass *object_class;
+
+ g_type_class_add_private (
+ class, sizeof (EMailSignatureTreeViewPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = mail_signature_tree_view_set_property;
+ object_class->get_property = mail_signature_tree_view_get_property;
+ object_class->dispose = mail_signature_tree_view_dispose;
+ object_class->constructed = mail_signature_tree_view_constructed;
+
+ 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));
+}
+
+static void
+e_mail_signature_tree_view_init (EMailSignatureTreeView *tree_view)
+{
+ tree_view->priv = E_MAIL_SIGNATURE_TREE_VIEW_GET_PRIVATE (tree_view);
+}
+
+GtkWidget *
+e_mail_signature_tree_view_new (ESourceRegistry *registry)
+{
+ g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), NULL);
+
+ return g_object_new (
+ E_TYPE_MAIL_SIGNATURE_TREE_VIEW,
+ "registry", registry, NULL);
+}
+
+void
+e_mail_signature_tree_view_refresh (EMailSignatureTreeView *tree_view)
+{
+ ESourceRegistry *registry;
+ GtkTreeModel *tree_model;
+ GtkTreeSelection *selection;
+ ESource *source;
+ GList *list, *link;
+ const gchar *extension_name;
+ gchar *saved_uid = NULL;
+
+ g_return_if_fail (E_IS_MAIL_SIGNATURE_TREE_VIEW (tree_view));
+
+ if (tree_view->priv->refresh_idle_id > 0) {
+ g_source_remove (tree_view->priv->refresh_idle_id);
+ tree_view->priv->refresh_idle_id = 0;
+ }
+
+ registry = e_mail_signature_tree_view_get_registry (tree_view);
+ tree_model = gtk_tree_view_get_model (GTK_TREE_VIEW (tree_view));
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree_view));
+
+ source = e_mail_signature_tree_view_ref_selected_source (tree_view);
+ if (source != NULL) {
+ saved_uid = e_source_dup_uid (source);
+ g_object_unref (source);
+ }
+
+ gtk_list_store_clear (GTK_LIST_STORE (tree_model));
+
+ extension_name = E_SOURCE_EXTENSION_MAIL_SIGNATURE;
+ list = e_source_registry_list_sources (registry, extension_name);
+
+ for (link = list; link != NULL; link = g_list_next (link)) {
+ GtkTreeIter iter;
+ const gchar *display_name;
+ const gchar *uid;
+
+ source = E_SOURCE (link->data);
+ display_name = e_source_get_display_name (source);
+ uid = e_source_get_uid (source);
+
+ gtk_list_store_append (GTK_LIST_STORE (tree_model), &iter);
+
+ gtk_list_store_set (
+ GTK_LIST_STORE (tree_model), &iter,
+ COLUMN_DISPLAY_NAME, display_name,
+ COLUMN_UID, uid, -1);
+ }
+
+ g_list_free_full (list, (GDestroyNotify) g_object_unref);
+
+ /* Try and restore the previous selected source. */
+
+ source = NULL;
+
+ if (saved_uid != NULL) {
+ source = e_source_registry_ref_source (registry, saved_uid);
+ g_free (saved_uid);
+ }
+
+ if (source != NULL) {
+ e_mail_signature_tree_view_set_selected_source (
+ tree_view, source);
+ g_object_unref (source);
+ }
+
+ /* Hint to refresh a signature preview. */
+ g_signal_emit_by_name (selection, "changed");
+}
+
+ESourceRegistry *
+e_mail_signature_tree_view_get_registry (EMailSignatureTreeView *tree_view)
+{
+ g_return_val_if_fail (E_IS_MAIL_SIGNATURE_TREE_VIEW (tree_view), NULL);
+
+ return tree_view->priv->registry;
+}
+
+ESource *
+e_mail_signature_tree_view_ref_selected_source (EMailSignatureTreeView *tree_view)
+{
+ ESourceRegistry *registry;
+ GtkTreeSelection *selection;
+ GtkTreeModel *tree_model;
+ GtkTreeIter iter;
+ ESource *source;
+ gchar *uid;
+
+ g_return_val_if_fail (E_IS_MAIL_SIGNATURE_TREE_VIEW (tree_view), NULL);
+
+ registry = e_mail_signature_tree_view_get_registry (tree_view);
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree_view));
+
+ if (!gtk_tree_selection_get_selected (selection, &tree_model, &iter))
+ return NULL;
+
+ gtk_tree_model_get (tree_model, &iter, COLUMN_UID, &uid, -1);
+ source = e_source_registry_ref_source (registry, uid);
+ g_free (uid);
+
+ return source;
+}
+
+void
+e_mail_signature_tree_view_set_selected_source (EMailSignatureTreeView *tree_view,
+ ESource *source)
+{
+ ESourceRegistry *registry;
+ GtkTreeSelection *selection;
+ GtkTreeModel *tree_model;
+ GtkTreeIter iter;
+ gboolean valid;
+
+ g_return_if_fail (E_IS_MAIL_SIGNATURE_TREE_VIEW (tree_view));
+ g_return_if_fail (E_IS_SOURCE (source));
+
+ /* It is a programming error to pass an ESource that has no
+ * "Mail Signature" extension. */
+ g_return_if_fail (SOURCE_IS_MAIL_SIGNATURE (source));
+
+ registry = e_mail_signature_tree_view_get_registry (tree_view);
+ tree_model = gtk_tree_view_get_model (GTK_TREE_VIEW (tree_view));
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree_view));
+
+ valid = gtk_tree_model_get_iter_first (tree_model, &iter);
+
+ while (valid) {
+ ESource *candidate;
+ gchar *uid;
+
+ gtk_tree_model_get (tree_model, &iter, COLUMN_UID, &uid, -1);
+ candidate = e_source_registry_ref_source (registry, uid);
+ g_free (uid);
+
+ if (candidate != NULL && e_source_equal (source, candidate)) {
+ gtk_tree_selection_select_iter (selection, &iter);
+ g_object_unref (candidate);
+ break;
+ }
+
+ if (candidate != NULL)
+ g_object_unref (candidate);
+
+ valid = gtk_tree_model_iter_next (tree_model, &iter);
+ }
+}
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 0000000000..4da53291a2
--- /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 0000000000..a419626b8d
--- /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 0000000000..cb2923d3fb
--- /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/e-util/e-menu-tool-action.c b/e-util/e-menu-tool-action.c
new file mode 100644
index 0000000000..3ed37cb008
--- /dev/null
+++ b/e-util/e-menu-tool-action.c
@@ -0,0 +1,59 @@
+/*
+ * e-menu-tool-action.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-menu-tool-action.h"
+
+G_DEFINE_TYPE (
+ EMenuToolAction,
+ e_menu_tool_action,
+ GTK_TYPE_ACTION)
+
+static void
+e_menu_tool_action_class_init (EMenuToolActionClass *class)
+{
+ GtkActionClass *action_class;
+
+ action_class = GTK_ACTION_CLASS (class);
+ action_class->toolbar_item_type = GTK_TYPE_MENU_TOOL_BUTTON;
+}
+
+static void
+e_menu_tool_action_init (EMenuToolAction *action)
+{
+}
+
+EMenuToolAction *
+e_menu_tool_action_new (const gchar *name,
+ const gchar *label,
+ const gchar *tooltip,
+ const gchar *stock_id)
+{
+ g_return_val_if_fail (name != NULL, NULL);
+
+ return g_object_new (
+ E_TYPE_MENU_TOOL_ACTION,
+ "name", name, "label", label, "tooltip",
+ tooltip, "stock-id", stock_id, NULL);
+}
diff --git a/e-util/e-menu-tool-action.h b/e-util/e-menu-tool-action.h
new file mode 100644
index 0000000000..aee47686e2
--- /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/e-util/e-menu-tool-button.c b/e-util/e-menu-tool-button.c
new file mode 100644
index 0000000000..c2a68d0c05
--- /dev/null
+++ b/e-util/e-menu-tool-button.c
@@ -0,0 +1,273 @@
+/*
+ * e-menu-tool-button.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-menu-tool-button.h"
+
+#define E_MENU_TOOL_BUTTON_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_MENU_TOOL_BUTTON, EMenuToolButtonPrivate))
+
+struct _EMenuToolButtonPrivate {
+ gchar *prefer_item;
+};
+
+enum {
+ PROP_0,
+ PROP_PREFER_ITEM
+};
+
+G_DEFINE_TYPE (
+ EMenuToolButton,
+ e_menu_tool_button,
+ GTK_TYPE_MENU_TOOL_BUTTON)
+
+static GtkWidget *
+menu_tool_button_clone_image (GtkWidget *source)
+{
+ GtkIconSize size;
+ GtkImageType image_type;
+ const gchar *icon_name;
+
+ /* XXX This isn't general purpose because it requires that the
+ * source image be using a named icon. Somewhat surprised
+ * GTK+ doesn't offer something like this. */
+ image_type = gtk_image_get_storage_type (GTK_IMAGE (source));
+ g_return_val_if_fail (image_type == GTK_IMAGE_ICON_NAME, NULL);
+ gtk_image_get_icon_name (GTK_IMAGE (source), &icon_name, &size);
+
+ return gtk_image_new_from_icon_name (icon_name, size);
+}
+
+static GtkMenuItem *
+menu_tool_button_get_prefer_menu_item (GtkMenuToolButton *menu_tool_button)
+{
+ GtkWidget *menu;
+ GtkMenuItem *item = NULL;
+ GList *children;
+ const gchar *prefer_item;
+
+ menu = gtk_menu_tool_button_get_menu (menu_tool_button);
+ if (!GTK_IS_MENU (menu))
+ return NULL;
+
+ children = gtk_container_get_children (GTK_CONTAINER (menu));
+ if (children == NULL)
+ return NULL;
+
+ prefer_item = e_menu_tool_button_get_prefer_item (
+ E_MENU_TOOL_BUTTON (menu_tool_button));
+ if (prefer_item != NULL && *prefer_item != '\0') {
+ GtkAction *action;
+ GList *iter;
+
+ for (iter = children; iter != NULL; iter = iter->next) {
+ item = GTK_MENU_ITEM (iter->data);
+
+ if (!item)
+ continue;
+
+ action = gtk_activatable_get_related_action (
+ GTK_ACTIVATABLE (item));
+ if (action && g_strcmp0 (gtk_action_get_name (action), prefer_item) == 0)
+ break;
+ else if (!action && g_strcmp0 (gtk_widget_get_name (GTK_WIDGET (item)), prefer_item) == 0)
+ break;
+
+ item = NULL;
+ }
+ }
+
+ if (!item)
+ item = GTK_MENU_ITEM (children->data);
+
+ g_list_free (children);
+
+ return item;
+}
+
+static void
+menu_tool_button_update_button (GtkToolButton *tool_button)
+{
+ GtkMenuItem *menu_item;
+ GtkMenuToolButton *menu_tool_button;
+ GtkImageMenuItem *image_menu_item;
+ GtkAction *action;
+ GtkWidget *image;
+ gchar *tooltip = NULL;
+
+ menu_tool_button = GTK_MENU_TOOL_BUTTON (tool_button);
+ menu_item = menu_tool_button_get_prefer_menu_item (menu_tool_button);
+ if (!GTK_IS_IMAGE_MENU_ITEM (menu_item))
+ return;
+
+ image_menu_item = GTK_IMAGE_MENU_ITEM (menu_item);
+ image = gtk_image_menu_item_get_image (image_menu_item);
+ if (!GTK_IS_IMAGE (image))
+ return;
+
+ image = menu_tool_button_clone_image (image);
+ gtk_tool_button_set_icon_widget (tool_button, image);
+ gtk_widget_show (image);
+
+ /* If the menu item is a proxy for a GtkAction, extract
+ * the action's tooltip and use it as our own tooltip. */
+ action = gtk_activatable_get_related_action (
+ GTK_ACTIVATABLE (menu_item));
+ if (action != NULL)
+ g_object_get (action, "tooltip", &tooltip, NULL);
+ gtk_widget_set_tooltip_text (GTK_WIDGET (tool_button), tooltip);
+ g_free (tooltip);
+}
+
+static void
+menu_tool_button_clicked (GtkToolButton *tool_button)
+{
+ GtkMenuItem *menu_item;
+ GtkMenuToolButton *menu_tool_button;
+
+ menu_tool_button = GTK_MENU_TOOL_BUTTON (tool_button);
+ menu_item = menu_tool_button_get_prefer_menu_item (menu_tool_button);
+
+ if (GTK_IS_MENU_ITEM (menu_item))
+ gtk_menu_item_activate (menu_item);
+}
+
+static void
+menu_tool_button_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_PREFER_ITEM:
+ e_menu_tool_button_set_prefer_item (
+ E_MENU_TOOL_BUTTON (object),
+ g_value_get_string (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+menu_tool_button_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_PREFER_ITEM:
+ g_value_set_string (
+ value, e_menu_tool_button_get_prefer_item (
+ E_MENU_TOOL_BUTTON (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+menu_tool_button_dispose (GObject *object)
+{
+ EMenuToolButtonPrivate *priv = E_MENU_TOOL_BUTTON (object)->priv;
+
+ if (priv->prefer_item) {
+ g_free (priv->prefer_item);
+ priv->prefer_item = NULL;
+ }
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (e_menu_tool_button_parent_class)->dispose (object);
+}
+
+static void
+e_menu_tool_button_class_init (EMenuToolButtonClass *class)
+{
+ GObjectClass *object_class;
+ GtkToolButtonClass *tool_button_class;
+
+ g_type_class_add_private (class, sizeof (EMenuToolButtonPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = menu_tool_button_set_property;
+ object_class->get_property = menu_tool_button_get_property;
+ object_class->dispose = menu_tool_button_dispose;
+
+ tool_button_class = GTK_TOOL_BUTTON_CLASS (class);
+ tool_button_class->clicked = menu_tool_button_clicked;
+
+ g_object_class_install_property (
+ object_class,
+ PROP_PREFER_ITEM,
+ g_param_spec_string (
+ "prefer-item",
+ "Prefer Item",
+ "Name of an item to show instead of the first",
+ NULL,
+ G_PARAM_READWRITE));
+}
+
+static void
+e_menu_tool_button_init (EMenuToolButton *button)
+{
+ button->priv = E_MENU_TOOL_BUTTON_GET_PRIVATE (button);
+
+ button->priv->prefer_item = NULL;
+
+ g_signal_connect (
+ button, "notify::menu",
+ G_CALLBACK (menu_tool_button_update_button), NULL);
+}
+
+GtkToolItem *
+e_menu_tool_button_new (const gchar *label)
+{
+ return g_object_new (E_TYPE_MENU_TOOL_BUTTON, "label", label, NULL);
+}
+
+void
+e_menu_tool_button_set_prefer_item (EMenuToolButton *button,
+ const gchar *prefer_item)
+{
+ g_return_if_fail (button != NULL);
+ g_return_if_fail (E_IS_MENU_TOOL_BUTTON (button));
+
+ if (g_strcmp0 (button->priv->prefer_item, prefer_item) == 0)
+ return;
+
+ g_free (button->priv->prefer_item);
+ button->priv->prefer_item = g_strdup (prefer_item);
+
+ g_object_notify (G_OBJECT (button), "prefer-item");
+}
+
+const gchar *
+e_menu_tool_button_get_prefer_item (EMenuToolButton *button)
+{
+ g_return_val_if_fail (button != NULL, NULL);
+ g_return_val_if_fail (E_IS_MENU_TOOL_BUTTON (button), NULL);
+
+ return button->priv->prefer_item;
+}
diff --git a/e-util/e-menu-tool-button.h b/e-util/e-menu-tool-button.h
new file mode 100644
index 0000000000..04519958d2
--- /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-util.c b/e-util/e-misc-utils.c
index 8d47da2186..7d1a0c6028 100644
--- a/e-util/e-util.c
+++ b/e-util/e-misc-utils.c
@@ -20,15 +20,12 @@
*
*/
-/**
- * SECTION: e-util
- * @include: e-util/e-util.h
- **/
-
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
+#include "e-misc-utils.h"
+
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
@@ -53,9 +50,7 @@
#include <camel/camel.h>
#include <libedataserver/libedataserver.h>
-#include "filter/e-filter-option.h"
-
-#include "e-util.h"
+#include "e-filter-option.h"
#include "e-util-private.h"
typedef struct _WindowData WindowData;
@@ -648,6 +643,73 @@ e_action_group_add_actions_localized (GtkActionGroup *action_group,
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,
@@ -719,6 +781,233 @@ e_categories_add_change_hook (GHookFunc func,
}
/**
+ * 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
*
@@ -1318,7 +1607,6 @@ e_util_guess_mime_type (const gchar *filename,
return mime_type;
}
-/* XXX: Should e-util/ really depend on filter/ ?? */
GSList *
e_util_get_category_filter_options (void)
{
diff --git a/e-util/e-misc-utils.h b/e-util/e-misc-utils.h
new file mode 100644
index 0000000000..0bd465e7d1
--- /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 9b68ccc473..f5042fa6a2 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 08f75ea67e..6c05541611 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 0000000000..ece556b0a9
--- /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 0000000000..fe10544bb5
--- /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 0000000000..ea7e2ef383
--- /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 0000000000..63ce9aa437
--- /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 0000000000..67afb504b3
--- /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 0000000000..7b1d11c0c6
--- /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 0000000000..770f51422f
--- /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 0000000000..ed37f2af30
--- /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 0000000000..a94b6ff5f3
--- /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 0000000000..1049699d63
--- /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/e-util/e-online-button.c b/e-util/e-online-button.c
new file mode 100644
index 0000000000..a3921a779f
--- /dev/null
+++ b/e-util/e-online-button.c
@@ -0,0 +1,210 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You 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-online-button.h"
+
+#include <glib/gi18n.h>
+
+#define E_ONLINE_BUTTON_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_ONLINE_BUTTON, EOnlineButtonPrivate))
+
+#define ONLINE_TOOLTIP \
+ _("Evolution is currently online. Click this button to work offline.")
+
+#define OFFLINE_TOOLTIP \
+ _("Evolution is currently offline. Click this button to work online.")
+
+#define NETWORK_UNAVAILABLE_TOOLTIP \
+ _("Evolution is currently offline because the network is unavailable.")
+
+struct _EOnlineButtonPrivate {
+ GtkWidget *image;
+ gboolean online;
+};
+
+enum {
+ PROP_0,
+ PROP_ONLINE
+};
+
+G_DEFINE_TYPE (
+ EOnlineButton,
+ e_online_button,
+ GTK_TYPE_BUTTON)
+
+static void
+online_button_update_tooltip (EOnlineButton *button)
+{
+ const gchar *tooltip;
+
+ if (e_online_button_get_online (button))
+ tooltip = ONLINE_TOOLTIP;
+ else if (gtk_widget_get_sensitive (GTK_WIDGET (button)))
+ tooltip = OFFLINE_TOOLTIP;
+ else
+ tooltip = NETWORK_UNAVAILABLE_TOOLTIP;
+
+ gtk_widget_set_tooltip_text (GTK_WIDGET (button), tooltip);
+}
+
+static void
+online_button_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_ONLINE:
+ e_online_button_set_online (
+ E_ONLINE_BUTTON (object),
+ g_value_get_boolean (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+online_button_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_ONLINE:
+ g_value_set_boolean (
+ value, e_online_button_get_online (
+ E_ONLINE_BUTTON (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+online_button_dispose (GObject *object)
+{
+ EOnlineButtonPrivate *priv;
+
+ priv = E_ONLINE_BUTTON_GET_PRIVATE (object);
+
+ if (priv->image != NULL) {
+ g_object_unref (priv->image);
+ priv->image = NULL;
+ }
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (e_online_button_parent_class)->dispose (object);
+}
+
+static void
+e_online_button_class_init (EOnlineButtonClass *class)
+{
+ GObjectClass *object_class;
+
+ g_type_class_add_private (class, sizeof (EOnlineButtonPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = online_button_set_property;
+ object_class->get_property = online_button_get_property;
+ object_class->dispose = online_button_dispose;
+
+ g_object_class_install_property (
+ object_class,
+ PROP_ONLINE,
+ g_param_spec_boolean (
+ "online",
+ "Online",
+ "The button state is online",
+ TRUE,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+}
+
+static void
+e_online_button_init (EOnlineButton *button)
+{
+ GtkWidget *widget;
+
+ button->priv = E_ONLINE_BUTTON_GET_PRIVATE (button);
+
+ gtk_widget_set_can_focus (GTK_WIDGET (button), FALSE);
+ gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
+
+ widget = gtk_image_new ();
+ gtk_container_add (GTK_CONTAINER (button), widget);
+ button->priv->image = g_object_ref (widget);
+ gtk_widget_show (widget);
+
+ g_signal_connect (
+ button, "notify::online",
+ G_CALLBACK (online_button_update_tooltip), NULL);
+
+ g_signal_connect (
+ button, "notify::sensitive",
+ G_CALLBACK (online_button_update_tooltip), NULL);
+}
+
+GtkWidget *
+e_online_button_new (void)
+{
+ return g_object_new (E_TYPE_ONLINE_BUTTON, NULL);
+}
+
+gboolean
+e_online_button_get_online (EOnlineButton *button)
+{
+ g_return_val_if_fail (E_IS_ONLINE_BUTTON (button), FALSE);
+
+ return button->priv->online;
+}
+
+void
+e_online_button_set_online (EOnlineButton *button,
+ gboolean online)
+{
+ GtkImage *image;
+ GtkIconInfo *icon_info;
+ GtkIconTheme *icon_theme;
+ const gchar *filename;
+ const gchar *icon_name;
+
+ g_return_if_fail (E_IS_ONLINE_BUTTON (button));
+
+ if (button->priv->online == online)
+ return;
+
+ button->priv->online = online;
+
+ image = GTK_IMAGE (button->priv->image);
+ icon_name = online ? "online" : "offline";
+ icon_theme = gtk_icon_theme_get_default ();
+
+ /* Prevent GTK+ from scaling these rectangular icons. */
+ icon_info = gtk_icon_theme_lookup_icon (
+ icon_theme, icon_name, GTK_ICON_SIZE_BUTTON, 0);
+ filename = gtk_icon_info_get_filename (icon_info);
+ gtk_image_set_from_file (image, filename);
+ gtk_icon_info_free (icon_info);
+
+ g_object_notify (G_OBJECT (button), "online");
+}
diff --git a/e-util/e-online-button.h b/e-util/e-online-button.h
new file mode 100644
index 0000000000..073489fef9
--- /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/e-util/e-paned.c b/e-util/e-paned.c
new file mode 100644
index 0000000000..3b8f16bbec
--- /dev/null
+++ b/e-util/e-paned.c
@@ -0,0 +1,503 @@
+/*
+ * e-paned.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-paned.h"
+
+#include <glib/gi18n-lib.h>
+
+#define E_PANED_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_PANED, EPanedPrivate))
+
+#define SYNC_REQUEST_NONE 0
+#define SYNC_REQUEST_POSITION 1
+#define SYNC_REQUEST_PROPORTION 2
+
+struct _EPanedPrivate {
+ gint hposition;
+ gint vposition;
+ gdouble proportion;
+
+ gulong wse_handler_id;
+
+ guint fixed_resize : 1;
+ guint sync_request : 2;
+ guint toplevel_ready : 1;
+};
+
+enum {
+ PROP_0,
+ PROP_HPOSITION,
+ PROP_VPOSITION,
+ PROP_PROPORTION,
+ PROP_FIXED_RESIZE
+};
+
+G_DEFINE_TYPE (
+ EPaned,
+ e_paned,
+ GTK_TYPE_PANED)
+
+static gboolean
+paned_queue_resize_on_idle (GtkWidget *paned)
+{
+ gtk_widget_queue_resize_no_redraw (paned);
+
+ return FALSE;
+}
+
+static gboolean
+paned_window_state_event_cb (EPaned *paned,
+ GdkEventWindowState *event,
+ GtkWidget *toplevel)
+{
+ /* Wait for WITHDRAWN to change from 1 to 0. */
+ if (!(event->changed_mask & GDK_WINDOW_STATE_WITHDRAWN))
+ return FALSE;
+
+ /* The whole point of this hack is to trap a point where if
+ * the window were to be maximized initially, the maximized
+ * allocation would already be negotiated. We're there now.
+ * Set a flag so we know it's safe to set GtkPaned position. */
+ paned->priv->toplevel_ready = TRUE;
+
+ if (paned->priv->sync_request != SYNC_REQUEST_NONE)
+ gtk_widget_queue_resize (GTK_WIDGET (paned));
+
+ /* We don't need to listen for window state events anymore. */
+ g_signal_handler_disconnect (toplevel, paned->priv->wse_handler_id);
+ paned->priv->wse_handler_id = 0;
+
+ return FALSE;
+}
+
+static void
+paned_notify_orientation_cb (EPaned *paned)
+{
+ /* Ignore the next "notify::position" emission. */
+ if (e_paned_get_fixed_resize (paned))
+ paned->priv->sync_request = SYNC_REQUEST_POSITION;
+ else
+ paned->priv->sync_request = SYNC_REQUEST_PROPORTION;
+ gtk_widget_queue_resize (GTK_WIDGET (paned));
+}
+
+static void
+paned_notify_position_cb (EPaned *paned)
+{
+ GtkAllocation allocation;
+ GtkOrientable *orientable;
+ GtkOrientation orientation;
+ gdouble proportion;
+ gint position;
+
+ /* If a sync has already been requested, do nothing. */
+ if (paned->priv->sync_request != SYNC_REQUEST_NONE)
+ return;
+
+ orientable = GTK_ORIENTABLE (paned);
+ orientation = gtk_orientable_get_orientation (orientable);
+
+ gtk_widget_get_allocation (GTK_WIDGET (paned), &allocation);
+ position = gtk_paned_get_position (GTK_PANED (paned));
+
+ g_object_freeze_notify (G_OBJECT (paned));
+
+ if (orientation == GTK_ORIENTATION_HORIZONTAL) {
+ position = MAX (0, allocation.width - position);
+ proportion = (gdouble) position / allocation.width;
+
+ paned->priv->hposition = position;
+ g_object_notify (G_OBJECT (paned), "hposition");
+ } else {
+ position = MAX (0, allocation.height - position);
+ proportion = (gdouble) position / allocation.height;
+
+ paned->priv->vposition = position;
+ g_object_notify (G_OBJECT (paned), "vposition");
+ }
+
+ paned->priv->proportion = proportion;
+ g_object_notify (G_OBJECT (paned), "proportion");
+
+ if (e_paned_get_fixed_resize (paned))
+ paned->priv->sync_request = SYNC_REQUEST_POSITION;
+ else
+ paned->priv->sync_request = SYNC_REQUEST_PROPORTION;
+
+ g_object_thaw_notify (G_OBJECT (paned));
+}
+
+static void
+paned_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_HPOSITION:
+ e_paned_set_hposition (
+ E_PANED (object),
+ g_value_get_int (value));
+ return;
+
+ case PROP_VPOSITION:
+ e_paned_set_vposition (
+ E_PANED (object),
+ g_value_get_int (value));
+ return;
+
+ case PROP_PROPORTION:
+ e_paned_set_proportion (
+ E_PANED (object),
+ g_value_get_double (value));
+ return;
+
+ case PROP_FIXED_RESIZE:
+ e_paned_set_fixed_resize (
+ E_PANED (object),
+ g_value_get_boolean (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+paned_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_HPOSITION:
+ g_value_set_int (
+ value, e_paned_get_hposition (
+ E_PANED (object)));
+ return;
+
+ case PROP_VPOSITION:
+ g_value_set_int (
+ value, e_paned_get_vposition (
+ E_PANED (object)));
+ return;
+
+ case PROP_PROPORTION:
+ g_value_set_double (
+ value, e_paned_get_proportion (
+ E_PANED (object)));
+ return;
+
+ case PROP_FIXED_RESIZE:
+ g_value_set_boolean (
+ value, e_paned_get_fixed_resize (
+ E_PANED (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+paned_realize (GtkWidget *widget)
+{
+ EPanedPrivate *priv;
+ GtkWidget *toplevel;
+ GdkWindowState state;
+ GdkWindow *window;
+
+ priv = E_PANED_GET_PRIVATE (widget);
+
+ /* Chain up to parent's realize() method. */
+ GTK_WIDGET_CLASS (e_paned_parent_class)->realize (widget);
+
+ /* XXX This would be easier if we could be notified of
+ * window state events directly, but I can't seem
+ * to make that happen. */
+
+ toplevel = gtk_widget_get_toplevel (widget);
+ window = gtk_widget_get_window (toplevel);
+ state = gdk_window_get_state (window);
+
+ /* If the window is withdrawn, wait for it to be shown before
+ * setting the pane position. If the window is already shown,
+ * it's safe to set the pane position immediately. */
+ if (state & GDK_WINDOW_STATE_WITHDRAWN)
+ priv->wse_handler_id = g_signal_connect_swapped (
+ toplevel, "window-state-event",
+ G_CALLBACK (paned_window_state_event_cb), widget);
+ else
+ priv->toplevel_ready = TRUE;
+}
+
+static void
+paned_size_allocate (GtkWidget *widget,
+ GtkAllocation *allocation)
+{
+ EPaned *paned = E_PANED (widget);
+ GtkOrientable *orientable;
+ GtkOrientation orientation;
+ gdouble proportion;
+ gint allocated;
+ gint position;
+
+ /* Chain up to parent's size_allocate() method. */
+ GTK_WIDGET_CLASS (e_paned_parent_class)->
+ size_allocate (widget, allocation);
+
+ if (!paned->priv->toplevel_ready)
+ return;
+
+ if (paned->priv->sync_request == SYNC_REQUEST_NONE)
+ return;
+
+ orientable = GTK_ORIENTABLE (paned);
+ orientation = gtk_orientable_get_orientation (orientable);
+
+ if (orientation == GTK_ORIENTATION_HORIZONTAL) {
+ allocated = allocation->width;
+ position = e_paned_get_hposition (paned);
+ } else {
+ allocated = allocation->height;
+ position = e_paned_get_vposition (paned);
+ }
+
+ proportion = e_paned_get_proportion (paned);
+
+ if (paned->priv->sync_request == SYNC_REQUEST_POSITION)
+ position = MAX (0, allocated - position);
+ else
+ position = (1.0 - proportion) * allocated;
+
+ gtk_paned_set_position (GTK_PANED (paned), position);
+
+ paned->priv->sync_request = SYNC_REQUEST_NONE;
+
+ /* gtk_paned_set_position() calls queue_resize, which cannot
+ * be called from size_allocate, so schedule it from an idle
+ * callback so the change takes effect. */
+ g_idle_add_full (
+ G_PRIORITY_DEFAULT_IDLE,
+ (GSourceFunc) paned_queue_resize_on_idle,
+ g_object_ref (paned),
+ (GDestroyNotify) g_object_unref);
+}
+
+static void
+e_paned_class_init (EPanedClass *class)
+{
+ GObjectClass *object_class;
+ GtkWidgetClass *widget_class;
+
+ g_type_class_add_private (class, sizeof (EPanedPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = paned_set_property;
+ object_class->get_property = paned_get_property;
+
+ widget_class = GTK_WIDGET_CLASS (class);
+ widget_class->realize = paned_realize;
+ widget_class->size_allocate = paned_size_allocate;
+
+ g_object_class_install_property (
+ object_class,
+ PROP_HPOSITION,
+ g_param_spec_int (
+ "hposition",
+ "Horizontal Position",
+ "Pane position when oriented horizontally",
+ G_MININT,
+ G_MAXINT,
+ 0,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_VPOSITION,
+ g_param_spec_int (
+ "vposition",
+ "Vertical Position",
+ "Pane position when oriented vertically",
+ G_MININT,
+ G_MAXINT,
+ 0,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_PROPORTION,
+ g_param_spec_double (
+ "proportion",
+ "Proportion",
+ "Proportion of the 2nd pane size",
+ 0.0,
+ 1.0,
+ 0.0,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_FIXED_RESIZE,
+ g_param_spec_boolean (
+ "fixed-resize",
+ "Fixed Resize",
+ "Keep the 2nd pane fixed during resize",
+ TRUE,
+ G_PARAM_READWRITE));
+}
+
+static void
+e_paned_init (EPaned *paned)
+{
+ paned->priv = E_PANED_GET_PRIVATE (paned);
+
+ paned->priv->proportion = 0.5;
+ paned->priv->fixed_resize = TRUE;
+
+ g_signal_connect (
+ paned, "notify::orientation",
+ G_CALLBACK (paned_notify_orientation_cb), NULL);
+
+ g_signal_connect (
+ paned, "notify::position",
+ G_CALLBACK (paned_notify_position_cb), NULL);
+}
+
+GtkWidget *
+e_paned_new (GtkOrientation orientation)
+{
+ return g_object_new (E_TYPE_PANED, "orientation", orientation, NULL);
+}
+
+gint
+e_paned_get_hposition (EPaned *paned)
+{
+ g_return_val_if_fail (E_IS_PANED (paned), 0);
+
+ return paned->priv->hposition;
+}
+
+void
+e_paned_set_hposition (EPaned *paned,
+ gint hposition)
+{
+ GtkOrientable *orientable;
+ GtkOrientation orientation;
+
+ g_return_if_fail (E_IS_PANED (paned));
+
+ if (hposition == paned->priv->hposition)
+ return;
+
+ paned->priv->hposition = hposition;
+
+ g_object_notify (G_OBJECT (paned), "hposition");
+
+ orientable = GTK_ORIENTABLE (paned);
+ orientation = gtk_orientable_get_orientation (orientable);
+
+ if (orientation == GTK_ORIENTATION_HORIZONTAL) {
+ paned->priv->sync_request = SYNC_REQUEST_POSITION;
+ gtk_widget_queue_resize (GTK_WIDGET (paned));
+ }
+}
+
+gint
+e_paned_get_vposition (EPaned *paned)
+{
+ g_return_val_if_fail (E_IS_PANED (paned), 0);
+
+ return paned->priv->vposition;
+}
+
+void
+e_paned_set_vposition (EPaned *paned,
+ gint vposition)
+{
+ GtkOrientable *orientable;
+ GtkOrientation orientation;
+
+ g_return_if_fail (E_IS_PANED (paned));
+
+ if (vposition == paned->priv->vposition)
+ return;
+
+ paned->priv->vposition = vposition;
+
+ g_object_notify (G_OBJECT (paned), "vposition");
+
+ orientable = GTK_ORIENTABLE (paned);
+ orientation = gtk_orientable_get_orientation (orientable);
+
+ if (orientation == GTK_ORIENTATION_VERTICAL) {
+ paned->priv->sync_request = SYNC_REQUEST_POSITION;
+ gtk_widget_queue_resize (GTK_WIDGET (paned));
+ }
+}
+
+gdouble
+e_paned_get_proportion (EPaned *paned)
+{
+ g_return_val_if_fail (E_IS_PANED (paned), 0.5);
+
+ return paned->priv->proportion;
+}
+
+void
+e_paned_set_proportion (EPaned *paned,
+ gdouble proportion)
+{
+ g_return_if_fail (E_IS_PANED (paned));
+ g_return_if_fail (CLAMP (proportion, 0.0, 1.0) == proportion);
+
+ paned->priv->proportion = proportion;
+
+ paned->priv->sync_request = SYNC_REQUEST_PROPORTION;
+ gtk_widget_queue_resize (GTK_WIDGET (paned));
+
+ g_object_notify (G_OBJECT (paned), "proportion");
+}
+
+gboolean
+e_paned_get_fixed_resize (EPaned *paned)
+{
+ g_return_val_if_fail (E_IS_PANED (paned), FALSE);
+
+ return paned->priv->fixed_resize;
+}
+
+void
+e_paned_set_fixed_resize (EPaned *paned,
+ gboolean fixed_resize)
+{
+ g_return_if_fail (E_IS_PANED (paned));
+
+ if (fixed_resize == paned->priv->fixed_resize)
+ return;
+
+ paned->priv->fixed_resize = fixed_resize;
+
+ g_object_notify (G_OBJECT (paned), "fixed-resize");
+}
diff --git a/e-util/e-paned.h b/e-util/e-paned.h
new file mode 100644
index 0000000000..79fa3ddfa4
--- /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 0000000000..51c0cb21bb
--- /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 0000000000..bf4cfc1e7f
--- /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 0000000000..83a4a7eaec
--- /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 0000000000..d95a0c907c
--- /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 0000000000..653d9906af
--- /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 6e36654061..3ef863c2a3 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 e59b5f5222..f56a6e095c 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 047d944193..b67bde548c 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 e3cfb31007..f1b4654127 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/e-util/e-popup-action.c b/e-util/e-popup-action.c
new file mode 100644
index 0000000000..27c90f67c3
--- /dev/null
+++ b/e-util/e-popup-action.c
@@ -0,0 +1,408 @@
+/*
+ * e-popup-action.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-popup-action.h"
+
+#include <glib/gi18n.h>
+
+#define E_POPUP_ACTION_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_POPUP_ACTION, EPopupActionPrivate))
+
+enum {
+ PROP_0,
+ PROP_RELATED_ACTION,
+ PROP_USE_ACTION_APPEARANCE
+};
+
+struct _EPopupActionPrivate {
+ GtkAction *related_action;
+ gboolean use_action_appearance;
+ gulong activate_handler_id;
+ gulong notify_handler_id;
+};
+
+/* Forward Declarations */
+static void e_popup_action_activatable_init (GtkActivatableIface *interface);
+
+G_DEFINE_TYPE_WITH_CODE (
+ EPopupAction,
+ e_popup_action,
+ GTK_TYPE_ACTION,
+ G_IMPLEMENT_INTERFACE (
+ GTK_TYPE_ACTIVATABLE,
+ e_popup_action_activatable_init))
+
+static void
+popup_action_notify_cb (GtkAction *action,
+ GParamSpec *pspec,
+ GtkActivatable *activatable)
+{
+ GtkActivatableIface *iface;
+
+ iface = GTK_ACTIVATABLE_GET_IFACE (activatable);
+ g_return_if_fail (iface->update != NULL);
+
+ iface->update (activatable, action, pspec->name);
+}
+
+static GtkAction *
+popup_action_get_related_action (EPopupAction *popup_action)
+{
+ return popup_action->priv->related_action;
+}
+
+static void
+popup_action_set_related_action (EPopupAction *popup_action,
+ GtkAction *related_action)
+{
+ GtkActivatable *activatable;
+
+ /* Do not call gtk_activatable_do_set_related_action() because
+ * it assumes the activatable object is a widget and tries to add
+ * it to the related actions's proxy list. Instead we'll just do
+ * the relevant steps manually. */
+
+ activatable = GTK_ACTIVATABLE (popup_action);
+
+ if (related_action == popup_action->priv->related_action)
+ return;
+
+ if (related_action != NULL)
+ g_object_ref (related_action);
+
+ if (popup_action->priv->related_action != NULL) {
+ g_signal_handler_disconnect (
+ popup_action,
+ popup_action->priv->activate_handler_id);
+ g_signal_handler_disconnect (
+ popup_action->priv->related_action,
+ popup_action->priv->notify_handler_id);
+ popup_action->priv->activate_handler_id = 0;
+ popup_action->priv->notify_handler_id = 0;
+ g_object_unref (popup_action->priv->related_action);
+ }
+
+ popup_action->priv->related_action = related_action;
+
+ if (related_action != NULL) {
+ popup_action->priv->activate_handler_id =
+ g_signal_connect_swapped (
+ popup_action, "activate",
+ G_CALLBACK (gtk_action_activate),
+ related_action);
+ popup_action->priv->notify_handler_id =
+ g_signal_connect (
+ related_action, "notify",
+ G_CALLBACK (popup_action_notify_cb),
+ popup_action);
+ gtk_activatable_sync_action_properties (
+ activatable, related_action);
+ } else
+ gtk_action_set_visible (GTK_ACTION (popup_action), FALSE);
+
+ g_object_notify (G_OBJECT (popup_action), "related-action");
+}
+
+static gboolean
+popup_action_get_use_action_appearance (EPopupAction *popup_action)
+{
+ return popup_action->priv->use_action_appearance;
+}
+
+static void
+popup_action_set_use_action_appearance (EPopupAction *popup_action,
+ gboolean use_action_appearance)
+{
+ GtkActivatable *activatable;
+ GtkAction *related_action;
+
+ if (popup_action->priv->use_action_appearance == use_action_appearance)
+ return;
+
+ popup_action->priv->use_action_appearance = use_action_appearance;
+
+ g_object_notify (G_OBJECT (popup_action), "use-action-appearance");
+
+ activatable = GTK_ACTIVATABLE (popup_action);
+ related_action = popup_action_get_related_action (popup_action);
+ gtk_activatable_sync_action_properties (activatable, related_action);
+}
+
+static void
+popup_action_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_RELATED_ACTION:
+ popup_action_set_related_action (
+ E_POPUP_ACTION (object),
+ g_value_get_object (value));
+ return;
+
+ case PROP_USE_ACTION_APPEARANCE:
+ popup_action_set_use_action_appearance (
+ E_POPUP_ACTION (object),
+ g_value_get_boolean (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+popup_action_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_RELATED_ACTION:
+ g_value_set_object (
+ value,
+ popup_action_get_related_action (
+ E_POPUP_ACTION (object)));
+ return;
+
+ case PROP_USE_ACTION_APPEARANCE:
+ g_value_set_boolean (
+ value,
+ popup_action_get_use_action_appearance (
+ E_POPUP_ACTION (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+popup_action_dispose (GObject *object)
+{
+ EPopupActionPrivate *priv;
+
+ priv = E_POPUP_ACTION_GET_PRIVATE (object);
+
+ if (priv->related_action != NULL) {
+ g_signal_handler_disconnect (
+ object,
+ priv->activate_handler_id);
+ g_signal_handler_disconnect (
+ priv->related_action,
+ priv->notify_handler_id);
+ g_object_unref (priv->related_action);
+ priv->related_action = NULL;
+ }
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (e_popup_action_parent_class)->dispose (object);
+}
+
+static void
+popup_action_update (GtkActivatable *activatable,
+ GtkAction *action,
+ const gchar *property_name)
+{
+ GObjectClass *class;
+ GParamSpec *pspec;
+ GValue *value;
+
+ /* Ignore "action-group" changes" */
+ if (strcmp (property_name, "action-group") == 0)
+ return;
+
+ /* Ignore "visible" changes. */
+ if (strcmp (property_name, "visible") == 0)
+ return;
+
+ value = g_slice_new0 (GValue);
+ class = G_OBJECT_GET_CLASS (action);
+ pspec = g_object_class_find_property (class, property_name);
+ g_value_init (value, pspec->value_type);
+
+ g_object_get_property (G_OBJECT (action), property_name, value);
+
+ if (strcmp (property_name, "sensitive") == 0)
+ property_name = "visible";
+ else if (!gtk_activatable_get_use_action_appearance (activatable))
+ goto exit;
+
+ g_object_set_property (G_OBJECT (activatable), property_name, value);
+
+exit:
+ g_value_unset (value);
+ g_slice_free (GValue, value);
+}
+
+static void
+popup_action_sync_action_properties (GtkActivatable *activatable,
+ GtkAction *action)
+{
+ if (action == NULL)
+ return;
+
+ /* XXX GTK+ 2.18 is still missing accessor functions for
+ * "hide-if-empty" and "visible-overflown" properties.
+ * These are rarely used so we'll skip them for now. */
+
+ /* A popup action is never shown as insensitive. */
+ gtk_action_set_sensitive (GTK_ACTION (activatable), TRUE);
+
+ gtk_action_set_visible (
+ GTK_ACTION (activatable),
+ gtk_action_get_sensitive (action));
+
+ gtk_action_set_visible_horizontal (
+ GTK_ACTION (activatable),
+ gtk_action_get_visible_horizontal (action));
+
+ gtk_action_set_visible_vertical (
+ GTK_ACTION (activatable),
+ gtk_action_get_visible_vertical (action));
+
+ gtk_action_set_is_important (
+ GTK_ACTION (activatable),
+ gtk_action_get_is_important (action));
+
+ if (!gtk_activatable_get_use_action_appearance (activatable))
+ return;
+
+ gtk_action_set_label (
+ GTK_ACTION (activatable),
+ gtk_action_get_label (action));
+
+ gtk_action_set_short_label (
+ GTK_ACTION (activatable),
+ gtk_action_get_short_label (action));
+
+ gtk_action_set_tooltip (
+ GTK_ACTION (activatable),
+ gtk_action_get_tooltip (action));
+
+ gtk_action_set_stock_id (
+ GTK_ACTION (activatable),
+ gtk_action_get_stock_id (action));
+
+ gtk_action_set_gicon (
+ GTK_ACTION (activatable),
+ gtk_action_get_gicon (action));
+
+ gtk_action_set_icon_name (
+ GTK_ACTION (activatable),
+ gtk_action_get_icon_name (action));
+}
+
+static void
+e_popup_action_class_init (EPopupActionClass *class)
+{
+ GObjectClass *object_class;
+
+ g_type_class_add_private (class, sizeof (EPopupActionPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = popup_action_set_property;
+ object_class->get_property = popup_action_get_property;
+ object_class->dispose = popup_action_dispose;
+
+ g_object_class_override_property (
+ object_class,
+ PROP_RELATED_ACTION,
+ "related-action");
+
+ g_object_class_override_property (
+ object_class,
+ PROP_USE_ACTION_APPEARANCE,
+ "use-action-appearance");
+}
+
+static void
+e_popup_action_init (EPopupAction *popup_action)
+{
+ popup_action->priv = E_POPUP_ACTION_GET_PRIVATE (popup_action);
+ popup_action->priv->use_action_appearance = TRUE;
+
+ /* Remain invisible until we have a related action. */
+ gtk_action_set_visible (GTK_ACTION (popup_action), FALSE);
+}
+
+static void
+e_popup_action_activatable_init (GtkActivatableIface *interface)
+{
+ interface->update = popup_action_update;
+ interface->sync_action_properties = popup_action_sync_action_properties;
+}
+
+EPopupAction *
+e_popup_action_new (const gchar *name)
+{
+ g_return_val_if_fail (name != NULL, NULL);
+
+ return g_object_new (E_TYPE_POPUP_ACTION, "name", name, NULL);
+}
+
+void
+e_action_group_add_popup_actions (GtkActionGroup *action_group,
+ const EPopupActionEntry *entries,
+ guint n_entries)
+{
+ guint ii;
+
+ g_return_if_fail (GTK_IS_ACTION_GROUP (action_group));
+
+ for (ii = 0; ii < n_entries; ii++) {
+ EPopupAction *popup_action;
+ GtkAction *related_action;
+ const gchar *label;
+
+ label = gtk_action_group_translate_string (
+ action_group, entries[ii].label);
+
+ related_action = gtk_action_group_get_action (
+ action_group, entries[ii].related);
+
+ if (related_action == NULL) {
+ g_warning (
+ "Related action '%s' not found in "
+ "action group '%s'", entries[ii].related,
+ gtk_action_group_get_name (action_group));
+ continue;
+ }
+
+ popup_action = e_popup_action_new (entries[ii].name);
+
+ gtk_activatable_set_related_action (
+ GTK_ACTIVATABLE (popup_action), related_action);
+
+ if (label != NULL && *label != '\0')
+ gtk_action_set_label (
+ GTK_ACTION (popup_action), label);
+
+ gtk_action_group_add_action (
+ action_group, GTK_ACTION (popup_action));
+
+ g_object_unref (popup_action);
+ }
+}
diff --git a/e-util/e-popup-action.h b/e-util/e-popup-action.h
new file mode 100644
index 0000000000..62e7b8ec0f
--- /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/e-util/e-popup-menu.c b/e-util/e-popup-menu.c
new file mode 100644
index 0000000000..3e82496748
--- /dev/null
+++ b/e-util/e-popup-menu.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:
+ * 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)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <libintl.h>
+#include <string.h>
+
+#include <gdk/gdkkeysyms.h>
+#include <gtk/gtk.h>
+
+#include "e-popup-menu.h"
+
+/*
+ * Creates an item with an optional icon
+ */
+static void
+make_item (GtkMenu *menu,
+ GtkMenuItem *item,
+ const gchar *name)
+{
+ GtkWidget *label;
+
+ if (*name == '\0')
+ return;
+
+ /*
+ * Ugh. This needs to go into Gtk+
+ */
+ label = gtk_label_new_with_mnemonic (name);
+ gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+ gtk_widget_show (label);
+
+ gtk_container_add (GTK_CONTAINER (item), label);
+}
+
+GtkMenu *
+e_popup_menu_create_with_domain (EPopupMenu *menu_list,
+ guint32 disable_mask,
+ guint32 hide_mask,
+ gpointer default_closure,
+ const gchar *domain)
+{
+ GtkMenu *menu = GTK_MENU (gtk_menu_new ());
+ gboolean last_item_separator = TRUE;
+ gint last_non_separator = -1;
+ gint i;
+
+ for (i = 0; menu_list[i].name; i++) {
+ if (strcmp ("", menu_list[i].name) && !(menu_list[i].disable_mask & hide_mask)) {
+ last_non_separator = i;
+ }
+ }
+
+ for (i = 0; i <= last_non_separator; i++) {
+ gboolean separator;
+
+ separator = !strcmp ("", menu_list[i].name);
+
+ if ((!(separator && last_item_separator)) && !(menu_list[i].disable_mask & hide_mask)) {
+ GtkWidget *item = NULL;
+
+ if (!separator) {
+ item = gtk_menu_item_new ();
+
+ make_item (menu, GTK_MENU_ITEM (item), dgettext (domain, menu_list[i].name));
+ } else {
+ item = gtk_menu_item_new ();
+ }
+
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
+
+ if (menu_list[i].fn)
+ g_signal_connect (
+ item, "activate",
+ G_CALLBACK (menu_list[i].fn),
+ default_closure);
+
+ if (menu_list[i].disable_mask & disable_mask)
+ gtk_widget_set_sensitive (item, FALSE);
+
+ gtk_widget_show (item);
+
+ last_item_separator = separator;
+ }
+ }
+
+ return menu;
+}
diff --git a/e-util/e-popup-menu.h b/e-util/e-popup-menu.h
new file mode 100644
index 0000000000..ec0979c576
--- /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/e-util/e-port-entry.c b/e-util/e-port-entry.c
new file mode 100644
index 0000000000..cf857b5d55
--- /dev/null
+++ b/e-util/e-port-entry.c
@@ -0,0 +1,549 @@
+/*
+ * 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:
+ * Dan Vratil <dvratil@redhat.com>
+ */
+
+#include "e-port-entry.h"
+
+#include <config.h>
+#include <errno.h>
+#include <stddef.h>
+#include <string.h>
+
+#define E_PORT_ENTRY_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_PORT_ENTRY, EPortEntryPrivate))
+
+struct _EPortEntryPrivate {
+ CamelNetworkSecurityMethod method;
+ CamelProviderPortEntry *entries;
+};
+
+enum {
+ PORT_NUM_COLUMN,
+ PORT_DESC_COLUMN,
+ PORT_IS_SSL_COLUMN
+};
+
+enum {
+ PROP_0,
+ PROP_IS_VALID,
+ PROP_PORT,
+ PROP_SECURITY_METHOD
+};
+
+G_DEFINE_TYPE (
+ EPortEntry,
+ e_port_entry,
+ GTK_TYPE_COMBO_BOX)
+
+static GtkEntry *
+port_entry_get_entry (EPortEntry *port_entry)
+{
+ return GTK_ENTRY (gtk_bin_get_child (GTK_BIN (port_entry)));
+}
+
+static gboolean
+port_entry_get_numeric_port (EPortEntry *port_entry,
+ gint *out_port)
+{
+ GtkEntry *entry;
+ const gchar *port_string;
+ gboolean valid;
+ gint port;
+
+ entry = port_entry_get_entry (port_entry);
+
+ port_string = gtk_entry_get_text (entry);
+ g_return_val_if_fail (port_string != NULL, FALSE);
+
+ errno = 0;
+ port = strtol (port_string, NULL, 10);
+ valid = (errno == 0) && (port == CLAMP (port, 1, G_MAXUINT16));
+
+ if (valid && out_port != NULL)
+ *out_port = port;
+
+ return valid;
+}
+
+static void
+port_entry_text_changed (GtkEditable *editable,
+ EPortEntry *port_entry)
+{
+ GObject *object = G_OBJECT (port_entry);
+ const gchar *desc = NULL;
+ gint port = 0;
+ gint ii = 0;
+
+ g_object_freeze_notify (object);
+
+ port_entry_get_numeric_port (port_entry, &port);
+
+ if (port_entry->priv->entries != NULL) {
+ while (port_entry->priv->entries[ii].port > 0) {
+ if (port == port_entry->priv->entries[ii].port) {
+ desc = port_entry->priv->entries[ii].desc;
+ break;
+ }
+ ii++;
+ }
+ }
+
+ if (desc != NULL)
+ gtk_widget_set_tooltip_text (GTK_WIDGET (port_entry), desc);
+ else
+ gtk_widget_set_has_tooltip (GTK_WIDGET (port_entry), FALSE);
+
+ g_object_notify (object, "port");
+ g_object_notify (object, "is-valid");
+
+ g_object_thaw_notify (object);
+}
+
+static void
+port_entry_method_changed (EPortEntry *port_entry)
+{
+ CamelNetworkSecurityMethod method;
+ gboolean standard_port = FALSE;
+ gboolean valid, have_ssl = FALSE, have_nossl = FALSE;
+ gint port = 0;
+ gint ii;
+
+ method = e_port_entry_get_security_method (port_entry);
+ valid = port_entry_get_numeric_port (port_entry, &port);
+
+ /* Only change the port number if it's currently on a standard
+ * port (i.e. listed in a CamelProviderPortEntry). Otherwise,
+ * leave custom port numbers alone. */
+
+ if (valid && port_entry->priv->entries != NULL) {
+ for (ii = 0; port_entry->priv->entries[ii].port > 0 && (!have_ssl || !have_nossl); ii++) {
+ /* Use only the first SSL/no-SSL port as a default in the list
+ * and skip the others */
+ if (port_entry->priv->entries[ii].is_ssl) {
+ if (have_ssl)
+ continue;
+ have_ssl = TRUE;
+ } else {
+ if (have_nossl)
+ continue;
+ have_nossl = TRUE;
+ }
+
+ if (port == port_entry->priv->entries[ii].port) {
+ standard_port = TRUE;
+ break;
+ }
+ }
+ }
+
+ if (valid && !standard_port)
+ return;
+
+ switch (method) {
+ case CAMEL_NETWORK_SECURITY_METHOD_SSL_ON_ALTERNATE_PORT:
+ e_port_entry_activate_secured_port (port_entry, 0);
+ break;
+ default:
+ e_port_entry_activate_nonsecured_port (port_entry, 0);
+ break;
+ }
+}
+
+static void
+port_entry_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_PORT:
+ e_port_entry_set_port (
+ E_PORT_ENTRY (object),
+ g_value_get_uint (value));
+ return;
+
+ case PROP_SECURITY_METHOD:
+ e_port_entry_set_security_method (
+ E_PORT_ENTRY (object),
+ g_value_get_enum (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+port_entry_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_IS_VALID:
+ g_value_set_boolean (
+ value, e_port_entry_is_valid (
+ E_PORT_ENTRY (object)));
+ return;
+
+ case PROP_PORT:
+ g_value_set_uint (
+ value, e_port_entry_get_port (
+ E_PORT_ENTRY (object)));
+ return;
+
+ case PROP_SECURITY_METHOD:
+ g_value_set_enum (
+ value, e_port_entry_get_security_method (
+ E_PORT_ENTRY (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+port_entry_constructed (GObject *object)
+{
+ GtkEntry *entry;
+
+ /* Chain up to parent's constructed() method. */
+ G_OBJECT_CLASS (e_port_entry_parent_class)->constructed (object);
+
+ entry = port_entry_get_entry (E_PORT_ENTRY (object));
+
+ g_signal_connect_after (
+ entry, "changed",
+ G_CALLBACK (port_entry_text_changed), object);
+}
+
+static void
+port_entry_get_preferred_width (GtkWidget *widget,
+ gint *minimum_size,
+ gint *natural_size)
+{
+ PangoContext *context;
+ PangoFontMetrics *metrics;
+ PangoFontDescription *font_desc;
+ GtkStyleContext *style_context;
+ GtkStateFlags state;
+ gint digit_width;
+ gint parent_entry_width_min;
+ gint parent_width_min;
+ GtkWidget *entry;
+
+ style_context = gtk_widget_get_style_context (widget);
+ state = gtk_widget_get_state_flags (widget);
+ gtk_style_context_get (
+ style_context, state, "font", &font_desc, NULL);
+ context = gtk_widget_get_pango_context (GTK_WIDGET (widget));
+ metrics = pango_context_get_metrics (
+ context, font_desc, pango_context_get_language (context));
+
+ digit_width = PANGO_PIXELS (
+ pango_font_metrics_get_approximate_digit_width (metrics));
+
+ /* Preferred width of the entry */
+ entry = gtk_bin_get_child (GTK_BIN (widget));
+ gtk_widget_get_preferred_width (entry, NULL, &parent_entry_width_min);
+
+ /* Preferred width of a standard combobox */
+ GTK_WIDGET_CLASS (e_port_entry_parent_class)->
+ get_preferred_width (widget, &parent_width_min, NULL);
+
+ /* 6 * digit_width - port number has max 5
+ * digits + extra free space for better look */
+ if (minimum_size != NULL)
+ *minimum_size =
+ parent_width_min - parent_entry_width_min +
+ 6 * digit_width;
+
+ if (natural_size != NULL)
+ *natural_size =
+ parent_width_min - parent_entry_width_min +
+ 6 * digit_width;
+
+ pango_font_metrics_unref (metrics);
+ pango_font_description_free (font_desc);
+}
+
+static void
+e_port_entry_class_init (EPortEntryClass *class)
+{
+ GObjectClass *object_class;
+ GtkWidgetClass *widget_class;
+
+ g_type_class_add_private (class, sizeof (EPortEntryPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = port_entry_set_property;
+ object_class->get_property = port_entry_get_property;
+ object_class->constructed = port_entry_constructed;
+
+ widget_class = GTK_WIDGET_CLASS (class);
+ widget_class->get_preferred_width = port_entry_get_preferred_width;
+
+ g_object_class_install_property (
+ object_class,
+ PROP_IS_VALID,
+ g_param_spec_boolean (
+ "is-valid",
+ NULL,
+ NULL,
+ FALSE,
+ G_PARAM_READABLE |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_PORT,
+ g_param_spec_uint (
+ "port",
+ NULL,
+ NULL,
+ 0, /* Min port, 0 = invalid port */
+ G_MAXUINT16, /* Max port */
+ 0,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_SECURITY_METHOD,
+ g_param_spec_enum (
+ "security-method",
+ "Security Method",
+ "Method used to establish a network connection",
+ CAMEL_TYPE_NETWORK_SECURITY_METHOD,
+ CAMEL_NETWORK_SECURITY_METHOD_NONE,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+}
+
+static void
+e_port_entry_init (EPortEntry *port_entry)
+{
+ GtkCellRenderer *renderer;
+ GtkListStore *store;
+
+ port_entry->priv = E_PORT_ENTRY_GET_PRIVATE (port_entry);
+
+ store = gtk_list_store_new (
+ 3, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN);
+
+ gtk_combo_box_set_model (
+ GTK_COMBO_BOX (port_entry), GTK_TREE_MODEL (store));
+ gtk_combo_box_set_entry_text_column (
+ GTK_COMBO_BOX (port_entry), PORT_NUM_COLUMN);
+ gtk_combo_box_set_id_column (
+ GTK_COMBO_BOX (port_entry), PORT_NUM_COLUMN);
+
+ renderer = gtk_cell_renderer_text_new ();
+ gtk_cell_renderer_set_sensitive (renderer, TRUE);
+ gtk_cell_layout_pack_start (
+ GTK_CELL_LAYOUT (port_entry), renderer, FALSE);
+ gtk_cell_layout_add_attribute (
+ GTK_CELL_LAYOUT (port_entry),
+ renderer, "text", PORT_NUM_COLUMN);
+
+ renderer = gtk_cell_renderer_text_new ();
+ gtk_cell_renderer_set_sensitive (renderer, FALSE);
+ gtk_cell_layout_pack_start (
+ GTK_CELL_LAYOUT (port_entry), renderer, TRUE);
+ gtk_cell_layout_add_attribute (
+ GTK_CELL_LAYOUT (port_entry),
+ renderer, "text", PORT_DESC_COLUMN);
+}
+
+GtkWidget *
+e_port_entry_new (void)
+{
+ return g_object_new (
+ E_TYPE_PORT_ENTRY, "has-entry", TRUE, NULL);
+}
+
+void
+e_port_entry_set_camel_entries (EPortEntry *port_entry,
+ CamelProviderPortEntry *entries)
+{
+ GtkComboBox *combo_box;
+ GtkTreeIter iter;
+ GtkTreeModel *model;
+ GtkListStore *store;
+ gint port = 0;
+ gint i = 0;
+
+ g_return_if_fail (E_IS_PORT_ENTRY (port_entry));
+ g_return_if_fail (entries);
+
+ port_entry->priv->entries = entries;
+
+ combo_box = GTK_COMBO_BOX (port_entry);
+ model = gtk_combo_box_get_model (combo_box);
+
+ store = GTK_LIST_STORE (model);
+ gtk_list_store_clear (store);
+
+ while (entries[i].port > 0) {
+ gchar *port_string;
+
+ /* Grab the first port number. */
+ if (port == 0)
+ port = entries[i].port;
+
+ port_string = g_strdup_printf ("%i", entries[i].port);
+
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (
+ store, &iter,
+ PORT_NUM_COLUMN, port_string,
+ PORT_DESC_COLUMN, entries[i].desc,
+ PORT_IS_SSL_COLUMN, entries[i].is_ssl,
+ -1);
+ i++;
+
+ g_free (port_string);
+ }
+
+ e_port_entry_set_port (port_entry, port);
+}
+
+gint
+e_port_entry_get_port (EPortEntry *port_entry)
+{
+ gint port = 0;
+
+ g_return_val_if_fail (E_IS_PORT_ENTRY (port_entry), 0);
+
+ port_entry_get_numeric_port (port_entry, &port);
+
+ return port;
+}
+
+void
+e_port_entry_set_port (EPortEntry *port_entry,
+ gint port)
+{
+ GtkEntry *entry;
+ gchar *port_string;
+
+ g_return_if_fail (E_IS_PORT_ENTRY (port_entry));
+
+ entry = port_entry_get_entry (port_entry);
+ port_string = g_strdup_printf ("%i", port);
+ gtk_entry_set_text (entry, port_string);
+ g_free (port_string);
+}
+
+gboolean
+e_port_entry_is_valid (EPortEntry *port_entry)
+{
+ g_return_val_if_fail (E_IS_PORT_ENTRY (port_entry), FALSE);
+
+ return port_entry_get_numeric_port (port_entry, NULL);
+}
+
+CamelNetworkSecurityMethod
+e_port_entry_get_security_method (EPortEntry *port_entry)
+{
+ g_return_val_if_fail (
+ E_IS_PORT_ENTRY (port_entry),
+ CAMEL_NETWORK_SECURITY_METHOD_NONE);
+
+ return port_entry->priv->method;
+}
+
+void
+e_port_entry_set_security_method (EPortEntry *port_entry,
+ CamelNetworkSecurityMethod method)
+{
+ g_return_if_fail (E_IS_PORT_ENTRY (port_entry));
+
+ port_entry->priv->method = method;
+
+ port_entry_method_changed (port_entry);
+
+ g_object_notify (G_OBJECT (port_entry), "security-method");
+}
+
+/**
+ * If there are more then one secured port in the model, you can specify
+ * which of the secured ports should be activated by specifying the index.
+ * The index counts only for secured ports, so if you have 5 ports of which
+ * ports 1, 3 and 5 are secured, the association is 0=>1, 1=>3, 2=>5
+ */
+void
+e_port_entry_activate_secured_port (EPortEntry *port_entry,
+ gint index)
+{
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ gboolean is_ssl;
+ gint iters = 0;
+
+ g_return_if_fail (E_IS_PORT_ENTRY (port_entry));
+
+ model = gtk_combo_box_get_model (GTK_COMBO_BOX (port_entry));
+
+ if (!gtk_tree_model_get_iter_first (model, &iter))
+ return;
+
+ do {
+ gtk_tree_model_get (
+ model, &iter, PORT_IS_SSL_COLUMN, &is_ssl, -1);
+ if (is_ssl && (iters == index)) {
+ gtk_combo_box_set_active_iter (
+ GTK_COMBO_BOX (port_entry), &iter);
+ return;
+ }
+
+ if (is_ssl)
+ iters++;
+
+ } while (gtk_tree_model_iter_next (model, &iter));
+}
+
+/**
+ * If there are more then one unsecured port in the model, you can specify
+ * which of the unsecured ports should be activated by specifiying the index.
+ * The index counts only for unsecured ports, so if you have 5 ports, of which
+ * ports 2 and 4 are unsecured, the associtation is 0=>2, 1=>4
+ */
+void
+e_port_entry_activate_nonsecured_port (EPortEntry *port_entry,
+ gint index)
+{
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ gboolean is_ssl;
+ gint iters = 0;
+
+ g_return_if_fail (E_IS_PORT_ENTRY (port_entry));
+
+ model = gtk_combo_box_get_model (GTK_COMBO_BOX (port_entry));
+
+ if (!gtk_tree_model_get_iter_first (model, &iter))
+ return;
+
+ do {
+ gtk_tree_model_get (model, &iter, PORT_IS_SSL_COLUMN, &is_ssl, -1);
+ if (!is_ssl && (iters == index)) {
+ gtk_combo_box_set_active_iter (
+ GTK_COMBO_BOX (port_entry), &iter);
+ return;
+ }
+
+ if (!is_ssl)
+ iters++;
+
+ } while (gtk_tree_model_iter_next (model, &iter));
+}
diff --git a/e-util/e-port-entry.h b/e-util/e-port-entry.h
new file mode 100644
index 0000000000..fc0eaa0d90
--- /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 0000000000..fbe6c01d21
--- /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 0000000000..f2efa015e6
--- /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 0000000000..27009ab087
--- /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 0000000000..3720744b6c
--- /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 e50ffd8647..bc2f4d3608 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 9469b63636..ee96e25d2e 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 0000000000..3f1403f03c
--- /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 0000000000..292756076d
--- /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 0000000000..4f5b219b90
--- /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 0000000000..4a5f710084
--- /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 0000000000..0df0aad5f8
--- /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 0000000000..a891e98f38
--- /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 0000000000..dc7ce8160d
--- /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 0000000000..f543edd187
--- /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 0000000000..c063ae41ae
--- /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 0000000000..d983b81c27
--- /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/e-util/e-search-bar.c b/e-util/e-search-bar.c
new file mode 100644
index 0000000000..9ed0c2d1c9
--- /dev/null
+++ b/e-util/e-search-bar.c
@@ -0,0 +1,771 @@
+/*
+ * e-search-bar.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-search-bar.h"
+
+#include <glib/gi18n.h>
+#include <gdk/gdkkeysyms.h>
+
+#define E_SEARCH_BAR_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_SEARCH_BAR, ESearchBarPrivate))
+
+struct _ESearchBarPrivate {
+ EWebView *web_view;
+ GtkWidget *entry;
+ GtkWidget *case_sensitive_button;
+ GtkWidget *wrapped_next_box;
+ GtkWidget *wrapped_prev_box;
+ GtkWidget *matches_label;
+
+ gchar *active_search;
+
+ guint rerun_search : 1;
+};
+
+enum {
+ PROP_0,
+ PROP_ACTIVE_SEARCH,
+ PROP_CASE_SENSITIVE,
+ PROP_TEXT,
+ PROP_WEB_VIEW
+};
+
+enum {
+ CHANGED,
+ CLEAR,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+G_DEFINE_TYPE (
+ ESearchBar,
+ e_search_bar,
+ GTK_TYPE_HBOX)
+
+static void
+search_bar_update_matches (ESearchBar *search_bar,
+ guint matches)
+{
+ GtkWidget *matches_label;
+ gchar *text;
+
+ search_bar->priv->rerun_search = FALSE;
+ matches_label = search_bar->priv->matches_label;
+
+ text = g_strdup_printf (_("Matches: %u"), matches);
+ gtk_label_set_text (GTK_LABEL (matches_label), text);
+ gtk_widget_show (matches_label);
+ g_free (text);
+}
+
+static void
+search_bar_update_highlights (ESearchBar *search_bar)
+{
+ EWebView *web_view;
+ gboolean visible;
+
+ web_view = e_search_bar_get_web_view (search_bar);
+
+ visible = gtk_widget_get_visible (GTK_WIDGET (search_bar));
+
+ webkit_web_view_set_highlight_text_matches (
+ WEBKIT_WEB_VIEW (web_view), visible);
+
+ e_search_bar_changed (search_bar);
+}
+
+static void
+search_bar_find (ESearchBar *search_bar,
+ gboolean search_forward)
+{
+ EWebView *web_view;
+ GtkWidget *widget;
+ gboolean case_sensitive;
+ gboolean new_search;
+ gboolean wrapped = FALSE;
+ gboolean success;
+ gchar *text;
+
+ web_view = e_search_bar_get_web_view (search_bar);
+ case_sensitive = e_search_bar_get_case_sensitive (search_bar);
+ text = e_search_bar_get_text (search_bar);
+
+ if (text == NULL || *text == '\0') {
+ e_search_bar_clear (search_bar);
+ g_free (text);
+ return;
+ }
+
+ new_search =
+ (search_bar->priv->active_search == NULL) ||
+ (g_strcmp0 (text, search_bar->priv->active_search) != 0);
+
+ if (new_search) {
+ guint matches;
+
+ webkit_web_view_unmark_text_matches (
+ WEBKIT_WEB_VIEW (web_view));
+ matches = webkit_web_view_mark_text_matches (
+ WEBKIT_WEB_VIEW (web_view),
+ text, case_sensitive, 0);
+ webkit_web_view_set_highlight_text_matches (
+ WEBKIT_WEB_VIEW (web_view), TRUE);
+ search_bar_update_matches (search_bar, matches);
+ }
+
+ success = webkit_web_view_search_text (
+ WEBKIT_WEB_VIEW (web_view),
+ text, case_sensitive, search_forward, FALSE);
+
+ if (!success)
+ wrapped = webkit_web_view_search_text (
+ WEBKIT_WEB_VIEW (web_view),
+ text, case_sensitive, search_forward, TRUE);
+
+ g_free (search_bar->priv->active_search);
+ search_bar->priv->active_search = text;
+
+ g_object_notify (G_OBJECT (search_bar), "active-search");
+
+ /* Update wrapped label visibility. */
+
+ widget = search_bar->priv->wrapped_next_box;
+
+ if (wrapped && search_forward)
+ gtk_widget_show (widget);
+ else
+ gtk_widget_hide (widget);
+
+ widget = search_bar->priv->wrapped_prev_box;
+
+ if (wrapped && !search_forward)
+ gtk_widget_show (widget);
+ else
+ gtk_widget_hide (widget);
+}
+
+static void
+search_bar_changed_cb (ESearchBar *search_bar)
+{
+ g_object_notify (G_OBJECT (search_bar), "text");
+}
+
+static void
+search_bar_find_next_cb (ESearchBar *search_bar)
+{
+ search_bar_find (search_bar, TRUE);
+}
+
+static void
+search_bar_find_previous_cb (ESearchBar *search_bar)
+{
+ search_bar_find (search_bar, FALSE);
+}
+
+static void
+search_bar_icon_release_cb (ESearchBar *search_bar,
+ GtkEntryIconPosition icon_pos,
+ GdkEvent *event)
+{
+ g_return_if_fail (icon_pos == GTK_ENTRY_ICON_SECONDARY);
+
+ e_search_bar_clear (search_bar);
+ gtk_widget_grab_focus (search_bar->priv->entry);
+}
+
+static void
+search_bar_toggled_cb (ESearchBar *search_bar)
+{
+ g_free (search_bar->priv->active_search);
+ search_bar->priv->active_search = NULL;
+
+ g_object_notify (G_OBJECT (search_bar), "active-search");
+ g_object_notify (G_OBJECT (search_bar), "case-sensitive");
+}
+
+static void
+search_bar_set_web_view (ESearchBar *search_bar,
+ EWebView *web_view)
+{
+ g_return_if_fail (E_IS_WEB_VIEW (web_view));
+ g_return_if_fail (search_bar->priv->web_view == NULL);
+
+ search_bar->priv->web_view = g_object_ref (web_view);
+}
+
+static void
+search_bar_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_CASE_SENSITIVE:
+ e_search_bar_set_case_sensitive (
+ E_SEARCH_BAR (object),
+ g_value_get_boolean (value));
+ return;
+
+ case PROP_TEXT:
+ e_search_bar_set_text (
+ E_SEARCH_BAR (object),
+ g_value_get_string (value));
+ return;
+
+ case PROP_WEB_VIEW:
+ search_bar_set_web_view (
+ E_SEARCH_BAR (object),
+ g_value_get_object (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+search_bar_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_ACTIVE_SEARCH:
+ g_value_set_boolean (
+ value, e_search_bar_get_active_search (
+ E_SEARCH_BAR (object)));
+ return;
+
+ case PROP_CASE_SENSITIVE:
+ g_value_set_boolean (
+ value, e_search_bar_get_case_sensitive (
+ E_SEARCH_BAR (object)));
+ return;
+
+ case PROP_TEXT:
+ g_value_take_string (
+ value, e_search_bar_get_text (
+ E_SEARCH_BAR (object)));
+ return;
+
+ case PROP_WEB_VIEW:
+ g_value_set_object (
+ value, e_search_bar_get_web_view (
+ E_SEARCH_BAR (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+search_bar_dispose (GObject *object)
+{
+ ESearchBarPrivate *priv;
+
+ priv = E_SEARCH_BAR_GET_PRIVATE (object);
+
+ if (priv->web_view != NULL) {
+ g_object_unref (priv->web_view);
+ priv->web_view = NULL;
+ }
+
+ if (priv->entry != NULL) {
+ g_object_unref (priv->entry);
+ priv->entry = NULL;
+ }
+
+ if (priv->case_sensitive_button != NULL) {
+ g_object_unref (priv->case_sensitive_button);
+ priv->case_sensitive_button = NULL;
+ }
+
+ if (priv->wrapped_next_box != NULL) {
+ g_object_unref (priv->wrapped_next_box);
+ priv->wrapped_next_box = NULL;
+ }
+
+ if (priv->wrapped_prev_box != NULL) {
+ g_object_unref (priv->wrapped_prev_box);
+ priv->wrapped_prev_box = NULL;
+ }
+
+ if (priv->matches_label != NULL) {
+ g_object_unref (priv->matches_label);
+ priv->matches_label = NULL;
+ }
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (e_search_bar_parent_class)->dispose (object);
+}
+
+static void
+search_bar_finalize (GObject *object)
+{
+ ESearchBarPrivate *priv;
+
+ priv = E_SEARCH_BAR_GET_PRIVATE (object);
+
+ g_free (priv->active_search);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (e_search_bar_parent_class)->finalize (object);
+}
+
+static void
+search_bar_constructed (GObject *object)
+{
+ ESearchBarPrivate *priv;
+
+ priv = E_SEARCH_BAR_GET_PRIVATE (object);
+
+ g_object_bind_property (
+ object, "case-sensitive",
+ priv->case_sensitive_button, "active",
+ G_BINDING_BIDIRECTIONAL |
+ G_BINDING_SYNC_CREATE);
+
+ /* Chain up to parent's constructed() method. */
+ G_OBJECT_CLASS (e_search_bar_parent_class)->constructed (object);
+}
+
+static void
+search_bar_show (GtkWidget *widget)
+{
+ ESearchBar *search_bar;
+
+ search_bar = E_SEARCH_BAR (widget);
+
+ /* Chain up to parent's show() method. */
+ GTK_WIDGET_CLASS (e_search_bar_parent_class)->show (widget);
+
+ gtk_widget_grab_focus (search_bar->priv->entry);
+
+ search_bar_update_highlights (search_bar);
+}
+
+static void
+search_bar_hide (GtkWidget *widget)
+{
+ ESearchBar *search_bar;
+
+ search_bar = E_SEARCH_BAR (widget);
+
+ /* Chain up to parent's hide() method. */
+ GTK_WIDGET_CLASS (e_search_bar_parent_class)->hide (widget);
+
+ search_bar_update_highlights (search_bar);
+}
+
+static gboolean
+search_bar_key_press_event (GtkWidget *widget,
+ GdkEventKey *event)
+{
+ GtkWidgetClass *widget_class;
+
+ if (event->keyval == GDK_KEY_Escape) {
+ gtk_widget_hide (widget);
+ return TRUE;
+ }
+
+ /* Chain up to parent's key_press_event() method. */
+ widget_class = GTK_WIDGET_CLASS (e_search_bar_parent_class);
+ return widget_class->key_press_event (widget, event);
+}
+
+static void
+search_bar_clear (ESearchBar *search_bar)
+{
+ WebKitWebView *web_view;
+
+ g_free (search_bar->priv->active_search);
+ search_bar->priv->active_search = NULL;
+
+ gtk_entry_set_text (GTK_ENTRY (search_bar->priv->entry), "");
+
+ gtk_widget_hide (search_bar->priv->wrapped_next_box);
+ gtk_widget_hide (search_bar->priv->wrapped_prev_box);
+ gtk_widget_hide (search_bar->priv->matches_label);
+
+ search_bar_update_highlights (search_bar);
+
+ web_view = WEBKIT_WEB_VIEW (search_bar->priv->web_view);
+ webkit_web_view_unmark_text_matches (web_view);
+
+ g_object_notify (G_OBJECT (search_bar), "active-search");
+}
+
+static void
+e_search_bar_class_init (ESearchBarClass *class)
+{
+ GObjectClass *object_class;
+ GtkWidgetClass *widget_class;
+
+ g_type_class_add_private (class, sizeof (ESearchBarPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = search_bar_set_property;
+ object_class->get_property = search_bar_get_property;
+ object_class->dispose = search_bar_dispose;
+ object_class->finalize = search_bar_finalize;
+ object_class->constructed = search_bar_constructed;
+
+ widget_class = GTK_WIDGET_CLASS (class);
+ widget_class->show = search_bar_show;
+ widget_class->hide = search_bar_hide;
+ widget_class->key_press_event = search_bar_key_press_event;
+
+ class->clear = search_bar_clear;
+
+ g_object_class_install_property (
+ object_class,
+ PROP_ACTIVE_SEARCH,
+ g_param_spec_boolean (
+ "active-search",
+ "Active Search",
+ NULL,
+ FALSE,
+ G_PARAM_READABLE));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_CASE_SENSITIVE,
+ g_param_spec_boolean (
+ "case-sensitive",
+ "Case Sensitive",
+ NULL,
+ FALSE,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_TEXT,
+ g_param_spec_string (
+ "text",
+ "Search Text",
+ NULL,
+ NULL,
+ G_PARAM_READWRITE));
+
+ 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[CHANGED] = g_signal_new (
+ "changed",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (ESearchBarClass, changed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ signals[CLEAR] = g_signal_new (
+ "clear",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (ESearchBarClass, clear),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+}
+
+static void
+e_search_bar_init (ESearchBar *search_bar)
+{
+ GtkWidget *label;
+ GtkWidget *widget;
+ GtkWidget *container;
+
+ search_bar->priv = E_SEARCH_BAR_GET_PRIVATE (search_bar);
+
+ gtk_box_set_spacing (GTK_BOX (search_bar), 12);
+
+ container = GTK_WIDGET (search_bar);
+
+ widget = gtk_hbox_new (FALSE, 1);
+ gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+ gtk_widget_show (widget);
+
+ container = widget;
+
+ widget = gtk_button_new ();
+ gtk_button_set_image (
+ GTK_BUTTON (widget), gtk_image_new_from_stock (
+ GTK_STOCK_CLOSE, GTK_ICON_SIZE_MENU));
+ gtk_button_set_relief (GTK_BUTTON (widget), GTK_RELIEF_NONE);
+ gtk_widget_set_tooltip_text (widget, _("Close the find bar"));
+ gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+ gtk_widget_show (widget);
+
+ g_signal_connect_swapped (
+ widget, "clicked",
+ G_CALLBACK (gtk_widget_hide), search_bar);
+
+ widget = gtk_label_new_with_mnemonic (_("Fin_d:"));
+ gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 3);
+ gtk_widget_show (widget);
+
+ label = widget;
+
+ widget = gtk_entry_new ();
+ gtk_entry_set_icon_from_stock (
+ GTK_ENTRY (widget), GTK_ENTRY_ICON_SECONDARY,
+ GTK_STOCK_CLEAR);
+ gtk_entry_set_icon_tooltip_text (
+ GTK_ENTRY (widget), GTK_ENTRY_ICON_SECONDARY,
+ _("Clear the search"));
+ gtk_label_set_mnemonic_widget (GTK_LABEL (label), widget);
+ gtk_widget_set_size_request (widget, 200, -1);
+ gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+ search_bar->priv->entry = g_object_ref (widget);
+ gtk_widget_show (widget);
+
+ g_object_bind_property (
+ search_bar, "active-search",
+ widget, "secondary-icon-sensitive",
+ G_BINDING_SYNC_CREATE);
+
+ g_signal_connect_swapped (
+ widget, "activate",
+ G_CALLBACK (search_bar_find_next_cb), search_bar);
+
+ g_signal_connect_swapped (
+ widget, "changed",
+ G_CALLBACK (search_bar_changed_cb), search_bar);
+
+ g_signal_connect_swapped (
+ widget, "icon-release",
+ G_CALLBACK (search_bar_icon_release_cb), search_bar);
+
+ widget = gtk_button_new_with_mnemonic (_("_Previous"));
+ gtk_button_set_image (
+ GTK_BUTTON (widget), gtk_image_new_from_stock (
+ GTK_STOCK_GO_BACK, GTK_ICON_SIZE_MENU));
+ gtk_button_set_relief (GTK_BUTTON (widget), GTK_RELIEF_NONE);
+ gtk_widget_set_tooltip_text (
+ widget, _("Find the previous occurrence of the phrase"));
+ gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+ gtk_widget_show (widget);
+
+ g_object_bind_property (
+ search_bar, "active-search",
+ widget, "sensitive",
+ G_BINDING_SYNC_CREATE);
+
+ g_signal_connect_swapped (
+ widget, "clicked",
+ G_CALLBACK (search_bar_find_previous_cb), search_bar);
+
+ widget = gtk_button_new_with_mnemonic (_("_Next"));
+ gtk_button_set_image (
+ GTK_BUTTON (widget), gtk_image_new_from_stock (
+ GTK_STOCK_GO_FORWARD, GTK_ICON_SIZE_MENU));
+ gtk_button_set_relief (GTK_BUTTON (widget), GTK_RELIEF_NONE);
+ gtk_widget_set_tooltip_text (
+ widget, _("Find the next occurrence of the phrase"));
+ gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+ gtk_widget_show (widget);
+
+ g_object_bind_property (
+ search_bar, "active-search",
+ widget, "sensitive",
+ G_BINDING_SYNC_CREATE);
+
+ g_signal_connect_swapped (
+ widget, "clicked",
+ G_CALLBACK (search_bar_find_next_cb), search_bar);
+
+ widget = gtk_check_button_new_with_mnemonic (_("Mat_ch case"));
+ gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+ search_bar->priv->case_sensitive_button = g_object_ref (widget);
+ gtk_widget_show (widget);
+
+ g_signal_connect_swapped (
+ widget, "toggled",
+ G_CALLBACK (search_bar_toggled_cb), search_bar);
+
+ g_signal_connect_swapped (
+ widget, "toggled",
+ G_CALLBACK (search_bar_find_next_cb), search_bar);
+
+ container = GTK_WIDGET (search_bar);
+
+ widget = gtk_hbox_new (FALSE, 6);
+ gtk_box_pack_start (GTK_BOX (container), widget, TRUE, TRUE, 0);
+ search_bar->priv->wrapped_next_box = g_object_ref (widget);
+ gtk_widget_hide (widget);
+
+ container = widget;
+
+ widget = gtk_image_new_from_icon_name (
+ "wrapped", GTK_ICON_SIZE_MENU);
+ gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+ gtk_widget_show (widget);
+
+ widget = gtk_label_new (
+ _("Reached bottom of page, continued from top"));
+ 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);
+ gtk_widget_show (widget);
+
+ container = GTK_WIDGET (search_bar);
+
+ widget = gtk_hbox_new (FALSE, 6);
+ gtk_box_pack_start (GTK_BOX (container), widget, TRUE, TRUE, 0);
+ search_bar->priv->wrapped_prev_box = g_object_ref (widget);
+ gtk_widget_hide (widget);
+
+ container = widget;
+
+ widget = gtk_image_new_from_icon_name (
+ "wrapped", GTK_ICON_SIZE_MENU);
+ gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+ gtk_widget_show (widget);
+
+ widget = gtk_label_new (
+ _("Reached top of page, continued from bottom"));
+ 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);
+ gtk_widget_show (widget);
+
+ container = GTK_WIDGET (search_bar);
+
+ widget = gtk_label_new (NULL);
+ gtk_box_pack_end (GTK_BOX (container), widget, FALSE, FALSE, 12);
+ search_bar->priv->matches_label = g_object_ref (widget);
+ gtk_widget_show (widget);
+}
+
+GtkWidget *
+e_search_bar_new (EWebView *web_view)
+{
+ g_return_val_if_fail (E_IS_WEB_VIEW (web_view), NULL);
+
+ return g_object_new (
+ E_TYPE_SEARCH_BAR, "web-view", web_view, NULL);
+}
+
+void
+e_search_bar_clear (ESearchBar *search_bar)
+{
+ g_return_if_fail (E_IS_SEARCH_BAR (search_bar));
+
+ g_signal_emit (search_bar, signals[CLEAR], 0);
+}
+
+void
+e_search_bar_changed (ESearchBar *search_bar)
+{
+ g_return_if_fail (E_IS_SEARCH_BAR (search_bar));
+
+ g_signal_emit (search_bar, signals[CHANGED], 0);
+}
+
+EWebView *
+e_search_bar_get_web_view (ESearchBar *search_bar)
+{
+ g_return_val_if_fail (E_IS_SEARCH_BAR (search_bar), NULL);
+
+ return search_bar->priv->web_view;
+}
+
+gboolean
+e_search_bar_get_active_search (ESearchBar *search_bar)
+{
+ g_return_val_if_fail (E_IS_SEARCH_BAR (search_bar), FALSE);
+
+ return (search_bar->priv->active_search != NULL);
+}
+
+gboolean
+e_search_bar_get_case_sensitive (ESearchBar *search_bar)
+{
+ GtkToggleButton *button;
+
+ g_return_val_if_fail (E_IS_SEARCH_BAR (search_bar), FALSE);
+
+ button = GTK_TOGGLE_BUTTON (search_bar->priv->case_sensitive_button);
+
+ return gtk_toggle_button_get_active (button);
+}
+
+void
+e_search_bar_set_case_sensitive (ESearchBar *search_bar,
+ gboolean case_sensitive)
+{
+ GtkToggleButton *button;
+
+ g_return_if_fail (E_IS_SEARCH_BAR (search_bar));
+
+ button = GTK_TOGGLE_BUTTON (search_bar->priv->case_sensitive_button);
+
+ gtk_toggle_button_set_active (button, case_sensitive);
+
+ g_object_notify (G_OBJECT (search_bar), "case-sensitive");
+}
+
+gchar *
+e_search_bar_get_text (ESearchBar *search_bar)
+{
+ GtkEntry *entry;
+ const gchar *text;
+
+ g_return_val_if_fail (E_IS_SEARCH_BAR (search_bar), NULL);
+
+ entry = GTK_ENTRY (search_bar->priv->entry);
+ text = gtk_entry_get_text (entry);
+
+ return g_strstrip (g_strdup (text));
+}
+
+void
+e_search_bar_set_text (ESearchBar *search_bar,
+ const gchar *text)
+{
+ GtkEntry *entry;
+
+ g_return_if_fail (E_IS_SEARCH_BAR (search_bar));
+
+ entry = GTK_ENTRY (search_bar->priv->entry);
+
+ if (text == NULL)
+ text = "";
+
+ /* This will trigger a "notify::text" signal. */
+ gtk_entry_set_text (entry, text);
+}
diff --git a/e-util/e-search-bar.h b/e-util/e-search-bar.h
new file mode 100644
index 0000000000..43e16453bd
--- /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/e-util/e-selectable.c b/e-util/e-selectable.c
new file mode 100644
index 0000000000..b8e4337fef
--- /dev/null
+++ b/e-util/e-selectable.c
@@ -0,0 +1,168 @@
+/*
+ * e-selectable.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-selectable.h"
+
+G_DEFINE_INTERFACE (
+ ESelectable,
+ e_selectable,
+ GTK_TYPE_WIDGET)
+
+static void
+e_selectable_default_init (ESelectableInterface *interface)
+{
+ g_object_interface_install_property (
+ interface,
+ g_param_spec_boxed (
+ "copy-target-list",
+ "Copy Target List",
+ NULL,
+ GTK_TYPE_TARGET_LIST,
+ G_PARAM_READABLE));
+
+ g_object_interface_install_property (
+ interface,
+ g_param_spec_boxed (
+ "paste-target-list",
+ "Paste Target List",
+ NULL,
+ GTK_TYPE_TARGET_LIST,
+ G_PARAM_READABLE));
+}
+
+void
+e_selectable_update_actions (ESelectable *selectable,
+ EFocusTracker *focus_tracker,
+ GdkAtom *clipboard_targets,
+ gint n_clipboard_targets)
+{
+ ESelectableInterface *interface;
+
+ g_return_if_fail (E_IS_SELECTABLE (selectable));
+
+ interface = E_SELECTABLE_GET_INTERFACE (selectable);
+ g_return_if_fail (interface->update_actions != NULL);
+
+ interface->update_actions (
+ selectable, focus_tracker,
+ clipboard_targets, n_clipboard_targets);
+}
+
+void
+e_selectable_cut_clipboard (ESelectable *selectable)
+{
+ ESelectableInterface *interface;
+
+ g_return_if_fail (E_IS_SELECTABLE (selectable));
+
+ interface = E_SELECTABLE_GET_INTERFACE (selectable);
+
+ if (interface->cut_clipboard != NULL)
+ interface->cut_clipboard (selectable);
+}
+
+void
+e_selectable_copy_clipboard (ESelectable *selectable)
+{
+ ESelectableInterface *interface;
+
+ g_return_if_fail (E_IS_SELECTABLE (selectable));
+
+ interface = E_SELECTABLE_GET_INTERFACE (selectable);
+
+ if (interface->copy_clipboard != NULL)
+ interface->copy_clipboard (selectable);
+}
+
+void
+e_selectable_paste_clipboard (ESelectable *selectable)
+{
+ ESelectableInterface *interface;
+
+ g_return_if_fail (E_IS_SELECTABLE (selectable));
+
+ interface = E_SELECTABLE_GET_INTERFACE (selectable);
+
+ if (interface->paste_clipboard != NULL)
+ interface->paste_clipboard (selectable);
+}
+
+void
+e_selectable_delete_selection (ESelectable *selectable)
+{
+ ESelectableInterface *interface;
+
+ g_return_if_fail (E_IS_SELECTABLE (selectable));
+
+ interface = E_SELECTABLE_GET_INTERFACE (selectable);
+
+ if (interface->delete_selection != NULL)
+ interface->delete_selection (selectable);
+}
+
+void
+e_selectable_select_all (ESelectable *selectable)
+{
+ ESelectableInterface *interface;
+
+ g_return_if_fail (E_IS_SELECTABLE (selectable));
+
+ interface = E_SELECTABLE_GET_INTERFACE (selectable);
+
+ if (interface->select_all != NULL)
+ interface->select_all (selectable);
+}
+
+GtkTargetList *
+e_selectable_get_copy_target_list (ESelectable *selectable)
+{
+ GtkTargetList *target_list;
+
+ g_return_val_if_fail (E_IS_SELECTABLE (selectable), NULL);
+
+ g_object_get (selectable, "copy-target-list", &target_list, NULL);
+
+ /* We want to return a borrowed reference to the target
+ * list, so undo the reference that g_object_get() added. */
+ gtk_target_list_unref (target_list);
+
+ return target_list;
+}
+
+GtkTargetList *
+e_selectable_get_paste_target_list (ESelectable *selectable)
+{
+ GtkTargetList *target_list;
+
+ g_return_val_if_fail (E_IS_SELECTABLE (selectable), NULL);
+
+ g_object_get (selectable, "paste-target-list", &target_list, NULL);
+
+ /* We want to return a borrowed reference to the target
+ * list, so undo the reference that g_object_get() added. */
+ gtk_target_list_unref (target_list);
+
+ return target_list;
+}
diff --git a/e-util/e-selectable.h b/e-util/e-selectable.h
new file mode 100644
index 0000000000..4e7faa8581
--- /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 0000000000..fe73857a8a
--- /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 0000000000..7292a3365e
--- /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 0000000000..f7123dd09e
--- /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 0000000000..b4551dd51f
--- /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 0000000000..4c553f485a
--- /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 0000000000..1d59e28fe1
--- /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 041b30c4f2..4092b07cbe 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 5d44cd4839..75089c4bc1 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 0000000000..bf50dbefc0
--- /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 0000000000..2cd8336f3c
--- /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/e-util/e-send-options.ui b/e-util/e-send-options.ui
new file mode 100644
index 0000000000..681e6e0693
--- /dev/null
+++ b/e-util/e-send-options.ui
@@ -0,0 +1,949 @@
+<?xml version="1.0"?>
+<interface>
+ <object class="GtkAdjustment" id="adjustment1">
+ <property name="upper">100</property>
+ <property name="lower">0</property>
+ <property name="page_increment">10</property>
+ <property name="step_increment">1</property>
+ <property name="page_size">0</property>
+ <property name="value">5</property>
+ </object>
+ <object class="GtkAdjustment" id="adjustment2">
+ <property name="upper">100</property>
+ <property name="lower">0</property>
+ <property name="page_increment">10</property>
+ <property name="step_increment">1</property>
+ <property name="page_size">0</property>
+ <property name="value">2</property>
+ </object>
+ <object class="GtkListStore" id="model1">
+ <columns>
+ <column type="gchararray"/>
+ </columns>
+ <data>
+ <row>
+ <col id="0" translatable="yes">Undefined</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">High</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Standard</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Low</col>
+ </row>
+ </data>
+ </object>
+ <object class="GtkListStore" id="model2">
+ <columns>
+ <column type="gchararray"/>
+ </columns>
+ <data>
+ <row>
+ <col id="0" translatable="yes">Normal</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Proprietary</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Confidential</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Secret</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Top Secret</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">For Your Eyes Only</col>
+ </row>
+ </data>
+ </object>
+ <object class="GtkListStore" id="model3">
+ <columns>
+ <column type="gchararray"/>
+ </columns>
+ <data>
+ <row>
+ <col id="0" translatable="yes" context="send-options" comments="Translators: Used in send options dialog">None</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Mail Receipt</col>
+ </row>
+ </data>
+ </object>
+ <object class="GtkListStore" id="model4">
+ <columns>
+ <column type="gchararray"/>
+ </columns>
+ <data>
+ <row>
+ <col id="0" translatable="yes" context="send-options">None</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Mail Receipt</col>
+ </row>
+ </data>
+ </object>
+ <object class="GtkListStore" id="model5">
+ <columns>
+ <column type="gchararray"/>
+ </columns>
+ <data>
+ <row>
+ <col id="0" translatable="yes" context="send-options">None</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Mail Receipt</col>
+ </row>
+ </data>
+ </object>
+ <object class="GtkListStore" id="model6">
+ <columns>
+ <column type="gchararray"/>
+ </columns>
+ <data>
+ <row>
+ <col id="0" translatable="yes" context="send-options">None</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Mail Receipt</col>
+ </row>
+ </data>
+ </object>
+ <!-- interface-requires gtk+ 2.16 -->
+ <!-- interface-naming-policy toplevel-contextual -->
+ <object class="GtkDialog" id="send-options-dialog">
+ <property name="visible">True</property>
+ <property name="can_default">True</property>
+ <property name="title" translatable="yes">Send Options</property>
+ <property name="resizable">False</property>
+ <property name="modal">True</property>
+ <property name="window_position">mouse</property>
+ <property name="type_hint">dialog</property>
+ <child internal-child="vbox">
+ <object class="GtkVBox" id="vbox">
+ <property name="visible">True</property>
+ <property name="can_default">True</property>
+ <child>
+ <object class="GtkNotebook" id="notebook">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <child>
+ <object class="GtkTable" id="general-options">
+ <property name="visible">True</property>
+ <property name="border_width">12</property>
+ <property name="n_rows">4</property>
+ <property name="n_columns">4</property>
+ <property name="column_spacing">12</property>
+ <property name="row_spacing">12</property>
+ <child>
+ <object class="GtkLabel" id="label92">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="right_attach">4</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame" id="frame16">
+ <property name="visible">True</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkTable" id="table32">
+ <property name="visible">True</property>
+ <property name="border_width">12</property>
+ <property name="n_rows">2</property>
+ <property name="n_columns">3</property>
+ <property name="column_spacing">12</property>
+ <property name="row_spacing">12</property>
+ <child>
+ <object class="GtkCheckButton" id="reply-request-button">
+ <property name="label" translatable="yes">R_eply requested</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="right_attach">3</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAlignment" id="alignment38">
+ <property name="visible">True</property>
+ <property name="xscale">0</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkTable" id="table33">
+ <property name="visible">True</property>
+ <property name="n_rows">2</property>
+ <property name="n_columns">3</property>
+ <property name="column_spacing">12</property>
+ <property name="row_spacing">12</property>
+ <child>
+ <object class="GtkRadioButton" id="reply-within">
+ <property name="label" translatable="yes" comments="Translators: This is part of 'Within [ X ] days', where [ X ] is a spinner with a number" context="ESendOptionsWithin">Wi_thin</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="within-days">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="adjustment">adjustment1</property>
+ <property name="climb_rate">1</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label93">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes" comments="Translators: This is part of 'Within [ X ] days', where [ X ] is a spinner with a number" context="ESendOptionsWithin">days</property>
+ </object>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="right_attach">3</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="reply-convinient">
+ <property name="label" translatable="yes">_When convenient</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ <property name="group">reply-within</property>
+ </object>
+ <packing>
+ <property name="right_attach">3</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="right_attach">3</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options"/>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label94">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Replies</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="right_attach">4</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame" id="frame17">
+ <property name="visible">True</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkTable" id="table34">
+ <property name="visible">True</property>
+ <property name="border_width">12</property>
+ <property name="n_rows">4</property>
+ <property name="n_columns">2</property>
+ <child>
+ <object class="GtkCheckButton" id="delay-delivery-button">
+ <property comments="To translators: This means Delay the message delivery for some time" name="label" translatable="yes">_Delay message delivery</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label95">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAlignment" id="alignment39">
+ <property name="visible">True</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkHBox" id="hbox11">
+ <property name="visible">True</property>
+ <property name="border_width">12</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkLabel" id="label96">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes" comments="Translators: This is part of 'After [ X ] days', where [ X ] is a spinner with a number" context="ESendOptionsAfter">_After</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">expire-after</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="expire-after">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="adjustment">adjustment2</property>
+ <property name="climb_rate">1</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label97">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes" comments="Translators: This is part of 'After [ X ] days', where [ X ] is a spinner with a number" context="ESendOptionsAfter">days</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="right_attach">2</property>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ <property name="y_options">GTK_EXPAND</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="expiration-button">
+ <property name="label" translatable="yes">_Set expiration date</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label98">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAlignment" id="alignment40">
+ <property name="visible">True</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkHBox" id="hbox13">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="border_width">12</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkLabel" id="until-label">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes" comments="Translators: This is part of 'Until [ date ]', where [ date ] is a date picker" context="ESendOptions">_Until</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">until-date</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="EDateEdit" id="until-date">
+ <property name="visible">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="right_attach">2</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label103">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Delivery Options</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="right_attach">4</property>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="combo-priority">
+ <property name="visible">True</property>
+ <property name="model">model1</property>
+ <child>
+ <object class="GtkCellRendererText" id="renderer1"/>
+ <attributes>
+ <attribute name="text">0</attribute>
+ </attributes>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="y_options">GTK_FILL</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="priority-label">
+ <property name="visible">True</property>
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">_Priority:</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">combo-priority</property>
+ </object>
+ <packing>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="security-label">
+ <property name="visible">True</property>
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">_Classification:</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">security-combo</property>
+ </object>
+ <packing>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="security-combo">
+ <property name="visible">True</property>
+ <property name="model">model2</property>
+ <child>
+ <object class="GtkCellRendererText" id="renderer2"/>
+ <attributes>
+ <attribute name="text">0</attribute>
+ </attributes>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options">GTK_FILL</property>
+ </packing>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="gopts-label">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Gene_ral Options</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="status-tracking">
+ <property name="visible">True</property>
+ <property name="border_width">12</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkFrame" id="frame18">
+ <property name="visible">True</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkAlignment" id="alignment42">
+ <property name="visible">True</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkVBox" id="vbox10">
+ <property name="visible">True</property>
+ <property name="border_width">12</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkCheckButton" id="create-sent-button">
+ <property name="label" translatable="yes">Creat_e a sent item to track information</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAlignment" id="alignment46">
+ <property name="visible">True</property>
+ <property name="left_padding">19</property>
+ <child>
+ <object class="GtkRadioButton" id="delivered">
+ <property name="label" translatable="yes">_Delivered</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAlignment" id="alignment47">
+ <property name="visible">True</property>
+ <property name="left_padding">19</property>
+ <child>
+ <object class="GtkRadioButton" id="delivered-opened">
+ <property name="label" translatable="yes">Deli_vered and opened</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ <property name="group">delivered</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAlignment" id="alignment48">
+ <property name="visible">True</property>
+ <property name="left_padding">19</property>
+ <child>
+ <object class="GtkRadioButton" id="all-info">
+ <property name="label" translatable="yes">_All information</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ <property name="group">delivered</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAlignment" id="alignment56">
+ <property name="visible">True</property>
+ <property name="left_padding">19</property>
+ <child>
+ <object class="GtkCheckButton" id="autodelete">
+ <property name="label" translatable="yes">A_uto-delete sent item</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">4</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label106">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Status Tracking</property>
+ <property name="use_underline">True</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame" id="frame19">
+ <property name="visible">True</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkAlignment" id="alignment43">
+ <property name="visible">True</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkTable" id="table35">
+ <property name="visible">True</property>
+ <property name="border_width">12</property>
+ <property name="n_rows">4</property>
+ <property name="n_columns">2</property>
+ <property name="column_spacing">12</property>
+ <property name="row_spacing">12</property>
+ <child>
+ <object class="GtkLabel" id="opened-label">
+ <property name="visible">True</property>
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">_When opened:</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">open-combo</property>
+ </object>
+ <packing>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="declined-label">
+ <property name="visible">True</property>
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">When decli_ned:</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">delete-combo</property>
+ </object>
+ <packing>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="completed-label">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">When co_mpleted:</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">complete-combo</property>
+ </object>
+ <packing>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="accepted-label">
+ <property name="visible">True</property>
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">When acce_pted:</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">accept-combo</property>
+ </object>
+ <packing>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="open-combo">
+ <property name="visible">True</property>
+ <property name="model">model3</property>
+ <child>
+ <object class="GtkCellRendererText" id="renderer3"/>
+ <attributes>
+ <attribute name="text">0</attribute>
+ </attributes>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="y_options">GTK_FILL</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="delete-combo">
+ <property name="visible">True</property>
+ <property name="model">model4</property>
+ <child>
+ <object class="GtkCellRendererText" id="renderer4"/>
+ <attributes>
+ <attribute name="text">0</attribute>
+ </attributes>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="y_options">GTK_FILL</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="accept-combo">
+ <property name="visible">True</property>
+ <property name="model">model5</property>
+ <child>
+ <object class="GtkCellRendererText" id="renderer5"/>
+ <attributes>
+ <attribute name="text">0</attribute>
+ </attributes>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="y_options">GTK_FILL</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="complete-combo">
+ <property name="visible">True</property>
+ <property name="model">model6</property>
+ <child>
+ <object class="GtkCellRendererText" id="renderer6"/>
+ <attributes>
+ <attribute name="text">0</attribute>
+ </attributes>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label111">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Return Notification</property>
+ <property name="use_underline">True</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="slabel">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Sta_tus Tracking</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child internal-child="action_area">
+ <object class="GtkHButtonBox" id="dialog-action_area1">
+ <property name="visible">True</property>
+ <property name="layout_style">end</property>
+ <child>
+ <object class="GtkButton" id="helpbutton1">
+ <property name="label">gtk-help</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_stock">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="cancelbutton1">
+ <property name="label">gtk-cancel</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_stock">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="okbutton1">
+ <property name="label">gtk-ok</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_stock">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="pack_type">end</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="-11">helpbutton1</action-widget>
+ <action-widget response="-6">cancelbutton1</action-widget>
+ <action-widget response="-5">okbutton1</action-widget>
+ </action-widgets>
+ </object>
+</interface>
diff --git a/e-util/e-sorter-array.c b/e-util/e-sorter-array.c
index 4caa092704..1c373160df 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 5fb9c31f6b..07a32b4b82 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 b55d985daa..ecb597a832 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 37015e54ae..94b63f3bd4 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 0000000000..d8d2273527
--- /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 0000000000..d022f4a8ce
--- /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/e-util/e-source-config-backend.c b/e-util/e-source-config-backend.c
new file mode 100644
index 0000000000..e6802f99ae
--- /dev/null
+++ b/e-util/e-source-config-backend.c
@@ -0,0 +1,140 @@
+/*
+ * e-source-config-backend.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-backend.h"
+
+G_DEFINE_TYPE (
+ ESourceConfigBackend,
+ e_source_config_backend,
+ E_TYPE_EXTENSION)
+
+static gboolean
+source_config_backend_allow_creation (ESourceConfigBackend *backend)
+{
+ return TRUE;
+}
+
+static void
+source_config_backend_insert_widgets (ESourceConfigBackend *backend,
+ ESource *scratch_source)
+{
+ /* does nothing */
+}
+
+static gboolean
+source_config_backend_check_complete (ESourceConfigBackend *backend,
+ ESource *scratch_source)
+{
+ return TRUE;
+}
+
+static void
+source_config_backend_commit_changes (ESourceConfigBackend *backend,
+ ESource *scratch_source)
+{
+ /* does nothing */
+}
+
+static void
+e_source_config_backend_class_init (ESourceConfigBackendClass *class)
+{
+ EExtensionClass *extension_class;
+
+ extension_class = E_EXTENSION_CLASS (class);
+ extension_class->extensible_type = E_TYPE_SOURCE_CONFIG;
+
+ class->allow_creation = source_config_backend_allow_creation;
+ class->insert_widgets = source_config_backend_insert_widgets;
+ class->check_complete = source_config_backend_check_complete;
+ class->commit_changes = source_config_backend_commit_changes;
+}
+
+static void
+e_source_config_backend_init (ESourceConfigBackend *backend)
+{
+}
+
+ESourceConfig *
+e_source_config_backend_get_config (ESourceConfigBackend *backend)
+{
+ EExtensible *extensible;
+
+ g_return_val_if_fail (E_IS_SOURCE_CONFIG_BACKEND (backend), NULL);
+
+ extensible = e_extension_get_extensible (E_EXTENSION (backend));
+
+ return E_SOURCE_CONFIG (extensible);
+}
+
+gboolean
+e_source_config_backend_allow_creation (ESourceConfigBackend *backend)
+{
+ ESourceConfigBackendClass *class;
+
+ g_return_val_if_fail (E_IS_SOURCE_CONFIG_BACKEND (backend), FALSE);
+
+ class = E_SOURCE_CONFIG_BACKEND_GET_CLASS (backend);
+ g_return_val_if_fail (class->allow_creation != NULL, FALSE);
+
+ return class->allow_creation (backend);
+}
+
+void
+e_source_config_backend_insert_widgets (ESourceConfigBackend *backend,
+ ESource *scratch_source)
+{
+ ESourceConfigBackendClass *class;
+
+ g_return_if_fail (E_IS_SOURCE_CONFIG_BACKEND (backend));
+ g_return_if_fail (E_IS_SOURCE (scratch_source));
+
+ class = E_SOURCE_CONFIG_BACKEND_GET_CLASS (backend);
+ g_return_if_fail (class->insert_widgets != NULL);
+
+ class->insert_widgets (backend, scratch_source);
+}
+
+gboolean
+e_source_config_backend_check_complete (ESourceConfigBackend *backend,
+ ESource *scratch_source)
+{
+ ESourceConfigBackendClass *class;
+
+ g_return_val_if_fail (E_IS_SOURCE_CONFIG_BACKEND (backend), FALSE);
+ g_return_val_if_fail (E_IS_SOURCE (scratch_source), FALSE);
+
+ class = E_SOURCE_CONFIG_BACKEND_GET_CLASS (backend);
+ g_return_val_if_fail (class->check_complete != NULL, FALSE);
+
+ return class->check_complete (backend, scratch_source);
+}
+
+void
+e_source_config_backend_commit_changes (ESourceConfigBackend *backend,
+ ESource *scratch_source)
+{
+ ESourceConfigBackendClass *class;
+
+ g_return_if_fail (E_IS_SOURCE_CONFIG_BACKEND (backend));
+ g_return_if_fail (E_IS_SOURCE (scratch_source));
+
+ class = E_SOURCE_CONFIG_BACKEND_GET_CLASS (backend);
+ g_return_if_fail (class->commit_changes != NULL);
+
+ class->commit_changes (backend, scratch_source);
+}
diff --git a/e-util/e-source-config-backend.h b/e-util/e-source-config-backend.h
new file mode 100644
index 0000000000..3191ca1c23
--- /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 0000000000..8a311c8ab1
--- /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 0000000000..6f01c8a0eb
--- /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 0000000000..aacb48dd5c
--- /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 0000000000..3868c0309b
--- /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 0000000000..68e29fd13c
--- /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 0000000000..eae45ba62f
--- /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 0000000000..4a75ed10e5
--- /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 0000000000..d4d92284fc
--- /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 452e91113d..e10097f38a 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/e-util/e-spell-entry.c b/e-util/e-spell-entry.c
new file mode 100644
index 0000000000..56f7c14f8c
--- /dev/null
+++ b/e-util/e-spell-entry.c
@@ -0,0 +1,866 @@
+/*
+ * e-spell-entry.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/>
+ *
+ */
+
+/* This code is based on libsexy's SexySpellEntry */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <glib/gi18n-lib.h>
+#include <gtk/gtk.h>
+
+#include <editor/gtkhtml-spell-language.h>
+#include <editor/gtkhtml-spell-checker.h>
+
+#include "e-spell-entry.h"
+
+enum {
+ PROP_0,
+ PROP_CHECKING_ENABLED
+};
+
+struct _ESpellEntryPrivate
+{
+ PangoAttrList *attr_list;
+ gint mark_character;
+ gint entry_scroll_offset;
+ GSettings *settings;
+ gboolean custom_checkers;
+ gboolean checking_enabled;
+ GSList *checkers;
+ gchar **words;
+ gint *word_starts;
+ gint *word_ends;
+};
+
+#define E_SPELL_ENTRY_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_SPELL_ENTRY, ESpellEntryPrivate))
+
+G_DEFINE_TYPE (ESpellEntry, e_spell_entry, GTK_TYPE_ENTRY);
+
+static gboolean
+word_misspelled (ESpellEntry *entry,
+ gint start,
+ gint end)
+{
+ const gchar *text;
+ gchar *word;
+ gboolean result = TRUE;
+
+ if (start == end)
+ return FALSE;
+
+ text = gtk_entry_get_text (GTK_ENTRY (entry));
+ word = g_new0 (gchar, end - start + 2);
+
+ g_strlcpy (word, text + start, end - start + 1);
+
+ if (g_unichar_isalpha (*word)) {
+ GSList *li;
+ gssize wlen = strlen (word);
+
+ for (li = entry->priv->checkers; li; li = g_slist_next (li)) {
+ GtkhtmlSpellChecker *checker = li->data;
+ if (gtkhtml_spell_checker_check_word (checker, word, wlen)) {
+ result = FALSE;
+ break;
+ }
+ }
+ }
+ g_free (word);
+
+ return result;
+}
+
+static void
+insert_underline (ESpellEntry *entry,
+ guint start,
+ guint end)
+{
+ PangoAttribute *ucolor = pango_attr_underline_color_new (65535, 0, 0);
+ PangoAttribute *unline = pango_attr_underline_new (PANGO_UNDERLINE_ERROR);
+
+ ucolor->start_index = start;
+ unline->start_index = start;
+
+ ucolor->end_index = end;
+ unline->end_index = end;
+
+ pango_attr_list_insert (entry->priv->attr_list, ucolor);
+ pango_attr_list_insert (entry->priv->attr_list, unline);
+}
+
+static void
+check_word (ESpellEntry *entry,
+ gint start,
+ gint end)
+{
+ PangoAttrIterator *it;
+
+ /* Check to see if we've got any attributes at this position.
+ * If so, free them, since we'll readd it if the word is misspelled */
+ it = pango_attr_list_get_iterator (entry->priv->attr_list);
+
+ if (it == NULL)
+ return;
+ do {
+ gint s, e;
+ pango_attr_iterator_range (it, &s, &e);
+ if (s == start) {
+ GSList *attrs = pango_attr_iterator_get_attrs (it);
+ g_slist_foreach (attrs, (GFunc) pango_attribute_destroy, NULL);
+ g_slist_free (attrs);
+ }
+ } while (pango_attr_iterator_next (it));
+ pango_attr_iterator_destroy (it);
+
+ if (word_misspelled (entry, start, end))
+ insert_underline (entry, start, end);
+}
+
+static void
+spell_entry_recheck_all (ESpellEntry *entry)
+{
+ GtkWidget *widget = GTK_WIDGET (entry);
+ PangoLayout *layout;
+ gint length, i;
+
+ if (!entry->priv->words)
+ return;
+
+ /* Remove all existing pango attributes. These will get read as we check */
+ pango_attr_list_unref (entry->priv->attr_list);
+ entry->priv->attr_list = pango_attr_list_new ();
+
+ if (entry->priv->checkers && entry->priv->checking_enabled) {
+ /* Loop through words */
+ for (i = 0; entry->priv->words[i]; i++) {
+ length = strlen (entry->priv->words[i]);
+ if (length == 0)
+ continue;
+ check_word (entry, entry->priv->word_starts[i], entry->priv->word_ends[i]);
+ }
+
+ layout = gtk_entry_get_layout (GTK_ENTRY (entry));
+ pango_layout_set_attributes (layout, entry->priv->attr_list);
+ }
+
+ if (gtk_widget_get_realized (widget))
+ gtk_widget_queue_draw (widget);
+}
+
+static void
+get_word_extents_from_position (ESpellEntry *entry,
+ gint *start,
+ gint *end,
+ guint position)
+{
+ const gchar *text;
+ gint i, bytes_pos;
+
+ *start = -1;
+ *end = -1;
+
+ if (entry->priv->words == NULL)
+ return;
+
+ text = gtk_entry_get_text (GTK_ENTRY (entry));
+ bytes_pos = (gint) (g_utf8_offset_to_pointer (text, position) - text);
+
+ for (i = 0; entry->priv->words[i]; i++) {
+ if (bytes_pos >= entry->priv->word_starts[i] &&
+ bytes_pos <= entry->priv->word_ends[i]) {
+ *start = entry->priv->word_starts[i];
+ *end = entry->priv->word_ends[i];
+ return;
+ }
+ }
+}
+
+static void
+entry_strsplit_utf8 (GtkEntry *entry,
+ gchar ***set,
+ gint **starts,
+ gint **ends)
+{
+ PangoLayout *layout;
+ PangoLogAttr *log_attrs;
+ const gchar *text;
+ gint n_attrs, n_strings, i, j;
+
+ layout = gtk_entry_get_layout (GTK_ENTRY (entry));
+ text = gtk_entry_get_text (GTK_ENTRY (entry));
+ pango_layout_get_log_attrs (layout, &log_attrs, &n_attrs);
+
+ /* Find how many words we have */
+ n_strings = 0;
+ for (i = 0; i < n_attrs; i++)
+ if (log_attrs[i].is_word_start)
+ n_strings++;
+
+ *set = g_new0 (gchar *, n_strings + 1);
+ *starts = g_new0 (gint, n_strings);
+ *ends = g_new0 (gint, n_strings);
+
+ /* Copy out strings */
+ for (i = 0, j = 0; i < n_attrs; i++) {
+ if (log_attrs[i].is_word_start) {
+ gint cend, bytes;
+ gchar *start;
+
+ /* Find the end of this string */
+ cend = i;
+ while (!(log_attrs[cend].is_word_end))
+ cend++;
+
+ /* Copy sub-string */
+ start = g_utf8_offset_to_pointer (text, i);
+ bytes = (gint) (g_utf8_offset_to_pointer (text, cend) - start);
+ (*set)[j] = g_new0 (gchar, bytes + 1);
+ (*starts)[j] = (gint) (start - text);
+ (*ends)[j] = (gint) (start - text + bytes);
+ g_utf8_strncpy ((*set)[j], start, cend - i);
+
+ /* Move on to the next word */
+ j++;
+ }
+ }
+
+ g_free (log_attrs);
+}
+
+static void
+add_to_dictionary (GtkWidget *menuitem,
+ ESpellEntry *entry)
+{
+ gchar *word;
+ gint start, end;
+ GtkhtmlSpellChecker *checker;
+
+ get_word_extents_from_position (entry, &start, &end, entry->priv->mark_character);
+ word = gtk_editable_get_chars (GTK_EDITABLE (entry), start, end);
+
+ checker = g_object_get_data (G_OBJECT (menuitem), "spell-entry-checker");
+ if (checker)
+ gtkhtml_spell_checker_add_word (checker, word, -1);
+
+ g_free (word);
+
+ if (entry->priv->words) {
+ g_strfreev (entry->priv->words);
+ g_free (entry->priv->word_starts);
+ g_free (entry->priv->word_ends);
+ }
+
+ entry_strsplit_utf8 (GTK_ENTRY (entry), &entry->priv->words, &entry->priv->word_starts, &entry->priv->word_ends);
+ spell_entry_recheck_all (entry);
+}
+
+static void
+ignore_all (GtkWidget *menuitem,
+ ESpellEntry *entry)
+{
+ gchar *word;
+ gint start, end;
+ GSList *li;
+
+ get_word_extents_from_position (entry, &start, &end, entry->priv->mark_character);
+ word = gtk_editable_get_chars (GTK_EDITABLE (entry), start, end);
+
+ for (li = entry->priv->checkers; li; li = g_slist_next (li)) {
+ GtkhtmlSpellChecker *checker = li->data;
+ gtkhtml_spell_checker_add_word_to_session (checker, word, -1);
+ }
+
+ g_free (word);
+
+ if (entry->priv->words) {
+ g_strfreev (entry->priv->words);
+ g_free (entry->priv->word_starts);
+ g_free (entry->priv->word_ends);
+ }
+ entry_strsplit_utf8 (GTK_ENTRY (entry), &entry->priv->words, &entry->priv->word_starts, &entry->priv->word_ends);
+ spell_entry_recheck_all (entry);
+}
+
+static void
+replace_word (GtkWidget *menuitem,
+ ESpellEntry *entry)
+{
+ gchar *oldword;
+ const gchar *newword;
+ gint start, end;
+ gint cursor;
+ GtkhtmlSpellChecker *checker;
+
+ get_word_extents_from_position (entry, &start, &end, entry->priv->mark_character);
+ oldword = gtk_editable_get_chars (GTK_EDITABLE (entry), start, end);
+ newword = gtk_label_get_text (GTK_LABEL (gtk_bin_get_child (GTK_BIN (menuitem))));
+
+ cursor = gtk_editable_get_position (GTK_EDITABLE (entry));
+ /* is the cursor at the end? If so, restore it there */
+ if (g_utf8_strlen (gtk_entry_get_text (GTK_ENTRY (entry)), -1) == cursor)
+ cursor = -1;
+ else if (cursor > start && cursor <= end)
+ cursor = start;
+
+ gtk_editable_delete_text (GTK_EDITABLE (entry), start, end);
+ gtk_editable_set_position (GTK_EDITABLE (entry), start);
+ gtk_editable_insert_text (
+ GTK_EDITABLE (entry), newword, strlen (newword),
+ &start);
+ gtk_editable_set_position (GTK_EDITABLE (entry), cursor);
+
+ checker = g_object_get_data (G_OBJECT (menuitem), "spell-entry-checker");
+
+ if (checker)
+ gtkhtml_spell_checker_store_replacement (checker, oldword, -1, newword, -1);
+
+ g_free (oldword);
+}
+
+static void
+build_suggestion_menu (ESpellEntry *entry,
+ GtkWidget *menu,
+ GtkhtmlSpellChecker *checker,
+ const gchar *word)
+{
+ GtkWidget *mi;
+ GList *suggestions, *iter;
+
+ suggestions = gtkhtml_spell_checker_get_suggestions (checker, word, -1);
+
+ if (!suggestions) {
+ /* no suggestions. Put something in the menu anyway... */
+ GtkWidget *label = gtk_label_new (_("(no suggestions)"));
+ PangoAttribute *attribute;
+ PangoAttrList *attribute_list;
+
+ attribute_list = pango_attr_list_new ();
+ attribute = pango_attr_style_new (PANGO_STYLE_ITALIC);
+ pango_attr_list_insert (attribute_list, attribute);
+ gtk_label_set_attributes (GTK_LABEL (label), attribute_list);
+ pango_attr_list_unref (attribute_list);
+
+ mi = gtk_separator_menu_item_new ();
+ gtk_container_add (GTK_CONTAINER (mi), label);
+ gtk_widget_show_all (mi);
+ gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), mi);
+ } else {
+ gint ii = 0;
+
+ /* build a set of menus with suggestions */
+ for (iter = suggestions; iter; iter = g_list_next (iter), ii++) {
+ if ((ii != 0) && (ii % 10 == 0)) {
+ mi = gtk_separator_menu_item_new ();
+ gtk_widget_show (mi);
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu), mi);
+
+ mi = gtk_menu_item_new_with_label (_("More..."));
+ gtk_widget_show (mi);
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu), mi);
+
+ menu = gtk_menu_new ();
+ gtk_menu_item_set_submenu (GTK_MENU_ITEM (mi), menu);
+ }
+
+ mi = gtk_menu_item_new_with_label (iter->data);
+ g_object_set_data (G_OBJECT (mi), "spell-entry-checker", checker);
+ g_signal_connect (mi, "activate", G_CALLBACK (replace_word), entry);
+ gtk_widget_show (mi);
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu), mi);
+ }
+ }
+
+ g_list_free_full (suggestions, g_free);
+}
+
+static GtkWidget *
+build_spelling_menu (ESpellEntry *entry,
+ const gchar *word)
+{
+ GtkhtmlSpellChecker *checker;
+ GtkWidget *topmenu, *mi;
+ gchar *label;
+
+ topmenu = gtk_menu_new ();
+
+ if (!entry->priv->checkers)
+ return topmenu;
+
+ /* Suggestions */
+ if (!entry->priv->checkers->next) {
+ checker = entry->priv->checkers->data;
+ build_suggestion_menu (entry, topmenu, checker, word);
+ } else {
+ GSList *li;
+ GtkWidget *menu;
+ const gchar *lang_name;
+
+ for (li = entry->priv->checkers; li; li = g_slist_next (li)) {
+ const GtkhtmlSpellLanguage *language;
+
+ checker = li->data;
+ language = gtkhtml_spell_checker_get_language (checker);
+ if (!language)
+ continue;
+
+ lang_name = gtkhtml_spell_language_get_name (language);
+ if (!lang_name)
+ lang_name = gtkhtml_spell_language_get_code (language);
+
+ mi = gtk_menu_item_new_with_label (lang_name ? lang_name : "???");
+
+ gtk_widget_show (mi);
+ gtk_menu_shell_append (GTK_MENU_SHELL (topmenu), mi);
+ menu = gtk_menu_new ();
+ gtk_menu_item_set_submenu (GTK_MENU_ITEM (mi), menu);
+ build_suggestion_menu (entry, menu, checker, word);
+ }
+ }
+
+ /* Separator */
+ mi = gtk_separator_menu_item_new ();
+ gtk_widget_show (mi);
+ gtk_menu_shell_append (GTK_MENU_SHELL (topmenu), mi);
+
+ /* + Add to Dictionary */
+ label = g_strdup_printf (_("Add \"%s\" to Dictionary"), word);
+ mi = gtk_image_menu_item_new_with_label (label);
+ g_free (label);
+
+ gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (mi), gtk_image_new_from_stock (GTK_STOCK_ADD, GTK_ICON_SIZE_MENU));
+
+ if (!entry->priv->checkers->next) {
+ checker = entry->priv->checkers->data;
+ g_object_set_data (G_OBJECT (mi), "spell-entry-checker", checker);
+ g_signal_connect (mi, "activate", G_CALLBACK (add_to_dictionary), entry);
+ } else {
+ GSList *li;
+ GtkWidget *menu, *submi;
+ const gchar *lang_name;
+
+ menu = gtk_menu_new ();
+ gtk_menu_item_set_submenu (GTK_MENU_ITEM (mi), menu);
+
+ for (li = entry->priv->checkers; li; li = g_slist_next (li)) {
+ const GtkhtmlSpellLanguage *language;
+
+ checker = li->data;
+ language = gtkhtml_spell_checker_get_language (checker);
+ if (!language)
+ continue;
+
+ lang_name = gtkhtml_spell_language_get_name (language);
+ if (!lang_name)
+ lang_name = gtkhtml_spell_language_get_code (language);
+
+ submi = gtk_menu_item_new_with_label (lang_name ? lang_name : "???");
+ g_object_set_data (G_OBJECT (submi), "spell-entry-checker", checker);
+ g_signal_connect (submi, "activate", G_CALLBACK (add_to_dictionary), entry);
+
+ gtk_widget_show (submi);
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu), submi);
+ }
+ }
+
+ gtk_widget_show_all (mi);
+ gtk_menu_shell_append (GTK_MENU_SHELL (topmenu), mi);
+
+ /* - Ignore All */
+ mi = gtk_image_menu_item_new_with_label (_("Ignore All"));
+ gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (mi), gtk_image_new_from_stock (GTK_STOCK_REMOVE, GTK_ICON_SIZE_MENU));
+ g_signal_connect (mi, "activate", G_CALLBACK (ignore_all), entry);
+ gtk_widget_show_all (mi);
+ gtk_menu_shell_append (GTK_MENU_SHELL (topmenu), mi);
+
+ return topmenu;
+}
+
+static void
+spell_entry_add_suggestions_menu (ESpellEntry *entry,
+ GtkMenu *menu,
+ const gchar *word)
+{
+ GtkWidget *icon, *mi;
+
+ g_return_if_fail (menu != NULL);
+ g_return_if_fail (word != NULL);
+
+ /* separator */
+ mi = gtk_separator_menu_item_new ();
+ gtk_widget_show (mi);
+ gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), mi);
+
+ /* Above the separator, show the suggestions menu */
+ icon = gtk_image_new_from_stock (GTK_STOCK_SPELL_CHECK, GTK_ICON_SIZE_MENU);
+ mi = gtk_image_menu_item_new_with_label (_("Spelling Suggestions"));
+ gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (mi), icon);
+
+ gtk_menu_item_set_submenu (GTK_MENU_ITEM (mi), build_spelling_menu (entry, word));
+
+ gtk_widget_show_all (mi);
+ gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), mi);
+}
+
+static gboolean
+spell_entry_popup_menu (ESpellEntry *entry)
+{
+ /* Menu popped up from a keybinding (menu key or <shift>+F10). Use
+ * the cursor position as the mark position */
+ entry->priv->mark_character = gtk_editable_get_position (GTK_EDITABLE (entry));
+
+ return FALSE;
+}
+
+static void
+spell_entry_populate_popup (ESpellEntry *entry,
+ GtkMenu *menu,
+ gpointer data)
+{
+ gint start, end;
+ gchar *word;
+
+ if (!entry->priv->checkers)
+ return;
+
+ get_word_extents_from_position (entry, &start, &end, entry->priv->mark_character);
+ if (start == end)
+ return;
+
+ if (!word_misspelled (entry, start, end))
+ return;
+
+ word = gtk_editable_get_chars (GTK_EDITABLE (entry), start, end);
+ g_return_if_fail (word != NULL);
+
+ spell_entry_add_suggestions_menu (entry, menu, word);
+
+ g_free (word);
+}
+
+static void
+spell_entry_changed (GtkEditable *editable)
+{
+ ESpellEntry *entry = E_SPELL_ENTRY (editable);
+
+ if (!entry->priv->checkers)
+ return;
+
+ if (entry->priv->words) {
+ g_strfreev (entry->priv->words);
+ g_free (entry->priv->word_starts);
+ g_free (entry->priv->word_ends);
+ }
+ entry_strsplit_utf8 (GTK_ENTRY (entry), &entry->priv->words, &entry->priv->word_starts, &entry->priv->word_ends);
+ spell_entry_recheck_all (entry);
+}
+
+static void
+spell_entry_notify_scroll_offset (ESpellEntry *spell_entry)
+{
+ g_return_if_fail (spell_entry != NULL);
+
+ g_object_get (G_OBJECT (spell_entry), "scroll-offset", &spell_entry->priv->entry_scroll_offset, NULL);
+}
+
+static GList *
+spell_entry_load_spell_languages (void)
+{
+ GSettings *settings;
+ GList *spell_languages = NULL;
+ gchar **strv;
+ gint ii;
+
+ /* Ask GSettings for a list of spell check language codes. */
+ settings = g_settings_new ("org.gnome.evolution.mail");
+ strv = g_settings_get_strv (settings, "composer-spell-languages");
+ g_object_unref (settings);
+
+ /* Convert the codes to spell language structs. */
+ for (ii = 0; strv[ii] != NULL; ii++) {
+ gchar *language_code = strv[ii];
+ const GtkhtmlSpellLanguage *language;
+
+ language = gtkhtml_spell_language_lookup (language_code);
+ if (language != NULL)
+ spell_languages = g_list_prepend (
+ spell_languages, (gpointer) language);
+ }
+
+ g_strfreev (strv);
+
+ spell_languages = g_list_reverse (spell_languages);
+
+ /* Pick a default spell language if it came back empty. */
+ if (spell_languages == NULL) {
+ const GtkhtmlSpellLanguage *language;
+
+ language = gtkhtml_spell_language_lookup (NULL);
+
+ if (language) {
+ spell_languages = g_list_prepend (
+ spell_languages, (gpointer) language);
+ }
+ }
+
+ return spell_languages;
+}
+
+static void
+spell_entry_settings_changed (ESpellEntry *spell_entry,
+ const gchar *key)
+{
+ GList *languages;
+
+ g_return_if_fail (spell_entry != NULL);
+
+ if (spell_entry->priv->custom_checkers)
+ return;
+
+ if (key && !g_str_equal (key, "composer-spell-languages"))
+ return;
+
+ languages = spell_entry_load_spell_languages ();
+ e_spell_entry_set_languages (spell_entry, languages);
+ g_list_free (languages);
+
+ spell_entry->priv->custom_checkers = FALSE;
+}
+
+static gint
+spell_entry_find_position (ESpellEntry *spell_entry,
+ gint x)
+{
+ PangoLayout *layout;
+ PangoLayoutLine *line;
+ gint index;
+ gint pos;
+ gint trailing;
+ const gchar *text;
+ GtkEntry *entry = GTK_ENTRY (spell_entry);
+
+ layout = gtk_entry_get_layout (entry);
+ text = pango_layout_get_text (layout);
+
+ line = pango_layout_get_lines_readonly (layout)->data;
+ pango_layout_line_x_to_index (line, x * PANGO_SCALE, &index, &trailing);
+
+ pos = g_utf8_pointer_to_offset (text, text + index);
+ pos += trailing;
+
+ return pos;
+}
+
+static gboolean
+e_spell_entry_draw (GtkWidget *widget,
+ cairo_t *cr)
+{
+ ESpellEntry *spell_entry = E_SPELL_ENTRY (widget);
+ GtkEntry *entry = GTK_ENTRY (widget);
+ PangoLayout *layout;
+
+ layout = gtk_entry_get_layout (entry);
+ pango_layout_set_attributes (layout, spell_entry->priv->attr_list);
+
+ return GTK_WIDGET_CLASS (e_spell_entry_parent_class)->draw (widget, cr);
+}
+
+static gboolean
+e_spell_entry_button_press (GtkWidget *widget,
+ GdkEventButton *event)
+{
+ ESpellEntry *spell_entry = E_SPELL_ENTRY (widget);
+
+ spell_entry->priv->mark_character = spell_entry_find_position (
+ spell_entry, event->x + spell_entry->priv->entry_scroll_offset);
+
+ return GTK_WIDGET_CLASS (e_spell_entry_parent_class)->button_press_event (widget, event);
+}
+
+static void
+spell_entry_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_CHECKING_ENABLED:
+ e_spell_entry_set_checking_enabled (
+ E_SPELL_ENTRY (object),
+ g_value_get_boolean (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+spell_entry_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_CHECKING_ENABLED:
+ g_value_set_boolean (
+ value,
+ e_spell_entry_get_checking_enabled (
+ E_SPELL_ENTRY (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+e_spell_entry_init (ESpellEntry *spell_entry)
+{
+ spell_entry->priv = E_SPELL_ENTRY_GET_PRIVATE (spell_entry);
+ spell_entry->priv->attr_list = pango_attr_list_new ();
+ spell_entry->priv->checkers = NULL;
+ spell_entry->priv->checking_enabled = TRUE;
+
+ g_signal_connect (spell_entry, "popup-menu", G_CALLBACK (spell_entry_popup_menu), NULL);
+ g_signal_connect (spell_entry, "populate-popup", G_CALLBACK (spell_entry_populate_popup), NULL);
+ g_signal_connect (spell_entry, "changed", G_CALLBACK (spell_entry_changed), NULL);
+ g_signal_connect (spell_entry, "notify::scroll-offset", G_CALLBACK (spell_entry_notify_scroll_offset), NULL);
+
+ /* listen for languages changes */
+ spell_entry->priv->settings = g_settings_new ("org.gnome.evolution.mail");
+ g_signal_connect_swapped (spell_entry->priv->settings, "changed", G_CALLBACK (spell_entry_settings_changed), spell_entry);
+
+ /* load current settings */
+ spell_entry_settings_changed (spell_entry, NULL);
+}
+
+static void
+e_spell_entry_finalize (GObject *object)
+{
+ ESpellEntry *entry;
+
+ g_return_if_fail (object != NULL);
+ g_return_if_fail (E_IS_SPELL_ENTRY (object));
+
+ entry = E_SPELL_ENTRY (object);
+
+ if (entry->priv->settings)
+ g_object_unref (entry->priv->settings);
+ if (entry->priv->checkers)
+ g_slist_free_full (entry->priv->checkers, g_object_unref);
+ if (entry->priv->attr_list)
+ pango_attr_list_unref (entry->priv->attr_list);
+ if (entry->priv->words)
+ g_strfreev (entry->priv->words);
+ if (entry->priv->word_starts)
+ g_free (entry->priv->word_starts);
+ if (entry->priv->word_ends)
+ g_free (entry->priv->word_ends);
+
+ G_OBJECT_CLASS (e_spell_entry_parent_class)->finalize (object);
+}
+
+static void
+e_spell_entry_class_init (ESpellEntryClass *class)
+{
+ GObjectClass *object_class;
+ GtkWidgetClass *widget_class;
+
+ g_type_class_add_private (class, sizeof (ESpellEntryPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = spell_entry_set_property;
+ object_class->get_property = spell_entry_get_property;
+ object_class->finalize = e_spell_entry_finalize;
+
+ widget_class = GTK_WIDGET_CLASS (class);
+ widget_class->draw = e_spell_entry_draw;
+ widget_class->button_press_event = e_spell_entry_button_press;
+
+ g_object_class_install_property (
+ object_class,
+ PROP_CHECKING_ENABLED,
+ g_param_spec_boolean (
+ "checking-enabled",
+ "checking enabled",
+ "Spell Checking is Enabled",
+ TRUE,
+ G_PARAM_READWRITE));
+}
+
+GtkWidget *
+e_spell_entry_new (void)
+{
+ return g_object_new (E_TYPE_SPELL_ENTRY, NULL);
+}
+
+/* 'languages' consists of 'const GtkhtmlSpellLanguage *' */
+void
+e_spell_entry_set_languages (ESpellEntry *spell_entry,
+ GList *languages)
+{
+ GList *iter;
+
+ g_return_if_fail (spell_entry != NULL);
+
+ spell_entry->priv->custom_checkers = TRUE;
+
+ if (spell_entry->priv->checkers)
+ g_slist_free_full (spell_entry->priv->checkers, g_object_unref);
+ spell_entry->priv->checkers = NULL;
+
+ for (iter = languages; iter; iter = g_list_next (iter)) {
+ const GtkhtmlSpellLanguage *language = iter->data;
+
+ if (language)
+ spell_entry->priv->checkers = g_slist_prepend (
+ spell_entry->priv->checkers,
+ gtkhtml_spell_checker_new (language));
+ }
+
+ spell_entry->priv->checkers = g_slist_reverse (spell_entry->priv->checkers);
+
+ if (gtk_widget_get_realized (GTK_WIDGET (spell_entry)))
+ spell_entry_recheck_all (spell_entry);
+}
+
+gboolean
+e_spell_entry_get_checking_enabled (ESpellEntry *spell_entry)
+{
+ g_return_val_if_fail (spell_entry != NULL, FALSE);
+
+ return spell_entry->priv->checking_enabled;
+}
+
+void
+e_spell_entry_set_checking_enabled (ESpellEntry *spell_entry,
+ gboolean enable_checking)
+{
+ g_return_if_fail (spell_entry != NULL);
+
+ if (spell_entry->priv->checking_enabled == enable_checking)
+ return;
+
+ spell_entry->priv->checking_enabled = enable_checking;
+ spell_entry_recheck_all (spell_entry);
+
+ g_object_notify (G_OBJECT (spell_entry), "checking-enabled");
+
+}
diff --git a/e-util/e-spell-entry.h b/e-util/e-spell-entry.h
new file mode 100644
index 0000000000..07c4c0d24d
--- /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 8736dba7d4..2b00f9faa4 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 39a22ba424..b482d7fada 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 0000000000..6de00f913b
--- /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 0000000000..cd1519b82e
--- /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 0000000000..608e14e826
--- /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 0000000000..4e5e18a5b6
--- /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 0000000000..243aa04e7a
--- /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 0000000000..d1cf089d2d
--- /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 0000000000..ae1a00cc65
--- /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 0000000000..98f89ffd10
--- /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 0000000000..7fc74d9f27
--- /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/e-util/e-table-config.ui b/e-util/e-table-config.ui
new file mode 100644
index 0000000000..cfc6cb57fc
--- /dev/null
+++ b/e-util/e-table-config.ui
@@ -0,0 +1,1594 @@
+<?xml version="1.0"?>
+<interface>
+ <!-- interface-requires gtk+ 2.12 -->
+ <!-- interface-naming-policy toplevel-contextual -->
+ <object class="GtkDialog" id="dialog-show-fields">
+ <property name="title" translatable="yes">Show Fields</property>
+ <property name="modal">True</property>
+ <property name="type_hint">dialog</property>
+ <child internal-child="vbox">
+ <object class="GtkVBox" id="dialog-vbox3">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">8</property>
+ <child>
+ <object class="GtkVBox" id="vbox2">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkTable" id="table2">
+ <property name="visible">True</property>
+ <property name="n_columns">5</property>
+ <property name="column_spacing">6</property>
+ <property name="row_spacing">6</property>
+ <property name="homogeneous">True</property>
+ <child>
+ <object class="GtkLabel" id="label-available">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">A_vailable Fields:</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="right_attach">2</property>
+ <property name="y_options">GTK_FILL</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label-displayed">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">_Show these fields in order:</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">3</property>
+ <property name="right_attach">5</property>
+ <property name="y_options">GTK_FILL</property>
+ </packing>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkTable" id="table3">
+ <property name="visible">True</property>
+ <property name="n_columns">5</property>
+ <property name="column_spacing">6</property>
+ <property name="row_spacing">6</property>
+ <property name="homogeneous">True</property>
+ <child>
+ <object class="GtkVBox" id="vbox4">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkScrolledWindow" id="available-scrolled">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">automatic</property>
+ <property name="vscrollbar_policy">automatic</property>
+ <property name="shadow_type">in</property>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="right_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox5">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkScrolledWindow" id="shown-scrolled">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">automatic</property>
+ <property name="vscrollbar_policy">automatic</property>
+ <property name="shadow_type">in</property>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox4">
+ <property name="visible">True</property>
+ <property name="spacing">6</property>
+ <property name="homogeneous">True</property>
+ <child>
+ <object class="GtkButton" id="button-up">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <child>
+ <object class="GtkAlignment" id="alignment7">
+ <property name="visible">True</property>
+ <property name="xscale">0</property>
+ <property name="yscale">0</property>
+ <child>
+ <object class="GtkHBox" id="hbox19">
+ <property name="visible">True</property>
+ <property name="spacing">2</property>
+ <child>
+ <object class="GtkImage" id="image3">
+ <property name="visible">True</property>
+ <property name="stock">gtk-go-up</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label29">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Move _Up</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button-down">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <child>
+ <object class="GtkAlignment" id="alignment8">
+ <property name="visible">True</property>
+ <property name="xscale">0</property>
+ <property name="yscale">0</property>
+ <child>
+ <object class="GtkHBox" id="hbox20">
+ <property name="visible">True</property>
+ <property name="spacing">2</property>
+ <child>
+ <object class="GtkImage" id="image4">
+ <property name="visible">True</property>
+ <property name="stock">gtk-go-down</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label30">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Move _Down</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">3</property>
+ <property name="right_attach">5</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox6">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkButton" id="button-add">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <child>
+ <object class="GtkAlignment" id="alignment5">
+ <property name="visible">True</property>
+ <property name="xalign">0.69999998807907104</property>
+ <property name="xscale">0</property>
+ <property name="yscale">0</property>
+ <child>
+ <object class="GtkHBox" id="hbox17">
+ <property name="visible">True</property>
+ <property name="spacing">2</property>
+ <child>
+ <object class="GtkLabel" id="label31">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Add</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkImage" id="image5">
+ <property name="visible">True</property>
+ <property name="stock">gtk-go-forward</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button-remove">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <child>
+ <object class="GtkAlignment" id="alignment6">
+ <property name="visible">True</property>
+ <property name="xscale">0</property>
+ <property name="yscale">0</property>
+ <child>
+ <object class="GtkHBox" id="hbox18">
+ <property name="visible">True</property>
+ <property name="spacing">2</property>
+ <child>
+ <object class="GtkImage" id="image1">
+ <property name="visible">True</property>
+ <property name="stock">gtk-go-back</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label27">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Remove</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="right_attach">3</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child internal-child="action_area">
+ <object class="GtkHButtonBox" id="dialog-action_area3">
+ <property name="visible">True</property>
+ <property name="layout_style">end</property>
+ <child>
+ <object class="GtkButton" id="button22">
+ <property name="label">gtk-cancel</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_stock">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button20">
+ <property name="label">gtk-ok</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_stock">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="pack_type">end</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="-6">button22</action-widget>
+ <action-widget response="-5">button20</action-widget>
+ </action-widgets>
+ </object>
+ <object class="GtkDialog" id="dialog-group-by">
+ <property name="title" translatable="yes">Group</property>
+ <property name="modal">True</property>
+ <property name="type_hint">dialog</property>
+ <child internal-child="vbox">
+ <object class="GtkVBox" id="dialog-vbox4">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">8</property>
+ <child>
+ <object class="GtkVBox" id="vbox24">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkHBox" id="hbox13">
+ <property name="visible">True</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkFrame" id="frame-group-1">
+ <property name="visible">True</property>
+ <property name="label_xalign">0</property>
+ <child>
+ <object class="GtkHBox" id="hbox5">
+ <property name="visible">True</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkVBox" id="vbox7">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkCheckButton" id="checkbutton-group-1">
+ <property name="label" translatable="yes">_Show field in View</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox8">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkRadioButton" id="radiobutton-ascending-group-1">
+ <property name="label" translatable="yes">Ascending</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="radiobutton-descending-group-1">
+ <property name="label" translatable="yes">Descending</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ <property name="group">radiobutton-ascending-group-1</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label1">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Group Items By</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label8">
+ <property name="visible">True</property>
+ <property name="justify">center</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label9">
+ <property name="visible">True</property>
+ <property name="justify">center</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label10">
+ <property name="visible">True</property>
+ <property name="justify">center</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox14">
+ <property name="visible">True</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label11">
+ <property name="visible">True</property>
+ <property name="justify">center</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame" id="frame-group-2">
+ <property name="visible">True</property>
+ <property name="label_xalign">0</property>
+ <child>
+ <object class="GtkHBox" id="hbox6">
+ <property name="visible">True</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkVBox" id="vbox9">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkCheckButton" id="checkbutton-group-2">
+ <property name="label" translatable="yes">Show _field in View</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox10">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkRadioButton" id="radiobutton-ascending-group-2">
+ <property name="label" translatable="yes">Ascending</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="radiobutton-descending-group-2">
+ <property name="label" translatable="yes">Descending</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ <property name="group">radiobutton-ascending-group-2</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label12">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Then By</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label19">
+ <property name="visible">True</property>
+ <property name="justify">center</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label18">
+ <property name="visible">True</property>
+ <property name="justify">center</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox15">
+ <property name="visible">True</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label13">
+ <property name="visible">True</property>
+ <property name="justify">center</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label32">
+ <property name="visible">True</property>
+ <property name="justify">center</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame" id="frame-group-3">
+ <property name="visible">True</property>
+ <property name="label_xalign">0</property>
+ <child>
+ <object class="GtkHBox" id="hbox7">
+ <property name="visible">True</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkVBox" id="vbox11">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkCheckButton" id="checkbutton-group-3">
+ <property name="label" translatable="yes">Show field i_n View</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox12">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkRadioButton" id="radiobutton-ascending-group-3">
+ <property name="label" translatable="yes">Ascending</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="radiobutton-descending-group-3">
+ <property name="label" translatable="yes">Descending</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ <property name="group">radiobutton-ascending-group-3</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label20">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Then By</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label17">
+ <property name="visible">True</property>
+ <property name="justify">center</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox16">
+ <property name="visible">True</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label14">
+ <property name="visible">True</property>
+ <property name="justify">center</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label16">
+ <property name="visible">True</property>
+ <property name="justify">center</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label15">
+ <property name="visible">True</property>
+ <property name="justify">center</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame" id="frame-group-4">
+ <property name="visible">True</property>
+ <property name="label_xalign">0</property>
+ <child>
+ <object class="GtkHBox" id="hbox8">
+ <property name="visible">True</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkVBox" id="vbox13">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkCheckButton" id="checkbutton-group-4">
+ <property name="label" translatable="yes">Show field in _View</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox14">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkRadioButton" id="radiobutton-ascending-group-4">
+ <property name="label" translatable="yes">Ascending</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="radiobutton-descending-group-4">
+ <property name="label" translatable="yes">Descending</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ <property name="group">radiobutton-ascending-group-4</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label21">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Then By</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child internal-child="action_area">
+ <object class="GtkHButtonBox" id="dialog-action_area4">
+ <property name="visible">True</property>
+ <property name="layout_style">end</property>
+ <child>
+ <object class="GtkButton" id="button39">
+ <property name="label" translatable="yes">Clear _All</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button42">
+ <property name="label">gtk-cancel</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_stock">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button41">
+ <property name="label">gtk-ok</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_stock">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="pack_type">end</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="0">button39</action-widget>
+ <action-widget response="-6">button42</action-widget>
+ <action-widget response="-5">button41</action-widget>
+ </action-widgets>
+ </object>
+ <object class="GtkDialog" id="dialog-sort">
+ <property name="title" translatable="yes">Sort</property>
+ <property name="modal">True</property>
+ <property name="type_hint">dialog</property>
+ <child internal-child="vbox">
+ <object class="GtkVBox" id="vbox15">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">8</property>
+ <child>
+ <object class="GtkTable" id="table5">
+ <property name="visible">True</property>
+ <property name="n_rows">4</property>
+ <property name="column_spacing">6</property>
+ <property name="row_spacing">6</property>
+ <child>
+ <object class="GtkFrame" id="frame-sort-4">
+ <property name="visible">True</property>
+ <property name="label_xalign">0</property>
+ <child>
+ <object class="GtkHBox" id="hbox9">
+ <property name="visible">True</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkAlignment" id="alignment1">
+ <property name="visible">True</property>
+ <property name="yscale">0</property>
+ </object>
+ <packing>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox17">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkRadioButton" id="radiobutton-ascending-sort-4">
+ <property name="label" translatable="yes">Ascending</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="radiobutton-descending-sort-4">
+ <property name="label" translatable="yes">Descending</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ <property name="group">radiobutton-ascending-sort-4</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label22">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Then By</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ <property name="y_options">GTK_FILL</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame" id="frame-sort-3">
+ <property name="visible">True</property>
+ <property name="label_xalign">0</property>
+ <child>
+ <object class="GtkHBox" id="hbox10">
+ <property name="visible">True</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkAlignment" id="alignment2">
+ <property name="visible">True</property>
+ <property name="yscale">0</property>
+ </object>
+ <packing>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox19">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkRadioButton" id="radiobutton-ascending-sort-3">
+ <property name="label" translatable="yes">Ascending</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="radiobutton-descending-sort-3">
+ <property name="label" translatable="yes">Descending</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ <property name="group">radiobutton-ascending-sort-3</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label23">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Then By</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="y_options">GTK_FILL</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame" id="frame-sort-2">
+ <property name="visible">True</property>
+ <property name="label_xalign">0</property>
+ <child>
+ <object class="GtkHBox" id="hbox11">
+ <property name="visible">True</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkAlignment" id="alignment3">
+ <property name="visible">True</property>
+ <property name="yscale">0</property>
+ </object>
+ <packing>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox21">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkRadioButton" id="radiobutton-ascending-sort-2">
+ <property name="label" translatable="yes">Ascending</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="radiobutton-descending-sort-2">
+ <property name="label" translatable="yes">Descending</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ <property name="group">radiobutton-ascending-sort-2</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label24">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Then By</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="y_options">GTK_FILL</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame" id="frame-sort-1">
+ <property name="visible">True</property>
+ <property name="label_xalign">0</property>
+ <child>
+ <object class="GtkHBox" id="hbox12">
+ <property name="visible">True</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkAlignment" id="alignment4">
+ <property name="visible">True</property>
+ <property name="yscale">0</property>
+ </object>
+ <packing>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox23">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkRadioButton" id="radiobutton-ascending-sort-1">
+ <property name="label" translatable="yes">Ascending</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="radiobutton-descending-sort-1">
+ <property name="label" translatable="yes">Descending</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ <property name="group">radiobutton-ascending-sort-1</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label25">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Sort Items By</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="y_options">GTK_FILL</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child internal-child="action_area">
+ <object class="GtkHButtonBox" id="hbuttonbox1">
+ <property name="visible">True</property>
+ <property name="layout_style">end</property>
+ <child>
+ <object class="GtkButton" id="button43">
+ <property name="label" translatable="yes">Clear All</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button45">
+ <property name="label">gtk-cancel</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_stock">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button44">
+ <property name="label">gtk-ok</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="has_default">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_stock">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="pack_type">end</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="0">button43</action-widget>
+ <action-widget response="-6">button45</action-widget>
+ <action-widget response="-5">button44</action-widget>
+ </action-widgets>
+ </object>
+ <object class="GtkDialog" id="e-table-config">
+ <property name="title">dialog1</property>
+ <property name="resizable">False</property>
+ <property name="modal">True</property>
+ <property name="window_position">center-on-parent</property>
+ <property name="type_hint">dialog</property>
+ <child internal-child="vbox">
+ <object class="GtkVBox" id="dialog-vbox6">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkFrame" id="top-frame">
+ <property name="visible">True</property>
+ <property name="border_width">2</property>
+ <property name="label_xalign">0</property>
+ <child>
+ <object class="GtkTable" id="table1">
+ <property name="visible">True</property>
+ <property name="border_width">2</property>
+ <property name="n_rows">3</property>
+ <property name="n_columns">3</property>
+ <property name="column_spacing">4</property>
+ <property name="row_spacing">2</property>
+ <child>
+ <object class="GtkButton" id="button-sort">
+ <property name="label" translatable="yes">_Sort...</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <signal name="clicked" handler="on_sort_clicked"/>
+ </object>
+ <packing>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button-group">
+ <property name="label" translatable="yes">_Group By...</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <signal name="clicked" handler="on_group_by_clicked"/>
+ </object>
+ <packing>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label-sort">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="wrap">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="y_options">GTK_FILL</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button-fields">
+ <property name="label" translatable="yes">_Fields Shown...</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <signal name="clicked" handler="on_group_by_clicked"/>
+ </object>
+ <packing>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label-fields">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="wrap">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="y_options">GTK_FILL</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label3">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="justify">center</property>
+ </object>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="right_attach">3</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options">GTK_FILL</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label4">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="justify">center</property>
+ </object>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="right_attach">3</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options">GTK_FILL</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label33">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="justify">center</property>
+ </object>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="right_attach">3</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options">GTK_FILL</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label-group">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="wrap">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label26">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Description</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child internal-child="action_area">
+ <object class="GtkHButtonBox" id="dialog-action_area6">
+ <property name="visible">True</property>
+ <property name="layout_style">end</property>
+ <child>
+ <object class="GtkButton" id="cancelbutton2">
+ <property name="label">gtk-cancel</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_stock">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="applybutton2">
+ <property name="label">gtk-apply</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_stock">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="okbutton2">
+ <property name="label">gtk-ok</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_stock">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="pack_type">end</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="-6">cancelbutton2</action-widget>
+ <action-widget response="-10">applybutton2</action-widget>
+ <action-widget response="-5">okbutton2</action-widget>
+ </action-widgets>
+ </object>
+</interface>
diff --git a/e-util/e-table-defines.h b/e-util/e-table-defines.h
new file mode 100644
index 0000000000..0575f1cea7
--- /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 0000000000..1820f35451
--- /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 0000000000..93acc4cea0
--- /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 0000000000..4c643089a1
--- /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 0000000000..15be375c53
--- /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 0000000000..f72e059f20
--- /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 0000000000..08bfeb6729
--- /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 0000000000..c402edb7fe
--- /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 0000000000..567b9afa5c
--- /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 0000000000..5741cd1093
--- /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 0000000000..3f6fb03b7a
--- /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 0000000000..8d1a91da69
--- /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 0000000000..93aa2bf2da
--- /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 0000000000..b119b06982
--- /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 0000000000..7e9e905753
--- /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 0000000000..103ed3a807
--- /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 0000000000..1cd0c717ab
--- /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 0000000000..d3ee1aca38
--- /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 0000000000..3022681caa
--- /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 0000000000..d06b26e147
--- /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 0000000000..298131eeed
--- /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 0000000000..de749ead68
--- /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 0000000000..09fdab90cc
--- /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 0000000000..a3f919b981
--- /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 0000000000..a71cac1d91
--- /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 0000000000..066d319122
--- /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 0000000000..c8167f8608
--- /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 0000000000..b9a7eb94e7
--- /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 0000000000..8762027eaf
--- /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 0000000000..1ae4d3e81b
--- /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 0000000000..3ed188e11c
--- /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 0000000000..db9c27e4d1
--- /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 0000000000..86f5538ffb
--- /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 0000000000..5b6a7bd8d6
--- /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 0000000000..1348e6487f
--- /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 0000000000..abe4b0c3ff
--- /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 0000000000..0f955ad4bb
--- /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 0000000000..d2654c55b4
--- /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 0000000000..c56c5b07f5
--- /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 0000000000..17c10d5328
--- /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 0000000000..60861e527a
--- /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 0000000000..3f548d349b
--- /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 0000000000..c9f4b65482
--- /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 0000000000..5fdc077503
--- /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 0000000000..9615a9b17f
--- /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 0000000000..23303ea418
--- /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 0000000000..2d5ccb4363
--- /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 0000000000..03cb429131
--- /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 0000000000..8ed43aed73
--- /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 0000000000..e5253be7c9
--- /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 0000000000..ac3cfc2879
--- /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 0000000000..8d9f3d0c8d
--- /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 0000000000..ca4adddd18
--- /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 0000000000..88532d03bd
--- /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 0000000000..9e8d69496d
--- /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 0000000000..b914e595b4
--- /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 0000000000..1b7b144ce5
--- /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 0000000000..7139ad15df
--- /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 0000000000..0853c54cb5
--- /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 0000000000..4dc90bebf1
--- /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 0000000000..8370e440df
--- /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 2a42ae939c..c734cf84d4 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 0b9c6c143c..5a8890d519 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 d7d0bb3854..cf7da4f5aa 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 a5da7810dd..7988bd6973 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 cf14ebb286..203e2de236 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/e-util/e-text-model-repos.c b/e-util/e-text-model-repos.c
new file mode 100644
index 0000000000..b56a213215
--- /dev/null
+++ b/e-util/e-text-model-repos.c
@@ -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:
+ * Jon Trowbridge <trow@ximian.com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-text-model-repos.h"
+
+#define MODEL_CLAMP(model, pos) (CLAMP((pos), 0, strlen((model)->text)))
+
+gint
+e_repos_absolute (gint pos,
+ gpointer data)
+{
+ EReposAbsolute *info = (EReposAbsolute *) data;
+ g_return_val_if_fail (data, -1);
+
+ pos = info->pos;
+ if (pos < 0) {
+ gint len = e_text_model_get_text_length (info->model);
+ pos += len + 1;
+ }
+
+ return e_text_model_validate_position (info->model, pos);
+}
+
+gint
+e_repos_insert_shift (gint pos,
+ gpointer data)
+{
+ EReposInsertShift *info = (EReposInsertShift *) data;
+ g_return_val_if_fail (data, -1);
+
+ if (pos >= info->pos)
+ pos += info->len;
+
+ return e_text_model_validate_position (info->model, pos);
+}
+
+gint
+e_repos_delete_shift (gint pos,
+ gpointer data)
+{
+ EReposDeleteShift *info = (EReposDeleteShift *) data;
+ g_return_val_if_fail (data, -1);
+
+ if (pos > info->pos + info->len)
+ pos -= info->len;
+ else if (pos > info->pos)
+ pos = info->pos;
+
+ return e_text_model_validate_position (info->model, pos);
+}
diff --git a/e-util/e-text-model-repos.h b/e-util/e-text-model-repos.h
new file mode 100644
index 0000000000..1450c02715
--- /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 0000000000..ab6bff8ff3
--- /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 0000000000..3426c183e2
--- /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 0000000000..d23decad86
--- /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 0000000000..40e92bf585
--- /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 0000000000..431287c2df
--- /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 0000000000..df87e80941
--- /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/e-util/e-timezone-dialog.ui b/e-util/e-timezone-dialog.ui
new file mode 100644
index 0000000000..5a23ec180e
--- /dev/null
+++ b/e-util/e-timezone-dialog.ui
@@ -0,0 +1,312 @@
+<?xml version="1.0"?>
+<!--*- mode: xml -*-->
+<interface>
+ <object class="GtkDialog" id="timezone-dialog">
+ <property name="title" translatable="yes">Select a Time Zone</property>
+ <property name="type">GTK_WINDOW_TOPLEVEL</property>
+ <property name="window_position">GTK_WIN_POS_NONE</property>
+ <property name="modal">False</property>
+ <property name="default_width">500</property>
+ <property name="default_height">400</property>
+ <property name="resizable">True</property>
+ <property name="destroy_with_parent">False</property>
+ <property name="decorated">True</property>
+ <property name="skip_taskbar_hint">False</property>
+ <property name="skip_pager_hint">False</property>
+ <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
+ <property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
+ <child internal-child="vbox">
+ <object class="GtkBox" id="dialog-vbox1">
+ <property name="orientation">vertical</property>
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">6</property>
+ <child internal-child="action_area">
+ <object class="GtkHButtonBox" id="dialog-action_area1">
+ <property name="visible">True</property>
+ <property name="layout_style">GTK_BUTTONBOX_END</property>
+ <child>
+ <object class="GtkButton" id="cancel-button">
+ <property name="visible">True</property>
+ <property name="can_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="label">gtk-cancel</property>
+ <property name="use_stock">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="ok-button">
+ <property name="visible">True</property>
+ <property name="can_default">True</property>
+ <property name="has_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="label">gtk-ok</property>
+ <property name="use_stock">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="pack_type">GTK_PACK_END</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="timezone-table">
+ <property name="orientation">vertical</property>
+ <property name="border_width">12</property>
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkBox" id="hbox3">
+ <property name="orientation">horizontal</property>
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkImage" id="image1">
+ <property name="visible">True</property>
+ <property name="stock">gtk-dialog-info</property>
+ <property name="icon_size">6</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label1">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Use the left mouse button to zoom in on an area of the map and select a time zone.
+Use the right mouse button to zoom out.</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label4">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Time Zones</property>
+ <property name="use_underline">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="hbox2">
+ <property name="orientation">horizontal</property>
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkLabel" id="label5">
+ <property name="visible">True</property>
+ <property name="label" translatable="no"> </property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="vbox1">
+ <property name="orientation">vertical</property>
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkScrolledWindow" id="map-window">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">GTK_POLICY_ALWAYS</property>
+ <property name="vscrollbar_policy">GTK_POLICY_ALWAYS</property>
+ <property name="shadow_type">GTK_SHADOW_IN</property>
+ <property name="window_placement">GTK_CORNER_TOP_LEFT</property>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="preview-label">
+ <property name="visible">True</property>
+ <property name="label" translatable="no">America/New_York</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label3">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Selection</property>
+ <property name="use_underline">True</property>
+ <property name="justify">GTK_JUSTIFY_CENTER</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="mnemonic_widget">timezone-combo</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="hbox1">
+ <property name="orientation">horizontal</property>
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkLabel" id="label6">
+ <property name="visible">True</property>
+ <property name="label" translatable="no"> </property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="timezone-combo">
+ <property name="visible">True</property>
+ <property name="add_tearoffs">False</property>
+ <property name="focus_on_click">True</property>
+ <accessibility>
+
+ </accessibility>
+ <child internal-child="accessible">
+ <object class="AtkObject" id="a11y-timezone-combo1">
+ <property name="AtkObject::accessible_name" translatable="yes">Timezone drop-down combination box</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="-2">cancel-button</action-widget>
+ <action-widget response="-3">ok-button</action-widget>
+ </action-widgets>
+ </object>
+</interface>
diff --git a/e-util/e-tree-memory-callbacks.c b/e-util/e-tree-memory-callbacks.c
new file mode 100644
index 0000000000..9d2fda6e3e
--- /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 0000000000..df47e9a491
--- /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 0000000000..0af5d27b31
--- /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 0000000000..3e58952ad2
--- /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 0000000000..aff912998c
--- /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 0000000000..e85a1adc12
--- /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 0000000000..db763cf782
--- /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 0000000000..1d02615a45
--- /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 0000000000..480b5a4e8a
--- /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 0000000000..eafa66eba5
--- /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 0000000000..25cfceb337
--- /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 0000000000..b6dacaf9d6
--- /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 0000000000..f76f11b26a
--- /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 0000000000..17f3304aa4
--- /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 0000000000..ee451cd28a
--- /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 0000000000..1d6243cc61
--- /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 3308b500d2..e494d351a6 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 9b1f389d76..4c295888d0 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 c519c2e6e2..2901744f8b 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 0000000000..7752732ccc
--- /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 0000000000..0925287b63
--- /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 5f5ef75587..b2da504fb2 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 fa98153223..a5ab42bd3b 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 0000000000..7303277f6a
--- /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 0000000000..ebe965c61b
--- /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/e-util/e-web-view-preview.c b/e-util/e-web-view-preview.c
new file mode 100644
index 0000000000..b75814fa83
--- /dev/null
+++ b/e-util/e-web-view-preview.c
@@ -0,0 +1,474 @@
+/*
+ * e-web-view-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/>
+ *
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-web-view-preview.h"
+
+#include <string.h>
+#include <glib/gi18n-lib.h>
+
+#define E_WEB_VIEW_PREVIEW_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_WEB_VIEW_PREVIEW, EWebViewPreviewPrivate))
+
+struct _EWebViewPreviewPrivate {
+ gboolean escape_values;
+ GString *updating_content; /* is NULL when not between begin_update/end_update */
+};
+
+enum {
+ PROP_0,
+ PROP_TREE_VIEW,
+ PROP_PREVIEW_WIDGET,
+ PROP_ESCAPE_VALUES
+};
+
+G_DEFINE_TYPE (
+ EWebViewPreview,
+ e_web_view_preview,
+ GTK_TYPE_VPANED);
+
+static void
+web_view_preview_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_ESCAPE_VALUES:
+ e_web_view_preview_set_escape_values (
+ E_WEB_VIEW_PREVIEW (object),
+ g_value_get_boolean (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+web_view_preview_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_TREE_VIEW:
+ g_value_set_object (
+ value, e_web_view_preview_get_tree_view (
+ E_WEB_VIEW_PREVIEW (object)));
+ return;
+
+ case PROP_PREVIEW_WIDGET:
+ g_value_set_object (
+ value, e_web_view_preview_get_preview (
+ E_WEB_VIEW_PREVIEW (object)));
+ return;
+
+ case PROP_ESCAPE_VALUES:
+ g_value_set_boolean (
+ value, e_web_view_preview_get_escape_values (
+ E_WEB_VIEW_PREVIEW (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+web_view_preview_dispose (GObject *object)
+{
+ EWebViewPreviewPrivate *priv;
+
+ priv = E_WEB_VIEW_PREVIEW_GET_PRIVATE (object);
+
+ if (priv->updating_content != NULL) {
+ g_string_free (priv->updating_content, TRUE);
+ priv->updating_content = NULL;
+ }
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (e_web_view_preview_parent_class)->dispose (object);
+}
+
+static void
+e_web_view_preview_class_init (EWebViewPreviewClass *class)
+{
+ GObjectClass *object_class;
+
+ g_type_class_add_private (class, sizeof (EWebViewPreviewPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = web_view_preview_set_property;
+ object_class->get_property = web_view_preview_get_property;
+ object_class->dispose = web_view_preview_dispose;
+
+ g_object_class_install_property (
+ object_class,
+ PROP_TREE_VIEW,
+ g_param_spec_object (
+ "tree-view",
+ "Tree View",
+ NULL,
+ GTK_TYPE_TREE_VIEW,
+ G_PARAM_READABLE));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_PREVIEW_WIDGET,
+ g_param_spec_object (
+ "preview-widget",
+ "Preview Widget",
+ NULL,
+ GTK_TYPE_WIDGET,
+ G_PARAM_READABLE));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_ESCAPE_VALUES,
+ g_param_spec_boolean (
+ "escape-values",
+ "Whether escaping values automatically, when inserting",
+ NULL,
+ TRUE,
+ G_PARAM_READWRITE));
+}
+
+static GtkWidget *
+in_scrolled_window (GtkWidget *widget)
+{
+ GtkWidget *sw;
+
+ g_return_val_if_fail (widget != NULL, NULL);
+
+ sw = gtk_scrolled_window_new (NULL, NULL);
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
+ gtk_container_add (GTK_CONTAINER (sw), widget);
+
+ gtk_widget_show (widget);
+ gtk_widget_show (sw);
+
+ return sw;
+}
+
+static void
+e_web_view_preview_init (EWebViewPreview *preview)
+{
+ GtkWidget *tree_view_sw, *web_view_sw;
+
+ preview->priv = E_WEB_VIEW_PREVIEW_GET_PRIVATE (preview);
+ preview->priv->escape_values = TRUE;
+
+ tree_view_sw = in_scrolled_window (gtk_tree_view_new ());
+ web_view_sw = in_scrolled_window (e_web_view_new ());
+
+ gtk_widget_hide (tree_view_sw);
+ gtk_widget_show (web_view_sw);
+
+ gtk_paned_pack1 (GTK_PANED (preview), tree_view_sw, FALSE, TRUE);
+ gtk_paned_pack2 (GTK_PANED (preview), web_view_sw, TRUE, TRUE);
+
+ /* rawly 3 lines of a text plus a little bit more */
+ if (gtk_paned_get_position (GTK_PANED (preview)) < 85)
+ gtk_paned_set_position (GTK_PANED (preview), 85);
+}
+
+GtkWidget *
+e_web_view_preview_new (void)
+{
+ return g_object_new (E_TYPE_WEB_VIEW_PREVIEW, NULL);
+}
+
+GtkTreeView *
+e_web_view_preview_get_tree_view (EWebViewPreview *preview)
+{
+ g_return_val_if_fail (preview != NULL, NULL);
+ g_return_val_if_fail (E_IS_WEB_VIEW_PREVIEW (preview), NULL);
+
+ return GTK_TREE_VIEW (gtk_bin_get_child (GTK_BIN (gtk_paned_get_child1 (GTK_PANED (preview)))));
+}
+
+GtkWidget *
+e_web_view_preview_get_preview (EWebViewPreview *preview)
+{
+ g_return_val_if_fail (preview != NULL, NULL);
+ g_return_val_if_fail (E_IS_WEB_VIEW_PREVIEW (preview), NULL);
+
+ return gtk_bin_get_child (GTK_BIN (gtk_paned_get_child2 (GTK_PANED (preview))));
+}
+
+void
+e_web_view_preview_set_preview (EWebViewPreview *preview,
+ GtkWidget *preview_widget)
+{
+ GtkWidget *old_child;
+
+ g_return_if_fail (E_IS_WEB_VIEW_PREVIEW (preview));
+ g_return_if_fail (GTK_IS_WIDGET (preview_widget));
+
+ old_child = gtk_bin_get_child (GTK_BIN (gtk_paned_get_child2 (GTK_PANED (preview))));
+ if (old_child) {
+ g_return_if_fail (old_child != preview_widget);
+ gtk_widget_destroy (old_child);
+ }
+
+ gtk_container_add (GTK_CONTAINER (gtk_paned_get_child2 (GTK_PANED (preview))), preview_widget);
+}
+
+void
+e_web_view_preview_show_tree_view (EWebViewPreview *preview)
+{
+ g_return_if_fail (E_IS_WEB_VIEW_PREVIEW (preview));
+
+ gtk_widget_show (gtk_paned_get_child1 (GTK_PANED (preview)));
+}
+
+void
+e_web_view_preview_hide_tree_view (EWebViewPreview *preview)
+{
+ g_return_if_fail (E_IS_WEB_VIEW_PREVIEW (preview));
+
+ gtk_widget_hide (gtk_paned_get_child1 (GTK_PANED (preview)));
+}
+
+void
+e_web_view_preview_set_escape_values (EWebViewPreview *preview,
+ gboolean escape)
+{
+ g_return_if_fail (E_IS_WEB_VIEW_PREVIEW (preview));
+
+ preview->priv->escape_values = escape;
+}
+
+gboolean
+e_web_view_preview_get_escape_values (EWebViewPreview *preview)
+{
+ g_return_val_if_fail (preview != NULL, FALSE);
+ g_return_val_if_fail (E_IS_WEB_VIEW_PREVIEW (preview), FALSE);
+ g_return_val_if_fail (preview->priv != NULL, FALSE);
+
+ return preview->priv->escape_values;
+}
+
+void
+e_web_view_preview_begin_update (EWebViewPreview *preview)
+{
+ g_return_if_fail (E_IS_WEB_VIEW_PREVIEW (preview));
+
+ if (preview->priv->updating_content) {
+ g_warning ("%s: Previous content update isn't finished with e_web_view_preview_end_update()", G_STRFUNC);
+ g_string_free (preview->priv->updating_content, TRUE);
+ }
+
+ preview->priv->updating_content = g_string_new ("<TABLE width=\"100%\" border=\"0\" cols=\"2\">");
+}
+
+void
+e_web_view_preview_end_update (EWebViewPreview *preview)
+{
+ GtkWidget *web_view;
+
+ g_return_if_fail (E_IS_WEB_VIEW_PREVIEW (preview));
+ g_return_if_fail (preview->priv->updating_content != NULL);
+
+ g_string_append (preview->priv->updating_content, "</TABLE>");
+
+ web_view = e_web_view_preview_get_preview (preview);
+ if (E_IS_WEB_VIEW (web_view))
+ e_web_view_load_string (E_WEB_VIEW (web_view), preview->priv->updating_content->str);
+
+ g_string_free (preview->priv->updating_content, TRUE);
+ preview->priv->updating_content = NULL;
+}
+
+static gchar *
+replace_string (const gchar *text,
+ const gchar *find,
+ const gchar *replace)
+{
+ const gchar *p, *next;
+ GString *str;
+ gint find_len;
+
+ g_return_val_if_fail (text != NULL, NULL);
+ g_return_val_if_fail (find != NULL, NULL);
+ g_return_val_if_fail (*find, NULL);
+
+ find_len = strlen (find);
+ str = g_string_new ("");
+
+ p = text;
+ while (next = strstr (p, find), next) {
+ if (p + 1 < next)
+ g_string_append_len (str, p, next - p);
+
+ if (replace && *replace)
+ g_string_append (str, replace);
+
+ p = next + find_len;
+ }
+
+ g_string_append (str, p);
+
+ return g_string_free (str, FALSE);
+}
+
+static gchar *
+web_view_preview_escape_text (EWebViewPreview *preview,
+ const gchar *text)
+{
+ gchar *utf8_valid, *res, *end;
+
+ if (!e_web_view_preview_get_escape_values (preview))
+ return NULL;
+
+ g_return_val_if_fail (text != NULL, NULL);
+
+ if (g_utf8_validate (text, -1, NULL)) {
+ res = g_markup_escape_text (text, -1);
+ } else {
+ utf8_valid = g_strdup (text);
+ while (end = NULL, !g_utf8_validate (utf8_valid, -1, (const gchar **) &end) && end && *end)
+ *end = '?';
+
+ res = g_markup_escape_text (utf8_valid, -1);
+
+ g_free (utf8_valid);
+ }
+
+ if (res && strchr (res, '\n')) {
+ /* replace line breaks with <BR> */
+ if (strchr (res, '\r')) {
+ end = replace_string (res, "\r", "");
+ g_free (res);
+ res = end;
+ }
+
+ end = replace_string (res, "\n", "<BR>");
+ g_free (res);
+ res = end;
+ }
+
+ return res;
+}
+
+void
+e_web_view_preview_add_header (EWebViewPreview *preview,
+ gint index,
+ const gchar *header)
+{
+ gchar *escaped;
+
+ g_return_if_fail (E_IS_WEB_VIEW_PREVIEW (preview));
+ g_return_if_fail (preview->priv->updating_content != NULL);
+ g_return_if_fail (header != NULL);
+
+ if (index < 1)
+ index = 1;
+ else if (index > 6)
+ index = 6;
+
+ escaped = web_view_preview_escape_text (preview, header);
+ if (escaped)
+ header = escaped;
+
+ g_string_append_printf (preview->priv->updating_content, "<TR><TD colspan=2><H%d>%s</H%d></TD></TR>", index, header, index);
+
+ g_free (escaped);
+}
+
+void
+e_web_view_preview_add_text (EWebViewPreview *preview,
+ const gchar *text)
+{
+ gchar *escaped;
+
+ g_return_if_fail (E_IS_WEB_VIEW_PREVIEW (preview));
+ g_return_if_fail (preview->priv->updating_content != NULL);
+ g_return_if_fail (text != NULL);
+
+ escaped = web_view_preview_escape_text (preview, text);
+ if (escaped)
+ text = escaped;
+
+ g_string_append_printf (preview->priv->updating_content, "<TR><TD colspan=2><FONT size=\"3\">%s</FONT></TD></TR>", text);
+
+ g_free (escaped);
+}
+
+void
+e_web_view_preview_add_raw_html (EWebViewPreview *preview,
+ const gchar *raw_html)
+{
+ g_return_if_fail (E_IS_WEB_VIEW_PREVIEW (preview));
+ g_return_if_fail (preview->priv->updating_content != NULL);
+ g_return_if_fail (raw_html != NULL);
+
+ g_string_append_printf (preview->priv->updating_content, "<TR><TD colspan=2>%s</TD></TR>", raw_html);
+}
+
+void
+e_web_view_preview_add_separator (EWebViewPreview *preview)
+{
+ g_return_if_fail (E_IS_WEB_VIEW_PREVIEW (preview));
+ g_return_if_fail (preview->priv->updating_content != NULL);
+
+ g_string_append (preview->priv->updating_content, "<TR><TD colspan=2><HR></TD></TR>");
+}
+
+void
+e_web_view_preview_add_empty_line (EWebViewPreview *preview)
+{
+ g_return_if_fail (E_IS_WEB_VIEW_PREVIEW (preview));
+ g_return_if_fail (preview->priv->updating_content != NULL);
+
+ g_string_append (preview->priv->updating_content, "<TR><TD colspan=2>&nbsp;</TD></TR>");
+}
+
+/* section can be NULL, but value cannot */
+void
+e_web_view_preview_add_section (EWebViewPreview *preview,
+ const gchar *section,
+ const gchar *value)
+{
+ gchar *escaped_section = NULL, *escaped_value;
+
+ g_return_if_fail (E_IS_WEB_VIEW_PREVIEW (preview));
+ g_return_if_fail (preview->priv->updating_content != NULL);
+ g_return_if_fail (value != NULL);
+
+ if (section) {
+ escaped_section = web_view_preview_escape_text (preview, section);
+ if (escaped_section)
+ section = escaped_section;
+ }
+
+ escaped_value = web_view_preview_escape_text (preview, value);
+ if (escaped_value)
+ value = escaped_value;
+
+ g_string_append_printf (preview->priv->updating_content, "<TR><TD width=\"10%%\" valign=\"top\" nowrap><FONT size=\"3\"><B>%s</B></FONT></TD><TD width=\"90%%\"><FONT size=\"3\">%s</FONT></TD></TR>", section ? section : "", value);
+
+ g_free (escaped_section);
+ g_free (escaped_value);
+}
diff --git a/e-util/e-web-view-preview.h b/e-util/e-web-view-preview.h
new file mode 100644
index 0000000000..54963b6fe6
--- /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 0000000000..2fefe4fa95
--- /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 0000000000..6690725b86
--- /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 0000000000..aaa66b6010
--- /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/e-util/e-xml-utils.h b/e-util/e-xml-utils.h
new file mode 100644
index 0000000000..9796569774
--- /dev/null
+++ b/e-util/e-xml-utils.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)
+ *
+ */
+
+#ifndef __E_XML_UTILS__
+#define __E_XML_UTILS__
+
+#include <glib.h>
+
+#include <libxml/tree.h>
+
+G_BEGIN_DECLS
+
+/* lang set to NULL means use the current locale. */
+xmlNode *e_xml_get_child_by_name_by_lang (const xmlNode *parent,
+ const xmlChar *child_name,
+ const gchar *lang);
+/* lang_list set to NULL means use the current locale. */
+xmlNode *e_xml_get_child_by_name_by_lang_list (const xmlNode *parent,
+ const gchar *name,
+ const GList *lang_list);
+xmlNode *e_xml_get_child_by_name_no_lang (const xmlNode *parent,
+ const gchar *name);
+
+gint e_xml_get_integer_prop_by_name (const xmlNode *parent,
+ const xmlChar *prop_name);
+gint e_xml_get_integer_prop_by_name_with_default (const xmlNode *parent,
+ const xmlChar *prop_name,
+ gint def);
+void e_xml_set_integer_prop_by_name (xmlNode *parent,
+ const xmlChar *prop_name,
+ gint value);
+
+guint e_xml_get_uint_prop_by_name (const xmlNode *parent,
+ const xmlChar *prop_name);
+guint e_xml_get_uint_prop_by_name_with_default (const xmlNode *parent,
+ const xmlChar *prop_name,
+ guint def);
+void e_xml_set_uint_prop_by_name (xmlNode *parent,
+ const xmlChar *prop_name,
+ guint value);
+
+gboolean e_xml_get_bool_prop_by_name (const xmlNode *parent,
+ const xmlChar *prop_name);
+gboolean e_xml_get_bool_prop_by_name_with_default (const xmlNode *parent,
+ const xmlChar *prop_name,
+ gboolean def);
+void e_xml_set_bool_prop_by_name (xmlNode *parent,
+ const xmlChar *prop_name,
+ gboolean value);
+
+gdouble e_xml_get_double_prop_by_name (const xmlNode *parent,
+ const xmlChar *prop_name);
+gdouble e_xml_get_double_prop_by_name_with_default (const xmlNode *parent,
+ const xmlChar *prop_name,
+ gdouble def);
+void e_xml_set_double_prop_by_name (xmlNode *parent,
+ const xmlChar *prop_name,
+ gdouble value);
+
+gchar *e_xml_get_string_prop_by_name (const xmlNode *parent,
+ const xmlChar *prop_name);
+gchar *e_xml_get_string_prop_by_name_with_default (const xmlNode *parent,
+ const xmlChar *prop_name,
+ const gchar *def);
+void e_xml_set_string_prop_by_name (xmlNode *parent,
+ const xmlChar *prop_name,
+ const gchar *value);
+
+gchar *e_xml_get_translated_string_prop_by_name (const xmlNode *parent,
+ const xmlChar *prop_name);
+
+G_END_DECLS
+
+#endif /* __E_XML_UTILS__ */
diff --git a/e-util/ea-calendar-cell.c b/e-util/ea-calendar-cell.c
new file mode 100644
index 0000000000..a9784df31a
--- /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 0000000000..2d228c2a74
--- /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 0000000000..2f5ac91d5b
--- /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 0000000000..db2e342020
--- /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/e-util/ea-cell-table.c b/e-util/ea-cell-table.c
new file mode 100644
index 0000000000..bbdef0aea1
--- /dev/null
+++ b/e-util/ea-cell-table.c
@@ -0,0 +1,215 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You 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-cell-table.h"
+
+EaCellTable *
+ea_cell_table_create (gint rows,
+ gint columns,
+ gboolean column_first)
+{
+ EaCellTable *cell_data;
+ gint index;
+
+ g_return_val_if_fail (((columns > 0) && (rows > 0)), NULL);
+
+ cell_data = g_new0 (EaCellTable, 1);
+
+ cell_data->column_first = column_first;
+ cell_data->columns = columns;
+ cell_data->rows = rows;
+
+ cell_data->column_labels = g_new0 (gchar *, columns);
+ for (index = columns -1; index >= 0; --index)
+ cell_data->column_labels[index] = NULL;
+
+ cell_data->row_labels = g_new0 (gchar *, rows);
+ for (index = rows -1; index >= 0; --index)
+ cell_data->row_labels[index] = NULL;
+
+ cell_data->cells = g_new0 (gpointer, (columns * rows));
+ for (index = (columns * rows) -1; index >= 0; --index)
+ cell_data->cells[index] = NULL;
+ return cell_data;
+}
+
+void
+ea_cell_table_destroy (EaCellTable *cell_data)
+{
+ gint index;
+ g_return_if_fail (cell_data);
+
+ for (index = 0; index < cell_data->columns; ++index)
+ if (cell_data->column_labels[index])
+ g_free (cell_data->column_labels[index]);
+ g_free (cell_data->column_labels);
+
+ for (index = 0; index < cell_data->rows; ++index)
+ if (cell_data->row_labels[index])
+ g_free (cell_data->row_labels[index]);
+ g_free (cell_data->row_labels);
+
+ for (index = (cell_data->columns * cell_data->rows) -1;
+ index >= 0; --index)
+ if (cell_data->cells[index] &&
+ G_IS_OBJECT (cell_data->cells[index]))
+ g_object_unref (cell_data->cells[index]);
+
+ g_free (cell_data->cells);
+}
+
+gpointer
+ea_cell_table_get_cell (EaCellTable *cell_data,
+ gint row,
+ gint column)
+{
+ gint index;
+
+ g_return_val_if_fail (cell_data, NULL);
+
+ index = ea_cell_table_get_index (cell_data, column, row);
+ if (index == -1)
+ return NULL;
+
+ return cell_data->cells[index];
+}
+
+gboolean
+ea_cell_table_set_cell (EaCellTable *cell_data,
+ gint row,
+ gint column,
+ gpointer cell)
+{
+ gint index;
+
+ g_return_val_if_fail (cell_data, FALSE);
+
+ index = ea_cell_table_get_index (cell_data, column, row);
+ if (index == -1)
+ return FALSE;
+
+ if (cell && G_IS_OBJECT (cell))
+ g_object_ref (cell);
+ if (cell_data->cells[index] &&
+ G_IS_OBJECT (cell_data->cells[index]))
+ g_object_unref (cell_data->cells[index]);
+ cell_data->cells[index] = cell;
+
+ return TRUE;
+}
+
+gpointer
+ea_cell_table_get_cell_at_index (EaCellTable *cell_data,
+ gint index)
+{
+ g_return_val_if_fail (cell_data, NULL);
+
+ if (index >=0 && index < (cell_data->columns * cell_data->rows))
+ return cell_data->cells[index];
+ return NULL;
+}
+
+gboolean
+ea_cell_table_set_cell_at_index (EaCellTable *cell_data,
+ gint index,
+ gpointer cell)
+{
+ g_return_val_if_fail (cell_data, FALSE);
+
+ if (index < 0 || index >=cell_data->columns * cell_data->rows)
+ return FALSE;
+
+ if (cell && G_IS_OBJECT (cell))
+ g_object_ref (cell);
+ if (cell_data->cells[index] &&
+ G_IS_OBJECT (cell_data->cells[index]))
+ g_object_unref (cell_data->cells[index]);
+ cell_data->cells[index] = cell;
+
+ return TRUE;
+}
+
+const gchar *
+ea_cell_table_get_column_label (EaCellTable *cell_data,
+ gint column)
+{
+ g_return_val_if_fail (cell_data, NULL);
+ g_return_val_if_fail ((column >= 0 && column < cell_data->columns), NULL);
+
+ return cell_data->column_labels[column];
+}
+
+void
+ea_cell_table_set_column_label (EaCellTable *cell_data,
+ gint column,
+ const gchar *label)
+{
+ g_return_if_fail (cell_data);
+ g_return_if_fail ((column >= 0 && column < cell_data->columns));
+
+ if (cell_data->column_labels[column])
+ g_free (cell_data->column_labels[column]);
+ cell_data->column_labels[column] = g_strdup (label);
+}
+
+const gchar *
+ea_cell_table_get_row_label (EaCellTable *cell_data,
+ gint row)
+{
+ g_return_val_if_fail (cell_data, NULL);
+ g_return_val_if_fail ((row >= 0 && row < cell_data->rows), NULL);
+
+ return cell_data->row_labels[row];
+}
+
+void
+ea_cell_table_set_row_label (EaCellTable *cell_data,
+ gint row,
+ const gchar *label)
+{
+ g_return_if_fail (cell_data);
+ g_return_if_fail ((row >= 0 && row < cell_data->rows));
+
+ if (cell_data->row_labels[row])
+ g_free (cell_data->row_labels[row]);
+ cell_data->row_labels[row] = g_strdup (label);
+}
+
+gint
+ea_cell_table_get_index (EaCellTable *cell_data,
+ gint row,
+ gint column)
+{
+ g_return_val_if_fail (cell_data, -1);
+ if (row < 0 || row >= cell_data->rows ||
+ column < 0 || column >= cell_data->columns)
+ return -1;
+
+ if (cell_data->column_first)
+ return column * cell_data->rows + row;
+ else
+ return row * cell_data->columns + column;
+}
diff --git a/e-util/ea-cell-table.h b/e-util/ea-cell-table.h
new file mode 100644
index 0000000000..3ddd74914c
--- /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 0000000000..c24469721d
--- /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 0000000000..0a65730359
--- /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 0000000000..3fd212ff94
--- /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 0000000000..9f5fb117a5
--- /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/e-util/filter.error.xml b/e-util/filter.error.xml
new file mode 100644
index 0000000000..62b75193d2
--- /dev/null
+++ b/e-util/filter.error.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<error-list domain="filter">
+
+ <error id="no-date" type="error">
+ <_primary>Missing date.</_primary>
+ <_secondary>You must choose a date.</_secondary>
+ </error>
+
+ <error id="no-file" type="error">
+ <_primary>Missing filename.</_primary>
+ <_secondary>You must specify a filename.</_secondary>
+ </error>
+
+ <error id="bad-file" type="error">
+ <_primary>File &quot;{0}&quot; does not exist or is not a regular file.</_primary>
+ <_secondary>You must specify a filename.</_secondary>
+ </error>
+
+ <error id="bad-regexp" type="error">
+ <_primary>Bad regular expression &quot;{0}&quot;.</_primary>
+ <_secondary>Could not compile regular expression &quot;{1}&quot;.</_secondary>
+ </error>
+
+ <error id="no-name" type="error">
+ <_primary>Missing name.</_primary>
+ <_secondary>You must name this filter.</_secondary>
+ </error>
+
+ <error id="bad-name-notunique" type="error">
+ <_primary>Name &quot;{0}&quot; already used.</_primary>
+ <_secondary>Please choose another name.</_secondary>
+ </error>
+
+</error-list>
diff --git a/e-util/filter.ui b/e-util/filter.ui
new file mode 100644
index 0000000000..d91292736d
--- /dev/null
+++ b/e-util/filter.ui
@@ -0,0 +1,591 @@
+<?xml version="1.0"?>
+<interface>
+ <requires lib="gtk+" version="2.16"/>
+ <!-- interface-naming-policy toplevel-contextual -->
+ <object class="GtkAdjustment" id="adjustment1">
+ <property name="value">1</property>
+ <property name="upper">1000</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ </object>
+ <object class="GtkListStore" id="model1">
+ <columns>
+ <!-- column-name gchararray -->
+ <column type="gchararray"/>
+ </columns>
+ <data>
+ <row>
+ <col id="0" translatable="yes">Incoming</col>
+ </row>
+ </data>
+ </object>
+ <object class="GtkListStore" id="model2">
+ <columns>
+ <!-- column-name gchararray -->
+ <column type="gchararray"/>
+ </columns>
+ <data>
+ <row>
+ <col id="0" translatable="yes">the current time</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">the time you specify</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">a time relative to the current time</col>
+ </row>
+ </data>
+ </object>
+ <object class="GtkListStore" id="model3">
+ <columns>
+ <!-- column-name gchararray -->
+ <column type="gchararray"/>
+ </columns>
+ <data>
+ <row>
+ <col id="0" translatable="yes">seconds</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">minutes</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">hours</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">days</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">weeks</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">months</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">years</col>
+ </row>
+ </data>
+ </object>
+ <object class="GtkListStore" id="model4">
+ <columns>
+ <!-- column-name gchararray -->
+ <column type="gchararray"/>
+ </columns>
+ <data>
+ <row>
+ <col id="0" translatable="yes">ago</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">in the future</col>
+ </row>
+ </data>
+ </object>
+ <object class="GtkListStore" id="rule_list_store">
+ <columns>
+ <!-- column-name column1 -->
+ <column type="gchararray"/>
+ <!-- column-name column2 -->
+ <column type="gpointer"/>
+ <!-- column-name column3 -->
+ <column type="gboolean"/>
+ </columns>
+ </object>
+ <object class="GtkVBox" id="rule_editor">
+ <property name="visible">True</property>
+ <property name="border_width">12</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label17">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Show filters for mail:</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="filter_source_combobox">
+ <property name="visible">True</property>
+ <property name="model">model1</property>
+ <child>
+ <object class="GtkCellRendererText" id="renderer1"/>
+ <attributes>
+ <attribute name="text">0</attribute>
+ </attributes>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="rule_frame">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="rule_label">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">_Filter Rules</property>
+ <property name="use_underline">True</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox10">
+ <property name="visible">True</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkLabel" id="label16">
+ <property name="visible">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox4">
+ <property name="visible">True</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkScrolledWindow" id="rule_scrolled_window">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">automatic</property>
+ <property name="vscrollbar_policy">automatic</property>
+ <property name="shadow_type">in</property>
+ <child>
+ <object class="GtkTreeView" id="rule_tree_view">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="model">rule_list_store</property>
+ <property name="headers_visible">False</property>
+ <child>
+ <object class="GtkTreeViewColumn" id="column_enabled">
+ <!--<property name="visible">False</property>-->
+ <property name="title">Enabled</property>
+ <child>
+ <object class="GtkCellRendererToggle" id="cell_renderer_enabled"/>
+ <attributes>
+ <attribute name="active">2</attribute>
+ </attributes>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkTreeViewColumn" id="column_rule_name">
+ <property name="title">Rule Name</property>
+ <child>
+ <object class="GtkCellRendererText" id="cell_renderer_rule_name"/>
+ <attributes>
+ <attribute name="text">0</attribute>
+ </attributes>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox5">
+ <property name="visible">True</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkVButtonBox" id="vbuttonbox4">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkButton" id="rule_add">
+ <property name="label">gtk-add</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_stock">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="rule_edit">
+ <property name="label" translatable="yes">_Edit</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="rule_delete">
+ <property name="label">gtk-remove</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_stock">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="rule_top">
+ <property name="label">gtk-goto-top</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_stock">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="rule_up">
+ <property name="label">gtk-go-up</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_stock">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="rule_down">
+ <property name="label">gtk-go-down</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_stock">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">5</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="rule_bottom">
+ <property name="label">gtk-goto-bottom</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_stock">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">6</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">3</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <object class="GtkVBox" id="filter_datespec">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkHBox" id="hbox5">
+ <property name="visible">True</property>
+ <property name="border_width">4</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label4">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Compare against</property>
+ <property name="justify">center</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="combobox_type">
+ <property name="visible">True</property>
+ <property name="model">model2</property>
+ <child>
+ <object class="GtkCellRendererText" id="renderer2"/>
+ <attributes>
+ <attribute name="text">0</attribute>
+ </attributes>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHSeparator" id="hseparator1">
+ <property name="visible">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="padding">1</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkNotebook" id="notebook_type">
+ <property name="visible">True</property>
+ <property name="show_tabs">False</property>
+ <property name="show_border">False</property>
+ <child>
+ <object class="GtkVBox" id="vbox9">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkLabel" id="label5">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">The message's date will be compared against
+the current time when filtering occurs.</property>
+ <property name="justify">center</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="label1">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">label1</property>
+ <property name="justify">center</property>
+ </object>
+ <packing>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox7">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkLabel" id="label6">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">The message's date will be compared against
+12:00am of the date specified.</property>
+ <property name="justify">center</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCalendar" id="calendar_specify">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="label2">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">label2</property>
+ <property name="justify">center</property>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox8">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkLabel" id="label7">
+ <property name="visible">True</property>
+ <property name="ypad">15</property>
+ <property name="label" translatable="yes">The message's date will be compared against
+a time relative to when filtering occurs.</property>
+ <property name="justify">center</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAlignment" id="alignment1">
+ <property name="visible">True</property>
+ <property name="top_padding">5</property>
+ <property name="left_padding">58</property>
+ <child>
+ <object class="GtkHBox" id="hbox6">
+ <property name="visible">True</property>
+ <property name="homogeneous">True</property>
+ <child>
+ <object class="GtkSpinButton" id="spin_relative">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="adjustment">adjustment1</property>
+ <property name="climb_rate">1</property>
+ </object>
+ <packing>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="combobox_relative">
+ <property name="visible">True</property>
+ <property name="model">model3</property>
+ <property name="active">0</property>
+ <child>
+ <object class="GtkCellRendererText" id="renderer3"/>
+ <attributes>
+ <attribute name="text">0</attribute>
+ </attributes>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="combobox_past_future">
+ <property name="visible">True</property>
+ <property name="model">model4</property>
+ <property name="active">0</property>
+ <child>
+ <object class="GtkCellRendererText" id="renderer4"/>
+ <attributes>
+ <attribute name="text">0</attribute>
+ </attributes>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="padding">2</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="label3">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">label3</property>
+ <property name="justify">center</property>
+ </object>
+ <packing>
+ <property name="position">2</property>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+</interface>
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 0000000000..523869bcb7
--- /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 0000000000..30ce4a7677
--- /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/e-util/gal-a11y-e-cell-registry.c b/e-util/gal-a11y-e-cell-registry.c
new file mode 100644
index 0000000000..db05ac05c1
--- /dev/null
+++ b/e-util/gal-a11y-e-cell-registry.c
@@ -0,0 +1,151 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You 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 "gal-a11y-e-cell-registry.h"
+
+static GObjectClass *parent_class;
+static GalA11yECellRegistry *default_registry;
+#define PARENT_TYPE (G_TYPE_OBJECT)
+
+struct _GalA11yECellRegistryPrivate {
+ GHashTable *table;
+};
+
+/* Static functions */
+
+static void
+gal_a11y_e_cell_registry_finalize (GObject *obj)
+{
+ GalA11yECellRegistry *registry = GAL_A11Y_E_CELL_REGISTRY (obj);
+
+ g_hash_table_destroy (registry->priv->table);
+ g_free (registry->priv);
+
+ G_OBJECT_CLASS (parent_class)->finalize (obj);
+}
+
+static void
+gal_a11y_e_cell_registry_class_init (GalA11yECellRegistryClass *class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+ parent_class = g_type_class_ref (PARENT_TYPE);
+
+ object_class->finalize = gal_a11y_e_cell_registry_finalize;
+}
+
+static void
+gal_a11y_e_cell_registry_init (GalA11yECellRegistry *registry)
+{
+ registry->priv = g_new (GalA11yECellRegistryPrivate, 1);
+ registry->priv->table = g_hash_table_new (NULL, NULL);
+}
+
+/**
+ * gal_a11y_e_cell_registry_get_type:
+ * @void:
+ *
+ * Registers the &GalA11yECellRegistry class if necessary, and returns the type ID
+ * associated to it.
+ *
+ * Return value: The type ID of the &GalA11yECellRegistry class.
+ **/
+GType
+gal_a11y_e_cell_registry_get_type (void)
+{
+ static GType type = 0;
+
+ if (!type) {
+ GTypeInfo info = {
+ sizeof (GalA11yECellRegistryClass),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) gal_a11y_e_cell_registry_class_init,
+ (GClassFinalizeFunc) NULL,
+ NULL, /* class_data */
+ sizeof (GalA11yECellRegistry),
+ 0,
+ (GInstanceInitFunc) gal_a11y_e_cell_registry_init,
+ NULL /* value_cell */
+ };
+
+ type = g_type_register_static (
+ PARENT_TYPE, "GalA11yECellRegistry", &info, 0);
+ }
+
+ return type;
+}
+
+static void
+init_default_registry (void)
+{
+ if (default_registry == NULL) {
+ default_registry = g_object_new (gal_a11y_e_cell_registry_get_type (), NULL);
+ }
+}
+
+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)
+{
+ GalA11yECellRegistryFunc func = NULL;
+ GType type;
+
+ if (registry == NULL) {
+ init_default_registry ();
+ registry = default_registry;
+ }
+
+ type = G_OBJECT_TYPE (cell_view->ecell);
+ while (func == NULL && type != 0) {
+ func = g_hash_table_lookup (registry->priv->table, GINT_TO_POINTER (type));
+ type = g_type_parent (type);
+ }
+
+ if (func == NULL)
+ func = gal_a11y_e_cell_new;
+
+ return func (item, cell_view, parent, model_col, view_col, row);
+}
+
+void
+gal_a11y_e_cell_registry_add_cell_type (GalA11yECellRegistry *registry,
+ GType type,
+ GalA11yECellRegistryFunc func)
+{
+ if (registry == NULL) {
+ init_default_registry ();
+ registry = default_registry;
+ }
+
+ g_hash_table_insert (registry->priv->table, GINT_TO_POINTER (type), func);
+}
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 0000000000..fdfd9dcffd
--- /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 0000000000..8be7f44122
--- /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 0000000000..bd3670edda
--- /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 0000000000..e0757f5300
--- /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 0000000000..caa5f4034a
--- /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 0000000000..7864dc04ea
--- /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 0000000000..cb6807e0a4
--- /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 0000000000..9f16b34fd9
--- /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 0000000000..63e8ecfe6b
--- /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 0000000000..ff923d8e40
--- /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 0000000000..cc6d47f6b1
--- /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 0000000000..bebe8c44a9
--- /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 0000000000..46f3939bbe
--- /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 0000000000..46fb374e9a
--- /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 0000000000..9d77467bda
--- /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/e-util/gal-a11y-e-table-factory.c b/e-util/gal-a11y-e-table-factory.c
new file mode 100644
index 0000000000..a3905ab393
--- /dev/null
+++ b/e-util/gal-a11y-e-table-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:
+ * 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 "gal-a11y-e-table-factory.h"
+
+static AtkObjectFactoryClass *parent_class;
+#define PARENT_TYPE (ATK_TYPE_OBJECT_FACTORY)
+
+/* Static functions */
+
+static GType
+gal_a11y_e_table_factory_get_accessible_type (void)
+{
+ return GAL_A11Y_TYPE_E_TABLE;
+}
+
+static AtkObject *
+gal_a11y_e_table_factory_create_accessible (GObject *obj)
+{
+ AtkObject *accessible;
+
+ accessible = gal_a11y_e_table_new (obj);
+
+ return accessible;
+}
+
+static void
+gal_a11y_e_table_factory_class_init (GalA11yETableFactoryClass *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_factory_create_accessible;
+ factory_class->get_accessible_type = gal_a11y_e_table_factory_get_accessible_type;
+}
+
+static void
+gal_a11y_e_table_factory_init (GalA11yETableFactory *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_factory_get_type (void)
+{
+ static GType type = 0;
+
+ if (!type) {
+ GTypeInfo info = {
+ sizeof (GalA11yETableFactoryClass),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) gal_a11y_e_table_factory_class_init,
+ (GClassFinalizeFunc) NULL,
+ NULL, /* class_data */
+ sizeof (GalA11yETableFactory),
+ 0,
+ (GInstanceInitFunc) gal_a11y_e_table_factory_init,
+ NULL /* value_table */
+ };
+
+ type = g_type_register_static (
+ PARENT_TYPE, "GalA11yETableFactory", &info, 0);
+ }
+
+ return type;
+}
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 0000000000..3a8b18f32f
--- /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 0000000000..3ef551d66a
--- /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 0000000000..4aef02d113
--- /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 0000000000..9f5c407507
--- /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 0000000000..4791a70354
--- /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 0000000000..f9178bd144
--- /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 0000000000..1e47965af4
--- /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 0000000000..191b30d362
--- /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 0000000000..4647fe3fcd
--- /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 0000000000..9b03a78483
--- /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 0000000000..22ebe09dd1
--- /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/e-util/gal-a11y-e-tree-factory.c b/e-util/gal-a11y-e-tree-factory.c
new file mode 100644
index 0000000000..00ce55c8c0
--- /dev/null
+++ b/e-util/gal-a11y-e-tree-factory.c
@@ -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:
+ * 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 "gal-a11y-e-tree-factory.h"
+
+static AtkObjectFactoryClass *parent_class;
+#define PARENT_TYPE (ATK_TYPE_OBJECT_FACTORY)
+
+/* Static functions */
+
+static GType
+gal_a11y_e_tree_factory_get_accessible_type (void)
+{
+ return GAL_A11Y_TYPE_E_TREE;
+}
+
+static AtkObject *
+gal_a11y_e_tree_factory_create_accessible (GObject *obj)
+{
+ AtkObject *accessible;
+
+ accessible = gal_a11y_e_tree_new (obj);
+
+ return accessible;
+}
+
+static void
+gal_a11y_e_tree_factory_class_init (GalA11yETreeFactoryClass *class)
+{
+ AtkObjectFactoryClass *factory_class = ATK_OBJECT_FACTORY_CLASS (class);
+
+ parent_class = g_type_class_ref (PARENT_TYPE);
+
+ factory_class->create_accessible = gal_a11y_e_tree_factory_create_accessible;
+ factory_class->get_accessible_type = gal_a11y_e_tree_factory_get_accessible_type;
+}
+
+static void
+gal_a11y_e_tree_factory_init (GalA11yETreeFactory *factory)
+{
+}
+
+/**
+ * gal_a11y_e_tree_factory_get_type:
+ * @void:
+ *
+ * Registers the &GalA11yETreeFactory class if necessary, and returns the type ID
+ * associated to it.
+ *
+ * Return value: The type ID of the &GalA11yETreeFactory class.
+ **/
+GType
+gal_a11y_e_tree_factory_get_type (void)
+{
+ static GType type = 0;
+
+ if (!type) {
+ GTypeInfo info = {
+ sizeof (GalA11yETreeFactoryClass),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) gal_a11y_e_tree_factory_class_init,
+ (GClassFinalizeFunc) NULL,
+ NULL, /* class_data */
+ sizeof (GalA11yETreeFactory),
+ 0,
+ (GInstanceInitFunc) gal_a11y_e_tree_factory_init,
+ NULL /* value_tree */
+ };
+
+ type = g_type_register_static (PARENT_TYPE, "GalA11yETreeFactory", &info, 0);
+ }
+
+ return type;
+}
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 0000000000..5919ab2091
--- /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 0000000000..52c34f312b
--- /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 0000000000..709fce0380
--- /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 0000000000..79ffcf286f
--- /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/e-util/gal-a11y-util.c b/e-util/gal-a11y-util.c
new file mode 100644
index 0000000000..9d44758187
--- /dev/null
+++ b/e-util/gal-a11y-util.c
@@ -0,0 +1,49 @@
+/*
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You 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-util.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)
+{
+ GTypeQuery query;
+
+ g_type_query (parent_type, &query);
+
+ info->class_size = query.class_size;
+ info->instance_size = query.instance_size + priv_size;
+
+ if (priv_offset)
+ *priv_offset = query.instance_size;
+
+ return g_type_register_static (parent_type, type_name, info, flags);
+}
diff --git a/e-util/gal-a11y-util.h b/e-util/gal-a11y-util.h
new file mode 100644
index 0000000000..75642621d9
--- /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 0000000000..4bed5944e1
--- /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 0000000000..a3b6973cf5
--- /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 0000000000..f9963acbfe
--- /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 0000000000..7219384a59
--- /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/e-util/gal-define-views.ui b/e-util/gal-define-views.ui
new file mode 100644
index 0000000000..b3314aa4ee
--- /dev/null
+++ b/e-util/gal-define-views.ui
@@ -0,0 +1,177 @@
+<?xml version="1.0"?>
+<!--*- mode: xml -*-->
+<interface>
+ <object class="GtkDialog" id="dialog1">
+ <property name="title" translatable="yes">Define Views for "%s"</property>
+ <property name="type_hint">GDK_WINDOW_TYPE_HINT_NORMAL</property>
+ <child internal-child="vbox">
+ <object class="GtkVBox" id="dialog-vbox1">
+ <property name="visible">True</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkTable" id="table-top">
+ <property name="visible">True</property>
+ <property name="n_rows">2</property>
+ <property name="n_columns">1</property>
+ <property name="column_spacing">6</property>
+ <property name="row_spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label-views">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Define Views for %s</property>
+ </object>
+ <packing>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox1">
+ <property name="visible">True</property>
+ <property name="border_width">6</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkScrolledWindow" id="scrolledwindow1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="hscrollbar_policy">GTK_POLICY_NEVER</property>
+ <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="shadow_type">GTK_SHADOW_IN</property>
+ <child>
+ <object class="GtkTreeView" id="treeview1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="headers_clickable">True</property>
+ <property name="reorderable">True</property>
+ <property name="fixed_height_mode">True</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkVButtonBox" id="vbuttonbox1">
+ <property name="visible">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="spacing">6</property>
+ <property name="layout_style">GTK_BUTTONBOX_START</property>
+ <child>
+ <object class="GtkButton" id="button-new">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label">gtk-new</property>
+ <property name="use_stock">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button-modify">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <child>
+ <object class="GtkAlignment" id="alignment33">
+ <property name="visible">True</property>
+ <property name="xscale">0</property>
+ <property name="yscale">0</property>
+ <child>
+ <object class="GtkHBox" id="hbox224">
+ <property name="visible">True</property>
+ <property name="spacing">2</property>
+ <child>
+ <object class="GtkImage" id="image8">
+ <property name="visible">True</property>
+ <property name="stock">gtk-properties</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label557">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Edit</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button-delete">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label">gtk-delete</property>
+ <property name="use_stock">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">12</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child internal-child="action_area">
+ <object class="GtkHButtonBox" id="dialog-action_area1">
+ <property name="visible">True</property>
+ <property name="layout_style">GTK_BUTTONBOX_END</property>
+ <child>
+ <object class="GtkButton" id="button7">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="label">gtk-close</property>
+ <property name="use_stock">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="pack_type">GTK_PACK_END</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="-5">button7</action-widget>
+ </action-widgets>
+ </object>
+</interface>
diff --git a/e-util/gal-view-collection.c b/e-util/gal-view-collection.c
new file mode 100644
index 0000000000..bcbad52fea
--- /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 0000000000..980f7c0365
--- /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 0000000000..3f50e2881a
--- /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 0000000000..92f7e64efb
--- /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 0000000000..632c959a85
--- /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 0000000000..cc4b617448
--- /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 0000000000..0e0dde05cb
--- /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 0000000000..abdcacd6ac
--- /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 0000000000..c71892e4ff
--- /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 0000000000..47b76b1155
--- /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/e-util/gal-view-instance-save-as-dialog.ui b/e-util/gal-view-instance-save-as-dialog.ui
new file mode 100644
index 0000000000..d7215f1f61
--- /dev/null
+++ b/e-util/gal-view-instance-save-as-dialog.ui
@@ -0,0 +1,133 @@
+<?xml version="1.0"?>
+<interface>
+ <!-- interface-requires gtk+ 2.12 -->
+ <!-- interface-naming-policy toplevel-contextual -->
+ <object class="GtkVBox" id="vbox-top">
+ <property name="visible">True</property>
+ <property name="border_width">5</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkVBox" id="vbox1">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkRadioButton" id="radiobutton-create">
+ <property name="label" translatable="yes">_Create new view</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="active">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAlignment" id="alignment1">
+ <property name="visible">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="left_padding">24</property>
+ <child>
+ <object class="GtkHBox" id="hbox1">
+ <property name="visible">True</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label8">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Name:</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">entry-create</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="entry-create">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">&#x25CF;</property>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox2">
+ <property name="visible">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkRadioButton" id="radiobutton-replace">
+ <property name="label" translatable="yes">_Replace existing view</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ <property name="group">radiobutton-create</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAlignment" id="alignment2">
+ <property name="visible">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="left_padding">24</property>
+ <child>
+ <object class="GtkScrolledWindow" id="scrolledwindow2">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="hscrollbar_policy">automatic</property>
+ <property name="vscrollbar_policy">automatic</property>
+ <property name="shadow_type">in</property>
+ <child>
+ <object class="GtkTreeView" id="custom-replace">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+</interface>
diff --git a/e-util/gal-view-instance.c b/e-util/gal-view-instance.c
new file mode 100644
index 0000000000..e0a107f146
--- /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 0000000000..c5debd1c3a
--- /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 0000000000..1df95a1985
--- /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 0000000000..503a594abb
--- /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/e-util/gal-view-new-dialog.ui b/e-util/gal-view-new-dialog.ui
new file mode 100644
index 0000000000..227e3954d8
--- /dev/null
+++ b/e-util/gal-view-new-dialog.ui
@@ -0,0 +1,177 @@
+<?xml version="1.0"?>
+<!--*- mode: xml -*-->
+<interface>
+ <object class="GtkDialog" id="dialog1">
+ <property name="title" translatable="yes"/>
+ <property name="type">GTK_WINDOW_TOPLEVEL</property>
+ <property name="window_position">GTK_WIN_POS_NONE</property>
+ <property name="modal">False</property>
+ <property name="resizable">True</property>
+ <property name="destroy_with_parent">False</property>
+ <property name="decorated">True</property>
+ <property name="skip_taskbar_hint">False</property>
+ <property name="skip_pager_hint">False</property>
+ <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
+ <property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
+ <child internal-child="vbox">
+ <object class="GtkVBox" id="dialog-vbox1">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">8</property>
+ <child internal-child="action_area">
+ <object class="GtkHButtonBox" id="dialog-action_area1">
+ <property name="visible">True</property>
+ <property name="layout_style">GTK_BUTTONBOX_END</property>
+ <child>
+ <object class="GtkButton" id="button1">
+ <property name="visible">True</property>
+ <property name="can_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="label">gtk-ok</property>
+ <property name="use_stock">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="button3">
+ <property name="visible">True</property>
+ <property name="can_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="label">gtk-cancel</property>
+ <property name="use_stock">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="pack_type">GTK_PACK_END</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkTable" id="table-top">
+ <property name="visible">True</property>
+ <property name="n_rows">4</property>
+ <property name="n_columns">1</property>
+ <property name="homogeneous">False</property>
+ <property name="row_spacing">6</property>
+ <property name="column_spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label1">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Name of new view:</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_CENTER</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="mnemonic_widget">entry-name</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">0</property>
+ <property name="bottom_attach">1</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="entry-name">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="editable">True</property>
+ <property name="visibility">True</property>
+ <property name="max_length">0</property>
+ <property name="text" translatable="yes"/>
+ <property name="has_frame">True</property>
+ <property name="activates_default">False</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="y_options">fill</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label2">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Type of view:</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_CENTER</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow" id="scrolledwindow1">
+ <property name="visible">True</property>
+ <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="shadow_type">GTK_SHADOW_IN</property>
+ <property name="window_placement">GTK_CORNER_TOP_LEFT</property>
+ <child>
+ <object class="GtkTreeView" id="list-type-list">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="headers_visible">False</property>
+ <property name="rules_hint">False</property>
+ <property name="reorderable">False</property>
+ <property name="enable_search">True</property>
+ <accessibility>
+
+ </accessibility>
+ <child internal-child="accessible">
+ <object class="AtkObject" id="a11y-list-type-list1">
+ <property name="AtkObject::accessible_name" translatable="yes">Type of View</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ <property name="x_options">fill</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="0">button1</action-widget>
+ <action-widget response="0">button3</action-widget>
+ </action-widgets>
+ </object>
+</interface>
diff --git a/e-util/gal-view.c b/e-util/gal-view.c
new file mode 100644
index 0000000000..4302988a6e
--- /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 0000000000..d769895d03
--- /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 0000000000..718c80e639
--- /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 0000000000..d9e14731e1
--- /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 0000000000..59ba42502b
--- /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/e-util/test-dateedit.c b/e-util/test-dateedit.c
new file mode 100644
index 0000000000..5592afbc70
--- /dev/null
+++ b/e-util/test-dateedit.c
@@ -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:
+ * Damon Chaplin <damon@ximian.com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+/*
+ * test-dateedit - tests the EDateEdit widget.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <gtk/gtk.h>
+#include "e-dateedit.h"
+
+static void delete_event_cb (GtkWidget *widget,
+ GdkEventAny *event,
+ GtkWidget *window);
+static void on_get_date_clicked (GtkWidget *button,
+ EDateEdit *dedit);
+static void on_toggle_24_hour_clicked (GtkWidget *button,
+ EDateEdit *dedit);
+static void on_changed (EDateEdit *dedit,
+ gchar *name);
+#if 0
+static void on_date_changed (EDateEdit *dedit,
+ gchar *name);
+static void on_time_changed (EDateEdit *dedit,
+ gchar *name);
+#endif
+
+gint
+main (gint argc,
+ gchar **argv)
+{
+ GtkWidget *window;
+ EDateEdit *dedit;
+ GtkWidget *table, *button;
+
+ gtk_init (&argc, &argv);
+
+ puts ("here");
+
+ window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+ gtk_window_set_title (GTK_WINDOW (window), "EDateEdit Test");
+ gtk_window_set_default_size (GTK_WINDOW (window), 300, 200);
+ 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), window);
+
+ table = gtk_table_new (3, 3, FALSE);
+ gtk_table_set_row_spacings (GTK_TABLE (table), 4);
+ gtk_table_set_col_spacings (GTK_TABLE (table), 4);
+ gtk_widget_show (table);
+
+ gtk_container_add (GTK_CONTAINER (window), table);
+
+ /* EDateEdit 1. */
+ dedit = E_DATE_EDIT (e_date_edit_new ());
+ gtk_table_attach (
+ GTK_TABLE (table), GTK_WIDGET (dedit),
+ 0, 1, 0, 1, GTK_FILL, GTK_EXPAND, 0, 0);
+ gtk_widget_show (GTK_WIDGET (dedit));
+
+#if 0
+ g_signal_connect (
+ dedit, "date_changed",
+ G_CALLBACK (on_date_changed), (gpointer) "1");
+ g_signal_connect (
+ dedit, "time_changed",
+ G_CALLBACK (on_time_changed), (gpointer) "1");
+#else
+ g_signal_connect (
+ dedit, "changed",
+ G_CALLBACK (on_changed), (gpointer) "1");
+#endif
+
+ button = gtk_button_new_with_label ("Print Date");
+ gtk_table_attach (
+ GTK_TABLE (table), button,
+ 1, 2, 0, 1, 0, 0, 0, 0);
+ gtk_widget_show (button);
+ g_signal_connect (
+ button, "clicked",
+ G_CALLBACK (on_get_date_clicked), dedit);
+
+ /* EDateEdit 2. */
+ dedit = E_DATE_EDIT (e_date_edit_new ());
+ gtk_table_attach (
+ GTK_TABLE (table), (GtkWidget *) dedit,
+ 0, 1, 1, 2, GTK_FILL, GTK_EXPAND, 0, 0);
+ gtk_widget_show ((GtkWidget *) (dedit));
+ e_date_edit_set_week_start_day (dedit, 1);
+ e_date_edit_set_show_week_numbers (dedit, TRUE);
+ e_date_edit_set_use_24_hour_format (dedit, FALSE);
+ e_date_edit_set_time_popup_range (dedit, 8, 18);
+ e_date_edit_set_show_time (dedit, FALSE);
+
+#if 0
+ g_signal_connect (
+ dedit, "date_changed",
+ G_CALLBACK (on_date_changed), (gpointer) "2");
+ g_signal_connect (
+ dedit, "time_changed",
+ G_CALLBACK (on_time_changed), (gpointer) "2");
+#else
+ g_signal_connect (
+ dedit, "changed",
+ G_CALLBACK (on_changed), (gpointer) "2");
+#endif
+
+ button = gtk_button_new_with_label ("Print Date");
+ gtk_table_attach (
+ GTK_TABLE (table), button,
+ 1, 2, 1, 2, 0, 0, 0, 0);
+ gtk_widget_show (button);
+ g_signal_connect (
+ button, "clicked",
+ G_CALLBACK (on_get_date_clicked), dedit);
+
+ /* EDateEdit 3. */
+ dedit = E_DATE_EDIT (e_date_edit_new ());
+ gtk_table_attach (
+ GTK_TABLE (table), (GtkWidget *) dedit,
+ 0, 1, 2, 3, GTK_FILL, GTK_EXPAND, 0, 0);
+ gtk_widget_show ((GtkWidget *) (dedit));
+ e_date_edit_set_week_start_day (dedit, 1);
+ e_date_edit_set_show_week_numbers (dedit, TRUE);
+ e_date_edit_set_use_24_hour_format (dedit, FALSE);
+ e_date_edit_set_time_popup_range (dedit, 8, 18);
+ e_date_edit_set_allow_no_date_set (dedit, TRUE);
+
+#if 0
+ g_signal_connect (
+ dedit, "date_changed",
+ G_CALLBACK (on_date_changed), (gpointer) "3");
+ g_signal_connect (
+ dedit, "time_changed",
+ G_CALLBACK (on_time_changed), (gpointer) "3");
+#else
+ g_signal_connect (
+ dedit, "changed",
+ G_CALLBACK (on_changed), (gpointer) "3");
+#endif
+
+ button = gtk_button_new_with_label ("Print Date");
+ gtk_table_attach (
+ GTK_TABLE (table), button,
+ 1, 2, 2, 3, 0, 0, 0, 0);
+ gtk_widget_show (button);
+ g_signal_connect (
+ button, "clicked",
+ G_CALLBACK (on_get_date_clicked), dedit);
+
+ button = gtk_button_new_with_label ("Toggle 24-hour");
+ gtk_table_attach (
+ GTK_TABLE (table), button,
+ 2, 3, 2, 3, 0, 0, 0, 0);
+ gtk_widget_show (button);
+ g_signal_connect (
+ button, "clicked",
+ G_CALLBACK (on_toggle_24_hour_clicked), dedit);
+
+ gtk_widget_show (window);
+
+ gtk_main ();
+
+ return 0;
+}
+
+static void
+delete_event_cb (GtkWidget *widget,
+ GdkEventAny *event,
+ GtkWidget *window)
+{
+ gtk_widget_destroy (window);
+
+ gtk_main_quit ();
+}
+
+static void
+on_get_date_clicked (GtkWidget *button,
+ EDateEdit *dedit)
+{
+ time_t t;
+
+ t = e_date_edit_get_time (dedit);
+ if (t == -1)
+ g_print ("Time: None\n");
+ else
+ g_print ("Time: %s", ctime (&t));
+
+ if (!e_date_edit_date_is_valid (dedit))
+ g_print (" Date invalid\n");
+
+ if (!e_date_edit_time_is_valid (dedit))
+ g_print (" Time invalid\n");
+}
+
+static void
+on_toggle_24_hour_clicked (GtkWidget *button,
+ EDateEdit *dedit)
+{
+ gboolean use_24_hour_format;
+
+ use_24_hour_format = e_date_edit_get_use_24_hour_format (dedit);
+ e_date_edit_set_use_24_hour_format (dedit, !use_24_hour_format);
+}
+
+#if 0
+static void
+on_date_changed (EDateEdit *dedit,
+ gchar *name)
+{
+ gint year, month, day;
+
+ if (e_date_edit_date_is_valid (dedit)) {
+ if (e_date_edit_get_date (dedit, &year, &month, &day)) {
+ g_print (
+ "Date %s changed to: %i/%i/%i (M/D/Y)\n",
+ name, month, day, year);
+ } else {
+ g_print ("Date %s changed to: None\n", name);
+ }
+ } else {
+ g_print ("Date %s changed to: Not Valid\n", name);
+ }
+}
+
+static void
+on_time_changed (EDateEdit *dedit,
+ gchar *name)
+{
+ gint hour, minute;
+
+ if (e_date_edit_time_is_valid (dedit)) {
+ if (e_date_edit_get_time_of_day (dedit, &hour, &minute)) {
+ g_print (
+ "Time %s changed to: %02i:%02i\n", name,
+ hour, minute);
+ } else {
+ g_print ("Time %s changed to: None\n", name);
+ }
+ } else {
+ g_print ("Time %s changed to: Not Valid\n", name);
+ }
+}
+#endif
+
+static void
+on_changed (EDateEdit *dedit,
+ gchar *name)
+{
+ gint year, month, day, hour, minute;
+
+ g_print ("Date %s changed ", name);
+
+ if (e_date_edit_date_is_valid (dedit)) {
+ if (e_date_edit_get_date (dedit, &year, &month, &day)) {
+ g_print ("M/D/Y: %i/%i/%i", month, day, year);
+ } else {
+ g_print ("None");
+ }
+ } else {
+ g_print ("Date Invalid");
+ }
+
+ if (e_date_edit_time_is_valid (dedit)) {
+ if (e_date_edit_get_time_of_day (dedit, &hour, &minute)) {
+ g_print (" %02i:%02i\n", hour, minute);
+ } else {
+ g_print (" None\n");
+ }
+ } else {
+ g_print (" Time Invalid\n");
+ }
+}
+
diff --git a/e-util/test-mail-signatures.c b/e-util/test-mail-signatures.c
new file mode 100644
index 0000000000..3dc5f0a720
--- /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 0000000000..3744ad9f1a
--- /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/e-util/test-preferences-window.c b/e-util/test-preferences-window.c
new file mode 100644
index 0000000000..4ad30e2245
--- /dev/null
+++ b/e-util/test-preferences-window.c
@@ -0,0 +1,108 @@
+/*
+ * test-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)
+ *
+ */
+
+#include "e-preferences-window.c"
+
+#include <gtk/gtk.h>
+
+static GtkWidget *
+create_page_number (gint i)
+{
+ gchar *caption;
+ GtkWidget *widget;
+
+ caption = g_strdup_printf ("Title of page %d", i);
+
+ widget = gtk_label_new (caption);
+ gtk_widget_show (widget);
+
+ g_free (caption);
+
+ return widget;
+}
+
+static GtkWidget *
+create_page_zero (EPreferencesWindow *preferences_window)
+{
+ return create_page_number (0);
+}
+static GtkWidget *
+create_page_one (EPreferencesWindow *preferences_window)
+{
+ return create_page_number (1);
+}
+static GtkWidget *
+create_page_two (EPreferencesWindow *preferences_window)
+{
+ return create_page_number (2);
+}
+
+static void
+add_pages (EPreferencesWindow *preferences_window)
+{
+ e_preferences_window_add_page (
+ preferences_window, "page-0",
+ "gtk-properties", "title 0", NULL,
+ create_page_zero, 0);
+ e_preferences_window_add_page (
+ preferences_window, "page-1",
+ "gtk-properties", "title 1", NULL,
+ create_page_one, 1);
+ e_preferences_window_add_page (
+ preferences_window, "page-2",
+ "gtk-properties", "title 2", NULL,
+ create_page_two, 2);
+}
+
+static gint
+delete_event_callback (GtkWidget *widget,
+ GdkEventAny *event,
+ gpointer data)
+{
+ gtk_main_quit ();
+
+ return TRUE;
+}
+
+gint
+main (gint argc,
+ gchar **argv)
+{
+ GtkWidget *window;
+
+ gtk_init (&argc, &argv);
+
+ window = e_preferences_window_new (NULL);
+ gtk_window_set_default_size (GTK_WINDOW (window), 400, 300);
+
+ g_signal_connect (
+ window, "delete-event",
+ G_CALLBACK (delete_event_callback), NULL);
+
+ add_pages (E_PREFERENCES_WINDOW (window));
+ e_preferences_window_setup (E_PREFERENCES_WINDOW (window));
+
+ gtk_widget_show (window);
+
+ gtk_main ();
+
+ return 0;
+}
diff --git a/e-util/test-source-combo-box.c b/e-util/test-source-combo-box.c
new file mode 100644
index 0000000000..cb40f6eb18
--- /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/e-util/test-source-config.c b/e-util/test-source-config.c
new file mode 100644
index 0000000000..4a5ce30d91
--- /dev/null
+++ b/e-util/test-source-config.c
@@ -0,0 +1,57 @@
+#include <stdlib.h>
+#include <gtk/gtk.h>
+
+#include <libedataserver/libedataserver.h>
+
+#include "e-source-config-dialog.h"
+
+static void
+dialog_response (GtkDialog *dialog,
+ gint response_id)
+{
+ gtk_main_quit ();
+}
+
+gint
+main (gint argc,
+ gchar **argv)
+{
+ ESourceRegistry *registry;
+ ESource *source = NULL;
+ GtkWidget *config;
+ GtkWidget *dialog;
+ 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);
+ }
+
+ if (argc > 1) {
+ source = e_source_registry_ref_source (registry, argv[1]);
+ if (source == NULL) {
+ g_printerr ("No such UID: %s\n", argv[1]);
+ exit (EXIT_FAILURE);
+ }
+ }
+
+ config = e_source_config_new (registry, source);
+ dialog = e_source_config_dialog_new (E_SOURCE_CONFIG (config));
+
+ g_signal_connect (
+ dialog, "response",
+ G_CALLBACK (dialog_response), NULL);
+
+ gtk_widget_show (config);
+ gtk_widget_show (dialog);
+
+ g_object_unref (source);
+
+ gtk_main ();
+
+ return 0;
+}
diff --git a/e-util/test-source-selector.c b/e-util/test-source-selector.c
new file mode 100644
index 0000000000..0c1a77289e
--- /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/e-util/tree-expanded.xpm b/e-util/tree-expanded.xpm
new file mode 100644
index 0000000000..94d162d40b
--- /dev/null
+++ b/e-util/tree-expanded.xpm
@@ -0,0 +1,23 @@
+/* XPM */
+static const gchar *tree_expanded_xpm[] = {
+"16 16 4 1",
+" c None",
+". c #FFFFFF",
+"* c #000000",
+"+ c #666666",
+" ",
+" ",
+" ",
+" ",
+" +++++++++ ",
+" +.......+ ",
+" +.......+ ",
+" +.......+ ",
+" +.*****.+ ",
+" +.......+ ",
+" +.......+ ",
+" +.......+ ",
+" +++++++++ ",
+" ",
+" ",
+" "};
diff --git a/e-util/tree-unexpanded.xpm b/e-util/tree-unexpanded.xpm
new file mode 100644
index 0000000000..d20ec5aa33
--- /dev/null
+++ b/e-util/tree-unexpanded.xpm
@@ -0,0 +1,23 @@
+/* XPM */
+static const gchar *tree_unexpanded_xpm[] = {
+"16 16 4 1",
+" c None",
+". c #FFFFFF",
+"* c #000000",
+"+ c #666666",
+" ",
+" ",
+" ",
+" ",
+" +++++++++ ",
+" +.......+ ",
+" +...*...+ ",
+" +...*...+ ",
+" +.*****.+ ",
+" +...*...+ ",
+" +...*...+ ",
+" +.......+ ",
+" +++++++++ ",
+" ",
+" ",
+" "};
diff --git a/e-util/widgets.error.xml b/e-util/widgets.error.xml
new file mode 100644
index 0000000000..efaa41c42e
--- /dev/null
+++ b/e-util/widgets.error.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<error-list domain="widgets">
+
+ <error id="ask-signature-changed" type="question" default="GTK_RESPONSE_YES">
+ <_primary>Do you wish to save your changes?</_primary>
+ <_secondary xml:space="preserve">This signature has been changed, but has not been saved.</_secondary>
+ <button _label="_Discard changes" response="GTK_RESPONSE_NO"/>
+ <button stock="gtk-cancel" response="GTK_RESPONSE_CANCEL"/>
+ <button stock="gtk-save" response="GTK_RESPONSE_YES"/>
+ </error>
+
+ <error id="blank-signature" type="error">
+ <_primary>Blank Signature</_primary>
+ <_secondary>Please provide an unique name to identify this signature.</_secondary>
+ </error>
+
+ <error id="no-load-signature" type="error">
+ <_primary>Could not load signature.</_primary>
+ <secondary xml:space="preserve">{0}</secondary>
+ </error>
+
+ <error id="no-save-signature" type="error">
+ <_primary>Could not save signature.</_primary>
+ <secondary xml:space="preserve">{0}</secondary>
+ </error>
+
+</error-list>
+