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     void updateYAlign(Font font) {
 133         Container c = getContainer();
 134         FontMetrics fm = (c != null) ? c.getFontMetrics(font) :
 135             Toolkit.getDefaultToolkit().getFontMetrics(font);
 136         float h = fm.getHeight();
 137         float d = fm.getDescent();
 138         yAlign = (h > 0) ? (h - d) / h : 0;
 139     }
 140 
 141     void resetBorder() {
 142         Component comp = getComponent();
 143 
 144         if (comp != null) {
 145             if (isEndTag()) {
 146                 ((JPanel)comp).setBorder(EndBorder);
 147             }
 148             else {
 149                 ((JPanel)comp).setBorder(StartBorder);
 150             }
 151         }
 152     }
 153 
 154     /**
 155      * This resets the text on the text component we created to match
 156      * that of the AttributeSet for the Element we represent.
 157      * <p>If this is invoked on the event dispatching thread, this
 158      * directly invokes <code>_setTextFromModel</code>, otherwise
 159      * <code>SwingUtilities.invokeLater</code> is used to schedule execution
 160      * of <code>_setTextFromModel</code>.
 161      */
 162     void setTextFromModel() {
 163         if (SwingUtilities.isEventDispatchThread()) {
 164             _setTextFromModel();
 165         }
 166         else {
 167             SwingUtilities.invokeLater(new Runnable() {
 168                 public void run() {
 169                     _setTextFromModel();
 170                 }
 171             });
 172         }
 173     }
 174 
 175     /**
 176      * This resets the text on the text component we created to match
 177      * that of the AttributeSet for the Element we represent.
 178      */
 179     void _setTextFromModel() {
 180         Document doc = getDocument();
 181         try {
 182             isSettingAttributes = true;
 183             if (doc instanceof AbstractDocument) {
 184                 ((AbstractDocument)doc).readLock();
 185             }
 186             JTextComponent text = getTextComponent();
 187             if (text != null) {
 188                 text.setText(getRepresentedText());
 189                 resetBorder();
 190                 Container host = getContainer();
 191                 if (host != null) {
 192                     preferenceChanged(this, true, true);
 193                     host.repaint();
 194                 }
 195             }
 196         }
 197         finally {
 198             isSettingAttributes = false;
 199             if (doc instanceof AbstractDocument) {
 200                 ((AbstractDocument)doc).readUnlock();
 201             }
 202         }
 203     }
 204 
 205     /**
 206      * This copies the text from the text component we've created
 207      * to the Element's AttributeSet we represent.
 208      * <p>If this is invoked on the event dispatching thread, this
 209      * directly invokes <code>_updateModelFromText</code>, otherwise
 210      * <code>SwingUtilities.invokeLater</code> is used to schedule execution
 211      * of <code>_updateModelFromText</code>.
 212      */
 213     void updateModelFromText() {
 214         if (!isSettingAttributes) {
 215             if (SwingUtilities.isEventDispatchThread()) {
 216                 _updateModelFromText();
 217             }
 218             else {
 219                 SwingUtilities.invokeLater(new Runnable() {
 220                     public void run() {
 221                         _updateModelFromText();
 222                     }
 223                 });
 224             }
 225         }
 226     }
 227 
 228     /**
 229      * This copies the text from the text component we've created
 230      * to the Element's AttributeSet we represent.
 231      */
 232     void _updateModelFromText() {
 233         Document doc = getDocument();
 234         Object name = getElement().getAttributes().getAttribute
 235             (StyleConstants.NameAttribute);
 236         if ((name instanceof HTML.UnknownTag) &&
 237             (doc instanceof StyledDocument)) {
 238             SimpleAttributeSet sas = new SimpleAttributeSet();
 239             JTextComponent textComponent = getTextComponent();
 240             if (textComponent != null) {
 241                 String text = textComponent.getText();
 242                 isSettingAttributes = true;
 243                 try {
 244                     sas.addAttribute(StyleConstants.NameAttribute,
 245                                      new HTML.UnknownTag(text));
 246                     ((StyledDocument)doc).setCharacterAttributes
 247                         (getStartOffset(), getEndOffset() -
 248                          getStartOffset(), sas, false);
 249                 }
 250                 finally {
 251                     isSettingAttributes = false;
 252                 }
 253             }
 254         }
 255     }
 256 
 257     JTextComponent getTextComponent() {
 258         Component comp = getComponent();
 259 
 260         return (comp == null) ? null : (JTextComponent)((Container)comp).
 261                                        getComponent(0);
 262     }
 263 
 264     String getRepresentedText() {
 265         String retValue = getElement().getName();
 266         return (retValue == null) ? "" : retValue;
 267     }
 268 
 269     boolean isEndTag() {
 270         AttributeSet as = getElement().getAttributes();
 271         if (as != null) {
 272             Object end = as.getAttribute(HTML.Attribute.ENDTAG);
 273             if (end != null && (end instanceof String) &&
 274                 ((String)end).equals("true")) {
 275                 return true;
 276             }
 277         }
 278         return false;
 279     }
 280 
 281     /** Alignment along the y axis, based on the font of the textfield. */
 282     float yAlign;
 283     /** Set to true when setting attributes. */
 284     boolean isSettingAttributes;
 285 
 286 
 287     // Following are for Borders that used for Unknown tags and comments.
 288     //
 289     // Border defines
 290     static final int circleR = 3;
 291     static final int circleD = circleR * 2;
 292     static final int tagSize = 6;
 293     static final int padding = 3;
 294     static final Color UnknownTagBorderColor = Color.black;
 295     static final Border StartBorder = new StartTagBorder();
 296     static final Border EndBorder = new EndTagBorder();
 297 
 298     @SuppressWarnings("serial") // Same-version serialization only
 299     static class StartTagBorder implements Border, Serializable {
 300         public void paintBorder(Component c, Graphics g, int x, int y,
 301                                 int width, int height) {
 302             g.setColor(UnknownTagBorderColor);
 303             x += padding;
 304             width -= (padding * 2);
 305             g.drawLine(x, y + circleR,
 306                        x, y + height - circleR);
 307             g.drawArc(x, y + height - circleD - 1,
 308                       circleD, circleD, 180, 90);
 309             g.drawArc(x, y, circleD, circleD, 90, 90);
 310             g.drawLine(x + circleR, y, x + width - tagSize, y);
 311             g.drawLine(x + circleR, y + height - 1,
 312                        x + width - tagSize, y + height - 1);
 313 
 314             g.drawLine(x + width - tagSize, y,
 315                        x + width - 1, y + height / 2);
 316             g.drawLine(x + width - tagSize, y + height,
 317                        x + width - 1, y + height / 2);
 318         }
 319 
 320         public Insets getBorderInsets(Component c) {
 321             return new Insets(2, 2 + padding, 2, tagSize + 2 + padding);
 322         }
 323 
 324         public boolean isBorderOpaque() {
 325             return false;
 326         }
 327     } // End of class HiddenTagView.StartTagBorder
 328 
 329     @SuppressWarnings("serial") // Same-version serialization only
 330     static class EndTagBorder implements Border, Serializable {
 331         public void paintBorder(Component c, Graphics g, int x, int y,
 332                                 int width, int height) {
 333             g.setColor(UnknownTagBorderColor);
 334             x += padding;
 335             width -= (padding * 2);
 336             g.drawLine(x + width - 1, y + circleR,
 337                        x + width - 1, y + height - circleR);
 338             g.drawArc(x + width - circleD - 1, y + height - circleD - 1,
 339                       circleD, circleD, 270, 90);
 340             g.drawArc(x + width - circleD - 1, y, circleD, circleD, 0, 90);
 341             g.drawLine(x + tagSize, y, x + width - circleR, y);
 342             g.drawLine(x + tagSize, y + height - 1,
 343                        x + width - circleR, y + height - 1);
 344 
 345             g.drawLine(x + tagSize, y,
 346                        x, y + height / 2);
 347             g.drawLine(x + tagSize, y + height,
 348                        x, y + height / 2);
 349         }
 350 
 351         public Insets getBorderInsets(Component c) {
 352             return new Insets(2, tagSize + 2 + padding, 2, 2 + padding);
 353         }
 354 
 355         public boolean isBorderOpaque() {
 356             return false;
 357         }
 358     } // End of class HiddenTagView.EndTagBorder
 359 
 360 
 361 } // End of HiddenTagView