Statistics
| Revision:

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
}