[gnome-contacts/wip/map-widget] WIP: Add map widget to sheet



commit fc5e6ad977633822c2c6b2e67edcf2da6d0698d8
Author: Jonas Danielsson <jonas threetimestwo org>
Date:   Fri Dec 5 08:18:46 2014 -0500

    WIP: Add map widget to sheet

 configure.ac                    |    4 +-
 data/Makefile.am                |   28 ++++++
 data/contacts.gresource.xml     |    1 +
 data/ui/contacts-address-map.ui |   48 +++++++++++
 src/Makefile.am                 |    1 +
 src/contacts-address-map.vala   |  176 +++++++++++++++++++++++++++++++++++++++
 src/contacts-app.vala           |   11 ++-
 src/contacts-contact-sheet.vala |   35 +++++---
 src/contacts-contact.vala       |   34 ++++++++
 src/contacts-utils.vala         |   42 +++++++++
 10 files changed, 363 insertions(+), 17 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index a7702fa..72c4f3b 100644
--- a/configure.ac
+++ b/configure.ac
@@ -50,10 +50,12 @@ pkg_modules="gtk+-3.0 >= 3.12.0
             libedataserver-1.2 >= 3.5.3
             goa-1.0
             gee-0.8
+            champlain-gtk-0.12
+            geocode-glib-1.0
             "
 PKG_CHECK_MODULES(CONTACTS, [$pkg_modules])
 
-CONTACTS_PACKAGES="--pkg gtk+-3.0 --pkg gio-2.0 --pkg gio-unix-2.0 --pkg folks --pkg folks-telepathy --pkg 
folks-eds --pkg libnotify"
+CONTACTS_PACKAGES="--pkg gtk+-3.0 --pkg gio-2.0 --pkg gio-unix-2.0 --pkg folks --pkg folks-telepathy --pkg 
folks-eds --pkg libnotify --pkg clutter-1.0 --pkg champlain-0.12 --pkg champlain-gtk-0.12 --pkg 
geocode-glib-1.0"
 AC_SUBST(CONTACTS_PACKAGES)
 
 # Optional dependency for the user accounts panel
diff --git a/data/Makefile.am b/data/Makefile.am
index 780f586..562d77c 100644
--- a/data/Makefile.am
+++ b/data/Makefile.am
@@ -37,6 +37,7 @@ EXTRA_DIST = \
        org.gnome.Contacts.search-provider.ini.in.in \
        contacts.gresource.xml \
        ui/app-menu.ui \
+       ui/contacts-address-map.ui \
        ui/contacts-window.ui \
        ui/contacts-list-pane.ui \
        ui/style.css \
@@ -52,3 +53,30 @@ DISTCLEANFILES = \
        org.gnome.Contacts.search-provider.ini \
        gnome-contacts.desktop \
        gnome-contacts.desktop.in
+
+private_icons =                                                                \
+       hicolor_apps_32x32_maps-pin.svg                                 \
+       hicolor_apps_16x16_map-icon-symbolic.svg                                \
+       $(NULL)
+
+
+install-icons:
+       for icon in $(private_icons); do \
+               THEME=`echo $$icon | cut -d_ -f1`; \
+               CONTEXT=`echo $$icon | cut -d_ -f2`; \
+               SIZE=`echo $$icon | cut -d_ -f3`; \
+               ICONFILE=`echo $$icon | cut -d_ -f4`; \
+               mkdir -p $(DESTDIR)$(datadir)/gnome-contacts/icons/$$THEME/$$SIZE/$$CONTEXT; \
+               $(INSTALL_DATA) $(srcdir)/icons/$$icon 
$(DESTDIR)$(datadir)/gnome-contacts/icons/$$THEME/$$SIZE/$$CONTEXT/$$ICONFILE; \
+       done
+
+uninstall-icons:
+       -for icon in $(private_icons); do \
+               THEME=`echo $$icon | cut -d_ -f1`; \
+               CONTEXT=`echo $$icon | cut -d_ -f2`; \
+               SIZE=`echo $$icon | cut -d_ -f3`; \
+               ICONFILE=`echo $$icon | cut -d_ -f4`; \
+               rm -f $(DESTDIR)$(datadir)/gnome-contacts/icons/$$THEME/$$SIZE/$$CONTEXT/$$ICONFILE; \
+       done
+
+install-data-local: install-icons
diff --git a/data/contacts.gresource.xml b/data/contacts.gresource.xml
index 2b1ba03..98bfee4 100644
--- a/data/contacts.gresource.xml
+++ b/data/contacts.gresource.xml
@@ -3,6 +3,7 @@
   <gresource prefix="/org/gnome/contacts">
     <file compressed="true">ui/style.css</file>
     <file compressed="true" preprocess="xml-stripblanks">ui/app-menu.ui</file>
+    <file compressed="true" preprocess="xml-stripblanks">ui/contacts-address-map.ui</file>
     <file compressed="true" preprocess="xml-stripblanks">ui/contacts-window.ui</file>
     <file compressed="true" preprocess="xml-stripblanks">ui/contacts-list-pane.ui</file>
   </gresource>
diff --git a/data/ui/contacts-address-map.ui b/data/ui/contacts-address-map.ui
new file mode 100644
index 0000000..9daad0f
--- /dev/null
+++ b/data/ui/contacts-address-map.ui
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <!-- interface-requires gtk+ 3.10 -->
+  <template class="ContactsAddressMap" parent="GtkFrame">
+    <property name="visible">True</property>
+    <property name="can_focus">False</property>
+    <property name="hexpand">False</property>
+    <property name="hexpand_set">True</property>
+    <property name="shadow_type">in</property>
+    <property name="width_request">300</property>
+    <property name="height_request">300</property>
+    <child>
+      <object class="GtkStack" id="map_stack">
+        <property name="visible">True</property>
+        <child>
+          <object class="GtkGrid" id="placeholder_grid">
+            <property name="visible">True</property>
+            <property name="orientation">vertical</property>
+            <property name="halign">center</property>
+            <property name="valign">center</property>
+            <property name="valign">center</property>
+            <property name="row-spacing">20</property>
+            <child>
+              <object class="GtkImage">
+                <property name="visible">True</property>
+                <property name="icon-name">map-icon-symbolic</property>
+                <property name="pixel-size">22</property>
+              </object>
+            </child>
+            <child>
+              <object class="GtkSpinner" id="map_spinner">
+                <property name="visible">False</property>
+                <property name="no-show-all">True</property>
+                <property name="active">True</property>
+              </object>
+            </child>
+          </object>
+        </child>
+        <child>
+          <object class="GtkGrid" id="map_grid">
+            <property name="orientation">vertical</property>
+            <property name="visible">True</property>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/src/Makefile.am b/src/Makefile.am
index a175980..87a4431 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -25,6 +25,7 @@ bin_PROGRAMS = gnome-contacts
 
 vala_sources = \
        contacts-app.vala \
+       contacts-address-map.vala \
        contacts-contact.vala \
        contacts-contact-sheet.vala \
        contacts-contact-editor.vala \
diff --git a/src/contacts-address-map.vala b/src/contacts-address-map.vala
new file mode 100644
index 0000000..f836f7c
--- /dev/null
+++ b/src/contacts-address-map.vala
@@ -0,0 +1,176 @@
+/* -*- Mode: vala; indent-tabs-mode: t; c-basic-offset: 2; tab-width: 8 -*- */
+/*
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+using Champlain;
+using Folks;
+using Geocode;
+using Gee;
+using Gtk;
+using GtkChamplain;
+
+[GtkTemplate (ui = "/org/gnome/contacts/ui/contacts-address-map.ui")]
+public class Contacts.AddressMap : Frame {
+
+  [GtkChild]
+  private Stack map_stack;
+
+  [GtkChild]
+  private Grid placeholder_grid;
+
+  [GtkChild]
+  private Grid map_grid;
+
+  [GtkChild]
+  private Spinner map_spinner;
+
+  private ulong notify_id;
+  private Set<PostalAddressFieldDetails> addresses;
+  private GLib.List<Place> found_places;
+  private Champlain.View map_view;
+  private MarkerLayer marker_layer;
+
+  public AddressMap (string id, Set<PostalAddressFieldDetails> postal_addresses) {
+    var map = new Embed ();
+    var map_factory = MapSourceFactory.dup_default ();
+
+    map_view = map.get_view ();
+    map_view.set_map_source (map_factory.create (MAP_SOURCE_OSM_MAPQUEST));
+    map_view.zoom_level = map_view.max_zoom_level - 2;
+    marker_layer = new MarkerLayer ();
+    map_view.add_layer (marker_layer);
+
+    // Do not propagate event to the Champlain clutter stage
+    map_view.get_stage ().captured_event.connect(() => { return true; });
+
+    map.button_press_event.connect(() => {
+        activate_action ("org.gnome.Maps",
+                         "show-contact",
+                         new Variant ("s", id),
+                         Gtk.get_current_event_time ());
+        return false;
+      });
+
+    addresses = postal_addresses;
+    found_places = new GLib.List<Place>();
+    map_grid.add (map);
+  }
+
+  public void load () {
+    map_stack.visible_child = placeholder_grid;
+    map_spinner.visible = true;
+    var geocodes = 0;
+
+    foreach (var addr in addresses) {
+      Contact.geocode_address.begin (addr.value, (object, res) => {
+          var place = Contact.geocode_address.end (res);
+          geocodes++;
+
+          map_spinner.visible = false;
+
+          if (place != null)
+            found_places.prepend (place);
+
+          if (geocodes == addresses.size) {
+            show_map ();
+          }
+        });
+    }
+  }
+
+  private void show_pin () {
+    var theme = IconTheme.get_default ();
+    var pixbuf = theme.load_icon ("maps-pin", 0, 0);
+    var image = new Clutter.Image ();
+    image.set_data (pixbuf.get_pixels (),
+                    Cogl.PixelFormat.RGBA_8888,
+                    pixbuf.get_width (),
+                    pixbuf.get_height (),
+                    pixbuf.get_rowstride ());
+
+    var actor = new Clutter.Actor ();
+    actor.set_content (image);
+    actor.set_size (pixbuf.get_width (),
+                    pixbuf.get_height ());
+
+    var marker = new Marker ();
+    var place = found_places.nth_data (0);
+    marker.latitude = place.location.latitude;
+    marker.longitude = place.location.longitude;
+    marker.add_actor (actor);
+    marker_layer.add_marker (marker);
+  }
+
+  private void show_labels () {
+
+    foreach (var place in found_places) {
+        var label = new Champlain.Label ();
+        label.text = (place.street_address != null) ?
+          place.street_address : place.street;
+
+        label.latitude = place.location.latitude;
+        label.longitude = place.location.longitude;
+        marker_layer.add_marker(label);
+
+    }
+  }
+
+  void on_state_notify () {
+    if (map_view.get_state () == Champlain.State.DONE) {
+      map_view.disconnect (notify_id);
+
+      if (found_places.length () == 1) {
+        var place = found_places.nth_data (0);
+
+        map_view.center_on (place.location.latitude,
+                            place.location.longitude);
+      } else {
+        /* Make sure the labels are visible */
+        var bbox = new Champlain.BoundingBox ();
+
+        foreach (var marker in marker_layer.get_children ()) {
+          var x = map_view.longitude_to_x ((marker as Marker).longitude);
+          var y = map_view.latitude_to_y ((marker as Marker).latitude);
+
+          /* 256 is tile size */
+          var lat = map_view.y_to_latitude (y - marker.height * 256);
+          var lon = map_view.x_to_longitude (x + marker.width * 256);
+
+          bbox.extend (lat, lon);
+          bbox.extend ((marker as Marker).latitude,
+                       (marker as Marker).longitude);
+        }
+        map_view.ensure_visible (bbox, false);
+      }
+    }
+  }
+
+  private void show_map () {
+    if (found_places.length () == 0) {
+      map_stack.visible_child = placeholder_grid;
+      return;
+    }
+
+    if (found_places.length () == 1) {
+      show_pin ();
+    } else {
+      show_labels ();
+    }
+
+    notify_id = map_view.notify["state"].connect(on_state_notify);
+    map_stack.visible_child = map_grid;
+  }
+}
diff --git a/src/contacts-app.vala b/src/contacts-app.vala
index 6b86e80..17e7629 100644
--- a/src/contacts-app.vala
+++ b/src/contacts-app.vala
@@ -266,15 +266,18 @@ public class Contacts.App : Gtk.Application {
   }
 
   public override void activate () {
-    /* window creation code */
+         var icon_theme = IconTheme.get_default ();
+         icon_theme.append_search_path(Config.PKGDATADIR + "/icons");
+         print ("icon dir %s", Config.PKGDATADIR + "/icons");
+
+         /* window creation code */
     if (window == null) {
       if (!contacts_store.is_prepared) {
        if (!is_prepare_scheluded) {
          schedule_window_creation ();
          return;
        }
-      }
-
+      }          
       create_app_menu ();
       create_window ();
       window.show ();
@@ -369,6 +372,6 @@ public class Contacts.App : Gtk.Application {
   public App () {
     Object (application_id: "org.gnome.Contacts", flags: ApplicationFlags.HANDLES_COMMAND_LINE);
     app = this;
-    settings = new GLib.Settings ("org.gnome.Contacts");
+    settings = new GLib.Settings ("org.gnome.Contacts");       
   }
 }
diff --git a/src/contacts-contact-sheet.vala b/src/contacts-contact-sheet.vala
index 0582615..bfcbf89 100644
--- a/src/contacts-contact-sheet.vala
+++ b/src/contacts-contact-sheet.vala
@@ -19,11 +19,15 @@
 using Gtk;
 using Folks;
 using Gee;
+using Geocode;
+using Champlain;
+using GtkChamplain;
+using Clutter;
 
 public class Contacts.ContactSheet : Grid {
 
   Button add_row_with_button (ref int row, string label_value, string value) {
-    var type_label = new Label (label_value);
+    var type_label = new Gtk.Label (label_value);
     type_label.xalign = 1.0f;
     type_label.set_halign (Align.END);
     type_label.get_style_context ().add_class ("dim-label");
@@ -37,14 +41,14 @@ public class Contacts.ContactSheet : Grid {
     attach (value_button, 1, row, 1, 1);
     row++;
 
-    (value_button.get_child () as Label).set_ellipsize (Pango.EllipsizeMode.END);
-    (value_button.get_child () as Label).wrap_mode = Pango.WrapMode.CHAR;
+    (value_button.get_child () as Gtk.Label).set_ellipsize (Pango.EllipsizeMode.END);
+    (value_button.get_child () as Gtk.Label).wrap_mode = Pango.WrapMode.CHAR;
 
     return value_button;
   }
 
   void add_row_with_link_button (ref int row, string label_value, string value) {
-    var type_label = new Label (label_value);
+    var type_label = new Gtk.Label (label_value);
     type_label.xalign = 1.0f;
     type_label.set_halign (Align.END);
     type_label.get_style_context ().add_class ("dim-label");
@@ -58,19 +62,19 @@ public class Contacts.ContactSheet : Grid {
     attach (value_button, 1, row, 1, 1);
     row++;
 
-    (value_button.get_child () as Label).set_ellipsize (Pango.EllipsizeMode.END);
-    (value_button.get_child () as Label).wrap_mode = Pango.WrapMode.CHAR;
+    (value_button.get_child () as Gtk.Label).set_ellipsize (Pango.EllipsizeMode.END);
+    (value_button.get_child () as Gtk.Label).wrap_mode = Pango.WrapMode.CHAR;
   }
 
   void add_row_with_label (ref int row, string label_value, string value) {
-    var type_label = new Label (label_value);
+    var type_label = new Gtk.Label (label_value);
     type_label.xalign = 1.0f;
     type_label.set_halign (Align.END);
     type_label.set_valign (Align.START);
     type_label.get_style_context ().add_class ("dim-label");
     attach (type_label, 0, row, 1, 1);
 
-    var value_label = new Label (value);
+    var value_label = new Gtk.Label (value);
     value_label.set_line_wrap (true);
     value_label.xalign = 0.0f;
     value_label.set_halign (Align.START);
@@ -90,7 +94,7 @@ public class Contacts.ContactSheet : Grid {
   public ContactSheet () {
     set_row_spacing (12);
     set_column_spacing (16);
-    set_orientation (Orientation.VERTICAL);
+    set_orientation (Gtk.Orientation.VERTICAL);
   }
 
   public void update (Contact c) {
@@ -104,7 +108,7 @@ public class Contacts.ContactSheet : Grid {
       });
     attach (image_frame,  0, 0, 1, 3);
 
-    var name_label = new Label (null);
+    var name_label = new Gtk.Label (null);
     name_label.set_hexpand (true);
     name_label.set_halign (Align.START);
     name_label.set_valign (Align.CENTER);
@@ -113,7 +117,7 @@ public class Contacts.ContactSheet : Grid {
     name_label.xalign = 0.0f;
 
     c.keep_widget_uptodate (name_label, (w) => {
-       (w as Label).set_markup (Markup.printf_escaped ("<span font='16'>%s</span>", c.display_name));
+       (w as Gtk.Label).set_markup (Markup.printf_escaped ("<span font='16'>%s</span>", c.display_name));
       });
     attach (name_label,  1, 0, 1, 3);
 
@@ -125,7 +129,7 @@ public class Contacts.ContactSheet : Grid {
     /* Cause personas are sorted properly I can do this */
     foreach (var p in personas) {
       if (!is_first_persona) {
-       var store_name = new Label("");
+       var store_name = new Gtk.Label("");
        store_name.set_markup (Markup.printf_escaped ("<span font='16px bold'>%s</span>",
                                                      Contact.format_persona_store_name_for_contact (p)));
        store_name.set_halign (Align.START);
@@ -221,6 +225,13 @@ public class Contacts.ContactSheet : Grid {
          }
          add_row_with_label (ref i, TypeSet.general.format_type (addr), all_strs);
        }
+
+       if (addr_details.postal_addresses.size > 0) {
+         var map = new AddressMap (c.individual.id, addr_details.postal_addresses);
+         map.load ();
+         attach (map, 1, i, 1, 1);
+         i++;
+       }
       }
 
       if (i != 3)
diff --git a/src/contacts-contact.vala b/src/contacts-contact.vala
index b3c14da..4078a75 100644
--- a/src/contacts-contact.vala
+++ b/src/contacts-contact.vala
@@ -20,6 +20,7 @@ using Gtk;
 using Folks;
 using Gee;
 using TelepathyGLib;
+using Geocode;
 
 public errordomain ContactError {
   NOT_IMPLEMENTED,
@@ -672,6 +673,39 @@ public class Contacts.Contact : GLib.Object  {
     return res;
   }
 
+  public static async Place geocode_address (PostalAddress addr) {
+    SourceFunc callback = geocode_address.callback;
+    var params = new HashTable<string, GLib.Value?>(str_hash, str_equal);
+
+    if (is_set (addr.street))
+      params.insert("street", addr.street);
+
+    if (is_set (addr.locality))
+      params.insert("locality", addr.locality);
+
+    if (is_set (addr.region))
+      params.insert("region", addr.region);
+
+    if (is_set (addr.country))
+      params.insert("country", addr.country);
+
+    Place? place = null;
+    var forward = new Forward.for_params (params);
+    forward.search_async.begin (null, (object, res) => {
+        try {
+          var places = forward.search_async.end (res);
+
+          place = places.nth_data (0);
+          callback ();
+        } catch (GLib.Error e) {
+          debug ("No geocode result found for contact");
+          callback ();
+        }
+      });
+    yield;
+    return place;
+  }
+
   public static string[] format_address (PostalAddress addr) {
     string[] lines = {};
 
diff --git a/src/contacts-utils.vala b/src/contacts-utils.vala
index 1e5eef8..83b8cb6 100644
--- a/src/contacts-utils.vala
+++ b/src/contacts-utils.vala
@@ -20,6 +20,9 @@ using Gtk;
 using Folks;
 using Gee;
 using TelepathyGLib;
+using DBus;
+using GLib;
+using Gdk;
 
 namespace Contacts {
   private static bool is_set (string? str) {
@@ -51,6 +54,45 @@ namespace Contacts {
                             ListBoxRow? before_row) {
     row.set_header (new Separator (Orientation.HORIZONTAL));
   }
+
+  [DBus (name = "org.freedesktop.Application")]
+  interface FreedesktopApplication : Object {
+    [DBus (name = "ActivateAction")]
+      public abstract void ActivateAction (string action,
+                                           Variant[] parameter,
+                                           HashTable<string, Variant> data) throws IOError;
+  }
+
+       public void activate_action (string app_id,
+                               string action,
+                               Variant? parameter,
+                               uint32 timestamp) {
+    FreedesktopApplication? con = null;
+
+    try {
+      string object_path = "/" + app_id.replace(".", "/");
+      Display display = Display.get_default ();
+      DesktopAppInfo info = new DesktopAppInfo (app_id + ".desktop");
+
+      con = Bus.get_proxy_sync (BusType.SESSION, app_id, object_path);
+      Gdk.AppLaunchContext context = display.get_app_launch_context ();
+      context.set_timestamp (timestamp);
+
+      Variant[] param_array = {};
+      if (parameter != null) {
+        param_array += parameter;
+      }
+
+      var data = new HashTable<string, Variant>(str_hash, str_equal);
+      data.insert ("desktop-startup-id",
+                   new Variant.string (context.get_startup_notify_id (info, new GLib.List<File>())));
+      //      Variant v = new Variant ("(sava{sv})", action, param_builder, data_builder);
+      //      print ("type: %s\n", v.get_type_string ());
+      con.ActivateAction (action, param_array, data);
+    } catch (IOError e) {
+
+    }
+  }
 }
 
 public class Center : Bin {


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]