1/* 2 * Copyright (C) 2011 Alexander Larsson <alexl@redhat.com> 3 * 4 * This program is free software; you can redistribute it and/or modify 5 * it under the terms of the GNU General Public License as published by 6 * the Free Software Foundation; either version 2 of the License, or 7 * (at your option) any later version. 8 * 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * 14 * You should have received a copy of the GNU General Public License 15 * along with this program. If not, see <http://www.gnu.org/licenses/>. 16 */ 17 18using Folks; 19 20public class Contacts.App : Gtk.Application { 21 private Settings settings; 22 23 private Store contacts_store; 24 25 private MainWindow window; 26 27 private const GLib.ActionEntry[] action_entries = { 28 { "quit", quit }, 29 { "help", show_help }, 30 { "about", show_about }, 31 { "change-book", change_address_book }, 32 { "online-accounts", online_accounts }, 33 { "new-contact", new_contact }, 34 { "show-contact", on_show_contact, "s"} 35 }; 36 37 private const OptionEntry[] options = { 38 { "email", 'e', 0, OptionArg.STRING, null, N_("Show contact with this email address") }, 39 { "individual", 'i', 0, OptionArg.STRING, null, N_("Show contact with this individual id") }, 40 { "search", 's', 0, OptionArg.STRING, null, N_("Show contacts with the given filter") }, 41 { "version", 'v', 0, OptionArg.NONE, null, N_("Show the current version of Contacts") }, 42 {} 43 }; 44 45 public App () { 46 Object ( 47 application_id: Config.APP_ID, 48 flags: ApplicationFlags.HANDLES_COMMAND_LINE 49 ); 50 51 this.settings = new Settings (this); 52 add_main_option_entries (options); 53 } 54 55 public override int command_line (ApplicationCommandLine command_line) { 56 var options = command_line.get_options_dict (); 57 58 activate (); 59 60 if ("individual" in options) { 61 var individual = options.lookup_value ("individual", VariantType.STRING); 62 if (individual != null) 63 show_individual.begin (individual.get_string ()); 64 } else if ("email" in options) { 65 var email = options.lookup_value ("email", VariantType.STRING); 66 if (email != null) 67 show_by_email.begin (email.get_string ()); 68 } else if ("search" in options) { 69 var search_term = options.lookup_value ("search", VariantType.STRING); 70 if (search_term != null) 71 show_search (search_term.get_string ()); 72 } 73 74 return 0; 75 } 76 77 public override int handle_local_options (VariantDict options) { 78 if ("version" in options) { 79 stdout.printf ("%s %s\n", Config.PACKAGE_NAME, Config.PACKAGE_VERSION); 80 return 0; 81 } 82 83 return -1; 84 } 85 86 public void show_contact (Individual? individual) { 87 window.set_shown_contact (individual); 88 } 89 90 public async void show_individual (string id) { 91 if (contacts_store.is_quiescent) { 92 show_individual_ready.begin (id); 93 } else { 94 contacts_store.quiescent.connect( () => { 95 show_individual_ready.begin (id); 96 }); 97 } 98 } 99 100 private async void show_individual_ready (string id) { 101 Individual? contact = null; 102 try { 103 contact = yield contacts_store.aggregator.look_up_individual (id); 104 } catch (Error e) { 105 debug ("Couldn't look up individual"); 106 } 107 if (contact != null) { 108 show_contact (contact); 109 } else { 110 var dialog = new Gtk.MessageDialog (this.window, Gtk.DialogFlags.DESTROY_WITH_PARENT, 111 Gtk.MessageType.ERROR, Gtk.ButtonsType.CLOSE, 112 _("No contact with id %s found"), id); 113 dialog.set_title (_("Contact not found")); 114 dialog.run (); 115 dialog.destroy (); 116 } 117 } 118 119 public void change_address_book () { 120 var dialog = new AddressbookDialog (this.contacts_store, this.window); 121 dialog.run (); 122 dialog.destroy (); 123 } 124 125 public void online_accounts () { 126 try { 127 var proxy = new DBusProxy.for_bus_sync (BusType.SESSION, 128 DBusProxyFlags.NONE, 129 null, 130 "org.gnome.ControlCenter", 131 "/org/gnome/ControlCenter", 132 "org.gtk.Actions"); 133 134 var builder = new VariantBuilder (new VariantType ("av") ); 135 builder.add ("v", new Variant.string ("")); 136 var param = new Variant.tuple ({ 137 new Variant.string ("launch-panel"), 138 new Variant.array (new VariantType ("v"), { 139 new Variant ("v", new Variant ("(sav)", "online-accounts", builder)) 140 }), 141 new Variant.array (new VariantType ("{sv}"), {}) 142 }); 143 144 proxy.call_sync ("Activate", param, DBusCallFlags.NONE, -1); 145 } catch (Error e) { 146 // TODO: Show error dialog 147 warning ("Couldn't open online-accounts: %s", e.message); 148 } 149 } 150 151 public void show_help () { 152 try { 153 Gtk.show_uri_on_window (window, "help:gnome-help/contacts", Gtk.get_current_event_time ()); 154 } catch (GLib.Error e1) { 155 warning ("Error showing help: %s", e1.message); 156 } 157 } 158 159 public void show_about () { 160 string[] authors = { 161 "Alexander Larsson <alexl@redhat.com>", 162 "Erick Pérez Castellanos <erick.red@gmail.com>", 163 "Niels De Graef <nielsdegraef@gmail.com>", 164 "Julian Sparber <jsparber@gnome.org>" 165 }; 166 string[] artists = { 167 "Allan Day <allanpday@gmail.com>" 168 }; 169 Gtk.show_about_dialog (window, 170 "artists", artists, 171 "authors", authors, 172 "translator-credits", _("translator-credits"), 173 "program-name", _("GNOME Contacts"), 174 "title", _("About GNOME Contacts"), 175 "comments", _("Contact Management Application"), 176 "copyright", _("© 2011 Red Hat, Inc.\n© 2011-2020 The Contacts Developers"), 177 "license-type", Gtk.License.GPL_2_0, 178 "logo-icon-name", Config.APP_ID, 179 "version", Config.PACKAGE_VERSION, 180 "website", "https://wiki.gnome.org/Apps/Contacts", 181 "wrap-license", true); 182 } 183 184 public async void show_by_email (string email_address) { 185 var query = new SimpleQuery(email_address, { "email-addresses" }); 186 Individual individual = yield contacts_store.find_contact (query); 187 if (individual != null) { 188 show_contact (individual); 189 } else { 190 var dialog = new Gtk.MessageDialog (this.window, Gtk.DialogFlags.DESTROY_WITH_PARENT, 191 Gtk.MessageType.ERROR, Gtk.ButtonsType.CLOSE, 192 _("No contact with email address %s found"), email_address); 193 dialog.set_title (_("Contact not found")); 194 dialog.run (); 195 dialog.destroy (); 196 } 197 } 198 199 public void show_search (string query) { 200 if (contacts_store.is_quiescent) { 201 window.show_search (query); 202 } else { 203 contacts_store.quiescent.connect_after (() => { 204 window.show_search (query); 205 }); 206 } 207 } 208 209 private void create_window () { 210 this.window = new MainWindow (this.settings, this, this.contacts_store); 211 212 show_contact_list (); 213 } 214 215 // We have to wait until our Store is quiescent before showing contacts. 216 // However, some backends can take quite a while to load (or even timeout), 217 // so make sure we also show something within a reasonable time frame. 218 private const int LOADING_TIMEOUT = 1; // in seconds 219 220 private void show_contact_list () { 221 uint timeout_id = 0; 222 223 // Happy flow callback 224 ulong quiescence_id = contacts_store.quiescent.connect (() => { 225 Source.remove (timeout_id); 226 debug ("Got quiescent in time. Showing contact list"); 227 window.show_contact_list (); 228 }); 229 230 // Timeout callback 231 timeout_id = Timeout.add_seconds (LOADING_TIMEOUT, () => { 232 contacts_store.disconnect (quiescence_id); 233 234 debug ("Didn't achieve quiescence in time! Showing contact list anyway"); 235 window.show_contact_list (); 236 return false; 237 }); 238 } 239 240 public override void startup () { 241 if (!ensure_eds_accounts (true)) 242 quit (); 243 244 this.contacts_store = new Store (); 245 base.startup (); 246 247 Hdy.init (); 248 249 load_styling (); 250 create_actions (); 251 } 252 253 private void create_actions () { 254 this.add_action_entries (action_entries, this); 255 256 this.set_accels_for_action ("app.help", {"F1"}); 257 this.set_accels_for_action ("app.new-contact", {"<Primary>n"}); 258 this.set_accels_for_action ("win.show-help-overlay", {"<Primary>question"}); 259 } 260 261 public void load_styling () { 262 var provider = new Gtk.CssProvider (); 263 provider.load_from_resource ("/org/gnome/Contacts/ui/style.css"); 264 Gtk.StyleContext.add_provider_for_screen (Gdk.Screen.get_default(), 265 provider, 266 Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION); 267 } 268 269 public override void activate () { 270 // Check if we've already done the setup process 271 if (this.settings.did_initial_setup) 272 create_window (); 273 else 274 run_setup (); 275 } 276 277 private void run_setup () { 278 // Disable the change-book action (don't want the user to do that during setup) 279 var change_book_action = lookup_action ("change-book") as SimpleAction; 280 change_book_action.set_enabled (false); 281 282 // Create and show the setup window 283 var setup_window = new SetupWindow (this, this.contacts_store); 284 setup_window.setup_done.connect ( (selected_store) => { 285 setup_window.destroy (); 286 287 eds_source_registry.set_default_address_book (selected_store.source); 288 this.settings.did_initial_setup = true; 289 290 change_book_action.set_enabled (true); // re-enable change-book action 291 create_window (); 292 }); 293 setup_window.show (); 294 } 295 296 public void new_contact () { 297 this.window.new_contact (); 298 } 299 300 private void on_show_contact(SimpleAction action, Variant? param) { 301 activate(); 302 303 var individual = param as string; 304 if (individual != null) 305 show_individual.begin (individual); 306 } 307 308} 309