1 /* 2 * graphics2d.js 3 * 4 * Sweet Home 3D, Copyright (c) 2024 Space Mushrooms <info@sweethome3d.com> 5 * 6 * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved. 7 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 8 * 9 * This code is free software; you can redistribute it and/or modify it 10 * under the terms of the GNU General Public License version 2 only, as 11 * published by the Free Software Foundation. Oracle designates this 12 * particular file as subject to the "Classpath" exception as provided 13 * by Oracle in the LICENSE file that accompanied OpenJDK 8 source code. 14 * 15 * This code is distributed in the hope that it will be useful, but WITHOUT 16 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 17 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 18 * version 2 for more details (a copy is included in the LICENSE file that 19 * accompanied this code). 20 * 21 * You should have received a copy of the GNU General Public License 22 * along with this program; if not, write to the Free Software 23 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 24 */ 25 26 // Graphics classes of OpenJDK 8 translated to Javascript 27 28 /** 29 * This class is a wrapper that implements 2D drawing functions on a canvas. 30 * Creates a new instance wrapping the given HTML canvas. 31 * @constructor 32 * @author Renaud Pawlak 33 * @author Emmanuel Puybaret 34 */ 35 function Graphics2D(canvas) { 36 this.context = canvas.getContext("2d"); 37 this.context.imageSmoothingEnabled = true; 38 this.context.setTransform(1, 0, 0, 1, 0, 0); 39 // Need to store also the current transform in Graphics2D 40 // because context.currentTransform isn't supported under all browsers 41 this.currentTransform = new java.awt.geom.AffineTransform(1., 0., 0., 1., 0., 0.); 42 43 var computedStyle = window.getComputedStyle(canvas); 44 this.context.font = computedStyle.font; 45 this.color = computedStyle.color; 46 this.background = computedStyle.background; 47 } 48 Graphics2D.prototype.constructor = Graphics2D; 49 50 /** 51 * Clears the canvas. 52 */ 53 Graphics2D.prototype.clear = function() { 54 this.context.clearRect(0, 0, this.context.canvas.width, this.context.canvas.height); 55 } 56 57 /** 58 * Gets the wrapped canvas context. 59 */ 60 Graphics2D.prototype.getContext = function() { 61 return this.context; 62 } 63 64 /** 65 * Draws a shape on the canvas using the current stroke. 66 * @param {java.awt.Shape} shape the shape to be drawn 67 */ 68 Graphics2D.prototype.draw = function(shape) { 69 this.createPathFromShape(shape); 70 this.context.stroke(); 71 } 72 73 /** 74 * @param {java.awt.Shape} shape the shape to create a path from 75 * @private 76 */ 77 Graphics2D.prototype.createPathFromShape = function(s) { 78 this.context.beginPath(); 79 var it = s.getPathIterator(java.awt.geom.AffineTransform.getTranslateInstance(0, 0)); 80 var coords = new Array(6); 81 while (!it.isDone()) { 82 switch (it.currentSegment(coords)) { 83 case java.awt.geom.PathIterator.SEG_MOVETO: 84 this.context.moveTo(coords[0], coords[1]); 85 break; 86 case java.awt.geom.PathIterator.SEG_LINETO: 87 this.context.lineTo(coords[0], coords[1]); 88 break; 89 case java.awt.geom.PathIterator.SEG_QUADTO: 90 this.context.lineTo(coords[0], coords[1]); 91 break; 92 case java.awt.geom.PathIterator.SEG_CUBICTO: 93 this.context.bezierCurveTo(coords[0], coords[1], coords[2], coords[3], coords[4], coords[5]); 94 break; 95 case java.awt.geom.PathIterator.SEG_CLOSE: 96 this.context.closePath(); 97 break; 98 default: 99 break; 100 } 101 it.next(); 102 } 103 } 104 105 /** 106 * Fills a shape on the canvas using the current paint. 107 * @param {java.awt.Shape} shape the shape to be filled 108 */ 109 Graphics2D.prototype.fill = function(s) { 110 this.createPathFromShape(s); 111 this.context.fill(); 112 } 113 114 /** 115 * Draws an image on the canvas. 116 * @param {HTMLImageElement} img the image to be drawn 117 * @param {number} x 118 * @param {number} y 119 * @param {string} [bgcolor] 120 */ 121 Graphics2D.prototype.drawImage = function(img, x, y, bgcolor) { 122 this.context.drawImage(img, x, y); 123 } 124 125 /** 126 * Draws an image on the canvas. 127 * @param {HTMLImageElement} img the image to be drawn 128 * @param {number} x 129 * @param {number} y 130 * @param {number} width 131 * @param {number} height 132 * @param {string} [bgcolor] 133 */ 134 Graphics2D.prototype.drawImageWithSize = function(img, x, y, width, height, bgcolor) { 135 this.context.drawImage(img, x, y, width, height); 136 } 137 138 /** 139 * Gets the current clip. 140 * @return {java.awt.Shape} the clip as a shape 141 */ 142 Graphics2D.prototype.getClip = function() { 143 return this.clipZone !== undefined ? this.clipZone : null; 144 } 145 146 /** 147 * Sets the current clip. 148 * @param {java.awt.Shape} clip the clip as a shape 149 */ 150 Graphics2D.prototype.setClip = function(clip) { 151 if (this.clipZone !== undefined) { 152 this.context.restore(); 153 delete this.clipZone; 154 } 155 if (clip != null) { 156 this.clip(clip); 157 } 158 } 159 160 /** 161 * Adds the given clip to the current clip. 162 * @param {java.awt.Shape} clip the added clip as a shape 163 */ 164 Graphics2D.prototype.clip = function(clip) { 165 if (this.clipZone === undefined) { 166 // Save current clipping zone 167 this.context.save(); 168 } 169 this.clipZone = clip; 170 if (clip != null) { 171 this.createPathFromShape(clip); 172 this.context.clip(); 173 } 174 } 175 176 /** 177 * Sets the current clip as a rectangle region. 178 * @param {number} x 179 * @param {number} y 180 * @param {number} width 181 * @param {number} height 182 */ 183 Graphics2D.prototype.clipRect = function(x, y, width, height) { 184 this.setClip(new java.awt.geom.Rectangle2D.Double(x, y, width, height)); 185 } 186 187 /** 188 * Translates the canvas transform matrix. 189 * @param {number} x 190 * @param {number} y 191 */ 192 Graphics2D.prototype.translate = function(x, y) { 193 this.currentTransform.translate(x, y); 194 this.context.translate(x, y); 195 } 196 197 /** 198 * Draws a string outline with the current stroke. 199 * @param {string} str 200 * @param {number} x 201 * @param {number} y 202 */ 203 Graphics2D.prototype.drawStringOutline = function(str, x, y) { 204 this.context.strokeText(str, x, y); 205 } 206 207 /** 208 * Draws a string with the current paint. 209 * @param {string} str 210 * @param {number} x 211 * @param {number} y 212 */ 213 Graphics2D.prototype.drawString = function(str, x, y) { 214 this.context.fillText(str, x, y); 215 } 216 217 /** 218 * Fills the given rectangular region with the current paint. 219 * @param {number} x 220 * @param {number} y 221 * @param {number} width 222 * @param {number} height 223 */ 224 Graphics2D.prototype.fillRect = function(x, y, width, height) { 225 this.context.fillRect(x, y, width, height); 226 } 227 228 /** 229 * Sets the current stroke and fill style as a CSS style. 230 * @param {string} color a CSS style 231 */ 232 Graphics2D.prototype.setColor = function(color) { 233 this.color = color; 234 this.context.strokeStyle = color; 235 this.context.fillStyle = color; 236 } 237 238 /** 239 * Gets the current color. 240 */ 241 Graphics2D.prototype.getColor = function() { 242 return this.color; 243 } 244 245 Graphics2D.prototype.setComposite = function(c) { 246 this.setColor(c); 247 } 248 249 /** 250 * Sets the alpha component for all subsequent drawing and fill operations. 251 * @param {number} alpha 252 */ 253 Graphics2D.prototype.setAlpha = function(alpha) { 254 this.context.globalAlpha = alpha; 255 } 256 257 /** 258 * Gets the alpha component of the canvas. 259 * @return {number} 260 */ 261 Graphics2D.prototype.getAlpha = function() { 262 return this.context.globalAlpha; 263 } 264 265 /** 266 * Rotates the canvas current transform matrix. 267 * @param {number} theta the rotation angle 268 * @param {number} [x] the rotation origin (x) 269 * @param {number} [y] the rotation origin (y) 270 */ 271 Graphics2D.prototype.rotate = function(theta, x, y) { 272 if (typeof x === 'number' && typeof y === 'number') { 273 this.currentTransform.rotate(theta, x, y); 274 this.context.translate(-x, -y); 275 this.context.rotate(theta); 276 this.context.translate(x, y); 277 } else { 278 this.currentTransform.rotate(theta); 279 this.context.rotate(theta); 280 } 281 } 282 283 /** 284 * Scales the canvas current transform matrix. 285 * @param {number} sx the x scale factor 286 * @param {number} sy the y scale factor 287 */ 288 Graphics2D.prototype.scale = function(sx, sy) { 289 this.currentTransform.scale(sx, sy); 290 this.context.scale(sx, sy); 291 } 292 293 /** 294 * Shears the canvas current transform matrix. 295 * @param {number} shx the x shear factor 296 * @param {number} shy the y shear factor 297 */ 298 Graphics2D.prototype.shear = function(shx, shy) { 299 this.currentTransform.shear(shx, shy); 300 this.context.transform(0, shx, shy, 0, 0, 0); 301 } 302 303 /** 304 * @ignore 305 */ 306 Graphics2D.prototype.dispose = function() { 307 } 308 309 /** 310 * Sets the current font. 311 * @param {string} font a CSS font descriptor 312 */ 313 Graphics2D.prototype.setFont = function(font) { 314 this.context.font = font; 315 } 316 317 /** 318 * Gets the current font. 319 * @return {string} a CSS font descriptor 320 */ 321 Graphics2D.prototype.getFont = function() { 322 return this.context.font; 323 } 324 325 /** 326 * Sets the fill style as a color. 327 * @param {string} color a CSS color descriptor 328 */ 329 Graphics2D.prototype.setBackground = function(color) { 330 this.background = color; 331 this.context.fillStyle = color; 332 } 333 334 /** 335 * Gets the fill style. 336 * @return {string} a CSS color descriptor 337 */ 338 Graphics2D.prototype.getBackground = function() { 339 return this.background; 340 } 341 342 /** 343 * Sets (overrides) the current transform matrix. 344 * @param {java.awt.geom.AffineTransform} transform the new transform matrix 345 */ 346 Graphics2D.prototype.setTransform = function(transform) { 347 this.currentTransform.setTransform(transform); 348 this.context.setTransform(transform.getScaleX(), transform.getShearY(), transform.getShearX(), transform.getScaleY(), transform.getTranslateX(), transform.getTranslateY()); 349 } 350 351 /** 352 * Gets the current transform matrix. 353 * @return {java.awt.geom.AffineTransform} the current transform matrix 354 */ 355 Graphics2D.prototype.getTransform = function() { 356 return new java.awt.geom.AffineTransform(this.currentTransform); 357 } 358 359 /** 360 * Applies the given transform matrix to the current transform matrix. 361 * @param {java.awt.geom.AffineTransform} transform the transform matrix to be applied 362 */ 363 Graphics2D.prototype.transform = function(transform) { 364 this.currentTransform.concatenate(transform); 365 this.context.transform(transform.getScaleX(), transform.getShearX(), transform.getShearY(), transform.getScaleY(), transform.getTranslateX(), transform.getTranslateY()); 366 } 367 368 Graphics2D.prototype.setPaintMode = function() { 369 } 370 371 /** 372 * Gets the current paint. 373 * @return {string|CanvasPattern} 374 */ 375 Graphics2D.prototype.getPaint = function() { 376 return this.color; 377 } 378 379 /** 380 * Sets the current paint. 381 * @param {string|CanvasPattern} paint 382 */ 383 Graphics2D.prototype.setPaint = function(paint) { 384 if (typeof paint === "string") { 385 this.setColor(paint); 386 } else { 387 this.context.strokeStyle = paint; 388 this.context.fillStyle = paint; 389 } 390 } 391 392 /** 393 * Sets the current stroke. 394 */ 395 Graphics2D.prototype.setStroke = function(s) { 396 this.context.lineWidth = s.getLineWidth(); 397 if (s.getDashArray() != null) { 398 this.context.setLineDash(s.getDashArray()); 399 this.context.lineDashOffset = s.getDashPhase(); 400 } else { 401 this.context.setLineDash([]); 402 } 403 switch (s.getLineJoin()) { 404 case java.awt.BasicStroke.JOIN_BEVEL: 405 this.context.lineJoin = "bevel"; 406 break; 407 case java.awt.BasicStroke.JOIN_MITER: 408 this.context.lineJoin = "miter"; 409 break; 410 case java.awt.BasicStroke.JOIN_ROUND: 411 this.context.lineJoin = "round"; 412 break; 413 } 414 switch (s.getEndCap()) { 415 case java.awt.BasicStroke.CAP_BUTT: 416 this.context.lineCap = "butt"; 417 break; 418 case java.awt.BasicStroke.CAP_ROUND: 419 this.context.lineCap = "round"; 420 break; 421 case java.awt.BasicStroke.CAP_SQUARE: 422 this.context.lineCap = "square"; 423 break; 424 } 425 this.context.miterLimit = s.getMiterLimit(); 426 } 427 428 /** 429 * Creates a pattern from an image. 430 * @param {HTMLImageElement} image 431 * @return CanvasPattern 432 */ 433 Graphics2D.prototype.createPattern = function(image) { 434 return this.context.createPattern(image, 'repeat'); 435 } 436 437 438 /** 439 * This utility class allows to get the metrics of a given font. Note that this class will approximate 440 * the metrics on older browsers where CanvasRenderingContext2D.measureText() is only partially implemented. 441 * Builds a font metrics instance for the given font. 442 * @param {string} font the given font, in a CSS canvas-compatible representation 443 * @constructor 444 * @author Renaud Pawlak 445 * @author Emmanuel Puybaret 446 */ 447 function FontMetrics(font) { 448 this.approximated = false; 449 this.font = font; 450 this.cached = false; 451 } 452 FontMetrics.prototype.constructor = FontMetrics; 453 454 /** 455 * Gets the bounds of the given string for this font metrics. 456 * @param {string} aString the string to get the bounds of 457 * @return {java.awt.geom.Rectangle2D} the bounds as an instance of java.awt.geom.Rectangle2D 458 */ 459 FontMetrics.prototype.getStringBounds = function(aString) { 460 this.compute(aString); 461 this.cached = false; 462 return new java.awt.geom.Rectangle2D.Double(0, -this.ascent, this.width, this.height); 463 } 464 465 /** 466 * Gets the font ascent. 467 * @return {number} the font ascent 468 */ 469 FontMetrics.prototype.getAscent = function() { 470 if (!this.cached) { 471 this.compute("Llp"); 472 } 473 return this.ascent; 474 } 475 476 /** 477 * Gets the font descent. 478 * @return {number} the font descent 479 */ 480 FontMetrics.prototype.getDescent = function() { 481 if (!this.cached) { 482 this.compute("Llp"); 483 } 484 return this.descent; 485 } 486 487 /** 488 * Gets the font height. 489 * @return {number} the font height 490 */ 491 FontMetrics.prototype.getHeight = function() { 492 if (!this.cached) { 493 this.compute("Llp"); 494 } 495 return this.height; 496 } 497 498 /** 499 * Computes the various dimensions of the given string, for the current canvas and font. 500 * This function caches the results so that it can be fast accessed in other functions. 501 * @param {string} aString the string to compute the dimensions of 502 * @private 503 */ 504 FontMetrics.prototype.compute = function(aString) { 505 if (!FontMetrics.context) { 506 FontMetrics.context = document.createElement("canvas").getContext("2d"); 507 } 508 FontMetrics.context.font = this.font; 509 var textMetrics = FontMetrics.context.measureText(aString); 510 if (textMetrics.fontBoundingBoxAscent) { 511 this.cached = true; 512 this.ascent = textMetrics.fontBoundingBoxAscent; 513 this.descent = textMetrics.fontBoundingBoxDescent; 514 this.height = this.ascent + this.descent; 515 this.width = textMetrics.width; 516 } else { 517 // height info is not available on old browsers, so we build an approx. 518 if (!this.approximated) { 519 this.approximated = true; 520 var font = new Font(this.font); 521 this.height = parseInt(font.size); 522 if (["Times", "Serif", "Helvetica"].indexOf(font.family) === -1) { 523 this.height *= 1.18; 524 } 525 this.descent = 0.23 * this.height; 526 this.ascent = this.height - this.descent; 527 this.cached = true; 528 } 529 this.width = textMetrics.width; 530 } 531 } 532 533 534 /** 535 * A font utility class. 536 * Creates a new font from a CSS font descriptor. 537 * @param cssFontDecriptor {string|Object} the font descriptor as a CSS string or an object {style, size, family, weight} 538 * @constructor 539 * @author Renaud Pawlak 540 */ 541 function Font(cssFontDecriptor) { 542 // font desciptors are normalized by the browser using the getComputedStyle function 543 if (!Font.element) { 544 Font.element = document.createElement('span'); 545 Font.element.style.display = 'none'; 546 document.body.appendChild(Font.element); 547 } 548 if (typeof cssFontDecriptor === 'string') { 549 Font.element.style.font = cssFontDecriptor; 550 } else { 551 if (cssFontDecriptor.style) { 552 Font.element.style.fontStyle = cssFontDecriptor.style; 553 } 554 if (cssFontDecriptor.size) { 555 Font.element.style.fontSize = cssFontDecriptor.size; 556 } 557 if (cssFontDecriptor.family) { 558 Font.element.style.fontFamily = cssFontDecriptor.family; 559 } 560 if (cssFontDecriptor.weight) { 561 Font.element.style.fontWeight = cssFontDecriptor.weight; 562 } 563 } 564 this.computedStyle = window.getComputedStyle(Font.element); 565 this.size = this.computedStyle.fontSize; 566 this.family = this.computedStyle.fontFamily; 567 this.style = this.computedStyle.fontStyle; 568 this.weight = this.computedStyle.fontWeight; 569 } 570 Font.prototype.constructor = Font; 571 572 /** 573 * Returns the font as a browser-normalized CSS string. 574 * @return {string} 575 */ 576 Font.prototype.toString = function() { 577 var font = ''; 578 if (this.weight != 'normal') { 579 font = this.weight + ' '; 580 } 581 if (this.style != 'normal') { 582 font += this.style + ' '; 583 } 584 font += this.size + ' ' + this.family; 585 return font; 586 } 587 588 589 /** 590 * Creates an empty action. 591 * Adapted from javax.swing.AbstractAction 592 * @constructor 593 * @author Georges Saab 594 * @ignore 595 */ 596 function AbstractAction() { 597 this.enabled = true; 598 } 599 600 /** 601 * Useful constants that can be used as the storage-retrieval key 602 * when setting or getting one of this object's properties (text 603 * or icon). 604 */ 605 /** 606 * Not currently used. 607 */ 608 AbstractAction.DEFAULT = "Default"; 609 610 /** 611 * The key used for storing the <code>String</code> name 612 * for the action, used for a menu or button. 613 */ 614 AbstractAction.NAME = "Name"; 615 616 /** 617 * The key used for storing a short <code>String</code> 618 * description for the action, used for tooltip text. 619 */ 620 AbstractAction.SHORT_DESCRIPTION = "ShortDescription"; 621 622 /** 623 * The key used for storing a longer <code>String</code> 624 * description for the action, could be used for context-sensitive help. 625 */ 626 AbstractAction.LONG_DESCRIPTION = "LongDescription"; 627 628 /** 629 * The key used for storing a small <code>Icon</code>, such 630 * as <code>ImageIcon</code>. This is typically used with 631 * menus such as <code>JMenuItem</code>. 632 * <p>If the same <code>Action</code> is used with menus and buttons you'll 633 * typically specify both a <code>SMALL_ICON</code> and a 634 * <code>LARGE_ICON_KEY</code>. The menu will use the 635 * <code>SMALL_ICON</code> and the button will use the 636 * <code>LARGE_ICON_KEY</code>. 637 */ 638 AbstractAction.SMALL_ICON = "SmallIcon"; 639 640 /** 641 * The key used to determine the command <code>String</code> for the 642 * <code>ActionEvent</code> that will be created when an 643 * <code>Action</code> is going to be notified as the result of 644 * residing in a <code>Keymap</code> associated with a 645 * <code>JComponent</code>. 646 */ 647 AbstractAction.ACTION_COMMAND_KEY = "ActionCommandKey"; 648 649 /** 650 * The key used for storing a <code>KeyStroke</code> to be used as the 651 * accelerator for the action. 652 */ 653 AbstractAction.ACCELERATOR_KEY = "AcceleratorKey"; 654 655 /** 656 * The key used for storing an <code>Integer</code> that corresponds to 657 * one of the <code>KeyEvent</code> key codes. The value is 658 * commonly used to specify a mnemonic. For example: 659 * <code>myAction.putValue(Action.MNEMONIC_KEY, KeyEvent.VK_A)</code> 660 * sets the mnemonic of <code>myAction</code> to 'a', while 661 * <code>myAction.putValue(Action.MNEMONIC_KEY, KeyEvent.getExtendedKeyCodeForChar('\u0444'))</code> 662 * sets the mnemonic of <code>myAction</code> to Cyrillic letter "Ef". 663 */ 664 AbstractAction.MNEMONIC_KEY = "MnemonicKey"; 665 666 /** 667 * The key used for storing a <code>Boolean</code> that corresponds 668 * to the selected state. This is typically used only for components 669 * that have a meaningful selection state. For example, 670 * <code>JRadioButton</code> and <code>JCheckBox</code> make use of 671 * this but instances of <code>JMenu</code> don't. 672 * <p>This property differs from the others in that it is both read 673 * by the component and set by the component. For example, 674 * if an <code>Action</code> is attached to a <code>JCheckBox</code> 675 * the selected state of the <code>JCheckBox</code> will be set from 676 * that of the <code>Action</code>. If the user clicks on the 677 * <code>JCheckBox</code> the selected state of the <code>JCheckBox</code> 678 * <b>and</b> the <code>Action</code> will <b>both</b> be updated. 679 */ 680 AbstractAction.SELECTED_KEY = "SwingSelectedKey"; 681 682 /** 683 * The key used for storing an <code>Integer</code> that corresponds 684 * to the index in the text (identified by the <code>NAME</code> 685 * property) that the decoration for a mnemonic should be rendered at. If 686 * the value of this property is greater than or equal to the length of 687 * the text, it will treated as -1. 688 */ 689 AbstractAction.DISPLAYED_MNEMONIC_INDEX_KEY = "SwingDisplayedMnemonicIndexKey"; 690 691 /** 692 * The key used for storing an <code>Icon</code>. This is typically 693 * used by buttons, such as <code>JButton</code> and 694 * <code>JToggleButton</code>. 695 * <p>If the same <code>Action</code> is used with menus and buttons you'll 696 * typically specify both a <code>SMALL_ICON</code> and a 697 * <code>LARGE_ICON_KEY</code>. The menu will use the 698 * <code>SMALL_ICON</code> and the button the <code>LARGE_ICON_KEY</code>. 699 */ 700 AbstractAction.LARGE_ICON_KEY = "SwingLargeIconKey"; 701 702 /** 703 * Unsupported operation. Subclasses should override this method if they want 704 * to associate a real action to this class. 705 * @param {java.awt.event.ActionEvent} ev 706 */ 707 AbstractAction.prototype.actionPerformed = function(ev) { 708 throw new UnsupportedOperationException(); 709 } 710 711 /** 712 * Gets the <code>Object</code> associated with the specified key. 713 * @param {string} key a string containing the specified <code>key</code> 714 * @return {Object} the binding <code>Object</code> stored with this key; if there 715 * are no keys, it will return <code>null</code> 716 */ 717 AbstractAction.prototype.getValue = function(key) { 718 if (key == "enabled") { 719 return this.enabled; 720 } 721 if (this.arrayTable == null) { 722 return null; 723 } 724 return this.arrayTable[key]; 725 } 726 727 /** 728 * Sets the <code>Value</code> associated with the specified key. 729 * @param {string} key the <code>String</code> that identifies the stored object 730 * @param {Object} newValue the <code>Object</code> to store using this key 731 */ 732 AbstractAction.prototype.putValue = function(key, newValue) { 733 var oldValue = null; 734 if (key == "enabled") { 735 if (newValue == null || !(newValue instanceof Boolean)) { 736 newValue = false; 737 } 738 oldValue = enabled; 739 this.enabled = newValue; 740 } else { 741 if (this.arrayTable == null) { 742 this.arrayTable = {}; 743 } 744 if (this.arrayTable[key] != null) 745 oldValue = this.arrayTable[key]; 746 // Remove the entry for key if newValue is null 747 // else put in the newValue for key. 748 if (newValue == null) { 749 delete this.arrayTable.key; 750 } else { 751 this.arrayTable[key] = newValue; 752 } 753 } 754 if (this.changeSupport != null) { 755 this.firePropertyChange(key, oldValue, newValue); 756 } 757 } 758 759 /** 760 * Returns true if the action is enabled. 761 * @return {boolean} true if the action is enabled, false otherwise 762 */ 763 AbstractAction.prototype.isEnabled = function() { 764 return this.enabled; 765 } 766 767 /** 768 * Sets whether the Action is enabled. 769 * @param {boolean} newValue true to enable the action, false to disable it 770 */ 771 AbstractAction.prototype.setEnabled = function(enabled) { 772 if (this.enabled != enabled) { 773 this.enabled = enabled; 774 if (this.changeSupport != null) { 775 this.firePropertyChange("enabled", !enabled, enabled); 776 } 777 } 778 } 779 780 /** 781 * Returns an array of <code>Object</code> which are keys for 782 * which values have been set for this <code>AbstractAction</code>, 783 * or <code>null</code> if no keys have values set. 784 * @return an array of key objects, or <code>null</code> if no keys have values set 785 */ 786 AbstractAction.prototype.getKeys = function() { 787 if (this.arrayTable == null) { 788 return null; 789 } 790 return this.arrayTable.getOwnPropertyNames(); 791 } 792 793 /** 794 * Supports reporting bound property changes. This method can be called 795 * when a bound property has changed and it will send the appropriate 796 * <code>PropertyChangeEvent</code> to any registered listener. 797 * @protected 798 */ 799 AbstractAction.prototype.firePropertyChange = function(propertyName, oldValue, newValue) { 800 if (this.changeSupport == null 801 || (oldValue != null && newValue != null && oldValue == newValue)) { 802 return; 803 } 804 this.changeSupport.firePropertyChange(propertyName, oldValue, newValue); 805 } 806 807 /** 808 * Adds a <code>PropertyChangeListener</code> to the listener list. 809 * The listener is registered for all properties. 810 * <p>A <code>PropertyChangeEvent</code> will get fired in response to setting 811 * a bound property, e.g. <code>setFont</code>, <code>setBackground</code>, 812 * or <code>setForeground</code>. 813 * Note that if the current component is inheriting its foreground, 814 * background, or font from its container, then no event will be 815 * fired in response to a change in the inherited property. 816 * @param {PropertyChangeListener} listener The <code>PropertyChangeListener</code> to be added 817 */ 818 AbstractAction.prototype.addPropertyChangeListener = function(listener) { 819 if (this.changeSupport == null) { 820 this.changeSupport = new PropertyChangeSupport(this); 821 } 822 this.changeSupport.addPropertyChangeListener(listener); 823 } 824 825 /** 826 * Removes a <code>PropertyChangeListener</code> from the listener list. 827 * This removes a <code>PropertyChangeListener</code> that was registered 828 * for all properties. 829 * @param {PropertyChangeListener} listener the <code>PropertyChangeListener</code> to be removed 830 */ 831 AbstractAction.prototype.removePropertyChangeListener = function(listener) { 832 if (this.changeSupport == null) { 833 return; 834 } 835 this.changeSupport.removePropertyChangeListener(listener); 836 } 837 838 /** 839 * Returns an array of all the <code>PropertyChangeListener</code>s added 840 * to this AbstractAction with addPropertyChangeListener(). 841 * @return {PropertyChangeListener[]} all of the <code>PropertyChangeListener</code>s added or an empty 842 * array if no listeners have been added 843 */ 844 AbstractAction.prototype.getPropertyChangeListeners = function() { 845 if (this.changeSupport == null) { 846 return new PropertyChangeListener[0]; 847 } 848 return this.changeSupport.getPropertyChangeListeners(); 849 } 850