Statistics
| Revision:

svn-gvsig-desktop / branches / v2_0_0_prep / libraries / libFMap_controls / src / org / gvsig / fmap / mapcontrol / MapControl.java @ 40391

History | View | Annotate | Download (87.1 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.dal.DataStoreNotification;
80
import org.gvsig.fmap.dal.feature.FeatureStoreNotification;
81
import org.gvsig.fmap.geom.Geometry;
82
import org.gvsig.fmap.geom.Geometry.SUBTYPES;
83
import org.gvsig.fmap.geom.GeometryLocator;
84
import org.gvsig.fmap.geom.GeometryManager;
85
import org.gvsig.fmap.geom.exception.CreateEnvelopeException;
86
import org.gvsig.fmap.geom.primitive.Envelope;
87
import org.gvsig.fmap.mapcontext.MapContext;
88
import org.gvsig.fmap.mapcontext.MapContextLocator;
89
import org.gvsig.fmap.mapcontext.MapContextManager;
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.observer.Observable;
111
import org.gvsig.tools.observer.Observer;
112
import org.gvsig.tools.task.Cancellable;
113
import org.gvsig.utils.exceptionHandling.ExceptionHandlingSupport;
114
import org.gvsig.utils.exceptionHandling.ExceptionListener;
115

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

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

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

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

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

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

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

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

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

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

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

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

    
411
    // private boolean isCancelled = true;
412

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

    
432
    /**
433
     * <p>
434
     * Reference to the {@link ViewPort ViewPort} of the {@link MapContext
435
     * MapContext} of this component.
436
     * </p>
437
     * 
438
     * <p>
439
     * After, the view port will change adapting itself according the current
440
     * projection and the extent.
441
     * </p>
442
     * 
443
     * @see #getViewPort()
444
     * 
445
     * @see ViewPort
446
     */
447
    protected ViewPort vp;
448

    
449
    /**
450
     * <p>
451
     * Manager of all <code>MapControl</code> painting requests.
452
     * </p>
453
     */
454
    private Drawer drawer;
455

    
456
    /**
457
     * <p>
458
     * Listener of all kind of mouse events produced in this component.
459
     * </p>
460
     * 
461
     * <p>
462
     * Delegates each mouse event to the current map tool.
463
     * </p>
464
     * 
465
     * @see #addBehavior(String, Behavior)
466
     * @see #addBehavior(String, Behavior[])
467
     * @see #getMapTool(String)
468
     * @see #getMapToolsKeySet()
469
     * @see #getNamesMapTools()
470
     * @see #setTool(String)
471
     */
472
    protected MapToolListener mapToolListener = new MapToolListener();
473

    
474
    /**
475
     * <p>
476
     * Listener of all events produced in a this component's
477
     * <code>MapContext</code> object during an atomic period of time.
478
     * </p>
479
     */
480
    private MapContextListener mapContextListener = new MapContextListener();
481

    
482
    /**
483
     * <p>
484
     * Group of <code>ExceptionListener</code> that, in whatever moment could be
485
     * notified a Throwable Java error or exception.
486
     * </p>
487
     * 
488
     * @see #addExceptionListener(ExceptionListener)
489
     * @see #removeExceptionListener(ExceptionListener)
490
     */
491
    private ExceptionHandlingSupport exceptionHandlingSupport =
492
        new ExceptionHandlingSupport();
493

    
494
    /**
495
     * <p>
496
     * Name of the previous tool used.
497
     * </p>
498
     */
499
    protected String prevTool;
500

    
501
    /**
502
     * <p>
503
     * Tool that will be used combined with the current tool of this
504
     * <code>MapControl</code>.
505
     * </p>
506
     */
507
    private Behavior combinedTool = null;
508

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

    
537
    /**
538
     * Kind of geometry drawn to identify the kind of control point selected by
539
     * the cursor's mouse.
540
     */
541
    private ISnapper usedSnap = null;
542

    
543
    /**
544
     * Determines if the snap tools are enabled or disabled.
545
     * 
546
     * @see #isRefentEnabled()
547
     * @see #setRefentEnabled(boolean)
548
     */
549
    private boolean bRefent = true;
550

    
551
    /**
552
     * Stores the 2D map coordinates of the last point added.
553
     */
554
    private double[] previousPoint = null;
555

    
556
    protected static MapControlManager mapControlManager =
557
        MapControlLocator.getMapControlManager();
558

    
559
    private static TreeMap selected = new TreeMap(new Comparator() {
560

    
561
        public int compare(Object o1, Object o2) {
562
            if (o1.getClass().equals(o2.getClass()))
563
                return 0;
564
            if (((ISnapper) o1).getPriority() > ((ISnapper) o2).getPriority())
565
                return 1;
566
            else
567
                return -1;
568
        }
569

    
570
    });
571

    
572
    /**
573
     * Represents the cursor's point selected in <i>map coordinates</i>.
574
     * 
575
     * @see MapControl#toMapPoint
576
     */
577
    private Point2D mapAdjustedPoint;
578

    
579
    /**
580
     * Renderer used to draw the layers.
581
     */
582
    private MapControlDrawer mapControlDrawer = null;
583
        private Cursor transparentCursor;
584
        
585
        private boolean disposed = false;
586

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

    
621
        setDoubleBuffered(false);
622
        setOpaque(true);
623
        status = DESACTUALIZADO;
624

    
625
        // Clase usada para cancelar el dibujado
626
        canceldraw = new CancelDraw();
627

    
628
        /*
629
         * We are not accessing the user preferences here.
630
         * This is an early initialization and is supposed
631
         * to be reset afterwards from a higher level (plugin)
632
         */
633
        MapContextManager mcm = MapContextLocator.getMapContextManager();
634
        vp = new ViewPort(mcm.getDefaultCRS());
635
        
636
        setMapContext(new MapContext(vp));
637

    
638
        // eventos
639
        this.addComponentListener(this);
640
        this.addMouseListener(mapToolListener);
641
        this.addMouseMotionListener(mapToolListener);
642
        this.addMouseWheelListener(mapToolListener);
643

    
644
        this.drawer = new Drawer();
645
        // Timer para mostrar el redibujado mientras se dibuja
646
        timer =
647
            new Timer(1000 / MapContext.getDrawFrameRate(),
648
                new ActionListener() {
649

    
650
                    public void actionPerformed(ActionEvent e) {
651

    
652
                        if (drawAnimationEnabled) {
653
                            MapControl.this.repaint();
654
                        }
655
                    }
656
                });
657
        initializeGrid();
658
        
659
        if(ToolsLocator.getDisposableManager() != null) {
660
                        ToolsLocator.getDisposableManager().bind(this);
661
                } else {
662
                        LOG.warn("Can't retrieve the disposable manager,");
663
                }
664
    }
665

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

    
703
        mapContext = model;
704

    
705
        if (mapContext.getViewPort() == null) {
706
            mapContext.setViewPort(vp);
707
        } else {
708
            vp = mapContext.getViewPort();
709
            cadgrid.setViewPort(vp);
710
        }
711

    
712
        mapContext.addAtomicEventListener(mapContextListener);
713

    
714
        status = DESACTUALIZADO;
715
    }
716

    
717
    /**
718
     * @return the mapControlDrawer
719
     */
720
    public MapControlDrawer getMapControlDrawer() {
721
        return mapControlDrawer;
722
    }
723

    
724
    /**
725
     * @param mapControlDrawer
726
     *            the mapControlDrawer to set
727
     */
728
    public void setMapControlDrawer(MapControlDrawer mapControlDrawer) {
729
        this.mapControlDrawer = mapControlDrawer;
730
        this.mapControlDrawer.setViewPort(vp);
731
    }
732

    
733
    /**
734
     * <p>
735
     * Gets this component's {@link MapContext MapContext} projection.
736
     * </p>
737
     * 
738
     * @return this component's {@link MapContext MapContext} projection
739
     * 
740
     * @see MapContext#getProjection()
741
     * @see MapControl#setProjection(IProjection)
742
     */
743
    public IProjection getProjection() {
744
        return getMapContext().getProjection();
745
    }
746

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

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

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

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

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

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

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

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

    
920
        if (combinedTool != null) {
921
            if (mapTool instanceof CompoundBehavior) {
922
                ((CompoundBehavior) mapTool).addMapBehavior(combinedTool, true);
923
            } else {
924
                currentMapTool =
925
                    new CompoundBehavior(new Behavior[] { currentMapTool });
926
                ((CompoundBehavior) currentMapTool).addMapBehavior(
927
                    combinedTool, true);
928
            }
929
        }
930

    
931
        // this.setCursor(mapTool.getCursor());
932
    }
933

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

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

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

    
999
        /*
1000
         * while (!isCancelled) {
1001
         * if (!drawer.isAlive()) {
1002
         * // Si hemos llegado aqu� con un thread vivo, seguramente
1003
         * // no estamos actualizados.
1004
         * 
1005
         * break;
1006
         * }
1007
         * 
1008
         * }
1009
         * canceldraw.setCancel(false);
1010
         * isCancelled = false;
1011
         * drawerAlive = false;
1012
         */
1013
    }
1014

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

    
1054
            Graphics gTemp = image.createGraphics();
1055
            Color theBackColor = vp.getBackColor();
1056
            if (theBackColor == null) {
1057
                gTemp.setColor(Color.WHITE);
1058
            } else {
1059
                gTemp.setColor(theBackColor);
1060
            }
1061

    
1062
            gTemp.fillRect(0, 0, getWidth(), getHeight());
1063
            gTemp.dispose();
1064
            status = DESACTUALIZADO;
1065
            // g.drawImage(image,0,0,null);
1066
            return true;
1067
        }
1068
        return false;
1069
    }
1070

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

    
1106
        try {
1107
            mapControlDrawer.startDrawing(this);
1108
        } catch (InterruptedException e) {
1109
            LOG.info("Error locking the MapControlDrawer", e);
1110
        }
1111
        mapControlDrawer.setGraphics(g);
1112
        mapControlDrawer.stopDrawing(this);
1113
        mapControlDrawer.setViewPort(getMapContext().getViewPort());
1114

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

    
1130
                        mapControlDrawer.drawImage(image, 0, 0);
1131

    
1132
                        drawer.put(new PaintingRequest());
1133
                        timer.start();
1134
                }
1135
        cadgrid.drawGrid(mapControlDrawer);
1136
        drawCursor();
1137
    }
1138

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

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

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

    
1207
    /**
1208
     * @deprecated use {@link #drawMap(boolean)} instead, or even
1209
     * better {@link #getMapContext()}.invalidate().
1210
     */
1211
    public void drawGraphics() {
1212
        drawMap(false);
1213
    }
1214

    
1215
    /**
1216
     * @see java.awt.event.ComponentListener#componentHidden(java.awt.event.ComponentEvent)
1217
     */
1218
    public void componentHidden(ComponentEvent e) {
1219
    }
1220

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

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

    
1245
    /**
1246
     * @see java.awt.event.ComponentListener#componentShown(java.awt.event.ComponentEvent)
1247
     */
1248
    public void componentShown(ComponentEvent e) {
1249
    }
1250

    
1251
    /**
1252
     * @see ExceptionHandlingSupport#addExceptionListener(ExceptionListener)
1253
     */
1254
    public void addExceptionListener(ExceptionListener o) {
1255
        exceptionHandlingSupport.addExceptionListener(o);
1256
    }
1257

    
1258
    /**
1259
     * @see ExceptionHandlingSupport#removeExceptionListener(ExceptionListener)
1260
     */
1261
    public boolean removeExceptionListener(ExceptionListener o) {
1262
        return exceptionHandlingSupport.removeExceptionListener(o);
1263
    }
1264

    
1265
    /**
1266
     * @see ExceptionHandlingSupport#throwException(Throwable)
1267
     */
1268
    protected void throwException(Throwable t) {
1269
        exceptionHandlingSupport.throwException(t);
1270
    }
1271

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

    
1285
        /**
1286
         * <p>
1287
         * Creates a new <code>PaintingRequest
1288
         * </p>
1289
         * instance.</p>
1290
         */
1291
        public PaintingRequest() {
1292
        }
1293

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

    
1337
                ViewPort viewPort = mapContext.getViewPort();
1338

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

    
1356
                timer.stop();
1357
                repaint();
1358

    
1359
            } catch (Throwable e) {
1360
                timer.stop();
1361
                e.printStackTrace();
1362
                throwException(e);
1363
            } 
1364
        }
1365
    }
1366

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

    
1393
        // Una mini cola de 2. No acumulamos peticiones de dibujado
1394
        // dibujamos solo lo �ltimo que nos han pedido.
1395

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

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

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

    
1429
        /**
1430
         * <p>
1431
         * Notifies the <code>Drawer2</code>'s worker to finish or continue with
1432
         * its process.
1433
         * </p>
1434
         * 
1435
         * @see #setShutdown(boolean)
1436
         */
1437
        private boolean shutdown;
1438

    
1439
                private Thread worker;
1440

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

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

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

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

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

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

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

    
1592
        /**
1593
         * <p>
1594
         * Determines if the drawing task must be canceled or not.
1595
         * </p>
1596
         * 
1597
         * @see #isCanceled()
1598
         * @see #setCanceled(boolean)
1599
         */
1600
        private boolean cancel = false;
1601

    
1602
        /**
1603
         * Creates a new <code>CancelDraw</code> object.
1604
         */
1605
        public CancelDraw() {
1606
        }
1607

    
1608
        /*
1609
         * (non-Javadoc)
1610
         * 
1611
         * @see com.iver.utiles.swing.threads.Cancellable#setCanceled(boolean)
1612
         */
1613
        public void setCanceled(boolean b) {
1614
            cancel = b;
1615
        }
1616

    
1617
        /*
1618
         * (non-Javadoc)
1619
         * 
1620
         * @see com.iver.utiles.swing.threads.Cancellable#isCanceled()
1621
         */
1622
        public boolean isCanceled() {
1623
            return cancel;
1624
        }
1625
    }
1626

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

    
1656
        /**
1657
         * <p>
1658
         * Used to avoid mouse wheel move events closed.
1659
         * </p>
1660
         * 
1661
         * <p>
1662
         * If a mouse wheel move event is produced
1663
         */
1664
        long t1;
1665

    
1666
        /**
1667
         * <p>
1668
         * Position of the mouse, in map coordinates.
1669
         * </p>
1670
         * 
1671
         * <p>
1672
         * This point coordinates will be used as center of the <i>zoom</i>
1673
         * operation.
1674
         * </p>
1675
         */
1676
        Point2D pReal;
1677

    
1678
        /**
1679
         * @see java.awt.event.MouseListener#mouseClicked(java.awt.event.MouseEvent)
1680
         * @see Behavior#mouseClicked(MouseEvent)
1681
         */
1682
        public void mouseClicked(MouseEvent e) {
1683
            try {
1684
                if (currentMapTool != null) {
1685
                    currentMapTool.mouseClicked(e);
1686
                }
1687
                Point2D p;
1688

    
1689
                if (mapAdjustedPoint != null) {
1690
                    p = mapAdjustedPoint;
1691
                } else {
1692
                    p = vp.toMapPoint(adjustedPoint);
1693
                }            
1694
                previousPoint = new double[] { p.getX(), p.getY() };                
1695
            } catch (BehaviorException t) {
1696
                throwException(t);
1697
            }
1698
        }
1699

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

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

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

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

    
1760
        /**
1761
         * @see java.awt.event.MouseWheelListener#mouseWheelMoved(java.awt.event.MouseWheelEvent)
1762
         * @see Behavior#mouseWheelMoved(MouseWheelEvent)
1763
         */
1764
        public void mouseWheelMoved(MouseWheelEvent e) {
1765
            try {
1766
                if (currentMapTool == null) {
1767
                    return;
1768
                }
1769

    
1770
                currentMapTool.mouseWheelMoved(e);
1771

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

    
1791
                    /*
1792
                     * Point2D pReal = new
1793
                     * Point2D.Double(vp.getAdjustedExtent().getCenterX(),
1794
                     * vp.getAdjustedExtent().getCenterY());
1795
                     */
1796
                    int amount = e.getWheelRotation();
1797
                    double nuevoX;
1798
                    double nuevoY;
1799
                    double factor;
1800

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

    
1820
                        try {
1821
                            vp.setEnvelope(geomManager.createEnvelope(x, y, x
1822
                                + width, y + height, SUBTYPES.GEOM2D));
1823
                        } catch (CreateEnvelopeException e1) {
1824
                            LOG.info("Error creating the envelope", e);
1825
                        }
1826
                    }
1827

    
1828
                }
1829
            } catch (BehaviorException t) {
1830
                throwException(t);
1831
            }
1832
        }
1833

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

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

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

    
1894
        /**
1895
         * @see org.gvsig.fmap.mapcontext.events.listeners.AtomicEventListener#atomicEvent(org.gvsig.fmap.mapcontext.events.AtomicEvent)
1896
         */
1897
        public void atomicEvent(final AtomicEvent e) {
1898
            if (!SwingUtilities.isEventDispatchThread()) {
1899
                SwingUtilities.invokeLater(new Runnable() {
1900

    
1901
                    public void run() {
1902
                        atomicEvent(e);
1903
                    }
1904
                });
1905
                return;
1906
            }
1907
            LayerEvent[] layerEvents = e.getLayerEvents();
1908

    
1909
            for (int i = 0; i < layerEvents.length; i++) {
1910
                if (layerEvents[i].getProperty().equals("visible")) {
1911
                    drawMap(false);
1912
                    return;
1913
                } else
1914
                    if (layerEvents[i].getEventType() == LayerEvent.EDITION_CHANGED) {
1915
                        drawMap(false);
1916
                        return;
1917
                    }
1918
            }
1919

    
1920
            if (e.getColorEvents().length > 0) {
1921
                drawMap(false);
1922
                return;
1923
            }
1924

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

    
1930
            if (e.getProjectionEvents().length > 0) {
1931
                // redraw = true;
1932
            }
1933

    
1934
            LayerCollectionEvent[] aux = e.getLayerCollectionEvents();
1935
            if (aux.length > 0) {
1936
                for (int i = 0; i < aux.length; i++) {
1937
                    if (aux[i].getAffectedLayer().getFLayerStatus()
1938
                        .isDriverLoaded()) {
1939
                        drawMap(false);
1940
                        return;
1941
                    }
1942
                }
1943

    
1944
            }
1945

    
1946
            if (e.getLegendEvents().length > 0) {
1947
                drawMap(false);
1948
                return;
1949
            }
1950

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

    
1958
    /**
1959
     * <p>
1960
     * Gets the <code>ViewPort</code> of this component's {@link MapContext
1961
     * MapContext} .
1962
     * </p>
1963
     * 
1964
     * @see MapContext#getViewPort()
1965
     */
1966
    public ViewPort getViewPort() {
1967
        return vp;
1968
    }
1969

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

    
1987
    /*
1988
     * (non-Javadoc)
1989
     * 
1990
     * @see
1991
     * com.iver.cit.gvsig.fmap.edition.commands.CommandListener#commandRepaint()
1992
     */
1993
    public void commandRepaint() {
1994
        drawMap(false);
1995
    }
1996

    
1997
    /*
1998
     * (non-Javadoc)
1999
     * 
2000
     * @see
2001
     * com.iver.cit.gvsig.fmap.edition.commands.CommandListener#commandRefresh()
2002
     */
2003
    public void commandRefresh() {
2004
        // TODO Auto-generated method stub
2005
    }
2006

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

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

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

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

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

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

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

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

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

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

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

    
2210
        if (currentMapTool == null) {
2211
            return;
2212
        }
2213

    
2214
        if (currentMapTool instanceof CompoundBehavior) {
2215
            ((CompoundBehavior) currentMapTool).addMapBehavior(combinedTool,
2216
                true);
2217
        } else {
2218
            currentMapTool =
2219
                new CompoundBehavior(new Behavior[] { currentMapTool });
2220
            ((CompoundBehavior) currentMapTool).addMapBehavior(combinedTool,
2221
                true);
2222
        }
2223

    
2224
    }
2225

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

    
2252
                ((CompoundBehavior) combinedTool).addMapBehavior(tool, true);
2253
            } else {
2254
                if (combinedTool.equals(tool)) {
2255
                    return;
2256
                }
2257

    
2258
                combinedTool =
2259
                    new CompoundBehavior(new Behavior[] { combinedTool });
2260
                ((CompoundBehavior) combinedTool).addMapBehavior(tool, true);
2261
            }
2262
        }
2263

    
2264
        if (currentMapTool == null) {
2265
            return;
2266
        }
2267

    
2268
        if (currentMapTool instanceof CompoundBehavior) {
2269
            ((CompoundBehavior) currentMapTool).addMapBehavior(tool, true);
2270
        } else {
2271
            currentMapTool =
2272
                new CompoundBehavior(new Behavior[] { currentMapTool });
2273
            ((CompoundBehavior) currentMapTool).addMapBehavior(tool, true);
2274
        }
2275
    }
2276

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

    
2289
        if (combinedTool == null) {
2290
            return;
2291
        }
2292

    
2293
        if (combinedTool instanceof CompoundBehavior) {
2294
            ((CompoundBehavior) combinedTool).removeMapBehavior(tool);
2295
        } else {
2296
            combinedTool = null;
2297
        }
2298
    }
2299

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

    
2325
        double dx = prefs.getDouble("grid.distancex", cadgrid.getGridSizeX());
2326
        double dy = prefs.getDouble("grid.distancey", cadgrid.getGridSizeY());
2327

    
2328
        setGridVisibility(showGrid);
2329
        setAdjustGrid(adjustGrid);
2330
        cadgrid.setGridSizeX(dx);
2331
        cadgrid.setGridSizeY(dy);
2332
    }
2333

    
2334
    public void setAdjustGrid(boolean adjustGrid) {
2335
        cadgrid.setAdjustGrid(adjustGrid);
2336
    }
2337

    
2338
    public void setGridVisibility(boolean showGrid) {
2339
        cadgrid.setShowGrid(showGrid);
2340
        cadgrid.setViewPort(getViewPort());
2341
        this.repaint();
2342
    }
2343

    
2344
    /**
2345
     * Uses like a mouse pointer the image that provides the
2346
     * selected tool.
2347
     * 
2348
     */
2349

    
2350
    private Image lastImageCursor = null;
2351
    private Cursor lastCursor = null;
2352

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

    
2366
    /**
2367
     * Makes the mouse pointer transparent.
2368
     */
2369
    private void setTransparentMouse() {
2370
        setCursor(transparentCursor);
2371
        lastCursor = transparentCursor;
2372
    }
2373

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

    
2396
        if (usedSnap != null) {
2397
            usedSnap.draw(mapControlDrawer, adjustedPoint);
2398
            setTransparentMouse();
2399
        } else {
2400
            setToolMouse();
2401
        }
2402
    }
2403

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

    
2425
    private double adjustToHandler(Point2D point,
2426
        Point2D mapHandlerAdjustedPoint) {
2427

    
2428
        if (!isRefentEnabled())
2429
            return Double.MAX_VALUE;
2430

    
2431
        List layersToSnap = getMapContext().getLayersToSnap();
2432

    
2433
        double mapTolerance =
2434
            vp.toMapDistance(mapControlManager.getTolerance());
2435
        double minDist = mapTolerance;
2436
        double middleTol = mapTolerance * 0.5;
2437
        Point2D mapPoint = point;
2438
        Envelope r = null;
2439
        try {
2440
            r =
2441
                geomManager.createEnvelope(mapPoint.getX() - middleTol,
2442
                    mapPoint.getY() - middleTol, mapPoint.getX() + middleTol,
2443
                    mapPoint.getY() + middleTol, SUBTYPES.GEOM2D);
2444
        } catch (Exception e1) {
2445
            LOG.info("Error creating the envelope", e1);
2446
            return Double.MAX_VALUE;
2447
        }
2448

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

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

    
2502
                        if (theSnappedPoint != null) {
2503
                            double distAux = theSnappedPoint.distance(point);
2504
                            if (minDist > distAux) {
2505
                                minDist = distAux;
2506
                                usedSnap = theSnapper;
2507
                                mapHandlerAdjustedPoint
2508
                                    .setLocation(theSnappedPoint);
2509
                            }
2510
                        }
2511
                    }
2512
                } // for n
2513
            } // visible
2514
        }
2515
        if (usedSnap != null)
2516
            return minDist;
2517
        return Double.MAX_VALUE;
2518

    
2519
    }
2520

    
2521
    /**
2522
     * Determines if snap tools are enabled or disabled.
2523
     * 
2524
     * @return <code>true</code> to enable the snap tools; <code>false</code> to
2525
     *         disable them
2526
     * 
2527
     * @see #setRefentEnabled(boolean)
2528
     */
2529
    public boolean isRefentEnabled() {
2530
        return bRefent;
2531
    }
2532

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

    
2555
        Point2D gridAdjustedPoint = vp.toMapPoint(point);
2556
        double minDistance = Double.MAX_VALUE;
2557

    
2558
        minDistance = cadgrid.adjustToGrid(gridAdjustedPoint);
2559
        if (minDistance < Double.MAX_VALUE) {
2560
            adjustedPoint = vp.fromMapPoint(gridAdjustedPoint);
2561
            mapAdjustedPoint = gridAdjustedPoint;
2562
        } else {
2563
            mapAdjustedPoint = null;
2564
        }
2565

    
2566
        Point2D handlerAdjustedPoint = null;
2567

    
2568
        // Se comprueba el ajuste a los handlers
2569
        if (mapAdjustedPoint != null) {
2570
            handlerAdjustedPoint = (Point2D) mapAdjustedPoint.clone(); // getMapControl().getViewPort().toMapPoint(point);
2571
        } else {
2572
            handlerAdjustedPoint = vp.toMapPoint(point);
2573
        }
2574

    
2575
        Point2D mapPoint = new Point2D.Double();
2576
        double distance = adjustToHandler(handlerAdjustedPoint, mapPoint);
2577

    
2578
        if (distance < minDistance) {
2579
            bForceCoord = true;
2580
            adjustedPoint = vp.fromMapPoint(mapPoint);
2581
            mapAdjustedPoint = mapPoint;
2582
            minDistance = distance;
2583
        }
2584

    
2585
        // Si no hay ajuste
2586
        if (minDistance == Double.MAX_VALUE) {
2587
            adjustedPoint = point;
2588
            mapAdjustedPoint = null;
2589
        }
2590
    }
2591

    
2592
    /**
2593
     * Sets the snap tools enabled or disabled.
2594
     * 
2595
     * @param activated
2596
     *            <code>true</code> to enable the snap tools; <code>false</code>
2597
     *            to disable them
2598
     * 
2599
     * @see #isRefentEnabled()
2600
     */
2601
    public void setRefentEnabled(boolean activated) {
2602
        bRefent = activated;
2603
    }
2604

    
2605
    public Grid getGrid() {
2606
        return cadgrid;
2607
    }
2608

    
2609
    public void update(Observable observable, Object notification) {
2610
        DataStoreNotification ddsn = (DataStoreNotification) notification;
2611
        String type = ddsn.getType();
2612
        if (type.equals(FeatureStoreNotification.AFTER_UNDO)
2613
            || type.equals(FeatureStoreNotification.AFTER_REDO)) {
2614
            repaint();
2615
        }
2616
    }
2617

    
2618
    /**
2619
     * @return the adjustedPoint
2620
     */
2621
    public Point2D getAdjustedPoint() {
2622
        return adjustedPoint;
2623
    }
2624

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

    
2632
    /**
2633
     * Gets the selected point. If the snapping is enabled
2634
     * it returns the selected point.
2635
     * 
2636
     * @return
2637
     *         The selected point
2638
     */
2639
    public Point2D getPoint() {
2640
        if (mapAdjustedPoint != null) {
2641
            return mapAdjustedPoint;
2642
        } else {
2643
            return getViewPort().toMapPoint(adjustedPoint);
2644
        }
2645
    }
2646

    
2647
        public synchronized void dispose() {
2648
                // Check if we have already been disposed, and don't do it again
2649
                if (!disposed && ToolsLocator.getDisposableManager().release(this)) {
2650
                        drawer.setShutdown(true);
2651
                        disposed = true;
2652
                }
2653
        }
2654
}