001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041: package org.netbeans.modules.visualweb.designer;
042:
043: import java.util.Arrays;
044: import org.netbeans.modules.visualweb.api.designer.DomProviderService.ResizeConstraint;
045: import java.awt.Color;
046: import java.awt.Cursor;
047: import java.awt.Graphics;
048: import java.awt.Insets;
049: import java.awt.Point;
050: import java.awt.Rectangle;
051: import java.awt.Shape;
052: import java.awt.event.InputEvent;
053: import java.awt.event.KeyEvent;
054: import java.awt.event.KeyListener;
055: import java.awt.event.MouseEvent;
056:
057: import javax.swing.ImageIcon;
058:
059: import org.w3c.dom.Element;
060:
061: import org.netbeans.modules.visualweb.css2.CssBox;
062: import org.netbeans.modules.visualweb.designer.html.HtmlAttribute;
063: import org.netbeans.modules.visualweb.designer.html.HtmlTag;
064:
065: /**
066: * Handle drawing (and "undrawing"!) a resize outline, as well as
067: * communicating a resize event to the GridHandler on completion
068: *
069: * @todo For aspect-preserving resizing, rather than basing the
070: * proportion on the original size of the box when resizing begins,
071: * base it on the -intrinsic- size of the box. For an image for
072: * example, that should be the size of the image. That will avoid
073: * having repeated resizings of the image (resulting in rounding
074: * errors) gradually breaking the correct aspect of the image.
075: * It will also "restore" the aspect if it has been non-aspect
076: * resized in the other dimensions.
077: *
078: * @author Tor Norbye
079: */
080: public class Resizer extends Interaction implements KeyListener {
081: /* Restore previous cursor after operation. */
082: protected transient Cursor previousCursor = null;
083: private WebForm webform;
084: private boolean snapDisabled = false;
085: // private MarkupDesignBean component;
086: private Element componentRootElement;
087: private int origW = 0;
088: private int origH = 0;
089: private int origX = 0;
090: private int origY = 0;
091: private int direction = 0;
092: private int prevX = -500;
093: private int prevY = -500;
094: private int prevMouseX = -500;
095: private int prevMouseY = -500;
096: private boolean preserveAspect;
097: private final CssBox box;
098:
099: /** Current size of the rectangle being resized. */
100: private Rectangle currentSize = null;
101:
102: /**
103: * Create a resizer which tracks resize mouse operations.
104: * @param element The element to be resized.
105: * @param direction The direction to resize the view.
106: * @param alloc The original shape of the view/component being resized
107: * @param preserveAspect Whether to preserve aspect on two-dimension resizing
108: * (e.g. dragging the corners - NE, NW, SE, SW)
109: */
110: public Resizer(WebForm webform, Element componentRootElement, /*MarkupDesignBean component,*/
111: CssBox box, int direction, Shape alloc,
112: boolean preserveAspect) {
113: this .webform = webform;
114: // this.component = component;
115: this .componentRootElement = componentRootElement;
116: this .direction = direction;
117: this .box = box;
118:
119: Rectangle r = (alloc instanceof Rectangle) ? (Rectangle) alloc
120: : alloc.getBounds();
121:
122: // We copy out the attributes since I've got a really bad experience
123: // with allocations getting mutated by other code in the View code.
124: // (For performance reasons they're probably reusing the same Rectangles
125: // over and over.)
126: origX = r.x;
127: origY = r.y;
128: origW = r.width;
129: origH = r.height;
130:
131: // Handle <img> tags. All components that render to a top-level
132: // <img> tag should be resized proportionally. We can't easily
133: // do this with metadata in all cases since for example
134: // a button should have aspect-preserving resizing only when it's
135: // an image button
136: // RaveElement element = (RaveElement)component.getElement();
137: // RaveElement rendered = element.getRendered();
138: // Element element = component.getElement();
139: // Element rendered = MarkupService.getRenderedElementForElement(element);
140: //
141: // if (rendered != null) {
142: // element = rendered;
143: // }
144: Element element = componentRootElement;
145:
146: String tagName = element.getTagName();
147:
148: if (HtmlTag.IMG.name.equals(tagName)
149: || (HtmlTag.INPUT.name.equals(tagName) && element
150: .getAttribute(HtmlAttribute.TYPE).equals(
151: "image"))) { // NOI18N
152: preserveAspect = true;
153: }
154:
155: this .preserveAspect = preserveAspect;
156:
157: if ((origW == 0) || (origH == 0)) {
158: this .preserveAspect = false;
159: }
160: }
161:
162: /** Cancel operation */
163: public void cancel(DesignerPane pane) {
164: pane.removeKeyListener(this );
165: cleanup(pane);
166: currentSize = null;
167: }
168:
169: private void cleanup(DesignerPane pane) {
170: // Restore the cursor to normal
171: pane.setCursor(previousCursor);
172:
173: // Restore status line
174: // StatusDisplayer_RAVE.getRaveDefault().clearPositionLabel();
175:
176: // Clear
177: if (prevX != -500) {
178: Rectangle dirty = new Rectangle();
179: resize(dirty, prevX, prevY);
180: dirty.width++;
181: dirty.height++;
182: pane.repaint(dirty);
183: }
184: }
185:
186: /** When the mouse press is released, get rid of the drawn resizer,
187: * and ask the selection manager to select all the components contained
188: * within the resizer bounds.
189: */
190: public void mouseReleased(MouseEvent e) {
191: try {
192: if ((e != null) && !e.isConsumed()) {
193: updateSnapState(e);
194:
195: Point p = e.getPoint();
196: DesignerPane pane = webform.getPane();
197: pane.removeKeyListener(this );
198:
199: // GridHandler gm = GridHandler.getInstance();
200: // GridHandler gm = webform.getGridHandler();
201: Rectangle r = new Rectangle();
202:
203: int x;
204: int y;
205:
206: if (snapDisabled) {
207: x = p.x;
208: y = p.y;
209: } else {
210: // GridHandler gm = GridHandler.getDefault();
211: // x = gm.snapX(p.x, box.getPositionedBy());
212: // y = gm.snapY(p.y, box.getPositionedBy());
213: x = webform.snapX(p.x, box.getPositionedBy());
214: y = webform.snapY(p.y, box.getPositionedBy());
215: }
216:
217: resize(r, x, y);
218:
219: // if ((component != null) && (r.width > 0) && (r.height > 0)) {
220: if ((componentRootElement != null) && (r.width > 0)
221: && (r.height > 0)) {
222: boolean resizeHorizontally = origW != r.width;
223: boolean resizeVertically = origH != r.height;
224:
225: // The width/height used in the Resizer includes the size of the borders.
226: // However, the width we set on the component should only be the
227: // size of the content box (see CSS box model).
228: Insets insets = box.getCssSizeInsets();
229: r.width -= insets.left;
230: r.width -= insets.right;
231: r.height -= insets.top;
232: r.height -= insets.bottom;
233:
234: if (!preserveAspect
235: && ((direction == Cursor.S_RESIZE_CURSOR) || (direction == Cursor.N_RESIZE_CURSOR))) {
236: resizeHorizontally = false;
237: }
238:
239: if (!preserveAspect
240: && ((direction == Cursor.E_RESIZE_CURSOR) || (direction == Cursor.W_RESIZE_CURSOR))) {
241: resizeVertically = false;
242: }
243:
244: // gm.resize(pane, component, r.x, origX != r.x, r.y, origY != r.y, r.width,
245: // resizeHorizontally, r.height, resizeVertically, box, snapDisabled);
246: // gm.resize(pane, componentRootElement, r.x, origX != r.x, r.y, origY != r.y, r.width,
247: // resizeHorizontally, r.height, resizeVertically, box, snapDisabled);
248: webform.getDomDocument().resizeComponent(webform,
249: componentRootElement, r.x, origX != r.x,
250: r.y, origY != r.y, r.width,
251: resizeHorizontally, r.height,
252: resizeVertically, box, !snapDisabled);
253: }
254:
255: /*
256: TODO:
257: We should have cursor feedback (error-cursor)
258: showing that releasing the cursor will not
259: resize the rectangle to a negative/zero length
260: */
261: cleanup(pane);
262:
263: e.consume();
264: }
265: } finally {
266: // component = null;
267: componentRootElement = null;
268: previousCursor = null;
269: currentSize = null;
270: }
271: }
272:
273: /**
274: * Moves the dragging rectangles
275: */
276: public void mouseDragged(MouseEvent e) {
277: Point p = e.getPoint();
278: prevMouseX = p.x;
279: prevMouseY = p.y;
280: update(e, p.x, p.y);
281: webform.getPane().scrollRectToVisible(new Rectangle(p));
282: }
283:
284: /** Set the given rectangle to contain the new bounds for
285: * the view (originally anchored at origX,origY,origW,origH) constrained
286: * by resize direction to the new coordinate x,y. */
287: private void resize(Rectangle r, int x, int y) {
288: switch (direction) {
289: case Cursor.S_RESIZE_CURSOR:
290: r.x = origX;
291: r.y = origY;
292: r.width = origW;
293: r.height = (y - origY);
294:
295: break;
296:
297: case Cursor.E_RESIZE_CURSOR:
298: r.x = origX;
299: r.y = origY;
300: r.width = (x - origX);
301: r.height = origH;
302:
303: break;
304:
305: case Cursor.SE_RESIZE_CURSOR:
306:
307: if (preserveAspect) {
308: r.x = origX;
309: r.y = origY;
310:
311: // We know origW and origH is nonzero when preserveAspect is set
312: // (enforced in constructor)
313: int dx = origW;
314: int dy = origH;
315:
316: // Signed distance from x,y to the line (origX,origY,origW,origH)
317: // (((x - origX) * dy) - ((y - origY) * dx)) / sqrt(dx*dx+dy*dy)
318: // However, I only care about the sign of the distance to determine
319: // if I should use x or y as the chosen dimension to assign
320: // (and compute the aspect ratio for the other dimension)
321: int x0 = origX;
322: int y0 = origY;
323: int d = ((x - x0) * dy) - ((y - y0) * dx);
324:
325: if (d > 0) {
326: r.width = x - origX;
327: r.height = (r.width * origH) / origW;
328: } else {
329: r.height = y - origY;
330: r.width = (r.height * origW) / origH;
331: }
332: } else {
333: r.x = origX;
334: r.y = origY;
335: r.width = (x - origX);
336: r.height = (y - origY);
337: }
338:
339: break;
340:
341: case Cursor.W_RESIZE_CURSOR:
342: r.x = x;
343: r.y = origY;
344: r.width = (origX - x) + origW;
345: r.height = origH;
346:
347: break;
348:
349: case Cursor.SW_RESIZE_CURSOR:
350:
351: if (preserveAspect) {
352: int dx = origW;
353: int dy = -origH;
354: int x0 = origX;
355: int y0 = origY + origH;
356: int d = ((x - x0) * dy) - ((y - y0) * dx);
357:
358: if (d > 0) {
359: r.width = origX - x + origW;
360: r.height = (r.width * origH) / origW;
361: } else {
362: r.height = y - origY;
363: r.width = (r.height * origW) / origH;
364: }
365:
366: r.x = (origX + origW) - r.width;
367: r.y = origY;
368: } else {
369: r.x = x;
370: r.y = origY;
371: r.width = (origX - x) + origW;
372: r.height = (y - origY);
373: }
374:
375: break;
376:
377: case Cursor.N_RESIZE_CURSOR:
378: r.x = origX;
379: r.y = y;
380: r.width = origW;
381: r.height = (origY - y) + origH;
382:
383: break;
384:
385: case Cursor.NW_RESIZE_CURSOR:
386:
387: if (preserveAspect) {
388: int dx = origW;
389: int dy = origH;
390: int x0 = origX;
391: int y0 = origY;
392: int d = ((x - x0) * dy) - ((y - y0) * dx);
393:
394: if (d < 0) {
395: r.width = origX - x + origW;
396: r.height = (r.width * origH) / origW;
397: } else {
398: r.height = origY - y + origH;
399: r.width = (r.height * origW) / origH;
400: }
401:
402: r.x = (origX + origW) - r.width;
403: r.y = (origY + origH) - r.height;
404: } else {
405: r.x = x;
406: r.y = y;
407: r.width = (origX - x) + origW;
408: r.height = (origY - y) + origH;
409: }
410:
411: break;
412:
413: case Cursor.NE_RESIZE_CURSOR:
414:
415: if (preserveAspect) {
416: int dx = origW;
417: int dy = -origH;
418: int x0 = origX;
419: int y0 = origY + origH;
420: int d = ((x - x0) * dy) - ((y - y0) * dx);
421:
422: if (d < 0) {
423: r.width = x - origX;
424: r.height = (r.width * origH) / origW;
425: } else {
426: r.height = origY - y + origH;
427: r.width = (r.height * origW) / origH;
428: }
429:
430: r.x = origX;
431: r.y = (origY + origH) - r.height;
432: } else {
433: r.x = origX;
434: r.y = y;
435: r.width = (x - origX);
436: r.height = (origY - y) + origH;
437: }
438:
439: break;
440:
441: default:
442: Thread.dumpStack();
443: }
444: }
445:
446: /** Draw the resize rectangle */
447: public void paint(Graphics g) {
448: if (currentSize != null) {
449: if (DesignerPane.useAlpha) {
450: g.setColor(webform.getColors().resizerColor);
451: g.fillRect(currentSize.x + 1, currentSize.y + 1,
452: currentSize.width - 1, currentSize.height - 1);
453: g.setColor(webform.getColors().resizerColorBorder);
454: } else {
455: g.setColor(Color.BLACK);
456: }
457:
458: g.drawRect(currentSize.x, currentSize.y, currentSize.width,
459: currentSize.height);
460: }
461: }
462:
463: /**
464: * Start the resizer by setting the dragging cursor and
465: * drawing dragging rectangles.
466: */
467: public void mousePressed(MouseEvent e) {
468: if (!e.isConsumed()) {
469: updateSnapState(e);
470:
471: Point p = e.getPoint();
472: prevMouseX = p.x;
473: prevMouseY = p.y;
474:
475: DesignerPane pane = webform.getPane();
476: pane.addKeyListener(this );
477:
478: previousCursor = pane.getCursor();
479: pane.setCursor(Cursor.getPredefinedCursor(direction));
480:
481: ImageIcon imgIcon = new ImageIcon(
482: Resizer.class
483: .getResource("/org/netbeans/modules/visualweb/designer/resources/drag_resize.gif"));
484: // StatusDisplayer_RAVE.getRaveDefault().setPositionLabelIcon(imgIcon);
485:
486: e.consume();
487: }
488: }
489:
490: private void updateSnapState(InputEvent e) {
491: snapDisabled = e.isShiftDown();
492: }
493:
494: private void update(InputEvent e, int px, int py) {
495: if (!e.isConsumed()) {
496: updateSnapState(e);
497:
498: DesignerPane pane = webform.getPane();
499:
500: Rectangle dirty;
501:
502: if (currentSize != null) {
503: dirty = currentSize;
504: dirty.width++;
505: dirty.height++;
506: } else {
507: dirty = new Rectangle();
508: }
509:
510: // GridHandler gm = GridHandler.getInstance();
511: // GridHandler gm = webform.getGridHandler();
512:
513: if (snapDisabled) {
514: prevX = px;
515: prevY = py;
516: } else {
517: // GridHandler gm = GridHandler.getDefault();
518: // prevX = gm.snapX(px, box.getPositionedBy());
519: // prevY = gm.snapY(py, box.getPositionedBy());
520: prevX = webform.snapX(px, box.getPositionedBy());
521: prevY = webform.snapY(py, box.getPositionedBy());
522: }
523:
524: currentSize = new Rectangle();
525: resize(currentSize, prevX, prevY);
526: dirty.add(currentSize.x, currentSize.y);
527: dirty.add(currentSize.x + currentSize.width, currentSize.y
528: + currentSize.height);
529: dirty.width++;
530: dirty.height++;
531: pane.repaint(dirty);
532:
533: int w = currentSize.width;
534: int h = currentSize.height;
535:
536: if (w < 0) {
537: w = 0;
538: }
539:
540: if (h < 0) {
541: h = 0;
542: }
543:
544: // StatusDisplayer_RAVE.getRaveDefault().setPositionLabelText(w + "," + h);
545:
546: e.consume();
547: }
548: }
549:
550: // --- implements KeyListener ---
551: public void keyPressed(KeyEvent e) {
552: if (snapDisabled != e.isShiftDown()) {
553: update(e, prevMouseX, prevMouseY);
554: }
555: }
556:
557: public void keyReleased(KeyEvent e) {
558: if (snapDisabled != e.isShiftDown()) {
559: update(e, prevMouseX, prevMouseY);
560: }
561: }
562:
563: public void keyTyped(KeyEvent e) {
564: }
565:
566: /** Look up the resize constraints for the given component */
567: public static/*int*/ResizeConstraint[] getResizeConstraints(
568: WebForm webForm, /*MarkupDesignBean component*/
569: Element componentRootElement) {
570: // Element element = component.getElement();
571: Element element = componentRootElement;
572:
573: if (element != null) {
574: // CssBox box = CssBox.getBox(element);
575: CssBox box = webForm.findCssBoxForElement(element);
576:
577: // Non-replaced inline formatted components are not resizable!
578: // See CSS2.1 spec, section 10.2
579: if ((box != null) && box.isInlineBox()
580: && !box.isReplacedBox()
581: && box.getBoxType().isNormalFlow()) {
582: // return Constants.ResizeConstraints.NONE;
583: return new ResizeConstraint[0];
584: }
585: }
586:
587: // int constraints = Constants.ResizeConstraints.ANY;
588: //
589: // // Special case: The Jsp Include box is not resizable.
590: // // If I build a BeanDescriptor for it I can inject
591: // // this value right on it, but I also want to make it
592: // // as NOT POSITIONABLE.
593: // if (component.getInstance() instanceof Jsp_Directive_Include) {
594: // return Constants.ResizeConstraints.NONE;
595: // }
596: //
597: // BeanInfo bi = component.getBeanInfo();
598: //
599: // if (bi != null) {
600: // BeanDescriptor bd = bi.getBeanDescriptor();
601: // Object o = bd.getValue(Constants.BeanDescriptor.RESIZE_CONSTRAINTS);
602: //
603: // if ((o != null) && o instanceof Integer) {
604: // constraints = ((Integer)o).intValue();
605: // }
606: // }
607: //
608: // return constraints;
609: return webForm.getDomProviderService()
610: .getResizeConstraintsForComponent(componentRootElement);
611: }
612:
613: public static boolean hasMaintainAspectRatioResizeConstraint(
614: ResizeConstraint[] resizeConstraints) {
615: if (resizeConstraints == null) {
616: return false;
617: }
618: return Arrays.asList(resizeConstraints).contains(
619: ResizeConstraint.MAINTAIN_ASPECT_RATIO);
620: }
621:
622: public static boolean hasTopResizeConstraint(
623: ResizeConstraint[] resizeConstraints) {
624: if (resizeConstraints == null) {
625: return false;
626: }
627: return Arrays.asList(resizeConstraints).contains(
628: ResizeConstraint.TOP);
629: }
630:
631: public static boolean hasLeftResizeConstraint(
632: ResizeConstraint[] resizeConstraints) {
633: if (resizeConstraints == null) {
634: return false;
635: }
636: return Arrays.asList(resizeConstraints).contains(
637: ResizeConstraint.LEFT);
638: }
639:
640: public static boolean hasRightResizeConstraint(
641: ResizeConstraint[] resizeConstraints) {
642: if (resizeConstraints == null) {
643: return false;
644: }
645: return Arrays.asList(resizeConstraints).contains(
646: ResizeConstraint.RIGHT);
647: }
648:
649: public static boolean hasBottomResizeConstraint(
650: ResizeConstraint[] resizeConstraints) {
651: if (resizeConstraints == null) {
652: return false;
653: }
654: return Arrays.asList(resizeConstraints).contains(
655: ResizeConstraint.BOTTOM);
656: }
657:
658: }
|