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:
042: package org.netbeans.modules.visualweb.designer;
043:
044: import java.awt.Insets;
045: import java.awt.Point;
046: import java.awt.Rectangle;
047: import java.awt.Toolkit;
048: import java.awt.dnd.DropTargetContext;
049: import java.awt.dnd.DropTargetDragEvent;
050: import java.awt.dnd.DropTargetDropEvent;
051: import java.awt.dnd.DropTargetEvent;
052: import java.awt.dnd.DropTargetListener;
053: import java.awt.event.ActionEvent;
054: import java.awt.event.ActionListener;
055:
056: import javax.swing.JComponent;
057: import javax.swing.Scrollable;
058: import javax.swing.SwingConstants;
059: import javax.swing.Timer;
060: import javax.swing.TransferHandler;
061: import javax.swing.plaf.UIResource;
062:
063: /**
064: * COPIED from javax.swing.plaf.basic.BasicDropTargetListener
065: * because that copy is package private.
066: *
067: * <p>
068: * The Swing DropTarget implementation supports multicast notification
069: * to listeners, so this implementation is used as an additional
070: * listener that extends the primary drop target functionality
071: * (i.e. linkage to the TransferHandler) to include autoscroll and
072: * establish an insertion point for the drop. This is used by the ComponentUI
073: * of components supporting a selection mechanism, which have a
074: * way of indicating a location within their model.
075: * <p>
076: * The autoscroll functionality is based upon the Swing scrolling mechanism
077: * of the Scrollable interface. The unit scroll increment is used to as
078: * the scroll amount, and the scrolling is based upon JComponent.getVisibleRect
079: * and JComponent.scrollRectToVisible. The band of area around the visible
080: * rectangle used to invoke autoscroll is based upon the unit scroll increment
081: * as that is assumed to represent the last possible item in the visible region.
082: * <p>
083: * The subclasses are expected to implement the following methods to manage the
084: * insertion location via the components selection mechanism.
085: * <ul>
086: * <li>saveComponentState
087: * <li>restoreComponentState
088: * <li>restoreComponentStateForDrop
089: * <li>updateInsertionLocation
090: * </ul>
091: *
092: * @author Timothy Prinzing
093: * @version 1.8 01/23/03
094: */
095: class BasicDropTargetListener implements DropTargetListener,
096: UIResource, ActionListener {
097:
098: /**
099: * construct a DropTargetAutoScroller
100: * <P>
101: * @param c the <code>Component</code>
102: * @param p the <code>Point</code>
103: */
104: protected BasicDropTargetListener() {
105: }
106:
107: /**
108: * called to save the state of a component in case it needs to
109: * be restored because a drop is not performed.
110: */
111: protected void saveComponentState(JComponent c) {
112: }
113:
114: /**
115: * called to restore the state of a component in case a drop
116: * is not performed.
117: */
118: protected void restoreComponentState(JComponent c) {
119: }
120:
121: /**
122: * called to restore the state of a component in case a drop
123: * is performed.
124: */
125: protected void restoreComponentStateForDrop(JComponent c) {
126: }
127:
128: /**
129: * called to set the insertion location to match the current
130: * mouse pointer coordinates.
131: */
132: protected void updateInsertionLocation(JComponent c, Point p) {
133: }
134:
135: /**
136: * Update the geometry of the autoscroll region. The geometry is
137: * maintained as a pair of rectangles. The region can cause
138: * a scroll if the pointer sits inside it for the duration of the
139: * timer. The region that causes the timer countdown is the area
140: * between the two rectangles.
141: * <p>
142: * This is implemented to use the visible area of the component
143: * as the outer rectangle and the insets are based upon the
144: * Scrollable information (if any). If the Scrollable is
145: * scrollable along an axis, the step increment is used as
146: * the autoscroll inset. If the component is not scrollable,
147: * the insets will be zero (i.e. autoscroll will not happen).
148: */
149: void updateAutoscrollRegion(JComponent c) {
150: // compute the outer
151: Rectangle visible = c.getVisibleRect();
152: // outer.reshape(visible.x, visible.y, visible.width, visible.height);
153: outer.setBounds(visible.x, visible.y, visible.width,
154: visible.height);
155:
156: // compute the insets
157: // TBD - the thing with the scrollable
158: Insets i = new Insets(0, 0, 0, 0);
159: if (c instanceof Scrollable) {
160: Scrollable s = (Scrollable) c;
161: i.left = s.getScrollableUnitIncrement(visible,
162: SwingConstants.HORIZONTAL, 1);
163: i.top = s.getScrollableUnitIncrement(visible,
164: SwingConstants.VERTICAL, 1);
165: i.right = s.getScrollableUnitIncrement(visible,
166: SwingConstants.HORIZONTAL, -1);
167: i.bottom = s.getScrollableUnitIncrement(visible,
168: SwingConstants.VERTICAL, -1);
169: }
170:
171: // set the inner from the insets
172: // inner.reshape(visible.x + i.left,
173: // visible.y + i.top,
174: // visible.width - (i.left + i.right),
175: // visible.height - (i.top + i.bottom));
176: inner.setBounds(visible.x + i.left, visible.y + i.top,
177: visible.width - (i.left + i.right), visible.height
178: - (i.top + i.bottom));
179: }
180:
181: /**
182: * Perform an autoscroll operation. This is implemented to scroll by the
183: * unit increment of the Scrollable using scrollRectToVisible. If the
184: * cursor is in a corner of the autoscroll region, more than one axis will
185: * scroll.
186: */
187: void autoscroll(JComponent c, Point pos) {
188: if (c instanceof Scrollable) {
189: Scrollable s = (Scrollable) c;
190: if (pos.y < inner.y) {
191: // scroll top downward
192: int dy = s.getScrollableUnitIncrement(outer,
193: SwingConstants.VERTICAL, 1);
194: Rectangle r = new Rectangle(inner.x, outer.y - dy,
195: inner.width, dy);
196: c.scrollRectToVisible(r);
197: } else if (pos.y > (inner.y + inner.height)) {
198: // scroll bottom upward
199: int dy = s.getScrollableUnitIncrement(outer,
200: SwingConstants.VERTICAL, -1);
201: Rectangle r = new Rectangle(inner.x, outer.y
202: + outer.height, inner.width, dy);
203: c.scrollRectToVisible(r);
204: }
205:
206: if (pos.x < inner.x) {
207: // scroll left side to the right
208: int dx = s.getScrollableUnitIncrement(outer,
209: SwingConstants.HORIZONTAL, 1);
210: Rectangle r = new Rectangle(outer.x - dx, inner.y, dx,
211: inner.height);
212: c.scrollRectToVisible(r);
213: } else if (pos.x > (inner.x + inner.width)) {
214: // scroll right side to the left
215: int dx = s.getScrollableUnitIncrement(outer,
216: SwingConstants.HORIZONTAL, -1);
217: Rectangle r = new Rectangle(outer.x + outer.width,
218: inner.y, dx, inner.height);
219: c.scrollRectToVisible(r);
220: }
221: }
222: }
223:
224: /**
225: * Initializes the internal properties if they haven't been already
226: * inited. This is done lazily to avoid loading of desktop properties.
227: */
228: private void initPropertiesIfNecessary() {
229: if (timer == null) {
230: Toolkit t = Toolkit.getDefaultToolkit();
231:
232: Integer initial = (Integer) t
233: .getDesktopProperty("DnD.Autoscroll.initialDelay");
234: if (initial == null) {
235: initial = new Integer(100);
236: }
237: Integer interval = (Integer) t
238: .getDesktopProperty("DnD.Autoscroll.interval");
239: if (interval == null) {
240: interval = new Integer(100);
241: }
242:
243: timer = new Timer(interval.intValue(), this );
244:
245: timer.setCoalesce(true);
246: timer.setInitialDelay(initial.intValue());
247:
248: // Integer h = (Integer)t.getDesktopProperty("DnD.Autoscroll.cursorHysteresis");
249: // hysteresis = h == null ? 10 : h.intValue();
250: }
251: }
252:
253: static JComponent getComponent(DropTargetEvent e) {
254: DropTargetContext context = e.getDropTargetContext();
255: return (JComponent) context.getComponent();
256: }
257:
258: // --- ActionListener methods --------------------------------------
259:
260: /**
261: * The timer fired, perform autoscroll if the pointer is within the
262: * autoscroll region.
263: * <P>
264: * @param e the <code>ActionEvent</code>
265: */
266: public synchronized void actionPerformed(ActionEvent e) {
267: updateAutoscrollRegion(component);
268: if (outer.contains(lastPosition)
269: && !inner.contains(lastPosition)) {
270: autoscroll(component, lastPosition);
271: }
272: }
273:
274: // --- DropTargetListener methods -----------------------------------
275:
276: public void dragEnter(DropTargetDragEvent e) {
277: component = getComponent(e);
278: TransferHandler th = component.getTransferHandler();
279: if (th instanceof DesignerTransferHandler) {
280: // XXX #99457 Internal enhanced TransferHandler to provide more fine-grained decision (based on Transferable as well).
281: canImport = ((DesignerTransferHandler) th).canImport(
282: component, e.getCurrentDataFlavors(), e
283: .getTransferable());
284: } else {
285: canImport = th.canImport(component, e
286: .getCurrentDataFlavors());
287: }
288: if (canImport) {
289: saveComponentState(component);
290: lastPosition = e.getLocation();
291: updateAutoscrollRegion(component);
292: initPropertiesIfNecessary();
293: }
294: }
295:
296: public void dragOver(DropTargetDragEvent e) {
297: /* Perhaps I need to add autoscrolling myself now....
298: if (canImport) {
299: Point p = e.getLocation();
300: updateInsertionLocation(component, p);
301:
302: // check autoscroll
303: synchronized(this) {
304: if (Math.abs(p.x - lastPosition.x) > hysteresis ||
305: Math.abs(p.y - lastPosition.y) > hysteresis) {
306: // no autoscroll
307: if (timer.isRunning()) timer.stop();
308: } else {
309: if (!timer.isRunning()) timer.start();
310: }
311: lastPosition = p;
312: }
313: }
314: */
315: }
316:
317: public void dragExit(DropTargetEvent e) {
318: if (canImport && component != null) {
319: restoreComponentState(component);
320: }
321: cleanup();
322: }
323:
324: public void drop(DropTargetDropEvent e) {
325: if (canImport) {
326: restoreComponentStateForDrop(component);
327: }
328: cleanup();
329: }
330:
331: public void dropActionChanged(DropTargetDragEvent e) {
332: }
333:
334: /**
335: * Cleans up internal state after the drop has finished (either succeeded
336: * or failed).
337: */
338: private void cleanup() {
339: if (timer != null) {
340: timer.stop();
341: }
342: component = null;
343: lastPosition = null;
344: }
345:
346: // --- fields --------------------------------------------------
347:
348: private Timer timer;
349: private Point lastPosition;
350: private Rectangle outer = new Rectangle();
351: private Rectangle inner = new Rectangle();
352: // private int hysteresis = 10;
353: private boolean canImport;
354:
355: /**
356: * The current component. The value is cached from the drop events and used
357: * by the timer. When a drag exits or a drop occurs, this value is cleared.
358: */
359: private JComponent component;
360:
361: }
|