From 0eed1bb21aff8b66ada9316f00b8ad356dcbf3d3 Mon Sep 17 00:00:00 2001 From: James Westman Date: Tue, 14 Jan 2020 22:35:18 -0600 Subject: [PATCH] composer: Add font buttons to toolbar This replaces the menu options in the overflow menu with nicer looking, more discoverable toolbar buttons. They work much the same way as before. --- icons/font-color-symbolic.svg | 19 ++ icons/font-size-symbolic.svg | 19 ++ icons/meson.build | 2 + src/client/composer/composer-web-view.vala | 11 +- src/client/composer/composer-widget.vala | 67 ++++-- src/client/util/util-gtk.vala | 9 + .../composer/composer-web-view-test.vala | 19 +- test/js/composer-page-state-test.vala | 6 +- ui/composer-menus.ui | 58 ----- ui/composer-web-view.js | 9 +- ui/composer-widget.ui | 218 +++++++++++++++--- 11 files changed, 316 insertions(+), 121 deletions(-) create mode 100644 icons/font-color-symbolic.svg create mode 100644 icons/font-size-symbolic.svg diff --git a/icons/font-color-symbolic.svg b/icons/font-color-symbolic.svg new file mode 100644 index 00000000..c51bc931 --- /dev/null +++ b/icons/font-color-symbolic.svg @@ -0,0 +1,19 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + diff --git a/icons/font-size-symbolic.svg b/icons/font-size-symbolic.svg new file mode 100644 index 00000000..07ecc544 --- /dev/null +++ b/icons/font-size-symbolic.svg @@ -0,0 +1,19 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + \ No newline at end of file diff --git a/icons/meson.build b/icons/meson.build index 2234d3b0..138ed558 100644 --- a/icons/meson.build +++ b/icons/meson.build @@ -6,6 +6,8 @@ icon_files = files( 'detach-symbolic.svg', 'text-x-generic-symbolic.svg', 'edit-symbolic.svg', + 'font-size-symbolic.svg', + 'font-color-symbolic.svg', 'format-ordered-list-symbolic.svg', 'format-ordered-list-symbolic-rtl.svg', 'format-text-remove-symbolic.svg', diff --git a/src/client/composer/composer-web-view.vala b/src/client/composer/composer-web-view.vala index 7f73becd..3dabf7fe 100644 --- a/src/client/composer/composer-web-view.vala +++ b/src/client/composer/composer-web-view.vala @@ -55,11 +55,16 @@ public class Composer.WebView : Components.WebView { public string link_url { get; private set; default = ""; } public string font_family { get; private set; default = "sans"; } public uint font_size { get; private set; default = 12; } + public Gdk.RGBA font_color { + get; + private set; + default = Util.Gtk.rgba(0, 0, 0, 1); + } private uint context = 0; public EditContext(string message) { - string[] values = message.split(","); + string[] values = message.split(";"); this.context = (uint) uint64.parse(values[0]); this.link_url = values[1]; @@ -73,6 +78,10 @@ public class Composer.WebView : Components.WebView { } this.font_size = (uint) uint64.parse(values[3]); + + Gdk.RGBA font_color = {0, 0, 0, 0}; + font_color.parse(values[4]); + this.font_color = font_color; } } diff --git a/src/client/composer/composer-widget.vala b/src/client/composer/composer-widget.vala index b4668980..e09a8806 100644 --- a/src/client/composer/composer-widget.vala +++ b/src/client/composer/composer-widget.vala @@ -129,7 +129,7 @@ public class Composer.Widget : Gtk.EventBox, Geary.BaseInterface { private const string ACTION_COLOR = "color"; private const string ACTION_INSERT_IMAGE = "insert-image"; private const string ACTION_INSERT_LINK = "insert-link"; - private const string ACTION_COMPOSE_AS_HTML = "compose-as-html"; + private const string ACTION_TEXT_FORMAT = "text-format"; private const string ACTION_SHOW_EXTENDED_HEADERS = "show-extended-headers"; private const string ACTION_SHOW_FORMATTING = "show-formatting"; private const string ACTION_DISCARD = "discard"; @@ -181,7 +181,7 @@ public class Composer.Widget : Gtk.EventBox, Geary.BaseInterface { { ACTION_ADD_ORIGINAL_ATTACHMENTS, on_pending_attachments }, { ACTION_CLOSE, on_close }, { ACTION_DISCARD, on_discard }, - { ACTION_COMPOSE_AS_HTML, on_toggle_action, null, "true", on_compose_as_html_toggled }, + { ACTION_TEXT_FORMAT, null, "s", "'html'", on_text_format }, { ACTION_DETACH, on_detach }, { ACTION_OPEN_INSPECTOR, on_open_inspector }, { ACTION_SELECT_DICTIONARY, on_select_dictionary }, @@ -377,13 +377,17 @@ public class Composer.Widget : Gtk.EventBox, Geary.BaseInterface { [GtkChild] private Gtk.Box toolbar_box; [GtkChild] private Gtk.Box top_buttons; [GtkChild] private Gtk.Box bottom_buttons; + [GtkChild] private Gtk.MenuButton font_button; + [GtkChild] private Gtk.Stack font_button_stack; + [GtkChild] private Gtk.MenuButton font_size_button; + [GtkChild] private Gtk.Image font_color_icon; + [GtkChild] private Gtk.MenuButton text_format_button; + [GtkChild] private Gtk.Button insert_link_button; [GtkChild] private Gtk.Button select_dictionary_button; [GtkChild] - private Gtk.MenuButton menu_button; - [GtkChild] private Gtk.Label info_label; [GtkChild] @@ -392,9 +396,6 @@ public class Composer.Widget : Gtk.EventBox, Geary.BaseInterface { private GLib.SimpleActionGroup composer_actions = new GLib.SimpleActionGroup(); private GLib.SimpleActionGroup editor_actions = new GLib.SimpleActionGroup(); - private Menu html_menu; - private Menu plain_menu; - private Menu context_menu_model; private Menu context_menu_rich_text; private Menu context_menu_plain_text; @@ -549,8 +550,6 @@ public class Composer.Widget : Gtk.EventBox, Geary.BaseInterface { Gtk.Builder builder = new Gtk.Builder.from_resource( "/org/gnome/Geary/composer-menus.ui" ); - this.html_menu = (Menu) builder.get_object("html_menu_model"); - this.plain_menu = (Menu) builder.get_object("plain_menu_model"); this.context_menu_model = (Menu) builder.get_object("context_menu_model"); this.context_menu_rich_text = (Menu) builder.get_object("context_menu_rich_text"); this.context_menu_plain_text = (Menu) builder.get_object("context_menu_plain_text"); @@ -614,6 +613,8 @@ public class Composer.Widget : Gtk.EventBox, Geary.BaseInterface { ((Gtk.CellRendererText) cells.data).ellipsize = END; load_entry_completions(); + + update_color_icon.begin(Util.Gtk.rgba(0, 0, 0, 0)); } public Widget.from_mailbox(Application.Client application, @@ -757,8 +758,8 @@ public class Composer.Widget : Gtk.EventBox, Geary.BaseInterface { // model and hence the menu_button constructs a new // popover. this.composer_actions.change_action_state( - ACTION_COMPOSE_AS_HTML, - this.application.config.compose_as_html + ACTION_TEXT_FORMAT, + this.application.config.compose_as_html ? "html" : "plain" ); set_mode(DETACHED); @@ -1149,7 +1150,8 @@ public class Composer.Widget : Gtk.EventBox, Geary.BaseInterface { ACTION_SHOW_EXTENDED_HEADERS, false ); entries_users.change_action_state( - ACTION_COMPOSE_AS_HTML, this.application.config.compose_as_html + ACTION_TEXT_FORMAT, + this.application.config.compose_as_html ? "html" : "plain" ); } @@ -2119,9 +2121,9 @@ public class Composer.Widget : Gtk.EventBox, Geary.BaseInterface { action.change_state(!action.state.get_boolean()); } - private void on_compose_as_html_toggled(SimpleAction? action, Variant? new_state) { - bool compose_as_html = new_state.get_boolean(); - action.set_state(compose_as_html); + private void on_text_format(SimpleAction? action, Variant? new_state) { + bool compose_as_html = new_state.get_string() == "html"; + action.set_state(new_state.get_string()); foreach (string html_action in HTML_ACTIONS) get_action(html_action).set_enabled(compose_as_html); @@ -2132,11 +2134,10 @@ public class Composer.Widget : Gtk.EventBox, Geary.BaseInterface { show_formatting.set_enabled(compose_as_html); update_formatting_toolbar(); - this.menu_button.menu_model = (compose_as_html) ? this.html_menu : this.plain_menu; - this.editor.set_rich_text(compose_as_html); this.application.config.compose_as_html = compose_as_html; + this.text_format_button.popover.popdown(); } private void on_show_extended_headers_toggled(GLib.SimpleAction? action, @@ -2165,10 +2166,14 @@ public class Composer.Widget : Gtk.EventBox, Geary.BaseInterface { } private void on_font_family(SimpleAction action, Variant? param) { + string font = param.get_string(); this.editor.execute_editing_command_with_argument( - "fontname", param.get_string() + "fontname", font ); - action.set_state(param.get_string()); + action.set_state(font); + + this.font_button_stack.visible_child_name = font; + this.font_button.popover.popdown(); } private void on_font_size(SimpleAction action, Variant? param) { @@ -2182,15 +2187,35 @@ public class Composer.Widget : Gtk.EventBox, Geary.BaseInterface { this.editor.execute_editing_command_with_argument("fontsize", size); action.set_state(param.get_string()); + + this.font_size_button.popover.popdown(); + } + + private async void update_color_icon(Gdk.RGBA color) { + var theme = Gtk.IconTheme.get_default(); + var icon = theme.lookup_icon("font-color-symbolic", 16, 0); + Gdk.RGBA fg_color = Util.Gtk.rgba(0, 0, 0, 1); + this.get_style_context().lookup_color("theme_fg_color", out fg_color); + + try { + var pixbuf = yield icon.load_symbolic_async(fg_color, color, null, null, null); + this.font_color_icon.pixbuf = pixbuf; + } catch(Error e) { + warning("Could not load icon `font-color-symbolic`!"); + this.font_color_icon.icon_name = "font-color-symbolic"; + } } private void on_select_color() { Gtk.ColorChooserDialog dialog = new Gtk.ColorChooserDialog(_("Select Color"), this.container.top_window); if (dialog.run() == Gtk.ResponseType.OK) { + var rgba = dialog.get_rgba(); this.editor.execute_editing_command_with_argument( - "forecolor", dialog.get_rgba().to_string() + "forecolor", rgba.to_string() ); + + this.update_color_icon.begin(rgba); } dialog.destroy(); } @@ -2675,6 +2700,8 @@ public class Composer.Widget : Gtk.EventBox, Geary.BaseInterface { ACTION_FONT_FAMILY, context.font_family ); + this.update_color_icon.begin(context.font_color); + if (context.font_size < 11) this.editor_actions.change_action_state(ACTION_FONT_SIZE, "small"); else if (context.font_size > 20) diff --git a/src/client/util/util-gtk.vala b/src/client/util/util-gtk.vala index 70864ac9..1d41f2e5 100644 --- a/src/client/util/util-gtk.vala +++ b/src/client/util/util-gtk.vala @@ -218,4 +218,13 @@ namespace Util.Gtk { return new_url; } + public Gdk.RGBA rgba(double red, double green, double blue, double alpha) { + return Gdk.RGBA() { + red = red, + green = green, + blue = blue, + alpha = alpha + }; + } + } diff --git a/test/client/composer/composer-web-view-test.vala b/test/client/composer/composer-web-view-test.vala index 64828f04..97c2af29 100644 --- a/test/client/composer/composer-web-view-test.vala +++ b/test/client/composer/composer-web-view-test.vala @@ -35,15 +35,20 @@ public class Composer.WebViewTest : Components.WebViewTestCase } public void edit_context() throws Error { - assert(!(new WebView.EditContext("0,,,").is_link)); - assert(new WebView.EditContext("1,,,").is_link); - assert(new WebView.EditContext("1,url,,").link_url == "url"); + assert(!(new WebView.EditContext("0;;;;").is_link)); + assert(new WebView.EditContext("1;;;;").is_link); + assert(new WebView.EditContext("1;url;;;").link_url == "url"); - assert(new WebView.EditContext("0,,Helvetica,").font_family == "sans"); - assert(new WebView.EditContext("0,,Times New Roman,").font_family == "serif"); - assert(new WebView.EditContext("0,,Courier,").font_family == "monospace"); + assert(new WebView.EditContext("0;;Helvetica;;").font_family == "sans"); + assert(new WebView.EditContext("0;;Times New Roman;;").font_family == "serif"); + assert(new WebView.EditContext("0;;Courier;;").font_family == "monospace"); - assert(new WebView.EditContext("0,,,12").font_size == 12); + assert(new WebView.EditContext("0;;;12;").font_size == 12); + + assert(new WebView.EditContext("0;;;;rgb(0, 0, 0)").font_color == Util.Gtk.rgba(0, 0, 0, 1)); + assert(new WebView.EditContext("0;;;;rgb(255, 0, 0)").font_color == Util.Gtk.rgba(1, 0, 0, 1)); + assert(new WebView.EditContext("0;;;;rgb(0, 255, 0)").font_color == Util.Gtk.rgba(0, 1, 0, 1)); + assert(new WebView.EditContext("0;;;;rgb(0, 0, 255)").font_color == Util.Gtk.rgba(0, 0, 1, 1)); } public void get_html() throws GLib.Error { diff --git a/test/js/composer-page-state-test.vala b/test/js/composer-page-state-test.vala index dfb8566c..6228cc68 100644 --- a/test/js/composer-page-state-test.vala +++ b/test/js/composer-page-state-test.vala @@ -127,7 +127,7 @@ class Composer.PageStateTest : Components.WebViewTestCase { Util.JS.to_string( run_javascript(@"new EditContext(document.getElementById('test')).encode()") .get_js_value() - ).has_prefix("1,url,")); + ).has_prefix("1;url;")); } catch (Util.JS.Error err) { print("Util.JS.Error: %s\n", err.message); assert_not_reached(); @@ -138,7 +138,7 @@ class Composer.PageStateTest : Components.WebViewTestCase { } public void edit_context_font() throws Error { - string html = "

para

"; + string html = "

para

"; load_body_fixture(html); try { @@ -146,7 +146,7 @@ class Composer.PageStateTest : Components.WebViewTestCase { Util.JS.to_string( run_javascript(@"new EditContext(document.getElementById('test')).encode()") .get_js_value() - ) == "0,,Comic Sans,144"); + ) == "0;;Comic Sans;144;rgb(255, 127, 1)"); } catch (Util.JS.Error err) { print("Util.JS.Error: %s\n", err.message); assert_not_reached(); diff --git a/ui/composer-menus.ui b/ui/composer-menus.ui index 2f4c342f..7acf20ab 100644 --- a/ui/composer-menus.ui +++ b/ui/composer-menus.ui @@ -1,64 +1,6 @@ - -
- - S_ans Serif - edt.font-family - sans - - - S_erif - edt.font-family - serif - - - _Fixed Width - edt.font-family - monospace - -
-
- - _Small - edt.font-size - small - - - _Medium - edt.font-size - medium - - - Lar_ge - edt.font-size - large - -
-
- - C_olor - edt.color - -
-
- - _Rich Text - win.compose-as-html - -
-
- - -
- - _Rich Text - win.compose-as-html - -
-
-
diff --git a/ui/composer-web-view.js b/ui/composer-web-view.js index e3870190..fc5dbf91 100644 --- a/ui/composer-web-view.js +++ b/ui/composer-web-view.js @@ -684,21 +684,24 @@ EditContext.prototype = { } this.fontFamily = fontFamily; this.fontSize = styles.getPropertyValue("font-size").replace("px", ""); + this.fontColor = styles.getPropertyValue("color"); }, equals: function(other) { return other != null && this.context == other.context && this.linkUrl == other.linkUrl && this.fontFamily == other.fontFamily - && this.fontSize == other.fontSize; + && this.fontSize == other.fontSize + && this.fontColor == other.fontColor; }, encode: function() { return [ this.context.toString(16), this.linkUrl, this.fontFamily, - this.fontSize - ].join(","); + this.fontSize, + this.fontColor + ].join(";"); } }; diff --git a/ui/composer-widget.ui b/ui/composer-widget.ui index 1288ac6e..94768a21 100644 --- a/ui/composer-widget.ui +++ b/ui/composer-widget.ui @@ -798,6 +798,137 @@ + + + True + True + False + False + Remove text formatting + edt.remove-format + True + + + True + False + 16 + format-text-remove-symbolic + + + + + + + + + True + False + 6 + start + + + True + True + font_menu + Change font type + + + True + False + horizontal + + + True + False + + + True + False + Sans Serif + start + + + sans + + + + + True + False + Serif + start + + + serif + + + + + True + False + Fixed Width + start + + + monospace + + + + + + + True + False + pan-down + + + + + + + + + True + True + edt.color + Change font color + + + True + False + + + + + + + True + True + font_size_menu + Change font size + + + True + False + horizontal + + + True + False + font-size-symbolic + + + + + True + False + pan-down + + + + + + True @@ -857,33 +988,6 @@ - - - True - False - 6 - start - - - True - True - False - False - Remove text formatting - edt.remove-format - True - - - True - False - 16 - format-text-remove-symbolic - - - - - - @@ -963,15 +1067,16 @@ - + True False False False + rich_text_menu True - view-more-symbolic + format-text-bold-symbolic @@ -1064,4 +1169,59 @@ + + +
+ + S_ans Serif + edt.font-family + sans + + + S_erif + edt.font-family + serif + + + _Fixed Width + edt.font-family + monospace + +
+
+ + +
+ + _Small + edt.font-size + small + + + _Medium + edt.font-size + medium + + + Lar_ge + edt.font-size + large + +
+
+ + +
+ + _Rich Text + win.text-format + html + + + _Plain Text + win.text-format + plain + +
+