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 */ 89 protected Component createComponent() { 90 AttributeSet attr = getElement().getAttributes(); 91 Component comp = StyleConstants.getComponent(attr); 92 return comp; 93 } 94 95 /** 96 * Fetch the component associated with the view. 97 */ 98 public final Component getComponent() { 99 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) ? 116 (Rectangle) a : 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) throws BadLocationException { 330 int p0 = getStartOffset(); 331 int p1 = getEndOffset(); 332 if ((pos >= p0) && (pos <= p1)) { 333 Rectangle r = a.getBounds(); 334 if (pos == p1) { 335 r.x += r.width; 336 } 337 r.width = 0; 338 return r; 339 } 340 throw new BadLocationException(pos + " not in range " + p0 + "," + p1, pos); 341 } 342 343 /** 344 * Provides a mapping from the view coordinate space to the logical 345 * coordinate space of the model. 346 * 347 * @param x the X coordinate >=0 348 * @param y the Y coordinate >=0 349 * @param a the allocated region to render into 350 * @return the location within the model that best represents 351 * the given point in the view 352 * @see View#viewToModel 353 */ 354 public int viewToModel(float x, float y, Shape a, Position.Bias[] bias) { 355 Rectangle alloc = (Rectangle) a; 356 if (x < alloc.x + (alloc.width / 2)) { 357 bias[0] = Position.Bias.Forward; 358 return getStartOffset(); 359 } 360 bias[0] = Position.Bias.Backward; 361 return getEndOffset(); 362 } 363 364 // --- member variables ------------------------------------------------ 365 366 private Component createdC; 367 private Invalidator c; 368 369 /** 370 * This class feeds the invalidate back to the 371 * hosting View. This is needed to get the View 372 * hierarchy to consider giving the component 373 * a different size (i.e. layout may have been 374 * cached between the associated view and the 375 * container hosting this component). 376 */ 377 @SuppressWarnings("serial") // JDK-implementation class 378 class Invalidator extends Container implements PropertyChangeListener { 379 380 // NOTE: When we remove this class we are going to have to some 381 // how enforce setting of the focus traversal keys on the children 382 // so that they don't inherit them from the JEditorPane. We need 383 // to do this as JEditorPane has abnormal bindings (it is a focus cycle 384 // root) and the children typically don't want these bindings as well. 385 386 Invalidator(Component child) { 387 setLayout(null); 388 add(child); 389 cacheChildSizes(); 390 } 391 392 /** 393 * The components invalid layout needs 394 * to be propagated through the view hierarchy 395 * so the views (which position the component) 396 * can have their layout recomputed. 397 */ 398 public void invalidate() { 399 super.invalidate(); 400 if (getParent() != null) { 401 preferenceChanged(null, true, true); 402 } 403 } 404 405 public void doLayout() { 406 cacheChildSizes(); 407 } 408 409 public void setBounds(int x, int y, int w, int h) { 410 super.setBounds(x, y, w, h); 411 if (getComponentCount() > 0) { 412 getComponent(0).setSize(w, h); 413 } 414 cacheChildSizes(); 415 } 416 417 public void validateIfNecessary() { 418 if (!isValid()) { 419 validate(); 420 } 421 } 422 423 private void cacheChildSizes() { 424 if (getComponentCount() > 0) { 425 Component child = getComponent(0); 426 min = child.getMinimumSize(); 427 pref = child.getPreferredSize(); 428 max = child.getMaximumSize(); 429 yalign = child.getAlignmentY(); 430 xalign = child.getAlignmentX(); 431 } else { 432 min = pref = max = new Dimension(0, 0); 433 } 434 } 435 436 /** 437 * Shows or hides this component depending on the value of parameter 438 * <code>b</code>. 439 * @param b If <code>true</code>, shows this component; 440 * otherwise, hides this component. 441 * @see #isVisible 442 * @since 1.1 443 */ 444 public void setVisible(boolean b) { 445 super.setVisible(b); 446 if (getComponentCount() > 0) { 447 getComponent(0).setVisible(b); 448 } 449 } 450 451 /** 452 * Overridden to fix 4759054. Must return true so that content 453 * is painted when inside a CellRendererPane which is normally 454 * invisible. 455 */ 456 public boolean isShowing() { 457 return true; 458 } 459 460 public Dimension getMinimumSize() { 461 validateIfNecessary(); 462 return min; 463 } 464 465 public Dimension getPreferredSize() { 466 validateIfNecessary(); 467 return pref; 468 } 469 470 public Dimension getMaximumSize() { 471 validateIfNecessary(); 472 return max; 473 } 474 475 public float getAlignmentX() { 476 validateIfNecessary(); 477 return xalign; 478 } 479 480 public float getAlignmentY() { 481 validateIfNecessary(); 482 return yalign; 483 } 484 485 public Set<AWTKeyStroke> getFocusTraversalKeys(int id) { 486 return KeyboardFocusManager.getCurrentKeyboardFocusManager(). 487 getDefaultFocusTraversalKeys(id); 488 } 489 490 public void propertyChange(PropertyChangeEvent ev) { 491 Boolean enable = (Boolean) ev.getNewValue(); 492 if (getComponentCount() > 0) { 493 getComponent(0).setEnabled(enable); 494 } 495 } 496 497 Dimension min; 498 Dimension pref; 499 Dimension max; 500 float yalign; 501 float xalign; 502 503 } 504 505 }