Commit 2e940316 authored by Eric Gregory's avatar Eric Gregory

Closes #6963 Search upgrade progress bar

parent 463199a1
......@@ -45,6 +45,7 @@ engine/api/geary-folder-supports-remove.vala
engine/api/geary-logging.vala
engine/api/geary-named-flag.vala
engine/api/geary-named-flags.vala
engine/api/geary-progress-monitor.vala
engine/api/geary-search-folder.vala
engine/api/geary-service-provider.vala
engine/api/geary-special-folder-type.vala
......@@ -267,6 +268,7 @@ client/ui/folder-menu.vala
client/ui/icon-factory.vala
client/ui/main-toolbar.vala
client/ui/main-window.vala
client/ui/monitored-progress-bar.vala
client/util/util-date.vala
client/util/util-email.vala
......
......@@ -77,6 +77,12 @@ along with Geary; if not, write to the Free Software Foundation, Inc.,
public Configuration config { get; private set; }
/**
* Fired when the current account in the UI has changed.
* TODO: This signal really belongs in the controller. See #7032 for the refactoring ticket.
*/
public signal void current_account_changed(Geary.Account? account);
private static GearyApplication _instance = null;
private GearyController? controller = null;
......@@ -484,5 +490,9 @@ along with Geary; if not, write to the Free Software Foundation, Inc.,
return 0; // on error
}
public void notify_current_account_changed(Geary.Account? account) {
current_account_changed(account);
}
}
......@@ -327,8 +327,6 @@ public class GearyController {
account.email_sent.connect(on_sent);
main_window.folder_list.set_user_folders_root_name(account, _("Labels"));
update_search_placeholder_text();
}
public async void disconnect_account_async(Geary.Account account, Cancellable? cancellable = null) {
......@@ -373,8 +371,6 @@ public class GearyController {
} catch (Error e) {
message("Error enumerating accounts: %s", e.message);
}
update_search_placeholder_text();
}
// Returns the number of open accounts.
......@@ -398,7 +394,6 @@ public class GearyController {
// by other utility methods
private void update_ui() {
update_tooltips();
update_search_placeholder_text();
Gtk.Action delete_message = GearyApplication.instance.actions.get_action(ACTION_DELETE_MESSAGE);
if (current_folder is Geary.FolderSupport.Archive) {
delete_message.label = ARCHIVE_MESSAGE_LABEL;
......@@ -473,7 +468,10 @@ public class GearyController {
debug("switching to %s", folder.to_string());
current_folder = folder;
current_account = folder.account;
if (current_account != folder.account) {
current_account = folder.account;
GearyApplication.instance.notify_current_account_changed(current_account);
}
if (!(current_folder is Geary.SearchFolder))
previous_non_search_folder = current_folder;
......@@ -1492,11 +1490,5 @@ public class GearyController {
main_window.folder_list.set_search(folder);
}
private void update_search_placeholder_text() {
main_window.main_toolbar.set_search_placeholder_text(
current_account == null || GearyApplication.instance.get_num_accounts() == 1 ?
_("Search") : _("Search %s account").printf(current_account.information.nickname));
}
}
......@@ -7,6 +7,7 @@
// Draws the main toolbar.
public class MainToolbar : Gtk.Box {
private const string ICON_CLEAR_NAME = "edit-clear-symbolic";
private const string DEFAULT_SEARCH_TEXT = _("Search");
private Gtk.Toolbar toolbar;
public FolderMenu copy_folder_menu { get; private set; }
......@@ -14,13 +15,18 @@ public class MainToolbar : Gtk.Box {
private GtkUtil.ToggleToolbarDropdown mark_menu_dropdown;
private GtkUtil.ToggleToolbarDropdown app_menu_dropdown;
private Gtk.ToolItem search_container;
private Gtk.Entry search_entry;
private Geary.ProgressMonitor? search_upgrade_progress_monitor = null;
private MonitoredProgressBar search_upgrade_progress_bar = new MonitoredProgressBar();
public signal void search_text_changed(string search_text);
public MainToolbar() {
Object(orientation: Gtk.Orientation.VERTICAL, spacing: 0);
GearyApplication.instance.current_account_changed.connect(on_account_changed);
Gtk.Builder builder = GearyApplication.instance.create_builder("toolbar.glade");
toolbar = builder.get_object("toolbar") as Gtk.Toolbar;
......@@ -61,6 +67,7 @@ public class MainToolbar : Gtk.Box {
mark_menu_dropdown.attach(mark_menu_button);
// Search bar.
search_container = (Gtk.ToolItem) builder.get_object("search_container");
search_entry = (Gtk.Entry) builder.get_object("search_entry");
search_entry.changed.connect(on_search_entry_changed);
search_entry.icon_release.connect(on_search_entry_icon_release);
......@@ -84,7 +91,11 @@ public class MainToolbar : Gtk.Box {
toolbar.get_style_context().add_class("primary-toolbar");
search_upgrade_progress_bar.show_text = true;
search_upgrade_progress_bar.margin_top = search_upgrade_progress_bar.margin_bottom = 3;
add(toolbar);
set_search_placeholder_text(DEFAULT_SEARCH_TEXT);
}
private Gtk.ToolButton set_toolbutton_action(Gtk.Builder builder, string action) {
......@@ -119,5 +130,41 @@ public class MainToolbar : Gtk.Box {
return false;
}
private void on_search_upgrade_start() {
search_container.remove(search_container.get_child());
search_container.add(search_upgrade_progress_bar);
search_upgrade_progress_bar.show();
}
private void on_search_upgrade_finished() {
search_container.remove(search_container.get_child());
search_container.add(search_entry);
}
private void on_account_changed(Geary.Account? account) {
on_search_upgrade_finished(); // Reset search box.
if (search_upgrade_progress_monitor != null) {
search_upgrade_progress_monitor.start.disconnect(on_search_upgrade_start);
search_upgrade_progress_monitor.finish.disconnect(on_search_upgrade_finished);
search_upgrade_progress_monitor = null;
}
if (account != null) {
search_upgrade_progress_monitor = account.search_upgrade_monitor;
search_upgrade_progress_bar.set_progress_monitor(search_upgrade_progress_monitor);
search_upgrade_progress_monitor.start.connect(on_search_upgrade_start);
search_upgrade_progress_monitor.finish.connect(on_search_upgrade_finished);
if (search_upgrade_progress_monitor.is_in_progress)
on_search_upgrade_start(); // Remove search box, we're already in progress.
}
search_upgrade_progress_bar.text = _("Indexing %s account").printf(account.information.nickname);
set_search_placeholder_text(account == null || GearyApplication.instance.get_num_accounts() == 1 ?
DEFAULT_SEARCH_TEXT : _("Search %s account").printf(account.information.nickname));
}
}
/* Copyright 2013 Yorba Foundation
*
* This software is licensed under the GNU Lesser General Public License
* (version 2.1 or later). See the COPYING file in this distribution.
*/
/**
* Adapts a progress bar to automatically display progress of a Geary.ProgressMonitor.
*/
public class MonitoredProgressBar : Gtk.ProgressBar {
private Geary.ProgressMonitor? monitor = null;
public void set_progress_monitor(Geary.ProgressMonitor monitor) {
this.monitor = monitor;
monitor.start.connect(on_start);
monitor.finish.connect(on_finish);
monitor.update.connect(on_update);
fraction = monitor.progress;
}
private void on_start() {
fraction = 0.0;
}
private void on_update(double total_progress, double change, Geary.ProgressMonitor monitor) {
fraction = total_progress;
}
private void on_finish() {
fraction = 1.0;
}
}
......@@ -6,6 +6,7 @@
public abstract class Geary.AbstractAccount : BaseObject, Geary.Account {
public Geary.AccountInformation information { get; protected set; }
public Geary.ProgressMonitor search_upgrade_monitor { get; protected set; }
private string name;
......
......@@ -15,6 +15,8 @@ public interface Geary.Account : BaseObject {
public abstract Geary.AccountInformation information { get; protected set; }
public abstract Geary.ProgressMonitor search_upgrade_monitor { get; protected set; }
public signal void opened();
public signal void closed();
......
/* Copyright 2013 Yorba Foundation
*
* This software is licensed under the GNU Lesser General Public License
* (version 2.1 or later). See the COPYING file in this distribution.
*/
/**
* Type of progress monitor.
*/
public enum Geary.ProgressType {
AGGREGATED,
ACTIVITY,
DB_UPGRADE,
SEARCH_INDEX
}
/**
* Base class for progress monitoring.
*/
public abstract class Geary.ProgressMonitor : BaseObject {
public const double MIN = 0.0;
public const double MAX = 1.0;
public double progress { get; protected set; default = MIN; }
public bool is_in_progress { get; protected set; default = false; }
public Geary.ProgressType progress_type { get; protected set; }
/**
* The start signal is fired just before progress begins. It will not fire again until after
* {@link finish} has fired.
*/
public signal void start();
/**
* Notifies the user of existing progress. Note that monitor refers to the monitor that
* invoked this update, which may not be the same as this object.
*/
public signal void update(double total_progress, double change, Geary.ProgressMonitor monitor);
/**
* Finish is fired when progress has completed.
*/
public signal void finish();
/**
* Users must call this before calling update. Must not be called again until
* {@link notify_finish()} has been called.
*/
public virtual void notify_start() {
assert(!is_in_progress);
progress = MIN;
is_in_progress = true;
start();
}
/**
* Users must call this when progress has completed. Must only be called after {@link notify_start()}
*/
public virtual void notify_finish() {
assert(is_in_progress);
is_in_progress = false;
finish();
}
}
/**
* Captures the progress of a single action.
*/
public class Geary.SimpleProgressMonitor : Geary.ProgressMonitor {
/**
* Creates a new progress monitor of the given type.
*/
public SimpleProgressMonitor(ProgressType type) {
this.progress_type = type;
}
/**
* Updates the progress by the given value. Must be between {@link MIN} and {@link MAX}. Must only
* be called after {@link notify_start()} and before {@link notify_finish()}
*/
public void increment(double value) {
assert(value > 0);
assert(is_in_progress);
if (progress + value > MAX)
value = MAX - progress;
progress += value;
update(progress, value, this);
}
}
/**
* Monitors the progress of a countable interval. Note that min and max are inclusive.
*/
public class Geary.IntervalProgressMonitor : Geary.ProgressMonitor {
private int min_interval;
private int max_interval;
private int current = 0;
/**
* Creates a new progress monitor with the given interval range.
*/
public IntervalProgressMonitor(ProgressType type, int min, int max) {
this.progress_type = type;
this.min_interval = min;
this.max_interval = max;
}
/**
* Sets a new interval. Must not be done while in progress.
*/
public void set_interval(int min, int max) {
assert(!is_in_progress);
this.min_interval = min;
this.max_interval = max;
}
public override void notify_start() {
current = 0;
base.notify_start();
}
/**
* Incrememts the progress
*/
public void increment(int count = 1) {
assert(is_in_progress);
assert(count + progress >= min_interval);
assert(count + progress <= max_interval);
current += count;
double new_progress = (1.0 * current - min_interval) / (1.0 * max_interval - min_interval);
double change = new_progress - progress;
progress = new_progress;
update(progress, change, this);
}
}
/**
* Captures progress of multiple actions by composing
* many progress monitors into one.
*/
public class Geary.AggregateProgressMonitor : Geary.ProgressMonitor {
private Gee.HashSet<Geary.ProgressMonitor> monitors = new Gee.HashSet<Geary.ProgressMonitor>();
/**
* Creates an aggregate progress monitor.
*/
public AggregateProgressMonitor() {
this.progress_type = Geary.ProgressType.AGGREGATED;
}
/**
* Adds a new progress monitor to this aggregate.
*/
public void add(Geary.ProgressMonitor pm) {
// TODO: Handle the case where we add a new monitor during progress.
monitors.add(pm);
pm.start.connect(on_start);
pm.update.connect(on_update);
pm.finish.connect(on_finish);
}
private void on_start() {
if (!is_in_progress)
notify_start();
}
private void on_update(double total_progress, double change, ProgressMonitor monitor) {
assert(is_in_progress);
double updated_progress = MIN;
foreach(Geary.ProgressMonitor pm in monitors)
updated_progress += pm.progress;
updated_progress /= monitors.size;
double aggregated_change = updated_progress - progress;
if (aggregated_change < 0)
aggregated_change = 0;
progress += updated_progress;
if (progress > MAX)
progress = MAX;
update(progress, aggregated_change, monitor);
}
private void on_finish() {
// Only signal completion once all progress monitors are complete.
foreach(Geary.ProgressMonitor pm in monitors) {
if (pm.is_in_progress)
return;
}
notify_finish();
}
}
......@@ -26,6 +26,8 @@ private class Geary.ImapDB.Account : BaseObject {
new Gee.HashMap<Geary.FolderPath, FolderReference>();
private Cancellable? background_cancellable = null;
public ImapEngine.ContactStore contact_store { get; private set; }
public IntervalProgressMonitor search_index_monitor { get; private set;
default = new IntervalProgressMonitor(ProgressType.SEARCH_INDEX, 0, 0); }
public Account(Geary.AccountInformation account_information) {
this.account_information = account_information;
......@@ -698,15 +700,35 @@ private class Geary.ImapDB.Account : BaseObject {
debug("Deleted %d duplicate folders", count);
}
public async int get_email_count_async(Cancellable? cancellable) throws Error {
check_open();
int count = 0;
yield db.exec_transaction_async(Db.TransactionType.RO, (cx) => {
count = do_get_email_count(cx, cancellable);
return Db.TransactionOutcome.SUCCESS;
}, cancellable);
return count;
}
private async void populate_search_table_async(Cancellable? cancellable) {
// TODO: send processing signal upwards.
debug("Populating search table");
try {
int total = yield get_email_count_async(cancellable);
search_index_monitor.set_interval(0, total);
search_index_monitor.notify_start();
while (!yield populate_search_table_batch_async(100, cancellable))
;
} catch (Error e) {
debug("Error populating search table: %s", e.message);
}
if (search_index_monitor.is_in_progress)
search_index_monitor.notify_finish();
debug("Done populating search table");
}
......@@ -749,6 +771,8 @@ private class Geary.ImapDB.Account : BaseObject {
}
++count;
search_index_monitor.increment();
result.next(cancellable);
}
......@@ -925,5 +949,17 @@ private class Geary.ImapDB.Account : BaseObject {
stmt.exec(cancellable);
}
private int do_get_email_count(Db.Connection cx, Cancellable? cancellable)
throws Error {
Db.Statement stmt = cx.prepare(
"SELECT COUNT(*) FROM MessageTable");
Db.Result results = stmt.exec(cancellable);
if (results.finished)
return 0;
return results.int_at(0);
}
}
......@@ -33,6 +33,8 @@ private abstract class Geary.ImapEngine.GenericAccount : Geary.AbstractAccount {
this.remote.login_failed.connect(on_login_failed);
this.remote.email_sent.connect(on_email_sent);
search_upgrade_monitor = local.search_index_monitor;
if (inbox_path == null) {
inbox_path = new Geary.FolderRoot(Imap.Account.INBOX_NAME, Imap.Account.ASSUMED_SEPARATOR,
Imap.Folder.CASE_SENSITIVE);
......
......@@ -6,11 +6,9 @@
<property name="can_focus">False</property>
<child>
<object class="GtkToolButton" id="GearyNewMessage">
<property name="use_action_appearance">False</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="tooltip_text" translatable="yes" comments="Button for creating a new email message">Start new conversation (Ctrl+N, N)</property>
<property name="use_action_appearance">False</property>
<property name="is_important">True</property>
<property name="label" translatable="yes">New Message</property>
<property name="use_underline">True</property>
......@@ -18,15 +16,13 @@
</object>
<packing>
<property name="expand">False</property>
<property name="homogeneous">False</property>
<property name="homogeneous">True</property>
</packing>
</child>
<child>
<object class="GtkSeparatorToolItem" id="separator">
<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>
</object>
<packing>
<property name="expand">False</property>
......@@ -34,11 +30,9 @@
</child>
<child>
<object class="GtkToolButton" id="GearyReplyToMessage">
<property name="use_action_appearance">False</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="tooltip_text" translatable="yes">Reply to last message in conversation (Ctrl+R, R)</property>
<property name="use_action_appearance">False</property>
<property name="is_important">True</property>
<property name="label" translatable="yes">Reply</property>
<property name="use_underline">True</property>
......@@ -46,16 +40,14 @@
</object>
<packing>
<property name="expand">False</property>
<property name="homogeneous">False</property>
<property name="homogeneous">True</property>
</packing>
</child>
<child>
<object class="GtkToolButton" id="GearyReplyAllMessage">
<property name="use_action_appearance">False</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="tooltip_text" translatable="yes">Reply to everyone in last message of conversation (Ctrl+Shift+R, Shift+R)</property>
<property name="use_action_appearance">False</property>
<property name="is_important">True</property>
<property name="label" translatable="yes">Reply All</property>
<property name="use_underline">True</property>
......@@ -63,16 +55,14 @@
</object>
<packing>
<property name="expand">False</property>
<property name="homogeneous">False</property>
<property name="homogeneous">True</property>
</packing>
</child>
<child>
<object class="GtkToolButton" id="GearyForwardMessage">
<property name="use_action_appearance">False</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="tooltip_text" translatable="yes">Send copy of last message in conversation (Ctrl+L, F)</property>
<property name="use_action_appearance">False</property>
<property name="is_important">True</property>
<property name="label" translatable="yes">Forward</property>
<property name="use_underline">True</property>
......@@ -80,15 +70,13 @@
</object>
<packing>
<property name="expand">False</property>
<property name="homogeneous">False</property>
<property name="homogeneous">True</property>
</packing>
</child>
<child>
<object class="GtkSeparatorToolItem" id="separator2">
<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>
</object>
<packing>
<property name="expand">False</property>
......@@ -96,52 +84,44 @@
</child>
<child>
<object class="GtkToggleToolButton" id="GearyMarkAsMenuButton">
<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>
<property name="label" translatable="yes">Mark</property>
<property name="icon_name">edit-mark</property>
</object>
<packing>
<property name="expand">False</property>
<property name="homogeneous">False</property>
<property name="homogeneous">True</property>
</packing>
</child>
<child>
<object class="GtkToggleToolButton" id="GearyCopyMenuButton">
<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>
<property name="label" translatable="yes">Label as</property>
<property name="icon_name">tag-new</property>
</object>
<packing>
<property name="expand">False</property>
<property name="homogeneous">False</property>
<property name="homogeneous">True</property>
</packing>
</child>
<child>
<object class="GtkToggleToolButton" id="GearyMoveMenuButton">
<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>
<property name="label" translatable="yes">Move to</property>
<property name="icon_name">mail-move</property>
</object>
<packing>
<property name="expand">False</property>
<property name="homogeneous">False</property>
<property name="homogeneous">True</property>
</packing>
</child>
<child>
<object class="GtkSeparatorToolItem" id="separator3">
<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>
</object>
<packing>
<property name="expand">False</property>
......@@ -156,15 +136,13 @@
</object>
<packing>
<property name="expand">False</property>
<property name="homogeneous">False</property>
<property name="homogeneous">True</property>
</packing>
</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>
......@@ -177,12 +155,13 @@
<object class="GtkToolItem" id="search_container">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="hexpand">True</property>
<child>
<object class="GtkEntry" id="search_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hexpand">True</property>
<property name="invisible_char"></property>
<property name="width_chars">35</property>
<property name="primary_icon_name">edit-find-symbolic</property>
<property name="secondary_icon_name">edit-clear-symbolic</property>
<property name="primary_icon_sensitive">False</property>
......@@ -190,23 +169,21 @@
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="expand">True</property>
<property name="homogeneous">True</property>
</packing>
</child>
<child>
<object class="GtkToggleToolButton" id="GearyGearMenuButton">
<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>
<property name="label" translatable="yes">Menu</property>
<property name="use_underline">True</property>
<property name="icon_name">application-menu</property>
</object>
<packing>
<property name="expand">False</property>
<property name="homogeneous">False</property>
<property name="homogeneous">True</property>
</packing>
</child>
</object>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment