[geary/mjog/mutiple-main-windows: 7/14] Move Application.MainWindow handling from Controller to Client
- From: Michael Gratton <mjog src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [geary/mjog/mutiple-main-windows: 7/14] Move Application.MainWindow handling from Controller to Client
- Date: Mon, 18 Nov 2019 04:32:13 +0000 (UTC)
commit 3ca121f06909d46eedc016fdf659d92561ae2716
Author: Michael Gratton <mike vee net>
Date: Thu Nov 14 12:17:05 2019 +1100
Move Application.MainWindow handling from Controller to Client
Add Application.Client::last_active_main_window, ::get_main_windows and
::get_active_main_window members to allow the controller easy access to
MainWindow instances. Remove the Application.Controller::main_window
property in favour of using these, generalise it to work with with
arbirary numbers of open main windows.
src/client/application/application-client.vala | 78 +++++-
src/client/application/application-controller.vala | 279 +++++++++++----------
.../application/application-main-window.vala | 160 +++++++++---
.../conversation-list/conversation-list-view.vala | 14 +-
src/client/folder-list/folder-list-tree.vala | 2 +
5 files changed, 342 insertions(+), 191 deletions(-)
---
diff --git a/src/client/application/application-client.vala b/src/client/application/application-client.vala
index fb7e4d8e..822799bd 100644
--- a/src/client/application/application-client.vala
+++ b/src/client/application/application-client.vala
@@ -178,6 +178,17 @@ public class Application.Client : Gtk.Application {
get; private set; default = null;
}
+ /**
+ * The last active main window.
+ *
+ * This will be null if no main windows exist, see {@link
+ * get_active_main_window} if you want to be guaranteed an
+ * instance.
+ */
+ public MainWindow? last_active_main_window {
+ get; private set; default = null;
+ }
+
/**
* Manages the autostart desktop file.
*
@@ -335,6 +346,7 @@ public class Application.Client : Gtk.Application {
)
);
this.add_main_option_entries(OPTION_ENTRIES);
+ this.window_removed.connect_after(on_window_removed);
}
public override bool local_command_line(ref unowned string[] args,
@@ -458,6 +470,38 @@ public class Application.Client : Gtk.Application {
}
}
+ /**
+ * Returns a collection of open main windows.
+ */
+ public Gee.Collection<MainWindow> get_main_windows() {
+ var windows = new Gee.LinkedList<MainWindow>();
+ foreach (Gtk.Window window in get_windows()) {
+ MainWindow? main = window as MainWindow;
+ if (main != null) {
+ windows.add(main);
+ }
+ }
+ return windows;
+ }
+
+ /**
+ * Returns the mostly recently active main window or a new instance.
+ *
+ * This returns the value of {@link last_active_main_window} if
+ * not null, else it constructs a new MainWindow instance and
+ * shows it.
+ */
+ public MainWindow get_active_main_window() {
+ MainWindow? active = this.last_active_main_window;
+ if (active == null) {
+ active = new MainWindow(this);
+ this.controller.register_window(active);
+ this.last_active_main_window = active;
+ active.show();
+ }
+ return active;
+ }
+
public void add_window_accelerators(string action,
string[] accelerators,
Variant? param = null) {
@@ -512,17 +556,13 @@ public class Application.Client : Gtk.Application {
public async void show_email(Geary.Folder? folder,
Geary.EmailIdentifier id) {
- yield this.present();
- this.controller.main_window.show_email.begin(
- folder,
- Geary.Collection.single(id),
- true
- );
+ MainWindow main = yield this.present();
+ main.show_email.begin(folder, Geary.Collection.single(id), true);
}
public async void show_folder(Geary.Folder? folder) {
- yield this.present();
- yield this.controller.main_window.select_folder(folder, true);
+ MainWindow main = yield this.present();
+ yield main.select_folder(folder, true);
}
public async void show_inspector() {
@@ -699,11 +739,13 @@ public class Application.Client : Gtk.Application {
withdraw_notification(ERROR_NOTIFICATION_ID);
}
- // Presents a main window. If the controller is not open, opens it
- // first.
- private async void present() {
+ // Presents a main window, opening the controller and window if
+ // needed.
+ private async MainWindow present() {
yield create_controller();
- this.controller.main_window.present();
+ MainWindow main = get_active_main_window();
+ main.present();
+ return main;
}
// Opens the controller
@@ -991,4 +1033,16 @@ public class Application.Client : Gtk.Application {
}
}
+ private void on_window_removed(Gtk.Window window) {
+ MainWindow? main = window as MainWindow;
+ if (main != null) {
+ this.controller.unregister_window(main);
+ if (this.last_active_main_window == main) {
+ this.last_active_main_window = Geary.Collection.get_first(
+ get_main_windows()
+ );
+ }
+ }
+ }
+
}
diff --git a/src/client/application/application-controller.vala
b/src/client/application/application-controller.vala
index 0c5889c7..b094f236 100644
--- a/src/client/application/application-controller.vala
+++ b/src/client/application/application-controller.vala
@@ -30,6 +30,25 @@ internal class Application.Controller : Geary.BaseObject {
);
}
+ /** Determines if folders should be added to main windows. */
+ public static bool should_add_folder(Gee.Collection<Geary.Folder>? all,
+ Geary.Folder folder) {
+ // if folder is openable, add it
+ if (folder.properties.is_openable != Geary.Trillian.FALSE)
+ return true;
+ else if (folder.properties.has_children == Geary.Trillian.FALSE)
+ return false;
+
+ // if folder contains children, we must ensure that there is at least one of the same type
+ Geary.SpecialFolderType type = folder.special_folder_type;
+ foreach (Geary.Folder other in all) {
+ if (other.special_folder_type == type && other.path.parent == folder.path)
+ return true;
+ }
+
+ return false;
+ }
+
/** Determines if the controller is open. */
public bool is_open {
@@ -54,9 +73,6 @@ internal class Application.Controller : Geary.BaseObject {
get; private set; default = new Application.AvatarStore();
}
- /** Default main window */
- public MainWindow main_window { get; private set; }
-
// Primary collection of the application's open accounts
private Gee.Map<Geary.AccountInformation,AccountContext> accounts =
new Gee.HashMap<Geary.AccountInformation,AccountContext>();
@@ -110,8 +126,6 @@ internal class Application.Controller : Geary.BaseObject {
this.application = application;
this.controller_open = cancellable;
- Geary.Engine engine = this.application.engine;
-
// This initializes the IconFactory, important to do before
// the actions are created (as they refer to some of Geary's
// custom icons)
@@ -162,18 +176,6 @@ internal class Application.Controller : Geary.BaseObject {
);
this.plugin_manager.load();
- this.main_window = new MainWindow(application, this);
- this.main_window.retry_service_problem.connect(on_retry_service_problem);
-
- engine.account_available.connect(on_account_available);
-
- // Connect to various UI signals.
- this.main_window.folder_list.set_new_messages_monitor(
- this.plugin_manager.notifications
- );
-
- this.main_window.conversation_list_view.grab_focus();
-
// Migrate configuration if necessary.
try {
Migrate.xdg_config_dir(this.application.get_user_data_directory(),
@@ -196,6 +198,8 @@ internal class Application.Controller : Geary.BaseObject {
error("Error opening libsecret: %s", err.message);
}
+ application.engine.account_available.connect(on_account_available);
+
this.account_manager = new Accounts.Manager(
libsecret,
this.application.get_user_config_directory(),
@@ -222,7 +226,7 @@ internal class Application.Controller : Geary.BaseObject {
// Start the engine and load our accounts
try {
- yield engine.open_async(
+ yield application.engine.open_async(
this.application.get_resource_directory(), cancellable
);
yield this.account_manager.load_accounts(cancellable);
@@ -251,7 +255,9 @@ internal class Application.Controller : Geary.BaseObject {
on_account_available
);
- this.main_window.set_sensitive(false);
+ foreach (MainWindow window in this.application.get_main_windows()) {
+ window.set_sensitive(false);
+ }
// Close any open composers up-front before anything else is
// shut down so any pending operations have a chance to
@@ -289,12 +295,11 @@ internal class Application.Controller : Geary.BaseObject {
// first so they don't block shutdown.
this.controller_open.cancel();
- // Release folder and conversations in the main window
- yield this.main_window.select_folder(null, false);
-
- // hide window while shutting down, as this can take a few
- // seconds under certain conditions
- this.main_window.hide();
+ // Release folder and conversations in main windows
+ foreach (MainWindow window in this.application.get_main_windows()) {
+ yield window.select_folder(null, false);
+ window.hide();
+ }
// Release notification monitoring early so held resources can
// be freed up
@@ -348,9 +353,8 @@ internal class Application.Controller : Geary.BaseObject {
on_account_removed
);
- if (this.main_window != null) {
- this.application.remove_window(this.main_window);
- this.main_window.destroy();
+ foreach (MainWindow window in this.application.get_main_windows()) {
+ window.destroy();
}
this.pending_mailtos.clear();
@@ -365,26 +369,35 @@ internal class Application.Controller : Geary.BaseObject {
* Opens or queues a new composer addressed to a specific email address.
*/
public void compose(string? mailto = null) {
- Geary.Account? selected = this.main_window.selected_account;
- if (selected == null) {
- // Schedule the send for after we have an account open.
- this.pending_mailtos.add(mailto);
- } else {
+ MainWindow? window = this.application.last_active_main_window;
+ if (window != null && window.selected_account != null) {
create_compose_widget(
- selected, NEW_MESSAGE, mailto, null, null, false
+ window,
+ window.selected_account,
+ NEW_MESSAGE,
+ mailto,
+ null,
+ null,
+ false
);
+ } else {
+ // Schedule the send for after we have an account open.
+ this.pending_mailtos.add(mailto);
}
}
/**
* Opens new composer with an existing message as context.
*/
- public void compose_with_context_email(Geary.Account account,
+ public void compose_with_context_email(MainWindow to_show,
+ Geary.Account account,
Composer.Widget.ComposeType type,
Geary.Email context,
string? quote,
bool is_draft) {
- create_compose_widget(account, type, null, context, quote, is_draft);
+ create_compose_widget(
+ to_show, account, type, null, context, quote, is_draft
+ );
}
/** Adds a new composer to be kept track of. */
@@ -470,7 +483,7 @@ internal class Application.Controller : Geary.BaseObject {
!(report.error.thrown is IOError.CANCELLED)) {
MainWindowInfoBar info_bar = new MainWindowInfoBar.for_problem(report);
info_bar.retry.connect(on_retry_problem);
- this.main_window.show_infobar(info_bar);
+ this.application.get_active_main_window().show_infobar(info_bar);
}
Geary.ServiceProblemReport? service_report =
@@ -879,6 +892,17 @@ internal class Application.Controller : Geary.BaseObject {
}
}
+ internal void register_window(MainWindow window) {
+ window.retry_service_problem.connect(on_retry_service_problem);
+ window.folder_list.set_new_messages_monitor(
+ this.plugin_manager.notifications
+ );
+ }
+
+ internal void unregister_window(MainWindow window) {
+ window.retry_service_problem.disconnect(on_retry_service_problem);
+ }
+
/** Returns the first open account, sorted by ordinal. */
internal Geary.AccountInformation? get_first_account() {
return this.accounts.keys.iterator().fold<Geary.AccountInformation?>(
@@ -1048,16 +1072,13 @@ internal class Application.Controller : Geary.BaseObject {
has_cert_error |= context.tls_validation_failed;
}
- foreach (Gtk.Window window in this.application.get_windows()) {
- MainWindow? main = window as MainWindow;
- if (main != null) {
- main.update_account_status(
- effective_status,
- has_auth_error,
- has_cert_error,
- service_problem_source
- );
- }
+ foreach (MainWindow window in this.application.get_main_windows()) {
+ window.update_account_status(
+ effective_status,
+ has_auth_error,
+ has_cert_error,
+ service_problem_source
+ );
}
}
@@ -1184,7 +1205,7 @@ internal class Application.Controller : Geary.BaseObject {
context.tls_validation_prompting = true;
try {
yield this.certificate_manager.prompt_pin_certificate(
- this.main_window,
+ this.application.get_active_main_window(),
context.account.information,
service,
endpoint,
@@ -1212,19 +1233,26 @@ internal class Application.Controller : Geary.BaseObject {
update_account_status();
}
- private void on_account_email_removed(Geary.Folder folder, Gee.Collection<Geary.EmailIdentifier> ids) {
- if (folder.special_folder_type == Geary.SpecialFolderType.OUTBOX) {
- main_window.status_bar.deactivate_message(StatusBar.Message.OUTBOX_SEND_FAILURE);
- main_window.status_bar.deactivate_message(StatusBar.Message.OUTBOX_SAVE_SENT_MAIL_FAILED);
+ private void on_account_email_removed(Geary.Folder folder,
+ Gee.Collection<Geary.EmailIdentifier> ids) {
+ if (folder.special_folder_type == OUTBOX) {
+ foreach (MainWindow window in this.application.get_main_windows()) {
+ window.status_bar.deactivate_message(StatusBar.Message.OUTBOX_SEND_FAILURE);
+ window.status_bar.deactivate_message(StatusBar.Message.OUTBOX_SAVE_SENT_MAIL_FAILED);
+ }
}
}
private void on_sending_started() {
- main_window.status_bar.activate_message(StatusBar.Message.OUTBOX_SENDING);
+ foreach (MainWindow window in this.application.get_main_windows()) {
+ window.status_bar.activate_message(StatusBar.Message.OUTBOX_SENDING);
+ }
}
private void on_sending_finished() {
- main_window.status_bar.deactivate_message(StatusBar.Message.OUTBOX_SENDING);
+ foreach (MainWindow window in this.application.get_main_windows()) {
+ window.status_bar.deactivate_message(StatusBar.Message.OUTBOX_SENDING);
+ }
}
// Returns true if the caller should try opening the account again
@@ -1234,7 +1262,8 @@ internal class Application.Controller : Geary.BaseObject {
// give the user two options: reset the Account local store, or exit Geary. A third
// could be done to leave the Account in an unopened state, but we don't currently
// have provisions for that.
- QuestionDialog dialog = new QuestionDialog(main_window,
+ QuestionDialog dialog = new QuestionDialog(
+ this.application.get_active_main_window(),
_("Unable to open the database for %s").printf(account.information.id),
_("There was an error opening the local mail database for this account. This is possibly due to
corruption of the database file in this directory:\n\n%s\n\nGeary can rebuild the database and re-synchronize
with the server or exit.\n\nRebuilding the database will destroy all local email and its attachments. <b>The
mail on the your server will not be affected.</b>")
.printf(account.information.data_dir.get_path()),
@@ -1246,7 +1275,8 @@ internal class Application.Controller : Geary.BaseObject {
try {
yield account.rebuild_async();
} catch (Error err) {
- ErrorDialog errdialog = new ErrorDialog(main_window,
+ ErrorDialog errdialog = new ErrorDialog(
+ this.application.get_active_main_window(),
_("Unable to rebuild database for ā%sā").printf(account.information.id),
_("Error during rebuild:\n\n%s").printf(err.message));
errdialog.run();
@@ -1287,8 +1317,9 @@ internal class Application.Controller : Geary.BaseObject {
if (did_attempt_open_all_accounts() &&
!this.upgrade_dialog.visible &&
!this.controller_open.is_cancelled() &&
- !this.application.is_background_service)
- main_window.show();
+ !this.application.is_background_service) {
+ this.application.get_active_main_window().present();
+ }
}
/**
@@ -1320,28 +1351,12 @@ internal class Application.Controller : Geary.BaseObject {
private void on_special_folder_type_changed(Geary.Folder folder,
Geary.SpecialFolderType old_type,
Geary.SpecialFolderType new_type) {
- Geary.AccountInformation info = folder.account.information;
-
- // Update the main window
- this.main_window.folder_list.remove_folder(folder);
- this.main_window.folder_list.add_folder(folder);
- // Since removing the folder will also remove its children
- // from the folder list, we need to check for any and re-add
- // them. See issue #11.
- try {
- foreach (Geary.Folder child in
- folder.account.list_matching_folders(folder.path)) {
- main_window.folder_list.add_folder(child);
- }
- } catch (Error err) {
- // Oh well
- }
-
// Update notifications
this.plugin_manager.notifications.remove_folder(folder);
if (folder.special_folder_type == Geary.SpecialFolderType.INBOX ||
(folder.special_folder_type == Geary.SpecialFolderType.NONE &&
is_inbox_descendant(folder))) {
+ Geary.AccountInformation info = folder.account.information;
this.plugin_manager.notifications.add_folder(
folder, this.accounts.get(info).cancellable
);
@@ -1356,32 +1371,20 @@ internal class Application.Controller : Geary.BaseObject {
if (available != null && available.size > 0) {
foreach (Geary.Folder folder in available) {
- if (!should_add_folder(available, folder)) {
+ if (!Controller.should_add_folder(available, folder)) {
continue;
}
- folder.special_folder_type_changed.connect(on_special_folder_type_changed);
- this.main_window.add_folder(folder);
+ folder.special_folder_type_changed.connect(
+ on_special_folder_type_changed
+ );
GLib.Cancellable cancellable = context.cancellable;
switch (folder.special_folder_type) {
case Geary.SpecialFolderType.INBOX:
- // Special case handling of inboxes
if (context.inbox == null) {
context.inbox = folder;
-
- // Select this inbox if there isn't an
- // existing folder selected and it is the
- // inbox for the first account
- if (!this.main_window.folder_list.is_any_selected() &&
- folder.account.information == get_first_account()) {
- // First we try to select the Inboxes branch inbox if
- // it's there, falling back to the main folder list.
- if (!main_window.folder_list.select_inbox(folder.account))
- main_window.folder_list.select_folder(folder);
- }
}
-
- folder.open_async.begin(Geary.Folder.OpenFlags.NO_DELAY, cancellable);
+ folder.open_async.begin(NO_DELAY, cancellable);
// Always notify for new messages in the Inbox
this.plugin_manager.notifications.add_folder(
@@ -1408,8 +1411,9 @@ internal class Application.Controller : Geary.BaseObject {
bool has_prev = unavailable_iterator.last();
while (has_prev) {
Geary.Folder folder = unavailable_iterator.get();
- folder.special_folder_type_changed.disconnect(on_special_folder_type_changed);
- this.main_window.remove_folder(folder);
+ folder.special_folder_type_changed.disconnect(
+ on_special_folder_type_changed
+ );
switch (folder.special_folder_type) {
case Geary.SpecialFolderType.INBOX:
@@ -1438,10 +1442,12 @@ internal class Application.Controller : Geary.BaseObject {
// A monitored folder must be selected to squelch notifications;
// if conversation list is at top of display, don't display
// and don't display if main window has top-level focus
+ MainWindow? window = this.application.last_active_main_window;
return (
- folder != this.main_window.selected_folder ||
- this.main_window.conversation_list_view.vadjustment.value != 0.0 ||
- !this.main_window.has_toplevel_focus
+ window != null &&
+ (folder != window.selected_folder ||
+ window.conversation_list_view.vadjustment.value != 0.0 ||
+ !window.has_toplevel_focus)
);
}
@@ -1449,14 +1455,17 @@ internal class Application.Controller : Geary.BaseObject {
// false and the supplied visible messages are visible in the conversation list view
public void clear_new_messages(string caller,
Gee.Set<Geary.App.Conversation>? supplied) {
- Geary.Folder? selected = this.main_window.selected_folder;
+ MainWindow? window = this.application.last_active_main_window;
+ Geary.Folder? selected = (
+ (window != null) ? window.selected_folder : null
+ );
NotificationContext notifications = this.plugin_manager.notifications;
if (selected != null && (
!notifications.get_folders().contains(selected) ||
should_notify_new_messages(selected))) {
Gee.Set<Geary.App.Conversation> visible =
- supplied ?? main_window.conversation_list_view.get_visible_conversations();
+ supplied ?? window.conversation_list_view.get_visible_conversations();
foreach (Geary.App.Conversation conversation in visible) {
try {
@@ -1475,8 +1484,14 @@ internal class Application.Controller : Geary.BaseObject {
/** Displays a composer on the last active main window. */
internal void show_composer(Composer.Widget composer,
- Gee.Collection<Geary.EmailIdentifier>? refers_to) {
- this.main_window.show_composer(composer, refers_to);
+ Gee.Collection<Geary.EmailIdentifier>? refers_to,
+ MainWindow? show_on) {
+ var target = show_on;
+ if (target == null) {
+ target = this.application.get_active_main_window();
+ }
+
+ target.show_composer(composer, refers_to);
composer.set_focus();
}
@@ -1506,7 +1521,8 @@ internal class Application.Controller : Geary.BaseObject {
* @param is_draft - Whether we're starting from a draft (true) or
* a new mail (false)
*/
- private void create_compose_widget(Geary.Account account,
+ private void create_compose_widget(MainWindow show_on,
+ Geary.Account account,
Composer.Widget.ComposeType compose_type,
string? mailto,
Geary.Email? referred,
@@ -1516,22 +1532,24 @@ internal class Application.Controller : Geary.BaseObject {
// composer, check for these first.
if (compose_type == NEW_MESSAGE && !is_draft) {
// We're creating a new message that isn't a draft, if
- // there's already a composer open, just use that
- Composer.Widget? existing =
- this.main_window.conversation_viewer.current_composer;
- if (existing != null &&
- existing.current_mode == PANED &&
- existing.is_blank) {
- existing.present();
- return;
+ // there's already an empty composer open, just use
+ // that
+ foreach (Composer.Widget existing in this.composer_widgets) {
+ if (existing != null &&
+ existing.current_mode == PANED &&
+ existing.is_blank) {
+ existing.present();
+ return;
+ }
}
} else if (compose_type != NEW_MESSAGE && referred != null) {
// A reply/forward was requested, see whether there is
- // already an inline message that is either a
- // reply/forward for that message, or there is a quote
- // to insert into it.
+ // already an inline message in the target window that is
+ // either a reply/forward for that message, or there is a
+ // quote to insert into it.
foreach (Composer.Widget existing in this.composer_widgets) {
- if ((existing.current_mode == INLINE ||
+ if (existing.get_toplevel() == show_on &&
+ (existing.current_mode == INLINE ||
existing.current_mode == INLINE_COMPACT) &&
(referred.id in existing.get_referred_ids() ||
quote != null)) {
@@ -1549,7 +1567,7 @@ internal class Application.Controller : Geary.BaseObject {
// new one. Replies must open inline in the main window,
// so we need to ensure there are no composers open there
// first.
- if (!this.main_window.close_composer(true)) {
+ if (!show_on.close_composer(true)) {
return;
}
}
@@ -1568,7 +1586,8 @@ internal class Application.Controller : Geary.BaseObject {
add_composer(widget);
show_composer(
widget,
- referred != null ? Geary.Collection.single(referred.id) : null
+ referred != null ? Geary.Collection.single(referred.id) : null,
+ show_on
);
this.load_composer.begin(
@@ -1629,7 +1648,9 @@ internal class Application.Controller : Geary.BaseObject {
).printf(Util.Email.to_short_recipient_display(sent));
Components.InAppNotification notification =
new Components.InAppNotification(message);
- this.main_window.add_notification(notification);
+ foreach (MainWindow window in this.application.get_main_windows()) {
+ window.add_notification(notification);
+ }
AccountContext? context = this.accounts.get(service.account);
if (context != null) {
@@ -1637,24 +1658,6 @@ internal class Application.Controller : Geary.BaseObject {
}
}
- private bool should_add_folder(Gee.Collection<Geary.Folder>? all,
- Geary.Folder folder) {
- // if folder is openable, add it
- if (folder.properties.is_openable != Geary.Trillian.FALSE)
- return true;
- else if (folder.properties.has_children == Geary.Trillian.FALSE)
- return false;
-
- // if folder contains children, we must ensure that there is at least one of the same type
- Geary.SpecialFolderType type = folder.special_folder_type;
- foreach (Geary.Folder other in all) {
- if (other.special_folder_type == type && other.path.parent == folder.path)
- return true;
- }
-
- return false;
- }
-
private Gee.Collection<Geary.EmailIdentifier>
to_in_folder_email_ids(Gee.Collection<Geary.App.Conversation> conversations) {
Gee.Collection<Geary.EmailIdentifier> messages =
@@ -2730,7 +2733,7 @@ private class Application.SendComposerCommand : ComposerCommand {
this.saved = null;
this.composer.set_enabled(true);
- this.application.controller.show_composer(this.composer, null);
+ this.application.controller.show_composer(this.composer, null, null);
clear_composer();
}
@@ -2784,7 +2787,7 @@ private class Application.SaveComposerCommand : ComposerCommand {
if (this.composer != null) {
this.destroy_timer.reset();
this.composer.set_enabled(true);
- this.controller.show_composer(this.composer, null);
+ this.controller.show_composer(this.composer, null, null);
clear_composer();
} else {
/// Translators: A label for an in-app notification.
@@ -2842,7 +2845,7 @@ private class Application.DiscardComposerCommand : ComposerCommand {
if (this.composer != null) {
this.destroy_timer.reset();
this.composer.set_enabled(true);
- this.controller.show_composer(this.composer, null);
+ this.controller.show_composer(this.composer, null, null);
clear_composer();
} else {
/// Translators: A label for an in-app notification.
diff --git a/src/client/application/application-main-window.vala
b/src/client/application/application-main-window.vala
index b262a3b0..fcf643e9 100644
--- a/src/client/application/application-main-window.vala
+++ b/src/client/application/application-main-window.vala
@@ -274,7 +274,7 @@ public class Application.MainWindow :
public signal void retry_service_problem(Geary.ClientService.Status problem);
- internal MainWindow(Client application, Controller controller) {
+ internal MainWindow(Client application) {
Object(
application: application,
show_menubar: false
@@ -306,17 +306,18 @@ public class Application.MainWindow :
this.update_ui_timeout.repetition = FOREVER;
// Add future and existing accounts to the main window
- controller.account_available.connect(
+ this.application.controller.account_available.connect(
on_account_available
);
- controller.account_unavailable.connect(
+ this.application.controller.account_unavailable.connect(
on_account_unavailable
);
- foreach (AccountContext context in controller.get_account_contexts()) {
+ foreach (AccountContext context in
+ this.application.controller.get_account_contexts()) {
add_account(context);
}
- this.main_layout.show_all();
+ this.conversation_list_view.grab_focus();
}
~MainWindow() {
@@ -687,24 +688,6 @@ public class Application.MainWindow :
}
}
- /** Adds a folder to the window. */
- internal void add_folder(Geary.Folder to_add) {
- this.folder_list.add_folder(to_add);
- if (to_add.account == this.selected_account) {
- this.main_toolbar.copy_folder_menu.add_folder(to_add);
- this.main_toolbar.move_folder_menu.add_folder(to_add);
- }
- }
-
- /** Removes a folder from the window. */
- internal void remove_folder(Geary.Folder to_remove) {
- if (to_remove.account == this.selected_account) {
- this.main_toolbar.copy_folder_menu.remove_folder(to_remove);
- this.main_toolbar.move_folder_menu.remove_folder(to_remove);
- }
- this.folder_list.remove_folder(to_remove);
- }
-
private void add_account(AccountContext to_add) {
if (!this.accounts.contains(to_add)) {
this.folder_list.set_user_folders_root_name(
@@ -723,6 +706,15 @@ public class Application.MainWindow :
to_add.commands.undone.connect(on_command_undo);
to_add.commands.redone.connect(on_command_redo);
+ to_add.account.folders_available_unavailable.connect(
+ on_folders_available_unavailable
+ );
+
+ folders_available(
+ to_add.account,
+ Geary.Account.sort_by_path(to_add.account.list_folders())
+ );
+
this.accounts.add(to_add);
}
}
@@ -755,6 +747,10 @@ public class Application.MainWindow :
}
}
+ to_remove.account.folders_available_unavailable.disconnect(
+ on_folders_available_unavailable
+ );
+
to_remove.commands.executed.disconnect(on_command_execute);
to_remove.commands.undone.disconnect(on_command_undo);
to_remove.commands.redone.disconnect(on_command_redo);
@@ -773,6 +769,30 @@ public class Application.MainWindow :
}
}
+ /** Adds a folder to the window. */
+ private void add_folder(Geary.Folder to_add) {
+ this.folder_list.add_folder(to_add);
+ if (to_add.account == this.selected_account) {
+ this.main_toolbar.copy_folder_menu.add_folder(to_add);
+ this.main_toolbar.move_folder_menu.add_folder(to_add);
+ }
+ to_add.special_folder_type_changed.connect(
+ on_special_folder_type_changed
+ );
+ }
+
+ /** Removes a folder from the window. */
+ private void remove_folder(Geary.Folder to_remove) {
+ to_remove.special_folder_type_changed.disconnect(
+ on_special_folder_type_changed
+ );
+ if (to_remove.account == this.selected_account) {
+ this.main_toolbar.copy_folder_menu.remove_folder(to_remove);
+ this.main_toolbar.move_folder_menu.remove_folder(to_remove);
+ }
+ this.folder_list.remove_folder(to_remove);
+ }
+
private AccountContext? get_selected_account_context() {
AccountContext? context = null;
if (this.selected_account != null) {
@@ -962,13 +982,6 @@ public class Application.MainWindow :
this.status_bar.add(this.spinner);
}
- // Returns true when there's a conversation list scrollbar visible, i.e. the list is tall
- // enough to need one. Otherwise returns false.
- public bool conversation_list_has_scrollbar() {
- Gtk.Scrollbar? scrollbar = this.conversation_list_scrolled.get_vscrollbar() as Gtk.Scrollbar;
- return scrollbar != null && scrollbar.get_visible();
- }
-
/** {@inheritDoc} */
public override bool key_press_event(Gdk.EventKey event) {
check_shift_event(event);
@@ -1272,6 +1285,44 @@ public class Application.MainWindow :
}
}
+ private void folders_available(Geary.Account account,
+ Gee.BidirSortedSet<Geary.Folder> available) {
+ foreach (Geary.Folder folder in available) {
+ if (Controller.should_add_folder(available, folder)) {
+ add_folder(folder);
+
+ if (folder.special_folder_type == INBOX) {
+ // Select this inbox if there isn't an existing
+ // folder selected and it is the inbox for the
+ // first account
+ Geary.AccountInformation? first_account =
+ this.application.controller.get_first_account();
+ if (!this.folder_list.is_any_selected() &&
+ folder.account.information == first_account) {
+ // First we try to select the Inboxes branch
+ // inbox if it's there, falling back to the
+ // main folder list.
+ if (!this.folder_list.select_inbox(folder.account)) {
+ this.folder_list.select_folder(folder);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private void folders_unavailable(Geary.Account account,
+ Gee.BidirSortedSet<Geary.Folder> unavailable) {
+ var unavailable_iterator = unavailable.bidir_iterator();
+ bool has_prev = unavailable_iterator.last();
+ while (has_prev) {
+ Geary.Folder folder = unavailable_iterator.get();
+ remove_folder(folder);
+
+ has_prev = unavailable_iterator.previous();
+ }
+ }
+
private async void open_conversation_monitor(Geary.App.ConversationMonitor to_open,
GLib.Cancellable cancellable) {
to_open.scan_completed.connect(on_scan_completed);
@@ -1331,6 +1382,7 @@ public class Application.MainWindow :
email_view.get_selection_for_quoting.begin((obj, res) => {
string? quote = email_view.get_selection_for_quoting.end(res);
this.application.controller.compose_with_context_email(
+ this,
account,
compose_type,
email_view.email,
@@ -1576,8 +1628,11 @@ public class Application.MainWindow :
private void on_scan_completed(Geary.App.ConversationMonitor monitor) {
// Done scanning. Check if we have enough messages to fill
// the conversation list; if not, trigger a load_more();
+ Gtk.Scrollbar? scrollbar = (
+ this.conversation_list_scrolled.get_vscrollbar() as Gtk.Scrollbar
+ );
if (is_visible() &&
- !conversation_list_has_scrollbar() &&
+ (scrollbar == null || !scrollbar.get_visible()) &&
monitor == this.conversations &&
monitor.can_load_more) {
debug("Not enough messages, loading more for folder %s",
@@ -1690,6 +1745,40 @@ public class Application.MainWindow :
this.remove_account.begin(account, to_select);
}
+
+ private void on_folders_available_unavailable(
+ Geary.Account account,
+ Gee.BidirSortedSet<Geary.Folder>? available,
+ Gee.BidirSortedSet<Geary.Folder>? unavailable
+ ) {
+ if (available != null) {
+ folders_available(account, available);
+ }
+ if (unavailable != null) {
+ folders_unavailable(account, unavailable);
+ }
+ }
+
+ private void on_special_folder_type_changed(Geary.Folder folder,
+ Geary.SpecialFolderType old_type,
+ Geary.SpecialFolderType new_type) {
+ // Update the main window
+ this.folder_list.remove_folder(folder);
+ this.folder_list.add_folder(folder);
+
+ // Since removing the folder will also remove its children
+ // from the folder list, we need to check for any and re-add
+ // them. See issue #11.
+ try {
+ foreach (Geary.Folder child in
+ folder.account.list_matching_folders(folder.path)) {
+ this.folder_list.add_folder(child);
+ }
+ } catch (Error err) {
+ // Oh well
+ }
+ }
+
private void on_command_execute(Command command) {
if (!(command is TrivialCommand)) {
// Only show an execute notification for non-trivial
@@ -1801,6 +1890,7 @@ public class Application.MainWindow :
if (!already_open) {
this.application.controller.compose_with_context_email(
+ this,
activated.base_folder.account,
NEW_MESSAGE,
draft,
@@ -2191,7 +2281,7 @@ public class Application.MainWindow :
Geary.Account? account = this.selected_account;
if (account != null) {
this.application.controller.compose_with_context_email(
- account, REPLY, target, quote, false
+ this, account, REPLY, target, quote, false
);
}
}
@@ -2200,7 +2290,7 @@ public class Application.MainWindow :
Geary.Account? account = this.selected_account;
if (account != null) {
this.application.controller.compose_with_context_email(
- account, REPLY_ALL, target, quote, false
+ this, account, REPLY_ALL, target, quote, false
);
}
}
@@ -2209,7 +2299,7 @@ public class Application.MainWindow :
Geary.Account? account = this.selected_account;
if (account != null) {
this.application.controller.compose_with_context_email(
- account, FORWARD, target, quote, false
+ this, account, FORWARD, target, quote, false
);
}
}
@@ -2218,7 +2308,7 @@ public class Application.MainWindow :
Geary.Account? account = this.selected_account;
if (account != null) {
this.application.controller.compose_with_context_email(
- account, NEW_MESSAGE, target, null, true
+ this, account, NEW_MESSAGE, target, null, true
);
}
}
diff --git a/src/client/conversation-list/conversation-list-view.vala
b/src/client/conversation-list/conversation-list-view.vala
index fb90cd09..2697554e 100644
--- a/src/client/conversation-list/conversation-list-view.vala
+++ b/src/client/conversation-list/conversation-list-view.vala
@@ -47,9 +47,10 @@ public class ConversationListView : Gtk.TreeView, Geary.BaseInterface {
Gtk.TreeSelection selection = get_selection();
selection.set_mode(Gtk.SelectionMode.MULTIPLE);
style_updated.connect(on_style_changed);
- show.connect(on_show);
row_activated.connect(on_row_activated);
+ notify["vadjustment"].connect(on_vadjustment_changed);
+
button_press_event.connect(on_button_press);
// Set up drag and drop.
@@ -72,6 +73,8 @@ public class ConversationListView : Gtk.TreeView, Geary.BaseInterface {
this.selection_update = new Geary.IdleManager(do_selection_changed);
this.selection_update.priority = Geary.IdleManager.Priority.LOW;
+
+ this.visible = true;
}
~ConversationListView() {
@@ -415,11 +418,6 @@ public class ConversationListView : Gtk.TreeView, Geary.BaseInterface {
schedule_visible_conversations_changed();
}
- private void on_show() {
- // Wait until we're visible to set this signal up.
- ((Gtk.Scrollable) this).get_vadjustment().value_changed.connect(on_value_changed);
- }
-
private void on_value_changed() {
if (this.enable_load_more) {
check_load_more();
@@ -584,4 +582,8 @@ public class ConversationListView : Gtk.TreeView, Geary.BaseInterface {
}
+ private void on_vadjustment_changed() {
+ this.vadjustment.value_changed.connect(on_value_changed);
+ }
+
}
diff --git a/src/client/folder-list/folder-list-tree.vala b/src/client/folder-list/folder-list-tree.vala
index 1c164f0f..95d4da63 100644
--- a/src/client/folder-list/folder-list-tree.vala
+++ b/src/client/folder-list/folder-list-tree.vala
@@ -38,6 +38,8 @@ public class FolderList.Tree : Sidebar.Tree, Geary.BaseInterface {
unowned Gtk.BindingSet? binding_set = Gtk.BindingSet.find("GtkTreeView");
assert(binding_set != null);
Gtk.BindingEntry.remove(binding_set, Gdk.Key.N, Gdk.ModifierType.CONTROL_MASK);
+
+ this.visible = true;
}
~Tree() {
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]