Closes #3769 Initial save to drafts functionality
This commit is contained in:
parent
14b9343da3
commit
bc8a77bcd1
14 changed files with 172 additions and 33 deletions
|
|
@ -38,6 +38,7 @@ public class ComposerWindow : Gtk.Window {
|
|||
private const string ACTION_INSERT_LINK = "insertlink";
|
||||
private const string ACTION_COMPOSE_AS_HTML = "compose as html";
|
||||
private const string ACTION_CLOSE = "close";
|
||||
private const string ACTION_SAVE = "save";
|
||||
|
||||
private const string URI_LIST_MIME_TYPE = "text/uri-list";
|
||||
private const string FILE_URI_PREFIX = "file://";
|
||||
|
|
@ -84,9 +85,6 @@ public class ComposerWindow : Gtk.Window {
|
|||
/// A list of keywords, separated by pipe ("|") characters, that suggest an attachment
|
||||
public const string ATTACHMENT_KEYWORDS_LOCALIZED = _("attach|enclosed|enclosing|cover letter");
|
||||
|
||||
// Signal sent when the "Send" button is clicked.
|
||||
public signal void send(ComposerWindow composer);
|
||||
|
||||
public Geary.Account account { get; private set; }
|
||||
|
||||
public string from { get; set; }
|
||||
|
|
@ -279,6 +277,8 @@ public class ComposerWindow : Gtk.Window {
|
|||
|
||||
actions.get_action(ACTION_CLOSE).activate.connect(on_close);
|
||||
|
||||
actions.get_action(ACTION_SAVE).activate.connect(on_save);
|
||||
|
||||
ui = new Gtk.UIManager();
|
||||
ui.insert_action_group(actions, 0);
|
||||
add_accel_group(ui.get_accel_group());
|
||||
|
|
@ -735,10 +735,38 @@ public class ComposerWindow : Gtk.Window {
|
|||
private void on_send() {
|
||||
if (should_send()) {
|
||||
linkify_document(editor.get_dom_document());
|
||||
send(this);
|
||||
account.send_email_async.begin(get_composed_email());
|
||||
destroy();
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the drafts folder for the current From account.
|
||||
private Geary.Folder? get_drafts_folder() {
|
||||
Geary.Folder? drafts_folder = null;
|
||||
try {
|
||||
drafts_folder = account.get_special_folder(Geary.SpecialFolderType.DRAFTS);
|
||||
} catch (Error e) {
|
||||
debug("Error getting drafts folder: %s", e.message);
|
||||
}
|
||||
|
||||
return drafts_folder;
|
||||
}
|
||||
|
||||
// Save to the draft folder, if available.
|
||||
// Note that drafts are NOT "linkified."
|
||||
private void on_save() {
|
||||
Geary.Folder? drafts_folder = get_drafts_folder();
|
||||
if (drafts_folder == null) {
|
||||
stdout.printf("No drafts folder available for this account.\n");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
account.create_email_async.begin(drafts_folder.path,
|
||||
new Geary.RFC822.Message.from_composed_email(get_composed_email()),
|
||||
new Geary.EmailFlags(), null, null);
|
||||
}
|
||||
|
||||
private void on_add_attachment_button_clicked() {
|
||||
AttachmentDialog dialog = null;
|
||||
do {
|
||||
|
|
@ -1489,6 +1517,9 @@ public class ComposerWindow : Gtk.Window {
|
|||
debug("Error updating account in Composer: %s", e.message);
|
||||
}
|
||||
}
|
||||
|
||||
// Only show Save button if we have a drafts folder to write to.
|
||||
actions.get_action(ACTION_SAVE).visible = get_drafts_folder() != null;
|
||||
}
|
||||
|
||||
private void set_entry_completions() {
|
||||
|
|
|
|||
|
|
@ -1415,7 +1415,6 @@ public class GearyController {
|
|||
else
|
||||
window = new ComposerWindow(current_account, compose_type, referred);
|
||||
window.set_position(Gtk.WindowPosition.CENTER);
|
||||
window.send.connect(on_send);
|
||||
|
||||
// We want to keep track of the open composer windows, so we can allow the user to cancel
|
||||
// an exit without losing their data.
|
||||
|
|
@ -1537,11 +1536,6 @@ public class GearyController {
|
|||
private void on_zoom_normal() {
|
||||
main_window.conversation_viewer.web_view.zoom_level = 1.0f;
|
||||
}
|
||||
|
||||
private void on_send(ComposerWindow composer_window) {
|
||||
composer_window.account.send_email_async.begin(composer_window.get_composed_email());
|
||||
composer_window.destroy();
|
||||
}
|
||||
|
||||
private void on_sent(Geary.RFC822.Message rfc822) {
|
||||
NotificationBubble.play_sound("message-sent-email");
|
||||
|
|
|
|||
|
|
@ -109,6 +109,30 @@ public abstract class Geary.AbstractAccount : BaseObject, Geary.Account {
|
|||
public abstract async Gee.Collection<string>? get_search_matches_async(
|
||||
Gee.Collection<Geary.EmailIdentifier> ids, Cancellable? cancellable = null) throws Error;
|
||||
|
||||
public virtual async void create_email_async(Geary.FolderPath path, Geary.RFC822.Message rfc822,
|
||||
Geary.EmailFlags? flags, DateTime? date_received, Cancellable? cancellable = null) throws Error {
|
||||
Folder folder = yield fetch_folder_async(path, cancellable);
|
||||
|
||||
FolderSupport.Create? supports_create = folder as FolderSupport.Create;
|
||||
if (supports_create == null)
|
||||
throw new EngineError.UNSUPPORTED("Folder %s does not support create", path.to_string());
|
||||
|
||||
yield supports_create.open_async(Folder.OpenFlags.NONE, cancellable);
|
||||
|
||||
// don't leave folder open if create fails
|
||||
Error? create_err = null;
|
||||
try {
|
||||
yield supports_create.create_email_async(rfc822, flags, date_received, cancellable);
|
||||
} catch (Error err) {
|
||||
create_err = err;
|
||||
}
|
||||
|
||||
yield supports_create.close_async(cancellable);
|
||||
|
||||
if (create_err != null)
|
||||
throw create_err;
|
||||
}
|
||||
|
||||
public virtual string to_string() {
|
||||
return name;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -275,6 +275,30 @@ public interface Geary.Account : BaseObject {
|
|||
public abstract async Gee.Collection<string>? get_search_matches_async(
|
||||
Gee.Collection<Geary.EmailIdentifier> ids, Cancellable? cancellable = null) throws Error;
|
||||
|
||||
/**
|
||||
* Creates (appends) the message to the specified {@link Folder}.
|
||||
*
|
||||
* Some implementations may locate the Folder for the caller, check that it supports
|
||||
* {@link FolderSupport.Create}, then call its create method. In that case, the usual
|
||||
* {@link EngineError} exceptions apply, except for the requirement that the Folder be open.
|
||||
* (@link Account} will open the Folder for the caller.)
|
||||
*
|
||||
* For other Folders, the Account may be able to bypass opening the folder and save it directly.
|
||||
*
|
||||
* The optional {@link EmailFlags} allows for those flags to be set when saved. Some Folders
|
||||
* may ignore those flags (i.e. Outbox) if not applicable.
|
||||
*
|
||||
* The optional DateTime allows for the message's "date received" time to be set when saved.
|
||||
* Like EmailFlags, this is optional if not applicable.
|
||||
*
|
||||
* Both values can be retrieved later via {@link EmailProperties}.
|
||||
*
|
||||
* @throws EngineError.NOT_FOUND If {@link FolderPath} could not be resolved.
|
||||
* @throws EngineError.UNSUPPORTED If the Folder does not support FolderSupport.Create.
|
||||
*/
|
||||
public abstract async void create_email_async(Geary.FolderPath path, Geary.RFC822.Message rfc822,
|
||||
Geary.EmailFlags? flags, DateTime? date_received, Cancellable? cancellable = null) throws Error;
|
||||
|
||||
/**
|
||||
* Used only for debugging. Should not be used for user-visible strings.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -9,12 +9,12 @@ public errordomain Geary.EngineError {
|
|||
ALREADY_OPEN,
|
||||
ALREADY_EXISTS,
|
||||
NOT_FOUND,
|
||||
READONLY,
|
||||
BAD_PARAMETERS,
|
||||
BAD_RESPONSE,
|
||||
INCOMPLETE_MESSAGE,
|
||||
SERVER_UNAVAILABLE,
|
||||
ALREADY_CLOSED,
|
||||
CLOSE_REQUIRED
|
||||
CLOSE_REQUIRED,
|
||||
UNSUPPORTED
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -14,17 +14,12 @@
|
|||
* without user interaction at some point in the future.
|
||||
*/
|
||||
public interface Geary.FolderSupport.Create : Geary.Folder {
|
||||
public enum Result {
|
||||
CREATED,
|
||||
MERGED
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a message in the folder. If the message already exists in the {@link Geary.Folder},
|
||||
* it will be merged (that is, fields in the message not already present will be added).
|
||||
*
|
||||
* The Folder must be opened prior to attempting this operation.
|
||||
*/
|
||||
public abstract async Result create_email_async(Geary.RFC822.Message rfc822, Cancellable? cancellable = null)
|
||||
throws Error;
|
||||
public abstract async void create_email_async(Geary.RFC822.Message rfc822, EmailFlags? flags,
|
||||
DateTime? date_received, Cancellable? cancellable = null) throws Error;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
*/
|
||||
|
||||
public class Geary.NamedFlag : BaseObject, Gee.Hashable<Geary.NamedFlag> {
|
||||
private string name;
|
||||
public string name { get; private set; }
|
||||
|
||||
public NamedFlag(string name) {
|
||||
this.name = name;
|
||||
|
|
|
|||
|
|
@ -231,7 +231,7 @@ private class Geary.SmtpOutboxFolder : Geary.AbstractLocalFolder, Geary.FolderSu
|
|||
Db.Statement stmt = cx.prepare(
|
||||
"INSERT INTO SmtpOutboxTable (message, ordering)"
|
||||
+ "VALUES (?, (SELECT COALESCE(MAX(ordering), 0) + 1 FROM SmtpOutboxTable))");
|
||||
stmt.bind_string_buffer(0, rfc822.get_body_rfc822_buffer_smtp(false));
|
||||
stmt.bind_string_buffer(0, rfc822.get_network_buffer(false));
|
||||
|
||||
int64 id = stmt.exec_insert(cancellable);
|
||||
|
||||
|
|
@ -274,13 +274,11 @@ private class Geary.SmtpOutboxFolder : Geary.AbstractLocalFolder, Geary.FolderSu
|
|||
return row.outbox_id;
|
||||
}
|
||||
|
||||
public virtual async Geary.FolderSupport.Create.Result create_email_async(Geary.RFC822.Message rfc822,
|
||||
Cancellable? cancellable = null) throws Error {
|
||||
public virtual async void create_email_async(Geary.RFC822.Message rfc822, EmailFlags? flags,
|
||||
DateTime? date_received, Cancellable? cancellable = null) throws Error {
|
||||
check_open();
|
||||
|
||||
yield enqueue_email_async(rfc822, cancellable);
|
||||
|
||||
return FolderSupport.Create.Result.CREATED;
|
||||
}
|
||||
|
||||
public override async Gee.List<Geary.Email>? list_email_by_id_async(
|
||||
|
|
|
|||
|
|
@ -536,6 +536,20 @@ private abstract class Geary.ImapEngine.GenericAccount : Geary.AbstractAccount {
|
|||
return yield local.get_search_matches_async(previous_prepared_search_query, ids, cancellable);
|
||||
}
|
||||
|
||||
public override async void create_email_async(FolderPath path, RFC822.Message rfc822, Geary.EmailFlags? flags,
|
||||
DateTime? date_received, Cancellable? cancellable = null) throws Error {
|
||||
// local folders go through normal paths
|
||||
Folder? folder = local_only.get(path);
|
||||
if (folder != null) {
|
||||
yield base.create_email_async(path, rfc822, flags, date_received, cancellable);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// use IMAP APPEND command on remote folders, which doesn't require opening a folder
|
||||
yield remote.create_email_async(path, rfc822, flags, date_received, cancellable);
|
||||
}
|
||||
|
||||
private void on_login_failed(Geary.Credentials? credentials) {
|
||||
do_login_failed_async.begin(credentials);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -341,6 +341,30 @@ private class Geary.Imap.Account : BaseObject {
|
|||
return (list_results.size > 0) ? list_results : null;
|
||||
}
|
||||
|
||||
public async void create_email_async(Geary.FolderPath path, RFC822.Message message,
|
||||
Geary.EmailFlags? flags, DateTime? date_received, Cancellable? cancellable) throws Error {
|
||||
check_open();
|
||||
|
||||
Geary.FolderPath? processed = normalize_inbox(path);
|
||||
if (processed == null)
|
||||
throw new ImapError.INVALID("Invalid path %s", path.to_string());
|
||||
|
||||
MessageFlags? msg_flags = null;
|
||||
if (flags != null) {
|
||||
Imap.EmailFlags imap_flags = Imap.EmailFlags.from_api_email_flags(flags);
|
||||
msg_flags = imap_flags.message_flags;
|
||||
}
|
||||
|
||||
InternalDate? internaldate = null;
|
||||
if (date_received != null)
|
||||
internaldate = new InternalDate.from_date_time(date_received);
|
||||
|
||||
AppendCommand cmd = new AppendCommand(new MailboxSpecifier.from_folder_path(processed, null),
|
||||
msg_flags, internaldate, message.get_network_buffer(false));
|
||||
|
||||
yield send_command_async(cmd, null, null, cancellable);
|
||||
}
|
||||
|
||||
private async StatusResponse send_command_async(Command cmd,
|
||||
Gee.List<MailboxInformation>? list_results, Gee.List<StatusData>? status_results,
|
||||
Cancellable? cancellable) throws Error {
|
||||
|
|
|
|||
|
|
@ -20,6 +20,26 @@ public class Geary.Imap.EmailFlags : Geary.EmailFlags {
|
|||
add(LOAD_REMOTE_IMAGES);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a generic {@link Geary.EmailFlags} to IMAP's internal representation of them.
|
||||
*
|
||||
* If the Geary.EmailFlags cannot be cast to IMAP's version, they're created.
|
||||
*/
|
||||
public static Imap.EmailFlags from_api_email_flags(Geary.EmailFlags api_flags) {
|
||||
Imap.EmailFlags? imap_flags = api_flags as Imap.EmailFlags;
|
||||
if (imap_flags != null)
|
||||
return imap_flags;
|
||||
|
||||
Gee.ArrayList<MessageFlag> msg_flags = new Gee.ArrayList<MessageFlag>();
|
||||
foreach (Geary.NamedFlag named_flag in api_flags.get_all())
|
||||
msg_flags.add(new MessageFlag(named_flag.name));
|
||||
|
||||
if (!api_flags.is_unread())
|
||||
msg_flags.add(MessageFlag.SEEN);
|
||||
|
||||
return new Imap.EmailFlags(new MessageFlags(msg_flags));
|
||||
}
|
||||
|
||||
protected override void notify_added(Gee.Collection<NamedFlag> added) {
|
||||
foreach (NamedFlag flag in added) {
|
||||
if (flag.equal_to(UNREAD))
|
||||
|
|
|
|||
|
|
@ -404,7 +404,7 @@ public class Geary.RFC822.Message : BaseObject {
|
|||
* Returns the {@link Message} as a {@link Memory.Buffer} suitable for in-memory use (i.e.
|
||||
* with native linefeed characters).
|
||||
*/
|
||||
public Memory.Buffer get_body_rfc822_buffer_native() throws RFC822Error {
|
||||
public Memory.Buffer get_native_buffer() throws RFC822Error {
|
||||
return message_to_memory_buffer(false, false);
|
||||
}
|
||||
|
||||
|
|
@ -415,7 +415,7 @@ public class Geary.RFC822.Message : BaseObject {
|
|||
* The buffer can also be dot-stuffed if required. See
|
||||
* [[http://tools.ietf.org/html/rfc2821#section-4.5.2]]
|
||||
*/
|
||||
public Memory.Buffer get_body_rfc822_buffer_smtp(bool dotstuffed) throws RFC822Error {
|
||||
public Memory.Buffer get_network_buffer(bool dotstuffed) throws RFC822Error {
|
||||
return message_to_memory_buffer(true, dotstuffed);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -173,7 +173,7 @@ public class Geary.Smtp.ClientSession {
|
|||
|
||||
// DATA
|
||||
Geary.RFC822.Message email_copy = new Geary.RFC822.Message.without_bcc(email);
|
||||
response = yield cx.send_data_async(email_copy.get_body_rfc822_buffer_smtp(true), true,
|
||||
response = yield cx.send_data_async(email_copy.get_network_buffer(true), true,
|
||||
cancellable);
|
||||
if (!response.code.is_success_completed())
|
||||
response.throw_error("Unable to send message");
|
||||
|
|
|
|||
|
|
@ -142,6 +142,11 @@
|
|||
<object class="GtkAction" id="close"/>
|
||||
<accelerator key="w" modifiers="GDK_CONTROL_MASK"/>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkAction" id="save">
|
||||
<property name="label" translatable="yes">Sa_ve Draft</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<object class="GtkArrow" id="menu arrow">
|
||||
<property name="visible">True</property>
|
||||
|
|
@ -554,10 +559,8 @@
|
|||
</child>
|
||||
<child>
|
||||
<object class="GtkToolItem" id="filler">
|
||||
<property name="use_action_appearance">False</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="use_action_appearance">False</property>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
|
|
@ -568,8 +571,8 @@
|
|||
</child>
|
||||
<child>
|
||||
<object class="GtkToggleToolButton" id="menu button">
|
||||
<property name="related_action">menu</property>
|
||||
<property name="use_action_appearance">False</property>
|
||||
<property name="related_action">menu</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="tooltip_text" translatable="yes" comments="Various options for formatting text">Formatting Menu</property>
|
||||
|
|
@ -665,10 +668,9 @@
|
|||
<child>
|
||||
<object class="GtkButton" id="add_pending_attachments">
|
||||
<property name="label" translatable="yes">_Include Original Attachments</property>
|
||||
<property name="visible">False</property>
|
||||
<property name="no_show_all">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="no_show_all">True</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="use_underline">True</property>
|
||||
|
|
@ -679,6 +681,19 @@
|
|||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="save_draft">
|
||||
<property name="related_action">save</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="Discard">
|
||||
<property name="label">gtk-discard</property>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue