2
* Copyright (C) 2007-2008 Camptocamp
4
* This file is part of MapFish Client
6
* MapFish Client is free software: you can redistribute it and/or modify
7
* it under the terms of the GNU General Public License as published by
8
* the Free Software Foundation, either version 3 of the License, or
9
* (at your option) any later version.
11
* MapFish Client is distributed in the hope that it will be useful,
12
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
* GNU General Public License for more details.
16
* You should have received a copy of the GNU General Public License
17
* along with MapFish Client. If not, see <http://www.gnu.org/licenses/>.
21
* @requires core/PrintProtocol.js
22
* @requires core/Util.js
23
* @requires OpenLayers/Control/DragFeature.js
24
* @requires OpenLayers/Layer/Vector.js
25
* @requires OpenLayers/Feature/Vector.js
26
* @requires OpenLayers/Geometry/Polygon.js
29
Ext.namespace('mapfish.widgets');
30
Ext.namespace('mapfish.widgets.print');
33
* Class: mapfish.widgets.print.Base
34
* Base class for the Ext panels used to communicate with the print module,
35
* automatically take the layers from the given {<OpenLayers.Map>} instance.
37
* If you put this panel directly inside an {Ext.TabPanel} or an accordion, it
38
* will activate/desactivate automaticaly. But if you have more complex
39
* layouts like windows or print panel in a panel in an {Ext.TabPanel}, it's
40
* your responsability to call enable() and disable().
47
* Constructor: mapfish.widgets.print.Base
50
* config - {Object} Config object
53
mapfish.widgets.print.Base = Ext.extend(Ext.Panel, {
56
* {<OpenLayers.Map>} - The OpenLayers Map object.
61
* APIProperty: overrides
62
* {Object} the map that specify the print module overrides for each layers.
63
* They can be used of changing the OL layer's bahaviors for the print
64
* module. See the documentation in {<mapfish.PrintProtocol>}.
69
* APIProperty: configUrl
70
* {String} - The URL to access .../config.json. Either this property or
77
* {Object} - The response from .../config.json. Either this property or
78
* configUrl must be set.
83
* APIProperty: layerTree
84
* {<mapfish.widgets.LayerTree>} - An optional layer tree. Needed only if you
85
* want to display legends.
91
* {Object} - An optional dictionary of {Ext.grid.GridPanel}. Needed only
92
* if you want to display search results. Can be function
93
* (returning the dictionary) that will be called each time the
94
* information is needed.
99
* APIProperty: serviceParams
100
* {Object} Additional params to send in the print service Ajax calls. Can
101
* be used to set the "locale" parameter.
107
* {<OpenLayers.Control.DragFeature>} - The control to move the extent.
112
* Property: rotateHandle
113
* {<OpenLayers.Feature.Vector>} - The handle used to rotate the page.
119
* {<OpenLayers.Layer.Vector>} - The layer to draw the extent
125
* {Ext.LoadingMask} - The mask used when loading the configuration or
126
* when generating the PDF
133
* Method: initComponent
134
* Overrides super-class initComponent method. Put in place the handlers
135
* for the showing/hidding of the panel. Then call the createComponent
138
initComponent: function() {
139
mapfish.widgets.print.Base.superclass.initComponent.call(this);
141
this.addEvents("configloaded");
144
this.on('expand', this.setUp, this);
145
this.on('collapse', this.tearDown, this);
148
this.on('activate', this.setUp, this);
149
this.on('deactivate', this.tearDown, this);
151
//for manual enable/disable
152
this.on('enable', this.setUp, this);
153
this.on('disable', this.tearDown, this);
155
if (this.overrides == null) {
159
this.on('render', function() {
160
var mask = this.mask = new Ext.LoadMask(this.getEl(), {
161
msg: OpenLayers.Lang.translate('mf.print.loadingConfig')
163
if (this.config == null) {
168
if (this.config == null) {
169
mapfish.PrintProtocol.getConfiguration(this.configUrl,
170
this.configReceived, this.configFailed, this, this.serviceParams);
172
this.fillComponent();
176
configReceived: function(config) {
180
this.config = config;
181
this.fillComponent();
183
// for the case the widget was opened before the config was received
185
this.fireEvent("configloaded");
188
configFailed: function() {
192
html: OpenLayers.Lang.translate('mf.print.serverDown')
202
* Method: isReallyVisible
204
* One cannot trust Ext's isVisible method. If a panel is within another
205
* panel that is not visible it returns true which is utterly wrong.
208
* {Boolean} True of the panel is really visible
210
isReallyVisible: function() {
211
if (!this.isVisible() || !this.body.isVisible(true)) return false;
213
this.bubble(function(component) {
214
return result = result &&
215
component.isVisible() &&
216
(!component.body || component.body.isVisible());
222
* Method: onShowEvent
224
* Called when the panel is activated.
227
if (!this.disabled && this.isReallyVisible() && this.config && !this.layer) {
228
this.map.addLayer(this.getOrCreateLayer());
229
this.pageDrag.activate();
234
* Method: onHideEvent
236
* Called when the panel is de-activated.
238
tearDown: function() {
239
if (this.config && this.pageDrag && this.layer) {
240
this.pageDrag.destroy();
241
this.pageDrag = null;
243
this.removeRotateHandle();
244
this.layer.removeFeatures(this.layer.features);
245
this.layer.destroy();
251
* Method: getOrCreateLayer
253
* If not already done, creates the layer used to represent the pages to
256
* {<OpenLayers.Layer.Vector>}
258
getOrCreateLayer: function() {
262
this.layer = new OpenLayers.Layer.Vector("Print" + this.getId(), {
263
displayInLayerSwitcher: false,
264
calculateInRange: function() {
269
this.pageDrag = new OpenLayers.Control.DragFeature(this.layer);
270
this.map.addControl(this.pageDrag);
271
var curFeature = null;
272
this.pageDrag.onStart = function(feature) {
273
OpenLayers.Control.DragFeature.prototype.onStart.apply(this, arguments);
274
curFeature = feature;
275
if (feature.attributes.rotate) {
276
self.pageRotateStart(feature);
278
self.pageDragStart(feature);
281
this.pageDrag.onDrag = function(feature) {
282
OpenLayers.Control.DragFeature.prototype.onDrag.apply(this, arguments);
283
if (!feature) feature = curFeature;
284
if (feature.attributes.rotate) {
285
self.pageRotated(feature);
288
this.pageDrag.onComplete = function(feature) {
289
OpenLayers.Control.DragFeature.prototype.onComplete.apply(this, arguments);
290
if (!feature) feature = curFeature;
291
if (feature.attributes.rotate) {
292
self.pageRotateComplete(feature);
294
self.pageDragComplete(feature);
299
this.afterLayerCreated();
305
* Method: pageRotateStart
307
* Called when the user starts to move the rotate handle.
310
* feature - {<OpenLayers.Feature.Vector>} The rotate handle.
312
pageRotateStart: function(feature) {
316
* Method: pageRotated
318
* Called when rotate handle is being moved.
321
* feature - {<OpenLayers.Feature.Vector>} The rotate handle.
323
pageRotated: function(feature) {
324
var center = feature.attributes.center;
325
var pos = feature.geometry;
326
var angle = Math.atan2(pos.x - center.x, pos.y - center.y) * 180 / Math.PI;
327
var page = feature.attributes.page;
328
page.attributes.rotation = angle;
329
page.geometry.rotate(feature.attributes.prevAngle - angle, center);
330
this.layer.drawFeature(page);
331
this.setCurRotation(Math.round(angle));
332
feature.attributes.prevAngle = angle;
336
* Method: pageRotateComplete
338
* Called when rotate handle is being released.
341
* feature - {<OpenLayers.Feature.Vector>} The rotate handle.
343
pageRotateComplete: function(feature) {
344
//put back the rotate handle at the page's edge
345
this.createRotateHandle(feature.attributes.page);
349
* Method: pageDragStart
351
* Called when we start editing a page.
354
* feature - {<OpenLayers.Feature.Vector>} The selected page.
356
pageDragStart: function(feature) {
357
this.removeRotateHandle();
361
* Method: removeRotateHandle
363
* Remove the rotation handle, if any.
365
removeRotateHandle: function() {
366
if (this.rotateHandle) {
367
this.rotateHandle.destroy();
368
this.rotateHandle = null;
373
* Method: pageDragComplete
375
* Called when we stop editing a page.
378
* feature - {<OpenLayers.Feature.Vector>} The selected page.
380
pageDragComplete: function(feature) {
381
if (this.getCurLayout().rotation) {
382
this.createRotateHandle(feature);
387
* Method: createRotateHandle
389
* Create the handle used to rotate the page.
392
* feature - {<OpenLayers.Feature.Vector>} The selected page.
394
createRotateHandle: function(feature) {
395
this.removeRotateHandle();
397
var firstPoint = feature.geometry.components[0].components[2];
398
var secondPoint = feature.geometry.components[0].components[3];
399
var lon = (firstPoint.x + secondPoint.x) / 2;
400
var lat = (firstPoint.y + secondPoint.y) / 2;
401
var rotatePoint = new OpenLayers.Geometry.Point(lon, lat);
402
var center = this.getCenterRectangle(feature);
403
this.rotateHandle = new OpenLayers.Feature.Vector(rotatePoint, {
406
center: {x: center[0], y: center[1]},
407
prevAngle: feature.attributes.rotation
409
this.layer.addFeatures(this.rotateHandle);
413
* Method: createRectangle
415
* Create the feature representing a page to print.
418
* center - {<OpenLayers.LonLat>} The center of the rectangle.
419
* scale - {Integer} The page's scale
420
* layout - {Object} The current layout object from the configuration.
421
* rotation - {float} The current rotation in degrees.
424
* {<OpenLayers.Feature.Vector>}
426
createRectangle: function(center, scale, layout, rotation) {
427
var extent = this.getExtent(center, scale, layout);
428
var rect = extent.toGeometry();
429
rect.rotate(-rotation, {x:center.lon, y:center.lat});
430
var feature = new OpenLayers.Feature.Vector(rect, {rotation: rotation});
431
this.layer.addFeatures(feature);
437
* Method: getCenterRectangle
440
* rectangle - {<OpenLayers.Feature.Vector>}
443
* {<OpenLayers.LonLat>} The center of the rectangle.
445
getCenterRectangle: function(rectangle) {
446
var center = rectangle.geometry.getBounds().getCenterLonLat();
447
return [center.lon, center.lat];
453
* Compute the page's extent.
456
* center - {<OpenLayers.LonLat>} The center of the rectangle.
457
* scale - {Integer} The page's scale
458
* layout - {Object} The current layout object from the configuration.
461
* {<OpenLayers.Bounds>}
463
getExtent: function(center, scale, layout) {
464
var unitsRatio = OpenLayers.INCHES_PER_UNIT[this.map.baseLayer.units];
466
var size = layout.map;
467
var w = size.width / 72.0 / unitsRatio * scale / 2.0;
468
var h = size.height / 72.0 / unitsRatio * scale / 2.0;
470
return new OpenLayers.Bounds(
478
* Finds the best scale to use for the given layout in function of the map's
482
* layout - {Object} The current layout object from the configuration.
485
* {Integer} The best scale
487
fitScale: function(layout) {
488
var availsTxt = this.config.scales;
489
if (availsTxt.length == 0) return;
491
for (var i = 0; i < availsTxt.length; ++i) {
492
avails.push(parseFloat(availsTxt[i].value));
494
avails.sort(function(a, b) {
498
var bounds = this.map.getExtent();
499
var unitsRatio = OpenLayers.INCHES_PER_UNIT[this.map.baseLayer.units];
500
var size = layout.map;
502
var targetScale = Math.min(bounds.getWidth() / size.width * 72.0 * unitsRatio,
503
bounds.getHeight() / size.height * 72.0 * unitsRatio);
505
var nearestScale = avails[0];
506
for (var j = 1; j < avails.length; ++j) {
507
if (avails[j] <= targetScale) {
508
nearestScale = avails[j];
520
* Do the actual printing.
523
//we don't want the layer used for the extent to be printed, don't we?
524
this.overrides[this.layer.name] = {visibility: false};
526
var printCommand = new mapfish.PrintProtocol(this.map, this.config,
527
this.overrides, this.getCurDpi(), this.serviceParams);
528
if (this.layerTree) {
529
this.addLegends(printCommand.spec);
532
this.addGrids(printCommand.spec);
534
this.fillSpec(printCommand);
536
this.mask.msg = OpenLayers.Lang.translate('mf.print.generatingPDF');
538
printCommand.createPDF(
539
function() { //success
542
function(request) { //failure
544
Ext.Msg.alert(OpenLayers.Lang.translate('mf.information'),
545
OpenLayers.Lang.translate('mf.print.popupBlocked') +
547
'<a href="' + request.getURL + '" target="_blanc">' +
548
request.getURL+'</a>');
550
Ext.Msg.alert(OpenLayers.Lang.translate('mf.error'),
551
OpenLayers.Lang.translate('mf.print.unableToPrint'));
560
* Add the grids' data to the given spec.
563
* spec - {Object} The print spec to fill.
565
addGrids: function(spec) {
566
var grids = this.grids;
567
if (grids && typeof grids == "function") {
571
for (var name in grids) {
572
var grid = grids[name];
577
var specData = spec[name].data = [];
578
var specCols = spec[name].columns = [];
579
var columns = grid.getColumnModel();
580
var store = grid.getStore();
581
for (var j = 0; j < columns.getColumnCount(); ++j) {
582
if (!columns.isHidden(j)) {
583
specCols.push(columns.getDataIndex(j));
586
store.each(function(record) {
588
for (var key in record.data) {
589
var val = record.data[key];
591
if (val.CLASS_NAME && val.CLASS_NAME == 'OpenLayers.Feature.Vector') {
592
val = new OpenLayers.Format.WKT().write(val);
606
* Add the layerTree's legends to the given spec.
609
* spec - {Object} The print spec to fill.
611
addLegends: function(spec) {
612
var legends = spec.legends = [];
614
function addLayer(layerNode) {
616
name: layerNode.attributes.printText || layerNode.attributes.text,
617
icon: mapfish.Util.relativeToAbsoluteURL(layerNode.attributes.icon)
619
var classesInfo = layerInfo.classes = [];
620
layerNode.eachChild(function(classNode) {
622
name: classNode.attributes.printText || classNode.attributes.text,
623
icon: mapfish.Util.relativeToAbsoluteURL(classNode.attributes.icon)
626
legends.push(layerInfo);
629
function goDeep(root) {
630
root.eachChild(function(node) {
631
var attr = node.attributes;
632
if (attr.checked && attr.layerNames) {
639
goDeep(this.layerTree.getRootNode());
643
* Method: getLayoutForName
645
* Finds the layout object from the configuration by it's name.
648
* layoutName - {String}
653
getLayoutForName: function(layoutName) {
654
var layouts = this.config.layouts;
655
for (var i = 0; i < layouts.length; ++i) {
656
var cur = layouts[i];
657
if (cur.name == layoutName) {
664
* Method: createScaleCombo
666
createScaleCombo: function() {
667
var scaleStore = new Ext.data.JsonStore({
669
fields: ['name', 'value'],
673
return new Ext.form.ComboBox({
674
fieldLabel: OpenLayers.Lang.translate('mf.print.scale'),
676
displayField: 'name',
680
id: 'scale_' + this.getId(),
681
hiddenId: 'scaleId_' + this.getId(),
685
triggerAction: 'all',
686
value: this.config.scales[this.config.scales.length - 1].value
691
* Method: createDpiCombo
693
createDpiCombo: function(name) {
694
if (this.config.dpis.length > 1) {
695
var dpiStore = new Ext.data.JsonStore({
697
fields: ['name', 'value'],
702
fieldLabel: OpenLayers.Lang.translate('mf.print.dpi'),
705
displayField: 'name',
709
id: 'dpi_' + this.getId(),
710
hiddenId: 'dpiId_' + this.getId(),
714
triggerAction: 'all',
715
value: this.config.dpis[0].value
721
value: this.config.dpis[0].value
727
* Method: createLayoutCombo
729
createLayoutCombo: function(name) {
730
if (this.config.layouts.length > 1) {
731
var layoutStore = new Ext.data.JsonStore({
737
return new Ext.form.ComboBox({
738
fieldLabel: OpenLayers.Lang.translate('mf.print.layout'),
740
displayField: 'name',
744
id: 'layout_' + this.getId(),
745
hiddenId: 'layoutId_' + this.getId(),
749
triggerAction: 'all',
750
value: this.config.layouts[0].name
753
return new Ext.form.Hidden({
755
value: this.config.layouts[0].name
761
* Method: createRotationTextField
763
* Creates a text field for editing the rotation. Only if the config
764
* has at least one layout allowing rotations, otherwise, returns null.
766
createRotationTextField: function() {
767
var layouts = this.config.layouts;
768
var hasRotation = false;
769
for (var i = 0; i < layouts.length && ! hasRotation; ++i) {
770
hasRotation = layouts[i].rotation;
773
var num = /^-?[0-9]+$/;
774
return new Ext.form.TextField({
775
fieldLabel: OpenLayers.Lang.translate('mf.print.rotation'),
780
validator: function(v) {
781
return num.test(v) ? true : "Not a number";
790
* Method: fillComponent
792
* Called by initComponent to create the component's sub-elements. To be
793
* implemented by child classes.
798
* Method: afterLayerCreated
800
* Called just after the layer has been created. To be implemented by child
803
afterLayerCreated: null,
808
* Returns the user selected DPI. To be implemented by child classes.
814
* Add the page definitions and set the other parameters. To be implemented
818
* printCommand - {<mapfish.PrintProtocol>} The print definition to fill.
823
* Method: getCurLayout
826
* {Object} - The current layout config object
831
* Method: setCurRotation
833
* Called when the rotation of the current page has been changed.