[gnome-maps/wip/mlundblad/transit-routing: 9/19] sidebar: Add functionallity for transit routing



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

    sidebar: Add functionallity for transit routing
    
    Adds a new mode button (enabled when the service file
    indicates an OpenTripPlanner instance, or if explicitly
    pointed out via an env variable).
    Adds showing transit itineraries in addition to regular
    turn-by-turn-based routes, and options for transit route
    search.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=755808

 data/gnome-maps.css |    5 +
 data/ui/sidebar.ui  |  453 +++++++++++++++++++++++++++++++++++++++++++++++---
 src/sidebar.js      |  459 ++++++++++++++++++++++++++++++++++++++++++++++----
 3 files changed, 855 insertions(+), 62 deletions(-)
---
diff --git a/data/gnome-maps.css b/data/gnome-maps.css
index 5f500fe..bde40bf 100644
--- a/data/gnome-maps.css
+++ b/data/gnome-maps.css
@@ -86,6 +86,11 @@
     color: rgb(0, 0, 0);
 }
 
+.shaded {
+    background-color: alpha(black, 0.1);
+    border-bottom: 1px solid alpha(black, 0.2);
+}
+
 .route-label {
     padding-left: 2px;
     padding-right: 2px;
diff --git a/data/ui/sidebar.ui b/data/ui/sidebar.ui
index 77347ed..6f44286 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,436 @@
           </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="GtkRevealer" id="transitRevealer">
+                <child>
+                  <object class="GtkStack" id="transitHeader">
+                    <property name="visible">True</property>
+                    <property name="transition-type">GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT_RIGHT</property>
+                    <child>
+                      <object class="GtkGrid" id="transitOptionsGrid">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="no-show-all">True</property>
+                        <style>
+                          <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">view-more-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>
+                    <child>
+                      <object class="GtkGrid" id="transitItineraryHeader">
+                        <property name="visible">True</property>
+                        <property name="can-focus">False</property>
+                        <style>
+                          <class name="shaded"/>
+                        </style>
+                        <child>
+                          <object class="GtkButton" id="transitItineraryBackButton">
+                            <property name="visible">True</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>
+                            <property name="halign">GTK_ALIGN_START</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="hexpand">False</property>
+                                    <property name="icon-name">go-previous-symbolic</property>
+                                  </object>
+                                </child>
+                              </object>
+                            </child>
+                          </object>
+                          <packing>
+                            <property name="left-attach">0</property>
+                            <property name="top-attach">0</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkLabel" id="transitItineraryTimeLabel">
+                            <property name="visible">True</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>
+                            <property name="hexpand">False</property>
+                            <property name="halign">GTK_ALIGN_START</property>
+                          </object>
+                          <packing>
+                            <property name="left-attach">1</property>
+                            <property name="top-attach">0</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkLabel" id="transitItineraryDurationLabel">
+                            <property name="visible">True</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>
+                            <property name="hexpand">True</property>
+                            <property name="halign">GTK_ALIGN_START</property>
+                            <style>
+                              <class name="dim-label"/>
+                            </style>
+                          </object>
+                          <packing>
+                            <property name="left-attach">2</property>
+                            <property name="top-attach">0</property>
+                          </packing>
+                        </child>
+                      </object>
+                      <packing>
+                        <property name="name">itinerary-header</property>
+                      </packing>
+                    </child>
+                  </object>
+                </child>
+              </object>
+              <packing>
+                <property name="left_attach">0</property>
+                <property name="top_attach">0</property>
+              </packing>
+            </child>
             <child>
-              <object class="GtkScrolledWindow" id="instructionWindow">
-                <property name="name">instruction-window</property>
+              <object class="GtkStack" id="instructionStack">
                 <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>
+                  <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>
-                    <style>
-                      <class name="frame"/>
-                    </style>
+                    <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="GtkScrolledWindow" id="transitWindow">
+                    <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="GtkStack" id="transitListStack">
+                        <property name="visible">True</property>
+                        <property 
name="transition-type">GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT_RIGHT</property>
+                        <child>
+                          <object class="GtkListBox" id="transitOverviewListBox">
+                            <property name="visible">True</property>
+                            <property name="can-focus">False</property>
+                          </object>
+                          <packing>
+                            <property name="name">overview</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkListBox" id="transitItineraryListBox">
+                            <property name="visible">True</property>
+                            <property name="can-focus">False</property>
+                            <property name="selection-mode">GTK_SELECTION_NONE</property>
+                          </object>
+                          <packing>
+                            <property name="name">itinerary</property>
+                          </packing>
+                        </child>
+                      </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="GtkStack" id="linkButtonStack">
             <child>
-              <object class="GtkSpinner" id="instructionSpinner">
+              <object class="GtkLinkButton" id="graphHopperLinkButton">
+                <property name="label" translatable="yes">Route search by GraphHopper</property>
                 <property name="visible">True</property>
-                <property name="can_focus">False</property>
-                <property name="active">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="GtkGrid">
+                <property name="visible">True</property>
+                <property name="halign">GTK_ALIGN_END</property>
+                <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="left-attach">0</property>
+                    <property name="top-attach">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkMenuButton">
+                    <property name="visible">True</property>
+                    <property name="popover">openTripPlannerDisclaimerPopover</property>
+                    <property name="halign">GTK_ALIGN_END</property>
+                    <property name="margin-top">5</property>
+                    <property name="margin-bottom">5</property>
+                    <property name="margin-end">5</property>
+                    <property name="margin-start">5</property>
+                    <style>
+                      <class name="flat"/>
+                    </style>
+                    <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">False</property>
+                            <property name="icon-name">dialog-information-symbolic</property>
+                          </object>
+                        </child>
+                      </object>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="left-attach">1</property>
+                    <property name="top-attach">0</property>
+                  </packing>
+                </child>
               </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="GtkLinkButton" id="linkbutton1">
-            <property name="label" translatable="yes">Route search by GraphHopper</property>
+          <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>
+  <object class="GtkPopover" id="openTripPlannerDisclaimerPopover">
+    <property name="visible">False</property>
+    <child>
+      <object class="GtkGrid">
+        <property name="visible">True</property>
+        <child>
+          <object class="GtkLabel">
+            <property name="visible">True</property>
+            <property name="margin-top">5</property>
+            <property name="margin-bottom">5</property>
+            <property name="margin-start">5</property>
+            <property name="margin-end">5</property>
+            <property name="label" translatable="yes">Routing itineraries for public transit is provided by 
GNOME
+using timetable data obtained from transit companies or agencies.
+The companies and agencies can not be held responsible for the results shown.
+GNOME can not guarantee correctness of the itineraries and schedules shown.
+Names and brands shown are to be considered as registred trademarks when applicable.</property>
+          </object>
+        </child>
+      </object>
+    </child>
+  </object>
 </interface>
diff --git a/src/sidebar.js b/src/sidebar.js
index 64ec5e6..ad76d60 100644
--- a/src/sidebar.js
+++ b/src/sidebar.js
@@ -20,8 +20,11 @@
  *         Mattias Bengtsson <mattias jc bengtsson gmail com>
  */
 
+const C_ = imports.gettext.dgettext;
 const Cairo = imports.cairo;
 const Gdk = imports.gi.Gdk;
+const Gio = imports.gi.Gio;
+const GLib = imports.gi.GLib;
 const GObject = imports.gi.GObject;
 const Gtk = imports.gi.Gtk;
 const Lang = imports.lang;
@@ -33,8 +36,20 @@ const PlaceStore  = imports.placeStore;
 const RouteEntry = imports.routeEntry;
 const RouteQuery = imports.routeQuery;
 const StoredRoute = imports.storedRoute;
+const TransitArrivalRow = imports.transitArrivalRow;
+const TransitItineraryRow = imports.transitItineraryRow;
+const TransitLegRow = imports.transitLegRow;
+const TransitMoreRow = imports.transitMoreRow;
+const TransitOptions = imports.transitOptions;
+const TransitPlan = imports.transitPlan;
 const Utils = imports.utils;
 
+// in org.gnome.desktop.interface
+const CLOCK_FORMAT_KEY = 'clock-format';
+
+let _desktopSettings = new Gio.Settings({ schema_id: 'org.gnome.desktop.interface' });
+let clockFormat = _desktopSettings.get_string(CLOCK_FORMAT_KEY);
+
 const Sidebar = new Lang.Class({
     Name: 'Sidebar',
     Extends: Gtk.Revealer,
@@ -48,40 +63,69 @@ const Sidebar = new Lang.Class({
                         'modeBikeToggle',
                         'modeCarToggle',
                         'modePedestrianToggle',
-                        'timeInfo' ],
+                        'modeTransitToggle',
+                        'timeInfo',
+                        'linkButtonStack',
+                        'transitWindow',
+                        'transitRevealer',
+                        'transitHeader',
+                        'transitTimeOptionsComboBox',
+                        'transitTimeEntry',
+                        'transitDateButton',
+                        'transitDateCalendar',
+                        'transitParametersMenuButton',
+                        'transitListStack',
+                        'transitOverviewListBox',
+                        'transitItineraryListBox',
+                        'transitItineraryBackButton',
+                        'transitItineraryTimeLabel',
+                        'transitItineraryDurationLabel',
+                        'busCheckButton',
+                        'tramCheckButton',
+                        'trainCheckButton',
+                        'subwayCheckButton',
+                        'ferryCheckButton' ],
 
     _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();
+                                        this._modeCarToggle,
+                                        this._modeTransitToggle);
 
-        let query = Application.routeService.query;
-
-        query.addPoint(0);
-        query.addPoint(1);
+        this._initQuerySignals();
+        this._query.addPoint(0);
+        this._query.addPoint(1);
+        this._switchRoutingMode(RouteQuery.Transportation.CAR);
+        /* enable/disable transit mode switch based on the presence of
+         * OpenTripPlanner */
+        this._modeTransitToggle.sensitive = Application.openTripPlanner.enabled;
     },
 
-    _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;
+            let previousMode = this._query.transportation;
+
+            if (button.active && previousMode !== mode) {
+                this._switchRoutingMode(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 +135,39 @@ const Sidebar = new Lang.Class({
             case transport.BIKE:
                 bike.active = true;
                 break;
+            case transport.TRANSIT:
+                transit.active = true;
+                break;
             }
+
+            this._switchRoutingMode(Application.routeQuery.transportation);
         };
 
-        setToggles();
-        query.connect('notify::transportation', setToggles);
+        setToggles.bind(this)();
+        this._query.connect('notify::transportation', setToggles.bind(this));
     },
 
-    _initQuerySignals: function() {
-        let query = Application.routeService.query;
+    _switchRoutingMode: function(mode) {
+        if (mode === RouteQuery.Transportation.TRANSIT) {
+            Application.setTransitRouting(true);
+            this._linkButtonStack.visible_child_name = 'openTripPlanner';
+            this._resetTransitOptions();
+            this._transitRevealer.reveal_child = true;
+            this._clearInstructions();
+        } else {
+            Application.setTransitRouting(false);
+            this._linkButtonStack.visible_child_name = 'graphHopper';
+            this._transitRevealer.reveal_child = false;
+            Application.openTripPlanner.plan.deselectItinerary();
+        }
+    },
 
-        query.connect('point-added', (function(obj, point, index) {
+    _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 +195,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,22 +213,38 @@ const Sidebar = new Lang.Class({
 
     _initInstructionList: function() {
         let route = Application.routeService.route;
-        let query = Application.routeService.query;
+        let transitPlan = Application.openTripPlanner.plan;
 
         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())
+        transitPlan.connect('reset', (function() {
+            this._clearTransitOverview();
+            this._showTransitOverview();
+            this._instructionStack.visible_child = this._transitWindow;
+            /* don't remove query points as with the turn-based routing,
+             * since we might get "no route" because of the time selected
+             * and so on */
+        }).bind(this));
+
+        this._query.connect('notify', (function() {
+            if (this._query.isValid()) {
                 this._instructionStack.visible_child = this._instructionSpinner;
-            else
-                this._clearInstructions();
+            } else {
+                if (this._query.transportation ===
+                    RouteQuery.Transportation.TRANSIT) {
+                    this._clearTransitOverview();
+                    this._showTransitOverview();
+                } else {
+                    this._clearInstructions();
+                }
+            }
 
             if (this._storeRouteTimeoutId)
                 this._cancelStore();
@@ -181,11 +259,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
@@ -213,6 +291,315 @@ const Sidebar = new Lang.Class({
             if (row)
                 this._mapView.showTurnPoint(row.turnPoint);
         }).bind(this));
+
+        transitPlan.connect('update', (function() {
+            this._clearTransitOverview();
+            this._showTransitOverview();
+            this._populateTransitItineraryOverview();
+        }).bind(this));
+
+        /* use list separators for the transit itinerary overview list */
+        this._transitOverviewListBox.set_header_func((function(row, prev) {
+            if (prev)
+                row.set_header(new Gtk.Separator());
+        }).bind(this));
+
+        this._transitOverviewListBox.connect('row-activated',
+                                             this._onItineraryOverviewRowActivated.bind(this));
+        this._transitItineraryBackButton.connect('clicked',
+                                                 this._showTransitOverview.bind(this));
+
+    },
+
+    _clearTransitOverview: function() {
+        let listBox = this._transitOverviewListBox;
+        listBox.forall(listBox.remove.bind(listBox));
+
+        this._instructionStack.visible_child = this._transitWindow;
+        this._timeInfo.label = '';
+        this._distanceInfo.label = '';
+    },
+
+    _clearTransitItinerary: function() {
+        let listBox = this._transitItineraryListBox;
+        listBox.forall(listBox.remove.bind(listBox));
+    },
+
+    _showTransitOverview: function() {
+        let plan = Application.openTripPlanner.plan;
+
+        this._transitListStack.visible_child_name = 'overview';
+        this._transitHeader.visible_child_name = 'options';
+        plan.deselectItinerary();
+    },
+
+    _showTransitItineraryView: function() {
+        this._transitListStack.visible_child_name = 'itinerary';
+        this._transitHeader.visible_child_name = 'itinerary-header';
+    },
+
+    _populateTransitItineraryOverview: function() {
+        let plan = Application.openTripPlanner.plan;
+
+        plan.itineraries.forEach((function(itinerary) {
+            let row =
+                new TransitItineraryRow.TransitItineraryRow({ visible: true,
+                                                              itinerary: itinerary });
+            this._transitOverviewListBox.add(row);
+        }).bind(this));
+        /* add the "load more" row */
+        this._transitOverviewListBox.add(
+            new TransitMoreRow.TransitMoreRow({ visible: true }));
+
+        /* add an empty list row to get a final separator */
+        this._transitOverviewListBox.add(new Gtk.ListBoxRow({ visible: true }));
+    },
+
+    _onItineraryActivated: function(itinerary) {
+        let plan = Application.openTripPlanner.plan;
+
+        this._populateTransitItinerary(itinerary);
+        this._showTransitItineraryView();
+        plan.selectItinerary(itinerary);
+    },
+
+    _onMoreActivated: function(row) {
+        row.startLoading();
+        Application.openTripPlanner.fetchMoreResults();
+    },
+
+    _onItineraryOverviewRowActivated: function(listBox, row) {
+        this._transitOverviewListBox.unselect_all();
+
+        if (row.itinerary)
+            this._onItineraryActivated(row.itinerary);
+        else
+            this._onMoreActivated(row);
+    },
+
+    _populateTransitItinerary: function(itinerary) {
+        this._transitItineraryTimeLabel.label =
+            itinerary.prettyPrintTimeInterval();
+        this._transitItineraryDurationLabel.label =
+            itinerary.prettyPrintDuration();
+
+        this._clearTransitItinerary();
+        for (let i = 0; i < itinerary.legs.length; i++) {
+            let leg = itinerary.legs[i];
+            let row = new TransitLegRow.TransitLegRow({ leg: leg,
+                                                        start: i === 0,
+                                                        mapView: this._mapView });
+            this._transitItineraryListBox.add(row);
+        }
+
+        /* insert the additional arrival row, showing the arrival place and time */
+        this._transitItineraryListBox.add(
+            new TransitArrivalRow.TransitArrivalRow({ itinerary: itinerary,
+                                                      mapView: this._mapView }));
+    },
+
+    _initTransitOptions: function() {
+        this._transitTimeOptionsComboBox.connect('changed',
+            this._onTransitTimeOptionsComboboxChanged.bind(this));
+        this._transitTimeEntry.connect('activate',
+            this._onTransitTimeEntryActivated.bind(this));
+        /* trigger an update of the query time as soon as focus leave the time
+         * entry, to allow the user to enter a time before selecting start
+         * and destination without having to press enter */
+        this._transitTimeEntry.connect('focus-out-event',
+            this._onTransitTimeEntryActivated.bind(this));
+        this._transitDateButton.popover.get_child().connect('day-selected-double-click',
+            this._onTransitDateCalenderDaySelected.bind(this));
+        this._transitDateButton.connect('toggled',
+            this._onTransitDateButtonToogled.bind(this));
+        this._transitParametersMenuButton.connect('toggled',
+            this._onTransitParametersToggled.bind(this))
+    },
+
+    _resetTransitOptions: function() {
+        /* reset to indicate departure now and forget any previous manually
+         * set time and date */
+        this._transitTimeOptionsComboBox.active_id = 'leaveNow';
+        this._timeSelected = false;
+        this._dateSelected = false;
+    },
+
+    _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;
+            this._timeSelected = null;
+            this._dateSelected = null;
+        } else {
+            this._transitTimeEntry.visible = true;
+            this._transitDateButton.visible = true;
+
+            if (!this._timeSelected)
+                this._updateTransitTimeEntry(GLib.DateTime.new_now_local());
+
+            if (!this._dateSelected)
+                this._updateTransitDateButton(GLib.DateTime.new_now_local());
+
+            if (this._transitTimeOptionsComboBox.active_id === 'arriveBy') {
+                this._query.arriveBy = true;
+            } else {
+                this._query.arriveBy = false;
+            }
+        }
+    },
+
+    /* parse a time from free-format into a string representation:
+     * hour:min
+     * TODO: maybe try to use some library to get better locale handling,
+     * or push for something in GLib */
+    _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;
+        }
+
+        /* allow using :, ., and the ratio symbol to separate hours:mins */
+        if (timeString.charAt(2) === ':' || timeString.charAt(1) === ':')
+            timeString = timeString.replace(':', '');
+        else 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;
+        }
+    },
+
+    _updateTransitTimeEntry: function(time) {
+        if (clockFormat === '24h')
+            this._transitTimeEntry.text = time.format('%R');
+        else
+            this._transitTimeEntry.text = time.format('%r');
+    },
+
+    _onTransitTimeEntryActivated: function() {
+        let timeString = this._transitTimeEntry.text;
+
+        if (timeString && timeString.length > 0) {
+            timeString = this._parseTimeString(timeString);
+
+            /* only trigger an update if a different time was entered */
+            if (timeString && timeString !== this._timeSelected) {
+                this._query.time = timeString;
+                /* remember that the user has selected a time */
+                this._timeSelected = timeString;
+            }
+        }
+    },
+
+    _updateTransitDateButton: function(date) {
+        /*
+         * Translators: this is a format string giving the equivalent to
+         * "may 29" according to the current locale's convensions.
+         */
+        this._transitDateButton.label =
+            date.format(C_("month-day-date", "%b %e"));
+    },
+
+    _onTransitDateCalenderDaySelected: function() {
+        let calendar = this._transitDateButton.popover.get_child();
+        let year = calendar.year;
+        let month = calendar.month + 1;
+        let day = calendar.day;
+        let date = '%04d-%02d-%02d'.format(year, month, day);
+
+        /* only trigger an update if a different date was selected */
+        if (date !== this._dateSelected) {
+            this._query.date = date;
+            this._transitDateButton.active = false;
+            this._updateTransitDateButton(GLib.DateTime.new_local(year, month, day,
+                                                                  0, 0, 0));
+            /* remember that the user has already selected a date */
+            this._dateSelected = date;
+        }
+    },
+
+    _onTransitDateButtonToogled: function() {
+        if (!this._transitDateButton.active)
+            this._onTransitDateCalenderDaySelected();
+    },
+
+    _createTransitOptions: function() {
+        let options = new TransitOptions.TransitOptions();
+        let busSelected = this._busCheckButton.active;
+        let tramSelected = this._tramCheckButton.active;
+        let trainSelected = this._trainCheckButton.active;
+        let subwaySelected = this._subwayCheckButton.active;
+        let ferrySelected = this._ferryCheckButton.active;
+
+        if (busSelected && tramSelected && trainSelected && subwaySelected &&
+            ferrySelected) {
+            options.showAllRouteTypes = true;
+        } else {
+            if (busSelected)
+                options.addRouteTypeToShow(TransitPlan.RouteType.BUS);
+            if (tramSelected)
+                options.addRouteTypeToShow(TransitPlan.RouteType.TRAM);
+            if (trainSelected)
+                options.addRouteTypeToShow(TransitPlan.RouteType.TRAIN);
+            if (subwaySelected)
+                options.addRouteTypeToShow(TransitPlan.RouteType.SUBWAY);
+            if (ferrySelected)
+                options.addRouteTypeToShow(TransitPlan.RouteType.FERRY);
+        }
+
+        return options;
+    },
+
+    _onTransitParametersToggled: function() {
+        if (!this._transitParametersMenuButton.active) {
+            let options = this._createTransitOptions();
+            this._query.transitOptions = options;
+        }
     },
 
     _clearInstructions: function() {
@@ -226,8 +613,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 +621,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]