1 /*
   2  * Copyright (c) 1997, 2014, 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.plaf.basic;
  26 
  27 import java.awt.*;
  28 import java.awt.event.*;
  29 import java.beans.*;
  30 import java.net.URL;
  31 import java.net.MalformedURLException;
  32 import javax.swing.*;
  33 import javax.swing.text.*;
  34 import javax.swing.text.html.*;
  35 import javax.swing.plaf.*;
  36 import javax.swing.border.*;
  37 
  38 
  39 /**
  40  * Provides the look and feel for a JEditorPane.
  41  * <p>
  42  * <strong>Warning:</strong>
  43  * Serialized objects of this class will not be compatible with
  44  * future Swing releases. The current serialization support is
  45  * appropriate for short term storage or RMI between applications running
  46  * the same version of Swing.  As of 1.4, support for long term storage
  47  * of all JavaBeans&trade;
  48  * has been added to the <code>java.beans</code> package.
  49  * Please see {@link java.beans.XMLEncoder}.
  50  *
  51  * @author  Timothy Prinzing
  52  */
  53 @SuppressWarnings("serial") // Same-version serialization only
  54 public class BasicEditorPaneUI extends BasicTextUI {
  55 
  56     /**
  57      * Creates a UI for the JTextPane.
  58      *
  59      * @param c the JTextPane component
  60      * @return the UI
  61      */
  62     public static ComponentUI createUI(JComponent c) {
  63         return new BasicEditorPaneUI();
  64     }
  65 
  66     /**
  67      * Creates a new BasicEditorPaneUI.
  68      */
  69     public BasicEditorPaneUI() {
  70         super();
  71     }
  72 
  73     /**
  74      * Fetches the name used as a key to lookup properties through the
  75      * UIManager.  This is used as a prefix to all the standard
  76      * text properties.
  77      *
  78      * @return the name ("EditorPane")
  79      */
  80     protected String getPropertyPrefix() {
  81         return "EditorPane";
  82     }
  83 
  84     /**
  85      *{@inheritDoc}
  86      *
  87      * @since 1.5
  88      */
  89     public void installUI(JComponent c) {
  90         super.installUI(c);
  91         updateDisplayProperties(c.getFont(),
  92                                 c.getForeground());
  93     }
  94 
  95     /**
  96      *{@inheritDoc}
  97      *
  98      * @since 1.5
  99      */
 100     public void uninstallUI(JComponent c) {
 101         cleanDisplayProperties();
 102         super.uninstallUI(c);
 103     }
 104 
 105     /**
 106      * Fetches the EditorKit for the UI.  This is whatever is
 107      * currently set in the associated JEditorPane.
 108      *
 109      * @return the editor capabilities
 110      * @see TextUI#getEditorKit
 111      */
 112     public EditorKit getEditorKit(JTextComponent tc) {
 113         JEditorPane pane = (JEditorPane) getComponent();
 114         return pane.getEditorKit();
 115     }
 116 
 117     /**
 118      * Fetch an action map to use.  The map for a JEditorPane
 119      * is not shared because it changes with the EditorKit.
 120      */
 121     ActionMap getActionMap() {
 122         ActionMap am = new ActionMapUIResource();
 123         am.put("requestFocus", new FocusAction());
 124         EditorKit editorKit = getEditorKit(getComponent());
 125         if (editorKit != null) {
 126             Action[] actions = editorKit.getActions();
 127             if (actions != null) {
 128                 addActions(am, actions);
 129             }
 130         }
 131         am.put(TransferHandler.getCutAction().getValue(Action.NAME),
 132                 TransferHandler.getCutAction());
 133         am.put(TransferHandler.getCopyAction().getValue(Action.NAME),
 134                 TransferHandler.getCopyAction());
 135         am.put(TransferHandler.getPasteAction().getValue(Action.NAME),
 136                 TransferHandler.getPasteAction());
 137         return am;
 138     }
 139 
 140     /**
 141      * This method gets called when a bound property is changed
 142      * on the associated JTextComponent.  This is a hook
 143      * which UI implementations may change to reflect how the
 144      * UI displays bound properties of JTextComponent subclasses.
 145      * This is implemented to rebuild the ActionMap based upon an
 146      * EditorKit change.
 147      *
 148      * @param evt the property change event
 149      */
 150     protected void propertyChange(PropertyChangeEvent evt) {
 151         super.propertyChange(evt);
 152         String name = evt.getPropertyName();
 153         if ("editorKit".equals(name)) {
 154             ActionMap map = SwingUtilities.getUIActionMap(getComponent());
 155             if (map != null) {
 156                 Object oldValue = evt.getOldValue();
 157                 if (oldValue instanceof EditorKit) {
 158                     Action[] actions = ((EditorKit)oldValue).getActions();
 159                     if (actions != null) {
 160                         removeActions(map, actions);
 161                     }
 162                 }
 163                 Object newValue = evt.getNewValue();
 164                 if (newValue instanceof EditorKit) {
 165                     Action[] actions = ((EditorKit)newValue).getActions();
 166                     if (actions != null) {
 167                         addActions(map, actions);
 168                     }
 169                 }
 170             }
 171             updateFocusTraversalKeys();
 172         } else if ("editable".equals(name)) {
 173             updateFocusTraversalKeys();
 174         } else if ("foreground".equals(name)
 175                    || "font".equals(name)
 176                    || "document".equals(name)
 177                    || JEditorPane.W3C_LENGTH_UNITS.equals(name)
 178                    || JEditorPane.HONOR_DISPLAY_PROPERTIES.equals(name)
 179                    ) {
 180             JComponent c = getComponent();
 181             updateDisplayProperties(c.getFont(), c.getForeground());
 182             if ( JEditorPane.W3C_LENGTH_UNITS.equals(name)
 183                  || JEditorPane.HONOR_DISPLAY_PROPERTIES.equals(name) ) {
 184                 modelChanged();
 185             }
 186             if ("foreground".equals(name)) {
 187                 Object honorDisplayPropertiesObject = c.
 188                     getClientProperty(JEditorPane.HONOR_DISPLAY_PROPERTIES);
 189                 boolean honorDisplayProperties = false;
 190                 if (honorDisplayPropertiesObject instanceof Boolean) {
 191                     honorDisplayProperties =
 192                         ((Boolean)honorDisplayPropertiesObject).booleanValue();
 193                 }
 194                 if (honorDisplayProperties) {
 195                     modelChanged();
 196                 }
 197             }
 198 
 199 
 200         }
 201     }
 202 
 203     void removeActions(ActionMap map, Action[] actions) {
 204         int n = actions.length;
 205         for (int i = 0; i < n; i++) {
 206             Action a = actions[i];
 207             map.remove(a.getValue(Action.NAME));
 208         }
 209     }
 210 
 211     void addActions(ActionMap map, Action[] actions) {
 212         int n = actions.length;
 213         for (int i = 0; i < n; i++) {
 214             Action a = actions[i];
 215             map.put(a.getValue(Action.NAME), a);
 216         }
 217     }
 218 
 219     void updateDisplayProperties(Font font, Color fg) {
 220         JComponent c = getComponent();
 221         Object honorDisplayPropertiesObject = c.
 222             getClientProperty(JEditorPane.HONOR_DISPLAY_PROPERTIES);
 223         boolean honorDisplayProperties = false;
 224         Object w3cLengthUnitsObject = c.getClientProperty(JEditorPane.
 225                                                           W3C_LENGTH_UNITS);
 226         boolean w3cLengthUnits = false;
 227         if (honorDisplayPropertiesObject instanceof Boolean) {
 228             honorDisplayProperties =
 229                 ((Boolean)honorDisplayPropertiesObject).booleanValue();
 230         }
 231         if (w3cLengthUnitsObject instanceof Boolean) {
 232             w3cLengthUnits = ((Boolean)w3cLengthUnitsObject).booleanValue();
 233         }
 234         if (this instanceof BasicTextPaneUI
 235             || honorDisplayProperties) {
 236              //using equals because can not use UIResource for Boolean
 237             Document doc = getComponent().getDocument();
 238             if (doc instanceof StyledDocument) {
 239                 if (doc instanceof HTMLDocument
 240                     && honorDisplayProperties) {
 241                     updateCSS(font, fg);
 242                 } else {
 243                     updateStyle(font, fg);
 244                 }
 245             }
 246         } else {
 247             cleanDisplayProperties();
 248         }
 249         if ( w3cLengthUnits ) {
 250             Document doc = getComponent().getDocument();
 251             if (doc instanceof HTMLDocument) {
 252                 StyleSheet documentStyleSheet =
 253                     ((HTMLDocument)doc).getStyleSheet();
 254                 documentStyleSheet.addRule("W3C_LENGTH_UNITS_ENABLE");
 255             }
 256         } else {
 257             Document doc = getComponent().getDocument();
 258             if (doc instanceof HTMLDocument) {
 259                 StyleSheet documentStyleSheet =
 260                     ((HTMLDocument)doc).getStyleSheet();
 261                 documentStyleSheet.addRule("W3C_LENGTH_UNITS_DISABLE");
 262             }
 263 
 264         }
 265     }
 266 
 267     /**
 268      * Attribute key to reference the default font.
 269      * used in javax.swing.text.StyleContext.getFont
 270      * to resolve the default font.
 271      */
 272     private static final String FONT_ATTRIBUTE_KEY = "FONT_ATTRIBUTE_KEY";
 273 
 274     void cleanDisplayProperties() {
 275         Document document = getComponent().getDocument();
 276         if (document instanceof HTMLDocument) {
 277             StyleSheet documentStyleSheet =
 278                 ((HTMLDocument)document).getStyleSheet();
 279             StyleSheet[] styleSheets = documentStyleSheet.getStyleSheets();
 280             if (styleSheets != null) {
 281                 for (StyleSheet s : styleSheets) {
 282                     if (s instanceof StyleSheetUIResource) {
 283                         documentStyleSheet.removeStyleSheet(s);
 284                         documentStyleSheet.addRule("BASE_SIZE_DISABLE");
 285                         break;
 286                     }
 287                 }
 288             }
 289             Style style = ((StyledDocument) document).getStyle(StyleContext.DEFAULT_STYLE);
 290             if (style.getAttribute(FONT_ATTRIBUTE_KEY) != null) {
 291                 style.removeAttribute(FONT_ATTRIBUTE_KEY);
 292             }
 293         }
 294     }
 295 
 296     static class StyleSheetUIResource extends StyleSheet implements UIResource {
 297     }
 298 
 299     private void updateCSS(Font font, Color fg) {
 300         JTextComponent component = getComponent();
 301         Document document = component.getDocument();
 302         if (document instanceof HTMLDocument) {
 303             StyleSheet styleSheet = new StyleSheetUIResource();
 304             StyleSheet documentStyleSheet =
 305                 ((HTMLDocument)document).getStyleSheet();
 306             StyleSheet[] styleSheets = documentStyleSheet.getStyleSheets();
 307             if (styleSheets != null) {
 308                 for (StyleSheet s : styleSheets) {
 309                     if (s instanceof StyleSheetUIResource) {
 310                         documentStyleSheet.removeStyleSheet(s);
 311                     }
 312                 }
 313             }
 314             String cssRule = sun.swing.
 315                 SwingUtilities2.displayPropertiesToCSS(font,
 316                                                        fg);
 317             styleSheet.addRule(cssRule);
 318             documentStyleSheet.addStyleSheet(styleSheet);
 319             documentStyleSheet.addRule("BASE_SIZE " +
 320                                        component.getFont().getSize());
 321             Style style = ((StyledDocument) document).getStyle(StyleContext.DEFAULT_STYLE);
 322             if (! font.equals(style.getAttribute(FONT_ATTRIBUTE_KEY))) {
 323                 style.addAttribute(FONT_ATTRIBUTE_KEY, font);
 324             }
 325         }
 326     }
 327 
 328     private void updateStyle(Font font, Color fg) {
 329         updateFont(font);
 330         updateForeground(fg);
 331     }
 332 
 333     /**
 334      * Update the color in the default style of the document.
 335      *
 336      * @param color the new color to use or null to remove the color attribute
 337      *              from the document's style
 338      */
 339     private void updateForeground(Color color) {
 340         StyledDocument doc = (StyledDocument)getComponent().getDocument();
 341         Style style = doc.getStyle(StyleContext.DEFAULT_STYLE);
 342 
 343         if (style == null) {
 344             return;
 345         }
 346 
 347         if (color == null) {
 348             if (style.getAttribute(StyleConstants.Foreground) != null) {
 349                 style.removeAttribute(StyleConstants.Foreground);
 350             }
 351         } else {
 352             if (! color.equals(StyleConstants.getForeground(style))) {
 353                 StyleConstants.setForeground(style, color);
 354             }
 355         }
 356     }
 357 
 358     /**
 359      * Update the font in the default style of the document.
 360      *
 361      * @param font the new font to use or null to remove the font attribute
 362      *             from the document's style
 363      */
 364     private void updateFont(Font font) {
 365         StyledDocument doc = (StyledDocument)getComponent().getDocument();
 366         Style style = doc.getStyle(StyleContext.DEFAULT_STYLE);
 367 
 368         if (style == null) {
 369             return;
 370         }
 371 
 372         String fontFamily = (String) style.getAttribute(StyleConstants.FontFamily);
 373         Integer fontSize = (Integer) style.getAttribute(StyleConstants.FontSize);
 374         Boolean isBold = (Boolean) style.getAttribute(StyleConstants.Bold);
 375         Boolean isItalic = (Boolean) style.getAttribute(StyleConstants.Italic);
 376         Font  fontAttribute = (Font) style.getAttribute(FONT_ATTRIBUTE_KEY);
 377         if (font == null) {
 378             if (fontFamily != null) {
 379                 style.removeAttribute(StyleConstants.FontFamily);
 380             }
 381             if (fontSize != null) {
 382                 style.removeAttribute(StyleConstants.FontSize);
 383             }
 384             if (isBold != null) {
 385                 style.removeAttribute(StyleConstants.Bold);
 386             }
 387             if (isItalic != null) {
 388                 style.removeAttribute(StyleConstants.Italic);
 389             }
 390             if (fontAttribute != null) {
 391                 style.removeAttribute(FONT_ATTRIBUTE_KEY);
 392            }
 393         } else {
 394             if (! font.getName().equals(fontFamily)) {
 395                 StyleConstants.setFontFamily(style, font.getName());
 396             }
 397             if (fontSize == null
 398                   || fontSize.intValue() != font.getSize()) {
 399                 StyleConstants.setFontSize(style, font.getSize());
 400             }
 401             if (isBold == null
 402                   || isBold.booleanValue() != font.isBold()) {
 403                 StyleConstants.setBold(style, font.isBold());
 404             }
 405             if (isItalic == null
 406                   || isItalic.booleanValue() != font.isItalic()) {
 407                 StyleConstants.setItalic(style, font.isItalic());
 408             }
 409             if (! font.equals(fontAttribute)) {
 410                 style.addAttribute(FONT_ATTRIBUTE_KEY, font);
 411             }
 412         }
 413     }
 414 }