Warn user before exiting if composer window has unsent text: Closes #5279

YorbaApplication's 'exiting' signal now allows callbacks to cancel the
edit. GearyController now keeps a list of all open ComposerWindows, and
listens for YorbaApplication's 'exiting' signal. When the signal is
received, GearyController warns the user about each open ComposerWindow
with unsent text. If the user cancels the closing of any of these
ComposerWindows, GearyController cancels the edit.
This commit is contained in:
Matthew Pirocchi 2012-05-22 14:04:24 -07:00
parent 9a64661f2c
commit 883e45577b
6 changed files with 77 additions and 6 deletions

View file

@ -263,9 +263,11 @@ along with Geary; if not, write to the Free Software Foundation, Inc.,
}
}
public override void exiting(bool panicked) {
public override bool exiting(bool panicked) {
if (controller.main_window != null)
controller.main_window.destroy();
return true;
}
public File get_user_data_directory() {

View file

@ -71,6 +71,7 @@ public class GearyController {
private Geary.Conversation? last_deleted_conversation = null;
private bool scan_in_progress = false;
private int conversations_added_counter = 0;
private Gee.LinkedList<ComposerWindow> composer_windows = new Gee.LinkedList<ComposerWindow>();
public GearyController() {
// Setup actions.
@ -80,6 +81,9 @@ public class GearyController {
GearyApplication.instance.load_ui_file("accelerators.ui");
GearyApplication.instance.config.display_preview_changed.connect(on_display_preview_changed);
// Listen for attempts to close the application.
GearyApplication.instance.exiting.connect(on_application_exiting);
// Create the main window (must be done after creating actions.)
main_window = new MainWindow();
@ -695,6 +699,15 @@ public class GearyController {
old_cancellable.cancel();
}
// We need to include the second parameter, or valac doesn't recognize the function as matching
// YorbaApplication.exiting's signature.
private bool on_application_exiting(YorbaApplication sender, bool panicked) {
if (close_composition_windows())
return true;
return sender.cancel_exit();
}
public void on_quit() {
GearyApplication.instance.exit();
}
@ -911,14 +924,41 @@ public class GearyController {
}
}
private bool close_composition_windows() {
// We want to allow the user to cancel a quit when they have unsent text.
// We are modifying the list as we go, so we can't simply iterate through it.
while (composer_windows.size > 0) {
ComposerWindow composer_window = composer_windows.first();
if (!composer_window.should_close())
return false;
// This will remove composer_window from composer_windows.
// See GearyController.on_composer_window_destroy.
composer_window.destroy();
}
// If we deleted all composer windows without the user cancelling, we can exit.
return true;
}
private void create_compose_window(Geary.ComposedEmail? prefill = null) {
ComposerWindow w = new ComposerWindow(prefill);
w.set_position(Gtk.WindowPosition.CENTER);
w.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.
composer_windows.add(w);
w.destroy.connect(on_composer_window_destroy);
w.show_all();
}
private void on_composer_window_destroy(Gtk.Widget sender) {
composer_windows.remove((ComposerWindow) sender);
}
private void on_new_message() {
create_compose_window();
}

View file

@ -345,7 +345,7 @@ public class ComposerWindow : Gtk.Window {
base.show_all();
}
private bool should_close() {
public bool should_close() {
// TODO: Check if the message was (automatically) saved
if (editor.can_undo()) {
var dialog = new Gtk.MessageDialog(this, 0,

View file

@ -34,6 +34,8 @@ public class MainWindow : Gtk.Window {
pixbuf_list.append(IconFactory.instance.geary);
set_default_icon_list(pixbuf_list);
delete_event.connect(on_delete_event);
create_layout();
}
@ -62,9 +64,13 @@ public class MainWindow : Gtk.Window {
GearyApplication.instance.config.messages_pane_position = messages_paned.get_position();
}
base.destroy();
}
private bool on_delete_event() {
GearyApplication.instance.exit();
base.destroy();
return true;
}
public override bool configure_event(Gdk.EventConfigure event) {

View file

@ -42,8 +42,10 @@ public class PreferencesDialog : Object {
}
}
private void on_exit(bool panicked) {
private bool on_exit(bool panicked) {
dialog.destroy();
return true;
}
private void on_autoselect_toggled() {

View file

@ -51,7 +51,14 @@ public abstract class YorbaApplication {
public virtual signal void activate(string[] args) {
}
public virtual signal void exiting(bool panicked) {
/**
* Signal that is activated when 'exit' is called, but before the application actually exits.
*
* To cancel an exit, a callback should return YorbaApplication.cancel_exit(). To procede with
* an exit, a callback should return true.
*/
public virtual signal bool exiting(bool panicked) {
return true;
}
/**
@ -143,7 +150,12 @@ public abstract class YorbaApplication {
this.exitcode = exitcode;
exiting_fired = true;
exiting(false);
if (!exiting(false)) {
exiting_fired = false;
this.exitcode = 0;
return;
}
if (Gtk.main_level() > 0)
Gtk.main_quit();
@ -151,6 +163,15 @@ public abstract class YorbaApplication {
Posix.exit(exitcode);
}
/**
* A callback for GearyApplication.exiting should return cancel_exit() to prevent the
* application from exiting.
*/
public bool cancel_exit() {
Signal.stop_emission_by_name(this, "exiting");
return false;
}
// This call will fire "exiting" only if it's not already been fired and halt the application
// in its tracks.
public void panic() {