[geary] Use GLib.Actions in the composer. Bug 770356.
- From: Michael Gratton <mjog src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [geary] Use GLib.Actions in the composer. Bug 770356.
- Date: Wed, 21 Sep 2016 05:06:25 +0000 (UTC)
commit ebd788968b6e230022e38d5699bbc510e04738b4
Author: Niels De Graef <nielsdegraef gmail com>
Date: Mon Aug 22 11:09:25 2016 +0200
Use GLib.Actions in the composer. Bug 770356.
Now using these instead of the old composer's actions. This led to quite
some changes:
* Use GLib.ActionEntry instead of Gtk.ActionEntry in
composer-widget.vala
* Action names can now be specified in the UI files.
* Use templates for the ComposerHeaderBar. Remove
Pillbar as superclass, since that was no longer necessary.
* Merge ComposerToolbar into ComposerWidget.
* Since actions can now be parameterized, some methods could be
merged (e.g. font size methods).
* The menu button in the composer now automatically uses a popover.
* Some methods and classes really deserved more comments.
* necessary POTFILES.in changes
Signed-off-by: Niels De Graef <nielsdegraef gmail com>
po/POTFILES.in | 6 +-
src/CMakeLists.txt | 1 -
src/client/application/geary-controller.vala | 11 +-
src/client/components/pill-toolbar.vala | 13 -
src/client/composer/composer-box.vala | 97 +-
src/client/composer/composer-container.vala | 127 ++-
src/client/composer/composer-embed.vala | 190 ++--
src/client/composer/composer-headerbar.vala | 107 +-
src/client/composer/composer-toolbar.vala | 58 -
src/client/composer/composer-widget.vala | 1927 ++++++++++++--------------
src/client/composer/composer-window.vala | 59 +-
src/client/composer/email-entry.vala | 15 +-
src/client/util/util-gtk.vala | 15 +
ui/CMakeLists.txt | 5 +-
ui/composer-headerbar.ui | 198 +++
ui/composer-menus.ui | 108 ++
ui/composer-widget.ui | 627 +++++++++
ui/composer.glade | 649 ---------
ui/composer_accelerators.ui | 43 -
19 files changed, 2201 insertions(+), 2055 deletions(-)
---
diff --git a/po/POTFILES.in b/po/POTFILES.in
index edca1ae..1d0e430 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -39,7 +39,6 @@ src/client/composer/composer-box.vala
src/client/composer/composer-container.vala
src/client/composer/composer-embed.vala
src/client/composer/composer-headerbar.vala
-src/client/composer/composer-toolbar.vala
src/client/composer/composer-widget.vala
src/client/composer/composer-window.vala
src/client/composer/contact-entry-completion.vala
@@ -384,8 +383,9 @@ src/mailer/main.vala
[type: gettext/glade]ui/account_spinner.glade
[type: gettext/glade]ui/app_menu.interface
[type: gettext/glade]ui/certificate_warning_dialog.glade
-[type: gettext/glade]ui/composer_accelerators.ui
-[type: gettext/glade]ui/composer.glade
+[type: gettext/glade]ui/composer-headerbar.ui
+[type: gettext/glade]ui/composer-menus.ui
+[type: gettext/glade]ui/composer-widget.ui
[type: gettext/glade]ui/edit_alternate_emails.glade
[type: gettext/glade]ui/find_bar.glade
[type: gettext/glade]ui/login.glade
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 51396b4..c05bbd3 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -348,7 +348,6 @@ client/composer/composer-box.vala
client/composer/composer-container.vala
client/composer/composer-embed.vala
client/composer/composer-headerbar.vala
-client/composer/composer-toolbar.vala
client/composer/composer-widget.vala
client/composer/composer-window.vala
client/composer/contact-entry-completion.vala
diff --git a/src/client/application/geary-controller.vala b/src/client/application/geary-controller.vala
index 3e6d772..34e6045 100644
--- a/src/client/application/geary-controller.vala
+++ b/src/client/application/geary-controller.vala
@@ -2199,7 +2199,16 @@ public class GearyController : Geary.BaseObject {
bool is_draft = false) {
create_compose_widget_async.begin(compose_type, referred, quote, mailto, is_draft);
}
-
+
+ /**
+ * Creates a composer widget. Depending on the arguments, this can be inline in the
+ * conversation or as a new window.
+ * @param compose_type - Whether it's a new message, a reply, a forwarded mail, ...
+ * @param referred - The mail of which we should copy the from/to/... addresses
+ * @param quote - The quote after the mail body
+ * @param mailto - A "mailto:"-link
+ * @param is_draft - Whether we're starting from a draft (true) or a new mail (false)
+ */
private async void create_compose_widget_async(ComposerWidget.ComposeType compose_type,
Geary.Email? referred = null, string? quote = null, string? mailto = null,
bool is_draft = false) {
diff --git a/src/client/components/pill-toolbar.vala b/src/client/components/pill-toolbar.vala
index 78d911b..849a94f 100644
--- a/src/client/components/pill-toolbar.vala
+++ b/src/client/components/pill-toolbar.vala
@@ -156,19 +156,6 @@ public class PillHeaderbar : Gtk.HeaderBar, PillBar {
public PillHeaderbar(Gtk.ActionGroup toolbar_action_group) {
initialize(toolbar_action_group);
}
-
- public bool close_button_at_end() {
- string layout;
- bool at_end = false;
- layout = Gtk.Settings.get_default().gtk_decoration_layout;
- // Based on logic of close_button_at_end in gtkheaderbar.c: Close button appears
- // at end iff "close" follows a colon in the layout string.
- if (layout != null) {
- int colon_ind = layout.index_of(":");
- at_end = (colon_ind >= 0 && layout.index_of("close", colon_ind) >= 0);
- }
- return at_end;
- }
}
/**
diff --git a/src/client/composer/composer-box.vala b/src/client/composer/composer-box.vala
index dd6344e..cf1596e 100644
--- a/src/client/composer/composer-box.vala
+++ b/src/client/composer/composer-box.vala
@@ -4,95 +4,78 @@
* (version 2.1 or later). See the COPYING file in this distribution.
*/
+/**
+ * A ComposerBox is a ComposerContainer that is used to compose mails in the main-window
+ * (i.e. not-detached), yet separate from a conversation.
+ */
public class ComposerBox : Gtk.Frame, ComposerContainer {
-
- private ComposerWidget composer;
+
private Gee.Set<Geary.App.Conversation>? prev_selection = null;
- private bool has_accel_group = false;
-
- public Gtk.Window top_window {
- get { return (Gtk.Window) get_toplevel(); }
+
+ protected ComposerWidget composer { get; set; }
+
+ protected Gee.MultiMap<string, string>? old_accelerators { get; set; }
+
+ public Gtk.ApplicationWindow top_window {
+ get { return (Gtk.ApplicationWindow) get_toplevel(); }
}
-
+
public ComposerBox(ComposerWidget composer) {
this.composer = composer;
-
- add(composer);
- composer.editor.focus_in_event.connect(on_focus_in);
- composer.editor.focus_out_event.connect(on_focus_out);
+
+ add(this.composer);
+ this.composer.editor.focus_in_event.connect(on_focus_in);
+ this.composer.editor.focus_out_event.connect(on_focus_out);
show();
-
+
get_style_context().add_class("geary-composer-box");
- if (composer.state == ComposerWidget.ComposerState.NEW) {
+ if (this.composer.state == ComposerWidget.ComposerState.NEW) {
ConversationListView conversation_list_view = ((MainWindow) GearyApplication.
instance.controller.main_window).conversation_list_view;
- prev_selection = conversation_list_view.get_selected_conversations();
+ this.prev_selection = conversation_list_view.get_selected_conversations();
conversation_list_view.get_selection().unselect_all();
-
- composer.free_header();
+
+ this.composer.free_header();
GearyApplication.instance.controller.main_window.main_toolbar.set_conversation_header(
composer.header);
get_style_context().add_class("geary-full-pane");
}
}
-
+
public void remove_composer() {
- if (composer.editor.has_focus)
+ if (this.composer.editor.has_focus)
on_focus_out();
- composer.editor.focus_in_event.disconnect(on_focus_in);
- composer.editor.focus_out_event.disconnect(on_focus_out);
-
- remove(composer);
+ this.composer.editor.focus_in_event.disconnect(on_focus_in);
+ this.composer.editor.focus_out_event.disconnect(on_focus_out);
+
+ remove(this.composer);
close_container();
}
-
-
- private bool on_focus_in() {
- // For some reason, on_focus_in gets called a bunch upon construction.
- if (!has_accel_group)
- top_window.add_accel_group(composer.ui.get_accel_group());
- has_accel_group = true;
- return false;
- }
-
- private bool on_focus_out() {
- top_window.remove_accel_group(composer.ui.get_accel_group());
- has_accel_group = false;
- return false;
- }
-
- public void present() {
- top_window.present();
- }
-
- public unowned Gtk.Widget get_focus() {
- return top_window.get_focus();
- }
-
+
public void vanish() {
hide();
parent.hide();
if (get_style_context().has_class("geary-full-pane"))
GearyApplication.instance.controller.main_window.main_toolbar.remove_conversation_header(
composer.header);
-
- composer.state = ComposerWidget.ComposerState.DETACHED;
- composer.editor.focus_in_event.disconnect(on_focus_in);
- composer.editor.focus_out_event.disconnect(on_focus_out);
-
- if (prev_selection != null) {
+
+ this.composer.state = ComposerWidget.ComposerState.DETACHED;
+ this.composer.editor.focus_in_event.disconnect(on_focus_in);
+ this.composer.editor.focus_out_event.disconnect(on_focus_out);
+
+ if (this.prev_selection != null) {
ConversationListView conversation_list_view = ((MainWindow) GearyApplication.
instance.controller.main_window).conversation_list_view;
- if (prev_selection.is_empty)
+ if (this.prev_selection.is_empty)
// Need to trigger "No messages selected"
- conversation_list_view.conversations_selected(prev_selection);
+ conversation_list_view.conversations_selected(this.prev_selection);
else
- conversation_list_view.select_conversations(prev_selection);
- prev_selection = null;
+ conversation_list_view.select_conversations(this.prev_selection);
+ this.prev_selection = null;
}
}
-
+
public void close_container() {
if (visible)
vanish();
diff --git a/src/client/composer/composer-container.vala b/src/client/composer/composer-container.vala
index 10366b2..22ac120 100644
--- a/src/client/composer/composer-container.vala
+++ b/src/client/composer/composer-container.vala
@@ -4,12 +4,129 @@
* (version 2.1 or later). See the COPYING file in this distribution.
*/
+/**
+ * A generic interface for widgets that have a single ComposerWidget-child.
+ */
public interface ComposerContainer {
- public abstract Gtk.Window top_window { get; }
-
- public abstract void present();
- public abstract unowned Gtk.Widget get_focus();
- public abstract void vanish();
+ // The ComposerWidget-child.
+ protected abstract ComposerWidget composer { get; set; }
+
+ // Workaround to retrieve all Gtk.Actions with conflicting accelerators
+ protected const string[] conflicting_actions = {
+ GearyController.ACTION_MARK_AS_UNREAD,
+ GearyController.ACTION_FORWARD_MESSAGE
+ };
+
+ // We use old_accelerators to keep track of the accelerators we temporarily disabled.
+ protected abstract Gee.MultiMap<string, string>? old_accelerators { get; set; }
+
+ // The toplevel window for the container. Note that it needs to be a GtkApplicationWindow.
+ public abstract Gtk.ApplicationWindow top_window { get; }
+
+ public virtual void present() {
+ this.top_window.present();
+ }
+
+ public virtual unowned Gtk.Widget get_focus() {
+ return this.top_window.get_focus();
+ }
+
public abstract void close_container();
+
+ /**
+ * Hides the widget (and possibly its parent). Usecase is when you don't want to close just yet
+ * but the composer should not be visible any longer (e.g. when you're still saving a draft).
+ */
+ public abstract void vanish();
+
+ /**
+ * Removes the composer from this ComposerContainer (e.g. when detaching)
+ */
public abstract void remove_composer();
+
+ protected virtual bool on_focus_in() {
+ if (this.old_accelerators == null) {
+ this.old_accelerators = new Gee.HashMultiMap<string, string>();
+ add_accelerators();
+ }
+ return false;
+ }
+
+ protected virtual bool on_focus_out() {
+ if (this.old_accelerators != null) {
+ remove_accelerators();
+ this.old_accelerators = null;
+ }
+ return false;
+ }
+
+ /**
+ * Adds the accelerators for the child composer, and temporarily removes conflicting
+ * accelerators from existing actions.
+ */
+ protected virtual void add_accelerators() {
+ GearyApplication app = GearyApplication.instance;
+
+ // Check for actions with conflicting accelerators
+ foreach (string action in ComposerWidget.action_accelerators.get_keys()) {
+ foreach (string accelerator in ComposerWidget.action_accelerators[action]) {
+ string[] actions = app.get_actions_for_accel(accelerator);
+
+ foreach (string conflicting_action in actions) {
+ remove_conflicting_accelerator(conflicting_action, accelerator);
+ this.old_accelerators[conflicting_action] = accelerator;
+ }
+ }
+ }
+
+ // Very stupid workaround while we still use Gtk.Actions in the GearyController
+ foreach (string conflicting_action in conflicting_actions)
+ app.actions.get_action(conflicting_action).disconnect_accelerator();
+
+ // Now add our actions to the window and their accelerators
+ foreach (string action in ComposerWidget.action_accelerators.get_keys()) {
+ this.top_window.add_action(composer.get_action(action));
+ app.set_accels_for_action("win." + action,
+ ComposerWidget.action_accelerators[action].to_array());
+ }
+ }
+
+ /**
+ * Removes the accelerators for the child composer, and restores previously removed accelerators.
+ */
+ protected virtual void remove_accelerators() {
+ foreach (string action in ComposerWidget.action_accelerators.get_keys())
+ GearyApplication.instance.set_accels_for_action("win." + action, {});
+
+ // Very stupid workaround while we still use Gtk.Actions in the GearyController
+ foreach (string conflicting_action in conflicting_actions)
+ GearyApplication.instance.actions.get_action(conflicting_action).connect_accelerator();
+
+ foreach (string action in old_accelerators.get_keys())
+ foreach (string accelerator in this.old_accelerators[action])
+ restore_conflicting_accelerator(action, accelerator);
+ }
+
+ // Helper method. Removes the given conflicting accelerator from the action's accelerators.
+ private void remove_conflicting_accelerator(string action, string accelerator) {
+ GearyApplication app = GearyApplication.instance;
+ string[] accelerators = app.get_accels_for_action(action);
+ if (accelerators.length == 0)
+ return;
+
+ string[] without_accel = new string[accelerators.length - 1];
+ foreach (string a in accelerators)
+ if (a != accelerator)
+ without_accel += a;
+
+ app.set_accels_for_action(action, without_accel);
+ }
+
+ // Helper method. Adds the given accelerator back to the action's accelerators.
+ private void restore_conflicting_accelerator(string action, string accelerator) {
+ GearyApplication app = GearyApplication.instance;
+ string[] accelerators = app.get_accels_for_action(action);
+ accelerators += accelerator;
+ app.set_accels_for_action(action, accelerators);
+ }
}
diff --git a/src/client/composer/composer-embed.vala b/src/client/composer/composer-embed.vala
index e404e3a..1c06300 100644
--- a/src/client/composer/composer-embed.vala
+++ b/src/client/composer/composer-embed.vala
@@ -4,41 +4,49 @@
* (version 2.1 or later). See the COPYING file in this distribution.
*/
+/**
+ * A ComposerEmbed is a widget that is used to compose emails that are inlined into a
+ * conversation view, e.g. for reply or forward mails.
+ */
public class ComposerEmbed : Gtk.EventBox, ComposerContainer {
-
+
private const int MIN_EDITOR_HEIGHT = 200;
-
- private ComposerWidget composer;
+
private ConversationViewer conversation_viewer;
+
+ // The id of the composer HTML element
private string embed_id;
private bool setting_inner_scroll;
private bool scrolled_to_bottom = false;
private double inner_scroll_adj_value;
private int inner_view_height;
private int min_height = MIN_EDITOR_HEIGHT;
- private bool has_accel_group = false;
-
- public Gtk.Window top_window {
- get { return (Gtk.Window) get_toplevel(); }
+
+ protected ComposerWidget composer { get; set; }
+
+ protected Gee.MultiMap<string, string>? old_accelerators { get; set; }
+
+ public Gtk.ApplicationWindow top_window {
+ get { return (Gtk.ApplicationWindow) get_toplevel(); }
}
-
+
public ComposerEmbed(ComposerWidget composer, ConversationViewer conversation_viewer,
Geary.Email referred) {
this.composer = composer;
this.conversation_viewer = conversation_viewer;
- halign = Gtk.Align.FILL;
- valign = Gtk.Align.FILL;
-
+ this.halign = Gtk.Align.FILL;
+ this.valign = Gtk.Align.FILL;
+
WebKit.DOM.HTMLElement? email_element = null;
email_element = conversation_viewer.web_view.get_dom_document().get_element_by_id(
conversation_viewer.get_div_id(referred.id)) as WebKit.DOM.HTMLElement;
- embed_id = referred.id.to_string() + "_reply";
+ this.embed_id = referred.id.to_string() + "_reply";
if (email_element == null) {
warning("Embedded composer could not find email to follow.");
email_element = conversation_viewer.web_view.get_dom_document().get_element_by_id(
"placeholder") as WebKit.DOM.HTMLElement;
}
-
+
try {
email_element.insert_adjacent_html("afterend",
@"<div id='$embed_id' class='composer_embed'></div>");
@@ -46,30 +54,30 @@ public class ComposerEmbed : Gtk.EventBox, ComposerContainer {
debug("Error creating embed element: %s", error.message);
return;
}
-
+
add(composer);
realize.connect(on_realize);
- composer.editor.focus_in_event.connect(on_focus_in);
- composer.editor.focus_out_event.connect(on_focus_out);
- composer.editor.document_load_finished.connect(on_loaded);
- conversation_viewer.compose_overlay.add_overlay(this);
+ this.composer.editor.focus_in_event.connect(on_focus_in);
+ this.composer.editor.focus_out_event.connect(on_focus_out);
+ this.composer.editor.document_load_finished.connect(on_loaded);
+ this.conversation_viewer.compose_overlay.add_overlay(this);
show();
present();
}
-
+
private void on_realize() {
update_style();
-
- Gtk.ScrolledWindow win = (Gtk.ScrolledWindow) composer.editor.parent;
+
+ Gtk.ScrolledWindow win = (Gtk.ScrolledWindow) this.composer.editor.parent;
win.get_vscrollbar().hide();
-
- composer.editor.vadjustment.value_changed.connect(on_inner_scroll);
- composer.editor.vadjustment.changed.connect(on_adjust_changed);
- composer.editor.user_changed_contents.connect(on_inner_size_changed);
-
+
+ this.composer.editor.vadjustment.value_changed.connect(on_inner_scroll);
+ this.composer.editor.vadjustment.changed.connect(on_adjust_changed);
+ this.composer.editor.user_changed_contents.connect(on_inner_size_changed);
+
reroute_scroll_handling(this);
}
-
+
private void on_loaded() {
try {
composer.editor.get_dom_document().body.get_class_list().add("embedded");
@@ -82,7 +90,7 @@ public class ComposerEmbed : Gtk.EventBox, ComposerContainer {
return false;
});
}
-
+
private void reroute_scroll_handling(Gtk.Widget widget) {
widget.add_events(Gdk.EventMask.SCROLL_MASK | Gdk.EventMask.SMOOTH_SCROLL_MASK);
widget.scroll_event.connect(on_inner_scroll_event);
@@ -92,7 +100,7 @@ public class ComposerEmbed : Gtk.EventBox, ComposerContainer {
reroute_scroll_handling(child);
}
}
-
+
private void disable_scroll_reroute(Gtk.Widget widget) {
widget.scroll_event.disconnect(on_inner_scroll_event);
Gtk.Container? container = widget as Gtk.Container;
@@ -101,44 +109,47 @@ public class ComposerEmbed : Gtk.EventBox, ComposerContainer {
disable_scroll_reroute(child);
}
}
-
+
private void update_style() {
Gdk.RGBA window_background = top_window.get_style_context()
.get_background_color(Gtk.StateFlags.NORMAL);
Gdk.RGBA background = get_style_context().get_background_color(Gtk.StateFlags.NORMAL);
-
+
if (background == window_background)
return;
-
+
get_style_context().changed.disconnect(update_style);
override_background_color(Gtk.StateFlags.NORMAL, window_background);
get_style_context().changed.connect(update_style);
}
-
+
public void remove_composer() {
- if (composer.editor.has_focus)
+ if (this.composer.editor.has_focus)
on_focus_out();
- composer.editor.focus_in_event.disconnect(on_focus_in);
- composer.editor.focus_out_event.disconnect(on_focus_out);
- composer.editor.vadjustment.value_changed.disconnect(on_inner_scroll);
- composer.editor.user_changed_contents.disconnect(on_inner_size_changed);
+
+ this.composer.editor.focus_in_event.disconnect(on_focus_in);
+ this.composer.editor.focus_out_event.disconnect(on_focus_out);
+
+ this.composer.editor.vadjustment.value_changed.disconnect(on_inner_scroll);
+ this.composer.editor.user_changed_contents.disconnect(on_inner_size_changed);
disable_scroll_reroute(this);
Gtk.ScrolledWindow win = (Gtk.ScrolledWindow) composer.editor.parent;
win.get_vscrollbar().show();
-
+
try {
- composer.editor.get_dom_document().body.get_class_list().remove("embedded");
+ this.composer.editor.get_dom_document().body.get_class_list().remove("embedded");
} catch (Error error) {
debug("Error setting class of editor: %s", error.message);
}
-
+
remove(composer);
close_container();
}
-
+
public bool set_position(ref Gdk.Rectangle allocation, double hscroll, double vscroll,
int view_height) {
- WebKit.DOM.Element embed =
conversation_viewer.web_view.get_dom_document().get_element_by_id(embed_id);
+ WebKit.DOM.Element embed = this.conversation_viewer.web_view.get_dom_document()
+ .get_element_by_id(this.embed_id);
if (embed == null)
return false;
@@ -162,76 +173,62 @@ public class ComposerEmbed : Gtk.EventBox, ComposerContainer {
}
allocation.x = (int) (embed.offset_left + embed.client_left) - (int) hscroll;
allocation.width = (int) embed.client_width;
-
+
// Work out adjustment of composer web view
- setting_inner_scroll = true;
- composer.editor.vadjustment.set_value(allocation.y - y_top);
- setting_inner_scroll = false;
+ this.setting_inner_scroll = true;
+ this.composer.editor.vadjustment.set_value(allocation.y - y_top);
+ this.setting_inner_scroll = false;
// This sets the scroll before the widget gets resized. Although the adjustment
// may be scrolled to the bottom right now, the current value may not do that
// once the widget is shrunk; for example, while scrolling down the page past
// the bottom of the editor. So if we're at the bottom, record that fact. When
// the limits of the adjustment are changed (watched by on_adjust_changed), we
// can keep it at the bottom.
- scrolled_to_bottom = (y_top <= 0 && available_height < view_height);
-
+ this.scrolled_to_bottom = (y_top <= 0 && available_height < view_height);
+
return true;
}
-
- private bool on_focus_in() {
- // For some reason, on_focus_in gets called a bunch upon construction.
- if (!has_accel_group)
- top_window.add_accel_group(composer.ui.get_accel_group());
- has_accel_group = true;
- return false;
- }
-
- private bool on_focus_out() {
- top_window.remove_accel_group(composer.ui.get_accel_group());
- has_accel_group = false;
- return false;
- }
-
+
private void on_inner_scroll(Gtk.Adjustment adj) {
- double delta = adj.value - inner_scroll_adj_value;
- inner_scroll_adj_value = adj.value;
+ double delta = adj.value - this.inner_scroll_adj_value;
+ this.inner_scroll_adj_value = adj.value;
if (delta != 0 && !setting_inner_scroll) {
- Gtk.Adjustment outer_adj = conversation_viewer.web_view.vadjustment;
+ Gtk.Adjustment outer_adj = this.conversation_viewer.web_view.vadjustment;
outer_adj.set_value(outer_adj.value + delta);
}
}
-
+
private void on_adjust_changed(Gtk.Adjustment adj) {
- if (scrolled_to_bottom) {
- setting_inner_scroll = true;
+ if (this.scrolled_to_bottom) {
+ this.setting_inner_scroll = true;
adj.set_value(adj.upper);
- setting_inner_scroll = false;
+ this.setting_inner_scroll = false;
}
}
-
+
private void on_inner_size_changed() {
- scrolled_to_bottom = false; // The inserted character may cause a desired scroll
+ this.scrolled_to_bottom = false; // The inserted character may cause a desired scroll
Idle.add(recalc_height); // So that this runs after the character has been inserted
}
-
+
private bool recalc_height() {
int view_height,
- base_height = get_allocated_height() - composer.editor.get_allocated_height();
+ base_height = get_allocated_height() - this.composer.editor.get_allocated_height();
try {
- view_height = (int) composer.editor.get_dom_document()
+ view_height = (int) this.composer.editor.get_dom_document()
.query_selector("#message-body").offset_height;
} catch (Error error) {
debug("Error getting height of editor: %s", error.message);
return false;
}
-
+
if (view_height != inner_view_height || min_height != base_height + MIN_EDITOR_HEIGHT) {
- inner_view_height = view_height;
- min_height = base_height + MIN_EDITOR_HEIGHT;
+ this.inner_view_height = view_height;
+ this.min_height = base_height + MIN_EDITOR_HEIGHT;
// Calculate height widget should be to avoid scrolling in editor
int widget_height = int.max(view_height + base_height - 2, min_height); //? about 2
- WebKit.DOM.Element embed = conversation_viewer.web_view
- .get_dom_document().get_element_by_id(embed_id);
+ WebKit.DOM.Element embed = this.conversation_viewer.web_view
+ .get_dom_document().get_element_by_id(this.embed_id);
if (embed != null) {
try {
embed.style.set_property("height", @"$widget_height", "");
@@ -242,40 +239,37 @@ public class ComposerEmbed : Gtk.EventBox, ComposerContainer {
}
return false;
}
-
+
private bool on_inner_scroll_event(Gdk.EventScroll event) {
- conversation_viewer.web_view.scroll_event(event);
+ this.conversation_viewer.web_view.scroll_event(event);
return true;
}
-
+
public void present() {
- top_window.present();
- conversation_viewer.web_view.get_dom_document().get_element_by_id(embed_id)
+ this.top_window.present();
+ this.conversation_viewer.web_view.get_dom_document().get_element_by_id(this.embed_id)
.scroll_into_view_if_needed(false);
}
-
- public unowned Gtk.Widget get_focus() {
- return top_window.get_focus();
- }
-
+
public void vanish() {
hide();
- composer.state = ComposerWidget.ComposerState.DETACHED;
- composer.editor.focus_in_event.disconnect(on_focus_in);
- composer.editor.focus_out_event.disconnect(on_focus_out);
-
- WebKit.DOM.Element embed =
conversation_viewer.web_view.get_dom_document().get_element_by_id(embed_id);
+ this.composer.state = ComposerWidget.ComposerState.DETACHED;
+ this.composer.editor.focus_in_event.disconnect(on_focus_in);
+ this.composer.editor.focus_out_event.disconnect(on_focus_out);
+
+ WebKit.DOM.Element embed = this.conversation_viewer.web_view.get_dom_document().
+ get_element_by_id(this.embed_id);
try{
embed.parent_element.remove_child(embed);
} catch (Error error) {
warning("Could not remove embed from WebView: %s", error.message);
}
}
-
+
public void close_container() {
if (visible)
vanish();
- conversation_viewer.compose_overlay.remove(this);
+ this.conversation_viewer.compose_overlay.remove(this);
}
}
diff --git a/src/client/composer/composer-headerbar.vala b/src/client/composer/composer-headerbar.vala
index 2a1522c..e909728 100644
--- a/src/client/composer/composer-headerbar.vala
+++ b/src/client/composer/composer-headerbar.vala
@@ -4,89 +4,42 @@
* (version 2.1 or later). See the COPYING file in this distribution.
*/
-public class ComposerHeaderbar : PillHeaderbar {
-
+[GtkTemplate (ui = "/org/gnome/Geary/composer-headerbar.ui")]
+public class ComposerHeaderbar : Gtk.HeaderBar {
+
public ComposerWidget.ComposerState state { get; set; }
+
public bool show_pending_attachments { get; set; default = false; }
- public bool send_enabled { get; set; default = false; }
-
- private Gtk.Button recipients;
- private Gtk.Label recipients_label;
+
+ [GtkChild]
private Gtk.Box detach_start;
+ [GtkChild]
private Gtk.Box detach_end;
-
- public ComposerHeaderbar(Gtk.ActionGroup action_group) {
- base(action_group);
-
- show_close_button = false;
-
- bool rtl = (get_direction() == Gtk.TextDirection.RTL);
-
- // Toolbar setup.
- Gee.List<Gtk.Button> insert = new Gee.ArrayList<Gtk.Button>();
-
- // Window management.
- detach_start = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 0);
- Gtk.Button detach_button = create_toolbar_button(null, ComposerWidget.ACTION_DETACH);
- detach_button.set_relief(Gtk.ReliefStyle.NONE);
- if (rtl)
- detach_button.set_margin_start(6);
- else
- detach_button.set_margin_end(6);
- detach_start.pack_start(detach_button);
- detach_start.pack_start(new Gtk.Separator(Gtk.Orientation.VERTICAL));
-
- detach_end = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 0);
- detach_button = create_toolbar_button(null, ComposerWidget.ACTION_DETACH);
- detach_button.set_relief(Gtk.ReliefStyle.NONE);
- if (rtl)
- detach_button.set_margin_end(6);
- else
- detach_button.set_margin_start(6);
- detach_end.pack_end(detach_button);
- detach_end.pack_end(new Gtk.Separator(Gtk.Orientation.VERTICAL));
-
- insert.add(create_toolbar_button(null, ComposerWidget.ACTION_CLOSE_DISCARD));
- insert.add(create_toolbar_button(null, ComposerWidget.ACTION_CLOSE_SAVE));
- Gtk.Box close_buttons = create_pill_buttons(insert, false);
- insert.clear();
-
- Gtk.Button send_button = create_toolbar_button(null, ComposerWidget.ACTION_SEND, true);
- send_button.get_style_context().add_class("suggested-action");
-
- Gtk.Box attach_buttons = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 0);
- Gtk.Button attach_only = create_toolbar_button(null, ComposerWidget.ACTION_ADD_ATTACHMENT);
- insert.add(create_toolbar_button(null, ComposerWidget.ACTION_ADD_ATTACHMENT));
- insert.add(create_toolbar_button(null, ComposerWidget.ACTION_ADD_ORIGINAL_ATTACHMENTS));
- Gtk.Box attach_pending = create_pill_buttons(insert, false);
- attach_buttons.pack_start(attach_only);
- attach_buttons.pack_start(attach_pending);
-
- recipients = new Gtk.Button();
- recipients.set_relief(Gtk.ReliefStyle.NONE);
- recipients_label = new Gtk.Label(null);
- recipients_label.set_ellipsize(Pango.EllipsizeMode.END);
- recipients.add(recipients_label);
- recipients.clicked.connect(() => { state = ComposerWidget.ComposerState.INLINE; });
-
- bind_property("state", recipients, "visible", BindingFlags.SYNC_CREATE,
+ [GtkChild]
+ private Gtk.Button recipients_button;
+ [GtkChild]
+ private Gtk.Label recipients_label;
+ [GtkChild]
+ private Gtk.Button new_message_attach_button;
+ [GtkChild]
+ private Gtk.Box conversation_attach_buttons;
+ [GtkChild]
+ private Gtk.Button send_button;
+
+ public ComposerHeaderbar() {
+ recipients_button.clicked.connect(() => { state = ComposerWidget.ComposerState.INLINE; });
+
+ send_button.image = new Gtk.Image.from_icon_name("mail-send-symbolic", Gtk.IconSize.MENU);
+
+ bind_property("state", recipients_button, "visible", BindingFlags.SYNC_CREATE,
(binding, source_value, ref target_value) => {
target_value = (state == ComposerWidget.ComposerState.INLINE_COMPACT);
return true;
});
- bind_property("show-pending-attachments", attach_only, "visible",
+ bind_property("show-pending-attachments", new_message_attach_button, "visible",
BindingFlags.SYNC_CREATE | BindingFlags.INVERT_BOOLEAN);
- bind_property("show-pending-attachments", attach_pending, "visible",
+ bind_property("show-pending-attachments", conversation_attach_buttons, "visible",
BindingFlags.SYNC_CREATE);
- bind_property("send-enabled", send_button, "sensitive", BindingFlags.SYNC_CREATE);
-
- add_start(detach_start);
- add_start(attach_buttons);
- add_start(recipients);
-
- add_end(detach_end);
- add_end(send_button);
- add_end(close_buttons);
notify["decoration-layout"].connect(set_detach_button_side);
realize.connect(set_detach_button_side);
@@ -97,14 +50,14 @@ public class ComposerHeaderbar : PillHeaderbar {
}
});
}
-
+
public void set_recipients(string label, string tooltip) {
recipients_label.label = label;
- recipients.tooltip_text = tooltip;
+ recipients_button.tooltip_text = tooltip;
}
-
+
private void set_detach_button_side() {
- bool at_end = close_button_at_end();
+ bool at_end = GtkUtil.close_button_at_end();
detach_start.visible = !at_end;
detach_end.visible = at_end;
}
diff --git a/src/client/composer/composer-widget.vala b/src/client/composer/composer-widget.vala
index 2d07a9e..047b3c7 100644
--- a/src/client/composer/composer-widget.vala
+++ b/src/client/composer/composer-widget.vala
@@ -4,7 +4,8 @@
* (version 2.1 or later). See the COPYING file in this distribution.
*/
-// Widget for sending messages.
+// The actual widget for sending messages. Should be put in a ComposerContainer
+[GtkTemplate (ui = "/org/gnome/Geary/composer-widget.ui")]
public class ComposerWidget : Gtk.EventBox {
public enum ComposeType {
NEW_MESSAGE,
@@ -36,44 +37,104 @@ public class ComposerWidget : Gtk.EventBox {
}
}
- public const string ACTION_UNDO = "undo";
- public const string ACTION_REDO = "redo";
- public const string ACTION_CUT = "cut";
- public const string ACTION_COPY = "copy";
- public const string ACTION_COPY_LINK = "copy link";
- public const string ACTION_PASTE = "paste";
- public const string ACTION_PASTE_FORMAT = "paste with formatting";
- public const string ACTION_BOLD = "bold";
- public const string ACTION_ITALIC = "italic";
- public const string ACTION_UNDERLINE = "underline";
- public const string ACTION_STRIKETHROUGH = "strikethrough";
- public const string ACTION_REMOVE_FORMAT = "removeformat";
- public const string ACTION_INDENT = "indent";
- public const string ACTION_OUTDENT = "outdent";
- public const string ACTION_JUSTIFY_LEFT = "justifyleft";
- public const string ACTION_JUSTIFY_RIGHT = "justifyright";
- public const string ACTION_JUSTIFY_CENTER = "justifycenter";
- public const string ACTION_JUSTIFY_FULL = "justifyfull";
- public const string ACTION_MENU = "menu";
- public const string ACTION_COLOR = "color";
- public const string ACTION_INSERT_LINK = "insertlink";
- public const string ACTION_COMPOSE_AS_HTML = "compose as html";
- public const string ACTION_SHOW_EXTENDED = "show extended";
- public const string ACTION_CLOSE = "close";
- public const string ACTION_CLOSE_SAVE = "close and save";
- public const string ACTION_CLOSE_DISCARD = "close and discard";
- public const string ACTION_DETACH = "detach";
- public const string ACTION_SEND = "send";
- public const string ACTION_ADD_ATTACHMENT = "add attachment";
- public const string ACTION_ADD_ORIGINAL_ATTACHMENTS = "add original attachments";
- public const string ACTION_SELECT_DICTIONARY = "select dictionary";
-
+ private SimpleActionGroup actions = new SimpleActionGroup();
+
+ private const string ACTION_UNDO = "undo";
+ private const string ACTION_REDO = "redo";
+ private const string ACTION_CUT = "cut";
+ private const string ACTION_COPY = "copy";
+ private const string ACTION_COPY_LINK = "copy-link";
+ private const string ACTION_PASTE = "paste";
+ private const string ACTION_PASTE_WITH_FORMATTING = "paste-with-formatting";
+ private const string ACTION_BOLD = "bold";
+ private const string ACTION_ITALIC = "italic";
+ private const string ACTION_UNDERLINE = "underline";
+ private const string ACTION_STRIKETHROUGH = "strikethrough";
+ private const string ACTION_FONT_SIZE = "font-size";
+ private const string ACTION_FONT_FAMILY = "font-family";
+ private const string ACTION_REMOVE_FORMAT = "remove-format";
+ private const string ACTION_INDENT = "indent";
+ private const string ACTION_OUTDENT = "outdent";
+ private const string ACTION_JUSTIFY = "justify";
+ private const string ACTION_COLOR = "color";
+ private const string ACTION_INSERT_LINK = "insert-link";
+ private const string ACTION_COMPOSE_AS_HTML = "compose-as-html";
+ private const string ACTION_SHOW_EXTENDED = "show-extended";
+ private const string ACTION_CLOSE = "close";
+ private const string ACTION_CLOSE_AND_SAVE = "close-and-save";
+ private const string ACTION_CLOSE_AND_DISCARD = "close-and-discard";
+ private const string ACTION_DETACH = "detach";
+ private const string ACTION_SEND = "send";
+ private const string ACTION_ADD_ATTACHMENT = "add-attachment";
+ private const string ACTION_ADD_ORIGINAL_ATTACHMENTS = "add-original-attachments";
+ private const string ACTION_SELECT_DICTIONARY = "select-dictionary";
+
+ private const string[] html_actions = {
+ ACTION_BOLD, ACTION_ITALIC, ACTION_UNDERLINE, ACTION_STRIKETHROUGH, ACTION_FONT_SIZE,
+ ACTION_FONT_FAMILY, ACTION_REMOVE_FORMAT, ACTION_COLOR, ACTION_JUSTIFY, ACTION_INSERT_LINK
+ };
+
+ private const ActionEntry[] action_entries = {
+ // Editor commands
+ {ACTION_UNDO, on_action },
+ {ACTION_REDO, on_action },
+ {ACTION_CUT, on_cut },
+ {ACTION_COPY, on_copy },
+ {ACTION_COPY_LINK, on_copy_link },
+ {ACTION_PASTE, on_paste },
+ {ACTION_PASTE_WITH_FORMATTING, on_paste_with_formatting },
+ {ACTION_BOLD, on_action, null, "false" },
+ {ACTION_ITALIC, on_action, null, "false" },
+ {ACTION_UNDERLINE, on_action, null, "false" },
+ {ACTION_STRIKETHROUGH, on_action, null, "false" },
+ {ACTION_FONT_SIZE, on_font_size, "s", "'medium'" },
+ {ACTION_FONT_FAMILY, on_font_family, "s", "'sans'" },
+ {ACTION_REMOVE_FORMAT, on_remove_format, null, "false" },
+ {ACTION_INDENT, on_indent },
+ {ACTION_OUTDENT, on_action },
+ {ACTION_JUSTIFY, on_justify, "s", "'left'" },
+ {ACTION_COLOR, on_select_color },
+ {ACTION_INSERT_LINK, on_insert_link },
+ // Composer commands
+ {ACTION_COMPOSE_AS_HTML, on_toggle_action, null, "true",
on_compose_as_html_toggled },
+ {ACTION_SHOW_EXTENDED, on_toggle_action, null, "false", on_show_extended_toggled
},
+ {ACTION_CLOSE, on_close
},
+ {ACTION_CLOSE_AND_SAVE, on_close_and_save
},
+ {ACTION_CLOSE_AND_DISCARD, on_close_and_discard
},
+ {ACTION_DETACH, on_detach
},
+ {ACTION_SEND, on_send
},
+ {ACTION_ADD_ATTACHMENT, on_add_attachment
},
+ {ACTION_ADD_ORIGINAL_ATTACHMENTS, on_pending_attachments
},
+ {ACTION_SELECT_DICTIONARY, on_select_dictionary
},
+ };
+
+ public static Gee.MultiMap<string, string> action_accelerators = new Gee.HashMultiMap<string, string>();
+ static construct {
+ action_accelerators.set(ACTION_UNDO, "<Ctrl>z");
+ action_accelerators.set(ACTION_REDO, "<Ctrl><Shift>z");
+ action_accelerators.set(ACTION_CUT, "<Ctrl>x");
+ action_accelerators.set(ACTION_COPY, "<Ctrl>x");
+ action_accelerators.set(ACTION_PASTE, "<Ctrl>v");
+ action_accelerators.set(ACTION_PASTE_WITH_FORMATTING, "<Ctrl><Shift>v");
+ action_accelerators.set(ACTION_INSERT_LINK, "<Ctrl>l");
+ action_accelerators.set(ACTION_INDENT, "<Ctrl>bracketright");
+ action_accelerators.set(ACTION_OUTDENT, "<Ctrl>bracketleft");
+ action_accelerators.set(ACTION_REMOVE_FORMAT, "<Ctrl>space");
+ action_accelerators.set(ACTION_BOLD, "<Ctrl>b");
+ action_accelerators.set(ACTION_ITALIC, "<Ctrl>i");
+ action_accelerators.set(ACTION_UNDERLINE, "<Ctrl>u");
+ action_accelerators.set(ACTION_STRIKETHROUGH, "<Ctrl>k");
+ action_accelerators.set(ACTION_CLOSE, "<Ctrl>w");
+ action_accelerators.set(ACTION_CLOSE, "Escape");
+ action_accelerators.set(ACTION_ADD_ATTACHMENT, "<Ctrl>t");
+ }
+
private const string DRAFT_SAVED_TEXT = _("Saved");
private const string DRAFT_SAVING_TEXT = _("Saving");
private const string DRAFT_ERROR_TEXT = _("Error saving");
private const string BACKSPACE_TEXT = _("Press Backspace to delete quote");
private const string DEFAULT_TITLE = _("New Message");
-
+
private const string URI_LIST_MIME_TYPE = "text/uri-list";
private const string FILE_URI_PREFIX = "file://";
private const string BODY_ID = "message-body";
@@ -142,116 +203,140 @@ public class ComposerWidget : Gtk.EventBox {
public Geary.RFC822.MailboxAddresses from { get; private set; }
public string to {
- get { return to_entry.get_text(); }
- set { to_entry.set_text(value); }
+ get { return this.to_entry.get_text(); }
+ set { this.to_entry.set_text(value); }
}
-
+
public string cc {
- get { return cc_entry.get_text(); }
- set { cc_entry.set_text(value); }
+ get { return this.cc_entry.get_text(); }
+ set { this.cc_entry.set_text(value); }
}
-
+
public string bcc {
- get { return bcc_entry.get_text(); }
- set { bcc_entry.set_text(value); }
+ get { return this.bcc_entry.get_text(); }
+ set { this.bcc_entry.set_text(value); }
}
public string reply_to {
- get { return reply_to_entry.get_text(); }
- set { reply_to_entry.set_text(value); }
+ get { return this.reply_to_entry.get_text(); }
+ set { this.reply_to_entry.set_text(value); }
}
-
+
public Gee.Set<Geary.RFC822.MessageID> in_reply_to = new Gee.HashSet<Geary.RFC822.MessageID>();
public string references { get; set; }
-
+
public string subject {
- get { return subject_entry.get_text(); }
- set { subject_entry.set_text(value); }
+ get { return this.subject_entry.get_text(); }
+ set { this.subject_entry.set_text(value); }
}
-
+
public string message {
owned get { return get_html(); }
set {
- body_html = value;
- editor.load_string(HTML_BODY, "text/html", "UTF8", "");
+ this.body_html = value;
+ this.editor.load_string(HTML_BODY, "text/html", "UTF8", "");
}
}
-
- public bool compose_as_html {
- get { return ((Gtk.ToggleAction) actions.get_action(ACTION_COMPOSE_AS_HTML)).active; }
- set { ((Gtk.ToggleAction) actions.get_action(ACTION_COMPOSE_AS_HTML)).active = value; }
- }
- public bool show_extended {
- get { return ((Gtk.ToggleAction) actions.get_action(ACTION_SHOW_EXTENDED)).active; }
- set { ((Gtk.ToggleAction) actions.get_action(ACTION_SHOW_EXTENDED)).active = value; }
- }
-
public ComposerState state { get; set; }
-
+
public ComposeType compose_type { get; private set; default = ComposeType.NEW_MESSAGE; }
-
+
public Gee.Set<Geary.EmailIdentifier> referred_ids = new Gee.HashSet<Geary.EmailIdentifier>();
-
+
public bool blank {
get {
- return to_entry.empty && cc_entry.empty && bcc_entry.empty && reply_to_entry.empty &&
- subject_entry.buffer.length == 0 && !editor.can_undo() && attachment_files.size == 0;
+ return this.to_entry.empty && this.cc_entry.empty && this.bcc_entry.empty &&
this.reply_to_entry.empty &&
+ this.subject_entry.buffer.length == 0 && !this.editor.can_undo() &&
this.attachment_files.size == 0;
}
}
-
- public ComposerHeaderbar header { get; private set; }
-
+
+ public ComposerHeaderbar header { get; private set; default = new ComposerHeaderbar(); }
+
public string draft_save_text { get; private set; }
-
+
public bool can_delete_quote { get; private set; default = false; }
-
+
public string toolbar_text { get; set; }
-
+
public string window_title { get; set; }
-
+
private ContactListStore? contact_list_store = null;
-
+
private string? body_html = null;
private Gee.Set<File> attachment_files = new Gee.HashSet<File>(Geary.Files.nullable_hash,
Geary.Files.nullable_equal);
-
- private Gtk.Builder builder;
+
+ [GtkChild]
+ private Gtk.Box composer_container;
+ [GtkChild]
private Gtk.Label from_label;
+ [GtkChild]
private Gtk.Label from_single;
- private Gtk.ComboBoxText from_multiple = new Gtk.ComboBoxText();
+ [GtkChild]
+ private Gtk.ComboBoxText from_multiple;
private Gee.ArrayList<FromAddressMap> from_list = new Gee.ArrayList<FromAddressMap>();
+ [GtkChild]
+ private Gtk.EventBox to_box;
+ [GtkChild]
+ private Gtk.Label to_label;
private EmailEntry to_entry;
+ [GtkChild]
+ private Gtk.EventBox cc_box;
+ [GtkChild]
+ private Gtk.Label cc_label;
private EmailEntry cc_entry;
+ [GtkChild]
+ private Gtk.EventBox bcc_box;
+ [GtkChild]
private Gtk.Label bcc_label;
private EmailEntry bcc_entry;
+ [GtkChild]
+ private Gtk.EventBox reply_to_box;
+ [GtkChild]
private Gtk.Label reply_to_label;
private EmailEntry reply_to_entry;
+ [GtkChild]
+ private Gtk.Label subject_label;
+ [GtkChild]
private Gtk.Entry subject_entry;
+ [GtkChild]
private Gtk.Label message_overlay_label;
+ [GtkChild]
private Gtk.Box attachments_box;
- private Gtk.Alignment hidden_on_attachment_drag_over;
- private Gtk.Alignment visible_on_attachment_drag_over;
+ [GtkChild]
+ private Gtk.Box hidden_on_attachment_drag_over;
+ [GtkChild]
+ private Gtk.Box visible_on_attachment_drag_over;
+ [GtkChild]
private Gtk.Widget hidden_on_attachment_drag_over_child;
+ [GtkChild]
private Gtk.Widget visible_on_attachment_drag_over_child;
-
- private Gtk.Menu menu = new Gtk.Menu();
- private Gtk.RadioMenuItem font_small;
- private Gtk.RadioMenuItem font_medium;
- private Gtk.RadioMenuItem font_large;
- private Gtk.RadioMenuItem font_sans;
- private Gtk.RadioMenuItem font_serif;
- private Gtk.RadioMenuItem font_monospace;
- private Gtk.MenuItem color_item;
- private Gtk.MenuItem html_item;
- private Gtk.MenuItem html_item2;
- private Gtk.MenuItem extended_item;
-
- private ComposerToolbar composer_toolbar;
- private Gtk.ActionGroup actions;
+ [GtkChild]
+ private Gtk.Widget recipients;
+ [GtkChild]
+ private Gtk.Box header_area;
+ [GtkChild]
+ private Gtk.Box composer_toolbar;
+ [GtkChild]
+ private Gtk.Button remove_format_button;
+ [GtkChild]
+ private Gtk.Button select_dictionary_button;
+ [GtkChild]
+ private Gtk.MenuButton menu_button;
+ [GtkChild]
+ private Gtk.Label info_label;
+ [GtkChild]
+ private Gtk.Box message_area;
+ [GtkChild]
+ private Gtk.ScrolledWindow editor_scrolled;
+
+ private Menu html_menu;
+ private Menu plain_menu;
+ private Menu context_menu_model;
+
private SpellCheckPopover? spell_check_popover = null;
private string? hover_url = null;
- private bool action_flag = false;
private bool is_attachment_overlay_visible = false;
private Gee.List<Geary.Attachment>? pending_attachments = null;
private Geary.RFC822.MailboxAddresses reply_to_addresses;
@@ -260,63 +345,53 @@ public class ComposerWidget : Gtk.EventBox {
private string forward_subject = "";
private bool top_posting = true;
private string? last_quote = null;
-
+
private Geary.App.DraftManager? draft_manager = null;
private Geary.EmailIdentifier? editing_draft_id = null;
private Geary.EmailFlags draft_flags = new Geary.EmailFlags.with(Geary.EmailFlags.DRAFT);
private uint draft_save_timeout_id = 0;
private bool is_closing = false;
-
- public WebKit.WebView editor;
+
+ public WebKit.WebView editor = new StylishWebView();
// We need to keep a reference to the edit-fixer in composer-window, so it doesn't get
// garbage-collected.
private WebViewEditFixer edit_fixer;
- public Gtk.UIManager ui;
private ComposerContainer container {
get { return (ComposerContainer) parent; }
}
-
+
public ComposerWidget(Geary.Account account, ComposeType compose_type,
Geary.Email? referred = null, string? quote = null, bool is_referred_draft = false) {
this.account = account;
this.compose_type = compose_type;
- if (compose_type == ComposeType.NEW_MESSAGE)
- state = ComposerState.NEW;
- else if (compose_type == ComposeType.FORWARD)
- state = ComposerState.INLINE;
+ if (this.compose_type == ComposeType.NEW_MESSAGE)
+ this.state = ComposerState.NEW;
+ else if (this.compose_type == ComposeType.FORWARD)
+ this.state = ComposerState.INLINE;
else
- state = ComposerState.INLINE_COMPACT;
-
- setup_drag_destination(this);
-
+ this.state = ComposerState.INLINE_COMPACT;
+
+ // Setup drag 'n drop
+ const Gtk.TargetEntry[] target_entries = { { URI_LIST_MIME_TYPE, 0, 0 } };
+ Gtk.drag_dest_set(this, Gtk.DestDefaults.MOTION | Gtk.DestDefaults.HIGHLIGHT,
+ target_entries, Gdk.DragAction.COPY);
+
add_events(Gdk.EventMask.KEY_PRESS_MASK | Gdk.EventMask.KEY_RELEASE_MASK);
- builder = GearyApplication.instance.create_builder("composer.glade");
-
- Gtk.Box box = builder.get_object("composer") as Gtk.Box;
- attachments_box = builder.get_object("attachments_box") as Gtk.Box;
- hidden_on_attachment_drag_over = (Gtk.Alignment)
builder.get_object("hidden_on_attachment_drag_over");
- hidden_on_attachment_drag_over_child = (Gtk.Widget)
builder.get_object("hidden_on_attachment_drag_over_child");
- visible_on_attachment_drag_over = (Gtk.Alignment)
builder.get_object("visible_on_attachment_drag_over");
- visible_on_attachment_drag_over_child = (Gtk.Widget)
builder.get_object("visible_on_attachment_drag_over_child");
- visible_on_attachment_drag_over.remove(visible_on_attachment_drag_over_child);
-
- Gtk.Widget recipients = builder.get_object("recipients") as Gtk.Widget;
+
+ this.visible_on_attachment_drag_over.remove(this.visible_on_attachment_drag_over_child);
bind_property("state", recipients, "visible", BindingFlags.SYNC_CREATE,
(binding, source_value, ref target_value) => {
- target_value = (state != ComposerState.INLINE_COMPACT);
+ target_value = (this.state != ComposerState.INLINE_COMPACT);
return true;
});
- string[] subject_elements = {"subject label", "subject"};
- foreach (string name in subject_elements) {
- Gtk.Widget widget = builder.get_object(name) as Gtk.Widget;
- bind_property("state", widget, "visible", BindingFlags.SYNC_CREATE,
- (binding, source_value, ref target_value) => {
- target_value = (state != ComposerState.INLINE && state != ComposerState.PANED);
- return true;
- });
- }
+ BindingTransformFunc bind_not_inline = (binding, source_value, ref target_value) => {
+ target_value = (this.state != ComposerState.INLINE && state != ComposerState.PANED);
+ return true;
+ };
+ bind_property("state", this.subject_label, "visible", BindingFlags.SYNC_CREATE, bind_not_inline);
+ bind_property("state", this.subject_entry, "visible", BindingFlags.SYNC_CREATE, bind_not_inline);
notify["state"].connect((s, p) => { update_from_field(); });
-
+
BindingTransformFunc set_toolbar_text = (binding, source_value, ref target_value) => {
if (draft_save_text == "" && can_delete_quote)
target_value = BACKSPACE_TEXT;
@@ -328,255 +403,98 @@ public class ComposerWidget : Gtk.EventBox {
set_toolbar_text);
bind_property("can-delete-quote", this, "toolbar-text", BindingFlags.SYNC_CREATE,
set_toolbar_text);
-
- from_label = (Gtk.Label) builder.get_object("from label");
- from_single = (Gtk.Label) builder.get_object("from_single");
- from_multiple = (Gtk.ComboBoxText) builder.get_object("from_multiple");
- to_entry = new EmailEntry(this);
- (builder.get_object("to") as Gtk.EventBox).add(to_entry);
- cc_entry = new EmailEntry(this);
- (builder.get_object("cc") as Gtk.EventBox).add(cc_entry);
- bcc_entry = new EmailEntry(this);
- (builder.get_object("bcc") as Gtk.EventBox).add(bcc_entry);
- reply_to_entry = new EmailEntry(this);
- (builder.get_object("reply to") as Gtk.EventBox).add(reply_to_entry);
-
- Gtk.Label to_label = (Gtk.Label) builder.get_object("to label");
- Gtk.Label cc_label = (Gtk.Label) builder.get_object("cc label");
- bcc_label = (Gtk.Label) builder.get_object("bcc label");
- reply_to_label = (Gtk.Label) builder.get_object("reply to label");
- to_label.set_mnemonic_widget(to_entry);
- cc_label.set_mnemonic_widget(cc_entry);
- bcc_label.set_mnemonic_widget(bcc_entry);
- reply_to_label.set_mnemonic_widget(reply_to_entry);
-
- to_entry.margin_top = cc_entry.margin_top = bcc_entry.margin_top = reply_to_entry.margin_top = 6;
-
+ this.to_entry = new EmailEntry(this);
+ this.to_box.add(to_entry);
+ this.cc_entry = new EmailEntry(this);
+ this.cc_box.add(cc_entry);
+ this.bcc_entry = new EmailEntry(this);
+ this.bcc_box.add(bcc_entry);
+ this.reply_to_entry = new EmailEntry(this);
+ this.reply_to_box.add(reply_to_entry);
+
+ this.to_label.set_mnemonic_widget(this.to_entry);
+ this.cc_label.set_mnemonic_widget(this.cc_entry);
+ this.bcc_label.set_mnemonic_widget(this.bcc_entry);
+ this.reply_to_label.set_mnemonic_widget(this.reply_to_entry);
+
+ this.to_entry.margin_top = this.cc_entry.margin_top = this.bcc_entry.margin_top =
this.reply_to_entry.margin_top = 6;
+
+ // Initialize menus
+ 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");
+
// TODO: It would be nicer to set the completions inside the EmailEntry constructor. But in
// testing, this can cause non-deterministic segfaults. Investigate why, and fix if possible.
set_entry_completions();
- subject_entry = builder.get_object("subject") as Gtk.Entry;
- subject_entry.bind_property("text", this, "window-title", BindingFlags.SYNC_CREATE,
+ this.subject_entry.bind_property("text", this, "window-title", BindingFlags.SYNC_CREATE,
(binding, source_value, ref target_value) => {
- target_value = Geary.String.is_empty_or_whitespace(subject_entry.text)
- ? DEFAULT_TITLE : subject_entry.text.strip();
+ target_value = Geary.String.is_empty_or_whitespace(this.subject_entry.text)
+ ? DEFAULT_TITLE : this.subject_entry.text.strip();
return true;
});
- Gtk.Alignment message_area = builder.get_object("message area") as Gtk.Alignment;
- actions = builder.get_object("compose actions") as Gtk.ActionGroup;
- // Can only happen after actions exits
- compose_as_html = GearyApplication.instance.config.compose_as_html;
-
- header = new ComposerHeaderbar(actions);
+
embed_header();
- bind_property("state", header, "state", BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL);
-
+ bind_property("state", this.header, "state", BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL);
+
// Listen to account signals to update from menu.
Geary.Engine.instance.account_available.connect(update_from_field);
Geary.Engine.instance.account_unavailable.connect(update_from_field);
// TODO: also listen for account updates to allow adding identities while writing an email
-
- Gtk.ScrolledWindow scroll = new Gtk.ScrolledWindow(null, null);
- scroll.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC);
-
- Gtk.Overlay message_overlay = new Gtk.Overlay();
- message_overlay.add(scroll);
- message_area.add(message_overlay);
-
- message_overlay_label = new Gtk.Label(null);
- message_overlay_label.ellipsize = Pango.EllipsizeMode.MIDDLE;
- message_overlay_label.halign = Gtk.Align.START;
- message_overlay_label.valign = Gtk.Align.END;
- message_overlay_label.realize.connect(on_message_overlay_label_realize);
- message_overlay.add_overlay(message_overlay_label);
-
- subject_entry.changed.connect(on_subject_changed);
- to_entry.changed.connect(validate_send_button);
- cc_entry.changed.connect(validate_send_button);
- bcc_entry.changed.connect(validate_send_button);
- reply_to_entry.changed.connect(validate_send_button);
-
- composer_toolbar = new ComposerToolbar(actions, menu);
- Gtk.Alignment toolbar_area = (Gtk.Alignment) builder.get_object("toolbar area");
- toolbar_area.add(composer_toolbar);
- bind_property("toolbar-text", composer_toolbar, "label-text", BindingFlags.SYNC_CREATE);
-
- actions.get_action(ACTION_UNDO).activate.connect(on_action);
- actions.get_action(ACTION_REDO).activate.connect(on_action);
-
- actions.get_action(ACTION_CUT).activate.connect(on_cut);
- actions.get_action(ACTION_COPY).activate.connect(on_copy);
- actions.get_action(ACTION_COPY_LINK).activate.connect(on_copy_link);
- actions.get_action(ACTION_PASTE).activate.connect(on_paste);
- actions.get_action(ACTION_PASTE_FORMAT).activate.connect(on_paste_with_formatting);
-
- actions.get_action(ACTION_BOLD).activate.connect(on_formatting_action);
- actions.get_action(ACTION_ITALIC).activate.connect(on_formatting_action);
- actions.get_action(ACTION_UNDERLINE).activate.connect(on_formatting_action);
- actions.get_action(ACTION_STRIKETHROUGH).activate.connect(on_formatting_action);
-
- actions.get_action(ACTION_REMOVE_FORMAT).activate.connect(on_remove_format);
- actions.get_action(ACTION_COMPOSE_AS_HTML).activate.connect(on_compose_as_html);
- actions.get_action(ACTION_SHOW_EXTENDED).activate.connect(on_show_extended);
-
- actions.get_action(ACTION_INDENT).activate.connect(on_indent);
- actions.get_action(ACTION_OUTDENT).activate.connect(on_action);
-
- actions.get_action(ACTION_JUSTIFY_LEFT).activate.connect(on_formatting_action);
- actions.get_action(ACTION_JUSTIFY_RIGHT).activate.connect(on_formatting_action);
- actions.get_action(ACTION_JUSTIFY_CENTER).activate.connect(on_formatting_action);
- actions.get_action(ACTION_JUSTIFY_FULL).activate.connect(on_formatting_action);
-
- actions.get_action(ACTION_COLOR).activate.connect(on_select_color);
- actions.get_action(ACTION_INSERT_LINK).activate.connect(on_insert_link);
-
- actions.get_action(ACTION_CLOSE).activate.connect(on_close);
- actions.get_action(ACTION_CLOSE_SAVE).activate.connect(on_close_and_save);
- actions.get_action(ACTION_CLOSE_DISCARD).activate.connect(on_close_and_discard);
-
- actions.get_action(ACTION_DETACH).activate.connect(on_detach);
- actions.get_action(ACTION_SEND).activate.connect(on_send);
- actions.get_action(ACTION_ADD_ATTACHMENT).activate.connect(on_add_attachment_button_clicked);
-
actions.get_action(ACTION_ADD_ORIGINAL_ATTACHMENTS).activate.connect(on_pending_attachments_button_clicked);
- actions.get_action(ACTION_SELECT_DICTIONARY).activate.connect(on_select_dictionary_clicked);
-
- ui = new Gtk.UIManager();
- ui.insert_action_group(actions, 0);
- GearyApplication.instance.load_ui_resource_for_manager(ui, "composer_accelerators.ui");
-
- add_extra_accelerators();
-
- from = new Geary.RFC822.MailboxAddresses.single(account.information.primary_mailbox);
-
- if (referred != null) {
- if (compose_type != ComposeType.NEW_MESSAGE) {
- add_recipients_and_ids(compose_type, referred);
- reply_subject = Geary.RFC822.Utils.create_subject_for_reply(referred);
- forward_subject = Geary.RFC822.Utils.create_subject_for_forward(referred);
- }
- last_quote = quote;
- switch (compose_type) {
- case ComposeType.NEW_MESSAGE:
- if (referred.from != null)
- from = referred.from;
- if (referred.to != null)
- to_entry.addresses = referred.to;
- if (referred.cc != null)
- cc_entry.addresses = referred.cc;
- if (referred.bcc != null)
- bcc_entry.addresses = referred.bcc;
- if (referred.in_reply_to != null)
- in_reply_to.add_all(referred.in_reply_to.list);
- if (referred.references != null)
- references = referred.references.to_rfc822_string();
- if (referred.subject != null)
- subject = referred.subject.value;
- try {
- Geary.RFC822.Message message = referred.get_message();
- if (message.has_html_body()) {
- body_html = message.get_html_body(null);
- } else {
- body_html = message.get_plain_body(true, null);
- }
- } catch (Error error) {
- debug("Error getting message body: %s", error.message);
- }
-
- if (is_referred_draft)
- editing_draft_id = referred.id;
-
- add_attachments(referred.attachments);
- break;
-
- case ComposeType.REPLY:
- case ComposeType.REPLY_ALL:
- subject = reply_subject;
- references = Geary.RFC822.Utils.reply_references(referred);
- body_html = "\n\n" + Geary.RFC822.Utils.quote_email_for_reply(referred, quote,
- Geary.RFC822.TextFormat.HTML);
- pending_attachments = referred.attachments;
- if (quote != null)
- top_posting = false;
- else
- can_delete_quote = true;
- break;
-
- case ComposeType.FORWARD:
- subject = forward_subject;
- body_html = "\n\n" + Geary.RFC822.Utils.quote_email_for_forward(referred, quote,
- Geary.RFC822.TextFormat.HTML);
- add_attachments(referred.attachments);
- pending_attachments = referred.attachments;
- break;
- }
- }
+
+ bind_property("toolbar-text", this.info_label, "label", BindingFlags.SYNC_CREATE);
+
+ this.from = new Geary.RFC822.MailboxAddresses.single(account.information.primary_mailbox);
+
+ if (referred != null)
+ fill_in_from_referred(referred, quote, is_referred_draft);
update_from_field();
// only add signature if the option is actually set and if this is not a draft
- if (account.information.use_email_signature && !is_referred_draft)
+ if (this.account.information.use_email_signature && !is_referred_draft)
add_signature_and_cursor();
else
set_cursor();
-
- editor = new StylishWebView();
- edit_fixer = new WebViewEditFixer(editor);
-
- editor.load_finished.connect(on_load_finished);
- editor.hovering_over_link.connect(on_hovering_over_link);
- editor.context_menu.connect(on_context_menu);
- editor.move_focus.connect(update_actions);
- editor.copy_clipboard.connect(update_actions);
- editor.cut_clipboard.connect(update_actions);
- editor.paste_clipboard.connect(update_actions);
- editor.undo.connect(update_actions);
- editor.redo.connect(update_actions);
- editor.selection_changed.connect(update_actions);
- editor.key_press_event.connect(on_editor_key_press);
- editor.user_changed_contents.connect(reset_draft_timer);
-
+
+ this.edit_fixer = new WebViewEditFixer(editor);
+
+ // Add actions once every element has been initialized and added
+ initialize_actions();
+
+ // Connect everything (can only happen after actions were added)
+ validate_send_button();
+ set_header_recipients();
+ this.to_entry.changed.connect(validate_send_button);
+ this.cc_entry.changed.connect(validate_send_button);
+ this.bcc_entry.changed.connect(validate_send_button);
+ this.reply_to_entry.changed.connect(validate_send_button);
+ this.editor.load_finished.connect(on_load_finished);
+ this.editor.hovering_over_link.connect(on_hovering_over_link);
+ this.editor.context_menu.connect(on_context_menu);
+ this.editor.move_focus.connect(update_actions);
+ this.editor.copy_clipboard.connect(update_actions);
+ this.editor.cut_clipboard.connect(update_actions);
+ this.editor.paste_clipboard.connect(update_actions);
+ this.editor.undo.connect(update_actions);
+ this.editor.redo.connect(update_actions);
+ this.editor.selection_changed.connect(update_actions);
+ this.editor.key_press_event.connect(on_editor_key_press);
+ this.editor.user_changed_contents.connect(reset_draft_timer);
+
// only do this after setting body_html
- editor.load_string(HTML_BODY, "text/html", "UTF8", "");
-
- editor.navigation_policy_decision_requested.connect(on_navigation_policy_decision_requested);
- editor.new_window_policy_decision_requested.connect(on_navigation_policy_decision_requested);
-
+ this.editor.load_string(HTML_BODY, "text/html", "UTF8", "");
+
+ this.editor.navigation_policy_decision_requested.connect(on_navigation_policy_decision_requested);
+ this.editor.new_window_policy_decision_requested.connect(on_navigation_policy_decision_requested);
+
GearyApplication.instance.config.settings.changed[Configuration.SPELL_CHECK_KEY].connect(
on_spell_check_changed);
-
- // Font family menu items.
- font_sans = new Gtk.RadioMenuItem(new SList<Gtk.RadioMenuItem>());
- font_sans.activate.connect(on_font_sans);
- font_sans.related_action = ui.get_action("ui/font_sans");
- font_serif = new Gtk.RadioMenuItem.from_widget(font_sans);
- font_serif.activate.connect(on_font_serif);
- font_serif.related_action = ui.get_action("ui/font_serif");
- font_monospace = new Gtk.RadioMenuItem.from_widget(font_sans);
- font_monospace.related_action = ui.get_action("ui/font_monospace");
- font_monospace.activate.connect(on_font_monospace);
-
- // Font size menu items.
- font_small = new Gtk.RadioMenuItem(new SList<Gtk.RadioMenuItem>());
- font_small.related_action = ui.get_action("ui/font_small");
- font_small.activate.connect(on_font_size_small);
- font_medium = new Gtk.RadioMenuItem.from_widget(font_small);
- font_medium.related_action = ui.get_action("ui/font_medium");
- font_medium.activate.connect(on_font_size_medium);
- font_large = new Gtk.RadioMenuItem.from_widget(font_small);
- font_large.related_action = ui.get_action("ui/font_large");
- font_large.activate.connect(on_font_size_large);
-
- color_item = new Gtk.MenuItem();
- color_item.related_action = ui.get_action("ui/color");
- html_item = new Gtk.CheckMenuItem();
- html_item.related_action = ui.get_action("ui/htmlcompose");
- extended_item = new Gtk.CheckMenuItem();
- extended_item.related_action = ui.get_action("ui/extended");
-
- html_item2 = new Gtk.CheckMenuItem();
- html_item2.related_action = ui.get_action("ui/htmlcompose");
-
- WebKit.WebSettings s = editor.settings;
+
+ WebKit.WebSettings s = this.editor.settings;
s.enable_spell_checking = GearyApplication.instance.config.spell_check;
s.spell_checking_languages = string.joinv(",",
GearyApplication.instance.config.spell_check_languages);
@@ -584,39 +502,36 @@ public class ComposerWidget : Gtk.EventBox {
s.enable_scripts = false;
s.enable_java_applet = false;
s.enable_plugins = false;
- editor.settings = s;
-
- scroll.add(editor);
- scroll.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC);
-
- add(box);
- validate_send_button();
+ this.editor.settings = s;
+
+ this.editor_scrolled.add(editor);
// Place the message area before the compose toolbar in the focus chain, so that
// the user can tab directly from the Subject: field to the message area.
+ // TODO: after bumping the min. GTK+ version to 3.16, we can/should do this in the UI file.
List<Gtk.Widget> chain = new List<Gtk.Widget>();
- chain.append(hidden_on_attachment_drag_over);
- chain.append(message_area);
- chain.append(composer_toolbar);
- chain.append(attachments_box);
- box.set_focus_chain(chain);
-
+ chain.append(this.hidden_on_attachment_drag_over);
+ chain.append(this.message_area);
+ chain.append(this.composer_toolbar);
+ chain.append(this.attachments_box);
+ this.composer_container.set_focus_chain(chain);
+
// If there's only one From option, open the drafts manager. If there's more than one,
// the drafts manager will be opened by on_from_changed().
- if (!from_multiple.visible)
+ if (!this.from_multiple.visible)
open_draft_manager_async.begin(null);
-
+
// Remind the conversation viewer of draft ids when it reloads
ConversationViewer conversation_viewer =
GearyApplication.instance.controller.main_window.conversation_viewer;
conversation_viewer.cleared.connect(() => {
- if (draft_manager != null)
- conversation_viewer.blacklist_by_id(draft_manager.current_draft_id);
+ if (this.draft_manager != null)
+ conversation_viewer.blacklist_by_id(this.draft_manager.current_draft_id);
});
-
+
destroy.connect(() => { close_draft_manager_async.begin(null); });
}
-
+
public ComposerWidget.from_mailto(Geary.Account account, string mailto) {
this(account, ComposeType.NEW_MESSAGE);
@@ -633,35 +548,46 @@ public class ComposerWidget : Gtk.EventBox {
Uri.unescape_string(param_parts[1]));
}
}
-
+
// Assemble the headers.
if (email.length > 0 && headers.contains("to"))
- to = "%s,%s".printf(email, Geary.Collection.get_first(headers.get("to")));
+ this.to = "%s,%s".printf(email, Geary.Collection.get_first(headers.get("to")));
else if (email.length > 0)
- to = email;
+ this.to = email;
else if (headers.contains("to"))
- to = Geary.Collection.get_first(headers.get("to"));
-
+ this.to = Geary.Collection.get_first(headers.get("to"));
+
if (headers.contains("cc"))
- cc = Geary.Collection.get_first(headers.get("cc"));
-
+ this.cc = Geary.Collection.get_first(headers.get("cc"));
+
if (headers.contains("bcc"))
- bcc = Geary.Collection.get_first(headers.get("bcc"));
-
+ this.bcc = Geary.Collection.get_first(headers.get("bcc"));
+
if (headers.contains("subject"))
- subject = Geary.Collection.get_first(headers.get("subject"));
-
+ this.subject = Geary.Collection.get_first(headers.get("subject"));
+
if (headers.contains("body"))
- body_html = Geary.HTML.preserve_whitespace(Geary.HTML.escape_markup(
+ this.body_html = Geary.HTML.preserve_whitespace(Geary.HTML.escape_markup(
Geary.Collection.get_first(headers.get("body"))));
-
+
foreach (string attachment in headers.get("attach"))
add_attachment(File.new_for_commandline_arg(attachment));
foreach (string attachment in headers.get("attachment"))
add_attachment(File.new_for_commandline_arg(attachment));
}
}
-
+
+ // Initializes all actions and adds them to the action group
+ private void initialize_actions() {
+ this.actions.add_action_entries(action_entries, this);
+
+ // for some reason, we can't use the same prefix.
+ insert_action_group("cmp", this.actions);
+ this.header.insert_action_group("cmh", this.actions);
+
+ update_actions();
+ }
+
public async void restore_draft_state_async(Geary.Account account) {
bool first_email = true;
@@ -691,58 +617,124 @@ public class ComposerWidget : Gtk.EventBox {
// composer's internal set of ids - we really shouldn't be
// messing around with the draft's recipients since the
// user may have already updated them.
- add_recipients_and_ids(compose_type, email, false);
+ add_recipients_and_ids(this.compose_type, email, false);
if (first_email) {
- reply_subject = Geary.RFC822.Utils.create_subject_for_reply(email);
- forward_subject = Geary.RFC822.Utils.create_subject_for_forward(email);
+ this.reply_subject = Geary.RFC822.Utils.create_subject_for_reply(email);
+ this.forward_subject = Geary.RFC822.Utils.create_subject_for_forward(email);
first_email = false;
}
}
if (first_email) // Either no referenced emails, or we don't have them. Treat as new.
return;
-
- if (cc == "")
- compose_type = ComposeType.REPLY;
+
+ if (this.cc == "")
+ this.compose_type = ComposeType.REPLY;
else
- compose_type = ComposeType.REPLY_ALL;
-
- to_entry.modified = cc_entry.modified = bcc_entry.modified = false;
+ this.compose_type = ComposeType.REPLY_ALL;
+
+ this.to_entry.modified = this.cc_entry.modified = this.bcc_entry.modified = false;
if (!to_entry.addresses.equal_to(reply_to_addresses))
- to_entry.modified = true;
+ this.to_entry.modified = true;
if (cc != "" && !cc_entry.addresses.equal_to(reply_cc_addresses))
- cc_entry.modified = true;
+ this.cc_entry.modified = true;
if (bcc != "")
- bcc_entry.modified = true;
-
+ this.bcc_entry.modified = true;
+
if (in_reply_to.size > 1) {
- state = ComposerState.PANED;
- } else if (compose_type == ComposeType.FORWARD || to_entry.modified || cc_entry.modified ||
- bcc_entry.modified) {
- state = ComposerState.INLINE;
+ this.state = ComposerState.PANED;
+ } else if (this.compose_type == ComposeType.FORWARD || this.to_entry.modified
+ || this.cc_entry.modified || this.bcc_entry.modified) {
+ this.state = ComposerState.INLINE;
} else {
- state = ComposerState.INLINE_COMPACT;
+ this.state = ComposerState.INLINE_COMPACT;
// Set recipients in header
- validate_send_button();
+ set_header_recipients();
}
}
-
- public void set_focus() {
- if (Geary.String.is_empty(to)) {
- to_entry.grab_focus();
- } else if (Geary.String.is_empty(subject)) {
- subject_entry.grab_focus();
- } else {
- editor.grab_focus();
+
+ // Copies the addresses (e.g. From/To/CC) and content from referred into this one
+ private void fill_in_from_referred(Geary.Email referred, string? quote, bool is_referred_draft) {
+ if (referred == null)
+ return;
+
+ if (this.compose_type != ComposeType.NEW_MESSAGE) {
+ add_recipients_and_ids(this.compose_type, referred);
+ this.reply_subject = Geary.RFC822.Utils.create_subject_for_reply(referred);
+ this.forward_subject = Geary.RFC822.Utils.create_subject_for_forward(referred);
+ }
+ this.last_quote = quote;
+ switch (this.compose_type) {
+ case ComposeType.NEW_MESSAGE:
+ if (referred.from != null)
+ this.from = referred.from;
+ if (referred.to != null)
+ this.to_entry.addresses = referred.to;
+ if (referred.cc != null)
+ this.cc_entry.addresses = referred.cc;
+ if (referred.bcc != null)
+ this.bcc_entry.addresses = referred.bcc;
+ if (referred.in_reply_to != null)
+ this.in_reply_to.add_all(referred.in_reply_to.list);
+ if (referred.references != null)
+ this.references = referred.references.to_rfc822_string();
+ if (referred.subject != null)
+ this.subject = referred.subject.value;
+ try {
+ Geary.RFC822.Message message = referred.get_message();
+ if (message.has_html_body()) {
+ this.body_html = message.get_html_body(null);
+ } else {
+ this.body_html = message.get_plain_body(true, null);
+ }
+ } catch (Error error) {
+ debug("Error getting message body: %s", error.message);
+ }
+
+ if (is_referred_draft)
+ this.editing_draft_id = referred.id;
+
+ add_attachments(referred.attachments);
+ break;
+
+ case ComposeType.REPLY:
+ case ComposeType.REPLY_ALL:
+ this.subject = reply_subject;
+ this.references = Geary.RFC822.Utils.reply_references(referred);
+ this.body_html = "\n\n" + Geary.RFC822.Utils.quote_email_for_reply(referred, quote,
+ Geary.RFC822.TextFormat.HTML);
+ this.pending_attachments = referred.attachments;
+ if (quote != null)
+ this.top_posting = false;
+ else
+ this.can_delete_quote = true;
+ break;
+
+ case ComposeType.FORWARD:
+ this.subject = forward_subject;
+ this.body_html = "\n\n" + Geary.RFC822.Utils.quote_email_for_forward(referred, quote,
+ Geary.RFC822.TextFormat.HTML);
+ add_attachments(referred.attachments);
+ this.pending_attachments = referred.attachments;
+ break;
}
}
-
+
+ public void set_focus() {
+ if (Geary.String.is_empty(to))
+ this.to_entry.grab_focus();
+ else if (Geary.String.is_empty(subject))
+ this.subject_entry.grab_focus();
+ else
+ this.editor.grab_focus();
+ }
+
private bool check_preferred_from_address(Gee.List<Geary.RFC822.MailboxAddress> account_addresses,
Geary.RFC822.MailboxAddresses? referred_addresses) {
if (referred_addresses != null) {
foreach (Geary.RFC822.MailboxAddress address in account_addresses) {
if (referred_addresses.get_all().contains(address)) {
- from = new Geary.RFC822.MailboxAddresses.single(address);
+ this.from = new Geary.RFC822.MailboxAddresses.single(address);
return true;
}
}
@@ -756,17 +748,17 @@ public class ComposerWidget : Gtk.EventBox {
else
realize.connect(on_load_finished_and_realized);
}
-
+
private void on_load_finished_and_realized() {
// This is safe to call even when this connection hasn't been made.
realize.disconnect(on_load_finished_and_realized);
- WebKit.DOM.Document document = editor.get_dom_document();
+ WebKit.DOM.Document document = this.editor.get_dom_document();
WebKit.DOM.HTMLElement? body = document.get_element_by_id(BODY_ID) as WebKit.DOM.HTMLElement;
assert(body != null);
- if (!Geary.String.is_empty(body_html)) {
+ if (!Geary.String.is_empty(this.body_html)) {
try {
- body.set_inner_html(body_html);
+ body.set_inner_html(this.body_html);
} catch (Error e) {
debug("Failed to load prefilled body: %s", e.message);
}
@@ -790,67 +782,53 @@ public class ComposerWidget : Gtk.EventBox {
}
protect_blockquote_styles();
-
+
set_focus(); // Focus in the GTK widget hierarchy
-
- // Ensure the editor is in correct mode re HTML and that the spell checker
- // is visible only when needed
- on_compose_as_html();
+
on_spell_check_changed();
- Util.DOM.bind_event(editor,"a", "click", (Callback) on_link_clicked, this);
+ Util.DOM.bind_event(this.editor, "a", "click", (Callback) on_link_clicked, this);
update_actions();
- on_show_extended();
-
+ this.actions.change_action_state(ACTION_SHOW_EXTENDED, false);
+ this.actions.change_action_state(ACTION_COMPOSE_AS_HTML,
+ GearyApplication.instance.config.compose_as_html);
+
if (can_delete_quote)
- editor.selection_changed.connect(() => { can_delete_quote = false; });
- }
-
- // Glade only allows one accelerator per-action. This method adds extra accelerators not defined
- // in the Glade file.
- private void add_extra_accelerators() {
- GtkUtil.add_accelerator(ui, actions, "Escape", ACTION_CLOSE);
- }
-
- private void setup_drag_destination(Gtk.Widget destination) {
- const Gtk.TargetEntry[] target_entries = { { URI_LIST_MIME_TYPE, 0, 0 } };
- Gtk.drag_dest_set(destination, Gtk.DestDefaults.MOTION | Gtk.DestDefaults.HIGHLIGHT,
- target_entries, Gdk.DragAction.COPY);
- destination.drag_data_received.connect(on_drag_data_received);
- destination.drag_drop.connect(on_drag_drop);
- destination.drag_motion.connect(on_drag_motion);
- destination.drag_leave.connect(on_drag_leave);
+ this.editor.selection_changed.connect(() => { this.can_delete_quote = false; });
}
-
+
private void show_attachment_overlay(bool visible) {
- if (is_attachment_overlay_visible == visible)
+ if (this.is_attachment_overlay_visible == visible)
return;
-
- is_attachment_overlay_visible = visible;
-
+
+ this.is_attachment_overlay_visible = visible;
+
// If we just make the widget invisible, it can still intercept drop signals. So we
// completely remove it instead.
if (visible) {
int height = hidden_on_attachment_drag_over.get_allocated_height();
- hidden_on_attachment_drag_over.remove(hidden_on_attachment_drag_over_child);
- visible_on_attachment_drag_over.add(visible_on_attachment_drag_over_child);
- visible_on_attachment_drag_over.set_size_request(-1, height);
+ this.hidden_on_attachment_drag_over.remove(this.hidden_on_attachment_drag_over_child);
+ this.visible_on_attachment_drag_over.add(this.visible_on_attachment_drag_over_child);
+ this.visible_on_attachment_drag_over.set_size_request(-1, height);
} else {
- hidden_on_attachment_drag_over.add(hidden_on_attachment_drag_over_child);
- visible_on_attachment_drag_over.remove(visible_on_attachment_drag_over_child);
- visible_on_attachment_drag_over.set_size_request(-1, -1);
+ this.hidden_on_attachment_drag_over.add(this.hidden_on_attachment_drag_over_child);
+ this.visible_on_attachment_drag_over.remove(this.visible_on_attachment_drag_over_child);
+ this.visible_on_attachment_drag_over.set_size_request(-1, -1);
}
}
-
+
+ [GtkCallback]
private bool on_drag_motion() {
show_attachment_overlay(true);
return false;
}
-
+
+ [GtkCallback]
private void on_drag_leave() {
show_attachment_overlay(false);
}
-
+
+ [GtkCallback]
private void on_drag_data_received(Gtk.Widget sender, Gdk.DragContext context, int x, int y,
Gtk.SelectionData selection_data, uint info, uint time_) {
@@ -870,7 +848,8 @@ public class ComposerWidget : Gtk.EventBox {
Gtk.drag_finish(context, dnd_success, false, time_);
}
-
+
+ [GtkCallback]
private bool on_drag_drop(Gtk.Widget sender, Gdk.DragContext context, int x, int y, uint time_) {
if (context.list_targets() == null)
return false;
@@ -889,108 +868,101 @@ public class ComposerWidget : Gtk.EventBox {
Gtk.drag_get_data(sender, context, target_type, time_);
return true;
}
-
+
public Geary.ComposedEmail get_composed_email(DateTime? date_override = null,
bool only_html = false) {
Geary.ComposedEmail email = new Geary.ComposedEmail(
date_override ?? new DateTime.now_local(), from);
- if (to_entry.addresses != null)
- email.to = to_entry.addresses;
-
- if (cc_entry.addresses != null)
- email.cc = cc_entry.addresses;
-
- if (bcc_entry.addresses != null)
- email.bcc = bcc_entry.addresses;
+ email.to = this.to_entry.addresses ?? email.to;
+ email.cc = this.cc_entry.addresses ?? email.cc;
+ email.bcc = this.bcc_entry.addresses ?? email.bcc;
+ email.reply_to = this.reply_to_entry.addresses ?? email.reply_to;
- if (reply_to_entry.addresses != null)
- email.reply_to = reply_to_entry.addresses;
-
- if ((compose_type == ComposeType.REPLY || compose_type == ComposeType.REPLY_ALL) &&
- !in_reply_to.is_empty)
+ if ((this.compose_type == ComposeType.REPLY || this.compose_type == ComposeType.REPLY_ALL) &&
+ !this.in_reply_to.is_empty)
email.in_reply_to =
new Geary.RFC822.MessageIDList.from_collection(in_reply_to).to_rfc822_string();
-
- if (!Geary.String.is_empty(references))
- email.references = references;
-
- if (!Geary.String.is_empty(subject))
- email.subject = subject;
-
- email.attachment_files.add_all(attachment_files);
-
- if (compose_as_html || only_html)
+
+ if (!Geary.String.is_empty(this.references))
+ email.references = this.references;
+
+ if (!Geary.String.is_empty(this.subject))
+ email.subject = this.subject;
+
+ email.attachment_files.add_all(this.attachment_files);
+
+ if (actions.get_action_state(ACTION_COMPOSE_AS_HTML).get_boolean() || only_html)
email.body_html = get_html();
if (!only_html)
email.body_text = get_text();
// User-Agent
email.mailer = GearyApplication.PRGNAME + "/" + GearyApplication.VERSION;
-
+
return email;
}
-
+
public override void show_all() {
base.show_all();
// Now, hide elements that we don't want shown
update_from_field();
- state = state; // Triggers visibilities
+ this.state = this.state; // Triggers visibilities
show_attachments();
}
-
+
public void change_compose_type(ComposeType new_type, Geary.Email? referred = null,
string? quote = null) {
- if (referred != null && quote != null && quote != last_quote) {
- last_quote = quote;
- WebKit.DOM.Document document = editor.get_dom_document();
+ if (referred != null && quote != null && quote != this.last_quote) {
+ this.last_quote = quote;
+ WebKit.DOM.Document document = this.editor.get_dom_document();
// Always use reply styling, since forward styling doesn't work for inline quotes
document.exec_command("insertHTML", false,
Geary.RFC822.Utils.quote_email_for_reply(referred, quote, Geary.RFC822.TextFormat.HTML));
-
+
if (!referred_ids.contains(referred.id)) {
add_recipients_and_ids(new_type, referred);
ensure_paned();
}
- } else if (new_type != compose_type) {
- bool recipients_modified = to_entry.modified || cc_entry.modified || bcc_entry.modified;
+ } else if (new_type != this.compose_type) {
+ bool recipients_modified = this.to_entry.modified || this.cc_entry.modified ||
this.bcc_entry.modified;
switch (new_type) {
case ComposeType.REPLY:
case ComposeType.REPLY_ALL:
- subject = reply_subject;
+ this.subject = this.reply_subject;
if (!recipients_modified) {
- to_entry.addresses = reply_to_addresses;
- cc_entry.addresses = (new_type == ComposeType.REPLY_ALL) ?
+ this.to_entry.addresses = reply_to_addresses;
+ this.cc_entry.addresses = (new_type == ComposeType.REPLY_ALL) ?
reply_cc_addresses : null;
- to_entry.modified = cc_entry.modified = false;
+ this.to_entry.modified = this.cc_entry.modified = false;
} else {
- to_entry.select_region(0, -1);
+ this.to_entry.select_region(0, -1);
}
break;
-
+
case ComposeType.FORWARD:
- if (state == ComposerState.INLINE_COMPACT)
- state = ComposerState.INLINE;
- subject = forward_subject;
+ if (this.state == ComposerState.INLINE_COMPACT)
+ this.state = ComposerState.INLINE;
+ this.subject = forward_subject;
if (!recipients_modified) {
- to = "";
- cc = "";
- to_entry.modified = cc_entry.modified = false;
+ this.to = "";
+ this.cc = "";
+ this.to_entry.modified = this.cc_entry.modified = false;
} else {
- to_entry.select_region(0, -1);
+ this.to_entry.select_region(0, -1);
}
break;
-
+
default:
assert_not_reached();
}
- compose_type = new_type;
+ this.compose_type = new_type;
}
-
- container.present();
+
+ this.container.present();
set_focus();
}
-
+
private void add_recipients_and_ids(ComposeType type, Geary.Email referred,
bool modify_headers = true) {
Gee.List<Geary.RFC822.MailboxAddress> sender_addresses =
@@ -999,7 +971,7 @@ public class ComposerWidget : Gtk.EventBox {
// Set the preferred from address. New messages should retain
// the account default and drafts should retain the draft's
// from addresses, so don't update them here
- if (compose_type != ComposeType.NEW_MESSAGE) {
+ if (this.compose_type != ComposeType.NEW_MESSAGE) {
if (!check_preferred_from_address(sender_addresses, referred.to)) {
if (!check_preferred_from_address(sender_addresses, referred.cc))
if (!check_preferred_from_address(sender_addresses, referred.bcc))
@@ -1020,19 +992,19 @@ public class ComposerWidget : Gtk.EventBox {
if (!modify_headers)
return;
- bool recipients_modified = to_entry.modified || cc_entry.modified || bcc_entry.modified;
+ bool recipients_modified = this.to_entry.modified || this.cc_entry.modified ||
this.bcc_entry.modified;
if (!recipients_modified) {
if (type == ComposeType.REPLY || type == ComposeType.REPLY_ALL)
- to_entry.addresses = Geary.RFC822.Utils.merge_addresses(to_entry.addresses,
+ this.to_entry.addresses = Geary.RFC822.Utils.merge_addresses(to_entry.addresses,
to_addresses);
if (type == ComposeType.REPLY_ALL)
- cc_entry.addresses = Geary.RFC822.Utils.remove_addresses(
- Geary.RFC822.Utils.merge_addresses(cc_entry.addresses, cc_addresses),
- to_entry.addresses);
+ this.cc_entry.addresses = Geary.RFC822.Utils.remove_addresses(
+ Geary.RFC822.Utils.merge_addresses(this.cc_entry.addresses, cc_addresses),
+ this.to_entry.addresses);
else
- cc_entry.addresses = Geary.RFC822.Utils.remove_addresses(cc_entry.addresses,
- to_entry.addresses);
- to_entry.modified = cc_entry.modified = false;
+ this.cc_entry.addresses = Geary.RFC822.Utils.remove_addresses(this.cc_entry.addresses,
+ this.to_entry.addresses);
+ this.to_entry.modified = this.cc_entry.modified = false;
}
in_reply_to.add(referred.message_id);
@@ -1044,7 +1016,8 @@ public class ComposerWidget : Gtk.EventBox {
// If use signature is enabled but no contents are on settings then we'll use ~/.signature, if any
// otherwise use whatever the user has input in settings dialog
- if (account.information.use_email_signature &&
Geary.String.is_empty_or_whitespace(account.information.email_signature)) {
+ if (this.account.information.use_email_signature
+ && Geary.String.is_empty_or_whitespace(this.account.information.email_signature)) {
File signature_file = File.new_for_path(Environment.get_home_dir()).get_child(".signature");
if (!signature_file.query_exists()) {
set_cursor();
@@ -1065,44 +1038,44 @@ public class ComposerWidget : Gtk.EventBox {
}
} else {
signature = account.information.email_signature;
- if(Geary.String.is_empty_or_whitespace(signature)) {
+ if (Geary.String.is_empty_or_whitespace(signature)) {
set_cursor();
return;
}
signature = Util.DOM.smart_escape(signature, true);
}
-
- if (body_html == null)
- body_html = CURSOR + "<br /><br />" + signature;
+
+ if (this.body_html == null)
+ this.body_html = CURSOR + "<br /><br />" + signature;
else if (top_posting)
- body_html = CURSOR + "<br /><br />" + signature + body_html;
+ this.body_html = CURSOR + "<br /><br />" + signature + this.body_html;
else
- body_html = body_html + CURSOR + "<br /><br />" + signature;
+ this.body_html = this.body_html + CURSOR + "<br /><br />" + signature;
}
-
+
private void set_cursor() {
if (top_posting)
- body_html = CURSOR + body_html;
+ this.body_html = CURSOR + this.body_html;
else
- body_html = body_html + CURSOR;
+ this.body_html = this.body_html + CURSOR;
}
-
+
private bool can_save() {
- return draft_manager != null
- && draft_manager.is_open
- && editor.can_undo()
- && account.information.save_drafts;
+ return this.draft_manager != null
+ && this.draft_manager.is_open
+ && this.editor.can_undo()
+ && this.account.information.save_drafts;
}
public CloseStatus should_close() {
- if (is_closing)
+ if (this.is_closing)
return CloseStatus.PENDING_CLOSE;
-
+
bool try_to_save = can_save();
-
- container.present();
+
+ this.container.present();
AlertDialog dialog;
-
+
if (try_to_save) {
dialog = new TernaryConfirmationDialog(container.top_window,
_("Do you want to discard this message?"), null, Stock._KEEP, Stock._DISCARD,
@@ -1111,7 +1084,7 @@ public class ComposerWidget : Gtk.EventBox {
dialog = new ConfirmationDialog(container.top_window,
_("Do you want to discard this message?"), null, Stock._DISCARD);
}
-
+
Gtk.ResponseType response = dialog.run();
if (response == Gtk.ResponseType.CANCEL || response == Gtk.ResponseType.DELETE_EVENT) {
return CloseStatus.CANCEL_CLOSE; // Cancel
@@ -1127,30 +1100,30 @@ public class ComposerWidget : Gtk.EventBox {
return CloseStatus.PENDING_CLOSE;
}
}
-
- private void on_close() {
+
+ private void on_close(SimpleAction action, Variant? param) {
if (should_close() == CloseStatus.DO_CLOSE)
- container.close_container();
+ this.container.close_container();
}
-
- private void on_close_and_save() {
+
+ private void on_close_and_save(SimpleAction action, Variant? param) {
if (can_save())
save_and_exit_async.begin();
else
- container.close_container();
+ this.container.close_container();
}
-
- private void on_close_and_discard() {
+
+ private void on_close_and_discard(SimpleAction action, Variant? param) {
discard_and_exit_async.begin();
}
-
+
private void on_detach() {
- if (state == ComposerState.DETACHED)
+ if (this.state == ComposerState.DETACHED)
return;
- Gtk.Widget? focus = container.top_window.get_focus();
- container.remove_composer();
+ Gtk.Widget? focus = this.container.top_window.get_focus();
+ this.container.remove_composer();
ComposerWindow window = new ComposerWindow(this);
- state = ComposerWidget.ComposerState.DETACHED;
+ this.state = ComposerWidget.ComposerState.DETACHED;
if (focus != null && focus.parent.visible) {
ComposerWindow focus_win = focus.get_toplevel() as ComposerWindow;
if (focus_win != null && focus_win == window)
@@ -1159,28 +1132,28 @@ public class ComposerWidget : Gtk.EventBox {
set_focus();
}
}
-
+
public void ensure_paned() {
- if (state == ComposerState.PANED || state == ComposerState.DETACHED)
+ if (this.state == ComposerState.PANED || this.state == ComposerState.DETACHED)
return;
- container.remove_composer();
+ this.container.remove_composer();
GearyApplication.instance.controller.main_window.conversation_viewer
.set_paned_composer(this);
- state = ComposerWidget.ComposerState.PANED;
+ this.state = ComposerWidget.ComposerState.PANED;
}
-
+
public void embed_header() {
- if (header.parent == null) {
- Gtk.Alignment header_area = (Gtk.Alignment) builder.get_object("header_area");
- header_area.add(header);
+ if (this.header.parent == null) {
+ this.header_area.add(this.header);
+ this.header.hexpand = true;
}
}
-
+
public void free_header() {
- if (header.parent != null)
- header.parent.remove(header);
+ if (this.header.parent != null)
+ this.header.parent.remove(this.header);
}
-
+
// compares all keys to all tokens according to user-supplied comparison function
// Returns true if found
private bool search_tokens(string[] keys, string[] tokens, CompareStringFunc cmp_func,
@@ -1205,7 +1178,7 @@ public class ComposerWidget : Gtk.EventBox {
private bool email_contains_attachment_keywords() {
// Filter out all content contained in block quotes
string filtered = @"$subject\n";
- filtered += Util.DOM.get_text_representation(editor.get_dom_document(), "blockquote");
+ filtered += Util.DOM.get_text_representation(this.editor.get_dom_document(), "blockquote");
Regex url_regex = null;
try {
@@ -1256,19 +1229,18 @@ public class ComposerWidget : Gtk.EventBox {
return false;
}
-
+
private bool should_send() {
bool has_subject = !Geary.String.is_empty(subject.strip());
bool has_body = !Geary.String.is_empty(get_html());
- bool has_attachment = attachment_files.size > 0;
- bool has_body_or_attachment = has_body || has_attachment;
-
+ bool has_attachment = this.attachment_files.size > 0;
+
string? confirmation = null;
- if (!has_subject && !has_body_or_attachment) {
+ if (!has_subject && !has_body && !has_attachment) {
confirmation = _("Send message with an empty subject and body?");
} else if (!has_subject) {
confirmation = _("Send message with an empty subject?");
- } else if (!has_body_or_attachment) {
+ } else if (!has_body && !has_attachment) {
confirmation = _("Send message with an empty body?");
} else if (!has_attachment && email_contains_attachment_keywords()) {
confirmation = _("Send message without an attachment?");
@@ -1281,23 +1253,23 @@ public class ComposerWidget : Gtk.EventBox {
}
return true;
}
-
+
// Sends the current message.
- private void on_send() {
+ private void on_send(SimpleAction action, Variant? param) {
if (should_send())
on_send_async.begin();
}
-
+
// Used internally by on_send()
private async void on_send_async() {
- container.vanish();
- is_closing = true;
+ this.container.vanish();
+ this.is_closing = true;
- Util.DOM.linkify_document(editor.get_dom_document());
+ Util.DOM.linkify_document(this.editor.get_dom_document());
// Perform send.
try {
- yield account.send_email_async(get_composed_email());
+ yield this.account.send_email_async(get_composed_email());
} catch (Error e) {
GLib.message("Error sending email: %s", e.message);
}
@@ -1310,41 +1282,41 @@ public class ComposerWidget : Gtk.EventBox {
// ignored
}
}
-
+
// Only close window after draft is deleted; this closes the drafts folder.
- container.close_container();
+ this.container.close_container();
}
-
+
private void on_draft_state_changed() {
- switch (draft_manager.draft_state) {
+ switch (this.draft_manager.draft_state) {
case Geary.App.DraftManager.DraftState.STORED:
- draft_save_text = DRAFT_SAVED_TEXT;
+ this.draft_save_text = DRAFT_SAVED_TEXT;
break;
-
+
case Geary.App.DraftManager.DraftState.STORING:
- draft_save_text = DRAFT_SAVING_TEXT;
+ this.draft_save_text = DRAFT_SAVING_TEXT;
break;
-
+
case Geary.App.DraftManager.DraftState.NOT_STORED:
- draft_save_text = "";
+ this.draft_save_text = "";
break;
-
+
case Geary.App.DraftManager.DraftState.ERROR:
- draft_save_text = DRAFT_ERROR_TEXT;
+ this.draft_save_text = DRAFT_ERROR_TEXT;
break;
-
+
default:
assert_not_reached();
}
}
-
+
private void on_draft_id_changed() {
GearyApplication.instance.controller.main_window.conversation_viewer.blacklist_by_id(
- draft_manager.current_draft_id);
+ this.draft_manager.current_draft_id);
}
-
+
private void on_draft_manager_fatal(Error err) {
- draft_save_text = DRAFT_ERROR_TEXT;
+ this.draft_save_text = DRAFT_ERROR_TEXT;
}
// Returns the drafts folder for the current From account.
@@ -1402,40 +1374,40 @@ public class ComposerWidget : Gtk.EventBox {
// Resets the draft save timeout.
private void reset_draft_timer() {
- draft_save_text = "";
+ this.draft_save_text = "";
cancel_draft_timer();
if (can_save())
draft_save_timeout_id = Timeout.add_seconds(DRAFT_TIMEOUT_SEC, on_save_draft_timeout);
}
-
+
// Cancels the draft save timeout
private void cancel_draft_timer() {
- if (draft_save_timeout_id == 0)
+ if (this.draft_save_timeout_id == 0)
return;
- Source.remove(draft_save_timeout_id);
- draft_save_timeout_id = 0;
+ Source.remove(this.draft_save_timeout_id);
+ this.draft_save_timeout_id = 0;
}
-
+
private bool on_save_draft_timeout() {
// this is not rescheduled by the event loop, so kill the timeout id
- draft_save_timeout_id = 0;
+ this.draft_save_timeout_id = 0;
save_draft();
return false;
}
-
+
// Note that drafts are NOT "linkified."
private Geary.Nonblocking.Semaphore? save_draft() {
// cancel timer in favor of just doing it now
cancel_draft_timer();
try {
- if (draft_manager != null) {
- return draft_manager.update(get_composed_email(null, true).to_rfc822_message(),
- draft_flags, null);
+ if (this.draft_manager != null) {
+ return this.draft_manager.update(get_composed_email(null, true).to_rfc822_message(),
+ this.draft_flags, null);
}
} catch (Error err) {
GLib.message("Unable to save draft: %s", err.message);
@@ -1443,30 +1415,30 @@ public class ComposerWidget : Gtk.EventBox {
return null;
}
-
+
private Geary.Nonblocking.Semaphore? discard_draft() {
// cancel timer in favor of this operation
cancel_draft_timer();
try {
- if (draft_manager != null)
- return draft_manager.discard();
+ if (this.draft_manager != null)
+ return this.draft_manager.discard();
} catch (Error err) {
GLib.message("Unable to discard draft: %s", err.message);
}
return null;
}
-
+
// Used while waiting for draft to save before closing widget.
private void make_gui_insensitive() {
- container.vanish();
+ this.container.vanish();
cancel_draft_timer();
}
-
+
private async void save_and_exit_async() {
make_gui_insensitive();
- is_closing = true;
+ this.is_closing = true;
save_draft();
try {
@@ -1474,17 +1446,17 @@ public class ComposerWidget : Gtk.EventBox {
} catch (Error err) {
// ignored
}
- if (draft_manager != null)
+ if (this.draft_manager != null)
GearyApplication.instance.controller.main_window.conversation_viewer
- .unblacklist_by_id(draft_manager.current_draft_id);
+ .unblacklist_by_id(this.draft_manager.current_draft_id);
- container.close_container();
+ this.container.close_container();
}
-
+
private async void discard_and_exit_async() {
make_gui_insensitive();
- is_closing = true;
-
+ this.is_closing = true;
+
discard_draft();
if (draft_manager != null)
draft_manager.discard_on_close = true;
@@ -1493,11 +1465,11 @@ public class ComposerWidget : Gtk.EventBox {
} catch (Error err) {
// ignored
}
-
- container.close_container();
+
+ this.container.close_container();
}
-
- private void on_add_attachment_button_clicked() {
+
+ private void on_add_attachment() {
AttachmentDialog dialog = null;
do {
// Transient parent of AttachmentDialog is this ComposerWindow
@@ -1506,31 +1478,31 @@ public class ComposerWidget : Gtk.EventBox {
// ComposerWindow, but as a GtkBin subclass a ComposerWindow can
// only contain one widget at a time;
// it already contains a widget of type GtkBox
- dialog = new AttachmentDialog(container.top_window);
+ dialog = new AttachmentDialog(this.container.top_window);
} while (!dialog.is_finished(add_attachment));
}
-
- private void on_pending_attachments_button_clicked() {
- add_attachments(pending_attachments, false);
+
+ private void on_pending_attachments() {
+ add_attachments(this.pending_attachments, false);
}
-
+
private void check_pending_attachments() {
- if (pending_attachments != null) {
- foreach (Geary.Attachment attachment in pending_attachments) {
- if (!attachment_files.contains(attachment.file)) {
- header.show_pending_attachments = true;
+ if (this.pending_attachments != null) {
+ foreach (Geary.Attachment attachment in this.pending_attachments) {
+ if (!this.attachment_files.contains(attachment.file)) {
+ this.header.show_pending_attachments = true;
return;
}
}
}
- header.show_pending_attachments = false;
+ this.header.show_pending_attachments = false;
}
-
+
private void attachment_failed(string msg) {
- ErrorDialog dialog = new ErrorDialog(container.top_window, _("Cannot add attachment"), msg);
+ ErrorDialog dialog = new ErrorDialog(this.container.top_window, _("Cannot add attachment"), msg);
dialog.run();
}
-
+
private bool add_attachment(File attachment_file, bool alert_errors = true) {
FileInfo attachment_file_info;
try {
@@ -1571,16 +1543,16 @@ public class ComposerWidget : Gtk.EventBox {
return false;
}
- if (!attachment_files.add(attachment_file)) {
+ if (!this.attachment_files.add(attachment_file)) {
if (alert_errors)
attachment_failed(_("\"%s\" already attached for
delivery.").printf(attachment_file.get_path()));
return false;
}
-
+
Gtk.Box box = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 6);
- attachments_box.pack_start(box);
-
+ this.attachments_box.pack_start(box);
+
/// In the composer, the filename followed by its filesize, i.e. "notes.txt (1.12KB)"
string label_text = _("%s (%s)").printf(attachment_file.get_basename(),
Files.get_filesize_as_string(attachment_file_info.get_size()));
@@ -1598,103 +1570,105 @@ public class ComposerWidget : Gtk.EventBox {
return true;
}
-
+
private void add_attachments(Gee.List<Geary.Attachment> attachments, bool alert_errors = true) {
foreach(Geary.Attachment attachment in attachments)
add_attachment(attachment.file, alert_errors);
}
-
+
private void remove_attachment(File file, Gtk.Box box) {
- if (!attachment_files.remove(file))
+ if (!this.attachment_files.remove(file))
return;
- foreach (weak Gtk.Widget child in attachments_box.get_children()) {
+ foreach (weak Gtk.Widget child in this.attachments_box.get_children()) {
if (child == box) {
- attachments_box.remove(box);
+ this.attachments_box.remove(box);
break;
}
}
show_attachments();
}
-
+
private void show_attachments() {
- if (attachment_files.size > 0 ) {
+ if (this.attachment_files.size > 0 )
attachments_box.show_all();
- } else {
+ else
attachments_box.hide();
- }
+
check_pending_attachments();
}
-
+
+ [GtkCallback]
private void on_subject_changed() {
reset_draft_timer();
}
-
+
private void validate_send_button() {
- header.send_enabled =
- to_entry.valid_or_empty && cc_entry.valid_or_empty && bcc_entry.valid_or_empty
- && (!to_entry.empty || !cc_entry.empty || !bcc_entry.empty);
- if (state == ComposerState.INLINE_COMPACT) {
- bool tocc = !to_entry.empty && !cc_entry.empty,
- ccbcc = !(to_entry.empty && cc_entry.empty) && !bcc_entry.empty;
- string label = to_entry.buffer.text + (tocc ? ", " : "")
- + cc_entry.buffer.text + (ccbcc ? ", " : "") + bcc_entry.buffer.text;
+ get_action(ACTION_SEND).set_enabled(this.to_entry.valid || this.cc_entry.valid ||
this.bcc_entry.valid);
+ }
+
+ private void set_header_recipients() {
+ if (this.state == ComposerState.INLINE_COMPACT) {
+ bool tocc = !this.to_entry.empty && !this.cc_entry.empty,
+ ccbcc = !(this.to_entry.empty && this.cc_entry.empty) && !this.bcc_entry.empty;
+ string label = this.to_entry.buffer.text + (tocc ? ", " : "")
+ + this.cc_entry.buffer.text + (ccbcc ? ", " : "") + this.bcc_entry.buffer.text;
StringBuilder tooltip = new StringBuilder();
if (to_entry.addresses != null)
- foreach(Geary.RFC822.MailboxAddress addr in to_entry.addresses)
+ foreach(Geary.RFC822.MailboxAddress addr in this.to_entry.addresses)
tooltip.append(_("To: ") + addr.get_full_address() + "\n");
if (cc_entry.addresses != null)
- foreach(Geary.RFC822.MailboxAddress addr in cc_entry.addresses)
+ foreach(Geary.RFC822.MailboxAddress addr in this.cc_entry.addresses)
tooltip.append(_("Cc: ") + addr.get_full_address() + "\n");
if (bcc_entry.addresses != null)
- foreach(Geary.RFC822.MailboxAddress addr in bcc_entry.addresses)
+ foreach(Geary.RFC822.MailboxAddress addr in this.bcc_entry.addresses)
tooltip.append(_("Bcc: ") + addr.get_full_address() + "\n");
if (reply_to_entry.addresses != null)
- foreach(Geary.RFC822.MailboxAddress addr in reply_to_entry.addresses)
+ foreach(Geary.RFC822.MailboxAddress addr in this.reply_to_entry.addresses)
tooltip.append(_("Reply-To: ") + addr.get_full_address() + "\n");
- header.set_recipients(label, tooltip.str.slice(0, -1)); // Remove trailing \n
+ this.header.set_recipients(label, tooltip.str.slice(0, -1)); // Remove trailing \n
}
-
+
reset_draft_timer();
}
-
- private void on_formatting_action(Gtk.Action action) {
- if (compose_as_html)
- on_action(action);
+
+ private void on_justify(SimpleAction action, Variant? param) {
+ this.editor.get_dom_document().exec_command("justify" + param.get_string(), false, "");
}
-
- private void on_action(Gtk.Action action) {
- if (action_flag)
+
+ private void on_action(SimpleAction action, Variant? param) {
+ if (!action.enabled)
return;
-
- action_flag = true; // prevents recursion
- editor.get_dom_document().exec_command(action.get_name(), false, "");
- action_flag = false;
+
+ // We need the unprefixed name to send as a command to the editor
+ string[] prefixed_action_name = action.get_name().split(".");
+ string action_name = prefixed_action_name[prefixed_action_name.length - 1];
+ this.editor.get_dom_document().exec_command(action_name, false, "");
}
-
- private void on_cut() {
- if (container.get_focus() == editor)
- editor.cut_clipboard();
- else if (container.get_focus() is Gtk.Editable)
- ((Gtk.Editable) container.get_focus()).cut_clipboard();
+
+ private void on_cut(SimpleAction action, Variant? param) {
+ if (this.container.get_focus() == this.editor)
+ this.editor.cut_clipboard();
+ else if (this.container.get_focus() is Gtk.Editable)
+ ((Gtk.Editable) this.container.get_focus()).cut_clipboard();
}
-
- private void on_copy() {
- if (container.get_focus() == editor)
- editor.copy_clipboard();
- else if (container.get_focus() is Gtk.Editable)
- ((Gtk.Editable) container.get_focus()).copy_clipboard();
+
+ private void on_copy(SimpleAction action, Variant? param) {
+ if (this.container.get_focus() == this.editor)
+ this.editor.copy_clipboard();
+ else if (this.container.get_focus() is Gtk.Editable)
+ ((Gtk.Editable) this.container.get_focus()).copy_clipboard();
}
-
- private void on_copy_link() {
+
+ private void on_copy_link(SimpleAction action, Variant? param) {
Gtk.Clipboard c = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD);
c.set_text(hover_url, -1);
c.store();
}
-
+
private WebKit.DOM.Node? get_left_text(WebKit.DOM.Node node, long offset) {
- WebKit.DOM.Document document = editor.get_dom_document();
+ WebKit.DOM.Document document = this.editor.get_dom_document();
string node_value = node.node_value;
// Offset is in unicode characters, but index is in bytes. We need to get the corresponding
@@ -1704,13 +1678,13 @@ public class ComposerWidget : Gtk.EventBox {
return offset > 0 ? document.create_text_node(node_value[0:index]) : null;
}
-
+
private void on_clipboard_text_received(Gtk.Clipboard clipboard, string? text) {
if (text == null)
return;
// Insert plain text from clipboard.
- WebKit.DOM.Document document = editor.get_dom_document();
+ WebKit.DOM.Document document = this.editor.get_dom_document();
document.exec_command("inserttext", false, text);
// The inserttext command will not scroll if needed, but we can't use the clipboard
@@ -1754,152 +1728,106 @@ public class ComposerWidget : Gtk.EventBox {
debug("Error scrolling pasted text into view: %s", err.message);
}
}
-
- private void on_paste() {
- if (container.get_focus() == editor)
+
+ private void on_paste(SimpleAction action, Variant? param) {
+ if (this.container.get_focus() == this.editor)
get_clipboard(Gdk.SELECTION_CLIPBOARD).request_text(on_clipboard_text_received);
- else if (container.get_focus() is Gtk.Editable)
- ((Gtk.Editable) container.get_focus()).paste_clipboard();
+ else if (this.container.get_focus() is Gtk.Editable)
+ ((Gtk.Editable) this.container.get_focus()).paste_clipboard();
}
-
- private void on_paste_with_formatting() {
- if (container.get_focus() == editor)
- editor.paste_clipboard();
+
+ private void on_paste_with_formatting(SimpleAction action, Variant? param) {
+ if (this.container.get_focus() == this.editor)
+ this.editor.paste_clipboard();
}
-
+
private void on_select_all() {
- editor.select_all();
+ this.editor.select_all();
}
-
- private void on_remove_format() {
- editor.get_dom_document().exec_command("removeformat", false, "");
- editor.get_dom_document().exec_command("removeparaformat", false, "");
- editor.get_dom_document().exec_command("unlink", false, "");
- editor.get_dom_document().exec_command("backcolor", false, "#ffffff");
- editor.get_dom_document().exec_command("forecolor", false, "#000000");
+
+ private void on_remove_format(SimpleAction action, Variant? param) {
+ this.editor.get_dom_document().exec_command("removeformat", false, "");
+ this.editor.get_dom_document().exec_command("removeparaformat", false, "");
+ this.editor.get_dom_document().exec_command("unlink", false, "");
+ this.editor.get_dom_document().exec_command("backcolor", false, "#ffffff");
+ this.editor.get_dom_document().exec_command("forecolor", false, "#000000");
}
-
- private void on_compose_as_html() {
- WebKit.DOM.DOMTokenList body_classes = editor.get_dom_document().body.get_class_list();
- if (!compose_as_html) {
- toggle_toolbar_buttons(false);
- build_plaintext_menu();
- try {
- body_classes.add("plain");
- } catch (Error error) {
- debug("Error setting composer style: %s", error.message);
- }
- } else {
- toggle_toolbar_buttons(true);
- build_html_menu();
- try {
+
+ // Use this for toggle actions, and use the change-state signal to respond to these state changes
+ private void on_toggle_action(SimpleAction? action, Variant? param) {
+ 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);
+
+ foreach (string html_action in html_actions)
+ get_action(html_action).set_enabled(compose_as_html);
+ this.remove_format_button.visible = compose_as_html;
+
+ this.menu_button.menu_model = (compose_as_html) ? this.html_menu : this.plain_menu;
+
+ // style editor accordingly
+ WebKit.DOM.DOMTokenList body_classes = this.editor.get_dom_document().body.get_class_list();
+ try {
+ if (compose_as_html)
body_classes.remove("plain");
- } catch (Error error) {
- debug("Error setting composer style: %s", error.message);
- }
+ else
+ body_classes.add("plain");
+ } catch (Error error) {
+ debug("Error setting composer style: %s", error.message);
}
+
+ // Remember preference
GearyApplication.instance.config.compose_as_html = compose_as_html;
}
- private void on_show_extended() {
- if (!show_extended) {
- bcc_label.visible = bcc_entry.visible = reply_to_label.visible = reply_to_entry.visible = false;
- } else {
- if (state == ComposerState.INLINE_COMPACT)
- state = ComposerState.INLINE;
- bcc_label.visible = bcc_entry.visible = reply_to_label.visible = reply_to_entry.visible = true;
- }
- }
-
- private void toggle_toolbar_buttons(bool show) {
- actions.get_action(ACTION_BOLD).visible =
- actions.get_action(ACTION_ITALIC).visible =
- actions.get_action(ACTION_UNDERLINE).visible =
- actions.get_action(ACTION_STRIKETHROUGH).visible =
- actions.get_action(ACTION_INSERT_LINK).visible =
- actions.get_action(ACTION_REMOVE_FORMAT).visible = show;
- }
-
- private void build_plaintext_menu() {
- GtkUtil.clear_menu(menu);
-
- menu.append(html_item2);
+ private void on_show_extended_toggled(SimpleAction? action, Variant? new_state) {
+ bool show_extended = new_state.get_boolean();
+ action.set_state(show_extended);
+ this.bcc_label.visible =
+ this.bcc_entry.visible =
+ this.reply_to_label.visible =
+ this.reply_to_entry.visible = show_extended;
- menu.append(new Gtk.SeparatorMenuItem());
- menu.append(extended_item);
- menu.show_all();
+ if (show_extended && this.state == ComposerState.INLINE_COMPACT)
+ this.state = ComposerState.INLINE;
}
-
- private void build_html_menu() {
- GtkUtil.clear_menu(menu);
-
- menu.append(font_sans);
- menu.append(font_serif);
- menu.append(font_monospace);
- menu.append(new Gtk.SeparatorMenuItem());
-
- menu.append(font_small);
- menu.append(font_medium);
- menu.append(font_large);
- menu.append(new Gtk.SeparatorMenuItem());
-
- menu.append(color_item);
- menu.append(new Gtk.SeparatorMenuItem());
-
- menu.append(html_item);
- menu.append(new Gtk.SeparatorMenuItem());
- menu.append(extended_item);
- menu.show_all(); // Call this or only menu items associated with actions will be displayed.
- }
-
- private void on_font_sans() {
- if (!action_flag)
- editor.get_dom_document().exec_command("fontname", false, "sans");
- }
-
- private void on_font_serif() {
- if (!action_flag)
- editor.get_dom_document().exec_command("fontname", false, "serif");
- }
-
- private void on_font_monospace() {
- if (!action_flag)
- editor.get_dom_document().exec_command("fontname", false, "monospace");
- }
-
- private void on_font_size_small() {
- if (!action_flag)
- editor.get_dom_document().exec_command("fontsize", false, "1");
- }
-
- private void on_font_size_medium() {
- if (!action_flag)
- editor.get_dom_document().exec_command("fontsize", false, "3");
- }
-
- private void on_font_size_large() {
- if (!action_flag)
- editor.get_dom_document().exec_command("fontsize", false, "7");
+ private void on_font_family(SimpleAction action, Variant? param) {
+ this.editor.get_dom_document().exec_command("fontname", false, param.get_string());
+ action.set_state(param.get_string());
+ }
+
+ private void on_font_size(SimpleAction action, Variant? param) {
+ string size = "";
+ if (param.get_string() == "small")
+ size = "1";
+ else if (param.get_string() == "medium")
+ size = "3";
+ else // Large
+ size = "7";
+
+ this.editor.get_dom_document().exec_command("fontsize", false, size);
+ action.set_state(param.get_string());
}
-
+
private void on_select_color() {
- if (compose_as_html) {
- Gtk.ColorChooserDialog dialog = new Gtk.ColorChooserDialog(_("Select Color"),
- container.top_window);
- if (dialog.run() == Gtk.ResponseType.OK)
- editor.get_dom_document().exec_command("forecolor", false, dialog.get_rgba().to_string());
-
- dialog.destroy();
- }
+ Gtk.ColorChooserDialog dialog = new Gtk.ColorChooserDialog(_("Select Color"),
+ this.container.top_window);
+ if (dialog.run() == Gtk.ResponseType.OK)
+ this.editor.get_dom_document().exec_command("forecolor", false, dialog.get_rgba().to_string());
+
+ dialog.destroy();
}
-
- private void on_indent(Gtk.Action action) {
- on_action(action);
-
+
+ private void on_indent(SimpleAction action, Variant? param) {
+ on_action(action, param);
+
// Undo styling of blockquotes
try {
- WebKit.DOM.NodeList node_list = editor.get_dom_document().query_selector_all(
+ WebKit.DOM.NodeList node_list = this.editor.get_dom_document().query_selector_all(
"blockquote[style=\"margin: 0 0 0 40px; border: none; padding: 0px;\"]");
for (int i = 0; i < node_list.length; ++i) {
WebKit.DOM.Element element = (WebKit.DOM.Element) node_list.item(i);
@@ -1910,12 +1838,12 @@ public class ComposerWidget : Gtk.EventBox {
debug("Error removing blockquote style: %s", error.message);
}
}
-
+
private void protect_blockquote_styles() {
// We will search for an remove a particular styling when we quote text. If that style
// exists in the quoted text, we alter it slightly so we don't mess with it later.
try {
- WebKit.DOM.NodeList node_list = editor.get_dom_document().query_selector_all(
+ WebKit.DOM.NodeList node_list = this.editor.get_dom_document().query_selector_all(
"blockquote[style=\"margin: 0 0 0 40px; border: none; padding: 0px;\"]");
for (int i = 0; i < node_list.length; ++i) {
((WebKit.DOM.Element) node_list.item(i)).set_attribute("style",
@@ -1925,12 +1853,11 @@ public class ComposerWidget : Gtk.EventBox {
debug("Error protecting blockquotes: %s", error.message);
}
}
-
- private void on_insert_link() {
- if (compose_as_html)
- link_dialog("http://");
+
+ private void on_insert_link(SimpleAction action, Variant? param) {
+ link_dialog("http://");
}
-
+
private static void on_link_clicked(WebKit.DOM.Element element, WebKit.DOM.Event event,
ComposerWidget composer) {
try {
@@ -1940,13 +1867,13 @@ public class ComposerWidget : Gtk.EventBox {
debug("Error selecting link: %s", e.message);
}
}
-
+
private void link_dialog(string link) {
Gtk.Dialog dialog = new Gtk.Dialog();
bool existing_link = false;
// Save information needed to re-establish selection
- WebKit.DOM.DOMSelection selection = editor.get_dom_document().get_default_view().
+ WebKit.DOM.DOMSelection selection = this.editor.get_dom_document().get_default_view().
get_selection();
WebKit.DOM.Node anchor_node = selection.anchor_node;
long anchor_offset = selection.anchor_offset;
@@ -1994,43 +1921,43 @@ public class ComposerWidget : Gtk.EventBox {
}
if (response == Gtk.ResponseType.OK)
- editor.get_dom_document().exec_command("createLink", false, entry.text);
+ this.editor.get_dom_document().exec_command("createLink", false, entry.text);
else if (response == Gtk.ResponseType.REJECT)
- editor.get_dom_document().exec_command("unlink", false, "");
+ this.editor.get_dom_document().exec_command("unlink", false, "");
dialog.destroy();
// Re-bind to anchor links. This must be done every time link have changed.
- Util.DOM.bind_event(editor,"a", "click", (Callback) on_link_clicked, this);
+ Util.DOM.bind_event(this.editor,"a", "click", (Callback) on_link_clicked, this);
}
-
+
private string get_html() {
- return ((WebKit.DOM.HTMLElement) editor.get_dom_document().get_element_by_id(BODY_ID))
+ return ((WebKit.DOM.HTMLElement) this.editor.get_dom_document().get_element_by_id(BODY_ID))
.get_inner_html();
}
-
+
private string get_text() {
- return Util.DOM.html_to_flowed_text((WebKit.DOM.HTMLElement) editor.get_dom_document()
+ return Util.DOM.html_to_flowed_text((WebKit.DOM.HTMLElement) this.editor.get_dom_document()
.get_element_by_id(BODY_ID));
}
-
+
private bool on_navigation_policy_decision_requested(WebKit.WebFrame frame,
WebKit.NetworkRequest request, WebKit.WebNavigationAction navigation_action,
WebKit.WebPolicyDecision policy_decision) {
policy_decision.ignore();
- if (compose_as_html)
+ if (this.actions.get_action_state(ACTION_COMPOSE_AS_HTML).get_boolean())
link_dialog(request.uri);
return true;
}
-
+
private void on_hovering_over_link(string? title, string? url) {
- if (compose_as_html) {
+ if (this.actions.get_action_state(ACTION_COMPOSE_AS_HTML).get_boolean()) {
message_overlay_label.label = url;
hover_url = url;
update_actions();
}
}
-
+
private void update_message_overlay_label_style() {
Gdk.RGBA window_background = container.top_window.get_style_context()
.get_background_color(Gtk.StateFlags.NORMAL);
@@ -2046,20 +1973,21 @@ public class ComposerWidget : Gtk.EventBox {
message_overlay_label.get_style_context().changed.connect(
on_message_overlay_label_style_changed);
}
-
+
+ [GtkCallback]
private void on_message_overlay_label_realize() {
update_message_overlay_label_style();
}
-
+
private void on_message_overlay_label_style_changed() {
update_message_overlay_label_style();
}
-
+
private void on_spell_check_changed() {
- editor.settings.enable_spell_checking = GearyApplication.instance.config.spell_check;
- actions.get_action(ACTION_SELECT_DICTIONARY).visible = editor.settings.enable_spell_checking;
+ this.editor.settings.enable_spell_checking = GearyApplication.instance.config.spell_check;
+ get_action(ACTION_SELECT_DICTIONARY).set_enabled(this.editor.settings.enable_spell_checking);
}
-
+
// This overrides the keypress handling for the *widget*; the WebView editor's keypress overrides
// are handled by on_editor_key_press
public override bool key_press_event(Gdk.EventKey event) {
@@ -2071,9 +1999,7 @@ public class ComposerWidget : Gtk.EventBox {
// always trap Ctrl+Enter/Ctrl+KeypadEnter to prevent the Enter leaking through
// to the controls, but only send if send is available
if ((event.state & Gdk.ModifierType.CONTROL_MASK) != 0) {
- if (header.send_enabled)
- on_send();
-
+ this.actions.activate_action(ACTION_SEND, null);
return true;
}
break;
@@ -2081,13 +2007,13 @@ public class ComposerWidget : Gtk.EventBox {
return base.key_press_event(event);
}
-
+
private bool on_context_menu(Gtk.Widget default_menu, WebKit.HitTestResult hit_test_result,
bool keyboard_triggered) {
Gtk.Menu context_menu = (Gtk.Menu) default_menu;
Gtk.MenuItem? ignore_spelling = null, learn_spelling = null;
bool suggestions = false;
-
+
GLib.List<weak Gtk.Widget> children = context_menu.get_children();
foreach (weak Gtk.Widget child in children) {
Gtk.MenuItem item = (Gtk.MenuItem) child;
@@ -2097,15 +2023,17 @@ public class ComposerWidget : Gtk.EventBox {
suggestions = true;
continue;
}
-
+
if (action == WebKit.ContextMenuAction.IGNORE_SPELLING)
ignore_spelling = item;
else if (action == WebKit.ContextMenuAction.LEARN_SPELLING)
learn_spelling = item;
}
- context_menu.remove(child);
}
-
+
+ context_menu.insert_action_group("cme", this.actions);
+ context_menu.bind_model(this.context_menu_model, "cme", true);
+
if (suggestions)
context_menu.append(new Gtk.SeparatorMenuItem());
if (ignore_spelling != null)
@@ -2114,69 +2042,26 @@ public class ComposerWidget : Gtk.EventBox {
context_menu.append(learn_spelling);
if (ignore_spelling != null || learn_spelling != null)
context_menu.append(new Gtk.SeparatorMenuItem());
-
- // Undo
- Gtk.MenuItem undo = new Gtk.ImageMenuItem();
- undo.related_action = actions.get_action(ACTION_UNDO);
- context_menu.append(undo);
-
- // Redo
- Gtk.MenuItem redo = new Gtk.ImageMenuItem();
- redo.related_action = actions.get_action(ACTION_REDO);
- context_menu.append(redo);
-
- context_menu.append(new Gtk.SeparatorMenuItem());
-
- // Cut
- Gtk.MenuItem cut = new Gtk.ImageMenuItem();
- cut.related_action = actions.get_action(ACTION_CUT);
- context_menu.append(cut);
-
- // Copy
- Gtk.MenuItem copy = new Gtk.ImageMenuItem();
- copy.related_action = actions.get_action(ACTION_COPY);
- context_menu.append(copy);
-
- // Copy link.
- Gtk.MenuItem copy_link = new Gtk.ImageMenuItem();
- copy_link.related_action = actions.get_action(ACTION_COPY_LINK);
- context_menu.append(copy_link);
-
- // Paste
- Gtk.MenuItem paste = new Gtk.ImageMenuItem();
- paste.related_action = actions.get_action(ACTION_PASTE);
- context_menu.append(paste);
-
- // Paste with formatting
- if (compose_as_html) {
- Gtk.MenuItem paste_format = new Gtk.ImageMenuItem();
- paste_format.related_action = actions.get_action(ACTION_PASTE_FORMAT);
- context_menu.append(paste_format);
- }
-
- context_menu.append(new Gtk.SeparatorMenuItem());
-
+
// Select all.
Gtk.MenuItem select_all_item = new Gtk.MenuItem.with_mnemonic(Stock.SELECT__ALL);
select_all_item.activate.connect(on_select_all);
context_menu.append(select_all_item);
-
+
context_menu.show_all();
-
update_actions();
-
return false;
}
- private void on_select_dictionary_clicked() {
- if (spell_check_popover == null) {
- spell_check_popover = new SpellCheckPopover(composer_toolbar.select_dictionary_button);
- spell_check_popover.selection_changed.connect((active_langs) => {
- editor.settings.spell_checking_languages = string.joinv(",", active_langs);
+ private void on_select_dictionary(SimpleAction action, Variant? param) {
+ if (this.spell_check_popover == null) {
+ this.spell_check_popover = new SpellCheckPopover(select_dictionary_button);
+ this.spell_check_popover.selection_changed.connect((active_langs) => {
+ this.editor.settings.spell_checking_languages = string.joinv(",", active_langs);
GearyApplication.instance.config.spell_check_languages = active_langs;
});
}
- spell_check_popover.toggle();
+ this.spell_check_popover.toggle();
}
private bool on_editor_key_press(Gdk.EventKey event) {
@@ -2203,20 +2088,20 @@ public class ComposerWidget : Gtk.EventBox {
return false;
}
- if (can_delete_quote) {
- can_delete_quote = false;
+ if (this.can_delete_quote) {
+ this.can_delete_quote = false;
if (event.keyval == Gdk.Key.BackSpace) {
- body_html = null;
- if (account.information.use_email_signature)
+ this.body_html = null;
+ if (this.account.information.use_email_signature)
add_signature_and_cursor();
else
set_cursor();
- editor.load_string(HTML_BODY, "text/html", "UTF8", "");
+ this.editor.load_string(HTML_BODY, "text/html", "UTF8", "");
return true;
}
}
- WebKit.DOM.Document document = editor.get_dom_document();
+ WebKit.DOM.Document document = this.editor.get_dom_document();
if (event.keyval == Gdk.Key.Tab) {
document.exec_command("inserthtml", false,
"<span style='white-space: pre-wrap'>\t</span>");
@@ -2242,112 +2127,116 @@ public class ComposerWidget : Gtk.EventBox {
return false;
}
-
+
+ /**
+ * Helper method, returns a composer action.
+ * @param action_name - The name of the action (as found in action_entries)
+ */
+ public SimpleAction? get_action(string action_name) {
+ return this.actions.lookup_action(action_name) as SimpleAction;
+ }
+
+ /**
+ * Updates the states of the composer's actions and whether they should be enabled.
+ */
private void update_actions() {
- // Undo/redo.
- actions.get_action(ACTION_UNDO).sensitive = editor.can_undo();
- actions.get_action(ACTION_REDO).sensitive = editor.can_redo();
-
- // Clipboard.
- actions.get_action(ACTION_CUT).sensitive = editor.can_cut_clipboard();
- actions.get_action(ACTION_COPY).sensitive = editor.can_copy_clipboard();
- actions.get_action(ACTION_COPY_LINK).sensitive = hover_url != null;
- actions.get_action(ACTION_PASTE).sensitive = editor.can_paste_clipboard();
- actions.get_action(ACTION_PASTE_FORMAT).sensitive = editor.can_paste_clipboard() && compose_as_html;
-
- // Style toggle buttons.
- WebKit.DOM.DOMWindow window = editor.get_dom_document().get_default_view();
+ // Basic editor commands
+ get_action(ACTION_UNDO).set_enabled(this.editor.can_undo());
+ get_action(ACTION_REDO).set_enabled(this.editor.can_redo());
+ get_action(ACTION_CUT).set_enabled(this.editor.can_cut_clipboard());
+ get_action(ACTION_COPY).set_enabled(this.editor.can_copy_clipboard());
+ get_action(ACTION_COPY_LINK).set_enabled(hover_url != null);
+ get_action(ACTION_PASTE).set_enabled(this.editor.can_paste_clipboard());
+ get_action(ACTION_PASTE_WITH_FORMATTING).set_enabled(this.editor.can_paste_clipboard()
+ && get_action(ACTION_COMPOSE_AS_HTML).state.get_boolean());
+
+ // Style formatting actions.
+ WebKit.DOM.DOMWindow window = this.editor.get_dom_document().get_default_view();
WebKit.DOM.DOMSelection? selection = window.get_selection();
if (selection == null)
return;
-
- actions.get_action(ACTION_REMOVE_FORMAT).sensitive = !selection.is_collapsed;
-
+
+ get_action(ACTION_REMOVE_FORMAT).set_enabled(!selection.is_collapsed
+ && get_action(ACTION_COMPOSE_AS_HTML).state.get_boolean());
+
WebKit.DOM.Element? active = selection.focus_node as WebKit.DOM.Element;
if (active == null && selection.focus_node != null)
active = selection.focus_node.get_parent_element();
-
- if (active != null && !action_flag) {
- action_flag = true;
-
+
+ if (active != null) {
WebKit.DOM.CSSStyleDeclaration styles = window.get_computed_style(active, "");
-
- ((Gtk.ToggleAction) actions.get_action(ACTION_BOLD)).active =
- styles.get_property_value("font-weight") == "bold";
-
- ((Gtk.ToggleAction) actions.get_action(ACTION_ITALIC)).active =
- styles.get_property_value("font-style") == "italic";
-
- ((Gtk.ToggleAction) actions.get_action(ACTION_UNDERLINE)).active =
- styles.get_property_value("text-decoration") == "underline";
-
- ((Gtk.ToggleAction) actions.get_action(ACTION_STRIKETHROUGH)).active =
- styles.get_property_value("text-decoration") == "line-through";
-
+
+ this.actions.change_action_state(ACTION_BOLD,
+ styles.get_property_value("font-weight") == "bold");
+ this.actions.change_action_state(ACTION_ITALIC,
+ styles.get_property_value("font-style") == "italic");
+ this.actions.change_action_state(ACTION_UNDERLINE,
+ styles.get_property_value("text-decoration") == "underline");
+ this.actions.change_action_state(ACTION_STRIKETHROUGH,
+ styles.get_property_value("text-decoration") == "line-through");
+
// Font family.
string font_name = styles.get_property_value("font-family").down();
- if (font_name.contains("sans-serif") ||
+ if (font_name.contains("sans") ||
font_name.contains("arial") ||
font_name.contains("trebuchet") ||
font_name.contains("helvetica"))
- font_sans.activate();
+ this.actions.change_action_state(ACTION_FONT_FAMILY, "sans");
else if (font_name.contains("serif") ||
font_name.contains("georgia") ||
font_name.contains("times"))
- font_serif.activate();
+ this.actions.change_action_state(ACTION_FONT_FAMILY, "serif");
else if (font_name.contains("monospace") ||
font_name.contains("courier") ||
font_name.contains("console"))
- font_monospace.activate();
-
+ this.actions.change_action_state(ACTION_FONT_FAMILY, "monospace");
+
// Font size.
int font_size;
styles.get_property_value("font-size").scanf("%dpx", out font_size);
if (font_size < 11)
- font_small.activate();
+ this.actions.change_action_state(ACTION_FONT_SIZE, "small");
else if (font_size > 20)
- font_large.activate();
+ this.actions.change_action_state(ACTION_FONT_SIZE, "large");
else
- font_medium.activate();
-
- action_flag = false;
+ this.actions.change_action_state(ACTION_FONT_SIZE, "medium");
}
}
-
+
private bool add_account_emails_to_from_list(Geary.Account account, bool set_active = false) {
Geary.RFC822.MailboxAddresses primary_address = new Geary.RFC822.MailboxAddresses.single(
account.information.primary_mailbox);
- from_multiple.append_text(primary_address.to_rfc822_string());
- from_list.add(new FromAddressMap(account, primary_address));
+ this.from_multiple.append_text(primary_address.to_rfc822_string());
+ this.from_list.add(new FromAddressMap(account, primary_address));
if (!set_active && from.equal_to(primary_address)) {
- from_multiple.set_active(from_list.size - 1);
+ this.from_multiple.set_active(this.from_list.size - 1);
set_active = true;
}
-
- if (account.information.alternate_mailboxes != null) {
- foreach (Geary.RFC822.MailboxAddress alternate_mailbox in
account.information.alternate_mailboxes) {
+
+ if (this.account.information.alternate_mailboxes != null) {
+ foreach (Geary.RFC822.MailboxAddress alternate_mailbox in
this.account.information.alternate_mailboxes) {
Geary.RFC822.MailboxAddresses addresses = new Geary.RFC822.MailboxAddresses.single(
alternate_mailbox);
// Displayed in the From dropdown to indicate an "alternate email address"
// for an account. The first printf argument will be the alternate email
// address, and the second will be the account's primary email address.
- string display = _("%1$s via %2$s").printf(addresses.to_rfc822_string(),
account.information.display_name);
- from_multiple.append_text(display);
- from_list.add(new FromAddressMap(account, addresses));
+ string display = _("%1$s via %2$s").printf(addresses.to_rfc822_string(),
this.account.information.display_name);
+ this.from_multiple.append_text(display);
+ this.from_list.add(new FromAddressMap(account, addresses));
- if (!set_active && from.equal_to(addresses)) {
- from_multiple.set_active(from_list.size - 1);
+ if (!set_active && this.from.equal_to(addresses)) {
+ this.from_multiple.set_active(this.from_list.size - 1);
set_active = true;
}
}
}
return set_active;
}
-
+
private void update_from_field() {
- from_multiple.changed.disconnect(on_from_changed);
- from_single.visible = from_multiple.visible = from_label.visible = false;
+ this.from_multiple.changed.disconnect(on_from_changed);
+ this.from_single.visible = this.from_multiple.visible = this.from_label.visible = false;
Gee.Map<string, Geary.AccountInformation> accounts;
try {
@@ -2359,30 +2248,27 @@ public class ComposerWidget : Gtk.EventBox {
}
// Don't show in inline, compact, or paned modes.
- if (state == ComposerState.INLINE || state == ComposerState.INLINE_COMPACT ||
- state == ComposerState.PANED)
+ if (this.state == ComposerState.INLINE || this.state == ComposerState.INLINE_COMPACT ||
+ this.state == ComposerState.PANED)
return;
// If there's only one account, show nothing. (From fields are hidden above.)
if (accounts.size < 1 || (accounts.size == 1 && Geary.traverse<Geary.AccountInformation>(
accounts.values).first().alternate_mailboxes == null))
return;
-
- from_label.visible = true;
-
- from_label.set_use_underline(true);
- from_label.set_mnemonic_widget(from_multiple);
+
+ this.from_label.visible = true;
+ this.from_label.set_mnemonic_widget(this.from_multiple);
// Composer label (with mnemonic underscore) for the account selector
// when choosing what address to send a message from.
- from_label.set_text_with_mnemonic(_("_From:"));
-
- from_multiple.visible = true;
- from_multiple.remove_all();
- from_list = new Gee.ArrayList<FromAddressMap>();
-
- bool set_active = false;
- if (compose_type == ComposeType.NEW_MESSAGE) {
- set_active = add_account_emails_to_from_list(account);
+ this.from_label.set_text_with_mnemonic(_("_From:"));
+
+ this.from_multiple.visible = true;
+ this.from_multiple.remove_all();
+ this.from_list = new Gee.ArrayList<FromAddressMap>();
+
+ bool set_active = add_account_emails_to_from_list(account);
+ if (this.compose_type == ComposeType.NEW_MESSAGE) {
foreach (Geary.AccountInformation info in accounts.values) {
try {
Geary.Account a = Geary.Engine.instance.get_account_instance(info);
@@ -2392,20 +2278,18 @@ public class ComposerWidget : Gtk.EventBox {
debug("Error getting account in composer: %s", e.message);
}
}
- } else {
- set_active = add_account_emails_to_from_list(account);
}
-
+
if (!set_active) {
// The identity or account that was active before has been removed
// use the best we can get now (primary address of the account or any other)
- from_multiple.set_active(0);
+ this.from_multiple.set_active(0);
on_from_changed();
}
-
- from_multiple.changed.connect(on_from_changed);
+
+ this.from_multiple.changed.connect(on_from_changed);
}
-
+
private void on_from_changed() {
bool changed = false;
try {
@@ -2416,43 +2300,44 @@ public class ComposerWidget : Gtk.EventBox {
// if the Geary.Account didn't change and the drafts folder is open(ing), do nothing more;
// need to check for the drafts folder because opening it in the case of multiple From:
- // is handled here alone, so need to open it if not already
- if (!changed && draft_manager != null)
+ // is handled here alone, so changed open it if not already
+ if (!changed && this.draft_manager != null)
return;
open_draft_manager_async.begin(null);
reset_draft_timer();
}
-
+
private bool update_from_account() throws Error {
- int index = from_multiple.get_active();
+ int index = this.from_multiple.get_active();
if (index < 0)
return false;
-
- assert(from_list.size > index);
- Geary.Account new_account = from_list.get(index).account;
- from = from_list.get(index).from;
- if (new_account == account)
+ assert(this.from_list.size > index);
+
+ Geary.Account new_account = this.from_list.get(index).account;
+ from = this.from_list.get(index).from;
+ if (new_account == this.account)
return false;
- account = new_account;
+ this.account = new_account;
set_entry_completions();
return true;
}
-
+
private void set_entry_completions() {
- if (contact_list_store != null && contact_list_store.contact_store == account.get_contact_store())
+ if (this.contact_list_store != null
+ && this.contact_list_store.contact_store == account.get_contact_store())
return;
-
- contact_list_store = new ContactListStore(account.get_contact_store());
-
- to_entry.completion = new ContactEntryCompletion(contact_list_store);
- cc_entry.completion = new ContactEntryCompletion(contact_list_store);
- bcc_entry.completion = new ContactEntryCompletion(contact_list_store);
- reply_to_entry.completion = new ContactEntryCompletion(contact_list_store);
+
+ this.contact_list_store = new ContactListStore(account.get_contact_store());
+
+ this.to_entry.completion = new ContactEntryCompletion(this.contact_list_store);
+ this.cc_entry.completion = new ContactEntryCompletion(this.contact_list_store);
+ this.bcc_entry.completion = new ContactEntryCompletion(this.contact_list_store);
+ this.reply_to_entry.completion = new ContactEntryCompletion(this.contact_list_store);
}
-
+
}
diff --git a/src/client/composer/composer-window.vala b/src/client/composer/composer-window.vala
index 1fd0396..5123308 100644
--- a/src/client/composer/composer-window.vala
+++ b/src/client/composer/composer-window.vala
@@ -4,50 +4,67 @@
* (version 2.1 or later). See the COPYING file in this distribution.
*/
-// Window for sending messages.
-public class ComposerWindow : Gtk.Window, ComposerContainer {
+/**
+ * A ComposerWindow is a ComposerContainer that is used to compose mails in a separate window
+ * (i.e. detached) of its own.
+ */
+public class ComposerWindow : Gtk.ApplicationWindow, ComposerContainer {
private bool closing = false;
-
+
+ protected ComposerWidget composer { get; set; }
+
+ protected Gee.MultiMap<string, string>? old_accelerators { get; set; }
+
+ public Gtk.ApplicationWindow top_window {
+ get { return this; }
+ }
+
public ComposerWindow(ComposerWidget composer) {
Object(type: Gtk.WindowType.TOPLEVEL);
-
- add(composer);
+ this.composer = composer;
+
+ // Make sure it gets added to the GtkApplication, to get the window-specific
+ // composer actions to work properly.
+ GearyApplication.instance.add_window(this);
+
+ add(this.composer);
+ focus_in_event.connect(on_focus_in);
+ focus_out_event.connect(on_focus_out);
- composer.header.show_close_button = true;
- composer.free_header();
- set_titlebar(composer.header);
- composer.bind_property("window-title", composer.header, "title",
+ this.composer.header.show_close_button = true;
+ this.composer.free_header();
+ set_titlebar(this.composer.header);
+ composer.bind_property("window-title", this.composer.header, "title",
BindingFlags.SYNC_CREATE);
- add_accel_group(composer.ui.get_accel_group());
show();
set_position(Gtk.WindowPosition.CENTER);
}
-
- public Gtk.Window top_window {
- get { return this; }
- }
-
+
public override void show() {
set_default_size(680, 600);
base.show();
}
-
+
public void close_container() {
- closing = true;
+ on_focus_out();
+ this.composer.editor.focus_in_event.disconnect(on_focus_in);
+ this.composer.editor.focus_out_event.disconnect(on_focus_out);
+
+ this.closing = true;
destroy();
}
-
+
public override bool delete_event(Gdk.EventAny event) {
- return !(closing ||
+ return !(this.closing ||
((ComposerWidget) get_child()).should_close() == ComposerWidget.CloseStatus.DO_CLOSE);
}
-
+
public void vanish() {
hide();
}
-
+
public void remove_composer() {
warning("Detached composer received remove");
}
diff --git a/src/client/composer/email-entry.vala b/src/client/composer/email-entry.vala
index 3f23f20..5a1b9a3 100644
--- a/src/client/composer/email-entry.vala
+++ b/src/client/composer/email-entry.vala
@@ -4,10 +4,13 @@
* (version 2.1 or later). See the COPYING file in this distribution.
*/
-// Displays a dialog for collecting the user's login data.
+// A custom entry for e-mail addresses
public class EmailEntry : Gtk.Entry {
- public bool valid_or_empty { get; set; default = true; }
+ // Whether this entry contains a valid email address
+ public bool valid { get; set; default = false; }
+
public bool empty { get; set; default = true; }
+
public bool modified = false;
// null or valid addresses
@@ -44,7 +47,7 @@ public class EmailEntry : Gtk.Entry {
updating = true;
addresses = null;
updating = false;
- valid_or_empty = true;
+ valid = false;
empty = true;
return;
}
@@ -56,7 +59,7 @@ public class EmailEntry : Gtk.Entry {
private void validate_addresses() {
if (addresses == null || addresses.size == 0) {
- valid_or_empty = true;
+ valid = false;
empty = true;
return;
}
@@ -64,11 +67,11 @@ public class EmailEntry : Gtk.Entry {
foreach (Geary.RFC822.MailboxAddress address in addresses) {
if (!address.is_valid()) {
- valid_or_empty = false;
+ valid = false;
return;
}
}
- valid_or_empty = true;
+ valid = true;
}
private bool on_key_press(Gtk.Widget widget, Gdk.EventKey event) {
diff --git a/src/client/util/util-gtk.vala b/src/client/util/util-gtk.vala
index e60e7a9..16ca6aa 100644
--- a/src/client/util/util-gtk.vala
+++ b/src/client/util/util-gtk.vala
@@ -138,4 +138,19 @@ public void set_label_xalign(Gtk.Label label, float xalign) {
label.set("xalign", xalign);
}
+/**
+ * Returns whether the close button is at the end of the headerbar.
+ */
+bool close_button_at_end() {
+ string layout = Gtk.Settings.get_default().gtk_decoration_layout;
+ bool at_end = false;
+ // Based on logic of close_button_at_end in gtkheaderbar.c: Close button appears
+ // at end iff "close" follows a colon in the layout string.
+ if (layout != null) {
+ int colon_ind = layout.index_of(":");
+ at_end = (colon_ind >= 0 && layout.index_of("close", colon_ind) >= 0);
+ }
+ return at_end;
+}
+
}
diff --git a/ui/CMakeLists.txt b/ui/CMakeLists.txt
index 20efd37..e16c285 100644
--- a/ui/CMakeLists.txt
+++ b/ui/CMakeLists.txt
@@ -6,8 +6,9 @@ set(RESOURCE_LIST
STRIPBLANKS "account_spinner.glade"
STRIPBLANKS "app_menu.interface"
STRIPBLANKS "certificate_warning_dialog.glade"
- STRIPBLANKS "composer.glade"
- STRIPBLANKS "composer_accelerators.ui"
+ STRIPBLANKS "composer-headerbar.ui"
+ STRIPBLANKS "composer-menus.ui"
+ STRIPBLANKS "composer-widget.ui"
STRIPBLANKS "edit_alternate_emails.glade"
STRIPBLANKS "find_bar.glade"
STRIPBLANKS "folder-popover.ui"
diff --git a/ui/composer-headerbar.ui b/ui/composer-headerbar.ui
new file mode 100644
index 0000000..7137ca0
--- /dev/null
+++ b/ui/composer-headerbar.ui
@@ -0,0 +1,198 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <requires lib="gtk+" version="3.14"/>
+ <template class="ComposerHeaderbar" parent="GtkHeaderBar">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="show_close_button">False</property>
+ <child>
+ <object class="GtkBox" id="detach_start">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkButton" id="detach_start_button">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="margin_end">6</property>
+ <property name="relief">GTK_RELIEF_NONE</property>
+ <property name="action_name">cmh.detach</property>
+ <property name="always_show_image">True</property>
+ <property name="tooltip_text" translatable="yes">Detach (Ctrl+D)</property>
+ <child>
+ <object class="GtkImage" id="detach_start_image">
+ <property name="icon_name">detach-symbolic</property>
+ <property name="pixel-size">16</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSeparator" id="detach_start_separator">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="new_message_attach_button">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="action_name">cmh.add-attachment</property>
+ <property name="always_show_image">True</property>
+ <property name="tooltip_text" translatable="yes">Attach File (Ctrl+T)</property>
+ <child>
+ <object class="GtkImage" id="new_message_attach_image">
+ <property name="icon_name">mail-attachment-symbolic</property>
+ <property name="pixel-size">16</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox" id="conversation_attach_buttons">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <style>
+ <class name="linked"/>
+ </style>
+ <child>
+ <object class="GtkButton" id="conversation_attach_new_button">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="action_name">cmh.add-attachment</property>
+ <property name="always_show_image">True</property>
+ <property name="tooltip_text" translatable="yes">Attach File (Ctrl+T)</property>
+ <child>
+ <object class="GtkImage" id="conversation_attach_new_image">
+ <property name="icon_name">mail-attachment-symbolic</property>
+ <property name="pixel-size">16</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="conversation_attach_original_button">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="action_name">cmh.add-original-attachments</property>
+ <property name="always_show_image">True</property>
+ <property name="tooltip_text" translatable="yes">Include Original Attachments</property>
+ <child>
+ <object class="GtkImage" id="conversation_attach_original_image">
+ <property name="icon_name">edit-copy-symbolic</property>
+ <property name="pixel-size">16</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="recipients_button">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="relief">GTK_RELIEF_NONE</property>
+ <child>
+ <object class="GtkLabel" id="recipients_label">
+ <property name="visible">True</property>
+ <property name="ellipsize">end</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox" id="detach_end">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkSeparator" id="detach_end_separator">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="detach_end_button">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="margin_start">6</property>
+ <property name="relief">GTK_RELIEF_NONE</property>
+ <property name="action_name">cmh.detach</property>
+ <property name="always_show_image">True</property>
+ <property name="tooltip_text" translatable="yes">Detach (Ctrl+D)</property>
+ <child>
+ <object class="GtkImage" id="detach_end_image">
+ <property name="icon_name">detach-symbolic</property>
+ <property name="pixel-size">16</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="pack_type">end</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="send_button">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="action_name">cmh.send</property>
+ <property name="always_show_image">True</property>
+ <property name="use_underline">True</property>
+ <property name="tooltip_text" translatable="yes">Send (Ctrl+Enter)</property>
+ <property name="label" translatable="yes">_Send</property>
+ <style>
+ <class name="suggested-action"/>
+ </style>
+ </object>
+ <packing>
+ <property name="pack_type">end</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="close_buttons">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <style>
+ <class name="linked"/>
+ </style>
+ <child>
+ <object class="GtkButton" id="discard_and_close_button">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="action_name">cmh.close-and-discard</property>
+ <property name="always_show_image">True</property>
+ <property name="tooltip_text" translatable="yes">Close and Discard</property>
+ <child>
+ <object class="GtkImage" id="discard_and_close_image">
+ <property name="icon_name">user-trash-symbolic</property>
+ <property name="pixel-size">16</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="save_and_close_button">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="action_name">cmh.close-and-save</property>
+ <property name="always_show_image">True</property>
+ <property name="tooltip_text" translatable="yes">Close and Save</property>
+ <child>
+ <object class="GtkImage" id="save_and_close_image">
+ <property name="icon_name">document-save-symbolic</property>
+ <property name="pixel-size">16</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="pack_type">end</property>
+ </packing>
+ </child>
+ </template>
+</interface>
diff --git a/ui/composer-menus.ui b/ui/composer-menus.ui
new file mode 100644
index 0000000..cf939e6
--- /dev/null
+++ b/ui/composer-menus.ui
@@ -0,0 +1,108 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<interface>
+ <menu id="html_menu_model">
+ <section>
+ <item>
+ <attribute name="label" translatable="yes">S_ans Serif</attribute>
+ <attribute name="action">cmp.font-family</attribute>
+ <attribute name="target">sans</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">S_erif</attribute>
+ <attribute name="action">cmp.font-family</attribute>
+ <attribute name="target">serif</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">_Fixed Width</attribute>
+ <attribute name="action">cmp.font-family</attribute>
+ <attribute name="target">monospace</attribute>
+ </item>
+ </section>
+ <section>
+ <item>
+ <attribute name="label" translatable="yes">_Small</attribute>
+ <attribute name="action">cmp.font-size</attribute>
+ <attribute name="target">small</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">_Medium</attribute>
+ <attribute name="action">cmp.font-size</attribute>
+ <attribute name="target">medium</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">Lar_ge</attribute>
+ <attribute name="action">cmp.font-size</attribute>
+ <attribute name="target">large</attribute>
+ </item>
+ </section>
+ <section>
+ <item>
+ <attribute name="label" translatable="yes">C_olor</attribute>
+ <attribute name="action">cmp.color</attribute>
+ </item>
+ </section>
+ <section>
+ <item>
+ <attribute name="label" translatable="yes">_Rich Text</attribute>
+ <attribute name="action">cmp.compose-as-html</attribute>
+ </item>
+ </section>
+ <section>
+ <item>
+ <attribute name="label" translatable="yes">Show Extended Fields</attribute>
+ <attribute name="action">cmp.show-extended</attribute>
+ </item>
+ </section>
+ </menu>
+
+ <menu id="plain_menu_model">
+ <section>
+ <item>
+ <attribute name="label" translatable="yes">_Rich Text</attribute>
+ <attribute name="action">cmp.compose-as-html</attribute>
+ </item>
+ </section>
+ <section>
+ <item>
+ <attribute name="label" translatable="yes">Show Extended Fields</attribute>
+ <attribute name="action">cmp.show-extended</attribute>
+ </item>
+ </section>
+ </menu>
+
+ <menu id="context_menu_model">
+ <section>
+ <item>
+ <attribute name="label" translatable="yes">_Undo</attribute>
+ <attribute name="action">undo</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">_Redo</attribute>
+ <attribute name="action">redo</attribute>
+ </item>
+ </section>
+ <section>
+ <item>
+ <attribute name="label" translatable="yes">Cu_t</attribute>
+ <attribute name="action">cut</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">_Copy</attribute>
+ <attribute name="action">copy</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">Copy _Link</attribute>
+ <attribute name="action">copy-link</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">_Paste</attribute>
+ <attribute name="action">paste</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">Paste _With Formatting</attribute>
+ <attribute name="action">paste-with-formatting</attribute>
+ </item>
+ </section>
+ </menu>
+</interface>
diff --git a/ui/composer-widget.ui b/ui/composer-widget.ui
new file mode 100644
index 0000000..98c17b5
--- /dev/null
+++ b/ui/composer-widget.ui
@@ -0,0 +1,627 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <requires lib="gtk+" version="3.14"/>
+ <template class="ComposerWidget" parent="GtkEventBox">
+ <property name="visible">True</property>
+ <signal name="drag_data_received" handler="on_drag_data_received"/>
+ <signal name="drag_drop" handler="on_drag_drop"/>
+ <signal name="drag_motion" handler="on_drag_motion"/>
+ <signal name="drag_leave" handler="on_drag_leave"/>
+ <child>
+ <object class="GtkBox" id="composer_container">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">2</property>
+ <child>
+ <object class="GtkBox" id="header_area">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="hidden_on_attachment_drag_over">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkBox" id="hidden_on_attachment_drag_over_child">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkGrid" id="recipients">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin_start">6</property>
+ <property name="margin_end">6</property>
+ <property name="margin_top">6</property>
+ <property name="row_spacing">0</property>
+ <property name="column_spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="to_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">end</property>
+ <property name="label" translatable="yes" comments="Address(es) e-mail is to be sent
to">_To</property>
+ <property name="use_underline">True</property>
+ <property name="justify">right</property>
+ <property name="mnemonic_widget">to_box</property>
+ <property name="margin_top">6</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="cc_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">end</property>
+ <property name="label" translatable="yes">_Cc</property>
+ <property name="use_underline">True</property>
+ <property name="justify">right</property>
+ <property name="mnemonic_widget">to_box</property>
+ <property name="margin_top">6</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">2</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEventBox" id="to_box">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="hexpand">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">1</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEventBox" id="cc_box">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="hexpand">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">2</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="subject_entry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hexpand">True</property>
+ <property name="invisible_char">•</property>
+ <property name="invisible_char_set">True</property>
+ <property name="margin_top">6</property>
+ <signal name="changed" handler="on_subject_changed" swapped="no"/>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">5</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="subject_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">end</property>
+ <property name="label" translatable="yes">_Subject</property>
+ <property name="use_underline">True</property>
+ <property name="justify">right</property>
+ <property name="mnemonic_widget">subject_entry</property>
+ <property name="margin_top">6</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">5</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="bcc_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">end</property>
+ <property name="label" translatable="yes">_Bcc</property>
+ <property name="use_underline">True</property>
+ <property name="justify">right</property>
+ <property name="mnemonic_widget">to_box</property>
+ <property name="margin_top">6</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">3</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEventBox" id="bcc_box">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="hexpand">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">3</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="reply_to_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">end</property>
+ <property name="label" translatable="yes">_Reply-To</property>
+ <property name="use_underline">True</property>
+ <property name="justify">right</property>
+ <property name="mnemonic_widget">to_box</property>
+ <property name="margin_top">6</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">4</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEventBox" id="reply_to_box">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="hexpand">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">4</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="from_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">end</property>
+ <property name="use_underline">True</property>
+ <property name="label" translatable="yes" comments="Geary account mail will be sent
from">From</property>
+ <property name="justify">right</property>
+ <property name="mnemonic_widget">to_box</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="from_container">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkLabel" id="from_single">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBoxText" id="from_multiple">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="entry_text_column">0</property>
+ <property name="id_column">1</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">0</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="visible_on_attachment_drag_over">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkBox" id="visible_on_attachment_drag_over_child">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="valign">center</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="valign">center</property>
+ <property name="label" translatable="yes">Drop files here</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="valign">center</property>
+ <property name="label" translatable="yes">To add them as attachments</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="composer_toolbar">
+ <property name="visible">True</property>
+ <property name="orientation">horizontal</property>
+ <property name="can_focus">False</property>
+ <property name="margin_start">6</property>
+ <property name="margin_end">6</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkBox" id="font_style_buttons">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <style>
+ <class name="linked"/>
+ </style>
+ <child>
+ <object class="GtkToggleButton" id="bold_button">
+ <property name="visible" bind-source="bold_button" bind-property="sensitive" />
+ <property name="can_focus">False</property>
+ <property name="always_show_image">True</property>
+ <property name="action_name">cmp.bold</property>
+ <property name="tooltip_text" translatable="yes">Bold (Ctrl+B)</property>
+ <child>
+ <object class="GtkImage" id="bold_image">
+ <property name="icon_name">format-text-bold-symbolic</property>
+ <property name="pixel-size">16</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkToggleButton" id="italics_button">
+ <property name="visible" bind-source="italics_button" bind-property="sensitive" />
+ <property name="can_focus">False</property>
+ <property name="action_name">cmp.italic</property>
+ <property name="always_show_image">True</property>
+ <property name="tooltip_text" translatable="yes">Italic (Ctrl+I)</property>
+ <child>
+ <object class="GtkImage" id="italics_image">
+ <property name="icon_name">format-text-italic-symbolic</property>
+ <property name="pixel-size">16</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkToggleButton" id="underline_button">
+ <property name="visible" bind-source="underline_button" bind-property="sensitive" />
+ <property name="can_focus">False</property>
+ <property name="action_name">cmp.underline</property>
+ <property name="always_show_image">True</property>
+ <property name="tooltip_text" translatable="yes">Underline (Ctrl+U)</property>
+ <child>
+ <object class="GtkImage" id="underline_image">
+ <property name="icon_name">format-text-underline-symbolic</property>
+ <property name="pixel-size">16</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkToggleButton" id="strikethrough_button">
+ <property name="visible" bind-source="strikethrough_button" bind-property="sensitive" />
+ <property name="can_focus">False</property>
+ <property name="action_name">cmp.strikethrough</property>
+ <property name="always_show_image">True</property>
+ <property name="tooltip_text" translatable="yes">Strikethrough (Ctrl+K)</property>
+ <child>
+ <object class="GtkImage" id="strikethrough_image">
+ <property name="icon_name">format-text-strikethrough-symbolic</property>
+ <property name="pixel-size">16</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox" id="indentation_buttons">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <style>
+ <class name="linked"/>
+ </style>
+ <child>
+ <object class="GtkButton" id="indent_button">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="action_name">cmp.indent</property>
+ <property name="always_show_image">True</property>
+ <property name="tooltip_text" translatable="yes">Quote text (Ctrl+])</property>
+ <child>
+ <object class="GtkImage" id="indent_image">
+ <property name="icon_name">format-indent-more-symbolic</property>
+ <property name="pixel-size">16</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="outdent_button">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="action_name">cmp.outdent</property>
+ <property name="always_show_image">True</property>
+ <property name="tooltip_text" translatable="yes">Unquote text (Ctrl+[)</property>
+ <child>
+ <object class="GtkImage" id="outdent_image">
+ <property name="icon_name">format-indent-less-symbolic</property>
+ <property name="pixel-size">16</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox" id="link_buttons">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <style>
+ <class name="linked"/>
+ </style>
+ <child>
+ <object class="GtkButton" id="insert_link_button">
+ <property name="visible" bind-source="insert_link_button" bind-property="sensitive" />
+ <property name="can_focus">False</property>
+ <property name="action_name">cmp.insert-link</property>
+ <property name="always_show_image">True</property>
+ <property name="tooltip_text" translatable="yes">Link (Ctrl+L)</property>
+ <child>
+ <object class="GtkImage" id="insert_link_image">
+ <property name="icon_name">insert-link-symbolic</property>
+ <property name="pixel-size">16</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="remove_format_button">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="always_show_image">True</property>
+ <property name="action_name">cmp.remove-format</property>
+ <property name="tooltip_text" translatable="yes">Remove formatting (Ctrl+Space)</property>
+ <child>
+ <object class="GtkImage" id="remove_format_image">
+ <property name="icon_name">format-text-remove-symbolic</property>
+ <property name="pixel-size">16</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="select_dictionary_button">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="action_name">cmp.select-dictionary</property>
+ <property name="always_show_image">True</property>
+ <property name="tooltip_text" translatable="yes">Select spell checking language</property>
+ <child>
+ <object class="GtkImage" id="select_dictionary_image">
+ <property name="icon_name">accessories-dictionary-symbolic</property>
+ <property name="pixel-size">16</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkMenuButton" id="menu_button">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="pack_type">end</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="info_label">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="pack_type">end</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="shadow_type">in</property>
+ <child>
+ <object class="GtkBox" id="message_area">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkOverlay" id="message_overlay">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkScrolledWindow" id="editor_scrolled">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">automatic</property>
+ <property name="vscrollbar_policy">automatic</property>
+ <child>
+ <placeholder />
+ </child>
+ </object>
+ </child>
+ <child type="overlay">
+ <object class="GtkLabel" id="message_overlay_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="valign">end</property>
+ <property name="ellipsize">middle</property>
+ <signal name="realize" handler="on_message_overlay_label_realize" swapped="no"/>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <style>
+ <class name="geary-composer-body"/>
+ </style>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="attachments_box">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin_start">6</property>
+ <property name="margin_end">6</property>
+ <property name="margin_top">6</property>
+ <property name="margin_bottom">6</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">5</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </template>
+</interface>
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]