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 <nielsdegraef@gmail.com>
This commit is contained in:
Niels De Graef 2016-11-23 00:58:05 +01:00 committed by Michael James Gratton
parent 9f331c4639
commit 776eebd48e
4 changed files with 314 additions and 213 deletions

View file

@ -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

View file

@ -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;
}
}

View file

@ -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"

132
ui/main-window.ui Normal file
View file

@ -0,0 +1,132 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<requires lib="gtk+" version="3.14"/>
<template class="MainWindow" parent="GtkApplicationWindow">
<property name="visible">False</property>
<property name="show_menubar">False</property>
<property name="events">GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK | GDK_FOCUS_CHANGE_MASK | GDK_STRUCTURE_MASK</property>
<signal name="delete_event" handler="on_delete_event"/>
<signal name="key_press_event" handler="on_key_press_event"/>
<signal name="key_release_event" handler="on_key_release_event"/>
<signal name="focus_in_event" handler="on_focus_event"/>
<child>
<object class="GtkBox" id="main_layout">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">0</property>
<child>
<object class="GtkPaned" id="conversations_paned">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">horizontal</property>
<child>
<object class="GtkBox" id="search_bar_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">0</property>
<style>
<class name="sidebar"/>
</style>
<child>
<object class="GtkPaned" id="folder_paned">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">horizontal</property>
<style>
<class name="geary-sidebar-pane-separator"/>
</style>
<child>
<object class="GtkBox" id="folder_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">0</property>
<child>
<object class="GtkFrame" id="folder_frame">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="shadow_type">in</property>
<style>
<class name="geary-folder-frame"/>
</style>
<child>
<object class="GtkScrolledWindow" id="folder_list_scrolled">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="width_request">100</property>
<property name="hscrollbar_policy">never</property>
<property name="vscrollbar_policy">automatic</property>
</object>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
</packing>
</child>
</object>
<packing>
<property name="resize">False</property>
<property name="shrink">False</property>
</packing>
</child>
<child>
<object class="GtkBox" id="conversation_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">0</property>
<child>
<object class="GtkFrame" id="conversation_frame">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="shadow_type">in</property>
<style>
<class name="geary-conversation-frame"/>
</style>
<child>
<object class="GtkScrolledWindow" id="conversation_list_scrolled">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="width_request">250</property>
<property name="hscrollbar_policy">automatic</property>
<property name="vscrollbar_policy">automatic</property>
</object>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
</packing>
</child>
</object>
<packing>
<property name="resize">True</property>
<property name="shrink">False</property>
</packing>
</child>
</object>
<packing>
<property name="pack_type">end</property>
<property name="expand">True</property>
<property name="fill">True</property>
</packing>
</child>
</object>
<packing>
<property name="resize">False</property>
<property name="shrink">False</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
</packing>
</child>
</object>
</child>
</template>
</interface>