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.extension.openide.text;
043:
044: import java.awt.*;
045: import java.awt.event.*;
046: import java.awt.font.*;
047: import java.awt.datatransfer.*;
048: import java.awt.dnd.*;
049: import java.beans.*;
050: import java.io.*;
051: import java.net.*;
052: import javax.swing.*;
053: import javax.swing.plaf.*;
054: import javax.swing.text.*;
055: import javax.swing.event.*;
056: import javax.swing.border.Border;
057: import javax.swing.plaf.UIResource;
058: import javax.swing.Timer;
059:
060: // XXX Copied from previously located openide/src/../text/ dir, this is not a NB code.
061:
062: /* A drop listener for text components. This seems necessary because
063: * the NetBeans editor doesn't inherit from the Swing plaf.basic package,
064: * so it's missing a bunch of drag & drop behavior.
065: * In particular, this class is responsible for moving the caret
066: * to the nearest drag location in the document, as well as autoscrolling
067: * the view when necesary.
068: * <p>
069: * This code is basically a merged version of text-related code in
070: * javax.swing.plaf.basic: BasicTextUI's TextDropTargetListener and
071: * its parent class, BasickDropTargetListener.
072: * <p>
073: * I had to copy it since it has package protected access in
074: * javax.swing.plaf.basic.
075: *
076: * <p>
077: * @author Tor Norbye
078: */
079: class TextDropTargetListener implements DropTargetListener, UIResource,
080: ActionListener {
081:
082: // The first code is the general BasicDropTargetListener code; at the
083: // end you'll find the TextDropTargetListener code
084:
085: /**
086: * construct a DropTargetAutoScroller
087: * <P>
088: * @param c the <code>Component</code>
089: * @param p the <code>Point</code>
090: */
091: protected TextDropTargetListener() {
092: }
093:
094: /**
095: * Update the geometry of the autoscroll region. The geometry is
096: * maintained as a pair of rectangles. The region can cause
097: * a scroll if the pointer sits inside it for the duration of the
098: * timer. The region that causes the timer countdown is the area
099: * between the two rectangles.
100: * <p>
101: * This is implemented to use the visible area of the component
102: * as the outer rectangle and the insets are based upon the
103: * Scrollable information (if any). If the Scrollable is
104: * scrollable along an axis, the step increment is used as
105: * the autoscroll inset. If the component is not scrollable,
106: * the insets will be zero (i.e. autoscroll will not happen).
107: */
108: void updateAutoscrollRegion(JComponent c) {
109: // compute the outer
110: Rectangle visible = c.getVisibleRect();
111: outer.reshape(visible.x, visible.y, visible.width,
112: visible.height);
113:
114: // compute the insets
115: // TBD - the thing with the scrollable
116: Insets i = new Insets(0, 0, 0, 0);
117: if (c instanceof Scrollable) {
118: Scrollable s = (Scrollable) c;
119: i.left = s.getScrollableUnitIncrement(visible,
120: SwingConstants.HORIZONTAL, 1);
121: i.top = s.getScrollableUnitIncrement(visible,
122: SwingConstants.VERTICAL, 1);
123: i.right = s.getScrollableUnitIncrement(visible,
124: SwingConstants.HORIZONTAL, -1);
125: i.bottom = s.getScrollableUnitIncrement(visible,
126: SwingConstants.VERTICAL, -1);
127: }
128:
129: // set the inner from the insets
130: inner.reshape(visible.x + i.left, visible.y + i.top,
131: visible.width - (i.left + i.right), visible.height
132: - (i.top + i.bottom));
133: }
134:
135: /**
136: * Perform an autoscroll operation. This is implemented to scroll by the
137: * unit increment of the Scrollable using scrollRectToVisible. If the
138: * cursor is in a corner of the autoscroll region, more than one axis will
139: * scroll.
140: */
141: void autoscroll(JComponent c, Point pos) {
142: if (c instanceof Scrollable) {
143: Scrollable s = (Scrollable) c;
144: if (pos.y < inner.y) {
145: // scroll top downward
146: int dy = s.getScrollableUnitIncrement(outer,
147: SwingConstants.VERTICAL, 1);
148: Rectangle r = new Rectangle(inner.x, outer.y - dy,
149: inner.width, dy);
150: c.scrollRectToVisible(r);
151: } else if (pos.y > (inner.y + inner.height)) {
152: // scroll bottom upward
153: int dy = s.getScrollableUnitIncrement(outer,
154: SwingConstants.VERTICAL, -1);
155: Rectangle r = new Rectangle(inner.x, outer.y
156: + outer.height, inner.width, dy);
157: c.scrollRectToVisible(r);
158: }
159:
160: if (pos.x < inner.x) {
161: // scroll left side to the right
162: int dx = s.getScrollableUnitIncrement(outer,
163: SwingConstants.HORIZONTAL, 1);
164: Rectangle r = new Rectangle(outer.x - dx, inner.y, dx,
165: inner.height);
166: c.scrollRectToVisible(r);
167: } else if (pos.x > (inner.x + inner.width)) {
168: // scroll right side to the left
169: int dx = s.getScrollableUnitIncrement(outer,
170: SwingConstants.HORIZONTAL, -1);
171: Rectangle r = new Rectangle(outer.x + outer.width,
172: inner.y, dx, inner.height);
173: c.scrollRectToVisible(r);
174: }
175: }
176: }
177:
178: /**
179: * Initializes the internal properties if they haven't been already
180: * inited. This is done lazily to avoid loading of desktop properties.
181: */
182: private void initPropertiesIfNecessary() {
183: if (timer == null) {
184: Toolkit t = Toolkit.getDefaultToolkit();
185: Integer initial = new Integer(100);
186: Integer interval = new Integer(100);
187:
188: try {
189: initial = (Integer) t
190: .getDesktopProperty("DnD.Autoscroll.initialDelay");
191: } catch (Exception e) {
192: // ignore
193: }
194: try {
195: interval = (Integer) t
196: .getDesktopProperty("DnD.Autoscroll.interval");
197: } catch (Exception e) {
198: // ignore
199: }
200: timer = new Timer(interval.intValue(), this );
201:
202: timer.setCoalesce(true);
203: timer.setInitialDelay(initial.intValue());
204:
205: try {
206: hysteresis = ((Integer) t
207: .getDesktopProperty("DnD.Autoscroll.cursorHysteresis"))
208: .intValue();
209: } catch (Exception e) {
210: // ignore
211: }
212: }
213: }
214:
215: static JComponent getComponent(DropTargetEvent e) {
216: DropTargetContext context = e.getDropTargetContext();
217: return (JComponent) context.getComponent();
218: }
219:
220: // --- ActionListener methods --------------------------------------
221:
222: /**
223: * The timer fired, perform autoscroll if the pointer is within the
224: * autoscroll region.
225: * <P>
226: * @param e the <code>ActionEvent</code>
227: */
228: public synchronized void actionPerformed(ActionEvent e) {
229: updateAutoscrollRegion(component);
230: if (outer.contains(lastPosition)
231: && !inner.contains(lastPosition)) {
232: autoscroll(component, lastPosition);
233: }
234: }
235:
236: // --- DropTargetListener methods -----------------------------------
237:
238: public void dragEnter(DropTargetDragEvent e) {
239: component = getComponent(e);
240: TransferHandler th = component.getTransferHandler();
241: canImport = th.canImport(component, e.getCurrentDataFlavors());
242: if (canImport) {
243: saveComponentState(component);
244: lastPosition = e.getLocation();
245: updateAutoscrollRegion(component);
246: initPropertiesIfNecessary();
247: }
248: }
249:
250: public void dragOver(DropTargetDragEvent e) {
251: if (canImport) {
252: Point p = e.getLocation();
253: updateInsertionLocation(component, p);
254:
255: // check autoscroll
256: synchronized (this ) {
257: if (Math.abs(p.x - lastPosition.x) > hysteresis
258: || Math.abs(p.y - lastPosition.y) > hysteresis) {
259: // no autoscroll
260: if (timer.isRunning())
261: timer.stop();
262: } else {
263: if (!timer.isRunning())
264: timer.start();
265: }
266: lastPosition = p;
267: }
268: }
269: }
270:
271: public void dragExit(DropTargetEvent e) {
272: if (canImport) {
273: restoreComponentState(component);
274: }
275: cleanup();
276: }
277:
278: public void drop(DropTargetDropEvent e) {
279: if (canImport) {
280: restoreComponentStateForDrop(component);
281: }
282: cleanup();
283: }
284:
285: public void dropActionChanged(DropTargetDragEvent e) {
286: }
287:
288: /**
289: * Cleans up internal state after the drop has finished (either succeeded
290: * or failed).
291: */
292: private void cleanup() {
293: if (timer != null) {
294: timer.stop();
295: }
296: component = null;
297: lastPosition = null;
298: }
299:
300: // --- fields --------------------------------------------------
301:
302: private Timer timer;
303: private Point lastPosition;
304: private Rectangle outer = new Rectangle();
305: private Rectangle inner = new Rectangle();
306: private int hysteresis = 10;
307: private boolean canImport;
308:
309: /**
310: * The current component. The value is cached from the drop events and used
311: * by the timer. When a drag exits or a drop occurs, this value is cleared.
312: */
313: private JComponent component;
314:
315: // TEXT DROP LISTENER SPECIFIC STUFF - from BasicTextUI's
316: // TextDropListener
317:
318: /**
319: * called to save the state of a component in case it needs to
320: * be restored because a drop is not performed.
321: */
322: protected void saveComponentState(JComponent comp) {
323: JTextComponent c = (JTextComponent) comp;
324: Caret caret = c.getCaret();
325: dot = caret.getDot();
326: mark = caret.getMark();
327: visible = caret.isVisible();
328: caret.setVisible(true);
329: }
330:
331: /**
332: * called to restore the state of a component
333: * because a drop was not performed.
334: */
335: protected void restoreComponentState(JComponent comp) {
336: JTextComponent c = (JTextComponent) comp;
337: Caret caret = c.getCaret();
338: caret.setDot(mark);
339: caret.moveDot(dot);
340: caret.setVisible(visible);
341: }
342:
343: /**
344: * called to restore the state of a component
345: * because a drop was performed.
346: */
347: protected void restoreComponentStateForDrop(JComponent comp) {
348: JTextComponent c = (JTextComponent) comp;
349: Caret caret = c.getCaret();
350: caret.setVisible(visible);
351: }
352:
353: /**
354: * called to set the insertion location to match the current
355: * mouse pointer coordinates.
356: */
357: protected void updateInsertionLocation(JComponent comp, Point p) {
358: JTextComponent c = (JTextComponent) comp;
359: c.setCaretPosition(c.viewToModel(p));
360: }
361:
362: int dot;
363: int mark;
364: boolean visible;
365: }
|