From d09d8de870b6697c8a8b262e7e077b871a69b315 Mon Sep 17 00:00:00 2001 From: Matthew Barnes Date: Mon, 10 Dec 2012 08:09:59 -0500 Subject: Consolidate base utility libraries into libeutil. Evolution consists of entirely too many small utility libraries, which increases linking and loading time, places a burden on higher layers of the application (e.g. modules) which has to remember to link to all the small in-tree utility libraries, and makes it difficult to generate API documentation for these utility libraries in one Gtk-Doc module. Merge the following utility libraries under the umbrella of libeutil, and enforce a single-include policy on libeutil so we can reorganize the files as desired without disrupting its pseudo-public API. libemail-utils/libemail-utils.la libevolution-utils/libevolution-utils.la filter/libfilter.la widgets/e-timezone-dialog/libetimezonedialog.la widgets/menus/libmenus.la widgets/misc/libemiscwidgets.la widgets/table/libetable.la widgets/text/libetext.la This also merges libedataserverui from the Evolution-Data-Server module, since Evolution is its only consumer nowadays, and I'd like to make some improvements to those APIs without concern for backward-compatibility. And finally, start a Gtk-Doc module for libeutil. It's going to be a project just getting all the symbols _listed_ much less _documented_. But the skeletal structure is in place and I'm off to a good start. --- Makefile.am | 7 +- a11y/Makefile.am | 23 - a11y/ea-factory.h | 114 - a11y/gal-a11y-factory.h | 85 - a11y/gal-a11y-util.c | 49 - a11y/gal-a11y-util.h | 36 - addressbook/gui/contact-editor/Makefile.am | 12 +- addressbook/gui/contact-editor/e-contact-editor.c | 11 +- .../gui/contact-editor/e-contact-quick-add.c | 3 - addressbook/gui/contact-list-editor/Makefile.am | 12 +- .../contact-list-editor/e-contact-list-editor.c | 7 +- .../contact-list-editor/e-contact-list-editor.h | 2 - .../gui/contact-list-editor/e-contact-list-model.c | 1 - addressbook/gui/merging/Makefile.am | 2 +- addressbook/gui/merging/eab-contact-compare.c | 2 +- addressbook/gui/widgets/Makefile.am | 7 - .../gui/widgets/e-addressbook-reflow-adapter.h | 3 +- addressbook/gui/widgets/e-addressbook-selector.c | 2 +- addressbook/gui/widgets/e-addressbook-selector.h | 2 - .../gui/widgets/e-addressbook-table-adapter.h | 2 +- addressbook/gui/widgets/e-addressbook-view.c | 29 +- addressbook/gui/widgets/e-addressbook-view.h | 3 - addressbook/gui/widgets/e-minicard-label.c | 9 +- addressbook/gui/widgets/e-minicard-view-widget.c | 5 +- addressbook/gui/widgets/e-minicard-view-widget.h | 3 +- addressbook/gui/widgets/e-minicard-view.c | 13 +- addressbook/gui/widgets/e-minicard-view.h | 4 +- addressbook/gui/widgets/e-minicard.c | 15 +- addressbook/gui/widgets/ea-addressbook.c | 4 +- addressbook/gui/widgets/eab-config.h | 2 +- addressbook/gui/widgets/eab-contact-display.c | 20 +- addressbook/gui/widgets/eab-contact-display.h | 2 +- addressbook/gui/widgets/eab-contact-formatter.c | 15 +- addressbook/gui/widgets/eab-gui-util.c | 8 +- addressbook/gui/widgets/eab-gui-util.h | 2 +- .../gui/widgets/gal-view-factory-minicard.h | 2 +- addressbook/gui/widgets/gal-view-minicard.c | 1 - addressbook/gui/widgets/gal-view-minicard.h | 2 +- addressbook/importers/Makefile.am | 8 +- addressbook/importers/evolution-csv-importer.c | 2 - addressbook/importers/evolution-ldif-importer.c | 2 - addressbook/importers/evolution-vcard-importer.c | 4 - addressbook/printing/Makefile.am | 9 +- addressbook/printing/e-contact-print.c | 1 - addressbook/util/Makefile.am | 2 - calendar/alarm-notify/Makefile.am | 9 +- calendar/alarm-notify/alarm-notify-dialog.c | 6 +- calendar/alarm-notify/alarm-notify.c | 2 + calendar/alarm-notify/alarm-notify.h | 1 - calendar/gui/Makefile.am | 10 +- calendar/gui/calendar-config.c | 3 +- calendar/gui/calendar-config.h | 2 +- calendar/gui/calendar-view-factory.h | 1 - calendar/gui/calendar-view.h | 1 - calendar/gui/comp-util.c | 2 - calendar/gui/comp-util.h | 2 +- calendar/gui/dialogs/Makefile.am | 11 +- calendar/gui/dialogs/alarm-dialog.c | 7 +- calendar/gui/dialogs/cancel-comp.c | 6 +- calendar/gui/dialogs/cancel-comp.h | 1 + calendar/gui/dialogs/comp-editor-util.c | 4 +- calendar/gui/dialogs/comp-editor-util.h | 1 - calendar/gui/dialogs/comp-editor.c | 9 - calendar/gui/dialogs/comp-editor.h | 4 +- calendar/gui/dialogs/copy-source-dialog.c | 3 +- calendar/gui/dialogs/delete-comp.c | 6 +- calendar/gui/dialogs/e-delegate-dialog.c | 1 - calendar/gui/dialogs/e-send-options-utils.h | 2 +- calendar/gui/dialogs/event-editor.c | 5 - calendar/gui/dialogs/event-page.c | 13 - calendar/gui/dialogs/memo-editor.c | 3 - calendar/gui/dialogs/memo-page.c | 11 - calendar/gui/dialogs/recurrence-page.c | 5 - calendar/gui/dialogs/save-comp.c | 1 - calendar/gui/dialogs/schedule-page.c | 3 +- calendar/gui/dialogs/schedule-page.h | 1 - calendar/gui/dialogs/select-source-dialog.c | 3 +- calendar/gui/dialogs/send-comp.c | 6 +- calendar/gui/dialogs/task-details-page.c | 7 +- calendar/gui/dialogs/task-editor.c | 3 - calendar/gui/dialogs/task-page.c | 11 - calendar/gui/e-cal-component-preview.c | 3 - calendar/gui/e-cal-component-preview.h | 3 +- calendar/gui/e-cal-config.h | 3 +- calendar/gui/e-cal-event.h | 3 +- calendar/gui/e-cal-list-view.c | 10 - calendar/gui/e-cal-list-view.h | 3 +- calendar/gui/e-cal-model.h | 4 +- calendar/gui/e-calendar-selector.c | 2 - calendar/gui/e-calendar-selector.h | 2 +- calendar/gui/e-calendar-view.c | 10 +- calendar/gui/e-cell-date-edit-text.c | 3 - calendar/gui/e-cell-date-edit-text.h | 2 +- calendar/gui/e-day-view-layout.c | 1 - calendar/gui/e-day-view-main-item.c | 10 +- calendar/gui/e-day-view-time-item.c | 1 - calendar/gui/e-day-view-top-item.c | 1 - calendar/gui/e-day-view.c | 24 +- calendar/gui/e-meeting-list-view.c | 1 - calendar/gui/e-meeting-list-view.h | 1 - calendar/gui/e-meeting-store.c | 1 - calendar/gui/e-meeting-store.h | 3 +- calendar/gui/e-meeting-time-sel-item.c | 2 - calendar/gui/e-meeting-time-sel.c | 7 - calendar/gui/e-meeting-time-sel.h | 6 +- calendar/gui/e-memo-list-selector.c | 1 - calendar/gui/e-memo-list-selector.h | 2 +- calendar/gui/e-memo-table.c | 16 +- calendar/gui/e-memo-table.h | 3 +- calendar/gui/e-select-names-editable.h | 2 - calendar/gui/e-task-list-selector.c | 1 - calendar/gui/e-task-list-selector.h | 2 +- calendar/gui/e-task-table.c | 17 +- calendar/gui/e-task-table.h | 3 +- calendar/gui/e-timezone-entry.c | 6 +- calendar/gui/e-week-view-event-item.c | 4 +- calendar/gui/e-week-view.c | 17 +- calendar/gui/ea-cal-view-event.c | 4 +- calendar/gui/ea-calendar-helpers.c | 1 - calendar/gui/ea-calendar.c | 3 +- calendar/gui/ea-day-view-cell.c | 1 - calendar/gui/ea-day-view-main-item.c | 4 +- calendar/gui/ea-week-view-cell.c | 1 - calendar/gui/ea-week-view-main-item.c | 5 +- calendar/gui/ea-week-view.c | 5 +- calendar/gui/gnome-cal.c | 32 +- calendar/gui/gnome-cal.h | 2 +- calendar/gui/itip-utils.c | 2 - calendar/gui/print.c | 6 +- calendar/gui/print.h | 3 +- calendar/gui/tag-calendar.h | 2 +- calendar/importers/Makefile.am | 4 +- calendar/importers/icalendar-importer.c | 9 +- composer/Makefile.am | 9 +- composer/e-composer-actions.c | 1 - composer/e-composer-activity.h | 1 - composer/e-composer-common.h | 1 + composer/e-composer-from-header.c | 2 - composer/e-composer-header-table.c | 2 - composer/e-composer-header-table.h | 1 - composer/e-composer-name-header.h | 1 - composer/e-composer-private.h | 18 - composer/e-composer-spell-header.c | 2 - composer/e-msg-composer.c | 4 - composer/e-msg-composer.h | 3 - configure.ac | 16 +- doc/reference/Makefile.am | 2 +- doc/reference/libeshell/Makefile.am | 80 + doc/reference/libeshell/libeshell-docs.sgml | 44 + doc/reference/libeshell/libeshell-overrides.txt | 0 doc/reference/libeshell/libeshell-sections.txt | 423 ++ doc/reference/libeshell/libeshell.types | 9 + .../libeshell/tmpl/e-mail-account-manager.sgml | 72 + .../libeshell/tmpl/e-mail-account-tree-view.sgml | 90 + .../libeshell/tmpl/e-mail-identity-combo-box.sgml | 56 + doc/reference/libeutil/Makefile.am | 67 + doc/reference/libeutil/libeutil-docs.sgml | 263 ++ doc/reference/libeutil/libeutil-overrides.txt | 0 doc/reference/libeutil/libeutil-sections.txt | 1900 +++++++++ doc/reference/libeutil/libeutil.types | 170 + doc/reference/shell/Makefile.am | 81 - doc/reference/shell/eshell-docs.sgml | 59 - doc/reference/shell/eshell-overrides.txt | 0 doc/reference/shell/eshell-sections.txt | 1096 ------ doc/reference/shell/eshell.types | 10 - .../shell/tmpl/e-mail-account-manager.sgml | 72 - .../shell/tmpl/e-mail-account-tree-view.sgml | 90 - .../shell/tmpl/e-mail-identity-combo-box.sgml | 56 - e-util/Makefile.am | 755 +++- e-util/arrow-down.xpm | 21 + e-util/arrow-up.xpm | 21 + e-util/check-empty.xpm | 21 + e-util/check-filled.xpm | 21 + e-util/e-action-combo-box.c | 578 +++ e-util/e-action-combo-box.h | 89 + e-util/e-activity-bar.c | 366 ++ e-util/e-activity-bar.h | 71 + e-util/e-activity-proxy.c | 381 ++ e-util/e-activity-proxy.h | 74 + e-util/e-activity.c | 3 +- e-util/e-activity.h | 7 +- e-util/e-alarm-selector.c | 94 + e-util/e-alarm-selector.h | 67 + e-util/e-alert-bar.c | 390 ++ e-util/e-alert-bar.h | 72 + e-util/e-alert-dialog.c | 403 ++ e-util/e-alert-dialog.h | 81 + e-util/e-alert-sink.c | 93 + e-util/e-alert-sink.h | 63 + e-util/e-alert.c | 1003 +++++ e-util/e-alert.h | 119 + e-util/e-attachment-bar.c | 778 ++++ e-util/e-attachment-bar.h | 83 + e-util/e-attachment-button.c | 868 +++++ e-util/e-attachment-button.h | 91 + e-util/e-attachment-dialog.c | 425 ++ e-util/e-attachment-dialog.h | 77 + e-util/e-attachment-handler-image.c | 246 ++ e-util/e-attachment-handler-image.h | 69 + e-util/e-attachment-handler-sendto.c | 229 ++ e-util/e-attachment-handler-sendto.h | 66 + e-util/e-attachment-handler.c | 133 + e-util/e-attachment-handler.h | 84 + e-util/e-attachment-icon-view.c | 570 +++ e-util/e-attachment-icon-view.h | 71 + e-util/e-attachment-paned.c | 904 +++++ e-util/e-attachment-paned.h | 99 + e-util/e-attachment-store.c | 1280 +++++++ e-util/e-attachment-store.h | 137 + e-util/e-attachment-tree-view.c | 623 +++ e-util/e-attachment-tree-view.h | 70 + e-util/e-attachment-view.c | 1906 +++++++++ e-util/e-attachment-view.h | 244 ++ e-util/e-attachment.c | 2882 ++++++++++++++ e-util/e-attachment.h | 159 + e-util/e-auth-combo-box.c | 266 ++ e-util/e-auth-combo-box.h | 75 + e-util/e-autocomplete-selector.c | 96 + e-util/e-autocomplete-selector.h | 68 + e-util/e-bit-array.c | 1 - e-util/e-bit-array.h | 4 + e-util/e-book-source-config.c | 287 ++ e-util/e-book-source-config.h | 71 + e-util/e-buffer-tagger.c | 692 ++++ e-util/e-buffer-tagger.h | 39 + e-util/e-cal-source-config.c | 431 +++ e-util/e-cal-source-config.h | 76 + e-util/e-calendar-item.c | 3773 ++++++++++++++++++ e-util/e-calendar-item.h | 392 ++ e-util/e-calendar.c | 848 ++++ e-util/e-calendar.h | 112 + e-util/e-canvas-background.c | 279 ++ e-util/e-canvas-background.h | 75 + e-util/e-canvas-utils.c | 222 ++ e-util/e-canvas-utils.h | 59 + e-util/e-canvas-vbox.c | 410 ++ e-util/e-canvas-vbox.h | 92 + e-util/e-canvas.c | 880 +++++ e-util/e-canvas.h | 141 + e-util/e-categories-config.c | 5 +- e-util/e-categories-config.h | 4 + e-util/e-categories-dialog.c | 155 + e-util/e-categories-dialog.h | 73 + e-util/e-categories-editor.c | 435 +++ e-util/e-categories-editor.h | 88 + e-util/e-categories-selector.c | 587 +++ e-util/e-categories-selector.h | 97 + e-util/e-category-completion.c | 505 +++ e-util/e-category-completion.h | 72 + e-util/e-category-editor.c | 343 ++ e-util/e-category-editor.h | 81 + e-util/e-cell-checkbox.c | 102 + e-util/e-cell-checkbox.h | 71 + e-util/e-cell-combo.c | 838 ++++ e-util/e-cell-combo.h | 89 + e-util/e-cell-date-edit.c | 1039 +++++ e-util/e-cell-date-edit.h | 124 + e-util/e-cell-date.c | 130 + e-util/e-cell-date.h | 74 + e-util/e-cell-hbox.c | 353 ++ e-util/e-cell-hbox.h | 91 + e-util/e-cell-number.c | 95 + e-util/e-cell-number.h | 71 + e-util/e-cell-percent.c | 160 + e-util/e-cell-percent.h | 76 + e-util/e-cell-pixbuf.c | 389 ++ e-util/e-cell-pixbuf.h | 75 + e-util/e-cell-popup.c | 550 +++ e-util/e-cell-popup.h | 118 + e-util/e-cell-renderer-color.c | 243 ++ e-util/e-cell-renderer-color.h | 79 + e-util/e-cell-size.c | 112 + e-util/e-cell-size.h | 72 + e-util/e-cell-text.c | 2810 ++++++++++++++ e-util/e-cell-text.h | 195 + e-util/e-cell-toggle.c | 469 +++ e-util/e-cell-toggle.h | 83 + e-util/e-cell-tree.c | 880 +++++ e-util/e-cell-tree.h | 90 + e-util/e-cell-vbox.c | 341 ++ e-util/e-cell-vbox.h | 93 + e-util/e-cell.c | 679 ++++ e-util/e-cell.h | 299 ++ e-util/e-charset-combo-box.c | 407 ++ e-util/e-charset-combo-box.h | 73 + e-util/e-charset.h | 4 + e-util/e-client-utils.c | 445 +++ e-util/e-client-utils.h | 64 + e-util/e-config.h | 6 +- e-util/e-contact-map-window.c | 500 +++ e-util/e-contact-map-window.h | 85 + e-util/e-contact-map.c | 407 ++ e-util/e-contact-map.h | 110 + e-util/e-contact-marker.c | 624 +++ e-util/e-contact-marker.h | 88 + e-util/e-contact-store.c | 1370 +++++++ e-util/e-contact-store.h | 94 + e-util/e-dateedit.c | 2497 ++++++++++++ e-util/e-dateedit.h | 219 ++ e-util/e-datetime-format.c | 5 +- e-util/e-datetime-format.h | 4 + e-util/e-destination-store.c | 751 ++++ e-util/e-destination-store.h | 106 + e-util/e-dialog-utils.h | 4 + e-util/e-dialog-widgets.h | 4 + e-util/e-event.h | 6 +- e-util/e-file-request.c | 2 - e-util/e-file-request.h | 4 + e-util/e-file-utils.h | 4 + e-util/e-filter-code.c | 102 + e-util/e-filter-code.h | 72 + e-util/e-filter-color.c | 163 + e-util/e-filter-color.h | 74 + e-util/e-filter-datespec.c | 513 +++ e-util/e-filter-datespec.h | 91 + e-util/e-filter-element.c | 446 +++ e-util/e-filter-element.h | 125 + e-util/e-filter-file.c | 261 ++ e-util/e-filter-file.h | 78 + e-util/e-filter-input.c | 304 ++ e-util/e-filter-input.h | 78 + e-util/e-filter-int.c | 230 ++ e-util/e-filter-int.h | 81 + e-util/e-filter-option.c | 566 +++ e-util/e-filter-option.h | 101 + e-util/e-filter-part.c | 513 +++ e-util/e-filter-part.h | 112 + e-util/e-filter-rule.c | 1241 ++++++ e-util/e-filter-rule.h | 163 + e-util/e-focus-tracker.c | 886 +++++ e-util/e-focus-tracker.h | 104 + e-util/e-html-utils.h | 4 + e-util/e-icon-factory.h | 4 + e-util/e-image-chooser.c | 562 +++ e-util/e-image-chooser.h | 80 + e-util/e-import-assistant.c | 1436 +++++++ e-util/e-import-assistant.h | 72 + e-util/e-import.h | 6 +- e-util/e-interval-chooser.c | 214 ++ e-util/e-interval-chooser.h | 72 + e-util/e-mail-identity-combo-box.c | 385 ++ e-util/e-mail-identity-combo-box.h | 75 + e-util/e-mail-signature-combo-box.c | 668 ++++ e-util/e-mail-signature-combo-box.h | 95 + e-util/e-mail-signature-editor.c | 914 +++++ e-util/e-mail-signature-editor.h | 87 + e-util/e-mail-signature-manager.c | 708 ++++ e-util/e-mail-signature-manager.h | 93 + e-util/e-mail-signature-preview.c | 358 ++ e-util/e-mail-signature-preview.h | 84 + e-util/e-mail-signature-script-dialog.c | 731 ++++ e-util/e-mail-signature-script-dialog.h | 94 + e-util/e-mail-signature-tree-view.c | 395 ++ e-util/e-mail-signature-tree-view.h | 80 + e-util/e-map.c | 1429 +++++++ e-util/e-map.h | 155 + e-util/e-menu-tool-action.c | 59 + e-util/e-menu-tool-action.h | 75 + e-util/e-menu-tool-button.c | 273 ++ e-util/e-menu-tool-button.h | 77 + e-util/e-misc-utils.c | 1807 +++++++++ e-util/e-misc-utils.h | 175 + e-util/e-mktemp.c | 3 +- e-util/e-mktemp.h | 4 + e-util/e-name-selector-dialog.c | 1863 +++++++++ e-util/e-name-selector-dialog.h | 100 + e-util/e-name-selector-entry.c | 3541 +++++++++++++++++ e-util/e-name-selector-entry.h | 124 + e-util/e-name-selector-list.c | 790 ++++ e-util/e-name-selector-list.h | 82 + e-util/e-name-selector-model.c | 663 ++++ e-util/e-name-selector-model.h | 108 + e-util/e-name-selector.c | 658 ++++ e-util/e-name-selector.h | 94 + e-util/e-online-button.c | 210 + e-util/e-online-button.h | 69 + e-util/e-paned.c | 503 +++ e-util/e-paned.h | 82 + e-util/e-passwords-win32.c | 1064 ++++++ e-util/e-passwords.c | 890 +++++ e-util/e-passwords.h | 81 + e-util/e-picture-gallery.c | 437 +++ e-util/e-picture-gallery.h | 71 + e-util/e-plugin-ui.c | 1 - e-util/e-plugin-ui.h | 4 + e-util/e-plugin.h | 4 + e-util/e-poolv.h | 4 + e-util/e-popup-action.c | 408 ++ e-util/e-popup-action.h | 96 + e-util/e-popup-menu.c | 112 + e-util/e-popup-menu.h | 59 + e-util/e-port-entry.c | 549 +++ e-util/e-port-entry.h | 91 + e-util/e-preferences-window.c | 643 ++++ e-util/e-preferences-window.h | 88 + e-util/e-preview-pane.c | 322 ++ e-util/e-preview-pane.h | 80 + e-util/e-print.c | 2 +- e-util/e-print.h | 4 + e-util/e-printable.c | 226 ++ e-util/e-printable.h | 93 + e-util/e-reflow-model.c | 411 ++ e-util/e-reflow-model.h | 129 + e-util/e-reflow.c | 1732 +++++++++ e-util/e-reflow.h | 145 + e-util/e-rule-context.c | 1026 +++++ e-util/e-rule-context.h | 218 ++ e-util/e-rule-editor.c | 920 +++++ e-util/e-rule-editor.h | 125 + e-util/e-search-bar.c | 771 ++++ e-util/e-search-bar.h | 89 + e-util/e-selectable.c | 168 + e-util/e-selectable.h | 85 + e-util/e-selection-model-array.c | 646 ++++ e-util/e-selection-model-array.h | 95 + e-util/e-selection-model-simple.c | 117 + e-util/e-selection-model-simple.h | 70 + e-util/e-selection-model.c | 813 ++++ e-util/e-selection-model.h | 209 + e-util/e-selection.c | 2 +- e-util/e-selection.h | 4 + e-util/e-send-options.c | 767 ++++ e-util/e-send-options.h | 131 + e-util/e-send-options.ui | 949 +++++ e-util/e-sorter-array.c | 3 +- e-util/e-sorter-array.h | 4 + e-util/e-sorter.c | 1 - e-util/e-sorter.h | 4 + e-util/e-source-combo-box.c | 701 ++++ e-util/e-source-combo-box.h | 90 + e-util/e-source-config-backend.c | 140 + e-util/e-source-config-backend.h | 98 + e-util/e-source-config-dialog.c | 394 ++ e-util/e-source-config-dialog.h | 69 + e-util/e-source-config.c | 1447 +++++++ e-util/e-source-config.h | 120 + e-util/e-source-selector-dialog.c | 453 +++ e-util/e-source-selector-dialog.h | 85 + e-util/e-source-selector.c | 2082 ++++++++++ e-util/e-source-selector.h | 141 + e-util/e-source-util.h | 6 +- e-util/e-spell-entry.c | 866 +++++ e-util/e-spell-entry.h | 63 + e-util/e-stock-request.c | 3 +- e-util/e-stock-request.h | 4 + e-util/e-table-click-to-add.c | 666 ++++ e-util/e-table-click-to-add.h | 99 + e-util/e-table-col-dnd.h | 43 + e-util/e-table-col.c | 222 ++ e-util/e-table-col.h | 115 + e-util/e-table-column-specification.c | 157 + e-util/e-table-column-specification.h | 93 + e-util/e-table-config.c | 1481 +++++++ e-util/e-table-config.h | 134 + e-util/e-table-config.ui | 1594 ++++++++ e-util/e-table-defines.h | 44 + e-util/e-table-extras.c | 410 ++ e-util/e-table-extras.h | 94 + e-util/e-table-field-chooser-dialog.c | 235 ++ e-util/e-table-field-chooser-dialog.h | 77 + e-util/e-table-field-chooser-item.c | 749 ++++ e-util/e-table-field-chooser-item.h | 97 + e-util/e-table-field-chooser.c | 335 ++ e-util/e-table-field-chooser.h | 84 + e-util/e-table-group-container.c | 1667 ++++++++ e-util/e-table-group-container.h | 138 + e-util/e-table-group-leaf.c | 816 ++++ e-util/e-table-group-leaf.h | 110 + e-util/e-table-group.c | 771 ++++ e-util/e-table-group.h | 242 ++ e-util/e-table-header-item.c | 2226 +++++++++++ e-util/e-table-header-item.h | 148 + e-util/e-table-header-utils.c | 282 ++ e-util/e-table-header-utils.h | 53 + e-util/e-table-header.c | 1013 +++++ e-util/e-table-header.h | 144 + e-util/e-table-item.c | 4041 ++++++++++++++++++++ e-util/e-table-item.h | 261 ++ e-util/e-table-memory-callbacks.c | 234 ++ e-util/e-table-memory-callbacks.h | 148 + e-util/e-table-memory-store.c | 637 +++ e-util/e-table-memory-store.h | 155 + e-util/e-table-memory.c | 271 ++ e-util/e-table-memory.h | 91 + e-util/e-table-model.c | 682 ++++ e-util/e-table-model.h | 217 ++ e-util/e-table-one.c | 252 ++ e-util/e-table-one.h | 75 + e-util/e-table-search.c | 235 ++ e-util/e-table-search.h | 86 + e-util/e-table-selection-model.c | 384 ++ e-util/e-table-selection-model.h | 91 + e-util/e-table-sort-info.c | 482 +++ e-util/e-table-sort-info.h | 133 + e-util/e-table-sorted-variable.c | 235 ++ e-util/e-table-sorted-variable.h | 85 + e-util/e-table-sorted.c | 328 ++ e-util/e-table-sorted.h | 84 + e-util/e-table-sorter.c | 519 +++ e-util/e-table-sorter.h | 94 + e-util/e-table-sorting-utils.c | 492 +++ e-util/e-table-sorting-utils.h | 95 + e-util/e-table-specification.c | 435 +++ e-util/e-table-specification.h | 116 + e-util/e-table-state.c | 320 ++ e-util/e-table-state.h | 89 + e-util/e-table-subset-variable.c | 267 ++ e-util/e-table-subset-variable.h | 105 + e-util/e-table-subset.c | 567 +++ e-util/e-table-subset.h | 120 + e-util/e-table-utils.c | 224 ++ e-util/e-table-utils.h | 54 + e-util/e-table-without.c | 412 ++ e-util/e-table-without.h | 104 + e-util/e-table.c | 3626 ++++++++++++++++++ e-util/e-table.h | 403 ++ e-util/e-text-event-processor-emacs-like.c | 1 - e-util/e-text-event-processor-emacs-like.h | 4 + e-util/e-text-event-processor-types.h | 4 + e-util/e-text-event-processor.c | 1 - e-util/e-text-event-processor.h | 4 + e-util/e-text-model-repos.c | 74 + e-util/e-text-model-repos.h | 58 + e-util/e-text-model.c | 642 ++++ e-util/e-text-model.h | 112 + e-util/e-text.c | 3405 +++++++++++++++++ e-util/e-text.h | 236 ++ e-util/e-timezone-dialog.c | 870 +++++ e-util/e-timezone-dialog.h | 77 + e-util/e-timezone-dialog.ui | 312 ++ e-util/e-tree-memory-callbacks.c | 314 ++ e-util/e-tree-memory-callbacks.h | 182 + e-util/e-tree-memory.c | 743 ++++ e-util/e-tree-memory.h | 124 + e-util/e-tree-model-generator.c | 1345 +++++++ e-util/e-tree-model-generator.h | 104 + e-util/e-tree-model.c | 1177 ++++++ e-util/e-tree-model.h | 298 ++ e-util/e-tree-selection-model.c | 939 +++++ e-util/e-tree-selection-model.h | 95 + e-util/e-tree-sorted.c | 1433 +++++++ e-util/e-tree-sorted.h | 104 + e-util/e-tree-table-adapter.c | 1414 +++++++ e-util/e-tree-table-adapter.h | 138 + e-util/e-tree.c | 3956 +++++++++++++++++++ e-util/e-tree.h | 376 ++ e-util/e-ui-manager.c | 2 +- e-util/e-ui-manager.h | 4 + e-util/e-unicode.h | 4 + e-util/e-url-entry.c | 159 + e-util/e-url-entry.h | 60 + e-util/e-util-enums.h | 4 + e-util/e-util.c | 1519 -------- e-util/e-util.h | 348 +- e-util/e-web-view-gtkhtml.c | 2317 +++++++++++ e-util/e-web-view-gtkhtml.h | 177 + e-util/e-web-view-preview.c | 474 +++ e-util/e-web-view-preview.h | 115 + e-util/e-web-view.c | 2945 ++++++++++++++ e-util/e-web-view.h | 224 ++ e-util/e-xml-utils.c | 448 +++ e-util/e-xml-utils.h | 93 + e-util/ea-calendar-cell.c | 404 ++ e-util/ea-calendar-cell.h | 90 + e-util/ea-calendar-item.c | 1373 +++++++ e-util/ea-calendar-item.h | 71 + e-util/ea-cell-table.c | 215 ++ e-util/ea-cell-table.h | 63 + e-util/ea-factory.h | 118 + e-util/ea-widgets.c | 36 + e-util/ea-widgets.h | 36 + e-util/evolution-source-viewer.c | 1176 ++++++ e-util/filter.error.xml | 34 + e-util/filter.ui | 591 +++ e-util/gal-a11y-e-cell-popup.c | 153 + e-util/gal-a11y-e-cell-popup.h | 65 + e-util/gal-a11y-e-cell-registry.c | 151 + e-util/gal-a11y-e-cell-registry.h | 75 + e-util/gal-a11y-e-cell-toggle.c | 198 + e-util/gal-a11y-e-cell-toggle.h | 67 + e-util/gal-a11y-e-cell-tree.c | 266 ++ e-util/gal-a11y-e-cell-tree.h | 66 + e-util/gal-a11y-e-cell-vbox.c | 235 ++ e-util/gal-a11y-e-cell-vbox.h | 67 + e-util/gal-a11y-e-cell.c | 648 ++++ e-util/gal-a11y-e-cell.h | 112 + e-util/gal-a11y-e-table-click-to-add-factory.c | 108 + e-util/gal-a11y-e-table-click-to-add-factory.h | 52 + e-util/gal-a11y-e-table-click-to-add.c | 358 ++ e-util/gal-a11y-e-table-click-to-add.h | 58 + e-util/gal-a11y-e-table-column-header.c | 243 ++ e-util/gal-a11y-e-table-column-header.h | 59 + e-util/gal-a11y-e-table-factory.c | 101 + e-util/gal-a11y-e-table-factory.h | 53 + e-util/gal-a11y-e-table-item-factory.c | 107 + e-util/gal-a11y-e-table-item-factory.h | 52 + e-util/gal-a11y-e-table-item.c | 1437 +++++++ e-util/gal-a11y-e-table-item.h | 62 + e-util/gal-a11y-e-table.c | 315 ++ e-util/gal-a11y-e-table.h | 62 + e-util/gal-a11y-e-text-factory.c | 103 + e-util/gal-a11y-e-text-factory.h | 52 + e-util/gal-a11y-e-text.c | 1141 ++++++ e-util/gal-a11y-e-text.h | 59 + e-util/gal-a11y-e-tree-factory.c | 99 + e-util/gal-a11y-e-tree-factory.h | 52 + e-util/gal-a11y-e-tree.c | 196 + e-util/gal-a11y-e-tree.h | 61 + e-util/gal-a11y-factory.h | 89 + e-util/gal-a11y-util.c | 49 + e-util/gal-a11y-util.h | 40 + e-util/gal-define-views-dialog.c | 451 +++ e-util/gal-define-views-dialog.h | 77 + e-util/gal-define-views-model.c | 352 ++ e-util/gal-define-views-model.h | 70 + e-util/gal-define-views.ui | 177 + e-util/gal-view-collection.c | 829 ++++ e-util/gal-view-collection.h | 150 + e-util/gal-view-etable.c | 335 ++ e-util/gal-view-etable.h | 96 + e-util/gal-view-factory-etable.c | 195 + e-util/gal-view-factory-etable.h | 77 + e-util/gal-view-factory.c | 101 + e-util/gal-view-factory.h | 85 + e-util/gal-view-instance-save-as-dialog.c | 359 ++ e-util/gal-view-instance-save-as-dialog.h | 92 + e-util/gal-view-instance-save-as-dialog.ui | 133 + e-util/gal-view-instance.c | 502 +++ e-util/gal-view-instance.h | 114 + e-util/gal-view-new-dialog.c | 291 ++ e-util/gal-view-new-dialog.h | 81 + e-util/gal-view-new-dialog.ui | 177 + e-util/gal-view.c | 280 ++ e-util/gal-view.h | 98 + e-util/test-calendar.c | 145 + e-util/test-category-completion.c | 67 + e-util/test-contact-store.c | 145 + e-util/test-dateedit.c | 299 ++ e-util/test-mail-signatures.c | 195 + e-util/test-name-selector.c | 102 + e-util/test-preferences-window.c | 108 + e-util/test-source-combo-box.c | 107 + e-util/test-source-config.c | 57 + e-util/test-source-selector.c | 157 + e-util/tree-expanded.xpm | 23 + e-util/tree-unexpanded.xpm | 23 + e-util/widgets.error.xml | 28 + em-format/Makefile.am | 14 +- em-format/e-mail-extension-registry.c | 13 +- em-format/e-mail-formatter-attachment-bar.c | 5 +- em-format/e-mail-formatter-attachment.c | 18 +- em-format/e-mail-formatter-error.c | 4 +- em-format/e-mail-formatter-extension.h | 4 +- em-format/e-mail-formatter-headers.c | 11 +- em-format/e-mail-formatter-image.c | 14 +- em-format/e-mail-formatter-message-rfc822.c | 12 +- em-format/e-mail-formatter-print-headers.c | 14 +- em-format/e-mail-formatter-print.c | 2 - em-format/e-mail-formatter-quote-attachment.c | 11 +- em-format/e-mail-formatter-quote-headers.c | 14 +- em-format/e-mail-formatter-quote-message-rfc822.c | 13 +- em-format/e-mail-formatter-quote-text-enriched.c | 10 +- em-format/e-mail-formatter-quote-text-html.c | 13 +- em-format/e-mail-formatter-quote-text-plain.c | 12 +- em-format/e-mail-formatter-secure-button.c | 4 +- em-format/e-mail-formatter-source.c | 9 +- em-format/e-mail-formatter-text-enriched.c | 9 +- em-format/e-mail-formatter-text-html.c | 16 +- em-format/e-mail-formatter-text-plain.c | 11 +- em-format/e-mail-formatter-utils.c | 12 +- em-format/e-mail-formatter.c | 2 - em-format/e-mail-formatter.h | 5 +- em-format/e-mail-parser-application-mbox.c | 10 +- em-format/e-mail-parser-application-smime.c | 10 +- em-format/e-mail-parser-attachment-bar.c | 9 +- em-format/e-mail-parser-extension.c | 2 - em-format/e-mail-parser-extension.h | 2 +- em-format/e-mail-parser-headers.c | 9 +- em-format/e-mail-parser-image.c | 9 +- em-format/e-mail-parser-inlinepgp-encrypted.c | 12 +- em-format/e-mail-parser-inlinepgp-signed.c | 12 +- em-format/e-mail-parser-message-deliverystatus.c | 8 +- em-format/e-mail-parser-message-external.c | 12 +- em-format/e-mail-parser-message-rfc822.c | 12 +- em-format/e-mail-parser-message.c | 11 +- em-format/e-mail-parser-multipart-alternative.c | 10 +- em-format/e-mail-parser-multipart-appledouble.c | 5 +- em-format/e-mail-parser-multipart-digest.c | 8 +- em-format/e-mail-parser-multipart-encrypted.c | 9 +- em-format/e-mail-parser-multipart-mixed.c | 10 +- em-format/e-mail-parser-multipart-related.c | 10 +- em-format/e-mail-parser-multipart-signed.c | 9 +- em-format/e-mail-parser-secure-button.c | 4 +- em-format/e-mail-parser-source.c | 7 +- em-format/e-mail-parser-text-enriched.c | 9 +- em-format/e-mail-parser-text-html.c | 12 +- em-format/e-mail-parser-text-plain.c | 13 +- em-format/e-mail-parser.c | 14 +- em-format/e-mail-part-attachment-bar.h | 2 - em-format/e-mail-part-utils.c | 1 - em-format/e-mail-part.h | 3 +- evolution-shell.pc.in | 4 +- evolution-zip.in | 7 - filter/Makefile.am | 76 - filter/e-filter-code.c | 102 - filter/e-filter-code.h | 68 - filter/e-filter-color.c | 163 - filter/e-filter-color.h | 70 - filter/e-filter-datespec.c | 515 --- filter/e-filter-datespec.h | 87 - filter/e-filter-element.c | 446 --- filter/e-filter-element.h | 120 - filter/e-filter-file.c | 262 -- filter/e-filter-file.h | 74 - filter/e-filter-input.c | 305 -- filter/e-filter-input.h | 74 - filter/e-filter-int.c | 230 -- filter/e-filter-int.h | 77 - filter/e-filter-option.c | 566 --- filter/e-filter-option.h | 97 - filter/e-filter-part.c | 513 --- filter/e-filter-part.h | 107 - filter/e-filter-rule.c | 1242 ------ filter/e-filter-rule.h | 159 - filter/e-rule-context.c | 1027 ----- filter/e-rule-context.h | 214 -- filter/e-rule-editor.c | 920 ----- filter/e-rule-editor.h | 121 - filter/filter.error.xml | 34 - filter/filter.ui | 591 --- libemail-engine/Makefile.am | 22 +- libemail-engine/e-mail-session.c | 5 +- libemail-engine/e-mail-session.h | 2 +- libemail-engine/e-mail-utils.c | 2 +- libemail-engine/em-filter-folder-element.c | 226 ++ libemail-engine/em-filter-folder-element.h | 74 + libemail-engine/em-vfolder-context.c | 110 + libemail-engine/em-vfolder-context.h | 70 + libemail-engine/em-vfolder-rule.c | 494 +++ libemail-engine/em-vfolder-rule.h | 102 + libemail-engine/libemail-engine.pc.in | 2 +- libemail-engine/mail-folder-cache.c | 2 +- libemail-engine/mail-mt.c | 663 ++++ libemail-engine/mail-mt.h | 125 + libemail-engine/mail-ops.c | 2 +- libemail-engine/mail-ops.h | 2 +- libemail-engine/mail-vfolder.c | 15 +- libemail-engine/mail-vfolder.h | 5 +- libemail-utils/Makefile.am | 44 - libemail-utils/em-filter-folder-element.c | 228 -- libemail-utils/em-filter-folder-element.h | 74 - libemail-utils/em-vfolder-context.c | 112 - libemail-utils/em-vfolder-context.h | 70 - libemail-utils/em-vfolder-rule.c | 496 --- libemail-utils/em-vfolder-rule.h | 102 - libemail-utils/libemail-utils.pc.in | 16 - libemail-utils/mail-mt.c | 663 ---- libemail-utils/mail-mt.h | 124 - libevolution-utils/Makefile.am | 45 - libevolution-utils/e-alert-dialog.c | 403 -- libevolution-utils/e-alert-dialog.h | 80 - libevolution-utils/e-alert-sink.c | 93 - libevolution-utils/e-alert-sink.h | 62 - libevolution-utils/e-alert.c | 1003 ----- libevolution-utils/e-alert.h | 119 - libevolution-utils/e-xml-utils.c | 447 --- libevolution-utils/e-xml-utils.h | 93 - libevolution-utils/evolution-util.c | 327 -- libevolution-utils/evolution-util.h | 47 - libevolution-utils/libevolution-utils.pc.in | 16 - mail/Makefile.am | 29 +- mail/e-mail-account-store.c | 3 - mail/e-mail-backend.c | 3 - mail/e-mail-browser.c | 4 - mail/e-mail-browser.h | 2 +- mail/e-mail-config-activity-page.c | 5 - mail/e-mail-config-activity-page.h | 2 +- mail/e-mail-config-assistant.c | 2 - mail/e-mail-config-auth-check.c | 7 +- mail/e-mail-config-identity-page.c | 4 +- mail/e-mail-config-provider-page.h | 2 +- mail/e-mail-config-window.c | 8 +- mail/e-mail-display.c | 31 +- mail/e-mail-display.h | 3 +- mail/e-mail-folder-pane.c | 18 +- mail/e-mail-migrate.c | 13 +- mail/e-mail-notebook-view.h | 1 - mail/e-mail-paned-view.c | 18 +- mail/e-mail-printer.c | 9 +- mail/e-mail-reader-utils.c | 35 +- mail/e-mail-reader.c | 46 +- mail/e-mail-reader.h | 4 +- mail/e-mail-request.c | 11 +- mail/e-mail-tag-editor.c | 1 - mail/e-mail-ui-session.c | 4 +- mail/e-mail-ui-session.h | 3 +- mail/e-mail-view.h | 1 - mail/em-composer-utils.c | 7 +- mail/em-config.h | 3 +- mail/em-event.h | 3 +- mail/em-filter-context.c | 2 - mail/em-filter-context.h | 2 +- mail/em-filter-editor-folder-element.c | 5 +- mail/em-filter-editor-folder-element.h | 4 +- mail/em-filter-editor.h | 3 +- mail/em-filter-rule.h | 2 +- mail/em-filter-source-element.c | 12 +- mail/em-filter-source-element.h | 2 +- mail/em-folder-properties.c | 2 +- mail/em-folder-properties.h | 1 - mail/em-folder-tree-model.c | 5 +- mail/em-folder-tree.c | 9 +- mail/em-folder-tree.h | 3 +- mail/em-folder-utils.c | 9 +- mail/em-search-context.c | 7 +- mail/em-search-context.h | 2 +- mail/em-subscription-editor.c | 10 +- mail/em-utils.c | 27 +- mail/em-vfolder-editor-context.c | 12 +- mail/em-vfolder-editor-context.h | 4 +- mail/em-vfolder-editor-rule.c | 1 - mail/em-vfolder-editor-rule.h | 4 +- mail/em-vfolder-editor.h | 3 +- mail/importers/Makefile.am | 6 +- mail/importers/elm-importer.c | 3 +- mail/importers/evolution-mbox-importer.c | 6 +- mail/importers/mail-importer.c | 4 +- mail/importers/mail-importer.h | 2 +- mail/importers/pine-importer.c | 3 +- mail/mail-autofilter.c | 3 - mail/mail-autofilter.h | 3 +- mail/mail-send-recv.c | 3 +- mail/mail-vfolder-ui.c | 5 +- mail/mail-vfolder-ui.h | 7 +- mail/message-list.c | 30 +- mail/message-list.h | 2 +- maint/Makefile.am | 2 +- modules/addressbook/Makefile.am | 8 - modules/addressbook/autocompletion-config.c | 4 - modules/addressbook/autocompletion-config.h | 1 - modules/addressbook/e-book-config-hook.c | 1 - .../e-book-config-name-selector-entry.c | 2 +- modules/addressbook/e-book-shell-backend.c | 5 - modules/addressbook/e-book-shell-content.c | 3 - modules/addressbook/e-book-shell-content.h | 2 - modules/addressbook/e-book-shell-sidebar.h | 2 - modules/addressbook/e-book-shell-view-actions.c | 6 - modules/addressbook/e-book-shell-view-private.c | 1 - modules/addressbook/e-book-shell-view-private.h | 7 - modules/audio-inline/Makefile.am | 5 +- .../audio-inline/e-mail-formatter-audio-inline.c | 8 +- modules/backup-restore/Makefile.am | 10 +- .../backup-restore/e-mail-config-restore-page.c | 4 +- modules/backup-restore/evolution-backup-restore.c | 6 +- modules/bogofilter/Makefile.am | 1 - modules/book-config-google/Makefile.am | 10 +- .../evolution-book-config-google.c | 4 +- modules/book-config-ldap/Makefile.am | 7 +- .../book-config-ldap/evolution-book-config-ldap.c | 4 +- modules/book-config-local/Makefile.am | 10 +- .../evolution-book-config-local.c | 3 +- modules/book-config-webdav/Makefile.am | 10 +- .../evolution-book-config-webdav.c | 3 +- modules/cal-config-caldav/Makefile.am | 6 +- modules/cal-config-caldav/e-caldav-chooser.c | 2 +- .../evolution-cal-config-caldav.c | 4 +- modules/cal-config-contacts/Makefile.am | 10 +- modules/cal-config-contacts/e-contacts-selector.h | 2 +- .../evolution-cal-config-contacts.c | 4 +- modules/cal-config-google/Makefile.am | 6 +- modules/cal-config-google/e-google-chooser.c | 2 +- .../evolution-cal-config-google.c | 3 +- modules/cal-config-local/Makefile.am | 10 +- .../cal-config-local/evolution-cal-config-local.c | 3 +- modules/cal-config-weather/Makefile.am | 6 +- .../evolution-cal-config-weather.c | 3 +- modules/cal-config-webcal/Makefile.am | 10 +- .../evolution-cal-config-webcal.c | 4 +- modules/calendar/Makefile.am | 9 +- modules/calendar/e-cal-attachment-handler.c | 1 - modules/calendar/e-cal-attachment-handler.h | 2 +- modules/calendar/e-cal-config-calendar-item.c | 1 - modules/calendar/e-cal-config-date-edit.c | 1 - modules/calendar/e-cal-config-hook.c | 1 - modules/calendar/e-cal-event-hook.c | 1 - modules/calendar/e-cal-shell-backend.c | 5 - modules/calendar/e-cal-shell-content.c | 4 - modules/calendar/e-cal-shell-content.h | 1 - modules/calendar/e-cal-shell-sidebar.c | 4 - modules/calendar/e-cal-shell-sidebar.h | 2 - modules/calendar/e-cal-shell-view-actions.c | 1 - modules/calendar/e-cal-shell-view-private.c | 1 - modules/calendar/e-cal-shell-view-private.h | 11 - modules/calendar/e-calendar-preferences.c | 6 - modules/calendar/e-calendar-preferences.h | 3 - modules/calendar/e-memo-shell-backend.c | 3 - modules/calendar/e-memo-shell-content.c | 4 - modules/calendar/e-memo-shell-content.h | 3 - modules/calendar/e-memo-shell-sidebar.c | 2 - modules/calendar/e-memo-shell-sidebar.h | 1 - modules/calendar/e-memo-shell-view-actions.c | 1 - modules/calendar/e-memo-shell-view-private.c | 2 - modules/calendar/e-memo-shell-view-private.h | 7 - modules/calendar/e-task-shell-backend.c | 3 - modules/calendar/e-task-shell-content.c | 4 - modules/calendar/e-task-shell-content.h | 3 - modules/calendar/e-task-shell-sidebar.c | 2 - modules/calendar/e-task-shell-sidebar.h | 1 - modules/calendar/e-task-shell-view-actions.c | 1 - modules/calendar/e-task-shell-view-private.c | 2 - modules/calendar/e-task-shell-view-private.h | 8 - modules/composer-autosave/Makefile.am | 5 +- modules/composer-autosave/e-composer-autosave.c | 1 - modules/composer-autosave/e-composer-registry.c | 1 - modules/imap-features/Makefile.am | 6 +- modules/itip-formatter/Makefile.am | 12 +- .../itip-formatter/e-conflict-search-selector.h | 2 +- modules/itip-formatter/e-mail-parser-itip.c | 5 +- modules/itip-formatter/e-mail-part-itip.h | 1 - modules/itip-formatter/itip-view.c | 13 +- modules/itip-formatter/plugin/Makefile.am | 10 +- modules/mail-config/Makefile.am | 7 +- .../mail-config/e-mail-config-remote-accounts.c | 2 - modules/mail-config/e-mail-config-smtp-backend.c | 2 - modules/mail/Makefile.am | 12 +- modules/mail/e-mail-attachment-handler.c | 1 - modules/mail/e-mail-attachment-handler.h | 2 +- modules/mail/e-mail-config-hook.c | 1 - modules/mail/e-mail-config-web-view-gtkhtml.c | 1 - modules/mail/e-mail-config-web-view.c | 1 - modules/mail/e-mail-event-hook.c | 1 - modules/mail/e-mail-shell-backend.c | 7 - modules/mail/e-mail-shell-backend.h | 3 +- modules/mail/e-mail-shell-content.c | 6 - modules/mail/e-mail-shell-view-private.c | 3 - modules/mail/e-mail-shell-view-private.h | 9 - modules/mail/e-mail-shell-view.c | 1 - modules/mail/em-account-prefs.c | 2 - modules/mail/em-account-prefs.h | 2 - modules/mail/em-composer-prefs.c | 6 - modules/mail/em-composer-prefs.h | 1 - modules/mail/em-mailer-prefs.c | 6 - modules/mail/em-mailer-prefs.h | 1 - modules/mail/em-network-prefs.c | 1 - modules/mail/em-network-prefs.h | 2 +- modules/mailto-handler/Makefile.am | 2 + modules/mdn/Makefile.am | 4 +- modules/mdn/evolution-mdn.c | 2 - modules/offline-alert/Makefile.am | 10 +- modules/offline-alert/evolution-offline-alert.c | 1 - modules/online-accounts/Makefile.am | 5 +- modules/plugin-lib/Makefile.am | 10 +- modules/plugin-lib/e-plugin-lib.h | 2 +- modules/plugin-manager/Makefile.am | 9 +- modules/plugin-manager/evolution-plugin-manager.c | 1 - modules/prefer-plain/Makefile.am | 11 +- modules/prefer-plain/plugin/Makefile.am | 9 +- modules/prefer-plain/plugin/config-ui.c | 2 - modules/spamassassin/Makefile.am | 9 +- modules/spamassassin/evolution-spamassassin.c | 1 - modules/startup-wizard/Makefile.am | 8 +- modules/startup-wizard/e-mail-config-import-page.c | 2 - modules/startup-wizard/e-mail-config-import-page.h | 2 +- .../e-mail-config-import-progress-page.h | 2 - modules/startup-wizard/e-startup-assistant.c | 2 + modules/startup-wizard/evolution-startup-wizard.c | 3 - modules/text-highlight/Makefile.am | 10 +- modules/tnef-attachment/Makefile.am | 5 +- .../e-mail-parser-tnef-attachment.c | 19 +- modules/vcard-inline/Makefile.am | 9 +- modules/vcard-inline/e-mail-parser-vcard-inline.c | 1 - modules/web-inspector/Makefile.am | 10 +- modules/web-inspector/evolution-web-inspector.c | 3 +- plugins/attachment-reminder/Makefile.am | 5 +- plugins/attachment-reminder/attachment-reminder.c | 7 - plugins/bbdb/Makefile.am | 4 +- plugins/bbdb/bbdb.c | 3 - plugins/bbdb/gaimbuddies.c | 5 +- plugins/dbx-import/Makefile.am | 10 +- plugins/dbx-import/dbx-importer.c | 7 +- plugins/email-custom-header/Makefile.am | 6 +- plugins/email-custom-header/email-custom-header.c | 3 +- plugins/external-editor/Makefile.am | 4 +- plugins/external-editor/external-editor.c | 1 - plugins/face/Makefile.am | 6 +- plugins/face/face.c | 3 - plugins/mail-notification/Makefile.am | 4 +- plugins/mail-notification/mail-notification.c | 1 - plugins/mail-to-task/Makefile.am | 11 +- plugins/mail-to-task/mail-to-task.c | 6 - plugins/mailing-list-actions/Makefile.am | 5 +- .../mailing-list-actions/mailing-list-actions.c | 3 +- plugins/mark-all-read/Makefile.am | 10 +- plugins/mark-all-read/mark-all-read.c | 1 - plugins/pst-import/Makefile.am | 7 +- plugins/pst-import/pst-importer.c | 13 +- plugins/publish-calendar/Makefile.am | 12 +- plugins/publish-calendar/publish-calendar.c | 3 - plugins/publish-calendar/publish-location.c | 3 +- plugins/publish-calendar/url-editor-dialog.h | 1 - plugins/save-calendar/Makefile.am | 10 +- plugins/save-calendar/format-handler.h | 4 +- plugins/save-calendar/save-calendar.c | 3 - plugins/templates/Makefile.am | 4 +- plugins/templates/templates.c | 5 - po/POTFILES.in | 5 +- shell/Makefile.am | 25 +- shell/e-convert-local-mail.c | 1 - shell/e-shell-backend.c | 36 +- shell/e-shell-backend.h | 2 +- shell/e-shell-content.c | 7 - shell/e-shell-migrate.c | 4 - shell/e-shell-searchbar.c | 3 - shell/e-shell-searchbar.h | 1 - shell/e-shell-sidebar.c | 2 - shell/e-shell-taskbar.c | 1 - shell/e-shell-utils.c | 2 - shell/e-shell-utils.h | 2 - shell/e-shell-view.c | 8 - shell/e-shell-view.h | 7 +- shell/e-shell-window-actions.c | 7 - shell/e-shell-window-private.h | 11 - shell/e-shell-window.c | 44 +- shell/e-shell-window.h | 5 +- shell/e-shell.c | 3 - shell/e-shell.h | 3 +- shell/es-event.h | 2 +- shell/main.c | 8 - smime/gui/Makefile.am | 8 +- smime/gui/certificate-manager.c | 4 - smime/gui/certificate-manager.h | 1 - smime/gui/component.c | 3 +- smime/lib/Makefile.am | 5 +- smime/lib/e-cert-db.c | 4 +- smime/lib/e-pkcs12.c | 2 +- widgets/LICENSE | 1 - widgets/Makefile.am | 9 - widgets/e-timezone-dialog/Makefile.am | 29 - widgets/e-timezone-dialog/e-timezone-dialog.c | 870 ----- widgets/e-timezone-dialog/e-timezone-dialog.h | 73 - widgets/e-timezone-dialog/e-timezone-dialog.ui | 312 -- widgets/menus/Makefile.am | 58 - widgets/menus/gal-define-views-dialog.c | 451 --- widgets/menus/gal-define-views-dialog.h | 73 - widgets/menus/gal-define-views-model.c | 353 -- widgets/menus/gal-define-views-model.h | 66 - widgets/menus/gal-define-views.ui | 177 - widgets/menus/gal-view-collection.c | 829 ---- widgets/menus/gal-view-collection.h | 146 - widgets/menus/gal-view-etable.c | 335 -- widgets/menus/gal-view-etable.h | 92 - widgets/menus/gal-view-factory-etable.c | 196 - widgets/menus/gal-view-factory-etable.h | 73 - widgets/menus/gal-view-factory.c | 103 - widgets/menus/gal-view-factory.h | 81 - widgets/menus/gal-view-instance-save-as-dialog.c | 358 -- widgets/menus/gal-view-instance-save-as-dialog.h | 88 - widgets/menus/gal-view-instance-save-as-dialog.ui | 133 - widgets/menus/gal-view-instance.c | 503 --- widgets/menus/gal-view-instance.h | 110 - widgets/menus/gal-view-new-dialog.c | 290 -- widgets/menus/gal-view-new-dialog.h | 77 - widgets/menus/gal-view-new-dialog.ui | 177 - widgets/menus/gal-view.c | 282 -- widgets/menus/gal-view.h | 94 - widgets/misc/Makefile.am | 289 -- widgets/misc/e-action-combo-box.c | 578 --- widgets/misc/e-action-combo-box.h | 85 - widgets/misc/e-activity-bar.c | 366 -- widgets/misc/e-activity-bar.h | 67 - widgets/misc/e-activity-proxy.c | 381 -- widgets/misc/e-activity-proxy.h | 70 - widgets/misc/e-alarm-selector.c | 94 - widgets/misc/e-alarm-selector.h | 63 - widgets/misc/e-alert-bar.c | 390 -- widgets/misc/e-alert-bar.h | 67 - widgets/misc/e-attachment-bar.c | 778 ---- widgets/misc/e-attachment-bar.h | 79 - widgets/misc/e-attachment-button.c | 868 ----- widgets/misc/e-attachment-button.h | 87 - widgets/misc/e-attachment-dialog.c | 425 -- widgets/misc/e-attachment-dialog.h | 73 - widgets/misc/e-attachment-handler-image.c | 248 -- widgets/misc/e-attachment-handler-image.h | 65 - widgets/misc/e-attachment-handler-sendto.c | 229 -- widgets/misc/e-attachment-handler-sendto.h | 62 - widgets/misc/e-attachment-handler.c | 133 - widgets/misc/e-attachment-handler.h | 80 - widgets/misc/e-attachment-icon-view.c | 570 --- widgets/misc/e-attachment-icon-view.h | 67 - widgets/misc/e-attachment-paned.c | 904 ----- widgets/misc/e-attachment-paned.h | 95 - widgets/misc/e-attachment-store.c | 1281 ------- widgets/misc/e-attachment-store.h | 133 - widgets/misc/e-attachment-tree-view.c | 623 --- widgets/misc/e-attachment-tree-view.h | 66 - widgets/misc/e-attachment-view.c | 1907 --------- widgets/misc/e-attachment-view.h | 240 -- widgets/misc/e-attachment.c | 2883 -------------- widgets/misc/e-attachment.h | 155 - widgets/misc/e-auth-combo-box.c | 266 -- widgets/misc/e-auth-combo-box.h | 71 - widgets/misc/e-autocomplete-selector.c | 96 - widgets/misc/e-autocomplete-selector.h | 64 - widgets/misc/e-book-source-config.c | 287 -- widgets/misc/e-book-source-config.h | 67 - widgets/misc/e-buffer-tagger.c | 691 ---- widgets/misc/e-buffer-tagger.h | 35 - widgets/misc/e-cal-source-config.c | 431 --- widgets/misc/e-cal-source-config.h | 72 - widgets/misc/e-calendar-item.c | 3772 ------------------ widgets/misc/e-calendar-item.h | 390 -- widgets/misc/e-calendar.c | 848 ---- widgets/misc/e-calendar.h | 108 - widgets/misc/e-canvas-background.c | 279 -- widgets/misc/e-canvas-background.h | 70 - widgets/misc/e-canvas-utils.c | 222 -- widgets/misc/e-canvas-utils.h | 55 - widgets/misc/e-canvas-vbox.c | 411 -- widgets/misc/e-canvas-vbox.h | 85 - widgets/misc/e-canvas.c | 882 ----- widgets/misc/e-canvas.h | 137 - widgets/misc/e-charset-combo-box.c | 407 -- widgets/misc/e-charset-combo-box.h | 69 - widgets/misc/e-contact-map-window.c | 500 --- widgets/misc/e-contact-map-window.h | 77 - widgets/misc/e-contact-map.c | 407 -- widgets/misc/e-contact-map.h | 106 - widgets/misc/e-contact-marker.c | 624 --- widgets/misc/e-contact-marker.h | 84 - widgets/misc/e-dateedit.c | 2498 ------------ widgets/misc/e-dateedit.h | 215 -- widgets/misc/e-focus-tracker.c | 886 ----- widgets/misc/e-focus-tracker.h | 100 - widgets/misc/e-image-chooser.c | 562 --- widgets/misc/e-image-chooser.h | 76 - widgets/misc/e-import-assistant.c | 1436 ------- widgets/misc/e-import-assistant.h | 68 - widgets/misc/e-interval-chooser.c | 214 -- widgets/misc/e-interval-chooser.h | 68 - widgets/misc/e-mail-identity-combo-box.c | 385 -- widgets/misc/e-mail-identity-combo-box.h | 71 - widgets/misc/e-mail-signature-combo-box.c | 668 ---- widgets/misc/e-mail-signature-combo-box.h | 91 - widgets/misc/e-mail-signature-editor.c | 914 ----- widgets/misc/e-mail-signature-editor.h | 82 - widgets/misc/e-mail-signature-manager.c | 708 ---- widgets/misc/e-mail-signature-manager.h | 88 - widgets/misc/e-mail-signature-preview.c | 358 -- widgets/misc/e-mail-signature-preview.h | 79 - widgets/misc/e-mail-signature-script-dialog.c | 731 ---- widgets/misc/e-mail-signature-script-dialog.h | 90 - widgets/misc/e-mail-signature-tree-view.c | 395 -- widgets/misc/e-mail-signature-tree-view.h | 76 - widgets/misc/e-map.c | 1430 ------- widgets/misc/e-map.h | 151 - widgets/misc/e-menu-tool-action.c | 59 - widgets/misc/e-menu-tool-action.h | 71 - widgets/misc/e-menu-tool-button.c | 273 -- widgets/misc/e-menu-tool-button.h | 73 - widgets/misc/e-online-button.c | 210 - widgets/misc/e-online-button.h | 65 - widgets/misc/e-paned.c | 503 --- widgets/misc/e-paned.h | 78 - widgets/misc/e-picture-gallery.c | 437 --- widgets/misc/e-picture-gallery.h | 67 - widgets/misc/e-popup-action.c | 408 -- widgets/misc/e-popup-action.h | 92 - widgets/misc/e-port-entry.c | 549 --- widgets/misc/e-port-entry.h | 87 - widgets/misc/e-preferences-window.c | 642 ---- widgets/misc/e-preferences-window.h | 84 - widgets/misc/e-preview-pane.c | 323 -- widgets/misc/e-preview-pane.h | 75 - widgets/misc/e-printable.c | 226 -- widgets/misc/e-printable.h | 89 - widgets/misc/e-search-bar.c | 771 ---- widgets/misc/e-search-bar.h | 84 - widgets/misc/e-selectable.c | 168 - widgets/misc/e-selectable.h | 80 - widgets/misc/e-selection-model-array.c | 647 ---- widgets/misc/e-selection-model-array.h | 91 - widgets/misc/e-selection-model-simple.c | 119 - widgets/misc/e-selection-model-simple.h | 66 - widgets/misc/e-selection-model.c | 813 ---- widgets/misc/e-selection-model.h | 205 - widgets/misc/e-send-options.c | 767 ---- widgets/misc/e-send-options.h | 127 - widgets/misc/e-send-options.ui | 949 ----- widgets/misc/e-source-config-backend.c | 140 - widgets/misc/e-source-config-backend.h | 94 - widgets/misc/e-source-config-dialog.c | 394 -- widgets/misc/e-source-config-dialog.h | 65 - widgets/misc/e-source-config.c | 1448 ------- widgets/misc/e-source-config.h | 116 - widgets/misc/e-spell-entry.c | 866 ----- widgets/misc/e-spell-entry.h | 59 - widgets/misc/e-url-entry.c | 157 - widgets/misc/e-url-entry.h | 56 - widgets/misc/e-web-view-gtkhtml.c | 2318 ----------- widgets/misc/e-web-view-gtkhtml.h | 173 - widgets/misc/e-web-view-preview.c | 474 --- widgets/misc/e-web-view-preview.h | 111 - widgets/misc/e-web-view.c | 2946 -------------- widgets/misc/e-web-view.h | 220 -- widgets/misc/ea-calendar-cell.c | 405 -- widgets/misc/ea-calendar-cell.h | 86 - widgets/misc/ea-calendar-item.c | 1372 ------- widgets/misc/ea-calendar-item.h | 67 - widgets/misc/ea-cell-table.c | 215 -- widgets/misc/ea-cell-table.h | 59 - widgets/misc/ea-widgets.c | 36 - widgets/misc/ea-widgets.h | 32 - widgets/misc/test-calendar.c | 146 - widgets/misc/test-dateedit.c | 299 -- widgets/misc/test-mail-signatures.c | 199 - widgets/misc/test-preferences-window.c | 108 - widgets/misc/test-source-config.c | 57 - widgets/misc/widgets.error.xml | 28 - widgets/table/Makefile.am | 193 - widgets/table/arrow-down.xpm | 21 - widgets/table/arrow-up.xpm | 21 - widgets/table/check-empty.xpm | 21 - widgets/table/check-filled.xpm | 21 - widgets/table/e-cell-checkbox.c | 104 - widgets/table/e-cell-checkbox.h | 67 - widgets/table/e-cell-combo.c | 839 ---- widgets/table/e-cell-combo.h | 85 - widgets/table/e-cell-date-edit.c | 1042 ----- widgets/table/e-cell-date-edit.h | 119 - widgets/table/e-cell-date.c | 130 - widgets/table/e-cell-date.h | 70 - widgets/table/e-cell-hbox.c | 354 -- widgets/table/e-cell-hbox.h | 86 - widgets/table/e-cell-number.c | 94 - widgets/table/e-cell-number.h | 67 - widgets/table/e-cell-percent.c | 160 - widgets/table/e-cell-percent.h | 72 - widgets/table/e-cell-pixbuf.c | 389 -- widgets/table/e-cell-pixbuf.h | 71 - widgets/table/e-cell-popup.c | 551 --- widgets/table/e-cell-popup.h | 113 - widgets/table/e-cell-size.c | 114 - widgets/table/e-cell-size.h | 68 - widgets/table/e-cell-text.c | 2811 -------------- widgets/table/e-cell-text.h | 190 - widgets/table/e-cell-toggle.c | 470 --- widgets/table/e-cell-toggle.h | 78 - widgets/table/e-cell-tree.c | 881 ----- widgets/table/e-cell-tree.h | 85 - widgets/table/e-cell-vbox.c | 342 -- widgets/table/e-cell-vbox.h | 88 - widgets/table/e-cell.c | 680 ---- widgets/table/e-cell.h | 294 -- widgets/table/e-popup-menu.c | 112 - widgets/table/e-popup-menu.h | 55 - widgets/table/e-table-click-to-add.c | 666 ---- widgets/table/e-table-click-to-add.h | 94 - widgets/table/e-table-col-dnd.h | 39 - widgets/table/e-table-col.c | 223 -- widgets/table/e-table-col.h | 110 - widgets/table/e-table-column-specification.c | 158 - widgets/table/e-table-column-specification.h | 89 - widgets/table/e-table-config.c | 1482 ------- widgets/table/e-table-config.h | 129 - widgets/table/e-table-config.ui | 1594 -------- widgets/table/e-table-defines.h | 40 - widgets/table/e-table-extras.c | 412 -- widgets/table/e-table-extras.h | 90 - widgets/table/e-table-field-chooser-dialog.c | 236 -- widgets/table/e-table-field-chooser-dialog.h | 72 - widgets/table/e-table-field-chooser-item.c | 751 ---- widgets/table/e-table-field-chooser-item.h | 92 - widgets/table/e-table-field-chooser.c | 337 -- widgets/table/e-table-field-chooser.h | 79 - widgets/table/e-table-group-container.c | 1668 -------- widgets/table/e-table-group-container.h | 133 - widgets/table/e-table-group-leaf.c | 816 ---- widgets/table/e-table-group-leaf.h | 105 - widgets/table/e-table-group.c | 773 ---- widgets/table/e-table-group.h | 237 -- widgets/table/e-table-header-item.c | 2227 ----------- widgets/table/e-table-header-item.h | 143 - widgets/table/e-table-header-utils.c | 282 -- widgets/table/e-table-header-utils.h | 49 - widgets/table/e-table-header.c | 1014 ----- widgets/table/e-table-header.h | 139 - widgets/table/e-table-item.c | 4041 -------------------- widgets/table/e-table-item.h | 256 -- widgets/table/e-table-memory-callbacks.c | 236 -- widgets/table/e-table-memory-callbacks.h | 144 - widgets/table/e-table-memory-store.c | 639 ---- widgets/table/e-table-memory-store.h | 151 - widgets/table/e-table-memory.c | 273 -- widgets/table/e-table-memory.h | 86 - widgets/table/e-table-model.c | 682 ---- widgets/table/e-table-model.h | 213 -- widgets/table/e-table-one.c | 254 -- widgets/table/e-table-one.h | 71 - widgets/table/e-table-search.c | 235 -- widgets/table/e-table-search.h | 82 - widgets/table/e-table-selection-model.c | 385 -- widgets/table/e-table-selection-model.h | 87 - widgets/table/e-table-sort-info.c | 483 --- widgets/table/e-table-sort-info.h | 129 - widgets/table/e-table-sorted-variable.c | 237 -- widgets/table/e-table-sorted-variable.h | 81 - widgets/table/e-table-sorted.c | 330 -- widgets/table/e-table-sorted.h | 80 - widgets/table/e-table-sorter.c | 520 --- widgets/table/e-table-sorter.h | 90 - widgets/table/e-table-sorting-utils.c | 492 --- widgets/table/e-table-sorting-utils.h | 91 - widgets/table/e-table-specification.c | 436 --- widgets/table/e-table-specification.h | 111 - widgets/table/e-table-state.c | 321 -- widgets/table/e-table-state.h | 84 - widgets/table/e-table-subset-variable.c | 269 -- widgets/table/e-table-subset-variable.h | 101 - widgets/table/e-table-subset.c | 569 --- widgets/table/e-table-subset.h | 116 - widgets/table/e-table-utils.c | 225 -- widgets/table/e-table-utils.h | 50 - widgets/table/e-table-without.c | 414 -- widgets/table/e-table-without.h | 100 - widgets/table/e-table.c | 3626 ------------------ widgets/table/e-table.dia | Bin 4514 -> 0 bytes widgets/table/e-table.h | 398 -- widgets/table/e-tree-memory-callbacks.c | 316 -- widgets/table/e-tree-memory-callbacks.h | 178 - widgets/table/e-tree-memory.c | 744 ---- widgets/table/e-tree-memory.h | 119 - widgets/table/e-tree-model.c | 1177 ------ widgets/table/e-tree-model.h | 294 -- widgets/table/e-tree-selection-model.c | 939 ----- widgets/table/e-tree-selection-model.h | 91 - widgets/table/e-tree-sorted.c | 1434 ------- widgets/table/e-tree-sorted.h | 99 - widgets/table/e-tree-table-adapter.c | 1413 ------- widgets/table/e-tree-table-adapter.h | 133 - widgets/table/e-tree.c | 3956 ------------------- widgets/table/e-tree.h | 372 -- widgets/table/gal-a11y-e-cell-popup.c | 153 - widgets/table/gal-a11y-e-cell-popup.h | 60 - widgets/table/gal-a11y-e-cell-registry.c | 151 - widgets/table/gal-a11y-e-cell-registry.h | 70 - widgets/table/gal-a11y-e-cell-toggle.c | 198 - widgets/table/gal-a11y-e-cell-toggle.h | 63 - widgets/table/gal-a11y-e-cell-tree.c | 266 -- widgets/table/gal-a11y-e-cell-tree.h | 62 - widgets/table/gal-a11y-e-cell-vbox.c | 235 -- widgets/table/gal-a11y-e-cell-vbox.h | 63 - widgets/table/gal-a11y-e-cell.c | 648 ---- widgets/table/gal-a11y-e-cell.h | 108 - .../table/gal-a11y-e-table-click-to-add-factory.c | 108 - .../table/gal-a11y-e-table-click-to-add-factory.h | 48 - widgets/table/gal-a11y-e-table-click-to-add.c | 358 -- widgets/table/gal-a11y-e-table-click-to-add.h | 54 - widgets/table/gal-a11y-e-table-column-header.c | 241 -- widgets/table/gal-a11y-e-table-column-header.h | 52 - widgets/table/gal-a11y-e-table-factory.c | 101 - widgets/table/gal-a11y-e-table-factory.h | 49 - widgets/table/gal-a11y-e-table-item-factory.c | 107 - widgets/table/gal-a11y-e-table-item-factory.h | 48 - widgets/table/gal-a11y-e-table-item.c | 1437 ------- widgets/table/gal-a11y-e-table-item.h | 57 - widgets/table/gal-a11y-e-table.c | 315 -- widgets/table/gal-a11y-e-table.h | 58 - widgets/table/gal-a11y-e-tree-factory.c | 99 - widgets/table/gal-a11y-e-tree-factory.h | 48 - widgets/table/gal-a11y-e-tree.c | 196 - widgets/table/gal-a11y-e-tree.h | 57 - widgets/table/sample.table | 45 - widgets/table/spec.xml | 21 - widgets/table/tree-expanded.xpm | 23 - widgets/table/tree-unexpanded.xpm | 23 - widgets/text/Makefile.am | 44 - widgets/text/e-reflow-model.c | 411 -- widgets/text/e-reflow-model.h | 125 - widgets/text/e-reflow.c | 1732 --------- widgets/text/e-reflow.h | 140 - widgets/text/e-text-model-repos.c | 74 - widgets/text/e-text-model-repos.h | 54 - widgets/text/e-text-model.c | 642 ---- widgets/text/e-text-model.h | 108 - widgets/text/e-text.c | 3405 ----------------- widgets/text/e-text.h | 232 -- widgets/text/gal-a11y-e-text-factory.c | 103 - widgets/text/gal-a11y-e-text-factory.h | 48 - widgets/text/gal-a11y-e-text.c | 1141 ------ widgets/text/gal-a11y-e-text.h | 55 - 1391 files changed, 176822 insertions(+), 152949 deletions(-) delete mode 100644 a11y/Makefile.am delete mode 100644 a11y/ea-factory.h delete mode 100644 a11y/gal-a11y-factory.h delete mode 100644 a11y/gal-a11y-util.c delete mode 100644 a11y/gal-a11y-util.h create mode 100644 doc/reference/libeshell/Makefile.am create mode 100644 doc/reference/libeshell/libeshell-docs.sgml create mode 100644 doc/reference/libeshell/libeshell-overrides.txt create mode 100644 doc/reference/libeshell/libeshell-sections.txt create mode 100644 doc/reference/libeshell/libeshell.types create mode 100644 doc/reference/libeshell/tmpl/e-mail-account-manager.sgml create mode 100644 doc/reference/libeshell/tmpl/e-mail-account-tree-view.sgml create mode 100644 doc/reference/libeshell/tmpl/e-mail-identity-combo-box.sgml create mode 100644 doc/reference/libeutil/Makefile.am create mode 100644 doc/reference/libeutil/libeutil-docs.sgml create mode 100644 doc/reference/libeutil/libeutil-overrides.txt create mode 100644 doc/reference/libeutil/libeutil-sections.txt create mode 100644 doc/reference/libeutil/libeutil.types delete mode 100644 doc/reference/shell/Makefile.am delete mode 100644 doc/reference/shell/eshell-docs.sgml delete mode 100644 doc/reference/shell/eshell-overrides.txt delete mode 100644 doc/reference/shell/eshell-sections.txt delete mode 100644 doc/reference/shell/eshell.types delete mode 100644 doc/reference/shell/tmpl/e-mail-account-manager.sgml delete mode 100644 doc/reference/shell/tmpl/e-mail-account-tree-view.sgml delete mode 100644 doc/reference/shell/tmpl/e-mail-identity-combo-box.sgml create mode 100644 e-util/arrow-down.xpm create mode 100644 e-util/arrow-up.xpm create mode 100644 e-util/check-empty.xpm create mode 100644 e-util/check-filled.xpm create mode 100644 e-util/e-action-combo-box.c create mode 100644 e-util/e-action-combo-box.h create mode 100644 e-util/e-activity-bar.c create mode 100644 e-util/e-activity-bar.h create mode 100644 e-util/e-activity-proxy.c create mode 100644 e-util/e-activity-proxy.h create mode 100644 e-util/e-alarm-selector.c create mode 100644 e-util/e-alarm-selector.h create mode 100644 e-util/e-alert-bar.c create mode 100644 e-util/e-alert-bar.h create mode 100644 e-util/e-alert-dialog.c create mode 100644 e-util/e-alert-dialog.h create mode 100644 e-util/e-alert-sink.c create mode 100644 e-util/e-alert-sink.h create mode 100644 e-util/e-alert.c create mode 100644 e-util/e-alert.h create mode 100644 e-util/e-attachment-bar.c create mode 100644 e-util/e-attachment-bar.h create mode 100644 e-util/e-attachment-button.c create mode 100644 e-util/e-attachment-button.h create mode 100644 e-util/e-attachment-dialog.c create mode 100644 e-util/e-attachment-dialog.h create mode 100644 e-util/e-attachment-handler-image.c create mode 100644 e-util/e-attachment-handler-image.h create mode 100644 e-util/e-attachment-handler-sendto.c create mode 100644 e-util/e-attachment-handler-sendto.h create mode 100644 e-util/e-attachment-handler.c create mode 100644 e-util/e-attachment-handler.h create mode 100644 e-util/e-attachment-icon-view.c create mode 100644 e-util/e-attachment-icon-view.h create mode 100644 e-util/e-attachment-paned.c create mode 100644 e-util/e-attachment-paned.h create mode 100644 e-util/e-attachment-store.c create mode 100644 e-util/e-attachment-store.h create mode 100644 e-util/e-attachment-tree-view.c create mode 100644 e-util/e-attachment-tree-view.h create mode 100644 e-util/e-attachment-view.c create mode 100644 e-util/e-attachment-view.h create mode 100644 e-util/e-attachment.c create mode 100644 e-util/e-attachment.h create mode 100644 e-util/e-auth-combo-box.c create mode 100644 e-util/e-auth-combo-box.h create mode 100644 e-util/e-autocomplete-selector.c create mode 100644 e-util/e-autocomplete-selector.h create mode 100644 e-util/e-book-source-config.c create mode 100644 e-util/e-book-source-config.h create mode 100644 e-util/e-buffer-tagger.c create mode 100644 e-util/e-buffer-tagger.h create mode 100644 e-util/e-cal-source-config.c create mode 100644 e-util/e-cal-source-config.h create mode 100644 e-util/e-calendar-item.c create mode 100644 e-util/e-calendar-item.h create mode 100644 e-util/e-calendar.c create mode 100644 e-util/e-calendar.h create mode 100644 e-util/e-canvas-background.c create mode 100644 e-util/e-canvas-background.h create mode 100644 e-util/e-canvas-utils.c create mode 100644 e-util/e-canvas-utils.h create mode 100644 e-util/e-canvas-vbox.c create mode 100644 e-util/e-canvas-vbox.h create mode 100644 e-util/e-canvas.c create mode 100644 e-util/e-canvas.h create mode 100644 e-util/e-categories-dialog.c create mode 100644 e-util/e-categories-dialog.h create mode 100644 e-util/e-categories-editor.c create mode 100644 e-util/e-categories-editor.h create mode 100644 e-util/e-categories-selector.c create mode 100644 e-util/e-categories-selector.h create mode 100644 e-util/e-category-completion.c create mode 100644 e-util/e-category-completion.h create mode 100644 e-util/e-category-editor.c create mode 100644 e-util/e-category-editor.h create mode 100644 e-util/e-cell-checkbox.c create mode 100644 e-util/e-cell-checkbox.h create mode 100644 e-util/e-cell-combo.c create mode 100644 e-util/e-cell-combo.h create mode 100644 e-util/e-cell-date-edit.c create mode 100644 e-util/e-cell-date-edit.h create mode 100644 e-util/e-cell-date.c create mode 100644 e-util/e-cell-date.h create mode 100644 e-util/e-cell-hbox.c create mode 100644 e-util/e-cell-hbox.h create mode 100644 e-util/e-cell-number.c create mode 100644 e-util/e-cell-number.h create mode 100644 e-util/e-cell-percent.c create mode 100644 e-util/e-cell-percent.h create mode 100644 e-util/e-cell-pixbuf.c create mode 100644 e-util/e-cell-pixbuf.h create mode 100644 e-util/e-cell-popup.c create mode 100644 e-util/e-cell-popup.h create mode 100644 e-util/e-cell-renderer-color.c create mode 100644 e-util/e-cell-renderer-color.h create mode 100644 e-util/e-cell-size.c create mode 100644 e-util/e-cell-size.h create mode 100644 e-util/e-cell-text.c create mode 100644 e-util/e-cell-text.h create mode 100644 e-util/e-cell-toggle.c create mode 100644 e-util/e-cell-toggle.h create mode 100644 e-util/e-cell-tree.c create mode 100644 e-util/e-cell-tree.h create mode 100644 e-util/e-cell-vbox.c create mode 100644 e-util/e-cell-vbox.h create mode 100644 e-util/e-cell.c create mode 100644 e-util/e-cell.h create mode 100644 e-util/e-charset-combo-box.c create mode 100644 e-util/e-charset-combo-box.h create mode 100644 e-util/e-client-utils.c create mode 100644 e-util/e-client-utils.h create mode 100644 e-util/e-contact-map-window.c create mode 100644 e-util/e-contact-map-window.h create mode 100644 e-util/e-contact-map.c create mode 100644 e-util/e-contact-map.h create mode 100644 e-util/e-contact-marker.c create mode 100644 e-util/e-contact-marker.h create mode 100644 e-util/e-contact-store.c create mode 100644 e-util/e-contact-store.h create mode 100644 e-util/e-dateedit.c create mode 100644 e-util/e-dateedit.h create mode 100644 e-util/e-destination-store.c create mode 100644 e-util/e-destination-store.h create mode 100644 e-util/e-filter-code.c create mode 100644 e-util/e-filter-code.h create mode 100644 e-util/e-filter-color.c create mode 100644 e-util/e-filter-color.h create mode 100644 e-util/e-filter-datespec.c create mode 100644 e-util/e-filter-datespec.h create mode 100644 e-util/e-filter-element.c create mode 100644 e-util/e-filter-element.h create mode 100644 e-util/e-filter-file.c create mode 100644 e-util/e-filter-file.h create mode 100644 e-util/e-filter-input.c create mode 100644 e-util/e-filter-input.h create mode 100644 e-util/e-filter-int.c create mode 100644 e-util/e-filter-int.h create mode 100644 e-util/e-filter-option.c create mode 100644 e-util/e-filter-option.h create mode 100644 e-util/e-filter-part.c create mode 100644 e-util/e-filter-part.h create mode 100644 e-util/e-filter-rule.c create mode 100644 e-util/e-filter-rule.h create mode 100644 e-util/e-focus-tracker.c create mode 100644 e-util/e-focus-tracker.h create mode 100644 e-util/e-image-chooser.c create mode 100644 e-util/e-image-chooser.h create mode 100644 e-util/e-import-assistant.c create mode 100644 e-util/e-import-assistant.h create mode 100644 e-util/e-interval-chooser.c create mode 100644 e-util/e-interval-chooser.h create mode 100644 e-util/e-mail-identity-combo-box.c create mode 100644 e-util/e-mail-identity-combo-box.h create mode 100644 e-util/e-mail-signature-combo-box.c create mode 100644 e-util/e-mail-signature-combo-box.h create mode 100644 e-util/e-mail-signature-editor.c create mode 100644 e-util/e-mail-signature-editor.h create mode 100644 e-util/e-mail-signature-manager.c create mode 100644 e-util/e-mail-signature-manager.h create mode 100644 e-util/e-mail-signature-preview.c create mode 100644 e-util/e-mail-signature-preview.h create mode 100644 e-util/e-mail-signature-script-dialog.c create mode 100644 e-util/e-mail-signature-script-dialog.h create mode 100644 e-util/e-mail-signature-tree-view.c create mode 100644 e-util/e-mail-signature-tree-view.h create mode 100644 e-util/e-map.c create mode 100644 e-util/e-map.h create mode 100644 e-util/e-menu-tool-action.c create mode 100644 e-util/e-menu-tool-action.h create mode 100644 e-util/e-menu-tool-button.c create mode 100644 e-util/e-menu-tool-button.h create mode 100644 e-util/e-misc-utils.c create mode 100644 e-util/e-misc-utils.h create mode 100644 e-util/e-name-selector-dialog.c create mode 100644 e-util/e-name-selector-dialog.h create mode 100644 e-util/e-name-selector-entry.c create mode 100644 e-util/e-name-selector-entry.h create mode 100644 e-util/e-name-selector-list.c create mode 100644 e-util/e-name-selector-list.h create mode 100644 e-util/e-name-selector-model.c create mode 100644 e-util/e-name-selector-model.h create mode 100644 e-util/e-name-selector.c create mode 100644 e-util/e-name-selector.h create mode 100644 e-util/e-online-button.c create mode 100644 e-util/e-online-button.h create mode 100644 e-util/e-paned.c create mode 100644 e-util/e-paned.h create mode 100644 e-util/e-passwords-win32.c create mode 100644 e-util/e-passwords.c create mode 100644 e-util/e-passwords.h create mode 100644 e-util/e-picture-gallery.c create mode 100644 e-util/e-picture-gallery.h create mode 100644 e-util/e-popup-action.c create mode 100644 e-util/e-popup-action.h create mode 100644 e-util/e-popup-menu.c create mode 100644 e-util/e-popup-menu.h create mode 100644 e-util/e-port-entry.c create mode 100644 e-util/e-port-entry.h create mode 100644 e-util/e-preferences-window.c create mode 100644 e-util/e-preferences-window.h create mode 100644 e-util/e-preview-pane.c create mode 100644 e-util/e-preview-pane.h create mode 100644 e-util/e-printable.c create mode 100644 e-util/e-printable.h create mode 100644 e-util/e-reflow-model.c create mode 100644 e-util/e-reflow-model.h create mode 100644 e-util/e-reflow.c create mode 100644 e-util/e-reflow.h create mode 100644 e-util/e-rule-context.c create mode 100644 e-util/e-rule-context.h create mode 100644 e-util/e-rule-editor.c create mode 100644 e-util/e-rule-editor.h create mode 100644 e-util/e-search-bar.c create mode 100644 e-util/e-search-bar.h create mode 100644 e-util/e-selectable.c create mode 100644 e-util/e-selectable.h create mode 100644 e-util/e-selection-model-array.c create mode 100644 e-util/e-selection-model-array.h create mode 100644 e-util/e-selection-model-simple.c create mode 100644 e-util/e-selection-model-simple.h create mode 100644 e-util/e-selection-model.c create mode 100644 e-util/e-selection-model.h create mode 100644 e-util/e-send-options.c create mode 100644 e-util/e-send-options.h create mode 100644 e-util/e-send-options.ui create mode 100644 e-util/e-source-combo-box.c create mode 100644 e-util/e-source-combo-box.h create mode 100644 e-util/e-source-config-backend.c create mode 100644 e-util/e-source-config-backend.h create mode 100644 e-util/e-source-config-dialog.c create mode 100644 e-util/e-source-config-dialog.h create mode 100644 e-util/e-source-config.c create mode 100644 e-util/e-source-config.h create mode 100644 e-util/e-source-selector-dialog.c create mode 100644 e-util/e-source-selector-dialog.h create mode 100644 e-util/e-source-selector.c create mode 100644 e-util/e-source-selector.h create mode 100644 e-util/e-spell-entry.c create mode 100644 e-util/e-spell-entry.h create mode 100644 e-util/e-table-click-to-add.c create mode 100644 e-util/e-table-click-to-add.h create mode 100644 e-util/e-table-col-dnd.h create mode 100644 e-util/e-table-col.c create mode 100644 e-util/e-table-col.h create mode 100644 e-util/e-table-column-specification.c create mode 100644 e-util/e-table-column-specification.h create mode 100644 e-util/e-table-config.c create mode 100644 e-util/e-table-config.h create mode 100644 e-util/e-table-config.ui create mode 100644 e-util/e-table-defines.h create mode 100644 e-util/e-table-extras.c create mode 100644 e-util/e-table-extras.h create mode 100644 e-util/e-table-field-chooser-dialog.c create mode 100644 e-util/e-table-field-chooser-dialog.h create mode 100644 e-util/e-table-field-chooser-item.c create mode 100644 e-util/e-table-field-chooser-item.h create mode 100644 e-util/e-table-field-chooser.c create mode 100644 e-util/e-table-field-chooser.h create mode 100644 e-util/e-table-group-container.c create mode 100644 e-util/e-table-group-container.h create mode 100644 e-util/e-table-group-leaf.c create mode 100644 e-util/e-table-group-leaf.h create mode 100644 e-util/e-table-group.c create mode 100644 e-util/e-table-group.h create mode 100644 e-util/e-table-header-item.c create mode 100644 e-util/e-table-header-item.h create mode 100644 e-util/e-table-header-utils.c create mode 100644 e-util/e-table-header-utils.h create mode 100644 e-util/e-table-header.c create mode 100644 e-util/e-table-header.h create mode 100644 e-util/e-table-item.c create mode 100644 e-util/e-table-item.h create mode 100644 e-util/e-table-memory-callbacks.c create mode 100644 e-util/e-table-memory-callbacks.h create mode 100644 e-util/e-table-memory-store.c create mode 100644 e-util/e-table-memory-store.h create mode 100644 e-util/e-table-memory.c create mode 100644 e-util/e-table-memory.h create mode 100644 e-util/e-table-model.c create mode 100644 e-util/e-table-model.h create mode 100644 e-util/e-table-one.c create mode 100644 e-util/e-table-one.h create mode 100644 e-util/e-table-search.c create mode 100644 e-util/e-table-search.h create mode 100644 e-util/e-table-selection-model.c create mode 100644 e-util/e-table-selection-model.h create mode 100644 e-util/e-table-sort-info.c create mode 100644 e-util/e-table-sort-info.h create mode 100644 e-util/e-table-sorted-variable.c create mode 100644 e-util/e-table-sorted-variable.h create mode 100644 e-util/e-table-sorted.c create mode 100644 e-util/e-table-sorted.h create mode 100644 e-util/e-table-sorter.c create mode 100644 e-util/e-table-sorter.h create mode 100644 e-util/e-table-sorting-utils.c create mode 100644 e-util/e-table-sorting-utils.h create mode 100644 e-util/e-table-specification.c create mode 100644 e-util/e-table-specification.h create mode 100644 e-util/e-table-state.c create mode 100644 e-util/e-table-state.h create mode 100644 e-util/e-table-subset-variable.c create mode 100644 e-util/e-table-subset-variable.h create mode 100644 e-util/e-table-subset.c create mode 100644 e-util/e-table-subset.h create mode 100644 e-util/e-table-utils.c create mode 100644 e-util/e-table-utils.h create mode 100644 e-util/e-table-without.c create mode 100644 e-util/e-table-without.h create mode 100644 e-util/e-table.c create mode 100644 e-util/e-table.h create mode 100644 e-util/e-text-model-repos.c create mode 100644 e-util/e-text-model-repos.h create mode 100644 e-util/e-text-model.c create mode 100644 e-util/e-text-model.h create mode 100644 e-util/e-text.c create mode 100644 e-util/e-text.h create mode 100644 e-util/e-timezone-dialog.c create mode 100644 e-util/e-timezone-dialog.h create mode 100644 e-util/e-timezone-dialog.ui create mode 100644 e-util/e-tree-memory-callbacks.c create mode 100644 e-util/e-tree-memory-callbacks.h create mode 100644 e-util/e-tree-memory.c create mode 100644 e-util/e-tree-memory.h create mode 100644 e-util/e-tree-model-generator.c create mode 100644 e-util/e-tree-model-generator.h create mode 100644 e-util/e-tree-model.c create mode 100644 e-util/e-tree-model.h create mode 100644 e-util/e-tree-selection-model.c create mode 100644 e-util/e-tree-selection-model.h create mode 100644 e-util/e-tree-sorted.c create mode 100644 e-util/e-tree-sorted.h create mode 100644 e-util/e-tree-table-adapter.c create mode 100644 e-util/e-tree-table-adapter.h create mode 100644 e-util/e-tree.c create mode 100644 e-util/e-tree.h create mode 100644 e-util/e-url-entry.c create mode 100644 e-util/e-url-entry.h delete mode 100644 e-util/e-util.c create mode 100644 e-util/e-web-view-gtkhtml.c create mode 100644 e-util/e-web-view-gtkhtml.h create mode 100644 e-util/e-web-view-preview.c create mode 100644 e-util/e-web-view-preview.h create mode 100644 e-util/e-web-view.c create mode 100644 e-util/e-web-view.h create mode 100644 e-util/e-xml-utils.c create mode 100644 e-util/e-xml-utils.h create mode 100644 e-util/ea-calendar-cell.c create mode 100644 e-util/ea-calendar-cell.h create mode 100644 e-util/ea-calendar-item.c create mode 100644 e-util/ea-calendar-item.h create mode 100644 e-util/ea-cell-table.c create mode 100644 e-util/ea-cell-table.h create mode 100644 e-util/ea-factory.h create mode 100644 e-util/ea-widgets.c create mode 100644 e-util/ea-widgets.h create mode 100644 e-util/evolution-source-viewer.c create mode 100644 e-util/filter.error.xml create mode 100644 e-util/filter.ui create mode 100644 e-util/gal-a11y-e-cell-popup.c create mode 100644 e-util/gal-a11y-e-cell-popup.h create mode 100644 e-util/gal-a11y-e-cell-registry.c create mode 100644 e-util/gal-a11y-e-cell-registry.h create mode 100644 e-util/gal-a11y-e-cell-toggle.c create mode 100644 e-util/gal-a11y-e-cell-toggle.h create mode 100644 e-util/gal-a11y-e-cell-tree.c create mode 100644 e-util/gal-a11y-e-cell-tree.h create mode 100644 e-util/gal-a11y-e-cell-vbox.c create mode 100644 e-util/gal-a11y-e-cell-vbox.h create mode 100644 e-util/gal-a11y-e-cell.c create mode 100644 e-util/gal-a11y-e-cell.h create mode 100644 e-util/gal-a11y-e-table-click-to-add-factory.c create mode 100644 e-util/gal-a11y-e-table-click-to-add-factory.h create mode 100644 e-util/gal-a11y-e-table-click-to-add.c create mode 100644 e-util/gal-a11y-e-table-click-to-add.h create mode 100644 e-util/gal-a11y-e-table-column-header.c create mode 100644 e-util/gal-a11y-e-table-column-header.h create mode 100644 e-util/gal-a11y-e-table-factory.c create mode 100644 e-util/gal-a11y-e-table-factory.h create mode 100644 e-util/gal-a11y-e-table-item-factory.c create mode 100644 e-util/gal-a11y-e-table-item-factory.h create mode 100644 e-util/gal-a11y-e-table-item.c create mode 100644 e-util/gal-a11y-e-table-item.h create mode 100644 e-util/gal-a11y-e-table.c create mode 100644 e-util/gal-a11y-e-table.h create mode 100644 e-util/gal-a11y-e-text-factory.c create mode 100644 e-util/gal-a11y-e-text-factory.h create mode 100644 e-util/gal-a11y-e-text.c create mode 100644 e-util/gal-a11y-e-text.h create mode 100644 e-util/gal-a11y-e-tree-factory.c create mode 100644 e-util/gal-a11y-e-tree-factory.h create mode 100644 e-util/gal-a11y-e-tree.c create mode 100644 e-util/gal-a11y-e-tree.h create mode 100644 e-util/gal-a11y-factory.h create mode 100644 e-util/gal-a11y-util.c create mode 100644 e-util/gal-a11y-util.h create mode 100644 e-util/gal-define-views-dialog.c create mode 100644 e-util/gal-define-views-dialog.h create mode 100644 e-util/gal-define-views-model.c create mode 100644 e-util/gal-define-views-model.h create mode 100644 e-util/gal-define-views.ui create mode 100644 e-util/gal-view-collection.c create mode 100644 e-util/gal-view-collection.h create mode 100644 e-util/gal-view-etable.c create mode 100644 e-util/gal-view-etable.h create mode 100644 e-util/gal-view-factory-etable.c create mode 100644 e-util/gal-view-factory-etable.h create mode 100644 e-util/gal-view-factory.c create mode 100644 e-util/gal-view-factory.h create mode 100644 e-util/gal-view-instance-save-as-dialog.c create mode 100644 e-util/gal-view-instance-save-as-dialog.h create mode 100644 e-util/gal-view-instance-save-as-dialog.ui create mode 100644 e-util/gal-view-instance.c create mode 100644 e-util/gal-view-instance.h create mode 100644 e-util/gal-view-new-dialog.c create mode 100644 e-util/gal-view-new-dialog.h create mode 100644 e-util/gal-view-new-dialog.ui create mode 100644 e-util/gal-view.c create mode 100644 e-util/gal-view.h create mode 100644 e-util/test-calendar.c create mode 100644 e-util/test-category-completion.c create mode 100644 e-util/test-contact-store.c create mode 100644 e-util/test-dateedit.c create mode 100644 e-util/test-mail-signatures.c create mode 100644 e-util/test-name-selector.c create mode 100644 e-util/test-preferences-window.c create mode 100644 e-util/test-source-combo-box.c create mode 100644 e-util/test-source-config.c create mode 100644 e-util/test-source-selector.c create mode 100644 e-util/tree-expanded.xpm create mode 100644 e-util/tree-unexpanded.xpm create mode 100644 e-util/widgets.error.xml delete mode 100644 filter/Makefile.am delete mode 100644 filter/e-filter-code.c delete mode 100644 filter/e-filter-code.h delete mode 100644 filter/e-filter-color.c delete mode 100644 filter/e-filter-color.h delete mode 100644 filter/e-filter-datespec.c delete mode 100644 filter/e-filter-datespec.h delete mode 100644 filter/e-filter-element.c delete mode 100644 filter/e-filter-element.h delete mode 100644 filter/e-filter-file.c delete mode 100644 filter/e-filter-file.h delete mode 100644 filter/e-filter-input.c delete mode 100644 filter/e-filter-input.h delete mode 100644 filter/e-filter-int.c delete mode 100644 filter/e-filter-int.h delete mode 100644 filter/e-filter-option.c delete mode 100644 filter/e-filter-option.h delete mode 100644 filter/e-filter-part.c delete mode 100644 filter/e-filter-part.h delete mode 100644 filter/e-filter-rule.c delete mode 100644 filter/e-filter-rule.h delete mode 100644 filter/e-rule-context.c delete mode 100644 filter/e-rule-context.h delete mode 100644 filter/e-rule-editor.c delete mode 100644 filter/e-rule-editor.h delete mode 100644 filter/filter.error.xml delete mode 100644 filter/filter.ui create mode 100644 libemail-engine/em-filter-folder-element.c create mode 100644 libemail-engine/em-filter-folder-element.h create mode 100644 libemail-engine/em-vfolder-context.c create mode 100644 libemail-engine/em-vfolder-context.h create mode 100644 libemail-engine/em-vfolder-rule.c create mode 100644 libemail-engine/em-vfolder-rule.h create mode 100644 libemail-engine/mail-mt.c create mode 100644 libemail-engine/mail-mt.h delete mode 100644 libemail-utils/Makefile.am delete mode 100644 libemail-utils/em-filter-folder-element.c delete mode 100644 libemail-utils/em-filter-folder-element.h delete mode 100644 libemail-utils/em-vfolder-context.c delete mode 100644 libemail-utils/em-vfolder-context.h delete mode 100644 libemail-utils/em-vfolder-rule.c delete mode 100644 libemail-utils/em-vfolder-rule.h delete mode 100644 libemail-utils/libemail-utils.pc.in delete mode 100644 libemail-utils/mail-mt.c delete mode 100644 libemail-utils/mail-mt.h delete mode 100644 libevolution-utils/Makefile.am delete mode 100644 libevolution-utils/e-alert-dialog.c delete mode 100644 libevolution-utils/e-alert-dialog.h delete mode 100644 libevolution-utils/e-alert-sink.c delete mode 100644 libevolution-utils/e-alert-sink.h delete mode 100644 libevolution-utils/e-alert.c delete mode 100644 libevolution-utils/e-alert.h delete mode 100644 libevolution-utils/e-xml-utils.c delete mode 100644 libevolution-utils/e-xml-utils.h delete mode 100644 libevolution-utils/evolution-util.c delete mode 100644 libevolution-utils/evolution-util.h delete mode 100644 libevolution-utils/libevolution-utils.pc.in delete mode 100644 widgets/LICENSE delete mode 100644 widgets/Makefile.am delete mode 100644 widgets/e-timezone-dialog/Makefile.am delete mode 100644 widgets/e-timezone-dialog/e-timezone-dialog.c delete mode 100644 widgets/e-timezone-dialog/e-timezone-dialog.h delete mode 100644 widgets/e-timezone-dialog/e-timezone-dialog.ui delete mode 100644 widgets/menus/Makefile.am delete mode 100644 widgets/menus/gal-define-views-dialog.c delete mode 100644 widgets/menus/gal-define-views-dialog.h delete mode 100644 widgets/menus/gal-define-views-model.c delete mode 100644 widgets/menus/gal-define-views-model.h delete mode 100644 widgets/menus/gal-define-views.ui delete mode 100644 widgets/menus/gal-view-collection.c delete mode 100644 widgets/menus/gal-view-collection.h delete mode 100644 widgets/menus/gal-view-etable.c delete mode 100644 widgets/menus/gal-view-etable.h delete mode 100644 widgets/menus/gal-view-factory-etable.c delete mode 100644 widgets/menus/gal-view-factory-etable.h delete mode 100644 widgets/menus/gal-view-factory.c delete mode 100644 widgets/menus/gal-view-factory.h delete mode 100644 widgets/menus/gal-view-instance-save-as-dialog.c delete mode 100644 widgets/menus/gal-view-instance-save-as-dialog.h delete mode 100644 widgets/menus/gal-view-instance-save-as-dialog.ui delete mode 100644 widgets/menus/gal-view-instance.c delete mode 100644 widgets/menus/gal-view-instance.h delete mode 100644 widgets/menus/gal-view-new-dialog.c delete mode 100644 widgets/menus/gal-view-new-dialog.h delete mode 100644 widgets/menus/gal-view-new-dialog.ui delete mode 100644 widgets/menus/gal-view.c delete mode 100644 widgets/menus/gal-view.h delete mode 100644 widgets/misc/Makefile.am delete mode 100644 widgets/misc/e-action-combo-box.c delete mode 100644 widgets/misc/e-action-combo-box.h delete mode 100644 widgets/misc/e-activity-bar.c delete mode 100644 widgets/misc/e-activity-bar.h delete mode 100644 widgets/misc/e-activity-proxy.c delete mode 100644 widgets/misc/e-activity-proxy.h delete mode 100644 widgets/misc/e-alarm-selector.c delete mode 100644 widgets/misc/e-alarm-selector.h delete mode 100644 widgets/misc/e-alert-bar.c delete mode 100644 widgets/misc/e-alert-bar.h delete mode 100644 widgets/misc/e-attachment-bar.c delete mode 100644 widgets/misc/e-attachment-bar.h delete mode 100644 widgets/misc/e-attachment-button.c delete mode 100644 widgets/misc/e-attachment-button.h delete mode 100644 widgets/misc/e-attachment-dialog.c delete mode 100644 widgets/misc/e-attachment-dialog.h delete mode 100644 widgets/misc/e-attachment-handler-image.c delete mode 100644 widgets/misc/e-attachment-handler-image.h delete mode 100644 widgets/misc/e-attachment-handler-sendto.c delete mode 100644 widgets/misc/e-attachment-handler-sendto.h delete mode 100644 widgets/misc/e-attachment-handler.c delete mode 100644 widgets/misc/e-attachment-handler.h delete mode 100644 widgets/misc/e-attachment-icon-view.c delete mode 100644 widgets/misc/e-attachment-icon-view.h delete mode 100644 widgets/misc/e-attachment-paned.c delete mode 100644 widgets/misc/e-attachment-paned.h delete mode 100644 widgets/misc/e-attachment-store.c delete mode 100644 widgets/misc/e-attachment-store.h delete mode 100644 widgets/misc/e-attachment-tree-view.c delete mode 100644 widgets/misc/e-attachment-tree-view.h delete mode 100644 widgets/misc/e-attachment-view.c delete mode 100644 widgets/misc/e-attachment-view.h delete mode 100644 widgets/misc/e-attachment.c delete mode 100644 widgets/misc/e-attachment.h delete mode 100644 widgets/misc/e-auth-combo-box.c delete mode 100644 widgets/misc/e-auth-combo-box.h delete mode 100644 widgets/misc/e-autocomplete-selector.c delete mode 100644 widgets/misc/e-autocomplete-selector.h delete mode 100644 widgets/misc/e-book-source-config.c delete mode 100644 widgets/misc/e-book-source-config.h delete mode 100644 widgets/misc/e-buffer-tagger.c delete mode 100644 widgets/misc/e-buffer-tagger.h delete mode 100644 widgets/misc/e-cal-source-config.c delete mode 100644 widgets/misc/e-cal-source-config.h delete mode 100644 widgets/misc/e-calendar-item.c delete mode 100644 widgets/misc/e-calendar-item.h delete mode 100644 widgets/misc/e-calendar.c delete mode 100644 widgets/misc/e-calendar.h delete mode 100644 widgets/misc/e-canvas-background.c delete mode 100644 widgets/misc/e-canvas-background.h delete mode 100644 widgets/misc/e-canvas-utils.c delete mode 100644 widgets/misc/e-canvas-utils.h delete mode 100644 widgets/misc/e-canvas-vbox.c delete mode 100644 widgets/misc/e-canvas-vbox.h delete mode 100644 widgets/misc/e-canvas.c delete mode 100644 widgets/misc/e-canvas.h delete mode 100644 widgets/misc/e-charset-combo-box.c delete mode 100644 widgets/misc/e-charset-combo-box.h delete mode 100644 widgets/misc/e-contact-map-window.c delete mode 100644 widgets/misc/e-contact-map-window.h delete mode 100644 widgets/misc/e-contact-map.c delete mode 100644 widgets/misc/e-contact-map.h delete mode 100644 widgets/misc/e-contact-marker.c delete mode 100644 widgets/misc/e-contact-marker.h delete mode 100644 widgets/misc/e-dateedit.c delete mode 100644 widgets/misc/e-dateedit.h delete mode 100644 widgets/misc/e-focus-tracker.c delete mode 100644 widgets/misc/e-focus-tracker.h delete mode 100644 widgets/misc/e-image-chooser.c delete mode 100644 widgets/misc/e-image-chooser.h delete mode 100644 widgets/misc/e-import-assistant.c delete mode 100644 widgets/misc/e-import-assistant.h delete mode 100644 widgets/misc/e-interval-chooser.c delete mode 100644 widgets/misc/e-interval-chooser.h delete mode 100644 widgets/misc/e-mail-identity-combo-box.c delete mode 100644 widgets/misc/e-mail-identity-combo-box.h delete mode 100644 widgets/misc/e-mail-signature-combo-box.c delete mode 100644 widgets/misc/e-mail-signature-combo-box.h delete mode 100644 widgets/misc/e-mail-signature-editor.c delete mode 100644 widgets/misc/e-mail-signature-editor.h delete mode 100644 widgets/misc/e-mail-signature-manager.c delete mode 100644 widgets/misc/e-mail-signature-manager.h delete mode 100644 widgets/misc/e-mail-signature-preview.c delete mode 100644 widgets/misc/e-mail-signature-preview.h delete mode 100644 widgets/misc/e-mail-signature-script-dialog.c delete mode 100644 widgets/misc/e-mail-signature-script-dialog.h delete mode 100644 widgets/misc/e-mail-signature-tree-view.c delete mode 100644 widgets/misc/e-mail-signature-tree-view.h delete mode 100644 widgets/misc/e-map.c delete mode 100644 widgets/misc/e-map.h delete mode 100644 widgets/misc/e-menu-tool-action.c delete mode 100644 widgets/misc/e-menu-tool-action.h delete mode 100644 widgets/misc/e-menu-tool-button.c delete mode 100644 widgets/misc/e-menu-tool-button.h delete mode 100644 widgets/misc/e-online-button.c delete mode 100644 widgets/misc/e-online-button.h delete mode 100644 widgets/misc/e-paned.c delete mode 100644 widgets/misc/e-paned.h delete mode 100644 widgets/misc/e-picture-gallery.c delete mode 100644 widgets/misc/e-picture-gallery.h delete mode 100644 widgets/misc/e-popup-action.c delete mode 100644 widgets/misc/e-popup-action.h delete mode 100644 widgets/misc/e-port-entry.c delete mode 100644 widgets/misc/e-port-entry.h delete mode 100644 widgets/misc/e-preferences-window.c delete mode 100644 widgets/misc/e-preferences-window.h delete mode 100644 widgets/misc/e-preview-pane.c delete mode 100644 widgets/misc/e-preview-pane.h delete mode 100644 widgets/misc/e-printable.c delete mode 100644 widgets/misc/e-printable.h delete mode 100644 widgets/misc/e-search-bar.c delete mode 100644 widgets/misc/e-search-bar.h delete mode 100644 widgets/misc/e-selectable.c delete mode 100644 widgets/misc/e-selectable.h delete mode 100644 widgets/misc/e-selection-model-array.c delete mode 100644 widgets/misc/e-selection-model-array.h delete mode 100644 widgets/misc/e-selection-model-simple.c delete mode 100644 widgets/misc/e-selection-model-simple.h delete mode 100644 widgets/misc/e-selection-model.c delete mode 100644 widgets/misc/e-selection-model.h delete mode 100644 widgets/misc/e-send-options.c delete mode 100644 widgets/misc/e-send-options.h delete mode 100644 widgets/misc/e-send-options.ui delete mode 100644 widgets/misc/e-source-config-backend.c delete mode 100644 widgets/misc/e-source-config-backend.h delete mode 100644 widgets/misc/e-source-config-dialog.c delete mode 100644 widgets/misc/e-source-config-dialog.h delete mode 100644 widgets/misc/e-source-config.c delete mode 100644 widgets/misc/e-source-config.h delete mode 100644 widgets/misc/e-spell-entry.c delete mode 100644 widgets/misc/e-spell-entry.h delete mode 100644 widgets/misc/e-url-entry.c delete mode 100644 widgets/misc/e-url-entry.h delete mode 100644 widgets/misc/e-web-view-gtkhtml.c delete mode 100644 widgets/misc/e-web-view-gtkhtml.h delete mode 100644 widgets/misc/e-web-view-preview.c delete mode 100644 widgets/misc/e-web-view-preview.h delete mode 100644 widgets/misc/e-web-view.c delete mode 100644 widgets/misc/e-web-view.h delete mode 100644 widgets/misc/ea-calendar-cell.c delete mode 100644 widgets/misc/ea-calendar-cell.h delete mode 100644 widgets/misc/ea-calendar-item.c delete mode 100644 widgets/misc/ea-calendar-item.h delete mode 100644 widgets/misc/ea-cell-table.c delete mode 100644 widgets/misc/ea-cell-table.h delete mode 100644 widgets/misc/ea-widgets.c delete mode 100644 widgets/misc/ea-widgets.h delete mode 100644 widgets/misc/test-calendar.c delete mode 100644 widgets/misc/test-dateedit.c delete mode 100644 widgets/misc/test-mail-signatures.c delete mode 100644 widgets/misc/test-preferences-window.c delete mode 100644 widgets/misc/test-source-config.c delete mode 100644 widgets/misc/widgets.error.xml delete mode 100644 widgets/table/Makefile.am delete mode 100644 widgets/table/arrow-down.xpm delete mode 100644 widgets/table/arrow-up.xpm delete mode 100644 widgets/table/check-empty.xpm delete mode 100644 widgets/table/check-filled.xpm delete mode 100644 widgets/table/e-cell-checkbox.c delete mode 100644 widgets/table/e-cell-checkbox.h delete mode 100644 widgets/table/e-cell-combo.c delete mode 100644 widgets/table/e-cell-combo.h delete mode 100644 widgets/table/e-cell-date-edit.c delete mode 100644 widgets/table/e-cell-date-edit.h delete mode 100644 widgets/table/e-cell-date.c delete mode 100644 widgets/table/e-cell-date.h delete mode 100644 widgets/table/e-cell-hbox.c delete mode 100644 widgets/table/e-cell-hbox.h delete mode 100644 widgets/table/e-cell-number.c delete mode 100644 widgets/table/e-cell-number.h delete mode 100644 widgets/table/e-cell-percent.c delete mode 100644 widgets/table/e-cell-percent.h delete mode 100644 widgets/table/e-cell-pixbuf.c delete mode 100644 widgets/table/e-cell-pixbuf.h delete mode 100644 widgets/table/e-cell-popup.c delete mode 100644 widgets/table/e-cell-popup.h delete mode 100644 widgets/table/e-cell-size.c delete mode 100644 widgets/table/e-cell-size.h delete mode 100644 widgets/table/e-cell-text.c delete mode 100644 widgets/table/e-cell-text.h delete mode 100644 widgets/table/e-cell-toggle.c delete mode 100644 widgets/table/e-cell-toggle.h delete mode 100644 widgets/table/e-cell-tree.c delete mode 100644 widgets/table/e-cell-tree.h delete mode 100644 widgets/table/e-cell-vbox.c delete mode 100644 widgets/table/e-cell-vbox.h delete mode 100644 widgets/table/e-cell.c delete mode 100644 widgets/table/e-cell.h delete mode 100644 widgets/table/e-popup-menu.c delete mode 100644 widgets/table/e-popup-menu.h delete mode 100644 widgets/table/e-table-click-to-add.c delete mode 100644 widgets/table/e-table-click-to-add.h delete mode 100644 widgets/table/e-table-col-dnd.h delete mode 100644 widgets/table/e-table-col.c delete mode 100644 widgets/table/e-table-col.h delete mode 100644 widgets/table/e-table-column-specification.c delete mode 100644 widgets/table/e-table-column-specification.h delete mode 100644 widgets/table/e-table-config.c delete mode 100644 widgets/table/e-table-config.h delete mode 100644 widgets/table/e-table-config.ui delete mode 100644 widgets/table/e-table-defines.h delete mode 100644 widgets/table/e-table-extras.c delete mode 100644 widgets/table/e-table-extras.h delete mode 100644 widgets/table/e-table-field-chooser-dialog.c delete mode 100644 widgets/table/e-table-field-chooser-dialog.h delete mode 100644 widgets/table/e-table-field-chooser-item.c delete mode 100644 widgets/table/e-table-field-chooser-item.h delete mode 100644 widgets/table/e-table-field-chooser.c delete mode 100644 widgets/table/e-table-field-chooser.h delete mode 100644 widgets/table/e-table-group-container.c delete mode 100644 widgets/table/e-table-group-container.h delete mode 100644 widgets/table/e-table-group-leaf.c delete mode 100644 widgets/table/e-table-group-leaf.h delete mode 100644 widgets/table/e-table-group.c delete mode 100644 widgets/table/e-table-group.h delete mode 100644 widgets/table/e-table-header-item.c delete mode 100644 widgets/table/e-table-header-item.h delete mode 100644 widgets/table/e-table-header-utils.c delete mode 100644 widgets/table/e-table-header-utils.h delete mode 100644 widgets/table/e-table-header.c delete mode 100644 widgets/table/e-table-header.h delete mode 100644 widgets/table/e-table-item.c delete mode 100644 widgets/table/e-table-item.h delete mode 100644 widgets/table/e-table-memory-callbacks.c delete mode 100644 widgets/table/e-table-memory-callbacks.h delete mode 100644 widgets/table/e-table-memory-store.c delete mode 100644 widgets/table/e-table-memory-store.h delete mode 100644 widgets/table/e-table-memory.c delete mode 100644 widgets/table/e-table-memory.h delete mode 100644 widgets/table/e-table-model.c delete mode 100644 widgets/table/e-table-model.h delete mode 100644 widgets/table/e-table-one.c delete mode 100644 widgets/table/e-table-one.h delete mode 100644 widgets/table/e-table-search.c delete mode 100644 widgets/table/e-table-search.h delete mode 100644 widgets/table/e-table-selection-model.c delete mode 100644 widgets/table/e-table-selection-model.h delete mode 100644 widgets/table/e-table-sort-info.c delete mode 100644 widgets/table/e-table-sort-info.h delete mode 100644 widgets/table/e-table-sorted-variable.c delete mode 100644 widgets/table/e-table-sorted-variable.h delete mode 100644 widgets/table/e-table-sorted.c delete mode 100644 widgets/table/e-table-sorted.h delete mode 100644 widgets/table/e-table-sorter.c delete mode 100644 widgets/table/e-table-sorter.h delete mode 100644 widgets/table/e-table-sorting-utils.c delete mode 100644 widgets/table/e-table-sorting-utils.h delete mode 100644 widgets/table/e-table-specification.c delete mode 100644 widgets/table/e-table-specification.h delete mode 100644 widgets/table/e-table-state.c delete mode 100644 widgets/table/e-table-state.h delete mode 100644 widgets/table/e-table-subset-variable.c delete mode 100644 widgets/table/e-table-subset-variable.h delete mode 100644 widgets/table/e-table-subset.c delete mode 100644 widgets/table/e-table-subset.h delete mode 100644 widgets/table/e-table-utils.c delete mode 100644 widgets/table/e-table-utils.h delete mode 100644 widgets/table/e-table-without.c delete mode 100644 widgets/table/e-table-without.h delete mode 100644 widgets/table/e-table.c delete mode 100644 widgets/table/e-table.dia delete mode 100644 widgets/table/e-table.h delete mode 100644 widgets/table/e-tree-memory-callbacks.c delete mode 100644 widgets/table/e-tree-memory-callbacks.h delete mode 100644 widgets/table/e-tree-memory.c delete mode 100644 widgets/table/e-tree-memory.h delete mode 100644 widgets/table/e-tree-model.c delete mode 100644 widgets/table/e-tree-model.h delete mode 100644 widgets/table/e-tree-selection-model.c delete mode 100644 widgets/table/e-tree-selection-model.h delete mode 100644 widgets/table/e-tree-sorted.c delete mode 100644 widgets/table/e-tree-sorted.h delete mode 100644 widgets/table/e-tree-table-adapter.c delete mode 100644 widgets/table/e-tree-table-adapter.h delete mode 100644 widgets/table/e-tree.c delete mode 100644 widgets/table/e-tree.h delete mode 100644 widgets/table/gal-a11y-e-cell-popup.c delete mode 100644 widgets/table/gal-a11y-e-cell-popup.h delete mode 100644 widgets/table/gal-a11y-e-cell-registry.c delete mode 100644 widgets/table/gal-a11y-e-cell-registry.h delete mode 100644 widgets/table/gal-a11y-e-cell-toggle.c delete mode 100644 widgets/table/gal-a11y-e-cell-toggle.h delete mode 100644 widgets/table/gal-a11y-e-cell-tree.c delete mode 100644 widgets/table/gal-a11y-e-cell-tree.h delete mode 100644 widgets/table/gal-a11y-e-cell-vbox.c delete mode 100644 widgets/table/gal-a11y-e-cell-vbox.h delete mode 100644 widgets/table/gal-a11y-e-cell.c delete mode 100644 widgets/table/gal-a11y-e-cell.h delete mode 100644 widgets/table/gal-a11y-e-table-click-to-add-factory.c delete mode 100644 widgets/table/gal-a11y-e-table-click-to-add-factory.h delete mode 100644 widgets/table/gal-a11y-e-table-click-to-add.c delete mode 100644 widgets/table/gal-a11y-e-table-click-to-add.h delete mode 100644 widgets/table/gal-a11y-e-table-column-header.c delete mode 100644 widgets/table/gal-a11y-e-table-column-header.h delete mode 100644 widgets/table/gal-a11y-e-table-factory.c delete mode 100644 widgets/table/gal-a11y-e-table-factory.h delete mode 100644 widgets/table/gal-a11y-e-table-item-factory.c delete mode 100644 widgets/table/gal-a11y-e-table-item-factory.h delete mode 100644 widgets/table/gal-a11y-e-table-item.c delete mode 100644 widgets/table/gal-a11y-e-table-item.h delete mode 100644 widgets/table/gal-a11y-e-table.c delete mode 100644 widgets/table/gal-a11y-e-table.h delete mode 100644 widgets/table/gal-a11y-e-tree-factory.c delete mode 100644 widgets/table/gal-a11y-e-tree-factory.h delete mode 100644 widgets/table/gal-a11y-e-tree.c delete mode 100644 widgets/table/gal-a11y-e-tree.h delete mode 100644 widgets/table/sample.table delete mode 100644 widgets/table/spec.xml delete mode 100644 widgets/table/tree-expanded.xpm delete mode 100644 widgets/table/tree-unexpanded.xpm delete mode 100644 widgets/text/Makefile.am delete mode 100644 widgets/text/e-reflow-model.c delete mode 100644 widgets/text/e-reflow-model.h delete mode 100644 widgets/text/e-reflow.c delete mode 100644 widgets/text/e-reflow.h delete mode 100644 widgets/text/e-text-model-repos.c delete mode 100644 widgets/text/e-text-model-repos.h delete mode 100644 widgets/text/e-text-model.c delete mode 100644 widgets/text/e-text-model.h delete mode 100644 widgets/text/e-text.c delete mode 100644 widgets/text/e-text.h delete mode 100644 widgets/text/gal-a11y-e-text-factory.c delete mode 100644 widgets/text/gal-a11y-e-text-factory.h delete mode 100644 widgets/text/gal-a11y-e-text.c delete mode 100644 widgets/text/gal-a11y-e-text.h diff --git a/Makefile.am b/Makefile.am index d9403ab794..888734a783 100644 --- a/Makefile.am +++ b/Makefile.am @@ -51,13 +51,8 @@ SUBDIRS = \ m4 \ data \ libgnomecanvas \ - libevolution-utils \ - filter \ - libemail-utils \ - libemail-engine \ e-util \ - a11y \ - widgets \ + libemail-engine \ shell \ $(SMIME_SUBDIR) \ em-format \ diff --git a/a11y/Makefile.am b/a11y/Makefile.am deleted file mode 100644 index 50f603f009..0000000000 --- a/a11y/Makefile.am +++ /dev/null @@ -1,23 +0,0 @@ -# for debug -#A11Y_CFLAGS += -pedantic -ansi -DACC_DEBUG -Werror - -privsolib_LTLIBRARIES = libevolution-a11y.la - -libevolution_a11y_la_CPPFLAGS = \ - $(AM_CPPFLAGS) \ - $(GNOME_PLATFORM_CFLAGS) \ - $(A11Y_CFLAGS) - -libevolution_a11y_la_SOURCES = \ - ea-factory.h \ - gal-a11y-util.c \ - gal-a11y-util.h \ - gal-a11y-factory.h - -libevolution_a11y_la_LDFLAGS = -avoid-version $(NO_UNDEFINED) - -libevolution_a11y_la_LIBADD = \ - $(top_builddir)/e-util/libeutil.la \ - $(GNOME_PLATFORM_LIBS) - --include $(top_srcdir)/git.mk diff --git a/a11y/ea-factory.h b/a11y/ea-factory.h deleted file mode 100644 index f68b3a065b..0000000000 --- a/a11y/ea-factory.h +++ /dev/null @@ -1,114 +0,0 @@ -/* - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with the program; if not, see - * - * - * Authors: - * Bolian Yin - * - * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) - * - */ - -/* Evolution Accessibility -*/ - -#ifndef _EA_FACTORY_H__ -#define _EA_FACTORY_H__ - -#include - -#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/a11y/gal-a11y-factory.h b/a11y/gal-a11y-factory.h deleted file mode 100644 index 9cd2b7b151..0000000000 --- a/a11y/gal-a11y-factory.h +++ /dev/null @@ -1,85 +0,0 @@ -/* - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with the program; if not, see - * - * - * Authors: - * Gilbert Fang - * - * This file is mainly from the gailfactory.h of GAIL. - * - * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) - * - */ - -#ifndef _GAL_A11Y_FACTORY_H__ -#define _GAL_A11Y_FACTORY_H__ - -#include -#include - -#define GAL_A11Y_FACTORY(type, type_as_function, opt_create_accessible) \ - \ -static GType \ -type_as_function ## _factory_get_accessible_type (void) \ -{ \ - return type; \ -} \ - \ -static AtkObject * \ -type_as_function ## _factory_create_accessible (GObject *obj) \ -{ \ - GtkWidget *widget; \ - AtkObject *accessible; \ - \ - g_return_val_if_fail (GTK_IS_WIDGET (obj), NULL); \ - \ - widget = GTK_WIDGET (obj); \ - \ - accessible = opt_create_accessible (widget); \ - \ - return accessible; \ -} \ - \ -static void \ -type_as_function ## _factory_class_init (AtkObjectFactoryClass *klass) \ -{ \ - klass->create_accessible = type_as_function ## _factory_create_accessible; \ - klass->get_accessible_type = type_as_function ## _factory_get_accessible_type;\ -} \ - \ -static GType \ -type_as_function ## _factory_get_type (void) \ -{ \ - static GType t = 0; \ - \ - if (!t) \ - { \ - gchar *name; \ - static const GTypeInfo tinfo = \ - { \ - sizeof (AtkObjectFactoryClass), \ - NULL, NULL, (GClassInitFunc) type_as_function ## _factory_class_init, \ - NULL, NULL, sizeof (AtkObjectFactory), 0, NULL, NULL \ - }; \ - \ - name = g_strconcat (g_type_name (type), "Factory", NULL); \ - t = g_type_register_static ( \ - ATK_TYPE_OBJECT_FACTORY, name, &tinfo, 0); \ - g_free (name); \ - } \ - \ - return t; \ -} - -#endif /* _GAL_A11Y_FACTORY_H__ */ diff --git a/a11y/gal-a11y-util.c b/a11y/gal-a11y-util.c deleted file mode 100644 index 9d44758187..0000000000 --- a/a11y/gal-a11y-util.c +++ /dev/null @@ -1,49 +0,0 @@ -/* - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with the program; if not, see - * - * - * Authors: - * Christopher James Lahey - * - * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) - * - */ - -#ifdef HAVE_CONFIG_H -#include -#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/a11y/gal-a11y-util.h b/a11y/gal-a11y-util.h deleted file mode 100644 index d6cf7f4000..0000000000 --- a/a11y/gal-a11y-util.h +++ /dev/null @@ -1,36 +0,0 @@ -/* - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with the program; if not, see - * - * - * Authors: - * Christopher James Lahey - * - * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) - * - */ - -#ifndef __GAL_A11Y_UTIL_H__ -#define __GAL_A11Y_UTIL_H__ - -#include - -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/addressbook/gui/contact-editor/Makefile.am b/addressbook/gui/contact-editor/Makefile.am index d67a21bba2..ddd4592bf9 100644 --- a/addressbook/gui/contact-editor/Makefile.am +++ b/addressbook/gui/contact-editor/Makefile.am @@ -3,16 +3,16 @@ privsolib_LTLIBRARIES = libecontacteditor.la libecontacteditor_la_CPPFLAGS = \ $(AM_CPPFLAGS) \ -I$(top_srcdir) \ - -I$(top_srcdir)/widgets \ -I$(top_srcdir)/addressbook/ \ -I$(top_srcdir)/addressbook/gui/merging \ - -I$(top_srcdir)/widgets/table \ -I$(top_builddir)/shell \ -DEVOLUTION_UIDIR=\""$(uidir)"\" \ -DEVOLUTION_IMAGESDIR=\""$(imagesdir)"\" \ -DG_LOG_DOMAIN=\"contact-editor\" \ $(EVOLUTION_DATA_SERVER_CFLAGS) \ - $(GNOME_PLATFORM_CFLAGS) + $(GNOME_PLATFORM_CFLAGS) \ + $(CHAMPLAIN_CFLAGS) \ + $(GTKHTML_CFLAGS) libecontacteditor_la_SOURCES = \ eab-editor.c \ @@ -28,15 +28,15 @@ libecontacteditor_la_LDFLAGS = -avoid-version $(NO_UNDEFINED) libecontacteditor_la_LIBADD = \ $(top_builddir)/e-util/libeutil.la \ - $(top_builddir)/widgets/misc/libemiscwidgets.la \ $(top_builddir)/addressbook/util/libeabutil.la \ $(top_builddir)/addressbook/gui/widgets/libeabwidgets.la \ $(top_builddir)/addressbook/gui/merging/libeabbookmerging.la \ $(top_builddir)/addressbook/printing/libecontactprint.la \ - $(top_builddir)/widgets/menus/libmenus.la \ $(EVOLUTION_ADDRESSBOOK_LIBS) \ $(EVOLUTION_DATA_SERVER_LIBS) \ - $(GNOME_PLATFORM_LIBS) + $(GNOME_PLATFORM_LIBS) \ + $(CHAMPLAIN_LIBS) \ + $(GTKHTML_LIBS) ui_DATA = \ contact-editor.ui \ diff --git a/addressbook/gui/contact-editor/e-contact-editor.c b/addressbook/gui/contact-editor/e-contact-editor.c index 8e2e455dc1..ef0700baed 100644 --- a/addressbook/gui/contact-editor/e-contact-editor.c +++ b/addressbook/gui/contact-editor/e-contact-editor.c @@ -30,23 +30,16 @@ #include #include #include + #include #include #include #include -#include +#include "shell/e-shell.h" #include "addressbook/printing/e-contact-print.h" #include "addressbook/gui/widgets/eab-gui-util.h" -#include "e-util/e-util.h" -#include "libevolution-utils/e-alert-dialog.h" -#include "misc/e-dateedit.h" -#include "misc/e-image-chooser.h" -#include "misc/e-url-entry.h" -#include "e-util/e-icon-factory.h" -#include "e-util/e-util-private.h" -#include "shell/e-shell.h" #include "eab-contact-merging.h" diff --git a/addressbook/gui/contact-editor/e-contact-quick-add.c b/addressbook/gui/contact-editor/e-contact-quick-add.c index e9076a9df8..6dcb7dc628 100644 --- a/addressbook/gui/contact-editor/e-contact-quick-add.c +++ b/addressbook/gui/contact-editor/e-contact-quick-add.c @@ -29,13 +29,10 @@ #include #include -#include - #include #include "e-contact-editor.h" #include "e-contact-quick-add.h" #include "eab-contact-merging.h" -#include "libevolution-utils/e-alert-dialog.h" typedef struct _QuickAdd QuickAdd; struct _QuickAdd { diff --git a/addressbook/gui/contact-list-editor/Makefile.am b/addressbook/gui/contact-list-editor/Makefile.am index 01a4c7c375..0479683406 100644 --- a/addressbook/gui/contact-list-editor/Makefile.am +++ b/addressbook/gui/contact-list-editor/Makefile.am @@ -3,7 +3,6 @@ privsolib_LTLIBRARIES = libecontactlisteditor.la libecontactlisteditor_la_CPPFLAGS = \ $(AM_CPPFLAGS) \ -I$(top_srcdir) \ - -I$(top_srcdir)/widgets \ -I$(top_srcdir)/addressbook/ \ -I$(top_srcdir)/addressbook/gui/merging \ -I$(top_srcdir)/addressbook/gui/contact-editor \ @@ -11,7 +10,9 @@ libecontactlisteditor_la_CPPFLAGS = \ -DEVOLUTION_UIDIR=\""$(uidir)"\" \ -DG_LOG_DOMAIN=\"contact-list-editor\" \ $(EVOLUTION_DATA_SERVER_CFLAGS) \ - $(GNOME_PLATFORM_CFLAGS) + $(GNOME_PLATFORM_CFLAGS) \ + $(CHAMPLAIN_CFLAGS) \ + $(GTKHTML_CFLAGS) libecontactlisteditor_la_SOURCES = \ e-contact-list-editor.c \ @@ -24,13 +25,12 @@ libecontactlisteditor_la_LDFLAGS = -avoid-version $(NO_UNDEFINED) libecontactlisteditor_la_LIBADD = \ $(top_builddir)/addressbook/util/libeabutil.la \ $(top_builddir)/addressbook/gui/contact-editor/libecontacteditor.la \ - $(top_builddir)/widgets/table/libetable.la \ - $(top_builddir)/widgets/misc/libemiscwidgets.la \ $(top_builddir)/e-util/libeutil.la \ $(top_builddir)/shell/libeshell.la \ - $(top_builddir)/libevolution-utils/libevolution-utils.la \ $(EVOLUTION_DATA_SERVER_LIBS) \ - $(GNOME_PLATFORM_LIBS) + $(GNOME_PLATFORM_LIBS) \ + $(CHAMPLAIN_LIBS) \ + $(GTKHTML_LIBS) ui_DATA = contact-list-editor.ui diff --git a/addressbook/gui/contact-list-editor/e-contact-list-editor.c b/addressbook/gui/contact-list-editor/e-contact-list-editor.c index a6205757d8..8f0c8783fb 100644 --- a/addressbook/gui/contact-list-editor/e-contact-list-editor.c +++ b/addressbook/gui/contact-list-editor/e-contact-list-editor.c @@ -25,10 +25,6 @@ #endif #include "e-contact-list-editor.h" -#include -#include -#include -#include "shell/e-shell.h" #include @@ -38,7 +34,8 @@ #include -#include "e-util/e-util.h" +#include "shell/e-shell.h" + #include "addressbook/gui/widgets/eab-gui-util.h" #include "addressbook/util/eab-book-util.h" diff --git a/addressbook/gui/contact-list-editor/e-contact-list-editor.h b/addressbook/gui/contact-list-editor/e-contact-list-editor.h index 07cc4db6bf..bafd7845b1 100644 --- a/addressbook/gui/contact-list-editor/e-contact-list-editor.h +++ b/addressbook/gui/contact-list-editor/e-contact-list-editor.h @@ -23,8 +23,6 @@ #ifndef __E_CONTACT_LIST_EDITOR_H__ #define __E_CONTACT_LIST_EDITOR_H__ -#include - #include "addressbook/gui/contact-editor/eab-editor.h" #define E_TYPE_CONTACT_LIST_EDITOR \ diff --git a/addressbook/gui/contact-list-editor/e-contact-list-model.c b/addressbook/gui/contact-list-editor/e-contact-list-model.c index 337fd351e4..d2aae534fa 100644 --- a/addressbook/gui/contact-list-editor/e-contact-list-model.c +++ b/addressbook/gui/contact-list-editor/e-contact-list-model.c @@ -26,7 +26,6 @@ #include #include "e-contact-list-model.h" -#include "libevolution-utils/e-alert-dialog.h" #include "shell/e-shell.h" #define E_CONTACT_LIST_MODEL_GET_PRIVATE(obj) \ diff --git a/addressbook/gui/merging/Makefile.am b/addressbook/gui/merging/Makefile.am index b1c2b45cf4..a6a1522c7a 100644 --- a/addressbook/gui/merging/Makefile.am +++ b/addressbook/gui/merging/Makefile.am @@ -5,10 +5,10 @@ libeabbookmerging_la_CPPFLAGS = \ -DG_LOG_DOMAIN=\"eab-contact-merging\" \ -DEVOLUTION_UIDIR=\""$(uidir)"\" \ -I$(top_srcdir) \ - -I$(top_srcdir)/widgets \ -I$(top_srcdir)/addressbook \ $(EVOLUTION_DATA_SERVER_CFLAGS) \ $(GNOME_PLATFORM_CFLAGS) \ + $(CHAMPLAIN_CFLAGS) \ $(GTKHTML_CFLAGS) libeabbookmerging_la_SOURCES = \ diff --git a/addressbook/gui/merging/eab-contact-compare.c b/addressbook/gui/merging/eab-contact-compare.c index 7c30b28da8..8b24ea3d57 100644 --- a/addressbook/gui/merging/eab-contact-compare.c +++ b/addressbook/gui/merging/eab-contact-compare.c @@ -28,7 +28,7 @@ #include #include -#include +#include "e-util/e-util.h" #include "addressbook/util/eab-book-util.h" #include "eab-contact-compare.h" diff --git a/addressbook/gui/widgets/Makefile.am b/addressbook/gui/widgets/Makefile.am index 19c9c4f72b..e7ca15e386 100644 --- a/addressbook/gui/widgets/Makefile.am +++ b/addressbook/gui/widgets/Makefile.am @@ -12,12 +12,9 @@ libeabwidgets_la_CPPFLAGS = \ -DEVOLUTION_IMAGESDIR=\"${imagesdir}\" \ -DEVOLUTION_PRIVDATADIR=\"${privdatadir}\" \ -I$(top_srcdir) \ - -I$(top_srcdir)/filter \ - -I$(top_srcdir)/widgets \ -I$(top_srcdir)/addressbook \ -I$(top_srcdir)/addressbook/gui/merging \ -I$(top_srcdir)/addressbook/util \ - -I$(top_srcdir)/widgets/misc \ -I$(top_builddir)/shell \ $(EVOLUTION_DATA_SERVER_CFLAGS) \ $(GNOME_PLATFORM_CFLAGS) \ @@ -69,11 +66,7 @@ libeabwidgets_la_SOURCES = \ ea-addressbook.h libeabwidgets_la_LIBADD = \ - $(top_builddir)/widgets/misc/libemiscwidgets.la \ $(top_builddir)/shell/libeshell.la \ - $(top_builddir)/widgets/table/libetable.la \ - $(top_builddir)/widgets/menus/libmenus.la \ - $(top_builddir)/a11y/libevolution-a11y.la \ $(top_builddir)/e-util/libeutil.la \ $(EVOLUTION_DATA_SERVER_LIBS) \ $(GNOME_PLATFORM_LIBS) \ diff --git a/addressbook/gui/widgets/e-addressbook-reflow-adapter.h b/addressbook/gui/widgets/e-addressbook-reflow-adapter.h index d65f3f39fd..fee217c2f1 100644 --- a/addressbook/gui/widgets/e-addressbook-reflow-adapter.h +++ b/addressbook/gui/widgets/e-addressbook-reflow-adapter.h @@ -22,7 +22,8 @@ #define _E_ADDRESSBOOK_REFLOW_ADAPTER_H_ #include -#include + +#include #include "e-addressbook-model.h" diff --git a/addressbook/gui/widgets/e-addressbook-selector.c b/addressbook/gui/widgets/e-addressbook-selector.c index 513da877bc..2441a0bc89 100644 --- a/addressbook/gui/widgets/e-addressbook-selector.c +++ b/addressbook/gui/widgets/e-addressbook-selector.c @@ -24,7 +24,7 @@ #include "e-addressbook-selector.h" -#include +#include #include #include diff --git a/addressbook/gui/widgets/e-addressbook-selector.h b/addressbook/gui/widgets/e-addressbook-selector.h index adabea7205..663f58656b 100644 --- a/addressbook/gui/widgets/e-addressbook-selector.h +++ b/addressbook/gui/widgets/e-addressbook-selector.h @@ -21,8 +21,6 @@ #ifndef E_ADDRESSBOOK_SELECTOR_H #define E_ADDRESSBOOK_SELECTOR_H -#include - #include "e-addressbook-view.h" /* Standard GObject macros */ diff --git a/addressbook/gui/widgets/e-addressbook-table-adapter.h b/addressbook/gui/widgets/e-addressbook-table-adapter.h index 51c9436ff2..4b089b2d0a 100644 --- a/addressbook/gui/widgets/e-addressbook-table-adapter.h +++ b/addressbook/gui/widgets/e-addressbook-table-adapter.h @@ -21,7 +21,7 @@ #ifndef _EAB_TABLE_ADAPTER_H_ #define _EAB_TABLE_ADAPTER_H_ -#include +#include /* Standard GObject macros */ #define E_TYPE_ADDRESSBOOK_TABLE_ADAPTER \ diff --git a/addressbook/gui/widgets/e-addressbook-view.c b/addressbook/gui/widgets/e-addressbook-view.c index 5206041d29..53402a9e6e 100644 --- a/addressbook/gui/widgets/e-addressbook-view.c +++ b/addressbook/gui/widgets/e-addressbook-view.c @@ -25,40 +25,29 @@ #include #endif +#include +#include + #include -#include
-#include
-#include
-#include -#include -#include -#include -#include +#include -#include "addressbook/printing/e-contact-print.h" -#include "ea-addressbook.h" +#include "e-addressbook-view.h" -#include "e-util/e-print.h" -#include "e-util/e-selection.h" #include "e-util/e-util.h" +#include "shell/e-shell-sidebar.h" + +#include "addressbook/printing/e-contact-print.h" +#include "ea-addressbook.h" #include "gal-view-minicard.h" #include "gal-view-factory-minicard.h" -#include "e-addressbook-view.h" #include "e-addressbook-model.h" #include "eab-gui-util.h" #include "util/eab-book-util.h" #include "e-addressbook-table-adapter.h" #include "eab-contact-merging.h" -#include "libevolution-utils/e-alert-dialog.h" -#include "e-util/e-util-private.h" - -#include -#include -#include - #define E_ADDRESSBOOK_VIEW_GET_PRIVATE(obj) \ (G_TYPE_INSTANCE_GET_PRIVATE \ ((obj), E_TYPE_ADDRESSBOOK_VIEW, EAddressbookViewPrivate)) diff --git a/addressbook/gui/widgets/e-addressbook-view.h b/addressbook/gui/widgets/e-addressbook-view.h index 3c8c5e2218..94668d6ace 100644 --- a/addressbook/gui/widgets/e-addressbook-view.h +++ b/addressbook/gui/widgets/e-addressbook-view.h @@ -25,10 +25,7 @@ #include -#include -#include #include -#include #include "e-addressbook-model.h" #include "eab-contact-display.h" diff --git a/addressbook/gui/widgets/e-minicard-label.c b/addressbook/gui/widgets/e-minicard-label.c index 618e4ea160..0e75917b80 100644 --- a/addressbook/gui/widgets/e-minicard-label.c +++ b/addressbook/gui/widgets/e-minicard-label.c @@ -27,14 +27,13 @@ #include "e-minicard-label.h" #include -#include #include -#include -#include -#include -#include #include +#include + +#include "e-util/e-util.h" + static void e_minicard_label_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec); static void e_minicard_label_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec); static gboolean e_minicard_label_event (GnomeCanvasItem *item, GdkEvent *event); diff --git a/addressbook/gui/widgets/e-minicard-view-widget.c b/addressbook/gui/widgets/e-minicard-view-widget.c index ecb2483652..7ff63a1bd9 100644 --- a/addressbook/gui/widgets/e-minicard-view-widget.c +++ b/addressbook/gui/widgets/e-minicard-view-widget.c @@ -25,11 +25,10 @@ #endif #include -#include -#include #include -#include "e-util/e-util.h" +#include + #include "e-minicard-view-widget.h" static void e_minicard_view_widget_set_property diff --git a/addressbook/gui/widgets/e-minicard-view-widget.h b/addressbook/gui/widgets/e-minicard-view-widget.h index f540a43f8e..71fe00a497 100644 --- a/addressbook/gui/widgets/e-minicard-view-widget.h +++ b/addressbook/gui/widgets/e-minicard-view-widget.h @@ -23,9 +23,10 @@ #ifndef __E_MINICARD_VIEW_WIDGET_H__ #define __E_MINICARD_VIEW_WIDGET_H__ -#include #include +#include + #include "e-minicard-view.h" G_BEGIN_DECLS diff --git a/addressbook/gui/widgets/e-minicard-view.c b/addressbook/gui/widgets/e-minicard-view.c index 1889399f3d..08b0cdabf4 100644 --- a/addressbook/gui/widgets/e-minicard-view.c +++ b/addressbook/gui/widgets/e-minicard-view.c @@ -26,16 +26,17 @@ #include "e-minicard-view.h" -#include "eab-gui-util.h" -#include "util/eab-book-util.h" -#include "e-util/e-util.h" +#include #include -#include -#include #include -#include +#include + #include "e-util/e-util.h" + +#include "eab-gui-util.h" +#include "util/eab-book-util.h" + #include "ea-addressbook.h" static void e_minicard_view_drag_data_get (GtkWidget *widget, diff --git a/addressbook/gui/widgets/e-minicard-view.h b/addressbook/gui/widgets/e-minicard-view.h index 1024a85088..bf116e4513 100644 --- a/addressbook/gui/widgets/e-minicard-view.h +++ b/addressbook/gui/widgets/e-minicard-view.h @@ -26,8 +26,8 @@ #include "e-minicard.h" -#include -#include +#include + #include "e-addressbook-reflow-adapter.h" G_BEGIN_DECLS diff --git a/addressbook/gui/widgets/e-minicard.c b/addressbook/gui/widgets/e-minicard.c index b0dae34b69..6a049aca50 100644 --- a/addressbook/gui/widgets/e-minicard.c +++ b/addressbook/gui/widgets/e-minicard.c @@ -24,20 +24,19 @@ #include #endif +#include "e-minicard.h" + #include -#include #include +#include + #include -#include -#include -#include -#include + +#include "e-util/e-util.h" + #include "eab-gui-util.h" -#include "e-minicard.h" #include "e-minicard-label.h" #include "e-minicard-view.h" -#include -#include #include "ea-addressbook.h" static void e_minicard_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec); diff --git a/addressbook/gui/widgets/ea-addressbook.c b/addressbook/gui/widgets/ea-addressbook.c index ca218f4245..51af22d0d4 100644 --- a/addressbook/gui/widgets/ea-addressbook.c +++ b/addressbook/gui/widgets/ea-addressbook.c @@ -24,8 +24,8 @@ #include #endif -#include -#include "a11y/ea-factory.h" +#include "e-util/e-util.h" + #include "ea-addressbook.h" #include "ea-minicard.h" #include "ea-minicard-view.h" diff --git a/addressbook/gui/widgets/eab-config.h b/addressbook/gui/widgets/eab-config.h index 3907889415..83c08b4172 100644 --- a/addressbook/gui/widgets/eab-config.h +++ b/addressbook/gui/widgets/eab-config.h @@ -26,7 +26,7 @@ #include -#include "e-util/e-config.h" +#include #define EAB_TYPE_CONFIG (eab_config_get_type ()) #define EAB_CONFIG(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EAB_TYPE_CONFIG, EABConfig)) diff --git a/addressbook/gui/widgets/eab-contact-display.c b/addressbook/gui/widgets/eab-contact-display.c index e00e089d38..549278b994 100644 --- a/addressbook/gui/widgets/eab-contact-display.c +++ b/addressbook/gui/widgets/eab-contact-display.c @@ -25,25 +25,17 @@ #endif #include "eab-contact-display.h" -#include "eab-contact-formatter.h" -#include "eab-gui-util.h" -#include "e-util/e-util.h" -#include "e-util/e-util-private.h" -#include "e-util/e-html-utils.h" -#include "e-util/e-icon-factory.h" -#include "e-util/e-plugin-ui.h" -#include "e-util/e-file-request.h" -#include "e-util/e-stock-request.h" +#include +#include #include -#ifdef WITH_CONTACT_MAPS -#include "widgets/misc/e-contact-map.h" -#endif +#include "e-util/e-util.h" -#include -#include +#include "eab-contact-formatter.h" + +#include "eab-gui-util.h" #define EAB_CONTACT_DISPLAY_GET_PRIVATE(obj) \ (G_TYPE_INSTANCE_GET_PRIVATE \ diff --git a/addressbook/gui/widgets/eab-contact-display.h b/addressbook/gui/widgets/eab-contact-display.h index 79243dedae..484e312b6b 100644 --- a/addressbook/gui/widgets/eab-contact-display.h +++ b/addressbook/gui/widgets/eab-contact-display.h @@ -25,7 +25,7 @@ #include -#include +#include /* Standard GObject macros */ #define EAB_TYPE_CONTACT_DISPLAY \ diff --git a/addressbook/gui/widgets/eab-contact-formatter.c b/addressbook/gui/widgets/eab-contact-formatter.c index a3893188e3..3684e4b81c 100644 --- a/addressbook/gui/widgets/eab-contact-formatter.c +++ b/addressbook/gui/widgets/eab-contact-formatter.c @@ -20,20 +20,13 @@ #include "eab-contact-formatter.h" -#include "eab-gui-util.h" -#include "e-util/e-util.h" -#include "e-util/e-util-private.h" -#include "e-util/e-html-utils.h" -#include "e-util/e-icon-factory.h" -#include "e-util/e-plugin-ui.h" - -#ifdef WITH_CONTACT_MAPS -#include "widgets/misc/e-contact-map.h" -#endif - #include #include +#include "e-util/e-util.h" + +#include "eab-gui-util.h" + G_DEFINE_TYPE ( EABContactFormatter, eab_contact_formatter, diff --git a/addressbook/gui/widgets/eab-gui-util.c b/addressbook/gui/widgets/eab-gui-util.c index 736f64d422..1fc8644833 100644 --- a/addressbook/gui/widgets/eab-gui-util.c +++ b/addressbook/gui/widgets/eab-gui-util.c @@ -35,16 +35,10 @@ #include #include -#include +#include "shell/e-shell.h" -#include #include "eab-gui-util.h" #include "util/eab-book-util.h" -#include "libevolution-utils/e-alert-dialog.h" -#include "e-util/e-html-utils.h" -#include "shell/e-shell.h" -#include "misc/e-image-chooser.h" -#include #include "eab-contact-merging.h" /* we link to camel for decoding quoted printable email addresses */ diff --git a/addressbook/gui/widgets/eab-gui-util.h b/addressbook/gui/widgets/eab-gui-util.h index 7cdd93e84d..6d3c7bf5cb 100644 --- a/addressbook/gui/widgets/eab-gui-util.h +++ b/addressbook/gui/widgets/eab-gui-util.h @@ -27,7 +27,7 @@ #include #include -#include "libevolution-utils/e-alert-sink.h" +#include G_BEGIN_DECLS diff --git a/addressbook/gui/widgets/gal-view-factory-minicard.h b/addressbook/gui/widgets/gal-view-factory-minicard.h index e96c4e455c..a01f5e9075 100644 --- a/addressbook/gui/widgets/gal-view-factory-minicard.h +++ b/addressbook/gui/widgets/gal-view-factory-minicard.h @@ -26,7 +26,7 @@ #ifndef GAL_VIEW_FACTORY_MINICARD_H #define GAL_VIEW_FACTORY_MINICARD_H -#include +#include /* Standard GObject macros */ #define GAL_TYPE_VIEW_FACTORY_MINICARD \ diff --git a/addressbook/gui/widgets/gal-view-minicard.c b/addressbook/gui/widgets/gal-view-minicard.c index a623c57c90..dffe8069be 100644 --- a/addressbook/gui/widgets/gal-view-minicard.c +++ b/addressbook/gui/widgets/gal-view-minicard.c @@ -28,7 +28,6 @@ #endif #include -#include #include "gal-view-minicard.h" diff --git a/addressbook/gui/widgets/gal-view-minicard.h b/addressbook/gui/widgets/gal-view-minicard.h index b360301268..04e67113de 100644 --- a/addressbook/gui/widgets/gal-view-minicard.h +++ b/addressbook/gui/widgets/gal-view-minicard.h @@ -25,7 +25,7 @@ #ifndef GAL_VIEW_MINICARD_H #define GAL_VIEW_MINICARD_H -#include +#include #include #include "e-addressbook-view.h" diff --git a/addressbook/importers/Makefile.am b/addressbook/importers/Makefile.am index 515370d38c..cd509f9eb6 100644 --- a/addressbook/importers/Makefile.am +++ b/addressbook/importers/Makefile.am @@ -7,10 +7,11 @@ libevolution_addressbook_importers_la_CPPFLAGS = \ -DG_LOG_DOMAIN=\"Evolution-Importer\" \ -I$(top_srcdir) \ -I$(top_srcdir)/addressbook \ - -I$(top_srcdir)/widgets \ -I$(top_builddir)/addressbook \ $(EVOLUTION_DATA_SERVER_CFLAGS) \ - $(GNOME_PLATFORM_CFLAGS) + $(GNOME_PLATFORM_CFLAGS) \ + $(CHAMPLAIN_CFLAGS) \ + $(GTKHTML_CFLAGS) libevolution_addressbook_importers_la_SOURCES = \ evolution-ldif-importer.c \ @@ -24,9 +25,10 @@ libevolution_addressbook_importers_la_LIBADD = \ $(top_builddir)/e-util/libeutil.la \ $(top_builddir)/shell/libeshell.la \ $(top_builddir)/addressbook/util/libeabutil.la \ - $(top_builddir)/widgets/misc/libemiscwidgets.la \ $(EVOLUTION_DATA_SERVER_LIBS) \ $(GNOME_PLATFORM_LIBS) \ + $(CHAMPLAIN_LIBS) \ + $(GTKHTML_LIBS) \ $(IMPORTERS_LIBS) -include $(top_srcdir)/git.mk diff --git a/addressbook/importers/evolution-csv-importer.c b/addressbook/importers/evolution-csv-importer.c index 7594ab1cf3..b32f8e1b7b 100644 --- a/addressbook/importers/evolution-csv-importer.c +++ b/addressbook/importers/evolution-csv-importer.c @@ -35,10 +35,8 @@ #include #include -#include #include -#include #include "evolution-addressbook-importers.h" diff --git a/addressbook/importers/evolution-ldif-importer.c b/addressbook/importers/evolution-ldif-importer.c index c95b2f4357..db2bfbf27d 100644 --- a/addressbook/importers/evolution-ldif-importer.c +++ b/addressbook/importers/evolution-ldif-importer.c @@ -42,10 +42,8 @@ #include #include -#include #include -#include #include "evolution-addressbook-importers.h" diff --git a/addressbook/importers/evolution-vcard-importer.c b/addressbook/importers/evolution-vcard-importer.c index d430bd24eb..5129754501 100644 --- a/addressbook/importers/evolution-vcard-importer.c +++ b/addressbook/importers/evolution-vcard-importer.c @@ -36,14 +36,10 @@ #include #include -#include #include #include -#include -#include -#include #include "evolution-addressbook-importers.h" diff --git a/addressbook/printing/Makefile.am b/addressbook/printing/Makefile.am index edf3cc9795..fa92d19676 100644 --- a/addressbook/printing/Makefile.am +++ b/addressbook/printing/Makefile.am @@ -12,7 +12,9 @@ libecontactprint_la_CPPFLAGS = \ -DEVOLUTION_UIDIR=\""$(uidir)"\" \ -DEVOLUTION_ECPSDIR=\""$(ecpsdir)"\" \ $(EVOLUTION_DATA_SERVER_CFLAGS) \ - $(GNOME_PLATFORM_CFLAGS) + $(GNOME_PLATFORM_CFLAGS) \ + $(CHAMPLAIN_CFLAGS) \ + $(GTKHTML_CFLAGS) noinst_LTLIBRARIES = libecontactprint.la @@ -22,10 +24,11 @@ libecontactprint_la_SOURCES = \ e-contact-print.h libecontactprint_la_LIBADD = \ - $(top_builddir)/widgets/misc/libemiscwidgets.la \ $(top_builddir)/e-util/libeutil.la \ $(EVOLUTION_DATA_SERVER_LIBS) \ - $(GNOME_PLATFORM_LIBS) + $(GNOME_PLATFORM_LIBS) \ + $(CHAMPLAIN_LIBS) \ + $(GTKHTML_LIBS) noinst_PROGRAMS = contact-print-test diff --git a/addressbook/printing/e-contact-print.c b/addressbook/printing/e-contact-print.c index 290863c817..248fa97515 100644 --- a/addressbook/printing/e-contact-print.c +++ b/addressbook/printing/e-contact-print.c @@ -33,7 +33,6 @@ #include #include -#include "e-util/e-print.h" #include "e-util/e-util.h" #include "e-util/e-util-private.h" diff --git a/addressbook/util/Makefile.am b/addressbook/util/Makefile.am index c27b18c67c..10f3bb0b97 100644 --- a/addressbook/util/Makefile.am +++ b/addressbook/util/Makefile.am @@ -10,7 +10,6 @@ libeabutil_la_CPPFLAGS = \ -I$(top_srcdir) \ -I$(top_builddir)/shell \ -I$(top_srcdir)/shell \ - -I$(top_srcdir)/widgets \ $(EVOLUTION_DATA_SERVER_CFLAGS) \ $(GNOME_PLATFORM_CFLAGS) @@ -21,7 +20,6 @@ libeabutil_la_SOURCES = \ libeabutil_la_LDFLAGS = -avoid-version $(NO_UNDEFINED) libeabutil_la_LIBADD = \ - $(top_builddir)/widgets/misc/libemiscwidgets.la \ $(top_builddir)/e-util/libeutil.la \ $(top_builddir)/shell/libeshell.la \ $(EVOLUTION_DATA_SERVER_LIBS) \ diff --git a/calendar/alarm-notify/Makefile.am b/calendar/alarm-notify/Makefile.am index 775a3693cb..2ff5b3dd46 100644 --- a/calendar/alarm-notify/Makefile.am +++ b/calendar/alarm-notify/Makefile.am @@ -12,7 +12,6 @@ evolution_alarm_notify_CPPFLAGS = \ $(AM_CPPFLAGS) \ -DG_LOG_DOMAIN=\"evolution-alarm-notify\" \ -I$(top_srcdir) \ - -I$(top_srcdir)/widgets \ -I$(top_srcdir)/calendar \ -DEVOLUTION_UIDIR=\""$(uidir)"\" \ -DEVOLUTION_ICONDIR=\""$(icondir)"\" \ @@ -21,7 +20,9 @@ evolution_alarm_notify_CPPFLAGS = \ $(EVOLUTION_DATA_SERVER_CFLAGS) \ $(GNOME_PLATFORM_CFLAGS) \ $(LIBNOTIFY_CFLAGS) \ - $(CANBERRA_CFLAGS) + $(CANBERRA_CFLAGS) \ + $(CHAMPLAIN_CFLAGS) \ + $(GTKHTML_CFLAGS) ui_DATA = \ alarm-notify.ui @@ -44,17 +45,17 @@ evolution_alarm_notify_SOURCES = \ evolution_alarm_notify_LDADD = \ $(top_builddir)/e-util/libeutil.la \ $(top_builddir)/composer/libcomposer.la \ - $(top_builddir)/widgets/misc/libemiscwidgets.la \ $(top_builddir)/calendar/gui/libevolution-calendar.la \ $(top_builddir)/calendar/importers/libevolution-calendar-importers.la \ $(top_builddir)/addressbook/gui/contact-editor/libecontacteditor.la \ $(top_builddir)/addressbook/gui/contact-list-editor/libecontactlisteditor.la \ $(top_builddir)/addressbook/util/libeabutil.la \ - $(top_builddir)/libevolution-utils/libevolution-utils.la \ $(EVOLUTION_DATA_SERVER_LIBS) \ $(GNOME_PLATFORM_LIBS) \ $(LIBNOTIFY_LIBS) \ $(CANBERRA_LIBS) \ + $(CHAMPLAIN_LIBS) \ + $(GTKHTML_LIBS) $(EVOLUTIONALARMNOTIFYICON) if OS_WIN32 diff --git a/calendar/alarm-notify/alarm-notify-dialog.c b/calendar/alarm-notify/alarm-notify-dialog.c index 3bb705a772..ac6ce49d93 100644 --- a/calendar/alarm-notify/alarm-notify-dialog.c +++ b/calendar/alarm-notify/alarm-notify-dialog.c @@ -30,14 +30,12 @@ #include #include +#include "e-util/e-util.h" + #include "alarm-notify-dialog.h" #include "config-data.h" #include "util.h" -#include "e-util/e-util.h" -#include "e-util/e-util-private.h" -#include "misc/e-buffer-tagger.h" - enum { ALARM_DISPLAY_COLUMN, ALARM_SUMMARY_COLUMN, diff --git a/calendar/alarm-notify/alarm-notify.c b/calendar/alarm-notify/alarm-notify.c index 54242f2049..ce7efee9ac 100644 --- a/calendar/alarm-notify/alarm-notify.c +++ b/calendar/alarm-notify/alarm-notify.c @@ -27,6 +27,8 @@ #include #include +#include "e-util/e-util.h" + #include "alarm.h" #include "alarm-notify.h" #include "alarm-queue.h" diff --git a/calendar/alarm-notify/alarm-notify.h b/calendar/alarm-notify/alarm-notify.h index 61097f310a..45de689a4e 100644 --- a/calendar/alarm-notify/alarm-notify.h +++ b/calendar/alarm-notify/alarm-notify.h @@ -28,7 +28,6 @@ #include #include -#include /* Standard GObject macros */ #define TYPE_ALARM_NOTIFY \ diff --git a/calendar/gui/Makefile.am b/calendar/gui/Makefile.am index 75991807df..f1c0727ab1 100644 --- a/calendar/gui/Makefile.am +++ b/calendar/gui/Makefile.am @@ -58,8 +58,6 @@ libevolution_calendar_la_CPPFLAGS = \ -I$(top_srcdir)/shell \ -I$(top_srcdir) \ -I$(top_srcdir)/calendar \ - -I$(top_srcdir)/widgets \ - -I$(top_srcdir)/widgets/misc \ -DEVOLUTION_RULEDIR=\"$(ruledir)\" \ -DEVOLUTION_UIDIR=\""$(uidir)"\" \ -DEVOLUTION_ETSPECDIR=\""$(etspecdir)"\" \ @@ -70,6 +68,7 @@ libevolution_calendar_la_CPPFLAGS = \ -DPREFIX=\""$(prefix)"\" \ $(EVOLUTION_DATA_SERVER_CFLAGS) \ $(GNOME_PLATFORM_CFLAGS) \ + $(CHAMPLAIN_CFLAGS) \ $(GTKHTML_CFLAGS) \ $(LIBSOUP_CFLAGS) @@ -205,18 +204,13 @@ libevolution_calendar_la_LIBADD = \ $(top_builddir)/composer/libcomposer.la \ $(top_builddir)/addressbook/gui/contact-editor/libecontacteditor.la \ $(top_builddir)/addressbook/gui/contact-list-editor/libecontactlisteditor.la \ - $(top_builddir)/widgets/menus/libmenus.la \ $(top_builddir)/shell/libeshell.la \ $(top_builddir)/calendar/gui/dialogs/libcal-dialogs.la \ $(top_builddir)/calendar/importers/libevolution-calendar-importers.la \ - $(top_builddir)/libemail-utils/libemail-utils.la \ - $(top_builddir)/widgets/e-timezone-dialog/libetimezonedialog.la \ - $(top_builddir)/widgets/misc/libemiscwidgets.la \ - $(top_builddir)/widgets/table/libetable.la \ - $(top_builddir)/filter/libfilter.la \ $(top_builddir)/e-util/libeutil.la \ $(EVOLUTION_DATA_SERVER_LIBS) \ $(GNOME_PLATFORM_LIBS) \ + $(CHAMPLAIN_LIBS) \ $(GTKHTML_LIBS) \ $(LIBSOUP_LIBS) diff --git a/calendar/gui/calendar-config.c b/calendar/gui/calendar-config.c index 1bc62730ab..15b6024931 100644 --- a/calendar/gui/calendar-config.c +++ b/calendar/gui/calendar-config.c @@ -32,8 +32,7 @@ #include #include #include -#include -#include + #include #include "calendar-config-keys.h" diff --git a/calendar/gui/calendar-config.h b/calendar/gui/calendar-config.h index c005ec399e..cd492c1480 100644 --- a/calendar/gui/calendar-config.h +++ b/calendar/gui/calendar-config.h @@ -32,7 +32,7 @@ #include #include -#include +#include /* These are used to get/set the working days in the week. The bit-flags are * combined together. The bits must be from 0 (Sun) to 6 (Sat) to match the diff --git a/calendar/gui/calendar-view-factory.h b/calendar/gui/calendar-view-factory.h index bad3dcfd1f..833c4bd281 100644 --- a/calendar/gui/calendar-view-factory.h +++ b/calendar/gui/calendar-view-factory.h @@ -26,7 +26,6 @@ #ifndef CALENDAR_VIEW_FACTORY_H #define CALENDAR_VIEW_FACTORY_H -#include #include "gnome-cal.h" G_BEGIN_DECLS diff --git a/calendar/gui/calendar-view.h b/calendar/gui/calendar-view.h index f3a2a3103b..d76f3f5449 100644 --- a/calendar/gui/calendar-view.h +++ b/calendar/gui/calendar-view.h @@ -26,7 +26,6 @@ #ifndef CALENDAR_VIEW_H #define CALENDAR_VIEW_H -#include #include "gnome-cal.h" G_BEGIN_DECLS diff --git a/calendar/gui/comp-util.c b/calendar/gui/comp-util.c index c0935ca4e5..c0e6dbb74c 100644 --- a/calendar/gui/comp-util.c +++ b/calendar/gui/comp-util.c @@ -28,12 +28,10 @@ #include #include -#include #include "calendar-config.h" #include "comp-util.h" #include "dialogs/delete-comp.h" -#include "e-util/e-categories-config.h" #include "gnome-cal.h" #include "shell/e-shell-window.h" diff --git a/calendar/gui/comp-util.h b/calendar/gui/comp-util.h index ff7a1351d3..1c91a69d64 100644 --- a/calendar/gui/comp-util.h +++ b/calendar/gui/comp-util.h @@ -28,7 +28,7 @@ #include #include -#include +#include struct _EShell; diff --git a/calendar/gui/dialogs/Makefile.am b/calendar/gui/dialogs/Makefile.am index 02af20b87b..fa6ba86e6b 100644 --- a/calendar/gui/dialogs/Makefile.am +++ b/calendar/gui/dialogs/Makefile.am @@ -4,18 +4,18 @@ libcal_dialogs_la_CPPFLAGS = \ $(AM_CPPFLAGS) \ -DG_LOG_DOMAIN=\"calendar-gui\" \ -I$(top_srcdir) \ - -I$(top_srcdir)/widgets \ -I$(top_builddir) \ -I$(top_srcdir)/calendar \ -I$(top_builddir)/shell \ -I$(top_srcdir)/shell \ - -I$(top_srcdir)/widgets/misc \ -DEVOLUTION_UIDIR=\""$(uidir)"\" \ -DEVOLUTION_ICONDIR=\""$(icondir)"\" \ -DEVOLUTION_IMAGESDIR=\""$(imagesdir)"\" \ -DPREFIX=\""$(prefix)"\" \ $(EVOLUTION_DATA_SERVER_CFLAGS) \ - $(GNOME_PLATFORM_CFLAGS) + $(GNOME_PLATFORM_CFLAGS) \ + $(CHAMPLAIN_CFLAGS) \ + $(GTKHTML_CFLAGS) ecalendarincludedir = $(privincludedir)/calendar/gui/dialogs @@ -50,10 +50,11 @@ ecalendarinclude_HEADERS = \ libcal_dialogs_la_LIBADD = \ $(top_builddir)/addressbook/util/libeabutil.la \ $(EVOLUTION_DATA_SERVER_LIBS) \ - $(GNOME_PLATFORM_LIBS) + $(GNOME_PLATFORM_LIBS) \ + $(CHAMPLAIN_LIBS) \ + $(GTKHTML_LIBS) libcal_dialogs_la_SOURCES = \ - $(IDL_GENERATED) \ alarm-dialog.c \ alarm-dialog.h \ alarm-list-dialog.c \ diff --git a/calendar/gui/dialogs/alarm-dialog.c b/calendar/gui/dialogs/alarm-dialog.c index dda1d8548b..d88fd12fb6 100644 --- a/calendar/gui/dialogs/alarm-dialog.c +++ b/calendar/gui/dialogs/alarm-dialog.c @@ -32,12 +32,11 @@ #include #include #include -#include -#include "e-util/e-util.h" -#include "e-util/e-dialog-widgets.h" -#include "e-util/e-util-private.h" #include + +#include "e-util/e-util.h" + #include "../calendar-config.h" #include "comp-editor-util.h" #include "alarm-dialog.h" diff --git a/calendar/gui/dialogs/cancel-comp.c b/calendar/gui/dialogs/cancel-comp.c index 5dbdb0ef8e..29038f4483 100644 --- a/calendar/gui/dialogs/cancel-comp.c +++ b/calendar/gui/dialogs/cancel-comp.c @@ -26,10 +26,12 @@ #include #endif +#include "cancel-comp.h" + #include #include -#include "libevolution-utils/e-alert-dialog.h" -#include "cancel-comp.h" + +#include "e-util/e-util.h" /* is_past_event: * diff --git a/calendar/gui/dialogs/cancel-comp.h b/calendar/gui/dialogs/cancel-comp.h index bfc1c6cbc4..5e6ea88076 100644 --- a/calendar/gui/dialogs/cancel-comp.h +++ b/calendar/gui/dialogs/cancel-comp.h @@ -26,6 +26,7 @@ #ifndef CANCEL_COMP_H #define CANCEL_COMP_H +#include #include gboolean cancel_component_dialog (GtkWindow *parent, ECalClient *cal_client, ECalComponent *comp, gboolean deleting); diff --git a/calendar/gui/dialogs/comp-editor-util.c b/calendar/gui/dialogs/comp-editor-util.c index 5938f52e14..52651190ac 100644 --- a/calendar/gui/dialogs/comp-editor-util.c +++ b/calendar/gui/dialogs/comp-editor-util.c @@ -31,9 +31,9 @@ #include #include -#include "widgets/misc/e-dateedit.h" +#include "shell/e-shell.h" + #include "../itip-utils.h" -#include #include "comp-editor-util.h" /** diff --git a/calendar/gui/dialogs/comp-editor-util.h b/calendar/gui/dialogs/comp-editor-util.h index a4ecc468a7..8c80683696 100644 --- a/calendar/gui/dialogs/comp-editor-util.h +++ b/calendar/gui/dialogs/comp-editor-util.h @@ -28,7 +28,6 @@ #include #include "comp-editor.h" #include "../e-meeting-attendee.h" -#include void comp_editor_dates (CompEditorPageDates *date, ECalComponent *comp); void comp_editor_free_dates (CompEditorPageDates *dates); diff --git a/calendar/gui/dialogs/comp-editor.c b/calendar/gui/dialogs/comp-editor.c index 4eaf1bce75..5fa87e861a 100644 --- a/calendar/gui/dialogs/comp-editor.c +++ b/calendar/gui/dialogs/comp-editor.c @@ -38,10 +38,6 @@ #include #include -#include -#include -#include -#include #include #include "../print.h" @@ -55,11 +51,6 @@ #include "comp-editor.h" #include "comp-editor-util.h" #include "../calendar-config-keys.h" -#include "widgets/misc/e-attachment-view.h" -#include "widgets/misc/e-attachment-paned.h" - -#include "libevolution-utils/e-alert-dialog.h" -#include "e-util/e-ui-manager.h" #define COMP_EDITOR_GET_PRIVATE(obj) \ (G_TYPE_INSTANCE_GET_PRIVATE \ diff --git a/calendar/gui/dialogs/comp-editor.h b/calendar/gui/dialogs/comp-editor.h index 14fbc0ff5b..72598acfe0 100644 --- a/calendar/gui/dialogs/comp-editor.h +++ b/calendar/gui/dialogs/comp-editor.h @@ -28,10 +28,10 @@ #include #include +#include + #include "../itip-utils.h" #include "comp-editor-page.h" -#include -#include /* Standard GObject macros */ #define TYPE_COMP_EDITOR \ diff --git a/calendar/gui/dialogs/copy-source-dialog.c b/calendar/gui/dialogs/copy-source-dialog.c index 5a7889e138..f8b668b933 100644 --- a/calendar/gui/dialogs/copy-source-dialog.c +++ b/calendar/gui/dialogs/copy-source-dialog.c @@ -27,7 +27,8 @@ #endif #include -#include + +#include "e-util/e-util.h" #include "copy-source-dialog.h" #include "select-source-dialog.h" diff --git a/calendar/gui/dialogs/delete-comp.c b/calendar/gui/dialogs/delete-comp.c index 93e2df0d91..0ec054cd39 100644 --- a/calendar/gui/dialogs/delete-comp.c +++ b/calendar/gui/dialogs/delete-comp.c @@ -26,10 +26,12 @@ #include #endif -#include -#include "libevolution-utils/e-alert-dialog.h" #include "delete-comp.h" +#include + +#include "e-util/e-util.h" + /** * delete_component_dialog: * @comp: A calendar component if a single component is to be deleted, or NULL diff --git a/calendar/gui/dialogs/e-delegate-dialog.c b/calendar/gui/dialogs/e-delegate-dialog.c index 9ae253dc00..9c5e17f59e 100644 --- a/calendar/gui/dialogs/e-delegate-dialog.c +++ b/calendar/gui/dialogs/e-delegate-dialog.c @@ -29,7 +29,6 @@ #include #include #include -#include #include "e-util/e-util.h" #include "e-util/e-util-private.h" diff --git a/calendar/gui/dialogs/e-send-options-utils.h b/calendar/gui/dialogs/e-send-options-utils.h index f5c3f0a408..6d365b3424 100644 --- a/calendar/gui/dialogs/e-send-options-utils.h +++ b/calendar/gui/dialogs/e-send-options-utils.h @@ -27,7 +27,7 @@ #include -#include "misc/e-send-options.h" +#include void e_send_options_utils_set_default_data (ESendOptionsDialog *sod, diff --git a/calendar/gui/dialogs/event-editor.c b/calendar/gui/dialogs/event-editor.c index 0681aa4ed7..2ea6297dcc 100644 --- a/calendar/gui/dialogs/event-editor.c +++ b/calendar/gui/dialogs/event-editor.c @@ -32,11 +32,6 @@ #include #include -#include -#include -#include -#include - #include "event-page.h" #include "recurrence-page.h" #include "schedule-page.h" diff --git a/calendar/gui/dialogs/event-page.c b/calendar/gui/dialogs/event-page.c index 12931cee09..793e98e49e 100644 --- a/calendar/gui/dialogs/event-page.c +++ b/calendar/gui/dialogs/event-page.c @@ -35,19 +35,6 @@ #include #include -#include - -#include -#include -#include -#include -#include - -#include -#include -#include -#include - #include "../e-alarm-list.h" #include "../e-meeting-attendee.h" #include "../e-meeting-list-view.h" diff --git a/calendar/gui/dialogs/memo-editor.c b/calendar/gui/dialogs/memo-editor.c index ae0d351618..839bce7bed 100644 --- a/calendar/gui/dialogs/memo-editor.c +++ b/calendar/gui/dialogs/memo-editor.c @@ -31,9 +31,6 @@ #include #include -#include -#include - #include "memo-page.h" #include "cancel-comp.h" #include "memo-editor.h" diff --git a/calendar/gui/dialogs/memo-page.c b/calendar/gui/dialogs/memo-page.c index ec99587f4e..79b659a898 100644 --- a/calendar/gui/dialogs/memo-page.c +++ b/calendar/gui/dialogs/memo-page.c @@ -34,17 +34,6 @@ #include #include -#include - -#include -#include -#include -#include - -#include -#include -#include - #include "../calendar-config.h" #include "comp-editor.h" #include "comp-editor-util.h" diff --git a/calendar/gui/dialogs/recurrence-page.c b/calendar/gui/dialogs/recurrence-page.c index 77862c6584..d5a93ea9db 100644 --- a/calendar/gui/dialogs/recurrence-page.c +++ b/calendar/gui/dialogs/recurrence-page.c @@ -33,17 +33,12 @@ #include #include -#include #include "../tag-calendar.h" #include "../weekday-picker.h" #include "comp-editor-util.h" #include "../e-date-time-list.h" #include "recurrence-page.h" -#include "e-util/e-util.h" -#include "e-util/e-dialog-widgets.h" -#include "e-util/e-util-private.h" - #define RECURRENCE_PAGE_GET_PRIVATE(obj) \ (G_TYPE_INSTANCE_GET_PRIVATE \ ((obj), TYPE_RECURRENCE_PAGE, RecurrencePagePrivate)) diff --git a/calendar/gui/dialogs/save-comp.c b/calendar/gui/dialogs/save-comp.c index 2fd53bd5db..3ae26252b7 100644 --- a/calendar/gui/dialogs/save-comp.c +++ b/calendar/gui/dialogs/save-comp.c @@ -25,7 +25,6 @@ #include #endif -#include "libevolution-utils/e-alert-dialog.h" #include "save-comp.h" #include "comp-editor.h" diff --git a/calendar/gui/dialogs/schedule-page.c b/calendar/gui/dialogs/schedule-page.c index 516a973751..bd3e05e171 100644 --- a/calendar/gui/dialogs/schedule-page.c +++ b/calendar/gui/dialogs/schedule-page.c @@ -31,8 +31,7 @@ #include #include -#include -#include + #include "../e-meeting-time-sel.h" #include "../itip-utils.h" #include "comp-editor-util.h" diff --git a/calendar/gui/dialogs/schedule-page.h b/calendar/gui/dialogs/schedule-page.h index 1c554364fa..9d38de4a23 100644 --- a/calendar/gui/dialogs/schedule-page.h +++ b/calendar/gui/dialogs/schedule-page.h @@ -28,7 +28,6 @@ #include "../e-meeting-store.h" #include "comp-editor.h" #include "comp-editor-page.h" -#include /* Standard GObject macros */ #define TYPE_SCHEDULE_PAGE \ diff --git a/calendar/gui/dialogs/select-source-dialog.c b/calendar/gui/dialogs/select-source-dialog.c index 9f80038e8b..5bdf4a7c5e 100644 --- a/calendar/gui/dialogs/select-source-dialog.c +++ b/calendar/gui/dialogs/select-source-dialog.c @@ -26,7 +26,8 @@ #endif #include -#include + +#include "e-util/e-util.h" #include "select-source-dialog.h" diff --git a/calendar/gui/dialogs/send-comp.c b/calendar/gui/dialogs/send-comp.c index 73938be875..b69e6eaade 100644 --- a/calendar/gui/dialogs/send-comp.c +++ b/calendar/gui/dialogs/send-comp.c @@ -26,10 +26,12 @@ #include #endif -#include -#include "libevolution-utils/e-alert-dialog.h" #include "send-comp.h" +#include + +#include "e-util/e-util.h" + static gboolean component_has_new_attendees (ECalComponent *comp) { diff --git a/calendar/gui/dialogs/task-details-page.c b/calendar/gui/dialogs/task-details-page.c index 2ada645842..e40db5ba86 100644 --- a/calendar/gui/dialogs/task-details-page.c +++ b/calendar/gui/dialogs/task-details-page.c @@ -31,16 +31,11 @@ #include #include -#include -#include + #include "../e-timezone-entry.h" #include "comp-editor-util.h" #include "task-details-page.h" -#include "e-util/e-util.h" -#include "e-util/e-dialog-widgets.h" -#include "e-util/e-util-private.h" - #define TASK_DETAILS_PAGE_GET_PRIVATE(obj) \ (G_TYPE_INSTANCE_GET_PRIVATE \ ((obj), TYPE_TASK_DETAILS_PAGE, TaskDetailsPagePrivate)) diff --git a/calendar/gui/dialogs/task-editor.c b/calendar/gui/dialogs/task-editor.c index 0c81d3ea47..07ad568f9d 100644 --- a/calendar/gui/dialogs/task-editor.c +++ b/calendar/gui/dialogs/task-editor.c @@ -32,9 +32,6 @@ #include #include -#include "e-util/e-plugin-ui.h" -#include "e-util/e-util-private.h" - #include "task-page.h" #include "task-details-page.h" #include "cancel-comp.h" diff --git a/calendar/gui/dialogs/task-page.c b/calendar/gui/dialogs/task-page.c index ebf279cb22..d8d64f32d6 100644 --- a/calendar/gui/dialogs/task-page.c +++ b/calendar/gui/dialogs/task-page.c @@ -34,17 +34,6 @@ #include #include -#include - -#include -#include -#include - -#include -#include -#include -#include - #include "../e-meeting-attendee.h" #include "../e-meeting-list-view.h" #include "../e-meeting-store.h" diff --git a/calendar/gui/e-cal-component-preview.c b/calendar/gui/e-cal-component-preview.c index fe9870e1d8..0647f9bcab 100644 --- a/calendar/gui/e-cal-component-preview.c +++ b/calendar/gui/e-cal-component-preview.c @@ -33,9 +33,6 @@ #include #include -#include -#include - #define E_CAL_COMPONENT_PREVIEW_GET_PRIVATE(obj) \ (G_TYPE_INSTANCE_GET_PRIVATE \ ((obj), E_TYPE_CAL_COMPONENT_PREVIEW, ECalComponentPreviewPrivate)) diff --git a/calendar/gui/e-cal-component-preview.h b/calendar/gui/e-cal-component-preview.h index 5048e3ab07..54909139ec 100644 --- a/calendar/gui/e-cal-component-preview.h +++ b/calendar/gui/e-cal-component-preview.h @@ -26,7 +26,8 @@ #include #include -#include + +#include /* Standard GObject macros */ #define E_TYPE_CAL_COMPONENT_PREVIEW \ diff --git a/calendar/gui/e-cal-config.h b/calendar/gui/e-cal-config.h index 5cadd428a1..e6db09ad6e 100644 --- a/calendar/gui/e-cal-config.h +++ b/calendar/gui/e-cal-config.h @@ -25,8 +25,7 @@ #define E_CAL_CONFIG_H #include - -#include "e-util/e-config.h" +#include /* Standard GObject macros */ #define E_TYPE_CAL_CONFIG \ diff --git a/calendar/gui/e-cal-event.h b/calendar/gui/e-cal-event.h index 447f1fd418..f44878d01f 100644 --- a/calendar/gui/e-cal-event.h +++ b/calendar/gui/e-cal-event.h @@ -24,8 +24,7 @@ #ifndef __E_CAL_EVENT_H__ #define __E_CAL_EVENT_H__ -#include "e-util/e-event.h" -#include "shell/e-shell-backend.h" +#include G_BEGIN_DECLS diff --git a/calendar/gui/e-cal-list-view.c b/calendar/gui/e-cal-list-view.c index 55280ce79c..94722c0319 100644 --- a/calendar/gui/e-cal-list-view.c +++ b/calendar/gui/e-cal-list-view.c @@ -34,16 +34,6 @@ #include #include #include -#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include -#include -#include #include "e-cal-model-calendar.h" #include "e-cell-date-edit-text.h" diff --git a/calendar/gui/e-cal-list-view.h b/calendar/gui/e-cal-list-view.h index ed4636f753..e5eff477a2 100644 --- a/calendar/gui/e-cal-list-view.h +++ b/calendar/gui/e-cal-list-view.h @@ -27,8 +27,7 @@ #include #include -#include
-#include
+#include #include "e-calendar-view.h" #include "gnome-cal.h" diff --git a/calendar/gui/e-cal-model.h b/calendar/gui/e-cal-model.h index 7f19e00c16..9164d9c06e 100644 --- a/calendar/gui/e-cal-model.h +++ b/calendar/gui/e-cal-model.h @@ -28,8 +28,8 @@ #include -#include -#include
+#include + #include "e-cell-date-edit-text.h" /* Standard GObject macros */ diff --git a/calendar/gui/e-calendar-selector.c b/calendar/gui/e-calendar-selector.c index 6c403e43a2..19eb4335b1 100644 --- a/calendar/gui/e-calendar-selector.c +++ b/calendar/gui/e-calendar-selector.c @@ -24,8 +24,6 @@ #include -#include "e-util/e-selection.h" - #define E_CALENDAR_SELECTOR_GET_PRIVATE(obj) \ (G_TYPE_INSTANCE_GET_PRIVATE \ ((obj), E_TYPE_CALENDAR_SELECTOR, ECalendarSelectorPrivate)) diff --git a/calendar/gui/e-calendar-selector.h b/calendar/gui/e-calendar-selector.h index 1e7128bbda..50832ead2b 100644 --- a/calendar/gui/e-calendar-selector.h +++ b/calendar/gui/e-calendar-selector.h @@ -21,7 +21,7 @@ #ifndef E_CALENDAR_SELECTOR_H #define E_CALENDAR_SELECTOR_H -#include +#include /* Standard GObject macros */ #define E_TYPE_CALENDAR_SELECTOR \ diff --git a/calendar/gui/e-calendar-view.c b/calendar/gui/e-calendar-view.c index 2801391952..d5ce9f362a 100644 --- a/calendar/gui/e-calendar-view.c +++ b/calendar/gui/e-calendar-view.c @@ -31,15 +31,7 @@ #include #include #include -#include - -#include -#include -#include -#include -#include -#include -#include + #include #include "comp-util.h" diff --git a/calendar/gui/e-cell-date-edit-text.c b/calendar/gui/e-cell-date-edit-text.c index 82d94eff11..37e648ed76 100644 --- a/calendar/gui/e-cell-date-edit-text.c +++ b/calendar/gui/e-cell-date-edit-text.c @@ -32,9 +32,6 @@ #include #include -#include -#include - #include "e-cell-date-edit-text.h" #define E_CELL_DATE_EDIT_TEXT_GET_PRIVATE(obj) \ diff --git a/calendar/gui/e-cell-date-edit-text.h b/calendar/gui/e-cell-date-edit-text.h index aab7e27edf..b620143617 100644 --- a/calendar/gui/e-cell-date-edit-text.h +++ b/calendar/gui/e-cell-date-edit-text.h @@ -25,7 +25,7 @@ #define _E_CELL_DATE_EDIT_TEXT_H_ #include -#include
+#include /* Standard GObject macros */ #define E_TYPE_CELL_DATE_EDIT_TEXT \ diff --git a/calendar/gui/e-day-view-layout.c b/calendar/gui/e-day-view-layout.c index 573a3b3ccd..c4bf94a512 100644 --- a/calendar/gui/e-day-view-layout.c +++ b/calendar/gui/e-day-view-layout.c @@ -31,7 +31,6 @@ #endif #include "e-day-view-layout.h" -#include "e-util/e-bit-array.h" static void e_day_view_layout_long_event (EDayViewEvent *event, guint8 *grid, diff --git a/calendar/gui/e-day-view-main-item.c b/calendar/gui/e-day-view-main-item.c index 61e238eaa4..1492cdebd5 100644 --- a/calendar/gui/e-day-view-main-item.c +++ b/calendar/gui/e-day-view-main-item.c @@ -29,15 +29,13 @@ #include #endif -#include +#include "e-day-view-main-item.h" -#include "e-util/e-categories-config.h" -#include "e-util/e-util.h" +#include "comp-util.h" +#include "e-calendar-view.h" +#include "e-calendar-view.h" #include "e-day-view-layout.h" -#include "e-day-view-main-item.h" #include "ea-calendar.h" -#include "e-calendar-view.h" -#include "comp-util.h" #define E_DAY_VIEW_MAIN_ITEM_GET_PRIVATE(obj) \ (G_TYPE_INSTANCE_GET_PRIVATE \ diff --git a/calendar/gui/e-day-view-time-item.c b/calendar/gui/e-day-view-time-item.c index f337aa06e1..c6d36d36df 100644 --- a/calendar/gui/e-day-view-time-item.c +++ b/calendar/gui/e-day-view-time-item.c @@ -31,7 +31,6 @@ #include "e-day-view-time-item.h" #include "calendar-config.h" -#include /* The spacing between items in the time column. GRID_X_PAD is the space down * either side of the column, i.e. outside the main horizontal grid lines. diff --git a/calendar/gui/e-day-view-top-item.c b/calendar/gui/e-day-view-top-item.c index 8d961ea6a5..ed5d8b0266 100644 --- a/calendar/gui/e-day-view-top-item.c +++ b/calendar/gui/e-day-view-top-item.c @@ -29,7 +29,6 @@ #endif #include -#include "e-util/e-categories-config.h" #include "e-calendar-view.h" #include "e-day-view-top-item.h" diff --git a/calendar/gui/e-day-view.c b/calendar/gui/e-day-view.c index db4557cbff..30eed74413 100644 --- a/calendar/gui/e-day-view.c +++ b/calendar/gui/e-day-view.c @@ -27,19 +27,14 @@ #endif #include "e-day-view.h" -#include "ea-calendar.h" #include #include -#include -#include -#include -#include -#include + #include -#include -#include -#include +#include + +#include "libgnomecanvas/libgnomecanvas.h" #include "dialogs/delete-comp.h" #include "dialogs/delete-error.h" @@ -47,17 +42,18 @@ #include "dialogs/cancel-comp.h" #include "dialogs/recur-comp.h" #include "dialogs/goto-dialog.h" -#include "print.h" + #include "calendar-config.h" #include "comp-util.h" -#include "itip-utils.h" #include "e-cal-model-calendar.h" -#include "e-day-view-time-item.h" -#include "e-day-view-top-item.h" #include "e-day-view-layout.h" #include "e-day-view-main-item.h" +#include "e-day-view-time-item.h" +#include "e-day-view-top-item.h" +#include "ea-calendar.h" +#include "itip-utils.h" #include "misc.h" -#include +#include "print.h" /* The minimum amount of space wanted on each side of the date string. */ #define E_DAY_VIEW_DATE_X_PAD 4 diff --git a/calendar/gui/e-meeting-list-view.c b/calendar/gui/e-meeting-list-view.c index 59ffa099e0..50999c767b 100644 --- a/calendar/gui/e-meeting-list-view.c +++ b/calendar/gui/e-meeting-list-view.c @@ -30,7 +30,6 @@ #include #include #include -#include #include "calendar-config.h" #include "e-meeting-list-view.h" diff --git a/calendar/gui/e-meeting-list-view.h b/calendar/gui/e-meeting-list-view.h index d62cb05636..aff278c3bf 100644 --- a/calendar/gui/e-meeting-list-view.h +++ b/calendar/gui/e-meeting-list-view.h @@ -25,7 +25,6 @@ #define _E_MEETING_LIST_VIEW_H_ #include -#include #include "e-meeting-store.h" diff --git a/calendar/gui/e-meeting-store.c b/calendar/gui/e-meeting-store.c index b8e065e2dd..4e8e4e32d1 100644 --- a/calendar/gui/e-meeting-store.c +++ b/calendar/gui/e-meeting-store.c @@ -31,7 +31,6 @@ #include #include -#include #include #include diff --git a/calendar/gui/e-meeting-store.h b/calendar/gui/e-meeting-store.h index f762cbc7a5..27c1f9ac78 100644 --- a/calendar/gui/e-meeting-store.h +++ b/calendar/gui/e-meeting-store.h @@ -26,7 +26,8 @@ #include #include -#include +#include + #include "e-meeting-attendee.h" /* Standard GObject macros */ diff --git a/calendar/gui/e-meeting-time-sel-item.c b/calendar/gui/e-meeting-time-sel-item.c index a15f25a6ef..aaa86230cc 100644 --- a/calendar/gui/e-meeting-time-sel-item.c +++ b/calendar/gui/e-meeting-time-sel-item.c @@ -30,8 +30,6 @@ #include #include -#include "e-util/e-datetime-format.h" - #include "calendar-config.h" #include "e-meeting-time-sel-item.h" #include "e-meeting-time-sel.h" diff --git a/calendar/gui/e-meeting-time-sel.c b/calendar/gui/e-meeting-time-sel.c index f3f8b3a846..18944065e2 100644 --- a/calendar/gui/e-meeting-time-sel.c +++ b/calendar/gui/e-meeting-time-sel.c @@ -36,13 +36,6 @@ #include #include -#include "misc/e-canvas.h" -#include "misc/e-canvas-utils.h" -#include "misc/e-dateedit.h" - -#include "e-util/e-util.h" -#include "e-util/e-datetime-format.h" - #include "e-meeting-utils.h" #include "e-meeting-list-view.h" #include "e-meeting-time-sel-item.h" diff --git a/calendar/gui/e-meeting-time-sel.h b/calendar/gui/e-meeting-time-sel.h index 7c3f7c3021..9f4fb0ac31 100644 --- a/calendar/gui/e-meeting-time-sel.h +++ b/calendar/gui/e-meeting-time-sel.h @@ -25,9 +25,9 @@ #include #include -#include -#include
-#include
+ +#include + #include "e-meeting-store.h" #include "e-meeting-list-view.h" diff --git a/calendar/gui/e-memo-list-selector.c b/calendar/gui/e-memo-list-selector.c index 8da45b68f3..366d41e702 100644 --- a/calendar/gui/e-memo-list-selector.c +++ b/calendar/gui/e-memo-list-selector.c @@ -25,7 +25,6 @@ #include #include -#include "e-util/e-selection.h" #include "calendar/gui/comp-util.h" #define E_MEMO_LIST_SELECTOR_GET_PRIVATE(obj) \ diff --git a/calendar/gui/e-memo-list-selector.h b/calendar/gui/e-memo-list-selector.h index f131d661cc..532fd312c6 100644 --- a/calendar/gui/e-memo-list-selector.h +++ b/calendar/gui/e-memo-list-selector.h @@ -26,7 +26,7 @@ #ifndef E_MEMO_LIST_SELECTOR_H #define E_MEMO_LIST_SELECTOR_H -#include +#include /* Standard GObject macros */ #define E_TYPE_MEMO_LIST_SELECTOR \ diff --git a/calendar/gui/e-memo-table.c b/calendar/gui/e-memo-table.c index 45d51b9060..7d60f84877 100644 --- a/calendar/gui/e-memo-table.c +++ b/calendar/gui/e-memo-table.c @@ -30,32 +30,20 @@ #include #endif +#include "e-memo-table.h" + #include #include #include #include -#include -#include
-#include
-#include
-#include
-#include
-#include -#include -#include -#include
-#include
#include "dialogs/delete-comp.h" #include "dialogs/delete-error.h" #include "dialogs/memo-editor.h" #include "e-cal-model-memos.h" -#include "e-memo-table.h" #include "e-calendar-view.h" #include "e-cell-date-edit-text.h" #include "print.h" -#include -#include #include "misc.h" #define E_MEMO_TABLE_GET_PRIVATE(obj) \ diff --git a/calendar/gui/e-memo-table.h b/calendar/gui/e-memo-table.h index 4f52ad35e4..f003aa6fff 100644 --- a/calendar/gui/e-memo-table.h +++ b/calendar/gui/e-memo-table.h @@ -25,9 +25,8 @@ #ifndef E_MEMO_TABLE_H #define E_MEMO_TABLE_H -#include
-#include
#include + #include "e-cal-model.h" /* diff --git a/calendar/gui/e-select-names-editable.h b/calendar/gui/e-select-names-editable.h index f168902db9..84732f7e72 100644 --- a/calendar/gui/e-select-names-editable.h +++ b/calendar/gui/e-select-names-editable.h @@ -24,8 +24,6 @@ #ifndef __E_SELECT_NAMES_EDITABLE_H__ #define __E_SELECT_NAMES_EDITABLE_H__ -#include - G_BEGIN_DECLS #define E_TYPE_SELECT_NAMES_EDITABLE (e_select_names_editable_get_type ()) diff --git a/calendar/gui/e-task-list-selector.c b/calendar/gui/e-task-list-selector.c index 1b8bbbae48..0989e7436c 100644 --- a/calendar/gui/e-task-list-selector.c +++ b/calendar/gui/e-task-list-selector.c @@ -25,7 +25,6 @@ #include #include -#include "e-util/e-selection.h" #include "calendar/gui/comp-util.h" #define E_TASK_LIST_SELECTOR_GET_PRIVATE(obj) \ diff --git a/calendar/gui/e-task-list-selector.h b/calendar/gui/e-task-list-selector.h index fd133d1c10..df79d5f0ab 100644 --- a/calendar/gui/e-task-list-selector.h +++ b/calendar/gui/e-task-list-selector.h @@ -26,7 +26,7 @@ #ifndef E_TASK_LIST_SELECTOR_H #define E_TASK_LIST_SELECTOR_H -#include +#include /* Standard GObject macros */ #define E_TYPE_TASK_LIST_SELECTOR \ diff --git a/calendar/gui/e-task-table.c b/calendar/gui/e-task-table.c index ed73cbe6ef..cda4896292 100644 --- a/calendar/gui/e-task-table.c +++ b/calendar/gui/e-task-table.c @@ -29,36 +29,23 @@ #include #endif +#include "e-task-table.h" + #include #include #include #include #include #include -#include -#include
-#include
-#include
-#include
-#include
-#include -#include -#include -#include
-#include
-#include
#include "calendar-config.h" #include "dialogs/delete-comp.h" #include "dialogs/delete-error.h" #include "dialogs/task-editor.h" #include "e-cal-model-tasks.h" -#include "e-task-table.h" #include "e-calendar-view.h" #include "e-cell-date-edit-text.h" #include "print.h" -#include -#include #include "misc.h" #define E_TASK_TABLE_GET_PRIVATE(obj) \ diff --git a/calendar/gui/e-task-table.h b/calendar/gui/e-task-table.h index cf215fd449..617679c76b 100644 --- a/calendar/gui/e-task-table.h +++ b/calendar/gui/e-task-table.h @@ -23,9 +23,8 @@ #ifndef E_TASK_TABLE_H #define E_TASK_TABLE_H -#include
-#include
#include + #include "e-cal-model.h" /* diff --git a/calendar/gui/e-timezone-entry.c b/calendar/gui/e-timezone-entry.c index 41b40dcf3b..9c9aeefacd 100644 --- a/calendar/gui/e-timezone-entry.c +++ b/calendar/gui/e-timezone-entry.c @@ -32,10 +32,12 @@ #include #endif -#include -#include #include "e-timezone-entry.h" +#include + +#include "e-util/e-util.h" + #define E_TIMEZONE_ENTRY_GET_PRIVATE(obj) \ (G_TYPE_INSTANCE_GET_PRIVATE \ ((obj), E_TYPE_TIMEZONE_ENTRY, ETimezoneEntryPrivate)) diff --git a/calendar/gui/e-week-view-event-item.c b/calendar/gui/e-week-view-event-item.c index 1aa507deb4..298319cdea 100644 --- a/calendar/gui/e-week-view-event-item.c +++ b/calendar/gui/e-week-view-event-item.c @@ -30,15 +30,13 @@ #include #endif -#include "e-util/e-categories-config.h" #include "e-week-view-event-item.h" #include + #include "e-calendar-view.h" #include "comp-util.h" -#include - #define E_WEEK_VIEW_EVENT_ITEM_GET_PRIVATE(obj) \ (G_TYPE_INSTANCE_GET_PRIVATE \ ((obj), E_TYPE_WEEK_VIEW_EVENT_ITEM, EWeekViewEventItemPrivate)) diff --git a/calendar/gui/e-week-view.c b/calendar/gui/e-week-view.c index 9a3e92de24..2e00d3b078 100644 --- a/calendar/gui/e-week-view.c +++ b/calendar/gui/e-week-view.c @@ -30,36 +30,31 @@ #endif #include "e-week-view.h" -#include "ea-calendar.h" #include #include #include #include -#include -#include -#include -#include -#include -#include + #include "dialogs/delete-comp.h" #include "dialogs/delete-error.h" #include "dialogs/send-comp.h" #include "dialogs/cancel-comp.h" #include "dialogs/recur-comp.h" #include "dialogs/goto-dialog.h" + #include "calendar-config.h" -#include "comp-util.h" -#include "itip-utils.h" #include "calendar-config.h" -#include "print.h" +#include "comp-util.h" #include "e-cal-model-calendar.h" #include "e-week-view-event-item.h" #include "e-week-view-layout.h" #include "e-week-view-main-item.h" #include "e-week-view-titles-item.h" +#include "ea-calendar.h" +#include "itip-utils.h" #include "misc.h" -#include +#include "print.h" /* Images */ #include "art/jump.xpm" diff --git a/calendar/gui/ea-cal-view-event.c b/calendar/gui/ea-cal-view-event.c index 35c3819062..fe1f731bfe 100644 --- a/calendar/gui/ea-cal-view-event.c +++ b/calendar/gui/ea-cal-view-event.c @@ -24,12 +24,12 @@ #include #endif +#include + #include "ea-cal-view-event.h" #include "ea-calendar-helpers.h" #include "ea-day-view.h" #include "ea-week-view.h" -#include -#include static void ea_cal_view_event_class_init (EaCalViewEventClass *klass); static void ea_cal_view_event_init (EaCalViewEvent *a11y); diff --git a/calendar/gui/ea-calendar-helpers.c b/calendar/gui/ea-calendar-helpers.c index e0234d0a08..8d907f5bbf 100644 --- a/calendar/gui/ea-calendar-helpers.c +++ b/calendar/gui/ea-calendar-helpers.c @@ -31,7 +31,6 @@ #include "e-day-view.h" #include "e-week-view.h" -#include #include /** diff --git a/calendar/gui/ea-calendar.c b/calendar/gui/ea-calendar.c index 2aa7daac88..4fe44ae7b7 100644 --- a/calendar/gui/ea-calendar.c +++ b/calendar/gui/ea-calendar.c @@ -24,10 +24,9 @@ #include #endif -#include #include + #include "ea-calendar-helpers.h" -#include "a11y/ea-factory.h" #include "ea-calendar.h" #include "calendar/gui/ea-cal-view.h" diff --git a/calendar/gui/ea-day-view-cell.c b/calendar/gui/ea-day-view-cell.c index 5e25a2aae1..c72b21ea0b 100644 --- a/calendar/gui/ea-day-view-cell.c +++ b/calendar/gui/ea-day-view-cell.c @@ -28,7 +28,6 @@ #include "ea-day-view-cell.h" #include "ea-day-view-main-item.h" #include "ea-day-view.h" -#include "a11y/ea-factory.h" /* EDayViewCell */ diff --git a/calendar/gui/ea-day-view-main-item.c b/calendar/gui/ea-day-view-main-item.c index 3367833a4f..e8f81ff67f 100644 --- a/calendar/gui/ea-day-view-main-item.c +++ b/calendar/gui/ea-day-view-main-item.c @@ -25,12 +25,12 @@ #include #endif +#include + #include "ea-day-view-main-item.h" #include "e-day-view-top-item.h" #include "ea-day-view.h" #include "ea-day-view-cell.h" -#include "ea-cell-table.h" -#include /* EaDayViewMainItem */ static void ea_day_view_main_item_class_init (EaDayViewMainItemClass *klass); diff --git a/calendar/gui/ea-week-view-cell.c b/calendar/gui/ea-week-view-cell.c index 2ceb8e17bc..d888b47774 100644 --- a/calendar/gui/ea-week-view-cell.c +++ b/calendar/gui/ea-week-view-cell.c @@ -28,7 +28,6 @@ #include "ea-week-view-cell.h" #include "ea-week-view-main-item.h" -#include "a11y/ea-factory.h" /* EWeekViewCell */ diff --git a/calendar/gui/ea-week-view-main-item.c b/calendar/gui/ea-week-view-main-item.c index d4597d8fdd..816660cb3f 100644 --- a/calendar/gui/ea-week-view-main-item.c +++ b/calendar/gui/ea-week-view-main-item.c @@ -26,10 +26,11 @@ #endif #include "ea-week-view-main-item.h" + +#include + #include "ea-week-view.h" #include "ea-week-view-cell.h" -#include "ea-cell-table.h" -#include /* EaWeekViewMainItem */ static void ea_week_view_main_item_class_init diff --git a/calendar/gui/ea-week-view.c b/calendar/gui/ea-week-view.c index 26cdcf2eed..24d6e8de83 100644 --- a/calendar/gui/ea-week-view.c +++ b/calendar/gui/ea-week-view.c @@ -25,11 +25,12 @@ #endif #include "ea-week-view.h" + +#include + #include "ea-cal-view-event.h" #include "ea-calendar-helpers.h" #include "ea-gnome-calendar.h" -#include -#include static void ea_week_view_class_init (EaWeekViewClass *klass); diff --git a/calendar/gui/gnome-cal.c b/calendar/gui/gnome-cal.c index 0ea1008701..8b9b2b3c17 100644 --- a/calendar/gui/gnome-cal.c +++ b/calendar/gui/gnome-cal.c @@ -29,38 +29,36 @@ #include #endif +#include "gnome-cal.h" + #include #include #include #include -#include + #include +#include -#include -#include -#include -#include "e-util/e-util.h" -#include "libevolution-utils/e-alert-dialog.h" -#include "e-util/e-util-private.h" #include "shell/e-shell.h" + #include "dialogs/delete-error.h" #include "dialogs/event-editor.h" + +#include "calendar-config.h" +#include "calendar-view-factory.h" +#include "calendar-view.h" #include "comp-util.h" +#include "e-cal-list-view.h" #include "e-cal-model-calendar.h" -#include "e-day-view.h" #include "e-day-view-time-item.h" +#include "e-day-view.h" +#include "e-memo-table.h" #include "e-month-view.h" +#include "e-task-table.h" #include "e-week-view.h" -#include "e-cal-list-view.h" -#include "gnome-cal.h" -#include "calendar-config.h" -#include "calendar-view.h" -#include "calendar-view-factory.h" -#include "tag-calendar.h" -#include "misc.h" #include "ea-calendar.h" -#include "e-memo-table.h" -#include "e-task-table.h" +#include "misc.h" +#include "tag-calendar.h" #define d(x) diff --git a/calendar/gui/gnome-cal.h b/calendar/gui/gnome-cal.h index c2f491b1bd..dcb801754f 100644 --- a/calendar/gui/gnome-cal.h +++ b/calendar/gui/gnome-cal.h @@ -31,7 +31,7 @@ #include #include -#include +#include #include "e-cal-model.h" diff --git a/calendar/gui/itip-utils.c b/calendar/gui/itip-utils.c index 1af60b8192..f758574c59 100644 --- a/calendar/gui/itip-utils.c +++ b/calendar/gui/itip-utils.c @@ -29,8 +29,6 @@ #include #include -#include - #include #include "itip-utils.h" diff --git a/calendar/gui/print.c b/calendar/gui/print.c index b4e6ff2133..c9e48a520c 100644 --- a/calendar/gui/print.c +++ b/calendar/gui/print.c @@ -28,16 +28,17 @@ #include #endif +#include "print.h" + #include #include #include #include #include + #include #include -#include -#include #include "e-cal-model.h" #include "e-day-view.h" #include "e-day-view-layout.h" @@ -45,7 +46,6 @@ #include "e-week-view-layout.h" #include "e-task-table.h" #include "gnome-cal.h" -#include "print.h" #include "art/jump.xpm" diff --git a/calendar/gui/print.h b/calendar/gui/print.h index b81bcfa36c..1b7d31194b 100644 --- a/calendar/gui/print.h +++ b/calendar/gui/print.h @@ -25,7 +25,8 @@ #ifndef PRINT_H #define PRINT_H -#include
+#include + #include "calendar/gui/gnome-cal.h" typedef enum { diff --git a/calendar/gui/tag-calendar.h b/calendar/gui/tag-calendar.h index aa3eb2a8a4..b9f406e61f 100644 --- a/calendar/gui/tag-calendar.h +++ b/calendar/gui/tag-calendar.h @@ -28,7 +28,7 @@ #define TAG_CALENDAR_H #include -#include +#include void tag_calendar_by_client (ECalendar *ecal, ECalClient *client, GCancellable *cancellable); void tag_calendar_by_comp (ECalendar *ecal, ECalComponent *comp, diff --git a/calendar/importers/Makefile.am b/calendar/importers/Makefile.am index 7480358f27..f9e984634d 100644 --- a/calendar/importers/Makefile.am +++ b/calendar/importers/Makefile.am @@ -6,10 +6,10 @@ libevolution_calendar_importers_la_CPPFLAGS = \ -DG_LOG_DOMAIN=\"Evolution-Importer\" \ -I$(top_srcdir) \ -I$(top_srcdir)/calendar \ - -I$(top_srcdir)/widgets \ -I$(top_builddir)/calendar \ $(EVOLUTION_DATA_SERVER_CFLAGS) \ $(GNOME_PLATFORM_CFLAGS) \ + $(CHAMPLAIN_CFLAGS) \ $(GTKHTML_CFLAGS) libevolution_calendar_importers_la_SOURCES = \ @@ -21,9 +21,9 @@ libevolution_calendar_importers_la_LDFLAGS = -avoid-version $(NO_UNDEFINED) libevolution_calendar_importers_la_LIBADD = \ $(top_builddir)/e-util/libeutil.la \ $(top_builddir)/shell/libeshell.la \ - $(top_builddir)/widgets/misc/libemiscwidgets.la \ $(EVOLUTION_DATA_SERVER_LIBS) \ $(GNOME_PLATFORM_LIBS) \ + $(CHAMPLAIN_LIBS) \ $(GTKHTML_LIBS) -include $(top_srcdir)/git.mk diff --git a/calendar/importers/icalendar-importer.c b/calendar/importers/icalendar-importer.c index 89e5f0ccfc..d54a8876c5 100644 --- a/calendar/importers/icalendar-importer.c +++ b/calendar/importers/icalendar-importer.c @@ -36,17 +36,12 @@ #include #include -#include #include -#include "evolution-calendar-importer.h" #include "shell/e-shell.h" -#include "gui/calendar-config-keys.h" -#include "e-util/e-import.h" -#include "e-util/e-util-private.h" -#include "e-util/e-datetime-format.h" -#include "misc/e-web-view-preview.h" +#include "evolution-calendar-importer.h" +#include "gui/calendar-config-keys.h" /* We timeout after 2 minutes, when opening the folders. */ #define IMPORTER_TIMEOUT_SECONDS 120 diff --git a/composer/Makefile.am b/composer/Makefile.am index 59b316097d..d5df64095f 100644 --- a/composer/Makefile.am +++ b/composer/Makefile.am @@ -27,10 +27,6 @@ libcomposer_la_CPPFLAGS = \ -I$(top_srcdir) \ -I$(top_builddir) \ -I$(top_builddir)/composer \ - -I$(top_srcdir)/widgets \ - -I$(top_builddir)/widgets \ - -I$(top_srcdir)/widgets/misc \ - -I$(top_builddir)/widgets/misc \ -I$(top_builddir)/shell \ -I$(top_srcdir)/shell \ -DEVOLUTION_DATADIR=\"$(datadir)\" \ @@ -40,6 +36,7 @@ libcomposer_la_CPPFLAGS = \ -DG_LOG_DOMAIN=\"composer\" \ $(EVOLUTION_DATA_SERVER_CFLAGS) \ $(GNOME_PLATFORM_CFLAGS) \ + $(CHAMPLAIN_CFLAGS) \ $(GTKHTML_CFLAGS) libcomposer_la_SOURCES = \ @@ -60,15 +57,13 @@ libcomposer_la_LDFLAGS = -avoid-version $(NO_UNDEFINED) libcomposer_la_LIBADD = \ $(top_builddir)/e-util/libeutil.la \ - $(top_builddir)/widgets/misc/libemiscwidgets.la \ $(top_builddir)/shell/libeshell.la \ $(top_builddir)/em-format/libemformat.la \ $(top_builddir)/addressbook/gui/contact-editor/libecontacteditor.la \ $(top_builddir)/addressbook/gui/contact-list-editor/libecontactlisteditor.la \ - $(top_builddir)/libemail-utils/libemail-utils.la \ - $(top_builddir)/libevolution-utils/libevolution-utils.la \ $(EVOLUTION_DATA_SERVER_LIBS) \ $(GNOME_PLATFORM_LIBS) \ + $(CHAMPLAIN_CFLAGS) \ $(GTKHTML_LIBS) ui_DATA = evolution-composer.ui diff --git a/composer/e-composer-actions.c b/composer/e-composer-actions.c index 966866b830..1317775364 100644 --- a/composer/e-composer-actions.c +++ b/composer/e-composer-actions.c @@ -25,7 +25,6 @@ #include #include -#include static void action_attach_cb (GtkAction *action, diff --git a/composer/e-composer-activity.h b/composer/e-composer-activity.h index 431e390062..a437e06e03 100644 --- a/composer/e-composer-activity.h +++ b/composer/e-composer-activity.h @@ -19,7 +19,6 @@ #ifndef E_COMPOSER_ACTIVITY_H #define E_COMPOSER_ACTIVITY_H -#include #include /* Standard GObject macros */ diff --git a/composer/e-composer-common.h b/composer/e-composer-common.h index 661797eee6..b58a138373 100644 --- a/composer/e-composer-common.h +++ b/composer/e-composer-common.h @@ -22,5 +22,6 @@ #define E_COMPOSER_COMMON #include +#include #endif /* E_COMPOSER_COMMON */ diff --git a/composer/e-composer-from-header.c b/composer/e-composer-from-header.c index ceecd68b87..d9afb5985a 100644 --- a/composer/e-composer-from-header.c +++ b/composer/e-composer-from-header.c @@ -24,8 +24,6 @@ #include "e-composer-from-header.h" -#include - G_DEFINE_TYPE ( EComposerFromHeader, e_composer_from_header, diff --git a/composer/e-composer-header-table.c b/composer/e-composer-header-table.c index 72c8dacaa8..2760ba50ee 100644 --- a/composer/e-composer-header-table.c +++ b/composer/e-composer-header-table.c @@ -22,10 +22,8 @@ #include "e-composer-header-table.h" #include -#include #include -#include #include "e-msg-composer.h" #include "e-composer-private.h" diff --git a/composer/e-composer-header-table.h b/composer/e-composer-header-table.h index 3e7e16c527..f459aaeae8 100644 --- a/composer/e-composer-header-table.h +++ b/composer/e-composer-header-table.h @@ -22,7 +22,6 @@ #include #include -#include /* Standard GObject macros */ #define E_TYPE_COMPOSER_HEADER_TABLE \ diff --git a/composer/e-composer-name-header.h b/composer/e-composer-name-header.h index b745941a3e..5632ddf0c0 100644 --- a/composer/e-composer-name-header.h +++ b/composer/e-composer-name-header.h @@ -19,7 +19,6 @@ #define E_COMPOSER_NAME_HEADER_H #include -#include #include diff --git a/composer/e-composer-private.h b/composer/e-composer-private.h index 4370b1a12b..fc358b9320 100644 --- a/composer/e-composer-private.h +++ b/composer/e-composer-private.h @@ -35,24 +35,6 @@ #include "e-composer-actions.h" #include "e-composer-activity.h" #include "e-composer-header-table.h" -#include "libevolution-utils/e-alert-sink.h" -#include "e-util/e-charset.h" -#include "e-util/e-marshal.h" -#include "e-util/e-mktemp.h" -#include "e-util/e-plugin-ui.h" -#include "e-util/e-selection.h" -#include "e-util/e-util.h" -#include "widgets/misc/e-activity-bar.h" -#include "widgets/misc/e-alert-bar.h" -#include "widgets/misc/e-attachment.h" -#include "widgets/misc/e-attachment-icon-view.h" -#include "widgets/misc/e-attachment-paned.h" -#include "widgets/misc/e-attachment-store.h" -#include "widgets/misc/e-mail-signature-combo-box.h" -#include "widgets/misc/e-picture-gallery.h" -#include "widgets/misc/e-preferences-window.h" -#include "widgets/misc/e-web-view-gtkhtml.h" -#include "shell/e-shell.h" #ifdef HAVE_XFREE #include diff --git a/composer/e-composer-spell-header.c b/composer/e-composer-spell-header.c index ad4ddca6dd..a3c945ded4 100644 --- a/composer/e-composer-spell-header.c +++ b/composer/e-composer-spell-header.c @@ -19,8 +19,6 @@ #include #endif -#include - #include "e-composer-spell-header.h" G_DEFINE_TYPE ( diff --git a/composer/e-msg-composer.c b/composer/e-msg-composer.c index 5cd9a70f75..dcfd3852e6 100644 --- a/composer/e-msg-composer.c +++ b/composer/e-msg-composer.c @@ -37,10 +37,6 @@ #include #include -#include -#include -#include - #include "e-composer-private.h" #include diff --git a/composer/e-msg-composer.h b/composer/e-msg-composer.h index a85993fd87..941845af70 100644 --- a/composer/e-msg-composer.h +++ b/composer/e-msg-composer.h @@ -28,9 +28,6 @@ #include #include -#include -#include -#include #include #include diff --git a/configure.ac b/configure.ac index 7f5f844756..d2d5d5bf10 100644 --- a/configure.ac +++ b/configure.ac @@ -303,7 +303,6 @@ PKG_CHECK_MODULES([EVOLUTION_DATA_SERVER], libebook-1.2 >= eds_minimum_version libecal-1.2 >= eds_minimum_version libedataserver-1.2 >= eds_minimum_version - libedataserverui-3.0 >= eds_minimum_version libebackend-1.2 >= eds_minimum_version]) AC_SUBST(EVOLUTION_DATA_SERVER_CFLAGS) AC_SUBST(EVOLUTION_DATA_SERVER_LIBS) @@ -1565,7 +1564,6 @@ AC_SUBST(EVOLUTION_DIR) AC_CONFIG_FILES([ po/Makefile.in Makefile -a11y/Makefile addressbook/Makefile addressbook/gui/Makefile addressbook/gui/contact-editor/Makefile @@ -1585,10 +1583,10 @@ data/evolution-settings.desktop.in data/icons/Makefile doc/Makefile doc/reference/Makefile -doc/reference/shell/Makefile +doc/reference/libeshell/Makefile +doc/reference/libeutil/Makefile e-util/Makefile em-format/Makefile -filter/Makefile help/Makefile help/quickref/Makefile help/quickref/C/Makefile @@ -1604,10 +1602,6 @@ help/quickref/pl/Makefile help/quickref/pt/Makefile help/quickref/sv/Makefile help/quickref/sq/Makefile -libevolution-utils/Makefile -libevolution-utils/libevolution-utils.pc -libemail-utils/Makefile -libemail-utils/libemail-utils.pc libemail-engine/Makefile libemail-engine/libemail-engine.pc libgnomecanvas/Makefile @@ -1620,12 +1614,6 @@ views/calendar/Makefile views/mail/Makefile views/tasks/Makefile views/memos/Makefile -widgets/Makefile -widgets/e-timezone-dialog/Makefile -widgets/menus/Makefile -widgets/misc/Makefile -widgets/text/Makefile -widgets/table/Makefile calendar/Makefile calendar/alarm-notify/Makefile calendar/importers/Makefile diff --git a/doc/reference/Makefile.am b/doc/reference/Makefile.am index 48d38adb0a..806e1b0d83 100644 --- a/doc/reference/Makefile.am +++ b/doc/reference/Makefile.am @@ -1,3 +1,3 @@ -SUBDIRS = shell +SUBDIRS = libeutil libeshell -include $(top_srcdir)/git.mk diff --git a/doc/reference/libeshell/Makefile.am b/doc/reference/libeshell/Makefile.am new file mode 100644 index 0000000000..7262075b74 --- /dev/null +++ b/doc/reference/libeshell/Makefile.am @@ -0,0 +1,80 @@ +# The name of the module, e.g. 'glib'. +DOC_MODULE=libeshell + +# The top-level SGML file. You can change this if you want to. +DOC_MAIN_SGML_FILE=$(DOC_MODULE)-docs.sgml + +# The directory containing the source code. Relative to $(srcdir). +# gtk-doc will search all .c & .h files beneath here for inline comments +# documenting the functions and macros. +# e.g. DOC_SOURCE_DIR=../../../gtk +DOC_SOURCE_DIR=../../../shell + +# Extra options to pass to gtkdoc-scangobj. Not normally needed. +SCANGOBJ_OPTIONS= + +# Extra options to supply to gtkdoc-scan. +# e.g. SCAN_OPTIONS=--deprecated-guards="GTK_DISABLE_DEPRECATED" +SCAN_OPTIONS= + +# Extra options to supply to gtkdoc-mkdb. +MKDB_OPTIONS=--sgml-mode --output-format=xml --name-space=e + +# Extra options to supply to gtkdoc-mktmpl +# e.g. MKTMPL_OPTIONS=--only-section-tmpl +MKTMPL_OPTIONS= + +# Extra options to supply to gtkdoc-fixref. Not normally needed. +# e.g. FIXXREF_OPTIONS=--extra-dir=../gdk-pixbuf/html --extra-dir=../gdk/html +FIXXREF_OPTIONS= + +# Used for dependencies. The docs will be rebuilt if any of these change. +HFILE_GLOB=$(top_srcdir)/shell/*.h +CFILE_GLOB=$(top_srcdir)/shell/*.c + +# Header files to ignore when scanning. +IGNORE_HFILES= \ + evo-version.h \ + e-shell-window-private.h \ + es-event.h + +# Images to copy into HTML directory. +HTML_IMAGES= + +# Extra SGML files that are included by $(DOC_MAIN_SGML_FILE). +content_files= + +# SGML files where gtk-doc abbrevations (#GtkWidget) are expanded +# These files must be listed here *and* in content_files +expand_content_files= + +# CFLAGS and LDFLAGS for compiling gtkdoc-scangobj with your library. +# Only needed if you are using gtkdoc-scangobj to dynamically query widget +# signals and properties. +GTKDOC_CFLAGS= \ + -I$(top_builddir) \ + -I$(top_srcdir) \ + $(EVOLUTION_DATA_SERVER_CFLAGS) \ + $(GTKHTML_CFLAGS) +GTKDOC_LIBS= \ + $(top_builddir)/libemail-engine/libemail-engine.la \ + $(top_builddir)/shell/libeshell.la \ + $(top_builddir)/e-util/libeutil.la \ + $(EVOLUTION_DATA_SERVER_LIBS) \ + $(GTKHTML_LIBS) + +# This includes the standard gtk-doc make rules, copied by gtkdocize. +include $(top_srcdir)/gtk-doc.make + +# Other files to distribute +# e.g. EXTRA_DIST += version.xml.in +EXTRA_DIST += + +# Files not to distribute +# for --rebuild-types in $(SCAN_OPTIONS), e.g. $(DOC_MODULE).types +# for --rebuild-sections in $(SCAN_OPTIONS) e.g. $(DOC_MODULE)-sections.txt +#DISTCLEANFILES += + +#TESTS = $(GTKDOC_CHECK) + +-include $(top_srcdir)/git.mk diff --git a/doc/reference/libeshell/libeshell-docs.sgml b/doc/reference/libeshell/libeshell-docs.sgml new file mode 100644 index 0000000000..8a9404fdc9 --- /dev/null +++ b/doc/reference/libeshell/libeshell-docs.sgml @@ -0,0 +1,44 @@ + + + + + Evolution Shell (libeshell) + + The latest version of this documentation can be found on-line at + http://library.gnome.org/devel/libeshell/. + + + + + The Shell + + + + + + + + + + + + + + + Actions + + + + + + Object Hierarchy + + + + + Index + + + + diff --git a/doc/reference/libeshell/libeshell-overrides.txt b/doc/reference/libeshell/libeshell-overrides.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/doc/reference/libeshell/libeshell-sections.txt b/doc/reference/libeshell/libeshell-sections.txt new file mode 100644 index 0000000000..f6b997d4a1 --- /dev/null +++ b/doc/reference/libeshell/libeshell-sections.txt @@ -0,0 +1,423 @@ +
+e-shell +EShell +EShell +e_shell_get_default +e_shell_load_modules +e_shell_get_shell_backends +e_shell_get_canonical_name +e_shell_get_backend_by_name +e_shell_get_backend_by_scheme +e_shell_get_shell_settings +e_shell_get_registry +e_shell_create_shell_window +e_shell_handle_uris +e_shell_submit_alert +e_shell_get_active_window +e_shell_get_meego_mode +e_shell_get_express_mode +e_shell_get_small_screen_mode +e_shell_get_module_directory +e_shell_get_network_available +e_shell_set_network_available +e_shell_lock_network_available +e_shell_get_online +e_shell_set_online +e_shell_get_preferences_window +e_shell_event +EShellQuitReason +e_shell_quit +e_shell_cancel_quit +e_shell_adapt_window_size +e_shell_set_startup_view +e_shell_get_startup_view +E_SHELL_MIGRATE_ERROR +EShellMigrateError +e_shell_migrate_attempt +e_shell_detect_meego + +E_SHELL +E_IS_SHELL +E_TYPE_SHELL +E_SHELL_CLASS +E_IS_SHELL_CLASS +E_SHELL_GET_CLASS +E_TYPE_SHELL_QUIT_REASON +EShellClass +e_shell_get_type +e_shell_quit_reason_get_type + +EShellPrivate +e_shell_migrate_error_quark +
+ +
+e-shell-backend +EShellBackend +EShellBackend +e_shell_backend_compare +e_shell_backend_get_config_dir +e_shell_backend_get_data_dir +e_shell_backend_get_shell +e_shell_backend_add_activity +e_shell_backend_cancel_all +e_shell_backend_is_busy +e_shell_backend_get_prefer_new_item +e_shell_backend_set_prefer_new_item +e_shell_backend_start +e_shell_backend_is_started +e_shell_backend_migrate + +E_SHELL_BACKEND +E_IS_SHELL_BACKEND +E_TYPE_SHELL_BACKEND +E_SHELL_BACKEND_CLASS +E_IS_SHELL_BACKEND_CLASS +E_SHELL_BACKEND_GET_CLASS +EShellBackendClass +e_shell_backend_get_type + +EShellBackendPrivate +
+ +
+e-shell-content +EShellContent +EShellContent +e_shell_content_new +e_shell_content_set_searchbar +e_shell_content_check_state +e_shell_content_focus_search_results +e_shell_content_get_alert_bar +e_shell_content_get_shell_view +e_shell_content_get_view_id +e_shell_content_set_view_id +e_shell_content_run_advanced_search_dialog +e_shell_content_run_edit_searches_dialog +e_shell_content_run_save_search_dialog + +E_SHELL_CONTENT +E_IS_SHELL_CONTENT +E_TYPE_SHELL_CONTENT +E_SHELL_CONTENT_CLASS +E_IS_SHELL_CONTENT_CLASS +E_SHELL_CONTENT_GET_CLASS +EShellContentClass +e_shell_content_get_type + +EShellContentPrivate +
+ +
+e-shell-searchbar +EShellSearchbar +EShellSearchbar +e_shell_searchbar_new +e_shell_searchbar_get_shell_view +e_shell_searchbar_get_express_mode +e_shell_searchbar_set_express_mode +e_shell_searchbar_get_filter_combo_box +e_shell_searchbar_get_filter_visible +e_shell_searchbar_set_filter_visible +e_shell_searchbar_get_labels_visible +e_shell_searchbar_set_labels_visible +e_shell_searchbar_get_search_hint +e_shell_searchbar_set_search_hint +e_shell_searchbar_get_search_option +e_shell_searchbar_set_search_option +e_shell_searchbar_get_search_text +e_shell_searchbar_set_search_text +e_shell_searchbar_get_search_visible +e_shell_searchbar_set_search_visible +e_shell_searchbar_get_search_box +e_shell_searchbar_get_scope_combo_box +e_shell_searchbar_get_scope_visible +e_shell_searchbar_set_scope_visible +e_shell_searchbar_set_state_dirty +e_shell_searchbar_get_state_group +e_shell_searchbar_set_state_group +e_shell_searchbar_load_state +e_shell_searchbar_save_state + +E_SHELL_SEARCHBAR +E_IS_SHELL_SEARCHBAR +E_TYPE_SHELL_SEARCHBAR +E_SHELL_SEARCHBAR_CLASS +E_IS_SHELL_SEARCHBAR_CLASS +E_SHELL_SEARCHBAR_GET_CLASS +EShellSearchbarClass +e_shell_searchbar_get_type + +EShellSearchbarPrivate +
+ +
+e-shell-settings +EShellSettings +EShellSettings +e_shell_settings_install_property +e_shell_settings_install_property_for_key +e_shell_settings_enable_debug +e_shell_settings_get_boolean +e_shell_settings_set_boolean +e_shell_settings_get_int +e_shell_settings_set_int +e_shell_settings_get_string +e_shell_settings_set_string +e_shell_settings_get_object +e_shell_settings_set_object +e_shell_settings_get_pointer +e_shell_settings_set_pointer + +E_SHELL_SETTINGS +E_IS_SHELL_SETTINGS +E_TYPE_SHELL_SETTINGS +E_SHELL_SETTINGS_CLASS +E_IS_SHELL_SETTINGS_CLASS +E_SHELL_SETTINGS_GET_CLASS +EShellSettingsClass +e_shell_settings_get_type + +EShellSettingsPrivate +
+ +
+e-shell-sidebar +EShellSidebar +EShellSidebar +e_shell_sidebar_new +e_shell_sidebar_check_state +e_shell_sidebar_get_shell_view +e_shell_sidebar_get_icon_name +e_shell_sidebar_set_icon_name +e_shell_sidebar_get_primary_text +e_shell_sidebar_set_primary_text +e_shell_sidebar_get_secondary_text +e_shell_sidebar_set_secondary_text + +E_SHELL_SIDEBAR +E_IS_SHELL_SIDEBAR +E_TYPE_SHELL_SIDEBAR +E_SHELL_SIDEBAR_CLASS +E_IS_SHELL_SIDEBAR_CLASS +E_SHELL_SIDEBAR_GET_CLASS +EShellSidebarClass +e_shell_sidebar_get_type + +EShellSidebarPrivate +
+ +
+e-shell-switcher +EShellSwitcher +EShellSwitcher +e_shell_switcher_new +e_shell_switcher_add_action +e_shell_switcher_get_style +e_shell_switcher_set_style +e_shell_switcher_unset_style +e_shell_switcher_get_visible +e_shell_switcher_set_visible + +E_SHELL_SWITCHER +E_IS_SHELL_SWITCHER +E_TYPE_SHELL_SWITCHER +E_SHELL_SWITCHER_CLASS +E_IS_SHELL_SWITCHER_CLASS +E_SHELL_SWITCHER_GET_CLASS +EShellSwitcherClass +e_shell_switcher_get_type + +EShellSwitcherPrivate +E_SHELL_SWITCHER_DEFAULT_TOOLBAR_STYLE +
+ +
+e-shell-taskbar +EShellTaskbar +EShellTaskbar +e_shell_taskbar_new +e_shell_taskbar_get_shell_view +e_shell_taskbar_get_message +e_shell_taskbar_set_message +e_shell_taskbar_unset_message +e_shell_taskbar_get_activity_count + +E_SHELL_TASKBAR +E_IS_SHELL_TASKBAR +E_TYPE_SHELL_TASKBAR +E_SHELL_TASKBAR_CLASS +E_IS_SHELL_TASKBAR_CLASS +E_SHELL_TASKBAR_GET_CLASS +EShellTaskbarClass +e_shell_taskbar_get_type + +EShellTaskbarPrivate +
+ +
+e-shell-utils +Shell Utilities +e_shell_configure_ui_manager +e_shell_run_open_dialog +e_shell_run_save_dialog +e_shell_utils_import_uris +e_shell_hide_widgets_for_express_mode +
+ +
+e-shell-view +EShellView +EShellView +EShellViewClass +e_shell_view_get_name +e_shell_view_get_action +e_shell_view_get_title +e_shell_view_set_title +e_shell_view_get_view_id +e_shell_view_set_view_id +e_shell_view_is_active +e_shell_view_get_page_num +e_shell_view_set_page_num +e_shell_view_get_searchbar +e_shell_view_get_search_name +e_shell_view_get_search_rule +e_shell_view_set_search_rule +e_shell_view_get_search_query +e_shell_view_get_shell_backend +e_shell_view_get_shell_content +e_shell_view_get_shell_sidebar +e_shell_view_get_shell_taskbar +e_shell_view_get_shell_window +e_shell_view_get_state_key_file +e_shell_view_set_state_dirty +e_shell_view_clear_search +e_shell_view_custom_search +e_shell_view_execute_search +e_shell_view_block_execute_search +e_shell_view_unblock_execute_search +e_shell_view_is_execute_search_blocked +e_shell_view_update_actions +e_shell_view_block_update_actions +e_shell_view_unblock_update_actions +e_shell_view_show_popup_menu +e_shell_view_new_view_instance +e_shell_view_write_source +e_shell_view_remove_source +e_shell_view_remote_delete_source + +E_SHELL_VIEW +E_IS_SHELL_VIEW +E_TYPE_SHELL_VIEW +E_SHELL_VIEW_CLASS +E_IS_SHELL_VIEW_CLASS +E_SHELL_VIEW_GET_CLASS +e_shell_view_get_type + +EShellViewPrivate +
+ +
+e-shell-window +EShellWindow +EShellWindow +e_shell_window_new +e_shell_window_get_shell +e_shell_window_get_shell_view +e_shell_window_peek_shell_view +e_shell_window_get_shell_view_action +e_shell_window_get_alert_bar +e_shell_window_get_focus_tracker +e_shell_window_get_ui_manager +e_shell_window_get_action +e_shell_window_get_action_group +e_shell_window_get_managed_widget +e_shell_window_get_active_view +e_shell_window_set_active_view +e_shell_window_get_safe_mode +e_shell_window_set_safe_mode +e_shell_window_add_action_group +e_shell_window_get_sidebar_visible +e_shell_window_set_sidebar_visible +e_shell_window_get_switcher_visible +e_shell_window_set_switcher_visible +e_shell_window_get_taskbar_visible +e_shell_window_set_taskbar_visible +e_shell_window_get_toolbar_visible +e_shell_window_set_toolbar_visible +e_shell_window_get_toolbar_new_prefer_item +e_shell_window_set_toolbar_new_prefer_item +e_shell_window_register_new_item_actions +e_shell_window_register_new_source_actions +e_shell_window_get_menu_bar_box + +E_SHELL_WINDOW +E_IS_SHELL_WINDOW +E_TYPE_SHELL_WINDOW +E_SHELL_WINDOW_CLASS +E_IS_SHELL_WINDOW_CLASS +E_SHELL_WINDOW_GET_CLASS +EShellWindowClass +e_shell_window_get_type + +EShellWindowPrivate +E_SHELL_WINDOW_ACTION +E_SHELL_WINDOW_ACTION_GROUP +
+ +
+shell-actions +Shell Actions +E_SHELL_WINDOW_ACTION_ABOUT +E_SHELL_WINDOW_ACTION_CLOSE +E_SHELL_WINDOW_ACTION_CONTENTS +E_SHELL_WINDOW_ACTION_COPY_CLIPBOARD +E_SHELL_WINDOW_ACTION_CUT_CLIPBOARD +E_SHELL_WINDOW_ACTION_DELETE_SELECTION +E_SHELL_WINDOW_ACTION_GAL_CUSTOM_VIEW +E_SHELL_WINDOW_ACTION_GAL_DEFINE_VIEWS +E_SHELL_WINDOW_ACTION_GAL_SAVE_CUSTOM_VIEW +E_SHELL_WINDOW_ACTION_GROUP_NEW_WINDOW +E_SHELL_WINDOW_ACTION_IMPORT +E_SHELL_WINDOW_ACTION_NEW_WINDOW +E_SHELL_WINDOW_ACTION_PAGE_SETUP +E_SHELL_WINDOW_ACTION_PASTE_CLIPBOARD +E_SHELL_WINDOW_ACTION_PREFERENCES +E_SHELL_WINDOW_ACTION_QUICK_REFERENCE +E_SHELL_WINDOW_ACTION_QUIT +E_SHELL_WINDOW_ACTION_SEARCH_ADVANCED +E_SHELL_WINDOW_ACTION_SEARCH_CLEAR +E_SHELL_WINDOW_ACTION_SEARCH_EDIT +E_SHELL_WINDOW_ACTION_SEARCH_OPTIONS +E_SHELL_WINDOW_ACTION_SEARCH_QUICK +E_SHELL_WINDOW_ACTION_SEARCH_SAVE +E_SHELL_WINDOW_ACTION_SELECT_ALL +E_SHELL_WINDOW_ACTION_SHOW_SIDEBAR +E_SHELL_WINDOW_ACTION_SHOW_SWITCHER +E_SHELL_WINDOW_ACTION_SHOW_TASKBAR +E_SHELL_WINDOW_ACTION_SHOW_TOOLBAR +E_SHELL_WINDOW_ACTION_SUBMIT_BUG +E_SHELL_WINDOW_ACTION_SWITCHER_INITIAL +E_SHELL_WINDOW_ACTION_SWITCHER_MENU +E_SHELL_WINDOW_ACTION_SWITCHER_STYLE_BOTH +E_SHELL_WINDOW_ACTION_SWITCHER_STYLE_ICONS +E_SHELL_WINDOW_ACTION_SWITCHER_STYLE_TEXT +E_SHELL_WINDOW_ACTION_SWITCHER_STYLE_USER +E_SHELL_WINDOW_ACTION_WORK_OFFLINE +E_SHELL_WINDOW_ACTION_WORK_ONLINE +
+ +
+action-groups +Action Groups +E_SHELL_WINDOW_ACTION_GROUP_SHELL +E_SHELL_WINDOW_ACTION_GROUP_SWITCHER +E_SHELL_WINDOW_ACTION_GROUP_NEW_ITEM +E_SHELL_WINDOW_ACTION_GROUP_NEW_SOURCE +E_SHELL_WINDOW_ACTION_GROUP_CUSTOM_RULES +E_SHELL_WINDOW_ACTION_GROUP_GAL_VIEW +E_SHELL_WINDOW_ACTION_GROUP_LOCKDOWN_APPLICATION_HANDLERS +E_SHELL_WINDOW_ACTION_GROUP_LOCKDOWN_PRINTING +E_SHELL_WINDOW_ACTION_GROUP_LOCKDOWN_PRINT_SETUP +E_SHELL_WINDOW_ACTION_GROUP_LOCKDOWN_SAVE_TO_DISK +
diff --git a/doc/reference/libeshell/libeshell.types b/doc/reference/libeshell/libeshell.types new file mode 100644 index 0000000000..b0038d55e1 --- /dev/null +++ b/doc/reference/libeshell/libeshell.types @@ -0,0 +1,9 @@ +e_shell_get_type +e_shell_backend_get_type +e_shell_content_get_type +e_shell_searchbar_get_type +e_shell_sidebar_get_type +e_shell_switcher_get_type +e_shell_taskbar_get_type +e_shell_view_get_type +e_shell_window_get_type diff --git a/doc/reference/libeshell/tmpl/e-mail-account-manager.sgml b/doc/reference/libeshell/tmpl/e-mail-account-manager.sgml new file mode 100644 index 0000000000..49fe04bdf2 --- /dev/null +++ b/doc/reference/libeshell/tmpl/e-mail-account-manager.sgml @@ -0,0 +1,72 @@ + +e-mail-account-manager + + + + + + + + + + + + + + + + + + + + + + + + + +@parent: +@priv: + + + + + + +@registry: +@Returns: + + + + + + + +@manager: + + + + + + + +@manager: + + + + + + + +@manager: + + + + + + + +@manager: +@Returns: + + diff --git a/doc/reference/libeshell/tmpl/e-mail-account-tree-view.sgml b/doc/reference/libeshell/tmpl/e-mail-account-tree-view.sgml new file mode 100644 index 0000000000..eaee2f12c5 --- /dev/null +++ b/doc/reference/libeshell/tmpl/e-mail-account-tree-view.sgml @@ -0,0 +1,90 @@ + +e-mail-account-tree-view + + + + + + + + + + + + + + + + + + + + + + + + + +@parent: +@priv: + + + + + + +@registry: +@Returns: + + + + + + + +@tree_view: + + + + + + + +@tree_view: + + + + + + + +@tree_view: + + + + + + + +@tree_view: +@Returns: + + + + + + + +@tree_view: +@Returns: + + + + + + + +@tree_view: +@source: + + diff --git a/doc/reference/libeshell/tmpl/e-mail-identity-combo-box.sgml b/doc/reference/libeshell/tmpl/e-mail-identity-combo-box.sgml new file mode 100644 index 0000000000..fec8130b21 --- /dev/null +++ b/doc/reference/libeshell/tmpl/e-mail-identity-combo-box.sgml @@ -0,0 +1,56 @@ + +e-mail-identity-combo-box + + + + + + + + + + + + + + + + + + + + + + + + + +@parent: +@priv: + + + + + + +@registry: +@Returns: + + + + + + + +@combo_box: + + + + + + + +@combo_box: +@Returns: + + diff --git a/doc/reference/libeutil/Makefile.am b/doc/reference/libeutil/Makefile.am new file mode 100644 index 0000000000..71eabf3d76 --- /dev/null +++ b/doc/reference/libeutil/Makefile.am @@ -0,0 +1,67 @@ +# The name of the module. +DOC_MODULE = libeutil + +# The top-level SGML file. +DOC_MAIN_SGML_FILE = libeutil-docs.sgml + +# Extra options to supply to gtkdoc-scan. +SCAN_OPTIONS = --deprecated-guards="EDS_DISABLE_DEPRECATED" + +# The directory containing the source code. Relative to $(srcdir). +DOC_SOURCE_DIR = $(top_srcdir)/e-util + +# Used for dependencies. The docs will be rebuilt if any of these change. +HFILE_GLOB = $(top_srcdir)/e-util/*.h +CFILE_GLOB = $(top_srcdir)/e-util/*.c + +# Ignore all accessiblity headers. +IGNORE_HFILES = \ + ea-calendar-cell.h \ + ea-calendar-item.h \ + ea-cell-table.h \ + ea-factory.h \ + ea-widgets.h \ + gal-a11y-e-cell-registry.h \ + gal-a11y-e-cell-toggle.h \ + gal-a11y-e-cell-tree.h \ + gal-a11y-e-cell-vbox.h \ + gal-a11y-e-cell.h \ + gal-a11y-e-popup.h \ + gal-a11y-e-table-click-to-add-factory.h \ + gal-a11y-e-table-click-to-add.h \ + gal-a11y-e-table-column-header.h \ + gal-a11y-e-table-factory.h \ + gal-a11y-e-table-item-factory.h \ + gal-a11y-e-table-item.h \ + gal-a11y-e-table.h \ + gal-a11y-e-text-factory.h \ + gal-a11y-e-text.h \ + gal-a11y-e-tree-factory.h \ + gal-a11y-e-tree.h \ + gal-a11y-factory.h + gal-a11y-util.h \ + $(NULL) + +GTKDOC_CFLAGS = \ + -I$(top_srcdir) \ + -I$(top_builddir) \ + $(EVOLUTION_DATA_SERVER_CFLAGS) \ + $(GNOME_PLATFORM_CFLAGS) \ + $(CHAMPLAIN_CFLAGS) \ + $(GTKHTML_CFLAGS) \ + $(NULL) + +GTKDOC_LIBS = \ + $(top_builddir)/e-util/libeutil.la \ + $(EVOLUTION_DATA_SERVER_LIBS) \ + $(GNOME_PLATFORM_LIBS) \ + $(CHAMPLAIN_LIBS) \ + $(GTKHTML_LIBS) \ + $(NULL) + +# This includes the standard gtk-doc make rules, copied by gtkdocize. +include $(top_srcdir)/gtk-doc.make + +#TESTS = $(GTKDOC_CHECK) + +-include $(top_srcdir)/git.mk diff --git a/doc/reference/libeutil/libeutil-docs.sgml b/doc/reference/libeutil/libeutil-docs.sgml new file mode 100644 index 0000000000..329047b791 --- /dev/null +++ b/doc/reference/libeutil/libeutil-docs.sgml @@ -0,0 +1,263 @@ + + +]> + + + Evolution Utilities (libeutil) + + The latest version of this documentation can be found on-line at + http://library.gnome.org/devel/libeutil/. + + + + + Basic Utility Functions + + + + + + + + + + + + + + + + Attachment Management + + + + + + + + + + + + + + Category Management + + + + + + + + + + Filtering and Searching + + + + + + + + + + + + + + + + Tables and Trees + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Text Processing + + + + + + + + + + + + View Management + + + + + + + + + + + + + + (Unsorted Sections) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Object Hierarchy + + + + + Index + + + + + + diff --git a/doc/reference/libeutil/libeutil-overrides.txt b/doc/reference/libeutil/libeutil-overrides.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/doc/reference/libeutil/libeutil-sections.txt b/doc/reference/libeutil/libeutil-sections.txt new file mode 100644 index 0000000000..98b939826e --- /dev/null +++ b/doc/reference/libeutil/libeutil-sections.txt @@ -0,0 +1,1900 @@ +
+e-action-combo-box +EActionComboBox +EActionComboBox +e_action_combo_box_new +e_action_combo_box_new_with_action +e_action_combo_box_get_action +e_action_combo_box_set_action +e_action_combo_box_get_current_value +e_action_combo_box_set_current_value +e_action_combo_box_add_separator_before +e_action_combo_box_add_separator_after + +E_ACTION_COMBO_BOX +E_IS_ACTION_COMBO_BOX +E_TYPE_ACTION_COMBO_BOX +E_ACTION_COMBO_BOX_CLASS +E_IS_ACTION_COMBO_BOX_CLASS +E_ACTION_COMBO_BOX_GET_CLASS +EActionComboBoxClass +e_action_combo_box_get_type + +EActionComboBoxPrivate +
+ +
+e-activity +EActivity +EActivity +e_activity_new +e_activity_describe +e_activity_get_alert_sink +e_activity_set_alert_sink +e_activity_get_cancellable +e_activity_set_cancellable +e_activity_get_icon_name +e_activity_set_icon_name +e_activity_get_percent +e_activity_set_percent +EActivityState +e_activity_get_state +e_activity_set_state +e_activity_get_text +e_activity_set_text + +E_ACTIVITY +E_IS_ACTIVITY +E_TYPE_ACTIVITY +E_ACTIVITY_CLASS +E_IS_ACTIVITY_CLASS +E_ACTIVITY_GET_CLASS +EActivityClass +e_activity_get_type + +EActivityPrivate +
+ +
+e-activity-bar +EActivityBar +EActivityBar +e_activity_bar_new +e_activity_bar_get_activity +e_activity_bar_set_activity + +E_ACTIVITY_BAR +E_IS_ACTIVITY_BAR +E_TYPE_ACTIVITY_BAR +E_ACTIVITY_BAR_CLASS +E_IS_ACTIVITY_BAR_CLASS +E_ACTIVITY_BAR_GET_CLASS +EActivityBarClass +e_activity_bar_get_type + +EActivityBarPrivate +
+ +
+e-activity-proxy +EActivityProxy +EActivityProxy +e_activity_proxy_new +e_activity_proxy_get_activity + +E_ACTIVITY_PROXY +E_IS_ACTIVITY_PROXY +E_TYPE_ACTIVITY_PROXY +E_ACTIVITY_PROXY_CLASS +E_IS_ACTIVITY_PROXY_CLASS +E_ACTIVITY_PROXY_GET_CLASS +EActivityProxyClass +e_activity_proxy_get_type + +EActivityProxyPrivate +
+ +
+e-alarm-selector +EAlarmSelector +EAlarmSelector +e_alarm_selector_new + +E_ALARM_SELECTOR +E_IS_ALARM_SELECTOR +E_TYPE_ALARM_SELECTOR +E_ALARM_SELECTOR_CLASS +E_IS_ALARM_SELECTOR_CLASS +E_ALARM_SELECTOR_GET_CLASS +EAlarmSelectorClass +e_alarm_selector_get_type + +EAlarmSelectorPrivate +
+ +
+e-alert +EAlert +E_ALERT_ASK_FILE_EXISTS_OVERWRITE +E_ALERT_NO_SAVE_FILE +E_ALERT_NO_LOAD_FILE +EAlert +e_alert_new +e_alert_new_valist +e_alert_get_default_response +e_alert_set_default_response +e_alert_get_message_type +e_alert_set_message_type +e_alert_get_primary_text +e_alert_set_primary_text +e_alert_get_secondary_text +e_alert_set_secondary_text +e_alert_get_stock_id +e_alert_add_action +e_alert_peek_actions +e_alert_create_image +e_alert_response +e_alert_start_timer +e_alert_submit +e_alert_submit_valist + +E_ALERT +E_IS_ALERT +E_TYPE_ALERT +E_ALERT_CLASS +E_IS_ALERT_CLASS +E_ALERT_GET_CLASS +EAlertClass +e_alert_get_type + +EAlertPrivate +
+ +
+e-alert-bar +EAlertBar +EAlertBar +e_alert_bar_new +e_alert_bar_clear +e_alert_bar_add_alert + +E_ALERT_BAR +E_IS_ALERT_BAR +E_TYPE_ALERT_BAR +E_ALERT_BAR_CLASS +E_IS_ALERT_BAR_CLASS +E_ALERT_BAR_GET_CLASS +EAlertBarClass +e_alert_bar_get_type + +EAlertBarPrivate +
+ +
+e-alert-dialog +EAlertDialog +EAlertDialog +e_alert_dialog_new +e_alert_dialog_new_for_args +e_alert_run_dialog +e_alert_run_dialog_for_args +e_alert_dialog_get_alert +e_alert_dialog_get_content_area + +E_ALERT_DIALOG +E_IS_ALERT_DIALOG +E_TYPE_ALERT_DIALOG +E_ALERT_DIALOG_CLASS +E_IS_ALERT_DIALOG_CLASS +E_ALERT_DIALOG_GET_CLASS +EAlertDialogClass +e_alert_dialog_get_type + +EAlertDialogPrivate +
+ +
+e-alert-sink +EAlertSink +EAlertSink +EAlertSinkInterface +e_alert_sink_submit_alert + +E_ALERT_SINK +E_IS_ALERT_SINK +E_TYPE_ALERT_SINK +E_ALERT_SINK_INTERFACE +E_IS_ALERT_SINK_INTERFACE +E_ALERT_SINK_GET_INTERFACE +e_alert_sink_get_type +
+ +
+e-attachment +EAttachment +EAttachment +e_attachment_new +e_attachment_new_for_path +e_attachment_new_for_uri +e_attachment_new_for_message +e_attachment_add_to_multipart +e_attachment_cancel +e_attachment_get_can_show +e_attachment_set_can_show +e_attachment_get_disposition +e_attachment_set_disposition +e_attachment_get_file +e_attachment_set_file +e_attachment_get_file_info +e_attachment_set_file_info +e_attachment_get_icon +e_attachment_get_loading +e_attachment_get_mime_part +e_attachment_set_mime_part +e_attachment_get_percent +e_attachment_get_reference +e_attachment_set_reference +e_attachment_get_saving +e_attachment_get_shown +e_attachment_set_shown +e_attachment_get_encrypted +e_attachment_set_encrypted +e_attachment_get_signed +e_attachment_set_signed +e_attachment_get_description +e_attachment_get_thumbnail_path +e_attachment_is_rfc822 +e_attachment_list_apps +e_attachment_load_async +e_attachment_load_finish +e_attachment_open_async +e_attachment_open_finish +e_attachment_save_async +e_attachment_save_finish +e_attachment_load_handle_error +e_attachment_open_handle_error +e_attachment_save_handle_error + +E_ATTACHMENT +E_IS_ATTACHMENT +E_TYPE_ATTACHMENT +E_ATTACHMENT_CLASS +E_IS_ATTACHMENT_CLASS +E_ATTACHMENT_GET_CLASS +EAttachmentClass +e_attachment_get_type + +EAttachmentPrivate +
+ +
+e-attachment-bar +EAttachmentBar +EAttachmentBar +e_attachment_bar_new +e_attachment_bar_get_active_view +e_attachment_bar_set_active_view +e_attachment_bar_get_expanded +e_attachment_bar_set_expanded +e_attachment_bar_get_store + +E_ATTACHMENT_BAR +E_IS_ATTACHMENT_BAR +E_TYPE_ATTACHMENT_BAR +E_ATTACHMENT_BAR_CLASS +E_IS_ATTACHMENT_BAR_CLASS +E_ATTACHMENT_BAR_GET_CLASS +EAttachmentBarClass +e_attachment_bar_get_type + +EAttachmentBarPrivate +
+ +
+e-attachment-button +EAttachmentButton +EAttachmentButton +e_attachment_button_new +e_attachment_button_get_view +e_attachment_button_get_attachment +e_attachment_button_set_attachment +e_attachment_button_get_expandable +e_attachment_button_set_expandable +e_attachment_button_get_expanded +e_attachment_button_set_expanded + +E_ATTACHMENT_BUTTON +E_IS_ATTACHMENT_BUTTON +E_TYPE_ATTACHMENT_BUTTON +E_ATTACHMENT_BUTTON_CLASS +E_IS_ATTACHMENT_BUTTON_CLASS +E_ATTACHMENT_BUTTON_GET_CLASS +EAttachmentButtonClass +e_attachment_button_get_type + +EAttachmentButtonPrivate +
+ +
+e-attachment-dialog +EAttachmentDialog +EAttachmentDialog +e_attachment_dialog_new +e_attachment_dialog_get_attachment +e_attachment_dialog_set_attachment + +E_ATTACHMENT_DIALOG +E_IS_ATTACHMENT_DIALOG +E_TYPE_ATTACHMENT_DIALOG +E_ATTACHMENT_DIALOG_CLASS +E_IS_ATTACHMENT_DIALOG_CLASS +E_ATTACHMENT_DIALOG_GET_CLASS +EAttachmentDialogClass +e_attachment_dialog_get_type + +EAttachmentDialogPrivate +
+ +
+e-attachment-handler +EAttachmentHandler +EAttachmentHandler +EAttachmentHandlerImage +EAttachmentHandlerSendto +e_attachment_handler_get_view +e_attachment_handler_get_drag_actions +e_attachment_handler_get_target_table + +E_ATTACHMENT_HANDLER +E_IS_ATTACHMENT_HANDLER +E_TYPE_ATTACHMENT_HANDLER +E_ATTACHMENT_HANDLER_CLASS +E_IS_ATTACHMENT_HANDLER_CLASS +E_ATTACHMENT_HANDLER_GET_CLASS +E_ATTACHMENT_HANDLER_IMAGE +E_IS_ATTACHMENT_HANDLER_IMAGE +E_TYPE_ATTACHMENT_HANDLER_IMAGE +E_ATTACHMENT_HANDLER_IMAGE_CLASS +E_IS_ATTACHMENT_HANDLER_IMAGE_CLASS +E_ATTACHMENT_HANDLER_IMAGE_GET_CLASS +E_ATTACHMENT_HANDLER_SENDTO +E_IS_ATTACHMENT_HANDLER_SENDTO +E_TYPE_ATTACHMENT_HANDLER_SENDTO +E_ATTACHMENT_HANDLER_SENDTO_CLASS +E_IS_ATTACHMENT_HANDLER_SENDTO_CLASS +E_ATTACHMENT_HANDLER_SENDTO_GET_CLASS +EAttachmentHandlerClass +EAttachmentHandlerImageClass +EAttachmentHandlerSendtoClass +e_attachment_handler_get_type +e_attachment_handler_image_get_type +e_attachment_handler_sendto_get_type + +EAttachmentHandlerPrivate +EAttachmentHandlerImagePrivate +EAttachmentHandlerSendtoPrivate +
+ +
+e-attachment-icon-view +EAttachmentIconView +EAttachmentIconView +e_attachment_icon_view_new +e_attachment_icon_view_set_default_icon_size + +E_ATTACHMENT_ICON_VIEW +E_IS_ATTACHMENT_ICON_VIEW +E_TYPE_ATTACHMENT_ICON_VIEW +E_ATTACHMENT_ICON_VIEW_CLASS +E_IS_ATTACHMENT_ICON_VIEW_CLASS +E_ATTACHMENT_ICON_VIEW_GET_CLASS +EAttachmentIconViewClass +e_attachment_icon_view_get_type + +EAttachmentIconViewPrivate +
+ +
+e-attachment-paned +EAttachmentPaned +EAttachmentPaned +e_attachment_paned_new +e_attachment_paned_get_content_area +e_attachment_paned_get_active_view +e_attachment_paned_set_active_view +e_attachment_paned_get_expanded +e_attachment_paned_set_expanded +e_attachment_paned_drag_data_received +e_attachment_paned_get_controls_container +e_attachment_paned_get_view_combo +e_attachment_paned_set_default_height + +E_ATTACHMENT_PANED +E_IS_ATTACHMENT_PANED +E_TYPE_ATTACHMENT_PANED +E_ATTACHMENT_PANED_CLASS +E_IS_ATTACHMENT_PANED_CLASS +E_ATTACHMENT_PANED_GET_CLASS +EAttachmentPanedClass +e_attachment_paned_get_type + +EAttachmentPanedPrivate +
+ +
+e-attachment-store +EAttachmentStore +EAttachmentStore +e_attachment_store_new +e_attachment_store_add_attachment +e_attachment_store_remove_attachment +e_attachment_store_add_to_multipart +e_attachment_store_get_attachments +e_attachment_store_get_num_attachments +e_attachment_store_get_num_loading +e_attachment_store_get_total_size +e_attachment_store_run_load_dialog +e_attachment_store_run_save_dialog +e_attachment_store_get_uris_async +e_attachment_store_get_uris_finish +e_attachment_store_load_async +e_attachment_store_load_finish +e_attachment_store_save_async +e_attachment_store_save_finish + +E_ATTACHMENT_STORE +E_IS_ATTACHMENT_STORE +E_TYPE_ATTACHMENT_STORE +E_ATTACHMENT_STORE_CLASS +E_IS_ATTACHMENT_STORE_CLASS +E_ATTACHMENT_STORE_GET_CLASS +EAttachmentStoreClass +e_attachment_store_get_type + +EAttachmentStorePrivate +
+ +
+e-attachment-tree-view +EAttachmentTreeView +EAttachmentTreeView +e_attachment_tree_view_new + +E_ATTACHMENT_TREE_VIEW +E_IS_ATTACHMENT_TREE_VIEW +E_TYPE_ATTACHMENT_TREE_VIEW +E_ATTACHMENT_TREE_VIEW_CLASS +E_IS_ATTACHMENT_TREE_VIEW_CLASS +E_ATTACHMENT_TREE_VIEW_GET_CLASS +EAttachmentTreeViewClass +e_attachment_tree_view_get_type + +EAttachmentTreeViewPrivate +
+ +
+e-attachment-view +EAttachmentView +EAttachmentView +e_attachment_view_init +e_attachment_view_dispose +e_attachment_view_finalize +e_attachment_view_get_private +e_attachment_view_get_store +e_attachment_view_get_editable +e_attachment_view_set_editable +e_attachment_view_get_target_list +e_attachment_view_get_drag_actions +e_attachment_view_get_selected_attachments +e_attachment_view_open_path +e_attachment_view_remove_selected +e_attachment_view_button_press_event +e_attachment_view_button_release_event +e_attachment_view_motion_notify_event +e_attachment_view_key_press_event +e_attachment_view_get_path_at_pos +e_attachment_view_get_selected_paths +e_attachment_view_path_is_selected +e_attachment_view_select_path +e_attachment_view_unselect_path +e_attachment_view_select_all +e_attachment_view_unselect_all +e_attachment_view_sync_selection +e_attachment_view_drag_source_set +e_attachment_view_drag_source_unset +e_attachment_view_drag_begin +e_attachment_view_drag_end +e_attachment_view_drag_data_get +e_attachment_view_drag_dest_set +e_attachment_view_drag_dest_unset +e_attachment_view_drag_motion +e_attachment_view_drag_drop +e_attachment_view_drag_data_received +e_attachment_view_get_action +e_attachment_view_add_action_group +e_attachment_view_get_action_group +e_attachment_view_get_popup_menu +e_attachment_view_get_ui_manager +e_attachment_view_show_popup_menu +e_attachment_view_update_actions + +E_ATTACHMENT_VIEW +E_IS_ATTACHMENT_VIEW +E_TYPE_ATTACHMENT_VIEW +E_ATTACHMENT_VIEW_IFACE +E_IS_ATTACHMENT_VIEW_IFACE +E_ATTACHMENT_VIEW_GET_IFACE +EAttachmentViewIface +e_attachment_view_get_type + +EAttachmentViewPrivate +
+ +
+e-auth-combo-box +EAuthComboBox +EAuthComboBox +e_auth_combo_box_new +e_auth_combo_box_get_provider +e_auth_combo_box_set_provider +e_auth_combo_box_update_available + +E_AUTH_COMBO_BOX +E_IS_AUTH_COMBO_BOX +E_TYPE_AUTH_COMBO_BOX +E_AUTH_COMBO_BOX_CLASS +E_IS_AUTH_COMBO_BOX_CLASS +E_AUTH_COMBO_BOX_GET_CLASS +EAuthComboBoxClass +e_auth_combo_box_get_type + +EAuthComboBoxPrivate +
+ +
+e-autocomplete-selector +EAutocompleteSelector +EAutocompleteSelector +e_autocomplete_selector_new + +E_AUTOCOMPLETE_SELECTOR +E_IS_AUTOCOMPLETE_SELECTOR +E_TYPE_AUTOCOMPLETE_SELECTOR +E_AUTOCOMPLETE_SELECTOR_CLASS +E_IS_AUTOCOMPLETE_SELECTOR_CLASS +E_AUTOCOMPLETE_SELECTOR_GET_CLASS +EAutocompleteSelectorClass +e_autocomplete_selector_get_type + +EAutocompleteSelectorPrivate +
+ +
+e-bit-array +Bit Arrays (Legacy) +EBitArray +e_bit_array_new +e_bit_array_value_at +e_bit_array_foreach +e_bit_array_selected_count +e_bit_array_select_all +e_bit_array_invert_selection +e_bit_array_bit_count +e_bit_array_change_one_row +e_bit_array_change_range +e_bit_array_select_single_row +e_bit_array_toggle_single_row +e_bit_array_insert +e_bit_array_delete +e_bit_array_delete_single_mode +e_bit_array_move_row + +E_BIT_ARRAY +E_IS_BIT_ARRAY +E_BIT_ARRAY_TYPE +E_BIT_ARRAY_CLASS +E_IS_BIT_ARRAY_CLASS +EBitArrayClass +e_bit_array_get_type +
+ +
+e-book-source-config +EBookSourceConfig +EBookSourceConfig +e_book_source_config_new +e_book_source_config_add_offline_toggle + +E_BOOK_SOURCE_CONFIG +E_IS_BOOK_SOURCE_CONFIG +E_TYPE_BOOK_SOURCE_CONFIG +E_BOOK_SOURCE_CONFIG_CLASS +E_IS_BOOK_SOURCE_CONFIG_CLASS +E_BOOK_SOURCE_CONFIG_GET_CLASS +EBookSourceConfigClass +e_book_source_config_get_type + +EBookSourceConfigPrivate +
+ +
+e-cal-source-config +ECalSourceConfig +ECalSourceConfig +e_cal_source_config_new +e_cal_source_config_get_source_type +e_cal_source_config_add_offline_toggle + +E_CAL_SOURCE_CONFIG +E_IS_CAL_SOURCE_CONFIG +E_TYPE_CAL_SOURCE_CONFIG +E_CAL_SOURCE_CONFIG_CLASS +E_IS_CAL_SOURCE_CONFIG_CLASS +E_CAL_SOURCE_CONFIG_GET_CLASS +ECalSourceConfigClass +e_cal_source_config_get_type + +ECalSourceConfigPrivate +
+ +
+e-calendar +ECalendar +ECalendar +e_calendar_new +e_calendar_set_minimum_size +e_calendar_set_maximum_size +e_calendar_get_border_size +e_calendar_set_focusable + +E_CALENDAR +E_IS_CALENDAR +E_TYPE_CALENDAR +E_CALENDAR_CLASS +E_IS_CALENDAR_CLASS +E_CALENDAR_GET_CLASS +ECalendarClass +e_calendar_get_type +
+ +
+e-calendar-item +ECalendarItem +E_CALENDAR_ITEM_YPAD_ABOVE_MONTH_NAME +E_CALENDAR_ITEM_YPAD_BELOW_MONTH_NAME +E_CALENDAR_ITEM_MARK_BOLD +E_CALENDAR_ITEM_MARK_ITALIC +E_CALENDAR_ITEM_MIN_CELL_XPAD +E_CALENDAR_ITEM_MIN_CELL_YPAD +E_CALENDAR_ITEM_YPAD_ABOVE_DAY_LETTERS +E_CALENDAR_ITEM_YPAD_BELOW_DAY_LETTERS +E_CALENDAR_ITEM_YPAD_ABOVE_CELLS +E_CALENDAR_ITEM_YPAD_BELOW_CELLS +E_CALENDAR_ITEM_XPAD_BEFORE_MONTH_NAME_WITH_BUTTON +E_CALENDAR_ITEM_XPAD_BEFORE_MONTH_NAME +E_CALENDAR_ITEM_XPAD_AFTER_MONTH_NAME +E_CALENDAR_ITEM_XPAD_AFTER_MONTH_NAME_WITH_BUTTON +E_CALENDAR_ITEM_XPAD_BEFORE_WEEK_NUMBERS +E_CALENDAR_ITEM_XPAD_AFTER_WEEK_NUMBERS +E_CALENDAR_ITEM_XPAD_BEFORE_CELLS +E_CALENDAR_ITEM_XPAD_AFTER_CELLS +ECalendarItemColors +ECalendarItem +e_calendar_item_get_first_month +e_calendar_item_set_first_month +e_calendar_item_get_max_days_sel +e_calendar_item_set_max_days_sel +e_calendar_item_get_days_start_week_sel +e_calendar_item_set_days_start_week_sel +e_calendar_item_get_display_popup +e_calendar_item_set_display_popup +e_calendar_item_get_date_range +e_calendar_item_get_selection +e_calendar_item_set_selection +e_calendar_item_clear_marks +e_calendar_item_mark_day +e_calendar_item_mark_days +ECalendarItemStyleCallback +e_calendar_item_set_style_callback +ECalendarItemGetTimeCallback +e_calendar_item_set_get_time_callback +e_calendar_item_normalize_date +e_calendar_item_get_week_number +e_calendar_item_style_set + +E_CALENDAR_ITEM +E_IS_CALENDAR_ITEM +E_TYPE_CALENDAR_ITEM +E_CALENDAR_ITEM_CLASS +E_IS_CALENDAR_ITEM_CLASS +E_CALENDAR_ITEM_GET_CLASS +ECalendarItemClass +e_calendar_item_get_type +
+ +
+e-canvas +ECanvas +ECanvasItemSelectionFunc +ECanvasItemSelectionCompareFunc +ECanvasSelectionInfo +ECanvas +e_canvas_new +e_canvas_item_grab_focus +e_canvas_item_request_reflow +e_canvas_item_request_parent_reflow +ECanvasItemReflowFunc +e_canvas_item_set_reflow_callback +ECanvasItemGrabCancelled +e_canvas_item_grab +e_canvas_item_ungrab + +E_CANVAS +E_IS_CANVAS +E_TYPE_CANVAS +E_CANVAS_CLASS +E_IS_CANVAS_CLASS +E_TYPE_CANVAS_CLASS +ECanvasClass +e_canvas_get_type +
+ +
+e-canvas-background +ECanvasBackground +ECanvasBackground + +E_CANVAS_BACKGROUND +E_IS_CANVAS_BACKGROUND +E_TYPE_CANVAS_BACKGROUND +E_CANVAS_BACKGROUND_CLASS +E_IS_CANVAS_BACKGROUND_CLASS +E_CANVAS_BACKGROUND_GET_CLASS +ECanvasBackgroundClass +e_canvas_background_get_type + +ECanvasBackgroundPrivate +
+ +
+e-canvas-vbox +ECanvasVbox +ECanvasVbox +e_canvas_vbox_add_item +e_canvas_vbox_add_item_start + +E_CANVAS_VBOX +E_IS_CANVAS_VBOX +E_TYPE_CANVAS_VBOX +E_CANVAS_VBOX_CLASS +E_IS_CANVAS_VBOX_CLASS +E_CANVAS_VBOX_GET_CLASS +ECanvasVboxClass +e_canvas_vbox_get_type +
+ +
+e-categories-config +Categories +e_categories_config_get_icon_for +e_categories_config_open_dialog_for_entry +
+ +
+e-categories-dialog +ECategoriesDialog +ECategoriesDialog +e_categories_dialog_new +e_categories_dialog_get_categories +e_categories_dialog_set_categories + +E_CATEGORIES_DIALOG +E_IS_CATEGORIES_DIALOG +E_TYPE_CATEGORIES_DIALOG +E_CATEGORIES_DIALOG_CLASS +E_IS_CATEGORIES_DIALOG_CLASS +E_CATEGORIES_DIALOG_GET_CLASS +ECategoriesDialogClass +e_categories_dialog_get_type + +ECategoriesDialogPrivate +
+ +
+e-categories-editor +ECategoriesEditor +ECategoriesEditor +e_categories_editor_new +e_categories_editor_get_categories +e_categories_editor_set_categories +e_categories_editor_get_entry_visible +e_categories_editor_set_entry_visible + +E_CATEGORIES_EDITOR +E_IS_CATEGORIES_EDITOR +E_TYPE_CATEGORIES_EDITOR +E_CATEGORIES_EDITOR_CLASS +E_IS_CATEGORIES_EDITOR_CLASS +E_CATEGORIES_EDITOR_GET_CLASS +ECategoriesEditorClass +e_categories_editor_get_type + +ECategoriesEditorPrivate +
+ +
+e-categories-selector +ECategoriesSelector +ECategoriesSelector +e_categories_selector_new +e_categories_selector_get_checked +e_categories_selector_set_checked +e_categories_selector_get_items_checkable +e_categories_selector_set_items_checkable +e_categories_selector_delete_selection +e_categories_selector_get_selected + +E_CATEGORIES_SELECTOR +E_IS_CATEGORIES_SELECTOR +E_TYPE_CATEGORIES_SELECTOR +E_CATEGORIES_SELECTOR_CLASS +E_IS_CATEGORIES_SELECTOR_CLASS +E_CATEGORIES_SELECTOR_GET_CLASS +ECategoriesSelectorClass +e_categories_selector_get_type + +ECategoriesSelectorPrivate +
+ +
+e-category-completion +ECategoryCompletion +ECategoryCompletion +e_category_completion_new + +E_TYPE_CATEGORY_COMPLETION +E_CATEGORY_COMPLETION +E_CATEGORY_COMPLETION_CLASS +E_IS_CATEGORY_COMPLETION +E_IS_CATEGORY_COMPLETION_CLASS +E_CATEGORY_COMPLETION_GET_CLASS +ECategoryCompletionClass +e_category_completion_get_type + +ECategoryCompletionPrivate +
+ +
+e-category-editor +ECategoryEditor +ECategoryEditor +e_category_editor_new +e_category_editor_create_category +e_category_editor_edit_category + +E_CATEGORY_EDITOR +E_IS_CATEGORY_EDITOR +E_TYPE_CATEGORY_EDITOR +E_CATEGORY_EDITOR_CLASS +E_IS_CATEGORY_EDITOR_CLASS +E_CATEGORY_EDITOR_GET_CLASS +ECategoryEditorClass +e_category_editor_get_type + +ECategoryEditorPrivate +
+ +
+e-cell-renderer-color +ECellRendererColor +ECellRendererColor +e_cell_renderer_color_new + +E_TYPE_CELL_RENDERER_COLOR +E_CELL_RENDERER_COLOR +E_CELL_RENDERER_COLOR_CLASS +E_IS_CELL_RENDERER_COLOR +E_IS_CELL_RENDERER_COLOR_CLASS +E_CELL_RENDERER_COLOR_GET_CLASS +ECellRendererColorClass +e_cell_renderer_color_get_type + +ECellRendererColorPrivate +
+ +
+e-charset-combo-box +ECharsetComboBox +ECharsetComboBox +e_charset_combo_box_new +e_charset_combo_box_get_charset +e_charset_combo_box_set_charset + +E_CHARSET_COMBO_BOX +E_IS_CHARSET_COMBO_BOX +E_TYPE_CHARSET_COMBO_BOX +E_CHARSET_COMBO_BOX_CLASS +E_IS_CHARSET_COMBO_BOX_CLASS +E_CHARSET_COMBO_BOX_GET_CLASS +ECharsetComboBoxClass +e_charset_combo_box_get_type + +ECharsetComboBoxPrivate +
+ +
+e-client-utils +EClient Utilities +EClientSourceType +e_client_utils_new +e_client_utils_open_new +e_client_utils_open_new_finish +
+ +
+e-contact-store +EContactStore +EContactStore +e_contact_store_new +e_contact_store_get_client +e_contact_store_get_contact +e_contact_store_find_contact +e_contact_store_get_clients +e_contact_store_add_client +e_contact_store_remove_client +e_contact_store_set_query +e_contact_store_peek_query + +E_CONTACT_STORE +E_IS_CONTACT_STORE +E_TYPE_CONTACT_STORE +E_CONTACT_STORE_CLASS +E_IS_CONTACT_STORE_CLASS +E_CONTACT_STORE_GET_CLASS +EContactStoreClass +e_contact_store_get_type + +EContactStorePrivate +
+ +
+e-date-edit +EDateEdit +EDateEditGetTimeCallback +e_date_edit_new +e_date_edit_set_editable +e_date_edit_date_is_valid +e_date_edit_time_is_valid +e_date_edit_get_time +e_date_edit_set_time +e_date_edit_get_date +e_date_edit_set_date +e_date_edit_get_time_of_day +e_date_edit_set_time_of_day +e_date_edit_set_date_and_time_of_day +e_date_edit_get_show_date +e_date_edit_set_show_date +e_date_edit_get_show_time +e_date_edit_set_show_time +e_date_edit_get_week_start_day +e_date_edit_set_week_start_day +e_date_edit_get_show_week_numbers +e_date_edit_set_show_week_numbers +e_date_edit_get_use_24_hour_format +e_date_edit_set_use_24_hour_format +e_date_edit_get_allow_no_date_set +e_date_edit_set_allow_no_date_set +e_date_edit_get_time_popup_range +e_date_edit_set_time_popup_range +e_date_edit_get_make_time_insensitive +e_date_edit_set_make_time_insensitive +e_date_edit_get_twodigit_year_can_future +e_date_edit_set_twodigit_year_can_future +e_date_edit_set_get_time_callback +e_date_edit_get_entry + +E_DATE_EDIT +E_IS_DATE_EDIT +E_TYPE_DATE_EDIT +E_DATE_EDIT_CLASS +E_IS_DATE_EDIT_CLASS +E_DATE_EDIT_GET_CLASS +EDateEditClass +e_date_edit_get_type + +EDateEditPrivate +
+ +
+e-datetime-format +Date and Time Formatting +DTFormatKind +e_datetime_format_add_setup_widget +e_datetime_format_format +e_datetime_format_format_tm +
+ +
+e-destination-store +EDestinationStore +EDestinationStore +EDestinationStoreColumnType +e_destination_store_new +e_destination_store_get_destination +e_destination_store_list_destinations +e_destination_store_insert_destination +e_destination_store_append_destination +e_destination_store_remove_destination +e_destination_store_remove_destination_nth +e_destination_store_get_destination_count +e_destination_store_get_path +e_destination_store_get_stamp + +E_DESTINATION_STORE +E_IS_DESTINATION_STORE +E_TYPE_DESTINATION_STORE +E_DESTINATION_STORE_CLASS +E_IS_DESTINATION_STORE_CLASS +E_DESTINATION_STORE_GET_CLASS +EDestinationStoreClass +e_destination_store_get_type + +EDestinationStorePrivate +
+ +
+e-dialog-utils +Dialog Utilities (Legacy) +e_notice +e_dialog_combo_box_set +e_dialog_combo_box_get +
+ +
+e-file-request +EFileRequest +EFileRequest + +E_FILE_REQUEST +E_IS_FILE_REQUEST +E_TYPE_FILE_REQUEST +E_FILE_REQUEST_CLASS +E_IS_FILE_REQUEST_CLASS +E_FILE_REQUEST_GET_CLASS +EFileRequestClass +e_file_request_get_type + +EFileRequestPrivate +
+ +
+e-filter-code +EFilterCode +EFilterCode +e_filter_code_new + +E_FILTER_CODE +E_IS_FILTER_CODE +E_TYPE_FILTER_CODE +E_FILTER_CODE_CLASS +E_IS_FILTER_CODE_CLASS +E_FILTER_CODE_GET_CLASS +EFilterCodeClass +e_filter_code_get_type + +EFilterCodePrivate +
+ +
+e-filter-color +EFilterColor +EFilterColor +e_filter_color_new + +E_FILTER_COLOR +E_IS_FILTER_COLOR +E_TYPE_FILTER_COLOR +E_FILTER_COLOR_CLASS +E_IS_FILTER_COLOR_CLASS +E_FILTER_COLOR_GET_CLASS +EFilterColorClass +e_filter_color_get_type + +EFilterColorPrivate + + +
+e-filter-datespec +EFilterDatespec +EFilterDatespec +EFilterDatespecType +e_filter_datespec_new + +E_FILTER_DATESPEC +E_IS_FILTER_DATESPEC +E_TYPE_FILTER_DATESPEC +E_FILTER_DATESPEC_CLASS +E_IS_FILTER_DATESPEC_CLASS +E_FILTER_DATESPEC_GET_CLASS +EFilterDatespecClass +e_filter_datespec_get_type + +EFilterDatespecPrivate + + +
+e-filter-element +EFilterElement +EFilterElement +e_filter_element_new +e_filter_element_set_data +e_filter_element_validate +e_filter_element_eq +e_filter_element_xml_create +e_filter_element_xml_encode +e_filter_element_xml_decode +e_filter_element_clone +e_filter_element_copy_value +e_filter_element_get_widget +e_filter_element_build_code +e_filter_element_format_sexp + +E_FILTER_ELEMENT +E_IS_FILTER_ELEMENT +E_TYPE_FILTER_ELEMENT +E_FILTER_ELEMENT_CLASS +E_IS_FILTER_ELEMENT_CLASS +E_FILTER_ELEMENT_GET_CLASS +EFilterElementClass +e_filter_element_get_type + +EFilterElementPrivate +
+ +
+e-filter-file +EFilterFile +EFilterFile +e_filter_file_new +e_filter_file_new_type_name +e_filter_file_set_path + +E_FILTER_FILE +E_IS_FILTER_FILE +E_TYPE_FILTER_FILE +E_FILTER_FILE_CLASS +E_IS_FILTER_FILE_CLASS +E_FILTER_FILE_GET_CLASS +EFilterFileClass +e_filter_file_get_type + +EFilterFilePrivate +
+ +
+e-filter-input +EFilterInput +EFilterInput +e_filter_input_new +e_filter_input_new_type_name +e_filter_input_set_value + +E_FILTER_INPUT +E_IS_FILTER_INPUT +E_TYPE_FILTER_INPUT +E_FILTER_INPUT_CLASS +E_IS_FILTER_INPUT_CLASS +E_TYPE_FILTER_INPUT_CLASS +EFilterInputClass +e_filter_input_get_type + +EFilterInputPrivate +
+ +
+e-filter-int +EFilterInt +EFilterInt +e_filter_int_new +e_filter_int_new_type +e_filter_int_set_value + +E_FILTER_INT +E_IS_FILTER_INT +E_TYPE_FILTER_INT +E_FILTER_INT_CLASS +E_IS_FILTER_INT_CLASS +E_FILTER_INT_GET_CLASS +EFilterIntClass +e_filter_int_get_type + +EFilterIntPrivate +
+ +
+e-filter-option +EFilterOption +EFilterOption +e_filter_option_new +e_filter_option_set_current +e_filter_option_get_current +e_filter_option_add +e_filter_option_remove_all + +E_FILTER_OPTION +E_IS_FILTER_OPTION +E_TYPE_FILTER_OPTION +E_FILTER_OPTION_CLASS +E_IS_FILTER_OPTION_CLASS +E_FILTER_OPTION_GET_CLASS +EFilterOptionClass +e_filter_option_get_type + +EFilterOptionPrivate +
+ +
+e-filter-part +EFilterPart +EFilterPart +e_filter_part_new +e_filter_part_validate +e_filter_part_eq +e_filter_part_xml_create +e_filter_part_xml_encode +e_filter_part_xml_decode +e_filter_part_clone +e_filter_part_copy_values +e_filter_part_find_element +e_filter_part_get_widget +e_filter_part_build_code +e_filter_part_expand_code +e_filter_part_build_code_list +e_filter_part_find_list +e_filter_part_next_list + +E_FILTER_PART +E_IS_FILTER_PART +E_TYPE_FILTER_PART +E_FILTER_PART_CLASS +E_IS_FILTER_PART_CLASS +E_FILTER_PART_GET_CLASS +EFilterPartClass +e_filter_part_get_type + +EFilterPartPrivate +
+ +
+e-filter-rule +EFilterRule +EFilterRule +e_filter_rule_new +e_filter_rule_clone +e_filter_rule_set_name +E_FILTER_SOURCE_INCOMING +E_FILTER_SOURCE_DEMAND +E_FILTER_SOURCE_OUTGOING +E_FILTER_SOURCE_JUNKTEST +e_filter_rule_set_source +e_filter_rule_validate +e_filter_rule_eq +e_filter_rule_xml_encode +e_filter_rule_xml_decode +e_filter_rule_copy +e_filter_rule_add_part +e_filter_rule_remove_part +e_filter_rule_replace_part +e_filter_rule_get_widget +e_filter_rule_build_code +e_filter_rule_emit_changed +e_filter_rule_next_list +e_filter_rule_find_list + +E_FILTER_RULE +E_IS_FILTER_RULE +E_TYPE_FILTER_RULE +E_FILTER_RULE_CLASS +E_IS_FILTER_RULE_CLASS +E_FILTER_RULE_GET_CLASS +EFilterRuleClass +e_filter_rule_get_type + +EFilterRulePrivate +
+ +
+e-focus-tracker +EFocusTracker +EFocusTracker +e_focus_tracker_new +e_focus_tracker_get_focus +e_focus_tracker_get_window +e_focus_tracker_get_cut_clipboard_action +e_focus_tracker_set_cut_clipboard_action +e_focus_tracker_get_copy_clipboard_action +e_focus_tracker_set_copy_clipboard_action +e_focus_tracker_get_paste_clipboard_action +e_focus_tracker_set_paste_clipboard_action +e_focus_tracker_get_delete_selection_action +e_focus_tracker_set_delete_selection_action +e_focus_tracker_get_select_all_action +e_focus_tracker_set_select_all_action +e_focus_tracker_update_actions +e_focus_tracker_cut_clipboard +e_focus_tracker_copy_clipboard +e_focus_tracker_paste_clipboard +e_focus_tracker_delete_selection +e_focus_tracker_select_all + +E_FOCUS_TRACKER +E_IS_FOCUS_TRACKER +E_TYPE_FOCUS_TRACKER +E_FOCUS_TRACKER_CLASS +E_IS_FOCUS_TRACKER_CLASS +E_FOCUS_TRACKER_GET_CLASS +EFocusTrackerClass +e_focus_tracker_get_type + +EFocusTrackerPrivate +
+ +
+e-html-utils +Text to HTML Conversion +E_TEXT_TO_HTML_PRE +E_TEXT_TO_HTML_CONVERT_NL +E_TEXT_TO_HTML_CONVERT_SPACES +E_TEXT_TO_HTML_CONVERT_URLS +E_TEXT_TO_HTML_MARK_CITATION +E_TEXT_TO_HTML_CONVERT_ADDRESSES +E_TEXT_TO_HTML_ESCAPE_8BIT +E_TEXT_TO_HTML_CITE +e_text_to_html_full +e_text_to_html +
+ +
+e-icon-factory +Icon Utilities (Legacy) +e_icon_factory_get_icon_filename +e_icon_factory_get_icon +e_icon_factory_pixbuf_scale +e_icon_factory_create_thumbnail +
+ +
+e-mail-identity-combo-box +EMailIdentityComboBox +EMailIdentityComboBox +e_mail_identity_combo_box_new +e_mail_identity_combo_box_refresh +e_mail_identity_combo_box_get_registry + +E_MAIL_IDENTITY_COMBO_BOX +E_IS_MAIL_IDENTITY_COMBO_BOX +E_TYPE_MAIL_IDENTITY_COMBO_BOX +E_MAIL_IDENTITY_COMBO_BOX_CLASS +E_IS_MAIL_IDENTITY_COMBO_BOX_CLASS +E_MAIL_IDENTITY_COMBO_BOX_GET_CLASS +EMailIdentityComboBoxClass +e_mail_identity_combo_box_get_type + +EMailIdentityComboBoxPrivate +
+ +
+e-misc-utils +Miscellaneous Utilities +e_get_accels_filename +e_show_uri +e_display_help +e_lookup_action +e_lookup_action_group +e_action_compare_by_label +e_action_group_remove_all_actions +e_radio_action_get_current_action +e_categories_add_change_hook +e_str_without_underscores +e_str_compare +e_str_case_compare +e_collate_compare +e_int_compare +e_color_to_value +e_format_number +ESortCompareFunc +e_bsearch +e_strftime_fix_am_pm +e_utf8_strftime_fix_am_pm +e_get_month_name +e_get_weekday_name +e_flexible_strtod +e_ascii_dtostr +e_file_lock_create +e_file_lock_destroy +e_file_lock_exists +e_util_guess_mime_type +e_util_get_category_filter_options +e_binding_transform_color_to_string +e_binding_transform_string_to_color +e_binding_transform_source_to_uid +e_binding_transform_uid_to_source +e_charset_add_radio_actions +e_file_replace_contents_async +e_file_replace_contents_finish +e_mktemp +e_mkstemp +e_mkdtemp +
+ +
+e-name-selector +ENameSelector +ENameSelector +e_name_selector_new +e_name_selector_get_registry +e_name_selector_peek_model +e_name_selector_peek_dialog +e_name_selector_peek_section_entry +e_name_selector_peek_section_list +e_name_selector_show_dialog +e_name_selector_load_books +e_name_selector_cancel_loading + +E_NAME_SELECTOR +E_IS_NAME_SELECTOR +E_TYPE_NAME_SELECTOR +E_NAME_SELECTOR_CLASS +E_IS_NAME_SELECTOR_CLASS +E_NAME_SELECTOR_GET_CLASS +ENameSelectorClass +e_name_selector_get_type + +ENameSelectorPrivate +
+ +
+e-name-selector-dialog +ENameSelectorDialog +ENameSelectorDialog +e_name_selector_dialog_new +e_name_selector_dialog_get_registry +e_name_selector_dialog_peek_model +e_name_selector_dialog_set_model +e_name_selector_dialog_set_destination_index +e_name_selector_dialog_set_scrolling_policy +e_name_selector_dialog_get_section_visible +e_name_selector_dialog_set_section_visible + +E_NAME_SELECTOR_DIALOG +E_IS_NAME_SELECTOR_DIALOG +E_TYPE_NAME_SELECTOR_DIALOG +E_NAME_SELECTOR_DIALOG_CLASS +E_IS_NAME_SELECTOR_DIALOG_CLASS +E_NAME_SELECTOR_DIALOG_GET_CLASS +ENameSelectorDialogClass +e_name_selector_dialog_get_type + +ENameSelectorDialogPrivate +
+ +
+e-name-selector-entry +ENameSelectorEntry +ENameSelectorEntry +e_name_selector_entry_new +e_name_selector_entry_get_registry +e_name_selector_entry_set_registry +e_name_selector_entry_get_minimum_query_length +e_name_selector_entry_set_minimum_query_length +e_name_selector_entry_get_show_address +e_name_selector_entry_set_show_address +e_name_selector_entry_peek_contact_store +e_name_selector_entry_set_contact_store +e_name_selector_entry_peek_destination_store +e_name_selector_entry_set_destination_store +e_name_selector_entry_get_popup_destination +e_name_selector_entry_set_contact_editor_func +e_name_selector_entry_set_contact_list_editor_func +ens_util_populate_user_query_fields + +E_NAME_SELECTOR_ENTRY +E_IS_NAME_SELECTOR_ENTRY +E_TYPE_NAME_SELECTOR_ENTRY +E_NAME_SELECTOR_ENTRY_CLASS +E_IS_NAME_SELECTOR_ENTRY_CLASS +E_NAME_SELECTOR_ENTRY_GET_CLASS +ENameSelectorEntryClass +e_name_selector_entry_get_type + +ENameSelectorEntryPrivate +
+ +
+e-name-selector-list +ENameSelectorList +ENameSelectorList +e_name_selector_list_new +e_name_selector_list_expand_clicked + +E_NAME_SELECTOR_LIST +E_IS_NAME_SELECTOR_LIST +E_TYPE_NAME_SELECTOR_LIST +E_NAME_SELECTOR_LIST_CLASS +E_IS_NAME_SELECTOR_LIST_CLASS +E_NAME_SELECTOR_LIST_GET_CLASS +ENameSelectorListClass +e_name_selector_list_get_type + +ENameSelectorListPrivate +
+ +
+e-name-selector-model +ENameSelectorModel +ENameSelectorModel +e_name_selector_model_new +e_name_selector_model_peek_contact_store +e_name_selector_model_peek_contact_filter +e_name_selector_model_list_sections +e_name_selector_model_peek_section +e_name_selector_model_add_section +e_name_selector_model_remove_section +e_name_selector_model_get_contact_emails_without_used +e_name_selector_model_free_emails_list + +E_NAME_SELECTOR_MODEL +E_IS_NAME_SELECTOR_MODEL +E_TYPE_NAME_SELECTOR_MODEL +E_NAME_SELECTOR_MODEL_CLASS +E_IS_NAME_SELECTOR_MODEL_CLASS +E_NAME_SELECTOR_MODEL_GET_CLASS +ENameSelectorModelClass +e_name_selector_model_get_type + +ENameSelectorModelPrivate +
+ +
+e-passwords +Password Utilities (Legacy) +e_passwords_init +e_passwords_shutdown +e_passwords_cancel +e_passwords_set_online +e_passwords_remember_password +e_passwords_add_password +e_passwords_get_password +e_passwords_forget_password +e_passwords_forget_passwords +e_passwords_clear_passwords +EPasswordsRememberType +e_passwords_ask_password +
+ +
+e-poolv +EPoolv +EPoolv +e_poolv_new +e_poolv_set +e_poolv_get +e_poolv_destroy +
+ +
+e-popup-action +EPopupAction +EPopupAction +e_popup_action_new +EPopupActionEntry +e_action_group_add_popup_actions + +E_POPUP_ACTION +E_IS_POPUP_ACTION +E_TYPE_POPUP_ACTION +E_POPUP_ACTION_CLASS +E_IS_POPUP_ACTION_CLASS +E_POPUP_ACTION_GET_CLASS +EPopupActionClass +e_popup_action_get_type + +EPopupActionPrivate +
+ +
+e-print +Printing +e_print_operation_new +e_print_run_page_setup_dialog +
+ +
+e-rule-context +ERuleContext +ERuleContext +ERuleContextRegisterFunc +ERuleContextPartFunc +ERuleContextRuleFunc +ERuleContextNextPartFunc +ERuleContextNextRuleFunc +e_rule_context_new +e_rule_context_load +e_rule_context_save +e_rule_context_revert +e_rule_context_add_part +e_rule_context_find_part +e_rule_context_create_part +e_rule_context_next_part +e_rule_context_next_rule +e_rule_context_find_rule +e_rule_context_find_rank_rule +e_rule_context_add_rule +e_rule_context_add_rule_gui +e_rule_context_remove_rule +e_rule_context_rank_rule +e_rule_context_get_rank_rule +e_rule_context_add_part_set +e_rule_context_add_rule_set +e_rule_context_new_element +e_rule_context_delete_uri +e_rule_context_rename_uri +e_rule_context_free_uri_list + +E_RULE_CONTEXT +E_IS_RULE_CONTEXT +E_TYPE_RULE_CONTEXT +E_RULE_CONTEXT_CLASS +E_IS_RULE_CONTEXT_CLASS +E_RULE_CONTEXT_GET_CLASS +ERuleContextClass +e_rule_context_get_type + +ERuleContextPrivate +
+ +
+e-rule-editor +ERuleEditor +ERuleEditor +ERuleEditorUndo +e_rule_editor_new +e_rule_editor_construct +e_rule_editor_set_source +e_rule_editor_set_sensitive +e_rule_editor_create_rule + +E_RULE_EDITOR +E_IS_RULE_EDITOR +E_TYPE_RULE_EDITOR +E_RULE_EDITOR_CLASS +E_IS_RULE_EDITOR_CLASS +E_RULE_EDITOR_GET_CLASS +ERuleEditorClass +e_rule_editor_get_type + +ERuleEditorPrivate +
+ +
+e-selection +Selections +e_target_list_add_calendar_targets +e_target_list_add_directory_targets +e_selection_data_set_calendar +e_selection_data_set_directory +e_selection_data_get_calendar +e_selection_data_get_directory +e_selection_data_targets_include_calendar +e_selection_data_targets_include_directory +e_targets_include_calendar +e_targets_include_directory +e_clipboard_set_calendar +e_clipboard_set_directory +e_clipboard_request_calendar +e_clipboard_request_directory +e_clipboard_wait_for_calendar +e_clipboard_wait_for_directory +e_clipboard_wait_is_calendar_available +e_clipboard_wait_is_directory_available +
+ +
+e-source-combo-box +ESourceComboBox +ESourceComboBox +e_source_combo_box_new +e_source_combo_box_get_registry +e_source_combo_box_set_registry +e_source_combo_box_get_extension_name +e_source_combo_box_set_extension_name +e_source_combo_box_get_show_colors +e_source_combo_box_set_show_colors +e_source_combo_box_ref_active +e_source_combo_box_set_active + +E_SOURCE_COMBO_BOX +E_IS_SOURCE_COMBO_BOX +E_TYPE_SOURCE_COMBO_BOX +E_SOURCE_COMBO_BOX_CLASS +E_IS_SOURCE_COMBO_BOX_CLASS +E_SOURCE_COMBO_BOX_GET_CLASS +ESourceComboBoxClass +e_source_combo_box_get_type + +ESourceComboBoxPrivate +
+ +
+e-source-selector +ESourceSelector +ESourceSelector +e_source_selector_new +e_source_selector_get_registry +e_source_selector_get_extension_name +e_source_selector_get_show_colors +e_source_selector_set_show_colors +e_source_selector_get_show_toggles +e_source_selector_set_show_toggles +e_source_selector_select_source +e_source_selector_unselect_source +e_source_selector_select_exclusive +e_source_selector_source_is_selected +e_source_selector_get_selection +e_source_selector_free_selection +e_source_selector_set_select_new +e_source_selector_edit_primary_selection +e_source_selector_ref_primary_selection +e_source_selector_set_primary_selection +e_source_selector_ref_source_by_path +e_source_selector_queue_write + +E_SOURCE_SELECTOR +E_IS_SOURCE_SELECTOR +E_TYPE_SOURCE_SELECTOR +E_SOURCE_SELECTOR_CLASS +E_IS_SOURCE_SELECTOR_CLASS +E_SOURCE_SELECTOR_GET_CLASS +ESourceSelectorClass +e_source_selector_get_type + +ESourceSelectorPrivate +
+ +
+e-source-selector-dialog +ESourceSelectorDialog +ESourceSelectorDialog +e_source_selector_dialog_new +e_source_selector_dialog_get_registry +e_source_selector_dialog_get_extension_name +e_source_selector_dialog_get_selector +e_source_selector_dialog_peek_primary_selection + +E_SOURCE_SELECTOR_DIALOG +E_IS_SOURCE_SELECTOR_DIALOG +E_TYPE_SOURCE_SELECTOR_DIALOG +E_SOURCE_SELECTOR_DIALOG_CLASS +E_IS_SOURCE_SELECTOR_DIALOG_CLASS +E_SOURCE_SELECTOR_DIALOG_GET_CLASS +ESourceSelectorDialogClass +e_source_selector_dialog_get_type + +ESourceSelectorDialogPrivate +
+ +
+e-tree-model-generator +ETreeModelGenerator +ETreeModelGeneratorGenerateFunc +ETreeModelGeneratorModifyFunc +ETreeModelGenerator +e_tree_model_generator_new +e_tree_model_generator_get_model +e_tree_model_generator_set_generate_func +e_tree_model_generator_set_modify_func +e_tree_model_generator_convert_child_path_to_path +e_tree_model_generator_convert_child_iter_to_iter +e_tree_model_generator_convert_path_to_child_path +e_tree_model_generator_convert_iter_to_child_iter + +E_TREE_MODEL_GENERATOR +E_IS_TREE_MODEL_GENERATOR +E_TYPE_TREE_MODEL_GENERATOR +E_TREE_MODEL_GENERATOR_CLASS +E_IS_TREE_MODEL_GENERATOR_CLASS +E_TREE_MODEL_GENERATOR_GET_CLASS +ETreeModelGeneratorClass +e_tree_model_generator_get_type + +ETreeModelGeneratorPrivate +
+ +
+e-web-view +EWebView +EWebView +e_web_view_new +e_web_view_clear +e_web_view_load_string +e_web_view_get_caret_mode +e_web_view_set_caret_mode +e_web_view_get_copy_target_list +e_web_view_get_disable_printing +e_web_view_set_disable_printing +e_web_view_get_disable_save_to_disk +e_web_view_set_disable_save_to_disk +e_web_view_get_editable +e_web_view_set_editable +e_web_view_get_inline_spelling +e_web_view_set_inline_spelling +e_web_view_get_magic_links +e_web_view_set_magic_links +e_web_view_get_magic_smileys +e_web_view_set_magic_smileys +e_web_view_get_selected_uri +e_web_view_set_selected_uri +e_web_view_get_open_proxy +e_web_view_set_open_proxy +e_web_view_get_paste_target_list +e_web_view_get_print_proxy +e_web_view_set_print_proxy +e_web_view_get_save_as_proxy +e_web_view_set_save_as_proxy +e_web_view_get_action +e_web_view_get_action_group +e_web_view_extract_uri +e_web_view_copy_clipboard +e_web_view_cut_clipboard +e_web_view_is_selection_active +e_web_view_paste_clipboard +e_web_view_scroll_forward +e_web_view_scroll_backward +e_web_view_select_all +e_web_view_unselect_all +e_web_view_zoom_100 +e_web_view_zoom_in +e_web_view_zoom_out +e_web_view_get_ui_manager +e_web_view_get_popup_menu +e_web_view_show_popup_menu +e_web_view_status_message +e_web_view_stop_loading +e_web_view_update_actions + +E_WEB_VIEW +E_IS_WEB_VIEW +E_TYPE_WEB_VIEW +E_WEB_VIEW_CLASS +E_IS_WEB_VIEW_CLASS +E_WEB_VIEW_GET_CLASS +EWebViewClass +e_web_view_get_type + +EWebViewPrivate +
+ +
+e-xml-utils +Reading and Writing XML +e_xml_get_child_by_name_by_lang +e_xml_get_child_by_name_by_lang_list +e_xml_get_child_by_name_no_lang +e_xml_get_integer_prop_by_name +e_xml_get_integer_prop_by_name_with_default +e_xml_set_integer_prop_by_name +e_xml_get_uint_prop_by_name +e_xml_get_uint_prop_by_name_with_default +e_xml_set_uint_prop_by_name +e_xml_get_bool_prop_by_name +e_xml_get_bool_prop_by_name_with_default +e_xml_set_bool_prop_by_name +e_xml_get_double_prop_by_name +e_xml_get_double_prop_by_name_with_default +e_xml_set_double_prop_by_name +e_xml_get_string_prop_by_name +e_xml_get_string_prop_by_name_with_default +e_xml_set_string_prop_by_name +e_xml_get_translated_string_prop_by_name +
+ diff --git a/doc/reference/libeutil/libeutil.types b/doc/reference/libeutil/libeutil.types new file mode 100644 index 0000000000..b78b7ade94 --- /dev/null +++ b/doc/reference/libeutil/libeutil.types @@ -0,0 +1,170 @@ +#include + +e_action_combo_box_get_type +e_activity_bar_get_type +e_activity_get_type +e_activity_proxy_get_type +e_alarm_selector_get_type +e_alert_bar_get_type +e_alert_dialog_get_type +e_alert_get_type +e_alert_sink_get_type +e_attachment_bar_get_type +e_attachment_button_get_type +e_attachment_dialog_get_type +e_attachment_get_type +e_attachment_handler_get_type +e_attachment_handler_image_get_type +e_attachment_handler_sendto_get_type +e_attachment_icon_view_get_type +e_attachment_paned_get_type +e_attachment_store_get_type +e_attachment_tree_view_get_type +e_attachment_view_get_type +e_auth_combo_box_get_type +e_bit_array_get_type +e_book_source_config_get_type +e_cal_source_config_get_type +e_calendar_get_type +e_calendar_item_get_type +e_canvas_background_get_type +e_canvas_get_type +e_canvas_vbox_get_type +e_categories_dialog_get_type +e_categories_editor_get_type +e_categories_selector_get_type +e_category_completion_get_type +e_category_editor_get_type +e_cell_checkbox_get_type +e_cell_combo_get_type +e_cell_date_edit_get_type +e_cell_date_get_type +e_cell_get_type +e_cell_hbox_get_type +e_cell_number_get_type +e_cell_percent_get_type +e_cell_pixbuf_get_type +e_cell_popup_get_type +e_cell_renderer_color_get_type +e_cell_size_get_type +e_cell_text_get_type +e_cell_toggle_get_type +e_cell_tree_get_type +e_cell_vbox_get_type +e_charset_combo_box_get_type +e_config_get_type +e_config_hook_get_type +e_contact_store_get_type +e_date_edit_get_type +e_destination_store_get_type +e_event_get_type +e_event_hook_get_type +e_file_request_get_type +e_filter_code_get_type +e_filter_color_get_type +e_filter_datespec_get_type +e_filter_element_get_type +e_filter_file_get_type +e_filter_input_get_type +e_filter_int_get_type +e_filter_option_get_type +e_filter_part_get_type +e_filter_rule_get_type +e_focus_tracker_get_type +e_image_chooser_get_type +e_import_assistant_get_type +e_import_get_type +e_import_hook_get_type +e_interval_chooser_get_type +e_map_get_type +e_menu_tool_action_get_type +e_menu_tool_button_get_type +e_name_selector_dialog_get_type +e_name_selector_entry_get_type +e_name_selector_get_type +e_name_selector_list_get_type +e_name_selector_model_get_type +e_online_button_get_type +e_paned_get_type +e_picture_gallery_get_type +e_plugin_get_type +e_plugin_hook_get_type +e_plugin_ui_hook_get_type +e_popup_action_get_type +e_port_entry_get_type +e_preferences_window_get_type +e_preview_pane_get_type +e_printable_get_type +e_reflow_get_type +e_reflow_model_get_type +e_rule_context_get_type +e_rule_editor_get_type +e_search_bar_get_type +e_selectable_get_type +e_selection_model_array_get_type +e_selection_model_get_type +e_selection_model_simple_get_type +e_send_options_dialog_get_type +e_sorter_array_get_type +e_sorter_get_type +e_source_combo_box_get_type +e_source_config_dialog_get_type +e_source_config_get_type +e_source_selector_dialog_get_type +e_source_selector_get_type +e_spell_entry_get_type +e_stock_request_get_type +e_table_click_to_add_get_type +e_table_col_get_type +e_table_column_specification_get_type +e_table_config_get_type +e_table_extras_get_type +e_table_field_chooser_dialog_get_type +e_table_field_chooser_get_type +e_table_field_chooser_item_get_type +e_table_get_type +e_table_group_get_type +e_table_group_leaf_get_type +e_table_header_get_type +e_table_header_item_get_type +e_table_item_get_type +e_table_memory_get_type +e_table_memory_store_get_type +e_table_model_get_type +e_table_one_get_type +e_table_search_get_type +e_table_selection_model_get_type +e_table_sort_info_get_type +e_table_sorted_get_type +e_table_sorter_get_type +e_table_specification_get_type +e_table_state_get_type +e_table_subset_get_type +e_table_without_get_type +e_text_event_processor_emacs_like_get_type +e_text_event_processor_get_type +e_text_get_type +e_text_model_get_type +e_timezone_dialog_get_type +e_tree_get_type +e_tree_memory_get_type +e_tree_model_generator_get_type +e_tree_model_get_type +e_tree_selection_model_get_type +e_tree_sorted_get_type +e_tree_table_adapter_get_type +e_ui_manager_get_type +e_url_entry_get_type +e_web_view_get_type +e_web_view_gtkhtml_get_type +e_web_view_preview_get_type +gal_define_views_dialog_get_type +gal_define_views_model_get_type +gal_view_collection_get_type +gal_view_etable_get_type +gal_view_factory_etable_get_type +gal_view_factory_get_type +gal_view_get_type +gal_view_instance_get_type +gal_view_instance_save_as_dialog_get_type +gal_view_new_dialog_get_type diff --git a/doc/reference/shell/Makefile.am b/doc/reference/shell/Makefile.am deleted file mode 100644 index 40004abf8c..0000000000 --- a/doc/reference/shell/Makefile.am +++ /dev/null @@ -1,81 +0,0 @@ -# The name of the module, e.g. 'glib'. -DOC_MODULE=eshell - -# The top-level SGML file. You can change this if you want to. -DOC_MAIN_SGML_FILE=$(DOC_MODULE)-docs.sgml - -# The directory containing the source code. Relative to $(srcdir). -# gtk-doc will search all .c & .h files beneath here for inline comments -# documenting the functions and macros. -# e.g. DOC_SOURCE_DIR=../../../gtk -DOC_SOURCE_DIR=../../.. - -# Extra options to pass to gtkdoc-scangobj. Not normally needed. -SCANGOBJ_OPTIONS= - -# Extra options to supply to gtkdoc-scan. -# e.g. SCAN_OPTIONS=--deprecated-guards="GTK_DISABLE_DEPRECATED" -SCAN_OPTIONS= - -# Extra options to supply to gtkdoc-mkdb. -MKDB_OPTIONS=--sgml-mode --output-format=xml --name-space=e - -# Extra options to supply to gtkdoc-mktmpl -# e.g. MKTMPL_OPTIONS=--only-section-tmpl -MKTMPL_OPTIONS= - -# Extra options to supply to gtkdoc-fixref. Not normally needed. -# e.g. FIXXREF_OPTIONS=--extra-dir=../gdk-pixbuf/html --extra-dir=../gdk/html -FIXXREF_OPTIONS= - -# Used for dependencies. The docs will be rebuilt if any of these change. -HFILE_GLOB=$(top_srcdir)/shell/*.h -CFILE_GLOB=$(top_srcdir)/shell/*.c - -# Header files to ignore when scanning. -IGNORE_HFILES=e-shell-window-private.h - -# Images to copy into HTML directory. -HTML_IMAGES= - -# Extra SGML files that are included by $(DOC_MAIN_SGML_FILE). -content_files= - -# SGML files where gtk-doc abbrevations (#GtkWidget) are expanded -# These files must be listed here *and* in content_files -expand_content_files= - -# CFLAGS and LDFLAGS for compiling gtkdoc-scangobj with your library. -# Only needed if you are using gtkdoc-scangobj to dynamically query widget -# signals and properties. -GTKDOC_CFLAGS= \ - -I$(top_builddir) \ - -I$(top_srcdir) \ - $(EVOLUTION_DATA_SERVER_CFLAGS) \ - $(GTKHTML_CFLAGS) -GTKDOC_LIBS= \ - $(top_builddir)/libemail-utils/libemail-utils.la \ - $(top_builddir)/libemail-engine/libemail-engine.la \ - $(top_builddir)/shell/libeshell.la \ - $(top_builddir)/e-util/libeutil.la \ - $(top_builddir)/filter/libfilter.la \ - $(top_builddir)/widgets/menus/libmenus.la \ - $(top_builddir)/widgets/misc/libemiscwidgets.la \ - $(EVOLUTION_DATA_SERVER_LIBS) \ - $(GTKHTML_LIBS) - -# This includes the standard gtk-doc make rules, copied by gtkdocize. -include $(top_srcdir)/gtk-doc.make - -# Other files to distribute -# e.g. EXTRA_DIST += version.xml.in -EXTRA_DIST += - -# Files not to distribute -# for --rebuild-types in $(SCAN_OPTIONS), e.g. $(DOC_MODULE).types -# for --rebuild-sections in $(SCAN_OPTIONS) e.g. $(DOC_MODULE)-sections.txt -#DISTCLEANFILES += - -#TESTS = $(GTKDOC_CHECK) - --include $(top_srcdir)/git.mk diff --git a/doc/reference/shell/eshell-docs.sgml b/doc/reference/shell/eshell-docs.sgml deleted file mode 100644 index df6cd4354d..0000000000 --- a/doc/reference/shell/eshell-docs.sgml +++ /dev/null @@ -1,59 +0,0 @@ - - - - - Evolution Shell Reference Manual - - The latest version of this documentation can be found on-line at - http://library.gnome.org/devel/eshell/unstable/. - - - - - The Shell - - - - - - - - - - - - - - - Basic Utility Functions - - - - - - - - - - - - - - - - Actions - - - - - - Object Hierarchy - - - - - Index - - - diff --git a/doc/reference/shell/eshell-overrides.txt b/doc/reference/shell/eshell-overrides.txt deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/doc/reference/shell/eshell-sections.txt b/doc/reference/shell/eshell-sections.txt deleted file mode 100644 index 27fbe428c2..0000000000 --- a/doc/reference/shell/eshell-sections.txt +++ /dev/null @@ -1,1096 +0,0 @@ -
-e-shell -EShell -EShell -e_shell_get_default -e_shell_load_modules -e_shell_get_shell_backends -e_shell_get_canonical_name -e_shell_get_backend_by_name -e_shell_get_backend_by_scheme -e_shell_get_shell_settings -e_shell_create_shell_window -e_shell_handle_uris -e_shell_submit_alert -e_shell_get_active_window -e_shell_get_meego_mode -e_shell_get_express_mode -e_shell_get_small_screen_mode -e_shell_get_module_directory -e_shell_get_network_available -e_shell_set_network_available -e_shell_lock_network_available -e_shell_get_online -e_shell_set_online -e_shell_get_preferences_window -e_shell_event -EShellQuitReason -e_shell_quit -e_shell_cancel_quit -e_shell_adapt_window_size -e_shell_set_startup_view -e_shell_get_startup_view -E_SHELL_MIGRATE_ERROR -EShellMigrateError -e_shell_migrate_attempt -e_shell_detect_meego - -E_SHELL -E_IS_SHELL -E_TYPE_SHELL -E_SHELL_CLASS -E_IS_SHELL_CLASS -E_SHELL_GET_CLASS -EShellClass -e_shell_get_type -e_shell_quit_reason_get_type - -EShellPrivate -e_shell_migrate_error_quark -
- -
-e-shell-backend -EShellBackend -EShellBackend -e_shell_backend_compare -e_shell_backend_get_config_dir -e_shell_backend_get_data_dir -e_shell_backend_get_shell -e_shell_backend_add_activity -e_shell_backend_cancel_all -e_shell_backend_is_busy -e_shell_backend_start -e_shell_backend_is_started -e_shell_backend_migrate - -E_SHELL_BACKEND -E_IS_SHELL_BACKEND -E_TYPE_SHELL_BACKEND -E_SHELL_BACKEND_CLASS -E_IS_SHELL_BACKEND_CLASS -E_SHELL_BACKEND_GET_CLASS -EShellBackendClass -e_shell_backend_get_type - -EShellBackendPrivate -
- -
-e-shell-content -EShellContent -EShellContent -e_shell_content_new -e_shell_content_set_searchbar -e_shell_content_check_state -e_shell_content_focus_search_results -e_shell_content_get_alert_bar -e_shell_content_get_shell_view -e_shell_content_get_view_id -e_shell_content_set_view_id -e_shell_content_run_advanced_search_dialog -e_shell_content_run_edit_searches_dialog -e_shell_content_run_save_search_dialog - -E_SHELL_CONTENT -E_IS_SHELL_CONTENT -E_TYPE_SHELL_CONTENT -E_SHELL_CONTENT_CLASS -E_IS_SHELL_CONTENT_CLASS -E_SHELL_CONTENT_GET_CLASS -EShellContentClass -e_shell_content_get_type - -EShellContentPrivate -
- -
-e-shell-searchbar -EShellSearchbar -EShellSearchbar -e_shell_searchbar_new -e_shell_searchbar_get_shell_view -e_shell_searchbar_get_express_mode -e_shell_searchbar_set_express_mode -e_shell_searchbar_get_filter_combo_box -e_shell_searchbar_get_filter_visible -e_shell_searchbar_set_filter_visible -e_shell_searchbar_get_labels_visible -e_shell_searchbar_set_labels_visible -e_shell_searchbar_get_search_hint -e_shell_searchbar_set_search_hint -e_shell_searchbar_get_search_option -e_shell_searchbar_set_search_option -e_shell_searchbar_get_search_text -e_shell_searchbar_set_search_text -e_shell_searchbar_get_search_visible -e_shell_searchbar_set_search_visible -e_shell_searchbar_get_scope_combo_box -e_shell_searchbar_get_scope_visible -e_shell_searchbar_set_scope_visible -e_shell_searchbar_set_state_dirty -e_shell_searchbar_get_state_group -e_shell_searchbar_set_state_group -e_shell_searchbar_load_state -e_shell_searchbar_save_state - -E_SHELL_SEARCHBAR -E_IS_SHELL_SEARCHBAR -E_TYPE_SHELL_SEARCHBAR -E_SHELL_SEARCHBAR_CLASS -E_IS_SHELL_SEARCHBAR_CLASS -E_SHELL_SEARCHBAR_GET_CLASS -EShellSearchbarClass -e_shell_searchbar_get_type - -EShellSearchbarPrivate -
- -
-e-shell-settings -EShellSettings -EShellSettings -e_shell_settings_install_property -e_shell_settings_install_property_for_key -e_shell_settings_enable_debug -e_shell_settings_get_boolean -e_shell_settings_set_boolean -e_shell_settings_get_int -e_shell_settings_set_int -e_shell_settings_get_string -e_shell_settings_set_string -e_shell_settings_get_object -e_shell_settings_set_object -e_shell_settings_get_pointer -e_shell_settings_set_pointer - -E_SHELL_SETTINGS -E_IS_SHELL_SETTINGS -E_TYPE_SHELL_SETTINGS -E_SHELL_SETTINGS_CLASS -E_IS_SHELL_SETTINGS_CLASS -E_SHELL_SETTINGS_GET_CLASS -EShellSettingsClass -e_shell_settings_get_type - -EShellSettingsPrivate -
- -
-e-shell-sidebar -EShellSidebar -EShellSidebar -e_shell_sidebar_new -e_shell_sidebar_check_state -e_shell_sidebar_get_shell_view -e_shell_sidebar_get_icon_name -e_shell_sidebar_set_icon_name -e_shell_sidebar_get_primary_text -e_shell_sidebar_set_primary_text -e_shell_sidebar_get_secondary_text -e_shell_sidebar_set_secondary_text - -E_SHELL_SIDEBAR -E_IS_SHELL_SIDEBAR -E_TYPE_SHELL_SIDEBAR -E_SHELL_SIDEBAR_CLASS -E_IS_SHELL_SIDEBAR_CLASS -E_SHELL_SIDEBAR_GET_CLASS -EShellSidebarClass -e_shell_sidebar_get_type - -EShellSidebarPrivate -
- -
-e-shell-switcher -EShellSwitcher -EShellSwitcher -e_shell_switcher_new -e_shell_switcher_add_action -e_shell_switcher_get_style -e_shell_switcher_set_style -e_shell_switcher_unset_style -e_shell_switcher_get_visible -e_shell_switcher_set_visible - -E_SHELL_SWITCHER -E_IS_SHELL_SWITCHER -E_TYPE_SHELL_SWITCHER -E_SHELL_SWITCHER_CLASS -E_IS_SHELL_SWITCHER_CLASS -E_SHELL_SWITCHER_GET_CLASS -EShellSwitcherClass -e_shell_switcher_get_type - -EShellSwitcherPrivate -E_SHELL_SWITCHER_DEFAULT_TOOLBAR_STYLE -
- -
-e-shell-taskbar -EShellTaskbar -EShellTaskbar -e_shell_taskbar_new -e_shell_taskbar_get_shell_view -e_shell_taskbar_get_message -e_shell_taskbar_set_message -e_shell_taskbar_unset_message -e_shell_taskbar_get_activity_count - -E_SHELL_TASKBAR -E_IS_SHELL_TASKBAR -E_TYPE_SHELL_TASKBAR -E_SHELL_TASKBAR_CLASS -E_IS_SHELL_TASKBAR_CLASS -E_SHELL_TASKBAR_GET_CLASS -EShellTaskbarClass -e_shell_taskbar_get_type - -EShellTaskbarPrivate -
- -
-e-shell-utils -Shell Utilities -e_shell_configure_ui_manager -e_shell_run_open_dialog -e_shell_run_save_dialog -e_shell_utils_import_uris -e_shell_hide_widgets_for_express_mode -
- -
-e-shell-view -EShellView -EShellView -EShellViewClass -e_shell_view_get_name -e_shell_view_get_action -e_shell_view_get_title -e_shell_view_set_title -e_shell_view_get_view_id -e_shell_view_set_view_id -e_shell_view_is_active -e_shell_view_get_page_num -e_shell_view_set_page_num -e_shell_view_get_searchbar -e_shell_view_get_search_name -e_shell_view_get_search_rule -e_shell_view_set_search_rule -e_shell_view_get_search_query -e_shell_view_get_size_group -e_shell_view_get_shell_backend -e_shell_view_get_shell_content -e_shell_view_get_shell_sidebar -e_shell_view_get_shell_taskbar -e_shell_view_get_shell_window -e_shell_view_get_state_key_file -e_shell_view_set_state_dirty -e_shell_view_clear_search -e_shell_view_custom_search -e_shell_view_execute_search -e_shell_view_block_execute_search -e_shell_view_unblock_execute_search -e_shell_view_update_actions -e_shell_view_block_update_actions -e_shell_view_unblock_update_actions -e_shell_view_show_popup_menu -e_shell_view_new_view_instance - -E_SHELL_VIEW -E_IS_SHELL_VIEW -E_TYPE_SHELL_VIEW -E_SHELL_VIEW_CLASS -E_IS_SHELL_VIEW_CLASS -E_SHELL_VIEW_GET_CLASS -e_shell_view_get_type - -EShellViewPrivate -
- -
-e-shell-window -EShellWindow -EShellWindow -e_shell_window_new -e_shell_window_get_shell -e_shell_window_get_shell_view -e_shell_window_peek_shell_view -e_shell_window_get_shell_view_action -e_shell_window_get_alert_bar -e_shell_window_get_focus_tracker -e_shell_window_get_ui_manager -e_shell_window_get_action -e_shell_window_get_action_group -e_shell_window_get_managed_widget -e_shell_window_get_active_view -e_shell_window_set_active_view -e_shell_window_get_safe_mode -e_shell_window_set_safe_mode -e_shell_window_add_action_group -e_shell_window_get_sidebar_visible -e_shell_window_set_sidebar_visible -e_shell_window_get_switcher_visible -e_shell_window_set_switcher_visible -e_shell_window_get_taskbar_visible -e_shell_window_set_taskbar_visible -e_shell_window_get_toolbar_visible -e_shell_window_set_toolbar_visible -e_shell_window_register_new_item_actions -e_shell_window_register_new_source_actions -e_shell_window_get_menu_bar_box - -E_SHELL_WINDOW -E_IS_SHELL_WINDOW -E_TYPE_SHELL_WINDOW -E_SHELL_WINDOW_CLASS -E_IS_SHELL_WINDOW_CLASS -E_SHELL_WINDOW_GET_CLASS -EShellWindowClass -e_shell_window_get_type - -EShellWindowPrivate -E_SHELL_WINDOW_ACTION -E_SHELL_WINDOW_ACTION_GROUP -
- -
-e-action-combo-box -EActionComboBox -e_action_combo_box_new -e_action_combo_box_new_with_action -e_action_combo_box_get_action -e_action_combo_box_set_action -e_action_combo_box_get_current_value -e_action_combo_box_set_current_value -e_action_combo_box_add_separator_before -e_action_combo_box_add_separator_after - -E_ACTION_COMBO_BOX -E_IS_ACTION_COMBO_BOX -E_TYPE_ACTION_COMBO_BOX -E_ACTION_COMBO_BOX_CLASS -E_IS_ACTION_COMBO_BOX_CLASS -E_ACTION_COMBO_BOX_GET_CLASS -EActionComboBoxClass -e_action_combo_box_get_type - -EActionComboBoxPrivate -
- -
-e-activity -EActivity -e_activity_new -e_activity_describe -e_activity_get_alert_sink -e_activity_set_alert_sink -e_activity_get_cancellable -e_activity_set_cancellable -e_activity_get_icon_name -e_activity_set_icon_name -e_activity_get_percent -e_activity_set_percent -e_activity_get_state -e_activity_set_state -e_activity_get_text -e_activity_set_text - -E_ACTIVITY -E_IS_ACTIVITY -E_TYPE_ACTIVITY -E_ACTIVITY_CLASS -E_IS_ACTIVITY_CLASS -E_ACTIVITY_GET_CLASS -EActivityClass -e_activity_get_type - -EActivityPrivate -
- -
-e-activity-proxy -EActivityProxy -e_activity_proxy_new -e_activity_proxy_get_activity - -E_ACTIVITY_PROXY -E_IS_ACTIVITY_PROXY -E_TYPE_ACTIVITY_PROXY -E_ACTIVITY_PROXY_CLASS -E_IS_ACTIVITY_PROXY_CLASS -E_ACTIVITY_PROXY_GET_CLASS -EActivityProxyClass -e_activity_proxy_get_type - -EActivityProxyPrivate -
- -
-e-alert -User Alert Handling -E_ALERT_ASK_FILE_EXISTS_OVERWRITE -E_ALERT_NO_SAVE_FILE -E_ALERT_NO_LOAD_FILE -EAlert -e_alert_new -e_alert_run_dialog -e_alert_run_dialog_for_args -
- -
-e-attachment -EAttachment -e_attachment_new -e_attachment_new_for_path -e_attachment_new_for_uri -e_attachment_new_for_message -e_attachment_add_to_multipart -e_attachment_cancel -e_attachment_get_can_show -e_attachment_set_can_show -e_attachment_get_disposition -e_attachment_set_disposition -e_attachment_get_file -e_attachment_set_file -e_attachment_get_file_info -e_attachment_set_file_info -e_attachment_get_icon -e_attachment_get_loading -e_attachment_get_mime_part -e_attachment_set_mime_part -e_attachment_get_percent -e_attachment_get_reference -e_attachment_set_reference -e_attachment_get_saving -e_attachment_get_shown -e_attachment_set_shown -e_attachment_get_encrypted -e_attachment_set_encrypted -e_attachment_get_signed -e_attachment_set_signed -e_attachment_get_description -e_attachment_get_thumbnail_path -e_attachment_is_rfc822 -e_attachment_list_apps -e_attachment_load_async -e_attachment_load_finish -e_attachment_open_async -e_attachment_open_finish -e_attachment_save_async -e_attachment_save_finish -e_attachment_load_handle_error -e_attachment_open_handle_error -e_attachment_save_handle_error - -E_ATTACHMENT -E_IS_ATTACHMENT -E_TYPE_ATTACHMENT -E_ATTACHMENT_CLASS -E_IS_ATTACHMENT_CLASS -E_ATTACHMENT_GET_CLASS -EAttachmentClass -e_attachment_get_type - -EAttachmentPrivate -
- -
-e-attachment-button -EAttachmentButton -e_attachment_button_new -e_attachment_button_get_view -e_attachment_button_get_attachment -e_attachment_button_set_attachment -e_attachment_button_get_expandable -e_attachment_button_set_expandable -e_attachment_button_get_expanded -e_attachment_button_set_expanded - -E_ATTACHMENT_BUTTON -E_IS_ATTACHMENT_BUTTON -E_TYPE_ATTACHMENT_BUTTON -E_ATTACHMENT_BUTTON_CLASS -E_IS_ATTACHMENT_BUTTON_CLASS -E_ATTACHMENT_BUTTON_GET_CLASS -EAttachmentButtonClass -e_attachment_button_get_type - -EAttachmentButtonPrivate -
- -
-e-attachment-dialog -EAttachmentDialog -e_attachment_dialog_new -e_attachment_dialog_get_attachment -e_attachment_dialog_set_attachment - -E_ATTACHMENT_DIALOG -E_IS_ATTACHMENT_DIALOG -E_TYPE_ATTACHMENT_DIALOG -E_ATTACHMENT_DIALOG_CLASS -E_IS_ATTACHMENT_DIALOG_CLASS -E_ATTACHMENT_DIALOG_GET_CLASS -EAttachmentDialogClass -e_attachment_dialog_get_type - -EAttachmentDialogPrivate -
- -
-e-attachment-handler -EAttachmentHandler -e_attachment_handler_get_view -e_attachment_handler_get_drag_actions -e_attachment_handler_get_target_table - -E_ATTACHMENT_HANDLER -E_IS_ATTACHMENT_HANDLER -E_TYPE_ATTACHMENT_HANDLER -E_ATTACHMENT_HANDLER_CLASS -E_IS_ATTACHMENT_HANDLER_CLASS -E_ATTACHMENT_HANDLER_GET_CLASS -EAttachmentHandlerClass -e_attachment_handler_get_type -e_attachment_handler_image_get_type -e_attachment_handler_sendto_get_type - -EAttachmentHandlerPrivate -
- -
-e-attachment-icon-view -EAttachmentIconView -e_attachment_icon_view_new -e_attachment_icon_view_set_default_icon_size - -E_ATTACHMENT_ICON_VIEW -E_IS_ATTACHMENT_ICON_VIEW -E_TYPE_ATTACHMENT_ICON_VIEW -E_ATTACHMENT_ICON_VIEW_CLASS -E_IS_ATTACHMENT_ICON_VIEW_CLASS -E_ATTACHMENT_ICON_VIEW_GET_CLASS -EAttachmentIconViewClass -e_attachment_icon_view_get_type - -EAttachmentIconViewPrivate -
- -
-e-attachment-paned -EAttachmentPaned -e_attachment_paned_new -e_attachment_paned_get_content_area -e_attachment_paned_get_active_view -e_attachment_paned_set_active_view -e_attachment_paned_get_expanded -e_attachment_paned_set_expanded -e_attachment_paned_drag_data_received -e_attachment_paned_get_controls_container -e_attachment_paned_get_view_combo -e_attachment_paned_set_default_height - -E_ATTACHMENT_PANED -E_IS_ATTACHMENT_PANED -E_TYPE_ATTACHMENT_PANED -E_ATTACHMENT_PANED_CLASS -E_IS_ATTACHMENT_PANED_CLASS -E_ATTACHMENT_PANED_GET_CLASS -EAttachmentPanedClass -e_attachment_paned_get_type - -EAttachmentPanedPrivate -
- -
-e-attachment-store -EAttachmentStore -e_attachment_store_new -e_attachment_store_add_attachment -e_attachment_store_remove_attachment -e_attachment_store_add_to_multipart -e_attachment_store_get_attachments -e_attachment_store_get_num_attachments -e_attachment_store_get_num_loading -e_attachment_store_get_total_size -e_attachment_store_run_load_dialog -e_attachment_store_run_save_dialog -e_attachment_store_get_uris_async -e_attachment_store_get_uris_finish -e_attachment_store_load_async -e_attachment_store_load_finish -e_attachment_store_save_async -e_attachment_store_save_finish - -E_ATTACHMENT_STORE -E_IS_ATTACHMENT_STORE -E_TYPE_ATTACHMENT_STORE -E_ATTACHMENT_STORE_CLASS -E_IS_ATTACHMENT_STORE_CLASS -E_ATTACHMENT_STORE_GET_CLASS -EAttachmentStoreClass -e_attachment_store_get_type - -EAttachmentStorePrivate -
- -
-e-attachment-tree-view -EAttachmentTreeView -e_attachment_tree_view_new - -E_ATTACHMENT_TREE_VIEW -E_IS_ATTACHMENT_TREE_VIEW -E_TYPE_ATTACHMENT_TREE_VIEW -E_ATTACHMENT_TREE_VIEW_CLASS -E_IS_ATTACHMENT_TREE_VIEW_CLASS -E_ATTACHMENT_TREE_VIEW_GET_CLASS -EAttachmentTreeViewClass -e_attachment_tree_view_get_type - -EAttachmentTreeViewPrivate -
- -
-e-attachment-view -EAttachmentView -e_attachment_view_init -e_attachment_view_dispose -e_attachment_view_finalize -e_attachment_view_get_private -e_attachment_view_get_store -e_attachment_view_get_editable -e_attachment_view_set_editable -e_attachment_view_get_target_list -e_attachment_view_get_drag_actions -e_attachment_view_get_selected_attachments -e_attachment_view_open_path -e_attachment_view_remove_selected -e_attachment_view_button_press_event -e_attachment_view_button_release_event -e_attachment_view_motion_notify_event -e_attachment_view_key_press_event -e_attachment_view_get_path_at_pos -e_attachment_view_get_selected_paths -e_attachment_view_path_is_selected -e_attachment_view_select_path -e_attachment_view_unselect_path -e_attachment_view_select_all -e_attachment_view_unselect_all -e_attachment_view_sync_selection -e_attachment_view_drag_source_set -e_attachment_view_drag_source_unset -e_attachment_view_drag_begin -e_attachment_view_drag_end -e_attachment_view_drag_data_get -e_attachment_view_drag_dest_set -e_attachment_view_drag_dest_unset -e_attachment_view_drag_motion -e_attachment_view_drag_drop -e_attachment_view_drag_data_received -e_attachment_view_get_action -e_attachment_view_add_action_group -e_attachment_view_get_action_group -e_attachment_view_get_popup_menu -e_attachment_view_get_ui_manager -e_attachment_view_recent_action_new -e_attachment_view_show_popup_menu -e_attachment_view_update_actions - -E_ATTACHMENT_VIEW -E_IS_ATTACHMENT_VIEW -E_TYPE_ATTACHMENT_VIEW -E_ATTACHMENT_VIEW_IFACE -E_IS_ATTACHMENT_VIEW_IFACE -E_ATTACHMENT_VIEW_GET_IFACE -EAttachmentViewIface -e_attachment_view_get_type - -EAttachmentViewPrivate -
- -
-e-bit-array -Bit Arrays (Legacy) -EBitArray -e_bit_array_new -e_bit_array_value_at -e_bit_array_foreach -e_bit_array_selected_count -e_bit_array_select_all -e_bit_array_invert_selection -e_bit_array_bit_count -e_bit_array_change_one_row -e_bit_array_change_range -e_bit_array_select_single_row -e_bit_array_toggle_single_row -e_bit_array_insert -e_bit_array_delete -e_bit_array_delete_single_mode -e_bit_array_move_row - -E_BIT_ARRAY -E_IS_BIT_ARRAY -E_BIT_ARRAY_TYPE -E_BIT_ARRAY_CLASS -E_IS_BIT_ARRAY_CLASS -EBitArrayClass -e_bit_array_get_type -
- -
-e-categories-config -Categories -e_categories_config_get_icon_for -e_categories_config_open_dialog_for_entry -
- -
-e-datetime-format -Date and Time Formatting -DTFormatKind -e_datetime_format_add_setup_widget -e_datetime_format_format -e_datetime_format_format_tm -
- -
-e-dialog-utils -Dialog Utilities (Legacy) -e_notice -e_dialog_combo_box_set -e_dialog_combo_box_get -
- -
-e-html-utils -Text to HTML Conversion -E_TEXT_TO_HTML_PRE -E_TEXT_TO_HTML_CONVERT_NL -E_TEXT_TO_HTML_CONVERT_SPACES -E_TEXT_TO_HTML_CONVERT_URLS -E_TEXT_TO_HTML_MARK_CITATION -E_TEXT_TO_HTML_CONVERT_ADDRESSES -E_TEXT_TO_HTML_ESCAPE_8BIT -E_TEXT_TO_HTML_CITE -e_text_to_html_full -e_text_to_html -
- -
-e-icon-factory -Icon Utilities (Legacy) -e_icon_factory_get_icon_filename -e_icon_factory_get_icon -e_icon_factory_pixbuf_scale -e_icon_factory_create_thumbnail -
- -
-e-mail-account-manager -EMailAccountManager -e_mail_account_manager_new -e_mail_account_manager_add_account -e_mail_account_manager_edit_account -e_mail_account_manager_delete_account -e_mail_account_manager_get_registry - -E_MAIL_ACCOUNT_MANAGER -E_IS_MAIL_ACCOUNT_MANAGER -E_TYPE_MAIL_ACCOUNT_MANAGER -E_MAIL_ACCOUNT_MANAGER_CLASS -E_IS_MAIL_ACCOUNT_MANAGER_CLASS -E_MAIL_ACCOUNT_MANAGER_GET_CLASS -e_mail_account_manager_get_type - -EMailAccountManagerPrivate -
- -
-e-mail-account-tree-view -EMailAccountTreeView -e_mail_account_tree_view_new -e_mail_account_tree_view_refresh -e_mail_account_tree_view_enable_selected -e_mail_account_tree_view_disable_selected -e_mail_account_tree_view_get_registry -e_mail_account_tree_view_get_selected_source -e_mail_account_tree_view_set_selected_source - -E_MAIL_ACCOUNT_TREE_VIEW -E_IS_MAIL_ACCOUNT_TREE_VIEW -E_TYPE_MAIL_ACCOUNT_TREE_VIEW -E_MAIL_ACCOUNT_TREE_VIEW_CLASS -E_IS_MAIL_ACCOUNT_TREE_VIEW_CLASS -E_MAIL_ACCOUNT_TREE_VIEW_GET_CLASS -EMailAccountTreeViewClass -e_mail_account_tree_view_get_type - -EMailAccountTreeViewPrivate -
- -
-e-mail-identity-combo-box -EMailIdentityComboBox -e_mail_identity_combo_box_new -e_mail_identity_combo_box_refresh -e_mail_identity_combo_box_get_registry - -E_MAIL_IDENTITY_COMBO_BOX -E_IS_MAIL_IDENTITY_COMBO_BOX -E_TYPE_MAIL_IDENTITY_COMBO_BOX -E_MAIL_IDENTITY_COMBO_BOX_CLASS -E_IS_MAIL_IDENTITY_COMBO_BOX_CLASS -E_MAIL_IDENTITY_COMBO_BOX_GET_CLASS -EMailIdentityComboBoxClass -e_mail_identity_combo_box_get_type - -EMailIdentityComboBoxPrivate -
- -
-e-poolv -EPoolv -EPoolv -e_poolv_new -e_poolv_set -e_poolv_get -e_poolv_destroy -
- -
-e-popup-action -EPopupAction -e_popup_action_new -EPopupActionEntry -e_action_group_add_popup_actions - -E_POPUP_ACTION -E_IS_POPUP_ACTION -E_TYPE_POPUP_ACTION -E_POPUP_ACTION_CLASS -E_IS_POPUP_ACTION_CLASS -E_POPUP_ACTION_GET_CLASS -EPopupActionClass -e_popup_action_get_type - -EPopupActionPrivate -
- -
-e-print -Printing -e_print_operation_new -e_print_run_page_setup_dialog -
- -
-e-selection -Selections -e_target_list_add_calendar_targets -e_target_list_add_directory_targets -e_selection_data_set_calendar -e_selection_data_set_directory -e_selection_data_get_calendar -e_selection_data_get_directory -e_selection_data_targets_include_calendar -e_selection_data_targets_include_directory -e_targets_include_calendar -e_targets_include_directory -e_clipboard_set_calendar -e_clipboard_set_directory -e_clipboard_request_calendar -e_clipboard_request_directory -e_clipboard_wait_for_calendar -e_clipboard_wait_for_directory -e_clipboard_wait_is_calendar_available -e_clipboard_wait_is_directory_available -
- -
-EWebView -e_web_view_new -e_web_view_clear -e_web_view_load_string -e_web_view_get_animate -e_web_view_set_animate -e_web_view_get_caret_mode -e_web_view_set_caret_mode -e_web_view_get_copy_target_list -e_web_view_get_disable_printing -e_web_view_set_disable_printing -e_web_view_get_disable_save_to_disk -e_web_view_set_disable_save_to_disk -e_web_view_get_editable -e_web_view_set_editable -e_web_view_get_inline_spelling -e_web_view_set_inline_spelling -e_web_view_get_magic_links -e_web_view_set_magic_links -e_web_view_get_magic_smileys -e_web_view_set_magic_smileys -e_web_view_get_selected_uri -e_web_view_set_selected_uri -e_web_view_get_open_proxy -e_web_view_set_open_proxy -e_web_view_get_paste_target_list -e_web_view_get_print_proxy -e_web_view_set_print_proxy -e_web_view_get_save_as_proxy -e_web_view_set_save_as_proxy -e_web_view_get_action -e_web_view_get_action_group -e_web_view_extract_uri -e_web_view_copy_clipboard -e_web_view_cut_clipboard -e_web_view_is_selection_active -e_web_view_paste_clipboard -e_web_view_scroll_forward -e_web_view_scroll_backward -e_web_view_select_all -e_web_view_unselect_all -e_web_view_zoom_100 -e_web_view_zoom_in -e_web_view_zoom_out -e_web_view_get_ui_manager -e_web_view_get_popup_menu -e_web_view_show_popup_menu -e_web_view_status_message -e_web_view_stop_loading -e_web_view_update_actions - -E_WEB_VIEW -E_IS_WEB_VIEW -E_TYPE_WEB_VIEW -E_WEB_VIEW_CLASS -E_IS_WEB_VIEW_CLASS -E_WEB_VIEW_GET_CLASS -EWebViewClass -e_web_view_get_type - -EWebViewPrivate -
- -
-e-util -Miscellaneous Utilities -e_get_accels_filename -e_show_uri -e_display_help -e_lookup_action -e_lookup_action_group -e_action_compare_by_label -e_action_group_remove_all_actions -e_radio_action_get_current_action -e_categories_add_change_hook -e_str_without_underscores -e_str_compare -e_str_case_compare -e_collate_compare -e_int_compare -e_color_to_value -e_format_number -ESortCompareFunc -e_bsearch -e_strftime_fix_am_pm -e_utf8_strftime_fix_am_pm -e_get_month_name -e_get_weekday_name -e_flexible_strtod -e_ascii_dtostr -e_file_lock_create -e_file_lock_destroy -e_file_lock_exists -e_util_guess_mime_type -e_util_get_category_filter_options -e_binding_transform_color_to_string -e_binding_transform_string_to_color -e_binding_transform_source_to_uid -e_binding_transform_uid_to_source -e_charset_add_radio_actions -e_file_replace_contents_async -e_file_replace_contents_finish -e_mktemp -e_mkstemp -e_mkdtemp - -E_TYPE_CAMEL_OBJECT -e_camel_object_get_type -
- -
-e-xml-utils -Reading and Writing XML -e_xml_get_child_by_name_by_lang -e_xml_get_child_by_name_by_lang_list -e_xml_get_child_by_name_no_lang -e_xml_get_integer_prop_by_name -e_xml_get_integer_prop_by_name_with_default -e_xml_set_integer_prop_by_name -e_xml_get_uint_prop_by_name -e_xml_get_uint_prop_by_name_with_default -e_xml_set_uint_prop_by_name -e_xml_get_bool_prop_by_name -e_xml_get_bool_prop_by_name_with_default -e_xml_set_bool_prop_by_name -e_xml_get_double_prop_by_name -e_xml_get_double_prop_by_name_with_default -e_xml_set_double_prop_by_name -e_xml_get_string_prop_by_name -e_xml_get_string_prop_by_name_with_default -e_xml_set_string_prop_by_name -e_xml_get_translated_string_prop_by_name -
- -
-shell-actions -Shell Actions -E_SHELL_WINDOW_ACTION_ABOUT -E_SHELL_WINDOW_ACTION_CLOSE -E_SHELL_WINDOW_ACTION_CONTENTS -E_SHELL_WINDOW_ACTION_FORGET_PASSWORDS -E_SHELL_WINDOW_ACTION_GAL_CUSTOM_VIEW -E_SHELL_WINDOW_ACTION_GAL_DEFINE_VIEWS -E_SHELL_WINDOW_ACTION_GAL_SAVE_CUSTOM_VIEW -E_SHELL_WINDOW_ACTION_IMPORT -E_SHELL_WINDOW_ACTION_NEW_WINDOW -E_SHELL_WINDOW_ACTION_PAGE_SETUP -E_SHELL_WINDOW_ACTION_PREFERENCES -E_SHELL_WINDOW_ACTION_QUICK_REFERENCE -E_SHELL_WINDOW_ACTION_QUIT -E_SHELL_WINDOW_ACTION_SEARCH_ADVANCED -E_SHELL_WINDOW_ACTION_SEARCH_CLEAR -E_SHELL_WINDOW_ACTION_SEARCH_EDIT -E_SHELL_WINDOW_ACTION_SEARCH_OPTIONS -E_SHELL_WINDOW_ACTION_SEARCH_QUICK -E_SHELL_WINDOW_ACTION_SEARCH_SAVE -E_SHELL_WINDOW_ACTION_SHOW_SIDEBAR -E_SHELL_WINDOW_ACTION_SHOW_SWITCHER -E_SHELL_WINDOW_ACTION_SHOW_TASKBAR -E_SHELL_WINDOW_ACTION_SHOW_TOOLBAR -E_SHELL_WINDOW_ACTION_SUBMIT_BUG -E_SHELL_WINDOW_ACTION_SWITCHER_INITIAL -E_SHELL_WINDOW_ACTION_SWITCHER_STYLE_BOTH -E_SHELL_WINDOW_ACTION_SWITCHER_STYLE_ICONS -E_SHELL_WINDOW_ACTION_SWITCHER_STYLE_TEXT -E_SHELL_WINDOW_ACTION_SWITCHER_STYLE_USER -E_SHELL_WINDOW_ACTION_WORK_OFFLINE -E_SHELL_WINDOW_ACTION_WORK_ONLINE -
- -
-action-groups -Action Groups -E_SHELL_WINDOW_ACTION_GROUP_SHELL -E_SHELL_WINDOW_ACTION_GROUP_SWITCHER -E_SHELL_WINDOW_ACTION_GROUP_NEW_ITEM -E_SHELL_WINDOW_ACTION_GROUP_NEW_SOURCE -E_SHELL_WINDOW_ACTION_GROUP_CUSTOM_RULES -E_SHELL_WINDOW_ACTION_GROUP_GAL_VIEW -E_SHELL_WINDOW_ACTION_GROUP_LOCKDOWN_APPLICATION_HANDLERS -E_SHELL_WINDOW_ACTION_GROUP_LOCKDOWN_PRINTING -E_SHELL_WINDOW_ACTION_GROUP_LOCKDOWN_PRINT_SETUP -E_SHELL_WINDOW_ACTION_GROUP_LOCKDOWN_SAVE_TO_DISK -
diff --git a/doc/reference/shell/eshell.types b/doc/reference/shell/eshell.types deleted file mode 100644 index 0832576163..0000000000 --- a/doc/reference/shell/eshell.types +++ /dev/null @@ -1,10 +0,0 @@ -e_bit_array_get_type -e_shell_get_type -e_shell_backend_get_type -e_shell_content_get_type -e_shell_searchbar_get_type -e_shell_sidebar_get_type -e_shell_switcher_get_type -e_shell_taskbar_get_type -e_shell_view_get_type -e_shell_window_get_type diff --git a/doc/reference/shell/tmpl/e-mail-account-manager.sgml b/doc/reference/shell/tmpl/e-mail-account-manager.sgml deleted file mode 100644 index 49fe04bdf2..0000000000 --- a/doc/reference/shell/tmpl/e-mail-account-manager.sgml +++ /dev/null @@ -1,72 +0,0 @@ - -e-mail-account-manager - - - - - - - - - - - - - - - - - - - - - - - - - -@parent: -@priv: - - - - - - -@registry: -@Returns: - - - - - - - -@manager: - - - - - - - -@manager: - - - - - - - -@manager: - - - - - - - -@manager: -@Returns: - - diff --git a/doc/reference/shell/tmpl/e-mail-account-tree-view.sgml b/doc/reference/shell/tmpl/e-mail-account-tree-view.sgml deleted file mode 100644 index eaee2f12c5..0000000000 --- a/doc/reference/shell/tmpl/e-mail-account-tree-view.sgml +++ /dev/null @@ -1,90 +0,0 @@ - -e-mail-account-tree-view - - - - - - - - - - - - - - - - - - - - - - - - - -@parent: -@priv: - - - - - - -@registry: -@Returns: - - - - - - - -@tree_view: - - - - - - - -@tree_view: - - - - - - - -@tree_view: - - - - - - - -@tree_view: -@Returns: - - - - - - - -@tree_view: -@Returns: - - - - - - - -@tree_view: -@source: - - diff --git a/doc/reference/shell/tmpl/e-mail-identity-combo-box.sgml b/doc/reference/shell/tmpl/e-mail-identity-combo-box.sgml deleted file mode 100644 index fec8130b21..0000000000 --- a/doc/reference/shell/tmpl/e-mail-identity-combo-box.sgml +++ /dev/null @@ -1,56 +0,0 @@ - -e-mail-identity-combo-box - - - - - - - - - - - - - - - - - - - - - - - - - -@parent: -@priv: - - - - - - -@registry: -@Returns: - - - - - - - -@combo_box: - - - - - - - -@combo_box: -@Returns: - - 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 +#endif + +#include "e-action-combo-box.h" + +#include + +#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 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 + +/* 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 + * + */ + +#ifdef HAVE_CONFIG_H +#include +#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 + * + */ + +#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION) +#error "Only should be included directly." +#endif + +#ifndef E_ACTIVITY_BAR_H +#define E_ACTIVITY_BAR_H + +#include +#include + +/* 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 + * + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "e-activity-proxy.h" + +#include + +#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 + * + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION) +#error "Only should be included directly." +#endif + +#ifndef E_ACTIVITY_PROXY_H +#define E_ACTIVITY_PROXY_H + +#include +#include + +/* 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 #include -#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 should be included directly." +#endif + #ifndef E_ACTIVITY_H #define E_ACTIVITY_H #include -#include + +#include #include /* 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 + * + */ + +#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 + * + */ + +#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION) +#error "Only should be included directly." +#endif + +#ifndef E_ALARM_SELECTOR_H +#define E_ALARM_SELECTOR_H + +#include + +/* 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 + * + */ + +#include "e-alert-bar.h" + +#include +#include + +#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 ( + "%s", 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 ( + "%s", 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 + * + */ + +#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION) +#error "Only should be included directly." +#endif + +#ifndef E_ALERT_BAR_H +#define E_ALERT_BAR_H + +#include + +#include + +/* 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 + * + * + * Authors: + * Michael Zucchi + * Jonathon Jongsma + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * Copyright (C) 2009 Intel Corporation + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#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 + * + * + * Authors: + * Michael Zucchi + * Jonathon Jongsma + * + * 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 + +#include + +/* 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 + * + */ + +/** + * 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 +#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 + * + */ + +#ifndef E_ALERT_SINK_H +#define E_ALERT_SINK_H + +#include + +#include + +/* 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 + * + * + * Authors: + * Michael Zucchi + * Jonathon Jongsma + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * Copyright (C) 2009 Intel Corporation + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include + +#include +#include + +#include +#include + +#include + +#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: + * + * + * Primary error text.? + * Secondary error text.? + *
Undefined + + + High + + + Standard + + + Low + + + + + + + + + + Normal + + + Proprietary + + + Confidential + + + Secret + + + Top Secret + + + For Your Eyes Only + + + + + + + + + + None + + + Mail Receipt + + + + + + + + + + None + + + Mail Receipt + + + + + + + + + + None + + + Mail Receipt + + + + + + + + + + None + + + Mail Receipt + + + + + + + True + True + Send Options + False + True + mouse + dialog + + + True + True + + + True + True + + + True + 12 + 4 + 4 + 12 + 12 + + + True + 0 + + + 2 + 4 + 1 + 2 + + + + + + True + 0 + none + + + True + 12 + 2 + 3 + 12 + 12 + + + R_eply requested + True + True + False + True + True + + + 3 + GTK_FILL + + + + + + True + 0 + 12 + + + True + 2 + 3 + 12 + 12 + + + Wi_thin + True + True + False + True + True + + + 1 + 2 + GTK_FILL + + + + + + True + True + adjustment1 + 1 + + + 1 + 2 + 1 + 2 + + + + + + True + 0 + days + + + 2 + 3 + 1 + 2 + GTK_FILL + + + + + + _When convenient + True + True + False + True + True + reply-within + + + 3 + GTK_FILL + + + + + + + + 3 + 1 + 2 + + + + + + + + + True + Replies + + + + + + + + 4 + 2 + 3 + GTK_FILL + + + + + + True + 0 + none + + + True + 12 + 4 + 2 + + + _Delay message delivery + True + True + False + True + True + + + GTK_FILL + + + + + + True + 0 + + + 1 + 2 + + + + + + True + 12 + + + True + 12 + 12 + + + True + 0 + _After + True + expire-after + + + False + False + 0 + + + + + True + True + adjustment2 + 1 + + + False + False + 1 + + + + + True + 0 + days + + + False + False + 2 + + + + + + + 2 + 3 + 4 + GTK_EXPAND + + + + + _Set expiration date + True + True + False + True + True + + + 2 + 3 + GTK_FILL + + + + + + True + 0 + + + 1 + 2 + 2 + 3 + GTK_FILL + + + + + + True + 12 + + + True + True + 12 + 12 + + + True + 0 + _Until + True + until-date + + + False + False + 0 + + + + + True + + + False + False + 1 + + + + + + + 2 + 1 + 2 + + + + + + + + True + Delivery Options + + + + + + + + 4 + 3 + 4 + GTK_FILL + + + + + + True + model1 + + + + 0 + + + + + 1 + 2 + 1 + 2 + GTK_FILL + + + + + True + 1 + _Priority: + True + combo-priority + + + 1 + 2 + + + + + + True + 1 + _Classification: + True + security-combo + + + GTK_FILL + + + + + + True + model2 + + + + 0 + + + + + 1 + 2 + GTK_FILL + GTK_FILL + + + + + + + + + + + + + True + Gene_ral Options + True + + + False + + + + + True + 12 + 12 + + + True + 0 + none + + + True + 12 + + + True + 12 + 12 + + + Creat_e a sent item to track information + True + True + False + True + True + + + False + False + 0 + + + + + True + 19 + + + _Delivered + True + True + False + True + True + + + + + False + False + 1 + + + + + True + 19 + + + Deli_vered and opened + True + True + False + True + True + delivered + + + + + False + False + 2 + + + + + True + 19 + + + _All information + True + True + False + True + True + delivered + + + + + False + False + 3 + + + + + True + 19 + + + A_uto-delete sent item + True + True + False + True + True + + + + + False + False + 4 + + + + + + + + + True + Status Tracking + True + + + + + + + + False + False + 0 + + + + + True + 0 + none + + + True + 12 + + + True + 12 + 4 + 2 + 12 + 12 + + + True + 1 + _When opened: + True + open-combo + + + GTK_FILL + + + + + + True + 1 + When decli_ned: + True + delete-combo + + + 1 + 2 + GTK_FILL + + + + + + True + 0 + When co_mpleted: + True + complete-combo + + + 3 + 4 + GTK_FILL + + + + + + True + 1 + When acce_pted: + True + accept-combo + + + 2 + 3 + GTK_FILL + + + + + + True + model3 + + + + 0 + + + + + 1 + 2 + GTK_FILL + + + + + True + model4 + + + + 0 + + + + + 1 + 2 + 1 + 2 + GTK_FILL + + + + + True + model5 + + + + 0 + + + + + 1 + 2 + 2 + 3 + GTK_FILL + + + + + True + model6 + + + + 0 + + + + + 1 + 2 + 3 + 4 + + + + + + + + + True + Return Notification + True + + + + + + + + False + False + 1 + + + + + + + True + Sta_tus Tracking + True + + + 1 + False + + + + + 2 + + + + + True + end + + + gtk-help + True + True + True + False + True + + + False + False + 0 + + + + + gtk-cancel + True + True + True + False + True + + + False + False + 1 + + + + + gtk-ok + True + True + True + False + True + + + False + False + 2 + + + + + False + end + 0 + + + + + + helpbutton1 + cancelbutton1 + okbutton1 + + + 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 #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 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 #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 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 +#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 should be included directly." +#endif + +#ifndef E_SOURCE_COMBO_BOX_H +#define E_SOURCE_COMBO_BOX_H + +#include +#include + +#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 + * + */ + +#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 + * + */ + +#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION) +#error "Only should be included directly." +#endif + +#ifndef E_SOURCE_CONFIG_BACKEND_H +#define E_SOURCE_CONFIG_BACKEND_H + +#include + +#include + +/* 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 + * + */ + +#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 + * + */ + +#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION) +#error "Only should be included directly." +#endif + +#ifndef E_SOURCE_CONFIG_DIALOG_H +#define E_SOURCE_CONFIG_DIALOG_H + +#include + +/* 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 + * + */ + +#include "e-source-config.h" + +#include +#include + +#include + +#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 + * + */ + +#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION) +#error "Only should be included directly." +#endif + +#ifndef E_SOURCE_CONFIG_H +#define E_SOURCE_CONFIG_H + +#include +#include + +/* 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 + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#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 ("%s", _("_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 + */ + +#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION) +#error "Only should be included directly." +#endif + +#ifndef E_SOURCE_SELECTOR_DIALOG_H +#define E_SOURCE_SELECTOR_DIALOG_H + +#include +#include + +/* 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 + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#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 + */ + +#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION) +#error "Only should be included directly." +#endif + +#ifndef E_SOURCE_SELECTOR_H +#define E_SOURCE_SELECTOR_H + +#include +#include + +/* 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 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 #include -#include +#include 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 + * + */ + +/* This code is based on libsexy's SexySpellEntry */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include + +#include +#include + +#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 +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 + * + */ + +#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION) +#error "Only should be included directly." +#endif + +#ifndef E_SPELL_ENTRY_H +#define E_SPELL_ENTRY_H + +#include + +#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 +#include #include -#include - #include #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 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 + * + * + * Authors: + * Chris Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "e-table-click-to-add.h" + +#include +#include +#include +#include +#include + +#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 + * + * + * Authors: + * Chris Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION) +#error "Only should be included directly." +#endif + +#ifndef _E_TABLE_CLICK_TO_ADD_H_ +#define _E_TABLE_CLICK_TO_ADD_H_ + +#include +#include + +#include +#include +#include +#include + +/* 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 + * + * + * Authors: + * Chris Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION) +#error "Only should be included directly." +#endif + +#ifndef _E_TABLE_COL_DND_H_ +#define _E_TABLE_COL_DND_H_ + +#include + +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 + * + * + * Authors: + * Miguel de Icaza + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#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 + * + * + * Authors: + * Miguel de Icaza + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION) +#error "Only should be included directly." +#endif + +#ifndef E_TABLE_COL_H +#define E_TABLE_COL_H + +#include + +#include + +/* 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 + * + * + * Authors: + * Chris Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "e-table-column-specification.h" + +#include + +#include +#include + +#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 + * + * + * Authors: + * Chris Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION) +#error "Only should be included directly." +#endif + +#ifndef _E_TABLE_COLUMN_SPECIFICATION_H_ +#define _E_TABLE_COLUMN_SPECIFICATION_H_ + +#include +#include + +/* 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 + * + * + * Authors: + * Chris Lahey + * Miguel de Icaza + * + * 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 +#endif + +#include "e-table-config.h" + +#include +#include + +#include +#include + +#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 = +"" +"" +" " +"" +"" +""; + +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 + * + * + * Authors: + * Chris Lahey + * Miguel de Icaza + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION) +#error "Only should be included directly." +#endif + +#ifndef _E_TABLE_CONFIG_H_ +#define _E_TABLE_CONFIG_H_ + +#include + +#include +#include +#include +#include +#include + +/* 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 @@ + + + + + + Show Fields + True + dialog + + + True + vertical + 8 + + + True + vertical + 6 + + + True + 5 + 6 + 6 + True + + + True + 0 + A_vailable Fields: + True + + + 2 + GTK_FILL + + + + + True + 0 + _Show these fields in order: + True + + + 3 + 5 + GTK_FILL + + + + + + + + False + 0 + + + + + True + 5 + 6 + 6 + True + + + True + vertical + 6 + + + True + True + automatic + automatic + in + + + + + + 0 + + + + + 2 + + + + + True + vertical + 6 + + + True + True + automatic + automatic + in + + + + + + 0 + + + + + True + 6 + True + + + True + True + True + + + True + 0 + 0 + + + True + 2 + + + True + gtk-go-up + + + False + False + 0 + + + + + True + Move _Up + True + + + False + False + 1 + + + + + + + + + False + 0 + + + + + True + True + True + + + True + 0 + 0 + + + True + 2 + + + True + gtk-go-down + + + False + False + 0 + + + + + True + Move _Down + True + + + False + False + 1 + + + + + + + + + False + 1 + + + + + False + 1 + + + + + 3 + 5 + + + + + True + vertical + 6 + + + True + True + False + + + True + 0.69999998807907104 + 0 + 0 + + + True + 2 + + + True + _Add + True + + + False + False + 0 + + + + + True + gtk-go-forward + + + False + False + 1 + + + + + + + + + False + False + 0 + + + + + True + True + False + + + True + 0 + 0 + + + True + 2 + + + True + gtk-go-back + + + False + False + 0 + + + + + True + _Remove + True + + + False + False + 1 + + + + + + + + + False + False + 1 + + + + + + + + 2 + 3 + + + + + 1 + + + + + 2 + + + + + True + end + + + gtk-cancel + True + True + True + False + True + + + False + False + 0 + + + + + gtk-ok + True + True + True + False + True + + + False + False + 1 + + + + + False + end + 0 + + + + + + button22 + button20 + + + + Group + True + dialog + + + True + vertical + 8 + + + True + vertical + + + True + 6 + + + True + 0 + + + True + 6 + + + True + vertical + + + _Show field in View + True + True + False + True + True + + + False + False + 0 + + + + + 0 + + + + + True + vertical + + + Ascending + True + True + False + True + True + + + False + False + 0 + + + + + Descending + True + True + False + True + True + radiobutton-ascending-group-1 + + + False + False + 1 + + + + + False + 1 + + + + + + + True + Group Items By + + + + + 0 + + + + + True + center + + + False + False + 1 + + + + + True + center + + + False + False + 2 + + + + + True + center + + + False + False + 3 + + + + + 0 + + + + + True + 6 + + + True + center + + + False + False + 0 + + + + + True + 0 + + + True + 6 + + + True + vertical + + + Show _field in View + True + True + False + True + True + + + False + False + 0 + + + + + 0 + + + + + True + vertical + + + Ascending + True + True + False + True + True + + + False + False + 0 + + + + + Descending + True + True + False + True + True + radiobutton-ascending-group-2 + + + False + False + 1 + + + + + False + 1 + + + + + + + True + Then By + + + + + 1 + + + + + True + center + + + False + False + 2 + + + + + True + center + + + False + False + 3 + + + + + 1 + + + + + True + 6 + + + True + center + + + False + False + 0 + + + + + True + center + + + False + False + 1 + + + + + True + 0 + + + True + 6 + + + True + vertical + + + Show field i_n View + True + True + False + True + True + + + False + False + 0 + + + + + 0 + + + + + True + vertical + + + Ascending + True + True + False + True + True + + + False + False + 0 + + + + + Descending + True + True + False + True + True + radiobutton-ascending-group-3 + + + False + False + 1 + + + + + False + 1 + + + + + + + True + Then By + + + + + 2 + + + + + True + center + + + False + False + 3 + + + + + 2 + + + + + True + 6 + + + True + center + + + False + False + 0 + + + + + True + center + + + False + False + 1 + + + + + True + center + + + False + False + 2 + + + + + True + 0 + + + True + 6 + + + True + vertical + + + Show field in _View + True + True + False + True + True + + + False + False + 0 + + + + + 0 + + + + + True + vertical + + + Ascending + True + True + False + True + True + + + False + False + 0 + + + + + Descending + True + True + False + True + True + radiobutton-ascending-group-4 + + + False + False + 1 + + + + + False + 1 + + + + + + + True + Then By + + + + + 3 + + + + + 3 + + + + + 2 + + + + + True + end + + + Clear _All + True + True + True + False + True + + + False + False + 0 + + + + + gtk-cancel + True + True + True + False + True + + + False + False + 1 + + + + + gtk-ok + True + True + True + False + True + + + False + False + 2 + + + + + False + end + 0 + + + + + + button39 + button42 + button41 + + + + Sort + True + dialog + + + True + vertical + 8 + + + True + 4 + 6 + 6 + + + True + 0 + + + True + 6 + + + True + 0 + + + 0 + + + + + True + vertical + + + Ascending + True + True + False + True + True + + + False + False + 0 + + + + + Descending + True + True + False + True + True + radiobutton-ascending-sort-4 + + + False + False + 1 + + + + + False + 1 + + + + + + + True + Then By + + + + + 3 + 4 + GTK_FILL + + + + + True + 0 + + + True + 6 + + + True + 0 + + + 0 + + + + + True + vertical + + + Ascending + True + True + False + True + True + + + False + False + 0 + + + + + Descending + True + True + False + True + True + radiobutton-ascending-sort-3 + + + False + False + 1 + + + + + False + 1 + + + + + + + True + Then By + + + + + 2 + 3 + GTK_FILL + + + + + True + 0 + + + True + 6 + + + True + 0 + + + 0 + + + + + True + vertical + + + Ascending + True + True + False + True + True + + + False + False + 0 + + + + + Descending + True + True + False + True + True + radiobutton-ascending-sort-2 + + + False + False + 1 + + + + + False + 1 + + + + + + + True + Then By + + + + + 1 + 2 + GTK_FILL + + + + + True + 0 + + + True + 6 + + + True + 0 + + + 0 + + + + + True + vertical + + + Ascending + True + True + False + True + True + + + False + False + 0 + + + + + Descending + True + True + False + True + True + radiobutton-ascending-sort-1 + + + False + False + 1 + + + + + False + 1 + + + + + + + True + Sort Items By + + + + + GTK_FILL + + + + + 2 + + + + + True + end + + + Clear All + True + True + True + False + True + + + False + False + 0 + + + + + gtk-cancel + True + True + True + False + True + + + False + False + 1 + + + + + gtk-ok + True + True + True + True + False + True + + + False + False + 2 + + + + + False + end + 0 + + + + + + button43 + button45 + button44 + + + + dialog1 + False + True + center-on-parent + dialog + + + True + vertical + + + True + 2 + 0 + + + True + 2 + 3 + 3 + 4 + 2 + + + _Sort... + True + True + True + False + True + + + + 2 + 3 + GTK_FILL + + + + + + _Group By... + True + True + True + False + True + + + + 1 + 2 + GTK_FILL + + + + + + True + 0 + True + + + 1 + 2 + 2 + 3 + GTK_FILL + + + + + _Fields Shown... + True + True + True + False + True + + + + GTK_FILL + + + + + + True + 0 + True + + + 1 + 2 + GTK_FILL + + + + + True + 0 + center + + + 2 + 3 + 1 + 2 + GTK_FILL + GTK_FILL + + + + + True + 0 + center + + + 2 + 3 + 2 + 3 + GTK_FILL + GTK_FILL + + + + + True + 0 + center + + + 2 + 3 + GTK_FILL + GTK_FILL + + + + + True + 0 + True + + + 1 + 2 + 1 + 2 + GTK_FILL + + + + + + + + True + Description + + + + + 2 + + + + + True + end + + + gtk-cancel + True + True + True + False + True + + + False + False + 0 + + + + + gtk-apply + True + True + True + False + True + + + False + False + 1 + + + + + gtk-ok + True + True + True + False + True + + + False + False + 2 + + + + + False + end + 0 + + + + + + cancelbutton2 + applybutton2 + okbutton2 + + + 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 + * + * + * Authors: + * Chris Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION) +#error "Only 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 + * + * + * Authors: + * Chris Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include + +#include + +#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 + * + * + * Authors: + * Chris Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION) +#error "Only should be included directly." +#endif + +#ifndef E_TABLE_EXTRAS_H +#define E_TABLE_EXTRAS_H + +#include +#include + +/* 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 + * + * + * Authors: + * Chris Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include + +#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 + * + * + * Authors: + * Chris Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION) +#error "Only should be included directly." +#endif + +#ifndef __E_TABLE_FIELD_CHOOSER_DIALOG_H__ +#define __E_TABLE_FIELD_CHOOSER_DIALOG_H__ + +#include + +#include +#include + +#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 + * + * + * Authors: + * Chris Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include +#include +#include +#include + +#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 + * + * + * Authors: + * Chris Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION) +#error "Only should be included directly." +#endif + +#ifndef _E_TABLE_FIELD_CHOOSER_ITEM_H_ +#define _E_TABLE_FIELD_CHOOSER_ITEM_H_ + +#include +#include + +#include + +/* 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 + * + * + * Authors: + * Chris Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "e-table-field-chooser.h" + +#include +#include +#include + +#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 + * + * + * Authors: + * Chris Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION) +#error "Only should be included directly." +#endif + +#ifndef __E_TABLE_FIELD_CHOOSER_H__ +#define __E_TABLE_FIELD_CHOOSER_H__ + +#include +#include + +#include + +/* 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 + * + * + * Authors: + * Chris Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "e-table-group-container.h" + +#include +#include +#include +#include + +#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 + * + * + * Authors: + * Chris Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION) +#error "Only should be included directly." +#endif + +#ifndef _E_TABLE_GROUP_CONTAINER_H_ +#define _E_TABLE_GROUP_CONTAINER_H_ + +#include + +#include +#include +#include +#include + +/* 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 + * + * + * Authors: + * Chris Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "e-table-group-leaf.h" + +#include +#include + +#include + +#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 + * + * + * Authors: + * Chris Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION) +#error "Only should be included directly." +#endif + +#ifndef _E_TABLE_GROUP_LEAF_H_ +#define _E_TABLE_GROUP_LEAF_H_ + +#include + +#include +#include +#include + +/* 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 + * + * + * Authors: + * Chris Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include + +#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 + * + * + * Authors: + * Chris Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION) +#error "Only should be included directly." +#endif + +#ifndef _E_TABLE_GROUP_H_ +#define _E_TABLE_GROUP_H_ + +#include + +#include +#include +#include +#include +#include +#include + +/* 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 + * + * + * Authors: + * Chris Lahey + * Miguel de Icaza + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "e-table-header-item.h" + +#include + +#include +#include +#include +#include + +#include + +#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), ði->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 + * + * + * Authors: + * Chris Lahey + * 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 should be included directly." +#endif + +#ifndef _E_TABLE_HEADER_ITEM_H_ +#define _E_TABLE_HEADER_ITEM_H_ + +#include +#include + +#include +#include +#include +#include + +/* 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 + * + * + * Authors: + * Chris Lahey + * Miguel de Icaza + * Federico Mena-Quintero + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "e-table-header-utils.h" + +#include /* strlen() */ + +#include + +#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 + * + * + * Authors: + * Chris Lahey + * Miguel de Icaza + * Federico Mena-Quintero + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION) +#error "Only should be included directly." +#endif + +#ifndef E_TABLE_HEADER_UTILS_H +#define E_TABLE_HEADER_UTILS_H + +#include + +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 + * + * + * Authors: + * Chris Lahey + * Miguel de Icaza + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include + +#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 ( + ð->columns[idx], ð->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 ( + ð->columns[pos + 1], ð->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 + * + * + * Authors: + * Chris Lahey + * Miguel de Icaza + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION) +#error "Only should be included directly." +#endif + +#ifndef _E_TABLE_COLUMN_H_ +#define _E_TABLE_COLUMN_H_ + +#include + +#include +#include + +/* 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 + * + * + * Authors: + * Chris Lahey + * Miguel de Icaza + * + * 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 +#endif + +#include "e-table-item.h" + +#include +#include +#include +#include + +#include +#include +#include + +#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 + * + * + * Authors: + * Chris Lahey + * Miguel de Icaza + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION) +#error "Only should be included directly." +#endif + +#ifndef _E_TABLE_ITEM_H_ +#define _E_TABLE_ITEM_H_ + +#include + +#include +#include +#include +#include +#include + +/* 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 + * + * + * Authors: + * Chris Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include +#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 + * + * + * Authors: + * Chris Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION) +#error "Only should be included directly." +#endif + +#ifndef _E_TABLE_MEMORY_CALLBACKS_H_ +#define _E_TABLE_MEMORY_CALLBACKS_H_ + +#include + +/* 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 + * + * + * Authors: + * Chris Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#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 + * + * + * Authors: + * Chris Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION) +#error "Only should be included directly." +#endif + +#ifndef _E_TABLE_MEMORY_STORE_H_ +#define _E_TABLE_MEMORY_STORE_H_ + +#include +#include + +/* 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 + * + * + * Authors: + * Chris Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include + +#include +#include + +#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 + * + * + * Authors: + * Chris Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION) +#error "Only should be included directly." +#endif + +#ifndef _E_TABLE_MEMORY_H_ +#define _E_TABLE_MEMORY_H_ + +#include + +#include + +/* 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 + * + * + * Authors: + * Chris Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include +#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 + * + * + * Authors: + * Chris Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION) +#error "Only should be included directly." +#endif + +#ifndef _E_TABLE_MODEL_H_ +#define _E_TABLE_MODEL_H_ + +#include + +/* 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 + * + * + * Authors: + * Chris Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include +#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 + * + * + * Authors: + * Chris Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION) +#error "Only should be included directly." +#endif + +#ifndef _E_TABLE_ONE_H_ +#define _E_TABLE_ONE_H_ + +#include + +/* 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 + * + * + * Authors: + * Chris Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "e-table-search.h" + +#include + +#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 + * + * + * Authors: + * Chris Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION) +#error "Only should be included directly." +#endif + +#ifndef _E_TABLE_SEARCH_H_ +#define _E_TABLE_SEARCH_H_ + +#include + +/* 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 + * + * + * Authors: + * Chris Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include + +#include + +#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 + * + * + * Authors: + * Chris Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION) +#error "Only should be included directly." +#endif + +#ifndef _E_TABLE_SELECTION_MODEL_H_ +#define _E_TABLE_SELECTION_MODEL_H_ + +#include +#include +#include + +/* 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 + * + * + * Authors: + * Chris Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "e-table-sort-info.h" + +#include + +#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 + * + * + * Authors: + * Chris Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION) +#error "Only should be included directly." +#endif + +#ifndef _E_TABLE_SORT_INFO_H_ +#define _E_TABLE_SORT_INFO_H_ + +#include +#include + +#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 + * + * + * Authors: + * Chris Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include + +#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 + * + * + * Authors: + * Chris Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION) +#error "Only should be included directly." +#endif + +#ifndef _E_TABLE_SORTED_VARIABLE_H_ +#define _E_TABLE_SORTED_VARIABLE_H_ + +#include +#include +#include +#include + +/* 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 + * + * + * Authors: + * Chris Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include + +#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 + * + * + * Authors: + * Chris Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION) +#error "Only should be included directly." +#endif + +#ifndef _E_TABLE_SORTED_H_ +#define _E_TABLE_SORTED_H_ + +#include +#include +#include +#include + +/* 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 + * + * + * Authors: + * Chris Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include + +#include + +#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 + * + * + * Authors: + * Chris Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION) +#error "Only should be included directly." +#endif + +#ifndef _E_TABLE_SORTER_H_ +#define _E_TABLE_SORTER_H_ + +#include +#include +#include +#include +#include + +/* 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 + * + * + * Authors: + * Chris Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "e-table-sorting-utils.h" + +#include +#include + +#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 + * + * + * Authors: + * Chris Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION) +#error "Only should be included directly." +#endif + +#ifndef _E_TABLE_SORTING_UTILS_H_ +#define _E_TABLE_SORTING_UTILS_H_ + +#include +#include +#include +#include + +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 + * + * + * Authors: + * Chris Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "e-table-specification.h" + +#include +#include + +#include +#include +#include + +#include + +#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 + * + * + * Authors: + * Chris Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION) +#error "Only should be included directly." +#endif + +#ifndef _E_TABLE_SPECIFICATION_H_ +#define _E_TABLE_SPECIFICATION_H_ + +#include + +#include +#include +#include +#include + +/* 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 + * + * + * Authors: + * Chris Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "e-table-state.h" + +#include +#include + +#include +#include + +#include + +#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 ("\n"); + for (i = 0; i < col_count; i++) + g_string_append_printf (str, " \n", i); + g_string_append (str, " \n"); + g_string_append (str, "\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 + * + * + * Authors: + * Chris Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION) +#error "Only should be included directly." +#endif + +#ifndef _E_TABLE_STATE_H_ +#define _E_TABLE_STATE_H_ + +#include + +#include + +/* 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 + * + * + * Authors: + * Chris Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include + +#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 + * + * + * Authors: + * Chris Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION) +#error "Only should be included directly." +#endif + +#ifndef _E_TABLE_SUBSET_VARIABLE_H_ +#define _E_TABLE_SUBSET_VARIABLE_H_ + +#include + +/* 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 + * + * + * Authors: + * Chris Lahey + * Miguel de Icaza + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#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 + * + * + * Authors: + * Chris Lahey + * Miguel de Icaza + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION) +#error "Only should be included directly." +#endif + +#ifndef _E_TABLE_SUBSET_H_ +#define _E_TABLE_SUBSET_H_ + +#include + +/* 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 + * + * + * Authors: + * Chris Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "e-table-utils.h" + +#include /* This file uses dgettext() but no _() */ +#include + +#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 + * + * + * Authors: + * Chris Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION) +#error "Only should be included directly." +#endif + +#ifndef _E_TABLE_UTILS_H_ +#define _E_TABLE_UTILS_H_ + +#include +#include +#include +#include + +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 + * + * + * Authors: + * Chris Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include + +#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 + * + * + * Authors: + * Chris Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION) +#error "Only should be included directly." +#endif + +#ifndef _E_TABLE_WITHOUT_H_ +#define _E_TABLE_WITHOUT_H_ + +#include + +/* 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 + * + * + * Authors: + * Chris Lahey + * Miguel de Icaza + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "e-table.h" + +#include +#include +#include + +#include +#include +#include +#include + +#include + +#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 + * + * + * Authors: + * Chris Lahey + * Miguel de Icaza + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION) +#error "Only should be included directly." +#endif + +#ifndef _E_TABLE_H_ +#define _E_TABLE_H_ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* 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 #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 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 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 #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 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 + * + * + * Authors: + * Jon Trowbridge + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include +#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 + * + * + * Authors: + * Jon Trowbridge + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION) +#error "Only 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 + * + * + * Authors: + * Chris Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#undef PARANOID_DEBUGGING + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "e-text-model.h" + +#include +#include + +#include + +#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 + * + * + * Authors: + * Chris Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION) +#error "Only should be included directly." +#endif + +#ifndef E_TEXT_MODEL_H +#define E_TEXT_MODEL_H + +#include + +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 + * Jon Trowbridge + * + * 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 + * + * 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 +#endif + +#include "e-text.h" + +#include +#include +#include + +#include +#include +#include + +#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, ¤t_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 + * + * + * Authors: + * Chris Lahey + * Jon Trowbridge + * + * 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 + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION) +#error "Only should be included directly." +#endif + +#ifndef E_TEXT_H +#define E_TEXT_H + +#include + +#include +#include +#include + +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 + * + * + * Authors: + * Damon Chaplin + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "e-timezone-dialog.h" + +#include +#include +#include + +#include + +#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 + * + * + * Authors: + * Damon Chaplin + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION) +#error "Only should be included directly." +#endif + +#ifndef E_TIMEZONE_DIALOG_H +#define E_TIMEZONE_DIALOG_H + +#include +#include + +/* 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 @@ + + + + + Select a Time Zone + GTK_WINDOW_TOPLEVEL + GTK_WIN_POS_NONE + False + 500 + 400 + True + False + True + False + False + GDK_WINDOW_TYPE_HINT_DIALOG + GDK_GRAVITY_NORTH_WEST + + + vertical + True + False + 6 + + + True + GTK_BUTTONBOX_END + + + True + True + True + gtk-cancel + True + GTK_RELIEF_NORMAL + True + + + + + True + True + True + True + gtk-ok + True + GTK_RELIEF_NORMAL + True + + + + + 0 + False + True + GTK_PACK_END + + + + + vertical + 12 + True + False + 12 + + + horizontal + True + False + 12 + + + True + gtk-dialog-info + 6 + 0.5 + 0.5 + 0 + 0 + + + 0 + False + False + + + + + True + 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. + False + False + GTK_JUSTIFY_LEFT + False + False + 0 + 0.5 + 0 + 0 + + + 0 + False + False + + + + + 0 + False + False + + + + + True + Time Zones + False + GTK_JUSTIFY_LEFT + False + False + 0 + 0.5 + 0 + 0 + + + + + + 0 + False + False + + + + + horizontal + True + False + 12 + + + True + + False + False + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + + + 0 + False + False + + + + + vertical + True + False + 6 + + + True + True + GTK_POLICY_ALWAYS + GTK_POLICY_ALWAYS + GTK_SHADOW_IN + GTK_CORNER_TOP_LEFT + + + + + + 0 + True + True + + + + + True + America/New_York + False + False + GTK_JUSTIFY_LEFT + False + False + 0 + 0 + 0 + 0 + + + 0 + False + False + + + + + 0 + True + True + + + + + 0 + True + True + + + + + True + _Selection + True + GTK_JUSTIFY_CENTER + False + False + 0 + 0.5 + 0 + 0 + timezone-combo + + + + + + 0 + False + False + + + + + horizontal + True + False + 12 + + + True + + False + False + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + + + 0 + False + False + + + + + True + False + True + + + + + + Timezone drop-down combination box + + + + + 0 + True + True + + + + + 0 + False + False + + + + + 0 + True + True + + + + + + cancel-button + ok-button + + + 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 + * + * + * Authors: + * Chris Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#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 + * + * + * Authors: + * Chris Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION) +#error "Only should be included directly." +#endif + +#ifndef _E_TREE_MEMORY_CALLBACKS_H_ +#define _E_TREE_MEMORY_CALLBACKS_H_ + +#include + +/* 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 + * + * + * Authors: + * Chris Lahey + * Chris Toshok + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "e-tree-memory.h" + +#include +#include +#include +#include +#include + +#include +#include + +#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 + * + * + * Authors: + * Chris Lahey + * Chris Toshok + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION) +#error "Only should be included directly." +#endif + +#ifndef _E_TREE_MEMORY_H_ +#define _E_TREE_MEMORY_H_ + +#include + +#include + +/* 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 + */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include +#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 + */ + +#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION) +#error "Only should be included directly." +#endif + +#ifndef E_TREE_MODEL_GENERATOR_H +#define E_TREE_MODEL_GENERATOR_H + +#include + +/* 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 + * + * + * Authors: + * Chris Lahey + * Chris Toshok + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "e-tree-model.h" + +#include +#include +#include +#include + +#include +#include +#include + +#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 + * + * + * Authors: + * Chris Lahey + * Chris Toshok + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION) +#error "Only should be included directly." +#endif + +#ifndef _E_TREE_MODEL_H_ +#define _E_TREE_MODEL_H_ + +#include + +/* 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 + * + * + * Authors: + * Chris Lahey + * Mike Kestner + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "e-tree-selection-model.h" + +#include + +#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 + * + * + * Authors: + * Chris Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION) +#error "Only should be included directly." +#endif + +#ifndef _E_TREE_SELECTION_MODEL_H_ +#define _E_TREE_SELECTION_MODEL_H_ + +#include +#include +#include + +/* 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 + * + * + * Authors: + * Chris Lahey + * Chris Toshok + * + * 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 +#endif + +#include "e-tree-sorted.h" + +#include +#include +#include +#include +#include +#include + +#include +#include + +#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 + * + * + * Authors: + * Chris Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION) +#error "Only should be included directly." +#endif + +#ifndef _E_TREE_SORTED_H_ +#define _E_TREE_SORTED_H_ + +#include + +#include +#include +#include + +/* 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 + * + * + * Authors: + * Chris Lahey + * Chris Toshok + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "e-tree-table-adapter.h" + +#include +#include + +#include + +#include +#include + +#include + +#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 + * + * + * Authors: + * Chris Lahey + * Chris Toshok + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION) +#error "Only should be included directly." +#endif + +#ifndef _E_TREE_TABLE_ADAPTER_H_ +#define _E_TREE_TABLE_ADAPTER_H_ + +#include + +#include +#include +#include +#include + +/* 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 + * + * + * Authors: + * Chris Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include + +#include +#include +#include + +#include + +#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 + * + * + * Authors: + * Chris Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION) +#error "Only should be included directly." +#endif + +#ifndef _E_TREE_H_ +#define _E_TREE_H_ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#define E_TREE_USE_TREE_SELECTION + +#ifdef E_TREE_USE_TREE_SELECTION +#include +#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 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 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 + * + * + * Authors: + * JP Rosevear + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "e-url-entry.h" + +#include +#include + +#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 + * + * + * Authors: + * JP Rosevear + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION) +#error "Only should be included directly." +#endif + +#ifndef _E_URL_ENTRY_H_ +#define _E_URL_ENTRY_H_ + +#include + +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 should be included directly." +#endif + #ifndef E_UTIL_ENUMS_H #define E_UTIL_ENUMS_H diff --git a/e-util/e-util.c b/e-util/e-util.c deleted file mode 100644 index 8d47da2186..0000000000 --- a/e-util/e-util.c +++ /dev/null @@ -1,1519 +0,0 @@ -/* - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with the program; if not, see - * - * - * Authors: - * Chris Lahey - * - * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) - * - */ - -/** - * SECTION: e-util - * @include: e-util/e-util.h - **/ - -#ifdef HAVE_CONFIG_H -#include -#endif - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#ifdef G_OS_WIN32 -#include -#endif - -#include -#include - -#include "filter/e-filter-option.h" - -#include "e-util.h" -#include "e-util-private.h" - -typedef struct _WindowData WindowData; - -struct _WindowData { - GtkWindow *window; - GSettings *settings; - ERestoreWindowFlags flags; - gint premax_width; - gint premax_height; - guint timeout_id; -}; - -static void -window_data_free (WindowData *data) -{ - if (data->settings != NULL) - g_object_unref (data->settings); - - if (data->timeout_id > 0) - g_source_remove (data->timeout_id); - - g_slice_free (WindowData, data); -} - -static gboolean -window_update_settings (WindowData *data) -{ - GSettings *settings = data->settings; - - if (data->flags & E_RESTORE_WINDOW_SIZE) { - GdkWindowState state; - GdkWindow *window; - gboolean maximized; - - window = gtk_widget_get_window (GTK_WIDGET (data->window)); - state = gdk_window_get_state (window); - maximized = ((state & GDK_WINDOW_STATE_MAXIMIZED) != 0); - - g_settings_set_boolean (settings, "maximized", maximized); - - if (!maximized) { - gint width, height; - - gtk_window_get_size (data->window, &width, &height); - - g_settings_set_int (settings, "width", width); - g_settings_set_int (settings, "height", height); - } - } - - if (data->flags & E_RESTORE_WINDOW_POSITION) { - gint x, y; - - gtk_window_get_position (data->window, &x, &y); - - g_settings_set_int (settings, "x", x); - g_settings_set_int (settings, "y", y); - } - - data->timeout_id = 0; - - return FALSE; -} - -static void -window_delayed_update_settings (WindowData *data) -{ - if (data->timeout_id > 0) - g_source_remove (data->timeout_id); - - data->timeout_id = g_timeout_add_seconds ( - 1, (GSourceFunc) window_update_settings, data); -} - -static gboolean -window_configure_event_cb (GtkWindow *window, - GdkEventConfigure *event, - WindowData *data) -{ - window_delayed_update_settings (data); - - return FALSE; -} - -static gboolean -window_state_event_cb (GtkWindow *window, - GdkEventWindowState *event, - WindowData *data) -{ - gboolean window_was_unmaximized; - - if (data->timeout_id > 0) - g_source_remove (data->timeout_id); - - window_was_unmaximized = - ((event->changed_mask & GDK_WINDOW_STATE_MAXIMIZED) != 0) && - ((event->new_window_state & GDK_WINDOW_STATE_MAXIMIZED) == 0); - - if (window_was_unmaximized) { - gint width, height; - - width = data->premax_width; - data->premax_width = 0; - - height = data->premax_height; - data->premax_height = 0; - - /* This only applies when the window is initially restored - * as maximized and is then unmaximized. GTK+ handles the - * unmaximized window size thereafter. */ - if (width > 0 && height > 0) - gtk_window_resize (window, width, height); - } - - window_delayed_update_settings (data); - - return FALSE; -} - -static gboolean -window_unmap_cb (GtkWindow *window, - WindowData *data) -{ - if (data->timeout_id > 0) - g_source_remove (data->timeout_id); - - /* It's too late to record the window position. - * gtk_window_get_position() will report (0, 0). */ - data->flags &= ~E_RESTORE_WINDOW_POSITION; - - window_update_settings (data); - - return FALSE; -} - -/** - * e_get_accels_filename: - * - * Returns the name of the user data file containing custom keyboard - * accelerator specifications. - * - * Returns: filename for accelerator specifications - **/ -const gchar * -e_get_accels_filename (void) -{ - static gchar *filename = NULL; - - if (G_UNLIKELY (filename == NULL)) { - const gchar *config_dir = e_get_user_config_dir (); - filename = g_build_filename (config_dir, "accels", NULL); - } - - return filename; -} - -/** - * e_show_uri: - * @parent: a parent #GtkWindow or %NULL - * @uri: the URI to show - * - * Launches the default application to show the given URI. The URI must - * be of a form understood by GIO. If the URI cannot be shown, it presents - * a dialog describing the error. The dialog is set as transient to @parent - * if @parent is non-%NULL. - **/ -void -e_show_uri (GtkWindow *parent, - const gchar *uri) -{ - GtkWidget *dialog; - GdkScreen *screen = NULL; - GError *error = NULL; - guint32 timestamp; - - g_return_if_fail (uri != NULL); - - timestamp = gtk_get_current_event_time (); - - if (parent != NULL) - screen = gtk_widget_get_screen (GTK_WIDGET (parent)); - - if (gtk_show_uri (screen, uri, timestamp, &error)) - return; - - dialog = gtk_message_dialog_new_with_markup ( - parent, GTK_DIALOG_DESTROY_WITH_PARENT, - GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, - "%s", - _("Could not open the link.")); - - gtk_message_dialog_format_secondary_text ( - GTK_MESSAGE_DIALOG (dialog), "%s", error->message); - - gtk_dialog_run (GTK_DIALOG (dialog)); - - gtk_widget_destroy (dialog); - g_error_free (error); -} - -/** - * e_display_help: - * @parent: a parent #GtkWindow or %NULL - * @link_id: help section to present or %NULL - * - * Opens the user documentation to the section given by @link_id, or to the - * table of contents if @link_id is %NULL. If the user documentation cannot - * be opened, it presents a dialog describing the error. The dialog is set - * as transient to @parent if @parent is non-%NULL. - **/ -void -e_display_help (GtkWindow *parent, - const gchar *link_id) -{ - GString *uri; - GtkWidget *dialog; - GdkScreen *screen = NULL; - GError *error = NULL; - guint32 timestamp; - - uri = g_string_new ("help:" PACKAGE); - timestamp = gtk_get_current_event_time (); - - if (parent != NULL) - screen = gtk_widget_get_screen (GTK_WIDGET (parent)); - - if (link_id != NULL) - g_string_append_printf (uri, "?%s", link_id); - - if (gtk_show_uri (screen, uri->str, timestamp, &error)) - goto exit; - - dialog = gtk_message_dialog_new_with_markup ( - parent, GTK_DIALOG_DESTROY_WITH_PARENT, - GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, - "%s", - _("Could not display help for Evolution.")); - - gtk_message_dialog_format_secondary_text ( - GTK_MESSAGE_DIALOG (dialog), "%s", error->message); - - gtk_dialog_run (GTK_DIALOG (dialog)); - - gtk_widget_destroy (dialog); - g_error_free (error); - -exit: - g_string_free (uri, TRUE); -} - -/** - * e_restore_window: - * @window: a #GtkWindow - * @settings_path: a #GSettings path - * @flags: flags indicating which window features to restore - * - * This function can restore one of or both a window's size and position - * using #GSettings keys at @settings_path which conform to the relocatable - * schema "org.gnome.evolution.window". - * - * If #E_RESTORE_WINDOW_SIZE is present in @flags, restore @window's - * previously recorded size and maximize state. - * - * If #E_RESTORE_WINDOW_POSITION is present in @flags, move @window to - * the previously recorded screen coordinates. - * - * The respective #GSettings values will be updated when the window is - * resized and/or moved. - **/ -void -e_restore_window (GtkWindow *window, - const gchar *settings_path, - ERestoreWindowFlags flags) -{ - WindowData *data; - GSettings *settings; - const gchar *schema; - - g_return_if_fail (GTK_IS_WINDOW (window)); - g_return_if_fail (settings_path != NULL); - - schema = "org.gnome.evolution.window"; - settings = g_settings_new_with_path (schema, settings_path); - - data = g_slice_new0 (WindowData); - data->window = window; - data->settings = g_object_ref (settings); - data->flags = flags; - - if (flags & E_RESTORE_WINDOW_SIZE) { - gint width, height; - - width = g_settings_get_int (settings, "width"); - height = g_settings_get_int (settings, "height"); - - if (width > 0 && height > 0) - gtk_window_resize (window, width, height); - - if (g_settings_get_boolean (settings, "maximized")) { - GdkScreen *screen; - GdkRectangle monitor_area; - gint x, y, monitor; - - x = g_settings_get_int (settings, "x"); - y = g_settings_get_int (settings, "y"); - - screen = gtk_window_get_screen (window); - gtk_window_get_size (window, &width, &height); - - data->premax_width = width; - data->premax_height = height; - - monitor = gdk_screen_get_monitor_at_point (screen, x, y); - if (monitor < 0 || monitor >= gdk_screen_get_n_monitors (screen)) - monitor = 0; - - gdk_screen_get_monitor_workarea (screen, monitor, &monitor_area); - - gtk_window_resize (window, monitor_area.width, monitor_area.height); - gtk_window_maximize (window); - } - } - - if (flags & E_RESTORE_WINDOW_POSITION) { - gint x, y; - - x = g_settings_get_int (settings, "x"); - y = g_settings_get_int (settings, "y"); - - gtk_window_move (window, x, y); - } - - g_object_set_data_full ( - G_OBJECT (window), - "e-util-window-data", data, - (GDestroyNotify) window_data_free); - - g_signal_connect ( - window, "configure-event", - G_CALLBACK (window_configure_event_cb), data); - - g_signal_connect ( - window, "window-state-event", - G_CALLBACK (window_state_event_cb), data); - - g_signal_connect ( - window, "unmap", - G_CALLBACK (window_unmap_cb), data); - - g_object_unref (settings); -} - -/** - * e_lookup_action: - * @ui_manager: a #GtkUIManager - * @action_name: the name of an action - * - * Returns the first #GtkAction named @action_name by traversing the - * list of action groups in @ui_manager. If no such action exists, the - * function emits a critical warning before returning %NULL, since this - * probably indicates a programming error and most code is not prepared - * to deal with lookup failures. - * - * Returns: the first #GtkAction named @action_name - **/ -GtkAction * -e_lookup_action (GtkUIManager *ui_manager, - const gchar *action_name) -{ - GtkAction *action = NULL; - GList *iter; - - g_return_val_if_fail (GTK_IS_UI_MANAGER (ui_manager), NULL); - g_return_val_if_fail (action_name != NULL, NULL); - - iter = gtk_ui_manager_get_action_groups (ui_manager); - - while (iter != NULL) { - GtkActionGroup *action_group = iter->data; - - action = gtk_action_group_get_action ( - action_group, action_name); - if (action != NULL) - return action; - - iter = g_list_next (iter); - } - - g_critical ("%s: action '%s' not found", G_STRFUNC, action_name); - - return NULL; -} - -/** - * e_lookup_action_group: - * @ui_manager: a #GtkUIManager - * @group_name: the name of an action group - * - * Returns the #GtkActionGroup in @ui_manager named @group_name. If no - * such action group exists, the function emits a critical warnings before - * returning %NULL, since this probably indicates a programming error and - * most code is not prepared to deal with lookup failures. - * - * Returns: the #GtkActionGroup named @group_name - **/ -GtkActionGroup * -e_lookup_action_group (GtkUIManager *ui_manager, - const gchar *group_name) -{ - GList *iter; - - g_return_val_if_fail (GTK_IS_UI_MANAGER (ui_manager), NULL); - g_return_val_if_fail (group_name != NULL, NULL); - - iter = gtk_ui_manager_get_action_groups (ui_manager); - - while (iter != NULL) { - GtkActionGroup *action_group = iter->data; - const gchar *name; - - name = gtk_action_group_get_name (action_group); - if (strcmp (name, group_name) == 0) - return action_group; - - iter = g_list_next (iter); - } - - g_critical ("%s: action group '%s' not found", G_STRFUNC, group_name); - - return NULL; -} - -/** - * e_action_compare_by_label: - * @action1: a #GtkAction - * @action2: a #GtkAction - * - * Compares the labels for @action1 and @action2 using g_utf8_collate(). - * - * Returns: < 0 if @action1 compares before @action2, 0 if they - * compare equal, > 0 if @action1 compares after @action2 - **/ -gint -e_action_compare_by_label (GtkAction *action1, - GtkAction *action2) -{ - gchar *label1; - gchar *label2; - gint result; - - /* XXX This is horribly inefficient but will generally only be - * used on short lists of actions during UI construction. */ - - if (action1 == action2) - return 0; - - g_object_get (action1, "label", &label1, NULL); - g_object_get (action2, "label", &label2, NULL); - - result = g_utf8_collate (label1, label2); - - g_free (label1); - g_free (label2); - - return result; -} - -/** - * e_action_group_remove_all_actions: - * @action_group: a #GtkActionGroup - * - * Removes all actions from the action group. - **/ -void -e_action_group_remove_all_actions (GtkActionGroup *action_group) -{ - GList *list, *iter; - - /* XXX I've proposed this function for inclusion in GTK+. - * GtkActionGroup stores actions in an internal hash - * table and can do this more efficiently by calling - * g_hash_table_remove_all(). - * - * http://bugzilla.gnome.org/show_bug.cgi?id=550485 */ - - g_return_if_fail (GTK_IS_ACTION_GROUP (action_group)); - - list = gtk_action_group_list_actions (action_group); - for (iter = list; iter != NULL; iter = iter->next) - gtk_action_group_remove_action (action_group, iter->data); - g_list_free (list); -} - -/** - * e_radio_action_get_current_action: - * @radio_action: a #GtkRadioAction - * - * Returns the currently active member of the group to which @radio_action - * belongs. - * - * Returns: the currently active group member - **/ -GtkRadioAction * -e_radio_action_get_current_action (GtkRadioAction *radio_action) -{ - GSList *group; - gint current_value; - - g_return_val_if_fail (GTK_IS_RADIO_ACTION (radio_action), NULL); - - group = gtk_radio_action_get_group (radio_action); - current_value = gtk_radio_action_get_current_value (radio_action); - - while (group != NULL) { - gint value; - - radio_action = GTK_RADIO_ACTION (group->data); - g_object_get (radio_action, "value", &value, NULL); - - if (value == current_value) - return radio_action; - - group = g_slist_next (group); - } - - return NULL; -} - -/** - * e_action_group_add_actions_localized: - * @action_group: a #GtkActionGroup to add @entries to - * @translation_domain: a translation domain to use - * to translate label and tooltip strings in @entries - * @entries: (array length=n_entries): an array of action descriptions - * @n_entries: the number of entries - * @user_data: data to pass to the action callbacks - * - * Adds #GtkAction-s defined by @entries to @action_group, with action's - * label and tooltip localized in the given translation domain, instead - * of the domain set on the @action_group. - * - * Since: 3.4 - **/ -void -e_action_group_add_actions_localized (GtkActionGroup *action_group, - const gchar *translation_domain, - const GtkActionEntry *entries, - guint n_entries, - gpointer user_data) -{ - GtkActionGroup *tmp_group; - GList *list, *iter; - gint ii; - - g_return_if_fail (action_group != NULL); - g_return_if_fail (entries != NULL); - g_return_if_fail (n_entries > 0); - g_return_if_fail (translation_domain != NULL); - g_return_if_fail (*translation_domain); - - tmp_group = gtk_action_group_new ("temporary-group"); - gtk_action_group_set_translation_domain (tmp_group, translation_domain); - gtk_action_group_add_actions (tmp_group, entries, n_entries, user_data); - - list = gtk_action_group_list_actions (tmp_group); - for (iter = list; iter != NULL; iter = iter->next) { - GtkAction *action = GTK_ACTION (iter->data); - const gchar *action_name; - - g_object_ref (action); - - action_name = gtk_action_get_name (action); - - for (ii = 0; ii < n_entries; ii++) { - if (g_strcmp0 (entries[ii].name, action_name) == 0) { - gtk_action_group_remove_action ( - tmp_group, action); - gtk_action_group_add_action_with_accel ( - action_group, action, - entries[ii].accelerator); - break; - } - } - - g_object_unref (action); - } - - g_list_free (list); - g_object_unref (tmp_group); -} - -/* Helper for e_categories_add_change_hook() */ -static void -categories_changed_cb (GObject *useless_opaque_object, - GHookList *hook_list) -{ - /* e_categories_register_change_listener() is broken because - * it requires callbacks to allow for some opaque GObject as - * the first argument (not does it document this). */ - g_hook_list_invoke (hook_list, FALSE); -} - -/* Helper for e_categories_add_change_hook() */ -static void -categories_weak_notify_cb (GHookList *hook_list, - gpointer where_the_object_was) -{ - GHook *hook; - - /* This should not happen, but if we fail to find the hook for - * some reason, g_hook_destroy_link() will warn about the NULL - * pointer, which is all we would do anyway so no need to test - * for it ourselves. */ - hook = g_hook_find_data (hook_list, TRUE, where_the_object_was); - g_hook_destroy_link (hook_list, hook); -} - -/** - * e_categories_add_change_hook: - * @func: a hook function - * @object: a #GObject to be passed to @func, or %NULL - * - * A saner alternative to e_categories_register_change_listener(). - * - * Adds a hook function to be called when a category is added, removed or - * modified. If @object is not %NULL, the hook function is automatically - * removed when @object is finalized. - **/ -void -e_categories_add_change_hook (GHookFunc func, - gpointer object) -{ - static gboolean initialized = FALSE; - static GHookList hook_list; - GHook *hook; - - g_return_if_fail (func != NULL); - - if (object != NULL) - g_return_if_fail (G_IS_OBJECT (object)); - - if (!initialized) { - g_hook_list_init (&hook_list, sizeof (GHook)); - e_categories_register_change_listener ( - G_CALLBACK (categories_changed_cb), &hook_list); - initialized = TRUE; - } - - hook = g_hook_alloc (&hook_list); - - hook->func = func; - hook->data = object; - - if (object != NULL) - g_object_weak_ref ( - G_OBJECT (object), (GWeakNotify) - categories_weak_notify_cb, &hook_list); - - g_hook_append (&hook_list, hook); -} - -/** - * e_str_without_underscores: - * @string: the string to strip underscores from - * - * Strips underscores from a string in the same way - * @gtk_label_new_with_mnemonics does. The returned string should be freed - * using g_free(). - * - * Returns: a newly-allocated string without underscores - */ -gchar * -e_str_without_underscores (const gchar *string) -{ - gchar *new_string; - const gchar *sp; - gchar *dp; - - new_string = g_malloc (strlen (string) + 1); - - dp = new_string; - for (sp = string; *sp != '\0'; sp++) { - if (*sp != '_') { - *dp = *sp; - dp++; - } else if (sp[1] == '_') { - /* Translate "__" in "_". */ - *dp = '_'; - dp++; - sp++; - } - } - *dp = 0; - - return new_string; -} - -gint -e_str_compare (gconstpointer x, - gconstpointer y) -{ - if (x == NULL || y == NULL) { - if (x == y) - return 0; - else - return x ? -1 : 1; - } - - return strcmp (x, y); -} - -gint -e_str_case_compare (gconstpointer x, - gconstpointer y) -{ - gchar *cx, *cy; - gint res; - - if (x == NULL || y == NULL) { - if (x == y) - return 0; - else - return x ? -1 : 1; - } - - cx = g_utf8_casefold (x, -1); - cy = g_utf8_casefold (y, -1); - - res = g_utf8_collate (cx, cy); - - g_free (cx); - g_free (cy); - - return res; -} - -gint -e_collate_compare (gconstpointer x, - gconstpointer y) -{ - if (x == NULL || y == NULL) { - if (x == y) - return 0; - else - return x ? -1 : 1; - } - - return g_utf8_collate (x, y); -} - -gint -e_int_compare (gconstpointer x, - gconstpointer y) -{ - gint nx = GPOINTER_TO_INT (x); - gint ny = GPOINTER_TO_INT (y); - - return (nx == ny) ? 0 : (nx < ny) ? -1 : 1; -} - -/** - * e_color_to_value: - * @color: a #GdkColor - * - * Converts a #GdkColor to a 24-bit RGB color value. - * - * Returns: a 24-bit color value - **/ -guint32 -e_color_to_value (GdkColor *color) -{ - GdkRGBA rgba; - - g_return_val_if_fail (color != NULL, 0); - - rgba.red = color->red / 65535.0; - rgba.green = color->green / 65535.0; - rgba.blue = color->blue / 65535.0; - rgba.alpha = 0.0; - - return e_rgba_to_value (&rgba); -} - -/** - * e_rgba_to_value: - * @rgba: a #GdkRGBA - * - * - * Converts #GdkRGBA to a 24-bit RGB color value - * - * Returns: a 24-bit color value - **/ -guint32 -e_rgba_to_value (GdkRGBA *rgba) -{ - guint16 red; - guint16 green; - guint16 blue; - - g_return_val_if_fail (rgba != NULL, 0); - - red = 255 * rgba->red; - green = 255 * rgba->green; - blue = 255 * rgba->blue; - - return (guint32) - ((((red & 0xFF) << 16) | - ((green & 0xFF) << 8) | - (blue & 0xFF)) & 0xffffff); -} - -static gint -epow10 (gint number) -{ - gint value = 1; - - while (number-- > 0) - value *= 10; - - return value; -} - -gchar * -e_format_number (gint number) -{ - GList *iterator, *list = NULL; - struct lconv *locality; - gint char_length = 0; - gint group_count = 0; - gchar *grouping; - gint last_count = 3; - gint divider; - gchar *value; - gchar *value_iterator; - - locality = localeconv (); - grouping = locality->grouping; - while (number) { - gchar *group; - switch (*grouping) { - default: - last_count = *grouping; - grouping++; - case 0: - divider = epow10 (last_count); - if (number >= divider) { - group = g_strdup_printf ( - "%0*d", last_count, number % divider); - } else { - group = g_strdup_printf ( - "%d", number % divider); - } - number /= divider; - break; - case CHAR_MAX: - group = g_strdup_printf ("%d", number); - number = 0; - break; - } - char_length += strlen (group); - list = g_list_prepend (list, group); - group_count++; - } - - if (list) { - value = g_new ( - gchar, 1 + char_length + (group_count - 1) * - strlen (locality->thousands_sep)); - - iterator = list; - value_iterator = value; - - strcpy (value_iterator, iterator->data); - value_iterator += strlen (iterator->data); - for (iterator = iterator->next; iterator; iterator = iterator->next) { - strcpy (value_iterator, locality->thousands_sep); - value_iterator += strlen (locality->thousands_sep); - - strcpy (value_iterator, iterator->data); - value_iterator += strlen (iterator->data); - } - g_list_foreach (list, (GFunc) g_free, NULL); - g_list_free (list); - return value; - } else { - return g_strdup ("0"); - } -} - -/* Perform a binary search for key in base which has nmemb elements - * of size bytes each. The comparisons are done by (*compare)(). */ -void -e_bsearch (gconstpointer key, - gconstpointer base, - gsize nmemb, - gsize size, - ESortCompareFunc compare, - gpointer closure, - gsize *start, - gsize *end) -{ - gsize l, u, idx; - gconstpointer p; - gint comparison; - if (!(start || end)) - return; - - l = 0; - u = nmemb; - while (l < u) { - idx = (l + u) / 2; - p = (((const gchar *) base) + (idx * size)); - comparison = (*compare) (key, p, closure); - if (comparison < 0) - u = idx; - else if (comparison > 0) - l = idx + 1; - else { - gsize lsave, usave; - lsave = l; - usave = u; - if (start) { - while (l < u) { - idx = (l + u) / 2; - p = (((const gchar *) base) + (idx * size)); - comparison = (*compare) (key, p, closure); - if (comparison <= 0) - u = idx; - else - l = idx + 1; - } - *start = l; - - l = lsave; - u = usave; - } - if (end) { - while (l < u) { - idx = (l + u) / 2; - p = (((const gchar *) base) + (idx * size)); - comparison = (*compare) (key, p, closure); - if (comparison < 0) - u = idx; - else - l = idx + 1; - } - *end = l; - } - return; - } - } - - if (start) - *start = l; - if (end) - *end = l; -} - -/* Function to do a last minute fixup of the AM/PM stuff if the locale - * and gettext haven't done it right. Most English speaking countries - * except the USA use the 24 hour clock (UK, Australia etc). However - * since they are English nobody bothers to write a language - * translation (gettext) file. So the locale turns off the AM/PM, but - * gettext does not turn on the 24 hour clock. Leaving a mess. - * - * This routine checks if AM/PM are defined in the locale, if not it - * forces the use of the 24 hour clock. - * - * The function itself is a front end on strftime and takes exactly - * the same arguments. - * - * TODO: Actually remove the '%p' from the fixed up string so that - * there isn't a stray space. - */ - -gsize -e_strftime_fix_am_pm (gchar *str, - gsize max, - const gchar *fmt, - const struct tm *tm) -{ - gchar buf[10]; - gchar *sp; - gchar *ffmt; - gsize ret; - - if (strstr (fmt, "%p") == NULL && strstr (fmt, "%P") == NULL) { - /* No AM/PM involved - can use the fmt string directly */ - ret = e_strftime (str, max, fmt, tm); - } else { - /* Get the AM/PM symbol from the locale */ - e_strftime (buf, 10, "%p", tm); - - if (buf[0]) { - /* AM/PM have been defined in the locale - * so we can use the fmt string directly. */ - ret = e_strftime (str, max, fmt, tm); - } else { - /* No AM/PM defined by locale - * must change to 24 hour clock. */ - ffmt = g_strdup (fmt); - for (sp = ffmt; (sp = strstr (sp, "%l")); sp++) { - /* Maybe this should be 'k', but I have never - * seen a 24 clock actually use that format. */ - sp[1]='H'; - } - for (sp = ffmt; (sp = strstr (sp, "%I")); sp++) { - sp[1]='H'; - } - ret = e_strftime (str, max, ffmt, tm); - g_free (ffmt); - } - } - - return (ret); -} - -gsize -e_utf8_strftime_fix_am_pm (gchar *str, - gsize max, - const gchar *fmt, - const struct tm *tm) -{ - gsize sz, ret; - gchar *locale_fmt, *buf; - - locale_fmt = g_locale_from_utf8 (fmt, -1, NULL, &sz, NULL); - if (!locale_fmt) - return 0; - - ret = e_strftime_fix_am_pm (str, max, locale_fmt, tm); - if (!ret) { - g_free (locale_fmt); - return 0; - } - - buf = g_locale_to_utf8 (str, ret, NULL, &sz, NULL); - if (!buf) { - g_free (locale_fmt); - return 0; - } - - if (sz >= max) { - gchar *tmp = buf + max - 1; - tmp = g_utf8_find_prev_char (buf, tmp); - if (tmp) - sz = tmp - buf; - else - sz = 0; - } - memcpy (str, buf, sz); - str[sz] = '\0'; - g_free (locale_fmt); - g_free (buf); - return sz; -} - -/** - * e_get_month_name: - * @month: month index - * @abbreviated: if %TRUE, abbreviate the month name - * - * Returns the localized name for @month. If @abbreviated is %TRUE, - * returns the locale's abbreviated month name. - * - * Returns: localized month name - **/ -const gchar * -e_get_month_name (GDateMonth month, - gboolean abbreviated) -{ - /* Make the indices correspond to the enum values. */ - static const gchar *abbr_names[G_DATE_DECEMBER + 1]; - static const gchar *full_names[G_DATE_DECEMBER + 1]; - static gboolean first_time = TRUE; - - g_return_val_if_fail (month >= G_DATE_JANUARY, NULL); - g_return_val_if_fail (month <= G_DATE_DECEMBER, NULL); - - if (G_UNLIKELY (first_time)) { - gchar buffer[256]; - GDateMonth ii; - GDate date; - - memset (abbr_names, 0, sizeof (abbr_names)); - memset (full_names, 0, sizeof (full_names)); - - /* First Julian day was in January. */ - g_date_set_julian (&date, 1); - - for (ii = G_DATE_JANUARY; ii <= G_DATE_DECEMBER; ii++) { - g_date_strftime (buffer, sizeof (buffer), "%b", &date); - abbr_names[ii] = g_intern_string (buffer); - g_date_strftime (buffer, sizeof (buffer), "%B", &date); - full_names[ii] = g_intern_string (buffer); - g_date_add_months (&date, 1); - } - - first_time = FALSE; - } - - return abbreviated ? abbr_names[month] : full_names[month]; -} - -/** - * e_get_weekday_name: - * @weekday: weekday index - * @abbreviated: if %TRUE, abbreviate the weekday name - * - * Returns the localized name for @weekday. If @abbreviated is %TRUE, - * returns the locale's abbreviated weekday name. - * - * Returns: localized weekday name - **/ -const gchar * -e_get_weekday_name (GDateWeekday weekday, - gboolean abbreviated) -{ - /* Make the indices correspond to the enum values. */ - static const gchar *abbr_names[G_DATE_SUNDAY + 1]; - static const gchar *full_names[G_DATE_SUNDAY + 1]; - static gboolean first_time = TRUE; - - g_return_val_if_fail (weekday >= G_DATE_MONDAY, NULL); - g_return_val_if_fail (weekday <= G_DATE_SUNDAY, NULL); - - if (G_UNLIKELY (first_time)) { - gchar buffer[256]; - GDateWeekday ii; - GDate date; - - memset (abbr_names, 0, sizeof (abbr_names)); - memset (full_names, 0, sizeof (full_names)); - - /* First Julian day was a Monday. */ - g_date_set_julian (&date, 1); - - for (ii = G_DATE_MONDAY; ii <= G_DATE_SUNDAY; ii++) { - g_date_strftime (buffer, sizeof (buffer), "%a", &date); - abbr_names[ii] = g_intern_string (buffer); - g_date_strftime (buffer, sizeof (buffer), "%A", &date); - full_names[ii] = g_intern_string (buffer); - g_date_add_days (&date, 1); - } - - first_time = FALSE; - } - - return abbreviated ? abbr_names[weekday] : full_names[weekday]; -} - -/* Evolution Locks for crash recovery */ -static const gchar * -get_lock_filename (void) -{ - static gchar *filename = NULL; - - if (G_UNLIKELY (filename == NULL)) - filename = g_build_filename ( - e_get_user_config_dir (), ".running", NULL); - - return filename; -} - -gboolean -e_file_lock_create (void) -{ - const gchar *filename = get_lock_filename (); - gboolean status = FALSE; - FILE *file; - - file = g_fopen (filename, "w"); - if (file != NULL) { - /* The lock file also serves as a PID file. */ - g_fprintf ( - file, "%" G_GINT64_FORMAT "\n", - (gint64) getpid ()); - fclose (file); - status = TRUE; - } else { - const gchar *errmsg = g_strerror (errno); - g_warning ("Lock file creation failed: %s", errmsg); - } - - return status; -} - -void -e_file_lock_destroy (void) -{ - const gchar *filename = get_lock_filename (); - - if (g_unlink (filename) == -1) { - const gchar *errmsg = g_strerror (errno); - g_warning ("Lock file deletion failed: %s", errmsg); - } -} - -gboolean -e_file_lock_exists (void) -{ - const gchar *filename = get_lock_filename (); - - return g_file_test (filename, G_FILE_TEST_EXISTS); -} - -/** - * e_util_guess_mime_type: - * @filename: a local file name, or URI - * @localfile: %TRUE to check the file content, FALSE to check only the name - * - * Tries to determine the MIME type for @filename. Free the returned - * string with g_free(). - * - * Returns: the MIME type of @filename, or %NULL if the the MIME type could - * not be determined - **/ -gchar * -e_util_guess_mime_type (const gchar *filename, - gboolean localfile) -{ - gchar *mime_type = NULL; - - g_return_val_if_fail (filename != NULL, NULL); - - if (localfile) { - GFile *file; - GFileInfo *fi; - - if (strstr (filename, "://")) - file = g_file_new_for_uri (filename); - else - file = g_file_new_for_path (filename); - - fi = g_file_query_info ( - file, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, - G_FILE_QUERY_INFO_NONE, NULL, NULL); - if (fi) { - mime_type = g_content_type_get_mime_type ( - g_file_info_get_content_type (fi)); - g_object_unref (fi); - } - - g_object_unref (file); - } - - if (!mime_type) { - /* file doesn't exists locally, thus guess based on the filename */ - gboolean uncertain = FALSE; - gchar *content_type; - - content_type = g_content_type_guess (filename, NULL, 0, &uncertain); - if (content_type) { - mime_type = g_content_type_get_mime_type (content_type); - g_free (content_type); - } - } - - return mime_type; -} - -/* XXX: Should e-util/ really depend on filter/ ?? */ -GSList * -e_util_get_category_filter_options (void) -{ - GSList *res = NULL; - GList *clist, *l; - - clist = e_categories_get_list (); - for (l = clist; l; l = l->next) { - const gchar *cname = l->data; - struct _filter_option *fo; - - if (!e_categories_is_searchable (cname)) - continue; - - fo = g_new0 (struct _filter_option, 1); - - fo->title = g_strdup (cname); - fo->value = g_strdup (cname); - res = g_slist_prepend (res, fo); - } - - g_list_free (clist); - - return g_slist_reverse (res); -} - -/** - * e_util_get_searchable_categories: - * - * Returns list of searchable categories only. The list should - * be freed with g_list_free() when done with it, but the items - * are internal strings, names of categories, which should not - * be touched in other than read-only way, in other words the same - * restrictions as for e_categories_get_list() applies here too. - **/ -GList * -e_util_get_searchable_categories (void) -{ - GList *res = NULL, *all_categories, *l; - - all_categories = e_categories_get_list (); - for (l = all_categories; l; l = l->next) { - const gchar *cname = l->data; - - if (e_categories_is_searchable (cname)) - res = g_list_prepend (res, (gpointer) cname); - } - - g_list_free (all_categories); - - return g_list_reverse (res); -} - -/** - * e_binding_transform_color_to_string: - * @binding: a #GBinding - * @source_value: a #GValue of type #GDK_TYPE_COLOR - * @target_value: a #GValue of type #G_TYPE_STRING - * @not_used: not used - * - * Transforms a #GdkColor value to a color string specification. - * - * Returns: %TRUE always - **/ -gboolean -e_binding_transform_color_to_string (GBinding *binding, - const GValue *source_value, - GValue *target_value, - gpointer not_used) -{ - const GdkColor *color; - gchar *string; - - g_return_val_if_fail (G_IS_BINDING (binding), FALSE); - - color = g_value_get_boxed (source_value); - if (!color) { - g_value_set_string (target_value, ""); - } else { - /* encode color manually, because css styles expect colors in #rrggbb, - * not in #rrrrggggbbbb, which is a result of gdk_color_to_string() - */ - string = g_strdup_printf ( - "#%02x%02x%02x", - (gint) color->red * 256 / 65536, - (gint) color->green * 256 / 65536, - (gint) color->blue * 256 / 65536); - g_value_set_string (target_value, string); - g_free (string); - } - - return TRUE; -} - -/** - * e_binding_transform_string_to_color: - * @binding: a #GBinding - * @source_value: a #GValue of type #G_TYPE_STRING - * @target_value: a #GValue of type #GDK_TYPE_COLOR - * @not_used: not used - * - * Transforms a color string specification to a #GdkColor. - * - * Returns: %TRUE if color string specification was valid - **/ -gboolean -e_binding_transform_string_to_color (GBinding *binding, - const GValue *source_value, - GValue *target_value, - gpointer not_used) -{ - GdkColor color; - const gchar *string; - gboolean success = FALSE; - - g_return_val_if_fail (G_IS_BINDING (binding), FALSE); - - string = g_value_get_string (source_value); - if (gdk_color_parse (string, &color)) { - g_value_set_boxed (target_value, &color); - success = TRUE; - } - - return success; -} - -/** - * e_binding_transform_source_to_uid: - * @binding: a #GBinding - * @source_value: a #GValue of type #E_TYPE_SOURCE - * @target_value: a #GValue of type #G_TYPE_STRING - * @registry: an #ESourceRegistry - * - * Transforms an #ESource object to its UID string. - * - * Returns: %TRUE if @source_value was an #ESource object - **/ -gboolean -e_binding_transform_source_to_uid (GBinding *binding, - const GValue *source_value, - GValue *target_value, - ESourceRegistry *registry) -{ - ESource *source; - const gchar *string; - gboolean success = FALSE; - - g_return_val_if_fail (G_IS_BINDING (binding), FALSE); - g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), FALSE); - - source = g_value_get_object (source_value); - if (E_IS_SOURCE (source)) { - string = e_source_get_uid (source); - g_value_set_string (target_value, string); - success = TRUE; - } - - return success; -} - -/** - * e_binding_transform_uid_to_source: - * @binding: a #GBinding - * @source_value: a #GValue of type #G_TYPE_STRING - * @target_value: a #GValue of type #E_TYPE_SOURCe - * @registry: an #ESourceRegistry - * - * Transforms an #ESource UID string to the corresponding #ESource object - * in @registry. - * - * Returns: %TRUE if @registry had an #ESource object with a matching - * UID string - **/ -gboolean -e_binding_transform_uid_to_source (GBinding *binding, - const GValue *source_value, - GValue *target_value, - ESourceRegistry *registry) -{ - ESource *source; - const gchar *string; - gboolean success = FALSE; - - g_return_val_if_fail (G_IS_BINDING (binding), FALSE); - g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), FALSE); - - string = g_value_get_string (source_value); - if (string == NULL || *string == '\0') - return FALSE; - - source = e_source_registry_ref_source (registry, string); - if (source != NULL) { - g_value_take_object (target_value, source); - success = TRUE; - } - - return success; -} diff --git a/e-util/e-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 * - * - * Authors: - * Chris Lahey - * - * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) - * */ #ifndef E_UTIL_H #define E_UTIL_H -#include -#include -#include +#define __E_UTIL_H_INSIDE__ #include -#include - -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include - -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "e-web-view-gtkhtml.h" + +#include +#include + +#include +#include + +#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 = +"" +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +""; + +/* 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, + "" + "" + "" + "" + ""); + + g_string_append ( + buffer, + "
" + "" + "" + "" + "
" + "" + ""); + + g_string_append_printf ( + buffer, + "" + "" + "" + "", + icon_uri, + e_alert_get_primary_text (alert), + e_alert_get_secondary_text (alert)); + + g_string_append ( + buffer, + "
" + "" + "" + "

%s

" + "%s" + "
" + "
" + "" + ""); + + 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 + * + */ + +/* 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 should be included directly." +#endif + +#ifndef E_WEB_VIEW_GTKHTML_H +#define E_WEB_VIEW_GTKHTML_H + +#include + +/* 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 + * + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "e-web-view-preview.h" + +#include +#include + +#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 (""); +} + +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, "
"); + + 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
*/ + if (strchr (res, '\r')) { + end = replace_string (res, "\r", ""); + g_free (res); + res = end; + } + + end = replace_string (res, "\n", "
"); + 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, "%s", 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, "%s", 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, "%s", 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, "
"); +} + +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, " "); +} + +/* 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, "%s%s", 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 + * + * + * 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 should be included directly." +#endif + +#ifndef E_WEB_VIEW_PREVIEW_H +#define E_WEB_VIEW_PREVIEW_H + +#include + +/* 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 + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "e-web-view.h" + +#include + +#include +#include +#include +#include + +#include +#include + +#define LIBSOUP_USE_UNSTABLE_REQUEST_API +#include +#include + +#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 = +"" +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +""; + +/* 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, + "" + "" + "" + "" + ""); + + g_string_append ( + buffer, + "" + "" + "" + "" + "
" + "" + ""); + + g_string_append_printf ( + buffer, + "" + "" + "" + "", + icon_name, + GTK_ICON_SIZE_DIALOG, + e_alert_get_primary_text (alert), + e_alert_get_secondary_text (alert)); + + g_string_append ( + buffer, + "
" + "" + "" + "

%s

" + "%s" + "
" + "
" + "" + ""); + + 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 ( + "", + 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 + * + */ + +/* 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 should be included directly." +#endif + +#ifndef E_WEB_VIEW_H +#define E_WEB_VIEW_H + +#include + +/* 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 + * + * + * Authors: + * Chris Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "e-xml-utils.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#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 + * + * + * Authors: + * Chris Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifndef __E_XML_UTILS__ +#define __E_XML_UTILS__ + +#include + +#include + +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 + * + * + * Authors: + * Bolian Yin + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#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 + * + * + * Authors: + * Bolian Yin + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION) +#error "Only should be included directly." +#endif + +#ifndef __EA_CALENDAR_CELL_H__ +#define __EA_CALENDAR_CELL_H__ + +#include +#include + +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 + * + * + * Authors: + * Bolian Yin + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include + +#include + +#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 + * + * + * Authors: + * Bolian Yin + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION) +#error "Only should be included directly." +#endif + +#ifndef __EA_CALENDAR_ITEM_H__ +#define __EA_CALENDAR_ITEM_H__ + +#include +#include + +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 + * + * + * Authors: + * Bolian Yin + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include +#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 + * + * + * Authors: + * Bolian Yin + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION) +#error "Only should be included directly." +#endif + +/* EaCellTable */ + +#include + +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 + * + * + * Authors: + * Bolian Yin + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION) +#error "Only should be included directly." +#endif + +/* Evolution Accessibility +*/ + +#ifndef _EA_FACTORY_H__ +#define _EA_FACTORY_H__ + +#include + +#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 + * + * + * Authors: + * Bolian Yin + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include +#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 + * + * + * Authors: + * Bolian Yin + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION) +#error "Only 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 + * + */ + +#include +#include +#include + +#include + +/* 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 @@ + + + + + <_primary>Missing date. + <_secondary>You must choose a date. + + + + <_primary>Missing filename. + <_secondary>You must specify a filename. + + + + <_primary>File "{0}" does not exist or is not a regular file. + <_secondary>You must specify a filename. + + + + <_primary>Bad regular expression "{0}". + <_secondary>Could not compile regular expression "{1}". + + + + <_primary>Missing name. + <_secondary>You must name this filter. + + + + <_primary>Name "{0}" already used. + <_secondary>Please choose another name. + + + 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 @@ + + + + + + 1 + 1000 + 1 + 10 + + + + + + + + + Incoming + + + + + + + + + + + the current time + + + the time you specify + + + a time relative to the current time + + + + + + + + + + + seconds + + + minutes + + + hours + + + days + + + weeks + + + months + + + years + + + + + + + + + + + ago + + + in the future + + + + + + + + + + + + + + + True + 12 + vertical + 6 + + + True + 0 + Show filters for mail: + + + False + False + 0 + + + + + True + model1 + + + + 0 + + + + + False + False + 1 + + + + + True + vertical + 6 + + + True + 0 + _Filter Rules + True + + + + + + False + False + 0 + + + + + True + 12 + + + True + + + False + False + 0 + + + + + True + 6 + + + True + True + automatic + automatic + in + + + True + True + rule_list_store + False + + + + Enabled + + + + 2 + + + + + + + Rule Name + + + + 0 + + + + + + + + + 0 + + + + + True + 6 + + + True + vertical + 6 + + + gtk-add + True + True + True + False + True + + + False + False + 0 + + + + + _Edit + True + True + True + False + True + + + False + False + 1 + + + + + gtk-remove + True + True + True + False + True + + + False + False + 2 + + + + + gtk-goto-top + True + True + True + False + True + + + False + False + 3 + + + + + gtk-go-up + True + True + True + False + True + + + False + False + 4 + + + + + gtk-go-down + True + True + True + False + True + + + False + False + 5 + + + + + gtk-goto-bottom + True + True + True + False + True + + + False + False + 6 + + + + + False + False + 0 + + + + + False + False + 1 + + + + + 1 + + + + + 1 + + + + + 3 + 2 + + + + + True + vertical + 6 + + + True + 4 + 6 + + + True + Compare against + center + + + False + False + 0 + + + + + True + model2 + + + + 0 + + + + + False + False + 1 + + + + + False + False + 0 + + + + + True + + + False + 1 + 1 + + + + + True + False + False + + + True + vertical + + + True + The message's date will be compared against +the current time when filtering occurs. + center + + + False + False + 0 + + + + + False + + + + + True + label1 + center + + + False + + + + + True + vertical + + + True + The message's date will be compared against +12:00am of the date specified. + center + + + False + False + 0 + + + + + True + True + + + 1 + + + + + 1 + + + + + True + label2 + center + + + 1 + False + + + + + True + vertical + + + True + 15 + The message's date will be compared against +a time relative to when filtering occurs. + center + + + False + False + 0 + + + + + True + 5 + 58 + + + True + True + + + True + True + adjustment1 + 1 + + + 0 + + + + + True + model3 + 0 + + + + 0 + + + + + False + False + 1 + + + + + True + model4 + 0 + + + + 0 + + + + + False + 2 + + + + + + + False + 2 + 1 + + + + + 2 + + + + + True + label3 + center + + + 2 + False + + + + + 2 + + + + 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 + * + * + * Authors: + * Yang Wu + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "gal-a11y-e-cell-popup.h" + +#include +#include +#include + +#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"), + "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 + * + * + * Authors: + * Yang Wu + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION) +#error "Only should be included directly." +#endif + +#ifndef __GAL_A11Y_E_CELL_POPUP_H__ +#define __GAL_A11Y_E_CELL_POPUP_H__ + +#include + +#include +#include + +#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 + * + * + * Authors: + * Christopher James Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include +#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 + * + * + * Authors: + * Christopher James Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION) +#error "Only should be included directly." +#endif + +#ifndef __GAL_A11Y_E_CELL_REGISTRY_H__ +#define __GAL_A11Y_E_CELL_REGISTRY_H__ + +#include + +#include +#include + +#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 + * + * + * Authors: + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "gal-a11y-e-cell-toggle.h" + +#include +#include + +#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 + * + * + * Authors: + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION) +#error "Only should be included directly." +#endif + +#ifndef __GAL_A11Y_E_CELL_TOGGLE_H__ +#define __GAL_A11Y_E_CELL_TOGGLE_H__ + +#include +#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 + * + * + * Authors: + * Tim Wo + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "gal-a11y-e-cell-tree.h" + +#include +#include + +#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 + * + * + * Authors: + * Tim Wo + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION) +#error "Only should be included directly." +#endif + +#ifndef __GAL_A11Y_E_CELL_TREE_H__ +#define __GAL_A11Y_E_CELL_TREE_H__ + +#include +#include +#include + +#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 + * + * + * Authors: + * Eric Zhao + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * Copyright (C) 2004 Sun Microsystem, Inc. + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "gal-a11y-e-cell-vbox.h" + +#include + +#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 + * + * + * Authors: + * Eric Zhao + * + * 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 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 + * + * + * Authors: + * Christopher James Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "gal-a11y-e-cell.h" + +#include + +#include +#include + +#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 + * + * + * Authors: + * Christopher James Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION) +#error "Only should be included directly." +#endif + +#ifndef __GAL_A11Y_E_CELL_H__ +#define __GAL_A11Y_E_CELL_H__ + +#include +#include + +#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 + * + * + * Authors: + * Yuedong Du + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "gal-a11y-e-table-click-to-add-factory.h" + +#include + +#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 + * + * + * Authors: + * Yuedong Du + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION) +#error "Only 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 + +#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 + * + * + * Authors: + * Yuedong Du + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "gal-a11y-e-table-click-to-add.h" + +#include +#include + +#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 + * + * + * Authors: + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION) +#error "Only should be included directly." +#endif + +#ifndef __GAL_A11Y_E_TABLE_CLICK_TO_ADD_H__ +#define __GAL_A11Y_E_TABLE_CLICK_TO_ADD_H__ + +#include +#include + +#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 + * + * + * Authors: + * Li Yuan + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "gal-a11y-e-table-column-header.h" + +#include +#include +#include + +#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 + * + * + * Authors: + * Li Yuan + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION) +#error "Only should be included directly." +#endif + +#ifndef __GAL_A11Y_E_TABLE_COLUMN_HEADER_H__ +#define __GAL_A11Y_E_TABLE_COLUMN_HEADER_H__ + +#include + +#include +#include + +#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 + * + * + * Authors: + * Christopher James Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include +#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 + * + * + * Authors: + * Christopher James Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION) +#error "Only should be included directly." +#endif + +#ifndef __GAL_A11Y_E_TABLE_FACTORY_H__ +#define __GAL_A11Y_E_TABLE_FACTORY_H__ + +#include + +#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 + * + * + * Authors: + * Yuedong Du + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "gal-a11y-e-table-item-factory.h" + +#include + +#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 + * + * + * Authors: + * Yuedong Du + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION) +#error "Only should be included directly." +#endif + +#ifndef __GAL_A11Y_E_TABLE_ITEM_FACTORY_H__ +#define __GAL_A11Y_E_TABLE_ITEM_FACTORY_H__ + +#include + +#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 + * + * + * Authors: + * Christopher James Lahey + * Bolian Yin + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "gal-a11y-e-table-item.h" + +#include + +#include + +#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 + * + * + * Authors: + * Christopher James Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION) +#error "Only should be included directly." +#endif + +#ifndef __GAL_A11Y_E_TABLE_ITEM_H__ +#define __GAL_A11Y_E_TABLE_ITEM_H__ + +#include + +#include + +#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 + * + * + * Authors: + * Christopher James Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include +#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 + * + * + * Authors: + * Christopher James Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION) +#error "Only should be included directly." +#endif + +#ifndef __GAL_A11Y_E_TABLE_H__ +#define __GAL_A11Y_E_TABLE_H__ + +#include +#include +#include + +#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 + * + * + * Authors: + * Christopher James Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include +#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 + * + * + * Authors: + * Christopher James Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION) +#error "Only should be included directly." +#endif + +#ifndef __GAL_A11Y_E_TEXT_FACTORY_H__ +#define __GAL_A11Y_E_TEXT_FACTORY_H__ + +#include + +#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 + * + * + * Authors: + * Christopher James Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "gal-a11y-e-text.h" + +#include + +#include + +#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 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 + * + * + * Authors: + * Christopher James Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION) +#error "Only should be included directly." +#endif + +#ifndef __GAL_A11Y_E_TEXT_H__ +#define __GAL_A11Y_E_TEXT_H__ + +#include + +#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 + * + * + * Authors: + * Yuedong Du + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include +#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 + * + * + * Authors: + * Yuedong Du + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION) +#error "Only should be included directly." +#endif + +#ifndef __GAL_A11Y_E_TREE_FACTORY_H__ +#define __GAL_A11Y_E_TREE_FACTORY_H__ + +#include + +#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 + * + * + * Authors: + * Yuedong Du + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include +#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 + * + * + * Authors: + * Yuedong Du + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION) +#error "Only should be included directly." +#endif + +#ifndef __GAL_A11Y_E_TREE_H__ +#define __GAL_A11Y_E_TREE_H__ + +#include +#include +#include + +#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 + * + * + * Authors: + * Gilbert Fang + * + * 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 should be included directly." +#endif + +#ifndef _GAL_A11Y_FACTORY_H__ +#define _GAL_A11Y_FACTORY_H__ + +#include +#include + +#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 + * + * + * Authors: + * Christopher James Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include +#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 + * + * + * Authors: + * Christopher James Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION) +#error "Only should be included directly." +#endif + +#ifndef __GAL_A11Y_UTIL_H__ +#define __GAL_A11Y_UTIL_H__ + +#include + +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 + * + * + * Authors: + * Chris Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#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 + * + * + * Authors: + * Chris Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION) +#error "Only should be included directly." +#endif + +#ifndef GAL_DEFINE_VIEWS_DIALOG_H +#define GAL_DEFINE_VIEWS_DIALOG_H + +#include +#include + +/* 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 + * + * + * Authors: + * Chris Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include + +#include + +#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 + * + * + * Authors: + * Chris Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION) +#error "Only should be included directly." +#endif + +#ifndef _GAL_DEFINE_VIEWS_MODEL_H_ +#define _GAL_DEFINE_VIEWS_MODEL_H_ + +#include +#include +#include + +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 @@ + + + + + Define Views for "%s" + GDK_WINDOW_TYPE_HINT_NORMAL + + + True + 6 + + + True + 2 + 1 + 6 + 6 + + + True + 0 + Define Views for %s + + + GTK_FILL + + + + + + True + 6 + 6 + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + GTK_POLICY_NEVER + GTK_POLICY_AUTOMATIC + GTK_SHADOW_IN + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + True + True + True + + + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 6 + GTK_BUTTONBOX_START + + + True + True + gtk-new + True + + + False + False + + + + + True + False + True + True + + + True + 0 + 0 + + + True + 2 + + + True + gtk-properties + + + False + False + + + + + True + _Edit + True + + + False + False + 1 + + + + + + + + + False + False + 1 + + + + + True + True + gtk-delete + True + + + False + False + 2 + + + + + False + False + 1 + + + + + 1 + 2 + + + + + 12 + 2 + + + + + True + GTK_BUTTONBOX_END + + + True + True + True + gtk-close + True + + + + + False + GTK_PACK_END + + + + + + button7 + + + 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 + * + * + * Authors: + * Chris Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "gal-view-collection.h" + +#include +#include +#include + +#include +#include + +#include + +#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 + * + * + * Authors: + * Chris Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION) +#error "Only should be included directly." +#endif + +#ifndef _GAL_VIEW_SET_H_ +#define _GAL_VIEW_SET_H_ + +#include + +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 + * + * + * Authors: + * Chris Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include +#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 + * + * + * Authors: + * Chris Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION) +#error "Only should be included directly." +#endif + +#ifndef GAL_VIEW_ETABLE_H +#define GAL_VIEW_ETABLE_H + +#include +#include +#include +#include +#include +#include + +/* 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 + * + * + * Authors: + * Chris Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#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 + * + * + * Authors: + * Chris Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION) +#error "Only should be included directly." +#endif + +#ifndef GAL_VIEW_FACTORY_ETABLE_H +#define GAL_VIEW_FACTORY_ETABLE_H + +#include +#include +#include + +/* 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 + * + * + * Authors: + * Chris Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include +#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 + * + * + * Authors: + * Chris Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION) +#error "Only should be included directly." +#endif + +#ifndef GAL_VIEW_FACTORY_H +#define GAL_VIEW_FACTORY_H + +#include + +/* 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 + * + * + * Authors: + * Chris Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "gal-view-instance-save-as-dialog.h" + +#include + +#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 + * + * + * Authors: + * Chris Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION) +#error "Only should be included directly." +#endif + +#ifndef GAL_VIEW_INSTANCE_SAVE_AS_DIALOG_H +#define GAL_VIEW_INSTANCE_SAVE_AS_DIALOG_H + +#include +#include +#include + +/* 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 @@ + + + + + + True + 5 + vertical + 12 + + + True + vertical + 6 + + + _Create new view + True + True + False + True + True + True + + + False + False + 0 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 24 + + + True + 6 + + + True + _Name: + True + entry-create + + + False + False + 0 + + + + + True + False + True + + + + 1 + + + + + + + 1 + + + + + False + False + 0 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + vertical + 6 + + + _Replace existing view + True + True + False + True + True + radiobutton-create + + + False + False + 0 + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + 24 + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + automatic + automatic + in + + + True + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + + + + + 1 + + + + + 1 + + + + 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 + * + * + * Authors: + * Chris Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "gal-view-instance.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#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 + * + * + * Authors: + * Chris Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION) +#error "Only should be included directly." +#endif + +#ifndef _GAL_VIEW_INSTANCE_H_ +#define _GAL_VIEW_INSTANCE_H_ + +#include + +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 + * + * + * Authors: + * Chris Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "gal-view-new-dialog.h" + +#include + +#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 + * + * + * Authors: + * Chris Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION) +#error "Only should be included directly." +#endif + +#ifndef __GAL_VIEW_NEW_DIALOG_H__ +#define __GAL_VIEW_NEW_DIALOG_H__ + +#include +#include + +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 @@ + + + + + + GTK_WINDOW_TOPLEVEL + GTK_WIN_POS_NONE + False + True + False + True + False + False + GDK_WINDOW_TYPE_HINT_DIALOG + GDK_GRAVITY_NORTH_WEST + + + True + False + 8 + + + True + GTK_BUTTONBOX_END + + + True + True + True + gtk-ok + True + GTK_RELIEF_NORMAL + True + + + + + True + True + True + gtk-cancel + True + GTK_RELIEF_NORMAL + True + + + + + 0 + False + True + GTK_PACK_END + + + + + True + 4 + 1 + False + 6 + 6 + + + True + Name of new view: + False + False + GTK_JUSTIFY_CENTER + False + False + 0 + 0.5 + 0 + 0 + entry-name + + + 0 + 1 + 0 + 1 + fill + + + + + + True + True + True + True + 0 + + True + False + + + 0 + 1 + 1 + 2 + fill + + + + + True + Type of view: + False + False + GTK_JUSTIFY_CENTER + False + False + 0 + 0.5 + 0 + 0 + + + 0 + 1 + 2 + 3 + fill + + + + + + True + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + GTK_SHADOW_IN + GTK_CORNER_TOP_LEFT + + + True + True + False + False + False + True + + + + + + Type of View + + + + + + + 0 + 1 + 3 + 4 + fill + + + + + 0 + True + True + + + + + + button1 + button3 + + + 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 + * + * + * Authors: + * Chris Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include +#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 + * + * + * Authors: + * Chris Lahey + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION) +#error "Only should be included directly." +#endif + +#ifndef GAL_VIEW_H +#define GAL_VIEW_H + +#include +#include + +/* 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 + * + * + * Authors: + * Damon Chaplin + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +/* + * test-calendar - tests the ECalendar widget. + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include + +/* 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 + +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 + */ + +#include + +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 + * + * + * Authors: + * Damon Chaplin + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +/* + * test-dateedit - tests the EDateEdit widget. + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#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 + * + */ + +#include + +#include + +#include + +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 ("
%s
", 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 ("EMailSignatureComboBox"); + 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 ("EMailSignatureManager"); + 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 + */ + +#include +#include + +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 + * + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#include "e-preferences-window.c" + +#include + +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 + */ + +#include +#include + +#include + +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 +#include + +#include + +#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 + */ + +#include + +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 @@ + + + + + <_primary>Do you wish to save your changes? + <_secondary xml:space="preserve">This signature has been changed, but has not been saved. +