Statistics
| Revision:

root / branches / v2_0_0_prep / libraries / libFMap_controls / src / org / gvsig / fmap / mapcontrol / MapControl.java @ 38514

History | View | Annotate | Download (87.5 KB)

1
/* gvSIG. Sistema de Informaci?n Geogr?fica de la Generalitat Valenciana
2
 *
3
 * Copyright (C) 2004 IVER T.I. and Generalitat Valenciana.
4
 *
5
 * This program is free software; you can redistribute it and/or
6
 * modify it under the terms of the GNU General Public License
7
 * as published by the Free Software Foundation; either version 2
8
 * of the License, or (at your option) any later version.
9
 *
10
 * This program is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
 * GNU General Public License for more details.
14
 *
15
 * You should have received a copy of the GNU General Public License
16
 * along with this program; if not, write to the Free Software
17
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,USA.
18
 *
19
 * For more information, contact:
20
 *
21
 *  Generalitat Valenciana
22
 *   Conselleria d'Infraestructures i Transport
23
 *   Av. Blasco Ib??ez, 50
24
 *   46010 VALENCIA
25
 *   SPAIN
26
 *
27
 *      +34 963862235
28
 *   gvsig@gva.es
29
 *      www.gvsig.gva.es
30
 *
31
 *    or
32
 *
33
 *   IVER T.I. S.A
34
 *   Salamanca 50
35
 *   46005 Valencia
36
 *   Spain
37
 *
38
 *   +34 963163400
39
 *   dac@iver.es
40
 */
41
package org.gvsig.fmap.mapcontrol;
42

    
43
import java.awt.Color;
44
import java.awt.Cursor;
45
import java.awt.Dimension;
46
import java.awt.Graphics;
47
import java.awt.Graphics2D;
48
import java.awt.Image;
49
import java.awt.Point;
50
import java.awt.Toolkit;
51
import java.awt.event.ActionEvent;
52
import java.awt.event.ActionListener;
53
import java.awt.event.ComponentEvent;
54
import java.awt.event.ComponentListener;
55
import java.awt.event.MouseEvent;
56
import java.awt.event.MouseListener;
57
import java.awt.event.MouseMotionListener;
58
import java.awt.event.MouseWheelEvent;
59
import java.awt.event.MouseWheelListener;
60
import java.awt.geom.Point2D;
61
import java.awt.image.BufferedImage;
62
import java.awt.image.MemoryImageSource;
63
import java.util.ArrayList;
64
import java.util.Comparator;
65
import java.util.HashMap;
66
import java.util.List;
67
import java.util.Set;
68
import java.util.TreeMap;
69
import java.util.prefs.Preferences;
70

    
71
import javax.swing.JComponent;
72
import javax.swing.SwingUtilities;
73
import javax.swing.Timer;
74

    
75
import org.cresques.cts.IProjection;
76
import org.slf4j.Logger;
77
import org.slf4j.LoggerFactory;
78

    
79
import org.gvsig.fmap.crs.CRSFactory;
80
import org.gvsig.fmap.dal.DataStoreNotification;
81
import org.gvsig.fmap.dal.feature.FeatureStoreNotification;
82
import org.gvsig.fmap.geom.Geometry;
83
import org.gvsig.fmap.geom.Geometry.SUBTYPES;
84
import org.gvsig.fmap.geom.GeometryLocator;
85
import org.gvsig.fmap.geom.GeometryManager;
86
import org.gvsig.fmap.geom.exception.CreateEnvelopeException;
87
import org.gvsig.fmap.geom.primitive.Envelope;
88
import org.gvsig.fmap.geom.util.Converter;
89
import org.gvsig.fmap.mapcontext.MapContext;
90
import org.gvsig.fmap.mapcontext.ViewPort;
91
import org.gvsig.fmap.mapcontext.events.AtomicEvent;
92
import org.gvsig.fmap.mapcontext.events.listeners.AtomicEventListener;
93
import org.gvsig.fmap.mapcontext.layers.FLayers;
94
import org.gvsig.fmap.mapcontext.layers.LayerCollectionEvent;
95
import org.gvsig.fmap.mapcontext.layers.LayerEvent;
96
import org.gvsig.fmap.mapcontext.layers.SpatialCache;
97
import org.gvsig.fmap.mapcontext.layers.vectorial.FLyrVect;
98
import org.gvsig.fmap.mapcontext.layers.vectorial.GraphicLayer;
99
import org.gvsig.fmap.mapcontrol.tools.BehaviorException;
100
import org.gvsig.fmap.mapcontrol.tools.CompoundBehavior;
101
import org.gvsig.fmap.mapcontrol.tools.Behavior.Behavior;
102
import org.gvsig.fmap.mapcontrol.tools.Listeners.ToolListener;
103
import org.gvsig.fmap.mapcontrol.tools.grid.Grid;
104
import org.gvsig.fmap.mapcontrol.tools.snapping.snappers.ISnapper;
105
import org.gvsig.fmap.mapcontrol.tools.snapping.snappers.ISnapperGeometriesVectorial;
106
import org.gvsig.fmap.mapcontrol.tools.snapping.snappers.ISnapperRaster;
107
import org.gvsig.fmap.mapcontrol.tools.snapping.snappers.ISnapperVectorial;
108
import org.gvsig.tools.ToolsLocator;
109
import org.gvsig.tools.dispose.Disposable;
110
import org.gvsig.tools.dispose.impl.AbstractDisposable;
111
import org.gvsig.tools.exception.BaseException;
112
import org.gvsig.tools.observer.Observable;
113
import org.gvsig.tools.observer.Observer;
114
import org.gvsig.tools.task.Cancellable;
115
import org.gvsig.utils.exceptionHandling.ExceptionHandlingSupport;
116
import org.gvsig.utils.exceptionHandling.ExceptionListener;
117

    
118
/**
119
 * <p>
120
 * A component that includes a {@link MapContext MapContext} with support for
121
 * use it as a particular {@link Behavior Behavior}.
122
 * </p>
123
 * 
124
 * <p>
125
 * A developer can register a set of <code>Behavior</code>, but only one (that
126
 * can be a composition of several) of them can be active. The active one
127
 * defines the way to work and access with its <code>MapContext</code>'s layers.
128
 * The active behavior, in combination with the appropriate {@link ToolListener
129
 * ToolListener} will allow user work with a particular <i>tool</i>.
130
 * </p>
131
 * 
132
 * <p>
133
 * All mouse events produced on this component will be delegated to the current
134
 * active behavior, the <i>currentMapTool</i>.
135
 * </p>
136
 * 
137
 * <p>
138
 * <b>Drawing process:</b>
139
 * </p>
140
 * 
141
 * <p>
142
 * Uses a double buffer for the drawing process of <code>MapContext</code>'s
143
 * information.
144
 * </p>
145
 * 
146
 * <p>
147
 * If the double buffer wasn't created, creates a new one.
148
 * </p>
149
 * 
150
 * <p>
151
 * Paints the component according the following algorithm: <br>
152
 * &nbsp If <i>status</i> is <i>UPDATED</i>:<br>
153
 * &nbsp &nbsp If there is a <i>double buffer</i>:<br>
154
 * &nbsp &nbsp &nbsp If there is a <i>behavior</i> for managing the
155
 * <code>MapControl</code> instance, delegates the drawing process to that
156
 * behavior, calling: <code><i>behavior_instance</i>.paintComponent(g)</code>.<br>
157
 * &nbsp &nbsp &nbsp Else, repaints the current graphical information quickly
158
 * calling: <code>g.drawImage(image,0,0,null)</code>.<br>
159
 * &nbsp Else, (<i>status</i> is <i>OUTDATED</i>, or <i>ONLY_GRAPHICS</i>):
160
 * executes a quickly repaint of the previous information calling
161
 * <code>g.drawImage(image,0,0,null)</code>, and creates a <i>painting
162
 * request</i> to delegate the heavy drawing process to the {@link Drawer2
163
 * Drawer2}'s worker thread, according the <i>SingleWorketThread</i> pattern,
164
 * starting a timer to update (invoking <code>repaint()</code>) the view every
165
 * delay of <code>1000 / drawFrameRate</code> ms. during that heavy drawing
166
 * process, and if its enabled <code>drawAnimationEnabled</code>. The
167
 * <i>painting request</i> once is being attended, invokes
168
 * <code>MapContext</code> to draw the layers:
169
 * <code>mapContext.draw(image, g, cancel,mapContext.getScaleView());</code>
170
 * <br>
171
 * <p>
172
 * Some notes:
173
 * <ul>
174
 * <li>The painting process can be cancelled calling {@link #cancelDrawing()
175
 * #cancelDrawing()}.</li>
176
 * <li>At last resort, the particular implementation of each layer in a
177
 * <code>MapControl</code>'s <code>MapContrext</code> will be that one which
178
 * will draw the graphical information, and, if supports, which could cancel its
179
 * drawing subprocess.</li>
180
 * <li>It's possible to force repaint all layers, calling
181
 * {@link #drawMap(boolean doClear) #drawMap(boolean)}.</li>
182
 * <li>It's possible repaint only the dirty layers, calling
183
 * {@link #rePaintDirtyLayers() #rePaintDirtyLayers()}.</li>
184
 * <li>It's possible repaint only the {@link GraphicLayer GraphicLayer}, calling
185
 * {@link #drawGraphics() #drawGraphics()}.</li>
186
 * </ul>
187
 * </p>
188
 * 
189
 * <p>
190
 * <b>Tools:</b>
191
 * </p>
192
 * 
193
 * <p>
194
 * A developer can:
195
 * <ul>
196
 * <li>Register each tool as:
197
 * <ul>
198
 * <li>A single behavior: {@link #addBehavior(String, Behavior)
199
 * #addMapTool(String, Behavior)}.</li>
200
 * <li>Or, a compound behavior: {@link #addBehavior(String, Behavior)
201
 * #addMapTool(String, Behavior)}.</li>
202
 * </ul>
203
 * </li>
204
 * <li>Get the current active tool: {@link #getCurrentMapTool()
205
 * #getCurrentMapTool()}.</li>
206
 * <li>Get the current active tool name: {@link #getCurrentTool()
207
 * #getCurrentTool()}.</li>
208
 * <li>Get a registered tool: {@link #getMapTool(String) #getMapTool(String)}.</li>
209
 * <li>Get the name of all tools registered: {@link #getMapToolsKeySet()
210
 * #getMapToolsKeySet()}.</li>
211
 * <li>Get all tools registered, including the name they were registered:
212
 * {@link #getNamesMapTools() #getNamesMapTools()}.</li>
213
 * <li>Determine if has a tool registered: {@link #hasTool(String)
214
 * #hasTool(String)}.</li>
215
 * <li>Set as an active tool, one of the registered: {@link #setTool(String)
216
 * #setTool(String)}.</li>
217
 * <li>Set as active tool, the previous used: {@link #setPrevTool()
218
 * #setPrevTool()}.</li>
219
 * <li>Set the current tool: {@link #setCurrentMapTool(Behavior)
220
 * #setCurrentMapTool(Behavior)}.</li>
221
 * <li>Change the draw frame rate: {@link #setDrawFrameRate(int)
222
 * #setDrawFrameRate(int)} and {@link #applyFrameRate() #applyFrameRate()}.</li>
223
 * <li>Get the draw frame rate: {@link #getDrawFrameRate() #getDrawFrameRate()}.
224
 * </li>
225
 * <li>Determine if will repaint this component each time timer finishes:
226
 * {@link #isDrawAnimationEnabled() #isDrawAnimationEnabled()}.</li>
227
 * <li>Change if will repaint this component each time timer finishes:
228
 * {@link #setDrawAnimationEnabled(boolean) #setDrawAnimationEnabled(boolean)}.</li>
229
 * <li>Get the shared object that determines if a drawing process must be
230
 * cancelled or can continue: {@link #getCanceldraw() #getCanceldraw()}.</li>
231
 * <li>Get the combined tool: {@link #getCombinedTool() #getCombinedTool()}.</li>
232
 * <li>Set a combined tool: {@link #setCombinedTool(Behavior)
233
 * #setCombinedTool(Behavior)}.</li>
234
 * <li>Remove the combined tool: {@link #removeCombinedTool()
235
 * #removeCombinedTool()}.</li>
236
 * </ul>
237
 * </p>
238
 * 
239
 * <p>
240
 * <b>Exception listener:</b>
241
 * </p>
242
 * 
243
 * <p>
244
 * Adding an <code>ExceptionListener</code>, can get notification about any
245
 * exception produced:
246
 * <ul>
247
 * <li>Attending a <i>painting request</i>.</li>
248
 * <li>Working with the active tool.</li>
249
 * <li>Applying a <i>zoom in</i> or <i>zoom out</i> operation.</li>
250
 * </ul>
251
 * </p>
252
 * 
253
 * <p>
254
 * <b>Other:</b>
255
 * </p>
256
 * 
257
 * <p>
258
 * Other useful capabilities of <code>MapControl</code>:
259
 * <ul>
260
 * <li>Cancel the current drawing process (notifying it also to the inner
261
 * <code>MapContext</code> instance and its layers): {@link #cancelDrawing()
262
 * #cancelDrawing()}.</li>
263
 * <li>Applying a <i>zoom in</i> operation centered at mouse position (without a
264
 * <code>ToolListener</code>): {@link #zoomIn() #zoomIn()}.</li>
265
 * <li>Applying a <i>zoom out</i> operation centered at mouse position (without
266
 * a <code>ToolListener</code>): {@link #zoomOut() #zoomOut()}.</li>
267
 * </ul>
268
 * </p>
269
 * 
270
 * @see CancelDraw
271
 * @see Drawer
272
 * @see MapContextListener
273
 * @see MapToolListener
274
 * 
275
 * @author Fernando Gonz?lez Cort?s
276
 * @author Pablo Piqueras Bartolom? (pablo.piqueras@iver.es)
277
 */
278
public class MapControl extends JComponent implements ComponentListener,
279
    Observer, Disposable {
280

    
281
    protected static final GeometryManager geomManager =
282
        GeometryLocator.getGeometryManager();
283
    private static final Logger LOG =
284
        LoggerFactory.getLogger(GeometryManager.class);
285

    
286
    /**
287
     * <p>
288
     * One of the possible status of <code>MapControl</code>. Determines that
289
     * all visible information has been drawn and its updated.
290
     * </p>
291
     */
292
    public static final int ACTUALIZADO = 0;
293

    
294
    /**
295
     * <p>
296
     * One of the possible status of <code>MapControl</code>. Determines that
297
     * not all visible information has been drawn or isn't updated.
298
     * </p>
299
     */
300
    public static final int DESACTUALIZADO = 1;
301

    
302
    /**
303
     * <p>
304
     * Determines if the drawer can update this <code>MapControl</code> instance
305
     * when the timer launches an event.
306
     * </p>
307
     */
308
    private static boolean drawAnimationEnabled = true;
309

    
310
    /**
311
     * <p>
312
     * Inner model with the layers, event support for drawing them, and the
313
     * <code>ViewPort</code> with information to adapt to the bounds available
314
     * in <i>image coordinates</i>.
315
     * </p>
316
     * 
317
     * @see #getMapContext()
318
     * @see #setMapContext(MapContext)
319
     */
320
    private MapContext mapContext = null;
321

    
322
    /**
323
     * <p>
324
     * All registered <code>Behavior</code> that can define a way to work with
325
     * this <code>MapControl</code>.
326
     * </p>
327
     * 
328
     * <p>
329
     * Only one of them can be active at a given moment.
330
     * </p>
331
     * 
332
     * @see #addBehavior(String, Behavior)
333
     * @see #addBehavior(String, Behavior[])
334
     * @see #getMapTool(String)
335
     * @see #getMapToolsKeySet()
336
     * @see #getNamesMapTools()
337
     */
338
    protected HashMap namesMapTools = new HashMap();
339

    
340
    /**
341
     * <p>
342
     * Active {@link Behavior Behavior} that will generate events according a
343
     * criterion, and then, with a {@link ToolListener ToolListener} associated,
344
     * will simulate to user that works with this component as a particular
345
     * tool.
346
     * </p>
347
     * 
348
     * @see #getCurrentMapTool()
349
     * @see #getCurrentTool()
350
     * @see #setTool(String)
351
     */
352
    protected Behavior currentMapTool = null;
353

    
354
    /**
355
     * <p>
356
     * Determines which's the current drawn status of this component:
357
     * <ul>
358
     * <li><b>OUTDATED</b>: all visible information has been drawn or isn't
359
     * updated.</li>
360
     * <li><b>UTDATED</b>: all visible information has been drawn and its
361
     * updated.</li>
362
     * <li><b>ONLY_GRAPHICS</b>: only the graphical layer must be drawn /
363
     * updated.</li>
364
     * </ul>
365
     * </p>
366
     * 
367
     * <p>
368
     * The <code>MapControl</code> drawing process will consider the value of
369
     * this parameter to decide which elements will be updated or drawn.
370
     * </p>
371
     */
372
    private int status = DESACTUALIZADO;
373

    
374
    /**
375
     * <p>
376
     * Image with a buffer to accelerate the draw the changes of the graphical
377
     * items in this component.
378
     * </p>
379
     * 
380
     * <p>
381
     * Firstly, information will be drawn in the buffer, and, when is outright
382
     * drawn, that information will be displayed. Meanwhile, the previous image
383
     * can be kept showed.
384
     * </p>
385
     * 
386
     * @see BufferedImage
387
     * 
388
     * @see #getImage()
389
     */
390
    private BufferedImage image = null;
391

    
392
    /**
393
     * <p>
394
     * Name of the tool used currently to interact with this component.
395
     * </p>
396
     * 
397
     * @see #getCurrentTool()
398
     * @see #setTool(String)
399
     */
400
    protected String currentTool;
401

    
402
    /**
403
     * <p>
404
     * Object to store the flag that notifies a drawing thread task and
405
     * <code>MapContext</code>'s layers, that must be canceled or can continue
406
     * with the process.
407
     * </p>
408
     * 
409
     * @see #cancelDrawing()
410
     */
411
    private CancelDraw canceldraw;
412

    
413
    // private boolean isCancelled = true;
414

    
415
    /**
416
     * <p>
417
     * Fires an action events after a specified delay.
418
     * </p>
419
     * 
420
     * <p>
421
     * <code>MapControl</code> will use the timer to update its visible
422
     * graphical information during a drawing process, or allowing to cancel
423
     * that process.
424
     * </p>
425
     * 
426
     * <p>
427
     * This is very useful to pretend faster interactivity to user when
428
     * <code>MapControl</code> has lots of layers, and / or layers with heavy
429
     * graphical elements, that need a long time to finish drawing all its data.
430
     * </p>
431
     */
432
    private Timer timer;
433

    
434
    /**
435
     * <p>
436
     * Reference to the {@link ViewPort ViewPort} of the {@link MapContext
437
     * MapContext} of this component.
438
     * </p>
439
     * 
440
     * <p>
441
     * The view port once is created an instance of <code>MapControl</code>, is
442
     * obtained from the <i>EPSG:23030</i> projection, that's the default
443
     * projection for this component.
444
     * </p>
445
     * 
446
     * <p>
447
     * After, the view port will change adapting itself according the current
448
     * projection and the extent.
449
     * </p>
450
     * 
451
     * @see #getViewPort()
452
     * 
453
     * @see ViewPort
454
     */
455
    protected ViewPort vp;
456

    
457
    /**
458
     * <p>
459
     * Manager of all <code>MapControl</code> painting requests.
460
     * </p>
461
     */
462
    private Drawer drawer;
463

    
464
    /**
465
     * <p>
466
     * Listener of all kind of mouse events produced in this component.
467
     * </p>
468
     * 
469
     * <p>
470
     * Delegates each mouse event to the current map tool.
471
     * </p>
472
     * 
473
     * @see #addBehavior(String, Behavior)
474
     * @see #addBehavior(String, Behavior[])
475
     * @see #getMapTool(String)
476
     * @see #getMapToolsKeySet()
477
     * @see #getNamesMapTools()
478
     * @see #setTool(String)
479
     */
480
    protected MapToolListener mapToolListener = new MapToolListener();
481

    
482
    /**
483
     * <p>
484
     * Listener of all events produced in a this component's
485
     * <code>MapContext</code> object during an atomic period of time.
486
     * </p>
487
     */
488
    private MapContextListener mapContextListener = new MapContextListener();
489

    
490
    /**
491
     * <p>
492
     * Group of <code>ExceptionListener</code> that, in whatever moment could be
493
     * notified a Throwable Java error or exception.
494
     * </p>
495
     * 
496
     * @see #addExceptionListener(ExceptionListener)
497
     * @see #removeExceptionListener(ExceptionListener)
498
     */
499
    private ExceptionHandlingSupport exceptionHandlingSupport =
500
        new ExceptionHandlingSupport();
501

    
502
    /**
503
     * <p>
504
     * Name of the previous tool used.
505
     * </p>
506
     */
507
    protected String prevTool;
508

    
509
    /**
510
     * <p>
511
     * Tool that will be used combined with the current tool of this
512
     * <code>MapControl</code>.
513
     * </p>
514
     */
515
    private Behavior combinedTool = null;
516

    
517
    /**
518
     * Optional grid that could be applied on the <code>MapControl</code>'s view
519
     * port.
520
     * 
521
     * @see #getGrid()
522
     * @see #setAdjustGrid(boolean)
523
     */
524
    private Grid cadgrid = new Grid();
525
    /**
526
     * Represents the cursor's point selected in <i>screen coordinates</i>.
527
     * 
528
     * @see ViewPort#fromMapPoint(Point2D)
529
     */
530
    private Point2D adjustedPoint;
531
    /**
532
     * <p>
533
     * Determines if the position of the snap of the mouse's cursor on the
534
     * <code>MapControl</code> is within the area around a control point of a
535
     * geometry.
536
     * </p>
537
     * 
538
     * <p>
539
     * The area is calculated as a circle centered at the control point and with
540
     * radius the pixels tolerance defined in the preferences.
541
     * </p>
542
     */
543
    private boolean bForceCoord = false;
544

    
545
    /**
546
     * Kind of geometry drawn to identify the kind of control point selected by
547
     * the cursor's mouse.
548
     */
549
    private ISnapper usedSnap = null;
550

    
551
    /**
552
     * Determines if the snap tools are enabled or disabled.
553
     * 
554
     * @see #isRefentEnabled()
555
     * @see #setRefentEnabled(boolean)
556
     */
557
    private boolean bRefent = true;
558

    
559
    /**
560
     * Stores the 2D map coordinates of the last point added.
561
     */
562
    private double[] previousPoint = null;
563

    
564
    protected static MapControlManager mapControlManager =
565
        MapControlLocator.getMapControlManager();
566

    
567
    private static TreeMap selected = new TreeMap(new Comparator() {
568

    
569
        public int compare(Object o1, Object o2) {
570
            if (o1.getClass().equals(o2.getClass()))
571
                return 0;
572
            if (((ISnapper) o1).getPriority() > ((ISnapper) o2).getPriority())
573
                return 1;
574
            else
575
                return -1;
576
        }
577

    
578
    });
579

    
580
    /**
581
     * Represents the cursor's point selected in <i>map coordinates</i>.
582
     * 
583
     * @see MapControl#toMapPoint
584
     */
585
    private Point2D mapAdjustedPoint;
586

    
587
    /**
588
     * Renderer used to draw the layers.
589
     */
590
    private MapControlDrawer mapControlDrawer = null;
591
        private Cursor transparentCursor;
592
        
593
        private boolean disposed = false;
594

    
595
    /**
596
     * <p>
597
     * Creates a new <code>MapControl</code> instance with the following
598
     * characteristics:
599
     * <ul>
600
     * <li><i>Name</i>: MapControl .</li>
601
     * <li>Disables the double buffer of <code>JComponent</code> .</li>
602
     * <li>Sets opaque <i>(see {@link JComponent#setOpaque(boolean)} )</i>.</li>
603
     * <li>Sets its status to <code>OUTDATED</code> .</li>
604
     * <li>Creates a new {@link CancelDraw CancelDraw} object to notify
605
     * <code>MapContext</code>'s layers if can continue processing the drawn or
606
     * must cancel it.</li>
607
     * <li>Creates a new {@link MapContext MapContext} with a new
608
     * {@link ViewPort ViewPort} in the projection <i>"EPSG:23030"</i> .</li>
609
     * <li>Creates a new {@link CommandListener CommandListener} for edition
610
     * operations.</li>
611
     * <li>Creates a new {@link MapToolListener MapToolListener}, and associates
612
     * it as a listener of whatever kind of mouse events produced in this
613
     * component.</li>
614
     * <li>Creates a new {@link Drawer2 Drawer2} for managing the painting
615
     * requests.</li>
616
     * <li>Creates a new timer that will invoke refresh this component
617
     * <code>drawFrameRate</code> per second, when is running a drawing process,
618
     * and its enabled <code>drawAnimationEnabled</code>.</li>
619
     * </ul>
620
     * </p>
621
     */
622
    public MapControl() {
623
        this.setName("MapControl");
624
        Toolkit toolkit = Toolkit.getDefaultToolkit();
625
        Image imageTransparentCursor = toolkit.createImage(new MemoryImageSource(16, 16, new int[16 * 16], 0,16));
626
        transparentCursor =
627
            toolkit.createCustomCursor(imageTransparentCursor, new Point(0, 0), "invisiblecursor");
628

    
629
        setDoubleBuffered(false);
630
        setOpaque(true);
631
        status = DESACTUALIZADO;
632

    
633
        // Clase usada para cancelar el dibujado
634
        canceldraw = new CancelDraw();
635

    
636
        // Modelo de datos y ventana del mismo
637
        // TODO: Cuando creamos un mapControl, deber?amos asignar
638
        // la projecci?n por defecto con la que vayamos a trabajar.
639
        // 23030 es el c?digo EPSG del UTM30 elipsoide ED50
640
        vp = new ViewPort(CRSFactory.getCRS("EPSG:23030"));
641
        setMapContext(new MapContext(vp));
642

    
643
        // eventos
644
        this.addComponentListener(this);
645
        this.addMouseListener(mapToolListener);
646
        this.addMouseMotionListener(mapToolListener);
647
        this.addMouseWheelListener(mapToolListener);
648

    
649
        this.drawer = new Drawer();
650
        // Timer para mostrar el redibujado mientras se dibuja
651
        timer =
652
            new Timer(1000 / MapContext.getDrawFrameRate(),
653
                new ActionListener() {
654

    
655
                    public void actionPerformed(ActionEvent e) {
656

    
657
                        if (drawAnimationEnabled) {
658
                            MapControl.this.repaint();
659
                        }
660
                    }
661
                });
662
        initializeGrid();
663
        
664
        if(ToolsLocator.getDisposableManager() != null) {
665
                        ToolsLocator.getDisposableManager().bind(this);
666
                } else {
667
                        LOG.warn("Can't retrieve the disposable manager,");
668
                }
669
    }
670

    
671
    /**
672
     * <p>
673
     * Sets a <code>MapContext</code> to this component.
674
     * </p>
675
     * 
676
     * <p>
677
     * The <code>MapContext</code> has the <i>model</i>, and most of the
678
     * <i>view</i>, and <i>control</i> logic of the layers of this component,
679
     * including a {@link ViewPort ViewPort} to adapt the information to the
680
     * projection, and to display it in the available area.
681
     * </p>
682
     * 
683
     * <p>
684
     * If <code>model</code> hadn't a <code>ViewPort</code>, assigns the current
685
     * one to it, otherwise, use its <code>ViewPort</code>.
686
     * </p>
687
     * 
688
     * <p>
689
     * After assigning the <code>MapContext</code> and <code>ViewPort</code>,
690
     * sets the same {@link MapContextListener MapContextListener} that was
691
     * using, and changes the <i>status</i> to <code>OUTDATED</code>.
692
     * </p>
693
     * 
694
     * @param model
695
     *            this component's <code>MapContext</code>, that includes the
696
     *            <code>ViewPort</code>.
697
     * 
698
     * @see MapContext
699
     * 
700
     * @see #getMapContext()
701
     */
702
    public void setMapContext(MapContext model) {
703
        if (mapContext != null) {
704
            mapContext.removeAtomicEventListener(mapContextListener);
705
            mapContext.dispose();
706
        }
707

    
708
        mapContext = model;
709

    
710
        if (mapContext.getViewPort() == null) {
711
            mapContext.setViewPort(vp);
712
        } else {
713
            vp = mapContext.getViewPort();
714
            cadgrid.setViewPort(vp);
715
        }
716

    
717
        mapContext.addAtomicEventListener(mapContextListener);
718

    
719
        status = DESACTUALIZADO;
720
    }
721

    
722
    /**
723
     * @return the mapControlDrawer
724
     */
725
    public MapControlDrawer getMapControlDrawer() {
726
        return mapControlDrawer;
727
    }
728

    
729
    /**
730
     * @param mapControlDrawer
731
     *            the mapControlDrawer to set
732
     */
733
    public void setMapControlDrawer(MapControlDrawer mapControlDrawer) {
734
        this.mapControlDrawer = mapControlDrawer;
735
        this.mapControlDrawer.setViewPort(vp);
736
    }
737

    
738
    /**
739
     * <p>
740
     * Gets this component's {@link MapContext MapContext} projection.
741
     * </p>
742
     * 
743
     * @return this component's {@link MapContext MapContext} projection
744
     * 
745
     * @see MapContext#getProjection()
746
     * @see MapControl#setProjection(IProjection)
747
     */
748
    public IProjection getProjection() {
749
        return getMapContext().getProjection();
750
    }
751

    
752
    /**
753
     * <p>
754
     * Sets the projection to this component's {@link MapContext MapContext}.
755
     * </p>
756
     * 
757
     * @param proj
758
     *            the kind of projection to this component's {@link MapContext
759
     *            MapContext}
760
     * 
761
     * @see MapContext#setProjection(IProjection)
762
     * @see MapControl#getProjection()
763
     */
764
    public void setProjection(IProjection proj) {
765
        getMapContext().setProjection(proj);
766
    }
767

    
768
    /**
769
     * <p>
770
     * Gets this component's <code>MapContext</code>, with the <i>model</i>, and
771
     * most of the <i>view</i>, and <i>control</i> logic of the layers of this
772
     * component, including a {@link ViewPort ViewPort} to adapt the information
773
     * to the projection, and display it in the available area.
774
     * </p>
775
     * 
776
     * @return this component's <code>MapContext</code>, that includes the
777
     *         <code>ViewPort</code> used to project the
778
     *         graphical information, and display it in the available area
779
     * 
780
     * @see MapContext
781
     * 
782
     * @see MapControl#setMapContext(MapContext)
783
     */
784
    public MapContext getMapContext() {
785
        return mapContext;
786
    }
787

    
788
    /**
789
     * <p>
790
     * Registers a new behavior to this component.
791
     * </p>
792
     * 
793
     * <p>
794
     * According the nature of the {@link Behavior Behavior}, different events
795
     * will be generated. Those events can be caught by a particular
796
     * {@link ToolListener ToolListener}, allowing user to interact with this
797
     * <code>MapControl</code> object as a <i>tool</i>.
798
     * </p>
799
     * 
800
     * @param name
801
     *            name to identify the behavior to add
802
     * @param tool
803
     *            the behavior to add
804
     * 
805
     * @see #addBehavior(String, Behavior[])
806
     * @see #getNamesMapTools()
807
     * @see #getMapToolsKeySet()
808
     * @see #hasTool(String)
809
     */
810
    public void addBehavior(String name, Behavior tool) {
811
        namesMapTools.put(name, tool);
812
        tool.setMapControl(this);
813
    }
814

    
815
    /**
816
     * <p>
817
     * Registers a new behavior to this component as a {@link CompoundBehavior
818
     * CompoundBehavior} made up of <code>tools</code>.
819
     * </p>
820
     * 
821
     * <p>
822
     * According the nature of the behaviors registered, different events will
823
     * be generated. Those events can be caught by a particular
824
     * {@link ToolListener ToolListener}, allowing user to interact with this
825
     * <code>MapControl</code> object as a <i>tool</i>.
826
     * </p>
827
     * 
828
     * @param name
829
     *            name to identify the compound behavior to add
830
     * @param tools
831
     *            the compound behavior to add
832
     * 
833
     * @see #addBehavior(String, Behavior)
834
     * @see #getNamesMapTools()
835
     * @see #getMapToolsKeySet()
836
     * @see #hasTool(String)
837
     */
838
    public void addBehavior(String name, Behavior[] tools) {
839
        CompoundBehavior tool = new CompoundBehavior(tools);
840
        addBehavior(name, tool);
841
    }
842

    
843
    /**
844
     * <p>
845
     * Gets the <code>Behavior</code> registered in this component, identified
846
     * by <code>name</code>.
847
     * </p>
848
     * 
849
     * @param name
850
     *            name of a registered behavior
851
     * 
852
     * @return tool the registered behavior in this component as
853
     *         <code>name</code>, or <code>null</code> if
854
     *         no one has that identifier
855
     * 
856
     * @see #addBehavior(String, Behavior)
857
     * @see #addBehavior(String, Behavior[])
858
     * @see #hasTool(String)
859
     */
860
    public Behavior getMapTool(String name) {
861
        return (Behavior) namesMapTools.get(name);
862
    }
863

    
864
    /**
865
     * <p>
866
     * Returns a set view of the keys that identified the tools registered.
867
     * </p>
868
     * 
869
     * @return a set view of the keys that identified the tools registered
870
     * 
871
     * @see HashMap#keySet()
872
     * 
873
     * @see #getNamesMapTools()
874
     * @see #addBehavior(String, Behavior)
875
     * @see #addBehavior(String, Behavior[])
876
     */
877
    public Set getMapToolsKeySet() {
878
        return namesMapTools.keySet();
879
    }
880

    
881
    /**
882
     * <p>
883
     * Returns <code>true</code> if this component contains a tool identified by
884
     * <code>toolName</code>.
885
     * </p>
886
     * 
887
     * @param toolName
888
     *            identifier of the tool
889
     * 
890
     * @return <code>true</code> if this component contains a tool identified by
891
     *         <code>toolName</code>; otherwise <code>false</code>
892
     * 
893
     * @see #addBehavior(String, Behavior)
894
     * @see #addBehavior(String, Behavior[])
895
     */
896
    public boolean hasTool(String toolName) {
897
        return namesMapTools.containsKey(toolName);
898
    }
899

    
900
    /**
901
     * <p>
902
     * Sets as current active <code>Behavior</code> associated to this
903
     * component, that one which is registered and identified by
904
     * <code>toolName</code>.
905
     * </p>
906
     * 
907
     * <p>
908
     * Changing the current active behavior for this <code>MapControl</code>,
909
     * implies also updating the previous <i>behavior</i> tool, and the current
910
     * cursor.
911
     * </p>
912
     * 
913
     * @param toolName
914
     *            name of a registered behavior
915
     * 
916
     * @see #getCurrentMapTool()
917
     * @see #getCurrentTool()
918
     */
919
    public void setTool(String toolName) {
920
        prevTool = getCurrentTool();
921
        Behavior mapTool = (Behavior) namesMapTools.get(toolName);
922
        currentMapTool = mapTool;
923
        currentTool = toolName;
924

    
925
        if (combinedTool != null) {
926
            if (mapTool instanceof CompoundBehavior) {
927
                ((CompoundBehavior) mapTool).addMapBehavior(combinedTool, true);
928
            } else {
929
                currentMapTool =
930
                    new CompoundBehavior(new Behavior[] { currentMapTool });
931
                ((CompoundBehavior) currentMapTool).addMapBehavior(
932
                    combinedTool, true);
933
            }
934
        }
935

    
936
        // this.setCursor(mapTool.getCursor());
937
    }
938

    
939
    /**
940
     * <p>
941
     * Gets as current active <code>Behavior</code> associated to this
942
     * component, that one which is registered and identified by
943
     * <code>toolName</code>.
944
     * </p>
945
     * 
946
     * <p>
947
     * Changing the current active behavior for this <code>MapControl</code>,
948
     * implies also updating the previous <i>behavior</i> tool, and the current
949
     * cursor.
950
     * </p>
951
     * 
952
     * @param toolName
953
     *            name of a registered behavior
954
     * 
955
     * @see #getCurrentTool()
956
     * @see #setTool(String)
957
     */
958
    public Behavior getCurrentMapTool() {
959
        return currentMapTool;
960
    }
961

    
962
    /**
963
     * <p>
964
     * Returns the name of the current selected tool on this MapControl
965
     * </p>
966
     * 
967
     * @return the name of the current's behavior tool associated to this
968
     *         component
969
     * 
970
     * @see #getCurrentMapTool()
971
     * @see #setTool(String)
972
     */
973
    public String getCurrentTool() {
974
        return currentTool;
975
    }
976

    
977
    /**
978
     * <p>
979
     * Determines that current drawing process of <code>MapControl</code>'s
980
     * <code>MapContext</code>'s data must be canceled.
981
     * </p>
982
     * 
983
     * <p>
984
     * It has no effects if now isn't drawing that graphical information.
985
     * </p>
986
     * 
987
     * <p>
988
     * At last resort, the particular implementation of each layer in this
989
     * <code>MapControl</code>'s <code>MapContrext</code> will be that one which
990
     * will draw the graphical information, and, if supports, which could cancel
991
     * its drawing subprocess.
992
     * </p>
993
     */
994
    public void cancelDrawing() {
995
        /*
996
         * if (drawer != null) {
997
         * if (!drawer.isAlive()) {
998
         * return;
999
         * }
1000
         * }
1001
         */
1002
        canceldraw.setCanceled(true);
1003

    
1004
        /*
1005
         * while (!isCancelled) {
1006
         * if (!drawer.isAlive()) {
1007
         * // Si hemos llegado aqu? con un thread vivo, seguramente
1008
         * // no estamos actualizados.
1009
         * 
1010
         * break;
1011
         * }
1012
         * 
1013
         * }
1014
         * canceldraw.setCancel(false);
1015
         * isCancelled = false;
1016
         * drawerAlive = false;
1017
         */
1018
    }
1019

    
1020
    /**
1021
     * <p>
1022
     * Creates a {@link BufferedImage BufferedImage} image if there was no
1023
     * buffered image, or if its viewport's image height or width is different
1024
     * from this component's size. Once has created a double-buffer, fills it
1025
     * with the vieport's background color, or with <i>white</i> if it had no
1026
     * background color.
1027
     * </p>
1028
     * 
1029
     * <p>
1030
     * If no double-buffered existed, creates a {@link BufferedImage
1031
     * BufferedImage} with the size of this component, and as an image with
1032
     * 8-bit RGBA color components packed into integer pixels. That image has a
1033
     * <code>DirectColorModel</code> with alpha. The color data in that image is
1034
     * considered not to be premultiplied with alpha.
1035
     * </p>
1036
     * 
1037
     * <p>
1038
     * Once has created and filled the new inner <code>MapControl</code>'s
1039
     * double-buffer, changes the status to <code>OUTDATED</code>.
1040
     * </p>
1041
     * 
1042
     * @return <code>true</code> if has created and filled a new double-buffer
1043
     *         for this <code>MapControl</code> instance; otherwise
1044
     *         <code>false</code>
1045
     */
1046
    private boolean adaptToImageSize() {
1047
        if ((image == null) || (vp.getImageWidth() != this.getWidth())
1048
            || (vp.getImageHeight() != this.getHeight())) {
1049
            image =
1050
                new BufferedImage(this.getWidth(), this.getHeight(),
1051
                    BufferedImage.TYPE_INT_ARGB);
1052
            // ESTILO MAC
1053
            // image = GraphicsEnvironment.getLocalGraphicsEnvironment()
1054
            // .getDefaultScreenDevice().getDefaultConfiguration()
1055
            // .createCompatibleImage(this.getWidth(), this.getHeight());
1056
            vp.setImageSize(new Dimension(getWidth(), getHeight()));
1057
            getMapContext().getViewPort().refreshExtent();
1058

    
1059
            Graphics gTemp = image.createGraphics();
1060
            Color theBackColor = vp.getBackColor();
1061
            if (theBackColor == null) {
1062
                gTemp.setColor(Color.WHITE);
1063
            } else {
1064
                gTemp.setColor(theBackColor);
1065
            }
1066

    
1067
            gTemp.fillRect(0, 0, getWidth(), getHeight());
1068
            gTemp.dispose();
1069
            status = DESACTUALIZADO;
1070
            // g.drawImage(image,0,0,null);
1071
            return true;
1072
        }
1073
        return false;
1074
    }
1075

    
1076
    /**
1077
     * <p>
1078
     * Paints the graphical information of this component using a double buffer.
1079
     * </p>
1080
     * 
1081
     * <p>
1082
     * If the double buffer wasn't created, creates a new one.
1083
     * </p>
1084
     * 
1085
     * <p>
1086
     * Paints the component according the following algorithm: <br>
1087
     * &nbsp If <i>status</i> is <i>UPDATED</i>:<br>
1088
     * &nbsp &nbsp If there is no <i>double buffer</i>:<br>
1089
     * &nbsp &nbsp &nbsp If there is a <i>behavior</i> for managing the
1090
     * <code>MapControl</code> instance, delegates the drawing process to that
1091
     * behavior, calling:
1092
     * <code><i>behavior_instance</i>.paintComponent(g)</code> &nbsp .<br>
1093
     * &nbsp &nbsp &nbsp Else, repaints the current graphical information
1094
     * quickly calling: <code>g.drawImage(image,0,0,null)</code> &nbsp .<br>
1095
     * &nbsp Else, (<i>status</i> is <i>OUTDATED</i>, or <i>ONLY_GRAPHICS</i>):
1096
     * executes a quickly repaint of the previous information calling
1097
     * <code>g.drawImage(image,0,0,null)</code>, and creates a <i>painting
1098
     * request</i> to delegate the heavy drawing process to the {@link Drawer2
1099
     * Drawer2}'s worker thread, according the <i>SingleWorketThread</i>
1100
     * pattern, starting a timer to update (invoking <code>repaint()</code> that
1101
     * comprises invoke this method) the view every delay of 360 ms. during the
1102
     * the process drawing.
1103
     * </p>
1104
     * 
1105
     * @see javax.swing.JComponent#paintComponent(java.awt.Graphics)
1106
     * @see Drawer2
1107
     */
1108
    protected void paintComponent(Graphics g) {
1109
        adaptToImageSize();
1110

    
1111
        try {
1112
            mapControlDrawer.startDrawing(this);
1113
        } catch (InterruptedException e) {
1114
            LOG.error("Error locking the MapControlDrawer", e);
1115
        }
1116
        mapControlDrawer.setGraphics(g);
1117
        mapControlDrawer.stopDrawing(this);
1118
        mapControlDrawer.setViewPort(getMapContext().getViewPort());
1119

    
1120
        if (status == ACTUALIZADO) {
1121
            /*
1122
             * Si hay un behaviour y la imagen es distinta de null se delega el
1123
             * dibujado
1124
             * en dicho behaviour
1125
             */
1126
            if (image != null) {
1127
                if (currentMapTool != null) {
1128
                    currentMapTool.paintComponent(mapControlDrawer);
1129
                } else {
1130
                    mapControlDrawer.drawImage(image, 0, 0);
1131
                }
1132
            }
1133
                } else if ((status == DESACTUALIZADO)) {
1134

    
1135
                        mapControlDrawer.drawImage(image, 0, 0);
1136

    
1137
                        drawer.put(new PaintingRequest());
1138
                        timer.start();
1139
                }
1140
        cadgrid.drawGrid(mapControlDrawer);
1141
        drawCursor();
1142
    }
1143

    
1144
    /**
1145
     * <p>
1146
     * Gets the {@link BufferedImage BufferedImage} used to accelerate the draw
1147
     * of new ''frames'' with changes, or new graphical items in this component.
1148
     * </p>
1149
     * 
1150
     * @return double buffered image used by this component to accelerate the
1151
     *         draw of its graphical information, or <code>null</code> if isn't
1152
     *         already created
1153
     * 
1154
     * @see BufferedImage
1155
     */
1156
    public BufferedImage getImage() {
1157
        return image;
1158
    }
1159

    
1160
    /**
1161
     * <p>
1162
     * Forces repaint all visible graphical information in this component.
1163
     * </p>
1164
     * 
1165
     * <p>
1166
     * If <code>doClear == true</code>, before repainting, clears the background
1167
     * color, with the inner viewport's background color.
1168
     * </p>
1169
     * 
1170
     * @param doClear
1171
     *            <code>true</code> if needs clearing the background color
1172
     *            before drawing the map
1173
     * 
1174
     * @see #cancelDrawing()
1175
     * @see FLayers#setDirty(boolean)
1176
     */
1177
    public void drawMap(boolean doClear) {
1178
        cancelDrawing();
1179
        // System.out.println("drawMap con doClear=" + doClear);
1180
        status = DESACTUALIZADO;
1181
        if (doClear) {
1182
            // image = null; // Se usa para el PAN
1183
            if (image != null) {
1184
                Graphics2D g = image.createGraphics();
1185
                Color theBackColor = vp.getBackColor();
1186
                if (theBackColor == null) {
1187
                    g.setColor(Color.WHITE);
1188
                } else {
1189
                    g.setColor(theBackColor);
1190
                }
1191
                g.fillRect(0, 0, vp.getImageWidth(), vp.getImageHeight());
1192
                g.dispose();
1193
            }
1194
        }
1195
        repaint();
1196
    }
1197

    
1198
    /**
1199
     * <p>
1200
     * Cancels any current drawing process, changing the status to
1201
     * <code>OUTDATED</code>, and forcing repaint only the layers dirty.
1202
     * </p>
1203
     * 
1204
     * @see #cancelDrawing()
1205
     */
1206
    public void rePaintDirtyLayers() {
1207
        cancelDrawing();
1208
        status = DESACTUALIZADO;
1209
        repaint();
1210
    }
1211

    
1212
    /**
1213
     * @deprecated use {@link #drawMap(boolean)} instead, or even
1214
     * better {@link #getMapContext()}.invalidate().
1215
     */
1216
    public void drawGraphics() {
1217
        drawMap(false);
1218
    }
1219

    
1220
    /**
1221
     * @see java.awt.event.ComponentListener#componentHidden(java.awt.event.ComponentEvent)
1222
     */
1223
    public void componentHidden(ComponentEvent e) {
1224
    }
1225

    
1226
    /**
1227
     * @see java.awt.event.ComponentListener#componentMoved(java.awt.event.ComponentEvent)
1228
     */
1229
    public void componentMoved(ComponentEvent e) {
1230
    }
1231

    
1232
    /**
1233
     * @see java.awt.event.ComponentListener#componentResized(java.awt.event.ComponentEvent)
1234
     */
1235
    public void componentResized(ComponentEvent e) {
1236
        /*
1237
         * image = new BufferedImage(this.getWidth(), this.getHeight(),
1238
         * BufferedImage.TYPE_INT_ARGB);
1239
         * Graphics gTemp = image.createGraphics();
1240
         * gTemp.setColor(vp.getBackColor());
1241
         * gTemp.fillRect(0,0,getWidth(), getHeight());
1242
         * System.out.println("MapControl resized");
1243
         * // image = null;
1244
         * vp.setImageSize(new Dimension(getWidth(), getHeight()));
1245
         * getMapContext().getViewPort().setScale();
1246
         */
1247
        // drawMap(true);
1248
    }
1249

    
1250
    /**
1251
     * @see java.awt.event.ComponentListener#componentShown(java.awt.event.ComponentEvent)
1252
     */
1253
    public void componentShown(ComponentEvent e) {
1254
    }
1255

    
1256
    /**
1257
     * @see ExceptionHandlingSupport#addExceptionListener(ExceptionListener)
1258
     */
1259
    public void addExceptionListener(ExceptionListener o) {
1260
        exceptionHandlingSupport.addExceptionListener(o);
1261
    }
1262

    
1263
    /**
1264
     * @see ExceptionHandlingSupport#removeExceptionListener(ExceptionListener)
1265
     */
1266
    public boolean removeExceptionListener(ExceptionListener o) {
1267
        return exceptionHandlingSupport.removeExceptionListener(o);
1268
    }
1269

    
1270
    /**
1271
     * @see ExceptionHandlingSupport#throwException(Throwable)
1272
     */
1273
    protected void throwException(Throwable t) {
1274
        exceptionHandlingSupport.throwException(t);
1275
    }
1276

    
1277
    /**
1278
     * <p>
1279
     * Represents each <code>MapControl</code>'s data painting request.
1280
     * </p>
1281
     * 
1282
     * <p>
1283
     * The request will be attended by a <code>Drawer2</code>, which will hold
1284
     * it since the <code>Drawer2</code>'s worker takes it, or arrives a new
1285
     * painting request, which will replace it.
1286
     * </p>
1287
     */
1288
    private class PaintingRequest {
1289

    
1290
        /**
1291
         * <p>
1292
         * Creates a new <code>PaintingRequest
1293
         * </p>
1294
         * instance.</p>
1295
         */
1296
        public PaintingRequest() {
1297
        }
1298

    
1299
        /**
1300
         * <p>
1301
         * <code>MapControl</code> paint process:
1302
         * </p>
1303
         * 
1304
         * <p>
1305
         * <ul>
1306
         * <li><i>1.- </i>Cancels all previous <code>MapControl</code>'s drawing
1307
         * processes.</li>
1308
         * <li><i>2.- </i>If <i>status</i> was OUTDATED:
1309
         * <ul>
1310
         * <li><i>2.1.- </i>Fills the background color with viewport's
1311
         * background color, or <i>white</i> if it was undefined.</li>
1312
         * <li><i>2.2.- </i>Notifies <i>MapContext</i> to be drawn invoking: <code>mapContext.draw(double-buffer, double-buffer's buffer, shared cancel-draw object, mapContext.getScaleView());</code>
1313
         * .</li>
1314
         * <li><i>2.3.- </i>If <code>canceldraw.isCanceled()</code>
1315
         * <ul>
1316
         * <li><i>2.3.1.- </i>Sets <i>status</i> to OUTDATED.</li>
1317
         * <li><i>2.3.2.- </i>Sets <i>dirty</i> all layers stored in
1318
         * <i>MapContext</i>.</li>
1319
         * </ul>
1320
         * </li>
1321
         * <li><i>2.4.- </i>Else, sets <i>status</i> to UPDATED.</li>
1322
         * </ul>
1323
         * </li>
1324
         * <li><i>3.- </i>Stops the <i>timer</i>.</li>
1325
         * <li><i>4.- </i>Repaints this component invoking:
1326
         * <code>repaint();</code></li>
1327
         * </ul>
1328
         * </p>
1329
         * 
1330
         * @see #cancelDrawing()
1331
         * @see MapContext#draw(BufferedImage, Graphics2D, Cancellable, double)
1332
         * @see MapContext#drawGraphics(BufferedImage, Graphics2D, Cancellable,
1333
         *      double)
1334
         * 
1335
         * @see ViewPort
1336
         */
1337
        public void paint() {
1338
            try {
1339
                canceldraw.setCanceled(false);
1340
                Graphics2D g = image.createGraphics();
1341

    
1342
                ViewPort viewPort = mapContext.getViewPort();
1343

    
1344
                if (status == DESACTUALIZADO) {
1345
                    Graphics2D gTemp = image.createGraphics();
1346
                    Color theBackColor = viewPort.getBackColor();
1347
                    if (theBackColor == null) {
1348
                        gTemp.setColor(Color.WHITE);
1349
                    } else {
1350
                        gTemp.setColor(theBackColor);
1351
                    }
1352
                    gTemp.fillRect(0, 0, viewPort.getImageWidth(), viewPort
1353
                        .getImageHeight());
1354
                    mapContext.draw(image, g, canceldraw, mapContext
1355
                        .getScaleView());
1356
                    if (!canceldraw.isCanceled()) {
1357
                        status = ACTUALIZADO;
1358
                    }
1359
                                }
1360

    
1361
                timer.stop();
1362
                repaint();
1363

    
1364
            } catch (Throwable e) {
1365
                timer.stop();
1366
                e.printStackTrace();
1367
                throwException(e);
1368
            } 
1369
        }
1370
    }
1371

    
1372
    /**
1373
     * <p>
1374
     * An instance of <code>Drawer2</code> could manage all
1375
     * <code>MapControl</code> painting requests.
1376
     * </p>
1377
     * 
1378
     * <p>
1379
     * Based on the <i>WorkerThread</i> software pattern, creates a worker
1380
     * thread that will attend sequentially the current waiting painting
1381
     * request, after finishing the previous (that could be by a cancel action).
1382
     * </p>
1383
     * 
1384
     * <p>
1385
     * All new {@link PaintingRequest PaintingRequest} generated will be stored
1386
     * as <i>waiting requests</i> since the worker attends it.
1387
     * </p>
1388
     * 
1389
     * <p>
1390
     * If a worker finished and there was no <i>painting request</i>, the worker
1391
     * would be set to wait until any <i>painting request</i> would be put.
1392
     * </p>
1393
     * 
1394
     * @author fjp
1395
     */
1396
    public class Drawer {
1397

    
1398
        // Una mini cola de 2. No acumulamos peticiones de dibujado
1399
        // dibujamos solo lo ?ltimo que nos han pedido.
1400

    
1401
        /**
1402
         * <p>
1403
         * Painting request that's been attended by the <code>Drawer2</code>'s
1404
         * worker.
1405
         * </p>
1406
         * 
1407
         * @see #put(org.gvsig.fmap.mapcontrol.MapControl.PaintingRequest)
1408
         * @see #take()
1409
         */
1410
        private PaintingRequest paintingRequest;
1411

    
1412
        /**
1413
         * <p>
1414
         * Painting request waiting to be attended by the <code>Drawer2</code>'s
1415
         * worker.
1416
         * </p>
1417
         * 
1418
         * @see #put(org.gvsig.fmap.mapcontrol.MapControl.PaintingRequest)
1419
         * @see #take()
1420
         */
1421
        private PaintingRequest waitingRequest;
1422

    
1423
        /**
1424
         * <p>
1425
         * Determines that the <code>Drawer2</code>'s worker is busy attending a
1426
         * painting request.
1427
         * </p>
1428
         * 
1429
         * @see #put(org.gvsig.fmap.mapcontrol.MapControl.PaintingRequest)
1430
         * @see #take()
1431
         */
1432
        private boolean waiting;
1433

    
1434
        /**
1435
         * <p>
1436
         * Notifies the <code>Drawer2</code>'s worker to finish or continue with
1437
         * its process.
1438
         * </p>
1439
         * 
1440
         * @see #setShutdown(boolean)
1441
         */
1442
        private boolean shutdown;
1443

    
1444
                private Thread worker;
1445

    
1446
        /**
1447
         * <p>
1448
         * Sets this <code>Drawer2</code>'s worker to finish or continue with
1449
         * its process.
1450
         * </p>
1451
         * 
1452
         * @param isShutdown
1453
         *            a boolean value
1454
         */
1455
        public void setShutdown(boolean isShutdown) {
1456
            shutdown = isShutdown;
1457
            if (shutdown) {
1458
                    worker.interrupt();
1459
            }
1460
        }
1461

    
1462
        /**
1463
         * <p>
1464
         * Creates a new drawer for managing all data painting requests in
1465
         * <code>MapControl</code>.
1466
         * </p>
1467
         * 
1468
         * <p>
1469
         * Includes the following steps:
1470
         * <ul>
1471
         * <li>By default, there is no <i>current painting request</i>.</li>
1472
         * <li>By default, there is no <i>waiting painting request</i>.</li>
1473
         * <li>By default, the worker thread is waiting no <i>painting
1474
         * request</i>.</li>
1475
         * <li>By default, the worker thread is running.</li>
1476
         * <li>Creates and starts a worker thread for attending the <i>painting
1477
         * requests</i>.</li>
1478
         * </ul>
1479
         * </p>
1480
         */
1481
        public Drawer() {
1482
            paintingRequest = null;
1483
            waitingRequest = null;
1484
            waiting = false;
1485
            shutdown = false;
1486
            worker = new Thread(new Worker(), "MapControl Drawer Worker");
1487
            worker.start();
1488
        }
1489

    
1490
        /**
1491
         * <p>
1492
         * Sets a <code>PaintingRequest</code> to be attended by the worker
1493
         * thread of this object. If this one was waiting, wakes up.
1494
         * </p>
1495
         * 
1496
         * <p>
1497
         * All waiting threads will be notified synchronized.
1498
         * </p>
1499
         * 
1500
         * @param newPaintRequest
1501
         * 
1502
         * @see #take()
1503
         */
1504
        public void put(PaintingRequest newPaintRequest) {
1505
            waitingRequest = newPaintRequest;
1506
            if (waiting) {
1507
                synchronized (this) {
1508
                    notifyAll();
1509
                }
1510
            }
1511
        }
1512

    
1513
        /**
1514
         * <p>
1515
         * Used by this object's worker, returns the current waiting drawing
1516
         * request, causing current thread to wait until another thread invokes
1517
         * {@link #put(org.gvsig.fmap.mapcontrol.MapControl.PaintingRequest)
1518
         * #put(com.iver.cit.gvsig.fmap.MapControl.PaintingRequest)}, if there
1519
         * was no waiting request.
1520
         * </p>
1521
         * 
1522
         * <p>
1523
         * All threads will access synchronized to the waiting request.
1524
         * </p>
1525
         * 
1526
         * @return <code>PaintingRequest</code> that was waiting to be attended
1527
         * 
1528
         * @see #put(org.gvsig.fmap.mapcontrol.MapControl.PaintingRequest)
1529
         */
1530
        public PaintingRequest take() {
1531
            if (waitingRequest == null) {
1532
                synchronized (this) {
1533
                    waiting = true;
1534
                    try {
1535
                        wait();
1536
                    } catch (InterruptedException ie) {
1537
                        waiting = false;
1538
                    }
1539
                }
1540
            }
1541
            paintingRequest = waitingRequest;
1542
            waitingRequest = null;
1543
            return paintingRequest;
1544
        }
1545

    
1546
        /**
1547
         * <p>
1548
         * Thread for attending painting requests.
1549
         * </p>
1550
         * 
1551
         * <p>
1552
         * If there was no double buffer, sets the status to
1553
         * <code>OUTDATED</code> and finishes, otherwise takes the painting
1554
         * request (it's probably that would wait some time), cancel the
1555
         * previous drawing process, and starts processing the request.
1556
         * </p>
1557
         * 
1558
         * @see Thread
1559
         */
1560
        private class Worker implements Runnable {
1561

    
1562
            /*
1563
             * (non-Javadoc)
1564
             * 
1565
             * @see java.lang.Runnable#run()
1566
             */
1567
            public void run() {
1568
                while (!shutdown) {
1569
                    PaintingRequest p = take();
1570
                    // System.out.println("Pintando");
1571
                    if (image != null) {
1572
                        cancelDrawing();
1573
                        if (p != null) {
1574
                                p.paint();
1575
                        }
1576
                    } else {
1577
                        status = DESACTUALIZADO;
1578
                    }
1579
                }
1580
            }
1581
        }
1582
    }
1583

    
1584
    /**
1585
     * <p>
1586
     * An instance of <code>CancelDraw</code> will be shared by all this
1587
     * <code>MapControl</code>'s <code>MapContext</code> layers, allowing
1588
     * receive a notification that, when they're been drawn, to be cancelled.
1589
     * </p>
1590
     * 
1591
     * @see Cancellable
1592
     * 
1593
     * @author Fernando Gonz?lez Cort?s
1594
     */
1595
    public class CancelDraw implements Cancellable {
1596

    
1597
        /**
1598
         * <p>
1599
         * Determines if the drawing task must be canceled or not.
1600
         * </p>
1601
         * 
1602
         * @see #isCanceled()
1603
         * @see #setCanceled(boolean)
1604
         */
1605
        private boolean cancel = false;
1606

    
1607
        /**
1608
         * Creates a new <code>CancelDraw</code> object.
1609
         */
1610
        public CancelDraw() {
1611
        }
1612

    
1613
        /*
1614
         * (non-Javadoc)
1615
         * 
1616
         * @see com.iver.utiles.swing.threads.Cancellable#setCanceled(boolean)
1617
         */
1618
        public void setCanceled(boolean b) {
1619
            cancel = b;
1620
        }
1621

    
1622
        /*
1623
         * (non-Javadoc)
1624
         * 
1625
         * @see com.iver.utiles.swing.threads.Cancellable#isCanceled()
1626
         */
1627
        public boolean isCanceled() {
1628
            return cancel;
1629
        }
1630
    }
1631

    
1632
    /**
1633
     * <p>
1634
     * Listens all kind of mouse events produced in {@link MapControl
1635
     * MapControl}, and invokes its current map tool <i>(
1636
     * {@link MapControl#getCurrentMapTool() MapControl#getCurrentMapTool()}</i>
1637
     * to simulate a behavior.
1638
     * </p>
1639
     * 
1640
     * <p>
1641
     * Mouse wheel moved events produce a <i>zoom in</i> operation if wheel
1642
     * rotation is negative, or a <i>zoom out</i> if its positive. Both will be
1643
     * centered in the position of the mouse, but, meanwhile <i>zoom in</i>
1644
     * operation applies a factor of 0.9, <i>zoom out</i> operation applies a
1645
     * factor of 1.2
1646
     * </p>
1647
     * 
1648
     * <p>
1649
     * Mouse wheel moved events can be produced as much frequently, that between
1650
     * each one, the drawing process could hadn't finished. This is the reason
1651
     * that, in this situation, cancels always the previous drawing process
1652
     * before applying a <i>zoom</i> operation, and ignores all new mouse
1653
     * positions that are produced before 1 second.
1654
     * </p>
1655
     * 
1656
     * @author Fernando Gonz?lez Cort?s
1657
     */
1658
    public class MapToolListener implements MouseListener, MouseWheelListener,
1659
        MouseMotionListener {
1660

    
1661
        /**
1662
         * <p>
1663
         * Used to avoid mouse wheel move events closed.
1664
         * </p>
1665
         * 
1666
         * <p>
1667
         * If a mouse wheel move event is produced
1668
         */
1669
        long t1;
1670

    
1671
        /**
1672
         * <p>
1673
         * Position of the mouse, in map coordinates.
1674
         * </p>
1675
         * 
1676
         * <p>
1677
         * This point coordinates will be used as center of the <i>zoom</i>
1678
         * operation.
1679
         * </p>
1680
         */
1681
        Point2D pReal;
1682

    
1683
        /**
1684
         * @see java.awt.event.MouseListener#mouseClicked(java.awt.event.MouseEvent)
1685
         * @see Behavior#mouseClicked(MouseEvent)
1686
         */
1687
        public void mouseClicked(MouseEvent e) {
1688
            try {
1689
                if (currentMapTool != null) {
1690
                    currentMapTool.mouseClicked(e);
1691
                }
1692
                Point2D p;
1693

    
1694
                if (mapAdjustedPoint != null) {
1695
                    p = mapAdjustedPoint;
1696
                } else {
1697
                    p = vp.toMapPoint(adjustedPoint);
1698
                }            
1699
                previousPoint = new double[] { p.getX(), p.getY() };                
1700
            } catch (BehaviorException t) {
1701
                throwException(t);
1702
            }
1703
        }
1704

    
1705
        /**
1706
         * @see java.awt.event.MouseListener#mouseEntered(java.awt.event.MouseEvent)
1707
         * @see Behavior#mouseEntered(MouseEvent)
1708
         */
1709
        public void mouseEntered(MouseEvent e) {
1710
            setToolMouse();
1711
            try {
1712
                if (currentMapTool != null) {
1713
                    currentMapTool.mouseEntered(e);
1714
                }
1715
            } catch (BehaviorException t) {
1716
                throwException(t);
1717
            }
1718
        }
1719

    
1720
        /**
1721
         * @see java.awt.event.MouseListener#mouseExited(java.awt.event.MouseEvent)
1722
         * @see Behavior#mouseExited(MouseEvent)
1723
         */
1724
        public void mouseExited(MouseEvent e) {
1725
            try {
1726
                if (currentMapTool != null) {
1727
                    currentMapTool.mouseExited(e);
1728
                }
1729
            } catch (BehaviorException t) {
1730
                throwException(t);
1731
            }
1732
            // Remove the snapping image if exist
1733
            usedSnap = null;
1734
            repaint();
1735
        }
1736

    
1737
        /**
1738
         * @see java.awt.event.MouseListener#mousePressed(java.awt.event.MouseEvent)
1739
         * @see Behavior#mousePressed(MouseEvent)
1740
         */
1741
        public void mousePressed(MouseEvent e) {
1742
            try {
1743
                if (currentMapTool != null) {
1744
                    currentMapTool.mousePressed(e);
1745
                }
1746
            } catch (BehaviorException t) {
1747
                throwException(t);
1748
            }
1749
        }
1750

    
1751
        /**
1752
         * @see java.awt.event.MouseListener#mouseReleased(java.awt.event.MouseEvent)
1753
         * @see Behavior#mouseReleased(MouseEvent)
1754
         */
1755
        public void mouseReleased(MouseEvent e) {
1756
            try {
1757
                if (currentMapTool != null) {
1758
                    currentMapTool.mouseReleased(e);
1759
                }
1760
            } catch (BehaviorException t) {
1761
                throwException(t);
1762
            }
1763
        }
1764

    
1765
        /**
1766
         * @see java.awt.event.MouseWheelListener#mouseWheelMoved(java.awt.event.MouseWheelEvent)
1767
         * @see Behavior#mouseWheelMoved(MouseWheelEvent)
1768
         */
1769
        public void mouseWheelMoved(MouseWheelEvent e) {
1770
            try {
1771
                if (currentMapTool == null) {
1772
                    return;
1773
                }
1774

    
1775
                currentMapTool.mouseWheelMoved(e);
1776

    
1777
                // Si el tool actual no ha consumido el evento
1778
                // entendemos que quiere el comportamiento por defecto.
1779
                if (!e.isConsumed()) {
1780
                    // Para usar el primer punto sobre el que queremos centrar
1781
                    // el mapa, dejamos pasar un segundo para considerar el
1782
                    // siguiente
1783
                    // punto como v?lido.
1784
                    if (t1 == 0) {
1785
                        t1 = System.currentTimeMillis();
1786
                        pReal = vp.toMapPoint(e.getPoint());
1787
                    } else {
1788
                        long t2 = System.currentTimeMillis();
1789
                        if ((t2 - t1) > 1000) {
1790
                            t1 = 0;
1791
                        }
1792
                    }
1793
                    cancelDrawing();
1794
                    ViewPort vp = getViewPort();
1795

    
1796
                    /*
1797
                     * Point2D pReal = new
1798
                     * Point2D.Double(vp.getAdjustedExtent().getCenterX(),
1799
                     * vp.getAdjustedExtent().getCenterY());
1800
                     */
1801
                    int amount = e.getWheelRotation();
1802
                    double nuevoX;
1803
                    double nuevoY;
1804
                    double factor;
1805

    
1806
                    if (amount < 0) // nos acercamos
1807
                    {
1808
                        factor = 0.9;
1809
                    } else // nos alejamos
1810
                    {
1811
                        factor = 1.2;
1812
                    }
1813
                    if (vp.getExtent() != null) {
1814
                        nuevoX =
1815
                            pReal.getX()
1816
                                - ((vp.getExtent().getWidth() * factor) / 2.0);
1817
                        nuevoY =
1818
                            pReal.getY()
1819
                                - ((vp.getExtent().getHeight() * factor) / 2.0);
1820
                        double x = nuevoX;
1821
                        double y = nuevoY;
1822
                        double width = vp.getExtent().getWidth() * factor;
1823
                        double height = vp.getExtent().getHeight() * factor;
1824

    
1825
                        try {
1826
                            vp.setEnvelope(geomManager.createEnvelope(x, y, x
1827
                                + width, y + height, SUBTYPES.GEOM2D));
1828
                        } catch (CreateEnvelopeException e1) {
1829
                            LOG.error("Error creating the envelope", e);
1830
                        }
1831
                    }
1832

    
1833
                }
1834
            } catch (BehaviorException t) {
1835
                throwException(t);
1836
            }
1837
        }
1838

    
1839
        /**
1840
         * @see java.awt.event.MouseMotionListener#mouseDragged(java.awt.event.MouseEvent)
1841
         * @see Behavior#mouseDragged(MouseEvent)
1842
         */
1843
        public void mouseDragged(MouseEvent e) {
1844
            calculateSnapPoint(e.getPoint());
1845
            try {
1846
                if (currentMapTool != null) {
1847
                    currentMapTool.mouseDragged(e);
1848
                }
1849
            } catch (BehaviorException t) {
1850
                throwException(t);
1851
            }
1852
            repaint();
1853
        }
1854

    
1855
        /**
1856
         * @see java.awt.event.MouseMotionListener#mouseMoved(java.awt.event.MouseEvent)
1857
         * @see Behavior#mouseMoved(MouseEvent)
1858
         */
1859
        public void mouseMoved(MouseEvent e) {
1860
            calculateSnapPoint(e.getPoint());
1861
            try {
1862
                if (currentMapTool != null) {
1863
                    currentMapTool.mouseMoved(e);
1864
                }
1865
            } catch (BehaviorException t) {
1866
                throwException(t);
1867
            }
1868
            repaint();
1869
        }
1870
    }
1871

    
1872
    /**
1873
     * <p<code>MapContextListener</code> listens all events produced in a
1874
     * <code>MapControl</code>'s <code>MapContext</code> object during an atomic
1875
     * period of time, and sets it to dirty, <i>executing
1876
     * <code>drawMap(false)</code>, if any of the
1877
     * following conditions is accomplished</i>:
1878
     * <ul>
1879
     * <li>Any of the <code>LayerEvent</code> in the <code>AtomicEvent</code>
1880
     * parameter notifies a <i>visibility change</i>.</li>
1881
     * <li>There is at least one <code>ColorEvent</code> in the
1882
     * <code>AtomicEvent</code> parameter.</li>
1883
     * <li>There is at least one <code>ExtentEvent</code> in the
1884
     * <code>AtomicEvent</code> parameter.</li>
1885
     * <li>Any of the <code>LayerCollectionEvent</code> in the
1886
     * <code>AtomicEvent</code> parameter notifies that a driver's layer has
1887
     * reloaded it successfully.</li>
1888
     * <li>There is at least one <code>LegendEvent</code> in the
1889
     * <code>AtomicEvent</code> parameter.</li>
1890
     * <li>There is at least one <code>SelectionEvent</code> in the
1891
     * <code>AtomicEvent</code> parameter.</li>
1892
     * </ul>
1893
     * </p>
1894
     * 
1895
     * @author Fernando Gonz?lez Cort?s
1896
     */
1897
    public class MapContextListener implements AtomicEventListener {
1898

    
1899
        /**
1900
         * @see org.gvsig.fmap.mapcontext.events.listeners.AtomicEventListener#atomicEvent(org.gvsig.fmap.mapcontext.events.AtomicEvent)
1901
         */
1902
        public void atomicEvent(final AtomicEvent e) {
1903
            if (!SwingUtilities.isEventDispatchThread()) {
1904
                SwingUtilities.invokeLater(new Runnable() {
1905

    
1906
                    public void run() {
1907
                        atomicEvent(e);
1908
                    }
1909
                });
1910
                return;
1911
            }
1912
            LayerEvent[] layerEvents = e.getLayerEvents();
1913

    
1914
            for (int i = 0; i < layerEvents.length; i++) {
1915
                if (layerEvents[i].getProperty().equals("visible")) {
1916
                    drawMap(false);
1917
                    return;
1918
                } else
1919
                    if (layerEvents[i].getEventType() == LayerEvent.EDITION_CHANGED) {
1920
                        drawMap(false);
1921
                        return;
1922
                    }
1923
            }
1924

    
1925
            if (e.getColorEvents().length > 0) {
1926
                drawMap(false);
1927
                return;
1928
            }
1929

    
1930
            if (e.getExtentEvents().length > 0) {
1931
                drawMap(false);
1932
                return;
1933
            }
1934

    
1935
            if (e.getProjectionEvents().length > 0) {
1936
                // redraw = true;
1937
            }
1938

    
1939
            LayerCollectionEvent[] aux = e.getLayerCollectionEvents();
1940
            if (aux.length > 0) {
1941
                for (int i = 0; i < aux.length; i++) {
1942
                    if (aux[i].getAffectedLayer().getFLayerStatus()
1943
                        .isDriverLoaded()) {
1944
                        drawMap(false);
1945
                        return;
1946
                    }
1947
                }
1948

    
1949
            }
1950

    
1951
            if (e.getLegendEvents().length > 0) {
1952
                drawMap(false);
1953
                return;
1954
            }
1955

    
1956
            if (e.getSelectionEvents().length > 0) {
1957
                drawMap(false);
1958
                return;
1959
            }
1960
        }
1961
    }
1962

    
1963
    /**
1964
     * <p>
1965
     * Gets the <code>ViewPort</code> of this component's {@link MapContext
1966
     * MapContext} .
1967
     * </p>
1968
     * 
1969
     * @see MapContext#getViewPort()
1970
     */
1971
    public ViewPort getViewPort() {
1972
        return vp;
1973
    }
1974

    
1975
    /**
1976
     * <p>
1977
     * Returns all registered <code>Behavior</code> that can define a way to
1978
     * work with this <code>MapControl</code>.
1979
     * </p>
1980
     * 
1981
     * @return registered <code>Behavior</code> to this <code>MapControl</code>
1982
     * 
1983
     * @see #addBehavior(String, Behavior)
1984
     * @see #addBehavior(String, Behavior[])
1985
     * @see #getMapToolsKeySet()
1986
     * @see #hasTool(String)
1987
     */
1988
    public HashMap getNamesMapTools() {
1989
        return namesMapTools;
1990
    }
1991

    
1992
    /*
1993
     * (non-Javadoc)
1994
     * 
1995
     * @see
1996
     * com.iver.cit.gvsig.fmap.edition.commands.CommandListener#commandRepaint()
1997
     */
1998
    public void commandRepaint() {
1999
        drawMap(false);
2000
    }
2001

    
2002
    /*
2003
     * (non-Javadoc)
2004
     * 
2005
     * @see
2006
     * com.iver.cit.gvsig.fmap.edition.commands.CommandListener#commandRefresh()
2007
     */
2008
    public void commandRefresh() {
2009
        // TODO Auto-generated method stub
2010
    }
2011

    
2012
    /**
2013
     * <p>
2014
     * Equivalent operation to <i>undo</i>.
2015
     * </p>
2016
     * 
2017
     * <p>
2018
     * Exchanges the previous tool with the current one.
2019
     * </p>
2020
     * 
2021
     * @see #addBehavior(String, Behavior)
2022
     * @see #addBehavior(String, Behavior[])
2023
     * @see #setTool(String)
2024
     */
2025
    public void setPrevTool() {
2026
        setTool(prevTool);
2027
    }
2028

    
2029
    /**
2030
     * <p>
2031
     * Executes a <i>zoom in</i> operation centered at the center of the extent.
2032
     * </p>
2033
     * 
2034
     * <p>
2035
     * This implementation is designed for being invoked outside a
2036
     * <code>Behavior</code>, for example by an action of pressing a button; and
2037
     * simulates that the event has been produced by releasing the <i>button
2038
     * 1</i> of the mouse using the registered <code>Behavior</code> in this
2039
     * <code>MapControl</code> object that's responsible for the <i>zoom in</i>
2040
     * operation.
2041
     * </p>
2042
     * 
2043
     * @see #zoomOut()
2044
     */
2045
    public void zoomIn() {
2046
        Behavior mapTool = (Behavior) namesMapTools.get("zoomIn");
2047
        ViewPort vp = getViewPort();
2048
        Envelope r = getViewPort().getAdjustedExtent();
2049
        Point2D pCenter = vp.fromMapPoint(r.getCenter(0), r.getCenter(1));
2050
        MouseEvent e =
2051
            new MouseEvent(this, MouseEvent.MOUSE_RELEASED,
2052
                MouseEvent.ACTION_EVENT_MASK, MouseEvent.BUTTON1, (int) pCenter
2053
                    .getX(), (int) pCenter.getY(), 1, true, MouseEvent.BUTTON1);
2054
        try {
2055
            mapTool.mousePressed(e);
2056
            mapTool.mouseReleased(e);
2057
        } catch (BehaviorException t) {
2058
            throwException(t);
2059
        }
2060
    }
2061

    
2062
    /**
2063
     * <p>
2064
     * Executes a <i>zoom out</i> operation centered at the center of the
2065
     * extent.
2066
     * </p>
2067
     * 
2068
     * <p>
2069
     * This implementation is thought for being invoked outside a
2070
     * <code>Behavior</code>, for example by an action of pressing a button, and
2071
     * simulates that the event has been produced by releasing the <i>button
2072
     * 1</i> of the mouse using the registered <code>Behavior</code> in this
2073
     * <code>MapControl</code> object that's responsible for the <i>zoom out</i>
2074
     * operation.
2075
     * </p>
2076
     * 
2077
     * @see #zoomIn()
2078
     */
2079
    public void zoomOut() {
2080
        Behavior mapTool = (Behavior) namesMapTools.get("zoomOut");
2081
        ViewPort vp = getViewPort();
2082
        Envelope r = getViewPort().getAdjustedExtent();
2083
        Point2D pCenter = vp.fromMapPoint(r.getCenter(0), r.getCenter(1));
2084
        MouseEvent e =
2085
            new MouseEvent(this, MouseEvent.MOUSE_RELEASED,
2086
                MouseEvent.ACTION_EVENT_MASK, MouseEvent.BUTTON1, (int) pCenter
2087
                    .getX(), (int) pCenter.getY(), 1, true, MouseEvent.BUTTON1);
2088
        try {
2089
            mapTool.mousePressed(e);
2090
            mapTool.mouseReleased(e);
2091
        } catch (BehaviorException t) {
2092
            throwException(t);
2093
        }
2094
    }
2095

    
2096
    /**
2097
     * <p>
2098
     * Returns the listener used to catch all mouse events produced in this
2099
     * <code>MapControl</code> instance and that redirects the calls to the
2100
     * current map tool.
2101
     * </p>
2102
     * 
2103
     * @return the map tool listener used
2104
     */
2105
    public MapToolListener getMapToolListener() {
2106
        return mapToolListener;
2107
    }
2108

    
2109
    // mapTool can be null, for instance, in 3D's navigation tools
2110
    /**
2111
     * <p>
2112
     * Sets <code>mapTool</code> as this <code>MapControl</code>'s current map
2113
     * tool.
2114
     * 
2115
     * @param mapTool
2116
     *            a map tool, or <code>null</code> to disable the interaction
2117
     *            with the user
2118
     * 
2119
     * @see #getCurrentMapTool()
2120
     * @see #getCurrentTool()
2121
     * @see #setTool(String)
2122
     * @see #setPrevTool()
2123
     * @see #addBehavior(String, Behavior)
2124
     * @see #addBehavior(String, Behavior[])
2125
     */
2126
    public void setCurrentMapTool(Behavior mapTool) {
2127
        currentMapTool = mapTool;
2128
    }
2129

    
2130
    /**
2131
     * <p>
2132
     * Sets the delay to the timer that refreshes this <code>MapControl</code>
2133
     * instance.
2134
     * </p>
2135
     * 
2136
     * <p>
2137
     * <code>Delay (in ms) = 1000 / getDrawFrameRate()</code>
2138
     * </p>
2139
     * 
2140
     * @see #getDrawFrameRate()
2141
     * @see #setDrawFrameRate(int)
2142
     */
2143
    public void applyFrameRate() {
2144
        if (MapContext.getDrawFrameRate() > 0) {
2145
            timer.setDelay(1000 / MapContext.getDrawFrameRate());
2146
        }
2147
    }
2148

    
2149
    /**
2150
     * <p>
2151
     * Determines if its enabled the repaint that invokes the timer according to
2152
     * {@link #getDrawFrameRate() #getDrawFrameRate()}.
2153
     * </p>
2154
     * 
2155
     * @return <code>true</code> if its enabled; otherwise <code>false</code>
2156
     */
2157
    public static boolean isDrawAnimationEnabled() {
2158
        return drawAnimationEnabled;
2159
    }
2160

    
2161
    /**
2162
     * <p>
2163
     * Sets if its enabled the repaint that invokes the timer according to
2164
     * {@link #getDrawFrameRate() #getDrawFrameRate()}.
2165
     * </p>
2166
     * 
2167
     * @param drawAnimationEnabled
2168
     *            <code>true</code> to enable the mode; otherwise
2169
     *            <code>false</code>
2170
     */
2171
    public static void setDrawAnimationEnabled(boolean drawAnimationEnabled) {
2172
        MapControl.drawAnimationEnabled = drawAnimationEnabled;
2173
    }
2174

    
2175
    /**
2176
     * <p>
2177
     * Gets the shared object that determines if a drawing process must be
2178
     * cancelled or can continue.
2179
     * </p>
2180
     * 
2181
     * @return the shared object that determines if a drawing process must be
2182
     *         cancelled or can continue
2183
     */
2184
    public CancelDraw getCanceldraw() {
2185
        return canceldraw;
2186
    }
2187

    
2188
    /**
2189
     * <p>
2190
     * Gets the tool used in combination with the current tool of this
2191
     * <code>MapControl</code>.
2192
     * </p>
2193
     * 
2194
     * @return the tool used in combination with the <code>currentMapTool</code>
2195
     *         ; <code>null</code> if there is
2196
     *         no combined tool
2197
     */
2198
    public Behavior getCombinedTool() {
2199
        return combinedTool;
2200
    }
2201

    
2202
    /**
2203
     * <p>
2204
     * Sets a tool to be used in combination with the current tool of this
2205
     * <code>MapControl</code>.
2206
     * </p>
2207
     * 
2208
     * @param combinedTool
2209
     *            a tool to be used in combination with the current tool of
2210
     *            <code>MapControl</code>
2211
     */
2212
    public void setCombinedTool(Behavior combinedTool) {
2213
        this.combinedTool = combinedTool;
2214

    
2215
        if (currentMapTool == null) {
2216
            return;
2217
        }
2218

    
2219
        if (currentMapTool instanceof CompoundBehavior) {
2220
            ((CompoundBehavior) currentMapTool).addMapBehavior(combinedTool,
2221
                true);
2222
        } else {
2223
            currentMapTool =
2224
                new CompoundBehavior(new Behavior[] { currentMapTool });
2225
            ((CompoundBehavior) currentMapTool).addMapBehavior(combinedTool,
2226
                true);
2227
        }
2228

    
2229
    }
2230

    
2231
    /**
2232
     * <p>
2233
     * Adds a new tool as combined tool.
2234
     * </p>
2235
     * <p>
2236
     * The new tool will be stored with the previous combined tools, and will be
2237
     * combined with the current tool.
2238
     * </p>
2239
     * <p>
2240
     * If <code>tool</code> was already stored as a combined tool, doesn't adds
2241
     * it.
2242
     * </p>
2243
     * 
2244
     * @param tool
2245
     *            a new tool to be used combined with the current tool
2246
     */
2247
    public void addCombinedBehavior(Behavior tool) {
2248
        tool.setMapControl(this);
2249
        if (combinedTool == null) {
2250
            combinedTool = tool;
2251
        } else {
2252
            if (combinedTool instanceof CompoundBehavior) {
2253
                if (((CompoundBehavior) combinedTool).containsBehavior(tool)) {
2254
                    return;
2255
                }
2256

    
2257
                ((CompoundBehavior) combinedTool).addMapBehavior(tool, true);
2258
            } else {
2259
                if (combinedTool.equals(tool)) {
2260
                    return;
2261
                }
2262

    
2263
                combinedTool =
2264
                    new CompoundBehavior(new Behavior[] { combinedTool });
2265
                ((CompoundBehavior) combinedTool).addMapBehavior(tool, true);
2266
            }
2267
        }
2268

    
2269
        if (currentMapTool == null) {
2270
            return;
2271
        }
2272

    
2273
        if (currentMapTool instanceof CompoundBehavior) {
2274
            ((CompoundBehavior) currentMapTool).addMapBehavior(tool, true);
2275
        } else {
2276
            currentMapTool =
2277
                new CompoundBehavior(new Behavior[] { currentMapTool });
2278
            ((CompoundBehavior) currentMapTool).addMapBehavior(tool, true);
2279
        }
2280
    }
2281

    
2282
    /**
2283
     * <p>
2284
     * Removes the tool <code>tool</code> used in combination with the current
2285
     * tool of this <code>MapControl</code>.
2286
     * </p>
2287
     */
2288
    public void removeCombinedTool(Behavior tool) {
2289
        if ((currentMapTool != null)
2290
            && (currentMapTool instanceof CompoundBehavior)) {
2291
            ((CompoundBehavior) currentMapTool).removeMapBehavior(tool);
2292
        }
2293

    
2294
        if (combinedTool == null) {
2295
            return;
2296
        }
2297

    
2298
        if (combinedTool instanceof CompoundBehavior) {
2299
            ((CompoundBehavior) combinedTool).removeMapBehavior(tool);
2300
        } else {
2301
            combinedTool = null;
2302
        }
2303
    }
2304

    
2305
    /**
2306
     * <p>
2307
     * Updates the grid on the <code>ViewPort</code> of the associated
2308
     * <code>MapControl</code> object according the values in the
2309
     * {@link com.iver.cit.gvsig.gui.cad.CADToolAdapter.prefs.Preferences
2310
     * com.iver.cit.gvsig.gui.cad.CADToolAdapter.prefs.Preferences}.
2311
     * </p>
2312
     * 
2313
     * <p>
2314
     * The preferences are:
2315
     * <ul>
2316
     * <li>Show/hide the grid.</li>
2317
     * <li>Adjust or not the grid.</li>
2318
     * <li>Horizontal ( X ) line separation.</li>
2319
     * <li>Vertical ( Y ) line separation.</li>
2320
     * </ul>
2321
     * </p>
2322
     */
2323
    public void initializeGrid() {
2324
        Preferences prefs = mapControlManager.getEditionPreferences();
2325
        boolean showGrid =
2326
            prefs.getBoolean("grid.showgrid", cadgrid.isShowGrid());
2327
        boolean adjustGrid =
2328
            prefs.getBoolean("grid.adjustgrid", cadgrid.isAdjustGrid());
2329

    
2330
        double dx = prefs.getDouble("grid.distancex", cadgrid.getGridSizeX());
2331
        double dy = prefs.getDouble("grid.distancey", cadgrid.getGridSizeY());
2332

    
2333
        setGridVisibility(showGrid);
2334
        setAdjustGrid(adjustGrid);
2335
        cadgrid.setGridSizeX(dx);
2336
        cadgrid.setGridSizeY(dy);
2337
    }
2338

    
2339
    public void setAdjustGrid(boolean adjustGrid) {
2340
        cadgrid.setAdjustGrid(adjustGrid);
2341
    }
2342

    
2343
    public void setGridVisibility(boolean showGrid) {
2344
        cadgrid.setShowGrid(showGrid);
2345
        cadgrid.setViewPort(getViewPort());
2346
        this.repaint();
2347
    }
2348

    
2349
    /**
2350
     * Uses like a mouse pointer the image that provides the
2351
     * selected tool.
2352
     * 
2353
     */
2354

    
2355
    private Image lastImageCursor = null;
2356
    private Cursor lastCursor = null;
2357

    
2358
    private void setToolMouse() {
2359
        Image imageCursor = getCurrentMapTool().getImageCursor();
2360
        if (imageCursor != null && imageCursor == lastImageCursor && lastCursor != null && lastCursor != transparentCursor) {
2361
            setCursor(lastCursor);
2362
            return;
2363
        }
2364
        lastImageCursor = imageCursor;
2365
        Toolkit toolkit = Toolkit.getDefaultToolkit();
2366
        Cursor c = toolkit.createCustomCursor(imageCursor, new Point(16, 16), "img");
2367
        setCursor(c);
2368
        lastCursor = c;
2369
    }
2370

    
2371
    /**
2372
     * Makes the mouse pointer transparent.
2373
     */
2374
    private void setTransparentMouse() {
2375
        setCursor(transparentCursor);
2376
        lastCursor = transparentCursor;
2377
    }
2378

    
2379
    /**
2380
     * <p>
2381
     * Draws a 31x31 pixels cross round the mouse's cursor with an small
2382
     * geometry centered:
2383
     * <ul>
2384
     * <li><i>an square centered</i>: if isn't over a <i>control point</i>.
2385
     * <li><i>an small geometry centered according to the kind of control
2386
     * point</i>: if it's over a control point. In this case, the small geometry
2387
     * is drawn by a {@link ISnapper ISnapper} type object.<br>
2388
     * On the other hand, a light-yellowed background tool tip text with the
2389
     * type of <i>control point</i> will be displayed.</li>
2390
     * </p>
2391
     * 
2392
     * @param g
2393
     *            <code>MapControl</code>'s graphics where the data will be
2394
     *            drawn
2395
     */
2396
    private void drawCursor() {
2397
        if (adjustedPoint == null) {
2398
            return;
2399
        }
2400

    
2401
        if (usedSnap != null) {
2402
            usedSnap.draw(mapControlDrawer, adjustedPoint);
2403
            setTransparentMouse();
2404
        } else {
2405
            setToolMouse();
2406
        }
2407
    }
2408

    
2409
    /**
2410
     * <p>
2411
     * Adjusts the <code>point</code> to the grid if its enabled, and sets
2412
     * <code>mapHandlerAdjustedPoint</code> with that new value.
2413
     * </p>
2414
     * 
2415
     * <p>
2416
     * The value returned is the distance between those points: the original and
2417
     * the adjusted one.
2418
     * </p>
2419
     * 
2420
     * @param point
2421
     *            point to adjust
2422
     * @param mapHandlerAdjustedPoint
2423
     *            <code>point</code> adjusted
2424
     * 
2425
     * @return distance from <code>point</code> to the adjusted one. If there is
2426
     *         no
2427
     *         adjustment, returns <code>Double.MAX_VALUE</code>.
2428
     */
2429

    
2430
    private double adjustToHandler(Point2D point,
2431
        Point2D mapHandlerAdjustedPoint) {
2432

    
2433
        if (!isRefentEnabled())
2434
            return Double.MAX_VALUE;
2435

    
2436
        ArrayList layersToSnap = getMapContext().getLayersToSnap();
2437

    
2438
        double mapTolerance =
2439
            vp.toMapDistance(mapControlManager.getTolerance());
2440
        double minDist = mapTolerance;
2441
        double middleTol = mapTolerance * 0.5;
2442
        Point2D mapPoint = point;
2443
        org.gvsig.fmap.geom.primitive.Envelope r;
2444
        com.vividsolutions.jts.geom.Envelope e = null;
2445
        try {
2446
            r =
2447
                geomManager.createEnvelope(mapPoint.getX() - middleTol,
2448
                    mapPoint.getY() - middleTol, mapPoint.getX() + middleTol,
2449
                    mapPoint.getY() + middleTol, SUBTYPES.GEOM2D);
2450

    
2451
            e = Converter.convertEnvelopeToJTS(r);
2452
        } catch (CreateEnvelopeException e1) {
2453
            LOG.error("Error creating the envelope", e1);
2454
        }
2455

    
2456
        usedSnap = null;
2457
        Point2D lastPoint = null;
2458
        if (previousPoint != null) {
2459
            lastPoint = new Point2D.Double(previousPoint[0], previousPoint[1]);
2460
        }
2461
        for (int j = 0; j < layersToSnap.size(); j++) {
2462
            FLyrVect lyrVect = (FLyrVect) layersToSnap.get(j);
2463
            SpatialCache cache = lyrVect.getSpatialCache();
2464
            if (lyrVect.isVisible()) {
2465
                // La lista de snappers est? siempre ordenada por prioridad. Los
2466
                // de mayor
2467
                // prioridad est?n primero.
2468
                List geoms = cache.query(e);
2469
                
2470
                for (int i = 0; i < mapControlManager.getSnapperCount(); i++)
2471
                {
2472
                    ISnapper theSnapper = mapControlManager.getSnapperAt(i);
2473
                    if (theSnapper instanceof ISnapperGeometriesVectorial)
2474
                    {
2475
                        ((ISnapperGeometriesVectorial)theSnapper).setGeometries(geoms);                        
2476
                    }
2477
                } 
2478
                
2479
                for (int n = 0; n < geoms.size(); n++) {
2480
                    Geometry geom = (Geometry) geoms.get(n);
2481
                    for (int i = 0; i < mapControlManager.getSnapperCount(); i++) {
2482
                        ISnapper theSnapper = mapControlManager.getSnapperAt(i);
2483
                        if (!theSnapper.isEnabled())
2484
                            continue;
2485

    
2486
                        if (usedSnap != null) {
2487
                            // Si ya tenemos un snap y es de alta prioridad,
2488
                            // cogemos ese. (A no ser que en otra capa
2489
                            // encontremos un snapper mejor)
2490
                            if (theSnapper.getPriority() > usedSnap
2491
                                .getPriority())
2492
                                break;
2493
                        }
2494
                        // SnappingVisitor snapVisitor = null;
2495
                        Point2D theSnappedPoint = null;
2496
                        if (theSnapper instanceof ISnapperVectorial) {
2497
                            theSnappedPoint =
2498
                                ((ISnapperVectorial) theSnapper).getSnapPoint(
2499
                                    point, geom, mapTolerance, lastPoint);
2500
                        }
2501
                        if (theSnapper instanceof ISnapperRaster) {
2502
                            ISnapperRaster snapRaster =
2503
                                (ISnapperRaster) theSnapper;
2504
                            theSnappedPoint =
2505
                                snapRaster.getSnapPoint(this, point,
2506
                                    mapTolerance, lastPoint);
2507
                        }
2508

    
2509
                        if (theSnappedPoint != null) {
2510
                            double distAux = theSnappedPoint.distance(point);
2511
                            if (minDist > distAux) {
2512
                                minDist = distAux;
2513
                                usedSnap = theSnapper;
2514
                                mapHandlerAdjustedPoint
2515
                                    .setLocation(theSnappedPoint);
2516
                            }
2517
                        }
2518
                    }
2519
                } // for n
2520
            } // visible
2521
        }
2522
        if (usedSnap != null)
2523
            return minDist;
2524
        return Double.MAX_VALUE;
2525

    
2526
    }
2527

    
2528
    /**
2529
     * Determines if snap tools are enabled or disabled.
2530
     * 
2531
     * @return <code>true</code> to enable the snap tools; <code>false</code> to
2532
     *         disable them
2533
     * 
2534
     * @see #setRefentEnabled(boolean)
2535
     */
2536
    public boolean isRefentEnabled() {
2537
        return bRefent;
2538
    }
2539

    
2540
    /**
2541
     * <p>
2542
     * Tries to find the nearest geometry or grid control point by the position
2543
     * of the current snap tool.
2544
     * </p>
2545
     * 
2546
     * <p>
2547
     * Prioritizes the grid control points than the geometries ones.
2548
     * </p>
2549
     * 
2550
     * <p>
2551
     * If finds any near, stores the <i>map</i> and <i>pixel</i> coordinates for
2552
     * the snap, and enables the <code>bForceCoord</code> attribute for the next
2553
     * draw of the mouse's cursor.
2554
     * </p>
2555
     * 
2556
     * @param point
2557
     *            current mouse 2D position
2558
     */
2559
    public void calculateSnapPoint(Point point) {
2560
        // Se comprueba el ajuste a rejilla
2561

    
2562
        Point2D gridAdjustedPoint = vp.toMapPoint(point);
2563
        double minDistance = Double.MAX_VALUE;
2564

    
2565
        minDistance = cadgrid.adjustToGrid(gridAdjustedPoint);
2566
        if (minDistance < Double.MAX_VALUE) {
2567
            adjustedPoint = vp.fromMapPoint(gridAdjustedPoint);
2568
            mapAdjustedPoint = gridAdjustedPoint;
2569
        } else {
2570
            mapAdjustedPoint = null;
2571
        }
2572

    
2573
        Point2D handlerAdjustedPoint = null;
2574

    
2575
        // Se comprueba el ajuste a los handlers
2576
        if (mapAdjustedPoint != null) {
2577
            handlerAdjustedPoint = (Point2D) mapAdjustedPoint.clone(); // getMapControl().getViewPort().toMapPoint(point);
2578
        } else {
2579
            handlerAdjustedPoint = vp.toMapPoint(point);
2580
        }
2581

    
2582
        Point2D mapPoint = new Point2D.Double();
2583
        double distance = adjustToHandler(handlerAdjustedPoint, mapPoint);
2584

    
2585
        if (distance < minDistance) {
2586
            bForceCoord = true;
2587
            adjustedPoint = vp.fromMapPoint(mapPoint);
2588
            mapAdjustedPoint = mapPoint;
2589
            minDistance = distance;
2590
        }
2591

    
2592
        // Si no hay ajuste
2593
        if (minDistance == Double.MAX_VALUE) {
2594
            adjustedPoint = point;
2595
            mapAdjustedPoint = null;
2596
        }
2597
    }
2598

    
2599
    /**
2600
     * Sets the snap tools enabled or disabled.
2601
     * 
2602
     * @param activated
2603
     *            <code>true</code> to enable the snap tools; <code>false</code>
2604
     *            to disable them
2605
     * 
2606
     * @see #isRefentEnabled()
2607
     */
2608
    public void setRefentEnabled(boolean activated) {
2609
        bRefent = activated;
2610
    }
2611

    
2612
    public Grid getGrid() {
2613
        return cadgrid;
2614
    }
2615

    
2616
    public void update(Observable observable, Object notification) {
2617
        DataStoreNotification ddsn = (DataStoreNotification) notification;
2618
        String type = ddsn.getType();
2619
        if (type.equals(FeatureStoreNotification.AFTER_UNDO)
2620
            || type.equals(FeatureStoreNotification.AFTER_REDO)) {
2621
            repaint();
2622
        }
2623
    }
2624

    
2625
    /**
2626
     * @return the adjustedPoint
2627
     */
2628
    public Point2D getAdjustedPoint() {
2629
        return adjustedPoint;
2630
    }
2631

    
2632
    /**
2633
     * @return the mapAdjustedPoint
2634
     */
2635
    public Point2D getMapAdjustedPoint() {
2636
        return mapAdjustedPoint;
2637
    }
2638

    
2639
    /**
2640
     * Gets the selected point. If the snapping is enabled
2641
     * it returns the selected point.
2642
     * 
2643
     * @return
2644
     *         The selected point
2645
     */
2646
    public Point2D getPoint() {
2647
        if (mapAdjustedPoint != null) {
2648
            return mapAdjustedPoint;
2649
        } else {
2650
            return getViewPort().toMapPoint(adjustedPoint);
2651
        }
2652
    }
2653

    
2654
        public synchronized void dispose() {
2655
                // Check if we have already been disposed, and don't do it again
2656
                if (!disposed && ToolsLocator.getDisposableManager().release(this)) {
2657
                        drawer.setShutdown(true);
2658
                        disposed = true;
2659
                }
2660
        }
2661
}