Statistics
| Revision:

svn-gvsig-desktop / trunk / org.gvsig.desktop / org.gvsig.desktop.library / org.gvsig.fmap.control / src / main / java / org / gvsig / fmap / mapcontrol / MapControl.java @ 42036

History | View | Annotate | Download (87.1 KB)

1
/**
2
 * gvSIG. Desktop Geographic Information System.
3
 *
4
 * Copyright (C) 2007-2013 gvSIG Association.
5
 *
6
 * This program is free software; you can redistribute it and/or
7
 * modify it under the terms of the GNU General Public License
8
 * as published by the Free Software Foundation; either version 3
9
 * of the License, or (at your option) any later version.
10
 *
11
 * This program is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
 * GNU General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU General Public License
17
 * along with this program; if not, write to the Free Software
18
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19
 * MA  02110-1301, USA.
20
 *
21
 * For any additional information, do not hesitate to contact us
22
 * at info AT gvsig.com, or visit our website www.gvsig.com.
23
 */
24
package org.gvsig.fmap.mapcontrol;
25

    
26
import java.awt.Color;
27
import java.awt.Cursor;
28
import java.awt.Dimension;
29
import java.awt.Graphics;
30
import java.awt.Graphics2D;
31
import java.awt.Image;
32
import java.awt.Point;
33
import java.awt.Toolkit;
34
import java.awt.event.ActionEvent;
35
import java.awt.event.ActionListener;
36
import java.awt.event.ComponentEvent;
37
import java.awt.event.ComponentListener;
38
import java.awt.event.MouseEvent;
39
import java.awt.event.MouseListener;
40
import java.awt.event.MouseMotionListener;
41
import java.awt.event.MouseWheelEvent;
42
import java.awt.event.MouseWheelListener;
43
import java.awt.geom.Point2D;
44
import java.awt.image.BufferedImage;
45
import java.awt.image.MemoryImageSource;
46
import java.util.Comparator;
47
import java.util.HashMap;
48
import java.util.List;
49
import java.util.Map;
50
import java.util.Set;
51
import java.util.TreeMap;
52
import java.util.prefs.Preferences;
53

    
54
import javax.swing.JComponent;
55
import javax.swing.SwingUtilities;
56
import javax.swing.Timer;
57

    
58
import org.cresques.cts.IProjection;
59
import org.gvsig.fmap.dal.DataStoreNotification;
60
import org.gvsig.fmap.dal.feature.FeatureStoreNotification;
61
import org.gvsig.fmap.geom.Geometry;
62
import org.gvsig.fmap.geom.Geometry.SUBTYPES;
63
import org.gvsig.fmap.geom.GeometryLocator;
64
import org.gvsig.fmap.geom.GeometryManager;
65
import org.gvsig.fmap.geom.exception.CreateEnvelopeException;
66
import org.gvsig.fmap.geom.primitive.Envelope;
67
import org.gvsig.fmap.mapcontext.MapContext;
68
import org.gvsig.fmap.mapcontext.MapContextLocator;
69
import org.gvsig.fmap.mapcontext.MapContextManager;
70
import org.gvsig.fmap.mapcontext.ViewPort;
71
import org.gvsig.fmap.mapcontext.events.AtomicEvent;
72
import org.gvsig.fmap.mapcontext.events.listeners.AtomicEventListener;
73
import org.gvsig.fmap.mapcontext.layers.FLayers;
74
import org.gvsig.fmap.mapcontext.layers.LayerCollectionEvent;
75
import org.gvsig.fmap.mapcontext.layers.LayerEvent;
76
import org.gvsig.fmap.mapcontext.layers.SpatialCache;
77
import org.gvsig.fmap.mapcontext.layers.vectorial.FLyrVect;
78
import org.gvsig.fmap.mapcontext.layers.vectorial.GraphicLayer;
79
import org.gvsig.fmap.mapcontrol.tools.BehaviorException;
80
import org.gvsig.fmap.mapcontrol.tools.CompoundBehavior;
81
import org.gvsig.fmap.mapcontrol.tools.Behavior.Behavior;
82
import org.gvsig.fmap.mapcontrol.tools.Listeners.ToolListener;
83
import org.gvsig.fmap.mapcontrol.tools.grid.Grid;
84
import org.gvsig.fmap.mapcontrol.tools.snapping.snappers.ISnapper;
85
import org.gvsig.fmap.mapcontrol.tools.snapping.snappers.ISnapperGeometriesVectorial;
86
import org.gvsig.fmap.mapcontrol.tools.snapping.snappers.ISnapperRaster;
87
import org.gvsig.fmap.mapcontrol.tools.snapping.snappers.ISnapperVectorial;
88
import org.gvsig.tools.ToolsLocator;
89
import org.gvsig.tools.dispose.Disposable;
90
import org.gvsig.tools.observer.Observable;
91
import org.gvsig.tools.observer.Observer;
92
import org.gvsig.tools.task.Cancellable;
93
import org.gvsig.utils.exceptionHandling.ExceptionHandlingSupport;
94
import org.gvsig.utils.exceptionHandling.ExceptionListener;
95
import org.slf4j.Logger;
96
import org.slf4j.LoggerFactory;
97

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

    
261
    protected static final GeometryManager geomManager =
262
        GeometryLocator.getGeometryManager();
263
    private static final Logger LOG =
264
        LoggerFactory.getLogger(GeometryManager.class);
265

    
266
    /**
267
     * <p>
268
     * One of the possible status of <code>MapControl</code>. Determines that
269
     * all visible information has been drawn and its updated.
270
     * </p>
271
     */
272
    public static final int ACTUALIZADO = 0;
273

    
274
    /**
275
     * <p>
276
     * One of the possible status of <code>MapControl</code>. Determines that
277
     * not all visible information has been drawn or isn't updated.
278
     * </p>
279
     */
280
    public static final int DESACTUALIZADO = 1;
281

    
282
    /**
283
     * <p>
284
     * Determines if the drawer can update this <code>MapControl</code> instance
285
     * when the timer launches an event.
286
     * </p>
287
     */
288
    private static boolean drawAnimationEnabled = true;
289

    
290
    /**
291
     * <p>
292
     * Inner model with the layers, event support for drawing them, and the
293
     * <code>ViewPort</code> with information to adapt to the bounds available
294
     * in <i>image coordinates</i>.
295
     * </p>
296
     *
297
     * @see #getMapContext()
298
     * @see #setMapContext(MapContext)
299
     */
300
    private MapContext mapContext = null;
301

    
302
    /**
303
     * <p>
304
     * All registered <code>Behavior</code> that can define a way to work with
305
     * this <code>MapControl</code>.
306
     * </p>
307
     *
308
     * <p>
309
     * Only one of them can be active at a given moment.
310
     * </p>
311
     *
312
     * @see #addBehavior(String, Behavior)
313
     * @see #addBehavior(String, Behavior[])
314
     * @see #getMapTool(String)
315
     * @see #getMapToolsKeySet()
316
     * @see #getNamesMapTools()
317
     */
318
    protected Map<String,Behavior> namesMapTools = new HashMap<String,Behavior>();
319

    
320
    /**
321
     * <p>
322
     * Active {@link Behavior Behavior} that will generate events according a
323
     * criterion, and then, with a {@link ToolListener ToolListener} associated,
324
     * will simulate to user that works with this component as a particular
325
     * tool.
326
     * </p>
327
     *
328
     * @see #getCurrentMapTool()
329
     * @see #getCurrentTool()
330
     * @see #setTool(String)
331
     */
332
    protected Behavior currentMapTool = null;
333

    
334
    /**
335
     * <p>
336
     * Determines which's the current drawn status of this component:
337
     * <ul>
338
     * <li><b>OUTDATED</b>: all visible information has been drawn or isn't
339
     * updated.</li>
340
     * <li><b>UTDATED</b>: all visible information has been drawn and its
341
     * updated.</li>
342
     * <li><b>ONLY_GRAPHICS</b>: only the graphical layer must be drawn /
343
     * updated.</li>
344
     * </ul>
345
     * </p>
346
     *
347
     * <p>
348
     * The <code>MapControl</code> drawing process will consider the value of
349
     * this parameter to decide which elements will be updated or drawn.
350
     * </p>
351
     */
352
    private int status = DESACTUALIZADO;
353

    
354
    /**
355
     * <p>
356
     * Image with a buffer to accelerate the draw the changes of the graphical
357
     * items in this component.
358
     * </p>
359
     *
360
     * <p>
361
     * Firstly, information will be drawn in the buffer, and, when is outright
362
     * drawn, that information will be displayed. Meanwhile, the previous image
363
     * can be kept showed.
364
     * </p>
365
     *
366
     * @see BufferedImage
367
     *
368
     * @see #getImage()
369
     */
370
    private BufferedImage image = null;
371

    
372
    /**
373
     * <p>
374
     * Name of the tool used currently to interact with this component.
375
     * </p>
376
     *
377
     * @see #getCurrentTool()
378
     * @see #setTool(String)
379
     */
380
    protected String currentTool;
381

    
382
    /**
383
     * <p>
384
     * Object to store the flag that notifies a drawing thread task and
385
     * <code>MapContext</code>'s layers, that must be canceled or can continue
386
     * with the process.
387
     * </p>
388
     *
389
     * @see #cancelDrawing()
390
     */
391
    private CancelDraw canceldraw;
392

    
393
    // private boolean isCancelled = true;
394

    
395
    /**
396
     * <p>
397
     * Fires an action events after a specified delay.
398
     * </p>
399
     *
400
     * <p>
401
     * <code>MapControl</code> will use the timer to update its visible
402
     * graphical information during a drawing process, or allowing to cancel
403
     * that process.
404
     * </p>
405
     *
406
     * <p>
407
     * This is very useful to pretend faster interactivity to user when
408
     * <code>MapControl</code> has lots of layers, and / or layers with heavy
409
     * graphical elements, that need a long time to finish drawing all its data.
410
     * </p>
411
     */
412
    private Timer timer;
413

    
414
    /**
415
     * <p>
416
     * Reference to the {@link ViewPort ViewPort} of the {@link MapContext
417
     * MapContext} of this component.
418
     * </p>
419
     *
420
     * <p>
421
     * After, the view port will change adapting itself according the current
422
     * projection and the extent.
423
     * </p>
424
     *
425
     * @see #getViewPort()
426
     *
427
     * @see ViewPort
428
     */
429
    protected ViewPort vp;
430

    
431
    /**
432
     * <p>
433
     * Manager of all <code>MapControl</code> painting requests.
434
     * </p>
435
     */
436
    private Drawer drawer;
437

    
438
    /**
439
     * <p>
440
     * Listener of all kind of mouse events produced in this component.
441
     * </p>
442
     *
443
     * <p>
444
     * Delegates each mouse event to the current map tool.
445
     * </p>
446
     *
447
     * @see #addBehavior(String, Behavior)
448
     * @see #addBehavior(String, Behavior[])
449
     * @see #getMapTool(String)
450
     * @see #getMapToolsKeySet()
451
     * @see #getNamesMapTools()
452
     * @see #setTool(String)
453
     */
454
    protected MapToolListener mapToolListener = new MapToolListener();
455

    
456
    /**
457
     * <p>
458
     * Listener of all events produced in a this component's
459
     * <code>MapContext</code> object during an atomic period of time.
460
     * </p>
461
     */
462
    private MapContextListener mapContextListener = new MapContextListener();
463

    
464
    /**
465
     * <p>
466
     * Group of <code>ExceptionListener</code> that, in whatever moment could be
467
     * notified a Throwable Java error or exception.
468
     * </p>
469
     *
470
     * @see #addExceptionListener(ExceptionListener)
471
     * @see #removeExceptionListener(ExceptionListener)
472
     */
473
    private ExceptionHandlingSupport exceptionHandlingSupport =
474
        new ExceptionHandlingSupport();
475

    
476
    /**
477
     * <p>
478
     * Name of the previous tool used.
479
     * </p>
480
     */
481
    protected String prevTool;
482

    
483
    /**
484
     * <p>
485
     * Tool that will be used combined with the current tool of this
486
     * <code>MapControl</code>.
487
     * </p>
488
     */
489
    private Behavior combinedTool = null;
490

    
491
    /**
492
     * Optional grid that could be applied on the <code>MapControl</code>'s view
493
     * port.
494
     *
495
     * @see #getGrid()
496
     * @see #setAdjustGrid(boolean)
497
     */
498
    private Grid cadgrid = new Grid();
499
    /**
500
     * Represents the cursor's point selected in <i>screen coordinates</i>.
501
     *
502
     * @see ViewPort#fromMapPoint(Point2D)
503
     */
504
    protected Point2D adjustedPoint;
505
    /**
506
     * <p>
507
     * Determines if the position of the snap of the mouse's cursor on the
508
     * <code>MapControl</code> is within the area around a control point of a
509
     * geometry.
510
     * </p>
511
     *
512
     * <p>
513
     * The area is calculated as a circle centered at the control point and with
514
     * radius the pixels tolerance defined in the preferences.
515
     * </p>
516
     */
517
    private boolean bForceCoord = false;
518

    
519
    /**
520
     * Kind of geometry drawn to identify the kind of control point selected by
521
     * the cursor's mouse.
522
     */
523
    private ISnapper usedSnap = null;
524

    
525
    /**
526
     * Determines if the snap tools are enabled or disabled.
527
     *
528
     * @see #isRefentEnabled()
529
     * @see #setRefentEnabled(boolean)
530
     */
531
    private boolean bRefent = true;
532

    
533
    /**
534
     * Stores the 2D map coordinates of the last point added.
535
     */
536
    private double[] previousPoint = null;
537

    
538
    protected static MapControlManager mapControlManager =
539
        MapControlLocator.getMapControlManager();
540

    
541
    private static TreeMap selected = new TreeMap(new Comparator() {
542

    
543
        public int compare(Object o1, Object o2) {
544
            if (o1.getClass().equals(o2.getClass()))
545
                return 0;
546
            if (((ISnapper) o1).getPriority() > ((ISnapper) o2).getPriority())
547
                return 1;
548
            else
549
                return -1;
550
        }
551

    
552
    });
553

    
554
    /**
555
     * Represents the cursor's point selected in <i>map coordinates</i>.
556
     *
557
     * @see MapControl#toMapPoint
558
     */
559
    protected Point2D mapAdjustedPoint;
560

    
561
    /**
562
     * Renderer used to draw the layers.
563
     */
564
    private MapControlDrawer mapControlDrawer = null;
565
        private Cursor transparentCursor;
566

    
567
        private boolean disposed = false;
568

    
569
    /**
570
     * <p>
571
     * Creates a new <code>MapControl</code> instance with the following
572
     * characteristics:
573
     * <ul>
574
     * <li><i>Name</i>: MapControl .</li>
575
     * <li>Disables the double buffer of <code>JComponent</code> .</li>
576
     * <li>Sets opaque <i>(see {@link JComponent#setOpaque(boolean)} )</i>.</li>
577
     * <li>Sets its status to <code>OUTDATED</code> .</li>
578
     * <li>Creates a new {@link CancelDraw CancelDraw} object to notify
579
     * <code>MapContext</code>'s layers if can continue processing the drawn or
580
     * must cancel it.</li>
581
     * <li>Creates a new {@link MapContext MapContext} with a new
582
     * {@link ViewPort ViewPort} in the default projection.</li>
583
     * <li>Creates a new {@link CommandListener CommandListener} for edition
584
     * operations.</li>
585
     * <li>Creates a new {@link MapToolListener MapToolListener}, and associates
586
     * it as a listener of whatever kind of mouse events produced in this
587
     * component.</li>
588
     * <li>Creates a new {@link Drawer2 Drawer2} for managing the painting
589
     * requests.</li>
590
     * <li>Creates a new timer that will invoke refresh this component
591
     * <code>drawFrameRate</code> per second, when is running a drawing process,
592
     * and its enabled <code>drawAnimationEnabled</code>.</li>
593
     * </ul>
594
     * </p>
595
     */
596
    public MapControl() {
597
        this(null);
598
    }
599

    
600
    public MapControl(MapContext theMapContext) {
601
        if( theMapContext == null ) {
602
            theMapContext = new MapContext(new ViewPort(MapContextLocator.getMapContextManager().getDefaultCRS()));
603
        }
604
        this.setName("MapControl");
605
        Toolkit toolkit = Toolkit.getDefaultToolkit();
606
        Image imageTransparentCursor = toolkit.createImage(new MemoryImageSource(16, 16, new int[16 * 16], 0,16));
607
        transparentCursor =
608
            toolkit.createCustomCursor(imageTransparentCursor, new Point(0, 0), "invisiblecursor");
609

    
610
        setDoubleBuffered(false);
611
        setOpaque(true);
612
        status = DESACTUALIZADO;
613

    
614
        // Clase usada para cancelar el dibujado
615
        canceldraw = new CancelDraw();
616

    
617
        vp = theMapContext.getViewPort();
618

    
619
        setMapContext(theMapContext);
620

    
621
        // eventos
622
        this.addComponentListener(this);
623
        this.addMouseListener(mapToolListener);
624
        this.addMouseMotionListener(mapToolListener);
625
        this.addMouseWheelListener(mapToolListener);
626

    
627
        this.drawer = new Drawer();
628
        // Timer para mostrar el redibujado mientras se dibuja
629
        timer =
630
            new Timer(1000 / MapContext.getDrawFrameRate(),
631
                new ActionListener() {
632

    
633
                    public void actionPerformed(ActionEvent e) {
634

    
635
                        if (drawAnimationEnabled) {
636
                            MapControl.this.repaint();
637
                        }
638
                    }
639
                });
640
        initializeGrid();
641

    
642
        if(ToolsLocator.getDisposableManager() != null) {
643
                        ToolsLocator.getDisposableManager().bind(this);
644
                } else {
645
                        LOG.warn("Can't retrieve the disposable manager,");
646
                }
647
    }
648

    
649
    /**
650
     * <p>
651
     * Sets a <code>MapContext</code> to this component.
652
     * </p>
653
     *
654
     * <p>
655
     * The <code>MapContext</code> has the <i>model</i>, and most of the
656
     * <i>view</i>, and <i>control</i> logic of the layers of this component,
657
     * including a {@link ViewPort ViewPort} to adapt the information to the
658
     * projection, and to display it in the available area.
659
     * </p>
660
     *
661
     * <p>
662
     * If <code>model</code> hadn't a <code>ViewPort</code>, assigns the current
663
     * one to it, otherwise, use its <code>ViewPort</code>.
664
     * </p>
665
     *
666
     * <p>
667
     * After assigning the <code>MapContext</code> and <code>ViewPort</code>,
668
     * sets the same {@link MapContextListener MapContextListener} that was
669
     * using, and changes the <i>status</i> to <code>OUTDATED</code>.
670
     * </p>
671
     *
672
     * @param model
673
     *            this component's <code>MapContext</code>, that includes the
674
     *            <code>ViewPort</code>.
675
     *
676
     * @see MapContext
677
     *
678
     * @see #getMapContext()
679
     */
680
    public void setMapContext(MapContext model) {
681
        if (mapContext != null) {
682
            mapContext.removeAtomicEventListener(mapContextListener);
683
            mapContext.dispose();
684
        }
685

    
686
        mapContext = model;
687

    
688
        if (mapContext.getViewPort() == null) {
689
            mapContext.setViewPort(vp);
690
        } else {
691
            vp = mapContext.getViewPort();
692
            cadgrid.setViewPort(vp);
693
        }
694

    
695
        mapContext.addAtomicEventListener(mapContextListener);
696

    
697
        status = DESACTUALIZADO;
698
    }
699

    
700
    /**
701
     * @return the mapControlDrawer
702
     */
703
    public MapControlDrawer getMapControlDrawer() {
704
        return mapControlDrawer;
705
    }
706

    
707
    /**
708
     * @param mapControlDrawer
709
     *            the mapControlDrawer to set
710
     */
711
    public void setMapControlDrawer(MapControlDrawer mapControlDrawer) {
712
        this.mapControlDrawer = mapControlDrawer;
713
        this.mapControlDrawer.setViewPort(vp);
714
    }
715

    
716
    /**
717
     * <p>
718
     * Gets this component's {@link MapContext MapContext} projection.
719
     * </p>
720
     *
721
     * @return this component's {@link MapContext MapContext} projection
722
     *
723
     * @see MapContext#getProjection()
724
     * @see MapControl#setProjection(IProjection)
725
     */
726
    public IProjection getProjection() {
727
        return getMapContext().getProjection();
728
    }
729

    
730
    /**
731
     * <p>
732
     * Sets the projection to this component's {@link MapContext MapContext}.
733
     * </p>
734
     *
735
     * @param proj
736
     *            the kind of projection to this component's {@link MapContext
737
     *            MapContext}
738
     *
739
     * @see MapContext#setProjection(IProjection)
740
     * @see MapControl#getProjection()
741
     */
742
    public void setProjection(IProjection proj) {
743
        getMapContext().setProjection(proj);
744
    }
745

    
746
    /**
747
     * <p>
748
     * Gets this component's <code>MapContext</code>, with the <i>model</i>, and
749
     * most of the <i>view</i>, and <i>control</i> logic of the layers of this
750
     * component, including a {@link ViewPort ViewPort} to adapt the information
751
     * to the projection, and display it in the available area.
752
     * </p>
753
     *
754
     * @return this component's <code>MapContext</code>, that includes the
755
     *         <code>ViewPort</code> used to project the
756
     *         graphical information, and display it in the available area
757
     *
758
     * @see MapContext
759
     *
760
     * @see MapControl#setMapContext(MapContext)
761
     */
762
    public MapContext getMapContext() {
763
        return mapContext;
764
    }
765

    
766
    /**
767
     * <p>
768
     * Registers a new behavior to this component.
769
     * </p>
770
     *
771
     * <p>
772
     * According the nature of the {@link Behavior Behavior}, different events
773
     * will be generated. Those events can be caught by a particular
774
     * {@link ToolListener ToolListener}, allowing user to interact with this
775
     * <code>MapControl</code> object as a <i>tool</i>.
776
     * </p>
777
     *
778
     * @param name
779
     *            name to identify the behavior to add
780
     * @param tool
781
     *            the behavior to add
782
     *
783
     * @see #addBehavior(String, Behavior[])
784
     * @see #getNamesMapTools()
785
     * @see #getMapToolsKeySet()
786
     * @see #hasTool(String)
787
     */
788
    public void addBehavior(String name, Behavior tool) {
789
        namesMapTools.put(name, tool);
790
        tool.setMapControl(this);
791
    }
792

    
793
    /**
794
     * <p>
795
     * Registers a new behavior to this component as a {@link CompoundBehavior
796
     * CompoundBehavior} made up of <code>tools</code>.
797
     * </p>
798
     *
799
     * <p>
800
     * According the nature of the behaviors registered, different events will
801
     * be generated. Those events can be caught by a particular
802
     * {@link ToolListener ToolListener}, allowing user to interact with this
803
     * <code>MapControl</code> object as a <i>tool</i>.
804
     * </p>
805
     *
806
     * @param name
807
     *            name to identify the compound behavior to add
808
     * @param tools
809
     *            the compound behavior to add
810
     *
811
     * @see #addBehavior(String, Behavior)
812
     * @see #getNamesMapTools()
813
     * @see #getMapToolsKeySet()
814
     * @see #hasTool(String)
815
     */
816
    public void addBehavior(String name, Behavior[] tools) {
817
        CompoundBehavior tool = new CompoundBehavior(tools);
818
        tool.setMapControl(this);
819
        addBehavior(name, tool);
820
    }
821

    
822
    /**
823
     * <p>
824
     * Gets the <code>Behavior</code> registered in this component, identified
825
     * by <code>name</code>.
826
     * </p>
827
     *
828
     * @param name
829
     *            name of a registered behavior
830
     *
831
     * @return tool the registered behavior in this component as
832
     *         <code>name</code>, or <code>null</code> if
833
     *         no one has that identifier
834
     *
835
     * @see #addBehavior(String, Behavior)
836
     * @see #addBehavior(String, Behavior[])
837
     * @see #hasTool(String)
838
     */
839
    public Behavior getMapTool(String name) {
840
        return (Behavior) namesMapTools.get(name);
841
    }
842

    
843
    /**
844
     * <p>
845
     * Returns a set view of the keys that identified the tools registered.
846
     * </p>
847
     *
848
     * @return a set view of the keys that identified the tools registered
849
     *
850
     * @see HashMap#keySet()
851
     *
852
     * @see #getNamesMapTools()
853
     * @see #addBehavior(String, Behavior)
854
     * @see #addBehavior(String, Behavior[])
855
     */
856
    public Set getMapToolsKeySet() {
857
        return namesMapTools.keySet();
858
    }
859

    
860
    /**
861
     * <p>
862
     * Returns <code>true</code> if this component contains a tool identified by
863
     * <code>toolName</code>.
864
     * </p>
865
     *
866
     * @param toolName
867
     *            identifier of the tool
868
     *
869
     * @return <code>true</code> if this component contains a tool identified by
870
     *         <code>toolName</code>; otherwise <code>false</code>
871
     *
872
     * @see #addBehavior(String, Behavior)
873
     * @see #addBehavior(String, Behavior[])
874
     */
875
    public boolean hasTool(String toolName) {
876
        return namesMapTools.containsKey(toolName);
877
    }
878

    
879
    /**
880
     * <p>
881
     * Sets as current active <code>Behavior</code> associated to this
882
     * component, that one which is registered and identified by
883
     * <code>toolName</code>.
884
     * </p>
885
     *
886
     * <p>
887
     * Changing the current active behavior for this <code>MapControl</code>,
888
     * implies also updating the previous <i>behavior</i> tool, and the current
889
     * cursor.
890
     * </p>
891
     *
892
     * @param toolName
893
     *            name of a registered behavior
894
     *
895
     * @see #getCurrentMapTool()
896
     * @see #getCurrentTool()
897
     */
898
    public void setTool(String toolName) {
899
        prevTool = getCurrentTool();
900
        Behavior mapTool = (Behavior) namesMapTools.get(toolName);
901
        currentMapTool = mapTool;
902
        currentTool = toolName;
903

    
904
        if (combinedTool != null) {
905
            if (mapTool instanceof CompoundBehavior) {
906
                ((CompoundBehavior) mapTool).addMapBehavior(combinedTool, true);
907
            } else {
908
                currentMapTool =
909
                    new CompoundBehavior(new Behavior[] { currentMapTool });
910
                ((CompoundBehavior) currentMapTool).addMapBehavior(
911
                    combinedTool, true);
912
                currentMapTool.setMapControl(this);
913
            }
914
        }
915

    
916
        // this.setCursor(mapTool.getCursor());
917
    }
918

    
919
    /**
920
     * <p>
921
     * Gets as current active <code>Behavior</code> associated to this
922
     * component, that one which is registered and identified by
923
     * <code>toolName</code>.
924
     * </p>
925
     *
926
     * <p>
927
     * Changing the current active behavior for this <code>MapControl</code>,
928
     * implies also updating the previous <i>behavior</i> tool, and the current
929
     * cursor.
930
     * </p>
931
     *
932
     * @param toolName
933
     *            name of a registered behavior
934
     *
935
     * @see #getCurrentTool()
936
     * @see #setTool(String)
937
     */
938
    public Behavior getCurrentMapTool() {
939
        return currentMapTool;
940
    }
941

    
942
    /**
943
     * <p>
944
     * Returns the name of the current selected tool on this MapControl
945
     * </p>
946
     *
947
     * @return the name of the current's behavior tool associated to this
948
     *         component
949
     *
950
     * @see #getCurrentMapTool()
951
     * @see #setTool(String)
952
     */
953
    public String getCurrentTool() {
954
        return currentTool;
955
    }
956

    
957
    /**
958
     * <p>
959
     * Determines that current drawing process of <code>MapControl</code>'s
960
     * <code>MapContext</code>'s data must be canceled.
961
     * </p>
962
     *
963
     * <p>
964
     * It has no effects if now isn't drawing that graphical information.
965
     * </p>
966
     *
967
     * <p>
968
     * At last resort, the particular implementation of each layer in this
969
     * <code>MapControl</code>'s <code>MapContrext</code> will be that one which
970
     * will draw the graphical information, and, if supports, which could cancel
971
     * its drawing subprocess.
972
     * </p>
973
     */
974
    public void cancelDrawing() {
975
        /*
976
         * if (drawer != null) {
977
         * if (!drawer.isAlive()) {
978
         * return;
979
         * }
980
         * }
981
         */
982
        canceldraw.setCanceled(true);
983

    
984
        /*
985
         * while (!isCancelled) {
986
         * if (!drawer.isAlive()) {
987
         * // Si hemos llegado aqu� con un thread vivo, seguramente
988
         * // no estamos actualizados.
989
         *
990
         * break;
991
         * }
992
         *
993
         * }
994
         * canceldraw.setCancel(false);
995
         * isCancelled = false;
996
         * drawerAlive = false;
997
         */
998
    }
999

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

    
1039
            Graphics gTemp = image.createGraphics();
1040
            Color theBackColor = vp.getBackColor();
1041
            if (theBackColor == null) {
1042
                gTemp.setColor(Color.WHITE);
1043
            } else {
1044
                gTemp.setColor(theBackColor);
1045
            }
1046

    
1047
            gTemp.fillRect(0, 0, getWidth(), getHeight());
1048
            gTemp.dispose();
1049
            status = DESACTUALIZADO;
1050
            // g.drawImage(image,0,0,null);
1051
            return true;
1052
        }
1053
        return false;
1054
    }
1055

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

    
1091
        try {
1092
            mapControlDrawer.startDrawing(this);
1093
        } catch (InterruptedException e) {
1094
            LOG.info("Error locking the MapControlDrawer", e);
1095
        }
1096
        mapControlDrawer.setGraphics(g);
1097
        mapControlDrawer.stopDrawing(this);
1098
        mapControlDrawer.setViewPort(getMapContext().getViewPort());
1099

    
1100
        if (status == ACTUALIZADO) {
1101
            /*
1102
             * Si hay un behaviour y la imagen es distinta de null se delega el
1103
             * dibujado
1104
             * en dicho behaviour
1105
             */
1106
            if (image != null) {
1107
                if (currentMapTool != null) {
1108
                    currentMapTool.paintComponent(mapControlDrawer,true);
1109
                } else {
1110
                    mapControlDrawer.drawImage(image, 0, 0);
1111
                }
1112
            }
1113
                } else if ((status == DESACTUALIZADO)) {
1114

    
1115
                        mapControlDrawer.drawImage(image, 0, 0);
1116

    
1117
                        drawer.put(new PaintingRequest());
1118
                        timer.start();
1119
                }
1120
        cadgrid.drawGrid(mapControlDrawer);
1121
        drawCursor();
1122
    }
1123

    
1124
    private String getStatusLabel(int status ) {
1125
        switch(status) {
1126
            case ACTUALIZADO:
1127
                return "ACTUALIZADO";
1128
            case DESACTUALIZADO:
1129
                return "DESACTUALIZADO";
1130
            default:
1131
                return Integer.toString(status);
1132
        }
1133
    }
1134
    /**
1135
     * <p>
1136
     * Gets the {@link BufferedImage BufferedImage} used to accelerate the draw
1137
     * of new ''frames'' with changes, or new graphical items in this component.
1138
     * </p>
1139
     *
1140
     * @return double buffered image used by this component to accelerate the
1141
     *         draw of its graphical information, or <code>null</code> if isn't
1142
     *         already created
1143
     *
1144
     * @see BufferedImage
1145
     */
1146
    public BufferedImage getImage() {
1147
        return image;
1148
    }
1149

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

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

    
1202
    /**
1203
     * @deprecated use {@link #drawMap(boolean)} instead, or even
1204
     * better {@link #getMapContext()}.invalidate().
1205
     */
1206
    public void drawGraphics() {
1207
        drawMap(false);
1208
    }
1209

    
1210
    /**
1211
     * @see java.awt.event.ComponentListener#componentHidden(java.awt.event.ComponentEvent)
1212
     */
1213
    public void componentHidden(ComponentEvent e) {
1214
    }
1215

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

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

    
1240
    /**
1241
     * @see java.awt.event.ComponentListener#componentShown(java.awt.event.ComponentEvent)
1242
     */
1243
    public void componentShown(ComponentEvent e) {
1244
    }
1245

    
1246
    /**
1247
     * @see ExceptionHandlingSupport#addExceptionListener(ExceptionListener)
1248
     */
1249
    public void addExceptionListener(ExceptionListener o) {
1250
        exceptionHandlingSupport.addExceptionListener(o);
1251
    }
1252

    
1253
    /**
1254
     * @see ExceptionHandlingSupport#removeExceptionListener(ExceptionListener)
1255
     */
1256
    public boolean removeExceptionListener(ExceptionListener o) {
1257
        return exceptionHandlingSupport.removeExceptionListener(o);
1258
    }
1259

    
1260
    /**
1261
     * @see ExceptionHandlingSupport#throwException(Throwable)
1262
     */
1263
    protected void throwException(Throwable t) {
1264
        exceptionHandlingSupport.throwException(t);
1265
    }
1266

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

    
1280
        /**
1281
         * <p>
1282
         * Creates a new <code>PaintingRequest
1283
         * </p>
1284
         * instance.</p>
1285
         */
1286
        public PaintingRequest() {
1287
        }
1288

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

    
1332
                ViewPort viewPort = mapContext.getViewPort();
1333

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

    
1351
                timer.stop();
1352
                repaint();
1353

    
1354
            } catch (Throwable e) {
1355
                timer.stop();
1356
                LOG.warn("Problems drawing mapcontext.",e);
1357
                throwException(e);
1358
            }
1359
        }
1360
    }
1361

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

    
1388
        // Una mini cola de 2. No acumulamos peticiones de dibujado
1389
        // dibujamos solo lo �ltimo que nos han pedido.
1390

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

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

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

    
1424
        /**
1425
         * <p>
1426
         * Notifies the <code>Drawer2</code>'s worker to finish or continue with
1427
         * its process.
1428
         * </p>
1429
         *
1430
         * @see #setShutdown(boolean)
1431
         */
1432
        private boolean shutdown;
1433

    
1434
                private Thread worker;
1435

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

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

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

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

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

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

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

    
1587
        /**
1588
         * <p>
1589
         * Determines if the drawing task must be canceled or not.
1590
         * </p>
1591
         *
1592
         * @see #isCanceled()
1593
         * @see #setCanceled(boolean)
1594
         */
1595
        private boolean cancel = false;
1596

    
1597
        /**
1598
         * Creates a new <code>CancelDraw</code> object.
1599
         */
1600
        public CancelDraw() {
1601
        }
1602

    
1603
        /*
1604
         * (non-Javadoc)
1605
         *
1606
         * @see com.iver.utiles.swing.threads.Cancellable#setCanceled(boolean)
1607
         */
1608
        public void setCanceled(boolean b) {
1609
            cancel = b;
1610
        }
1611

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

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

    
1651
        /**
1652
         * <p>
1653
         * Used to avoid mouse wheel move events closed.
1654
         * </p>
1655
         *
1656
         * <p>
1657
         * If a mouse wheel move event is produced
1658
         */
1659
        long t1;
1660

    
1661
        /**
1662
         * <p>
1663
         * Position of the mouse, in map coordinates.
1664
         * </p>
1665
         *
1666
         * <p>
1667
         * This point coordinates will be used as center of the <i>zoom</i>
1668
         * operation.
1669
         * </p>
1670
         */
1671
        Point2D pReal;
1672

    
1673
        /**
1674
         * @see java.awt.event.MouseListener#mouseClicked(java.awt.event.MouseEvent)
1675
         * @see Behavior#mouseClicked(MouseEvent)
1676
         */
1677
        public void mouseClicked(MouseEvent e) {
1678
            try {
1679
                if (currentMapTool != null) {
1680
                    currentMapTool.mouseClicked(e);
1681
                }
1682
                Point2D p;
1683

    
1684
                if (mapAdjustedPoint != null) {
1685
                    p = mapAdjustedPoint;
1686
                } else {
1687
                    p = vp.toMapPoint(adjustedPoint);
1688
                }
1689
                previousPoint = new double[] { p.getX(), p.getY() };
1690
            } catch (BehaviorException t) {
1691
                throwException(t);
1692
            }
1693
        }
1694

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

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

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

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

    
1755
        /**
1756
         * @see java.awt.event.MouseWheelListener#mouseWheelMoved(java.awt.event.MouseWheelEvent)
1757
         * @see Behavior#mouseWheelMoved(MouseWheelEvent)
1758
         */
1759
        public void mouseWheelMoved(MouseWheelEvent e) {
1760
            try {
1761
                if (currentMapTool == null) {
1762
                    return;
1763
                }
1764

    
1765
                currentMapTool.mouseWheelMoved(e);
1766

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

    
1786
                    /*
1787
                     * Point2D pReal = new
1788
                     * Point2D.Double(vp.getAdjustedExtent().getCenterX(),
1789
                     * vp.getAdjustedExtent().getCenterY());
1790
                     */
1791
                    int amount = e.getWheelRotation();
1792
                    double nuevoX;
1793
                    double nuevoY;
1794
                    double factor;
1795

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

    
1815
                        try {
1816
                            vp.setEnvelope(geomManager.createEnvelope(x, y, x
1817
                                + width, y + height, SUBTYPES.GEOM2D));
1818
                        } catch (CreateEnvelopeException e1) {
1819
                            LOG.info("Error creating the envelope", e);
1820
                        }
1821
                    }
1822

    
1823
                }
1824
            } catch (BehaviorException t) {
1825
                throwException(t);
1826
            }
1827
        }
1828

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

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

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

    
1889
        /**
1890
         * @see org.gvsig.fmap.mapcontext.events.listeners.AtomicEventListener#atomicEvent(org.gvsig.fmap.mapcontext.events.AtomicEvent)
1891
         */
1892
        public void atomicEvent(final AtomicEvent e) {
1893
            if (!SwingUtilities.isEventDispatchThread()) {
1894
                SwingUtilities.invokeLater(new Runnable() {
1895

    
1896
                    public void run() {
1897
                        atomicEvent(e);
1898
                    }
1899
                });
1900
                return;
1901
            }
1902
            LayerEvent[] layerEvents = e.getLayerEvents();
1903

    
1904
            for (int i = 0; i < layerEvents.length; i++) {
1905
                if (layerEvents[i].getProperty().equals("visible")) {
1906
                    drawMap(false);
1907
                    return;
1908
                } else
1909
                    if (layerEvents[i].getEventType() == LayerEvent.EDITION_CHANGED) {
1910
                        drawMap(false);
1911
                        return;
1912
                    }
1913
            }
1914

    
1915
            if (e.getColorEvents().length > 0) {
1916
                drawMap(false);
1917
                return;
1918
            }
1919

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

    
1925
            if (e.getProjectionEvents().length > 0) {
1926
                // redraw = true;
1927
            }
1928

    
1929
            LayerCollectionEvent[] aux = e.getLayerCollectionEvents();
1930
            if (aux.length > 0) {
1931
                for (int i = 0; i < aux.length; i++) {
1932
                    if (aux[i].getAffectedLayer().getFLayerStatus()
1933
                        .isDriverLoaded()) {
1934
                        drawMap(false);
1935
                        return;
1936
                    }
1937
                }
1938

    
1939
            }
1940

    
1941
            if (e.getLegendEvents().length > 0) {
1942
                drawMap(false);
1943
                return;
1944
            }
1945

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

    
1953
    /**
1954
     * <p>
1955
     * Gets the <code>ViewPort</code> of this component's {@link MapContext
1956
     * MapContext} .
1957
     * </p>
1958
     *
1959
     * @see MapContext#getViewPort()
1960
     */
1961
    public ViewPort getViewPort() {
1962
        return vp;
1963
    }
1964

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

    
1982
    /*
1983
     * (non-Javadoc)
1984
     *
1985
     * @see
1986
     * com.iver.cit.gvsig.fmap.edition.commands.CommandListener#commandRepaint()
1987
     */
1988
    public void commandRepaint() {
1989
        drawMap(false);
1990
    }
1991

    
1992
    /*
1993
     * (non-Javadoc)
1994
     *
1995
     * @see
1996
     * com.iver.cit.gvsig.fmap.edition.commands.CommandListener#commandRefresh()
1997
     */
1998
    public void commandRefresh() {
1999
        // TODO Auto-generated method stub
2000
    }
2001

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

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

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

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

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

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

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

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

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

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

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

    
2205
        if (currentMapTool == null) {
2206
            return;
2207
        }
2208

    
2209
        if (currentMapTool instanceof CompoundBehavior) {
2210
            ((CompoundBehavior) currentMapTool).addMapBehavior(combinedTool,
2211
                true);
2212
        } else {
2213
            currentMapTool =
2214
                new CompoundBehavior(new Behavior[] { currentMapTool });
2215
            ((CompoundBehavior) currentMapTool).addMapBehavior(combinedTool,
2216
                true);
2217
        }
2218

    
2219
    }
2220

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

    
2247
                ((CompoundBehavior) combinedTool).addMapBehavior(tool, true);
2248
            } else {
2249
                if (combinedTool.equals(tool)) {
2250
                    return;
2251
                }
2252

    
2253
                combinedTool =
2254
                    new CompoundBehavior(new Behavior[] { combinedTool });
2255
                ((CompoundBehavior) combinedTool).addMapBehavior(tool, true);
2256
                combinedTool.setMapControl(this);
2257
            }
2258
        }
2259

    
2260
        if (currentMapTool == null) {
2261
            return;
2262
        }
2263

    
2264
        if (currentMapTool instanceof CompoundBehavior) {
2265
            ((CompoundBehavior) currentMapTool).addMapBehavior(tool, true);
2266
        } else {
2267
            currentMapTool =
2268
                new CompoundBehavior(new Behavior[] { currentMapTool });
2269
            ((CompoundBehavior) currentMapTool).addMapBehavior(tool, true);
2270
        }
2271
    }
2272

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

    
2285
        if (combinedTool == null) {
2286
            return;
2287
        }
2288

    
2289
        if (combinedTool instanceof CompoundBehavior) {
2290
            ((CompoundBehavior) combinedTool).removeMapBehavior(tool);
2291
        } else {
2292
            combinedTool = null;
2293
        }
2294
    }
2295

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

    
2321
        double dx = prefs.getDouble("grid.distancex", cadgrid.getGridSizeX());
2322
        double dy = prefs.getDouble("grid.distancey", cadgrid.getGridSizeY());
2323

    
2324
        setGridVisibility(showGrid);
2325
        setAdjustGrid(adjustGrid);
2326
        cadgrid.setGridSizeX(dx);
2327
        cadgrid.setGridSizeY(dy);
2328
    }
2329

    
2330
    public void setAdjustGrid(boolean adjustGrid) {
2331
        cadgrid.setAdjustGrid(adjustGrid);
2332
    }
2333

    
2334
    public void setGridVisibility(boolean showGrid) {
2335
        cadgrid.setShowGrid(showGrid);
2336
        cadgrid.setViewPort(getViewPort());
2337
        this.repaint();
2338
    }
2339

    
2340
    /**
2341
     * Uses like a mouse pointer the image that provides the
2342
     * selected tool.
2343
     *
2344
     */
2345

    
2346
    private Image lastImageCursor = null;
2347
    private Cursor lastCursor = null;
2348

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

    
2362
    /**
2363
     * Makes the mouse pointer transparent.
2364
     */
2365
    private void setTransparentMouse() {
2366
        setCursor(transparentCursor);
2367
        lastCursor = transparentCursor;
2368
    }
2369

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

    
2392
        if (usedSnap != null) {
2393
            usedSnap.draw(mapControlDrawer, adjustedPoint);
2394
            setTransparentMouse();
2395
        } else {
2396
            setToolMouse();
2397
        }
2398
    }
2399

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

    
2421
    private double adjustToHandler(Point2D point,
2422
        Point2D mapHandlerAdjustedPoint) {
2423

    
2424
        if (!isRefentEnabled())
2425
            return Double.MAX_VALUE;
2426

    
2427
        List layersToSnap = getMapContext().getLayersToSnap();
2428

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

    
2445
        usedSnap = null;
2446
        Point2D lastPoint = null;
2447
        if (previousPoint != null) {
2448
            lastPoint = new Point2D.Double(previousPoint[0], previousPoint[1]);
2449
        }
2450
        for (int j = 0; j < layersToSnap.size(); j++) {
2451
            FLyrVect lyrVect = (FLyrVect) layersToSnap.get(j);
2452
            SpatialCache cache = lyrVect.getSpatialCache();
2453
            if (lyrVect.isVisible()) {
2454
                // La lista de snappers est� siempre ordenada por prioridad. Los
2455
                // de mayor
2456
                // prioridad est�n primero.
2457
                List geoms = cache.query(r);
2458

    
2459
                for (int i = 0; i < mapControlManager.getSnapperCount(); i++)
2460
                {
2461
                    ISnapper theSnapper = mapControlManager.getSnapperAt(i);
2462
                    if (theSnapper instanceof ISnapperGeometriesVectorial)
2463
                    {
2464
                        ((ISnapperGeometriesVectorial)theSnapper).setGeometries(geoms);
2465
                    }
2466
                }
2467

    
2468
                for (int n = 0; n < geoms.size(); n++) {
2469
                    Geometry geom = (Geometry) geoms.get(n);
2470
                    for (int i = 0; i < mapControlManager.getSnapperCount(); i++) {
2471
                        ISnapper theSnapper = mapControlManager.getSnapperAt(i);
2472
                        if (!theSnapper.isEnabled())
2473
                            continue;
2474

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

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

    
2515
    }
2516

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

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

    
2551
        Point2D gridAdjustedPoint = vp.toMapPoint(point);
2552
        double minDistance = Double.MAX_VALUE;
2553

    
2554
        minDistance = cadgrid.adjustToGrid(gridAdjustedPoint);
2555
        if (minDistance < Double.MAX_VALUE) {
2556
            adjustedPoint = vp.fromMapPoint(gridAdjustedPoint);
2557
            mapAdjustedPoint = gridAdjustedPoint;
2558
        } else {
2559
            mapAdjustedPoint = null;
2560
        }
2561

    
2562
        Point2D handlerAdjustedPoint = null;
2563

    
2564
        // Se comprueba el ajuste a los handlers
2565
        if (mapAdjustedPoint != null) {
2566
            handlerAdjustedPoint = (Point2D) mapAdjustedPoint.clone(); // getMapControl().getViewPort().toMapPoint(point);
2567
        } else {
2568
            handlerAdjustedPoint = vp.toMapPoint(point);
2569
        }
2570

    
2571
        Point2D mapPoint = new Point2D.Double();
2572
        double distance = adjustToHandler(handlerAdjustedPoint, mapPoint);
2573

    
2574
        if (distance < minDistance) {
2575
            bForceCoord = true;
2576
            adjustedPoint = vp.fromMapPoint(mapPoint);
2577
            mapAdjustedPoint = mapPoint;
2578
            minDistance = distance;
2579
        }
2580

    
2581
        // Si no hay ajuste
2582
        if (minDistance == Double.MAX_VALUE) {
2583
            adjustedPoint = point;
2584
            mapAdjustedPoint = null;
2585
        }
2586
    }
2587

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

    
2601
    public Grid getGrid() {
2602
        return cadgrid;
2603
    }
2604

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

    
2614
    /**
2615
     * @return the adjustedPoint
2616
     */
2617
    public Point2D getAdjustedPoint() {
2618
        return adjustedPoint;
2619
    }
2620

    
2621
    /**
2622
     * @return the mapAdjustedPoint
2623
     */
2624
    public Point2D getMapAdjustedPoint() {
2625
        return mapAdjustedPoint;
2626
    }
2627

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

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