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 @ 41098

History | View | Annotate | Download (86.9 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.ArrayList;
47
import java.util.Comparator;
48
import java.util.HashMap;
49
import java.util.List;
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.slf4j.Logger;
60
import org.slf4j.LoggerFactory;
61

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

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

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

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

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

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

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

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

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

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

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

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

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

    
394
    // private boolean isCancelled = true;
395

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
553
    });
554

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

    
562
    /**
563
     * Renderer used to draw the layers.
564
     */
565
    private MapControlDrawer mapControlDrawer = null;
566
        private Cursor transparentCursor;
567
        
568
        private boolean disposed = false;
569

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

    
604
        setDoubleBuffered(false);
605
        setOpaque(true);
606
        status = DESACTUALIZADO;
607

    
608
        // Clase usada para cancelar el dibujado
609
        canceldraw = new CancelDraw();
610

    
611
        /*
612
         * We are not accessing the user preferences here.
613
         * This is an early initialization and is supposed
614
         * to be reset afterwards from a higher level (plugin)
615
         */
616
        MapContextManager mcm = MapContextLocator.getMapContextManager();
617
        vp = new ViewPort(mcm.getDefaultCRS());
618
        
619
        setMapContext(new MapContext(vp));
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
        addBehavior(name, tool);
819
    }
820

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

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

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

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

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

    
914
        // this.setCursor(mapTool.getCursor());
915
    }
916

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

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

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

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

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

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

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

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

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

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

    
1113
                        mapControlDrawer.drawImage(image, 0, 0);
1114

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

    
1122
    /**
1123
     * <p>
1124
     * Gets the {@link BufferedImage BufferedImage} used to accelerate the draw
1125
     * of new ''frames'' with changes, or new graphical items in this component.
1126
     * </p>
1127
     * 
1128
     * @return double buffered image used by this component to accelerate the
1129
     *         draw of its graphical information, or <code>null</code> if isn't
1130
     *         already created
1131
     * 
1132
     * @see BufferedImage
1133
     */
1134
    public BufferedImage getImage() {
1135
        return image;
1136
    }
1137

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

    
1176
    /**
1177
     * <p>
1178
     * Cancels any current drawing process, changing the status to
1179
     * <code>OUTDATED</code>, and forcing repaint only the layers dirty.
1180
     * </p>
1181
     * 
1182
     * @see #cancelDrawing()
1183
     */
1184
    public void rePaintDirtyLayers() {
1185
        cancelDrawing();
1186
        status = DESACTUALIZADO;
1187
        repaint();
1188
    }
1189

    
1190
    /**
1191
     * @deprecated use {@link #drawMap(boolean)} instead, or even
1192
     * better {@link #getMapContext()}.invalidate().
1193
     */
1194
    public void drawGraphics() {
1195
        drawMap(false);
1196
    }
1197

    
1198
    /**
1199
     * @see java.awt.event.ComponentListener#componentHidden(java.awt.event.ComponentEvent)
1200
     */
1201
    public void componentHidden(ComponentEvent e) {
1202
    }
1203

    
1204
    /**
1205
     * @see java.awt.event.ComponentListener#componentMoved(java.awt.event.ComponentEvent)
1206
     */
1207
    public void componentMoved(ComponentEvent e) {
1208
    }
1209

    
1210
    /**
1211
     * @see java.awt.event.ComponentListener#componentResized(java.awt.event.ComponentEvent)
1212
     */
1213
    public void componentResized(ComponentEvent e) {
1214
        /*
1215
         * image = new BufferedImage(this.getWidth(), this.getHeight(),
1216
         * BufferedImage.TYPE_INT_ARGB);
1217
         * Graphics gTemp = image.createGraphics();
1218
         * gTemp.setColor(vp.getBackColor());
1219
         * gTemp.fillRect(0,0,getWidth(), getHeight());
1220
         * System.out.println("MapControl resized");
1221
         * // image = null;
1222
         * vp.setImageSize(new Dimension(getWidth(), getHeight()));
1223
         * getMapContext().getViewPort().setScale();
1224
         */
1225
        // drawMap(true);
1226
    }
1227

    
1228
    /**
1229
     * @see java.awt.event.ComponentListener#componentShown(java.awt.event.ComponentEvent)
1230
     */
1231
    public void componentShown(ComponentEvent e) {
1232
    }
1233

    
1234
    /**
1235
     * @see ExceptionHandlingSupport#addExceptionListener(ExceptionListener)
1236
     */
1237
    public void addExceptionListener(ExceptionListener o) {
1238
        exceptionHandlingSupport.addExceptionListener(o);
1239
    }
1240

    
1241
    /**
1242
     * @see ExceptionHandlingSupport#removeExceptionListener(ExceptionListener)
1243
     */
1244
    public boolean removeExceptionListener(ExceptionListener o) {
1245
        return exceptionHandlingSupport.removeExceptionListener(o);
1246
    }
1247

    
1248
    /**
1249
     * @see ExceptionHandlingSupport#throwException(Throwable)
1250
     */
1251
    protected void throwException(Throwable t) {
1252
        exceptionHandlingSupport.throwException(t);
1253
    }
1254

    
1255
    /**
1256
     * <p>
1257
     * Represents each <code>MapControl</code>'s data painting request.
1258
     * </p>
1259
     * 
1260
     * <p>
1261
     * The request will be attended by a <code>Drawer2</code>, which will hold
1262
     * it since the <code>Drawer2</code>'s worker takes it, or arrives a new
1263
     * painting request, which will replace it.
1264
     * </p>
1265
     */
1266
    private class PaintingRequest {
1267

    
1268
        /**
1269
         * <p>
1270
         * Creates a new <code>PaintingRequest
1271
         * </p>
1272
         * instance.</p>
1273
         */
1274
        public PaintingRequest() {
1275
        }
1276

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

    
1320
                ViewPort viewPort = mapContext.getViewPort();
1321

    
1322
                if (status == DESACTUALIZADO) {
1323
                    Graphics2D gTemp = image.createGraphics();
1324
                    Color theBackColor = viewPort.getBackColor();
1325
                    if (theBackColor == null) {
1326
                        gTemp.setColor(Color.WHITE);
1327
                    } else {
1328
                        gTemp.setColor(theBackColor);
1329
                    }
1330
                    gTemp.fillRect(0, 0, viewPort.getImageWidth(), viewPort
1331
                        .getImageHeight());
1332
                    mapContext.draw(image, g, canceldraw, mapContext
1333
                        .getScaleView());
1334
                    if (!canceldraw.isCanceled()) {
1335
                        status = ACTUALIZADO;
1336
                    }
1337
                                }
1338

    
1339
                timer.stop();
1340
                repaint();
1341

    
1342
            } catch (Throwable e) {
1343
                timer.stop();
1344
                e.printStackTrace();
1345
                throwException(e);
1346
            } 
1347
        }
1348
    }
1349

    
1350
    /**
1351
     * <p>
1352
     * An instance of <code>Drawer2</code> could manage all
1353
     * <code>MapControl</code> painting requests.
1354
     * </p>
1355
     * 
1356
     * <p>
1357
     * Based on the <i>WorkerThread</i> software pattern, creates a worker
1358
     * thread that will attend sequentially the current waiting painting
1359
     * request, after finishing the previous (that could be by a cancel action).
1360
     * </p>
1361
     * 
1362
     * <p>
1363
     * All new {@link PaintingRequest PaintingRequest} generated will be stored
1364
     * as <i>waiting requests</i> since the worker attends it.
1365
     * </p>
1366
     * 
1367
     * <p>
1368
     * If a worker finished and there was no <i>painting request</i>, the worker
1369
     * would be set to wait until any <i>painting request</i> would be put.
1370
     * </p>
1371
     * 
1372
     * @author fjp
1373
     */
1374
    public class Drawer {
1375

    
1376
        // Una mini cola de 2. No acumulamos peticiones de dibujado
1377
        // dibujamos solo lo �ltimo que nos han pedido.
1378

    
1379
        /**
1380
         * <p>
1381
         * Painting request that's been attended by the <code>Drawer2</code>'s
1382
         * worker.
1383
         * </p>
1384
         * 
1385
         * @see #put(org.gvsig.fmap.mapcontrol.MapControl.PaintingRequest)
1386
         * @see #take()
1387
         */
1388
        private PaintingRequest paintingRequest;
1389

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

    
1401
        /**
1402
         * <p>
1403
         * Determines that the <code>Drawer2</code>'s worker is busy attending a
1404
         * painting request.
1405
         * </p>
1406
         * 
1407
         * @see #put(org.gvsig.fmap.mapcontrol.MapControl.PaintingRequest)
1408
         * @see #take()
1409
         */
1410
        private boolean waiting;
1411

    
1412
        /**
1413
         * <p>
1414
         * Notifies the <code>Drawer2</code>'s worker to finish or continue with
1415
         * its process.
1416
         * </p>
1417
         * 
1418
         * @see #setShutdown(boolean)
1419
         */
1420
        private boolean shutdown;
1421

    
1422
                private Thread worker;
1423

    
1424
        /**
1425
         * <p>
1426
         * Sets this <code>Drawer2</code>'s worker to finish or continue with
1427
         * its process.
1428
         * </p>
1429
         * 
1430
         * @param isShutdown
1431
         *            a boolean value
1432
         */
1433
        public void setShutdown(boolean isShutdown) {
1434
            shutdown = isShutdown;
1435
            if (shutdown) {
1436
                    worker.interrupt();
1437
            }
1438
        }
1439

    
1440
        /**
1441
         * <p>
1442
         * Creates a new drawer for managing all data painting requests in
1443
         * <code>MapControl</code>.
1444
         * </p>
1445
         * 
1446
         * <p>
1447
         * Includes the following steps:
1448
         * <ul>
1449
         * <li>By default, there is no <i>current painting request</i>.</li>
1450
         * <li>By default, there is no <i>waiting painting request</i>.</li>
1451
         * <li>By default, the worker thread is waiting no <i>painting
1452
         * request</i>.</li>
1453
         * <li>By default, the worker thread is running.</li>
1454
         * <li>Creates and starts a worker thread for attending the <i>painting
1455
         * requests</i>.</li>
1456
         * </ul>
1457
         * </p>
1458
         */
1459
        public Drawer() {
1460
            paintingRequest = null;
1461
            waitingRequest = null;
1462
            waiting = false;
1463
            shutdown = false;
1464
            worker = new Thread(new Worker(), "MapControl Drawer Worker");
1465
            worker.start();
1466
        }
1467

    
1468
        /**
1469
         * <p>
1470
         * Sets a <code>PaintingRequest</code> to be attended by the worker
1471
         * thread of this object. If this one was waiting, wakes up.
1472
         * </p>
1473
         * 
1474
         * <p>
1475
         * All waiting threads will be notified synchronized.
1476
         * </p>
1477
         * 
1478
         * @param newPaintRequest
1479
         * 
1480
         * @see #take()
1481
         */
1482
        public void put(PaintingRequest newPaintRequest) {
1483
            waitingRequest = newPaintRequest;
1484
            if (waiting) {
1485
                synchronized (this) {
1486
                    notifyAll();
1487
                }
1488
            }
1489
        }
1490

    
1491
        /**
1492
         * <p>
1493
         * Used by this object's worker, returns the current waiting drawing
1494
         * request, causing current thread to wait until another thread invokes
1495
         * {@link #put(org.gvsig.fmap.mapcontrol.MapControl.PaintingRequest)
1496
         * #put(com.iver.cit.gvsig.fmap.MapControl.PaintingRequest)}, if there
1497
         * was no waiting request.
1498
         * </p>
1499
         * 
1500
         * <p>
1501
         * All threads will access synchronized to the waiting request.
1502
         * </p>
1503
         * 
1504
         * @return <code>PaintingRequest</code> that was waiting to be attended
1505
         * 
1506
         * @see #put(org.gvsig.fmap.mapcontrol.MapControl.PaintingRequest)
1507
         */
1508
        public PaintingRequest take() {
1509
            if (waitingRequest == null) {
1510
                synchronized (this) {
1511
                    waiting = true;
1512
                    try {
1513
                        wait();
1514
                    } catch (InterruptedException ie) {
1515
                        waiting = false;
1516
                    }
1517
                }
1518
            }
1519
            paintingRequest = waitingRequest;
1520
            waitingRequest = null;
1521
            return paintingRequest;
1522
        }
1523

    
1524
        /**
1525
         * <p>
1526
         * Thread for attending painting requests.
1527
         * </p>
1528
         * 
1529
         * <p>
1530
         * If there was no double buffer, sets the status to
1531
         * <code>OUTDATED</code> and finishes, otherwise takes the painting
1532
         * request (it's probably that would wait some time), cancel the
1533
         * previous drawing process, and starts processing the request.
1534
         * </p>
1535
         * 
1536
         * @see Thread
1537
         */
1538
        private class Worker implements Runnable {
1539

    
1540
            /*
1541
             * (non-Javadoc)
1542
             * 
1543
             * @see java.lang.Runnable#run()
1544
             */
1545
            public void run() {
1546
                while (!shutdown) {
1547
                    PaintingRequest p = take();
1548
                    // System.out.println("Pintando");
1549
                    if (image != null) {
1550
                        cancelDrawing();
1551
                        if (p != null) {
1552
                                p.paint();
1553
                        }
1554
                    } else {
1555
                        status = DESACTUALIZADO;
1556
                    }
1557
                }
1558
            }
1559
        }
1560
    }
1561

    
1562
    /**
1563
     * <p>
1564
     * An instance of <code>CancelDraw</code> will be shared by all this
1565
     * <code>MapControl</code>'s <code>MapContext</code> layers, allowing
1566
     * receive a notification that, when they're been drawn, to be cancelled.
1567
     * </p>
1568
     * 
1569
     * @see Cancellable
1570
     * 
1571
     * @author Fernando Gonz�lez Cort�s
1572
     */
1573
    public class CancelDraw implements Cancellable {
1574

    
1575
        /**
1576
         * <p>
1577
         * Determines if the drawing task must be canceled or not.
1578
         * </p>
1579
         * 
1580
         * @see #isCanceled()
1581
         * @see #setCanceled(boolean)
1582
         */
1583
        private boolean cancel = false;
1584

    
1585
        /**
1586
         * Creates a new <code>CancelDraw</code> object.
1587
         */
1588
        public CancelDraw() {
1589
        }
1590

    
1591
        /*
1592
         * (non-Javadoc)
1593
         * 
1594
         * @see com.iver.utiles.swing.threads.Cancellable#setCanceled(boolean)
1595
         */
1596
        public void setCanceled(boolean b) {
1597
            cancel = b;
1598
        }
1599

    
1600
        /*
1601
         * (non-Javadoc)
1602
         * 
1603
         * @see com.iver.utiles.swing.threads.Cancellable#isCanceled()
1604
         */
1605
        public boolean isCanceled() {
1606
            return cancel;
1607
        }
1608
    }
1609

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

    
1639
        /**
1640
         * <p>
1641
         * Used to avoid mouse wheel move events closed.
1642
         * </p>
1643
         * 
1644
         * <p>
1645
         * If a mouse wheel move event is produced
1646
         */
1647
        long t1;
1648

    
1649
        /**
1650
         * <p>
1651
         * Position of the mouse, in map coordinates.
1652
         * </p>
1653
         * 
1654
         * <p>
1655
         * This point coordinates will be used as center of the <i>zoom</i>
1656
         * operation.
1657
         * </p>
1658
         */
1659
        Point2D pReal;
1660

    
1661
        /**
1662
         * @see java.awt.event.MouseListener#mouseClicked(java.awt.event.MouseEvent)
1663
         * @see Behavior#mouseClicked(MouseEvent)
1664
         */
1665
        public void mouseClicked(MouseEvent e) {
1666
            try {
1667
                if (currentMapTool != null) {
1668
                    currentMapTool.mouseClicked(e);
1669
                }
1670
                Point2D p;
1671

    
1672
                if (mapAdjustedPoint != null) {
1673
                    p = mapAdjustedPoint;
1674
                } else {
1675
                    p = vp.toMapPoint(adjustedPoint);
1676
                }            
1677
                previousPoint = new double[] { p.getX(), p.getY() };                
1678
            } catch (BehaviorException t) {
1679
                throwException(t);
1680
            }
1681
        }
1682

    
1683
        /**
1684
         * @see java.awt.event.MouseListener#mouseEntered(java.awt.event.MouseEvent)
1685
         * @see Behavior#mouseEntered(MouseEvent)
1686
         */
1687
        public void mouseEntered(MouseEvent e) {
1688
            setToolMouse();
1689
            try {
1690
                if (currentMapTool != null) {
1691
                    currentMapTool.mouseEntered(e);
1692
                }
1693
            } catch (BehaviorException t) {
1694
                throwException(t);
1695
            }
1696
        }
1697

    
1698
        /**
1699
         * @see java.awt.event.MouseListener#mouseExited(java.awt.event.MouseEvent)
1700
         * @see Behavior#mouseExited(MouseEvent)
1701
         */
1702
        public void mouseExited(MouseEvent e) {
1703
            try {
1704
                if (currentMapTool != null) {
1705
                    currentMapTool.mouseExited(e);
1706
                }
1707
            } catch (BehaviorException t) {
1708
                throwException(t);
1709
            }
1710
            // Remove the snapping image if exist
1711
            usedSnap = null;
1712
            repaint();
1713
        }
1714

    
1715
        /**
1716
         * @see java.awt.event.MouseListener#mousePressed(java.awt.event.MouseEvent)
1717
         * @see Behavior#mousePressed(MouseEvent)
1718
         */
1719
        public void mousePressed(MouseEvent e) {
1720
            try {
1721
                if (currentMapTool != null) {
1722
                    currentMapTool.mousePressed(e);
1723
                }
1724
            } catch (BehaviorException t) {
1725
                throwException(t);
1726
            }
1727
        }
1728

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

    
1743
        /**
1744
         * @see java.awt.event.MouseWheelListener#mouseWheelMoved(java.awt.event.MouseWheelEvent)
1745
         * @see Behavior#mouseWheelMoved(MouseWheelEvent)
1746
         */
1747
        public void mouseWheelMoved(MouseWheelEvent e) {
1748
            try {
1749
                if (currentMapTool == null) {
1750
                    return;
1751
                }
1752

    
1753
                currentMapTool.mouseWheelMoved(e);
1754

    
1755
                // Si el tool actual no ha consumido el evento
1756
                // entendemos que quiere el comportamiento por defecto.
1757
                if (!e.isConsumed()) {
1758
                    // Para usar el primer punto sobre el que queremos centrar
1759
                    // el mapa, dejamos pasar un segundo para considerar el
1760
                    // siguiente
1761
                    // punto como v�lido.
1762
                    if (t1 == 0) {
1763
                        t1 = System.currentTimeMillis();
1764
                        pReal = vp.toMapPoint(e.getPoint());
1765
                    } else {
1766
                        long t2 = System.currentTimeMillis();
1767
                        if ((t2 - t1) > 1000) {
1768
                            t1 = 0;
1769
                        }
1770
                    }
1771
                    cancelDrawing();
1772
                    ViewPort vp = getViewPort();
1773

    
1774
                    /*
1775
                     * Point2D pReal = new
1776
                     * Point2D.Double(vp.getAdjustedExtent().getCenterX(),
1777
                     * vp.getAdjustedExtent().getCenterY());
1778
                     */
1779
                    int amount = e.getWheelRotation();
1780
                    double nuevoX;
1781
                    double nuevoY;
1782
                    double factor;
1783

    
1784
                    if (amount < 0) // nos acercamos
1785
                    {
1786
                        factor = 0.9;
1787
                    } else // nos alejamos
1788
                    {
1789
                        factor = 1.2;
1790
                    }
1791
                    if (vp.getExtent() != null) {
1792
                        nuevoX =
1793
                            pReal.getX()
1794
                                - ((vp.getExtent().getWidth() * factor) / 2.0);
1795
                        nuevoY =
1796
                            pReal.getY()
1797
                                - ((vp.getExtent().getHeight() * factor) / 2.0);
1798
                        double x = nuevoX;
1799
                        double y = nuevoY;
1800
                        double width = vp.getExtent().getWidth() * factor;
1801
                        double height = vp.getExtent().getHeight() * factor;
1802

    
1803
                        try {
1804
                            vp.setEnvelope(geomManager.createEnvelope(x, y, x
1805
                                + width, y + height, SUBTYPES.GEOM2D));
1806
                        } catch (CreateEnvelopeException e1) {
1807
                            LOG.info("Error creating the envelope", e);
1808
                        }
1809
                    }
1810

    
1811
                }
1812
            } catch (BehaviorException t) {
1813
                throwException(t);
1814
            }
1815
        }
1816

    
1817
        /**
1818
         * @see java.awt.event.MouseMotionListener#mouseDragged(java.awt.event.MouseEvent)
1819
         * @see Behavior#mouseDragged(MouseEvent)
1820
         */
1821
        public void mouseDragged(MouseEvent e) {
1822
            calculateSnapPoint(e.getPoint());
1823
            try {
1824
                if (currentMapTool != null) {
1825
                    currentMapTool.mouseDragged(e);
1826
                }
1827
            } catch (BehaviorException t) {
1828
                throwException(t);
1829
            }
1830
            repaint();
1831
        }
1832

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

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

    
1877
        /**
1878
         * @see org.gvsig.fmap.mapcontext.events.listeners.AtomicEventListener#atomicEvent(org.gvsig.fmap.mapcontext.events.AtomicEvent)
1879
         */
1880
        public void atomicEvent(final AtomicEvent e) {
1881
            if (!SwingUtilities.isEventDispatchThread()) {
1882
                SwingUtilities.invokeLater(new Runnable() {
1883

    
1884
                    public void run() {
1885
                        atomicEvent(e);
1886
                    }
1887
                });
1888
                return;
1889
            }
1890
            LayerEvent[] layerEvents = e.getLayerEvents();
1891

    
1892
            for (int i = 0; i < layerEvents.length; i++) {
1893
                if (layerEvents[i].getProperty().equals("visible")) {
1894
                    drawMap(false);
1895
                    return;
1896
                } else
1897
                    if (layerEvents[i].getEventType() == LayerEvent.EDITION_CHANGED) {
1898
                        drawMap(false);
1899
                        return;
1900
                    }
1901
            }
1902

    
1903
            if (e.getColorEvents().length > 0) {
1904
                drawMap(false);
1905
                return;
1906
            }
1907

    
1908
            if (e.getExtentEvents().length > 0) {
1909
                drawMap(false);
1910
                return;
1911
            }
1912

    
1913
            if (e.getProjectionEvents().length > 0) {
1914
                // redraw = true;
1915
            }
1916

    
1917
            LayerCollectionEvent[] aux = e.getLayerCollectionEvents();
1918
            if (aux.length > 0) {
1919
                for (int i = 0; i < aux.length; i++) {
1920
                    if (aux[i].getAffectedLayer().getFLayerStatus()
1921
                        .isDriverLoaded()) {
1922
                        drawMap(false);
1923
                        return;
1924
                    }
1925
                }
1926

    
1927
            }
1928

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

    
1934
            if (e.getSelectionEvents().length > 0) {
1935
                drawMap(false);
1936
                return;
1937
            }
1938
        }
1939
    }
1940

    
1941
    /**
1942
     * <p>
1943
     * Gets the <code>ViewPort</code> of this component's {@link MapContext
1944
     * MapContext} .
1945
     * </p>
1946
     * 
1947
     * @see MapContext#getViewPort()
1948
     */
1949
    public ViewPort getViewPort() {
1950
        return vp;
1951
    }
1952

    
1953
    /**
1954
     * <p>
1955
     * Returns all registered <code>Behavior</code> that can define a way to
1956
     * work with this <code>MapControl</code>.
1957
     * </p>
1958
     * 
1959
     * @return registered <code>Behavior</code> to this <code>MapControl</code>
1960
     * 
1961
     * @see #addBehavior(String, Behavior)
1962
     * @see #addBehavior(String, Behavior[])
1963
     * @see #getMapToolsKeySet()
1964
     * @see #hasTool(String)
1965
     */
1966
    public HashMap getNamesMapTools() {
1967
        return namesMapTools;
1968
    }
1969

    
1970
    /*
1971
     * (non-Javadoc)
1972
     * 
1973
     * @see
1974
     * com.iver.cit.gvsig.fmap.edition.commands.CommandListener#commandRepaint()
1975
     */
1976
    public void commandRepaint() {
1977
        drawMap(false);
1978
    }
1979

    
1980
    /*
1981
     * (non-Javadoc)
1982
     * 
1983
     * @see
1984
     * com.iver.cit.gvsig.fmap.edition.commands.CommandListener#commandRefresh()
1985
     */
1986
    public void commandRefresh() {
1987
        // TODO Auto-generated method stub
1988
    }
1989

    
1990
    /**
1991
     * <p>
1992
     * Equivalent operation to <i>undo</i>.
1993
     * </p>
1994
     * 
1995
     * <p>
1996
     * Exchanges the previous tool with the current one.
1997
     * </p>
1998
     * 
1999
     * @see #addBehavior(String, Behavior)
2000
     * @see #addBehavior(String, Behavior[])
2001
     * @see #setTool(String)
2002
     */
2003
    public void setPrevTool() {
2004
        setTool(prevTool);
2005
    }
2006

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

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

    
2074
    /**
2075
     * <p>
2076
     * Returns the listener used to catch all mouse events produced in this
2077
     * <code>MapControl</code> instance and that redirects the calls to the
2078
     * current map tool.
2079
     * </p>
2080
     * 
2081
     * @return the map tool listener used
2082
     */
2083
    public MapToolListener getMapToolListener() {
2084
        return mapToolListener;
2085
    }
2086

    
2087
    // mapTool can be null, for instance, in 3D's navigation tools
2088
    /**
2089
     * <p>
2090
     * Sets <code>mapTool</code> as this <code>MapControl</code>'s current map
2091
     * tool.
2092
     * 
2093
     * @param mapTool
2094
     *            a map tool, or <code>null</code> to disable the interaction
2095
     *            with the user
2096
     * 
2097
     * @see #getCurrentMapTool()
2098
     * @see #getCurrentTool()
2099
     * @see #setTool(String)
2100
     * @see #setPrevTool()
2101
     * @see #addBehavior(String, Behavior)
2102
     * @see #addBehavior(String, Behavior[])
2103
     */
2104
    public void setCurrentMapTool(Behavior mapTool) {
2105
        currentMapTool = mapTool;
2106
    }
2107

    
2108
    /**
2109
     * <p>
2110
     * Sets the delay to the timer that refreshes this <code>MapControl</code>
2111
     * instance.
2112
     * </p>
2113
     * 
2114
     * <p>
2115
     * <code>Delay (in ms) = 1000 / getDrawFrameRate()</code>
2116
     * </p>
2117
     * 
2118
     * @see #getDrawFrameRate()
2119
     * @see #setDrawFrameRate(int)
2120
     */
2121
    public void applyFrameRate() {
2122
        if (MapContext.getDrawFrameRate() > 0) {
2123
            timer.setDelay(1000 / MapContext.getDrawFrameRate());
2124
        }
2125
    }
2126

    
2127
    /**
2128
     * <p>
2129
     * Determines if its enabled the repaint that invokes the timer according to
2130
     * {@link #getDrawFrameRate() #getDrawFrameRate()}.
2131
     * </p>
2132
     * 
2133
     * @return <code>true</code> if its enabled; otherwise <code>false</code>
2134
     */
2135
    public static boolean isDrawAnimationEnabled() {
2136
        return drawAnimationEnabled;
2137
    }
2138

    
2139
    /**
2140
     * <p>
2141
     * Sets if its enabled the repaint that invokes the timer according to
2142
     * {@link #getDrawFrameRate() #getDrawFrameRate()}.
2143
     * </p>
2144
     * 
2145
     * @param drawAnimationEnabled
2146
     *            <code>true</code> to enable the mode; otherwise
2147
     *            <code>false</code>
2148
     */
2149
    public static void setDrawAnimationEnabled(boolean drawAnimationEnabled) {
2150
        MapControl.drawAnimationEnabled = drawAnimationEnabled;
2151
    }
2152

    
2153
    /**
2154
     * <p>
2155
     * Gets the shared object that determines if a drawing process must be
2156
     * cancelled or can continue.
2157
     * </p>
2158
     * 
2159
     * @return the shared object that determines if a drawing process must be
2160
     *         cancelled or can continue
2161
     */
2162
    public CancelDraw getCanceldraw() {
2163
        return canceldraw;
2164
    }
2165

    
2166
    /**
2167
     * <p>
2168
     * Gets the tool used in combination with the current tool of this
2169
     * <code>MapControl</code>.
2170
     * </p>
2171
     * 
2172
     * @return the tool used in combination with the <code>currentMapTool</code>
2173
     *         ; <code>null</code> if there is
2174
     *         no combined tool
2175
     */
2176
    public Behavior getCombinedTool() {
2177
        return combinedTool;
2178
    }
2179

    
2180
    /**
2181
     * <p>
2182
     * Sets a tool to be used in combination with the current tool of this
2183
     * <code>MapControl</code>.
2184
     * </p>
2185
     * 
2186
     * @param combinedTool
2187
     *            a tool to be used in combination with the current tool of
2188
     *            <code>MapControl</code>
2189
     */
2190
    public void setCombinedTool(Behavior combinedTool) {
2191
        this.combinedTool = combinedTool;
2192

    
2193
        if (currentMapTool == null) {
2194
            return;
2195
        }
2196

    
2197
        if (currentMapTool instanceof CompoundBehavior) {
2198
            ((CompoundBehavior) currentMapTool).addMapBehavior(combinedTool,
2199
                true);
2200
        } else {
2201
            currentMapTool =
2202
                new CompoundBehavior(new Behavior[] { currentMapTool });
2203
            ((CompoundBehavior) currentMapTool).addMapBehavior(combinedTool,
2204
                true);
2205
        }
2206

    
2207
    }
2208

    
2209
    /**
2210
     * <p>
2211
     * Adds a new tool as combined tool.
2212
     * </p>
2213
     * <p>
2214
     * The new tool will be stored with the previous combined tools, and will be
2215
     * combined with the current tool.
2216
     * </p>
2217
     * <p>
2218
     * If <code>tool</code> was already stored as a combined tool, doesn't adds
2219
     * it.
2220
     * </p>
2221
     * 
2222
     * @param tool
2223
     *            a new tool to be used combined with the current tool
2224
     */
2225
    public void addCombinedBehavior(Behavior tool) {
2226
        tool.setMapControl(this);
2227
        if (combinedTool == null) {
2228
            combinedTool = tool;
2229
        } else {
2230
            if (combinedTool instanceof CompoundBehavior) {
2231
                if (((CompoundBehavior) combinedTool).containsBehavior(tool)) {
2232
                    return;
2233
                }
2234

    
2235
                ((CompoundBehavior) combinedTool).addMapBehavior(tool, true);
2236
            } else {
2237
                if (combinedTool.equals(tool)) {
2238
                    return;
2239
                }
2240

    
2241
                combinedTool =
2242
                    new CompoundBehavior(new Behavior[] { combinedTool });
2243
                ((CompoundBehavior) combinedTool).addMapBehavior(tool, true);
2244
            }
2245
        }
2246

    
2247
        if (currentMapTool == null) {
2248
            return;
2249
        }
2250

    
2251
        if (currentMapTool instanceof CompoundBehavior) {
2252
            ((CompoundBehavior) currentMapTool).addMapBehavior(tool, true);
2253
        } else {
2254
            currentMapTool =
2255
                new CompoundBehavior(new Behavior[] { currentMapTool });
2256
            ((CompoundBehavior) currentMapTool).addMapBehavior(tool, true);
2257
        }
2258
    }
2259

    
2260
    /**
2261
     * <p>
2262
     * Removes the tool <code>tool</code> used in combination with the current
2263
     * tool of this <code>MapControl</code>.
2264
     * </p>
2265
     */
2266
    public void removeCombinedTool(Behavior tool) {
2267
        if ((currentMapTool != null)
2268
            && (currentMapTool instanceof CompoundBehavior)) {
2269
            ((CompoundBehavior) currentMapTool).removeMapBehavior(tool);
2270
        }
2271

    
2272
        if (combinedTool == null) {
2273
            return;
2274
        }
2275

    
2276
        if (combinedTool instanceof CompoundBehavior) {
2277
            ((CompoundBehavior) combinedTool).removeMapBehavior(tool);
2278
        } else {
2279
            combinedTool = null;
2280
        }
2281
    }
2282

    
2283
    /**
2284
     * <p>
2285
     * Updates the grid on the <code>ViewPort</code> of the associated
2286
     * <code>MapControl</code> object according the values in the
2287
     * {@link com.iver.cit.gvsig.gui.cad.CADToolAdapter.prefs.Preferences
2288
     * com.iver.cit.gvsig.gui.cad.CADToolAdapter.prefs.Preferences}.
2289
     * </p>
2290
     * 
2291
     * <p>
2292
     * The preferences are:
2293
     * <ul>
2294
     * <li>Show/hide the grid.</li>
2295
     * <li>Adjust or not the grid.</li>
2296
     * <li>Horizontal ( X ) line separation.</li>
2297
     * <li>Vertical ( Y ) line separation.</li>
2298
     * </ul>
2299
     * </p>
2300
     */
2301
    public void initializeGrid() {
2302
        Preferences prefs = mapControlManager.getEditionPreferences();
2303
        boolean showGrid =
2304
            prefs.getBoolean("grid.showgrid", cadgrid.isShowGrid());
2305
        boolean adjustGrid =
2306
            prefs.getBoolean("grid.adjustgrid", cadgrid.isAdjustGrid());
2307

    
2308
        double dx = prefs.getDouble("grid.distancex", cadgrid.getGridSizeX());
2309
        double dy = prefs.getDouble("grid.distancey", cadgrid.getGridSizeY());
2310

    
2311
        setGridVisibility(showGrid);
2312
        setAdjustGrid(adjustGrid);
2313
        cadgrid.setGridSizeX(dx);
2314
        cadgrid.setGridSizeY(dy);
2315
    }
2316

    
2317
    public void setAdjustGrid(boolean adjustGrid) {
2318
        cadgrid.setAdjustGrid(adjustGrid);
2319
    }
2320

    
2321
    public void setGridVisibility(boolean showGrid) {
2322
        cadgrid.setShowGrid(showGrid);
2323
        cadgrid.setViewPort(getViewPort());
2324
        this.repaint();
2325
    }
2326

    
2327
    /**
2328
     * Uses like a mouse pointer the image that provides the
2329
     * selected tool.
2330
     * 
2331
     */
2332

    
2333
    private Image lastImageCursor = null;
2334
    private Cursor lastCursor = null;
2335

    
2336
    private void setToolMouse() {
2337
        Image imageCursor = getCurrentMapTool().getImageCursor();
2338
        if (imageCursor != null && imageCursor == lastImageCursor && lastCursor != null && lastCursor != transparentCursor) {
2339
            setCursor(lastCursor);
2340
            return;
2341
        }
2342
        lastImageCursor = imageCursor;
2343
        Toolkit toolkit = Toolkit.getDefaultToolkit();
2344
        Cursor c = toolkit.createCustomCursor(imageCursor, new Point(16, 16), "img");
2345
        setCursor(c);
2346
        lastCursor = c;
2347
    }
2348

    
2349
    /**
2350
     * Makes the mouse pointer transparent.
2351
     */
2352
    private void setTransparentMouse() {
2353
        setCursor(transparentCursor);
2354
        lastCursor = transparentCursor;
2355
    }
2356

    
2357
    /**
2358
     * <p>
2359
     * Draws a 31x31 pixels cross round the mouse's cursor with an small
2360
     * geometry centered:
2361
     * <ul>
2362
     * <li><i>an square centered</i>: if isn't over a <i>control point</i>.
2363
     * <li><i>an small geometry centered according to the kind of control
2364
     * point</i>: if it's over a control point. In this case, the small geometry
2365
     * is drawn by a {@link ISnapper ISnapper} type object.<br>
2366
     * On the other hand, a light-yellowed background tool tip text with the
2367
     * type of <i>control point</i> will be displayed.</li>
2368
     * </p>
2369
     * 
2370
     * @param g
2371
     *            <code>MapControl</code>'s graphics where the data will be
2372
     *            drawn
2373
     */
2374
    private void drawCursor() {
2375
        if (adjustedPoint == null) {
2376
            return;
2377
        }
2378

    
2379
        if (usedSnap != null) {
2380
            usedSnap.draw(mapControlDrawer, adjustedPoint);
2381
            setTransparentMouse();
2382
        } else {
2383
            setToolMouse();
2384
        }
2385
    }
2386

    
2387
    /**
2388
     * <p>
2389
     * Adjusts the <code>point</code> to the grid if its enabled, and sets
2390
     * <code>mapHandlerAdjustedPoint</code> with that new value.
2391
     * </p>
2392
     * 
2393
     * <p>
2394
     * The value returned is the distance between those points: the original and
2395
     * the adjusted one.
2396
     * </p>
2397
     * 
2398
     * @param point
2399
     *            point to adjust
2400
     * @param mapHandlerAdjustedPoint
2401
     *            <code>point</code> adjusted
2402
     * 
2403
     * @return distance from <code>point</code> to the adjusted one. If there is
2404
     *         no
2405
     *         adjustment, returns <code>Double.MAX_VALUE</code>.
2406
     */
2407

    
2408
    private double adjustToHandler(Point2D point,
2409
        Point2D mapHandlerAdjustedPoint) {
2410

    
2411
        if (!isRefentEnabled())
2412
            return Double.MAX_VALUE;
2413

    
2414
        List layersToSnap = getMapContext().getLayersToSnap();
2415

    
2416
        double mapTolerance =
2417
            vp.toMapDistance(mapControlManager.getTolerance());
2418
        double minDist = mapTolerance;
2419
        double middleTol = mapTolerance * 0.5;
2420
        Point2D mapPoint = point;
2421
        Envelope r = null;
2422
        try {
2423
            r =
2424
                geomManager.createEnvelope(mapPoint.getX() - middleTol,
2425
                    mapPoint.getY() - middleTol, mapPoint.getX() + middleTol,
2426
                    mapPoint.getY() + middleTol, SUBTYPES.GEOM2D);
2427
        } catch (Exception e1) {
2428
            LOG.info("Error creating the envelope", e1);
2429
            return Double.MAX_VALUE;
2430
        }
2431

    
2432
        usedSnap = null;
2433
        Point2D lastPoint = null;
2434
        if (previousPoint != null) {
2435
            lastPoint = new Point2D.Double(previousPoint[0], previousPoint[1]);
2436
        }
2437
        for (int j = 0; j < layersToSnap.size(); j++) {
2438
            FLyrVect lyrVect = (FLyrVect) layersToSnap.get(j);
2439
            SpatialCache cache = lyrVect.getSpatialCache();
2440
            if (lyrVect.isVisible()) {
2441
                // La lista de snappers est� siempre ordenada por prioridad. Los
2442
                // de mayor
2443
                // prioridad est�n primero.
2444
                List geoms = cache.query(r);
2445
                
2446
                for (int i = 0; i < mapControlManager.getSnapperCount(); i++)
2447
                {
2448
                    ISnapper theSnapper = mapControlManager.getSnapperAt(i);
2449
                    if (theSnapper instanceof ISnapperGeometriesVectorial)
2450
                    {
2451
                        ((ISnapperGeometriesVectorial)theSnapper).setGeometries(geoms);                        
2452
                    }
2453
                } 
2454
                
2455
                for (int n = 0; n < geoms.size(); n++) {
2456
                    Geometry geom = (Geometry) geoms.get(n);
2457
                    for (int i = 0; i < mapControlManager.getSnapperCount(); i++) {
2458
                        ISnapper theSnapper = mapControlManager.getSnapperAt(i);
2459
                        if (!theSnapper.isEnabled())
2460
                            continue;
2461

    
2462
                        if (usedSnap != null) {
2463
                            // Si ya tenemos un snap y es de alta prioridad,
2464
                            // cogemos ese. (A no ser que en otra capa
2465
                            // encontremos un snapper mejor)
2466
                            if (theSnapper.getPriority() > usedSnap
2467
                                .getPriority())
2468
                                break;
2469
                        }
2470
                        // SnappingVisitor snapVisitor = null;
2471
                        Point2D theSnappedPoint = null;
2472
                        if (theSnapper instanceof ISnapperVectorial) {
2473
                            theSnappedPoint =
2474
                                ((ISnapperVectorial) theSnapper).getSnapPoint(
2475
                                    point, geom, mapTolerance, lastPoint);
2476
                        }
2477
                        if (theSnapper instanceof ISnapperRaster) {
2478
                            ISnapperRaster snapRaster =
2479
                                (ISnapperRaster) theSnapper;
2480
                            theSnappedPoint =
2481
                                snapRaster.getSnapPoint(this, point,
2482
                                    mapTolerance, lastPoint);
2483
                        }
2484

    
2485
                        if (theSnappedPoint != null) {
2486
                            double distAux = theSnappedPoint.distance(point);
2487
                            if (minDist > distAux) {
2488
                                minDist = distAux;
2489
                                usedSnap = theSnapper;
2490
                                mapHandlerAdjustedPoint
2491
                                    .setLocation(theSnappedPoint);
2492
                            }
2493
                        }
2494
                    }
2495
                } // for n
2496
            } // visible
2497
        }
2498
        if (usedSnap != null)
2499
            return minDist;
2500
        return Double.MAX_VALUE;
2501

    
2502
    }
2503

    
2504
    /**
2505
     * Determines if snap tools are enabled or disabled.
2506
     * 
2507
     * @return <code>true</code> to enable the snap tools; <code>false</code> to
2508
     *         disable them
2509
     * 
2510
     * @see #setRefentEnabled(boolean)
2511
     */
2512
    public boolean isRefentEnabled() {
2513
        return bRefent;
2514
    }
2515

    
2516
    /**
2517
     * <p>
2518
     * Tries to find the nearest geometry or grid control point by the position
2519
     * of the current snap tool.
2520
     * </p>
2521
     * 
2522
     * <p>
2523
     * Prioritizes the grid control points than the geometries ones.
2524
     * </p>
2525
     * 
2526
     * <p>
2527
     * If finds any near, stores the <i>map</i> and <i>pixel</i> coordinates for
2528
     * the snap, and enables the <code>bForceCoord</code> attribute for the next
2529
     * draw of the mouse's cursor.
2530
     * </p>
2531
     * 
2532
     * @param point
2533
     *            current mouse 2D position
2534
     */
2535
    public void calculateSnapPoint(Point point) {
2536
        // Se comprueba el ajuste a rejilla
2537

    
2538
        Point2D gridAdjustedPoint = vp.toMapPoint(point);
2539
        double minDistance = Double.MAX_VALUE;
2540

    
2541
        minDistance = cadgrid.adjustToGrid(gridAdjustedPoint);
2542
        if (minDistance < Double.MAX_VALUE) {
2543
            adjustedPoint = vp.fromMapPoint(gridAdjustedPoint);
2544
            mapAdjustedPoint = gridAdjustedPoint;
2545
        } else {
2546
            mapAdjustedPoint = null;
2547
        }
2548

    
2549
        Point2D handlerAdjustedPoint = null;
2550

    
2551
        // Se comprueba el ajuste a los handlers
2552
        if (mapAdjustedPoint != null) {
2553
            handlerAdjustedPoint = (Point2D) mapAdjustedPoint.clone(); // getMapControl().getViewPort().toMapPoint(point);
2554
        } else {
2555
            handlerAdjustedPoint = vp.toMapPoint(point);
2556
        }
2557

    
2558
        Point2D mapPoint = new Point2D.Double();
2559
        double distance = adjustToHandler(handlerAdjustedPoint, mapPoint);
2560

    
2561
        if (distance < minDistance) {
2562
            bForceCoord = true;
2563
            adjustedPoint = vp.fromMapPoint(mapPoint);
2564
            mapAdjustedPoint = mapPoint;
2565
            minDistance = distance;
2566
        }
2567

    
2568
        // Si no hay ajuste
2569
        if (minDistance == Double.MAX_VALUE) {
2570
            adjustedPoint = point;
2571
            mapAdjustedPoint = null;
2572
        }
2573
    }
2574

    
2575
    /**
2576
     * Sets the snap tools enabled or disabled.
2577
     * 
2578
     * @param activated
2579
     *            <code>true</code> to enable the snap tools; <code>false</code>
2580
     *            to disable them
2581
     * 
2582
     * @see #isRefentEnabled()
2583
     */
2584
    public void setRefentEnabled(boolean activated) {
2585
        bRefent = activated;
2586
    }
2587

    
2588
    public Grid getGrid() {
2589
        return cadgrid;
2590
    }
2591

    
2592
    public void update(Observable observable, Object notification) {
2593
        DataStoreNotification ddsn = (DataStoreNotification) notification;
2594
        String type = ddsn.getType();
2595
        if (type.equals(FeatureStoreNotification.AFTER_UNDO)
2596
            || type.equals(FeatureStoreNotification.AFTER_REDO)) {
2597
            repaint();
2598
        }
2599
    }
2600

    
2601
    /**
2602
     * @return the adjustedPoint
2603
     */
2604
    public Point2D getAdjustedPoint() {
2605
        return adjustedPoint;
2606
    }
2607

    
2608
    /**
2609
     * @return the mapAdjustedPoint
2610
     */
2611
    public Point2D getMapAdjustedPoint() {
2612
        return mapAdjustedPoint;
2613
    }
2614

    
2615
    /**
2616
     * Gets the selected point. If the snapping is enabled
2617
     * it returns the selected point.
2618
     * 
2619
     * @return
2620
     *         The selected point
2621
     */
2622
    public Point2D getPoint() {
2623
        if (mapAdjustedPoint != null) {
2624
            return mapAdjustedPoint;
2625
        } else {
2626
            return getViewPort().toMapPoint(adjustedPoint);
2627
        }
2628
    }
2629

    
2630
        public synchronized void dispose() {
2631
                // Check if we have already been disposed, and don't do it again
2632
                if (!disposed && ToolsLocator.getDisposableManager().release(this)) {
2633
                        drawer.setShutdown(true);
2634
                        disposed = true;
2635
                }
2636
        }
2637
}