[gnome-maps/wip/mlundblad/transit-routing: 7/8] sidebar: WIP, Hook up a new transit mode button



commit 8e8f980bc0df7c9e20ed5cd2202fd41bea85f377
Author: Marcus Lundblad <ml update uu se>
Date:   Thu Mar 17 21:34:11 2016 +0100

    sidebar: WIP, Hook up a new transit mode button
    
    Performs transit routing, work in progress...

 data/gnome-maps.css |    5 +
 data/ui/sidebar.ui  |  276 ++++++++++++++++++++++++++++++++++++++++++++++-----
 src/sidebar.js      |  225 +++++++++++++++++++++++++++++++++++-------
 3 files changed, 447 insertions(+), 59 deletions(-)
---
diff --git a/data/gnome-maps.css b/data/gnome-maps.css
index 0f7c8ce..42cc838 100644
--- a/data/gnome-maps.css
+++ b/data/gnome-maps.css
@@ -81,3 +81,8 @@
 .maps-check-in GtkTextView {
     font-size: large;
 }
+
+.shaded {
+    background-color: alpha(black, 0.1);
+    border-bottom: 1px solid alpha(black, 0.2);
+}
diff --git a/data/ui/sidebar.ui b/data/ui/sidebar.ui
index 77347ed..a141d2e 100644
--- a/data/ui/sidebar.ui
+++ b/data/ui/sidebar.ui
@@ -94,6 +94,28 @@
                 </style>
               </object>
             </child>
+            <child>
+              <object class="GtkRadioButton" id="modeTransitToggle">
+                <property name="name">mode-transit-toggle</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="draw_indicator">False</property>
+                <property name="group">modeBikeToggle</property>
+                <property name="height-request">32</property>
+                <property name="width-request">42</property>
+                <child>
+                  <object class="GtkImage" id="mode-transit-image">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="icon-name">route-transit-symbolic</property>
+                  </object>
+                </child>
+                <style>
+                  <class name="transportation-mode-button"/>
+                </style>
+              </object>
+            </child>
             <style>
               <class name="linked"/>
             </style>
@@ -140,55 +162,259 @@
           </object>
         </child>
         <child>
-          <object class="GtkStack" id="instructionStack">
+          <object class="GtkGrid">
             <property name="visible">True</property>
-            <property name="can_focus">False</property>
+            <property name="valign">fill</property>
+            <property name="vexpand">True</property>
+            <property name="hexpand_set">True</property>
+            <style>
+              <class name="frame"/>
+            </style>
             <child>
-              <object class="GtkScrolledWindow" id="instructionWindow">
-                <property name="name">instruction-window</property>
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
-                <property name="valign">fill</property>
-                <property name="vexpand">True</property>
-                <property name="margin">1</property>
-                <property name="hscrollbar_policy">never</property>
+              <object class="GtkStack" id="transitHeader">
+                <property name="no_show_all">True</property>
+                <property name="visible">False</property>
                 <child>
-                  <object class="GtkListBox" id="instructionList">
-                    <property name="name">instruction-list</property>
+                  <object class="GtkGrid" id="transitOptionsGrid">
                     <property name="visible">True</property>
                     <property name="can_focus">False</property>
-                    <property name="valign">fill</property>
                     <style>
-                      <class name="frame"/>
+                      <class name="shaded"/>
                     </style>
+                    <child>
+                      <object class="GtkComboBoxText"
+                              id="transitTimeOptionsComboBox">
+                        <property name="visible">True</property>
+                        <property name="active_id">leaveNow</property>
+                        <property name="margin_start">6</property>
+                        <property name="margin_end">6</property>
+                        <property name="margin_top">4</property>
+                        <property name="margin_bottom">4</property>
+                        <items>
+                          <item translatable="yes" id="leaveNow">Leave Now</item>
+                          <item translatable="yes" id="leaveBy">Leave By</item>
+                          <item translatable="yes" id="arriveBy">Arrive By</item>
+                        </items>
+                      </object>
+                      <packing>
+                        <property name="left_attach">0</property>
+                        <property name="top_attach">0</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkEntry" id="transitTimeEntry">
+                        <property name="visible">False</property>
+                        <property name="width_chars">5</property>
+                        <property name="margin_start">3</property>
+                        <property name="margin_end">3</property>
+                        <property name="margin_top">4</property>
+                        <property name="margin_bottom">4</property>
+                      </object>
+                      <packing>
+                        <property name="left_attach">1</property>
+                        <property name="top_attach">0</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkMenuButton" id="transitDateButton">
+                        <property name="visible">False</property>
+                        <property name="popover">transitDatePopover</property>
+                        <property name="margin_start">3</property>
+                        <property name="margin_end">3</property>
+                        <property name="margin_top">4</property>
+                        <property name="margin_bottom">4</property>
+                      </object>
+                      <packing>
+                        <property name="left_attach">2</property>
+                        <property name="top_attach">0</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkMenuButton" id="transitParametersMenuButton">
+                        <property name="visible">True</property>
+                        <property name="popover">transitParametersPopover</property>
+                        <property name="halign">GTK_ALIGN_END</property>
+                        <property name="margin_start">3</property>
+                        <property name="margin_end">6</property>
+                        <property name="margin_top">4</property>
+                        <property name="margin_bottom">4</property>
+                        <child>
+                          <object class="GtkGrid">
+                            <property name="visible">True</property>
+                            <property name="valign">GTK_ALIGN_CENTER</property>
+                            <child>
+                              <object class="GtkImage">
+                                <property name="visible">True</property>
+                                <property name="can_focus">False</property>
+                                <property name="halign">GTK_ALIGN_CENTER</property>
+                                <property name="hexpand">True</property>
+                                <property name="icon-name">open-menu-symbolic</property>
+                              </object>
+                            </child>
+                          </object>
+                        </child>
+                      </object>
+                      <packing>
+                        <property name="left_attach">3</property>
+                        <property name="top_attach">0</property>
+                      </packing>
+                    </child>
                   </object>
+                  <packing>
+                    <property name="name">options</property>
+                  </packing>
                 </child>
               </object>
+              <packing>
+                <property name="left_attach">0</property>
+                <property name="top_attach">0</property>
+              </packing>
             </child>
+
             <child>
-              <object class="GtkSpinner" id="instructionSpinner">
+              <object class="GtkStack" id="instructionStack">
                 <property name="visible">True</property>
                 <property name="can_focus">False</property>
-                <property name="active">True</property>
+                <child>
+                  <object class="GtkScrolledWindow" id="instructionWindow">
+                    <property name="name">instruction-window</property>
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="valign">fill</property>
+                    <property name="vexpand">True</property>
+                    <property name="margin">1</property>
+                    <property name="hscrollbar_policy">never</property>
+                    <child>
+                      <object class="GtkListBox" id="instructionList">
+                        <property name="name">instruction-list</property>
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="valign">fill</property>
+                        <property name="hexpand">True</property>
+                      </object>
+                    </child>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkSpinner" id="instructionSpinner">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="active">True</property>
+                  </object>
+                </child>
               </object>
+              <packing>
+                <property name="left_attach">0</property>
+                <property name="top_attach">1</property>
+              </packing>
             </child>
           </object>
         </child>
         <child>
-          <object class="GtkLinkButton" id="linkbutton1">
-            <property name="label" translatable="yes">Route search by GraphHopper</property>
+          <object class="GtkStack" id="linkButtonStack">
+            <child>
+              <object class="GtkLinkButton" id="graphHopperLinkButton">
+                <property name="label" translatable="yes">Route search by GraphHopper</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="use_action_appearance">False</property>
+                <property name="relief">none</property>
+                <property name="uri">https://graphhopper.com</property>
+                <style>
+                  <class name="small-label"/>
+                </style>
+              </object>
+              <packing>
+                <property name="name">graphHopper</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkLinkButton" id="openTripPlannerLinkButton">
+                <property name="label" translatable="yes">Route search by OpenTripPlanner</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="use_action_appearance">False</property>
+                <property name="relief">none</property>
+                <!-- opentripplanner.org uses an SSL cert only valid for github
+                     domains... -->
+                <property name="uri">http://www.opentripplanner.org</property>
+                <style>
+                  <class name="small-label"/>
+                </style>
+              </object>
+              <packing>
+                <property name="name">openTripPlanner</property>
+              </packing>
+            </child>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+  <object class="GtkPopover" id="transitDatePopover">
+    <property name="visible">False</property>
+    <child>
+      <object class="GtkCalendar" id="transitDateCalendar">
+        <property name="visible">True</property>
+      </object>
+    </child>
+  </object>
+  <object class="GtkPopover" id="transitParametersPopover">
+    <property name="visible">False</property>
+    <child>
+      <object class="GtkGrid">
+        <property name="visible">True</property>
+        <property name="margin">6</property>
+        <property name="orientation">GTK_ORIENTATION_VERTICAL</property>
+        <child>
+          <object class="GtkLabel">
             <property name="visible">True</property>
-            <property name="can_focus">True</property>
-            <property name="receives_default">True</property>
-            <property name="use_action_appearance">False</property>
-            <property name="relief">none</property>
-            <property name="uri">https://graphhopper.com</property>
+            <property name="halign">GTK_ALIGN_START</property>
+            <property name="label" translatable="yes">Show</property>
+            <property name="margin_start">6</property>
             <style>
-              <class name="small-label"/>
+              <class name="dim-label"/>
             </style>
           </object>
         </child>
+        <child>
+          <object class="GtkCheckButton" id="busCheckButton">
+            <property name="visible">True</property>
+            <property name="active">True</property>
+            <property name="label" translatable="yes">Buses</property>
+          </object>
+        </child>
+        <child>
+          <object class="GtkCheckButton" id="tramCheckButton">
+            <property name="visible">True</property>
+            <property name="active">True</property>
+            <property name="label" translatable="yes">Trams</property>
+          </object>
+        </child>
+        <child>
+          <object class="GtkCheckButton" id="trainCheckButton">
+            <property name="visible">True</property>
+            <property name="active">True</property>
+            <property name="label" translatable="yes">Trains</property>
+          </object>
+        </child>
+        <child>
+          <object class="GtkCheckButton" id="subwayCheckButton">
+            <property name="visible">True</property>
+            <property name="active">True</property>
+            <property name="label" translatable="yes">Subway</property>
+          </object>
+        </child>
+        <child>
+          <object class="GtkCheckButton" id="ferryCheckButton">
+            <property name="visible">True</property>
+            <property name="active">True</property>
+            <property name="label" translatable="yes">Ferries</property>
+          </object>
+        </child>
       </object>
     </child>
-  </template>
+  </object>
 </interface>
diff --git a/src/sidebar.js b/src/sidebar.js
index 64ec5e6..9d89f4a 100644
--- a/src/sidebar.js
+++ b/src/sidebar.js
@@ -48,40 +48,64 @@ const Sidebar = new Lang.Class({
                         'modeBikeToggle',
                         'modeCarToggle',
                         'modePedestrianToggle',
-                        'timeInfo' ],
+                        'modeTransitToggle',
+                        'timeInfo',
+                        'linkButtonStack',
+                        'transitHeader',
+                        'transitTimeOptionsComboBox',
+                        'transitTimeEntry',
+                        'transitDateButton',
+                        'transitDateCalendar',
+                        'transitParametersMenuButton' ],
 
     _init: function(mapView) {
         this.parent({ transition_type: Gtk.RevealerTransitionType.SLIDE_LEFT });
 
         this._mapView = mapView;
 
+        this._query = Application.routeQuery;
         this._initInstructionList();
-
+        this._initTransitOptions();
         this._initTransportationToggles(this._modePedestrianToggle,
                                         this._modeBikeToggle,
-                                        this._modeCarToggle);
-        this._initQuerySignals();
-
-        let query = Application.routeService.query;
+                                        this._modeCarToggle,
+                                        this._modeTransitToggle);
 
-        query.addPoint(0);
-        query.addPoint(1);
+        this._initQuerySignals();
+        this._query.addPoint(0);
+        this._query.addPoint(1);
+        this._switchRoutingMode(RouteQuery.Transportation.CAR);
     },
 
-    _initTransportationToggles: function(pedestrian, bike, car) {
-        let query = Application.routeService.query;
+    _initTransportationToggles: function(pedestrian, bike, car, transit) {
         let transport = RouteQuery.Transportation;
 
         let onToggle = function(mode, button) {
-            if (button.active && query.transportation !== mode)
-                query.transportation = mode;
+            Utils.debug('onToggle: ' + mode + ', query.mode: ' + this._query.transportation);
+
+            let previousMode = this._query.transportation;
+
+            /* if the transportation mode changes to/from transit
+               change the routing engine */
+            if (button.active &&
+                ((mode !== transport.TRANSIT
+                  && previousMode === transport.TRANSIT)
+                 || (mode === transport.TRANSIT
+                     && previousMode !== transport.TRANSIT))) {
+                Utils.debug('switching routing mode');
+                this._switchRoutingMode(mode);
+            }
+
+            if (button.active && previousMode !== mode)
+                this._query.transportation = mode;
         };
         pedestrian.connect('toggled', onToggle.bind(this, transport.PEDESTRIAN));
         car.connect('toggled', onToggle.bind(this, transport.CAR));
         bike.connect('toggled', onToggle.bind(this, transport.BIKE));
+        transit.connect('toggled', onToggle.bind(this, transport.TRANSIT))
 
         let setToggles = function() {
-            switch(query.transportation) {
+            switch(Application.routeQuery.transportation) {
             case transport.PEDESTRIAN:
                 pedestrian.active = true;
                 break;
@@ -91,21 +115,42 @@ const Sidebar = new Lang.Class({
             case transport.BIKE:
                 bike.active = true;
                 break;
+            case transport.TRANSIT:
+                transit.active = true;
+                break;
             }
         };
 
         setToggles();
-        query.connect('notify::transportation', setToggles);
+        this._query.connect('notify::transportation', setToggles);
     },
 
-    _initQuerySignals: function() {
-        let query = Application.routeService.query;
+    _switchRoutingMode: function(mode) {
+        let graphHopper = Application.routeService;
+        let openTripPlanner = Application.openTripPlanner;
 
-        query.connect('point-added', (function(obj, point, index) {
+        if (mode === RouteQuery.Transportation.TRANSIT) {
+            Utils.debug('switching to transit');
+            graphHopper.disconnect();
+            openTripPlanner.connect();
+            this._linkButtonStack.visible_child_name = 'openTripPlanner';
+            this._transitHeader.show();
+            this._clearInstructions();
+        } else {
+            Utils.debug('switch from transit');
+            openTripPlanner.disconnect();
+            graphHopper.connect();
+            this._linkButtonStack.visible_child_name = 'graphHopper';
+            this._transitHeader.hide();
+        }
+    },
+
+    _initQuerySignals: function() {
+        this._query.connect('point-added', (function(obj, point, index) {
             this._createRouteEntry(index, point);
         }).bind(this));
 
-        query.connect('point-removed', (function(obj, point, index) {
+        this._query.connect('point-removed', (function(obj, point, index) {
             let row = this._entryList.get_row_at_index(index);
             row.destroy();
         }).bind(this));
@@ -133,17 +178,17 @@ const Sidebar = new Lang.Class({
         if (type === RouteEntry.Type.FROM) {
             routeEntry.button.connect('clicked', (function() {
                 let lastIndex = this._entryList.get_children().length;
-                Application.routeService.query.addPoint(lastIndex - 1);
+                this._query.addPoint(lastIndex - 1);
             }).bind(this));
 
             this.bind_property('child-revealed',
                                routeEntry.entry, 'has_focus',
                                GObject.BindingFlags.DEFAULT);
         } else if (type === RouteEntry.Type.VIA) {
-            routeEntry.button.connect('clicked', function() {
+            routeEntry.button.connect('clicked', (function() {
                 let row = routeEntry.get_parent();
-                Application.routeService.query.removePoint(row.get_index());
-            });
+                this._query.removePoint(row.get_index());
+            }).bind(this));
         }
 
         this._initRouteDragAndDrop(routeEntry);
@@ -151,19 +196,18 @@ const Sidebar = new Lang.Class({
 
     _initInstructionList: function() {
         let route = Application.routeService.route;
-        let query = Application.routeService.query;
 
         route.connect('reset', (function() {
             this._clearInstructions();
 
             let length = this._entryList.get_children().length;
             for (let index = 1; index < (length - 1); index++) {
-                query.removePoint(index);
+                this._query.removePoint(index);
             }
         }).bind(this));
 
-        query.connect('notify', (function() {
-            if (query.isValid())
+        this._query.connect('notify', (function() {
+            if (this._query.isValid())
                 this._instructionStack.visible_child = this._instructionSpinner;
             else
                 this._clearInstructions();
@@ -181,11 +225,11 @@ const Sidebar = new Lang.Class({
 
             this._storeRouteTimeoutId = Mainloop.timeout_add(5000, (function() {
                 let placeStore = Application.placeStore;
-                let places = query.filledPoints.map(function(point) {
+                let places = this._query.filledPoints.map(function(point) {
                     return point.place;
                 });
                 let storedRoute = new StoredRoute.StoredRoute({
-                    transportation: query.transportation,
+                    transportation: this._query.transportation,
                     route: route,
                     places: places,
                     geoclue: Application.geoclue
@@ -215,6 +259,121 @@ const Sidebar = new Lang.Class({
         }).bind(this));
     },
 
+    _initTransitOptions: function() {
+        this._transitTimeOptionsComboBox.connect('changed',
+            this._onTransitTimeOptionsComboboxChanged.bind(this));
+        this._transitTimeEntry.connect('activate',
+            this._onTransitTimeEntryActivated.bind(this));
+        this._transitDateButton.popover.get_child().connect('day-selected',
+            this._onTransitDateCalenderDaySelected.bind(this));
+        this._transitParametersMenuButton.connect('toggled',
+            this._onTransitParametersToggled.bind(this))
+    },
+
+    _onTransitTimeOptionsComboboxChanged: function() {
+        if (this._transitTimeOptionsComboBox.active_id === 'leaveNow') {
+            this._transitTimeEntry.visible = false;
+            this._transitDateButton.visible = false;
+            this._query.arriveBy = false;
+            this._query.time = null;
+            this._query.date = null;
+        } else {
+            this._transitTimeEntry.visible = true;
+            this._transitDateButton.visible = true;
+
+            if (this._transitTimeOptionsComboBox.active_id === 'arriveBy') {
+                this._query.arriveBy = true;
+            } else {
+                this._query.arriveBy = false;
+                /* TODO: if the user hasn't already manually entered a
+                 * time, fill in the current time in the time entry */
+            }
+        }
+    },
+
+    _parseTimeString: function(timeString) {
+        let pmSet = false;
+        let hours;
+        let mins;
+        /* remove extra whitespaces */
+        timeString = timeString.replace(/\s+/g, '');
+
+        if (timeString.endsWith('am')) {
+            timeString = timeString.substring(0, timeString.length - 2);
+        } else if (timeString.endsWith('pm')) {
+            timeString = timeString.substring(0, timeString.length - 2);
+            pmSet = true;
+        }
+
+        if (timeString.charAt(2) === ':' || timeString.charAt(1) === ':')
+            timeString = timeString.replace(':', '');
+        else if (timeString.charAt(2) === '\u2236' ||
+                 timeString.charAt(1) === '\u2236')
+            timeString = timeString.replace('\u2236', '');
+
+        if (timeString.length === 4) {
+            /* expect a full time specification (hours, minutes) */
+            hours = timeString.substring(0, 2);
+            mins = timeString.substring(2, 4);
+        } else if (timeString.length === 3) {
+            /* interpret a 3 digit string as h:mm */
+            hours = '0' + timeString.substring(0, 1);
+            mins = timeString.substring(1, 3);
+        } else if (timeString.length === 2) {
+            /* expect just the hour part */
+            hours = timeString.substring(0, 2);
+            mins = '00';
+        } else if (timeString.length === 1) {
+            /* expect just the hour part, one digit */
+            hours = '0' + timeString;
+            mins = '00';
+        } else {
+            /* this makes no sense, just bail out */
+            return null;
+        }
+
+        /* check if the parts can be interpreted as numbers */
+        if (hours % 1 === 0 && mins % 1 === 0) {
+            if (pmSet)
+                hours = parseInt(hours) + 12;
+
+            /* if the hours or minutes is out-of-range, bail out */
+            if (hours < 0 || hours > 24 || mins < 0 || mins > 59)
+                return null;
+
+            return hours + ':' + mins;
+        } else {
+            return null;
+        }
+    },
+
+    _onTransitTimeEntryActivated: function() {
+        let timeString = this._transitTimeEntry.text;
+
+        if (timeString && timeString.length > 0) {
+            timeString = this._parseTimeString(timeString);
+
+            Utils.debug('entered time parsed as: ' + timeString);
+
+            if (timeString) {
+                this._query.time = timeString;
+            }
+        }
+    },
+
+    _onTransitDateCalenderDaySelected: function() {
+        let calendar = this._transitDateButton.popover.get_child();
+        let date = calendar.year + '-' + (calendar.month + 1) + '-' + calendar.day;
+        this._query.date = date;
+        this._transitDateButton.active = false;
+    },
+
+    _onTransitParametersToggled: function() {
+        if (!this._transitParametersMenuButton.active) {
+            Utils.debug('params selected');
+        }
+    },
+
     _clearInstructions: function() {
         let listBox = this._instructionList;
         listBox.forall(listBox.remove.bind(listBox));
@@ -226,8 +385,7 @@ const Sidebar = new Lang.Class({
 
     // Iterate over points and establish the new order of places
     _reorderRoutePoints: function(srcIndex, destIndex) {
-        let query = Application.routeService.query;
-        let points = query.points;
+        let points = this._query.points;
         let srcPlace = this._draggedPoint.place;
 
         // Determine if we are swapping from "above" or "below"
@@ -235,19 +393,18 @@ const Sidebar = new Lang.Class({
 
         // Hold off on notifying the changes to query.points until
         // we have re-arranged the places.
-        query.freeze_notify();
+        this._query.freeze_notify();
 
         for (let i = destIndex; i !== (srcIndex + step); i += step) {
             // swap
             [points[i].place, srcPlace] = [srcPlace, points[i].place];
         }
 
-        query.thaw_notify();
+        this._query.thaw_notify();
     },
 
     _onDragDrop: function(row, context, x, y, time) {
-        let query = Application.routeService.query;
-        let srcIndex = query.points.indexOf(this._draggedPoint);
+        let srcIndex = this._query.points.indexOf(this._draggedPoint);
         let destIndex = row.get_index();
 
         this._reorderRoutePoints(srcIndex, destIndex);


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