Closes #5005 Paste is now plain text by default, added context menu, action sensitivity

This commit is contained in:
Eric Gregory 2012-04-13 17:45:10 -07:00
parent a99b02c001
commit 0793679dac
4 changed files with 179 additions and 22 deletions

View file

@ -8,6 +8,28 @@
public class ComposerWindow : Gtk.Window {
private static string DEFAULT_TITLE = _("New Message");
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_FORMAT = "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_REMOVE_FORMAT = "removeformat";
private const string ACTION_INDENT = "indent";
private const string ACTION_OUTDENT = "outdent";
private const string ACTION_JUSTIFY_LEFT = "justifyleft";
private const string ACTION_JUSTIFY_RIGHT = "justifyright";
private const string ACTION_JUSTIFY_CENTER = "justifycenter";
private const string ACTION_JUSTIFY_FULL = "justifyfull";
private const string ACTION_FONT = "font";
private const string ACTION_COLOR = "color";
private const string ACTION_INSERT_LINK = "insertlink";
private const string REPLY_ID = "reply";
private const string HTML_BODY = """
<html><head><title></title>
@ -72,6 +94,9 @@ public class ComposerWindow : Gtk.Window {
private Gtk.Entry subject_entry;
private Gtk.Button send_button;
private Gtk.Label message_overlay_label;
private Gtk.Menu? context_menu = null;
private Gtk.ActionGroup actions;
private string? hover_url = null;
private WebKit.WebView editor;
private Gtk.UIManager ui;
@ -92,7 +117,7 @@ public class ComposerWindow : Gtk.Window {
(builder.get_object("bcc") as Gtk.EventBox).add(bcc_entry);
subject_entry = builder.get_object("subject") as Gtk.Entry;
Gtk.Alignment msg_area = builder.get_object("message area") as Gtk.Alignment;
Gtk.ActionGroup actions = builder.get_object("compose actions") as Gtk.ActionGroup;
actions = builder.get_object("compose actions") as Gtk.ActionGroup;
Gtk.ScrolledWindow scroll = new Gtk.ScrolledWindow(null, null);
scroll.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC);
@ -113,31 +138,33 @@ public class ComposerWindow : Gtk.Window {
cc_entry.changed.connect(validate_send_button);
bcc_entry.changed.connect(validate_send_button);
actions.get_action("undo").activate.connect(on_action);
actions.get_action("redo").activate.connect(on_action);
actions.get_action(ACTION_UNDO).activate.connect(on_action);
actions.get_action(ACTION_REDO).activate.connect(on_action);
actions.get_action("cut").activate.connect(on_cut);
actions.get_action("copy").activate.connect(on_copy);
actions.get_action("paste").activate.connect(on_paste);
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("bold").activate.connect(on_action);
actions.get_action("italic").activate.connect(on_action);
actions.get_action("underline").activate.connect(on_action);
actions.get_action("strikethrough").activate.connect(on_action);
actions.get_action(ACTION_BOLD).activate.connect(on_action);
actions.get_action(ACTION_ITALIC).activate.connect(on_action);
actions.get_action(ACTION_UNDERLINE).activate.connect(on_action);
actions.get_action(ACTION_STRIKETHROUGH).activate.connect(on_action);
actions.get_action("removeformat").activate.connect(on_remove_format);
actions.get_action(ACTION_REMOVE_FORMAT).activate.connect(on_remove_format);
actions.get_action("indent").activate.connect(on_action);
actions.get_action("outdent").activate.connect(on_action);
actions.get_action(ACTION_INDENT).activate.connect(on_action);
actions.get_action(ACTION_OUTDENT).activate.connect(on_action);
actions.get_action("justifyleft").activate.connect(on_action);
actions.get_action("justifyright").activate.connect(on_action);
actions.get_action("justifycenter").activate.connect(on_action);
actions.get_action("justifyfull").activate.connect(on_action);
actions.get_action(ACTION_JUSTIFY_LEFT).activate.connect(on_action);
actions.get_action(ACTION_JUSTIFY_RIGHT).activate.connect(on_action);
actions.get_action(ACTION_JUSTIFY_CENTER).activate.connect(on_action);
actions.get_action(ACTION_JUSTIFY_FULL).activate.connect(on_action);
actions.get_action("font").activate.connect(on_select_font);
actions.get_action("color").activate.connect(on_select_color);
actions.get_action("insertlink").activate.connect(on_insert_link);
actions.get_action(ACTION_FONT).activate.connect(on_select_font);
actions.get_action(ACTION_COLOR).activate.connect(on_select_color);
actions.get_action(ACTION_INSERT_LINK).activate.connect(on_insert_link);
ui = new Gtk.UIManager();
ui.insert_action_group(actions, 0);
@ -169,7 +196,18 @@ public class ComposerWindow : Gtk.Window {
editor.set_editable(true);
editor.load_finished.connect(on_load_finished);
editor.hovering_over_link.connect(on_hovering_over_link);
editor.load_string(HTML_BODY, "text/html", "UTF8", ""); // only do this after setting reply_body
editor.button_press_event.connect(on_button_press_event);
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.user_changed_contents.connect(update_actions);
// only do this after setting reply_body
editor.load_string(HTML_BODY, "text/html", "UTF8", "");
if (!Geary.String.is_empty(to) && !Geary.String.is_empty(subject))
editor.grab_focus();
@ -188,6 +226,7 @@ public class ComposerWindow : Gtk.Window {
s.enable_scripts = false;
s.enable_java_applet = false;
s.enable_plugins = false;
s.enable_default_context_menu = false;
editor.settings = s;
scroll.add(editor);
@ -217,6 +256,8 @@ public class ComposerWindow : Gtk.Window {
} else if (!Geary.String.is_empty(to)) {
subject_entry.grab_focus();
}
update_actions();
}
public Geary.ComposedEmail get_composed_email(
@ -316,13 +357,38 @@ public class ComposerWindow : Gtk.Window {
((Gtk.Editable) get_focus()).copy_clipboard();
}
private void on_copy_link() {
Gtk.Clipboard c = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD);
c.set_text(hover_url, -1);
c.store();
}
private void on_clipboard_text_received(Gtk.Clipboard clipboard, string? text) {
if (text == null)
return;
// Insert plain text from clipboard.
editor.get_dom_document().exec_command("inserthtml", false,
Geary.HTML.newlines_to_br(Geary.HTML.escape_markup(text)));
}
private void on_paste() {
if (get_focus() == editor)
editor.paste_clipboard();
//editor.paste_clipboard();
get_clipboard(Gdk.SELECTION_CLIPBOARD).request_text(on_clipboard_text_received);
else if (get_focus() is Gtk.Editable)
((Gtk.Editable) get_focus()).paste_clipboard();
}
private void on_paste_with_formatting() {
if (get_focus() == editor)
editor.paste_clipboard();
}
private void on_select_all() {
editor.select_all();
}
private void on_remove_format() {
editor.get_dom_document().exec_command("removeformat", false, "");
editor.get_dom_document().exec_command("removeparaformat", false, "");
@ -438,6 +504,8 @@ public class ComposerWindow : Gtk.Window {
private void on_hovering_over_link(string? title, string? url) {
message_overlay_label.label = url;
hover_url = url;
update_actions();
}
private void on_spell_check_changed() {
@ -474,5 +542,74 @@ public class ComposerWindow : Gtk.Window {
return base.key_press_event(event);
}
private bool on_button_press_event(Gdk.EventButton event) {
if (event.button == 3)
create_context_menu(event);
return false;
}
private void create_context_menu(Gdk.EventButton event) {
context_menu = new Gtk.Menu();
// Undo
Gtk.MenuItem undo = new Gtk.MenuItem();
undo.related_action = actions.get_action(ACTION_UNDO);
context_menu.append(undo);
// Redo
Gtk.MenuItem redo = new Gtk.MenuItem();
redo.related_action = actions.get_action(ACTION_REDO);
context_menu.append(redo);
context_menu.append(new Gtk.MenuItem());
// Cut
Gtk.MenuItem cut = new Gtk.MenuItem();
cut.related_action = actions.get_action(ACTION_CUT);
context_menu.append(cut);
// Copy
Gtk.MenuItem copy = new Gtk.MenuItem();
copy.related_action = actions.get_action(ACTION_COPY);
context_menu.append(copy);
// Copy link.
Gtk.MenuItem copy_link = new Gtk.MenuItem();
copy_link.related_action = actions.get_action(ACTION_COPY_LINK);
context_menu.append(copy_link);
// Paste
Gtk.MenuItem paste = new Gtk.MenuItem();
paste.related_action = actions.get_action(ACTION_PASTE);
context_menu.append(paste);
// Paste with formatting
Gtk.MenuItem paste_format = new Gtk.MenuItem();
paste_format.related_action = actions.get_action(ACTION_PASTE_FORMAT);
context_menu.append(paste_format);
context_menu.append(new Gtk.MenuItem());
// Select all.
Gtk.MenuItem select_all_item = new Gtk.MenuItem.with_mnemonic(_("Select _All"));
select_all_item.activate.connect(on_select_all);
context_menu.append(select_all_item);
context_menu.show_all();
context_menu.popup(null, null, null, event.button, event.time);
}
private void update_actions() {
actions.get_action(ACTION_UNDO).sensitive = editor.can_undo();
actions.get_action(ACTION_REDO).sensitive = editor.can_redo();
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();
}
}

View file

@ -10,6 +10,10 @@ public inline string escape_markup(string? plain) {
return (!String.is_empty(plain) && plain.validate()) ? Markup.escape_text(plain) : "";
}
public inline string newlines_to_br(string? text) {
return !String.is_empty(text) ? text.replace("\n", "<br />") : "";
}
// Removes any text between < and >. Additionally, if input terminates in the middle of a tag,
// the tag will be removed.
// If the HTML is invalid, the original string will be returned.

View file

@ -134,6 +134,17 @@
</object>
<accelerator key="space" modifiers="GDK_CONTROL_MASK"/>
</child>
<child>
<object class="GtkAction" id="paste with formatting">
<property name="label" translatable="yes" context="Clipboard paste with rich text">Paste _with formatting</property>
</object>
<accelerator key="v" modifiers="GDK_SHIFT_MASK | GDK_CONTROL_MASK"/>
</child>
<child>
<object class="GtkAction" id="copy link">
<property name="label" translatable="yes">Copy _link</property>
</object>
</child>
</object>
<object class="GtkBox" id="composer">
<property name="visible">True</property>
@ -219,6 +230,7 @@
<child>
<object class="GtkEventBox" id="to">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="hexpand">True</property>
</object>
<packing>
@ -231,6 +243,7 @@
<child>
<object class="GtkEventBox" id="cc">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="hexpand">True</property>
</object>
<packing>
@ -290,6 +303,7 @@
<child>
<object class="GtkEventBox" id="bcc">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="hexpand">True</property>
</object>
<packing>

View file

@ -4,7 +4,9 @@
<accelerator action="cut" />
<accelerator action="copy" />
<accelerator action="copy link" />
<accelerator action="paste" />
<accelerator action="paste with formatting" />
<accelerator action="bold" />
<accelerator action="italic" />