From 8650fb139a9143f04615de74ff569bce3e0c4ce3 Mon Sep 17 00:00:00 2001 From: Tomas Popela Date: Mon, 9 Jun 2014 16:32:25 +0200 Subject: Bug 540362: [webkit-composer] Use webkit for composer Merge wip/webkit-composer branch into master. --- addressbook/gui/contact-editor/Makefile.am | 6 +- addressbook/gui/contact-list-editor/Makefile.am | 6 +- addressbook/gui/widgets/Makefile.am | 8 +- addressbook/gui/widgets/eab-contact-formatter.c | 8 +- addressbook/importers/Makefile.am | 8 +- addressbook/printing/Makefile.am | 6 +- addressbook/util/Makefile.am | 8 +- art/Makefile.am | 12 +- art/confidential-stamp.jpg | Bin 0 -> 5708 bytes art/draft-paper.png | Bin 0 -> 597 bytes art/draft-stamp.jpg | Bin 0 -> 4660 bytes art/midnight-stars.jpg | Bin 0 -> 7190 bytes art/paper.png | Bin 0 -> 266 bytes art/rect.png | Bin 0 -> 3795 bytes art/ribbon.jpg | Bin 0 -> 3193 bytes art/texture.png | Bin 0 -> 137 bytes calendar/alarm-notify/Makefile.am | 8 +- calendar/gui/Makefile.am | 8 +- calendar/gui/dialogs/Makefile.am | 6 +- calendar/importers/Makefile.am | 6 +- composer/Makefile.am | 8 +- composer/e-composer-actions.c | 81 +- composer/e-composer-actions.h | 4 +- composer/e-composer-activity.c | 187 - composer/e-composer-activity.h | 64 - composer/e-composer-private.c | 858 ++- composer/e-composer-private.h | 25 +- composer/e-composer-spell-header.c | 13 - composer/e-composer-spell-header.h | 3 - composer/e-msg-composer.c | 1318 ++-- composer/e-msg-composer.h | 30 +- configure.ac | 58 +- data/evolution.convert | 1 - data/org.gnome.evolution.mail.gschema.xml.in | 15 +- doc/reference/evolution-mail-composer/Makefile.am | 2 - .../evolution-mail-composer-docs.sgml | 1 - .../evolution-mail-composer-sections.txt | 19 - .../evolution-mail-composer.types | 2 - doc/reference/evolution-mail-formatter/Makefile.am | 2 - doc/reference/evolution-shell/Makefile.am | 2 - doc/reference/evolution-util/Makefile.am | 5 +- .../evolution-util/evolution-util-docs.sgml | 30 +- .../evolution-util/evolution-util-sections.txt | 1081 +++- doc/reference/evolution-util/evolution-util.types | 26 +- e-util/Makefile.am | 67 +- e-util/e-action-combo-box.c | 42 +- e-util/e-action-combo-box.h | 1 + e-util/e-color-chooser-widget.c | 253 + e-util/e-color-chooser-widget.h | 71 + e-util/e-color-combo.c | 976 +++ e-util/e-color-combo.h | 96 + e-util/e-emoticon-action.c | 278 + e-util/e-emoticon-action.h | 73 + e-util/e-emoticon-chooser-menu.c | 184 + e-util/e-emoticon-chooser-menu.h | 70 + e-util/e-emoticon-chooser.c | 178 + e-util/e-emoticon-chooser.h | 77 + e-util/e-emoticon-tool-button.c | 695 +++ e-util/e-emoticon-tool-button.h | 75 + e-util/e-emoticon.c | 118 + e-util/e-emoticon.h | 53 + e-util/e-focus-tracker.c | 45 + e-util/e-html-editor-actions.c | 2028 +++++++ e-util/e-html-editor-actions.h | 155 + e-util/e-html-editor-cell-dialog.c | 872 +++ e-util/e-html-editor-cell-dialog.h | 72 + e-util/e-html-editor-dialog.c | 248 + e-util/e-html-editor-dialog.h | 74 + e-util/e-html-editor-find-dialog.c | 224 + e-util/e-html-editor-find-dialog.h | 73 + e-util/e-html-editor-hrule-dialog.c | 421 ++ e-util/e-html-editor-hrule-dialog.h | 70 + e-util/e-html-editor-image-dialog.c | 703 +++ e-util/e-html-editor-image-dialog.h | 73 + e-util/e-html-editor-link-dialog.c | 390 ++ e-util/e-html-editor-link-dialog.h | 70 + e-util/e-html-editor-manager.ui | 181 + e-util/e-html-editor-page-dialog.c | 513 ++ e-util/e-html-editor-page-dialog.h | 70 + e-util/e-html-editor-paragraph-dialog.c | 154 + e-util/e-html-editor-paragraph-dialog.h | 71 + e-util/e-html-editor-private.h | 103 + e-util/e-html-editor-replace-dialog.c | 288 + e-util/e-html-editor-replace-dialog.h | 71 + e-util/e-html-editor-selection.c | 5576 +++++++++++++++++ e-util/e-html-editor-selection.h | 250 + e-util/e-html-editor-spell-check-dialog.c | 710 +++ e-util/e-html-editor-spell-check-dialog.h | 73 + e-util/e-html-editor-table-dialog.c | 866 +++ e-util/e-html-editor-table-dialog.h | 69 + e-util/e-html-editor-text-dialog.c | 298 + e-util/e-html-editor-text-dialog.h | 69 + e-util/e-html-editor-utils.c | 116 + e-util/e-html-editor-utils.h | 44 + e-util/e-html-editor-view.c | 6303 ++++++++++++++++++++ e-util/e-html-editor-view.h | 164 + e-util/e-html-editor.c | 1178 ++++ e-util/e-html-editor.h | 108 + e-util/e-image-chooser-dialog.c | 223 + e-util/e-image-chooser-dialog.h | 74 + e-util/e-mail-signature-editor.c | 319 +- e-util/e-mail-signature-editor.h | 8 +- e-util/e-mail-signature-manager.c | 18 +- e-util/e-mail-signature-preview.c | 8 +- e-util/e-misc-utils.c | 44 + e-util/e-misc-utils.h | 3 + e-util/e-name-selector-entry.c | 6 +- e-util/e-spell-checker.c | 783 +++ e-util/e-spell-checker.h | 95 + e-util/e-spell-dictionary.c | 797 +++ e-util/e-spell-dictionary.h | 99 + e-util/e-spell-entry.c | 373 +- e-util/e-spell-entry.h | 7 +- e-util/e-util-enums.h | 221 + e-util/e-util.h | 26 +- e-util/e-web-view-gtkhtml.c | 2352 -------- e-util/e-web-view-gtkhtml.h | 209 - e-util/e-web-view.c | 150 +- e-util/e-web-view.h | 13 + e-util/test-html-editor.c | 497 ++ em-format/Makefile.am | 8 +- em-format/e-mail-formatter-enums.h | 18 - em-format/e-mail-formatter-quote-attachment.c | 9 +- em-format/e-mail-formatter-quote-text-html.c | 2 +- em-format/e-mail-formatter-quote-text-plain.c | 4 +- em-format/e-mail-formatter-quote.c | 54 +- em-format/e-mail-formatter.c | 10 +- em-format/e-mail-formatter.h | 4 +- libemail-engine/Makefile.am | 2 - mail/Makefile.am | 5 +- mail/e-http-request.c | 72 +- mail/e-mail-display.c | 130 +- mail/e-mail-reader-utils.c | 1 - mail/e-mail-reader.c | 2 +- mail/em-composer-utils.c | 120 +- mail/em-utils.c | 5 + mail/em-utils.h | 1 + mail/importers/Makefile.am | 6 +- mail/mail-config.ui | 93 +- modules/addressbook/Makefile.am | 8 +- modules/backup-restore/Makefile.am | 4 - modules/backup-restore/evolution-backup-tool.c | 44 +- modules/book-config-google/Makefile.am | 6 +- modules/book-config-ldap/Makefile.am | 8 +- modules/book-config-local/Makefile.am | 6 +- modules/book-config-webdav/Makefile.am | 6 +- modules/cal-config-caldav/Makefile.am | 8 +- modules/cal-config-contacts/Makefile.am | 6 +- modules/cal-config-google/Makefile.am | 8 +- modules/cal-config-local/Makefile.am | 6 +- modules/cal-config-weather/Makefile.am | 8 +- modules/cal-config-webcal/Makefile.am | 6 +- modules/calendar/Makefile.am | 6 +- modules/composer-autosave/Makefile.am | 41 +- modules/composer-autosave/e-composer-autosave.c | 14 +- modules/contact-photos/Makefile.am | 2 - modules/gravatar/Makefile.am | 2 - modules/itip-formatter/Makefile.am | 6 +- modules/itip-formatter/itip-view.c | 16 +- modules/itip-formatter/plugin/Makefile.am | 6 +- modules/mail-config/Makefile.am | 2 - modules/mail/Makefile.am | 6 +- modules/mail/e-mail-shell-backend.c | 19 +- modules/mail/e-mail-shell-view-private.c | 3 +- modules/mail/e-mail-shell-view-private.h | 1 - modules/mail/em-composer-prefs.c | 118 +- modules/mail/em-composer-prefs.h | 5 +- modules/mail/em-mailer-prefs.c | 13 +- modules/mailto-handler/Makefile.am | 6 +- modules/mdn/Makefile.am | 6 +- modules/offline-alert/Makefile.am | 6 +- modules/plugin-lib/Makefile.am | 6 +- modules/plugin-manager/Makefile.am | 6 +- modules/prefer-plain/Makefile.am | 6 +- modules/prefer-plain/plugin/Makefile.am | 6 +- modules/settings/Makefile.am | 6 +- modules/settings/e-settings-deprecated.c | 2 +- modules/settings/e-settings-spell-checker.c | 117 + modules/settings/e-settings-spell-checker.h | 65 + modules/settings/e-settings-web-view-gtkhtml.c | 324 - modules/settings/e-settings-web-view-gtkhtml.h | 64 - modules/settings/e-settings-web-view.c | 189 +- modules/settings/evolution-module-settings.c | 4 +- modules/spamassassin/Makefile.am | 6 +- modules/startup-wizard/Makefile.am | 2 - modules/text-highlight/Makefile.am | 6 +- modules/tnef-attachment/Makefile.am | 8 +- modules/vcard-inline/Makefile.am | 4 +- modules/web-inspector/Makefile.am | 6 +- plugins/attachment-reminder/Makefile.am | 6 +- plugins/bbdb/Makefile.am | 6 +- plugins/dbx-import/Makefile.am | 6 +- plugins/email-custom-header/Makefile.am | 6 +- plugins/email-custom-header/email-custom-header.c | 11 +- plugins/external-editor/Makefile.am | 6 +- plugins/external-editor/external-editor.c | 106 +- plugins/face/Makefile.am | 6 +- plugins/face/face.c | 12 +- plugins/mail-notification/Makefile.am | 6 +- plugins/mail-to-task/Makefile.am | 6 +- plugins/mailing-list-actions/Makefile.am | 6 +- plugins/pst-import/Makefile.am | 8 +- plugins/publish-calendar/Makefile.am | 8 +- plugins/save-calendar/Makefile.am | 6 +- plugins/templates/Makefile.am | 6 +- plugins/templates/templates.c | 6 +- po/POTFILES.in | 19 +- po/POTFILES.skip | 1 + shell/Makefile.am | 8 +- smime/gui/Makefile.am | 8 +- smime/lib/Makefile.am | 8 +- 211 files changed, 32916 insertions(+), 5470 deletions(-) create mode 100644 art/confidential-stamp.jpg create mode 100644 art/draft-paper.png create mode 100644 art/draft-stamp.jpg create mode 100644 art/midnight-stars.jpg create mode 100644 art/paper.png create mode 100644 art/rect.png create mode 100644 art/ribbon.jpg create mode 100644 art/texture.png delete mode 100644 composer/e-composer-activity.c delete mode 100644 composer/e-composer-activity.h create mode 100644 e-util/e-color-chooser-widget.c create mode 100644 e-util/e-color-chooser-widget.h create mode 100644 e-util/e-color-combo.c create mode 100644 e-util/e-color-combo.h create mode 100644 e-util/e-emoticon-action.c create mode 100644 e-util/e-emoticon-action.h create mode 100644 e-util/e-emoticon-chooser-menu.c create mode 100644 e-util/e-emoticon-chooser-menu.h create mode 100644 e-util/e-emoticon-chooser.c create mode 100644 e-util/e-emoticon-chooser.h create mode 100644 e-util/e-emoticon-tool-button.c create mode 100644 e-util/e-emoticon-tool-button.h create mode 100644 e-util/e-emoticon.c create mode 100644 e-util/e-emoticon.h create mode 100644 e-util/e-html-editor-actions.c create mode 100644 e-util/e-html-editor-actions.h create mode 100644 e-util/e-html-editor-cell-dialog.c create mode 100644 e-util/e-html-editor-cell-dialog.h create mode 100644 e-util/e-html-editor-dialog.c create mode 100644 e-util/e-html-editor-dialog.h create mode 100644 e-util/e-html-editor-find-dialog.c create mode 100644 e-util/e-html-editor-find-dialog.h create mode 100644 e-util/e-html-editor-hrule-dialog.c create mode 100644 e-util/e-html-editor-hrule-dialog.h create mode 100644 e-util/e-html-editor-image-dialog.c create mode 100644 e-util/e-html-editor-image-dialog.h create mode 100644 e-util/e-html-editor-link-dialog.c create mode 100644 e-util/e-html-editor-link-dialog.h create mode 100644 e-util/e-html-editor-manager.ui create mode 100644 e-util/e-html-editor-page-dialog.c create mode 100644 e-util/e-html-editor-page-dialog.h create mode 100644 e-util/e-html-editor-paragraph-dialog.c create mode 100644 e-util/e-html-editor-paragraph-dialog.h create mode 100644 e-util/e-html-editor-private.h create mode 100644 e-util/e-html-editor-replace-dialog.c create mode 100644 e-util/e-html-editor-replace-dialog.h create mode 100644 e-util/e-html-editor-selection.c create mode 100644 e-util/e-html-editor-selection.h create mode 100644 e-util/e-html-editor-spell-check-dialog.c create mode 100644 e-util/e-html-editor-spell-check-dialog.h create mode 100644 e-util/e-html-editor-table-dialog.c create mode 100644 e-util/e-html-editor-table-dialog.h create mode 100644 e-util/e-html-editor-text-dialog.c create mode 100644 e-util/e-html-editor-text-dialog.h create mode 100644 e-util/e-html-editor-utils.c create mode 100644 e-util/e-html-editor-utils.h create mode 100644 e-util/e-html-editor-view.c create mode 100644 e-util/e-html-editor-view.h create mode 100644 e-util/e-html-editor.c create mode 100644 e-util/e-html-editor.h create mode 100644 e-util/e-image-chooser-dialog.c create mode 100644 e-util/e-image-chooser-dialog.h create mode 100644 e-util/e-spell-checker.c create mode 100644 e-util/e-spell-checker.h create mode 100644 e-util/e-spell-dictionary.c create mode 100644 e-util/e-spell-dictionary.h delete mode 100644 e-util/e-web-view-gtkhtml.c delete mode 100644 e-util/e-web-view-gtkhtml.h create mode 100644 e-util/test-html-editor.c create mode 100644 modules/settings/e-settings-spell-checker.c create mode 100644 modules/settings/e-settings-spell-checker.h delete mode 100644 modules/settings/e-settings-web-view-gtkhtml.c delete mode 100644 modules/settings/e-settings-web-view-gtkhtml.h diff --git a/addressbook/gui/contact-editor/Makefile.am b/addressbook/gui/contact-editor/Makefile.am index e62b6cf058..8356a7e5a4 100644 --- a/addressbook/gui/contact-editor/Makefile.am +++ b/addressbook/gui/contact-editor/Makefile.am @@ -11,8 +11,8 @@ libecontacteditor_la_CPPFLAGS = \ -DG_LOG_DOMAIN=\"contact-editor\" \ $(EVOLUTION_DATA_SERVER_CFLAGS) \ $(GNOME_PLATFORM_CFLAGS) \ - $(GTKHTML_CFLAGS) \ - $(CODE_COVERAGE_CFLAGS) + $(CODE_COVERAGE_CFLAGS) \ + $(NULL) libecontacteditor_la_SOURCES = \ eab-editor.c \ @@ -36,7 +36,7 @@ libecontacteditor_la_LIBADD = \ $(EVOLUTION_ADDRESSBOOK_LIBS) \ $(EVOLUTION_DATA_SERVER_LIBS) \ $(GNOME_PLATFORM_LIBS) \ - $(GTKHTML_LIBS) + $(NULL) ui_DATA = \ contact-editor.ui \ diff --git a/addressbook/gui/contact-list-editor/Makefile.am b/addressbook/gui/contact-list-editor/Makefile.am index faf2d435f9..c1c2bd2604 100644 --- a/addressbook/gui/contact-list-editor/Makefile.am +++ b/addressbook/gui/contact-list-editor/Makefile.am @@ -11,8 +11,8 @@ libecontactlisteditor_la_CPPFLAGS = \ -DG_LOG_DOMAIN=\"contact-list-editor\" \ $(EVOLUTION_DATA_SERVER_CFLAGS) \ $(GNOME_PLATFORM_CFLAGS) \ - $(GTKHTML_CFLAGS) \ - $(CODE_COVERAGE_CFLAGS) + $(CODE_COVERAGE_CFLAGS) \ + $(NULL) libecontactlisteditor_la_SOURCES = \ e-contact-list-editor.c \ @@ -29,7 +29,7 @@ libecontactlisteditor_la_LIBADD = \ $(top_builddir)/shell/libevolution-shell.la \ $(EVOLUTION_DATA_SERVER_LIBS) \ $(GNOME_PLATFORM_LIBS) \ - $(GTKHTML_LIBS) + $(NULL) ui_DATA = contact-list-editor.ui diff --git a/addressbook/gui/widgets/Makefile.am b/addressbook/gui/widgets/Makefile.am index 715f84b63c..26b8b0156e 100644 --- a/addressbook/gui/widgets/Makefile.am +++ b/addressbook/gui/widgets/Makefile.am @@ -18,10 +18,10 @@ libeabwidgets_la_CPPFLAGS = \ -I$(top_builddir)/shell \ $(EVOLUTION_DATA_SERVER_CFLAGS) \ $(GNOME_PLATFORM_CFLAGS) \ - $(GTKHTML_CFLAGS) \ $(CHAMPLAIN_CFLAGS) \ $(GEO_CFLAGS) \ - $(CODE_COVERAGE_CFLAGS) + $(CODE_COVERAGE_CFLAGS) \ + $(NULL) eabincludedir = $(privincludedir)/addressbook/gui/widgets @@ -81,9 +81,9 @@ libeabwidgets_la_LIBADD = \ $(top_builddir)/addressbook/util/libeabutil.la \ $(EVOLUTION_DATA_SERVER_LIBS) \ $(GNOME_PLATFORM_LIBS) \ - $(GTKHTML_LIBS) \ $(CHAMPLAIN_LIBS) \ - $(GEO_LIBS) + $(GEO_LIBS) \ + $(NULL) dist-hook: cd $(distdir); rm -f $(BUILT_SOURCES) diff --git a/addressbook/gui/widgets/eab-contact-formatter.c b/addressbook/gui/widgets/eab-contact-formatter.c index 7bfa46893b..34b3a5ed3c 100644 --- a/addressbook/gui/widgets/eab-contact-formatter.c +++ b/addressbook/gui/widgets/eab-contact-formatter.c @@ -1395,13 +1395,7 @@ collapse_contacts_list (WebKitDOMEventTarget *event_target, gboolean hidden; document = user_data; -#if WEBKIT_CHECK_VERSION(2,2,0) /* XXX should really be (2,1,something) */ - id = webkit_dom_element_get_id ( - WEBKIT_DOM_ELEMENT (event_target)); -#else - id = webkit_dom_html_element_get_id ( - WEBKIT_DOM_HTML_ELEMENT (event_target)); -#endif + id = webkit_dom_element_get_id (WEBKIT_DOM_ELEMENT (event_target)); list_id = g_strconcat ("list-", id, NULL); list = webkit_dom_document_get_element_by_id (document, list_id); diff --git a/addressbook/importers/Makefile.am b/addressbook/importers/Makefile.am index 89f8f9bea9..0d1d2624b0 100644 --- a/addressbook/importers/Makefile.am +++ b/addressbook/importers/Makefile.am @@ -10,8 +10,8 @@ libevolution_addressbook_importers_la_CPPFLAGS = \ -I$(top_builddir)/addressbook \ $(EVOLUTION_DATA_SERVER_CFLAGS) \ $(GNOME_PLATFORM_CFLAGS) \ - $(GTKHTML_CFLAGS) \ - $(CODE_COVERAGE_CFLAGS) + $(CODE_COVERAGE_CFLAGS) \ + $(NULL) libevolution_addressbook_importers_la_SOURCES = \ evolution-ldif-importer.c \ @@ -27,7 +27,7 @@ libevolution_addressbook_importers_la_LIBADD = \ $(top_builddir)/addressbook/util/libeabutil.la \ $(EVOLUTION_DATA_SERVER_LIBS) \ $(GNOME_PLATFORM_LIBS) \ - $(GTKHTML_LIBS) \ - $(IMPORTERS_LIBS) + $(IMPORTERS_LIBS) \ + $(NULL) -include $(top_srcdir)/git.mk diff --git a/addressbook/printing/Makefile.am b/addressbook/printing/Makefile.am index a6e3aac41c..c0773590bd 100644 --- a/addressbook/printing/Makefile.am +++ b/addressbook/printing/Makefile.am @@ -13,8 +13,8 @@ libecontactprint_la_CPPFLAGS = \ -DEVOLUTION_ECPSDIR=\""$(ecpsdir)"\" \ $(EVOLUTION_DATA_SERVER_CFLAGS) \ $(GNOME_PLATFORM_CFLAGS) \ - $(GTKHTML_CFLAGS) \ - $(CODE_COVERAGE_CFLAGS) + $(CODE_COVERAGE_CFLAGS) \ + $(NULL) noinst_LTLIBRARIES = libecontactprint.la @@ -30,7 +30,7 @@ libecontactprint_la_LIBADD = \ $(top_builddir)/addressbook/util/libeabutil.la \ $(EVOLUTION_DATA_SERVER_LIBS) \ $(GNOME_PLATFORM_LIBS) \ - $(GTKHTML_LIBS) + $(NULL) EXTRA_DIST = \ $(ecps_DATA) diff --git a/addressbook/util/Makefile.am b/addressbook/util/Makefile.am index 4657dbb486..33f1f1b29f 100644 --- a/addressbook/util/Makefile.am +++ b/addressbook/util/Makefile.am @@ -12,9 +12,9 @@ libeabutil_la_CPPFLAGS = \ -I$(top_srcdir)/shell \ $(CAMEL_CFLAGS) \ $(EVOLUTION_DATA_SERVER_CFLAGS) \ - $(GTKHTML_CFLAGS) \ $(GNOME_PLATFORM_CFLAGS) \ - $(CODE_COVERAGE_CFLAGS) + $(CODE_COVERAGE_CFLAGS) \ + $(NULL) libeabutil_la_SOURCES = \ eab-book-util.c \ @@ -27,8 +27,8 @@ libeabutil_la_LIBADD = \ $(top_builddir)/shell/libevolution-shell.la \ $(CAMEL_LIBS) \ $(EVOLUTION_DATA_SERVER_LIBS) \ - $(GTKHTML_LIBS) \ - $(GNOME_PLATFORM_LIBS) + $(GNOME_PLATFORM_LIBS) \ + $(NULL) dist-hook: cd $(distdir); rm -f $(BUILT_SOURCES) diff --git a/art/Makefile.am b/art/Makefile.am index 850be7e8ce..276d435b05 100644 --- a/art/Makefile.am +++ b/art/Makefile.am @@ -1,7 +1,15 @@ images_DATA = \ - world_map-960.png \ + confidential-stamp.jpg \ + draft-paper.png \ + draft-stamp.jpg \ + midnight-stars.jpg \ + minus.png \ + paper.png \ plus.png \ - minus.png + rect.png \ + ribbon.jpg \ + texture.png \ + world_map-960.png EXTRA_DIST = \ README \ diff --git a/art/confidential-stamp.jpg b/art/confidential-stamp.jpg new file mode 100644 index 0000000000..0dece7c04f Binary files /dev/null and b/art/confidential-stamp.jpg differ diff --git a/art/draft-paper.png b/art/draft-paper.png new file mode 100644 index 0000000000..177d568eab Binary files /dev/null and b/art/draft-paper.png differ diff --git a/art/draft-stamp.jpg b/art/draft-stamp.jpg new file mode 100644 index 0000000000..623f50ea56 Binary files /dev/null and b/art/draft-stamp.jpg differ diff --git a/art/midnight-stars.jpg b/art/midnight-stars.jpg new file mode 100644 index 0000000000..22f01b36f8 Binary files /dev/null and b/art/midnight-stars.jpg differ diff --git a/art/paper.png b/art/paper.png new file mode 100644 index 0000000000..bca355b80d Binary files /dev/null and b/art/paper.png differ diff --git a/art/rect.png b/art/rect.png new file mode 100644 index 0000000000..b7e633c22b Binary files /dev/null and b/art/rect.png differ diff --git a/art/ribbon.jpg b/art/ribbon.jpg new file mode 100644 index 0000000000..03ca65e8c3 Binary files /dev/null and b/art/ribbon.jpg differ diff --git a/art/texture.png b/art/texture.png new file mode 100644 index 0000000000..b0925a6cdc Binary files /dev/null and b/art/texture.png differ diff --git a/calendar/alarm-notify/Makefile.am b/calendar/alarm-notify/Makefile.am index 057ac6a9a4..ecd24afa43 100644 --- a/calendar/alarm-notify/Makefile.am +++ b/calendar/alarm-notify/Makefile.am @@ -21,8 +21,8 @@ evolution_alarm_notify_CPPFLAGS = \ $(GNOME_PLATFORM_CFLAGS) \ $(LIBNOTIFY_CFLAGS) \ $(CANBERRA_CFLAGS) \ - $(GTKHTML_CFLAGS) \ - $(CODE_COVERAGE_CFLAGS) + $(CODE_COVERAGE_CFLAGS) \ + $(NULL) ui_DATA = \ alarm-notify.ui @@ -55,8 +55,8 @@ evolution_alarm_notify_LDADD = \ $(GNOME_PLATFORM_LIBS) \ $(LIBNOTIFY_LIBS) \ $(CANBERRA_LIBS) \ - $(GTKHTML_LIBS) - $(EVOLUTIONALARMNOTIFYICON) + $(EVOLUTIONALARMNOTIFYICON) \ + $(NULL) evolution_alarm_notify_LDFLAGS = $(CODE_COVERAGE_LDFLAGS) diff --git a/calendar/gui/Makefile.am b/calendar/gui/Makefile.am index de752c11d4..2dca7d50bd 100644 --- a/calendar/gui/Makefile.am +++ b/calendar/gui/Makefile.am @@ -67,9 +67,9 @@ libevolution_calendar_la_CPPFLAGS = \ -DPREFIX=\""$(prefix)"\" \ $(EVOLUTION_DATA_SERVER_CFLAGS) \ $(GNOME_PLATFORM_CFLAGS) \ - $(GTKHTML_CFLAGS) \ $(LIBSOUP_CFLAGS) \ - $(CODE_COVERAGE_CFLAGS) + $(CODE_COVERAGE_CFLAGS) \ + $(NULL) etspec_DATA = \ e-calendar-table.etspec \ @@ -207,8 +207,8 @@ libevolution_calendar_la_LIBADD = \ $(top_builddir)/e-util/libevolution-util.la \ $(EVOLUTION_DATA_SERVER_LIBS) \ $(GNOME_PLATFORM_LIBS) \ - $(GTKHTML_LIBS) \ - $(LIBSOUP_LIBS) + $(LIBSOUP_LIBS) \ + $(NULL) libevolution_calendar_la_LDFLAGS = -avoid-version $(NO_UNDEFINED) $(CODE_COVERAGE_LDFLAGS) diff --git a/calendar/gui/dialogs/Makefile.am b/calendar/gui/dialogs/Makefile.am index 9c7b3053a2..ec5afaa7a2 100644 --- a/calendar/gui/dialogs/Makefile.am +++ b/calendar/gui/dialogs/Makefile.am @@ -14,8 +14,8 @@ libcal_dialogs_la_CPPFLAGS = \ -DPREFIX=\""$(prefix)"\" \ $(EVOLUTION_DATA_SERVER_CFLAGS) \ $(GNOME_PLATFORM_CFLAGS) \ - $(GTKHTML_CFLAGS) \ - $(CODE_COVERAGE_CFLAGS) + $(CODE_COVERAGE_CFLAGS) \ + $(NULL) ecalendarincludedir = $(privincludedir)/calendar/gui/dialogs @@ -52,7 +52,7 @@ libcal_dialogs_la_LIBADD = \ $(top_builddir)/addressbook/util/libeabutil.la \ $(EVOLUTION_DATA_SERVER_LIBS) \ $(GNOME_PLATFORM_LIBS) \ - $(GTKHTML_LIBS) + $(NULL) libcal_dialogs_la_SOURCES = \ alarm-dialog.c \ diff --git a/calendar/importers/Makefile.am b/calendar/importers/Makefile.am index 206a852f4c..957de01280 100644 --- a/calendar/importers/Makefile.am +++ b/calendar/importers/Makefile.am @@ -9,8 +9,8 @@ libevolution_calendar_importers_la_CPPFLAGS = \ -I$(top_builddir)/calendar \ $(EVOLUTION_DATA_SERVER_CFLAGS) \ $(GNOME_PLATFORM_CFLAGS) \ - $(GTKHTML_CFLAGS) \ - $(CODE_COVERAGE_CFLAGS) + $(CODE_COVERAGE_CFLAGS) \ + $(NULL) libevolution_calendar_importers_la_SOURCES = \ evolution-calendar-importer.h \ @@ -23,6 +23,6 @@ libevolution_calendar_importers_la_LIBADD = \ $(top_builddir)/shell/libevolution-shell.la \ $(EVOLUTION_DATA_SERVER_LIBS) \ $(GNOME_PLATFORM_LIBS) \ - $(GTKHTML_LIBS) + $(NULL) -include $(top_srcdir)/git.mk diff --git a/composer/Makefile.am b/composer/Makefile.am index aa7886403e..86a411a07e 100644 --- a/composer/Makefile.am +++ b/composer/Makefile.am @@ -10,7 +10,6 @@ evolution_mail_composer_includedir = $(privincludedir)/composer evolution_mail_composer_include_HEADERS = \ e-composer-actions.h \ - e-composer-activity.h \ e-composer-common.h \ e-composer-from-header.h \ e-composer-header-table.h \ @@ -36,13 +35,12 @@ libevolution_mail_composer_la_CPPFLAGS = \ -DG_LOG_DOMAIN=\"composer\" \ $(EVOLUTION_DATA_SERVER_CFLAGS) \ $(GNOME_PLATFORM_CFLAGS) \ - $(GTKHTML_CFLAGS) \ - $(CODE_COVERAGE_CFLAGS) + $(CODE_COVERAGE_CFLAGS) \ + $(NULL) libevolution_mail_composer_la_SOURCES = \ $(evolution_mail_composer_include_HEADERS) \ e-composer-actions.c \ - e-composer-activity.c \ e-composer-from-header.c \ e-composer-header-table.c \ e-composer-header.c \ @@ -63,7 +61,7 @@ libevolution_mail_composer_la_LIBADD = \ $(top_builddir)/addressbook/gui/contact-list-editor/libecontactlisteditor.la \ $(EVOLUTION_DATA_SERVER_LIBS) \ $(GNOME_PLATFORM_LIBS) \ - $(GTKHTML_LIBS) + $(NULL) ui_DATA = evolution-composer.ui diff --git a/composer/e-composer-actions.c b/composer/e-composer-actions.c index 047bf735bd..f917a2c70e 100644 --- a/composer/e-composer-actions.c +++ b/composer/e-composer-actions.c @@ -134,20 +134,24 @@ static void action_pgp_encrypt_cb (GtkToggleAction *action, EMsgComposer *composer) { - GtkhtmlEditor *editor; + EHTMLEditor *editor; + EHTMLEditorView *view; - editor = GTKHTML_EDITOR (composer); - gtkhtml_editor_set_changed (editor, TRUE); + editor = e_msg_composer_get_editor (composer); + view = e_html_editor_get_view (editor); + e_html_editor_view_set_changed (view, TRUE); } static void action_pgp_sign_cb (GtkToggleAction *action, EMsgComposer *composer) { - GtkhtmlEditor *editor; + EHTMLEditor *editor; + EHTMLEditorView *view; - editor = GTKHTML_EDITOR (composer); - gtkhtml_editor_set_changed (editor, TRUE); + editor = e_msg_composer_get_editor (composer); + view = e_html_editor_get_view (editor); + e_html_editor_view_set_changed (view, TRUE); } static void @@ -198,12 +202,14 @@ static void action_save_cb (GtkAction *action, EMsgComposer *composer) { - GtkhtmlEditor *editor = GTKHTML_EDITOR (composer); + EHTMLEditor *editor; + EHTMLEditorView *view; const gchar *filename; gint fd; GError *error = NULL; - filename = gtkhtml_editor_get_filename (editor); + editor = e_msg_composer_get_editor (composer); + filename = e_html_editor_get_filename (editor); if (filename == NULL) { gtk_action_activate (ACTION (SAVE_AS)); return; @@ -233,7 +239,7 @@ action_save_cb (GtkAction *action, } else close (fd); - if (!gtkhtml_editor_save (editor, filename, TRUE, &error)) { + if (!e_html_editor_save (editor, filename, TRUE, &error)) { e_alert_submit ( E_ALERT_SINK (composer), E_ALERT_NO_SAVE_FILE, @@ -242,13 +248,15 @@ action_save_cb (GtkAction *action, return; } - gtkhtml_editor_run_command (GTKHTML_EDITOR (composer), "saved"); + view = e_html_editor_get_view (editor); + e_html_editor_view_set_changed (view, TRUE); } static void action_save_as_cb (GtkAction *action, EMsgComposer *composer) { + EHTMLEditor *editor; GtkWidget *dialog; gchar *filename; gint response; @@ -272,8 +280,9 @@ action_save_as_cb (GtkAction *action, if (response != GTK_RESPONSE_OK) goto exit; + editor = e_msg_composer_get_editor (composer); filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog)); - gtkhtml_editor_set_filename (GTKHTML_EDITOR (composer), filename); + e_html_editor_set_filename (editor, filename); g_free (filename); gtk_action_activate (ACTION (SAVE)); @@ -300,20 +309,24 @@ static void action_smime_encrypt_cb (GtkToggleAction *action, EMsgComposer *composer) { - GtkhtmlEditor *editor; + EHTMLEditor *editor; + EHTMLEditorView *view; - editor = GTKHTML_EDITOR (composer); - gtkhtml_editor_set_changed (editor, TRUE); + editor = e_msg_composer_get_editor (composer); + view = e_html_editor_get_view (editor); + e_html_editor_view_set_changed (view, TRUE); } static void action_smime_sign_cb (GtkToggleAction *action, EMsgComposer *composer) { - GtkhtmlEditor *editor; + EHTMLEditor *editor; + EHTMLEditorView *view; - editor = GTKHTML_EDITOR (composer); - gtkhtml_editor_set_changed (editor, TRUE); + editor = e_msg_composer_get_editor (composer); + view = e_html_editor_get_view (editor); + e_html_editor_view_set_changed (view, TRUE); } static gboolean @@ -514,15 +527,15 @@ e_composer_actions_init (EMsgComposer *composer) GtkActionGroup *action_group; GtkAccelGroup *accel_group; GtkUIManager *ui_manager; - GtkhtmlEditor *editor; - EWebViewGtkHTML *web_view; + EHTMLEditor *editor; + EHTMLEditorView *view; gboolean visible; g_return_if_fail (E_IS_MSG_COMPOSER (composer)); - editor = GTKHTML_EDITOR (composer); - web_view = e_msg_composer_get_web_view (composer); - ui_manager = gtkhtml_editor_get_ui_manager (editor); + editor = e_msg_composer_get_editor (composer); + view = e_html_editor_get_view (editor); + ui_manager = e_html_editor_get_ui_manager (editor); /* Composer Actions */ action_group = composer->priv->composer_actions; @@ -566,23 +579,33 @@ e_composer_actions_init (EMsgComposer *composer) ACTION (SAVE_DRAFT), "short-label", _("Save Draft"), NULL); g_object_bind_property ( - composer, "html-mode", + view, "html-mode", ACTION (PICTURE_GALLERY), "sensitive", G_BINDING_SYNC_CREATE); g_object_bind_property ( - web_view, "editable", - GTKHTML_EDITOR_ACTION_EDIT_MENU (editor), "sensitive", + view, "editable", + e_html_editor_get_action (editor, "edit-menu"), "sensitive", G_BINDING_SYNC_CREATE); g_object_bind_property ( - web_view, "editable", - GTKHTML_EDITOR_ACTION_FORMAT_MENU (editor), "sensitive", + view, "editable", + e_html_editor_get_action (editor, "format-menu"), "sensitive", G_BINDING_SYNC_CREATE); g_object_bind_property ( - web_view, "editable", - GTKHTML_EDITOR_ACTION_INSERT_MENU (editor), "sensitive", + view, "editable", + e_html_editor_get_action (editor, "insert-menu"), "sensitive", + G_BINDING_SYNC_CREATE); + + g_object_bind_property ( + view, "editable", + e_html_editor_get_action (editor, "options-menu"), "sensitive", + G_BINDING_SYNC_CREATE); + + g_object_bind_property ( + view, "editable", + e_html_editor_get_action (editor, "picture-gallery"), "sensitive", G_BINDING_SYNC_CREATE); #if defined (HAVE_NSS) diff --git a/composer/e-composer-actions.h b/composer/e-composer-actions.h index 611154e9be..291953109e 100644 --- a/composer/e-composer-actions.h +++ b/composer/e-composer-actions.h @@ -18,7 +18,9 @@ #define E_COMPOSER_ACTIONS_H #define E_COMPOSER_ACTION(composer, name) \ - (gtkhtml_editor_get_action (GTKHTML_EDITOR (composer), (name))) + (e_html_editor_get_action ( \ + e_msg_composer_get_editor ( \ + E_MSG_COMPOSER (composer)), (name))) #define E_COMPOSER_ACTION_ATTACH(composer) \ E_COMPOSER_ACTION ((composer), "attach") diff --git a/composer/e-composer-activity.c b/composer/e-composer-activity.c deleted file mode 100644 index 513915f189..0000000000 --- a/composer/e-composer-activity.c +++ /dev/null @@ -1,187 +0,0 @@ -/* - * e-composer-activity.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. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * 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, see . - * - */ - -#ifdef HAVE_CONFIG_H -#include -#endif - -#include "e-composer-private.h" - -#define E_COMPOSER_ACTIVITY_GET_PRIVATE(obj) \ - (G_TYPE_INSTANCE_GET_PRIVATE \ - ((obj), E_TYPE_COMPOSER_ACTIVITY, EComposerActivityPrivate)) - -struct _EComposerActivityPrivate { - EMsgComposer *composer; - gboolean saved_editable; -}; - -enum { - PROP_0, - PROP_COMPOSER -}; - -G_DEFINE_TYPE ( - EComposerActivity, - e_composer_activity, - E_TYPE_ACTIVITY) - -static void -composer_activity_lock_interface (EComposerActivity *activity) -{ - GtkActionGroup *action_group; - EMsgComposer *composer; - EWebViewGtkHTML *web_view; - gboolean editable; - - composer = e_composer_activity_get_composer (activity); - - web_view = e_msg_composer_get_web_view (composer); - editable = e_web_view_gtkhtml_get_editable (web_view); - e_web_view_gtkhtml_set_editable (web_view, FALSE); - activity->priv->saved_editable = editable; - - action_group = composer->priv->async_actions; - gtk_action_group_set_sensitive (action_group, FALSE); -} - -static void -composer_activity_unlock_interface (EComposerActivity *activity) -{ - GtkActionGroup *action_group; - EMsgComposer *composer; - EWebViewGtkHTML *web_view; - gboolean editable; - - composer = e_composer_activity_get_composer (activity); - - editable = activity->priv->saved_editable; - web_view = e_msg_composer_get_web_view (composer); - e_web_view_gtkhtml_set_editable (web_view, editable); - - action_group = composer->priv->async_actions; - gtk_action_group_set_sensitive (action_group, TRUE); -} - -static void -composer_activity_set_composer (EComposerActivity *activity, - EMsgComposer *composer) -{ - g_return_if_fail (E_IS_MSG_COMPOSER (composer)); - g_return_if_fail (activity->priv->composer == NULL); - - activity->priv->composer = g_object_ref (composer); - - composer_activity_lock_interface (activity); -} - -static void -composer_activity_set_property (GObject *object, - guint property_id, - const GValue *value, - GParamSpec *pspec) -{ - switch (property_id) { - case PROP_COMPOSER: - composer_activity_set_composer ( - E_COMPOSER_ACTIVITY (object), - g_value_get_object (value)); - return; - } - - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); -} - -static void -composer_activity_get_property (GObject *object, - guint property_id, - GValue *value, - GParamSpec *pspec) -{ - switch (property_id) { - case PROP_COMPOSER: - g_value_set_object ( - value, e_composer_activity_get_composer ( - E_COMPOSER_ACTIVITY (object))); - return; - } - - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); -} - -static void -composer_activity_dispose (GObject *object) -{ - EComposerActivity *activity; - - activity = E_COMPOSER_ACTIVITY (object); - - if (activity->priv->composer != NULL) { - composer_activity_unlock_interface (activity); - g_object_unref (activity->priv->composer); - activity->priv->composer = NULL; - } - - /* Chain up to parent's dispose() method. */ - G_OBJECT_CLASS (e_composer_activity_parent_class)->dispose (object); -} - -static void -e_composer_activity_class_init (EComposerActivityClass *class) -{ - GObjectClass *object_class; - - g_type_class_add_private (class, sizeof (EComposerActivityPrivate)); - - object_class = G_OBJECT_CLASS (class); - object_class->set_property = composer_activity_set_property; - object_class->get_property = composer_activity_get_property; - object_class->dispose = composer_activity_dispose; - - g_object_class_install_property ( - object_class, - PROP_COMPOSER, - g_param_spec_object ( - "composer", - NULL, - NULL, - E_TYPE_MSG_COMPOSER, - G_PARAM_READWRITE | - G_PARAM_CONSTRUCT_ONLY)); -} - -static void -e_composer_activity_init (EComposerActivity *activity) -{ - activity->priv = E_COMPOSER_ACTIVITY_GET_PRIVATE (activity); -} - -EActivity * -e_composer_activity_new (EMsgComposer *composer) -{ - return g_object_new ( - E_TYPE_COMPOSER_ACTIVITY, - "composer", composer, NULL); -} - -EMsgComposer * -e_composer_activity_get_composer (EComposerActivity *activity) -{ - g_return_val_if_fail (E_IS_COMPOSER_ACTIVITY (activity), NULL); - - return activity->priv->composer; -} diff --git a/composer/e-composer-activity.h b/composer/e-composer-activity.h deleted file mode 100644 index 0966eba332..0000000000 --- a/composer/e-composer-activity.h +++ /dev/null @@ -1,64 +0,0 @@ -/* - * e-composer-activity.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. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * 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, see . - * - */ - -#ifndef E_COMPOSER_ACTIVITY_H -#define E_COMPOSER_ACTIVITY_H - -#include - -/* Standard GObject macros */ -#define E_TYPE_COMPOSER_ACTIVITY \ - (e_composer_activity_get_type ()) -#define E_COMPOSER_ACTIVITY(obj) \ - (G_TYPE_CHECK_INSTANCE_CAST \ - ((obj), E_TYPE_COMPOSER_ACTIVITY, EComposerActivity)) -#define E_COMPOSER_ACTIVITY_CLASS(cls) \ - (G_TYPE_CHECK_CLASS_CAST \ - ((cls), E_TYPE_COMPOSER_ACTIVITY, EComposerActivityClass)) -#define E_IS_COMPOSER_ACTIVITY(obj) \ - (G_TYPE_CHECK_INSTANCE_TYPE \ - ((obj), E_TYPE_COMPOSER_ACTIVITY)) -#define E_IS_COMPOSER_ACTIVITY_CLASS(cls) \ - (G_TYPE_CHECK_CLASS_TYPE \ - ((cls), E_TYPE_COMPOSER_ACTIVITY)) -#define E_COMPOSER_ACTIVITY_GET_CLASS(obj) \ - (G_TYPE_INSTANCE_GET_CLASS \ - ((obj), E_TYPE_COMPOSER_ACTIVITY, EComposerActivityClass)) - -G_BEGIN_DECLS - -typedef struct _EComposerActivity EComposerActivity; -typedef struct _EComposerActivityClass EComposerActivityClass; -typedef struct _EComposerActivityPrivate EComposerActivityPrivate; - -struct _EComposerActivity { - EActivity parent; - EComposerActivityPrivate *priv; -}; - -struct _EComposerActivityClass { - EActivityClass parent_class; -}; - -GType e_composer_activity_get_type (void); -EActivity * e_composer_activity_new (EMsgComposer *composer); -EMsgComposer * e_composer_activity_get_composer - (EComposerActivity *activity); - -G_END_DECLS - -#endif /* E_COMPOSER_ACTIVITY_H */ diff --git a/composer/e-composer-private.c b/composer/e-composer-private.c index 59e1625639..bc3b6d8078 100644 --- a/composer/e-composer-private.c +++ b/composer/e-composer-private.c @@ -27,15 +27,19 @@ /* Initial height of the picture gallery. */ #define GALLERY_INITIAL_HEIGHT 150 +#define UNICODE_ZERO_WIDTH_SPACE "\xe2\x80\x8b" + static void composer_setup_charset_menu (EMsgComposer *composer) { + EHTMLEditor *editor; GtkUIManager *ui_manager; const gchar *path; GList *list; guint merge_id; - ui_manager = gtkhtml_editor_get_ui_manager (GTKHTML_EDITOR (composer)); + editor = e_msg_composer_get_editor (composer); + ui_manager = e_html_editor_get_ui_manager (editor); path = "/main-menu/options-menu/charset-menu"; merge_id = gtk_ui_manager_new_merge_id (ui_manager); @@ -57,63 +61,23 @@ composer_setup_charset_menu (EMsgComposer *composer) gtk_ui_manager_ensure_update (ui_manager); } -static void -msg_composer_url_requested_cb (GtkHTML *html, - const gchar *uri, - GtkHTMLStream *stream, - EMsgComposer *composer) -{ - GByteArray *array; - GHashTable *hash_table; - CamelDataWrapper *wrapper; - CamelStream *camel_stream; - CamelMimePart *mime_part; - - hash_table = composer->priv->inline_images_by_url; - mime_part = g_hash_table_lookup (hash_table, uri); - - if (mime_part == NULL) { - hash_table = composer->priv->inline_images; - mime_part = g_hash_table_lookup (hash_table, uri); - } - - /* If this is not an inline image request, - * allow the signal emission to continue. */ - if (mime_part == NULL) - return; - - array = g_byte_array_new (); - camel_stream = camel_stream_mem_new_with_byte_array (array); - wrapper = camel_medium_get_content (CAMEL_MEDIUM (mime_part)); - camel_data_wrapper_decode_to_stream_sync ( - wrapper, camel_stream, NULL, NULL); - - gtk_html_write (html, stream, (gchar *) array->data, array->len); - - gtk_html_end (html, stream, GTK_HTML_STREAM_OK); - - g_object_unref (camel_stream); - - /* gtk_html_end() destroys the GtkHTMLStream, so we need to - * stop the signal emission so nothing else tries to use it. */ - g_signal_stop_emission_by_name (html, "url-requested"); -} - static void composer_update_gallery_visibility (EMsgComposer *composer) { - GtkhtmlEditor *editor; + EHTMLEditor *editor; + EHTMLEditorView *view; GtkToggleAction *toggle_action; gboolean gallery_active; - gboolean html_mode; + gboolean is_html; - editor = GTKHTML_EDITOR (composer); - html_mode = gtkhtml_editor_get_html_mode (editor); + editor = e_msg_composer_get_editor (composer); + view = e_html_editor_get_view (editor); + is_html = e_html_editor_view_get_html_mode (view); toggle_action = GTK_TOGGLE_ACTION (ACTION (PICTURE_GALLERY)); gallery_active = gtk_toggle_action_get_active (toggle_action); - if (html_mode && gallery_active) { + if (is_html && gallery_active) { gtk_widget_show (composer->priv->gallery_scrolled_window); gtk_widget_show (composer->priv->gallery_icon_view); } else { @@ -122,30 +86,16 @@ composer_update_gallery_visibility (EMsgComposer *composer) } } -static void -composer_spell_languages_changed (EMsgComposer *composer, - GList *languages) -{ - EComposerHeader *header; - EComposerHeaderTable *table; - - table = e_msg_composer_get_header_table (composer); - header = e_composer_header_table_get_header ( - table, E_COMPOSER_HEADER_SUBJECT); - - e_composer_spell_header_set_languages ( - E_COMPOSER_SPELL_HEADER (header), languages); -} - void e_composer_private_constructed (EMsgComposer *composer) { EMsgComposerPrivate *priv = composer->priv; EFocusTracker *focus_tracker; + EComposerHeader *header; EShell *shell; - EWebViewGtkHTML *web_view; EClientCache *client_cache; - GtkhtmlEditor *editor; + EHTMLEditor *editor; + EHTMLEditorView *view; GtkUIManager *ui_manager; GtkAction *action; GtkWidget *container; @@ -158,14 +108,14 @@ e_composer_private_constructed (EMsgComposer *composer) gint ii; GError *error = NULL; - editor = GTKHTML_EDITOR (composer); - ui_manager = gtkhtml_editor_get_ui_manager (editor); + editor = e_msg_composer_get_editor (composer); + ui_manager = e_html_editor_get_ui_manager (editor); + view = e_html_editor_get_view (editor); settings = g_settings_new ("org.gnome.evolution.mail"); shell = e_msg_composer_get_shell (composer); client_cache = e_shell_get_client_cache (shell); - web_view = e_msg_composer_get_web_view (composer); /* Each composer window gets its own window group. */ window = GTK_WINDOW (composer); @@ -179,19 +129,17 @@ e_composer_private_constructed (EMsgComposer *composer) priv->extra_hdr_names = g_ptr_array_new (); priv->extra_hdr_values = g_ptr_array_new (); - priv->inline_images = g_hash_table_new_full ( - g_str_hash, g_str_equal, - (GDestroyNotify) g_free, - (GDestroyNotify) NULL); - - priv->inline_images_by_url = g_hash_table_new_full ( - g_str_hash, g_str_equal, - (GDestroyNotify) g_free, - (GDestroyNotify) g_object_unref); - priv->charset = e_composer_get_default_charset (); + priv->is_from_draft = FALSE; priv->is_from_message = FALSE; + priv->is_from_new_message = FALSE; + priv->set_signature_from_message = FALSE; + priv->disable_signature = FALSE; + priv->busy = FALSE; + priv->saved_editable= FALSE; + + priv->focused_entry = NULL; e_composer_actions_init (composer); @@ -216,48 +164,58 @@ e_composer_private_constructed (EMsgComposer *composer) focus_tracker = e_focus_tracker_new (GTK_WINDOW (composer)); - action = gtkhtml_editor_get_action (editor, "cut"); + action = e_html_editor_get_action (editor, "cut"); e_focus_tracker_set_cut_clipboard_action (focus_tracker, action); - action = gtkhtml_editor_get_action (editor, "copy"); + action = e_html_editor_get_action (editor, "copy"); e_focus_tracker_set_copy_clipboard_action (focus_tracker, action); - action = gtkhtml_editor_get_action (editor, "paste"); + action = e_html_editor_get_action (editor, "paste"); e_focus_tracker_set_paste_clipboard_action (focus_tracker, action); - action = gtkhtml_editor_get_action (editor, "select-all"); + action = e_html_editor_get_action (editor, "select-all"); e_focus_tracker_set_select_all_action (focus_tracker, action); priv->focus_tracker = focus_tracker; - container = editor->vbox; + widget = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); + gtk_container_add (GTK_CONTAINER (composer), widget); + gtk_widget_show (widget); + + container = widget; - /* Construct the activity bar. */ + /* Construct the main menu and toolbar. */ - widget = e_activity_bar_new (); + widget = e_html_editor_get_managed_widget (editor, "/main-menu"); gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0); - priv->activity_bar = g_object_ref_sink (widget); - /* EActivityBar controls its own visibility. */ - - /* Construct the alert bar for errors. */ + gtk_widget_show (widget); - widget = e_alert_bar_new (); + widget = e_html_editor_get_managed_widget (editor, "/main-toolbar"); gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0); - priv->alert_bar = g_object_ref_sink (widget); - /* EAlertBar controls its own visibility. */ + gtk_widget_show (widget); /* Construct the header table. */ widget = e_composer_header_table_new (client_cache); gtk_container_set_border_width (GTK_CONTAINER (widget), 6); gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0); - gtk_box_reorder_child (GTK_BOX (container), widget, 2); - priv->header_table = g_object_ref_sink (widget); + priv->header_table = g_object_ref (widget); gtk_widget_show (widget); - g_signal_connect ( - G_OBJECT (composer), "spell-languages-changed", - G_CALLBACK (composer_spell_languages_changed), NULL); + header = e_composer_header_table_get_header ( + E_COMPOSER_HEADER_TABLE (widget), + E_COMPOSER_HEADER_SUBJECT); + g_object_bind_property ( + view, "spell-checker", + header->input_widget, "spell-checker", + G_BINDING_SYNC_CREATE); + + /* Construct the editing toolbars. We'll have to reparent + * the embedded EHTMLEditorView a little further down. */ + + widget = GTK_WIDGET (editor); + gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0); + gtk_widget_show (widget); /* Construct the attachment paned. */ @@ -267,8 +225,8 @@ e_composer_private_constructed (EMsgComposer *composer) gtk_widget_show (widget); g_object_bind_property ( - web_view, "editable", - widget, "editable", + view, "editable", + widget, "sensitive", G_BINDING_SYNC_CREATE); container = e_attachment_paned_get_content_area ( @@ -288,13 +246,13 @@ e_composer_private_constructed (EMsgComposer *composer) GTK_SCROLLED_WINDOW (widget), GTK_SHADOW_IN); gtk_widget_set_size_request (widget, -1, GALLERY_INITIAL_HEIGHT); gtk_paned_pack1 (GTK_PANED (container), widget, FALSE, FALSE); - priv->gallery_scrolled_window = g_object_ref_sink (widget); + priv->gallery_scrolled_window = g_object_ref (widget); gtk_widget_show (widget); - /* Reparent the scrolled window containing the GtkHTML widget - * into the content area of the top attachment pane. */ + /* Reparent the scrolled window containing the web view + * widget into the content area of the top attachment pane. */ - widget = GTK_WIDGET (web_view); + widget = GTK_WIDGET (view); widget = gtk_widget_get_parent (widget); gtk_widget_reparent (widget, container); @@ -310,16 +268,16 @@ e_composer_private_constructed (EMsgComposer *composer) priv->gallery_icon_view = g_object_ref_sink (widget); g_free (gallery_path); - e_signal_connect_notify ( - composer, "notify::html-mode", - G_CALLBACK (composer_update_gallery_visibility), NULL); + e_signal_connect_notify_swapped ( + view, "notify::mode", + G_CALLBACK (composer_update_gallery_visibility), composer); g_signal_connect_swapped ( ACTION (PICTURE_GALLERY), "toggled", G_CALLBACK (composer_update_gallery_visibility), composer); - /* XXX What is this for? */ - g_object_set_data (G_OBJECT (composer), "vbox", editor->vbox); + /* Initial sync */ + composer_update_gallery_visibility (composer); /* Bind headers to their corresponding actions. */ @@ -361,20 +319,21 @@ e_composer_private_constructed (EMsgComposer *composer) G_BINDING_SYNC_CREATE); } - /* Install a handler for inline images. */ + /* Disable actions that start asynchronous activities while an + * asynchronous activity is in progress. We enforce this with + * a simple inverted binding to EMsgComposer's "busy" property. */ - /* XXX We no longer use GtkhtmlEditor::uri-requested because it - * conflicts with EWebView's url_requested() method, which - * unconditionally launches an async operation. I changed - * GtkHTML::url-requested to be a G_SIGNAL_RUN_LAST so that - * our handler runs first. If we can handle the request - * we'll stop the signal emission to prevent EWebView from - * launching an async operation. Messy, but works until we - * switch to WebKit. --mbarnes */ + g_object_bind_property ( + composer, "busy", + priv->async_actions, "sensitive", + G_BINDING_SYNC_CREATE | + G_BINDING_INVERT_BOOLEAN); - g_signal_connect ( - web_view, "url-requested", - G_CALLBACK (msg_composer_url_requested_cb), composer); + g_object_bind_property ( + composer, "busy", + priv->header_table, "sensitive", + G_BINDING_SYNC_CREATE | + G_BINDING_INVERT_BOOLEAN); g_object_unref (settings); } @@ -389,21 +348,16 @@ e_composer_private_dispose (EMsgComposer *composer) composer->priv->shell = NULL; } + if (composer->priv->editor != NULL) { + g_object_unref (composer->priv->editor); + composer->priv->editor = NULL; + } + if (composer->priv->header_table != NULL) { g_object_unref (composer->priv->header_table); composer->priv->header_table = NULL; } - if (composer->priv->activity_bar != NULL) { - g_object_unref (composer->priv->activity_bar); - composer->priv->activity_bar = NULL; - } - - if (composer->priv->alert_bar != NULL) { - g_object_unref (composer->priv->alert_bar); - composer->priv->alert_bar = NULL; - } - if (composer->priv->attachment_paned != NULL) { g_object_unref (composer->priv->attachment_paned); composer->priv->attachment_paned = NULL; @@ -434,11 +388,10 @@ e_composer_private_dispose (EMsgComposer *composer) composer->priv->composer_actions = NULL; } - g_clear_object (&composer->priv->gallery_icon_view); - g_clear_object (&composer->priv->gallery_scrolled_window); - - g_hash_table_remove_all (composer->priv->inline_images); - g_hash_table_remove_all (composer->priv->inline_images_by_url); + if (composer->priv->gallery_scrolled_window != NULL) { + g_object_unref (composer->priv->gallery_scrolled_window); + composer->priv->gallery_scrolled_window = NULL; + } if (composer->priv->redirect != NULL) { g_object_unref (composer->priv->redirect); @@ -462,10 +415,6 @@ e_composer_private_finalize (EMsgComposer *composer) g_free (composer->priv->charset); g_free (composer->priv->mime_type); g_free (composer->priv->mime_body); - g_free (composer->priv->selected_signature_uid); - - g_hash_table_destroy (composer->priv->inline_images); - g_hash_table_destroy (composer->priv->inline_images_by_url); } gchar * @@ -525,92 +474,13 @@ e_composer_get_default_charset (void) return charset; } -gchar * -e_composer_decode_clue_value (const gchar *encoded_value) -{ - GString *buffer; - const gchar *cp; - - /* Decode a GtkHtml "ClueFlow" value. */ - - g_return_val_if_fail (encoded_value != NULL, NULL); - - buffer = g_string_sized_new (strlen (encoded_value)); - - /* Copy the value, decoding escaped characters as we go. */ - cp = encoded_value; - while (*cp != '\0') { - if (*cp == '.') { - cp++; - switch (*cp) { - case '.': - g_string_append_c (buffer, '.'); - break; - case '1': - g_string_append_c (buffer, '"'); - break; - case '2': - g_string_append_c (buffer, '='); - break; - default: - /* Invalid escape sequence. */ - g_string_free (buffer, TRUE); - return NULL; - } - } else - g_string_append_c (buffer, *cp); - cp++; - } - - return g_string_free (buffer, FALSE); -} - -gchar * -e_composer_encode_clue_value (const gchar *decoded_value) -{ - gchar *encoded_value; - gchar **strv; - - /* Encode a GtkHtml "ClueFlow" value. */ - - g_return_val_if_fail (decoded_value != NULL, NULL); - - /* XXX This is inefficient but easy to understand. */ - - encoded_value = g_strdup (decoded_value); - - /* Substitution: '.' --> '..' (do this first) */ - if (strchr (encoded_value, '.') != NULL) { - strv = g_strsplit (encoded_value, ".", 0); - g_free (encoded_value); - encoded_value = g_strjoinv ("..", strv); - g_strfreev (strv); - } - - /* Substitution: '"' --> '.1' */ - if (strchr (encoded_value, '"') != NULL) { - strv = g_strsplit (encoded_value, """", 0); - g_free (encoded_value); - encoded_value = g_strjoinv (".1", strv); - g_strfreev (strv); - } - - /* Substitution: '=' --> '.2' */ - if (strchr (encoded_value, '=') != NULL) { - strv = g_strsplit (encoded_value, "=", 0); - g_free (encoded_value); - encoded_value = g_strjoinv (".2", strv); - g_strfreev (strv); - } - - return encoded_value; -} - gboolean e_composer_paste_html (EMsgComposer *composer, GtkClipboard *clipboard) { - GtkhtmlEditor *editor; + EHTMLEditor *editor; + EHTMLEditorView *view; + EHTMLEditorSelection *editor_selection; gchar *html; g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), FALSE); @@ -619,9 +489,15 @@ e_composer_paste_html (EMsgComposer *composer, html = e_clipboard_wait_for_html (clipboard); g_return_val_if_fail (html != NULL, FALSE); - editor = GTKHTML_EDITOR (composer); - gtkhtml_editor_insert_html (editor, html); + editor = e_msg_composer_get_editor (composer); + view = e_html_editor_get_view (editor); + editor_selection = e_html_editor_view_get_selection (view); + e_html_editor_selection_insert_html (editor_selection, html); + e_html_editor_view_check_magic_links (view, FALSE); + e_html_editor_view_force_spell_check (view); + + e_html_editor_selection_scroll_to_caret (editor_selection); g_free (html); return TRUE; @@ -631,7 +507,8 @@ gboolean e_composer_paste_image (EMsgComposer *composer, GtkClipboard *clipboard) { - GtkhtmlEditor *editor; + EHTMLEditor *editor; + EHTMLEditorView *html_editor_view; EAttachmentStore *store; EAttachmentView *view; GdkPixbuf *pixbuf = NULL; @@ -643,7 +520,6 @@ e_composer_paste_image (EMsgComposer *composer, g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), FALSE); g_return_val_if_fail (GTK_IS_CLIPBOARD (clipboard), FALSE); - editor = GTKHTML_EDITOR (composer); view = e_msg_composer_get_attachment_view (composer); store = e_attachment_view_get_store (view); @@ -673,9 +549,15 @@ e_composer_paste_image (EMsgComposer *composer, /* In HTML mode, paste the image into the message body. * In text mode, add the image to the attachment store. */ - if (gtkhtml_editor_get_html_mode (editor)) - gtkhtml_editor_insert_image (editor, uri); - else { + editor = e_msg_composer_get_editor (composer); + html_editor_view = e_html_editor_get_view (editor); + if (e_html_editor_view_get_html_mode (html_editor_view)) { + EHTMLEditorSelection *selection; + + selection = e_html_editor_view_get_selection (html_editor_view); + e_html_editor_selection_insert_image (selection, uri); + e_html_editor_selection_scroll_to_caret (selection); + } else { EAttachment *attachment; attachment = e_attachment_new_for_uri (uri); @@ -705,7 +587,9 @@ gboolean e_composer_paste_text (EMsgComposer *composer, GtkClipboard *clipboard) { - GtkhtmlEditor *editor; + EHTMLEditor *editor; + EHTMLEditorView *view; + EHTMLEditorSelection *editor_selection; gchar *text; g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), FALSE); @@ -714,8 +598,18 @@ e_composer_paste_text (EMsgComposer *composer, text = gtk_clipboard_wait_for_text (clipboard); g_return_val_if_fail (text != NULL, FALSE); - editor = GTKHTML_EDITOR (composer); - gtkhtml_editor_insert_text (editor, text); + editor = e_msg_composer_get_editor (composer); + view = e_html_editor_get_view (editor); + editor_selection = e_html_editor_view_get_selection (view); + /* If WebView doesn't have focus, focus it */ + if (!gtk_widget_has_focus (GTK_WIDGET (view))) + gtk_widget_grab_focus (GTK_WIDGET (view)); + + e_html_editor_selection_insert_text (editor_selection, text); + + e_html_editor_view_check_magic_links (view, FALSE); + e_html_editor_view_force_spell_check (view); + e_html_editor_selection_scroll_to_caret (editor_selection); g_free (text); @@ -756,6 +650,35 @@ e_composer_paste_uris (EMsgComposer *composer, return TRUE; } +gboolean +e_composer_selection_is_base64_uris (EMsgComposer *composer, + GtkSelectionData *selection) +{ + gboolean all_base64_uris = TRUE; + gchar **uris; + guint ii; + + g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), FALSE); + g_return_val_if_fail (selection != NULL, FALSE); + + uris = gtk_selection_data_get_uris (selection); + + if (!uris) + return FALSE; + + for (ii = 0; uris[ii] != NULL; ii++) { + if (!((g_str_has_prefix (uris[ii], "data:") || strstr (uris[ii], ";data:")) + && strstr (uris[ii], ";base64,"))) { + all_base64_uris = FALSE; + break; + } + } + + g_strfreev (uris); + + return all_base64_uris; +} + gboolean e_composer_selection_is_image_uris (EMsgComposer *composer, GtkSelectionData *selection) @@ -769,7 +692,7 @@ e_composer_selection_is_image_uris (EMsgComposer *composer, uris = gtk_selection_data_get_uris (selection); - if (uris == NULL) + if (!uris) return FALSE; for (ii = 0; uris[ii] != NULL; ii++) { @@ -858,20 +781,256 @@ use_top_signature (EMsgComposer *composer) return top_signature; } +static void +composer_size_allocate_cb (GtkWidget *widget, + gpointer user_data) +{ + GtkWidget *scrolled_window; + GtkAdjustment *adj; + + scrolled_window = gtk_widget_get_parent (GTK_WIDGET (widget)); + adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (scrolled_window)); + + /* Scroll only when there is some size allocated */ + if (gtk_adjustment_get_upper (adj) != 0.0) { + /* Scroll web view down to caret */ + gtk_adjustment_set_value (adj, gtk_adjustment_get_upper (adj) - gtk_adjustment_get_page_size (adj)); + gtk_scrolled_window_set_vadjustment (GTK_SCROLLED_WINDOW (scrolled_window), adj); + /* Disconnect because we don't want to scroll down the view on every window size change */ + g_signal_handlers_disconnect_by_func ( + widget, G_CALLBACK (composer_size_allocate_cb), NULL); + } +} + +static void +insert_paragraph_with_input (WebKitDOMElement *paragraph, + WebKitDOMElement *body) +{ + WebKitDOMNode *node = webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (body)); + + if (node) { + webkit_dom_node_insert_before ( + WEBKIT_DOM_NODE (body), + WEBKIT_DOM_NODE (paragraph), + node, + NULL); + } else { + webkit_dom_node_append_child ( + WEBKIT_DOM_NODE (body), + WEBKIT_DOM_NODE (paragraph), + NULL); + } +} + +static void +composer_move_caret (EMsgComposer *composer) +{ + EHTMLEditor *editor; + EHTMLEditorView *view; + EHTMLEditorSelection *editor_selection; + GSettings *settings; + gboolean start_bottom, html_mode, top_signature; + gboolean has_paragraphs_in_body = TRUE; + WebKitDOMDocument *document; + WebKitDOMDOMWindow *window; + WebKitDOMDOMSelection *dom_selection; + WebKitDOMElement *input_start, *element, *signature; + WebKitDOMHTMLElement *body; + WebKitDOMNodeList *list, *blockquotes; + WebKitDOMRange *new_range; + + /* When there is an option composer-reply-start-bottom set we have + * to move the caret between reply and signature. */ + settings = g_settings_new ("org.gnome.evolution.mail"); + start_bottom = g_settings_get_boolean (settings, "composer-reply-start-bottom"); + g_object_unref (settings); + + top_signature = + use_top_signature (composer) && + !composer->priv->is_from_message && + !composer->priv->is_from_new_message; + + editor = e_msg_composer_get_editor (composer); + view = e_html_editor_get_view (editor); + editor_selection = e_html_editor_view_get_selection (view); + html_mode = e_html_editor_view_get_html_mode (view); + + document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view)); + window = webkit_dom_document_get_default_view (document); + dom_selection = webkit_dom_dom_window_get_selection (window); + + body = webkit_dom_document_get_body (document); + webkit_dom_element_set_attribute ( + WEBKIT_DOM_ELEMENT (body), "data-message", "", NULL); + new_range = webkit_dom_document_create_range (document); + + element = webkit_dom_document_get_element_by_id (document, "-x-evo-caret-position"); + /* Caret position found => composer mode changed */ + if (element) { + e_html_editor_selection_restore_caret_position (editor_selection); + /* We want to force spellcheck just in case that we switched to plain + * text mode (when switching to html mode, the underlined words are + * preserved */ + if (!html_mode) + e_html_editor_view_force_spell_check (view); + return; + } + + /* If editing message as new don't handle with caret */ + if (composer->priv->is_from_message || composer->priv->is_from_draft) { + if (composer->priv->is_from_message) + webkit_dom_element_set_attribute ( + WEBKIT_DOM_ELEMENT (body), + "data-edit-as-new", + "", + NULL); + e_html_editor_selection_restore_caret_position (editor_selection); + e_html_editor_selection_scroll_to_caret (editor_selection); + + e_html_editor_view_force_spell_check (view); + return; + } + + e_html_editor_selection_block_selection_changed (editor_selection); + + /* When the new message is written from the beginning - note it into body */ + if (composer->priv->is_from_new_message) { + webkit_dom_element_set_attribute ( + WEBKIT_DOM_ELEMENT (body), "data-new-message", "", NULL); + } + + list = webkit_dom_document_get_elements_by_class_name (document, "-x-evo-paragraph"); + signature = webkit_dom_document_query_selector (document, ".-x-evo-signature", NULL); + /* Situation when wrapped paragraph is just in signature and not in message body */ + if (webkit_dom_node_list_get_length (list) == 1) { + if (signature && webkit_dom_element_query_selector (signature, ".-x-evo-paragraph", NULL)) + has_paragraphs_in_body = FALSE; + } + + if (webkit_dom_node_list_get_length (list) == 0) + has_paragraphs_in_body = FALSE; + + blockquotes = webkit_dom_document_get_elements_by_tag_name (document, "blockquote"); + + if (!has_paragraphs_in_body) { + element = e_html_editor_selection_get_paragraph_element ( + editor_selection, document, -1, 0); + webkit_dom_element_set_id (element, "-x-evo-input-start"); + webkit_dom_html_element_set_inner_html ( + WEBKIT_DOM_HTML_ELEMENT (element), UNICODE_ZERO_WIDTH_SPACE, NULL); + if (top_signature) + element_add_class (element, "-x-evo-top-signature"); + } + + if (start_bottom) { + if (webkit_dom_node_list_get_length (blockquotes) != 0) { + if (!has_paragraphs_in_body) { + if (!top_signature) { + webkit_dom_node_insert_before ( + WEBKIT_DOM_NODE (body), + WEBKIT_DOM_NODE (element), + signature ? + webkit_dom_node_get_parent_node ( + WEBKIT_DOM_NODE (signature)) : + webkit_dom_node_get_next_sibling ( + webkit_dom_node_list_item ( + blockquotes, 0)), + NULL); + } else { + webkit_dom_node_append_child ( + WEBKIT_DOM_NODE (body), + WEBKIT_DOM_NODE (element), + NULL); + } + } + + e_html_editor_selection_restore_caret_position (editor_selection); + if (!html_mode) + e_html_editor_view_quote_plain_text (view); + e_html_editor_view_force_spell_check (view); + + input_start = webkit_dom_document_get_element_by_id ( + document, "-x-evo-input-start"); + if (input_start) + webkit_dom_range_select_node_contents ( + new_range, WEBKIT_DOM_NODE (input_start), NULL); + + webkit_dom_range_collapse (new_range, FALSE, NULL); + } else { + if (!has_paragraphs_in_body) + insert_paragraph_with_input ( + element, WEBKIT_DOM_ELEMENT (body)); + + webkit_dom_range_select_node_contents ( + new_range, + webkit_dom_node_get_first_child ( + WEBKIT_DOM_NODE (body)), + NULL); + webkit_dom_range_collapse (new_range, TRUE, NULL); + } + + g_signal_connect ( + view, "size-allocate", + G_CALLBACK (composer_size_allocate_cb), NULL); + } else { + /* Move caret on the beginning of message */ + if (!has_paragraphs_in_body) { + insert_paragraph_with_input ( + element, WEBKIT_DOM_ELEMENT (body)); + + if (webkit_dom_node_list_get_length (blockquotes) != 0) { + if (!html_mode) { + WebKitDOMNode *blockquote; + + blockquote = webkit_dom_node_list_item (blockquotes, 0); + + /* FIXME determine when we can skip this */ + e_html_editor_selection_wrap_paragraph ( + editor_selection, + WEBKIT_DOM_ELEMENT (blockquote)); + + e_html_editor_selection_restore_caret_position (editor_selection); + e_html_editor_view_quote_plain_text (view); + body = webkit_dom_document_get_body (document); + } + } + } + + e_html_editor_view_force_spell_check (view); + + webkit_dom_range_select_node_contents ( + new_range, + WEBKIT_DOM_NODE ( + webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (body))), + NULL); + webkit_dom_range_collapse (new_range, TRUE, NULL); + } + + webkit_dom_dom_selection_remove_all_ranges (dom_selection); + webkit_dom_dom_selection_add_range (dom_selection, new_range); + + e_html_editor_selection_unblock_selection_changed (editor_selection); +} + static void composer_load_signature_cb (EMailSignatureComboBox *combo_box, GAsyncResult *result, EMsgComposer *composer) { GString *html_buffer = NULL; - GtkhtmlEditor *editor; gchar *contents = NULL; gsize length = 0; const gchar *active_id; - gchar *encoded_uid = NULL; gboolean top_signature; gboolean is_html; GError *error = NULL; + EHTMLEditor *editor; + EHTMLEditorView *view; + WebKitDOMDocument *document; + WebKitDOMNodeList *signatures; + gulong list_length, ii; + GSettings *settings; + gboolean start_bottom; e_mail_signature_combo_box_load_selected_finish ( combo_box, result, &contents, &length, &is_html, &error); @@ -887,7 +1046,12 @@ composer_load_signature_cb (EMailSignatureComboBox *combo_box, * Always put the signature at the bottom for that case. */ top_signature = use_top_signature (composer) && - !composer->priv->is_from_message; + !composer->priv->is_from_message && + !composer->priv->is_from_new_message; + + settings = g_settings_new ("org.gnome.evolution.mail"); + start_bottom = g_settings_get_boolean (settings, "composer-reply-start-bottom"); + g_object_unref (settings); if (contents == NULL) goto insert; @@ -911,24 +1075,13 @@ composer_load_signature_cb (EMailSignatureComboBox *combo_box, /* The combo box active ID is the signature's ESource UID. */ active_id = gtk_combo_box_get_active_id (GTK_COMBO_BOX (combo_box)); - if (active_id != NULL && *active_id != '\0') - encoded_uid = e_composer_encode_clue_value (active_id); - g_string_append_printf ( html_buffer, - "" - "", - (encoded_uid != NULL) ? encoded_uid : ""); - - g_string_append ( - html_buffer, - "
"); + "", + (active_id != NULL) ? active_id : ""); if (!is_html) - g_string_append (html_buffer, "
\n");
+		g_string_append (html_buffer, "
");
 
 	/* The signature dash convention ("-- \n") is specified
 	 * in the "Son of RFC 1036", section 4.3.2.
@@ -939,8 +1092,8 @@ composer_load_signature_cb (EMailSignatureComboBox *combo_box,
 		const gchar *delim_nl;
 
 		if (is_html) {
-			delim = "-- \n
"; - delim_nl = "\n-- \n
"; + delim = "--
"; + delim_nl = "\n--
"; } else { delim = "-- \n"; delim_nl = "\n-- \n"; @@ -958,73 +1111,148 @@ composer_load_signature_cb (EMailSignatureComboBox *combo_box, g_string_append_len (html_buffer, contents, length); if (!is_html) - g_string_append (html_buffer, "
\n"); - - if (top_signature) - g_string_append (html_buffer, "
"); - - g_string_append (html_buffer, "
"); + g_string_append (html_buffer, ""); - g_free (encoded_uid); + g_string_append (html_buffer, ""); g_free (contents); insert: /* Remove the old signature and insert the new one. */ - editor = GTKHTML_EDITOR (composer); + editor = e_msg_composer_get_editor (composer); + view = e_html_editor_get_view (editor); + + document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view)); + + signatures = webkit_dom_document_get_elements_by_class_name ( + document, "-x-evo-signature"); + list_length = webkit_dom_node_list_get_length (signatures); + for (ii = 0; ii < list_length; ii++) { + WebKitDOMNode *node; + gchar *id; + + node = webkit_dom_node_list_item (signatures, ii); + id = webkit_dom_element_get_id (WEBKIT_DOM_ELEMENT (node)); + + /* When we are editing a message with signature we need to set active + * signature id in signature combo box otherwise no signature will be + * added but we have to do it just once when the composer opens */ + if (composer->priv->is_from_message && composer->priv->set_signature_from_message) { + gchar *name = webkit_dom_element_get_attribute (WEBKIT_DOM_ELEMENT (node), "name"); + gtk_combo_box_set_active_id (GTK_COMBO_BOX (combo_box), name); + g_free (name); + composer->priv->set_signature_from_message = FALSE; + } + + if (id && (strlen (id) == 1) && (*id == '1')) { + /* We have to remove the div containing the span with signature */ + WebKitDOMNode *next_sibling; + WebKitDOMNode *parent; + + parent = webkit_dom_node_get_parent_node (node); + next_sibling = webkit_dom_node_get_next_sibling (parent); - /* This prevents our command before/after callbacks from - * screwing around with the signature as we insert it. */ - composer->priv->in_signature_insert = TRUE; + if (WEBKIT_DOM_IS_HTMLBR_ELEMENT (next_sibling)) + webkit_dom_node_remove_child ( + webkit_dom_node_get_parent_node (next_sibling), + next_sibling, + NULL); - gtkhtml_editor_freeze (editor); - gtkhtml_editor_run_command (editor, "cursor-position-save"); - gtkhtml_editor_undo_begin (editor, "Set signature", "Reset signature"); + webkit_dom_node_remove_child ( + webkit_dom_node_get_parent_node (parent), + parent, + NULL); + + g_free (id); + break; + } - gtkhtml_editor_run_command (editor, "block-selection"); - gtkhtml_editor_run_command (editor, "cursor-bod"); - if (gtkhtml_editor_search_by_data (editor, 1, "ClueFlow", "signature", "1")) { - gtkhtml_editor_run_command (editor, "select-paragraph"); - gtkhtml_editor_run_command (editor, "delete"); - gtkhtml_editor_set_paragraph_data (editor, "signature", "0"); - gtkhtml_editor_run_command (editor, "delete-back"); + g_free (id); } - gtkhtml_editor_run_command (editor, "unblock-selection"); if (html_buffer != NULL) { - gtkhtml_editor_run_command (editor, "insert-paragraph"); - if (!gtkhtml_editor_run_command (editor, "cursor-backward")) - gtkhtml_editor_run_command (editor, "insert-paragraph"); - else - gtkhtml_editor_run_command (editor, "cursor-forward"); - - gtkhtml_editor_set_paragraph_data (editor, "orig", "0"); - gtkhtml_editor_run_command (editor, "indent-zero"); - gtkhtml_editor_run_command (editor, "style-normal"); - gtkhtml_editor_insert_html (editor, html_buffer->str); + if (*html_buffer->str) { + WebKitDOMElement *element; + WebKitDOMHTMLElement *body; + + body = webkit_dom_document_get_body (document); + element = webkit_dom_document_create_element (document, "DIV", NULL); + + webkit_dom_html_element_set_inner_html ( + WEBKIT_DOM_HTML_ELEMENT (element), html_buffer->str, NULL); + + if (top_signature) { + WebKitDOMNode *signature_inserted; + WebKitDOMNode *child = + webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (body)); + WebKitDOMElement *br = + webkit_dom_document_create_element ( + document, "br", NULL); + + if (start_bottom) { + signature_inserted = webkit_dom_node_insert_before ( + WEBKIT_DOM_NODE (body), + WEBKIT_DOM_NODE (element), + child, + NULL); + } else { + WebKitDOMElement *input_start = + webkit_dom_document_get_element_by_id ( + document, "-x-evo-input-start"); + /* When we are using signature on top the caret + * should be before the signature */ + signature_inserted = webkit_dom_node_insert_before ( + WEBKIT_DOM_NODE (body), + WEBKIT_DOM_NODE (element), + input_start ? + webkit_dom_node_get_next_sibling ( + WEBKIT_DOM_NODE (input_start)) : + child, + NULL); + } + + webkit_dom_node_insert_before ( + WEBKIT_DOM_NODE (body), + WEBKIT_DOM_NODE (br), + webkit_dom_node_get_next_sibling (signature_inserted), + NULL); + } else { + webkit_dom_node_append_child ( + WEBKIT_DOM_NODE (body), + WEBKIT_DOM_NODE (element), + NULL); + } + } g_string_free (html_buffer, TRUE); - - } else if (top_signature) { - /* Insert paragraph after the signature ClueFlow stuff. */ - if (gtkhtml_editor_run_command (editor, "cursor-forward")) - gtkhtml_editor_run_command (editor, "insert-paragraph"); } - gtkhtml_editor_undo_end (editor); - gtkhtml_editor_run_command (editor, "cursor-position-restore"); - gtkhtml_editor_thaw (editor); - - composer->priv->in_signature_insert = FALSE; + composer_move_caret (composer); exit: g_object_unref (composer); } -static gboolean -is_null_or_none (const gchar *text) +static void +composer_web_view_load_status_changed_cb (WebKitWebView *webkit_web_view, + GParamSpec *pspec, + EMsgComposer *composer) { - return !text || g_strcmp0 (text, "none") == 0; + WebKitLoadStatus status; + + g_return_if_fail (E_IS_MSG_COMPOSER (composer)); + + status = webkit_web_view_get_load_status (webkit_web_view); + + if (status != WEBKIT_LOAD_FINISHED) + return; + + g_signal_handlers_disconnect_by_func ( + webkit_web_view, + G_CALLBACK (composer_web_view_load_status_changed_cb), + NULL); + + e_composer_update_signature (composer); } void @@ -1032,29 +1260,35 @@ e_composer_update_signature (EMsgComposer *composer) { EComposerHeaderTable *table; EMailSignatureComboBox *combo_box; - const gchar *signature_uid; + EHTMLEditor *editor; + EHTMLEditorView *view; + WebKitLoadStatus status; g_return_if_fail (E_IS_MSG_COMPOSER (composer)); - /* Do nothing if we're redirecting a message. */ - if (composer->priv->redirect) + /* Do nothing if we're redirecting a message or we disabled the signature * on purpose */ + if (composer->priv->redirect || composer->priv->disable_signature) return; table = e_msg_composer_get_header_table (composer); - signature_uid = e_composer_header_table_get_signature_uid (table); - - /* this is a case when the signature combo cleared itself for a reload */ - if (!signature_uid) - return; - - if (g_strcmp0 (signature_uid, composer->priv->selected_signature_uid) == 0 || - (is_null_or_none (signature_uid) && is_null_or_none (composer->priv->selected_signature_uid))) - return; - - g_free (composer->priv->selected_signature_uid); - composer->priv->selected_signature_uid = g_strdup (signature_uid); - combo_box = e_composer_header_table_get_signature_combo_box (table); + editor = e_msg_composer_get_editor (composer); + view = e_html_editor_get_view (editor); + + status = webkit_web_view_get_load_status (WEBKIT_WEB_VIEW (view)); + /* If document is not loaded, we will wait for him */ + if (status != WEBKIT_LOAD_FINISHED) { + /* Disconnect previous handlers */ + g_signal_handlers_disconnect_by_func ( + WEBKIT_WEB_VIEW (view), + G_CALLBACK (composer_web_view_load_status_changed_cb), + composer); + g_signal_connect ( + WEBKIT_WEB_VIEW(view), "notify::load-status", + G_CALLBACK (composer_web_view_load_status_changed_cb), + composer); + return; + } /* XXX Signature files should be local and therefore load quickly, * so while we do load them asynchronously we don't allow for diff --git a/composer/e-composer-private.h b/composer/e-composer-private.h index b09e025e0c..46c72b78ee 100644 --- a/composer/e-composer-private.h +++ b/composer/e-composer-private.h @@ -32,7 +32,6 @@ #include #include "e-composer-actions.h" -#include "e-composer-activity.h" #include "e-composer-header-table.h" #ifdef HAVE_XFREE @@ -58,11 +57,11 @@ struct _EMsgComposerPrivate { gpointer shell; /* weak pointer */ + EHTMLEditor *editor; + /*** UI Management ***/ GtkWidget *header_table; - GtkWidget *activity_bar; - GtkWidget *alert_bar; GtkWidget *attachment_paned; EFocusTracker *focus_tracker; @@ -82,10 +81,6 @@ struct _EMsgComposerPrivate { GtkWidget *address_dialog; - GHashTable *inline_images; - GHashTable *inline_images_by_url; - GList *current_images; - gchar *mime_type; gchar *mime_body; gchar *charset; @@ -97,9 +92,18 @@ struct _EMsgComposerPrivate { CamelMimeMessage *redirect; + gboolean busy; + gboolean disable_signature; + gboolean is_from_draft; gboolean is_from_message; - - gchar *selected_signature_uid; + gboolean is_from_new_message; + /* The web view is uneditable while the editor is busy. + * This is used to restore the previous editable state. */ + gboolean saved_editable; + gboolean set_signature_from_message; + + gint focused_entry_selection_start; + gint focused_entry_selection_end; }; void e_composer_private_constructed (EMsgComposer *composer); @@ -121,6 +125,9 @@ gboolean e_composer_paste_text (EMsgComposer *composer, GtkClipboard *clipboard); gboolean e_composer_paste_uris (EMsgComposer *composer, GtkClipboard *clipboard); +gboolean e_composer_selection_is_base64_uris + (EMsgComposer *composer, + GtkSelectionData *selection); gboolean e_composer_selection_is_image_uris (EMsgComposer *composer, GtkSelectionData *selection); diff --git a/composer/e-composer-spell-header.c b/composer/e-composer-spell-header.c index c4fc471bc9..b2d2dfa2a4 100644 --- a/composer/e-composer-spell-header.c +++ b/composer/e-composer-spell-header.c @@ -63,16 +63,3 @@ e_composer_spell_header_new_button (ESourceRegistry *registry, "registry", registry, NULL); } -void -e_composer_spell_header_set_languages (EComposerSpellHeader *header, - GList *languages) -{ - ESpellEntry *spell_entry; - - g_return_if_fail (header != NULL); - - spell_entry = E_SPELL_ENTRY (E_COMPOSER_HEADER (header)->input_widget); - g_return_if_fail (spell_entry != NULL); - - e_spell_entry_set_languages (spell_entry, languages); -} diff --git a/composer/e-composer-spell-header.h b/composer/e-composer-spell-header.h index d440ee157c..f0034a0f84 100644 --- a/composer/e-composer-spell-header.h +++ b/composer/e-composer-spell-header.h @@ -66,9 +66,6 @@ EComposerHeader * e_composer_spell_header_new_button (ESourceRegistry *registry, const gchar *label); -void e_composer_spell_header_set_languages - (EComposerSpellHeader *header, - GList *languages); G_END_DECLS diff --git a/composer/e-msg-composer.c b/composer/e-msg-composer.c index 57b716c1ad..d3891beb4e 100644 --- a/composer/e-msg-composer.c +++ b/composer/e-msg-composer.c @@ -35,6 +35,7 @@ #include #include #include +#include #include "e-composer-private.h" @@ -72,19 +73,22 @@ struct _AsyncContext { /* Flags for building a message. */ typedef enum { - COMPOSER_FLAG_HTML_CONTENT = 1 << 0, - COMPOSER_FLAG_SAVE_OBJECT_DATA = 1 << 1, - COMPOSER_FLAG_PRIORITIZE_MESSAGE = 1 << 2, - COMPOSER_FLAG_REQUEST_READ_RECEIPT = 1 << 3, - COMPOSER_FLAG_PGP_SIGN = 1 << 4, - COMPOSER_FLAG_PGP_ENCRYPT = 1 << 5, - COMPOSER_FLAG_SMIME_SIGN = 1 << 6, - COMPOSER_FLAG_SMIME_ENCRYPT = 1 << 7, - COMPOSER_FLAG_DRAFT = 1 << 8 + COMPOSER_FLAG_HTML_CONTENT = 1 << 0, + COMPOSER_FLAG_SAVE_OBJECT_DATA = 1 << 1, + COMPOSER_FLAG_PRIORITIZE_MESSAGE = 1 << 2, + COMPOSER_FLAG_REQUEST_READ_RECEIPT = 1 << 3, + COMPOSER_FLAG_PGP_SIGN = 1 << 4, + COMPOSER_FLAG_PGP_ENCRYPT = 1 << 5, + COMPOSER_FLAG_SMIME_SIGN = 1 << 6, + COMPOSER_FLAG_SMIME_ENCRYPT = 1 << 7, + COMPOSER_FLAG_HTML_MODE = 1 << 8, + COMPOSER_FLAG_SAVE_DRAFT = 1 << 9 } ComposerFlags; enum { PROP_0, + PROP_BUSY, + PROP_EDITOR, PROP_FOCUS_TRACKER, PROP_SHELL }; @@ -98,6 +102,24 @@ enum { LAST_SIGNAL }; +enum DndTargetType { + DND_TARGET_TYPE_TEXT_URI_LIST, + DND_TARGET_TYPE_MOZILLA_URL, + DND_TARGET_TYPE_TEXT_HTML, + DND_TARGET_TYPE_UTF8_STRING, + DND_TARGET_TYPE_TEXT_PLAIN, + DND_TARGET_TYPE_STRING +}; + +static GtkTargetEntry drag_dest_targets[] = { + { (gchar *) "text/uri-list", 0, DND_TARGET_TYPE_TEXT_URI_LIST }, + { (gchar *) "_NETSCAPE_URL", 0, DND_TARGET_TYPE_MOZILLA_URL }, + { (gchar *) "text/html", 0, DND_TARGET_TYPE_TEXT_HTML }, + { (gchar *) "UTF8_STRING", 0, DND_TARGET_TYPE_UTF8_STRING }, + { (gchar *) "text/plain", 0, DND_TARGET_TYPE_TEXT_PLAIN }, + { (gchar *) "STRING", 0, DND_TARGET_TYPE_STRING }, +}; + static guint signals[LAST_SIGNAL]; /* used by e_msg_composer_add_message_attachments () */ @@ -128,14 +150,10 @@ static void handle_multipart_signed (EMsgComposer *composer, GCancellable *cancellable, gint depth); -static void e_msg_composer_alert_sink_init (EAlertSinkInterface *iface); - G_DEFINE_TYPE_WITH_CODE ( EMsgComposer, e_msg_composer, - GTKHTML_TYPE_EDITOR, - G_IMPLEMENT_INTERFACE ( - E_TYPE_ALERT_SINK, e_msg_composer_alert_sink_init) + GTK_TYPE_WINDOW, G_IMPLEMENT_INTERFACE (E_TYPE_EXTENSIBLE, NULL)) static void @@ -407,45 +425,6 @@ best_charset (GByteArray *buf, return g_strdup (charset); } -static void -clear_current_images (EMsgComposer *composer) -{ - EMsgComposerPrivate *p = composer->priv; - g_list_free (p->current_images); - p->current_images = NULL; -} - -void -e_msg_composer_clear_inlined_table (EMsgComposer *composer) -{ - EMsgComposerPrivate *p = composer->priv; - - g_hash_table_remove_all (p->inline_images); - g_hash_table_remove_all (p->inline_images_by_url); -} - -static void -add_inlined_images (EMsgComposer *composer, - CamelMultipart *multipart) -{ - EMsgComposerPrivate *p = composer->priv; - - GList *d = p->current_images; - GHashTable *added; - - added = g_hash_table_new (g_direct_hash, g_direct_equal); - while (d) { - CamelMimePart *part = d->data; - - if (!g_hash_table_lookup (added, part)) { - camel_multipart_add_part (multipart, part); - g_hash_table_insert (added, part, part); - } - d = d->next; - } - g_hash_table_destroy (added); -} - /* These functions builds a CamelMimeMessage for the message that the user has * composed in 'composer'. */ @@ -1040,6 +1019,25 @@ composer_build_message_thread (GSimpleAsyncResult *simple, #endif /* HAVE_NSS */ } +static void +composer_add_evolution_composer_mode_header (CamelMedium *medium, + ComposerFlags flags) +{ + GString *string; + + string = g_string_sized_new (128); + + if (flags & COMPOSER_FLAG_HTML_MODE) + g_string_append (string, "text/html"); + else + g_string_append (string, "text/plain"); + + camel_medium_add_header ( + medium, "X-Evolution-Composer-Mode", string->str); + + g_string_free (string, TRUE); +} + static void composer_add_evolution_format_header (CamelMedium *medium, ComposerFlags flags) @@ -1082,7 +1080,6 @@ composer_build_message (EMsgComposer *composer, EMsgComposerPrivate *priv; GSimpleAsyncResult *simple; AsyncContext *context; - GtkhtmlEditor *editor; EAttachmentView *view; EAttachmentStore *store; EComposerHeaderTable *table; @@ -1103,7 +1100,6 @@ composer_build_message (EMsgComposer *composer, gint i; priv = composer->priv; - editor = GTKHTML_EDITOR (composer); table = e_msg_composer_get_header_table (composer); view = e_msg_composer_get_attachment_view (composer); store = e_attachment_view_get_store (view); @@ -1120,19 +1116,17 @@ composer_build_message (EMsgComposer *composer, context->session = e_msg_composer_ref_session (composer); context->from = e_msg_composer_get_from (composer); - if ((flags & COMPOSER_FLAG_DRAFT) == 0) { - if ((flags & COMPOSER_FLAG_PGP_SIGN) != 0) - context->pgp_sign = TRUE; + if (flags & COMPOSER_FLAG_PGP_SIGN) + context->pgp_sign = TRUE; - if ((flags & COMPOSER_FLAG_PGP_ENCRYPT) != 0) - context->pgp_encrypt = TRUE; + if (flags & COMPOSER_FLAG_PGP_ENCRYPT) + context->pgp_encrypt = TRUE; - if ((flags & COMPOSER_FLAG_SMIME_SIGN) != 0) - context->smime_sign = TRUE; + if (flags & COMPOSER_FLAG_SMIME_SIGN) + context->smime_sign = TRUE; - if ((flags & COMPOSER_FLAG_SMIME_ENCRYPT) != 0) - context->smime_encrypt = TRUE; - } + if (flags & COMPOSER_FLAG_SMIME_ENCRYPT) + context->smime_encrypt = TRUE; context->need_thread = context->pgp_sign || context->pgp_encrypt || @@ -1210,6 +1204,41 @@ composer_build_message (EMsgComposer *composer, composer_add_evolution_format_header ( CAMEL_MEDIUM (context->message), flags); + /* X-Evolution-Composer-Mode */ + composer_add_evolution_composer_mode_header ( + CAMEL_MEDIUM (context->message), flags); + + if (flags & COMPOSER_FLAG_SAVE_DRAFT) { + gchar *text; + EHTMLEditor *editor; + EHTMLEditorView *view; + EHTMLEditorSelection *selection; + + editor = e_msg_composer_get_editor (composer); + view = e_html_editor_get_view (editor); + selection = e_html_editor_view_get_selection (view); + + data = g_byte_array_new (); + + e_html_editor_view_embed_styles (view); + e_html_editor_selection_save_caret_position (selection); + + text = e_html_editor_view_get_text_html_for_drafts (view); + + e_html_editor_view_remove_embed_styles (view); + e_html_editor_selection_restore_caret_position (selection); + + g_byte_array_append (data, (guint8 *) text, strlen (text)); + + g_free (text); + + type = camel_content_type_new ("text", "html"); + camel_content_type_set_param (type, "charset", "utf-8"); + iconv_charset = camel_iconv_charset_name ("utf-8"); + + goto wrap_drafts_html; + } + /* Build the text/plain part. */ if (priv->mime_body) { @@ -1235,11 +1264,14 @@ composer_build_message (EMsgComposer *composer, } else { gchar *text; - gsize length; + EHTMLEditor *editor; + EHTMLEditorView *view; + editor = e_msg_composer_get_editor (composer); + view = e_html_editor_get_view (editor); data = g_byte_array_new (); - text = gtkhtml_editor_get_text_plain (editor, &length); - g_byte_array_append (data, (guint8 *) text, (guint) length); + text = e_html_editor_view_get_text_plain (view); + g_byte_array_append (data, (guint8 *) text, strlen (text)); g_free (text); type = camel_content_type_new ("text", "plain"); @@ -1252,6 +1284,7 @@ composer_build_message (EMsgComposer *composer, } } + wrap_drafts_html: mem_stream = camel_stream_mem_new_with_byte_array (data); stream = camel_stream_filter_new (mem_stream); g_object_unref (mem_stream); @@ -1298,25 +1331,27 @@ composer_build_message (EMsgComposer *composer, * ... */ - if (flags & COMPOSER_FLAG_HTML_CONTENT) { + if ((flags & COMPOSER_FLAG_HTML_CONTENT) != 0 && + !(flags & COMPOSER_FLAG_SAVE_DRAFT)) { gchar *text; + guint count; gsize length; gboolean pre_encode; + EHTMLEditor *editor; + EHTMLEditorView *view; + GList *inline_images; - clear_current_images (composer); - - if (flags & COMPOSER_FLAG_SAVE_OBJECT_DATA) - gtkhtml_editor_run_command (editor, "save-data-on"); + editor = e_msg_composer_get_editor (composer); + view = e_html_editor_get_view (editor); + inline_images = e_html_editor_view_get_parts_for_inline_images (view); data = g_byte_array_new (); - text = gtkhtml_editor_get_text_html (editor, &length); + text = e_html_editor_view_get_text_html (view); + length = strlen (text); g_byte_array_append (data, (guint8 *) text, (guint) length); pre_encode = text_requires_quoted_printable (text, length); g_free (text); - if (flags & COMPOSER_FLAG_SAVE_OBJECT_DATA) - gtkhtml_editor_run_command (editor, "save-data-off"); - mem_stream = camel_stream_mem_new_with_byte_array (data); stream = camel_stream_filter_new (mem_stream); g_object_unref (mem_stream); @@ -1366,7 +1401,9 @@ composer_build_message (EMsgComposer *composer, /* If there are inlined images, construct a multipart/related * containing the multipart/alternative and the images. */ - if (priv->current_images) { + count = g_list_length (inline_images); + if (count > 0) { + guint ii; CamelMultipart *html_with_images; html_with_images = camel_multipart_new (); @@ -1385,8 +1422,12 @@ composer_build_message (EMsgComposer *composer, g_object_unref (body); - add_inlined_images (composer, html_with_images); - clear_current_images (composer); + for (ii = 0; ii < count; ii++) { + CamelMimePart *part = g_list_nth_data (inline_images, ii); + camel_multipart_add_part ( + html_with_images, part); + g_object_unref (part); + } context->top_level_part = CAMEL_DATA_WRAPPER (html_with_images); @@ -1493,7 +1534,8 @@ use_top_signature (EMsgComposer *composer) return top_signature; } -#define NO_SIGNATURE_TEXT \ +/* FIXME WEBKIT Nope....*/ +#define NO_SIGNATURE_TEXT \ "" \ @@ -1506,7 +1548,8 @@ set_editor_text (EMsgComposer *composer, const gchar *text, gboolean set_signature) { - gchar *body = NULL; + EHTMLEditor *editor; + EHTMLEditorView *view; g_return_if_fail (E_IS_MSG_COMPOSER (composer)); g_return_if_fail (text != NULL); @@ -1527,20 +1570,23 @@ set_editor_text (EMsgComposer *composer, /* "Edit as New Message" sets "priv->is_from_message". * Always put the signature at the bottom for that case. */ + + editor = e_msg_composer_get_editor (composer); + view = e_html_editor_get_view (editor); + if (!composer->priv->is_from_message && use_top_signature (composer)) { + gchar *body; /* put marker to the top */ - body = g_strdup_printf ("
" NO_SIGNATURE_TEXT "%s", text); + body = g_strdup_printf ("
%s", text); + e_html_editor_view_set_text_html (view, body); + g_free (body); } else { - /* no marker => to the bottom */ - body = g_strdup_printf ("%s
", text); + e_html_editor_view_set_text_html (view, text); } - gtkhtml_editor_set_text_html (GTKHTML_EDITOR (composer), body, -1); - if (set_signature) e_composer_update_signature (composer); - g_free (body); } /* Miscellaneous callbacks. */ @@ -1548,12 +1594,16 @@ set_editor_text (EMsgComposer *composer, static void attachment_store_changed_cb (EMsgComposer *composer) { - GtkhtmlEditor *editor; + EHTMLEditor *editor; + EHTMLEditorView *view; /* Mark the editor as changed so it prompts about unsaved * changes on close. */ - editor = GTKHTML_EDITOR (composer); - gtkhtml_editor_set_changed (editor, TRUE); + editor = e_msg_composer_get_editor (composer); + if (editor) { + view = e_html_editor_get_view (editor); + e_html_editor_view_set_changed (view, TRUE); + } } static void @@ -1648,11 +1698,11 @@ msg_composer_paste_clipboard_targets_cb (GtkClipboard *clipboard, gint n_targets, EMsgComposer *composer) { - GtkhtmlEditor *editor; - gboolean html_mode; + EHTMLEditor *editor; + EHTMLEditorView *view; - editor = GTKHTML_EDITOR (composer); - html_mode = gtkhtml_editor_get_html_mode (editor); + editor = e_msg_composer_get_editor (composer); + view = e_html_editor_get_view (editor); /* Order is important here to ensure common use cases are * handled correctly. See GNOME bug #603715 for details. */ @@ -1663,7 +1713,7 @@ msg_composer_paste_clipboard_targets_cb (GtkClipboard *clipboard, } /* Only paste HTML content in HTML mode. */ - if (html_mode) { + if (e_html_editor_view_get_html_mode (view)) { if (e_targets_include_html (targets, n_targets)) { e_composer_paste_html (composer, clipboard); return; @@ -1682,48 +1732,31 @@ msg_composer_paste_clipboard_targets_cb (GtkClipboard *clipboard, } static void -msg_composer_paste_clipboard_cb (EWebViewGtkHTML *web_view, - EMsgComposer *composer) +msg_composer_paste_primary_clipboard_cb (EHTMLEditorView *view, + EMsgComposer *composer) { GtkClipboard *clipboard; - clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD); + clipboard = gtk_clipboard_get (GDK_SELECTION_PRIMARY); gtk_clipboard_request_targets ( clipboard, (GtkClipboardTargetsReceivedFunc) msg_composer_paste_clipboard_targets_cb, composer); - - g_signal_stop_emission_by_name (web_view, "paste-clipboard"); } static void -msg_composer_realize_gtkhtml_cb (GtkWidget *widget, +msg_composer_paste_clipboard_cb (EHTMLEditorView *view, EMsgComposer *composer) { - EAttachmentView *view; - GtkTargetList *target_list; - GtkTargetEntry *targets; - gint n_targets; - - /* XXX GtkHTML doesn't set itself up as a drag destination until - * it's realized, and we need to amend to its target list so - * it will accept the same drag targets as the attachment bar. - * Do this any earlier and GtkHTML will just overwrite us. */ - - /* When redirecting a message, the message body is not - * editable and therefore cannot be a drag destination. */ - if (!e_web_view_gtkhtml_get_editable (E_WEB_VIEW_GTKHTML (widget))) - return; - - view = e_msg_composer_get_attachment_view (composer); + GtkClipboard *clipboard; - target_list = e_attachment_view_get_target_list (view); - targets = gtk_target_table_new_from_list (target_list, &n_targets); + clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD); - target_list = gtk_drag_dest_get_target_list (widget); - gtk_target_list_add_table (target_list, targets, n_targets); + gtk_clipboard_request_targets ( + clipboard, (GtkClipboardTargetsReceivedFunc) + msg_composer_paste_clipboard_targets_cb, composer); - gtk_target_table_free (targets, n_targets); + g_signal_stop_emission_by_name (view, "paste-clipboard"); } static gboolean @@ -1744,6 +1777,31 @@ msg_composer_drag_motion_cb (GtkWidget *widget, return e_attachment_view_drag_motion (view, context, x, y, time); } +static gchar * +next_uri (guchar **uri_list, + gint *len, + gint *list_len) +{ + guchar *uri, *begin; + + begin = *uri_list; + *len = 0; + while (**uri_list && **uri_list != '\n' && **uri_list != '\r' && *list_len) { + (*uri_list) ++; + (*len) ++; + (*list_len) --; + } + + uri = (guchar *) g_strndup ((gchar *) begin, *len); + + while ((!**uri_list || **uri_list == '\n' || **uri_list == '\r') && *list_len) { + (*uri_list) ++; + (*list_len) --; + } + + return (gchar *) uri; +} + static void msg_composer_drag_data_received_cb (GtkWidget *widget, GdkDragContext *context, @@ -1755,43 +1813,87 @@ msg_composer_drag_data_received_cb (GtkWidget *widget, EMsgComposer *composer) { EAttachmentView *view; + EHTMLEditor *editor; + EHTMLEditorView *html_editor_view; + EHTMLEditorSelection *editor_selection; - /* HTML mode has a few special cases for drops... */ - if (gtkhtml_editor_get_html_mode (GTKHTML_EDITOR (composer))) { + editor = e_msg_composer_get_editor (composer); + html_editor_view = e_html_editor_get_view (editor); + editor_selection = e_html_editor_view_get_selection (html_editor_view); + /* HTML mode has a few special cases for drops... */ + if (e_html_editor_view_get_html_mode (html_editor_view)) { /* If we're receiving an image, we want the image to be * inserted in the message body. Let GtkHtml handle it. */ + /* FIXME WebKit - how to reproduce this? if (gtk_selection_data_targets_include_image (selection, TRUE)) return; - + */ /* If we're receiving URIs and -all- the URIs point to * image files, we want the image(s) to be inserted in - * the message body. Let GtkHtml handle it. */ - if (e_composer_selection_is_image_uris (composer, selection)) - return; - } + * the message body. */ + if (e_composer_selection_is_image_uris (composer, selection)) { + const guchar *data; + gint length; + gint list_len, len; + gchar *uri; + + data = gtk_selection_data_get_data (selection); + length = gtk_selection_data_get_length (selection); + + if (!data || length < 0) + return; + + list_len = length; + do { + uri = next_uri ((guchar **) &data, &len, &list_len); + e_html_editor_selection_insert_image (editor_selection, uri); + } while (list_len); + } - view = e_msg_composer_get_attachment_view (composer); + if (e_composer_selection_is_base64_uris (composer, selection)) { + const guchar *data; + gint length; + gint list_len, len; + gchar *uri; - /* Forward the data to the attachment view. Note that calling - * e_attachment_view_drag_data_received() will not work because - * that function only handles the case where all the other drag - * handlers have failed. */ - e_attachment_paned_drag_data_received ( - E_ATTACHMENT_PANED (view), - context, x, y, selection, info, time); + data = gtk_selection_data_get_data (selection); + length = gtk_selection_data_get_length (selection); - /* Stop the signal from propagating to GtkHtml. */ + if (!data || length < 0) + return; + + list_len = length; + do { + uri = next_uri ((guchar **) &data, &len, &list_len); + + e_html_editor_selection_insert_image (editor_selection, uri); + } while (list_len); + } + } else { + view = e_msg_composer_get_attachment_view (composer); + + /* Forward the data to the attachment view. Note that calling + * e_attachment_view_drag_data_received() will not work because + * that function only handles the case where all the other drag + * handlers have failed. */ + e_attachment_paned_drag_data_received ( + E_ATTACHMENT_PANED (view), + context, x, y, selection, info, time); + } + /* Stop the signal from propagating */ g_signal_stop_emission_by_name (widget, "drag-data-received"); } static void msg_composer_notify_header_cb (EMsgComposer *composer) { - GtkhtmlEditor *editor; + EHTMLEditor *editor; + EHTMLEditorView *view; - editor = GTKHTML_EDITOR (composer); - gtkhtml_editor_set_changed (editor, TRUE); + editor = e_msg_composer_get_editor (composer); + view = e_html_editor_get_view (editor); + e_html_editor_view_set_changed (view, TRUE); } static gboolean @@ -1892,6 +1994,12 @@ msg_composer_get_property (GObject *object, GParamSpec *pspec) { switch (property_id) { + case PROP_BUSY: + g_value_set_boolean ( + value, e_msg_composer_is_busy ( + E_MSG_COMPOSER (object))); + return; + case PROP_FOCUS_TRACKER: g_value_set_object ( value, e_msg_composer_get_focus_tracker ( @@ -1950,31 +2058,72 @@ msg_composer_gallery_drag_data_get (GtkIconView *icon_view, g_free (str_data); } +static void +composer_notify_activity_cb (EActivityBar *activity_bar, + GParamSpec *pspec, + EMsgComposer *composer) +{ + EHTMLEditor *editor; + EHTMLEditorView *view; + WebKitWebView *web_view; + gboolean editable; + gboolean busy; + + busy = (e_activity_bar_get_activity (activity_bar) != NULL); + + if (busy == composer->priv->busy) + return; + + composer->priv->busy = busy; + + if (busy) + e_msg_composer_save_focused_widget (composer); + + editor = e_msg_composer_get_editor (composer); + view = e_html_editor_get_view (editor); + web_view = WEBKIT_WEB_VIEW (view); + + if (busy) { + editable = webkit_web_view_get_editable (web_view); + webkit_web_view_set_editable (web_view, FALSE); + composer->priv->saved_editable = editable; + } else { + editable = composer->priv->saved_editable; + webkit_web_view_set_editable (web_view, editable); + } + + g_object_notify (G_OBJECT (composer), "busy"); + + if (!busy) + e_msg_composer_restore_focus_on_composer (composer); +} + static void msg_composer_constructed (GObject *object) { EShell *shell; - GtkhtmlEditor *editor; EMsgComposer *composer; + EActivityBar *activity_bar; EAttachmentView *view; EAttachmentStore *store; EComposerHeaderTable *table; - EWebViewGtkHTML *web_view; + EHTMLEditor *editor; + EHTMLEditorView *html_editor_view; GtkUIManager *ui_manager; GtkToggleAction *action; GSettings *settings; const gchar *id; gboolean active; - editor = GTKHTML_EDITOR (object); composer = E_MSG_COMPOSER (object); shell = e_msg_composer_get_shell (composer); e_composer_private_constructed (composer); - web_view = e_msg_composer_get_web_view (composer); - ui_manager = gtkhtml_editor_get_ui_manager (editor); + editor = e_msg_composer_get_editor (composer); + html_editor_view = e_html_editor_get_view (editor); + ui_manager = e_html_editor_get_ui_manager (editor); view = e_msg_composer_get_attachment_view (composer); table = E_COMPOSER_HEADER_TABLE (composer->priv->header_table); @@ -2004,6 +2153,12 @@ msg_composer_constructed (GObject *object) "/org/gnome/evolution/mail/composer-window/", E_RESTORE_WINDOW_SIZE); + activity_bar = e_html_editor_get_activity_bar (editor); + g_signal_connect ( + activity_bar, "notify::activity", + G_CALLBACK (composer_notify_activity_cb), composer); + + /* Honor User Preferences */ /* FIXME This should be an EMsgComposer property. */ @@ -2016,21 +2171,21 @@ msg_composer_constructed (GObject *object) /* Clipboard Support */ g_signal_connect ( - web_view, "paste-clipboard", + html_editor_view, "paste-clipboard", G_CALLBACK (msg_composer_paste_clipboard_cb), composer); - /* Drag-and-Drop Support */ - g_signal_connect ( - web_view, "realize", - G_CALLBACK (msg_composer_realize_gtkhtml_cb), composer); + html_editor_view, "paste-primary-clipboard", + G_CALLBACK (msg_composer_paste_primary_clipboard_cb), composer); + + /* Drag-and-Drop Support */ g_signal_connect ( - web_view, "drag-motion", + html_editor_view, "drag-motion", G_CALLBACK (msg_composer_drag_motion_cb), composer); g_signal_connect ( - web_view, "drag-data-received", + html_editor_view, "drag-data-received", G_CALLBACK (msg_composer_drag_data_received_cb), composer); g_signal_connect ( @@ -2079,7 +2234,13 @@ msg_composer_constructed (GObject *object) G_CALLBACK (attachment_store_changed_cb), composer); /* Initialization may have tripped the "changed" state. */ - gtkhtml_editor_set_changed (editor, FALSE); + e_html_editor_view_set_changed (html_editor_view, FALSE); + + gtk_drag_dest_set ( + GTK_WIDGET (html_editor_view), + GTK_DEST_DEFAULT_HIGHLIGHT | GTK_DEST_DEFAULT_DROP, + drag_dest_targets, G_N_ELEMENTS (drag_dest_targets), + GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK); id = "org.gnome.evolution.composer"; e_plugin_ui_register_manager (ui_manager, id, composer); @@ -2087,6 +2248,7 @@ msg_composer_constructed (GObject *object) e_extensible_load_extensions (E_EXTENSIBLE (composer)); + e_msg_composer_set_body_text (composer, "", TRUE); /* Chain up to parent's constructed() method. */ G_OBJECT_CLASS (e_msg_composer_parent_class)->constructed (object); } @@ -2119,14 +2281,19 @@ msg_composer_dispose (GObject *object) static void msg_composer_map (GtkWidget *widget) { + EMsgComposer *composer; EComposerHeaderTable *table; GtkWidget *input_widget; + EHTMLEditor *editor; + EHTMLEditorView *view; const gchar *text; /* Chain up to parent's map() method. */ GTK_WIDGET_CLASS (e_msg_composer_parent_class)->map (widget); - table = e_msg_composer_get_header_table (E_MSG_COMPOSER (widget)); + composer = E_MSG_COMPOSER (widget); + editor = e_msg_composer_get_editor (composer); + table = e_msg_composer_get_header_table (composer); /* If the 'To' field is empty, focus it. */ input_widget = @@ -2149,7 +2316,8 @@ msg_composer_map (GtkWidget *widget) } /* Jump to the editor as a last resort. */ - gtkhtml_editor_run_command (GTKHTML_EDITOR (widget), "grab-focus"); + view = e_html_editor_get_view (editor); + gtk_widget_grab_focus (GTK_WIDGET (view)); } static gboolean @@ -2158,12 +2326,12 @@ msg_composer_key_press_event (GtkWidget *widget, { EMsgComposer *composer; GtkWidget *input_widget; - GtkhtmlEditor *editor; - EWebViewGtkHTML *web_view; + EHTMLEditor *editor; + EHTMLEditorView *view; - editor = GTKHTML_EDITOR (widget); composer = E_MSG_COMPOSER (widget); - web_view = e_msg_composer_get_web_view (composer); + editor = e_msg_composer_get_editor (composer); + view = e_html_editor_get_view (editor); input_widget = e_composer_header_table_get_header ( @@ -2183,12 +2351,12 @@ msg_composer_key_press_event (GtkWidget *widget, } if (event->keyval == GDK_KEY_Tab && gtk_widget_is_focus (input_widget)) { - gtkhtml_editor_run_command (editor, "grab-focus"); + gtk_widget_grab_focus (GTK_WIDGET (view)); return TRUE; } if (event->keyval == GDK_KEY_ISO_Left_Tab && - gtk_widget_is_focus (GTK_WIDGET (web_view))) { + gtk_widget_is_focus (GTK_WIDGET (view))) { gtk_widget_grab_focus (input_widget); return TRUE; } @@ -2198,160 +2366,6 @@ msg_composer_key_press_event (GtkWidget *widget, key_press_event (widget, event); } -static void -msg_composer_cut_clipboard (GtkhtmlEditor *editor) -{ - /* Do nothing. EFocusTracker handles this. */ -} - -static void -msg_composer_copy_clipboard (GtkhtmlEditor *editor) -{ - /* Do nothing. EFocusTracker handles this. */ -} - -static void -msg_composer_paste_clipboard (GtkhtmlEditor *editor) -{ - /* Do nothing. EFocusTracker handles this. */ -} - -static void -msg_composer_select_all (GtkhtmlEditor *editor) -{ - /* Do nothing. EFocusTracker handles this. */ -} - -static void -msg_composer_command_before (GtkhtmlEditor *editor, - const gchar *command) -{ - EMsgComposer *composer; - const gchar *data; - - composer = E_MSG_COMPOSER (editor); - - if (strcmp (command, "insert-paragraph") != 0) - return; - - if (composer->priv->in_signature_insert) - return; - - data = gtkhtml_editor_get_paragraph_data (editor, "orig"); - if (data != NULL && *data == '1') { - gtkhtml_editor_run_command (editor, "text-default-color"); - gtkhtml_editor_run_command (editor, "italic-off"); - return; - }; - - data = gtkhtml_editor_get_paragraph_data (editor, "signature"); - if (data != NULL && *data == '1') { - gtkhtml_editor_run_command (editor, "text-default-color"); - gtkhtml_editor_run_command (editor, "italic-off"); - } -} - -static void -msg_composer_command_after (GtkhtmlEditor *editor, - const gchar *command) -{ - EMsgComposer *composer; - const gchar *data; - - composer = E_MSG_COMPOSER (editor); - - if (strcmp (command, "insert-paragraph") != 0) - return; - - if (composer->priv->in_signature_insert) - return; - - gtkhtml_editor_run_command (editor, "italic-off"); - - data = gtkhtml_editor_get_paragraph_data (editor, "orig"); - if (data != NULL && *data == '1') - e_msg_composer_reply_indent (composer); - gtkhtml_editor_set_paragraph_data (editor, "orig", "0"); - - data = gtkhtml_editor_get_paragraph_data (editor, "signature"); - if (data == NULL || *data != '1') - return; - - /* Clear the signature. */ - if (gtkhtml_editor_is_paragraph_empty (editor)) - gtkhtml_editor_set_paragraph_data (editor, "signature" ,"0"); - - else if (gtkhtml_editor_is_previous_paragraph_empty (editor) && - gtkhtml_editor_run_command (editor, "cursor-backward")) { - - gtkhtml_editor_set_paragraph_data (editor, "signature", "0"); - gtkhtml_editor_run_command (editor, "cursor-forward"); - } - - gtkhtml_editor_run_command (editor, "text-default-color"); - gtkhtml_editor_run_command (editor, "italic-off"); -} - -static gchar * -msg_composer_image_uri (GtkhtmlEditor *editor, - const gchar *uri) -{ - EMsgComposer *composer; - GHashTable *hash_table; - CamelMimePart *part; - const gchar *cid; - - composer = E_MSG_COMPOSER (editor); - - hash_table = composer->priv->inline_images_by_url; - part = g_hash_table_lookup (hash_table, uri); - - if (part == NULL && g_str_has_prefix (uri, "file:")) - part = e_msg_composer_add_inline_image_from_file ( - composer, uri + 5); - - if (part == NULL && g_str_has_prefix (uri, "cid:")) { - hash_table = composer->priv->inline_images; - part = g_hash_table_lookup (hash_table, uri); - } - - if (part == NULL) - return NULL; - - composer->priv->current_images = - g_list_prepend (composer->priv->current_images, part); - - cid = camel_mime_part_get_content_id (part); - if (cid == NULL) - return NULL; - - return g_strconcat ("cid:", cid, NULL); -} - -static void -msg_composer_object_deleted (GtkhtmlEditor *editor) -{ - const gchar *data; - - if (!gtkhtml_editor_is_paragraph_empty (editor)) - return; - - data = gtkhtml_editor_get_paragraph_data (editor, "orig"); - if (data != NULL && *data == '1') { - gtkhtml_editor_set_paragraph_data (editor, "orig", "0"); - gtkhtml_editor_run_command (editor, "indent-zero"); - gtkhtml_editor_run_command (editor, "style-normal"); - gtkhtml_editor_run_command (editor, "text-default-color"); - gtkhtml_editor_run_command (editor, "italic-off"); - gtkhtml_editor_run_command (editor, "insert-paragraph"); - gtkhtml_editor_run_command (editor, "delete-back"); - } - - data = gtkhtml_editor_get_paragraph_data (editor, "signature"); - if (data != NULL && *data == '1') - gtkhtml_editor_set_paragraph_data (editor, "signature", "0"); -} - static gboolean msg_composer_presend (EMsgComposer *composer) { @@ -2359,34 +2373,6 @@ msg_composer_presend (EMsgComposer *composer) return TRUE; } -static void -msg_composer_submit_alert (EAlertSink *alert_sink, - EAlert *alert) -{ - EMsgComposerPrivate *priv; - EAlertBar *alert_bar; - GtkWidget *dialog; - GtkWindow *parent; - - priv = E_MSG_COMPOSER_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 gboolean msg_composer_accumulator_false_abort (GSignalInvocationHint *ihint, GValue *return_accu, @@ -2402,12 +2388,27 @@ msg_composer_accumulator_false_abort (GSignalInvocationHint *ihint, return v_boolean; } +/** + * e_msg_composer_is_busy: + * @composer: an #EMsgComposer + * + * Returns %TRUE only while an #EActivity is in progress. + * + * Returns: whether @composer is busy + **/ +gboolean +e_msg_composer_is_busy (EMsgComposer *composer) +{ + g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), FALSE); + + return composer->priv->busy; +} + static void e_msg_composer_class_init (EMsgComposerClass *class) { GObjectClass *object_class; GtkWidgetClass *widget_class; - GtkhtmlEditorClass *editor_class; g_type_class_add_private (class, sizeof (EMsgComposerPrivate)); @@ -2422,19 +2423,19 @@ e_msg_composer_class_init (EMsgComposerClass *class) widget_class->map = msg_composer_map; widget_class->key_press_event = msg_composer_key_press_event; - editor_class = GTKHTML_EDITOR_CLASS (class); - editor_class->cut_clipboard = msg_composer_cut_clipboard; - editor_class->copy_clipboard = msg_composer_copy_clipboard; - editor_class->paste_clipboard = msg_composer_paste_clipboard; - editor_class->select_all = msg_composer_select_all; - editor_class->command_before = msg_composer_command_before; - editor_class->command_after = msg_composer_command_after; - editor_class->image_uri = msg_composer_image_uri; - editor_class->link_clicked = NULL; /* EWebView handles this */ - editor_class->object_deleted = msg_composer_object_deleted; - class->presend = msg_composer_presend; + g_object_class_install_property ( + object_class, + PROP_BUSY, + g_param_spec_boolean ( + "busy", + "Busy", + "Whether an activity is in progress", + FALSE, + G_PARAM_READABLE | + G_PARAM_STATIC_STRINGS)); + g_object_class_install_property ( object_class, PROP_FOCUS_TRACKER, @@ -2511,16 +2512,12 @@ e_msg_composer_class_init (EMsgComposerClass *class) E_TYPE_ACTIVITY); } -static void -e_msg_composer_alert_sink_init (EAlertSinkInterface *iface) -{ - iface->submit_alert = msg_composer_submit_alert; -} - static void e_msg_composer_init (EMsgComposer *composer) { composer->priv = E_MSG_COMPOSER_GET_PRIVATE (composer); + + composer->priv->editor = g_object_ref_sink (e_html_editor_new ()); } /** @@ -2538,7 +2535,23 @@ e_msg_composer_new (EShell *shell) return g_object_new ( E_TYPE_MSG_COMPOSER, - "html", e_web_view_gtkhtml_new (), "shell", shell, NULL); + "shell", shell, NULL); +} + +/** + * e_msg_composer_get_editor: + * @composer: an #EMsgComposer + * + * Returns @composer's internal #EHTMLEditor instance. + * + * Returns: an #EHTMLEditor + **/ +EHTMLEditor * +e_msg_composer_get_editor (EMsgComposer *composer) +{ + g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), NULL); + + return composer->priv->editor; } EFocusTracker * @@ -2581,12 +2594,16 @@ add_attachments_handle_mime_part (EMsgComposer *composer, { CamelContentType *content_type; CamelDataWrapper *wrapper; + EHTMLEditor *editor; + EHTMLEditorView *view; if (!mime_part) return; content_type = camel_mime_part_get_content_type (mime_part); wrapper = camel_medium_get_content (CAMEL_MEDIUM (mime_part)); + editor = e_msg_composer_get_editor (composer); + view = e_html_editor_get_view (editor); if (CAMEL_IS_MULTIPART (wrapper)) { /* another layer of multipartness... */ @@ -2596,10 +2613,10 @@ add_attachments_handle_mime_part (EMsgComposer *composer, } else if (just_inlines) { if (camel_mime_part_get_content_id (mime_part) || camel_mime_part_get_content_location (mime_part)) - e_msg_composer_add_inline_image_from_mime_part ( - composer, mime_part); + e_html_editor_view_add_inline_image_from_mime_part ( + view, mime_part); } else if (related && camel_content_type_is (content_type, "image", "*")) { - e_msg_composer_add_inline_image_from_mime_part (composer, mime_part); + e_html_editor_view_add_inline_image_from_mime_part (view, mime_part); } else if (camel_content_type_is (content_type, "text", "*") && camel_mime_part_get_filename (mime_part) == NULL) { /* Do nothing if this is a text/anything without a @@ -2988,16 +3005,40 @@ handle_multipart (EMsgComposer *composer, /* Since the first part is not multipart/alternative, * this must be the body. */ - html = emcu_part_to_html ( - composer, mime_part, &length, keep_signature, cancellable); - if (html) - e_msg_composer_set_pending_body (composer, html, length); + + /* If we are opening message from Drafts */ + if (composer->priv->is_from_draft) { + /* Extract the body */ + CamelDataWrapper *dw; + + dw = camel_medium_get_content ((CamelMedium *) mime_part); + if (dw) { + CamelStream *mem = camel_stream_mem_new (); + GByteArray *bytes; + + camel_data_wrapper_decode_to_stream_sync (dw, mem, cancellable, NULL); + camel_stream_close (mem, cancellable, NULL); + + bytes = camel_stream_mem_get_byte_array (CAMEL_STREAM_MEM (mem)); + if (bytes && bytes->len) + html = g_strndup ((const gchar *) bytes->data, bytes->len); + + g_object_unref (mem); + } + } else { + html = emcu_part_to_html ( + composer, mime_part, &length, keep_signature, cancellable); + } + e_msg_composer_set_pending_body (composer, html, length); } else if (camel_mime_part_get_content_id (mime_part) || camel_mime_part_get_content_location (mime_part)) { /* special in-line attachment */ - e_msg_composer_add_inline_image_from_mime_part ( - composer, mime_part); + EHTMLEditor *editor; + + editor = e_msg_composer_get_editor (composer); + e_html_editor_view_add_inline_image_from_mime_part ( + e_html_editor_get_view (editor), mime_part); } else { /* normal attachment */ @@ -3009,28 +3050,46 @@ handle_multipart (EMsgComposer *composer, static void set_signature_gui (EMsgComposer *composer) { - GtkhtmlEditor *editor; + EHTMLEditor *editor; + EHTMLEditorView *view; + WebKitDOMDocument *document; + WebKitDOMNodeList *nodes; EComposerHeaderTable *table; EMailSignatureComboBox *combo_box; - const gchar *data; gchar *uid; + gulong ii, length; - editor = GTKHTML_EDITOR (composer); table = e_msg_composer_get_header_table (composer); combo_box = e_composer_header_table_get_signature_combo_box (table); - if (!gtkhtml_editor_search_by_data (editor, 1, "ClueFlow", "signature", "1")) - return; - - data = gtkhtml_editor_get_paragraph_data (editor, "signature_name"); + editor = e_msg_composer_get_editor (composer); + view = e_html_editor_get_view (editor); + document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view)); - if (!g_str_has_prefix (data, "uid:")) - return; + uid = NULL; + nodes = webkit_dom_document_get_elements_by_class_name ( + document, "-x-evo-signature"); + length = webkit_dom_node_list_get_length (nodes); + for (ii = 0; ii < length; ii++) { + WebKitDOMNode *node; + gchar *id; + + node = webkit_dom_node_list_item (nodes, ii); + id = webkit_dom_element_get_id (WEBKIT_DOM_ELEMENT (node)); + if (id && (strlen (id) == 1) && (*id == '1')) { + uid = webkit_dom_element_get_attribute ( + WEBKIT_DOM_ELEMENT (node), "name"); + g_free (id); + break; + } + g_free (id); + } /* The combo box active ID is the signature's ESource UID. */ - uid = e_composer_decode_clue_value (data + 4); - gtk_combo_box_set_active_id (GTK_COMBO_BOX (combo_box), uid); - g_free (uid); + if (uid != NULL) { + gtk_combo_box_set_active_id (GTK_COMBO_BOX (combo_box), uid); + g_free (uid); + } } static void @@ -3100,7 +3159,7 @@ e_msg_composer_new_with_message (EShell *shell, { CamelInternetAddress *to, *cc, *bcc; GList *To = NULL, *Cc = NULL, *Bcc = NULL, *postto = NULL; - const gchar *format, *subject; + const gchar *format, *subject, *composer_mode; EDestination **Tov, **Ccv, **Bccv; GHashTable *auto_cc, *auto_bcc; CamelContentType *content_type; @@ -3110,6 +3169,8 @@ e_msg_composer_new_with_message (EShell *shell, EMsgComposerPrivate *priv; EComposerHeaderTable *table; ESource *source = NULL; + EHTMLEditor *editor; + EHTMLEditorView *view; GtkToggleAction *action; struct _camel_header_raw *xev; gchar *identity_uid; @@ -3131,7 +3192,9 @@ e_msg_composer_new_with_message (EShell *shell, composer = e_msg_composer_new (shell); priv = E_MSG_COMPOSER_GET_PRIVATE (composer); + editor = e_msg_composer_get_editor (composer); table = e_msg_composer_get_header_table (composer); + view = e_html_editor_get_view (editor); if (postto) { e_composer_header_table_set_post_to_list (table, postto); @@ -3250,6 +3313,13 @@ e_msg_composer_new_with_message (EShell *shell, /* Restore the format editing preference */ format = camel_medium_get_header ( CAMEL_MEDIUM (message), "X-Evolution-Format"); + + composer_mode = camel_medium_get_header ( + CAMEL_MEDIUM (message), "X-Evolution-Composer-Mode"); + + if (composer_mode && *composer_mode) + composer->priv->is_from_draft = TRUE; + if (format != NULL) { gchar **flags; @@ -3259,11 +3329,21 @@ e_msg_composer_new_with_message (EShell *shell, flags = g_strsplit (format, ", ", 0); for (i = 0; flags[i]; i++) { if (g_ascii_strcasecmp (flags[i], "text/html") == 0) { - gtkhtml_editor_set_html_mode ( - GTKHTML_EDITOR (composer), TRUE); + if (g_ascii_strcasecmp (composer_mode, "text/html") == 0) { + e_html_editor_view_set_html_mode ( + view, TRUE); + } else { + e_html_editor_view_set_html_mode ( + view, FALSE); + } } else if (g_ascii_strcasecmp (flags[i], "text/plain") == 0) { - gtkhtml_editor_set_html_mode ( - GTKHTML_EDITOR (composer), FALSE); + if (g_ascii_strcasecmp (composer_mode, "text/html") == 0) { + e_html_editor_view_set_html_mode ( + view, TRUE); + } else { + e_html_editor_view_set_html_mode ( + view, FALSE); + } } else if (g_ascii_strcasecmp (flags[i], "pgp-sign") == 0) { action = GTK_TOGGLE_ACTION (ACTION (PGP_SIGN)); gtk_toggle_action_set_active (action, TRUE); @@ -3367,14 +3447,35 @@ e_msg_composer_new_with_message (EShell *shell, ACTION (SMIME_ENCRYPT)), TRUE); } - html = emcu_part_to_html ( - composer, CAMEL_MIME_PART (message), - &length, keep_signature, cancellable); - if (html) - e_msg_composer_set_pending_body (composer, html, length); + /* If we are opening message from Drafts */ + if (composer->priv->is_from_draft) { + /* Extract the body */ + CamelDataWrapper *dw; + + dw = camel_medium_get_content ((CamelMedium *) mime_part); + if (dw) { + CamelStream *mem = camel_stream_mem_new (); + GByteArray *bytes; + + camel_data_wrapper_decode_to_stream_sync (dw, mem, cancellable, NULL); + camel_stream_close (mem, cancellable, NULL); + + bytes = camel_stream_mem_get_byte_array (CAMEL_STREAM_MEM (mem)); + if (bytes && bytes->len) + html = g_strndup ((const gchar *) bytes->data, bytes->len); + + g_object_unref (mem); + } + } else { + html = emcu_part_to_html ( + composer, CAMEL_MIME_PART (message), + &length, keep_signature, cancellable); + } + e_msg_composer_set_pending_body (composer, html, length); } priv->is_from_message = TRUE; + priv->set_signature_from_message = TRUE; /* We wait until now to set the body text because we need to * ensure that the attachment bar has all the attachments before @@ -3403,7 +3504,8 @@ e_msg_composer_new_redirect (EShell *shell, { EMsgComposer *composer; EComposerHeaderTable *table; - EWebViewGtkHTML *web_view; + EHTMLEditor *editor; + EHTMLEditorView *view; const gchar *subject; g_return_val_if_fail (E_IS_SHELL (shell), NULL); @@ -3421,8 +3523,9 @@ e_msg_composer_new_redirect (EShell *shell, e_composer_header_table_set_identity_uid (table, identity_uid); e_composer_header_table_set_subject (table, subject); - web_view = e_msg_composer_get_web_view (composer); - e_web_view_gtkhtml_set_editable (web_view, FALSE); + editor = e_msg_composer_get_editor (composer); + view = e_html_editor_get_view (editor); + webkit_web_view_set_editable (WEBKIT_WEB_VIEW (view), FALSE); return composer; } @@ -3473,30 +3576,6 @@ e_msg_composer_get_shell (EMsgComposer *composer) return E_SHELL (composer->priv->shell); } -/** - * e_msg_composer_get_web_view: - * @composer: an #EMsgComposer - * - * Returns the #EWebView widget in @composer. - * - * Returns: the #EWebView - **/ -EWebViewGtkHTML * -e_msg_composer_get_web_view (EMsgComposer *composer) -{ - GtkHTML *html; - GtkhtmlEditor *editor; - - g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), NULL); - - /* This is a convenience function to avoid - * repeating this awkwardness everywhere */ - editor = GTKHTML_EDITOR (composer); - html = gtkhtml_editor_get_html (editor); - - return E_WEB_VIEW_GTKHTML (html); -} - static void msg_composer_send_cb (EMsgComposer *composer, GAsyncResult *result, @@ -3504,7 +3583,8 @@ msg_composer_send_cb (EMsgComposer *composer, { CamelMimeMessage *message; EAlertSink *alert_sink; - GtkhtmlEditor *editor; + EHTMLEditor *editor; + EHTMLEditorView *view; GError *error = NULL; alert_sink = e_activity_get_alert_sink (context->activity); @@ -3536,8 +3616,9 @@ msg_composer_send_cb (EMsgComposer *composer, g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message)); /* The callback can set editor 'changed' if anything failed. */ - editor = GTKHTML_EDITOR (composer); - gtkhtml_editor_set_changed (editor, FALSE); + editor = e_msg_composer_get_editor (composer); + view = e_html_editor_get_view (editor); + e_html_editor_view_set_changed (view, TRUE); g_signal_emit ( composer, signals[SEND], 0, @@ -3557,9 +3638,8 @@ msg_composer_send_cb (EMsgComposer *composer, void e_msg_composer_send (EMsgComposer *composer) { + EHTMLEditor *editor; AsyncContext *context; - EAlertSink *alert_sink; - EActivityBar *activity_bar; GCancellable *cancellable; gboolean proceed_with_send = TRUE; @@ -3573,18 +3653,12 @@ e_msg_composer_send (EMsgComposer *composer) return; } - context = g_slice_new0 (AsyncContext); - context->activity = e_composer_activity_new (composer); - - alert_sink = E_ALERT_SINK (composer); - e_activity_set_alert_sink (context->activity, alert_sink); + editor = e_msg_composer_get_editor (composer); - cancellable = camel_operation_new (); - e_activity_set_cancellable (context->activity, cancellable); - g_object_unref (cancellable); + context = g_slice_new0 (AsyncContext); + context->activity = e_html_editor_new_activity (editor); - activity_bar = E_ACTIVITY_BAR (composer->priv->activity_bar); - e_activity_bar_set_activity (activity_bar, context->activity); + cancellable = e_activity_get_cancellable (context->activity); e_msg_composer_get_message ( composer, G_PRIORITY_DEFAULT, cancellable, @@ -3599,7 +3673,8 @@ msg_composer_save_to_drafts_cb (EMsgComposer *composer, { CamelMimeMessage *message; EAlertSink *alert_sink; - GtkhtmlEditor *editor; + EHTMLEditor *editor; + EHTMLEditorView *view; GError *error = NULL; alert_sink = e_activity_get_alert_sink (context->activity); @@ -3640,8 +3715,9 @@ msg_composer_save_to_drafts_cb (EMsgComposer *composer, g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message)); /* The callback can set editor 'changed' if anything failed. */ - editor = GTKHTML_EDITOR (composer); - gtkhtml_editor_set_changed (editor, FALSE); + editor = e_msg_composer_get_editor (composer); + view = e_html_editor_get_view (editor); + e_html_editor_view_set_changed (view, FALSE); g_signal_emit ( composer, signals[SAVE_TO_DRAFTS], @@ -3666,25 +3742,18 @@ msg_composer_save_to_drafts_cb (EMsgComposer *composer, void e_msg_composer_save_to_drafts (EMsgComposer *composer) { + EHTMLEditor *editor; AsyncContext *context; - EAlertSink *alert_sink; - EActivityBar *activity_bar; GCancellable *cancellable; g_return_if_fail (E_IS_MSG_COMPOSER (composer)); - context = g_slice_new0 (AsyncContext); - context->activity = e_composer_activity_new (composer); - - alert_sink = E_ALERT_SINK (composer); - e_activity_set_alert_sink (context->activity, alert_sink); + editor = e_msg_composer_get_editor (composer); - cancellable = camel_operation_new (); - e_activity_set_cancellable (context->activity, cancellable); - g_object_unref (cancellable); + context = g_slice_new0 (AsyncContext); + context->activity = e_html_editor_new_activity (editor); - activity_bar = E_ACTIVITY_BAR (composer->priv->activity_bar); - e_activity_bar_set_activity (activity_bar, context->activity); + cancellable = e_activity_get_cancellable (context->activity); e_msg_composer_get_message_draft ( composer, G_PRIORITY_DEFAULT, cancellable, @@ -3699,7 +3768,8 @@ msg_composer_save_to_outbox_cb (EMsgComposer *composer, { CamelMimeMessage *message; EAlertSink *alert_sink; - GtkhtmlEditor *editor; + EHTMLEditor *editor; + EHTMLEditorView *view; GError *error = NULL; alert_sink = e_activity_get_alert_sink (context->activity); @@ -3734,9 +3804,9 @@ msg_composer_save_to_outbox_cb (EMsgComposer *composer, async_context_free (context); - /* XXX This should be elsewhere. */ - editor = GTKHTML_EDITOR (composer); - gtkhtml_editor_set_changed (editor, FALSE); + editor = e_msg_composer_get_editor (composer); + view = e_html_editor_get_view (editor); + e_html_editor_view_set_changed (view, FALSE); } /** @@ -3748,9 +3818,8 @@ msg_composer_save_to_outbox_cb (EMsgComposer *composer, void e_msg_composer_save_to_outbox (EMsgComposer *composer) { + EHTMLEditor *editor; AsyncContext *context; - EAlertSink *alert_sink; - EActivityBar *activity_bar; GCancellable *cancellable; gboolean proceed_with_save = TRUE; @@ -3762,18 +3831,12 @@ e_msg_composer_save_to_outbox (EMsgComposer *composer) if (!proceed_with_save) return; - context = g_slice_new0 (AsyncContext); - context->activity = e_composer_activity_new (composer); + editor = e_msg_composer_get_editor (composer); - alert_sink = E_ALERT_SINK (composer); - e_activity_set_alert_sink (context->activity, alert_sink); - - cancellable = camel_operation_new (); - e_activity_set_cancellable (context->activity, cancellable); - g_object_unref (cancellable); + context = g_slice_new0 (AsyncContext); + context->activity = e_html_editor_new_activity (editor); - activity_bar = E_ACTIVITY_BAR (composer->priv->activity_bar); - e_activity_bar_set_activity (activity_bar, context->activity); + cancellable = e_activity_get_cancellable (context->activity); e_msg_composer_get_message ( composer, G_PRIORITY_DEFAULT, cancellable, @@ -3835,26 +3898,19 @@ void e_msg_composer_print (EMsgComposer *composer, GtkPrintOperationAction print_action) { + EHTMLEditor *editor; AsyncContext *context; - EAlertSink *alert_sink; - EActivityBar *activity_bar; GCancellable *cancellable; g_return_if_fail (E_IS_MSG_COMPOSER (composer)); + editor = e_msg_composer_get_editor (composer); + context = g_slice_new0 (AsyncContext); - context->activity = e_composer_activity_new (composer); + context->activity = e_html_editor_new_activity (editor); context->print_action = print_action; - alert_sink = E_ALERT_SINK (composer); - e_activity_set_alert_sink (context->activity, alert_sink); - - cancellable = camel_operation_new (); - e_activity_set_cancellable (context->activity, cancellable); - g_object_unref (cancellable); - - activity_bar = E_ACTIVITY_BAR (composer->priv->activity_bar); - e_activity_bar_set_activity (activity_bar, context->activity); + cancellable = e_activity_get_cancellable (context->activity); e_msg_composer_get_message_print ( composer, G_PRIORITY_DEFAULT, cancellable, @@ -4226,15 +4282,21 @@ e_msg_composer_set_body (EMsgComposer *composer, { EMsgComposerPrivate *priv = composer->priv; EComposerHeaderTable *table; - EWebViewGtkHTML *web_view; + EHTMLEditor *editor; + EHTMLEditorView *view; ESource *source; const gchar *identity_uid; gchar *buff; g_return_if_fail (E_IS_MSG_COMPOSER (composer)); + editor = e_msg_composer_get_editor (composer); + view = e_html_editor_get_view (editor); table = e_msg_composer_get_header_table (composer); + /* Disable signature */ + priv->disable_signature = TRUE; + identity_uid = e_composer_header_table_get_identity_uid (table); source = e_composer_header_table_ref_source (table, identity_uid); @@ -4245,10 +4307,8 @@ e_msg_composer_set_body (EMsgComposer *composer, set_editor_text (composer, buff, FALSE); g_free (buff); - gtkhtml_editor_set_html_mode (GTKHTML_EDITOR (composer), FALSE); - - web_view = e_msg_composer_get_web_view (composer); - e_web_view_gtkhtml_set_editable (web_view, FALSE); + e_html_editor_view_set_html_mode (view, FALSE); + webkit_web_view_set_editable (WEBKIT_WEB_VIEW (view), FALSE); g_free (priv->mime_body); priv->mime_body = g_strdup (body); @@ -4459,109 +4519,12 @@ e_msg_composer_attach (EMsgComposer *composer, attachment = e_attachment_new (); e_attachment_set_mime_part (attachment, mime_part); e_attachment_store_add_attachment (store, attachment); - e_attachment_load (attachment, NULL); + e_attachment_load_async ( + attachment, (GAsyncReadyCallback) + e_attachment_load_handle_error, composer); g_object_unref (attachment); } -/** - * e_msg_composer_add_inline_image_from_file: - * @composer: a composer object - * @filename: the name of the file containing the image - * - * This reads in the image in @filename and adds it to @composer - * as an inline image, to be wrapped in a multipart/related. - * - * Returns: the newly-created CamelMimePart (which must be reffed - * if the caller wants to keep its own reference), or %NULL on error. - **/ -CamelMimePart * -e_msg_composer_add_inline_image_from_file (EMsgComposer *composer, - const gchar *filename) -{ - gchar *mime_type, *cid, *url, *name, *dec_file_name; - CamelStream *stream; - CamelDataWrapper *wrapper; - CamelMimePart *part; - EMsgComposerPrivate *p = composer->priv; - - dec_file_name = g_strdup (filename); - camel_url_decode (dec_file_name); - - if (!g_file_test (dec_file_name, G_FILE_TEST_IS_REGULAR)) - return NULL; - - stream = camel_stream_fs_new_with_name ( - dec_file_name, O_RDONLY, 0, NULL); - if (!stream) - return NULL; - - wrapper = camel_data_wrapper_new (); - camel_data_wrapper_construct_from_stream_sync ( - wrapper, stream, NULL, NULL); - g_object_unref (CAMEL_OBJECT (stream)); - - mime_type = e_util_guess_mime_type (dec_file_name, TRUE); - if (mime_type == NULL) - mime_type = g_strdup ("application/octet-stream"); - camel_data_wrapper_set_mime_type (wrapper, mime_type); - g_free (mime_type); - - part = camel_mime_part_new (); - camel_medium_set_content (CAMEL_MEDIUM (part), wrapper); - g_object_unref (wrapper); - - cid = camel_header_msgid_generate (); - camel_mime_part_set_content_id (part, cid); - name = g_path_get_basename (dec_file_name); - camel_mime_part_set_filename (part, name); - g_free (name); - camel_mime_part_set_encoding (part, CAMEL_TRANSFER_ENCODING_BASE64); - - url = g_strdup_printf ("file:%s", dec_file_name); - g_hash_table_insert (p->inline_images_by_url, url, part); - - url = g_strdup_printf ("cid:%s", cid); - g_hash_table_insert (p->inline_images, url, part); - g_free (cid); - - g_free (dec_file_name); - - return part; -} - -/** - * e_msg_composer_add_inline_image_from_mime_part: - * @composer: a composer object - * @part: a CamelMimePart containing image data - * - * This adds the mime part @part to @composer as an inline image, to - * be wrapped in a multipart/related. - **/ -void -e_msg_composer_add_inline_image_from_mime_part (EMsgComposer *composer, - CamelMimePart *part) -{ - gchar *url; - const gchar *location, *cid; - EMsgComposerPrivate *p = composer->priv; - - cid = camel_mime_part_get_content_id (part); - if (!cid) { - camel_mime_part_set_content_id (part, NULL); - cid = camel_mime_part_get_content_id (part); - } - - url = g_strdup_printf ("cid:%s", cid); - g_hash_table_insert (p->inline_images, url, part); - g_object_ref (part); - - location = camel_mime_part_get_content_location (part); - if (location != NULL) - g_hash_table_insert ( - p->inline_images_by_url, - g_strdup (location), part); -} - static void composer_get_message_ready (EMsgComposer *composer, GAsyncResult *result, @@ -4604,16 +4567,21 @@ e_msg_composer_get_message (EMsgComposer *composer, GSimpleAsyncResult *simple; GtkAction *action; ComposerFlags flags = 0; + EHTMLEditor *editor; + EHTMLEditorView *view; g_return_if_fail (E_IS_MSG_COMPOSER (composer)); + editor = e_msg_composer_get_editor (composer); + view = e_html_editor_get_view (editor); + simple = g_simple_async_result_new ( G_OBJECT (composer), callback, user_data, e_msg_composer_get_message); g_simple_async_result_set_check_cancellable (simple, cancellable); - if (gtkhtml_editor_get_html_mode (GTKHTML_EDITOR (composer))) + if (e_html_editor_view_get_html_mode (view)) flags |= COMPOSER_FLAG_HTML_CONTENT; action = ACTION (PRIORITIZE_MESSAGE); @@ -4730,8 +4698,10 @@ e_msg_composer_get_message_draft (EMsgComposer *composer, GAsyncReadyCallback callback, gpointer user_data) { + EHTMLEditor *editor; + EHTMLEditorView *view; GSimpleAsyncResult *simple; - ComposerFlags flags = COMPOSER_FLAG_DRAFT; + ComposerFlags flags = COMPOSER_FLAG_SAVE_DRAFT; GtkAction *action; g_return_if_fail (E_IS_MSG_COMPOSER (composer)); @@ -4742,8 +4712,13 @@ e_msg_composer_get_message_draft (EMsgComposer *composer, g_simple_async_result_set_check_cancellable (simple, cancellable); - if (gtkhtml_editor_get_html_mode (GTKHTML_EDITOR (composer))) - flags |= COMPOSER_FLAG_HTML_CONTENT; + editor = e_msg_composer_get_editor (composer); + view = e_html_editor_get_view (editor); + /* We need to remember composer mode */ + if (e_html_editor_view_get_html_mode (view)) + flags |= COMPOSER_FLAG_HTML_MODE; + /* We want to save HTML content everytime when we save as draft */ + flags |= COMPOSER_FLAG_SAVE_DRAFT; action = ACTION (PRIORITIZE_MESSAGE); if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) @@ -4872,17 +4847,19 @@ e_msg_composer_get_reply_to (EMsgComposer *composer) GByteArray * e_msg_composer_get_raw_message_text (EMsgComposer *composer) { - GtkhtmlEditor *editor; + EHTMLEditor *editor; + EHTMLEditorView *view; GByteArray *array; gchar *text; - gsize length; g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), NULL); + editor = e_msg_composer_get_editor (composer); + view = e_html_editor_get_view (editor); + array = g_byte_array_new (); - editor = GTKHTML_EDITOR (composer); - text = gtkhtml_editor_get_text_plain (editor, &length); - g_byte_array_append (array, (guint8 *) text, (guint) length); + text = e_html_editor_view_get_text_plain (view); + g_byte_array_append (array, (guint8 *) text, strlen (text)); g_free (text); return array; @@ -4915,22 +4892,24 @@ e_msg_composer_can_close (EMsgComposer *composer, gboolean can_save_draft) { gboolean res = FALSE; - GtkhtmlEditor *editor; + EHTMLEditor *editor; + EHTMLEditorView *view; EComposerHeaderTable *table; GdkWindow *window; GtkWidget *widget; const gchar *subject, *message_name; gint response; - editor = GTKHTML_EDITOR (composer); widget = GTK_WIDGET (composer); + editor = e_msg_composer_get_editor (composer); + view = e_html_editor_get_view (editor); /* this means that there is an async operation running, * in which case the composer cannot be closed */ if (!gtk_action_group_get_sensitive (composer->priv->async_actions)) return FALSE; - if (!gtkhtml_editor_get_changed (editor)) + if (!e_html_editor_view_get_changed (view)) return TRUE; window = gtk_widget_get_window (widget); @@ -4968,32 +4947,6 @@ e_msg_composer_can_close (EMsgComposer *composer, return res; } -void -e_msg_composer_reply_indent (EMsgComposer *composer) -{ - GtkhtmlEditor *editor; - - g_return_if_fail (E_IS_MSG_COMPOSER (composer)); - - editor = GTKHTML_EDITOR (composer); - - if (!gtkhtml_editor_is_paragraph_empty (editor)) { - if (gtkhtml_editor_is_previous_paragraph_empty (editor)) - gtkhtml_editor_run_command (editor, "cursor-backward"); - else { - gtkhtml_editor_run_command (editor, "text-default-color"); - gtkhtml_editor_run_command (editor, "italic-off"); - gtkhtml_editor_run_command (editor, "insert-paragraph"); - return; - } - } - - gtkhtml_editor_run_command (editor, "style-normal"); - gtkhtml_editor_run_command (editor, "indent-zero"); - gtkhtml_editor_run_command (editor, "text-default-color"); - gtkhtml_editor_run_command (editor, "italic-off"); -} - EComposerHeaderTable * e_msg_composer_get_header_table (EMsgComposer *composer) { @@ -5010,76 +4963,101 @@ e_msg_composer_get_attachment_view (EMsgComposer *composer) return E_ATTACHMENT_VIEW (composer->priv->attachment_paned); } -GList * -e_load_spell_languages (void) +void +e_save_spell_languages (const GList *spell_dicts) { GSettings *settings; - GList *spell_languages = NULL; - gchar **strv; - gint ii; + GPtrArray *lang_array; + + /* Build a list of spell check language codes. */ + lang_array = g_ptr_array_new (); + while (spell_dicts != NULL) { + ESpellDictionary *dict = spell_dicts->data; + const gchar *language_code; + + language_code = e_spell_dictionary_get_code (dict); + g_ptr_array_add (lang_array, (gpointer) language_code); + + spell_dicts = g_list_next (spell_dicts); + } + + g_ptr_array_add (lang_array, NULL); - /* Ask GSettings for a list of spell check language codes. */ + /* Save the language codes to GSettings. */ settings = g_settings_new ("org.gnome.evolution.mail"); - strv = g_settings_get_strv (settings, "composer-spell-languages"); + g_settings_set_strv ( + settings, "composer-spell-languages", + (const gchar * const *) lang_array->pdata); 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; + g_ptr_array_free (lang_array, TRUE); +} - language = gtkhtml_spell_language_lookup (language_code); - if (language != NULL) - spell_languages = g_list_prepend ( - spell_languages, (gpointer) language); - } +void +e_msg_composer_is_from_new_message (EMsgComposer *composer, + gboolean is_from_new_message) +{ + g_return_if_fail (composer != NULL); + + composer->priv->is_from_new_message = is_from_new_message; +} + +void +e_msg_composer_save_focused_widget (EMsgComposer *composer) +{ + GtkWidget *widget; - g_strfreev (strv); + g_return_if_fail (E_IS_MSG_COMPOSER (composer)); - spell_languages = g_list_reverse (spell_languages); + widget = gtk_window_get_focus (GTK_WINDOW (composer)); + composer->priv->focused_entry = widget; - /* Pick a default spell language if it came back empty. */ - if (spell_languages == NULL) { - const GtkhtmlSpellLanguage *language; + if (E_IS_HTML_EDITOR_VIEW (widget)) { + EHTMLEditorSelection *selection; - language = gtkhtml_spell_language_lookup (NULL); + selection = e_html_editor_view_get_selection ( + E_HTML_EDITOR_VIEW (widget)); - if (language) { - spell_languages = g_list_prepend ( - spell_languages, (gpointer) language); - } + e_html_editor_selection_save (selection); } - return spell_languages; + if (GTK_IS_EDITABLE (widget)) { + gtk_editable_get_selection_bounds ( + GTK_EDITABLE (widget), + &composer->priv->focused_entry_selection_start, + &composer->priv->focused_entry_selection_end); + } } void -e_save_spell_languages (GList *spell_languages) +e_msg_composer_restore_focus_on_composer (EMsgComposer *composer) { - GSettings *settings; - GPtrArray *lang_array; + GtkWidget *widget = composer->priv->focused_entry; - /* Build a list of spell check language codes. */ - lang_array = g_ptr_array_new (); - while (spell_languages != NULL) { - const GtkhtmlSpellLanguage *language; - const gchar *language_code; + g_return_if_fail (E_IS_MSG_COMPOSER (composer)); - language = spell_languages->data; - language_code = gtkhtml_spell_language_get_code (language); - g_ptr_array_add (lang_array, (gpointer) language_code); + if (!widget) + return; + + gtk_window_set_focus (GTK_WINDOW (composer), widget); - spell_languages = g_list_next (spell_languages); + if (GTK_IS_EDITABLE (widget)) { + gtk_editable_select_region ( + GTK_EDITABLE (widget), + composer->priv->focused_entry_selection_start, + composer->priv->focused_entry_selection_end); } - g_ptr_array_add (lang_array, NULL); + if (E_IS_HTML_EDITOR_VIEW (widget)) { + EHTMLEditorSelection *selection; - /* Save the language codes to GSettings. */ - settings = g_settings_new ("org.gnome.evolution.mail"); - g_settings_set_strv ( - settings, "composer-spell-languages", - (const gchar * const *) lang_array->pdata); - g_object_unref (settings); + e_html_editor_view_force_spell_check (E_HTML_EDITOR_VIEW (widget)); - g_ptr_array_free (lang_array, TRUE); + selection = e_html_editor_view_get_selection ( + E_HTML_EDITOR_VIEW (widget)); + + e_html_editor_selection_restore (selection); + } + + composer->priv->focused_entry = NULL; } diff --git a/composer/e-msg-composer.h b/composer/e-msg-composer.h index 1fbc1043fd..f6990415f7 100644 --- a/composer/e-msg-composer.h +++ b/composer/e-msg-composer.h @@ -24,7 +24,6 @@ #define E_MSG_COMPOSER_H #include -#include #include #include @@ -57,12 +56,12 @@ typedef struct _EMsgComposerClass EMsgComposerClass; typedef struct _EMsgComposerPrivate EMsgComposerPrivate; struct _EMsgComposer { - GtkhtmlEditor parent; + GtkWindow parent; EMsgComposerPrivate *priv; }; struct _EMsgComposerClass { - GtkhtmlEditorClass parent_class; + GtkWindowClass parent_class; /* Signals */ gboolean (*presend) (EMsgComposer *composer); @@ -93,12 +92,11 @@ EMsgComposer * e_msg_composer_new_redirect (EShell *shell, CamelMimeMessage *message, const gchar *identity_uid, GCancellable *cancellable); +EHTMLEditor * e_msg_composer_get_editor (EMsgComposer *composer); EFocusTracker * e_msg_composer_get_focus_tracker (EMsgComposer *composer); CamelSession * e_msg_composer_ref_session (EMsgComposer *composer); EShell * e_msg_composer_get_shell (EMsgComposer *composer); -EWebViewGtkHTML * - e_msg_composer_get_web_view (EMsgComposer *composer); void e_msg_composer_send (EMsgComposer *composer); void e_msg_composer_save_to_drafts (EMsgComposer *composer); @@ -131,12 +129,6 @@ void e_msg_composer_set_source_headers CamelMessageFlags flags); void e_msg_composer_attach (EMsgComposer *composer, CamelMimePart *mime_part); -CamelMimePart * e_msg_composer_add_inline_image_from_file - (EMsgComposer *composer, - const gchar *filename); -void e_msg_composer_add_inline_image_from_mime_part - (EMsgComposer *composer, - CamelMimePart *part); void e_msg_composer_get_message (EMsgComposer *composer, gint io_priority, GCancellable *cancellable, @@ -175,8 +167,6 @@ CamelInternetAddress * CamelInternetAddress * e_msg_composer_get_reply_to (EMsgComposer *composer); -void e_msg_composer_clear_inlined_table - (EMsgComposer *composer); void e_msg_composer_add_message_attachments (EMsgComposer *composer, CamelMimeMessage *message, @@ -186,8 +176,6 @@ void e_msg_composer_request_close (EMsgComposer *composer); gboolean e_msg_composer_can_close (EMsgComposer *composer, gboolean can_save_draft); -void e_msg_composer_reply_indent (EMsgComposer *composer); - EComposerHeaderTable * e_msg_composer_get_header_table (EMsgComposer *composer); EAttachmentView * @@ -198,9 +186,15 @@ GByteArray * e_msg_composer_get_raw_message_text gboolean e_msg_composer_is_exiting (EMsgComposer *composer); -GList * e_load_spell_languages (void); -void e_save_spell_languages (GList *spell_languages); - +void e_save_spell_languages (const GList *spell_languages); +void e_msg_composer_is_from_new_message + (EMsgComposer *composer, + gboolean is_from_new_message); +void e_msg_composer_save_focused_widget + (EMsgComposer *composer); +void e_msg_composer_restore_focus_on_composer + (EMsgComposer *composer); +gboolean e_msg_composer_is_busy (EMsgComposer *composer); G_END_DECLS #endif /* E_MSG_COMPOSER_H */ diff --git a/configure.ac b/configure.ac index b03c1e6215..fcd15e5944 100644 --- a/configure.ac +++ b/configure.ac @@ -48,9 +48,10 @@ m4_define([eds_minimum_version], [evo_version]) m4_define([gtkhtml_minimum_version], [4.5.2]) m4_define([gdk_pixbuf_minimum_version], [2.24.0]) m4_define([gcr_minimum_version], [3.4]) +m4_define([enchant_minimum_version], [1.1.7]) m4_define([gnome_desktop_minimum_version], [2.91.3]) m4_define([gsettings_desktop_schemas_minimum_version], [2.91.92]) -m4_define([webkitgtk_minimum_version], [2.0.1]) +m4_define([webkitgtk_minimum_version], [2.2.0]) m4_define([libgdata_minimum_version], [0.10]) m4_define([libxml_minimum_version], [2.7.3]) m4_define([shared_mime_info_minimum_version], [0.22]) @@ -286,6 +287,15 @@ PKG_CHECK_MODULES([EVOLUTION_DATA_SERVER], AC_SUBST(EVOLUTION_DATA_SERVER_CFLAGS) AC_SUBST(EVOLUTION_DATA_SERVER_LIBS) + +dnl **************** +dnl Enchant Library +dnl **************** +PKG_CHECK_MODULES([ENCHANT], + [enchant >= enchant_minimum_version]) +AC_SUBST(ENCHANT_CFLAGS) +AC_SUBST(ENCHANT_LIBS) + dnl ****************************** dnl Canberra / Canberra-GTK Sound dnl ****************************** @@ -481,6 +491,42 @@ fi AC_CHECK_FUNCS(mkdtemp) +dnl ************************************************** +dnl iso-codes +dnl ************************************************** +AC_MSG_CHECKING([for iso-codes package]) +have_iso_codes=no +if $PKG_CONFIG --exists iso-codes; then + if $PKG_CONFIG iso-codes --atleast-version=0.49; then + have_iso_codes=yes + AC_MSG_RESULT([$have_iso_codes]) + else + AC_MSG_WARN([iso-codes detected, but version 0.49 or later is required due to licensing]) + fi +else + AC_MSG_RESULT([$have_iso_codes]) +fi + +if test "x$have_iso_codes" = "xyes"; then + AC_MSG_CHECKING([whether iso-codes has iso-639 and iso-3166 domains]) + if $PKG_CONFIG --variable=domains iso-codes | grep 639 >/dev/null 2>&1 && \ + $PKG_CONFIG --variable=domains iso-codes | grep 3166 >/dev/null 2>&1 ; then + result=yes + else + result=no + have_iso_codes=no + fi + AC_MSG_RESULT([$result]) +fi + +if test "x$have_iso_codes" = "xyes"; then + AC_DEFINE_UNQUOTED([ISO_CODES_PREFIX], + ["`$PKG_CONFIG --variable=prefix iso-codes`"], + [ISO codes prefix]) + AC_DEFINE([HAVE_ISO_CODES], [1], + [Define if you have the iso-codes package]) +fi + dnl ************************************************** dnl Accessibility support dnl ************************************************** @@ -567,16 +613,6 @@ AM_CONDITIONAL(SUNLDAP, [test x$with_sunldap != xno]) AC_DEFINE(HANDLE_LIBICAL_MEMORY, 1, [Define it once memory returned by libical is free'ed properly]) -dnl ************************* -dnl GTKHTML check -dnl XXX Drop the version from the package name? -dnl ************************* -PKG_CHECK_MODULES([GTKHTML], - [libgtkhtml-4.0 >= gtkhtml_minimum_version - gtkhtml-editor-4.0]) -AC_SUBST(GTKHTML_CFLAGS) -AC_SUBST(GTKHTML_LIBS) - dnl ******************************************************************************** dnl security extension support (SSL and S/MIME) dnl diff --git a/data/evolution.convert b/data/evolution.convert index 60ff3bbc4b..d25e6f48ea 100644 --- a/data/evolution.convert +++ b/data/evolution.convert @@ -128,7 +128,6 @@ composer-show-post-from = /apps/evolution/mail/composer/show_post_from composer-show-post-reply-to = /apps/evolution/mail/composer/show_post_reply_to composer-show-reply-to = /apps/evolution/mail/composer/show_mail_reply_to composer-sign-reply-if-signed = /apps/evolution/mail/composer/sign_reply_if_signed -composer-spell-color = /apps/evolution/mail/composer/spell_color composer-spell-languages = /apps/evolution/mail/composer/spell_languages composer-top-signature = /apps/evolution/mail/composer/top_signature default-account = /apps/evolution/mail/default_account diff --git a/data/org.gnome.evolution.mail.gschema.xml.in b/data/org.gnome.evolution.mail.gschema.xml.in index c1b2e25545..c13540db63 100644 --- a/data/org.gnome.evolution.mail.gschema.xml.in +++ b/data/org.gnome.evolution.mail.gschema.xml.in @@ -95,11 +95,6 @@ <_summary>Send HTML mail by default <_description>Send HTML mail by default. - - '#FFFF00000000' - <_summary>Spell checking color - <_description>Underline color for misspelled words when using inline spelling. - [] <_summary>Spell checking languages @@ -165,6 +160,16 @@ <_summary>List of localized 'Re' <_description>Comma-separated list of localized 'Re' abbreviations to skip in a subject text when replying to a message, as an addition to the standard "Re" prefix. An example is 'SV,AV'. + + false + <_summary>Enable developer mode + <_description>Enables some hidden actions and tools aimed for development and debugging. + + + 71 + <_summary>Number of characters for wrapping + <_description>Will autowrap lines after given number of characters. + 'mbox' <_summary>Save file format for drag-and-drop operation diff --git a/doc/reference/evolution-mail-composer/Makefile.am b/doc/reference/evolution-mail-composer/Makefile.am index 580dc63a3b..3fbc28fe7b 100644 --- a/doc/reference/evolution-mail-composer/Makefile.am +++ b/doc/reference/evolution-mail-composer/Makefile.am @@ -22,14 +22,12 @@ GTKDOC_CFLAGS= \ -I$(top_builddir) \ $(EVOLUTION_DATA_SERVER_CFLAGS) \ $(GNOME_PLATFORM_CFLAGS) \ - $(GTKHTML_CFLAGS) \ $(NULL) GTKDOC_LIBS= \ $(top_builddir)/composer/libevolution-mail-composer.la \ $(EVOLUTION_DATA_SERVER_LIBS) \ $(GNOME_PLATFORM_LIBS) \ - $(GTKHTML_LIBS) \ $(NULL) # This includes the standard gtk-doc make rules, copied by gtkdocize. diff --git a/doc/reference/evolution-mail-composer/evolution-mail-composer-docs.sgml b/doc/reference/evolution-mail-composer/evolution-mail-composer-docs.sgml index becbfd678f..b7d06b39db 100644 --- a/doc/reference/evolution-mail-composer/evolution-mail-composer-docs.sgml +++ b/doc/reference/evolution-mail-composer/evolution-mail-composer-docs.sgml @@ -16,7 +16,6 @@ Mail Composition - diff --git a/doc/reference/evolution-mail-composer/evolution-mail-composer-sections.txt b/doc/reference/evolution-mail-composer/evolution-mail-composer-sections.txt index 4b286f94ec..cbe3f9ec3e 100644 --- a/doc/reference/evolution-mail-composer/evolution-mail-composer-sections.txt +++ b/doc/reference/evolution-mail-composer/evolution-mail-composer-sections.txt @@ -1,22 +1,3 @@ -
-e-composer-activity -EComposerActivity -EComposerActivity -e_composer_activity_new -e_composer_activity_get_composer - -E_COMPOSER_ACTIVITY -E_IS_COMPOSER_ACTIVITY -E_TYPE_COMPOSER_ACTIVITY -E_COMPOSER_ACTIVITY_CLASS -E_IS_COMPOSER_ACTIVITY_CLASS -E_COMPOSER_ACTIVITY_GET_CLASS -EComposerActivityClass -e_composer_activity_get_type - -EComposerActivityPrivate -
-
e-composer-from-header EComposerFromHeader diff --git a/doc/reference/evolution-mail-composer/evolution-mail-composer.types b/doc/reference/evolution-mail-composer/evolution-mail-composer.types index bf4ab7c4d6..5e26731470 100644 --- a/doc/reference/evolution-mail-composer/evolution-mail-composer.types +++ b/doc/reference/evolution-mail-composer/evolution-mail-composer.types @@ -1,4 +1,3 @@ -#include #include #include #include @@ -8,7 +7,6 @@ #include #include -e_composer_activity_get_type e_composer_from_header_get_type e_composer_header_get_type e_composer_header_table_get_type diff --git a/doc/reference/evolution-mail-formatter/Makefile.am b/doc/reference/evolution-mail-formatter/Makefile.am index aff3eb7142..ed313fcb86 100644 --- a/doc/reference/evolution-mail-formatter/Makefile.am +++ b/doc/reference/evolution-mail-formatter/Makefile.am @@ -21,14 +21,12 @@ GTKDOC_CFLAGS= \ -I$(top_builddir) \ $(EVOLUTION_DATA_SERVER_CFLAGS) \ $(GNOME_PLATFORM_CFLAGS) \ - $(GTKHTML_CFLAGS) \ $(NULL) GTKDOC_LIBS= \ $(top_builddir)/em-format/libevolution-mail-formatter.la \ $(EVOLUTION_DATA_SERVER_LIBS) \ $(GNOME_PLATFORM_LIBS) \ - $(GTKHTML_LIBS) \ $(NULL) # This includes the standard gtk-doc make rules, copied by gtkdocize. diff --git a/doc/reference/evolution-shell/Makefile.am b/doc/reference/evolution-shell/Makefile.am index 7afbc72919..c64068e384 100644 --- a/doc/reference/evolution-shell/Makefile.am +++ b/doc/reference/evolution-shell/Makefile.am @@ -22,7 +22,6 @@ GTKDOC_CFLAGS= \ -I$(top_builddir) \ $(EVOLUTION_DATA_SERVER_CFLAGS) \ $(GNOME_PLATFORM_CFLAGS) \ - $(GTKHTML_CFLAGS) \ $(NULL) GTKDOC_LIBS= \ @@ -31,7 +30,6 @@ GTKDOC_LIBS= \ $(top_builddir)/e-util/libevolution-util.la \ $(EVOLUTION_DATA_SERVER_LIBS) \ $(GNOME_PLATFORM_LIBS) \ - $(GTKHTML_LIBS) \ $(NULL) # This includes the standard gtk-doc make rules, copied by gtkdocize. diff --git a/doc/reference/evolution-util/Makefile.am b/doc/reference/evolution-util/Makefile.am index a7055a4f39..48d1d13845 100644 --- a/doc/reference/evolution-util/Makefile.am +++ b/doc/reference/evolution-util/Makefile.am @@ -16,6 +16,9 @@ CFILE_GLOB = $(top_srcdir)/e-util/*.c # Ignore all accessiblity headers. IGNORE_HFILES = \ + e-html-editor-actions.h \ + e-html-editor-private.h \ + e-html-editor-utils.h \ e-marshal.h \ e-table-col-dnd.h \ e-table-defines.h \ @@ -52,14 +55,12 @@ GTKDOC_CFLAGS = \ -I$(top_builddir) \ $(EVOLUTION_DATA_SERVER_CFLAGS) \ $(GNOME_PLATFORM_CFLAGS) \ - $(GTKHTML_CFLAGS) \ $(NULL) GTKDOC_LIBS = \ $(top_builddir)/e-util/libevolution-util.la \ $(EVOLUTION_DATA_SERVER_LIBS) \ $(GNOME_PLATFORM_LIBS) \ - $(GTKHTML_LIBS) \ $(NULL) # Extra options to supply to gtkdoc-mkdb diff --git a/doc/reference/evolution-util/evolution-util-docs.sgml b/doc/reference/evolution-util/evolution-util-docs.sgml index 53badcc278..262ddae97d 100644 --- a/doc/reference/evolution-util/evolution-util-docs.sgml +++ b/doc/reference/evolution-util/evolution-util-docs.sgml @@ -94,11 +94,39 @@ HTML Rendering - + + HTML Editing + + + + + + + + + + + + + + + + + + + + + + + + + + + Mail Signatures diff --git a/doc/reference/evolution-util/evolution-util-sections.txt b/doc/reference/evolution-util/evolution-util-sections.txt index 5f7497c5f9..b5b4ee76bb 100644 --- a/doc/reference/evolution-util/evolution-util-sections.txt +++ b/doc/reference/evolution-util/evolution-util-sections.txt @@ -1322,6 +1322,55 @@ e_client_selector_get_type EClientSelectorPrivate
+
+e-color-chooser-widget +EColorChooserWidget +EColorChooserWidget +e_color_chooser_widget_new + +E_COLOR_CHOOSER_WIDGET +E_IS_COLOR_CHOOSER_WIDGET +E_TYPE_COLOR_CHOOSER_WIDGET +E_COLOR_CHOOSER_WIDGET_CLASS +E_IS_COLOR_CHOOSER_WIDGET_CLASS +E_COLOR_CHOOSER_WIDGET_GET_CLASS +EColorChooserWidgetClass +e_color_chooser_widget_get_type + +EColorChooserWidgetPrivate +
+ +
+e-color-combo +EColorCombo +EColorCombo +e_color_combo_new +e_color_combo_new_defaults +e_color_combo_popup +e_color_combo_popdown +e_color_combo_get_current_color +e_color_combo_set_current_color +e_color_combo_get_default_color +e_color_combo_set_default_color +e_color_combo_get_default_label +e_color_combo_set_default_label +e_color_combo_get_default_transparent +e_color_combo_set_default_transparent +e_color_combo_get_palette +e_color_combo_set_palette + +E_COLOR_COMBO +E_IS_COLOR_COMBO +E_TYPE_COLOR_COMBO +E_COLOR_COMBO_CLASS +E_IS_COLOR_COMBO_CLASS +E_COLOR_COMBO_GET_CLASS +EColorComboClass +e_color_combo_get_type + +EColorComboPrivate +
+
e-config EConfig @@ -1518,6 +1567,890 @@ e_dialog_combo_box_get e_dialog_button_new_with_icon
+
+e-editor +EEditor +EEditor +e_editor_new +e_editor_is_busy +e_editor_get_editor_widget +e_editor_get_builder +e_editor_get_ui_manager +e_editor_get_action +e_editor_get_action_group +e_editor_get_widget +e_editor_get_managed_widget +e_editor_get_filename +e_editor_set_filename +e_editor_new_activity +e_editor_pack_above +e_editor_save + +E_EDITOR +E_IS_EDITOR +E_TYPE_EDITOR +E_EDITOR_CLASS +E_IS_EDITOR_CLASS +E_EDITOR_GET_CLASS +EEditorClass +e_editor_get_type + +EEditorPrivate +
+ +
+e-html-editor-cell-dialog +EHTMLEditorCellDialog +EHTMLEditorCellDialog +e_html_editor_cell_dialog_new +e_html_editor_cell_dialog_show + +E_HTML_EDITOR_CELL_DIALOG +E_IS_HTML_EDITOR_CELL_DIALOG +E_TYPE_HTML_EDITOR_CELL_DIALOG +E_HTML_EDITOR_CELL_DIALOG_CLASS +E_IS_HTML_EDITOR_CELL_DIALOG_CLASS +E_HTML_EDITOR_CELL_DIALOG_GET_CLASS +EHTMLEditorCellDialogClass +e_html_editor_cell_dialog_get_type + +EHTMLEditorCellDialogPrivate +
+ +
+e-editor-dialog +EEditorDialog +e_editor_dialog_get_editor +e_editor_dialog_get_button_box +e_editor_dialog_get_container + +E_EDITOR_DIALOG +E_IS_EDITOR_DIALOG +E_TYPE_EDITOR_DIALOG +E_EDITOR_DIALOG_CLASS +E_IS_EDITOR_DIALOG_CLASS +E_EDITOR_DIALOG_GET_CLASS +EEditorDialogClass +e_editor_dialog_get_type + +EEditorDialogPrivate +
+ +
+e-html-editor-find-dialog +EHTMLEditorFindDialog +e_html_editor_find_dialog_new +e_html_editor_find_dialog_find_next + +E_HTML_EDITOR_FIND_DIALOG +E_IS_HTML_EDITOR_FIND_DIALOG +E_TYPE_HTML_EDITOR_FIND_DIALOG +E_HTML_EDITOR_FIND_DIALOG_CLASS +E_IS_HTML_EDITOR_FIND_DIALOG_CLASS +E_HTML_EDITOR_FIND_DIALOG_GET_CLASS +EHTMLEditorFindDialogClass +e_html_editor_find_dialog_get_type + +EHTMLEditorFindDialogPrivate +
+ +
+e-editor-hrule-dialog +EHTMLEditorHRuleDialog +EHTMLEditorHRuleDialog +e_html_editor_hrule_dialog_new + +E_HTML_EDITOR_HRULE_DIALOG +E_IS_HTML_EDITOR_HRULE_DIALOG +E_TYPE_HTML_EDITOR_HRULE_DIALOG +E_HTML_EDITOR_HRULE_DIALOG_CLASS +E_IS_HTML_EDITOR_HRULE_DIALOG_CLASS +E_HTML_EDITOR_HRULE_DIALOG_GET_CLASS +EHTMLEditorHRuleDialogClass +e_html_editor_hrule_dialog_get_type + +EHTMLEditorHRuleDialogPrivate +
+ +
+e-html-editor-image-dialog +EHTMLEditorImageDialog +EHTMLEditorImageDialog +e_html_editor_image_dialog_new +e_html_editor_image_dialog_show + +E_HTML_EDITOR_IMAGE_DIALOG +E_IS_HTML_EDITOR_IMAGE_DIALOG +E_TYPE_HTML_EDITOR_IMAGE_DIALOG +E_HTML_EDITOR_IMAGE_DIALOG_CLASS +E_IS_HTML_EDITOR_IMAGE_DIALOG_CLASS +E_HTML_EDITOR_IMAGE_DIALOG_GET_CLASS +EHTMLEditorImageDialogClass +e_html_editor_image_dialog_get_type + +EHTMLEditorImageDialogPrivate +
+ +
+e-html-editor-link-dialog +EHTMLEditorLinkDialog +EHTMLEditorLinkDialog +e_html_editor_link_dialog_new + +E_HTML_EDITOR_LINK_DIALOG +E_IS_HTML_EDITOR_LINK_DIALOG +E_TYPE_HTML_EDITOR_LINK_DIALOG +E_HTML_EDITOR_LINK_DIALOG_CLASS +E_IS_HTML_EDITOR_LINK_DIALOG_CLASS +E_HTML_EDITOR_LINK_DIALOG_GET_CLASS +EHTMLEditorLinkDialogClass +e_html_editor_link_dialog_get_type + +EHTMLEditorLinkDialogPrivate +
+ +
+e-html-editor-page-dialog +EHTMLEditorPageDialog +EHTMLEditorPageDialog +e_html_editor_page_dialog_new + +E_HTML_EDITOR_PAGE_DIALOG +E_IS_HTML_EDITOR_PAGE_DIALOG +E_TYPE_HTML_EDITOR_PAGE_DIALOG +E_HTML_EDITOR_PAGE_DIALOG_CLASS +E_IS_HTML_EDITOR_PAGE_DIALOG_CLASS +E_HTML_EDITOR_PAGE_DIALOG_GET_CLASS +EHTMLEditorPageDialogClass +e_html_editor_page_dialog_get_type + +EHTMLEditorPageDialogPrivate +
+ +
+e-html-editor-paragraph-dialog +EHTMLEditorParagraphDialog +EHTMLEditorParagraphDialog +e_html_editor_paragraph_dialog_new + +E_HTML_EDITOR_PARAGRAPH_DIALOG +E_IS_HTML_EDITOR_PARAGRAPH_DIALOG +E_TYPE_HTML_EDITOR_PARAGRAPH_DIALOG +E_HTML_EDITOR_PARAGRAPH_DIALOG_CLASS +E_IS_HTML_EDITOR_PARAGRAPH_DIALOG_CLASS +E_HTML_EDITOR_PARAGRAPH_DIALOG_GET_CLASS +EHTMLEditorParagraphDialogClass +e_html_editor_paragraph_dialog_get_type + +EHTMLEditorParagraphDialogPrivate +
+ +
+e-html-editor-replace-dialog +EHTMLEditorReplaceDialog +EHTMLEditorReplaceDialog +e_html_editor_replace_dialog_new + +E_HTML_EDITOR_REPLACE_DIALOG +E_IS_HTML_EDITOR_REPLACE_DIALOG +E_TYPE_HTML_EDITOR_REPLACE_DIALOG +E_HTML_EDITOR_REPLACE_DIALOG_CLASS +E_IS_HTML_EDITOR_REPLACE_DIALOG_CLASS +E_HTML_EDITOR_REPLACE_DIALOG_GET_CLASS +EHTMLEditorReplaceDialogClass +e_html_editor_replace_dialog_get_type + +EHTMLEditorReplaceDialogPrivate +
+ +
+e-editor-selection +EEditorSelection +e_editor_selection_ref_editor_widget +e_editor_selection_has_text +e_editor_selection_get_caret_word +e_editor_selection_replace_caret_word +EEditorSelectionAlignment +e_editor_selection_get_alignment +e_editor_selection_set_alignment +e_editor_selection_get_background_color +e_editor_selection_set_background_color +e_editor_selection_get_font_color +e_editor_selection_set_font_color +e_editor_selection_get_font_name +e_editor_selection_set_font_name +EEditorSelectionFontSize +e_editor_selection_get_font_size +e_editor_selection_set_font_size +EEditorSelectionBlockFormat +e_editor_selection_get_block_format +e_editor_selection_set_block_format +e_editor_selection_is_citation +e_editor_selection_is_indented +e_editor_selection_indent +e_editor_selection_unindent +e_editor_selection_is_bold +e_editor_selection_set_bold +e_editor_selection_is_italic +e_editor_selection_set_italic +e_editor_selection_is_monospaced +e_editor_selection_set_monospaced +e_editor_selection_is_strike_through +e_editor_selection_set_strike_through +e_editor_selection_is_superscript +e_editor_selection_set_superscript +e_editor_selection_is_subscript +e_editor_selection_set_subscript +e_editor_selection_is_underline +e_editor_selection_set_underline +e_editor_selection_unlink +e_editor_selection_create_link +e_editor_selection_get_string +e_editor_selection_replace +e_editor_selection_insert_html +e_editor_selection_insert_image +e_editor_selection_insert_text +e_editor_selection_wrap_lines +e_editor_selection_save +e_editor_selection_restore +EEditorSelectionGranularity +e_editor_selection_move +e_editor_selection_extend + +E_EDITOR_SELECTION +E_IS_EDITOR_SELECTION +E_TYPE_EDITOR_SELECTION +E_EDITOR_SELECTION_CLASS +E_IS_EDITOR_SELECTION_CLASS +E_EDITOR_SELECTION_GET_CLASS +EEditorSelectionClass +e_editor_selection_get_type + +EEditorSelectionPrivate +
+ +
+e-html-editor-spell-check-dialog +EHTMLEditorSpellCheckDialog +EHTMLEditorSpellCheckDialog +e_html_editor_spell_check_dialog_new +e_html_editor_spell_check_dialog_update_dictionaries + +E_HTML_EDITOR_SPELL_CHECK_DIALOG +E_IS_HTML_EDITOR_SPELL_CHECK_DIALOG +E_TYPE_HTML_EDITOR_SPELL_CHECK_DIALOG +E_HTML_EDITOR_SPELL_CHECK_DIALOG_CLASS +E_IS_HTML_EDITOR_SPELL_CHECK_DIALOG_CLASS +E_HTML_EDITOR_SPELL_CHECK_DIALOG_GET_CLASS +EHTMLEditorSpellCheckDialogClass +e_html_editor_spell_check_dialog_get_type + +EHTMLEditorSpellCheckDialogPrivate +
+ +
+e-html-editor-table-dialog +EHTMLEditorTableDialog +EHTMLEditorTableDialog +e_html_editor_table_dialog_new + +E_HTML_EDITOR_TABLE_DIALOG +E_IS_HTML_EDITOR_TABLE_DIALOG +E_TYPE_HTML_EDITOR_TABLE_DIALOG +E_HTML_EDITOR_TABLE_DIALOG_CLASS +E_IS_HTML_EDITOR_TABLE_DIALOG_CLASS +E_HTML_EDITOR_TABLE_DIALOG_GET_CLASS +EHTMLEditorTableDialogClass +e_html_editor_table_dialog_get_type + +EHTMLEditorTableDialogPrivate +
+ +
+e-html-editor-text-dialog +EHTMLEditorTextDialog +EHTMLEditorTextDialog +e_html_editor_text_dialog_new + +E_HTML_EDITOR_TEXT_DIALOG +E_IS_HTML_EDITOR_TEXT_DIALOG +E_TYPE_HTML_EDITOR_TEXT_DIALOG +E_HTML_EDITOR_TEXT_DIALOG_CLASS +E_IS_HTML_EDITOR_TEXT_DIALOG_CLASS +E_HTML_EDITOR_TEXT_DIALOG_GET_CLASS +EHTMLEditorTextDialogClass +e_html_editor_text_dialog_get_type + +EHTMLEditorTextDialogPrivate +
+ +
+e-editor-widget +EEditorWidget +EEditorWidget +e_editor_widget_new +e_editor_widget_get_selection +EEditorWidgetCommand +e_editor_widget_exec_command +e_editor_widget_get_changed +e_editor_widget_set_changed +e_editor_widget_get_html_mode +e_editor_widget_set_html_mode +e_editor_widget_get_inline_spelling +e_editor_widget_set_inline_spelling +e_editor_widget_get_magic_links +e_editor_widget_set_magic_links +e_editor_widget_get_magic_smileys +e_editor_widget_set_magic_smileys +e_editor_widget_get_spell_checker +e_editor_widget_get_text_html +e_editor_widget_get_text_plain +e_editor_widget_set_text_html +e_editor_widget_set_text_plain +e_editor_widget_paste_clipboard_quoted +e_editor_widget_update_fonts + +E_EDITOR_WIDGET +E_IS_EDITOR_WIDGET +E_TYPE_EDITOR_WIDGET +E_EDITOR_WIDGET_CLASS +E_IS_EDITOR_WIDGET_CLASS +E_EDITOR_WIDGET_GET_CLASS +EEditorWidgetClass +e_editor_widget_get_type + +EEditorWidgetPrivate +
+ +
+e-emoticon +EEmoticon +EEmoticon +e_emoticon_equal +e_emoticon_copy +e_emoticon_free +e_emoticon_get_uri + +E_TYPE_EMOTICON +e_emoticon_get_type +
+ +
+e-emoticon-action +EEmoticonAction +EEmoticonAction +e_emoticon_action_new + +E_EMOTICON_ACTION +E_IS_EMOTICON_ACTION +E_TYPE_EMOTICON_ACTION +E_EMOTICON_ACTION_CLASS +E_IS_EMOTICON_ACTION_CLASS +E_EMOTICON_ACTION_GET_CLASS +EEmoticonActionClass +e_emoticon_action_get_type + +EEmoticonActionPrivate +
+ +
+e-emoticon-chooser +EEmoticonChooser +EEmoticonChooser +EEmoticonChooserInterface +e_emoticon_chooser_get_current_emoticon +e_emoticon_chooser_set_current_emoticon +e_emoticon_chooser_item_activated +e_emoticon_chooser_get_items +e_emoticon_chooser_lookup_emoticon + +E_EMOTICON_CHOOSER +E_IS_EMOTICON_CHOOSER +E_TYPE_EMOTICON_CHOOSER +E_EMOTICON_CHOOSER_GET_INTERFACE +e_emoticon_chooser_get_type +
+ +
+e-emoticon-chooser-menu +EEmoticonChooserMenu +EEmoticonChooserMenu +e_emoticon_chooser_menu_new + +E_EMOTICON_CHOOSER_MENU +E_IS_EMOTICON_CHOOSER_MENU +E_TYPE_EMOTICON_CHOOSER_MENU +E_EMOTICON_CHOOSER_MENU_CLASS +E_IS_EMOTICON_CHOOSER_MENU_CLASS +E_EMOTICON_CHOOSER_MENU_GET_CLASS +EEmoticonChooserMenuClass +e_emoticon_chooser_menu_get_type + +EEmoticonChooserMenuPrivate +
+ +
+e-emoticon-tool-button +EEmoticonToolButton +EEmoticonToolButton +e_emoticon_tool_button_new +e_emoticon_tool_button_popup +e_emoticon_tool_button_popdown + +E_EMOTICON_TOOL_BUTTON +E_IS_EMOTICON_TOOL_BUTTON +E_TYPE_EMOTICON_TOOL_BUTTON +E_EMOTICON_TOOL_BUTTON_CLASS +E_IS_EMOTICON_TOOL_BUTTON_CLASS +E_EMOTICON_TOOL_BUTTON_GET_CLASS +EEmoticonToolButtonClass +e_emoticon_tool_button_get_type + +EEmoticonToolButtonPrivate +
+ +
+e-html-editor +EHTMLEditor +EHTMLEditor +e_html_editor_new +e_html_editor_is_busy +e_html_editor_get_view +e_html_editor_get_builder +e_html_editor_get_ui_manager +e_html_editor_get_action +e_html_editor_get_action_group +e_html_editor_get_widget +e_html_editor_get_managed_widget +e_html_editor_get_filename +e_html_editor_set_filename +e_html_editor_new_activity +e_html_editor_pack_above +e_html_editor_save + +E_HTML_EDITOR +E_IS_HTML_EDITOR +E_TYPE_HTML_EDITOR +E_HTML_EDITOR_CLASS +E_IS_HTML_EDITOR_CLASS +E_HTML_EDITOR_GET_CLASS +EHTMLEditorClass +e_html_editor_get_type + +EHTMLEditorPrivate +
+ +
+e-editor-cell-dialog +EEditorCellDialog +EEditorCellDialog +e_editor_cell_dialog_new +e_editor_cell_dialog_show + +E_EDITOR_CELL_DIALOG +E_IS_EDITOR_CELL_DIALOG +E_TYPE_EDITOR_CELL_DIALOG +E_EDITOR_CELL_DIALOG_CLASS +E_IS_EDITOR_CELL_DIALOG_CLASS +E_EDITOR_CELL_DIALOG_GET_CLASS +EEditorCellDialogClass +e_editor_cell_dialog_get_type + +EEditorCellDialogPrivate +
+ +
+e-html-editor-dialog +EHTMLEditorDialog +e_html_editor_dialog_get_editor +e_html_editor_dialog_get_button_box +e_html_editor_dialog_get_container + +E_HTML_EDITOR_DIALOG +E_IS_HTML_EDITOR_DIALOG +E_TYPE_HTML_EDITOR_DIALOG +E_HTML_EDITOR_DIALOG_CLASS +E_IS_HTML_EDITOR_DIALOG_CLASS +E_HTML_EDITOR_DIALOG_GET_CLASS +EEditorDialogClass +e_html_editor_dialog_get_type + +EHTMLEditorDialogPrivate +
+ +
+e-editor-find-dialog +EEditorFindDialog +e_editor_find_dialog_new +e_editor_find_dialog_find_next + +E_EDITOR_FIND_DIALOG +E_IS_EDITOR_FIND_DIALOG +E_TYPE_EDITOR_FIND_DIALOG +E_EDITOR_FIND_DIALOG_CLASS +E_IS_EDITOR_FIND_DIALOG_CLASS +E_EDITOR_FIND_DIALOG_GET_CLASS +EEditorFindDialogClass +e_editor_find_dialog_get_type + +EEditorFindDialogPrivate +
+ +
+e-editor-hrule-dialog +EEditorHRuleDialog +EEditorHRuleDialog +e_editor_hrule_dialog_new + +E_EDITOR_HRULE_DIALOG +E_IS_EDITOR_HRULE_DIALOG +E_TYPE_EDITOR_HRULE_DIALOG +E_EDITOR_HRULE_DIALOG_CLASS +E_IS_EDITOR_HRULE_DIALOG_CLASS +E_EDITOR_HRULE_DIALOG_GET_CLASS +EEditorHRuleDialogClass +e_editor_hrule_dialog_get_type + +EEditorHRuleDialogPrivate +
+ +
+e-editor-image-dialog +EEditorImageDialog +EEditorImageDialog +e_editor_image_dialog_new +e_editor_image_dialog_show + +E_EDITOR_IMAGE_DIALOG +E_IS_EDITOR_IMAGE_DIALOG +E_TYPE_EDITOR_IMAGE_DIALOG +E_EDITOR_IMAGE_DIALOG_CLASS +E_IS_EDITOR_IMAGE_DIALOG_CLASS +E_EDITOR_IMAGE_DIALOG_GET_CLASS +EEditorImageDialogClass +e_editor_image_dialog_get_type + +EEditorImageDialogPrivate +
+ +
+e-editor-link-dialog +EEditorLinkDialog +EEditorLinkDialog +e_editor_link_dialog_new + +E_EDITOR_LINK_DIALOG +E_IS_EDITOR_LINK_DIALOG +E_TYPE_EDITOR_LINK_DIALOG +E_EDITOR_LINK_DIALOG_CLASS +E_IS_EDITOR_LINK_DIALOG_CLASS +E_EDITOR_LINK_DIALOG_GET_CLASS +EEditorLinkDialogClass +e_editor_link_dialog_get_type + +EEditorLinkDialogPrivate +
+ +
+e-editor-page-dialog +EEditorPageDialog +EEditorPageDialog +e_editor_page_dialog_new + +E_EDITOR_PAGE_DIALOG +E_IS_EDITOR_PAGE_DIALOG +E_TYPE_EDITOR_PAGE_DIALOG +E_EDITOR_PAGE_DIALOG_CLASS +E_IS_EDITOR_PAGE_DIALOG_CLASS +E_EDITOR_PAGE_DIALOG_GET_CLASS +EEditorPageDialogClass +e_editor_page_dialog_get_type + +EEditorPageDialogPrivate +
+ +
+e-editor-paragraph-dialog +EEditorParagraphDialog +EEditorParagraphDialog +e_editor_paragraph_dialog_new + +E_EDITOR_PARAGRAPH_DIALOG +E_IS_EDITOR_PARAGRAPH_DIALOG +E_TYPE_EDITOR_PARAGRAPH_DIALOG +E_EDITOR_PARAGRAPH_DIALOG_CLASS +E_IS_EDITOR_PARAGRAPH_DIALOG_CLASS +E_EDITOR_PARAGRAPH_DIALOG_GET_CLASS +EEditorParagraphClass +e_editor_paragraph_dialog_get_type + +EEditorParagraphDialogPrivate +
+ +
+e-editor-replace-dialog +EEditorReplaceDialog +EEditorReplaceDialog +e_editor_replace_dialog_new + +E_EDITOR_REPLACE_DIALOG +E_IS_EDITOR_REPLACE_DIALOG +E_TYPE_EDITOR_REPLACE_DIALOG +E_EDITOR_REPLACE_DIALOG_CLASS +E_IS_EDITOR_REPLACE_DIALOG_CLASS +E_EDITOR_REPLACE_DIALOG_GET_CLASS +EEditorReplaceDialogClass +e_editor_replace_dialog_get_type + +EEditorReplaceDialogPrivate +
+ +
+e-html-editor-selection +EHTMLEditorSelection +e_html_editor_selection_ref_html_html_editor_view +e_html_editor_selection_has_text +e_html_editor_selection_get_caret_word +e_html_editor_selection_replace_caret_word +EHTMLEditorSelectionAlignment +e_html_editor_selection_get_alignment +e_html_editor_selection_set_alignment +e_html_editor_selection_get_background_color +e_html_editor_selection_set_background_color +e_html_editor_selection_get_font_color +e_html_editor_selection_set_font_color +e_html_editor_selection_get_font_name +e_html_editor_selection_set_font_name +EHTMLEditorSelectionFontSize +e_html_editor_selection_get_font_size +e_html_editor_selection_set_font_size +EHTMLEditorSelectionBlockFormat +e_html_editor_selection_get_block_format +e_html_editor_selection_set_block_format +e_html_editor_selection_is_citation +e_html_editor_selection_is_indented +e_html_editor_selection_indent +e_html_editor_selection_unindent +e_html_editor_selection_is_bold +e_html_editor_selection_set_bold +e_html_editor_selection_is_italic +e_html_editor_selection_set_italic +e_html_editor_selection_is_monospaced +e_html_editor_selection_set_monospaced +e_html_editor_selection_is_strike_through +e_html_editor_selection_set_strike_through +e_html_editor_selection_is_superscript +e_html_editor_selection_set_superscript +e_html_editor_selection_is_subscript +e_html_editor_selection_set_subscript +e_html_editor_selection_is_underline +e_html_editor_selection_set_underline +e_html_editor_selection_unlink +e_html_editor_selection_create_link +e_html_editor_selection_get_string +e_html_editor_selection_replace +e_html_editor_selection_insert_html +e_html_editor_selection_insert_image +e_html_editor_selection_insert_text +e_html_editor_selection_wrap_lines +e_html_editor_selection_save +e_html_editor_selection_restore +EHTMLEditorSelectionGranularity +e_html_editor_selection_move +e_html_editor_selection_extend + +E_HTML_EDITOR_SELECTION +E_IS_HTML_EDITOR_SELECTION +E_TYPE_HTML_EDITOR_SELECTION +E_HTML_EDITOR_SELECTION_CLASS +E_IS_HTML_EDITOR_SELECTION_CLASS +E_HTML_EDITOR_SELECTION_GET_CLASS +EHTMLEditorSelectionClass +e_html_editor_selection_get_type + +EHTMLEditorSelectionPrivate +
+ +
+e-editor-spell-check-dialog +EEditorSpellCheckDialog +EEditorSpellCheckDialog +e_editor_spell_check_dialog_new +e_editor_spell_check_dialog_update_dictionaries + +E_EDITOR_SPELL_CHECK_DIALOG +E_IS_EDITOR_SPELL_CHECK_DIALOG +E_TYPE_EDITOR_SPELL_CHECK_DIALOG +E_EDITOR_SPELL_CHECK_DIALOG_CLASS +E_IS_EDITOR_SPELL_CHECK_DIALOG_CLASS +E_EDITOR_SPELL_CHECK_DIALOG_GET_CLASS +EEditorSpellCheckDialogClass +e_editor_spell_check_dialog_get_type + +EEditorSpellCheckDialogPrivate +
+ +
+e-editor-table-dialog +EEditorTableDialog +EEditorTableDialog +e_editor_table_dialog_new + +E_EDITOR_TABLE_DIALOG +E_IS_EDITOR_TABLE_DIALOG +E_TYPE_EDITOR_TABLE_DIALOG +E_EDITOR_TABLE_DIALOG_CLASS +E_IS_EDITOR_TABLE_DIALOG_CLASS +E_EDITOR_TABLE_DIALOG_GET_CLASS +EEditorTableDialogClass +e_editor_table_dialog_get_type + +EEditorTableDialogPrivate +
+ +
+e-editor-text-dialog +EEditorTextDialog +EEditorTextDialog +e_editor_text_dialog_new + +E_EDITOR_TEXT_DIALOG +E_IS_EDITOR_TEXT_DIALOG +E_TYPE_EDITOR_TEXT_DIALOG +E_EDITOR_TEXT_DIALOG_CLASS +E_IS_EDITOR_TEXT_DIALOG_CLASS +E_EDITOR_TEXT_DIALOG_GET_CLASS +EEditorTextDialogClass +e_editor_text_dialog_get_type + +EEditorTextDialogPrivate +
+ +
+e-editor-widget +EHTMLEditorView +EHTMLEditorView +e_html_editor_view_new +e_html_editor_view_get_selection +EHTMLEditorViewCommand +e_html_editor_view_exec_command +e_html_editor_view_get_changed +e_html_editor_view_set_changed +e_html_editor_view_get_html_mode +e_html_editor_view_set_html_mode +e_html_editor_view_get_inline_spelling +e_html_editor_view_set_inline_spelling +e_html_editor_view_get_magic_links +e_html_editor_view_set_magic_links +e_html_editor_view_get_magic_smileys +e_html_editor_view_set_magic_smileys +e_html_editor_view_get_spell_checker +e_html_editor_view_get_text_html +e_html_editor_view_get_text_plain +e_html_editor_view_set_text_html +e_html_editor_view_set_text_plain +e_html_editor_view_paste_clipboard_quoted +e_html_editor_view_update_fonts + +E_HTML_EDITOR_VIEW +E_IS_HTML_EDITOR_VIEW +E_TYPE_HTML_EDITOR_VIEW +E_HTML_EDITOR_VIEW_CLASS +E_IS_HTML_EDITOR_VIEW_CLASS +E_HTML_EDITOR_VIEW_GET_CLASS +EHTMLEditorViewClass +e_html_editor_view_get_type + +EHTMLEditorViewPrivate +
+ +
+e-emoticon +EEmoticon +EEmoticon +e_emoticon_equal +e_emoticon_copy +e_emoticon_free +e_emoticon_get_uri + +E_TYPE_EMOTICON +e_emoticon_get_type +
+ +
+e-emoticon-action +EEmoticonAction +EEmoticonAction +e_emoticon_action_new + +E_EMOTICON_ACTION +E_IS_EMOTICON_ACTION +E_TYPE_EMOTICON_ACTION +E_EMOTICON_ACTION_CLASS +E_IS_EMOTICON_ACTION_CLASS +E_EMOTICON_ACTION_GET_CLASS +EEmoticonActionClass +e_emoticon_action_get_type + +EEmoticonActionPrivate +
+ +
+e-emoticon-chooser +EEmoticonChooser +EEmoticonChooser +EEmoticonChooserInterface +e_emoticon_chooser_get_current_emoticon +e_emoticon_chooser_set_current_emoticon +e_emoticon_chooser_item_activated +e_emoticon_chooser_get_items +e_emoticon_chooser_lookup_emoticon + +E_EMOTICON_CHOOSER +E_IS_EMOTICON_CHOOSER +E_TYPE_EMOTICON_CHOOSER +E_EMOTICON_CHOOSER_GET_INTERFACE +e_emoticon_chooser_get_type +
+ +
+e-emoticon-chooser-menu +EEmoticonChooserMenu +EEmoticonChooserMenu +e_emoticon_chooser_menu_new + +E_EMOTICON_CHOOSER_MENU +E_IS_EMOTICON_CHOOSER_MENU +E_TYPE_EMOTICON_CHOOSER_MENU +E_EMOTICON_CHOOSER_MENU_CLASS +E_IS_EMOTICON_CHOOSER_MENU_CLASS +E_EMOTICON_CHOOSER_MENU_GET_CLASS +EEmoticonChooserMenuClass +e_emoticon_chooser_menu_get_type + +EEmoticonChooserMenuPrivate +
+ +
+e-emoticon-tool-button +EEmoticonToolButton +EEmoticonToolButton +e_emoticon_tool_button_new +e_emoticon_tool_button_popup +e_emoticon_tool_button_popdown + +E_EMOTICON_TOOL_BUTTON +E_IS_EMOTICON_TOOL_BUTTON +E_TYPE_EMOTICON_TOOL_BUTTON +E_EMOTICON_TOOL_BUTTON_CLASS +E_IS_EMOTICON_TOOL_BUTTON_CLASS +E_EMOTICON_TOOL_BUTTON_GET_CLASS +EEmoticonToolButtonClass +e_emoticon_tool_button_get_type + +EEmoticonToolButtonPrivate +
+
e-event EEvent @@ -1907,6 +2840,25 @@ e_image_chooser_get_type EImageChooserPrivate
+
+e-image-chooser-dialog +EImageChooserDialog +EImageChooserDialog +e_image_chooser_dialog_new +e_image_chooser_dialog_run + +E_IMAGE_CHOOSER_DIALOG +E_IS_IMAGE_CHOOSER_DIALOG +E_TYPE_IMAGE_CHOOSER_DIALOG +E_IMAGE_CHOOSER_DIALOG_CLASS +E_IS_IMAGE_CHOOSER_DIALOG_CLASS +E_IMAGE_CHOOSER_DIALOG_GET_CLASS +EImageChooserDialogClass +e_image_chooser_dialog_get_type + +EImageChooserDialogPrivate +
+
e-import EImport @@ -2055,6 +3007,7 @@ EMailSignatureComboBoxPrivate EMailSignatureEditor EMailSignatureEditor e_mail_signature_editor_new +e_mail_signature_editor_get_editor e_mail_signature_editor_get_focus_tracker e_mail_signature_editor_get_registry e_mail_signature_editor_get_source @@ -3391,14 +4344,72 @@ e_source_util_write e_source_util_remote_delete
+
+e-spell-checker +ESpellChecker +ESpellChecker +e_spell_checker_new +e_spell_checker_list_available_dicts +e_spell_checker_ref_dictionary +e_spell_checker_get_enchant_dict +e_spell_checker_get_language_active +e_spell_checker_set_language_active +e_spell_checker_list_active_languages +e_spell_checker_count_active_languages +e_spell_checker_check_word +e_spell_checker_learn_word +e_spell_checker_ignore_word + +E_SPELL_CHECKER +E_IS_SPELL_CHECKER +E_TYPE_SPELL_CHECKER +E_SPELL_CHECKER_CLASS +E_IS_SPELL_CHECKER_CLASS +E_SPELL_CHECKER_GET_CLASS +ESpellCheckerClass +e_spell_checker_get_type + +ESpellCheckerPrivate +
+ +
+e-spell-dictionary +ESpellDictionary +ESpellDictionary +e_spell_dictionary_new +e_spell_dictionary_hash +e_spell_dictionary_equal +e_spell_dictionary_compare +e_spell_dictionary_get_name +e_spell_dictionary_get_code +e_spell_dictionary_ref_spell_checker +e_spell_dictionary_check_word +e_spell_dictionary_learn_word +e_spell_dictionary_ignore_word +e_spell_dictionary_get_suggestions +e_spell_dictionary_store_correction + +E_SPELL_DICTIONARY +E_IS_SPELL_DICTIONARY +E_TYPE_SPELL_DICTIONARY +E_SPELL_DICTIONARY_CLASS +E_IS_SPELL_DICTIONARY_CLASS +E_SPELL_DICTIONARY_GET_CLASS +ESpellDictionaryClass +e_spell_dictionary_get_type + +ESpellDictionaryPrivate +
+
e-spell-entry ESpellEntry ESpellEntry e_spell_entry_new -e_spell_entry_set_languages e_spell_entry_get_checking_enabled e_spell_entry_set_checking_enabled +e_spell_entry_get_spell_checker +e_spell_entry_set_spell_checker E_SPELL_ENTRY E_IS_SPELL_ENTRY @@ -4567,74 +5578,6 @@ e_web_view_get_type EWebViewPrivate
-
-e-web-view-gtkhtml -EWebViewGtkHTML -EWebViewGtkHTML -e_web_view_gtkhtml_new -e_web_view_gtkhtml_clear -e_web_view_gtkhtml_load_string -e_web_view_gtkhtml_get_animate -e_web_view_gtkhtml_set_animate -e_web_view_gtkhtml_get_caret_mode -e_web_view_gtkhtml_set_caret_mode -e_web_view_gtkhtml_get_copy_target_list -e_web_view_gtkhtml_get_disable_printing -e_web_view_gtkhtml_set_disable_printing -e_web_view_gtkhtml_get_disable_save_to_disk -e_web_view_gtkhtml_set_disable_save_to_disk -e_web_view_gtkhtml_get_editable -e_web_view_gtkhtml_set_editable -e_web_view_gtkhtml_get_inline_spelling -e_web_view_gtkhtml_set_inline_spelling -e_web_view_gtkhtml_get_magic_links -e_web_view_gtkhtml_set_magic_links -e_web_view_gtkhtml_get_magic_smileys -e_web_view_gtkhtml_set_magic_smileys -e_web_view_gtkhtml_get_selected_uri -e_web_view_gtkhtml_set_selected_uri -e_web_view_gtkhtml_get_cursor_image -e_web_view_gtkhtml_set_cursor_image -e_web_view_gtkhtml_get_open_proxy -e_web_view_gtkhtml_set_open_proxy -e_web_view_gtkhtml_get_paste_target_list -e_web_view_gtkhtml_get_print_proxy -e_web_view_gtkhtml_set_print_proxy -e_web_view_gtkhtml_get_save_as_proxy -e_web_view_gtkhtml_set_save_as_proxy -e_web_view_gtkhtml_get_action -e_web_view_gtkhtml_get_action_group -e_web_view_gtkhtml_extract_uri -e_web_view_gtkhtml_copy_clipboard -e_web_view_gtkhtml_cut_clipboard -e_web_view_gtkhtml_is_selection_active -e_web_view_gtkhtml_paste_clipboard -e_web_view_gtkhtml_scroll_forward -e_web_view_gtkhtml_scroll_backward -e_web_view_gtkhtml_select_all -e_web_view_gtkhtml_unselect_all -e_web_view_gtkhtml_zoom_100 -e_web_view_gtkhtml_zoom_in -e_web_view_gtkhtml_zoom_out -e_web_view_gtkhtml_get_ui_manager -e_web_view_gtkhtml_get_popup_menu -e_web_view_gtkhtml_show_popup_menu -e_web_view_gtkhtml_status_message -e_web_view_gtkhtml_stop_loading -e_web_view_gtkhtml_update_actions - -E_WEB_VIEW_GTKHTML -E_IS_WEB_VIEW_GTKHTML -E_TYPE_WEB_VIEW_GTKHTML -E_WEB_VIEW_GTKHTML_CLASS -E_IS_WEB_VIEW_GTKHTML_CLASS -E_WEB_VIEW_GTKHTML_GET_CLASS -EWebViewGtkHTMLClass -e_web_view_gtkhtml_get_type - -EWebViewGtkHTMLPrivate -
-
e-web-view-preview EWebViewPreview diff --git a/doc/reference/evolution-util/evolution-util.types b/doc/reference/evolution-util/evolution-util.types index c47d820386..812856735e 100644 --- a/doc/reference/evolution-util/evolution-util.types +++ b/doc/reference/evolution-util/evolution-util.types @@ -55,12 +55,34 @@ e_charset_combo_box_get_type e_client_cache_get_type e_client_combo_box_get_type e_client_selector_get_type +e_color_chooser_widget_get_type +e_color_combo_get_type e_config_get_type e_config_hook_get_type e_contact_store_get_type e_data_capture_get_type e_date_edit_get_type e_destination_store_get_type +e_html_editor_get_type +e_html_editor_cell_dialog_get_type +e_html_editor_dialog_get_type +e_html_editor_find_dialog_get_type +e_html_editor_hrule_dialog_get_type +e_html_editor_image_dialog_get_type +e_html_editor_link_dialog_get_type +e_html_editor_page_dialog_get_type +e_html_editor_paragraph_dialog_get_type +e_html_editor_replace_dialog_get_type +e_html_editor_selection_get_type +e_html_editor_spell_check_dialog_get_type +e_html_editor_table_dialog_get_type +e_html_editor_text_dialog_get_type +e_html_editor_view_get_type +e_emoticon_action_get_type +e_emoticon_chooser_get_type +e_emoticon_chooser_menu_get_type +e_emoticon_get_type +e_emoticon_tool_button_get_type e_event_get_type e_event_hook_get_type e_file_request_get_type @@ -75,6 +97,7 @@ e_filter_option_get_type e_filter_part_get_type e_filter_rule_get_type e_focus_tracker_get_type +e_image_chooser_dialog_get_type e_image_chooser_get_type e_import_assistant_get_type e_import_get_type @@ -123,6 +146,8 @@ e_source_config_dialog_get_type e_source_config_get_type e_source_selector_dialog_get_type e_source_selector_get_type +e_spell_checker_get_type +e_spell_dictionary_get_type e_spell_entry_get_type e_stock_request_get_type e_table_click_to_add_get_type @@ -163,7 +188,6 @@ e_tree_table_adapter_get_type e_tree_view_frame_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_view_collection_get_type gal_view_etable_get_type diff --git a/e-util/Makefile.am b/e-util/Makefile.am index 66cdc220e4..6d9499db27 100644 --- a/e-util/Makefile.am +++ b/e-util/Makefile.am @@ -40,6 +40,7 @@ errordir = $(privdatadir)/errors @EVO_PLUGIN_RULE@ ui_DATA = \ + e-html-editor-manager.ui \ e-send-options.ui \ e-table-config.ui \ e-timezone-dialog.ui \ @@ -60,6 +61,7 @@ noinst_PROGRAMS = \ test-category-completion \ test-contact-store \ test-dateedit \ + test-html-editor \ test-mail-signatures \ test-name-selector \ test-preferences-window \ @@ -99,7 +101,7 @@ libevolution_util_la_CPPFLAGS = \ $(EVOLUTION_DATA_SERVER_CFLAGS) \ $(GNOME_PLATFORM_CFLAGS) \ $(GEO_CFLAGS) \ - $(GTKHTML_CFLAGS) \ + $(ENCHANT_CFLAGS) \ $(GTKSPELL_CFLAGS) \ $(CODE_COVERAGE_CFLAGS) \ $(NULL) @@ -165,6 +167,8 @@ evolution_util_include_HEADERS = \ e-client-cache.h \ e-client-combo-box.h \ e-client-selector.h \ + e-color-chooser-widget.h \ + e-color-combo.h \ e-config.h \ e-contact-store.h \ e-data-capture.h \ @@ -173,6 +177,11 @@ evolution_util_include_HEADERS = \ e-destination-store.h \ e-dialog-utils.h \ e-dialog-widgets.h \ + e-emoticon-action.h \ + e-emoticon-chooser-menu.h \ + e-emoticon-chooser.h \ + e-emoticon-tool-button.h \ + e-emoticon.h \ e-event.h \ e-file-request.h \ e-file-utils.h \ @@ -187,9 +196,27 @@ evolution_util_include_HEADERS = \ e-filter-part.h \ e-filter-rule.h \ e-focus-tracker.h \ + e-html-editor-actions.h \ + e-html-editor-cell-dialog.h \ + e-html-editor-dialog.h \ + e-html-editor-find-dialog.h \ + e-html-editor-hrule-dialog.h \ + e-html-editor-image-dialog.h \ + e-html-editor-link-dialog.h \ + e-html-editor-page-dialog.h \ + e-html-editor-paragraph-dialog.h \ + e-html-editor-replace-dialog.h \ + e-html-editor-selection.h \ + e-html-editor-spell-check-dialog.h \ + e-html-editor-table-dialog.h \ + e-html-editor-text-dialog.h \ + e-html-editor-utils.h \ + e-html-editor-view.h \ + e-html-editor.h \ e-html-utils.h \ e-icon-factory.h \ e-image-chooser.h \ + e-image-chooser-dialog.h \ e-import-assistant.h \ e-import.h \ e-interval-chooser.h \ @@ -252,6 +279,8 @@ evolution_util_include_HEADERS = \ e-source-selector-dialog.h \ e-source-selector.h \ e-source-util.h \ + e-spell-checker.h \ + e-spell-dictionary.h \ e-spell-entry.h \ e-spell-text-view.h \ e-stock-request.h \ @@ -305,7 +334,6 @@ evolution_util_include_HEADERS = \ 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-widget-undo.h \ @@ -410,6 +438,8 @@ libevolution_util_la_SOURCES = \ e-client-cache.c \ e-client-combo-box.c \ e-client-selector.c \ + e-color-chooser-widget.c \ + e-color-combo.c \ e-config.c \ e-contact-store.c \ e-data-capture.c \ @@ -418,6 +448,11 @@ libevolution_util_la_SOURCES = \ e-destination-store.c \ e-dialog-utils.c \ e-dialog-widgets.c \ + e-emoticon-action.c \ + e-emoticon-chooser-menu.c \ + e-emoticon-chooser.c \ + e-emoticon-tool-button.c \ + e-emoticon.c \ e-event.c \ e-file-request.c \ e-file-utils.c \ @@ -432,9 +467,28 @@ libevolution_util_la_SOURCES = \ e-filter-part.c \ e-filter-rule.c \ e-focus-tracker.c \ + e-html-editor-actions.c \ + e-html-editor-cell-dialog.c \ + e-html-editor-dialog.c \ + e-html-editor-find-dialog.c \ + e-html-editor-hrule-dialog.c \ + e-html-editor-image-dialog.c \ + e-html-editor-link-dialog.c \ + e-html-editor-page-dialog.c \ + e-html-editor-paragraph-dialog.c \ + e-html-editor-private.h \ + e-html-editor-replace-dialog.c \ + e-html-editor-selection.c \ + e-html-editor-spell-check-dialog.c \ + e-html-editor-table-dialog.c \ + e-html-editor-text-dialog.c \ + e-html-editor-utils.c \ + e-html-editor-view.c \ + e-html-editor.c \ e-html-utils.c \ e-icon-factory.c \ e-image-chooser.c \ + e-image-chooser-dialog.c \ e-import-assistant.c \ e-import.c \ e-interval-chooser.c \ @@ -497,6 +551,8 @@ libevolution_util_la_SOURCES = \ e-source-selector-dialog.c \ e-source-selector.c \ e-source-util.c \ + e-spell-checker.c \ + e-spell-dictionary.c \ e-spell-entry.c \ e-spell-text-view.c \ e-stock-request.c \ @@ -547,7 +603,6 @@ libevolution_util_la_SOURCES = \ 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-widget-undo.c \ @@ -590,7 +645,7 @@ libevolution_util_la_LIBADD = \ $(EVOLUTION_DATA_SERVER_LIBS) \ $(GNOME_PLATFORM_LIBS) \ $(GEO_LIBS) \ - $(GTKHTML_LIBS) \ + $(ENCHANT_LIBS) \ $(GTKSPELL_LIBS) \ $(INTLLIBS) \ $(MATH_LIB) \ @@ -625,6 +680,10 @@ test_dateedit_CPPFLAGS = $(TEST_CPPFLAGS) test_dateedit_SOURCES = test-dateedit.c test_dateedit_LDADD = $(TEST_LDADD) +test_html_editor_CPPFLAGS = $(TEST_CPPFLAGS) +test_html_editor_SOURCES = test-html-editor.c +test_html_editor_LDADD = $(TEST_LDADD) + test_mail_signatures_CPPFLAGS = $(TEST_CPPFLAGS) test_mail_signatures_SOURCES = test-mail-signatures.c test_mail_signatures_LDADD = $(TEST_LDADD) diff --git a/e-util/e-action-combo-box.c b/e-util/e-action-combo-box.c index 1b784b8ee8..33d678ab1e 100644 --- a/e-util/e-action-combo-box.c +++ b/e-util/e-action-combo-box.c @@ -104,10 +104,6 @@ action_combo_box_render_pixbuf (GtkCellLayout *layout, gboolean visible; gint width; - /* Do any of the actions have an icon? */ - if (!combo_box->priv->group_has_icons) - return; - gtk_tree_model_get (model, iter, COLUMN_ACTION, &action, -1); /* A NULL action means the row is a separator. */ @@ -122,8 +118,12 @@ action_combo_box_render_pixbuf (GtkCellLayout *layout, "visible", &visible, NULL); - /* Keep the pixbuf renderer a fixed size for proper alignment. */ - gtk_icon_size_lookup (GTK_ICON_SIZE_MENU, &width, NULL); + /* If some action has an icon */ + if (combo_box->priv->group_has_icons) + /* Keep the pixbuf renderer a fixed size for proper alignment. */ + gtk_icon_size_lookup (GTK_ICON_SIZE_MENU, &width, NULL); + else + width = 0; /* 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 @@ -245,13 +245,25 @@ action_combo_box_update_model (EActionComboBox *combo_box) GtkRadioAction *action = list->data; GtkTreePath *path; GtkTreeIter iter; - gchar *icon_name; - gchar *stock_id; + gchar *icon_name = NULL; + gchar *stock_id = NULL; + gboolean visible = FALSE; gint value; - g_object_get ( - action, "icon-name", &icon_name, - "stock-id", &stock_id, NULL); + g_object_get (action, + "icon-name", &icon_name, + "stock-id", &stock_id, + "visible", &visible, + NULL); + + if (!visible) { + g_free (icon_name); + g_free (stock_id); + + list = g_slist_next (list); + continue; + } + combo_box->priv->group_has_icons |= (icon_name != NULL || stock_id != NULL); g_free (icon_name); @@ -583,3 +595,11 @@ e_action_combo_box_add_separator_after (EActionComboBox *combo_box, GTK_LIST_STORE (model), &iter, COLUMN_ACTION, NULL, COLUMN_SORT, (gfloat) action_value + 0.5, -1); } + +void +e_action_combo_box_update_model (EActionComboBox *combo_box) +{ + g_return_if_fail (E_IS_ACTION_COMBO_BOX (combo_box)); + + action_combo_box_update_model (combo_box); +} diff --git a/e-util/e-action-combo-box.h b/e-util/e-action-combo-box.h index f3a5a0b658..3a44ed730a 100644 --- a/e-util/e-action-combo-box.h +++ b/e-util/e-action-combo-box.h @@ -81,6 +81,7 @@ void e_action_combo_box_add_separator_before void e_action_combo_box_add_separator_after (EActionComboBox *combo_box, gint action_value); +void e_action_combo_box_update_model (EActionComboBox *combo_box); G_END_DECLS diff --git a/e-util/e-color-chooser-widget.c b/e-util/e-color-chooser-widget.c new file mode 100644 index 0000000000..5761ebf2ff --- /dev/null +++ b/e-util/e-color-chooser-widget.c @@ -0,0 +1,253 @@ + +/* e-color-chooser-widget.c + * + * Copyright (C) 2012 Dan Vrátil + * + * 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-color-chooser-widget.h" + +#include + +#define E_COLOR_CHOOSER_WIDGET_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE \ + ((obj), E_TYPE_COLOR_CHOOSER_WIDGET, EColorChooserWidgetPrivate)) + +/** + * EColorChooserWidget: + * + * This widget wrapps around #GtkColorChooserWidget and allows the widget to be + * used as a delegate for #GtkComboBox for instance. + */ + +struct _EColorChooserWidgetPrivate { + gboolean showing_editor; +}; + +enum { + SIGNAL_EDITOR_ACTIVATED, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL]; + +G_DEFINE_TYPE ( + EColorChooserWidget, + e_color_chooser_widget, + GTK_TYPE_COLOR_CHOOSER_WIDGET); + +/* UGLY UGLY UGLY! + * GtkColorChooserWidget sends "color-activated" signal + * only when user double-clicks the color. This is totally stupid + * and since we want to use it in a combobox-like widget, we need + * to be notified upon single click (which by default only selects the color). + * + * Unfortunatelly the GtkColorSwatch widget, which handles the button-press + * event is a non-public widget embedded within the GtkColorChooserWidget, + * so we can't just subclass it and fix the behavior. + * + * Here we override button_press_event of the GtkColorSwatch and manually + * emit the 'activate' signal on single click. This is stupid, ugly and I + * want to punch someone for such a stupid design... + */ +static gboolean +color_chooser_widget_button_press_event (GtkWidget *widget, + GdkEventButton *event) +{ + if ((event->type == GDK_BUTTON_PRESS) && + (event->button == GDK_BUTTON_PRIMARY)) { + + g_signal_emit_by_name (widget, "activate"); + + return TRUE; + } + + return FALSE; +} + +static void +color_chooser_widget_color_activated (GtkColorChooser *chooser, + GdkRGBA *color, + gpointer user_data) +{ + /* Because we are simulating the double-click by accepting only + * single click, the color in the swatch is actually not selected, + * so we must do it manually */ + gtk_color_chooser_set_rgba (chooser, color); +} + +static gboolean +run_color_chooser_dialog (gpointer user_data) +{ + EColorChooserWidgetPrivate *priv; + GtkWidget *parent_window; + GtkWidget *parent_chooser; + GtkWidget *dialog; + GtkWidget *chooser; + + parent_chooser = user_data; + + g_object_set ( + G_OBJECT (parent_chooser), "show-editor", FALSE, NULL); + + parent_window = g_object_get_data (G_OBJECT (parent_chooser), "window"); + if (!parent_window) + parent_window = gtk_widget_get_toplevel (parent_chooser); + dialog = gtk_dialog_new_with_buttons ( + N_("Choose custom color"), + GTK_WINDOW (parent_window), + GTK_DIALOG_MODAL, + GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, + GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, NULL); + + chooser = gtk_color_chooser_widget_new (); + g_object_set (G_OBJECT (chooser), "show-editor", TRUE, NULL); + gtk_box_pack_start ( + GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), + chooser, TRUE, TRUE, 5); + + gtk_widget_show_all (chooser); + + if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT) { + GdkRGBA color; + + gtk_color_chooser_get_rgba (GTK_COLOR_CHOOSER (chooser), &color); + gtk_color_chooser_set_rgba (GTK_COLOR_CHOOSER (parent_chooser), &color); + + g_signal_emit_by_name (parent_chooser, "color-activated", &color); + } + + gtk_widget_destroy (dialog); + + priv = E_COLOR_CHOOSER_WIDGET_GET_PRIVATE (parent_chooser); + priv->showing_editor = FALSE; + + return FALSE; +} + +static void +color_chooser_show_editor_notify_cb (EColorChooserWidget *chooser, + GParamSpec *pspec, + gpointer user_data) +{ + gboolean show_editor; + + g_object_get (G_OBJECT (chooser), "show-editor", &show_editor, NULL); + + /* Nothing to do here... */ + if ((show_editor == FALSE) || (chooser->priv->showing_editor == TRUE)) + return; + + chooser->priv->showing_editor = TRUE; + + /* Hide the editor - we don't want to display the single-color editor + * within this widget. We rather create a dialog window with the editor + * (we can't do it from this callback as Gtk would stop it in order to + * prevent endless recursion probably) */ + g_idle_add (run_color_chooser_dialog, chooser); + g_signal_emit (chooser, signals[SIGNAL_EDITOR_ACTIVATED], 0); +} + +void +e_color_chooser_widget_class_init (EColorChooserWidgetClass *class) +{ + g_type_class_add_private (class, sizeof (EColorChooserWidgetPrivate)); + + signals[SIGNAL_EDITOR_ACTIVATED] = g_signal_new ( + "editor-activated", + E_TYPE_COLOR_CHOOSER_WIDGET, + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (EColorChooserWidgetClass, editor_activated), + NULL, + NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); +} + +/* Recursively go through GtkContainers within the GtkColorChooserWidget + * and try to find GtkColorSwatch widget. */ +static GtkWidget * +find_swatch (GtkContainer *container) +{ + GList *children, *child; + + children = gtk_container_get_children (container); + for (child = children; child; child = g_list_next (child)) { + GtkWidget *widget = child->data; + GtkWidget *swatch; + + if (GTK_IS_CONTAINER (widget)) { + swatch = find_swatch (GTK_CONTAINER (widget)); + + if (swatch != NULL) { + g_list_free (children); + return swatch; + } + } + + if (g_strcmp0 (G_OBJECT_TYPE_NAME (widget), "GtkColorSwatch") == 0) { + g_list_free (children); + return widget; + } + } + + g_list_free (children); + + return NULL; +} + +void +e_color_chooser_widget_init (EColorChooserWidget *widget) +{ + GtkWidget *swatch; + + widget->priv = E_COLOR_CHOOSER_WIDGET_GET_PRIVATE (widget); + widget->priv->showing_editor = FALSE; + + swatch = find_swatch (GTK_CONTAINER (widget)); + + /* If swatch is NULL then GTK changed something and this widget + * becomes broken... */ + g_return_if_fail (swatch != NULL); + + if (swatch) { + GtkWidgetClass *swatch_class; + swatch_class = GTK_WIDGET_GET_CLASS (swatch); + swatch_class->button_press_event = color_chooser_widget_button_press_event; + } + + g_signal_connect ( + widget, "color-activated", + G_CALLBACK (color_chooser_widget_color_activated), NULL); + + g_signal_connect ( + widget, "notify::show-editor", + G_CALLBACK (color_chooser_show_editor_notify_cb), NULL); +} + +GtkWidget * +e_color_chooser_widget_new (void) +{ + return g_object_new ( + E_TYPE_COLOR_CHOOSER_WIDGET, + "show-editor", FALSE, + "use-alpha", FALSE, + NULL); +} diff --git a/e-util/e-color-chooser-widget.h b/e-util/e-color-chooser-widget.h new file mode 100644 index 0000000000..3b7c60de7f --- /dev/null +++ b/e-util/e-color-chooser-widget.h @@ -0,0 +1,71 @@ +/* e-color-chooser-widget.h + * + * Copyright (C) 2012 Dan Vrátil + * + * 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_COLOR_CHOOSER_WIDGET_H +#define E_COLOR_CHOOSER_WIDGET_H + +#include + +/* Standard GObject macros */ +#define E_TYPE_COLOR_CHOOSER_WIDGET \ + (e_color_chooser_widget_get_type ()) +#define E_COLOR_CHOOSER_WIDGET(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST \ + ((obj), E_TYPE_COLOR_CHOOSER_WIDGET, EColorChooserWidget)) +#define E_COLOR_CHOOSER_WIDGET_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_CAST \ + ((cls), E_TYPE_COLOR_CHOOSER_WIDGET, EColorChooserWidgetClass)) +#define E_IS_COLOR_CHOOSER_WIDGET(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE \ + ((obj), E_TYPE_COLOR_CHOOSER_WIDGET)) +#define E_IS_COLOR_CHOOSER_WIDGET_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_TYPE \ + ((cls), E_TYPE_COLOR_CHOOSER_WIDGET)) +#define E_COLOR_CHOOSER_WIDGET_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS \ + ((obj), E_TYPE_COLOR_CHOOSER_WIDGET, EColorChooserWidgetClass)) + +G_BEGIN_DECLS + +typedef struct _EColorChooserWidget EColorChooserWidget; +typedef struct _EColorChooserWidgetClass EColorChooserWidgetClass; +typedef struct _EColorChooserWidgetPrivate EColorChooserWidgetPrivate; + +struct _EColorChooserWidget { + GtkColorChooserWidget parent; + EColorChooserWidgetPrivate *priv; +}; + +struct _EColorChooserWidgetClass { + GtkColorChooserWidgetClass parent_class; + + void (*editor_activated) (GtkColorChooserWidget *chooser); +}; + +GType e_color_chooser_widget_get_type (void) G_GNUC_CONST; +GtkWidget * e_color_chooser_widget_new (void); + +G_END_DECLS + +#endif /* E_COLOR_CHOOSER_WIDGET_H */ + diff --git a/e-util/e-color-combo.c b/e-util/e-color-combo.c new file mode 100644 index 0000000000..f2f46e4bec --- /dev/null +++ b/e-util/e-color-combo.c @@ -0,0 +1,976 @@ +/* e-color-combo.c + * + * Copyright (C) 2012 Dan Vrátil + * + * 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-color-combo.h" +#include "e-color-chooser-widget.h" + +#include +#include +#include +#include + +#define E_COLOR_COMBO_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE \ + ((obj), E_TYPE_COLOR_COMBO, EColorComboPrivate)) + +struct _EColorComboPrivate { + GtkWidget *color_frame; /* not referenced */ + GtkWidget *arrow; /* not referenced */ + + GtkWidget *window; + GtkWidget *default_button; /* not referenced */ + GtkWidget *chooser_widget; /* not referenced */ + + guint popup_shown : 1; + guint popup_in_progress : 1; + + GdkRGBA *current_color; + GdkRGBA *default_color; + gint default_transparent: 1; + + GList *palette; + + GdkDevice *grab_keyboard; + GdkDevice *grab_mouse; +}; + +enum { + PROP_0, + PROP_CURRENT_COLOR, + PROP_DEFAULT_COLOR, + PROP_DEFAULT_LABEL, + PROP_DEFAULT_TRANSPARENT, + PROP_PALETTE, + PROP_POPUP_SHOWN +}; + +enum { + ACTIVATED, + POPUP, + POPDOWN, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL]; +static GdkRGBA black = { 0, 0, 0, 1 }; + +static struct { + const gchar *color; + const gchar *tooltip; +} default_colors[] = { + + { "#000000", N_("black") }, + { "#993300", N_("light brown") }, + { "#333300", N_("brown gold") }, + { "#003300", N_("dark green #2") }, + { "#003366", N_("navy") }, + { "#000080", N_("dark blue") }, + { "#333399", N_("purple #2") }, + { "#333333", N_("very dark gray") }, + + { "#800000", N_("dark red") }, + { "#FF6600", N_("red-orange") }, + { "#808000", N_("gold") }, + { "#008000", N_("dark green") }, + { "#008080", N_("dull blue") }, + { "#0000FF", N_("blue") }, + { "#666699", N_("dull purple") }, + { "#808080", N_("dark grey") }, + + { "#FF0000", N_("red") }, + { "#FF9900", N_("orange") }, + { "#99CC00", N_("lime") }, + { "#339966", N_("dull green") }, + { "#33CCCC", N_("dull blue #2") }, + { "#3366FF", N_("sky blue #2") }, + { "#800080", N_("purple") }, + { "#969696", N_("gray") }, + + { "#FF00FF", N_("magenta") }, + { "#FFCC00", N_("bright orange") }, + { "#FFFF00", N_("yellow") }, + { "#00FF00", N_("green") }, + { "#00FFFF", N_("cyan") }, + { "#00CCFF", N_("bright blue") }, + { "#993366", N_("red purple") }, + { "#C0C0C0", N_("light grey") }, + + { "#FF99CC", N_("pink") }, + { "#FFCC99", N_("light orange") }, + { "#FFFF99", N_("light yellow") }, + { "#CCFFCC", N_("light green") }, + { "#CCFFFF", N_("light cyan") }, + { "#99CCFF", N_("light blue") }, + { "#CC99FF", N_("light purple") }, + { "#FFFFFF", N_("white") } +}; + +G_DEFINE_TYPE ( + EColorCombo, + e_color_combo, + GTK_TYPE_BUTTON); + +static void +color_combo_reposition_window (EColorCombo *combo) +{ + GdkScreen *screen; + GdkWindow *window; + GdkRectangle monitor; + GtkAllocation allocation; + gint monitor_num; + gint x, y, width, height; + + screen = gtk_widget_get_screen (GTK_WIDGET (combo)); + window = gtk_widget_get_window (GTK_WIDGET (combo)); + monitor_num = gdk_screen_get_monitor_at_window (screen, window); + gdk_screen_get_monitor_geometry (screen, monitor_num, &monitor); + + gdk_window_get_origin (window, &x, &y); + + if (!gtk_widget_get_has_window (GTK_WIDGET (combo))) { + gtk_widget_get_allocation (GTK_WIDGET (combo), &allocation); + x += allocation.x; + y += allocation.y; + } + + gtk_widget_get_allocation (combo->priv->window, &allocation); + width = allocation.width; + height = allocation.height; + + x = CLAMP (x, monitor.x, monitor.x + monitor.width - width); + y = CLAMP (y, monitor.y, monitor.y + monitor.height - height); + + gtk_window_move (GTK_WINDOW (combo->priv->window), x, y); +} + +static void +color_combo_popup (EColorCombo *combo) +{ + GdkWindow *window; + gboolean grab_status; + GdkDevice *device, *mouse, *keyboard; + guint32 activate_time; + + device = gtk_get_current_event_device (); + g_return_if_fail (device != NULL); + + if (!gtk_widget_get_realized (GTK_WIDGET (combo))) + return; + + if (combo->priv->popup_shown) + return; + + activate_time = gtk_get_current_event_time (); + if (gdk_device_get_source (device) == GDK_SOURCE_KEYBOARD) { + keyboard = device; + mouse = gdk_device_get_associated_device (device); + } else { + keyboard = gdk_device_get_associated_device (device); + mouse = device; + } + + /* Position the window over the button. */ + color_combo_reposition_window (combo); + + /* Show the pop-up. */ + gtk_widget_show_all (combo->priv->window); + gtk_widget_grab_focus (combo->priv->window); + + /* Try to grab the pointer and keyboard. */ + window = gtk_widget_get_window (combo->priv->window); + grab_status = + (keyboard == NULL) || + (gdk_device_grab ( + keyboard, window, + GDK_OWNERSHIP_WINDOW, TRUE, + GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK, + NULL, activate_time) == GDK_GRAB_SUCCESS); + if (grab_status) { + grab_status = + (mouse == NULL) || + (gdk_device_grab ( + mouse, window, + GDK_OWNERSHIP_WINDOW, TRUE, + GDK_BUTTON_PRESS_MASK | + GDK_BUTTON_RELEASE_MASK | + GDK_POINTER_MOTION_MASK, + NULL, activate_time) == GDK_GRAB_SUCCESS); + if (!grab_status && keyboard) + gdk_device_ungrab (keyboard, activate_time); + } + + if (grab_status) { + gtk_device_grab_add (combo->priv->window, mouse, TRUE); + combo->priv->grab_keyboard = keyboard; + combo->priv->grab_mouse = mouse; + } else { + gtk_widget_hide (combo->priv->window); + } + + /* Always make sure the editor-mode is OFF */ + g_object_set ( + G_OBJECT (combo->priv->chooser_widget), + "show-editor", FALSE, NULL); +} + +static void +color_combo_popdown (EColorCombo *combo) +{ + if (!gtk_widget_get_realized (GTK_WIDGET (combo))) + return; + + if (!combo->priv->popup_shown) + return; + + /* Hide the pop-up. */ + gtk_device_grab_remove (combo->priv->window, combo->priv->grab_mouse); + gtk_widget_hide (combo->priv->window); + + if (combo->priv->grab_keyboard) + gdk_device_ungrab (combo->priv->grab_keyboard, GDK_CURRENT_TIME); + if (combo->priv->grab_mouse) + gdk_device_ungrab (combo->priv->grab_mouse, GDK_CURRENT_TIME); + + combo->priv->grab_keyboard = NULL; + combo->priv->grab_mouse = NULL; +} + +static gboolean +color_combo_window_button_press_event_cb (EColorCombo *combo, + GdkEvent *event, + gpointer user_data) +{ + GtkWidget *event_widget; + + event_widget = gtk_get_event_widget ((GdkEvent *) event); + + if (event_widget == combo->priv->window) + return TRUE; + + if (combo->priv->popup_shown == TRUE) + return FALSE; + + color_combo_popup (combo); + + combo->priv->popup_in_progress = TRUE; + + return TRUE; +} + +static gboolean +color_combo_window_button_release_event_cb (EColorCombo *combo, + GdkEvent *event, + gpointer user_data) +{ + gboolean popup_in_progress; + + popup_in_progress = combo->priv->popup_in_progress; + combo->priv->popup_in_progress = FALSE; + + if (popup_in_progress) + return FALSE; + + if (combo->priv->popup_shown) + goto popdown; + + return FALSE; + +popdown: + color_combo_popdown (combo); + + return TRUE; +} + +static void +color_combo_child_show_cb (EColorCombo *combo) +{ + combo->priv->popup_shown = TRUE; + g_object_notify (G_OBJECT (combo), "popup-shown"); +} + +static void +color_combo_child_hide_cb (EColorCombo *combo) +{ + combo->priv->popup_shown = FALSE; + g_object_notify (G_OBJECT (combo), "popup-shown"); +} + +static void +color_combo_get_preferred_width (GtkWidget *widget, + gint *min_width, + gint *natural_width) +{ + GtkWidgetClass *widget_class; + + widget_class = GTK_WIDGET_CLASS (e_color_combo_parent_class); + widget_class->get_preferred_width (widget, min_width, natural_width); + + /* Make sure the box with color sample is always visible */ + if (min_width) + *min_width += 20; + + if (natural_width) + *natural_width += 20; +} + +static gboolean +color_combo_button_press_event_cb (GtkWidget *widget, + GdkEventButton *event) +{ + EColorCombo *combo = E_COLOR_COMBO (widget); + GdkWindow *window; + gint x, y, width, height; + + window = gtk_widget_get_window (combo->priv->color_frame); + gdk_window_get_position (window, &x, &y); + /* Width - only width of the frame with color box */ + width = gtk_widget_get_allocated_width (combo->priv->color_frame); + + /* Height - height of the entire button (widget) */ + height = gtk_widget_get_allocated_height (widget); + + /* Check whether user clicked on the color frame - in such case + * apply the color immediatelly without displaying the popup widget */ + if ((event->x_root >= x) && (event->x_root <= x + width) && + (event->y_root >= y) && (event->y_root <= y + height)) { + GdkRGBA color; + + e_color_combo_get_current_color (combo, &color); + g_signal_emit (combo, signals[ACTIVATED], 0, &color); + + return TRUE; + } + + /* Otherwise display the popup widget */ + if (combo->priv->popup_shown) { + color_combo_popdown (combo); + } else { + color_combo_popup (combo); + } + + return FALSE; +} + +static void +color_combo_swatch_color_changed (EColorCombo *combo, + GdkRGBA *color, + gpointer user_data) +{ + g_signal_emit (combo, signals[ACTIVATED], 0, color); + + e_color_combo_set_current_color (combo, color); + + color_combo_popdown (combo); +} + +static void +color_combo_draw_frame_cb (GtkWidget *widget, + cairo_t *cr, + gpointer user_data) +{ + EColorCombo *combo = user_data; + GdkRGBA rgba; + GtkAllocation allocation; + gint height, width; + + e_color_combo_get_current_color (combo, &rgba); + + gtk_widget_get_allocation (widget, &allocation); + width = allocation.width; + height = allocation.height; + + cairo_rectangle (cr, 0, 0, width - 10, height); + cairo_set_source_rgb (cr, rgba.red, rgba.green, rgba.blue); + cairo_fill (cr); +} + +static void +color_combo_set_default_color_cb (EColorCombo *combo, + gpointer user_data) +{ + GdkRGBA color; + + e_color_combo_get_default_color (combo, &color); + e_color_combo_set_current_color (combo, &color); + + g_signal_emit (combo, signals[ACTIVATED], 0, &color); +} + +static void +color_combo_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + switch (property_id) { + case PROP_CURRENT_COLOR: + e_color_combo_set_current_color ( + E_COLOR_COMBO (object), + g_value_get_boxed (value)); + return; + + case PROP_DEFAULT_COLOR: + e_color_combo_set_default_color ( + E_COLOR_COMBO (object), + g_value_get_boxed (value)); + return; + + case PROP_DEFAULT_LABEL: + e_color_combo_set_default_label ( + E_COLOR_COMBO (object), + g_value_get_string (value)); + return; + + case PROP_DEFAULT_TRANSPARENT: + e_color_combo_set_default_transparent ( + E_COLOR_COMBO (object), + g_value_get_boolean (value)); + return; + + case PROP_PALETTE: + e_color_combo_set_palette ( + E_COLOR_COMBO (object), + g_value_get_object (value)); + return; + + case PROP_POPUP_SHOWN: + if (g_value_get_boolean (value)) + e_color_combo_popup ( + E_COLOR_COMBO (object)); + else + e_color_combo_popdown ( + E_COLOR_COMBO (object)); + return; + } + + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); +} + +static void +color_combo_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + EColorComboPrivate *priv; + GdkRGBA color; + + priv = E_COLOR_COMBO_GET_PRIVATE (object); + + switch (property_id) { + case PROP_CURRENT_COLOR: + e_color_combo_get_current_color ( + E_COLOR_COMBO (object), &color); + g_value_set_boxed (value, &color); + return; + + case PROP_DEFAULT_COLOR: + e_color_combo_get_default_color ( + E_COLOR_COMBO (object), &color); + g_value_set_boxed (value, &color); + return; + + case PROP_DEFAULT_LABEL: + g_value_set_string ( + value, e_color_combo_get_default_label ( + E_COLOR_COMBO (object))); + return; + + case PROP_DEFAULT_TRANSPARENT: + g_value_set_boolean ( + value, + e_color_combo_get_default_transparent ( + E_COLOR_COMBO (object))); + return; + + case PROP_PALETTE: + g_value_set_object ( + value, e_color_combo_get_palette ( + E_COLOR_COMBO (object))); + return; + + case PROP_POPUP_SHOWN: + g_value_set_boolean (value, priv->popup_shown); + return; + } + + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); +} + +static void +color_combo_dispose (GObject *object) +{ + EColorComboPrivate *priv; + + priv = E_COLOR_COMBO_GET_PRIVATE (object); + + if (priv->window != NULL) { + gtk_widget_destroy (priv->window); + priv->window = NULL; + } + + if (priv->current_color != NULL) { + gdk_rgba_free (priv->current_color); + priv->current_color = NULL; + } + + if (priv->default_color != NULL) { + gdk_rgba_free (priv->default_color); + priv->default_color = NULL; + } + + g_list_free_full (priv->palette, (GDestroyNotify) gdk_rgba_free); + priv->palette = NULL; + + /* Chain up to parent's dispose() method. */ + G_OBJECT_CLASS (e_color_combo_parent_class)->dispose (object); +} + +static void +e_color_combo_class_init (EColorComboClass *class) +{ + GObjectClass *object_class; + GtkWidgetClass *widget_class; + + g_type_class_add_private (class, sizeof (EColorComboPrivate)); + + object_class = G_OBJECT_CLASS (class); + object_class->set_property = color_combo_set_property; + object_class->get_property = color_combo_get_property; + object_class->dispose = color_combo_dispose; + + widget_class = GTK_WIDGET_CLASS (class); + widget_class->get_preferred_width = color_combo_get_preferred_width; + widget_class->button_press_event = color_combo_button_press_event_cb; + + class->popup = color_combo_popup; + class->popdown = color_combo_popdown; + + g_object_class_install_property ( + object_class, + PROP_CURRENT_COLOR, + g_param_spec_boxed ( + "current-color", + "Current color", + "The currently selected color", + GDK_TYPE_RGBA, + G_PARAM_READWRITE)); + + g_object_class_install_property ( + object_class, + PROP_DEFAULT_COLOR, + g_param_spec_boxed ( + "default-color", + "Default color", + "The color associated with the default button", + GDK_TYPE_RGBA, + G_PARAM_CONSTRUCT | + G_PARAM_READWRITE)); + + g_object_class_install_property ( + object_class, + PROP_DEFAULT_LABEL, + g_param_spec_string ( + "default-label", + "Default label", + "The label for the default button", + _("Default"), + G_PARAM_CONSTRUCT | + G_PARAM_READWRITE)); + + g_object_class_install_property ( + object_class, + PROP_DEFAULT_TRANSPARENT, + g_param_spec_boolean ( + "default-transparent", + "Default is transparent", + "Whether the default color is transparent", + FALSE, + G_PARAM_CONSTRUCT | + G_PARAM_READWRITE)); + + g_object_class_install_property ( + object_class, + PROP_PALETTE, + g_param_spec_pointer ( + "palette", + "Color palette", + "Custom color palette", + G_PARAM_READWRITE)); + + g_object_class_install_property ( + object_class, + PROP_POPUP_SHOWN, + g_param_spec_boolean ( + "popup-shown", + "Popup shown", + "Whether the combo's dropdown is shown", + FALSE, + G_PARAM_READWRITE)); + + signals[ACTIVATED] = g_signal_new ( + "activated", + G_OBJECT_CLASS_TYPE (class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (EColorComboClass, activated), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + signals[POPUP] = g_signal_new ( + "popup", + G_OBJECT_CLASS_TYPE (class), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (EColorComboClass, popup), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + signals[POPDOWN] = g_signal_new ( + "popdown", + G_OBJECT_CLASS_TYPE (class), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (EColorComboClass, popdown), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + gtk_binding_entry_add_signal ( + gtk_binding_set_by_class (class), + GDK_KEY_Down, GDK_MOD1_MASK, "popup", 0); + gtk_binding_entry_add_signal ( + gtk_binding_set_by_class (class), + GDK_KEY_KP_Down, GDK_MOD1_MASK, "popup", 0); + + gtk_binding_entry_add_signal ( + gtk_binding_set_by_class (class), + GDK_KEY_Up, GDK_MOD1_MASK, "popdown", 0); + gtk_binding_entry_add_signal ( + gtk_binding_set_by_class (class), + GDK_KEY_KP_Up, GDK_MOD1_MASK, "popdown", 0); + gtk_binding_entry_add_signal ( + gtk_binding_set_by_class (class), + GDK_KEY_Escape, 0, "popdown", 0); +} + +static void +e_color_combo_init (EColorCombo *combo) +{ + GtkWidget *container; + GtkWidget *toplevel; + GtkWidget *widget; + GList *palette; + guint ii; + + combo->priv = E_COLOR_COMBO_GET_PRIVATE (combo); + + widget = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + gtk_container_add (GTK_CONTAINER (combo), widget); + + container = widget; + + /* Build the combo button. */ + widget = gtk_frame_new (NULL); + gtk_box_pack_start (GTK_BOX (container), widget, TRUE, TRUE, 0); + g_signal_connect ( + widget, "draw", + G_CALLBACK (color_combo_draw_frame_cb), combo); + combo->priv->color_frame = widget; /* do not reference */ + + widget = gtk_separator_new (GTK_ORIENTATION_VERTICAL); + gtk_box_pack_start (GTK_BOX (container), widget, FALSE, TRUE, 0); + + widget = gtk_arrow_new (GTK_ARROW_DOWN, GTK_SHADOW_NONE); + gtk_box_pack_start (GTK_BOX (container), widget, FALSE, TRUE, 0); + combo->priv->arrow = widget; /* do not reference */ + + /* Build the drop-down menu */ + widget = gtk_window_new (GTK_WINDOW_POPUP); + gtk_container_set_border_width (GTK_CONTAINER (widget), 5); + gtk_window_set_resizable (GTK_WINDOW (widget), FALSE); + gtk_window_set_type_hint ( + GTK_WINDOW (widget), GDK_WINDOW_TYPE_HINT_COMBO); + combo->priv->window = g_object_ref_sink (widget); + + toplevel = gtk_widget_get_toplevel (GTK_WIDGET (combo)); + if (GTK_IS_WINDOW (toplevel)) { + gtk_window_group_add_window ( + gtk_window_get_group (GTK_WINDOW (toplevel)), + GTK_WINDOW (widget)); + gtk_window_set_transient_for ( + GTK_WINDOW (widget), GTK_WINDOW (toplevel)); + } + + g_signal_connect_swapped ( + widget, "show", + G_CALLBACK (color_combo_child_show_cb), combo); + g_signal_connect_swapped ( + widget, "hide", + G_CALLBACK (color_combo_child_hide_cb), combo); + g_signal_connect_swapped ( + widget, "button-press-event", + G_CALLBACK (color_combo_window_button_press_event_cb), combo); + g_signal_connect_swapped ( + widget, "button-release-event", + G_CALLBACK (color_combo_window_button_release_event_cb), combo); + + container = widget; + + widget = gtk_grid_new (); + gtk_grid_set_row_spacing (GTK_GRID (widget), 5); + gtk_container_add (GTK_CONTAINER (container), widget); + + container = widget; + + widget = gtk_button_new (); + gtk_grid_attach (GTK_GRID (container), widget, 0, 0, 1, 1); + combo->priv->default_button = widget; /* do not reference */ + + g_signal_connect_swapped ( + widget, "clicked", + G_CALLBACK (color_combo_set_default_color_cb), combo); + g_signal_connect_swapped ( + widget, "clicked", + G_CALLBACK (color_combo_popdown), combo); + + widget = e_color_chooser_widget_new (); + g_object_set_data (G_OBJECT (widget), "window", combo->priv->window); + gtk_grid_attach (GTK_GRID (container), widget, 0, 1, 1, 1); + combo->priv->chooser_widget = widget; /* do not reference */ + + g_signal_connect_swapped ( + widget, "color-activated", + G_CALLBACK (color_combo_swatch_color_changed), combo); + g_signal_connect_swapped ( + widget, "editor-activated", + G_CALLBACK (color_combo_popdown), combo); + + palette = NULL; + for (ii = 0; ii < G_N_ELEMENTS (default_colors); ii++) { + GdkRGBA *color = g_new0 (GdkRGBA, 1); + gdk_rgba_parse (color, default_colors[ii].color); + + palette = g_list_prepend (palette, color); + } + palette = g_list_reverse (palette); + e_color_combo_set_palette (combo, palette); + g_list_free_full (palette, (GDestroyNotify) g_free); + + combo->priv->current_color = gdk_rgba_copy (&black); + combo->priv->default_color = gdk_rgba_copy (&black); +} + +GtkWidget * +e_color_combo_new (void) +{ + return g_object_new (E_TYPE_COLOR_COMBO, NULL); +} + +GtkWidget * +e_color_combo_new_defaults (GdkRGBA *default_color, + const gchar *default_label) +{ + g_return_val_if_fail (default_color != NULL, NULL); + g_return_val_if_fail (default_label != NULL, NULL); + + return g_object_new ( + E_TYPE_COLOR_COMBO, + "default-color", default_color, + "default-label", default_label, + NULL); +} + +void +e_color_combo_popup (EColorCombo *combo) +{ + g_return_if_fail (E_IS_COLOR_COMBO (combo)); + + g_signal_emit (combo, signals[POPUP], 0); +} + +void +e_color_combo_popdown (EColorCombo *combo) +{ + g_return_if_fail (E_IS_COLOR_COMBO (combo)); + + g_signal_emit (combo, signals[POPDOWN], 0); +} + +void +e_color_combo_get_current_color (EColorCombo *combo, + GdkRGBA *color) +{ + g_return_if_fail (E_IS_COLOR_COMBO (combo)); + g_return_if_fail (color != NULL); + + color->red = combo->priv->current_color->red; + color->green = combo->priv->current_color->green; + color->blue = combo->priv->current_color->blue; + color->alpha = combo->priv->current_color->alpha; +} + +void +e_color_combo_set_current_color (EColorCombo *combo, + const GdkRGBA *color) +{ + g_return_if_fail (E_IS_COLOR_COMBO (combo)); + + if (color == NULL) + color = &black; + + if (combo->priv->current_color) { + + if (gdk_rgba_equal (color, combo->priv->current_color)) { + return; + } + + gdk_rgba_free (combo->priv->current_color); + } + + combo->priv->current_color = gdk_rgba_copy (color); + + gtk_color_chooser_set_rgba ( + GTK_COLOR_CHOOSER (combo->priv->chooser_widget), color); + gtk_widget_queue_draw (combo->priv->color_frame); + + g_object_notify (G_OBJECT (combo), "current-color"); +} + +void +e_color_combo_get_default_color (EColorCombo *combo, + GdkRGBA *color) +{ + g_return_if_fail (E_IS_COLOR_COMBO (combo)); + g_return_if_fail (color != NULL); + + color->red = combo->priv->default_color->red; + color->green = combo->priv->default_color->green; + color->blue = combo->priv->default_color->blue; + color->alpha = combo->priv->default_color->alpha; +} + +void +e_color_combo_set_default_color (EColorCombo *combo, + const GdkRGBA *color) +{ + g_return_if_fail (E_IS_COLOR_COMBO (combo)); + + if (color == NULL) + color = &black; + + if (combo->priv->default_color) { + + if (gdk_rgba_equal (color, combo->priv->default_color)) { + return; + } + + gdk_rgba_free (combo->priv->default_color); + } + combo->priv->default_color = gdk_rgba_copy (color); + + gtk_color_chooser_set_rgba ( + GTK_COLOR_CHOOSER (combo->priv->chooser_widget), color); + + g_object_notify (G_OBJECT (combo), "default-color"); +} + +const gchar * +e_color_combo_get_default_label (EColorCombo *combo) +{ + g_return_val_if_fail (E_IS_COLOR_COMBO (combo), NULL); + + return gtk_button_get_label (GTK_BUTTON (combo->priv->default_button)); +} + +void +e_color_combo_set_default_label (EColorCombo *combo, + const gchar *text) +{ + g_return_if_fail (E_IS_COLOR_COMBO (combo)); + + gtk_button_set_label (GTK_BUTTON (combo->priv->default_button), text); + + g_object_notify (G_OBJECT (combo), "default-label"); +} + +gboolean +e_color_combo_get_default_transparent (EColorCombo *combo) +{ + g_return_val_if_fail (E_IS_COLOR_COMBO (combo), FALSE); + + return combo->priv->default_transparent; +} + +void +e_color_combo_set_default_transparent (EColorCombo *combo, + gboolean transparent) +{ + g_return_if_fail (E_IS_COLOR_COMBO (combo)); + + combo->priv->default_transparent = transparent; + + g_object_notify (G_OBJECT (combo), "default-transparent"); +} + +GList * +e_color_combo_get_palette (EColorCombo *combo) +{ + g_return_val_if_fail (E_IS_COLOR_COMBO (combo), NULL); + + return g_list_copy (combo->priv->palette); +} + +void +e_color_combo_set_palette (EColorCombo *combo, + GList *palette) +{ + gint ii, count, colors_per_line; + GList *iter; + GdkRGBA *colors; + + g_return_if_fail (E_IS_COLOR_COMBO (combo)); + + count = g_list_length (palette); + colors_per_line = (count % 10 == 0) ? 10 : 9; + + colors = g_malloc_n (count, sizeof (GdkRGBA)); + g_list_free_full (combo->priv->palette, (GDestroyNotify) gdk_rgba_free); + ii = 0; + combo->priv->palette = NULL; + for (iter = palette; iter; iter = g_list_next (iter)) { + combo->priv->palette = g_list_prepend ( + combo->priv->palette, gdk_rgba_copy (iter->data)); + + colors[ii] = *((GdkRGBA *) iter->data); + ii++; + } + combo->priv->palette = g_list_reverse (combo->priv->palette); + + gtk_color_chooser_add_palette ( + GTK_COLOR_CHOOSER (combo->priv->chooser_widget), + GTK_ORIENTATION_HORIZONTAL, 0, 0, NULL); + gtk_color_chooser_add_palette ( + GTK_COLOR_CHOOSER (combo->priv->chooser_widget), + GTK_ORIENTATION_HORIZONTAL, colors_per_line, count, colors); + g_free (colors); +} diff --git a/e-util/e-color-combo.h b/e-util/e-color-combo.h new file mode 100644 index 0000000000..41f7fd1574 --- /dev/null +++ b/e-util/e-color-combo.h @@ -0,0 +1,96 @@ +/* e-color-combo.h + * + * Copyright (C) 2012 Dan Vrátil + * + * 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_COLOR_COMBO_H +#define E_COLOR_COMBO_H + +#include + +/* Standard GObject macros */ +#define E_TYPE_COLOR_COMBO \ + (e_color_combo_get_type ()) +#define E_COLOR_COMBO(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST \ + ((obj), E_TYPE_COLOR_COMBO, EColorCombo)) +#define E_COLOR_COMBO_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_CAST \ + ((cls), E_TYPE_COLOR_COMBO, EColorComboClass)) +#define E_IS_COLOR_COMBO(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE \ + ((obj), E_TYPE_COLOR_COMBO)) +#define E_IS_COLOR_COMBO_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_TYPE \ + ((cls), E_TYPE_COLOR_COMBO)) +#define E_COLOR_COMBO_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS \ + ((obj), E_TYPE_COLOR_COMBO, EColorComboClass)) + +G_BEGIN_DECLS + +typedef struct _EColorCombo EColorCombo; +typedef struct _EColorComboClass EColorComboClass; +typedef struct _EColorComboPrivate EColorComboPrivate; + +struct _EColorCombo { + GtkButton parent; + EColorComboPrivate *priv; +}; + +struct _EColorComboClass { + GtkButtonClass parent_class; + + void (*popup) (EColorCombo *combo); + void (*popdown) (EColorCombo *combo); + void (*activated) (EColorCombo *combo, + GdkRGBA *color); +}; + +GType e_color_combo_get_type (void) G_GNUC_CONST; +GtkWidget * e_color_combo_new (void); +GtkWidget * e_color_combo_new_defaults (GdkRGBA *default_color, + const gchar *default_label); +void e_color_combo_popup (EColorCombo *combo); +void e_color_combo_popdown (EColorCombo *combo); +void e_color_combo_get_current_color (EColorCombo *combo, + GdkRGBA *rgba); +void e_color_combo_set_current_color (EColorCombo *combo, + const GdkRGBA *color); +void e_color_combo_get_default_color (EColorCombo *combo, + GdkRGBA *color); +void e_color_combo_set_default_color (EColorCombo *combo, + const GdkRGBA *default_color); +const gchar * e_color_combo_get_default_label (EColorCombo *combo); +void e_color_combo_set_default_label (EColorCombo *combo, + const gchar *text); +gboolean e_color_combo_get_default_transparent + (EColorCombo *combo); +void e_color_combo_set_default_transparent + (EColorCombo *combo, + gboolean transparent); +GList * e_color_combo_get_palette (EColorCombo *combo); +void e_color_combo_set_palette (EColorCombo *combo, + GList *palette); + +G_END_DECLS + +#endif /* E_COLOR_COMBO_H */ diff --git a/e-util/e-emoticon-action.c b/e-util/e-emoticon-action.c new file mode 100644 index 0000000000..0850d33f49 --- /dev/null +++ b/e-util/e-emoticon-action.c @@ -0,0 +1,278 @@ +/* + * e-emoticon-action.c + * + * Copyright (C) 2008 Novell, Inc. + * Copyright (C) 2012 Dan Vrátil + * + * 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. + */ + +#include "e-emoticon-action.h" + +#include "e-emoticon-chooser.h" +#include "e-emoticon-chooser-menu.h" +#include "e-emoticon-tool-button.h" + +#define E_EMOTICON_ACTION_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE \ + ((obj), E_TYPE_EMOTICON_ACTION, EEmoticonActionPrivate)) + +struct _EEmoticonActionPrivate { + GList *choosers; + EEmoticonChooser *current_chooser; +}; + +enum { + PROP_0, + PROP_CURRENT_FACE +}; + +/* Forward Declarations */ +static void e_emoticon_action_interface_init + (EEmoticonChooserInterface *interface); + +G_DEFINE_TYPE_WITH_CODE ( + EEmoticonAction, + e_emoticon_action, + GTK_TYPE_ACTION, + G_IMPLEMENT_INTERFACE ( + E_TYPE_EMOTICON_CHOOSER, + e_emoticon_action_interface_init)) + +static void +emoticon_action_proxy_item_activated_cb (EEmoticonAction *action, + EEmoticonChooser *chooser) +{ + action->priv->current_chooser = chooser; + + g_signal_emit_by_name (action, "item-activated"); +} + +static void +emoticon_action_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + switch (property_id) { + case PROP_CURRENT_FACE: + e_emoticon_chooser_set_current_emoticon ( + E_EMOTICON_CHOOSER (object), + g_value_get_boxed (value)); + return; + } + + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); +} + +static void +emoticon_action_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + switch (property_id) { + case PROP_CURRENT_FACE: + g_value_set_boxed ( + value, e_emoticon_chooser_get_current_emoticon ( + E_EMOTICON_CHOOSER (object))); + return; + } + + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); +} + +static void +emoticon_action_finalize (GObject *object) +{ + EEmoticonActionPrivate *priv; + + priv = E_EMOTICON_ACTION_GET_PRIVATE (object); + + g_list_free (priv->choosers); + + /* Chain up to parent's finalize() method. */ + G_OBJECT_CLASS (e_emoticon_action_parent_class)->finalize (object); +} + +static void +emoticon_action_activate (GtkAction *action) +{ + EEmoticonActionPrivate *priv; + + priv = E_EMOTICON_ACTION_GET_PRIVATE (action); + + priv->current_chooser = NULL; +} + +static GtkWidget * +emoticon_action_create_menu_item (GtkAction *action) +{ + GtkWidget *item; + GtkWidget *menu; + + item = gtk_image_menu_item_new (); + menu = gtk_action_create_menu (action); + gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), menu); + gtk_widget_show (menu); + + return item; +} + +static GtkWidget * +emoticon_action_create_tool_item (GtkAction *action) +{ + return GTK_WIDGET (e_emoticon_tool_button_new ()); +} + +static void +emoticon_action_connect_proxy (GtkAction *action, + GtkWidget *proxy) +{ + EEmoticonActionPrivate *priv; + + priv = E_EMOTICON_ACTION_GET_PRIVATE (action); + + if (!E_IS_EMOTICON_CHOOSER (proxy)) + goto chainup; + + if (g_list_find (priv->choosers, proxy) != NULL) + goto chainup; + + g_signal_connect_swapped ( + proxy, "item-activated", + G_CALLBACK (emoticon_action_proxy_item_activated_cb), action); + +chainup: + /* Chain up to parent's connect_proxy() method. */ + GTK_ACTION_CLASS (e_emoticon_action_parent_class)-> + connect_proxy (action, proxy); +} + +static void +emoticon_action_disconnect_proxy (GtkAction *action, + GtkWidget *proxy) +{ + EEmoticonActionPrivate *priv; + + priv = E_EMOTICON_ACTION_GET_PRIVATE (action); + + priv->choosers = g_list_remove (priv->choosers, proxy); + + /* Chain up to parent's disconnect_proxy() method. */ + GTK_ACTION_CLASS (e_emoticon_action_parent_class)-> + disconnect_proxy (action, proxy); +} + +static GtkWidget * +emoticon_action_create_menu (GtkAction *action) +{ + EEmoticonActionPrivate *priv; + GtkWidget *widget; + + priv = E_EMOTICON_ACTION_GET_PRIVATE (action); + + widget = e_emoticon_chooser_menu_new (); + + g_signal_connect_swapped ( + widget, "item-activated", + G_CALLBACK (emoticon_action_proxy_item_activated_cb), action); + + priv->choosers = g_list_prepend (priv->choosers, widget); + + return widget; +} + +static EEmoticon * +emoticon_action_get_current_emoticon (EEmoticonChooser *chooser) +{ + EEmoticonActionPrivate *priv; + EEmoticon *emoticon = NULL; + + priv = E_EMOTICON_ACTION_GET_PRIVATE (chooser); + + if (priv->current_chooser != NULL) + emoticon = e_emoticon_chooser_get_current_emoticon ( + priv->current_chooser); + + return emoticon; +} + +static void +emoticon_action_set_current_emoticon (EEmoticonChooser *chooser, + EEmoticon *emoticon) +{ + EEmoticonActionPrivate *priv; + GList *iter; + + priv = E_EMOTICON_ACTION_GET_PRIVATE (chooser); + + for (iter = priv->choosers; iter != NULL; iter = iter->next) { + EEmoticonChooser *proxy_chooser = iter->data; + + e_emoticon_chooser_set_current_emoticon (proxy_chooser, emoticon); + } +} + +static void +e_emoticon_action_class_init (EEmoticonActionClass *class) +{ + GObjectClass *object_class; + GtkActionClass *action_class; + + g_type_class_add_private (class, sizeof (EEmoticonAction)); + + object_class = G_OBJECT_CLASS (class); + object_class->set_property = emoticon_action_set_property; + object_class->get_property = emoticon_action_get_property; + object_class->finalize = emoticon_action_finalize; + + action_class = GTK_ACTION_CLASS (class); + action_class->activate = emoticon_action_activate; + action_class->create_menu_item = emoticon_action_create_menu_item; + action_class->create_tool_item = emoticon_action_create_tool_item; + action_class->connect_proxy = emoticon_action_connect_proxy; + action_class->disconnect_proxy = emoticon_action_disconnect_proxy; + action_class->create_menu = emoticon_action_create_menu; + + g_object_class_override_property ( + object_class, PROP_CURRENT_FACE, "current-emoticon"); +} + +static void +e_emoticon_action_interface_init (EEmoticonChooserInterface *interface) +{ + interface->get_current_emoticon = emoticon_action_get_current_emoticon; + interface->set_current_emoticon = emoticon_action_set_current_emoticon; +} + +static void +e_emoticon_action_init (EEmoticonAction *action) +{ + action->priv = E_EMOTICON_ACTION_GET_PRIVATE (action); +} + +GtkAction * +e_emoticon_action_new (const gchar *name, + const gchar *label, + const gchar *tooltip, + const gchar *stock_id) +{ + g_return_val_if_fail (name != NULL, NULL); + + return g_object_new ( + E_TYPE_EMOTICON_ACTION, "name", name, "label", label, + "tooltip", tooltip, "stock-id", stock_id, NULL); +} diff --git a/e-util/e-emoticon-action.h b/e-util/e-emoticon-action.h new file mode 100644 index 0000000000..0e450e8750 --- /dev/null +++ b/e-util/e-emoticon-action.h @@ -0,0 +1,73 @@ +/* + * e-emoticon-action.h + * + * Copyright (C) 2008 Novell, Inc. + * Copyright (C) 2012 Dan Vrátil + * + * 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_EMOTICON_ACTION_H +#define E_EMOTICON_ACTION_H + +#include + +/* Standard GObject macros */ +#define E_TYPE_EMOTICON_ACTION \ + (e_emoticon_action_get_type ()) +#define E_EMOTICON_ACTION(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST \ + ((obj), E_TYPE_EMOTICON_ACTION, EEmoticonAction)) +#define E_EMOTICON_ACTION_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_CAST \ + ((cls), E_TYPE_EMOTICON_ACTION, EEmoticonActionClass)) +#define E_IS_EMOTICON_ACTION(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE \ + ((obj), E_TYPE_EMOTICON_ACTION)) +#define E_IS_EMOTICON_ACTION_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_TYPE \ + ((cls), E_TYPE_EMOTICON_ACTION)) +#define E_EMOTICON_ACTION_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS \ + ((obj), E_TYPE_EMOTICON_ACTION, EEmoticonActionClass)) + +G_BEGIN_DECLS + +typedef struct _EEmoticonAction EEmoticonAction; +typedef struct _EEmoticonActionClass EEmoticonActionClass; +typedef struct _EEmoticonActionPrivate EEmoticonActionPrivate; + +struct _EEmoticonAction { + GtkAction parent; + EEmoticonActionPrivate *priv; +}; + +struct _EEmoticonActionClass { + GtkActionClass parent_class; +}; + +GType e_emoticon_action_get_type (void) G_GNUC_CONST; +GtkAction * e_emoticon_action_new (const gchar *name, + const gchar *label, + const gchar *tooltip, + const gchar *stock_id); + +G_END_DECLS + +#endif /* E_EMOTICON_ACTION_H */ diff --git a/e-util/e-emoticon-chooser-menu.c b/e-util/e-emoticon-chooser-menu.c new file mode 100644 index 0000000000..f2ed3376cf --- /dev/null +++ b/e-util/e-emoticon-chooser-menu.c @@ -0,0 +1,184 @@ +/* + * e-emoticon-chooser-menu.c + * + * Copyright (C) 2008 Novell, Inc. + * Copyright (C) 2012 Dan Vrátil + * + * 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-emoticon-chooser-menu.h" +#include "e-emoticon-chooser.h" + +#include + +enum { + PROP_0, + PROP_CURRENT_FACE +}; + +/* Forward Declarations */ +static void e_emoticon_chooser_menu_interface_init + (EEmoticonChooserInterface *interface); + +G_DEFINE_TYPE_WITH_CODE ( + EEmoticonChooserMenu, + e_emoticon_chooser_menu, + GTK_TYPE_MENU, + G_IMPLEMENT_INTERFACE ( + E_TYPE_EMOTICON_CHOOSER, + e_emoticon_chooser_menu_interface_init)) + +static void +emoticon_chooser_menu_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + switch (property_id) { + case PROP_CURRENT_FACE: + e_emoticon_chooser_set_current_emoticon ( + E_EMOTICON_CHOOSER (object), + g_value_get_boxed (value)); + return; + } + + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); +} + +static void +emoticon_chooser_menu_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + switch (property_id) { + case PROP_CURRENT_FACE: + g_value_set_boxed ( + value, + e_emoticon_chooser_get_current_emoticon ( + E_EMOTICON_CHOOSER (object))); + return; + } + + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); +} + +static EEmoticon * +emoticon_chooser_menu_get_current_emoticon (EEmoticonChooser *chooser) +{ + GtkWidget *item; + + item = gtk_menu_get_active (GTK_MENU (chooser)); + if (item == NULL) + return NULL; + + return g_object_get_data (G_OBJECT (item), "emoticon"); +} + +static void +emoticon_chooser_menu_set_current_emoticon (EEmoticonChooser *chooser, + EEmoticon *emoticon) +{ + GList *list, *iter; + + list = gtk_container_get_children (GTK_CONTAINER (chooser)); + + for (iter = list; iter != NULL; iter = iter->next) { + GtkWidget *item = iter->data; + EEmoticon *candidate; + + candidate = g_object_get_data (G_OBJECT (item), "emoticon"); + if (candidate == NULL) + continue; + + if (e_emoticon_equal (emoticon, candidate)) { + gtk_menu_shell_activate_item ( + GTK_MENU_SHELL (chooser), item, TRUE); + break; + } + } + + g_list_free (list); +} + +static void +e_emoticon_chooser_menu_class_init (EEmoticonChooserMenuClass *class) +{ + GObjectClass *object_class; + + object_class = G_OBJECT_CLASS (class); + object_class->set_property = emoticon_chooser_menu_set_property; + object_class->get_property = emoticon_chooser_menu_get_property; + + g_object_class_override_property ( + object_class, PROP_CURRENT_FACE, "current-emoticon"); +} + +static void +e_emoticon_chooser_menu_interface_init (EEmoticonChooserInterface *interface) +{ + interface->get_current_emoticon = + emoticon_chooser_menu_get_current_emoticon; + interface->set_current_emoticon = + emoticon_chooser_menu_set_current_emoticon; +} + +static void +e_emoticon_chooser_menu_init (EEmoticonChooserMenu *chooser_menu) +{ + EEmoticonChooser *chooser; + GList *list, *iter; + + chooser = E_EMOTICON_CHOOSER (chooser_menu); + list = e_emoticon_chooser_get_items (); + + for (iter = list; iter != NULL; iter = iter->next) { + EEmoticon *emoticon = iter->data; + GtkWidget *item; + + /* To keep translated strings in subclasses */ + item = gtk_image_menu_item_new_with_mnemonic (_(emoticon->label)); + gtk_image_menu_item_set_image ( + GTK_IMAGE_MENU_ITEM (item), + gtk_image_new_from_icon_name ( + emoticon->icon_name, GTK_ICON_SIZE_MENU)); + gtk_widget_show (item); + + g_object_set_data_full ( + G_OBJECT (item), "emoticon", + e_emoticon_copy (emoticon), + (GDestroyNotify) e_emoticon_free); + + g_signal_connect_swapped ( + item, "activate", + G_CALLBACK (e_emoticon_chooser_item_activated), + chooser); + + gtk_menu_shell_append (GTK_MENU_SHELL (chooser_menu), item); + } + + g_list_free (list); +} + +GtkWidget * +e_emoticon_chooser_menu_new (void) +{ + return g_object_new (E_TYPE_EMOTICON_CHOOSER_MENU, NULL); +} diff --git a/e-util/e-emoticon-chooser-menu.h b/e-util/e-emoticon-chooser-menu.h new file mode 100644 index 0000000000..d4f99543bf --- /dev/null +++ b/e-util/e-emoticon-chooser-menu.h @@ -0,0 +1,70 @@ +/* + * e-emoticon-chooser-menu.h + * + * Copyright (C) 2008 Novell, Inc. + * Copyright (C) 2012 Dan Vrátil + * + * 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_EMOTICON_CHOOSER_MENU_H +#define E_EMOTICON_CHOOSER_MENU_H + +#include + +/* Standard GObject macros */ +#define E_TYPE_EMOTICON_CHOOSER_MENU \ + (e_emoticon_chooser_menu_get_type ()) +#define E_EMOTICON_CHOOSER_MENU(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST \ + ((obj), E_TYPE_EMOTICON_CHOOSER_MENU, EEmoticonChooserMenu)) +#define E_EMOTICON_CHOOSER_MENU_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_CAST \ + ((cls), E_TYPE_EMOTICON_CHOOSER_MENU, EEmoticonChooserMenuClass)) +#define E_IS_EMOTICON_CHOOSER_MENU(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE \ + ((obj), E_TYPE_EMOTICON_CHOOSER_MENU)) +#define E_IS_EMOTICON_CHOOSER_MENU_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_TYPE \ + ((cls), E_TYPE_EMOTICON_CHOOSER_MENU)) +#define E_EMOTICON_CHOOSER_MENU_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS \ + ((obj), E_TYPE_EMOTICON_CHOOSER_MENU, EEmoticonChooserMenuClass)) + +G_BEGIN_DECLS + +typedef struct _EEmoticonChooserMenu EEmoticonChooserMenu; +typedef struct _EEmoticonChooserMenuClass EEmoticonChooserMenuClass; +typedef struct _EEmoticonChooserMenuPrivate EEmoticonChooserMenuPrivate; + +struct _EEmoticonChooserMenu { + GtkMenu parent; +}; + +struct _EEmoticonChooserMenuClass { + GtkMenuClass parent_class; +}; + +GType e_emoticon_chooser_menu_get_type + (void) G_GNUC_CONST; +GtkWidget * e_emoticon_chooser_menu_new (void); + +G_END_DECLS + +#endif /* E_EMOTICON_CHOOSER_MENU_H */ diff --git a/e-util/e-emoticon-chooser.c b/e-util/e-emoticon-chooser.c new file mode 100644 index 0000000000..44ce06ba55 --- /dev/null +++ b/e-util/e-emoticon-chooser.c @@ -0,0 +1,178 @@ +/* + * e-emoticon-chooser.c + * + * Copyright (C) 2008 Novell, Inc. + * Copyright (C) 2012 Dan Vrátil + * + * 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-emoticon-chooser.h" + +#include + +/* Constant version of EEMoticon. */ +typedef struct { + const gchar *label; + const gchar *icon_name; + const gchar *text_face; +} ConstantEmoticon; + +static ConstantEmoticon available_emoticons[] = { + /* Translators: :-) */ + { N_("_Smile"), "face-smile", ":-)" }, + /* Translators: :-( */ + { N_("S_ad"), "face-sad", ":-(" }, + /* Translators: ;-) */ + { N_("_Wink"), "face-wink", ";-)" }, + /* Translators: :-P */ + { N_("Ton_gue"), "face-raspberry", ":-P" }, + /* Translators: :-)) */ + { N_("Laug_h"), "face-laugh", ":-))" }, + /* Translators: :-| */ + { N_("_Plain"), "face-plain", ":-|" }, + /* Translators: :-! */ + { N_("Smi_rk"), "face-smirk", ":-!" }, + /* Translators: :"-) */ + { N_("_Embarrassed"), "face-embarrassed", ":\"-)" }, + /* Translators: :-D */ + { N_("_Big Smile"), "face-smile-big", ":-D" }, + /* Translators: :-/ */ + { N_("Uncer_tain"), "face-uncertain", ":-/" }, + /* Translators: :-O */ + { N_("S_urprise"), "face-surprise", ":-O" }, + /* Translators: :-S */ + { N_("W_orried"), "face-worried", ":-S" }, + /* Translators: :-* */ + { N_("_Kiss"), "face-kiss", ":-*" }, + /* Translators: X-( */ + { N_("A_ngry"), "face-angry", "X-(" }, + /* Translators: B-) */ + { N_("_Cool"), "face-cool", "B-)" }, + /* Translators: O:-) */ + { N_("Ange_l"), "face-angel", "O:-)" }, + /* Translators: :'( */ + { N_("Cr_ying"), "face-crying", ":'(" }, + /* Translators: :-Q */ + { N_("S_ick"), "face-sick", ":-Q" }, + /* Translators: |-) */ + { N_("Tire_d"), "face-tired", "|-)" }, + /* Translators: >:-) */ + { N_("De_vilish"), "face-devilish", ">:-)" }, + /* Translators: :-(|) */ + { N_("_Monkey"), "face-monkey", ":-(|)" } +}; + +enum { + ITEM_ACTIVATED, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL]; + +G_DEFINE_INTERFACE ( + EEmoticonChooser, + e_emoticon_chooser, + G_TYPE_OBJECT) + +static void +e_emoticon_chooser_default_init (EEmoticonChooserInterface *interface) +{ + g_object_interface_install_property ( + interface, + g_param_spec_boxed ( + "current-emoticon", + "Current Emoticon", + "Currently selected emoticon", + E_TYPE_EMOTICON, + G_PARAM_READWRITE)); + + signals[ITEM_ACTIVATED] = g_signal_new ( + "item-activated", + G_TYPE_FROM_INTERFACE (interface), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (EEmoticonChooserInterface, item_activated), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); +} + +EEmoticon * +e_emoticon_chooser_get_current_emoticon (EEmoticonChooser *chooser) +{ + EEmoticonChooserInterface *interface; + + g_return_val_if_fail (E_IS_EMOTICON_CHOOSER (chooser), NULL); + + interface = E_EMOTICON_CHOOSER_GET_INTERFACE (chooser); + g_return_val_if_fail (interface->get_current_emoticon != NULL, NULL); + + return interface->get_current_emoticon (chooser); +} + +void +e_emoticon_chooser_set_current_emoticon (EEmoticonChooser *chooser, + EEmoticon *emoticon) +{ + EEmoticonChooserInterface *interface; + + g_return_if_fail (E_IS_EMOTICON_CHOOSER (chooser)); + + interface = E_EMOTICON_CHOOSER_GET_INTERFACE (chooser); + g_return_if_fail (interface->set_current_emoticon != NULL); + + interface->set_current_emoticon (chooser, emoticon); +} + +void +e_emoticon_chooser_item_activated (EEmoticonChooser *chooser) +{ + g_return_if_fail (E_IS_EMOTICON_CHOOSER (chooser)); + + g_signal_emit (chooser, signals[ITEM_ACTIVATED], 0); +} + +GList * +e_emoticon_chooser_get_items (void) +{ + GList *list = NULL; + gint ii; + + for (ii = 0; ii < G_N_ELEMENTS (available_emoticons); ii++) + list = g_list_prepend (list, &available_emoticons[ii]); + + return g_list_reverse (list); +} + +const EEmoticon * +e_emoticon_chooser_lookup_emoticon (const gchar *icon_name) +{ + gint ii; + + g_return_val_if_fail (icon_name && *icon_name, NULL); + + for (ii = 0; ii < G_N_ELEMENTS (available_emoticons); ii++) { + if (strcmp (available_emoticons[ii].icon_name, icon_name) == 0) { + return (const EEmoticon *) &available_emoticons[ii]; + } + } + + return NULL; +} + diff --git a/e-util/e-emoticon-chooser.h b/e-util/e-emoticon-chooser.h new file mode 100644 index 0000000000..14e899fd4e --- /dev/null +++ b/e-util/e-emoticon-chooser.h @@ -0,0 +1,77 @@ +/* + * e-emoticon-chooser.h + * + * Copyright (C) 2008 Novell, Inc. + * Copyright (C) 2012 Dan Vrátil + * + * 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_EMOTICON_CHOOSER_H +#define E_EMOTICON_CHOOSER_H + +#include + +/* Standard GObject macros */ +#define E_TYPE_EMOTICON_CHOOSER \ + (e_emoticon_chooser_get_type ()) +#define E_EMOTICON_CHOOSER(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST \ + ((obj), E_TYPE_EMOTICON_CHOOSER, EEmoticonChooser)) +#define E_IS_EMOTICON_CHOOSER(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE \ + ((obj), E_TYPE_EMOTICON_CHOOSER)) +#define E_EMOTICON_CHOOSER_GET_INTERFACE(obj) \ + (G_TYPE_INSTANCE_GET_INTERFACE \ + ((obj), E_TYPE_EMOTICON_CHOOSER, EEmoticonChooserInterface)) + +G_BEGIN_DECLS + +typedef struct _EEmoticonChooser EEmoticonChooser; +typedef struct _EEmoticonChooserInterface EEmoticonChooserInterface; + +struct _EEmoticonChooserInterface { + GTypeInterface parent_interface; + + /* Methods */ + EEmoticon * (*get_current_emoticon) (EEmoticonChooser *chooser); + void (*set_current_emoticon) (EEmoticonChooser *chooser, + EEmoticon *emoticon); + + /* Signals */ + void (*item_activated) (EEmoticonChooser *chooser); +}; + +GType e_emoticon_chooser_get_type (void) G_GNUC_CONST; +EEmoticon * e_emoticon_chooser_get_current_emoticon + (EEmoticonChooser *chooser); +void e_emoticon_chooser_set_current_emoticon + (EEmoticonChooser *chooser, + EEmoticon *emoticon); +void e_emoticon_chooser_item_activated + (EEmoticonChooser *chooser); + +GList * e_emoticon_chooser_get_items (void); +const EEmoticon * + e_emoticon_chooser_lookup_emoticon + (const gchar *icon_name); + +G_END_DECLS + +#endif /* E_EMOTICON_CHOOSER_H */ diff --git a/e-util/e-emoticon-tool-button.c b/e-util/e-emoticon-tool-button.c new file mode 100644 index 0000000000..54f99c94b0 --- /dev/null +++ b/e-util/e-emoticon-tool-button.c @@ -0,0 +1,695 @@ +/* + * e-emoticon-tool-button.c + * + * Copyright (C) 2008 Novell, Inc. + * Copyright (C) 2012 Dan Vrátil + * + * 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-emoticon-tool-button.h" + +/* XXX The "button" aspects of this widget are based heavily on the + * GtkComboBox tree-view implementation. Consider splitting it + * into a reusable "button-with-an-empty-window" widget. */ + +#include +#include +#include + +#include "e-emoticon-chooser.h" + +#define E_EMOTICON_TOOL_BUTTON_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE \ + ((obj), E_TYPE_EMOTICON_TOOL_BUTTON, EEmoticonToolButtonPrivate)) + +/* XXX Should calculate this dynamically. */ +#define NUM_ROWS 7 +#define NUM_COLS 3 + +enum { + PROP_0, + PROP_CURRENT_EMOTICON, + PROP_POPUP_SHOWN +}; + +enum { + POPUP, + POPDOWN, + LAST_SIGNAL +}; + +struct _EEmoticonToolButtonPrivate { + GtkWidget *active_button; /* not referenced */ + GtkWidget *table; + GtkWidget *window; + + guint popup_shown : 1; + guint popup_in_progress : 1; + GdkDevice *grab_keyboard; + GdkDevice *grab_mouse; +}; + +static guint signals[LAST_SIGNAL]; + +/* Forward Declarations */ +static void e_emoticon_tool_button_interface_init + (EEmoticonChooserInterface *interface); + +G_DEFINE_TYPE_WITH_CODE ( + EEmoticonToolButton, + e_emoticon_tool_button, + GTK_TYPE_TOGGLE_TOOL_BUTTON, + G_IMPLEMENT_INTERFACE ( + E_TYPE_EMOTICON_CHOOSER, + e_emoticon_tool_button_interface_init)) + +/* XXX Copied from _gtk_toolbar_elide_underscores() */ +static gchar * +emoticon_tool_button_elide_underscores (const gchar *original) +{ + gchar *q, *result; + const gchar *p, *end; + gsize len; + gboolean last_underscore; + + if (!original) + return NULL; + + len = strlen (original); + q = result = g_malloc (len + 1); + last_underscore = FALSE; + + end = original + len; + for (p = original; p < end; p++) { + if (!last_underscore && *p == '_') + last_underscore = TRUE; + else { + last_underscore = FALSE; + if (original + 2 <= p && p + 1 <= end && + p[-2] == '(' && p[-1] == '_' && + p[0] != '_' && p[1] == ')') { + q--; + *q = '\0'; + p++; + } else + *q++ = *p; + } + } + + if (last_underscore) + *q++ = '_'; + + *q = '\0'; + + return result; +} + +static void +emoticon_tool_button_reposition_window (EEmoticonToolButton *button) +{ + GdkScreen *screen; + GdkWindow *window; + GdkRectangle monitor; + GtkAllocation allocation; + gint monitor_num; + gint x, y, width, height; + + screen = gtk_widget_get_screen (GTK_WIDGET (button)); + window = gtk_widget_get_window (GTK_WIDGET (button)); + monitor_num = gdk_screen_get_monitor_at_window (screen, window); + gdk_screen_get_monitor_geometry (screen, monitor_num, &monitor); + + gdk_window_get_origin (window, &x, &y); + + if (!gtk_widget_get_has_window (GTK_WIDGET (button))) { + gtk_widget_get_allocation (GTK_WIDGET (button), &allocation); + x += allocation.x; + y += allocation.y; + } + + gtk_widget_get_allocation (button->priv->window, &allocation); + width = allocation.width; + height = allocation.height; + + x = CLAMP (x, monitor.x, monitor.x + monitor.width - width); + y = CLAMP (y, monitor.y, monitor.y + monitor.height - height); + + gtk_window_move (GTK_WINDOW (button->priv->window), x, y); +} + +static void +emoticon_tool_button_emoticon_clicked_cb (EEmoticonToolButton *button, + GtkWidget *emoticon_button) +{ + button->priv->active_button = emoticon_button; + e_emoticon_tool_button_popdown (button); +} + +static gboolean +emoticon_tool_button_emoticon_release_event_cb (EEmoticonToolButton *button, + GdkEventButton *event, + GtkButton *emoticon_button) +{ + GtkStateType state; + + state = gtk_widget_get_state (GTK_WIDGET (button)); + + if (state != GTK_STATE_NORMAL) + gtk_button_clicked (emoticon_button); + + return FALSE; +} + +static gboolean +emoticon_tool_button_button_release_event_cb (EEmoticonToolButton *button, + GdkEventButton *event) +{ + GtkToggleToolButton *tool_button; + GtkWidget *event_widget; + gboolean popup_in_progress; + + tool_button = GTK_TOGGLE_TOOL_BUTTON (button); + event_widget = gtk_get_event_widget ((GdkEvent *) event); + + popup_in_progress = button->priv->popup_in_progress; + button->priv->popup_in_progress = FALSE; + + if (event_widget != GTK_WIDGET (button)) + goto popdown; + + if (popup_in_progress) + return FALSE; + + if (gtk_toggle_tool_button_get_active (tool_button)) + goto popdown; + + return FALSE; + +popdown: + e_emoticon_tool_button_popdown (button); + + return TRUE; +} + +static void +emoticon_tool_button_child_show_cb (EEmoticonToolButton *button) +{ + button->priv->popup_shown = TRUE; + g_object_notify (G_OBJECT (button), "popup-shown"); +} + +static void +emoticon_tool_button_child_hide_cb (EEmoticonToolButton *button) +{ + button->priv->popup_shown = FALSE; + g_object_notify (G_OBJECT (button), "popup-shown"); +} + +static gboolean +emoticon_tool_button_child_key_press_event_cb (EEmoticonToolButton *button, + GdkEventKey *event) +{ + GtkWidget *window = button->priv->window; + + if (!gtk_bindings_activate_event (G_OBJECT (window), event)) + gtk_bindings_activate_event (G_OBJECT (button), event); + + return TRUE; +} + +static void +emoticon_tool_button_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + switch (property_id) { + case PROP_CURRENT_EMOTICON: + e_emoticon_chooser_set_current_emoticon ( + E_EMOTICON_CHOOSER (object), + g_value_get_boxed (value)); + return; + + case PROP_POPUP_SHOWN: + if (g_value_get_boolean (value)) + e_emoticon_tool_button_popup ( + E_EMOTICON_TOOL_BUTTON (object)); + else + e_emoticon_tool_button_popdown ( + E_EMOTICON_TOOL_BUTTON (object)); + return; + } + + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); +} + +static void +emoticon_tool_button_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + EEmoticonToolButtonPrivate *priv; + + priv = E_EMOTICON_TOOL_BUTTON_GET_PRIVATE (object); + + switch (property_id) { + case PROP_CURRENT_EMOTICON: + g_value_set_boxed ( + value, + e_emoticon_chooser_get_current_emoticon ( + E_EMOTICON_CHOOSER (object))); + return; + + case PROP_POPUP_SHOWN: + g_value_set_boolean (value, priv->popup_shown); + return; + } + + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); +} + +static void +emoticon_tool_button_dispose (GObject *object) +{ + EEmoticonToolButtonPrivate *priv; + + priv = E_EMOTICON_TOOL_BUTTON_GET_PRIVATE (object); + + if (priv->window != NULL) { + gtk_widget_destroy (priv->window); + priv->window = NULL; + } + + /* Chain up to parent's dispose() method. */ + G_OBJECT_CLASS (e_emoticon_tool_button_parent_class)->dispose (object); +} + +static gboolean +emoticon_tool_button_press_event (GtkWidget *widget, + GdkEventButton *event) +{ + EEmoticonToolButton *button; + GtkToggleToolButton *toggle_button; + GtkWidget *event_widget; + + button = E_EMOTICON_TOOL_BUTTON (widget); + + event_widget = gtk_get_event_widget ((GdkEvent *) event); + + if (event_widget == button->priv->window) + return TRUE; + + if (event_widget != widget) + return FALSE; + + toggle_button = GTK_TOGGLE_TOOL_BUTTON (widget); + if (gtk_toggle_tool_button_get_active (toggle_button)) + return FALSE; + + e_emoticon_tool_button_popup (button); + + button->priv->popup_in_progress = TRUE; + + return TRUE; +} + +static void +emoticon_tool_button_toggled (GtkToggleToolButton *button) +{ + if (gtk_toggle_tool_button_get_active (button)) + e_emoticon_tool_button_popup ( + E_EMOTICON_TOOL_BUTTON (button)); + else + e_emoticon_tool_button_popdown ( + E_EMOTICON_TOOL_BUTTON (button)); +} + +static void +emoticon_tool_button_popup (EEmoticonToolButton *button) +{ + GtkToggleToolButton *tool_button; + GdkWindow *window; + gboolean grab_status; + GdkDevice *device, *mouse, *keyboard; + guint32 activate_time; + + device = gtk_get_current_event_device (); + g_return_if_fail (device != NULL); + + if (!gtk_widget_get_realized (GTK_WIDGET (button))) + return; + + if (button->priv->popup_shown) + return; + + activate_time = gtk_get_current_event_time (); + if (gdk_device_get_source (device) == GDK_SOURCE_KEYBOARD) { + keyboard = device; + mouse = gdk_device_get_associated_device (device); + } else { + keyboard = gdk_device_get_associated_device (device); + mouse = device; + } + + /* Position the window over the button. */ + emoticon_tool_button_reposition_window (button); + + /* Show the pop-up. */ + gtk_widget_show (button->priv->window); + gtk_widget_grab_focus (button->priv->window); + + /* Activate the tool button. */ + tool_button = GTK_TOGGLE_TOOL_BUTTON (button); + gtk_toggle_tool_button_set_active (tool_button, TRUE); + + /* Try to grab the pointer and keyboard. */ + window = gtk_widget_get_window (button->priv->window); + grab_status = !keyboard || + gdk_device_grab ( + keyboard, window, + GDK_OWNERSHIP_WINDOW, TRUE, + GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK, + NULL, activate_time) == GDK_GRAB_SUCCESS; + if (grab_status) { + grab_status = !mouse || + gdk_device_grab (mouse, window, + GDK_OWNERSHIP_WINDOW, TRUE, + GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK, + NULL, activate_time) == GDK_GRAB_SUCCESS; + if (!grab_status && keyboard) + gdk_device_ungrab (keyboard, activate_time); + } + + if (grab_status) { + gtk_device_grab_add (button->priv->window, mouse, TRUE); + button->priv->grab_keyboard = keyboard; + button->priv->grab_mouse = mouse; + } else { + gtk_widget_hide (button->priv->window); + } +} + +static void +emoticon_tool_button_popdown (EEmoticonToolButton *button) +{ + GtkToggleToolButton *tool_button; + + if (!gtk_widget_get_realized (GTK_WIDGET (button))) + return; + + if (!button->priv->popup_shown) + return; + + /* Hide the pop-up. */ + gtk_device_grab_remove (button->priv->window, button->priv->grab_mouse); + gtk_widget_hide (button->priv->window); + + /* Deactivate the tool button. */ + tool_button = GTK_TOGGLE_TOOL_BUTTON (button); + gtk_toggle_tool_button_set_active (tool_button, FALSE); + + if (button->priv->grab_keyboard) + gdk_device_ungrab (button->priv->grab_keyboard, GDK_CURRENT_TIME); + if (button->priv->grab_mouse) + gdk_device_ungrab (button->priv->grab_mouse, GDK_CURRENT_TIME); + + button->priv->grab_keyboard = NULL; + button->priv->grab_mouse = NULL; +} + +static EEmoticon * +emoticon_tool_button_get_current_emoticon (EEmoticonChooser *chooser) +{ + EEmoticonToolButtonPrivate *priv; + + priv = E_EMOTICON_TOOL_BUTTON_GET_PRIVATE (chooser); + + if (priv->active_button == NULL) + return NULL; + + return g_object_get_data (G_OBJECT (priv->active_button), "emoticon"); +} + +static void +emoticon_tool_button_set_current_emoticon (EEmoticonChooser *chooser, + EEmoticon *emoticon) +{ + EEmoticonToolButtonPrivate *priv; + GList *list, *iter; + + priv = E_EMOTICON_TOOL_BUTTON_GET_PRIVATE (chooser); + + list = gtk_container_get_children (GTK_CONTAINER (priv->table)); + + for (iter = list; iter != NULL; iter = iter->next) { + GtkWidget *item = iter->data; + EEmoticon *candidate; + + candidate = g_object_get_data (G_OBJECT (item), "emoticon"); + if (candidate == NULL) + continue; + + if (e_emoticon_equal (emoticon, candidate)) { + gtk_button_clicked (GTK_BUTTON (item)); + break; + } + } + + g_list_free (list); +} + +static void +e_emoticon_tool_button_class_init (EEmoticonToolButtonClass *class) +{ + GObjectClass *object_class; + GtkWidgetClass *widget_class; + GtkToggleToolButtonClass *toggle_tool_button_class; + + g_type_class_add_private (class, sizeof (EEmoticonToolButtonPrivate)); + + object_class = G_OBJECT_CLASS (class); + object_class->set_property = emoticon_tool_button_set_property; + object_class->get_property = emoticon_tool_button_get_property; + object_class->dispose = emoticon_tool_button_dispose; + + widget_class = GTK_WIDGET_CLASS (class); + widget_class->button_press_event = emoticon_tool_button_press_event; + + toggle_tool_button_class = GTK_TOGGLE_TOOL_BUTTON_CLASS (class); + toggle_tool_button_class->toggled = emoticon_tool_button_toggled; + + class->popup = emoticon_tool_button_popup; + class->popdown = emoticon_tool_button_popdown; + + g_object_class_override_property ( + object_class, PROP_CURRENT_EMOTICON, "current-emoticon"); + + g_object_class_install_property ( + object_class, + PROP_POPUP_SHOWN, + g_param_spec_boolean ( + "popup-shown", + "Popup Shown", + "Whether the button's dropdown is shown", + FALSE, + G_PARAM_READWRITE)); + + signals[POPUP] = g_signal_new ( + "popup", + G_OBJECT_CLASS_TYPE (class), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (EEmoticonToolButtonClass, popup), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + signals[POPDOWN] = g_signal_new ( + "popdown", + G_OBJECT_CLASS_TYPE (class), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (EEmoticonToolButtonClass, popdown), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + gtk_binding_entry_add_signal ( + gtk_binding_set_by_class (class), + GDK_KEY_Down, GDK_MOD1_MASK, "popup", 0); + gtk_binding_entry_add_signal ( + gtk_binding_set_by_class (class), + GDK_KEY_KP_Down, GDK_MOD1_MASK, "popup", 0); + + gtk_binding_entry_add_signal ( + gtk_binding_set_by_class (class), + GDK_KEY_Up, GDK_MOD1_MASK, "popdown", 0); + gtk_binding_entry_add_signal ( + gtk_binding_set_by_class (class), + GDK_KEY_KP_Up, GDK_MOD1_MASK, "popdown", 0); + gtk_binding_entry_add_signal ( + gtk_binding_set_by_class (class), + GDK_KEY_Escape, 0, "popdown", 0); +} + +static void +e_emoticon_tool_button_interface_init (EEmoticonChooserInterface *interface) +{ + interface->get_current_emoticon = + emoticon_tool_button_get_current_emoticon; + interface->set_current_emoticon = + emoticon_tool_button_set_current_emoticon; +} + +static void +e_emoticon_tool_button_init (EEmoticonToolButton *button) +{ + EEmoticonChooser *chooser; + GtkWidget *toplevel; + GtkWidget *container; + GtkWidget *widget; + GtkWidget *window; + GList *list, *iter; + gint ii; + + button->priv = E_EMOTICON_TOOL_BUTTON_GET_PRIVATE (button); + + /* Build the pop-up window. */ + + window = gtk_window_new (GTK_WINDOW_POPUP); + gtk_window_set_resizable (GTK_WINDOW (window), FALSE); + gtk_window_set_type_hint ( + GTK_WINDOW (window), GDK_WINDOW_TYPE_HINT_COMBO); + button->priv->window = g_object_ref_sink (window); + + toplevel = gtk_widget_get_toplevel (GTK_WIDGET (button)); + if (GTK_IS_WINDOW (toplevel)) { + gtk_window_group_add_window ( + gtk_window_get_group (GTK_WINDOW (toplevel)), + GTK_WINDOW (window)); + gtk_window_set_transient_for ( + GTK_WINDOW (window), GTK_WINDOW (toplevel)); + } + + g_signal_connect_swapped ( + window, "show", + G_CALLBACK (emoticon_tool_button_child_show_cb), button); + g_signal_connect_swapped ( + window, "hide", + G_CALLBACK (emoticon_tool_button_child_hide_cb), button); + g_signal_connect_swapped ( + window, "button-release-event", + G_CALLBACK (emoticon_tool_button_button_release_event_cb), + button); + g_signal_connect_swapped ( + window, "key-press-event", + G_CALLBACK (emoticon_tool_button_child_key_press_event_cb), + button); + + /* Build the pop-up window contents. */ + + widget = gtk_frame_new (NULL); + gtk_frame_set_shadow_type (GTK_FRAME (widget), GTK_SHADOW_OUT); + gtk_container_add (GTK_CONTAINER (window), widget); + gtk_widget_show (widget); + + container = widget; + + widget = gtk_table_new (NUM_ROWS, NUM_COLS, TRUE); + gtk_table_set_row_spacings (GTK_TABLE (widget), 0); + gtk_table_set_col_spacings (GTK_TABLE (widget), 0); + gtk_container_add (GTK_CONTAINER (container), widget); + button->priv->table = g_object_ref (widget); + gtk_widget_show (widget); + + container = widget; + + chooser = E_EMOTICON_CHOOSER (button); + list = e_emoticon_chooser_get_items (); + g_assert (g_list_length (list) <= NUM_ROWS * NUM_COLS); + + for (iter = list, ii = 0; iter != NULL; iter = iter->next, ii++) { + EEmoticon *emoticon = iter->data; + guint left = ii % NUM_COLS; + guint top = ii / NUM_COLS; + gchar *tooltip; + + tooltip = emoticon_tool_button_elide_underscores ( + gettext (emoticon->label)); + + widget = gtk_button_new (); + gtk_button_set_image ( + GTK_BUTTON (widget), + gtk_image_new_from_icon_name ( + emoticon->icon_name, GTK_ICON_SIZE_BUTTON)); + gtk_button_set_relief (GTK_BUTTON (widget), GTK_RELIEF_NONE); + gtk_widget_set_tooltip_text (widget, tooltip); + gtk_widget_show (widget); + + g_object_set_data_full ( + G_OBJECT (widget), "emoticon", + e_emoticon_copy (emoticon), + (GDestroyNotify) e_emoticon_free); + + g_signal_connect_swapped ( + widget, "clicked", + G_CALLBACK (emoticon_tool_button_emoticon_clicked_cb), + button); + + g_signal_connect_swapped ( + widget, "clicked", + G_CALLBACK (e_emoticon_chooser_item_activated), + chooser); + + g_signal_connect_swapped ( + widget, "button-release-event", + G_CALLBACK (emoticon_tool_button_emoticon_release_event_cb), + button); + + gtk_table_attach ( + GTK_TABLE (container), widget, + left, left + 1, top, top + 1, 0, 0, 0, 0); + + g_free (tooltip); + } + + g_list_free (list); +} + +GtkToolItem * +e_emoticon_tool_button_new (void) +{ + return g_object_new (E_TYPE_EMOTICON_TOOL_BUTTON, NULL); +} + +void +e_emoticon_tool_button_popup (EEmoticonToolButton *button) +{ + g_return_if_fail (E_IS_EMOTICON_TOOL_BUTTON (button)); + + g_signal_emit (button, signals[POPUP], 0); +} + +void +e_emoticon_tool_button_popdown (EEmoticonToolButton *button) +{ + g_return_if_fail (E_IS_EMOTICON_TOOL_BUTTON (button)); + + g_signal_emit (button, signals[POPDOWN], 0); +} diff --git a/e-util/e-emoticon-tool-button.h b/e-util/e-emoticon-tool-button.h new file mode 100644 index 0000000000..fc7dc8ef64 --- /dev/null +++ b/e-util/e-emoticon-tool-button.h @@ -0,0 +1,75 @@ +/* + * e-emoticon-tool-button.h + * + * Copyright (C) 2008 Novell, Inc. + * Copyright (C) 2012 Dan Vrátil + * + * 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_EMOTICON_TOOL_BUTTON_H +#define E_EMOTICON_TOOL_BUTTON_H + +#include + +/* Standard GObject macros */ +#define E_TYPE_EMOTICON_TOOL_BUTTON \ + (e_emoticon_tool_button_get_type ()) +#define E_EMOTICON_TOOL_BUTTON(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST \ + ((obj), E_TYPE_EMOTICON_TOOL_BUTTON, EEmoticonToolButton)) +#define E_EMOTICON_TOOL_BUTTON_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_CAST \ + ((cls), E_TYPE_EMOTICON_TOOL_BUTTON, EEmoticonToolButtonClass)) +#define E_IS_EMOTICON_TOOL_BUTTON(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE \ + ((obj), E_TYPE_EMOTICON_TOOL_BUTTON)) +#define E_IS_EMOTICON_TOOL_BUTTON_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_TYPE \ + ((cls), E_TYPE_EMOTICON_TOOL_BUTTON)) +#define E_EMOTICON_TOOL_BUTTON_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS \ + ((obj), E_TYPE_EMOTICON_TOOL_BUTTON, EEmoticonToolButtonClass)) + +G_BEGIN_DECLS + +typedef struct _EEmoticonToolButton EEmoticonToolButton; +typedef struct _EEmoticonToolButtonClass EEmoticonToolButtonClass; +typedef struct _EEmoticonToolButtonPrivate EEmoticonToolButtonPrivate; + +struct _EEmoticonToolButton { + GtkToggleToolButton parent; + EEmoticonToolButtonPrivate *priv; +}; + +struct _EEmoticonToolButtonClass { + GtkToggleToolButtonClass parent_class; + + void (*popup) (EEmoticonToolButton *button); + void (*popdown) (EEmoticonToolButton *button); +}; + +GType e_emoticon_tool_button_get_type (void) G_GNUC_CONST; +GtkToolItem * e_emoticon_tool_button_new (void); +void e_emoticon_tool_button_popup (EEmoticonToolButton *button); +void e_emoticon_tool_button_popdown (EEmoticonToolButton *button); + +G_END_DECLS + +#endif /* E_EMOTICON_TOOL_BUTTON_H */ diff --git a/e-util/e-emoticon.c b/e-util/e-emoticon.c new file mode 100644 index 0000000000..c543e52417 --- /dev/null +++ b/e-util/e-emoticon.c @@ -0,0 +1,118 @@ +/* + * e-emoticon.c + * + * Copyright (C) 2008 Novell, Inc. + * Copyright (C) 2012 Dan Vrátil + * + * 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. + */ + +#include "e-emoticon.h" + +#include + +static EEmoticon * +emoticon_copy (EEmoticon *emoticon) +{ + EEmoticon *copy; + + copy = g_slice_new (EEmoticon); + copy->label = g_strdup (emoticon->label); + copy->icon_name = g_strdup (emoticon->icon_name); + copy->text_face = g_strdup (emoticon->text_face); + + return copy; +} + +static void +emoticon_free (EEmoticon *emoticon) +{ + g_free (emoticon->label); + g_free (emoticon->icon_name); + g_free (emoticon->text_face); + g_slice_free (EEmoticon, emoticon); +} + +GType +e_emoticon_get_type (void) +{ + static GType type = 0; + + if (G_UNLIKELY (type == 0)) + type = g_boxed_type_register_static ( + "EEmoticon", + (GBoxedCopyFunc) emoticon_copy, + (GBoxedFreeFunc) emoticon_free); + + return type; +} + +gboolean +e_emoticon_equal (EEmoticon *emoticon_a, + EEmoticon *emoticon_b) +{ + if (((emoticon_a == NULL) && (emoticon_b != NULL)) || + ((emoticon_a != NULL) && (emoticon_b == NULL))) + return FALSE; + + if (emoticon_a == emoticon_b) + return TRUE; + + if (g_strcmp0 (emoticon_a->label, emoticon_b->label) != 0) + return FALSE; + + if (g_strcmp0 (emoticon_a->icon_name, emoticon_b->icon_name) != 0) + return FALSE; + + if (g_strcmp0 (emoticon_a->text_face, emoticon_b->text_face) != 0) + return FALSE; + + return TRUE; +} + +EEmoticon * +e_emoticon_copy (EEmoticon *emoticon) +{ + return g_boxed_copy (E_TYPE_EMOTICON, emoticon); +} + +void +e_emoticon_free (EEmoticon *emoticon) +{ + g_boxed_free (E_TYPE_EMOTICON, emoticon); +} + +gchar * +e_emoticon_get_uri (EEmoticon *emoticon) +{ + GtkIconInfo *icon_info; + GtkIconTheme *icon_theme; + const gchar *filename; + gchar *uri = NULL; + + icon_theme = gtk_icon_theme_get_default (); + icon_info = gtk_icon_theme_lookup_icon ( + icon_theme, emoticon->icon_name, 16, 0); + g_return_val_if_fail (icon_info != NULL, NULL); + + filename = gtk_icon_info_get_filename (icon_info); + if (filename != NULL) { + uri = g_filename_to_uri (filename, NULL, NULL); + } + gtk_icon_info_free (icon_info); + g_return_val_if_fail (uri != NULL, NULL); + + return uri; +} diff --git a/e-util/e-emoticon.h b/e-util/e-emoticon.h new file mode 100644 index 0000000000..66327ab788 --- /dev/null +++ b/e-util/e-emoticon.h @@ -0,0 +1,53 @@ +/* + * e-emoticon.h + * + * Copyright (C) 2008 Novell, Inc. + * Copyright (C) 2012 Dan Vrátil + * + * 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_EMOTICON_H +#define E_EMOTICON_H + +#include + +#define E_TYPE_EMOTICON \ + (e_emoticon_get_type ()) + +G_BEGIN_DECLS + +typedef struct _EEmoticon EEmoticon; + +struct _EEmoticon { + gchar *label; + gchar *icon_name; + gchar *text_face; +}; + +GType e_emoticon_get_type (void) G_GNUC_CONST; +gboolean e_emoticon_equal (EEmoticon *emoticon_a, + EEmoticon *emoticon_b); +EEmoticon * e_emoticon_copy (EEmoticon *emoticon); +void e_emoticon_free (EEmoticon *emoticon); +gchar * e_emoticon_get_uri (EEmoticon *emoticon); + +G_END_DECLS + +#endif /* E_EMOTICON_H */ diff --git a/e-util/e-focus-tracker.c b/e-util/e-focus-tracker.c index 0879a16c1d..3d3daec9fb 100644 --- a/e-util/e-focus-tracker.c +++ b/e-util/e-focus-tracker.c @@ -28,6 +28,7 @@ #include "e-selectable.h" #include "e-widget-undo.h" +#include "e-html-editor-view.h" #define E_FOCUS_TRACKER_GET_PRIVATE(obj) \ (G_TYPE_INSTANCE_GET_PRIVATE \ @@ -254,6 +255,42 @@ focus_tracker_text_view_update_actions (EFocusTracker *focus_tracker, focus_tracker_update_undo_redo (focus_tracker, GTK_WIDGET (text_view), can_edit_text); } +static void +focus_tracker_editor_update_actions (EFocusTracker *focus_tracker, + EHTMLEditorView *view, + GdkAtom *targets, + gint n_targets) +{ + GtkAction *action; + gboolean can_copy; + gboolean can_cut; + gboolean can_paste; + + g_object_get (view, + "can-copy", &can_copy, + "can-cut", &can_cut, + "can-paste", &can_paste, + NULL); + + action = e_focus_tracker_get_cut_clipboard_action (focus_tracker); + if (action != NULL) { + gtk_action_set_sensitive (action, can_cut); + gtk_action_set_tooltip (action, _("Cut the selection")); + } + + action = e_focus_tracker_get_copy_clipboard_action (focus_tracker); + if (action != NULL) { + gtk_action_set_sensitive (action, can_copy); + gtk_action_set_tooltip (action, _("Copy the selection")); + } + + action = e_focus_tracker_get_paste_clipboard_action (focus_tracker); + if (action != NULL) { + gtk_action_set_sensitive (action, can_paste); + gtk_action_set_tooltip (action, _("Paste the clipboard")); + } +} + static void focus_tracker_selectable_update_actions (EFocusTracker *focus_tracker, ESelectable *selectable, @@ -331,6 +368,11 @@ focus_tracker_targets_received_cb (GtkClipboard *clipboard, focus_tracker, GTK_TEXT_VIEW (focus), targets, n_targets); + else if (E_IS_HTML_EDITOR_VIEW (focus)) + focus_tracker_editor_update_actions ( + focus_tracker, E_HTML_EDITOR_VIEW (focus), + targets, n_targets); + g_object_unref (focus_tracker); } @@ -349,6 +391,9 @@ focus_tracker_set_focus_cb (GtkWindow *window, if (GTK_IS_TEXT_VIEW (focus)) break; + if (E_IS_HTML_EDITOR_VIEW (focus)) + break; + focus = gtk_widget_get_parent (focus); } diff --git a/e-util/e-html-editor-actions.c b/e-util/e-html-editor-actions.c new file mode 100644 index 0000000000..6db3ad48da --- /dev/null +++ b/e-util/e-html-editor-actions.c @@ -0,0 +1,2028 @@ +/* e-html-editor-actions.c + * + * Copyright (C) 2012 Dan Vrátil + * + * 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 +#include +#include +#include + +#include "e-html-editor.h" +#include "e-html-editor-private.h" +#include "e-html-editor-actions.h" +#include "e-html-editor-utils.h" +#include "e-emoticon-action.h" +#include "e-emoticon-chooser.h" +#include "e-image-chooser-dialog.h" +#include "e-spell-checker.h" + +static void +insert_html_file_ready_cb (GFile *file, + GAsyncResult *result, + EHTMLEditor *editor) +{ + EHTMLEditorSelection *selection; + gchar *contents = NULL; + gsize length; + GError *error = NULL; + + g_file_load_contents_finish ( + file, result, &contents, &length, NULL, &error); + if (error != NULL) { + GtkWidget *dialog; + + dialog = gtk_message_dialog_new ( + GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (editor))), + 0, GTK_MESSAGE_ERROR, + GTK_BUTTONS_CLOSE, _("Failed to insert HTML file.")); + gtk_message_dialog_format_secondary_text ( + GTK_MESSAGE_DIALOG (dialog), "%s.", error->message); + gtk_dialog_run (GTK_DIALOG (dialog)); + gtk_widget_destroy (dialog); + + g_clear_error (&error); + g_object_unref (editor); + return; + } + + selection = e_html_editor_view_get_selection ( + e_html_editor_get_view (editor)); + e_html_editor_selection_insert_html (selection, contents); + g_free (contents); + + g_object_unref (editor); +} + +static void +insert_text_file_ready_cb (GFile *file, + GAsyncResult *result, + EHTMLEditor *editor) +{ + EHTMLEditorSelection *selection; + gchar *contents; + gsize length; + GError *error = NULL; + + g_file_load_contents_finish ( + file, result, &contents, &length, NULL, &error); + if (error != NULL) { + GtkWidget *dialog; + + dialog = gtk_message_dialog_new ( + GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (editor))), + 0, GTK_MESSAGE_ERROR, + GTK_BUTTONS_CLOSE, _("Failed to insert text file.")); + gtk_message_dialog_format_secondary_text ( + GTK_MESSAGE_DIALOG (dialog), "%s.", error->message); + gtk_dialog_run (GTK_DIALOG (dialog)); + gtk_widget_destroy (dialog); + + g_clear_error (&error); + g_object_unref (editor); + return; + } + + selection = e_html_editor_view_get_selection ( + e_html_editor_get_view (editor)); + e_html_editor_selection_insert_text (selection, contents); + g_free (contents); + + g_object_unref (editor); +} + +static void +editor_update_static_spell_actions (EHTMLEditor *editor) +{ + ESpellChecker *checker; + EHTMLEditorView *view; + guint count; + + view = e_html_editor_get_view (editor); + checker = e_html_editor_view_get_spell_checker (view); + + count = e_spell_checker_count_active_languages (checker); + + gtk_action_set_visible (ACTION (CONTEXT_SPELL_ADD), count == 1); + gtk_action_set_visible (ACTION (CONTEXT_SPELL_ADD_MENU), count > 1); + gtk_action_set_visible (ACTION (CONTEXT_SPELL_IGNORE), count > 0); + + gtk_action_set_sensitive (ACTION (SPELL_CHECK), count > 0); +} + +/***************************************************************************** + * Action Callbacks + *****************************************************************************/ + +static void +action_context_delete_cell_cb (GtkAction *action, + EHTMLEditor *editor) +{ + WebKitDOMNode *sibling; + WebKitDOMElement *cell; + + g_return_if_fail (editor->priv->table_cell != NULL); + + cell = e_html_editor_dom_node_find_parent_element (editor->priv->table_cell, "TD"); + if (!cell) { + cell = e_html_editor_dom_node_find_parent_element ( + editor->priv->table_cell, "TH"); + } + g_return_if_fail (cell != NULL); + + sibling = webkit_dom_node_get_previous_sibling (WEBKIT_DOM_NODE (cell)); + if (!sibling) { + sibling = webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (cell)); + } + + webkit_dom_node_remove_child ( + webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (cell)), + WEBKIT_DOM_NODE (cell), NULL); + + if (sibling) { + webkit_dom_html_table_cell_element_set_col_span ( + WEBKIT_DOM_HTML_TABLE_CELL_ELEMENT (sibling), + webkit_dom_html_table_cell_element_get_col_span ( + WEBKIT_DOM_HTML_TABLE_CELL_ELEMENT (sibling)) + 1); + } +} + +static void +action_context_delete_column_cb (GtkAction *action, + EHTMLEditor *editor) +{ + WebKitDOMElement *cell, *table; + WebKitDOMHTMLCollection *rows; + gulong index, length, ii; + + g_return_if_fail (editor->priv->table_cell != NULL); + + /* Find TD in which the selection starts */ + cell = e_html_editor_dom_node_find_parent_element (editor->priv->table_cell, "TD"); + if (!cell) { + cell = e_html_editor_dom_node_find_parent_element ( + editor->priv->table_cell, "TH"); + } + g_return_if_fail (cell != NULL); + + table = e_html_editor_dom_node_find_parent_element (WEBKIT_DOM_NODE (cell), "TABLE"); + g_return_if_fail (table != NULL); + + rows = webkit_dom_html_table_element_get_rows ( + WEBKIT_DOM_HTML_TABLE_ELEMENT (table)); + length = webkit_dom_html_collection_get_length (rows); + + index = webkit_dom_html_table_cell_element_get_cell_index ( + WEBKIT_DOM_HTML_TABLE_CELL_ELEMENT (cell)); + + for (ii = 0; ii < length; ii++) { + WebKitDOMNode *row; + + row = webkit_dom_html_collection_item (rows, ii); + + webkit_dom_html_table_row_element_delete_cell ( + WEBKIT_DOM_HTML_TABLE_ROW_ELEMENT (row), index, NULL); + } +} + +static void +action_context_delete_row_cb (GtkAction *action, + EHTMLEditor *editor) +{ + WebKitDOMElement *row; + + g_return_if_fail (editor->priv->table_cell != NULL); + + row = e_html_editor_dom_node_find_parent_element (editor->priv->table_cell, "TR"); + g_return_if_fail (row != NULL); + + webkit_dom_node_remove_child ( + webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (row)), + WEBKIT_DOM_NODE (row), NULL); +} + +static void +action_context_delete_table_cb (GtkAction *action, + EHTMLEditor *editor) +{ + WebKitDOMElement *table; + + g_return_if_fail (editor->priv->table_cell != NULL); + + table = e_html_editor_dom_node_find_parent_element (editor->priv->table_cell, "TABLE"); + g_return_if_fail (table != NULL); + + webkit_dom_node_remove_child ( + webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (table)), + WEBKIT_DOM_NODE (table), NULL); +} + +static void +action_context_insert_column_after_cb (GtkAction *action, + EHTMLEditor *editor) +{ + WebKitDOMElement *cell, *row; + gulong index; + + g_return_if_fail (editor->priv->table_cell != NULL); + + cell = e_html_editor_dom_node_find_parent_element (editor->priv->table_cell, "TD"); + if (!cell) { + cell = e_html_editor_dom_node_find_parent_element ( + editor->priv->table_cell, "TH"); + } + g_return_if_fail (cell != NULL); + + row = e_html_editor_dom_node_find_parent_element (WEBKIT_DOM_NODE (cell), "TR"); + g_return_if_fail (row != NULL); + + /* Get the first row in the table */ + row = WEBKIT_DOM_ELEMENT ( + webkit_dom_node_get_first_child ( + webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (row)))); + + index = webkit_dom_html_table_cell_element_get_cell_index ( + WEBKIT_DOM_HTML_TABLE_CELL_ELEMENT (cell)); + + while (row) { + webkit_dom_html_table_row_element_insert_cell ( + WEBKIT_DOM_HTML_TABLE_ROW_ELEMENT (row), index + 1, NULL); + + row = WEBKIT_DOM_ELEMENT ( + webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (row))); + } +} + +static void +action_context_insert_column_before_cb (GtkAction *action, + EHTMLEditor *editor) +{ + WebKitDOMElement *cell, *row; + gulong index; + + g_return_if_fail (editor->priv->table_cell != NULL); + + cell = e_html_editor_dom_node_find_parent_element (editor->priv->table_cell, "TD"); + if (!cell) { + cell = e_html_editor_dom_node_find_parent_element ( + editor->priv->table_cell, "TH"); + } + g_return_if_fail (cell != NULL); + + row = e_html_editor_dom_node_find_parent_element (WEBKIT_DOM_NODE (cell), "TR"); + g_return_if_fail (row != NULL); + + /* Get the first row in the table */ + row = WEBKIT_DOM_ELEMENT ( + webkit_dom_node_get_first_child ( + webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (row)))); + + index = webkit_dom_html_table_cell_element_get_cell_index ( + WEBKIT_DOM_HTML_TABLE_CELL_ELEMENT (cell)); + + while (row) { + webkit_dom_html_table_row_element_insert_cell ( + WEBKIT_DOM_HTML_TABLE_ROW_ELEMENT (row), index - 1, NULL); + + row = WEBKIT_DOM_ELEMENT ( + webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (row))); + } +} + +static void +action_context_insert_row_above_cb (GtkAction *action, + EHTMLEditor *editor) +{ + WebKitDOMElement *row, *table; + WebKitDOMHTMLCollection *cells; + WebKitDOMHTMLElement *new_row; + gulong index, cell_count, ii; + + g_return_if_fail (editor->priv->table_cell != NULL); + + row = e_html_editor_dom_node_find_parent_element (editor->priv->table_cell, "TR"); + g_return_if_fail (row != NULL); + + table = e_html_editor_dom_node_find_parent_element (WEBKIT_DOM_NODE (row), "TABLE"); + g_return_if_fail (table != NULL); + + index = webkit_dom_html_table_row_element_get_row_index ( + WEBKIT_DOM_HTML_TABLE_ROW_ELEMENT (row)); + + new_row = webkit_dom_html_table_element_insert_row ( + WEBKIT_DOM_HTML_TABLE_ELEMENT (table), index, NULL); + + cells = webkit_dom_html_table_row_element_get_cells ( + WEBKIT_DOM_HTML_TABLE_ROW_ELEMENT (row)); + cell_count = webkit_dom_html_collection_get_length (cells); + for (ii = 0; ii < cell_count; ii++) { + webkit_dom_html_table_row_element_insert_cell ( + WEBKIT_DOM_HTML_TABLE_ROW_ELEMENT (new_row), -1, NULL); + } + +} + +static void +action_context_insert_row_below_cb (GtkAction *action, + EHTMLEditor *editor) +{ + WebKitDOMElement *row, *table; + WebKitDOMHTMLCollection *cells; + WebKitDOMHTMLElement *new_row; + gulong index, cell_count, ii; + + g_return_if_fail (editor->priv->table_cell != NULL); + + row = e_html_editor_dom_node_find_parent_element (editor->priv->table_cell, "TR"); + g_return_if_fail (row != NULL); + + table = e_html_editor_dom_node_find_parent_element (WEBKIT_DOM_NODE (row), "TABLE"); + g_return_if_fail (table != NULL); + + index = webkit_dom_html_table_row_element_get_row_index ( + WEBKIT_DOM_HTML_TABLE_ROW_ELEMENT (row)); + + new_row = webkit_dom_html_table_element_insert_row ( + WEBKIT_DOM_HTML_TABLE_ELEMENT (table), index + 1, NULL); + + cells = webkit_dom_html_table_row_element_get_cells ( + WEBKIT_DOM_HTML_TABLE_ROW_ELEMENT (row)); + cell_count = webkit_dom_html_collection_get_length (cells); + for (ii = 0; ii < cell_count; ii++) { + webkit_dom_html_table_row_element_insert_cell ( + WEBKIT_DOM_HTML_TABLE_ROW_ELEMENT (new_row), -1, NULL); + } +} + +static void +action_context_remove_link_cb (GtkAction *action, + EHTMLEditor *editor) +{ + EHTMLEditorView *view; + EHTMLEditorSelection *selection; + + view = e_html_editor_get_view (editor); + selection = e_html_editor_view_get_selection (view); + + e_html_editor_selection_unlink (selection); +} + +static void +action_context_spell_add_cb (GtkAction *action, + EHTMLEditor *editor) +{ + ESpellChecker *spell_checker; + EHTMLEditorSelection *selection; + gchar *word; + + spell_checker = e_html_editor_view_get_spell_checker ( + editor->priv->html_editor_view); + selection = e_html_editor_view_get_selection (editor->priv->html_editor_view); + + word = e_html_editor_selection_get_caret_word (selection); + if (word && *word) { + e_spell_checker_learn_word (spell_checker, word); + } +} + +static void +action_context_spell_ignore_cb (GtkAction *action, + EHTMLEditor *editor) +{ + ESpellChecker *spell_checker; + EHTMLEditorSelection *selection; + gchar *word; + + spell_checker = e_html_editor_view_get_spell_checker ( + editor->priv->html_editor_view); + selection = e_html_editor_view_get_selection (editor->priv->html_editor_view); + + word = e_html_editor_selection_get_caret_word (selection); + if (word && *word) { + e_spell_checker_ignore_word (spell_checker, word); + } +} + +static void +action_copy_cb (GtkAction *action, + EHTMLEditor *editor) +{ + webkit_web_view_copy_clipboard ( + WEBKIT_WEB_VIEW (e_html_editor_get_view (editor))); +} + +static void +action_cut_cb (GtkAction *action, + EHTMLEditor *editor) +{ + webkit_web_view_cut_clipboard ( + WEBKIT_WEB_VIEW (e_html_editor_get_view (editor))); +} + +static void +action_indent_cb (GtkAction *action, + EHTMLEditor *editor) +{ + e_html_editor_selection_indent (editor->priv->selection); +} + +static void +action_insert_emoticon_cb (GtkAction *action, + EHTMLEditor *editor) +{ + EHTMLEditorView *view; + EEmoticon *emoticon; + + emoticon = e_emoticon_chooser_get_current_emoticon ( + E_EMOTICON_CHOOSER (action)); + g_return_if_fail (emoticon != NULL); + + view = e_html_editor_get_view (editor); + e_html_editor_view_insert_smiley (view, emoticon); +} + +static void +action_insert_html_file_cb (GtkToggleAction *action, + EHTMLEditor *editor) +{ + GtkWidget *dialog; + GtkFileFilter *filter; + + dialog = gtk_file_chooser_dialog_new ( + _("Insert HTML File"), NULL, + GTK_FILE_CHOOSER_ACTION_OPEN, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, NULL); + + filter = gtk_file_filter_new (); + gtk_file_filter_set_name (filter, _("HTML file")); + gtk_file_filter_add_mime_type (filter, "text/html"); + gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter); + + if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT) { + GFile *file; + + file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog)); + + /* XXX Need a way to cancel this. */ + g_file_load_contents_async ( + file, NULL, (GAsyncReadyCallback) + insert_html_file_ready_cb, + g_object_ref (editor)); + + g_object_unref (file); + } + + gtk_widget_destroy (dialog); +} + +static void +action_insert_image_cb (GtkAction *action, + EHTMLEditor *editor) +{ + GtkWidget *dialog; + + dialog = e_image_chooser_dialog_new (_("Insert Image"), NULL); + + if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT) { + EHTMLEditorView *view; + EHTMLEditorSelection *selection; + gchar *uri; + + uri = gtk_file_chooser_get_uri (GTK_FILE_CHOOSER (dialog)); + + view = e_html_editor_get_view (editor); + selection = e_html_editor_view_get_selection (view); + e_html_editor_selection_insert_image (selection, uri); + + g_free (uri); + } + + gtk_widget_destroy (dialog); +} + +static void +action_insert_link_cb (GtkAction *action, + EHTMLEditor *editor) +{ + if (editor->priv->link_dialog == NULL) + editor->priv->link_dialog = + e_html_editor_link_dialog_new (editor); + + gtk_window_present (GTK_WINDOW (editor->priv->link_dialog)); +} + +static void +action_insert_rule_cb (GtkAction *action, + EHTMLEditor *editor) +{ + if (editor->priv->hrule_dialog == NULL) + editor->priv->hrule_dialog = + e_html_editor_hrule_dialog_new (editor); + + gtk_window_present (GTK_WINDOW (editor->priv->hrule_dialog)); +} + +static void +action_insert_table_cb (GtkAction *action, + EHTMLEditor *editor) +{ + if (editor->priv->table_dialog == NULL) + editor->priv->table_dialog = + e_html_editor_table_dialog_new (editor); + + gtk_window_present (GTK_WINDOW (editor->priv->table_dialog)); +} + +static void +action_insert_text_file_cb (GtkAction *action, + EHTMLEditor *editor) +{ + GtkWidget *dialog; + GtkFileFilter *filter; + + dialog = gtk_file_chooser_dialog_new ( + _("Insert text file"), NULL, + GTK_FILE_CHOOSER_ACTION_OPEN, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, NULL); + + filter = gtk_file_filter_new (); + gtk_file_filter_set_name (filter, _("Text file")); + gtk_file_filter_add_mime_type (filter, "text/plain"); + gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter); + + if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT) { + GFile *file; + + file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog)); + + /* XXX Need a way to cancel this. */ + g_file_load_contents_async ( + file, NULL, (GAsyncReadyCallback) + insert_text_file_ready_cb, + g_object_ref (editor)); + + g_object_unref (file); + } + + gtk_widget_destroy (dialog); +} + +static void +action_language_cb (GtkToggleAction *toggle_action, + EHTMLEditor *editor) +{ + ESpellChecker *checker; + EHTMLEditorView *view; + const gchar *language_code; + GtkAction *add_action; + gchar *action_name; + gboolean active; + + view = e_html_editor_get_view (editor); + checker = e_html_editor_view_get_spell_checker (view); + language_code = gtk_action_get_name (GTK_ACTION (toggle_action)); + + active = gtk_toggle_action_get_active (toggle_action); + e_spell_checker_set_language_active (checker, language_code, active); + + /* Update "Add Word To" context menu item visibility. */ + action_name = g_strdup_printf ("context-spell-add-%s", language_code); + add_action = e_html_editor_get_action (editor, action_name); + gtk_action_set_visible (add_action, active); + g_free (action_name); + + editor_update_static_spell_actions (editor); + + g_signal_emit_by_name (editor, "spell-languages-changed"); +} + +static gboolean +update_mode_combobox (gpointer data) +{ + EHTMLEditor *editor = data; + EHTMLEditorView *view; + GtkAction *action; + gboolean is_html; + + if (!E_IS_HTML_EDITOR (editor)) + return FALSE; + + view = e_html_editor_get_view (editor); + is_html = e_html_editor_view_get_html_mode (view); + + action = gtk_action_group_get_action ( + editor->priv->core_actions, "mode-html"); + gtk_radio_action_set_current_value ( + GTK_RADIO_ACTION (action), (is_html ? 1 : 0)); + + return FALSE; +} + +static void +action_mode_cb (GtkRadioAction *action, + GtkRadioAction *current, + EHTMLEditor *editor) +{ + GtkActionGroup *action_group; + EHTMLEditorView *view; + GtkWidget *style_combo_box; + gboolean is_html; + + view = e_html_editor_get_view (editor); + is_html = e_html_editor_view_get_html_mode (view); + + /* This must be done from idle callback, because apparently we can change + * current value in callback of current value change */ + g_idle_add (update_mode_combobox, editor); + + action_group = editor->priv->html_actions; + gtk_action_group_set_sensitive (action_group, is_html); + + action_group = editor->priv->html_context_actions; + gtk_action_group_set_visible (action_group, is_html); + + gtk_widget_set_sensitive (editor->priv->color_combo_box, is_html); + + if (is_html) { + gtk_widget_show (editor->priv->html_toolbar); + } else { + gtk_widget_hide (editor->priv->html_toolbar); + } + + /* Certain paragraph styles are HTML-only. */ + gtk_action_set_sensitive (ACTION (STYLE_H1), is_html); + gtk_action_set_visible (ACTION (STYLE_H1), is_html); + gtk_action_set_sensitive (ACTION (STYLE_H2), is_html); + gtk_action_set_visible (ACTION (STYLE_H2), is_html); + gtk_action_set_sensitive (ACTION (STYLE_H3), is_html); + gtk_action_set_visible (ACTION (STYLE_H3), is_html); + gtk_action_set_sensitive (ACTION (STYLE_H4), is_html); + gtk_action_set_visible (ACTION (STYLE_H4), is_html); + gtk_action_set_sensitive (ACTION (STYLE_H5), is_html); + gtk_action_set_visible (ACTION (STYLE_H5), is_html); + gtk_action_set_sensitive (ACTION (STYLE_H6), is_html); + gtk_action_set_visible (ACTION (STYLE_H6), is_html); + gtk_action_set_sensitive (ACTION (STYLE_ADDRESS), is_html); + gtk_action_set_visible (ACTION (STYLE_ADDRESS), is_html); + + /* Hide them from the action combo box as well */ + style_combo_box = e_html_editor_get_style_combo_box (editor); + e_action_combo_box_update_model (E_ACTION_COMBO_BOX (style_combo_box)); +} + +static void +action_paste_cb (GtkAction *action, + EHTMLEditor *editor) +{ + EHTMLEditorView *view = e_html_editor_get_view (editor); + + /* Paste only if WebView has focus */ + if (gtk_widget_has_focus (GTK_WIDGET (view))) { + webkit_web_view_paste_clipboard ( + WEBKIT_WEB_VIEW (view)); + + e_html_editor_view_force_spell_check (view); + } +} + +static void +action_paste_quote_cb (GtkAction *action, + EHTMLEditor *editor) +{ + e_html_editor_view_paste_clipboard_quoted ( + e_html_editor_get_view (editor)); + + e_html_editor_view_force_spell_check ( + e_html_editor_get_view (editor)); +} + +static void +action_properties_cell_cb (GtkAction *action, + EHTMLEditor *editor) +{ + if (editor->priv->cell_dialog == NULL) { + editor->priv->cell_dialog = + e_html_editor_cell_dialog_new (editor); + } + + e_html_editor_cell_dialog_show ( + E_HTML_EDITOR_CELL_DIALOG (editor->priv->cell_dialog), + editor->priv->table_cell); +} + +static void +action_properties_image_cb (GtkAction *action, + EHTMLEditor *editor) +{ + if (editor->priv->image_dialog == NULL) { + editor->priv->image_dialog = + e_html_editor_image_dialog_new (editor); + } + + e_html_editor_image_dialog_show ( + E_HTML_EDITOR_IMAGE_DIALOG (editor->priv->image_dialog), + editor->priv->image); +} + +static void +action_properties_link_cb (GtkAction *action, + EHTMLEditor *editor) +{ + if (editor->priv->link_dialog == NULL) { + editor->priv->link_dialog = + e_html_editor_link_dialog_new (editor); + } + + gtk_window_present (GTK_WINDOW (editor->priv->link_dialog)); +} + +static void +action_properties_page_cb (GtkAction *action, + EHTMLEditor *editor) +{ + if (editor->priv->page_dialog == NULL) { + editor->priv->page_dialog = + e_html_editor_page_dialog_new (editor); + } + + gtk_window_present (GTK_WINDOW (editor->priv->page_dialog)); +} + +static void +action_properties_paragraph_cb (GtkAction *action, + EHTMLEditor *editor) +{ + if (editor->priv->paragraph_dialog == NULL) { + editor->priv->paragraph_dialog = + e_html_editor_paragraph_dialog_new (editor); + } + + gtk_window_present (GTK_WINDOW (editor->priv->paragraph_dialog)); +} + +static void +action_properties_rule_cb (GtkAction *action, + EHTMLEditor *editor) +{ + if (editor->priv->hrule_dialog == NULL) { + editor->priv->hrule_dialog = + e_html_editor_hrule_dialog_new (editor); + } + + gtk_window_present (GTK_WINDOW (editor->priv->hrule_dialog)); +} + +static void +action_properties_table_cb (GtkAction *action, + EHTMLEditor *editor) +{ + if (editor->priv->table_dialog == NULL) { + editor->priv->table_dialog = + e_html_editor_table_dialog_new (editor); + } + + gtk_window_present (GTK_WINDOW (editor->priv->table_dialog)); +} + +static void +action_properties_text_cb (GtkAction *action, + EHTMLEditor *editor) +{ + if (editor->priv->text_dialog == NULL) { + editor->priv->text_dialog = + e_html_editor_text_dialog_new (editor); + } + + gtk_window_present (GTK_WINDOW (editor->priv->text_dialog)); +} + +static void +action_redo_cb (GtkAction *action, + EHTMLEditor *editor) +{ + webkit_web_view_redo ( + WEBKIT_WEB_VIEW (e_html_editor_get_view (editor))); +} + +static void +action_select_all_cb (GtkAction *action, + EHTMLEditor *editor) +{ + webkit_web_view_select_all ( + WEBKIT_WEB_VIEW (e_html_editor_get_view (editor))); +} + +static void +action_show_find_cb (GtkAction *action, + EHTMLEditor *editor) +{ + if (editor->priv->find_dialog == NULL) { + editor->priv->find_dialog = e_html_editor_find_dialog_new (editor); + gtk_action_set_sensitive (ACTION (FIND_AGAIN), TRUE); + } + + gtk_window_present (GTK_WINDOW (editor->priv->find_dialog)); +} + +static void +action_find_again_cb (GtkAction *action, + EHTMLEditor *editor) +{ + if (editor->priv->find_dialog == NULL) { + return; + } + + e_html_editor_find_dialog_find_next ( + E_HTML_EDITOR_FIND_DIALOG (editor->priv->find_dialog)); +} + +static void +action_show_replace_cb (GtkAction *action, + EHTMLEditor *editor) +{ + if (editor->priv->replace_dialog == NULL) { + editor->priv->replace_dialog = + e_html_editor_replace_dialog_new (editor); + } + + gtk_window_present (GTK_WINDOW (editor->priv->replace_dialog)); +} + +static void +action_spell_check_cb (GtkAction *action, + EHTMLEditor *editor) +{ + if (editor->priv->spell_check_dialog == NULL) { + editor->priv->spell_check_dialog = + e_html_editor_spell_check_dialog_new (editor); + } + + gtk_window_present (GTK_WINDOW (editor->priv->spell_check_dialog)); +} + +static void +action_undo_cb (GtkAction *action, + EHTMLEditor *editor) +{ + webkit_web_view_undo ( + WEBKIT_WEB_VIEW (e_html_editor_get_view (editor))); +} + +static void +action_unindent_cb (GtkAction *action, + EHTMLEditor *editor) +{ + e_html_editor_selection_unindent (editor->priv->selection); +} + +static void +action_wrap_lines_cb (GtkAction *action, + EHTMLEditor *editor) +{ + e_html_editor_selection_wrap_lines (editor->priv->selection); +} + +static void +action_show_webkit_inspector_cb (GtkAction *action, + EHTMLEditor *editor) +{ + WebKitWebInspector *inspector; + EHTMLEditorView *view; + + view = e_html_editor_get_view (editor); + inspector = webkit_web_view_get_inspector (WEBKIT_WEB_VIEW (view)); + + webkit_web_inspector_show (inspector); +} + +/***************************************************************************** + * Core Actions + * + * These actions are always enabled. + *****************************************************************************/ + +static GtkActionEntry core_entries[] = { + + { "copy", + GTK_STOCK_COPY, + N_("_Copy"), + "c", + N_("Copy selected text to the clipboard"), + G_CALLBACK (action_copy_cb) }, + + { "cut", + GTK_STOCK_CUT, + N_("Cu_t"), + "x", + N_("Cut selected text to the clipboard"), + G_CALLBACK (action_cut_cb) }, + + { "indent", + GTK_STOCK_INDENT, + N_("_Increase Indent"), + "bracketright", + N_("Increase Indent"), + G_CALLBACK (action_indent_cb) }, + + { "insert-html-file", + NULL, + N_("_HTML File..."), + NULL, + NULL, + G_CALLBACK (action_insert_html_file_cb) }, + + { "insert-text-file", + NULL, + N_("Te_xt File..."), + NULL, + NULL, + G_CALLBACK (action_insert_text_file_cb) }, + + { "paste", + GTK_STOCK_PASTE, + N_("_Paste"), + "v", + N_("Paste text from the clipboard"), + G_CALLBACK (action_paste_cb) }, + + { "paste-quote", + NULL, + N_("Paste _Quotation"), + "v", + NULL, + G_CALLBACK (action_paste_quote_cb) }, + + { "redo", + GTK_STOCK_REDO, + N_("_Redo"), + "z", + N_("Redo the last undone action"), + G_CALLBACK (action_redo_cb) }, + + { "select-all", + GTK_STOCK_SELECT_ALL, + N_("Select _All"), + "a", + NULL, + G_CALLBACK (action_select_all_cb) }, + + { "show-find", + GTK_STOCK_FIND, + N_("_Find..."), + "f", + N_("Search for text"), + G_CALLBACK (action_show_find_cb) }, + + { "find-again", + NULL, + N_("Find A_gain"), + "g", + NULL, + G_CALLBACK (action_find_again_cb) }, + + { "show-replace", + GTK_STOCK_FIND_AND_REPLACE, + N_("Re_place..."), + "h", + N_("Search for and replace text"), + G_CALLBACK (action_show_replace_cb) }, + + { "spell-check", + GTK_STOCK_SPELL_CHECK, + N_("Check _Spelling..."), + "F7", + NULL, + G_CALLBACK (action_spell_check_cb) }, + + { "undo", + GTK_STOCK_UNDO, + N_("_Undo"), + "z", + N_("Undo the last action"), + G_CALLBACK (action_undo_cb) }, + + { "unindent", + GTK_STOCK_UNINDENT, + N_("_Decrease Indent"), + "bracketleft", + N_("Decrease Indent"), + G_CALLBACK (action_unindent_cb) }, + + { "wrap-lines", + NULL, + N_("_Wrap Lines"), + "k", + NULL, + G_CALLBACK (action_wrap_lines_cb) }, + + { "webkit-inspector", + NULL, + N_("Open Inspector"), + NULL, + NULL, + G_CALLBACK (action_show_webkit_inspector_cb) }, + + /* Menus */ + + { "edit-menu", + NULL, + N_("_Edit"), + NULL, + NULL, + NULL }, + + { "file-menu", + NULL, + N_("_File"), + NULL, + NULL, + NULL }, + + { "format-menu", + NULL, + N_("For_mat"), + NULL, + NULL, + NULL }, + + { "paragraph-style-menu", + NULL, + N_("_Paragraph Style"), + NULL, + NULL, + NULL }, + + { "insert-menu", + NULL, + N_("_Insert"), + NULL, + NULL, + NULL }, + + { "justify-menu", + NULL, + N_("_Alignment"), + NULL, + NULL, + NULL }, + + { "language-menu", + NULL, + N_("Current _Languages"), + NULL, + NULL, + NULL }, + + { "view-menu", + NULL, + N_("_View"), + NULL, + NULL, + NULL } +}; + +static GtkRadioActionEntry core_justify_entries[] = { + + { "justify-center", + GTK_STOCK_JUSTIFY_CENTER, + N_("_Center"), + "e", + N_("Center Alignment"), + E_HTML_EDITOR_SELECTION_ALIGNMENT_CENTER }, + + { "justify-left", + GTK_STOCK_JUSTIFY_LEFT, + N_("_Left"), + "l", + N_("Left Alignment"), + E_HTML_EDITOR_SELECTION_ALIGNMENT_LEFT }, + + { "justify-right", + GTK_STOCK_JUSTIFY_RIGHT, + N_("_Right"), + "r", + N_("Right Alignment"), + E_HTML_EDITOR_SELECTION_ALIGNMENT_RIGHT } +}; + +static GtkRadioActionEntry core_mode_entries[] = { + + { "mode-html", + NULL, + N_("_HTML"), + NULL, + N_("HTML editing mode"), + TRUE }, /* e_html_editor_view_set_html_mode */ + + { "mode-plain", + NULL, + N_("Plain _Text"), + NULL, + N_("Plain text editing mode"), + FALSE } /* e_html_editor_view_set_html_mode */ +}; + +static GtkRadioActionEntry core_style_entries[] = { + + { "style-normal", + NULL, + N_("_Normal"), + "0", + NULL, + E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_PARAGRAPH }, + + { "style-h1", + NULL, + N_("Header _1"), + "1", + NULL, + E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_H1 }, + + { "style-h2", + NULL, + N_("Header _2"), + "2", + NULL, + E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_H2 }, + + { "style-h3", + NULL, + N_("Header _3"), + "3", + NULL, + E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_H3 }, + + { "style-h4", + NULL, + N_("Header _4"), + "4", + NULL, + E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_H4 }, + + { "style-h5", + NULL, + N_("Header _5"), + "5", + NULL, + E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_H5 }, + + { "style-h6", + NULL, + N_("Header _6"), + "6", + NULL, + E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_H6 }, + + { "style-preformat", + NULL, + N_("_Preformatted"), + "7", + NULL, + E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_PRE }, + + { "style-address", + NULL, + N_("A_ddress"), + "8", + NULL, + E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_ADDRESS }, + + { "style-blockquote", + NULL, + N_("_Blockquote"), + "9", + NULL, + E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_BLOCKQUOTE }, + + { "style-list-bullet", + NULL, + N_("_Bulleted List"), + NULL, + NULL, + E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_UNORDERED_LIST }, + + { "style-list-roman", + NULL, + N_("_Roman Numeral List"), + NULL, + NULL, + E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_ORDERED_LIST_ROMAN }, + + { "style-list-number", + NULL, + N_("Numbered _List"), + NULL, + NULL, + E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_ORDERED_LIST }, + + { "style-list-alpha", + NULL, + N_("_Alphabetical List"), + NULL, + NULL, + E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_ORDERED_LIST_ALPHA } +}; + +/***************************************************************************** + * Core Actions (HTML only) + * + * These actions are only enabled in HTML mode. + *****************************************************************************/ + +static GtkActionEntry html_entries[] = { + + { "insert-image", + "insert-image", + N_("_Image..."), + NULL, + N_("Insert Image"), + G_CALLBACK (action_insert_image_cb) }, + + { "insert-link", + "insert-link", + N_("_Link..."), + NULL, + N_("Insert Link"), + G_CALLBACK (action_insert_link_cb) }, + + { "insert-rule", + "stock_insert-rule", + /* Translators: 'Rule' here means a horizontal line in an HTML text */ + N_("_Rule..."), + NULL, + /* Translators: 'Rule' here means a horizontal line in an HTML text */ + N_("Insert Rule"), + G_CALLBACK (action_insert_rule_cb) }, + + { "insert-table", + "stock_insert-table", + N_("_Table..."), + NULL, + N_("Insert Table"), + G_CALLBACK (action_insert_table_cb) }, + + { "properties-cell", + NULL, + N_("_Cell..."), + NULL, + NULL, + G_CALLBACK (action_properties_cell_cb) }, + + { "properties-image", + NULL, + N_("_Image..."), + NULL, + NULL, + G_CALLBACK (action_properties_image_cb) }, + + { "properties-link", + NULL, + N_("_Link..."), + NULL, + NULL, + G_CALLBACK (action_properties_link_cb) }, + + { "properties-page", + NULL, + N_("Pa_ge..."), + NULL, + NULL, + G_CALLBACK (action_properties_page_cb) }, + + { "properties-rule", + NULL, + /* Translators: 'Rule' here means a horizontal line in an HTML text */ + N_("_Rule..."), + NULL, + NULL, + G_CALLBACK (action_properties_rule_cb) }, + + { "properties-table", + NULL, + N_("_Table..."), + NULL, + NULL, + G_CALLBACK (action_properties_table_cb) }, + + /* Menus */ + + { "font-size-menu", + NULL, + N_("Font _Size"), + NULL, + NULL, + NULL }, + + { "font-style-menu", + NULL, + N_("_Font Style"), + NULL, + NULL, + NULL }, +}; + +static GtkToggleActionEntry html_toggle_entries[] = { + + { "bold", + GTK_STOCK_BOLD, + N_("_Bold"), + "b", + N_("Bold"), + NULL, + FALSE }, + + { "italic", + GTK_STOCK_ITALIC, + N_("_Italic"), + "i", + N_("Italic"), + NULL, + FALSE }, + + { "monospaced", + "stock_text-monospaced", + N_("_Plain Text"), + "t", + N_("Plain Text"), + NULL, + FALSE }, + + { "strikethrough", + GTK_STOCK_STRIKETHROUGH, + N_("_Strikethrough"), + NULL, + N_("Strikethrough"), + NULL, + FALSE }, + + { "underline", + GTK_STOCK_UNDERLINE, + N_("_Underline"), + "u", + N_("Underline"), + NULL, + FALSE } +}; + +static GtkRadioActionEntry html_size_entries[] = { + + { "size-minus-two", + NULL, + /* Translators: This is a font size level. It is shown on a tool bar. Please keep it as short as possible. */ + N_("-2"), + NULL, + NULL, + E_HTML_EDITOR_SELECTION_FONT_SIZE_TINY }, + + { "size-minus-one", + NULL, + /* Translators: This is a font size level. It is shown on a tool bar. Please keep it as short as possible. */ + N_("-1"), + NULL, + NULL, + E_HTML_EDITOR_SELECTION_FONT_SIZE_SMALL }, + + { "size-plus-zero", + NULL, + /* Translators: This is a font size level. It is shown on a tool bar. Please keep it as short as possible. */ + N_("+0"), + NULL, + NULL, + E_HTML_EDITOR_SELECTION_FONT_SIZE_NORMAL }, + + { "size-plus-one", + NULL, + /* Translators: This is a font size level. It is shown on a tool bar. Please keep it as short as possible. */ + N_("+1"), + NULL, + NULL, + E_HTML_EDITOR_SELECTION_FONT_SIZE_BIG }, + + { "size-plus-two", + NULL, + /* Translators: This is a font size level. It is shown on a tool bar. Please keep it as short as possible. */ + N_("+2"), + NULL, + NULL, + E_HTML_EDITOR_SELECTION_FONT_SIZE_BIGGER }, + + { "size-plus-three", + NULL, + /* Translators: This is a font size level. It is shown on a tool bar. Please keep it as short as possible. */ + N_("+3"), + NULL, + NULL, + E_HTML_EDITOR_SELECTION_FONT_SIZE_LARGE }, + + { "size-plus-four", + NULL, + /* Translators: This is a font size level. It is shown on a tool bar. Please keep it as short as possible. */ + N_("+4"), + NULL, + NULL, + E_HTML_EDITOR_SELECTION_FONT_SIZE_VERY_LARGE } +}; + +/***************************************************************************** + * Context Menu Actions + * + * These require separate action entries so we can toggle their visiblity + * rather than their sensitivity as we do with main menu / toolbar actions. + * Note that some of these actions use the same callback function as their + * non-context sensitive counterparts. + *****************************************************************************/ + +static GtkActionEntry context_entries[] = { + + { "context-delete-cell", + NULL, + N_("Cell Contents"), + NULL, + NULL, + G_CALLBACK (action_context_delete_cell_cb) }, + + { "context-delete-column", + NULL, + N_("Column"), + NULL, + NULL, + G_CALLBACK (action_context_delete_column_cb) }, + + { "context-delete-row", + NULL, + N_("Row"), + NULL, + NULL, + G_CALLBACK (action_context_delete_row_cb) }, + + { "context-delete-table", + NULL, + N_("Table"), + NULL, + NULL, + G_CALLBACK (action_context_delete_table_cb) }, + + /* Menus */ + + { "context-delete-table-menu", + NULL, + /* Translators: Popup menu item caption, containing all the Delete options for a table */ + N_("Table Delete"), + NULL, + NULL, + NULL }, + + { "context-input-methods-menu", + NULL, + N_("Input Methods"), + NULL, + NULL, + NULL }, + + { "context-insert-table-menu", + NULL, + /* Translators: Popup menu item caption, containing all the Insert options for a table */ + N_("Table Insert"), + NULL, + NULL, + NULL }, + + { "context-properties-menu", + NULL, + N_("Properties"), + NULL, + NULL, + NULL }, +}; + +/***************************************************************************** + * Context Menu Actions (HTML only) + * + * These actions are never visible in plain-text mode. Note that some + * of them use the same callback function as their non-context sensitive + * counterparts. + *****************************************************************************/ + +static GtkActionEntry html_context_entries[] = { + + { "context-insert-column-after", + NULL, + N_("Column After"), + NULL, + NULL, + G_CALLBACK (action_context_insert_column_after_cb) }, + + { "context-insert-column-before", + NULL, + N_("Column Before"), + NULL, + NULL, + G_CALLBACK (action_context_insert_column_before_cb) }, + + { "context-insert-link", + NULL, + N_("Insert _Link"), + NULL, + NULL, + G_CALLBACK (action_insert_link_cb) }, + + { "context-insert-row-above", + NULL, + N_("Row Above"), + NULL, + NULL, + G_CALLBACK (action_context_insert_row_above_cb) }, + + { "context-insert-row-below", + NULL, + N_("Row Below"), + NULL, + NULL, + G_CALLBACK (action_context_insert_row_below_cb) }, + + { "context-insert-table", + NULL, + N_("Table"), + NULL, + NULL, + G_CALLBACK (action_insert_table_cb) }, + + { "context-properties-cell", + NULL, + N_("Cell..."), + NULL, + NULL, + G_CALLBACK (action_properties_cell_cb) }, + + { "context-properties-image", + NULL, + N_("Image..."), + NULL, + NULL, + G_CALLBACK (action_properties_image_cb) }, + + { "context-properties-link", + NULL, + N_("Link..."), + NULL, + NULL, + G_CALLBACK (action_properties_link_cb) }, + + { "context-properties-page", + NULL, + N_("Page..."), + NULL, + NULL, + G_CALLBACK (action_properties_page_cb) }, + + { "context-properties-paragraph", + NULL, + N_("Paragraph..."), + NULL, + NULL, + G_CALLBACK (action_properties_paragraph_cb) }, + + { "context-properties-rule", + NULL, + /* Translators: 'Rule' here means a horizontal line in an HTML text */ + N_("Rule..."), + NULL, + NULL, + G_CALLBACK (action_properties_rule_cb) }, + + { "context-properties-table", + NULL, + N_("Table..."), + NULL, + NULL, + G_CALLBACK (action_properties_table_cb) }, + + { "context-properties-text", + NULL, + N_("Text..."), + NULL, + NULL, + G_CALLBACK (action_properties_text_cb) }, + + { "context-remove-link", + NULL, + N_("Remove Link"), + NULL, + NULL, + G_CALLBACK (action_context_remove_link_cb) } +}; + +/***************************************************************************** + * Context Menu Actions (spell checking only) + * + * These actions are only visible when the word underneath the cursor is + * misspelled. + *****************************************************************************/ + +static GtkActionEntry spell_context_entries[] = { + + { "context-spell-add", + NULL, + N_("Add Word to Dictionary"), + NULL, + NULL, + G_CALLBACK (action_context_spell_add_cb) }, + + { "context-spell-ignore", + NULL, + N_("Ignore Misspelled Word"), + NULL, + NULL, + G_CALLBACK (action_context_spell_ignore_cb) }, + + { "context-spell-add-menu", + NULL, + N_("Add Word To"), + NULL, + NULL, + NULL }, + + /* Menus */ + + { "context-more-suggestions-menu", + NULL, + N_("More Suggestions"), + NULL, + NULL, + NULL } +}; + +static void +editor_actions_setup_languages_menu (EHTMLEditor *editor) +{ + ESpellChecker *checker; + EHTMLEditorView *view; + GtkUIManager *manager; + GtkActionGroup *action_group; + GList *list, *link; + guint merge_id; + + manager = editor->priv->manager; + action_group = editor->priv->language_actions; + view = e_html_editor_get_view (editor); + checker = e_html_editor_view_get_spell_checker (view); + merge_id = gtk_ui_manager_new_merge_id (manager); + + list = e_spell_checker_list_available_dicts (checker); + + for (link = list; link != NULL; link = g_list_next (link)) { + ESpellDictionary *dictionary = link->data; + GtkToggleAction *action; + gboolean active; + + action = gtk_toggle_action_new ( + e_spell_dictionary_get_code (dictionary), + e_spell_dictionary_get_name (dictionary), + NULL, NULL); + + /* Do this BEFORE connecting to the "toggled" signal. + * We're not prepared to invoke the signal handler yet. + * The "Add Word To" actions have not yet been added. */ + active = e_spell_checker_get_language_active ( + checker, e_spell_dictionary_get_code (dictionary)); + gtk_toggle_action_set_active (action, active); + + g_signal_connect ( + action, "toggled", + G_CALLBACK (action_language_cb), editor); + + gtk_action_group_add_action ( + action_group, GTK_ACTION (action)); + + g_object_unref (action); + + gtk_ui_manager_add_ui ( + manager, merge_id, + "/main-menu/edit-menu/language-menu", + e_spell_dictionary_get_code (dictionary), + e_spell_dictionary_get_code (dictionary), + GTK_UI_MANAGER_AUTO, FALSE); + } + + g_list_free (list); +} + +static void +editor_actions_setup_spell_check_menu (EHTMLEditor *editor) +{ + ESpellChecker *checker; + GtkUIManager *manager; + GtkActionGroup *action_group; + GList *available_dicts, *iter; + guint merge_id; + + manager = editor->priv->manager; + action_group = editor->priv->spell_check_actions;; + checker = e_html_editor_view_get_spell_checker (editor->priv->html_editor_view); + available_dicts = e_spell_checker_list_available_dicts (checker); + merge_id = gtk_ui_manager_new_merge_id (manager); + + for (iter = available_dicts; iter; iter = iter->next) { + ESpellDictionary *dictionary = iter->data; + GtkAction *action; + const gchar *code; + const gchar *name; + gchar *action_label; + gchar *action_name; + + code = e_spell_dictionary_get_code (dictionary); + name = e_spell_dictionary_get_name (dictionary); + + /* Add a suggestion menu. */ + action_name = g_strdup_printf ( + "context-spell-suggest-%s-menu", code); + + action = gtk_action_new (action_name, name, NULL, NULL); + gtk_action_group_add_action (action_group, action); + g_object_unref (action); + + gtk_ui_manager_add_ui ( + manager, merge_id, + "/context-menu/context-spell-suggest", + action_name, action_name, + GTK_UI_MANAGER_MENU, FALSE); + + g_free (action_name); + + /* Add an item to the "Add Word To" menu. */ + action_name = g_strdup_printf ("context-spell-add-%s", code); + /* Translators: %s will be replaced with the actual dictionary + * name, where a user can add a word to. This is part of an + * "Add Word To" submenu. */ + action_label = g_strdup_printf (_("%s Dictionary"), name); + + action = gtk_action_new ( + action_name, action_label, NULL, NULL); + + g_signal_connect ( + action, "activate", + G_CALLBACK (action_context_spell_add_cb), editor); + + /* Visibility is dependent on whether the + * corresponding language action is active. */ + gtk_action_set_visible (action, FALSE); + + gtk_action_group_add_action (action_group, action); + + g_object_unref (action); + + gtk_ui_manager_add_ui ( + manager, merge_id, + "/context-menu/context-spell-add-menu", + action_name, action_name, + GTK_UI_MANAGER_AUTO, FALSE); + + g_free (action_label); + g_free (action_name); + } + + g_list_free (available_dicts); +} + +void +editor_actions_init (EHTMLEditor *editor) +{ + GtkAction *action; + GtkActionGroup *action_group; + GtkUIManager *manager; + const gchar *domain; + EHTMLEditorView *view; + GSettings *settings; + + g_return_if_fail (E_IS_HTML_EDITOR (editor)); + + manager = e_html_editor_get_ui_manager (editor); + domain = GETTEXT_PACKAGE; + view = e_html_editor_get_view (editor); + + /* Core Actions */ + action_group = editor->priv->core_actions; + gtk_action_group_set_translation_domain (action_group, domain); + gtk_action_group_add_actions ( + action_group, core_entries, + G_N_ELEMENTS (core_entries), editor); + gtk_action_group_add_radio_actions ( + action_group, core_justify_entries, + G_N_ELEMENTS (core_justify_entries), + E_HTML_EDITOR_SELECTION_ALIGNMENT_LEFT, + NULL, NULL); + gtk_action_group_add_radio_actions ( + action_group, core_mode_entries, + G_N_ELEMENTS (core_mode_entries), + TRUE, + G_CALLBACK (action_mode_cb), editor); + gtk_action_group_add_radio_actions ( + action_group, core_style_entries, + G_N_ELEMENTS (core_style_entries), + E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_PARAGRAPH, + NULL, NULL); + gtk_ui_manager_insert_action_group (manager, action_group, 0); + + action = gtk_action_group_get_action (action_group, "mode-html"); + g_object_bind_property ( + view, "html-mode", + action, "current-value", + G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE); + + /* Synchronize wiget mode with the buttons */ + e_html_editor_view_set_html_mode (view, TRUE); + + /* Face Action */ + action = e_emoticon_action_new ( + "insert-emoticon", _("_Emoticon"), + _("Insert Emoticon"), NULL); + g_object_set (action, "icon-name", "face-smile", NULL); + g_signal_connect ( + action, "item-activated", + G_CALLBACK (action_insert_emoticon_cb), editor); + gtk_action_group_add_action (action_group, action); + g_object_unref (action); + + /* Core Actions (HTML only) */ + action_group = editor->priv->html_actions; + gtk_action_group_set_translation_domain (action_group, domain); + gtk_action_group_add_actions ( + action_group, html_entries, + G_N_ELEMENTS (html_entries), editor); + gtk_action_group_add_toggle_actions ( + action_group, html_toggle_entries, + G_N_ELEMENTS (html_toggle_entries), editor); + gtk_action_group_add_radio_actions ( + action_group, html_size_entries, + G_N_ELEMENTS (html_size_entries), + E_HTML_EDITOR_SELECTION_FONT_SIZE_NORMAL, + NULL, NULL); + gtk_ui_manager_insert_action_group (manager, action_group, 0); + + /* Context Menu Actions */ + action_group = editor->priv->context_actions; + gtk_action_group_set_translation_domain (action_group, domain); + gtk_action_group_add_actions ( + action_group, context_entries, + G_N_ELEMENTS (context_entries), editor); + gtk_ui_manager_insert_action_group (manager, action_group, 0); + + /* Context Menu Actions (HTML only) */ + action_group = editor->priv->html_context_actions; + gtk_action_group_set_translation_domain (action_group, domain); + gtk_action_group_add_actions ( + action_group, html_context_entries, + G_N_ELEMENTS (html_context_entries), editor); + gtk_ui_manager_insert_action_group (manager, action_group, 0); + + /* Context Menu Actions (spell check only) */ + action_group = editor->priv->spell_check_actions; + gtk_action_group_set_translation_domain (action_group, domain); + gtk_action_group_add_actions ( + action_group, spell_context_entries, + G_N_ELEMENTS (spell_context_entries), editor); + gtk_ui_manager_insert_action_group (manager, action_group, 0); + + /* Language actions are generated dynamically. */ + editor_actions_setup_languages_menu (editor); + action_group = editor->priv->language_actions; + gtk_ui_manager_insert_action_group (manager, action_group, 0); + + /* Some spell check actions are generated dynamically. */ + action_group = editor->priv->suggestion_actions; + editor_actions_setup_spell_check_menu (editor); + gtk_ui_manager_insert_action_group (manager, action_group, 0); + + /* Do this after all language actions are initialized. */ + editor_update_static_spell_actions (editor); + + /* Fine Tuning */ + + g_object_set ( + G_OBJECT (ACTION (SHOW_FIND)), + "short-label", _("_Find"), NULL); + g_object_set ( + G_OBJECT (ACTION (SHOW_REPLACE)), + "short-label", _("Re_place"), NULL); + g_object_set ( + G_OBJECT (ACTION (INSERT_IMAGE)), + "short-label", _("_Image"), NULL); + g_object_set ( + G_OBJECT (ACTION (INSERT_LINK)), + "short-label", _("_Link"), NULL); + g_object_set ( + G_OBJECT (ACTION (INSERT_RULE)), + /* Translators: 'Rule' here means a horizontal line in an HTML text */ + "short-label", _("_Rule"), NULL); + g_object_set ( + G_OBJECT (ACTION (INSERT_TABLE)), + "short-label", _("_Table"), NULL); + + gtk_action_set_sensitive (ACTION (UNINDENT), FALSE); + gtk_action_set_sensitive (ACTION (FIND_AGAIN), FALSE); + gtk_action_set_sensitive (ACTION (SPELL_CHECK), FALSE); + + g_object_bind_property ( + view, "can-redo", + ACTION (REDO), "sensitive", + G_BINDING_SYNC_CREATE); + g_object_bind_property ( + view, "can-undo", + ACTION (UNDO), "sensitive", + G_BINDING_SYNC_CREATE); + g_object_bind_property ( + view, "can-copy", + ACTION (COPY), "sensitive", + G_BINDING_SYNC_CREATE); + g_object_bind_property ( + view, "can-cut", + ACTION (CUT), "sensitive", + G_BINDING_SYNC_CREATE); + g_object_bind_property ( + view, "can-paste", + ACTION (PASTE), "sensitive", + G_BINDING_SYNC_CREATE); + + /* This is connected to JUSTIFY_LEFT action only, but + * it automatically applies on all actions in the group. */ + g_object_bind_property ( + editor->priv->selection, "alignment", + ACTION (JUSTIFY_LEFT), "current-value", + G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL); + g_object_bind_property ( + editor->priv->selection, "bold", + ACTION (BOLD), "active", + G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL); + g_object_bind_property ( + editor->priv->selection, "font-size", + ACTION (FONT_SIZE_GROUP), "current-value", + G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL); + g_object_bind_property ( + editor->priv->selection, "block-format", + ACTION (STYLE_NORMAL), "current-value", + G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL); + g_object_bind_property ( + editor->priv->selection, "indented", + ACTION (UNINDENT), "sensitive", + G_BINDING_SYNC_CREATE); + g_object_bind_property ( + editor->priv->selection, "italic", + ACTION (ITALIC), "active", + G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL); + g_object_bind_property ( + editor->priv->selection, "monospaced", + ACTION (MONOSPACED), "active", + G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL); + g_object_bind_property ( + editor->priv->selection, "strikethrough", + ACTION (STRIKETHROUGH), "active", + G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL); + g_object_bind_property ( + editor->priv->selection, "underline", + ACTION (UNDERLINE), "active", + G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL); + + /* Disable all actions and toolbars when editor is not editable */ + g_object_bind_property ( + view, "editable", + editor->priv->core_actions, "sensitive", + G_BINDING_SYNC_CREATE); + g_object_bind_property ( + view, "editable", + editor->priv->html_actions, "sensitive", + G_BINDING_SYNC_CREATE); + g_object_bind_property ( + view, "editable", + editor->priv->spell_check_actions, "sensitive", + G_BINDING_SYNC_CREATE); + g_object_bind_property ( + view, "editable", + editor->priv->suggestion_actions, "sensitive", + G_BINDING_SYNC_CREATE); + + settings = g_settings_new ("org.gnome.evolution.mail"); + gtk_action_set_visible ( + ACTION (WEBKIT_INSPECTOR), + g_settings_get_boolean (settings, "composer-developer-mode")); + g_object_unref (settings); +} diff --git a/e-util/e-html-editor-actions.h b/e-util/e-html-editor-actions.h new file mode 100644 index 0000000000..8999add9dc --- /dev/null +++ b/e-util/e-html-editor-actions.h @@ -0,0 +1,155 @@ +/* e-html-editor-actions.h + * + * Copyright (C) 2012 Dan Vrátil + * + * 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_HTML_EDITOR_ACTIONS_H +#define E_HTML_EDITOR_ACTIONS_H + +#define E_HTML_EDITOR_ACTION(editor, name) \ + (e_html_editor_get_action (E_HTML_EDITOR (editor), (name))) + +#define E_HTML_EDITOR_ACTION_BOLD(editor) \ + E_HTML_EDITOR_ACTION ((editor), "bold") +#define E_HTML_EDITOR_ACTION_CONTEXT_DELETE_CELL(editor) \ + E_HTML_EDITOR_ACTION ((editor), "context-delete-cell") +#define E_HTML_EDITOR_ACTION_CONTEXT_DELETE_COLUMN(editor) \ + E_HTML_EDITOR_ACTION ((editor), "context-delete-column") +#define E_HTML_EDITOR_ACTION_CONTEXT_DELETE_ROW(editor) \ + E_HTML_EDITOR_ACTION ((editor), "context-delete-row") +#define E_HTML_EDITOR_ACTION_CONTEXT_DELETE_TABLE(editor) \ + E_HTML_EDITOR_ACTION ((editor), "context-delete-table") +#define E_HTML_EDITOR_ACTION_CONTEXT_INSERT_COLUMN_AFTER(editor) \ + E_HTML_EDITOR_ACTION ((editor), "context-insert-column-after") +#define E_HTML_EDITOR_ACTION_CONTEXT_INSERT_COLUMN_BEFORE(editor) \ + E_HTML_EDITOR_ACTION ((editor), "context-insert-column-before") +#define E_HTML_EDITOR_ACTION_CONTEXT_INSERT_ROW_ABOVE(editor) \ + E_HTML_EDITOR_ACTION ((editor), "context-insert-row-above") +#define E_HTML_EDITOR_ACTION_CONTEXT_INSERT_ROW_BELOW(editor) \ + E_HTML_EDITOR_ACTION ((editor), "context-insert-row-below") +#define E_HTML_EDITOR_ACTION_CONTEXT_INSERT_TABLE(editor) \ + E_HTML_EDITOR_ACTION ((editor), "context-insert-table") +#define E_HTML_EDITOR_ACTION_CONTEXT_PROPERTIES_CELL(editor) \ + E_HTML_EDITOR_ACTION ((editor), "context-properties-cell") +#define E_HTML_EDITOR_ACTION_CONTEXT_PROPERTIES_IMAGE(editor) \ + E_HTML_EDITOR_ACTION ((editor), "context-properties-image") +#define E_HTML_EDITOR_ACTION_CONTEXT_PROPERTIES_LINK(editor) \ + E_HTML_EDITOR_ACTION ((editor), "context-properties-link") +#define E_HTML_EDITOR_ACTION_CONTEXT_PROPERTIES_PARAGRAPH(editor) \ + E_HTML_EDITOR_ACTION ((editor), "context-properties-paragraph") +#define E_HTML_EDITOR_ACTION_CONTEXT_PROPERTIES_RULE(editor) \ + E_HTML_EDITOR_ACTION ((editor), "context-properties-rule") +#define E_HTML_EDITOR_ACTION_CONTEXT_PROPERTIES_TABLE(editor) \ + E_HTML_EDITOR_ACTION ((editor), "context-properties-table") +#define E_HTML_EDITOR_ACTION_CONTEXT_PROPERTIES_TEXT(editor) \ + E_HTML_EDITOR_ACTION ((editor), "context-properties-text") +#define E_HTML_EDITOR_ACTION_CONTEXT_REMOVE_LINK(editor) \ + E_HTML_EDITOR_ACTION ((editor), "context-remove-link") +#define E_HTML_EDITOR_ACTION_CONTEXT_SPELL_ADD(editor) \ + E_HTML_EDITOR_ACTION ((editor), "context-spell-add") +#define E_HTML_EDITOR_ACTION_CONTEXT_SPELL_ADD_MENU(editor) \ + E_HTML_EDITOR_ACTION ((editor), "context-spell-add-menu") +#define E_HTML_EDITOR_ACTION_CONTEXT_SPELL_IGNORE(editor) \ + E_HTML_EDITOR_ACTION ((editor), "context-spell-ignore") +#define E_HTML_EDITOR_ACTION_COPY(editor) \ + E_HTML_EDITOR_ACTION ((editor), "copy") +#define E_HTML_EDITOR_ACTION_CUT(editor) \ + E_HTML_EDITOR_ACTION ((editor), "cut") +#define E_HTML_EDITOR_ACTION_EDIT_MENU(editor) \ + E_HTML_EDITOR_ACTION ((editor), "edit-menu") +#define E_HTML_EDITOR_ACTION_FIND_AGAIN(editor) \ + E_HTML_EDITOR_ACTION ((editor), "find-again") +#define E_HTML_EDITOR_ACTION_FONT_SIZE_GROUP(editor) \ + E_HTML_EDITOR_ACTION ((editor), "size-plus-zero") +#define E_HTML_EDITOR_ACTION_FORMAT_MENU(editor) \ + E_HTML_EDITOR_ACTION ((editor), "format-menu") +#define E_HTML_EDITOR_ACTION_FORMAT_TEXT(editor) \ + E_HTML_EDITOR_ACTION ((editor), "format-text") +#define E_HTML_EDITOR_ACTION_INSERT_IMAGE(editor) \ + E_HTML_EDITOR_ACTION ((editor), "insert-image") +#define E_HTML_EDITOR_ACTION_INSERT_LINK(editor) \ + E_HTML_EDITOR_ACTION ((editor), "insert-link") +#define E_HTML_EDITOR_ACTION_INSERT_MENU(editor) \ + E_HTML_EDITOR_ACTION ((editor), "insert-menu") +#define E_HTML_EDITOR_ACTION_INSERT_RULE(editor) \ + E_HTML_EDITOR_ACTION ((editor), "insert-rule") +#define E_HTML_EDITOR_ACTION_INSERT_TABLE(editor) \ + E_HTML_EDITOR_ACTION ((editor), "insert-table") +#define E_HTML_EDITOR_ACTION_ITALIC(editor) \ + E_HTML_EDITOR_ACTION ((editor), "italic") +#define E_HTML_EDITOR_ACTION_JUSTIFY_CENTER(editor) \ + E_HTML_EDITOR_ACTION ((editor), "justify-center") +#define E_HTML_EDITOR_ACTION_JUSTIFY_LEFT(editor) \ + E_HTML_EDITOR_ACTION ((editor), "justify-left") +#define E_HTML_EDITOR_ACTION_JUSTIFY_RIGHT(editor) \ + E_HTML_EDITOR_ACTION ((editor), "justify-right") +#define E_HTML_EDITOR_ACTION_MODE_HTML(editor) \ + E_HTML_EDITOR_ACTION ((editor), "mode-html") +#define E_HTML_EDITOR_ACTION_MODE_PLAIN(editor) \ + E_HTML_EDITOR_ACTION ((editor), "mode-plain") +#define E_HTML_EDITOR_ACTION_MONOSPACED(editor) \ + E_HTML_EDITOR_ACTION ((editor), "monospaced") +#define E_HTML_EDITOR_ACTION_PASTE(editor) \ + E_HTML_EDITOR_ACTION ((editor), "paste") +#define E_HTML_EDITOR_ACTION_PROPERTIES_RULE(editor) \ + E_HTML_EDITOR_ACTION ((editor), "properties-rule") +#define E_HTML_EDITOR_ACTION_PROPERTIES_TABLE(editor) \ + E_HTML_EDITOR_ACTION ((editor), "properties-table") +#define E_HTML_EDITOR_ACTION_REDO(editor) \ + E_HTML_EDITOR_ACTION ((editor), "redo") +#define E_HTML_EDITOR_ACTION_SHOW_FIND(editor) \ + E_HTML_EDITOR_ACTION ((editor), "show-find") +#define E_HTML_EDITOR_ACTION_SHOW_REPLACE(editor) \ + E_HTML_EDITOR_ACTION ((editor), "show-replace") +#define E_HTML_EDITOR_ACTION_SIZE_PLUS_ZERO(editor) \ + E_HTML_EDITOR_ACTION ((editor), "size-plus-zero") +#define E_HTML_EDITOR_ACTION_SPELL_CHECK(editor) \ + E_HTML_EDITOR_ACTION ((editor), "spell-check") +#define E_HTML_EDITOR_ACTION_STRIKETHROUGH(editor) \ + E_HTML_EDITOR_ACTION ((editor), "strikethrough") +#define E_HTML_EDITOR_ACTION_STYLE_ADDRESS(editor) \ + E_HTML_EDITOR_ACTION ((editor), "style-address") +#define E_HTML_EDITOR_ACTION_STYLE_H1(editor) \ + E_HTML_EDITOR_ACTION ((editor), "style-h1") +#define E_HTML_EDITOR_ACTION_STYLE_H2(editor) \ + E_HTML_EDITOR_ACTION ((editor), "style-h2") +#define E_HTML_EDITOR_ACTION_STYLE_H3(editor) \ + E_HTML_EDITOR_ACTION ((editor), "style-h3") +#define E_HTML_EDITOR_ACTION_STYLE_H4(editor) \ + E_HTML_EDITOR_ACTION ((editor), "style-h4") +#define E_HTML_EDITOR_ACTION_STYLE_H5(editor) \ + E_HTML_EDITOR_ACTION ((editor), "style-h5") +#define E_HTML_EDITOR_ACTION_STYLE_H6(editor) \ + E_HTML_EDITOR_ACTION ((editor), "style-h6") +#define E_HTML_EDITOR_ACTION_STYLE_NORMAL(editor) \ + E_HTML_EDITOR_ACTION ((editor), "style-normal") +#define E_HTML_EDITOR_ACTION_TEST_URL(editor) \ + E_HTML_EDITOR_ACTION ((editor), "test-url") +#define E_HTML_EDITOR_ACTION_UNDERLINE(editor) \ + E_HTML_EDITOR_ACTION ((editor), "underline") +#define E_HTML_EDITOR_ACTION_UNDO(editor) \ + E_HTML_EDITOR_ACTION ((editor), "undo") +#define E_HTML_EDITOR_ACTION_UNINDENT(editor) \ + E_HTML_EDITOR_ACTION ((editor), "unindent") +#define E_HTML_EDITOR_ACTION_WEBKIT_INSPECTOR(editor) \ + E_HTML_EDITOR_ACTION ((editor), "webkit-inspector") + +#endif /* E_HTML_EDITOR_ACTIONS_H */ diff --git a/e-util/e-html-editor-cell-dialog.c b/e-util/e-html-editor-cell-dialog.c new file mode 100644 index 0000000000..2b487288ae --- /dev/null +++ b/e-util/e-html-editor-cell-dialog.c @@ -0,0 +1,872 @@ +/* + * e-html-editor-cell-dialog.c + * + * Copyright (C) 2012 Dan Vrátil + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public 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-html-editor-cell-dialog.h" + +#include +#include + +#include "e-color-combo.h" +#include "e-html-editor-utils.h" +#include "e-image-chooser-dialog.h" +#include "e-misc-utils.h" + +#define E_HTML_EDITOR_CELL_DIALOG_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE \ + ((obj), E_TYPE_HTML_EDITOR_CELL_DIALOG, EHTMLEditorCellDialogPrivate)) + +struct _EHTMLEditorCellDialogPrivate { + GtkWidget *scope_cell_button; + GtkWidget *scope_table_button; + GtkWidget *scope_row_button; + GtkWidget *scope_column_button; + + GtkWidget *halign_combo; + GtkWidget *valign_combo; + + GtkWidget *wrap_text_check; + GtkWidget *header_style_check; + + GtkWidget *width_check; + GtkWidget *width_edit; + GtkWidget *width_units; + + GtkWidget *row_span_edit; + GtkWidget *col_span_edit; + + GtkWidget *background_color_picker; + GtkWidget *background_image_chooser; + + WebKitDOMElement *cell; + guint scope; +}; + +enum { + SCOPE_CELL, + SCOPE_ROW, + SCOPE_COLUMN, + SCOPE_TABLE +} DialogScope; + +static GdkRGBA white = { 1, 1, 1, 1 }; + +typedef void (*DOMStrFunc) (WebKitDOMHTMLTableCellElement *cell, const gchar *val, gpointer user_data); +typedef void (*DOMUlongFunc) (WebKitDOMHTMLTableCellElement *cell, gulong val, gpointer user_data); +typedef void (*DOMBoolFunc) (WebKitDOMHTMLTableCellElement *cell, gboolean val, gpointer user_data); + +G_DEFINE_TYPE ( + EHTMLEditorCellDialog, + e_html_editor_cell_dialog, + E_TYPE_HTML_EDITOR_DIALOG); + +static void +call_cell_dom_func (WebKitDOMHTMLTableCellElement *cell, + gpointer func, + GValue *value, + gpointer user_data) +{ + if (G_VALUE_HOLDS_STRING (value)) { + DOMStrFunc f = func; + f (cell, g_value_get_string (value), user_data); + } else if (G_VALUE_HOLDS_ULONG (value)) { + DOMUlongFunc f = func; + f (cell, g_value_get_ulong (value), user_data); + } else if (G_VALUE_HOLDS_BOOLEAN (value)) { + DOMBoolFunc f = func; + f (cell, g_value_get_boolean (value), user_data); + } +} + +static void +for_each_cell_do (WebKitDOMElement *row, + gpointer func, + GValue *value, + gpointer user_data) +{ + WebKitDOMHTMLCollection *cells; + gulong ii, length; + cells = webkit_dom_html_table_row_element_get_cells ( + WEBKIT_DOM_HTML_TABLE_ROW_ELEMENT (row)); + length = webkit_dom_html_collection_get_length (cells); + for (ii = 0; ii < length; ii++) { + WebKitDOMNode *cell; + cell = webkit_dom_html_collection_item (cells, ii); + if (!cell) { + continue; + } + + call_cell_dom_func ( + WEBKIT_DOM_HTML_TABLE_CELL_ELEMENT (cell), func, value, user_data); + } +} + +static void +html_editor_cell_dialog_set_attribute (EHTMLEditorCellDialog *dialog, + gpointer func, + GValue *value, + gpointer user_data) +{ + if (dialog->priv->scope == SCOPE_CELL) { + + call_cell_dom_func ( + WEBKIT_DOM_HTML_TABLE_CELL_ELEMENT (dialog->priv->cell), + func, value, user_data); + + } else if (dialog->priv->scope == SCOPE_COLUMN) { + gulong index, ii, length; + WebKitDOMElement *table; + WebKitDOMHTMLCollection *rows; + + index = webkit_dom_html_table_cell_element_get_cell_index ( + WEBKIT_DOM_HTML_TABLE_CELL_ELEMENT (dialog->priv->cell)); + table = e_html_editor_dom_node_find_parent_element ( + WEBKIT_DOM_NODE (dialog->priv->cell), "TABLE"); + if (!table) { + return; + } + + rows = webkit_dom_html_table_element_get_rows ( + WEBKIT_DOM_HTML_TABLE_ELEMENT (table)); + length = webkit_dom_html_collection_get_length (rows); + for (ii = 0; ii < length; ii++) { + WebKitDOMNode *row, *cell; + WebKitDOMHTMLCollection *cells; + + row = webkit_dom_html_collection_item (rows, ii); + cells = webkit_dom_html_table_row_element_get_cells ( + WEBKIT_DOM_HTML_TABLE_ROW_ELEMENT (row)); + cell = webkit_dom_html_collection_item (cells, index); + if (!cell) { + continue; + } + + call_cell_dom_func ( + WEBKIT_DOM_HTML_TABLE_CELL_ELEMENT (cell), + func, value, user_data); + } + + } else if (dialog->priv->scope == SCOPE_ROW) { + WebKitDOMElement *row; + + row = e_html_editor_dom_node_find_parent_element ( + WEBKIT_DOM_NODE (dialog->priv->cell), "TR"); + if (!row) { + return; + } + + for_each_cell_do (row, func, value, user_data); + + } else if (dialog->priv->scope == SCOPE_TABLE) { + gulong ii, length; + WebKitDOMElement *table; + WebKitDOMHTMLCollection *rows; + + table = e_html_editor_dom_node_find_parent_element ( + WEBKIT_DOM_NODE (dialog->priv->cell), "TABLE"); + if (!table) { + return; + } + + rows = webkit_dom_html_table_element_get_rows ( + WEBKIT_DOM_HTML_TABLE_ELEMENT (table)); + length = webkit_dom_html_collection_get_length (rows); + for (ii = 0; ii < length; ii++) { + WebKitDOMNode *row; + + row = webkit_dom_html_collection_item (rows, ii); + if (!row) { + continue; + } + + for_each_cell_do ( + WEBKIT_DOM_ELEMENT (row), func, value, user_data); + } + } +} + +static void +html_editor_cell_dialog_set_scope (EHTMLEditorCellDialog *dialog) +{ + if (gtk_toggle_button_get_active ( + GTK_TOGGLE_BUTTON (dialog->priv->scope_cell_button))) { + + dialog->priv->scope = SCOPE_CELL; + + } else if (gtk_toggle_button_get_active ( + GTK_TOGGLE_BUTTON (dialog->priv->scope_row_button))) { + + dialog->priv->scope = SCOPE_ROW; + + } else if (gtk_toggle_button_get_active ( + GTK_TOGGLE_BUTTON (dialog->priv->scope_column_button))) { + + dialog->priv->scope = SCOPE_COLUMN; + + } else if (gtk_toggle_button_get_active ( + GTK_TOGGLE_BUTTON (dialog->priv->scope_table_button))) { + + dialog->priv->scope = SCOPE_TABLE; + + } +} + +static void +html_editor_cell_dialog_set_valign (EHTMLEditorCellDialog *dialog) +{ + GValue val = { 0 }; + + g_value_init (&val, G_TYPE_STRING); + g_value_set_string ( + &val, + gtk_combo_box_get_active_id ( + GTK_COMBO_BOX (dialog->priv->valign_combo))); + + html_editor_cell_dialog_set_attribute ( + dialog, webkit_dom_html_table_cell_element_set_v_align, &val, NULL); + + g_value_unset (&val); +} + +static void +html_editor_cell_dialog_set_halign (EHTMLEditorCellDialog *dialog) +{ + GValue val = { 0 }; + + g_value_init (&val, G_TYPE_STRING); + g_value_set_string ( + &val, + gtk_combo_box_get_active_id ( + GTK_COMBO_BOX (dialog->priv->halign_combo))); + + html_editor_cell_dialog_set_attribute ( + dialog, webkit_dom_html_table_cell_element_set_align, &val, NULL); + + g_value_unset (&val); +} + +static void +html_editor_cell_dialog_set_wrap_text (EHTMLEditorCellDialog *dialog) +{ + GValue val = { 0 }; + + g_value_init (&val, G_TYPE_BOOLEAN); + g_value_set_boolean ( + &val, + !gtk_toggle_button_get_active ( + GTK_TOGGLE_BUTTON (dialog->priv->wrap_text_check))); + + html_editor_cell_dialog_set_attribute ( + dialog, webkit_dom_html_table_cell_element_set_no_wrap, &val, NULL); +} + +static void +cell_set_header_style (WebKitDOMHTMLTableCellElement *cell, + gboolean header_style, + gpointer user_data) +{ + EHTMLEditorCellDialog *dialog = user_data; + WebKitDOMDocument *document; + WebKitDOMNodeList *nodes; + WebKitDOMElement *new_cell; + gulong length, ii; + gchar *tagname; + + document = webkit_dom_node_get_owner_document (WEBKIT_DOM_NODE (cell)); + tagname = webkit_dom_element_get_tag_name (WEBKIT_DOM_ELEMENT (cell)); + + if (header_style && (g_ascii_strncasecmp (tagname, "TD", 2) == 0)) { + + new_cell = webkit_dom_document_create_element (document, "TH", NULL); + + } else if (!header_style && (g_ascii_strncasecmp (tagname, "TH", 2) == 0)) { + + new_cell = webkit_dom_document_create_element (document, "TD", NULL); + + } else { + g_free (tagname); + return; + } + + /* Move all child nodes from cell to new_cell */ + nodes = webkit_dom_node_get_child_nodes (WEBKIT_DOM_NODE (cell)); + length = webkit_dom_node_list_get_length (nodes); + for (ii = 0; ii < length; ii++) { + WebKitDOMNode *node; + + node = webkit_dom_node_list_item (nodes, ii); + webkit_dom_node_append_child ( + WEBKIT_DOM_NODE (new_cell), node, NULL); + } + + /* Insert new_cell before cell and remove cell */ + webkit_dom_node_insert_before ( + webkit_dom_node_get_parent_node ( + WEBKIT_DOM_NODE (cell)), + WEBKIT_DOM_NODE (new_cell), + WEBKIT_DOM_NODE (cell), NULL); + + webkit_dom_node_remove_child ( + webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (cell)), + WEBKIT_DOM_NODE (cell), NULL); + + dialog->priv->cell = new_cell; + + g_free (tagname); +} + +static void +html_editor_cell_dialog_set_header_style (EHTMLEditorCellDialog *dialog) +{ + GValue val = { 0 }; + + g_value_init (&val, G_TYPE_BOOLEAN); + g_value_set_boolean ( + &val, + gtk_toggle_button_get_active ( + GTK_TOGGLE_BUTTON (dialog->priv->header_style_check))); + + html_editor_cell_dialog_set_attribute ( + dialog, cell_set_header_style, &val, dialog); +} + +static void +html_editor_cell_dialog_set_width (EHTMLEditorCellDialog *dialog) +{ + GValue val = { 0 }; + gchar *width; + + if (!gtk_toggle_button_get_active ( + GTK_TOGGLE_BUTTON (dialog->priv->width_check))) { + + width = g_strdup ("auto"); + } else { + + width = g_strdup_printf ( + "%d%s", + gtk_spin_button_get_value_as_int ( + GTK_SPIN_BUTTON (dialog->priv->width_edit)), + ((gtk_combo_box_get_active ( + GTK_COMBO_BOX (dialog->priv->width_units)) == 0) ? + "px" : "%")); + } + + g_value_init (&val, G_TYPE_STRING); + g_value_take_string (&val, width); + html_editor_cell_dialog_set_attribute ( + dialog, webkit_dom_html_table_cell_element_set_width, &val, NULL); + + g_free (width); +} + +static void +html_editor_cell_dialog_set_column_span (EHTMLEditorCellDialog *dialog) +{ + GValue val = { 0 }; + + g_value_init (&val, G_TYPE_ULONG); + g_value_set_ulong ( + &val, + gtk_spin_button_get_value_as_int ( + GTK_SPIN_BUTTON (dialog->priv->col_span_edit))); + + html_editor_cell_dialog_set_attribute ( + dialog, webkit_dom_html_table_cell_element_set_col_span, &val, NULL); +} + +static void +html_editor_cell_dialog_set_row_span (EHTMLEditorCellDialog *dialog) +{ + GValue val = { 0 }; + + g_value_init (&val, G_TYPE_ULONG); + g_value_set_ulong ( + &val, + gtk_spin_button_get_value_as_int ( + GTK_SPIN_BUTTON (dialog->priv->row_span_edit))); + + html_editor_cell_dialog_set_attribute ( + dialog, webkit_dom_html_table_cell_element_set_row_span, &val, NULL); +} + +static void +html_editor_cell_dialog_set_background_color (EHTMLEditorCellDialog *dialog) +{ + gchar *color; + GdkRGBA rgba; + GValue val = { 0 }; + + e_color_combo_get_current_color ( + E_COLOR_COMBO (dialog->priv->background_color_picker), &rgba); + color = g_strdup_printf ("#%06x", e_rgba_to_value (&rgba)); + + g_value_init (&val, G_TYPE_STRING); + g_value_take_string (&val, color); + + html_editor_cell_dialog_set_attribute ( + dialog, webkit_dom_html_table_cell_element_set_bg_color, &val, NULL); + + g_free (color); +} + +static void +cell_set_background_image (WebKitDOMHTMLTableCellElement *cell, + const gchar *uri, + gpointer user_data) +{ + if (!uri || !*uri) { + webkit_dom_element_remove_attribute ( + WEBKIT_DOM_ELEMENT (cell), "background"); + } else { + webkit_dom_element_set_attribute ( + WEBKIT_DOM_ELEMENT (cell), "background", uri, NULL); + } +} + +static void +html_editor_cell_dialog_set_background_image (EHTMLEditorCellDialog *dialog) +{ + const gchar *uri; + GValue val = { 0 }; + + uri = gtk_file_chooser_get_uri ( + GTK_FILE_CHOOSER (dialog->priv->background_image_chooser)); + + g_value_init (&val, G_TYPE_STRING); + g_value_take_string (&val, (gchar *) uri); + + html_editor_cell_dialog_set_attribute ( + dialog, cell_set_background_image, &val, NULL); +} + +static void +html_editor_cell_dialog_show (GtkWidget *widget) +{ + EHTMLEditorCellDialog *dialog; + gchar *tmp; + GdkRGBA color; + + dialog = E_HTML_EDITOR_CELL_DIALOG (widget); + + gtk_toggle_button_set_active ( + GTK_TOGGLE_BUTTON (dialog->priv->scope_cell_button), TRUE); + + tmp = webkit_dom_html_table_cell_element_get_align ( + WEBKIT_DOM_HTML_TABLE_CELL_ELEMENT (dialog->priv->cell)); + gtk_combo_box_set_active_id ( + GTK_COMBO_BOX (dialog->priv->halign_combo), + (tmp && *tmp) ? tmp : "left"); + g_free (tmp); + + tmp = webkit_dom_html_table_cell_element_get_v_align ( + WEBKIT_DOM_HTML_TABLE_CELL_ELEMENT (dialog->priv->cell)); + gtk_combo_box_set_active_id ( + GTK_COMBO_BOX (dialog->priv->valign_combo), + (tmp && *tmp) ? tmp : "middle"); + g_free (tmp); + + gtk_toggle_button_set_active ( + GTK_TOGGLE_BUTTON (dialog->priv->wrap_text_check), + !webkit_dom_html_table_cell_element_get_no_wrap ( + WEBKIT_DOM_HTML_TABLE_CELL_ELEMENT (dialog->priv->cell))); + + tmp = webkit_dom_element_get_tag_name ( + WEBKIT_DOM_ELEMENT (dialog->priv->cell)); + gtk_toggle_button_set_active ( + GTK_TOGGLE_BUTTON (dialog->priv->header_style_check), + (g_ascii_strncasecmp (tmp, "TH", 2) == 0)); + g_free (tmp); + + tmp = webkit_dom_html_table_cell_element_get_width ( + WEBKIT_DOM_HTML_TABLE_CELL_ELEMENT (dialog->priv->cell)); + if (tmp && *tmp) { + gint val = atoi (tmp); + gtk_spin_button_set_value ( + GTK_SPIN_BUTTON (dialog->priv->width_edit), val); + gtk_toggle_button_set_active ( + GTK_TOGGLE_BUTTON (dialog->priv->width_check), TRUE); + } else { + gtk_spin_button_set_value ( + GTK_SPIN_BUTTON (dialog->priv->width_edit), 0); + gtk_toggle_button_set_active ( + GTK_TOGGLE_BUTTON (dialog->priv->width_check), FALSE); + } + gtk_combo_box_set_active_id ( + GTK_COMBO_BOX (dialog->priv->width_units), "units-px"); + g_free (tmp); + + gtk_spin_button_set_value ( + GTK_SPIN_BUTTON (dialog->priv->row_span_edit), + webkit_dom_html_table_cell_element_get_row_span ( + WEBKIT_DOM_HTML_TABLE_CELL_ELEMENT (dialog->priv->cell))); + gtk_spin_button_set_value ( + GTK_SPIN_BUTTON (dialog->priv->col_span_edit), + webkit_dom_html_table_cell_element_get_col_span ( + WEBKIT_DOM_HTML_TABLE_CELL_ELEMENT (dialog->priv->cell))); + + if (webkit_dom_element_has_attribute ( + WEBKIT_DOM_ELEMENT (dialog->priv->cell), "background")) { + tmp = webkit_dom_element_get_attribute ( + WEBKIT_DOM_ELEMENT (dialog->priv->cell), "background"); + + gtk_file_chooser_set_uri ( + GTK_FILE_CHOOSER (dialog->priv->background_image_chooser), + tmp); + + g_free (tmp); + } else { + gtk_file_chooser_unselect_all ( + GTK_FILE_CHOOSER (dialog->priv->background_image_chooser)); + } + + tmp = webkit_dom_html_table_cell_element_get_bg_color ( + WEBKIT_DOM_HTML_TABLE_CELL_ELEMENT (dialog->priv->cell)); + if (!tmp || *tmp) { + color = white; + } + if (gdk_rgba_parse (&color, tmp)) { + e_color_combo_set_current_color ( + E_COLOR_COMBO (dialog->priv->background_color_picker), + &color); + } else { + e_color_combo_set_current_color ( + E_COLOR_COMBO (dialog->priv->background_color_picker), + &white); + } + g_free (tmp); + + GTK_WIDGET_CLASS (e_html_editor_cell_dialog_parent_class)->show (widget); +} + +static void +e_html_editor_cell_dialog_class_init (EHTMLEditorCellDialogClass *class) +{ + GtkWidgetClass *widget_class; + + g_type_class_add_private (class, sizeof (EHTMLEditorCellDialogPrivate)); + + widget_class = GTK_WIDGET_CLASS (class); + widget_class->show = html_editor_cell_dialog_show; +} + +static void +e_html_editor_cell_dialog_init (EHTMLEditorCellDialog *dialog) +{ + GtkGrid *main_layout, *grid; + GtkWidget *widget; + GtkFileFilter *file_filter; + + dialog->priv = E_HTML_EDITOR_CELL_DIALOG_GET_PRIVATE (dialog); + + main_layout = e_html_editor_dialog_get_container (E_HTML_EDITOR_DIALOG (dialog)); + + /* == Scope == */ + widget = gtk_label_new (""); + gtk_label_set_markup (GTK_LABEL (widget), _("Scope")); + gtk_misc_set_alignment (GTK_MISC (widget), 0, 0.5); + gtk_grid_attach (main_layout, widget, 0, 0, 1, 1); + + grid = GTK_GRID (gtk_grid_new ()); + gtk_grid_set_row_spacing (grid, 5); + gtk_grid_set_column_spacing (grid, 5); + gtk_grid_attach (main_layout, GTK_WIDGET (grid), 0, 1, 1, 1); + gtk_widget_set_margin_left (GTK_WIDGET (grid), 10); + + /* Scope: cell */ + widget = gtk_image_new_from_icon_name ("stock_select-cell", GTK_ICON_SIZE_BUTTON); + gtk_grid_attach (grid, widget, 0, 0, 1, 1); + + widget = gtk_radio_button_new_with_mnemonic (NULL, _("C_ell")); + gtk_grid_attach (grid, widget, 1, 0, 1, 1); + dialog->priv->scope_cell_button = widget; + + g_signal_connect_swapped ( + widget, "toggled", + G_CALLBACK (html_editor_cell_dialog_set_scope), dialog); + + /* Scope: row */ + widget = gtk_image_new_from_icon_name ("stock_select-row", GTK_ICON_SIZE_BUTTON); + gtk_grid_attach (grid, widget, 2, 0, 1, 1); + + widget = gtk_radio_button_new_with_mnemonic_from_widget ( + GTK_RADIO_BUTTON (dialog->priv->scope_cell_button), _("_Row")); + gtk_grid_attach (grid, widget, 3, 0, 1, 1); + dialog->priv->scope_row_button = widget; + + g_signal_connect_swapped ( + widget, "toggled", + G_CALLBACK (html_editor_cell_dialog_set_scope), dialog); + + /* Scope: table */ + widget = gtk_image_new_from_icon_name ("stock_select-table", GTK_ICON_SIZE_BUTTON); + gtk_grid_attach (grid, widget, 0, 1, 1, 1); + + widget = gtk_radio_button_new_with_mnemonic_from_widget ( + GTK_RADIO_BUTTON (dialog->priv->scope_cell_button), _("_Table")); + gtk_grid_attach (grid, widget, 1, 1, 1, 1); + dialog->priv->scope_table_button = widget; + + g_signal_connect_swapped ( + widget, "toggled", + G_CALLBACK (html_editor_cell_dialog_set_scope), dialog); + + /* Scope: column */ + widget = gtk_image_new_from_icon_name ("stock_select-column", GTK_ICON_SIZE_BUTTON); + gtk_grid_attach (grid, widget, 2, 1, 1, 1); + + widget = gtk_radio_button_new_with_mnemonic_from_widget ( + GTK_RADIO_BUTTON (dialog->priv->scope_cell_button), _("Col_umn")); + gtk_grid_attach (grid, widget, 3, 1, 1, 1); + dialog->priv->scope_column_button = widget; + + g_signal_connect_swapped ( + widget, "toggled", + G_CALLBACK (html_editor_cell_dialog_set_scope), dialog); + + /* == Alignment & Behavior == */ + widget = gtk_label_new (""); + gtk_label_set_markup (GTK_LABEL (widget), _("Alignment & Behavior")); + gtk_misc_set_alignment (GTK_MISC (widget), 0, 0.5); + gtk_grid_attach (main_layout, widget, 0, 2, 1, 1); + + grid = GTK_GRID (gtk_grid_new ()); + gtk_grid_set_row_spacing (grid, 5); + gtk_grid_set_column_spacing (grid, 5); + gtk_grid_attach (main_layout, GTK_WIDGET (grid), 0, 3, 1, 1); + gtk_widget_set_margin_left (GTK_WIDGET (grid), 10); + + /* Horizontal */ + widget = gtk_combo_box_text_new (); + gtk_widget_set_hexpand (widget, TRUE); + gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (widget), "left", _("Left")); + gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (widget), "center", _("Center")); + gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (widget), "right", _("Right")); + gtk_grid_attach (grid, widget, 1, 0, 1, 1); + dialog->priv->halign_combo = widget; + + g_signal_connect_swapped ( + widget, "changed", + G_CALLBACK (html_editor_cell_dialog_set_halign), dialog); + + widget = gtk_label_new_with_mnemonic (_("_Horizontal:")); + gtk_label_set_justify (GTK_LABEL (widget), GTK_JUSTIFY_RIGHT); + gtk_label_set_mnemonic_widget (GTK_LABEL (widget), dialog->priv->halign_combo); + gtk_grid_attach (grid, widget, 0, 0, 1, 1); + + /* Vertical */ + widget = gtk_combo_box_text_new (); + gtk_widget_set_hexpand (widget, TRUE); + gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (widget), "top", _("Top")); + gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (widget), "middle", _("Middle")); + gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (widget), "bottom", _("Bottom")); + gtk_grid_attach (grid, widget, 3, 0, 1, 1); + dialog->priv->valign_combo = widget; + + g_signal_connect_swapped ( + widget, "changed", + G_CALLBACK (html_editor_cell_dialog_set_valign), dialog); + + widget = gtk_label_new_with_mnemonic (_("_Vertical:")); + gtk_label_set_justify (GTK_LABEL (widget), GTK_JUSTIFY_RIGHT); + gtk_label_set_mnemonic_widget (GTK_LABEL (widget), dialog->priv->valign_combo); + gtk_grid_attach (grid, widget, 2, 0, 1, 1); + + /* Wrap Text */ + widget = gtk_check_button_new_with_mnemonic (_("_Wrap Text")); + dialog->priv->wrap_text_check = widget; + + g_signal_connect_swapped ( + widget, "toggled", + G_CALLBACK (html_editor_cell_dialog_set_wrap_text), dialog); + + /* Header Style */ + widget = gtk_check_button_new_with_mnemonic (_("_Header Style")); + dialog->priv->header_style_check = widget; + + g_signal_connect_swapped ( + widget, "toggled", + G_CALLBACK (html_editor_cell_dialog_set_header_style), dialog); + + widget = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 5); + gtk_box_pack_start (GTK_BOX (widget), dialog->priv->wrap_text_check, FALSE, FALSE, 0); + gtk_box_pack_start (GTK_BOX (widget), dialog->priv->header_style_check, FALSE, FALSE, 0); + gtk_grid_attach (grid, widget, 0, 1, 4, 1); + + /* == Layout == */ + widget = gtk_label_new (""); + gtk_label_set_markup (GTK_LABEL (widget), _("Layout")); + gtk_misc_set_alignment (GTK_MISC (widget), 0, 0.5); + gtk_grid_attach (main_layout, widget, 0, 4, 1, 1); + + grid = GTK_GRID (gtk_grid_new ()); + gtk_grid_set_row_spacing (grid, 5); + gtk_grid_set_column_spacing (grid, 5); + gtk_grid_attach (main_layout, GTK_WIDGET (grid), 0, 5, 1, 1); + gtk_widget_set_margin_left (GTK_WIDGET (grid), 10); + + /* Width */ + widget = gtk_check_button_new_with_mnemonic (_("_Width")); + gtk_grid_attach (grid, widget, 0, 0, 1, 1); + dialog->priv->width_check = widget; + + widget = gtk_spin_button_new_with_range (0, G_MAXUINT, 1); + gtk_grid_attach (grid, widget, 1, 0, 1, 1); + dialog->priv->width_edit = widget; + + g_signal_connect_swapped ( + widget, "value-changed", + G_CALLBACK (html_editor_cell_dialog_set_width), dialog); + g_object_bind_property ( + dialog->priv->width_check, "active", + widget, "sensitive", + G_BINDING_SYNC_CREATE); + + widget = gtk_combo_box_text_new (); + gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (widget), "unit-px", "px"); + gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (widget), "unit-percent", "%"); + gtk_grid_attach (grid, widget, 2, 0, 1, 1); + dialog->priv->width_units = widget; + + g_signal_connect_swapped ( + widget, "changed", + G_CALLBACK (html_editor_cell_dialog_set_width), dialog); + g_object_bind_property ( + dialog->priv->width_check, "active", + widget, "sensitive", + G_BINDING_SYNC_CREATE); + + /* Row Span */ + widget = gtk_spin_button_new_with_range (0, G_MAXUINT, 1); + gtk_grid_attach (grid, widget, 4, 0, 1, 1); + dialog->priv->row_span_edit = widget; + + g_signal_connect_swapped ( + widget, "value-changed", + G_CALLBACK (html_editor_cell_dialog_set_row_span), dialog); + + widget = gtk_label_new_with_mnemonic (_("Row S_pan:")); + gtk_label_set_justify (GTK_LABEL (widget), GTK_JUSTIFY_RIGHT); + gtk_label_set_mnemonic_widget (GTK_LABEL (widget), dialog->priv->row_span_edit); + gtk_grid_attach (grid, widget, 3, 0, 1, 1); + + /* Column Span */ + widget = gtk_spin_button_new_with_range (0, G_MAXUINT, 1); + gtk_grid_attach (grid, widget, 4, 1, 1, 1); + dialog->priv->col_span_edit = widget; + + g_signal_connect_swapped ( + widget, "value-changed", + G_CALLBACK (html_editor_cell_dialog_set_column_span), dialog); + + widget = gtk_label_new_with_mnemonic (_("Co_lumn Span:")); + gtk_label_set_justify (GTK_LABEL (widget), GTK_JUSTIFY_RIGHT); + gtk_label_set_mnemonic_widget (GTK_LABEL (widget), dialog->priv->col_span_edit); + gtk_grid_attach (grid, widget, 3, 1, 1, 1); + + /* == Background == */ + widget = gtk_label_new (""); + gtk_label_set_markup (GTK_LABEL (widget), _("Background")); + gtk_misc_set_alignment (GTK_MISC (widget), 0, 0.5); + gtk_grid_attach (main_layout, widget, 0, 6, 1, 1); + + grid = GTK_GRID (gtk_grid_new ()); + gtk_grid_set_row_spacing (grid, 5); + gtk_grid_set_column_spacing (grid, 5); + gtk_grid_attach (main_layout, GTK_WIDGET (grid), 0, 7, 1, 1); + gtk_widget_set_margin_left (GTK_WIDGET (grid), 10); + + /* Color */ + widget = e_color_combo_new (); + e_color_combo_set_default_color (E_COLOR_COMBO (widget), &white); + gtk_widget_set_hexpand (widget, TRUE); + gtk_grid_attach (grid, widget, 1, 0, 1, 1); + g_signal_connect_swapped ( + widget, "notify::current-color", + G_CALLBACK (html_editor_cell_dialog_set_background_color), dialog); + dialog->priv->background_color_picker = widget; + + widget = gtk_label_new_with_mnemonic (_("C_olor:")); + gtk_label_set_justify (GTK_LABEL (widget), GTK_JUSTIFY_RIGHT); + gtk_label_set_mnemonic_widget ( + GTK_LABEL (widget), dialog->priv->background_color_picker); + gtk_grid_attach (grid, widget, 0, 0, 1, 1); + + /* Image */ + widget = e_image_chooser_dialog_new ( + _("Choose Background Image"), + GTK_WINDOW (dialog)); + dialog->priv->background_image_chooser = widget; + + file_filter = gtk_file_filter_new (); + gtk_file_filter_set_name (file_filter, _("Images")); + gtk_file_filter_add_mime_type (file_filter, "image/*"); + + widget = gtk_file_chooser_button_new_with_dialog ( + dialog->priv->background_image_chooser); + gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (widget), file_filter); + gtk_widget_set_hexpand (widget, TRUE); + gtk_grid_attach (grid, widget, 1, 1, 1, 1); + g_signal_connect_swapped ( + widget, "file-set", + G_CALLBACK (html_editor_cell_dialog_set_background_image), dialog); + dialog->priv->background_image_chooser = widget; + + widget =gtk_label_new_with_mnemonic (_("_Image:")); + gtk_label_set_justify (GTK_LABEL (widget), GTK_JUSTIFY_RIGHT); + gtk_label_set_mnemonic_widget ( + GTK_LABEL (widget), dialog->priv->background_image_chooser); + gtk_grid_attach (grid, widget, 0, 1, 1, 1); + + gtk_widget_show_all (GTK_WIDGET (main_layout)); +} + +GtkWidget * +e_html_editor_cell_dialog_new (EHTMLEditor *editor) +{ + return GTK_WIDGET ( + g_object_new ( + E_TYPE_HTML_EDITOR_CELL_DIALOG, + "editor", editor, + "title", N_("Cell Properties"), + NULL)); +} + +void +e_html_editor_cell_dialog_show (EHTMLEditorCellDialog *dialog, + WebKitDOMNode *cell) +{ + EHTMLEditorCellDialogClass *class; + + g_return_if_fail (E_IS_HTML_EDITOR_CELL_DIALOG (dialog)); + g_return_if_fail (cell != NULL); + + dialog->priv->cell = e_html_editor_dom_node_find_parent_element (cell, "TD"); + if (dialog->priv->cell == NULL) { + dialog->priv->cell = + e_html_editor_dom_node_find_parent_element (cell, "TH"); + } + + class = E_HTML_EDITOR_CELL_DIALOG_GET_CLASS (dialog); + GTK_WIDGET_CLASS (class)->show (GTK_WIDGET (dialog)); +} + diff --git a/e-util/e-html-editor-cell-dialog.h b/e-util/e-html-editor-cell-dialog.h new file mode 100644 index 0000000000..3a7f60822f --- /dev/null +++ b/e-util/e-html-editor-cell-dialog.h @@ -0,0 +1,72 @@ +/* + * e-html-editor-cell-dialog.h + * + * Copyright (C) 2012 Dan Vrátil + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public 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_HTML_EDITOR_CELL_DIALOG_H +#define E_HTML_EDITOR_CELL_DIALOG_H + +#include + +/* Standard GObject macros */ +#define E_TYPE_HTML_EDITOR_CELL_DIALOG \ + (e_html_editor_cell_dialog_get_type ()) +#define E_HTML_EDITOR_CELL_DIALOG(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST \ + ((obj), E_TYPE_HTML_EDITOR_CELL_DIALOG, EHTMLEditorCellDialog)) +#define E_HTML_EDITOR_CELL_DIALOG_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_CAST \ + ((cls), E_TYPE_HTML_EDITOR_CELL_DIALOG, EHTMLEditorCellDialogClass)) +#define E_IS_HTML_EDITOR_CELL_DIALOG(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE \ + ((obj), E_TYPE_HTML_EDITOR_CELL_DIALOG)) +#define E_IS_HTML_EDITOR_CELL_DIALOG_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_TYPE \ + ((cls), E_TYPE_HTML_EDITOR_CELL_DIALOG)) +#define E_HTML_EDITOR_CELL_DIALOG_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS \ + ((obj), E_TYPE_HTML_EDITOR_CELL_DIALOG, EHTMLEditorCellDialogClass)) + +G_BEGIN_DECLS + +typedef struct _EHTMLEditorCellDialog EHTMLEditorCellDialog; +typedef struct _EHTMLEditorCellDialogClass EHTMLEditorCellDialogClass; +typedef struct _EHTMLEditorCellDialogPrivate EHTMLEditorCellDialogPrivate; + +struct _EHTMLEditorCellDialog { + EHTMLEditorDialog parent; + EHTMLEditorCellDialogPrivate *priv; +}; + +struct _EHTMLEditorCellDialogClass { + EHTMLEditorDialogClass parent_class; +}; + +GType e_html_editor_cell_dialog_get_type + (void) G_GNUC_CONST; +GtkWidget * e_html_editor_cell_dialog_new (EHTMLEditor *editor); +void e_html_editor_cell_dialog_show (EHTMLEditorCellDialog *dialog, + WebKitDOMNode *cell); + +G_END_DECLS + +#endif /* E_HTML_EDITOR_CELL_DIALOG_H */ diff --git a/e-util/e-html-editor-dialog.c b/e-util/e-html-editor-dialog.c new file mode 100644 index 0000000000..5b43ebd316 --- /dev/null +++ b/e-util/e-html-editor-dialog.c @@ -0,0 +1,248 @@ +/* + * e-html-editor-dialog.h + * + * Copyright (C) 2012 Dan Vrátil + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public 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-html-editor-dialog.h" + +#define E_HTML_EDITOR_DIALOG_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE \ + ((obj), E_TYPE_HTML_EDITOR_DIALOG, EHTMLEditorDialogPrivate)) + +struct _EHTMLEditorDialogPrivate { + EHTMLEditor *editor; + + GtkBox *button_box; + GtkGrid *container; +}; + +enum { + PROP_0, + PROP_EDITOR, +}; + +G_DEFINE_ABSTRACT_TYPE ( + EHTMLEditorDialog, + e_html_editor_dialog, + GTK_TYPE_WINDOW); + +static void +html_editor_dialog_set_editor (EHTMLEditorDialog *dialog, + EHTMLEditor *editor) +{ + dialog->priv->editor = g_object_ref (editor); + + g_object_notify (G_OBJECT (dialog), "editor"); +} + +static void +html_editor_dialog_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + switch (property_id) { + case PROP_EDITOR: + g_value_set_object ( + value, + e_html_editor_dialog_get_editor ( + E_HTML_EDITOR_DIALOG (object))); + return; + } + + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); +} + +static void +html_editor_dialog_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + switch (property_id) { + case PROP_EDITOR: + html_editor_dialog_set_editor ( + E_HTML_EDITOR_DIALOG (object), + g_value_get_object (value)); + return; + } + + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); +} + +static void +html_editor_dialog_constructed (GObject *object) +{ + EHTMLEditorDialog *dialog = E_HTML_EDITOR_DIALOG (object); + + /* Chain up to parent implementation first */ + G_OBJECT_CLASS (e_html_editor_dialog_parent_class)->constructed (object); + + gtk_window_set_transient_for ( + GTK_WINDOW (dialog), + GTK_WINDOW (gtk_widget_get_toplevel ( + GTK_WIDGET (dialog->priv->editor)))); +} + +static void +html_editor_dialog_show (GtkWidget *widget) +{ + EHTMLEditorDialogPrivate *priv; + + priv = E_HTML_EDITOR_DIALOG_GET_PRIVATE (widget); + + gtk_widget_show_all (GTK_WIDGET (priv->container)); + gtk_widget_show_all (GTK_WIDGET (priv->button_box)); + + GTK_WIDGET_CLASS (e_html_editor_dialog_parent_class)->show (widget); +} + +static void +html_editor_dialog_dispose (GObject *object) +{ + EHTMLEditorDialogPrivate *priv; + + priv = E_HTML_EDITOR_DIALOG_GET_PRIVATE (object); + + g_clear_object (&priv->editor); + + /* Chain up to parent's implementation */ + G_OBJECT_CLASS (e_html_editor_dialog_parent_class)->dispose (object); +} + +static void +e_html_editor_dialog_class_init (EHTMLEditorDialogClass *class) +{ + GObjectClass *object_class; + GtkWidgetClass *widget_class; + + g_type_class_add_private (class, sizeof (EHTMLEditorDialogPrivate)); + + object_class = G_OBJECT_CLASS (class); + object_class->get_property = html_editor_dialog_get_property; + object_class->set_property = html_editor_dialog_set_property; + object_class->dispose = html_editor_dialog_dispose; + object_class->constructed = html_editor_dialog_constructed; + + widget_class = GTK_WIDGET_CLASS (class); + widget_class->show = html_editor_dialog_show; + + g_object_class_install_property ( + object_class, + PROP_EDITOR, + g_param_spec_object ( + "editor", + NULL, + NULL, + E_TYPE_HTML_EDITOR, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE)); +} + +static gboolean +key_press_event_cb (GtkWidget *widget, + GdkEventKey *event, + gpointer user_data) +{ + if (event->keyval == GDK_KEY_Escape) { + gtk_widget_hide (widget); + return TRUE; + } + + return FALSE; +} + +static void +e_html_editor_dialog_init (EHTMLEditorDialog *dialog) +{ + GtkBox *main_layout; + GtkGrid *grid; + GtkWidget *widget, *button_box; + + dialog->priv = E_HTML_EDITOR_DIALOG_GET_PRIVATE (dialog); + + main_layout = GTK_BOX (gtk_box_new (GTK_ORIENTATION_VERTICAL, 5)); + gtk_container_add (GTK_CONTAINER (dialog), GTK_WIDGET (main_layout)); + gtk_container_set_border_width (GTK_CONTAINER (dialog), 10); + + grid = GTK_GRID (gtk_grid_new ()); + gtk_grid_set_row_spacing (grid, 10); + gtk_grid_set_column_spacing (grid, 10); + gtk_box_pack_start (main_layout, GTK_WIDGET (grid), TRUE, TRUE, 5); + dialog->priv->container = grid; + + /* == Button box == */ + widget = gtk_button_new_from_stock (GTK_STOCK_CLOSE); + g_signal_connect_swapped ( + widget, "clicked", + G_CALLBACK (gtk_widget_hide), dialog); + + button_box = gtk_button_box_new (GTK_ORIENTATION_HORIZONTAL); + gtk_button_box_set_layout (GTK_BUTTON_BOX (button_box), GTK_BUTTONBOX_END); + gtk_box_set_spacing (GTK_BOX (button_box), 5); + gtk_box_pack_start (main_layout, button_box, TRUE, TRUE, 5); + gtk_box_pack_start (GTK_BOX (button_box), widget, FALSE, FALSE, 5); + dialog->priv->button_box = GTK_BOX (button_box); + + gtk_widget_show_all (GTK_WIDGET (main_layout)); + + g_object_set ( + G_OBJECT (dialog), + "destroy-with-parent", TRUE, + "modal", TRUE, + "resizable", FALSE, + "window-position", GTK_WIN_POS_CENTER_ON_PARENT, + NULL); + + /* Don't destroy the dialog when closed! */ + g_signal_connect ( + dialog, "delete-event", + G_CALLBACK (gtk_widget_hide_on_delete), NULL); + + g_signal_connect ( + dialog, "key-press-event", + G_CALLBACK (key_press_event_cb), NULL); +} + +EHTMLEditor * +e_html_editor_dialog_get_editor (EHTMLEditorDialog *dialog) +{ + g_return_val_if_fail (E_IS_HTML_EDITOR_DIALOG (dialog), NULL); + + return dialog->priv->editor; +} + +GtkGrid * +e_html_editor_dialog_get_container (EHTMLEditorDialog *dialog) +{ + g_return_val_if_fail (E_IS_HTML_EDITOR_DIALOG (dialog), NULL); + + return dialog->priv->container; +} + +GtkBox * +e_html_editor_dialog_get_button_box (EHTMLEditorDialog *dialog) +{ + g_return_val_if_fail (E_IS_HTML_EDITOR_DIALOG (dialog), NULL); + + return dialog->priv->button_box; +} + diff --git a/e-util/e-html-editor-dialog.h b/e-util/e-html-editor-dialog.h new file mode 100644 index 0000000000..37fc7a53c3 --- /dev/null +++ b/e-util/e-html-editor-dialog.h @@ -0,0 +1,74 @@ +/* + * e-html-editor-dialog.h + * + * Copyright (C) 2012 Dan Vrátil + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public 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_HTML_EDITOR_DIALOG_H +#define E_HTML_EDITOR_DIALOG_H + +#include +#include + +/* Standard GObject macros */ +#define E_TYPE_HTML_EDITOR_DIALOG \ + (e_html_editor_dialog_get_type ()) +#define E_HTML_EDITOR_DIALOG(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST \ + ((obj), E_TYPE_HTML_EDITOR_DIALOG, EHTMLEditorDialog)) +#define E_HTML_EDITOR_DIALOG_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_CAST \ + ((cls), E_TYPE_HTML_EDITOR_DIALOG, EHTMLEditorDialogClass)) +#define E_IS_HTML_EDITOR_DIALOG(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE \ + ((obj), E_TYPE_HTML_EDITOR_DIALOG)) +#define E_IS_HTML_EDITOR_DIALOG_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_TYPE \ + ((cls), E_TYPE_HTML_EDITOR_DIALOG)) +#define E_HTML_EDITOR_DIALOG_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS \ + ((obj), E_TYPE_HTML_EDITOR_DIALOG, EHTMLEditorDialogClass)) + +G_BEGIN_DECLS + +typedef struct _EHTMLEditorDialog EHTMLEditorDialog; +typedef struct _EHTMLEditorDialogClass EHTMLEditorDialogClass; +typedef struct _EHTMLEditorDialogPrivate EHTMLEditorDialogPrivate; + +struct _EHTMLEditorDialog { + GtkWindow parent; + EHTMLEditorDialogPrivate *priv; +}; + +struct _EHTMLEditorDialogClass { + GtkWindowClass parent_class; +}; + +GType e_html_editor_dialog_get_type (void) G_GNUC_CONST; +EHTMLEditor * e_html_editor_dialog_get_editor (EHTMLEditorDialog *dialog); +GtkBox * e_html_editor_dialog_get_button_box + (EHTMLEditorDialog *dialog); +GtkGrid * e_html_editor_dialog_get_container + (EHTMLEditorDialog *dialog); + +G_END_DECLS + +#endif /* E_HTML_EDITOR_DIALOG_H */ diff --git a/e-util/e-html-editor-find-dialog.c b/e-util/e-html-editor-find-dialog.c new file mode 100644 index 0000000000..9f44cf8dc5 --- /dev/null +++ b/e-util/e-html-editor-find-dialog.c @@ -0,0 +1,224 @@ +/* + * e-html-editor-find-dialog.h + * + * Copyright (C) 2012 Dan Vrátil + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public 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-html-editor-find-dialog.h" + +#include +#include + +#define E_HTML_EDITOR_FIND_DIALOG_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE \ + ((obj), E_TYPE_HTML_EDITOR_FIND_DIALOG, EHTMLEditorFindDialogPrivate)) + +struct _EHTMLEditorFindDialogPrivate { + GtkWidget *entry; + GtkWidget *backwards; + GtkWidget *case_sensitive; + GtkWidget *wrap_search; + + GtkWidget *find_button; + + GtkWidget *result_label; +}; + +G_DEFINE_TYPE ( + EHTMLEditorFindDialog, + e_html_editor_find_dialog, + E_TYPE_HTML_EDITOR_DIALOG); + +static void +reset_dialog (EHTMLEditorFindDialog *dialog) +{ + gtk_widget_set_sensitive (dialog->priv->find_button, TRUE); + gtk_widget_hide (dialog->priv->result_label); +} + +static void +html_editor_find_dialog_show (GtkWidget *widget) +{ + EHTMLEditorFindDialog *dialog = E_HTML_EDITOR_FIND_DIALOG (widget); + + reset_dialog (dialog); + gtk_widget_grab_focus (dialog->priv->entry); + + /* Chain up to parent's implementation */ + GTK_WIDGET_CLASS (e_html_editor_find_dialog_parent_class)->show (widget); +} + +static void +html_editor_find_dialog_find_cb (EHTMLEditorFindDialog *dialog) +{ + gboolean found; + EHTMLEditor *editor; + EHTMLEditorView *view; + + editor = e_html_editor_dialog_get_editor (E_HTML_EDITOR_DIALOG (dialog)); + view = e_html_editor_get_view (editor); + found = webkit_web_view_search_text ( + WEBKIT_WEB_VIEW (view), + gtk_entry_get_text ( + GTK_ENTRY (dialog->priv->entry)), + gtk_toggle_button_get_active ( + GTK_TOGGLE_BUTTON ( + dialog->priv->case_sensitive)), + !gtk_toggle_button_get_active ( + GTK_TOGGLE_BUTTON ( + dialog->priv->backwards)), + gtk_toggle_button_get_active ( + GTK_TOGGLE_BUTTON ( + dialog->priv->wrap_search))); + + gtk_widget_set_sensitive (dialog->priv->find_button, found); + + /* We give focus to WebKit so that the selection is highlited. + * Without focus selection is not visible (at least with my default + * color scheme). The focus in fact is not given to WebKit, because + * this dialog is modal, but it satisfies it in a way that it paints + * the selection :) */ + gtk_widget_grab_focus (GTK_WIDGET (view)); + + if (!found) { + gtk_label_set_label ( + GTK_LABEL (dialog->priv->result_label), + N_("No match found")); + gtk_widget_show (dialog->priv->result_label); + } +} + +static gboolean +entry_key_release_event (GtkWidget *widget, + GdkEvent *event, + gpointer user_data) +{ + GdkEventKey *key = &event->key; + EHTMLEditorFindDialog *dialog = user_data; + + if (key->keyval == GDK_KEY_Return) { + html_editor_find_dialog_find_cb (dialog); + return TRUE; + } + + reset_dialog (dialog); + return FALSE; +} + +static void +e_html_editor_find_dialog_class_init (EHTMLEditorFindDialogClass *class) +{ + GtkWidgetClass *widget_class; + + g_type_class_add_private (class, sizeof (EHTMLEditorFindDialogPrivate)); + + widget_class = GTK_WIDGET_CLASS (class); + widget_class->show = html_editor_find_dialog_show; +} + +static void +e_html_editor_find_dialog_init (EHTMLEditorFindDialog *dialog) +{ + GtkGrid *main_layout; + GtkBox *box; + GtkWidget *widget; + + dialog->priv = E_HTML_EDITOR_FIND_DIALOG_GET_PRIVATE (dialog); + + main_layout = e_html_editor_dialog_get_container (E_HTML_EDITOR_DIALOG (dialog)); + + widget = gtk_entry_new (); + gtk_widget_set_hexpand (widget, TRUE); + gtk_grid_attach (main_layout, widget, 0, 0, 1, 1); + dialog->priv->entry = widget; + g_signal_connect ( + widget, "key-release-event", + G_CALLBACK (entry_key_release_event), dialog); + + box = GTK_BOX (gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 5)); + gtk_grid_attach (main_layout, GTK_WIDGET (box), 0, 1, 1, 1); + + widget = gtk_check_button_new_with_mnemonic (N_("Search _backwards")); + gtk_box_pack_start (box, widget, FALSE, FALSE, 0); + dialog->priv->backwards = widget; + g_signal_connect_swapped ( + widget, "toggled", + G_CALLBACK (reset_dialog), dialog); + + widget = gtk_check_button_new_with_mnemonic (N_("Case _Sensitive")); + gtk_box_pack_start (box, widget, FALSE, FALSE, 0); + dialog->priv->case_sensitive = widget; + g_signal_connect_swapped ( + widget, "toggled", + G_CALLBACK (reset_dialog), dialog); + + widget = gtk_check_button_new_with_mnemonic (N_("_Wrap Search")); + gtk_box_pack_start (box, widget, FALSE, FALSE, 0); + dialog->priv->wrap_search = widget; + g_signal_connect_swapped ( + widget, "toggled", + G_CALLBACK (reset_dialog), dialog); + + box = GTK_BOX (gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 5)); + gtk_grid_attach (main_layout, GTK_WIDGET (box), 0, 2, 1, 1); + + widget = gtk_label_new (""); + gtk_box_pack_start (box, widget, FALSE, FALSE, 0); + dialog->priv->result_label = widget; + + widget = gtk_button_box_new (GTK_ORIENTATION_HORIZONTAL); + gtk_box_set_spacing (GTK_BOX (widget), 5); + gtk_button_box_set_layout (GTK_BUTTON_BOX (widget), GTK_BUTTONBOX_END); + gtk_box_pack_end (box, widget, TRUE, TRUE, 0); + box = GTK_BOX (widget); + + box = e_html_editor_dialog_get_button_box (E_HTML_EDITOR_DIALOG (dialog)); + widget = gtk_button_new_from_stock (GTK_STOCK_FIND); + gtk_box_pack_start (box, widget, FALSE, FALSE, 5); + g_signal_connect_swapped ( + widget, "clicked", + G_CALLBACK (html_editor_find_dialog_find_cb), dialog); + dialog->priv->find_button = widget; + + gtk_widget_show_all (GTK_WIDGET (main_layout)); +} + +GtkWidget * +e_html_editor_find_dialog_new (EHTMLEditor *editor) +{ + return GTK_WIDGET ( + g_object_new ( + E_TYPE_HTML_EDITOR_FIND_DIALOG, + "editor", editor, + "icon-name", GTK_STOCK_FIND, + "title", N_("Find"), + NULL)); +} + +void +e_html_editor_find_dialog_find_next (EHTMLEditorFindDialog *dialog) +{ + if (gtk_entry_get_text_length (GTK_ENTRY (dialog->priv->entry)) == 0) { + return; + } + + html_editor_find_dialog_find_cb (dialog); +} diff --git a/e-util/e-html-editor-find-dialog.h b/e-util/e-html-editor-find-dialog.h new file mode 100644 index 0000000000..60134f467f --- /dev/null +++ b/e-util/e-html-editor-find-dialog.h @@ -0,0 +1,73 @@ +/* + * e-html-editor-find-dialog.h + * + * Copyright (C) 2012 Dan Vrátil + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public 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_HTML_EDITOR_FIND_DIALOG_H +#define E_HTML_EDITOR_FIND_DIALOG_H + +#include + +/* Standard GObject macros */ +#define E_TYPE_HTML_EDITOR_FIND_DIALOG \ + (e_html_editor_find_dialog_get_type ()) +#define E_HTML_EDITOR_FIND_DIALOG(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST \ + ((obj), E_TYPE_HTML_EDITOR_FIND_DIALOG, EHTMLEditorFindDialog)) +#define E_HTML_EDITOR_FIND_DIALOG_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_CAST \ + ((cls), E_TYPE_HTML_EDITOR_FIND_DIALOG, EHTMLEditorFindDialogClass)) +#define E_IS_HTML_EDITOR_FIND_DIALOG(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE \ + ((obj), E_TYPE_HTML_EDITOR_FIND_DIALOG)) +#define E_IS_HTML_EDITOR_FIND_DIALOG_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_TYPE \ + ((cls), E_TYPE_HTML_EDITOR_FIND_DIALOG)) +#define E_HTML_EDITOR_FIND_DIALOG_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS \ + ((obj), E_TYPE_HTML_EDITOR_FIND_DIALOG, EHTMLEditorFindDialogClass)) + +G_BEGIN_DECLS + +typedef struct _EHTMLEditorFindDialog EHTMLEditorFindDialog; +typedef struct _EHTMLEditorFindDialogClass EHTMLEditorFindDialogClass; +typedef struct _EHTMLEditorFindDialogPrivate EHTMLEditorFindDialogPrivate; + +struct _EHTMLEditorFindDialog { + EHTMLEditorDialog parent; + EHTMLEditorFindDialogPrivate *priv; +}; + +struct _EHTMLEditorFindDialogClass { + EHTMLEditorDialogClass parent_class; +}; + +GType e_html_editor_find_dialog_get_type + (void) G_GNUC_CONST; +GtkWidget * e_html_editor_find_dialog_new (EHTMLEditor *editor); +void e_html_editor_find_dialog_find_next + (EHTMLEditorFindDialog *dialog); + +G_END_DECLS + +#endif /* E_HTML_EDITOR_FIND_DIALOG_H */ + diff --git a/e-util/e-html-editor-hrule-dialog.c b/e-util/e-html-editor-hrule-dialog.c new file mode 100644 index 0000000000..9ace655812 --- /dev/null +++ b/e-util/e-html-editor-hrule-dialog.c @@ -0,0 +1,421 @@ +/* + * e-html-editor-hrule-dialog.h + * + * Copyright (C) 2012 Dan Vrátil + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public 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-html-editor-hrule-dialog.h" +#include "e-html-editor-utils.h" +#include "e-html-editor-view.h" + +#include +#include +#include + +#define E_HTML_EDITOR_HRULE_DIALOG_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE \ + ((obj), E_TYPE_HTML_EDITOR_HRULE_DIALOG, EHTMLEditorHRuleDialogPrivate)) + +struct _EHTMLEditorHRuleDialogPrivate { + GtkWidget *width_edit; + GtkWidget *size_edit; + GtkWidget *unit_combo; + + GtkWidget *alignment_combo; + GtkWidget *shaded_check; + + WebKitDOMHTMLHRElement *hr_element; +}; + +G_DEFINE_TYPE ( + EHTMLEditorHRuleDialog, + e_html_editor_hrule_dialog, + E_TYPE_HTML_EDITOR_DIALOG); + +static void +html_editor_hrule_dialog_set_alignment (EHTMLEditorHRuleDialog *dialog) +{ + const gchar *alignment; + + g_return_if_fail (WEBKIT_DOM_IS_HTMLHR_ELEMENT (dialog->priv->hr_element)); + + alignment = gtk_combo_box_get_active_id ( + GTK_COMBO_BOX (dialog->priv->alignment_combo)); + + webkit_dom_htmlhr_element_set_align (dialog->priv->hr_element, alignment); +} + +static void +html_editor_hrule_dialog_get_alignment (EHTMLEditorHRuleDialog *dialog) +{ + gchar *alignment; + + g_return_if_fail (WEBKIT_DOM_IS_HTMLHR_ELEMENT (dialog->priv->hr_element)); + + alignment = webkit_dom_htmlhr_element_get_align (dialog->priv->hr_element); + + gtk_combo_box_set_active_id ( + GTK_COMBO_BOX (dialog->priv->alignment_combo), alignment); + g_free (alignment); +} + +static void +html_editor_hrule_dialog_set_size (EHTMLEditorHRuleDialog *dialog) +{ + gchar *size; + + g_return_if_fail (WEBKIT_DOM_IS_HTMLHR_ELEMENT (dialog->priv->hr_element)); + + size = g_strdup_printf ( + "%d", + (gint) gtk_spin_button_get_value ( + GTK_SPIN_BUTTON (dialog->priv->size_edit))); + + webkit_dom_htmlhr_element_set_size (dialog->priv->hr_element, size); + + g_free (size); +} + +static void +html_editor_hrule_dialog_get_size (EHTMLEditorHRuleDialog *dialog) +{ + gchar *size; + gint size_int = 0; + + g_return_if_fail (WEBKIT_DOM_IS_HTMLHR_ELEMENT (dialog->priv->hr_element)); + + size = webkit_dom_htmlhr_element_get_size (dialog->priv->hr_element); + if (size && *size) { + size_int = atoi (size); + } + + if (size_int == 0) { + size_int = 2; + } + + gtk_spin_button_set_value ( + GTK_SPIN_BUTTON (dialog->priv->size_edit), (gdouble) size_int); + + g_free (size); +} + +static void +html_editor_hrule_dialog_set_width (EHTMLEditorHRuleDialog *dialog) +{ + gchar *width, *units; + + g_return_if_fail (WEBKIT_DOM_IS_HTMLHR_ELEMENT (dialog->priv->hr_element)); + + units = gtk_combo_box_text_get_active_text ( + GTK_COMBO_BOX_TEXT (dialog->priv->unit_combo)); + width = g_strdup_printf ( + "%d%s", + (gint) gtk_spin_button_get_value ( + GTK_SPIN_BUTTON (dialog->priv->width_edit)), + units); + + webkit_dom_htmlhr_element_set_width (dialog->priv->hr_element, width); + + g_free (units); + g_free (width); +} + +static void +html_editor_hrule_dialog_get_width (EHTMLEditorHRuleDialog *dialog) +{ + gchar *width; + const gchar *units; + gint width_int = 0; + + g_return_if_fail (WEBKIT_DOM_IS_HTMLHR_ELEMENT (dialog->priv->hr_element)); + + width = webkit_dom_htmlhr_element_get_width (dialog->priv->hr_element); + if (width && *width) { + width_int = atoi (width); + + if (strstr (width, "%") != NULL) { + units = "units-percent"; + } else { + units = "units-px"; + } + } + + if (width_int == 0) { + width_int = 100; + units = "units-percent"; + } + + gtk_spin_button_set_value ( + GTK_SPIN_BUTTON (dialog->priv->width_edit), (gdouble) width_int); + gtk_combo_box_set_active_id ( + GTK_COMBO_BOX (dialog->priv->unit_combo), units); + + g_free (width); +} + +static void +html_editor_hrule_dialog_set_shading (EHTMLEditorHRuleDialog *dialog) +{ + gboolean no_shade; + + g_return_if_fail (WEBKIT_DOM_IS_HTMLHR_ELEMENT (dialog->priv->hr_element)); + + no_shade = !gtk_toggle_button_get_active ( + GTK_TOGGLE_BUTTON (dialog->priv->shaded_check)); + + webkit_dom_htmlhr_element_set_no_shade (dialog->priv->hr_element, no_shade); +} + +static void +html_editor_hrule_dialog_get_shading (EHTMLEditorHRuleDialog *dialog) +{ + g_return_if_fail (WEBKIT_DOM_IS_HTMLHR_ELEMENT (dialog->priv->hr_element)); + + gtk_toggle_button_set_active ( + GTK_TOGGLE_BUTTON (dialog->priv->shaded_check), + !webkit_dom_htmlhr_element_get_no_shade (dialog->priv->hr_element)); +} + +static void +html_editor_hrule_dialog_hide (GtkWidget *widget) +{ + EHTMLEditorHRuleDialogPrivate *priv; + + priv = E_HTML_EDITOR_HRULE_DIALOG_GET_PRIVATE (widget); + + priv->hr_element = NULL; + + GTK_WIDGET_CLASS (e_html_editor_hrule_dialog_parent_class)->hide (widget); +} + +static void +html_editor_hrule_dialog_show (GtkWidget *widget) +{ + EHTMLEditorHRuleDialog *dialog; + EHTMLEditor *editor; + EHTMLEditorSelection *editor_selection; + EHTMLEditorView *view; + + WebKitDOMDocument *document; + WebKitDOMDOMWindow *window; + WebKitDOMDOMSelection *selection; + WebKitDOMElement *rule; + + dialog = E_HTML_EDITOR_HRULE_DIALOG (widget); + editor = e_html_editor_dialog_get_editor (E_HTML_EDITOR_DIALOG (dialog)); + view = e_html_editor_get_view (editor); + editor_selection = e_html_editor_view_get_selection (view); + + document = webkit_web_view_get_dom_document ( + WEBKIT_WEB_VIEW (view)); + window = webkit_dom_document_get_default_view (document); + selection = webkit_dom_dom_window_get_selection (window); + if (webkit_dom_dom_selection_get_range_count (selection) < 1) { + GTK_WIDGET_CLASS (e_html_editor_hrule_dialog_parent_class)->show (widget); + return; + } + + rule = e_html_editor_view_get_element_under_mouse_click (view); + if (!rule) { + WebKitDOMElement *caret, *parent, *element; + + caret = e_html_editor_selection_save_caret_position (editor_selection); + + parent = webkit_dom_node_get_parent_element (WEBKIT_DOM_NODE (caret)); + element = caret; + + while (!WEBKIT_DOM_IS_HTML_BODY_ELEMENT (parent)) { + element = parent; + parent = webkit_dom_node_get_parent_element ( + WEBKIT_DOM_NODE (parent)); + } + + rule = webkit_dom_document_create_element (document, "HR", NULL); + + /* Insert horizontal rule into body below the caret */ + webkit_dom_node_insert_before ( + WEBKIT_DOM_NODE (parent), + WEBKIT_DOM_NODE (rule), + webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (element)), + NULL); + + e_html_editor_selection_clear_caret_position_marker (editor_selection); + + dialog->priv->hr_element = WEBKIT_DOM_HTMLHR_ELEMENT (rule); + + /* For new rule reset the values to default */ + gtk_spin_button_set_value ( + GTK_SPIN_BUTTON (dialog->priv->width_edit), 100.0); + gtk_combo_box_set_active_id ( + GTK_COMBO_BOX (dialog->priv->unit_combo), "units-percent"); + gtk_spin_button_set_value ( + GTK_SPIN_BUTTON (dialog->priv->size_edit), 2.0); + gtk_combo_box_set_active_id ( + GTK_COMBO_BOX (dialog->priv->alignment_combo), "left"); + gtk_toggle_button_set_active ( + GTK_TOGGLE_BUTTON (dialog->priv->shaded_check), FALSE); + + html_editor_hrule_dialog_set_alignment (dialog); + html_editor_hrule_dialog_set_size (dialog); + html_editor_hrule_dialog_set_alignment (dialog); + html_editor_hrule_dialog_set_shading (dialog); + + e_html_editor_view_set_changed (view, TRUE); + } else { + dialog->priv->hr_element = WEBKIT_DOM_HTMLHR_ELEMENT (rule); + + html_editor_hrule_dialog_get_alignment (dialog); + html_editor_hrule_dialog_get_size (dialog); + html_editor_hrule_dialog_get_width (dialog); + html_editor_hrule_dialog_get_shading (dialog); + } + + /* Chain up to parent implementation */ + GTK_WIDGET_CLASS (e_html_editor_hrule_dialog_parent_class)->show (widget); +} + +static void +e_html_editor_hrule_dialog_class_init (EHTMLEditorHRuleDialogClass *class) +{ + GtkWidgetClass *widget_class; + + g_type_class_add_private (class, sizeof (EHTMLEditorHRuleDialogPrivate)); + + widget_class = GTK_WIDGET_CLASS (class); + widget_class->show = html_editor_hrule_dialog_show; + widget_class->hide = html_editor_hrule_dialog_hide; +} + +static void +e_html_editor_hrule_dialog_init (EHTMLEditorHRuleDialog *dialog) +{ + GtkGrid *main_layout, *grid; + GtkWidget *widget; + + dialog->priv = E_HTML_EDITOR_HRULE_DIALOG_GET_PRIVATE (dialog); + + main_layout = e_html_editor_dialog_get_container (E_HTML_EDITOR_DIALOG (dialog)); + + /* == Size == */ + widget = gtk_label_new (""); + gtk_label_set_markup (GTK_LABEL (widget), _("Size")); + gtk_misc_set_alignment (GTK_MISC (widget), 0, 0.5); + gtk_grid_attach (main_layout, widget, 0, 0, 1, 1); + + grid = GTK_GRID (gtk_grid_new ()); + gtk_grid_set_column_spacing (grid, 5); + gtk_grid_set_row_spacing (grid, 5); + gtk_widget_set_margin_left (GTK_WIDGET (grid), 10); + gtk_grid_attach (main_layout, GTK_WIDGET (grid), 0, 1, 1, 1); + + /* Width */ + widget = gtk_spin_button_new_with_range (0.0, 100.0, 1.0); + gtk_spin_button_set_digits (GTK_SPIN_BUTTON (widget), 0); + gtk_spin_button_set_value (GTK_SPIN_BUTTON (widget), 100); + g_signal_connect_swapped ( + widget, "value-changed", + G_CALLBACK (html_editor_hrule_dialog_set_width), dialog); + dialog->priv->width_edit = widget; + gtk_grid_attach (grid, widget, 1, 0, 1, 1); + + widget = gtk_label_new_with_mnemonic (_("_Width:")); + gtk_label_set_justify (GTK_LABEL (widget), GTK_JUSTIFY_RIGHT); + gtk_label_set_mnemonic_widget (GTK_LABEL (widget), dialog->priv->size_edit); + gtk_grid_attach (grid, widget, 0, 0, 1, 1); + + widget = gtk_combo_box_text_new (); + gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (widget), "units-px", "px"); + gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (widget), "units-percent", "%"); + gtk_combo_box_set_active_id (GTK_COMBO_BOX (widget), "units-percent"); + g_signal_connect_swapped ( + widget, "changed", + G_CALLBACK (html_editor_hrule_dialog_set_width), dialog); + dialog->priv->unit_combo = widget; + gtk_grid_attach (grid, widget, 2, 0, 1, 1); + + /* Size */ + widget = gtk_spin_button_new_with_range (0.0, 100.0, 1.0); + gtk_spin_button_set_digits (GTK_SPIN_BUTTON (widget), 0); + gtk_spin_button_set_value (GTK_SPIN_BUTTON (widget), 2); + g_signal_connect_swapped ( + widget, "value-changed", + G_CALLBACK (html_editor_hrule_dialog_set_size), dialog); + dialog->priv->size_edit = widget; + gtk_grid_attach (grid, widget, 1, 1, 1, 1); + + widget = gtk_label_new_with_mnemonic (_("_Size:")); + gtk_label_set_justify (GTK_LABEL (widget), GTK_JUSTIFY_RIGHT); + gtk_label_set_mnemonic_widget (GTK_LABEL (widget), dialog->priv->size_edit); + gtk_grid_attach (grid, widget, 0, 1, 1, 1); + + /* == Style == */ + widget = gtk_label_new (""); + gtk_label_set_markup (GTK_LABEL (widget), _("Style")); + gtk_misc_set_alignment (GTK_MISC (widget), 0, 0.5); + gtk_grid_attach (main_layout, widget, 0, 2, 1, 1); + + grid = GTK_GRID (gtk_grid_new ()); + gtk_grid_set_column_spacing (grid, 5); + gtk_grid_set_row_spacing (grid, 5); + gtk_widget_set_margin_left (GTK_WIDGET (grid), 10); + gtk_grid_attach (main_layout, GTK_WIDGET (grid), 0, 3, 1, 1); + + /* Alignment */ + widget = gtk_combo_box_text_new (); + gtk_combo_box_text_append ( + GTK_COMBO_BOX_TEXT (widget), "left", _("Left")); + gtk_combo_box_text_append ( + GTK_COMBO_BOX_TEXT (widget), "center", _("Center")); + gtk_combo_box_text_append ( + GTK_COMBO_BOX_TEXT (widget), "right", _("Right")); + gtk_combo_box_set_active_id (GTK_COMBO_BOX (widget), "left"); + g_signal_connect_swapped ( + widget, "changed", + G_CALLBACK (html_editor_hrule_dialog_set_alignment), dialog); + dialog->priv->alignment_combo = widget; + gtk_grid_attach (grid, widget, 1, 0, 2, 1); + + widget = gtk_label_new_with_mnemonic (_("_Alignment:")); + gtk_label_set_justify (GTK_LABEL (widget), GTK_JUSTIFY_RIGHT); + gtk_label_set_mnemonic_widget (GTK_LABEL (widget), widget); + gtk_grid_attach (grid, widget, 0, 0, 1, 1); + + /* Shaded */ + widget = gtk_check_button_new_with_mnemonic (_("S_haded")); + g_signal_connect_swapped ( + widget, "toggled", + G_CALLBACK (html_editor_hrule_dialog_set_shading), dialog); + dialog->priv->shaded_check = widget; + gtk_grid_attach (grid, widget, 0, 1, 2, 1); + + gtk_widget_show_all (GTK_WIDGET (main_layout)); +} + +GtkWidget * +e_html_editor_hrule_dialog_new (EHTMLEditor *editor) +{ + return GTK_WIDGET ( + g_object_new ( + E_TYPE_HTML_EDITOR_HRULE_DIALOG, + "editor", editor, + "title", _("Rule properties"), + NULL)); +} diff --git a/e-util/e-html-editor-hrule-dialog.h b/e-util/e-html-editor-hrule-dialog.h new file mode 100644 index 0000000000..876fc2515c --- /dev/null +++ b/e-util/e-html-editor-hrule-dialog.h @@ -0,0 +1,70 @@ +/* + * e-html-editor-hrule-dialog.h + * + * Copyright (C) 2012 Dan Vrátil + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public 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_HTML_EDITOR_HRULE_DIALOG_H +#define E_HTML_EDITOR_HRULE_DIALOG_H + +#include + +/* Standard GObject macros */ +#define E_TYPE_HTML_EDITOR_HRULE_DIALOG \ + (e_html_editor_hrule_dialog_get_type ()) +#define E_HTML_EDITOR_HRULE_DIALOG(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST \ + ((obj), E_TYPE_HTML_EDITOR_HRULE_DIALOG, EHTMLEditorHRuleDialog)) +#define E_HTML_EDITOR_HRULE_DIALOG_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_CAST \ + ((cls), E_TYPE_HTML_EDITOR_HRULE_DIALOG, EHTMLEditorHRuleDialogClass)) +#define E_IS_HTML_EDITOR_HRULE_DIALOG(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE \ + ((obj), E_TYPE_HTML_EDITOR_HRULE_DIALOG)) +#define E_IS_HTML_EDITOR_HRULE_DIALOG_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_TYPE \ + ((cls), E_TYPE_HTML_EDITOR_HRULE_DIALOG)) +#define E_HTML_EDITOR_HRULE_DIALOG_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS \ + ((obj), E_TYPE_HTML_EDITOR_HRULE_DIALOG, EHTMLEditorHRuleDialogClass)) + +G_BEGIN_DECLS + +typedef struct _EHTMLEditorHRuleDialog EHTMLEditorHRuleDialog; +typedef struct _EHTMLEditorHRuleDialogClass EHTMLEditorHRuleDialogClass; +typedef struct _EHTMLEditorHRuleDialogPrivate EHTMLEditorHRuleDialogPrivate; + +struct _EHTMLEditorHRuleDialog { + EHTMLEditorDialog parent; + EHTMLEditorHRuleDialogPrivate *priv; +}; + +struct _EHTMLEditorHRuleDialogClass { + EHTMLEditorDialogClass parent_class; +}; + +GType e_html_editor_hrule_dialog_get_type + (void) G_GNUC_CONST; +GtkWidget * e_html_editor_hrule_dialog_new (EHTMLEditor *editor); + +G_END_DECLS + +#endif /* E_HTML_EDITOR_HRULE_DIALOG_H */ diff --git a/e-util/e-html-editor-image-dialog.c b/e-util/e-html-editor-image-dialog.c new file mode 100644 index 0000000000..346de4419b --- /dev/null +++ b/e-util/e-html-editor-image-dialog.c @@ -0,0 +1,703 @@ +/* + * e-html-editor-image-dialog.h + * + * Copyright (C) 2012 Dan Vrátil + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public 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-html-editor-image-dialog.h" + +#include +#include + +#include "e-html-editor-utils.h" +#include "e-image-chooser-dialog.h" + +#define E_HTML_EDITOR_IMAGE_DIALOG_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE \ + ((obj), E_TYPE_HTML_EDITOR_IMAGE_DIALOG, EHTMLEditorImageDialogPrivate)) + +struct _EHTMLEditorImageDialogPrivate { + GtkWidget *file_chooser; + GtkWidget *description_edit; + + GtkWidget *width_edit; + GtkWidget *width_units; + GtkWidget *height_edit; + GtkWidget *height_units; + GtkWidget *alignment; + + GtkWidget *x_padding_edit; + GtkWidget *y_padding_edit; + GtkWidget *border_edit; + + GtkWidget *url_edit; + GtkWidget *test_url_button; + + WebKitDOMHTMLImageElement *image; +}; + +G_DEFINE_TYPE ( + EHTMLEditorImageDialog, + e_html_editor_image_dialog, + E_TYPE_HTML_EDITOR_DIALOG); + +static void +html_editor_image_dialog_set_src (EHTMLEditorImageDialog *dialog) +{ + EHTMLEditor *editor; + EHTMLEditorSelection *editor_selection; + EHTMLEditorView *view; + + editor = e_html_editor_dialog_get_editor (E_HTML_EDITOR_DIALOG (dialog)); + view = e_html_editor_get_view (editor); + editor_selection = e_html_editor_view_get_selection (view); + + e_html_editor_selection_replace_image_src ( + editor_selection, + WEBKIT_DOM_ELEMENT (dialog->priv->image), + gtk_file_chooser_get_uri ( + GTK_FILE_CHOOSER (dialog->priv->file_chooser))); +} + +static void +html_editor_image_dialog_set_alt (EHTMLEditorImageDialog *dialog) +{ + webkit_dom_html_image_element_set_alt ( + dialog->priv->image, + gtk_entry_get_text (GTK_ENTRY (dialog->priv->description_edit))); +} + +static void +html_editor_image_dialog_set_width (EHTMLEditorImageDialog *dialog) +{ + gint requested; + gulong natural; + gint width; + + natural = webkit_dom_html_image_element_get_natural_width ( + dialog->priv->image); + requested = gtk_spin_button_get_value_as_int ( + GTK_SPIN_BUTTON (dialog->priv->width_edit)); + + switch (gtk_combo_box_get_active ( + GTK_COMBO_BOX (dialog->priv->width_units))) { + + case 0: /* px */ + width = requested; + break; + + case 1: /* percent */ + width = natural * requested * 0.01; + break; + + case 2: /* follow */ + width = natural; + break; + + } + + webkit_dom_html_image_element_set_width (dialog->priv->image, width); +} + +static void +html_editor_image_dialog_set_width_units (EHTMLEditorImageDialog *dialog) +{ + gint requested; + gulong natural; + gint width = 0; + + natural = webkit_dom_html_image_element_get_natural_width ( + dialog->priv->image); + requested = gtk_spin_button_get_value_as_int ( + GTK_SPIN_BUTTON (dialog->priv->width_edit)); + + switch (gtk_combo_box_get_active ( + GTK_COMBO_BOX (dialog->priv->width_units))) { + + case 0: /* px */ + if (gtk_widget_is_sensitive (dialog->priv->width_edit)) { + width = requested * natural * 0.01; + } else { + width = natural; + } + webkit_dom_element_remove_attribute ( + WEBKIT_DOM_ELEMENT (dialog->priv->image), "style"); + gtk_widget_set_sensitive (dialog->priv->width_edit, TRUE); + break; + + case 1: /* percent */ + if (gtk_widget_is_sensitive (dialog->priv->width_edit)) { + width = (((gdouble) requested) / natural) * 100; + } else { + width = 100; + } + webkit_dom_element_remove_attribute ( + WEBKIT_DOM_ELEMENT (dialog->priv->image), "style"); + gtk_widget_set_sensitive (dialog->priv->width_edit, TRUE); + break; + + case 2: /* follow */ + webkit_dom_element_set_attribute ( + WEBKIT_DOM_ELEMENT (dialog->priv->image), + "style", + "width: auto;", + NULL); + gtk_widget_set_sensitive (dialog->priv->width_edit, FALSE); + break; + } + + if (width != 0) { + gtk_spin_button_set_value ( + GTK_SPIN_BUTTON (dialog->priv->width_edit), width); + } +} + +static void +html_editor_image_dialog_set_height (EHTMLEditorImageDialog *dialog) +{ + gint requested; + gulong natural; + gint height; + + natural = webkit_dom_html_image_element_get_natural_height ( + dialog->priv->image); + requested = gtk_spin_button_get_value_as_int ( + GTK_SPIN_BUTTON (dialog->priv->height_edit)); + + switch (gtk_combo_box_get_active ( + GTK_COMBO_BOX (dialog->priv->height_units))) { + + case 0: /* px */ + height = requested; + break; + + case 1: /* percent */ + height = natural * requested * 0.01; + break; + + case 2: /* follow */ + height = natural; + break; + + } + + webkit_dom_html_image_element_set_height (dialog->priv->image, height); +} + +static void +html_editor_image_dialog_set_height_units (EHTMLEditorImageDialog *dialog) +{ + gint requested; + gulong natural; + gint height = -1; + + natural = webkit_dom_html_image_element_get_natural_height ( + dialog->priv->image); + requested = gtk_spin_button_get_value_as_int ( + GTK_SPIN_BUTTON (dialog->priv->height_edit)); + + switch (gtk_combo_box_get_active ( + GTK_COMBO_BOX (dialog->priv->height_units))) { + + case 0: /* px */ + if (gtk_widget_is_sensitive (dialog->priv->height_edit)) { + height = requested * natural * 0.01; + } else { + height = natural; + } + webkit_dom_element_remove_attribute ( + WEBKIT_DOM_ELEMENT (dialog->priv->image), "style"); + gtk_widget_set_sensitive (dialog->priv->height_edit, TRUE); + break; + + case 1: /* percent */ + if (gtk_widget_is_sensitive (dialog->priv->height_edit)) { + height = (((gdouble) requested) / natural) * 100; + } else { + height = 100; + } + webkit_dom_element_remove_attribute ( + WEBKIT_DOM_ELEMENT (dialog->priv->image), "style"); + gtk_widget_set_sensitive (dialog->priv->height_edit, TRUE); + break; + + case 2: /* follow */ + webkit_dom_element_set_attribute ( + WEBKIT_DOM_ELEMENT (dialog->priv->image), + "style", + "height: auto;", + NULL); + gtk_widget_set_sensitive (dialog->priv->height_edit, FALSE); + break; + } + + if (height != -1) { + gtk_spin_button_set_value ( + GTK_SPIN_BUTTON (dialog->priv->height_edit), height); + } +} + +static void +html_editor_image_dialog_set_alignment (EHTMLEditorImageDialog *dialog) +{ + webkit_dom_html_image_element_set_align ( + dialog->priv->image, + gtk_combo_box_get_active_id ( + GTK_COMBO_BOX (dialog->priv->alignment))); +} + +static void +html_editor_image_dialog_set_x_padding (EHTMLEditorImageDialog *dialog) +{ + webkit_dom_html_image_element_set_hspace ( + dialog->priv->image, + gtk_spin_button_get_value_as_int ( + GTK_SPIN_BUTTON (dialog->priv->x_padding_edit))); +} + +static void +html_editor_image_dialog_set_y_padding (EHTMLEditorImageDialog *dialog) +{ + webkit_dom_html_image_element_set_vspace ( + dialog->priv->image, + gtk_spin_button_get_value_as_int ( + GTK_SPIN_BUTTON (dialog->priv->y_padding_edit))); +} + +static void +html_editor_image_dialog_set_border (EHTMLEditorImageDialog *dialog) +{ + gchar *val; + + val = g_strdup_printf ( + "%d", gtk_spin_button_get_value_as_int ( + GTK_SPIN_BUTTON (dialog->priv->border_edit))); + + webkit_dom_html_image_element_set_border (dialog->priv->image, val); + + g_free (val); +} + +static void +html_editor_image_dialog_set_url (EHTMLEditorImageDialog *dialog) +{ + WebKitDOMElement *link; + const gchar *url; + + url = gtk_entry_get_text (GTK_ENTRY (dialog->priv->url_edit)); + link = e_html_editor_dom_node_find_parent_element ( + WEBKIT_DOM_NODE (dialog->priv->image), "A"); + + if (link) { + if (!url || !*url) { + webkit_dom_node_insert_before ( + webkit_dom_node_get_parent_node ( + WEBKIT_DOM_NODE (link)), + WEBKIT_DOM_NODE (dialog->priv->image), + WEBKIT_DOM_NODE (link), NULL); + webkit_dom_node_remove_child ( + webkit_dom_node_get_parent_node ( + WEBKIT_DOM_NODE (link)), + WEBKIT_DOM_NODE (link), NULL); + } else { + webkit_dom_html_anchor_element_set_href ( + WEBKIT_DOM_HTML_ANCHOR_ELEMENT (link), url); + } + } else { + if (url && *url) { + WebKitDOMDocument *document; + + document = webkit_dom_node_get_owner_document ( + WEBKIT_DOM_NODE (dialog->priv->image)); + link = webkit_dom_document_create_element ( + document, "A", NULL); + + webkit_dom_html_anchor_element_set_href ( + WEBKIT_DOM_HTML_ANCHOR_ELEMENT (link), url); + + webkit_dom_node_insert_before ( + webkit_dom_node_get_parent_node ( + WEBKIT_DOM_NODE (dialog->priv->image)), + WEBKIT_DOM_NODE (link), + WEBKIT_DOM_NODE (dialog->priv->image), NULL); + + webkit_dom_node_append_child ( + WEBKIT_DOM_NODE (link), + WEBKIT_DOM_NODE (dialog->priv->image), NULL); + } + } +} + +static void +html_editor_image_dialog_test_url (EHTMLEditorImageDialog *dialog) +{ + gtk_show_uri ( + gtk_window_get_screen (GTK_WINDOW (dialog)), + gtk_entry_get_text (GTK_ENTRY (dialog->priv->url_edit)), + GDK_CURRENT_TIME, + NULL); +} + +static void +html_editor_image_dialog_show (GtkWidget *widget) +{ + EHTMLEditorImageDialog *dialog; + WebKitDOMElement *link; + gchar *tmp; + glong val; + + dialog = E_HTML_EDITOR_IMAGE_DIALOG (widget); + + if (!dialog->priv->image) { + return; + } + + tmp = webkit_dom_element_get_attribute ( + WEBKIT_DOM_ELEMENT (dialog->priv->image), "data-uri"); + if (tmp && *tmp) { + gtk_file_chooser_set_uri ( + GTK_FILE_CHOOSER (dialog->priv->file_chooser), tmp); + gtk_widget_set_sensitive ( + GTK_WIDGET (dialog->priv->file_chooser), TRUE); + g_free (tmp); + } else { + gtk_file_chooser_set_uri ( + GTK_FILE_CHOOSER (dialog->priv->file_chooser), ""); + gtk_widget_set_sensitive ( + GTK_WIDGET (dialog->priv->file_chooser), FALSE); + } + + tmp = webkit_dom_html_image_element_get_alt (dialog->priv->image); + gtk_entry_set_text (GTK_ENTRY (dialog->priv->description_edit), tmp ? tmp : ""); + g_free (tmp); + + val = webkit_dom_html_image_element_get_width (dialog->priv->image); + gtk_spin_button_set_value ( + GTK_SPIN_BUTTON (dialog->priv->width_edit), val); + gtk_combo_box_set_active_id ( + GTK_COMBO_BOX (dialog->priv->width_units), "units-px"); + + val = webkit_dom_html_image_element_get_height (dialog->priv->image); + gtk_spin_button_set_value ( + GTK_SPIN_BUTTON (dialog->priv->height_edit), val); + gtk_combo_box_set_active_id ( + GTK_COMBO_BOX (dialog->priv->height_units), "units-px"); + + tmp = webkit_dom_html_image_element_get_border (dialog->priv->image); + gtk_combo_box_set_active_id ( + GTK_COMBO_BOX (dialog->priv->alignment), + (tmp && *tmp) ? tmp : "bottom"); + g_free (tmp); + + val = webkit_dom_html_image_element_get_hspace (dialog->priv->image); + gtk_spin_button_set_value ( + GTK_SPIN_BUTTON (dialog->priv->x_padding_edit), val); + + val = webkit_dom_html_image_element_get_vspace (dialog->priv->image); + gtk_spin_button_set_value ( + GTK_SPIN_BUTTON (dialog->priv->y_padding_edit), val); + + link = e_html_editor_dom_node_find_parent_element ( + WEBKIT_DOM_NODE (dialog->priv->image), "A"); + if (link) { + tmp = webkit_dom_html_anchor_element_get_href ( + WEBKIT_DOM_HTML_ANCHOR_ELEMENT (link)); + gtk_entry_set_text (GTK_ENTRY (dialog->priv->url_edit), tmp); + g_free (tmp); + } + + /* Chain up to parent implementation */ + GTK_WIDGET_CLASS (e_html_editor_image_dialog_parent_class)->show (widget); +} + +static void +html_editor_image_dialog_hide (GtkWidget *widget) +{ + EHTMLEditorImageDialogPrivate *priv; + + priv = E_HTML_EDITOR_IMAGE_DIALOG_GET_PRIVATE (widget); + + priv->image = NULL; + + GTK_WIDGET_CLASS (e_html_editor_image_dialog_parent_class)->hide (widget); +} + +static void +e_html_editor_image_dialog_class_init (EHTMLEditorImageDialogClass *class) +{ + GtkWidgetClass *widget_class; + + g_type_class_add_private (class, sizeof (EHTMLEditorImageDialogPrivate)); + + widget_class = GTK_WIDGET_CLASS (class); + widget_class->show = html_editor_image_dialog_show; + widget_class->hide = html_editor_image_dialog_hide; +} + +static void +e_html_editor_image_dialog_init (EHTMLEditorImageDialog *dialog) +{ + GtkGrid *main_layout, *grid; + GtkWidget *widget; + GtkFileFilter *file_filter; + + dialog->priv = E_HTML_EDITOR_IMAGE_DIALOG_GET_PRIVATE (dialog); + + main_layout = e_html_editor_dialog_get_container (E_HTML_EDITOR_DIALOG (dialog)); + + /* == General == */ + widget = gtk_label_new (""); + gtk_label_set_markup (GTK_LABEL (widget), _("General")); + gtk_misc_set_alignment (GTK_MISC (widget), 0, 0.5); + gtk_grid_attach (main_layout, widget, 0, 0, 1, 1); + + grid = GTK_GRID (gtk_grid_new ()); + gtk_grid_set_row_spacing (grid, 5); + gtk_grid_set_column_spacing (grid, 5); + gtk_grid_attach (main_layout, GTK_WIDGET (grid), 0, 1, 1, 1); + gtk_widget_set_margin_left (GTK_WIDGET (grid), 10); + + /* Source */ + widget = e_image_chooser_dialog_new ( + _("Choose Background Image"), + GTK_WINDOW (dialog)); + gtk_file_chooser_set_action ( + GTK_FILE_CHOOSER (widget), GTK_FILE_CHOOSER_ACTION_OPEN); + + file_filter = gtk_file_filter_new (); + gtk_file_filter_set_name (file_filter, _("Images")); + gtk_file_filter_add_mime_type (file_filter, "image/*"); + + widget = gtk_file_chooser_button_new_with_dialog (widget); + gtk_widget_set_hexpand (widget, TRUE); + gtk_grid_attach (grid, widget, 1, 0, 1, 1); + g_signal_connect_swapped ( + widget, "file-set", + G_CALLBACK (html_editor_image_dialog_set_src), dialog); + dialog->priv->file_chooser = widget; + + widget = gtk_label_new_with_mnemonic (_("_Source:")); + gtk_label_set_justify (GTK_LABEL (widget), GTK_JUSTIFY_RIGHT); + gtk_label_set_mnemonic_widget (GTK_LABEL (widget), dialog->priv->file_chooser); + gtk_grid_attach (grid, widget, 0, 0, 1, 1); + + /* Description */ + widget = gtk_entry_new (); + gtk_widget_set_hexpand (widget, TRUE); + gtk_grid_attach (grid, widget, 1, 1, 1, 1); + g_signal_connect_swapped ( + widget, "notify::text", + G_CALLBACK (html_editor_image_dialog_set_alt), dialog); + dialog->priv->description_edit = widget; + + widget = gtk_label_new_with_mnemonic (_("_Description:")); + gtk_label_set_justify (GTK_LABEL (widget), GTK_JUSTIFY_RIGHT); + gtk_label_set_mnemonic_widget (GTK_LABEL (widget), dialog->priv->description_edit); + gtk_grid_attach (grid, widget, 0, 1, 1, 1); + + /* == Layout == */ + widget = gtk_label_new (""); + gtk_label_set_markup (GTK_LABEL (widget), _("Layout")); + gtk_misc_set_alignment (GTK_MISC (widget), 0, 0.5); + gtk_grid_attach (main_layout, widget, 0, 2, 1, 1); + + grid = GTK_GRID (gtk_grid_new ()); + gtk_grid_set_row_spacing (grid, 5); + gtk_grid_set_column_spacing (grid, 5); + gtk_grid_attach (main_layout, GTK_WIDGET (grid), 0, 3, 1, 1); + gtk_widget_set_margin_left (GTK_WIDGET (grid), 10); + + /* Width */ + widget = gtk_spin_button_new_with_range (1, G_MAXUINT, 1); + gtk_grid_attach (grid, widget, 1, 0, 1, 1); + g_signal_connect_swapped ( + widget, "value-changed", + G_CALLBACK (html_editor_image_dialog_set_width), dialog); + dialog->priv->width_edit = widget; + + widget = gtk_label_new_with_mnemonic (_("_Width:")); + gtk_label_set_justify (GTK_LABEL (widget), GTK_JUSTIFY_RIGHT); + gtk_label_set_mnemonic_widget (GTK_LABEL (widget), dialog->priv->width_edit); + gtk_grid_attach (grid, widget, 0, 0, 1, 1); + + widget = gtk_combo_box_text_new (); + gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (widget), "units-px", "px"); + gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (widget), "units-percent", "%"); + gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (widget), "units-follow", "follow"); + gtk_combo_box_set_active_id (GTK_COMBO_BOX (widget), "units-px"); + gtk_grid_attach (grid, widget, 2, 0, 1, 1); + g_signal_connect_swapped ( + widget, "changed", + G_CALLBACK (html_editor_image_dialog_set_width_units), dialog); + dialog->priv->width_units = widget; + + /* Height */ + widget = gtk_spin_button_new_with_range (1, G_MAXUINT, 1); + gtk_grid_attach (grid, widget, 1, 1, 1, 1); + g_signal_connect_swapped ( + widget, "value-changed", + G_CALLBACK (html_editor_image_dialog_set_height), dialog); + dialog->priv->height_edit = widget; + + widget = gtk_label_new_with_mnemonic (_("_Height:")); + gtk_label_set_justify (GTK_LABEL (widget), GTK_JUSTIFY_RIGHT); + gtk_label_set_mnemonic_widget (GTK_LABEL (widget), dialog->priv->height_edit); + gtk_grid_attach (grid, widget, 0, 1, 1, 1); + + widget = gtk_combo_box_text_new (); + gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (widget), "units-px", "px"); + gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (widget), "units-percent", "%"); + gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (widget), "units-follow", "follow"); + gtk_combo_box_set_active_id (GTK_COMBO_BOX (widget), "units-px"); + gtk_grid_attach (grid, widget, 2, 1, 1, 1); + g_signal_connect_swapped ( + widget, "changed", + G_CALLBACK (html_editor_image_dialog_set_height_units), dialog); + dialog->priv->height_units = widget; + + /* Alignment */ + widget = gtk_combo_box_text_new (); + gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (widget), "top", _("Top")); + gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (widget), "middle", _("Middle")); + gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (widget), "bottom", _("Bottom")); + gtk_combo_box_set_active_id (GTK_COMBO_BOX (widget), "bottom"); + gtk_grid_attach (grid, widget, 1, 2, 1, 1); + g_signal_connect_swapped ( + widget, "changed", + G_CALLBACK (html_editor_image_dialog_set_alignment), dialog); + dialog->priv->alignment = widget; + + widget = gtk_label_new_with_mnemonic (_("_Alignment")); + gtk_label_set_justify (GTK_LABEL (widget), GTK_JUSTIFY_RIGHT); + gtk_label_set_mnemonic_widget (GTK_LABEL (widget), dialog->priv->alignment); + gtk_grid_attach (grid, widget, 0, 2, 1, 1); + + /* X Padding */ + widget = gtk_spin_button_new_with_range (0, G_MAXUINT, 1); + gtk_grid_attach (grid, widget, 5, 0, 1, 1); + g_signal_connect_swapped ( + widget, "value-changed", + G_CALLBACK (html_editor_image_dialog_set_x_padding), dialog); + dialog->priv->x_padding_edit = widget; + + widget = gtk_label_new_with_mnemonic (_("_X-Padding:")); + gtk_label_set_justify (GTK_LABEL (widget), GTK_JUSTIFY_RIGHT); + gtk_label_set_mnemonic_widget (GTK_LABEL (widget), dialog->priv->x_padding_edit); + gtk_grid_attach (grid, widget, 4, 0, 1, 1); + + widget = gtk_label_new ("px"); + gtk_grid_attach (grid, widget, 6, 0, 1, 1); + + /* Y Padding */ + widget = gtk_spin_button_new_with_range (0, G_MAXUINT, 1); + gtk_grid_attach (grid, widget, 5, 1, 1, 1); + g_signal_connect_swapped ( + widget, "value-changed", + G_CALLBACK (html_editor_image_dialog_set_y_padding), dialog); + dialog->priv->y_padding_edit = widget; + + widget = gtk_label_new_with_mnemonic (_("_Y-Padding:")); + gtk_label_set_justify (GTK_LABEL (widget), GTK_JUSTIFY_RIGHT); + gtk_label_set_mnemonic_widget (GTK_LABEL (widget), dialog->priv->y_padding_edit); + gtk_grid_attach (grid, widget, 4, 1, 1, 1); + + widget = gtk_label_new ("px"); + gtk_grid_attach (grid, widget, 6, 1, 1, 1); + + /* Border */ + widget = gtk_spin_button_new_with_range (0, G_MAXUINT, 1); + gtk_grid_attach (grid, widget, 5, 2, 1, 1); + g_signal_connect_swapped ( + widget, "value-changed", + G_CALLBACK (html_editor_image_dialog_set_border), dialog); + dialog->priv->border_edit = widget; + + widget = gtk_label_new_with_mnemonic (_("_Border:")); + gtk_label_set_justify (GTK_LABEL (widget), GTK_JUSTIFY_RIGHT); + gtk_label_set_mnemonic_widget (GTK_LABEL (widget), dialog->priv->border_edit); + gtk_grid_attach (grid, widget, 4, 2, 1, 1); + + widget = gtk_label_new ("px"); + gtk_grid_attach (grid, widget, 6, 2, 1, 1); + + /* == Layout == */ + widget = gtk_label_new (""); + gtk_label_set_markup (GTK_LABEL (widget), _("Link")); + gtk_misc_set_alignment (GTK_MISC (widget), 0, 0.5); + gtk_grid_attach (main_layout, widget, 0, 4, 1, 1); + + grid = GTK_GRID (gtk_grid_new ()); + gtk_grid_set_row_spacing (grid, 5); + gtk_grid_set_column_spacing (grid, 5); + gtk_grid_attach (main_layout, GTK_WIDGET (grid), 0, 5, 1, 1); + gtk_widget_set_margin_left (GTK_WIDGET (grid), 10); + + widget = gtk_entry_new (); + gtk_grid_attach (grid, widget, 1 ,0, 1, 1); + gtk_widget_set_hexpand (widget, TRUE); + g_signal_connect_swapped ( + widget, "notify::text", + G_CALLBACK (html_editor_image_dialog_set_url), dialog); + dialog->priv->url_edit = widget; + + widget = gtk_label_new_with_mnemonic (_("_URL:")); + gtk_label_set_justify (GTK_LABEL (widget), GTK_JUSTIFY_RIGHT); + gtk_label_set_mnemonic_widget (GTK_LABEL (widget), dialog->priv->url_edit); + gtk_grid_attach (grid, widget, 0, 0, 1, 1); + + widget = gtk_button_new_with_mnemonic (_("_Test URL...")); + gtk_grid_attach (grid, widget, 2, 0, 1, 1); + g_signal_connect_swapped ( + widget, "clicked", + G_CALLBACK (html_editor_image_dialog_test_url), dialog); + dialog->priv->test_url_button = widget; + + gtk_widget_show_all (GTK_WIDGET (main_layout)); +} + +GtkWidget * +e_html_editor_image_dialog_new (EHTMLEditor *editor) +{ + return GTK_WIDGET ( + g_object_new ( + E_TYPE_HTML_EDITOR_IMAGE_DIALOG, + "editor", editor, + "title", N_("Image Properties"), + NULL)); +} + +void +e_html_editor_image_dialog_show (EHTMLEditorImageDialog *dialog, + WebKitDOMNode *image) +{ + EHTMLEditorImageDialogClass *class; + + g_return_if_fail (E_IS_HTML_EDITOR_IMAGE_DIALOG (dialog)); + + if (image) { + dialog->priv->image = WEBKIT_DOM_HTML_IMAGE_ELEMENT (image); + } else { + dialog->priv->image = NULL; + } + + class = E_HTML_EDITOR_IMAGE_DIALOG_GET_CLASS (dialog); + GTK_WIDGET_CLASS (class)->show (GTK_WIDGET (dialog)); +} diff --git a/e-util/e-html-editor-image-dialog.h b/e-util/e-html-editor-image-dialog.h new file mode 100644 index 0000000000..efdbaf963c --- /dev/null +++ b/e-util/e-html-editor-image-dialog.h @@ -0,0 +1,73 @@ +/* + * e-html-editor-image-dialog.h + * + * Copyright (C) 2012 Dan Vrátil + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public 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_HTML_EDITOR_IMAGE_DIALOG_H +#define E_HTML_EDITOR_IMAGE_DIALOG_H + +#include + +/* Standard GObject macros */ +#define E_TYPE_HTML_EDITOR_IMAGE_DIALOG \ + (e_html_editor_image_dialog_get_type ()) +#define E_HTML_EDITOR_IMAGE_DIALOG(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST \ + ((obj), E_TYPE_HTML_EDITOR_IMAGE_DIALOG, EHTMLEditorImageDialog)) +#define E_HTML_EDITOR_IMAGE_DIALOG_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_CAST \ + ((cls), E_TYPE_HTML_EDITOR_IMAGE_DIALOG, EHTMLEditorImageDialogClass)) +#define E_IS_HTML_EDITOR_IMAGE_DIALOG(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE \ + ((obj), E_TYPE_HTML_EDITOR_IMAGE_DIALOG)) +#define E_IS_HTML_EDITOR_IMAGE_DIALOG_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_TYPE \ + ((cls), E_TYPE_HTML_EDITOR_IMAGE_DIALOG)) +#define E_HTML_EDITOR_IMAGE_DIALOG_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS \ + ((obj), E_TYPE_HTML_EDITOR_IMAGE_DIALOG, EHTMLEditorImageDialogClass)) + +G_BEGIN_DECLS + +typedef struct _EHTMLEditorImageDialog EHTMLEditorImageDialog; +typedef struct _EHTMLEditorImageDialogClass EHTMLEditorImageDialogClass; +typedef struct _EHTMLEditorImageDialogPrivate EHTMLEditorImageDialogPrivate; + +struct _EHTMLEditorImageDialog { + EHTMLEditorDialog parent; + EHTMLEditorImageDialogPrivate *priv; +}; + +struct _EHTMLEditorImageDialogClass { + EHTMLEditorDialogClass parent_class; +}; + +GType e_html_editor_image_dialog_get_type + (void) G_GNUC_CONST; +GtkWidget * e_html_editor_image_dialog_new (EHTMLEditor *editor); +void e_html_editor_image_dialog_show (EHTMLEditorImageDialog *dialog, + WebKitDOMNode *image); + +G_END_DECLS + +#endif /* E_HTML_EDITOR_IMAGE_DIALOG_H */ + diff --git a/e-util/e-html-editor-link-dialog.c b/e-util/e-html-editor-link-dialog.c new file mode 100644 index 0000000000..0572d07c09 --- /dev/null +++ b/e-util/e-html-editor-link-dialog.c @@ -0,0 +1,390 @@ +/* + * e-html-editor-link-dialog.h + * + * Copyright (C) 2012 Dan Vrátil + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public 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-html-editor-link-dialog.h" +#include "e-html-editor-selection.h" +#include "e-html-editor-utils.h" +#include "e-html-editor-view.h" + +#include + +#define E_HTML_EDITOR_LINK_DIALOG_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE \ + ((obj), E_TYPE_HTML_EDITOR_LINK_DIALOG, EHTMLEditorLinkDialogPrivate)) + +G_DEFINE_TYPE ( + EHTMLEditorLinkDialog, + e_html_editor_link_dialog, + E_TYPE_HTML_EDITOR_DIALOG); + +struct _EHTMLEditorLinkDialogPrivate { + GtkWidget *url_edit; + GtkWidget *label_edit; + GtkWidget *test_button; + + GtkWidget *remove_link_button; + GtkWidget *ok_button; + + gboolean label_autofill; +}; + +static void +html_editor_link_dialog_test_link (EHTMLEditorLinkDialog *dialog) +{ + gtk_show_uri ( + gtk_window_get_screen (GTK_WINDOW (dialog)), + gtk_entry_get_text (GTK_ENTRY (dialog->priv->url_edit)), + GDK_CURRENT_TIME, + NULL); +} + +static void +html_editor_link_dialog_url_changed (EHTMLEditorLinkDialog *dialog) +{ + if (dialog->priv->label_autofill && + gtk_widget_is_sensitive (dialog->priv->label_edit)) { + const gchar *text; + + text = gtk_entry_get_text ( + GTK_ENTRY (dialog->priv->url_edit)); + gtk_entry_set_text ( + GTK_ENTRY (dialog->priv->label_edit), text); + } +} + +static gboolean +html_editor_link_dialog_description_changed (EHTMLEditorLinkDialog *dialog) +{ + const gchar *text; + + text = gtk_entry_get_text (GTK_ENTRY (dialog->priv->label_edit)); + dialog->priv->label_autofill = (*text == '\0'); + + return FALSE; +} + +static void +html_editor_link_dialog_remove_link (EHTMLEditorLinkDialog *dialog) +{ + EHTMLEditor *editor; + EHTMLEditorView *view; + EHTMLEditorSelection *selection; + + editor = e_html_editor_dialog_get_editor (E_HTML_EDITOR_DIALOG (dialog)); + view = e_html_editor_get_view (editor); + selection = e_html_editor_view_get_selection (view); + e_html_editor_selection_unlink (selection); + + gtk_widget_hide (GTK_WIDGET (dialog)); +} + +static void +html_editor_link_dialog_ok (EHTMLEditorLinkDialog *dialog) +{ + EHTMLEditor *editor; + EHTMLEditorView *view; + EHTMLEditorSelection *selection; + WebKitDOMDocument *document; + WebKitDOMDOMWindow *window; + WebKitDOMDOMSelection *dom_selection; + WebKitDOMRange *range; + WebKitDOMElement *link; + + editor = e_html_editor_dialog_get_editor (E_HTML_EDITOR_DIALOG (dialog)); + view = e_html_editor_get_view (editor); + selection = e_html_editor_view_get_selection (view); + + document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view)); + window = webkit_dom_document_get_default_view (document); + dom_selection = webkit_dom_dom_window_get_selection (window); + + if (!dom_selection || + (webkit_dom_dom_selection_get_range_count (dom_selection) == 0)) { + gtk_widget_hide (GTK_WIDGET (dialog)); + return; + } + + range = webkit_dom_dom_selection_get_range_at (dom_selection, 0, NULL); + link = e_html_editor_dom_node_find_parent_element ( + webkit_dom_range_get_start_container (range, NULL), "A"); + if (!link) { + if ((webkit_dom_range_get_start_container (range, NULL) != + webkit_dom_range_get_end_container (range, NULL)) || + (webkit_dom_range_get_start_offset (range, NULL) != + webkit_dom_range_get_end_offset (range, NULL))) { + + WebKitDOMDocumentFragment *fragment; + fragment = webkit_dom_range_extract_contents (range, NULL); + link = e_html_editor_dom_node_find_child_element ( + WEBKIT_DOM_NODE (fragment), "A"); + webkit_dom_range_insert_node ( + range, WEBKIT_DOM_NODE (fragment), NULL); + + webkit_dom_dom_selection_set_base_and_extent ( + dom_selection, + webkit_dom_range_get_start_container (range, NULL), + webkit_dom_range_get_start_offset (range, NULL), + webkit_dom_range_get_end_container (range, NULL), + webkit_dom_range_get_end_offset (range, NULL), + NULL); + } else { + /* get element that was clicked on */ + link = e_html_editor_view_get_element_under_mouse_click (view); + if (!WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT (link)) + link = NULL; + } + } + + if (link) { + webkit_dom_html_anchor_element_set_href ( + WEBKIT_DOM_HTML_ANCHOR_ELEMENT (link), + gtk_entry_get_text (GTK_ENTRY (dialog->priv->url_edit))); + webkit_dom_html_element_set_inner_html ( + WEBKIT_DOM_HTML_ELEMENT (link), + gtk_entry_get_text (GTK_ENTRY (dialog->priv->label_edit)), + NULL); + } else { + gchar *text; + + /* Check whether a text is selected or not */ + text = webkit_dom_range_get_text (range); + if (text && *text) { + e_html_editor_selection_create_link ( + selection, + gtk_entry_get_text ( + GTK_ENTRY (dialog->priv->url_edit))); + } else { + gchar *html = g_strdup_printf ( + "%s", + gtk_entry_get_text ( + GTK_ENTRY (dialog->priv->url_edit)), + gtk_entry_get_text ( + GTK_ENTRY (dialog->priv->label_edit))); + + e_html_editor_view_exec_command ( + view, E_HTML_EDITOR_VIEW_COMMAND_INSERT_HTML, html); + + g_free (html); + + } + + g_free (text); + } + + gtk_widget_hide (GTK_WIDGET (dialog)); +} + +static gboolean +html_editor_link_dialog_entry_key_pressed (EHTMLEditorLinkDialog *dialog, + GdkEventKey *event) +{ + /* We can't do thins in key_released, because then you could not open + * this dialog from main menu by pressing enter on Insert->Link action */ + if (event->keyval == GDK_KEY_Return) { + html_editor_link_dialog_ok (dialog); + return TRUE; + } + + return FALSE; +} + +static void +html_editor_link_dialog_show (GtkWidget *widget) +{ + EHTMLEditor *editor; + EHTMLEditorView *view; + EHTMLEditorLinkDialog *dialog; + WebKitDOMDocument *document; + WebKitDOMDOMWindow *window; + WebKitDOMDOMSelection *dom_selection; + WebKitDOMRange *range; + WebKitDOMElement *link; + + dialog = E_HTML_EDITOR_LINK_DIALOG (widget); + editor = e_html_editor_dialog_get_editor (E_HTML_EDITOR_DIALOG (dialog)); + view = e_html_editor_get_view (editor); + + document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view)); + window = webkit_dom_document_get_default_view (document); + dom_selection = webkit_dom_dom_window_get_selection (window); + + /* Reset to default values */ + gtk_entry_set_text (GTK_ENTRY (dialog->priv->url_edit), "http://"); + gtk_entry_set_text (GTK_ENTRY (dialog->priv->label_edit), ""); + gtk_widget_set_sensitive (dialog->priv->label_edit, TRUE); + gtk_widget_set_sensitive (dialog->priv->remove_link_button, TRUE); + dialog->priv->label_autofill = TRUE; + + /* No selection at all */ + if (!dom_selection || + webkit_dom_dom_selection_get_range_count (dom_selection) < 1) { + gtk_widget_set_sensitive (dialog->priv->remove_link_button, FALSE); + goto chainup; + } + + range = webkit_dom_dom_selection_get_range_at (dom_selection, 0, NULL); + link = e_html_editor_dom_node_find_parent_element ( + webkit_dom_range_get_start_container (range, NULL), "A"); + if (!link) { + if ((webkit_dom_range_get_start_container (range, NULL) != + webkit_dom_range_get_end_container (range, NULL)) || + (webkit_dom_range_get_start_offset (range, NULL) != + webkit_dom_range_get_end_offset (range, NULL))) { + + WebKitDOMDocumentFragment *fragment; + fragment = webkit_dom_range_clone_contents (range, NULL); + link = e_html_editor_dom_node_find_child_element ( + WEBKIT_DOM_NODE (fragment), "A"); + } else { + /* get element that was clicked on */ + link = e_html_editor_view_get_element_under_mouse_click (view); + if (!WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT (link)) + link = NULL; + } + } + + if (link) { + gchar *href, *text; + + href = webkit_dom_html_anchor_element_get_href ( + WEBKIT_DOM_HTML_ANCHOR_ELEMENT (link)); + text = webkit_dom_html_element_get_inner_text ( + WEBKIT_DOM_HTML_ELEMENT (link)); + + gtk_entry_set_text ( + GTK_ENTRY (dialog->priv->url_edit), href); + gtk_entry_set_text ( + GTK_ENTRY (dialog->priv->label_edit), text); + + g_free (text); + g_free (href); + } else { + gchar *text; + + text = webkit_dom_range_get_text (range); + if (text && *text) { + gtk_entry_set_text ( + GTK_ENTRY (dialog->priv->label_edit), text); + gtk_widget_set_sensitive ( + dialog->priv->label_edit, FALSE); + gtk_widget_set_sensitive ( + dialog->priv->remove_link_button, FALSE); + } + g_free (text); + } + + chainup: + /* Chain up to parent implementation */ + GTK_WIDGET_CLASS (e_html_editor_link_dialog_parent_class)->show (widget); +} + +static void +e_html_editor_link_dialog_class_init (EHTMLEditorLinkDialogClass *class) +{ + GtkWidgetClass *widget_class; + + g_type_class_add_private (class, sizeof (EHTMLEditorLinkDialogPrivate)); + + widget_class = GTK_WIDGET_CLASS (class); + widget_class->show = html_editor_link_dialog_show; +} + +static void +e_html_editor_link_dialog_init (EHTMLEditorLinkDialog *dialog) +{ + GtkGrid *main_layout; + GtkBox *button_box; + GtkWidget *widget; + + dialog->priv = E_HTML_EDITOR_LINK_DIALOG_GET_PRIVATE (dialog); + + main_layout = e_html_editor_dialog_get_container (E_HTML_EDITOR_DIALOG (dialog)); + + widget = gtk_entry_new (); + gtk_grid_attach (main_layout, widget, 1, 0, 1, 1); + g_signal_connect_swapped ( + widget, "notify::text", + G_CALLBACK (html_editor_link_dialog_url_changed), dialog); + g_signal_connect_swapped ( + widget, "key-press-event", + G_CALLBACK (html_editor_link_dialog_entry_key_pressed), dialog); + dialog->priv->url_edit = widget; + + widget = gtk_label_new_with_mnemonic (_("_URL:")); + gtk_label_set_justify (GTK_LABEL (widget), GTK_JUSTIFY_RIGHT); + gtk_label_set_mnemonic_widget (GTK_LABEL (widget), dialog->priv->url_edit); + gtk_grid_attach (main_layout, widget, 0, 0, 1, 1); + + widget = gtk_button_new_with_mnemonic (_("_Test URL...")); + gtk_grid_attach (main_layout, widget, 2, 0, 1, 1); + g_signal_connect_swapped ( + widget, "clicked", + G_CALLBACK (html_editor_link_dialog_test_link), dialog); + dialog->priv->test_button = widget; + + widget = gtk_entry_new (); + gtk_grid_attach (main_layout, widget, 1, 1, 2, 1); + g_signal_connect_swapped ( + widget, "key-release-event", + G_CALLBACK (html_editor_link_dialog_description_changed), dialog); + g_signal_connect_swapped ( + widget, "key-press-event", + G_CALLBACK (html_editor_link_dialog_entry_key_pressed), dialog); + dialog->priv->label_edit = widget; + + widget = gtk_label_new_with_mnemonic (_("_Description:")); + gtk_label_set_justify (GTK_LABEL (widget), GTK_JUSTIFY_RIGHT); + gtk_label_set_mnemonic_widget (GTK_LABEL (widget), dialog->priv->label_edit); + gtk_grid_attach (main_layout, widget, 0, 1, 1, 1); + + button_box = e_html_editor_dialog_get_button_box (E_HTML_EDITOR_DIALOG (dialog)); + + widget = gtk_button_new_with_mnemonic (_("_Remove Link")); + g_signal_connect_swapped ( + widget, "clicked", + G_CALLBACK (html_editor_link_dialog_remove_link), dialog); + gtk_box_pack_start (button_box, widget, FALSE, FALSE, 5); + dialog->priv->remove_link_button = widget; + + widget = gtk_button_new_from_stock (GTK_STOCK_OK); + g_signal_connect_swapped ( + widget, "clicked", + G_CALLBACK (html_editor_link_dialog_ok), dialog); + gtk_box_pack_end (button_box, widget, FALSE, FALSE, 5); + dialog->priv->ok_button = widget; + + gtk_widget_show_all (GTK_WIDGET (main_layout)); +} + +GtkWidget * +e_html_editor_link_dialog_new (EHTMLEditor *editor) +{ + return GTK_WIDGET ( + g_object_new ( + E_TYPE_HTML_EDITOR_LINK_DIALOG, + "editor", editor, + "icon-name", "insert-link", + "title", N_("Link Properties"), + NULL)); +} diff --git a/e-util/e-html-editor-link-dialog.h b/e-util/e-html-editor-link-dialog.h new file mode 100644 index 0000000000..a1e426fe01 --- /dev/null +++ b/e-util/e-html-editor-link-dialog.h @@ -0,0 +1,70 @@ +/* + * e-html-editor-link-dialog.h + * + * Copyright (C) 2012 Dan Vrátil + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public 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_HTML_EDITOR_LINK_DIALOG_H +#define E_HTML_EDITOR_LINK_DIALOG_H + +#include + +/* Standard GObject macros */ +#define E_TYPE_HTML_EDITOR_LINK_DIALOG \ + (e_html_editor_link_dialog_get_type ()) +#define E_HTML_EDITOR_LINK_DIALOG(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST \ + ((obj), E_TYPE_HTML_EDITOR_LINK_DIALOG, EHTMLEditorLinkDialog)) +#define E_HTML_EDITOR_LINK_DIALOG_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_CAST \ + ((cls), E_TYPE_HTML_EDITOR_LINK_DIALOG, EHTMLEditorLinkDialogClass)) +#define E_IS_HTML_EDITOR_LINK_DIALOG(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE \ + ((obj), E_TYPE_HTML_EDITOR_LINK_DIALOG)) +#define E_IS_HTML_EDITOR_LINK_DIALOG_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_TYPE \ + ((cls), E_TYPE_HTML_EDITOR_LINK_DIALOG)) +#define E_HTML_EDITOR_LINK_DIALOG_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS \ + ((obj), E_TYPE_HTML_EDITOR_LINK_DIALOG, EHTMLEditorLinkDialogClass)) + +G_BEGIN_DECLS + +typedef struct _EHTMLEditorLinkDialog EHTMLEditorLinkDialog; +typedef struct _EHTMLEditorLinkDialogClass EHTMLEditorLinkDialogClass; +typedef struct _EHTMLEditorLinkDialogPrivate EHTMLEditorLinkDialogPrivate; + +struct _EHTMLEditorLinkDialog { + EHTMLEditorDialog parent; + EHTMLEditorLinkDialogPrivate *priv; +}; + +struct _EHTMLEditorLinkDialogClass { + EHTMLEditorDialogClass parent_class; +}; + +GType e_html_editor_link_dialog_get_type + (void) G_GNUC_CONST; +GtkWidget * e_html_editor_link_dialog_new (EHTMLEditor *editor); + +G_END_DECLS + +#endif /* E_HTML_EDITOR_LINK_DIALOG_H */ diff --git a/e-util/e-html-editor-manager.ui b/e-util/e-html-editor-manager.ui new file mode 100644 index 0000000000..75f2c34b20 --- /dev/null +++ b/e-util/e-html-editor-manager.ui @@ -0,0 +1,181 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/e-util/e-html-editor-page-dialog.c b/e-util/e-html-editor-page-dialog.c new file mode 100644 index 0000000000..53c8fce8c7 --- /dev/null +++ b/e-util/e-html-editor-page-dialog.c @@ -0,0 +1,513 @@ +/* + * e-html-editor-page-dialog.h + * + * Copyright (C) 2012 Dan Vrátil + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public 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-html-editor-page-dialog.h" + +#include + +#include "e-color-combo.h" +#include "e-misc-utils.h" + +#define E_HTML_EDITOR_PAGE_DIALOG_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE \ + ((obj), E_TYPE_HTML_EDITOR_PAGE_DIALOG, EHTMLEditorPageDialogPrivate)) + +struct _EHTMLEditorPageDialogPrivate { + GtkWidget *text_color_picker; + GtkWidget *link_color_picker; + GtkWidget *background_color_picker; + + GtkWidget *background_template_combo; + GtkWidget *background_image_filechooser; +}; + +typedef struct _Template { + const gchar *name; + const gchar *filename; + GdkRGBA text_color; + GdkRGBA link_color; + GdkRGBA background_color; + gint left_margin; +} Template; + +static const Template templates[] = { + + { + N_("None"), + NULL, + { 0.0, 0.0 , 0.0 , 1 }, + { 0.3, 0.55, 0.85, 1 }, + { 1.0, 1.0 , 1.0 , 1 }, + 0 + }, + + { + N_("Perforated Paper"), + "paper.png", + { 0.0, 0.0, 0.0, 1 }, + { 0.0, 0.2, 0.4, 1 }, + { 1.0, 1.0, 1.0, 0 }, + 30 + }, + + { + N_("Blue Ink"), + "texture.png", + { 0.1, 0.12, 0.56, 1 }, + { 0.0, 0.0, 1.0, 1 }, + { 1.0, 1.0, 1.0, 1 }, + 0 + }, + + { + N_("Paper"), + "rect.png", + { 0, 0, 0, 1 }, + { 0, 0, 1, 1 }, + { 1, 1, 1, 1 }, + 0 + }, + + { + N_("Ribbon"), + "ribbon.jpg", + { 0.0, 0.0, 0.0, 1 }, + { 0.6, 0.2, 0.4, 1 }, + { 1.0, 1.0, 1.0, 1 }, + 70 + }, + + { + N_("Midnight"), + "midnight-stars.jpg", + { 1.0, 1.0, 1.0, 1 }, + { 1.0, 0.6, 0.0, 1 }, + { 0.0, 0.0, 0.0, 1 }, + 0 + }, + + { + N_("Confidential"), + "confidential-stamp.jpg", + { 0.0, 0.0, 0.0, 1 }, + { 0.0, 0.0, 1.0, 1 }, + { 1.0, 1.0, 1.0, 1 }, + 0 + }, + + { + N_("Draft"), + "draft-stamp.jpg", + { 0.0, 0.0, 0.0, 1 }, + { 0.0, 0.0, 1.0, 1 }, + { 1.0, 1.0, 1.0, 1 }, + 0 + }, + + { + N_("Graph Paper"), + "draft-paper.png", + { 0.0 , 0.0 , 0.5, 1 }, + { 0.88, 0.13, 0.14, 1 }, + { 1.0 , 1.0 , 1.0 , 1 }, + 0 + } +}; + +G_DEFINE_TYPE ( + EHTMLEditorPageDialog, + e_html_editor_page_dialog, + E_TYPE_HTML_EDITOR_DIALOG); + +static void +html_editor_page_dialog_set_text_color (EHTMLEditorPageDialog *dialog) +{ + EHTMLEditor *editor; + EHTMLEditorView *view; + WebKitDOMDocument *document; + WebKitDOMHTMLElement *body; + GdkRGBA rgba; + gchar *color; + + editor = e_html_editor_dialog_get_editor (E_HTML_EDITOR_DIALOG (dialog)); + view = e_html_editor_get_view (editor); + document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view)); + body = webkit_dom_document_get_body (document); + + e_color_combo_get_current_color ( + E_COLOR_COMBO (dialog->priv->text_color_picker), &rgba); + + color = g_strdup_printf ("#%06x", e_rgba_to_value (&rgba)); + webkit_dom_html_body_element_set_text ( + WEBKIT_DOM_HTML_BODY_ELEMENT (body), color); + + g_free (color); +} + +static void +html_editor_page_dialog_set_link_color (EHTMLEditorPageDialog *dialog) +{ + EHTMLEditor *editor; + EHTMLEditorView *view; + WebKitDOMDocument *document; + WebKitDOMHTMLElement *body; + GdkRGBA rgba; + gchar *color; + + editor = e_html_editor_dialog_get_editor (E_HTML_EDITOR_DIALOG (dialog)); + view = e_html_editor_get_view (editor); + document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view)); + body = webkit_dom_document_get_body (document); + + e_color_combo_get_current_color ( + E_COLOR_COMBO (dialog->priv->link_color_picker), &rgba); + + color = g_strdup_printf ("#%06x", e_rgba_to_value (&rgba)); + webkit_dom_html_body_element_set_link ( + WEBKIT_DOM_HTML_BODY_ELEMENT (body), color); + + g_free (color); +} + +static void +html_editor_page_dialog_set_background_color (EHTMLEditorPageDialog *dialog) +{ + EHTMLEditor *editor; + EHTMLEditorView *view; + WebKitDOMDocument *document; + WebKitDOMHTMLElement *body; + GdkRGBA rgba; + gchar *color; + + editor = e_html_editor_dialog_get_editor (E_HTML_EDITOR_DIALOG (dialog)); + view = e_html_editor_get_view (editor); + document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view)); + body = webkit_dom_document_get_body (document); + + e_color_combo_get_current_color ( + E_COLOR_COMBO (dialog->priv->background_color_picker), &rgba); + + color = g_strdup_printf ("#%06x", e_rgba_to_value (&rgba)); + + webkit_dom_html_body_element_set_bg_color ( + WEBKIT_DOM_HTML_BODY_ELEMENT (body), color); + + g_free (color); +} + +static void +html_editor_page_dialog_set_background_from_template (EHTMLEditorPageDialog *dialog) +{ + const Template *tmplt; + + tmplt = &templates[ + gtk_combo_box_get_active ( + GTK_COMBO_BOX (dialog->priv->background_template_combo))]; + + /* Special case - 'none' template */ + if (tmplt->filename == NULL) { + gtk_file_chooser_unselect_all ( + GTK_FILE_CHOOSER (dialog->priv->background_image_filechooser)); + } else { + gchar *filename; + + e_color_combo_set_current_color ( + E_COLOR_COMBO (dialog->priv->text_color_picker), + &tmplt->text_color); + e_color_combo_set_current_color ( + E_COLOR_COMBO (dialog->priv->background_color_picker), + &tmplt->background_color); + e_color_combo_set_current_color ( + E_COLOR_COMBO (dialog->priv->link_color_picker), + &tmplt->link_color); + + filename = g_build_filename (EVOLUTION_IMAGESDIR, tmplt->filename, NULL); + + gtk_file_chooser_set_filename ( + GTK_FILE_CHOOSER (dialog->priv->background_image_filechooser), + filename); + g_free (filename); + } + +} + +static void +html_editor_page_dialog_set_background_image (EHTMLEditorPageDialog *dialog) +{ + EHTMLEditor *editor; + EHTMLEditorView *view; + WebKitDOMDocument *document; + WebKitDOMHTMLElement *body; + gchar *uri; + + editor = e_html_editor_dialog_get_editor (E_HTML_EDITOR_DIALOG (dialog)); + view = e_html_editor_get_view (editor); + document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view)); + body = webkit_dom_document_get_body (document); + + uri = gtk_file_chooser_get_uri ( + GTK_FILE_CHOOSER ( + dialog->priv->background_image_filechooser)); + + webkit_dom_html_body_element_set_background ( + WEBKIT_DOM_HTML_BODY_ELEMENT (body), uri ? uri : ""); + + g_free (uri); +} + +static void +html_editor_page_dialog_show (GtkWidget *widget) +{ + EHTMLEditor *editor; + EHTMLEditorView *view; + EHTMLEditorPageDialog *dialog; + WebKitDOMDocument *document; + WebKitDOMHTMLElement *body; + gchar *tmp; + GdkRGBA rgba; + + dialog = E_HTML_EDITOR_PAGE_DIALOG (widget); + editor = e_html_editor_dialog_get_editor (E_HTML_EDITOR_DIALOG (dialog)); + view = e_html_editor_get_view (editor); + + document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view)); + body = webkit_dom_document_get_body (document); + + tmp = webkit_dom_html_body_element_get_background ( + WEBKIT_DOM_HTML_BODY_ELEMENT (body)); + if (tmp && *tmp) { + gint ii; + gchar *fname = g_filename_from_uri (tmp, NULL, NULL); + for (ii = 0; ii < G_N_ELEMENTS (templates); ii++) { + const Template *tmplt = &templates[ii]; + + if (g_strcmp0 (tmplt->filename, fname) == 0) { + gtk_combo_box_set_active ( + GTK_COMBO_BOX (dialog->priv->background_template_combo), + ii); + break; + } + } + g_free (fname); + } else { + gtk_combo_box_set_active ( + GTK_COMBO_BOX (dialog->priv->background_template_combo), 0); + } + g_free (tmp); + + tmp = webkit_dom_html_body_element_get_text ( + WEBKIT_DOM_HTML_BODY_ELEMENT (body)); + if (!tmp || !*tmp) { + GdkColor *color; + GtkStyle *style = gtk_widget_get_style (GTK_WIDGET (view)); + color = &style->text[GTK_STATE_NORMAL]; + + rgba.alpha = 1; + rgba.red = ((gdouble) color->red) / G_MAXUINT16; + rgba.green = ((gdouble) color->green) / G_MAXUINT16; + rgba.blue = ((gdouble) color->blue) / G_MAXUINT16; + } else { + gdk_rgba_parse (&rgba, tmp); + } + g_free (tmp); + e_color_combo_set_current_color ( + E_COLOR_COMBO (dialog->priv->text_color_picker), &rgba); + + tmp = webkit_dom_html_body_element_get_link ( + WEBKIT_DOM_HTML_BODY_ELEMENT (body)); + if (!tmp || !*tmp) { + GdkColor color; + gtk_widget_style_get ( + GTK_WIDGET (view), "link-color", &color, NULL); + + rgba.alpha = 1; + rgba.red = ((gdouble) color.red) / G_MAXUINT16; + rgba.green = ((gdouble) color.green) / G_MAXUINT16; + rgba.blue = ((gdouble) color.blue) / G_MAXUINT16; + } else { + gdk_rgba_parse (&rgba, tmp); + } + g_free (tmp); + e_color_combo_set_current_color ( + E_COLOR_COMBO (dialog->priv->link_color_picker), &rgba); + + tmp = webkit_dom_html_body_element_get_bg_color ( + WEBKIT_DOM_HTML_BODY_ELEMENT (body)); + if (!tmp || !*tmp) { + GdkColor *color; + GtkStyle *style = gtk_widget_get_style (GTK_WIDGET (view)); + color = &style->base[GTK_STATE_NORMAL]; + + rgba.alpha = 1; + rgba.red = ((gdouble) color->red) / G_MAXUINT16; + rgba.green = ((gdouble) color->green) / G_MAXUINT16; + rgba.blue = ((gdouble) color->blue) / G_MAXUINT16; + + } else { + gdk_rgba_parse (&rgba, tmp); + } + g_free (tmp); + e_color_combo_set_current_color ( + E_COLOR_COMBO (dialog->priv->background_color_picker), &rgba); + + GTK_WIDGET_CLASS (e_html_editor_page_dialog_parent_class)->show (widget); +} + +static void +e_html_editor_page_dialog_class_init (EHTMLEditorPageDialogClass *class) +{ + GtkWidgetClass *widget_class; + + g_type_class_add_private (class, sizeof (EHTMLEditorPageDialogPrivate)); + + widget_class = GTK_WIDGET_CLASS (class); + widget_class->show = html_editor_page_dialog_show; +} + +static void +e_html_editor_page_dialog_init (EHTMLEditorPageDialog *dialog) +{ + GtkGrid *grid, *main_layout; + GtkWidget *widget; + gint ii; + + dialog->priv = E_HTML_EDITOR_PAGE_DIALOG_GET_PRIVATE (dialog); + + main_layout = e_html_editor_dialog_get_container (E_HTML_EDITOR_DIALOG (dialog)); + + /* == Colors == */ + widget = gtk_label_new (""); + gtk_label_set_markup (GTK_LABEL (widget), _("Colors")); + gtk_misc_set_alignment (GTK_MISC (widget), 0, 0.5); + gtk_grid_attach (main_layout, widget, 0, 0, 1, 1); + + grid = GTK_GRID (gtk_grid_new ()); + gtk_grid_set_row_spacing (grid, 5); + gtk_grid_set_column_spacing (grid, 5); + gtk_grid_attach (main_layout, GTK_WIDGET (grid), 0, 1, 1, 1); + gtk_widget_set_margin_left (GTK_WIDGET (grid), 10); + + /* Text */ + widget = e_color_combo_new (); + gtk_widget_set_hexpand (widget, TRUE); + g_signal_connect_swapped ( + widget, "notify::current-color", + G_CALLBACK (html_editor_page_dialog_set_text_color), dialog); + gtk_grid_attach (grid, widget, 1, 0, 1, 1); + dialog->priv->text_color_picker = widget; + + widget = gtk_label_new_with_mnemonic (_("_Text:")); + gtk_label_set_justify (GTK_LABEL (widget), GTK_JUSTIFY_RIGHT); + gtk_label_set_mnemonic_widget ( + GTK_LABEL (widget), dialog->priv->text_color_picker); + gtk_grid_attach (grid, widget, 0, 0, 1, 1); + + /* Link */ + widget = e_color_combo_new (); + gtk_widget_set_hexpand (widget, TRUE); + g_signal_connect_swapped ( + widget, "notify::current-color", + G_CALLBACK (html_editor_page_dialog_set_link_color), dialog); + gtk_grid_attach (grid, widget, 1, 1, 1, 1); + dialog->priv->link_color_picker = widget; + + widget = gtk_label_new_with_mnemonic (_("_Link:")); + gtk_label_set_justify (GTK_LABEL (widget), GTK_JUSTIFY_RIGHT); + gtk_label_set_mnemonic_widget ( + GTK_LABEL (widget), dialog->priv->link_color_picker); + gtk_grid_attach (grid, widget, 0, 1, 1, 1); + + /* Background */ + widget = e_color_combo_new (); + gtk_widget_set_hexpand (widget, TRUE); + g_signal_connect_swapped ( + widget, "notify::current-color", + G_CALLBACK (html_editor_page_dialog_set_background_color), dialog); + gtk_grid_attach (grid, widget, 1, 2, 1, 1); + dialog->priv->background_color_picker = widget; + + widget = gtk_label_new_with_mnemonic (_("_Background:")); + gtk_label_set_justify (GTK_LABEL (widget), GTK_JUSTIFY_RIGHT); + gtk_label_set_mnemonic_widget ( + GTK_LABEL (widget), dialog->priv->background_color_picker); + gtk_grid_attach (grid, widget, 0, 2, 1, 1); + + /* == Background Image == */ + widget = gtk_label_new (""); + gtk_label_set_markup (GTK_LABEL (widget), _("Background Image")); + gtk_misc_set_alignment (GTK_MISC (widget), 0, 0.5); + gtk_grid_attach (main_layout, widget, 0, 2, 1, 1); + + grid = GTK_GRID (gtk_grid_new ()); + gtk_grid_set_row_spacing (grid, 5); + gtk_grid_set_column_spacing (grid, 5); + gtk_grid_attach (main_layout, GTK_WIDGET (grid), 0, 3, 1, 1); + gtk_widget_set_margin_left (GTK_WIDGET (grid), 10); + + /* Template */ + widget = gtk_combo_box_text_new (); + for (ii = 0; ii < G_N_ELEMENTS (templates); ii++) { + gtk_combo_box_text_append_text ( + GTK_COMBO_BOX_TEXT (widget), templates[ii].name); + } + g_signal_connect_swapped ( + widget, "changed", + G_CALLBACK (html_editor_page_dialog_set_background_from_template), dialog); + gtk_grid_attach (grid, widget, 1, 0, 1, 1); + dialog->priv->background_template_combo = widget; + + widget = gtk_label_new_with_mnemonic (_("_Template:")); + gtk_label_set_justify (GTK_LABEL (widget), GTK_JUSTIFY_RIGHT); + gtk_label_set_mnemonic_widget ( + GTK_LABEL (widget), dialog->priv->background_template_combo); + gtk_grid_attach (grid, widget, 0, 0, 1, 1); + + /* Custom image */ + widget = gtk_file_chooser_button_new ( + _("Selection a file"), GTK_FILE_CHOOSER_ACTION_OPEN); + g_signal_connect_swapped ( + widget, "selection-changed", + G_CALLBACK (html_editor_page_dialog_set_background_image), dialog); + gtk_grid_attach (grid, widget, 1, 1, 1, 1); + dialog->priv->background_image_filechooser = widget; + + widget = gtk_label_new_with_mnemonic (_("_Custom:")); + gtk_label_set_justify (GTK_LABEL (widget), GTK_JUSTIFY_RIGHT); + gtk_label_set_mnemonic_widget ( + GTK_LABEL (widget), dialog->priv->background_image_filechooser); + gtk_grid_attach (grid, widget, 0, 1, 1, 1); + + gtk_widget_show_all (GTK_WIDGET (main_layout)); +} + +GtkWidget * +e_html_editor_page_dialog_new (EHTMLEditor *editor) +{ + return GTK_WIDGET ( + g_object_new ( + E_TYPE_HTML_EDITOR_PAGE_DIALOG, + "editor", editor, + "title", N_("Page Properties"), + NULL)); +} diff --git a/e-util/e-html-editor-page-dialog.h b/e-util/e-html-editor-page-dialog.h new file mode 100644 index 0000000000..5678efc4b6 --- /dev/null +++ b/e-util/e-html-editor-page-dialog.h @@ -0,0 +1,70 @@ +/* + * e-html-editor-page-dialog.h + * + * Copyright (C) 2012 Dan Vrátil + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public 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_HTML_EDITOR_PAGE_DIALOG_H +#define E_HTML_EDITOR_PAGE_DIALOG_H + +#include + +/* Standard GObject macros */ +#define E_TYPE_HTML_EDITOR_PAGE_DIALOG \ + (e_html_editor_page_dialog_get_type ()) +#define E_HTML_EDITOR_PAGE_DIALOG(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST \ + ((obj), E_TYPE_HTML_EDITOR_PAGE_DIALOG, EHTMLEditorPageDialog)) +#define E_HTML_EDITOR_PAGE_DIALOG_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_CAST \ + ((cls), E_TYPE_HTML_EDITOR_PAGE_DIALOG, EHTMLEditorPageDialogClass)) +#define E_IS_HTML_EDITOR_PAGE_DIALOG(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE \ + ((obj), E_TYPE_HTML_EDITOR_PAGE_DIALOG)) +#define E_IS_HTML_EDITOR_PAGE_DIALOG_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_TYPE \ + ((cls), E_TYPE_HTML_EDITOR_PAGE_DIALOG)) +#define E_HTML_EDITOR_PAGE_DIALOG_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS \ + ((obj), E_TYPE_HTML_EDITOR_PAGE_DIALOG, EHTMLEditorPageDialogClass)) + +G_BEGIN_DECLS + +typedef struct _EHTMLEditorPageDialog EHTMLEditorPageDialog; +typedef struct _EHTMLEditorPageDialogClass EHTMLEditorPageDialogClass; +typedef struct _EHTMLEditorPageDialogPrivate EHTMLEditorPageDialogPrivate; + +struct _EHTMLEditorPageDialog { + EHTMLEditorDialog parent; + EHTMLEditorPageDialogPrivate *priv; +}; + +struct _EHTMLEditorPageDialogClass { + EHTMLEditorDialogClass parent_class; +}; + +GType e_html_editor_page_dialog_get_type + (void) G_GNUC_CONST; +GtkWidget * e_html_editor_page_dialog_new (EHTMLEditor *editor); + +G_END_DECLS + +#endif /* E_HTML_EDITOR_PAGE_DIALOG_H */ diff --git a/e-util/e-html-editor-paragraph-dialog.c b/e-util/e-html-editor-paragraph-dialog.c new file mode 100644 index 0000000000..f0fce973a6 --- /dev/null +++ b/e-util/e-html-editor-paragraph-dialog.c @@ -0,0 +1,154 @@ +/* + * e-html-editor-paragraph-dialog.c + * + * Copyright (C) 2012 Dan Vrátil + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public 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-html-editor-paragraph-dialog.h" + +#include + +#include "e-action-combo-box.h" + +#define E_HTML_EDITOR_PARAGRAPH_DIALOG_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE \ + ((obj), E_TYPE_HTML_EDITOR_PARAGRAPH_DIALOG, EHTMLEditorParagraphDialogPrivate)) + +G_DEFINE_TYPE ( + EHTMLEditorParagraphDialog, + e_html_editor_paragraph_dialog, + E_TYPE_HTML_EDITOR_DIALOG); + +struct _EHTMLEditorParagraphDialogPrivate { + GtkWidget *style_combo; + + GtkWidget *left_button; + GtkWidget *center_button; + GtkWidget *right_button; +}; + +static void +html_editor_paragraph_dialog_constructed (GObject *object) +{ + GtkGrid *main_layout, *grid; + GtkWidget *widget; + EHTMLEditor *editor; + EHTMLEditorParagraphDialog *dialog; + + dialog = E_HTML_EDITOR_PARAGRAPH_DIALOG (object); + editor = e_html_editor_dialog_get_editor (E_HTML_EDITOR_DIALOG (dialog)); + + main_layout = e_html_editor_dialog_get_container (E_HTML_EDITOR_DIALOG (dialog)); + + /* == General == */ + widget = gtk_label_new (""); + gtk_label_set_markup (GTK_LABEL (widget), _("General")); + gtk_misc_set_alignment (GTK_MISC (widget), 0, 0.5); + gtk_grid_attach (main_layout, widget, 0, 0, 1, 1); + + grid = GTK_GRID (gtk_grid_new ()); + gtk_grid_set_row_spacing (grid, 5); + gtk_grid_set_column_spacing (grid, 5); + gtk_grid_attach (main_layout, GTK_WIDGET (grid), 0, 1, 1, 1); + gtk_widget_set_margin_left (GTK_WIDGET (grid), 10); + + /* Style */ + widget = e_action_combo_box_new_with_action ( + GTK_RADIO_ACTION (e_html_editor_get_action (editor, "style-normal"))); + gtk_widget_set_hexpand (widget, TRUE); + gtk_grid_attach (grid, widget, 1, 0, 1, 1); + dialog->priv->style_combo = widget; + + widget = gtk_label_new_with_mnemonic (_("_Style:")); + gtk_label_set_justify (GTK_LABEL (widget), GTK_JUSTIFY_RIGHT); + gtk_label_set_mnemonic_widget (GTK_LABEL (widget), dialog->priv->style_combo); + gtk_grid_attach (grid, widget, 0, 0, 1, 1); + + /* == Alignment == */ + widget = gtk_label_new (""); + gtk_label_set_markup (GTK_LABEL (widget), _("Alignment")); + gtk_misc_set_alignment (GTK_MISC (widget), 0, 0.5); + gtk_grid_attach (main_layout, widget, 0, 2, 1, 1); + + grid = GTK_GRID (gtk_grid_new ()); + gtk_grid_set_row_spacing (grid, 5); + gtk_grid_set_column_spacing (grid, 5); + gtk_grid_attach (main_layout, GTK_WIDGET (grid), 0, 3, 1, 1); + gtk_widget_set_margin_left (GTK_WIDGET (grid), 10); + + /* Left */ + widget = gtk_toggle_button_new_with_label (GTK_STOCK_JUSTIFY_LEFT); + gtk_button_set_use_stock (GTK_BUTTON (widget), TRUE); + gtk_activatable_set_related_action ( + GTK_ACTIVATABLE (widget), + e_html_editor_get_action (editor, "justify-left")); + gtk_grid_attach (grid, widget, 0, 0, 1, 1); + dialog->priv->left_button = widget; + + /* Center */ + widget = gtk_toggle_button_new_with_label (GTK_STOCK_JUSTIFY_CENTER); + gtk_button_set_use_stock (GTK_BUTTON (widget), TRUE); + gtk_grid_attach (grid, widget, 1, 0, 1, 1); + gtk_activatable_set_related_action ( + GTK_ACTIVATABLE (widget), + e_html_editor_get_action (editor, "justify-center")); + dialog->priv->center_button = widget; + + /* Right */ + widget = gtk_toggle_button_new_with_label (GTK_STOCK_JUSTIFY_RIGHT); + gtk_button_set_use_stock (GTK_BUTTON (widget), TRUE); + gtk_grid_attach (grid, widget, 2, 0, 1, 1); + gtk_activatable_set_related_action ( + GTK_ACTIVATABLE (widget), + e_html_editor_get_action (editor, "justify-right")); + dialog->priv->right_button = widget; + + gtk_widget_show_all (GTK_WIDGET (main_layout)); +} + +static void +e_html_editor_paragraph_dialog_class_init (EHTMLEditorParagraphDialogClass *class) +{ + GObjectClass *object_class; + + g_type_class_add_private ( + class, sizeof (EHTMLEditorParagraphDialogPrivate)); + + object_class = G_OBJECT_CLASS (class); + object_class->constructed = html_editor_paragraph_dialog_constructed; +} + +static void +e_html_editor_paragraph_dialog_init (EHTMLEditorParagraphDialog *dialog) +{ + dialog->priv = E_HTML_EDITOR_PARAGRAPH_DIALOG_GET_PRIVATE (dialog); +} + +GtkWidget * +e_html_editor_paragraph_dialog_new (EHTMLEditor *editor) +{ + return GTK_WIDGET ( + g_object_new ( + E_TYPE_HTML_EDITOR_PARAGRAPH_DIALOG, + "editor", editor, + "title", N_("Paragraph Properties"), + NULL)); +} diff --git a/e-util/e-html-editor-paragraph-dialog.h b/e-util/e-html-editor-paragraph-dialog.h new file mode 100644 index 0000000000..17855bd3c8 --- /dev/null +++ b/e-util/e-html-editor-paragraph-dialog.h @@ -0,0 +1,71 @@ +/* + * e-html-editor-paragraph-dialog.h + * + * Copyright (C) 2012 Dan Vrátil + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public 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_HTML_EDITOR_PARAGRAPH_DIALOG_H +#define E_HTML_EDITOR_PARAGRAPH_DIALOG_H + +#include + +/* Standard GObject macros */ +#define E_TYPE_HTML_EDITOR_PARAGRAPH_DIALOG \ + (e_html_editor_paragraph_dialog_get_type ()) +#define E_HTML_EDITOR_PARAGRAPH_DIALOG(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST \ + ((obj), E_TYPE_HTML_EDITOR_PARAGRAPH_DIALOG, EHTMLEditorParagraphDialog)) +#define E_HTML_EDITOR_PARAGRAPH_DIALOG_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_CAST \ + ((cls), E_TYPE_HTML_EDITOR_PARAGRAPH_DIALOG, EHTMLEditorParagraphDialogClass)) +#define E_IS_HTML_EDITOR_PARAGRAPH_DIALOG(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE \ + ((obj), E_TYPE_HTML_EDITOR_PARAGRAPH_DIALOG)) +#define E_IS_HTML_EDITOR_PARAGRAPH_DIALOG_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_TYPE \ + ((cls), E_TYPE_HTML_EDITOR_PARAGRAPH_DIALOG)) +#define E_HTML_EDITOR_PARAGRAPH_DIALOG_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS \ + ((obj), E_TYPE_HTML_EDITOR_PARAGRAPH_DIALOG, EHTMLEditorParagraphDialogClass)) + +G_BEGIN_DECLS + +typedef struct _EHTMLEditorParagraphDialog EHTMLEditorParagraphDialog; +typedef struct _EHTMLEditorParagraphDialogClass EHTMLEditorParagraphDialogClass; +typedef struct _EHTMLEditorParagraphDialogPrivate EHTMLEditorParagraphDialogPrivate; + +struct _EHTMLEditorParagraphDialog { + EHTMLEditorDialog parent; + EHTMLEditorParagraphDialogPrivate *priv; +}; + +struct _EHTMLEditorParagraphDialogClass { + EHTMLEditorDialogClass parent_class; +}; + +GType e_html_editor_paragraph_dialog_get_type + (void) G_GNUC_CONST; +GtkWidget * e_html_editor_paragraph_dialog_new + (EHTMLEditor *editor); + +G_END_DECLS + +#endif /* E_HTML_EDITOR_PARAGRAPH_DIALOG_H */ diff --git a/e-util/e-html-editor-private.h b/e-util/e-html-editor-private.h new file mode 100644 index 0000000000..dc4658bd57 --- /dev/null +++ b/e-util/e-html-editor-private.h @@ -0,0 +1,103 @@ +/* + * e-html-editor-private.h + * + * Copyright (C) 2012 Dan Vrátil + * + * 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. + */ + +#ifndef E_HTML_EDITOR_PRIVATE_H +#define E_HTML_EDITOR_PRIVATE_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_XFREE +#include +#endif + +#define ACTION(name) (E_HTML_EDITOR_ACTION_##name (editor)) +#define WIDGET(name) (E_HTML_EDITOR_WIDGETS_##name (editor)) + +G_BEGIN_DECLS + +struct _EHTMLEditorPrivate { + GtkUIManager *manager; + GtkActionGroup *core_actions; + GtkActionGroup *html_actions; + GtkActionGroup *context_actions; + GtkActionGroup *html_context_actions; + GtkActionGroup *language_actions; + GtkActionGroup *spell_check_actions; + GtkActionGroup *suggestion_actions; + + GtkWidget *main_menu; + GtkWidget *main_toolbar; + GtkWidget *edit_toolbar; + GtkWidget *html_toolbar; + GtkWidget *activity_bar; + GtkWidget *alert_bar; + GtkWidget *edit_area; + + GtkWidget *find_dialog; + GtkWidget *replace_dialog; + GtkWidget *link_dialog; + GtkWidget *hrule_dialog; + GtkWidget *table_dialog; + GtkWidget *page_dialog; + GtkWidget *image_dialog; + GtkWidget *text_dialog; + GtkWidget *paragraph_dialog; + GtkWidget *cell_dialog; + GtkWidget *spell_check_dialog; + + GtkWidget *color_combo_box; + GtkWidget *mode_combo_box; + GtkWidget *size_combo_box; + GtkWidget *style_combo_box; + GtkWidget *scrolled_window; + + EHTMLEditorView *html_editor_view; + EHTMLEditorSelection *selection; + + gchar *filename; + + guint spell_suggestions_merge_id; + + WebKitDOMNode *image; + WebKitDOMNode *table_cell; + + gint editor_layout_row; +}; + +void editor_actions_init (EHTMLEditor *editor); + +G_END_DECLS + +#endif /* E_HTML_EDITOR_PRIVATE_H */ diff --git a/e-util/e-html-editor-replace-dialog.c b/e-util/e-html-editor-replace-dialog.c new file mode 100644 index 0000000000..7addcdfdad --- /dev/null +++ b/e-util/e-html-editor-replace-dialog.c @@ -0,0 +1,288 @@ +/* + * e-html-editor-replace-dialog.h + * + * Copyright (C) 2012 Dan Vrátil + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public 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-html-editor-replace-dialog.h" + +#include + +#define E_HTML_EDITOR_REPLACE_DIALOG_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE \ + ((obj), E_TYPE_HTML_EDITOR_REPLACE_DIALOG, EHTMLEditorReplaceDialogPrivate)) + +G_DEFINE_TYPE ( + EHTMLEditorReplaceDialog, + e_html_editor_replace_dialog, + E_TYPE_HTML_EDITOR_DIALOG); + +struct _EHTMLEditorReplaceDialogPrivate { + GtkWidget *search_entry; + GtkWidget *replace_entry; + + GtkWidget *case_sensitive; + GtkWidget *backwards; + GtkWidget *wrap; + + GtkWidget *result_label; + + GtkWidget *skip_button; + GtkWidget *replace_button; + GtkWidget *replace_all_button; + + EHTMLEditor *editor; +}; + +enum { + PROP_0, + PROP_EDITOR +}; + +static gboolean +jump (EHTMLEditorReplaceDialog *dialog) +{ + EHTMLEditor *editor; + WebKitWebView *webview; + gboolean found; + + editor = e_html_editor_dialog_get_editor (E_HTML_EDITOR_DIALOG (dialog)); + webview = WEBKIT_WEB_VIEW ( + e_html_editor_get_view (editor)); + + found = webkit_web_view_search_text ( + webview, + gtk_entry_get_text (GTK_ENTRY (dialog->priv->search_entry)), + gtk_toggle_button_get_active ( + GTK_TOGGLE_BUTTON (dialog->priv->case_sensitive)), + !gtk_toggle_button_get_active ( + GTK_TOGGLE_BUTTON (dialog->priv->backwards)), + gtk_toggle_button_get_active ( + GTK_TOGGLE_BUTTON (dialog->priv->wrap))); + + return found; +} + +static void +html_editor_replace_dialog_skip_cb (EHTMLEditorReplaceDialog *dialog) +{ + if (!jump (dialog)) { + gtk_label_set_label ( + GTK_LABEL (dialog->priv->result_label), + N_("No match found")); + gtk_widget_show (dialog->priv->result_label); + } else { + gtk_widget_hide (dialog->priv->result_label); + } +} + +static void +html_editor_replace_dialog_replace_cb (EHTMLEditorReplaceDialog *dialog) +{ + EHTMLEditor *editor; + EHTMLEditorView *view; + EHTMLEditorSelection *selection; + + /* Jump to next matching word */ + if (!jump (dialog)) { + gtk_label_set_label ( + GTK_LABEL (dialog->priv->result_label), + N_("No match found")); + gtk_widget_show (dialog->priv->result_label); + return; + } else { + gtk_widget_hide (dialog->priv->result_label); + } + + editor = e_html_editor_dialog_get_editor (E_HTML_EDITOR_DIALOG (dialog)); + view = e_html_editor_get_view (editor); + selection = e_html_editor_view_get_selection (view); + + e_html_editor_selection_replace ( + selection, + gtk_entry_get_text (GTK_ENTRY (dialog->priv->replace_entry))); +} + +static void +html_editor_replace_dialog_replace_all_cb (EHTMLEditorReplaceDialog *dialog) +{ + gint i = 0; + gchar *result; + EHTMLEditor *editor; + EHTMLEditorView *view; + EHTMLEditorSelection *selection; + const gchar *replacement; + + editor = e_html_editor_dialog_get_editor (E_HTML_EDITOR_DIALOG (dialog)); + view = e_html_editor_get_view (editor); + selection = e_html_editor_view_get_selection (view); + replacement = gtk_entry_get_text (GTK_ENTRY (dialog->priv->replace_entry)); + + while (jump (dialog)) { + e_html_editor_selection_replace (selection, replacement); + i++; + + /* Jump behind the word */ + e_html_editor_selection_move ( + selection, TRUE, E_HTML_EDITOR_SELECTION_GRANULARITY_WORD); + } + + result = g_strdup_printf (_("%d occurences replaced"), i); + gtk_label_set_label (GTK_LABEL (dialog->priv->result_label), result); + gtk_widget_show (dialog->priv->result_label); + g_free (result); +} + +static void +html_editor_replace_dialog_entry_changed (EHTMLEditorReplaceDialog *dialog) +{ + gboolean ready; + ready = ((gtk_entry_get_text_length ( + GTK_ENTRY (dialog->priv->search_entry)) != 0) && + (gtk_entry_get_text_length ( + GTK_ENTRY (dialog->priv->replace_entry)) != 0)); + + gtk_widget_set_sensitive (dialog->priv->skip_button, ready); + gtk_widget_set_sensitive (dialog->priv->replace_button, ready); + gtk_widget_set_sensitive (dialog->priv->replace_all_button, ready); +} + +static void +html_editor_replace_dialog_show (GtkWidget *widget) +{ + EHTMLEditorReplaceDialog *dialog = E_HTML_EDITOR_REPLACE_DIALOG (widget); + + gtk_widget_grab_focus (dialog->priv->search_entry); + gtk_widget_hide (dialog->priv->result_label); + + /* Chain up to parent implementation */ + GTK_WIDGET_CLASS (e_html_editor_replace_dialog_parent_class)->show (widget); +} + +static void +e_html_editor_replace_dialog_class_init (EHTMLEditorReplaceDialogClass *class) +{ + GtkWidgetClass *widget_class; + + g_type_class_add_private (class, sizeof (EHTMLEditorReplaceDialogPrivate)); + + widget_class = GTK_WIDGET_CLASS (class); + widget_class->show = html_editor_replace_dialog_show; +} + +static void +e_html_editor_replace_dialog_init (EHTMLEditorReplaceDialog *dialog) +{ + GtkGrid *main_layout; + GtkWidget *widget, *layout; + GtkBox *button_box; + + dialog->priv = E_HTML_EDITOR_REPLACE_DIALOG_GET_PRIVATE (dialog); + + main_layout = e_html_editor_dialog_get_container (E_HTML_EDITOR_DIALOG (dialog)); + + widget = gtk_entry_new (); + gtk_grid_attach (main_layout, widget, 1, 0, 2, 1); + dialog->priv->search_entry = widget; + g_signal_connect_swapped ( + widget, "notify::text-length", + G_CALLBACK (html_editor_replace_dialog_entry_changed), dialog); + + widget = gtk_label_new_with_mnemonic (_("R_eplace:")); + gtk_label_set_mnemonic_widget (GTK_LABEL (widget), dialog->priv->search_entry); + gtk_label_set_justify (GTK_LABEL (widget), GTK_JUSTIFY_RIGHT); + gtk_grid_attach (main_layout, widget, 0, 0, 1, 1); + + widget = gtk_entry_new (); + gtk_grid_attach (main_layout, widget, 1, 1, 2, 1); + dialog->priv->replace_entry = widget; + g_signal_connect_swapped ( + widget, "notify::text-length", + G_CALLBACK (html_editor_replace_dialog_entry_changed), dialog); + + widget = gtk_label_new_with_mnemonic (_("_With:")); + gtk_label_set_mnemonic_widget (GTK_LABEL (widget), dialog->priv->replace_entry); + gtk_label_set_justify (GTK_LABEL (widget), GTK_JUSTIFY_RIGHT); + gtk_grid_attach (main_layout, widget, 0, 1, 1, 1); + + layout = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 5); + gtk_grid_attach (main_layout, layout, 1, 2, 2, 1); + + widget = gtk_check_button_new_with_mnemonic (_("Search _backwards")); + gtk_box_pack_start (GTK_BOX (layout), widget, FALSE, FALSE, 0); + dialog->priv->backwards = widget; + + widget = gtk_check_button_new_with_mnemonic (_("_Case sensitive")); + gtk_box_pack_start (GTK_BOX (layout), widget, FALSE, FALSE, 0); + dialog->priv->case_sensitive = widget; + + widget = gtk_check_button_new_with_mnemonic (_("Wra_p search")); + gtk_box_pack_start (GTK_BOX (layout), widget, FALSE, FALSE, 0); + dialog->priv->wrap = widget; + + widget = gtk_label_new (""); + gtk_grid_attach (main_layout, widget, 0, 3, 2, 1); + dialog->priv->result_label = widget; + + button_box = e_html_editor_dialog_get_button_box (E_HTML_EDITOR_DIALOG (dialog)); + + widget = gtk_button_new_with_mnemonic (_("_Skip")); + gtk_box_pack_start (button_box, widget, FALSE, FALSE, 5); + gtk_widget_set_sensitive (widget, FALSE); + dialog->priv->skip_button = widget; + g_signal_connect_swapped ( + widget, "clicked", + G_CALLBACK (html_editor_replace_dialog_skip_cb), dialog); + + widget = gtk_button_new_with_mnemonic (_("_Replace")); + gtk_box_pack_start (button_box, widget, FALSE, FALSE, 5); + gtk_widget_set_sensitive (widget, FALSE); + dialog->priv->replace_button = widget; + g_signal_connect_swapped ( + widget, "clicked", + G_CALLBACK (html_editor_replace_dialog_replace_cb), dialog); + + widget = gtk_button_new_with_mnemonic (_("Replace _All")); + gtk_box_pack_start (button_box, widget, FALSE, FALSE, 5); + gtk_widget_set_sensitive (widget, FALSE); + dialog->priv->replace_all_button = widget; + g_signal_connect_swapped ( + widget, "clicked", + G_CALLBACK (html_editor_replace_dialog_replace_all_cb), dialog); + + gtk_widget_show_all (GTK_WIDGET (main_layout)); +} + +GtkWidget * +e_html_editor_replace_dialog_new (EHTMLEditor *editor) +{ + return GTK_WIDGET ( + g_object_new ( + E_TYPE_HTML_EDITOR_REPLACE_DIALOG, + "editor", editor, + "icon-name", GTK_STOCK_FIND_AND_REPLACE, + "resizable", FALSE, + "title", N_("Replace"), + "transient-for", gtk_widget_get_toplevel (GTK_WIDGET (editor)), + "type", GTK_WINDOW_TOPLEVEL, + "window-position", GTK_WIN_POS_CENTER_ON_PARENT, + NULL)); +} diff --git a/e-util/e-html-editor-replace-dialog.h b/e-util/e-html-editor-replace-dialog.h new file mode 100644 index 0000000000..f253e428aa --- /dev/null +++ b/e-util/e-html-editor-replace-dialog.h @@ -0,0 +1,71 @@ +/* + * e-html-editor-replace-dialog.h + * + * Copyright (C) 2012 Dan Vrátil + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public 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_HTML_EDITOR_REPLACE_DIALOG_H +#define E_HTML_EDITOR_REPLACE_DIALOG_H + +#include + +/* Standard GObject macros */ +#define E_TYPE_HTML_EDITOR_REPLACE_DIALOG \ + (e_html_editor_replace_dialog_get_type ()) +#define E_HTML_EDITOR_REPLACE_DIALOG(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST \ + ((obj), E_TYPE_HTML_EDITOR_REPLACE_DIALOG, EHTMLEditorReplaceDialog)) +#define E_HTML_EDITOR_REPLACE_DIALOG_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_CAST \ + ((cls), E_TYPE_HTML_EDITOR_REPLACE_DIALOG, EHTMLEditorReplaceDialogClass)) +#define E_IS_HTML_EDITOR_REPLACE_DIALOG(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE \ + ((obj), E_TYPE_HTML_EDITOR_REPLACE_DIALOG)) +#define E_IS_HTML_EDITOR_REPLACE_DIALOG_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_TYPE \ + ((cls), E_TYPE_HTML_EDITOR_REPLACE_DIALOG)) +#define E_HTML_EDITOR_REPLACE_DIALOG_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS \ + ((obj), E_TYPE_HTML_EDITOR_REPLACE_DIALOG, EHTMLEditorReplaceDialogClass)) + +G_BEGIN_DECLS + +typedef struct _EHTMLEditorReplaceDialog EHTMLEditorReplaceDialog; +typedef struct _EHTMLEditorReplaceDialogClass EHTMLEditorReplaceDialogClass; +typedef struct _EHTMLEditorReplaceDialogPrivate EHTMLEditorReplaceDialogPrivate; + +struct _EHTMLEditorReplaceDialog { + EHTMLEditorDialog parent; + EHTMLEditorReplaceDialogPrivate *priv; +}; + +struct _EHTMLEditorReplaceDialogClass { + EHTMLEditorDialogClass parent_class; +}; + +GType e_html_editor_replace_dialog_get_type + (void) G_GNUC_CONST; +GtkWidget * e_html_editor_replace_dialog_new (EHTMLEditor *editor); + +G_END_DECLS + +#endif /* E_HTML_EDITOR_REPLACE_DIALOG_H */ + diff --git a/e-util/e-html-editor-selection.c b/e-util/e-html-editor-selection.c new file mode 100644 index 0000000000..c109063f41 --- /dev/null +++ b/e-util/e-html-editor-selection.c @@ -0,0 +1,5576 @@ +/* + * e-html-editor-selection.c + * + * Copyright (C) 2012 Dan Vrátil + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public 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-html-editor-selection.h" +#include "e-html-editor-view.h" +#include "e-html-editor.h" +#include "e-html-editor-utils.h" + +#include + +#include +#include +#include +#include +#include + +#define E_HTML_EDITOR_SELECTION_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE \ + ((obj), E_TYPE_HTML_EDITOR_SELECTION, EHTMLEditorSelectionPrivate)) + +#define UNICODE_ZERO_WIDTH_SPACE "\xe2\x80\x8b" +#define UNICODE_NBSP "\xc2\xa0" + +#define SPACES_PER_INDENTATION 4 +#define SPACES_PER_LIST_LEVEL 8 + +/** + * EHTMLEditorSelection + * + * The #EHTMLEditorSelection object represents current position of the cursor + * with the editor or current text selection within the editor. To obtain + * valid #EHTMLEditorSelection, call e_html_editor_view_get_selection(). + */ + +struct _EHTMLEditorSelectionPrivate { + + GWeakRef html_editor_view; + gulong selection_changed_handler_id; + + gchar *text; + + gboolean is_bold; + gboolean is_italic; + gboolean is_underline; + gboolean is_monospaced; + gboolean is_strikethrough; + + gchar *background_color; + gchar *font_color; + gchar *font_family; + + gulong selection_offset; + + gint word_wrap_length; + guint font_size; + + EHTMLEditorSelectionAlignment alignment; +}; + +enum { + PROP_0, + PROP_ALIGNMENT, + PROP_BACKGROUND_COLOR, + PROP_BLOCK_FORMAT, + PROP_BOLD, + PROP_HTML_EDITOR_VIEW, + PROP_FONT_COLOR, + PROP_FONT_NAME, + PROP_FONT_SIZE, + PROP_INDENTED, + PROP_ITALIC, + PROP_MONOSPACED, + PROP_STRIKETHROUGH, + PROP_SUBSCRIPT, + PROP_SUPERSCRIPT, + PROP_TEXT, + PROP_UNDERLINE +}; + +static const GdkRGBA black = { 0 }; + +G_DEFINE_TYPE ( + EHTMLEditorSelection, + e_html_editor_selection, + G_TYPE_OBJECT +); + +static WebKitDOMRange * +html_editor_selection_get_current_range (EHTMLEditorSelection *selection) +{ + EHTMLEditorView *view; + WebKitWebView *web_view; + WebKitDOMDocument *document; + WebKitDOMDOMWindow *window; + WebKitDOMDOMSelection *dom_selection; + WebKitDOMRange *range = NULL; + + view = e_html_editor_selection_ref_html_editor_view (selection); + g_return_val_if_fail (view != NULL, NULL); + + web_view = WEBKIT_WEB_VIEW (view); + + document = webkit_web_view_get_dom_document (web_view); + window = webkit_dom_document_get_default_view (document); + if (!window) + goto exit; + + dom_selection = webkit_dom_dom_window_get_selection (window); + if (!WEBKIT_DOM_IS_DOM_SELECTION (dom_selection)) + goto exit; + + if (webkit_dom_dom_selection_get_range_count (dom_selection) < 1) + goto exit; + + range = webkit_dom_dom_selection_get_range_at (dom_selection, 0, NULL); + + exit: + g_object_unref (view); + + return range; +} + +static gboolean +get_has_style (EHTMLEditorSelection *selection, + const gchar *style_tag) +{ + WebKitDOMNode *node; + WebKitDOMElement *element; + WebKitDOMRange *range; + gboolean result; + gint tag_len; + + range = html_editor_selection_get_current_range (selection); + if (!range) + return FALSE; + + node = webkit_dom_range_get_start_container (range, NULL); + if (WEBKIT_DOM_IS_ELEMENT (node)) + element = WEBKIT_DOM_ELEMENT (node); + else + element = webkit_dom_node_get_parent_element (node); + + tag_len = strlen (style_tag); + result = FALSE; + while (!result && element) { + gchar *element_tag; + gboolean accept_citation = FALSE; + + element_tag = webkit_dom_element_get_tag_name (element); + + if (g_ascii_strncasecmp (style_tag, "citation", 8) == 0) { + accept_citation = TRUE; + result = ((strlen (element_tag) == 10 /* strlen ("blockquote") */) && + (g_ascii_strncasecmp (element_tag, "blockquote", 10) == 0)); + if (element_has_class (element, "-x-evo-indented")) + result = FALSE; + } else { + result = ((tag_len == strlen (element_tag)) && + (g_ascii_strncasecmp (element_tag, style_tag, tag_len) == 0)); + } + + /* Special case:
marks quotation, while + * just
is used for indentation. If the
+ * has type=cite, then ignore it unless style_tag is "citation" */ + if (result && g_ascii_strncasecmp (element_tag, "blockquote", 10) == 0) { + if (webkit_dom_element_has_attribute (element, "type")) { + gchar *type; + type = webkit_dom_element_get_attribute (element, "type"); + if (!accept_citation && (g_ascii_strncasecmp (type, "cite", 4) == 0)) { + result = FALSE; + } + g_free (type); + } else { + if (accept_citation) + result = FALSE; + } + } + + g_free (element_tag); + + if (result) + break; + + element = webkit_dom_node_get_parent_element ( + WEBKIT_DOM_NODE (element)); + } + + return result; +} + +static gchar * +get_font_property (EHTMLEditorSelection *selection, + const gchar *font_property) +{ + WebKitDOMRange *range; + WebKitDOMNode *node; + WebKitDOMElement *element; + gchar *value; + + range = html_editor_selection_get_current_range (selection); + if (!range) + return NULL; + + node = webkit_dom_range_get_common_ancestor_container (range, NULL); + element = e_html_editor_dom_node_find_parent_element (node, "FONT"); + if (!element) + return NULL; + + g_object_get (G_OBJECT (element), font_property, &value, NULL); + + return value; +} + +static void +html_editor_selection_selection_changed_cb (WebKitWebView *webview, + EHTMLEditorSelection *selection) +{ + g_object_freeze_notify (G_OBJECT (selection)); + + g_object_notify (G_OBJECT (selection), "alignment"); + g_object_notify (G_OBJECT (selection), "background-color"); + g_object_notify (G_OBJECT (selection), "bold"); + g_object_notify (G_OBJECT (selection), "font-name"); + g_object_notify (G_OBJECT (selection), "font-size"); + g_object_notify (G_OBJECT (selection), "font-color"); + g_object_notify (G_OBJECT (selection), "block-format"); + g_object_notify (G_OBJECT (selection), "indented"); + g_object_notify (G_OBJECT (selection), "italic"); + g_object_notify (G_OBJECT (selection), "monospaced"); + g_object_notify (G_OBJECT (selection), "strikethrough"); + g_object_notify (G_OBJECT (selection), "subscript"); + g_object_notify (G_OBJECT (selection), "superscript"); + g_object_notify (G_OBJECT (selection), "text"); + g_object_notify (G_OBJECT (selection), "underline"); + + g_object_thaw_notify (G_OBJECT (selection)); +} + +void +e_html_editor_selection_block_selection_changed (EHTMLEditorSelection *selection) +{ + EHTMLEditorView *view; + + view = e_html_editor_selection_ref_html_editor_view (selection); + g_signal_handlers_block_by_func ( + view, html_editor_selection_selection_changed_cb, selection); + g_object_unref (view); +} + +void +e_html_editor_selection_unblock_selection_changed (EHTMLEditorSelection *selection) +{ + EHTMLEditorView *view; + + view = e_html_editor_selection_ref_html_editor_view (selection); + g_signal_handlers_unblock_by_func ( + view, html_editor_selection_selection_changed_cb, selection); + g_object_unref (view); +} + +static void +html_editor_selection_set_html_editor_view (EHTMLEditorSelection *selection, + EHTMLEditorView *view) +{ + gulong handler_id; + + g_return_if_fail (E_IS_HTML_EDITOR_VIEW (view)); + + g_weak_ref_set (&selection->priv->html_editor_view, view); + + handler_id = g_signal_connect ( + view, "selection-changed", + G_CALLBACK (html_editor_selection_selection_changed_cb), + selection); + + selection->priv->selection_changed_handler_id = handler_id; +} + +static void +html_editor_selection_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GdkRGBA rgba = { 0 }; + + switch (property_id) { + case PROP_ALIGNMENT: + g_value_set_int ( + value, + e_html_editor_selection_get_alignment ( + E_HTML_EDITOR_SELECTION (object))); + return; + + case PROP_BACKGROUND_COLOR: + g_value_set_string ( + value, + e_html_editor_selection_get_background_color ( + E_HTML_EDITOR_SELECTION (object))); + return; + + case PROP_BLOCK_FORMAT: + g_value_set_int ( + value, + e_html_editor_selection_get_block_format ( + E_HTML_EDITOR_SELECTION (object))); + return; + + case PROP_BOLD: + g_value_set_boolean ( + value, + e_html_editor_selection_is_bold ( + E_HTML_EDITOR_SELECTION (object))); + return; + + case PROP_HTML_EDITOR_VIEW: + g_value_take_object ( + value, + e_html_editor_selection_ref_html_editor_view ( + E_HTML_EDITOR_SELECTION (object))); + return; + + case PROP_FONT_COLOR: + e_html_editor_selection_get_font_color ( + E_HTML_EDITOR_SELECTION (object), &rgba); + g_value_set_boxed (value, &rgba); + return; + + case PROP_FONT_NAME: + g_value_set_string ( + value, + e_html_editor_selection_get_font_name ( + E_HTML_EDITOR_SELECTION (object))); + return; + + case PROP_FONT_SIZE: + g_value_set_int ( + value, + e_html_editor_selection_get_font_size ( + E_HTML_EDITOR_SELECTION (object))); + return; + + case PROP_INDENTED: + g_value_set_boolean ( + value, + e_html_editor_selection_is_indented ( + E_HTML_EDITOR_SELECTION (object))); + return; + + case PROP_ITALIC: + g_value_set_boolean ( + value, + e_html_editor_selection_is_italic ( + E_HTML_EDITOR_SELECTION (object))); + return; + + case PROP_MONOSPACED: + g_value_set_boolean ( + value, + e_html_editor_selection_is_monospaced ( + E_HTML_EDITOR_SELECTION (object))); + return; + + case PROP_STRIKETHROUGH: + g_value_set_boolean ( + value, + e_html_editor_selection_is_strikethrough ( + E_HTML_EDITOR_SELECTION (object))); + return; + + case PROP_SUBSCRIPT: + g_value_set_boolean ( + value, + e_html_editor_selection_is_subscript ( + E_HTML_EDITOR_SELECTION (object))); + return; + + case PROP_SUPERSCRIPT: + g_value_set_boolean ( + value, + e_html_editor_selection_is_superscript ( + E_HTML_EDITOR_SELECTION (object))); + return; + + case PROP_TEXT: + g_value_set_string ( + value, + e_html_editor_selection_get_string ( + E_HTML_EDITOR_SELECTION (object))); + break; + + case PROP_UNDERLINE: + g_value_set_boolean ( + value, + e_html_editor_selection_is_underline ( + E_HTML_EDITOR_SELECTION (object))); + return; + } + + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); +} + +static void +html_editor_selection_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + switch (property_id) { + case PROP_ALIGNMENT: + e_html_editor_selection_set_alignment ( + E_HTML_EDITOR_SELECTION (object), + g_value_get_int (value)); + return; + + case PROP_BACKGROUND_COLOR: + e_html_editor_selection_set_background_color ( + E_HTML_EDITOR_SELECTION (object), + g_value_get_string (value)); + return; + + case PROP_BOLD: + e_html_editor_selection_set_bold ( + E_HTML_EDITOR_SELECTION (object), + g_value_get_boolean (value)); + return; + + case PROP_HTML_EDITOR_VIEW: + html_editor_selection_set_html_editor_view ( + E_HTML_EDITOR_SELECTION (object), + g_value_get_object (value)); + return; + + case PROP_FONT_COLOR: + e_html_editor_selection_set_font_color ( + E_HTML_EDITOR_SELECTION (object), + g_value_get_boxed (value)); + return; + + case PROP_BLOCK_FORMAT: + e_html_editor_selection_set_block_format ( + E_HTML_EDITOR_SELECTION (object), + g_value_get_int (value)); + return; + + case PROP_FONT_NAME: + e_html_editor_selection_set_font_name ( + E_HTML_EDITOR_SELECTION (object), + g_value_get_string (value)); + return; + + case PROP_FONT_SIZE: + e_html_editor_selection_set_font_size ( + E_HTML_EDITOR_SELECTION (object), + g_value_get_int (value)); + return; + + case PROP_ITALIC: + e_html_editor_selection_set_italic ( + E_HTML_EDITOR_SELECTION (object), + g_value_get_boolean (value)); + return; + + case PROP_MONOSPACED: + e_html_editor_selection_set_monospaced ( + E_HTML_EDITOR_SELECTION (object), + g_value_get_boolean (value)); + return; + + case PROP_STRIKETHROUGH: + e_html_editor_selection_set_strikethrough ( + E_HTML_EDITOR_SELECTION (object), + g_value_get_boolean (value)); + return; + + case PROP_SUBSCRIPT: + e_html_editor_selection_set_subscript ( + E_HTML_EDITOR_SELECTION (object), + g_value_get_boolean (value)); + return; + + case PROP_SUPERSCRIPT: + e_html_editor_selection_set_superscript ( + E_HTML_EDITOR_SELECTION (object), + g_value_get_boolean (value)); + return; + + case PROP_UNDERLINE: + e_html_editor_selection_set_underline ( + E_HTML_EDITOR_SELECTION (object), + g_value_get_boolean (value)); + return; + } + + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); +} + +static void +html_editor_selection_dispose (GObject *object) +{ + EHTMLEditorSelectionPrivate *priv; + EHTMLEditorView *view; + + priv = E_HTML_EDITOR_SELECTION_GET_PRIVATE (object); + + view = g_weak_ref_get (&priv->html_editor_view); + if (view != NULL) { + g_signal_handler_disconnect ( + view, priv->selection_changed_handler_id); + priv->selection_changed_handler_id = 0; + g_object_unref (view); + } + + g_weak_ref_set (&priv->html_editor_view, NULL); + + /* Chain up to parent's dispose() method. */ + G_OBJECT_CLASS (e_html_editor_selection_parent_class)->dispose (object); +} + +static void +html_editor_selection_finalize (GObject *object) +{ + EHTMLEditorSelection *selection = E_HTML_EDITOR_SELECTION (object); + + g_free (selection->priv->text); + g_free (selection->priv->background_color); + g_free (selection->priv->font_color); + g_free (selection->priv->font_family); + + /* Chain up to parent's finalize() method. */ + G_OBJECT_CLASS (e_html_editor_selection_parent_class)->finalize (object); +} + +static void +e_html_editor_selection_class_init (EHTMLEditorSelectionClass *class) +{ + GObjectClass *object_class; + + g_type_class_add_private (class, sizeof (EHTMLEditorSelectionPrivate)); + + object_class = G_OBJECT_CLASS (class); + object_class->get_property = html_editor_selection_get_property; + object_class->set_property = html_editor_selection_set_property; + object_class->dispose = html_editor_selection_dispose; + object_class->finalize = html_editor_selection_finalize; + + /** + * EHTMLEditorSelectionalignment + * + * Holds alignment of current paragraph. + */ + /* FIXME: Convert the enum to a proper type */ + g_object_class_install_property ( + object_class, + PROP_ALIGNMENT, + g_param_spec_int ( + "alignment", + NULL, + NULL, + E_HTML_EDITOR_SELECTION_ALIGNMENT_LEFT, + E_HTML_EDITOR_SELECTION_ALIGNMENT_RIGHT, + E_HTML_EDITOR_SELECTION_ALIGNMENT_LEFT, + G_PARAM_READWRITE)); + + /** + * EHTMLEditorSelectionbackground-color + * + * Holds background color of current selection or at current cursor + * position. + */ + g_object_class_install_property ( + object_class, + PROP_BACKGROUND_COLOR, + g_param_spec_string ( + "background-color", + NULL, + NULL, + NULL, + G_PARAM_READWRITE)); + + /** + * EHTMLEditorSelectionblock-format + * + * Holds block format of current paragraph. See + * #EHTMLEditorSelectionBlockFormat for valid values. + */ + /* FIXME Convert the EHTMLEditorSelectionBlockFormat + * enum to a proper type. */ + g_object_class_install_property ( + object_class, + PROP_BLOCK_FORMAT, + g_param_spec_int ( + "block-format", + NULL, + NULL, + 0, + G_MAXINT, + 0, + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS)); + + /** + * EHTMLEditorSelectionbold + * + * Holds whether current selection or text at current cursor position + * is bold. + */ + g_object_class_install_property ( + object_class, + PROP_BOLD, + g_param_spec_boolean ( + "bold", + NULL, + NULL, + FALSE, + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property ( + object_class, + PROP_HTML_EDITOR_VIEW, + g_param_spec_object ( + "html-editor-view", + NULL, + NULL, + E_TYPE_HTML_EDITOR_VIEW, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); + + /** + * EHTMLEditorSelectionfont-color + * + * Holds font color of current selection or at current cursor position. + */ + g_object_class_install_property ( + object_class, + PROP_FONT_COLOR, + g_param_spec_boxed ( + "font-color", + NULL, + NULL, + GDK_TYPE_RGBA, + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS)); + + /** + * EHTMLEditorSelectionfont-name + * + * Holds name of font in current selection or at current cursor + * position. + */ + g_object_class_install_property ( + object_class, + PROP_FONT_NAME, + g_param_spec_string ( + "font-name", + NULL, + NULL, + NULL, + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS)); + + /** + * EHTMLEditorSelectionfont-size + * + * Holds point size of current selection or at current cursor position. + */ + g_object_class_install_property ( + object_class, + PROP_FONT_SIZE, + g_param_spec_int ( + "font-size", + NULL, + NULL, + 1, + 7, + 3, + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS)); + + /** + * EHTMLEditorSelectionindented + * + * Holds whether current paragraph is indented. This does not include + * citations. + */ + g_object_class_install_property ( + object_class, + PROP_INDENTED, + g_param_spec_boolean ( + "indented", + NULL, + NULL, + FALSE, + G_PARAM_READABLE | + G_PARAM_STATIC_STRINGS)); + + /** + * EHTMLEditorSelectionitalic + * + * Holds whether current selection or letter at current cursor position + * is italic. + */ + g_object_class_install_property ( + object_class, + PROP_ITALIC, + g_param_spec_boolean ( + "italic", + NULL, + NULL, + FALSE, + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS)); + + /** + * EHTMLEditorSelectionmonospaced + * + * Holds whether current selection or letter at current cursor position + * is monospaced. + */ + g_object_class_install_property ( + object_class, + PROP_MONOSPACED, + g_param_spec_boolean ( + "monospaced", + NULL, + NULL, + FALSE, + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS)); + + /** + * EHTMLEditorSelectionstrikethrough + * + * Holds whether current selection or letter at current cursor position + * is strikethrough. + */ + g_object_class_install_property ( + object_class, + PROP_STRIKETHROUGH, + g_param_spec_boolean ( + "strikethrough", + NULL, + NULL, + FALSE, + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS)); + + /** + * EHTMLEditorSelectionsuperscript + * + * Holds whether current selection or letter at current cursor position + * is in superscript. + */ + g_object_class_install_property ( + object_class, + PROP_SUPERSCRIPT, + g_param_spec_boolean ( + "superscript", + NULL, + NULL, + FALSE, + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS)); + + /** + * EHTMLEditorSelectionsubscript + * + * Holds whether current selection or letter at current cursor position + * is in subscript. + */ + g_object_class_install_property ( + object_class, + PROP_SUBSCRIPT, + g_param_spec_boolean ( + "subscript", + NULL, + NULL, + FALSE, + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS)); + + /** + * EHTMLEditorSelectiontext + * + * Holds always up-to-date text of current selection. + */ + g_object_class_install_property ( + object_class, + PROP_TEXT, + g_param_spec_string ( + "text", + NULL, + NULL, + NULL, + G_PARAM_READABLE | + G_PARAM_STATIC_STRINGS)); + + /** + * EHTMLEditorSelectionunderline + * + * Holds whether current selection or letter at current cursor position + * is underlined. + */ + g_object_class_install_property ( + object_class, + PROP_UNDERLINE, + g_param_spec_boolean ( + "underline", + NULL, + NULL, + FALSE, + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS)); +} + +static void +e_html_editor_selection_init (EHTMLEditorSelection *selection) +{ + GSettings *g_settings; + + selection->priv = E_HTML_EDITOR_SELECTION_GET_PRIVATE (selection); + + g_settings = g_settings_new ("org.gnome.evolution.mail"); + selection->priv->word_wrap_length = + g_settings_get_int (g_settings, "composer-word-wrap-length"); + g_object_unref (g_settings); +} + +gint +e_html_editor_selection_get_word_wrap_length (EHTMLEditorSelection *selection) +{ + g_return_val_if_fail (E_IS_HTML_EDITOR_SELECTION (selection), 72); + + return selection->priv->word_wrap_length; +} + +/** + * e_html_editor_selection_ref_html_editor_view: + * @selection: an #EHTMLEditorSelection + * + * Returns a new reference to @selection's #EHTMLEditorView. Unreference + * the #EHTMLEditorView with g_object_unref() when finished with it. + * + * Returns: an #EHTMLEditorView + **/ +EHTMLEditorView * +e_html_editor_selection_ref_html_editor_view (EHTMLEditorSelection *selection) +{ + g_return_val_if_fail (E_IS_HTML_EDITOR_SELECTION (selection), NULL); + + return g_weak_ref_get (&selection->priv->html_editor_view); +} + +/** + * e_html_editor_selection_has_text: + * @selection: an #EHTMLEditorSelection + * + * Returns whether current selection contains any text. + * + * Returns: @TRUE when current selection contains text, @FALSE otherwise. + */ +gboolean +e_html_editor_selection_has_text (EHTMLEditorSelection *selection) +{ + WebKitDOMRange *range; + WebKitDOMNode *node; + + g_return_val_if_fail (E_IS_HTML_EDITOR_SELECTION (selection), FALSE); + + range = html_editor_selection_get_current_range (selection); + + node = webkit_dom_range_get_start_container (range, NULL); + if (webkit_dom_node_get_node_type (node) == 3) + return TRUE; + + node = webkit_dom_range_get_end_container (range, NULL); + if (webkit_dom_node_get_node_type (node) == 3) + return TRUE; + + node = WEBKIT_DOM_NODE (webkit_dom_range_clone_contents (range, NULL)); + while (node) { + if (webkit_dom_node_get_node_type (node) == 3) + return TRUE; + + if (webkit_dom_node_has_child_nodes (node)) { + node = webkit_dom_node_get_first_child (node); + } else if (webkit_dom_node_get_next_sibling (node)) { + node = webkit_dom_node_get_next_sibling (node); + } else { + node = webkit_dom_node_get_parent_node (node); + if (node) { + node = webkit_dom_node_get_next_sibling (node); + } + } + } + + return FALSE; +} + +/** + * e_html_editor_selection_get_caret_word: + * @selection: an #EHTMLEditorSelection + * + * Returns word under cursor. + * + * Returns: A newly allocated string with current caret word or @NULL when there + * is no text under cursor or when selection is active. [transfer-full]. + */ +gchar * +e_html_editor_selection_get_caret_word (EHTMLEditorSelection *selection) +{ + WebKitDOMRange *range; + + g_return_val_if_fail (E_IS_HTML_EDITOR_SELECTION (selection), NULL); + + range = html_editor_selection_get_current_range (selection); + + /* Don't operate on the visible selection */ + range = webkit_dom_range_clone_range (range, NULL); + webkit_dom_range_expand (range, "word", NULL); + + return webkit_dom_range_to_string (range, NULL); +} + +/** + * e_html_editor_selection_replace_caret_word: + * @selection: an #EHTMLEditorSelection + * @replacement: a string to replace current caret word with + * + * Replaces current word under cursor with @replacement. + */ +void +e_html_editor_selection_replace_caret_word (EHTMLEditorSelection *selection, + const gchar *replacement) +{ + EHTMLEditorView *view; + WebKitWebView *web_view; + WebKitDOMDocument *document; + WebKitDOMDOMWindow *window; + WebKitDOMDOMSelection *dom_selection; + WebKitDOMRange *range; + + g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection)); + g_return_if_fail (replacement != NULL); + + view = e_html_editor_selection_ref_html_editor_view (selection); + g_return_if_fail (view != NULL); + + web_view = WEBKIT_WEB_VIEW (view); + + range = html_editor_selection_get_current_range (selection); + document = webkit_web_view_get_dom_document (web_view); + window = webkit_dom_document_get_default_view (document); + dom_selection = webkit_dom_dom_window_get_selection (window); + + webkit_dom_range_expand (range, "word", NULL); + webkit_dom_dom_selection_add_range (dom_selection, range); + + e_html_editor_selection_insert_html (selection, replacement); + + g_object_unref (view); +} + +/** + * e_html_editor_selection_get_string: + * @selection: an #EHTMLEditorSelection + * + * Returns currently selected string. + * + * Returns: A pointer to content of current selection. The string is owned by + * #EHTMLEditorSelection and should not be free'd. + */ +const gchar * +e_html_editor_selection_get_string (EHTMLEditorSelection *selection) +{ + WebKitDOMRange *range; + + g_return_val_if_fail (E_IS_HTML_EDITOR_SELECTION (selection), NULL); + + range = html_editor_selection_get_current_range (selection); + if (!range) + return NULL; + + g_free (selection->priv->text); + selection->priv->text = webkit_dom_range_get_text (range); + + return selection->priv->text; +} + +/** + * e_html_editor_selection_replace: + * @selection: an #EHTMLEditorSelection + * @new_string: a string to replace current selection with + * + * Replaces currently selected text with @new_string. + */ +void +e_html_editor_selection_replace (EHTMLEditorSelection *selection, + const gchar *new_string) +{ + EHTMLEditorView *view; + + g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection)); + + view = e_html_editor_selection_ref_html_editor_view (selection); + g_return_if_fail (view != NULL); + + e_html_editor_view_exec_command ( + view, E_HTML_EDITOR_VIEW_COMMAND_INSERT_TEXT, new_string); + + g_object_unref (view); +} + +/** + * e_html_editor_selection_get_list_alignment_from_node: + * @node: #an WebKitDOMNode + * + * Returns alignment of given list. + * + * Returns: #EHTMLEditorSelectionAlignment + */ +EHTMLEditorSelectionAlignment +e_html_editor_selection_get_list_alignment_from_node (WebKitDOMNode *node) +{ + if (element_has_class (WEBKIT_DOM_ELEMENT (node), "-x-evo-list-item-align-left")) + return E_HTML_EDITOR_SELECTION_ALIGNMENT_LEFT; + if (element_has_class (WEBKIT_DOM_ELEMENT (node), "-x-evo-list-item-align-center")) + return E_HTML_EDITOR_SELECTION_ALIGNMENT_CENTER; + if (element_has_class (WEBKIT_DOM_ELEMENT (node), "-x-evo-list-item-align-right")) + return E_HTML_EDITOR_SELECTION_ALIGNMENT_RIGHT; + + return E_HTML_EDITOR_SELECTION_ALIGNMENT_LEFT; +} + +/** + * e_html_editor_selection_get_alignment: + * @selection: #an EHTMLEditorSelection + * + * Returns alignment of current paragraph + * + * Returns: #EHTMLEditorSelectionAlignment + */ +EHTMLEditorSelectionAlignment +e_html_editor_selection_get_alignment (EHTMLEditorSelection *selection) +{ + EHTMLEditorSelectionAlignment alignment; + EHTMLEditorView *view; + gchar *value; + WebKitDOMCSSStyleDeclaration *style; + WebKitDOMDocument *document; + WebKitDOMDOMWindow *window; + WebKitDOMElement *element; + WebKitDOMNode *node; + WebKitDOMRange *range; + + g_return_val_if_fail ( + E_IS_HTML_EDITOR_SELECTION (selection), + E_HTML_EDITOR_SELECTION_ALIGNMENT_LEFT); + + view = e_html_editor_selection_ref_html_editor_view (selection); + g_return_val_if_fail (view != NULL, FALSE); + + document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view)); + g_object_unref (view); + window = webkit_dom_document_get_default_view (document); + range = html_editor_selection_get_current_range (selection); + if (!range) + return E_HTML_EDITOR_SELECTION_ALIGNMENT_LEFT; + + node = webkit_dom_range_get_start_container (range, NULL); + if (!node) + return E_HTML_EDITOR_SELECTION_ALIGNMENT_LEFT; + + if (WEBKIT_DOM_IS_ELEMENT (node)) + element = WEBKIT_DOM_ELEMENT (node); + else + element = webkit_dom_node_get_parent_element (node); + + style = webkit_dom_dom_window_get_computed_style (window, element, NULL); + value = webkit_dom_css_style_declaration_get_property_value (style, "text-align"); + + if (!value || !*value || + (g_ascii_strncasecmp (value, "left", 4) == 0)) { + alignment = E_HTML_EDITOR_SELECTION_ALIGNMENT_LEFT; + } else if (g_ascii_strncasecmp (value, "center", 6) == 0) { + alignment = E_HTML_EDITOR_SELECTION_ALIGNMENT_CENTER; + } else if (g_ascii_strncasecmp (value, "right", 5) == 0) { + alignment = E_HTML_EDITOR_SELECTION_ALIGNMENT_RIGHT; + } else { + alignment = E_HTML_EDITOR_SELECTION_ALIGNMENT_LEFT; + } + + g_free (value); + + return alignment; +} + +static void +set_ordered_list_type_to_element (WebKitDOMElement *list, + EHTMLEditorSelectionBlockFormat format) +{ + if (format == E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_ORDERED_LIST) + webkit_dom_element_remove_attribute (list, "type"); + else if (format == E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_ORDERED_LIST_ALPHA) + webkit_dom_element_set_attribute (list, "type", "A", NULL); + else if (format == E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_ORDERED_LIST_ROMAN) + webkit_dom_element_set_attribute (list, "type", "I", NULL); +} + +static WebKitDOMElement * +create_list_element (EHTMLEditorSelection *selection, + WebKitDOMDocument *document, + EHTMLEditorSelectionBlockFormat format, + gint level, + gboolean html_mode) +{ + WebKitDOMElement *list; + gint offset = -SPACES_PER_LIST_LEVEL; + gboolean inserting_unordered_list = + format == E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_UNORDERED_LIST; + + list = webkit_dom_document_create_element ( + document, inserting_unordered_list ? "UL" : "OL", NULL); + + set_ordered_list_type_to_element (list, format); + + if (level >= 0) + offset = (level + 1) * -SPACES_PER_LIST_LEVEL; + + if (!html_mode) + e_html_editor_selection_set_paragraph_style ( + selection, list, -1, offset, ""); + + return list; +} + +static void +remove_node (WebKitDOMNode *node) +{ + webkit_dom_node_remove_child ( + webkit_dom_node_get_parent_node (node), node, NULL); +} + +static void +format_change_list_from_list (EHTMLEditorSelection *selection, + WebKitDOMDocument *document, + EHTMLEditorSelectionBlockFormat to, + gboolean html_mode) +{ + gboolean after_selection_end = FALSE; + WebKitDOMElement *selection_start_marker, *selection_end_marker, *new_list; + WebKitDOMNode *source_list, *source_list_clone, *current_list, *item; + + selection_start_marker = webkit_dom_document_query_selector ( + document, "span#-x-evo-selection-start-marker", NULL); + selection_end_marker = webkit_dom_document_query_selector ( + document, "span#-x-evo-selection-end-marker", NULL); + + if (!selection_start_marker || !selection_end_marker) + return; + + new_list = create_list_element (selection, document, to, 0, html_mode); + + /* Copy elements from previous block to list */ + item = webkit_dom_node_get_parent_node ( + WEBKIT_DOM_NODE (selection_start_marker)); + source_list = webkit_dom_node_get_parent_node (item); + current_list = source_list; + source_list_clone = webkit_dom_node_clone_node (source_list, FALSE); + + if (element_has_class (WEBKIT_DOM_ELEMENT (source_list), "-x-evo-indented")) + element_add_class (WEBKIT_DOM_ELEMENT (new_list), "-x-evo-indented"); + + while (item) { + WebKitDOMNode *next_item = webkit_dom_node_get_next_sibling (item); + + if (WEBKIT_DOM_IS_HTMLLI_ELEMENT (item)) { + webkit_dom_node_append_child ( + after_selection_end ? + source_list_clone : WEBKIT_DOM_NODE (new_list), + WEBKIT_DOM_NODE (item), + NULL); + } + + if (webkit_dom_node_contains (item, WEBKIT_DOM_NODE (selection_end_marker))) { + source_list_clone = webkit_dom_node_clone_node (current_list, FALSE); + after_selection_end = TRUE; + } + + if (!next_item) { + if (after_selection_end) + break; + current_list = webkit_dom_node_get_next_sibling (current_list); + next_item = webkit_dom_node_get_first_child (current_list); + } + item = next_item; + } + + if (webkit_dom_node_has_child_nodes (source_list_clone)) + webkit_dom_node_insert_before ( + webkit_dom_node_get_parent_node (source_list), + WEBKIT_DOM_NODE (source_list_clone), + webkit_dom_node_get_next_sibling (source_list), NULL); + if (webkit_dom_node_has_child_nodes (WEBKIT_DOM_NODE (new_list))) + webkit_dom_node_insert_before ( + webkit_dom_node_get_parent_node (source_list), + WEBKIT_DOM_NODE (new_list), + webkit_dom_node_get_next_sibling (source_list), NULL); + if (!webkit_dom_node_has_child_nodes (source_list)) + remove_node (source_list); +} + +/** + * e_html_editor_selection_set_alignment: + * @selection: an #EHTMLEditorSelection + * @alignment: an #EHTMLEditorSelectionAlignment value to apply + * + * Sets alignment of current paragraph to give @alignment. + */ +void +e_html_editor_selection_set_alignment (EHTMLEditorSelection *selection, + EHTMLEditorSelectionAlignment alignment) +{ + EHTMLEditorView *view; + EHTMLEditorViewCommand command; + const gchar *class = ""; + WebKitDOMDocument *document; + WebKitDOMElement *selection_start_marker; + WebKitDOMNode *parent; + + g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection)); + + if (e_html_editor_selection_get_alignment (selection) == alignment) + return; + + switch (alignment) { + case E_HTML_EDITOR_SELECTION_ALIGNMENT_CENTER: + command = E_HTML_EDITOR_VIEW_COMMAND_JUSTIFY_CENTER; + class = "-x-evo-list-item-align-center"; + break; + + case E_HTML_EDITOR_SELECTION_ALIGNMENT_LEFT: + command = E_HTML_EDITOR_VIEW_COMMAND_JUSTIFY_LEFT; + break; + + case E_HTML_EDITOR_SELECTION_ALIGNMENT_RIGHT: + command = E_HTML_EDITOR_VIEW_COMMAND_JUSTIFY_RIGHT; + class = "-x-evo-list-item-align-right"; + break; + } + + selection->priv->alignment = alignment; + + view = e_html_editor_selection_ref_html_editor_view (selection); + g_return_if_fail (view != NULL); + + document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view)); + + e_html_editor_selection_save (selection); + + selection_start_marker = webkit_dom_document_query_selector ( + document, "span#-x-evo-selection-start-marker", NULL); + + if (!selection_start_marker) { + g_object_unref (view); + return; + } + + parent = webkit_dom_node_get_parent_node ( + WEBKIT_DOM_NODE (selection_start_marker)); + + if (WEBKIT_DOM_IS_HTMLLI_ELEMENT (parent)) { + element_remove_class ( + WEBKIT_DOM_ELEMENT (parent), + "-x-evo-list-item-align-center"); + element_remove_class ( + WEBKIT_DOM_ELEMENT (parent), + "-x-evo-list-item-align-right"); + + element_add_class (WEBKIT_DOM_ELEMENT (parent), class); + } else { + e_html_editor_view_exec_command (view, command, NULL); + } + + e_html_editor_selection_restore (selection); + + g_object_unref (view); + + g_object_notify (G_OBJECT (selection), "alignment"); +} + +/** + * e_html_editor_selection_get_background_color: + * @selection: an #EHTMLEditorSelection + * + * Returns background color of currently selected text or letter at current + * cursor position. + * + * Returns: A string with code of current background color. + */ +const gchar * +e_html_editor_selection_get_background_color (EHTMLEditorSelection *selection) +{ + WebKitDOMNode *ancestor; + WebKitDOMRange *range; + WebKitDOMCSSStyleDeclaration *css; + + g_return_val_if_fail (E_IS_HTML_EDITOR_SELECTION (selection), NULL); + + range = html_editor_selection_get_current_range (selection); + + ancestor = webkit_dom_range_get_common_ancestor_container (range, NULL); + + css = webkit_dom_element_get_style (WEBKIT_DOM_ELEMENT (ancestor)); + selection->priv->background_color = + webkit_dom_css_style_declaration_get_property_value ( + css, "background-color"); + + return selection->priv->background_color; +} + +/** + * e_html_editor_selection_set_background_color: + * @selection: an #EHTMLEditorSelection + * @color: code of new background color to set + * + * Changes background color of current selection or letter at current cursor + * position to @color. + */ +void +e_html_editor_selection_set_background_color (EHTMLEditorSelection *selection, + const gchar *color) +{ + EHTMLEditorView *view; + EHTMLEditorViewCommand command; + + g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection)); + g_return_if_fail (color != NULL && *color != '\0'); + + view = e_html_editor_selection_ref_html_editor_view (selection); + g_return_if_fail (view != NULL); + + command = E_HTML_EDITOR_VIEW_COMMAND_BACKGROUND_COLOR; + e_html_editor_view_exec_command (view, command, color); + + g_object_unref (view); + + g_object_notify (G_OBJECT (selection), "background-color"); +} + +static gint +get_indentation_level (WebKitDOMElement *element) +{ + WebKitDOMElement *parent; + gint level = 0; + + if (element_has_class (element, "-x-evo-indented")) + level++; + + parent = webkit_dom_node_get_parent_element (WEBKIT_DOM_NODE (element)); + /* Count level of indentation */ + while (parent && !WEBKIT_DOM_IS_HTML_BODY_ELEMENT (parent)) { + if (element_has_class (parent, "-x-evo-indented")) + level++; + + parent = webkit_dom_node_get_parent_element (WEBKIT_DOM_NODE (parent)); + } + + return level; +} + +static WebKitDOMNode * +get_block_node (WebKitDOMRange *range) +{ + WebKitDOMNode *node; + + node = webkit_dom_range_get_common_ancestor_container (range, NULL); + if (!WEBKIT_DOM_IS_ELEMENT (node)) + node = WEBKIT_DOM_NODE (webkit_dom_node_get_parent_element (node)); + + if (element_has_class (WEBKIT_DOM_ELEMENT (node), "-x-evo-temp-text-wrapper")) + node = WEBKIT_DOM_NODE (webkit_dom_node_get_parent_element (node)); + + return node; +} + +/** + * e_html_editor_selection_get_list_format_from_node: + * @node: an #WebKitDOMNode + * + * Returns block format of given list. + * + * Returns: #EHTMLEditorSelectionBlockFormat + */ +EHTMLEditorSelectionBlockFormat +e_html_editor_selection_get_list_format_from_node (WebKitDOMNode *node) +{ + EHTMLEditorSelectionBlockFormat format = + E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_UNORDERED_LIST; + + if (WEBKIT_DOM_IS_HTMLLI_ELEMENT (node)) + return -1; + + if (WEBKIT_DOM_IS_HTMLU_LIST_ELEMENT (node)) + return format; + + if (WEBKIT_DOM_IS_ELEMENT (node)) { + gchar *type_value = webkit_dom_element_get_attribute ( + WEBKIT_DOM_ELEMENT (node), "type"); + + if (!type_value) + return E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_ORDERED_LIST; + + if (!*type_value) + format = E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_ORDERED_LIST; + else if (g_ascii_strcasecmp (type_value, "A") == 0) + format = E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_ORDERED_LIST_ALPHA; + else if (g_ascii_strcasecmp (type_value, "I") == 0) + format = E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_ORDERED_LIST_ROMAN; + g_free (type_value); + } + + return -1; +} + +/** + * e_html_editor_selection_get_block_format: + * @selection: an #EHTMLEditorSelection + * + * Returns block format of current paragraph. + * + * Returns: #EHTMLEditorSelectionBlockFormat + */ +EHTMLEditorSelectionBlockFormat +e_html_editor_selection_get_block_format (EHTMLEditorSelection *selection) +{ + WebKitDOMNode *node; + WebKitDOMRange *range; + WebKitDOMElement *element; + EHTMLEditorSelectionBlockFormat result; + + g_return_val_if_fail ( + E_IS_HTML_EDITOR_SELECTION (selection), + E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_PARAGRAPH); + + range = html_editor_selection_get_current_range (selection); + if (!range) + return E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_PARAGRAPH; + + node = webkit_dom_range_get_start_container (range, NULL); + + if (e_html_editor_dom_node_find_parent_element (node, "UL")) { + result = E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_UNORDERED_LIST; + } else if ((element = e_html_editor_dom_node_find_parent_element (node, "OL")) != NULL) { + if (webkit_dom_element_has_attribute (element, "type")) { + gchar *type; + + type = webkit_dom_element_get_attribute (element, "type"); + if (type && ((*type == 'a') || (*type == 'A'))) { + result = E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_ORDERED_LIST_ALPHA; + } else if (type && ((*type == 'i') || (*type == 'I'))) { + result = E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_ORDERED_LIST_ROMAN; + } else { + result = E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_ORDERED_LIST; + } + + g_free (type); + } else { + result = E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_ORDERED_LIST; + } + } else if (e_html_editor_dom_node_find_parent_element (node, "PRE")) { + result = E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_PRE; + } else if (e_html_editor_dom_node_find_parent_element (node, "ADDRESS")) { + result = E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_ADDRESS; + } else if (e_html_editor_dom_node_find_parent_element (node, "H1")) { + result = E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_H1; + } else if (e_html_editor_dom_node_find_parent_element (node, "H2")) { + result = E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_H2; + } else if (e_html_editor_dom_node_find_parent_element (node, "H3")) { + result = E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_H3; + } else if (e_html_editor_dom_node_find_parent_element (node, "H4")) { + result = E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_H4; + } else if (e_html_editor_dom_node_find_parent_element (node, "H5")) { + result = E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_H5; + } else if (e_html_editor_dom_node_find_parent_element (node, "H6")) { + result = E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_H6; + } else if ((element = e_html_editor_dom_node_find_parent_element (node, "BLOCKQUOTE")) != NULL) { + if (element_has_class (element, "-x-evo-indented")) + result = E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_PARAGRAPH; + else { + WebKitDOMNode *block = get_block_node (range); + + if (element_has_class (WEBKIT_DOM_ELEMENT (block), "-x-evo-paragraph")) + result = E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_PARAGRAPH; + else + result = E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_BLOCKQUOTE; + } + } else if (e_html_editor_dom_node_find_parent_element (node, "P")) { + result = E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_PARAGRAPH; + } else { + result = E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_PARAGRAPH; + } + + return result; +} + +static gboolean +is_caret_position_node (WebKitDOMNode *node) +{ + return element_has_id (WEBKIT_DOM_ELEMENT (node), "-x-evo-caret-position"); +} + +static void +merge_list_into_list (WebKitDOMNode *from, + WebKitDOMNode *to, + gboolean insert_before) +{ + WebKitDOMNode *item; + + if (!(to && from)) + return; + + while ((item = webkit_dom_node_get_first_child (from)) != NULL) { + if (insert_before) + webkit_dom_node_insert_before ( + to, item, webkit_dom_node_get_last_child (to), NULL); + else + webkit_dom_node_append_child (to, item, NULL); + } + + if (!webkit_dom_node_has_child_nodes (from)) + remove_node (from); + +} + +static void +merge_lists_if_possible (WebKitDOMNode *list) +{ + EHTMLEditorSelectionBlockFormat format, prev, next; + WebKitDOMNode *prev_sibling, *next_sibling; + + prev_sibling = webkit_dom_node_get_previous_sibling (WEBKIT_DOM_NODE (list)); + next_sibling = webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (list)); + + format = e_html_editor_selection_get_list_format_from_node (list), + prev = e_html_editor_selection_get_list_format_from_node (prev_sibling); + next = e_html_editor_selection_get_list_format_from_node (next_sibling); + + if (format == prev && format != -1 && prev != -1) + merge_list_into_list (prev_sibling, list, TRUE); + + if (format == next && format != -1 && next != -1) + merge_list_into_list (next_sibling, list, FALSE); +} + +static void +remove_wrapping (WebKitDOMElement *element) +{ + WebKitDOMNodeList *list; + gint ii, length; + + list = webkit_dom_element_query_selector_all ( + element, "br.-x-evo-wrap-br", NULL); + length = webkit_dom_node_list_get_length (list); + for (ii = 0; ii < length; ii++) + remove_node (webkit_dom_node_list_item (list, ii)); +} + +static void +remove_quoting (WebKitDOMElement *element) +{ + WebKitDOMNodeList *list; + gint ii, length; + + list = webkit_dom_element_query_selector_all ( + element, "span.-x-evo-quoted", NULL); + length = webkit_dom_node_list_get_length (list); + for (ii = 0; ii < length; ii++) { + remove_node (webkit_dom_node_list_item (list, ii)); + } + + list = webkit_dom_element_query_selector_all ( + element, "span.-x-evo-temp-text-wrapper", NULL); + length = webkit_dom_node_list_get_length (list); + for (ii = 0; ii < length; ii++) { + WebKitDOMNode *nd = webkit_dom_node_list_item (list, ii); + + while (webkit_dom_node_has_child_nodes (nd)) { + webkit_dom_node_insert_before ( + webkit_dom_node_get_parent_node (nd), + webkit_dom_node_get_first_child (nd), + nd, + NULL); + } + + remove_node (nd); + } + + webkit_dom_node_normalize (WEBKIT_DOM_NODE (element)); +} + +static gboolean +node_is_list (WebKitDOMNode *node) +{ + return node && ( + WEBKIT_DOM_IS_HTMLO_LIST_ELEMENT (node) || + WEBKIT_DOM_IS_HTMLU_LIST_ELEMENT (node)); +} + +static gint +get_citation_level (WebKitDOMNode *node) +{ + WebKitDOMNode *parent = webkit_dom_node_get_parent_node (node); + gint level = 0; + + while (parent && !WEBKIT_DOM_IS_HTML_BODY_ELEMENT (parent)) { + if (WEBKIT_DOM_IS_HTML_QUOTE_ELEMENT (parent) && + webkit_dom_element_has_attribute (WEBKIT_DOM_ELEMENT (parent), "type")) + level++; + + parent = webkit_dom_node_get_parent_node (parent); + } + + return level; +} + +static void +format_change_block_to_block (EHTMLEditorSelection *selection, + EHTMLEditorSelectionBlockFormat format, + EHTMLEditorView *view, + const gchar *value, + WebKitDOMDocument *document) +{ + gboolean after_selection_end, quoted = FALSE; + WebKitDOMElement *selection_start_marker, *selection_end_marker, *element; + WebKitDOMNode *block, *next_block; + + e_html_editor_selection_save (selection); + selection_start_marker = webkit_dom_document_query_selector ( + document, "span#-x-evo-selection-start-marker", NULL); + selection_end_marker = webkit_dom_document_query_selector ( + document, "span#-x-evo-selection-end-marker", NULL); + block = webkit_dom_node_get_parent_node ( + WEBKIT_DOM_NODE (selection_start_marker)); + if (element_has_class (WEBKIT_DOM_ELEMENT (block), "-x-evo-temp-text-wrapper")) { + block = webkit_dom_node_get_parent_node (block); + remove_wrapping (WEBKIT_DOM_ELEMENT (block)); + remove_quoting (WEBKIT_DOM_ELEMENT (block)); + quoted = TRUE; + } + + /* Process all blocks that are in the selection one by one */ + while (block) { + WebKitDOMNode *child; + + after_selection_end = webkit_dom_node_contains ( + block, WEBKIT_DOM_NODE (selection_end_marker)); + + next_block = webkit_dom_node_get_next_sibling ( + WEBKIT_DOM_NODE (block)); + + if (format == E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_PARAGRAPH) + element = e_html_editor_selection_get_paragraph_element ( + selection, document, -1, 0); + else + element = webkit_dom_document_create_element ( + document, value, NULL); + + while ((child = webkit_dom_node_get_first_child (block))) + webkit_dom_node_append_child ( + WEBKIT_DOM_NODE (element), child, NULL); + + webkit_dom_node_insert_before ( + webkit_dom_node_get_parent_node (block), + WEBKIT_DOM_NODE (element), + block, + NULL); + + remove_node (block); + + block = next_block; + + if (after_selection_end) + break; + } + + if (format == E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_PARAGRAPH && + !e_html_editor_view_get_html_mode (view)) { + gint citation_level, quote; + + citation_level = get_citation_level (WEBKIT_DOM_NODE (element)); + quote = citation_level ? citation_level + 1 : 0; + + element = e_html_editor_selection_wrap_paragraph_length ( + selection, element, selection->priv->word_wrap_length - quote); + } + + if (quoted) + e_html_editor_view_quote_plain_text_element (view, element); + + e_html_editor_selection_restore (selection); +} + +static void +remove_node_if_empty (WebKitDOMNode *node) +{ + if (!WEBKIT_DOM_IS_NODE (node)) + return; + + if (!webkit_dom_node_get_first_child (node)) { + remove_node (node); + } else { + gchar *text_content; + + text_content = webkit_dom_node_get_text_content (node); + if (!text_content) + remove_node (node); + + if (text_content && !*text_content) + remove_node (node); + + g_free (text_content); + } +} + +static void +format_change_block_to_list (EHTMLEditorSelection *selection, + EHTMLEditorSelectionBlockFormat format, + EHTMLEditorView *view, + WebKitDOMDocument *document) +{ + gboolean after_selection_end; + gboolean html_mode = e_html_editor_view_get_html_mode (view); + WebKitDOMElement *selection_start_marker, *selection_end_marker, *item, *list; + WebKitDOMNode *block, *next_block; + + e_html_editor_selection_save (selection); + + selection_start_marker = webkit_dom_document_query_selector ( + document, "span#-x-evo-selection-start-marker", NULL); + selection_end_marker = webkit_dom_document_query_selector ( + document, "span#-x-evo-selection-end-marker", NULL); + block = webkit_dom_node_get_parent_node ( + WEBKIT_DOM_NODE (selection_start_marker)); + + list = create_list_element (selection, document, format, 0, html_mode); + + if (element_has_class (WEBKIT_DOM_ELEMENT (block), "-x-evo-temp-text-wrapper")) { + WebKitDOMElement *element; + + block = webkit_dom_node_get_parent_node (block); + + remove_wrapping (WEBKIT_DOM_ELEMENT (block)); + remove_quoting (WEBKIT_DOM_ELEMENT (block)); + + e_html_editor_view_exec_command ( + view, E_HTML_EDITOR_VIEW_COMMAND_INSERT_NEW_LINE_IN_QUOTED_CONTENT, NULL); + + element = webkit_dom_document_query_selector ( + document, "body>br", NULL); + + webkit_dom_node_replace_child ( + webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (element)), + WEBKIT_DOM_NODE (list), + WEBKIT_DOM_NODE (element), + NULL); + + selection_start_marker = webkit_dom_document_query_selector ( + document, "span#-x-evo-selection-start-marker", NULL); + selection_end_marker = webkit_dom_document_query_selector ( + document, "span#-x-evo-selection-end-marker", NULL); + block = webkit_dom_node_get_parent_node ( + WEBKIT_DOM_NODE (selection_start_marker)); + } else + webkit_dom_node_insert_before ( + webkit_dom_node_get_parent_node (block), + WEBKIT_DOM_NODE (list), + block, + NULL); + + /* Process all blocks that are in the selection one by one */ + while (block) { + gboolean empty = FALSE; + gchar *content; + WebKitDOMNode *child; + + after_selection_end = webkit_dom_node_contains ( + block, WEBKIT_DOM_NODE (selection_end_marker)); + + next_block = webkit_dom_node_get_next_sibling ( + WEBKIT_DOM_NODE (block)); + + item = webkit_dom_document_create_element (document, "LI", NULL); + content = webkit_dom_node_get_text_content (block); + + empty = !*content || (g_strcmp0 (content, UNICODE_ZERO_WIDTH_SPACE) == 0); + g_free (content); + + /* We have to use again the hidden space to move caret into newly inserted list */ + if (empty) { + webkit_dom_html_element_set_inner_html ( + WEBKIT_DOM_HTML_ELEMENT (item), + UNICODE_ZERO_WIDTH_SPACE, + NULL); + } + + while ((child = webkit_dom_node_get_first_child (block))) + webkit_dom_node_append_child ( + WEBKIT_DOM_NODE (item), child, NULL); + + webkit_dom_node_append_child ( + WEBKIT_DOM_NODE (list), WEBKIT_DOM_NODE (item), NULL); + + remove_node (block); + + block = next_block; + + if (after_selection_end) + break; + } + + merge_lists_if_possible (WEBKIT_DOM_NODE (list)); + + e_html_editor_view_force_spell_check (view); + e_html_editor_selection_restore (selection); +} + +static void +format_change_list_to_list (EHTMLEditorSelection *selection, + EHTMLEditorSelectionBlockFormat format, + WebKitDOMDocument *document, + gboolean html_mode) +{ + EHTMLEditorSelectionBlockFormat prev = 0, next = 0; + gboolean done = FALSE, indented = FALSE; + gboolean selection_starts_in_first_child, selection_ends_in_last_child; + WebKitDOMElement *selection_start_marker, *selection_end_marker; + WebKitDOMNode *prev_list, *current_list, *next_list; + + e_html_editor_selection_save (selection); + + selection_start_marker = webkit_dom_document_query_selector ( + document, "span#-x-evo-selection-start-marker", NULL); + selection_end_marker = webkit_dom_document_query_selector ( + document, "span#-x-evo-selection-end-marker", NULL); + + current_list = webkit_dom_node_get_parent_node ( + webkit_dom_node_get_parent_node ( + WEBKIT_DOM_NODE (selection_start_marker))); + + prev_list = webkit_dom_node_get_parent_node ( + webkit_dom_node_get_parent_node ( + WEBKIT_DOM_NODE (selection_start_marker))); + + next_list = webkit_dom_node_get_parent_node ( + webkit_dom_node_get_parent_node ( + WEBKIT_DOM_NODE (selection_end_marker))); + + selection_starts_in_first_child = + webkit_dom_node_contains ( + webkit_dom_node_get_first_child (current_list), + WEBKIT_DOM_NODE (selection_start_marker)); + + selection_ends_in_last_child = + webkit_dom_node_contains ( + webkit_dom_node_get_last_child (current_list), + WEBKIT_DOM_NODE (selection_end_marker)); + + indented = element_has_class (WEBKIT_DOM_ELEMENT (current_list), "-x-evo-indented"); + + if (!prev_list || !next_list || indented) { + format_change_list_from_list (selection, document, format, html_mode); + goto out; + } + + if (webkit_dom_node_is_same_node (prev_list, next_list)) { + prev_list = webkit_dom_node_get_previous_sibling ( + webkit_dom_node_get_parent_node ( + webkit_dom_node_get_parent_node ( + WEBKIT_DOM_NODE (selection_start_marker)))); + next_list = webkit_dom_node_get_next_sibling ( + webkit_dom_node_get_parent_node ( + webkit_dom_node_get_parent_node ( + WEBKIT_DOM_NODE (selection_end_marker)))); + if (!prev_list || !next_list) { + format_change_list_from_list (selection, document, format, html_mode); + goto out; + } + } + + prev = e_html_editor_selection_get_list_format_from_node (prev_list); + next = e_html_editor_selection_get_list_format_from_node (next_list); + + if (format == prev && format != -1 && prev != -1) { + if (selection_starts_in_first_child && selection_ends_in_last_child) { + done = TRUE; + merge_list_into_list (current_list, prev_list, FALSE); + } + } + + if (format == next && format != -1 && next != -1) { + if (selection_starts_in_first_child && selection_ends_in_last_child) { + done = TRUE; + merge_list_into_list (next_list, prev_list, FALSE); + } + } + + if (done) + goto out; + + format_change_list_from_list (selection, document, format, html_mode); +out: + e_html_editor_selection_restore (selection); +} + +static void +format_change_list_to_block (EHTMLEditorSelection *selection, + EHTMLEditorSelectionBlockFormat format, + WebKitDOMDocument *document) +{ + gboolean after_end = FALSE; + WebKitDOMElement *selection_start, *element, *selection_end; + WebKitDOMNode *source_list, *next_item, *item, *source_list_clone; + + e_html_editor_selection_save (selection); + + selection_start = webkit_dom_document_query_selector ( + document, "span#-x-evo-selection-start-marker", NULL); + selection_end = webkit_dom_document_query_selector ( + document, "span#-x-evo-selection-end-marker", NULL); + + item = webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (selection_start)); + source_list = webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (item)); + source_list_clone = webkit_dom_node_clone_node (source_list, FALSE); + + webkit_dom_node_insert_before ( + webkit_dom_node_get_parent_node (source_list), + WEBKIT_DOM_NODE (source_list_clone), + webkit_dom_node_get_next_sibling (source_list), + NULL); + + next_item = item; + + /* Process all nodes that are in selection one by one */ + while (next_item) { + WebKitDOMNode *tmp; + + tmp = webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (next_item)); + + if (!after_end) { + if (format == E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_PARAGRAPH) + element = e_html_editor_selection_get_paragraph_element (selection, document, -1, 0); + else if (format == E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_PRE) + element = webkit_dom_document_create_element (document, "PRE", NULL); + else if (format == E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_BLOCKQUOTE) + element = webkit_dom_document_create_element (document, "BLOCKQUOTE", NULL); + else + element = e_html_editor_selection_get_paragraph_element (selection, document, -1, 0); + + after_end = webkit_dom_node_contains (next_item, WEBKIT_DOM_NODE (selection_end)); + + while (webkit_dom_node_get_first_child (next_item)) { + WebKitDOMNode *node = webkit_dom_node_get_first_child (next_item); + + webkit_dom_node_append_child ( + WEBKIT_DOM_NODE (element), node, NULL); + } + + webkit_dom_node_insert_before ( + webkit_dom_node_get_parent_node (source_list), + WEBKIT_DOM_NODE (element), + source_list_clone, + NULL); + + remove_node (next_item); + + next_item = tmp; + } else { + webkit_dom_node_append_child ( + source_list_clone, next_item, NULL); + + next_item = tmp; + } + } + + remove_node_if_empty (source_list_clone); + remove_node_if_empty (source_list); + + e_html_editor_selection_restore (selection); +} + +/** + * e_html_editor_selection_set_block_format: + * @selection: an #EHTMLEditorSelection + * @format: an #EHTMLEditorSelectionBlockFormat value + * + * Changes block format of current paragraph to @format. + */ +void +e_html_editor_selection_set_block_format (EHTMLEditorSelection *selection, + EHTMLEditorSelectionBlockFormat format) +{ + EHTMLEditorView *view; + EHTMLEditorSelectionBlockFormat current_format; + const gchar *value; + gboolean has_selection = FALSE; + gboolean from_list = FALSE, to_list = FALSE, html_mode; + WebKitDOMDocument *document; + WebKitDOMRange *range; + + g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection)); + + current_format = e_html_editor_selection_get_block_format (selection); + if (current_format == format) { + return; + } + + switch (format) { + case E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_BLOCKQUOTE: + value = "BLOCKQUOTE"; + break; + case E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_H1: + value = "H1"; + break; + case E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_H2: + value = "H2"; + break; + case E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_H3: + value = "H3"; + break; + case E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_H4: + value = "H4"; + break; + case E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_H5: + value = "H5"; + break; + case E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_H6: + value = "H6"; + break; + case E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_PARAGRAPH: + value = "P"; + break; + case E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_PRE: + value = "PRE"; + break; + case E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_ADDRESS: + value = "ADDRESS"; + break; + case E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_ORDERED_LIST: + case E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_ORDERED_LIST_ALPHA: + case E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_ORDERED_LIST_ROMAN: + to_list = TRUE; + value = NULL; + break; + case E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_UNORDERED_LIST: + to_list = TRUE; + value = NULL; + break; + case E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_NONE: + default: + value = NULL; + break; + } + + if (g_strcmp0 (e_html_editor_selection_get_string (selection), "") != 0) + has_selection = TRUE; + + /* H1 - H6 have bold font by default */ + if (format >= E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_H1 && + format <= E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_H6) + selection->priv->is_bold = TRUE; + + view = e_html_editor_selection_ref_html_editor_view (selection); + g_return_if_fail (view != NULL); + + html_mode = e_html_editor_view_get_html_mode (view); + document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view)); + + from_list = + current_format >= E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_UNORDERED_LIST; + + range = html_editor_selection_get_current_range (selection); + if (!range) { + g_object_unref (view); + return; + } + + if (from_list && to_list) + format_change_list_to_list (selection, format, document, html_mode); + + if (!from_list && !to_list) + format_change_block_to_block (selection, format, view, value, document); + + if (from_list && !to_list) + format_change_list_to_block (selection, format, document); + + if (!from_list && to_list) + format_change_block_to_list (selection, format, view, document); + + if (!has_selection) + e_html_editor_view_force_spell_check (view); + + g_object_unref (view); + + /* When changing the format we need to re-set the alignment */ + e_html_editor_selection_set_alignment (selection, selection->priv->alignment); + + g_object_notify (G_OBJECT (selection), "block-format"); +} + +/** + * e_html_editor_selection_get_font_color: + * @selection: an #EHTMLEditorSelection + * @rgba: a #GdkRGBA object to be set to current font color + * + * Sets @rgba to contain color of current text selection or letter at current + * cursor position. + */ +void +e_html_editor_selection_get_font_color (EHTMLEditorSelection *selection, + GdkRGBA *rgba) +{ + gchar *color; + g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection)); + + if (g_strcmp0 (e_html_editor_selection_get_string (selection), "") == 0) { + color = g_strdup (selection->priv->font_color); + } else { + color = get_font_property (selection, "color"); + if (!color) { + *rgba = black; + return; + } + } + + gdk_rgba_parse (rgba, color); + g_free (color); +} + +/** + * e_html_editor_selection_set_font_color: + * @selection: an #EHTMLEditorSelection + * @rgba: a #GdkRGBA + * + * Sets font color of current selection or letter at current cursor position to + * color defined in @rgba. + */ +void +e_html_editor_selection_set_font_color (EHTMLEditorSelection *selection, + const GdkRGBA *rgba) +{ + EHTMLEditorView *view; + EHTMLEditorViewCommand command; + guint32 rgba_value; + gchar *color; + + g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection)); + + if (!rgba) + rgba = &black; + + rgba_value = e_rgba_to_value ((GdkRGBA *) rgba); + + view = e_html_editor_selection_ref_html_editor_view (selection); + g_return_if_fail (view != NULL); + + command = E_HTML_EDITOR_VIEW_COMMAND_FORE_COLOR; + color = g_strdup_printf ("#%06x", rgba_value); + selection->priv->font_color = g_strdup (color); + e_html_editor_view_exec_command (view, command, color); + g_free (color); + + g_object_unref (view); + + g_object_notify (G_OBJECT (selection), "font-color"); +} + +/** + * e_html_editor_selection_get_font_name: + * @selection: an #EHTMLEditorSelection + * + * Returns name of font used in current selection or at letter at current cursor + * position. + * + * Returns: A string with font name. [transfer-none] + */ +const gchar * +e_html_editor_selection_get_font_name (EHTMLEditorSelection *selection) +{ + WebKitDOMNode *node; + WebKitDOMRange *range; + WebKitDOMCSSStyleDeclaration *css; + + g_return_val_if_fail (E_IS_HTML_EDITOR_SELECTION (selection), NULL); + + range = html_editor_selection_get_current_range (selection); + node = webkit_dom_range_get_common_ancestor_container (range, NULL); + + g_free (selection->priv->font_family); + css = webkit_dom_element_get_style (WEBKIT_DOM_ELEMENT (node)); + selection->priv->font_family = + webkit_dom_css_style_declaration_get_property_value (css, "fontFamily"); + + return selection->priv->font_family; +} + +/** + * e_html_editor_selection_set_font_name: + * @selection: an #EHTMLEditorSelection + * @font_name: a font name to apply + * + * Sets font name of current selection or of letter at current cursor position + * to @font_name. + */ +void +e_html_editor_selection_set_font_name (EHTMLEditorSelection *selection, + const gchar *font_name) +{ + EHTMLEditorView *view; + EHTMLEditorViewCommand command; + + g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection)); + + view = e_html_editor_selection_ref_html_editor_view (selection); + g_return_if_fail (view != NULL); + + command = E_HTML_EDITOR_VIEW_COMMAND_FONT_NAME; + e_html_editor_view_exec_command (view, command, font_name); + + g_object_unref (view); + + g_object_notify (G_OBJECT (selection), "font-name"); +} + +/** + * e_editor_Selection_get_font_size: + * @selection: an #EHTMLEditorSelection + * + * Returns point size of current selection or of letter at current cursor position. + */ + guint +e_html_editor_selection_get_font_size (EHTMLEditorSelection *selection) +{ + gchar *size; + guint size_int; + + g_return_val_if_fail ( + E_IS_HTML_EDITOR_SELECTION (selection), + E_HTML_EDITOR_SELECTION_FONT_SIZE_NORMAL); + + size = get_font_property (selection, "size"); + if (!size) + return E_HTML_EDITOR_SELECTION_FONT_SIZE_NORMAL; + + size_int = atoi (size); + g_free (size); + + if (size_int == 0) + return E_HTML_EDITOR_SELECTION_FONT_SIZE_NORMAL; + + return size_int; +} + +/** + * e_html_editor_selection_set_font_size: + * @selection: an #EHTMLEditorSelection + * @font_size: point size to apply + * + * Sets font size of current selection or of letter at current cursor position + * to @font_size. + */ +void +e_html_editor_selection_set_font_size (EHTMLEditorSelection *selection, + guint font_size) +{ + EHTMLEditorView *view; + EHTMLEditorViewCommand command; + gchar *size_str; + + g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection)); + + view = e_html_editor_selection_ref_html_editor_view (selection); + g_return_if_fail (view != NULL); + + selection->priv->font_size = font_size; + command = E_HTML_EDITOR_VIEW_COMMAND_FONT_SIZE; + size_str = g_strdup_printf ("%d", font_size); + e_html_editor_view_exec_command (view, command, size_str); + g_free (size_str); + + g_object_unref (view); + + g_object_notify (G_OBJECT (selection), "font-size"); +} + +/** + * e_html_editor_selection_is_citation: + * @selection: an #EHTMLEditorSelection + * + * Returns whether current paragraph is a citation. + * + * Returns: @TRUE when current paragraph is a citation, @FALSE otherwise. + */ +gboolean +e_html_editor_selection_is_citation (EHTMLEditorSelection *selection) +{ + gboolean ret_val; + gchar *value, *text_content; + WebKitDOMNode *node; + WebKitDOMRange *range; + + g_return_val_if_fail (E_IS_HTML_EDITOR_SELECTION (selection), FALSE); + + range = html_editor_selection_get_current_range (selection); + if (!range) + return FALSE; + + node = webkit_dom_range_get_common_ancestor_container (range, NULL); + + if (WEBKIT_DOM_IS_TEXT (node)) + return get_has_style (selection, "citation"); + + /* If we are changing the format of block we have to re-set bold property, + * otherwise it will be turned off because of no text in composer */ + text_content = webkit_dom_node_get_text_content (node); + if (g_strcmp0 (text_content, "") == 0) { + g_free (text_content); + return FALSE; + } + g_free (text_content); + + value = webkit_dom_element_get_attribute (WEBKIT_DOM_ELEMENT (node), "type"); + + /* citation ==
*/ + if (g_strstr_len (value, -1, "cite")) + ret_val = TRUE; + else + ret_val = get_has_style (selection, "citation"); + + g_free (value); + return ret_val; +} + +/** + * e_html_editor_selection_is_indented: + * @selection: an #EHTMLEditorSelection + * + * Returns whether current paragraph is indented. This does not include + * citations. To check, whether paragraph is a citation, use + * e_html_editor_selection_is_citation(). + * + * Returns: @TRUE when current paragraph is indented, @FALSE otherwise. + */ +gboolean +e_html_editor_selection_is_indented (EHTMLEditorSelection *selection) +{ + WebKitDOMRange *range; + WebKitDOMNode *node; + WebKitDOMElement *element; + + g_return_val_if_fail (E_IS_HTML_EDITOR_SELECTION (selection), FALSE); + + range = html_editor_selection_get_current_range (selection); + if (!range) + return FALSE; + + node = webkit_dom_range_get_end_container (range, NULL); + if (!WEBKIT_DOM_IS_ELEMENT (node)) + node = WEBKIT_DOM_NODE (webkit_dom_node_get_parent_element (node)); + + element = webkit_dom_node_get_parent_element (node); + + return element_has_class (element, "-x-evo-indented"); +} + +static gboolean +is_in_html_mode (EHTMLEditorSelection *selection) +{ + EHTMLEditorView *view = e_html_editor_selection_ref_html_editor_view (selection); + gboolean ret_val; + + g_return_val_if_fail (view != NULL, FALSE); + + ret_val = e_html_editor_view_get_html_mode (view); + + g_object_unref (view); + + return ret_val; +} + +static gint +get_list_level (WebKitDOMNode *node) +{ + gint level = 0; + + while (node && !WEBKIT_DOM_IS_HTML_BODY_ELEMENT (node)) { + if (node_is_list (node)) + level++; + node = webkit_dom_node_get_parent_node (node); + } + + return level; +} + +/** + * e_html_editor_selection_indent: + * @selection: an #EHTMLEditorSelection + * + * Indents current paragraph by one level. + */ +void +e_html_editor_selection_indent (EHTMLEditorSelection *selection) +{ + gboolean has_selection; + EHTMLEditorView *view; + EHTMLEditorViewCommand command; + + g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection)); + + view = e_html_editor_selection_ref_html_editor_view (selection); + g_return_if_fail (view != NULL); + + has_selection = g_strcmp0 (e_html_editor_selection_get_string (selection), "") != 0; + + if (!has_selection) { + WebKitDOMDocument *document; + WebKitDOMRange *range; + WebKitDOMNode *node, *clone; + WebKitDOMElement *element, *caret_position; + gint word_wrap_length = selection->priv->word_wrap_length; + gint level; + gint final_width = 0; + + document = webkit_web_view_get_dom_document ( + WEBKIT_WEB_VIEW (view)); + + caret_position = e_html_editor_selection_save_caret_position (selection); + + range = html_editor_selection_get_current_range (selection); + if (!range) { + g_object_unref (view); + return; + } + + node = webkit_dom_range_get_end_container (range, NULL); + if (!WEBKIT_DOM_IS_ELEMENT (node)) + node = WEBKIT_DOM_NODE ( + webkit_dom_node_get_parent_element (node)); + + if (WEBKIT_DOM_IS_HTMLLI_ELEMENT (node)) { + gboolean html_mode = e_html_editor_view_get_html_mode (view); + WebKitDOMElement *list; + WebKitDOMNode *source_list = webkit_dom_node_get_parent_node (node); + EHTMLEditorSelectionBlockFormat format; + + format = e_html_editor_selection_get_list_format_from_node (source_list); + + list = create_list_element ( + selection, document, format, get_list_level (node), html_mode); + + element_add_class (list, "-x-evo-indented"); + webkit_dom_node_insert_before ( + source_list, WEBKIT_DOM_NODE (list), node, NULL); + webkit_dom_node_append_child ( + WEBKIT_DOM_NODE (list), node, NULL); + if (!webkit_dom_node_contains (node, WEBKIT_DOM_NODE (caret_position))) { + webkit_dom_node_append_child ( + node, WEBKIT_DOM_NODE (caret_position), NULL); + } + + merge_lists_if_possible (WEBKIT_DOM_NODE (list)); + + e_html_editor_selection_restore_caret_position (selection); + + g_object_unref (view); + return; + } + + level = get_indentation_level (WEBKIT_DOM_ELEMENT (node)); + + final_width = word_wrap_length - SPACES_PER_INDENTATION * (level + 1); + if (final_width < 10 && !is_in_html_mode (selection)) { + e_html_editor_selection_restore_caret_position (selection); + g_object_unref (view); + return; + } + + element = webkit_dom_node_get_parent_element (node); + clone = webkit_dom_node_clone_node (node, TRUE); + + /* Remove style and let the paragraph inherit it from parent */ + if (element_has_class (WEBKIT_DOM_ELEMENT (clone), "-x-evo-paragraph")) + webkit_dom_element_remove_attribute ( + WEBKIT_DOM_ELEMENT (clone), "style"); + + element = e_html_editor_selection_get_indented_element ( + selection, document, final_width); + + webkit_dom_node_append_child ( + WEBKIT_DOM_NODE (element), + clone, + NULL); + + webkit_dom_node_replace_child ( + webkit_dom_node_get_parent_node (node), + WEBKIT_DOM_NODE (element), + node, + NULL); + + e_html_editor_selection_restore_caret_position (selection); + } else { + command = E_HTML_EDITOR_VIEW_COMMAND_INDENT; + e_html_editor_selection_save (selection); + e_html_editor_view_exec_command (view, command, NULL); + } + + e_html_editor_view_force_spell_check_for_current_paragraph (view); + + if (has_selection) + e_html_editor_selection_restore (selection); + + g_object_unref (view); + + g_object_notify (G_OBJECT (selection), "indented"); +} + +static const gchar * +get_css_alignment_value (EHTMLEditorSelectionAlignment alignment) +{ + if (alignment == E_HTML_EDITOR_SELECTION_ALIGNMENT_LEFT) + return ""; /* Left is by default on ltr */ + + if (alignment == E_HTML_EDITOR_SELECTION_ALIGNMENT_CENTER) + return "text-align: center;"; + + if (alignment == E_HTML_EDITOR_SELECTION_ALIGNMENT_RIGHT) + return "text-align: right;"; + + return ""; +} + +static void +unindent_list (EHTMLEditorSelection *selection, + WebKitDOMDocument *document) +{ + gboolean after = FALSE; + WebKitDOMElement *new_list; + WebKitDOMNode *source_list, *source_list_clone, *current_list, *item; + WebKitDOMElement *selection_start_marker; + WebKitDOMElement *selection_end_marker; + + selection_start_marker = webkit_dom_document_query_selector ( + document, "span#-x-evo-selection-start-marker", NULL); + selection_end_marker = webkit_dom_document_query_selector ( + document, "span#-x-evo-selection-end-marker", NULL); + + if (!selection_start_marker || !selection_end_marker) + return; + + /* Copy elements from previous block to list */ + item = webkit_dom_node_get_parent_node ( + WEBKIT_DOM_NODE (selection_start_marker)); + source_list = webkit_dom_node_get_parent_node (item); + new_list = WEBKIT_DOM_ELEMENT ( + webkit_dom_node_clone_node (source_list, FALSE)); + current_list = source_list; + source_list_clone = webkit_dom_node_clone_node (source_list, FALSE); + + webkit_dom_node_insert_before ( + webkit_dom_node_get_parent_node (source_list), + WEBKIT_DOM_NODE (source_list_clone), + webkit_dom_node_get_next_sibling (source_list), + NULL); + + if (element_has_class (WEBKIT_DOM_ELEMENT (source_list), "-x-evo-indented")) + element_add_class (WEBKIT_DOM_ELEMENT (new_list), "-x-evo-indented"); + + while (item) { + WebKitDOMNode *next_item = webkit_dom_node_get_next_sibling (item); + + if (WEBKIT_DOM_IS_HTMLLI_ELEMENT (item)) { + if (after) { + webkit_dom_node_append_child ( + source_list_clone, WEBKIT_DOM_NODE (item), NULL); + } else { + webkit_dom_node_insert_before ( + webkit_dom_node_get_parent_node (source_list), + item, + webkit_dom_node_get_next_sibling (source_list), + NULL); + } + } + + if (webkit_dom_node_contains (item, WEBKIT_DOM_NODE (selection_end_marker))) + after = TRUE; + + if (!next_item) { + if (after) + break; + + current_list = webkit_dom_node_get_next_sibling (current_list); + next_item = webkit_dom_node_get_first_child (current_list); + } + item = next_item; + } + + remove_node_if_empty (source_list_clone); + remove_node_if_empty (source_list); +} +/** + * e_html_editor_selection_unindent: + * @selection: an #EHTMLEditorSelection + * + * Unindents current paragraph by one level. + */ +void +e_html_editor_selection_unindent (EHTMLEditorSelection *selection) +{ + gboolean has_selection; + EHTMLEditorView *view; + EHTMLEditorViewCommand command; + + g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection)); + + view = e_html_editor_selection_ref_html_editor_view (selection); + g_return_if_fail (view != NULL); + + has_selection = g_strcmp0 (e_html_editor_selection_get_string (selection), "") != 0; + + if (!has_selection) { + EHTMLEditorSelectionAlignment alignment; + gboolean before_node = TRUE, reinsert_caret_position = FALSE; + const gchar *align_value; + gint word_wrap_length = selection->priv->word_wrap_length; + gint level, width; + WebKitDOMDocument *document; + WebKitDOMElement *element; + WebKitDOMElement *prev_blockquote = NULL, *next_blockquote = NULL; + WebKitDOMNode *node, *clone, *node_clone, *caret_node; + WebKitDOMRange *range; + + document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view)); + + e_html_editor_selection_save_caret_position (selection); + + alignment = e_html_editor_selection_get_alignment (selection); + align_value = get_css_alignment_value (alignment); + + range = html_editor_selection_get_current_range (selection); + if (!range) { + g_object_unref (view); + return; + } + + node = webkit_dom_range_get_end_container (range, NULL); + if (!WEBKIT_DOM_IS_ELEMENT (node)) + node = WEBKIT_DOM_NODE (webkit_dom_node_get_parent_element (node)); + + element = webkit_dom_node_get_parent_element (node); + + if (WEBKIT_DOM_IS_HTMLLI_ELEMENT (node)) { + e_html_editor_selection_save (selection); + e_html_editor_selection_clear_caret_position_marker (selection); + + unindent_list (selection, document); + e_html_editor_selection_restore (selection); + g_object_unref (view); + return; + } + + if (!WEBKIT_DOM_IS_HTML_QUOTE_ELEMENT (element)) + return; + + element_add_class (WEBKIT_DOM_ELEMENT (node), "-x-evo-to-unindent"); + + level = get_indentation_level (element); + width = word_wrap_length - SPACES_PER_INDENTATION * level; + clone = WEBKIT_DOM_NODE (webkit_dom_node_clone_node (WEBKIT_DOM_NODE (element), TRUE)); + + /* Look if we have previous siblings, if so, we have to + * create new blockquote that will include them */ + if (webkit_dom_node_get_previous_sibling (node)) + prev_blockquote = e_html_editor_selection_get_indented_element ( + selection, document, width); + + /* Look if we have next siblings, if so, we have to + * create new blockquote that will include them */ + if (webkit_dom_node_get_next_sibling (node)) + next_blockquote = e_html_editor_selection_get_indented_element ( + selection, document, width); + + /* Copy nodes that are before / after the element that we want to unindent */ + while (webkit_dom_node_has_child_nodes (clone)) { + WebKitDOMNode *child; + + child = webkit_dom_node_get_first_child (clone); + + if (is_caret_position_node (child)) { + reinsert_caret_position = TRUE; + caret_node = webkit_dom_node_clone_node (child, TRUE); + remove_node (child); + continue; + } + + if (webkit_dom_node_is_equal_node (child, node)) { + before_node = FALSE; + node_clone = webkit_dom_node_clone_node (child, TRUE); + remove_node (child); + continue; + } + + webkit_dom_node_append_child ( + before_node ? + WEBKIT_DOM_NODE (prev_blockquote) : + WEBKIT_DOM_NODE (next_blockquote), + child, + NULL); + + remove_node (child); + } + + element_remove_class (WEBKIT_DOM_ELEMENT (node_clone), "-x-evo-to-unindent"); + + /* Insert blockqoute with nodes that were before the element that we want to unindent */ + if (prev_blockquote) { + if (webkit_dom_node_has_child_nodes (WEBKIT_DOM_NODE (prev_blockquote))) { + webkit_dom_node_insert_before ( + webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (element)), + WEBKIT_DOM_NODE (prev_blockquote), + WEBKIT_DOM_NODE (element), + NULL); + } + } + + /* Reinsert the caret position */ + if (reinsert_caret_position) { + webkit_dom_node_insert_before ( + node_clone, + caret_node, + webkit_dom_node_get_first_child (node_clone), + NULL); + } + + if (level == 1 && element_has_class (WEBKIT_DOM_ELEMENT (node_clone), "-x-evo-paragraph")) + e_html_editor_selection_set_paragraph_style ( + selection, WEBKIT_DOM_ELEMENT (node_clone), word_wrap_length, 0, align_value); + + /* Insert the unindented element */ + webkit_dom_node_insert_before ( + webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (element)), + node_clone, + WEBKIT_DOM_NODE (element), + NULL); + + /* Insert blockqoute with nodes that were after the element that we want to unindent */ + if (next_blockquote) { + if (webkit_dom_node_has_child_nodes (WEBKIT_DOM_NODE (prev_blockquote))) { + webkit_dom_node_insert_before ( + webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (element)), + WEBKIT_DOM_NODE (next_blockquote), + WEBKIT_DOM_NODE (element), + NULL); + } + } + + /* Remove old blockquote */ + remove_node (WEBKIT_DOM_NODE (element)); + + e_html_editor_selection_restore_caret_position (selection); + } else { + command = E_HTML_EDITOR_VIEW_COMMAND_OUTDENT; + e_html_editor_selection_save (selection); + e_html_editor_view_exec_command (view, command, NULL); + } + + e_html_editor_view_force_spell_check_for_current_paragraph (view); + + if (has_selection) + e_html_editor_selection_restore (selection); + + g_object_unref (view); + + g_object_notify (G_OBJECT (selection), "indented"); +} + +/** + * e_html_editor_selection_is_bold: + * @selection: an #EHTMLEditorSelection + * + * Returns whether current selection or letter at current cursor position + * is bold. + * + * Returns @TRUE when selection is bold, @FALSE otherwise. + */ +gboolean +e_html_editor_selection_is_bold (EHTMLEditorSelection *selection) +{ + gboolean ret_val; + gchar *value, *text_content; + EHTMLEditorView *view; + WebKitDOMCSSStyleDeclaration *style; + WebKitDOMDocument *document; + WebKitDOMDOMWindow *window; + WebKitDOMNode *node; + WebKitDOMElement *element; + WebKitDOMRange *range; + + g_return_val_if_fail (E_IS_HTML_EDITOR_SELECTION (selection), FALSE); + + view = e_html_editor_selection_ref_html_editor_view (selection); + g_return_val_if_fail (view != NULL, FALSE); + + if (!e_html_editor_view_get_html_mode (view)) { + g_object_unref (view); + return FALSE; + } + + document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view)); + g_object_unref (view); + window = webkit_dom_document_get_default_view (document); + + range = html_editor_selection_get_current_range (selection); + if (!range) + return FALSE; + + node = webkit_dom_range_get_common_ancestor_container (range, NULL); + /* If we are changing the format of block we have to re-set bold property, + * otherwise it will be turned off because of no text in composer */ + text_content = webkit_dom_node_get_text_content (node); + if (g_strcmp0 (text_content, "") == 0) { + g_free (text_content); + return selection->priv->is_bold; + } + g_free (text_content); + + if (WEBKIT_DOM_IS_ELEMENT (node)) + element = WEBKIT_DOM_ELEMENT (node); + else + element = webkit_dom_node_get_parent_element (node); + + style = webkit_dom_dom_window_get_computed_style (window, element, NULL); + value = webkit_dom_css_style_declaration_get_property_value (style, "font-weight"); + + if (g_strstr_len (value, -1, "normal")) + ret_val = FALSE; + else + ret_val = TRUE; + + g_free (value); + return ret_val; +} + +/** + * e_html_editor_selection_set_bold: + * @selection: an #EHTMLEditorSelection + * @bold: @TRUE to enable bold, @FALSE to disable + * + * Toggles bold formatting of current selection or letter at current cursor + * position, depending on whether @bold is @TRUE or @FALSE. + */ +void +e_html_editor_selection_set_bold (EHTMLEditorSelection *selection, + gboolean bold) +{ + EHTMLEditorView *view; + EHTMLEditorViewCommand command; + + g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection)); + + if (e_html_editor_selection_is_bold (selection) == bold) + return; + + selection->priv->is_bold = bold; + + view = e_html_editor_selection_ref_html_editor_view (selection); + g_return_if_fail (view != NULL); + + command = E_HTML_EDITOR_VIEW_COMMAND_BOLD; + e_html_editor_view_exec_command (view, command, NULL); + + g_object_unref (view); + + g_object_notify (G_OBJECT (selection), "bold"); +} + +/** + * e_html_editor_selection_is_italic: + * @selection: an #EHTMLEditorSelection + * + * Returns whether current selection or letter at current cursor position + * is italic. + * + * Returns @TRUE when selection is italic, @FALSE otherwise. + */ +gboolean +e_html_editor_selection_is_italic (EHTMLEditorSelection *selection) +{ + gboolean ret_val; + gchar *value, *text_content; + EHTMLEditorView *view; + WebKitDOMCSSStyleDeclaration *style; + WebKitDOMDocument *document; + WebKitDOMDOMWindow *window; + WebKitDOMNode *node; + WebKitDOMElement *element; + WebKitDOMRange *range; + + g_return_val_if_fail (E_IS_HTML_EDITOR_SELECTION (selection), FALSE); + + view = e_html_editor_selection_ref_html_editor_view (selection); + g_return_val_if_fail (view != NULL, FALSE); + + if (!e_html_editor_view_get_html_mode (view)) { + g_object_unref (view); + return FALSE; + } + + document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view)); + g_object_unref (view); + window = webkit_dom_document_get_default_view (document); + + range = html_editor_selection_get_current_range (selection); + if (!range) + return FALSE; + + node = webkit_dom_range_get_common_ancestor_container (range, NULL); + /* If we are changing the format of block we have to re-set italic property, + * otherwise it will be turned off because of no text in composer */ + text_content = webkit_dom_node_get_text_content (node); + if (g_strcmp0 (text_content, "") == 0) { + g_free (text_content); + return selection->priv->is_italic; + } + g_free (text_content); + + if (WEBKIT_DOM_IS_ELEMENT (node)) + element = WEBKIT_DOM_ELEMENT (node); + else + element = webkit_dom_node_get_parent_element (node); + + style = webkit_dom_dom_window_get_computed_style (window, element, NULL); + value = webkit_dom_css_style_declaration_get_property_value (style, "font-style"); + + if (g_strstr_len (value, -1, "italic")) + ret_val = TRUE; + else + ret_val = FALSE; + + g_free (value); + return ret_val; +} + +/** + * e_html_editor_selection_set_italic: + * @selection: an #EHTMLEditorSelection + * @italic: @TRUE to enable italic, @FALSE to disable + * + * Toggles italic formatting of current selection or letter at current cursor + * position, depending on whether @italic is @TRUE or @FALSE. + */ +void +e_html_editor_selection_set_italic (EHTMLEditorSelection *selection, + gboolean italic) +{ + EHTMLEditorView *view; + EHTMLEditorViewCommand command; + + g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection)); + + if (e_html_editor_selection_is_italic (selection) == italic) + return; + + selection->priv->is_italic = italic; + + view = e_html_editor_selection_ref_html_editor_view (selection); + g_return_if_fail (view != NULL); + + command = E_HTML_EDITOR_VIEW_COMMAND_ITALIC; + e_html_editor_view_exec_command (view, command, NULL); + + g_object_unref (view); + + g_object_notify (G_OBJECT (selection), "italic"); +} + +static gboolean +is_monospaced_element (WebKitDOMElement *element) +{ + gchar *value; + gboolean ret_val = FALSE; + + if (!element) + return FALSE; + + if (!WEBKIT_DOM_IS_HTML_FONT_ELEMENT (element)) + return FALSE; + + value = webkit_dom_element_get_attribute (element, "face"); + + if (g_strcmp0 (value, "monospace") == 0) + ret_val = TRUE; + + g_free (value); + + return ret_val; +} + +/** + * e_html_editor_selection_is_monospaced: + * @selection: an #EHTMLEditorSelection + * + * Returns whether current selection or letter at current cursor position + * is monospaced. + * + * Returns @TRUE when selection is monospaced, @FALSE otherwise. + */ +gboolean +e_html_editor_selection_is_monospaced (EHTMLEditorSelection *selection) +{ + gboolean ret_val; + gchar *value, *text_content; + EHTMLEditorView *view; + WebKitDOMCSSStyleDeclaration *style; + WebKitDOMDocument *document; + WebKitDOMDOMWindow *window; + WebKitDOMNode *node; + WebKitDOMElement *element; + WebKitDOMRange *range; + + g_return_val_if_fail (E_IS_HTML_EDITOR_SELECTION (selection), FALSE); + + view = e_html_editor_selection_ref_html_editor_view (selection); + g_return_val_if_fail (view != NULL, FALSE); + + if (!e_html_editor_view_get_html_mode (view)) { + g_object_unref (view); + return FALSE; + } + + document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view)); + g_object_unref (view); + window = webkit_dom_document_get_default_view (document); + + range = html_editor_selection_get_current_range (selection); + if (!range) + return FALSE; + + node = webkit_dom_range_get_common_ancestor_container (range, NULL); + /* If we are changing the format of block we have to re-set italic property, + * otherwise it will be turned off because of no text in composer */ + text_content = webkit_dom_node_get_text_content (node); + if (g_strcmp0 (text_content, "") == 0) { + g_free (text_content); + return selection->priv->is_monospaced; + } + g_free (text_content); + + if (WEBKIT_DOM_IS_ELEMENT (node)) + element = WEBKIT_DOM_ELEMENT (node); + else + element = webkit_dom_node_get_parent_element (node); + + style = webkit_dom_dom_window_get_computed_style (window, element, NULL); + value = webkit_dom_css_style_declaration_get_property_value (style, "font-family"); + + if (g_strstr_len (value, -1, "monospace")) + ret_val = TRUE; + else + ret_val = FALSE; + + g_free (value); + return ret_val; +} + +static void +move_caret_into_element (WebKitDOMDocument *document, + WebKitDOMElement *element) +{ + WebKitDOMDOMWindow *window; + WebKitDOMDOMSelection *window_selection; + WebKitDOMRange *new_range; + + if (!element) + return; + + window = webkit_dom_document_get_default_view (document); + window_selection = webkit_dom_dom_window_get_selection (window); + new_range = webkit_dom_document_create_range (document); + + webkit_dom_range_select_node_contents ( + new_range, WEBKIT_DOM_NODE (element), NULL); + webkit_dom_range_collapse (new_range, FALSE, NULL); + webkit_dom_dom_selection_remove_all_ranges (window_selection); + webkit_dom_dom_selection_add_range (window_selection, new_range); +} + +/** + * e_html_editor_selection_set_monospaced: + * @selection: an #EHTMLEditorSelection + * @monospaced: @TRUE to enable monospaced, @FALSE to disable + * + * Toggles monospaced formatting of current selection or letter at current cursor + * position, depending on whether @monospaced is @TRUE or @FALSE. + */ +void +e_html_editor_selection_set_monospaced (EHTMLEditorSelection *selection, + gboolean monospaced) +{ + EHTMLEditorView *view; + WebKitWebView *web_view; + WebKitDOMDocument *document; + WebKitDOMRange *range; + WebKitDOMDOMWindow *window; + WebKitDOMDOMSelection *window_selection; + + g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection)); + + if (e_html_editor_selection_is_monospaced (selection) == monospaced) + return; + + selection->priv->is_monospaced = monospaced; + + range = html_editor_selection_get_current_range (selection); + if (!range) + return; + + view = e_html_editor_selection_ref_html_editor_view (selection); + g_return_if_fail (view != NULL); + + web_view = WEBKIT_WEB_VIEW (view); + + document = webkit_web_view_get_dom_document (web_view); + window = webkit_dom_document_get_default_view (document); + window_selection = webkit_dom_dom_window_get_selection (window); + + if (monospaced) { + gchar *font_size_str; + guint font_size; + WebKitDOMElement *monospace; + + monospace = webkit_dom_document_create_element ( + document, "font", NULL); + webkit_dom_element_set_attribute ( + monospace, "face", "monospace", NULL); + + font_size = selection->priv->font_size; + if (font_size == 0) + font_size = E_HTML_EDITOR_SELECTION_FONT_SIZE_NORMAL; + font_size_str = g_strdup_printf ("%d", font_size); + webkit_dom_element_set_attribute ( + monospace, "size", font_size_str, NULL); + g_free (font_size_str); + + if (g_strcmp0 (e_html_editor_selection_get_string (selection), "") != 0) { + gchar *html, *outer_html; + + webkit_dom_node_append_child ( + WEBKIT_DOM_NODE (monospace), + WEBKIT_DOM_NODE ( + webkit_dom_range_clone_contents (range, NULL)), + NULL); + + outer_html = webkit_dom_html_element_get_outer_html ( + WEBKIT_DOM_HTML_ELEMENT (monospace)); + + html = g_strconcat ( + /* Mark selection for restoration */ + "", + outer_html, + "", + NULL), + + e_html_editor_selection_insert_html (selection, html); + + e_html_editor_selection_restore (selection); + + g_free (html); + g_free (outer_html); + } else { + /* https://bugs.webkit.org/show_bug.cgi?id=15256 */ + webkit_dom_html_element_set_inner_html ( + WEBKIT_DOM_HTML_ELEMENT (monospace), + UNICODE_ZERO_WIDTH_SPACE, + NULL); + webkit_dom_range_insert_node ( + range, WEBKIT_DOM_NODE (monospace), NULL); + + move_caret_into_element (document, monospace); + } + } else { + gboolean is_bold, is_italic, is_underline, is_strikethrough; + guint font_size; + WebKitDOMElement *tt_element; + WebKitDOMNode *node; + + node = webkit_dom_range_get_end_container (range, NULL); + if (WEBKIT_DOM_IS_ELEMENT (node) && + is_monospaced_element (WEBKIT_DOM_ELEMENT (node))) { + tt_element = WEBKIT_DOM_ELEMENT (node); + } else { + tt_element = e_html_editor_dom_node_find_parent_element (node, "FONT"); + + if (!is_monospaced_element (tt_element)) { + g_object_unref (view); + return; + } + } + + /* Save current formatting */ + is_bold = selection->priv->is_bold; + is_italic = selection->priv->is_italic; + is_underline = selection->priv->is_underline; + is_strikethrough = selection->priv->is_strikethrough; + font_size = selection->priv->font_size; + if (font_size == 0) + font_size = E_HTML_EDITOR_SELECTION_FONT_SIZE_NORMAL; + + if (g_strcmp0 (e_html_editor_selection_get_string (selection), "") != 0) { + gchar *html, *outer_html, *inner_html, *beginning, *end; + gchar *start_position, *end_position, *font_size_str; + WebKitDOMElement *wrapper; + + wrapper = webkit_dom_document_create_element ( + document, "SPAN", NULL); + webkit_dom_element_set_id (wrapper, "-x-evo-remove-tt"); + webkit_dom_range_surround_contents ( + range, WEBKIT_DOM_NODE (wrapper), NULL); + + html = webkit_dom_html_element_get_outer_html ( + WEBKIT_DOM_HTML_ELEMENT (tt_element)); + + start_position = g_strstr_len ( + html, -1, ""); + + beginning = g_utf8_substring ( + html, 0, g_utf8_pointer_to_offset (html, start_position)); + inner_html = webkit_dom_html_element_get_inner_html ( + WEBKIT_DOM_HTML_ELEMENT (wrapper)); + end = g_utf8_substring ( + html, + g_utf8_pointer_to_offset (html, end_position) + 7, + g_utf8_strlen (html, -1)), + + font_size_str = g_strdup_printf ("%d", font_size); + + outer_html = + g_strconcat ( + /* Beginning */ + beginning, + /* End the previous FONT tag */ + "", + /* Mark selection for restoration */ + "", + /* Inside will be the same */ + inner_html, + "", + /* Start the new FONT element */ + "", + /* End - we have to start after */ + end, + NULL), + + g_free (font_size_str); + + webkit_dom_html_element_set_outer_html ( + WEBKIT_DOM_HTML_ELEMENT (tt_element), + outer_html, + NULL); + + e_html_editor_selection_restore (selection); + + g_free (html); + g_free (outer_html); + g_free (inner_html); + g_free (beginning); + g_free (end); + } else { + WebKitDOMRange *new_range; + gchar *outer_html; + gchar *tmp; + + webkit_dom_element_set_id (tt_element, "ev-tt"); + + outer_html = webkit_dom_html_element_get_outer_html ( + WEBKIT_DOM_HTML_ELEMENT (tt_element)); + tmp = g_strconcat (outer_html, UNICODE_ZERO_WIDTH_SPACE, NULL); + webkit_dom_html_element_set_outer_html ( + WEBKIT_DOM_HTML_ELEMENT (tt_element), + tmp, NULL); + + /* We need to get that element again */ + tt_element = webkit_dom_document_get_element_by_id ( + document, "ev-tt"); + webkit_dom_element_remove_attribute ( + WEBKIT_DOM_ELEMENT (tt_element), "id"); + + new_range = webkit_dom_document_create_range (document); + webkit_dom_range_set_start_after ( + new_range, WEBKIT_DOM_NODE (tt_element), NULL); + webkit_dom_range_set_end_after ( + new_range, WEBKIT_DOM_NODE (tt_element), NULL); + + webkit_dom_dom_selection_remove_all_ranges ( + window_selection); + webkit_dom_dom_selection_add_range ( + window_selection, new_range); + + webkit_dom_dom_selection_modify ( + window_selection, "move", "right", "character"); + + g_free (outer_html); + g_free (tmp); + + e_html_editor_view_force_spell_check_for_current_paragraph ( + view); + } + + /* Re-set formatting */ + if (is_bold) + e_html_editor_selection_set_bold (selection, TRUE); + if (is_italic) + e_html_editor_selection_set_italic (selection, TRUE); + if (is_underline) + e_html_editor_selection_set_underline (selection, TRUE); + if (is_strikethrough) + e_html_editor_selection_set_strikethrough (selection, TRUE); + + e_html_editor_selection_set_font_size (selection, font_size); + } + + g_object_unref (view); + + g_object_notify (G_OBJECT (selection), "monospaced"); +} + +/** + * e_html_editor_selection_is_strikethrough: + * @selection: an #EHTMLEditorSelection + * + * Returns whether current selection or letter at current cursor position + * is striked through. + * + * Returns @TRUE when selection is striked through, @FALSE otherwise. + */ +gboolean +e_html_editor_selection_is_strikethrough (EHTMLEditorSelection *selection) +{ + gboolean ret_val; + gchar *value, *text_content; + EHTMLEditorView *view; + WebKitDOMCSSStyleDeclaration *style; + WebKitDOMDocument *document; + WebKitDOMDOMWindow *window; + WebKitDOMNode *node; + WebKitDOMElement *element; + WebKitDOMRange *range; + + g_return_val_if_fail (E_IS_HTML_EDITOR_SELECTION (selection), FALSE); + + view = e_html_editor_selection_ref_html_editor_view (selection); + g_return_val_if_fail (view != NULL, FALSE); + + if (!e_html_editor_view_get_html_mode (view)) { + g_object_unref (view); + return FALSE; + } + + document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view)); + g_object_unref (view); + window = webkit_dom_document_get_default_view (document); + + range = html_editor_selection_get_current_range (selection); + if (!range) + return FALSE; + + node = webkit_dom_range_get_common_ancestor_container (range, NULL); + /* If we are changing the format of block we have to re-set strikethrough property, + * otherwise it will be turned off because of no text in composer */ + text_content = webkit_dom_node_get_text_content (node); + if (g_strcmp0 (text_content, "") == 0) { + g_free (text_content); + return selection->priv->is_strikethrough; + } + g_free (text_content); + + if (WEBKIT_DOM_IS_ELEMENT (node)) + element = WEBKIT_DOM_ELEMENT (node); + else + element = webkit_dom_node_get_parent_element (node); + + style = webkit_dom_dom_window_get_computed_style (window, element, NULL); + value = webkit_dom_css_style_declaration_get_property_value (style, "text-decoration"); + + if (g_strstr_len (value, -1, "line-through")) + ret_val = TRUE; + else + ret_val = get_has_style (selection, "strike"); + + g_free (value); + return ret_val; +} + +/** + * e_html_editor_selection_set_strikethrough: + * @selection: an #EHTMLEditorSelection + * @strikethrough: @TRUE to enable strikethrough, @FALSE to disable + * + * Toggles strike through formatting of current selection or letter at current + * cursor position, depending on whether @strikethrough is @TRUE or @FALSE. + */ +void +e_html_editor_selection_set_strikethrough (EHTMLEditorSelection *selection, + gboolean strikethrough) +{ + EHTMLEditorView *view; + EHTMLEditorViewCommand command; + + g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection)); + + if (e_html_editor_selection_is_strikethrough (selection) == strikethrough) + return; + + selection->priv->is_strikethrough = strikethrough; + + view = e_html_editor_selection_ref_html_editor_view (selection); + g_return_if_fail (view != NULL); + + command = E_HTML_EDITOR_VIEW_COMMAND_STRIKETHROUGH; + e_html_editor_view_exec_command (view, command, NULL); + + g_object_unref (view); + + g_object_notify (G_OBJECT (selection), "strikethrough"); +} + +/** + * e_html_editor_selection_is_subscript: + * @selection: an #EHTMLEditorSelection + * + * Returns whether current selection or letter at current cursor position + * is in subscript. + * + * Returns @TRUE when selection is in subscript, @FALSE otherwise. + */ +gboolean +e_html_editor_selection_is_subscript (EHTMLEditorSelection *selection) +{ + EHTMLEditorView *view; + WebKitDOMNode *node; + WebKitDOMRange *range; + + g_return_val_if_fail (E_IS_HTML_EDITOR_SELECTION (selection), FALSE); + + view = e_html_editor_selection_ref_html_editor_view (selection); + g_return_val_if_fail (view != NULL, FALSE); + + if (!e_html_editor_view_get_html_mode (view)) { + g_object_unref (view); + return FALSE; + } + + g_object_unref (view); + + range = html_editor_selection_get_current_range (selection); + node = webkit_dom_range_get_common_ancestor_container (range, NULL); + + while (node) { + gchar *tag_name; + + tag_name = webkit_dom_element_get_tag_name (WEBKIT_DOM_ELEMENT (node)); + + if (g_ascii_strncasecmp (tag_name, "sub", 3) == 0) { + g_free (tag_name); + break; + } + + g_free (tag_name); + node = webkit_dom_node_get_parent_node (node); + } + + return (node != NULL); +} + +/** + * e_html_editor_selection_set_subscript: + * @selection: an #EHTMLEditorSelection + * @subscript: @TRUE to enable subscript, @FALSE to disable + * + * Toggles subscript of current selection or letter at current cursor position, + * depending on whether @subscript is @TRUE or @FALSE. + */ +void +e_html_editor_selection_set_subscript (EHTMLEditorSelection *selection, + gboolean subscript) +{ + EHTMLEditorView *view; + EHTMLEditorViewCommand command; + + g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection)); + + if (e_html_editor_selection_is_subscript (selection) == subscript) + return; + + view = e_html_editor_selection_ref_html_editor_view (selection); + g_return_if_fail (view != NULL); + + command = E_HTML_EDITOR_VIEW_COMMAND_SUBSCRIPT; + e_html_editor_view_exec_command (view, command, NULL); + + g_object_unref (view); + + g_object_notify (G_OBJECT (selection), "subscript"); +} + +/** + * e_html_editor_selection_is_superscript: + * @selection: an #EHTMLEditorSelection + * + * Returns whether current selection or letter at current cursor position + * is in superscript. + * + * Returns @TRUE when selection is in superscript, @FALSE otherwise. + */ +gboolean +e_html_editor_selection_is_superscript (EHTMLEditorSelection *selection) +{ + EHTMLEditorView *view; + WebKitDOMNode *node; + WebKitDOMRange *range; + + g_return_val_if_fail (E_IS_HTML_EDITOR_SELECTION (selection), FALSE); + + view = e_html_editor_selection_ref_html_editor_view (selection); + g_return_val_if_fail (view != NULL, FALSE); + + if (!e_html_editor_view_get_html_mode (view)) { + g_object_unref (view); + return FALSE; + } + + g_object_unref (view); + + range = html_editor_selection_get_current_range (selection); + node = webkit_dom_range_get_common_ancestor_container (range, NULL); + + while (node) { + gchar *tag_name; + + tag_name = webkit_dom_element_get_tag_name (WEBKIT_DOM_ELEMENT (node)); + + if (g_ascii_strncasecmp (tag_name, "sup", 3) == 0) { + g_free (tag_name); + break; + } + + g_free (tag_name); + node = webkit_dom_node_get_parent_node (node); + } + + return (node != NULL); +} + +/** + * e_html_editor_selection_set_superscript: + * @selection: an #EHTMLEditorSelection + * @superscript: @TRUE to enable superscript, @FALSE to disable + * + * Toggles superscript of current selection or letter at current cursor position, + * depending on whether @superscript is @TRUE or @FALSE. + */ +void +e_html_editor_selection_set_superscript (EHTMLEditorSelection *selection, + gboolean superscript) +{ + EHTMLEditorView *view; + EHTMLEditorViewCommand command; + + g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection)); + + if (e_html_editor_selection_is_superscript (selection) == superscript) + return; + + view = e_html_editor_selection_ref_html_editor_view (selection); + g_return_if_fail (view != NULL); + + command = E_HTML_EDITOR_VIEW_COMMAND_SUPERSCRIPT; + e_html_editor_view_exec_command (view, command, NULL); + + g_object_unref (view); + + g_object_notify (G_OBJECT (selection), "superscript"); +} + +/** + * e_html_editor_selection_is_underline: + * @selection: an #EHTMLEditorSelection + * + * Returns whether current selection or letter at current cursor position + * is underlined. + * + * Returns @TRUE when selection is underlined, @FALSE otherwise. + */ +gboolean +e_html_editor_selection_is_underline (EHTMLEditorSelection *selection) +{ + gboolean ret_val; + gchar *value, *text_content; + EHTMLEditorView *view; + WebKitDOMCSSStyleDeclaration *style; + WebKitDOMDocument *document; + WebKitDOMDOMWindow *window; + WebKitDOMNode *node; + WebKitDOMElement *element; + WebKitDOMRange *range; + + g_return_val_if_fail (E_IS_HTML_EDITOR_SELECTION (selection), FALSE); + + view = e_html_editor_selection_ref_html_editor_view (selection); + g_return_val_if_fail (view != NULL, FALSE); + + if (!e_html_editor_view_get_html_mode (view)) { + g_object_unref (view); + return FALSE; + } + + document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view)); + g_object_unref (view); + window = webkit_dom_document_get_default_view (document); + + range = html_editor_selection_get_current_range (selection); + if (!range) + return FALSE; + + node = webkit_dom_range_get_common_ancestor_container (range, NULL); + /* If we are changing the format of block we have to re-set underline property, + * otherwise it will be turned off because of no text in composer */ + text_content = webkit_dom_node_get_text_content (node); + if (g_strcmp0 (text_content, "") == 0) { + g_free (text_content); + return selection->priv->is_underline; + } + g_free (text_content); + + if (WEBKIT_DOM_IS_ELEMENT (node)) + element = WEBKIT_DOM_ELEMENT (node); + else + element = webkit_dom_node_get_parent_element (node); + + style = webkit_dom_dom_window_get_computed_style (window, element, NULL); + value = webkit_dom_css_style_declaration_get_property_value (style, "text-decoration"); + + if (g_strstr_len (value, -1, "underline")) + ret_val = TRUE; + else + ret_val = get_has_style (selection, "u"); + + g_free (value); + return ret_val; +} + +/** + * e_html_editor_selection_set_underline: + * @selection: an #EHTMLEditorSelection + * @underline: @TRUE to enable underline, @FALSE to disable + * + * Toggles underline formatting of current selection or letter at current + * cursor position, depending on whether @underline is @TRUE or @FALSE. + */ +void +e_html_editor_selection_set_underline (EHTMLEditorSelection *selection, + gboolean underline) +{ + EHTMLEditorView *view; + EHTMLEditorViewCommand command; + + g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection)); + + if (e_html_editor_selection_is_underline (selection) == underline) + return; + + selection->priv->is_underline = underline; + + view = e_html_editor_selection_ref_html_editor_view (selection); + g_return_if_fail (view != NULL); + + command = E_HTML_EDITOR_VIEW_COMMAND_UNDERLINE; + e_html_editor_view_exec_command (view, command, NULL); + + g_object_unref (view); + + g_object_notify (G_OBJECT (selection), "underline"); +} + +/** + * e_html_editor_selection_unlink: + * @selection: an #EHTMLEditorSelection + * + * Removes any links (<A> elements) from current selection or at current + * cursor position. + */ +void +e_html_editor_selection_unlink (EHTMLEditorSelection *selection) +{ + EHTMLEditorView *view; + EHTMLEditorViewCommand command; + WebKitDOMDocument *document; + WebKitDOMDOMWindow *window; + WebKitDOMDOMSelection *dom_selection; + WebKitDOMRange *range; + WebKitDOMElement *link; + + g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection)); + + view = e_html_editor_selection_ref_html_editor_view (selection); + g_return_if_fail (view != NULL); + + document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view)); + window = webkit_dom_document_get_default_view (document); + dom_selection = webkit_dom_dom_window_get_selection (window); + + range = webkit_dom_dom_selection_get_range_at (dom_selection, 0, NULL); + link = e_html_editor_dom_node_find_parent_element ( + webkit_dom_range_get_start_container (range, NULL), "A"); + + if (!link) { + gchar *text; + /* get element that was clicked on */ + link = e_html_editor_view_get_element_under_mouse_click (view); + if (!WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT (link)) + link = NULL; + + text = webkit_dom_html_element_get_inner_text ( + WEBKIT_DOM_HTML_ELEMENT (link)); + webkit_dom_html_element_set_outer_html (WEBKIT_DOM_HTML_ELEMENT (link), text, NULL); + g_free (text); + } else { + command = E_HTML_EDITOR_VIEW_COMMAND_UNLINK; + e_html_editor_view_exec_command (view, command, NULL); + } + g_object_unref (view); +} + +/** + * e_html_editor_selection_create_link: + * @selection: an #EHTMLEditorSelection + * @uri: destination of the new link + * + * Converts current selection into a link pointing to @url. + */ +void +e_html_editor_selection_create_link (EHTMLEditorSelection *selection, + const gchar *uri) +{ + EHTMLEditorView *view; + EHTMLEditorViewCommand command; + + g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection)); + g_return_if_fail (uri != NULL && *uri != '\0'); + + view = e_html_editor_selection_ref_html_editor_view (selection); + g_return_if_fail (view != NULL); + + command = E_HTML_EDITOR_VIEW_COMMAND_CREATE_LINK; + e_html_editor_view_exec_command (view, command, uri); + + g_object_unref (view); +} + +/** + * e_html_editor_selection_insert_text: + * @selection: an #EHTMLEditorSelection + * @plain_text: text to insert + * + * Inserts @plain_text at current cursor position. When a text range is selected, + * it will be replaced by @plain_text. + */ +void +e_html_editor_selection_insert_text (EHTMLEditorSelection *selection, + const gchar *plain_text) +{ + EHTMLEditorView *view; + EHTMLEditorViewCommand command; + + g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection)); + g_return_if_fail (plain_text != NULL); + + view = e_html_editor_selection_ref_html_editor_view (selection); + g_return_if_fail (view != NULL); + + command = E_HTML_EDITOR_VIEW_COMMAND_INSERT_TEXT; + e_html_editor_view_exec_command (view, command, plain_text); + + g_object_unref (view); +} + +/** + * e_html_editor_selection_insert_html: + * @selection: an #EHTMLEditorSelection + * @html_text: an HTML code to insert + * + * Insert @html_text into document at current cursor position. When a text range + * is selected, it will be replaced by @html_text. + */ +void +e_html_editor_selection_insert_html (EHTMLEditorSelection *selection, + const gchar *html_text) +{ + EHTMLEditorView *view; + EHTMLEditorViewCommand command; + + g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection)); + g_return_if_fail (html_text != NULL); + + view = e_html_editor_selection_ref_html_editor_view (selection); + g_return_if_fail (view != NULL); + + command = E_HTML_EDITOR_VIEW_COMMAND_INSERT_HTML; + if (e_html_editor_view_get_html_mode (view)) { + e_html_editor_view_exec_command (view, command, html_text); + } else { + e_html_editor_view_convert_and_insert_html_to_plain_text ( + view, html_text); + } + + g_object_unref (view); +} + + +/************************* image_load_and_insert_async() *************************/ + +typedef struct _LoadContext LoadContext; + +struct _LoadContext { + EHTMLEditorSelection *selection; + WebKitDOMElement *element; + GInputStream *input_stream; + GOutputStream *output_stream; + GFile *file; + GFileInfo *file_info; + goffset total_num_bytes; + gssize bytes_read; + const gchar *content_type; + const gchar *filename; + gchar buffer[4096]; +}; + +/* Forward Declaration */ +static void +image_load_stream_read_cb (GInputStream *input_stream, + GAsyncResult *result, + LoadContext *load_context); + +static LoadContext * +image_load_context_new (EHTMLEditorSelection *selection) +{ + LoadContext *load_context; + + load_context = g_slice_new0 (LoadContext); + load_context->selection = selection; + + return load_context; +} + +static void +image_load_context_free (LoadContext *load_context) +{ + if (load_context->input_stream != NULL) + g_object_unref (load_context->input_stream); + + if (load_context->output_stream != NULL) + g_object_unref (load_context->output_stream); + + if (load_context->file_info != NULL) + g_object_unref (load_context->file_info); + + if (load_context->file != NULL) + g_object_unref (load_context->file); + + g_slice_free (LoadContext, load_context); +} + +static void +replace_base64_image_src (EHTMLEditorSelection *selection, + WebKitDOMElement *element, + const gchar *base64_content, + const gchar *filename, + const gchar *uri) +{ + EHTMLEditorView *view; + + view = e_html_editor_selection_ref_html_editor_view (selection); + g_return_if_fail (view != NULL); + + e_html_editor_view_set_changed (view, TRUE); + g_object_unref (view); + + webkit_dom_html_image_element_set_src ( + WEBKIT_DOM_HTML_IMAGE_ELEMENT (element), + base64_content); + webkit_dom_element_set_attribute ( + WEBKIT_DOM_ELEMENT (element), "data-uri", uri, NULL); + webkit_dom_element_set_attribute ( + WEBKIT_DOM_ELEMENT (element), "data-inline", "", NULL); + webkit_dom_element_set_attribute ( + WEBKIT_DOM_ELEMENT (element), "data-name", + filename ? filename : "", NULL); +} + +static void +insert_base64_image (EHTMLEditorSelection *selection, + const gchar *base64_content, + const gchar *filename, + const gchar *uri) +{ + EHTMLEditorView *view; + WebKitDOMDocument *document; + WebKitDOMElement *element, *caret_position, *resizable_wrapper; + WebKitDOMText *text; + + caret_position = e_html_editor_selection_save_caret_position (selection); + + view = e_html_editor_selection_ref_html_editor_view (selection); + g_return_if_fail (view != NULL); + + document = webkit_web_view_get_dom_document ( + WEBKIT_WEB_VIEW (view)); + + e_html_editor_view_set_changed (view, TRUE); + g_object_unref (view); + + resizable_wrapper = + webkit_dom_document_create_element (document, "span", NULL); + webkit_dom_element_set_attribute ( + resizable_wrapper, "class", "-x-evo-resizable-wrapper", NULL); + + element = webkit_dom_document_create_element (document, "img", NULL); + webkit_dom_html_image_element_set_src ( + WEBKIT_DOM_HTML_IMAGE_ELEMENT (element), + base64_content); + webkit_dom_element_set_attribute ( + WEBKIT_DOM_ELEMENT (element), "data-uri", uri, NULL); + webkit_dom_element_set_attribute ( + WEBKIT_DOM_ELEMENT (element), "data-inline", "", NULL); + webkit_dom_element_set_attribute ( + WEBKIT_DOM_ELEMENT (element), "data-name", + filename ? filename : "", NULL); + webkit_dom_node_append_child ( + WEBKIT_DOM_NODE (resizable_wrapper), + WEBKIT_DOM_NODE (element), + NULL); + + webkit_dom_node_insert_before ( + webkit_dom_node_get_parent_node ( + WEBKIT_DOM_NODE (caret_position)), + WEBKIT_DOM_NODE (resizable_wrapper), + WEBKIT_DOM_NODE (caret_position), + NULL); + + /* We have to again use UNICODE_ZERO_WIDTH_SPACE character to restore + * caret on right position */ + text = webkit_dom_document_create_text_node ( + document, UNICODE_ZERO_WIDTH_SPACE); + + webkit_dom_node_insert_before ( + webkit_dom_node_get_parent_node ( + WEBKIT_DOM_NODE (caret_position)), + WEBKIT_DOM_NODE (text), + WEBKIT_DOM_NODE (caret_position), + NULL); + + e_html_editor_selection_restore_caret_position (selection); +} + +static void +image_load_finish (LoadContext *load_context) +{ + EHTMLEditorSelection *selection; + GMemoryOutputStream *output_stream; + gchar *base64_encoded, *mime_type, *output, *uri; + gsize size; + gpointer data; + + output_stream = G_MEMORY_OUTPUT_STREAM (load_context->output_stream); + + selection = load_context->selection; + + mime_type = g_content_type_get_mime_type (load_context->content_type); + + data = g_memory_output_stream_get_data (output_stream); + size = g_memory_output_stream_get_data_size (output_stream); + uri = g_file_get_uri (load_context->file); + + base64_encoded = g_base64_encode ((const guchar *) data, size); + output = g_strconcat ("data:", mime_type, ";base64,", base64_encoded, NULL); + if (load_context->element) + replace_base64_image_src ( + selection, load_context->element, output, load_context->filename, uri); + else + insert_base64_image (selection, output, load_context->filename, uri); + + g_free (base64_encoded); + g_free (output); + g_free (mime_type); + g_free (uri); + + image_load_context_free (load_context); +} + +static void +image_load_write_cb (GOutputStream *output_stream, + GAsyncResult *result, + LoadContext *load_context) +{ + GInputStream *input_stream; + gssize bytes_written; + GError *error = NULL; + + bytes_written = g_output_stream_write_finish ( + output_stream, result, &error); + + if (error) { + image_load_context_free (load_context); + return; + } + + input_stream = load_context->input_stream; + + if (bytes_written < load_context->bytes_read) { + g_memmove ( + load_context->buffer, + load_context->buffer + bytes_written, + load_context->bytes_read - bytes_written); + load_context->bytes_read -= bytes_written; + + g_output_stream_write_async ( + output_stream, + load_context->buffer, + load_context->bytes_read, + G_PRIORITY_DEFAULT, NULL, + (GAsyncReadyCallback) image_load_write_cb, + load_context); + } else + g_input_stream_read_async ( + input_stream, + load_context->buffer, + sizeof (load_context->buffer), + G_PRIORITY_DEFAULT, NULL, + (GAsyncReadyCallback) image_load_stream_read_cb, + load_context); +} + +static void +image_load_stream_read_cb (GInputStream *input_stream, + GAsyncResult *result, + LoadContext *load_context) +{ + GOutputStream *output_stream; + gssize bytes_read; + GError *error = NULL; + + bytes_read = g_input_stream_read_finish ( + input_stream, result, &error); + + if (error) { + image_load_context_free (load_context); + return; + } + + if (bytes_read == 0) { + image_load_finish (load_context); + return; + } + + output_stream = load_context->output_stream; + load_context->bytes_read = bytes_read; + + g_output_stream_write_async ( + output_stream, + load_context->buffer, + load_context->bytes_read, + G_PRIORITY_DEFAULT, NULL, + (GAsyncReadyCallback) image_load_write_cb, + load_context); +} + +static void +image_load_file_read_cb (GFile *file, + GAsyncResult *result, + LoadContext *load_context) +{ + GFileInputStream *input_stream; + GOutputStream *output_stream; + GError *error = NULL; + + /* Input stream might be NULL, so don't use cast macro. */ + input_stream = g_file_read_finish (file, result, &error); + load_context->input_stream = (GInputStream *) input_stream; + + if (error) { + image_load_context_free (load_context); + return; + } + + /* Load the contents into a GMemoryOutputStream. */ + output_stream = g_memory_output_stream_new ( + NULL, 0, g_realloc, g_free); + + load_context->output_stream = output_stream; + + g_input_stream_read_async ( + load_context->input_stream, + load_context->buffer, + sizeof (load_context->buffer), + G_PRIORITY_DEFAULT, NULL, + (GAsyncReadyCallback) image_load_stream_read_cb, + load_context); +} + +static void +image_load_query_info_cb (GFile *file, + GAsyncResult *result, + LoadContext *load_context) +{ + GFileInfo *file_info; + GError *error = NULL; + + file_info = g_file_query_info_finish (file, result, &error); + if (error) { + image_load_context_free (load_context); + return; + } + + load_context->content_type = g_file_info_get_content_type (file_info); + load_context->total_num_bytes = g_file_info_get_size (file_info); + load_context->filename = g_file_info_get_name (file_info); + + g_file_read_async ( + file, G_PRIORITY_DEFAULT, + NULL, (GAsyncReadyCallback) + image_load_file_read_cb, load_context); +} + +static void +image_load_and_insert_async (EHTMLEditorSelection *selection, + WebKitDOMElement *element, + const gchar *uri) +{ + LoadContext *load_context; + GFile *file; + + g_return_if_fail (uri && *uri); + + file = g_file_new_for_uri (uri); + g_return_if_fail (file != NULL); + + load_context = image_load_context_new (selection); + load_context->file = file; + load_context->element = element; + + g_file_query_info_async ( + file, "standard::*", + G_FILE_QUERY_INFO_NONE,G_PRIORITY_DEFAULT, + NULL, (GAsyncReadyCallback) + image_load_query_info_cb, load_context); +} + +/** + * e_html_editor_selection_insert_image: + * @selection: an #EHTMLEditorSelection + * @image_uri: an URI of the source image + * + * Inserts image at current cursor position using @image_uri as source. When a + * text range is selected, it will be replaced by the image. + */ +void +e_html_editor_selection_insert_image (EHTMLEditorSelection *selection, + const gchar *image_uri) +{ + g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection)); + g_return_if_fail (image_uri != NULL); + + if (is_in_html_mode (selection)) { + if (strstr (image_uri, ";base64,")) { + if (g_str_has_prefix (image_uri, "data:")) + insert_base64_image (selection, image_uri, "", ""); + if (strstr (image_uri, ";data")) { + const gchar *base64_data = strstr (image_uri, ";") + 1; + gchar *filename; + glong filename_length; + + filename_length = + g_utf8_strlen (image_uri, -1) - + g_utf8_strlen (base64_data, -1) - 1; + filename = g_strndup (image_uri, filename_length); + + insert_base64_image (selection, base64_data, filename, ""); + g_free (filename); + } + } else + image_load_and_insert_async (selection, NULL, image_uri); + } +} + +/** + * e_html_editor_selection_replace_image_src: + * @selection: an #EHTMLEditorSelection + * @image: #WebKitDOMElement representation of image + * @image_uri: an URI of the source image + * + * Replace the src attribute of the given @image with @image_uri. + */ +void +e_html_editor_selection_replace_image_src (EHTMLEditorSelection *selection, + WebKitDOMElement *image, + const gchar *image_uri) +{ + g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection)); + g_return_if_fail (image_uri != NULL); + g_return_if_fail (WEBKIT_DOM_IS_HTML_IMAGE_ELEMENT (image)); + + if (strstr (image_uri, ";base64,")) { + if (g_str_has_prefix (image_uri, "data:")) + replace_base64_image_src ( + selection, image, image_uri, "", ""); + if (strstr (image_uri, ";data")) { + const gchar *base64_data = strstr (image_uri, ";") + 1; + gchar *filename; + glong filename_length; + + filename_length = + g_utf8_strlen (image_uri, -1) - + g_utf8_strlen (base64_data, -1) - 1; + filename = g_strndup (image_uri, filename_length); + + replace_base64_image_src ( + selection, image, base64_data, filename, ""); + g_free (filename); + } + } else + image_load_and_insert_async (selection, image, image_uri); +} + +/** + * e_html_editor_selection_clear_caret_position_marker: + * @selection: an #EHTMLEditorSelection + * + * Removes previously set caret position marker from composer. + */ +void +e_html_editor_selection_clear_caret_position_marker (EHTMLEditorSelection *selection) +{ + EHTMLEditorView *view; + WebKitDOMDocument *document; + WebKitDOMElement *element; + + g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection)); + + view = e_html_editor_selection_ref_html_editor_view (selection); + g_return_if_fail (view != NULL); + + document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view)); + + element = webkit_dom_document_get_element_by_id (document, "-x-evo-caret-position"); + + if (element) + remove_node (WEBKIT_DOM_NODE (element)); + + g_object_unref (view); +} + +WebKitDOMNode * +e_html_editor_selection_get_caret_position_node (WebKitDOMDocument *document) +{ + WebKitDOMElement *element; + + element = webkit_dom_document_create_element (document, "SPAN", NULL); + webkit_dom_element_set_id (element, "-x-evo-caret-position"); + webkit_dom_element_set_attribute ( + element, "style", "color: red", NULL); + webkit_dom_html_element_set_inner_html ( + WEBKIT_DOM_HTML_ELEMENT (element), "*", NULL); + + return WEBKIT_DOM_NODE (element); +} + +/** + * e_html_editor_selection_save_caret_position: + * @selection: an #EHTMLEditorSelection + * + * Saves current caret position in composer. + * + * Returns: #WebKitDOMElement that was created on caret position + */ +WebKitDOMElement * +e_html_editor_selection_save_caret_position (EHTMLEditorSelection *selection) +{ + EHTMLEditorView *view; + WebKitDOMDocument *document; + WebKitDOMNode *split_node; + WebKitDOMNode *start_offset_node; + WebKitDOMNode *caret_node; + WebKitDOMRange *range; + gulong start_offset; + + g_return_val_if_fail (E_IS_HTML_EDITOR_SELECTION (selection), NULL); + + view = e_html_editor_selection_ref_html_editor_view (selection); + g_return_val_if_fail (view != NULL, NULL); + + document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view)); + g_object_unref (view); + + e_html_editor_selection_clear_caret_position_marker (selection); + + range = html_editor_selection_get_current_range (selection); + if (!range) + return NULL; + + start_offset = webkit_dom_range_get_start_offset (range, NULL); + start_offset_node = webkit_dom_range_get_end_container (range, NULL); + + caret_node = e_html_editor_selection_get_caret_position_node (document); + + if (WEBKIT_DOM_IS_TEXT (start_offset_node) && start_offset != 0) { + WebKitDOMText *split_text; + + split_text = webkit_dom_text_split_text ( + WEBKIT_DOM_TEXT (start_offset_node), + start_offset, NULL); + split_node = WEBKIT_DOM_NODE (split_text); + } else { + split_node = start_offset_node; + } + + webkit_dom_node_insert_before ( + webkit_dom_node_get_parent_node (start_offset_node), + caret_node, + split_node, + NULL); + + return WEBKIT_DOM_ELEMENT (caret_node); +} + +static void +fix_quoting_nodes_after_caret_restoration (WebKitDOMDOMSelection *window_selection, + WebKitDOMNode *prev_sibling, + WebKitDOMNode *next_sibling) +{ + WebKitDOMNode *tmp_node; + + if (!element_has_class (WEBKIT_DOM_ELEMENT (prev_sibling), "-x-evo-temp-text-wrapper")) + return; + + webkit_dom_dom_selection_modify ( + window_selection, "move", "forward", "character"); + tmp_node = webkit_dom_node_get_next_sibling ( + webkit_dom_node_get_first_child (prev_sibling)); + + webkit_dom_node_insert_before ( + webkit_dom_node_get_parent_node (prev_sibling), + tmp_node, + next_sibling, + NULL); + + tmp_node = webkit_dom_node_get_first_child (prev_sibling); + + webkit_dom_node_insert_before ( + webkit_dom_node_get_parent_node (prev_sibling), + tmp_node, + webkit_dom_node_get_previous_sibling (next_sibling), + NULL); + + remove_node (prev_sibling); + + webkit_dom_dom_selection_modify ( + window_selection, "move", "backward", "character"); +} + +/** + * e_html_editor_selection_restore_caret_position: + * @selection: an #EHTMLEditorSelection + * + * Restores previously saved caret position in composer. + */ +void +e_html_editor_selection_restore_caret_position (EHTMLEditorSelection *selection) +{ + EHTMLEditorView *view; + WebKitDOMDocument *document; + WebKitDOMElement *element; + gboolean fix_after_quoting; + gboolean swap_direction = FALSE; + + g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection)); + + view = e_html_editor_selection_ref_html_editor_view (selection); + g_return_if_fail (view != NULL); + + document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view)); + g_object_unref (view); + + element = webkit_dom_document_get_element_by_id ( + document, "-x-evo-caret-position"); + fix_after_quoting = element_has_class (element, "-x-evo-caret-quoting"); + + if (element) { + WebKitDOMDOMWindow *window; + WebKitDOMNode *parent_node; + WebKitDOMDOMSelection *window_selection; + WebKitDOMNode *prev_sibling; + WebKitDOMNode *next_sibling; + + if (!webkit_dom_node_get_previous_sibling (WEBKIT_DOM_NODE (element))) + swap_direction = TRUE; + + window = webkit_dom_document_get_default_view (document); + window_selection = webkit_dom_dom_window_get_selection (window); + parent_node = webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (element)); + /* If parent is BODY element, we try to restore the position on the + * element that is next to us */ + if (WEBKIT_DOM_IS_HTML_BODY_ELEMENT (parent_node)) { + /* Look if we have DIV on right */ + next_sibling = webkit_dom_node_get_next_sibling ( + WEBKIT_DOM_NODE (element)); + if (!WEBKIT_DOM_IS_ELEMENT (next_sibling)) { + e_html_editor_selection_clear_caret_position_marker (selection); + return; + } + + if (element_has_class (WEBKIT_DOM_ELEMENT (next_sibling), "-x-evo-paragraph")) { + remove_node (WEBKIT_DOM_NODE (element)); + + move_caret_into_element ( + document, WEBKIT_DOM_ELEMENT (next_sibling)); + + goto out; + } + } + + move_caret_into_element (document, element); + + if (fix_after_quoting) { + prev_sibling = webkit_dom_node_get_previous_sibling ( + WEBKIT_DOM_NODE (element)); + next_sibling = webkit_dom_node_get_next_sibling ( + WEBKIT_DOM_NODE (element)); + if (!next_sibling) + fix_after_quoting = FALSE; + } + + remove_node (WEBKIT_DOM_NODE (element)); + + if (fix_after_quoting) + fix_quoting_nodes_after_caret_restoration ( + window_selection, prev_sibling, next_sibling); + out: + /* FIXME If caret position is restored and afterwards the + * position is saved it is not on the place where it supposed + * to be (it is in the beginning of parent's element. It can + * be avoided by moving with the caret. */ + if (swap_direction) { + webkit_dom_dom_selection_modify ( + window_selection, "move", "forward", "character"); + webkit_dom_dom_selection_modify ( + window_selection, "move", "backward", "character"); + } else { + webkit_dom_dom_selection_modify ( + window_selection, "move", "backward", "character"); + webkit_dom_dom_selection_modify ( + window_selection, "move", "forward", "character"); + } + } +} + +static gint +find_where_to_break_line (WebKitDOMNode *node, + gint max_len, + gint word_wrap_length) +{ + gchar *str, *text_start; + gunichar uc; + gint pos; + gint last_space = 0; + gint length; + gint ret_val = 0; + gchar* position; + + text_start = webkit_dom_character_data_get_data (WEBKIT_DOM_CHARACTER_DATA (node)); + length = g_utf8_strlen (text_start, -1); + + pos = 1; + last_space = 0; + str = text_start; + do { + uc = g_utf8_get_char (str); + if (!uc) { + g_free (text_start); + if (pos <= max_len) + return pos; + else + return last_space; + } + + /* If last_space is zero then the word is longer than + * word_wrap_length characters, so continue until we find + * a space */ + if ((pos > max_len) && (last_space > 0)) { + if (last_space > word_wrap_length) { + g_free (text_start); + return last_space; + } + + if (last_space > max_len) { + if (g_unichar_isspace (g_utf8_get_char (text_start))) + ret_val = 1; + + g_free (text_start); + return ret_val; + } + + if (last_space == max_len - 1) { + uc = g_utf8_get_char (str); + if (g_unichar_isspace (uc)) + last_space++; + } + + g_free (text_start); + return last_space; + } + + if (g_unichar_isspace (uc)) + last_space = pos; + + pos += 1; + str = g_utf8_next_char (str); + } while (*str); + + position = g_utf8_offset_to_pointer (text_start, max_len); + + if (g_unichar_isspace (g_utf8_get_char (position))) { + ret_val = max_len + 1; + } else { + if (last_space < max_len) { + ret_val = last_space; + } else { + if (length > word_wrap_length) + ret_val = last_space; + else + ret_val = 0; + } + } + + g_free (text_start); + + return ret_val; +} + +static WebKitDOMElement * +wrap_lines (EHTMLEditorSelection *selection, + WebKitDOMNode *paragraph, + WebKitDOMDocument *document, + gboolean remove_all_br, + gint word_wrap_length) +{ + WebKitDOMNode *node, *start_node; + WebKitDOMNode *paragraph_clone; + WebKitDOMDocumentFragment *fragment; + WebKitDOMElement *element; + WebKitDOMNodeList *wrap_br; + gint len, ii, br_count; + gulong length_left; + glong paragraph_char_count; + gchar *text_content; + + if (selection) { + paragraph_char_count = g_utf8_strlen ( + e_html_editor_selection_get_string (selection), -1); + + fragment = webkit_dom_range_clone_contents ( + html_editor_selection_get_current_range (selection), NULL); + + /* Select all BR elements or just ours that are used for wrapping. + * We are not removing user BR elements when this function is activated + * from Format->Wrap Lines action */ + wrap_br = webkit_dom_document_fragment_query_selector_all ( + fragment, + remove_all_br ? "br" : "br.-x-evo-wrap-br", + NULL); + } else { + WebKitDOMElement *caret_node; + + if (!webkit_dom_node_has_child_nodes (paragraph)) + return WEBKIT_DOM_ELEMENT (paragraph); + + paragraph_clone = webkit_dom_node_clone_node (paragraph, TRUE); + caret_node = webkit_dom_element_query_selector ( + WEBKIT_DOM_ELEMENT (paragraph_clone), + "span#-x-evo-caret-position", NULL); + text_content = webkit_dom_node_get_text_content (paragraph_clone); + paragraph_char_count = g_utf8_strlen (text_content, -1); + if (caret_node) + paragraph_char_count--; + g_free (text_content); + + wrap_br = webkit_dom_element_query_selector_all ( + WEBKIT_DOM_ELEMENT (paragraph_clone), + remove_all_br ? "br" : "br.-x-evo-wrap-br", + NULL); + } + + /* And remove them */ + br_count = webkit_dom_node_list_get_length (wrap_br); + for (ii = 0; ii < br_count; ii++) + remove_node (webkit_dom_node_list_item (wrap_br, ii)); + + if (selection) + node = WEBKIT_DOM_NODE (fragment); + else { + webkit_dom_node_normalize (paragraph_clone); + node = webkit_dom_node_get_first_child (paragraph_clone); + if (node) { + text_content = webkit_dom_node_get_text_content (node); + if (g_strcmp0 ("\n", text_content) == 0) + node = webkit_dom_node_get_next_sibling (node); + g_free (text_content); + } + } + + start_node = node; + len = 0; + while (node) { + gint offset = 0; + + if (WEBKIT_DOM_IS_TEXT (node)) { + const gchar *newline; + WebKitDOMNode *next_sibling; + + /* If there is temporary hidden space we remove it */ + text_content = webkit_dom_node_get_text_content (node); + if (strstr (text_content, UNICODE_ZERO_WIDTH_SPACE)) { + if (g_str_has_prefix (text_content, UNICODE_ZERO_WIDTH_SPACE)) + webkit_dom_character_data_delete_data ( + WEBKIT_DOM_CHARACTER_DATA (node), + 0, + 1, + NULL); + if (g_str_has_suffix (text_content, UNICODE_ZERO_WIDTH_SPACE)) + webkit_dom_character_data_delete_data ( + WEBKIT_DOM_CHARACTER_DATA (node), + g_utf8_strlen (text_content, -1) - 1, + 1, + NULL); + g_free (text_content); + text_content = webkit_dom_node_get_text_content (node); + } + newline = g_strstr_len (text_content, -1, "\n"); + + next_sibling = node; + while (newline) { + WebKitDOMElement *element; + + next_sibling = WEBKIT_DOM_NODE (webkit_dom_text_split_text ( + WEBKIT_DOM_TEXT (next_sibling), + g_utf8_pointer_to_offset (text_content, newline), + NULL)); + + if (!next_sibling) + break; + + element = webkit_dom_document_create_element ( + document, "BR", NULL); + element_add_class (element, "-x-evo-temp-wrap-text-br"); + + webkit_dom_node_insert_before ( + webkit_dom_node_get_parent_node (next_sibling), + WEBKIT_DOM_NODE (element), + next_sibling, + NULL); + + g_free (text_content); + + text_content = webkit_dom_node_get_text_content (next_sibling); + if (g_str_has_prefix (text_content, "\n")) { + webkit_dom_character_data_delete_data ( + WEBKIT_DOM_CHARACTER_DATA (next_sibling), 0, 1, NULL); + g_free (text_content); + text_content = + webkit_dom_node_get_text_content (next_sibling); + } + newline = g_strstr_len (text_content, -1, "\n"); + } + g_free (text_content); + } else { + /* If element is ANCHOR we wrap it separately */ + if (WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT (node)) { + glong anchor_length; + + text_content = webkit_dom_node_get_text_content (node); + anchor_length = g_utf8_strlen (text_content, -1); + if (len + anchor_length > word_wrap_length) { + element = webkit_dom_document_create_element ( + document, "BR", NULL); + element_add_class (element, "-x-evo-wrap-br"); + webkit_dom_node_insert_before ( + webkit_dom_node_get_parent_node (node), + WEBKIT_DOM_NODE (element), + node, + NULL); + len = anchor_length; + } else + len += anchor_length; + + g_free (text_content); + node = webkit_dom_node_get_next_sibling (node); + continue; + } + + if (is_caret_position_node (node)) { + node = webkit_dom_node_get_next_sibling (node); + continue; + } + + /* When we are not removing user-entered BR elements (lines wrapped by user), + * we need to skip those elements */ + if (!remove_all_br && WEBKIT_DOM_IS_HTMLBR_ELEMENT (node)) { + if (!element_has_class (WEBKIT_DOM_ELEMENT (node), "-x-evo-wrap-br")) { + len = 0; + node = webkit_dom_node_get_next_sibling (node); + continue; + } + } + goto next_node; + } + + /* If length of this node + what we already have is still less + * then word_wrap_length characters, then just join it and continue to next + * node */ + length_left = webkit_dom_character_data_get_length ( + WEBKIT_DOM_CHARACTER_DATA (node)); + + if ((length_left + len) < word_wrap_length) { + len += length_left; + goto next_node; + } + + /* wrap until we have something */ + while ((length_left + len) > word_wrap_length) { + /* Find where we can line-break the node so that it + * effectively fills the rest of current row */ + offset = find_where_to_break_line ( + node, word_wrap_length - len, word_wrap_length); + + element = webkit_dom_document_create_element (document, "BR", NULL); + element_add_class (element, "-x-evo-wrap-br"); + + if (offset > 0 && offset <= word_wrap_length) { + if (offset != length_left) + webkit_dom_text_split_text ( + WEBKIT_DOM_TEXT (node), offset, NULL); + + if (webkit_dom_node_get_next_sibling (node)) { + gchar *nd_content; + WebKitDOMNode *nd = webkit_dom_node_get_next_sibling (node); + + nd = webkit_dom_node_get_next_sibling (node); + nd_content = webkit_dom_node_get_text_content (nd); + if (nd_content && *nd_content) { + if (g_str_has_prefix (nd_content, " ")) + webkit_dom_character_data_replace_data ( + WEBKIT_DOM_CHARACTER_DATA (nd), 0, 1, "", NULL); + g_free (nd_content); + nd_content = webkit_dom_node_get_text_content (nd); + if (g_strcmp0 (nd_content, UNICODE_NBSP) == 0) + remove_node (nd); + g_free (nd_content); + } + + webkit_dom_node_insert_before ( + webkit_dom_node_get_parent_node (node), + WEBKIT_DOM_NODE (element), + nd, + NULL); + } else { + webkit_dom_node_append_child ( + webkit_dom_node_get_parent_node (node), + WEBKIT_DOM_NODE (element), + NULL); + } + } else if (offset > word_wrap_length) { + if (offset != length_left) + webkit_dom_text_split_text ( + WEBKIT_DOM_TEXT (node), offset + 1, NULL); + + if (webkit_dom_node_get_next_sibling (node)) { + gchar *nd_content; + WebKitDOMNode *nd = webkit_dom_node_get_next_sibling (node); + + nd = webkit_dom_node_get_next_sibling (node); + nd_content = webkit_dom_node_get_text_content (nd); + if (nd_content && *nd_content) { + if (g_str_has_prefix (nd_content, " ")) + webkit_dom_character_data_replace_data ( + WEBKIT_DOM_CHARACTER_DATA (nd), 0, 1, "", NULL); + g_free (nd_content); + nd_content = webkit_dom_node_get_text_content (nd); + if (g_strcmp0 (nd_content, UNICODE_NBSP) == 0) + remove_node (nd); + g_free (nd_content); + } + + webkit_dom_node_insert_before ( + webkit_dom_node_get_parent_node (node), + WEBKIT_DOM_NODE (element), + nd, + NULL); + } else { + webkit_dom_node_append_child ( + webkit_dom_node_get_parent_node (node), + WEBKIT_DOM_NODE (element), + NULL); + } + len = 0; + break; + } else { + webkit_dom_node_insert_before ( + webkit_dom_node_get_parent_node (node), + WEBKIT_DOM_NODE (element), + node, + NULL); + } + length_left = webkit_dom_character_data_get_length ( + WEBKIT_DOM_CHARACTER_DATA (node)); + + len = 0; + } + len += length_left - offset; + next_node: + if (WEBKIT_DOM_IS_HTMLLI_ELEMENT (node)) + len = 0; + + /* Move to next node */ + if (webkit_dom_node_has_child_nodes (node)) { + node = webkit_dom_node_get_first_child (node); + } else if (webkit_dom_node_get_next_sibling (node)) { + node = webkit_dom_node_get_next_sibling (node); + } else { + if (webkit_dom_node_is_equal_node (node, start_node)) + break; + + node = webkit_dom_node_get_parent_node (node); + if (node) + node = webkit_dom_node_get_next_sibling (node); + } + } + + if (selection) { + gchar *html; + + /* Create a wrapper DIV and put the processed content into it */ + element = webkit_dom_document_create_element (document, "DIV", NULL); + element_add_class (element, "-x-evo-paragraph"); + webkit_dom_node_append_child ( + WEBKIT_DOM_NODE (element), + WEBKIT_DOM_NODE (start_node), + NULL); + + webkit_dom_node_normalize (WEBKIT_DOM_NODE (element)); + /* Get HTML code of the processed content */ + html = webkit_dom_html_element_get_inner_html (WEBKIT_DOM_HTML_ELEMENT (element)); + + /* Overwrite the current selection be the processed content */ + e_html_editor_selection_insert_html (selection, html); + + g_free (html); + + return NULL; + } else { + webkit_dom_node_normalize (paragraph_clone); + + /* Replace paragraph with wrapped one */ + webkit_dom_node_replace_child ( + webkit_dom_node_get_parent_node (paragraph), + paragraph_clone, + paragraph, + NULL); + + return WEBKIT_DOM_ELEMENT (paragraph_clone); + } +} + +void +e_html_editor_selection_set_indented_style (EHTMLEditorSelection *selection, + WebKitDOMElement *element, + gint width) +{ + EHTMLEditorSelectionAlignment alignment; + gchar *style; + const gchar *align_value; + gint word_wrap_length = (width == -1) ? selection->priv->word_wrap_length : width; + gint start = 0, end = 0; + + alignment = e_html_editor_selection_get_alignment (selection); + align_value = get_css_alignment_value (alignment); + + if (alignment == E_HTML_EDITOR_SELECTION_ALIGNMENT_LEFT) + start = SPACES_PER_INDENTATION; + + if (alignment == E_HTML_EDITOR_SELECTION_ALIGNMENT_CENTER) { + start = SPACES_PER_INDENTATION; + } + + if (alignment == E_HTML_EDITOR_SELECTION_ALIGNMENT_RIGHT) { + start = 0; + end = SPACES_PER_INDENTATION; + } + + webkit_dom_element_set_class_name (element, "-x-evo-indented"); + /* We don't want vertical space bellow and above blockquote inserted by + * WebKit's User Agent Stylesheet. We have to override it through style attribute. */ + if (is_in_html_mode (selection)) + style = g_strdup_printf ( + "-webkit-margin-start: %dch; -webkit-margin-end : %dch; %s", + start, end, align_value); + else + style = g_strdup_printf ( + "-webkit-margin-start: %dch; -webkit-margin-end : %dch; " + "word-wrap: normal; width: %dch; %s", + start, end, word_wrap_length, align_value); + + webkit_dom_element_set_attribute (element, "style", style, NULL); + g_free (style); +} + +WebKitDOMElement * +e_html_editor_selection_get_indented_element (EHTMLEditorSelection *selection, + WebKitDOMDocument *document, + gint width) +{ + WebKitDOMElement *element; + + element = webkit_dom_document_create_element (document, "BLOCKQUOTE", NULL); + e_html_editor_selection_set_indented_style (selection, element, width); + + return element; +} + +void +e_html_editor_selection_set_paragraph_style (EHTMLEditorSelection *selection, + WebKitDOMElement *element, + gint width, + gint offset, + const gchar *style_to_add) +{ + EHTMLEditorSelectionAlignment alignment; + const gchar *align_value = NULL; + char *style = NULL; + gint word_wrap_length = (width == -1) ? selection->priv->word_wrap_length : width; + + alignment = e_html_editor_selection_get_alignment (selection); + align_value = get_css_alignment_value (alignment); + + element_add_class (element, "-x-evo-paragraph"); + if (!is_in_html_mode (selection)) { + style = g_strdup_printf ( + "width: %dch; word-wrap: normal; %s %s", + (word_wrap_length + offset), align_value, style_to_add); + } else { + if (*align_value || *style_to_add) + style = g_strdup_printf ( + "%s %s", align_value, style_to_add); + } + if (style) { + webkit_dom_element_set_attribute (element, "style", style, NULL); + g_free (style); + } +} + +WebKitDOMElement * +e_html_editor_selection_get_paragraph_element (EHTMLEditorSelection *selection, + WebKitDOMDocument *document, + gint width, + gint offset) +{ + WebKitDOMElement *element; + + element = webkit_dom_document_create_element (document, "DIV", NULL); + e_html_editor_selection_set_paragraph_style (selection, element, width, offset, ""); + + return element; +} + +WebKitDOMElement * +e_html_editor_selection_put_node_into_paragraph (EHTMLEditorSelection *selection, + WebKitDOMDocument *document, + WebKitDOMNode *node, + WebKitDOMNode *caret_position) +{ + WebKitDOMRange *range; + WebKitDOMElement *container; + + range = webkit_dom_document_create_range (document); + container = e_html_editor_selection_get_paragraph_element (selection, document, -1, 0); + webkit_dom_range_select_node (range, node, NULL); + webkit_dom_range_surround_contents (range, WEBKIT_DOM_NODE (container), NULL); + /* We have to move caret position inside this container */ + webkit_dom_node_append_child (WEBKIT_DOM_NODE (container), caret_position, NULL); + + return container; +} + +/** + * e_html_editor_selection_wrap_lines: + * @selection: an #EHTMLEditorSelection + * + * Wraps all lines in current selection to be 71 characters long. + */ +void +e_html_editor_selection_wrap_lines (EHTMLEditorSelection *selection) +{ + EHTMLEditorView *view; + WebKitDOMRange *range; + WebKitDOMDocument *document; + WebKitDOMElement *active_paragraph, *caret; + + g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection)); + + view = e_html_editor_selection_ref_html_editor_view (selection); + g_return_if_fail (view != NULL); + + document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view)); + g_object_unref (view); + + caret = e_html_editor_selection_save_caret_position (selection); + if (g_strcmp0 (e_html_editor_selection_get_string (selection), "") == 0) { + WebKitDOMNode *end_container; + WebKitDOMNode *parent; + WebKitDOMNode *paragraph; + gchar *text_content; + + /* We need to save caret position and restore it after + * wrapping the selection, but we need to save it before we + * start to modify selection */ + range = html_editor_selection_get_current_range (selection); + if (!range) + return; + + end_container = webkit_dom_range_get_common_ancestor_container (range, NULL); + + /* Wrap only text surrounded in DIV and P tags */ + parent = webkit_dom_node_get_parent_node(end_container); + if (WEBKIT_DOM_IS_HTML_DIV_ELEMENT (parent) || + WEBKIT_DOM_IS_HTML_PARAGRAPH_ELEMENT (parent)) { + element_add_class ( + WEBKIT_DOM_ELEMENT (parent), "-x-evo-paragraph"); + paragraph = parent; + } else { + WebKitDOMElement *parent_div = + e_html_editor_dom_node_find_parent_element (parent, "DIV"); + + if (element_has_class (parent_div, "-x-evo-paragraph")) { + paragraph = WEBKIT_DOM_NODE (parent_div); + } else { + if (!caret) + return; + + /* We try to select previous sibling */ + paragraph = webkit_dom_node_get_previous_sibling ( + WEBKIT_DOM_NODE (caret)); + if (paragraph) { + /* When there is just text without container + * we have to surround it with paragraph div */ + if (WEBKIT_DOM_IS_TEXT (paragraph)) + paragraph = WEBKIT_DOM_NODE ( + e_html_editor_selection_put_node_into_paragraph ( + selection, document, paragraph, + WEBKIT_DOM_NODE (caret))); + } else { + /* When some weird element is selected, return */ + e_html_editor_selection_clear_caret_position_marker (selection); + return; + } + } + } + + if (!paragraph) + return; + + webkit_dom_element_remove_attribute ( + WEBKIT_DOM_ELEMENT (paragraph), "style"); + webkit_dom_element_set_id ( + WEBKIT_DOM_ELEMENT (paragraph), "-x-evo-active-paragraph"); + + text_content = webkit_dom_node_get_text_content (paragraph); + /* If there is hidden space character in the beginning we remove it */ + if (strstr (text_content, UNICODE_ZERO_WIDTH_SPACE)) { + if (g_str_has_prefix (text_content, UNICODE_ZERO_WIDTH_SPACE)) { + WebKitDOMNode *node; + + node = webkit_dom_node_get_first_child (paragraph); + + webkit_dom_character_data_delete_data ( + WEBKIT_DOM_CHARACTER_DATA (node), + 0, + 1, + NULL); + } + if (g_str_has_suffix (text_content, UNICODE_ZERO_WIDTH_SPACE)) { + WebKitDOMNode *node; + + node = webkit_dom_node_get_last_child (paragraph); + + webkit_dom_character_data_delete_data ( + WEBKIT_DOM_CHARACTER_DATA (node), + g_utf8_strlen (text_content, -1) -1, + 1, + NULL); + } + } + g_free (text_content); + + wrap_lines ( + NULL, paragraph, document, FALSE, + selection->priv->word_wrap_length); + + } else { + e_html_editor_selection_save_caret_position (selection); + /* If we have selection -> wrap it */ + wrap_lines ( + selection, NULL, document, FALSE, + selection->priv->word_wrap_length); + } + + active_paragraph = webkit_dom_document_get_element_by_id ( + document, "-x-evo-active-paragraph"); + /* We have to move caret on position where it was before modifying the text */ + e_html_editor_selection_restore_caret_position (selection); + + /* Set paragraph as non-active */ + if (active_paragraph) + webkit_dom_element_remove_attribute ( + WEBKIT_DOM_ELEMENT (active_paragraph), "id"); +} + +WebKitDOMElement * +e_html_editor_selection_wrap_paragraph_length (EHTMLEditorSelection *selection, + WebKitDOMElement *paragraph, + gint length) +{ + WebKitDOMDocument *document; + + g_return_val_if_fail (E_IS_HTML_EDITOR_SELECTION (selection), NULL); + g_return_val_if_fail (WEBKIT_DOM_IS_ELEMENT (paragraph), NULL); + g_return_val_if_fail (length > 10, NULL); + + document = webkit_dom_node_get_owner_document (WEBKIT_DOM_NODE (paragraph)); + + return wrap_lines ( + NULL, WEBKIT_DOM_NODE (paragraph), document, FALSE, length); +} + +void +e_html_editor_selection_wrap_paragraphs_in_document (EHTMLEditorSelection *selection, + WebKitDOMDocument *document) +{ + WebKitDOMNodeList *list; + gint ii, length; + + g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection)); + + list = webkit_dom_document_query_selector_all ( + document, "div.-x-evo-paragraph:not(#-x-evo-input-start)", NULL); + + length = webkit_dom_node_list_get_length (list); + + for (ii = 0; ii < length; ii++) { + gint quote, citation_level; + WebKitDOMNode *node = webkit_dom_node_list_item (list, ii); + + citation_level = get_citation_level (node); + quote = citation_level ? citation_level + 1 : 0; + + if (node_is_list (node)) { + WebKitDOMNode *item = webkit_dom_node_get_first_child (node); + + while (item && WEBKIT_DOM_IS_HTMLLI_ELEMENT (item)) { + e_html_editor_selection_wrap_paragraph_length ( + selection, + WEBKIT_DOM_ELEMENT (item), + selection->priv->word_wrap_length - quote); + item = webkit_dom_node_get_next_sibling (item); + } + } else { + e_html_editor_selection_wrap_paragraph_length ( + selection, + WEBKIT_DOM_ELEMENT (node), + selection->priv->word_wrap_length - quote); + } + } +} + +WebKitDOMElement * +e_html_editor_selection_wrap_paragraph (EHTMLEditorSelection *selection, + WebKitDOMElement *paragraph) +{ + gint indentation_level, citation_level, quote; + gint final_width, word_wrap_length, offset = 0; + + g_return_val_if_fail (E_IS_HTML_EDITOR_SELECTION (selection), NULL); + g_return_val_if_fail (WEBKIT_DOM_IS_ELEMENT (paragraph), NULL); + + word_wrap_length = selection->priv->word_wrap_length; + indentation_level = get_indentation_level (paragraph); + citation_level = get_citation_level (WEBKIT_DOM_NODE (paragraph)); + + if (node_is_list (WEBKIT_DOM_NODE (paragraph)) || + WEBKIT_DOM_IS_HTMLLI_ELEMENT (paragraph)) { + + gint list_level = get_list_level (WEBKIT_DOM_NODE (paragraph)); + indentation_level = 0; + + if (list_level > 0) + offset = list_level * -SPACES_PER_LIST_LEVEL; + else + offset = -SPACES_PER_LIST_LEVEL; + } + + quote = citation_level ? citation_level + 1 : 0; + quote *= 2; + + final_width = word_wrap_length - quote + offset; + final_width -= SPACES_PER_INDENTATION * indentation_level; + + return e_html_editor_selection_wrap_paragraph_length ( + selection, WEBKIT_DOM_ELEMENT (paragraph), final_width); +} + +/** + * e_html_editor_selection_save: + * @selection: an #EHTMLEditorSelection + * + * Saves current cursor position or current selection range. The selection can + * be later restored by calling e_html_editor_selection_restore(). + * + * Note that calling e_html_editor_selection_save() overwrites previously saved + * position. + * + * Note that this method inserts special markings into the HTML code that are + * used to later restore the selection. It can happen that by deleting some + * segments of the document some of the markings are deleted too. In that case + * restoring the selection by e_html_editor_selection_restore() can fail. Also by + * moving text segments (Cut & Paste) can result in moving the markings + * elsewhere, thus e_html_editor_selection_restore() will restore the selection + * incorrectly. + * + * It is recommended to use this method only when you are not planning to make + * bigger changes to content or structure of the document (formatting changes + * are usually OK). + */ +void +e_html_editor_selection_save (EHTMLEditorSelection *selection) +{ + EHTMLEditorView *view; + WebKitWebView *web_view; + WebKitDOMDocument *document; + WebKitDOMRange *range; + WebKitDOMNode *container; + WebKitDOMElement *marker; + + g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection)); + + view = e_html_editor_selection_ref_html_editor_view (selection); + g_return_if_fail (view != NULL); + + web_view = WEBKIT_WEB_VIEW (view); + + document = webkit_web_view_get_dom_document (web_view); + + /* First remove all markers (if present) */ + marker = webkit_dom_document_get_element_by_id ( + document, "-x-evo-selection-start-marker"); + if (marker != NULL) + remove_node (WEBKIT_DOM_NODE (marker)); + + marker = webkit_dom_document_get_element_by_id ( + document, "-x-evo-selection-end-marker"); + if (marker != NULL) + remove_node (WEBKIT_DOM_NODE (marker)); + + range = html_editor_selection_get_current_range (selection); + + if (range != NULL) { + WebKitDOMNode *marker_node; + WebKitDOMNode *parent_node; + WebKitDOMNode *split_node; + glong offset; + + marker = webkit_dom_document_create_element ( + document, "SPAN", NULL); + webkit_dom_element_set_id ( + marker, "-x-evo-selection-start-marker"); + + container = webkit_dom_range_get_start_container (range, NULL); + offset = webkit_dom_range_get_start_offset (range, NULL); + + if (WEBKIT_DOM_IS_TEXT (container)) { + WebKitDOMText *split_text; + + split_text = webkit_dom_text_split_text ( + WEBKIT_DOM_TEXT (container), offset, NULL); + split_node = WEBKIT_DOM_NODE (split_text); + } else if (WEBKIT_DOM_IS_HTMLLI_ELEMENT (container)) { + webkit_dom_node_insert_before ( + container, + WEBKIT_DOM_NODE (marker), + webkit_dom_node_get_first_child ( + WEBKIT_DOM_NODE (container)), + NULL); + + goto end_marker; + } else { + if (!webkit_dom_node_get_previous_sibling (container)) { + split_node = webkit_dom_node_get_parent_node ( + container); + } else if (!webkit_dom_node_get_next_sibling (container)) { + split_node = webkit_dom_node_get_parent_node ( + container); + split_node = webkit_dom_node_get_next_sibling ( + split_node); + } else + split_node = container; + } + + if (!split_node) { + webkit_dom_node_insert_before ( + container, + WEBKIT_DOM_NODE (marker), + webkit_dom_node_get_first_child ( + WEBKIT_DOM_NODE (container)), + NULL); + } else { + marker_node = WEBKIT_DOM_NODE (marker); + parent_node = webkit_dom_node_get_parent_node (split_node); + + webkit_dom_node_insert_before ( + parent_node, marker_node, split_node, NULL); + } + end_marker: + marker = webkit_dom_document_create_element ( + document, "SPAN", NULL); + webkit_dom_element_set_id ( + marker, "-x-evo-selection-end-marker"); + + container = webkit_dom_range_get_end_container (range, NULL); + offset = webkit_dom_range_get_end_offset (range, NULL); + + if (WEBKIT_DOM_IS_TEXT (container) && offset != 0) { + WebKitDOMText *split_text; + + split_text = webkit_dom_text_split_text ( + WEBKIT_DOM_TEXT (container), offset, NULL); + split_node = WEBKIT_DOM_NODE (split_text); + } else if (WEBKIT_DOM_IS_HTMLLI_ELEMENT (container)) { + webkit_dom_node_append_child ( + container, WEBKIT_DOM_NODE (marker), NULL); + g_object_unref (view); + return; + } else { + if (!webkit_dom_node_get_previous_sibling (container)) { + split_node = webkit_dom_node_get_parent_node ( + container); + } else if (!webkit_dom_node_get_next_sibling (container)) { + split_node = webkit_dom_node_get_parent_node ( + container); + split_node = webkit_dom_node_get_next_sibling ( + split_node); + } else + split_node = container; + } + + marker_node = WEBKIT_DOM_NODE (marker); + + if (split_node) { + parent_node = webkit_dom_node_get_parent_node (split_node); + + webkit_dom_node_insert_before ( + parent_node, marker_node, split_node, NULL); + } else { + webkit_dom_node_append_child ( + WEBKIT_DOM_NODE (container), + marker_node, + NULL); + } + } + + g_object_unref (view); +} + +/** + * e_html_editor_selection_restore: + * @selection: an #EHTMLEditorSelection + * + * Restores cursor position or selection range that was saved by + * e_html_editor_selection_save(). + * + * Note that calling this function without calling e_html_editor_selection_save() + * before is a programming error and the behavior is undefined. + */ +void +e_html_editor_selection_restore (EHTMLEditorSelection *selection) +{ + EHTMLEditorView *view; + WebKitWebView *web_view; + WebKitDOMDocument *document; + WebKitDOMDOMWindow *window; + WebKitDOMDOMSelection *dom_selection; + WebKitDOMRange *range; + WebKitDOMElement *marker; + + g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection)); + + view = e_html_editor_selection_ref_html_editor_view (selection); + g_return_if_fail (view != NULL); + + web_view = WEBKIT_WEB_VIEW (view); + + document = webkit_web_view_get_dom_document (web_view); + window = webkit_dom_document_get_default_view (document); + dom_selection = webkit_dom_dom_window_get_selection (window); + range = webkit_dom_document_create_range (document); + + if (range != NULL) { + marker = webkit_dom_document_get_element_by_id ( + document, "-x-evo-selection-start-marker"); + if (!marker) { + marker = webkit_dom_document_get_element_by_id ( + document, "-x-evo-selection-end-marker"); + if (marker) + remove_node (WEBKIT_DOM_NODE (marker)); + return; + } + + webkit_dom_range_set_start_after ( + range, WEBKIT_DOM_NODE (marker), NULL); + remove_node (WEBKIT_DOM_NODE (marker)); + + marker = webkit_dom_document_get_element_by_id ( + document, "-x-evo-selection-end-marker"); + if (!marker) { + marker = webkit_dom_document_get_element_by_id ( + document, "-x-evo-selection-start-marker"); + if (marker) + remove_node (WEBKIT_DOM_NODE (marker)); + return; + } + + webkit_dom_range_set_end_before ( + range, WEBKIT_DOM_NODE (marker), NULL); + remove_node (WEBKIT_DOM_NODE (marker)); + + webkit_dom_dom_selection_remove_all_ranges (dom_selection); + webkit_dom_dom_selection_add_range (dom_selection, range); + } + + g_object_unref (view); +} + +static void +html_editor_selection_modify (EHTMLEditorSelection *selection, + const gchar *alter, + gboolean forward, + EHTMLEditorSelectionGranularity granularity) +{ + EHTMLEditorView *view; + WebKitWebView *web_view; + WebKitDOMDocument *document; + WebKitDOMDOMWindow *window; + WebKitDOMDOMSelection *dom_selection; + const gchar *granularity_str; + + g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection)); + + view = e_html_editor_selection_ref_html_editor_view (selection); + g_return_if_fail (view != NULL); + + web_view = WEBKIT_WEB_VIEW (view); + + document = webkit_web_view_get_dom_document (web_view); + window = webkit_dom_document_get_default_view (document); + dom_selection = webkit_dom_dom_window_get_selection (window); + + switch (granularity) { + case E_HTML_EDITOR_SELECTION_GRANULARITY_CHARACTER: + granularity_str = "character"; + break; + case E_HTML_EDITOR_SELECTION_GRANULARITY_WORD: + granularity_str = "word"; + break; + } + + webkit_dom_dom_selection_modify ( + dom_selection, alter, + forward ? "forward" : "backward", + granularity_str); + + g_object_unref (view); +} + +/** + * e_html_editor_selection_extend: + * @selection: an #EHTMLEditorSelection + * @forward: whether to extend selection forward or backward + * @granularity: granularity of the extension + * + * Extends current selection in given direction by given granularity. + */ +void +e_html_editor_selection_extend (EHTMLEditorSelection *selection, + gboolean forward, + EHTMLEditorSelectionGranularity granularity) +{ + html_editor_selection_modify (selection, "extend", forward, granularity); +} + +/** + * e_html_editor_selection_move: + * @selection: an #EHTMLEditorSelection + * @forward: whether to move the selection forward or backward + * @granularity: granularity of the movement + * + * Moves current selection in given direction by given granularity + */ +void +e_html_editor_selection_move (EHTMLEditorSelection *selection, + gboolean forward, + EHTMLEditorSelectionGranularity granularity) +{ + html_editor_selection_modify (selection, "move", forward, granularity); +} + +void +e_html_editor_selection_scroll_to_caret (EHTMLEditorSelection *selection) +{ + WebKitDOMElement *caret; + + caret = e_html_editor_selection_save_caret_position (selection); + + webkit_dom_element_scroll_into_view (caret, TRUE); + + e_html_editor_selection_clear_caret_position_marker (selection); +} diff --git a/e-util/e-html-editor-selection.h b/e-util/e-html-editor-selection.h new file mode 100644 index 0000000000..c0d2d1817f --- /dev/null +++ b/e-util/e-html-editor-selection.h @@ -0,0 +1,250 @@ +/* + * e-html-editor-selection.h + * + * Copyright (C) 2012 Dan Vrátil + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public 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_HTML_EDITOR_SELECTION_H +#define E_HTML_EDITOR_SELECTION_H + +#include +#include +#include + +/* Standard GObject macros */ +#define E_TYPE_HTML_EDITOR_SELECTION \ + (e_html_editor_selection_get_type ()) +#define E_HTML_EDITOR_SELECTION(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST \ + ((obj), E_TYPE_HTML_EDITOR_SELECTION, EHTMLEditorSelection)) +#define E_HTML_EDITOR_SELECTION_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_CAST \ + ((cls), E_TYPE_HTML_EDITOR_SELECTION, EHTMLEditorSelectionClass)) +#define E_IS_HTML_EDITOR_SELECTION(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE \ + ((obj), E_TYPE_HTML_EDITOR_SELECTION)) +#define E_IS_HTML_EDITOR_SELECTION_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_TYPE \ + ((cls), E_TYPE_HTML_EDITOR_SELECTION)) +#define E_HTML_EDITOR_SELECTION_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS \ + ((obj), E_TYPE_HTML_EDITOR_SELECTION, EHTMLEditorSelectionClass)) + +G_BEGIN_DECLS + +struct _EHTMLEditorView; + +typedef struct _EHTMLEditorSelection EHTMLEditorSelection; +typedef struct _EHTMLEditorSelectionClass EHTMLEditorSelectionClass; +typedef struct _EHTMLEditorSelectionPrivate EHTMLEditorSelectionPrivate; + +struct _EHTMLEditorSelection { + GObject parent; + EHTMLEditorSelectionPrivate *priv; +}; + +struct _EHTMLEditorSelectionClass { + GObjectClass parent_class; +}; + +GType e_html_editor_selection_get_type + (void) G_GNUC_CONST; +struct _EHTMLEditorView * + e_html_editor_selection_ref_html_editor_view + (EHTMLEditorSelection *selection); +void e_html_editor_selection_block_selection_changed + (EHTMLEditorSelection *selection); +void e_html_editor_selection_unblock_selection_changed + (EHTMLEditorSelection *selection); +gint e_html_editor_selection_get_word_wrap_length + (EHTMLEditorSelection *selection); +gboolean e_html_editor_selection_has_text + (EHTMLEditorSelection *selection); +gchar * e_html_editor_selection_get_caret_word + (EHTMLEditorSelection *selection); +void e_html_editor_selection_replace_caret_word + (EHTMLEditorSelection *selection, + const gchar *replacement); +EHTMLEditorSelectionAlignment + e_html_editor_selection_get_alignment + (EHTMLEditorSelection *selection); +void e_html_editor_selection_set_alignment + (EHTMLEditorSelection *selection, + EHTMLEditorSelectionAlignment alignment); +const gchar * e_html_editor_selection_get_background_color + (EHTMLEditorSelection *selection); +void e_html_editor_selection_set_background_color + (EHTMLEditorSelection *selection, + const gchar *color); +void e_html_editor_selection_get_font_color + (EHTMLEditorSelection *selection, + GdkRGBA *rgba); +void e_html_editor_selection_set_font_color + (EHTMLEditorSelection *selection, + const GdkRGBA *rgba); +const gchar * e_html_editor_selection_get_font_name + (EHTMLEditorSelection *selection); +void e_html_editor_selection_set_font_name + (EHTMLEditorSelection *selection, + const gchar *font_name); +guint e_html_editor_selection_get_font_size + (EHTMLEditorSelection *selection); +void e_html_editor_selection_set_font_size + (EHTMLEditorSelection *selection, + guint font_size); +EHTMLEditorSelectionBlockFormat + e_html_editor_selection_get_block_format + (EHTMLEditorSelection *selection); +void e_html_editor_selection_set_block_format + (EHTMLEditorSelection *selection, + EHTMLEditorSelectionBlockFormat format); +gboolean e_html_editor_selection_is_citation + (EHTMLEditorSelection *selection); +gboolean e_html_editor_selection_is_indented + (EHTMLEditorSelection *selection); +void e_html_editor_selection_indent (EHTMLEditorSelection *selection); +void e_html_editor_selection_unindent + (EHTMLEditorSelection *selection); +gboolean e_html_editor_selection_is_bold (EHTMLEditorSelection *selection); +void e_html_editor_selection_set_bold + (EHTMLEditorSelection *selection, + gboolean bold); +gboolean e_html_editor_selection_is_italic + (EHTMLEditorSelection *selection); +void e_html_editor_selection_set_italic + (EHTMLEditorSelection *selection, + gboolean italic); +gboolean e_html_editor_selection_is_monospaced + (EHTMLEditorSelection *selection); +void e_html_editor_selection_set_monospaced + (EHTMLEditorSelection *selection, + gboolean monospaced); +gboolean e_html_editor_selection_is_strikethrough + (EHTMLEditorSelection *selection); +void e_html_editor_selection_set_strikethrough + (EHTMLEditorSelection *selection, + gboolean strikethrough); +gboolean e_html_editor_selection_is_superscript + (EHTMLEditorSelection *selection); +void e_html_editor_selection_set_superscript + (EHTMLEditorSelection *selection, + gboolean superscript); +gboolean e_html_editor_selection_is_subscript + (EHTMLEditorSelection *selection); +void e_html_editor_selection_set_subscript + (EHTMLEditorSelection *selection, + gboolean subscript); +gboolean e_html_editor_selection_is_underline + (EHTMLEditorSelection *selection); +void e_html_editor_selection_set_underline + (EHTMLEditorSelection *selection, + gboolean underline); +void e_html_editor_selection_unlink (EHTMLEditorSelection *selection); +void e_html_editor_selection_create_link + (EHTMLEditorSelection *selection, + const gchar *uri); +const gchar * e_html_editor_selection_get_string + (EHTMLEditorSelection *selection); +void e_html_editor_selection_replace (EHTMLEditorSelection *selection, + const gchar *new_string); +void e_html_editor_selection_insert_html + (EHTMLEditorSelection *selection, + const gchar *html_text); +void e_html_editor_selection_replace_image_src + (EHTMLEditorSelection *selection, + WebKitDOMElement *image, + const gchar *image_uri); +void e_html_editor_selection_insert_image + (EHTMLEditorSelection *selection, + const gchar *image_uri); +void e_html_editor_selection_insert_text + (EHTMLEditorSelection *selection, + const gchar *plain_text); +void e_html_editor_selection_clear_caret_position_marker + (EHTMLEditorSelection *selection); +WebKitDOMNode * + e_html_editor_selection_get_caret_position_node + (WebKitDOMDocument *document); +WebKitDOMElement * + e_html_editor_selection_save_caret_position + (EHTMLEditorSelection *selection); +void e_html_editor_selection_restore_caret_position + (EHTMLEditorSelection *selection); +void e_html_editor_selection_set_indented_style + (EHTMLEditorSelection *selection, + WebKitDOMElement *element, + gint width); +WebKitDOMElement * + e_html_editor_selection_get_indented_element + (EHTMLEditorSelection *selection, + WebKitDOMDocument *document, + gint width); +void e_html_editor_selection_set_paragraph_style + (EHTMLEditorSelection *selection, + WebKitDOMElement *element, + gint width, + gint offset, + const gchar *style_to_add); +WebKitDOMElement * + e_html_editor_selection_get_paragraph_element + (EHTMLEditorSelection *selection, + WebKitDOMDocument *document, + gint width, + gint offset); +WebKitDOMElement * + e_html_editor_selection_put_node_into_paragraph + (EHTMLEditorSelection *selection, + WebKitDOMDocument *document, + WebKitDOMNode *node, + WebKitDOMNode *caret_position); +void e_html_editor_selection_wrap_lines + (EHTMLEditorSelection *selection); +WebKitDOMElement * + e_html_editor_selection_wrap_paragraph_length + (EHTMLEditorSelection *selection, + WebKitDOMElement *paragraph, + gint length); +void e_html_editor_selection_wrap_paragraphs_in_document + (EHTMLEditorSelection *selection, + WebKitDOMDocument *document); +WebKitDOMElement * + e_html_editor_selection_wrap_paragraph + (EHTMLEditorSelection *selection, + WebKitDOMElement *paragraph); +void e_html_editor_selection_save (EHTMLEditorSelection *selection); +void e_html_editor_selection_restore (EHTMLEditorSelection *selection); +void e_html_editor_selection_move (EHTMLEditorSelection *selection, + gboolean forward, + EHTMLEditorSelectionGranularity granularity); +void e_html_editor_selection_extend (EHTMLEditorSelection *selection, + gboolean forward, + EHTMLEditorSelectionGranularity granularity); +void e_html_editor_selection_scroll_to_caret + (EHTMLEditorSelection *selection); +EHTMLEditorSelectionBlockFormat + e_html_editor_selection_get_list_format_from_node + (WebKitDOMNode *node); +EHTMLEditorSelectionAlignment + e_html_editor_selection_get_list_alignment_from_node + (WebKitDOMNode *node); +G_END_DECLS + +#endif /* E_HTML_EDITOR_SELECTION_H */ diff --git a/e-util/e-html-editor-spell-check-dialog.c b/e-util/e-html-editor-spell-check-dialog.c new file mode 100644 index 0000000000..b399dfabd8 --- /dev/null +++ b/e-util/e-html-editor-spell-check-dialog.c @@ -0,0 +1,710 @@ +/* + * e-html-editor-spell-dialog.c + * + * Copyright (C) 2012 Dan Vrátil + * + * 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-html-editor-spell-check-dialog.h" + +#include +#include + +#include "e-html-editor-view.h" +#include "e-spell-checker.h" +#include "e-spell-dictionary.h" + +#define E_HTML_EDITOR_SPELL_CHECK_DIALOG_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE \ + ((obj), E_TYPE_HTML_EDITOR_SPELL_CHECK_DIALOG, EHTMLEditorSpellCheckDialogPrivate)) + +struct _EHTMLEditorSpellCheckDialogPrivate { + GtkWidget *add_word_button; + GtkWidget *back_button; + GtkWidget *dictionary_combo; + GtkWidget *ignore_button; + GtkWidget *replace_button; + GtkWidget *replace_all_button; + GtkWidget *skip_button; + GtkWidget *suggestion_label; + GtkWidget *tree_view; + + WebKitDOMDOMSelection *selection; + + gchar *word; + ESpellDictionary *current_dict; +}; + +enum { + COLUMN_NAME, + COLUMN_DICTIONARY, + NUM_COLUMNS +}; + +G_DEFINE_TYPE ( + EHTMLEditorSpellCheckDialog, + e_html_editor_spell_check_dialog, + E_TYPE_HTML_EDITOR_DIALOG) + +static void +html_editor_spell_check_dialog_set_word (EHTMLEditorSpellCheckDialog *dialog, + const gchar *word) +{ + EHTMLEditor *editor; + EHTMLEditorView *view; + GtkTreeView *tree_view; + GtkListStore *store; + gchar *markup; + GList *list, *link; + + if (word == NULL) + return; + + if (dialog->priv->word != word) { + g_free (dialog->priv->word); + dialog->priv->word = g_strdup (word); + } + + markup = g_strdup_printf (_("Suggestions for '%s'"), word); + gtk_label_set_markup ( + GTK_LABEL (dialog->priv->suggestion_label), markup); + g_free (markup); + + tree_view = GTK_TREE_VIEW (dialog->priv->tree_view); + store = GTK_LIST_STORE (gtk_tree_view_get_model (tree_view)); + gtk_list_store_clear (store); + + list = e_spell_dictionary_get_suggestions ( + dialog->priv->current_dict, word, -1); + + for (link = list; link != NULL; link = g_list_next (link)) { + GtkTreeIter iter; + gchar *suggestion = link->data; + + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, 0, suggestion, -1); + } + + g_list_free_full (list, (GDestroyNotify) g_free); + + /* We give focus to WebKit so that the currently selected word + * is highlited. Without focus selection is not visible (at + * least with my default color scheme). The focus in fact is not + * given to WebKit, because this dialog is modal, but it satisfies + * it in a way that it paints the selection :) */ + editor = e_html_editor_dialog_get_editor (E_HTML_EDITOR_DIALOG (dialog)); + view = e_html_editor_get_view (editor); + gtk_widget_grab_focus (GTK_WIDGET (view)); +} + +static gboolean +select_next_word (EHTMLEditorSpellCheckDialog *dialog) +{ + WebKitDOMNode *anchor, *focus; + gulong anchor_offset, focus_offset; + + anchor = webkit_dom_dom_selection_get_anchor_node (dialog->priv->selection); + anchor_offset = webkit_dom_dom_selection_get_anchor_offset (dialog->priv->selection); + + focus = webkit_dom_dom_selection_get_focus_node (dialog->priv->selection); + focus_offset = webkit_dom_dom_selection_get_focus_offset (dialog->priv->selection); + + /* Jump _behind_ next word */ + webkit_dom_dom_selection_modify ( + dialog->priv->selection, "move", "forward", "word"); + /* Jump before the word */ + webkit_dom_dom_selection_modify ( + dialog->priv->selection, "move", "backward", "word"); + /* Select it */ + webkit_dom_dom_selection_modify ( + dialog->priv->selection, "extend", "forward", "word"); + + /* If the selection didn't change, then we have most probably + * reached the end of document - return FALSE */ + return !((anchor == webkit_dom_dom_selection_get_anchor_node ( + dialog->priv->selection)) && + (anchor_offset == webkit_dom_dom_selection_get_anchor_offset ( + dialog->priv->selection)) && + (focus == webkit_dom_dom_selection_get_focus_node ( + dialog->priv->selection)) && + (focus_offset == webkit_dom_dom_selection_get_focus_offset ( + dialog->priv->selection))); +} + +static gboolean +html_editor_spell_check_dialog_next (EHTMLEditorSpellCheckDialog *dialog) +{ + WebKitDOMNode *start = NULL, *end = NULL; + gulong start_offset, end_offset; + + if (dialog->priv->word == NULL) { + webkit_dom_dom_selection_modify ( + dialog->priv->selection, "move", "left", "documentboundary"); + } else { + /* Remember last selected word */ + start = webkit_dom_dom_selection_get_anchor_node ( + dialog->priv->selection); + end = webkit_dom_dom_selection_get_focus_node ( + dialog->priv->selection); + start_offset = webkit_dom_dom_selection_get_anchor_offset ( + dialog->priv->selection); + end_offset = webkit_dom_dom_selection_get_focus_offset ( + dialog->priv->selection); + } + + while (select_next_word (dialog)) { + WebKitDOMRange *range; + WebKitSpellChecker *checker; + gint loc, len; + gchar *word; + + range = webkit_dom_dom_selection_get_range_at ( + dialog->priv->selection, 0, NULL); + word = webkit_dom_range_get_text (range); + + checker = WEBKIT_SPELL_CHECKER (webkit_get_text_checker ()); + webkit_spell_checker_check_spelling_of_string ( + checker, word, &loc, &len); + + /* Found misspelled word! */ + if (loc != -1) { + html_editor_spell_check_dialog_set_word (dialog, word); + g_free (word); + return TRUE; + } + + g_free (word); + } + + /* Restore the selection to contain the last misspelled word. This is + * reached only when we reach the end of the document */ + if (start && end) { + webkit_dom_dom_selection_set_base_and_extent ( + dialog->priv->selection, start, start_offset, + end, end_offset, NULL); + } + + /* Close the dialog */ + gtk_widget_hide (GTK_WIDGET (dialog)); + return FALSE; +} + +static gboolean +select_previous_word (EHTMLEditorSpellCheckDialog *dialog) +{ + WebKitDOMNode *old_anchor_node; + WebKitDOMNode *new_anchor_node; + gulong old_anchor_offset; + gulong new_anchor_offset; + + old_anchor_node = webkit_dom_dom_selection_get_anchor_node ( + dialog->priv->selection); + old_anchor_offset = webkit_dom_dom_selection_get_anchor_offset ( + dialog->priv->selection); + + /* Jump on the beginning of current word */ + webkit_dom_dom_selection_modify ( + dialog->priv->selection, "move", "backward", "word"); + /* Jump before previous word */ + webkit_dom_dom_selection_modify ( + dialog->priv->selection, "move", "backward", "word"); + /* Select it */ + webkit_dom_dom_selection_modify ( + dialog->priv->selection, "extend", "forward", "word"); + + /* If the selection start didn't change, then we have most probably + * reached the beginnig of document. Return FALSE */ + + new_anchor_node = webkit_dom_dom_selection_get_anchor_node ( + dialog->priv->selection); + new_anchor_offset = webkit_dom_dom_selection_get_anchor_offset ( + dialog->priv->selection); + + return (new_anchor_node != old_anchor_node) || + (new_anchor_offset != old_anchor_offset); +} + +static gboolean +html_editor_spell_check_dialog_prev (EHTMLEditorSpellCheckDialog *dialog) +{ + WebKitDOMNode *start = NULL, *end = NULL; + gulong start_offset, end_offset; + + if (dialog->priv->word == NULL) { + webkit_dom_dom_selection_modify ( + dialog->priv->selection, + "move", "right", "documentboundary"); + webkit_dom_dom_selection_modify ( + dialog->priv->selection, + "extend", "backward", "word"); + } else { + /* Remember last selected word */ + start = webkit_dom_dom_selection_get_anchor_node ( + dialog->priv->selection); + end = webkit_dom_dom_selection_get_focus_node ( + dialog->priv->selection); + start_offset = webkit_dom_dom_selection_get_anchor_offset ( + dialog->priv->selection); + end_offset = webkit_dom_dom_selection_get_focus_offset ( + dialog->priv->selection); + } + + while (select_previous_word (dialog)) { + WebKitDOMRange *range; + WebKitSpellChecker *checker; + gint loc, len; + gchar *word; + + range = webkit_dom_dom_selection_get_range_at ( + dialog->priv->selection, 0, NULL); + word = webkit_dom_range_get_text (range); + + checker = WEBKIT_SPELL_CHECKER (webkit_get_text_checker ()); + webkit_spell_checker_check_spelling_of_string ( + checker, word, &loc, &len); + + /* Found misspelled word! */ + if (loc != -1) { + html_editor_spell_check_dialog_set_word (dialog, word); + g_free (word); + return TRUE; + } + + g_free (word); + } + + /* Restore the selection to contain the last misspelled word. This is + * reached only when we reach the beginning of the document */ + if (start && end) { + webkit_dom_dom_selection_set_base_and_extent ( + dialog->priv->selection, start, start_offset, + end, end_offset, NULL); + } + + /* Close the dialog */ + gtk_widget_hide (GTK_WIDGET (dialog)); + return FALSE; +} + +static void +html_editor_spell_check_dialog_replace (EHTMLEditorSpellCheckDialog *dialog) +{ + EHTMLEditor *editor; + EHTMLEditorView *view; + EHTMLEditorSelection *editor_selection; + GtkTreeModel *model; + GtkTreeSelection *selection; + GtkTreeIter iter; + gchar *replacement; + + editor = e_html_editor_dialog_get_editor (E_HTML_EDITOR_DIALOG (dialog)); + view = e_html_editor_get_view (editor); + editor_selection = e_html_editor_view_get_selection (view); + + selection = gtk_tree_view_get_selection ( + GTK_TREE_VIEW (dialog->priv->tree_view)); + gtk_tree_selection_get_selected (selection, &model, &iter); + gtk_tree_model_get (model, &iter, 0, &replacement, -1); + + e_html_editor_selection_insert_html ( + editor_selection, replacement); + + g_free (replacement); + html_editor_spell_check_dialog_next (dialog); +} + +static void +html_editor_spell_check_dialog_replace_all (EHTMLEditorSpellCheckDialog *dialog) +{ + EHTMLEditor *editor; + EHTMLEditorView *view; + EHTMLEditorSelection *editor_selection; + GtkTreeModel *model; + GtkTreeSelection *selection; + GtkTreeIter iter; + gchar *replacement; + + editor = e_html_editor_dialog_get_editor (E_HTML_EDITOR_DIALOG (dialog)); + view = e_html_editor_get_view (editor); + editor_selection = e_html_editor_view_get_selection (view); + + selection = gtk_tree_view_get_selection ( + GTK_TREE_VIEW (dialog->priv->tree_view)); + gtk_tree_selection_get_selected (selection, &model, &iter); + gtk_tree_model_get (model, &iter, 0, &replacement, -1); + + /* Repeatedly search for 'word', then replace selection by + * 'replacement'. Repeat until there's at least one occurence of + * 'word' in the document */ + while (webkit_web_view_search_text ( + WEBKIT_WEB_VIEW (view), dialog->priv->word, + FALSE, TRUE, TRUE)) { + + e_html_editor_selection_insert_html ( + editor_selection, replacement); + } + + g_free (replacement); + html_editor_spell_check_dialog_next (dialog); +} + +static void +html_editor_spell_check_dialog_ignore (EHTMLEditorSpellCheckDialog *dialog) +{ + if (dialog->priv->word == NULL) + return; + + e_spell_dictionary_ignore_word ( + dialog->priv->current_dict, dialog->priv->word, -1); + + html_editor_spell_check_dialog_next (dialog); +} + +static void +html_editor_spell_check_dialog_learn (EHTMLEditorSpellCheckDialog *dialog) +{ + if (dialog->priv->word == NULL) + return; + + e_spell_dictionary_learn_word ( + dialog->priv->current_dict, dialog->priv->word, -1); + + html_editor_spell_check_dialog_next (dialog); +} + +static void +html_editor_spell_check_dialog_set_dictionary (EHTMLEditorSpellCheckDialog *dialog) +{ + GtkComboBox *combo_box; + GtkTreeModel *model; + GtkTreeIter iter; + ESpellDictionary *dictionary; + + combo_box = GTK_COMBO_BOX (dialog->priv->dictionary_combo); + gtk_combo_box_get_active_iter (combo_box, &iter); + model = gtk_combo_box_get_model (combo_box); + + gtk_tree_model_get (model, &iter, 1, &dictionary, -1); + + dialog->priv->current_dict = dictionary; + + /* Update suggestions */ + html_editor_spell_check_dialog_set_word (dialog, dialog->priv->word); +} + +static void +html_editor_spell_check_dialog_show (GtkWidget *widget) +{ + EHTMLEditor *editor; + EHTMLEditorView *view; + EHTMLEditorSpellCheckDialog *dialog; + WebKitDOMDocument *document; + WebKitDOMDOMWindow *window; + + dialog = E_HTML_EDITOR_SPELL_CHECK_DIALOG (widget); + + g_free (dialog->priv->word); + dialog->priv->word = NULL; + + editor = e_html_editor_dialog_get_editor (E_HTML_EDITOR_DIALOG (dialog)); + view = e_html_editor_get_view (editor); + + document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view)); + window = webkit_dom_document_get_default_view (document); + dialog->priv->selection = webkit_dom_dom_window_get_selection (window); + + /* Select the first word or quit */ + if (html_editor_spell_check_dialog_next (dialog)) { + GTK_WIDGET_CLASS (e_html_editor_spell_check_dialog_parent_class)-> + show (widget); + } +} + +static void +html_editor_spell_check_dialog_finalize (GObject *object) +{ + EHTMLEditorSpellCheckDialogPrivate *priv; + + priv = E_HTML_EDITOR_SPELL_CHECK_DIALOG_GET_PRIVATE (object); + + g_free (priv->word); + + /* Chain up to parent's finalize() method. */ + G_OBJECT_CLASS (e_html_editor_spell_check_dialog_parent_class)-> + finalize (object); +} + +static void +html_editor_spell_check_dialog_constructed (GObject *object) +{ + EHTMLEditorSpellCheckDialog *dialog; + + /* Chain up to parent's constructed() method. */ + G_OBJECT_CLASS (e_html_editor_spell_check_dialog_parent_class)-> + constructed (object); + + dialog = E_HTML_EDITOR_SPELL_CHECK_DIALOG (object); + e_html_editor_spell_check_dialog_update_dictionaries (dialog); +} + +static void +e_html_editor_spell_check_dialog_class_init (EHTMLEditorSpellCheckDialogClass *class) +{ + GtkWidgetClass *widget_class; + GObjectClass *object_class; + + g_type_class_add_private ( + class, sizeof (EHTMLEditorSpellCheckDialogPrivate)); + + object_class = G_OBJECT_CLASS (class); + object_class->finalize = html_editor_spell_check_dialog_finalize; + object_class->constructed = html_editor_spell_check_dialog_constructed; + + widget_class = GTK_WIDGET_CLASS (class); + widget_class->show = html_editor_spell_check_dialog_show; +} + +static void +e_html_editor_spell_check_dialog_init (EHTMLEditorSpellCheckDialog *dialog) +{ + GtkWidget *widget; + GtkGrid *main_layout; + GtkListStore *store; + GtkTreeViewColumn *column; + GtkCellRenderer *renderer; + + dialog->priv = E_HTML_EDITOR_SPELL_CHECK_DIALOG_GET_PRIVATE (dialog); + + main_layout = e_html_editor_dialog_get_container (E_HTML_EDITOR_DIALOG (dialog)); + + /* == Suggestions == */ + widget = gtk_label_new (""); + gtk_label_set_markup (GTK_LABEL (widget), _("Suggestions")); + gtk_misc_set_alignment (GTK_MISC (widget), 0, 0.5); + gtk_grid_attach (main_layout, widget, 0, 0, 2, 1); + dialog->priv->suggestion_label = widget; + + /* Tree view */ + widget = gtk_tree_view_new (); + gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (widget), FALSE); + gtk_widget_set_vexpand (widget, TRUE); + gtk_widget_set_hexpand (widget, TRUE); + dialog->priv->tree_view = widget; + + /* Column */ + column = gtk_tree_view_column_new_with_attributes ( + "", gtk_cell_renderer_text_new (), "text", 0, NULL); + gtk_tree_view_append_column (GTK_TREE_VIEW (widget), column); + + /* Store */ + store = gtk_list_store_new (1, G_TYPE_STRING); + gtk_tree_view_set_model ( + GTK_TREE_VIEW (widget), GTK_TREE_MODEL (store)); + + /* Scrolled Window */ + widget = gtk_scrolled_window_new (NULL, NULL); + gtk_widget_set_size_request (widget, 150, -1); + 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_ETCHED_IN); + gtk_container_add (GTK_CONTAINER (widget), dialog->priv->tree_view); + gtk_grid_attach (main_layout, widget, 0, 1, 1, 5); + + /* Replace */ + widget = gtk_button_new_with_mnemonic (_("Replace")); + gtk_button_set_image ( + GTK_BUTTON (widget), + gtk_image_new_from_stock ( + GTK_STOCK_CONVERT, GTK_ICON_SIZE_BUTTON)); + gtk_grid_attach (main_layout, widget, 1, 1, 1, 1); + dialog->priv->replace_button = widget; + + g_signal_connect_swapped ( + widget, "clicked", + G_CALLBACK (html_editor_spell_check_dialog_replace), dialog); + + /* Replace All */ + widget = gtk_button_new_with_mnemonic (_("Replace All")); + gtk_button_set_image ( + GTK_BUTTON (widget), + gtk_image_new_from_stock ( + GTK_STOCK_APPLY, GTK_ICON_SIZE_BUTTON)); + gtk_grid_attach (main_layout, widget, 1, 2, 1, 1); + dialog->priv->replace_all_button = widget; + + g_signal_connect_swapped ( + widget, "clicked", + G_CALLBACK (html_editor_spell_check_dialog_replace_all), dialog); + + /* Ignore */ + widget = gtk_button_new_with_mnemonic (_("Ignore")); + gtk_button_set_image ( + GTK_BUTTON (widget), + gtk_image_new_from_stock ( + GTK_STOCK_CLEAR, GTK_ICON_SIZE_BUTTON)); + gtk_grid_attach (main_layout, widget, 1, 3, 1, 1); + dialog->priv->ignore_button = widget; + + g_signal_connect_swapped ( + widget, "clicked", + G_CALLBACK (html_editor_spell_check_dialog_ignore), dialog); + + /* Skip */ + widget = gtk_button_new_with_mnemonic (_("Skip")); + gtk_button_set_image ( + GTK_BUTTON (widget), + gtk_image_new_from_stock ( + GTK_STOCK_GO_FORWARD, GTK_ICON_SIZE_BUTTON)); + gtk_grid_attach (main_layout, widget, 1, 4, 1, 1); + dialog->priv->skip_button = widget; + + g_signal_connect_swapped ( + widget, "clicked", + G_CALLBACK (html_editor_spell_check_dialog_next), dialog); + + /* Back */ + widget = gtk_button_new_with_mnemonic (_("Back")); + gtk_button_set_image ( + GTK_BUTTON (widget), + gtk_image_new_from_stock ( + GTK_STOCK_GO_BACK, GTK_ICON_SIZE_BUTTON)); + gtk_grid_attach (main_layout, widget, 1, 5, 1, 1); + + g_signal_connect_swapped ( + widget, "clicked", + G_CALLBACK (html_editor_spell_check_dialog_prev), dialog); + + /* Dictionary label */ + widget = gtk_label_new (""); + gtk_label_set_markup (GTK_LABEL (widget), _("Dictionary")); + gtk_misc_set_alignment (GTK_MISC (widget), 0, 0); + gtk_grid_attach (main_layout, widget, 0, 6, 2, 1); + + /* Dictionaries combo */ + widget = gtk_combo_box_new (); + gtk_grid_attach (main_layout, widget, 0, 7, 1, 1); + dialog->priv->dictionary_combo = widget; + + renderer = gtk_cell_renderer_text_new (); + gtk_cell_layout_pack_start ( + GTK_CELL_LAYOUT (widget), renderer, TRUE); + gtk_cell_layout_add_attribute ( + GTK_CELL_LAYOUT (widget), renderer, "text", 0); + + g_signal_connect_swapped ( + widget, "changed", + G_CALLBACK (html_editor_spell_check_dialog_set_dictionary), dialog); + + /* Add Word button */ + widget = gtk_button_new_with_mnemonic (_("Add Word")); + gtk_button_set_image ( + GTK_BUTTON (widget), + gtk_image_new_from_stock ( + GTK_STOCK_ADD, GTK_ICON_SIZE_BUTTON)); + gtk_grid_attach (main_layout, widget, 1, 7, 1, 1); + dialog->priv->add_word_button = widget; + + g_signal_connect_swapped ( + widget, "clicked", + G_CALLBACK (html_editor_spell_check_dialog_learn), dialog); + + gtk_widget_show_all (GTK_WIDGET (main_layout)); +} + +GtkWidget * +e_html_editor_spell_check_dialog_new (EHTMLEditor *editor) +{ + return g_object_new ( + E_TYPE_HTML_EDITOR_SPELL_CHECK_DIALOG, + "editor", editor, + "title", N_("Spell Checking"), + NULL); +} + +void +e_html_editor_spell_check_dialog_update_dictionaries (EHTMLEditorSpellCheckDialog *dialog) +{ + EHTMLEditor *editor; + EHTMLEditorView *view; + ESpellChecker *spell_checker; + GtkComboBox *combo_box; + GtkListStore *store; + GQueue queue = G_QUEUE_INIT; + gchar **languages; + guint n_languages = 0; + guint ii; + + g_return_if_fail (E_IS_HTML_EDITOR_SPELL_CHECK_DIALOG (dialog)); + + editor = e_html_editor_dialog_get_editor (E_HTML_EDITOR_DIALOG (dialog)); + view = e_html_editor_get_view (editor); + spell_checker = e_html_editor_view_get_spell_checker (view); + + languages = e_spell_checker_list_active_languages ( + spell_checker, &n_languages); + for (ii = 0; ii < n_languages; ii++) { + ESpellDictionary *dictionary; + + dictionary = e_spell_checker_ref_dictionary ( + spell_checker, languages[ii]); + if (dictionary != NULL) + g_queue_push_tail (&queue, dictionary); + else + g_warning ( + "%s: No '%s' dictionary found", + G_STRFUNC, languages[ii]); + } + g_strfreev (languages); + + /* Populate a list store for the combo box. */ + store = gtk_list_store_new ( + NUM_COLUMNS, + G_TYPE_STRING, /* COLUMN_NAME */ + E_TYPE_SPELL_DICTIONARY); /* COLUMN_DICTIONARY */ + + while (!g_queue_is_empty (&queue)) { + ESpellDictionary *dictionary; + GtkTreeIter iter; + const gchar *name; + + dictionary = g_queue_pop_head (&queue); + name = e_spell_dictionary_get_name (dictionary); + + gtk_list_store_append (store, &iter); + gtk_list_store_set ( + store, &iter, + COLUMN_NAME, name, + COLUMN_DICTIONARY, dictionary, + -1); + + g_object_unref (dictionary); + } + + /* FIXME Try to restore selection. */ + combo_box = GTK_COMBO_BOX (dialog->priv->dictionary_combo); + gtk_combo_box_set_model (combo_box, GTK_TREE_MODEL (store)); + gtk_combo_box_set_active (combo_box, 0); + + g_object_unref (store); +} + diff --git a/e-util/e-html-editor-spell-check-dialog.h b/e-util/e-html-editor-spell-check-dialog.h new file mode 100644 index 0000000000..4191cd98ad --- /dev/null +++ b/e-util/e-html-editor-spell-check-dialog.h @@ -0,0 +1,73 @@ +/* + * e-html-editor-spell-check-dialog.h + * + * Copyright (C) 2012 Dan Vrátil + * + * 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_HTML_EDITOR_SPELL_CHECK_DIALOG_H +#define E_HTML_EDITOR_SPELL_CHECK_DIALOG_H + +#include + +/* Standard GObject macros */ +#define E_TYPE_HTML_EDITOR_SPELL_CHECK_DIALOG \ + (e_html_editor_spell_check_dialog_get_type ()) +#define E_HTML_EDITOR_SPELL_CHECK_DIALOG(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST \ + ((obj), E_TYPE_HTML_EDITOR_SPELL_CHECK_DIALOG, EHTMLEditorSpellCheckDialog)) +#define E_HTML_EDITOR_SPELL_CHECK_DIALOG_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_CAST \ + ((cls), E_TYPE_HTML_EDITOR_SPELL_CHECK_DIALOG, EHTMLEditorSpellCheckDialogClass)) +#define E_IS_HTML_EDITOR_SPELL_CHECK_DIALOG(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE \ + ((obj), E_TYPE_HTML_EDITOR_SPELL_CHECK_DIALOG)) +#define E_IS_HTML_EDITOR_SPELL_CHECK_DIALOG_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_TYPE \ + ((cls), E_TYPE_HTML_EDITOR_SPELL_CHECK_DIALOG)) +#define E_HTML_EDITOR_SPELL_CHECK_DIALOG_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS \ + ((obj), E_TYPE_HTML_EDITOR_SPELL_CHECK_DIALOG, EHTMLEditorSpellCheckDialogClass)) + +G_BEGIN_DECLS + +typedef struct _EHTMLEditorSpellCheckDialog EHTMLEditorSpellCheckDialog; +typedef struct _EHTMLEditorSpellCheckDialogClass EHTMLEditorSpellCheckDialogClass; +typedef struct _EHTMLEditorSpellCheckDialogPrivate EHTMLEditorSpellCheckDialogPrivate; + +struct _EHTMLEditorSpellCheckDialog { + EHTMLEditorDialog parent; + EHTMLEditorSpellCheckDialogPrivate *priv; +}; + +struct _EHTMLEditorSpellCheckDialogClass { + EHTMLEditorDialogClass parent_class; +}; + +GType e_html_editor_spell_check_dialog_get_type + (void) G_GNUC_CONST; +GtkWidget * e_html_editor_spell_check_dialog_new + (EHTMLEditor *editor); +void e_html_editor_spell_check_dialog_update_dictionaries + (EHTMLEditorSpellCheckDialog *dialog); + +G_END_DECLS + +#endif /* E_HTML_EDITOR_SPELL_CHECK_DIALOG_H */ diff --git a/e-util/e-html-editor-table-dialog.c b/e-util/e-html-editor-table-dialog.c new file mode 100644 index 0000000000..467d2a62e8 --- /dev/null +++ b/e-util/e-html-editor-table-dialog.c @@ -0,0 +1,866 @@ +/* + * e-html-editor-table-dialog.h + * + * Copyright (C) 2012 Dan Vrátil + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public 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-html-editor-table-dialog.h" + +#include + +#include "e-color-combo.h" +#include "e-html-editor-utils.h" +#include "e-image-chooser-dialog.h" +#include "e-misc-utils.h" + +#define E_HTML_EDITOR_TABLE_DIALOG_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE \ + ((obj), E_TYPE_HTML_EDITOR_TABLE_DIALOG, EHTMLEditorTableDialogPrivate)) + +struct _EHTMLEditorTableDialogPrivate { + GtkWidget *rows_edit; + GtkWidget *columns_edit; + + GtkWidget *width_edit; + GtkWidget *width_units; + GtkWidget *width_check; + + GtkWidget *spacing_edit; + GtkWidget *padding_edit; + GtkWidget *border_edit; + + GtkWidget *alignment_combo; + + GtkWidget *background_color_button; + GtkWidget *background_image_button; + GtkWidget *image_chooser_dialog; + + WebKitDOMHTMLTableElement *table_element; +}; + +static GdkRGBA white = { 1, 1, 1, 1 }; + +G_DEFINE_TYPE ( + EHTMLEditorTableDialog, + e_html_editor_table_dialog, + E_TYPE_HTML_EDITOR_DIALOG); + +static WebKitDOMElement * +html_editor_table_dialog_create_table (EHTMLEditorTableDialog *dialog) +{ + EHTMLEditor *editor; + EHTMLEditorSelection *editor_selection; + EHTMLEditorView *view; + WebKitDOMDocument *document; + WebKitDOMElement *table, *br, *caret, *parent, *element; + gint i; + + editor = e_html_editor_dialog_get_editor (E_HTML_EDITOR_DIALOG (dialog)); + view = e_html_editor_get_view (editor); + editor_selection = e_html_editor_view_get_selection (view); + + document = webkit_web_view_get_dom_document ( + WEBKIT_WEB_VIEW (view)); + + /* Default 3x3 table */ + table = webkit_dom_document_create_element (document, "TABLE", NULL); + for (i = 0; i < 3; i++) { + WebKitDOMHTMLElement *row; + gint j; + + row = webkit_dom_html_table_element_insert_row ( + WEBKIT_DOM_HTML_TABLE_ELEMENT (table), -1, NULL); + + for (j = 0; j < 3; j++) { + webkit_dom_html_table_row_element_insert_cell ( + WEBKIT_DOM_HTML_TABLE_ROW_ELEMENT (row), -1, NULL); + } + } + + caret = e_html_editor_selection_save_caret_position (editor_selection); + + parent = webkit_dom_node_get_parent_element (WEBKIT_DOM_NODE (caret)); + element = caret; + + while (!WEBKIT_DOM_IS_HTML_BODY_ELEMENT (parent)) { + element = parent; + parent = webkit_dom_node_get_parent_element ( + WEBKIT_DOM_NODE (parent)); + } + + br = webkit_dom_document_create_element (document, "BR", NULL); + webkit_dom_node_insert_before ( + WEBKIT_DOM_NODE (parent), + WEBKIT_DOM_NODE (br), + webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (element)), + NULL); + + /* Insert the table into body below the caret */ + webkit_dom_node_insert_before ( + WEBKIT_DOM_NODE (parent), + WEBKIT_DOM_NODE (table), + webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (element)), + NULL); + + e_html_editor_selection_clear_caret_position_marker (editor_selection); + + e_html_editor_view_set_changed (view, TRUE); + + return table; +} + +static void +html_editor_table_dialog_set_row_count (EHTMLEditorTableDialog *dialog) +{ + WebKitDOMHTMLCollection *rows; + gulong ii, current_count, expected_count; + + g_return_if_fail (dialog->priv->table_element); + + rows = webkit_dom_html_table_element_get_rows (dialog->priv->table_element); + current_count = webkit_dom_html_collection_get_length (rows); + expected_count = gtk_spin_button_get_value ( + GTK_SPIN_BUTTON (dialog->priv->rows_edit)); + + if (current_count < expected_count) { + for (ii = 0; ii < expected_count - current_count; ii++) { + webkit_dom_html_table_element_insert_row ( + dialog->priv->table_element, -1, NULL); + } + } else if (current_count > expected_count) { + for (ii = 0; ii < current_count - expected_count; ii++) { + webkit_dom_html_table_element_delete_row ( + dialog->priv->table_element, -1, NULL); + } + } +} + +static void +html_editor_table_dialog_get_row_count (EHTMLEditorTableDialog *dialog) +{ + WebKitDOMHTMLCollection *rows; + + g_return_if_fail (dialog->priv->table_element); + + rows = webkit_dom_html_table_element_get_rows (dialog->priv->table_element); + + gtk_spin_button_set_value ( + GTK_SPIN_BUTTON (dialog->priv->rows_edit), + webkit_dom_html_collection_get_length (rows)); +} + +static void +html_editor_table_dialog_set_column_count (EHTMLEditorTableDialog *dialog) +{ + WebKitDOMHTMLCollection *rows; + gulong ii, row_count, expected_columns; + + g_return_if_fail (dialog->priv->table_element); + + rows = webkit_dom_html_table_element_get_rows (dialog->priv->table_element); + row_count = webkit_dom_html_collection_get_length (rows); + expected_columns = gtk_spin_button_get_value ( + GTK_SPIN_BUTTON (dialog->priv->columns_edit)); + + for (ii = 0; ii < row_count; ii++) { + WebKitDOMHTMLTableRowElement *row; + WebKitDOMHTMLCollection *cells; + gulong jj, current_columns; + + row = WEBKIT_DOM_HTML_TABLE_ROW_ELEMENT ( + webkit_dom_html_collection_item (rows, ii)); + + cells = webkit_dom_html_table_row_element_get_cells (row); + current_columns = webkit_dom_html_collection_get_length (cells); + + if (current_columns < expected_columns) { + for (jj = 0; jj < expected_columns - current_columns; jj++) { + webkit_dom_html_table_row_element_insert_cell ( + row, -1, NULL); + } + } else if (expected_columns < current_columns) { + for (jj = 0; jj < current_columns - expected_columns; jj++) { + webkit_dom_html_table_row_element_delete_cell ( + row, -1, NULL); + } + } + } +} + +static void +html_editor_table_dialog_get_column_count (EHTMLEditorTableDialog *dialog) +{ + WebKitDOMHTMLCollection *rows, *columns; + WebKitDOMNode *row; + + g_return_if_fail (dialog->priv->table_element); + + rows = webkit_dom_html_table_element_get_rows (dialog->priv->table_element); + row = webkit_dom_html_collection_item (rows, 0); + + columns = webkit_dom_html_table_row_element_get_cells ( + WEBKIT_DOM_HTML_TABLE_ROW_ELEMENT (row)); + + gtk_spin_button_set_value ( + GTK_SPIN_BUTTON (dialog->priv->columns_edit), + webkit_dom_html_collection_get_length (columns)); +} + +static void +html_editor_table_dialog_set_width (EHTMLEditorTableDialog *dialog) +{ + gchar *width; + + g_return_if_fail (dialog->priv->table_element); + + if (gtk_toggle_button_get_active ( + GTK_TOGGLE_BUTTON (dialog->priv->width_check))) { + gchar *units; + + units = gtk_combo_box_text_get_active_text ( + GTK_COMBO_BOX_TEXT (dialog->priv->width_units)); + width = g_strdup_printf ( + "%d%s", + gtk_spin_button_get_value_as_int ( + GTK_SPIN_BUTTON (dialog->priv->width_edit)), + units); + g_free (units); + + gtk_widget_set_sensitive (dialog->priv->width_edit, TRUE); + gtk_widget_set_sensitive (dialog->priv->width_units, TRUE); + } else { + width = g_strdup ("auto"); + + gtk_widget_set_sensitive (dialog->priv->width_edit, FALSE); + gtk_widget_set_sensitive (dialog->priv->width_units, FALSE); + } + + webkit_dom_html_table_element_set_width ( + dialog->priv->table_element, width); + g_free (width); +} + +static void +html_editor_table_dialog_get_width (EHTMLEditorTableDialog *dialog) +{ + gchar *width; + + width = webkit_dom_html_table_element_get_width (dialog->priv->table_element); + if (!width || !*width || g_ascii_strncasecmp (width, "auto", 4) == 0) { + gtk_toggle_button_set_active ( + GTK_TOGGLE_BUTTON (dialog->priv->width_check), FALSE); + gtk_spin_button_set_value ( + GTK_SPIN_BUTTON (dialog->priv->width_edit), 100); + gtk_combo_box_set_active_id ( + GTK_COMBO_BOX (dialog->priv->width_units), "units-percent"); + } else { + gint width_int = atoi (width); + + gtk_toggle_button_set_active ( + GTK_TOGGLE_BUTTON (dialog->priv->width_check), TRUE); + gtk_spin_button_set_value ( + GTK_SPIN_BUTTON (dialog->priv->width_edit), width_int); + gtk_combo_box_set_active_id ( + GTK_COMBO_BOX (dialog->priv->width_units), + ((strstr (width, "%") == NULL) ? + "units-px" : "units-percent")); + } + g_free (width); +} + +static void +html_editor_table_dialog_set_alignment (EHTMLEditorTableDialog *dialog) +{ + g_return_if_fail (dialog->priv->table_element); + + webkit_dom_html_table_element_set_align ( + dialog->priv->table_element, + gtk_combo_box_get_active_id ( + GTK_COMBO_BOX (dialog->priv->alignment_combo))); +} + +static void +html_editor_table_dialog_get_alignment (EHTMLEditorTableDialog *dialog) +{ + gchar *alignment; + + g_return_if_fail (dialog->priv->table_element); + + alignment = webkit_dom_html_table_element_get_align ( + dialog->priv->table_element); + + gtk_combo_box_set_active_id ( + GTK_COMBO_BOX (dialog->priv->alignment_combo), alignment); + + g_free (alignment); +} + +static void +html_editor_table_dialog_set_padding (EHTMLEditorTableDialog *dialog) +{ + gchar *padding; + + g_return_if_fail (dialog->priv->table_element); + + padding = g_strdup_printf ( + "%d", + gtk_spin_button_get_value_as_int ( + GTK_SPIN_BUTTON (dialog->priv->padding_edit))); + + webkit_dom_html_table_element_set_cell_padding ( + dialog->priv->table_element, padding); + + g_free (padding); +} + +static void +html_editor_table_dialog_get_padding (EHTMLEditorTableDialog *dialog) +{ + gchar *padding; + gint padding_int; + + g_return_if_fail (dialog->priv->table_element); + + padding = webkit_dom_html_table_element_get_cell_padding ( + dialog->priv->table_element); + if (!padding || !*padding) { + padding_int = 0; + } else { + padding_int = atoi (padding); + } + + gtk_spin_button_set_value ( + GTK_SPIN_BUTTON (dialog->priv->padding_edit), padding_int); + + g_free (padding); +} + +static void +html_editor_table_dialog_set_spacing (EHTMLEditorTableDialog *dialog) +{ + gchar *spacing; + + g_return_if_fail (dialog->priv->table_element); + + spacing = g_strdup_printf ( + "%d", + gtk_spin_button_get_value_as_int ( + GTK_SPIN_BUTTON (dialog->priv->spacing_edit))); + + webkit_dom_html_table_element_set_cell_spacing ( + dialog->priv->table_element, spacing); + + g_free (spacing); +} + +static void +html_editor_table_dialog_get_spacing (EHTMLEditorTableDialog *dialog) +{ + gchar *spacing; + gint spacing_int; + + g_return_if_fail (dialog->priv->table_element); + + spacing = webkit_dom_html_table_element_get_cell_spacing ( + dialog->priv->table_element); + if (!spacing || !*spacing) { + spacing_int = 0; + } else { + spacing_int = atoi (spacing); + } + + gtk_spin_button_set_value ( + GTK_SPIN_BUTTON (dialog->priv->spacing_edit), spacing_int); + + g_free (spacing); +} + +static void +html_editor_table_dialog_set_border (EHTMLEditorTableDialog *dialog) +{ + gchar *border; + + g_return_if_fail (dialog->priv->table_element); + + border = g_strdup_printf ( + "%d", + gtk_spin_button_get_value_as_int ( + GTK_SPIN_BUTTON (dialog->priv->border_edit))); + + webkit_dom_html_table_element_set_border ( + dialog->priv->table_element, border); + + g_free (border); +} + +static void +html_editor_table_dialog_get_border (EHTMLEditorTableDialog *dialog) +{ + gchar *border; + gint border_int; + + g_return_if_fail (dialog->priv->table_element); + + border = webkit_dom_html_table_element_get_border ( + dialog->priv->table_element); + if (!border || !*border) { + border_int = 0; + } else { + border_int = atoi (border); + } + + gtk_spin_button_set_value ( + GTK_SPIN_BUTTON (dialog->priv->border_edit), border_int); + + g_free (border); +} + +static void +html_editor_table_dialog_set_background_color (EHTMLEditorTableDialog *dialog) +{ + gchar *color; + GdkRGBA rgba; + + g_return_if_fail (dialog->priv->table_element); + + e_color_combo_get_current_color ( + E_COLOR_COMBO (dialog->priv->background_color_button), &rgba); + color = g_strdup_printf ( + "#%06x", e_rgba_to_value (&rgba)); + + webkit_dom_html_table_element_set_bg_color ( + dialog->priv->table_element, color); + + g_free (color); +} + +static void +html_editor_table_dialog_get_background_color (EHTMLEditorTableDialog *dialog) +{ + gchar *color; + GdkRGBA rgba; + + g_return_if_fail (dialog->priv->table_element); + + color = webkit_dom_html_table_element_get_bg_color ( + dialog->priv->table_element); + + gdk_rgba_parse (&rgba, color); + + e_color_combo_set_current_color ( + E_COLOR_COMBO (dialog->priv->background_color_button), &rgba); + + g_free (color); +} + +static void +html_editor_table_dialog_set_background_image (EHTMLEditorTableDialog *dialog) +{ + const gchar *filename; + + g_return_if_fail (dialog->priv->table_element); + + filename = gtk_file_chooser_get_filename ( + GTK_FILE_CHOOSER (dialog->priv->background_image_button)); + + if (filename) { + webkit_dom_element_set_attribute ( + WEBKIT_DOM_ELEMENT (dialog->priv->table_element), + "background", filename, NULL); + } else { + webkit_dom_element_remove_attribute ( + WEBKIT_DOM_ELEMENT (dialog->priv->table_element), + "background"); + } +} + +static void +html_editor_table_dialog_get_background_image (EHTMLEditorTableDialog *dialog) +{ + g_return_if_fail (dialog->priv->table_element); + + if (!webkit_dom_element_has_attribute ( + WEBKIT_DOM_ELEMENT (dialog->priv->table_element), "background")) { + + gtk_file_chooser_unselect_all ( + GTK_FILE_CHOOSER (dialog->priv->background_image_button)); + return; + } else { + gchar *background; + + background = webkit_dom_element_get_attribute ( + WEBKIT_DOM_ELEMENT (dialog->priv->table_element), "background"); + + gtk_file_chooser_set_filename ( + GTK_FILE_CHOOSER (dialog->priv->background_image_button), + background); + + g_free (background); + } +} + +static void +html_editor_table_dialog_get_values (EHTMLEditorTableDialog *dialog) +{ + html_editor_table_dialog_get_row_count (dialog); + html_editor_table_dialog_get_column_count (dialog); + html_editor_table_dialog_get_width (dialog); + html_editor_table_dialog_get_alignment (dialog); + html_editor_table_dialog_get_spacing (dialog); + html_editor_table_dialog_get_padding (dialog); + html_editor_table_dialog_get_border (dialog); + html_editor_table_dialog_get_background_color (dialog); + html_editor_table_dialog_get_background_image (dialog); +} + +static void +html_editor_table_dialog_reset_values (EHTMLEditorTableDialog *dialog) +{ + gtk_spin_button_set_value ( + GTK_SPIN_BUTTON (dialog->priv->rows_edit), 3); + gtk_spin_button_set_value ( + GTK_SPIN_BUTTON (dialog->priv->columns_edit), 3); + gtk_combo_box_set_active_id ( + GTK_COMBO_BOX (dialog->priv->alignment_combo), "left"); + + gtk_toggle_button_set_active ( + GTK_TOGGLE_BUTTON (dialog->priv->width_check), TRUE); + gtk_spin_button_set_value ( + GTK_SPIN_BUTTON (dialog->priv->width_edit), 100); + gtk_combo_box_set_active_id ( + GTK_COMBO_BOX (dialog->priv->width_units), "units-percent"); + + gtk_spin_button_set_value ( + GTK_SPIN_BUTTON (dialog->priv->spacing_edit), 2); + gtk_spin_button_set_value ( + GTK_SPIN_BUTTON (dialog->priv->padding_edit), 1); + gtk_spin_button_set_value ( + GTK_SPIN_BUTTON (dialog->priv->border_edit), 1); + + e_color_combo_set_current_color ( + E_COLOR_COMBO (dialog->priv->background_color_button), &white); + gtk_file_chooser_unselect_all ( + GTK_FILE_CHOOSER (dialog->priv->background_image_button)); + + html_editor_table_dialog_set_row_count (dialog); + html_editor_table_dialog_set_column_count (dialog); + html_editor_table_dialog_set_width (dialog); + html_editor_table_dialog_set_alignment (dialog); + html_editor_table_dialog_set_spacing (dialog); + html_editor_table_dialog_set_padding (dialog); + html_editor_table_dialog_set_border (dialog); + html_editor_table_dialog_set_background_color (dialog); + html_editor_table_dialog_set_background_image (dialog); +} + +static void +html_editor_table_dialog_show (GtkWidget *widget) +{ + EHTMLEditorTableDialog *dialog; + EHTMLEditor *editor; + EHTMLEditorView *view; + WebKitDOMDocument *document; + WebKitDOMDOMWindow *window; + WebKitDOMDOMSelection *selection; + + dialog = E_HTML_EDITOR_TABLE_DIALOG (widget); + editor = e_html_editor_dialog_get_editor (E_HTML_EDITOR_DIALOG (dialog)); + view = e_html_editor_get_view (editor); + + document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view)); + 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)) { + WebKitDOMElement *table; + WebKitDOMRange *range; + + range = webkit_dom_dom_selection_get_range_at (selection, 0, NULL); + table = e_html_editor_dom_node_find_parent_element ( + webkit_dom_range_get_start_container (range, NULL), "TABLE"); + + if (!table) { + dialog->priv->table_element = WEBKIT_DOM_HTML_TABLE_ELEMENT ( + html_editor_table_dialog_create_table (dialog)); + html_editor_table_dialog_reset_values (dialog); + } else { + dialog->priv->table_element = + WEBKIT_DOM_HTML_TABLE_ELEMENT (table); + html_editor_table_dialog_get_values (dialog); + } + } + + /* Chain up to parent implementation */ + GTK_WIDGET_CLASS (e_html_editor_table_dialog_parent_class)->show (widget); +} + +static void +html_editor_table_dialog_hide (GtkWidget *widget) +{ + EHTMLEditorTableDialogPrivate *priv; + + priv = E_HTML_EDITOR_TABLE_DIALOG_GET_PRIVATE (widget); + + priv->table_element = NULL; + + GTK_WIDGET_CLASS (e_html_editor_table_dialog_parent_class)->hide (widget); +} + +static void +e_html_editor_table_dialog_class_init (EHTMLEditorTableDialogClass *class) +{ + GtkWidgetClass *widget_class; + + g_type_class_add_private (class, sizeof (EHTMLEditorTableDialogPrivate)); + + widget_class = GTK_WIDGET_CLASS (class); + widget_class->show = html_editor_table_dialog_show; + widget_class->hide = html_editor_table_dialog_hide; +} + +static void +e_html_editor_table_dialog_init (EHTMLEditorTableDialog *dialog) +{ + GtkGrid *main_layout, *grid; + GtkWidget *widget; + GtkFileFilter *file_filter; + + dialog->priv = E_HTML_EDITOR_TABLE_DIALOG_GET_PRIVATE (dialog); + + main_layout = e_html_editor_dialog_get_container (E_HTML_EDITOR_DIALOG (dialog)); + + /* == General == */ + widget = gtk_label_new (""); + gtk_label_set_markup (GTK_LABEL (widget), _("General")); + gtk_misc_set_alignment (GTK_MISC (widget), 0, 0.5); + gtk_grid_attach (main_layout, widget, 0, 0, 1, 1); + + grid = GTK_GRID (gtk_grid_new ()); + gtk_grid_set_row_spacing (grid, 5); + gtk_grid_set_column_spacing (grid, 5); + gtk_grid_attach (main_layout, GTK_WIDGET (grid), 0, 1, 1, 1); + gtk_widget_set_margin_left (GTK_WIDGET (grid), 10); + + /* Rows */ + widget = gtk_image_new_from_icon_name ("stock_select-row", GTK_ICON_SIZE_BUTTON); + gtk_grid_attach (grid, widget, 0, 0, 1, 1); + + widget = gtk_spin_button_new_with_range (1, G_MAXINT, 1); + gtk_spin_button_set_digits (GTK_SPIN_BUTTON (widget), 0); + gtk_grid_attach (grid, widget, 2, 0, 1, 1); + g_signal_connect_swapped ( + widget, "value-changed", + G_CALLBACK (html_editor_table_dialog_set_row_count), dialog); + dialog->priv->rows_edit = widget; + + widget = gtk_label_new_with_mnemonic (_("_Rows:")); + gtk_label_set_justify (GTK_LABEL (widget), GTK_JUSTIFY_RIGHT); + gtk_label_set_mnemonic_widget (GTK_LABEL (widget), dialog->priv->rows_edit); + gtk_grid_attach (grid, widget, 1, 0, 1, 1); + + /* Columns */ + widget = gtk_image_new_from_icon_name ("stock_select-column", GTK_ICON_SIZE_BUTTON); + gtk_grid_attach (grid, widget, 3, 0, 1, 1); + + widget = gtk_spin_button_new_with_range (1, G_MAXINT, 1); + gtk_spin_button_set_digits (GTK_SPIN_BUTTON (widget), 0); + gtk_grid_attach (grid, widget, 5, 0, 1, 1); + g_signal_connect_swapped ( + widget, "value-changed", + G_CALLBACK (html_editor_table_dialog_set_column_count), dialog); + dialog->priv->columns_edit = widget; + + widget = gtk_label_new_with_mnemonic (_("C_olumns:")); + gtk_label_set_justify (GTK_LABEL (widget), GTK_JUSTIFY_RIGHT); + gtk_label_set_mnemonic_widget (GTK_LABEL (widget), dialog->priv->columns_edit); + gtk_grid_attach (grid, widget, 4, 0, 1, 1); + + /* == Layout == */ + widget = gtk_label_new (""); + gtk_label_set_markup (GTK_LABEL (widget), _("Layout")); + gtk_misc_set_alignment (GTK_MISC (widget), 0, 0.5); + gtk_grid_attach (main_layout, widget, 0, 2, 1, 1); + + grid = GTK_GRID (gtk_grid_new ()); + gtk_grid_set_row_spacing (grid, 5); + gtk_grid_set_column_spacing (grid, 5); + gtk_grid_attach (main_layout, GTK_WIDGET (grid), 0, 3, 1, 1); + gtk_widget_set_margin_left (GTK_WIDGET (grid), 10); + + /* Width */ + widget = gtk_check_button_new_with_mnemonic (_("_Width:")); + gtk_grid_attach (grid, widget, 0, 0, 1, 1); + g_signal_connect_swapped ( + widget, "toggled", + G_CALLBACK (html_editor_table_dialog_set_width), dialog); + dialog->priv->width_check = widget; + + widget = gtk_spin_button_new_with_range (1, 100, 1); + gtk_spin_button_set_digits (GTK_SPIN_BUTTON (widget), 0); + gtk_grid_attach (grid, widget, 1, 0, 1, 1); + g_signal_connect_swapped ( + widget, "value-changed", + G_CALLBACK (html_editor_table_dialog_set_width), dialog); + dialog->priv->width_edit = widget; + + widget = gtk_combo_box_text_new (); + gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (widget), "units-px", "px"); + gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (widget), "units-percent", "%"); + gtk_grid_attach (grid, widget, 2, 0, 1, 1); + g_signal_connect_swapped ( + widget, "changed", + G_CALLBACK (html_editor_table_dialog_set_width), dialog); + dialog->priv->width_units = widget; + + /* Spacing */ + widget = gtk_spin_button_new_with_range (0, G_MAXINT, 1); + gtk_spin_button_set_digits (GTK_SPIN_BUTTON (widget), 0); + gtk_grid_attach (grid, widget, 5, 0, 1, 1); + g_signal_connect_swapped ( + widget, "value-changed", + G_CALLBACK (html_editor_table_dialog_set_spacing), dialog); + dialog->priv->spacing_edit = widget; + + widget = gtk_label_new_with_mnemonic (_("_Spacing:")); + gtk_label_set_justify (GTK_LABEL (widget), GTK_JUSTIFY_RIGHT); + gtk_label_set_mnemonic_widget (GTK_LABEL (widget), dialog->priv->spacing_edit); + gtk_grid_attach (grid, widget, 4, 0, 1, 1); + + widget = gtk_label_new ("px"); + gtk_grid_attach (grid, widget, 6, 0, 1, 1); + + /* Padding */ + widget = gtk_spin_button_new_with_range (0, G_MAXINT, 1); + gtk_spin_button_set_digits (GTK_SPIN_BUTTON (widget), 0); + gtk_grid_attach (grid, widget, 5, 1, 1, 1); + g_signal_connect_swapped ( + widget, "value-changed", + G_CALLBACK (html_editor_table_dialog_set_padding), dialog); + dialog->priv->padding_edit = widget; + + widget = gtk_label_new_with_mnemonic (_("_Padding:")); + gtk_label_set_justify (GTK_LABEL (widget), GTK_JUSTIFY_RIGHT); + gtk_label_set_mnemonic_widget (GTK_LABEL (widget), dialog->priv->padding_edit); + gtk_grid_attach (grid, widget, 4, 1, 1, 1); + + widget = gtk_label_new ("px"); + gtk_grid_attach (grid, widget, 6, 1, 1, 1); + + /* Border */ + widget = gtk_spin_button_new_with_range (0, G_MAXINT, 1); + gtk_spin_button_set_digits (GTK_SPIN_BUTTON (widget), 0); + gtk_grid_attach (grid, widget, 5, 2, 1, 1); + g_signal_connect_swapped ( + widget, "value-changed", + G_CALLBACK (html_editor_table_dialog_set_border), dialog); + dialog->priv->border_edit = widget; + + widget = gtk_label_new_with_mnemonic (_("_Border:")); + gtk_label_set_justify (GTK_LABEL (widget), GTK_JUSTIFY_RIGHT); + gtk_label_set_mnemonic_widget (GTK_LABEL (widget), dialog->priv->border_edit); + gtk_grid_attach (grid, widget, 4, 2, 1, 1); + + widget = gtk_label_new ("px"); + gtk_grid_attach (grid, widget, 6, 2, 1, 1); + + /* Alignment */ + widget = gtk_combo_box_text_new (); + gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (widget), "left", _("Left")); + gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (widget), "center", _("Center")); + gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (widget), "right", _("Right")); + gtk_grid_attach (grid, widget, 1, 1, 2, 1); + g_signal_connect_swapped ( + widget, "changed", + G_CALLBACK (html_editor_table_dialog_set_alignment), dialog); + dialog->priv->alignment_combo = widget; + + widget = gtk_label_new_with_mnemonic (_("_Alignment:")); + gtk_label_set_justify (GTK_LABEL (widget), GTK_JUSTIFY_RIGHT); + gtk_label_set_mnemonic_widget (GTK_LABEL (widget), dialog->priv->alignment_combo); + gtk_grid_attach (grid, widget, 0, 1, 1, 1); + + /* == Background == */ + widget = gtk_label_new (""); + gtk_label_set_markup (GTK_LABEL (widget), _("Background")); + gtk_misc_set_alignment (GTK_MISC (widget), 0, 0.5); + gtk_grid_attach (main_layout, widget, 0, 4, 1, 1); + + grid = GTK_GRID (gtk_grid_new ()); + gtk_grid_set_row_spacing (grid, 5); + gtk_grid_set_column_spacing (grid, 5); + gtk_grid_attach (main_layout, GTK_WIDGET (grid), 0, 5, 1, 1); + gtk_widget_set_margin_left (GTK_WIDGET (grid), 10); + + /* Color */ + widget = e_color_combo_new (); + e_color_combo_set_default_color (E_COLOR_COMBO (widget), &white); + gtk_widget_set_hexpand (widget, TRUE); + gtk_grid_attach (grid, widget, 1, 0, 1, 1); + g_signal_connect_swapped ( + widget, "notify::current-color", + G_CALLBACK (html_editor_table_dialog_set_background_color), dialog); + dialog->priv->background_color_button = widget; + + widget = gtk_label_new_with_mnemonic (_("_Color:")); + gtk_label_set_justify (GTK_LABEL (widget), GTK_JUSTIFY_RIGHT); + gtk_label_set_mnemonic_widget ( + GTK_LABEL (widget), dialog->priv->background_color_button); + gtk_grid_attach (grid, widget, 0, 0, 1, 1); + + /* Image */ + widget = e_image_chooser_dialog_new ( + _("Choose Background Image"), + GTK_WINDOW (dialog)); + dialog->priv->image_chooser_dialog = widget; + + file_filter = gtk_file_filter_new (); + gtk_file_filter_set_name (file_filter, _("Images")); + gtk_file_filter_add_mime_type (file_filter, "image/*"); + + widget = gtk_file_chooser_button_new_with_dialog ( + dialog->priv->image_chooser_dialog); + gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (widget), file_filter); + gtk_widget_set_hexpand (widget, TRUE); + gtk_grid_attach (grid, widget, 1, 1, 1, 1); + g_signal_connect_swapped ( + widget, "file-set", + G_CALLBACK (html_editor_table_dialog_set_background_image), dialog); + dialog->priv->background_image_button = widget; + + widget =gtk_label_new_with_mnemonic (_("Image:")); + gtk_label_set_justify (GTK_LABEL (widget), GTK_JUSTIFY_RIGHT); + gtk_label_set_mnemonic_widget ( + GTK_LABEL (widget), dialog->priv->background_image_button); + gtk_grid_attach (grid, widget, 0, 1, 1, 1); + + gtk_widget_show_all (GTK_WIDGET (main_layout)); +} + +GtkWidget * +e_html_editor_table_dialog_new (EHTMLEditor *editor) +{ + return GTK_WIDGET ( + g_object_new ( + E_TYPE_HTML_EDITOR_TABLE_DIALOG, + "editor", editor, + "title", N_("Table Properties"), + NULL)); +} + diff --git a/e-util/e-html-editor-table-dialog.h b/e-util/e-html-editor-table-dialog.h new file mode 100644 index 0000000000..70c790d22d --- /dev/null +++ b/e-util/e-html-editor-table-dialog.h @@ -0,0 +1,69 @@ +/* + * e-html-editor-table-dialog.h + * + * Copyright (C) 2012 Dan Vrátil + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public 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_HTML_EDITOR_TABLE_DIALOG_H +#define E_HTML_EDITOR_TABLE_DIALOG_H + +#include + +/* Standard GObject macros */ +#define E_TYPE_HTML_EDITOR_TABLE_DIALOG \ + (e_html_editor_table_dialog_get_type ()) +#define E_HTML_EDITOR_TABLE_DIALOG(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST \ + ((obj), E_TYPE_HTML_EDITOR_TABLE_DIALOG, EHTMLEditorTableDialog)) +#define E_HTML_EDITOR_TABLE_DIALOG_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_CAST \ + ((cls), E_TYPE_HTML_EDITOR_TABLE_DIALOG, EHTMLEditorTableDialogClass)) +#define E_IS_HTML_EDITOR_TABLE_DIALOG(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE \ + ((obj), E_TYPE_HTML_EDITOR_TABLE_DIALOG)) +#define E_IS_HTML_EDITOR_TABLE_DIALOG_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_TYPE \ + ((cls), E_TYPE_HTML_EDITOR_TABLE_DIALOG)) +#define E_HTML_EDITOR_TABLE_DIALOG_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS \ + ((obj), E_TYPE_HTML_EDITOR_TABLE_DIALOG, EHTMLEditorTableDialogClass)) + +G_BEGIN_DECLS + +typedef struct _EHTMLEditorTableDialog EHTMLEditorTableDialog; +typedef struct _EHTMLEditorTableDialogClass EHTMLEditorTableDialogClass; +typedef struct _EHTMLEditorTableDialogPrivate EHTMLEditorTableDialogPrivate; + +struct _EHTMLEditorTableDialog { + EHTMLEditorDialog parent; + EHTMLEditorTableDialogPrivate *priv; +}; + +struct _EHTMLEditorTableDialogClass { + EHTMLEditorDialogClass parent_class; +}; + +GType e_html_editor_table_dialog_get_type (void) G_GNUC_CONST; +GtkWidget * e_html_editor_table_dialog_new (EHTMLEditor *editor); + +G_END_DECLS + +#endif /* E_HTML_EDITOR_TABLE_DIALOG_H */ diff --git a/e-util/e-html-editor-text-dialog.c b/e-util/e-html-editor-text-dialog.c new file mode 100644 index 0000000000..2db792c70a --- /dev/null +++ b/e-util/e-html-editor-text-dialog.c @@ -0,0 +1,298 @@ +/* + * e-html-editor-text-dialog.c + * + * Copyright (C) 2012 Dan Vrátil + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public 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-html-editor-text-dialog.h" + +#include + +#include "e-color-combo.h" + +#define E_HTML_EDITOR_TEXT_DIALOG_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE \ + ((obj), E_TYPE_HTML_EDITOR_TEXT_DIALOG, EHTMLEditorTextDialogPrivate)) + +struct _EHTMLEditorTextDialogPrivate { + GtkWidget *bold_check; + GtkWidget *italic_check; + GtkWidget *underline_check; + GtkWidget *strikethrough_check; + + GtkWidget *color_check; + GtkWidget *size_check; +}; + +G_DEFINE_TYPE ( + EHTMLEditorTextDialog, + e_html_editor_text_dialog, + E_TYPE_HTML_EDITOR_DIALOG); + +static void +html_editor_text_dialog_set_bold (EHTMLEditorTextDialog *dialog) +{ + EHTMLEditor *editor; + EHTMLEditorView *view; + EHTMLEditorSelection *selection; + + editor = e_html_editor_dialog_get_editor (E_HTML_EDITOR_DIALOG (dialog)); + view = e_html_editor_get_view (editor); + selection = e_html_editor_view_get_selection (view); + + e_html_editor_selection_set_bold ( + selection, + gtk_toggle_button_get_active ( + GTK_TOGGLE_BUTTON (dialog->priv->bold_check))); +} + +static void +html_editor_text_dialog_set_italic (EHTMLEditorTextDialog *dialog) +{ + EHTMLEditor *editor; + EHTMLEditorView *view; + EHTMLEditorSelection *selection; + + editor = e_html_editor_dialog_get_editor (E_HTML_EDITOR_DIALOG (dialog)); + view = e_html_editor_get_view (editor); + selection = e_html_editor_view_get_selection (view); + + e_html_editor_selection_set_italic ( + selection, + gtk_toggle_button_get_active ( + GTK_TOGGLE_BUTTON (dialog->priv->italic_check))); +} + +static void +html_editor_text_dialog_set_underline (EHTMLEditorTextDialog *dialog) +{ + EHTMLEditor *editor; + EHTMLEditorView *view; + EHTMLEditorSelection *selection; + + editor = e_html_editor_dialog_get_editor (E_HTML_EDITOR_DIALOG (dialog)); + view = e_html_editor_get_view (editor); + selection = e_html_editor_view_get_selection (view); + + e_html_editor_selection_set_underline ( + selection, + gtk_toggle_button_get_active ( + GTK_TOGGLE_BUTTON (dialog->priv->underline_check))); +} + +static void +html_editor_text_dialog_set_strikethrough (EHTMLEditorTextDialog *dialog) +{ + EHTMLEditor *editor; + EHTMLEditorView *view; + EHTMLEditorSelection *selection; + + editor = e_html_editor_dialog_get_editor (E_HTML_EDITOR_DIALOG (dialog)); + view = e_html_editor_get_view (editor); + selection = e_html_editor_view_get_selection (view); + + e_html_editor_selection_set_strikethrough ( + selection, + gtk_toggle_button_get_active ( + GTK_TOGGLE_BUTTON (dialog->priv->strikethrough_check))); +} + +static void +html_editor_text_dialog_set_color (EHTMLEditorTextDialog *dialog) +{ + EHTMLEditor *editor; + EHTMLEditorView *view; + EHTMLEditorSelection *selection; + GdkRGBA rgba; + + editor = e_html_editor_dialog_get_editor (E_HTML_EDITOR_DIALOG (dialog)); + view = e_html_editor_get_view (editor); + selection = e_html_editor_view_get_selection (view); + + e_color_combo_get_current_color ( + E_COLOR_COMBO (dialog->priv->color_check), &rgba); + e_html_editor_selection_set_font_color (selection, &rgba); +} + +static void +html_editor_text_dialog_set_size (EHTMLEditorTextDialog *dialog) +{ + EHTMLEditor *editor; + EHTMLEditorView *view; + EHTMLEditorSelection *selection; + gint size; + + editor = e_html_editor_dialog_get_editor (E_HTML_EDITOR_DIALOG (dialog)); + view = e_html_editor_get_view (editor); + selection = e_html_editor_view_get_selection (view); + size = gtk_combo_box_get_active (GTK_COMBO_BOX (dialog->priv->size_check)); + + e_html_editor_selection_set_font_size (selection, size + 1); +} + +static void +html_editor_text_dialog_show (GtkWidget *widget) +{ + EHTMLEditorTextDialog *dialog; + EHTMLEditor *editor; + EHTMLEditorView *view; + EHTMLEditorSelection *selection; + GdkRGBA rgba; + + dialog = E_HTML_EDITOR_TEXT_DIALOG (widget); + editor = e_html_editor_dialog_get_editor (E_HTML_EDITOR_DIALOG (dialog)); + view = e_html_editor_get_view (editor); + selection = e_html_editor_view_get_selection (view); + + gtk_toggle_button_set_active ( + GTK_TOGGLE_BUTTON (dialog->priv->bold_check), + e_html_editor_selection_is_bold (selection)); + gtk_toggle_button_set_active ( + GTK_TOGGLE_BUTTON (dialog->priv->italic_check), + e_html_editor_selection_is_italic (selection)); + gtk_toggle_button_set_active ( + GTK_TOGGLE_BUTTON (dialog->priv->underline_check), + e_html_editor_selection_is_underline (selection)); + gtk_toggle_button_set_active ( + GTK_TOGGLE_BUTTON (dialog->priv->strikethrough_check), + e_html_editor_selection_is_strikethrough (selection)); + + gtk_combo_box_set_active ( + GTK_COMBO_BOX (dialog->priv->size_check), + e_html_editor_selection_get_font_size (selection)); + + e_html_editor_selection_get_font_color (selection, &rgba); + e_color_combo_set_current_color ( + E_COLOR_COMBO (dialog->priv->color_check), &rgba); + + GTK_WIDGET_CLASS (e_html_editor_text_dialog_parent_class)->show (widget); +} + +static void +e_html_editor_text_dialog_class_init (EHTMLEditorTextDialogClass *class) +{ + GtkWidgetClass *widget_class; + + g_type_class_add_private (class, sizeof (EHTMLEditorTextDialogPrivate)); + + widget_class = GTK_WIDGET_CLASS (class); + widget_class->show = html_editor_text_dialog_show; +} + +static void +e_html_editor_text_dialog_init (EHTMLEditorTextDialog *dialog) +{ + GtkGrid *main_layout; + GtkWidget *widget; + + dialog->priv = E_HTML_EDITOR_TEXT_DIALOG_GET_PRIVATE (dialog); + + main_layout = e_html_editor_dialog_get_container (E_HTML_EDITOR_DIALOG (dialog)); + + /* Bold */ + widget = gtk_image_new_from_stock (GTK_STOCK_BOLD, GTK_ICON_SIZE_BUTTON); + gtk_grid_attach (main_layout, widget, 0, 0, 1, 1); + + widget = gtk_check_button_new_with_mnemonic (_("_Bold")); + gtk_grid_attach (main_layout, widget, 1, 0, 1, 1); + g_signal_connect_swapped ( + widget, "toggled", + G_CALLBACK (html_editor_text_dialog_set_bold), dialog); + dialog->priv->bold_check = widget; + + /* Italic */ + widget = gtk_image_new_from_stock (GTK_STOCK_ITALIC, GTK_ICON_SIZE_BUTTON); + gtk_grid_attach (main_layout, widget, 0, 1, 1, 1); + + widget = gtk_check_button_new_with_mnemonic (_("_Italic")); + gtk_grid_attach (main_layout, widget, 1, 1, 1, 1); + g_signal_connect_swapped ( + widget, "toggled", + G_CALLBACK (html_editor_text_dialog_set_italic), dialog); + dialog->priv->italic_check = widget; + + /* Underline */ + widget = gtk_image_new_from_stock (GTK_STOCK_UNDERLINE, GTK_ICON_SIZE_BUTTON); + gtk_grid_attach (main_layout, widget, 0, 2, 1, 1); + + widget = gtk_check_button_new_with_mnemonic (_("_Underline")); + gtk_grid_attach (main_layout, widget, 1, 2, 1, 1); + g_signal_connect_swapped ( + widget, "toggled", + G_CALLBACK (html_editor_text_dialog_set_underline), dialog); + dialog->priv->underline_check = widget; + + widget = gtk_image_new_from_stock (GTK_STOCK_STRIKETHROUGH, GTK_ICON_SIZE_BUTTON); + gtk_grid_attach (main_layout, widget, 0, 3, 1, 1); + + widget = gtk_check_button_new_with_mnemonic (_("_Strikethrough")); + gtk_grid_attach (main_layout, widget, 1, 3, 1, 1); + g_signal_connect_swapped ( + widget, "toggled", + G_CALLBACK (html_editor_text_dialog_set_strikethrough), dialog); + dialog->priv->strikethrough_check = widget; + + /* Color */ + widget = e_color_combo_new (); + gtk_grid_attach (main_layout, widget, 3, 0, 1, 1); + g_signal_connect_swapped ( + widget, "notify::current-color", + G_CALLBACK (html_editor_text_dialog_set_color), dialog); + dialog->priv->color_check = widget; + + widget = gtk_label_new_with_mnemonic (_("_Color:")); + gtk_label_set_justify (GTK_LABEL (widget), GTK_JUSTIFY_RIGHT); + gtk_label_set_mnemonic_widget (GTK_LABEL (widget), dialog->priv->color_check); + gtk_grid_attach (main_layout, widget, 2, 0, 1, 1); + + /* Size */ + widget = gtk_combo_box_text_new (); + gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (widget), "minus-two", "-2"); + gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (widget), "minus-one", "-1"); + gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (widget), "minus-zero", "0"); + gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (widget), "plus-one", "+1"); + gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (widget), "plus-two", "+2"); + gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (widget), "plus-three", "+3"); + gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (widget), "plus-four", "+4"); + gtk_grid_attach (main_layout, widget, 3, 1, 1, 1); + g_signal_connect_swapped ( + widget, "changed", + G_CALLBACK (html_editor_text_dialog_set_size), dialog); + dialog->priv->size_check = widget; + + widget = gtk_label_new_with_mnemonic (_("Si_ze:")); + gtk_label_set_justify (GTK_LABEL (widget), GTK_JUSTIFY_RIGHT); + gtk_label_set_mnemonic_widget (GTK_LABEL (widget), dialog->priv->size_check); + gtk_grid_attach (main_layout, widget, 2, 1, 1, 1); + + gtk_widget_show_all (GTK_WIDGET (main_layout)); +} + +GtkWidget * +e_html_editor_text_dialog_new (EHTMLEditor *editor) +{ + return GTK_WIDGET ( + g_object_new ( + E_TYPE_HTML_EDITOR_TEXT_DIALOG, + "editor", editor, + "title", N_("Text Properties"), + NULL)); +} diff --git a/e-util/e-html-editor-text-dialog.h b/e-util/e-html-editor-text-dialog.h new file mode 100644 index 0000000000..006b780009 --- /dev/null +++ b/e-util/e-html-editor-text-dialog.h @@ -0,0 +1,69 @@ +/* + * e-html-editor-text-dialog.h + * + * Copyright (C) 2012 Dan Vrátil + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public 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_HTML_EDITOR_TEXT_DIALOG_H +#define E_HTML_EDITOR_TEXT_DIALOG_H + +#include + +/* Standard GObject macros */ +#define E_TYPE_HTML_EDITOR_TEXT_DIALOG \ + (e_html_editor_text_dialog_get_type ()) +#define E_HTML_EDITOR_TEXT_DIALOG(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST \ + ((obj), E_TYPE_HTML_EDITOR_TEXT_DIALOG, EHTMLEditorTextDialog)) +#define E_HTML_EDITOR_TEXT_DIALOG_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_CAST \ + ((cls), E_TYPE_HTML_EDITOR_TEXT_DIALOG, EHTMLEditorTextDialogClass)) +#define E_IS_HTML_EDITOR_TEXT_DIALOG(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE \ + ((obj), E_TYPE_HTML_EDITOR_TEXT_DIALOG)) +#define E_IS_HTML_EDITOR_TEXT_DIALOG_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_TYPE \ + ((cls), E_TYPE_HTML_EDITOR_TEXT_DIALOG)) +#define E_HTML_EDITOR_TEXT_DIALOG_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS \ + ((obj), E_TYPE_HTML_EDITOR_TEXT_DIALOG, EHTMLEditorTextDialogClass)) + +G_BEGIN_DECLS + +typedef struct _EHTMLEditorTextDialog EHTMLEditorTextDialog; +typedef struct _EHTMLEditorTextDialogClass EHTMLEditorTextDialogClass; +typedef struct _EHTMLEditorTextDialogPrivate EHTMLEditorTextDialogPrivate; + +struct _EHTMLEditorTextDialog { + EHTMLEditorDialog parent; + EHTMLEditorTextDialogPrivate *priv; +}; + +struct _EHTMLEditorTextDialogClass { + EHTMLEditorDialogClass parent_class; +}; + +GType e_html_editor_text_dialog_get_type (void) G_GNUC_CONST; +GtkWidget * e_html_editor_text_dialog_new (EHTMLEditor *editor); + +G_END_DECLS + +#endif /* E_HTML_EDITOR_TEXT_DIALOG_H */ diff --git a/e-util/e-html-editor-utils.c b/e-util/e-html-editor-utils.c new file mode 100644 index 0000000000..2807ea94b0 --- /dev/null +++ b/e-util/e-html-editor-utils.c @@ -0,0 +1,116 @@ +/* + * e-html-editor-utils.c + * + * Copyright (C) 2012 Dan Vrátil + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public 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-html-editor-utils.h" +#include + +/** + * e_html_editor_dom_node_find_parent_element: + * @node: Start node + * @tagname: Tag name of element to search + * + * Recursively searches for first occurance of element with given @tagname + * that is parent of given @node. + * + * Returns: A #WebKitDOMElement with @tagname representing parent of @node or + * @NULL when @node has no parent with given @tagname. When @node matches @tagname, + * then the @node is returned. + */ +WebKitDOMElement * +e_html_editor_dom_node_find_parent_element (WebKitDOMNode *node, + const gchar *tagname) +{ + gint taglen = strlen (tagname); + + while (node) { + + if (WEBKIT_DOM_IS_ELEMENT (node)) { + gchar *node_tagname; + + node_tagname = webkit_dom_element_get_tag_name ( + WEBKIT_DOM_ELEMENT (node)); + + if (node_tagname && + (strlen (node_tagname) == taglen) && + (g_ascii_strncasecmp (node_tagname, tagname, taglen) == 0)) { + g_free (node_tagname); + return WEBKIT_DOM_ELEMENT (node); + } + + g_free (node_tagname); + } + + node = WEBKIT_DOM_NODE (webkit_dom_node_get_parent_element (node)); + } + + return NULL; +} + +/** + * e_html_editor_dom_node_find_child_element: + * @node: Start node + * @tagname: Tag name of element to search. + * + * Recursively searches for first occurence of element with given @tagname that + * is a child of @node. + * + * Returns: A #WebKitDOMElement with @tagname representing a child of @node or + * @NULL when @node has no child with given @tagname. When @node matches @tagname, + * then the @node is returned. + */ +WebKitDOMElement * +e_html_editor_dom_node_find_child_element (WebKitDOMNode *node, + const gchar *tagname) +{ + WebKitDOMNode *start_node = node; + gint taglen = strlen (tagname); + + do { + if (WEBKIT_DOM_IS_ELEMENT (node)) { + gchar *node_tagname; + + node_tagname = webkit_dom_element_get_tag_name ( + WEBKIT_DOM_ELEMENT (node)); + + if (node_tagname && + (strlen (node_tagname) == taglen) && + (g_ascii_strncasecmp (node_tagname, tagname, taglen) == 0)) { + g_free (node_tagname); + return WEBKIT_DOM_ELEMENT (node); + } + + g_free (node_tagname); + } + + if (webkit_dom_node_has_child_nodes (node)) { + node = webkit_dom_node_get_first_child (node); + } else if (webkit_dom_node_get_next_sibling (node)) { + node = webkit_dom_node_get_next_sibling (node); + } else { + node = webkit_dom_node_get_parent_node (node); + } + } while (!webkit_dom_node_is_same_node (node, start_node)); + + return NULL; +} diff --git a/e-util/e-html-editor-utils.h b/e-util/e-html-editor-utils.h new file mode 100644 index 0000000000..7331a87709 --- /dev/null +++ b/e-util/e-html-editor-utils.h @@ -0,0 +1,44 @@ +/* + * e-html-editor-utils.h + * + * Copyright (C) 2012 Dan Vrátil + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public 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_HTML_EDITOR_UTILS_H +#define E_HTML_EDITOR_UTILS_H + +#include + +G_BEGIN_DECLS + +WebKitDOMElement * + e_html_editor_dom_node_find_parent_element + (WebKitDOMNode *node, + const gchar *tagname); + +WebKitDOMElement * + e_html_editor_dom_node_find_child_element + (WebKitDOMNode *node, + const gchar *tagname); + +G_END_DECLS + +#endif /* E_HTML_EDITOR_UTILS_H */ diff --git a/e-util/e-html-editor-view.c b/e-util/e-html-editor-view.c new file mode 100644 index 0000000000..21cadb3a5e --- /dev/null +++ b/e-util/e-html-editor-view.c @@ -0,0 +1,6303 @@ +/* + * e-html-editor-view.c + * + * Copyright (C) 2012 Dan Vrátil + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public 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-html-editor-view.h" +#include "e-html-editor.h" +#include "e-emoticon-chooser.h" + +#include +#include +#include +#include + +#define E_HTML_EDITOR_VIEW_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE \ + ((obj), E_TYPE_HTML_EDITOR_VIEW, EHTMLEditorViewPrivate)) + +#define UNICODE_ZERO_WIDTH_SPACE "\xe2\x80\x8b" + +#define URL_PATTERN \ + "((([A-Za-z]{3,9}:(?:\\/\\/)?)(?:[\\-;:&=\\+\\$,\\w]+@)?" \ + "[A-Za-z0-9\\.\\-]+|(?:www\\.|[\\-;:&=\\+\\$,\\w]+@)" \ + "[A-Za-z0-9\\.\\-]+)((?:\\/[\\+~%\\/\\.\\w\\-]*)?\\?" \ + "?(?:[\\-\\+=&;%@\\.\\w]*)#?(?:[\\.\\!\\/\\\\w]*))?)" + +#define URL_PATTERN_SPACE URL_PATTERN "\\s" + +#define QUOTE_SYMBOL ">" + +/* Keep synchronized with the same value in EHTMLEditorSelection */ +#define SPACES_PER_LIST_LEVEL 8 + +/** + * EHTMLEditorView: + * + * The #EHTMLEditorView is a WebKit-based rich text editor. The view itself + * only provides means to configure global behavior of the editor. To work + * with the actual content, current cursor position or current selection, + * use #EHTMLEditorSelection object. + */ + +struct _EHTMLEditorViewPrivate { + gint changed : 1; + gint inline_spelling : 1; + gint magic_links : 1; + gint magic_smileys : 1; + gint can_copy : 1; + gint can_cut : 1; + gint can_paste : 1; + gint can_redo : 1; + gint can_undo : 1; + gint reload_in_progress : 1; + gint html_mode : 1; + + EHTMLEditorSelection *selection; + + WebKitDOMElement *element_under_mouse; + + GHashTable *inline_images; + + GSettings *font_settings; + GSettings *aliasing_settings; + + gboolean convertor_insert; + + WebKitWebView *convertor_web_view; +}; + +enum { + PROP_0, + PROP_CAN_COPY, + PROP_CAN_CUT, + PROP_CAN_PASTE, + PROP_CAN_REDO, + PROP_CAN_UNDO, + PROP_CHANGED, + PROP_HTML_MODE, + PROP_INLINE_SPELLING, + PROP_MAGIC_LINKS, + PROP_MAGIC_SMILEYS, + PROP_SPELL_CHECKER +}; + +enum { + POPUP_EVENT, + PASTE_PRIMARY_CLIPBOARD, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = { 0 }; + +static CamelDataCache *emd_global_http_cache = NULL; + +G_DEFINE_TYPE_WITH_CODE ( + EHTMLEditorView, + e_html_editor_view, + WEBKIT_TYPE_WEB_VIEW, + G_IMPLEMENT_INTERFACE ( + E_TYPE_EXTENSIBLE, NULL)) + +static WebKitDOMRange * +html_editor_view_get_dom_range (EHTMLEditorView *view) +{ + WebKitDOMDocument *document; + WebKitDOMDOMWindow *window; + WebKitDOMDOMSelection *selection; + + document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view)); + window = webkit_dom_document_get_default_view (document); + selection = webkit_dom_dom_window_get_selection (window); + + if (webkit_dom_dom_selection_get_range_count (selection) < 1) { + return NULL; + } + + return webkit_dom_dom_selection_get_range_at (selection, 0, NULL); +} + +static void +html_editor_view_user_changed_contents_cb (EHTMLEditorView *view, + gpointer user_data) +{ + WebKitWebView *web_view; + gboolean can_redo, can_undo; + + web_view = WEBKIT_WEB_VIEW (view); + + e_html_editor_view_set_changed (view, TRUE); + + can_redo = webkit_web_view_can_redo (web_view); + if (view->priv->can_redo != can_redo) { + view->priv->can_redo = can_redo; + g_object_notify (G_OBJECT (view), "can-redo"); + } + + can_undo = webkit_web_view_can_undo (web_view); + if (view->priv->can_undo != can_undo) { + view->priv->can_undo = can_undo; + g_object_notify (G_OBJECT (view), "can-undo"); + } +} + +static void +html_editor_view_selection_changed_cb (EHTMLEditorView *view, + gpointer user_data) +{ + WebKitWebView *web_view; + gboolean can_copy, can_cut, can_paste; + + web_view = WEBKIT_WEB_VIEW (view); + + /* When the webview is being (re)loaded, the document is in an + * inconsistant state and there is no selection, so don't propagate + * the signal further to EHTMLEditorSelection and others and wait until + * the load is finished. */ + if (view->priv->reload_in_progress) { + g_signal_stop_emission_by_name (view, "selection-changed"); + return; + } + + can_copy = webkit_web_view_can_copy_clipboard (web_view); + if (view->priv->can_copy != can_copy) { + view->priv->can_copy = can_copy; + g_object_notify (G_OBJECT (view), "can-copy"); + } + + can_cut = webkit_web_view_can_cut_clipboard (web_view); + if (view->priv->can_cut != can_cut) { + view->priv->can_cut = can_cut; + g_object_notify (G_OBJECT (view), "can-cut"); + } + + can_paste = webkit_web_view_can_paste_clipboard (web_view); + if (view->priv->can_paste != can_paste) { + view->priv->can_paste = can_paste; + g_object_notify (G_OBJECT (view), "can-paste"); + } +} + +static gboolean +html_editor_view_should_show_delete_interface_for_element (EHTMLEditorView *view, + WebKitDOMHTMLElement *element) +{ + return FALSE; +} + +void +e_html_editor_view_force_spell_check_for_current_paragraph (EHTMLEditorView *view) +{ + EHTMLEditorSelection *selection; + WebKitDOMDocument *document; + WebKitDOMDOMSelection *dom_selection; + WebKitDOMDOMWindow *window; + WebKitDOMElement *caret, *parent, *element; + WebKitDOMRange *end_range, *actual; + WebKitDOMText *text; + + document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view)); + window = webkit_dom_document_get_default_view (document); + dom_selection = webkit_dom_dom_window_get_selection (window); + + element = webkit_dom_document_query_selector ( + document, "body[spellcheck=true]", NULL); + + if (!element) + return; + + selection = e_html_editor_view_get_selection (view); + caret = e_html_editor_selection_save_caret_position (selection); + + /* Block callbacks of selection-changed signal as we don't want to + * recount all the block format things in EHTMLEditorSelection and here as well + * when we are moving with caret */ + g_signal_handlers_block_by_func ( + view, html_editor_view_selection_changed_cb, NULL); + e_html_editor_selection_block_selection_changed (selection); + + parent = webkit_dom_node_get_parent_element (WEBKIT_DOM_NODE (caret)); + element = caret; + + while (parent && !WEBKIT_DOM_IS_HTML_BODY_ELEMENT (parent)) { + element = parent; + parent = webkit_dom_node_get_parent_element ( + WEBKIT_DOM_NODE (parent)); + } + + /* Append some text on the end of the element */ + text = webkit_dom_document_create_text_node (document, "-x-evo-end"); + webkit_dom_node_append_child ( + WEBKIT_DOM_NODE (element), WEBKIT_DOM_NODE (text), NULL); + + /* Create range that's pointing on the end of this text */ + end_range = webkit_dom_document_create_range (document); + webkit_dom_range_select_node_contents ( + end_range, WEBKIT_DOM_NODE (text), NULL); + webkit_dom_range_collapse (end_range, FALSE, NULL); + + /* Move on the beginning of the paragraph */ + actual = webkit_dom_document_create_range (document); + webkit_dom_range_select_node_contents ( + actual, WEBKIT_DOM_NODE (element), NULL); + webkit_dom_range_collapse (actual, TRUE, NULL); + webkit_dom_dom_selection_remove_all_ranges (dom_selection); + webkit_dom_dom_selection_add_range (dom_selection, actual); + + /* Go through all words to spellcheck them. To avoid this we have to wait for + * http://www.w3.org/html/wg/drafts/html/master/editing.html#dom-forcespellcheck */ + actual = webkit_dom_dom_selection_get_range_at (dom_selection, 0, NULL); + /* We are moving forward word by word until we hit the text on the end of + * the paragraph that we previously inserted there */ + while (actual && webkit_dom_range_compare_boundary_points (end_range, 2, actual, NULL) != 0) { + webkit_dom_dom_selection_modify ( + dom_selection, "move", "forward", "word"); + actual = webkit_dom_dom_selection_get_range_at ( + dom_selection, 0, NULL); + } + + /* Remove the text that we inserted on the end of the paragraph */ + webkit_dom_node_remove_child ( + WEBKIT_DOM_NODE (element), WEBKIT_DOM_NODE (text), NULL); + + /* Unblock the callbacks */ + g_signal_handlers_unblock_by_func ( + view, html_editor_view_selection_changed_cb, NULL); + e_html_editor_selection_unblock_selection_changed (selection); + + e_html_editor_selection_restore_caret_position (selection); +} + +static void +move_caret_into_element (WebKitDOMDocument *document, + WebKitDOMElement *element) +{ + WebKitDOMDOMWindow *window; + WebKitDOMDOMSelection *window_selection; + WebKitDOMRange *new_range; + + if (!element) + return; + + window = webkit_dom_document_get_default_view (document); + window_selection = webkit_dom_dom_window_get_selection (window); + new_range = webkit_dom_document_create_range (document); + + webkit_dom_range_select_node_contents ( + new_range, WEBKIT_DOM_NODE (element), NULL); + webkit_dom_range_collapse (new_range, FALSE, NULL); + webkit_dom_dom_selection_remove_all_ranges (window_selection); + webkit_dom_dom_selection_add_range (window_selection, new_range); +} + +static void +refresh_spell_check (EHTMLEditorView *view, + gboolean enable_spell_check) +{ + EHTMLEditorSelection *selection; + WebKitDOMDocument *document; + WebKitDOMDOMSelection *dom_selection; + WebKitDOMDOMWindow *window; + WebKitDOMHTMLElement *body; + WebKitDOMRange *end_range, *actual; + WebKitDOMText *text; + + document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view)); + window = webkit_dom_document_get_default_view (document); + dom_selection = webkit_dom_dom_window_get_selection (window); + + /* Enable/Disable spellcheck in composer */ + body = webkit_dom_document_get_body (document); + webkit_dom_element_set_attribute ( + WEBKIT_DOM_ELEMENT (body), + "spellcheck", + enable_spell_check ? "true" : "false", + NULL); + + selection = e_html_editor_view_get_selection (view); + e_html_editor_selection_save_caret_position (selection); + + /* Sometimes the web view is not event focused, so we have to move caret + * into body */ + if (!webkit_dom_document_get_element_by_id (document, "-x-evo-caret-position")) { + move_caret_into_element ( + document, + WEBKIT_DOM_ELEMENT (webkit_dom_document_get_body (document))); + e_html_editor_selection_save_caret_position (selection); + } + + /* Block callbacks of selection-changed signal as we don't want to + * recount all the block format things in EHTMLEditorSelection and here as well + * when we are moving with caret */ + g_signal_handlers_block_by_func ( + view, html_editor_view_selection_changed_cb, NULL); + e_html_editor_selection_block_selection_changed (selection); + + /* Append some text on the end of the body */ + text = webkit_dom_document_create_text_node (document, "-x-evo-end"); + webkit_dom_node_append_child ( + WEBKIT_DOM_NODE (body), WEBKIT_DOM_NODE (text), NULL); + + /* Create range that's pointing on the end of this text */ + end_range = webkit_dom_document_create_range (document); + webkit_dom_range_select_node_contents ( + end_range, WEBKIT_DOM_NODE (text), NULL); + webkit_dom_range_collapse (end_range, FALSE, NULL); + + /* Move on the beginning of the document */ + webkit_dom_dom_selection_modify ( + dom_selection, "move", "backward", "documentboundary"); + + /* Go through all words to spellcheck them. To avoid this we have to wait for + * http://www.w3.org/html/wg/drafts/html/master/editing.html#dom-forcespellcheck */ + actual = webkit_dom_dom_selection_get_range_at (dom_selection, 0, NULL); + /* We are moving forward word by word until we hit the text on the end of + * the body that we previously inserted there */ + while (actual && webkit_dom_range_compare_boundary_points (end_range, 2, actual, NULL) != 0) { + webkit_dom_dom_selection_modify ( + dom_selection, "move", "forward", "word"); + actual = webkit_dom_dom_selection_get_range_at ( + dom_selection, 0, NULL); + } + + /* Remove the text that we inserted on the end of the body */ + webkit_dom_node_remove_child ( + WEBKIT_DOM_NODE (body), WEBKIT_DOM_NODE (text), NULL); + + /* Unblock the callbacks */ + g_signal_handlers_unblock_by_func ( + view, html_editor_view_selection_changed_cb, NULL); + e_html_editor_selection_unblock_selection_changed (selection); + + e_html_editor_selection_restore_caret_position (selection); +} + +void +e_html_editor_view_turn_spell_check_off (EHTMLEditorView *view) +{ + refresh_spell_check (view, FALSE); +} + +void +e_html_editor_view_force_spell_check (EHTMLEditorView *view) +{ + refresh_spell_check (view, TRUE); +} + +static void +body_input_event_cb (WebKitDOMElement *element, + WebKitDOMEvent *event, + EHTMLEditorView *view) +{ + WebKitDOMNode *node; + WebKitDOMRange *range = html_editor_view_get_dom_range (view); + + e_html_editor_view_set_changed (view, TRUE); + + node = webkit_dom_range_get_end_container (range, NULL); + + /* After toggling monospaced format, we are using UNICODE_ZERO_WIDTH_SPACE + * to move caret into right space. When this callback is called it is not + * necassary anymore so remove it */ + if (e_html_editor_view_get_html_mode (view)) { + WebKitDOMElement *parent = webkit_dom_node_get_parent_element (node); + + if (parent) { + WebKitDOMNode *prev_sibling; + + prev_sibling = webkit_dom_node_get_previous_sibling ( + WEBKIT_DOM_NODE (parent)); + + if (prev_sibling && WEBKIT_DOM_IS_TEXT (prev_sibling)) { + gchar *text = webkit_dom_node_get_text_content ( + prev_sibling); + + if (g_strcmp0 (text, UNICODE_ZERO_WIDTH_SPACE) == 0) { + webkit_dom_node_remove_child ( + webkit_dom_node_get_parent_node ( + prev_sibling), + prev_sibling, + NULL); + } + g_free (text); + } + + } + } + + /* If text before caret includes UNICODE_ZERO_WIDTH_SPACE character, remove it */ + if (WEBKIT_DOM_IS_TEXT (node)) { + gchar *text = webkit_dom_character_data_get_data (WEBKIT_DOM_CHARACTER_DATA (node)); + glong length = g_utf8_strlen (text, -1); + WebKitDOMNode *parent; + + /* We have to preserve empty paragraphs with just UNICODE_ZERO_WIDTH_SPACE + * character as when we will remove it it will collapse */ + if (length > 1) { + if (g_str_has_prefix (text, UNICODE_ZERO_WIDTH_SPACE)) + webkit_dom_character_data_replace_data ( + WEBKIT_DOM_CHARACTER_DATA (node), 0, 1, "", NULL); + else if (g_str_has_suffix (text, UNICODE_ZERO_WIDTH_SPACE)) + webkit_dom_character_data_replace_data ( + WEBKIT_DOM_CHARACTER_DATA (node), length - 1, 1, "", NULL); + } + g_free (text); + + parent = webkit_dom_node_get_parent_node (node); + if ((WEBKIT_DOM_IS_HTML_PARAGRAPH_ELEMENT (parent) || + WEBKIT_DOM_IS_HTML_DIV_ELEMENT (parent)) && + !element_has_class (WEBKIT_DOM_ELEMENT (parent), "-x-evo-paragraph")) { + if (e_html_editor_view_get_html_mode (view)) { + element_add_class ( + WEBKIT_DOM_ELEMENT (parent), "-x-evo-paragraph"); + } else { + e_html_editor_selection_set_paragraph_style ( + e_html_editor_view_get_selection (view), + WEBKIT_DOM_ELEMENT (parent), + -1, 0, ""); + } + } + + /* When new smiley is added we have to use UNICODE_HIDDEN_SPACE to set the + * caret position to right place. It is removed when user starts typing. But + * when the user will press left arrow he will move the caret into + * smiley wrapper. If he will start to write there we have to move the written + * text out of the wrapper and move caret to right place */ + if (WEBKIT_DOM_IS_ELEMENT (parent) && + element_has_class (WEBKIT_DOM_ELEMENT (parent), "-x-evo-smiley-wrapper")) { + WebKitDOMDocument *document; + + document = webkit_web_view_get_dom_document ( + WEBKIT_WEB_VIEW (view)); + + webkit_dom_node_insert_before ( + webkit_dom_node_get_parent_node (parent), + e_html_editor_selection_get_caret_position_node ( + document), + webkit_dom_node_get_next_sibling (parent), + NULL); + webkit_dom_node_insert_before ( + webkit_dom_node_get_parent_node (parent), + node, + webkit_dom_node_get_next_sibling (parent), + NULL); + e_html_editor_selection_restore_caret_position ( + e_html_editor_view_get_selection (view)); + } + } +} + +static void +set_base64_to_element_attribute (EHTMLEditorView *view, + WebKitDOMElement *element, + const gchar *attribute) +{ + gchar *attribute_value; + const gchar *base64_src; + + attribute_value = webkit_dom_element_get_attribute (element, attribute); + + if ((base64_src = g_hash_table_lookup (view->priv->inline_images, attribute_value)) != NULL) { + const gchar *base64_data = strstr (base64_src, ";") + 1; + gchar *name; + glong name_length; + + name_length = + g_utf8_strlen (base64_src, -1) - + g_utf8_strlen (base64_data, -1) - 1; + name = g_strndup (base64_src, name_length); + + webkit_dom_element_set_attribute (element, "data-inline", "", NULL); + webkit_dom_element_set_attribute (element, "data-name", name, NULL); + webkit_dom_element_set_attribute (element, attribute, base64_data, NULL); + + g_free (name); + } +} + +static void +change_cid_images_src_to_base64 (EHTMLEditorView *view) +{ + gint ii, length; + WebKitDOMDocument *document; + WebKitDOMElement *document_element; + WebKitDOMNamedNodeMap *attributes; + WebKitDOMNodeList *list; + + document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view)); + document_element = webkit_dom_document_get_document_element (document); + + list = webkit_dom_document_query_selector_all (document, "img[src^=\"cid:\"]", NULL); + length = webkit_dom_node_list_get_length (list); + for (ii = 0; ii < length; ii++) { + WebKitDOMNode *node = webkit_dom_node_list_item (list, ii); + + set_base64_to_element_attribute (view, WEBKIT_DOM_ELEMENT (node), "src"); + } + + /* Namespaces */ + attributes = webkit_dom_element_get_attributes (document_element); + length = webkit_dom_named_node_map_get_length (attributes); + for (ii = 0; ii < length; ii++) { + gchar *name; + WebKitDOMNode *node = webkit_dom_named_node_map_item (attributes, ii); + + name = webkit_dom_node_get_local_name (node); + + if (g_str_has_prefix (name, "xmlns:")) { + const gchar *ns = name + 6; + gchar *attribute_ns = g_strconcat (ns, ":src", NULL); + gchar *selector = g_strconcat ("img[", ns, "\\:src^=\"cid:\"]", NULL); + gint ns_length, jj; + + list = webkit_dom_document_query_selector_all ( + document, selector, NULL); + ns_length = webkit_dom_node_list_get_length (list); + for (jj = 0; jj < ns_length; jj++) { + WebKitDOMNode *node = webkit_dom_node_list_item (list, jj); + + set_base64_to_element_attribute ( + view, WEBKIT_DOM_ELEMENT (node), attribute_ns); + } + + g_free (attribute_ns); + g_free (selector); + } + g_free (name); + } + + list = webkit_dom_document_query_selector_all ( + document, "[background^=\"cid:\"]", NULL); + length = webkit_dom_node_list_get_length (list); + for (ii = 0; ii < length; ii++) { + WebKitDOMNode *node = webkit_dom_node_list_item (list, ii); + + set_base64_to_element_attribute ( + view, WEBKIT_DOM_ELEMENT (node), "background"); + } + g_hash_table_remove_all (view->priv->inline_images); +} + +/* For purpose of this function see e-mail-formatter-quote.c */ +static void +put_body_in_citation (WebKitDOMDocument *document) +{ + WebKitDOMElement *cite_body = webkit_dom_document_query_selector ( + document, "span.-x-evo-cite-body", NULL); + + if (cite_body) { + WebKitDOMHTMLElement *body = webkit_dom_document_get_body (document); + gchar *inner_html, *with_citation; + + webkit_dom_node_remove_child ( + WEBKIT_DOM_NODE (body), + WEBKIT_DOM_NODE (cite_body), + NULL); + + inner_html = webkit_dom_html_element_get_inner_html (body); + with_citation = g_strconcat ( + "
", + inner_html, "", NULL); + webkit_dom_html_element_set_inner_html (body, with_citation, NULL); + g_free (inner_html); + g_free (with_citation); + } +} + +/* For purpose of this function see e-mail-formatter-quote.c */ +static void +move_elements_to_body (WebKitDOMDocument *document) +{ + WebKitDOMHTMLElement *body = webkit_dom_document_get_body (document); + WebKitDOMNodeList *list; + gint ii; + + list = webkit_dom_document_query_selector_all ( + document, "span.-x-evo-to-body", NULL); + for (ii = webkit_dom_node_list_get_length (list) - 1; ii >= 0; ii--) { + WebKitDOMNode *node = webkit_dom_node_list_item (list, ii); + + while (webkit_dom_node_has_child_nodes (node)) { + webkit_dom_node_insert_before ( + WEBKIT_DOM_NODE (body), + webkit_dom_node_get_first_child (node), + webkit_dom_node_get_first_child ( + WEBKIT_DOM_NODE (body)), + NULL); + } + + webkit_dom_node_remove_child ( + webkit_dom_node_get_parent_node (node), + WEBKIT_DOM_NODE (node), + NULL); + } +} + +static void +repair_gmail_blockquotes (WebKitDOMDocument *document) +{ + WebKitDOMNodeList *list; + gint ii, length; + + list = webkit_dom_document_query_selector_all ( + document, "blockquote.gmail_quote", NULL); + length = webkit_dom_node_list_get_length (list); + for (ii = 0; ii < length; ii++) { + WebKitDOMNode *node = webkit_dom_node_list_item (list, ii); + + webkit_dom_element_remove_attribute (WEBKIT_DOM_ELEMENT (node), "class"); + webkit_dom_element_remove_attribute (WEBKIT_DOM_ELEMENT (node), "style"); + webkit_dom_element_set_attribute (WEBKIT_DOM_ELEMENT (node), "type", "cite", NULL); + } +} + +static void +html_editor_view_load_status_changed (EHTMLEditorView *view) +{ + WebKitDOMDocument *document; + WebKitDOMHTMLElement *body; + WebKitLoadStatus status; + + status = webkit_web_view_get_load_status (WEBKIT_WEB_VIEW (view)); + if (status != WEBKIT_LOAD_FINISHED) + return; + + view->priv->reload_in_progress = FALSE; + + document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view)); + body = webkit_dom_document_get_body (document); + + webkit_dom_element_remove_attribute (WEBKIT_DOM_ELEMENT (body), "style"); + webkit_dom_element_set_attribute ( + WEBKIT_DOM_ELEMENT (body), "data-message", "", NULL); + + put_body_in_citation (document); + move_elements_to_body (document); + repair_gmail_blockquotes (document); + + /* Register on input event that is called when the content (body) is modified */ + webkit_dom_event_target_add_event_listener ( + WEBKIT_DOM_EVENT_TARGET (body), + "input", + G_CALLBACK (body_input_event_cb), + FALSE, + view); + + if (view->priv->html_mode) + change_cid_images_src_to_base64 (view); +} + +/* Based on original use_pictograms() from GtkHTML */ +static const gchar *emoticons_chars = + /* 0 */ "DO)(|/PQ*!" + /* 10 */ "S\0:-\0:\0:-\0" + /* 20 */ ":\0:;=-\"\0:;" + /* 30 */ "B\"|\0:-'\0:X" + /* 40 */ "\0:\0:-\0:\0:-" + /* 50 */ "\0:\0:-\0:\0:-" + /* 60 */ "\0:\0:\0:-\0:\0" + /* 70 */ ":-\0:\0:-\0:\0"; +static gint emoticons_states[] = { + /* 0 */ 12, 17, 22, 34, 43, 48, 53, 58, 65, 70, + /* 10 */ 75, 0, -15, 15, 0, -15, 0, -17, 20, 0, + /* 20 */ -17, 0, -14, -20, -14, 28, 63, 0, -14, -20, + /* 30 */ -3, 63, -18, 0, -12, 38, 41, 0, -12, -2, + /* 40 */ 0, -4, 0, -10, 46, 0, -10, 0, -19, 51, + /* 50 */ 0, -19, 0, -11, 56, 0, -11, 0, -13, 61, + /* 60 */ 0, -13, 0, -6, 0, 68, -7, 0, -7, 0, + /* 70 */ -16, 73, 0, -16, 0, -21, 78, 0, -21, 0 }; +static const gchar *emoticons_icon_names[] = { + "face-angel", + "face-angry", + "face-cool", + "face-crying", + "face-devilish", + "face-embarrassed", + "face-kiss", + "face-laugh", /* not used */ + "face-monkey", /* not used */ + "face-plain", + "face-raspberry", + "face-sad", + "face-sick", + "face-smile", + "face-smile-big", + "face-smirk", + "face-surprise", + "face-tired", + "face-uncertain", + "face-wink", + "face-worried" +}; + +static void +html_editor_view_check_magic_links (EHTMLEditorView *view, + WebKitDOMRange *range, + gboolean include_space_by_user, + GdkEventKey *event) +{ + gchar *node_text; + gchar **urls; + GRegex *regex = NULL; + GMatchInfo *match_info; + gint start_pos_url, end_pos_url; + WebKitDOMNode *node; + gboolean include_space = FALSE; + gboolean return_pressed = FALSE; + + if (event != NULL) { + if ((event->keyval == GDK_KEY_Return) || + (event->keyval == GDK_KEY_Linefeed) || + (event->keyval == GDK_KEY_KP_Enter)) { + + return_pressed = TRUE; + } + + if (event->keyval == GDK_KEY_space) + include_space = TRUE; + } else { + include_space = include_space_by_user; + } + + node = webkit_dom_range_get_end_container (range, NULL); + + if (return_pressed) + node = webkit_dom_node_get_previous_sibling (node); + + if (!node) + return; + + if (!WEBKIT_DOM_IS_TEXT (node)) { + if (webkit_dom_node_has_child_nodes (node)) + node = webkit_dom_node_get_first_child (node); + if (!WEBKIT_DOM_IS_TEXT (node)) + return; + } + + node_text = webkit_dom_text_get_whole_text (WEBKIT_DOM_TEXT (node)); + if (!node_text || !(*node_text) || !g_utf8_validate (node_text, -1, NULL)) + return; + + regex = g_regex_new (include_space ? URL_PATTERN_SPACE : URL_PATTERN, 0, 0, NULL); + + if (!regex) { + g_free (node_text); + return; + } + + g_regex_match_all (regex, node_text, G_REGEX_MATCH_NOTEMPTY, &match_info); + urls = g_match_info_fetch_all (match_info); + + if (urls) { + gchar *final_url, *url_end_raw; + glong url_start, url_end, url_length; + WebKitDOMDocument *document; + WebKitDOMNode *url_text_node_clone; + WebKitDOMText *url_text_node; + WebKitDOMElement *anchor; + const gchar* url_text; + + document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view)); + + if (!return_pressed) + e_html_editor_selection_save_caret_position ( + e_html_editor_view_get_selection (view)); + + g_match_info_fetch_pos (match_info, 0, &start_pos_url, &end_pos_url); + + /* Get start and end position of url in node's text because positions + * that we get from g_match_info_fetch_pos are not UTF-8 aware */ + url_end_raw = g_strndup(node_text, end_pos_url); + url_end = g_utf8_strlen (url_end_raw, -1); + + url_length = g_utf8_strlen (urls[0], -1); + url_start = url_end - url_length; + + webkit_dom_text_split_text ( + WEBKIT_DOM_TEXT (node), + include_space ? url_end - 1 : url_end, + NULL); + + url_text_node = webkit_dom_text_split_text ( + WEBKIT_DOM_TEXT (node), url_start, NULL); + url_text_node_clone = webkit_dom_node_clone_node ( + WEBKIT_DOM_NODE (url_text_node), TRUE); + url_text = webkit_dom_text_get_whole_text ( + WEBKIT_DOM_TEXT (url_text_node_clone)); + + final_url = g_strconcat ( + g_str_has_prefix (url_text, "www") ? "http://" : "", url_text, NULL); + + /* Create and prepare new anchor element */ + anchor = webkit_dom_document_create_element (document, "A", NULL); + + webkit_dom_html_element_set_inner_html ( + WEBKIT_DOM_HTML_ELEMENT (anchor), + url_text, + NULL); + + webkit_dom_html_anchor_element_set_href ( + WEBKIT_DOM_HTML_ANCHOR_ELEMENT (anchor), + final_url); + + /* Insert new anchor element into document */ + webkit_dom_node_replace_child ( + webkit_dom_node_get_parent_node (node), + WEBKIT_DOM_NODE (anchor), + WEBKIT_DOM_NODE (url_text_node), + NULL); + + if (!return_pressed) + e_html_editor_selection_restore_caret_position ( + e_html_editor_view_get_selection (view)); + + g_free (url_end_raw); + g_free (final_url); + } else { + WebKitDOMElement *parent; + WebKitDOMNode *prev_sibling; + gchar *href, *text, *url; + gint diff; + const char* text_to_append; + gboolean appending_to_link = FALSE; + + parent = webkit_dom_node_get_parent_element (node); + prev_sibling = webkit_dom_node_get_previous_sibling (node); + + /* If previous sibling is ANCHOR and actual text node is not beginning with + * space => we're appending to link */ + if (WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT (prev_sibling)) { + text_to_append = webkit_dom_node_get_text_content (node); + if (g_strcmp0 (text_to_append, "") != 0 && + !g_unichar_isspace (g_utf8_get_char (text_to_append))) { + + appending_to_link = TRUE; + parent = WEBKIT_DOM_ELEMENT (prev_sibling); + } + } + + /* If parent is ANCHOR => we're editing the link */ + if (!WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT (parent) && !appending_to_link) { + g_match_info_free (match_info); + g_regex_unref (regex); + g_free (node_text); + return; + } + + /* edit only if href and description are the same */ + href = webkit_dom_html_anchor_element_get_href ( + WEBKIT_DOM_HTML_ANCHOR_ELEMENT (parent)); + + if (appending_to_link) { + gchar *inner_text; + + inner_text = + webkit_dom_html_element_get_inner_text ( + WEBKIT_DOM_HTML_ELEMENT (parent)), + + text = g_strconcat (inner_text, text_to_append, NULL); + g_free (inner_text); + } else + text = webkit_dom_html_element_get_inner_text ( + WEBKIT_DOM_HTML_ELEMENT (parent)); + + if (strstr (href, "://") && !strstr (text, "://")) { + url = strstr (href, "://") + 3; + diff = strlen (text) - strlen (url); + + if (text [strlen (text) - 1] != '/') + diff++; + + if ((g_strcmp0 (url, text) != 0 && ABS (diff) == 1) || appending_to_link) { + gchar *inner_html, *protocol, *new_href; + + protocol = g_strndup (href, strstr (href, "://") - href + 3); + inner_html = webkit_dom_html_element_get_inner_html ( + WEBKIT_DOM_HTML_ELEMENT (parent)); + new_href = g_strconcat ( + protocol, inner_html, appending_to_link ? text_to_append : "", NULL); + + webkit_dom_html_anchor_element_set_href ( + WEBKIT_DOM_HTML_ANCHOR_ELEMENT (parent), + new_href); + + if (appending_to_link) { + gchar *tmp; + + tmp = g_strconcat (inner_html, text_to_append, NULL); + webkit_dom_html_element_set_inner_html ( + WEBKIT_DOM_HTML_ELEMENT (parent), + tmp, + NULL); + + webkit_dom_node_remove_child ( + webkit_dom_node_get_parent_node (node), + node, NULL); + + g_free (tmp); + } + + g_free (new_href); + g_free (protocol); + g_free (inner_html); + } + } else { + diff = strlen (text) - strlen (href); + if (text [strlen (text) - 1] != '/') + diff++; + + if ((g_strcmp0 (href, text) != 0 && ABS (diff) == 1) || appending_to_link) { + gchar *inner_html; + gchar *new_href; + + inner_html = webkit_dom_html_element_get_inner_html ( + WEBKIT_DOM_HTML_ELEMENT (parent)); + new_href = g_strconcat ( + inner_html, + appending_to_link ? text_to_append : "", + NULL); + + webkit_dom_html_anchor_element_set_href ( + WEBKIT_DOM_HTML_ANCHOR_ELEMENT (parent), + new_href); + + if (appending_to_link) { + gchar *tmp; + + tmp = g_strconcat (inner_html, text_to_append, NULL); + webkit_dom_html_element_set_inner_html ( + WEBKIT_DOM_HTML_ELEMENT (parent), + tmp, + NULL); + + webkit_dom_node_remove_child ( + webkit_dom_node_get_parent_node (node), + node, NULL); + + g_free (tmp); + } + + g_free (new_href); + g_free (inner_html); + } + + } + g_free (text); + g_free (href); + } + + g_match_info_free (match_info); + g_regex_unref (regex); + g_free (node_text); +} + +typedef struct _LoadContext LoadContext; + +struct _LoadContext { + EHTMLEditorView *view; + gchar *content_type; + gchar *name; + EEmoticon *emoticon; +}; + +static LoadContext * +emoticon_load_context_new (EHTMLEditorView *view, + EEmoticon *emoticon) +{ + LoadContext *load_context; + + load_context = g_slice_new0 (LoadContext); + load_context->view = view; + load_context->emoticon = emoticon; + + return load_context; +} + +static void +emoticon_load_context_free (LoadContext *load_context) +{ + g_free (load_context->content_type); + g_free (load_context->name); + g_slice_free (LoadContext, load_context); +} + +static void +emoticon_read_async_cb (GFile *file, + GAsyncResult *result, + LoadContext *load_context) +{ + EHTMLEditorView *view = load_context->view; + EEmoticon *emoticon = load_context->emoticon; + GError *error = NULL; + gchar *html, *node_text = NULL, *mime_type; + gchar *base64_encoded, *output, *data; + const gchar *emoticon_start; + GFileInputStream *input_stream; + GOutputStream *output_stream; + gssize size; + WebKitDOMDocument *document; + WebKitDOMElement *span, *caret_position; + WebKitDOMNode *node; + WebKitDOMRange *range; + + input_stream = g_file_read_finish (file, result, &error); + g_return_if_fail (!error && input_stream); + + output_stream = g_memory_output_stream_new (NULL, 0, g_realloc, g_free); + + size = g_output_stream_splice ( + output_stream, G_INPUT_STREAM (input_stream), + G_OUTPUT_STREAM_SPLICE_NONE, NULL, &error); + + if (error || (size == -1)) + goto out; + + caret_position = e_html_editor_selection_save_caret_position ( + e_html_editor_view_get_selection (view)); + + if (caret_position) { + WebKitDOMNode *parent; + + parent = webkit_dom_node_get_parent_node ( + WEBKIT_DOM_NODE (caret_position)); + + /* Situation when caret is restored in body and not in paragraph */ + if (WEBKIT_DOM_IS_HTML_BODY_ELEMENT (parent)) { + caret_position = WEBKIT_DOM_ELEMENT ( + webkit_dom_node_remove_child ( + WEBKIT_DOM_NODE (parent), + WEBKIT_DOM_NODE (caret_position), + NULL)); + + caret_position = WEBKIT_DOM_ELEMENT ( + webkit_dom_node_insert_before ( + webkit_dom_node_get_first_child ( + WEBKIT_DOM_NODE (parent)), + WEBKIT_DOM_NODE (caret_position), + webkit_dom_node_get_first_child ( + webkit_dom_node_get_first_child ( + WEBKIT_DOM_NODE (parent))), + NULL)); + } + } + + mime_type = g_content_type_get_mime_type (load_context->content_type); + document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view)); + range = html_editor_view_get_dom_range (view); + node = webkit_dom_range_get_end_container (range, NULL); + if (WEBKIT_DOM_IS_TEXT (node)) + node_text = webkit_dom_text_get_whole_text (WEBKIT_DOM_TEXT (node)); + span = webkit_dom_document_create_element (document, "SPAN", NULL); + + data = g_memory_output_stream_get_data (G_MEMORY_OUTPUT_STREAM (output_stream)); + + base64_encoded = g_base64_encode ((const guchar *) data, size); + output = g_strconcat ("data:", mime_type, ";base64,", base64_encoded, NULL); + + /* Insert span with image representation and another one with text + * represetation and hide/show them dependant on active composer mode */ + /* ​ == UNICODE_ZERO_WIDTH_SPACE */ + html = g_strdup_printf ( + "" + "\"%s\"" + "%s" + "​", + output, emoticon ? emoticon->text_face : "", emoticon->icon_name, + load_context->name, emoticon ? emoticon->text_face : ""); + + span = WEBKIT_DOM_ELEMENT ( + webkit_dom_node_insert_before ( + webkit_dom_node_get_parent_node ( + WEBKIT_DOM_NODE (caret_position)), + WEBKIT_DOM_NODE (span), + WEBKIT_DOM_NODE (caret_position), + NULL)); + + webkit_dom_html_element_set_outer_html ( + WEBKIT_DOM_HTML_ELEMENT (span), html, NULL); + + if (node_text) { + emoticon_start = g_utf8_strrchr ( + node_text, -1, g_utf8_get_char (emoticon->text_face)); + if (emoticon_start) { + webkit_dom_character_data_delete_data ( + WEBKIT_DOM_CHARACTER_DATA (node), + g_utf8_strlen (node_text, -1) - strlen (emoticon_start), + strlen (emoticon->text_face), + NULL); + } + } + + e_html_editor_selection_restore_caret_position ( + e_html_editor_view_get_selection (view)); + + e_html_editor_view_set_changed (view, TRUE); + + g_free (html); + g_free (node_text); + g_free (base64_encoded); + g_free (output); + g_free (mime_type); + g_object_unref (output_stream); + out: + emoticon_load_context_free (load_context); +} + +static void +emoticon_query_info_async_cb (GFile *file, + GAsyncResult *result, + LoadContext *load_context) +{ + GError *error = NULL; + GFileInfo *info; + + info = g_file_query_info_finish (file, result, &error); + g_return_if_fail (!error && info); + + load_context->content_type = g_strdup (g_file_info_get_content_type (info)); + load_context->name = g_strdup (g_file_info_get_name (info)); + + g_file_read_async ( + file, G_PRIORITY_DEFAULT, NULL, + (GAsyncReadyCallback) emoticon_read_async_cb, load_context); + + g_object_unref (info); +} + +void +e_html_editor_view_insert_smiley (EHTMLEditorView *view, + EEmoticon *emoticon) +{ + GFile *file; + gchar *filename_uri; + LoadContext *load_context; + + filename_uri = e_emoticon_get_uri (emoticon); + g_return_if_fail (filename_uri != NULL); + + load_context = emoticon_load_context_new (view, emoticon); + + file = g_file_new_for_uri (filename_uri); + g_file_query_info_async ( + file, "standard::*", G_FILE_QUERY_INFO_NONE, + G_PRIORITY_DEFAULT, NULL, + (GAsyncReadyCallback) emoticon_query_info_async_cb, load_context); + + g_free (filename_uri); + g_object_unref (file); +} + +static void +html_editor_view_check_magic_smileys (EHTMLEditorView *view, + WebKitDOMRange *range) +{ + gint pos; + gint state; + gint relative; + gint start; + gchar *node_text; + gunichar uc; + WebKitDOMNode *node; + + node = webkit_dom_range_get_end_container (range, NULL); + if (!WEBKIT_DOM_IS_TEXT (node)) + return; + + node_text = webkit_dom_text_get_whole_text (WEBKIT_DOM_TEXT (node)); + if (node_text == NULL) + return; + + start = webkit_dom_range_get_end_offset (range, NULL) - 1; + pos = start; + state = 0; + while (pos >= 0) { + uc = g_utf8_get_char (g_utf8_offset_to_pointer (node_text, pos)); + relative = 0; + while (emoticons_chars[state + relative]) { + if (emoticons_chars[state + relative] == uc) + break; + relative++; + } + state = emoticons_states[state + relative]; + /* 0 .. not found, -n .. found n-th */ + if (state <= 0) + break; + pos--; + } + + /* Special case needed to recognize angel and devilish. */ + if (pos > 0 && state == -14) { + uc = g_utf8_get_char (g_utf8_offset_to_pointer (node_text, pos - 1)); + if (uc == 'O') { + state = -1; + pos--; + } else if (uc == '>') { + state = -5; + pos--; + } + } + + if (state < 0) { + const EEmoticon *emoticon; + + if (pos > 0) { + uc = g_utf8_get_char (g_utf8_offset_to_pointer (node_text, pos - 1)); + if (!g_unichar_isspace (uc)) { + g_free (node_text); + return; + } + } + + emoticon = (e_emoticon_chooser_lookup_emoticon ( + emoticons_icon_names[-state - 1])); + e_html_editor_view_insert_smiley (view, (EEmoticon *) emoticon); + } + + g_free (node_text); +} + +static void +html_editor_view_set_links_active (EHTMLEditorView *view, + gboolean active) +{ + WebKitDOMDocument *document; + WebKitDOMElement *style; + + document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view)); + + if (active) { + style = webkit_dom_document_get_element_by_id ( + document, "--evolution-editor-style-a"); + if (style) { + webkit_dom_node_remove_child ( + webkit_dom_node_get_parent_node ( + WEBKIT_DOM_NODE (style)), + WEBKIT_DOM_NODE (style), NULL); + } + } else { + WebKitDOMHTMLHeadElement *head; + head = webkit_dom_document_get_head (document); + + style = webkit_dom_document_create_element (document, "STYLE", NULL); + webkit_dom_element_set_id (style, "--evolution-editor-style-a"); + webkit_dom_html_element_set_inner_text ( + WEBKIT_DOM_HTML_ELEMENT (style), "a { cursor: text; }", NULL); + + webkit_dom_node_append_child ( + WEBKIT_DOM_NODE (head), WEBKIT_DOM_NODE (style), NULL); + } +} + +static void +clipboard_text_received (GtkClipboard *clipboard, + const gchar *text, + EHTMLEditorView *view) +{ + EHTMLEditorSelection *selection; + gchar *escaped_text; + WebKitDOMDocument *document; + WebKitDOMDOMWindow *window; + WebKitDOMDOMSelection *dom_selection; + WebKitDOMElement *blockquote, *element; + WebKitDOMNode *node; + WebKitDOMRange *range; + + if (!text || !*text) + return; + + selection = e_html_editor_view_get_selection (view); + + document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view)); + window = webkit_dom_document_get_default_view (document); + dom_selection = webkit_dom_dom_window_get_selection (window); + + /* This is a trick to escape any HTML characters (like <, > or &). + *