1 /* 2 * Copyright (c) 1997, 2013, Oracle and/or its affiliates. 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. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 package javax.swing.text; 26 27 import java.awt.*; 28 import java.beans.PropertyChangeEvent; 29 import java.beans.PropertyChangeListener; 30 import java.util.Set; 31 import javax.swing.SwingUtilities; 32 import javax.swing.event.*; 33 34 /** 35 * Component decorator that implements the view interface. The 36 * entire element is used to represent the component. This acts 37 * as a gateway from the display-only View implementations to 38 * interactive lightweight components (ie it allows components 39 * to be embedded into the View hierarchy). 40 * <p> 41 * The component is placed relative to the text baseline 42 * according to the value returned by 43 * <code>Component.getAlignmentY</code>. For Swing components 44 * this value can be conveniently set using the method 45 * <code>JComponent.setAlignmentY</code>. For example, setting 46 * a value of <code>0.75</code> will cause 75 percent of the 47 * component to be above the baseline, and 25 percent of the 48 * component to be below the baseline. 49 * <p> 50 * This class is implemented to do the extra work necessary to 51 * work properly in the presence of multiple threads (i.e. from 52 * asynchronous notification of model changes for example) by 53 * ensuring that all component access is done on the event thread. 54 * <p> 55 * The component used is determined by the return value of the 56 * createComponent method. The default implementation of this 57 * method is to return the component held as an attribute of 58 * the element (by calling StyleConstants.getComponent). A 59 * limitation of this behavior is that the component cannot 60 * be used by more than one text component (i.e. with a shared 61 * model). Subclasses can remove this constraint by implementing 62 * the createComponent to actually create a component based upon 63 * some kind of specification contained in the attributes. The 64 * ObjectView class in the html package is an example of a 65 * ComponentView implementation that supports multiple component 66 * views of a shared model. 67 * 68 * @author Timothy Prinzing 69 */ 70 public class ComponentView extends View { 71 72 /** 73 * Creates a new ComponentView object. 74 * 75 * @param elem the element to decorate 76 */ 77 public ComponentView(Element elem) { 78 super(elem); 79 } 80 81 /** 82 * Create the component that is associated with 83 * this view. This will be called when it has 84 * been determined that a new component is needed. 85 * This would result from a call to setParent or 86 * as a result of being notified that attributes 87 * have changed. 88 * @return the component that is associated with 89 * this view 90 */ 91 protected Component createComponent() { 92 AttributeSet attr = getElement().getAttributes(); 93 Component comp = StyleConstants.getComponent(attr); 94 return comp; 95 } 96 97 /** 98 * Fetch the component associated with the view. 99 * @return the component associated with the view 100 */ 101 public final Component getComponent() { 102 return createdC; 103 } 104 105 // --- View methods --------------------------------------------- 106 107 /** 108 * The real paint behavior occurs naturally from the association 109 * that the component has with its parent container (the same 110 * container hosting this view). This is implemented to do nothing. 111 * 112 * @param g the graphics context 113 * @param a the shape 114 * @see View#paint 115 */ 116 public void paint(Graphics g, Shape a) { 117 if (c != null) { 118 Rectangle alloc = (a instanceof Rectangle) ? 119 (Rectangle) a : a.getBounds(); 120 c.setBounds(alloc.x, alloc.y, alloc.width, alloc.height); 121 } 122 } 123 124 /** 125 * Determines the preferred span for this view along an 126 * axis. This is implemented to return the value 127 * returned by Component.getPreferredSize along the 128 * axis of interest. 129 * 130 * @param axis may be either View.X_AXIS or View.Y_AXIS 131 * @return the span the view would like to be rendered into >=0. 132 * Typically the view is told to render into the span 133 * that is returned, although there is no guarantee. 134 * The parent may choose to resize or break the view. 135 * @exception IllegalArgumentException for an invalid axis 136 */ 137 public float getPreferredSpan(int axis) { 138 if ((axis != X_AXIS) && (axis != Y_AXIS)) { 139 throw new IllegalArgumentException("Invalid axis: " + axis); 140 } 141 if (c != null) { 142 Dimension size = c.getPreferredSize(); 143 if (axis == View.X_AXIS) { 144 return size.width; 145 } else { 146 return size.height; 147 } 148 } 149 return 0; 150 } 151 152 /** 153 * Determines the minimum span for this view along an 154 * axis. This is implemented to return the value 155 * returned by Component.getMinimumSize along the 156 * axis of interest. 157 * 158 * @param axis may be either View.X_AXIS or View.Y_AXIS 159 * @return the span the view would like to be rendered into >=0. 160 * Typically the view is told to render into the span 161 * that is returned, although there is no guarantee. 162 * The parent may choose to resize or break the view. 163 * @exception IllegalArgumentException for an invalid axis 164 */ 165 public float getMinimumSpan(int axis) { 166 if ((axis != X_AXIS) && (axis != Y_AXIS)) { 167 throw new IllegalArgumentException("Invalid axis: " + axis); 168 } 169 if (c != null) { 170 Dimension size = c.getMinimumSize(); 171 if (axis == View.X_AXIS) { 172 return size.width; 173 } else { 174 return size.height; 175 } 176 } 177 return 0; 178 } 179 180 /** 181 * Determines the maximum span for this view along an 182 * axis. This is implemented to return the value 183 * returned by Component.getMaximumSize along the 184 * axis of interest. 185 * 186 * @param axis may be either View.X_AXIS or View.Y_AXIS 187 * @return the span the view would like to be rendered into >=0. 188 * Typically the view is told to render into the span 189 * that is returned, although there is no guarantee. 190 * The parent may choose to resize or break the view. 191 * @exception IllegalArgumentException for an invalid axis 192 */ 193 public float getMaximumSpan(int axis) { 194 if ((axis != X_AXIS) && (axis != Y_AXIS)) { 195 throw new IllegalArgumentException("Invalid axis: " + axis); 196 } 197 if (c != null) { 198 Dimension size = c.getMaximumSize(); 199 if (axis == View.X_AXIS) { 200 return size.width; 201 } else { 202 return size.height; 203 } 204 } 205 return 0; 206 } 207 208 /** 209 * Determines the desired alignment for this view along an 210 * axis. This is implemented to give the alignment of the 211 * embedded component. 212 * 213 * @param axis may be either View.X_AXIS or View.Y_AXIS 214 * @return the desired alignment. This should be a value 215 * between 0.0 and 1.0 where 0 indicates alignment at the 216 * origin and 1.0 indicates alignment to the full span 217 * away from the origin. An alignment of 0.5 would be the 218 * center of the view. 219 */ 220 public float getAlignment(int axis) { 221 if (c != null) { 222 switch (axis) { 223 case View.X_AXIS: 224 return c.getAlignmentX(); 225 case View.Y_AXIS: 226 return c.getAlignmentY(); 227 } 228 } 229 return super.getAlignment(axis); 230 } 231 232 /** 233 * Sets the parent for a child view. 234 * The parent calls this on the child to tell it who its 235 * parent is, giving the view access to things like 236 * the hosting Container. The superclass behavior is 237 * executed, followed by a call to createComponent if 238 * the parent view parameter is non-null and a component 239 * has not yet been created. The embedded components parent 240 * is then set to the value returned by <code>getContainer</code>. 241 * If the parent view parameter is null, this view is being 242 * cleaned up, thus the component is removed from its parent. 243 * <p> 244 * The changing of the component hierarchy will 245 * touch the component lock, which is the one thing 246 * that is not safe from the View hierarchy. Therefore, 247 * this functionality is executed immediately if on the 248 * event thread, or is queued on the event queue if 249 * called from another thread (notification of change 250 * from an asynchronous update). 251 * 252 * @param p the parent 253 */ 254 public void setParent(View p) { 255 super.setParent(p); 256 if (SwingUtilities.isEventDispatchThread()) { 257 setComponentParent(); 258 } else { 259 Runnable callSetComponentParent = new Runnable() { 260 public void run() { 261 Document doc = getDocument(); 262 try { 263 if (doc instanceof AbstractDocument) { 264 ((AbstractDocument)doc).readLock(); 265 } 266 setComponentParent(); 267 Container host = getContainer(); 268 if (host != null) { 269 preferenceChanged(null, true, true); 270 host.repaint(); 271 } 272 } finally { 273 if (doc instanceof AbstractDocument) { 274 ((AbstractDocument)doc).readUnlock(); 275 } 276 } 277 } 278 }; 279 SwingUtilities.invokeLater(callSetComponentParent); 280 } 281 } 282 283 /** 284 * Set the parent of the embedded component 285 * with assurance that it is thread-safe. 286 */ 287 void setComponentParent() { 288 View p = getParent(); 289 if (p != null) { 290 Container parent = getContainer(); 291 if (parent != null) { 292 if (c == null) { 293 // try to build a component 294 Component comp = createComponent(); 295 if (comp != null) { 296 createdC = comp; 297 c = new Invalidator(comp); 298 } 299 } 300 if (c != null) { 301 if (c.getParent() == null) { 302 // components associated with the View tree are added 303 // to the hosting container with the View as a constraint. 304 parent.add(c, this); 305 parent.addPropertyChangeListener("enabled", c); 306 } 307 } 308 } 309 } else { 310 if (c != null) { 311 Container parent = c.getParent(); 312 if (parent != null) { 313 // remove the component from its hosting container 314 parent.remove(c); 315 parent.removePropertyChangeListener("enabled", c); 316 } 317 } 318 } 319 } 320 321 /** 322 * Provides a mapping from the coordinate space of the model to 323 * that of the view. 324 * 325 * @param pos the position to convert >=0 326 * @param a the allocated region to render into 327 * @return the bounding box of the given position is returned 328 * @exception BadLocationException if the given position does not 329 * represent a valid location in the associated document 330 * @see View#modelToView 331 */ 332 public Shape modelToView(int pos, Shape a, Position.Bias b) throws BadLocationException { 333 int p0 = getStartOffset(); 334 int p1 = getEndOffset(); 335 if ((pos >= p0) && (pos <= p1)) { 336 Rectangle r = a.getBounds(); 337 if (pos == p1) { 338 r.x += r.width; 339 } 340 r.width = 0; 341 return r; 342 } 343 throw new BadLocationException(pos + " not in range " + p0 + "," + p1, pos); 344 } 345 346 /** 347 * Provides a mapping from the view coordinate space to the logical 348 * coordinate space of the model. 349 * 350 * @param x the X coordinate >=0 351 * @param y the Y coordinate >=0 352 * @param a the allocated region to render into 353 * @return the location within the model that best represents 354 * the given point in the view 355 * @see View#viewToModel 356 */ 357 public int viewToModel(float x, float y, Shape a, 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 @SuppressWarnings("serial") // JDK-implementation class 381 class Invalidator extends Container implements 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 b If <code>true</code>, shows this component; 443 * otherwise, hides this component. 444 * @see #isVisible 445 * @since 1.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 Set<AWTKeyStroke> getFocusTraversalKeys(int id) { 489 return KeyboardFocusManager.getCurrentKeyboardFocusManager(). 490 getDefaultFocusTraversalKeys(id); 491 } 492 493 public void propertyChange(PropertyChangeEvent ev) { 494 Boolean enable = (Boolean) ev.getNewValue(); 495 if (getComponentCount() > 0) { 496 getComponent(0).setEnabled(enable); 497 } 498 } 499 500 Dimension min; 501 Dimension pref; 502 Dimension max; 503 float yalign; 504 float xalign; 505 506 } 507 508 }