[shotwell] map: Initial Map-Widget
- From: Jens Georg <jensgeorg src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [shotwell] map: Initial Map-Widget
- Date: Sat, 23 Feb 2019 19:02:04 +0000 (UTC)
commit 35346fc1087169d2b22923ac880a50eba0779717
Author: Andreas Brauchli <a brauchli elementarea net>
Date: Thu Jul 14 13:46:00 2016 +0200
map: Initial Map-Widget
Initial map widget support:
- Add map widget to properties
- Increase the DB schema to v21:
Add position (gps) metadata to DB
- marker vector graphics courtesy of Alexander Wilms licensed
CC0 https://creativecommons.org/publicdomain/zero/1.0/
.../scalable/actions/gps-marker-selected.svg | 427 +++++++++++++++++++++
data/icons/hicolor/scalable/actions/gps-marker.svg | 370 ++++++++++++++++++
src/MapWidget.vala | 397 +++++++++++++++++++
src/MetadataWriter.vala | 10 +-
src/Page.vala | 7 +-
src/Photo.vala | 57 ++-
src/Properties.vala | 42 +-
src/Resources.vala | 2 +
src/core/SourceInterfaces.vala | 15 +
src/db/DatabaseTable.vala | 15 +-
src/db/Db.vala | 18 +-
src/db/PhotoTable.vala | 82 +++-
src/library/LibraryWindow.vala | 9 +-
src/main.vala | 2 +-
src/photos/PhotoMetadata.vala | 22 +-
src/util/misc.vala | 4 +-
16 files changed, 1428 insertions(+), 51 deletions(-)
---
diff --git a/data/icons/hicolor/scalable/actions/gps-marker-selected.svg
b/data/icons/hicolor/scalable/actions/gps-marker-selected.svg
new file mode 100644
index 00000000..4f65c1db
--- /dev/null
+++ b/data/icons/hicolor/scalable/actions/gps-marker-selected.svg
@@ -0,0 +1,427 @@
+<?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.742239"
+ height="38.981468"
+ id="svg2"
+ version="1.1"
+ inkscape:version="0.48.3.1 r9886"
+ sodipodi:docname="gps-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" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3787-3"
+ id="radialGradient3909"
+ 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
+ id="linearGradient3787-3">
+ <stop
+ style="stop-color:#535353;stop-opacity:1;"
+ offset="0"
+ id="stop3789-9" />
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0;"
+ offset="1"
+ id="stop3791-3" />
+ </linearGradient>
+ <filter
+ inkscape:collect="always"
+ id="filter3803-2"
+ 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-5" />
+ </filter>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3765-3"
+ id="linearGradient3911"
+ 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" />
+ <linearGradient
+ id="linearGradient3765-3">
+ <stop
+ style="stop-color:#23b3ff;stop-opacity:1;"
+ offset="0"
+ id="stop3767-3" />
+ <stop
+ style="stop-color:#124cd1;stop-opacity:0.92490119;"
+ offset="1"
+ id="stop3769-4" />
+ </linearGradient>
+ </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.118743"
+ inkscape:cy="60.73634"
+ 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.5341px"
+ originy="-506.68832px" />
+ </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.5341,-506.6911)">
+ <g
+ transform="translate(-76.094785,7.3864159e-4)"
+ id="g3042">
+ <g
+ transform="translate(22,75.224057)"
+ id="g3895">
+ <path
+ sodipodi:type="arc"
+
style="opacity:0.58662612;fill:url(#radialGradient3909);fill-opacity:1;stroke:none;filter:url(#filter3803-2)"
+ id="path3785"
+ sodipodi:cx="374.25"
+ sodipodi:cy="464.11218"
+ sodipodi:rx="8.25"
+ sodipodi:ry="2.75"
+ 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"
+ transform="matrix(1.3594635,0,0,1,-133.77921,1)" />
+ <path
+
style="fill:url(#linearGradient3911);fill-opacity:1;stroke:#0b3e83;stroke-width:1;stroke-miterlimit:4;stroke-opacity:0.96862745;stroke-dasharray:none"
+ d="m 375,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"
+ id="path2985"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="sszczss" />
+ <path
+ sodipodi:nodetypes="sszczss"
+ inkscape:connector-curvature="0"
+ id="path3775"
+ d="m 375,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"
+
style="fill:none;stroke:#69a3f2;stroke-width:1.03512061;stroke-miterlimit:4;stroke-opacity:0.96862745;stroke-dasharray:none"
/>
+ <path
+ transform="matrix(0.23144871,0,0,0.23144871,286.76018,350.69905)"
+ 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="path3783"
+
style="fill:#ffffff;fill-opacity:1;stroke:#5e9cf1;stroke-width:4.32061148;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:0.96862745;stroke-dasharray:none;stroke-dashoffset:0.7"
+ sodipodi:type="arc" />
+ <path
+ sodipodi:type="arc"
+
style="fill:#ffffff;fill-opacity:1;stroke:#0c438d;stroke-width:6.2157526;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:0.96862745;stroke-dasharray:none;stroke-dashoffset:0.7"
+ id="path3779"
+ 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.17705667,0,0,0.17705667,307.49715,372.2444)" />
+ </g>
+ </g>
+ </g>
+</svg>
diff --git a/data/icons/hicolor/scalable/actions/gps-marker.svg
b/data/icons/hicolor/scalable/actions/gps-marker.svg
new file mode 100644
index 00000000..564a38a4
--- /dev/null
+++ b/data/icons/hicolor/scalable/actions/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 00000000..87777e36
--- /dev/null
+++ b/src/MapWidget.vala
@@ -0,0 +1,397 @@
+/* Copyright 2016 Software Freedom Conservancy Inc.
+ *
+ * This software is licensed under the GNU LGPL (version 2.1 or later).
+ * See the COPYING file in this distribution.
+ */
+
+private class PositionMarker : Object {
+ private MapWidget map_widget;
+
+ protected PositionMarker.from_group(MapWidget map_widget) {
+ this.map_widget = map_widget;
+ }
+
+ public PositionMarker(MapWidget map_widget, DataView view, Champlain.Marker marker) {
+ this.map_widget = map_widget;
+ this.view = view;
+ marker.selectable = true;
+ marker.button_release_event.connect ((event) => {
+ if (event.button > 1)
+ return true;
+ map_widget.select_data_view(this);
+ return true;
+ });
+ marker.enter_event.connect ((event) => {
+ map_widget.highlight_data_view(this);
+ return true;
+ });
+ marker.leave_event.connect ((event) => {
+ map_widget.unhighlight_data_view(this);
+ return true;
+ });
+ this.marker = marker;
+ }
+
+ public bool selected {
+ get {
+ return marker.get_selected();
+ }
+ set {
+ marker.set_selected(value);
+ if (!(marker is Champlain.Point)) {
+ // first child of the marker is a ClutterGroup which contains the texture
+ var t = (Clutter.Texture) marker.get_first_child().get_first_child();
+ if (value) {
+ t.set_cogl_texture(map_widget.marker_selected_cogl_texture);
+ } else {
+ t.set_cogl_texture(map_widget.marker_cogl_texture);
+ }
+ }
+ }
+ }
+
+ public Champlain.Marker marker { get; protected set; }
+ // Geo lookup
+ // public string location_country { get; set; }
+ // public string location_city { get; set; }
+ public unowned DataView view { get; protected set; }
+}
+
+private class MarkerGroup : PositionMarker {
+ private Gee.Set<PositionMarker> markers = new Gee.HashSet<PositionMarker>();
+ public MarkerGroup(MapWidget map_widget, PositionMarker first_marker) {
+ base.from_group(map_widget);
+ markers.add(first_marker);
+ // use the first markers internal texture as the group's
+ marker = first_marker.marker;
+ view = first_marker.view;
+ }
+ public void add_marker(PositionMarker marker) {
+ markers.add(marker);
+ }
+ public Gee.Set<PositionMarker> get_markers() {
+ return markers;
+ }
+}
+
+private class MapWidget : Gtk.Bin {
+ private const uint DEFAULT_ZOOM_LEVEL = 8;
+ private const long MARKER_GROUP_RASTER_WIDTH = 30l;
+
+ private static MapWidget instance = null;
+
+ private GtkChamplain.Embed gtk_champlain_widget = new GtkChamplain.Embed();
+ private Champlain.View map_view = null;
+ private uint last_zoom_level = DEFAULT_ZOOM_LEVEL;
+ private Champlain.Scale map_scale = new Champlain.Scale();
+ private Champlain.MarkerLayer marker_layer = new Champlain.MarkerLayer();
+ private Gee.Map<DataView, PositionMarker> position_markers =
+ new Gee.HashMap<DataView, PositionMarker>();
+ private Gee.TreeMap<long, Gee.TreeMap<long, MarkerGroup>> marker_groups_tree =
+ new Gee.TreeMap<long, Gee.TreeMap<long, MarkerGroup>>();
+ private Gee.Collection<MarkerGroup> marker_groups = new Gee.LinkedList<MarkerGroup>();
+ private unowned Page page = null;
+
+ public Cogl.Handle marker_cogl_texture { get; private set; }
+ public Cogl.Handle marker_selected_cogl_texture { get; private set; }
+
+ private MapWidget() {
+ add(gtk_champlain_widget);
+ setup_map();
+ }
+
+ public static MapWidget get_instance() {
+ if (instance == null)
+ instance = new MapWidget();
+ return instance;
+ }
+
+ public override void drag_data_received(Gdk.DragContext context, int x, int y,
+ Gtk.SelectionData selection_data, uint info, uint time) {
+ bool success = false;
+ Gee.List<MediaSource>? media = unserialize_media_sources(selection_data.get_data(),
+ selection_data.get_length());
+ if (media != null && media.size > 0) {
+ double lat = map_view.y_to_latitude(y);
+ double lon = map_view.x_to_longitude(x);
+ success = internal_drop_received(media, lat, lon);
+ }
+
+ Gtk.drag_finish(context, success, false, time);
+ }
+
+ public void set_page(Page page) {
+ this.page = page;
+ }
+
+ public void clear() {
+ marker_layer.remove_all();
+ marker_groups_tree.clear();
+ marker_groups.clear();
+ position_markers.clear();
+ }
+
+ public void add_position_marker(DataView view) {
+ DataSource view_source = view.get_source();
+ if (!(view_source is Positionable)) {
+ return;
+ }
+ Positionable p = (Positionable) view_source;
+ GpsCoords gps_coords = p.get_gps_coords();
+ if (gps_coords.has_gps <= 0) {
+ return;
+ }
+
+ // rasterize coords
+ long x = (long)(map_view.longitude_to_x(gps_coords.longitude) / MARKER_GROUP_RASTER_WIDTH);
+ long y = (long)(map_view.latitude_to_y(gps_coords.latitude) / MARKER_GROUP_RASTER_WIDTH);
+ PositionMarker position_marker = create_position_marker(view);
+ var yg = marker_groups_tree.get(x);
+ if (yg == null) {
+ // y group doesn't exist, initialize it
+ yg = new Gee.TreeMap<long, MarkerGroup>();
+ var mg = new MarkerGroup(this, position_marker);
+ yg.set(y, mg);
+ marker_groups.add(mg);
+ marker_groups_tree.set(x, yg);
+ add_marker(mg.marker);
+ } else {
+ var mg = yg.get(y);
+ if (mg == null) {
+ // first marker in this group
+ mg = new MarkerGroup(this, position_marker);
+ yg.set(y, mg);
+ marker_groups.add(mg);
+ add_marker(mg.marker);
+ } else {
+ // marker group already exists
+ mg.add_marker(position_marker);
+ }
+ }
+
+ position_markers.set(view, position_marker);
+ }
+
+ public void show_position_markers() {
+ if (!position_markers.is_empty) {
+ 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);
+ }
+ }
+
+ public void select_data_view(PositionMarker m) {
+ ViewCollection page_view = null;
+ if (page != null)
+ page_view = page.get_view();
+ if (page_view != null && m.view is CheckerboardItem) {
+ Marker marked = page_view.start_marking();
+ marked.mark(m.view);
+ page_view.unselect_all();
+ page_view.select_marked(marked);
+ }
+ }
+
+ public void highlight_data_view(PositionMarker m) {
+ if (page != null && m.view is CheckerboardItem) {
+ CheckerboardItem item = (CheckerboardItem) m.view;
+
+ // if item is in any way out of view, scroll to it
+ Gtk.Adjustment vadj = page.get_vadjustment();
+
+ if (!(get_adjustment_relation(vadj, item.allocation.y) == AdjustmentRelation.IN_RANGE
+ && (get_adjustment_relation(vadj, item.allocation.y + item.allocation.height) ==
AdjustmentRelation.IN_RANGE))) {
+
+ // scroll to see the new item
+ int top = 0;
+ if (item.allocation.y < vadj.get_value()) {
+ top = item.allocation.y;
+ top -= CheckerboardLayout.ROW_GUTTER_PADDING / 2;
+ } else {
+ top = item.allocation.y + item.allocation.height - (int) vadj.get_page_size();
+ top += CheckerboardLayout.ROW_GUTTER_PADDING / 2;
+ }
+
+ vadj.set_value(top);
+ }
+ item.brighten();
+ }
+ }
+
+ public void unhighlight_data_view(PositionMarker m) {
+ if (page != null && m.view is CheckerboardItem) {
+ CheckerboardItem item = (CheckerboardItem) m.view;
+ item.unbrighten();
+ }
+ }
+
+ public void highlight_position_marker(DataView v) {
+ PositionMarker? m = position_markers.get(v);
+ if (m != null) {
+ m.selected = true;
+ }
+ }
+
+ public void unhighlight_position_marker(DataView v) {
+ PositionMarker? m = position_markers.get(v);
+ if (m != null) {
+ m.selected = false;
+ }
+ }
+
+ private void setup_map() {
+ map_view = gtk_champlain_widget.get_view();
+ map_view.add_layer(marker_layer);
+
+ // add scale to bottom left corner of the map
+ map_scale.content_gravity = Clutter.ContentGravity.BOTTOM_LEFT;
+ map_scale.connect_view(map_view);
+ map_view.bin_layout_add(map_scale, Clutter.BinAlignment.START, Clutter.BinAlignment.END);
+
+ map_view.set_zoom_on_double_click(false);
+ map_view.layer_relocated.connect(map_relocated_handler);
+
+ Gtk.TargetEntry[] dnd_targets = {
+ LibraryWindow.DND_TARGET_ENTRIES[LibraryWindow.TargetType.URI_LIST],
+ LibraryWindow.DND_TARGET_ENTRIES[LibraryWindow.TargetType.MEDIA_LIST]
+ };
+ Gtk.drag_dest_set(this, Gtk.DestDefaults.ALL, dnd_targets,
+ Gdk.DragAction.COPY | Gdk.DragAction.LINK | Gdk.DragAction.ASK);
+ 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);
+ Gdk.Pixbuf gdk_marker_selected = Resources.get_icon(Resources.ICON_GPS_MARKER_SELECTED);
+ try {
+ // this is what GtkClutter.Texture.set_from_pixmap does
+ var tex = new Clutter.Texture(); // TODO: DEPRECATED Use Clutter.Image
+ 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();
+ tex.set_from_rgb_data(gdk_marker_selected.get_pixels(),
+ gdk_marker_selected.get_has_alpha(),
+ gdk_marker_selected.get_width(),
+ gdk_marker_selected.get_height(),
+ gdk_marker_selected.get_rowstride(),
+ gdk_marker_selected.get_has_alpha() ? 4 : 3,
+ Clutter.TextureFlags.NONE);
+ marker_selected_cogl_texture = tex.get_cogl_texture();
+ } catch (GLib.Error e) {
+ // Fall back to the generic champlain marker
+ marker_cogl_texture = null;
+ marker_selected_cogl_texture = null;
+ }
+ }
+
+ private PositionMarker create_position_marker(DataView view) {
+ DataSource data_source = view.get_source();
+ Positionable p = (Positionable) data_source;
+ GpsCoords gps_coords = p.get_gps_coords();
+ assert(gps_coords.has_gps > 0);
+ 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.Marker();
+ var t = new Clutter.Texture();
+ t.set_cogl_texture(marker_cogl_texture);
+ champlain_marker.add_child(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 new PositionMarker(this, view, champlain_marker);
+ }
+
+ private void add_marker(Champlain.Marker marker) {
+ marker_layer.add_marker(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;
+ }
+
+ private void map_relocated_handler() {
+ uint new_zoom_level = map_view.get_zoom_level();
+ if (last_zoom_level != new_zoom_level) {
+ rezoom();
+ last_zoom_level = new_zoom_level;
+ }
+ }
+
+ private void rezoom() {
+ marker_groups_tree.clear();
+ Gee.Collection<MarkerGroup> marker_groups_new = new Gee.LinkedList<MarkerGroup>();
+ foreach (var marker_group in marker_groups) {
+ marker_layer.remove_marker(marker_group.marker);
+ foreach (var position_marker in marker_group.get_markers()) {
+ // rasterize coords
+ long x = (long)(map_view.longitude_to_x(position_marker.marker.longitude) /
MARKER_GROUP_RASTER_WIDTH);
+ long y = (long)(map_view.latitude_to_y(position_marker.marker.latitude) /
MARKER_GROUP_RASTER_WIDTH);
+ var yg = marker_groups_tree.get(x);
+ if (yg == null) {
+ // y group doesn't exist, initialize it
+ yg = new Gee.TreeMap<long, MarkerGroup>();
+ var mg = new MarkerGroup(this, position_marker);
+ yg.set(y, mg);
+ marker_groups_new.add(mg);
+ marker_groups_tree.set(x, yg);
+ add_marker(mg.marker);
+ } else {
+ var mg = yg.get(y);
+ if (mg == null) {
+ // first marker -> create new group
+ mg = new MarkerGroup(this, position_marker);
+ yg.set(y, mg);
+ marker_groups_new.add(mg);
+ add_marker(mg.marker);
+ } else {
+ // marker group already exists
+ mg.add_marker(position_marker);
+ }
+ }
+ }
+ }
+ marker_groups = marker_groups_new;
+ }
+
+ private bool internal_drop_received(Gee.List<MediaSource> media, double lat, double lon) {
+ int i = 0;
+ bool success = false;
+ while (i < media.size) {
+ Positionable p = media.get(i) as Positionable;
+ if (p != null) {
+ GpsCoords gps_coords = GpsCoords() {
+ has_gps = 1,
+ latitude = lat,
+ longitude = lon
+ };
+ p.set_gps_coords(gps_coords);
+ success = true;
+ }
+ ++i;
+ }
+ return success;
+ }
+}
diff --git a/src/MetadataWriter.vala b/src/MetadataWriter.vala
index 0c232605..f55a11eb 100644
--- a/src/MetadataWriter.vala
+++ b/src/MetadataWriter.vala
@@ -15,7 +15,7 @@ public class MetadataWriter : Object {
public const uint COMMIT_DELAY_MSEC = 3000;
public const uint COMMIT_SPACING_MSEC = 50;
- private const string[] INTERESTED_PHOTO_METADATA_DETAILS = { "name", "comment", "rating",
"exposure-time" };
+ private const string[] INTERESTED_PHOTO_METADATA_DETAILS = { "name", "comment", "rating",
"exposure-time", "gps" };
private class CommitJob : BackgroundJob {
public LibraryPhoto photo;
@@ -120,6 +120,14 @@ public class MetadataWriter : Object {
changed = true;
}
+ // gps location
+ GpsCoords current_gps_coords = photo.get_gps_coords();
+ GpsCoords metadata_gps_coords = metadata.get_gps_coords();
+ if (!current_gps_coords.equals(ref metadata_gps_coords)) {
+ metadata.set_gps_coords(current_gps_coords);
+ changed = true;
+ }
+
// tags (keywords) ... replace (or clear) entirely rather than union or intersection
Gee.Set<string> safe_keywords = new Gee.HashSet<string>();
diff --git a/src/Page.vala b/src/Page.vala
index 93cedd56..666dbdf1 100644
--- a/src/Page.vala
+++ b/src/Page.vala
@@ -1242,6 +1242,7 @@ public abstract class CheckerboardPage : Page {
private bool autoscroll_scheduled = false;
private CheckerboardItem activated_item = null;
private Gee.ArrayList<CheckerboardItem> previously_selected = null;
+ private MapWidget map_widget = null;
public enum Activator {
KEYBOARD,
@@ -1298,6 +1299,8 @@ public abstract class CheckerboardPage : Page {
// scrollbar policy
set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC);
+
+ map_widget = MapWidget.get_instance();
}
public void init_item_context_menu(string path) {
@@ -1751,6 +1754,7 @@ public abstract class CheckerboardPage : Page {
// either something new is highlighted or now hovering over empty space, so dim old item
if (current_hovered_item != null) {
current_hovered_item.handle_mouse_leave();
+ map_widget.unhighlight_position_marker(current_hovered_item);
current_hovered_item = null;
}
@@ -1761,7 +1765,8 @@ public abstract class CheckerboardPage : Page {
// brighten the new item
current_hovered_item = item;
current_hovered_item.handle_mouse_enter();
-
+ map_widget.highlight_position_marker(item);
+
return true;
}
diff --git a/src/Photo.vala b/src/Photo.vala
index a858ae31..9135eea5 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";
@@ -1222,6 +1222,7 @@ public abstract class Photo : PhotoSource, Dateable {
Orientation orientation = Orientation.TOP_LEFT;
time_t exposure_time = 0;
string title = "";
+ GpsCoords gps_coords = GpsCoords();
string comment = "";
Rating rating = Rating.UNRATED;
@@ -1237,6 +1238,7 @@ public abstract class Photo : PhotoSource, Dateable {
orientation = detected.metadata.get_orientation();
title = detected.metadata.get_title();
+ gps_coords = detected.metadata.get_gps_coords();
comment = detected.metadata.get_comment();
params.keywords = detected.metadata.get_keywords();
rating = detected.metadata.get_rating();
@@ -1272,6 +1274,7 @@ public abstract class Photo : PhotoSource, Dateable {
params.row.flags = 0;
params.row.master.file_format = detected.file_format;
params.row.title = title;
+ params.row.gps_coords = gps_coords;
params.row.comment = comment;
params.row.rating = rating;
@@ -1313,6 +1316,7 @@ public abstract class Photo : PhotoSource, Dateable {
params.row.flags = 0;
params.row.master.file_format = PhotoFileFormat.JFIF;
params.row.title = null;
+ params.row.gps_coords = GpsCoords();
params.row.comment = null;
params.row.rating = Rating.UNRATED;
@@ -1465,7 +1469,9 @@ public abstract class Photo : PhotoSource, Dateable {
list += "image:orientation";
updated_row.master.original_orientation = backing.original_orientation;
}
-
+
+ GpsCoords gps_coords = GpsCoords();
+
if (detected.metadata != null) {
MetadataDateTime? date_time = detected.metadata.get_exposure_date_time();
if (date_time != null && updated_row.exposure_time != date_time.get_timestamp())
@@ -1473,6 +1479,11 @@ public abstract class Photo : PhotoSource, Dateable {
if (updated_row.title != detected.metadata.get_title())
list += "metadata:name";
+
+ gps_coords = detected.metadata.get_gps_coords();
+ if (updated_row.gps_coords != gps_coords)
+ list += "metadata:gps";
+
if (updated_row.comment != detected.metadata.get_comment())
list += "metadata:comment";
@@ -1493,7 +1504,8 @@ public abstract class Photo : PhotoSource, Dateable {
MetadataDateTime? date_time = detected.metadata.get_exposure_date_time();
if (date_time != null)
updated_row.exposure_time = date_time.get_timestamp();
-
+
+ updated_row.gps_coords = gps_coords;
updated_row.title = detected.metadata.get_title();
updated_row.comment = detected.metadata.get_comment();
updated_row.rating = detected.metadata.get_rating();
@@ -1604,6 +1616,7 @@ public abstract class Photo : PhotoSource, Dateable {
if (reimport_state.metadata != null) {
set_title(reimport_state.metadata.get_title());
+ set_gps_coords(reimport_state.metadata.get_gps_coords());
set_comment(reimport_state.metadata.get_comment());
set_rating(reimport_state.metadata.get_rating());
apply_user_metadata_for_reimport(reimport_state.metadata);
@@ -2365,6 +2378,29 @@ public abstract class Photo : PhotoSource, Dateable {
if (committed)
notify_altered(new Alteration("metadata", "name"));
}
+
+ public GpsCoords get_gps_coords() {
+ lock (row) {
+ return row.gps_coords;
+ }
+ }
+
+ public void set_gps_coords(GpsCoords gps_coords) {
+ DatabaseError dberr = null;
+ lock (row) {
+ try {
+ PhotoTable.get_instance().set_gps_coords(row.photo_id, gps_coords);
+ row.gps_coords = gps_coords;
+ } catch (DatabaseError err) {
+ dberr = err;
+ }
+ }
+ if (dberr == null)
+ notify_altered(new Alteration("metadata", "gps"));
+ else
+ warning("Unable to write gps coordinates for %s: %s", to_string(), dberr.message);
+ }
+
public override bool set_comment(string? comment) {
string? new_comment = prep_comment(comment);
@@ -3215,6 +3251,7 @@ public abstract class Photo : PhotoSource, Dateable {
double orientation_time = 0.0;
total_timer.start();
+
#endif
// get required fields all at once, to avoid holding the row lock
@@ -4979,7 +5016,12 @@ public class LibraryPhoto : Photo, Flaggable, Monitorable {
this.import_keywords = null;
thumbnail_scheduler = new OneShotScheduler("LibraryPhoto", generate_thumbnails);
-
+ // import gps coords of photos imported with prior versions of shotwell
+ if (row.gps_coords.has_gps == -1) {
+ var gps_import_scheduler = new OneShotScheduler("LibraryPhoto", import_gps_metadata);
+ gps_import_scheduler.at_priority_idle(Priority.LOW);
+ }
+
// if marked in a state where they're held in an orphanage, rehydrate their backlinks
if ((row.flags & (FLAG_TRASH | FLAG_OFFLINE)) != 0)
rehydrate_backlinks(global, row.backlinks);
@@ -5100,7 +5142,12 @@ public class LibraryPhoto : Photo, Flaggable, Monitorable {
// fire signal that thumbnails have changed
notify_thumbnail_altered();
}
-
+
+ private void import_gps_metadata() {
+ GpsCoords gps_coords = get_metadata().get_gps_coords();
+ set_gps_coords(gps_coords);
+ }
+
// These keywords are only used during import and should not be relied upon elsewhere.
public Gee.Collection<string>? get_import_keywords() {
return import_keywords;
diff --git a/src/Properties.vala b/src/Properties.vala
index 2a07bb00..c7a094a2 100644
--- a/src/Properties.vala
+++ b/src/Properties.vala
@@ -4,12 +4,16 @@
* See the COPYING file in this distribution.
*/
-private abstract class Properties : Gtk.Grid {
- uint line_count = 0;
+private abstract class Properties : Gtk.Box {
+ protected Gtk.Grid grid = new Gtk.Grid();
+ protected uint line_count = 0;
public Properties() {
- row_spacing = 6;
- column_spacing = 12;
+ grid.row_spacing = 6;
+ grid.column_spacing = 12;
+ set_homogeneous(false);
+ set_orientation(Gtk.Orientation.VERTICAL);
+ pack_start(grid, false, false, 0);
}
protected void add_line(string label_text, string info_text, bool multi_line = false, string? href =
null) {
@@ -62,12 +66,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, 3);
+ grid.attach(info, 1, (int) line_count, 1, 3);
} else {
- attach(info, 1, (int) line_count, 1, 1);
+ grid.attach(info, 1, (int) line_count, 1, 1);
}
line_count++;
@@ -140,9 +144,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;
}
@@ -171,8 +175,11 @@ private class BasicProperties : Properties {
private double clip_duration;
private string raw_developer;
private string raw_assoc;
+ private MapWidget map_widget;
public BasicProperties() {
+ map_widget = MapWidget.get_instance();
+ pack_start(map_widget, true, true, 0);
}
protected override void clear_properties() {
@@ -190,6 +197,7 @@ private class BasicProperties : Properties {
clip_duration = 0.0;
raw_developer = "";
raw_assoc = "";
+ map_widget.clear();
}
protected override void get_single_properties(DataView view) {
@@ -260,6 +268,8 @@ private class BasicProperties : Properties {
}
end_time = start_time;
}
+ map_widget.add_position_marker(view);
+
}
protected override void get_multiple_properties(Gee.Iterable<DataView>? iter) {
@@ -269,8 +279,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();
@@ -282,7 +292,7 @@ private class BasicProperties : Properties {
if (end_time == 0 || exposure_time > end_time)
end_time = exposure_time;
}
-
+
photo_count++;
} else if (source is EventSource) {
EventSource event_source = (EventSource) source;
@@ -324,12 +334,14 @@ private class BasicProperties : Properties {
video_count++;
}
+ map_widget.add_position_marker(view);
}
}
protected override void get_properties(Page current_page) {
base.get_properties(current_page);
+ map_widget.set_page(current_page);
if (end_time == 0)
end_time = start_time;
if (start_time == 0)
@@ -455,6 +467,8 @@ private class BasicProperties : Properties {
}
}
}
+
+ map_widget.show_position_markers();
}
}
@@ -485,7 +499,7 @@ private class ExtendedProperties : Properties {
public ExtendedProperties() {
base();
- row_spacing = 6;
+ grid.row_spacing = 6;
}
// Event stuff
diff --git a/src/Resources.vala b/src/Resources.vala
index 689b4da3..54c109c6 100644
--- a/src/Resources.vala
+++ b/src/Resources.vala
@@ -82,6 +82,8 @@ along with Shotwell; if not, write to the Free Software Foundation, Inc.,
public const int ICON_FILTER_REJECTED_OR_BETTER_FIXED_SIZE = 32;
public const int ICON_FILTER_UNRATED_OR_BETTER_FIXED_SIZE = 16;
public const int ICON_ZOOM_SCALE = 16;
+ public const string ICON_GPS_MARKER = "gps-marker.svg";
+ public const string ICON_GPS_MARKER_SELECTED = "gps-marker-selected.svg";
public const string ICON_CAMERAS = "camera-photo-symbolic";
public const string ICON_EVENTS = "multiple-events-symbolic";
diff --git a/src/core/SourceInterfaces.vala b/src/core/SourceInterfaces.vala
index 91a8acad..6e0c149a 100644
--- a/src/core/SourceInterfaces.vala
+++ b/src/core/SourceInterfaces.vala
@@ -42,3 +42,18 @@ public interface Indexable : DataSource {
}
}
+// Positionable DataSources provide a globally locatable point in longitude and latitude degrees
+
+public struct GpsCoords {
+ public int has_gps;
+ public double latitude;
+ public double longitude;
+ public bool equals(ref GpsCoords gps) {
+ return (has_gps == 0 && gps.has_gps == 0) || (latitude == gps.latitude && longitude ==
gps.longitude);
+ }
+}
+
+public interface Positionable : DataSource {
+ public abstract GpsCoords get_gps_coords();
+ public abstract void set_gps_coords(GpsCoords gps_coords);
+}
diff --git a/src/db/DatabaseTable.vala b/src/db/DatabaseTable.vala
index 64ef9cd7..dc69f2c5 100644
--- a/src/db/DatabaseTable.vala
+++ b/src/db/DatabaseTable.vala
@@ -21,6 +21,7 @@ public abstract class DatabaseTable {
* tables are created on demand and tables and columns are easily ignored when already present.
* However, the change should be noted in upgrade_database() as a comment.
***/
+ public const int SCHEMA_VERSION = 21;
#if ENABLE_FACES
public const int SCHEMA_VERSION = 21;
#else
@@ -291,7 +292,19 @@ public abstract class DatabaseTable {
if (res != Sqlite.DONE)
throw_error("DatabaseTable.update_int64_by_id_2 %s.%s".printf(table_name, column), res);
}
-
+
+ protected void update_double_by_id_2(int64 id, string column, double value) throws DatabaseError {
+ Sqlite.Statement stmt;
+ prepare_update_by_id(id, column, out stmt);
+
+ int res = stmt.bind_double(1, value);
+ assert(res == Sqlite.OK);
+
+ res = stmt.step();
+ if (res != Sqlite.DONE)
+ throw_error("DatabaseTable.update_double_by_id_2 %s.%s".printf(table_name, column), res);
+ }
+
protected void delete_by_id(int64 id) throws DatabaseError {
Sqlite.Statement stmt;
int res = db.prepare_v2("DELETE FROM %s WHERE id=?".printf(table_name), -1, out stmt);
diff --git a/src/db/Db.vala b/src/db/Db.vala
index ac24f113..34646324 100644
--- a/src/db/Db.vala
+++ b/src/db/Db.vala
@@ -293,7 +293,7 @@ private VerifyResult upgrade_database(int input_version) {
}
version = 16;
-
+
//
// Version 17:
// * Added comment column to PhotoTable and VideoTable
@@ -376,10 +376,24 @@ private VerifyResult upgrade_database(int input_version) {
version = 21;
#endif
+ //
+ // Version 21:
+ // * Add has_gps, gps_lat and gps_lon columns to PhotoTable
+
+ if (!DatabaseTable.ensure_column("PhotoTable", "has_gps", "INTEGER DEFAULT -1",
+ "upgrade_database: adding gps_lat column to PhotoTable")
+ || !DatabaseTable.ensure_column("PhotoTable", "gps_lat", "REAL",
+ "upgrade_database: adding gps_lat column to PhotoTable")
+ || !DatabaseTable.ensure_column("PhotoTable", "gps_lon", "REAL",
+ "upgrade_database: adding gps_lon column to PhotoTable")) {
+ return VerifyResult.UPGRADE_ERROR;
+ }
+
+ version = 21;
//
// Finalize the upgrade process
//
-
+
assert(version == DatabaseTable.SCHEMA_VERSION);
VersionTable.get_instance().update_version(version, Resources.APP_VERSION);
diff --git a/src/db/PhotoTable.vala b/src/db/PhotoTable.vala
index 24cec863..369c0a31 100644
--- a/src/db/PhotoTable.vala
+++ b/src/db/PhotoTable.vala
@@ -84,6 +84,7 @@ public class PhotoRow {
public uint64 flags;
public Rating rating;
public string title;
+ public GpsCoords gps_coords;
public string comment;
public string? backlinks;
public time_t time_reimported;
@@ -103,6 +104,10 @@ public class PhotoRow {
development_ids = new BackingPhotoID[RawDeveloper.as_array().length];
foreach (RawDeveloper d in RawDeveloper.as_array())
development_ids[d] = BackingPhotoID();
+ gps_coords = GpsCoords();
+ development_ids = new BackingPhotoID[RawDeveloper.as_array().length];
+ foreach (RawDeveloper d in RawDeveloper.as_array())
+ development_ids[d] = BackingPhotoID();
}
}
@@ -140,6 +145,9 @@ public class PhotoTable : DatabaseTable {
+ "develop_shotwell_id INTEGER DEFAULT -1, "
+ "develop_camera_id INTEGER DEFAULT -1, "
+ "develop_embedded_id INTEGER DEFAULT -1, "
+ + "has_gps INTEGER DEFAULT -1, "
+ + "gps_lat REAL, "
+ + "gps_lon REAL, "
+ "comment TEXT"
+ ")", -1, out stmt);
assert(res == Sqlite.OK);
@@ -209,8 +217,8 @@ public class PhotoTable : DatabaseTable {
int res = db.prepare_v2(
"INSERT INTO PhotoTable (filename, width, height, filesize, timestamp, exposure_time, "
+ "orientation, original_orientation, import_id, event_id, md5, thumbnail_md5, "
- + "exif_md5, time_created, file_format, title, rating, editable_id, developer, comment) "
- + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
+ + "exif_md5, time_created, file_format, title, rating, editable_id, developer, has_gps, gps_lat,
gps_lon, comment) "
+ + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
-1, out stmt);
assert(res == Sqlite.OK);
@@ -254,7 +262,13 @@ public class PhotoTable : DatabaseTable {
assert(res == Sqlite.OK);
res = stmt.bind_text(19, photo_row.developer.to_string());
assert(res == Sqlite.OK);
- res = stmt.bind_text(20, photo_row.comment);
+ res = stmt.bind_int(20, photo_row.gps_coords.has_gps);
+ assert(res == Sqlite.OK);
+ res = stmt.bind_double(21, photo_row.gps_coords.latitude);
+ assert(res == Sqlite.OK);
+ res = stmt.bind_double(22, photo_row.gps_coords.longitude);
+ assert(res == Sqlite.OK);
+ res = stmt.bind_text(23, photo_row.comment);
assert(res == Sqlite.OK);
res = stmt.step();
@@ -285,7 +299,8 @@ public class PhotoTable : DatabaseTable {
int res = db.prepare_v2(
"UPDATE PhotoTable SET width = ?, height = ?, filesize = ?, timestamp = ?, "
+ "exposure_time = ?, orientation = ?, original_orientation = ?, md5 = ?, "
- + "exif_md5 = ?, thumbnail_md5 = ?, file_format = ?, title = ?, time_reimported = ? "
+ + "exif_md5 = ?, thumbnail_md5 = ?, file_format = ?, title = ?, "
+ + "has_gps = ?, gps_lat = ?, gps_lon = ?, time_reimported = ? "
+ "WHERE id = ?", -1, out stmt);
assert(res == Sqlite.OK);
@@ -315,9 +330,15 @@ public class PhotoTable : DatabaseTable {
assert(res == Sqlite.OK);
res = stmt.bind_text(12, row.title);
assert(res == Sqlite.OK);
- res = stmt.bind_int64(13, time_reimported);
+ res = stmt.bind_int(13, row.gps_coords.has_gps);
+ assert(res == Sqlite.OK);
+ res = stmt.bind_double(14, row.gps_coords.latitude);
+ assert(res == Sqlite.OK);
+ res = stmt.bind_double(15, row.gps_coords.longitude);
+ assert(res == Sqlite.OK);
+ res = stmt.bind_int64(16, time_reimported);
assert(res == Sqlite.OK);
- res = stmt.bind_int64(14, row.photo_id.id);
+ res = stmt.bind_int64(17, row.photo_id.id);
assert(res == Sqlite.OK);
res = stmt.step();
@@ -390,7 +411,7 @@ public class PhotoTable : DatabaseTable {
+ "original_orientation, import_id, event_id, transformations, md5, thumbnail_md5, "
+ "exif_md5, time_created, flags, rating, file_format, title, backlinks, "
+ "time_reimported, editable_id, metadata_dirty, developer, develop_shotwell_id, "
- + "develop_camera_id, develop_embedded_id, comment "
+ + "develop_camera_id, develop_embedded_id, has_gps, gps_lat, gps_lon, comment "
+ "FROM PhotoTable WHERE id=?",
-1, out stmt);
assert(res == Sqlite.OK);
@@ -430,7 +451,10 @@ public class PhotoTable : DatabaseTable {
row.development_ids[RawDeveloper.SHOTWELL] = BackingPhotoID(stmt.column_int64(24));
row.development_ids[RawDeveloper.CAMERA] = BackingPhotoID(stmt.column_int64(25));
row.development_ids[RawDeveloper.EMBEDDED] = BackingPhotoID(stmt.column_int64(26));
- row.comment = stmt.column_text(27);
+ row.gps_coords.has_gps = stmt.column_int(27);
+ row.gps_coords.latitude = stmt.column_double(28);
+ row.gps_coords.longitude = stmt.column_double(29);
+ row.comment = stmt.column_text(30);
return row;
}
@@ -442,7 +466,7 @@ public class PhotoTable : DatabaseTable {
+ "original_orientation, import_id, event_id, transformations, md5, thumbnail_md5, "
+ "exif_md5, time_created, flags, rating, file_format, title, backlinks, time_reimported, "
+ "editable_id, metadata_dirty, developer, develop_shotwell_id, develop_camera_id, "
- + "develop_embedded_id, comment FROM PhotoTable",
+ + "develop_embedded_id, has_gps, gps_lat, gps_lon, comment FROM PhotoTable",
-1, out stmt);
assert(res == Sqlite.OK);
@@ -478,7 +502,10 @@ public class PhotoTable : DatabaseTable {
row.development_ids[RawDeveloper.SHOTWELL] = BackingPhotoID(stmt.column_int64(25));
row.development_ids[RawDeveloper.CAMERA] = BackingPhotoID(stmt.column_int64(26));
row.development_ids[RawDeveloper.EMBEDDED] = BackingPhotoID(stmt.column_int64(27));
- row.comment = stmt.column_text(28);
+ row.gps_coords.has_gps = stmt.column_int(28);
+ row.gps_coords.latitude = stmt.column_double(29);
+ row.gps_coords.longitude = stmt.column_double(30);
+ row.comment = stmt.column_text(31);
validate_orientation(row);
@@ -500,9 +527,9 @@ public class PhotoTable : DatabaseTable {
int res = db.prepare_v2("INSERT INTO PhotoTable (filename, width, height, filesize, "
+ "timestamp, exposure_time, orientation, original_orientation, import_id, event_id, "
+ "transformations, md5, thumbnail_md5, exif_md5, time_created, flags, rating, "
- + "file_format, title, editable_id, developer, develop_shotwell_id, develop_camera_id, "
- + "develop_embedded_id, comment) "
- + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
+ + "file_format, title, has_gps, gps_lat, gps_lon, editable_id, developer, "
+ + "develop_shotwell_id, develop_camera_id, develop_embedded_id, comment) "
+ + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
-1, out stmt);
assert(res == Sqlite.OK);
@@ -544,18 +571,23 @@ public class PhotoTable : DatabaseTable {
assert(res == Sqlite.OK);
res = stmt.bind_text(19, original.title);
assert(res == Sqlite.OK);
- res = stmt.bind_int64(20, editable_id.id);
+ res = stmt.bind_int(20, original.gps_coords.has_gps);
assert(res == Sqlite.OK);
-
- res = stmt.bind_text(21, original.developer.to_string());
+ res = stmt.bind_double(21, original.gps_coords.latitude);
+ assert(res == Sqlite.OK);
+ res = stmt.bind_double(22, original.gps_coords.longitude);
+ assert(res == Sqlite.OK);
+ res = stmt.bind_int64(23, editable_id.id);
assert(res == Sqlite.OK);
- res = stmt.bind_int64(22, develop_shotwell.id);
+ res = stmt.bind_text(24, original.developer.to_string());
assert(res == Sqlite.OK);
- res = stmt.bind_int64(23, develop_camera_id.id);
+ res = stmt.bind_int64(25, develop_shotwell.id);
assert(res == Sqlite.OK);
- res = stmt.bind_int64(24, develop_embedded_id.id);
+ res = stmt.bind_int64(26, develop_camera_id.id);
assert(res == Sqlite.OK);
- res = stmt.bind_text(25, original.comment);
+ res = stmt.bind_int64(27, develop_embedded_id.id);
+ assert(res == Sqlite.OK);
+ res = stmt.bind_text(28, original.comment);
assert(res == Sqlite.OK);
res = stmt.step();
@@ -572,7 +604,15 @@ public class PhotoTable : DatabaseTable {
public bool set_title(PhotoID photo_id, string? new_title) {
return update_text_by_id(photo_id.id, "title", new_title != null ? new_title : "");
}
-
+
+ public void set_gps_coords(PhotoID photo_id, GpsCoords new_gps_coords) throws DatabaseError {
+ update_int_by_id_2(photo_id.id, "has_gps", new_gps_coords.has_gps);
+ if (new_gps_coords.has_gps > 0) {
+ update_double_by_id_2(photo_id.id, "gps_lat", new_gps_coords.latitude);
+ update_double_by_id_2(photo_id.id, "gps_lon", new_gps_coords.longitude);
+ }
+ }
+
public bool set_comment(PhotoID photo_id, string? new_comment) {
return update_text_by_id(photo_id.id, "comment", new_comment != null ? new_comment : "");
}
diff --git a/src/library/LibraryWindow.vala b/src/library/LibraryWindow.vala
index 3300c6c3..079e801d 100644
--- a/src/library/LibraryWindow.vala
+++ b/src/library/LibraryWindow.vala
@@ -144,7 +144,9 @@ public class LibraryWindow : AppWindow {
private Gtk.ProgressBar background_progress_bar = new Gtk.ProgressBar();
private bool background_progress_displayed = false;
- private BasicProperties basic_properties = new BasicProperties();
+ // Instantiate later in constructor becase the map support loads its icons in there and we need
+ // to have the global app instance available for that
+ private BasicProperties basic_properties;
private ExtendedProperties extended_properties = new ExtendedProperties();
private Gtk.Revealer extended_properties_revealer = new Gtk.Revealer();
@@ -192,6 +194,7 @@ public class LibraryWindow : AppWindow {
search_toolbar = new SearchFilterToolbar(search_actions);
// create the main layout & start at the Library page
+ basic_properties = new BasicProperties();
create_layout(library_branch.photos_entry.get_page());
// settings that should persist between sessions
@@ -1168,11 +1171,11 @@ public class LibraryWindow : AppWindow {
basic_properties.halign = Gtk.Align.FILL;
basic_properties.valign = Gtk.Align.CENTER;
basic_properties.hexpand = true;
- basic_properties.vexpand = false;
+ basic_properties.vexpand = true;
basic_properties.margin_top = 10;
basic_properties.margin_bottom = 10;
basic_properties.margin_start = 6;
- basic_properties.margin_end = 0;
+ basic_properties.margin_end = 6;
bottom_frame.add(basic_properties);
bottom_frame.get_style_context().remove_class("frame");
diff --git a/src/main.vala b/src/main.vala
index a7339ed8..4add1df6 100644
--- a/src/main.vala
+++ b/src/main.vala
@@ -370,7 +370,7 @@ void main(string[] args) {
// init GTK (valac has already called g_threads_init())
try {
- Gtk.init_with_args(ref args, _("[FILE]"), CommandlineOptions.entries,
+ GtkClutter.init_with_args(ref args, _("[FILE]"), CommandlineOptions.entries,
Resources.APP_GETTEXT_PACKAGE);
var use_dark = Config.Facade.get_instance().get_gtk_theme_variant();
diff --git a/src/photos/PhotoMetadata.vala b/src/photos/PhotoMetadata.vala
index 74dc5591..cd890c56 100644
--- a/src/photos/PhotoMetadata.vala
+++ b/src/photos/PhotoMetadata.vala
@@ -1168,7 +1168,27 @@ public class PhotoMetadata : MediaMetadata {
return true;
}
-
+
+ public GpsCoords get_gps_coords() {
+ GpsCoords gps_coords = GpsCoords();
+ double altitude;
+ gps_coords.has_gps = exiv2.get_gps_info(out gps_coords.longitude, out gps_coords.latitude, out
altitude) ? 1 : 0;
+ if (gps_coords.has_gps > 0) {
+ if (get_string("Exif.GPSInfo.GPSLongitudeRef") == "W" && gps_coords.longitude > 0)
+ gps_coords.longitude = -gps_coords.longitude;
+ if (get_string("Exif.GPSInfo.GPSLatitudeRef") == "S" && gps_coords.latitude > 0)
+ gps_coords.latitude = -gps_coords.latitude;
+ }
+ return gps_coords;
+ }
+
+ public void set_gps_coords(GpsCoords gps_coords) {
+ if (gps_coords.has_gps > 0)
+ exiv2.set_gps_info(gps_coords.longitude, gps_coords.latitude, 0.0);
+ else
+ exiv2.delete_gps_info();
+ }
+
public bool get_exposure(out MetadataRational exposure) {
return get_rational("Exif.Photo.ExposureTime", out exposure);
}
diff --git a/src/util/misc.vala b/src/util/misc.vala
index 6111ea3f..d1d6431a 100644
--- a/src/util/misc.vala
+++ b/src/util/misc.vala
@@ -273,7 +273,9 @@ public class OneShotScheduler {
}
public void at_idle() {
- at_priority_idle(Priority.DEFAULT_IDLE);
+ // needs to be lower (higher priority) than Clutter.PRIORITY_REDRAW which is
+ // set at Priority.HIGH_IDLE + 50
+ at_priority_idle(Priority.HIGH_IDLE + 40);
}
public void at_priority_idle(int priority) {
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]