[gnome-maps] Add Print Route Feature



commit ac88800dccbbc836b6e7a7bd6e5ee20fb225a997
Author: Amisha Singla <amishas157 gmail com>
Date:   Mon Feb 15 22:08:15 2016 +0530

    Add Print Route Feature
    
    Print Operation class is added to draw the cairo surfaces given to it.
    Print Layout acts as a tool-box which sub-classes like short Route
    layout , long route layout use to layout the surfaces as per the
    respective requirements of rendering various surfaces.
    GUI is added to give users an ability to access print Route feature.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=746790

 data/ui/help-overlay.ui              |    9 +-
 data/ui/main-window.ui               |   21 +++
 po/POTFILES.in                       |    2 +
 src/longPrintLayout.js               |   98 ++++++++++++
 src/mainWindow.js                    |   18 ++-
 src/org.gnome.Maps.src.gresource.xml |    4 +
 src/printLayout.js                   |  277 ++++++++++++++++++++++++++++++++++
 src/printOperation.js                |  108 +++++++++++++
 src/route.js                         |    4 +-
 src/shortPrintLayout.js              |   61 ++++++++
 10 files changed, 598 insertions(+), 4 deletions(-)
---
diff --git a/data/ui/help-overlay.ui b/data/ui/help-overlay.ui
index 45c11d7..70b3692 100644
--- a/data/ui/help-overlay.ui
+++ b/data/ui/help-overlay.ui
@@ -23,7 +23,7 @@
                 <property name="accelerator">&lt;Primary&gt;F</property>
               </object>
             </child>
-           <child>
+            <child>
               <object class="GtkShortcutsShortcut">
                 <property name="visible">1</property>
                 <property name="title" translatable="yes" context="shortcut window">Toggle route 
planner</property>
@@ -33,6 +33,13 @@
             <child>
               <object class="GtkShortcutsShortcut">
                 <property name="visible">1</property>
+                <property name="title" translatable="yes" context="shortcut window">Print route</property>
+                <property name="accelerator">&lt;Primary&gt;P</property>
+              </object>
+            </child>
+            <child>
+              <object class="GtkShortcutsShortcut">
+                <property name="visible">1</property>
                 <property name="title" translatable="yes" context="shortcut window">Quit</property>
                 <property name="accelerator">&lt;Primary&gt;Q</property>
               </object>
diff --git a/data/ui/main-window.ui b/data/ui/main-window.ui
index f91dce2..e350f79 100644
--- a/data/ui/main-window.ui
+++ b/data/ui/main-window.ui
@@ -97,6 +97,27 @@
             <property name="pack-type">end</property>
           </packing>
         </child>
+        <child>
+          <object class="GtkButton" id="printRouteButton">
+            <property name="name">print-route</property>
+            <property name="can-focus">True</property>
+            <property name="tooltip-text" translatable="yes">Print Route</property>
+            <property name="action-name">win.print-route</property>
+            <property name="valign">center</property>
+            <style>
+              <class name="image-button"/>
+            </style>
+            <child>
+              <object class="GtkImage" id="print-route-button-image">
+                <property name="visible">True</property>
+                <property name="icon-name">document-print-symbolic</property>
+              </object>
+            </child>
+          </object>
+          <packing>
+            <property name="pack-type">end</property>
+          </packing>
+        </child>
       </object>
     </child>
     <child>
diff --git a/po/POTFILES.in b/po/POTFILES.in
index cc069f7..df436e1 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -38,6 +38,8 @@ src/osmEditDialog.js
 src/placeBubble.js
 src/placeEntry.js
 src/place.js
+src/printLayout.js
+src/printOperation.js
 src/routeService.js
 src/sendToDialog.js
 src/shapeLayer.js
diff --git a/src/longPrintLayout.js b/src/longPrintLayout.js
new file mode 100644
index 0000000..22ae720
--- /dev/null
+++ b/src/longPrintLayout.js
@@ -0,0 +1,98 @@
+/* -*- Mode: JS2; indent-tabs-mode: nil; js2-basic-offset: 4 -*- */
+/* vim: set et ts=4 sw=4: */
+/*
+ * GNOME Maps is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * GNOME Maps is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with GNOME Maps; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Amisha Singla <amishas157 gmail com>
+ */
+
+const Lang = imports.lang;
+
+const PrintLayout = imports.printLayout;
+
+const _NUM_MINIMAPS = 5;
+
+/* All following constants are ratios of surface size to page size */
+const _Instruction = {
+    SCALE_X: 0.57,
+    SCALE_Y: 0.05,
+    SCALE_MARGIN: 0.01
+};
+const _MiniMapView = {
+    SCALE_X: 0.4,
+    SCALE_Y: 0.20,
+    SCALE_MARGIN: 0.03,
+    ZOOM_LEVEL: 18
+};
+
+const LongPrintLayout = new Lang.Class({
+    Name: 'LongPrintLayout',
+    Extends: PrintLayout.PrintLayout,
+
+    _init: function(params) {
+        this._route = params.route;
+        delete params.route;
+
+        /* (Header + 3 maps) + instructions */
+        let totalSurfaces = 4 + this._route.turnPoints.length;
+        params.totalSurfaces = totalSurfaces;
+
+        this.parent(params);
+    },
+
+    render: function() {
+        this.parent();
+
+        let instructionWidth = _Instruction.SCALE_X * this._pageWidth;
+        let instructionHeight = _Instruction.SCALE_Y * this._pageHeight;
+        let instructionMargin = _Instruction.SCALE_MARGIN * this._pageHeight;
+
+        let miniMapViewWidth = _MiniMapView.SCALE_X * this._pageWidth;
+        let miniMapViewHeight = _MiniMapView.SCALE_Y * this._pageHeight;
+        let miniMapViewMargin = _MiniMapView.SCALE_MARGIN * this._pageHeight;
+        let miniMapViewZoomLevel = _MiniMapView.ZOOM_LEVEL;
+
+        let dy = 0;
+        let locationsLength = this._route.turnPoints.length;
+
+        /* Fixed number of locations are plotted on minimaps which requires a
+         * check on instructions bound. Later on this can be made dynamic
+         * depending upon factors like total number of instructions, complexity
+         * of neighbourhood areas, etc.
+         */
+        let nthStartLocation = Math.min(_NUM_MINIMAPS, locationsLength);
+        let startLocations = this._createLocationArray(0, nthStartLocation);
+        this._drawMapView(miniMapViewWidth, miniMapViewHeight,
+                          miniMapViewZoomLevel, startLocations);
+
+        /* x-cursor is increased temporarily for rendering instructions */
+        let tmpX = this._cursorX;
+        this._route.turnPoints.forEach(function(turnPoint) {
+            dy = instructionHeight + instructionMargin;
+            this._adjustPage(dy);
+            this._cursorX = tmpX + miniMapViewWidth + miniMapViewMargin;
+            this._drawInstruction(instructionWidth, instructionHeight,
+                                  turnPoint);
+            this._cursorY += dy;
+        }.bind(this));
+        this._cursorX = tmpX;
+
+        let firstEndLocation = Math.max(0, locationsLength - _NUM_MINIMAPS);
+        let endLocations = this._createLocationArray(firstEndLocation,
+                                                     locationsLength);
+        this._cursorY = Math.max(0, this._cursorY - miniMapViewHeight);
+        this._drawMapView(miniMapViewWidth, miniMapViewHeight,
+                          miniMapViewZoomLevel, endLocations);
+    }
+});
diff --git a/src/mainWindow.js b/src/mainWindow.js
index 3c388de..afb7297 100644
--- a/src/mainWindow.js
+++ b/src/mainWindow.js
@@ -38,6 +38,7 @@ const LocationServiceNotification = imports.locationServiceNotification;
 const MapView = imports.mapView;
 const PlaceEntry = imports.placeEntry;
 const PlaceStore = imports.placeStore;
+const PrintOperation = imports.printOperation;
 const Sidebar = imports.sidebar;
 const Utils = imports.utils;
 const ZoomControl = imports.zoomControl;
@@ -57,7 +58,8 @@ const MainWindow = new Lang.Class({
                         'gotoUserLocationButton',
                         'toggleSidebarButton',
                         'layersButton',
-                        'favoritesButton' ],
+                        'favoritesButton',
+                        'printRouteButton' ],
 
     get mapView() {
         return this._mapView;
@@ -202,6 +204,10 @@ const MainWindow = new Lang.Class({
             'find': {
                 accels: ['<Primary>F'],
                 onActivate: this._placeEntry.grab_focus.bind(this._placeEntry)
+            },
+            'print-route': {
+                accels: ['<Primary>P'],
+                onActivate: this._printRouteActivate.bind(this)
             }
         });
     },
@@ -248,6 +254,9 @@ const MainWindow = new Lang.Class({
             this._favoritesButton.sensitive = favoritesPopover.rows > 0;
         }).bind(this));
 
+        this._mapView.bind_property('routeVisible', this._printRouteButton,
+                                    'visible', GObject.BindingFlags.DEFAULT);
+
         Application.geoclue.connect('notify::state',
                                     this._updateLocationSensitivity.bind(this));
         this.application.connect('notify::connected', (function() {
@@ -259,6 +268,7 @@ const MainWindow = new Lang.Class({
             this._favoritesButton.sensitive = (app.connected &&
                                                favoritesPopover.rows > 0);
             this._placeEntry.sensitive = app.connected;
+            this._printRouteButton.sensitive = app.connected;
         }).bind(this));
     },
 
@@ -365,6 +375,12 @@ const MainWindow = new Lang.Class({
         }
     },
 
+    _printRouteActivate: function() {
+        if (this._mapView.routeVisible) {
+            let operation = new PrintOperation.PrintOperation({ mainWindow: this });
+        }
+    },
+
     _onMapTypeMenuActivate: function(action) {
         let state = action.get_state().get_boolean();
         action.set_state(GLib.Variant.new('b', !state));
diff --git a/src/org.gnome.Maps.src.gresource.xml b/src/org.gnome.Maps.src.gresource.xml
index 769ec81..ecd8ef4 100644
--- a/src/org.gnome.Maps.src.gresource.xml
+++ b/src/org.gnome.Maps.src.gresource.xml
@@ -26,6 +26,7 @@
     <file>layersPopover.js</file>
     <file>location.js</file>
     <file>locationServiceNotification.js</file>
+    <file>longPrintLayout.js</file>
     <file>main.js</file>
     <file>mainWindow.js</file>
     <file>mapBubble.js</file>
@@ -52,6 +53,8 @@
     <file>placeMarker.js</file>
     <file>placePopover.js</file>
     <file>placeStore.js</file>
+    <file>printLayout.js</file>
+    <file>printOperation.js</file>
     <file>route.js</file>
     <file>routeEntry.js</file>
     <file>routeQuery.js</file>
@@ -61,6 +64,7 @@
     <file>settings.js</file>
     <file>sendToDialog.js</file>
     <file>shapeLayer.js</file>
+    <file>shortPrintLayout.js</file>
     <file>sidebar.js</file>
     <file>socialPlace.js</file>
     <file>socialPlaceListBox.js</file>
diff --git a/src/printLayout.js b/src/printLayout.js
new file mode 100644
index 0000000..b4e72fa
--- /dev/null
+++ b/src/printLayout.js
@@ -0,0 +1,277 @@
+/* -*- Mode: JS2; indent-tabs-mode: nil; js2-basic-offset: 4 -*- */
+/* vim: set et ts=4 sw=4: */
+/*
+ * GNOME Maps is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * GNOME Maps is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with GNOME Maps; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Amisha Singla <amishas157 gmail com>
+ */
+
+const Cairo = imports.cairo;
+const Champlain = imports.gi.Champlain;
+const Clutter = imports.gi.Clutter;
+const GObject = imports.gi.GObject;
+const Gtk = imports.gi.Gtk;
+const Lang = imports.lang;
+const Pango = imports.gi.Pango;
+const PangoCairo = imports.gi.PangoCairo;
+
+const Application = imports.application;
+const InstructionRow = imports.instructionRow;
+const MapMarker = imports.mapMarker;
+const MapView = imports.mapView;
+const TurnPointMarker = imports.turnPointMarker;
+
+/* Following constant has unit as meters */
+const _SHORT_LAYOUT_MAX_DISTANCE = 3000;
+
+const _STROKE_COLOR = new Clutter.Color({ red: 0,
+                                          blue: 255,
+                                          green: 0,
+                                          alpha: 100 });
+const _STROKE_WIDTH = 5.0;
+
+/* All following constants are ratios of surface size to page size */
+const _Header = {
+    SCALE_X: 0.9,
+    SCALE_Y: 0.03,
+    SCALE_MARGIN: 0.01
+};
+const _MapView = {
+    SCALE_X: 1.0,
+    SCALE_Y: 0.4,
+    SCALE_MARGIN: 0.04,
+    ZOOM_LEVEL: 18
+};
+
+function newFromRoute(route, pageWidth, pageHeight) {
+    /*
+     * To avoid the circular dependencies, imports has
+     * been carried out in this method
+     */
+    if (route.distance > _SHORT_LAYOUT_MAX_DISTANCE) {
+        return new imports.longPrintLayout.LongPrintLayout({
+            route: route,
+            pageWidth: pageWidth,
+            pageHeight: pageHeight
+        });
+    } else {
+        return new imports.shortPrintLayout.ShortPrintLayout({
+            route: route,
+            pageWidth: pageWidth,
+            pageHeight: pageHeight
+        });
+    }
+}
+
+const PrintLayout = new Lang.Class({
+    Name: 'PrintLayout',
+    Extends: GObject.Object,
+    Abstract: true,
+    Signals: {
+        'render-complete': { }
+    },
+
+    _init: function(params) {
+        this._pageWidth = params.pageWidth;
+        delete params.pageWidth;
+
+        this._pageHeight = params.pageHeight;
+        delete params.pageHeight;
+
+        this._totalSurfaces = params.totalSurfaces;
+        delete params.totalSurfaces;
+
+        this.parent();
+
+        this.numPages = 0;
+        this.surfaceObjects = [];
+        this._surfacesRendered = 0;
+        this.renderFinished = false;
+        this._initSignals();
+    },
+
+    render: function() {
+        let headerWidth = _Header.SCALE_X * this._pageWidth;
+        let headerHeight = _Header.SCALE_Y * this._pageHeight;
+        let headerMargin = _Header.SCALE_MARGIN * this._pageHeight;
+
+        let mapViewWidth = _MapView.SCALE_X * this._pageWidth;
+        let mapViewHeight = _MapView.SCALE_Y * this._pageHeight;
+        let mapViewMargin = _MapView.SCALE_MARGIN * this._pageHeight;
+        let mapViewZoomLevel = _MapView.ZOOM_LEVEL;
+
+        this._createNewPage();
+        let dy = 0;
+
+        /*
+         * Before rendering each surface, page adjustment is done. It is checked if it
+         * can be adjusted in current page, otherwise a new page is created
+         */
+        dy = headerHeight + headerMargin;
+        this._adjustPage(dy);
+        this._drawHeader(headerWidth, headerHeight);
+        this._cursorY += dy;
+
+        dy = mapViewHeight + mapViewMargin;
+        this._adjustPage(dy);
+        let locationsLength = this._route.turnPoints.length;
+        let allLocations = this._createLocationArray(0, locationsLength);
+        this._drawMapView(mapViewWidth, mapViewHeight,
+                          mapViewZoomLevel, allLocations);
+        this._cursorY += dy;
+    },
+
+    _initSignals: function() {
+        this.connect('render-complete', (function() {
+            this.renderFinished = true;
+        }).bind(this));
+    },
+
+    _drawMapView: function(width, height, zoomLevel, locations) {
+        let pageNum = this.numPages - 1;
+        let x = this._cursorX;
+        let y = this._cursorY;
+        let factory = Champlain.MapSourceFactory.dup_default();
+        let mapSource = factory.create_cached_source(MapView.MapType.STREET);
+        let view = new Champlain.View({ width: width,
+                                        height: height,
+                                        zoom_level: zoomLevel });
+        view.set_map_source(mapSource);
+
+        this._addRouteLayer(view);
+
+        view.ensure_visible(this._route.createBBox(locations), false);
+        if (view.state !== Champlain.State.DONE) {
+            let notifyId = view.connect('notify::state', (function() {
+                if (view.state === Champlain.State.DONE) {
+                    view.disconnect(notifyId);
+                    let surface = view.to_surface(true);
+                    if (surface)
+                        this._addSurface(surface, x, y, pageNum);
+                }
+            }).bind(this));
+        } else {
+            let surface = view.to_surface(true);
+            if (surface)
+                this._addSurface(surface, x, y, pageNum);
+        }
+    },
+
+    _createLocationArray: function(startIndex, endIndex) {
+        let locationArray = [];
+        for (let i = startIndex; i < endIndex; i++) {
+            locationArray.push(this._route.turnPoints[i].coordinate);
+        }
+        return locationArray;
+    },
+
+    _addRouteLayer: function(view) {
+        let routeLayer = new Champlain.PathLayer({ stroke_width: _STROKE_WIDTH,
+                                                   stroke_color: _STROKE_COLOR });
+        view.add_layer(routeLayer);
+        this._route.path.forEach(routeLayer.add_node.bind(routeLayer));
+    },
+
+    _drawInstruction: function(width, height, turnPoint) {
+        let pageNum = this.numPages - 1;
+        let x = this._cursorX;
+        let y = this._cursorY;
+        let instructionWidget = new Gtk.OffscreenWindow({ visible: true });
+        let instructionEntry =  new InstructionRow.InstructionRow({
+            visible: true,
+            turnPoint: turnPoint
+        });
+
+        instructionWidget.width_request = width;
+        instructionWidget.height_request = height;
+
+        /* Paint the background of the entry to be transparent */
+        instructionEntry.connect('draw', (function(widget, cr) {
+            cr.setSourceRGBA(0.0, 0.0, 0.0, 0.0);
+            cr.setOperator(Cairo.Operator.SOURCE);
+            cr.paint();
+            cr.setOperator(Cairo.Operator.OVER);
+        }).bind(this));
+
+        instructionEntry.queue_draw();
+        instructionWidget.add(instructionEntry);
+        instructionWidget.set_valign(Gtk.Align.START);
+        instructionWidget.connect('damage-event', (function(widget) {
+            let surface = widget.get_surface();
+            this._addSurface(surface, x, y, pageNum);
+        }).bind(this));
+    },
+
+    _drawHeader: function(width, height) {
+        let pageNum = this.numPages - 1;
+        let x = this._cursorX;
+        let y = this._cursorY;
+        let surface = new Cairo.ImageSurface(Cairo.Format.ARGB32, width, height);
+        let cr = new Cairo.Context(surface);
+        let layout = PangoCairo.create_layout(cr);
+        let from = this._formatQueryPlaceName(0);
+        let to = this._formatQueryPlaceName(-1);
+        let header = _("From %s to %s").format(from, to);
+        let desc = Pango.FontDescription.from_string("sans");
+
+        layout.set_text(header, -1);
+        layout.set_height(Pango.units_from_double(height));
+        layout.set_width(Pango.units_from_double(width));
+        layout.set_font_description(desc);
+        layout.set_alignment(Pango.Alignment.CENTER);
+        PangoCairo.layout_path(cr, layout);
+        cr.setSourceRGB(0.0,0.0,0.0);
+        cr.fill();
+
+        this._addSurface(surface, x, y, pageNum);
+    },
+
+    _addSurface: function(surface, x, y, pageNum) {
+        this.surfaceObjects[pageNum].push({ surface: surface, x: x, y: y });
+        this._surfacesRendered++;
+        if (this._surfacesRendered === this._totalSurfaces)
+            this.emit('render-complete');
+    },
+
+    _adjustPage: function(dy) {
+        if (this._cursorY + dy > this._pageHeight)
+            this._createNewPage();
+    },
+
+    _createNewPage: function() {
+        this.numPages++;
+        this.surfaceObjects[this.numPages - 1] = [];
+        this._cursorX = 0;
+        this._cursorY = 0;
+    },
+
+    _formatQueryPlaceName: function(index) {
+        let query = Application.routeService.query;
+        if (index === -1)
+            index = query.filledPoints.length - 1;
+        let name;
+        let place = query.filledPoints[index].place;
+        if (place.name) {
+            name = place.name;
+            if (name.length > 25)
+                name = name.substr(0, 22) + '\u2026';
+        } else {
+            let lat = place.location.latitude.toFixed(5);
+            let lon = place.location.latitude.toFixed(5);
+            name = '%s, %s'.format(lat, lon);
+        }
+
+        return name;
+    }
+});
diff --git a/src/printOperation.js b/src/printOperation.js
new file mode 100644
index 0000000..422490d
--- /dev/null
+++ b/src/printOperation.js
@@ -0,0 +1,108 @@
+/* -*- Mode: JS2; indent-tabs-mode: nil; js2-basic-offset: 4 -*- */
+/* vim: set et ts=4 sw=4: */
+/*
+ * GNOME Maps is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * GNOME Maps is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with GNOME Maps; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Amisha Singla <amishas157 gmail com>
+ */
+
+const Gtk = imports.gi.Gtk;
+const Lang = imports.lang;
+const Mainloop = imports.mainloop;
+
+const Application = imports.application;
+const PrintLayout = imports.printLayout;
+const Utils = imports.utils;
+
+const _MIN_TIME_TO_ABORT = 3000;
+
+const PrintOperation = new Lang.Class({
+    Name: 'PrintOperation',
+
+    _init: function(params) {
+        this._mainWindow = params.mainWindow;
+        delete params.mainWindow;
+
+        this._operation = new Gtk.PrintOperation({ embed_page_setup: true });
+        this._operation.connect('begin-print', this._beginPrint.bind(this));
+        this._operation.connect('paginate', this._paginate.bind(this));
+        this._operation.connect('draw-page', this._drawPage.bind(this));
+
+        this._abortDialog = new Gtk.MessageDialog({
+            transient_for: this._mainWindow,
+            destroy_with_parent: true,
+            message_type: Gtk.MessageType.OTHER,
+            modal: true,
+            text: _("Loading map tiles for printing"),
+            secondary_text: _("You can abort printing if this takes too long")
+        });
+        this._abortDialog.add_button(_("Abort printing"),
+                                     Gtk.ResponseType.CANCEL);
+        this._responseId = this._abortDialog.connect('response',
+                                                     this.onAbortDialogResponse.bind(this));
+
+        this._runPrintOperation();
+    },
+
+    _beginPrint: function(operation, context, data) {
+        let route = Application.routeService.route;
+        let width = context.get_width();
+        let height = context.get_height();
+
+        Mainloop.timeout_add(_MIN_TIME_TO_ABORT, (function() {
+            if (this._operation.get_status() !== Gtk.PrintStatus.FINISHED) {
+                this._abortDialog.show();
+            }
+            return false;
+        }).bind(this), null);
+
+        this._layout = PrintLayout.newFromRoute(route, width, height);
+        this._layout.render();
+    },
+
+    onAbortDialogResponse: function(dialog, response) {
+        if (response === Gtk.ResponseType.DELETE_EVENT ||
+            response === Gtk.ResponseType.CANCEL) {
+            this._abortDialog.disconnect(this._responseId);
+            this._operation.cancel();
+            this._abortDialog.close();
+        }
+    },
+
+    _paginate: function(operation, context) {
+        if (this._layout.renderFinished) {
+            operation.set_n_pages(this._layout.numPages);
+            this._abortDialog.close();
+        }
+        return this._layout.renderFinished;
+    },
+
+    _drawPage: function(operation, context, page_num, data) {
+        let cr = context.get_cairo_context();
+        this._layout.surfaceObjects[page_num].forEach((function(so) {
+            cr.setSourceSurface(so.surface, so.x, so.y);
+            cr.paint();
+        }).bind(this));
+    },
+
+    _runPrintOperation: function() {
+        let result = this._operation.run(Gtk.PrintOperationAction.PRINT_DIALOG,
+                                         this._mainWindow, null);
+
+        if (result === Gtk.PrintOperationResult.ERROR) {
+            let error = this._operation.get_error();
+            Utils.debug('Failed to print: %s'.format(error));
+        }
+    }
+});
diff --git a/src/route.js b/src/route.js
index 61df3ae..2b4656d 100644
--- a/src/route.js
+++ b/src/route.js
@@ -53,7 +53,7 @@ const Route = new Lang.Class({
         this.turnPoints = turnPoints;
         this.distance = distance;
         this.time = time;
-        this.bbox = bbox || this._createBBox(path);
+        this.bbox = bbox || this.createBBox(path);
 
         this.emit('update');
     },
@@ -67,7 +67,7 @@ const Route = new Lang.Class({
         this.emit('reset');
     },
 
-    _createBBox: function(coordinates) {
+    createBBox: function(coordinates) {
         let bbox = new Champlain.BoundingBox();
         coordinates.forEach(function({ latitude, longitude }) {
             bbox.extend(latitude, longitude);
diff --git a/src/shortPrintLayout.js b/src/shortPrintLayout.js
new file mode 100644
index 0000000..61be99b
--- /dev/null
+++ b/src/shortPrintLayout.js
@@ -0,0 +1,61 @@
+/* -*- Mode: JS2; indent-tabs-mode: nil; js2-basic-offset: 4 -*- */
+/* vim: set et ts=4 sw=4: */
+/*
+ * GNOME Maps is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * GNOME Maps is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with GNOME Maps; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Amisha Singla <amishas157 gmail com>
+ */
+
+const Lang = imports.lang;
+
+const PrintLayout = imports.printLayout;
+
+/* All following constants are ratios of surface size to page size */
+const _Instruction = {
+    SCALE_X: 1.0,
+    SCALE_Y: 0.05,
+    SCALE_MARGIN: 0.01
+};
+
+const ShortPrintLayout = new Lang.Class({
+    Name: 'ShortPrintLayout',
+    Extends: PrintLayout.PrintLayout,
+
+    _init: function(params) {
+        this._route = params.route;
+        delete params.route;
+
+        /* (Header +  map) + instructions */
+        let totalSurfaces = 2 + this._route.turnPoints.length;
+        params.totalSurfaces = totalSurfaces;
+
+        this.parent(params);
+    },
+
+    render: function() {
+        this.parent();
+
+        let instructionWidth = _Instruction.SCALE_X * this._pageWidth;
+        let instructionHeight = _Instruction.SCALE_Y * this._pageHeight;
+        let instructionMargin = _Instruction.SCALE_MARGIN * this._pageHeight;
+        let dy = 0;
+
+        this._route.turnPoints.forEach(function(turnPoint) {
+            dy = instructionHeight + instructionMargin;
+            this._adjustPage(dy);
+            this._drawInstruction(instructionWidth, instructionHeight, turnPoint);
+            this._cursorY += dy;
+        }.bind(this));
+    }
+});


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