001 /*
002 * Copyright 1997-2007 Sun Microsystems, Inc. All Rights Reserved.
003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004 *
005 * This code is free software; you can redistribute it and/or modify it
006 * under the terms of the GNU General Public License version 2 only, as
007 * published by the Free Software Foundation. Sun designates this
008 * particular file as subject to the "Classpath" exception as provided
009 * by Sun in the LICENSE file that accompanied this code.
010 *
011 * This code is distributed in the hope that it will be useful, but WITHOUT
012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014 * version 2 for more details (a copy is included in the LICENSE file that
015 * accompanied this code).
016 *
017 * You should have received a copy of the GNU General Public License version
018 * 2 along with this work; if not, write to the Free Software Foundation,
019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020 *
021 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022 * CA 95054 USA or visit www.sun.com if you need additional information or
023 * have any questions.
024 */
025 package javax.swing.text;
026
027 import java.awt.*;
028 import java.beans.PropertyChangeEvent;
029 import java.beans.PropertyChangeListener;
030 import javax.swing.SwingUtilities;
031 import javax.swing.event.*;
032
033 /**
034 * Component decorator that implements the view interface. The
035 * entire element is used to represent the component. This acts
036 * as a gateway from the display-only View implementations to
037 * interactive lightweight components (ie it allows components
038 * to be embedded into the View hierarchy).
039 * <p>
040 * The component is placed relative to the text baseline
041 * according to the value returned by
042 * <code>Component.getAlignmentY</code>. For Swing components
043 * this value can be conveniently set using the method
044 * <code>JComponent.setAlignmentY</code>. For example, setting
045 * a value of <code>0.75</code> will cause 75 percent of the
046 * component to be above the baseline, and 25 percent of the
047 * component to be below the baseline.
048 * <p>
049 * This class is implemented to do the extra work necessary to
050 * work properly in the presence of multiple threads (i.e. from
051 * asynchronous notification of model changes for example) by
052 * ensuring that all component access is done on the event thread.
053 * <p>
054 * The component used is determined by the return value of the
055 * createComponent method. The default implementation of this
056 * method is to return the component held as an attribute of
057 * the element (by calling StyleConstants.getComponent). A
058 * limitation of this behavior is that the component cannot
059 * be used by more than one text component (i.e. with a shared
060 * model). Subclasses can remove this constraint by implementing
061 * the createComponent to actually create a component based upon
062 * some kind of specification contained in the attributes. The
063 * ObjectView class in the html package is an example of a
064 * ComponentView implementation that supports multiple component
065 * views of a shared model.
066 *
067 * @author Timothy Prinzing
068 * @version 1.60 05/05/07
069 */
070 public class ComponentView extends View {
071
072 /**
073 * Creates a new ComponentView object.
074 *
075 * @param elem the element to decorate
076 */
077 public ComponentView(Element elem) {
078 super (elem);
079 }
080
081 /**
082 * Create the component that is associated with
083 * this view. This will be called when it has
084 * been determined that a new component is needed.
085 * This would result from a call to setParent or
086 * as a result of being notified that attributes
087 * have changed.
088 */
089 protected Component createComponent() {
090 AttributeSet attr = getElement().getAttributes();
091 Component comp = StyleConstants.getComponent(attr);
092 return comp;
093 }
094
095 /**
096 * Fetch the component associated with the view.
097 */
098 public final Component getComponent() {
099 return createdC;
100 }
101
102 // --- View methods ---------------------------------------------
103
104 /**
105 * The real paint behavior occurs naturally from the association
106 * that the component has with its parent container (the same
107 * container hosting this view). This is implemented to do nothing.
108 *
109 * @param g the graphics context
110 * @param a the shape
111 * @see View#paint
112 */
113 public void paint(Graphics g, Shape a) {
114 if (c != null) {
115 Rectangle alloc = (a instanceof Rectangle) ? (Rectangle) a
116 : a.getBounds();
117 c.setBounds(alloc.x, alloc.y, alloc.width, alloc.height);
118 }
119 }
120
121 /**
122 * Determines the preferred span for this view along an
123 * axis. This is implemented to return the value
124 * returned by Component.getPreferredSize along the
125 * axis of interest.
126 *
127 * @param axis may be either View.X_AXIS or View.Y_AXIS
128 * @return the span the view would like to be rendered into >= 0.
129 * Typically the view is told to render into the span
130 * that is returned, although there is no guarantee.
131 * The parent may choose to resize or break the view.
132 * @exception IllegalArgumentException for an invalid axis
133 */
134 public float getPreferredSpan(int axis) {
135 if ((axis != X_AXIS) && (axis != Y_AXIS)) {
136 throw new IllegalArgumentException("Invalid axis: " + axis);
137 }
138 if (c != null) {
139 Dimension size = c.getPreferredSize();
140 if (axis == View.X_AXIS) {
141 return size.width;
142 } else {
143 return size.height;
144 }
145 }
146 return 0;
147 }
148
149 /**
150 * Determines the minimum span for this view along an
151 * axis. This is implemented to return the value
152 * returned by Component.getMinimumSize along the
153 * axis of interest.
154 *
155 * @param axis may be either View.X_AXIS or View.Y_AXIS
156 * @return the span the view would like to be rendered into >= 0.
157 * Typically the view is told to render into the span
158 * that is returned, although there is no guarantee.
159 * The parent may choose to resize or break the view.
160 * @exception IllegalArgumentException for an invalid axis
161 */
162 public float getMinimumSpan(int axis) {
163 if ((axis != X_AXIS) && (axis != Y_AXIS)) {
164 throw new IllegalArgumentException("Invalid axis: " + axis);
165 }
166 if (c != null) {
167 Dimension size = c.getMinimumSize();
168 if (axis == View.X_AXIS) {
169 return size.width;
170 } else {
171 return size.height;
172 }
173 }
174 return 0;
175 }
176
177 /**
178 * Determines the maximum span for this view along an
179 * axis. This is implemented to return the value
180 * returned by Component.getMaximumSize along the
181 * axis of interest.
182 *
183 * @param axis may be either View.X_AXIS or View.Y_AXIS
184 * @return the span the view would like to be rendered into >= 0.
185 * Typically the view is told to render into the span
186 * that is returned, although there is no guarantee.
187 * The parent may choose to resize or break the view.
188 * @exception IllegalArgumentException for an invalid axis
189 */
190 public float getMaximumSpan(int axis) {
191 if ((axis != X_AXIS) && (axis != Y_AXIS)) {
192 throw new IllegalArgumentException("Invalid axis: " + axis);
193 }
194 if (c != null) {
195 Dimension size = c.getMaximumSize();
196 if (axis == View.X_AXIS) {
197 return size.width;
198 } else {
199 return size.height;
200 }
201 }
202 return 0;
203 }
204
205 /**
206 * Determines the desired alignment for this view along an
207 * axis. This is implemented to give the alignment of the
208 * embedded component.
209 *
210 * @param axis may be either View.X_AXIS or View.Y_AXIS
211 * @return the desired alignment. This should be a value
212 * between 0.0 and 1.0 where 0 indicates alignment at the
213 * origin and 1.0 indicates alignment to the full span
214 * away from the origin. An alignment of 0.5 would be the
215 * center of the view.
216 */
217 public float getAlignment(int axis) {
218 if (c != null) {
219 switch (axis) {
220 case View.X_AXIS:
221 return c.getAlignmentX();
222 case View.Y_AXIS:
223 return c.getAlignmentY();
224 }
225 }
226 return super .getAlignment(axis);
227 }
228
229 /**
230 * Sets the parent for a child view.
231 * The parent calls this on the child to tell it who its
232 * parent is, giving the view access to things like
233 * the hosting Container. The superclass behavior is
234 * executed, followed by a call to createComponent if
235 * the parent view parameter is non-null and a component
236 * has not yet been created. The embedded components parent
237 * is then set to the value returned by <code>getContainer</code>.
238 * If the parent view parameter is null, this view is being
239 * cleaned up, thus the component is removed from its parent.
240 * <p>
241 * The changing of the component hierarchy will
242 * touch the component lock, which is the one thing
243 * that is not safe from the View hierarchy. Therefore,
244 * this functionality is executed immediately if on the
245 * event thread, or is queued on the event queue if
246 * called from another thread (notification of change
247 * from an asynchronous update).
248 *
249 * @param p the parent
250 */
251 public void setParent(View p) {
252 super .setParent(p);
253 if (SwingUtilities.isEventDispatchThread()) {
254 setComponentParent();
255 } else {
256 Runnable callSetComponentParent = new Runnable() {
257 public void run() {
258 Document doc = getDocument();
259 try {
260 if (doc instanceof AbstractDocument) {
261 ((AbstractDocument) doc).readLock();
262 }
263 setComponentParent();
264 Container host = getContainer();
265 if (host != null) {
266 preferenceChanged(null, true, true);
267 host.repaint();
268 }
269 } finally {
270 if (doc instanceof AbstractDocument) {
271 ((AbstractDocument) doc).readUnlock();
272 }
273 }
274 }
275 };
276 SwingUtilities.invokeLater(callSetComponentParent);
277 }
278 }
279
280 /**
281 * Set the parent of the embedded component
282 * with assurance that it is thread-safe.
283 */
284 void setComponentParent() {
285 View p = getParent();
286 if (p != null) {
287 Container parent = getContainer();
288 if (parent != null) {
289 if (c == null) {
290 // try to build a component
291 Component comp = createComponent();
292 if (comp != null) {
293 createdC = comp;
294 c = new Invalidator(comp);
295 }
296 }
297 if (c != null) {
298 if (c.getParent() == null) {
299 // components associated with the View tree are added
300 // to the hosting container with the View as a constraint.
301 parent.add(c, this );
302 parent.addPropertyChangeListener("enabled", c);
303 }
304 }
305 }
306 } else {
307 if (c != null) {
308 Container parent = c.getParent();
309 if (parent != null) {
310 // remove the component from its hosting container
311 parent.remove(c);
312 parent.removePropertyChangeListener("enabled", c);
313 }
314 }
315 }
316 }
317
318 /**
319 * Provides a mapping from the coordinate space of the model to
320 * that of the view.
321 *
322 * @param pos the position to convert >= 0
323 * @param a the allocated region to render into
324 * @return the bounding box of the given position is returned
325 * @exception BadLocationException if the given position does not
326 * represent a valid location in the associated document
327 * @see View#modelToView
328 */
329 public Shape modelToView(int pos, Shape a, Position.Bias b)
330 throws BadLocationException {
331 int p0 = getStartOffset();
332 int p1 = getEndOffset();
333 if ((pos >= p0) && (pos <= p1)) {
334 Rectangle r = a.getBounds();
335 if (pos == p1) {
336 r.x += r.width;
337 }
338 r.width = 0;
339 return r;
340 }
341 throw new BadLocationException(pos + " not in range " + p0
342 + "," + p1, pos);
343 }
344
345 /**
346 * Provides a mapping from the view coordinate space to the logical
347 * coordinate space of the model.
348 *
349 * @param x the X coordinate >= 0
350 * @param y the Y coordinate >= 0
351 * @param a the allocated region to render into
352 * @return the location within the model that best represents
353 * the given point in the view
354 * @see View#viewToModel
355 */
356 public int viewToModel(float x, float y, Shape a,
357 Position.Bias[] bias) {
358 Rectangle alloc = (Rectangle) a;
359 if (x < alloc.x + (alloc.width / 2)) {
360 bias[0] = Position.Bias.Forward;
361 return getStartOffset();
362 }
363 bias[0] = Position.Bias.Backward;
364 return getEndOffset();
365 }
366
367 // --- member variables ------------------------------------------------
368
369 private Component createdC;
370 private Invalidator c;
371
372 /**
373 * This class feeds the invalidate back to the
374 * hosting View. This is needed to get the View
375 * hierarchy to consider giving the component
376 * a different size (i.e. layout may have been
377 * cached between the associated view and the
378 * container hosting this component).
379 */
380 class Invalidator extends Container implements
381 PropertyChangeListener {
382
383 // NOTE: When we remove this class we are going to have to some
384 // how enforce setting of the focus traversal keys on the children
385 // so that they don't inherit them from the JEditorPane. We need
386 // to do this as JEditorPane has abnormal bindings (it is a focus cycle
387 // root) and the children typically don't want these bindings as well.
388
389 Invalidator(Component child) {
390 setLayout(null);
391 add(child);
392 cacheChildSizes();
393 }
394
395 /**
396 * The components invalid layout needs
397 * to be propagated through the view hierarchy
398 * so the views (which position the component)
399 * can have their layout recomputed.
400 */
401 public void invalidate() {
402 super .invalidate();
403 if (getParent() != null) {
404 preferenceChanged(null, true, true);
405 }
406 }
407
408 public void doLayout() {
409 cacheChildSizes();
410 }
411
412 public void setBounds(int x, int y, int w, int h) {
413 super .setBounds(x, y, w, h);
414 if (getComponentCount() > 0) {
415 getComponent(0).setSize(w, h);
416 }
417 cacheChildSizes();
418 }
419
420 public void validateIfNecessary() {
421 if (!isValid()) {
422 validate();
423 }
424 }
425
426 private void cacheChildSizes() {
427 if (getComponentCount() > 0) {
428 Component child = getComponent(0);
429 min = child.getMinimumSize();
430 pref = child.getPreferredSize();
431 max = child.getMaximumSize();
432 yalign = child.getAlignmentY();
433 xalign = child.getAlignmentX();
434 } else {
435 min = pref = max = new Dimension(0, 0);
436 }
437 }
438
439 /**
440 * Shows or hides this component depending on the value of parameter
441 * <code>b</code>.
442 * @param <code>b</code> If <code>true</code>, shows this component;
443 * otherwise, hides this component.
444 * @see #isVisible
445 * @since JDK1.1
446 */
447 public void setVisible(boolean b) {
448 super .setVisible(b);
449 if (getComponentCount() > 0) {
450 getComponent(0).setVisible(b);
451 }
452 }
453
454 /**
455 * Overridden to fix 4759054. Must return true so that content
456 * is painted when inside a CellRendererPane which is normally
457 * invisible.
458 */
459 public boolean isShowing() {
460 return true;
461 }
462
463 public Dimension getMinimumSize() {
464 validateIfNecessary();
465 return min;
466 }
467
468 public Dimension getPreferredSize() {
469 validateIfNecessary();
470 return pref;
471 }
472
473 public Dimension getMaximumSize() {
474 validateIfNecessary();
475 return max;
476 }
477
478 public float getAlignmentX() {
479 validateIfNecessary();
480 return xalign;
481 }
482
483 public float getAlignmentY() {
484 validateIfNecessary();
485 return yalign;
486 }
487
488 public java.util.Set getFocusTraversalKeys(int id) {
489 return KeyboardFocusManager
490 .getCurrentKeyboardFocusManager()
491 .getDefaultFocusTraversalKeys(id);
492 }
493
494 public void propertyChange(PropertyChangeEvent ev) {
495 Boolean enable = (Boolean) ev.getNewValue();
496 if (getComponentCount() > 0) {
497 getComponent(0).setEnabled(enable);
498 }
499 }
500
501 Dimension min;
502 Dimension pref;
503 Dimension max;
504 float yalign;
505 float xalign;
506
507 }
508
509 }
|