1 /*
   2  * Copyright (c) 1998, 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.text.html;
  26 
  27 import java.awt.*;
  28 import java.awt.event.*;
  29 import java.io.*;
  30 import java.net.MalformedURLException;
  31 import java.net.URL;
  32 import javax.swing.text.*;
  33 import javax.swing.*;
  34 import javax.swing.border.*;
  35 import javax.swing.event.*;
  36 import java.util.*;
  37 
  38 /**
  39  * HiddenTagView subclasses EditableView to contain a JTextField showing
  40  * the element name. When the textfield is edited the element name is
  41  * reset. As this inherits from EditableView if the JTextComponent is
  42  * not editable, the textfield will not be visible.
  43  *
  44  * @author  Scott Violet
  45  */
  46 class HiddenTagView extends EditableView implements DocumentListener {
  47     HiddenTagView(Element e) {
  48         super(e);
  49         yAlign = 1;
  50     }
  51 
  52     protected Component createComponent() {
  53         JTextField tf = new JTextField(getElement().getName());
  54         Document doc = getDocument();
  55         Font font;
  56         if (doc instanceof StyledDocument) {
  57             font = ((StyledDocument)doc).getFont(getAttributes());
  58             tf.setFont(font);
  59         }
  60         else {
  61             font = tf.getFont();
  62         }
  63         tf.getDocument().addDocumentListener(this);
  64         updateYAlign(font);
  65 
  66         // Create a panel to wrap the textfield so that the textfields
  67         // laf border shows through.
  68         JPanel panel = new JPanel(new BorderLayout());
  69         panel.setBackground(null);
  70         if (isEndTag()) {
  71             panel.setBorder(EndBorder);
  72         }
  73         else {
  74             panel.setBorder(StartBorder);
  75         }
  76         panel.add(tf);
  77         return panel;
  78     }
  79 
  80     public float getAlignment(int axis) {
  81         if (axis == View.Y_AXIS) {
  82             return yAlign;
  83         }
  84         return 0.5f;
  85     }
  86 
  87     public float getMinimumSpan(int axis) {
  88         if (axis == View.X_AXIS && isVisible()) {
  89             // Default to preferred.
  90             return Math.max(30, super.getPreferredSpan(axis));
  91         }
  92         return super.getMinimumSpan(axis);
  93     }
  94 
  95     public float getPreferredSpan(int axis) {
  96         if (axis == View.X_AXIS && isVisible()) {
  97             return Math.max(30, super.getPreferredSpan(axis));
  98         }
  99         return super.getPreferredSpan(axis);
 100     }
 101 
 102     public float getMaximumSpan(int axis) {
 103         if (axis == View.X_AXIS && isVisible()) {
 104             // Default to preferred.
 105             return Math.max(30, super.getMaximumSpan(axis));
 106         }
 107         return super.getMaximumSpan(axis);
 108     }
 109 
 110     // DocumentListener methods
 111     public void insertUpdate(DocumentEvent e) {
 112         updateModelFromText();
 113     }
 114 
 115     public void removeUpdate(DocumentEvent e) {
 116         updateModelFromText();
 117     }
 118 
 119     public void changedUpdate(DocumentEvent e) {
 120         updateModelFromText();
 121     }
 122 
 123     // View method
 124     public void changedUpdate(DocumentEvent e, Shape a, ViewFactory f) {
 125         if (!isSettingAttributes) {
 126             setTextFromModel();
 127         }
 128     }
 129 
 130     // local methods
 131 
 132     @SuppressWarnings("deprecation")
 133     void updateYAlign(Font font) {
 134         Container c = getContainer();
 135         FontMetrics fm = (c != null) ? c.getFontMetrics(font) :
 136             Toolkit.getDefaultToolkit().getFontMetrics(font);
 137         float h = fm.getHeight();
 138         float d = fm.getDescent();
 139         yAlign = (h > 0) ? (h - d) / h : 0;
 140     }
 141 
 142     void resetBorder() {
 143         Component comp = getComponent();
 144 
 145         if (comp != null) {
 146             if (isEndTag()) {
 147                 ((JPanel)comp).setBorder(EndBorder);
 148             }
 149             else {
 150                 ((JPanel)comp).setBorder(StartBorder);
 151             }
 152         }
 153     }
 154 
 155     /**
 156      * This resets the text on the text component we created to match
 157      * that of the AttributeSet for the Element we represent.
 158      * <p>If this is invoked on the event dispatching thread, this
 159      * directly invokes <code>_setTextFromModel</code>, otherwise
 160      * <code>SwingUtilities.invokeLater</code> is used to schedule execution
 161      * of <code>_setTextFromModel</code>.
 162      */
 163     void setTextFromModel() {
 164         if (SwingUtilities.isEventDispatchThread()) {
 165             _setTextFromModel();
 166         }
 167         else {
 168             SwingUtilities.invokeLater(new Runnable() {
 169                 public void run() {
 170                     _setTextFromModel();
 171                 }
 172             });
 173         }
 174     }
 175 
 176     /**
 177      * This resets the text on the text component we created to match
 178      * that of the AttributeSet for the Element we represent.
 179      */
 180     void _setTextFromModel() {
 181         Document doc = getDocument();
 182         try {
 183             isSettingAttributes = true;
 184             if (doc instanceof AbstractDocument) {
 185                 ((AbstractDocument)doc).readLock();
 186             }
 187             JTextComponent text = getTextComponent();
 188             if (text != null) {
 189                 text.setText(getRepresentedText());
 190                 resetBorder();
 191                 Container host = getContainer();
 192                 if (host != null) {
 193                     preferenceChanged(this, true, true);
 194                     host.repaint();
 195                 }
 196             }
 197         }
 198         finally {
 199             isSettingAttributes = false;
 200             if (doc instanceof AbstractDocument) {
 201                 ((AbstractDocument)doc).readUnlock();
 202             }
 203         }
 204     }
 205 
 206     /**
 207      * This copies the text from the text component we've created
 208      * to the Element's AttributeSet we represent.
 209      * <p>If this is invoked on the event dispatching thread, this
 210      * directly invokes <code>_updateModelFromText</code>, otherwise
 211      * <code>SwingUtilities.invokeLater</code> is used to schedule execution
 212      * of <code>_updateModelFromText</code>.
 213      */
 214     void updateModelFromText() {
 215         if (!isSettingAttributes) {
 216             if (SwingUtilities.isEventDispatchThread()) {
 217                 _updateModelFromText();
 218             }
 219             else {
 220                 SwingUtilities.invokeLater(new Runnable() {
 221                     public void run() {
 222                         _updateModelFromText();
 223                     }
 224                 });
 225             }
 226         }
 227     }
 228 
 229     /**
 230      * This copies the text from the text component we've created
 231      * to the Element's AttributeSet we represent.
 232      */
 233     void _updateModelFromText() {
 234         Document doc = getDocument();
 235         Object name = getElement().getAttributes().getAttribute
 236             (StyleConstants.NameAttribute);
 237         if ((name instanceof HTML.UnknownTag) &&
 238             (doc instanceof StyledDocument)) {
 239             SimpleAttributeSet sas = new SimpleAttributeSet();
 240             JTextComponent textComponent = getTextComponent();
 241             if (textComponent != null) {
 242                 String text = textComponent.getText();
 243                 isSettingAttributes = true;
 244                 try {
 245                     sas.addAttribute(StyleConstants.NameAttribute,
 246                                      new HTML.UnknownTag(text));
 247                     ((StyledDocument)doc).setCharacterAttributes
 248                         (getStartOffset(), getEndOffset() -
 249                          getStartOffset(), sas, false);
 250                 }
 251                 finally {
 252                     isSettingAttributes = false;
 253                 }
 254             }
 255         }
 256     }
 257 
 258     JTextComponent getTextComponent() {
 259         Component comp = getComponent();
 260 
 261         return (comp == null) ? null : (JTextComponent)((Container)comp).
 262                                        getComponent(0);
 263     }
 264 
 265     String getRepresentedText() {
 266         String retValue = getElement().getName();
 267         return (retValue == null) ? "" : retValue;
 268     }
 269 
 270     boolean isEndTag() {
 271         AttributeSet as = getElement().getAttributes();
 272         if (as != null) {
 273             Object end = as.getAttribute(HTML.Attribute.ENDTAG);
 274             if (end != null && (end instanceof String) &&
 275                 ((String)end).equals("true")) {
 276                 return true;
 277             }
 278         }
 279         return false;
 280     }
 281 
 282     /** Alignment along the y axis, based on the font of the textfield. */
 283     float yAlign;
 284     /** Set to true when setting attributes. */
 285     boolean isSettingAttributes;
 286 
 287 
 288     // Following are for Borders that used for Unknown tags and comments.
 289     //
 290     // Border defines
 291     static final int circleR = 3;
 292     static final int circleD = circleR * 2;
 293     static final int tagSize = 6;
 294     static final int padding = 3;
 295     static final Color UnknownTagBorderColor = Color.black;
 296     static final Border StartBorder = new StartTagBorder();
 297     static final Border EndBorder = new EndTagBorder();
 298 
 299     @SuppressWarnings("serial") // Same-version serialization only
 300     static class StartTagBorder implements Border, Serializable {
 301         public void paintBorder(Component c, Graphics g, int x, int y,
 302                                 int width, int height) {
 303             g.setColor(UnknownTagBorderColor);
 304             x += padding;
 305             width -= (padding * 2);
 306             g.drawLine(x, y + circleR,
 307                        x, y + height - circleR);
 308             g.drawArc(x, y + height - circleD - 1,
 309                       circleD, circleD, 180, 90);
 310             g.drawArc(x, y, circleD, circleD, 90, 90);
 311             g.drawLine(x + circleR, y, x + width - tagSize, y);
 312             g.drawLine(x + circleR, y + height - 1,
 313                        x + width - tagSize, y + height - 1);
 314 
 315             g.drawLine(x + width - tagSize, y,
 316                        x + width - 1, y + height / 2);
 317             g.drawLine(x + width - tagSize, y + height,
 318                        x + width - 1, y + height / 2);
 319         }
 320 
 321         public Insets getBorderInsets(Component c) {
 322             return new Insets(2, 2 + padding, 2, tagSize + 2 + padding);
 323         }
 324 
 325         public boolean isBorderOpaque() {
 326             return false;
 327         }
 328     } // End of class HiddenTagView.StartTagBorder
 329 
 330     @SuppressWarnings("serial") // Same-version serialization only
 331     static class EndTagBorder implements Border, Serializable {
 332         public void paintBorder(Component c, Graphics g, int x, int y,
 333                                 int width, int height) {
 334             g.setColor(UnknownTagBorderColor);
 335             x += padding;
 336             width -= (padding * 2);
 337             g.drawLine(x + width - 1, y + circleR,
 338                        x + width - 1, y + height - circleR);
 339             g.drawArc(x + width - circleD - 1, y + height - circleD - 1,
 340                       circleD, circleD, 270, 90);
 341             g.drawArc(x + width - circleD - 1, y, circleD, circleD, 0, 90);
 342             g.drawLine(x + tagSize, y, x + width - circleR, y);
 343             g.drawLine(x + tagSize, y + height - 1,
 344                        x + width - circleR, y + height - 1);
 345 
 346             g.drawLine(x + tagSize, y,
 347                        x, y + height / 2);
 348             g.drawLine(x + tagSize, y + height,
 349                        x, y + height / 2);
 350         }
 351 
 352         public Insets getBorderInsets(Component c) {
 353             return new Insets(2, tagSize + 2 + padding, 2, 2 + padding);
 354         }
 355 
 356         public boolean isBorderOpaque() {
 357             return false;
 358         }
 359     } // End of class HiddenTagView.EndTagBorder
 360 
 361 
 362 } // End of HiddenTagView