0001: /*
0002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
0003: *
0004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
0005: *
0006: * The contents of this file are subject to the terms of either the GNU
0007: * General Public License Version 2 only ("GPL") or the Common
0008: * Development and Distribution License("CDDL") (collectively, the
0009: * "License"). You may not use this file except in compliance with the
0010: * License. You can obtain a copy of the License at
0011: * http://www.netbeans.org/cddl-gplv2.html
0012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
0013: * specific language governing permissions and limitations under the
0014: * License. When distributing the software, include this License Header
0015: * Notice in each file and include the License file at
0016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
0017: * particular file as subject to the "Classpath" exception as provided
0018: * by Sun in the GPL Version 2 section of the License file that
0019: * accompanied this code. If applicable, add the following below the
0020: * License Header, with the fields enclosed by brackets [] replaced by
0021: * your own identifying information:
0022: * "Portions Copyrighted [year] [name of copyright owner]"
0023: *
0024: * Contributor(s):
0025: *
0026: * The Original Software is NetBeans. The Initial Developer of the Original
0027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
0028: * Microsystems, Inc. All Rights Reserved.
0029: *
0030: * If you wish your version of this file to be governed by only the CDDL
0031: * or only the GPL Version 2, indicate your decision by adding
0032: * "[Contributor] elects to include this software in this distribution
0033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
0034: * single choice of license, a recipient has the option to distribute
0035: * your version of this file under either the CDDL, the GPL Version 2 or
0036: * to extend the choice of license to its licensees as provided above.
0037: * However, if you add GPL Version 2 code and therefore, elected the GPL
0038: * Version 2 license, then the option applies only if the new code is
0039: * made subject to such option by the copyright holder.
0040: */
0041: package org.netbeans.modules.visualweb.designer;
0042:
0043: import org.netbeans.modules.visualweb.api.designer.DomProvider.DomPosition;
0044: import org.netbeans.modules.visualweb.api.designer.cssengine.CssProvider;
0045: import org.netbeans.modules.visualweb.api.designer.markup.MarkupService;
0046: import org.netbeans.modules.visualweb.css2.ModelViewMapper;
0047: import java.awt.Color;
0048: import java.awt.Component;
0049: import java.awt.Cursor;
0050: import java.awt.Font;
0051: import java.awt.FontMetrics;
0052: import java.awt.Graphics;
0053: import java.awt.Graphics2D;
0054: import java.awt.Point;
0055: import java.awt.Rectangle;
0056: import java.awt.datatransfer.Transferable;
0057: import java.awt.dnd.DragSource;
0058: import java.awt.dnd.DropTarget;
0059: import java.awt.dnd.DropTargetDragEvent;
0060: import java.awt.dnd.DropTargetDropEvent;
0061: import java.awt.dnd.DropTargetEvent;
0062: import java.awt.dnd.DropTargetListener;
0063: import java.awt.event.ActionEvent;
0064: import java.awt.event.KeyEvent;
0065: import java.awt.font.FontRenderContext;
0066: import java.awt.geom.Rectangle2D;
0067: import java.io.PrintStream;
0068: import java.io.PrintWriter;
0069: import java.util.TooManyListenersException;
0070: import java.util.logging.Level;
0071: import java.util.logging.Logger;
0072: import javax.swing.AbstractAction;
0073: import javax.swing.ActionMap;
0074:
0075: import javax.swing.CellRendererPane;
0076: import javax.swing.ImageIcon;
0077: import javax.swing.InputMap;
0078: import javax.swing.JComponent;
0079: import javax.swing.KeyStroke;
0080: import javax.swing.UIManager;
0081: import javax.swing.border.EmptyBorder;
0082:
0083: import org.openide.util.NbBundle;
0084:
0085: import org.w3c.dom.Element;
0086: import org.w3c.dom.Node;
0087: import org.w3c.dom.NodeList;
0088:
0089: import org.netbeans.modules.visualweb.css2.PageBox;
0090: import org.netbeans.modules.visualweb.api.designer.cssengine.XhtmlCss;
0091: import org.netbeans.modules.visualweb.designer.html.HtmlAttribute;
0092: import org.netbeans.modules.visualweb.designer.html.HtmlTag;
0093:
0094: //import javax.swing.plaf.basic.BasicGraphicsUtils;
0095:
0096: /** The form designer pane - a subclass of JEditorPane
0097: * which adds various specializations when editable=true,
0098: * such as grid mode rendering, component drag & drop handling,
0099: * etc.
0100: * <p>
0101: *
0102: * @author Tor Norbye
0103: */
0104: public class DesignerPane extends
0105: org.netbeans.modules.visualweb.text.DesignerPaneBase
0106: /*implements PropertyChangeListener, PreferenceChangeListener*/{
0107: /** Whether or not to use alpha rendering in the GUI */
0108: static boolean useAlpha = true;
0109: private static int adjustX = 0;
0110: private static int adjustY = 0;
0111:
0112: /**
0113: * This rectangle indicates the current clipping rectangle used during
0114: * a paint. The recursive paint operation can bail when outside of
0115: * this clipping region.
0116: * May get clobbered by other webforms painting so use only for
0117: * the duration of a hierarchy paint operation.
0118: */
0119: public static final Rectangle clip = new Rectangle();
0120:
0121: /**
0122: * This point points to the bottom right corner of the clipping
0123: * rectangle (clip). So it's (clipx+clip.width,clip.y+clip.height).
0124: * This just simplifies code everywhere doing edge comparisons.
0125: */
0126: public static final Point clipBr = new Point(); // br for "bottom right"
0127:
0128: /**
0129: * This rectangle indicates the dirty region of the screen during
0130: * laoyut (e.g. needing repaint). We will be calling repaint with
0131: * this rectangle.
0132: * May get clobbered by other webforms painting so use only for
0133: * the duration of a layout hierarchy computation.
0134: */
0135: /*static*/private Rectangle dirty = new Rectangle(); // XXX move to PageBox
0136:
0137: /**
0138: * Flag which controls whether we should try to do smart clipping optimizations.
0139: * As soon as we're confident that this works it should be made unconditional.
0140: */
0141: public static boolean INCREMENTAL_LAYOUT = System
0142: .getProperty("rave.designer.noclip") == null; // NOI18N
0143:
0144: /** When true, optimize painting by clipping according to dirty regions only */
0145: public static final boolean DEBUG_REPAINT = false; // System.getProperty("rave.designer.debugclip") != null;
0146:
0147: // DEBUG:
0148: // Log info pertaining to component lookup
0149: //final private static boolean debugcomp = false;
0150: private DndHandler dndHandler;
0151: private final DesignerTransferHandler designerTransferHandler;
0152:
0153: private boolean grid = false;
0154: private DropTargetListener gridDropListener = null;
0155:
0156: private final WebForm webform;
0157:
0158: private FontMetrics metrics;
0159: private FontMetrics boldMetrics;
0160:
0161: public DesignerPane(final WebForm webform/*, Document document*/) {
0162: super (/*document*/);
0163:
0164: this .webform = webform;
0165:
0166: this .designerTransferHandler = new DesignerTransferHandler(
0167: webform);
0168:
0169: installActions();
0170: init();
0171: }
0172:
0173: private void installActions() {
0174: installEscapeAction();
0175: }
0176:
0177: private void installEscapeAction() {
0178: InputMap inputMap = getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
0179: ActionMap actionMap = getActionMap();
0180:
0181: inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0),
0182: "escape-multiplex"); // NOI18N
0183: actionMap.put("escape-multiplex", new EscapeAction(this ));
0184: }
0185:
0186: private void init() {
0187: //webform.setPane(this);
0188:
0189: /*
0190: Insets insets = getInsets();
0191: adjustX = insets.left;
0192: adjustY = insets.top;
0193: */
0194: setBorder(new EmptyBorder(0, 0, 0, 0));
0195:
0196: // CellRendererPane rendererPane = new CellRendererPane();
0197: CellRendererPane rendererPane = webform.getRenderPane();
0198: add(rendererPane);
0199: // webform.setRenderPane(rendererPane);
0200:
0201: // XXX Now set in designer/jsf.
0202: // // Set listener.
0203: // DesignerSettings settings = DesignerSettings.getInstance();
0204: //// settings.addPropertyChangeListener(WeakListeners.propertyChange(this, settings));
0205: // settings.addWeakPreferenceChangeListener(this);
0206:
0207: setDropTarget(new DesignerPaneDropTarget(this ));
0208:
0209: dndHandler = new DndHandler(webform);
0210: // setTransferHandler(dndHandler);
0211: setTransferHandler(designerTransferHandler);
0212:
0213: updateUI();
0214:
0215: // if (!webform.getModel().isBusted() &&
0216: // !webform.getDocument().isGridMode()) {
0217: // showCaretAtBeginning();
0218: // }
0219: setGridDropListener(true);
0220:
0221: initA11Y();
0222: }
0223:
0224: private void initA11Y() {
0225: getAccessibleContext().setAccessibleName(
0226: NbBundle.getMessage(DesignerPane.class,
0227: "ACSN_DesignerPane"));
0228: getAccessibleContext().setAccessibleDescription(
0229: NbBundle.getMessage(DesignerPane.class,
0230: "ACSD_DesignerPane"));
0231: }
0232:
0233: // We have our own UI for this
0234:
0235: /**
0236: * Reloads the pluggable UI. The key used to fetch the
0237: * new interface is <code>getUIClassID()</code>. The type of
0238: * the UI is <code>TextUI</code>. <code>invalidate</code>
0239: * is called after setting the UI.
0240: */
0241: public void updateUI() {
0242: fine("Updating UI, ui=" + ui);
0243: // XXX #105443 To pass the page box.
0244: // XXX FIXME Move the pageBox field to the DesignerPane (wrong place in UI).
0245: PageBox pageBox = ui instanceof DesignerPaneUI ? ((DesignerPaneUI) ui)
0246: .getPageBox()
0247: : null;
0248: fine("pageBox=" + pageBox); // TEMP
0249:
0250: //setUI((DesignerPaneUI)UIManager.getUI(this));
0251: setUI(DesignerPaneUI.createUI(this ));
0252: fine("after update, ui=" + ui);
0253:
0254: if (pageBox != null && ui instanceof DesignerPaneUI) {
0255: ((DesignerPaneUI) ui).setPageBox(pageBox);
0256: }
0257:
0258: invalidate();
0259: }
0260:
0261: protected void paintComponent(Graphics g) {
0262: super .paintComponent(g);
0263:
0264: /*
0265: ((Graphics2D)g).setRenderingHint(
0266: RenderingHints.KEY_ANTIALIASING,
0267: RenderingHints.VALUE_ANTIALIAS_ON);
0268: */
0269:
0270: // // Draw "here's what you need to do" explanation on the canvas
0271: // // when the document is "empty".
0272: // Document doc = getDocument();
0273: // if (isDocumentEmpty(doc)) {
0274: if (isDocumentEmpty(webform.getHtmlBody())) {
0275: // Document is probably empty. TODO: Find a better and more
0276: // accurate way to quickly determine this! For now, if you
0277: // go and remove the title and header tags, you can end up
0278: // with a nonempty doc that has length < 4!)
0279: Graphics2D g2d = (Graphics2D) g;
0280: paintCenteredText(g2d);
0281: }
0282: }
0283:
0284: /** Paint the "help" text when the page is empty */
0285: private void paintCenteredText(Graphics2D g) {
0286: final int PADDING = 5; // Padding around text that's cleared
0287: final int LINESPACING = 3; // Extra pixels between text lines
0288:
0289: // This implementation is slow/inefficient, but since it's only run
0290: // when the page is empty we know we're not busy
0291: String text;
0292:
0293: if (isGridMode()) {
0294: text = NbBundle.getMessage(DesignerPane.class, "GridText"); // NOI18N
0295: } else {
0296: text = NbBundle.getMessage(DesignerPane.class, "FlowText"); // NOI18N
0297: }
0298:
0299: int textLines = 1;
0300:
0301: for (int i = 0, n = text.length(); i < n; i++) {
0302: if (text.charAt(i) == '\n') {
0303: textLines++;
0304: }
0305: }
0306:
0307: int width = getWidth();
0308: int height = getHeight();
0309:
0310: // DesignerSettings designerSettings = DesignerSettings.getInstance();
0311: // if (designerSettings.getPageSizeWidth() != -1) {
0312: // width = designerSettings.getPageSizeWidth();
0313: // height = designerSettings.getPageSizeHeight();
0314: // }
0315: if (webform.getPageSizeWidth() != -1) {
0316: width = webform.getPageSizeWidth();
0317: height = webform.getPageSizeHeight();
0318: }
0319:
0320: int center = height / 2;
0321:
0322: Font font = UIManager.getFont("Label.font"); // NOI18N
0323: g.setFont(font);
0324:
0325: // FontMetrics metrics = Toolkit.getDefaultToolkit().getFontMetrics(font);
0326: FontMetrics metrics = DesignerUtils.getFontMetrics(font);
0327: FontRenderContext frc = g.getFontRenderContext();
0328: int lineHeight = metrics.getHeight() + LINESPACING;
0329:
0330: int top = center - ((lineHeight * textLines) / 2);
0331:
0332: int minx = width;
0333: int maxx = 0;
0334: int nextLine = 0;
0335:
0336: for (int line = 0; line < textLines; line++) {
0337: int lineEnd = text.indexOf('\n', nextLine);
0338: String lineText;
0339:
0340: if (lineEnd != -1) {
0341: lineText = text.substring(nextLine, lineEnd);
0342: nextLine = lineEnd + 1;
0343: } else {
0344: lineText = text.substring(nextLine);
0345: }
0346:
0347: Rectangle2D bounds1 = font.getStringBounds(lineText, frc);
0348: int lx = (width - ((int) bounds1.getWidth())) / 2;
0349:
0350: if (lx < minx) {
0351: minx = lx;
0352: }
0353:
0354: int xw = lx + (int) bounds1.getWidth();
0355:
0356: if (xw > maxx) {
0357: maxx = xw;
0358: }
0359: }
0360:
0361: // Clear background under text
0362: Color background = null;
0363:
0364: if (webform.getHtmlBody() != null) {
0365: // TODO - look up the page box from the ui delegate instead,
0366: // and ask the page box for its bg - it has properly looked
0367: // up the background color, not just from the background-color
0368: // attribute, but the background shorthand property
0369: // background = CssLookup.getColor(webform.getBody(), XhtmlCss.BACKGROUND_COLOR_INDEX);
0370: background = CssProvider.getValueService()
0371: .getColorForElement(webform.getHtmlBody(),
0372: XhtmlCss.BACKGROUND_COLOR_INDEX);
0373: }
0374:
0375: if (background == null) {
0376: background = getBackground();
0377: }
0378:
0379: g.setColor(background);
0380:
0381: int miny = top;
0382: int maxy = top + (textLines * lineHeight);
0383: g.fillRect(minx - PADDING, miny, maxx - minx + (2 * PADDING),
0384: maxy - miny + (2 * PADDING));
0385:
0386: // Draw text
0387: g.setColor(webform.getColors().gridColor);
0388: nextLine = 0;
0389:
0390: int y = top + (2 * PADDING); // XXX change to padding constant
0391: y += (metrics.getHeight() - metrics.getDescent());
0392:
0393: for (int line = 0; line < textLines; line++) {
0394: int lineEnd = text.indexOf('\n', nextLine);
0395: String lineText;
0396:
0397: if (lineEnd != -1) {
0398: lineText = text.substring(nextLine, lineEnd);
0399: nextLine = lineEnd + 1;
0400: } else {
0401: lineText = text.substring(nextLine);
0402: }
0403:
0404: Rectangle2D bounds1 = font.getStringBounds(lineText, frc);
0405: int lx = (width - ((int) bounds1.getWidth())) / 2;
0406:
0407: g.drawString(lineText, lx, y);
0408: y += lineHeight;
0409: }
0410: }
0411:
0412: void setGridMode(boolean on) {
0413: if (grid == on) {
0414: return;
0415: }
0416:
0417: grid = on;
0418:
0419: webform.getSelection().clearSelection(true);
0420:
0421: // Cursor depends on mode: in grid mode, show pointer, in flow mode,
0422: // show insert/text cursor.
0423: if (on) {
0424: setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
0425: } else {
0426: setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR));
0427: }
0428:
0429: webform.getManager().setInsertBox(null, null);
0430:
0431: // Always do it, so we can drop on grid regions as well
0432: //setGridDropListener(on);
0433: // Unselect text before switching, since there's no
0434: // caret to move once you switch to grid mode.
0435: // Position pos = getCaretPosition();
0436: DomPosition pos = getCaretPosition();
0437:
0438: select(pos, pos);
0439:
0440: if (on) {
0441: // if (getCaret() != null) {
0442: // setCaret(null);
0443: // }
0444: hideCaret();
0445: } else {
0446: showCaretAtBeginning();
0447: }
0448:
0449: if (isShowing()) {
0450: invalidate();
0451: revalidate();
0452: getParent().validate();
0453: repaint();
0454: }
0455: }
0456:
0457: private void setGridDropListener(boolean on) {
0458: // The drop handler depends on the mode. In flow mode, we can
0459: // use Swing's default (which I think is
0460: // BasicTextUI.TextDropTargetListener). In grid mode, use our
0461: // own, since we need to know the pixel position at the drop.
0462: // TODO: we've gotta "remove" the builtin/flow one as well, it's
0463: // intefering on Windows (I see cursor flicker) and on OSX
0464: // it's downright breaking things.
0465: DropTarget dropTarget = getDropTarget();
0466:
0467: if (dropTarget != null) {
0468: if (on) {
0469: if (gridDropListener == null) {
0470: gridDropListener = new DesignerDropHandler(this );
0471: }
0472:
0473: try {
0474: dropTarget.addDropTargetListener(gridDropListener);
0475: } catch (TooManyListenersException tmle) {
0476: // should not happen... swing drop target is multicast
0477: tmle.printStackTrace();
0478: }
0479: } else {
0480: if (gridDropListener != null) {
0481: dropTarget
0482: .removeDropTargetListener(gridDropListener);
0483: }
0484: }
0485: }
0486: }
0487:
0488: /**
0489: * Position the caret at the beginning of the document (and show the caret
0490: * too, if necessary)
0491: */
0492: public void showCaretAtBeginning() {
0493: // Position pos = ModelViewMapper.getFirstDocumentPosition(webform, true);
0494: DomPosition pos = ModelViewMapper.getFirstDocumentPosition(
0495: webform, true);
0496:
0497: // if (pos == Position.NONE) {
0498: if (pos == DomPosition.NONE) {
0499: hideCaret();
0500: } else {
0501: showCaret(pos);
0502: }
0503: }
0504:
0505: public void hideCaret() {
0506: // DesignerCaret dc = getCaret();
0507:
0508: // if (dc != null) {
0509: if (hasCaret()) {
0510: setCaret(null);
0511: }
0512: }
0513:
0514: // public void showCaret(Position dot) {
0515: public void showCaret(DomPosition dot) {
0516: // if (dot == Position.NONE) {
0517: if (dot == DomPosition.NONE) {
0518: hideCaret();
0519:
0520: return;
0521: }
0522:
0523: // if (getCaret() == null) {
0524: // DesignerCaret dc = getPaneUI().createCaret();
0525: // setCaret(dc);
0526: // }
0527: if (!hasCaret()) {
0528: createCaret();
0529: }
0530:
0531: // setCaretPosition(dot);
0532: setCaretDot(dot);
0533: }
0534:
0535: /*
0536: public void repaint(long tm, int x, int y, int width, int height) {
0537: super.repaint(tm, x, y, width, height);
0538: if (debugpaints) {
0539: if (getWidth()/2 < width && getHeight()/2 < height) {
0540: System.out.print(" repaint request(size=" + ((width*height)/1000) + " kbpix2, x=" + x + ", y=" + y + ", w=" + width + ",h=" + height + ") - ");
0541:
0542: Throwable t = new Throwable();
0543: t.fillInStackTrace();
0544: StackTraceElement stack[] = t.getStackTrace();
0545: int frames = 0;
0546: for (int i = 1; i < stack.length; i++) {
0547: StackTraceElement caller = stack[i];
0548: String className = caller.getClassName();
0549: if (!className.startsWith("com.sun.rave.")) {
0550: continue;
0551: }
0552: className = className.substring(className.lastIndexOf('.')+1);
0553: String methodName = caller.getMethodName();
0554: System.out.print("-> " + className + "." + methodName + "():" + caller.getLineNumber());
0555: frames++;
0556: if (frames == 2) {
0557: break;
0558: }
0559: }
0560: System.out.println("");
0561: }
0562: }
0563: }
0564: */
0565: public void repaint() {
0566: super .repaint();
0567:
0568: if (DEBUG_REPAINT) {
0569: System.out.print("FULL REPAINT REQUEST: ");
0570:
0571: Throwable t = new Throwable();
0572: t.fillInStackTrace();
0573:
0574: StackTraceElement[] stack = t.getStackTrace();
0575: int frames = 0;
0576:
0577: for (int i = 1; i < stack.length; i++) {
0578: StackTraceElement caller = stack[i];
0579: String className = caller.getClassName();
0580:
0581: if (!className.startsWith("com.sun.rave.")) {
0582: continue;
0583: }
0584:
0585: className = className.substring(className
0586: .lastIndexOf('.') + 1);
0587:
0588: String methodName = caller.getMethodName();
0589: System.out.print("-> " + className + "." + methodName
0590: + "():" + caller.getLineNumber());
0591: frames++;
0592:
0593: if (frames == 3) {
0594: break;
0595: }
0596: }
0597:
0598: System.out.println("");
0599: }
0600: }
0601:
0602: /** Return true if this editor pane is using grid mode */
0603: public boolean isGridMode() {
0604: return grid;
0605: }
0606:
0607: /** Return distance from the top left corner to the top left view
0608: * canvas. This is the amount that mouse positions need to get corrected
0609: * for in order for pixels in the web page space to correspond to mouse
0610: * coordinates. Since this distance may be different between the
0611: * horizontal and the vertical directions, separate methods provide
0612: * these distances.
0613: */
0614: public static int getAdjustX() {
0615: return adjustX;
0616: }
0617:
0618: /** Return distance from the top left corner to the top left view
0619: * canvas. This is the amount that mouse positions need to get corrected
0620: * for in order for pixels in the web page space to correspond to mouse
0621: * coordinates. Since this distance may be different between the
0622: * horizontal and the vertical directions, separate methods provide
0623: * these distances.
0624: */
0625: public static int getAdjustY() {
0626: return adjustY;
0627: }
0628:
0629: public WebForm getWebForm() {
0630: return webform;
0631: }
0632:
0633: public DesignerPaneUI getPaneUI() {
0634: DesignerPaneUI ui = (DesignerPaneUI) getUI();
0635:
0636: return ui;
0637: }
0638:
0639: /*
0640: public void addNotify() {
0641: System.out.println("addNotify: updating viewport");
0642: super.addNotify();
0643: DesignerPaneUI ui = (DesignerPaneUI)getUI();
0644: ui.updateViewport();
0645: }
0646:
0647: public void removeNotify() {
0648: System.out.println("removeNotify: updating viewport");
0649: super.removeNotify();
0650: }
0651: */
0652: public void updateViewport() {
0653: DesignerPaneUI ui = (DesignerPaneUI) getUI();
0654: ui.updateViewport();
0655: }
0656:
0657: /** Return the drag & drop handler associated with this pane */
0658: public DndHandler getDndHandler() {
0659: return dndHandler;
0660: }
0661:
0662: /** Return the default font to use */
0663: public FontMetrics getMetrics() {
0664: if (metrics == null) {
0665: Font font = getFont();
0666: // metrics = Toolkit.getDefaultToolkit().getFontMetrics(font);
0667: metrics = DesignerUtils.getFontMetrics(font);
0668: }
0669:
0670: return metrics;
0671: }
0672:
0673: /** Return the bold font to use */
0674: public FontMetrics getBoldMetrics() {
0675: if (boldMetrics == null) {
0676: Font font = getFont();
0677: font = font.deriveFont(Font.BOLD);
0678: // boldMetrics = Toolkit.getDefaultToolkit().getFontMetrics(font);
0679: boldMetrics = DesignerUtils.getFontMetrics(font);
0680: }
0681:
0682: return boldMetrics;
0683: }
0684:
0685: public PageBox getPageBox() {
0686: return ((DesignerPaneUI) getUI()).getPageBox();
0687: }
0688:
0689: /**
0690: * Return true if the rectangle spanning from (x,y) to (x2,y2) is completely
0691: * outside the clipping rectangle (so can be culled). Note it's assumed that
0692: * x < x2 and y < y2.
0693: */
0694: public static boolean isOutsideClip(int x, int y, int x2, int y2) {
0695: return ((clip != null) && (y2 < clip.y)) || (x2 < clip.x)
0696: || (clipBr.y < y) || (clipBr.x < x);
0697: }
0698:
0699: /**
0700: * Clear the dirty rectangle such that the next addDirtyPoint
0701: * call will establish a corner point for the new dirty rectangle.
0702: * (In particular, we don't just want to do "new Rectangle()" followed
0703: * by some addDirtyPoint calls because that will include the initial "point"
0704: * from the new Rectangle() at (0,0) so we'll end up with a dirty rectangle
0705: * from (0,0) to (maxx, maxy) instead of a dirty rectangle from
0706: * (minx,miny) to (maxx,maxy).
0707: */
0708: /*public static*/void clearDirty() {
0709: dirty = null;
0710: }
0711:
0712: /** Add the given point to the dirty rectangle. */
0713: /*public static*/void addDirtyPoint(int x, int y) {
0714: if (dirty == null) {
0715: dirty = new Rectangle(x, y, 0, 0);
0716: } else {
0717: dirty.add(x, y);
0718: }
0719: }
0720:
0721: /** Add the given rectangle to the dirty rectangle. */
0722: /*public static*/void addDirtyRectangle(int x, int y, int w, int h) {
0723: if (dirty == null) {
0724: dirty = new Rectangle(x, y, w, h);
0725: } else {
0726: dirty.add(x, y);
0727: dirty.add(x + w + 1, y + h + 1);
0728: }
0729: }
0730:
0731: /**
0732: * Request a repaint for the regions marked dirty since the last repaintDirty or
0733: * clearDirty calls. Note that the the dirty rectangle is shared among all
0734: * instances of DesignerPane so use for self contained contiguous blocks of code only.
0735: * @param force Has no effect if a dirty rectangle has been initialized, but if
0736: * dirty is null, it will force a full repaint if force is true, otherwise it
0737: * will do nothing.
0738: */
0739: public void repaintDirty(boolean force) {
0740: if (dirty != null) {
0741: if (DEBUG_REPAINT) {
0742: System.out
0743: .println("Requesting a repaint using a dirty region of "
0744: + dirty);
0745: }
0746:
0747: repaint(dirty);
0748: clearDirty();
0749: } else if (force) {
0750: repaint();
0751: }
0752: }
0753:
0754: /** Indicates whether the document is "empty",
0755: * A document is considered empty if it does not contain a body tag,
0756: * or if it contains any tags below the body which is not a <XXXform> tag
0757: * (typically <h:form>) or more than one form tag (<XXXform>).
0758: * Also, the form tag can not contain any other elements. */
0759: private static boolean isDocumentEmpty(/*Document doc*/Element body) {
0760: if (DesignerUtils.DEBUG) {
0761: DesignerUtils.debugLog(DesignerUtils.class.getName()
0762: + ".isDocumentEmpty(Document)");
0763: }
0764: // if(doc == null) {
0765: // throw(new IllegalArgumentException("Null document"));
0766: // }
0767: // TODO Better would be if document can tell whether it is modified or not (comparing
0768: // to the original template).
0769:
0770: // WebForm webform = doc.getWebForm();
0771:
0772: // RaveElement b = webform.getBody();
0773: // Element b = webform.getHtmlBody();
0774: Element b = body;
0775:
0776: if (b == null) {
0777: return true;
0778: }
0779:
0780: if (!b.getTagName().equals(HtmlTag.BODY.name)) {
0781: // Document fragments
0782: return false;
0783: }
0784:
0785: // XXX #6347037, when background is set, don't draw the text.
0786: // When the attribute is invalid, then the text is missing,
0787: // but seems to be better then the having text when there is a background.
0788: if (b.hasAttribute(HtmlAttribute.BACKGROUND)) {
0789: return false;
0790: }
0791: // Operate on the source DOM nodes since it's harder to know what kinds
0792: // of random junk will be rendered into the HTML... such as hidden
0793: // inputs etc.
0794: // if (b.isRendered()) {
0795: // b = b.getSource();
0796: // }
0797:
0798: // It is the rendered element (retrieved from #getHtmlBody).
0799: // if (MarkupService.isRenderedNode(b)) {
0800: b = MarkupService.getSourceElementForElement(b);
0801: // }
0802: // XXX Possible NPE (experienced with fragment).
0803: if (b == null) {
0804: return false;
0805: }
0806:
0807: NodeList list = b.getChildNodes();
0808: int len = list.getLength();
0809:
0810: int formsCount = 0;
0811:
0812: for (int i = 0; i < len; i++) {
0813: Node child = list.item(i);
0814:
0815: if (child.getNodeType() == org.w3c.dom.Node.ELEMENT_NODE) {
0816: Element form = (Element) child;
0817:
0818: String tag = form.getTagName();
0819:
0820: if (tag.equals(HtmlTag.BR.name)) {
0821: // Ok to have a couple of <br's> - we put those in ourselves
0822: continue;
0823: } else if (!tag.endsWith("form")) { // NOI18N
0824: return false;
0825: }
0826:
0827: // The tag is form:
0828: formsCount++;
0829: if (formsCount > 1) {
0830: // #6330716 When other form is added, the doc is considered non-empty.
0831: return false;
0832: }
0833:
0834: if (form.getChildNodes().getLength() != 0) {
0835: NodeList list2 = form.getChildNodes();
0836: int len2 = list2.getLength();
0837:
0838: int brs = 0;
0839:
0840: for (int j = 0; j < len2; j++) {
0841: Node child2 = list2.item(j);
0842:
0843: if (child2.getNodeType() == org.w3c.dom.Node.ELEMENT_NODE) {
0844: // Accept a couple of <br>'s since we put those there
0845: // ourselves
0846: if (HtmlTag.BR.name
0847: .equals(((Element) child2)
0848: .getTagName())) {
0849: brs++;
0850:
0851: if (brs > 2) {
0852: return false;
0853: }
0854: } else {
0855: return false;
0856: }
0857: } else if (child2.getNodeType() == org.w3c.dom.Node.TEXT_NODE) {
0858: if (!DesignerUtils.onlyWhitespace(child2
0859: .getNodeValue())) {
0860: return false;
0861: }
0862: }
0863: }
0864: }
0865: } else if (child.getNodeType() == org.w3c.dom.Node.TEXT_NODE) {
0866: if (!DesignerUtils.onlyWhitespace(child.getNodeValue())) {
0867: return false;
0868: }
0869: }
0870: }
0871:
0872: return true;
0873: }
0874:
0875: /** Adding listing of <code>CssBox</code>es to standard <codde>Component</code>s list.
0876: * @see java.awt.Container#list(java.io.PrintStream, int) */
0877: public void list(PrintStream out, int indent) {
0878: super .list(out, indent);
0879:
0880: PageBox pageBox = getPageBox();
0881: if (pageBox == null) {
0882: return;
0883: }
0884: pageBox.list(out, indent + 1);
0885: }
0886:
0887: /** Adding listing of <code>CssBox</code>es to standard <codde>Component</code>s list.
0888: * @see java.awt.Container#list(java.io.PrintWriter, int) */
0889: public void list(PrintWriter out, int indent) {
0890: super .list(out, indent);
0891:
0892: PageBox pageBox = getPageBox();
0893: if (pageBox == null) {
0894: return;
0895: }
0896: pageBox.list(out, indent + 1);
0897: }
0898:
0899: //// public void propertyChange(PropertyChangeEvent evt) {
0900: // public void preferenceChange(PreferenceChangeEvent evt) {
0901: //// if (DesignerSettings.PROP_SHOW_DECORATIONS.equals(evt.getPropertyName())) {
0902: // if (DesignerSettings.PROP_SHOW_DECORATIONS.equals(evt.getKey())) {
0903: // // XXX maybe better to have a copy of the value and in that case to reset it.
0904: // repaint();
0905: // }
0906: // }
0907:
0908: class DesignerDropHandler implements DropTargetListener {
0909: Component pane;
0910: Cursor org_cursor;
0911:
0912: public DesignerDropHandler(Component pane) {
0913: this .pane = pane;
0914: }
0915:
0916: public void dragEnter(DropTargetDragEvent dtde) {
0917: // XXX #6245208 To ignore CnC when DnD is in progress.
0918: // InteractionManager.setIgnoreCnC(true);
0919: webform.getManager().setIgnoreCnC(true);
0920:
0921: if (webform.isGridMode()) {
0922: ImageIcon imgIcon = new ImageIcon(
0923: this
0924: .getClass()
0925: .getResource(
0926: "/org/netbeans/modules/visualweb/designer/resources/drop_position.gif")); // TODO get marquee icon
0927: // StatusDisplayer_RAVE.getRaveDefault().setPositionLabelIcon(imgIcon);
0928: org_cursor = pane.getCursor();
0929: pane.setCursor(DragSource.DefaultCopyDrop);
0930: }
0931: }
0932:
0933: public void dragExit(DropTargetEvent dte) {
0934: // XXX #6245208 We can't differenciate between ESC and plain drag exit here.
0935: //// InteractionManager.setIgnoreCnC(false);
0936: // webform.getManager().setIgnoreCnC(false);
0937:
0938: pane.setCursor(org_cursor);
0939: dndHandler.clearDropMatch();
0940: }
0941:
0942: public void dragOver(DropTargetDragEvent dtde) {
0943: Point p = dtde.getLocation();
0944:
0945: // // Annoyingly we can't get the transferable out of the drag event
0946: // // (we need it to get the list of classnames being dropped so
0947: // // we can check if the drop is permitted etc.) so use a
0948: // // transferable registry hack instead:
0949: // Transferable transferable = DndHandler.getActiveTransferable();
0950: // #6457267.
0951: Transferable transferable = dtde.getTransferable();
0952:
0953: if (transferable != null) {
0954: try {
0955: int dropType = webform.getManager()
0956: .updateDropState(p, false, transferable);
0957:
0958: // TODO - consult the drop action too and see if applicable! Use
0959: // DndHandler.computeActions to compute bitmask to compare with
0960: if (dropType == DndHandler.DROP_DENIED) {
0961: dtde.rejectDrag();
0962: } else {
0963: // TODO - get the actual permitted actions used in dropType
0964: // computations and do bitwise compare with dtde.getDropAction()
0965: // such that we for example correctly indicate if you're
0966: // permitted to link if that's what you're trying to do.
0967: dtde.acceptDrag(dtde.getDropAction());
0968: }
0969: } catch (Throwable t) {
0970: log(t);
0971: }
0972: }
0973:
0974: //StatusDisplayer.getDefault().setStatusText("(" + p.x + ", " + p.y + ")");
0975: // TODO : compute the component position itself, not the mouse position
0976: // StatusDisplayer_RAVE.getRaveDefault().setPositionLabelText(p.x + "," + p.y);
0977: }
0978:
0979: public void drop(DropTargetDropEvent dtde) {
0980: // XXX #6245208 To ignore CnC.
0981: // InteractionManager.setIgnoreCnC(false);
0982: webform.getManager().setIgnoreCnC(false);
0983:
0984: // Grid mode: stash away the drag position for the transfer
0985: // handler
0986: // assert getTransferHandler() == dndHandler;
0987:
0988: // XXX #6482668 This has to be after updating the drop state.
0989: // dndHandler.clearDropMatch();
0990:
0991: // Determine if you're dropping on a grid area
0992: // or on a flow area
0993: Point ep = dtde.getLocation();
0994: // webform.getManager().updateDropState(ep, true, DndHandler.getActiveTransferable());
0995: // XXX #6457267. Revise this part.
0996: webform.getManager().updateDropState(ep, true, null);
0997:
0998: dndHandler.setDropAction(dtde.getDropAction());
0999:
1000: dndHandler.clearDropMatch();
1001:
1002: /*
1003: if (getTransferHandler() instanceof DndHandler) {
1004: DndHandler handler =
1005: (DndHandler)getTransferHandler();
1006: handler.setDropPoint(dtde.getLocation());
1007: }
1008: */
1009: // StatusDisplayer_RAVE.getRaveDefault().clearPositionLabel();
1010: }
1011:
1012: public void dropActionChanged(DropTargetDragEvent dtde) {
1013: }
1014: }
1015:
1016: /** Horrible hack necessary because I get notified of the escape key
1017: * twice: sometimes by my key handler above, sometimes by the default key
1018: * handler, and sometimes by both!
1019: * @todo This may no longer be an issue now that I'm using
1020: * a better input map (ANCESTOR_OF.... instead of FOCUSED_)
1021: */
1022: private long lastEscape = -1;
1023:
1024: private boolean seenEscape(long when) {
1025: if (lastEscape == when) {
1026: return true;
1027: }
1028:
1029: lastEscape = when;
1030:
1031: return false;
1032: }
1033:
1034: public void escape(long when) {
1035: // XXX Revise this handling, it is very suspicious.
1036: if (!seenEscape(when)) {
1037: // webform.performEscape();
1038: webform.getManager().getMouseHandler().escape();
1039: }
1040: }
1041:
1042: private abstract static class DesignerPaneAction extends
1043: AbstractAction {
1044: private final DesignerPane designerPane;
1045:
1046: public DesignerPaneAction(DesignerPane designerPane) {
1047: this .designerPane = designerPane;
1048: }
1049:
1050: protected DesignerPane getDesignerPane() {
1051: return designerPane;
1052: }
1053: } // End of JsfTopComponentAction.
1054:
1055: /** */
1056: private static class EscapeAction extends DesignerPaneAction {
1057: public EscapeAction(DesignerPane designerPane) {
1058: super (designerPane);
1059: }
1060:
1061: public void actionPerformed(ActionEvent evt) {
1062: getDesignerPane().escape(evt.getWhen());
1063: }
1064: } // End of EscapeAction.
1065:
1066: private static void fine(String message) {
1067: Logger logger = getLogger();
1068: logger.fine(message);
1069: }
1070:
1071: private static void log(Throwable throwable) {
1072: Logger logger = getLogger();
1073: logger.log(Level.INFO, null, throwable);
1074: }
1075:
1076: private static Logger getLogger() {
1077: return Logger.getLogger(DesignerPane.class.getName());
1078: }
1079: }
|