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