Statistics
| Revision:

svn-gvsig-desktop / tags / v2_0_0_Build_2060 / libraries / libFMap_controls / src / org / gvsig / fmap / mapcontrol / MapControl.java @ 39360

History | View | Annotate | Download (87.6 KB)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
415
    // private boolean isCancelled = true;
416

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

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

    
453
    /**
454
     * <p>
455
     * Manager of all <code>MapControl</code> painting requests.
456
     * </p>
457
     */
458
    private Drawer drawer;
459

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

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

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

    
498
    /**
499
     * <p>
500
     * Name of the previous tool used.
501
     * </p>
502
     */
503
    protected String prevTool;
504

    
505
    /**
506
     * <p>
507
     * Tool that will be used combined with the current tool of this
508
     * <code>MapControl</code>.
509
     * </p>
510
     */
511
    private Behavior combinedTool = null;
512

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

    
541
    /**
542
     * Kind of geometry drawn to identify the kind of control point selected by
543
     * the cursor's mouse.
544
     */
545
    private ISnapper usedSnap = null;
546

    
547
    /**
548
     * Determines if the snap tools are enabled or disabled.
549
     * 
550
     * @see #isRefentEnabled()
551
     * @see #setRefentEnabled(boolean)
552
     */
553
    private boolean bRefent = true;
554

    
555
    /**
556
     * Stores the 2D map coordinates of the last point added.
557
     */
558
    private double[] previousPoint = null;
559

    
560
    protected static MapControlManager mapControlManager =
561
        MapControlLocator.getMapControlManager();
562

    
563
    private static TreeMap selected = new TreeMap(new Comparator() {
564

    
565
        public int compare(Object o1, Object o2) {
566
            if (o1.getClass().equals(o2.getClass()))
567
                return 0;
568
            if (((ISnapper) o1).getPriority() > ((ISnapper) o2).getPriority())
569
                return 1;
570
            else
571
                return -1;
572
        }
573

    
574
    });
575

    
576
    /**
577
     * Represents the cursor's point selected in <i>map coordinates</i>.
578
     * 
579
     * @see MapControl#toMapPoint
580
     */
581
    private Point2D mapAdjustedPoint;
582

    
583
    /**
584
     * Renderer used to draw the layers.
585
     */
586
    private MapControlDrawer mapControlDrawer = null;
587
        private Cursor transparentCursor;
588
        
589
        private boolean disposed = false;
590

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

    
625
        setDoubleBuffered(false);
626
        setOpaque(true);
627
        status = DESACTUALIZADO;
628

    
629
        // Clase usada para cancelar el dibujado
630
        canceldraw = new CancelDraw();
631

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

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

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

    
654
                    public void actionPerformed(ActionEvent e) {
655

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

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

    
707
        mapContext = model;
708

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

    
716
        mapContext.addAtomicEventListener(mapContextListener);
717

    
718
        status = DESACTUALIZADO;
719
    }
720

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1341
                ViewPort viewPort = mapContext.getViewPort();
1342

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

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

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

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

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

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

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

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

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

    
1443
                private Thread worker;
1444

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1774
                currentMapTool.mouseWheelMoved(e);
1775

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1948
            }
1949

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
2228
    }
2229

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
2450
            // e = Converter.convertEnvelopeToJTS(r);
2451
            e = ((com.vividsolutions.jts.geom.Geometry) r.getGeometry().invokeOperation(ToJTS.CODE, null)).getEnvelopeInternal();
2452

    
2453
        } catch (Exception e1) {
2454
            LOG.error("Error creating the envelope", e1);
2455
        }
2456

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

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

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

    
2527
    }
2528

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

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

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

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

    
2574
        Point2D handlerAdjustedPoint = null;
2575

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

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

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

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

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

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

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

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

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

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

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