/* -*- 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 .
*
* Author: Amisha Singla
*/
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;
}
});