svn-gvsig-desktop / trunk / extensions / extGraph_predes / src / com / iver / cit / gvsig / graph / gui / ImageView.java @ 8264
History | View | Annotate | Download (27.2 KB)
1 | 8215 | azabala | /*
|
---|---|---|---|
2 | * Created on 20-oct-2006
|
||
3 | *
|
||
4 | * gvSIG. Sistema de Informaci?n Geogr?fica de la Generalitat Valenciana
|
||
5 | *
|
||
6 | * Copyright (C) 2004 IVER T.I. and Generalitat Valenciana.
|
||
7 | *
|
||
8 | * This program is free software; you can redistribute it and/or
|
||
9 | * modify it under the terms of the GNU General Public License
|
||
10 | * as published by the Free Software Foundation; either version 2
|
||
11 | * of the License, or (at your option) any later version.
|
||
12 | *
|
||
13 | * This program is distributed in the hope that it will be useful,
|
||
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
16 | * GNU General Public License for more details.
|
||
17 | *
|
||
18 | * You should have received a copy of the GNU General Public License
|
||
19 | * along with this program; if not, write to the Free Software
|
||
20 | * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,USA.
|
||
21 | *
|
||
22 | * For more information, contact:
|
||
23 | *
|
||
24 | * Generalitat Valenciana
|
||
25 | * Conselleria d'Infraestructures i Transport
|
||
26 | * Av. Blasco Ib??ez, 50
|
||
27 | * 46010 VALENCIA
|
||
28 | * SPAIN
|
||
29 | *
|
||
30 | * +34 963862235
|
||
31 | * gvsig@gva.es
|
||
32 | * www.gvsig.gva.es
|
||
33 | *
|
||
34 | * or
|
||
35 | *
|
||
36 | * IVER T.I. S.A
|
||
37 | * Salamanca 50
|
||
38 | * 46005 Valencia
|
||
39 | * Spain
|
||
40 | *
|
||
41 | * +34 963163400
|
||
42 | * dac@iver.es
|
||
43 | */
|
||
44 | /* CVS MESSAGES:
|
||
45 | *
|
||
46 | * $Id$
|
||
47 | * $Log$
|
||
48 | * Revision 1.1 2006-10-20 19:54:01 azabala
|
||
49 | * *** empty log message ***
|
||
50 | *
|
||
51 | *
|
||
52 | */
|
||
53 | package com.iver.cit.gvsig.graph.gui; |
||
54 | |||
55 | import java.awt.*; |
||
56 | import java.awt.event.*; |
||
57 | import java.awt.image.ImageObserver; |
||
58 | import java.io.*; |
||
59 | import java.net.*; |
||
60 | import java.util.Dictionary; |
||
61 | import javax.swing.*; |
||
62 | import javax.swing.text.*; |
||
63 | import javax.swing.text.html.*; |
||
64 | import javax.swing.event.*; |
||
65 | |||
66 | import com.iver.cit.gvsig.geoprocess.wizard.GeoProcessingExtension; |
||
67 | |||
68 | public class ImageView extends View implements ImageObserver, MouseListener, MouseMotionListener{ |
||
69 | |||
70 | // --- Attribute Values ------------------------------------------
|
||
71 | |||
72 | public static final String |
||
73 | TOP = "top",
|
||
74 | TEXTTOP = "texttop",
|
||
75 | MIDDLE = "middle",
|
||
76 | ABSMIDDLE = "absmiddle",
|
||
77 | CENTER = "center",
|
||
78 | BOTTOM = "bottom";
|
||
79 | |||
80 | Class pluginEnvironmentClass;
|
||
81 | // --- Construction ----------------------------------------------
|
||
82 | |||
83 | /**
|
||
84 | * Creates a new view that represents an IMG element.
|
||
85 | *
|
||
86 | * @param elem the element to create a view for
|
||
87 | */
|
||
88 | public ImageView(Element elem, Class aClass) { |
||
89 | super(elem);
|
||
90 | this.pluginEnvironmentClass = aClass;
|
||
91 | initialize(elem); |
||
92 | StyleSheet sheet = getStyleSheet();
|
||
93 | attr = sheet.getViewAttributes(this);
|
||
94 | |||
95 | } |
||
96 | |||
97 | |||
98 | private void initialize( Element elem ) { |
||
99 | synchronized(this) { |
||
100 | loading = true;
|
||
101 | fWidth = fHeight = 0;
|
||
102 | } |
||
103 | int width = 0; |
||
104 | int height = 0; |
||
105 | boolean customWidth = false; |
||
106 | boolean customHeight = false; |
||
107 | try {
|
||
108 | fElement = elem; |
||
109 | |||
110 | // Request image from document's cache:
|
||
111 | AttributeSet attr = elem.getAttributes();
|
||
112 | if (isURL()) {
|
||
113 | URL src = getSourceURL();
|
||
114 | if( src != null ) { |
||
115 | Dictionary cache = (Dictionary) getDocument().getProperty(IMAGE_CACHE_PROPERTY); |
||
116 | if( cache != null ) |
||
117 | fImage = (Image) cache.get(src);
|
||
118 | else
|
||
119 | fImage = Toolkit.getDefaultToolkit().getImage(src);
|
||
120 | } |
||
121 | } |
||
122 | else {
|
||
123 | |||
124 | /******** Code to load from relative path *************/
|
||
125 | String src =
|
||
126 | (String) fElement.getAttributes().getAttribute
|
||
127 | (HTML.Attribute.SRC);
|
||
128 | //TODO
|
||
129 | // src = processSrcPath(src);
|
||
130 | URL url = pluginEnvironmentClass.getClassLoader().getResource(src);
|
||
131 | fImage = Toolkit.getDefaultToolkit().createImage(url);
|
||
132 | try { waitForImage(); }
|
||
133 | catch (InterruptedException e) { fImage = null; } |
||
134 | /******************************************************/
|
||
135 | |||
136 | } |
||
137 | |||
138 | // Get height/width from params or image or defaults:
|
||
139 | height = getIntAttr(HTML.Attribute.HEIGHT,-1); |
||
140 | customHeight = (height>0);
|
||
141 | if( !customHeight && fImage != null ) |
||
142 | height = fImage.getHeight(this);
|
||
143 | if( height <= 0 ) |
||
144 | height = DEFAULT_HEIGHT; |
||
145 | |||
146 | width = getIntAttr(HTML.Attribute.WIDTH,-1); |
||
147 | customWidth = (width>0);
|
||
148 | if( !customWidth && fImage != null ) |
||
149 | width = fImage.getWidth(this);
|
||
150 | if( width <= 0 ) |
||
151 | width = DEFAULT_WIDTH; |
||
152 | |||
153 | // Make sure the image starts loading:
|
||
154 | if( fImage != null ) |
||
155 | if( customWidth && customHeight )
|
||
156 | Toolkit.getDefaultToolkit().prepareImage(fImage,height,
|
||
157 | width,this);
|
||
158 | else
|
||
159 | Toolkit.getDefaultToolkit().prepareImage(fImage,-1,-1, |
||
160 | this);
|
||
161 | |||
162 | /********************************************************
|
||
163 | // Rob took this out. Changed scope of src.
|
||
164 | if( DEBUG ) {
|
||
165 | if( fImage != null )
|
||
166 | System.out.println("ImageInfo: new on "+src+
|
||
167 | " ("+fWidth+"x"+fHeight+")");
|
||
168 | else
|
||
169 | System.out.println("ImageInfo: couldn't get image at "+
|
||
170 | src);
|
||
171 | if(isLink())
|
||
172 | System.out.println(" It's a link! Border = "+
|
||
173 | getBorder());
|
||
174 | //((AbstractDocument.AbstractElement)elem).dump(System.out,4);
|
||
175 | }
|
||
176 | ********************************************************/
|
||
177 | } finally {
|
||
178 | synchronized(this) { |
||
179 | loading = false;
|
||
180 | if (customWidth || fWidth == 0) { |
||
181 | fWidth = width; |
||
182 | } |
||
183 | if (customHeight || fHeight == 0) { |
||
184 | fHeight = height; |
||
185 | } |
||
186 | } |
||
187 | } |
||
188 | } |
||
189 | |||
190 | /** Determines if path is in the form of a URL */
|
||
191 | private boolean isURL() { |
||
192 | String src =
|
||
193 | (String) fElement.getAttributes().getAttribute(HTML.Attribute.SRC); |
||
194 | return src.toLowerCase().startsWith("file") || |
||
195 | src.toLowerCase().startsWith("http");
|
||
196 | } |
||
197 | |||
198 | /** Checks to see if the absolute path is availabe thru an application
|
||
199 | global static variable or thru a system variable. If so, appends
|
||
200 | the relative path to the absolute path and returns the String. */
|
||
201 | // private String processSrcPath(String src) {
|
||
202 | // String val = src;
|
||
203 | //
|
||
204 | // File imageFile = new File(src);
|
||
205 | // if (imageFile.isAbsolute()) return src;
|
||
206 | //
|
||
207 | // //try to get application images path...
|
||
208 | // if (PicTest.ApplicationImagePath != null) {
|
||
209 | // String imagePath = PicTest.ApplicationImagePath;
|
||
210 | // val = (new File(imagePath, imageFile.getPath())).toString();
|
||
211 | // }
|
||
212 | // //try to get system images path...
|
||
213 | // else {
|
||
214 | // String imagePath = System.getProperty("system.image.path.key");
|
||
215 | // if (imagePath != null) {
|
||
216 | // val = (new File(imagePath, imageFile.getPath())).toString();
|
||
217 | // }
|
||
218 | // }
|
||
219 | //
|
||
220 | // //System.out.println("src before: " + src + ", src after: " + val);
|
||
221 | // return val;
|
||
222 | // }
|
||
223 | |||
224 | /** Added this guy to make sure an image is loaded - ie no broken
|
||
225 | images. So far its used only for images loaded off the disk (non-URL).
|
||
226 | It seems to work marvelously. By the way, it does the same thing as
|
||
227 | MediaTracker, but you dont need to know the component its being
|
||
228 | rendered on. Rob */
|
||
229 | private void waitForImage() throws InterruptedException { |
||
230 | int w = fImage.getWidth(this); |
||
231 | int h = fImage.getHeight(this); |
||
232 | |||
233 | while (true) { |
||
234 | int flags = Toolkit.getDefaultToolkit().checkImage(fImage, w, h, this); |
||
235 | |||
236 | if ( ((flags & ERROR) != 0) || ((flags & ABORT) != 0 ) ) |
||
237 | throw new InterruptedException(); |
||
238 | else if ((flags & (ALLBITS | FRAMEBITS)) != 0) |
||
239 | return;
|
||
240 | Thread.sleep(10); |
||
241 | //System.out.println("rise and shine...");
|
||
242 | } |
||
243 | } |
||
244 | |||
245 | |||
246 | /**
|
||
247 | * Fetches the attributes to use when rendering. This is
|
||
248 | * implemented to multiplex the attributes specified in the
|
||
249 | * model with a StyleSheet.
|
||
250 | */
|
||
251 | public AttributeSet getAttributes() { |
||
252 | return attr;
|
||
253 | } |
||
254 | |||
255 | /** Is this image within a link? */
|
||
256 | boolean isLink( ) {
|
||
257 | //! It would be nice to cache this but in an editor it can change
|
||
258 | // See if I have an HREF attribute courtesy of the enclosing A tag:
|
||
259 | AttributeSet anchorAttr = (AttributeSet) |
||
260 | fElement.getAttributes().getAttribute(HTML.Tag.A);
|
||
261 | if (anchorAttr != null) { |
||
262 | return anchorAttr.isDefined(HTML.Attribute.HREF); |
||
263 | } |
||
264 | return false; |
||
265 | } |
||
266 | |||
267 | /** Returns the size of the border to use. */
|
||
268 | int getBorder( ) {
|
||
269 | return getIntAttr(HTML.Attribute.BORDER, isLink() ?DEFAULT_BORDER :0); |
||
270 | } |
||
271 | |||
272 | /** Returns the amount of extra space to add along an axis. */
|
||
273 | int getSpace( int axis ) { |
||
274 | return getIntAttr( axis==X_AXIS ?HTML.Attribute.HSPACE :HTML.Attribute.VSPACE, |
||
275 | 0 );
|
||
276 | } |
||
277 | |||
278 | /** Returns the border's color, or null if this is not a link. */
|
||
279 | Color getBorderColor( ) {
|
||
280 | StyledDocument doc = (StyledDocument) getDocument(); |
||
281 | return doc.getForeground(getAttributes());
|
||
282 | } |
||
283 | |||
284 | /** Returns the image's vertical alignment. */
|
||
285 | float getVerticalAlignment( ) {
|
||
286 | String align = (String) fElement.getAttributes().getAttribute(HTML.Attribute.ALIGN); |
||
287 | if( align != null ) { |
||
288 | align = align.toLowerCase(); |
||
289 | if( align.equals(TOP) || align.equals(TEXTTOP) )
|
||
290 | return 0.0f; |
||
291 | else if( align.equals(this.CENTER) || align.equals(MIDDLE) |
||
292 | || align.equals(ABSMIDDLE) ) |
||
293 | return 0.5f; |
||
294 | } |
||
295 | return 1.0f; // default alignment is bottom |
||
296 | } |
||
297 | |||
298 | boolean hasPixels( ImageObserver obs ) { |
||
299 | return fImage != null && fImage.getHeight(obs)>0 |
||
300 | && fImage.getWidth(obs)>0;
|
||
301 | } |
||
302 | |||
303 | |||
304 | /** Return a URL for the image source,
|
||
305 | or null if it could not be determined. */
|
||
306 | private URL getSourceURL( ) { |
||
307 | String src = (String) fElement.getAttributes().getAttribute(HTML.Attribute.SRC); |
||
308 | if( src==null ) return null; |
||
309 | |||
310 | URL reference = ((HTMLDocument)getDocument()).getBase(); |
||
311 | try {
|
||
312 | URL u = new URL(reference,src); |
||
313 | return u;
|
||
314 | } catch (MalformedURLException e) { |
||
315 | return null; |
||
316 | } |
||
317 | } |
||
318 | |||
319 | /** Look up an integer-valued attribute. <b>Not</b> recursive. */
|
||
320 | private int getIntAttr(HTML.Attribute name, int deflt ) { |
||
321 | AttributeSet attr = fElement.getAttributes();
|
||
322 | if( attr.isDefined(name) ) { // does not check parents! |
||
323 | int i;
|
||
324 | String val = (String) attr.getAttribute(name); |
||
325 | if( val == null ) |
||
326 | i = deflt; |
||
327 | else
|
||
328 | try{
|
||
329 | i = Math.max(0, Integer.parseInt(val)); |
||
330 | }catch( NumberFormatException x ) { |
||
331 | i = deflt; |
||
332 | } |
||
333 | return i;
|
||
334 | } else
|
||
335 | return deflt;
|
||
336 | } |
||
337 | |||
338 | |||
339 | /**
|
||
340 | * Establishes the parent view for this view.
|
||
341 | * Seize this moment to cache the AWT Container I'm in.
|
||
342 | */
|
||
343 | public void setParent(View parent) { |
||
344 | super.setParent(parent);
|
||
345 | fContainer = parent!=null ?getContainer() :null; |
||
346 | if( parent==null && fComponent!=null ) { |
||
347 | fComponent.getParent().remove(fComponent); |
||
348 | fComponent = null;
|
||
349 | } |
||
350 | } |
||
351 | |||
352 | /** My attributes may have changed. */
|
||
353 | public void changedUpdate(DocumentEvent e, Shape a, ViewFactory f) { |
||
354 | if(DEBUG) System.out.println("ImageView: changedUpdate begin..."); |
||
355 | super.changedUpdate(e,a,f);
|
||
356 | float align = getVerticalAlignment();
|
||
357 | |||
358 | int height = fHeight;
|
||
359 | int width = fWidth;
|
||
360 | |||
361 | initialize(getElement()); |
||
362 | |||
363 | boolean hChanged = fHeight!=height;
|
||
364 | boolean wChanged = fWidth!=width;
|
||
365 | if( hChanged || wChanged || getVerticalAlignment()!=align ) {
|
||
366 | if(DEBUG) System.out.println("ImageView: calling preferenceChanged"); |
||
367 | getParent().preferenceChanged(this,hChanged,wChanged);
|
||
368 | } |
||
369 | if(DEBUG) System.out.println("ImageView: changedUpdate end; valign="+getVerticalAlignment()); |
||
370 | } |
||
371 | |||
372 | |||
373 | // --- Painting --------------------------------------------------------
|
||
374 | |||
375 | /**
|
||
376 | * Paints the image.
|
||
377 | *
|
||
378 | * @param g the rendering surface to use
|
||
379 | * @param a the allocated region to render into
|
||
380 | * @see View#paint
|
||
381 | */
|
||
382 | public void paint(Graphics g, Shape a) { |
||
383 | Color oldColor = g.getColor();
|
||
384 | fBounds = a.getBounds(); |
||
385 | int border = getBorder();
|
||
386 | int x = fBounds.x + border + getSpace(X_AXIS);
|
||
387 | int y = fBounds.y + border + getSpace(Y_AXIS);
|
||
388 | int width = fWidth;
|
||
389 | int height = fHeight;
|
||
390 | int sel = getSelectionState();
|
||
391 | |||
392 | // Make sure my Component is in the right place:
|
||
393 | /*
|
||
394 | if( fComponent == null ) {
|
||
395 | fComponent = new Component() { };
|
||
396 | fComponent.addMouseListener(this);
|
||
397 | fComponent.addMouseMotionListener(this);
|
||
398 | fComponent.setCursor(Cursor.getDefaultCursor()); // use arrow cursor
|
||
399 | fContainer.add(fComponent);
|
||
400 | }
|
||
401 | fComponent.setBounds(x,y,width,height);
|
||
402 | */
|
||
403 | // If no pixels yet, draw gray outline and icon:
|
||
404 | if( ! hasPixels(this) ) { |
||
405 | g.setColor(Color.lightGray);
|
||
406 | g.drawRect(x,y,width-1,height-1); |
||
407 | g.setColor(oldColor); |
||
408 | loadIcons(); |
||
409 | Icon icon = fImage==null ?sMissingImageIcon :sPendingImageIcon; |
||
410 | if( icon != null ) |
||
411 | icon.paintIcon(getContainer(), g, x, y); |
||
412 | } |
||
413 | |||
414 | // Draw image:
|
||
415 | if( fImage != null ) { |
||
416 | g.drawImage(fImage,x, y,width,height,this);
|
||
417 | // Use the following instead of g.drawImage when
|
||
418 | // BufferedImageGraphics2D.setXORMode is fixed (4158822).
|
||
419 | |||
420 | // Use Xor mode when selected/highlighted.
|
||
421 | //! Could darken image instead, but it would be more expensive.
|
||
422 | /*
|
||
423 | if( sel > 0 )
|
||
424 | g.setXORMode(Color.white);
|
||
425 | g.drawImage(fImage,x, y,
|
||
426 | width,height,this);
|
||
427 | if( sel > 0 )
|
||
428 | g.setPaintMode();
|
||
429 | */
|
||
430 | } |
||
431 | |||
432 | // If selected exactly, we need a black border & grow-box:
|
||
433 | Color bc = getBorderColor();
|
||
434 | if( sel == 2 ) { |
||
435 | // Make sure there's room for a border:
|
||
436 | int delta = 2-border; |
||
437 | if( delta > 0 ) { |
||
438 | x += delta; |
||
439 | y += delta; |
||
440 | width -= delta<<1;
|
||
441 | height -= delta<<1;
|
||
442 | border = 2;
|
||
443 | } |
||
444 | bc = null;
|
||
445 | g.setColor(Color.black);
|
||
446 | // Draw grow box:
|
||
447 | g.fillRect(x+width-5,y+height-5,5,5); |
||
448 | } |
||
449 | |||
450 | // Draw border:
|
||
451 | if( border > 0 ) { |
||
452 | if( bc != null ) g.setColor(bc); |
||
453 | // Draw a thick rectangle:
|
||
454 | for( int i=1; i<=border; i++ ) |
||
455 | g.drawRect(x-i, y-i, width-1+i+i, height-1+i+i); |
||
456 | g.setColor(oldColor); |
||
457 | } |
||
458 | } |
||
459 | |||
460 | /** Request that this view be repainted.
|
||
461 | Assumes the view is still at its last-drawn location. */
|
||
462 | protected void repaint( long delay ) { |
||
463 | if( fContainer != null && fBounds!=null ) { |
||
464 | fContainer.repaint(delay, |
||
465 | fBounds.x,fBounds.y,fBounds.width,fBounds.height); |
||
466 | } |
||
467 | } |
||
468 | |||
469 | /** Determines whether the image is selected, and if it's the only thing selected.
|
||
470 | @return 0 if not selected, 1 if selected, 2 if exclusively selected.
|
||
471 | "Exclusive" selection is only returned when editable. */
|
||
472 | protected int getSelectionState( ) { |
||
473 | int p0 = fElement.getStartOffset();
|
||
474 | int p1 = fElement.getEndOffset();
|
||
475 | if (fContainer instanceof JTextComponent) { |
||
476 | JTextComponent textComp = (JTextComponent)fContainer; |
||
477 | int start = textComp.getSelectionStart();
|
||
478 | int end = textComp.getSelectionEnd();
|
||
479 | if( start<=p0 && end>=p1 ) {
|
||
480 | if( start==p0 && end==p1 && isEditable() )
|
||
481 | return 2; |
||
482 | else
|
||
483 | return 1; |
||
484 | } |
||
485 | } |
||
486 | return 0; |
||
487 | } |
||
488 | |||
489 | protected boolean isEditable( ) { |
||
490 | return fContainer instanceof JEditorPane |
||
491 | && ((JEditorPane)fContainer).isEditable();
|
||
492 | } |
||
493 | |||
494 | /** Returns the text editor's highlight color. */
|
||
495 | protected Color getHighlightColor( ) { |
||
496 | JTextComponent textComp = (JTextComponent)fContainer; |
||
497 | return textComp.getSelectionColor();
|
||
498 | } |
||
499 | |||
500 | // --- Progressive display ---------------------------------------------
|
||
501 | |||
502 | // This can come on any thread. If we are in the process of reloading
|
||
503 | // the image and determining our state (loading == true) we don't fire
|
||
504 | // preference changed, or repaint, we just reset the fWidth/fHeight as
|
||
505 | // necessary and return. This is ok as we know when loading finishes
|
||
506 | // it will pick up the new height/width, if necessary.
|
||
507 | public boolean imageUpdate( Image img, int flags, int x, int y, |
||
508 | int width, int height ) { |
||
509 | if( fImage==null || fImage != img ) |
||
510 | return false; |
||
511 | |||
512 | // Bail out if there was an error:
|
||
513 | if( (flags & (ABORT|ERROR)) != 0 ) { |
||
514 | fImage = null;
|
||
515 | repaint(0);
|
||
516 | return false; |
||
517 | } |
||
518 | |||
519 | // Resize image if necessary:
|
||
520 | short changed = 0; |
||
521 | if( (flags & ImageObserver.HEIGHT) != 0 ) |
||
522 | if( ! getElement().getAttributes().isDefined(HTML.Attribute.HEIGHT) ) { |
||
523 | changed |= 1;
|
||
524 | } |
||
525 | if( (flags & ImageObserver.WIDTH) != 0 ) |
||
526 | if( ! getElement().getAttributes().isDefined(HTML.Attribute.WIDTH) ) { |
||
527 | changed |= 2;
|
||
528 | } |
||
529 | synchronized(this) { |
||
530 | if ((changed & 1) == 1) { |
||
531 | fWidth = width; |
||
532 | } |
||
533 | if ((changed & 2) == 2) { |
||
534 | fHeight = height; |
||
535 | } |
||
536 | if (loading) {
|
||
537 | // No need to resize or repaint, still in the process of
|
||
538 | // loading.
|
||
539 | return true; |
||
540 | } |
||
541 | } |
||
542 | if( changed != 0 ) { |
||
543 | // May need to resize myself, asynchronously:
|
||
544 | if( DEBUG ) System.out.println("ImageView: resized to "+fWidth+"x"+fHeight); |
||
545 | |||
546 | Document doc = getDocument();
|
||
547 | try {
|
||
548 | if (doc instanceof AbstractDocument) { |
||
549 | ((AbstractDocument)doc).readLock();
|
||
550 | } |
||
551 | preferenceChanged(this,true,true); |
||
552 | } finally {
|
||
553 | if (doc instanceof AbstractDocument) { |
||
554 | ((AbstractDocument)doc).readUnlock();
|
||
555 | } |
||
556 | } |
||
557 | |||
558 | return true; |
||
559 | } |
||
560 | |||
561 | // Repaint when done or when new pixels arrive:
|
||
562 | if( (flags & (FRAMEBITS|ALLBITS)) != 0 ) |
||
563 | repaint(0);
|
||
564 | else if( (flags & SOMEBITS) != 0 ) |
||
565 | if( sIsInc )
|
||
566 | repaint(sIncRate); |
||
567 | |||
568 | return ((flags & ALLBITS) == 0); |
||
569 | } |
||
570 | /*
|
||
571 | /**
|
||
572 | * Static properties for incremental drawing.
|
||
573 | * Swiped from Component.java
|
||
574 | * @see #imageUpdate
|
||
575 | */
|
||
576 | private static boolean sIsInc = true; |
||
577 | private static int sIncRate = 100; |
||
578 | |||
579 | // --- Layout ----------------------------------------------------------
|
||
580 | |||
581 | /**
|
||
582 | * Determines the preferred span for this view along an
|
||
583 | * axis.
|
||
584 | *
|
||
585 | * @param axis may be either X_AXIS or Y_AXIS
|
||
586 | * @returns the span the view would like to be rendered into.
|
||
587 | * Typically the view is told to render into the span
|
||
588 | * that is returned, although there is no guarantee.
|
||
589 | * The parent may choose to resize or break the view.
|
||
590 | */
|
||
591 | public float getPreferredSpan(int axis) { |
||
592 | //if(DEBUG)System.out.println("ImageView: getPreferredSpan");
|
||
593 | int extra = 2*(getBorder()+getSpace(axis)); |
||
594 | switch (axis) {
|
||
595 | case View.X_AXIS: |
||
596 | return fWidth+extra;
|
||
597 | case View.Y_AXIS: |
||
598 | return fHeight+extra;
|
||
599 | default:
|
||
600 | throw new IllegalArgumentException("Invalid axis: " + axis); |
||
601 | } |
||
602 | } |
||
603 | |||
604 | /**
|
||
605 | * Determines the desired alignment for this view along an
|
||
606 | * axis. This is implemented to give the alignment to the
|
||
607 | * bottom of the icon along the y axis, and the default
|
||
608 | * along the x axis.
|
||
609 | *
|
||
610 | * @param axis may be either X_AXIS or Y_AXIS
|
||
611 | * @returns the desired alignment. This should be a value
|
||
612 | * between 0.0 and 1.0 where 0 indicates alignment at the
|
||
613 | * origin and 1.0 indicates alignment to the full span
|
||
614 | * away from the origin. An alignment of 0.5 would be the
|
||
615 | * center of the view.
|
||
616 | */
|
||
617 | public float getAlignment(int axis) { |
||
618 | switch (axis) {
|
||
619 | case View.Y_AXIS: |
||
620 | return getVerticalAlignment();
|
||
621 | default:
|
||
622 | return super.getAlignment(axis); |
||
623 | } |
||
624 | } |
||
625 | |||
626 | /**
|
||
627 | * Provides a mapping from the document model coordinate space
|
||
628 | * to the coordinate space of the view mapped to it.
|
||
629 | *
|
||
630 | * @param pos the position to convert
|
||
631 | * @param a the allocated region to render into
|
||
632 | * @return the bounding box of the given position
|
||
633 | * @exception BadLocationException if the given position does not represent a
|
||
634 | * valid location in the associated document
|
||
635 | * @see View#modelToView
|
||
636 | */
|
||
637 | public Shape modelToView(int pos, Shape a, Position.Bias b) throws BadLocationException { |
||
638 | int p0 = getStartOffset();
|
||
639 | int p1 = getEndOffset();
|
||
640 | if ((pos >= p0) && (pos <= p1)) {
|
||
641 | Rectangle r = a.getBounds();
|
||
642 | if (pos == p1) {
|
||
643 | r.x += r.width; |
||
644 | } |
||
645 | r.width = 0;
|
||
646 | return r;
|
||
647 | } |
||
648 | return null; |
||
649 | } |
||
650 | |||
651 | /**
|
||
652 | * Provides a mapping from the view coordinate space to the logical
|
||
653 | * coordinate space of the model.
|
||
654 | *
|
||
655 | * @param x the X coordinate
|
||
656 | * @param y the Y coordinate
|
||
657 | * @param a the allocated region to render into
|
||
658 | * @return the location within the model that best represents the
|
||
659 | * given point of view
|
||
660 | * @see View#viewToModel
|
||
661 | */
|
||
662 | public int viewToModel(float x, float y, Shape a, Position.Bias[] bias) { |
||
663 | Rectangle alloc = (Rectangle) a; |
||
664 | if (x < alloc.x + alloc.width) {
|
||
665 | bias[0] = Position.Bias.Forward; |
||
666 | return getStartOffset();
|
||
667 | } |
||
668 | bias[0] = Position.Bias.Backward; |
||
669 | return getEndOffset();
|
||
670 | } |
||
671 | |||
672 | /**
|
||
673 | * Set the size of the view. (Ignored.)
|
||
674 | *
|
||
675 | * @param width the width
|
||
676 | * @param height the height
|
||
677 | */
|
||
678 | public void setSize(float width, float height) { |
||
679 | // Ignore this -- image size is determined by the tag attrs and
|
||
680 | // the image itself, not the surrounding layout!
|
||
681 | } |
||
682 | |||
683 | /** Change the size of this image. This alters the HEIGHT and WIDTH
|
||
684 | attributes of the Element and causes a re-layout. */
|
||
685 | protected void resize( int width, int height ) { |
||
686 | if( width==fWidth && height==fHeight )
|
||
687 | return;
|
||
688 | |||
689 | fWidth = width; |
||
690 | fHeight= height; |
||
691 | |||
692 | // Replace attributes in document:
|
||
693 | MutableAttributeSet attr = new SimpleAttributeSet(); |
||
694 | attr.addAttribute(HTML.Attribute.WIDTH ,Integer.toString(width)); |
||
695 | attr.addAttribute(HTML.Attribute.HEIGHT,Integer.toString(height)); |
||
696 | ((StyledDocument)getDocument()).setCharacterAttributes(
|
||
697 | fElement.getStartOffset(), |
||
698 | fElement.getEndOffset(), |
||
699 | attr, false);
|
||
700 | } |
||
701 | |||
702 | // --- Mouse event handling --------------------------------------------
|
||
703 | |||
704 | /** Select or grow image when clicked. */
|
||
705 | public void mousePressed(MouseEvent e){ |
||
706 | Dimension size = fComponent.getSize();
|
||
707 | if( e.getX() >= size.width-7 && e.getY() >= size.height-7 |
||
708 | && getSelectionState()==2 ) {
|
||
709 | // Click in selected grow-box:
|
||
710 | if(DEBUG)System.out.println("ImageView: grow!!! Size="+fWidth+"x"+fHeight); |
||
711 | Point loc = fComponent.getLocationOnScreen();
|
||
712 | fGrowBase = new Point(loc.x+e.getX() - fWidth, |
||
713 | loc.y+e.getY() - fHeight); |
||
714 | fGrowProportionally = e.isShiftDown(); |
||
715 | } else {
|
||
716 | // Else select image:
|
||
717 | fGrowBase = null;
|
||
718 | JTextComponent comp = (JTextComponent)fContainer; |
||
719 | int start = fElement.getStartOffset();
|
||
720 | int end = fElement.getEndOffset();
|
||
721 | int mark = comp.getCaret().getMark();
|
||
722 | int dot = comp.getCaret().getDot();
|
||
723 | if( e.isShiftDown() ) {
|
||
724 | // extend selection if shift key down:
|
||
725 | if( mark <= start )
|
||
726 | comp.moveCaretPosition(end); |
||
727 | else
|
||
728 | comp.moveCaretPosition(start); |
||
729 | } else {
|
||
730 | // just select image, without shift:
|
||
731 | if( mark!=start )
|
||
732 | comp.setCaretPosition(start); |
||
733 | if( dot!=end )
|
||
734 | comp.moveCaretPosition(end); |
||
735 | } |
||
736 | } |
||
737 | } |
||
738 | |||
739 | /** Resize image if initial click was in grow-box: */
|
||
740 | public void mouseDragged(MouseEvent e ) { |
||
741 | if( fGrowBase != null ) { |
||
742 | Point loc = fComponent.getLocationOnScreen();
|
||
743 | int width = Math.max(2, loc.x+e.getX() - fGrowBase.x); |
||
744 | int height= Math.max(2, loc.y+e.getY() - fGrowBase.y); |
||
745 | |||
746 | if( e.isShiftDown() && fImage!=null ) { |
||
747 | // Make sure size is proportional to actual image size:
|
||
748 | float imgWidth = fImage.getWidth(this); |
||
749 | float imgHeight = fImage.getHeight(this); |
||
750 | if( imgWidth>0 && imgHeight>0 ) { |
||
751 | float prop = imgHeight / imgWidth;
|
||
752 | float pwidth = height / prop;
|
||
753 | float pheight= width * prop;
|
||
754 | if( pwidth > width )
|
||
755 | width = (int) pwidth;
|
||
756 | else
|
||
757 | height = (int) pheight;
|
||
758 | } |
||
759 | } |
||
760 | |||
761 | resize(width,height); |
||
762 | } |
||
763 | } |
||
764 | |||
765 | public void mouseReleased(MouseEvent e){ |
||
766 | fGrowBase = null;
|
||
767 | //! Should post some command to make the action undo-able
|
||
768 | } |
||
769 | |||
770 | /** On double-click, open image properties dialog. */
|
||
771 | public void mouseClicked(MouseEvent e){ |
||
772 | if( e.getClickCount() == 2 ) { |
||
773 | //$ IMPLEMENT
|
||
774 | } |
||
775 | } |
||
776 | |||
777 | public void mouseEntered(MouseEvent e){ |
||
778 | } |
||
779 | public void mouseMoved(MouseEvent e ) { |
||
780 | } |
||
781 | public void mouseExited(MouseEvent e){ |
||
782 | } |
||
783 | |||
784 | // --- Static icon accessors -------------------------------------------
|
||
785 | |||
786 | private Icon makeIcon(final String gifFile) throws IOException { |
||
787 | /* Copy resource into a byte array. This is
|
||
788 | * necessary because several browsers consider
|
||
789 | * Class.getResource a security risk because it
|
||
790 | * can be used to load additional classes.
|
||
791 | * Class.getResourceAsStream just returns raw
|
||
792 | * bytes, which we can convert to an image.
|
||
793 | */
|
||
794 | InputStream resource = ImageView.class.getResourceAsStream(gifFile); |
||
795 | |||
796 | if (resource == null) { |
||
797 | System.err.println(ImageView.class.getName() + "/" + |
||
798 | gifFile + " not found.");
|
||
799 | return null; |
||
800 | } |
||
801 | BufferedInputStream in =
|
||
802 | new BufferedInputStream(resource); |
||
803 | ByteArrayOutputStream out =
|
||
804 | new ByteArrayOutputStream(1024); |
||
805 | byte[] buffer = new byte[1024]; |
||
806 | int n;
|
||
807 | while ((n = in.read(buffer)) > 0) { |
||
808 | out.write(buffer, 0, n);
|
||
809 | } |
||
810 | in.close(); |
||
811 | out.flush(); |
||
812 | |||
813 | buffer = out.toByteArray(); |
||
814 | if (buffer.length == 0) { |
||
815 | System.err.println("warning: " + gifFile + |
||
816 | " is zero-length");
|
||
817 | return null; |
||
818 | } |
||
819 | return new ImageIcon(buffer); |
||
820 | } |
||
821 | |||
822 | private void loadIcons( ) { |
||
823 | try{
|
||
824 | if( sPendingImageIcon == null ) |
||
825 | sPendingImageIcon = makeIcon(PENDING_IMAGE_SRC); |
||
826 | if( sMissingImageIcon == null ) |
||
827 | sMissingImageIcon = makeIcon(MISSING_IMAGE_SRC); |
||
828 | }catch( Exception x ) { |
||
829 | System.err.println("ImageView: Couldn't load image icons"); |
||
830 | } |
||
831 | } |
||
832 | |||
833 | protected StyleSheet getStyleSheet() { |
||
834 | HTMLDocument doc = (HTMLDocument) getDocument(); |
||
835 | return doc.getStyleSheet();
|
||
836 | } |
||
837 | |||
838 | // --- member variables ------------------------------------------------
|
||
839 | |||
840 | private AttributeSet attr; |
||
841 | private Element fElement; |
||
842 | private Image fImage; |
||
843 | private int fHeight,fWidth; |
||
844 | private Container fContainer; |
||
845 | private Rectangle fBounds; |
||
846 | private Component fComponent; |
||
847 | private Point fGrowBase; // base of drag while growing image |
||
848 | private boolean fGrowProportionally; // should grow be proportional? |
||
849 | /** Set to true, while the receiver is locked, to indicate the reciever
|
||
850 | * is loading the image. This is used in imageUpdate. */
|
||
851 | private boolean loading; |
||
852 | |||
853 | // --- constants and static stuff --------------------------------
|
||
854 | |||
855 | private static Icon sPendingImageIcon, |
||
856 | sMissingImageIcon; |
||
857 | private static final String |
||
858 | PENDING_IMAGE_SRC = "icons/image-delayed.gif", // both stolen from HotJava |
||
859 | MISSING_IMAGE_SRC = "icons/image-failed.gif";
|
||
860 | |||
861 | private static final boolean DEBUG = false; |
||
862 | |||
863 | //$ move this someplace public
|
||
864 | static final String IMAGE_CACHE_PROPERTY = "imageCache"; |
||
865 | |||
866 | // Height/width to use before we know the real size:
|
||
867 | private static final int |
||
868 | DEFAULT_WIDTH = 32,
|
||
869 | DEFAULT_HEIGHT= 32,
|
||
870 | // Default value of BORDER param: //? possibly move into stylesheet?
|
||
871 | DEFAULT_BORDER= 2;
|
||
872 | |||
873 | } |