[shotwell/wip/716987-basic-map: 1/2] Basic implementation, from ritchiew. A bit more cleanup needed



commit 6669fa8231a8a43b633649b12d940e7b2dfbd0ee
Author: Jim Nelson <jim yorba org>
Date:   Thu Jan 15 14:39:29 2015 -0800

    Basic implementation, from ritchiew.  A bit more cleanup needed

 Makefile                         |    9 +
 icons/gps-marker.svg             |  370 ++++++++++++++++++++++++++++++++++++++
 src/MapWidget.vala               |  120 ++++++++++++
 src/Page.vala                    |    8 +
 src/Photo.vala                   |    8 +-
 src/Properties.vala              |   58 +++++--
 src/Resources.vala               |    1 +
 src/core/SourceInterfaces.vala   |    4 +
 src/library/ImportQueuePage.vala |    4 +
 src/library/LibraryWindow.vala   |    6 +-
 src/main.vala                    |    2 +-
 src/photos/PhotoMetadata.vala    |   30 +++
 12 files changed, 603 insertions(+), 17 deletions(-)
---
diff --git a/Makefile b/Makefile
index 7f8a1b1..e66d7d3 100644
--- a/Makefile
+++ b/Makefile
@@ -78,6 +78,7 @@ UNUNITIZED_SRC_FILES = \
        Debug.vala \
        ColorTransformation.vala \
        Properties.vala \
+       MapWidget.vala \
        CustomComponents.vala \
        Event.vala \
        International.vala \
@@ -253,6 +254,10 @@ LOCAL_PKGS = \
 
 EXT_PKGS = \
        atk \
+       champlain-0.12 \
+       champlain-gtk-0.12 \
+       clutter-1.0 \
+       clutter-gtk-1.0 \
        gdk-3.0 \
        gee-0.8 \
        gexiv2 \
@@ -285,6 +290,10 @@ THUMBNAILER_PKGS = \
 DIRECT_LIBS =
 
 EXT_PKG_VERSIONS = \
+       champlain-0.12 >= 0.12.3 \
+       champlain-gtk-0.12 >= 0.12.3 \
+       clutter-1.0 >= 1.12.0 \
+       clutter-gtk-1.0 >= 1.0.0 \
        gee-0.8 >= 0.8.5 \
        gexiv2 >= 0.4.90 \
        gio-unix-2.0 >= 2.20 \
diff --git a/icons/gps-marker.svg b/icons/gps-marker.svg
new file mode 100644
index 0000000..564a38a
--- /dev/null
+++ b/icons/gps-marker.svg
@@ -0,0 +1,370 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/";
+   xmlns:cc="http://creativecommons.org/ns#";
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#";
+   xmlns:svg="http://www.w3.org/2000/svg";
+   xmlns="http://www.w3.org/2000/svg";
+   xmlns:xlink="http://www.w3.org/1999/xlink";
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd";
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape";
+   width="28.74"
+   height="38.98"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.48.3.1 r9886"
+   sodipodi:docname="marker.svg">
+  <defs
+     id="defs4">
+    <linearGradient
+       id="linearGradient3887">
+      <stop
+         id="stop3889"
+         offset="0"
+         style="stop-color:#ff573f;stop-opacity:1;" />
+      <stop
+         id="stop3891"
+         offset="1"
+         style="stop-color:#b71111;stop-opacity:1;" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       id="linearGradient3857">
+      <stop
+         style="stop-color:#87b5f5;stop-opacity:1;"
+         offset="0"
+         id="stop3859" />
+      <stop
+         style="stop-color:#87b5f5;stop-opacity:0;"
+         offset="1"
+         id="stop3861" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       id="linearGradient3849">
+      <stop
+         style="stop-color:#ffffff;stop-opacity:1;"
+         offset="0"
+         id="stop3851" />
+      <stop
+         style="stop-color:#ffffff;stop-opacity:0;"
+         offset="1"
+         id="stop3853" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient3827">
+      <stop
+         id="stop3829"
+         offset="0"
+         style="stop-color:#50a9ff;stop-opacity:1;" />
+      <stop
+         id="stop3831"
+         offset="1"
+         style="stop-color:#0034a9;stop-opacity:0.92490119;" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient3821">
+      <stop
+         id="stop3823"
+         offset="0"
+         style="stop-color:#60aaf1;stop-opacity:1;" />
+      <stop
+         id="stop3825"
+         offset="1"
+         style="stop-color:#124cd1;stop-opacity:0.92490119;" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient3787">
+      <stop
+         style="stop-color:#535353;stop-opacity:1;"
+         offset="0"
+         id="stop3789" />
+      <stop
+         style="stop-color:#ffffff;stop-opacity:0;"
+         offset="1"
+         id="stop3791" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient3765">
+      <stop
+         style="stop-color:#23b3ff;stop-opacity:1;"
+         offset="0"
+         id="stop3767" />
+      <stop
+         style="stop-color:#124cd1;stop-opacity:0.92490119;"
+         offset="1"
+         id="stop3769" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3765"
+       id="linearGradient3771"
+       x1="381.42856"
+       y1="335.09586"
+       x2="381.42856"
+       y2="567.15851"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.13306152,0,0,0.13306152,324.43662,388.73998)" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3787"
+       id="radialGradient3793"
+       cx="374.25"
+       cy="464.11218"
+       fx="374.25"
+       fy="464.11218"
+       r="8.25"
+       gradientTransform="matrix(1,0,0,0.33333333,0,309.40812)"
+       gradientUnits="userSpaceOnUse" />
+    <filter
+       inkscape:collect="always"
+       id="filter3803"
+       x="-0.096096098"
+       width="1.1921922"
+       y="-0.2882883"
+       height="1.5765766"
+       color-interpolation-filters="sRGB">
+      <feGaussianBlur
+         inkscape:collect="always"
+         stdDeviation="0.66066066"
+         id="feGaussianBlur3805" />
+    </filter>
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3787"
+       id="radialGradient3843"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(1,0,0,0.33333333,0,309.40812)"
+       cx="374.25"
+       cy="464.11218"
+       fx="374.25"
+       fy="464.11218"
+       r="8.25" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3765"
+       id="linearGradient3845"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.13306152,0,0,0.13306152,394.43662,388.73998)"
+       x1="381.42856"
+       y1="335.09586"
+       x2="381.42856"
+       y2="567.15851" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3849"
+       id="linearGradient3855"
+       x1="461.5"
+       y1="477.36218"
+       x2="462.5"
+       y2="434.36218"
+       gradientUnits="userSpaceOnUse" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3857"
+       id="linearGradient3863"
+       x1="444.95898"
+       y1="433.89029"
+       x2="444.95898"
+       y2="454.77341"
+       gradientUnits="userSpaceOnUse" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3849"
+       id="linearGradient3871"
+       x1="382.17749"
+       y1="377.47879"
+       x2="382.17749"
+       y2="414.47479"
+       gradientUnits="userSpaceOnUse" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3787"
+       id="radialGradient3883"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(1,0,0,0.33333333,0,309.40812)"
+       cx="374.25"
+       cy="464.11218"
+       fx="374.25"
+       fy="464.11218"
+       r="8.25" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3887"
+       id="linearGradient3885"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.13306152,0,0,0.13306152,270.34295,388.73998)"
+       x1="381.42856"
+       y1="335.09586"
+       x2="381.42856"
+       y2="567.15851" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3787"
+       id="radialGradient3925"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(1,0,0,0.33333333,0,309.40812)"
+       cx="374.25"
+       cy="464.11218"
+       fx="374.25"
+       fy="464.11218"
+       r="8.25" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3765"
+       id="linearGradient3927"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.13306152,0,0,0.13306152,324.43662,388.73998)"
+       x1="381.42856"
+       y1="335.09586"
+       x2="381.42856"
+       y2="567.15851" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3787"
+       id="radialGradient3941"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(1,0,0,0.33333333,0,309.40812)"
+       cx="374.25"
+       cy="464.11218"
+       fx="374.25"
+       fy="464.11218"
+       r="8.25" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3765"
+       id="linearGradient3943"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.13306152,0,0,0.13306152,324.43662,388.73998)"
+       x1="381.42856"
+       y1="335.09586"
+       x2="381.42856"
+       y2="567.15851" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3787"
+       id="radialGradient3945"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(1,0,0,0.33333333,0,309.40812)"
+       cx="374.25"
+       cy="464.11218"
+       fx="374.25"
+       fy="464.11218"
+       r="8.25" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3887"
+       id="linearGradient3947"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.13306152,0,0,0.13306152,270.34295,388.73998)"
+       x1="381.42856"
+       y1="335.09586"
+       x2="381.42856"
+       y2="567.15851" />
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="4"
+     inkscape:cx="-23.119863"
+     inkscape:cy="10.735606"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     inkscape:snap-global="false"
+     fit-margin-left="1"
+     units="px"
+     fit-margin-top="1"
+     fit-margin-right="1"
+     fit-margin-bottom="1"
+     inkscape:window-width="1440"
+     inkscape:window-height="844"
+     inkscape:window-x="0"
+     inkscape:window-y="27"
+     inkscape:window-maximized="1">
+    <inkscape:grid
+       type="xygrid"
+       id="grid2987"
+       empspacing="5"
+       visible="true"
+       enabled="true"
+       snapvisiblegridlinesonly="true"
+       originx="-306.53522px"
+       originy="-506.68905px" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage"; />
+        <dc:title></dc:title>
+        <cc:license
+           rdf:resource="" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Ebene 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(-306.53522,-506.69183)">
+    <g
+       id="g3902"
+       transform="translate(0,75.224057)">
+      <g
+         id="g3035">
+        <path
+           transform="matrix(1.3594635,0,0,1,-187.87288,1)"
+           d="m 382.5,464.11218 c 0,1.51879 -3.69365,2.75 -8.25,2.75 -4.55635,0 -8.25,-1.23121 -8.25,-2.75 
0,-1.51878 3.69365,-2.75 8.25,-2.75 4.55635,0 8.25,1.23122 8.25,2.75 z"
+           sodipodi:ry="2.75"
+           sodipodi:rx="8.25"
+           sodipodi:cy="464.11218"
+           sodipodi:cx="374.25"
+           id="path3873"
+           
style="opacity:0.58662612;fill:url(#radialGradient3945);fill-opacity:1;stroke:none;filter:url(#filter3803)"
+           sodipodi:type="arc" />
+        <path
+           sodipodi:nodetypes="sszczss"
+           inkscape:connector-curvature="0"
+           id="path3875"
+           d="m 320.90633,432.9663 c -5.14414,0 -9.3143,4.17016 -9.3143,9.31432 0,1.76829 0.91939,4.12348 
1.34724,4.82763 0.42786,0.70414 7.79657,17.16494 7.79657,17.16494 0,0 7.58026,-16.29039 8.03775,-17.01109 
0.45749,-0.7207 1.44704,-3.14782 1.44704,-4.98148 0,-5.14416 -4.17016,-9.31432 -9.3143,-9.31432 z"
+           
style="fill:url(#linearGradient3947);fill-opacity:1;stroke:#982f26;stroke-width:1;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
 />
+        <path
+           
style="fill:none;stroke:#e19089;stroke-width:1.03512061;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+           d="m 320.90633,434.00353 c -4.50028,0 -8.14847,3.69949 -8.14847,8.26307 0,1.56873 0.80432,3.6581 
1.17861,4.28277 0.3743,0.62468 6.82071,15.22767 6.82071,15.22767 0,0 6.63148,-14.45182 7.03171,-15.09118 
0.40022,-0.63936 1.26591,-2.79256 1.26591,-4.41926 0,-4.56358 -3.64819,-8.26307 -8.14847,-8.26307 z"
+           id="path3877"
+           inkscape:connector-curvature="0"
+           sodipodi:nodetypes="sszczss" />
+        <path
+           sodipodi:type="arc"
+           
style="fill:#ffffff;fill-opacity:1;stroke:#e19089;stroke-width:4.32061148;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0.7"
+           id="path3879"
+           sodipodi:cx="381.25"
+           sodipodi:cy="396.11218"
+           sodipodi:rx="18.75"
+           sodipodi:ry="18.75"
+           d="m 400,396.11218 c 0,10.35534 -8.39466,18.75 -18.75,18.75 -10.35534,0 -18.75,-8.39466 
-18.75,-18.75 0,-10.35534 8.39466,-18.75 18.75,-18.75 10.35534,0 18.75,8.39466 18.75,18.75 z"
+           transform="matrix(0.23144871,0,0,0.23144871,232.66651,350.69905)" />
+        <path
+           transform="matrix(0.17705667,0,0,0.17705667,253.40348,372.2444)"
+           d="m 400,396.11218 c 0,10.35534 -8.39466,18.75 -18.75,18.75 -10.35534,0 -18.75,-8.39466 
-18.75,-18.75 0,-10.35534 8.39466,-18.75 18.75,-18.75 10.35534,0 18.75,8.39466 18.75,18.75 z"
+           sodipodi:ry="18.75"
+           sodipodi:rx="18.75"
+           sodipodi:cy="396.11218"
+           sodipodi:cx="381.25"
+           id="path3881"
+           
style="fill:#ffffff;fill-opacity:1;stroke:#982f26;stroke-width:6.2157526;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0.7"
+           sodipodi:type="arc" />
+      </g>
+    </g>
+  </g>
+</svg>
diff --git a/src/MapWidget.vala b/src/MapWidget.vala
new file mode 100644
index 0000000..c4d761e
--- /dev/null
+++ b/src/MapWidget.vala
@@ -0,0 +1,120 @@
+ /* Copyright 2015 Yorba Foundation
+ *
+ * This software is licensed under the GNU LGPL (version 2.1 or later).
+ * See the COPYING file in this distribution.
+ */
+
+private class MapWidget : GtkChamplain.Embed {
+    private const uint DEFAULT_ZOOM_LEVEL = 8;
+
+    private static MapWidget? instance = null;
+
+    public Cogl.Handle marker_cogl_texture { get; private set; }
+    
+    private Champlain.View? map_view = null;
+    private Champlain.Scale map_scale = new Champlain.Scale();
+    private Champlain.MarkerLayer marker_layer = new Champlain.MarkerLayer();
+
+    public static MapWidget get_instance() {
+        if (instance == null)
+            instance = new MapWidget();
+        
+        return instance;
+    }
+
+    public void setup_map() {
+        // add scale to bottom left corner of the map
+        map_view = get_view();
+        map_view.add_layer(marker_layer);
+        map_scale.x_align = Clutter.ActorAlign.START;
+        map_scale.y_align = Clutter.ActorAlign.END;
+        map_scale.connect_view(map_view);
+        map_view.add(map_scale);
+
+        map_view.set_zoom_on_double_click(false);
+
+        button_press_event.connect(map_zoom_handler);
+        set_size_request(200, 200);
+
+        // Load gdk pixbuf via Resources class
+        Gdk.Pixbuf gdk_marker = Resources.get_icon(Resources.ICON_GPS_MARKER);
+        try {
+            // this is what GtkClutter.Texture.set_from_pixmap does
+            Clutter.Texture tex = new Clutter.Texture();
+            tex.set_from_rgb_data(gdk_marker.get_pixels(),
+                gdk_marker.get_has_alpha(),
+                gdk_marker.get_width(),
+                gdk_marker.get_height(),
+                gdk_marker.get_rowstride(),
+                gdk_marker.get_has_alpha() ? 4 : 3,
+                Clutter.TextureFlags.NONE);
+            marker_cogl_texture = tex.get_cogl_texture();
+        } catch (GLib.Error e) {
+            // Fall back to the generic champlain marker
+            marker_cogl_texture = null;
+        }
+    }
+
+    public void clear() {
+        marker_layer.remove_all();
+    }
+
+    public void add_position_marker(DataView view) {
+        clear();
+        
+        Positionable? positionable = view.get_source() as Positionable;
+        if (positionable == null)
+            return;
+        
+        GpsCoords? gps_coords = positionable.get_gps_coords();
+        if (gps_coords == null)
+            return;
+        
+        Champlain.Marker marker = create_champlain_marker(gps_coords);
+        marker_layer.add_marker(marker);
+    }
+
+    public void show_position_markers() {
+        if (marker_layer.get_markers().length() != 0) {
+            if (map_view.get_zoom_level() < DEFAULT_ZOOM_LEVEL) {
+                map_view.set_zoom_level(DEFAULT_ZOOM_LEVEL);
+            }
+            Champlain.BoundingBox bbox = marker_layer.get_bounding_box();
+            map_view.ensure_visible(bbox, true);
+        }
+    }
+
+    private Champlain.Marker create_champlain_marker(GpsCoords gps_coords) {
+        Champlain.Marker champlain_marker;
+        if (marker_cogl_texture == null) {
+            // Fall back to the generic champlain marker
+            champlain_marker = new Champlain.Point.full(12, { red:10, green:10, blue:255, alpha:255 });
+        } else {
+            champlain_marker = new Champlain.CustomMarker(); // TODO: deprecated, switch to Champlain.Marker 
once libchamplain-0.12.4 is used
+            Clutter.Texture t = new Clutter.Texture();
+            t.set_cogl_texture(marker_cogl_texture);
+            champlain_marker.add(t);
+        }
+        champlain_marker.set_pivot_point(0.5f, 0.5f); // set center of marker
+        champlain_marker.set_location(gps_coords.latitude, gps_coords.longitude);
+        
+        return champlain_marker;
+    }
+
+    private bool map_zoom_handler(Gdk.EventButton event) {
+        if (event.type == Gdk.EventType.2BUTTON_PRESS) {
+            if (event.button == 1 || event.button == 3) {
+                double lat = map_view.y_to_latitude(event.y);
+                double lon = map_view.x_to_longitude(event.x);
+                if (event.button == 1) {
+                    map_view.zoom_in();
+                } else {
+                    map_view.zoom_out();
+                }
+                map_view.center_on(lat, lon);
+                return true;
+            }
+        }
+        return false;
+    }
+}
diff --git a/src/Page.vala b/src/Page.vala
index 1e6d706..aa87b18 100644
--- a/src/Page.vala
+++ b/src/Page.vala
@@ -1159,6 +1159,10 @@ public abstract class Page : Gtk.ScrolledWindow {
 
         return false;
     }
+
+    public virtual bool is_map_display_enabled(){
+        return false;
+    }
 }
 
 public abstract class CheckerboardPage : Page {
@@ -2401,6 +2405,10 @@ public abstract class SinglePhotoPage : Page {
         
         return (base.key_press_event != null) ? base.key_press_event(event) : true;
     }
+
+    public override bool is_map_display_enabled(){
+        return true;
+    }
 }
 
 //
diff --git a/src/Photo.vala b/src/Photo.vala
index 98a3175..8b5d49e 100644
--- a/src/Photo.vala
+++ b/src/Photo.vala
@@ -158,7 +158,7 @@ public enum Rating {
 // particular photo without modifying the backing image file.  The interface allows for
 // transformations to be stored persistently elsewhere or in memory until they're committed en
 // masse to an image file.
-public abstract class Photo : PhotoSource, Dateable {
+public abstract class Photo : PhotoSource, Dateable, Positionable {
     // Need to use "thumb" rather than "photo" for historical reasons -- this name is used
     // directly to load thumbnails from disk by already-existing filenames
     public const string TYPENAME = "thumb";
@@ -2345,6 +2345,12 @@ public abstract class Photo : PhotoSource, Dateable {
             notify_altered(new Alteration("metadata", "name"));
     }
     
+    public GpsCoords? get_gps_coords() {
+        PhotoMetadata? metadata = get_metadata();
+        
+        return (metadata != null) ? metadata.get_gps_coords() : null;
+    }
+    
     public override bool set_comment(string? comment) {
         string? new_comment = prep_comment(comment);
         
diff --git a/src/Properties.vala b/src/Properties.vala
index 4a86308..1e77ece 100644
--- a/src/Properties.vala
+++ b/src/Properties.vala
@@ -4,12 +4,15 @@
  * See the COPYING file in this distribution.
  */
 
-private abstract class Properties : Gtk.Grid {
-    uint line_count = 0;
+private abstract class Properties : Gtk.VBox {
+    protected Gtk.Grid grid = new Gtk.Grid();
+    protected uint line_count = 0;
 
     public Properties() {
-        row_spacing = 0;
-        column_spacing = 6;
+        grid.column_spacing = 6;
+        set_homogeneous(false);
+        set_spacing(0);
+        pack_start(grid, false, true, 0);
     }
 
     protected void add_line(string label_text, string info_text, bool multi_line = false) {
@@ -48,12 +51,12 @@ private abstract class Properties : Gtk.Grid {
             info = (Gtk.Widget) info_label;
         }
 
-        attach(label, 0, (int) line_count, 1, 1);
+        grid.attach(label, 0, (int) line_count, 1, 1);
 
         if (multi_line) {
-            attach(info, 1, (int) line_count, 1, 2);
+            grid.attach(info, 1, (int) line_count, 2, 1);
         } else {
-            attach(info, 1, (int) line_count, 1, 1);
+            grid.attach(info, 1, (int) line_count, 1, 1);
         }
 
         line_count++;
@@ -126,9 +129,9 @@ private abstract class Properties : Gtk.Grid {
     }
 
     protected virtual void clear_properties() {
-        foreach (Gtk.Widget child in get_children())
-            remove(child);
-        
+        foreach (Gtk.Widget child in grid.get_children())
+            grid.remove(child);
+
         line_count = 0;
     }
 
@@ -143,7 +146,7 @@ private abstract class Properties : Gtk.Grid {
     }
     
     public void unselect_text() {
-        foreach (Gtk.Widget child in get_children()) {
+        foreach (Gtk.Widget child in grid.get_children()) {
             if (child is Gtk.Label)
                 ((Gtk.Label) child).select_region(0, 0);
         }
@@ -164,8 +167,14 @@ private class BasicProperties : Properties {
     private double clip_duration;
     private string raw_developer;
     private string raw_assoc;
+    private GpsCoords? gps_coords;
+    private MapWidget map_widget;
+    private bool map_widget_displayed;
 
     public BasicProperties() {
+        map_widget = MapWidget.get_instance();
+        map_widget.setup_map();
+        this.pack_end(map_widget);
     }
 
     protected override void clear_properties() {
@@ -183,6 +192,8 @@ private class BasicProperties : Properties {
         clip_duration = 0.0;
         raw_developer = "";
         raw_assoc = "";
+        map_widget.clear();
+        gps_coords = null;
     }
 
     protected override void get_single_properties(DataView view) {
@@ -192,7 +203,7 @@ private class BasicProperties : Properties {
 
         title = source.get_name();
         
-        if (source is PhotoSource || source is PhotoImportSource) {           
+        if (source is PhotoSource || source is PhotoImportSource) {
             start_time = (source is PhotoSource) ? ((PhotoSource) source).get_exposure_time() :
                 ((PhotoImportSource) source).get_exposure_time();
             end_time = start_time;
@@ -216,6 +227,9 @@ private class BasicProperties : Properties {
                 dimensions = (metadata.get_pixel_dimensions() != null) ?
                     metadata.get_orientation().rotate_dimensions(metadata.get_pixel_dimensions()) :
                     Dimensions(0, 0);
+
+                gps_coords = metadata.get_gps_coords();
+                map_widget.add_position_marker(view);
             }
             
             if (source is PhotoSource)
@@ -262,8 +276,8 @@ private class BasicProperties : Properties {
         video_count = 0;
         foreach (DataView view in iter) {
             DataSource source = view.get_source();
-            
-            if (source is PhotoSource || source is PhotoImportSource) {                  
+
+            if (source is PhotoSource || source is PhotoImportSource) {
                 time_t exposure_time = (source is PhotoSource) ?
                     ((PhotoSource) source).get_exposure_time() :
                     ((PhotoImportSource) source).get_exposure_time();
@@ -448,6 +462,22 @@ private class BasicProperties : Properties {
                 }
             }
         }
+        
+        if (gps_coords != null && page.is_map_display_enabled()){
+            map_widget_displayed = true;
+            map_widget.show_position_markers();
+        }
+        else
+            map_widget_displayed = false;
+    }
+    
+    public override void show_all() {
+        base.show_all();
+        map_widget.set_visible(map_widget_displayed);
+    }
+    
+    public bool get_map_widget_displayed(){
+        return map_widget_displayed;
     }
 }
 
diff --git a/src/Resources.vala b/src/Resources.vala
index 61bd518..74f3321 100644
--- a/src/Resources.vala
+++ b/src/Resources.vala
@@ -110,6 +110,7 @@ along with Shotwell; if not, write to the Free Software Foundation, Inc.,
     public const string ICON_ZOOM_IN = "zoom-in.png";
     public const string ICON_ZOOM_OUT = "zoom-out.png";
     public const int ICON_ZOOM_SCALE = 16;
+    public const string ICON_GPS_MARKER = "gps-marker.svg";
 
     public const string ICON_CAMERAS = "camera-photo";
     public const string ICON_EVENTS = "multiple-events";
diff --git a/src/core/SourceInterfaces.vala b/src/core/SourceInterfaces.vala
index 59956d3..d49955b 100644
--- a/src/core/SourceInterfaces.vala
+++ b/src/core/SourceInterfaces.vala
@@ -42,3 +42,7 @@ public interface Indexable : DataSource {
     }
 }
 
+// Positionable DataSources provide a globally locatable point in longitude and latitude degrees
+public interface Positionable : DataSource {
+    public abstract GpsCoords? get_gps_coords();
+}
diff --git a/src/library/ImportQueuePage.vala b/src/library/ImportQueuePage.vala
index 5ace1d8..931065b 100644
--- a/src/library/ImportQueuePage.vala
+++ b/src/library/ImportQueuePage.vala
@@ -204,5 +204,9 @@ public class ImportQueuePage : SinglePhotoPage {
     private void on_fatal_error(ImportResult result, string message) {
         AppWindow.error_message(message);
     }
+    
+    public override bool is_map_display_enabled(){
+        return false;
+    }
 }
 
diff --git a/src/library/LibraryWindow.vala b/src/library/LibraryWindow.vala
index 908a892..7229c9a 100644
--- a/src/library/LibraryWindow.vala
+++ b/src/library/LibraryWindow.vala
@@ -1507,8 +1507,12 @@ public class LibraryWindow : AppWindow {
     }
     
     private void on_update_properties_now() {
-        if (bottom_frame.visible)
+        if (bottom_frame.visible){
+            bool map_was_displayed = basic_properties.get_map_widget_displayed();
             basic_properties.update_properties(get_current_page());
+            if (map_was_displayed && !basic_properties.get_map_widget_displayed())
+                sidebar_paned.set_position(1000);
+        }
 
         if (extended_properties.visible)
             extended_properties.update_properties(get_current_page());
diff --git a/src/main.vala b/src/main.vala
index 8c045fd..aa3125b 100644
--- a/src/main.vala
+++ b/src/main.vala
@@ -335,7 +335,7 @@ void main(string[] args) {
     
     // init GTK (valac has already called g_threads_init())
     try {
-        Gtk.init_with_args(ref args, _("[FILE]"), CommandlineOptions.get_options(),
+        GtkClutter.init_with_args(ref args, _("[FILE]"), CommandlineOptions.get_options(),
             Resources.APP_GETTEXT_PACKAGE);
     } catch (Error e) {
         print(e.message + "\n");
diff --git a/src/photos/PhotoMetadata.vala b/src/photos/PhotoMetadata.vala
index 37804bf..102a456 100644
--- a/src/photos/PhotoMetadata.vala
+++ b/src/photos/PhotoMetadata.vala
@@ -96,6 +96,22 @@ public abstract class PhotoPreview {
     }
 }
 
+public class GpsCoords {
+    public double latitude { get; private set; }
+    public double longitude { get; private set; }
+    public double altitude { get; private set; }
+    
+    public GpsCoords(double latitude, double longitude, double altitude) {
+        this.latitude = latitude;
+        this.longitude = longitude;
+        this.altitude = altitude;
+    }
+    
+    public bool equal_to(GpsCoords other) {
+        return latitude == other.latitude && longitude == other.longitude && altitude == other.altitude;
+    }
+}
+
 public class PhotoMetadata : MediaMetadata {
     public enum SetOption {
         ALL_DOMAINS,
@@ -1009,6 +1025,20 @@ public class PhotoMetadata : MediaMetadata {
         return true;
     }
     
+    public GpsCoords? get_gps_coords() {
+        double latitude, longitude, altitude;
+        if (!exiv2.get_gps_info(out longitude, out latitude, out altitude))
+            return null;
+        
+        if (get_string("Exif.GPSInfo.GPSLongitudeRef") == "W" && longitude > 0)
+            longitude = -longitude;
+        
+        if (get_string("Exif.GPSInfo.GPSLatitudeRef") == "S" && latitude > 0)
+            latitude = -latitude;
+        
+        return new GpsCoords(latitude, longitude, altitude);
+    }
+    
     public bool get_exposure(out MetadataRational exposure) {
         return get_rational("Exif.Photo.ExposureTime", out exposure);
     }


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