From 776eebd48e13148726a309f44f0cd49ea6e33029 Mon Sep 17 00:00:00 2001 From: Niels De Graef Date: Wed, 23 Nov 2016 00:58:05 +0100 Subject: [PATCH] Clean up MainWindow. Bug 775006. * Use GtkTemplate to simplify layout. * Prefix instance variables with this. * Extract helper methods for clarity. Signed-off-by: Niels De Graef --- po/POTFILES.in | 1 + src/client/components/main-window.vala | 393 +++++++++++-------------- ui/CMakeLists.txt | 1 + ui/main-window.ui | 132 +++++++++ 4 files changed, 314 insertions(+), 213 deletions(-) create mode 100644 ui/main-window.ui diff --git a/po/POTFILES.in b/po/POTFILES.in index e07a56a7..8ecfb1a4 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -398,6 +398,7 @@ src/mailer/main.vala [type: gettext/glade]ui/gtk/menus.ui [type: gettext/glade]ui/login.glade [type: gettext/glade]ui/main-toolbar.ui +[type: gettext/glade]ui/main-window.ui [type: gettext/glade]ui/password-dialog.glade [type: gettext/glade]ui/preferences.glade [type: gettext/glade]ui/remove_confirm.glade diff --git a/src/client/components/main-window.vala b/src/client/components/main-window.vala index cb072bca..6dbf84ed 100644 --- a/src/client/components/main-window.vala +++ b/src/client/components/main-window.vala @@ -6,52 +6,70 @@ * (version 2.1 or later). See the COPYING file in this distribution. */ +[GtkTemplate (ui = "/org/gnome/Geary/main-window.ui")] public class MainWindow : Gtk.ApplicationWindow { - private const int MESSAGE_LIST_WIDTH = 250; - private const int FOLDER_LIST_WIDTH = 100; private const int STATUS_BAR_HEIGHT = 18; - - /// Fired when the shift key is pressed or released. + + /** Fired when the shift key is pressed or released. */ public signal void on_shift_key(bool pressed); - - public FolderList.Tree folder_list { get; private set; default = new FolderList.Tree(); } - public MainToolbar main_toolbar { get; private set; } - public SearchBar search_bar { get; private set; default = new SearchBar(); } - public ConversationListView conversation_list_view { get; private set; default = new ConversationListView(); } - public ConversationViewer conversation_viewer { get; private set; } - public StatusBar status_bar { get; private set; default = new StatusBar(); } + public Geary.Folder? current_folder { get; private set; default = null; } - public Configuration config { get; private set; } - + + private Geary.AggregateProgressMonitor progress_monitor = new Geary.AggregateProgressMonitor(); + private Geary.ProgressMonitor? folder_progress = null; + + // Used to save/load the window state between sessions. public int window_width { get; set; } public int window_height { get; set; } public bool window_maximized { get; set; } - private Gtk.Paned folder_paned = new Gtk.Paned(Gtk.Orientation.HORIZONTAL); - private Gtk.Paned conversations_paned = new Gtk.Paned(Gtk.Orientation.HORIZONTAL); - - private Gtk.ScrolledWindow conversation_list_scrolled; + // Widget descendants + public FolderList.Tree folder_list { get; private set; default = new FolderList.Tree(); } + public MainToolbar main_toolbar { get; private set; } + public SearchBar search_bar { get; private set; default = new SearchBar(); } + public ConversationListView conversation_list_view { get; private set; default = new ConversationListView(); } + public ConversationViewer conversation_viewer { get; private set; default = new ConversationViewer(); } + public StatusBar status_bar { get; private set; default = new StatusBar(); } private MonitoredSpinner spinner = new MonitoredSpinner(); + [GtkChild] + private Gtk.Box search_bar_box; + [GtkChild] + private Gtk.Paned folder_paned; + [GtkChild] + private Gtk.Paned conversations_paned; + [GtkChild] private Gtk.Box folder_box; + [GtkChild] + private Gtk.ScrolledWindow folder_list_scrolled; + [GtkChild] private Gtk.Box conversation_box; - private Geary.AggregateProgressMonitor progress_monitor = new Geary.AggregateProgressMonitor(); - private Geary.ProgressMonitor? folder_progress = null; - + [GtkChild] + private Gtk.ScrolledWindow conversation_list_scrolled; + public MainWindow(GearyApplication application) { Object(application: application); - set_show_menubar(false); - add_events(Gdk.EventMask.KEY_PRESS_MASK | Gdk.EventMask.KEY_RELEASE_MASK - | Gdk.EventMask.FOCUS_CHANGE_MASK); + load_config(application.config); + restore_saved_window_state(); - config = application.config; - conversation_viewer = new ConversationViewer(); + add_accel_group(application.ui_manager.get_accel_group()); - // This code both loads AND saves the pane positions with live - // updating. This is more resilient against crashes because - // the value in dconf changes *immediately*, and stays saved - // in the event of a crash. - config.bind(Configuration.MESSAGES_PANE_POSITION_KEY, conversations_paned, "position"); + application.controller.notify[GearyController.PROP_CURRENT_CONVERSATION] + .connect(on_conversation_monitor_changed); + application.controller.folder_selected.connect(on_folder_selected); + Geary.Engine.instance.account_available.connect(on_account_available); + Geary.Engine.instance.account_unavailable.connect(on_account_unavailable); + + set_styling(); + setup_layout(application.config); + on_change_orientation(); + } + + private void load_config(Configuration config) { + // This code both loads AND saves the pane positions with live updating. This is more + // resilient against crashes because the value in dconf changes *immediately*, and + // stays saved in the event of a crash. + config.bind(Configuration.MESSAGES_PANE_POSITION_KEY, this.conversations_paned, "position"); config.bind(Configuration.WINDOW_WIDTH_KEY, this, "window-width"); config.bind(Configuration.WINDOW_HEIGHT_KEY, this, "window-height"); config.bind(Configuration.WINDOW_MAXIMIZE_KEY, this, "window-maximized"); @@ -60,8 +78,11 @@ public class MainWindow : Gtk.ApplicationWindow { config.folder_list_pane_position_horizontal = config.folder_list_pane_position_old; config.messages_pane_position += config.folder_list_pane_position_old; } + config.settings.changed[Configuration.FOLDER_LIST_PANE_HORIZONTAL_KEY] + .connect(on_change_orientation); + } - // Restore saved window state + private void restore_saved_window_state() { Gdk.Screen? screen = get_screen(); if (screen != null && this.window_width <= screen.get_width() && @@ -71,61 +92,10 @@ public class MainWindow : Gtk.ApplicationWindow { if (this.window_maximized) { maximize(); } - set_position(Gtk.WindowPosition.CENTER); - - add_accel_group(GearyApplication.instance.ui_manager.get_accel_group()); - - spinner.set_progress_monitor(progress_monitor); - - delete_event.connect(on_delete_event); - key_press_event.connect(on_key_press_event); - key_release_event.connect(on_key_release_event); - focus_in_event.connect(on_focus_event); - GearyApplication.instance.config.settings.changed[ - Configuration.FOLDER_LIST_PANE_HORIZONTAL_KEY].connect(on_change_orientation); - GearyApplication.instance.controller.notify[GearyController.PROP_CURRENT_CONVERSATION]. - connect(on_conversation_monitor_changed); - GearyApplication.instance.controller.folder_selected.connect(on_folder_selected); - Geary.Engine.instance.account_available.connect(on_account_available); - Geary.Engine.instance.account_unavailable.connect(on_account_unavailable); - - // Toolbar. - main_toolbar = new MainToolbar(config); - main_toolbar.bind_property("search-open", search_bar, "search-mode-enabled", - BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL); - main_toolbar.bind_property("find-open", conversation_viewer.conversation_find_bar, "search-mode-enabled", - BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL); - if (config.desktop_environment == Configuration.DesktopEnvironment.UNITY) { - BindingTransformFunc title_func = (binding, source, ref target) => { - string folder = current_folder != null ? current_folder.get_display_name() + " " : ""; - string account = main_toolbar.account != null ? "(%s)".printf(main_toolbar.account) : ""; - - target = "%s%s - %s".printf(folder, account, GearyApplication.NAME); - - return true; - }; - bind_property("current-folder", this, "title", BindingFlags.SYNC_CREATE, title_func); - main_toolbar.bind_property("account", this, "title", BindingFlags.SYNC_CREATE, title_func); - } else { - main_toolbar.show_close_button = true; - set_titlebar(main_toolbar); - } - - set_styling(); - create_layout(); - on_change_orientation(); + this.window_position = Gtk.WindowPosition.CENTER; } - private bool on_delete_event() { - if (Args.hidden_startup || GearyApplication.instance.config.startup_notifications) - return hide_on_delete(); - - GearyApplication.instance.exit(); - - return true; - } - - // Fired on [un]maximize and possibly others. Save maximized state + // Called on [un]maximize and possibly others. Save maximized state // for the next start. public override bool window_state_event(Gdk.EventWindowState event) { if ((event.new_window_state & Gdk.WindowState.WITHDRAWN) == 0) { @@ -139,7 +109,7 @@ public class MainWindow : Gtk.ApplicationWindow { return base.window_state_event(event); } - // Fired on window resize. Save window size for the next start. + // Called on window resize. Save window size for the next start. public override void size_allocate(Gtk.Allocation allocation) { base.size_allocate(allocation); @@ -177,96 +147,53 @@ public class MainWindow : Gtk.ApplicationWindow { }); provider.load_from_resource(@"/org/gnome/Geary/geary.css"); } - - private void create_layout() { - Gtk.Box main_layout = new Gtk.Box(Gtk.Orientation.VERTICAL, 0); - - // folder list - Gtk.ScrolledWindow folder_list_scrolled = new Gtk.ScrolledWindow(null, null); - folder_list_scrolled.set_size_request(FOLDER_LIST_WIDTH, -1); - folder_list_scrolled.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC); - folder_list_scrolled.add(folder_list); - Gtk.Frame folder_frame = new Gtk.Frame(null); - folder_frame.shadow_type = Gtk.ShadowType.IN; - folder_frame.get_style_context ().add_class("geary-folder-frame"); - folder_frame.add(folder_list_scrolled); - folder_box = new Gtk.Box(Gtk.Orientation.VERTICAL, 0); - folder_box.pack_start(folder_frame, true, true); - - // message list - conversation_list_scrolled = new Gtk.ScrolledWindow(null, null); - conversation_list_scrolled.set_size_request(MESSAGE_LIST_WIDTH, -1); - conversation_list_scrolled.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC); - conversation_list_scrolled.add(conversation_list_view); - Gtk.Frame conversation_frame = new Gtk.Frame(null); - conversation_frame.shadow_type = Gtk.ShadowType.IN; - conversation_frame.get_style_context ().add_class("geary-conversation-frame"); - conversation_frame.add(conversation_list_scrolled); - conversation_box = new Gtk.Box(Gtk.Orientation.VERTICAL, 0); - conversation_box.pack_start(conversation_frame, true, true); - - // Three-pane display. - status_bar.set_size_request(-1, STATUS_BAR_HEIGHT); - status_bar.set_border_width(2); - spinner.set_size_request(STATUS_BAR_HEIGHT - 2, -1); - status_bar.add(spinner); - - folder_paned.get_style_context().add_class("geary-sidebar-pane-separator"); - - // Folder list to the left of everything. - folder_paned.pack1(folder_box, false, false); - folder_paned.pack2(conversation_box, true, false); - - Gtk.Box search_bar_box = new Gtk.Box(Gtk.Orientation.VERTICAL, 0); - search_bar_box.pack_start(search_bar, false, false, 0); - search_bar_box.pack_start(folder_paned); - search_bar_box.get_style_context().add_class(Gtk.STYLE_CLASS_SIDEBAR); - - // Message list left of message viewer. - conversations_paned.pack1(search_bar_box, false, false); - conversations_paned.pack2(conversation_viewer, true, true); + private void setup_layout(Configuration config) { + // Toolbar + this.main_toolbar = new MainToolbar(config); + this.main_toolbar.bind_property("search-open", this.search_bar, "search-mode-enabled", + BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL); + this.main_toolbar.bind_property("find-open", this.conversation_viewer.conversation_find_bar, + "search-mode-enabled", BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL); if (config.desktop_environment == Configuration.DesktopEnvironment.UNITY) { - main_layout.pack_start(main_toolbar, false, true, 0); + BindingTransformFunc title_func = (binding, source, ref target) => { + string folder = current_folder != null ? current_folder.get_display_name() + " " : ""; + string account = main_toolbar.account != null ? "(%s)".printf(main_toolbar.account) : ""; + + target = "%s%s - %s".printf(folder, account, GearyApplication.NAME); + + return true; + }; + bind_property("current-folder", this, "title", BindingFlags.SYNC_CREATE, title_func); + main_toolbar.bind_property("account", this, "title", BindingFlags.SYNC_CREATE, title_func); + } else { + main_toolbar.show_close_button = true; + set_titlebar(main_toolbar); } - main_layout.pack_end(conversations_paned, true, true, 0); - - add(main_layout); + // Search bar + this.search_bar_box.pack_start(this.search_bar, false, false, 0); + // Folder list + this.folder_list_scrolled.add(this.folder_list); + // Conversation list + this.conversation_list_scrolled.add(this.conversation_list_view); + // Conversation viewer + this.conversations_paned.pack2(this.conversation_viewer, true, true); + + // Status bar + this.status_bar.set_size_request(-1, STATUS_BAR_HEIGHT); + this.status_bar.set_border_width(2); + this.spinner.set_size_request(STATUS_BAR_HEIGHT - 2, -1); + this.spinner.set_progress_monitor(progress_monitor); + 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 = conversation_list_scrolled.get_vscrollbar() as Gtk.Scrollbar; + Gtk.Scrollbar? scrollbar = this.conversation_list_scrolled.get_vscrollbar() as Gtk.Scrollbar; return scrollbar != null && scrollbar.get_visible(); } - - private bool on_key_press_event(Gdk.EventKey event) { - if ((event.keyval == Gdk.Key.Shift_L || event.keyval == Gdk.Key.Shift_R) - && (event.state & Gdk.ModifierType.SHIFT_MASK) == 0 && !search_bar.search_entry_has_focus) - on_shift_key(true); - - // Check whether the focused widget wants to handle it, if not let the accelerators kick in - // via the default handling - return propagate_key_event(event); - } - - private bool on_key_release_event(Gdk.EventKey event) { - // FIXME: it's possible the user will press two shift keys. We want - // the shift key to report as released when they release ALL of them. - // There doesn't seem to be an easy way to do this in Gdk. - if ((event.keyval == Gdk.Key.Shift_L || event.keyval == Gdk.Key.Shift_R) - && !search_bar.search_entry_has_focus) - on_shift_key(false); - - return propagate_key_event(event); - } - - private bool on_focus_event() { - on_shift_key(false); - return false; - } private void on_conversation_monitor_changed() { ConversationListStore? old_model = this.conversation_list_view.get_model(); @@ -293,107 +220,147 @@ public class MainWindow : Gtk.ApplicationWindow { } private void on_folder_selected(Geary.Folder? folder) { - if (folder_progress != null) { - progress_monitor.remove(folder_progress); - folder_progress = null; + if (this.folder_progress != null) { + this.progress_monitor.remove(this.folder_progress); + this.folder_progress = null; } - + if (folder != null) { - folder_progress = folder.opening_monitor; - progress_monitor.add(folder_progress); + this.folder_progress = folder.opening_monitor; + this.progress_monitor.add(this.folder_progress); } - + // disconnect from old folder - if (current_folder != null) - current_folder.properties.notify.disconnect(update_headerbar); - + if (this.current_folder != null) + this.current_folder.properties.notify.disconnect(update_headerbar); + // connect to new folder if (folder != null) folder.properties.notify.connect(update_headerbar); - + // swap it in - current_folder = folder; - + this.current_folder = folder; + update_headerbar(); } - + private void on_account_available(Geary.AccountInformation account) { try { - progress_monitor.add(Geary.Engine.instance.get_account_instance(account).opening_monitor); - progress_monitor.add(Geary.Engine.instance.get_account_instance(account).sending_monitor); + this.progress_monitor.add(Geary.Engine.instance.get_account_instance(account).opening_monitor); + this.progress_monitor.add(Geary.Engine.instance.get_account_instance(account).sending_monitor); } catch (Error e) { debug("Could not access account progress monitors: %s", e.message); } } - + private void on_account_unavailable(Geary.AccountInformation account) { try { - progress_monitor.remove(Geary.Engine.instance.get_account_instance(account).opening_monitor); - progress_monitor.remove(Geary.Engine.instance.get_account_instance(account).sending_monitor); + this.progress_monitor.remove(Geary.Engine.instance.get_account_instance(account).opening_monitor); + this.progress_monitor.remove(Geary.Engine.instance.get_account_instance(account).sending_monitor); } catch (Error e) { debug("Could not access account progress monitors: %s", e.message); } } - + private void on_change_orientation() { bool horizontal = GearyApplication.instance.config.folder_list_pane_horizontal; bool initial = true; - - if (status_bar.parent != null) { - status_bar.parent.remove(status_bar); + + if (this.status_bar.parent != null) { + this.status_bar.parent.remove(status_bar); initial = false; } - - GLib.Settings.unbind(folder_paned, "position"); - folder_paned.orientation = horizontal ? Gtk.Orientation.HORIZONTAL : + + GLib.Settings.unbind(this.folder_paned, "position"); + this.folder_paned.orientation = horizontal ? Gtk.Orientation.HORIZONTAL : Gtk.Orientation.VERTICAL; - + int folder_list_width = GearyApplication.instance.config.folder_list_pane_position_horizontal; if (horizontal) { if (!initial) - conversations_paned.position += folder_list_width; - folder_box.pack_start(status_bar, false, false); + this.conversations_paned.position += folder_list_width; + this.folder_box.pack_start(status_bar, false, false); } else { if (!initial) - conversations_paned.position -= folder_list_width; - conversation_box.pack_start(status_bar, false, false); + this.conversations_paned.position -= folder_list_width; + this.conversation_box.pack_start(status_bar, false, false); } - + GearyApplication.instance.config.bind( horizontal ? Configuration.FOLDER_LIST_PANE_POSITION_HORIZONTAL_KEY : Configuration.FOLDER_LIST_PANE_POSITION_VERTICAL_KEY, - folder_paned, "position"); + this.folder_paned, "position"); } - + private void update_headerbar() { - if (current_folder == null) { - main_toolbar.account = null; - main_toolbar.folder = null; - + if (this.current_folder == null) { + this.main_toolbar.account = null; + this.main_toolbar.folder = null; + return; } - - main_toolbar.account = current_folder.account.information.nickname; - + + this.main_toolbar.account = this.current_folder.account.information.nickname; + /// Current folder's name followed by its unread count, i.e. "Inbox (42)" // except for Drafts and Outbox, where we show total count int count; - switch (current_folder.special_folder_type) { + switch (this.current_folder.special_folder_type) { case Geary.SpecialFolderType.DRAFTS: case Geary.SpecialFolderType.OUTBOX: - count = current_folder.properties.email_total; + count = this.current_folder.properties.email_total; break; - + default: - count = current_folder.properties.email_unread; + count = this.current_folder.properties.email_unread; break; } - + if (count > 0) - main_toolbar.folder = _("%s (%d)").printf(current_folder.get_display_name(), count); + this.main_toolbar.folder = _("%s (%d)").printf(this.current_folder.get_display_name(), count); else - main_toolbar.folder = current_folder.get_display_name(); + this.main_toolbar.folder = this.current_folder.get_display_name(); + } + + [GtkCallback] + private bool on_key_press_event(Gdk.EventKey event) { + if ((event.keyval == Gdk.Key.Shift_L || event.keyval == Gdk.Key.Shift_R) + && (event.state & Gdk.ModifierType.SHIFT_MASK) == 0 + && !this.search_bar.search_entry_has_focus) + on_shift_key(true); + + // Check whether the focused widget wants to handle it, if not let the accelerators kick in + // via the default handling + return propagate_key_event(event); + } + + [GtkCallback] + private bool on_key_release_event(Gdk.EventKey event) { + // FIXME: it's possible the user will press two shift keys. We want + // the shift key to report as released when they release ALL of them. + // There doesn't seem to be an easy way to do this in Gdk. + if ((event.keyval == Gdk.Key.Shift_L || event.keyval == Gdk.Key.Shift_R) + && !this.search_bar.search_entry_has_focus) + on_shift_key(false); + + return propagate_key_event(event); + } + + [GtkCallback] + private bool on_focus_event() { + on_shift_key(false); + return false; + } + + [GtkCallback] + private bool on_delete_event() { + if (Args.hidden_startup || GearyApplication.instance.config.startup_notifications) + return hide_on_delete(); + + GearyApplication.instance.exit(); + + return true; } } diff --git a/ui/CMakeLists.txt b/ui/CMakeLists.txt index e2ac935d..501ab7a2 100644 --- a/ui/CMakeLists.txt +++ b/ui/CMakeLists.txt @@ -23,6 +23,7 @@ set(RESOURCE_LIST STRIPBLANKS "gtk/menus.ui" STRIPBLANKS "login.glade" STRIPBLANKS "main-toolbar.ui" + STRIPBLANKS "main-window.ui" STRIPBLANKS "password-dialog.glade" STRIPBLANKS "preferences.glade" STRIPBLANKS "remove_confirm.glade" diff --git a/ui/main-window.ui b/ui/main-window.ui new file mode 100644 index 00000000..f61c55ab --- /dev/null +++ b/ui/main-window.ui @@ -0,0 +1,132 @@ + + + + +